mirror of https://github.com/renovatebot/renovate
164 lines
4.2 KiB
TypeScript
164 lines
4.2 KiB
TypeScript
import { quote } from 'shlex';
|
|
import upath from 'upath';
|
|
import { GlobalConfig } from '../../../config/global';
|
|
import { logger } from '../../../logger';
|
|
import { exec } from '../../../util/exec';
|
|
import type { ExecOptions } from '../../../util/exec/types';
|
|
import { readLocalFile } from '../../../util/fs';
|
|
import { getRepoStatus } from '../../../util/git';
|
|
import type {
|
|
UpdateArtifact,
|
|
UpdateArtifactsConfig,
|
|
UpdateArtifactsResult,
|
|
} from '../types';
|
|
import {
|
|
getCopierVersionConstraint,
|
|
getPythonVersionConstraint,
|
|
} from './utils';
|
|
|
|
const DEFAULT_COMMAND_OPTIONS = ['--skip-answered', '--defaults'];
|
|
|
|
function buildCommand(
|
|
config: UpdateArtifactsConfig,
|
|
packageFileName: string,
|
|
newVersion: string,
|
|
): string {
|
|
const command = ['copier', 'update', ...DEFAULT_COMMAND_OPTIONS];
|
|
if (GlobalConfig.get('allowScripts') && !config.ignoreScripts) {
|
|
command.push('--trust');
|
|
}
|
|
command.push(
|
|
'--answers-file',
|
|
quote(upath.basename(packageFileName)),
|
|
'--vcs-ref',
|
|
quote(newVersion),
|
|
);
|
|
return command.join(' ');
|
|
}
|
|
|
|
function artifactError(
|
|
packageFileName: string,
|
|
message: string,
|
|
): UpdateArtifactsResult[] {
|
|
return [
|
|
{
|
|
artifactError: {
|
|
lockFile: packageFileName,
|
|
stderr: message,
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
export async function updateArtifacts({
|
|
packageFileName,
|
|
updatedDeps,
|
|
config,
|
|
}: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> {
|
|
if (!updatedDeps || updatedDeps.length !== 1) {
|
|
// Each answers file (~ packageFileName) has exactly one dependency to update.
|
|
return artifactError(
|
|
packageFileName,
|
|
`Unexpected number of dependencies: ${updatedDeps.length} (should be 1)`,
|
|
);
|
|
}
|
|
|
|
const newVersion = updatedDeps[0]?.newVersion ?? updatedDeps[0]?.newValue;
|
|
if (!newVersion) {
|
|
return artifactError(
|
|
packageFileName,
|
|
'Missing copier template version to update to',
|
|
);
|
|
}
|
|
|
|
const command = buildCommand(config, packageFileName, newVersion);
|
|
const execOptions: ExecOptions = {
|
|
cwdFile: packageFileName,
|
|
docker: {},
|
|
userConfiguredEnv: config.env,
|
|
toolConstraints: [
|
|
{
|
|
toolName: 'python',
|
|
constraint: getPythonVersionConstraint(config),
|
|
},
|
|
{
|
|
toolName: 'copier',
|
|
constraint: getCopierVersionConstraint(config),
|
|
},
|
|
],
|
|
};
|
|
try {
|
|
await exec(command, execOptions);
|
|
} catch (err) {
|
|
logger.debug({ err }, `Failed to update copier template: ${err.message}`);
|
|
return artifactError(packageFileName, err.message);
|
|
}
|
|
|
|
const status = await getRepoStatus();
|
|
// If the answers file didn't change, Copier did not update anything.
|
|
if (!status.modified.includes(packageFileName)) {
|
|
return null;
|
|
}
|
|
|
|
if (status.conflicted.length > 0) {
|
|
// Sometimes, Copier erroneously reports conflicts.
|
|
const msg =
|
|
`Updating the Copier template yielded ${status.conflicted.length} merge conflicts. ` +
|
|
'Please check the proposed changes carefully! Conflicting files:\n * ' +
|
|
status.conflicted.join('\n * ');
|
|
logger.debug({ packageFileName, depName: updatedDeps[0]?.depName }, msg);
|
|
}
|
|
|
|
const res: UpdateArtifactsResult[] = [];
|
|
|
|
for (const f of [
|
|
...status.modified,
|
|
...status.not_added,
|
|
...status.conflicted,
|
|
]) {
|
|
const fileRes: UpdateArtifactsResult = {
|
|
file: {
|
|
type: 'addition',
|
|
path: f,
|
|
contents: await readLocalFile(f),
|
|
},
|
|
};
|
|
if (status.conflicted.includes(f)) {
|
|
// Make the reviewer aware of the conflicts.
|
|
// This will be posted in a comment.
|
|
fileRes.notice = {
|
|
file: f,
|
|
message:
|
|
'This file had merge conflicts. Please check the proposed changes carefully!',
|
|
};
|
|
}
|
|
res.push(fileRes);
|
|
}
|
|
for (const f of status.deleted) {
|
|
res.push({
|
|
file: {
|
|
type: 'deletion',
|
|
path: f,
|
|
},
|
|
});
|
|
}
|
|
// `git status` might detect a rename, which is then not contained
|
|
// in not_added/deleted. Ensure we respect renames as well if they happen.
|
|
for (const f of status.renamed) {
|
|
res.push({
|
|
file: {
|
|
type: 'deletion',
|
|
path: f.from,
|
|
},
|
|
});
|
|
res.push({
|
|
file: {
|
|
type: 'addition',
|
|
path: f.to,
|
|
contents: await readLocalFile(f.to),
|
|
},
|
|
});
|
|
}
|
|
return res;
|
|
}
|