Make the plugin contract version the release version, and publish the Docker Hub overview from CI

This commit is contained in:
2026-08-20 23:16:11 +02:00
parent 27a8cdc385
commit c35ba3fb4e
17 changed files with 270 additions and 50 deletions
+45
View File
@@ -0,0 +1,45 @@
// The plugin contract version and the release version are one number (README → Contract
// versioning). This is the gate that keeps them one: a tag whose major.minor disagrees with
// HOST_API_VERSION would ship a host that misreports itself to every plugin's compatibility check.
// Pure and unit tested; the git/tag side lives in the workflows that call the CLI below.
export type ContractCheck = { ok: true } | { ok: false; error: string };
const SEMVER = /^v?(\d+)\.(\d+)\.(\d+)$/;
export function readHostApiVersion(source: string): string | null {
return /HOST_API_VERSION\s*=\s*"([^"]+)"/.exec(source)?.[1] ?? null;
}
// Patch is deliberately not compared: checkApiVersion ignores it, and auto-release cuts patch
// releases with no commit to bump the constant in.
export function checkTagMatchesContract(tag: string, hostApiVersion: string | null): ContractCheck {
if (hostApiVersion === null) {
return { error: "HOST_API_VERSION not found in src/plugin-host/plugin.ts", ok: false };
}
const t = SEMVER.exec(tag);
if (!t) return { error: `tag must be vX.Y.Z, got ${JSON.stringify(tag)}`, ok: false };
const h = SEMVER.exec(hostApiVersion);
if (!h) return { error: `HOST_API_VERSION must be X.Y.Z, got ${JSON.stringify(hostApiVersion)}`, ok: false };
if (t[1] === h[1] && t[2] === h[2]) return { ok: true };
return {
error:
`${tag} does not match HOST_API_VERSION ${hostApiVersion} — the contract version IS the release ` +
`version. Set HOST_API_VERSION to ${t[1]}.${t[2]}.0 in src/plugin-host/plugin.ts, merge that, ` +
"then tag.",
ok: false,
};
}
// CLI: node auto-release/contract-version.ts <tag> <path/to/plugin.ts> → exits 1 on mismatch.
if (process.argv[1]?.endsWith("/contract-version.ts")) {
const [, , tag, pluginPath] = process.argv;
const { readFileSync } = await import("node:fs");
const source = readFileSync(pluginPath ?? "src/plugin-host/plugin.ts", "utf8");
const result = checkTagMatchesContract(tag ?? "", readHostApiVersion(source));
if (!result.ok) {
process.stderr.write(`${result.error}\n`);
process.exit(1);
}
process.stdout.write(`${tag} matches HOST_API_VERSION\n`);
}