@formspec/build 0.1.0-alpha.1 → 0.1.0-alpha.11

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 (108) hide show
  1. package/README.md +138 -0
  2. package/dist/__tests__/analyzer-edge-cases.test.d.ts +13 -0
  3. package/dist/__tests__/analyzer-edge-cases.test.d.ts.map +1 -0
  4. package/dist/__tests__/analyzer.test.d.ts +5 -0
  5. package/dist/__tests__/analyzer.test.d.ts.map +1 -0
  6. package/dist/__tests__/codegen.test.d.ts +5 -0
  7. package/dist/__tests__/codegen.test.d.ts.map +1 -0
  8. package/dist/__tests__/decorator-pipeline.test.d.ts +11 -0
  9. package/dist/__tests__/decorator-pipeline.test.d.ts.map +1 -0
  10. package/dist/__tests__/fixtures/edge-cases.d.ts +110 -0
  11. package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -0
  12. package/dist/__tests__/fixtures/example-a-builtins.d.ts +12 -0
  13. package/dist/__tests__/fixtures/example-a-builtins.d.ts.map +1 -0
  14. package/dist/__tests__/fixtures/example-b-decorators.d.ts +5 -0
  15. package/dist/__tests__/fixtures/example-b-decorators.d.ts.map +1 -0
  16. package/dist/__tests__/fixtures/example-b-extended.d.ts +5 -0
  17. package/dist/__tests__/fixtures/example-b-extended.d.ts.map +1 -0
  18. package/dist/__tests__/fixtures/example-c-custom.d.ts +5 -0
  19. package/dist/__tests__/fixtures/example-c-custom.d.ts.map +1 -0
  20. package/dist/__tests__/fixtures/example-c-decorators.d.ts +5 -0
  21. package/dist/__tests__/fixtures/example-c-decorators.d.ts.map +1 -0
  22. package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts +6 -0
  23. package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts.map +1 -0
  24. package/dist/__tests__/fixtures/example-e-decorators.d.ts +11 -0
  25. package/dist/__tests__/fixtures/example-e-decorators.d.ts.map +1 -0
  26. package/dist/__tests__/fixtures/example-e-no-namespace.d.ts +5 -0
  27. package/dist/__tests__/fixtures/example-e-no-namespace.d.ts.map +1 -0
  28. package/dist/__tests__/fixtures/example-interface-types.d.ts +102 -0
  29. package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -0
  30. package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts +16 -0
  31. package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts.map +1 -0
  32. package/dist/__tests__/fixtures/example-nested-class.d.ts +45 -0
  33. package/dist/__tests__/fixtures/example-nested-class.d.ts.map +1 -0
  34. package/dist/__tests__/fixtures/sample-forms.d.ts +55 -0
  35. package/dist/__tests__/fixtures/sample-forms.d.ts.map +1 -0
  36. package/dist/__tests__/interface-types.test.d.ts +11 -0
  37. package/dist/__tests__/interface-types.test.d.ts.map +1 -0
  38. package/dist/__tests__/jsdoc-constraints.test.d.ts +10 -0
  39. package/dist/__tests__/jsdoc-constraints.test.d.ts.map +1 -0
  40. package/dist/analyzer/class-analyzer.d.ts +139 -0
  41. package/dist/analyzer/class-analyzer.d.ts.map +1 -0
  42. package/dist/analyzer/decorator-extractor.d.ts +78 -0
  43. package/dist/analyzer/decorator-extractor.d.ts.map +1 -0
  44. package/dist/analyzer/jsdoc-constraints.d.ts +47 -0
  45. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -0
  46. package/dist/analyzer/program.d.ts +53 -0
  47. package/dist/analyzer/program.d.ts.map +1 -0
  48. package/dist/analyzer/type-converter.d.ts +75 -0
  49. package/dist/analyzer/type-converter.d.ts.map +1 -0
  50. package/dist/browser.cjs +549 -0
  51. package/dist/browser.cjs.map +1 -0
  52. package/dist/browser.d.ts +56 -0
  53. package/dist/browser.d.ts.map +1 -0
  54. package/dist/browser.js +501 -0
  55. package/dist/browser.js.map +1 -0
  56. package/dist/build.d.ts +890 -0
  57. package/dist/cli.cjs +2267 -0
  58. package/dist/cli.cjs.map +1 -0
  59. package/dist/cli.js +2204 -103
  60. package/dist/cli.js.map +1 -1
  61. package/dist/codegen/index.d.ts +75 -0
  62. package/dist/codegen/index.d.ts.map +1 -0
  63. package/dist/generators/class-schema.d.ts +114 -0
  64. package/dist/generators/class-schema.d.ts.map +1 -0
  65. package/dist/generators/method-schema.d.ts +67 -0
  66. package/dist/generators/method-schema.d.ts.map +1 -0
  67. package/dist/index.cjs +2093 -0
  68. package/dist/index.cjs.map +1 -0
  69. package/dist/index.d.ts +11 -3
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +2024 -104
  72. package/dist/index.js.map +1 -1
  73. package/dist/internals.cjs +1345 -0
  74. package/dist/internals.cjs.map +1 -0
  75. package/dist/internals.d.ts +21 -0
  76. package/dist/internals.d.ts.map +1 -0
  77. package/dist/internals.js +1298 -0
  78. package/dist/internals.js.map +1 -0
  79. package/dist/json-schema/generator.d.ts.map +1 -1
  80. package/dist/json-schema/schema.d.ts +16 -0
  81. package/dist/json-schema/schema.d.ts.map +1 -0
  82. package/dist/json-schema/types.d.ts +28 -0
  83. package/dist/json-schema/types.d.ts.map +1 -1
  84. package/dist/ui-schema/generator.d.ts +15 -0
  85. package/dist/ui-schema/generator.d.ts.map +1 -1
  86. package/dist/ui-schema/schema.d.ts +357 -0
  87. package/dist/ui-schema/schema.d.ts.map +1 -0
  88. package/dist/ui-schema/types.d.ts +8 -73
  89. package/dist/ui-schema/types.d.ts.map +1 -1
  90. package/package.json +25 -7
  91. package/dist/__tests__/cli.test.js +0 -178
  92. package/dist/__tests__/cli.test.js.map +0 -1
  93. package/dist/__tests__/edge-cases.test.js +0 -209
  94. package/dist/__tests__/edge-cases.test.js.map +0 -1
  95. package/dist/__tests__/generator.test.js +0 -208
  96. package/dist/__tests__/generator.test.js.map +0 -1
  97. package/dist/__tests__/integration.test.js +0 -163
  98. package/dist/__tests__/integration.test.js.map +0 -1
  99. package/dist/__tests__/write-schemas.test.js +0 -196
  100. package/dist/__tests__/write-schemas.test.js.map +0 -1
  101. package/dist/json-schema/generator.js +0 -146
  102. package/dist/json-schema/generator.js.map +0 -1
  103. package/dist/json-schema/types.js +0 -7
  104. package/dist/json-schema/types.js.map +0 -1
  105. package/dist/ui-schema/generator.js +0 -150
  106. package/dist/ui-schema/generator.js.map +0 -1
  107. package/dist/ui-schema/types.js +0 -8
  108. package/dist/ui-schema/types.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,22 +1,2131 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * FormSpec CLI - Generate JSON Schema and UI Schema from form definitions
