Skip to content

Commit b4283ae

Browse files
fix(cooldown): support newest, minor, patch, and semver targets (#1553)
1 parent 90fc55d commit b4283ae

File tree

2 files changed

+187
-13
lines changed

2 files changed

+187
-13
lines changed

src/package-managers/npm.ts

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { SpawnPleaseOptions } from '../types/SpawnPleaseOptions'
2727
import { Version } from '../types/Version'
2828
import { VersionResult } from '../types/VersionResult'
2929
import { VersionSpec } from '../types/VersionSpec'
30-
import { filterPredicate, satisfiesNodeEngine } from './filters'
30+
import { filterPredicate, satisfiesCooldownPeriod, satisfiesNodeEngine } from './filters'
3131

3232
const EXPLICIT_RANGE_OPS = new Set(['-', '||', '&&', '<', '<=', '>', '>='])
3333

@@ -918,6 +918,17 @@ export const newest: GetVersion = async (
918918
// sort by timestamp (entry[1]) and map versions
919919
const versionsSortedByTime = sortBy(Object.entries(timesSatisfyingNodeEngine), v => v[1]).map(([version]) => version)
920920

921+
if (options.cooldown) {
922+
const versionsSatisfiesfyingCooldownPeriod = versionsSortedByTime.filter(version =>
923+
satisfiesCooldownPeriod(
924+
decorateTagPackumentWithTime((result as Packument).versions[version], result as Packument),
925+
options.cooldown,
926+
),
927+
)
928+
929+
return { version: versionsSatisfiesfyingCooldownPeriod.at(-1) }
930+
}
931+
921932
return { version: versionsSortedByTime.at(-1) }
922933
}
923934

@@ -936,12 +947,28 @@ export const minor: GetVersion = async (
936947
npmConfig?: NpmConfig,
937948
npmConfigProject?: NpmConfig,
938949
): Promise<VersionResult> => {
939-
const versions = (
940-
await fetchUpgradedPackumentMemo(packageName, ['versions'], currentVersion, options, 0, npmConfig, npmConfigProject)
941-
)?.versions as Index<Packument>
950+
const fields: (keyof Packument)[] = ['versions']
951+
952+
if (options.cooldown) {
953+
fields.push('time')
954+
}
955+
956+
const packument = await fetchUpgradedPackumentMemo(
957+
packageName,
958+
fields,
959+
currentVersion,
960+
options,
961+
0,
962+
npmConfig,
963+
npmConfigProject,
964+
)
965+
966+
const versions = packument?.versions as Index<Packument>
942967
const version = versionUtil.findGreatestByLevel(
943968
Object.values(versions || {})
944-
.filter(filterPredicate(options))
969+
.filter(tagPackument =>
970+
filterPredicate(options)(decorateTagPackumentWithTime(tagPackument, packument as Partial<Packument>)),
971+
)
945972
.map(o => o.version),
946973
currentVersion,
947974
'minor',
@@ -964,12 +991,28 @@ export const patch: GetVersion = async (
964991
npmConfig?: NpmConfig,
965992
npmConfigProject?: NpmConfig,
966993
): Promise<VersionResult> => {
967-
const versions = (
968-
await fetchUpgradedPackumentMemo(packageName, ['versions'], currentVersion, options, 0, npmConfig, npmConfigProject)
969-
)?.versions as Index<Packument>
994+
const fields: (keyof Packument)[] = ['versions']
995+
996+
if (options.cooldown) {
997+
fields.push('time')
998+
}
999+
1000+
const packument = await fetchUpgradedPackumentMemo(
1001+
packageName,
1002+
fields,
1003+
currentVersion,
1004+
options,
1005+
0,
1006+
npmConfig,
1007+
npmConfigProject,
1008+
)
1009+
1010+
const versions = packument?.versions as Index<Packument>
9701011
const version = versionUtil.findGreatestByLevel(
9711012
Object.values(versions || {})
972-
.filter(filterPredicate(options))
1013+
.filter(tagPackument =>
1014+
filterPredicate(options)(decorateTagPackumentWithTime(tagPackument, packument as Partial<Packument>)),
1015+
)
9731016
.map(o => o.version),
9741017
currentVersion,
9751018
'patch',
@@ -992,14 +1035,30 @@ export const semver: GetVersion = async (
9921035
npmConfig?: NpmConfig,
9931036
npmConfigProject?: NpmConfig,
9941037
): Promise<VersionResult> => {
995-
const versions = (
996-
await fetchUpgradedPackumentMemo(packageName, ['versions'], currentVersion, options, 0, npmConfig, npmConfigProject)
997-
)?.versions as Index<Packument>
1038+
const fields: (keyof Packument)[] = ['versions']
1039+
1040+
if (options.cooldown) {
1041+
fields.push('time')
1042+
}
1043+
1044+
const packument = await fetchUpgradedPackumentMemo(
1045+
packageName,
1046+
fields,
1047+
currentVersion,
1048+
options,
1049+
0,
1050+
npmConfig,
1051+
npmConfigProject,
1052+
)
1053+
1054+
const versions = packument?.versions as Index<Packument>
9981055
// ignore explicit version ranges
9991056
if (isExplicitRange(currentVersion)) return { version: null }
10001057

10011058
const versionsFiltered = Object.values(versions || {})
1002-
.filter(filterPredicate(options))
1059+
.filter(tagPackument =>
1060+
filterPredicate(options)(decorateTagPackumentWithTime(tagPackument, packument as Partial<Packument>)),
1061+
)
10031062
.map(o => o.version)
10041063
// TODO: Upgrading within a prerelease does not seem to work.
10051064
// { includePrerelease: true } does not help.

test/cooldown.test.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,121 @@ describe('cooldown', () => {
304304
})
305305
})
306306

307+
describe('when newest target', () => {
308+
it('upgrades package to newest version older than cooldown period', async () => {
309+
// Given: test-package@1.0.0 installed, version 1.2.0 released 5 days ago (within cooldown), version 1.1.0 released 15 days ago (outside 10-day cooldown)
310+
const cooldown = 10
311+
const packageData: PackageFile = {
312+
dependencies: {
313+
'test-package': '1.0.0',
314+
},
315+
}
316+
const stub = stubVersions(
317+
createMockVersion({
318+
name: 'test-package',
319+
versions: {
320+
'1.2.0': new Date(NOW - 5 * DAY).toISOString(),
321+
'1.1.0': new Date(NOW - 15 * DAY).toISOString(),
322+
},
323+
}),
324+
)
325+
326+
// When ncu is run with the cooldown parameter and target is 'newest'
327+
const result = await ncu({ packageData, cooldown, target: 'newest' })
328+
329+
// Then: package is upgraded to version 1.1.0 (newest version outside cooldown)
330+
expect(result).to.have.property('test-package', '1.1.0')
331+
332+
stub.restore()
333+
})
334+
})
335+
336+
describe('when minor target', () => {
337+
it('upgrades package to newest minor version older than cooldown period', async () => {
338+
// Given: test-package@1.0.0 installed, version 1.2.0 released 5 days ago (within cooldown), version 1.1.0 released 15 days ago (outside 10-day cooldown)
339+
const cooldown = 10
340+
const packageData: PackageFile = {
341+
dependencies: {
342+
'test-package': '1.0.0',
343+
},
344+
}
345+
const stub = stubVersions(
346+
createMockVersion({
347+
name: 'test-package',
348+
versions: {
349+
'1.2.0': new Date(NOW - 5 * DAY).toISOString(),
350+
'1.1.0': new Date(NOW - 15 * DAY).toISOString(),
351+
},
352+
}),
353+
)
354+
355+
// When ncu is run with the cooldown parameter and target is 'minor'
356+
const result = await ncu({ packageData, cooldown, target: 'minor' })
357+
358+
// Then: package is upgraded to version 1.1.0 (newest minor version outside cooldown)
359+
expect(result).to.have.property('test-package', '1.1.0')
360+
361+
stub.restore()
362+
})
363+
})
364+
365+
describe('when patch target', () => {
366+
it('upgrades package to newest patch version older than cooldown period', async () => {
367+
// Given: test-package@1.0.0 installed, version 1.0.2 released 5 days ago (within cooldown), version 1.0.1 released 15 days ago (outside 10-day cooldown)
368+
const cooldown = 10
369+
const packageData: PackageFile = {
370+
dependencies: {
371+
'test-package': '1.0.0',
372+
},
373+
}
374+
const stub = stubVersions(
375+
createMockVersion({
376+
name: 'test-package',
377+
versions: {
378+
'1.0.2': new Date(NOW - 5 * DAY).toISOString(),
379+
'1.0.1': new Date(NOW - 15 * DAY).toISOString(),
380+
},
381+
}),
382+
)
383+
384+
// When ncu is run with the cooldown parameter and target is 'patch'
385+
const result = await ncu({ packageData, cooldown, target: 'patch' })
386+
387+
// Then: package is upgraded to version 1.0.1 (newest patch version outside cooldown)
388+
expect(result).to.have.property('test-package', '1.0.1')
389+
390+
stub.restore()
391+
})
392+
})
393+
394+
describe('when semver target', () => {
395+
it('upgrades package to newest semver version older than cooldown period', async () => {
396+
// Given: test-package@1.0.0 installed, version 1.1.0 released 5 days ago (within cooldown), version 1.0.1 released 15 days ago (outside 10-day cooldown)
397+
const cooldown = 10
398+
const packageData: PackageFile = {
399+
dependencies: {
400+
'test-package': '^1.0.0',
401+
},
402+
}
403+
const stub = stubVersions(
404+
createMockVersion({
405+
name: 'test-package',
406+
versions: {
407+
'1.1.0': new Date(NOW - 5 * DAY).toISOString(),
408+
'1.0.1': new Date(NOW - 15 * DAY).toISOString(),
409+
},
410+
}),
411+
)
412+
413+
// When ncu is run with the cooldown parameter and target is 'semver'
414+
const result = await ncu({ packageData, cooldown, target: 'semver' })
415+
416+
// Then: package is upgraded to version ^1.0.1 (newest semver version outside cooldown)
417+
expect(result).to.have.property('test-package', '^1.0.1')
418+
stub.restore()
419+
})
420+
})
421+
307422
it('skips package upgrade if no time data and cooldown is set', async () => {
308423
// Given: cooldown days is set to 10 days, test-package is installed in version 1.0.0, and the latest version - 1.1.0 was released 5 days ago (inside cooldown period). Another version 1.0.1 was released 10 days ago (outside cooldown period), but it is not the latest version, so it should not be upgraded either.
309424
const cooldown = 10

0 commit comments

Comments
 (0)