import { readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { execSync } from "node:child_process"; const ROOT = join(import.meta.dir, ".."); const PKGS_DIR = join(ROOT, "pkgs"); const BUILD_ORDER = [ "types", "collection", "permissions", "builders", "gateway", "ratelimit", "rest", "cache", "structures", "router", "client", ] as const; type PkgShort = (typeof BUILD_ORDER)[number]; type BumpType = "patch" | "minor" | "major"; interface Pkg { name: string; short: PkgShort; version: string; deps: string[]; peers: string[]; path: string; } const isDryRun = process.argv.includes("--dry-run") || process.argv.includes("-d"); function git(cmd: string): string { return execSync(`git ${cmd}`, { cwd: ROOT, encoding: "utf-8" }).trim(); } function run(cmd: string, cwd = ROOT, prefix = "\t") { if (isDryRun) { console.log(`${prefix} $ ${cmd}`); return; } try { execSync(cmd, { cwd, stdio: "inherit" }); } catch { rollback(); } } function rollback() { console.error("\nfailed. reverting changes..."); git("checkout -- pkgs/*/package.json"); process.exit(1); } function loadPackages(): Map { const pkgs = new Map(); for (const short of BUILD_ORDER) { const pkgPath = join(PKGS_DIR, short, "package.json"); const raw = JSON.parse(readFileSync(pkgPath, "utf-8")); const deps = Object.keys(raw.dependencies ?? {}) .filter((d) => d.startsWith("@purrkit/")) .map((d) => d.replace("@purrkit/", "")); const peers = Object.keys(raw.peerDependencies ?? {}) .filter((d) => d.startsWith("@purrkit/")) .map((d) => d.replace("@purrkit/", "")); pkgs.set(short, { name: raw.name, short, version: raw.version, deps, peers, path: pkgPath }); } return pkgs; } function bumpVersion(version: string, type: BumpType): string { const [major, minor, patch] = version.split(".").map(Number); if (type === "major") return `${major! + 1}.0.0`; if (type === "minor") return `${major}.${minor! + 1}.0`; if (type === "patch") return `${major}.${minor}.${patch! + 1}`; throw new Error(`invalid bump type: ${type}`); } function findAffected(pkgs: Map, target: PkgShort): PkgShort[] { const affectedSet = new Set([target]); function walk(current: string) { for (const [short, pkg] of pkgs) { if (affectedSet.has(short)) continue; if (pkg.deps.includes(current) || pkg.peers.includes(current)) { affectedSet.add(short); walk(short); } } } walk(target); return BUILD_ORDER.filter((short) => affectedSet.has(short)); } function main() { const [targetShort, bumpType] = process.argv.slice(2).filter((arg) => !arg.startsWith("-")) as [ PkgShort, BumpType, ]; const validBumpTypes: BumpType[] = ["patch", "minor", "major"]; if (!targetShort || !validBumpTypes.includes(bumpType)) { console.error("usage: bun run scripts/publish.ts [--dry-run]"); console.error(" e.g: bun run scripts/publish.ts rest patch --dry-run"); process.exit(1); } if (!BUILD_ORDER.includes(targetShort)) { console.error(`unknown package: ${targetShort}`); console.error(`available: ${BUILD_ORDER.join(", ")}`); process.exit(1); } if (!isDryRun && git("status --porcelain pkgs")) { console.error("work tree is dirty. commit or stash changes first."); process.exit(1); } const pkgs = loadPackages(); const target = pkgs.get(targetShort)!; const affected = findAffected(pkgs, targetShort); console.log( `bumping @purrkit/${targetShort} (${target.version} -> ${bumpVersion(target.version, bumpType)})${isDryRun ? " [DRY RUN]" : ""}`, ); console.log(`affected: ${affected.map((s) => `@purrkit/${s}`).join(" -> ")}`); for (const short of affected) { const pkg = pkgs.get(short)!; const oldVersion = pkg.version; const newVersion = bumpVersion(oldVersion, bumpType); pkg.version = newVersion; if (!isDryRun) { const raw = JSON.parse(readFileSync(pkg.path, "utf-8")); raw.version = newVersion; writeFileSync(pkg.path, JSON.stringify(raw, null, "\t") + "\n"); } console.log(`\t${pkg.name}: ${oldVersion} -> ${newVersion}`); } console.log("\nbuilding..."); for (const short of affected) { run(`bun run build:${short}`); } console.log("\npublishing..."); for (const short of affected) { const pkg = pkgs.get(short)!; console.log(`\t${pkg.name}@${pkg.version}`); run("bun publish --access public", join(PKGS_DIR, short), "\t "); } console.log("\ncommitting & tagging..."); const commitMsg = `release: ${affected.map((s) => `@purrkit/${s}@${pkgs.get(s)!.version}`).join(", ")}`; run("git add pkgs/*/package.json"); run(`git commit -m "${commitMsg}"`); for (const short of affected) { const pkg = pkgs.get(short)!; run(`git tag -a ${pkg.name}@${pkg.version} -m "${pkg.name}@${pkg.version}"`); } console.log("\ndone."); } main();