Skip to content

Commit

Permalink
feat: replace deprecated dependencies with their replacements
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieMagee committed Nov 7, 2021
1 parent b9be8d4 commit 6515576
Show file tree
Hide file tree
Showing 19 changed files with 272 additions and 13 deletions.
29 changes: 29 additions & 0 deletions docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -1652,6 +1652,31 @@ For example to apply a special label for Major updates:
}
```

### replacementName

Use this field to define the name of a replacement package.
Must be used with `replacementVersion` (see example below).
New package rules can be added by editing [`replacements.ts`](https://github.com/renovatebot/renovate/blob/main/lib/config/presets/internal/replacements.ts)

### replacementVersion

Use this field to define the name of a replacement package.
Must be used with `replacementVersion`.
For example to replace the npm package `jade` with version `2.0.0` of the package `pug`:

```json
{
"packageRules": [
{
"matchDatasources": ["npm"],
"matchPackageNames": ["jade"],
"replacementName": "pug",
"replacementVersion": "2.0.0"
}
]
}
```

## patch

Add to this object if you wish to define rules that apply only to patch updates.
Expand Down Expand Up @@ -2233,6 +2258,10 @@ In case there is a need to configure them manually, it can be done using this `r

The field supports multiple URLs however it is datasource-dependent on whether only the first is used or multiple.

## replacement

Add to this object if you wish to define rules that apply only to PRs that replace dependencies.

## respectLatest

Similar to `ignoreUnstable`, this option controls whether to update to versions that are greater than the version tagged as `latest` in the repository.
Expand Down
39 changes: 39 additions & 0 deletions lib/config/options/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,28 @@ const options: RenovateOptions[] = [
cli: false,
env: false,
},
{
name: 'replacementName',
description:
'The name of the new dependency that replaces the old deprecated dependency.',
type: 'string',
stage: 'package',
parent: 'packageRules',
mergeable: true,
cli: false,
env: false,
},
{
name: 'replacementVersion',
description:
'The version of the new dependency that replaces the old deprecated dependency.',
type: 'string',
stage: 'package',
parent: 'packageRules',
mergeable: true,
cli: false,
env: false,
},
{
name: 'matchUpdateTypes',
description:
Expand Down Expand Up @@ -1196,6 +1218,23 @@ const options: RenovateOptions[] = [
cli: false,
mergeable: true,
},
{
name: 'replacement',
description: 'Configuration to apply when replacing a dependency.',
stage: 'package',
type: 'object',
default: {
branchTopic: '{{{depNameSanitized}}}-replacement',
commitMessageAction: 'Replace',
commitMessageExtra:
'with {{newName}} {{#if isMajor}}v{{{newMajor}}}{{else}}{{#if isSingleVersion}}v{{{newVersion}}}{{else}}{{{newValue}}}{{/if}}{{/if}}',
prBodyNotes: [
'This is a special PR that replaces `{{{depNameSanitized}}}` with the community suggested minimal stable replacement version.',
],
},
cli: false,
mergeable: true,
},
// Semantic commit / Semantic release
{
name: 'semanticCommits',
Expand Down
1 change: 1 addition & 0 deletions lib/config/presets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export function parsePreset(input: string): ParsedPreset {
'packages',
'preview',
'regexManagers',
'replacements',
'schedule',
'workarounds',
];
Expand Down
2 changes: 2 additions & 0 deletions lib/config/presets/internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as npm from './npm';
import * as packagesPreset from './packages';
import * as previewPreset from './preview';
import * as regexManagersPreset from './regex-managers';
import * as replacements from './replacements';
import * as schedulePreset from './schedule';
import * as workaroundsPreset from './workarounds';

Expand All @@ -25,6 +26,7 @@ export const groups: Record<string, Record<string, Preset>> = {
packages: packagesPreset.presets,
preview: previewPreset.presets,
regexManagers: regexManagersPreset.presets,
replacements: replacements.presets,
schedule: schedulePreset.presets,
workarounds: workaroundsPreset.presets,
};
Expand Down
19 changes: 19 additions & 0 deletions lib/config/presets/internal/replacements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Preset } from '../types';

export const presets: Record<string, Preset> = {
all: {
description: 'All replacements',
extends: ['replacements:jade-to-pug'],
},
'jade-to-pug': {
description: 'Jade was renamed to Pug',
packageRules: [
{
matchDatasources: ['npm'],
matchPackageNames: ['jade'],
replacementName: 'pug',
replacementVersion: '2.0.0',
},
],
},
};
3 changes: 2 additions & 1 deletion lib/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,8 @@ export type UpdateType =
| 'lockFileMaintenance'
| 'lockfileUpdate'
| 'rollback'
| 'bump';
| 'bump'
| 'replacement';

export type MatchStringsStrategy = 'any' | 'recursive' | 'combination';

Expand Down
14 changes: 14 additions & 0 deletions lib/datasource/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,4 +298,18 @@ describe('datasource/index', () => {
});
expect(res.sourceUrl).toEqual('https://github.com/Jasig/cas');
});

