chore: migrated release script to TypeScript

This commit is contained in:
can1357
2026-01-04 16:16:15 +01:00
parent 06a2a069c1
commit 77bca78dd3
3 changed files with 173 additions and 148 deletions
+1 -3
View File
@@ -20,9 +20,7 @@
"prepublishOnly": "bun run build && bun run check",
"publish": "bun run prepublishOnly && npm publish -ws --access public",
"publish:dry": "bun run prepublishOnly && npm publish -ws --access public --dry-run",
"release:patch": "bun scripts/release.mjs patch",
"release:minor": "bun scripts/release.mjs minor",
"release:major": "bun scripts/release.mjs major"
"release": "bun scripts/release.ts"
},
"devDependencies": {
"@biomejs/biome": "2.3.10",
-145
View File
@@ -1,145 +0,0 @@
#!/usr/bin/env node
/**
* Release script for pi-mono
*
* Usage: node scripts/release.mjs <major|minor|patch>
*
* Steps:
* 1. Check for uncommitted changes
* 2. Bump version via npm run version:xxx
* 3. Update CHANGELOG.md files: [Unreleased] -> [version] - date
* 4. Commit and tag
* 5. Publish to npm
* 6. Add new [Unreleased] section to changelogs
* 7. Commit
*/
import { execSync } from "child_process";
import { readFileSync, writeFileSync, readdirSync, existsSync } from "fs";
import { join } from "path";
const BUMP_TYPE = process.argv[2];
if (!["major", "minor", "patch"].includes(BUMP_TYPE)) {
console.error("Usage: node scripts/release.mjs <major|minor|patch>");
process.exit(1);
}
function run(cmd, options = {}) {
console.log(`$ ${cmd}`);
try {
return execSync(cmd, { encoding: "utf-8", stdio: options.silent ? "pipe" : "inherit", ...options });
} catch (e) {
if (!options.ignoreError) {
console.error(`Command failed: ${cmd}`);
process.exit(1);
}
return null;
}
}
function getVersion() {
const pkg = JSON.parse(readFileSync("packages/ai/package.json", "utf-8"));
return pkg.version;
}
function getChangelogs() {
const packagesDir = "packages";
const packages = readdirSync(packagesDir);
return packages
.map((pkg) => join(packagesDir, pkg, "CHANGELOG.md"))
.filter((path) => existsSync(path));
}
function updateChangelogsForRelease(version) {
const date = new Date().toISOString().split("T")[0];
const changelogs = getChangelogs();
for (const changelog of changelogs) {
const content = readFileSync(changelog, "utf-8");
if (!content.includes("## [Unreleased]")) {
console.log(` Skipping ${changelog}: no [Unreleased] section`);
continue;
}
const updated = content.replace(
"## [Unreleased]",
`## [${version}] - ${date}`
);
writeFileSync(changelog, updated);
console.log(` Updated ${changelog}`);
}
}
function addUnreleasedSection() {
const changelogs = getChangelogs();
const unreleasedSection = "## [Unreleased]\n\n";
for (const changelog of changelogs) {
const content = readFileSync(changelog, "utf-8");
// Insert after "# Changelog\n\n"
const updated = content.replace(
/^(# Changelog\n\n)/,
`$1${unreleasedSection}`
);
writeFileSync(changelog, updated);
console.log(` Added [Unreleased] to ${changelog}`);
}
}
// Main flow
console.log("\n=== Release Script ===\n");
// 1. Check for uncommitted changes
console.log("Checking for uncommitted changes...");
const status = run("git status --porcelain", { silent: true });
if (status && status.trim()) {
console.error("Error: Uncommitted changes detected. Commit or stash first.");
console.error(status);
process.exit(1);
}
console.log(" Working directory clean\n");
// 2. Bump version
console.log(`Bumping version (${BUMP_TYPE})...`);
run(`npm run version:${BUMP_TYPE}`);
const version = getVersion();
console.log(` New version: ${version}\n`);
// 3. Update changelogs
console.log("Updating CHANGELOG.md files...");
updateChangelogsForRelease(version);
console.log();
// 4. Commit and tag
console.log("Committing and tagging...");
run("git add .");
run(`git commit -m "Release v${version}"`);
run(`git tag v${version}`);
console.log();
// 5. Publish
console.log("Publishing to npm...");
run("npm run publish");
console.log();
// 6. Add new [Unreleased] sections
console.log("Adding [Unreleased] sections for next cycle...");
addUnreleasedSection();
console.log();
// 7. Commit
console.log("Committing changelog updates...");
run("git add .");
run(`git commit -m "Add [Unreleased] section for next cycle"`);
console.log();
// 8. Push
console.log("Pushing to remote...");
run("git push origin main");
run(`git push origin v${version}`);
console.log();
console.log(`=== Released v${version} ===`);
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env bun
/**
* Release script for pi-mono
*
* Usage: bun scripts/release.ts <version>
*
* Example: bun scripts/release.ts 3.10.0
*
* Steps:
* 1. Pre-flight checks (clean working dir, on main branch)
* 2. Update package.json versions
* 3. Regenerate lockfile
* 4. Update CHANGELOGs ([Unreleased] -> [version] - date, add new [Unreleased])
* 5. Commit and tag
* 6. Push
* 7. Watch CI
*/
import type { Subprocess } from "bun";
import { Glob } from "bun";
const VERSION = process.argv[2];
if (!VERSION || !/^\d+\.\d+\.\d+/.test(VERSION)) {
console.error("Usage: bun scripts/release.ts <version>");
console.error("Example: bun scripts/release.ts 3.10.0");
process.exit(1);
}
interface RunOptions {
silent?: boolean;
capture?: boolean;
ignoreError?: boolean;
}
function run(cmd: string[], options: RunOptions = {}): string {
const cmdStr = cmd.join(" ");
if (!options.silent) console.log(`$ ${cmdStr}`);
const result = Bun.spawnSync(cmd, {
stdout: options.capture ? "pipe" : "inherit",
stderr: "inherit",
});
if (result.exitCode !== 0 && !options.ignoreError) {
console.error(`Command failed: ${cmdStr}`);
process.exit(1);
}
return result.stdout?.toString().trim() ?? "";
}
async function getChangelogs(): Promise<string[]> {
const glob = new Glob("packages/*/CHANGELOG.md");
const paths: string[] = [];
for await (const path of glob.scan(".")) {
paths.push(path);
}
return paths;
}
async function getPackageJsonPaths(): Promise<string[]> {
const glob = new Glob("packages/*/package.json");
const paths: string[] = [];
for await (const path of glob.scan(".")) {
paths.push(path);
}
return paths;
}
async function updateChangelogsForRelease(version: string): Promise<void> {
const date = new Date().toISOString().split("T")[0];
const changelogs = await getChangelogs();
for (const changelog of changelogs) {
let content = await Bun.file(changelog).text();
if (!content.includes("## [Unreleased]")) {
console.log(` Skipping ${changelog}: no [Unreleased] section`);
continue;
}
// Replace [Unreleased] with version and date
content = content.replace("## [Unreleased]", `## [${version}] - ${date}`);
// Add new [Unreleased] section after # Changelog header
content = content.replace(/^(# Changelog\n\n)/, `$1## [Unreleased]\n\n`);
await Bun.write(changelog, content);
console.log(` Updated ${changelog}`);
}
}
// === Main Flow ===
console.log("\n=== Release Script ===\n");
// 1. Pre-flight checks
console.log("Pre-flight checks...");
const branch = run(["git", "branch", "--show-current"], { capture: true, silent: true });
if (branch !== "main") {
console.error(`Error: Must be on main branch (currently on '${branch}')`);
process.exit(1);
}
console.log(" On main branch");
const status = run(["git", "status", "--porcelain"], { capture: true, silent: true });
if (status) {
console.error("Error: Uncommitted changes detected. Commit or stash first.");
console.error(status);
process.exit(1);
}
console.log(" Working directory clean\n");
// 2. Update package versions
console.log(`Updating package versions to ${VERSION}...`);
const pkgJsonPaths = await getPackageJsonPaths();
run(["sd", '"version": "[^"]+"', `"version": "${VERSION}"`, ...pkgJsonPaths]);
// Verify
console.log(" Verifying versions:");
for (const pkgPath of pkgJsonPaths) {
const pkgJson = await Bun.file(pkgPath).json();
console.log(` ${pkgJson.name}: ${pkgJson.version}`);
}
console.log();
// 3. Regenerate lockfile
console.log("Regenerating lockfile...");
run(["rm", "-f", "bun.lock"]);
run(["bun", "install"]);
console.log();
// 4. Update changelogs
console.log("Updating CHANGELOGs...");
await updateChangelogsForRelease(VERSION);
console.log();
// 5. Commit and tag
console.log("Committing and tagging...");
run(["git", "add", "."]);
run(["git", "commit", "-m", `chore: bump version to ${VERSION}`]);
run(["git", "tag", `v${VERSION}`]);
console.log();
// 6. Push
console.log("Pushing to remote...");
run(["git", "push", "origin", "main"]);
run(["git", "push", "origin", `v${VERSION}`]);
console.log();
// 7. Watch CI
console.log("Watching CI...");
const runListOutput = run(["gh", "run", "list", "--limit", "1", "--json", "databaseId"], { capture: true, silent: true });
const runs = JSON.parse(runListOutput);
if (runs.length > 0) {
const runId = runs[0].databaseId;
console.log(` Watching run ${runId}...`);
run(["gh", "run", "watch", String(runId), "--exit-status"], { ignoreError: true });
}
console.log();
console.log(`=== Released v${VERSION} ===`);
console.log();
console.log("If CI failed, fix the issue and run:");
console.log(" git commit --amend --no-edit");
console.log(" git push origin main --force");
console.log(` git tag -f v${VERSION} && git push origin v${VERSION} --force`);
console.log();
console.log("To publish to npm:");
console.log(" npm run publish");