mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
pnpm release:prepare <version> sets the app version in Cargo.toml, package.json and tauri.conf.json, refreshes Cargo.lock's workspace entries, and copies wit/*.wit over the plugin API snapshot when wit/ declares a newer API. The release workflow refused a tag with a stale snapshot, but the copy itself was a manual step.
115 lines
5.1 KiB
TypeScript
115 lines
5.1 KiB
TypeScript
/**
|
|
* Prepares the release commit: `pnpm release:prepare 0.1.38` sets the app version in
|
|
* Cargo.toml, package.json and src-tauri/tauri.conf.json, refreshes Cargo.lock, and,
|
|
* when wit/ carries a newer plugin API than the released snapshot, copies wit/*.wit
|
|
* over sdk/rust/openidea-plugin/tests/api/released/ (the release workflow refuses a
|
|
* tag whose wit/ and snapshot versions differ). Commit the result as `chore: v<version>`.
|
|
*/
|
|
import { spawnSync } from "node:child_process";
|
|
import { copyFileSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
export const SNAPSHOT = "sdk/rust/openidea-plugin/tests/api/released";
|
|
|
|
/** `0.1.38` from `0.1.38` or `v0.1.38`; throws for anything else. */
|
|
export function parseVersion(arg: string | undefined): string {
|
|
const version = arg?.replace(/^v/, "");
|
|
if (!version || !/^\d+\.\d+\.\d+$/.test(version)) throw new Error(`expected a version like 0.1.38, got ${arg}`);
|
|
return version;
|
|
}
|
|
|
|
function compare(a: string, b: string): number {
|
|
const [x, y] = [a, b].map((v) => v.split(".").map(Number));
|
|
for (let i = 0; i < 3; i++) if (x![i] !== y![i]) return x![i]! - y![i]!;
|
|
return 0;
|
|
}
|
|
|
|
/** The line index and value of `version` in `[workspace.package]`. */
|
|
function workspaceVersionLine(lines: string[]): { index: number; version: string } {
|
|
const start = lines.findIndex((line) => line.trim() === "[workspace.package]");
|
|
for (let i = start + 1; start >= 0 && i < lines.length && !lines[i]!.startsWith("["); i++) {
|
|
const match = /^version = "([^"]+)"$/.exec(lines[i]!.trim());
|
|
if (match) return { index: i, version: match[1]! };
|
|
}
|
|
throw new Error("Cargo.toml: no version in [workspace.package]");
|
|
}
|
|
|
|
export function cargoVersion(toml: string): string {
|
|
return workspaceVersionLine(toml.split("\n")).version;
|
|
}
|
|
|
|
export function setCargoVersion(toml: string, version: string): string {
|
|
const lines = toml.split("\n");
|
|
const { index } = workspaceVersionLine(lines);
|
|
lines[index] = `version = "${version}"`;
|
|
return lines.join("\n");
|
|
}
|
|
|
|
/** Replaces the top-level `"version"` of a JSON file, keeping its formatting. */
|
|
export function setJsonVersion(json: string, version: string): string {
|
|
const current = (JSON.parse(json) as { version?: unknown }).version;
|
|
if (typeof current !== "string") throw new Error("no top-level version");
|
|
return json.replace(`"version": "${current}"`, `"version": "${version}"`);
|
|
}
|
|
|
|
/** `1.2.0` from a `package openidea:plugin@1.2.0;` line. */
|
|
export function apiVersion(wit: string): string {
|
|
const match = /^package openidea:plugin@([^;]+);$/m.exec(wit);
|
|
if (!match) throw new Error("no `package openidea:plugin@…;` line");
|
|
return match[1]!;
|
|
}
|
|
|
|
/**
|
|
* Copies `wit/*.wit` over the snapshot when wit/ declares another API version, and
|
|
* returns the versions; leaves the snapshot alone otherwise (no API change to release).
|
|
*/
|
|
export function syncApiSnapshot(root: string): { from: string; to: string; copied: boolean } {
|
|
const wit = join(root, "wit");
|
|
const snapshot = join(root, SNAPSHOT);
|
|
const to = apiVersion(readFileSync(join(wit, "plugin.wit"), "utf8"));
|
|
const from = apiVersion(readFileSync(join(snapshot, "plugin.wit"), "utf8"));
|
|
if (from === to) return { from, to, copied: false };
|
|
for (const file of readdirSync(snapshot)) if (file.endsWith(".wit")) rmSync(join(snapshot, file));
|
|
for (const file of readdirSync(wit)) if (file.endsWith(".wit")) copyFileSync(join(wit, file), join(snapshot, file));
|
|
return { from, to, copied: true };
|
|
}
|
|
|
|
function edit(path: string, change: (text: string) => string) {
|
|
const before = readFileSync(path, "utf8");
|
|
const after = change(before);
|
|
if (after === before) throw new Error(`${path}: nothing changed`);
|
|
writeFileSync(path, after);
|
|
}
|
|
|
|
function main() {
|
|
const root = fileURLToPath(new URL("..", import.meta.url));
|
|
const version = parseVersion(process.argv[2]);
|
|
const current = cargoVersion(readFileSync(join(root, "Cargo.toml"), "utf8"));
|
|
if (compare(version, current) <= 0) throw new Error(`${version} is not newer than the current ${current}`);
|
|
|
|
edit(join(root, "Cargo.toml"), (text) => setCargoVersion(text, version));
|
|
edit(join(root, "package.json"), (text) => setJsonVersion(text, version));
|
|
edit(join(root, "src-tauri/tauri.conf.json"), (text) => setJsonVersion(text, version));
|
|
const cargo = spawnSync("cargo", ["update", "--workspace"], { cwd: root, stdio: "inherit" });
|
|
if (cargo.status !== 0) throw new Error("cargo update --workspace failed");
|
|
console.log(`version ${current} -> ${version} (Cargo.toml, Cargo.lock, package.json, tauri.conf.json)`);
|
|
|
|
const api = syncApiSnapshot(root);
|
|
console.log(
|
|
api.copied
|
|
? `plugin API ${api.from} -> ${api.to}: copied wit/*.wit to ${SNAPSHOT}/`
|
|
: `plugin API ${api.to}: unchanged since the last release`,
|
|
);
|
|
console.log(`next: commit as "chore: v${version}", then tag v${version}`);
|
|
}
|
|
|
|
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
console.error(`release:prepare: ${error instanceof Error ? error.message : String(error)}`);
|
|
process.exit(1);
|
|
}
|
|
}
|