Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
### Fixed

- Fix version numbers without `.` failing to match open tickets, resulting in duplicates
- Dependicus no longer sets due dates that are in the past for overdue dependencies. When a calculated due date falls before today, the issue is created without a due date instead.

### Removed

Expand Down
52 changes: 43 additions & 9 deletions src/core/utils/versionUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,28 +426,62 @@ describe('calculateDueDate', () => {
});

it('calculates due date from first required version publish date', () => {
const now = new Date('2024-01-01');
const versions = [makeVersion('2.0.0', '2024-06-15'), makeVersion('2.0.1', '2024-07-01')];

const dueDate = calculateDueDate('1.0.0', versions, 'major', 360, '2023-01-01');
const dueDate = calculateDueDate('1.0.0', versions, 'major', 360, '2023-01-01', now);

expect(dueDate.getFullYear()).toBe(2025);
expect(dueDate.getMonth()).toBe(5); // June is 5 (0-indexed)
expect(dueDate?.getFullYear()).toBe(2025);
expect(dueDate?.getMonth()).toBe(5); // June is 5 (0-indexed)
});

it('falls back to fallback publish date if no versions between', () => {
const dueDate = calculateDueDate('1.0.0', [], 'major', 360, '2024-01-15');
const now = new Date('2024-01-01');
const dueDate = calculateDueDate('1.0.0', [], 'major', 360, '2024-01-15', now);

expect(dueDate.getFullYear()).toBe(2025);
expect(dueDate.getMonth()).toBe(0); // January
expect(dueDate?.getFullYear()).toBe(2025);
expect(dueDate?.getMonth()).toBe(0); // January
});

it('uses correct threshold for different update types', () => {
const now = new Date('2024-01-01');
const versions = [makeVersion('1.1.0', '2024-06-15')];

const dueDate = calculateDueDate('1.0.0', versions, 'minor', 180, '2024-01-01');
const dueDate = calculateDueDate('1.0.0', versions, 'minor', 180, '2024-01-01', now);

expect(dueDate.getFullYear()).toBe(2024);
expect(dueDate.getMonth()).toBe(11); // December
expect(dueDate?.getFullYear()).toBe(2024);
expect(dueDate?.getMonth()).toBe(11); // December
});

it('returns undefined when calculated due date is in the past', () => {
const now = new Date('2026-07-06');
const versions = [makeVersion('2.0.0', '2023-07-24')];

// Due date would be 2023-07-24 + 180 days = 2024-01-20 (in the past)
const dueDate = calculateDueDate('1.0.0', versions, 'major', 180, undefined, now);

expect(dueDate).toBeUndefined();
});

it('returns the due date when calculated due date is in the future', () => {
const now = new Date('2024-01-01');
const versions = [makeVersion('2.0.0', '2024-06-15')];

// Due date would be 2024-06-15 + 180 days = 2024-12-12 (in the future)
const dueDate = calculateDueDate('1.0.0', versions, 'major', 180, undefined, now);

expect(dueDate).toBeDefined();
expect(dueDate?.getFullYear()).toBe(2024);
expect(dueDate?.getMonth()).toBe(11); // December
});

it('returns undefined when fallback publish date results in past due date', () => {
const now = new Date('2026-07-06');

// Using fallback date from 2 years ago with 180 day threshold = past due date
const dueDate = calculateDueDate('1.0.0', [], 'major', 180, '2023-01-15', now);

expect(dueDate).toBeUndefined();
});
});

Expand Down
11 changes: 10 additions & 1 deletion src/core/utils/versionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,14 +293,18 @@ export function findFirstVersionOfType(
*
* If no versions are found between current and latest, falls back to
* the provided fallbackPublishDate.
*
* Returns undefined if the calculated due date is in the past, as there's
* no value in setting a past due date for already-overdue dependencies.
*/
export function calculateDueDate(
currentVersion: string,
versionsBetween: readonly PackageVersionInfo[],
updateType: 'major' | 'minor' | 'patch',
thresholdDays: number,
fallbackPublishDate: string | undefined,
): Date {
now: Date = new Date(),
): Date | undefined {
const firstVersion = findFirstVersionOfType(currentVersion, versionsBetween, updateType);

const availableDate = firstVersion?.publishDate
Expand All @@ -312,6 +316,11 @@ export function calculateDueDate(
const dueDate = new Date(availableDate);
dueDate.setDate(dueDate.getDate() + thresholdDays);

// Don't set past due dates for already-overdue dependencies
if (dueDate < now) {
return undefined;
}

return dueDate;
}

Expand Down
52 changes: 51 additions & 1 deletion src/github-issues/issueReconciler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,23 @@ describe('reconcileGitHubIssues', () => {
setupMocks();

const deps: DirectDependency[] = [
{ name: 'test-pkg', ecosystem: 'npm', versions: [makeVersion()] },
{
name: 'test-pkg',
ecosystem: 'npm',
versions: [makeVersion({ publishDate: '2025-08-01' })],
},
];
const store = makeStore();
// Update the versions between to have a recent major version
const scoped = store.scoped('npm');
scoped.setVersionFact('test-pkg', '1.0.0', FactKeys.VERSIONS_BETWEEN, [
{
version: '2.0.0',
publishDate: '2025-08-01',
isPrerelease: false,
registryUrl: 'https://www.npmjs.com/package/test-pkg/v/2.0.0',
},
]);

const result = await reconcileGitHubIssues(deps, store, baseConfig, () =>
makeSpec({
Expand All @@ -311,6 +325,42 @@ describe('reconcileGitHubIssues', () => {
expect(createCall.title).toContain('(due ');
});

it('does not append past due dates to title for dueDate policy', async () => {
setupMocks();

const deps: DirectDependency[] = [
{
name: 'test-pkg',
ecosystem: 'npm',
versions: [makeVersion({ publishDate: '2023-01-01' })],
},
];
const store = makeStore();
// Update the versions between to have an old major version
const scoped = store.scoped('npm');
scoped.setVersionFact('test-pkg', '1.0.0', FactKeys.VERSIONS_BETWEEN, [
{
version: '2.0.0',
publishDate: '2023-01-01',
isPrerelease: false,
registryUrl: 'https://www.npmjs.com/package/test-pkg/v/2.0.0',
},
]);

const result = await reconcileGitHubIssues(deps, store, baseConfig, () =>
makeSpec({
policy: { type: 'dueDate' },
daysOverdue: 10,
thresholdDays: 360,
}),
);

expect(result.created).toBe(1);
const createCall = mockOctokit.issues.create.mock.calls[0]![0];
// Should not contain due date since it would be in the past
expect(createCall.title).not.toContain('(due ');
});

it('includes assignees when assignment is assign type', async () => {
setupMocks();

Expand Down
2 changes: 1 addition & 1 deletion src/github-issues/issueReconciler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,7 @@ export async function reconcileGitHubIssues(
dep.worstCompliance.thresholdDays ?? group.worstCompliance.thresholdDays ?? 0,
version.publishDate,
);
if (!earliestDueDate || depDueDate < earliestDueDate) {
if (depDueDate && (!earliestDueDate || depDueDate < earliestDueDate)) {
earliestDueDate = depDueDate;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/linear/issueReconciler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ export async function reconcileIssues(
dep.worstCompliance.thresholdDays ?? group.worstCompliance.thresholdDays ?? 0,
version.publishDate,
);
if (!earliestDueDate || depDueDate < earliestDueDate) {
if (depDueDate && (!earliestDueDate || depDueDate < earliestDueDate)) {
earliestDueDate = depDueDate;
}
}
Expand Down
Loading
Loading