#!/usr/bin/env node /** * 版本化发布:递增版本号 → 同步 manifest.json / package.json / updates.xml → * 发布构建 → 打出 zip,并打印后续 git 操作命令(不自动提交/打 tag)。 * * 用法: * npm run release -- patch 1.0.0 → 1.0.1 * npm run release -- minor 1.0.0 → 1.1.0 * npm run release -- major 1.0.0 → 2.0.0 * npm run release -- 1.4.0 指定版本 * npm run release -- patch --codebase http://host/auto-login.crx 同步自托管更新地址 */ import { readFile, writeFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { packageExtension } from "./package.mjs"; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const MANIFEST = path.join(ROOT, "manifest.json"); const PKG = path.join(ROOT, "package.json"); const UPDATES = path.join(ROOT, "updates.xml"); function bump(current, kind) { const parts = current.split(".").map((n) => parseInt(n, 10)); while (parts.length < 3) parts.push(0); if (kind === "major") return [parts[0] + 1, 0, 0].join("."); if (kind === "minor") return [parts[0], parts[1] + 1, 0].join("."); if (kind === "patch") return [parts[0], parts[1], parts[2] + 1].join("."); if (/^\d+(\.\d+){0,3}$/.test(kind)) return kind; throw new Error(`无法识别的版本参数:${kind}(支持 patch / minor / major / x.y.z)`); } /** 只替换 version 字段,保留文件原有格式(避免整份 JSON 被重排产生无意义 diff) */ async function setJsonVersion(file, version) { const raw = await readFile(file, "utf8"); const next = raw.replace(/("version"\s*:\s*")[^"]*(")/, `$1${version}$2`); if (next === raw) throw new Error(`未能在 ${path.basename(file)} 中找到 version 字段`); await writeFile(file, next); } /** 同步自托管更新清单里的 version 与 codebase */ async function syncUpdatesXml(version, codebase) { let xml; try { xml = await readFile(UPDATES, "utf8"); } catch { return false; } let next = xml.replace(/(]*?version=')[^']*(')/, `$1${version}$2`); if (codebase) { next = next.replace(/(]*?codebase=')[^']*(')/, `$1${codebase}$2`); } await writeFile(UPDATES, next); return true; } async function main() { const args = process.argv.slice(2); const codebaseIdx = args.indexOf("--codebase"); const codebase = codebaseIdx !== -1 ? args[codebaseIdx + 1] : null; const kind = args.find((a) => !a.startsWith("--") && a !== codebase) || "patch"; const manifest = JSON.parse(await readFile(MANIFEST, "utf8")); const pkg = JSON.parse(await readFile(PKG, "utf8")); const nextVersion = bump(manifest.version, kind); const prevVersion = manifest.version; await setJsonVersion(MANIFEST, nextVersion); if (pkg.version !== nextVersion) await setJsonVersion(PKG, nextVersion); const updatesSynced = await syncUpdatesXml(nextVersion, codebase); console.log(`[release] ${prevVersion} → ${nextVersion}`); const { zipPath } = await packageExtension(); console.log("\n[release] 完成。后续操作:"); console.log( ` git add manifest.json package.json${updatesSynced ? " updates.xml" : ""} && git commit -m "release: v${nextVersion}"`, ); console.log(` git tag v${nextVersion}`); console.log(" git push && git push --tags"); console.log(`\n产物:${path.relative(ROOT, zipPath)}`); console.log( "上传 Chrome Web Store 用该 zip;自托管自更新需用同一份内容打成 .crx 并放到 updates.xml 的 codebase 地址。", ); } await main();