194 lines
7.0 KiB
JavaScript
194 lines
7.0 KiB
JavaScript
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`,
|
|
);
|
|
}
|