[READ-ONLY] Mirror of https://github.com/danielroe/uppt. A composite GitHub Action that turns conventional commits into a draft release PR, tags the PR on merge, and stages publishing to npm via OIDC
github-action npm npmjs oidc staged-publish trusted-publishing
0

Configure Feed

Select the types of activity you want to include in your feed.

uppt / scripts / publish.ts
2.9 kB 87 lines
1// Stage-publish prebuilt tarball(s) to npm using OIDC trusted 2// publishing. The maintainer approves the staged version with 2FA on 3// npmjs.com afterwards. 4// 5// The tarball(s) were produced by `uppt/pack` in an earlier job in the 6// same workflow run and downloaded into `TARBALL_DIR` by 7// `actions/download-artifact`. When `TARBALL_FILES` is set (a JSON 8// array of filenames, emitted by `uppt/pack` as a step output), we 9// publish exactly those, in order. Otherwise we fall back to scanning 10// `TARBALL_DIR` for `*.tgz`. 11// 12// `npm publish <tarball>` doesn't run lifecycle scripts in any case 13// (the tarball is treated as an opaque artifact), but we still pass 14// `--ignore-scripts` for clarity. 15// 16// Env: 17// NPM_ACCESS `public` (default) or `restricted` 18// TARBALL_DIR directory holding the prebuilt `*.tgz` files 19// TARBALL_FILES optional JSON array of filenames within TARBALL_DIR 20 21import process from 'node:process' 22import { execFileSync } from 'node:child_process' 23import { existsSync, readdirSync } from 'node:fs' 24import { resolve } from 'node:path' 25 26function run (cmd: string, args: string[]) { 27 console.log('$', cmd, ...args) 28 execFileSync(cmd, args, { stdio: 'inherit' }) 29} 30 31function parseTarballFiles (raw: string): string[] { 32 let parsed: unknown 33 try { 34 parsed = JSON.parse(raw) 35 } 36 catch (err) { 37 throw new Error(`TARBALL_FILES is not valid JSON: ${(err as Error).message}`) 38 } 39 if (!Array.isArray(parsed)) { 40 throw new Error('TARBALL_FILES must be a JSON array of filenames') 41 } 42 for (const entry of parsed) { 43 if (typeof entry !== 'string' || !entry.endsWith('.tgz')) { 44 throw new Error(`TARBALL_FILES contains a non-tarball entry: ${JSON.stringify(entry)}`) 45 } 46 } 47 return parsed as string[] 48} 49 50function main () { 51 const access = process.env.NPM_ACCESS === 'restricted' ? 'restricted' : 'public' 52 53 const dir = process.env.TARBALL_DIR 54 if (!dir) throw new Error('TARBALL_DIR is required') 55 if (!existsSync(dir)) throw new Error(`TARBALL_DIR does not exist: ${dir}`) 56 57 const filesEnv = process.env.TARBALL_FILES?.trim() 58 let tarballs: string[] 59 if (filesEnv) { 60 tarballs = parseTarballFiles(filesEnv) 61 if (!tarballs.length) { 62 throw new Error('TARBALL_FILES was provided but is empty') 63 } 64 } 65 else { 66 tarballs = readdirSync(dir).filter(f => f.endsWith('.tgz')).sort() 67 if (!tarballs.length) { 68 throw new Error(`No *.tgz found in ${dir}. Did the pack job upload the artifact?`) 69 } 70 } 71 72 for (const tarball of tarballs) { 73 const tarballPath = resolve(dir, tarball) 74 if (!existsSync(tarballPath)) { 75 throw new Error(`Tarball '${tarball}' is not present in ${dir}`) 76 } 77 run('npm', ['stage', 'publish', tarballPath, '--provenance', '--ignore-scripts', `--access=${access}`]) 78 } 79} 80 81try { 82 main() 83} 84catch (err) { 85 console.error(err) 86 process.exit(1) 87}