Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(datasource/npm): mark all releases deprecated if latest deprecated #27875

Merged
merged 3 commits into from Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
viceice marked this conversation as resolved.
Show resolved Hide resolved
logger.trace(
`Package cache for npm package "${packageName}" is from an old revision - discarding`,
);
delete cachedResult.cacheData;
viceice marked this conversation as resolved.
Show resolved Hide resolved
}
}
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;
}
viceice marked this conversation as resolved.
Show resolved Hide resolved
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