diff --git a/src/lib/cherrypickAndCreateTargetPullRequest/cherrypickAndCreateTargetPullRequest.test.ts b/src/lib/cherrypickAndCreateTargetPullRequest/cherrypickAndCreateTargetPullRequest.test.ts index 3066492f..69b6bf72 100644 --- a/src/lib/cherrypickAndCreateTargetPullRequest/cherrypickAndCreateTargetPullRequest.test.ts +++ b/src/lib/cherrypickAndCreateTargetPullRequest/cherrypickAndCreateTargetPullRequest.test.ts @@ -28,7 +28,7 @@ describe('cherrypickAndCreateTargetPullRequest', () => { execSpy = jest .spyOn(childProcess, 'spawnPromise') - // mock all exec commands to respond without errors + // mock all spawn commands to respond without errors .mockResolvedValue({ stdout: '', stderr: '', code: 0, cmdArgs: [] }); consoleLogSpy = jest.spyOn(logger, 'consoleLog'); diff --git a/src/lib/child-process-promisified.ts b/src/lib/child-process-promisified.ts index 3756f260..9fac2f86 100644 --- a/src/lib/child-process-promisified.ts +++ b/src/lib/child-process-promisified.ts @@ -1,22 +1,28 @@ import childProcess from 'child_process'; -import { promisify } from 'util'; import apm from 'elastic-apm-node'; import { logger } from './logger'; -const execPromisified = promisify(childProcess.exec); -export async function exec( - cmd: string, - options: childProcess.ExecOptions & { cwd: string }, -) { - const res = await execPromisified(cmd, { - maxBuffer: 100 * 1024 * 1024, - ...options, +type SpawnErrorContext = { + cmdArgs: ReadonlyArray; + code: number; + stderr: string; + stdout: string; +}; - // ensure that git commands return english error messages - env: { ...process.env, LANG: 'en_US' }, - }); +export class SpawnError extends Error { + context: SpawnErrorContext; + constructor(context: SpawnErrorContext) { + const cmdArgs = context.cmdArgs.join(' '); + const message = `Code: ${ + context.code + }, Args: "${cmdArgs}", Message: ${context.stderr.trim()}`; - return res; + super(message); + Error.captureStackTrace(this, SpawnError); + this.name = 'SpawnError'; + this.message = message; + this.context = context; + } } type SpawnPromiseResponse = { @@ -83,17 +89,6 @@ export async function spawnPromise( }); } -function startSpawnSpan(cmd: string, cmdArgs: ReadonlyArray) { - const span = apm.startSpan(`Spawn: "${cmd}"`); - const fullCmd = getFullCmd(cmd, cmdArgs); - const firstCmdArg = cmdArgs.filter( - (cmdArg) => !cmdArg.startsWith('--') && !cmdArg.startsWith('-'), - )[0]; - span?.setType('spawn', cmd, firstCmdArg); - span?.setLabel(`cmd`, fullCmd); - return span; -} - export const spawnStream = (cmd: string, cmdArgs: ReadonlyArray) => { const spawnSpan = startSpawnSpan(cmd, cmdArgs); @@ -111,29 +106,17 @@ export const spawnStream = (cmd: string, cmdArgs: ReadonlyArray) => { return res; }; -function getFullCmd(cmd: string, cmdArgs: ReadonlyArray) { - return `${cmd} ${cmdArgs.join(' ')}`; +function startSpawnSpan(cmd: string, cmdArgs: ReadonlyArray) { + const span = apm.startSpan(`Spawn: "${cmd}"`); + const fullCmd = getFullCmd(cmd, cmdArgs); + const firstCmdArg = cmdArgs.filter( + (cmdArg) => !cmdArg.startsWith('--') && !cmdArg.startsWith('-'), + )[0]; + span?.setType('spawn', cmd, firstCmdArg); + span?.setLabel(`cmd`, fullCmd); + return span; } -export type SpawnErrorContext = { - cmdArgs: ReadonlyArray; - code: number; - stderr: string; - stdout: string; -}; - -export class SpawnError extends Error { - context: SpawnErrorContext; - constructor(context: SpawnErrorContext) { - const cmdArgs = context.cmdArgs.join(' '); - const message = `Code: ${ - context.code - }, Args: "${cmdArgs}", Message: ${context.stderr.trim()}`; - - super(message); - Error.captureStackTrace(this, SpawnError); - this.name = 'SpawnError'; - this.message = message; - this.context = context; - } +function getFullCmd(cmd: string, cmdArgs: ReadonlyArray) { + return `${cmd} ${cmdArgs.join(' ')}`; } diff --git a/src/lib/git.private.test.ts b/src/lib/git.private.test.ts index c6a05298..13d16dbd 100644 --- a/src/lib/git.private.test.ts +++ b/src/lib/git.private.test.ts @@ -4,6 +4,7 @@ import path from 'path'; import makeDir from 'make-dir'; import { Commit } from '../entrypoint.api'; import { ValidConfigOptions } from '../options/options'; +import { exec } from '../test/childProcessHelper'; import { getDevAccessToken } from '../test/private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from '../test/sandbox'; import * as childProcess from './child-process-promisified'; @@ -178,7 +179,7 @@ describe('git.private', () => { targetBranch: '7.x', backportBranch: 'my-backport-branch', }); - await childProcess.exec( + await exec( `git remote add sorenlouv https://x-access-token:${accessToken}@github.com/sorenlouv/repo-with-conflicts.git`, { cwd }, ); @@ -380,7 +381,7 @@ describe('git.private', () => { cwd, ); - await childProcess.exec('git checkout 7.x', { cwd }); + await exec('git checkout 7.x', { cwd }); // cherry-pick file try { @@ -394,7 +395,7 @@ describe('git.private', () => { } // disregard conflicts and stage all files - await childProcess.exec('git add -A', { cwd }); + await exec('git add -A', { cwd }); await commitChanges({ commit, commitAuthor, options }); @@ -779,10 +780,7 @@ async function getCurrentSha(cwd: string) { } async function getCurrentBranchName(cwd: string) { - const { stdout } = await childProcess.exec( - 'git rev-parse --abbrev-ref HEAD', - { cwd }, - ); + const { stdout } = await exec('git rev-parse --abbrev-ref HEAD', { cwd }); return stdout.trim(); } diff --git a/src/lib/github/v4/getRepoOwnerAndNameFromGitRemotes.private.test.ts b/src/lib/github/v4/getRepoOwnerAndNameFromGitRemotes.private.test.ts index 52d1aa9f..a87c1f32 100644 --- a/src/lib/github/v4/getRepoOwnerAndNameFromGitRemotes.private.test.ts +++ b/src/lib/github/v4/getRepoOwnerAndNameFromGitRemotes.private.test.ts @@ -1,6 +1,6 @@ +import { exec } from '../../../test/childProcessHelper'; import { getDevAccessToken } from '../../../test/private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from '../../../test/sandbox'; -import * as childProcess from '../../child-process-promisified'; import { getRepoOwnerAndNameFromGitRemotes } from './getRepoOwnerAndNameFromGitRemotes'; const sandboxPath = getSandboxPath({ filename: __filename }); @@ -11,8 +11,8 @@ describe('fetchRemoteProjectConfig', () => { it('retrives the original owner from github', async () => { await resetSandbox(sandboxPath); const execOpts = { cwd: sandboxPath }; - await childProcess.exec(`git init`, execOpts); - await childProcess.exec( + await exec(`git init`, execOpts); + await exec( `git remote add sorenlouv git@github.com:sorenlouv/kibana.git`, execOpts, ); @@ -33,16 +33,10 @@ describe('fetchRemoteProjectConfig', () => { it('swallows the error and returns empty', async () => { await resetSandbox(sandboxPath); const execOpts = { cwd: sandboxPath }; - await childProcess.exec(`git init`, execOpts); - await childProcess.exec( - `git remote add foo git@github.com:foo/kibana.git`, - execOpts, - ); + await exec(`git init`, execOpts); + await exec(`git remote add foo git@github.com:foo/kibana.git`, execOpts); - await childProcess.exec( - `git remote add bar git@github.com:bar/kibana.git`, - execOpts, - ); + await exec(`git remote add bar git@github.com:bar/kibana.git`, execOpts); expect( await getRepoOwnerAndNameFromGitRemotes({ @@ -57,7 +51,7 @@ describe('fetchRemoteProjectConfig', () => { it('returns empty', async () => { await resetSandbox(sandboxPath); const execOpts = { cwd: sandboxPath }; - await childProcess.exec(`git init`, execOpts); + await exec(`git init`, execOpts); expect( await getRepoOwnerAndNameFromGitRemotes({ diff --git a/src/test/childProcessHelper.ts b/src/test/childProcessHelper.ts new file mode 100644 index 00000000..e31d234f --- /dev/null +++ b/src/test/childProcessHelper.ts @@ -0,0 +1,18 @@ +import childProcess from 'child_process'; +import { promisify } from 'util'; +const execPromisified = promisify(childProcess.exec); + +export async function exec( + cmd: string, + options: childProcess.ExecOptions & { cwd: string }, +) { + const res = await execPromisified(cmd, { + maxBuffer: 100 * 1024 * 1024, + ...options, + + // ensure that git commands return english error messages + env: { ...process.env, LANG: 'en_US' }, + }); + + return res; +} diff --git a/src/test/e2e/cli/commit-author.private.test.ts b/src/test/e2e/cli/commit-author.private.test.ts index a313665c..53aafe97 100644 --- a/src/test/e2e/cli/commit-author.private.test.ts +++ b/src/test/e2e/cli/commit-author.private.test.ts @@ -1,4 +1,4 @@ -import { exec } from '../../../lib/child-process-promisified'; +import { exec } from '../../childProcessHelper'; import { getDevAccessToken } from '../../private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from '../../sandbox'; import { runBackportViaCli } from './runBackportViaCli'; diff --git a/src/test/e2e/cli/different-merge-strategies.private.test.ts b/src/test/e2e/cli/different-merge-strategies.private.test.ts index 895d234a..ce1527ae 100644 --- a/src/test/e2e/cli/different-merge-strategies.private.test.ts +++ b/src/test/e2e/cli/different-merge-strategies.private.test.ts @@ -1,5 +1,5 @@ import fs from 'fs/promises'; -import { exec } from '../../../lib/child-process-promisified'; +import { exec } from '../../childProcessHelper'; import { getDevAccessToken } from '../../private/getDevAccessToken'; import { replaceStringAndLinebreaks } from '../../replaceStringAndLinebreaks'; import { getSandboxPath, resetSandbox } from '../../sandbox'; diff --git a/src/test/e2e/cli/entrypoint.cli.private.test.ts b/src/test/e2e/cli/entrypoint.cli.private.test.ts index b42061bf..9b1a1921 100644 --- a/src/test/e2e/cli/entrypoint.cli.private.test.ts +++ b/src/test/e2e/cli/entrypoint.cli.private.test.ts @@ -1,5 +1,5 @@ -import { exec } from '../../../lib/child-process-promisified'; import * as packageVersion from '../../../utils/packageVersion'; +import { exec } from '../../childProcessHelper'; import { getDevAccessToken } from '../../private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from '../../sandbox'; import { runBackportViaCli } from './runBackportViaCli'; diff --git a/src/test/e2e/cli/gracefully-handles-corrupt-repo.private.test.ts b/src/test/e2e/cli/gracefully-handles-corrupt-repo.private.test.ts index 2911974e..db34d929 100644 --- a/src/test/e2e/cli/gracefully-handles-corrupt-repo.private.test.ts +++ b/src/test/e2e/cli/gracefully-handles-corrupt-repo.private.test.ts @@ -1,4 +1,4 @@ -import { exec } from '../../../lib/child-process-promisified'; +import { exec } from '../../childProcessHelper'; import { getDevAccessToken } from '../../private/getDevAccessToken'; import { getSandboxPath, resetSandbox } from '../../sandbox'; import { runBackportViaCli } from './runBackportViaCli'; diff --git a/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts b/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts index 8d4fb07b..425855ca 100644 --- a/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts +++ b/src/test/e2e/cli/test-that-repo-can-be-cloned.private.test.ts @@ -1,4 +1,4 @@ -import { exec } from '../../../lib/child-process-promisified'; +import { exec } from '../../childProcessHelper'; import { getDevAccessToken } from '../../private/getDevAccessToken'; import { replaceStringAndLinebreaks } from '../../replaceStringAndLinebreaks'; import { getSandboxPath, resetSandbox } from '../../sandbox';