import fs from "fs"; import path from "path"; export interface CheckCodePlanTarget { network: string; manifestDir: string; contracts: string[]; } export interface CheckCodePlanInput { root: string; runId: string; artifactHash: string; deploymentId?: string; sourceSuperTaskId?: string; developmentNetwork?: string; executionId?: string; targets: CheckCodePlanTarget[]; } export interface CheckCodePlanResult { planPath: string; runId: string; artifactHash: string; } const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,199}$/; const HASH = /^[0-9a-f]{64}$/; const MANIFEST_DIR = /^\.openzeppelin(?:\.[a-z0-9][a-z0-9-]*)?$/; const safeId = (value: string, label: string): string => { if (!SAFE_ID.test(value)) throw new Error(`${label} is invalid`); return value; }; const superTaskIdentity = (input: CheckCodePlanInput): boolean => { const values = [ input.deploymentId, input.sourceSuperTaskId, input.developmentNetwork, input.executionId, ]; if (values.every(Boolean)) return true; if (values.some(Boolean)) { throw new Error( "SuperTask CheckCode plan requires deploymentId, sourceSuperTaskId, developmentNetwork, and executionId together", ); } return false; }; const writeTextAtomic = (filePath: string, contents: string): void => { fs.mkdirSync(path.dirname(filePath), { recursive: true }); const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`; try { fs.writeFileSync(temporary, contents); fs.renameSync(temporary, filePath); } catch (error) { if (fs.existsSync(temporary)) fs.unlinkSync(temporary); throw error; } }; export const prepareCheckCodePlan = ( input: CheckCodePlanInput, ): CheckCodePlanResult => { const runId = safeId(input.runId, "CheckCode runId"); if (!HASH.test(input.artifactHash)) throw new Error("CheckCode artifactHash is invalid"); const isSuperTask = superTaskIdentity(input); const targets = [...input.targets] .map((target) => ({ network: safeId(target.network, "CheckCode target network"), manifestDir: target.manifestDir, contracts: [...new Set(target.contracts.map((item) => item.trim()).filter(Boolean))].sort(), })) .sort((left, right) => left.network.localeCompare(right.network)); if (targets.length === 0) throw new Error("CheckCode plan requires at least one target"); if (new Set(targets.map((target) => target.network)).size !== targets.length) { throw new Error("CheckCode plan target networks must be unique"); } for (const target of targets) { if (!MANIFEST_DIR.test(target.manifestDir)) { throw new Error(`CheckCode manifestDir is invalid for ${target.network}`); } } if (isSuperTask && ( targets.length !== 1 || targets[0].network !== input.developmentNetwork )) { throw new Error("SuperTask CheckCode plan must target only developmentNetwork"); } const relativePath = isSuperTask ? `scripts/tasks/${safeId(input.deploymentId!, "deploymentId")}/docs/checkcode-plan.md` : "docs/checkcode-plan.md"; const lines = [ "# CheckCode Plan", "", `runId: ${runId}`, `artifactHash: ${input.artifactHash}`, ...(isSuperTask ? [ `deploymentId: ${safeId(input.deploymentId!, "deploymentId")}`, `sourceSuperTaskId: ${safeId(input.sourceSuperTaskId!, "sourceSuperTaskId")}`, `developmentNetwork: ${safeId(input.developmentNetwork!, "developmentNetwork")}`, `executionId: ${safeId(input.executionId!, "executionId")}`, ] : []), "", "## Targets", ...targets.flatMap((target) => [ `- network: ${target.network}`, ` manifestDir: ${target.manifestDir}`, ` contracts: ${target.contracts.join(", ") || ""}`, ]), "", ]; writeTextAtomic(path.join(input.root, ...relativePath.split("/")), lines.join("\n")); return { planPath: relativePath, runId, artifactHash: input.artifactHash }; }; export const runCheckCodePlanCli = ( argv: string[], defaultRoot: string = process.cwd(), ): CheckCodePlanResult => { const values = new Map(); for (let index = 0; index < argv.length; index += 1) { const item = argv[index]; if (!item.startsWith("--")) continue; const equalAt = item.indexOf("="); const key = item.slice(2, equalAt > 2 ? equalAt : undefined); const next = argv[index + 1]; const value = equalAt > 2 ? item.slice(equalAt + 1) : next; if (!value || value.startsWith("--")) throw new Error(`--${key} requires a value`); if (equalAt < 0) index += 1; const current = values.get(key) || []; current.push(value); values.set(key, current); } const one = (key: string, required = false): string | undefined => { const items = values.get(key) || []; if (items.length > 1) throw new Error(`--${key} may be provided only once`); if (required && !items[0]?.trim()) throw new Error(`--${key} is required`); return items[0]?.trim() || undefined; }; const developmentNetwork = one("developmentNetwork"); const network = one("network") || developmentNetwork; if (!network) throw new Error("--network or --developmentNetwork is required"); return prepareCheckCodePlan({ root: path.resolve(one("root") || defaultRoot), runId: one("runId", true)!, artifactHash: one("artifactHash", true)!, deploymentId: one("deploymentId"), sourceSuperTaskId: one("sourceSuperTaskId"), developmentNetwork, executionId: one("executionId"), targets: [{ network, manifestDir: one("manifestDir", true)!, contracts: (values.get("contract") || []) .flatMap((item) => item.split(",")) .map((item) => item.trim()) .filter(Boolean), }], }); };