chore: migrated release script to TypeScript
This commit is contained in:
+1
-3
@@ -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",
|
||||
|
||||
@@ -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} ===`);
|
||||
Executable
+172
@@ -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");
|
||||
Reference in New Issue
Block a user