/** * Crypto primitive select codegen. * * Reads `crypto.config.ts` and, for every registered primitive, writes * `packages/universal/crypto-/src/primitives//.select.ts` * as a one-line re-export of `./impl/..js`. * * The generated file carries a "GENERATED DO NOT EDIT" banner comment. * Any hand-edit is detected by `check-crypto-selects.ts` and fails CI. */ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { cryptoConfig, type PrimitiveFlavour } from "../crypto.config.js"; // Inlined from @suaveplan/crypto-utils/primitive-id so this script can run // without needing the crypto-utils package to be built/linked first. // Must stay in sync with universal/crypto/crypto-utils/src/primitive-id/primitive-id.ts. const HASH_PRIMITIVE_IDS = [ "sha256", "sha384", "sha512", "hmac", "hkdf", "pbkdf2", "argon2id", ] as const; const CURVE_PRIMITIVE_IDS = ["secp256k1", "ed25519", "x25519", "p256"] as const; const CIPHER_PRIMITIVE_IDS = [ "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", ] as const; type HashId = (typeof HASH_PRIMITIVE_IDS)[number]; type CurveId = (typeof CURVE_PRIMITIVE_IDS)[number]; type CipherId = (typeof CIPHER_PRIMITIVE_IDS)[number]; export type PrimitiveId = HashId | CurveId | CipherId; function primitiveCategory(id: PrimitiveId): "hash" | "curve" | "cipher" { if ((HASH_PRIMITIVE_IDS as readonly string[]).includes(id)) return "hash"; if ((CURVE_PRIMITIVE_IDS as readonly string[]).includes(id)) return "curve"; return "cipher"; } const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), ".."); export const BANNER = "/* GENERATED — DO NOT EDIT. Regenerate with `bun scripts/gen-crypto-selects.ts`. */"; export function selectFileContents( id: PrimitiveId, flavour: PrimitiveFlavour ): string { return `${BANNER}\nexport * from "./impl/${id}.${flavour}.js";\n`; } function categoryPackageName(id: PrimitiveId): string { const cat = primitiveCategory(id); if (cat === "hash") return "crypto-hash"; if (cat === "curve") return "crypto-curve"; return "crypto-cipher"; } export function selectPath(id: PrimitiveId): string { const pkg = categoryPackageName(id); return resolve( REPO_ROOT, `universal/auth/${pkg}/src/primitives/${id}/${id}.select.ts` ); } export interface SelectPlan { readonly id: PrimitiveId; readonly flavour: PrimitiveFlavour; readonly path: string; readonly contents: string; } export function plan(): readonly SelectPlan[] { const out: SelectPlan[] = []; const allIds = [ ...HASH_PRIMITIVE_IDS, ...CURVE_PRIMITIVE_IDS, ...CIPHER_PRIMITIVE_IDS, ] as readonly PrimitiveId[]; for (const id of allIds) { const flavour = cryptoConfig[id]; out.push({ id, flavour, path: selectPath(id), contents: selectFileContents(id, flavour), }); } return out; } export function writePlan(plans: readonly SelectPlan[]): readonly { readonly path: string; readonly action: "written" | "unchanged"; }[] { const results: { path: string; action: "written" | "unchanged" }[] = []; for (const p of plans) { mkdirSync(dirname(p.path), { recursive: true }); let current: string | undefined; if (existsSync(p.path)) { current = readFileSync(p.path, "utf8"); } if (current === p.contents) { results.push({ path: p.path, action: "unchanged" }); continue; } if (current !== undefined && !current.startsWith(BANNER)) { throw new Error( `Refusing to overwrite hand-edited select file (banner missing): ${p.path}` ); } writeFileSync(p.path, p.contents, "utf8"); results.push({ path: p.path, action: "written" }); } return results; } async function main(): Promise { const results = writePlan(plan()); const written = results.filter((r) => r.action === "written").length; const unchanged = results.filter((r) => r.action === "unchanged").length; console.log( `crypto-selects: ${written} written, ${unchanged} unchanged (${results.length} total)` ); } if (import.meta.main) { await main(); }