Files
modelstudioai__cli/tools/release/publish-stable.mjs
2026-07-25 00:33:11 +08:00

99 lines
3.2 KiB
JavaScript

#!/usr/bin/env node
import { parseArgs } from "util";
import { runCheck } from "./check.mjs";
import { createTag, currentBranch, isWorkingTreeClean, pushTag, tagExists } from "./lib/git.mjs";
import { npmViewExists, pnpmPublish } from "./lib/npm.mjs";
import { ALL_PACKAGES, PACKAGES } from "./lib/packages.mjs";
import { releaseBinaryArtifacts } from "./lib/binary-release.mjs";
function log(msg = "") {
process.stdout.write(`${msg}\n`);
}
function step(msg) {
log(`\n==> ${msg}`);
}
const { values } = parseArgs({
options: {
"dry-run": { type: "boolean", default: false },
knowledge: { type: "boolean", default: false },
"skip-binary": { type: "boolean", default: false },
},
allowPositionals: false,
});
const dryRun = values["dry-run"];
const knowledge = values.knowledge;
const skipBinary = values["skip-binary"];
const packages = knowledge ? ALL_PACKAGES : PACKAGES;
try {
if (!dryRun && !process.env.CI) {
throw new Error("publish-stable is CI-only. Pass --dry-run to test locally.");
}
if (!dryRun) {
step("preflight: working tree clean + on main");
if (!isWorkingTreeClean()) {
throw new Error("git working tree is not clean; commit or stash first.");
}
const branch = currentBranch();
if (branch !== "main") {
throw new Error(`must publish from main, currently on ${branch}.`);
}
} else {
log("[dry-run] skipping working-tree + branch preflight");
}
const { coreJson } = await runCheck({ knowledge });
const version = coreJson.version; // all packages share this, asserted by runCheck
step(`idempotency: check ${version} against registry`);
const published = new Map();
for (const pkg of packages) {
const exists = npmViewExists(pkg.name, version);
published.set(pkg.key, exists);
log(`${pkg.name}@${version}: ${exists ? "already published" : "to publish"}`);
}
if (packages.every((pkg) => published.get(pkg.key))) {
throw new Error(
`version ${version} is already published for all target packages; bump the package versions before retrying.`,
);
}
// 1) npm (dependency order: core → runtime → commands → cli [→ kscli])
for (const pkg of packages) {
if (published.get(pkg.key)) continue;
step(`publish ${pkg.name}@${version} (tag=latest, provenance)`);
pnpmPublish(pkg, { tag: "latest", provenance: true, dryRun });
}
// 2) git tag — must be on origin before the GitHub Release step (--verify-tag)
const tag = `v${version}`;
if (dryRun) {
log("\n[dry-run] skipping git tag");
} else if (tagExists(tag)) {
log(`tag ${tag} already exists; skipping tag push`);
} else {
step(`tag ${tag} and push`);
createTag(tag);
pushTag(tag);
}
// 3) binary GitHub Release (same version; orchestrated here, not a separate release entry)
if (skipBinary) {
log("\n[skip-binary] skipping binary GitHub Release");
} else {
step(`publish binary GitHub Release (mode=stable, version=${version})`);
await releaseBinaryArtifacts({ mode: "stable", dryRun });
}
const parts = ["npm"];
if (!skipBinary) parts.push("binary");
log(`\nstable release complete (${parts.join(" + ")}).`);
} catch (error) {
process.stderr.write(`\nrelease publish-stable failed: ${error.message}\n`);
process.exit(1);
}