ttsc 0.12.3 → 0.13.0
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/cmd/platform/main.go +9 -0
- package/cmd/ttsc/api_compile.go +122 -101
- package/cmd/ttsc/api_compile_branches_test.go +55 -57
- package/cmd/ttsc/api_transform.go +67 -49
- package/cmd/ttsc/api_transform_branches_test.go +30 -32
- package/cmd/ttsc/build.go +141 -108
- package/cmd/ttsc/build_branches_test.go +95 -97
- package/cmd/ttsc/filter.go +70 -0
- package/cmd/ttsc/filter_strips_unknown_forwarded_flag_test.go +61 -0
- package/cmd/ttsc/flags_gen.go +30 -0
- package/cmd/ttsc/helpers_test.go +39 -28
- package/cmd/ttsc/main.go +111 -104
- package/cmd/ttsc/main_alias_branches_test.go +21 -23
- package/cmd/ttscserver/main.go +96 -90
- package/cmd/ttscserver/run_lsp_prefers_tsgo_flag_test.go +22 -22
- package/cmd/ttscserver/run_lsp_reports_server_error_test.go +22 -22
- package/cmd/ttscserver/run_lsp_uses_tsgo_environment_test.go +23 -23
- package/cmd/utility-host/main.go +30 -23
- package/driver/lsp.go +53 -7
- package/driver/plugins.go +88 -72
- package/driver/program.go +464 -340
- package/driver/rewrite.go +477 -380
- package/internal/lspserver/lsp_envelope.go +64 -64
- package/internal/lspserver/lsp_frame.go +60 -60
- package/internal/lspserver/lsp_plugin.go +57 -57
- package/internal/lspserver/lsp_proxy.go +265 -251
- package/internal/lspserver/lsp_server.go +155 -144
- package/lib/TtscCompiler.d.ts +7 -0
- package/lib/TtscCompiler.js +7 -0
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/compiler/internal/buildNativeCompiler.d.ts +11 -1
- package/lib/compiler/internal/buildNativeCompiler.js +23 -3
- package/lib/compiler/internal/buildNativeCompiler.js.map +1 -1
- package/lib/compiler/internal/compileProjectInMemory.d.ts +9 -0
- package/lib/compiler/internal/compileProjectInMemory.js +36 -21
- package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/paths.d.ts +27 -0
- package/lib/compiler/internal/paths.js +70 -0
- package/lib/compiler/internal/paths.js.map +1 -0
- package/lib/compiler/internal/project/readProjectConfig.d.ts +9 -1
- package/lib/compiler/internal/project/readProjectConfig.js +80 -1
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/project/resolveProjectConfig.d.ts +13 -1
- package/lib/compiler/internal/project/resolveProjectConfig.js +42 -1
- package/lib/compiler/internal/project/resolveProjectConfig.js.map +1 -1
- package/lib/compiler/internal/resolveBinary.d.ts +9 -1
- package/lib/compiler/internal/resolveBinary.js +17 -1
- package/lib/compiler/internal/resolveBinary.js.map +1 -1
- package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +16 -1
- package/lib/compiler/internal/resolveEmittedJavaScript.js +42 -10
- package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
- package/lib/compiler/internal/resolveTsgo.d.ts +21 -1
- package/lib/compiler/internal/resolveTsgo.js +23 -1
- package/lib/compiler/internal/resolveTsgo.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +24 -4
- package/lib/compiler/internal/runBuild.js +355 -93
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.d.ts +10 -1
- package/lib/compiler/internal/runSingleFileEmit.js +14 -1
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +5 -0
- package/lib/compiler/internal/sharedHostHelpers.js +6 -2
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/spawnNative.d.ts +21 -0
- package/lib/compiler/internal/spawnNative.js +59 -0
- package/lib/compiler/internal/spawnNative.js.map +1 -0
- package/lib/compiler/internal/transformProjectInMemory.d.ts +15 -1
- package/lib/compiler/internal/transformProjectInMemory.js +57 -61
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/parser.d.ts +63 -0
- package/lib/flags/parser.js +254 -0
- package/lib/flags/parser.js.map +1 -0
- package/lib/flags/schema.d.ts +147 -0
- package/lib/flags/schema.js +405 -0
- package/lib/flags/schema.js.map +1 -0
- package/lib/launcher/internal/getCompilerVersionText.js +7 -0
- package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
- package/lib/launcher/internal/prepareExecution.js +35 -0
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/resolveCacheDir.d.ts +9 -0
- package/lib/launcher/internal/resolveCacheDir.js +22 -0
- package/lib/launcher/internal/resolveCacheDir.js.map +1 -0
- package/lib/launcher/internal/resolveTtscserverBinary.js +2 -0
- package/lib/launcher/internal/resolveTtscserverBinary.js.map +1 -1
- package/lib/launcher/internal/runTtsc.d.ts +10 -0
- package/lib/launcher/internal/runTtsc.js +90 -153
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +10 -35
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.d.ts +8 -0
- package/lib/launcher/internal/runTtsx.js +137 -79
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/plugin/internal/buildSourcePlugin.d.ts +38 -0
- package/lib/plugin/internal/buildSourcePlugin.js +40 -17
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +26 -0
- package/lib/plugin/internal/loadProjectPlugins.js +90 -41
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscCompilerTransformation.d.ts +15 -2
- package/lib/structures/ITtscPlugin.d.ts +24 -0
- package/lib/structures/ITtscPluginCapabilities.d.ts +29 -0
- package/lib/structures/ITtscPluginCapabilities.js +3 -0
- package/lib/structures/ITtscPluginCapabilities.js.map +1 -0
- 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/ITtscLoadedNativePlugin.d.ts +5 -0
- package/lib/structures/internal/TtscCommonOptions.d.ts +19 -0
- package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +0 -2
- package/package.json +9 -9
- package/shim/ast/shim.go +24 -0
- package/shim/checker/shim.go +35 -0
- package/shim/core/shim.go +17 -0
- package/shim/diagnosticwriter/shim.go +4 -0
- package/shim/lsp/shim.go +3 -3
- package/shim/printer/shim.go +20 -0
- package/src/TtscCompiler.ts +7 -0
- package/src/compiler/internal/buildNativeCompiler.ts +23 -3
- package/src/compiler/internal/compileProjectInMemory.ts +34 -25
- package/src/compiler/internal/paths.ts +68 -0
- package/src/compiler/internal/project/readProjectConfig.ts +86 -1
- package/src/compiler/internal/project/resolveProjectConfig.ts +44 -1
- package/src/compiler/internal/resolveBinary.ts +17 -1
- package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -12
- package/src/compiler/internal/resolveTsgo.ts +29 -1
- package/src/compiler/internal/runBuild.ts +398 -112
- package/src/compiler/internal/runSingleFileEmit.ts +14 -1
- package/src/compiler/internal/sharedHostHelpers.ts +7 -7
- package/src/compiler/internal/spawnNative.ts +64 -0
- package/src/compiler/internal/transformProjectInMemory.ts +47 -67
- package/src/flags/parser.ts +367 -0
- package/src/flags/schema.ts +570 -0
- package/src/launcher/internal/getCompilerVersionText.ts +7 -0
- package/src/launcher/internal/prepareExecution.ts +35 -0
- package/src/launcher/internal/resolveCacheDir.ts +19 -0
- package/src/launcher/internal/resolveTtscserverBinary.ts +2 -0
- package/src/launcher/internal/runTtsc.ts +97 -143
- package/src/launcher/internal/runTtscserver.ts +9 -1
- package/src/launcher/internal/runTtsx.ts +149 -82
- package/src/plugin/internal/buildSourcePlugin.ts +40 -14
- package/src/plugin/internal/loadProjectPlugins.ts +116 -45
- package/src/structures/ITtscCompilerTransformation.ts +15 -2
- package/src/structures/ITtscPlugin.ts +26 -1
- package/src/structures/ITtscPluginCapabilities.ts +29 -0
- package/src/structures/index.ts +1 -0
- package/src/structures/internal/ITtscLoadedNativePlugin.ts +5 -0
- package/src/structures/internal/TtscCommonOptions.ts +19 -0
- package/src/structures/internal/TtscSingleFileEmitOptions.ts +0 -2
- package/tools/gen_shims/main.go +43 -8
- package/utility/flags_gen.go +30 -0
- package/utility/host.go +304 -245
|
@@ -2,9 +2,24 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
getBoolean,
|
|
7
|
+
getNumber,
|
|
8
|
+
getString,
|
|
9
|
+
parseFlags,
|
|
10
|
+
} from "../../flags/parser";
|
|
5
11
|
import { getCompilerVersionText } from "./getCompilerVersionText";
|
|
6
12
|
import { prepareExecution } from "./prepareExecution";
|
|
7
|
-
|
|
13
|
+
import { resolveCacheDir } from "./resolveCacheDir";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* CLI entry point for `ttsx`. Type-checks the owning project via tsgo, emits
|
|
17
|
+
* JavaScript to a PID-isolated temp directory, rewrites ESM specifiers when
|
|
18
|
+
* needed, and executes the compiled entry with the current Node.js runtime.
|
|
19
|
+
*
|
|
20
|
+
* @param argv - Command-line arguments (defaults to `process.argv.slice(2)`).
|
|
21
|
+
* @returns The child-process exit code, or `2` on a ttsx-level error.
|
|
22
|
+
*/
|
|
8
23
|
export function runTtsx(
|
|
9
24
|
argv: readonly string[] = process.argv.slice(2),
|
|
10
25
|
): number {
|
|
@@ -39,8 +54,20 @@ function run(argv: readonly string[]): number {
|
|
|
39
54
|
const prepared = prepareExecution(entry, {
|
|
40
55
|
binary: parsed.binary,
|
|
41
56
|
cacheDir: resolveCacheDir(cwd, parsed.cacheDir),
|
|
57
|
+
checkers: parsed.checkers,
|
|
42
58
|
cwd,
|
|
59
|
+
passthrough: parsed.tsgoFlags,
|
|
60
|
+
// `--no-plugins` builds the entry's owning project with plugin
|
|
61
|
+
// discovery and loading disabled. ttsc's own config loaders use it
|
|
62
|
+
// when they evaluate a `*.config.ts` through ttsx: that build only
|
|
63
|
+
// needs to type-check and run the config file, so loading the host
|
|
64
|
+
// project's transform/check plugins (`@nestia/core`, `typia`, …)
|
|
65
|
+
// would be both wasteful and wrong — those plugins impose project
|
|
66
|
+
// requirements (e.g. `strict` mode) the ephemeral config-loader
|
|
67
|
+
// tsconfig deliberately does not satisfy.
|
|
68
|
+
plugins: parsed.noPlugins ? false : undefined,
|
|
43
69
|
project: parsed.project,
|
|
70
|
+
singleThreaded: parsed.singleThreaded,
|
|
44
71
|
});
|
|
45
72
|
return runPreparedEntry(parsed, prepared, cwd);
|
|
46
73
|
}
|
|
@@ -53,82 +80,108 @@ function formatError(error: unknown): string {
|
|
|
53
80
|
}
|
|
54
81
|
|
|
55
82
|
function parseCLI(argv: readonly string[]) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
case "--cwd":
|
|
87
|
-
cwd = takeValue(current, head);
|
|
88
|
-
break;
|
|
89
|
-
case "--cache-dir":
|
|
90
|
-
cacheDir = takeValue(current, head);
|
|
91
|
-
break;
|
|
92
|
-
case "-r":
|
|
93
|
-
case "--require":
|
|
94
|
-
preload.push(takeValue(current, head));
|
|
95
|
-
break;
|
|
96
|
-
case "--binary":
|
|
97
|
-
binary = takeValue(current, head);
|
|
98
|
-
break;
|
|
99
|
-
default:
|
|
100
|
-
if (current.startsWith("--project=")) {
|
|
101
|
-
project = current.slice("--project=".length);
|
|
102
|
-
} else if (current.startsWith("--cwd=")) {
|
|
103
|
-
cwd = current.slice("--cwd=".length);
|
|
104
|
-
} else if (current.startsWith("--cache-dir=")) {
|
|
105
|
-
cacheDir = current.slice("--cache-dir=".length);
|
|
106
|
-
} else if (current.startsWith("--binary=")) {
|
|
107
|
-
binary = current.slice("--binary=".length);
|
|
108
|
-
} else if (current.startsWith("-")) {
|
|
109
|
-
throw new Error(`ttsx: unknown option ${current}`);
|
|
110
|
-
} else {
|
|
111
|
-
entry = current;
|
|
112
|
-
}
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
83
|
+
// ttsx accepts ttsc-style flags plus its own `--no-plugins` / `--require`.
|
|
84
|
+
// The shared schema engine recognises both; the engine returns positional
|
|
85
|
+
// tokens (entry file + flag values that aren't `.ts`) and a passthrough
|
|
86
|
+
// list mirroring the pre-schema behaviour.
|
|
87
|
+
//
|
|
88
|
+
// Order pin: ttsx accepts `-P` as an alias for `--project`. The schema
|
|
89
|
+
// declares `-p` for ttsc; ttsx's lowercase shape would collide on `-p`
|
|
90
|
+
// → `--tsconfig`, so the legacy `-P` (uppercase) is treated as `--project`
|
|
91
|
+
// via a manual rewrite before the engine sees argv. We preserve the
|
|
92
|
+
// historical behaviour and emit a structural error otherwise.
|
|
93
|
+
const rewritten = argv.map((token) =>
|
|
94
|
+
token === "-P"
|
|
95
|
+
? "--project"
|
|
96
|
+
: token.startsWith("-P=")
|
|
97
|
+
? `--project=${token.slice("-P=".length)}`
|
|
98
|
+
: token,
|
|
99
|
+
);
|
|
100
|
+
// Terminal flags (--help / --version) short-circuit before parsing so
|
|
101
|
+
// ttsx prints help text even when the entry file is missing.
|
|
102
|
+
for (const token of rewritten) {
|
|
103
|
+
if (token === "-h" || token === "--help") return "help" as const;
|
|
104
|
+
if (token === "-v" || token === "--version") return "version" as const;
|
|
105
|
+
}
|
|
106
|
+
const result = parseFlags({
|
|
107
|
+
argv: rewritten,
|
|
108
|
+
errorPrefix: "ttsx:",
|
|
109
|
+
forwardAfterFirstPositional: true,
|
|
110
|
+
honorDoubleDashSeparator: true,
|
|
111
|
+
subcommand: "ttsx",
|
|
112
|
+
});
|
|
116
113
|
|
|
117
|
-
|
|
114
|
+
const entry = result.positional.find(looksLikeEntryFile);
|
|
115
|
+
if (entry === undefined) {
|
|
118
116
|
throw new Error("ttsx: entry file is required");
|
|
119
117
|
}
|
|
118
|
+
// With `forwardAfterFirstPositional: true` the parser reports
|
|
119
|
+
// `result.positional` as just the entry, `result.passthrough` as flags
|
|
120
|
+
// arriving BEFORE the entry (tsgo-forwarded), and `result.tail` as every
|
|
121
|
+
// token AFTER the entry — those are the user program's argv (e.g. the
|
|
122
|
+
// `generate --input src/input` tail of `ttsx typia.ts generate
|
|
123
|
+
// --input src/input`) and MUST NOT reach tsgo. Anything in positional
|
|
124
|
+
// that is not the entry is a pre-entry flag value (e.g. `--target es2020`)
|
|
125
|
+
// that the parser stored positionally; forward those to tsgo with the
|
|
126
|
+
// rest of `passthrough`.
|
|
127
|
+
const preEntryValues: string[] = result.positional.filter(
|
|
128
|
+
(token) => token !== entry && !looksLikeEntryFile(token),
|
|
129
|
+
);
|
|
130
|
+
const postEntryArgs: string[] = [...result.tail];
|
|
131
|
+
|
|
132
|
+
const preload: string[] = [];
|
|
133
|
+
// `--require` accepts repeated values; the schema engine writes the
|
|
134
|
+
// LAST one into `values`, so reconstruct the full list by scanning the
|
|
135
|
+
// raw argv. Mirrors the legacy parser's `preload.push(takeValue(...))`
|
|
136
|
+
// behaviour.
|
|
137
|
+
//
|
|
138
|
+
// Stop the rescue scan at the first token that begins tail mode —
|
|
139
|
+
// either the entry file or the `--` separator. Without this guard,
|
|
140
|
+
// `ttsx entry.ts -r preload.cjs` would BOTH preload `preload.cjs` AND
|
|
141
|
+
// forward `-r preload.cjs` to the entry's argv, double-effecting the
|
|
142
|
+
// module load. The schema engine already routes post-entry tokens to
|
|
143
|
+
// `result.tail`; the rescue scan must respect the same boundary.
|
|
144
|
+
const scanEnd = rewritten.findIndex(
|
|
145
|
+
(token) => looksLikeEntryFile(token) || token === "--",
|
|
146
|
+
);
|
|
147
|
+
const scanLimit = scanEnd === -1 ? rewritten.length : scanEnd;
|
|
148
|
+
for (let i = 0; i < scanLimit; i += 1) {
|
|
149
|
+
const token = rewritten[i]!;
|
|
150
|
+
if (token === "-r" || token === "--require") {
|
|
151
|
+
const value = rewritten[i + 1];
|
|
152
|
+
if (value !== undefined && !value.startsWith("-")) {
|
|
153
|
+
preload.push(value);
|
|
154
|
+
i += 1;
|
|
155
|
+
}
|
|
156
|
+
} else if (token.startsWith("--require=")) {
|
|
157
|
+
preload.push(token.slice("--require=".length));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
120
160
|
|
|
121
161
|
return {
|
|
122
|
-
binary,
|
|
123
|
-
cacheDir,
|
|
124
|
-
|
|
162
|
+
binary: getString(result, "--binary"),
|
|
163
|
+
cacheDir: getString(result, "--cache-dir"),
|
|
164
|
+
checkers: getNumber(result, "--checkers"),
|
|
165
|
+
cwd: getString(result, "--cwd"),
|
|
125
166
|
entry,
|
|
126
|
-
|
|
167
|
+
noPlugins: getBoolean(result, "--no-plugins") === true,
|
|
168
|
+
passthrough: postEntryArgs,
|
|
127
169
|
preload,
|
|
128
|
-
project,
|
|
170
|
+
project: getString(result, "--tsconfig"),
|
|
171
|
+
singleThreaded: getBoolean(result, "--singleThreaded") === true,
|
|
172
|
+
tsgoFlags: [...result.passthrough, ...preEntryValues],
|
|
129
173
|
};
|
|
130
174
|
}
|
|
131
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Report whether a bare CLI token is the TypeScript entry file rather than a
|
|
178
|
+
* forwarded flag's value. ttsx runs a TypeScript entrypoint, so only a token
|
|
179
|
+
* with a TypeScript source extension is treated as the entry.
|
|
180
|
+
*/
|
|
181
|
+
function looksLikeEntryFile(token: string): boolean {
|
|
182
|
+
return [".ts", ".tsx", ".mts", ".cts"].some((ext) => token.endsWith(ext));
|
|
183
|
+
}
|
|
184
|
+
|
|
132
185
|
function printHelp(): void {
|
|
133
186
|
process.stdout.write(
|
|
134
187
|
[
|
|
@@ -142,10 +195,16 @@ function printHelp(): void {
|
|
|
142
195
|
" --cwd <dir> Resolve entry/project relative to this directory",
|
|
143
196
|
" --cache-dir <dir> Override the runner and source-plugin cache root",
|
|
144
197
|
" --binary <path> Use an explicit tsgo binary",
|
|
198
|
+
" --no-plugins Build the project without ttsc plugins",
|
|
145
199
|
" -r, --require <module> Preload a module before the entrypoint",
|
|
200
|
+
" --singleThreaded Run TypeScript-Go single-threaded (one checker)",
|
|
201
|
+
" --checkers <n> Type-checker pool size (default: TypeScript-Go's)",
|
|
146
202
|
" -h, --help Show this help",
|
|
147
203
|
" -v, --version Print the runner version",
|
|
148
204
|
"",
|
|
205
|
+
" Any other flag before the entry is forwarded to tsgo, so options like",
|
|
206
|
+
" --strict apply to the type-check (e.g. ttsx --strict src/index.ts).",
|
|
207
|
+
"",
|
|
149
208
|
"Examples:",
|
|
150
209
|
" ttsx src/index.ts",
|
|
151
210
|
" ttsx --project tsconfig.json src/index.ts -- --port 3000",
|
|
@@ -172,21 +231,6 @@ function isRelativeSpecifier(specifier: string): boolean {
|
|
|
172
231
|
);
|
|
173
232
|
}
|
|
174
233
|
|
|
175
|
-
function resolveCacheDir(cwd: string, cacheDir?: string): string | undefined {
|
|
176
|
-
if (!cacheDir) {
|
|
177
|
-
return undefined;
|
|
178
|
-
}
|
|
179
|
-
return path.isAbsolute(cacheDir) ? cacheDir : path.resolve(cwd, cacheDir);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function takeValue(flag: string, rest: string[]): string {
|
|
183
|
-
const value = rest.shift();
|
|
184
|
-
if (!value) {
|
|
185
|
-
throw new Error(`ttsx: ${flag} requires a value`);
|
|
186
|
-
}
|
|
187
|
-
return value;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
234
|
function runPreparedEntry(
|
|
191
235
|
parsed: Exclude<ReturnType<typeof parseCLI>, "help" | "version">,
|
|
192
236
|
execution: ReturnType<typeof prepareExecution>,
|
|
@@ -222,6 +266,12 @@ function runPreparedEntry(
|
|
|
222
266
|
return result.status ?? 1;
|
|
223
267
|
}
|
|
224
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Walk every `.js`/`.mjs`/`.cjs` file in `root` and rewrite bare relative
|
|
271
|
+
* specifiers (e.g. `"./foo"`) to include the resolved file extension so Node
|
|
272
|
+
* can load them with `--input-type=module`. Files that did not change are not
|
|
273
|
+
* written back.
|
|
274
|
+
*/
|
|
225
275
|
function rewriteEsmSpecifiers(root: string): void {
|
|
226
276
|
const stack = [root];
|
|
227
277
|
while (stack.length !== 0) {
|
|
@@ -431,26 +481,37 @@ function findTemplateExpressionEnd(
|
|
|
431
481
|
return null;
|
|
432
482
|
}
|
|
433
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Append or fix a file extension on a relative specifier so that Node's ESM
|
|
486
|
+
* loader can resolve it. The resolution order mirrors Node's own algorithm:
|
|
487
|
+
* exact file match → directory index → `.js` fallback. Non-relative specifiers
|
|
488
|
+
* and already-extensioned paths are returned unchanged.
|
|
489
|
+
*/
|
|
434
490
|
function withResolvableExtension(fromFile: string, specifier: string): string {
|
|
435
491
|
if (!specifier.startsWith(".")) {
|
|
492
|
+
// Bare specifiers (packages, builtins) need no rewriting.
|
|
436
493
|
return specifier;
|
|
437
494
|
}
|
|
438
495
|
if (/\.(?:[cm]?js|json|node)$/i.test(specifier)) {
|
|
496
|
+
// Already has a concrete extension the loader understands.
|
|
439
497
|
return specifier;
|
|
440
498
|
}
|
|
441
499
|
const [pathname, suffix = ""] = splitSpecifierSuffix(specifier);
|
|
442
500
|
const fromDir = path.dirname(fromFile);
|
|
501
|
+
// 1. Try the specifier as a file path with each JS extension.
|
|
443
502
|
for (const extension of [".js", ".mjs", ".cjs"]) {
|
|
444
503
|
if (fs.existsSync(path.resolve(fromDir, pathname + extension))) {
|
|
445
504
|
return pathname + extension + suffix;
|
|
446
505
|
}
|
|
447
506
|
}
|
|
507
|
+
// 2. Try interpreting it as a directory with an index file.
|
|
448
508
|
const directory = pathname.replace(/\/+$/, "");
|
|
449
509
|
for (const extension of [".js", ".mjs", ".cjs"]) {
|
|
450
510
|
if (fs.existsSync(path.resolve(fromDir, directory, "index" + extension))) {
|
|
451
511
|
return `${directory}/index${extension}${suffix}`;
|
|
452
512
|
}
|
|
453
513
|
}
|
|
514
|
+
// 3. Last resort: append `.js` and let Node surface the error if it's wrong.
|
|
454
515
|
return `${specifier}.js`;
|
|
455
516
|
}
|
|
456
517
|
|
|
@@ -553,6 +614,12 @@ function skipRegex(input: string, start: number): number {
|
|
|
553
614
|
return input.length;
|
|
554
615
|
}
|
|
555
616
|
|
|
617
|
+
/**
|
|
618
|
+
* Determine whether the `/` at `start` opens a regex literal rather than a
|
|
619
|
+
* division operator. This is a conservative approximation: check the preceding
|
|
620
|
+
* non-whitespace token — if it is an operator character or a keyword that must
|
|
621
|
+
* be followed by an expression, we assume regex.
|
|
622
|
+
*/
|
|
556
623
|
function looksLikeRegexStart(input: string, start: number): boolean {
|
|
557
624
|
let i = start - 1;
|
|
558
625
|
while (i >= 0 && /\s/.test(input[i]!)) {
|
|
@@ -5,6 +5,8 @@ import { createRequire } from "node:module";
|
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
|
|
8
|
+
import { findNearestGoMod } from "../../compiler/internal/paths";
|
|
9
|
+
|
|
8
10
|
const GO_MOD_SEARCH_MAX_DEPTH = 3;
|
|
9
11
|
const TTSC_GO_MODULE_PATH = "github.com/samchon/ttsc/packages/ttsc";
|
|
10
12
|
const TSGO_GO_MODULE_PATH = "github.com/microsoft/typescript-go";
|
|
@@ -397,20 +399,6 @@ function resolveSourceBuildTarget(opts: {
|
|
|
397
399
|
};
|
|
398
400
|
}
|
|
399
401
|
|
|
400
|
-
function findNearestGoMod(from: string, maxDepth: number): string | null {
|
|
401
|
-
let current = path.resolve(from);
|
|
402
|
-
let depth = 0;
|
|
403
|
-
while (true) {
|
|
404
|
-
const candidate = path.join(current, "go.mod");
|
|
405
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
406
|
-
if (depth >= maxDepth) return null;
|
|
407
|
-
const parent = path.dirname(current);
|
|
408
|
-
if (parent === current) return null;
|
|
409
|
-
current = parent;
|
|
410
|
-
depth += 1;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
402
|
function materializeScratchDir(source: string, scratch: string): void {
|
|
415
403
|
fs.mkdirSync(scratch, { recursive: true });
|
|
416
404
|
fs.cpSync(source, scratch, {
|
|
@@ -768,6 +756,19 @@ function walkForGoMod(dir: string, out: string[]): void {
|
|
|
768
756
|
}
|
|
769
757
|
}
|
|
770
758
|
|
|
759
|
+
/**
|
|
760
|
+
* Resolve the directory where compiled plugin binaries are cached.
|
|
761
|
+
*
|
|
762
|
+
* Priority order:
|
|
763
|
+
*
|
|
764
|
+
* 1. `cacheDir` option (resolved relative to `projectRoot`).
|
|
765
|
+
* 2. `TTSC_CACHE_DIR` environment variable.
|
|
766
|
+
* 3. Platform-specific global user cache (XDG / AppData / Library/Caches).
|
|
767
|
+
*
|
|
768
|
+
* When falling back to the global cache, a GC pass is triggered
|
|
769
|
+
* opportunistically to evict old or oversized entries before the cache is
|
|
770
|
+
* used.
|
|
771
|
+
*/
|
|
771
772
|
export function resolvePluginCacheRoot(
|
|
772
773
|
projectRoot: string,
|
|
773
774
|
cacheDir?: string,
|
|
@@ -783,6 +784,13 @@ export function resolvePluginCacheRoot(
|
|
|
783
784
|
return root;
|
|
784
785
|
}
|
|
785
786
|
|
|
787
|
+
/**
|
|
788
|
+
* Return the absolute path to the global plugin binary cache root.
|
|
789
|
+
*
|
|
790
|
+
* The path follows platform conventions (XDG on Linux, AppData on Windows,
|
|
791
|
+
* Library/Caches on macOS) and does not depend on project-local config. Used by
|
|
792
|
+
* `ttsc cache clean` to enumerate cache locations.
|
|
793
|
+
*/
|
|
786
794
|
export function resolveGlobalPluginCacheRoot(): string {
|
|
787
795
|
return path.join(
|
|
788
796
|
resolveUserCacheRoot(),
|
|
@@ -791,6 +799,13 @@ export function resolveGlobalPluginCacheRoot(): string {
|
|
|
791
799
|
);
|
|
792
800
|
}
|
|
793
801
|
|
|
802
|
+
/**
|
|
803
|
+
* Return all directories that `ttsc cache clean` should wipe for a project.
|
|
804
|
+
*
|
|
805
|
+
* Covers three locations where plugin binaries may accumulate: the global user
|
|
806
|
+
* cache, the project-local `node_modules/.ttsc` cache (legacy), and the
|
|
807
|
+
* project-local `.ttsc` cache.
|
|
808
|
+
*/
|
|
794
809
|
export function defaultPluginCacheCleanTargets(projectRoot: string): string[] {
|
|
795
810
|
return [
|
|
796
811
|
resolveGlobalPluginCacheRoot(),
|
|
@@ -878,6 +893,17 @@ function resolveGoCompiler(): string {
|
|
|
878
893
|
return "go";
|
|
879
894
|
}
|
|
880
895
|
|
|
896
|
+
/**
|
|
897
|
+
* Compute a deterministic SHA-256 cache key for a plugin build.
|
|
898
|
+
*
|
|
899
|
+
* The key covers every input that can produce a different binary: ttsc/tsgo
|
|
900
|
+
* versions, platform, entry package, Go compiler identity, Go build environment
|
|
901
|
+
* variables, overlay module sources, plugin source files, and contributor
|
|
902
|
+
* source files. Contributors are sorted by name so declaration order does not
|
|
903
|
+
* affect the key.
|
|
904
|
+
*
|
|
905
|
+
* Exposed for testing and for the `ttsc cache` CLI command.
|
|
906
|
+
*/
|
|
881
907
|
export function computeCacheKey(inputs: {
|
|
882
908
|
contributors?: readonly ITtscBuildContributor[];
|
|
883
909
|
dir: string;
|
|
@@ -2,9 +2,10 @@ import fs from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import { findNearestGoMod } from "../../compiler/internal/paths";
|
|
5
6
|
import { readProjectConfig } from "../../compiler/internal/project/readProjectConfig";
|
|
6
|
-
import type { ITtscPluginContributor } from "../../structures/ITtscPluginContributor";
|
|
7
7
|
import type { ITtscPlugin } from "../../structures/ITtscPlugin";
|
|
8
|
+
import type { ITtscPluginContributor } from "../../structures/ITtscPluginContributor";
|
|
8
9
|
import type { ITtscPluginFactoryContext } from "../../structures/ITtscPluginFactoryContext";
|
|
9
10
|
import type { ITtscProjectPluginConfig } from "../../structures/ITtscProjectPluginConfig";
|
|
10
11
|
import type { TtscPluginStage } from "../../structures/TtscPluginStage";
|
|
@@ -30,6 +31,24 @@ type PackageManifest = {
|
|
|
30
31
|
ttsc?: unknown;
|
|
31
32
|
};
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Resolve, load, and build all native plugin sidecars for a TypeScript project.
|
|
36
|
+
*
|
|
37
|
+
* Reads the project config, discovers plugin entries (from tsconfig and package
|
|
38
|
+
* auto-discovery), validates and composes their descriptors, then invokes
|
|
39
|
+
* `buildSourcePlugin` to compile each Go source package into a cached binary.
|
|
40
|
+
* Returns the ordered set of loaded native plugins alongside the parsed project
|
|
41
|
+
* config.
|
|
42
|
+
*
|
|
43
|
+
* @param options.binary - Absolute path to the ttsc native helper binary.
|
|
44
|
+
* @param options.cacheDir - Override the plugin binary cache directory.
|
|
45
|
+
* @param options.cwd - Working directory for resolving relative paths.
|
|
46
|
+
* @param options.entries - Explicit plugin entries; `false` disables all
|
|
47
|
+
* plugins (skips both tsconfig entries and package auto-discovery).
|
|
48
|
+
* @param options.file - Path to the tsconfig/jsconfig file.
|
|
49
|
+
* @param options.projectRoot - Override the project root directory.
|
|
50
|
+
* @param options.tsconfig - Alias for `file`.
|
|
51
|
+
*/
|
|
33
52
|
export function loadProjectPlugins(options: {
|
|
34
53
|
binary: string;
|
|
35
54
|
cacheDir?: string;
|
|
@@ -85,21 +104,31 @@ export function loadProjectPlugins(options: {
|
|
|
85
104
|
validatePluginSource(plugin);
|
|
86
105
|
const contributors = validatePluginContributors(plugin);
|
|
87
106
|
const source = resolvePluginSource(plugin.source, context.projectRoot);
|
|
88
|
-
const kind = resolveNativeSourceKind(
|
|
107
|
+
const kind = resolveNativeSourceKind(
|
|
108
|
+
source,
|
|
109
|
+
plugin,
|
|
110
|
+
entries[index]!.config,
|
|
111
|
+
index,
|
|
112
|
+
);
|
|
89
113
|
if (kind === "linked" && stage !== "transform") {
|
|
90
114
|
throw new Error(
|
|
91
115
|
`ttsc: plugin "${pluginLabel(plugin, entries[index]!.config, index)}" source is a linked Go package, but only transform-stage plugins can be linked into a compiler host`,
|
|
92
116
|
);
|
|
93
117
|
}
|
|
94
118
|
const linkedContributorName =
|
|
95
|
-
kind === "linked"
|
|
119
|
+
kind === "linked"
|
|
120
|
+
? `linked_${String(index).padStart(6, "0")}`
|
|
121
|
+
: undefined;
|
|
96
122
|
return {
|
|
123
|
+
capabilities: plugin.capabilities,
|
|
97
124
|
contributors,
|
|
98
125
|
config: entries[index]!.config,
|
|
99
126
|
kind,
|
|
100
127
|
label: pluginLabel(plugin, entries[index]!.config, index),
|
|
101
128
|
linkedContributorName,
|
|
102
129
|
name: plugin.name,
|
|
130
|
+
reportsTypeScriptDiagnostics:
|
|
131
|
+
plugin.reportsTypeScriptDiagnostics === true,
|
|
103
132
|
source,
|
|
104
133
|
stage,
|
|
105
134
|
};
|
|
@@ -170,10 +199,12 @@ export function loadProjectPlugins(options: {
|
|
|
170
199
|
}
|
|
171
200
|
return {
|
|
172
201
|
binary,
|
|
202
|
+
capabilities: record.capabilities,
|
|
173
203
|
config: record.config,
|
|
174
204
|
contributors: record.contributors,
|
|
175
205
|
kind: record.kind,
|
|
176
206
|
name: record.name,
|
|
207
|
+
reportsTypeScriptDiagnostics: record.reportsTypeScriptDiagnostics,
|
|
177
208
|
source: record.source,
|
|
178
209
|
stage: record.stage,
|
|
179
210
|
};
|
|
@@ -261,6 +292,15 @@ function composePluginSources(
|
|
|
261
292
|
return {
|
|
262
293
|
...plugin,
|
|
263
294
|
source: aggregate.plugin.source,
|
|
295
|
+
contributors: aggregate.plugin.contributors,
|
|
296
|
+
// The composed plugin's runtime BINARY is the aggregate's binary,
|
|
297
|
+
// so the CLI surface (which flags the sidecar parses) is the
|
|
298
|
+
// aggregate's. Inherit `capabilities` from the aggregate so a
|
|
299
|
+
// capability the aggregate declares — e.g. threadingArgs — does
|
|
300
|
+
// not get silently dropped just because the composed entry's own
|
|
301
|
+
// descriptor omitted it. If the aggregate did not set capabilities
|
|
302
|
+
// we keep the composed plugin's own as a fallback.
|
|
303
|
+
capabilities: aggregate.plugin.capabilities ?? plugin.capabilities,
|
|
264
304
|
};
|
|
265
305
|
});
|
|
266
306
|
}
|
|
@@ -276,6 +316,14 @@ function matchesPluginAlias(
|
|
|
276
316
|
);
|
|
277
317
|
}
|
|
278
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Return `true` when the project has at least one enabled plugin entry.
|
|
321
|
+
*
|
|
322
|
+
* Used by callers that need to skip plugin-specific work when no plugins are
|
|
323
|
+
* configured, without paying the full cost of `loadProjectPlugins`.
|
|
324
|
+
*
|
|
325
|
+
* @param entries - Explicit entries; `false` always returns `false`.
|
|
326
|
+
*/
|
|
279
327
|
export function hasProjectPluginEntries(
|
|
280
328
|
project: ITtscParsedProjectConfig,
|
|
281
329
|
entries?: readonly ITtscProjectPluginConfig[] | false,
|
|
@@ -510,38 +558,72 @@ function loadPluginEntry(
|
|
|
510
558
|
context: ITtscPluginFactoryContext,
|
|
511
559
|
baseDir: string,
|
|
512
560
|
): ITtscPlugin {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const request = resolvePluginRequest(specifier, baseDir);
|
|
519
|
-
const mod = require(request) as {
|
|
520
|
-
createTtscPlugin?: TtscPluginFactory;
|
|
521
|
-
default?: ITtscPlugin | TtscPluginFactory;
|
|
522
|
-
} & Partial<Record<"plugin", ITtscPlugin | TtscPluginFactory>>;
|
|
523
|
-
const candidate =
|
|
524
|
-
mod.createTtscPlugin ??
|
|
525
|
-
mod.default ??
|
|
526
|
-
mod.plugin ??
|
|
527
|
-
(mod as unknown as ITtscPlugin | TtscPluginFactory);
|
|
528
|
-
if (typeof candidate === "function") {
|
|
529
|
-
const plugin = candidate(context);
|
|
530
|
-
if (!isTtscPlugin(plugin)) {
|
|
561
|
+
return withPluginLoaderEnv(() => {
|
|
562
|
+
const specifier = entry.transform;
|
|
563
|
+
if (typeof specifier !== "string" || specifier.length === 0) {
|
|
531
564
|
throw new Error(
|
|
532
|
-
`ttsc: plugin
|
|
565
|
+
`ttsc: plugin entry is missing a string "transform" field`,
|
|
533
566
|
);
|
|
534
567
|
}
|
|
535
|
-
|
|
536
|
-
|
|
568
|
+
|
|
569
|
+
const request = resolvePluginRequest(specifier, baseDir);
|
|
570
|
+
const mod = require(request) as {
|
|
571
|
+
createTtscPlugin?: TtscPluginFactory;
|
|
572
|
+
default?: ITtscPlugin | TtscPluginFactory;
|
|
573
|
+
} & Partial<Record<"plugin", ITtscPlugin | TtscPluginFactory>>;
|
|
574
|
+
const candidate =
|
|
575
|
+
mod.createTtscPlugin ??
|
|
576
|
+
mod.default ??
|
|
577
|
+
mod.plugin ??
|
|
578
|
+
(mod as unknown as ITtscPlugin | TtscPluginFactory);
|
|
579
|
+
if (typeof candidate === "function") {
|
|
580
|
+
const plugin = candidate(context);
|
|
581
|
+
if (!isTtscPlugin(plugin)) {
|
|
582
|
+
throw new Error(
|
|
583
|
+
`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`,
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
rejectJsTransformFunctions(specifier, plugin);
|
|
587
|
+
return plugin;
|
|
588
|
+
}
|
|
589
|
+
if (isTtscPlugin(candidate)) {
|
|
590
|
+
rejectJsTransformFunctions(specifier, candidate);
|
|
591
|
+
return candidate;
|
|
592
|
+
}
|
|
593
|
+
throw new Error(
|
|
594
|
+
`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`,
|
|
595
|
+
);
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function withPluginLoaderEnv<T>(run: () => T): T {
|
|
600
|
+
const previousNode = process.env.TTSC_NODE_BINARY;
|
|
601
|
+
const previousTtsx = process.env.TTSC_TTSX_BINARY;
|
|
602
|
+
process.env.TTSC_NODE_BINARY ??= process.execPath;
|
|
603
|
+
process.env.TTSC_TTSX_BINARY ??= path.join(
|
|
604
|
+
__dirname,
|
|
605
|
+
"..",
|
|
606
|
+
"..",
|
|
607
|
+
"launcher",
|
|
608
|
+
"ttsx.js",
|
|
609
|
+
);
|
|
610
|
+
try {
|
|
611
|
+
return run();
|
|
612
|
+
} finally {
|
|
613
|
+
restoreEnv("TTSC_NODE_BINARY", previousNode);
|
|
614
|
+
restoreEnv("TTSC_TTSX_BINARY", previousTtsx);
|
|
537
615
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function restoreEnv(
|
|
619
|
+
key: "TTSC_NODE_BINARY" | "TTSC_TTSX_BINARY",
|
|
620
|
+
value: string | undefined,
|
|
621
|
+
): void {
|
|
622
|
+
if (value === undefined) {
|
|
623
|
+
delete process.env[key];
|
|
624
|
+
} else {
|
|
625
|
+
process.env[key] = value;
|
|
541
626
|
}
|
|
542
|
-
throw new Error(
|
|
543
|
-
`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`,
|
|
544
|
-
);
|
|
545
627
|
}
|
|
546
628
|
|
|
547
629
|
function isTtscPlugin(value: unknown): value is ITtscPlugin {
|
|
@@ -610,7 +692,10 @@ function resolveNativeSourceKind(
|
|
|
610
692
|
config: ITtscProjectPluginConfig,
|
|
611
693
|
index: number,
|
|
612
694
|
): "executable" | "linked" {
|
|
613
|
-
const packageDir = resolveGoPackageDir(
|
|
695
|
+
const packageDir = resolveGoPackageDir(
|
|
696
|
+
source,
|
|
697
|
+
pluginLabel(plugin, config, index),
|
|
698
|
+
);
|
|
614
699
|
if (findNearestGoMod(packageDir, GO_MOD_SEARCH_MAX_DEPTH) === null) {
|
|
615
700
|
throw new Error(
|
|
616
701
|
`ttsc: plugin "${pluginLabel(plugin, config, index)}" source must be inside a Go module with go.mod within ${GO_MOD_SEARCH_MAX_DEPTH} parent directories: ${source}`,
|
|
@@ -625,20 +710,6 @@ function resolveNativeSourceKind(
|
|
|
625
710
|
return packageName === "main" ? "executable" : "linked";
|
|
626
711
|
}
|
|
627
712
|
|
|
628
|
-
function findNearestGoMod(from: string, maxDepth: number): string | null {
|
|
629
|
-
let current = path.resolve(from);
|
|
630
|
-
let depth = 0;
|
|
631
|
-
while (true) {
|
|
632
|
-
const candidate = path.join(current, "go.mod");
|
|
633
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
634
|
-
if (depth >= maxDepth) return null;
|
|
635
|
-
const parent = path.dirname(current);
|
|
636
|
-
if (parent === current) return null;
|
|
637
|
-
current = parent;
|
|
638
|
-
depth += 1;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
713
|
function resolveGoPackageDir(source: string, label: string): string {
|
|
643
714
|
if (!fs.existsSync(source)) {
|
|
644
715
|
throw new Error(`ttsc: plugin "${label}" source does not exist: ${source}`);
|