[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
1// Pack the current tag into one or more tarballs in `PACK_OUT_DIR`.
2// The composite action then hands those tarballs to a workflow artifact
3// upload step; the `publish` job in the same workflow run downloads the
4// artifact and runs `npm stage publish` on the tarball without ever
5// installing the package's dependencies.
6//
7// Two paths:
8// - pnpm-lock.yaml present: `pnpm pack` so `catalog:` specifiers get
9// resolved into the tarball.
10// - otherwise: `npm pack`.
11//
12// Env:
13// PACK_OUT_DIR directory to write the `*.tgz` into (created if
14// missing). The action then uploads its contents as
15// a workflow artifact.
16// GITHUB_OUTPUT set by the runner; receives `files=<json array>`.
17// GITHUB_REF must be `refs/tags/v*` (set automatically)
18// PACKAGES newline-separated list of publishable workspace
19// paths/globs; when set, the script packs each
20// listed workspace instead of the root.
21
22import process from 'node:process'
23import { execFileSync } from 'node:child_process'
24import { appendFileSync, existsSync, mkdirSync, statSync } from 'node:fs'
25import { resolve } from 'node:path'
26
27import { parseFilenames } from './_pack-json.ts'
28import { resolveWorkspaces } from './_workspaces.ts'
29
30function runCapture (cmd: string, args: string[], cwd?: string): string {
31 console.log('$', cmd, ...args, cwd ? `(cwd: ${cwd})` : '')
32 return execFileSync(cmd, args, {
33 stdio: ['ignore', 'pipe', 'inherit'],
34 encoding: 'utf8',
35 maxBuffer: 16 * 1024 * 1024,
36 cwd,
37 })
38}
39
40function main () {
41 const ref = process.env.GITHUB_REF ?? ''
42 if (!ref.startsWith('refs/tags/v')) {
43 throw new Error(`GITHUB_REF must be a 'refs/tags/v*' ref, got '${ref || '<unset>'}'`)
44 }
45 const tag = ref.slice('refs/tags/'.length)
46 if (!/^v\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/.test(tag)) {
47 throw new Error(`Refusing to pack: tag "${tag}" is not strict semver`)
48 }
49
50 const outDir = process.env.PACK_OUT_DIR
51 if (!outDir) throw new Error('PACK_OUT_DIR is required')
52 mkdirSync(outDir, { recursive: true })
53
54 const hasPnpmLock = existsSync(resolve(process.cwd(), 'pnpm-lock.yaml'))
55 const packagesInput = process.env.PACKAGES?.trim() ?? ''
56 const targets = packagesInput.length
57 ? resolveWorkspaces(process.cwd(), packagesInput).map(ws => ({ name: ws.name, cwd: ws.dir }))
58 : [{ name: '<root>', cwd: process.cwd() }]
59
60 const filenames: string[] = []
61 for (const target of targets) {
62 const stdout = hasPnpmLock
63 ? runCapture('pnpm', ['pack', '--pack-destination', outDir, '--json'], target.cwd)
64 : runCapture('npm', ['pack', '--pack-destination', outDir, '--json', '--silent'], target.cwd)
65 const packed = parseFilenames(stdout)
66 for (const filename of packed) {
67 if (filenames.includes(filename)) {
68 throw new Error(`Pack tool produced duplicate tarball '${filename}' (from ${target.name}); workspace package names and versions must be unique.`)
69 }
70 filenames.push(filename)
71 }
72 }
73
74 for (const filename of filenames) {
75 const tarballPath = resolve(outDir, filename)
76 if (!existsSync(tarballPath)) {
77 throw new Error(`Pack tool reported '${filename}' but it is not present in ${outDir}`)
78 }
79 const size = statSync(tarballPath).size
80 console.log(`Packed ${filename} (${size} bytes) for ${tag}`)
81 }
82
83 const githubOutput = process.env.GITHUB_OUTPUT
84 if (githubOutput) {
85 appendFileSync(githubOutput, `files=${JSON.stringify(filenames)}\n`)
86 }
87}
88
89try {
90 main()
91}
92catch (err) {
93 console.error(err)
94 process.exit(1)
95}