Files
gstack/lib/restamp-release.ts
T
Garry Tanandcapy-ai[bot] 9e826b2994 feat(release): versions at release under merge: direct (GSTA-258 L2 PR 2) (#3978)
* feat(release): versions at release under merge: direct (gstack-release-stamp open, restamp --release, land --pin on release/<version>; GSTA-258 L2 PR 2)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(release): commit only the stamped files, leave no local release branch, reopen replaces the branch; regenerate doctor anchors

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(release): land a release head only on the main it was stamped on

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(release): review round — refresh notes before the release freeze check, check manifest/lockfile/CHANGELOG headings and every release output, branch names the stamped version, reuse an open release PR; restamp stays under 800 lines

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(release): round 2 — a deleted release output is refused, a release that merged onto a moved main is reported, branch-name test

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(release): released CHANGELOG sections must stay byte-identical, rerun land checks the stamped base, --bump refused with --release, open ignores fork PRs

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(release): name the release PR where docs said versions are set at every merge

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(release): a release that deletes a release output is refused by name, before the release tool runs

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(land): the release rerun reads the pin's parent after fetching the pin; tests for the rerun, the released-section guard and the fork filter

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(release): a release may not delete a release output; the release land rerun reads the pin's parent after fetching

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(release): drop the ship-policy.md re-audit sentences (docs risk accepted by GSTA-258; they go into the follow-up)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-04 03:38:56 +00:00

18 lines
1.5 KiB
TypeScript

/**
* restamp-release — the precondition of `gstack-restamp --release` (lib/release-stamp.ts, docs/ship-policy.md#release-stamp):
* a release stamp runs on a clean checkout of origin/<base> under changelog_fragments, so the release PR carries only the stamp.
*/
import { spawnSync } from 'node:child_process';
const git = (cwd: string, args: string[]) => (spawnSync('git', args, { cwd, encoding: 'utf8', timeout: 60_000 }).stdout ?? '').trim();
/** Why a release stamp cannot run here, or null when it can. */
export function releaseRefusal(repoRoot: string, base: string, baseRef: string | undefined, fragDir: string | null): { message: string; fix?: string } | null {
if (!fragDir) return { message: '--release needs changelog_fragments in the base policy (a release folds the fragments); nothing written' };
const tip = git(repoRoot, ['rev-parse', '--verify', '--quiet', `${baseRef ?? `origin/${base}`}^{commit}`]);
if (!tip || git(repoRoot, ['rev-parse', 'HEAD']) !== tip) return { message: `--release runs on a checkout of origin/${base} (${tip.slice(0, 12) || 'unresolved'}), not on ${git(repoRoot, ['rev-parse', '--short', 'HEAD'])}; nothing written`, fix: `git fetch origin ${base} && git checkout --detach origin/${base}` };
const dirty = git(repoRoot, ['status', '--porcelain', '--untracked-files=no']);
if (dirty) return { message: `--release needs a clean checkout; commit or stash: ${dirty.split('\n').slice(0, 5).map(l => l.slice(3)).join(', ')}; nothing written` };
return null;
}