ttsc
Version:
General-purpose TypeScript-Go compiler, runtime, plugin host, and LSP host.
178 lines • 7.13 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.compileProjectInMemory = compileProjectInMemory;
const node_fs_1 = __importDefault(require("node:fs"));
const node_os_1 = __importDefault(require("node:os"));
const node_path_1 = __importDefault(require("node:path"));
const loadProjectPlugins_1 = require("../../plugin/internal/loadProjectPlugins");
const buildNativeCompiler_1 = require("./buildNativeCompiler");
const paths_1 = require("./paths");
const readProjectConfig_1 = require("./project/readProjectConfig");
const runBuild_1 = require("./runBuild");
const spawnNative_1 = require("./spawnNative");
/**
* Compile a project and capture emitted files without writing to the project
* tree.
*
* When no plugins are configured the fast path spawns the native ttsc compiler
* host (`cmd/ttsc api-compile`) which returns a structured JSON response
* containing diagnostics and an output file map. When plugins are present the
* slow path goes through `runBuild` into a temp directory and reads the files
* back from disk.
*
* @returns A map of output path → file content plus a `TtscBuildResult` with
* diagnostics and the exit status.
*/
function compileProjectInMemory(options) {
const cwd = node_path_1.default.resolve(options.cwd ?? process.cwd());
const project = (0, readProjectConfig_1.readProjectConfig)({
cwd,
projectRoot: options.projectRoot,
tsconfig: options.tsconfig,
});
if (shouldUsePluginBuild(options, project)) {
return compileProjectWithPlugins(options, cwd, project);
}
const tsconfig = project.path;
const binary = (0, buildNativeCompiler_1.buildNativeCompiler)({
cacheBaseDir: project.root,
cacheDir: options.cacheDir ?? options.env?.TTSC_CACHE_DIR,
packageRoot: (0, paths_1.packageRootDir)(),
});
const res = (0, spawnNative_1.spawnNative)(binary, ["api-compile", "--cwd", project.root, "--tsconfig", tsconfig], {
cwd: project.root,
env: { ...process.env, ...options.env },
});
if (res.error) {
throw new Error(`ttsc: failed to spawn native compiler host ${binary}: ${res.error.message}`);
}
const output = parseNativeCompileOutput((0, spawnNative_1.outputText)(res.stdout), (0, spawnNative_1.outputText)(res.stderr));
return {
output: output.output,
result: {
diagnostics: output.diagnostics,
status: res.status ?? 1,
stdout: "",
stderr: (0, spawnNative_1.outputText)(res.stderr),
},
};
}
/** Return true when the project or the call-level options declare any plugins. */
function hasConfiguredPlugins(options, project) {
return (0, loadProjectPlugins_1.hasProjectPluginEntries)(project, options.plugins);
}
/**
* Route plugin discovery failures through runBuild's recoverable setup path.
* The fast native-host lane cannot surface the plugin error or run the
* post-failure TypeScript check, while the plugin-backed lane can do both.
*/
function shouldUsePluginBuild(options, project) {
try {
return hasConfiguredPlugins(options, project);
}
catch {
return true;
}
}
/**
* Plugin-backed compilation: emit into a temp directory via `runBuild`, then
* read back every file the build wrote so they can be returned as strings.
*/
function compileProjectWithPlugins(options, cwd, project) {
const tempRoot = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "ttsc-api-output-"));
const tempOutDir = node_path_1.default.join(tempRoot, "out");
try {
const result = (0, runBuild_1.runBuild)({
...options,
cwd,
emit: true,
forceListEmittedFiles: true,
outDir: tempOutDir,
quiet: true,
resolvedProject: project,
structuredDiagnostics: true,
tsconfig: project.path,
});
return {
output: readOutputDirectory(tempOutDir, outputKeyMapper(project)),
result,
};
}
finally {
node_fs_1.default.rmSync(tempRoot, { force: true, recursive: true });
}
}
/**
* Build a function that maps a path relative to the temp output directory to
* the key used in the returned `output` map.
*
* When `outDir` is inside the project root the key is relative to the project
* root (preserving the `outDir` prefix). When `outDir` is outside the project
* root the key is absolute-style (`/absolute/outDir/relative`). When `outDir`
* is absent the key is the bare relative path.
*/
function outputKeyMapper(project) {
const outDir = project.compilerOptions.outDir;
if (!outDir) {
return (relativePath) => relativePath;
}
const relativeOutDir = node_path_1.default.relative(project.root, outDir);
if (relativeOutDir !== "" && !(0, paths_1.isOutsideRelativePath)(relativeOutDir)) {
const prefix = pathToKey(relativeOutDir);
return (relativePath) => node_path_1.default.posix.join(prefix, relativePath);
}
return (relativePath) => pathToKey(node_path_1.default.join(outDir, relativePath));
}
/** Read every file in `directory` recursively and return a `path→content` map. */
function readOutputDirectory(directory, keyOf) {
const output = {};
if (!node_fs_1.default.existsSync(directory)) {
return output;
}
for (const file of listFiles(directory)) {
output[keyOf(pathToKey(node_path_1.default.relative(directory, file)))] = node_fs_1.default.readFileSync(file, "utf8");
}
return output;
}
/** Recursively list all files under `directory`, sorted for stable output. */
function listFiles(directory) {
const out = [];
for (const entry of node_fs_1.default.readdirSync(directory, { withFileTypes: true })) {
const location = node_path_1.default.join(directory, entry.name);
if (entry.isDirectory()) {
out.push(...listFiles(location));
}
else if (entry.isFile()) {
out.push(location);
}
}
return out.sort();
}
/** Normalise a file path to a forward-slash key suitable for the output map. */
function pathToKey(file) {
return file.replace(/\\/g, "/");
}
/**
* Parse the JSON envelope written by the native compiler host to stdout.
*
* On success returns `{ diagnostics, output }`. On JSON parse failure throws a
* descriptive error using stderr (preferred) or stdout as context, so callers
* see the original compiler error rather than a generic JSON parse message.
*/
function parseNativeCompileOutput(stdout, stderr) {
try {
const parsed = JSON.parse(stdout);
return {
diagnostics: parsed.diagnostics ?? [],
output: parsed.output ?? {},
};
}
catch {
throw new Error((stderr || stdout).trim() ||
"ttsc: native compiler host returned no output");
}
}
//# sourceMappingURL=compileProjectInMemory.js.map