212 lines
6.3 KiB
TypeScript
212 lines
6.3 KiB
TypeScript
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { createInterface } from "node:readline";
|
|
import { join } from "node:path";
|
|
|
|
export interface Semver {
|
|
major: number;
|
|
minor: number;
|
|
patch: number;
|
|
}
|
|
|
|
export type BumpType = "major" | "minor" | "patch";
|
|
|
|
export function parseSemver(version: string): Semver {
|
|
const parts = version.split(".");
|
|
if (parts.length !== 3) {
|
|
throw new Error(`无效的版本号格式: ${version}`);
|
|
}
|
|
const [major, minor, patch] = parts.map((p) => {
|
|
const n = Number(p);
|
|
if (Number.isNaN(n) || !Number.isInteger(n) || n < 0) {
|
|
throw new Error(`无效的版本号格式: ${version}`);
|
|
}
|
|
return n;
|
|
});
|
|
return { major: major!, minor: minor!, patch: patch! };
|
|
}
|
|
|
|
export function bumpVersion(current: string, type: BumpType): string {
|
|
const semver = parseSemver(current);
|
|
switch (type) {
|
|
case "major":
|
|
return `${semver.major + 1}.0.0`;
|
|
case "minor":
|
|
return `${semver.major}.${semver.minor + 1}.0`;
|
|
case "patch":
|
|
return `${semver.major}.${semver.minor}.${semver.patch + 1}`;
|
|
}
|
|
}
|
|
|
|
async function ask(query: string): Promise<string> {
|
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
return new Promise((resolve) => {
|
|
rl.question(query, (answer) => {
|
|
rl.close();
|
|
resolve(answer.trim());
|
|
});
|
|
});
|
|
}
|
|
|
|
async function selectBumpType(): Promise<BumpType> {
|
|
console.log("选择版本递增类型:");
|
|
console.log(" 1) major - 不兼容的 API 变更");
|
|
console.log(" 2) minor - 向下兼容的功能新增");
|
|
console.log(" 3) patch - 向下兼容的问题修正");
|
|
|
|
while (true) {
|
|
const answer = await ask("请输入 1/2/3 [3]: ");
|
|
const choice = answer || "3";
|
|
if (choice === "1") return "major";
|
|
if (choice === "2") return "minor";
|
|
if (choice === "3") return "patch";
|
|
console.log("无效选择,请输入 1、2 或 3");
|
|
}
|
|
}
|
|
|
|
async function stepBumpVersion(): Promise<string> {
|
|
const pkgPath = join(import.meta.dir, "..", "package.json");
|
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as { version: string };
|
|
if (typeof pkg.version !== "string" || pkg.version.length === 0) {
|
|
throw new Error("package.json 中缺少有效的 version 字段");
|
|
}
|
|
const currentVersion = pkg.version;
|
|
|
|
const bumpType = await selectBumpType();
|
|
const newVersion = bumpVersion(currentVersion, bumpType);
|
|
|
|
const answer = await ask(`确认版本号 ${currentVersion} → ${newVersion}? [y/N]: `);
|
|
if (answer.toLowerCase() !== "y") {
|
|
console.log("已取消");
|
|
process.exit(0);
|
|
}
|
|
|
|
return newVersion;
|
|
}
|
|
|
|
async function runTests(): Promise<void> {
|
|
console.log("\n运行测试...");
|
|
const proc = Bun.spawn(["bun", "test", "--path-ignore-patterns", "tests/agent/**"], {
|
|
stdio: ["inherit", "inherit", "inherit"],
|
|
});
|
|
const exitCode = await proc.exited;
|
|
if (exitCode !== 0) {
|
|
throw new Error(`测试失败 (exit code: ${exitCode}),已跳过 git 和发布步骤`);
|
|
}
|
|
}
|
|
|
|
async function checkWorkingTree(): Promise<void> {
|
|
const proc = Bun.spawn(["git", "status", "--porcelain"], {
|
|
stdio: ["inherit", "pipe", "inherit"],
|
|
});
|
|
const output = await new Response(proc.stdout).text();
|
|
const lines = output.trim().split("\n").filter(Boolean);
|
|
if (lines.length > 0) {
|
|
throw new Error("工作区有未提交变更,请先提交或清理后再运行 release");
|
|
}
|
|
}
|
|
|
|
async function stepGitCommitTag(version: string): Promise<void> {
|
|
console.log("\n准备提交:");
|
|
console.log(` git add package.json`);
|
|
console.log(` git commit -m "chore: release v${version}"`);
|
|
console.log(` git tag v${version}`);
|
|
|
|
const answer = await ask("确认执行以上 git 操作? [y/N]: ");
|
|
if (answer.toLowerCase() !== "y") {
|
|
console.log("已取消 git 操作");
|
|
process.exit(0);
|
|
}
|
|
|
|
// git add package.json
|
|
const addProc = Bun.spawn(["git", "add", "package.json"], {
|
|
stdio: ["inherit", "inherit", "inherit"],
|
|
});
|
|
const addExit = await addProc.exited;
|
|
if (addExit !== 0) {
|
|
throw new Error("git add 失败");
|
|
}
|
|
|
|
// git commit
|
|
const commitProc = Bun.spawn(["git", "commit", "-m", `chore: release v${version}`], {
|
|
stdio: ["inherit", "inherit", "inherit"],
|
|
});
|
|
const commitExit = await commitProc.exited;
|
|
if (commitExit !== 0) {
|
|
throw new Error("git commit 失败");
|
|
}
|
|
|
|
// git tag
|
|
const tagProc = Bun.spawn(
|
|
["git", "tag", "-a", `v${version}`, "-m", `chore: release v${version}`],
|
|
{
|
|
stdio: ["inherit", "inherit", "inherit"],
|
|
},
|
|
);
|
|
const tagExit = await tagProc.exited;
|
|
if (tagExit !== 0) {
|
|
throw new Error("git tag 失败");
|
|
}
|
|
|
|
console.log(`git commit 和 tag v${version} 已完成`);
|
|
}
|
|
|
|
async function stepNpmPublish(): Promise<void> {
|
|
console.log("\nnpm 发布预览:");
|
|
const dryRunProc = Bun.spawn(["bun", "publish", "--dry-run"], {
|
|
stdio: ["inherit", "inherit", "inherit"],
|
|
});
|
|
const dryRunExit = await dryRunProc.exited;
|
|
if (dryRunExit !== 0) {
|
|
throw new Error("npm publish --dry-run 失败");
|
|
}
|
|
|
|
const answer = await ask("确认发布到 npm? [y/N]: ");
|
|
if (answer.toLowerCase() !== "y") {
|
|
console.log("已取消发布");
|
|
process.exit(0);
|
|
}
|
|
|
|
const proc = Bun.spawn(["bun", "publish", "--access", "public"], {
|
|
stdio: ["inherit", "inherit", "inherit"],
|
|
});
|
|
const exitCode = await proc.exited;
|
|
if (exitCode !== 0) {
|
|
throw new Error(`npm publish 失败 (exit code: ${exitCode}),请检查 npm 登录状态 (npm whoami)`);
|
|
}
|
|
|
|
console.log("npm 发布成功");
|
|
}
|
|
|
|
function writeVersion(version: string): void {
|
|
const pkgPath = join(import.meta.dir, "..", "package.json");
|
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as { version: string };
|
|
pkg.version = version;
|
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
await checkWorkingTree();
|
|
|
|
const newVersion = await stepBumpVersion();
|
|
console.log(`目标版本: ${newVersion}`);
|
|
|
|
await runTests();
|
|
console.log("[1/3] 测试通过");
|
|
|
|
writeVersion(newVersion);
|
|
console.log(`[2/3] 版本号已更新: ${newVersion}`);
|
|
|
|
await stepGitCommitTag(newVersion);
|
|
console.log(`[3/3] git commit 和 tag v${newVersion} 完成`);
|
|
|
|
await stepNpmPublish();
|
|
console.log("npm 发布完成");
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
main().catch((err: unknown) => {
|
|
console.error(err instanceof Error ? err.message : String(err));
|
|
process.exit(1);
|
|
});
|
|
}
|