diff --git a/.changeset/orange-adults-mix.md b/.changeset/orange-adults-mix.md new file mode 100644 index 0000000000..71019a92e3 --- /dev/null +++ b/.changeset/orange-adults-mix.md @@ -0,0 +1,6 @@ +--- +"@pnpm/npm-resolver": patch +"pnpm": patch +--- + +When `minimumReleaseAge` is set and the `latest` tag is not mature enough, prefer a non-deprecated version as the new `latest` [#9987](https://github.com/pnpm/pnpm/issues/9987). diff --git a/resolving/npm-resolver/src/pickPackageFromMeta.ts b/resolving/npm-resolver/src/pickPackageFromMeta.ts index 1cd0e404f4..a2f6682e03 100644 --- a/resolving/npm-resolver/src/pickPackageFromMeta.ts +++ b/resolving/npm-resolver/src/pickPackageFromMeta.ts @@ -277,7 +277,12 @@ function filterMetaByPublishedDate (meta: PackageMetaWithTime, publishedBy: Date bestVersion = candidate } else { try { - if (semver.gt(candidate, bestVersion, true)) { + const candidateIsDeprecated = meta.versions[candidate].deprecated != null + const bestVersionIsDeprecated = meta.versions[bestVersion].deprecated != null + if ( + (semver.gt(candidate, bestVersion, true) && (bestVersionIsDeprecated === candidateIsDeprecated)) || + (bestVersionIsDeprecated && !candidateIsDeprecated) + ) { bestVersion = candidate } } catch (err) { diff --git a/resolving/npm-resolver/test/distTagsByDate.test.ts b/resolving/npm-resolver/test/distTagsByDate.test.ts index 3b3a1e035d..d70ffbf54a 100644 --- a/resolving/npm-resolver/test/distTagsByDate.test.ts +++ b/resolving/npm-resolver/test/distTagsByDate.test.ts @@ -79,6 +79,65 @@ test('repopulate dist-tag to highest same-major version within the date cutoff', expect(res!.id).toBe(`${name}@3.1.0`) }) +test('repopulate dist-tag to highest same-major version within the date cutoff. Prefer non-deprecated version', async () => { + const name = 'dist-tag-date' + const meta = { + name, + versions: { + '3.0.0': { + name, + version: '3.0.0', + dist: { tarball: `https://registry.npmjs.org/${name}/-/${name}-3.0.0.tgz` }, + }, + '3.1.0': { + name, + version: '3.1.0', + dist: { tarball: `https://registry.npmjs.org/${name}/-/${name}-3.1.0.tgz` }, + deprecated: 'This version is deprecated', + }, + '3.2.0': { + name, + version: '3.2.0', + dist: { tarball: `https://registry.npmjs.org/${name}/-/${name}-3.2.0.tgz` }, + }, + '2.9.9': { + name, + version: '2.9.9', + dist: { tarball: `https://registry.npmjs.org/${name}/-/${name}-2.9.9.tgz` }, + }, + }, + 'dist-tags': { + latest: '3.2.0', + }, + time: { + '2.9.9': '2020-01-01T00:00:00.000Z', + '3.0.0': '2020-02-01T00:00:00.000Z', + '3.1.0': '2020-03-01T00:00:00.000Z', + '3.2.0': '2020-05-01T00:00:00.000Z', + }, + } + + // Cutoff before 3.2.0, so latest must be remapped to 3.1.0 (same major 3) + const cutoff = new Date('2020-04-01T00:00:00.000Z') + + nock(registries.default) + .get(`/${name}`) + .reply(200, meta) + + const cacheDir = tempy.directory() + const { resolveFromNpm } = createResolveFromNpm({ + cacheDir, + fullMetadata: true, + registries, + }) + + const res = await resolveFromNpm({ alias: name, bareSpecifier: 'latest' }, { + publishedBy: cutoff, + }) + + expect(res!.id).toBe(`${name}@3.0.0`) +}) + test('repopulate dist-tag to highest non-prerelease same-major version within the date cutoff', async () => { const name = 'dist-tag-date' const meta = {