@tsonic/cli 0.0.63 → 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.
Files changed (82) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/aikya/bindings.d.ts +44 -0
  3. package/dist/aikya/bindings.d.ts.map +1 -0
  4. package/dist/aikya/bindings.js +777 -0
  5. package/dist/aikya/bindings.js.map +1 -0
  6. package/dist/aikya/bindings.test.d.ts +2 -0
  7. package/dist/aikya/bindings.test.d.ts.map +1 -0
  8. package/dist/aikya/bindings.test.js +554 -0
  9. package/dist/aikya/bindings.test.js.map +1 -0
  10. package/dist/cli/dispatcher.d.ts.map +1 -1
  11. package/dist/cli/dispatcher.js +15 -0
  12. package/dist/cli/dispatcher.js.map +1 -1
  13. package/dist/cli/help.d.ts.map +1 -1
  14. package/dist/cli/help.js +4 -1
  15. package/dist/cli/help.js.map +1 -1
  16. package/dist/cli/parser.d.ts.map +1 -1
  17. package/dist/cli/parser.js +6 -0
  18. package/dist/cli/parser.js.map +1 -1
  19. package/dist/cli/parser.test.js +10 -0
  20. package/dist/cli/parser.test.js.map +1 -1
  21. package/dist/commands/add-npm.d.ts +3 -2
  22. package/dist/commands/add-npm.d.ts.map +1 -1
  23. package/dist/commands/add-npm.js +69 -177
  24. package/dist/commands/add-npm.js.map +1 -1
  25. package/dist/commands/add-npm.test.js +276 -2
  26. package/dist/commands/add-npm.test.js.map +1 -1
  27. package/dist/commands/build-library-bindings-aliases.test.js +349 -4
  28. package/dist/commands/build-library-bindings-aliases.test.js.map +1 -1
  29. package/dist/commands/build.d.ts.map +1 -1
  30. package/dist/commands/build.js +168 -148
  31. package/dist/commands/build.js.map +1 -1
  32. package/dist/commands/build.test.js +22 -3
  33. package/dist/commands/build.test.js.map +1 -1
  34. package/dist/commands/generate.d.ts.map +1 -1
  35. package/dist/commands/generate.js +5 -0
  36. package/dist/commands/generate.js.map +1 -1
  37. package/dist/commands/init.d.ts +5 -2
  38. package/dist/commands/init.d.ts.map +1 -1
  39. package/dist/commands/init.js +42 -7
  40. package/dist/commands/init.js.map +1 -1
  41. package/dist/commands/init.test.js +82 -2
  42. package/dist/commands/init.test.js.map +1 -1
  43. package/dist/commands/library-bindings-augment.d.ts.map +1 -1
  44. package/dist/commands/library-bindings-augment.js +376 -53
  45. package/dist/commands/library-bindings-augment.js.map +1 -1
  46. package/dist/commands/library-bindings-augment.test.js +460 -2
  47. package/dist/commands/library-bindings-augment.test.js.map +1 -1
  48. package/dist/commands/library-bindings-firstparty-regressions.test.d.ts +2 -0
  49. package/dist/commands/library-bindings-firstparty-regressions.test.d.ts.map +1 -0
  50. package/dist/commands/library-bindings-firstparty-regressions.test.js +217 -0
  51. package/dist/commands/library-bindings-firstparty-regressions.test.js.map +1 -0
  52. package/dist/commands/library-bindings-firstparty.d.ts +3 -0
  53. package/dist/commands/library-bindings-firstparty.d.ts.map +1 -0
  54. package/dist/commands/library-bindings-firstparty.js +2250 -0
  55. package/dist/commands/library-bindings-firstparty.js.map +1 -0
  56. package/dist/commands/restore.d.ts.map +1 -1
  57. package/dist/commands/restore.js +3 -1
  58. package/dist/commands/restore.js.map +1 -1
  59. package/dist/commands/restore.test.js +29 -0
  60. package/dist/commands/restore.test.js.map +1 -1
  61. package/dist/commands/run-build-regressions.test.js +72 -0
  62. package/dist/commands/run-build-regressions.test.js.map +1 -1
  63. package/dist/config.d.ts.map +1 -1
  64. package/dist/config.js +16 -2
  65. package/dist/config.js.map +1 -1
  66. package/dist/config.test.js +57 -0
  67. package/dist/config.test.js.map +1 -1
  68. package/dist/dotnet/runtime-dlls.d.ts +1 -0
  69. package/dist/dotnet/runtime-dlls.d.ts.map +1 -1
  70. package/dist/dotnet/runtime-dlls.js +1 -0
  71. package/dist/dotnet/runtime-dlls.js.map +1 -1
  72. package/dist/surface/profiles.d.ts +10 -0
  73. package/dist/surface/profiles.d.ts.map +1 -0
  74. package/dist/surface/profiles.js +61 -0
  75. package/dist/surface/profiles.js.map +1 -0
  76. package/dist/surface/profiles.test.d.ts +2 -0
  77. package/dist/surface/profiles.test.d.ts.map +1 -0
  78. package/dist/surface/profiles.test.js +49 -0
  79. package/dist/surface/profiles.test.js.map +1 -0
  80. package/dist/types.d.ts +10 -0
  81. package/dist/types.d.ts.map +1 -1
  82. package/package.json +4 -4
