1// Regenerates src/ghostty/constants.ts from the vendored C headers.
2// Parses `typedef enum ... } Name;` blocks, tracking implicit +1 values.
3// Run from the package root: node vendor/gen-constants.mjs
4import { readFileSync, readdirSync, writeFileSync } from "node:fs";
5import { join, dirname } from "node:path";
6import { fileURLToPath } from "node:url";
7
8const root = dirname(fileURLToPath(import.meta.url));
9const headerDir = join(root, "include/ghostty/vt");
10const headers = readdirSync(headerDir, { recursive: true })
11 .filter((f) => String(f).endsWith(".h"))
12 .map((f) => readFileSync(join(headerDir, String(f)), "utf8"));
13
14const enums = new Map();
15const enumRe = /typedef enum[^{]*\{([\s\S]*?)\}\s*(\w+)\s*;/g;
16for (const src of headers) {
17 for (const m of src.matchAll(enumRe)) {
18 const [, body, name] = m;
19 const members = [];
20 let next = 0;
21 const stripped = body.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
22 for (const entry of stripped.split(",")) {
23 const em = entry.match(/([A-Z][A-Z0-9_]+)\s*(?:=\s*([^,\s]+))?/);
24 if (!em) continue;
25 const [, ident, valueExpr] = em;
26 if (ident.endsWith("_MAX_VALUE")) continue;
27 let value = next;
28 if (valueExpr !== undefined) {
29 if (/^-?\d+$/.test(valueExpr)) value = Number(valueExpr);
30 else if (/^0x[0-9a-fA-F]+$/.test(valueExpr)) value = Number(valueExpr);
31 else continue; // symbolic (GHOSTTY_ENUM_MAX_VALUE etc.)
32 }
33 members.push([ident, value]);
34 next = value + 1;
35 }
36 if (members.length > 0 && name.startsWith("Ghostty")) enums.set(name, members);
37 }
38}
39
40let out = `// GENERATED by vendor/gen-constants.mjs from the vendored libghostty-vt headers\n`;
41out += `// (commit in vendor/COMMIT). Do not edit by hand; re-run the generator on bump.\n\n`;
42for (const [name, members] of [...enums.entries()].sort(([a], [b]) => a.localeCompare(b))) {
43 out += `export const ${name} = {\n`;
44 for (const [ident, value] of members) out += ` ${ident}: ${value},\n`;
45 out += `} as const;\n\n`;
46}
47out = `${out.trimEnd()}\n`;
48const outPath = join(root, "../src/ghostty/constants.ts");
49if (process.argv.includes("--check")) {
50 const current = readFileSync(outPath, "utf8");
51 if (current !== out) {
52 let mismatch = 0;
53 while (current[mismatch] === out[mismatch]) mismatch++;
54 throw new Error(
55 `${outPath} is stale at byte ${mismatch}: expected ${JSON.stringify(out.slice(mismatch, mismatch + 80))}, got ${JSON.stringify(current.slice(mismatch, mismatch + 80))}; run node vendor/gen-constants.mjs`,
56 );
57 }
58 console.log(`verified ${outPath}: ${enums.size} enums`);
59} else {
60 writeFileSync(outPath, out);
61 console.log(`wrote ${outPath}: ${enums.size} enums`);
62}