@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.
Files changed (95) 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 +17 -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 -5
  18. package/dist/cli/parser.js.map +1 -1
  19. package/dist/cli/parser.test.js +13 -2
  20. package/dist/cli/parser.test.js.map +1 -1
  21. package/dist/commands/add-common.js +1 -1
  22. package/dist/commands/add-common.js.map +1 -1
  23. package/dist/commands/add-npm.d.ts +3 -2
  24. package/dist/commands/add-npm.d.ts.map +1 -1
  25. package/dist/commands/add-npm.js +69 -177
  26. package/dist/commands/add-npm.js.map +1 -1
  27. package/dist/commands/add-npm.test.js +277 -2
  28. package/dist/commands/add-npm.test.js.map +1 -1
  29. package/dist/commands/add-package.d.ts.map +1 -1
  30. package/dist/commands/add-package.js +1 -0
  31. package/dist/commands/add-package.js.map +1 -1
  32. package/dist/commands/build-library-bindings-aliases.test.js +877 -5
  33. package/dist/commands/build-library-bindings-aliases.test.js.map +1 -1
  34. package/dist/commands/build-native-lib.test.js +4 -1
  35. package/dist/commands/build-native-lib.test.js.map +1 -1
  36. package/dist/commands/build.d.ts.map +1 -1
  37. package/dist/commands/build.js +168 -148
  38. package/dist/commands/build.js.map +1 -1
  39. package/dist/commands/build.test.js +22 -3
  40. package/dist/commands/build.test.js.map +1 -1
  41. package/dist/commands/generate.d.ts.map +1 -1
  42. package/dist/commands/generate.js +5 -0
  43. package/dist/commands/generate.js.map +1 -1
  44. package/dist/commands/init.d.ts +5 -2
  45. package/dist/commands/init.d.ts.map +1 -1
  46. package/dist/commands/init.js +42 -7
  47. package/dist/commands/init.js.map +1 -1
  48. package/dist/commands/init.test.js +82 -2
  49. package/dist/commands/init.test.js.map +1 -1
  50. package/dist/commands/library-bindings-augment.d.ts +2 -0
  51. package/dist/commands/library-bindings-augment.d.ts.map +1 -1
  52. package/dist/commands/library-bindings-augment.js +396 -57
  53. package/dist/commands/library-bindings-augment.js.map +1 -1
  54. package/dist/commands/library-bindings-augment.test.d.ts +2 -0
  55. package/dist/commands/library-bindings-augment.test.d.ts.map +1 -0
  56. package/dist/commands/library-bindings-augment.test.js +547 -0
  57. package/dist/commands/library-bindings-augment.test.js.map +1 -0
  58. package/dist/commands/library-bindings-firstparty-regressions.test.d.ts +2 -0
  59. package/dist/commands/library-bindings-firstparty-regressions.test.d.ts.map +1 -0
  60. package/dist/commands/library-bindings-firstparty-regressions.test.js +217 -0
  61. package/dist/commands/library-bindings-firstparty-regressions.test.js.map +1 -0
  62. package/dist/commands/library-bindings-firstparty.d.ts +3 -0
  63. package/dist/commands/library-bindings-firstparty.d.ts.map +1 -0
  64. package/dist/commands/library-bindings-firstparty.js +2250 -0
  65. package/dist/commands/library-bindings-firstparty.js.map +1 -0
  66. package/dist/commands/restore.d.ts.map +1 -1
  67. package/dist/commands/restore.js +9 -8
  68. package/dist/commands/restore.js.map +1 -1
  69. package/dist/commands/restore.test.js +29 -0
  70. package/dist/commands/restore.test.js.map +1 -1
  71. package/dist/commands/run-build-regressions.test.js +72 -0
  72. package/dist/commands/run-build-regressions.test.js.map +1 -1
  73. package/dist/commands/run.d.ts.map +1 -1
  74. package/dist/commands/run.js +1 -0
  75. package/dist/commands/run.js.map +1 -1
  76. package/dist/config.d.ts.map +1 -1
  77. package/dist/config.js +16 -2
  78. package/dist/config.js.map +1 -1
  79. package/dist/config.test.js +57 -0
  80. package/dist/config.test.js.map +1 -1
  81. package/dist/dotnet/runtime-dlls.d.ts +1 -0
  82. package/dist/dotnet/runtime-dlls.d.ts.map +1 -1
  83. package/dist/dotnet/runtime-dlls.js +1 -0
  84. package/dist/dotnet/runtime-dlls.js.map +1 -1
  85. package/dist/surface/profiles.d.ts +10 -0
  86. package/dist/surface/profiles.d.ts.map +1 -0
  87. package/dist/surface/profiles.js +61 -0
  88. package/dist/surface/profiles.js.map +1 -0
  89. package/dist/surface/profiles.test.d.ts +2 -0
  90. package/dist/surface/profiles.test.d.ts.map +1 -0
  91. package/dist/surface/profiles.test.js +49 -0
  92. package/dist/surface/profiles.test.js.map +1 -0
  93. package/dist/types.d.ts +10 -0
  94. package/dist/types.d.ts.map +1 -1
  95. package/package.json +5 -5
