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