@@ -1,6 +1,7 @@
1
1
  import { copyFileSync, existsSync, readFileSync, readdirSync, writeFileSync, } from "node:fs";
2
2
  import { basename, dirname, join, resolve, posix } from "node:path";
3
3
  import { buildModuleDependencyGraph, } from "@tsonic/frontend";
4
+ import { resolveSurfaceCapabilities } from "../surface/profiles.js";
4
5
  import * as ts from "typescript";
5
6
  const renderDiagnostics = (diags) => {
6
7
  return diags
@@ -21,6 +22,10 @@ const printTypeNodeText = (node, sourceFile) => {
21
22
  .printNode(ts.EmitHint.Unspecified, node, sourceFile)
22
23
  .trim();
23
24
  };
25
+ const textContainsIdentifier = (text, identifier) => {
26
+ const pattern = new RegExp(String.raw `\b${escapeRegExp(identifier)}\b`);
27
+ return pattern.test(text);
28
+ };
24
29
  const ensureUndefinedInType = (typeText) => {
25
30
  const trimmed = typeText.trim();
26
31
  if (/\bundefined\b/.test(trimmed))
@@ -222,6 +227,36 @@ const patchInternalIndexWithMemberOverrides = (internalIndexDtsPath, overrides)
222
227
  `This property was declared in TS source and should exist in CLR metadata.`,
223
228
  };
224
229
  }
230
+ if (o.isOptional &&
231
+ o.emitOptionalPropertySyntax &&
232
+ !o.memberName.startsWith("__tsonic_type_")) {
233
+ const baseType = (o.replaceWithSourceType ? o.sourceTypeText : undefined) ??
234
+ getterMatch?.[2] ??
235
+ setterMatch?.[2] ??
236
+ "";
237
+ const nextType = applyWrappersToBaseType(baseType, o.wrappers);
238
+ const matchForIndent = getterMatch ?? setterMatch;
239
+ const indent = matchForIndent?.[0].match(/^\s*/)?.[0] ?? " ";
240
+ const optionalPropertyLine = `${indent}${o.memberName}?: ${nextType};`;
241
+ if (getterMatch && setterMatch) {
242
+ const getterStart = getterMatch.index ?? 0;
243
+ const setterStart = setterMatch.index ?? 0;
244
+ const start = Math.min(getterStart, setterStart);
245
+ const getterEnd = getterStart + getterMatch[0].length;
246
+ const setterEnd = setterStart + setterMatch[0].length;
247
+ const end = Math.max(getterEnd, setterEnd);
248
+ body = body.slice(0, start) + optionalPropertyLine + body.slice(end);
249
+ continue;
250
+ }
251
+ if (getterMatch) {
252
+ body = body.replace(getterRe, optionalPropertyLine);
253
+ continue;
254
+ }
255
+ if (setterMatch) {
256
+ body = body.replace(setterRe, optionalPropertyLine);
257
+ continue;
258
+ }
259
+ }
225
260
  if (getterMatch) {
226
261
  const baseType = (o.replaceWithSourceType ? o.sourceTypeText : undefined) ??
227
262
  getterMatch[2] ??
@@ -290,7 +325,7 @@ export const patchInternalIndexBrandMarkersOptional = (internalIndexDtsPath, typ
290
325
  }
291
326
  return { ok: true, value: undefined };
292
327
  };
