ttsc 0.3.1 → 0.4.0-dev.20260424
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/LICENSE +1 -1
- package/README.md +216 -302
- package/cmd/ttsc/build.go +128 -0
- package/cmd/ttsc/main.go +222 -0
- package/cmd/ttsc/main_test.go +288 -0
- package/cmd/ttsc/transform.go +166 -0
- package/driver/host.go +31 -0
- package/driver/program.go +298 -0
- package/driver/rewrite.go +315 -0
- package/go.mod +48 -0
- package/go.sum +22 -0
- package/lib/api.d.ts +91 -0
- package/lib/api.js +325 -0
- package/lib/api.js.map +1 -0
- package/lib/cli.d.ts +1 -0
- package/lib/cli.js +481 -0
- package/lib/cli.js.map +1 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +34 -0
- package/lib/index.js.map +1 -0
- package/lib/native.d.ts +15 -0
- package/lib/native.js +34 -0
- package/lib/native.js.map +1 -0
- package/lib/platform.d.ts +66 -0
- package/lib/platform.js +170 -0
- package/lib/platform.js.map +1 -0
- package/lib/plugin.d.ts +47 -0
- package/lib/plugin.js +150 -0
- package/lib/plugin.js.map +1 -0
- package/lib/project.d.ts +32 -0
- package/lib/project.js +303 -0
- package/lib/project.js.map +1 -0
- package/package.json +70 -63
- package/shim/ast/extra-shim.json +4 -0
- package/shim/ast/go.mod +5 -0
- package/shim/ast/shim.go +117 -0
- package/shim/bundled/extra-shim.json +1 -0
- package/shim/bundled/go.mod +5 -0
- package/shim/bundled/shim.go +19 -0
- package/shim/checker/extra-shim.json +31 -0
- package/shim/checker/go.mod +5 -0
- package/shim/checker/shim.go +146 -0
- package/shim/compiler/extra-shim.json +4 -0
- package/shim/compiler/go.mod +5 -0
- package/shim/compiler/shim.go +50 -0
- package/shim/core/extra-shim.json +4 -0
- package/shim/core/go.mod +5 -0
- package/shim/core/shim.go +11 -0
- package/shim/diagnosticwriter/go.mod +5 -0
- package/shim/diagnosticwriter/shim.go +29 -0
- package/shim/parser/extra-shim.json +4 -0
- package/shim/parser/go.mod +5 -0
- package/shim/parser/shim.go +55 -0
- package/shim/scanner/extra-shim.json +4 -0
- package/shim/scanner/go.mod +5 -0
- package/shim/scanner/shim.go +96 -0
- package/shim/tsoptions/extra-shim.json +4 -0
- package/shim/tsoptions/go.mod +5 -0
- package/shim/tsoptions/shim.go +123 -0
- package/shim/tspath/extra-shim.json +4 -0
- package/shim/tspath/go.mod +5 -0
- package/shim/tspath/shim.go +165 -0
- package/shim/vfs/cachedvfs/extra-shim.json +4 -0
- package/shim/vfs/cachedvfs/go.mod +5 -0
- package/shim/vfs/cachedvfs/shim.go +12 -0
- package/shim/vfs/extra-shim.json +4 -0
- package/shim/vfs/go.mod +5 -0
- package/shim/vfs/osvfs/extra-shim.json +4 -0
- package/shim/vfs/osvfs/go.mod +5 -0
- package/shim/vfs/osvfs/shim.go +13 -0
- package/shim/vfs/shim.go +19 -0
- package/src/api.ts +416 -0
- package/src/cli.ts +492 -0
- package/src/index.ts +18 -0
- package/src/launcher/ttsc-dev.js +42 -0
- package/src/launcher/ttsc.js +19 -0
- package/src/native.ts +60 -0
- package/src/platform.ts +163 -0
- package/src/plugin.ts +241 -0
- package/src/project.ts +330 -0
- package/test/native.test.ts +60 -0
- package/test/package.test.ts +123 -0
- package/test/platform.test.ts +58 -0
- package/test/project.test.ts +195 -0
- package/tools/gen_shims/go.mod +14 -0
- package/tools/gen_shims/go.sum +12 -0
- package/tools/gen_shims/main.go +446 -0
- package/CHANGELOG.md +0 -2
- package/bin/tsc +0 -2
- package/bin/tsserver +0 -2
- package/lib/PluginCreator.d.ts +0 -67
- package/lib/PluginCreator.js +0 -190
- package/lib/cancelationToken.d.ts +0 -0
- package/lib/cancelationToken.js +0 -2
- package/lib/loadTypescript.d.ts +0 -7
- package/lib/loadTypescript.js +0 -56
- package/lib/patchCreateProgram.d.ts +0 -21
- package/lib/patchCreateProgram.js +0 -190
- package/lib/tsc.d.ts +0 -1
- package/lib/tsc.js +0 -24
- package/lib/tsserver.d.ts +0 -0
- package/lib/tsserver.js +0 -2
- package/lib/tsserverlibrary.d.ts +0 -3
- package/lib/tsserverlibrary.js +0 -4
- package/lib/typescript.d.ts +0 -3
- package/lib/typescript.js +0 -4
- package/lib/typescriptServices.d.ts +0 -3
- package/lib/typescriptServices.js +0 -4
- package/lib/typingsInstaller.d.ts +0 -0
- package/lib/typingsInstaller.js +0 -2
- package/lib/watchGuard.d.ts +0 -0
- package/lib/watchGuard.js +0 -2
package/src/project.ts
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
|
|
6
|
+
export interface ProjectLocatorOptions {
|
|
7
|
+
cwd?: string;
|
|
8
|
+
file?: string;
|
|
9
|
+
tsconfig?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ProjectPluginConfig {
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
transform?: string;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ParsedProjectConfig {
|
|
19
|
+
compilerOptions: {
|
|
20
|
+
outDir?: string;
|
|
21
|
+
plugins: ProjectPluginConfig[];
|
|
22
|
+
};
|
|
23
|
+
path: string;
|
|
24
|
+
root: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveWorkingDirectory(cwd?: string): string {
|
|
28
|
+
return path.resolve(cwd ?? process.cwd());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function resolveAbsolutePath(cwd: string, target: string): string {
|
|
32
|
+
return path.isAbsolute(target) ? target : path.resolve(cwd, target);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveProjectConfig(opts: ProjectLocatorOptions = {}): string {
|
|
36
|
+
const cwd = resolveWorkingDirectory(opts.cwd);
|
|
37
|
+
if (opts.tsconfig) {
|
|
38
|
+
const resolved = resolveAbsolutePath(cwd, opts.tsconfig);
|
|
39
|
+
if (!fs.existsSync(resolved)) {
|
|
40
|
+
throw new Error(`ttsc: tsconfig not found: ${resolved}`);
|
|
41
|
+
}
|
|
42
|
+
return resolveRealPath(resolved);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const start = opts.file
|
|
46
|
+
? resolveRealPath(resolveAbsolutePath(cwd, opts.file))
|
|
47
|
+
: cwd;
|
|
48
|
+
const from = isDirectory(start) ? start : path.dirname(start);
|
|
49
|
+
const found = findUp(from, ["tsconfig.json", "jsconfig.json"]);
|
|
50
|
+
if (!found) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`ttsc: could not find tsconfig.json or jsconfig.json starting from ${from}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return resolveRealPath(found);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function resolveProjectRoot(opts: ProjectLocatorOptions = {}): string {
|
|
59
|
+
return path.dirname(resolveProjectConfig(opts));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function defaultCacheDirectory(projectRoot: string, tool: string): string {
|
|
63
|
+
return path.join(projectRoot, "node_modules", ".cache", "ttsc", tool);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function readProjectConfig(opts: ProjectLocatorOptions = {}): ParsedProjectConfig {
|
|
67
|
+
const tsconfig = resolveProjectConfig(opts);
|
|
68
|
+
const root = path.dirname(tsconfig);
|
|
69
|
+
const compilerOptions = readResolvedCompilerOptions(tsconfig);
|
|
70
|
+
return {
|
|
71
|
+
compilerOptions: {
|
|
72
|
+
outDir: compilerOptions.outDir,
|
|
73
|
+
plugins: compilerOptions.plugins,
|
|
74
|
+
},
|
|
75
|
+
path: tsconfig,
|
|
76
|
+
root,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface CacheKeyInput {
|
|
81
|
+
file: string;
|
|
82
|
+
tsconfig: string;
|
|
83
|
+
version: string;
|
|
84
|
+
mode?: string;
|
|
85
|
+
extra?: readonly string[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createTransformCacheKey(input: CacheKeyInput): string {
|
|
89
|
+
const hash = crypto.createHash("sha256");
|
|
90
|
+
hash.update("file\0");
|
|
91
|
+
hash.update(fs.readFileSync(input.file));
|
|
92
|
+
hash.update("\0tsconfig\0");
|
|
93
|
+
hash.update(fs.readFileSync(input.tsconfig));
|
|
94
|
+
hash.update(`\0version\0${input.version}`);
|
|
95
|
+
hash.update(`\0mode\0${input.mode ?? ""}`);
|
|
96
|
+
for (const item of input.extra ?? []) {
|
|
97
|
+
hash.update(`\0extra\0${item}`);
|
|
98
|
+
}
|
|
99
|
+
return hash.digest("hex");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function findUp(from: string, names: readonly string[]): string | null {
|
|
103
|
+
let current = path.resolve(from);
|
|
104
|
+
while (true) {
|
|
105
|
+
for (const name of names) {
|
|
106
|
+
const candidate = path.join(current, name);
|
|
107
|
+
if (fs.existsSync(candidate)) {
|
|
108
|
+
return candidate;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const parent = path.dirname(current);
|
|
112
|
+
if (parent === current) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
current = parent;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isDirectory(location: string): boolean {
|
|
120
|
+
try {
|
|
121
|
+
return fs.statSync(location).isDirectory();
|
|
122
|
+
} catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isProjectPluginConfig(value: unknown): value is ProjectPluginConfig {
|
|
128
|
+
return typeof value === "object" && value !== null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface RawTsconfig {
|
|
132
|
+
extends?: unknown;
|
|
133
|
+
compilerOptions?: {
|
|
134
|
+
outDir?: unknown;
|
|
135
|
+
plugins?: unknown;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface ResolvedCompilerOptions {
|
|
140
|
+
outDir?: string;
|
|
141
|
+
plugins: ProjectPluginConfig[];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function resolveRealPath(location: string): string {
|
|
145
|
+
try {
|
|
146
|
+
return fs.realpathSync(location);
|
|
147
|
+
} catch {
|
|
148
|
+
return location;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function readResolvedCompilerOptions(
|
|
153
|
+
tsconfig: string,
|
|
154
|
+
seen: Set<string> = new Set(),
|
|
155
|
+
): ResolvedCompilerOptions {
|
|
156
|
+
const canonical = resolveRealPath(tsconfig);
|
|
157
|
+
if (seen.has(canonical)) {
|
|
158
|
+
throw new Error(`ttsc: circular tsconfig extends detected: ${canonical}`);
|
|
159
|
+
}
|
|
160
|
+
seen.add(canonical);
|
|
161
|
+
|
|
162
|
+
const parsed = parseJsonc(fs.readFileSync(canonical, "utf8")) as RawTsconfig;
|
|
163
|
+
const own = parsed.compilerOptions;
|
|
164
|
+
const base =
|
|
165
|
+
typeof parsed.extends === "string"
|
|
166
|
+
? readResolvedCompilerOptions(resolveExtendsConfig(canonical, parsed.extends), seen)
|
|
167
|
+
: { plugins: [] };
|
|
168
|
+
return {
|
|
169
|
+
outDir:
|
|
170
|
+
typeof own?.outDir === "string"
|
|
171
|
+
? resolveAbsolutePath(path.dirname(canonical), own.outDir)
|
|
172
|
+
: base.outDir,
|
|
173
|
+
plugins: Array.isArray(own?.plugins)
|
|
174
|
+
? own.plugins.filter(isProjectPluginConfig)
|
|
175
|
+
: base.plugins,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function resolveExtendsConfig(tsconfig: string, specifier: string): string {
|
|
180
|
+
const baseDir = path.dirname(tsconfig);
|
|
181
|
+
if (path.isAbsolute(specifier)) {
|
|
182
|
+
return resolveExistingExtendsPath(specifier);
|
|
183
|
+
}
|
|
184
|
+
if (isRelativePluginSpecifier(specifier)) {
|
|
185
|
+
return resolveExistingExtendsPath(path.resolve(baseDir, specifier));
|
|
186
|
+
}
|
|
187
|
+
const resolver = createRequire(tsconfig);
|
|
188
|
+
try {
|
|
189
|
+
return resolveRealPath(resolver.resolve(specifier));
|
|
190
|
+
} catch {
|
|
191
|
+
return resolveRealPath(resolver.resolve(`${specifier}.json`));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function resolveExistingExtendsPath(location: string): string {
|
|
196
|
+
const candidates = new Set<string>([
|
|
197
|
+
location,
|
|
198
|
+
`${location}.json`,
|
|
199
|
+
path.join(location, "tsconfig.json"),
|
|
200
|
+
]);
|
|
201
|
+
for (const candidate of candidates) {
|
|
202
|
+
if (fs.existsSync(candidate)) {
|
|
203
|
+
return resolveRealPath(candidate);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
throw new Error(`ttsc: extended tsconfig not found: ${location}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function isRelativePluginSpecifier(specifier: string): boolean {
|
|
210
|
+
return (
|
|
211
|
+
specifier === "." ||
|
|
212
|
+
specifier === ".." ||
|
|
213
|
+
specifier.startsWith("./") ||
|
|
214
|
+
specifier.startsWith("../") ||
|
|
215
|
+
specifier.startsWith(".\\") ||
|
|
216
|
+
specifier.startsWith("..\\")
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function parseJsonc(input: string): unknown {
|
|
221
|
+
return JSON.parse(stripTrailingCommas(stripComments(input)));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function stripComments(input: string): string {
|
|
225
|
+
let output = "";
|
|
226
|
+
let inBlockComment = false;
|
|
227
|
+
let inLineComment = false;
|
|
228
|
+
let inString = false;
|
|
229
|
+
let quote = "";
|
|
230
|
+
let escape = false;
|
|
231
|
+
|
|
232
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
233
|
+
const current = input[i]!;
|
|
234
|
+
const next = input[i + 1];
|
|
235
|
+
|
|
236
|
+
if (inBlockComment) {
|
|
237
|
+
if (current === "*" && next === "/") {
|
|
238
|
+
inBlockComment = false;
|
|
239
|
+
i += 1;
|
|
240
|
+
}
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (inLineComment) {
|
|
244
|
+
if (current === "\n") {
|
|
245
|
+
inLineComment = false;
|
|
246
|
+
output += current;
|
|
247
|
+
}
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (inString) {
|
|
251
|
+
output += current;
|
|
252
|
+
if (escape) {
|
|
253
|
+
escape = false;
|
|
254
|
+
} else if (current === "\\") {
|
|
255
|
+
escape = true;
|
|
256
|
+
} else if (current === quote) {
|
|
257
|
+
inString = false;
|
|
258
|
+
quote = "";
|
|
259
|
+
}
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if ((current === '"' || current === "'") && !inString) {
|
|
264
|
+
inString = true;
|
|
265
|
+
quote = current;
|
|
266
|
+
output += current;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (current == "/" && next === "/") {
|
|
270
|
+
inLineComment = true;
|
|
271
|
+
i += 1;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (current == "/" && next === "*") {
|
|
275
|
+
inBlockComment = true;
|
|
276
|
+
i += 1;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
output += current;
|
|
280
|
+
}
|
|
281
|
+
return output;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function stripTrailingCommas(input: string): string {
|
|
285
|
+
let output = "";
|
|
286
|
+
let inString = false;
|
|
287
|
+
let quote = "";
|
|
288
|
+
let escape = false;
|
|
289
|
+
|
|
290
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
291
|
+
const current = input[i]!;
|
|
292
|
+
if (inString) {
|
|
293
|
+
output += current;
|
|
294
|
+
if (escape) {
|
|
295
|
+
escape = false;
|
|
296
|
+
} else if (current === "\\") {
|
|
297
|
+
escape = true;
|
|
298
|
+
} else if (current === quote) {
|
|
299
|
+
inString = false;
|
|
300
|
+
quote = "";
|
|
301
|
+
}
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (current === '"' || current === "'") {
|
|
306
|
+
inString = true;
|
|
307
|
+
quote = current;
|
|
308
|
+
output += current;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (current === ",") {
|
|
312
|
+
const next = nextNonWhitespace(input, i + 1);
|
|
313
|
+
if (next === "}" || next === "]") {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
output += current;
|
|
318
|
+
}
|
|
319
|
+
return output;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function nextNonWhitespace(input: string, from: number): string | undefined {
|
|
323
|
+
for (let i = from; i < input.length; i += 1) {
|
|
324
|
+
const current = input[i]!;
|
|
325
|
+
if (/\s/.test(current) === false) {
|
|
326
|
+
return current;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const assert = require("node:assert/strict");
|
|
2
|
+
const test = require("node:test");
|
|
3
|
+
|
|
4
|
+
const { resolveNativeBackend } = require("../src/native.ts");
|
|
5
|
+
|
|
6
|
+
test("resolveNativeBackend accepts versioned native backend descriptors", () => {
|
|
7
|
+
const backend = resolveNativeBackend({
|
|
8
|
+
name: "native-test",
|
|
9
|
+
native: {
|
|
10
|
+
mode: "native-test",
|
|
11
|
+
binary: "/tmp/ttsc-native-test",
|
|
12
|
+
contractVersion: 1,
|
|
13
|
+
capabilities: ["rewrite", "diagnostics"],
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
assert.deepEqual(backend, {
|
|
18
|
+
mode: "native-test",
|
|
19
|
+
binary: "/tmp/ttsc-native-test",
|
|
20
|
+
contractVersion: 1,
|
|
21
|
+
capabilities: ["rewrite", "diagnostics"],
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("resolveNativeBackend keeps nativeMode/nativeBinary as a legacy alias", () => {
|
|
26
|
+
const backend = resolveNativeBackend({
|
|
27
|
+
name: "legacy-native-test",
|
|
28
|
+
nativeMode: "legacy-native-test",
|
|
29
|
+
nativeBinary: "/tmp/ttsc-legacy-native-test",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
assert.deepEqual(backend, {
|
|
33
|
+
mode: "legacy-native-test",
|
|
34
|
+
binary: "/tmp/ttsc-legacy-native-test",
|
|
35
|
+
contractVersion: 1,
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("resolveNativeBackend rejects ambiguous native declarations", () => {
|
|
40
|
+
assert.throws(
|
|
41
|
+
() =>
|
|
42
|
+
resolveNativeBackend({
|
|
43
|
+
name: "bad-native-test",
|
|
44
|
+
native: { mode: "native-test" },
|
|
45
|
+
nativeMode: "native-test",
|
|
46
|
+
}),
|
|
47
|
+
/must use either native or nativeMode\/nativeBinary/,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("resolveNativeBackend rejects unsupported native contract versions", () => {
|
|
52
|
+
assert.throws(
|
|
53
|
+
() =>
|
|
54
|
+
resolveNativeBackend({
|
|
55
|
+
name: "future-native-test",
|
|
56
|
+
native: { mode: "native-test", contractVersion: 2 },
|
|
57
|
+
}),
|
|
58
|
+
/unsupported native contract version 2/,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const assert = require("node:assert/strict");
|
|
2
|
+
const fs = require("node:fs");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
const test = require("node:test");
|
|
5
|
+
|
|
6
|
+
const workspaceRoot = path.resolve(__dirname, "../../..");
|
|
7
|
+
|
|
8
|
+
test("package metadata separates monorepo source exports from publish exports", () => {
|
|
9
|
+
for (const packageName of ["ttsc", "ttsx"]) {
|
|
10
|
+
const packageJson = readPackageJson(packageName);
|
|
11
|
+
assert.match(packageJson.main, /^src\/.+\.ts$/);
|
|
12
|
+
assert.match(packageJson.types, /^src\/.+\.ts$/);
|
|
13
|
+
assert.equal(packageJson.exports["."], "./src/index.ts");
|
|
14
|
+
assert.equal(packageJson.publishConfig.main, "lib/index.js");
|
|
15
|
+
assert.equal(packageJson.publishConfig.types, "lib/index.d.ts");
|
|
16
|
+
assert.equal(
|
|
17
|
+
packageJson.publishConfig.exports["."].types,
|
|
18
|
+
"./lib/index.d.ts",
|
|
19
|
+
);
|
|
20
|
+
assert.equal(
|
|
21
|
+
packageJson.publishConfig.exports["."].default,
|
|
22
|
+
"./lib/index.js",
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("ttsc exposes plugin helpers through the root package only", () => {
|
|
28
|
+
const packageJson = readPackageJson("ttsc");
|
|
29
|
+
assert.equal(packageJson.exports["./plugin"], undefined);
|
|
30
|
+
assert.equal(packageJson.publishConfig.exports["./plugin"], undefined);
|
|
31
|
+
assert.equal(
|
|
32
|
+
fs.existsSync(path.join(workspaceRoot, "packages", "ttsc", "src", "plugin.ts")),
|
|
33
|
+
true,
|
|
34
|
+
);
|
|
35
|
+
assert.equal(
|
|
36
|
+
fs.existsSync(path.join(workspaceRoot, "packages", "ttsc", "src", "index.ts")),
|
|
37
|
+
true,
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("ttsc declares platform packages as optional dependencies", () => {
|
|
42
|
+
const packageJson = readPackageJson("ttsc");
|
|
43
|
+
const expected = [
|
|
44
|
+
"@ttsc/linux-x64",
|
|
45
|
+
"@ttsc/linux-arm64",
|
|
46
|
+
"@ttsc/darwin-x64",
|
|
47
|
+
"@ttsc/darwin-arm64",
|
|
48
|
+
"@ttsc/win32-x64",
|
|
49
|
+
"@ttsc/win32-arm64",
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
assert.deepEqual(
|
|
53
|
+
Object.keys(packageJson.optionalDependencies).sort(),
|
|
54
|
+
expected.toSorted(),
|
|
55
|
+
);
|
|
56
|
+
for (const name of expected) {
|
|
57
|
+
assert.equal(packageJson.optionalDependencies[name], "workspace:*");
|
|
58
|
+
}
|
|
59
|
+
assert.equal(packageJson.files.includes("native"), false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("published package file lists keep TypeScript and Go sources", () => {
|
|
63
|
+
const ttsc = readPackageJson("ttsc");
|
|
64
|
+
for (const entry of [
|
|
65
|
+
"cmd",
|
|
66
|
+
"driver",
|
|
67
|
+
"shim",
|
|
68
|
+
"src",
|
|
69
|
+
"test",
|
|
70
|
+
"tools",
|
|
71
|
+
"go.mod",
|
|
72
|
+
"go.sum",
|
|
73
|
+
]) {
|
|
74
|
+
assert.equal(
|
|
75
|
+
ttsc.files.includes(entry),
|
|
76
|
+
true,
|
|
77
|
+
`ttsc files must include ${entry}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
for (const entry of ["native", "node_modules", "THIRD-PARTY-LICENSES.md"]) {
|
|
81
|
+
assert.equal(
|
|
82
|
+
ttsc.files.includes(entry),
|
|
83
|
+
false,
|
|
84
|
+
`ttsc files must not include ${entry}`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
assert.equal(ttsc.files.includes("tsconfig.json"), false);
|
|
88
|
+
|
|
89
|
+
const ttsx = readPackageJson("ttsx");
|
|
90
|
+
assert.equal(ttsx.files.includes("src"), true, "ttsx files must include src");
|
|
91
|
+
assert.equal(ttsx.files.includes("tsconfig.json"), false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("platform packages expose os cpu constrained native binaries", () => {
|
|
95
|
+
const matrix = {
|
|
96
|
+
"ttsc-linux-x64": ["@ttsc/linux-x64", "linux", "x64", "Linux x64"],
|
|
97
|
+
"ttsc-linux-arm64": ["@ttsc/linux-arm64", "linux", "arm64", "Linux arm64"],
|
|
98
|
+
"ttsc-darwin-x64": ["@ttsc/darwin-x64", "darwin", "x64", "macOS x64"],
|
|
99
|
+
"ttsc-darwin-arm64": ["@ttsc/darwin-arm64", "darwin", "arm64", "macOS arm64"],
|
|
100
|
+
"ttsc-win32-x64": ["@ttsc/win32-x64", "win32", "x64", "Windows x64"],
|
|
101
|
+
"ttsc-win32-arm64": ["@ttsc/win32-arm64", "win32", "arm64", "Windows arm64"],
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
for (const [dir, [name, os, cpu, label]] of Object.entries(matrix)) {
|
|
105
|
+
const packageJson = readPackageJson(dir);
|
|
106
|
+
assert.equal(packageJson.name, name);
|
|
107
|
+
assert.deepEqual(packageJson.os, [os]);
|
|
108
|
+
assert.deepEqual(packageJson.cpu, [cpu]);
|
|
109
|
+
assert.deepEqual(packageJson.files, ["bin", "package.json", "README.md"]);
|
|
110
|
+
assert.equal(packageJson.scripts.build, "node ../../scripts/build-platform-package.cjs");
|
|
111
|
+
assert.equal(packageJson.scripts.prepack, "pnpm run build");
|
|
112
|
+
assert.equal(packageJson.description.includes(label), true);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
function readPackageJson(packageName) {
|
|
117
|
+
return JSON.parse(
|
|
118
|
+
fs.readFileSync(
|
|
119
|
+
path.join(workspaceRoot, "packages", packageName, "package.json"),
|
|
120
|
+
"utf8",
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const assert = require("node:assert/strict");
|
|
2
|
+
const test = require("node:test");
|
|
3
|
+
const {
|
|
4
|
+
binaryName,
|
|
5
|
+
installHint,
|
|
6
|
+
platformPackageRequest,
|
|
7
|
+
resolveBinary,
|
|
8
|
+
} = require("../src/platform.ts");
|
|
9
|
+
|
|
10
|
+
test("resolveBinary prefers TTSC_BINARY absolute override", () => {
|
|
11
|
+
const resolved = resolveBinary({
|
|
12
|
+
env: {
|
|
13
|
+
TTSC_BINARY: "/tmp/custom-ttsc",
|
|
14
|
+
},
|
|
15
|
+
localBinaryLookup: () => "/tmp/local-ttsc",
|
|
16
|
+
resolver: () => {
|
|
17
|
+
throw new Error("resolver should not be called when TTSC_BINARY is set");
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
assert.equal(resolved, "/tmp/custom-ttsc");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("resolveBinary falls back to local binary when optional package is missing", () => {
|
|
24
|
+
const resolved = resolveBinary({
|
|
25
|
+
env: {},
|
|
26
|
+
localBinaryLookup: () => "/tmp/local-ttsc",
|
|
27
|
+
resolver: () => {
|
|
28
|
+
throw new Error("optional package missing");
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
assert.equal(resolved, "/tmp/local-ttsc");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("resolveBinary returns null when only published and local binaries are missing", () => {
|
|
35
|
+
const resolved = resolveBinary({
|
|
36
|
+
env: {},
|
|
37
|
+
localBinaryLookup: () => null,
|
|
38
|
+
moduleDir: `${process.cwd()}/src`,
|
|
39
|
+
resolver: () => {
|
|
40
|
+
throw new Error("optional package missing");
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
assert.equal(resolved, null);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("platform helpers describe the published package contract", () => {
|
|
47
|
+
assert.equal(binaryName({ platform: "win32" }), "ttsc.exe");
|
|
48
|
+
assert.equal(binaryName({ platform: "linux" }), "ttsc");
|
|
49
|
+
assert.equal(
|
|
50
|
+
platformPackageRequest({ platform: "linux", arch: "x64" }),
|
|
51
|
+
"@ttsc/linux-x64/bin/ttsc",
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const hint = installHint({ platform: "linux", arch: "x64" });
|
|
55
|
+
assert.match(hint, /@ttsc\/linux-x64/);
|
|
56
|
+
assert.match(hint, /TTSC_BINARY env var/);
|
|
57
|
+
assert.doesNotMatch(hint, /ttsc-dev\.js/);
|
|
58
|
+
});
|