@tsonic/cli 0.0.62 → 0.0.64
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/dist/.tsbuildinfo +1 -1
- package/dist/aikya/bindings.d.ts +44 -0
- package/dist/aikya/bindings.d.ts.map +1 -0
- package/dist/aikya/bindings.js +777 -0
- package/dist/aikya/bindings.js.map +1 -0
- package/dist/aikya/bindings.test.d.ts +2 -0
- package/dist/aikya/bindings.test.d.ts.map +1 -0
- package/dist/aikya/bindings.test.js +554 -0
- package/dist/aikya/bindings.test.js.map +1 -0
- package/dist/cli/dispatcher.d.ts.map +1 -1
- package/dist/cli/dispatcher.js +17 -0
- package/dist/cli/dispatcher.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +4 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/parser.d.ts.map +1 -1
- package/dist/cli/parser.js +6 -5
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/parser.test.js +13 -2
- package/dist/cli/parser.test.js.map +1 -1
- package/dist/commands/add-common.js +1 -1
- package/dist/commands/add-common.js.map +1 -1
- package/dist/commands/add-npm.d.ts +3 -2
- package/dist/commands/add-npm.d.ts.map +1 -1
- package/dist/commands/add-npm.js +69 -177
- package/dist/commands/add-npm.js.map +1 -1
- package/dist/commands/add-npm.test.js +277 -2
- package/dist/commands/add-npm.test.js.map +1 -1
- package/dist/commands/add-package.d.ts.map +1 -1
- package/dist/commands/add-package.js +1 -0
- package/dist/commands/add-package.js.map +1 -1
- package/dist/commands/build-library-bindings-aliases.test.js +877 -5
- package/dist/commands/build-library-bindings-aliases.test.js.map +1 -1
- package/dist/commands/build-native-lib.test.js +4 -1
- package/dist/commands/build-native-lib.test.js.map +1 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +168 -148
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/build.test.js +22 -3
- package/dist/commands/build.test.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +5 -0
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.d.ts +5 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +42 -7
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +82 -2
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/library-bindings-augment.d.ts +2 -0
- package/dist/commands/library-bindings-augment.d.ts.map +1 -1
- package/dist/commands/library-bindings-augment.js +396 -57
- package/dist/commands/library-bindings-augment.js.map +1 -1
- package/dist/commands/library-bindings-augment.test.d.ts +2 -0
- package/dist/commands/library-bindings-augment.test.d.ts.map +1 -0
- package/dist/commands/library-bindings-augment.test.js +547 -0
- package/dist/commands/library-bindings-augment.test.js.map +1 -0
- package/dist/commands/library-bindings-firstparty-regressions.test.d.ts +2 -0
- package/dist/commands/library-bindings-firstparty-regressions.test.d.ts.map +1 -0
- package/dist/commands/library-bindings-firstparty-regressions.test.js +217 -0
- package/dist/commands/library-bindings-firstparty-regressions.test.js.map +1 -0
- package/dist/commands/library-bindings-firstparty.d.ts +3 -0
- package/dist/commands/library-bindings-firstparty.d.ts.map +1 -0
- package/dist/commands/library-bindings-firstparty.js +2250 -0
- package/dist/commands/library-bindings-firstparty.js.map +1 -0
- package/dist/commands/restore.d.ts.map +1 -1
- package/dist/commands/restore.js +9 -8
- package/dist/commands/restore.js.map +1 -1
- package/dist/commands/restore.test.js +29 -0
- package/dist/commands/restore.test.js.map +1 -1
- package/dist/commands/run-build-regressions.test.js +72 -0
- package/dist/commands/run-build-regressions.test.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +1 -0
- package/dist/commands/run.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -2
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +57 -0
- package/dist/config.test.js.map +1 -1
- package/dist/dotnet/runtime-dlls.d.ts +1 -0
- package/dist/dotnet/runtime-dlls.d.ts.map +1 -1
- package/dist/dotnet/runtime-dlls.js +1 -0
- package/dist/dotnet/runtime-dlls.js.map +1 -1
- package/dist/surface/profiles.d.ts +10 -0
- package/dist/surface/profiles.d.ts.map +1 -0
- package/dist/surface/profiles.js +61 -0
- package/dist/surface/profiles.js.map +1 -0
- package/dist/surface/profiles.test.d.ts +2 -0
- package/dist/surface/profiles.test.d.ts.map +1 -0
- package/dist/surface/profiles.test.js +49 -0
- package/dist/surface/profiles.test.js.map +1 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { describe, it } from "mocha";
|
|
11
11
|
import { expect } from "chai";
|
|
12
|
-
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, symlinkSync, writeFileSync, } from "node:fs";
|
|
12
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, symlinkSync, writeFileSync, } from "node:fs";
|
|
13
13
|
import { spawnSync } from "node:child_process";
|
|
14
14
|
import { tmpdir } from "node:os";
|
|
15
15
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -171,6 +171,16 @@ describe("build command (library bindings)", function () {
|
|
|
171
171
|
expect(dbInternalContent).to.include("ExtensionMethods as __TsonicExt_Linq");
|
|
172
172
|
expect(dbInternalContent).to.include("ExtensionMethods as __TsonicExt_Tasks");
|
|
173
173
|
expect(dbInternalContent).to.match(/readonly\s+Numbers:\s+__TsonicExt_Tasks<__TsonicExt_Linq</);
|
|
174
|
+
const rootBindingsPath = join(bindingsDir, "Test.Lib", "bindings.json");
|
|
175
|
+
expect(existsSync(rootBindingsPath)).to.equal(true);
|
|
176
|
+
const rootBindings = JSON.parse(readFileSync(rootBindingsPath, "utf-8"));
|
|
177
|
+
expect(rootBindings.namespace).to.equal("Test.Lib");
|
|
178
|
+
expect(rootBindings.producer?.tool).to.equal("tsonic");
|
|
179
|
+
expect(rootBindings.producer?.mode).to.equal("aikya-firstparty");
|
|
180
|
+
expect(Object.keys(rootBindings.exports ?? {})).to.include("ok");
|
|
181
|
+
expect(Object.keys(rootBindings.exports ?? {})).to.include("err");
|
|
182
|
+
expect(Object.keys(rootBindings.exports ?? {})).to.include("loadConfig");
|
|
183
|
+
expect((rootBindings.types ?? []).some((entry) => entry.clrName === "Test.Lib.QueryHolder")).to.equal(true);
|
|
174
184
|
// Namespace facade for the "types" module must include TS-level aliases.
|
|
175
185
|
expect(typesContent).to.include("Tsonic source type aliases (generated)");
|
|
176
186
|
expect(typesContent).to.include("export type Id = string;");
|
|
@@ -209,6 +219,291 @@ describe("build command (library bindings)", function () {
|
|
|
209
219
|
rmSync(dir, { recursive: true, force: true });
|
|
210
220
|
}
|
|
211
221
|
});
|
|
222
|
+
it("fails fast when a library source uses default exports (unsupported for first-party namespace facades)", () => {
|
|
223
|
+
const dir = mkdtempSync(join(tmpdir(), "tsonic-lib-bindings-default-"));
|
|
224
|
+
try {
|
|
225
|
+
const wsConfigPath = join(dir, "tsonic.workspace.json");
|
|
226
|
+
mkdirSync(join(dir, "packages", "lib", "src"), { recursive: true });
|
|
227
|
+
mkdirSync(join(dir, "node_modules"), { recursive: true });
|
|
228
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "test", private: true, type: "module" }, null, 2) + "\n", "utf-8");
|
|
229
|
+
writeFileSync(wsConfigPath, JSON.stringify({
|
|
230
|
+
$schema: "https://tsonic.org/schema/workspace/v1.json",
|
|
231
|
+
dotnetVersion: "net10.0",
|
|
232
|
+
}, null, 2) + "\n", "utf-8");
|
|
233
|
+
writeFileSync(join(dir, "packages", "lib", "package.json"), JSON.stringify({
|
|
234
|
+
name: "lib",
|
|
235
|
+
private: true,
|
|
236
|
+
type: "module",
|
|
237
|
+
exports: {
|
|
238
|
+
"./package.json": "./package.json",
|
|
239
|
+
"./*.js": {
|
|
240
|
+
types: "./dist/tsonic/bindings/*.d.ts",
|
|
241
|
+
default: "./dist/tsonic/bindings/*.js",
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
}, null, 2) + "\n", "utf-8");
|
|
245
|
+
writeFileSync(join(dir, "packages", "lib", "tsonic.json"), JSON.stringify({
|
|
246
|
+
$schema: "https://tsonic.org/schema/v1.json",
|
|
247
|
+
rootNamespace: "Test.Lib",
|
|
248
|
+
entryPoint: "src/index.ts",
|
|
249
|
+
sourceRoot: "src",
|
|
250
|
+
outputDirectory: "generated",
|
|
251
|
+
outputName: "Test.Lib",
|
|
252
|
+
output: {
|
|
253
|
+
type: "library",
|
|
254
|
+
targetFrameworks: ["net10.0"],
|
|
255
|
+
nativeAot: false,
|
|
256
|
+
generateDocumentation: false,
|
|
257
|
+
includeSymbols: false,
|
|
258
|
+
packable: false,
|
|
259
|
+
},
|
|
260
|
+
}, null, 2) + "\n", "utf-8");
|
|
261
|
+
writeFileSync(join(dir, "packages", "lib", "src", "index.ts"), [`const value = 1;`, `export default value;`, ``].join("\n"), "utf-8");
|
|
262
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/dotnet"), join(dir, "node_modules/@tsonic/dotnet"));
|
|
263
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/core"), join(dir, "node_modules/@tsonic/core"));
|
|
264
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/globals"), join(dir, "node_modules/@tsonic/globals"));
|
|
265
|
+
const cliPath = join(repoRoot, "packages/cli/dist/index.js");
|
|
266
|
+
const result = spawnSync("node", [
|
|
267
|
+
cliPath,
|
|
268
|
+
"build",
|
|
269
|
+
"--project",
|
|
270
|
+
"lib",
|
|
271
|
+
"--config",
|
|
272
|
+
wsConfigPath,
|
|
273
|
+
"--quiet",
|
|
274
|
+
], { cwd: dir, encoding: "utf-8" });
|
|
275
|
+
expect(result.status).to.not.equal(0);
|
|
276
|
+
const combinedOutput = `${result.stderr}\n${result.stdout}`;
|
|
277
|
+
expect(combinedOutput).to.include("Unsupported default export");
|
|
278
|
+
expect(combinedOutput).to.include("First-party bindings generation");
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
rmSync(dir, { recursive: true, force: true });
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
it("fails fast when a library source re-exports from a non-local module specifier", () => {
|
|
285
|
+
const dir = mkdtempSync(join(tmpdir(), "tsonic-lib-bindings-reexport-"));
|
|
286
|
+
try {
|
|
287
|
+
const wsConfigPath = join(dir, "tsonic.workspace.json");
|
|
288
|
+
mkdirSync(join(dir, "packages", "lib", "src"), { recursive: true });
|
|
289
|
+
mkdirSync(join(dir, "node_modules"), { recursive: true });
|
|
290
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "test", private: true, type: "module" }, null, 2) + "\n", "utf-8");
|
|
291
|
+
writeFileSync(wsConfigPath, JSON.stringify({
|
|
292
|
+
$schema: "https://tsonic.org/schema/workspace/v1.json",
|
|
293
|
+
dotnetVersion: "net10.0",
|
|
294
|
+
}, null, 2) + "\n", "utf-8");
|
|
295
|
+
writeFileSync(join(dir, "packages", "lib", "package.json"), JSON.stringify({
|
|
296
|
+
name: "lib",
|
|
297
|
+
private: true,
|
|
298
|
+
type: "module",
|
|
299
|
+
exports: {
|
|
300
|
+
"./package.json": "./package.json",
|
|
301
|
+
"./*.js": {
|
|
302
|
+
types: "./dist/tsonic/bindings/*.d.ts",
|
|
303
|
+
default: "./dist/tsonic/bindings/*.js",
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
}, null, 2) + "\n", "utf-8");
|
|
307
|
+
writeFileSync(join(dir, "packages", "lib", "tsonic.json"), JSON.stringify({
|
|
308
|
+
$schema: "https://tsonic.org/schema/v1.json",
|
|
309
|
+
rootNamespace: "Test.Lib",
|
|
310
|
+
entryPoint: "src/index.ts",
|
|
311
|
+
sourceRoot: "src",
|
|
312
|
+
outputDirectory: "generated",
|
|
313
|
+
outputName: "Test.Lib",
|
|
314
|
+
output: {
|
|
315
|
+
type: "library",
|
|
316
|
+
targetFrameworks: ["net10.0"],
|
|
317
|
+
nativeAot: false,
|
|
318
|
+
generateDocumentation: false,
|
|
319
|
+
includeSymbols: false,
|
|
320
|
+
packable: false,
|
|
321
|
+
},
|
|
322
|
+
}, null, 2) + "\n", "utf-8");
|
|
323
|
+
writeFileSync(join(dir, "packages", "lib", "src", "index.ts"), [`export { Console } from "@tsonic/dotnet/System.js";`, ``].join("\n"), "utf-8");
|
|
324
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/dotnet"), join(dir, "node_modules/@tsonic/dotnet"));
|
|
325
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/core"), join(dir, "node_modules/@tsonic/core"));
|
|
326
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/globals"), join(dir, "node_modules/@tsonic/globals"));
|
|
327
|
+
const cliPath = join(repoRoot, "packages/cli/dist/index.js");
|
|
328
|
+
const result = spawnSync("node", [
|
|
329
|
+
cliPath,
|
|
330
|
+
"build",
|
|
331
|
+
"--project",
|
|
332
|
+
"lib",
|
|
333
|
+
"--config",
|
|
334
|
+
wsConfigPath,
|
|
335
|
+
"--quiet",
|
|
336
|
+
], { cwd: dir, encoding: "utf-8" });
|
|
337
|
+
expect(result.status).to.not.equal(0);
|
|
338
|
+
const combinedOutput = `${result.stderr}\n${result.stdout}`;
|
|
339
|
+
expect(combinedOutput).to.include("Unsupported re-export");
|
|
340
|
+
expect(combinedOutput).to.include("supports only relative re-exports from local source modules");
|
|
341
|
+
}
|
|
342
|
+
finally {
|
|
343
|
+
rmSync(dir, { recursive: true, force: true });
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
it("fails fast when a library source exports destructuring declarators", () => {
|
|
347
|
+
const dir = mkdtempSync(join(tmpdir(), "tsonic-lib-bindings-destructure-"));
|
|
348
|
+
try {
|
|
349
|
+
const wsConfigPath = join(dir, "tsonic.workspace.json");
|
|
350
|
+
mkdirSync(join(dir, "packages", "lib", "src"), { recursive: true });
|
|
351
|
+
mkdirSync(join(dir, "node_modules"), { recursive: true });
|
|
352
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "test", private: true, type: "module" }, null, 2) + "\n", "utf-8");
|
|
353
|
+
writeFileSync(wsConfigPath, JSON.stringify({
|
|
354
|
+
$schema: "https://tsonic.org/schema/workspace/v1.json",
|
|
355
|
+
dotnetVersion: "net10.0",
|
|
356
|
+
}, null, 2) + "\n", "utf-8");
|
|
357
|
+
writeFileSync(join(dir, "packages", "lib", "package.json"), JSON.stringify({
|
|
358
|
+
name: "lib",
|
|
359
|
+
private: true,
|
|
360
|
+
type: "module",
|
|
361
|
+
exports: {
|
|
362
|
+
"./package.json": "./package.json",
|
|
363
|
+
"./*.js": {
|
|
364
|
+
types: "./dist/tsonic/bindings/*.d.ts",
|
|
365
|
+
default: "./dist/tsonic/bindings/*.js",
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
}, null, 2) + "\n", "utf-8");
|
|
369
|
+
writeFileSync(join(dir, "packages", "lib", "tsonic.json"), JSON.stringify({
|
|
370
|
+
$schema: "https://tsonic.org/schema/v1.json",
|
|
371
|
+
rootNamespace: "Test.Lib",
|
|
372
|
+
entryPoint: "src/index.ts",
|
|
373
|
+
sourceRoot: "src",
|
|
374
|
+
outputDirectory: "generated",
|
|
375
|
+
outputName: "Test.Lib",
|
|
376
|
+
output: {
|
|
377
|
+
type: "library",
|
|
378
|
+
targetFrameworks: ["net10.0"],
|
|
379
|
+
nativeAot: false,
|
|
380
|
+
generateDocumentation: false,
|
|
381
|
+
includeSymbols: false,
|
|
382
|
+
packable: false,
|
|
383
|
+
},
|
|
384
|
+
}, null, 2) + "\n", "utf-8");
|
|
385
|
+
writeFileSync(join(dir, "packages", "lib", "src", "index.ts"), [
|
|
386
|
+
`const source = { value: 1 };`,
|
|
387
|
+
`export const { value } = source;`,
|
|
388
|
+
``,
|
|
389
|
+
].join("\n"), "utf-8");
|
|
390
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/dotnet"), join(dir, "node_modules/@tsonic/dotnet"));
|
|
391
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/core"), join(dir, "node_modules/@tsonic/core"));
|
|
392
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/globals"), join(dir, "node_modules/@tsonic/globals"));
|
|
393
|
+
const cliPath = join(repoRoot, "packages/cli/dist/index.js");
|
|
394
|
+
const result = spawnSync("node", [
|
|
395
|
+
cliPath,
|
|
396
|
+
"build",
|
|
397
|
+
"--project",
|
|
398
|
+
"lib",
|
|
399
|
+
"--config",
|
|
400
|
+
wsConfigPath,
|
|
401
|
+
"--quiet",
|
|
402
|
+
], { cwd: dir, encoding: "utf-8" });
|
|
403
|
+
expect(result.status).to.not.equal(0);
|
|
404
|
+
const combinedOutput = `${result.stderr}\n${result.stdout}`;
|
|
405
|
+
expect(combinedOutput).to.include("Unsupported exported variable declarator");
|
|
406
|
+
expect(combinedOutput).to.include("requires identifier-based exported variables");
|
|
407
|
+
}
|
|
408
|
+
finally {
|
|
409
|
+
rmSync(dir, { recursive: true, force: true });
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
it("resolves local re-export chains transitively for both types and values", () => {
|
|
413
|
+
const dir = mkdtempSync(join(tmpdir(), "tsonic-lib-bindings-chain-"));
|
|
414
|
+
try {
|
|
415
|
+
const wsConfigPath = join(dir, "tsonic.workspace.json");
|
|
416
|
+
mkdirSync(join(dir, "packages", "lib", "src", "config"), {
|
|
417
|
+
recursive: true,
|
|
418
|
+
});
|
|
419
|
+
mkdirSync(join(dir, "node_modules"), { recursive: true });
|
|
420
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "test", private: true, type: "module" }, null, 2) + "\n", "utf-8");
|
|
421
|
+
writeFileSync(wsConfigPath, JSON.stringify({
|
|
422
|
+
$schema: "https://tsonic.org/schema/workspace/v1.json",
|
|
423
|
+
dotnetVersion: "net10.0",
|
|
424
|
+
}, null, 2) + "\n", "utf-8");
|
|
425
|
+
writeFileSync(join(dir, "packages", "lib", "package.json"), JSON.stringify({
|
|
426
|
+
name: "lib",
|
|
427
|
+
private: true,
|
|
428
|
+
type: "module",
|
|
429
|
+
exports: {
|
|
430
|
+
"./package.json": "./package.json",
|
|
431
|
+
"./*.js": {
|
|
432
|
+
types: "./dist/tsonic/bindings/*.d.ts",
|
|
433
|
+
default: "./dist/tsonic/bindings/*.js",
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
}, null, 2) + "\n", "utf-8");
|
|
437
|
+
writeFileSync(join(dir, "packages", "lib", "tsonic.json"), JSON.stringify({
|
|
438
|
+
$schema: "https://tsonic.org/schema/v1.json",
|
|
439
|
+
rootNamespace: "Test.Lib",
|
|
440
|
+
entryPoint: "src/index.ts",
|
|
441
|
+
sourceRoot: "src",
|
|
442
|
+
outputDirectory: "generated",
|
|
443
|
+
outputName: "Test.Lib",
|
|
444
|
+
output: {
|
|
445
|
+
type: "library",
|
|
446
|
+
targetFrameworks: ["net10.0"],
|
|
447
|
+
nativeAot: false,
|
|
448
|
+
generateDocumentation: false,
|
|
449
|
+
includeSymbols: false,
|
|
450
|
+
packable: false,
|
|
451
|
+
},
|
|
452
|
+
}, null, 2) + "\n", "utf-8");
|
|
453
|
+
writeFileSync(join(dir, "packages", "lib", "src", "config", "loaded-config.ts"), [`export interface LoadedConfig {`, ` title: string;`, `}`, ``].join("\n"), "utf-8");
|
|
454
|
+
writeFileSync(join(dir, "packages", "lib", "src", "config", "loader.ts"), [
|
|
455
|
+
`import type { LoadedConfig } from "./loaded-config.ts";`,
|
|
456
|
+
``,
|
|
457
|
+
`export function loadSiteConfig(): LoadedConfig {`,
|
|
458
|
+
` return { title: "site" };`,
|
|
459
|
+
`}`,
|
|
460
|
+
``,
|
|
461
|
+
].join("\n"), "utf-8");
|
|
462
|
+
writeFileSync(join(dir, "packages", "lib", "src", "config", "index.ts"), [
|
|
463
|
+
`export type { LoadedConfig } from "./loaded-config.ts";`,
|
|
464
|
+
`export { loadSiteConfig } from "./loader.ts";`,
|
|
465
|
+
``,
|
|
466
|
+
].join("\n"), "utf-8");
|
|
467
|
+
writeFileSync(join(dir, "packages", "lib", "src", "config.ts"), [
|
|
468
|
+
`import type { LoadedConfig as LoadedConfigLocal } from "./config/index.ts";`,
|
|
469
|
+
`import { loadSiteConfig as loadSiteConfigLocal } from "./config/index.ts";`,
|
|
470
|
+
``,
|
|
471
|
+
`export type { LoadedConfigLocal as LoadedConfig };`,
|
|
472
|
+
`export { loadSiteConfigLocal as loadSiteConfig };`,
|
|
473
|
+
``,
|
|
474
|
+
].join("\n"), "utf-8");
|
|
475
|
+
writeFileSync(join(dir, "packages", "lib", "src", "index.ts"), [
|
|
476
|
+
`export type { LoadedConfig } from "./config.ts";`,
|
|
477
|
+
`export { loadSiteConfig } from "./config.ts";`,
|
|
478
|
+
``,
|
|
479
|
+
].join("\n"), "utf-8");
|
|
480
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/dotnet"), join(dir, "node_modules/@tsonic/dotnet"));
|
|
481
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/core"), join(dir, "node_modules/@tsonic/core"));
|
|
482
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/globals"), join(dir, "node_modules/@tsonic/globals"));
|
|
483
|
+
const cliPath = join(repoRoot, "packages/cli/dist/index.js");
|
|
484
|
+
const result = spawnSync("node", [
|
|
485
|
+
cliPath,
|
|
486
|
+
"build",
|
|
487
|
+
"--project",
|
|
488
|
+
"lib",
|
|
489
|
+
"--config",
|
|
490
|
+
wsConfigPath,
|
|
491
|
+
"--quiet",
|
|
492
|
+
], { cwd: dir, encoding: "utf-8" });
|
|
493
|
+
expect(result.status).to.equal(0, result.stderr || result.stdout);
|
|
494
|
+
const bindingsRoot = join(dir, "packages", "lib", "dist", "tsonic", "bindings");
|
|
495
|
+
const facade = readFileSync(join(bindingsRoot, "Test.Lib.d.ts"), "utf-8");
|
|
496
|
+
const internal = readFileSync(join(bindingsRoot, "Test.Lib", "internal", "index.d.ts"), "utf-8");
|
|
497
|
+
const rootBindings = JSON.parse(readFileSync(join(bindingsRoot, "Test.Lib", "bindings.json"), "utf-8"));
|
|
498
|
+
expect(facade).to.include("export type { LoadedConfig }");
|
|
499
|
+
expect(facade).to.match(/export declare function loadSiteConfig\(\):\s*LoadedConfig/);
|
|
500
|
+
expect(internal).to.match(/interface\s+LoadedConfig\$instance/);
|
|
501
|
+
expect(rootBindings.exports?.loadSiteConfig?.kind).to.equal("method");
|
|
502
|
+
}
|
|
503
|
+
finally {
|
|
504
|
+
rmSync(dir, { recursive: true, force: true });
|
|
505
|
+
}
|
|
506
|
+
});
|
|
212
507
|
it("preserves source-level optional/interface/discriminated typing across library bindings", () => {
|
|
213
508
|
const dir = mkdtempSync(join(tmpdir(), "tsonic-lib-bindings-source-"));
|
|
214
509
|
try {
|
|
@@ -305,6 +600,27 @@ describe("build command (library bindings)", function () {
|
|
|
305
600
|
writeFileSync(join(dir, "packages", "core", "src", "contracts.ts"), [
|
|
306
601
|
`import type { int } from "@tsonic/core/types.js";`,
|
|
307
602
|
``,
|
|
603
|
+
`export class BuildRequest {`,
|
|
604
|
+
` destinationDir: string = "";`,
|
|
605
|
+
` buildDrafts: boolean = false;`,
|
|
606
|
+
`}`,
|
|
607
|
+
``,
|
|
608
|
+
`export class ServeRequest extends BuildRequest {`,
|
|
609
|
+
` host: string = "127.0.0.1";`,
|
|
610
|
+
`}`,
|
|
611
|
+
``,
|
|
612
|
+
`export interface ContractBase {`,
|
|
613
|
+
` requestId: string;`,
|
|
614
|
+
`}`,
|
|
615
|
+
``,
|
|
616
|
+
`export interface ContractDerived extends ContractBase {`,
|
|
617
|
+
` payload: string;`,
|
|
618
|
+
`}`,
|
|
619
|
+
``,
|
|
620
|
+
`export function getRequestId(contract: ContractDerived): string {`,
|
|
621
|
+
` return contract.requestId;`,
|
|
622
|
+
`}`,
|
|
623
|
+
``,
|
|
308
624
|
`export class Entity {`,
|
|
309
625
|
` Maybe?: int;`,
|
|
310
626
|
`}`,
|
|
@@ -320,18 +636,30 @@ describe("build command (library bindings)", function () {
|
|
|
320
636
|
writeFileSync(join(dir, "packages", "core", "src", "index.ts"), [
|
|
321
637
|
`export type { Ok, Err, Result } from "./types.ts";`,
|
|
322
638
|
`export { ok, err, renderMarkdownDomain } from "./types.ts";`,
|
|
323
|
-
`export { Entity, dispatch } from "./contracts.ts";`,
|
|
324
|
-
`export type { DomainEvent } from "./contracts.ts";`,
|
|
639
|
+
`export { BuildRequest, ServeRequest, getRequestId, Entity, dispatch } from "./contracts.ts";`,
|
|
640
|
+
`export type { ContractBase, ContractDerived, DomainEvent } from "./contracts.ts";`,
|
|
325
641
|
``,
|
|
326
642
|
].join("\n"), "utf-8");
|
|
327
643
|
writeFileSync(join(dir, "packages", "app", "src", "App.ts"), [
|
|
328
644
|
`import type { int } from "@tsonic/core/types.js";`,
|
|
329
|
-
`import { Entity, dispatch, renderMarkdownDomain, err } from "@acme/core/Acme.Core.js";`,
|
|
645
|
+
`import { Entity, ServeRequest, getRequestId, dispatch, renderMarkdownDomain, err } from "@acme/core/Acme.Core.js";`,
|
|
646
|
+
`import type { ContractDerived } from "@acme/core/Acme.Core.js";`,
|
|
330
647
|
``,
|
|
331
648
|
`const entity = new Entity();`,
|
|
332
649
|
`const maybe: int | undefined = undefined;`,
|
|
333
650
|
`entity.Maybe = maybe;`,
|
|
334
651
|
``,
|
|
652
|
+
`const serveReq = new ServeRequest();`,
|
|
653
|
+
`serveReq.destinationDir = "out";`,
|
|
654
|
+
`serveReq.buildDrafts = true;`,
|
|
655
|
+
`serveReq.host = "localhost";`,
|
|
656
|
+
``,
|
|
657
|
+
`const contract: ContractDerived = { requestId: "r-1", payload: "ok" };`,
|
|
658
|
+
`const requestId = getRequestId(contract);`,
|
|
659
|
+
`if (requestId.Length === 0) {`,
|
|
660
|
+
` err("missing request id");`,
|
|
661
|
+
`}`,
|
|
662
|
+
``,
|
|
335
663
|
`const eventData: Record<string, unknown> = { id: "evt-1" };`,
|
|
336
664
|
`dispatch({ type: "evt", data: eventData });`,
|
|
337
665
|
``,
|
|
@@ -371,6 +699,14 @@ describe("build command (library bindings)", function () {
|
|
|
371
699
|
"--quiet",
|
|
372
700
|
], { cwd: dir, encoding: "utf-8" });
|
|
373
701
|
expect(buildApp.status, buildApp.stderr || buildApp.stdout).to.equal(0);
|
|
702
|
+
const rootBindingsPath = join(dir, "packages", "core", "dist", "tsonic", "bindings", "Acme.Core", "bindings.json");
|
|
703
|
+
expect(existsSync(rootBindingsPath)).to.equal(true);
|
|
704
|
+
const rootBindings = JSON.parse(readFileSync(rootBindingsPath, "utf-8"));
|
|
705
|
+
expect(rootBindings.producer?.tool).to.equal("tsonic");
|
|
706
|
+
expect(rootBindings.producer?.mode).to.equal("aikya-firstparty");
|
|
707
|
+
expect(Object.keys(rootBindings.exports ?? {})).to.include("renderMarkdownDomain");
|
|
708
|
+
expect(Object.keys(rootBindings.exports ?? {})).to.include("dispatch");
|
|
709
|
+
expect((rootBindings.types ?? []).some((t) => t.clrName === "Acme.Core.Entity")).to.equal(true);
|
|
374
710
|
const coreTypesFacade = readFileSync(join(dir, "packages", "core", "dist", "tsonic", "bindings", "Acme.Core.d.ts"), "utf-8");
|
|
375
711
|
expect(coreTypesFacade).to.include("export type Result<T, E = string> = Ok<T> | Err<E>;");
|
|
376
712
|
expect(coreTypesFacade).to.include("export type Ok<T> =");
|
|
@@ -390,9 +726,545 @@ describe("build command (library bindings)", function () {
|
|
|
390
726
|
}
|
|
391
727
|
});
|
|
392
728
|
expect(entityInternalPath).to.not.equal(undefined);
|
|
729
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
393
730
|
const coreEntitiesInternal = readFileSync(entityInternalPath, "utf-8");
|
|
394
|
-
expect(coreEntitiesInternal).to.match(/set Maybe\(value: [^)]+undefined\)\s
|
|
731
|
+
expect(coreEntitiesInternal).to.match(/(set Maybe\(value: [^)]+undefined\)\s*;|Maybe: [^;]+undefined\s*;)/);
|
|
395
732
|
expect(coreEntitiesInternal).to.include("data: Record<string, unknown>");
|
|
733
|
+
expect(coreEntitiesInternal).to.match(/interface\s+ServeRequest\$instance\s+extends\s+BuildRequest/);
|
|
734
|
+
expect(coreEntitiesInternal).to.match(/interface\s+ContractDerived\$instance\s+extends\s+ContractBase/);
|
|
735
|
+
}
|
|
736
|
+
finally {
|
|
737
|
+
rmSync(dir, { recursive: true, force: true });
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
it("preserves Maximus lowered type/value surfaces across dependency bindings", () => {
|
|
741
|
+
const dir = mkdtempSync(join(tmpdir(), "tsonic-lib-bindings-maximus-"));
|
|
742
|
+
try {
|
|
743
|
+
const wsConfigPath = join(dir, "tsonic.workspace.json");
|
|
744
|
+
mkdirSync(join(dir, "packages", "core", "src"), { recursive: true });
|
|
745
|
+
mkdirSync(join(dir, "packages", "app", "src"), { recursive: true });
|
|
746
|
+
mkdirSync(join(dir, "node_modules"), { recursive: true });
|
|
747
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify({
|
|
748
|
+
name: "test",
|
|
749
|
+
private: true,
|
|
750
|
+
type: "module",
|
|
751
|
+
workspaces: ["packages/*"],
|
|
752
|
+
}, null, 2) + "\n", "utf-8");
|
|
753
|
+
writeFileSync(wsConfigPath, JSON.stringify({
|
|
754
|
+
$schema: "https://tsonic.org/schema/workspace/v1.json",
|
|
755
|
+
dotnetVersion: "net10.0",
|
|
756
|
+
}, null, 2) + "\n", "utf-8");
|
|
757
|
+
writeFileSync(join(dir, "packages", "core", "package.json"), JSON.stringify({
|
|
758
|
+
name: "@acme/core",
|
|
759
|
+
private: true,
|
|
760
|
+
type: "module",
|
|
761
|
+
exports: {
|
|
762
|
+
"./package.json": "./package.json",
|
|
763
|
+
"./*.js": {
|
|
764
|
+
types: "./dist/tsonic/bindings/*.d.ts",
|
|
765
|
+
default: "./dist/tsonic/bindings/*.js",
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
}, null, 2) + "\n", "utf-8");
|
|
769
|
+
writeFileSync(join(dir, "packages", "app", "package.json"), JSON.stringify({
|
|
770
|
+
name: "@acme/app",
|
|
771
|
+
private: true,
|
|
772
|
+
type: "module",
|
|
773
|
+
dependencies: {
|
|
774
|
+
"@acme/core": "workspace:*",
|
|
775
|
+
},
|
|
776
|
+
}, null, 2) + "\n", "utf-8");
|
|
777
|
+
writeFileSync(join(dir, "packages", "core", "tsonic.json"), JSON.stringify({
|
|
778
|
+
$schema: "https://tsonic.org/schema/v1.json",
|
|
779
|
+
rootNamespace: "Acme.Core",
|
|
780
|
+
entryPoint: "src/index.ts",
|
|
781
|
+
sourceRoot: "src",
|
|
782
|
+
outputDirectory: "generated",
|
|
783
|
+
outputName: "Acme.Core",
|
|
784
|
+
output: {
|
|
785
|
+
type: "library",
|
|
786
|
+
targetFrameworks: ["net10.0"],
|
|
787
|
+
nativeAot: false,
|
|
788
|
+
generateDocumentation: false,
|
|
789
|
+
includeSymbols: false,
|
|
790
|
+
packable: false,
|
|
791
|
+
},
|
|
792
|
+
}, null, 2) + "\n", "utf-8");
|
|
793
|
+
writeFileSync(join(dir, "packages", "app", "tsonic.json"), JSON.stringify({
|
|
794
|
+
$schema: "https://tsonic.org/schema/v1.json",
|
|
795
|
+
rootNamespace: "Acme.App",
|
|
796
|
+
entryPoint: "src/App.ts",
|
|
797
|
+
sourceRoot: "src",
|
|
798
|
+
references: {
|
|
799
|
+
libraries: [
|
|
800
|
+
"../core/generated/bin/Release/net10.0/Acme.Core.dll",
|
|
801
|
+
],
|
|
802
|
+
},
|
|
803
|
+
outputDirectory: "generated",
|
|
804
|
+
outputName: "Acme.App",
|
|
805
|
+
output: {
|
|
806
|
+
type: "executable",
|
|
807
|
+
targetFrameworks: ["net10.0"],
|
|
808
|
+
nativeAot: false,
|
|
809
|
+
generateDocumentation: false,
|
|
810
|
+
includeSymbols: false,
|
|
811
|
+
packable: false,
|
|
812
|
+
},
|
|
813
|
+
}, null, 2) + "\n", "utf-8");
|
|
814
|
+
writeFileSync(join(dir, "packages", "core", "src", "types.ts"), [
|
|
815
|
+
`import type { int } from "@tsonic/core/types.js";`,
|
|
816
|
+
``,
|
|
817
|
+
`export type User = { name: string; age: int };`,
|
|
818
|
+
`export type UserFlags = { [K in keyof User]?: boolean };`,
|
|
819
|
+
`export type UserReadonly = Readonly<User>;`,
|
|
820
|
+
`export type UserPartial = Partial<User>;`,
|
|
821
|
+
`export type UserRequired = Required<UserFlags>;`,
|
|
822
|
+
`export type UserPick = Pick<User, "name">;`,
|
|
823
|
+
`export type UserOmit = Omit<User, "age">;`,
|
|
824
|
+
`export type Box<T> = { value: T };`,
|
|
825
|
+
`export type BoxReadonly<T> = Readonly<Box<T>>;`,
|
|
826
|
+
`export type BoxPartial<T> = Partial<Box<T>>;`,
|
|
827
|
+
`export type BoxRequired<T> = Required<BoxPartial<T>>;`,
|
|
828
|
+
`export type Mutable<T> = { -readonly [K in keyof T]: T[K] };`,
|
|
829
|
+
`export type Head<T extends readonly unknown[]> = T extends readonly [infer H, ...unknown[]] ? H : never;`,
|
|
830
|
+
`export type Tail<T extends readonly unknown[]> = T extends readonly [unknown, ...infer R] ? R : never;`,
|
|
831
|
+
`export type Last<T extends readonly unknown[]> = T extends readonly [...unknown[], infer L] ? L : never;`,
|
|
832
|
+
`export type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;`,
|
|
833
|
+
`export type AsyncValue<T> = T extends Promise<infer U> ? U : T;`,
|
|
834
|
+
`export type AwaitedScore = Awaited<Promise<int>>;`,
|
|
835
|
+
`export type SuccessResult<T> = Extract<{ ok: true; value: T } | { ok: false; error: string }, { ok: true }>;`,
|
|
836
|
+
`export type FailureResult = Exclude<{ ok: true; value: int } | { ok: false; error: string }, { ok: true; value: int }>;`,
|
|
837
|
+
`export type NonNullName = NonNullable<string | null | undefined>;`,
|
|
838
|
+
`export type ExtractStatus = Extract<"ok" | "err", "ok">;`,
|
|
839
|
+
`export type ExcludeStatus = Exclude<"ok" | "err", "err">;`,
|
|
840
|
+
`export type UserAndMeta = User & { id: string };`,
|
|
841
|
+
`export type PrefixSuffix<T extends unknown[]> = [string, ...T, boolean];`,
|
|
842
|
+
`export type UserTuple = [name: string, age: int];`,
|
|
843
|
+
`export type UserTupleSpread<T extends unknown[]> = [User, ...T, boolean];`,
|
|
844
|
+
`export type EventPayload = { kind: "click"; x: int; y: int } | { kind: "keyup"; key: string };`,
|
|
845
|
+
`export type ClickPayload = Extract<EventPayload, { kind: "click" }>;`,
|
|
846
|
+
`export type ApiUserRoute = "/api/users";`,
|
|
847
|
+
`export type ApiPostRoute = "/api/posts";`,
|
|
848
|
+
`export type RoutePair = [ApiUserRoute, ApiPostRoute];`,
|
|
849
|
+
`export type PairJoin<A, B> = [A, B];`,
|
|
850
|
+
`export type Mapper<T> = (value: T) => T;`,
|
|
851
|
+
`export type MapperParams = Parameters<Mapper<User>>;`,
|
|
852
|
+
`export type MapperResult = ReturnType<Mapper<User>>;`,
|
|
853
|
+
`export type SymbolScores = Record<symbol, int>;`,
|
|
854
|
+
``,
|
|
855
|
+
`export class UserRecord {`,
|
|
856
|
+
` constructor(public name: string, public age: int) {}`,
|
|
857
|
+
`}`,
|
|
858
|
+
``,
|
|
859
|
+
`export type UserRecordCtorArgs = ConstructorParameters<typeof UserRecord>;`,
|
|
860
|
+
`export type UserRecordInstance = InstanceType<typeof UserRecord>;`,
|
|
861
|
+
`export type ConstructorArgs = ConstructorParameters<typeof UserRecord>;`,
|
|
862
|
+
`export type RecordInstance = InstanceType<typeof UserRecord>;`,
|
|
863
|
+
``,
|
|
864
|
+
`export const id = <T>(value: T): T => value;`,
|
|
865
|
+
``,
|
|
866
|
+
`export function projectFlags(user: User): UserFlags {`,
|
|
867
|
+
` return { name: user.name.Length > 0, age: user.age > 0 };`,
|
|
868
|
+
`}`,
|
|
869
|
+
``,
|
|
870
|
+
`export function lookupScore(scores: SymbolScores, key: symbol): int {`,
|
|
871
|
+
` return scores[key] ?? 0;`,
|
|
872
|
+
`}`,
|
|
873
|
+
``,
|
|
874
|
+
`export function invokeMapper<T>(value: T, mapper: Mapper<T>): T {`,
|
|
875
|
+
` return mapper(value);`,
|
|
876
|
+
`}`,
|
|
877
|
+
``,
|
|
878
|
+
`export function createBox<T>(value: T): Box<T> {`,
|
|
879
|
+
` return { value };`,
|
|
880
|
+
`}`,
|
|
881
|
+
``,
|
|
882
|
+
`export function toRoute(path: "users" | "posts"): ApiUserRoute | ApiPostRoute {`,
|
|
883
|
+
` return path === "users" ? "/api/users" : "/api/posts";`,
|
|
884
|
+
`}`,
|
|
885
|
+
``,
|
|
886
|
+
`export function projectEvent(payload: EventPayload): PairJoin<string, EventPayload> {`,
|
|
887
|
+
` return ["evt", payload];`,
|
|
888
|
+
`}`,
|
|
889
|
+
``,
|
|
890
|
+
`export function createUserTuple(user: User): UserTuple {`,
|
|
891
|
+
` return [user.name, user.age];`,
|
|
892
|
+
`}`,
|
|
893
|
+
``,
|
|
894
|
+
].join("\n"), "utf-8");
|
|
895
|
+
writeFileSync(join(dir, "packages", "core", "src", "runtime.ts"), [
|
|
896
|
+
`import type { int } from "@tsonic/core/types.js";`,
|
|
897
|
+
``,
|
|
898
|
+
`export function chainScore(seed: Promise<int>): Promise<int> {`,
|
|
899
|
+
` return seed`,
|
|
900
|
+
` .then((value) => value + 1)`,
|
|
901
|
+
` .catch((_error) => 0)`,
|
|
902
|
+
` .finally(() => {});`,
|
|
903
|
+
`}`,
|
|
904
|
+
``,
|
|
905
|
+
`export async function loadSideEffects(): Promise<void> {`,
|
|
906
|
+
` await import("./side-effect.ts");`,
|
|
907
|
+
`}`,
|
|
908
|
+
``,
|
|
909
|
+
`export function* nextValues(start: int): Generator<int, int, int> {`,
|
|
910
|
+
` const next = (yield start) + 1;`,
|
|
911
|
+
` yield next;`,
|
|
912
|
+
` return next + 1;`,
|
|
913
|
+
`}`,
|
|
914
|
+
``,
|
|
915
|
+
].join("\n"), "utf-8");
|
|
916
|
+
writeFileSync(join(dir, "packages", "core", "src", "side-effect.ts"), [`export const loaded = true;`, ``].join("\n"), "utf-8");
|
|
917
|
+
writeFileSync(join(dir, "packages", "core", "src", "index.ts"), [
|
|
918
|
+
`export type {`,
|
|
919
|
+
` User,`,
|
|
920
|
+
` UserFlags,`,
|
|
921
|
+
` UserReadonly,`,
|
|
922
|
+
` UserPartial,`,
|
|
923
|
+
` UserRequired,`,
|
|
924
|
+
` UserPick,`,
|
|
925
|
+
` UserOmit,`,
|
|
926
|
+
` UnwrapPromise,`,
|
|
927
|
+
` NonNullName,`,
|
|
928
|
+
` ExtractStatus,`,
|
|
929
|
+
` ExcludeStatus,`,
|
|
930
|
+
` UserAndMeta,`,
|
|
931
|
+
` PrefixSuffix,`,
|
|
932
|
+
` Box,`,
|
|
933
|
+
` BoxReadonly,`,
|
|
934
|
+
` Mutable,`,
|
|
935
|
+
` Head,`,
|
|
936
|
+
` Tail,`,
|
|
937
|
+
` Last,`,
|
|
938
|
+
` AsyncValue,`,
|
|
939
|
+
` AwaitedScore,`,
|
|
940
|
+
` SuccessResult,`,
|
|
941
|
+
` FailureResult,`,
|
|
942
|
+
` UserTuple,`,
|
|
943
|
+
` UserTupleSpread,`,
|
|
944
|
+
` EventPayload,`,
|
|
945
|
+
` ClickPayload,`,
|
|
946
|
+
` ApiUserRoute,`,
|
|
947
|
+
` ApiPostRoute,`,
|
|
948
|
+
` RoutePair,`,
|
|
949
|
+
` PairJoin,`,
|
|
950
|
+
` Mapper,`,
|
|
951
|
+
` MapperParams,`,
|
|
952
|
+
` MapperResult,`,
|
|
953
|
+
` SymbolScores,`,
|
|
954
|
+
` UserRecordCtorArgs,`,
|
|
955
|
+
` UserRecordInstance,`,
|
|
956
|
+
` ConstructorArgs,`,
|
|
957
|
+
` RecordInstance,`,
|
|
958
|
+
`} from "./types.ts";`,
|
|
959
|
+
`export {`,
|
|
960
|
+
` id,`,
|
|
961
|
+
` UserRecord,`,
|
|
962
|
+
` projectFlags,`,
|
|
963
|
+
` lookupScore,`,
|
|
964
|
+
` invokeMapper,`,
|
|
965
|
+
` createBox,`,
|
|
966
|
+
` toRoute,`,
|
|
967
|
+
` projectEvent,`,
|
|
968
|
+
` createUserTuple,`,
|
|
969
|
+
`} from "./types.ts";`,
|
|
970
|
+
`export { chainScore, loadSideEffects, nextValues } from "./runtime.ts";`,
|
|
971
|
+
``,
|
|
972
|
+
].join("\n"), "utf-8");
|
|
973
|
+
writeFileSync(join(dir, "packages", "app", "src", "App.ts"), [
|
|
974
|
+
`import type { int } from "@tsonic/core/types.js";`,
|
|
975
|
+
`import { Console } from "@tsonic/dotnet/System.js";`,
|
|
976
|
+
`import type {`,
|
|
977
|
+
` User,`,
|
|
978
|
+
` UserFlags,`,
|
|
979
|
+
` UserReadonly,`,
|
|
980
|
+
` UserPartial,`,
|
|
981
|
+
` UserRequired,`,
|
|
982
|
+
` UserPick,`,
|
|
983
|
+
` UserOmit,`,
|
|
984
|
+
` UnwrapPromise,`,
|
|
985
|
+
` NonNullName,`,
|
|
986
|
+
` ExtractStatus,`,
|
|
987
|
+
` ExcludeStatus,`,
|
|
988
|
+
` UserAndMeta,`,
|
|
989
|
+
` PrefixSuffix,`,
|
|
990
|
+
` Mapper,`,
|
|
991
|
+
` Box,`,
|
|
992
|
+
` BoxReadonly,`,
|
|
993
|
+
` BoxPartial,`,
|
|
994
|
+
` BoxRequired,`,
|
|
995
|
+
` Mutable,`,
|
|
996
|
+
` Head,`,
|
|
997
|
+
` Tail,`,
|
|
998
|
+
` Last,`,
|
|
999
|
+
` AsyncValue,`,
|
|
1000
|
+
` AwaitedScore,`,
|
|
1001
|
+
` SuccessResult,`,
|
|
1002
|
+
` FailureResult,`,
|
|
1003
|
+
` UserTuple,`,
|
|
1004
|
+
` UserTupleSpread,`,
|
|
1005
|
+
` EventPayload,`,
|
|
1006
|
+
` ClickPayload,`,
|
|
1007
|
+
` ApiUserRoute,`,
|
|
1008
|
+
` ApiPostRoute,`,
|
|
1009
|
+
` RoutePair,`,
|
|
1010
|
+
` PairJoin,`,
|
|
1011
|
+
` UserRecordCtorArgs,`,
|
|
1012
|
+
` UserRecordInstance,`,
|
|
1013
|
+
` ConstructorArgs,`,
|
|
1014
|
+
` RecordInstance,`,
|
|
1015
|
+
`} from "@acme/core/Acme.Core.js";`,
|
|
1016
|
+
`import {`,
|
|
1017
|
+
` id,`,
|
|
1018
|
+
` UserRecord,`,
|
|
1019
|
+
` projectFlags,`,
|
|
1020
|
+
` invokeMapper,`,
|
|
1021
|
+
` createBox,`,
|
|
1022
|
+
` toRoute,`,
|
|
1023
|
+
` projectEvent,`,
|
|
1024
|
+
` createUserTuple,`,
|
|
1025
|
+
`} from "@acme/core/Acme.Core.js";`,
|
|
1026
|
+
``,
|
|
1027
|
+
`const copied = id<int>(7);`,
|
|
1028
|
+
`const copyAlias = id;`,
|
|
1029
|
+
`const copiedAgain = copyAlias<int>(copied);`,
|
|
1030
|
+
``,
|
|
1031
|
+
`const ctorArgs: UserRecordCtorArgs = ["Ada", copiedAgain];`,
|
|
1032
|
+
`void ctorArgs;`,
|
|
1033
|
+
`const user: UserRecordInstance = new UserRecord("Ada", copiedAgain);`,
|
|
1034
|
+
`const userView: User = { name: user.name, age: user.age };`,
|
|
1035
|
+
`const flags = userView as unknown as UserFlags;`,
|
|
1036
|
+
`const score: int = copiedAgain;`,
|
|
1037
|
+
`const route = toRoute("users");`,
|
|
1038
|
+
`const tupleFromFn = createUserTuple(userView);`,
|
|
1039
|
+
`const boxUser = createBox(userView);`,
|
|
1040
|
+
``,
|
|
1041
|
+
`type ProbeBox = Box<User>;`,
|
|
1042
|
+
`type ProbeBoxReadonly = BoxReadonly<User>;`,
|
|
1043
|
+
`type ProbeBoxPartial = BoxPartial<User>;`,
|
|
1044
|
+
`type ProbeBoxRequired = BoxRequired<User>;`,
|
|
1045
|
+
`type ProbeMutable = Mutable<UserReadonly>;`,
|
|
1046
|
+
`type ProbeHead = Head<[int, string]>;`,
|
|
1047
|
+
`type ProbeTail = Tail<[string, int, boolean]>;`,
|
|
1048
|
+
`type ProbeLast = Last<[string, int, boolean]>;`,
|
|
1049
|
+
`type ProbeAsync = AsyncValue<Promise<int>>;`,
|
|
1050
|
+
`type ProbeAwaited = AwaitedScore;`,
|
|
1051
|
+
`type ProbeSuccess = SuccessResult<int>;`,
|
|
1052
|
+
`type ProbeFailure = FailureResult;`,
|
|
1053
|
+
`type ProbeTuple = UserTuple;`,
|
|
1054
|
+
`type ProbeTupleSpread = UserTupleSpread<[int]>;`,
|
|
1055
|
+
`type ProbeEvent = EventPayload;`,
|
|
1056
|
+
`type ProbeClick = ClickPayload;`,
|
|
1057
|
+
`type ProbeRouteA = ApiUserRoute;`,
|
|
1058
|
+
`type ProbeRouteB = ApiPostRoute;`,
|
|
1059
|
+
`type ProbeRoutePair = RoutePair;`,
|
|
1060
|
+
`type ProbePairJoin = PairJoin<ApiUserRoute, EventPayload>;`,
|
|
1061
|
+
`type ProbeCtorArgs = ConstructorArgs;`,
|
|
1062
|
+
`type ProbeRecordInstance = RecordInstance;`,
|
|
1063
|
+
``,
|
|
1064
|
+
`const mappedAgain: int = copiedAgain + 1;`,
|
|
1065
|
+
``,
|
|
1066
|
+
`void user;`,
|
|
1067
|
+
`void flags;`,
|
|
1068
|
+
`void route;`,
|
|
1069
|
+
`void tupleFromFn;`,
|
|
1070
|
+
`void boxUser;`,
|
|
1071
|
+
`Console.WriteLine(mappedAgain + score);`,
|
|
1072
|
+
``,
|
|
1073
|
+
].join("\n"), "utf-8");
|
|
1074
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/dotnet"), join(dir, "node_modules/@tsonic/dotnet"));
|
|
1075
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/core"), join(dir, "node_modules/@tsonic/core"));
|
|
1076
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/globals"), join(dir, "node_modules/@tsonic/globals"));
|
|
1077
|
+
linkDir(join(dir, "packages", "core"), join(dir, "node_modules/@acme/core"));
|
|
1078
|
+
const cliPath = join(repoRoot, "packages/cli/dist/index.js");
|
|
1079
|
+
const buildCore = spawnSync("node", [
|
|
1080
|
+
cliPath,
|
|
1081
|
+
"build",
|
|
1082
|
+
"--project",
|
|
1083
|
+
"core",
|
|
1084
|
+
"--config",
|
|
1085
|
+
wsConfigPath,
|
|
1086
|
+
"--quiet",
|
|
1087
|
+
], { cwd: dir, encoding: "utf-8" });
|
|
1088
|
+
expect(buildCore.status, buildCore.stderr || buildCore.stdout).to.equal(0);
|
|
1089
|
+
const buildApp = spawnSync("node", [
|
|
1090
|
+
cliPath,
|
|
1091
|
+
"build",
|
|
1092
|
+
"--project",
|
|
1093
|
+
"app",
|
|
1094
|
+
"--config",
|
|
1095
|
+
wsConfigPath,
|
|
1096
|
+
"--quiet",
|
|
1097
|
+
], { cwd: dir, encoding: "utf-8" });
|
|
1098
|
+
expect(buildApp.status, buildApp.stderr || buildApp.stdout).to.equal(0);
|
|
1099
|
+
const bindingsDir = join(dir, "packages", "core", "dist", "tsonic", "bindings");
|
|
1100
|
+
const rootBindingsPath = join(bindingsDir, "Acme.Core", "bindings.json");
|
|
1101
|
+
expect(existsSync(rootBindingsPath)).to.equal(true);
|
|
1102
|
+
const rootBindings = JSON.parse(readFileSync(rootBindingsPath, "utf-8"));
|
|
1103
|
+
expect(rootBindings.producer?.tool).to.equal("tsonic");
|
|
1104
|
+
expect(rootBindings.producer?.mode).to.equal("aikya-firstparty");
|
|
1105
|
+
expect(Object.keys(rootBindings.exports ?? {})).to.include("projectFlags");
|
|
1106
|
+
expect(Object.keys(rootBindings.exports ?? {})).to.include("createBox");
|
|
1107
|
+
const collectDts = (root) => {
|
|
1108
|
+
const out = [];
|
|
1109
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
1110
|
+
const entryPath = join(root, entry.name);
|
|
1111
|
+
if (entry.isDirectory()) {
|
|
1112
|
+
out.push(...collectDts(entryPath));
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
if (entry.isFile() && entry.name.endsWith(".d.ts")) {
|
|
1116
|
+
out.push(entryPath);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
return out;
|
|
1120
|
+
};
|
|
1121
|
+
const allFacadeText = collectDts(bindingsDir)
|
|
1122
|
+
.map((path) => readFileSync(path, "utf-8"))
|
|
1123
|
+
.join("\n");
|
|
1124
|
+
const expectedTypeAliases = [
|
|
1125
|
+
"UserFlags",
|
|
1126
|
+
"UserReadonly",
|
|
1127
|
+
"UserPartial",
|
|
1128
|
+
"UserRequired",
|
|
1129
|
+
"UserPick",
|
|
1130
|
+
"UserOmit",
|
|
1131
|
+
"Box",
|
|
1132
|
+
"BoxReadonly",
|
|
1133
|
+
"BoxPartial",
|
|
1134
|
+
"BoxRequired",
|
|
1135
|
+
"Mutable",
|
|
1136
|
+
"Head",
|
|
1137
|
+
"Tail",
|
|
1138
|
+
"Last",
|
|
1139
|
+
"UnwrapPromise",
|
|
1140
|
+
"AsyncValue",
|
|
1141
|
+
"AwaitedScore",
|
|
1142
|
+
"SuccessResult",
|
|
1143
|
+
"FailureResult",
|
|
1144
|
+
"NonNullName",
|
|
1145
|
+
"ExtractStatus",
|
|
1146
|
+
"ExcludeStatus",
|
|
1147
|
+
"UserAndMeta",
|
|
1148
|
+
"PrefixSuffix",
|
|
1149
|
+
"UserTuple",
|
|
1150
|
+
"UserTupleSpread",
|
|
1151
|
+
"EventPayload",
|
|
1152
|
+
"ClickPayload",
|
|
1153
|
+
"ApiUserRoute",
|
|
1154
|
+
"ApiPostRoute",
|
|
1155
|
+
"RoutePair",
|
|
1156
|
+
"PairJoin",
|
|
1157
|
+
"Mapper",
|
|
1158
|
+
"MapperParams",
|
|
1159
|
+
"MapperResult",
|
|
1160
|
+
"SymbolScores",
|
|
1161
|
+
"UserRecordCtorArgs",
|
|
1162
|
+
"UserRecordInstance",
|
|
1163
|
+
"ConstructorArgs",
|
|
1164
|
+
"RecordInstance",
|
|
1165
|
+
];
|
|
1166
|
+
for (const alias of expectedTypeAliases) {
|
|
1167
|
+
expect(allFacadeText, `expected generated bindings to contain alias '${alias}'`).to.match(new RegExp(`\\bexport\\s+type\\s+${alias}\\b`));
|
|
1168
|
+
}
|
|
1169
|
+
const expectedValueExports = [
|
|
1170
|
+
"id",
|
|
1171
|
+
"UserRecord",
|
|
1172
|
+
"projectFlags",
|
|
1173
|
+
"lookupScore",
|
|
1174
|
+
"invokeMapper",
|
|
1175
|
+
"createBox",
|
|
1176
|
+
"toRoute",
|
|
1177
|
+
"projectEvent",
|
|
1178
|
+
"createUserTuple",
|
|
1179
|
+
"chainScore",
|
|
1180
|
+
"loadSideEffects",
|
|
1181
|
+
"nextValues",
|
|
1182
|
+
];
|
|
1183
|
+
for (const value of expectedValueExports) {
|
|
1184
|
+
expect(allFacadeText, `expected generated bindings to contain value export '${value}'`).to.match(new RegExp(`\\b${value}\\b`));
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
finally {
|
|
1188
|
+
rmSync(dir, { recursive: true, force: true });
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
it("supports keyof/index/template-literal alias surfaces without unresolved any fallback", () => {
|
|
1192
|
+
const dir = mkdtempSync(join(tmpdir(), "tsonic-lib-bindings-unsupported-"));
|
|
1193
|
+
try {
|
|
1194
|
+
const wsConfigPath = join(dir, "tsonic.workspace.json");
|
|
1195
|
+
mkdirSync(join(dir, "packages", "lib", "src"), { recursive: true });
|
|
1196
|
+
mkdirSync(join(dir, "node_modules"), { recursive: true });
|
|
1197
|
+
writeFileSync(join(dir, "package.json"), JSON.stringify({
|
|
1198
|
+
name: "test",
|
|
1199
|
+
private: true,
|
|
1200
|
+
type: "module",
|
|
1201
|
+
}, null, 2) + "\n", "utf-8");
|
|
1202
|
+
writeFileSync(wsConfigPath, JSON.stringify({
|
|
1203
|
+
$schema: "https://tsonic.org/schema/workspace/v1.json",
|
|
1204
|
+
dotnetVersion: "net10.0",
|
|
1205
|
+
}, null, 2) + "\n", "utf-8");
|
|
1206
|
+
writeFileSync(join(dir, "packages", "lib", "package.json"), JSON.stringify({
|
|
1207
|
+
name: "@acme/lib",
|
|
1208
|
+
private: true,
|
|
1209
|
+
type: "module",
|
|
1210
|
+
exports: {
|
|
1211
|
+
"./package.json": "./package.json",
|
|
1212
|
+
"./*.js": {
|
|
1213
|
+
types: "./dist/tsonic/bindings/*.d.ts",
|
|
1214
|
+
default: "./dist/tsonic/bindings/*.js",
|
|
1215
|
+
},
|
|
1216
|
+
},
|
|
1217
|
+
}, null, 2) + "\n", "utf-8");
|
|
1218
|
+
writeFileSync(join(dir, "packages", "lib", "tsonic.json"), JSON.stringify({
|
|
1219
|
+
$schema: "https://tsonic.org/schema/v1.json",
|
|
1220
|
+
rootNamespace: "Acme.Lib",
|
|
1221
|
+
entryPoint: "src/index.ts",
|
|
1222
|
+
sourceRoot: "src",
|
|
1223
|
+
outputDirectory: "generated",
|
|
1224
|
+
outputName: "Acme.Lib",
|
|
1225
|
+
output: {
|
|
1226
|
+
type: "library",
|
|
1227
|
+
targetFrameworks: ["net10.0"],
|
|
1228
|
+
nativeAot: false,
|
|
1229
|
+
generateDocumentation: false,
|
|
1230
|
+
includeSymbols: false,
|
|
1231
|
+
packable: false,
|
|
1232
|
+
},
|
|
1233
|
+
}, null, 2) + "\n", "utf-8");
|
|
1234
|
+
writeFileSync(join(dir, "packages", "lib", "src", "index.ts"), [
|
|
1235
|
+
`export type User = { name: string; age: number };`,
|
|
1236
|
+
`export type KeyOfUser = keyof User;`,
|
|
1237
|
+
`export type ValueOfUser = User[keyof User];`,
|
|
1238
|
+
`export type EventMap = { click: { x: number }; keyup: { key: string } };`,
|
|
1239
|
+
`export type EventName = keyof EventMap;`,
|
|
1240
|
+
`export type EventPayload<N extends EventName> = EventMap[N];`,
|
|
1241
|
+
`export type RoutePath<T extends string> = \`/api/\${T}\`;`,
|
|
1242
|
+
``,
|
|
1243
|
+
].join("\n"), "utf-8");
|
|
1244
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/dotnet"), join(dir, "node_modules/@tsonic/dotnet"));
|
|
1245
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/core"), join(dir, "node_modules/@tsonic/core"));
|
|
1246
|
+
linkDir(join(repoRoot, "node_modules/@tsonic/globals"), join(dir, "node_modules/@tsonic/globals"));
|
|
1247
|
+
const cliPath = join(repoRoot, "packages/cli/dist/index.js");
|
|
1248
|
+
const result = spawnSync("node", [
|
|
1249
|
+
cliPath,
|
|
1250
|
+
"build",
|
|
1251
|
+
"--project",
|
|
1252
|
+
"lib",
|
|
1253
|
+
"--config",
|
|
1254
|
+
wsConfigPath,
|
|
1255
|
+
"--quiet",
|
|
1256
|
+
], { cwd: dir, encoding: "utf-8" });
|
|
1257
|
+
expect(result.status).to.equal(0);
|
|
1258
|
+
const output = `${result.stderr}\n${result.stdout}`;
|
|
1259
|
+
expect(output).to.not.include("resolved to 'any'");
|
|
1260
|
+
const facadePath = join(dir, "packages", "lib", "dist", "tsonic", "bindings", "Acme.Lib.d.ts");
|
|
1261
|
+
expect(existsSync(facadePath)).to.equal(true);
|
|
1262
|
+
const facadeText = readFileSync(facadePath, "utf-8");
|
|
1263
|
+
expect(facadeText).to.include("export type KeyOfUser");
|
|
1264
|
+
expect(facadeText).to.include("export type ValueOfUser");
|
|
1265
|
+
expect(facadeText).to.include("export type EventName");
|
|
1266
|
+
expect(facadeText).to.include("export type EventPayload");
|
|
1267
|
+
expect(facadeText).to.include("export type RoutePath");
|
|
396
1268
|
}
|
|
397
1269
|
finally {
|
|
398
1270
|
rmSync(dir, { recursive: true, force: true });
|