Initial Commit
This commit is contained in:
137
scripts/gen-crypto-selects.ts
Normal file
137
scripts/gen-crypto-selects.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Crypto primitive select codegen.
|
||||
*
|
||||
* Reads `crypto.config.ts` and, for every registered primitive, writes
|
||||
* `packages/universal/crypto-<category>/src/primitives/<id>/<id>.select.ts`
|
||||
* as a one-line re-export of `./impl/<id>.<flavour>.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<void> {
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user