@tsonic/cli 0.0.61 → 0.0.63

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.
Files changed (89) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/cli/dispatcher.d.ts.map +1 -1
  3. package/dist/cli/dispatcher.js +15 -5
  4. package/dist/cli/dispatcher.js.map +1 -1
  5. package/dist/cli/help.d.ts.map +1 -1
  6. package/dist/cli/help.js +1 -0
  7. package/dist/cli/help.js.map +1 -1
  8. package/dist/cli/parser.d.ts.map +1 -1
  9. package/dist/cli/parser.js +9 -6
  10. package/dist/cli/parser.js.map +1 -1
  11. package/dist/cli/parser.test.js +12 -3
  12. package/dist/cli/parser.test.js.map +1 -1
  13. package/dist/commands/add-common.d.ts +1 -0
  14. package/dist/commands/add-common.d.ts.map +1 -1
  15. package/dist/commands/add-common.js +38 -4
  16. package/dist/commands/add-common.js.map +1 -1
  17. package/dist/commands/add-common.test.js.map +1 -1
  18. package/dist/commands/add-deps.test.js +1 -8
  19. package/dist/commands/add-deps.test.js.map +1 -1
  20. package/dist/commands/add-framework.d.ts.map +1 -1
  21. package/dist/commands/add-framework.js +3 -1
  22. package/dist/commands/add-framework.js.map +1 -1
  23. package/dist/commands/add-npm.d.ts.map +1 -1
  24. package/dist/commands/add-npm.js +18 -6
  25. package/dist/commands/add-npm.js.map +1 -1
  26. package/dist/commands/add-npm.test.js +25 -9
  27. package/dist/commands/add-npm.test.js.map +1 -1
  28. package/dist/commands/add-nuget.d.ts.map +1 -1
  29. package/dist/commands/add-nuget.js +3 -1
  30. package/dist/commands/add-nuget.js.map +1 -1
  31. package/dist/commands/add-package.d.ts.map +1 -1
  32. package/dist/commands/add-package.js +17 -5
  33. package/dist/commands/add-package.js.map +1 -1
  34. package/dist/commands/build-library-bindings-aliases.test.js +724 -5
  35. package/dist/commands/build-library-bindings-aliases.test.js.map +1 -1
  36. package/dist/commands/build-native-lib.test.js +64 -7
  37. package/dist/commands/build-native-lib.test.js.map +1 -1
  38. package/dist/commands/build-no-generate.test.js +19 -2
  39. package/dist/commands/build-no-generate.test.js.map +1 -1
  40. package/dist/commands/build.d.ts.map +1 -1
  41. package/dist/commands/build.js +37 -9
  42. package/dist/commands/build.js.map +1 -1
  43. package/dist/commands/build.test.d.ts +2 -0
  44. package/dist/commands/build.test.d.ts.map +1 -0
  45. package/dist/commands/build.test.js +106 -0
  46. package/dist/commands/build.test.js.map +1 -0
  47. package/dist/commands/generate.d.ts.map +1 -1
  48. package/dist/commands/generate.js +8 -4
  49. package/dist/commands/generate.js.map +1 -1
  50. package/dist/commands/init.d.ts.map +1 -1
  51. package/dist/commands/init.js +1 -1
  52. package/dist/commands/init.js.map +1 -1
  53. package/dist/commands/init.test.js +1 -1
  54. package/dist/commands/init.test.js.map +1 -1
  55. package/dist/commands/library-bindings-augment.d.ts +12 -0
  56. package/dist/commands/library-bindings-augment.d.ts.map +1 -1
  57. package/dist/commands/library-bindings-augment.js +648 -49
  58. package/dist/commands/library-bindings-augment.js.map +1 -1
  59. package/dist/commands/library-bindings-augment.test.d.ts +2 -0
  60. package/dist/commands/library-bindings-augment.test.d.ts.map +1 -0
  61. package/dist/commands/library-bindings-augment.test.js +89 -0
  62. package/dist/commands/library-bindings-augment.test.js.map +1 -0
  63. package/dist/commands/remove-nuget.d.ts.map +1 -1
  64. package/dist/commands/remove-nuget.js.map +1 -1
  65. package/dist/commands/restore.d.ts.map +1 -1
  66. package/dist/commands/restore.js +65 -22
  67. package/dist/commands/restore.js.map +1 -1
  68. package/dist/commands/restore.test.js +116 -27
  69. package/dist/commands/restore.test.js.map +1 -1
  70. package/dist/commands/run-build-regressions.test.js +42 -8
  71. package/dist/commands/run-build-regressions.test.js.map +1 -1
  72. package/dist/commands/run.d.ts.map +1 -1
  73. package/dist/commands/run.js +1 -0
  74. package/dist/commands/run.js.map +1 -1
  75. package/dist/commands/test.d.ts.map +1 -1
  76. package/dist/commands/test.js +5 -1
  77. package/dist/commands/test.js.map +1 -1
  78. package/dist/commands/update-nuget.d.ts.map +1 -1
  79. package/dist/commands/update-nuget.js.map +1 -1
  80. package/dist/config.d.ts.map +1 -1
  81. package/dist/config.js +22 -9
  82. package/dist/config.js.map +1 -1
  83. package/dist/dotnet/nuget-config.test.js +1 -1
  84. package/dist/dotnet/nuget-config.test.js.map +1 -1
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +4 -2
  87. package/dist/index.js.map +1 -1
  88. package/package.json +5 -5
  89. package/runtime/nodejs.dll +0 -0
