Skip to content

Commit af2618e

Browse files
committed
Guard release workflow helper docs
1 parent ef40333 commit af2618e

5 files changed

Lines changed: 98 additions & 34 deletions

File tree

.github/workflows/ray-core-release.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ name: "@razroo/ray-core Release to npm"
22

33
# Publish when a GitHub Release whose tag starts with `core-v` is published.
44
#
5-
# Cutting a release (after bumping packages/core/package.json version):
6-
# TAG="core-v$(bun -e 'console.log(require("./packages/core/package.json").version)')"
7-
# git tag -a "$TAG" -m "Release $TAG (@razroo/ray-core)" && git push origin "$TAG"
8-
# gh release create "$TAG" --generate-notes --title "$TAG"
5+
# Cut releases with the guarded helper from main:
6+
# bun run release:github -- --dry-run
7+
# bun run release:github -- --yes
98

109
on:
1110
release:

.github/workflows/ray-sdk-release.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ name: "@razroo/ray-sdk Release to npm"
33
# Publish when a GitHub Release whose tag starts with `sdk-v` is published.
44
# Publish @razroo/ray-core first if its version changed; SDK releases often follow core.
55
#
6-
# Cutting a release (after bumping packages/sdk/package.json version):
7-
# TAG="sdk-v$(bun -e 'console.log(require("./packages/sdk/package.json").version)')"
8-
# git tag -a "$TAG" -m "Release $TAG (@razroo/ray-sdk)" && git push origin "$TAG"
9-
# gh release create "$TAG" --generate-notes --title "$TAG"
6+
# Cut releases with the guarded helper from main:
7+
# bun run release:github -- --dry-run
8+
# bun run release:github -- --yes
109

1110
on:
1211
release:

docs/npm-publishing.md

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -66,43 +66,24 @@ Infrastructure-only PRs can add an empty changeset: `bunx changeset add --empty`
6666

6767
## Cut a release (GH + npm)
6868

69-
1. Ensure **`bun run version`** has been run and the version bump is committed on **`main`**.
69+
1. Ensure **`bun run version`** has been run and the version bump is committed and pushed on **`main`**.
7070

71-
2. Create **annotated tags** pointing at that commit:
71+
2. Create both annotated tags and GitHub Releases with the guarded helper:
7272

7373
```bash
74-
TAG_CORE="core-v$(bun -e 'console.log(require("./packages/core/package.json").version)')"
75-
TAG_SDK="sdk-v$(bun -e 'console.log(require("./packages/sdk/package.json").version)')"
76-
git tag -a "$TAG_CORE" -m "Release $TAG_CORE (@razroo/ray-core)"
77-
git tag -a "$TAG_SDK" -m "Release $TAG_SDK (@razroo/ray-sdk)"
78-
git push --atomic origin "$TAG_CORE" "$TAG_SDK"
74+
bun run release:github -- --dry-run
75+
bun run release:github -- --yes
7976
```
8077

81-
Push linked package tags atomically so a failed push cannot publish only one side of the release pair.
78+
The helper checks that the working tree is clean, `main` is synced with `origin/main`, package versions match, and GitHub CLI auth is available. It creates annotated `core-v…` and `sdk-v…` tags, pushes them atomically, creates both GitHub Releases, and safely reuses already-created annotated tags or releases on retry.
8279

83-
3. Create GitHub Releases (fires the npm workflows):
84-
85-
```bash
86-
gh release create "$TAG_CORE" --generate-notes --title "$TAG_CORE"
87-
gh release create "$TAG_SDK" --generate-notes --title "$TAG_SDK"
88-
```
89-
90-
4. Workflow behavior:
80+
3. Workflow behavior:
9181
- Confirms the release tag commit is reachable from `origin/main`.
9282
- Uses **`gh api`** to confirm the **`quality`** check run on the tagged commit succeeded (geometra-style gate before npm).
9383
- Runs **`packages/*/scripts/release/check-source.mjs`** so the tag matches `package.json`.
9484
- **`bun run build`**, **`bun pm pack`**, then **`npm publish <tarball> --provenance`** with OIDC provenance (`id-token: write`).
9585

96-
5. Omit or delete a faulty GitHub Release and tag before re-cutting; avoid amending published tags.
97-
98-
### One-command tags + GitHub Releases (`gh`)
99-
100-
After **`bun run version`** is committed on **`main`** and pushed (`git push origin main`), you can create both tags and GitHub Releases with:
101-
102-
```bash
103-
bun run release:github -- --dry-run # plan only
104-
bun run release:github -- --yes # tag, git push --atomic tags, gh release create ×2
105-
```
86+
4. Omit or delete a faulty GitHub Release and tag before re-cutting; avoid amending published tags.
10687