293
- const splitTopLevelTypeArgs = (text) => {
328
+ const splitTopLevelCommaSeparated = (text) => {
294
329
  const parts = [];
295
330
  let depthAngle = 0;
296
331
  let depthParen = 0;
@@ -327,6 +362,9 @@ const splitTopLevelTypeArgs = (text) => {
327
362
  parts.push(text.slice(start).trim());
328
363
  return parts.filter((p) => p.length > 0);
329
364
  };
365
+ const splitTopLevelTypeArgs = (text) => {
366
+ return splitTopLevelCommaSeparated(text);
367
+ };
330
368
  const expandUnionsDeep = (typeText) => {
331
369
  const unionPrefixRe = /Union_\d+</g;
332
370
  let result = typeText;
@@ -366,6 +404,26 @@ const expandUnionsDeep = (typeText) => {
366
404
  }
367
405
  return result;
368
406
  };
407
+ const collectSourceTypeImportsForSignature = (signature, typeImportsByLocalName) => {
408
+ const required = [];
409
+ for (const [localName, imported] of typeImportsByLocalName) {
410
+ const source = imported.source.trim();
411
+ // Relative source imports do not map to published facade paths.
412
+ if (source.startsWith(".") || source.startsWith("/"))
413
+ continue;
414
+ const appearsInSignature = textContainsIdentifier(signature.typeParametersText, localName) ||
415
+ textContainsIdentifier(signature.parametersText, localName) ||
416
+ textContainsIdentifier(signature.returnTypeText, localName);
417
+ if (!appearsInSignature)
418
+ continue;
419
+ required.push({
420
+ source,
421
+ importedName: imported.importedName,
422
+ localName,
423
+ });
424
+ }
425
+ return required.sort((a, b) => a.localName.localeCompare(b.localName));
426
+ };
369
427
  const patchFacadeWithSourceFunctionSignatures = (facadeDtsPath, signaturesByName) => {
370
428
  if (!existsSync(facadeDtsPath)) {
371
429
  return {
@@ -383,47 +441,209 @@ const patchFacadeWithSourceFunctionSignatures = (facadeDtsPath, signaturesByName
383
441
  if (currentMatch) {
384
442
  const existingReturnType = currentMatch[2]?.trim() ?? "";
385
443
  const replacement = Array.from(new Set(signatures.map((sig) => {
386
- const returnType = sig.returnTypeText.includes("{")
387
- ? expandUnionsDeep(existingReturnType)
388
- : sig.returnTypeText;
444
+ const existingIsUnknown = existingReturnType === "unknown";
445
+ const existingHasAnon = /__Anon_/.test(existingReturnType);
446
+ const existingHasGenericAritySuffix = /\b[A-Za-z_$][\w$]*_\d+\s*</.test(existingReturnType);
447
+ const returnType = (() => {
448
+ if (!sig.returnTypeText.includes("{")) {
449
+ return sig.returnTypeText;
450
+ }
451
+ if (existingIsUnknown) {
452
+ return sig.returnTypeText;
453
+ }
454
+ if (existingHasAnon) {
455
+ return expandUnionsDeep(existingReturnType);
456
+ }
457
+ if (existingHasGenericAritySuffix) {
458
+ return sig.returnTypeText;
459
+ }
460
+ return expandUnionsDeep(existingReturnType);
461
+ })();
389
462
  return `export declare function ${name}${sig.typeParametersText}(${sig.parametersText}): ${returnType};`;
390
463
  }))).join("\n");
391
464
  next = next.replace(fnRe, replacement);
392
465
  continue;
393
466
  }
394
- // If no function declaration match, try const Func<...> pattern
395
- const constFuncRe = new RegExp(String.raw `^export\s+declare\s+const\s+${escapeRegExp(name)}\s*:\s*Func<([\s\S]+?)>\s*;`, "m");
396
- const constMatch = constFuncRe.exec(next);
467
+ // If no function declaration match, try const callable pattern.
468
+ const constDeclRe = new RegExp(String.raw `^export\s+declare\s+const\s+${escapeRegExp(name)}\s*:\s*([^;]+);`, "m");
469
+ const constMatch = constDeclRe.exec(next);
397
470
  if (!constMatch || !constMatch[1])
398
471
  continue;
399
- const funcTypeArgs = splitTopLevelTypeArgs(constMatch[1]);
400
- if (funcTypeArgs.length < 2)
472
+ const constTypeText = constMatch[1].trim();
473
+ let expectedParamCount;
474
+ let forcedReturnType;
475
+ const funcTypeMatch = /^Func<([\s\S]+)>$/.exec(constTypeText);
476
+ if (funcTypeMatch?.[1]) {
477
+ const funcTypeArgs = splitTopLevelTypeArgs(funcTypeMatch[1]);
478
+ if (funcTypeArgs.length < 2)
479
+ continue;
480
+ expectedParamCount = funcTypeArgs.length - 1;
481
+ const lastTypeArg = funcTypeArgs.at(-1);
482
+ if (!lastTypeArg)
483
+ continue;
484
+ forcedReturnType = expandUnionsDeep(lastTypeArg);
485
+ }
486
+ const replacement = Array.from(new Set(signatures
487
+ .filter((sig) => {
488
+ if (expectedParamCount === undefined)
489
+ return true;
490
+ const paramCount = splitTopLevelCommaSeparated(sig.parametersText).length;
491
+ return paramCount === expectedParamCount;
492
+ })
493
+ .map((sig) => {
494
+ const returnType = forcedReturnType ?? sig.returnTypeText;
495
+ return `export declare function ${name}${sig.typeParametersText}(${sig.parametersText}): ${returnType};`;
496
+ }))).join("\n");
497
+ if (replacement.length === 0)
401
498
  continue;
402
- // Last arg = return type, remaining args = parameter types
403
- const facadeParamTypes = funcTypeArgs.slice(0, -1);
404
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
405
- const facadeReturnType = funcTypeArgs[funcTypeArgs.length - 1];
406
- // Use the first source signature for parameter names
407
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
408
- const sourceSig = signatures[0];
409
- const sourceParams = sourceSig.parametersText
410
- .split(",")
411
- .map((p) => p.trim())
412
- .filter((p) => p.length > 0);
413
- // If count mismatch, skip patching for this declaration
414
- if (sourceParams.length !== facadeParamTypes.length)
499
+ next = next.replace(constDeclRe, replacement);
500
+ }
501
+ if (next !== original) {
502
+ writeFileSync(facadeDtsPath, next, "utf-8");
503
+ }
504
+ return { ok: true, value: undefined };
505
+ };
506
+ const parseNamedImports = (namedImportsText) => {
507
+ return namedImportsText
508
+ .split(",")
509
+ .map((part) => part.trim())
510
+ .filter((part) => part.length > 0)
511
+ .map((part) => {
512
+ const source = part.replace(/^\s*type\s+/, "");
513
+ const asMatch = /^\s*([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*)\s*$/.exec(source);
514
+ if (asMatch && asMatch[1] && asMatch[2]) {
515
+ return { imported: asMatch[1], local: asMatch[2] };
516
+ }
517
+ const identMatch = /^\s*([A-Za-z_$][\w$]*)\s*$/.exec(source);
518
+ if (identMatch && identMatch[1]) {
519
+ return { imported: identMatch[1], local: identMatch[1] };
520
+ }
521
+ return undefined;
522
+ })
523
+ .filter((specifier) => specifier !== undefined);
524
+ };
525
+ const formatNamedImportSpecifier = (specifier) => {
526
+ if (specifier.imported === specifier.local) {
527
+ return specifier.local;
528
+ }
529
+ return `${specifier.imported} as ${specifier.local}`;
530
+ };
531
+ const ensureInternalTypeImportsForFacade = (facadeDtsPath) => {
532
+ if (!existsSync(facadeDtsPath)) {
533
+ return {
534
+ ok: false,
535
+ error: `Facade declaration file not found at ${facadeDtsPath}`,
536
+ };
537
+ }
538
+ const original = readFileSync(facadeDtsPath, "utf-8");
539
+ const internalImportRe = /^import\s+\*\s+as\s+Internal\s+from\s+['"]([^'"]+)['"];\s*$/m;
540
+ const internalImportMatch = internalImportRe.exec(original);
541
+ if (!internalImportMatch || !internalImportMatch[1]) {
542
+ return { ok: true, value: undefined };
543
+ }
544
+ const internalSpecifier = internalImportMatch[1];
545
+ const exportFromInternalRe = new RegExp(String.raw `^export\s+\{([^}]*)\}\s+from\s+['"]${escapeRegExp(internalSpecifier)}['"];\s*$`, "gm");
546
+ const neededTypeSpecifiers = new Map();
547
+ for (const match of original.matchAll(exportFromInternalRe)) {
548
+ const named = match[1];
549
+ if (!named)
550
+ continue;
551
+ for (const specifier of parseNamedImports(named)) {
552
+ neededTypeSpecifiers.set(specifier.local, specifier);
553
+ }
554
+ }
555
+ if (neededTypeSpecifiers.size === 0) {
556
+ return { ok: true, value: undefined };
557
+ }
558
+ const existingTypeImportRe = new RegExp(String.raw `^import\s+type\s+\{([^}]*)\}\s+from\s+['"]${escapeRegExp(internalSpecifier)}['"];\s*$`, "m");
559
+ const existingTypeImportMatch = existingTypeImportRe.exec(original);
560
+ if (existingTypeImportMatch && existingTypeImportMatch[1]) {
561
+ for (const specifier of parseNamedImports(existingTypeImportMatch[1])) {
562
+ neededTypeSpecifiers.set(specifier.local, specifier);
563
+ }
564
+ }
565
+ const sortedSpecifiers = Array.from(neededTypeSpecifiers.values()).sort((a, b) => a.local.localeCompare(b.local));
566
+ const importLine = `import type { ${sortedSpecifiers
567
+ .map((specifier) => formatNamedImportSpecifier(specifier))
568
+ .join(", ")} } from '${internalSpecifier}';`;
569
+ let next = original;
570
+ if (existingTypeImportMatch && existingTypeImportMatch[0]) {
571
+ next = next.replace(existingTypeImportRe, importLine);
572
+ }
573
+ else if (internalImportMatch[0]) {
574
+ const anchor = internalImportMatch[0];
575
+ next = next.replace(anchor, `${anchor}\n${importLine}`);
576
+ }
577
+ if (next !== original) {
578
+ writeFileSync(facadeDtsPath, next, "utf-8");
579
+ }
580
+ return { ok: true, value: undefined };
581
+ };
582
+ const ensureSourceTypeImportsForFacade = (facadeDtsPath, importsByLocalName) => {
583
+ if (!existsSync(facadeDtsPath)) {
584
+ return {
585
+ ok: false,
586
+ error: `Facade declaration file not found at ${facadeDtsPath}`,
587
+ };
588
+ }
589
+ const original = readFileSync(facadeDtsPath, "utf-8");
590
+ if (importsByLocalName.size === 0) {
591
+ return { ok: true, value: undefined };
592
+ }
593
+ const existingLocals = new Set();
594
+ const importRe = /^import\s+(.*)\s+from\s+['"][^'"]+['"];\s*$/gm;
595
+ for (const match of original.matchAll(importRe)) {
596
+ const clause = match[1]?.trim();
597
+ if (!clause)
598
+ continue;
599
+ const nsMatch = /^\*\s+as\s+([A-Za-z_$][\w$]*)$/.exec(clause);
600
+ if (nsMatch?.[1]) {
601
+ existingLocals.add(nsMatch[1]);
602
+ continue;
603
+ }
604
+ const defaultMatch = /^([A-Za-z_$][\w$]*)$/.exec(clause);
605
+ if (defaultMatch?.[1]) {
606
+ existingLocals.add(defaultMatch[1]);
607
+ continue;
608
+ }
609
+ const namedMatch = /^(?:type\s+)?\{([^}]*)\}$/.exec(clause);
610
+ if (namedMatch?.[1]) {
611
+ for (const specifier of parseNamedImports(namedMatch[1])) {
612
+ existingLocals.add(specifier.local);
613
+ }
614
+ }
615
+ }
616
+ const grouped = new Map();
617
+ for (const binding of Array.from(importsByLocalName.values()).sort((a, b) => a.localName.localeCompare(b.localName))) {
618
+ if (existingLocals.has(binding.localName))
415
619
  continue;
416
- // Pair source parameter NAMES with facade parameter TYPES
417
- const pairedParams = sourceParams.map((param, idx) => {
418
- const colonIdx = param.indexOf(":");
419
- const paramName = colonIdx >= 0 ? param.slice(0, colonIdx).trim() : param.trim();
420
- return `${paramName}: ${facadeParamTypes[idx]}`;
620
+ const list = grouped.get(binding.source) ?? [];
621
+ list.push({
622
+ imported: binding.importedName,
623
+ local: binding.localName,
421
624
  });
422
- const expandedReturnType = expandUnionsDeep(facadeReturnType);
423
- const typeParamsText = sourceSig.typeParametersText;
424
- const replacement = `export declare function ${name}${typeParamsText}(${pairedParams.join(", ")}): ${expandedReturnType};`;
425
- next = next.replace(constFuncRe, replacement);
625
+ grouped.set(binding.source, list);
426
626
  }
627
+ if (grouped.size === 0) {
628
+ const startMarker = "// Tsonic source function type imports (generated)";
629
+ const endMarker = "// End Tsonic source function type imports";
630
+ const next = stripExistingSection(original, startMarker, endMarker);
631
+ if (next !== original) {
632
+ writeFileSync(facadeDtsPath, next, "utf-8");
633
+ }
634
+ return { ok: true, value: undefined };
635
+ }
636
+ const lines = [];
637
+ for (const [source, specifiers] of Array.from(grouped.entries()).sort((a, b) => a[0].localeCompare(b[0]))) {
638
+ const unique = Array.from(new Map(specifiers.map((specifier) => [
639
+ `${specifier.imported}|${specifier.local}`,
640
+ specifier,
641
+ ])).values()).sort((a, b) => a.local.localeCompare(b.local));
642
+ lines.push(`import type { ${unique.map((s) => formatNamedImportSpecifier(s)).join(", ")} } from '${source}';`);
643
+ }
644
+ const startMarker = "// Tsonic source function type imports (generated)";
645
+ const endMarker = "// End Tsonic source function type imports";
646
+ const next = upsertSectionAfterImports(original, startMarker, endMarker, lines.join("\n"));
427
647
  if (next !== original) {
428
648
  writeFileSync(facadeDtsPath, next, "utf-8");
429
649
  }
@@ -679,12 +899,14 @@ const buildModuleSourceIndex = (absoluteFilePath, fileKey) => {
679
899
  };
680
900
  };
681
901
  const typeNodeUsesImportedTypeNames = (node, typeImportsByLocalName) => {
902
+ const allowlistedImportSources = new Set(["@tsonic/core/types.js"]);
682
903
  let found = false;
683
904
  const visit = (current) => {
684
905
  if (found)
685
906
  return;
686
907
  if (ts.isTypeReferenceNode(current) && ts.isIdentifier(current.typeName)) {
687
- if (typeImportsByLocalName.has(current.typeName.text)) {
908
+ const imported = typeImportsByLocalName.get(current.typeName.text);
909
+ if (imported && !allowlistedImportSources.has(imported.source.trim())) {
688
910
  found = true;
689
911
  return;
690
912
  }
@@ -903,7 +1125,7 @@ const printIrType = (type, ctx) => {
903
1125
  return "unknown";
904
1126
  }
905
1127
  };
906
- const renderExportedTypeAlias = (stmt, internalIndexDts) => {
1128
+ const renderExportedTypeAlias = (stmt, internalIndexDts, _sourceAlias) => {
907
1129
  const typeParams = printTypeParameters(stmt.typeParameters);
908
1130
  if (stmt.type.kind === "objectType") {
909
1131
  const arity = stmt.typeParameters?.length ?? 0;
@@ -921,11 +1143,17 @@ const renderExportedTypeAlias = (stmt, internalIndexDts) => {
921
1143
  : "";
922
1144
  return {
923
1145
  ok: true,
924
- value: `export type ${stmt.name}${typeParams} = Internal.${internalName}${typeArgs};`,
1146
+ value: {
1147
+ line: `export type ${stmt.name}${typeParams} = ${internalName}${typeArgs};`,
1148
+ internalAliasImport: internalName,
1149
+ },
925
1150
  };
926
1151
  }
927
1152
  const rhs = printIrType(stmt.type, { parentPrecedence: 0 });
928
- return { ok: true, value: `export type ${stmt.name}${typeParams} = ${rhs};` };
1153
+ return {
1154
+ ok: true,
1155
+ value: { line: `export type ${stmt.name}${typeParams} = ${rhs};` },
1156
+ };
929
1157
  };
930
1158
  /**
931
1159
  * Overlay already-augmented bindings from dependency assemblies.
@@ -1008,6 +1236,7 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1008
1236
  }
1009
1237
  const absoluteEntryPoint = resolve(config.projectRoot, entryPoint);
1010
1238
  const absoluteSourceRoot = resolve(config.projectRoot, config.sourceRoot);
1239
+ const surfaceCapabilities = resolveSurfaceCapabilities(config.surface);
1011
1240
  const typeLibraries = config.libraries.filter((lib) => !lib.endsWith(".dll"));
1012
1241
  const allTypeRoots = [...config.typeRoots, ...typeLibraries].map((p) => resolve(config.workspaceRoot, p));
1013
1242
  const compilerOptions = {
@@ -1015,6 +1244,8 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1015
1244
  sourceRoot: absoluteSourceRoot,
1016
1245
  rootNamespace: config.rootNamespace,
1017
1246
  typeRoots: allTypeRoots,
1247
+ surface: config.surface,
1248
+ useStandardLib: surfaceCapabilities.useStandardLib,
1018
1249
  verbose: false,
1019
1250
  };
1020
1251
  const graphResult = buildModuleDependencyGraph(absoluteEntryPoint, compilerOptions);
@@ -1071,6 +1302,7 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1071
1302
  };
1072
1303
  // 1) Per-namespace exported type aliases (including non-structural aliases).
1073
1304
  const exportedAliasesByNamespace = new Map();
1305
+ const sourceIndexByFileKeyForAliases = new Map();
1074
1306
  for (const m of modules) {
1075
1307
  const isExportedTypeAlias = (stmt) => stmt.kind === "typeAliasDeclaration" && stmt.isExported;
1076
1308
  const exportedAliases = m.body.filter(isExportedTypeAlias);
@@ -1080,27 +1312,60 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1080
1312
  const internalIndexDts = existsSync(info.internalIndexDtsPath)
1081
1313
  ? readFileSync(info.internalIndexDtsPath, "utf-8")
1082
1314
  : "";
1315
+ const moduleKey = normalizeModuleFileKey(m.filePath);
1316
+ const sourceIndex = (() => {
1317
+ const cached = sourceIndexByFileKeyForAliases.get(moduleKey);
1318
+ if (cached)
1319
+ return { ok: true, value: cached };
1320
+ const absolutePath = resolve(absoluteSourceRoot, moduleKey);
1321
+ const indexed = buildModuleSourceIndex(absolutePath, moduleKey);
1322
+ if (!indexed.ok)
1323
+ return indexed;
1324
+ sourceIndexByFileKeyForAliases.set(moduleKey, indexed.value);
1325
+ return indexed;
1326
+ })();
1327
+ if (!sourceIndex.ok)
1328
+ return sourceIndex;
1083
1329
  for (const stmt of exportedAliases) {
1084
- const rendered = renderExportedTypeAlias(stmt, internalIndexDts);
1330
+ const rendered = renderExportedTypeAlias(stmt, internalIndexDts, sourceIndex.value.typeAliasesByName.get(stmt.name));
1085
1331
  if (!rendered.ok)
1086
1332
  return rendered;
1087
- const list = exportedAliasesByNamespace.get(m.namespace) ?? [];
1088
- list.push(rendered.value);
1089
- exportedAliasesByNamespace.set(m.namespace, list);
1333
+ const current = exportedAliasesByNamespace.get(m.namespace) ?? {
1334
+ lines: [],
1335
+ internalAliasImports: new Set(),
1336
+ };
1337
+ current.lines.push(rendered.value.line);
1338
+ if (rendered.value.internalAliasImport) {
1339
+ current.internalAliasImports.add(rendered.value.internalAliasImport);
1340
+ }
1341
+ exportedAliasesByNamespace.set(m.namespace, current);
1090
1342
  }
1091
1343
  }
1092
1344
  const aliasStart = "// Tsonic source type aliases (generated)";
1093
1345
  const aliasEnd = "// End Tsonic source type aliases";
1094
- for (const [ns, lines] of exportedAliasesByNamespace) {
1346
+ const aliasImportsStart = "// Tsonic source alias imports (generated)";
1347
+ const aliasImportsEnd = "// End Tsonic source alias imports";
1348
+ for (const [ns, aliasData] of exportedAliasesByNamespace) {
1095
1349
  const info = facadesByNamespace.get(ns);
1096
1350
  if (!info)
1097
1351
  continue;
1098
- if (lines.length === 0)
1352
+ if (aliasData.lines.length === 0)
1099
1353
  continue;
1100
- const unique = Array.from(new Set(lines)).sort((a, b) => a.localeCompare(b));
1354
+ const unique = Array.from(new Set(aliasData.lines)).sort((a, b) => a.localeCompare(b));
1101
1355
  const body = unique.join("\n");
1102
1356
  const current = readFileSync(info.facadeDtsPath, "utf-8");
1103
- const next = upsertSection(current, aliasStart, aliasEnd, body);
1357
+ const internalImportMatch = current.match(/^import\s+\*\s+as\s+Internal\s+from\s+['"](.+)['"];\s*$/m);
1358
+ let next = current;
1359
+ if (internalImportMatch?.[1] && aliasData.internalAliasImports.size > 0) {
1360
+ const internalAliasImportLine = `import type { ${Array.from(aliasData.internalAliasImports)
1361
+ .sort((a, b) => a.localeCompare(b))
1362
+ .join(", ")} } from '${internalImportMatch[1]}';`;
1363
+ next = upsertSectionAfterImports(next, aliasImportsStart, aliasImportsEnd, internalAliasImportLine);
1364
+ }
1365
+ else {
1366
+ next = stripExistingSection(next, aliasImportsStart, aliasImportsEnd);
1367
+ }
1368
+ next = upsertSectionAfterImports(next, aliasStart, aliasEnd, body);
1104
1369
  writeFileSync(info.facadeDtsPath, next, "utf-8");
1105
1370
  }
1106
1371
  // 2) Entry-module re-export surface (matches TS semantics for library entrypoints).
@@ -1176,7 +1441,7 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1176
1441
  // graph and patching the published internal/index.d.ts:
1177
1442
  // - Re-apply ExtensionMethods wrappers (class + interface members)
1178
1443
  // - Preserve optional (`?`) semantics by allowing `undefined` on patched members
1179
- // - For exported interfaces, preserve source structural member types when safe
1444
+ // - Preserve TS structural typing on interfaces/type-literal aliases (exported + local)
1180
1445
  const sourceIndexByFileKey = new Map();
1181
1446
  for (const m of modules) {
1182
1447
  // Synthetic IR modules (e.g., program-wide anonymous type declarations) do not
@@ -1193,6 +1458,7 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1193
1458
  const overridesByInternalIndex = new Map();
1194
1459
  const brandOptionalTypesByInternalIndex = new Map();
1195
1460
  const functionSignaturesByFacade = new Map();
1461
+ const sourceTypeImportsByFacade = new Map();
1196
1462
  for (const m of modules) {
1197
1463
  if (m.filePath.startsWith("__tsonic/"))
1198
1464
  continue;
@@ -1203,13 +1469,37 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1203
1469
  const sourceIndex = sourceIndexByFileKey.get(moduleKey);
1204
1470
  if (!sourceIndex)
1205
1471
  continue;
1472
+ const allInterfaces = m.body.filter((s) => s.kind === "interfaceDeclaration");
1473
+ const allTypeAliases = m.body.filter((s) => s.kind === "typeAliasDeclaration");
1206
1474
  const hasExportedSourceFunctions = sourceIndex.exportedFunctionSignaturesByName.size > 0;
1207
1475
  if (exportedClasses.length === 0 &&
1208
1476
  exportedInterfaces.length === 0 &&
1209
1477
  exportedAliases.length === 0 &&
1478
+ allInterfaces.length === 0 &&
1479
+ allTypeAliases.length === 0 &&
1210
1480
  !hasExportedSourceFunctions)
1211
1481
  continue;
1212
1482
  const info = facadesByNamespace.get(m.namespace) ?? ensureFacade(m.namespace);
1483
+ const brandTargets = brandOptionalTypesByInternalIndex.get(info.internalIndexDtsPath) ??
1484
+ new Set();
1485
+ for (const iface of allInterfaces) {
1486
+ brandTargets.add(iface.name);
1487
+ }
1488
+ for (const alias of allTypeAliases) {
1489
+ const sourceAlias = sourceIndex.typeAliasesByName.get(alias.name);
1490
+ if (!sourceAlias)
1491
+ continue;
1492
+ const aliasType = unwrapParens(sourceAlias.type);
1493
+ if (!ts.isTypeLiteralNode(aliasType))
1494
+ continue;
1495
+ const arity = sourceAlias.typeParameters.length;
1496
+ const internalAliasName = `${alias.name}__Alias${arity > 0 ? `_${arity}` : ""}`;
1497
+ brandTargets.add(alias.name);
1498
+ brandTargets.add(internalAliasName);
1499
+ }
1500
+ if (brandTargets.size > 0) {
1501
+ brandOptionalTypesByInternalIndex.set(info.internalIndexDtsPath, brandTargets);
1502
+ }
1213
1503
  for (const [name, signatures,] of sourceIndex.exportedFunctionSignaturesByName) {
1214
1504
  if (signatures.length === 0)
1215
1505
  continue;
@@ -1219,6 +1509,28 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1219
1509
  list.push(...signatures);
1220
1510
  byName.set(name, list);
1221
1511
  functionSignaturesByFacade.set(info.facadeDtsPath, byName);
1512
+ const importsByLocal = sourceTypeImportsByFacade.get(info.facadeDtsPath) ??
1513
+ new Map();
1514
+ for (const signature of signatures) {
1515
+ for (const binding of collectSourceTypeImportsForSignature(signature, sourceIndex.typeImportsByLocalName)) {
1516
+ const existing = importsByLocal.get(binding.localName);
1517
+ if (existing) {
1518
+ if (existing.source !== binding.source ||
1519
+ existing.importedName !== binding.importedName) {
1520
+ return {
1521
+ ok: false,
1522
+ error: `Conflicting source type import alias '${binding.localName}' while augmenting ${info.facadeDtsPath}.\n` +
1523
+ `- ${existing.importedName} from '${existing.source}'\n` +
1524
+ `- ${binding.importedName} from '${binding.source}'\n` +
1525
+ `Fix: disambiguate source type imports for exported function signatures.`,
1526
+ };
1527
+ }
1528
+ continue;
1529
+ }
1530
+ importsByLocal.set(binding.localName, binding);
1531
+ }
1532
+ }
1533
+ sourceTypeImportsByFacade.set(info.facadeDtsPath, importsByLocal);
1222
1534
  }
1223
1535
  for (const cls of exportedClasses) {
1224
1536
  const memberTypes = sourceIndex.memberTypesByClassAndMember.get(cls.name);
@@ -1243,13 +1555,21 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1243
1555
  if (!wrappersResult.ok)
1244
1556
  return wrappersResult;
1245
1557
  const wrappers = wrappersResult.value;
1246
- if (wrappers.length === 0 && !sourceMember.isOptional)
1558
+ const canUseSourceTypeText = !typeNodeUsesImportedTypeNames(sourceMember.typeNode, sourceIndex.typeImportsByLocalName);
1559
+ if (!canUseSourceTypeText &&
1560
+ wrappers.length === 0 &&
1561
+ !sourceMember.isOptional) {
1247
1562
  continue;
1563
+ }
1248
1564
  const list = overridesByInternalIndex.get(info.internalIndexDtsPath) ?? [];
1249
1565
  list.push({
1250
1566
  namespace: m.namespace,
1251
1567
  className: cls.name,
1252
1568
  memberName: member.name,
1569
+ sourceTypeText: canUseSourceTypeText
1570
+ ? sourceMember.typeText
1571
+ : undefined,
1572
+ replaceWithSourceType: canUseSourceTypeText,
1253
1573
  isOptional: sourceMember.isOptional,
1254
1574
  wrappers,
1255
1575
  });
@@ -1288,14 +1608,11 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1288
1608
  : undefined,
1289
1609
  replaceWithSourceType: canUseSourceTypeText,
1290
1610
  isOptional: sourceMember.isOptional,
1611
+ emitOptionalPropertySyntax: true,
1291
1612
  wrappers,
1292
1613
  });
1293
1614
  overridesByInternalIndex.set(info.internalIndexDtsPath, list);
1294
1615
  }
1295
- const brandTargets = brandOptionalTypesByInternalIndex.get(info.internalIndexDtsPath) ??
1296
- new Set();
1297
- brandTargets.add(iface.name);
1298
- brandOptionalTypesByInternalIndex.set(info.internalIndexDtsPath, brandTargets);
1299
1616
  }
1300
1617
  for (const alias of exportedAliases) {
1301
1618
  const sourceAlias = sourceIndex.typeAliasesByName.get(alias.name);
@@ -1306,10 +1623,6 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1306
1623
  continue;
1307
1624
  const arity = sourceAlias.typeParameters.length;
1308
1625
  const internalAliasName = `${alias.name}__Alias${arity > 0 ? `_${arity}` : ""}`;
1309
- const brandTargets = brandOptionalTypesByInternalIndex.get(info.internalIndexDtsPath) ??
1310
- new Set();
1311
- brandTargets.add(internalAliasName);
1312
- brandOptionalTypesByInternalIndex.set(info.internalIndexDtsPath, brandTargets);
1313
1626
  for (const member of aliasType.members) {
1314
1627
  if (!ts.isPropertySignature(member))
1315
1628
  continue;
@@ -1361,6 +1674,16 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1361
1674
  if (!result.ok)
1362
1675
  return result;
1363
1676
  }
1677
+ for (const [facadePath, importsByLocalName] of sourceTypeImportsByFacade) {
1678
+ const result = ensureSourceTypeImportsForFacade(facadePath, importsByLocalName);
1679
+ if (!result.ok)
1680
+ return result;
1681
+ }
1682
+ for (const info of facadesByNamespace.values()) {
1683
+ const result = ensureInternalTypeImportsForFacade(info.facadeDtsPath);
1684
+ if (!result.ok)
1685
+ return result;
1686
+ }
1364
1687
  return { ok: true, value: undefined };
1365
1688
  };
1366
1689
  //# sourceMappingURL=library-bindings-augment.js.map