@formspec/build 0.1.0-alpha.40 → 0.1.0-alpha.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -58,11 +58,35 @@ function normalizeDeclarationPolicy(input) {
58
58
  displayName: normalizeScalarPolicy(input?.displayName)
59
59
  };
60
60
  }
61
+ function normalizeEnumMemberDisplayNamePolicy(input) {
62
+ if (input?.mode === "infer-if-missing") {
63
+ return {
64
+ mode: "infer-if-missing",
65
+ infer: input.infer
66
+ };
67
+ }
68
+ if (input?.mode === "require-explicit") {
69
+ return {
70
+ mode: "require-explicit",
71
+ infer: () => ""
72
+ };
73
+ }
74
+ return {
75
+ mode: "disabled",
76
+ infer: () => ""
77
+ };
78
+ }
79
+ function normalizeEnumMemberPolicy(input) {
80
+ return {
81
+ displayName: normalizeEnumMemberDisplayNamePolicy(input?.displayName)
82
+ };
83
+ }
61
84
  function normalizeMetadataPolicy(input) {
62
85
  return {
63
86
  type: normalizeDeclarationPolicy(input?.type),
64
87
  field: normalizeDeclarationPolicy(input?.field),
65
- method: normalizeDeclarationPolicy(input?.method)
88
+ method: normalizeDeclarationPolicy(input?.method),
89
+ enumMember: normalizeEnumMemberPolicy(input?.enumMember)
66
90
  };
67
91
  }
68
92
  function getDeclarationMetadataPolicy(policy, declarationKind) {
@@ -175,6 +199,40 @@ function pickResolvedMetadataValue(baseValue, overlayValue) {
175
199
  }
176
200
  return baseValue ?? overlayValue;
177
201
  }
