feat: replace deprecated dependencies with their replacements
This commit is contained in:
Родитель
32181ae6c2
Коммит
3d3b205172
|
@ -1363,6 +1363,14 @@ If the `versioning` for a dependency is not captured with a named group then it
|
|||
|
||||
This is only necessary in case you need to manually configure a registry URL to use for datasource lookups. Applies to PyPI (pip) only for now. Supports only one URL for now but is defined as a list for forward compatibility.
|
||||
|
||||
### replacementName
|
||||
|
||||
Valid in `packageRules`
|
||||
|
||||
### replacementVersion
|
||||
|
||||
Valid in `packageRules`
|
||||
|
||||
## requiredStatusChecks
|
||||
|
||||
This is a future feature that is partially implemented. Currently Renovate's default behaviour is to only automerge if every status check has succeeded. In future, this might be configurable to allow certain status checks to be ignored.
|
||||
|
|
|
@ -192,7 +192,8 @@ export type UpdateType =
|
|||
| 'lockFileMaintenance'
|
||||
| 'lockfileUpdate'
|
||||
| 'rollback'
|
||||
| 'bump';
|
||||
| 'bump'
|
||||
| 'replacement';
|
||||
|
||||
// TODO: Proper typings
|
||||
export interface PackageRule
|
||||
|
|
|
@ -865,6 +865,26 @@ const options: RenovateOptions[] = [
|
|||
cli: false,
|
||||
env: false,
|
||||
},
|
||||
{
|
||||
name: 'replacementName',
|
||||
description: '',
|
||||
type: 'string',
|
||||
stage: 'package',
|
||||
parent: 'packageRules',
|
||||
mergeable: true,
|
||||
cli: false,
|
||||
env: false,
|
||||
},
|
||||
{
|
||||
name: 'replacementVersion',
|
||||
description: '',
|
||||
type: 'string',
|
||||
stage: 'package',
|
||||
parent: 'packageRules',
|
||||
mergeable: true,
|
||||
cli: false,
|
||||
env: false,
|
||||
},
|
||||
{
|
||||
name: 'updateTypes',
|
||||
description:
|
||||
|
|
|
@ -25,6 +25,8 @@ export interface GetPkgReleasesConfig extends ReleasesConfigBase {
|
|||
depName: string;
|
||||
lookupName?: string;
|
||||
versioning?: string;
|
||||
replacementName?: string;
|
||||
replacementVersion?: string;
|
||||
}
|
||||
|
||||
export function isGetPkgReleasesConfig(
|
||||
|
@ -63,6 +65,8 @@ export interface ReleaseResult {
|
|||
name?: string;
|
||||
pkgName?: string;
|
||||
releases: Release[];
|
||||
replacementName?: string;
|
||||
replacementVersion?: string;
|
||||
sourceUrl?: string;
|
||||
tags?: Record<string, string>;
|
||||
versions?: any;
|
||||
|
|
|
@ -237,4 +237,17 @@ 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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -161,6 +161,19 @@ function resolveRegistryUrls(
|
|||
return registryUrls.filter(Boolean);
|
||||
}
|
||||
|
||||
function applyReplacements(
|
||||
dep: ReleaseResult,
|
||||
config: GetReleasesInternalConfig
|
||||
): ReleaseResult {
|
||||
if (config.replacementName && config.replacementVersion) {
|
||||
const ret = clone(dep);
|
||||
ret.replacementName = config.replacementName;
|
||||
ret.replacementVersion = config.replacementVersion;
|
||||
return ret;
|
||||
}
|
||||
return dep;
|
||||
}
|
||||
|
||||
async function fetchReleases(
|
||||
config: GetReleasesInternalConfig
|
||||
): Promise<ReleaseResult | null> {
|
||||
|
@ -208,6 +221,7 @@ async function fetchReleases(
|
|||
return null;
|
||||
}
|
||||
addMetaData(dep, datasourceName, config.lookupName);
|
||||
dep = applyReplacements(dep, config);
|
||||
return dep;
|
||||
}
|
||||
|
||||
|
|
|
@ -141,6 +141,7 @@ export interface LookupUpdate {
|
|||
newDigestShort?: string;
|
||||
newMajor?: number;
|
||||
newMinor?: number;
|
||||
newName?: string;
|
||||
newValue: string;
|
||||
newVersion?: string;
|
||||
semanticCommitType?: string;
|
||||
|
@ -187,6 +188,7 @@ export interface Upgrade<T = Record<string, any>>
|
|||
newDigest?: string;
|
||||
newFrom?: string;
|
||||
newMajor?: number;
|
||||
newName?: string;
|
||||
newValue?: string;
|
||||
newVersion?: string;
|
||||
packageFile?: string;
|
||||
|
|
|
@ -198,6 +198,32 @@ describe('workers/branch/package-json', () => {
|
|||
});
|
||||
expect(testContent).toBeNull();
|
||||
});
|
||||
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');
|
||||
});
|
||||
});
|
||||
describe('.bumpPackageVersion()', () => {
|
||||
const content = JSON.stringify({
|
||||
|
|
|
@ -57,6 +57,55 @@ export function bumpPackageVersion(
|
|||
}
|
||||
}
|
||||
|
||||
function replaceAsString(
|
||||
parsedContents: any,
|
||||
fileContent: string,
|
||||
depType: string,
|
||||
depName: string,
|
||||
oldVersion: string,
|
||||
newValue: string
|
||||
): string | null {
|
||||
// Update the file = this is what we want
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (depName === oldVersion) {
|
||||
delete Object.assign(parsedContents[depType], {
|
||||
[newValue]: parsedContents[depType][oldVersion],
|
||||
})[oldVersion];
|
||||
} else if (depType === 'resolutions') {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
parsedContents.resolutions[depName] = newValue;
|
||||
} else {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
parsedContents[depType][depName] = newValue;
|
||||
}
|
||||
// Look for the old version number
|
||||
const searchString = `"${oldVersion}"`;
|
||||
const newString = `"${newValue}"`;
|
||||
// Skip ahead to depType section
|
||||
let searchIndex = fileContent.indexOf(`"${depType}"`) + depType.length;
|
||||
logger.trace(`Starting search at index ${searchIndex}`);
|
||||
// Iterate through the rest of the file
|
||||
for (; searchIndex < fileContent.length; searchIndex += 1) {
|
||||
// First check if we have a hit for the old version
|
||||
if (matchAt(fileContent, searchIndex, searchString)) {
|
||||
logger.trace(`Found match at index ${searchIndex}`);
|
||||
// Now test if the result matches
|
||||
const testContent = replaceAt(
|
||||
fileContent,
|
||||
searchIndex,
|
||||
searchString,
|
||||
newString
|
||||
);
|
||||
// Compare the parsed JSON structure of old and new
|
||||
if (equal(parsedContents, JSON.parse(testContent))) {
|
||||
return testContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
// istanbul ignore next: not possible to get here
|
||||
return null;
|
||||
}
|
||||
|
||||
export function updateDependency({
|
||||
fileContent,
|
||||
upgrade,
|
||||
|
@ -95,33 +144,23 @@ export function updateDependency({
|
|||
upgrade.bumpVersion
|
||||
);
|
||||
}
|
||||
// Update the file = this is what we want
|
||||
parsedContents[depType][depName] = newValue;
|
||||
// Look for the old version number
|
||||
const searchString = `"${oldVersion}"`;
|
||||
const newString = `"${newValue}"`;
|
||||
let newFileContent = null;
|
||||
// Skip ahead to depType section
|
||||
let searchIndex = fileContent.indexOf(`"${depType}"`) + depType.length;
|
||||
logger.trace(`Starting search at index ${searchIndex}`);
|
||||
// Iterate through the rest of the file
|
||||
for (; searchIndex < fileContent.length; searchIndex += 1) {
|
||||
// First check if we have a hit for the old version
|
||||
if (matchAt(fileContent, searchIndex, searchString)) {
|
||||
logger.trace(`Found match at index ${searchIndex}`);
|
||||
// Now test if the result matches
|
||||
const testContent = replaceAt(
|
||||
fileContent,
|
||||
searchIndex,
|
||||
searchString,
|
||||
newString
|
||||
);
|
||||
// Compare the parsed JSON structure of old and new
|
||||
if (equal(parsedContents, JSON.parse(testContent))) {
|
||||
newFileContent = testContent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let newFileContent = replaceAsString(
|
||||
parsedContents,
|
||||
fileContent,
|
||||
depType,
|
||||
depName,
|
||||
oldVersion,
|
||||
newValue
|
||||
);
|
||||
if (upgrade.newName) {
|
||||
newFileContent = replaceAsString(
|
||||
parsedContents,
|
||||
newFileContent,
|
||||
depType,
|
||||
depName,
|
||||
depName,
|
||||
upgrade.newName
|
||||
);
|
||||
}
|
||||
// istanbul ignore if
|
||||
if (!newFileContent) {
|
||||
|
@ -151,33 +190,14 @@ export function updateDependency({
|
|||
'Upgraded dependency exists in yarn resolutions but is different version'
|
||||
);
|
||||
}
|
||||
// Look for the old version number
|
||||
const oldResolution = `"${parsedContents.resolutions[depKey]}"`;
|
||||
const newResolution = `"${newValue}"`;
|
||||
// Update the file = this is what we want
|
||||
parsedContents.resolutions[depKey] = newValue;
|
||||
// Skip ahead to depType section
|
||||
searchIndex = newFileContent.indexOf(`"resolutions"`);
|
||||
logger.trace(`Starting search at index ${searchIndex}`);
|
||||
// Iterate through the rest of the file
|
||||
for (; searchIndex < newFileContent.length; searchIndex += 1) {
|
||||
// First check if we have a hit for the old version
|
||||
if (matchAt(newFileContent, searchIndex, oldResolution)) {
|
||||
logger.trace(`Found match at index ${searchIndex}`);
|
||||
// Now test if the result matches
|
||||
const testContent = replaceAt(
|
||||
newFileContent,
|
||||
searchIndex,
|
||||
oldResolution,
|
||||
newResolution
|
||||
);
|
||||
// Compare the parsed JSON structure of old and new
|
||||
if (equal(parsedContents, JSON.parse(testContent))) {
|
||||
newFileContent = testContent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
newFileContent = replaceAsString(
|
||||
parsedContents,
|
||||
newFileContent,
|
||||
'resolutions',
|
||||
depKey,
|
||||
parsedContents.resolutions[depKey],
|
||||
newValue
|
||||
);
|
||||
}
|
||||
}
|
||||
return bumpPackageVersion(
|
||||
|
|
|
@ -167,6 +167,13 @@ export async function lookupUpdates(
|
|||
if (dependency.deprecationMessage) {
|
||||
logger.debug({ dependency: depName }, 'Found deprecationMessage');
|
||||
res.deprecationMessage = dependency.deprecationMessage;
|
||||
if (dependency.replacementName && dependency.replacementVersion) {
|
||||
res.updates.push({
|
||||
updateType: 'replacement',
|
||||
newName: dependency.replacementName,
|
||||
newValue: dependency.replacementVersion,
|
||||
});
|
||||
}
|
||||
}
|
||||
res.sourceUrl = dependency?.sourceUrl;
|
||||
if (dependency.sourceDirectory) {
|
||||
|
|
|
@ -16,6 +16,15 @@ function getUpdateTypeRules(packageRules: PackageRule[]): PackageRule[] {
|
|||
return packageRules.filter((rule) => is.nonEmptyArray(rule.updateTypes));
|
||||
}
|
||||
|
||||
function sanitizeDepName(depName: string): string {
|
||||
return depName
|
||||
.replace('@types/', '')
|
||||
.replace('@', '')
|
||||
.replace(/\//g, '-')
|
||||
.replace(/\s+/g, '-')
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
export async function flattenUpdates(
|
||||
config: RenovateConfig,
|
||||
packageFiles: Record<string, any[]>
|
||||
|
@ -28,6 +37,7 @@ export async function flattenUpdates(
|
|||
'pin',
|
||||
'digest',
|
||||
'lockFileMaintenance',
|
||||
'replacement',
|
||||
];
|
||||
for (const [manager, files] of Object.entries(packageFiles)) {
|
||||
const managerConfig = getManagerConfig(config, manager);
|
||||
|
@ -62,12 +72,10 @@ export async function flattenUpdates(
|
|||
updateConfig = applyPackageRules(updateConfig);
|
||||
delete updateConfig.packageRules;
|
||||
updateConfig.depNameSanitized = updateConfig.depName
|
||||
? updateConfig.depName
|
||||
.replace('@types/', '')
|
||||
.replace('@', '')
|
||||
.replace(/\//g, '-')
|
||||
.replace(/\s+/g, '-')
|
||||
.toLowerCase()
|
||||
? sanitizeDepName(updateConfig.depName)
|
||||
: undefined;
|
||||
updateConfig.newNameSanitized = updateConfig.newName
|
||||
? sanitizeDepName(updateConfig.newName)
|
||||
: undefined;
|
||||
if (
|
||||
updateConfig.language === LANGUAGE_DOCKER &&
|
||||
|
|
Загрузка…
Ссылка в новой задаче