it('applies replacements', async () => {
npmDatasource.getReleases.mockResolvedValue({
releases: [{ version: '1.0.0' }],
});
const res = await datasource.getPkgReleases({
datasource: datasourceNpm.id,
depName: 'abc',
replacementName: 'def',
replacementVersion: '2.0.0',
});
expect(res.replacementName).toEqual('def');
expect(res.replacementVersion).toEqual('2.0.0');
});
});
13 changes: 13 additions & 0 deletions lib/datasource/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,18 @@ export function getDefaultVersioning(datasourceName: string): string {
return datasource?.defaultVersioning || 'semver';
}

function applyReplacements(
config: GetReleasesInternalConfig
): Pick<ReleaseResult, 'replacementName' | 'replacementVersion'> | undefined {
if (config.replacementName && config.replacementVersion) {
return {
replacementName: config.replacementName,
replacementVersion: config.replacementVersion,
};
}
return undefined;
}

async function fetchReleases(
config: GetReleasesInternalConfig
): Promise<ReleaseResult | null> {
Expand Down Expand Up @@ -250,6 +262,7 @@ async function fetchReleases(
return null;
}
addMetaData(dep, datasourceName, config.lookupName);
dep = { ...dep, ...applyReplacements(config) };
return dep;
}

Expand Down
4 changes: 4 additions & 0 deletions lib/datasource/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export interface GetPkgReleasesConfig extends ReleasesConfigBase {
versioning?: string;
extractVersion?: string;
constraints?: Record<string, string>;
replacementName?: string;
replacementVersion?: string;
}

export interface Release {
Expand Down Expand Up @@ -60,6 +62,8 @@ export interface ReleaseResult {
sourceUrl?: string;
sourceDirectory?: string;
registryUrl?: string;
replacementName?: string;
replacementVersion?: string;
}

export interface DatasourceApi {
Expand Down
43 changes: 43 additions & 0 deletions lib/manager/npm/update/dependency/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,5 +210,48 @@ describe('manager/npm/update/dependency/index', () => {
});
expect(testContent).toEqual(outputContent);
});

it('returns null if empty file', () => {
const upgrade = {
depType: 'dependencies',
depName: 'angular-touch-not',
newValue: '1.5.8',
};
const testContent = npmUpdater.updateDependency({
fileContent: null,
upgrade,
});
expect(testContent).toBeNull();
});

it('replaces package', () => {
const upgrade = {
depType: 'dependencies',
depName: 'config',
newName: 'abc',
newValue: '2.0.0',
};
const testContent = npmUpdater.updateDependency({
fileContent: input01Content,
upgrade,
});
expect(JSON.parse(testContent).dependencies.config).toBeUndefined();
expect(JSON.parse(testContent).dependencies.abc).toEqual('2.0.0');
});

