ttsc 0.7.0 → 0.7.2
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 +3 -5
- package/cmd/platform/main.go +1 -1
- package/cmd/ttsc/api_transform.go +64 -0
- package/cmd/ttsc/main.go +4 -18
- package/driver/program.go +2 -2
- package/lib/TtscCompiler.d.ts +20 -0
- package/lib/TtscCompiler.js +49 -0
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.d.ts +3 -0
- package/lib/compiler/internal/{runTransform.js → runSingleFileEmit.js} +10 -8
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -0
- package/lib/compiler/internal/transformProjectInMemory.d.ts +7 -0
- package/lib/compiler/internal/transformProjectInMemory.js +275 -0
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -0
- package/lib/launcher/internal/prepareExecution.js +89 -8
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +2 -76
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/structures/ITtscCompilerTransformation.d.ts +35 -0
- package/lib/structures/ITtscCompilerTransformation.js +3 -0
- package/lib/structures/ITtscCompilerTransformation.js.map +1 -0
- package/lib/structures/TtscPluginStage.d.ts +7 -4
- package/lib/structures/index.d.ts +1 -0
- package/lib/structures/index.js +1 -0
- package/lib/structures/index.js.map +1 -1
- package/lib/structures/internal/TtscCommonOptions.d.ts +1 -1
- package/lib/structures/internal/{TtscTransformOptions.d.ts → TtscSingleFileEmitOptions.d.ts} +4 -4
- package/lib/structures/internal/{TtscTransformOptions.js → TtscSingleFileEmitOptions.js} +1 -1
- package/lib/structures/internal/TtscSingleFileEmitOptions.js.map +1 -0
- package/package.json +8 -8
- package/src/TtscCompiler.ts +64 -0
- package/src/compiler/internal/{runTransform.ts → runSingleFileEmit.ts} +11 -7
- package/src/compiler/internal/transformProjectInMemory.ts +368 -0
- package/src/launcher/internal/prepareExecution.ts +114 -8
- package/src/launcher/internal/runTtsc.ts +6 -79
- package/src/structures/ITtscCompilerTransformation.ts +46 -0
- package/src/structures/TtscPluginStage.ts +7 -4
- package/src/structures/index.ts +1 -0
- package/src/structures/internal/TtscCommonOptions.ts +1 -1
- package/src/structures/internal/{TtscTransformOptions.ts → TtscSingleFileEmitOptions.ts} +4 -4
- package/test/compiler.test.ts +196 -8
- package/cmd/ttsc/transform.go +0 -160
- package/lib/compiler/internal/runTransform.d.ts +0 -3
- package/lib/compiler/internal/runTransform.js.map +0 -1
- package/lib/structures/internal/TtscTransformOptions.js.map +0 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ITtscCompilerDiagnostic } from "./ITtscCompilerDiagnostic";
|
|
2
|
+
/**
|
|
3
|
+
* Result of a TypeScript source-to-source transformation operation.
|
|
4
|
+
*
|
|
5
|
+
* This mirrors `embed-typescript`'s `IEmbedTypeScriptTransformation` model.
|
|
6
|
+
* Unlike {@link ITtscCompilerResult}, this contract is not an emit contract:
|
|
7
|
+
* the `typescript` map must contain TypeScript source text, not generated
|
|
8
|
+
* JavaScript, declaration files, or source maps.
|
|
9
|
+
*/
|
|
10
|
+
export type ITtscCompilerTransformation = ITtscCompilerTransformation.ISuccess | ITtscCompilerTransformation.IFailure | ITtscCompilerTransformation.IException;
|
|
11
|
+
export declare namespace ITtscCompilerTransformation {
|
|
12
|
+
/** Successful source-to-source transformation result. */
|
|
13
|
+
interface ISuccess {
|
|
14
|
+
/** Indicates that transformation completed without diagnostics. */
|
|
15
|
+
type: "success";
|
|
16
|
+
/** Transformed TypeScript source text keyed by project file path. */
|
|
17
|
+
typescript: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
/** Source-to-source transformation result that completed with diagnostics. */
|
|
20
|
+
interface IFailure {
|
|
21
|
+
/** Indicates that transformation completed with diagnostics. */
|
|
22
|
+
type: "failure";
|
|
23
|
+
/** Transformed or partially transformed TypeScript source text. */
|
|
24
|
+
typescript: Record<string, string>;
|
|
25
|
+
/** Diagnostics reported during transformation. */
|
|
26
|
+
diagnostics: ITtscCompilerDiagnostic[];
|
|
27
|
+
}
|
|
28
|
+
/** Unexpected host-level error during transformation. */
|
|
29
|
+
interface IException {
|
|
30
|
+
/** Indicates that transformation could not complete normally. */
|
|
31
|
+
type: "exception";
|
|
32
|
+
/** Raw error thrown by the ttsc host. */
|
|
33
|
+
error: unknown;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ITtscCompilerTransformation.js","sourceRoot":"","sources":["../../src/structures/ITtscCompilerTransformation.ts"],"names":[],"mappings":""}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pipeline stage where a plugin's lazily built Go sidecar participates.
|
|
3
3
|
*
|
|
4
|
-
* - `"transform"`: owns the TypeScript
|
|
5
|
-
* is the right stage for compiler-transform plugins such as
|
|
6
|
-
* source rewrites.
|
|
7
|
-
*
|
|
4
|
+
* - `"transform"`: owns the TypeScript transform/emit pass. This is the
|
|
5
|
+
* default and is the right stage for compiler-transform plugins such as
|
|
6
|
+
* typia-style source rewrites. During compilation, a transform sidecar
|
|
7
|
+
* produces the project's emitted output. During
|
|
8
|
+
* {@link TtscCompiler.transform}, the same sidecar may expose a
|
|
9
|
+
* source-to-source `transform` command that returns transformed TypeScript
|
|
10
|
+
* text.
|
|
8
11
|
* - `"check"`: runs before emit and reports diagnostics only. Use this for
|
|
9
12
|
* lint or validation plugins that should fail the compile before JavaScript
|
|
10
13
|
* or declaration output is generated.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./ITtscCompilerContext";
|
|
2
2
|
export * from "./ITtscCompilerDiagnostic";
|
|
3
3
|
export * from "./ITtscCompilerResult";
|
|
4
|
+
export * from "./ITtscCompilerTransformation";
|
|
4
5
|
export * from "./ITtscPlugin";
|
|
5
6
|
export * from "./ITtscPluginFactoryContext";
|
|
6
7
|
export * from "./ITtscProjectPluginConfig";
|
package/lib/structures/index.js
CHANGED
|
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./ITtscCompilerContext"), exports);
|
|
18
18
|
__exportStar(require("./ITtscCompilerDiagnostic"), exports);
|
|
19
19
|
__exportStar(require("./ITtscCompilerResult"), exports);
|
|
20
|
+
__exportStar(require("./ITtscCompilerTransformation"), exports);
|
|
20
21
|
__exportStar(require("./ITtscPlugin"), exports);
|
|
21
22
|
__exportStar(require("./ITtscPluginFactoryContext"), exports);
|
|
22
23
|
__exportStar(require("./ITtscProjectPluginConfig"), exports);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/structures/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,yDAAuC;AACvC,4DAA0C;AAC1C,wDAAsC;AACtC,gDAA8B;AAC9B,8DAA4C;AAC5C,6DAA2C;AAC3C,oDAAkC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/structures/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,yDAAuC;AACvC,4DAA0C;AAC1C,wDAAsC;AACtC,gEAA8C;AAC9C,gDAA8B;AAC9B,8DAA4C;AAC5C,6DAA2C;AAC3C,oDAAkC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ITtscProjectPluginConfig } from "../ITtscProjectPluginConfig";
|
|
2
|
-
/** Internal options shared by the CLI build,
|
|
2
|
+
/** Internal options shared by the CLI build, single-file emit, and runtime paths. */
|
|
3
3
|
export interface TtscCommonOptions {
|
|
4
4
|
/**
|
|
5
5
|
* Explicit TypeScript-Go executable.
|
package/lib/structures/internal/{TtscTransformOptions.d.ts → TtscSingleFileEmitOptions.d.ts}
RENAMED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { TtscCommonOptions } from "./TtscCommonOptions";
|
|
2
|
-
/** Internal options for
|
|
3
|
-
export interface
|
|
4
|
-
/** Source file to
|
|
2
|
+
/** Internal options for emitting one source file through the file-argument path. */
|
|
3
|
+
export interface TtscSingleFileEmitOptions extends TtscCommonOptions {
|
|
4
|
+
/** Source file to emit. Absolute paths and `cwd`-relative paths work. */
|
|
5
5
|
file: string;
|
|
6
6
|
/** Project config owning `file`; discovered from `file` when omitted. */
|
|
7
7
|
tsconfig?: string;
|
|
8
|
-
/** Optional output file path.
|
|
8
|
+
/** Optional output file path. */
|
|
9
9
|
out?: string;
|
|
10
10
|
/** Suppress summary banners from ttsc/native sidecars. Defaults to `true`. */
|
|
11
11
|
quiet?: boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TtscSingleFileEmitOptions.js","sourceRoot":"","sources":["../../../src/structures/internal/TtscSingleFileEmitOptions.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ttsc",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "General-purpose TypeScript-Go compiler, plugin host, and runtime.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
"rimraf": "^6.1.2"
|
|
32
32
|
},
|
|
33
33
|
"optionalDependencies": {
|
|
34
|
-
"@ttsc/linux-x64": "0.7.
|
|
35
|
-
"@ttsc/linux-arm": "0.7.
|
|
36
|
-
"@ttsc/
|
|
37
|
-
"@ttsc/
|
|
38
|
-
"@ttsc/darwin-
|
|
39
|
-
"@ttsc/win32-x64": "0.7.
|
|
40
|
-
"@ttsc/win32-arm64": "0.7.
|
|
34
|
+
"@ttsc/linux-x64": "0.7.2",
|
|
35
|
+
"@ttsc/linux-arm": "0.7.2",
|
|
36
|
+
"@ttsc/linux-arm64": "0.7.2",
|
|
37
|
+
"@ttsc/darwin-x64": "0.7.2",
|
|
38
|
+
"@ttsc/darwin-arm64": "0.7.2",
|
|
39
|
+
"@ttsc/win32-x64": "0.7.2",
|
|
40
|
+
"@ttsc/win32-arm64": "0.7.2"
|
|
41
41
|
},
|
|
42
42
|
"sideEffects": false,
|
|
43
43
|
"files": [
|
package/src/TtscCompiler.ts
CHANGED
|
@@ -4,10 +4,12 @@ import * as path from "node:path";
|
|
|
4
4
|
import { compileProjectInMemory } from "./compiler/internal/compileProjectInMemory";
|
|
5
5
|
import { resolveProjectConfig } from "./compiler/internal/project/resolveProjectConfig";
|
|
6
6
|
import { resolveBinary } from "./compiler/internal/resolveBinary";
|
|
7
|
+
import { transformProjectInMemory } from "./compiler/internal/transformProjectInMemory";
|
|
7
8
|
import { loadProjectPlugins } from "./plugin/internal/loadProjectPlugins";
|
|
8
9
|
import type { ITtscCompilerContext } from "./structures/ITtscCompilerContext";
|
|
9
10
|
import type { ITtscCompilerDiagnostic } from "./structures/ITtscCompilerDiagnostic";
|
|
10
11
|
import type { ITtscCompilerResult } from "./structures/ITtscCompilerResult";
|
|
12
|
+
import type { ITtscCompilerTransformation } from "./structures/ITtscCompilerTransformation";
|
|
11
13
|
import type { TtscBuildResult } from "./structures/internal/TtscBuildResult";
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -28,6 +30,8 @@ import type { TtscBuildResult } from "./structures/internal/TtscBuildResult";
|
|
|
28
30
|
* context.
|
|
29
31
|
* - {@link TtscCompiler.compile}: compile the configured project and return a
|
|
30
32
|
* structured result instead of terminal text.
|
|
33
|
+
* - {@link TtscCompiler.transform}: transform the configured project and
|
|
34
|
+
* return an embed-style transformation result.
|
|
31
35
|
*/
|
|
32
36
|
export class TtscCompiler {
|
|
33
37
|
private readonly context: ITtscCompilerContext;
|
|
@@ -116,6 +120,28 @@ export class TtscCompiler {
|
|
|
116
120
|
return runProject(() => compileProjectInMemory(this.compilerContext()));
|
|
117
121
|
}
|
|
118
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Transform the configured project and return TypeScript text by file path.
|
|
125
|
+
*
|
|
126
|
+
* This is the source-to-source API for plugin authors. It must not return
|
|
127
|
+
* JavaScript emit, declaration files, or source maps; those artifacts belong
|
|
128
|
+
* to {@link TtscCompiler.compile}. A transform sidecar is expected to write
|
|
129
|
+
* JSON shaped as `{ "typescript": { "src/file.ts": "..." } }` to stdout.
|
|
130
|
+
* When no transform sidecar is configured, ttsc returns the TypeScript files
|
|
131
|
+
* loaded by the TypeScript-Go Program together with normal diagnostics.
|
|
132
|
+
*
|
|
133
|
+
* The returned shape mirrors `embed-typescript`'s transformation API:
|
|
134
|
+
* `success` and `failure` carry a `typescript` map, while unexpected host
|
|
135
|
+
* errors return `exception`.
|
|
136
|
+
*
|
|
137
|
+
* @returns Transformation result containing TypeScript text or diagnostics.
|
|
138
|
+
*/
|
|
139
|
+
public transform(): ITtscCompilerTransformation {
|
|
140
|
+
return runTransformation(() =>
|
|
141
|
+
transformProjectInMemory(this.compilerContext()),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
119
145
|
private compilerContext(): ITtscCompilerContext {
|
|
120
146
|
return {
|
|
121
147
|
...this.context,
|
|
@@ -186,6 +212,11 @@ interface ProjectResult {
|
|
|
186
212
|
result: TtscBuildResult;
|
|
187
213
|
}
|
|
188
214
|
|
|
215
|
+
interface ProjectTransformation {
|
|
216
|
+
result: TtscBuildResult;
|
|
217
|
+
typescript: Record<string, string>;
|
|
218
|
+
}
|
|
219
|
+
|
|
189
220
|
function runProject(task: () => ProjectResult): ITtscCompilerResult {
|
|
190
221
|
try {
|
|
191
222
|
return toCompilerResult(task());
|
|
@@ -197,6 +228,19 @@ function runProject(task: () => ProjectResult): ITtscCompilerResult {
|
|
|
197
228
|
}
|
|
198
229
|
}
|
|
199
230
|
|
|
231
|
+
function runTransformation(
|
|
232
|
+
task: () => ProjectTransformation,
|
|
233
|
+
): ITtscCompilerTransformation {
|
|
234
|
+
try {
|
|
235
|
+
return toCompilerTransformation(task());
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return {
|
|
238
|
+
error: normalizeError(error),
|
|
239
|
+
type: "exception",
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
200
244
|
function toCompilerResult(project: ProjectResult): ITtscCompilerResult {
|
|
201
245
|
const { output, result } = project;
|
|
202
246
|
if (result.status === 0 && result.diagnostics.length === 0) {
|
|
@@ -229,6 +273,26 @@ function createProcessDiagnostic(
|
|
|
229
273
|
};
|
|
230
274
|
}
|
|
231
275
|
|
|
276
|
+
function toCompilerTransformation(
|
|
277
|
+
project: ProjectTransformation,
|
|
278
|
+
): ITtscCompilerTransformation {
|
|
279
|
+
const { result, typescript } = project;
|
|
280
|
+
if (result.status === 0 && result.diagnostics.length === 0) {
|
|
281
|
+
return {
|
|
282
|
+
type: "success",
|
|
283
|
+
typescript,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
diagnostics:
|
|
288
|
+
result.diagnostics.length === 0
|
|
289
|
+
? [createProcessDiagnostic(result)]
|
|
290
|
+
: result.diagnostics,
|
|
291
|
+
type: "failure",
|
|
292
|
+
typescript,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
232
296
|
function normalizeError(error: unknown): unknown {
|
|
233
297
|
if (!(error instanceof Error)) {
|
|
234
298
|
return error;
|
|
@@ -2,16 +2,18 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { TtscSingleFileEmitOptions } from "../../structures/internal/TtscSingleFileEmitOptions";
|
|
6
6
|
import { resolveProjectConfig } from "./project/resolveProjectConfig";
|
|
7
7
|
import { resolveEmittedJavaScript } from "./resolveEmittedJavaScript";
|
|
8
8
|
import { runBuild } from "./runBuild";
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
export function
|
|
10
|
+
/** Emit one source file by building its project into a temporary directory. */
|
|
11
|
+
export function runSingleFileEmit(options: TtscSingleFileEmitOptions): string {
|
|
12
12
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
13
13
|
const sourceFile = realpathIfExists(
|
|
14
|
-
path.isAbsolute(options.file)
|
|
14
|
+
path.isAbsolute(options.file)
|
|
15
|
+
? options.file
|
|
16
|
+
: path.resolve(cwd, options.file),
|
|
15
17
|
);
|
|
16
18
|
const tsconfig = resolveProjectConfig({
|
|
17
19
|
cwd,
|
|
@@ -19,7 +21,7 @@ export function runTransform(options: TtscTransformOptions): string {
|
|
|
19
21
|
tsconfig: options.tsconfig,
|
|
20
22
|
});
|
|
21
23
|
const projectRoot = path.dirname(tsconfig);
|
|
22
|
-
const outDir = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-
|
|
24
|
+
const outDir = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-single-file-"));
|
|
23
25
|
try {
|
|
24
26
|
const result = runBuild({
|
|
25
27
|
...options,
|
|
@@ -31,7 +33,7 @@ export function runTransform(options: TtscTransformOptions): string {
|
|
|
31
33
|
});
|
|
32
34
|
if (result.status !== 0) {
|
|
33
35
|
throw new Error(
|
|
34
|
-
"ttsc
|
|
36
|
+
"ttsc single-file emit exited " +
|
|
35
37
|
result.status +
|
|
36
38
|
"\n" +
|
|
37
39
|
(result.stderr || result.stdout),
|
|
@@ -44,7 +46,9 @@ export function runTransform(options: TtscTransformOptions): string {
|
|
|
44
46
|
sourceFile,
|
|
45
47
|
});
|
|
46
48
|
if (emitted === null) {
|
|
47
|
-
throw new Error(
|
|
49
|
+
throw new Error(
|
|
50
|
+
`ttsc single-file emit: no output produced for ${sourceFile}`,
|
|
51
|
+
);
|
|
48
52
|
}
|
|
49
53
|
const transformed = fs.readFileSync(emitted, "utf8");
|
|
50
54
|
if (options.out) {
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { loadProjectPlugins } from "../../plugin/internal/loadProjectPlugins";
|
|
6
|
+
import type { ITtscCompilerContext } from "../../structures/ITtscCompilerContext";
|
|
7
|
+
import type { ITtscCompilerDiagnostic } from "../../structures/ITtscCompilerDiagnostic";
|
|
8
|
+
import type { ITtscProjectPluginConfig } from "../../structures/ITtscProjectPluginConfig";
|
|
9
|
+
import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
|
|
10
|
+
import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscParsedProjectConfig";
|
|
11
|
+
import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult";
|
|
12
|
+
import { buildNativeCompiler } from "./buildNativeCompiler";
|
|
13
|
+
import { readProjectConfig } from "./project/readProjectConfig";
|
|
14
|
+
import { resolveBinary } from "./resolveBinary";
|
|
15
|
+
import { resolveTsgo } from "./resolveTsgo";
|
|
16
|
+
|
|
17
|
+
/** Transform a project and capture TypeScript source output in memory. */
|
|
18
|
+
export function transformProjectInMemory(options: ITtscCompilerContext): {
|
|
19
|
+
result: TtscBuildResult;
|
|
20
|
+
typescript: Record<string, string>;
|
|
21
|
+
} {
|
|
22
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
23
|
+
const project = readProjectConfig({
|
|
24
|
+
cwd,
|
|
25
|
+
tsconfig: options.tsconfig,
|
|
26
|
+
});
|
|
27
|
+
if (configuredPlugins(options, project).length !== 0) {
|
|
28
|
+
return transformProjectWithPlugins(options, cwd, project);
|
|
29
|
+
}
|
|
30
|
+
return transformProjectWithNativeHost(options, project);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function configuredPlugins(
|
|
34
|
+
options: ITtscCompilerContext,
|
|
35
|
+
project: ITtscParsedProjectConfig,
|
|
36
|
+
): ITtscProjectPluginConfig[] {
|
|
37
|
+
if (options.plugins === false) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
const entries = options.plugins ?? project.compilerOptions.plugins;
|
|
41
|
+
return entries.filter((entry) => entry.enabled !== false);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function transformProjectWithNativeHost(
|
|
45
|
+
options: ITtscCompilerContext,
|
|
46
|
+
project: ITtscParsedProjectConfig,
|
|
47
|
+
): {
|
|
48
|
+
result: TtscBuildResult;
|
|
49
|
+
typescript: Record<string, string>;
|
|
50
|
+
} {
|
|
51
|
+
const binary = buildNativeCompiler({
|
|
52
|
+
cacheBaseDir: project.root,
|
|
53
|
+
cacheDir: options.cacheDir,
|
|
54
|
+
packageRoot: packageRootDir(),
|
|
55
|
+
});
|
|
56
|
+
const res = spawnNative(
|
|
57
|
+
binary,
|
|
58
|
+
["api-transform", "--cwd", project.root, "--tsconfig", project.path],
|
|
59
|
+
{
|
|
60
|
+
cwd: project.root,
|
|
61
|
+
env: { ...process.env, ...options.env },
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
if (res.error) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`ttsc: failed to spawn native compiler host ${binary}: ${res.error.message}`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const output = parseNativeTransformOutput(
|
|
71
|
+
outputText(res.stdout),
|
|
72
|
+
outputText(res.stderr),
|
|
73
|
+
);
|
|
74
|
+
return {
|
|
75
|
+
result: {
|
|
76
|
+
diagnostics: output.diagnostics,
|
|
77
|
+
status: res.status ?? 1,
|
|
78
|
+
stdout: "",
|
|
79
|
+
stderr: outputText(res.stderr),
|
|
80
|
+
},
|
|
81
|
+
typescript: output.typescript,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function transformProjectWithPlugins(
|
|
86
|
+
options: ITtscCompilerContext,
|
|
87
|
+
cwd: string,
|
|
88
|
+
project: ITtscParsedProjectConfig,
|
|
89
|
+
): {
|
|
90
|
+
result: TtscBuildResult;
|
|
91
|
+
typescript: Record<string, string>;
|
|
92
|
+
} {
|
|
93
|
+
const loaded = loadProjectPlugins({
|
|
94
|
+
binary: resolveBinary(options) ?? "",
|
|
95
|
+
cacheDir: options.cacheDir ?? options.env?.TTSC_CACHE_DIR,
|
|
96
|
+
cwd,
|
|
97
|
+
entries: options.plugins,
|
|
98
|
+
tsconfig: project.path,
|
|
99
|
+
});
|
|
100
|
+
const checks = loaded.nativePlugins.filter(
|
|
101
|
+
(plugin) => plugin.stage === "check",
|
|
102
|
+
);
|
|
103
|
+
const transformers = loaded.nativePlugins.filter(
|
|
104
|
+
(plugin) => plugin.stage === "transform",
|
|
105
|
+
);
|
|
106
|
+
const checked = runNativeChecks(
|
|
107
|
+
options,
|
|
108
|
+
project,
|
|
109
|
+
loaded.nativePlugins,
|
|
110
|
+
checks,
|
|
111
|
+
);
|
|
112
|
+
if (checked.status !== 0) {
|
|
113
|
+
return {
|
|
114
|
+
result: checked,
|
|
115
|
+
typescript: {},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (transformers.length === 0) {
|
|
119
|
+
return transformProjectWithNativeHost(options, project);
|
|
120
|
+
}
|
|
121
|
+
assertSingleTransformHost(transformers);
|
|
122
|
+
|
|
123
|
+
const plugin = transformers[0]!;
|
|
124
|
+
const res = spawnNative(
|
|
125
|
+
plugin.binary,
|
|
126
|
+
createNativeTransformArgs(project, transformers),
|
|
127
|
+
{
|
|
128
|
+
cwd: project.root,
|
|
129
|
+
env: nativePluginEnv(options, project.root),
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
if (res.error) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`ttsc.transform: failed to spawn ${plugin.binary}: ${res.error.message}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const output = parseNativeTransformOutput(
|
|
138
|
+
outputText(res.stdout),
|
|
139
|
+
outputText(res.stderr),
|
|
140
|
+
);
|
|
141
|
+
return {
|
|
142
|
+
result: {
|
|
143
|
+
diagnostics: output.diagnostics,
|
|
144
|
+
status: res.status ?? 1,
|
|
145
|
+
stdout: "",
|
|
146
|
+
stderr: outputText(res.stderr),
|
|
147
|
+
},
|
|
148
|
+
typescript: output.typescript,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function runNativeChecks(
|
|
153
|
+
options: ITtscCompilerContext,
|
|
154
|
+
project: ITtscParsedProjectConfig,
|
|
155
|
+
nativePlugins: readonly ITtscLoadedNativePlugin[],
|
|
156
|
+
checks: readonly ITtscLoadedNativePlugin[],
|
|
157
|
+
): TtscBuildResult {
|
|
158
|
+
let result: TtscBuildResult = {
|
|
159
|
+
diagnostics: [],
|
|
160
|
+
status: 0,
|
|
161
|
+
stdout: "",
|
|
162
|
+
stderr: "",
|
|
163
|
+
};
|
|
164
|
+
for (const plugin of checks) {
|
|
165
|
+
const res = spawnNative(
|
|
166
|
+
plugin.binary,
|
|
167
|
+
createNativeCheckArgs(project, nativePlugins),
|
|
168
|
+
{
|
|
169
|
+
cwd: project.root,
|
|
170
|
+
env: nativePluginEnv(options, project.root),
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
if (res.error) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`ttsc.transform.check: failed to spawn ${plugin.binary}: ${res.error.message}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
result = appendBuildResult(result, {
|
|
179
|
+
diagnostics: [],
|
|
180
|
+
status: res.status ?? 1,
|
|
181
|
+
stdout: outputText(res.stdout),
|
|
182
|
+
stderr: outputText(res.stderr),
|
|
183
|
+
});
|
|
184
|
+
if (result.status !== 0) {
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function createNativeTransformArgs(
|
|
192
|
+
project: ITtscParsedProjectConfig,
|
|
193
|
+
plugins: readonly ITtscLoadedNativePlugin[],
|
|
194
|
+
): string[] {
|
|
195
|
+
return [
|
|
196
|
+
"transform",
|
|
197
|
+
"--tsconfig=" + project.path,
|
|
198
|
+
"--plugins-json=" + serializeNativePlugins(plugins),
|
|
199
|
+
"--cwd=" + project.root,
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function createNativeCheckArgs(
|
|
204
|
+
project: ITtscParsedProjectConfig,
|
|
205
|
+
plugins: readonly ITtscLoadedNativePlugin[],
|
|
206
|
+
): string[] {
|
|
207
|
+
return [
|
|
208
|
+
"check",
|
|
209
|
+
"--tsconfig=" + project.path,
|
|
210
|
+
"--plugins-json=" + serializeNativePlugins(plugins),
|
|
211
|
+
"--cwd=" + project.root,
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function serializeNativePlugins(
|
|
216
|
+
plugins: readonly ITtscLoadedNativePlugin[],
|
|
217
|
+
): string {
|
|
218
|
+
return JSON.stringify(
|
|
219
|
+
plugins.map((plugin) => ({
|
|
220
|
+
config: plugin.config,
|
|
221
|
+
name: plugin.name,
|
|
222
|
+
stage: plugin.stage,
|
|
223
|
+
})),
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function assertSingleTransformHost(
|
|
228
|
+
plugins: readonly ITtscLoadedNativePlugin[],
|
|
229
|
+
): void {
|
|
230
|
+
const binaries = [...new Set(plugins.map((plugin) => plugin.binary))];
|
|
231
|
+
if (binaries.length > 1) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
"ttsc: multiple transform native backends cannot share one source-to-source pass",
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function appendBuildResult(
|
|
239
|
+
left: TtscBuildResult,
|
|
240
|
+
right: TtscBuildResult,
|
|
241
|
+
): TtscBuildResult {
|
|
242
|
+
return {
|
|
243
|
+
diagnostics: [...left.diagnostics, ...right.diagnostics],
|
|
244
|
+
status: right.status !== 0 ? right.status : left.status,
|
|
245
|
+
stdout: left.stdout + right.stdout,
|
|
246
|
+
stderr: left.stderr + right.stderr,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function nativePluginEnv(
|
|
251
|
+
options: ITtscCompilerContext,
|
|
252
|
+
projectRoot: string,
|
|
253
|
+
): NodeJS.ProcessEnv {
|
|
254
|
+
const tsgo = resolveTsgo({ ...options, cwd: projectRoot });
|
|
255
|
+
return {
|
|
256
|
+
...process.env,
|
|
257
|
+
TTSC_NODE_BINARY: process.env.TTSC_NODE_BINARY ?? process.execPath,
|
|
258
|
+
TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? tsgo.binary,
|
|
259
|
+
TTSC_TTSX_BINARY:
|
|
260
|
+
process.env.TTSC_TTSX_BINARY ??
|
|
261
|
+
path.join(__dirname, "..", "..", "launcher", "ttsx.js"),
|
|
262
|
+
...options.env,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function spawnNative(
|
|
267
|
+
binary: string,
|
|
268
|
+
args: readonly string[],
|
|
269
|
+
options: {
|
|
270
|
+
cwd?: string;
|
|
271
|
+
env?: NodeJS.ProcessEnv;
|
|
272
|
+
},
|
|
273
|
+
) {
|
|
274
|
+
const viaNode = /\.(?:[cm]?js|ts)$/i.test(binary);
|
|
275
|
+
if (!viaNode) {
|
|
276
|
+
ensureExecutable(binary);
|
|
277
|
+
}
|
|
278
|
+
return spawnSync(
|
|
279
|
+
viaNode ? process.execPath : binary,
|
|
280
|
+
viaNode ? [binary, ...args] : [...args],
|
|
281
|
+
{
|
|
282
|
+
cwd: options.cwd,
|
|
283
|
+
encoding: "utf8",
|
|
284
|
+
env: options.env,
|
|
285
|
+
maxBuffer: 1024 * 1024 * 256,
|
|
286
|
+
windowsHide: true,
|
|
287
|
+
},
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function ensureExecutable(binary: string): void {
|
|
292
|
+
if (process.platform === "win32") {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
const mode = fs.statSync(binary).mode & 0o777;
|
|
297
|
+
if ((mode & 0o111) !== 0) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
fs.chmodSync(binary, mode | 0o755);
|
|
301
|
+
} catch {
|
|
302
|
+
/* keep the original spawn error path */
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function outputText(value: string | Buffer | null | undefined): string {
|
|
307
|
+
if (value == null) {
|
|
308
|
+
return "";
|
|
309
|
+
}
|
|
310
|
+
return typeof value === "string" ? value : value.toString("utf8");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function parseNativeTransformOutput(
|
|
314
|
+
stdout: string,
|
|
315
|
+
stderr: string,
|
|
316
|
+
): {
|
|
317
|
+
diagnostics: ITtscCompilerDiagnostic[];
|
|
318
|
+
typescript: Record<string, string>;
|
|
319
|
+
} {
|
|
320
|
+
try {
|
|
321
|
+
const parsed = JSON.parse(stdout) as {
|
|
322
|
+
diagnostics?: ITtscCompilerDiagnostic[];
|
|
323
|
+
typescript?: Record<string, string>;
|
|
324
|
+
};
|
|
325
|
+
if (!isTextRecord(parsed.typescript)) {
|
|
326
|
+
throw new Error(
|
|
327
|
+
"ttsc: native transform host did not return a TypeScript source map",
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
diagnostics: Array.isArray(parsed.diagnostics) ? parsed.diagnostics : [],
|
|
332
|
+
typescript: parsed.typescript,
|
|
333
|
+
};
|
|
334
|
+
} catch (error) {
|
|
335
|
+
if (error instanceof Error && !(error instanceof SyntaxError)) {
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
338
|
+
throw new Error(
|
|
339
|
+
(stderr || stdout).trim() ||
|
|
340
|
+
"ttsc: native transform host returned no output",
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function isTextRecord(value: unknown): value is Record<string, string> {
|
|
346
|
+
return (
|
|
347
|
+
typeof value === "object" &&
|
|
348
|
+
value !== null &&
|
|
349
|
+
Object.values(value).every((entry) => typeof entry === "string")
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function packageRootDir(): string {
|
|
354
|
+
let current = path.resolve(__dirname);
|
|
355
|
+
while (true) {
|
|
356
|
+
if (
|
|
357
|
+
fs.existsSync(path.join(current, "package.json")) &&
|
|
358
|
+
fs.existsSync(path.join(current, "go.mod"))
|
|
359
|
+
) {
|
|
360
|
+
return fs.realpathSync.native?.(current) ?? fs.realpathSync(current);
|
|
361
|
+
}
|
|
362
|
+
const parent = path.dirname(current);
|
|
363
|
+
if (parent === current) {
|
|
364
|
+
throw new Error("ttsc: package root not found for native compiler build");
|
|
365
|
+
}
|
|
366
|
+
current = parent;
|
|
367
|
+
}
|
|
368
|
+
}
|