renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
364 lines • 16.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateArtifacts = updateArtifacts;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const semver_1 = tslib_1.__importDefault(require("semver"));
const shlex_1 = require("shlex");
const upath_1 = tslib_1.__importDefault(require("upath"));
const global_1 = require("../../../config/global");
const error_messages_1 = require("../../../constants/error-messages");
const logger_1 = require("../../../logger");
const array_1 = require("../../../util/array");
const env_1 = require("../../../util/env");
const exec_1 = require("../../../util/exec");
const filter_map_1 = require("../../../util/filter-map");
const fs_1 = require("../../../util/fs");
const git_1 = require("../../../util/git");
const auth_1 = require("../../../util/git/auth");
const regex_1 = require("../../../util/regex");
const semver_2 = require("../../versioning/semver");
const artifacts_extra_1 = require("./artifacts-extra");
const { major, valid } = semver_1.default;
function getUpdateImportPathCmds(updatedDeps, { constraints }) {
// Check if we fail to parse any major versions and log that they're skipped
const invalidMajorDeps = updatedDeps.filter(({ newVersion }) => !valid(newVersion));
if (invalidMajorDeps.length > 0) {
invalidMajorDeps.forEach(({ depName }) => logger_1.logger.warn({ depName }, 'Ignoring dependency: Could not get major version'));
}
const updateImportCommands = updatedDeps
.filter(({ newVersion }) => valid(newVersion) && !newVersion.endsWith('+incompatible'))
.map(({ depName, newVersion }) => ({
depName: depName,
newMajor: major(newVersion),
}))
// Skip path updates going from v0 to v1
.filter(({ depName, newMajor }) => depName.startsWith('gopkg.in/') || newMajor > 1)
.map(({ depName, newMajor }) => `mod upgrade --mod-name=${depName} -t=${newMajor}`);
if (updateImportCommands.length > 0) {
let installMarwanModArgs = 'install github.com/marwan-at-work/mod/cmd/mod@latest';
const gomodModCompatibility = constraints?.gomodMod;
if (gomodModCompatibility) {
if (gomodModCompatibility.startsWith('v') &&
(0, semver_2.isValid)(gomodModCompatibility.replace((0, regex_1.regEx)(/^v/), ''))) {
installMarwanModArgs = installMarwanModArgs.replace((0, regex_1.regEx)(/@latest$/), `@${gomodModCompatibility}`);
}
else {
logger_1.logger.debug({ gomodModCompatibility }, 'marwan-at-work/mod compatibility range is not valid - skipping');
}
}
else {
logger_1.logger.debug('No marwan-at-work/mod compatibility range found - installing marwan-at-work/mod latest');
}
updateImportCommands.unshift(`go ${installMarwanModArgs}`);
}
return updateImportCommands;
}
function useModcacherw(goVersion) {
if (!is_1.default.string(goVersion)) {
return true;
}
const [, majorPart, minorPart] = (0, array_1.coerceArray)((0, regex_1.regEx)(/(\d+)\.(\d+)/).exec(goVersion));
const [major, minor] = [majorPart, minorPart].map((x) => parseInt(x, 10));
return (!Number.isNaN(major) &&
!Number.isNaN(minor) &&
(major > 1 || (major === 1 && minor >= 14)));
}
async function updateArtifacts({ packageFileName: goModFileName, updatedDeps, newPackageFileContent: newGoModContent, config, }) {
logger_1.logger.debug(`gomod.updateArtifacts(${goModFileName})`);
const sumFileName = goModFileName.replace((0, regex_1.regEx)(/\.mod$/), '.sum');
const existingGoSumContent = await (0, fs_1.readLocalFile)(sumFileName);
if (!existingGoSumContent) {
logger_1.logger.debug('No go.sum found');
return null;
}
const goModDir = upath_1.default.dirname(goModFileName);
const vendorDir = upath_1.default.join(goModDir, 'vendor/');
const vendorModulesFileName = upath_1.default.join(vendorDir, 'modules.txt');
const useVendor = !!config.postUpdateOptions?.includes('gomodVendor') ||
(!config.postUpdateOptions?.includes('gomodSkipVendor') &&
(await (0, fs_1.readLocalFile)(vendorModulesFileName)) !== null);
let massagedGoMod = newGoModContent;
if (config.postUpdateOptions?.includes('gomodMassage')) {
// Regex match inline replace directive, example:
// replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
// https://go.dev/ref/mod#go-mod-file-replace
// replace bracket after comments, so it doesn't break the regex, doing a complex regex causes problems
// when there's a comment and ")" after it, the regex will read replace block until comment.. and stop.
massagedGoMod = massagedGoMod
.split('\n')
.map((line) => {
if (line.trim().startsWith('//')) {
return line.replace(')', 'renovate-replace-bracket');
}
return line;
})
.join('\n');
const inlineReplaceRegEx = (0, regex_1.regEx)(/(\r?\n)(replace\s+[^\s]+\s+=>\s+\.\.\/.*)/g);
// $1 will be matched with the (\r?n) group
// $2 will be matched with the inline replace match, example
// "// renovate-replace replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5"
const inlineCommentOut = '$1// renovate-replace $2';
// Regex match replace directive block, example:
// replace (
// golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
// )
const blockReplaceRegEx = (0, regex_1.regEx)(/(\r?\n)replace\s*\([^)]+\s*\)/g);
/**
* replacerFunction for commenting out replace blocks
* @param match A string representing a golang replace directive block
* @returns A commented out block with // renovate-replace
*/
const blockCommentOut = (match) => match.replace(/(\r?\n)/g, '$1// renovate-replace ');
// Comment out golang replace directives
massagedGoMod = massagedGoMod
.replace(inlineReplaceRegEx, inlineCommentOut)
.replace(blockReplaceRegEx, blockCommentOut);
if (massagedGoMod !== newGoModContent) {
logger_1.logger.debug('Removed some relative replace statements and comments from go.mod');
}
}
const goConstraints = config.constraints?.go ?? getGoConstraints(newGoModContent);
try {
await (0, fs_1.writeLocalFile)(goModFileName, massagedGoMod);
const cmd = 'go';
const env = (0, env_1.getEnv)();
const execOptions = {
cwdFile: goModFileName,
extraEnv: {
GOPATH: await (0, fs_1.ensureCacheDir)('go'),
GOPROXY: env.GOPROXY,
GOPRIVATE: env.GOPRIVATE,
GONOPROXY: env.GONOPROXY,
GONOSUMDB: env.GONOSUMDB,
GOSUMDB: env.GOSUMDB,
GOINSECURE: env.GOINSECURE,
/* v8 ignore next -- TODO: add test */
GOFLAGS: useModcacherw(goConstraints) ? '-modcacherw' : null,
CGO_ENABLED: global_1.GlobalConfig.get('binarySource') === 'docker' ? '0' : null,
...(0, auth_1.getGitEnvironmentVariables)(['go']),
},
docker: {},
toolConstraints: [
{
toolName: 'golang',
constraint: goConstraints,
},
],
};
const execCommands = [];
let goGetDirs;
if (config.goGetDirs) {
goGetDirs = config.goGetDirs
.filter((dir) => {
const isValid = (0, fs_1.isValidLocalPath)(dir);
if (!isValid) {
logger_1.logger.warn({ dir }, 'Invalid path in goGetDirs');
}
return isValid;
})
.map(shlex_1.quote)
.join(' ');
if (goGetDirs === '') {
throw new Error('Invalid goGetDirs');
}
}
let args = `get -d -t ${goGetDirs ?? './...'}`;
logger_1.logger.trace({ cmd, args }, 'go get command included');
execCommands.push(`${cmd} ${args}`);
// Update import paths on major updates
const isImportPathUpdateRequired = config.postUpdateOptions?.includes('gomodUpdateImportPaths') &&
config.updateType === 'major';
if (isImportPathUpdateRequired) {
const updateImportCmds = getUpdateImportPathCmds(updatedDeps, config);
if (updateImportCmds.length > 0) {
logger_1.logger.debug(updateImportCmds, 'update import path commands included');
// The updates
execCommands.push(...updateImportCmds);
}
}
const mustSkipGoModTidy = !config.postUpdateOptions?.includes('gomodUpdateImportPaths') &&
config.updateType === 'major';
if (mustSkipGoModTidy) {
logger_1.logger.debug('go mod tidy command skipped');
}
let tidyOpts = '';
if (config.postUpdateOptions?.includes('gomodTidy1.17')) {
tidyOpts += ' -compat=1.17';
}
if (config.postUpdateOptions?.includes('gomodTidyE')) {
tidyOpts += ' -e';
}
const isGoModTidyRequired = !mustSkipGoModTidy &&
(config.postUpdateOptions?.includes('gomodTidy') === true ||
config.postUpdateOptions?.includes('gomodTidy1.17') === true ||
config.postUpdateOptions?.includes('gomodTidyE') === true ||
(config.updateType === 'major' && isImportPathUpdateRequired));
if (isGoModTidyRequired) {
args = 'mod tidy' + tidyOpts;
logger_1.logger.debug('go mod tidy command included');
execCommands.push(`${cmd} ${args}`);
}
const goWorkSumFileName = upath_1.default.join(goModDir, 'go.work.sum');
if (useVendor) {
// If we find a go.work, then use go workspace vendoring.
const goWorkFile = await (0, fs_1.findLocalSiblingOrParent)(goModFileName, 'go.work');
if (goWorkFile) {
args = 'work vendor';
logger_1.logger.debug('using go work vendor');
execCommands.push(`${cmd} ${args}`);
args = 'work sync';
logger_1.logger.debug('using go work sync');
execCommands.push(`${cmd} ${args}`);
}
else {
args = 'mod vendor';
logger_1.logger.debug('using go mod vendor');
execCommands.push(`${cmd} ${args}`);
}
if (isGoModTidyRequired) {
args = 'mod tidy' + tidyOpts;
logger_1.logger.debug('go mod tidy command included');
execCommands.push(`${cmd} ${args}`);
}
}
// We tidy one more time as a solution for #6795
if (isGoModTidyRequired) {
args = 'mod tidy' + tidyOpts;
logger_1.logger.debug('go mod tidy command included');
execCommands.push(`${cmd} ${args}`);
}
await (0, exec_1.exec)(execCommands, execOptions);
const status = await (0, git_1.getRepoStatus)();
if (!status.modified.includes(sumFileName) &&
!status.modified.includes(goModFileName) &&
!status.modified.includes(goWorkSumFileName)) {
return null;
}
const res = [];
if (status.modified.includes(sumFileName)) {
logger_1.logger.debug('Returning updated go.sum');
res.push({
file: {
type: 'addition',
path: sumFileName,
contents: await (0, fs_1.readLocalFile)(sumFileName),
},
});
}
if (status.modified.includes(goWorkSumFileName)) {
logger_1.logger.debug('Returning updated go.work.sum');
res.push({
file: {
type: 'addition',
path: goWorkSumFileName,
contents: await (0, fs_1.readLocalFile)(goWorkSumFileName),
},
});
}
// Include all the .go file import changes
if (isImportPathUpdateRequired) {
logger_1.logger.debug('Returning updated go source files for import path changes');
for (const f of status.modified) {
if (f.endsWith('.go')) {
res.push({
file: {
type: 'addition',
path: f,
contents: await (0, fs_1.readLocalFile)(f),
},
});
}
}
}
if (useVendor) {
for (const f of status.modified.concat(status.not_added)) {
if (f.startsWith(vendorDir)) {
res.push({
file: {
type: 'addition',
path: f,
contents: await (0, fs_1.readLocalFile)(f),
},
});
}
}
for (const f of (0, array_1.coerceArray)(status.deleted)) {
res.push({
file: {
type: 'deletion',
path: f,
},
});
}
}
// TODO: throws in tests (#22198)
const finalGoModContent = (await (0, fs_1.readLocalFile)(goModFileName, 'utf8'))
.replace((0, regex_1.regEx)(/\/\/ renovate-replace /g), '')
.replace((0, regex_1.regEx)(/renovate-replace-bracket/g), ')');
if (finalGoModContent !== newGoModContent) {
const artifactResult = {
file: {
type: 'addition',
path: goModFileName,
contents: finalGoModContent,
},
};
const updatedDepNames = (0, filter_map_1.filterMap)(updatedDeps, (dep) => dep?.depName);
const extraDepsNotice = (0, artifacts_extra_1.getExtraDepsNotice)(newGoModContent, finalGoModContent, updatedDepNames);
if (extraDepsNotice) {
artifactResult.notice = {
file: goModFileName,
message: extraDepsNotice,
};
}
logger_1.logger.debug('Found updated go.mod after go.sum update');
res.push(artifactResult);
}
return res;
}
catch (err) {
// istanbul ignore if
if (err.message === error_messages_1.TEMPORARY_ERROR) {
throw err;
}
logger_1.logger.debug({ err }, 'Failed to update go.sum');
return [
{
artifactError: {
lockFile: sumFileName,
stderr: err.message,
},
},
];
}
}
function getGoConstraints(content) {
// prefer toolchain directive when go.mod has one
const toolchainMatch = (0, regex_1.regEx)(/^toolchain\s*go(?<gover>\d+\.\d+\.\d+)$/m).exec(content);
const toolchainVer = toolchainMatch?.groups?.gover;
if (toolchainVer) {
logger_1.logger.debug(`Using go version ${toolchainVer} found in toolchain directive`);
return toolchainVer;
}
// If go.mod doesn't have toolchain directive and has a full go version spec,
// for example `go 1.23.6`, pick this version, this doesn't match major.minor version spec.
//
// This is because when go.mod have same version defined in go directive and toolchain directive,
// go will remove toolchain directive from go.mod.
//
// For example, go will rewrite `go 1.23.5\ntoolchain go1.23.5` to `go 1.23.5` by default,
// in this case, the go directive is the toolchain directive.
const goFullVersion = (0, regex_1.regEx)(/^go\s*(?<gover>\d+\.\d+\.\d+)$/m).exec(content)
?.groups?.gover;
if (goFullVersion) {
return goFullVersion;
}
const re = (0, regex_1.regEx)(/^go\s*(?<gover>\d+\.\d+)$/m);
const match = re.exec(content);
if (!match?.groups?.gover) {
return undefined;
}
return `^${match.groups.gover}`;
}
//# sourceMappingURL=artifacts.js.map