@formspec/build 0.1.0-alpha.44 → 0.1.0-alpha.46

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 (44) hide show
  1. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  2. package/dist/analyzer/program.d.ts +4 -1
  3. package/dist/analyzer/program.d.ts.map +1 -1
  4. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  5. package/dist/browser.cjs +33 -7
  6. package/dist/browser.cjs.map +1 -1
  7. package/dist/browser.js +34 -10
  8. package/dist/browser.js.map +1 -1
  9. package/dist/build-alpha.d.ts +106 -5
  10. package/dist/build-beta.d.ts +106 -5
  11. package/dist/build-internal.d.ts +106 -5
  12. package/dist/build.d.ts +106 -5
  13. package/dist/cli.cjs +364 -116
  14. package/dist/cli.cjs.map +1 -1
  15. package/dist/cli.js +360 -115
  16. package/dist/cli.js.map +1 -1
  17. package/dist/extensions/index.d.ts +1 -1
  18. package/dist/extensions/index.d.ts.map +1 -1
  19. package/dist/extensions/registry.d.ts +64 -5
  20. package/dist/extensions/registry.d.ts.map +1 -1
  21. package/dist/extensions/symbol-registry.d.ts +33 -0
  22. package/dist/extensions/symbol-registry.d.ts.map +1 -0
  23. package/dist/generators/class-schema.d.ts +32 -0
  24. package/dist/generators/class-schema.d.ts.map +1 -1
  25. package/dist/generators/discovered-schema.d.ts.map +1 -1
  26. package/dist/index.cjs +350 -109
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +351 -112
  31. package/dist/index.js.map +1 -1
  32. package/dist/internals.cjs +121 -23
  33. package/dist/internals.cjs.map +1 -1
  34. package/dist/internals.d.ts +2 -2
  35. package/dist/internals.d.ts.map +1 -1
  36. package/dist/internals.js +121 -26
  37. package/dist/internals.js.map +1 -1
  38. package/dist/json-schema/generator.d.ts.map +1 -1
  39. package/dist/metadata/collision-guards.d.ts.map +1 -1
  40. package/dist/metadata/policy.d.ts.map +1 -1
  41. package/dist/metadata/resolve.d.ts.map +1 -1
  42. package/dist/ui-schema/ir-generator.d.ts.map +1 -1
  43. package/dist/validate/constraint-validator.d.ts.map +1 -1
  44. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -229,7 +229,9 @@ function resolveTypeNodeMetadata(type, options) {
229
229
  case "object":
230
230
  return {
231
231
  ...type,
232
- properties: type.properties.map((property) => resolveObjectPropertyMetadata(property, options))
232
+ properties: type.properties.map(
233
+ (property) => resolveObjectPropertyMetadata(property, options)
234
+ )
233
235
  };
234
236
  case "record":
235
237
  return {
@@ -1655,10 +1657,7 @@ function generateJsonSchema(form, options) {
1655
1657
  const metadata = options?.metadata;
1656
1658
  const vendorPrefix = options?.vendorPrefix;
1657
1659
  const enumSerialization = options?.enumSerialization;
1658
- const ir = canonicalizeChainDSL(
1659
- form,
1660
- metadata !== void 0 ? { metadata } : void 0
1661
- );
1660
+ const ir = canonicalizeChainDSL(form, metadata !== void 0 ? { metadata } : void 0);
1662
1661
  const internalOptions = vendorPrefix === void 0 && enumSerialization === void 0 ? void 0 : {
1663
1662
  ...vendorPrefix !== void 0 && { vendorPrefix },
1664
1663
  ...enumSerialization !== void 0 && { enumSerialization }
@@ -1874,7 +1873,10 @@ function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
1874
1873
  break;
1875
1874
  }
1876
1875
  case "conditional": {
1877
- const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
1876
+ const newRule = createShowRule(
1877
+ fieldNameMap.get(element.fieldName) ?? element.fieldName,
1878
+ element.value
1879
+ );
1878
1880
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
1879
1881
  const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
1880
1882
  result.push(...childElements);
@@ -1931,7 +1933,7 @@ function generateUiSchema(form, options) {
1931
1933
 
1932
1934
  // src/index.ts
1933
1935
  import * as fs from "fs";
1934
- import * as path2 from "path";
1936
+ import * as path3 from "path";
1935
1937
 
1936
1938
  // src/extensions/registry.ts
1937
1939
  import {
@@ -1955,8 +1957,10 @@ function buildConstraintTagSources(extensions) {
1955
1957
  }
1956
1958
  function createExtensionRegistry(extensions) {
1957
1959
  const reservedTagSources = buildConstraintTagSources(extensions);
1960
+ let symbolMap = /* @__PURE__ */ new Map();
1958
1961
  const typeMap = /* @__PURE__ */ new Map();
1959
1962
  const typeNameMap = /* @__PURE__ */ new Map();
1963
+ const brandMap = /* @__PURE__ */ new Map();
1960
1964
  const constraintMap = /* @__PURE__ */ new Map();
1961
1965
  const constraintTagMap = /* @__PURE__ */ new Map();
1962
1966
  const builtinBroadeningMap = /* @__PURE__ */ new Map();
@@ -1980,6 +1984,20 @@ function createExtensionRegistry(extensions) {
1980
1984
  registration: type
1981
1985
  });
1982
1986
  }
1987
+ if (type.brand !== void 0) {
1988
+ if (type.brand === "__integerBrand") {
1989
+ throw new Error(
1990
+ `Brand "__integerBrand" is reserved for the builtin Integer type and cannot be registered by extensions`
1991
+ );
1992
+ }
1993
+ if (brandMap.has(type.brand)) {
1994
+ throw new Error(`Duplicate custom type brand: "${type.brand}"`);
1995
+ }
1996
+ brandMap.set(type.brand, {
1997
+ extensionId: ext.extensionId,
1998
+ registration: type
1999
+ });
2000
+ }
1983
2001
  if (type.builtinConstraintBroadenings !== void 0) {
1984
2002
  for (const broadening of type.builtinConstraintBroadenings) {
1985
2003
  const key = `${qualifiedId}:${broadening.tagName}`;
@@ -2049,7 +2067,10 @@ function createExtensionRegistry(extensions) {
2049
2067
  `Metadata tag "@${canonicalTagName}" conflicts with existing FormSpec tag "@${canonicalTagName}".`
2050
2068
  );
2051
2069
  }
2052
- if (Object.hasOwn(BUILTIN_CONSTRAINT_DEFINITIONS, normalizeConstraintTagName(canonicalTagName))) {
2070
+ if (Object.hasOwn(
2071
+ BUILTIN_CONSTRAINT_DEFINITIONS,
2072
+ normalizeConstraintTagName(canonicalTagName)
2073
+ )) {
2053
2074
  throw new Error(
2054
2075
  `Metadata tag "@${canonicalTagName}" conflicts with existing FormSpec tag "@${normalizeConstraintTagName(canonicalTagName)}".`
2055
2076
  );
@@ -2070,6 +2091,11 @@ function createExtensionRegistry(extensions) {
2070
2091
  extensions,
2071
2092
  findType: (typeId) => typeMap.get(typeId),
2072
2093
  findTypeByName: (typeName) => typeNameMap.get(typeName),
2094
+ findTypeByBrand: (brand) => brandMap.get(brand),
2095
+ findTypeBySymbol: (symbol) => symbolMap.get(symbol),
2096
+ setSymbolMap: (map) => {
2097
+ symbolMap = map;
2098
+ },
2073
2099
  findConstraint: (constraintId) => constraintMap.get(constraintId),
2074
2100
  findConstraintTag: (tagName) => constraintTagMap.get(normalizeFormSpecTagName(tagName)),
2075
2101
  findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
@@ -2140,7 +2166,7 @@ var jsonSchema7Schema = z3.lazy(
2140
2166
  );
2141
2167
 
2142
2168
  // src/generators/class-schema.ts
2143
- import * as ts5 from "typescript";
2169
+ import * as ts6 from "typescript";
2144
2170
 
2145
2171
  // src/analyzer/program.ts
2146
2172
  import * as ts4 from "typescript";
@@ -2952,30 +2978,70 @@ function isObjectType(type) {
2952
2978
  function isIntersectionType(type) {
2953
2979
  return !!(type.flags & ts3.TypeFlags.Intersection);
2954
2980
  }
2981
+ function collectBrandIdentifiers(type) {
2982
+ if (!type.isIntersection()) {
2983
+ return [];
2984
+ }
2985
+ const brands = [];
2986
+ for (const prop of type.getProperties()) {
2987
+ const decl = prop.valueDeclaration ?? prop.declarations?.[0];
2988
+ if (decl === void 0) continue;
2989
+ if (!ts3.isPropertySignature(decl) && !ts3.isPropertyDeclaration(decl)) continue;
2990
+ if (!ts3.isComputedPropertyName(decl.name)) continue;
2991
+ if (!ts3.isIdentifier(decl.name.expression)) continue;
2992
+ brands.push(decl.name.expression.text);
2993
+ }
2994
+ return brands;
2995
+ }
2996
+ function resolveCanonicalSymbol(type, checker) {
2997
+ const raw = type.aliasSymbol ?? type.getSymbol();
2998
+ if (raw === void 0) return void 0;
2999
+ return raw.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(raw) : raw;
3000
+ }
3001
+ function resolveSymbolBasedCustomType(type, extensionRegistry, checker) {
3002
+ if (extensionRegistry === void 0) {
3003
+ return null;
3004
+ }
3005
+ const canonical = resolveCanonicalSymbol(type, checker);
3006
+ if (canonical === void 0) {
3007
+ return null;
3008
+ }
3009
+ const registration = extensionRegistry.findTypeBySymbol(canonical);
3010
+ if (registration === void 0) {
3011
+ return null;
3012
+ }
3013
+ return {
3014
+ kind: "custom",
3015
+ typeId: `${registration.extensionId}/${registration.registration.typeName}`,
3016
+ payload: null
3017
+ };
3018
+ }
2955
3019
  function isIntegerBrandedType(type) {
2956
3020
  if (!type.isIntersection()) {
2957
3021
  return false;
2958
3022
  }
2959
- const hasNumberBase = type.types.some(
2960
- (member) => !!(member.flags & ts3.TypeFlags.Number)
2961
- );
3023
+ const hasNumberBase = type.types.some((member) => !!(member.flags & ts3.TypeFlags.Number));
2962
3024
  if (!hasNumberBase) {
2963
3025
  return false;
2964
3026
  }
2965
- return type.getProperties().some((prop) => {
2966
- const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
2967
- if (declaration === void 0) {
2968
- return false;
2969
- }
2970
- if (!ts3.isPropertySignature(declaration) && !ts3.isPropertyDeclaration(declaration)) {
2971
- return false;
2972
- }
2973
- const name = declaration.name;
2974
- if (!ts3.isComputedPropertyName(name)) {
2975
- return false;
3027
+ return collectBrandIdentifiers(type).includes("__integerBrand");
3028
+ }
3029
+ function resolveBrandedCustomType(type, extensionRegistry) {
3030
+ if (extensionRegistry === void 0) {
3031
+ return null;
3032
+ }
3033
+ for (const brand of collectBrandIdentifiers(type)) {
3034
+ const registration = extensionRegistry.findTypeByBrand(brand);
3035
+ if (registration === void 0) {
3036
+ continue;
2976
3037
  }
2977
- return ts3.isIdentifier(name.expression) && name.expression.text === "__integerBrand";
2978
- });
3038
+ return {
3039
+ kind: "custom",
3040
+ typeId: `${registration.extensionId}/${registration.registration.typeName}`,
3041
+ payload: null
3042
+ };
3043
+ }
3044
+ return null;
2979
3045
  }
2980
3046
  function isResolvableObjectLikeAliasTypeNode(typeNode) {
2981
3047
  if (ts3.isParenthesizedTypeNode(typeNode)) {
@@ -4088,6 +4154,14 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
4088
4154
  if (customType) {
4089
4155
  return customType;
4090
4156
  }
4157
+ const symbolCustomType = resolveSymbolBasedCustomType(type, extensionRegistry, checker);
4158
+ if (symbolCustomType) {
4159
+ return symbolCustomType;
4160
+ }
4161
+ const brandedCustomType = resolveBrandedCustomType(type, extensionRegistry);
4162
+ if (brandedCustomType) {
4163
+ return brandedCustomType;
4164
+ }
4091
4165
  const primitiveAlias = tryResolveNamedPrimitiveAlias(
4092
4166
  type,
4093
4167
  checker,
@@ -5032,7 +5106,7 @@ function createProgramContextFromProgram(program, filePath) {
5032
5106
  sourceFile
5033
5107
  };
5034
5108
  }
5035
- function createProgramContext(filePath) {
5109
+ function createProgramContext(filePath, additionalFiles) {
5036
5110
  const absolutePath = path.resolve(filePath);
5037
5111
  const fileDir = path.dirname(absolutePath);
5038
5112
  const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
@@ -5055,7 +5129,8 @@ function createProgramContext(filePath) {
5055
5129
  throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
5056
5130
  }
5057
5131
  compilerOptions = parsed.options;
5058
- fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
5132
+ const normalizedAdditional = (additionalFiles ?? []).map((f) => path.resolve(f));
5133
+ fileNames = [.../* @__PURE__ */ new Set([...parsed.fileNames, absolutePath, ...normalizedAdditional])];
5059
5134
  } else {
5060
5135
  compilerOptions = {
5061
5136
  target: ts4.ScriptTarget.ES2022,
@@ -5065,7 +5140,8 @@ function createProgramContext(filePath) {
5065
5140
  skipLibCheck: true,
5066
5141
  declaration: true
5067
5142
  };
5068
- fileNames = [absolutePath];
5143
+ const normalizedAdditional = (additionalFiles ?? []).map((f) => path.resolve(f));
5144
+ fileNames = [.../* @__PURE__ */ new Set([absolutePath, ...normalizedAdditional])];
5069
5145
  }
5070
5146
  const program = ts4.createProgram(fileNames, compilerOptions);
5071
5147
  const sourceFile = program.getSourceFile(absolutePath);
@@ -5369,10 +5445,134 @@ function makeFileProvenance(filePath) {
5369
5445
  };
5370
5446
  }
5371
5447
 
5448
+ // src/extensions/symbol-registry.ts
5449
+ import * as ts5 from "typescript";
5450
+ import * as path2 from "path";
5451
+ function buildSymbolMapFromConfig(configPath, program, checker, extensionRegistry) {
5452
+ const symbolMap = /* @__PURE__ */ new Map();
5453
+ const normalizedPath = path2.resolve(configPath);
5454
+ const configFile = program.getSourceFile(normalizedPath);
5455
+ if (configFile === void 0) {
5456
+ return symbolMap;
5457
+ }
5458
+ function visit(node) {
5459
+ if (ts5.isCallExpression(node) && isDefineCustomTypeCall(node, checker)) {
5460
+ processDefineCustomTypeCall(node);
5461
+ }
5462
+ ts5.forEachChild(node, visit);
5463
+ }
5464
+ function processDefineCustomTypeCall(call) {
5465
+ const typeArgNode = call.typeArguments?.[0];
5466
+ if (typeArgNode === void 0) {
5467
+ return;
5468
+ }
5469
+ const resolvedType = checker.getTypeFromTypeNode(typeArgNode);
5470
+ const canonical = resolveCanonicalSymbol2(resolvedType, checker);
5471
+ if (canonical === void 0) {
5472
+ return;
5473
+ }
5474
+ const typeName = extractTypeNameFromCallArg(call);
5475
+ if (typeName === null) {
5476
+ return;
5477
+ }
5478
+ let entry;
5479
+ const extensionId = extractEnclosingExtensionId(call, checker);
5480
+ if (extensionId !== null) {
5481
+ const reg = extensionRegistry.findType(`${extensionId}/${typeName}`);
5482
+ if (reg !== void 0) {
5483
+ entry = { extensionId, registration: reg };
5484
+ }
5485
+ }
5486
+ entry ??= findRegistrationByTypeName(extensionRegistry, typeName);
5487
+ if (entry === void 0) {
5488
+ return;
5489
+ }
5490
+ symbolMap.set(canonical, entry);
5491
+ }
5492
+ visit(configFile);
5493
+ return symbolMap;
5494
+ }
5495
+ function isDefineCustomTypeCall(node, checker) {
5496
+ if (node.typeArguments === void 0 || node.typeArguments.length === 0) return false;
5497
+ const callSymbol = checker.getSymbolAtLocation(node.expression);
5498
+ if (callSymbol !== void 0) {
5499
+ const resolved = callSymbol.flags & ts5.SymbolFlags.Alias ? checker.getAliasedSymbol(callSymbol) : callSymbol;
5500
+ const decl = resolved.declarations?.[0];
5501
+ if (decl !== void 0) {
5502
+ const sourceFile = decl.getSourceFile().fileName.replace(/\\/g, "/");
5503
+ return resolved.name === "defineCustomType" && // Match whether in node_modules/@formspec/core or monorepo packages/core.
5504
+ (sourceFile.includes("@formspec/core") || sourceFile.includes("/packages/core/"));
5505
+ }
5506
+ }
5507
+ return ts5.isIdentifier(node.expression) && node.expression.text === "defineCustomType";
5508
+ }
5509
+ function resolveCanonicalSymbol2(type, checker) {
5510
+ const raw = type.aliasSymbol ?? type.getSymbol();
5511
+ if (raw === void 0) return void 0;
5512
+ return raw.flags & ts5.SymbolFlags.Alias ? checker.getAliasedSymbol(raw) : raw;
5513
+ }
5514
+ function extractTypeNameFromCallArg(call) {
5515
+ const arg = call.arguments[0];
5516
+ if (arg === void 0 || !ts5.isObjectLiteralExpression(arg)) {
5517
+ return null;
5518
+ }
5519
+ const typeNameProp = arg.properties.find(
5520
+ (p) => ts5.isPropertyAssignment(p) && ts5.isIdentifier(p.name) && p.name.text === "typeName"
5521
+ );
5522
+ if (typeNameProp === void 0 || !ts5.isStringLiteral(typeNameProp.initializer)) {
5523
+ return null;
5524
+ }
5525
+ return typeNameProp.initializer.text;
5526
+ }
5527
+ function extractEnclosingExtensionId(call, checker) {
5528
+ for (let node = call.parent; !ts5.isSourceFile(node); node = node.parent) {
5529
+ if (ts5.isCallExpression(node) && isDefineExtensionCall(node, checker)) {
5530
+ return extractExtensionIdFromCallArg(node);
5531
+ }
5532
+ }
5533
+ return null;
5534
+ }
5535
+ function isDefineExtensionCall(node, checker) {
5536
+ const callSymbol = checker.getSymbolAtLocation(node.expression);
5537
+ if (callSymbol !== void 0) {
5538
+ const resolved = callSymbol.flags & ts5.SymbolFlags.Alias ? checker.getAliasedSymbol(callSymbol) : callSymbol;
5539
+ const decl = resolved.declarations?.[0];
5540
+ if (decl !== void 0) {
5541
+ const sourceFile = decl.getSourceFile().fileName.replace(/\\/g, "/");
5542
+ return resolved.name === "defineExtension" && (sourceFile.includes("@formspec/core") || sourceFile.includes("/packages/core/"));
5543
+ }
5544
+ }
5545
+ return ts5.isIdentifier(node.expression) && node.expression.text === "defineExtension";
5546
+ }
5547
+ function extractExtensionIdFromCallArg(call) {
5548
+ const arg = call.arguments[0];
5549
+ if (arg === void 0 || !ts5.isObjectLiteralExpression(arg)) {
5550
+ return null;
5551
+ }
5552
+ const prop = arg.properties.find(
5553
+ (p) => ts5.isPropertyAssignment(p) && ts5.isIdentifier(p.name) && p.name.text === "extensionId"
5554
+ );
5555
+ if (prop === void 0 || !ts5.isStringLiteral(prop.initializer)) {
5556
+ return null;
5557
+ }
5558
+ return prop.initializer.text;
5559
+ }
5560
+ function findRegistrationByTypeName(registry, typeName) {
5561
+ for (const ext of registry.extensions) {
5562
+ if (ext.types === void 0) {
5563
+ continue;
5564
+ }
5565
+ for (const type of ext.types) {
5566
+ if (type.typeName === typeName) {
5567
+ return { extensionId: ext.extensionId, registration: type };
5568
+ }
5569
+ }
5570
+ }
5571
+ return void 0;
5572
+ }
5573
+
5372
5574
  // src/validate/constraint-validator.ts
5373
- import {
5374
- analyzeConstraintTargets
5375
- } from "@formspec/analysis/internal";
5575
+ import { analyzeConstraintTargets } from "@formspec/analysis/internal";
5376
5576
  function validateFieldNode(ctx, field) {
5377
5577
  const analysis = analyzeConstraintTargets(
5378
5578
  field.name,
@@ -5503,7 +5703,8 @@ function formatLocation(location) {
5503
5703
  return `${location.file}:${String(location.line)}:${String(location.column)}`;
5504
5704
  }
5505
5705
  function generateSchemasFromClass(options) {
5506
- const ctx = createProgramContext(options.filePath);
5706
+ const additionalFiles = options.configPath !== void 0 ? [options.configPath] : void 0;
5707
+ const ctx = createProgramContext(options.filePath, additionalFiles);
5507
5708
  const classDecl = findClassByName(ctx.sourceFile, options.className);
5508
5709
  if (!classDecl) {
5509
5710
  throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
@@ -5568,7 +5769,8 @@ function generateSchemasDetailed(options) {
5568
5769
  function generateSchemasDetailedInternal(options) {
5569
5770
  let ctx;
5570
5771
  try {
5571
- ctx = createProgramContext(options.filePath);
5772
+ const additionalFiles = options.configPath !== void 0 ? [options.configPath] : void 0;
5773
+ ctx = createProgramContext(options.filePath, additionalFiles);
5572
5774
  } catch (error) {
5573
5775
  return {
5574
5776
  ok: false,
@@ -5607,13 +5809,16 @@ function generateSchemasFromProgramDetailedInternal(options) {
5607
5809
  }
5608
5810
  function generateSchemasBatch(options) {
5609
5811
  const contextCache = /* @__PURE__ */ new Map();
5812
+ const resolved = resolveOptions(options);
5813
+ let symbolMapProgram;
5610
5814
  return options.targets.map((target) => {
5611
5815
  let ctx;
5612
5816
  try {
5613
- const cacheKey = ts5.sys.useCaseSensitiveFileNames ? target.filePath : target.filePath.toLowerCase();
5817
+ const cacheKey = ts6.sys.useCaseSensitiveFileNames ? target.filePath : target.filePath.toLowerCase();
5614
5818
  const cachedContext = contextCache.get(cacheKey);
5615
5819
  if (cachedContext === void 0) {
5616
- ctx = createProgramContext(target.filePath);
5820
+ const additionalFiles = options.configPath !== void 0 ? [options.configPath] : void 0;
5821
+ ctx = createProgramContext(target.filePath, additionalFiles);
5617
5822
  contextCache.set(cacheKey, ctx);
5618
5823
  } else {
5619
5824
  ctx = cachedContext;
@@ -5624,9 +5829,25 @@ function generateSchemasBatch(options) {
5624
5829
  diagnostics: [createProgramContextFailureDiagnostic(target.filePath, error)]
5625
5830
  });
5626
5831
  }
5832
+ if (options.configPath !== void 0 && resolved.extensionRegistry !== void 0 && isMutableRegistry(resolved.extensionRegistry) && ctx.program !== symbolMapProgram) {
5833
+ const symbolMap = buildSymbolMapFromConfig(
5834
+ options.configPath,
5835
+ ctx.program,
5836
+ ctx.checker,
5837
+ resolved.extensionRegistry
5838
+ );
5839
+ resolved.extensionRegistry.setSymbolMap(symbolMap);
5840
+ symbolMapProgram = ctx.program;
5841
+ }
5627
5842
  return withTarget(
5628
5843
  target,
5629
- generateSchemasFromDetailedProgramContext(ctx, target.filePath, target.typeName, options)
5844
+ generateSchemasFromResolvedOptions(
5845
+ ctx,
5846
+ target.filePath,
5847
+ target.typeName,
5848
+ resolved,
5849
+ options.discriminator
5850
+ )
5630
5851
  );
5631
5852
  });
5632
5853
  }
@@ -5647,11 +5868,32 @@ function generateSchemasBatchFromProgram(options) {
5647
5868
  );
5648
5869
  });
5649
5870
  }
5871
+ function isMutableRegistry(reg) {
5872
+ return "setSymbolMap" in reg && typeof reg.setSymbolMap === "function";
5873
+ }
5874
+ function resolveStaticOptions(options) {
5875
+ const legacyRegistry = options.extensionRegistry;
5876
+ const configRegistry = legacyRegistry === void 0 && options.config?.extensions !== void 0 ? createExtensionRegistry(options.config.extensions) : void 0;
5877
+ return {
5878
+ extensionRegistry: legacyRegistry ?? configRegistry,
5879
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5880
+ vendorPrefix: options.vendorPrefix ?? options.config?.vendorPrefix,
5881
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5882
+ enumSerialization: options.enumSerialization ?? options.config?.enumSerialization,
5883
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5884
+ metadata: options.metadata ?? options.config?.metadata
5885
+ };
5886
+ }
5650
5887
  function resolveOptions(options) {
5651
5888
  const configRegistry = options.config?.extensions !== void 0 ? createExtensionRegistry(options.config.extensions) : void 0;
5889
+ const legacyRegistry = options.extensionRegistry;
5652
5890
  return {
5653
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5654
- extensionRegistry: options.extensionRegistry ?? configRegistry,
5891
+ // When the caller provides the deprecated extensionRegistry field directly,
5892
+ // it is typed as the read-only ExtensionRegistry interface. We cast here
5893
+ // because the legacy path was introduced before MutableExtensionRegistry was
5894
+ // split out; callers using createExtensionRegistry() always get a mutable
5895
+ // registry, and this cast is safe for all registries produced by this module.
5896
+ extensionRegistry: legacyRegistry ?? configRegistry,
5655
5897
  // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5656
5898
  vendorPrefix: options.vendorPrefix ?? options.config?.vendorPrefix,
5657
5899
  // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
@@ -5662,13 +5904,31 @@ function resolveOptions(options) {
5662
5904
  }
5663
5905
  function generateSchemasFromDetailedProgramContext(ctx, filePath, typeName, options) {
5664
5906
  const resolved = resolveOptions(options);
5907
+ if (options.configPath !== void 0 && resolved.extensionRegistry !== void 0 && isMutableRegistry(resolved.extensionRegistry)) {
5908
+ const symbolMap = buildSymbolMapFromConfig(
5909
+ options.configPath,
5910
+ ctx.program,
5911
+ ctx.checker,
5912
+ resolved.extensionRegistry
5913
+ );
5914
+ resolved.extensionRegistry.setSymbolMap(symbolMap);
5915
+ }
5916
+ return generateSchemasFromResolvedOptions(
5917
+ ctx,
5918
+ filePath,
5919
+ typeName,
5920
+ resolved,
5921
+ options.discriminator
5922
+ );
5923
+ }
5924
+ function generateSchemasFromResolvedOptions(ctx, filePath, typeName, resolved, discriminator) {
5665
5925
  const analysisResult = analyzeNamedTypeToIRFromProgramContextDetailed(
5666
5926
  ctx,
5667
5927
  filePath,
5668
5928
  typeName,
5669
5929
  resolved.extensionRegistry,
5670
5930
  resolved.metadata,
5671
- options.discriminator
5931
+ discriminator
5672
5932
  );
5673
5933
  if (!analysisResult.ok) {
5674
5934
  return {
@@ -5710,7 +5970,7 @@ function createProgramContextFailureDiagnostic(filePath, error) {
5710
5970
  }
5711
5971
 
5712
5972
  // src/static-build.ts
5713
- import * as ts6 from "typescript";
5973
+ import * as ts7 from "typescript";
5714
5974
  function toStaticBuildContext(context) {
5715
5975
  return context;
5716
5976
  }
@@ -5725,7 +5985,7 @@ function getModuleSymbol(context) {
5725
5985
  return context.checker.getSymbolAtLocation(context.sourceFile) ?? sourceFileWithSymbol.symbol;
5726
5986
  }
5727
5987
  function isSchemaSourceDeclaration(declaration) {
5728
- return ts6.isClassDeclaration(declaration) || ts6.isInterfaceDeclaration(declaration) || ts6.isTypeAliasDeclaration(declaration);
5988
+ return ts7.isClassDeclaration(declaration) || ts7.isInterfaceDeclaration(declaration) || ts7.isTypeAliasDeclaration(declaration);
5729
5989
  }
5730
5990
  function resolveModuleExport(context, exportName = "default") {
5731
5991
  const moduleSymbol = getModuleSymbol(context);
@@ -5736,14 +5996,14 @@ function resolveModuleExport(context, exportName = "default") {
5736
5996
  if (exportSymbol === null) {
5737
5997
  return null;
5738
5998
  }
5739
- return exportSymbol.flags & ts6.SymbolFlags.Alias ? context.checker.getAliasedSymbol(exportSymbol) : exportSymbol;
5999
+ return exportSymbol.flags & ts7.SymbolFlags.Alias ? context.checker.getAliasedSymbol(exportSymbol) : exportSymbol;
5740
6000
  }
5741
6001
  function resolveModuleExportDeclaration(context, exportName = "default") {
5742
6002
  return resolveModuleExport(context, exportName)?.declarations?.find(isSchemaSourceDeclaration) ?? null;
5743
6003
  }
5744
6004
 
5745
6005
  // src/generators/discovered-schema.ts
5746
- import * as ts7 from "typescript";
6006
+ import * as ts8 from "typescript";
5747
6007
  import { analyzeMetadataForNodeWithChecker as analyzeMetadataForNodeWithChecker2 } from "@formspec/analysis/internal";
5748
6008
  import { IR_VERSION as IR_VERSION3 } from "@formspec/core/internals";
5749
6009
  function toDiscoveredTypeSchemas(result, resolvedMetadata) {
@@ -5753,17 +6013,17 @@ function toDiscoveredTypeSchemas(result, resolvedMetadata) {
5753
6013
  };
5754
6014
  }
5755
6015
  function isNamedTypeDeclaration(declaration) {
5756
- return ts7.isClassDeclaration(declaration) || ts7.isInterfaceDeclaration(declaration) || ts7.isTypeAliasDeclaration(declaration);
6016
+ return ts8.isClassDeclaration(declaration) || ts8.isInterfaceDeclaration(declaration) || ts8.isTypeAliasDeclaration(declaration);
5757
6017
  }
5758
6018
  function hasConcreteTypeArguments(type, checker) {
5759
6019
  if ("aliasTypeArguments" in type && Array.isArray(type.aliasTypeArguments) && type.aliasTypeArguments.length > 0) {
5760
6020
  return true;
5761
6021
  }
5762
- if ((type.flags & ts7.TypeFlags.Object) === 0) {
6022
+ if ((type.flags & ts8.TypeFlags.Object) === 0) {
5763
6023
  return false;
5764
6024
  }
5765
6025
  const objectType = type;
5766
- if ((objectType.objectFlags & ts7.ObjectFlags.Reference) === 0) {
6026
+ if ((objectType.objectFlags & ts8.ObjectFlags.Reference) === 0) {
5767
6027
  return false;
5768
6028
  }
5769
6029
  return checker.getTypeArguments(objectType).length > 0;
@@ -5776,13 +6036,13 @@ function getNamedTypeDeclaration2(type) {
5776
6036
  return declaration;
5777
6037
  }
5778
6038
  }
5779
- const aliasDeclaration = type.aliasSymbol?.declarations?.find(ts7.isTypeAliasDeclaration);
6039
+ const aliasDeclaration = type.aliasSymbol?.declarations?.find(ts8.isTypeAliasDeclaration);
5780
6040
  return aliasDeclaration;
5781
6041
  }
5782
6042
  function getFallbackName(sourceNode, fallback = "AnonymousType") {
5783
6043
  if (sourceNode !== void 0 && "name" in sourceNode) {
5784
6044
  const namedNode = sourceNode;
5785
- if (namedNode.name !== void 0 && ts7.isIdentifier(namedNode.name)) {
6045
+ if (namedNode.name !== void 0 && ts8.isIdentifier(namedNode.name)) {
5786
6046
  return namedNode.name.text;
5787
6047
  }
5788
6048
  }
@@ -5818,10 +6078,9 @@ function omitApiName(metadata) {
5818
6078
  const { apiName: _apiName, ...rest } = metadata;
5819
6079
  return Object.keys(rest).length > 0 ? rest : void 0;
5820
6080
  }
5821
- function enforceRequiredMetadata(metadata, declarationKind, logicalName, options) {
6081
+ function enforceRequiredMetadata(metadata, declarationKind, logicalName, metadataPolicy) {
5822
6082
  const declarationPolicy = getDeclarationMetadataPolicy(
5823
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5824
- normalizeMetadataPolicy(options.metadata),
6083
+ normalizeMetadataPolicy(metadataPolicy),
5825
6084
  declarationKind
5826
6085
  );
5827
6086
  if (metadata?.apiName === void 0 && declarationPolicy.apiName.mode === "require-explicit") {
@@ -5866,7 +6125,7 @@ function describeRootType(rootType, typeRegistry, fallbackName) {
5866
6125
  type: definition.type
5867
6126
  };
5868
6127
  }
5869
- function toStandaloneJsonSchema(root, typeRegistry, options) {
6128
+ function toStandaloneJsonSchema(root, typeRegistry, resolved) {
5870
6129
  const syntheticFieldMetadata = omitApiName(root.metadata);
5871
6130
  const syntheticField = {
5872
6131
  kind: "field",
@@ -5895,23 +6154,16 @@ function toStandaloneJsonSchema(root, typeRegistry, options) {
5895
6154
  provenance: syntheticField.provenance
5896
6155
  },
5897
6156
  {
5898
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5899
- policy: normalizeMetadataPolicy(options?.metadata),
6157
+ policy: normalizeMetadataPolicy(resolved?.metadata),
5900
6158
  surface: "tsdoc",
5901
6159
  rootLogicalName: root.name
5902
6160
  }
5903
6161
  );
5904
- const schema = generateJsonSchemaFromIR(
5905
- ir,
5906
- {
5907
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5908
- extensionRegistry: options?.extensionRegistry,
5909
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5910
- enumSerialization: options?.enumSerialization,
5911
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5912
- vendorPrefix: options?.vendorPrefix
5913
- }
5914
- );
6162
+ const schema = generateJsonSchemaFromIR(ir, {
6163
+ extensionRegistry: resolved?.extensionRegistry,
6164
+ enumSerialization: resolved?.enumSerialization,
6165
+ vendorPrefix: resolved?.vendorPrefix
6166
+ });
5915
6167
  const result = schema.properties?.["__result"];
5916
6168
  if (result === void 0) {
5917
6169
  throw new Error("FormSpec failed to extract the standalone schema root from the synthetic IR.");
@@ -5928,26 +6180,23 @@ function toStandaloneJsonSchema(root, typeRegistry, options) {
5928
6180
  $defs: schema.$defs
5929
6181
  };
5930
6182
  }
5931
- function generateSchemasFromAnalysis(analysis, filePath, options) {
6183
+ function generateSchemasFromAnalysis(analysis, filePath, resolved) {
5932
6184
  return toDiscoveredTypeSchemas(
5933
6185
  generateClassSchemas(
5934
6186
  analysis,
5935
6187
  { file: filePath },
5936
6188
  {
5937
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5938
- extensionRegistry: options?.extensionRegistry,
5939
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5940
- enumSerialization: options?.enumSerialization,
5941
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5942
- metadata: options?.metadata,
5943
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5944
- vendorPrefix: options?.vendorPrefix
6189
+ extensionRegistry: resolved?.extensionRegistry,
6190
+ enumSerialization: resolved?.enumSerialization,
6191
+ metadata: resolved?.metadata,
6192
+ vendorPrefix: resolved?.vendorPrefix
5945
6193
  }
5946
6194
  ),
5947
6195
  analysis.metadata
5948
6196
  );
5949
6197
  }
5950
6198
  function generateSchemasFromResolvedType(options, skipNamedDeclaration = false, rootOverride) {
6199
+ const resolved = resolveStaticOptions(options);
5951
6200
  const namedDeclaration = skipNamedDeclaration || hasConcreteTypeArguments(options.type, options.context.checker) ? void 0 : getNamedTypeDeclaration2(options.type);
5952
6201
  if (namedDeclaration !== void 0) {
5953
6202
  return generateSchemasFromDeclaration({
@@ -5965,10 +6214,8 @@ function generateSchemasFromResolvedType(options, skipNamedDeclaration = false,
5965
6214
  typeRegistry,
5966
6215
  /* @__PURE__ */ new Set(),
5967
6216
  options.sourceNode,
5968
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5969
- createAnalyzerMetadataPolicy(options.metadata, options.discriminator),
5970
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
5971
- options.extensionRegistry,
6217
+ createAnalyzerMetadataPolicy(resolved.metadata, options.discriminator),
6218
+ resolved.extensionRegistry,
5972
6219
  diagnostics
5973
6220
  );
5974
6221
  if (diagnostics.length > 0) {
@@ -5999,71 +6246,64 @@ function generateSchemasFromResolvedType(options, skipNamedDeclaration = false,
5999
6246
  root.annotations
6000
6247
  ),
6001
6248
  filePath,
6002
- options
6249
+ resolved
6003
6250
  );
6004
6251
  }
6005
6252
  return {
6006
- jsonSchema: toStandaloneJsonSchema(root, typeRegistry, options),
6253
+ jsonSchema: toStandaloneJsonSchema(root, typeRegistry, resolved),
6007
6254
  uiSchema: null,
6008
6255
  ...root.metadata !== void 0 && { resolvedMetadata: root.metadata }
6009
6256
  };
6010
6257
  }
6011
6258
  function generateSchemasFromDeclaration(options) {
6012
6259
  const filePath = options.declaration.getSourceFile().fileName;
6013
- if (ts7.isClassDeclaration(options.declaration)) {
6260
+ const resolved = resolveStaticOptions(options);
6261
+ if (ts8.isClassDeclaration(options.declaration)) {
6014
6262
  return generateSchemasFromAnalysis(
6015
6263
  analyzeClassToIR(
6016
6264
  options.declaration,
6017
6265
  options.context.checker,
6018
6266
  filePath,
6019
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6020
- options.extensionRegistry,
6021
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6022
- options.metadata,
6267
+ resolved.extensionRegistry,
6268
+ resolved.metadata,
6023
6269
  options.discriminator
6024
6270
  ),
6025
6271
  filePath,
6026
- options
6272
+ resolved
6027
6273
  );
6028
6274
  }
6029
- if (ts7.isInterfaceDeclaration(options.declaration)) {
6275
+ if (ts8.isInterfaceDeclaration(options.declaration)) {
6030
6276
  return generateSchemasFromAnalysis(
6031
6277
  analyzeInterfaceToIR(
6032
6278
  options.declaration,
6033
6279
  options.context.checker,
6034
6280
  filePath,
6035
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6036
- options.extensionRegistry,
6037
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6038
- options.metadata,
6281
+ resolved.extensionRegistry,
6282
+ resolved.metadata,
6039
6283
  options.discriminator
6040
6284
  ),
6041
6285
  filePath,
6042
- options
6286
+ resolved
6043
6287
  );
6044
6288
  }
6045
- if (ts7.isTypeAliasDeclaration(options.declaration)) {
6289
+ if (ts8.isTypeAliasDeclaration(options.declaration)) {
6046
6290
  const analyzedAlias = analyzeTypeAliasToIR(
6047
6291
  options.declaration,
6048
6292
  options.context.checker,
6049
6293
  filePath,
6050
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6051
- options.extensionRegistry,
6052
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6053
- options.metadata,
6294
+ resolved.extensionRegistry,
6295
+ resolved.metadata,
6054
6296
  options.discriminator
6055
6297
  );
6056
6298
  if (analyzedAlias.ok) {
6057
- return generateSchemasFromAnalysis(analyzedAlias.analysis, filePath, options);
6299
+ return generateSchemasFromAnalysis(analyzedAlias.analysis, filePath, resolved);
6058
6300
  }
6059
6301
  const aliasRootInfo = analyzeDeclarationRootInfo(
6060
6302
  options.declaration,
6061
6303
  options.context.checker,
6062
6304
  filePath,
6063
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6064
- options.extensionRegistry,
6065
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6066
- options.metadata
6305
+ resolved.extensionRegistry,
6306
+ resolved.metadata
6067
6307
  );
6068
6308
  if (aliasRootInfo.diagnostics.length > 0) {
6069
6309
  const diagnosticDetails = aliasRootInfo.diagnostics.map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`).join("; ");
@@ -6105,7 +6345,7 @@ function generateSchemasFromReturnType(options) {
6105
6345
  const returnType = signature !== void 0 ? options.context.checker.getReturnTypeOfSignature(signature) : options.context.checker.getTypeAtLocation(options.declaration);
6106
6346
  const type = unwrapPromiseType(options.context.checker, returnType);
6107
6347
  const sourceNode = type !== returnType ? unwrapPromiseTypeNode(options.declaration.type) ?? options.declaration.type ?? options.declaration : options.declaration.type ?? options.declaration;
6108
- const fallbackName = options.declaration.name !== void 0 && ts7.isIdentifier(options.declaration.name) ? `${options.declaration.name.text}ReturnType` : "ReturnType";
6348
+ const fallbackName = options.declaration.name !== void 0 && ts8.isIdentifier(options.declaration.name) ? `${options.declaration.name.text}ReturnType` : "ReturnType";
6109
6349
  return generateSchemasFromResolvedType({
6110
6350
  ...options,
6111
6351
  type,
@@ -6114,20 +6354,19 @@ function generateSchemasFromReturnType(options) {
6114
6354
  });
6115
6355
  }
6116
6356
  function resolveDeclarationMetadata(options) {
6357
+ const resolved = resolveStaticOptions(options);
6117
6358
  const analysis = analyzeMetadataForNodeWithChecker2({
6118
6359
  checker: options.context.checker,
6119
6360
  node: options.declaration,
6120
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6121
- metadata: options.metadata,
6122
- // eslint-disable-next-line @typescript-eslint/no-deprecated -- migration bridge reads deprecated fields
6123
- extensions: options.extensionRegistry?.extensions,
6361
+ metadata: resolved.metadata,
6362
+ extensions: resolved.extensionRegistry?.extensions,
6124
6363
  buildContext: options.context
6125
6364
  });
6126
6365
  if (analysis === null) {
6127
6366
  return void 0;
6128
6367
  }
6129
6368
  const metadata = analysis.resolvedMetadata;
6130
- enforceRequiredMetadata(metadata, analysis.declarationKind, analysis.logicalName, options);
6369
+ enforceRequiredMetadata(metadata, analysis.declarationKind, analysis.logicalName, resolved.metadata);
6131
6370
  return metadata;
6132
6371
  }
6133
6372
  function unwrapPromiseType(checker, type) {
@@ -6140,14 +6379,14 @@ function unwrapPromiseTypeNode(typeNode) {
6140
6379
  if (typeNode === void 0) {
6141
6380
  return void 0;
6142
6381
  }
6143
- if (ts7.isParenthesizedTypeNode(typeNode)) {
6382
+ if (ts8.isParenthesizedTypeNode(typeNode)) {
6144
6383
  const unwrapped = unwrapPromiseTypeNode(typeNode.type);
6145
6384
  return unwrapped ?? typeNode;
6146
6385
  }
6147
6386
  return isPromiseTypeReferenceNode(typeNode) ? typeNode.typeArguments[0] : typeNode;
6148
6387
  }
6149
6388
  function isPromiseTypeReferenceNode(typeNode) {
6150
- return ts7.isTypeReferenceNode(typeNode) && ts7.isIdentifier(typeNode.typeName) && typeNode.typeName.text === "Promise" && typeNode.typeArguments !== void 0 && typeNode.typeArguments.length > 0;
6389
+ return ts8.isTypeReferenceNode(typeNode) && ts8.isIdentifier(typeNode.typeName) && typeNode.typeName.text === "Promise" && typeNode.typeArguments !== void 0 && typeNode.typeArguments.length > 0;
6151
6390
  }
6152
6391
 
6153
6392
  // src/generators/mixed-authoring.ts
@@ -6369,8 +6608,8 @@ function writeSchemas(form, options) {
6369
6608
  if (!fs.existsSync(outDir)) {
6370
6609
  fs.mkdirSync(outDir, { recursive: true });
6371
6610
  }
6372
- const jsonSchemaPath = path2.join(outDir, `${name}-schema.json`);
6373
- const uiSchemaPath = path2.join(outDir, `${name}-uischema.json`);
6611
+ const jsonSchemaPath = path3.join(outDir, `${name}-schema.json`);
6612
+ const uiSchemaPath = path3.join(outDir, `${name}-uischema.json`);
6374
6613
  fs.writeFileSync(jsonSchemaPath, JSON.stringify(jsonSchema, null, indent));
6375
6614
  fs.writeFileSync(uiSchemaPath, JSON.stringify(uiSchema2, null, indent));
6376
6615
  return { jsonSchemaPath, uiSchemaPath };