202
+ function resolveEnumMemberDisplayName(current, policy, context) {
203
+ if (current !== void 0) {
204
+ return current;
205
+ }
206
+ if (policy.mode === "require-explicit") {
207
+ throw new Error(
208
+ `Metadata policy requires explicit displayName for enum member "${context.logicalName}" on the ${context.surface} surface.`
209
+ );
210
+ }
211
+ if (policy.mode !== "infer-if-missing") {
212
+ return void 0;
213
+ }
214
+ const inferredValue = policy.infer(context).trim();
215
+ return inferredValue !== "" ? inferredValue : void 0;
216
+ }
217
+ function resolveEnumTypeMetadata(type, options) {
218
+ const members = type.members.map((member) => {
219
+ const displayName = resolveEnumMemberDisplayName(
220
+ member.displayName,
221
+ options.policy.enumMember.displayName,
222
+ {
223
+ surface: options.surface,
224
+ logicalName: String(member.value),
225
+ memberValue: member.value,
226
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
227
+ }
228
+ );
229
+ if (displayName === member.displayName) {
230
+ return member;
231
+ }
232
+ return displayName === void 0 ? { value: member.value } : { value: member.value, displayName };
233
+ });
234
+ return members.some((member, index) => member !== type.members[index]) ? { ...type, members } : type;
235
+ }
178
236
  function resolveTypeNodeMetadata(type, options) {
179
237
  switch (type.kind) {
180
238
  case "array":
@@ -197,9 +255,10 @@ function resolveTypeNodeMetadata(type, options) {
197
255
  ...type,
198
256
  members: type.members.map((member) => resolveTypeNodeMetadata(member, options))
199
257
  };
258
+ case "enum":
259
+ return resolveEnumTypeMetadata(type, options);
200
260
  case "reference":
201
261
  case "primitive":
202
- case "enum":
203
262
  case "dynamic":
204
263
  case "custom":
205
264
  return type;
@@ -302,11 +361,10 @@ function getDisplayName(metadata) {
302
361
  return metadata?.displayName?.value;
303
362
  }
304
363
  function resolveFormIRMetadata(ir, options) {
305
- const rootLogicalName = options.rootLogicalName ?? ir.name ?? "FormSpec";
306
- const metadata = resolveResolvedMetadata(ir.metadata, options.policy.type, {
364
+ const metadata = options.resolveRootTypeMetadata === false ? ir.metadata : resolveResolvedMetadata(ir.metadata, options.policy.type, {
307
365
  surface: options.surface,
308
366
  declarationKind: "type",
309
- logicalName: rootLogicalName,
367
+ logicalName: options.rootLogicalName ?? ir.name ?? "FormSpec",
310
368
  ...options.buildContext !== void 0 && { buildContext: options.buildContext }
311
369
  });
312
370
  return {
@@ -352,7 +410,7 @@ function canonicalizeChainDSL(form, options) {
352
410
  const metadataPolicy = normalizeMetadataPolicy(
353
411
  options?.metadata ?? _getFormSpecMetadataPolicy(form)
354
412
  );
355
- return {
413
+ const ir = {
356
414
  kind: "form-ir",
357
415
  irVersion: IR_VERSION,
358
416
  elements: canonicalizeElements(form.elements, metadataPolicy),
@@ -360,6 +418,13 @@ function canonicalizeChainDSL(form, options) {
360
418
  typeRegistry: {},
361
419
  provenance: CHAIN_DSL_PROVENANCE
362
420
  };
421
+ return resolveFormIRMetadata(ir, {
422
+ policy: metadataPolicy,
423
+ surface: "chain-dsl",
424
+ // Chain DSL has no root/type-metadata authoring surface, so only resolve
425
+ // field/type-registry metadata and enum-member labels here.
426
+ resolveRootTypeMetadata: false
427
+ });
363
428
  }
364
429
  function canonicalizeElements(elements, metadataPolicy) {
365
430
  return elements.map((element) => canonicalizeElement(element, metadataPolicy));
@@ -931,17 +996,25 @@ var init_collision_guards = __esm({
931
996
  // src/json-schema/ir-generator.ts
932
997
  function makeContext(options) {
933
998
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
999
+ const rawEnumSerialization = options?.enumSerialization;
934
1000
  if (!vendorPrefix.startsWith("x-")) {
935
1001
  throw new Error(
936
1002
  `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
937
1003
  );
938
1004
  }
1005
+ if (rawEnumSerialization !== void 0 && rawEnumSerialization !== "enum" && rawEnumSerialization !== "oneOf") {
1006
+ throw new Error(
1007
+ `Invalid enumSerialization "${rawEnumSerialization}". Expected "enum" or "oneOf".`
1008
+ );
1009
+ }
1010
+ const enumSerialization = rawEnumSerialization ?? "enum";
939
1011
  return {
940
1012
  defs: {},
941
1013
  typeNameMap: {},
942
1014
  typeRegistry: {},
943
1015
  extensionRegistry: options?.extensionRegistry,
944
- vendorPrefix
1016
+ vendorPrefix,
1017
+ enumSerialization
945
1018
  };
946
1019
  }
947
1020
  function generateJsonSchemaFromIR(ir, options) {
@@ -1115,7 +1188,7 @@ function generateTypeNode(type, ctx) {
1115
1188
  case "primitive":
1116
1189
  return generatePrimitiveType(type);
1117
1190
  case "enum":
1118
- return generateEnumType(type);
1191
+ return generateEnumType(type, ctx);
1119
1192
  case "array":
1120
1193
  return generateArrayType(type, ctx);
1121
1194
  case "object":
@@ -1141,20 +1214,37 @@ function generatePrimitiveType(type) {
1141
1214
  type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
1142
1215
  };
1143
1216
  }
1144
- function generateEnumType(type) {
1145
- const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
1146
- if (hasDisplayNames) {
1217
+ function generateEnumType(type, ctx) {
1218
+ if (ctx.enumSerialization === "oneOf") {
1147
1219
  return {
1148
- oneOf: type.members.map((m) => {
1149
- const entry = { const: m.value };
1150
- if (m.displayName !== void 0) {
1151
- entry.title = m.displayName;
1152
- }
1153
- return entry;
1154
- })
1220
+ oneOf: type.members.map((m) => ({
1221
+ const: m.value,
1222
+ title: m.displayName ?? String(m.value)
1223
+ }))
1155
1224
  };
1156
1225
  }
1157
- return { enum: type.members.map((m) => m.value) };
1226
+ const schema = { enum: type.members.map((m) => m.value) };
1227
+ const displayNames = buildEnumDisplayNameExtension(type);
1228
+ if (displayNames !== void 0) {
1229
+ schema[`${ctx.vendorPrefix}-display-names`] = displayNames;
1230
+ }
1231
+ return schema;
1232
+ }
1233
+ function buildEnumDisplayNameExtension(type) {
1234
+ if (!type.members.some((member) => member.displayName !== void 0)) {
1235
+ return void 0;
1236
+ }
1237
+ const displayNames = /* @__PURE__ */ Object.create(null);
1238
+ for (const member of type.members) {
1239
+ const key = String(member.value);
1240
+ if (Object.hasOwn(displayNames, key)) {
1241
+ throw new Error(
1242
+ `Enum display-name key "${key}" is ambiguous after stringification. Use oneOf serialization for mixed string/number enum values that collide.`
1243
+ );
1244
+ }
1245
+ displayNames[key] = member.displayName ?? key;
1246
+ }
1247
+ return displayNames;
1158
1248
  }
1159
1249
  function generateArrayType(type, ctx) {
1160
1250
  return {
@@ -1554,11 +1644,17 @@ var init_ir_generator = __esm({
1554
1644
 
1555
1645
  // src/json-schema/generator.ts
1556
1646
  function generateJsonSchema(form, options) {
1647
+ const metadata = options?.metadata;
1648
+ const vendorPrefix = options?.vendorPrefix;
1649
+ const enumSerialization = options?.enumSerialization;
1557
1650
  const ir = canonicalizeChainDSL(
1558
1651
  form,
1559
- options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1652
+ metadata !== void 0 ? { metadata } : void 0
1560
1653
  );
1561
- const internalOptions = options?.vendorPrefix === void 0 ? void 0 : { vendorPrefix: options.vendorPrefix };
1654
+ const internalOptions = vendorPrefix === void 0 && enumSerialization === void 0 ? void 0 : {
1655
+ ...vendorPrefix !== void 0 && { vendorPrefix },
1656
+ ...enumSerialization !== void 0 && { enumSerialization }
1657
+ };
1562
1658
  return generateJsonSchemaFromIR(ir, internalOptions);
1563
1659
  }
1564
1660
  var init_generator = __esm({
@@ -5606,6 +5702,7 @@ function generateSchemasFromClass(options) {
5606
5702
  {
5607
5703
  extensionRegistry: options.extensionRegistry,
5608
5704
  metadata: options.metadata,
5705
+ enumSerialization: options.enumSerialization,
5609
5706
  vendorPrefix: options.vendorPrefix
5610
5707
  }
5611
5708
  );
@@ -5735,6 +5832,7 @@ function generateSchemasFromDetailedProgramContext(ctx, filePath, typeName, opti
5735
5832
  {
5736
5833
  extensionRegistry: options.extensionRegistry,
5737
5834
  metadata: options.metadata,
5835
+ enumSerialization: options.enumSerialization,
5738
5836
  vendorPrefix: options.vendorPrefix
5739
5837
  }
5740
5838
  );
@@ -5951,7 +6049,7 @@ function toStandaloneJsonSchema(root, typeRegistry, options) {
5951
6049
  column: 0
5952
6050
  }
5953
6051
  };
5954
- const schema = generateJsonSchemaFromIR(
6052
+ const ir = resolveFormIRMetadata(
5955
6053
  {
5956
6054
  kind: "form-ir",
5957
6055
  name: root.name,
@@ -5962,8 +6060,17 @@ function toStandaloneJsonSchema(root, typeRegistry, options) {
5962
6060
  typeRegistry,
5963
6061
  provenance: syntheticField.provenance
5964
6062
  },
6063
+ {
6064
+ policy: normalizeMetadataPolicy(options?.metadata),
6065
+ surface: "tsdoc",
6066
+ rootLogicalName: root.name
6067
+ }
6068
+ );
6069
+ const schema = generateJsonSchemaFromIR(
6070
+ ir,
5965
6071
  {
5966
6072
  extensionRegistry: options?.extensionRegistry,
6073
+ enumSerialization: options?.enumSerialization,
5967
6074
  vendorPrefix: options?.vendorPrefix
5968
6075
  }
5969
6076
  );
@@ -5990,6 +6097,7 @@ function generateSchemasFromAnalysis(analysis, filePath, options) {
5990
6097
  { file: filePath },
5991
6098
  {
5992
6099
  extensionRegistry: options?.extensionRegistry,
6100
+ enumSerialization: options?.enumSerialization,
5993
6101
  metadata: options?.metadata,
5994
6102
  vendorPrefix: options?.vendorPrefix
5995
6103
  }
@@ -6435,8 +6543,19 @@ function buildFormSchemas(form, options) {
6435
6543
  };
6436
6544
  }
6437
6545
  function writeSchemas(form, options) {
6438
- const { outDir, name = "schema", indent = 2, vendorPrefix, metadata } = options;
6439
- const buildOptions = vendorPrefix === void 0 && metadata === void 0 ? void 0 : { vendorPrefix, metadata };
6546
+ const {
6547
+ outDir,
6548
+ name = "schema",
6549
+ indent = 2,
6550
+ vendorPrefix,
6551
+ enumSerialization,
6552
+ metadata
6553
+ } = options;
6554
+ const buildOptions = vendorPrefix === void 0 && enumSerialization === void 0 && metadata === void 0 ? void 0 : {
6555
+ ...vendorPrefix !== void 0 && { vendorPrefix },
6556
+ ...enumSerialization !== void 0 && { enumSerialization },
6557
+ ...metadata !== void 0 && { metadata }
6558
+ };
6440
6559
  const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
6441
6560
  if (!fs.existsSync(outDir)) {
6442
6561
  fs.mkdirSync(outDir, { recursive: true });
@@ -6481,6 +6600,8 @@ Usage:
6481
6600
  Options:
6482
6601
  -o, --out-dir <dir> Output directory (default: ./generated)
6483
6602
  -n, --name <name> Base name for output files (default: derived from input)
6603
+ --enum-serialization <enum|oneOf>
6604
+ Enum JSON Schema representation (default: enum)
6484
6605
  -h, --help Show this help message
6485
6606
 
6486
6607
  Example:
@@ -6497,6 +6618,7 @@ function parseArgs(args) {
6497
6618
  const positional = [];
6498
6619
  let outDir = "./generated";
6499
6620
  let name = "";
6621
+ let enumSerialization = "enum";
6500
6622
  for (let i = 0; i < args.length; i++) {
6501
6623
  const arg = args[i];
6502
6624
  if (arg === void 0) continue;
@@ -6524,6 +6646,20 @@ function parseArgs(args) {
6524
6646
  i++;
6525
6647
  continue;
6526
6648
  }
6649
+ if (arg === "--enum-serialization") {
6650
+ const nextArg = args[i + 1];
6651
+ if (!nextArg) {
6652
+ console.error("Error: --enum-serialization requires a value");
6653
+ return null;
6654
+ }
6655
+ if (nextArg !== "enum" && nextArg !== "oneOf") {
6656
+ console.error('Error: --enum-serialization must be "enum" or "oneOf"');
6657
+ return null;
6658
+ }
6659
+ enumSerialization = nextArg;
6660
+ i++;
6661
+ continue;
6662
+ }
6527
6663
  if (arg.startsWith("-")) {
6528
6664
  console.error(`Error: Unknown option: ${arg}`);
6529
6665
  return null;
@@ -6543,7 +6679,7 @@ function parseArgs(args) {
6543
6679
  if (!name) {
6544
6680
  name = path3.basename(inputFile, path3.extname(inputFile));
6545
6681
  }
6546
- return { inputFile, outDir, name };
6682
+ return { inputFile, outDir, name, enumSerialization };
6547
6683
  }
6548
6684
  async function main() {
6549
6685
  const args = process.argv.slice(2);
@@ -6551,7 +6687,7 @@ async function main() {
6551
6687
  if (!options) {
6552
6688
  process.exit(1);
6553
6689
  }
6554
- const { inputFile, outDir, name } = options;
6690
+ const { inputFile, outDir, name, enumSerialization } = options;
6555
6691
  const absoluteInput = path3.resolve(process.cwd(), inputFile);
6556
6692
  try {
6557
6693
  const fileUrl = pathToFileURL(absoluteInput).href;
@@ -6568,7 +6704,7 @@ async function main() {
6568
6704
  const { writeSchemas: writeSchemas2 } = await Promise.resolve().then(() => (init_index(), index_exports));
6569
6705
  const { jsonSchemaPath, uiSchemaPath } = writeSchemas2(
6570
6706
  form,
6571
- { outDir, name }
6707
+ { outDir, name, enumSerialization }
6572
6708
  );
6573
6709
  console.log("Generated:");
6574
6710
  console.log(` ${jsonSchemaPath}`);