Skip to content

Commit 05dac2c

Browse files
committed
fix: make release script idempotent and fail on publish errors
1 parent 990f721 commit 05dac2c

1 file changed

Lines changed: 50 additions & 10 deletions

File tree

scripts/release.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@ function run(cmd: string, opts?: { cwd?: string; stdio?: "pipe" | "inherit" }) {
1919
return result?.trim() ?? "";
2020
}
2121

22+
function tryRun(cmd: string, opts?: { cwd?: string; stdio?: "pipe" | "inherit" }): { ok: boolean; output: string } {
23+
try {
24+
return { ok: true, output: run(cmd, opts) };
25+
} catch {
26+
return { ok: false, output: "" };
27+
}
28+
}
29+
30+
function isPublished(name: string, version: string): boolean {
31+
const { ok } = tryRun(`npm view ${name}@${version} version`);
32+
return ok;
33+
}
34+
2235
function fatal(msg: string): never {
2336
console.error(`\x1b[31mError:\x1b[0m ${msg}`);
2437
process.exit(1);
@@ -162,27 +175,54 @@ async function main() {
162175
for (const pkg of packages) {
163176
run(`git add ${join(pkg, "package.json")}`);
164177
}
165-
run(`git commit -m "release: v${version}"`);
166-
run("git push origin main");
178+
const staged = run("git diff --cached --name-only");
179+
if (staged) {
180+
run(`git commit -m "release: v${version}"`);
181+
run("git push origin main");
182+
} else {
183+
console.log(" No version changes to commit, skipping.");
184+
}
167185

168186
// Git tag & GitHub release
169187
console.log(`\n\x1b[1mCreating git tag v${version}...\x1b[0m`);
170-
run(`git tag v${version}`);
171-
run(`git push origin v${version}`);
188+
const tagExists = tryRun(`git rev-parse v${version}`).ok;
189+
if (tagExists) {
190+
console.log(` Tag v${version} already exists, skipping.`);
191+
} else {
192+
run(`git tag v${version}`);
193+
run(`git push origin v${version}`);
194+
}
172195

173196
const prerelease = tag === "rc" ? "--prerelease" : "";
174-
console.log("\n\x1b[1mCreating GitHub release...\x1b[0m");
175-
run(
176-
`gh release create v${version} --title "v${version}" --generate-notes ${prerelease}`.trim(),
177-
{ stdio: "inherit" },
178-
);
197+
const releaseExists = tryRun(`gh release view v${version}`).ok;
198+
if (releaseExists) {
199+
console.log(` GitHub release v${version} already exists, skipping.`);
200+
} else {
201+
console.log("\n\x1b[1mCreating GitHub release...\x1b[0m");
202+
run(
203+
`gh release create v${version} --title "v${version}" --generate-notes ${prerelease}`.trim(),
204+
{ stdio: "inherit" },
205+
);
206+
}
179207

180208
// Publish
181209
console.log(`\n\x1b[1mPublishing to npm (tag: ${tag})...\x1b[0m`);
210+
const failures: string[] = [];
182211
for (const pkg of packages) {
183212
const name = JSON.parse(readFileSync(join(pkg, "package.json"), "utf-8")).name;
213+
if (isPublished(name, version)) {
214+
console.log(` \x1b[33m⏭ ${name}@${version} already published, skipping.\x1b[0m`);
215+
continue;
216+
}
184217
console.log(` Publishing ${name}...`);
185-
run(`pnpm publish --access public --tag ${tag} --no-git-checks`, { cwd: pkg, stdio: "inherit" });
218+
const { ok } = tryRun(`pnpm publish --access public --tag ${tag} --no-git-checks`, { cwd: pkg, stdio: "inherit" });
219+
if (!ok) {
220+
failures.push(name);
221+
}
222+
}
223+
224+
if (failures.length > 0) {
225+
fatal(`Failed to publish: ${failures.join(", ")}`);
186226
}
187227

188228
console.log(`\n\x1b[32m✓ Released v${version}\x1b[0m`);

0 commit comments

Comments
 (0)