4
- *
5
- * Usage:
6
- * formspec-build <input-file> [options]
7
- *
8
- * Options:
9
- * -o, --out-dir <dir> Output directory (default: ./generated)
10
- * -n, --name <name> Base name for output files (default: derived from input)
11
- * -h, --help Show help
12
- *
13
- * Example:
14
- * formspec-build src/forms/product.ts -o ./schemas -n product
15
- */
16
- import * as path from "node:path";
17
- import { pathToFileURL } from "node:url";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/json-schema/schema.ts
13
+ import { z } from "zod";
14
+ var jsonSchemaTypeSchema, jsonSchema7Schema;
15
+ var init_schema = __esm({
16
+ "src/json-schema/schema.ts"() {
17
+ "use strict";
18
+ jsonSchemaTypeSchema = z.enum([
19
+ "string",
20
+ "number",
21
+ "integer",
22
+ "boolean",
23
+ "object",
24
+ "array",
25
+ "null"
26
+ ]);
27
+ jsonSchema7Schema = z.lazy(
28
+ () => z.object({
29
+ $schema: z.string().optional(),
30
+ $id: z.string().optional(),
31
+ $ref: z.string().optional(),
32
+ // Metadata
33
+ title: z.string().optional(),
34
+ description: z.string().optional(),
35
+ deprecated: z.boolean().optional(),
36
+ // Type
37
+ type: z.union([jsonSchemaTypeSchema, z.array(jsonSchemaTypeSchema)]).optional(),
38
+ // String validation
39
+ minLength: z.number().optional(),
40
+ maxLength: z.number().optional(),
41
+ pattern: z.string().optional(),
42
+ // Number validation
43
+ minimum: z.number().optional(),
44
+ maximum: z.number().optional(),
45
+ exclusiveMinimum: z.number().optional(),
46
+ exclusiveMaximum: z.number().optional(),
47
+ // Enum
48
+ enum: z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])).readonly().optional(),
49
+ const: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional(),
50
+ // Object
51
+ properties: z.record(z.string(), jsonSchema7Schema).optional(),
52
+ required: z.array(z.string()).optional(),
53
+ additionalProperties: z.union([z.boolean(), jsonSchema7Schema]).optional(),
54
+ // Array
55
+ items: z.union([jsonSchema7Schema, z.array(jsonSchema7Schema)]).optional(),
56
+ minItems: z.number().optional(),
57
+ maxItems: z.number().optional(),
58
+ // Composition
59
+ allOf: z.array(jsonSchema7Schema).optional(),
60
+ anyOf: z.array(jsonSchema7Schema).optional(),
61
+ oneOf: z.array(jsonSchema7Schema).optional(),
62
+ not: jsonSchema7Schema.optional(),
63
+ // Conditional
64
+ if: jsonSchema7Schema.optional(),
65
+ then: jsonSchema7Schema.optional(),
66
+ else: jsonSchema7Schema.optional(),
67
+ // Format
68
+ format: z.string().optional(),
69
+ // Default
70
+ default: z.unknown().optional(),
71
+ // FormSpec extensions
72
+ "x-formspec-source": z.string().optional(),
73
+ "x-formspec-params": z.array(z.string()).readonly().optional(),
74
+ "x-formspec-schemaSource": z.string().optional()
75
+ }).passthrough()
76
+ );
77
+ }
78
+ });
79
+
80
+ // src/json-schema/generator.ts
81
+ import { z as z2 } from "zod";
82
+ function parseOrThrow(schema, value, label) {
83
+ try {
84
+ return schema.parse(value);
85
+ } catch (error) {
86
+ if (error instanceof z2.ZodError) {
87
+ throw new Error(
88
+ `Generated ${label} failed validation:
89
+ ${error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
90
+ );
91
+ }
92
+ throw error;
93
+ }
94
+ }
95
+ function generateNestedSchema(elements) {
96
+ const properties = {};
97
+ const required = [];
98
+ collectFields(elements, properties, required);
99
+ const uniqueRequired = [...new Set(required)];
100
+ return {
101
+ type: "object",
102
+ properties,
103
+ ...uniqueRequired.length > 0 && { required: uniqueRequired }
104
+ };
105
+ }
106
+ function fieldToJsonSchema(field) {
107
+ const base = {};
108
+ if (field.label !== void 0) {
109
+ base.title = field.label;
110
+ }
111
+ switch (field._field) {
112
+ case "text":
113
+ return { ...base, type: "string" };
114
+ case "number":
115
+ return {
116
+ ...base,
117
+ type: "number",
118
+ ...field.min !== void 0 && { minimum: field.min },
119
+ ...field.max !== void 0 && { maximum: field.max }
120
+ };
121
+ case "boolean":
122
+ return { ...base, type: "boolean" };
123
+ case "enum": {
124
+ const opts = field.options;
125
+ const isObjectOptions = opts.length > 0 && opts.every(
126
+ (opt) => typeof opt === "object" && "id" in opt && "label" in opt
127
+ );
128
+ if (isObjectOptions) {
129
+ return {
130
+ ...base,
131
+ type: "string",
132
+ oneOf: opts.map((o) => ({
133
+ const: o.id,
134
+ title: o.label
135
+ }))
136
+ };
137
+ }
138
+ return { ...base, type: "string", enum: opts };
139
+ }
140
+ case "dynamic_enum":
141
+ return {
142
+ ...base,
143
+ type: "string",
144
+ "x-formspec-source": field.source,
145
+ ...field.params !== void 0 && field.params.length > 0 && { "x-formspec-params": field.params }
146
+ };
147
+ case "dynamic_schema":
148
+ return {
149
+ ...base,
150
+ type: "object",
151
+ additionalProperties: true,
152
+ "x-formspec-schemaSource": field.schemaSource
153
+ };
154
+ case "array": {
155
+ const arrayField = field;
156
+ return {
157
+ ...base,
158
+ type: "array",
159
+ items: generateNestedSchema(arrayField.items),
160
+ ...arrayField.minItems !== void 0 && { minItems: arrayField.minItems },
161
+ ...arrayField.maxItems !== void 0 && { maxItems: arrayField.maxItems }
162
+ };
163
+ }
164
+ case "object": {
165
+ const objectField = field;
166
+ const nestedSchema = generateNestedSchema(objectField.properties);
167
+ return {
168
+ ...base,
169
+ ...nestedSchema
170
+ };
171
+ }
172
+ default: {
173
+ const _exhaustive = field;
174
+ return _exhaustive;
175
+ }
176
+ }
177
+ }
178
+ function collectFields(elements, properties, required) {
179
+ for (const element of elements) {
180
+ switch (element._type) {
181
+ case "field":
182
+ properties[element.name] = fieldToJsonSchema(element);
183
+ if (element.required === true) {
184
+ required.push(element.name);
185
+ }
186
+ break;
187
+ case "group":
188
+ collectFields(element.elements, properties, required);
189
+ break;
190
+ case "conditional":
191
+ collectFields(
192
+ element.elements,
193
+ properties,
194
+ required
195
+ );
196
+ break;
197
+ }
198
+ }
199
+ }
200
+ function generateJsonSchema(form) {
201
+ const properties = {};
202
+ const required = [];
203
+ collectFields(form.elements, properties, required);
204
+ const uniqueRequired = [...new Set(required)];
205
+ const result = {
206
+ $schema: "https://json-schema.org/draft-07/schema#",
207
+ type: "object",
208
+ properties,
209
+ ...uniqueRequired.length > 0 && { required: uniqueRequired }
210
+ };
211
+ return parseOrThrow(jsonSchema7Schema, result, "JSON Schema");
212
+ }
213
+ var init_generator = __esm({
214
+ "src/json-schema/generator.ts"() {
215
+ "use strict";
216
+ init_schema();
217
+ }
218
+ });
219
+
220
+ // src/ui-schema/schema.ts
221
+ import { z as z3 } from "zod";
222
+ var jsonPointerSchema, ruleEffectSchema, uiSchemaElementTypeSchema, ruleConditionSchema, schemaBasedConditionSchema, ruleSchema, uiSchemaElementSchema, controlSchema, verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorySchema, categorizationSchema, labelElementSchema, uiSchema;
223
+ var init_schema2 = __esm({
224
+ "src/ui-schema/schema.ts"() {
225
+ "use strict";
226
+ jsonPointerSchema = z3.string();
227
+ ruleEffectSchema = z3.enum(["SHOW", "HIDE", "ENABLE", "DISABLE"]);
228
+ uiSchemaElementTypeSchema = z3.enum([
229
+ "Control",
230
+ "VerticalLayout",
231
+ "HorizontalLayout",
232
+ "Group",
233
+ "Categorization",
234
+ "Category",
235
+ "Label"
236
+ ]);
237
+ ruleConditionSchema = z3.lazy(
238
+ () => z3.object({
239
+ const: z3.unknown().optional(),
240
+ enum: z3.array(z3.unknown()).readonly().optional(),
241
+ type: z3.string().optional(),
242
+ not: ruleConditionSchema.optional(),
243
+ minimum: z3.number().optional(),
244
+ maximum: z3.number().optional(),
245
+ exclusiveMinimum: z3.number().optional(),
246
+ exclusiveMaximum: z3.number().optional(),
247
+ minLength: z3.number().optional(),
248
+ properties: z3.record(z3.string(), ruleConditionSchema).optional(),
249
+ required: z3.array(z3.string()).optional(),
250
+ allOf: z3.array(ruleConditionSchema).optional()
251
+ }).strict()
252
+ );
253
+ schemaBasedConditionSchema = z3.object({
254
+ scope: jsonPointerSchema,
255
+ schema: ruleConditionSchema
256
+ }).strict();
257
+ ruleSchema = z3.object({
258
+ effect: ruleEffectSchema,
259
+ condition: schemaBasedConditionSchema
260
+ }).strict();
261
+ uiSchemaElementSchema = z3.lazy(
262
+ () => z3.union([
263
+ controlSchema,
264
+ verticalLayoutSchema,
265
+ horizontalLayoutSchema,
266
+ groupLayoutSchema,
267
+ categorizationSchema,
268
+ categorySchema,
269
+ labelElementSchema
270
+ ])
271
+ );
272
+ controlSchema = z3.object({
273
+ type: z3.literal("Control"),
274
+ scope: jsonPointerSchema,
275
+ label: z3.union([z3.string(), z3.literal(false)]).optional(),
276
+ rule: ruleSchema.optional(),
277
+ options: z3.record(z3.string(), z3.unknown()).optional()
278
+ }).passthrough();
279
+ verticalLayoutSchema = z3.lazy(
280
+ () => z3.object({
281
+ type: z3.literal("VerticalLayout"),
282
+ elements: z3.array(uiSchemaElementSchema),
283
+ rule: ruleSchema.optional(),
284
+ options: z3.record(z3.string(), z3.unknown()).optional()
285
+ }).passthrough()
286
+ );
287
+ horizontalLayoutSchema = z3.lazy(
288
+ () => z3.object({
289
+ type: z3.literal("HorizontalLayout"),
290
+ elements: z3.array(uiSchemaElementSchema),
291
+ rule: ruleSchema.optional(),
292
+ options: z3.record(z3.string(), z3.unknown()).optional()
293
+ }).passthrough()
294
+ );
295
+ groupLayoutSchema = z3.lazy(
296
+ () => z3.object({
297
+ type: z3.literal("Group"),
298
+ label: z3.string(),
299
+ elements: z3.array(uiSchemaElementSchema),
300
+ rule: ruleSchema.optional(),
301
+ options: z3.record(z3.string(), z3.unknown()).optional()
302
+ }).passthrough()
303
+ );
304
+ categorySchema = z3.lazy(
305
+ () => z3.object({
306
+ type: z3.literal("Category"),
307
+ label: z3.string(),
308
+ elements: z3.array(uiSchemaElementSchema),
309
+ rule: ruleSchema.optional(),
310
+ options: z3.record(z3.string(), z3.unknown()).optional()
311
+ }).passthrough()
312
+ );
313
+ categorizationSchema = z3.lazy(
314
+ () => z3.object({
315
+ type: z3.literal("Categorization"),
316
+ elements: z3.array(categorySchema),
317
+ label: z3.string().optional(),
318
+ rule: ruleSchema.optional(),
319
+ options: z3.record(z3.string(), z3.unknown()).optional()
320
+ }).passthrough()
321
+ );
322
+ labelElementSchema = z3.object({
323
+ type: z3.literal("Label"),
324
+ text: z3.string(),
325
+ rule: ruleSchema.optional(),
326
+ options: z3.record(z3.string(), z3.unknown()).optional()
327
+ }).passthrough();
328
+ uiSchema = z3.lazy(
329
+ () => z3.union([verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorizationSchema])
330
+ );
331
+ }
332
+ });
333
+
334
+ // src/ui-schema/generator.ts
335
+ import { z as z4 } from "zod";
336
+ function parseOrThrow2(schema, value, label) {
337
+ try {
338
+ return schema.parse(value);
339
+ } catch (error) {
340
+ if (error instanceof z4.ZodError) {
341
+ throw new Error(
342
+ `Generated ${label} failed validation:
343
+ ${error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
344
+ );
345
+ }
346
+ throw error;
347
+ }
348
+ }
349
+ function fieldToScope(fieldName) {
350
+ return `#/properties/${fieldName}`;
351
+ }
352
+ function createShowRule(fieldName, value) {
353
+ return {
354
+ effect: "SHOW",
355
+ condition: {
356
+ scope: fieldToScope(fieldName),
357
+ schema: { const: value }
358
+ }
359
+ };
360
+ }
361
+ function combineRules(parentRule, childRule) {
362
+ const parentCondition = parentRule.condition;
363
+ const childCondition = childRule.condition;
364
+ return {
365
+ effect: "SHOW",
366
+ condition: {
367
+ scope: "#",
368
+ schema: {
369
+ allOf: [
370
+ {
371
+ properties: {
372
+ [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
373
+ }
374
+ },
375
+ {
376
+ properties: {
377
+ [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
378
+ }
379
+ }
380
+ ]
381
+ }
382
+ }
383
+ };
384
+ }
385
+ function elementsToUiSchema(elements, parentRule) {
386
+ const result = [];
387
+ for (const element of elements) {
388
+ switch (element._type) {
389
+ case "field": {
390
+ const control = {
391
+ type: "Control",
392
+ scope: fieldToScope(element.name),
393
+ ...element.label !== void 0 && { label: element.label },
394
+ ...parentRule !== void 0 && { rule: parentRule }
395
+ };
396
+ result.push(control);
397
+ break;
398
+ }
399
+ case "group": {
400
+ const groupElement = element;
401
+ const group = {
402
+ type: "Group",
403
+ label: groupElement.label,
404
+ elements: elementsToUiSchema(groupElement.elements, parentRule),
405
+ ...parentRule !== void 0 && { rule: parentRule }
406
+ };
407
+ result.push(group);
408
+ break;
409
+ }
410
+ case "conditional": {
411
+ const conditionalElement = element;
412
+ const newRule = createShowRule(conditionalElement.field, conditionalElement.value);
413
+ const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
414
+ const childElements = elementsToUiSchema(conditionalElement.elements, combinedRule);
415
+ result.push(...childElements);
416
+ break;
417
+ }
418
+ }
419
+ }
420
+ return result;
421
+ }
422
+ function formSpecFieldToElement(field, scopePrefix = "#/properties") {
423
+ const control = {
424
+ type: "Control",
425
+ scope: `${scopePrefix}/${field.id}`
426
+ };
427
+ if (field.label !== void 0) {
428
+ control.label = field.label;
429
+ }
430
+ if (field.showWhen !== void 0 && typeof field.showWhen === "object" && "field" in field.showWhen && "value" in field.showWhen) {
431
+ const sw = field.showWhen;
432
+ control.rule = {
433
+ effect: "SHOW",
434
+ condition: {
435
+ scope: `#/properties/${sw.field}`,
436
+ schema: { const: sw.value }
437
+ }
438
+ };
439
+ }
440
+ return control;
441
+ }
442
+ function generateUiSchemaFromFields(fields) {
443
+ const groupMap = /* @__PURE__ */ new Map();
444
+ const orderedKeys = [];
445
+ const ungrouped = [];
446
+ for (const field of fields) {
447
+ const element = formSpecFieldToElement(field);
448
+ if (field.group !== void 0) {
449
+ if (!groupMap.has(field.group)) {
450
+ groupMap.set(field.group, []);
451
+ orderedKeys.push(field.group);
452
+ }
453
+ groupMap.get(field.group).push(element);
454
+ } else {
455
+ orderedKeys.push(null);
456
+ ungrouped.push(element);
457
+ }
458
+ }
459
+ const elements = [];
460
+ let ungroupedIndex = 0;
461
+ for (const key of orderedKeys) {
462
+ if (key === null) {
463
+ const el = ungrouped[ungroupedIndex++];
464
+ if (el !== void 0) {
465
+ elements.push(el);
466
+ }
467
+ } else {
468
+ const groupElements = groupMap.get(key) ?? [];
469
+ const groupLayout = {
470
+ type: "Group",
471
+ label: key,
472
+ elements: groupElements
473
+ };
474
+ elements.push(groupLayout);
475
+ }
476
+ }
477
+ const result = {
478
+ type: "VerticalLayout",
479
+ elements
480
+ };
481
+ return parseOrThrow2(uiSchema, result, "UI Schema");
482
+ }
483
+ function generateUiSchema(form) {
484
+ const result = {
485
+ type: "VerticalLayout",
486
+ elements: elementsToUiSchema(form.elements)
487
+ };
488
+ return parseOrThrow2(uiSchema, result, "UI Schema");
489
+ }
490
+ var init_generator2 = __esm({
491
+ "src/ui-schema/generator.ts"() {
492
+ "use strict";
493
+ init_schema2();
494
+ }
495
+ });
496
+
497
+ // src/json-schema/types.ts
498
+ function setSchemaExtension(schema, key, value) {
499
+ schema[key] = value;
500
+ }
501
+ function getSchemaExtension(schema, key) {
502
+ return schema[key];
503
+ }
504
+ var init_types = __esm({
505
+ "src/json-schema/types.ts"() {
506
+ "use strict";
507
+ }
508
+ });
509
+
510
+ // src/analyzer/decorator-extractor.ts
511
+ import * as ts from "typescript";
512
+ import "@formspec/core";
513
+ function extractDecorators(member) {
514
+ const decorators = [];
515
+ const modifiers = ts.canHaveDecorators(member) ? ts.getDecorators(member) : void 0;
516
+ if (!modifiers) return decorators;
517
+ for (const decorator of modifiers) {
518
+ const info = parseDecorator(decorator);
519
+ if (info) {
520
+ decorators.push(info);
521
+ }
522
+ }
523
+ return decorators;
524
+ }
525
+ function parseDecorator(decorator) {
526
+ const expr = decorator.expression;
527
+ if (ts.isIdentifier(expr)) {
528
+ return {
529
+ name: expr.text,
530
+ args: [],
531
+ node: decorator
532
+ };
533
+ }
534
+ if (ts.isCallExpression(expr)) {
535
+ const callee = expr.expression;
536
+ let name = null;
537
+ if (ts.isIdentifier(callee)) {
538
+ name = callee.text;
539
+ } else if (ts.isPropertyAccessExpression(callee)) {
540
+ name = callee.name.text;
541
+ }
542
+ if (!name) return null;
543
+ const args = expr.arguments.map(extractArgValue);
544
+ return {
545
+ name,
546
+ args,
547
+ node: decorator
548
+ };
549
+ }
550
+ return null;
551
+ }
552
+ function extractArgValue(node) {
553
+ if (ts.isStringLiteral(node)) {
554
+ return node.text;
555
+ }
556
+ if (ts.isNumericLiteral(node)) {
557
+ return Number(node.text);
558
+ }
559
+ if (node.kind === ts.SyntaxKind.TrueKeyword) {
560
+ return true;
561
+ }
562
+ if (node.kind === ts.SyntaxKind.FalseKeyword) {
563
+ return false;
564
+ }
565
+ if (node.kind === ts.SyntaxKind.NullKeyword) {
566
+ return null;
567
+ }
568
+ if (ts.isPrefixUnaryExpression(node)) {
569
+ if (node.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(node.operand)) {
570
+ return -Number(node.operand.text);
571
+ }
572
+ if (node.operator === ts.SyntaxKind.PlusToken && ts.isNumericLiteral(node.operand)) {
573
+ return Number(node.operand.text);
574
+ }
575
+ }
576
+ if (ts.isArrayLiteralExpression(node)) {
577
+ return node.elements.map((el) => {
578
+ if (ts.isSpreadElement(el)) {
579
+ return null;
580
+ }
581
+ return extractArgValue(el);
582
+ });
583
+ }
584
+ if (ts.isObjectLiteralExpression(node)) {
585
+ const obj = {};
586
+ for (const prop of node.properties) {
587
+ if (ts.isPropertyAssignment(prop)) {
588
+ const key = getPropertyName(prop.name);
589
+ if (key) {
590
+ obj[key] = extractArgValue(prop.initializer);
591
+ }
592
+ } else if (ts.isShorthandPropertyAssignment(prop)) {
593
+ const key = prop.name.text;
594
+ obj[key] = null;
595
+ }
596
+ }
597
+ return obj;
598
+ }
599
+ if (ts.isNoSubstitutionTemplateLiteral(node)) {
600
+ return node.text;
601
+ }
602
+ if (ts.isRegularExpressionLiteral(node)) {
603
+ const regexText = node.text;
604
+ const lastSlash = regexText.lastIndexOf("/");
605
+ if (lastSlash > 0) {
606
+ return regexText.substring(1, lastSlash);
607
+ }
608
+ return regexText;
609
+ }
610
+ if (ts.isNewExpression(node)) {
611
+ if (ts.isIdentifier(node.expression) && node.expression.text === "RegExp" && node.arguments && node.arguments.length > 0) {
612
+ const firstArg = node.arguments[0];
613
+ if (firstArg && ts.isStringLiteral(firstArg)) {
614
+ return firstArg.text;
615
+ }
616
+ }
617
+ }
618
+ if (ts.isIdentifier(node)) {
619
+ return null;
620
+ }
621
+ return null;
622
+ }
623
+ function getPropertyName(name) {
624
+ if (ts.isIdentifier(name)) {
625
+ return name.text;
626
+ }
627
+ if (ts.isStringLiteral(name)) {
628
+ return name.text;
629
+ }
630
+ if (ts.isNumericLiteral(name)) {
631
+ return name.text;
632
+ }
633
+ return null;
634
+ }
635
+ function isFormSpecDecoratorsPath(fileName) {
636
+ const normalized = fileName.replace(/\\/g, "/");
637
+ return normalized.includes("node_modules/@formspec/decorators") || normalized.includes("/packages/decorators/");
638
+ }
639
+ function resolveDecorator(decorator, checker) {
640
+ const expr = decorator.expression;
641
+ let targetNode;
642
+ let name;
643
+ if (ts.isIdentifier(expr)) {
644
+ targetNode = expr;
645
+ name = expr.text;
646
+ } else if (ts.isCallExpression(expr)) {
647
+ if (ts.isIdentifier(expr.expression)) {
648
+ targetNode = expr.expression;
649
+ name = expr.expression.text;
650
+ } else {
651
+ return null;
652
+ }
653
+ } else {
654
+ return null;
655
+ }
656
+ if (name in FORMSPEC_DECORATORS) {
657
+ const symbol = checker.getSymbolAtLocation(targetNode);
658
+ if (symbol) {
659
+ const declarations = symbol.declarations;
660
+ if (declarations && declarations.length > 0) {
661
+ const decl = declarations[0];
662
+ if (decl) {
663
+ const sourceFile = decl.getSourceFile();
664
+ const fileName = sourceFile.fileName;
665
+ if (isFormSpecDecoratorsPath(fileName)) {
666
+ return {
667
+ name,
668
+ isFormSpec: true,
669
+ isMarker: !ts.isCallExpression(expr)
670
+ };
671
+ }
672
+ }
673
+ }
674
+ }
675
+ }
676
+ const resolvedSymbol = checker.getSymbolAtLocation(targetNode);
677
+ if (!resolvedSymbol) return null;
678
+ const type = checker.getTypeOfSymbol(resolvedSymbol);
679
+ const props = type.getProperties();
680
+ let extendsBuiltin;
681
+ let extensionName;
682
+ let isMarker = false;
683
+ for (const prop of props) {
684
+ const escapedName = prop.getEscapedName();
685
+ if (escapedName.startsWith("__@") && (escapedName.includes("formspec.extends") || escapedName.includes("FORMSPEC_EXTENDS"))) {
686
+ const propType = checker.getTypeOfSymbol(prop);
687
+ if (propType.isStringLiteral()) {
688
+ extendsBuiltin = propType.value;
689
+ }
690
+ }
691
+ if (escapedName.startsWith("__@") && (escapedName.includes("formspec.extension") || escapedName.includes("FORMSPEC_EXTENSION"))) {
692
+ const propType = checker.getTypeOfSymbol(prop);
693
+ if (propType.isStringLiteral()) {
694
+ extensionName = propType.value;
695
+ }
696
+ }
697
+ if (escapedName.startsWith("__@") && (escapedName.includes("formspec.marker") || escapedName.includes("FORMSPEC_MARKER"))) {
698
+ isMarker = true;
699
+ }
700
+ }
701
+ if (extendsBuiltin) {
702
+ return {
703
+ name,
704
+ extendsBuiltin,
705
+ isFormSpec: true,
706
+ isMarker: false
707
+ };
708
+ }
709
+ if (extensionName) {
710
+ return {
711
+ name,
712
+ extensionName,
713
+ isFormSpec: true,
714
+ isMarker
715
+ };
716
+ }
717
+ if (isMarker) {
718
+ return {
719
+ name,
720
+ isFormSpec: true,
721
+ isMarker: true
722
+ };
723
+ }
724
+ return null;
725
+ }
726
+ var FORMSPEC_DECORATORS;
727
+ var init_decorator_extractor = __esm({
728
+ "src/analyzer/decorator-extractor.ts"() {
729
+ "use strict";
730
+ FORMSPEC_DECORATORS = {
731
+ // Display metadata
732
+ Field: { argTypes: ["object"] },
733
+ // Grouping
734
+ Group: { argTypes: ["string"] },
735
+ // Conditional display
736
+ ShowWhen: { argTypes: ["object"] },
737
+ // Enum options
738
+ EnumOptions: { argTypes: ["array"] },
739
+ // Numeric constraints
740
+ Minimum: { argTypes: ["number"] },
741
+ Maximum: { argTypes: ["number"] },
742
+ ExclusiveMinimum: { argTypes: ["number"] },
743
+ ExclusiveMaximum: { argTypes: ["number"] },
744
+ // String constraints
745
+ MinLength: { argTypes: ["number"] },
746
+ MaxLength: { argTypes: ["number"] },
747
+ Pattern: { argTypes: ["string"] }
748
+ };
749
+ }
750
+ });
751
+
752
+ // src/analyzer/jsdoc-constraints.ts
753
+ import * as ts2 from "typescript";
754
+ import { CONSTRAINT_TAG_DEFINITIONS } from "@formspec/core";
755
+ function extractJSDocConstraints(node) {
756
+ const results = [];
757
+ const jsDocTags = ts2.getJSDocTags(node);
758
+ for (const tag of jsDocTags) {
759
+ const tagName = tag.tagName.text;
760
+ if (!(tagName in CONSTRAINT_TAG_DEFINITIONS)) {
761
+ continue;
762
+ }
763
+ const constraintName = tagName;
764
+ const expectedType = CONSTRAINT_TAG_DEFINITIONS[constraintName];
765
+ const commentText = getTagCommentText(tag);
766
+ if (commentText === void 0 || commentText === "") {
767
+ continue;
768
+ }
769
+ const trimmed = commentText.trim();
770
+ if (trimmed === "") {
771
+ continue;
772
+ }
773
+ if (expectedType === "number") {
774
+ const value = Number(trimmed);
775
+ if (Number.isNaN(value)) {
776
+ continue;
777
+ }
778
+ results.push(createSyntheticDecorator(constraintName, value));
779
+ } else if (expectedType === "json") {
780
+ try {
781
+ const parsed = JSON.parse(trimmed);
782
+ if (!Array.isArray(parsed)) {
783
+ continue;
784
+ }
785
+ results.push(createSyntheticDecorator(constraintName, parsed));
786
+ } catch {
787
+ continue;
788
+ }
789
+ } else {
790
+ results.push(createSyntheticDecorator(constraintName, trimmed));
791
+ }
792
+ }
793
+ return results;
794
+ }
795
+ function extractJSDocFieldMetadata(node) {
796
+ const jsDocTags = ts2.getJSDocTags(node);
797
+ let displayName;
798
+ let description;
799
+ for (const tag of jsDocTags) {
800
+ const tagName = tag.tagName.text;
801
+ const commentText = getTagCommentText(tag);
802
+ if (commentText === void 0 || commentText.trim() === "") {
803
+ continue;
804
+ }
805
+ const trimmed = commentText.trim();
806
+ if (tagName === "Field_displayName") {
807
+ displayName = trimmed;
808
+ } else if (tagName === "Field_description") {
809
+ description = trimmed;
810
+ }
811
+ }
812
+ if (displayName === void 0 && description === void 0) {
813
+ return null;
814
+ }
815
+ const fieldOpts = {
816
+ ...displayName !== void 0 ? { displayName } : {},
817
+ ...description !== void 0 ? { description } : {}
818
+ };
819
+ return createSyntheticDecorator("Field", fieldOpts);
820
+ }
821
+ function getTagCommentText(tag) {
822
+ if (tag.comment === void 0) {
823
+ return void 0;
824
+ }
825
+ if (typeof tag.comment === "string") {
826
+ return tag.comment;
827
+ }
828
+ return ts2.getTextOfJSDocComment(tag.comment);
829
+ }
830
+ function createSyntheticDecorator(name, value) {
831
+ return {
832
+ name,
833
+ args: [value],
834
+ node: void 0
835
+ };
836
+ }
837
+ var init_jsdoc_constraints = __esm({
838
+ "src/analyzer/jsdoc-constraints.ts"() {
839
+ "use strict";
840
+ }
841
+ });
842
+
843
+ // src/analyzer/class-analyzer.ts
844
+ import * as ts3 from "typescript";
845
+ function analyzeClass(classDecl, checker) {
846
+ const name = classDecl.name?.text ?? "AnonymousClass";
847
+ const fields = [];
848
+ const instanceMethods = [];
849
+ const staticMethods = [];
850
+ for (const member of classDecl.members) {
851
+ if (ts3.isPropertyDeclaration(member)) {
852
+ const fieldInfo = analyzeField(member, checker);
853
+ if (fieldInfo) {
854
+ fields.push(fieldInfo);
855
+ }
856
+ } else if (ts3.isMethodDeclaration(member)) {
857
+ const methodInfo = analyzeMethod(member, checker);
858
+ if (methodInfo) {
859
+ const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
860
+ if (isStatic) {
861
+ staticMethods.push(methodInfo);
862
+ } else {
863
+ instanceMethods.push(methodInfo);
864
+ }
865
+ }
866
+ }
867
+ }
868
+ return {
869
+ name,
870
+ fields,
871
+ instanceMethods,
872
+ staticMethods
873
+ };
874
+ }
875
+ function analyzeField(prop, checker) {
876
+ if (!ts3.isIdentifier(prop.name)) {
877
+ return null;
878
+ }
879
+ const name = prop.name.text;
880
+ const typeNode = prop.type;
881
+ const type = checker.getTypeAtLocation(prop);
882
+ const optional = prop.questionToken !== void 0;
883
+ const decorators = extractDecorators(prop);
884
+ for (const dec of decorators) {
885
+ if (dec.node) {
886
+ const resolved = resolveDecorator(dec.node, checker);
887
+ if (resolved) {
888
+ dec.resolved = resolved;
889
+ }
890
+ }
891
+ }
892
+ if (prop.type) {
893
+ const aliasConstraints = extractTypeAliasConstraints(prop.type, checker);
894
+ decorators.push(...aliasConstraints);
895
+ }
896
+ const jsdocConstraints = extractJSDocConstraints(prop);
897
+ decorators.push(...jsdocConstraints);
898
+ const deprecated = hasDeprecatedTag(prop);
899
+ const defaultValue = extractDefaultValue(prop.initializer);
900
+ return {
901
+ name,
902
+ typeNode,
903
+ type,
904
+ optional,
905
+ decorators,
906
+ deprecated,
907
+ defaultValue
908
+ };
909
+ }
910
+ function extractTypeAliasConstraints(typeNode, checker) {
911
+ if (!ts3.isTypeReferenceNode(typeNode)) return [];
912
+ const symbol = checker.getSymbolAtLocation(typeNode.typeName);
913
+ if (!symbol?.declarations) return [];
914
+ const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
915
+ if (!aliasDecl) return [];
916
+ if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
917
+ return extractJSDocConstraints(aliasDecl);
918
+ }
919
+ function hasDeprecatedTag(node) {
920
+ const jsDocTags = ts3.getJSDocTags(node);
921
+ return jsDocTags.some((tag) => tag.tagName.text === "deprecated");
922
+ }
923
+ function extractDefaultValue(initializer) {
924
+ if (!initializer) return void 0;
925
+ if (ts3.isStringLiteral(initializer)) {
926
+ return initializer.text;
927
+ }
928
+ if (ts3.isNumericLiteral(initializer)) {
929
+ return Number(initializer.text);
930
+ }
931
+ if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
932
+ return true;
933
+ }
934
+ if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
935
+ return false;
936
+ }
937
+ if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
938
+ return null;
939
+ }
940
+ if (ts3.isPrefixUnaryExpression(initializer)) {
941
+ if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
942
+ return -Number(initializer.operand.text);
943
+ }
944
+ }
945
+ return void 0;
946
+ }
947
+ function analyzeMethod(method, checker) {
948
+ if (!ts3.isIdentifier(method.name)) {
949
+ return null;
950
+ }
951
+ const name = method.name.text;
952
+ const parameters = [];
953
+ for (const param of method.parameters) {
954
+ if (ts3.isIdentifier(param.name)) {
955
+ const paramInfo = analyzeParameter(param, checker);
956
+ parameters.push(paramInfo);
957
+ }
958
+ }
959
+ const returnTypeNode = method.type;
960
+ const signature = checker.getSignatureFromDeclaration(method);
961
+ const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getTypeAtLocation(method);
962
+ return {
963
+ name,
964
+ parameters,
965
+ returnTypeNode,
966
+ returnType
967
+ };
968
+ }
969
+ function analyzeParameter(param, checker) {
970
+ const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
971
+ const typeNode = param.type;
972
+ const type = checker.getTypeAtLocation(param);
973
+ const formSpecExportName = detectFormSpecReference(typeNode);
974
+ const optional = param.questionToken !== void 0 || param.initializer !== void 0;
975
+ return {
976
+ name,
977
+ typeNode,
978
+ type,
979
+ formSpecExportName,
980
+ optional
981
+ };
982
+ }
983
+ function detectFormSpecReference(typeNode) {
984
+ if (!typeNode) return null;
985
+ if (!ts3.isTypeReferenceNode(typeNode)) return null;
986
+ const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
987
+ if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
988
+ const typeArg = typeNode.typeArguments?.[0];
989
+ if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
990
+ if (ts3.isIdentifier(typeArg.exprName)) {
991
+ return typeArg.exprName.text;
992
+ }
993
+ if (ts3.isQualifiedName(typeArg.exprName)) {
994
+ return typeArg.exprName.right.text;
995
+ }
996
+ return null;
997
+ }
998
+ function analyzeInterface(interfaceDecl, checker) {
999
+ const name = interfaceDecl.name.text;
1000
+ const fields = [];
1001
+ for (const member of interfaceDecl.members) {
1002
+ if (ts3.isPropertySignature(member)) {
1003
+ const fieldInfo = analyzeInterfaceProperty(member, checker);
1004
+ if (fieldInfo) {
1005
+ fields.push(fieldInfo);
1006
+ }
1007
+ }
1008
+ }
1009
+ return {
1010
+ name,
1011
+ fields,
1012
+ instanceMethods: [],
1013
+ staticMethods: []
1014
+ };
1015
+ }
1016
+ function analyzeTypeAlias(typeAlias, checker) {
1017
+ if (!ts3.isTypeLiteralNode(typeAlias.type)) {
1018
+ const sourceFile = typeAlias.getSourceFile();
1019
+ const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
1020
+ const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1021
+ return {
1022
+ ok: false,
1023
+ error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
1024
+ };
1025
+ }
1026
+ const name = typeAlias.name.text;
1027
+ const fields = [];
1028
+ for (const member of typeAlias.type.members) {
1029
+ if (ts3.isPropertySignature(member)) {
1030
+ const fieldInfo = analyzeInterfaceProperty(member, checker);
1031
+ if (fieldInfo) {
1032
+ fields.push(fieldInfo);
1033
+ }
1034
+ }
1035
+ }
1036
+ return {
1037
+ ok: true,
1038
+ analysis: {
1039
+ name,
1040
+ fields,
1041
+ instanceMethods: [],
1042
+ staticMethods: []
1043
+ }
1044
+ };
1045
+ }
1046
+ function analyzeInterfaceProperty(prop, checker) {
1047
+ if (!ts3.isIdentifier(prop.name)) {
1048
+ return null;
1049
+ }
1050
+ const name = prop.name.text;
1051
+ const typeNode = prop.type;
1052
+ const type = checker.getTypeAtLocation(prop);
1053
+ const optional = prop.questionToken !== void 0;
1054
+ const decorators = [];
1055
+ if (typeNode) {
1056
+ const aliasConstraints = extractTypeAliasConstraints(typeNode, checker);
1057
+ decorators.push(...aliasConstraints);
1058
+ }
1059
+ const fieldMetadata = extractJSDocFieldMetadata(prop);
1060
+ if (fieldMetadata) {
1061
+ decorators.push(fieldMetadata);
1062
+ }
1063
+ const jsdocConstraints = extractJSDocConstraints(prop);
1064
+ decorators.push(...jsdocConstraints);
1065
+ const deprecated = hasDeprecatedTag(prop);
1066
+ return {
1067
+ name,
1068
+ typeNode,
1069
+ type,
1070
+ optional,
1071
+ decorators,
1072
+ deprecated,
1073
+ defaultValue: void 0
1074
+ };
1075
+ }
1076
+ var init_class_analyzer = __esm({
1077
+ "src/analyzer/class-analyzer.ts"() {
1078
+ "use strict";
1079
+ init_decorator_extractor();
1080
+ init_jsdoc_constraints();
1081
+ }
1082
+ });
1083
+
1084
+ // src/analyzer/type-converter.ts
1085
+ import * as ts4 from "typescript";
1086
+ function getNamedTypeFieldInfoMap(type, checker) {
1087
+ const symbols = [type.getSymbol(), type.aliasSymbol].filter(
1088
+ (s) => s?.declarations != null && s.declarations.length > 0
1089
+ );
1090
+ for (const symbol of symbols) {
1091
+ const declarations = symbol.declarations;
1092
+ if (!declarations) continue;
1093
+ const classDecl = declarations.find(ts4.isClassDeclaration);
1094
+ if (classDecl) {
1095
+ const map = /* @__PURE__ */ new Map();
1096
+ for (const member of classDecl.members) {
1097
+ if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
1098
+ const fieldInfo = analyzeField(member, checker);
1099
+ if (fieldInfo) map.set(fieldInfo.name, fieldInfo);
1100
+ }
1101
+ }
1102
+ return map;
1103
+ }
1104
+ const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
1105
+ if (interfaceDecl) {
1106
+ return buildFieldInfoMapFromSignatures(interfaceDecl.members, checker);
1107
+ }
1108
+ const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
1109
+ if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
1110
+ return buildFieldInfoMapFromSignatures(typeAliasDecl.type.members, checker);
1111
+ }
1112
+ }
1113
+ return null;
1114
+ }
1115
+ function buildFieldInfoMapFromSignatures(members, checker) {
1116
+ const map = /* @__PURE__ */ new Map();
1117
+ for (const member of members) {
1118
+ if (ts4.isPropertySignature(member)) {
1119
+ const fieldInfo = analyzeInterfaceProperty(member, checker);
1120
+ if (fieldInfo) {
1121
+ map.set(fieldInfo.name, fieldInfo);
1122
+ }
1123
+ }
1124
+ }
1125
+ return map;
1126
+ }
1127
+ function getObjectPropertyInfos(type, checker) {
1128
+ const fieldInfoMap = getNamedTypeFieldInfoMap(type, checker);
1129
+ const result = [];
1130
+ for (const prop of type.getProperties()) {
1131
+ const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
1132
+ if (!declaration) continue;
1133
+ const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1134
+ const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1135
+ const fieldInfo = fieldInfoMap?.get(prop.name) ?? void 0;
1136
+ result.push({ name: prop.name, type: propType, optional, fieldInfo });
1137
+ }
1138
+ return result;
1139
+ }
1140
+ function convertType(type, checker) {
1141
+ return convertTypeInternal(type, checker, /* @__PURE__ */ new Set());
1142
+ }
1143
+ function convertTypeInternal(type, checker, visiting) {
1144
+ if (type.flags & ts4.TypeFlags.String) {
1145
+ return { jsonSchema: { type: "string" }, formSpecFieldType: "text" };
1146
+ }
1147
+ if (type.flags & ts4.TypeFlags.Number) {
1148
+ return { jsonSchema: { type: "number" }, formSpecFieldType: "number" };
1149
+ }
1150
+ if (type.flags & ts4.TypeFlags.Boolean) {
1151
+ return { jsonSchema: { type: "boolean" }, formSpecFieldType: "boolean" };
1152
+ }
1153
+ if (type.flags & ts4.TypeFlags.Null) {
1154
+ return { jsonSchema: { type: "null" }, formSpecFieldType: "null" };
1155
+ }
1156
+ if (type.flags & ts4.TypeFlags.Undefined) {
1157
+ return { jsonSchema: {}, formSpecFieldType: "undefined" };
1158
+ }
1159
+ if (type.isStringLiteral()) {
1160
+ return {
1161
+ jsonSchema: { const: type.value },
1162
+ formSpecFieldType: "enum"
1163
+ };
1164
+ }
1165
+ if (type.isNumberLiteral()) {
1166
+ return {
1167
+ jsonSchema: { const: type.value },
1168
+ formSpecFieldType: "number"
1169
+ };
1170
+ }
1171
+ if (type.isUnion()) {
1172
+ return convertUnionType(type, checker, visiting);
1173
+ }
1174
+ if (checker.isArrayType(type)) {
1175
+ return convertArrayType(type, checker, visiting);
1176
+ }
1177
+ if (type.flags & ts4.TypeFlags.Object) {
1178
+ return convertObjectType(type, checker, visiting);
1179
+ }
1180
+ return { jsonSchema: {}, formSpecFieldType: "unknown" };
1181
+ }
1182
+ function convertUnionType(type, checker, visiting) {
1183
+ const types = type.types;
1184
+ const nonNullTypes = types.filter(
1185
+ (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1186
+ );
1187
+ const hasNull = types.some((t) => t.flags & ts4.TypeFlags.Null);
1188
+ const isBooleanUnion = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1189
+ if (isBooleanUnion) {
1190
+ const result = {
1191
+ jsonSchema: { type: "boolean" },
1192
+ formSpecFieldType: "boolean"
1193
+ };
1194
+ if (hasNull) {
1195
+ result.jsonSchema = { oneOf: [{ type: "boolean" }, { type: "null" }] };
1196
+ }
1197
+ return result;
1198
+ }
1199
+ const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1200
+ if (allStringLiterals && nonNullTypes.length > 0) {
1201
+ const enumValues = nonNullTypes.map((t) => t.value);
1202
+ const result = {
1203
+ jsonSchema: { enum: enumValues },
1204
+ formSpecFieldType: "enum"
1205
+ };
1206
+ if (hasNull) {
1207
+ result.jsonSchema = { oneOf: [{ enum: enumValues }, { type: "null" }] };
1208
+ }
1209
+ return result;
1210
+ }
1211
+ const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1212
+ if (allNumberLiterals && nonNullTypes.length > 0) {
1213
+ const enumValues = nonNullTypes.map((t) => t.value);
1214
+ const result = {
1215
+ jsonSchema: { enum: enumValues },
1216
+ formSpecFieldType: "enum"
1217
+ };
1218
+ if (hasNull) {
1219
+ result.jsonSchema = { oneOf: [{ enum: enumValues }, { type: "null" }] };
1220
+ }
1221
+ return result;
1222
+ }
1223
+ if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1224
+ const result = convertTypeInternal(nonNullTypes[0], checker, visiting);
1225
+ if (hasNull) {
1226
+ result.jsonSchema = { oneOf: [result.jsonSchema, { type: "null" }] };
1227
+ }
1228
+ return result;
1229
+ }
1230
+ const schemas = nonNullTypes.map((t) => convertTypeInternal(t, checker, visiting).jsonSchema);
1231
+ if (hasNull) {
1232
+ schemas.push({ type: "null" });
1233
+ }
1234
+ return {
1235
+ jsonSchema: { oneOf: schemas },
1236
+ formSpecFieldType: "union"
1237
+ };
1238
+ }
1239
+ function convertArrayType(type, checker, visiting) {
1240
+ const typeArgs = type.typeArguments;
1241
+ const elementType = typeArgs?.[0];
1242
+ const itemSchema = elementType ? convertTypeInternal(elementType, checker, visiting).jsonSchema : {};
1243
+ return {
1244
+ jsonSchema: {
1245
+ type: "array",
1246
+ items: itemSchema
1247
+ },
1248
+ formSpecFieldType: "array"
1249
+ };
1250
+ }
1251
+ function convertObjectType(type, checker, visiting) {
1252
+ if (visiting.has(type)) {
1253
+ return { jsonSchema: { type: "object" }, formSpecFieldType: "object" };
1254
+ }
1255
+ visiting.add(type);
1256
+ const properties = {};
1257
+ const required = [];
1258
+ for (const propInfo of getObjectPropertyInfos(type, checker)) {
1259
+ const propSchema = convertTypeInternal(propInfo.type, checker, visiting).jsonSchema;
1260
+ properties[propInfo.name] = propInfo.fieldInfo ? applyDecoratorsToSchema(propSchema, propInfo.fieldInfo.decorators, propInfo.fieldInfo) : propSchema;
1261
+ if (!propInfo.optional) {
1262
+ required.push(propInfo.name);
1263
+ }
1264
+ }
1265
+ visiting.delete(type);
1266
+ return {
1267
+ jsonSchema: {
1268
+ type: "object",
1269
+ properties,
1270
+ ...required.length > 0 ? { required } : {}
1271
+ },
1272
+ formSpecFieldType: "object"
1273
+ };
1274
+ }
1275
+ function createFormSpecField(fieldName, type, decorators, optional, checker, visitedTypes = /* @__PURE__ */ new Set()) {
1276
+ const { formSpecFieldType } = convertType(type, checker);
1277
+ const field = {
1278
+ _field: formSpecFieldType,
1279
+ id: fieldName
1280
+ };
1281
+ if (!optional) {
1282
+ field.required = true;
1283
+ }
1284
+ if (formSpecFieldType === "object" && type.flags & ts4.TypeFlags.Object) {
1285
+ if (!visitedTypes.has(type)) {
1286
+ visitedTypes.add(type);
1287
+ const nestedFields = [];
1288
+ for (const propInfo of getObjectPropertyInfos(type, checker)) {
1289
+ nestedFields.push(
1290
+ createFormSpecField(
1291
+ propInfo.name,
1292
+ propInfo.type,
1293
+ propInfo.fieldInfo?.decorators ?? [],
1294
+ propInfo.optional,
1295
+ checker,
1296
+ visitedTypes
1297
+ )
1298
+ );
1299
+ }
1300
+ visitedTypes.delete(type);
1301
+ if (nestedFields.length > 0) {
1302
+ field.fields = nestedFields;
1303
+ }
1304
+ }
1305
+ }
1306
+ for (const dec of decorators) {
1307
+ applyDecoratorToField(field, dec);
1308
+ }
1309
+ return field;
1310
+ }
1311
+ function applyDecoratorToField(field, decorator) {
1312
+ const { args } = decorator;
1313
+ const resolved = decorator.resolved;
1314
+ const effectiveName = resolved?.extendsBuiltin ?? decorator.name;
1315
+ switch (effectiveName) {
1316
+ case "Field": {
1317
+ const opts = args[0];
1318
+ if (typeof opts === "object" && opts !== null && !Array.isArray(opts)) {
1319
+ if (typeof opts["displayName"] === "string") {
1320
+ field.label = opts["displayName"];
1321
+ }
1322
+ if (typeof opts["description"] === "string") {
1323
+ field.description = opts["description"];
1324
+ }
1325
+ if (typeof opts["placeholder"] === "string") {
1326
+ field.placeholder = opts["placeholder"];
1327
+ }
1328
+ }
1329
+ break;
1330
+ }
1331
+ case "Minimum":
1332
+ if (typeof args[0] === "number") {
1333
+ field.min = args[0];
1334
+ }
1335
+ break;
1336
+ case "Maximum":
1337
+ if (typeof args[0] === "number") {
1338
+ field.max = args[0];
1339
+ }
1340
+ break;
1341
+ case "MinLength":
1342
+ if (typeof args[0] === "number") {
1343
+ field.minLength = args[0];
1344
+ }
1345
+ break;
1346
+ case "MaxLength":
1347
+ if (typeof args[0] === "number") {
1348
+ field.maxLength = args[0];
1349
+ }
1350
+ break;
1351
+ case "Pattern":
1352
+ if (typeof args[0] === "string") {
1353
+ field.pattern = args[0];
1354
+ }
1355
+ break;
1356
+ case "EnumOptions":
1357
+ if (Array.isArray(args[0])) {
1358
+ field.options = args[0];
1359
+ }
1360
+ break;
1361
+ case "ShowWhen":
1362
+ if (typeof args[0] === "object" && args[0] !== null) {
1363
+ field.showWhen = args[0];
1364
+ }
1365
+ break;
1366
+ case "Group":
1367
+ if (typeof args[0] === "string") {
1368
+ field.group = args[0];
1369
+ }
1370
+ break;
1371
+ }
1372
+ }
1373
+ function applyDecoratorsToSchema(schema, decorators, fieldInfo) {
1374
+ const result = { ...schema };
1375
+ for (const dec of decorators) {
1376
+ const { args } = dec;
1377
+ const resolved = dec.resolved;
1378
+ const effectiveName = resolved?.extendsBuiltin ?? dec.name;
1379
+ switch (effectiveName) {
1380
+ case "Field": {
1381
+ const opts = args[0];
1382
+ if (typeof opts === "object" && opts !== null && !Array.isArray(opts)) {
1383
+ if (typeof opts["displayName"] === "string") {
1384
+ result.title = opts["displayName"];
1385
+ }
1386
+ if (typeof opts["description"] === "string") {
1387
+ result.description = opts["description"];
1388
+ }
1389
+ }
1390
+ break;
1391
+ }
1392
+ case "Minimum":
1393
+ if (typeof args[0] === "number") {
1394
+ result.minimum = args[0];
1395
+ }
1396
+ break;
1397
+ case "Maximum":
1398
+ if (typeof args[0] === "number") {
1399
+ result.maximum = args[0];
1400
+ }
1401
+ break;
1402
+ case "ExclusiveMinimum":
1403
+ if (typeof args[0] === "number") {
1404
+ result.exclusiveMinimum = args[0];
1405
+ }
1406
+ break;
1407
+ case "ExclusiveMaximum":
1408
+ if (typeof args[0] === "number") {
1409
+ result.exclusiveMaximum = args[0];
1410
+ }
1411
+ break;
1412
+ case "MinLength":
1413
+ if (typeof args[0] === "number") {
1414
+ result.minLength = args[0];
1415
+ }
1416
+ break;
1417
+ case "MaxLength":
1418
+ if (typeof args[0] === "number") {
1419
+ result.maxLength = args[0];
1420
+ }
1421
+ break;
1422
+ case "Pattern":
1423
+ if (typeof args[0] === "string") {
1424
+ result.pattern = args[0];
1425
+ }
1426
+ break;
1427
+ }
1428
+ if (resolved?.extensionName && /^[a-z][a-z0-9-]*$/.test(resolved.extensionName)) {
1429
+ const key = `x-formspec-${resolved.extensionName}`;
1430
+ if (resolved.isMarker) {
1431
+ setSchemaExtension(result, key, true);
1432
+ } else {
1433
+ setSchemaExtension(result, key, args[0] ?? true);
1434
+ }
1435
+ }
1436
+ }
1437
+ if (fieldInfo) {
1438
+ if (fieldInfo.deprecated) {
1439
+ result.deprecated = true;
1440
+ }
1441
+ if (fieldInfo.defaultValue !== void 0) {
1442
+ result.default = fieldInfo.defaultValue;
1443
+ }
1444
+ }
1445
+ return result;
1446
+ }
1447
+ var init_type_converter = __esm({
1448
+ "src/analyzer/type-converter.ts"() {
1449
+ "use strict";
1450
+ init_class_analyzer();
1451
+ init_types();
1452
+ }
1453
+ });
1454
+
1455
+ // src/analyzer/program.ts
1456
+ import * as ts5 from "typescript";
1457
+ import * as path from "path";
1458
+ function createProgramContext(filePath) {
1459
+ const absolutePath = path.resolve(filePath);
1460
+ const fileDir = path.dirname(absolutePath);
1461
+ const configPath = ts5.findConfigFile(fileDir, ts5.sys.fileExists.bind(ts5.sys), "tsconfig.json");
1462
+ let compilerOptions;
1463
+ let fileNames;
1464
+ if (configPath) {
1465
+ const configFile = ts5.readConfigFile(configPath, ts5.sys.readFile.bind(ts5.sys));
1466
+ if (configFile.error) {
1467
+ throw new Error(
1468
+ `Error reading tsconfig.json: ${ts5.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
1469
+ );
1470
+ }
1471
+ const parsed = ts5.parseJsonConfigFileContent(
1472
+ configFile.config,
1473
+ ts5.sys,
1474
+ path.dirname(configPath)
1475
+ );
1476
+ if (parsed.errors.length > 0) {
1477
+ const errorMessages = parsed.errors.map((e) => ts5.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
1478
+ throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
1479
+ }
1480
+ compilerOptions = parsed.options;
1481
+ fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
1482
+ } else {
1483
+ compilerOptions = {
1484
+ target: ts5.ScriptTarget.ES2022,
1485
+ module: ts5.ModuleKind.NodeNext,
1486
+ moduleResolution: ts5.ModuleResolutionKind.NodeNext,
1487
+ strict: true,
1488
+ skipLibCheck: true,
1489
+ declaration: true
1490
+ };
1491
+ fileNames = [absolutePath];
1492
+ }
1493
+ const program = ts5.createProgram(fileNames, compilerOptions);
1494
+ const sourceFile = program.getSourceFile(absolutePath);
1495
+ if (!sourceFile) {
1496
+ throw new Error(`Could not find source file: ${absolutePath}`);
1497
+ }
1498
+ return {
1499
+ program,
1500
+ checker: program.getTypeChecker(),
1501
+ sourceFile
1502
+ };
1503
+ }
1504
+ function findNodeByName(sourceFile, name, predicate, getName) {
1505
+ let result = null;
1506
+ function visit(node) {
1507
+ if (result) return;
1508
+ if (predicate(node) && getName(node) === name) {
1509
+ result = node;
1510
+ return;
1511
+ }
1512
+ ts5.forEachChild(node, visit);
1513
+ }
1514
+ visit(sourceFile);
1515
+ return result;
1516
+ }
1517
+ function findClassByName(sourceFile, className) {
1518
+ return findNodeByName(sourceFile, className, ts5.isClassDeclaration, (n) => n.name?.text);
1519
+ }
1520
+ function findInterfaceByName(sourceFile, interfaceName) {
1521
+ return findNodeByName(sourceFile, interfaceName, ts5.isInterfaceDeclaration, (n) => n.name.text);
1522
+ }
1523
+ function findTypeAliasByName(sourceFile, aliasName) {
1524
+ return findNodeByName(sourceFile, aliasName, ts5.isTypeAliasDeclaration, (n) => n.name.text);
1525
+ }
1526
+ var init_program = __esm({
1527
+ "src/analyzer/program.ts"() {
1528
+ "use strict";
1529
+ }
1530
+ });
1531
+
1532
+ // src/generators/class-schema.ts
1533
+ function generateClassSchemas(analysis, checker) {
1534
+ const properties = {};
1535
+ const required = [];
1536
+ const uiElements = [];
1537
+ for (const field of analysis.fields) {
1538
+ const { jsonSchema: baseSchema } = convertType(field.type, checker);
1539
+ const fieldSchema = applyDecoratorsToSchema(baseSchema, field.decorators, field);
1540
+ properties[field.name] = fieldSchema;
1541
+ if (!field.optional) {
1542
+ required.push(field.name);
1543
+ }
1544
+ const formSpecField = createFormSpecField(
1545
+ field.name,
1546
+ field.type,
1547
+ field.decorators,
1548
+ field.optional,
1549
+ checker
1550
+ );
1551
+ uiElements.push(formSpecField);
1552
+ }
1553
+ const jsonSchema = {
1554
+ type: "object",
1555
+ properties,
1556
+ ...required.length > 0 ? { required } : {}
1557
+ };
1558
+ const uiSchema2 = generateUiSchemaFromFields(uiElements);
1559
+ return { jsonSchema, uiSchema: uiSchema2 };
1560
+ }
1561
+ function generateSchemasFromClass(options) {
1562
+ const ctx = createProgramContext(options.filePath);
1563
+ const classDecl = findClassByName(ctx.sourceFile, options.className);
1564
+ if (!classDecl) {
1565
+ throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
1566
+ }
1567
+ const analysis = analyzeClass(classDecl, ctx.checker);
1568
+ return generateClassSchemas(analysis, ctx.checker);
1569
+ }
1570
+ function generateSchemas(options) {
1571
+ const ctx = createProgramContext(options.filePath);
1572
+ const classDecl = findClassByName(ctx.sourceFile, options.typeName);
1573
+ if (classDecl) {
1574
+ const analysis = analyzeClass(classDecl, ctx.checker);
1575
+ return generateClassSchemas(analysis, ctx.checker);
1576
+ }
1577
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
1578
+ if (interfaceDecl) {
1579
+ const analysis = analyzeInterface(interfaceDecl, ctx.checker);
1580
+ return generateClassSchemas(analysis, ctx.checker);
1581
+ }
1582
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
1583
+ if (typeAlias) {
1584
+ const result = analyzeTypeAlias(typeAlias, ctx.checker);
1585
+ if (result.ok) {
1586
+ return generateClassSchemas(result.analysis, ctx.checker);
1587
+ }
1588
+ throw new Error(result.error);
1589
+ }
1590
+ throw new Error(
1591
+ `Type "${options.typeName}" not found as a class, interface, or type alias in ${options.filePath}`
1592
+ );
1593
+ }
1594
+ var init_class_schema = __esm({
1595
+ "src/generators/class-schema.ts"() {
1596
+ "use strict";
1597
+ init_type_converter();
1598
+ init_generator2();
1599
+ init_program();
1600
+ init_class_analyzer();
1601
+ }
1602
+ });
1603
+
1604
+ // src/codegen/index.ts
1605
+ import * as ts6 from "typescript";
1606
+ import * as path2 from "path";
1607
+ import * as fs from "fs";
1608
+ function findDecoratedClasses(files, baseDir) {
1609
+ const program = ts6.createProgram(files, {
1610
+ target: ts6.ScriptTarget.ESNext,
1611
+ module: ts6.ModuleKind.ESNext,
1612
+ moduleResolution: ts6.ModuleResolutionKind.NodeNext,
1613
+ // TC39 Stage 3 decorators — do not use experimentalDecorators
1614
+ strict: true
1615
+ });
1616
+ const checker = program.getTypeChecker();
1617
+ const results = [];
1618
+ for (const sourceFile of program.getSourceFiles()) {
1619
+ if (sourceFile.isDeclarationFile) continue;
1620
+ if (!files.some((f) => path2.resolve(f) === sourceFile.fileName)) continue;
1621
+ ts6.forEachChild(sourceFile, (node) => {
1622
+ if (ts6.isClassDeclaration(node) && hasDecoratedProperties(node)) {
1623
+ const className = node.name?.text;
1624
+ if (!className) return;
1625
+ const typeMetadata = extractTypeMetadata(node, checker);
1626
+ const relativePath = path2.relative(baseDir, sourceFile.fileName);
1627
+ const exported = isClassExported(node, sourceFile);
1628
+ results.push({
1629
+ name: className,
1630
+ sourcePath: relativePath.replace(/\.tsx?$/, ""),
1631
+ typeMetadata,
1632
+ isExported: exported
1633
+ });
1634
+ }
1635
+ });
1636
+ }
1637
+ return results;
1638
+ }
1639
+ function hasDecoratedProperties(node) {
1640
+ return node.members.some((member) => {
1641
+ if (!ts6.isPropertyDeclaration(member)) return false;
1642
+ const decorators = ts6.getDecorators(member);
1643
+ return decorators !== void 0 && decorators.length > 0;
1644
+ });
1645
+ }
1646
+ function isClassExported(classNode, sourceFile) {
1647
+ const className = classNode.name?.text;
1648
+ if (!className) return false;
1649
+ const modifiers = ts6.getModifiers(classNode);
1650
+ if (modifiers) {
1651
+ const hasExport = modifiers.some((mod) => mod.kind === ts6.SyntaxKind.ExportKeyword);
1652
+ if (hasExport) return true;
1653
+ }
1654
+ for (const statement of sourceFile.statements) {
1655
+ if (ts6.isExportDeclaration(statement) && statement.exportClause) {
1656
+ if (ts6.isNamedExports(statement.exportClause)) {
1657
+ for (const element of statement.exportClause.elements) {
1658
+ const localName = element.propertyName?.text ?? element.name.text;
1659
+ if (localName === className) {
1660
+ return true;
1661
+ }
1662
+ }
1663
+ }
1664
+ }
1665
+ }
1666
+ return false;
1667
+ }
1668
+ function extractTypeMetadata(classNode, checker) {
1669
+ const metadata = {};
1670
+ for (const member of classNode.members) {
1671
+ if (!ts6.isPropertyDeclaration(member)) continue;
1672
+ if (!member.name || !ts6.isIdentifier(member.name)) continue;
1673
+ const symbol = checker.getSymbolAtLocation(member.name);
1674
+ if (!symbol) continue;
1675
+ const type = checker.getTypeOfSymbolAtLocation(symbol, member);
1676
+ const fieldName = member.name.text;
1677
+ const isOptional = !!(symbol.flags & ts6.SymbolFlags.Optional);
1678
+ const typeInfo = convertTypeToMetadata(type, checker);
1679
+ if (isOptional) {
1680
+ typeInfo.optional = true;
1681
+ }
1682
+ metadata[fieldName] = typeInfo;
1683
+ }
1684
+ return metadata;
1685
+ }
1686
+ function convertTypeToMetadata(type, checker, visited = /* @__PURE__ */ new Set()) {
1687
+ const isObjectType = (type.flags & ts6.TypeFlags.Object) !== 0;
1688
+ if (isObjectType) {
1689
+ if (visited.has(type)) {
1690
+ return { type: "unknown" };
1691
+ }
1692
+ visited.add(type);
1693
+ }
1694
+ if (type.isUnion()) {
1695
+ const types = type.types;
1696
+ const nonNullTypes = types.filter(
1697
+ (t) => !(t.flags & (ts6.TypeFlags.Null | ts6.TypeFlags.Undefined))
1698
+ );
1699
+ const hasNull = types.some((t) => t.flags & ts6.TypeFlags.Null);
1700
+ const isBooleanUnion = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts6.TypeFlags.BooleanLiteral);
1701
+ if (isBooleanUnion) {
1702
+ const result = { type: "boolean" };
1703
+ if (hasNull) result.nullable = true;
1704
+ return result;
1705
+ }
1706
+ const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1707
+ if (allStringLiterals && nonNullTypes.length > 0) {
1708
+ const result = {
1709
+ type: "enum",
1710
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- TypeScript doesn't narrow array types from `every` predicate
1711
+ values: nonNullTypes.map((t) => t.value)
1712
+ };
1713
+ if (hasNull) result.nullable = true;
1714
+ return result;
1715
+ }
1716
+ const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1717
+ if (allNumberLiterals && nonNullTypes.length > 0) {
1718
+ const result = {
1719
+ type: "enum",
1720
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- TypeScript doesn't narrow array types from `every` predicate
1721
+ values: nonNullTypes.map((t) => t.value)
1722
+ };
1723
+ if (hasNull) result.nullable = true;
1724
+ return result;
1725
+ }
1726
+ if (nonNullTypes.length === 1) {
1727
+ const singleType = nonNullTypes[0];
1728
+ if (!singleType) return { type: "unknown" };
1729
+ const result = convertTypeToMetadata(singleType, checker, visited);
1730
+ if (hasNull) result.nullable = true;
1731
+ return result;
1732
+ }
1733
+ return { type: "unknown" };
1734
+ }
1735
+ if (type.flags & ts6.TypeFlags.String) return { type: "string" };
1736
+ if (type.flags & ts6.TypeFlags.Number) return { type: "number" };
1737
+ if (type.flags & ts6.TypeFlags.Boolean) return { type: "boolean" };
1738
+ if (type.isStringLiteral()) {
1739
+ return { type: "enum", values: [type.value] };
1740
+ }
1741
+ if (type.isNumberLiteral()) {
1742
+ return { type: "enum", values: [type.value] };
1743
+ }
1744
+ if (checker.isArrayType(type)) {
1745
+ const typeArgs = type.typeArguments;
1746
+ return {
1747
+ type: "array",
1748
+ itemType: typeArgs?.[0] ? convertTypeToMetadata(typeArgs[0], checker, visited) : { type: "unknown" }
1749
+ };
1750
+ }
1751
+ if (type.flags & ts6.TypeFlags.Object) {
1752
+ const properties = {};
1753
+ for (const prop of type.getProperties()) {
1754
+ const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
1755
+ if (!declaration) continue;
1756
+ const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1757
+ const propOptional = !!(prop.flags & ts6.SymbolFlags.Optional);
1758
+ const propMeta = convertTypeToMetadata(propType, checker, visited);
1759
+ if (propOptional) propMeta.optional = true;
1760
+ properties[prop.name] = propMeta;
1761
+ }
1762
+ if (Object.keys(properties).length > 0) {
1763
+ return { type: "object", properties };
1764
+ }
1765
+ return { type: "object" };
1766
+ }
1767
+ return { type: "unknown" };
1768
+ }
1769
+ function generateCodegenOutput(classes, outputPath, baseDir) {
1770
+ const outputDir = path2.dirname(path2.resolve(outputPath));
1771
+ const lines = [
1772
+ "/**",
1773
+ " * Auto-generated by FormSpec CLI.",
1774
+ " * DO NOT EDIT - changes will be overwritten.",
1775
+ " *",
1776
+ " * This file provides:",
1777
+ " * - Type metadata patches for runtime schema generation",
1778
+ " * - Inferred schema types (e.g., UserFormSchema)",
1779
+ " * - Type-safe FormSpec accessors (e.g., getUserFormFormSpec())",
1780
+ " *",
1781
+ " * Usage:",
1782
+ " * import { UserFormSchema, getUserFormFormSpec } from './__formspec_types__';",
1783
+ " *",
1784
+ " * const data: UserFormSchema = { name: 'Alice', country: 'us' };",
1785
+ " * const spec = getUserFormFormSpec();",
1786
+ " */",
1787
+ "",
1788
+ "/* eslint-disable @typescript-eslint/no-explicit-any */",
1789
+ ""
1790
+ ];
1791
+ for (const cls of classes) {
1792
+ const absoluteSourcePath = path2.resolve(baseDir, cls.sourcePath);
1793
+ const importPath = getRelativeImportPath(outputDir, absoluteSourcePath);
1794
+ lines.push(`import { ${cls.name} } from "${importPath}";`);
1795
+ }
1796
+ lines.push("");
1797
+ lines.push("// =============================================================================");
1798
+ lines.push("// Type Metadata Patches");
1799
+ lines.push("// =============================================================================");
1800
+ lines.push("");
1801
+ for (const cls of classes) {
1802
+ const metadataJson = JSON.stringify(cls.typeMetadata, null, 2).split("\n").map((line, i) => i === 0 ? line : " " + line).join("\n");
1803
+ lines.push(`(${cls.name} as any).__formspec_types__ = ${metadataJson};`);
1804
+ lines.push("");
1805
+ }
1806
+ lines.push("// =============================================================================");
1807
+ lines.push("// Inferred Schema Types");
1808
+ lines.push("// =============================================================================");
1809
+ lines.push("");
1810
+ for (const cls of classes) {
1811
+ lines.push(generateSchemaType(cls));
1812
+ lines.push("");
1813
+ }
1814
+ lines.push("// =============================================================================");
1815
+ lines.push("// Type-Safe FormSpec Accessors");
1816
+ lines.push("// =============================================================================");
1817
+ lines.push("");
1818
+ for (const cls of classes) {
1819
+ lines.push(generateTypedAccessor(cls));
1820
+ lines.push("");
1821
+ }
1822
+ return lines.join("\n");
1823
+ }
1824
+ function metadataToTypeString(metadata) {
1825
+ const baseType = metadataToBaseTypeString(metadata);
1826
+ if (metadata.nullable && metadata.optional) {
1827
+ return `${baseType} | null | undefined`;
1828
+ }
1829
+ if (metadata.nullable) {
1830
+ return `${baseType} | null`;
1831
+ }
1832
+ if (metadata.optional) {
1833
+ return `${baseType} | undefined`;
1834
+ }
1835
+ return baseType;
1836
+ }
1837
+ function needsPropertyQuoting(key) {
1838
+ if (!VALID_IDENTIFIER.test(key)) {
1839
+ return true;
1840
+ }
1841
+ return RESERVED_WORDS.has(key);
1842
+ }
1843
+ function escapePropertyKey(key) {
1844
+ if (needsPropertyQuoting(key)) {
1845
+ return JSON.stringify(key);
1846
+ }
1847
+ return key;
1848
+ }
1849
+ function metadataToBaseTypeString(metadata) {
1850
+ switch (metadata.type) {
1851
+ case "string":
1852
+ return "string";
1853
+ case "number":
1854
+ return "number";
1855
+ case "boolean":
1856
+ return "boolean";
1857
+ case "enum":
1858
+ if (metadata.values && metadata.values.length > 0) {
1859
+ return metadata.values.map((v) => typeof v === "string" ? JSON.stringify(v) : String(v)).join(" | ");
1860
+ }
1861
+ return "string";
1862
+ case "array":
1863
+ if (metadata.itemType) {
1864
+ const itemType = metadataToTypeString(metadata.itemType);
1865
+ if (itemType.includes("|")) {
1866
+ return `(${itemType})[]`;
1867
+ }
1868
+ return `${itemType}[]`;
1869
+ }
1870
+ return "unknown[]";
1871
+ case "object":
1872
+ if (metadata.properties && Object.keys(metadata.properties).length > 0) {
1873
+ const props = Object.entries(metadata.properties).map(([key, propMeta]) => {
1874
+ const optional = propMeta.optional ? "?" : "";
1875
+ return `${escapePropertyKey(key)}${optional}: ${metadataToTypeString(propMeta)}`;
1876
+ }).join("; ");
1877
+ return `{ ${props} }`;
1878
+ }
1879
+ return "Record<string, unknown>";
1880
+ default:
1881
+ return "unknown";
1882
+ }
1883
+ }
1884
+ function generateSchemaType(cls) {
1885
+ const props = Object.entries(cls.typeMetadata).map(([fieldName, metadata]) => {
1886
+ const optional = metadata.optional ? "?" : "";
1887
+ const typeStr = metadataToTypeString(metadata);
1888
+ return ` ${escapePropertyKey(fieldName)}${optional}: ${typeStr};`;
1889
+ }).join("\n");
1890
+ return `export type ${cls.name}Schema = {
1891
+ ${props}
1892
+ };`;
1893
+ }
1894
+ function metadataTypeToFieldType(type) {
1895
+ switch (type) {
1896
+ case "string":
1897
+ return "text";
1898
+ case "number":
1899
+ return "number";
1900
+ case "boolean":
1901
+ return "boolean";
1902
+ case "enum":
1903
+ return "enum";
1904
+ case "array":
1905
+ return "array";
1906
+ case "object":
1907
+ return "object";
1908
+ default:
1909
+ return "text";
1910
+ }
1911
+ }
1912
+ function generateTypedAccessor(cls) {
1913
+ const lines = [];
1914
+ const elementTypes = Object.entries(cls.typeMetadata).map(([fieldName, metadata]) => {
1915
+ const fieldType = metadataTypeToFieldType(metadata.type);
1916
+ const required = !metadata.optional;
1917
+ let elementType = `{ readonly _field: "${fieldType}"; readonly id: "${fieldName}"; readonly required: ${String(required)}`;
1918
+ if (metadata.type === "enum" && metadata.values) {
1919
+ const optionValues = metadata.values.map((v) => typeof v === "string" ? JSON.stringify(v) : String(v)).join(", ");
1920
+ elementType += `; readonly options: readonly [${optionValues}]`;
1921
+ }
1922
+ elementType += " }";
1923
+ return elementType;
1924
+ });
1925
+ lines.push(`export type ${cls.name}Elements = readonly [
1926
+ ${elementTypes.join(",\n ")}
1927
+ ];`);
1928
+ lines.push("");
1929
+ lines.push(`export type ${cls.name}FormSpec = { readonly elements: ${cls.name}Elements };`);
1930
+ lines.push("");
1931
+ lines.push(`/**`);
1932
+ lines.push(` * Type-safe FormSpec accessor for ${cls.name}.`);
1933
+ lines.push(` * Reads the patched type metadata from the class.`);
1934
+ lines.push(` */`);
1935
+ lines.push(`export function get${cls.name}FormSpec(): ${cls.name}FormSpec {`);
1936
+ lines.push(
1937
+ ` const types = (${cls.name} as any).__formspec_types__ as Record<string, unknown>[];`
1938
+ );
1939
+ lines.push(` return { elements: types } as unknown as ${cls.name}FormSpec;`);
1940
+ lines.push(`}`);
1941
+ return lines.join("\n");
1942
+ }
1943
+ function getRelativeImportPath(outputDir, sourcePath) {
1944
+ let relativePath = path2.relative(outputDir, sourcePath);
1945
+ if (!relativePath.startsWith(".")) {
1946
+ relativePath = "./" + relativePath;
1947
+ }
1948
+ relativePath = relativePath.replace(/\\/g, "/");
1949
+ return relativePath + ".js";
1950
+ }
1951
+ function runCodegen(options) {
1952
+ const baseDir = options.baseDir ?? path2.dirname(options.output);
1953
+ const absoluteFiles = options.files.map((f) => path2.resolve(f));
1954
+ console.log(`Scanning ${String(absoluteFiles.length)} file(s) for decorated classes...`);
1955
+ const classes = findDecoratedClasses(absoluteFiles, baseDir);
1956
+ if (classes.length === 0) {
1957
+ console.log("No decorated classes found.");
1958
+ return;
1959
+ }
1960
+ console.log(`Found ${String(classes.length)} decorated class(es):`);
1961
+ for (const cls of classes) {
1962
+ const fieldCount = Object.keys(cls.typeMetadata).length;
1963
+ console.log(` - ${cls.name} (${String(fieldCount)} field(s))`);
1964
+ }
1965
+ const unexported = classes.filter((cls) => !cls.isExported);
1966
+ if (unexported.length > 0) {
1967
+ console.warn(
1968
+ `
1969
+ \u26A0\uFE0F Warning: The following decorated classes are not exported from their source files:`
1970
+ );
1971
+ for (const cls of unexported) {
1972
+ console.warn(` - ${cls.name} (${cls.sourcePath})`);
1973
+ }
1974
+ console.warn(
1975
+ `
1976
+ The generated code will fail to compile because it cannot import these classes.`
1977
+ );
1978
+ console.warn(` To fix this, add 'export' to the class declaration, for example:`);
1979
+ console.warn(` export class YourClassName { ... }
1980
+ `);
1981
+ }
1982
+ const output = generateCodegenOutput(classes, options.output, baseDir);
1983
+ const outputDir = path2.dirname(options.output);
1984
+ if (!fs.existsSync(outputDir)) {
1985
+ fs.mkdirSync(outputDir, { recursive: true });
1986
+ }
1987
+ fs.writeFileSync(options.output, output);
1988
+ console.log(`
1989
+ Generated: ${options.output}`);
1990
+ }
1991
+ var VALID_IDENTIFIER, RESERVED_WORDS;
1992
+ var init_codegen = __esm({
1993
+ "src/codegen/index.ts"() {
1994
+ "use strict";
1995
+ VALID_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
1996
+ RESERVED_WORDS = /* @__PURE__ */ new Set([
1997
+ // Keywords
1998
+ "break",
1999
+ "case",
2000
+ "catch",
2001
+ "continue",
2002
+ "debugger",
2003
+ "default",
2004
+ "delete",
2005
+ "do",
2006
+ "else",
2007
+ "finally",
2008
+ "for",
2009
+ "function",
2010
+ "if",
2011
+ "in",
2012
+ "instanceof",
2013
+ "new",
2014
+ "return",
2015
+ "switch",
2016
+ "this",
2017
+ "throw",
2018
+ "try",
2019
+ "typeof",
2020
+ "var",
2021
+ "void",
2022
+ "while",
2023
+ "with",
2024
+ "class",
2025
+ "const",
2026
+ "enum",
2027
+ "export",
2028
+ "extends",
2029
+ "import",
2030
+ "super",
2031
+ "implements",
2032
+ "interface",
2033
+ "let",
2034
+ "package",
2035
+ "private",
2036
+ "protected",
2037
+ "public",
2038
+ "static",
2039
+ "yield",
2040
+ // ES6+ keywords
2041
+ "async",
2042
+ "await",
2043
+ // Literal values (not technically keywords but best to quote)
2044
+ "null",
2045
+ "true",
2046
+ "false",
2047
+ // Accessor keywords
2048
+ "get",
2049
+ "set",
2050
+ // Strict mode reserved
2051
+ "arguments",
2052
+ "eval"
2053
+ ]);
2054
+ }
2055
+ });
2056
+
2057
+ // src/index.ts
2058
+ var index_exports = {};
2059
+ __export(index_exports, {
2060
+ buildFormSchemas: () => buildFormSchemas,
2061
+ categorizationSchema: () => categorizationSchema,
2062
+ categorySchema: () => categorySchema,
2063
+ controlSchema: () => controlSchema,
2064
+ findDecoratedClasses: () => findDecoratedClasses,
2065
+ generateCodegenOutput: () => generateCodegenOutput,
2066
+ generateJsonSchema: () => generateJsonSchema,
2067
+ generateSchemas: () => generateSchemas,
2068
+ generateSchemasFromClass: () => generateSchemasFromClass,
2069
+ generateUiSchema: () => generateUiSchema,
2070
+ generateUiSchemaFromFields: () => generateUiSchemaFromFields,
2071
+ getSchemaExtension: () => getSchemaExtension,
2072
+ groupLayoutSchema: () => groupLayoutSchema,
2073
+ horizontalLayoutSchema: () => horizontalLayoutSchema,
2074
+ jsonSchema7Schema: () => jsonSchema7Schema,
2075
+ jsonSchemaTypeSchema: () => jsonSchemaTypeSchema,
2076
+ labelElementSchema: () => labelElementSchema,
2077
+ ruleConditionSchema: () => ruleConditionSchema,
2078
+ ruleEffectSchema: () => ruleEffectSchema,
2079
+ ruleSchema: () => ruleSchema,
2080
+ runCodegen: () => runCodegen,
2081
+ schemaBasedConditionSchema: () => schemaBasedConditionSchema,
2082
+ setSchemaExtension: () => setSchemaExtension,
2083
+ uiSchemaElementSchema: () => uiSchemaElementSchema,
2084
+ uiSchemaElementTypeSchema: () => uiSchemaElementTypeSchema,
2085
+ uiSchemaSchema: () => uiSchema,
2086
+ verticalLayoutSchema: () => verticalLayoutSchema,
2087
+ writeSchemas: () => writeSchemas
2088
+ });
2089
+ import * as fs2 from "fs";
2090
+ import * as path3 from "path";
2091
+ function buildFormSchemas(form) {
2092
+ return {
2093
+ jsonSchema: generateJsonSchema(form),
2094
+ uiSchema: generateUiSchema(form)
2095
+ };
2096
+ }
2097
+ function writeSchemas(form, options) {
2098
+ const { outDir, name = "schema", indent = 2 } = options;
2099
+ const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form);
2100
+ if (!fs2.existsSync(outDir)) {
2101
+ fs2.mkdirSync(outDir, { recursive: true });
2102
+ }
2103
+ const jsonSchemaPath = path3.join(outDir, `${name}-schema.json`);
2104
+ const uiSchemaPath = path3.join(outDir, `${name}-uischema.json`);
2105
+ fs2.writeFileSync(jsonSchemaPath, JSON.stringify(jsonSchema, null, indent));
2106
+ fs2.writeFileSync(uiSchemaPath, JSON.stringify(uiSchema2, null, indent));
2107
+ return { jsonSchemaPath, uiSchemaPath };
2108
+ }
2109
+ var init_index = __esm({
2110
+ "src/index.ts"() {
2111
+ "use strict";
2112
+ init_generator();
2113
+ init_generator2();
2114
+ init_types();
2115
+ init_schema2();
2116
+ init_schema();
2117
+ init_generator();
2118
+ init_generator2();
2119
+ init_class_schema();
2120
+ init_codegen();
2121
+ }
2122
+ });
2123
+
2124
+ // src/cli.ts
2125
+ import * as path4 from "path";
2126
+ import { pathToFileURL } from "url";
18
2127
  function printHelp() {
19
- console.log(`
2128
+ console.log(`
20
2129
  FormSpec Build CLI - Generate JSON Schema and UI Schema
21
2130
 
22
2131
  Usage:
@@ -38,101 +2147,93 @@ The input file should export a FormSpec as its default export or as 'form':
38
2147
  `);
39
2148
  }
40
2149
  function parseArgs(args) {
41
- const positional = [];
42
- let outDir = "./generated";
43
- let name = "";
44
- for (let i = 0; i < args.length; i++) {
45
- const arg = args[i];
46
- if (arg === undefined)
47
- continue;
48
- if (arg === "-h" || arg === "--help") {
49
- printHelp();
50
- process.exit(0);
51
- }
52
- if (arg === "-o" || arg === "--out-dir") {
53
- const nextArg = args[i + 1];
54
- if (!nextArg) {
55
- console.error("Error: --out-dir requires a value");
56
- return null;
57
- }
58
- outDir = nextArg;
59
- i++;
60
- continue;
61
- }
62
- if (arg === "-n" || arg === "--name") {
63
- const nextArg = args[i + 1];
64
- if (!nextArg) {
65
- console.error("Error: --name requires a value");
66
- return null;
67
- }
68
- name = nextArg;
69
- i++;
70
- continue;
71
- }
72
- if (arg.startsWith("-")) {
73
- console.error(`Error: Unknown option: ${arg}`);
74
- return null;
75
- }
76
- positional.push(arg);
2150
+ const positional = [];
2151
+ let outDir = "./generated";
2152
+ let name = "";
2153
+ for (let i = 0; i < args.length; i++) {
2154
+ const arg = args[i];
2155
+ if (arg === void 0) continue;
2156
+ if (arg === "-h" || arg === "--help") {
2157
+ printHelp();
2158
+ process.exit(0);
77
2159
  }
78
- if (positional.length === 0) {
79
- console.error("Error: No input file specified");
80
- printHelp();
2160
+ if (arg === "-o" || arg === "--out-dir") {
2161
+ const nextArg = args[i + 1];
2162
+ if (!nextArg) {
2163
+ console.error("Error: --out-dir requires a value");
81
2164
  return null;
2165
+ }
2166
+ outDir = nextArg;
2167
+ i++;
2168
+ continue;
82
2169
  }
83
- const inputFile = positional[0];
84
- if (!inputFile) {
85
- console.error("Error: No input file specified");
2170
+ if (arg === "-n" || arg === "--name") {
2171
+ const nextArg = args[i + 1];
2172
+ if (!nextArg) {
2173
+ console.error("Error: --name requires a value");
86
2174
  return null;
2175
+ }
2176
+ name = nextArg;
2177
+ i++;
2178
+ continue;
87
2179
  }
88
- // Default name from input file
89
- if (!name) {
90
- name = path.basename(inputFile, path.extname(inputFile));
2180
+ if (arg.startsWith("-")) {
2181
+ console.error(`Error: Unknown option: ${arg}`);
2182
+ return null;
91
2183
  }
92
- return { inputFile, outDir, name };
2184
+ positional.push(arg);
2185
+ }
2186
+ if (positional.length === 0) {
2187
+ console.error("Error: No input file specified");
2188
+ printHelp();
2189
+ return null;
2190
+ }
2191
+ const inputFile = positional[0];
2192
+ if (!inputFile) {
2193
+ console.error("Error: No input file specified");
2194
+ return null;
2195
+ }
2196
+ if (!name) {
2197
+ name = path4.basename(inputFile, path4.extname(inputFile));
2198
+ }
2199
+ return { inputFile, outDir, name };
93
2200
  }
94
2201
  async function main() {
95
- const args = process.argv.slice(2);
96
- const options = parseArgs(args);
97
- if (!options) {
98
- process.exit(1);
99
- }
100
- const { inputFile, outDir, name } = options;
101
- // Resolve input file path
102
- const absoluteInput = path.resolve(process.cwd(), inputFile);
103
- try {
104
- // Dynamically import the input file
105
- // Use file URL for cross-platform compatibility (Windows paths need file:// URLs)
106
- const fileUrl = pathToFileURL(absoluteInput).href;
107
- const module = (await import(fileUrl));
108
- // Look for the form export
109
- const form = module["default"] ?? module["form"];
110
- if (!form ||
111
- typeof form !== "object" ||
112
- !("elements" in form)) {
113
- console.error("Error: Input file must export a FormSpec as default export or as 'form'");
114
- console.error("Example:");
115
- console.error(' export default formspec(field.text("name"));');
116
- console.error(" // or");
117
- console.error(' export const form = formspec(field.text("name"));');
118
- process.exit(1);
119
- }
120
- // Import writeSchemas dynamically to avoid circular deps
121
- const { writeSchemas } = await import("./index.js");
122
- const { jsonSchemaPath, uiSchemaPath } = writeSchemas(form, { outDir, name });
123
- console.log("Generated:");
124
- console.log(` ${jsonSchemaPath}`);
125
- console.log(` ${uiSchemaPath}`);
126
- }
127
- catch (error) {
128
- if (error instanceof Error) {
129
- console.error(`Error: ${error.message}`);
130
- }
131
- else {
132
- console.error("Error:", error);
133
- }
134
- process.exit(1);
2202
+ const args = process.argv.slice(2);
2203
+ const options = parseArgs(args);
2204
+ if (!options) {
2205
+ process.exit(1);
2206
+ }
2207
+ const { inputFile, outDir, name } = options;
2208
+ const absoluteInput = path4.resolve(process.cwd(), inputFile);
2209
+ try {
2210
+ const fileUrl = pathToFileURL(absoluteInput).href;
2211
+ const module = await import(fileUrl);
2212
+ const form = module["default"] ?? module["form"];
2213
+ if (!form || typeof form !== "object" || !("elements" in form)) {
2214
+ console.error("Error: Input file must export a FormSpec as default export or as 'form'");
2215
+ console.error("Example:");
2216
+ console.error(' export default formspec(field.text("name"));');
2217
+ console.error(" // or");
2218
+ console.error(' export const form = formspec(field.text("name"));');
2219
+ process.exit(1);
2220
+ }
2221
+ const { writeSchemas: writeSchemas2 } = await Promise.resolve().then(() => (init_index(), index_exports));
2222
+ const { jsonSchemaPath, uiSchemaPath } = writeSchemas2(
2223
+ form,
2224
+ { outDir, name }
2225
+ );
2226
+ console.log("Generated:");
2227
+ console.log(` ${jsonSchemaPath}`);
2228
+ console.log(` ${uiSchemaPath}`);
2229
+ } catch (error) {
2230
+ if (error instanceof Error) {
2231
+ console.error(`Error: ${error.message}`);
2232
+ } else {
2233
+ console.error("Error:", error);
135
2234
  }
2235
+ process.exit(1);
2236
+ }
136
2237
  }
137
2238
  void main();
138
2239
  //# sourceMappingURL=cli.js.map