@@ -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] ??
@@ -252,7 +287,7 @@ const patchInternalIndexWithMemberOverrides = (internalIndexDtsPath, overrides)
252
287
  }
253
288
  return { ok: true, value: undefined };
254
289
  };
255
- const patchInternalIndexBrandMarkersOptional = (internalIndexDtsPath, typeNames) => {
290
+ export const patchInternalIndexBrandMarkersOptional = (internalIndexDtsPath, typeNames) => {
256
291
  if (!existsSync(internalIndexDtsPath)) {
257
292
  return {
258
293
  ok: false,
@@ -275,7 +310,7 @@ const patchInternalIndexBrandMarkersOptional = (internalIndexDtsPath, typeNames)
275
310
  const head = block.slice(0, open + 1);
276
311
  let body = block.slice(open + 1, close);
277
312
  const tail = block.slice(close);
278
- const brandRe = /(^\s*readonly\s+__tsonic_type_[A-Za-z0-9_]+)\s*:\s*never\s*;/m;
313
+ const brandRe = /(^\s*readonly\s+__tsonic_type_[A-Za-z0-9_]+)\s*:\s*never\s*;/gm;
279
314
  if (!brandRe.test(body))
280
315
  continue;
281
316
  body = body.replace(brandRe, "$1?: never;");
@@ -290,7 +325,7 @@ const patchInternalIndexBrandMarkersOptional = (internalIndexDtsPath, typeNames)
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,11 +362,13 @@ 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;
333
371
  // Iterate until no more Union_N< patterns remain
334
- // eslint-disable-next-line no-constant-condition
335
372
  while (true) {
336
373
  unionPrefixRe.lastIndex = 0;
337
374
  const prefixMatch = unionPrefixRe.exec(result);
@@ -367,6 +404,26 @@ const expandUnionsDeep = (typeText) => {
367
404
  }
368
405
  return result;
369
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
+ };
370
427
  const patchFacadeWithSourceFunctionSignatures = (facadeDtsPath, signaturesByName) => {
371
428
  if (!existsSync(facadeDtsPath)) {
372
429
  return {
@@ -384,45 +441,209 @@ const patchFacadeWithSourceFunctionSignatures = (facadeDtsPath, signaturesByName
384
441
  if (currentMatch) {
385
442
  const existingReturnType = currentMatch[2]?.trim() ?? "";
386
443
  const replacement = Array.from(new Set(signatures.map((sig) => {
387
- const returnType = sig.returnTypeText.includes("{")
388
- ? expandUnionsDeep(existingReturnType)
389
- : 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
+ })();
390
462
  return `export declare function ${name}${sig.typeParametersText}(${sig.parametersText}): ${returnType};`;
391
463
  }))).join("\n");
392
464
  next = next.replace(fnRe, replacement);
393
465
  continue;
394
466
  }
395
- // If no function declaration match, try const Func<...> pattern
396
- const constFuncRe = new RegExp(String.raw `^export\s+declare\s+const\s+${escapeRegExp(name)}\s*:\s*Func<([\s\S]+?)>\s*;`, "m");
397
- 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);
398
470
  if (!constMatch || !constMatch[1])
399
471
  continue;
400
- const funcTypeArgs = splitTopLevelTypeArgs(constMatch[1]);
401
- 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)
402
498
  continue;
403
- // Last arg = return type, remaining args = parameter types
404
- const facadeParamTypes = funcTypeArgs.slice(0, -1);
405
- const facadeReturnType = funcTypeArgs[funcTypeArgs.length - 1];
406
- // Use the first source signature for parameter names
407
- const sourceSig = signatures[0];
408
- const sourceParams = sourceSig.parametersText
409
- .split(",")
410
- .map((p) => p.trim())
411
- .filter((p) => p.length > 0);
412
- // If count mismatch, skip patching for this declaration
413
- 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)
414
550
  continue;
415
- // Pair source parameter NAMES with facade parameter TYPES
416
- const pairedParams = sourceParams.map((param, idx) => {
417
- const colonIdx = param.indexOf(":");
418
- const paramName = colonIdx >= 0 ? param.slice(0, colonIdx).trim() : param.trim();
419
- return `${paramName}: ${facadeParamTypes[idx]}`;
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))
619
+ continue;
620
+ const list = grouped.get(binding.source) ?? [];
621
+ list.push({
622
+ imported: binding.importedName,
623
+ local: binding.localName,
420
624
  });
421
- const expandedReturnType = expandUnionsDeep(facadeReturnType);
422
- const typeParamsText = sourceSig.typeParametersText;
423
- const replacement = `export declare function ${name}${typeParamsText}(${pairedParams.join(", ")}): ${expandedReturnType};`;
424
- next = next.replace(constFuncRe, replacement);
625
+ grouped.set(binding.source, list);
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 };
425
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"));
426
647
  if (next !== original) {
427
648
  writeFileSync(facadeDtsPath, next, "utf-8");
428
649
  }
@@ -678,12 +899,14 @@ const buildModuleSourceIndex = (absoluteFilePath, fileKey) => {
678
899
  };
679
900
  };
680
901
  const typeNodeUsesImportedTypeNames = (node, typeImportsByLocalName) => {
902
+ const allowlistedImportSources = new Set(["@tsonic/core/types.js"]);
681
903
  let found = false;
682
904
  const visit = (current) => {
683
905
  if (found)
684
906
  return;
685
907
  if (ts.isTypeReferenceNode(current) && ts.isIdentifier(current.typeName)) {
686
- if (typeImportsByLocalName.has(current.typeName.text)) {
908
+ const imported = typeImportsByLocalName.get(current.typeName.text);
909
+ if (imported && !allowlistedImportSources.has(imported.source.trim())) {
687
910
  found = true;
688
911
  return;
689
912
  }
@@ -777,6 +1000,7 @@ const collectExtensionWrapperImportsFromSourceType = (opts) => {
777
1000
  localName: ident,
778
1001
  aliasName: `__TsonicExt_${ident}`,
779
1002
  });
1003
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
780
1004
  currentNode = args[0];
781
1005
  }
782
1006
  return { ok: true, value: wrappers };
@@ -901,7 +1125,7 @@ const printIrType = (type, ctx) => {
901
1125
  return "unknown";
902
1126
  }
903
1127
  };
904
- const renderExportedTypeAlias = (stmt, internalIndexDts) => {
1128
+ const renderExportedTypeAlias = (stmt, internalIndexDts, _sourceAlias) => {
905
1129
  const typeParams = printTypeParameters(stmt.typeParameters);
906
1130
  if (stmt.type.kind === "objectType") {
907
1131
  const arity = stmt.typeParameters?.length ?? 0;
@@ -919,11 +1143,17 @@ const renderExportedTypeAlias = (stmt, internalIndexDts) => {
919
1143
  : "";
920
1144
  return {
921
1145
  ok: true,
922
- value: `export type ${stmt.name}${typeParams} = Internal.${internalName}${typeArgs};`,
1146
+ value: {
1147
+ line: `export type ${stmt.name}${typeParams} = ${internalName}${typeArgs};`,
1148
+ internalAliasImport: internalName,
1149
+ },
923
1150
  };
924
1151
  }
925
1152
  const rhs = printIrType(stmt.type, { parentPrecedence: 0 });
926
- 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
+ };
927
1157
  };
928
1158
  /**
929
1159
  * Overlay already-augmented bindings from dependency assemblies.
@@ -943,9 +1173,7 @@ export const overlayDependencyBindings = (config, bindingsOutDir) => {
943
1173
  // Skip the current package's own assembly.
944
1174
  if (assemblyName === config.outputName)
945
1175
  continue;
946
- // Convention: DLL at <project>/dist/<dotnetVersion>/<name>.dll,
947
- // bindings at <project>/dist/tsonic/bindings/.
948
- const depBindingsDir = join(dirname(dirname(lib)), "tsonic", "bindings");
1176
+ const depBindingsDir = resolveDependencyBindingsDirForDll(lib);
949
1177
  if (existsSync(depBindingsDir)) {
950
1178
  depBindingsDirByAssembly.set(assemblyName, depBindingsDir);
951
1179
  }
@@ -985,6 +1213,22 @@ export const overlayDependencyBindings = (config, bindingsOutDir) => {
985
1213
  }
986
1214
  return { ok: true, value: undefined };
987
1215
  };
1216
+ export const resolveDependencyBindingsDirForDll = (dllPath) => {
1217
+ // Walk up from the DLL and resolve the nearest project-style bindings directory:
1218
+ // - <project>/dist/tsonic/bindings
1219
+ let cursor = resolve(dirname(dllPath));
1220
+ for (let i = 0; i < 24; i++) {
1221
+ const projectStyle = join(cursor, "dist", "tsonic", "bindings");
1222
+ if (existsSync(projectStyle))
1223
+ return projectStyle;
1224
+ const parent = dirname(cursor);
1225
+ if (parent === cursor)
1226
+ break;
1227
+ cursor = parent;
1228
+ }
1229
+ // No bindings directory found; caller checks existsSync before use.
1230
+ return join(resolve(dirname(dllPath)), "dist", "tsonic", "bindings");
1231
+ };
988
1232
  export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
989
1233
  const entryPoint = config.entryPoint;
990
1234
  if (!entryPoint) {
@@ -992,6 +1236,7 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
992
1236
  }
993
1237
  const absoluteEntryPoint = resolve(config.projectRoot, entryPoint);
994
1238
  const absoluteSourceRoot = resolve(config.projectRoot, config.sourceRoot);
1239
+ const surfaceCapabilities = resolveSurfaceCapabilities(config.surface);
995
1240
  const typeLibraries = config.libraries.filter((lib) => !lib.endsWith(".dll"));
996
1241
  const allTypeRoots = [...config.typeRoots, ...typeLibraries].map((p) => resolve(config.workspaceRoot, p));
997
1242
  const compilerOptions = {
@@ -999,6 +1244,8 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
999
1244
  sourceRoot: absoluteSourceRoot,
1000
1245
  rootNamespace: config.rootNamespace,
1001
1246
  typeRoots: allTypeRoots,
1247
+ surface: config.surface,
1248
+ useStandardLib: surfaceCapabilities.useStandardLib,
1002
1249
  verbose: false,
1003
1250
  };
1004
1251
  const graphResult = buildModuleDependencyGraph(absoluteEntryPoint, compilerOptions);
@@ -1055,6 +1302,7 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1055
1302
  };
1056
1303
  // 1) Per-namespace exported type aliases (including non-structural aliases).
1057
1304
  const exportedAliasesByNamespace = new Map();
1305
+ const sourceIndexByFileKeyForAliases = new Map();
1058
1306
  for (const m of modules) {
1059
1307
  const isExportedTypeAlias = (stmt) => stmt.kind === "typeAliasDeclaration" && stmt.isExported;
1060
1308
  const exportedAliases = m.body.filter(isExportedTypeAlias);
@@ -1064,27 +1312,60 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1064
1312
  const internalIndexDts = existsSync(info.internalIndexDtsPath)
1065
1313
  ? readFileSync(info.internalIndexDtsPath, "utf-8")
1066
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;
1067
1329
  for (const stmt of exportedAliases) {
1068
- const rendered = renderExportedTypeAlias(stmt, internalIndexDts);
1330
+ const rendered = renderExportedTypeAlias(stmt, internalIndexDts, sourceIndex.value.typeAliasesByName.get(stmt.name));
1069
1331
  if (!rendered.ok)
1070
1332
  return rendered;
1071
- const list = exportedAliasesByNamespace.get(m.namespace) ?? [];
1072
- list.push(rendered.value);
1073
- 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);
1074
1342
  }
1075
1343
  }
1076
1344
  const aliasStart = "// Tsonic source type aliases (generated)";
1077
1345
  const aliasEnd = "// End Tsonic source type aliases";
1078
- 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) {
1079
1349
  const info = facadesByNamespace.get(ns);
1080
1350
  if (!info)
1081
1351
  continue;
1082
- if (lines.length === 0)
1352
+ if (aliasData.lines.length === 0)
1083
1353
  continue;
1084
- 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));
1085
1355
  const body = unique.join("\n");
1086
1356
  const current = readFileSync(info.facadeDtsPath, "utf-8");
1087
- 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);
1088
1369
  writeFileSync(info.facadeDtsPath, next, "utf-8");
1089
1370
  }
1090
1371
  // 2) Entry-module re-export surface (matches TS semantics for library entrypoints).
@@ -1160,7 +1441,7 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1160
1441
  // graph and patching the published internal/index.d.ts:
1161
1442
  // - Re-apply ExtensionMethods wrappers (class + interface members)
1162
1443
  // - Preserve optional (`?`) semantics by allowing `undefined` on patched members
1163
- // - For exported interfaces, preserve source structural member types when safe
1444
+ // - Preserve TS structural typing on interfaces/type-literal aliases (exported + local)
1164
1445
  const sourceIndexByFileKey = new Map();
1165
1446
  for (const m of modules) {
1166
1447
  // Synthetic IR modules (e.g., program-wide anonymous type declarations) do not
@@ -1177,6 +1458,7 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1177
1458
  const overridesByInternalIndex = new Map();
1178
1459
  const brandOptionalTypesByInternalIndex = new Map();
1179
1460
  const functionSignaturesByFacade = new Map();
1461
+ const sourceTypeImportsByFacade = new Map();
1180
1462
  for (const m of modules) {
1181
1463
  if (m.filePath.startsWith("__tsonic/"))
1182
1464
  continue;
@@ -1187,13 +1469,37 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1187
1469
  const sourceIndex = sourceIndexByFileKey.get(moduleKey);
1188
1470
  if (!sourceIndex)
1189
1471
  continue;
1472
+ const allInterfaces = m.body.filter((s) => s.kind === "interfaceDeclaration");
1473
+ const allTypeAliases = m.body.filter((s) => s.kind === "typeAliasDeclaration");
1190
1474
  const hasExportedSourceFunctions = sourceIndex.exportedFunctionSignaturesByName.size > 0;
1191
1475
  if (exportedClasses.length === 0 &&
1192
1476
  exportedInterfaces.length === 0 &&
1193
1477
  exportedAliases.length === 0 &&
1478
+ allInterfaces.length === 0 &&
1479
+ allTypeAliases.length === 0 &&
1194
1480
  !hasExportedSourceFunctions)
1195
1481
  continue;
1196
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
+ }
1197
1503
  for (const [name, signatures,] of sourceIndex.exportedFunctionSignaturesByName) {
1198
1504
  if (signatures.length === 0)
1199
1505
  continue;
@@ -1203,6 +1509,28 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1203
1509
  list.push(...signatures);
1204
1510
  byName.set(name, list);
1205
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);
1206
1534
  }
1207
1535
  for (const cls of exportedClasses) {
1208
1536
  const memberTypes = sourceIndex.memberTypesByClassAndMember.get(cls.name);
@@ -1227,13 +1555,21 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1227
1555
  if (!wrappersResult.ok)
1228
1556
  return wrappersResult;
1229
1557
  const wrappers = wrappersResult.value;
1230
- if (wrappers.length === 0 && !sourceMember.isOptional)
1558
+ const canUseSourceTypeText = !typeNodeUsesImportedTypeNames(sourceMember.typeNode, sourceIndex.typeImportsByLocalName);
1559
+ if (!canUseSourceTypeText &&
1560
+ wrappers.length === 0 &&
1561
+ !sourceMember.isOptional) {
1231
1562
  continue;
1563
+ }
1232
1564
  const list = overridesByInternalIndex.get(info.internalIndexDtsPath) ?? [];
1233
1565
  list.push({
1234
1566
  namespace: m.namespace,
1235
1567
  className: cls.name,
1236
1568
  memberName: member.name,
1569
+ sourceTypeText: canUseSourceTypeText
1570
+ ? sourceMember.typeText
1571
+ : undefined,
1572
+ replaceWithSourceType: canUseSourceTypeText,
1237
1573
  isOptional: sourceMember.isOptional,
1238
1574
  wrappers,
1239
1575
  });
@@ -1272,14 +1608,11 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1272
1608
  : undefined,
1273
1609
  replaceWithSourceType: canUseSourceTypeText,
1274
1610
  isOptional: sourceMember.isOptional,
1611
+ emitOptionalPropertySyntax: true,
1275
1612
  wrappers,
1276
1613
  });
1277
1614
  overridesByInternalIndex.set(info.internalIndexDtsPath, list);
1278
1615
  }
1279
- const brandTargets = brandOptionalTypesByInternalIndex.get(info.internalIndexDtsPath) ??
1280
- new Set();
1281
- brandTargets.add(iface.name);
1282
- brandOptionalTypesByInternalIndex.set(info.internalIndexDtsPath, brandTargets);
1283
1616
  }
1284
1617
  for (const alias of exportedAliases) {
1285
1618
  const sourceAlias = sourceIndex.typeAliasesByName.get(alias.name);
@@ -1290,10 +1623,6 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1290
1623
  continue;
1291
1624
  const arity = sourceAlias.typeParameters.length;
1292
1625
  const internalAliasName = `${alias.name}__Alias${arity > 0 ? `_${arity}` : ""}`;
1293
- const brandTargets = brandOptionalTypesByInternalIndex.get(info.internalIndexDtsPath) ??
1294
- new Set();
1295
- brandTargets.add(internalAliasName);
1296
- brandOptionalTypesByInternalIndex.set(info.internalIndexDtsPath, brandTargets);
1297
1626
  for (const member of aliasType.members) {
1298
1627
  if (!ts.isPropertySignature(member))
1299
1628
  continue;
@@ -1345,6 +1674,16 @@ export const augmentLibraryBindingsFromSource = (config, bindingsOutDir) => {
1345
1674
  if (!result.ok)
1346
1675
  return result;
1347
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
+ }
1348
1687
  return { ok: true, value: undefined };
1349
1688
  };
1350
1689
  //# sourceMappingURL=library-bindings-augment.js.map