feat: replace deprecated dependencies with their replacements

This commit is contained in:
Jamie Magee 2020-02-23 21:00:01 +01:00
Родитель 32181ae6c2
Коммит 3d3b205172
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4429B642B1718654
11 изменённых файлов: 184 добавлений и 61 удалений

Просмотреть файл

@ -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 &&