diff --git a/lib/workers/branch/index.spec.ts b/lib/workers/branch/index.spec.ts index 883067782c0fac4e44500338149904eccaa04f3e..d2ef81e49890d8e4d4ff8acaaca6f90aeb5185a3 100644 --- a/lib/workers/branch/index.spec.ts +++ b/lib/workers/branch/index.spec.ts @@ -687,6 +687,16 @@ describe('workers/branch', () => { localDir: '/localDir', allowedPostUpgradeCommands: ['^echo {{{versioning}}}$'], allowPostUpgradeCommandTemplating: true, + upgrades: [ + { + ...defaultConfig, + depName: 'some-dep-name', + postUpgradeTasks: { + commands: ['echo {{{versioning}}}', 'disallowed task'], + fileFilters: ['modified_file', 'deleted_file'], + }, + } as never, + ], }); expect(result).toEqual(ProcessBranchResult.Done); @@ -742,6 +752,16 @@ describe('workers/branch', () => { localDir: '/localDir', allowedPostUpgradeCommands: ['^echo {{{versioning}}}$'], allowPostUpgradeCommandTemplating: false, + upgrades: [ + { + ...defaultConfig, + depName: 'some-dep-name', + postUpgradeTasks: { + commands: ['echo {{{versioning}}}', 'disallowed task'], + fileFilters: ['modified_file', 'deleted_file'], + }, + } as never, + ], }); expect(result).toEqual(ProcessBranchResult.Done); @@ -749,5 +769,136 @@ describe('workers/branch', () => { cwd: '/localDir', }); }); + + it('executes post-upgrade tasks with multiple dependecy in one branch', async () => { + const updatedPackageFile: File = { + name: 'pom.xml', + contents: 'pom.xml file contents', + }; + getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ + updatedPackageFiles: [updatedPackageFile], + artifactErrors: [], + } as never); + npmPostExtract.getAdditionalFiles.mockResolvedValueOnce({ + artifactErrors: [], + updatedArtifacts: [ + { + name: 'yarn.lock', + contents: Buffer.from([1, 2, 3]) /* Binary content */, + }, + ], + } as never); + git.branchExists.mockReturnValueOnce(true); + platform.getBranchPr.mockResolvedValueOnce({ + title: 'rebase!', + state: PrState.Open, + body: `- [x] <!-- rebase-check -->`, + } as never); + git.isBranchModified.mockResolvedValueOnce(true); + git.getRepoStatus + .mockResolvedValueOnce({ + modified: ['modified_file', 'modified_then_deleted_file'], + not_added: [], + deleted: ['deleted_file', 'deleted_then_created_file'], + } as StatusResult) + .mockResolvedValueOnce({ + modified: ['modified_file', 'deleted_then_created_file'], + not_added: [], + deleted: ['deleted_file', 'modified_then_deleted_file'], + } as StatusResult); + global.trustLevel = 'high'; + + fs.outputFile.mockReturnValue(); + fs.readFile + .mockResolvedValueOnce(Buffer.from('modified file content')) + .mockResolvedValueOnce(Buffer.from('this file will not exists')) + .mockResolvedValueOnce(Buffer.from('modified file content again')) + .mockResolvedValueOnce(Buffer.from('this file was once deleted')); + + schedule.isScheduledNow.mockReturnValueOnce(false); + commit.commitFilesToBranch.mockResolvedValueOnce(null); + + const inconfig = { + ...config, + postUpgradeTasks: { + commands: ['echo {{{depName}}}', 'disallowed task'], + fileFilters: [ + 'modified_file', + 'deleted_file', + 'deleted_then_created_file', + 'modified_then_deleted_file', + ], + }, + localDir: '/localDir', + allowedPostUpgradeCommands: ['^echo {{{depName}}}$'], + allowPostUpgradeCommandTemplating: true, + upgrades: [ + { + ...defaultConfig, + depName: 'some-dep-name-1', + postUpgradeTasks: { + commands: ['echo {{{depName}}}', 'disallowed task'], + fileFilters: [ + 'modified_file', + 'deleted_file', + 'deleted_then_created_file', + 'modified_then_deleted_file', + ], + }, + } as never, + { + ...defaultConfig, + depName: 'some-dep-name-2', + postUpgradeTasks: { + commands: ['echo {{{depName}}}', 'disallowed task'], + fileFilters: [ + 'modified_file', + 'deleted_file', + 'deleted_then_created_file', + 'modified_then_deleted_file', + ], + }, + } as never, + ], + }; + + const result = await branchWorker.processBranch(inconfig); + + expect(result).toEqual(ProcessBranchResult.Done); + expect(exec.exec).toHaveBeenNthCalledWith(1, 'echo some-dep-name-1', { + cwd: '/localDir', + }); + expect(exec.exec).toHaveBeenNthCalledWith(2, 'echo some-dep-name-2', { + cwd: '/localDir', + }); + expect(exec.exec).toHaveBeenCalledTimes(2); + expect( + (commit.commitFilesToBranch.mock.calls[0][0].updatedArtifacts.find( + (f) => f.name === 'modified_file' + ).contents as Buffer).toString() + ).toBe('modified file content again'); + expect( + (commit.commitFilesToBranch.mock.calls[0][0].updatedArtifacts.find( + (f) => f.name === 'deleted_then_created_file' + ).contents as Buffer).toString() + ).toBe('this file was once deleted'); + expect( + commit.commitFilesToBranch.mock.calls[0][0].updatedArtifacts.find( + (f) => + f.contents === 'deleted_then_created_file' && f.name === '|delete|' + ) + ).toBeUndefined(); + expect( + commit.commitFilesToBranch.mock.calls[0][0].updatedArtifacts.find( + (f) => f.name === 'modified_then_deleted_file' + ) + ).toBeUndefined(); + expect( + commit.commitFilesToBranch.mock.calls[0][0].updatedArtifacts.find( + (f) => + f.contents === 'modified_then_deleted_file' && f.name === '|delete|' + ) + ).not.toBeUndefined(); + }); }); }); diff --git a/lib/workers/branch/index.ts b/lib/workers/branch/index.ts index 25d05a5cfdf954f91c00387aee2f98537a227ccb..ee876a14c9ea14a960f95507258a5fd03a22685b 100644 --- a/lib/workers/branch/index.ts +++ b/lib/workers/branch/index.ts @@ -12,7 +12,7 @@ import { SYSTEM_INSUFFICIENT_DISK_SPACE, WORKER_FILE_UPDATE_FAILED, } from '../../constants/error-messages'; -import { logger } from '../../logger'; +import { addMeta, logger, removeMeta } from '../../logger'; import { getAdditionalFiles } from '../../manager/npm/post-update'; import { Pr, platform } from '../../platform'; import { BranchStatus, PrState } from '../../types'; @@ -342,97 +342,116 @@ export async function processBranch( global.trustLevel === 'high' && is.nonEmptyArray(config.allowedPostUpgradeCommands) ) { - logger.debug( - { - tasks: config.postUpgradeTasks, - allowedCommands: config.allowedPostUpgradeCommands, - }, - 'Checking for post-upgrade tasks' - ); - const commands = config.postUpgradeTasks.commands || []; - const fileFilters = config.postUpgradeTasks.fileFilters || []; + for (const upgrade of config.upgrades) { + addMeta({ dep: upgrade.depName }); + logger.trace( + { + tasks: upgrade.postUpgradeTasks, + allowedCommands: config.allowedPostUpgradeCommands, + }, + 'Checking for post-upgrade tasks' + ); + const commands = upgrade.postUpgradeTasks.commands || []; + const fileFilters = upgrade.postUpgradeTasks.fileFilters || []; - if (is.nonEmptyArray(commands)) { - // Persist updated files in file system so any executed commands can see them - for (const file of config.updatedPackageFiles.concat( - config.updatedArtifacts - )) { - if (file.name !== '|delete|') { - let contents; - if (typeof file.contents === 'string') { - contents = Buffer.from(file.contents); - } else { - contents = file.contents; + if (is.nonEmptyArray(commands)) { + // Persist updated files in file system so any executed commands can see them + for (const file of config.updatedPackageFiles.concat( + config.updatedArtifacts + )) { + if (file.name !== '|delete|') { + let contents; + if (typeof file.contents === 'string') { + contents = Buffer.from(file.contents); + } else { + contents = file.contents; + } + await writeLocalFile(file.name, contents); } - await writeLocalFile(file.name, contents); } - } - for (const cmd of commands) { - if ( - !config.allowedPostUpgradeCommands.some((pattern) => - regEx(pattern).test(cmd) - ) - ) { - logger.warn( - { - cmd, - allowedPostUpgradeCommands: config.allowedPostUpgradeCommands, - }, - 'Post-upgrade task did not match any on allowed list' - ); - } else { - const compiledCmd = config.allowPostUpgradeCommandTemplating - ? template.compile(cmd, config) - : cmd; + for (const cmd of commands) { + if ( + !config.allowedPostUpgradeCommands.some((pattern) => + regEx(pattern).test(cmd) + ) + ) { + logger.warn( + { + cmd, + allowedPostUpgradeCommands: config.allowedPostUpgradeCommands, + }, + 'Post-upgrade task did not match any on allowed list' + ); + } else { + const compiledCmd = config.allowPostUpgradeCommandTemplating + ? template.compile(cmd, upgrade) + : cmd; - logger.debug({ cmd: compiledCmd }, 'Executing post-upgrade task'); + logger.debug({ cmd: compiledCmd }, 'Executing post-upgrade task'); - const execResult = await exec(compiledCmd, { - cwd: config.localDir, - }); + const execResult = await exec(compiledCmd, { + cwd: config.localDir, + }); - logger.debug( - { cmd: compiledCmd, ...execResult }, - 'Executed post-upgrade task' - ); + logger.debug( + { cmd: compiledCmd, ...execResult }, + 'Executed post-upgrade task' + ); + } } - } - const status = await getRepoStatus(); + const status = await getRepoStatus(); - for (const relativePath of status.modified.concat(status.not_added)) { - for (const pattern of fileFilters) { - if (minimatch(relativePath, pattern)) { - logger.debug( - { file: relativePath, pattern }, - 'Post-upgrade file saved' - ); - const existingContent = await readLocalFile(relativePath); - config.updatedArtifacts.push({ - name: relativePath, - contents: existingContent, - }); + for (const relativePath of status.modified.concat(status.not_added)) { + for (const pattern of fileFilters) { + if (minimatch(relativePath, pattern)) { + logger.debug( + { file: relativePath, pattern }, + 'Post-upgrade file saved' + ); + const existingContent = await readLocalFile(relativePath); + const existingUpdatedArtifacts = config.updatedArtifacts.find( + (ua) => ua.name === relativePath + ); + if (existingUpdatedArtifacts) { + existingUpdatedArtifacts.contents = existingContent; + } else { + config.updatedArtifacts.push({ + name: relativePath, + contents: existingContent, + }); + } + // If the file is deleted by a previous post-update command, remove the deletion from updatedArtifacts + config.updatedArtifacts = config.updatedArtifacts.filter( + (ua) => ua.name !== '|delete|' || ua.contents !== relativePath + ); + } } } - } - for (const relativePath of status.deleted || []) { - for (const pattern of fileFilters) { - if (minimatch(relativePath, pattern)) { - logger.debug( - { file: relativePath, pattern }, - 'Post-upgrade file removed' - ); - config.updatedArtifacts.push({ - name: '|delete|', - contents: relativePath, - }); + for (const relativePath of status.deleted || []) { + for (const pattern of fileFilters) { + if (minimatch(relativePath, pattern)) { + logger.debug( + { file: relativePath, pattern }, + 'Post-upgrade file removed' + ); + config.updatedArtifacts.push({ + name: '|delete|', + contents: relativePath, + }); + // If the file is created or modified by a previous post-update command, remove the modification from updatedArtifacts + config.updatedArtifacts = config.updatedArtifacts.filter( + (ua) => ua.name !== relativePath + ); + } } } } } } + removeMeta(['dep']); if (config.artifactErrors?.length) { if (config.releaseTimestamp) {