import { createHash } from "node:crypto"; import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs"; import { createRequire } from "node:module"; import { dirname, join, relative, resolve, sep } from "node:path"; import process from "node:process"; import { fileURLToPath } from "node:url"; const require = createRequire(import.meta.url); const here = dirname(fileURLToPath(import.meta.url)); const root = resolve(here, ".."); const scriptPath = fileURLToPath(import.meta.url); function normalizeText(value) { return value.replace(/\r\n?/g, "\n"); } function sha256(value) { return createHash("sha256").update(value).digest("hex"); } function loadTypeScript() { // TypeScript 7's root package is the native CLI and no longer exposes the // legacy compiler API used by the embedded editor compiler. Keep that API // isolated behind @wrnexus/typecheck's explicit TypeScript 6 dependency // while the workspace itself type-checks with TypeScript 7. const candidates = [ process.env.TYPESCRIPT_PATH, join(root, "packages", "typecheck", "node_modules", "typescript"), "typescript", ].filter(Boolean); for (const candidate of candidates) { try { return require(candidate); } catch { // Try the next local or explicitly supplied compiler path. } } throw new Error( "TypeScript is required to build the editor compiler. Run `bun install` or set TYPESCRIPT_PATH.", ); } const ts = loadTypeScript(); function walk(dir) { const files = []; for (const entry of readdirSync(dir)) { const path = join(dir, entry); const stat = statSync(path); if (stat.isDirectory()) files.push(...walk(path)); else if (stat.isFile() && path.endsWith(".ts") && !path.endsWith(".test.ts")) files.push(path); } return files; } function moduleId(file) { return relative(root, file).split(sep).join("/"); } const sourceFiles = [ ...walk(join(root, "packages", "syntax", "src")), ...walk(join(root, "packages", "compiler", "src")), ].sort(); const sourceHashBuilder = createHash("sha256"); const modules = []; for (const file of sourceFiles) { const id = moduleId(file); const source = normalizeText(readFileSync(file, "utf8")); sourceHashBuilder.update(id); sourceHashBuilder.update("\0"); sourceHashBuilder.update(source); sourceHashBuilder.update("\0"); const result = ts.transpileModule(source, { fileName: file, reportDiagnostics: true, compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS, esModuleInterop: true, skipLibCheck: true, sourceMap: false, inlineSourceMap: false, removeComments: false, rewriteRelativeImportExtensions: true, newLine: ts.NewLineKind.LineFeed, }, }); const diagnostics = result.diagnostics ?? []; if (diagnostics.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) { const message = ts.formatDiagnosticsWithColorAndContext(diagnostics, { getCanonicalFileName: (name) => name, getCurrentDirectory: () => root, getNewLine: () => "\n", }); throw new Error(message); } modules.push( `${JSON.stringify(id)}: function (module, exports, require, __filename, __dirname) {\n${normalizeText(result.outputText)}\n}`, ); } const sourceHash = sourceHashBuilder.digest("hex"); const generatorHash = sha256(normalizeText(readFileSync(scriptPath, "utf8"))); const output = `"use strict"; // Generated by scripts/build-editor-compiler.mjs. Do not edit directly. // WRN editor compiler source hash: ${sourceHash} // WRN editor compiler generator hash: ${generatorHash} // Generated with TypeScript: ${ts.version} const __nodeRequire = require; const __path = __nodeRequire("node:path"); const __modules = { ${modules.join(",\n")} }; const __aliases = { "@wrnexus/syntax": "packages/syntax/src/index.ts", "@wrnexus/syntax/parser": "packages/syntax/src/parser.ts", "@wrnexus/syntax/tokenizer": "packages/syntax/src/tokenizer.ts", "@wrnexus/syntax/types": "packages/syntax/src/types.ts", "@wrnexus/syntax/diagnostics": "packages/syntax/src/diagnostics.ts", "@wrnexus/syntax/spec": "packages/syntax/src/spec.ts" }; const __cache = Object.create(null); function __normalize(id) { const normalized = id.split("\\\\").join("/"); return normalized.startsWith("./") ? normalized.slice(2) : normalized; } function __resolve(request, parent) { if (__aliases[request]) return __aliases[request]; if (!request.startsWith(".")) return null; const base = __normalize(__path.posix.join(__path.posix.dirname(parent), request)); const candidates = [ base, base.endsWith(".js") ? base.slice(0, -3) + ".ts" : base, base.endsWith(".ts") ? base : base + ".ts", (base.endsWith("/") ? base.slice(0, -1) : base) + "/index.ts" ]; for (const candidate of candidates) { if (__modules[candidate]) return candidate; } return null; } function __load(id) { if (__cache[id]) return __cache[id].exports; const factory = __modules[id]; if (!factory) throw new Error("WRN editor compiler module not found: " + id); const module = { exports: {} }; __cache[id] = module; const localRequire = (request) => { const resolved = __resolve(request, id); return resolved ? __load(resolved) : __nodeRequire(request); }; factory(module, module.exports, localRequire, id, __path.posix.dirname(id)); return module.exports; } module.exports = __load("packages/compiler/src/index.ts"); `; const destination = join(root, "editors", "vscode", "src", "compiler.cjs"); if (process.argv.includes("--check")) { const existing = existsSync(destination) ? normalizeText(readFileSync(destination, "utf8")) : ""; const embeddedSourceHash = existing.match( /^\/\/ WRN editor compiler source hash: ([a-f0-9]{64})$/m, )?.[1]; const embeddedGeneratorHash = existing.match( /^\/\/ WRN editor compiler generator hash: ([a-f0-9]{64})$/m, )?.[1]; const embeddedTypeScriptVersion = existing.match(/^\/\/ Generated with TypeScript: (.+)$/m)?.[1]; const hashesMatch = embeddedSourceHash === sourceHash && embeddedGeneratorHash === generatorHash; const exactMatch = existing === normalizeText(output); const compatibleCompilerDifference = hashesMatch && Boolean(embeddedTypeScriptVersion) && embeddedTypeScriptVersion !== ts.version; if (!hashesMatch || (!exactMatch && !compatibleCompilerDifference)) { throw new Error( `${relative(root, destination)} is stale. Run bun run --cwd editors/vscode build and commit the result.`, ); } const versionDetail = exactMatch ? `TypeScript ${ts.version}` : `source-compatible output generated with TypeScript ${embeddedTypeScriptVersion}; current TypeScript is ${ts.version}`; process.stdout.write( `Verified ${relative(root, destination)} matches ${sourceFiles.length} TypeScript modules (${versionDetail}).\n`, ); } else { writeFileSync(destination, output, "utf8"); process.stdout.write( `Built ${relative(root, destination)} from ${sourceFiles.length} TypeScript modules with TypeScript ${ts.version}.\n`, ); }