返回 DeepSeek-Reasonix
verify-release-tag-identity.mjs
根目录 / scripts / verify-release-tag-identity.mjs
1 // Inspect the same maintainer credential used for the atomic tag push. A dry
2 // run cannot prove server-side ruleset authorization, so read the live policy.
3 import { execFileSync } from "node:child_process";
4 import { appendFileSync } from "node:fs";
5 import { pathToFileURL } from "node:url";
6
7 const repository = "esengine/DeepSeek-Reasonix";
8
9 // Release tags contain no slash after refs/tags/. Unknown pattern syntax is
10 // treated conservatively: it may include a tag, but cannot exclude one.
11 function matches(pattern, ref, unknown) {
12 if (pattern === "~ALL") return true;
13 if (typeof pattern !== "string" || /[\[\]{}\\]/.test(pattern) || pattern.includes("**") || pattern.startsWith("~")) return unknown;
14 const expression = pattern.split("").map(char => char === "*" ? "[^/]*" : char === "?" ? "[^/]" : char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("");
15 return new RegExp(`^${expression}$`).test(ref);
16 }
17
18 export function verifyIdentity({ actor, repo, rulesets, expectedActor, expectedID, version }) {
19 if (!/^[A-Za-z0-9][A-Za-z0-9-]*$/.test(expectedActor || "")) throw new Error("Configure RELEASE_TAG_ACTOR with the authorized maintainer login");
20 if (!/^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/.test(version)) throw new Error("Invalid release version");
21 if (actor.type !== "User" || actor.login?.toLowerCase() !== expectedActor.toLowerCase() || !Number.isSafeInteger(actor.id)) throw new Error("Release tag credential does not identify the configured maintainer");
22 if (expectedID && String(actor.id) !== String(expectedID)) throw new Error("Release tag identity changed after preflight");
23 if (repo.full_name !== repository || repo.permissions?.push !== true) throw new Error("Release tag identity cannot push to the official repository");
24 if (!Array.isArray(rulesets)) throw new Error("Missing repository rulesets");
25 const refs = [`v${version}`, `npm-v${version}`, `desktop-v${version}`].map(tag => `refs/tags/${tag}`);
26 const checked = [];
27 for (const rule of rulesets) {
28 if (!["active", "evaluate", "disabled"].includes(rule.enforcement)) throw new Error("Unknown ruleset enforcement");
29 if (rule.enforcement !== "active" || !["tag", "push"].includes(rule.target)) continue;
30 const conditions = rule.conditions?.ref_name;
31 const applies = rule.target === "push" || !conditions || refs.some(ref =>
32 (!Array.isArray(conditions.include) || conditions.include.some(pattern => matches(pattern, ref, true))) &&
33 !(conditions.exclude || []).some(pattern => matches(pattern, ref, false)));
34 if (!applies) continue;
35 if (!Array.isArray(rule.rules)) throw new Error(`Cannot inspect ruleset ${rule.id}`);
36 // Updates and deletions are never requested. All other rules must explicitly
37 // allow this maintainer to bypass; do not guess at server rule semantics.
38 if (rule.rules.some(item => !["update", "deletion"].includes(item.type)) && rule.current_user_can_bypass !== "always") {
39 throw new Error(`Release tag identity cannot bypass active ruleset ${rule.id}; configure an already-authorized maintainer, not weaker protections`);
40 }
41 checked.push(rule.id);
42 }
43 return { actor: actor.login, actorID: String(actor.id), rulesets: checked };
44 }
45
46 if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
47 if (!process.env.GH_TOKEN) throw new Error("RELEASE_TAG_TOKEN is required; the default Actions token is not a release identity");
48 if (process.env.GITHUB_ACTIONS === "true" && (process.env.GITHUB_REPOSITORY !== repository || process.env.GITHUB_REF !== "refs/heads/main-v2" || process.env.GITHUB_REF_PROTECTED !== "true")) throw new Error("Release identity checks require protected official main-v2");
49 const api = endpoint => JSON.parse(execFileSync("gh", ["api", endpoint], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] }));
50 const listed = JSON.parse(execFileSync("gh", ["api", "--paginate", "--slurp", `repos/${repository}/rulesets?includes_parents=true&per_page=100`], { encoding: "utf8" })).flat();
51 const result = verifyIdentity({
52 actor: api("user"), repo: api(`repos/${repository}`),
53 rulesets: listed.filter(rule => ["tag", "push"].includes(rule.target) && rule.enforcement === "active").map(rule => api(`repos/${repository}/rulesets/${rule.id}?includes_parents=true`)),
54 expectedActor: process.env.RELEASE_TAG_ACTOR, expectedID: process.env.RELEASE_TAG_ACTOR_ID, version: process.argv[2],
55 });
56 console.log(JSON.stringify(result));
57 if (process.env.GITHUB_OUTPUT) appendFileSync(process.env.GITHUB_OUTPUT, `actor_id=${result.actorID}\n`);
58 }
59
59 lines Plain Text