10788
Requires [**GitHub CLI**](https://cli.github.com/) (`gh`) authenticated (`gh auth login`). If a transient failure leaves local or remote annotated tags at the release commit, or creates one GitHub Release before the other, the helper reuses the safe pieces on retry and creates only what is still missing; local or remote lightweight tags, tags pointing elsewhere, and ambiguous release probes fail closed. NPM publish still runs in Actions when each release is **published**.
10889

scripts/package-runtime-coverage.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,7 @@ test("validatePackageRuntimeCoverage rejects lightweight release tag workflow co
11161116
'name: "@razroo/ray-core Release to npm"',
11171117
'# TAG="core-v$(bun -e \'console.log(require("./packages/core/package.json").version)\')"',
11181118
'# git tag "$TAG" && git push origin "$TAG"',
1119+
"# bun run release:github -- --yes",
11191120
"jobs:",
11201121
" publish:",
11211122
" if: github.repository == 'razroo/ray'",
@@ -1146,6 +1147,62 @@ test("validatePackageRuntimeCoverage rejects lightweight release tag workflow co
11461147
diagnostic.code === "workflow_npm_release_lightweight_tag_doc" && diagnostic.line === 3,
11471148
),
11481149
);
1150+
assert.ok(
1151+
diagnostics.some(
1152+
(diagnostic) =>
1153+
diagnostic.code === "workflow_npm_release_manual_command_doc" && diagnostic.line === 3,
1154+
),
1155+
);
1156+
});
1157+
1158+
test("validatePackageRuntimeCoverage requires guarded release helper workflow comments", async (t) => {
1159+
const tempDir = await mkdtemp(path.join(tmpdir(), "ray-package-runtime-release-helper-docs-"));
1160+
t.after(async () => {
1161+
await rm(tempDir, { recursive: true, force: true });
1162+
});
1163+
const workflowDir = path.join(tempDir, ".github", "workflows");
1164+
await mkdir(workflowDir, { recursive: true });
1165+
await writeFile(
1166+
path.join(workflowDir, "ray-sdk-release.yml"),
1167+
[
1168+
'name: "@razroo/ray-sdk Release to npm"',
1169+
"# Cutting a release:",
1170+
'# git tag -a "$TAG" -m "Release $TAG"',
1171+
'# git push origin "$TAG"',
1172+
'# gh release create "$TAG" --generate-notes --title "$TAG"',
1173+
"jobs:",
1174+
" publish:",
1175+
" if: github.repository == 'razroo/ray'",
1176+
" runs-on: ubuntu-latest",
1177+
" timeout-minutes: 60",
1178+
" permissions:",
1179+
" id-token: write",
1180+
" steps:",
1181+
" - run: timeout 120s git fetch --no-tags --prune origin main:refs/remotes/origin/main",
1182+
' - run: git merge-base --is-ancestor "$SHA" refs/remotes/origin/main',
1183+
" - run: timeout 300s npm publish ./pkg.tgz --access public --provenance",
1184+
" env:",
1185+
" NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}",
1186+
"",
1187+
].join("\n"),
1188+
);
1189+
1190+
const summary = await validatePackageRuntimeCoverage({
1191+
cwd: tempDir,
1192+
packageJsonPaths: [],
1193+
});
1194+
const diagnostics = summary.results.flatMap((result) => result.diagnostics);
1195+
1196+
assert.equal(summary.ok, false);
1197+
assert.ok(
1198+
diagnostics.some((diagnostic) => diagnostic.code === "workflow_npm_release_helper_doc_missing"),
1199+
);
1200+
assert.equal(
1201+
diagnostics.filter(
1202+
(diagnostic) => diagnostic.code === "workflow_npm_release_manual_command_doc",
1203+
).length,
1204+
3,
1205+
);
11491206
});
11501207

11511208
test("validatePackageRuntimeCoverage rejects unsafe release tag doc commands", async (t) => {

scripts/package-runtime-coverage.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ const workflowReleaseMainAncestryPattern =
7070
const workflowIdTokenWritePattern = /^\s*id-token:\s*write\s*$/m;
7171
const workflowNpmTokenPattern = /NODE_AUTH_TOKEN:\s*\$\{\{\s*secrets\.NPM_TOKEN\s*\}\}/;
7272
const workflowLightweightReleaseTagCommentPattern = /^\s*#\s*git\s+tag\s+"\$TAG"(?:\s|$)/;
73+
const workflowManualReleaseCommandCommentPattern =
74+
/^\s*#\s*(?:git\s+(?:tag|push)\b|gh\s+release\s+create\b)/;
75+
const workflowReleaseGithubHelperCommentPattern = /^\s*#.*\bbun\s+run\s+release:github\b/;
7376
const releaseTagVariablePattern = /\$(?:\{TAG(?:_(?:CORE|SDK))?\}|TAG(?:_(?:CORE|SDK))?)/;
7477
const releaseDocGitTagCommandPattern = /^git\s+tag\s+/;
7578
const releaseDocAnnotatedGitTagCommandPattern = /^git\s+tag\s+(?:-a|--annotate)\b/;
@@ -1863,6 +1866,20 @@ function validateNpmReleaseWorkflowTagDocs(
18631866
}
18641867

18651868
const diagnostics: PackageRuntimeCoverageDiagnostic[] = [];
1869+
const documentsGuardedHelper = lines.some((line) =>
1870+
workflowReleaseGithubHelperCommentPattern.test(line),
1871+
);
1872+
1873+
if (!documentsGuardedHelper) {
1874+
diagnostics.push({
1875+
level: "error",
1876+
code: "workflow_npm_release_helper_doc_missing",
1877+
workflowPath,
1878+
message:
1879+
"npm release workflow cutting comments must point maintainers at `bun run release:github` so release tags and GitHub Releases use the guarded helper.",
1880+
});
1881+
}
1882+
18661883
for (const [index, rawLine] of lines.entries()) {
18671884
if (workflowLightweightReleaseTagCommentPattern.test(rawLine)) {
18681885
diagnostics.push({
@@ -1874,6 +1891,17 @@ function validateNpmReleaseWorkflowTagDocs(
18741891
"npm release workflow cutting comments must use annotated git tags so operator docs do not recommend lightweight release tags.",
18751892
});
18761893
}
1894+
1895+
if (workflowManualReleaseCommandCommentPattern.test(rawLine)) {
1896+
diagnostics.push({
1897+
level: "error",
1898+
code: "workflow_npm_release_manual_command_doc",
1899+
workflowPath,
1900+
line: index + 1,
1901+
message:
1902+
"npm release workflow cutting comments must not document direct git tag, git push, or gh release create commands; use `bun run release:github` instead.",
1903+
});
1904+
}
18771905
}
18781906

18791907
return diagnostics;

0 commit comments

Comments
 (0)