Skip to content

Commit

Permalink
fix(datasource/npm): mark all releases deprecated if latest deprecated (
Browse files Browse the repository at this point in the history
#27875)

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
  • Loading branch information
rarkins and viceice committed Mar 15, 2024
1 parent b4dc29b commit c1517aa
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 21 deletions.
Expand Up @@ -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",
},
Expand Down
29 changes: 23 additions & 6 deletions lib/modules/datasource/npm/get.spec.ts
Expand Up @@ -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');
Expand Down Expand Up @@ -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' });
Expand All @@ -492,6 +507,7 @@ describe('modules/datasource/npm/get', () => {
packageCache.get.mockResolvedValueOnce({
some: 'result',
cacheData: {
revision: CACHE_REVISION,
softExpireAt: '2020',
etag: 'some-etag',
},
Expand All @@ -508,6 +524,7 @@ describe('modules/datasource/npm/get', () => {
packageCache.get.mockResolvedValueOnce({
some: 'result',
cacheData: {
revision: CACHE_REVISION,
softExpireAt: '2020',
etag: 'some-etag',
},
Expand Down
17 changes: 12 additions & 5 deletions lib/modules/datasource/npm/get.ts
Expand Up @@ -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_.-]+)$/,
);
Expand Down Expand Up @@ -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,
);
Expand All @@ -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
Expand Down Expand Up @@ -193,6 +197,9 @@ export async function getDependency(
) {
release.sourceDirectory = source.sourceDirectory;
}
if (dep.deprecationMessage) {
release.isDeprecated = true;
}
return release;
});
logger.trace({ dep }, 'dep');
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions lib/modules/datasource/npm/types.ts
Expand Up @@ -35,6 +35,7 @@ export interface NpmResponse {

export interface CachedReleaseResult extends ReleaseResult {
cacheData?: {
revision?: number;
etag: string | undefined;
softExpireAt: string;
};
Expand Down
32 changes: 22 additions & 10 deletions lib/workers/repository/process/lookup/index.spec.ts
Expand Up @@ -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')
Expand All @@ -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',
Expand All @@ -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;
Expand All @@ -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')
Expand All @@ -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',
Expand Down

0 comments on commit c1517aa

Please sign in to comment.