ttsc 0.4.4 → 0.5.0-dev.20260429
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -3
- package/cmd/ttsc/main.go +2 -2
- package/driver/program.go +120 -15
- package/go.mod +3 -3
- package/go.sum +6 -6
- package/lib/api.d.ts +3 -3
- package/lib/api.js +275 -624
- package/lib/api.js.map +1 -1
- package/lib/cli.js +78 -2
- package/lib/cli.js.map +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.js +3 -3
- package/lib/native.d.ts +5 -0
- package/lib/native.js +8 -0
- package/lib/native.js.map +1 -1
- package/lib/plugin.d.ts +8 -48
- package/lib/plugin.js +79 -132
- package/lib/plugin.js.map +1 -1
- package/lib/runner/cli.js +1 -0
- package/lib/runner/cli.js.map +1 -1
- package/lib/source-build.d.ts +30 -0
- package/lib/source-build.js +365 -0
- package/lib/source-build.js.map +1 -0
- package/package.json +9 -9
- package/shim/ast/go.mod +1 -1
- package/shim/ast/lint.go +328 -0
- package/shim/bundled/go.mod +1 -1
- package/shim/checker/go.mod +1 -1
- package/shim/compiler/go.mod +1 -1
- package/shim/core/go.mod +1 -1
- package/shim/core/shim.go +15 -0
- package/shim/diagnosticwriter/go.mod +1 -1
- package/shim/diagnosticwriter/lint.go +137 -0
- package/shim/parser/go.mod +1 -1
- package/shim/scanner/go.mod +1 -1
- package/shim/tsoptions/go.mod +1 -1
- package/shim/tspath/go.mod +1 -1
- package/shim/vfs/cachedvfs/go.mod +1 -1
- package/shim/vfs/go.mod +1 -1
- package/shim/vfs/osvfs/go.mod +1 -1
- package/src/api.ts +413 -771
- package/src/cli.ts +82 -2
- package/src/index.ts +3 -3
- package/src/native.ts +18 -0
- package/src/plugin.ts +107 -213
- package/src/runner/cli.ts +5 -0
- package/src/source-build.ts +383 -0
- package/test/native.test.ts +43 -0
- package/test/source-build.test.ts +219 -0
package/src/api.ts
CHANGED
|
@@ -23,10 +23,7 @@ import * as path from "node:path";
|
|
|
23
23
|
|
|
24
24
|
import { type ResolveOptions, resolveBinary } from "./platform";
|
|
25
25
|
import {
|
|
26
|
-
type
|
|
27
|
-
type TtscPlugin,
|
|
28
|
-
applyPluginSourceTransformsWithMap,
|
|
29
|
-
applyPluginTransforms,
|
|
26
|
+
type LoadedNativePlugin,
|
|
30
27
|
loadProjectPlugins,
|
|
31
28
|
} from "./plugin";
|
|
32
29
|
import {
|
|
@@ -90,7 +87,7 @@ export interface BuildOptions extends CommonOptions {
|
|
|
90
87
|
quiet?: boolean;
|
|
91
88
|
/** @internal Caller already ran diagnostics and accepts responsibility. */
|
|
92
89
|
skipDiagnosticsCheck?: boolean;
|
|
93
|
-
/** @internal Force `tsgo --listEmittedFiles`
|
|
90
|
+
/** @internal Force `tsgo --listEmittedFiles` for caller-side output discovery. */
|
|
94
91
|
forceListEmittedFiles?: boolean;
|
|
95
92
|
}
|
|
96
93
|
|
|
@@ -155,6 +152,47 @@ function outputText(value: string | Buffer | null | undefined): string {
|
|
|
155
152
|
return typeof value === "string" ? value : value.toString("utf8");
|
|
156
153
|
}
|
|
157
154
|
|
|
155
|
+
function hasCapability(
|
|
156
|
+
plugin: LoadedNativePlugin,
|
|
157
|
+
capability: string,
|
|
158
|
+
): boolean {
|
|
159
|
+
return plugin.backend.capabilities?.includes(capability) === true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isOutputPlugin(plugin: LoadedNativePlugin): boolean {
|
|
163
|
+
return hasCapability(plugin, "output");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isCheckOnlyPlugin(plugin: LoadedNativePlugin): boolean {
|
|
167
|
+
const capabilities = plugin.backend.capabilities ?? [];
|
|
168
|
+
return (
|
|
169
|
+
capabilities.length > 0 &&
|
|
170
|
+
capabilities.every((capability) => capability === "check")
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function isCompilerPlugin(plugin: LoadedNativePlugin): boolean {
|
|
175
|
+
return !isOutputPlugin(plugin) && !isCheckOnlyPlugin(plugin);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function outputPlugins(
|
|
179
|
+
plugins: readonly LoadedNativePlugin[],
|
|
180
|
+
): LoadedNativePlugin[] {
|
|
181
|
+
return plugins.filter(isOutputPlugin);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function checkOnlyPlugins(
|
|
185
|
+
plugins: readonly LoadedNativePlugin[],
|
|
186
|
+
): LoadedNativePlugin[] {
|
|
187
|
+
return plugins.filter(isCheckOnlyPlugin);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function compilerPlugins(
|
|
191
|
+
plugins: readonly LoadedNativePlugin[],
|
|
192
|
+
): LoadedNativePlugin[] {
|
|
193
|
+
return plugins.filter(isCompilerPlugin);
|
|
194
|
+
}
|
|
195
|
+
|
|
158
196
|
/**
|
|
159
197
|
* Transform a single .ts file and return the rewritten JS as a string.
|
|
160
198
|
*
|
|
@@ -172,16 +210,8 @@ export function transform(options: TransformOptions): string {
|
|
|
172
210
|
? options.file
|
|
173
211
|
: path.resolve(options.cwd ?? process.cwd(), options.file),
|
|
174
212
|
);
|
|
175
|
-
if (execution.
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
if (execution.nativeBinary) {
|
|
179
|
-
return transformWithNativeBinary(options, execution, sourceFile);
|
|
180
|
-
}
|
|
181
|
-
if (execution.nativeMode !== "none") {
|
|
182
|
-
throw new Error(
|
|
183
|
-
`ttsc.transform: native rewrite mode "${execution.nativeMode}" requires a plugin-provided version-matched binary`,
|
|
184
|
-
);
|
|
213
|
+
if (execution.nativePlugins.length > 0) {
|
|
214
|
+
return transformWithNativePlugins(options, execution, sourceFile);
|
|
185
215
|
}
|
|
186
216
|
|
|
187
217
|
const tempOutDir = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-transform-"));
|
|
@@ -205,18 +235,7 @@ export function transform(options: TransformOptions): string {
|
|
|
205
235
|
if (!emitted) {
|
|
206
236
|
throw new Error(`ttsc.transform: no output produced for ${sourceFile}`);
|
|
207
237
|
}
|
|
208
|
-
const transformed =
|
|
209
|
-
execution.outputPlugins,
|
|
210
|
-
{
|
|
211
|
-
command: "transform",
|
|
212
|
-
compilerOptions: execution.compilerOptions,
|
|
213
|
-
cwd: execution.cwd,
|
|
214
|
-
projectRoot: execution.projectRoot,
|
|
215
|
-
sourceFile,
|
|
216
|
-
tsconfig: execution.tsconfig,
|
|
217
|
-
},
|
|
218
|
-
fs.readFileSync(emitted, "utf8"),
|
|
219
|
-
);
|
|
238
|
+
const transformed = fs.readFileSync(emitted, "utf8");
|
|
220
239
|
if (options.out) {
|
|
221
240
|
fs.mkdirSync(path.dirname(options.out), { recursive: true });
|
|
222
241
|
fs.writeFileSync(options.out, transformed, "utf8");
|
|
@@ -227,129 +246,84 @@ export function transform(options: TransformOptions): string {
|
|
|
227
246
|
}
|
|
228
247
|
}
|
|
229
248
|
|
|
230
|
-
function
|
|
249
|
+
function transformWithNativePlugins(
|
|
231
250
|
options: TransformOptions,
|
|
232
251
|
execution: ExecutionContext,
|
|
233
252
|
sourceFile: string,
|
|
234
253
|
): string {
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
"--file=" + sourceFile,
|
|
238
|
-
"--tsconfig=" + execution.tsconfig,
|
|
239
|
-
"--rewrite-mode=" + execution.nativeMode,
|
|
240
|
-
];
|
|
241
|
-
|
|
242
|
-
const res = spawnBinary(execution.nativeBinary!, args, {
|
|
243
|
-
cwd: options.cwd,
|
|
244
|
-
env: mergeEnv(options.env),
|
|
245
|
-
encoding: "utf8",
|
|
246
|
-
});
|
|
247
|
-
if (res.error) {
|
|
248
|
-
throw new Error(
|
|
249
|
-
"ttsc.transform: failed to spawn " +
|
|
250
|
-
execution.nativeBinary +
|
|
251
|
-
": " +
|
|
252
|
-
res.error.message,
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
if (res.status !== 0) {
|
|
254
|
+
const checked = runNativeCheckPlugins(options, execution);
|
|
255
|
+
if (checked.status !== 0) {
|
|
256
256
|
throw new Error(
|
|
257
|
-
"ttsc.transform exited " +
|
|
257
|
+
"ttsc.transform exited " + checked.status + "\n" + checked.stderr,
|
|
258
258
|
);
|
|
259
259
|
}
|
|
260
|
-
const transformed = finalizeTransformText(
|
|
261
|
-
execution.outputPlugins,
|
|
262
|
-
{
|
|
263
|
-
command: "transform",
|
|
264
|
-
compilerOptions: execution.compilerOptions,
|
|
265
|
-
cwd: execution.cwd,
|
|
266
|
-
projectRoot: execution.projectRoot,
|
|
267
|
-
sourceFile,
|
|
268
|
-
tsconfig: execution.tsconfig,
|
|
269
|
-
},
|
|
270
|
-
outputText(res.stdout),
|
|
271
|
-
);
|
|
272
|
-
if (options.out) {
|
|
273
|
-
fs.mkdirSync(path.dirname(options.out), { recursive: true });
|
|
274
|
-
fs.writeFileSync(options.out, transformed, "utf8");
|
|
275
|
-
}
|
|
276
|
-
return transformed;
|
|
277
|
-
}
|
|
278
260
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
sourceFile: string,
|
|
283
|
-
): string {
|
|
284
|
-
if (execution.nativeBinary || execution.nativeMode !== "none") {
|
|
285
|
-
throw new Error(
|
|
286
|
-
"ttsc.transform: transformSource cannot be combined with native rewrite mode yet",
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
const materialized = materializeSourceTransformedProject(
|
|
290
|
-
execution,
|
|
291
|
-
"transform",
|
|
292
|
-
sourceFile,
|
|
293
|
-
);
|
|
294
|
-
const tempOutDir = path.join(materialized.root, ".ttsc-transform-out");
|
|
261
|
+
const compilers = compilerPlugins(execution.nativePlugins);
|
|
262
|
+
const outputs = outputPlugins(execution.nativePlugins);
|
|
263
|
+
const tempOutDir = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-transform-"));
|
|
295
264
|
try {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
plugins: false,
|
|
304
|
-
tsconfig: materialized.tsconfig,
|
|
305
|
-
});
|
|
306
|
-
if (result.status !== 0) {
|
|
307
|
-
throw new Error(
|
|
308
|
-
"ttsc.transform exited " +
|
|
309
|
-
result.status +
|
|
310
|
-
"\n" +
|
|
311
|
-
(result.stderr || result.stdout),
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
const emitted = findEmittedFile(tempOutDir, materialized.root, tempSourceFile);
|
|
315
|
-
if (!emitted) {
|
|
316
|
-
throw new Error(`ttsc.transform: no output produced for ${sourceFile}`);
|
|
317
|
-
}
|
|
318
|
-
let transformed = finalizeTransformText(
|
|
319
|
-
execution.outputPlugins,
|
|
320
|
-
{
|
|
321
|
-
command: "transform",
|
|
322
|
-
compilerOptions: execution.compilerOptions,
|
|
323
|
-
cwd: execution.cwd,
|
|
324
|
-
projectRoot: execution.projectRoot,
|
|
265
|
+
let emitted: string | null = null;
|
|
266
|
+
if (compilers.length !== 0) {
|
|
267
|
+
assertSingleCompilerHost(compilers);
|
|
268
|
+
emitted = path.join(tempOutDir, "transform.js");
|
|
269
|
+
const transformed = runNativeCompilerTransform(
|
|
270
|
+
options,
|
|
271
|
+
execution,
|
|
325
272
|
sourceFile,
|
|
273
|
+
emitted,
|
|
274
|
+
compilers,
|
|
275
|
+
);
|
|
276
|
+
if (transformed.status !== 0) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
"ttsc.transform exited " +
|
|
279
|
+
transformed.status +
|
|
280
|
+
"\n" +
|
|
281
|
+
transformed.stderr,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
const result = build({
|
|
286
|
+
...options,
|
|
287
|
+
emit: true,
|
|
288
|
+
outDir: tempOutDir,
|
|
289
|
+
plugins: false,
|
|
290
|
+
skipDiagnosticsCheck: checkOnlyPlugins(execution.nativePlugins).length !== 0,
|
|
326
291
|
tsconfig: execution.tsconfig,
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
292
|
+
});
|
|
293
|
+
if (result.status !== 0) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
"ttsc.transform exited " +
|
|
296
|
+
result.status +
|
|
297
|
+
"\n" +
|
|
298
|
+
(result.stderr || result.stdout),
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
emitted = findEmittedFile(tempOutDir, execution.projectRoot, sourceFile);
|
|
302
|
+
if (!emitted) {
|
|
303
|
+
throw new Error(`ttsc.transform: no output produced for ${sourceFile}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
for (const plugin of outputs) {
|
|
308
|
+
const result = runNativeOutputPlugin(options, execution, plugin, emitted);
|
|
309
|
+
if (result.status !== 0) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
"ttsc.transform exited " +
|
|
312
|
+
result.status +
|
|
313
|
+
"\n" +
|
|
314
|
+
(result.stderr || result.stdout),
|
|
344
315
|
);
|
|
345
|
-
transformed = rewriteSourceMapReference(transformed, path.basename(outMap));
|
|
346
316
|
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const transformed = fs.readFileSync(emitted, "utf8");
|
|
320
|
+
if (options.out) {
|
|
347
321
|
fs.mkdirSync(path.dirname(options.out), { recursive: true });
|
|
348
322
|
fs.writeFileSync(options.out, transformed, "utf8");
|
|
349
323
|
}
|
|
350
324
|
return transformed;
|
|
351
325
|
} finally {
|
|
352
|
-
fs.rmSync(
|
|
326
|
+
fs.rmSync(tempOutDir, { recursive: true, force: true });
|
|
353
327
|
}
|
|
354
328
|
}
|
|
355
329
|
|
|
@@ -376,20 +350,61 @@ export interface BuildResult {
|
|
|
376
350
|
*/
|
|
377
351
|
export function build(options: BuildOptions = {}): BuildResult {
|
|
378
352
|
const execution = resolveExecutionContext(options);
|
|
379
|
-
if (execution.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
353
|
+
if (execution.nativePlugins.length > 0) {
|
|
354
|
+
const compilers = compilerPlugins(execution.nativePlugins);
|
|
355
|
+
const outputs = outputPlugins(execution.nativePlugins);
|
|
356
|
+
const checked = runNativeCheckPlugins(options, execution);
|
|
357
|
+
if (checked.status !== 0) {
|
|
358
|
+
return checked;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (options.emit === false) {
|
|
362
|
+
if (compilers.length !== 0) {
|
|
363
|
+
assertSingleCompilerHost(compilers);
|
|
364
|
+
return appendBuildOutput(
|
|
365
|
+
checked,
|
|
366
|
+
buildWithNativeCompilerPlugins(options, execution, compilers),
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
if (checked.stdout !== "" || checked.stderr !== "") {
|
|
370
|
+
return checked;
|
|
371
|
+
}
|
|
372
|
+
return runTsgo(execution, ["--noEmit"], options);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
let result: BuildResult;
|
|
376
|
+
if (compilers.length !== 0) {
|
|
377
|
+
assertSingleCompilerHost(compilers);
|
|
378
|
+
result = appendBuildOutput(
|
|
379
|
+
checked,
|
|
380
|
+
buildWithNativeCompilerPlugins(options, execution, compilers),
|
|
381
|
+
);
|
|
382
|
+
} else {
|
|
383
|
+
if (
|
|
384
|
+
checked.stdout === "" &&
|
|
385
|
+
checked.stderr === "" &&
|
|
386
|
+
options.skipDiagnosticsCheck !== true
|
|
387
|
+
) {
|
|
388
|
+
const tsgoChecked = runTsgo(execution, ["--noEmit"], options);
|
|
389
|
+
if (tsgoChecked.status !== 0) {
|
|
390
|
+
return tsgoChecked;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const args = createTsgoBuildArgs(execution, options, {
|
|
394
|
+
listEmittedFiles:
|
|
395
|
+
outputs.length !== 0 || options.forceListEmittedFiles === true,
|
|
396
|
+
});
|
|
397
|
+
const emitted = runTsgoBuild(execution, options, args);
|
|
398
|
+
result = appendBuildOutput(checked, emitted);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (result.status !== 0 || outputs.length === 0) {
|
|
402
|
+
return result;
|
|
403
|
+
}
|
|
404
|
+
return appendBuildOutput(
|
|
405
|
+
result,
|
|
406
|
+
applyOutputPlugins(options, execution, result.emittedFiles ?? [], outputs),
|
|
407
|
+
);
|
|
393
408
|
}
|
|
394
409
|
|
|
395
410
|
if (options.emit !== false && options.skipDiagnosticsCheck !== true) {
|
|
@@ -401,96 +416,23 @@ export function build(options: BuildOptions = {}): BuildResult {
|
|
|
401
416
|
|
|
402
417
|
const args = createTsgoBuildArgs(execution, options, {
|
|
403
418
|
listEmittedFiles:
|
|
404
|
-
options.emit !== false &&
|
|
405
|
-
(execution.outputPlugins.length > 0 || options.forceListEmittedFiles === true),
|
|
406
|
-
});
|
|
407
|
-
const res = spawnBinary(execution.tsgo.binary, args, {
|
|
408
|
-
cwd: execution.projectRoot,
|
|
409
|
-
env: mergeEnv(options.env),
|
|
410
|
-
encoding: "utf8",
|
|
411
|
-
});
|
|
412
|
-
if (res.error) {
|
|
413
|
-
throw new Error(
|
|
414
|
-
"ttsc.build: failed to spawn " +
|
|
415
|
-
execution.tsgo.binary +
|
|
416
|
-
": " +
|
|
417
|
-
res.error.message,
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
const result = {
|
|
421
|
-
status: res.status ?? 1,
|
|
422
|
-
stdout: outputText(res.stdout),
|
|
423
|
-
stderr: outputText(res.stderr),
|
|
424
|
-
};
|
|
425
|
-
const emittedFiles = parseEmittedFiles(result.stdout);
|
|
426
|
-
if (emittedFiles.length !== 0) {
|
|
427
|
-
result.stdout = stripEmittedFileLines(result.stdout);
|
|
428
|
-
}
|
|
429
|
-
if (result.status === 0 && options.emit !== false && execution.outputPlugins.length > 0) {
|
|
430
|
-
applyBuildPlugins(execution.outputPlugins, execution, emittedFiles);
|
|
431
|
-
}
|
|
432
|
-
return normalizeFailedDiagnostics({ ...result, emittedFiles });
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function buildWithNativeBinary(
|
|
436
|
-
options: BuildOptions,
|
|
437
|
-
execution: ExecutionContext,
|
|
438
|
-
): BuildResult {
|
|
439
|
-
const args = createNativeBuildArgs(execution, options);
|
|
440
|
-
const res = spawnBinary(execution.nativeBinary!, args, {
|
|
441
|
-
cwd: execution.projectRoot,
|
|
442
|
-
env: mergeEnv(options.env),
|
|
443
|
-
encoding: "utf8",
|
|
444
|
-
});
|
|
445
|
-
if (res.error) {
|
|
446
|
-
throw new Error(
|
|
447
|
-
"ttsc.build: failed to spawn " +
|
|
448
|
-
execution.nativeBinary +
|
|
449
|
-
": " +
|
|
450
|
-
res.error.message,
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
return normalizeFailedDiagnostics({
|
|
454
|
-
status: res.status ?? 1,
|
|
455
|
-
stdout: outputText(res.stdout),
|
|
456
|
-
stderr: outputText(res.stderr),
|
|
419
|
+
options.emit !== false && options.forceListEmittedFiles === true,
|
|
457
420
|
});
|
|
421
|
+
return runTsgoBuild(execution, options, args);
|
|
458
422
|
}
|
|
459
423
|
|
|
460
|
-
function
|
|
424
|
+
function buildWithNativeCompilerPlugins(
|
|
461
425
|
options: BuildOptions,
|
|
462
426
|
execution: ExecutionContext,
|
|
427
|
+
plugins: readonly LoadedNativePlugin[],
|
|
463
428
|
): BuildResult {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}
|
|
472
|
-
const materialized = materializeSourceTransformedProject(execution, "build");
|
|
473
|
-
try {
|
|
474
|
-
const outDir = mapOutDirToMaterializedProject(options.outDir, execution, materialized);
|
|
475
|
-
const result = build({
|
|
476
|
-
...options,
|
|
477
|
-
cwd: materialized.root,
|
|
478
|
-
forceListEmittedFiles: true,
|
|
479
|
-
outDir,
|
|
480
|
-
plugins: false,
|
|
481
|
-
tsconfig: materialized.tsconfig,
|
|
482
|
-
});
|
|
483
|
-
if (result.status === 0 && options.emit !== false) {
|
|
484
|
-
const copied = copyMaterializedEmittedFiles(materialized, result.emittedFiles ?? []);
|
|
485
|
-
result.emittedFiles = copied;
|
|
486
|
-
if (execution.outputPlugins.length > 0) {
|
|
487
|
-
applyBuildPlugins(execution.outputPlugins, execution, copied);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
return result;
|
|
491
|
-
} finally {
|
|
492
|
-
fs.rmSync(materialized.root, { recursive: true, force: true });
|
|
493
|
-
}
|
|
429
|
+
return runNativePluginCommand(
|
|
430
|
+
plugins[0]!,
|
|
431
|
+
createNativeBuildArgs(execution, options, plugins),
|
|
432
|
+
options,
|
|
433
|
+
execution,
|
|
434
|
+
"ttsc.build",
|
|
435
|
+
);
|
|
494
436
|
}
|
|
495
437
|
|
|
496
438
|
/**
|
|
@@ -527,6 +469,36 @@ function runTsgo(
|
|
|
527
469
|
});
|
|
528
470
|
}
|
|
529
471
|
|
|
472
|
+
function runTsgoBuild(
|
|
473
|
+
execution: ExecutionContext,
|
|
474
|
+
options: BuildOptions,
|
|
475
|
+
args: readonly string[],
|
|
476
|
+
): BuildResult {
|
|
477
|
+
const res = spawnBinary(execution.tsgo.binary, args, {
|
|
478
|
+
cwd: execution.projectRoot,
|
|
479
|
+
env: mergeEnv(options.env),
|
|
480
|
+
encoding: "utf8",
|
|
481
|
+
});
|
|
482
|
+
if (res.error) {
|
|
483
|
+
throw new Error(
|
|
484
|
+
"ttsc.build: failed to spawn " +
|
|
485
|
+
execution.tsgo.binary +
|
|
486
|
+
": " +
|
|
487
|
+
res.error.message,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
const result = {
|
|
491
|
+
status: res.status ?? 1,
|
|
492
|
+
stdout: outputText(res.stdout),
|
|
493
|
+
stderr: outputText(res.stderr),
|
|
494
|
+
};
|
|
495
|
+
const emittedFiles = parseEmittedFiles(result.stdout);
|
|
496
|
+
if (emittedFiles.length !== 0) {
|
|
497
|
+
result.stdout = stripEmittedFileLines(result.stdout);
|
|
498
|
+
}
|
|
499
|
+
return normalizeFailedDiagnostics({ ...result, emittedFiles });
|
|
500
|
+
}
|
|
501
|
+
|
|
530
502
|
function createTsgoBuildArgs(
|
|
531
503
|
execution: ExecutionContext,
|
|
532
504
|
options: BuildOptions,
|
|
@@ -550,11 +522,13 @@ function createTsgoBuildArgs(
|
|
|
550
522
|
function createNativeBuildArgs(
|
|
551
523
|
execution: ExecutionContext,
|
|
552
524
|
options: BuildOptions,
|
|
525
|
+
plugins: readonly LoadedNativePlugin[],
|
|
553
526
|
): string[] {
|
|
554
527
|
const args = [
|
|
555
528
|
options.emit === false ? "check" : "build",
|
|
556
529
|
"--tsconfig=" + execution.tsconfig,
|
|
557
|
-
"--rewrite-mode=" +
|
|
530
|
+
"--rewrite-mode=" + (options.rewriteMode ?? plugins[0]?.backend.mode ?? "none"),
|
|
531
|
+
"--plugins-json=" + serializeNativePlugins(plugins),
|
|
558
532
|
"--cwd=" + execution.projectRoot,
|
|
559
533
|
];
|
|
560
534
|
if (options.emit === true) {
|
|
@@ -573,6 +547,209 @@ function createNativeBuildArgs(
|
|
|
573
547
|
return args;
|
|
574
548
|
}
|
|
575
549
|
|
|
550
|
+
function createNativeCheckArgs(
|
|
551
|
+
execution: ExecutionContext,
|
|
552
|
+
options: BuildOptions | TransformOptions,
|
|
553
|
+
plugin: LoadedNativePlugin,
|
|
554
|
+
): string[] {
|
|
555
|
+
const args = [
|
|
556
|
+
"check",
|
|
557
|
+
"--tsconfig=" + execution.tsconfig,
|
|
558
|
+
"--rewrite-mode=" + (options.rewriteMode ?? plugin.backend.mode),
|
|
559
|
+
"--plugins-json=" + serializeNativePlugins(execution.nativePlugins),
|
|
560
|
+
"--cwd=" + execution.projectRoot,
|
|
561
|
+
];
|
|
562
|
+
if ("outDir" in options && options.outDir) {
|
|
563
|
+
args.push("--outDir=" + path.resolve(execution.cwd, options.outDir));
|
|
564
|
+
}
|
|
565
|
+
if ("quiet" in options && options.quiet === false) {
|
|
566
|
+
args.push("--verbose");
|
|
567
|
+
} else if ("quiet" in options && options.quiet === true) {
|
|
568
|
+
args.push("--quiet");
|
|
569
|
+
}
|
|
570
|
+
return args;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function createNativeOutputArgs(
|
|
574
|
+
execution: ExecutionContext,
|
|
575
|
+
options: BuildOptions | TransformOptions,
|
|
576
|
+
plugin: LoadedNativePlugin,
|
|
577
|
+
file: string,
|
|
578
|
+
): string[] {
|
|
579
|
+
const args = [
|
|
580
|
+
"output",
|
|
581
|
+
"--file=" + file,
|
|
582
|
+
"--tsconfig=" + execution.tsconfig,
|
|
583
|
+
"--rewrite-mode=" + (options.rewriteMode ?? plugin.backend.mode),
|
|
584
|
+
"--plugins-json=" + serializeNativePlugins(execution.nativePlugins),
|
|
585
|
+
"--cwd=" + execution.projectRoot,
|
|
586
|
+
];
|
|
587
|
+
if ("outDir" in options && options.outDir) {
|
|
588
|
+
args.push("--outDir=" + path.resolve(execution.cwd, options.outDir));
|
|
589
|
+
}
|
|
590
|
+
return args;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function createNativeTransformArgs(
|
|
594
|
+
execution: ExecutionContext,
|
|
595
|
+
options: TransformOptions,
|
|
596
|
+
sourceFile: string,
|
|
597
|
+
out: string,
|
|
598
|
+
plugins: readonly LoadedNativePlugin[],
|
|
599
|
+
): string[] {
|
|
600
|
+
return [
|
|
601
|
+
"transform",
|
|
602
|
+
"--file=" + sourceFile,
|
|
603
|
+
"--out=" + out,
|
|
604
|
+
"--tsconfig=" + execution.tsconfig,
|
|
605
|
+
"--rewrite-mode=" + (options.rewriteMode ?? plugins[0]?.backend.mode ?? "none"),
|
|
606
|
+
"--plugins-json=" + serializeNativePlugins(plugins),
|
|
607
|
+
];
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function serializeNativePlugins(plugins: readonly LoadedNativePlugin[]): string {
|
|
611
|
+
return JSON.stringify(
|
|
612
|
+
plugins.map((plugin) => ({
|
|
613
|
+
config: plugin.config,
|
|
614
|
+
contractVersion: plugin.backend.contractVersion,
|
|
615
|
+
mode: plugin.backend.mode,
|
|
616
|
+
name: plugin.name,
|
|
617
|
+
})),
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function runNativeCheckPlugins(
|
|
622
|
+
options: BuildOptions | TransformOptions,
|
|
623
|
+
execution: ExecutionContext,
|
|
624
|
+
): BuildResult {
|
|
625
|
+
let out: BuildResult = { status: 0, stdout: "", stderr: "" };
|
|
626
|
+
for (const plugin of checkOnlyPlugins(execution.nativePlugins)) {
|
|
627
|
+
const result = runNativePluginCommand(
|
|
628
|
+
plugin,
|
|
629
|
+
createNativeCheckArgs(execution, options, plugin),
|
|
630
|
+
options,
|
|
631
|
+
execution,
|
|
632
|
+
"ttsc.check",
|
|
633
|
+
);
|
|
634
|
+
out = appendBuildOutput(out, result);
|
|
635
|
+
if (result.status !== 0) {
|
|
636
|
+
return out;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return out;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function runNativeCompilerTransform(
|
|
643
|
+
options: TransformOptions,
|
|
644
|
+
execution: ExecutionContext,
|
|
645
|
+
sourceFile: string,
|
|
646
|
+
out: string,
|
|
647
|
+
plugins: readonly LoadedNativePlugin[],
|
|
648
|
+
): BuildResult {
|
|
649
|
+
return runNativePluginCommand(
|
|
650
|
+
plugins[0]!,
|
|
651
|
+
createNativeTransformArgs(execution, options, sourceFile, out, plugins),
|
|
652
|
+
options,
|
|
653
|
+
execution,
|
|
654
|
+
"ttsc.transform",
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function applyOutputPlugins(
|
|
659
|
+
options: BuildOptions,
|
|
660
|
+
execution: ExecutionContext,
|
|
661
|
+
emittedFiles: readonly string[],
|
|
662
|
+
plugins: readonly LoadedNativePlugin[],
|
|
663
|
+
): BuildResult {
|
|
664
|
+
let out: BuildResult = { status: 0, stdout: "", stderr: "" };
|
|
665
|
+
for (const plugin of plugins) {
|
|
666
|
+
for (const file of emittedFiles) {
|
|
667
|
+
if (!fs.existsSync(file)) {
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
const result = runNativeOutputPlugin(options, execution, plugin, file);
|
|
671
|
+
out = appendBuildOutput(out, result);
|
|
672
|
+
if (result.status !== 0) {
|
|
673
|
+
return out;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return out;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function runNativeOutputPlugin(
|
|
681
|
+
options: BuildOptions | TransformOptions,
|
|
682
|
+
execution: ExecutionContext,
|
|
683
|
+
plugin: LoadedNativePlugin,
|
|
684
|
+
file: string,
|
|
685
|
+
): BuildResult {
|
|
686
|
+
return runNativePluginCommand(
|
|
687
|
+
plugin,
|
|
688
|
+
createNativeOutputArgs(execution, options, plugin, file),
|
|
689
|
+
options,
|
|
690
|
+
execution,
|
|
691
|
+
"ttsc.output",
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function runNativePluginCommand(
|
|
696
|
+
plugin: LoadedNativePlugin,
|
|
697
|
+
args: readonly string[],
|
|
698
|
+
options: BuildOptions | TransformOptions,
|
|
699
|
+
execution: ExecutionContext,
|
|
700
|
+
label: string,
|
|
701
|
+
): BuildResult {
|
|
702
|
+
const binary = plugin.backend.binary;
|
|
703
|
+
if (!binary) {
|
|
704
|
+
return {
|
|
705
|
+
status: 2,
|
|
706
|
+
stdout: "",
|
|
707
|
+
stderr: `${label}: plugin "${plugin.name}" requires a version-matched binary\n`,
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
const res = spawnBinary(binary, args, {
|
|
711
|
+
cwd: execution.projectRoot,
|
|
712
|
+
env: mergeEnv(options.env),
|
|
713
|
+
encoding: "utf8",
|
|
714
|
+
});
|
|
715
|
+
if (res.error) {
|
|
716
|
+
throw new Error(
|
|
717
|
+
`${label}: failed to spawn ${binary}: ${res.error.message}`,
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
return normalizeFailedDiagnostics({
|
|
721
|
+
status: res.status ?? 1,
|
|
722
|
+
stdout: outputText(res.stdout),
|
|
723
|
+
stderr: outputText(res.stderr),
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function appendBuildOutput(left: BuildResult, right: BuildResult): BuildResult {
|
|
728
|
+
return normalizeFailedDiagnostics({
|
|
729
|
+
emittedFiles:
|
|
730
|
+
right.emittedFiles !== undefined ? right.emittedFiles : left.emittedFiles,
|
|
731
|
+
status: right.status !== 0 ? right.status : left.status,
|
|
732
|
+
stdout: left.stdout + right.stdout,
|
|
733
|
+
stderr: left.stderr + right.stderr,
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function assertSingleCompilerHost(plugins: readonly LoadedNativePlugin[]): void {
|
|
738
|
+
const binaries = [
|
|
739
|
+
...new Set(
|
|
740
|
+
plugins
|
|
741
|
+
.map((plugin) => plugin.backend.binary)
|
|
742
|
+
.filter((binary): binary is string => typeof binary === "string"),
|
|
743
|
+
),
|
|
744
|
+
];
|
|
745
|
+
if (binaries.length > 1) {
|
|
746
|
+
throw new Error(
|
|
747
|
+
"ttsc: multiple compiler native backends cannot share one emit pass; " +
|
|
748
|
+
"use output-capability plugins for post-emit transforms",
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
576
753
|
/** Ask the binary for its version banner. Handy for user-agent strings. */
|
|
577
754
|
export function version(options: CommonOptions = {}): string {
|
|
578
755
|
const tsgo = resolveTsgo(options);
|
|
@@ -588,9 +765,9 @@ export function version(options: CommonOptions = {}): string {
|
|
|
588
765
|
}
|
|
589
766
|
|
|
590
767
|
/**
|
|
591
|
-
* Promise-facing variant of `transform()`. The
|
|
592
|
-
*
|
|
593
|
-
*
|
|
768
|
+
* Promise-facing variant of `transform()`. The host path stays synchronous so
|
|
769
|
+
* plugin descriptors can stay dependency-free, but many adapter surfaces still
|
|
770
|
+
* prefer a Promise-returning function.
|
|
594
771
|
*/
|
|
595
772
|
export function transformAsync(options: TransformOptions): Promise<string> {
|
|
596
773
|
return Promise.resolve().then(() => transform(options));
|
|
@@ -599,11 +776,8 @@ export function transformAsync(options: TransformOptions): Promise<string> {
|
|
|
599
776
|
interface ExecutionContext {
|
|
600
777
|
compilerOptions: Record<string, unknown>;
|
|
601
778
|
cwd: string;
|
|
602
|
-
|
|
603
|
-
nativeMode: string;
|
|
604
|
-
outputPlugins: readonly TtscPlugin[];
|
|
779
|
+
nativePlugins: readonly LoadedNativePlugin[];
|
|
605
780
|
projectRoot: string;
|
|
606
|
-
sourcePlugins: readonly TtscPlugin[];
|
|
607
781
|
tsgo: ResolvedTsgo;
|
|
608
782
|
tsconfig: string;
|
|
609
783
|
}
|
|
@@ -628,58 +802,13 @@ function resolveExecutionContext(
|
|
|
628
802
|
return {
|
|
629
803
|
compilerOptions: loaded.project.compilerOptions,
|
|
630
804
|
cwd,
|
|
631
|
-
|
|
632
|
-
nativeMode: options.rewriteMode ?? loaded.nativeMode,
|
|
633
|
-
outputPlugins: loaded.plugins.filter((plugin) => plugin.transformOutput),
|
|
805
|
+
nativePlugins: loaded.nativePlugins,
|
|
634
806
|
projectRoot,
|
|
635
|
-
sourcePlugins: loaded.plugins.filter((plugin) => plugin.transformSource),
|
|
636
807
|
tsgo,
|
|
637
808
|
tsconfig,
|
|
638
809
|
};
|
|
639
810
|
}
|
|
640
811
|
|
|
641
|
-
function finalizeTransformText(
|
|
642
|
-
plugins: readonly TtscPlugin[],
|
|
643
|
-
context: Omit<Parameters<typeof applyPluginTransforms>[1], "code">,
|
|
644
|
-
text: string,
|
|
645
|
-
): string {
|
|
646
|
-
if (plugins.length === 0) {
|
|
647
|
-
return text;
|
|
648
|
-
}
|
|
649
|
-
return applyPluginTransforms(plugins, {
|
|
650
|
-
...context,
|
|
651
|
-
code: text,
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
function applyBuildPlugins(
|
|
656
|
-
plugins: readonly TtscPlugin[],
|
|
657
|
-
execution: ExecutionContext,
|
|
658
|
-
emittedFiles: readonly string[],
|
|
659
|
-
): void {
|
|
660
|
-
if (plugins.length === 0) {
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
for (const file of emittedFiles) {
|
|
664
|
-
if (!isJavaScriptOutput(file) || !fs.existsSync(file)) {
|
|
665
|
-
continue;
|
|
666
|
-
}
|
|
667
|
-
const current = fs.readFileSync(file, "utf8");
|
|
668
|
-
const next = applyPluginTransforms(plugins, {
|
|
669
|
-
code: current,
|
|
670
|
-
command: "build",
|
|
671
|
-
compilerOptions: execution.compilerOptions,
|
|
672
|
-
cwd: execution.cwd,
|
|
673
|
-
outputFile: file,
|
|
674
|
-
projectRoot: execution.projectRoot,
|
|
675
|
-
tsconfig: execution.tsconfig,
|
|
676
|
-
});
|
|
677
|
-
if (next !== current) {
|
|
678
|
-
fs.writeFileSync(file, next, "utf8");
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
812
|
function isJavaScriptOutput(file: string): boolean {
|
|
684
813
|
return /\.(?:[cm]?js)$/i.test(file);
|
|
685
814
|
}
|
|
@@ -788,490 +917,3 @@ function readOwnPackageVersion(): string {
|
|
|
788
917
|
return "0.0.0";
|
|
789
918
|
}
|
|
790
919
|
}
|
|
791
|
-
|
|
792
|
-
interface MaterializedProject {
|
|
793
|
-
mappers: Map<string, SourcePositionMapper>;
|
|
794
|
-
originalRoot: string;
|
|
795
|
-
root: string;
|
|
796
|
-
tsconfig: string;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
interface SourcePositionMapper {
|
|
800
|
-
finalCode: string;
|
|
801
|
-
originalCode: string;
|
|
802
|
-
stages: readonly TtscSourceTransformStage[];
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
function materializeSourceTransformedProject(
|
|
806
|
-
execution: ExecutionContext,
|
|
807
|
-
command: "build" | "transform",
|
|
808
|
-
onlySourceFile?: string,
|
|
809
|
-
): MaterializedProject {
|
|
810
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-source-"));
|
|
811
|
-
const materialized: MaterializedProject = {
|
|
812
|
-
mappers: new Map(),
|
|
813
|
-
originalRoot: execution.projectRoot,
|
|
814
|
-
root,
|
|
815
|
-
tsconfig: path.join(root, path.relative(execution.projectRoot, execution.tsconfig)),
|
|
816
|
-
};
|
|
817
|
-
copyProjectForSourceTransforms(execution.projectRoot, root, execution.compilerOptions);
|
|
818
|
-
const files = onlySourceFile
|
|
819
|
-
? [toMaterializedPath(onlySourceFile, materialized)]
|
|
820
|
-
: listSourceTransformFiles(root);
|
|
821
|
-
for (const tempFile of files) {
|
|
822
|
-
if (!fs.existsSync(tempFile) || !fs.statSync(tempFile).isFile()) {
|
|
823
|
-
continue;
|
|
824
|
-
}
|
|
825
|
-
const originalFile = fromMaterializedPath(tempFile, materialized);
|
|
826
|
-
const current = fs.readFileSync(tempFile, "utf8");
|
|
827
|
-
const transformed = applyPluginSourceTransformsWithMap(execution.sourcePlugins, {
|
|
828
|
-
code: current,
|
|
829
|
-
command,
|
|
830
|
-
compilerOptions: execution.compilerOptions,
|
|
831
|
-
cwd: execution.cwd,
|
|
832
|
-
projectRoot: execution.projectRoot,
|
|
833
|
-
sourceFile: originalFile,
|
|
834
|
-
tsconfig: execution.tsconfig,
|
|
835
|
-
});
|
|
836
|
-
const next = transformed.code;
|
|
837
|
-
if (next !== current) {
|
|
838
|
-
fs.writeFileSync(tempFile, next, "utf8");
|
|
839
|
-
}
|
|
840
|
-
if (transformed.stages.length > 0) {
|
|
841
|
-
materialized.mappers.set(filepathKey(tempFile), {
|
|
842
|
-
finalCode: next,
|
|
843
|
-
originalCode: current,
|
|
844
|
-
stages: transformed.stages,
|
|
845
|
-
});
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
return materialized;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
function copyProjectForSourceTransforms(
|
|
852
|
-
source: string,
|
|
853
|
-
target: string,
|
|
854
|
-
compilerOptions: Record<string, unknown>,
|
|
855
|
-
): void {
|
|
856
|
-
const excluded = new Set([".git", "node_modules"]);
|
|
857
|
-
const outDir = typeof compilerOptions.outDir === "string" ? compilerOptions.outDir : "";
|
|
858
|
-
const resolvedOutDir = outDir ? path.resolve(outDir) : "";
|
|
859
|
-
copyDirectoryFiltered(source, target, (entry) => {
|
|
860
|
-
if (excluded.has(path.basename(entry))) {
|
|
861
|
-
return false;
|
|
862
|
-
}
|
|
863
|
-
if (resolvedOutDir && isPathInside(entry, resolvedOutDir)) {
|
|
864
|
-
return false;
|
|
865
|
-
}
|
|
866
|
-
return true;
|
|
867
|
-
});
|
|
868
|
-
const nodeModules = path.join(source, "node_modules");
|
|
869
|
-
if (fs.existsSync(nodeModules)) {
|
|
870
|
-
const targetNodeModules = path.join(target, "node_modules");
|
|
871
|
-
try {
|
|
872
|
-
fs.symlinkSync(nodeModules, targetNodeModules, "junction");
|
|
873
|
-
} catch {
|
|
874
|
-
copyDirectoryFiltered(nodeModules, targetNodeModules, () => true);
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
function copyDirectoryFiltered(
|
|
880
|
-
source: string,
|
|
881
|
-
target: string,
|
|
882
|
-
shouldCopy: (entry: string) => boolean,
|
|
883
|
-
): void {
|
|
884
|
-
if (!shouldCopy(source)) {
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
const stat = fs.lstatSync(source);
|
|
888
|
-
if (stat.isSymbolicLink()) {
|
|
889
|
-
const link = fs.readlinkSync(source);
|
|
890
|
-
fs.symlinkSync(link, target);
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
if (stat.isDirectory()) {
|
|
894
|
-
fs.mkdirSync(target, { recursive: true });
|
|
895
|
-
for (const entry of fs.readdirSync(source)) {
|
|
896
|
-
copyDirectoryFiltered(path.join(source, entry), path.join(target, entry), shouldCopy);
|
|
897
|
-
}
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
if (stat.isFile()) {
|
|
901
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
902
|
-
fs.copyFileSync(source, target);
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
function listSourceTransformFiles(root: string): string[] {
|
|
907
|
-
const out: string[] = [];
|
|
908
|
-
const stack = [root];
|
|
909
|
-
while (stack.length !== 0) {
|
|
910
|
-
const current = stack.pop()!;
|
|
911
|
-
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
912
|
-
if (entry.name === "node_modules" || entry.name === ".git") {
|
|
913
|
-
continue;
|
|
914
|
-
}
|
|
915
|
-
const next = path.join(current, entry.name);
|
|
916
|
-
if (entry.isDirectory()) {
|
|
917
|
-
stack.push(next);
|
|
918
|
-
} else if (entry.isFile() && isTransformableSource(next)) {
|
|
919
|
-
out.push(next);
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
return out;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
function isTransformableSource(file: string): boolean {
|
|
927
|
-
return /\.(?:[cm]?tsx?)$/i.test(file) && !/\.d\.[cm]?ts$/i.test(file);
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
function mapOutDirToMaterializedProject(
|
|
931
|
-
outDir: string | undefined,
|
|
932
|
-
execution: ExecutionContext,
|
|
933
|
-
materialized: MaterializedProject,
|
|
934
|
-
): string | undefined {
|
|
935
|
-
if (!outDir) {
|
|
936
|
-
return undefined;
|
|
937
|
-
}
|
|
938
|
-
const resolved = path.resolve(execution.cwd, outDir);
|
|
939
|
-
if (isPathInside(resolved, execution.projectRoot)) {
|
|
940
|
-
return toMaterializedPath(resolved, materialized);
|
|
941
|
-
}
|
|
942
|
-
return path.join(materialized.root, ".ttsc-out");
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
function copyMaterializedEmittedFiles(
|
|
946
|
-
materialized: MaterializedProject,
|
|
947
|
-
emittedFiles: readonly string[],
|
|
948
|
-
): string[] {
|
|
949
|
-
const copied: string[] = [];
|
|
950
|
-
for (const emitted of emittedFiles) {
|
|
951
|
-
if (!isPathInside(emitted, materialized.root)) {
|
|
952
|
-
continue;
|
|
953
|
-
}
|
|
954
|
-
const target = fromMaterializedPath(emitted, materialized);
|
|
955
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
956
|
-
if (/\.map$/i.test(emitted)) {
|
|
957
|
-
fs.writeFileSync(
|
|
958
|
-
target,
|
|
959
|
-
patchSourceMapText(fs.readFileSync(emitted, "utf8"), materialized, target),
|
|
960
|
-
"utf8",
|
|
961
|
-
);
|
|
962
|
-
} else {
|
|
963
|
-
fs.copyFileSync(emitted, target);
|
|
964
|
-
}
|
|
965
|
-
copied.push(target);
|
|
966
|
-
}
|
|
967
|
-
return copied;
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
function toMaterializedPath(file: string, materialized: MaterializedProject): string {
|
|
971
|
-
const relative = path.relative(materialized.originalRoot, file);
|
|
972
|
-
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
973
|
-
return file;
|
|
974
|
-
}
|
|
975
|
-
return path.join(materialized.root, relative);
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
function fromMaterializedPath(file: string, materialized: MaterializedProject): string {
|
|
979
|
-
const relative = path.relative(materialized.root, file);
|
|
980
|
-
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
981
|
-
return file;
|
|
982
|
-
}
|
|
983
|
-
return path.join(materialized.originalRoot, relative);
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
function isPathInside(file: string, parent: string): boolean {
|
|
987
|
-
const relative = path.relative(parent, file);
|
|
988
|
-
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
function filepathKey(file: string): string {
|
|
992
|
-
return path.resolve(file).replace(/\\/g, "/");
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
function patchSourceMapText(
|
|
996
|
-
text: string,
|
|
997
|
-
materialized: MaterializedProject,
|
|
998
|
-
outputFile: string,
|
|
999
|
-
tempOutputFile = toMaterializedPath(outputFile, materialized),
|
|
1000
|
-
): string {
|
|
1001
|
-
let map: {
|
|
1002
|
-
sourceRoot?: string;
|
|
1003
|
-
sources?: string[];
|
|
1004
|
-
sourcesContent?: string[];
|
|
1005
|
-
[key: string]: unknown;
|
|
1006
|
-
};
|
|
1007
|
-
try {
|
|
1008
|
-
map = JSON.parse(text);
|
|
1009
|
-
} catch {
|
|
1010
|
-
return text;
|
|
1011
|
-
}
|
|
1012
|
-
if (!Array.isArray(map.sources)) {
|
|
1013
|
-
return text;
|
|
1014
|
-
}
|
|
1015
|
-
const tempMapDir = path.dirname(tempOutputFile);
|
|
1016
|
-
const originalMapDir = path.dirname(outputFile);
|
|
1017
|
-
const nextSources: string[] = [];
|
|
1018
|
-
const nextSourcesContent = Array.isArray(map.sourcesContent)
|
|
1019
|
-
? [...map.sourcesContent]
|
|
1020
|
-
: undefined;
|
|
1021
|
-
const sourceMappings = new Map<number, SourcePositionMapper>();
|
|
1022
|
-
map.sources.forEach((source, index) => {
|
|
1023
|
-
const tempSource = path.isAbsolute(source)
|
|
1024
|
-
? source
|
|
1025
|
-
: path.resolve(tempMapDir, source);
|
|
1026
|
-
if (isPathInside(tempSource, materialized.root)) {
|
|
1027
|
-
const originalSource = fromMaterializedPath(tempSource, materialized);
|
|
1028
|
-
const mapper = materialized.mappers.get(filepathKey(tempSource));
|
|
1029
|
-
if (mapper) {
|
|
1030
|
-
sourceMappings.set(index, mapper);
|
|
1031
|
-
}
|
|
1032
|
-
nextSources.push(relativeSourceMapPath(originalMapDir, originalSource));
|
|
1033
|
-
if (nextSourcesContent) {
|
|
1034
|
-
try {
|
|
1035
|
-
nextSourcesContent[index] = fs.readFileSync(originalSource, "utf8");
|
|
1036
|
-
} catch {
|
|
1037
|
-
/* keep emitted sourcesContent */
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
} else {
|
|
1041
|
-
nextSources.push(source);
|
|
1042
|
-
}
|
|
1043
|
-
});
|
|
1044
|
-
if (sourceMappings.size > 0 && typeof map.mappings === "string") {
|
|
1045
|
-
map.mappings = remapSourceMapMappings(map.mappings, sourceMappings);
|
|
1046
|
-
}
|
|
1047
|
-
map.sources = nextSources;
|
|
1048
|
-
if (nextSourcesContent) {
|
|
1049
|
-
map.sourcesContent = nextSourcesContent;
|
|
1050
|
-
}
|
|
1051
|
-
delete map.sourceRoot;
|
|
1052
|
-
return JSON.stringify(map);
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
function relativeSourceMapPath(fromDir: string, file: string): string {
|
|
1056
|
-
const relative = path.relative(fromDir, file).replace(/\\/g, "/");
|
|
1057
|
-
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
function rewriteSourceMapReference(code: string, mapFileName: string): string {
|
|
1061
|
-
const reference = `//# sourceMappingURL=${mapFileName}`;
|
|
1062
|
-
if (/\/\/# sourceMappingURL=.*(?:\r?\n)?$/m.test(code)) {
|
|
1063
|
-
return code.replace(/\/\/# sourceMappingURL=.*(?:\r?\n)?$/m, `${reference}\n`);
|
|
1064
|
-
}
|
|
1065
|
-
return code.endsWith("\n") ? `${code}${reference}\n` : `${code}\n${reference}\n`;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
function remapSourceMapMappings(
|
|
1069
|
-
mappings: string,
|
|
1070
|
-
sourceMappings: Map<number, SourcePositionMapper>,
|
|
1071
|
-
): string {
|
|
1072
|
-
const decoded = decodeMappings(mappings);
|
|
1073
|
-
for (const line of decoded) {
|
|
1074
|
-
for (const segment of line) {
|
|
1075
|
-
if (segment.length < 4) {
|
|
1076
|
-
continue;
|
|
1077
|
-
}
|
|
1078
|
-
const mapper = sourceMappings.get(segment[1]!);
|
|
1079
|
-
if (!mapper) {
|
|
1080
|
-
continue;
|
|
1081
|
-
}
|
|
1082
|
-
const mapped = mapTransformedPositionToOriginal(mapper, {
|
|
1083
|
-
column: segment[3]!,
|
|
1084
|
-
line: segment[2]!,
|
|
1085
|
-
});
|
|
1086
|
-
segment[2] = mapped.line;
|
|
1087
|
-
segment[3] = mapped.column;
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
return encodeMappings(decoded);
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
function mapTransformedPositionToOriginal(
|
|
1094
|
-
mapper: SourcePositionMapper,
|
|
1095
|
-
position: { line: number; column: number },
|
|
1096
|
-
): { line: number; column: number } {
|
|
1097
|
-
let offset = lineColumnToOffset(mapper.finalCode, position.line, position.column);
|
|
1098
|
-
for (let index = mapper.stages.length - 1; index >= 0; index -= 1) {
|
|
1099
|
-
offset = mapOffsetToPreviousStage(offset, mapper.stages[index]!);
|
|
1100
|
-
}
|
|
1101
|
-
return offsetToLineColumn(mapper.originalCode, offset);
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
function mapOffsetToPreviousStage(
|
|
1105
|
-
offset: number,
|
|
1106
|
-
stage: TtscSourceTransformStage,
|
|
1107
|
-
): number {
|
|
1108
|
-
let delta = 0;
|
|
1109
|
-
for (const edit of stage.edits) {
|
|
1110
|
-
if (offset < edit.newStart) {
|
|
1111
|
-
return clampOffset(offset - delta, stage.before.length);
|
|
1112
|
-
}
|
|
1113
|
-
if (offset < edit.newEnd) {
|
|
1114
|
-
return edit.start;
|
|
1115
|
-
}
|
|
1116
|
-
delta += edit.code.length - (edit.end - edit.start);
|
|
1117
|
-
}
|
|
1118
|
-
return clampOffset(offset - delta, stage.before.length);
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
function lineColumnToOffset(source: string, line: number, column: number): number {
|
|
1122
|
-
const starts = lineStarts(source);
|
|
1123
|
-
const start = starts[Math.min(Math.max(line, 0), starts.length - 1)] ?? 0;
|
|
1124
|
-
return clampOffset(start + Math.max(column, 0), source.length);
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
function offsetToLineColumn(
|
|
1128
|
-
source: string,
|
|
1129
|
-
offset: number,
|
|
1130
|
-
): { line: number; column: number } {
|
|
1131
|
-
const starts = lineStarts(source);
|
|
1132
|
-
const clamped = clampOffset(offset, source.length);
|
|
1133
|
-
let low = 0;
|
|
1134
|
-
let high = starts.length - 1;
|
|
1135
|
-
while (low <= high) {
|
|
1136
|
-
const mid = (low + high) >> 1;
|
|
1137
|
-
const start = starts[mid]!;
|
|
1138
|
-
const next = starts[mid + 1] ?? Number.POSITIVE_INFINITY;
|
|
1139
|
-
if (clamped < start) {
|
|
1140
|
-
high = mid - 1;
|
|
1141
|
-
} else if (clamped >= next) {
|
|
1142
|
-
low = mid + 1;
|
|
1143
|
-
} else {
|
|
1144
|
-
return { column: clamped - start, line: mid };
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
const last = starts.length - 1;
|
|
1148
|
-
return { column: clamped - (starts[last] ?? 0), line: last };
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
function lineStarts(source: string): number[] {
|
|
1152
|
-
const starts = [0];
|
|
1153
|
-
for (let index = 0; index < source.length; index += 1) {
|
|
1154
|
-
const ch = source.charCodeAt(index);
|
|
1155
|
-
if (ch === 13 /* \r */) {
|
|
1156
|
-
if (source.charCodeAt(index + 1) === 10 /* \n */) {
|
|
1157
|
-
index += 1;
|
|
1158
|
-
}
|
|
1159
|
-
starts.push(index + 1);
|
|
1160
|
-
} else if (ch === 10 /* \n */) {
|
|
1161
|
-
starts.push(index + 1);
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
return starts;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
function clampOffset(offset: number, length: number): number {
|
|
1168
|
-
return Math.min(Math.max(offset, 0), length);
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
type SourceMapSegment = number[];
|
|
1172
|
-
type DecodedMappings = SourceMapSegment[][];
|
|
1173
|
-
|
|
1174
|
-
const BASE64_CHARS =
|
|
1175
|
-
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
1176
|
-
const BASE64_VALUES = new Map(
|
|
1177
|
-
[...BASE64_CHARS].map((char, index) => [char, index] as const),
|
|
1178
|
-
);
|
|
1179
|
-
|
|
1180
|
-
function decodeMappings(mappings: string): DecodedMappings {
|
|
1181
|
-
const lines: DecodedMappings = [];
|
|
1182
|
-
let line: SourceMapSegment[] = [];
|
|
1183
|
-
let segment: SourceMapSegment = [];
|
|
1184
|
-
let index = 0;
|
|
1185
|
-
const state = [0, 0, 0, 0, 0];
|
|
1186
|
-
while (index < mappings.length) {
|
|
1187
|
-
const char = mappings[index]!;
|
|
1188
|
-
if (char === ";") {
|
|
1189
|
-
if (segment.length > 0) {
|
|
1190
|
-
line.push(segment);
|
|
1191
|
-
segment = [];
|
|
1192
|
-
}
|
|
1193
|
-
lines.push(line);
|
|
1194
|
-
line = [];
|
|
1195
|
-
state[0] = 0;
|
|
1196
|
-
index += 1;
|
|
1197
|
-
continue;
|
|
1198
|
-
}
|
|
1199
|
-
if (char === ",") {
|
|
1200
|
-
if (segment.length > 0) {
|
|
1201
|
-
line.push(segment);
|
|
1202
|
-
segment = [];
|
|
1203
|
-
}
|
|
1204
|
-
index += 1;
|
|
1205
|
-
continue;
|
|
1206
|
-
}
|
|
1207
|
-
const read = readVlq(mappings, index);
|
|
1208
|
-
index = read.next;
|
|
1209
|
-
const field = segment.length;
|
|
1210
|
-
state[field] = (state[field] ?? 0) + read.value;
|
|
1211
|
-
segment.push(state[field]!);
|
|
1212
|
-
}
|
|
1213
|
-
if (segment.length > 0) {
|
|
1214
|
-
line.push(segment);
|
|
1215
|
-
}
|
|
1216
|
-
lines.push(line);
|
|
1217
|
-
return lines;
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
function encodeMappings(lines: DecodedMappings): string {
|
|
1221
|
-
const state = [0, 0, 0, 0, 0];
|
|
1222
|
-
return lines
|
|
1223
|
-
.map((line) => {
|
|
1224
|
-
state[0] = 0;
|
|
1225
|
-
return line
|
|
1226
|
-
.map((segment) =>
|
|
1227
|
-
segment
|
|
1228
|
-
.map((value, index) => {
|
|
1229
|
-
const relative = value - (state[index] ?? 0);
|
|
1230
|
-
state[index] = value;
|
|
1231
|
-
return writeVlq(relative);
|
|
1232
|
-
})
|
|
1233
|
-
.join(""),
|
|
1234
|
-
)
|
|
1235
|
-
.join(",");
|
|
1236
|
-
})
|
|
1237
|
-
.join(";");
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
function readVlq(input: string, start: number): { next: number; value: number } {
|
|
1241
|
-
let index = start;
|
|
1242
|
-
let shift = 0;
|
|
1243
|
-
let value = 0;
|
|
1244
|
-
while (index < input.length) {
|
|
1245
|
-
const digit = BASE64_VALUES.get(input[index]!);
|
|
1246
|
-
if (digit === undefined) {
|
|
1247
|
-
throw new Error(`ttsc: invalid source map VLQ digit ${input[index]}`);
|
|
1248
|
-
}
|
|
1249
|
-
index += 1;
|
|
1250
|
-
const continuation = (digit & 32) !== 0;
|
|
1251
|
-
value += (digit & 31) << shift;
|
|
1252
|
-
shift += 5;
|
|
1253
|
-
if (!continuation) {
|
|
1254
|
-
const negative = (value & 1) === 1;
|
|
1255
|
-
const decoded = value >> 1;
|
|
1256
|
-
return { next: index, value: negative ? -decoded : decoded };
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
throw new Error("ttsc: unterminated source map VLQ segment");
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
function writeVlq(value: number): string {
|
|
1263
|
-
let vlq = Math.abs(value) << 1;
|
|
1264
|
-
if (value < 0) {
|
|
1265
|
-
vlq |= 1;
|
|
1266
|
-
}
|
|
1267
|
-
let out = "";
|
|
1268
|
-
do {
|
|
1269
|
-
let digit = vlq & 31;
|
|
1270
|
-
vlq >>>= 5;
|
|
1271
|
-
if (vlq > 0) {
|
|
1272
|
-
digit |= 32;
|
|
1273
|
-
}
|
|
1274
|
-
out += BASE64_CHARS[digit]!;
|
|
1275
|
-
} while (vlq > 0);
|
|
1276
|
-
return out;
|
|
1277
|
-
}
|