it('replaces glob package resolutions', () => {
const upgrade = {
depType: 'dependencies',
depName: 'config',
newName: 'abc',
newValue: '2.0.0',
};
const testContent = npmUpdater.updateDependency({
fileContent: input01GlobContent,
upgrade,
});
expect(JSON.parse(testContent).resolutions.config).toBeUndefined();
expect(JSON.parse(testContent).resolutions['**/abc']).toEqual('2.0.0');
});
});
});
36 changes: 33 additions & 3 deletions lib/manager/npm/update/dependency/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,24 @@ function replaceAsString(
fileContent: string,
depType: string,
depName: string,
oldVersion: string,
oldValue: string,
newValue: string
): string | null {
// Update the file = this is what we want
if (depType === 'packageManager') {
// eslint-disable-next-line no-param-reassign
parsedContents[depType] = newValue;
} else if (depName === oldValue) {
// The old value is the name of the dependency itself
delete Object.assign(parsedContents[depType], {
[newValue]: parsedContents[depType][oldValue],
})[oldValue];
} else {
// The old value is the version of the dependency
// eslint-disable-next-line no-param-reassign
parsedContents[depType][depName] = newValue;
}
// Look for the old version number
const searchString = `"${oldVersion}"`;
const searchString = `"${oldValue}"`;
const newString = `"${newValue}"`;
// Skip ahead to depType section
let searchIndex = fileContent.indexOf(`"${depType}"`) + depType.length;
Expand Down Expand Up @@ -96,6 +101,16 @@ export function updateDependency({
oldVersion,
newValue
);
if (upgrade.newName) {
newFileContent = replaceAsString(
parsedContents,
newFileContent,
depType,
depName,
depName,
upgrade.newName
);
}
// istanbul ignore if
if (!newFileContent) {
logger.debug(
Expand Down Expand Up @@ -132,6 +147,21 @@ export function updateDependency({
parsedContents.resolutions[depKey],
newValue
);
if (upgrade.newName) {
if (depKey === `**/${depName}`) {
// handles the case where a replacement is in a resolution
// eslint-disable-next-line no-param-reassign
upgrade.newName = `**/${upgrade.newName}`;
}
newFileContent = replaceAsString(
parsedContents,
newFileContent,
'resolutions',
depKey,
depKey,
upgrade.newName
);
}
}
}
return newFileContent;
Expand Down
3 changes: 3 additions & 0 deletions lib/manager/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,11 @@ export interface LookupUpdate {
isPin?: boolean;
isRange?: boolean;
isRollback?: boolean;
isReplacement?: boolean;
newDigest?: string;
newMajor?: number;
newMinor?: number;
newName?: string;
newValue: string;
semanticCommitType?: string;
pendingChecks?: boolean;
Expand Down Expand Up @@ -177,6 +179,7 @@ export interface Upgrade<T = Record<string, any>>
newDigest?: string;
newFrom?: string;
newMajor?: number;
newName?: string;
newValue?: string;
packageFile?: string;
rangeStrategy?: RangeStrategy;
Expand Down
1 change: 1 addition & 0 deletions lib/util/merge-confidence/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const updateTypeConfidenceMapping: Record<UpdateType, MergeConfidence> = {
lockFileMaintenance: 'neutral',
lockfileUpdate: 'neutral',
rollback: 'neutral',
replacement: 'neutral',
major: null,
minor: null,
patch: null,
Expand Down
5 changes: 4 additions & 1 deletion lib/util/template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const allowedFields = {
isPatch: 'true if the upgrade is a patch upgrade',
isPin: 'true if the upgrade is pinning dependencies',
isRollback: 'true if the upgrade is a rollback PR',
isReplacement: 'true if the upgrade is a replacement',
isRange: 'true if the new value is a range',
isSingleVersion:
'true if the upgrade is to a single version rather than a range',
Expand All @@ -72,6 +73,8 @@ export const allowedFields = {
'The major version of the new version. e.g. "3" if the new version if "3.1.0"',
newMinor:
'The minor version of the new version. e.g. "1" if the new version if "3.1.0"',
newName:
'The name of the new dependency that replaces the current deprecated dependency',
newValue:
'The new value in the upgrade. Can be a range or version e.g. "^3.0.0" or "3.1.0"',
newVersion: 'The new version in the upgrade, e.g. "3.1.0"',
Expand All @@ -91,7 +94,7 @@ export const allowedFields = {
semanticPrefix: 'The fully generated semantic prefix for commit messages',
sourceRepoSlug: 'The slugified pathname of the sourceUrl, if present',
sourceUrl: 'The source URL for the package',
updateType: 'One of digest, pin, rollback, patch, minor, major',
updateType: 'One of digest, pin, rollback, patch, minor, major, replacement',
upgrades: 'An array of upgrade objects in the branch',
url: 'The url of the release notes',
version: 'The version number of the changelog',
Expand Down
Loading

0 comments on commit 6515576

Please sign in to comment.