UNPKG

ttsc

Version:

General-purpose TypeScript-Go compiler, runtime, plugin host, and LSP host.

178 lines 7.13 kB
"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