/** pgh deploy <repo> [<ref>] | status | log <repo> | trigger <repo> [<ref>] | gc [<repo>] */
export const usage = `pgh deploy — CI/CD config + release log
pgh deploy status Overall release status
pgh deploy log <repo> Releases for one repo
pgh deploy trigger <repo> [<ref>] Build a release
pgh deploy <repo> [<ref>] Shorthand for trigger
pgh deploy gc [<repo>] [--keep N] DELETE stale release versions
pgh deploy gc [<repo>] --dry-run List what gc WOULD delete
Default ref: refs/heads/master
Default --keep: 3 newest unpinned versions per repo.
gc never touches a version a CHANNEL POINTS AT — pinning to a channel is how you
say "keep this one". Everything else is a build artifact: release versions are
plain files with no dedup, so an app publishing a large bundle accumulates it
once per version, forever, unless something collects it.
pgh deploy gc hyper-tps --dry-run # always look first
pgh deploy gc hyper-tps --keep 1 # keep only the newest + pinned`
const DEFAULT_REF = 'refs/heads/master'
export async function run (client, args) {
const sub = args[0]
if (!sub) throw new Error(usage)
if (sub === 'status') return client.call('release-status', {})
if (sub === 'log') {
const repo = args[1]
if (!repo) throw new Error('usage: pgh deploy log <repo>')
return client.call('list-repo-releases', { repo })
}
if (sub === 'trigger') {
const repo = args[1]
const ref = args[2] || DEFAULT_REF
if (!repo) throw new Error('usage: pgh deploy trigger <repo> [<ref>]')
return client.call('build-release', { repo, ref })
}
if (sub === 'gc') {
// The daemon has had gc-releases since the release manager was written; it
// had no way to be invoked, so nothing ever collected anything.
const rest = args.slice(1)
const dryRun = rest.includes('--dry-run')
const keepIdx = rest.indexOf('--keep')
let keepN
if (keepIdx !== -1) {
keepN = Number(rest[keepIdx + 1])
if (!Number.isInteger(keepN) || keepN < 0) {
throw new Error('--keep takes a non-negative integer')
}
}
// A bare positional, not a flag, and not the value consumed by --keep.
//
// The keepIdx === -1 guard is load-bearing: without it, ABSENT --keep gives
// keepIdx + 1 === 0, which excludes the first positional — so
// `gc hyper-tps --dry-run` dropped the repo, and an omitted repo means EVERY
// repo. A scoped collection would have silently become a fleet-wide one.
const repo = rest.find((a, i) =>
!a.startsWith('--') && (keepIdx === -1 || i !== keepIdx + 1))
return client.call('gc-releases', { repo, keepN, dryRun })
}
// Default: treat first arg as repo name → trigger build
const ref = args[1] || DEFAULT_REF
return client.call('build-release', { repo: sub, ref })
}