|
1 | 1 | import path from 'node:path'; |
2 | 2 | import fs from 'node:fs/promises'; |
| 3 | +import { tmpdir } from 'node:os'; |
| 4 | +import { pipeline } from 'node:stream/promises'; |
3 | 5 | import semver from 'semver'; |
4 | 6 | import * as gst from 'git-secure-tag'; |
5 | 7 |
|
@@ -196,31 +198,44 @@ export default class ReleasePromotion extends Session { |
196 | 198 |
|
197 | 199 | async verifyTagSignature(version) { |
198 | 200 | const { cli } = this; |
199 | | - const verifyTagPattern = /gpg:[^\n]+\ngpg:\s+using \w+ key ([^\n]+)\ngpg:\s+issuer "([^"]+)"\ngpg:\s+Good signature from (?:"[^"]+"(?: \[ultimate\])?\ngpg:\s+aka )*"([^<]+) <\2>"/; |
200 | | - const [verifyTagOutput, haystack] = await Promise.all([forceRunAsync( |
201 | | - 'git', ['--no-pager', |
202 | | - 'verify-tag', |
203 | | - `v${version}` |
204 | | - ], { ignoreFailure: false, captureStderr: true }), fs.readFile('README.md')]); |
205 | | - const match = verifyTagPattern.exec(verifyTagOutput); |
206 | | - if (match == null) { |
207 | | - cli.warn('git was not able to verify the tag:'); |
208 | | - cli.info(verifyTagOutput); |
209 | | - } else { |
210 | | - const [, keyID, email, name] = match; |
211 | | - const needle = `* **${name}** <<${email}>>\n ${'`'}${keyID}${'`'}`; |
212 | | - if (haystack.includes(needle)) { |
213 | | - return; |
| 201 | + |
| 202 | + cli.startSpinner('Downloading active releasers keyring from nodejs/release-keys...'); |
| 203 | + const [keyRingStream, [GNUPGHOME, keyRingFd]] = await Promise.all([ |
| 204 | + fetch('https://github.com/nodejs/release-keys/raw/HEAD/gpg-only-active-keys/pubring.kbx'), |
| 205 | + fs.mkdtemp(path.join(tmpdir(), 'ncu-')) |
| 206 | + .then(async d => [d, await fs.open(path.join(d, 'pubring.kbx'), 'w')]), |
| 207 | + ]); |
| 208 | + if (!keyRingStream.ok) throw new Error('Failed to download keyring', { cause: keyRingStream }); |
| 209 | + await pipeline(keyRingStream.body, keyRingFd.createWriteStream()); |
| 210 | + cli.stopSpinner('Active releasers keyring stored in temp directory'); |
| 211 | + |
| 212 | + try { |
| 213 | + await forceRunAsync( |
| 214 | + 'git', ['--no-pager', |
| 215 | + 'verify-tag', |
| 216 | + `v${version}` |
| 217 | + ], { |
| 218 | + ignoreFailure: false, |
| 219 | + spawnArgs: { env: { ...process.env, GNUPGHOME } }, |
| 220 | + }); |
| 221 | + cli.ok('git tag signature verified'); |
| 222 | + } catch (cause) { |
| 223 | + cli.error('git was not able to verify the tag'); |
| 224 | + cli.warn('This means that either the tag was signed with the wrong key,'); |
| 225 | + cli.warn('or that nodejs/release-keys contains outdated information.'); |
| 226 | + cli.warn('The release should not proceed.'); |
| 227 | + if (!await cli.prompt('Do you want to proceed anyway?', { defaultAnswer: false })) { |
| 228 | + if (await cli.prompt('Do you want to delete the local tag?')) { |
| 229 | + await forceRunAsync('git', ['tag', '-d', `v${version}`]); |
| 230 | + } else { |
| 231 | + cli.info(`Run 'git tag -d v${version}' to remove the local tag.`); |
| 232 | + } |
| 233 | + throw new Error('Aborted', { cause }); |
214 | 234 | } |
215 | | - cli.warn('Tag was signed with an undocumented identity/key pair!'); |
216 | | - cli.info('Expected to find the following entry in the README:'); |
217 | | - cli.info(needle); |
218 | | - cli.info('If you are using a subkey, it might be OK.'); |
219 | | - } |
220 | | - cli.info(`If that doesn't sound right, consider removing the tag (git tag -d v${version |
221 | | - }), check your local config, and start the process over.`); |
222 | | - if (!await cli.prompt('Do you want to proceed anyway?', { defaultAnswer: false })) { |
223 | | - throw new Error('Aborted'); |
| 235 | + } finally { |
| 236 | + cli.startSpinner('Cleaning up temp files'); |
| 237 | + await fs.rm(GNUPGHOME, { force: true, recursive: true }); |
| 238 | + cli.stopSpinner('Temp files removed'); |
224 | 239 | } |
225 | 240 | } |
226 | 241 |
|
|
0 commit comments