UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

364 lines • 16.5 kB
"use strict"; 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