Files
pikasTech-agentrun/scripts/ci/verify-reviewed-plan.mjs
T
2026-07-22 16:46:20 +02:00

50 lines
2.4 KiB
JavaScript

#!/usr/bin/env node
import { readFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
const [actualPath, envelopePath] = process.argv.slice(2);
if (!actualPath || !envelopePath) throw new Error("usage: verify-reviewed-plan.mjs <actual-plan.json> <reviewed-plan-envelope>");
const actual = JSON.parse(await readFile(actualPath, "utf8"));
const envelope = (await readFile(envelopePath, "utf8")).trim();
const result = verifyReviewedPlan(actual, envelope);
process.stdout.write(`${JSON.stringify(result)}\n`);
if (!result.ok) process.exit(42);
}
export function verifyReviewedPlan(actual, envelope) {
const consumer = actual?.planIdentity?.inputs?.consumer;
if (typeof consumer !== "string" || consumer.length === 0) throw new Error("actual plan consumer is missing");
const prefix = `unidesk-release-plan-v1.${consumer}.`;
if (!envelope.startsWith(prefix)) throw new Error("reviewed plan envelope is missing, unsupported, or belongs to another consumer");
const expected = JSON.parse(Buffer.from(envelope.slice(prefix.length), "base64url").toString("utf8"));
const actualScope = scope(actual);
const expectedScope = scope(expected);
const additions = Object.fromEntries(Object.keys(actualScope).map((key) => [key, actualScope[key].filter((item) => !expectedScope[key].includes(item))]));
return {
event: "reviewed-plan-admission",
ok: expected.version === "v1"
&& expected.planIdentity === actual.planIdentity?.value
&& expected.target === actual.planIdentity?.inputs?.target
&& expected.consumer === consumer
&& expected.sourceCommit === actual.sourceCommitId
&& expected.baseCommit === actual.baseCommitId
&& Object.values(additions).every((items) => items.length === 0),
expectedIdentity: expected.planIdentity ?? null,
actualIdentity: actual.planIdentity?.value ?? null,
expectedScope,
actualScope,
additions,
valuesPrinted: false,
};
}
function scope(value) {
const sorted = (items) => [...new Set(Array.isArray(items) ? items.filter((item) => typeof item === "string" && item) : [])].sort();
return {
buildServices: sorted(value?.buildServices ?? value?.scope?.buildServices),
rolloutServices: sorted(value?.rolloutServices ?? value?.scope?.rolloutServices),
affectedServices: sorted(value?.affectedServices ?? value?.scope?.affectedServices),
};
}