Skip to content

Commit 6664ce1

Browse files
authored
Use npm staged publishing (#209)
* ci: use npm staged publishing * fix: preserve staged publish release tags * fix: create root release tags for staged publish * ci: simplify staged publish helper
1 parent 676ed9d commit 6664ce1

2 files changed

Lines changed: 70 additions & 3 deletions

File tree

.github/scripts/stage-package.mjs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env node
2+
import { spawnSync } from "node:child_process";
3+
import { readFileSync } from "node:fs";
4+
5+
const pkg = JSON.parse(readFileSync("package.json", "utf8"));
6+
const access = pkg.publishConfig?.access || "public";
7+
const distTag = pkg.version.match(/^[^-]+-([0-9A-Za-z-]+)/)?.[1] || "latest";
8+
const tagName = `v${pkg.version}`;
9+
10+
function run(command, args, options = {}) {
11+
const result = spawnSync(command, args, {
12+
cwd: process.cwd(),
13+
encoding: "utf8",
14+
stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit",
15+
});
16+
17+
if (options.capture) {
18+
process.stdout.write(result.stdout || "");
19+
process.stderr.write(result.stderr || "");
20+
}
21+
22+
if (result.status !== 0) process.exit(result.status || 1);
23+
return result.stdout || "";
24+
}
25+
26+
function versionExists() {
27+
const result = spawnSync("npm", ["view", `${pkg.name}@${pkg.version}`, "version", "--json"], {
28+
encoding: "utf8",
29+
stdio: ["ignore", "pipe", "pipe"],
30+
});
31+
32+
if (result.status === 0) return true;
33+
const output = `${result.stdout}\n${result.stderr}`;
34+
if (output.includes("E404") || output.includes("No match found")) return false;
35+
36+
process.stdout.write(result.stdout || "");
37+
process.stderr.write(result.stderr || "");
38+
throw new Error(`Could not check npm version for ${pkg.name}@${pkg.version}`);
39+
}
40+
41+
if (versionExists()) {
42+
console.log(`${pkg.name}@${pkg.version} is already published; skipping stage publish.`);
43+
process.exit(0);
44+
}
45+
46+
console.log(`Staging ${pkg.name}@${pkg.version} with dist-tag ${distTag}...`);
47+
const output = run(
48+
"npm",
49+
["stage", "publish", ".", "--provenance", "--access", access, "--tag", distTag, "--json"],
50+
{ capture: true },
51+
);
52+
53+
const stageId = output.match(/"stageId"\s*:\s*"([^"]+)"/)?.[1];
54+
const existingTag = spawnSync("git", ["rev-parse", "--verify", "--quiet", `refs/tags/${tagName}`], {
55+
stdio: "ignore",
56+
});
57+
if (existingTag.status === 0) {
58+
console.log(`Git tag ${tagName} already exists locally.`);
59+
} else {
60+
run("git", ["tag", tagName, "-m", tagName]);
61+
}
62+
63+
// changesets/action parses this line, then pushes the tag and creates the GitHub release.
64+
console.log(`New tag: ${tagName}`);
65+
console.log(`Staged ${pkg.name}@${pkg.version}${stageId ? ` (${stageId})` : ""}`);
66+
console.log("Approve staged packages with `npm stage approve <stage-id>` after review.");

.github/workflows/release.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
registry-url: "https://registry.npmjs.org"
3333

3434
- name: Update npm
35-
run: npm install -g npm@11.11.1
35+
run: npm install -g npm@11.15.0
3636

3737
- name: Install dependencies
3838
run: npm ci --ignore-scripts
@@ -76,7 +76,7 @@ jobs:
7676
registry-url: "https://registry.npmjs.org"
7777

7878
- name: Update npm
79-
run: npm install -g npm@11.11.1
79+
run: npm install -g npm@11.15.0
8080

8181
- name: Install dependencies
8282
run: npm ci --ignore-scripts
@@ -87,7 +87,8 @@ jobs:
8787
- name: Publish packages
8888
uses: changesets/action@e0145edc7d9d8679003495b11f87bd8ef63c0cba # v1.5.3
8989
with:
90-
publish: npm exec -- changeset publish
90+
publish: node .github/scripts/stage-package.mjs
91+
createGithubReleases: true
9192
commitMode: github-api
9293
env:
9394
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

0 commit comments

Comments
 (0)