diff --git a/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap index 02cf5b7c7e009e282cc0db5be1bd56375da9554b..643d0d75ba63df39c1afb7c8a645473fb2ed05e8 100644 --- a/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap +++ b/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap @@ -221,6 +221,7 @@ Marking the latest version of an npm package as deprecated results in the entire "dependencies": undefined, "devDependencies": undefined, "gitRef": undefined, + "isDeprecated": true, "releaseTimestamp": "2018-05-06T05:21:53.000Z", "version": "0.0.1", }, diff --git a/lib/modules/datasource/npm/get.spec.ts b/lib/modules/datasource/npm/get.spec.ts index b101911166ee7b0f14454365d636d49c1fb84831..b19d4359e9394683b4761c087acc2c1728c37e90 100644 --- a/lib/modules/datasource/npm/get.spec.ts +++ b/lib/modules/datasource/npm/get.spec.ts @@ -4,7 +4,7 @@ import { ExternalHostError } from '../../../types/errors/external-host-error'; import * as _packageCache from '../../../util/cache/package'; import * as hostRules from '../../../util/host-rules'; import { Http } from '../../../util/http'; -import { getDependency } from './get'; +import { CACHE_REVISION, getDependency } from './get'; import { resolveRegistryUrl, setNpmrc } from './npmrc'; jest.mock('../../../util/cache/package'); @@ -473,16 +473,31 @@ describe('modules/datasource/npm/get', () => { `); }); - it('returns cached legacy', async () => { - packageCache.get.mockResolvedValueOnce({ some: 'result' }); - const dep = await getDependency(http, 'https://some.url', 'some-package'); - expect(dep).toMatchObject({ some: 'result' }); + it('discards cache with no revision', async () => { + setNpmrc('registry=https://test.org\n_authToken=XXX'); + + packageCache.get.mockResolvedValueOnce({ + some: 'result', + cacheData: { softExpireAt: '2099' }, + }); + + httpMock + .scope('https://test.org') + .get('/@neutrinojs%2Freact') + .reply(200, { + name: '@neutrinojs/react', + versions: { '1.0.0': {} }, + }); + const registryUrl = resolveRegistryUrl('@neutrinojs/react'); + const dep = await getDependency(http, registryUrl, '@neutrinojs/react'); + + expect(dep?.releases).toHaveLength(1); }); it('returns unexpired cache', async () => { packageCache.get.mockResolvedValueOnce({ some: 'result', - cacheData: { softExpireAt: '2099' }, + cacheData: { revision: CACHE_REVISION, softExpireAt: '2099' }, }); const dep = await getDependency(http, 'https://some.url', 'some-package'); expect(dep).toMatchObject({ some: 'result' }); @@ -492,6 +507,7 @@ describe('modules/datasource/npm/get', () => { packageCache.get.mockResolvedValueOnce({ some: 'result', cacheData: { + revision: CACHE_REVISION, softExpireAt: '2020', etag: 'some-etag', }, @@ -508,6 +524,7 @@ describe('modules/datasource/npm/get', () => { packageCache.get.mockResolvedValueOnce({ some: 'result', cacheData: { + revision: CACHE_REVISION, softExpireAt: '2020', etag: 'some-etag', }, diff --git a/lib/modules/datasource/npm/get.ts b/lib/modules/datasource/npm/get.ts index 6018d41efbac4b61c60e120c8ef2e8a4091938bb..33f5da96829bda8dba06208cd37d27c55f60c7fe 100644 --- a/lib/modules/datasource/npm/get.ts +++ b/lib/modules/datasource/npm/get.ts @@ -14,6 +14,8 @@ import { joinUrlParts } from '../../../util/url'; import type { Release, ReleaseResult } from '../types'; import type { CachedReleaseResult, NpmResponse } from './types'; +export const CACHE_REVISION = 1; + const SHORT_REPO_REGEX = regEx( /^((?<platform>bitbucket|github|gitlab):)?(?<shortRepo>[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)$/, ); @@ -82,8 +84,8 @@ export async function getDependency( cacheNamespace, packageUrl, ); - if (cachedResult) { - if (cachedResult.cacheData) { + if (cachedResult?.cacheData) { + if (cachedResult.cacheData.revision === CACHE_REVISION) { const softExpireAt = DateTime.fromISO( cachedResult.cacheData.softExpireAt, ); @@ -94,8 +96,10 @@ export async function getDependency( } logger.trace('Cached result is soft expired'); } else { - logger.trace('Reusing legacy cached result'); - return cachedResult; + logger.trace( + `Package cache for npm package "${packageName}" is from an old revision - discarding`, + ); + delete cachedResult.cacheData; } } const cacheMinutes = process.env.RENOVATE_CACHE_NPM_MINUTES @@ -193,6 +197,9 @@ export async function getDependency( ) { release.sourceDirectory = source.sourceDirectory; } + if (dep.deprecationMessage) { + release.isDeprecated = true; + } return release; }); logger.trace({ dep }, 'dep'); @@ -202,7 +209,7 @@ export async function getDependency( regEx(/(^|,)\s*public\s*(,|$)/).test(cacheControl) ) { dep.isPrivate = false; - const cacheData = { softExpireAt, etag }; + const cacheData = { revision: CACHE_REVISION, softExpireAt, etag }; await packageCache.set( cacheNamespace, packageUrl, diff --git a/lib/modules/datasource/npm/types.ts b/lib/modules/datasource/npm/types.ts index e67dd987a0e0efb7ca92085ae035b44b973028ad..2cd011d84bc072f292dedbb149ce7aa046366a52 100644 --- a/lib/modules/datasource/npm/types.ts +++ b/lib/modules/datasource/npm/types.ts @@ -35,6 +35,7 @@ export interface NpmResponse { export interface CachedReleaseResult extends ReleaseResult { cacheData?: { + revision?: number; etag: string | undefined; softExpireAt: string; }; diff --git a/lib/workers/repository/process/lookup/index.spec.ts b/lib/workers/repository/process/lookup/index.spec.ts index c4310a3444a4890fbae091b326ab20d1f82031e2..e56be9918897ad0501b636d49f7039a5df9cc59e 100644 --- a/lib/workers/repository/process/lookup/index.spec.ts +++ b/lib/workers/repository/process/lookup/index.spec.ts @@ -3154,13 +3154,16 @@ describe('workers/repository/process/lookup/index', () => { }); }); - it('ignores deprecated', async () => { + it('ignores deprecated when it is not the latest', async () => { config.currentValue = '1.3.0'; config.packageName = 'q2'; config.datasource = NpmDatasource.id; const returnJson = JSON.parse(JSON.stringify(qJson)); returnJson.name = 'q2'; + // mark latest minor as deprecated returnJson.versions['1.4.1'].deprecated = 'true'; + // make sure latest release isn't the one deprecated as otherwise every release is deprecated + returnJson['dist-tags'].latest = '2.0.3'; httpMock .scope('https://registry.npmjs.org') .get('/q2') @@ -3169,16 +3172,8 @@ describe('workers/repository/process/lookup/index', () => { const res = await Result.wrap( lookup.lookupUpdates(config), ).unwrapOrThrow(); - expect(res).toEqual({ currentVersion: '1.3.0', - deprecationMessage: codeBlock` - On registry \`https://registry.npmjs.org\`, the "latest" version of dependency \`q2\` has the following deprecation notice: - - \`true\` - - Marking the latest version of an npm package as deprecated results in the entire package being considered deprecated, so contact the package author you think this is a mistake. - `, fixedVersion: '1.3.0', isSingleVersion: true, registryUrl: 'https://registry.npmjs.org', @@ -3193,13 +3188,22 @@ describe('workers/repository/process/lookup/index', () => { releaseTimestamp: expect.any(String), updateType: 'minor', }, + { + bucket: 'major', + newMajor: 2, + newMinor: 0, + newValue: '2.0.3', + newVersion: '2.0.3', + releaseTimestamp: expect.any(String), + updateType: 'major', + }, ], versioning: 'npm', warnings: [], }); }); - it('is deprecated', async () => { + it('treats all versions as deprecated if latest is deprecated', async () => { config.currentValue = '1.3.0'; config.packageName = 'q3'; config.datasource = NpmDatasource.id; @@ -3209,6 +3213,7 @@ describe('workers/repository/process/lookup/index', () => { deprecated: true, repository: { url: null, directory: 'test' }, }; + returnJson.versions['1.4.1'].deprecated = 'true'; httpMock .scope('https://registry.npmjs.org') @@ -3221,6 +3226,13 @@ describe('workers/repository/process/lookup/index', () => { expect(res).toEqual({ currentVersion: '1.3.0', + deprecationMessage: codeBlock` + On registry \`https://registry.npmjs.org\`, the "latest" version of dependency \`q3\` has the following deprecation notice: + + \`true\` + + Marking the latest version of an npm package as deprecated results in the entire package being considered deprecated, so contact the package author you think this is a mistake. + `, fixedVersion: '1.3.0', isSingleVersion: true, registryUrl: 'https://registry.npmjs.org',