diff --git a/lib/promote_release.js b/lib/promote_release.js index d717110f..692b90c7 100644 --- a/lib/promote_release.js +++ b/lib/promote_release.js @@ -226,20 +226,28 @@ export default class ReleasePromotion extends Session { async verifyTagSignature() { const { cli, version } = this; - const [needle, haystack] = await Promise.all([forceRunAsync( + const verifyTagPattern = /gpg:[^\n]+\ngpg:\s+using RSA key ([^\n]+)\ngpg:\s+issuer "([^"]+)"\ngpg:\s+Good signature from "([^<]+) <\2>"/; + const [verifyTagOutput, haystack] = await Promise.all([forceRunAsync( 'git', ['--no-pager', - 'log', '-1', - `refs/tags/v${version}`, - '--format=* **%an** <<%ae>>\n `%GF`' - ], { captureStdout: true }), fs.readFile('README.md')]); - if (haystack.includes(needle)) { - return; + 'verify-tag', + `v${version}` + ], { ignoreFailure: false, captureStderr: true }), fs.readFile('README.md')]); + const match = verifyTagPattern.exec(verifyTagOutput); + if (match == null) { + cli.warn('git was not able to verify the tag:'); + cli.info(verifyTagOutput); + } else { + const [, keyID, email, name] = match; + const needle = `* **${name}** <<${email}>>\n ${'`'}${keyID}${'`'}`; + if (haystack.includes(needle)) { + return; + } + cli.warn('Tag was signed with an undocumented identity/key pair!'); + cli.info('Expected to find the following entry in the README:'); + cli.info(needle); + cli.info('If you are using a subkey, it might be OK.'); } - cli.warn('Tag was signed with an undocumented identity/key pair!'); - cli.info('Expected to find the following entry in the README:'); - cli.info(needle); - cli.info('If you are using a subkey, it might be OK.'); - cli.info(`Otherwise consider removing the tag (git tag -d v${version + cli.info(`If that doesn't sound right, consider removing the tag (git tag -d v${version }), check your local config, and start the process over.`); if (!await cli.prompt('Do you want to proceed anyway?', { defaultAnswer: false })) { throw new Error('Aborted'); @@ -383,7 +391,6 @@ export default class ReleasePromotion extends Session { { cause: err } ); } - await forceRunAsync('git', ['tag', '--verify', `v${version}`], { ignoreFailure: false }); this.cli.info('Using the existing tag'); } } diff --git a/lib/run.js b/lib/run.js index c9f87c70..578e8f40 100644 --- a/lib/run.js +++ b/lib/run.js @@ -12,14 +12,19 @@ function runAsyncBase(cmd, args, { ignoreFailure = true, spawnArgs, input, + captureStderr = false, captureStdout = false } = {}) { if (cmd instanceof URL) { cmd = fileURLToPath(cmd); } let stdio = 'inherit'; - if (captureStdout || input != null) { - stdio = [input == null ? 'inherit' : 'pipe', captureStdout ? 'pipe' : 'inherit', 'inherit']; + if (captureStderr || captureStdout || input != null) { + stdio = [ + input == null ? 'inherit' : 'pipe', + captureStdout ? 'pipe' : 'inherit', + captureStderr ? 'pipe' : 'inherit' + ]; } return new Promise((resolve, reject) => { const opt = Object.assign({ @@ -30,6 +35,12 @@ function runAsyncBase(cmd, args, { debuglog('[Spawn]', `${cmd} ${(args || []).join(' ')}`, opt); } const child = spawn(cmd, args, opt); + let stderr; + if (!captureStdout && captureStderr) { + stderr = ''; + child.stderr.setEncoding('utf8'); + child.stderr.on('data', (chunk) => { stderr += chunk; }); + } let stdout; if (captureStdout) { stdout = ''; @@ -51,7 +62,7 @@ function runAsyncBase(cmd, args, { stdout = stdout.split(/\r?\n/g); if (stdout[stdout.length - 1] === '') stdout.pop(); } - return resolve(stdout); + return resolve(stdout ?? stderr); }); if (input != null) child.stdin.end(input); });