@@ -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";
@@ -149,7 +149,10 @@ describe("build command (library bindings)", function () {
149
149
  .filter((n) => n.endsWith(".d.ts"))
150
150
  .map((n) => join(bindingsDir, n));
151
151
  expect(dtsFiles.length).to.be.greaterThan(0);
152
- const all = dtsFiles.map((p) => ({ path: p, content: readFileSync(p, "utf-8") }));
152
+ const all = dtsFiles.map((p) => ({
153
+ path: p,
154
+ content: readFileSync(p, "utf-8"),
155
+ }));
153
156
  const entryFacade = all.find((f) => f.content.includes("Tsonic entrypoint re-exports (generated)"));
154
157
  const typesFacade = all.find((f) => f.content.includes("// Namespace: Test.Lib.types"));
155
158
  const configFacade = all.find((f) => f.content.includes("// Namespace: Test.Lib.config"));
@@ -171,12 +174,12 @@ describe("build command (library bindings)", function () {
171
174
  // Namespace facade for the "types" module must include TS-level aliases.
172
175
  expect(typesContent).to.include("Tsonic source type aliases (generated)");
173
176
  expect(typesContent).to.include("export type Id = string;");
174
- expect(typesContent).to.include("export type Ok<T> = Internal.Ok__Alias_1<T>;");
175
- expect(typesContent).to.include("export type Err<E> = Internal.Err__Alias_1<E>;");
177
+ expect(typesContent).to.match(/export type Ok<\s*T\s*> = /);
178
+ expect(typesContent).to.match(/export type Err<\s*E\s*> = /);
176
179
  expect(typesContent).to.include("export type Result<T, E = string> = Ok<T> | Err<E>;");
177
180
  // Structural aliases in non-types modules are also surfaced.
178
181
  expect(configContent).to.include("Tsonic source type aliases (generated)");
179
- expect(configContent).to.include("export type ServerConfig = Internal.ServerConfig__Alias;");
182
+ expect(configContent).to.match(/export type ServerConfig = /);
180
183
  // Root namespace facade must re-export the entrypoint's type/value surface.
181
184
  expect(rootContent).to.include("Tsonic entrypoint re-exports (generated)");
182
185
  expect(rootContent).to.include("from './");
@@ -206,5 +209,721 @@ describe("build command (library bindings)", function () {
206
209
  rmSync(dir, { recursive: true, force: true });
207
210
  }
208
211
  });
212
+ it("preserves source-level optional/interface/discriminated typing across library bindings", () => {
213
+ const dir = mkdtempSync(join(tmpdir(), "tsonic-lib-bindings-source-"));
214
+ try {
215
+ const wsConfigPath = join(dir, "tsonic.workspace.json");
216
+ mkdirSync(join(dir, "packages", "core", "src"), { recursive: true });
217
+ mkdirSync(join(dir, "packages", "app", "src"), { recursive: true });
218
+ mkdirSync(join(dir, "node_modules"), { recursive: true });
219
+ writeFileSync(join(dir, "package.json"), JSON.stringify({
220
+ name: "test",
221
+ private: true,
222
+ type: "module",
223
+ workspaces: ["packages/*"],
224
+ }, null, 2) + "\n", "utf-8");
225
+ writeFileSync(wsConfigPath, JSON.stringify({
226
+ $schema: "https://tsonic.org/schema/workspace/v1.json",
227
+ dotnetVersion: "net10.0",
228
+ }, null, 2) + "\n", "utf-8");
229
+ writeFileSync(join(dir, "packages", "core", "package.json"), JSON.stringify({
230
+ name: "@acme/core",
231
+ private: true,
232
+ type: "module",
233
+ exports: {
234
+ "./package.json": "./package.json",
235
+ "./*.js": {
236
+ types: "./dist/tsonic/bindings/*.d.ts",
237
+ default: "./dist/tsonic/bindings/*.js",
238
+ },
239
+ },
240
+ }, null, 2) + "\n", "utf-8");
241
+ writeFileSync(join(dir, "packages", "app", "package.json"), JSON.stringify({
242
+ name: "@acme/app",
243
+ private: true,
244
+ type: "module",
245
+ dependencies: {
246
+ "@acme/core": "workspace:*",
247
+ },
248
+ }, null, 2) + "\n", "utf-8");
249
+ writeFileSync(join(dir, "packages", "core", "tsonic.json"), JSON.stringify({
250
+ $schema: "https://tsonic.org/schema/v1.json",
251
+ rootNamespace: "Acme.Core",
252
+ entryPoint: "src/index.ts",
253
+ sourceRoot: "src",
254
+ outputDirectory: "generated",
255
+ outputName: "Acme.Core",
256
+ output: {
257
+ type: "library",
258
+ targetFrameworks: ["net10.0"],
259
+ nativeAot: false,
260
+ generateDocumentation: false,
261
+ includeSymbols: false,
262
+ packable: false,
263
+ },
264
+ }, null, 2) + "\n", "utf-8");
265
+ writeFileSync(join(dir, "packages", "app", "tsonic.json"), JSON.stringify({
266
+ $schema: "https://tsonic.org/schema/v1.json",
267
+ rootNamespace: "Acme.App",
268
+ entryPoint: "src/App.ts",
269
+ sourceRoot: "src",
270
+ references: {
271
+ libraries: [
272
+ "../core/generated/bin/Release/net10.0/Acme.Core.dll",
273
+ ],
274
+ },
275
+ outputDirectory: "generated",
276
+ outputName: "Acme.App",
277
+ output: {
278
+ type: "executable",
279
+ targetFrameworks: ["net10.0"],
280
+ nativeAot: false,
281
+ generateDocumentation: false,
282
+ includeSymbols: false,
283
+ packable: false,
284
+ },
285
+ }, null, 2) + "\n", "utf-8");
286
+ writeFileSync(join(dir, "packages", "core", "src", "types.ts"), [
287
+ `export type Ok<T> = { success: true; data: T };`,
288
+ `export type Err<E> = { success: false; error: E };`,
289
+ `export type Result<T, E = string> = Ok<T> | Err<E>;`,
290
+ ``,
291
+ `export function ok<T>(data: T): Ok<T> {`,
292
+ ` return { success: true, data };`,
293
+ `}`,
294
+ ``,
295
+ `export function err<E>(error: E): Err<E> {`,
296
+ ` return { success: false, error };`,
297
+ `}`,
298
+ ``,
299
+ `export function renderMarkdownDomain(content: string): Result<{ rendered: string }, string> {`,
300
+ ` if (content.Length === 0) return err("empty");`,
301
+ ` return ok({ rendered: content });`,
302
+ `}`,
303
+ ``,
304
+ ].join("\n"), "utf-8");
305
+ writeFileSync(join(dir, "packages", "core", "src", "contracts.ts"), [
306
+ `import type { int } from "@tsonic/core/types.js";`,
307
+ ``,
308
+ `export class Entity {`,
309
+ ` Maybe?: int;`,
310
+ `}`,
311
+ ``,
312
+ `export interface DomainEvent {`,
313
+ ` type: string;`,
314
+ ` data: Record<string, unknown>;`,
315
+ `}`,
316
+ ``,
317
+ `export function dispatch(_event: DomainEvent): void {}`,
318
+ ``,
319
+ ].join("\n"), "utf-8");
320
+ writeFileSync(join(dir, "packages", "core", "src", "index.ts"), [
321
+ `export type { Ok, Err, Result } from "./types.ts";`,
322
+ `export { ok, err, renderMarkdownDomain } from "./types.ts";`,
323
+ `export { Entity, dispatch } from "./contracts.ts";`,
324
+ `export type { DomainEvent } from "./contracts.ts";`,
325
+ ``,
326
+ ].join("\n"), "utf-8");
327
+ writeFileSync(join(dir, "packages", "app", "src", "App.ts"), [
328
+ `import type { int } from "@tsonic/core/types.js";`,
329
+ `import { Entity, dispatch, renderMarkdownDomain, err } from "@acme/core/Acme.Core.js";`,
330
+ ``,
331
+ `const entity = new Entity();`,
332
+ `const maybe: int | undefined = undefined;`,
333
+ `entity.Maybe = maybe;`,
334
+ ``,
335
+ `const eventData: Record<string, unknown> = { id: "evt-1" };`,
336
+ `dispatch({ type: "evt", data: eventData });`,
337
+ ``,
338
+ `const renderResult = renderMarkdownDomain("hello");`,
339
+ `if (!renderResult.success) {`,
340
+ ` err(renderResult.error);`,
341
+ `} else {`,
342
+ ` const rendered = renderResult.data.rendered;`,
343
+ ` if (rendered.Length === 0) {`,
344
+ ` err("invalid");`,
345
+ ` }`,
346
+ `}`,
347
+ ``,
348
+ ].join("\n"), "utf-8");
349
+ linkDir(join(repoRoot, "node_modules/@tsonic/dotnet"), join(dir, "node_modules/@tsonic/dotnet"));
350
+ linkDir(join(repoRoot, "node_modules/@tsonic/core"), join(dir, "node_modules/@tsonic/core"));
351
+ linkDir(join(repoRoot, "node_modules/@tsonic/globals"), join(dir, "node_modules/@tsonic/globals"));
352
+ linkDir(join(dir, "packages", "core"), join(dir, "node_modules/@acme/core"));
353
+ const cliPath = join(repoRoot, "packages/cli/dist/index.js");
354
+ const buildCore = spawnSync("node", [
355
+ cliPath,
356
+ "build",
357
+ "--project",
358
+ "core",
359
+ "--config",
360
+ wsConfigPath,
361
+ "--quiet",
362
+ ], { cwd: dir, encoding: "utf-8" });
363
+ expect(buildCore.status, buildCore.stderr || buildCore.stdout).to.equal(0);
364
+ const buildApp = spawnSync("node", [
365
+ cliPath,
366
+ "build",
367
+ "--project",
368
+ "app",
369
+ "--config",
370
+ wsConfigPath,
371
+ "--quiet",
372
+ ], { cwd: dir, encoding: "utf-8" });
373
+ expect(buildApp.status, buildApp.stderr || buildApp.stdout).to.equal(0);
374
+ const coreTypesFacade = readFileSync(join(dir, "packages", "core", "dist", "tsonic", "bindings", "Acme.Core.d.ts"), "utf-8");
375
+ expect(coreTypesFacade).to.include("export type Result<T, E = string> = Ok<T> | Err<E>;");
376
+ expect(coreTypesFacade).to.include("export type Ok<T> =");
377
+ expect(coreTypesFacade).to.include("export type Err<E> =");
378
+ const bindingsRoot = join(dir, "packages", "core", "dist", "tsonic", "bindings");
379
+ const namespaceDirs = readdirSync(bindingsRoot, { withFileTypes: true })
380
+ .filter((entry) => entry.isDirectory())
381
+ .map((entry) => entry.name);
382
+ const entityInternalPath = namespaceDirs
383
+ .map((name) => join(bindingsRoot, name, "internal", "index.d.ts"))
384
+ .find((path) => {
385
+ try {
386
+ return readFileSync(path, "utf-8").includes("interface Entity$instance");
387
+ }
388
+ catch {
389
+ return false;
390
+ }
391
+ });
392
+ expect(entityInternalPath).to.not.equal(undefined);
393
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
394
+ const coreEntitiesInternal = readFileSync(entityInternalPath, "utf-8");
395
+ expect(coreEntitiesInternal).to.match(/set Maybe\(value: [^)]+undefined\)\s*;/);
396
+ expect(coreEntitiesInternal).to.include("data: Record<string, unknown>");
397
+ }
398
+ finally {
399
+ rmSync(dir, { recursive: true, force: true });
400
+ }
401
+ });
402
+ it("preserves Maximus lowered type/value surfaces across dependency bindings", () => {
403
+ const dir = mkdtempSync(join(tmpdir(), "tsonic-lib-bindings-maximus-"));
404
+ try {
405
+ const wsConfigPath = join(dir, "tsonic.workspace.json");
406
+ mkdirSync(join(dir, "packages", "core", "src"), { recursive: true });
407
+ mkdirSync(join(dir, "packages", "app", "src"), { recursive: true });
408
+ mkdirSync(join(dir, "node_modules"), { recursive: true });
409
+ writeFileSync(join(dir, "package.json"), JSON.stringify({
410
+ name: "test",
411
+ private: true,
412
+ type: "module",
413
+ workspaces: ["packages/*"],
414
+ }, null, 2) + "\n", "utf-8");
415
+ writeFileSync(wsConfigPath, JSON.stringify({
416
+ $schema: "https://tsonic.org/schema/workspace/v1.json",
417
+ dotnetVersion: "net10.0",
418
+ }, null, 2) + "\n", "utf-8");
419
+ writeFileSync(join(dir, "packages", "core", "package.json"), JSON.stringify({
420
+ name: "@acme/core",
421
+ private: true,
422
+ type: "module",
423
+ exports: {
424
+ "./package.json": "./package.json",
425
+ "./*.js": {
426
+ types: "./dist/tsonic/bindings/*.d.ts",
427
+ default: "./dist/tsonic/bindings/*.js",
428
+ },
429
+ },
430
+ }, null, 2) + "\n", "utf-8");
431
+ writeFileSync(join(dir, "packages", "app", "package.json"), JSON.stringify({
432
+ name: "@acme/app",
433
+ private: true,
434
+ type: "module",
435
+ dependencies: {
436
+ "@acme/core": "workspace:*",
437
+ },
438
+ }, null, 2) + "\n", "utf-8");
439
+ writeFileSync(join(dir, "packages", "core", "tsonic.json"), JSON.stringify({
440
+ $schema: "https://tsonic.org/schema/v1.json",
441
+ rootNamespace: "Acme.Core",
442
+ entryPoint: "src/index.ts",
443
+ sourceRoot: "src",
444
+ outputDirectory: "generated",
445
+ outputName: "Acme.Core",
446
+ output: {
447
+ type: "library",
448
+ targetFrameworks: ["net10.0"],
449
+ nativeAot: false,
450
+ generateDocumentation: false,
451
+ includeSymbols: false,
452
+ packable: false,
453
+ },
454
+ }, null, 2) + "\n", "utf-8");
455
+ writeFileSync(join(dir, "packages", "app", "tsonic.json"), JSON.stringify({
456
+ $schema: "https://tsonic.org/schema/v1.json",
457
+ rootNamespace: "Acme.App",
458
+ entryPoint: "src/App.ts",
459
+ sourceRoot: "src",
460
+ references: {
461
+ libraries: [
462
+ "../core/generated/bin/Release/net10.0/Acme.Core.dll",
463
+ ],
464
+ },
465
+ outputDirectory: "generated",
466
+ outputName: "Acme.App",
467
+ output: {
468
+ type: "executable",
469
+ targetFrameworks: ["net10.0"],
470
+ nativeAot: false,
471
+ generateDocumentation: false,
472
+ includeSymbols: false,
473
+ packable: false,
474
+ },
475
+ }, null, 2) + "\n", "utf-8");
476
+ writeFileSync(join(dir, "packages", "core", "src", "types.ts"), [
477
+ `import type { int } from "@tsonic/core/types.js";`,
478
+ ``,
479
+ `export type User = { name: string; age: int };`,
480
+ `export type UserFlags = { [K in keyof User]?: boolean };`,
481
+ `export type UserReadonly = Readonly<User>;`,
482
+ `export type UserPartial = Partial<User>;`,
483
+ `export type UserRequired = Required<UserFlags>;`,
484
+ `export type UserPick = Pick<User, "name">;`,
485
+ `export type UserOmit = Omit<User, "age">;`,
486
+ `export type Box<T> = { value: T };`,
487
+ `export type BoxReadonly<T> = Readonly<Box<T>>;`,
488
+ `export type BoxPartial<T> = Partial<Box<T>>;`,
489
+ `export type BoxRequired<T> = Required<BoxPartial<T>>;`,
490
+ `export type Mutable<T> = { -readonly [K in keyof T]: T[K] };`,
491
+ `export type Head<T extends readonly unknown[]> = T extends readonly [infer H, ...unknown[]] ? H : never;`,
492
+ `export type Tail<T extends readonly unknown[]> = T extends readonly [unknown, ...infer R] ? R : never;`,
493
+ `export type Last<T extends readonly unknown[]> = T extends readonly [...unknown[], infer L] ? L : never;`,
494
+ `export type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;`,
495
+ `export type AsyncValue<T> = T extends Promise<infer U> ? U : T;`,
496
+ `export type AwaitedScore = Awaited<Promise<int>>;`,
497
+ `export type SuccessResult<T> = Extract<{ ok: true; value: T } | { ok: false; error: string }, { ok: true }>;`,
498
+ `export type FailureResult = Exclude<{ ok: true; value: int } | { ok: false; error: string }, { ok: true; value: int }>;`,
499
+ `export type NonNullName = NonNullable<string | null | undefined>;`,
500
+ `export type ExtractStatus = Extract<"ok" | "err", "ok">;`,
501
+ `export type ExcludeStatus = Exclude<"ok" | "err", "err">;`,
502
+ `export type UserAndMeta = User & { id: string };`,
503
+ `export type PrefixSuffix<T extends unknown[]> = [string, ...T, boolean];`,
504
+ `export type UserTuple = [name: string, age: int];`,
505
+ `export type UserTupleSpread<T extends unknown[]> = [User, ...T, boolean];`,
506
+ `export type EventPayload = { kind: "click"; x: int; y: int } | { kind: "keyup"; key: string };`,
507
+ `export type ClickPayload = Extract<EventPayload, { kind: "click" }>;`,
508
+ `export type ApiUserRoute = "/api/users";`,
509
+ `export type ApiPostRoute = "/api/posts";`,
510
+ `export type RoutePair = [ApiUserRoute, ApiPostRoute];`,
511
+ `export type PairJoin<A, B> = [A, B];`,
512
+ `export type Mapper<T> = (value: T) => T;`,
513
+ `export type MapperParams = Parameters<Mapper<User>>;`,
514
+ `export type MapperResult = ReturnType<Mapper<User>>;`,
515
+ `export type SymbolScores = Record<symbol, int>;`,
516
+ ``,
517
+ `export class UserRecord {`,
518
+ ` constructor(public name: string, public age: int) {}`,
519
+ `}`,
520
+ ``,
521
+ `export type UserRecordCtorArgs = ConstructorParameters<typeof UserRecord>;`,
522
+ `export type UserRecordInstance = InstanceType<typeof UserRecord>;`,
523
+ `export type ConstructorArgs = ConstructorParameters<typeof UserRecord>;`,
524
+ `export type RecordInstance = InstanceType<typeof UserRecord>;`,
525
+ ``,
526
+ `export const id = <T>(value: T): T => value;`,
527
+ ``,
528
+ `export function projectFlags(user: User): UserFlags {`,
529
+ ` return { name: user.name.Length > 0, age: user.age > 0 };`,
530
+ `}`,
531
+ ``,
532
+ `export function lookupScore(scores: SymbolScores, key: symbol): int {`,
533
+ ` return scores[key] ?? 0;`,
534
+ `}`,
535
+ ``,
536
+ `export function invokeMapper<T>(value: T, mapper: Mapper<T>): T {`,
537
+ ` return mapper(value);`,
538
+ `}`,
539
+ ``,
540
+ `export function createBox<T>(value: T): Box<T> {`,
541
+ ` return { value };`,
542
+ `}`,
543
+ ``,
544
+ `export function toRoute(path: "users" | "posts"): ApiUserRoute | ApiPostRoute {`,
545
+ ` return path === "users" ? "/api/users" : "/api/posts";`,
546
+ `}`,
547
+ ``,
548
+ `export function projectEvent(payload: EventPayload): PairJoin<string, EventPayload> {`,
549
+ ` return ["evt", payload];`,
550
+ `}`,
551
+ ``,
552
+ `export function createUserTuple(user: User): UserTuple {`,
553
+ ` return [user.name, user.age];`,
554
+ `}`,
555
+ ``,
556
+ ].join("\n"), "utf-8");
557
+ writeFileSync(join(dir, "packages", "core", "src", "runtime.ts"), [
558
+ `import type { int } from "@tsonic/core/types.js";`,
559
+ ``,
560
+ `export function chainScore(seed: Promise<int>): Promise<int> {`,
561
+ ` return seed`,
562
+ ` .then((value) => value + 1)`,
563
+ ` .catch((_error) => 0)`,
564
+ ` .finally(() => {});`,
565
+ `}`,
566
+ ``,
567
+ `export async function loadSideEffects(): Promise<void> {`,
568
+ ` await import("./side-effect.ts");`,
569
+ `}`,
570
+ ``,
571
+ `export function* nextValues(start: int): Generator<int, int, int> {`,
572
+ ` const next = (yield start) + 1;`,
573
+ ` yield next;`,
574
+ ` return next + 1;`,
575
+ `}`,
576
+ ``,
577
+ ].join("\n"), "utf-8");
578
+ writeFileSync(join(dir, "packages", "core", "src", "side-effect.ts"), [`export const loaded = true;`, ``].join("\n"), "utf-8");
579
+ writeFileSync(join(dir, "packages", "core", "src", "index.ts"), [
580
+ `export type {`,
581
+ ` User,`,
582
+ ` UserFlags,`,
583
+ ` UserReadonly,`,
584
+ ` UserPartial,`,
585
+ ` UserRequired,`,
586
+ ` UserPick,`,
587
+ ` UserOmit,`,
588
+ ` UnwrapPromise,`,
589
+ ` NonNullName,`,
590
+ ` ExtractStatus,`,
591
+ ` ExcludeStatus,`,
592
+ ` UserAndMeta,`,
593
+ ` PrefixSuffix,`,
594
+ ` Box,`,
595
+ ` BoxReadonly,`,
596
+ ` Mutable,`,
597
+ ` Head,`,
598
+ ` Tail,`,
599
+ ` Last,`,
600
+ ` AsyncValue,`,
601
+ ` AwaitedScore,`,
602
+ ` SuccessResult,`,
603
+ ` FailureResult,`,
604
+ ` UserTuple,`,
605
+ ` UserTupleSpread,`,
606
+ ` EventPayload,`,
607
+ ` ClickPayload,`,
608
+ ` ApiUserRoute,`,
609
+ ` ApiPostRoute,`,
610
+ ` RoutePair,`,
611
+ ` PairJoin,`,
612
+ ` Mapper,`,
613
+ ` MapperParams,`,
614
+ ` MapperResult,`,
615
+ ` SymbolScores,`,
616
+ ` UserRecordCtorArgs,`,
617
+ ` UserRecordInstance,`,
618
+ ` ConstructorArgs,`,
619
+ ` RecordInstance,`,
620
+ `} from "./types.ts";`,
621
+ `export {`,
622
+ ` id,`,
623
+ ` UserRecord,`,
624
+ ` projectFlags,`,
625
+ ` lookupScore,`,
626
+ ` invokeMapper,`,
627
+ ` createBox,`,
628
+ ` toRoute,`,
629
+ ` projectEvent,`,
630
+ ` createUserTuple,`,
631
+ `} from "./types.ts";`,
632
+ `export { chainScore, loadSideEffects, nextValues } from "./runtime.ts";`,
633
+ ``,
634
+ ].join("\n"), "utf-8");
635
+ writeFileSync(join(dir, "packages", "app", "src", "App.ts"), [
636
+ `import type { int } from "@tsonic/core/types.js";`,
637
+ `import { Console } from "@tsonic/dotnet/System.js";`,
638
+ `import type {`,
639
+ ` User,`,
640
+ ` UserFlags,`,
641
+ ` UserReadonly,`,
642
+ ` UserPartial,`,
643
+ ` UserRequired,`,
644
+ ` UserPick,`,
645
+ ` UserOmit,`,
646
+ ` UnwrapPromise,`,
647
+ ` NonNullName,`,
648
+ ` ExtractStatus,`,
649
+ ` ExcludeStatus,`,
650
+ ` UserAndMeta,`,
651
+ ` PrefixSuffix,`,
652
+ ` Mapper,`,
653
+ ` Box,`,
654
+ ` BoxReadonly,`,
655
+ ` BoxPartial,`,
656
+ ` BoxRequired,`,
657
+ ` Mutable,`,
658
+ ` Head,`,
659
+ ` Tail,`,
660
+ ` Last,`,
661
+ ` AsyncValue,`,
662
+ ` AwaitedScore,`,
663
+ ` SuccessResult,`,
664
+ ` FailureResult,`,
665
+ ` UserTuple,`,
666
+ ` UserTupleSpread,`,
667
+ ` EventPayload,`,
668
+ ` ClickPayload,`,
669
+ ` ApiUserRoute,`,
670
+ ` ApiPostRoute,`,
671
+ ` RoutePair,`,
672
+ ` PairJoin,`,
673
+ ` UserRecordCtorArgs,`,
674
+ ` UserRecordInstance,`,
675
+ ` ConstructorArgs,`,
676
+ ` RecordInstance,`,
677
+ `} from "@acme/core/Acme.Core.js";`,
678
+ `import {`,
679
+ ` id,`,
680
+ ` UserRecord,`,
681
+ ` projectFlags,`,
682
+ ` invokeMapper,`,
683
+ ` createBox,`,
684
+ ` toRoute,`,
685
+ ` projectEvent,`,
686
+ ` createUserTuple,`,
687
+ `} from "@acme/core/Acme.Core.js";`,
688
+ ``,
689
+ `const copied = id<int>(7);`,
690
+ `const copyAlias = id;`,
691
+ `const copiedAgain = copyAlias<int>(copied);`,
692
+ ``,
693
+ `const ctorArgs: UserRecordCtorArgs = ["Ada", copiedAgain];`,
694
+ `void ctorArgs;`,
695
+ `const user: UserRecordInstance = new UserRecord("Ada", copiedAgain);`,
696
+ `const userView: User = { name: user.name, age: user.age };`,
697
+ `const flags = userView as unknown as UserFlags;`,
698
+ `const score: int = copiedAgain;`,
699
+ `const route = toRoute("users");`,
700
+ `const tupleFromFn = createUserTuple(userView);`,
701
+ `const boxUser = createBox(userView);`,
702
+ ``,
703
+ `type ProbeBox = Box<User>;`,
704
+ `type ProbeBoxReadonly = BoxReadonly<User>;`,
705
+ `type ProbeBoxPartial = BoxPartial<User>;`,
706
+ `type ProbeBoxRequired = BoxRequired<User>;`,
707
+ `type ProbeMutable = Mutable<UserReadonly>;`,
708
+ `type ProbeHead = Head<[int, string]>;`,
709
+ `type ProbeTail = Tail<[string, int, boolean]>;`,
710
+ `type ProbeLast = Last<[string, int, boolean]>;`,
711
+ `type ProbeAsync = AsyncValue<Promise<int>>;`,
712
+ `type ProbeAwaited = AwaitedScore;`,
713
+ `type ProbeSuccess = SuccessResult<int>;`,
714
+ `type ProbeFailure = FailureResult;`,
715
+ `type ProbeTuple = UserTuple;`,
716
+ `type ProbeTupleSpread = UserTupleSpread<[int]>;`,
717
+ `type ProbeEvent = EventPayload;`,
718
+ `type ProbeClick = ClickPayload;`,
719
+ `type ProbeRouteA = ApiUserRoute;`,
720
+ `type ProbeRouteB = ApiPostRoute;`,
721
+ `type ProbeRoutePair = RoutePair;`,
722
+ `type ProbePairJoin = PairJoin<ApiUserRoute, EventPayload>;`,
723
+ `type ProbeCtorArgs = ConstructorArgs;`,
724
+ `type ProbeRecordInstance = RecordInstance;`,
725
+ ``,
726
+ `const mappedAgain: int = copiedAgain + 1;`,
727
+ ``,
728
+ `void user;`,
729
+ `void flags;`,
730
+ `void route;`,
731
+ `void tupleFromFn;`,
732
+ `void boxUser;`,
733
+ `Console.WriteLine(mappedAgain + score);`,
734
+ ``,
735
+ ].join("\n"), "utf-8");
736
+ linkDir(join(repoRoot, "node_modules/@tsonic/dotnet"), join(dir, "node_modules/@tsonic/dotnet"));
737
+ linkDir(join(repoRoot, "node_modules/@tsonic/core"), join(dir, "node_modules/@tsonic/core"));
738
+ linkDir(join(repoRoot, "node_modules/@tsonic/globals"), join(dir, "node_modules/@tsonic/globals"));
739
+ linkDir(join(dir, "packages", "core"), join(dir, "node_modules/@acme/core"));
740
+ const cliPath = join(repoRoot, "packages/cli/dist/index.js");
741
+ const buildCore = spawnSync("node", [
742
+ cliPath,
743
+ "build",
744
+ "--project",
745
+ "core",
746
+ "--config",
747
+ wsConfigPath,
748
+ "--quiet",
749
+ ], { cwd: dir, encoding: "utf-8" });
750
+ expect(buildCore.status, buildCore.stderr || buildCore.stdout).to.equal(0);
751
+ const buildApp = spawnSync("node", [
752
+ cliPath,
753
+ "build",
754
+ "--project",
755
+ "app",
756
+ "--config",
757
+ wsConfigPath,
758
+ "--quiet",
759
+ ], { cwd: dir, encoding: "utf-8" });
760
+ expect(buildApp.status, buildApp.stderr || buildApp.stdout).to.equal(0);
761
+ const bindingsDir = join(dir, "packages", "core", "dist", "tsonic", "bindings");
762
+ const collectDts = (root) => {
763
+ const out = [];
764
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
765
+ const entryPath = join(root, entry.name);
766
+ if (entry.isDirectory()) {
767
+ out.push(...collectDts(entryPath));
768
+ continue;
769
+ }
770
+ if (entry.isFile() && entry.name.endsWith(".d.ts")) {
771
+ out.push(entryPath);
772
+ }
773
+ }
774
+ return out;
775
+ };
776
+ const allFacadeText = collectDts(bindingsDir)
777
+ .map((path) => readFileSync(path, "utf-8"))
778
+ .join("\n");
779
+ const expectedTypeAliases = [
780
+ "UserFlags",
781
+ "UserReadonly",
782
+ "UserPartial",
783
+ "UserRequired",
784
+ "UserPick",
785
+ "UserOmit",
786
+ "Box",
787
+ "BoxReadonly",
788
+ "BoxPartial",
789
+ "BoxRequired",
790
+ "Mutable",
791
+ "Head",
792
+ "Tail",
793
+ "Last",
794
+ "UnwrapPromise",
795
+ "AsyncValue",
796
+ "AwaitedScore",
797
+ "SuccessResult",
798
+ "FailureResult",
799
+ "NonNullName",
800
+ "ExtractStatus",
801
+ "ExcludeStatus",
802
+ "UserAndMeta",
803
+ "PrefixSuffix",
804
+ "UserTuple",
805
+ "UserTupleSpread",
806
+ "EventPayload",
807
+ "ClickPayload",
808
+ "ApiUserRoute",
809
+ "ApiPostRoute",
810
+ "RoutePair",
811
+ "PairJoin",
812
+ "Mapper",
813
+ "MapperParams",
814
+ "MapperResult",
815
+ "SymbolScores",
816
+ "UserRecordCtorArgs",
817
+ "UserRecordInstance",
818
+ "ConstructorArgs",
819
+ "RecordInstance",
820
+ ];
821
+ for (const alias of expectedTypeAliases) {
822
+ expect(allFacadeText, `expected generated bindings to contain alias '${alias}'`).to.match(new RegExp(`\\bexport\\s+type\\s+${alias}\\b`));
823
+ }
824
+ const expectedValueExports = [
825
+ "id",
826
+ "UserRecord",
827
+ "projectFlags",
828
+ "lookupScore",
829
+ "invokeMapper",
830
+ "createBox",
831
+ "toRoute",
832
+ "projectEvent",
833
+ "createUserTuple",
834
+ "chainScore",
835
+ "loadSideEffects",
836
+ "nextValues",
837
+ ];
838
+ for (const value of expectedValueExports) {
839
+ expect(allFacadeText, `expected generated bindings to contain value export '${value}'`).to.match(new RegExp(`\\b${value}\\b`));
840
+ }
841
+ }
842
+ finally {
843
+ rmSync(dir, { recursive: true, force: true });
844
+ }
845
+ });
846
+ it("supports keyof/index/template-literal alias surfaces without unresolved any fallback", () => {
847
+ const dir = mkdtempSync(join(tmpdir(), "tsonic-lib-bindings-unsupported-"));
848
+ try {
849
+ const wsConfigPath = join(dir, "tsonic.workspace.json");
850
+ mkdirSync(join(dir, "packages", "lib", "src"), { recursive: true });
851
+ mkdirSync(join(dir, "node_modules"), { recursive: true });
852
+ writeFileSync(join(dir, "package.json"), JSON.stringify({
853
+ name: "test",
854
+ private: true,
855
+ type: "module",
856
+ }, null, 2) + "\n", "utf-8");
857
+ writeFileSync(wsConfigPath, JSON.stringify({
858
+ $schema: "https://tsonic.org/schema/workspace/v1.json",
859
+ dotnetVersion: "net10.0",
860
+ }, null, 2) + "\n", "utf-8");
861
+ writeFileSync(join(dir, "packages", "lib", "package.json"), JSON.stringify({
862
+ name: "@acme/lib",
863
+ private: true,
864
+ type: "module",
865
+ exports: {
866
+ "./package.json": "./package.json",
867
+ "./*.js": {
868
+ types: "./dist/tsonic/bindings/*.d.ts",
869
+ default: "./dist/tsonic/bindings/*.js",
870
+ },
871
+ },
872
+ }, null, 2) + "\n", "utf-8");
873
+ writeFileSync(join(dir, "packages", "lib", "tsonic.json"), JSON.stringify({
874
+ $schema: "https://tsonic.org/schema/v1.json",
875
+ rootNamespace: "Acme.Lib",
876
+ entryPoint: "src/index.ts",
877
+ sourceRoot: "src",
878
+ outputDirectory: "generated",
879
+ outputName: "Acme.Lib",
880
+ output: {
881
+ type: "library",
882
+ targetFrameworks: ["net10.0"],
883
+ nativeAot: false,
884
+ generateDocumentation: false,
885
+ includeSymbols: false,
886
+ packable: false,
887
+ },
888
+ }, null, 2) + "\n", "utf-8");
889
+ writeFileSync(join(dir, "packages", "lib", "src", "index.ts"), [
890
+ `export type User = { name: string; age: number };`,
891
+ `export type KeyOfUser = keyof User;`,
892
+ `export type ValueOfUser = User[keyof User];`,
893
+ `export type EventMap = { click: { x: number }; keyup: { key: string } };`,
894
+ `export type EventName = keyof EventMap;`,
895
+ `export type EventPayload<N extends EventName> = EventMap[N];`,
896
+ `export type RoutePath<T extends string> = \`/api/\${T}\`;`,
897
+ ``,
898
+ ].join("\n"), "utf-8");
899
+ linkDir(join(repoRoot, "node_modules/@tsonic/dotnet"), join(dir, "node_modules/@tsonic/dotnet"));
900
+ linkDir(join(repoRoot, "node_modules/@tsonic/core"), join(dir, "node_modules/@tsonic/core"));
901
+ linkDir(join(repoRoot, "node_modules/@tsonic/globals"), join(dir, "node_modules/@tsonic/globals"));
902
+ const cliPath = join(repoRoot, "packages/cli/dist/index.js");
903
+ const result = spawnSync("node", [
904
+ cliPath,
905
+ "build",
906
+ "--project",
907
+ "lib",
908
+ "--config",
909
+ wsConfigPath,
910
+ "--quiet",
911
+ ], { cwd: dir, encoding: "utf-8" });
912
+ expect(result.status).to.equal(0);
913
+ const output = `${result.stderr}\n${result.stdout}`;
914
+ expect(output).to.not.include("resolved to 'any'");
915
+ const facadePath = join(dir, "packages", "lib", "dist", "tsonic", "bindings", "Acme.Lib.d.ts");
916
+ expect(existsSync(facadePath)).to.equal(true);
917
+ const facadeText = readFileSync(facadePath, "utf-8");
918
+ expect(facadeText).to.include("export type KeyOfUser");
919
+ expect(facadeText).to.include("export type ValueOfUser");
920
+ expect(facadeText).to.include("export type EventName");
921
+ expect(facadeText).to.include("export type EventPayload");
922
+ expect(facadeText).to.include("export type RoutePath");
923
+ }
924
+ finally {
925
+ rmSync(dir, { recursive: true, force: true });
926
+ }
927
+ });
209
928
  });
210
929
  //# sourceMappingURL=build-library-bindings-aliases.test.js.map