80 lines
3.9 KiB
TypeScript
80 lines
3.9 KiB
TypeScript
// A branch's protection (novox/hq ADR 0237): no direct push, and on main only the tools' account merges (ADR 0398 §2).
|
|
// The forge requires no status to merge (ADR 0534 §1): the checks still run and report, and mesh-delivery and the
|
|
// merge tool judge them (ADR 0534 §3, §4), never the forge. Pure, so it is tested without a forge; the client does the
|
|
// asking (client.ts).
|
|
|
|
import { readFileSync } from "node:fs";
|
|
import { TOOLS_ACCOUNT } from "./tools-account.js";
|
|
|
|
/** The branch whose rule takes no push and lets only the tools' account merge (novox/hq ADR 0398 §2, issue 948). */
|
|
export const MAIN_BRANCH = "main";
|
|
|
|
/** A branch protection rule, as the forge keeps it — the fields the mesh reads; the forge sends more. */
|
|
export interface BranchProtection {
|
|
rule_name?: string;
|
|
branch_name?: string;
|
|
enable_push?: boolean;
|
|
enable_status_check?: boolean;
|
|
status_check_contexts?: string[] | null;
|
|
required_approvals?: number;
|
|
block_admin_merge_override?: boolean;
|
|
enable_merge_whitelist?: boolean;
|
|
merge_whitelist_usernames?: string[] | null;
|
|
[field: string]: unknown;
|
|
}
|
|
|
|
/** What a branch's protection is set to. A field not given is left as the rule has it (or the forge's
|
|
* default on a new rule), except pushes, which a new rule refuses unless `push` says otherwise. */
|
|
export interface ProtectionWanted {
|
|
/** Whether a person may push to the branch directly. */
|
|
push?: boolean;
|
|
/** Whether an administrator is stopped from merging past what the rule asks. */
|
|
blockAdminOverride?: boolean;
|
|
/** Who may merge into the branch; for main only ever the tools' account (ADR 0398 §2). Not given: left as it is.
|
|
* Empty or null asks for no whitelist, which the guard refuses for main; the body for main ignores it. */
|
|
mergeWhitelist?: string[] | null;
|
|
}
|
|
|
|
/** What every rule the mesh writes says of statuses: none is required to merge (novox/hq ADR 0534 §1). */
|
|
export function noStatusBody(): Record<string, unknown> {
|
|
return { enable_status_check: false, status_check_contexts: [] };
|
|
}
|
|
|
|
/** A forge login a merge whitelist may name. */
|
|
const LOGIN = /^[A-Za-z0-9_][A-Za-z0-9_.-]{0,39}$/;
|
|
|
|
/** Who merges into main: the tools' account, for mesh-delivery, then each login of the module's config value
|
|
* main-mergers once, the operator's own, so the operator can always merge by hand at the forge (novox/hq ADR 0534 §2).
|
|
* Read from the module's runtime config file (MESH_GITEA_CONFIG_FILE); a file not there, or a name that is no login,
|
|
* leaves the tools' account alone, the narrowest whitelist. */
|
|
export function mainMergers(env: NodeJS.ProcessEnv = process.env): string[] {
|
|
const out = [TOOLS_ACCOUNT];
|
|
let given: unknown = [];
|
|
try {
|
|
if (env.MESH_GITEA_CONFIG_FILE) given = JSON.parse(readFileSync(env.MESH_GITEA_CONFIG_FILE, "utf8"))["main-mergers"] ?? [];
|
|
} catch {
|
|
given = [];
|
|
}
|
|
for (const name of Array.isArray(given) ? given : []) {
|
|
if (typeof name === "string" && LOGIN.test(name) && !name.includes("..") && !out.includes(name)) out.push(name);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** What main's rule always says: no push, only the tools' account and main-mergers merge (novox/hq ADR 0398 §2,
|
|
* issue 948, ADR 0534 §2), and no status required (ADR 0534 §1). */
|
|
export function mainRuleBody(mergers: string[] = mainMergers()): Record<string, unknown> {
|
|
return { enable_push: false, enable_merge_whitelist: true, merge_whitelist_usernames: mergers, ...noStatusBody() };
|
|
}
|
|
|
|
/** The forge's body for a wanted protection. */
|
|
export function protectionBody(want: ProtectionWanted, creating: boolean, branch = ""): Record<string, unknown> {
|
|
const body: Record<string, unknown> = noStatusBody();
|
|
if (want.push !== undefined) body.enable_push = want.push;
|
|
else if (creating) body.enable_push = false;
|
|
if (want.blockAdminOverride !== undefined) body.block_admin_merge_override = want.blockAdminOverride;
|
|
if (branch === MAIN_BRANCH) Object.assign(body, mainRuleBody()); // whatever was asked: main takes no push (ADR 0398 §2)
|
|
return body;
|
|
}
|
|
|