@formspec/cli 0.1.0-alpha.10

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 (38) hide show
  1. package/README.md +480 -0
  2. package/dist/__tests__/analyzer.test.d.ts +8 -0
  3. package/dist/__tests__/analyzer.test.d.ts.map +1 -0
  4. package/dist/__tests__/analyzer.test.js +70 -0
  5. package/dist/__tests__/analyzer.test.js.map +1 -0
  6. package/dist/__tests__/codegen.test.d.ts +8 -0
  7. package/dist/__tests__/codegen.test.d.ts.map +1 -0
  8. package/dist/__tests__/codegen.test.js +51 -0
  9. package/dist/__tests__/codegen.test.js.map +1 -0
  10. package/dist/__tests__/edge-cases.test.d.ts +12 -0
  11. package/dist/__tests__/edge-cases.test.d.ts.map +1 -0
  12. package/dist/__tests__/edge-cases.test.js +169 -0
  13. package/dist/__tests__/edge-cases.test.js.map +1 -0
  14. package/dist/__tests__/fixtures/edge-cases.d.ts +110 -0
  15. package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -0
  16. package/dist/__tests__/fixtures/edge-cases.js +137 -0
  17. package/dist/__tests__/fixtures/edge-cases.js.map +1 -0
  18. package/dist/__tests__/fixtures/sample-forms.d.ts +55 -0
  19. package/dist/__tests__/fixtures/sample-forms.d.ts.map +1 -0
  20. package/dist/__tests__/fixtures/sample-forms.js +78 -0
  21. package/dist/__tests__/fixtures/sample-forms.js.map +1 -0
  22. package/dist/__tests__/integration.test.d.ts +5 -0
  23. package/dist/__tests__/integration.test.d.ts.map +1 -0
  24. package/dist/__tests__/integration.test.js +186 -0
  25. package/dist/__tests__/integration.test.js.map +1 -0
  26. package/dist/index.d.ts +19 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +374 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/output/writer.d.ts +82 -0
  31. package/dist/output/writer.d.ts.map +1 -0
  32. package/dist/output/writer.js +152 -0
  33. package/dist/output/writer.js.map +1 -0
  34. package/dist/runtime/formspec-loader.d.ts +80 -0
  35. package/dist/runtime/formspec-loader.d.ts.map +1 -0
  36. package/dist/runtime/formspec-loader.js +152 -0
  37. package/dist/runtime/formspec-loader.js.map +1 -0
  38. package/package.json +47 -0
package/README.md ADDED
@@ -0,0 +1,480 @@
1
+ # @formspec/cli
2
+
3
+ CLI tool for generating JSON Schema and JSON Forms UI Schema from TypeScript source files.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @formspec/cli
9
+ # or
10
+ pnpm add @formspec/cli
11
+ ```
12
+
13
+ ## Requirements
14
+
15
+ **Package configuration** (package.json):
16
+
17
+ ```json
18
+ {
19
+ "type": "module"
20
+ }
21
+ ```
22
+
23
+ **TypeScript configuration** (tsconfig.json):
24
+
25
+ ```json
26
+ {
27
+ "compilerOptions": {
28
+ "experimentalDecorators": true,
29
+ "module": "NodeNext",
30
+ "moduleResolution": "NodeNext"
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```bash
38
+ # Generate schemas from a TypeScript class
39
+ formspec generate ./src/forms.ts MyClass -o ./generated
40
+
41
+ # Generate schemas from all FormSpec exports in a file (chain DSL)
42
+ formspec generate ./src/forms.ts -o ./generated
43
+ ```
44
+
45
+ ## Commands
46
+
47
+ ### `generate` - Build-time Schema Generation
48
+
49
+ Generate JSON Schema and UI Schema files from TypeScript source files.
50
+
51
+ ```bash
52
+ formspec generate <file> [className] [options]
53
+ ```
54
+
55
+ **Arguments:**
56
+
57
+ - `<file>` - Path to TypeScript source file
58
+ - `[className]` - Optional class name to analyze (required for decorated classes)
59
+
60
+ **Options:**
61
+
62
+ - `-o, --output <dir>` - Output directory (default: `./generated`)
63
+ - `-c, --compiled <path>` - Path to compiled JS file (auto-detected if omitted)
64
+
65
+ **Examples:**
66
+
67
+ ```bash
68
+ # Generate from a decorated class
69
+ formspec generate ./src/forms.ts UserForm -o ./generated
70
+
71
+ # Generate from chain DSL exports (requires compiled JS)
72
+ tsc && formspec generate ./src/forms.ts -o ./generated
73
+ ```
74
+
75
+ ### `codegen` - Runtime Type Metadata Generation
76
+
77
+ Generate a TypeScript file that patches decorated classes with type metadata, enabling runtime schema generation.
78
+
79
+ ```bash
80
+ formspec codegen <files...> [options]
81
+ ```
82
+
83
+ **Arguments:**
84
+
85
+ - `<files...>` - TypeScript source files to analyze
86
+
87
+ **Options:**
88
+
89
+ - `-o, --output <file>` - Output file (default: `./__formspec_types__.ts`)
90
+
91
+ **Examples:**
92
+
93
+ ```bash
94
+ # Generate type metadata for a single file
95
+ formspec codegen ./src/forms.ts -o ./src/__formspec_types__.ts
96
+
97
+ # Generate for multiple files
98
+ formspec codegen ./src/**/*.ts -o ./src/__formspec_types__.ts
99
+ ```
100
+
101
+ **Usage in code:**
102
+
103
+ ```typescript
104
+ // Import once at application entry point (side-effect import)
105
+ import "./__formspec_types__.js";
106
+
107
+ // Then use runtime schema generation
108
+ import { UserForm } from "./forms.js";
109
+ import { buildFormSchemas } from "@formspec/decorators";
110
+
111
+ const { jsonSchema, uiSchema } = buildFormSchemas(UserForm);
112
+ ```
113
+
114
+ **When to use `codegen`:**
115
+
116
+ - You want to generate schemas at runtime (not build time)
117
+ - You're using the decorator DSL (not chain DSL)
118
+ - You need type information that TypeScript erases at compile time
119
+
120
+ > **Note:** The chain DSL (`@formspec/dsl`) doesn't require codegen because it preserves type information at runtime.
121
+
122
+ ## Output Formats
123
+
124
+ FormSpec supports two output formats for UI schemas:
125
+
126
+ ### FormSpec Native Format (`ux_spec.json`)
127
+
128
+ Generated by the CLI's `generate` command. Uses FormSpec's internal representation:
129
+
130
+ ```json
131
+ {
132
+ "elements": [
133
+ {
134
+ "_field": "text",
135
+ "id": "name",
136
+ "label": "Full Name",
137
+ "required": true
138
+ },
139
+ {
140
+ "_field": "enum",
141
+ "id": "country",
142
+ "label": "Country",
143
+ "options": [
144
+ { "id": "us", "label": "United States" },
145
+ { "id": "ca", "label": "Canada" }
146
+ ]
147
+ }
148
+ ]
149
+ }
150
+ ```
151
+
152
+ ### JSON Forms Format (`uiSchema`)
153
+
154
+ Generated by `buildFormSchemas()` at runtime. Compatible with [JSON Forms](https://jsonforms.io/):
155
+
156
+ ```json
157
+ {
158
+ "type": "VerticalLayout",
159
+ "elements": [
160
+ {
161
+ "type": "Control",
162
+ "scope": "#/properties/name",
163
+ "label": "Full Name"
164
+ },
165
+ {
166
+ "type": "Control",
167
+ "scope": "#/properties/country",
168
+ "label": "Country"
169
+ }
170
+ ]
171
+ }
172
+ ```
173
+
174
+ ### Which Format to Use
175
+
176
+ | Use Case | Format | Generated By |
177
+ | ---------------------- | --------------- | ------------------------ |
178
+ | Custom form renderer | FormSpec Native | `formspec generate` CLI |
179
+ | JSON Forms library | JSON Forms | `buildFormSchemas()` |
180
+ | Server-side generation | Either | Depends on your renderer |
181
+
182
+ Both formats are generated from the same FormSpec definition, ensuring consistency between build-time and runtime generation.
183
+
184
+ ## When to Use
185
+
186
+ ### Decorators + CLI vs Chain DSL
187
+
188
+ FormSpec offers two approaches for defining forms:
189
+
190
+ | Approach | Best For | Schema Generation |
191
+ | -------------------- | ----------------------------------- | ------------------------------------------------------ |
192
+ | **Decorators + CLI** | Class-based data models, DTOs | Build-time (static analysis) or runtime (with codegen) |
193
+ | **Chain DSL** | Dynamic forms, runtime construction | Build-time or runtime (no code generation needed) |
194
+
195
+ **Use decorators + CLI when:**
196
+
197
+ - Your forms map directly to TypeScript classes or DTOs
198
+ - You prefer class-based modeling with validation constraints
199
+ - You want type information inferred from TypeScript (no redundant type hints)
200
+ - You need to generate schemas at build time for CI/CD pipelines
201
+
202
+ **Use Chain DSL (`@formspec/dsl`) when:**
203
+
204
+ - Forms are constructed dynamically at runtime
205
+ - Form definitions come from a database or API
206
+ - Forms don't correspond to specific TypeScript types
207
+ - You want runtime schema generation without a build step
208
+
209
+ Both approaches produce identical JSON Schema and UI Schema output.
210
+
211
+ ## Features
212
+
213
+ ### Static Type Analysis
214
+
215
+ The CLI uses the TypeScript Compiler API to statically analyze your source files. It automatically infers:
216
+
217
+ | TypeScript Type | JSON Schema | FormSpec Field |
218
+ | ------------------- | ------------------------------------------- | ---------------------------------------- |
219
+ | `string` | `{ "type": "string" }` | `{ "_field": "text" }` |
220
+ | `number` | `{ "type": "number" }` | `{ "_field": "number" }` |
221
+ | `boolean` | `{ "type": "boolean" }` | `{ "_field": "boolean" }` |
222
+ | `"a" \| "b" \| "c"` | `{ "enum": ["a", "b", "c"] }` | `{ "_field": "enum", "options": [...] }` |
223
+ | `string[]` | `{ "type": "array", "items": {...} }` | `{ "_field": "array" }` |
224
+ | `{ a: string }` | `{ "type": "object", "properties": {...} }` | `{ "_field": "object" }` |
225
+ | `field?: T` | not in `required` array | `{ "required": false }` |
226
+
227
+ ### Decorator Recognition
228
+
229
+ The CLI recognizes decorators by name through static analysis. You don't need a specific decorator library - any decorator with a recognized name will work.
230
+
231
+ #### Supported Decorators
232
+
233
+ | Decorator | Purpose | Example |
234
+ | -------------------- | ------------------------- | ---------------------------------------------------- |
235
+ | `@Label(text)` | Set field label | `@Label("Full Name")` |
236
+ | `@Placeholder(text)` | Set placeholder text | `@Placeholder("Enter name...")` |
237
+ | `@Description(text)` | Set field description | `@Description("Your legal name")` |
238
+ | `@Min(n)` | Set minimum value | `@Min(0)` |
239
+ | `@Max(n)` | Set maximum value | `@Max(100)` |
240
+ | `@MinLength(n)` | Set minimum string length | `@MinLength(1)` |
241
+ | `@MaxLength(n)` | Set maximum string length | `@MaxLength(255)` |
242
+ | `@MinItems(n)` | Set minimum array items | `@MinItems(1)` |
243
+ | `@MaxItems(n)` | Set maximum array items | `@MaxItems(10)` |
244
+ | `@Pattern(regex)` | Set validation pattern | `@Pattern("^[a-z]+$")` |
245
+ | `@EnumOptions(opts)` | Override enum options | `@EnumOptions([{id: "us", label: "United States"}])` |
246
+ | `@ShowWhen(cond)` | Conditional visibility | `@ShowWhen({ field: "type", value: "other" })` |
247
+ | `@Group(name)` | Group fields together | `@Group("Contact Info")` |
248
+
249
+ #### Using Decorators
250
+
251
+ Install the `@formspec/decorators` package:
252
+
253
+ ```bash
254
+ npm install @formspec/decorators
255
+ ```
256
+
257
+ Then use the decorators in your class:
258
+
259
+ ```typescript
260
+ // user-registration.ts
261
+ import { Label, Min, Max, EnumOptions } from "@formspec/decorators";
262
+
263
+ class UserRegistration {
264
+ @Label("Full Name")
265
+ name!: string;
266
+
267
+ @Label("Email Address")
268
+ email!: string;
269
+
270
+ @Label("Age")
271
+ @Min(18)
272
+ @Max(120)
273
+ age?: number;
274
+
275
+ @Label("Country")
276
+ @EnumOptions([
277
+ { id: "us", label: "United States" },
278
+ { id: "ca", label: "Canada" },
279
+ { id: "uk", label: "United Kingdom" },
280
+ ])
281
+ country!: "us" | "ca" | "uk";
282
+ }
283
+ ```
284
+
285
+ Run the CLI:
286
+
287
+ ```bash
288
+ formspec generate ./src/user-registration.ts UserRegistration -o ./generated
289
+ ```
290
+
291
+ > **Note**: The decorators are no-ops at runtime with zero overhead. The CLI reads them through static analysis of your TypeScript source code.
292
+
293
+ ### FormSpec Chain DSL Support
294
+
295
+ The CLI also supports the FormSpec chain DSL. Export your FormSpec definitions and the CLI will generate schemas for them:
296
+
297
+ ```typescript
298
+ // forms.ts
299
+ import { formspec, field } from "@formspec/dsl";
300
+
301
+ export const ContactForm = formspec(
302
+ field.text("name", { label: "Name", required: true }),
303
+ field.text("email", { label: "Email", required: true }),
304
+ field.text("message", { label: "Message" })
305
+ );
306
+ ```
307
+
308
+ ```bash
309
+ formspec generate ./src/forms.ts -o ./generated
310
+ ```
311
+
312
+ ### Method Parameter Analysis
313
+
314
+ The CLI can detect `InferSchema<typeof X>` or `InferFormSchema<typeof X>` patterns in method parameters and use the referenced FormSpec to generate parameter schemas:
315
+
316
+ ```typescript
317
+ import { formspec, field, type InferFormSchema } from "@formspec/dsl";
318
+
319
+ export const ActivateParams = formspec(
320
+ field.number("amount", { label: "Amount", min: 100 }),
321
+ field.number("installments", { min: 2, max: 12 })
322
+ );
323
+
324
+ class PaymentPlan {
325
+ status!: "active" | "paused" | "canceled";
326
+
327
+ activate(params: InferFormSchema<typeof ActivateParams>): boolean {
328
+ // ...
329
+ }
330
+ }
331
+ ```
332
+
333
+ The CLI will generate schemas for both the class fields and the method parameters.
334
+
335
+ ## Output Structure
336
+
337
+ ```
338
+ generated/
339
+ ├── ClassName/
340
+ │ ├── schema.json # JSON Schema for class fields
341
+ │ ├── ux_spec.json # JSON Forms UI Schema for form rendering
342
+ │ ├── instance_methods/
343
+ │ │ └── methodName/
344
+ │ │ ├── params.schema.json
345
+ │ │ ├── params.ux_spec.json # (if FormSpec-based params)
346
+ │ │ └── return_type.schema.json
347
+ │ └── static_methods/
348
+ │ └── methodName/
349
+ │ └── ...
350
+ └── formspecs/
351
+ └── ExportName/
352
+ ├── schema.json
353
+ └── ux_spec.json
354
+ ```
355
+
356
+ ## Example Output
357
+
358
+ Given this TypeScript class:
359
+
360
+ ```typescript
361
+ import { Label, Min, Max, EnumOptions } from "@formspec/decorators";
362
+
363
+ class ContactForm {
364
+ @Label("Full Name")
365
+ name!: string;
366
+
367
+ @Label("Email Address")
368
+ email!: string;
369
+
370
+ @Label("Age")
371
+ @Min(18)
372
+ @Max(120)
373
+ age?: number;
374
+
375
+ @Label("Country")
376
+ @EnumOptions([
377
+ { id: "us", label: "United States" },
378
+ { id: "ca", label: "Canada" },
379
+ ])
380
+ country!: "us" | "ca";
381
+ }
382
+ ```
383
+
384
+ Running `formspec generate ./contact-form.ts ContactForm` produces:
385
+
386
+ **schema.json:**
387
+
388
+ ```json
389
+ {
390
+ "type": "object",
391
+ "properties": {
392
+ "name": { "type": "string", "title": "Full Name" },
393
+ "email": { "type": "string", "title": "Email Address" },
394
+ "age": { "type": "number", "title": "Age", "minimum": 18, "maximum": 120 },
395
+ "country": { "enum": ["us", "ca"], "title": "Country" }
396
+ },
397
+ "required": ["name", "email", "country"]
398
+ }
399
+ ```
400
+
401
+ **ux_spec.json:**
402
+
403
+ ```json
404
+ {
405
+ "elements": [
406
+ { "_field": "text", "id": "name", "label": "Full Name", "required": true },
407
+ { "_field": "text", "id": "email", "label": "Email Address", "required": true },
408
+ { "_field": "number", "id": "age", "label": "Age", "min": 18, "max": 120 },
409
+ {
410
+ "_field": "enum",
411
+ "id": "country",
412
+ "label": "Country",
413
+ "required": true,
414
+ "options": [
415
+ { "id": "us", "label": "United States" },
416
+ { "id": "ca", "label": "Canada" }
417
+ ]
418
+ }
419
+ ]
420
+ }
421
+ ```
422
+
423
+ ## CLI Reference
424
+
425
+ ```
426
+ formspec generate <file> [className] [options]
427
+
428
+ Arguments:
429
+ <file> Path to TypeScript source file
430
+ [className] Optional class name (if omitted, generates from FormSpec exports only)
431
+
432
+ Options:
433
+ -o, --output <dir> Output directory (default: ./generated)
434
+ -c, --compiled <path> Path to compiled JS file (auto-detected if omitted)
435
+ -h, --help Show help message
436
+
437
+ Aliases:
438
+ formspec analyze Same as 'generate' (backwards compatibility)
439
+ ```
440
+
441
+ ## TypeScript Configuration
442
+
443
+ For decorator support, ensure your `tsconfig.json` includes:
444
+
445
+ ```json
446
+ {
447
+ "compilerOptions": {
448
+ "experimentalDecorators": true
449
+ }
450
+ }
451
+ ```
452
+
453
+ > **Note**: The `emitDecoratorMetadata` flag is not required. The CLI performs static analysis and reads decorators directly from the AST without using reflection.
454
+
455
+ ## Troubleshooting
456
+
457
+ ### "Could not load compiled module" Warning
458
+
459
+ This warning appears when the CLI cannot find a compiled JavaScript version of your TypeScript file. This is expected if you haven't compiled your TypeScript yet.
460
+
461
+ **The CLI will still work** - it uses static TypeScript analysis which doesn't require compiled output. The warning only affects method parameters that use `InferSchema<typeof X>`, which require the FormSpec to be loaded at runtime.
462
+
463
+ To suppress this warning, compile your TypeScript first:
464
+
465
+ ```bash
466
+ tsc
467
+ formspec generate ./src/forms.ts MyClass -o ./generated
468
+ ```
469
+
470
+ ### Decorators Not Being Recognized
471
+
472
+ Ensure:
473
+
474
+ 1. Decorator names match exactly (case-sensitive): `@Label`, not `@label`
475
+ 2. Decorators are function calls: `@Label("text")`, not `@Label`
476
+ 3. The decorator is imported (even if it's a stub)
477
+
478
+ ## License
479
+
480
+ UNLICENSED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Unit tests for the analyzer module (imported from @formspec/build).
3
+ *
4
+ * These tests verify that the CLI can use the analysis API from @formspec/build.
5
+ * Comprehensive analyzer tests are in @formspec/build.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=analyzer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/analyzer.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Unit tests for the analyzer module (imported from @formspec/build).
3
+ *
4
+ * These tests verify that the CLI can use the analysis API from @formspec/build.
5
+ * Comprehensive analyzer tests are in @formspec/build.
6
+ */
7
+ import { describe, it, expect } from "vitest";
8
+ import * as path from "node:path";
9
+ import { createProgramContext, findClassByName, analyzeClass } from "@formspec/build/internals";
10
+ const fixturesDir = path.join(__dirname, "fixtures");
11
+ const sampleFormsPath = path.join(fixturesDir, "sample-forms.ts");
12
+ describe("program", () => {
13
+ it("creates program context from TypeScript file", () => {
14
+ const ctx = createProgramContext(sampleFormsPath);
15
+ expect(ctx.program).toBeDefined();
16
+ expect(ctx.checker).toBeDefined();
17
+ expect(ctx.sourceFile).toBeDefined();
18
+ expect(ctx.sourceFile.fileName).toContain("sample-forms.ts");
19
+ });
20
+ it("throws for non-existent file", () => {
21
+ expect(() => createProgramContext("/non/existent/file.ts")).toThrow();
22
+ });
23
+ });
24
+ describe("findClassByName", () => {
25
+ it("finds InstallmentPlan class", () => {
26
+ const ctx = createProgramContext(sampleFormsPath);
27
+ const classDecl = findClassByName(ctx.sourceFile, "InstallmentPlan");
28
+ expect(classDecl).not.toBeNull();
29
+ expect(classDecl?.name?.text).toBe("InstallmentPlan");
30
+ });
31
+ it("finds SimpleProduct class", () => {
32
+ const ctx = createProgramContext(sampleFormsPath);
33
+ const classDecl = findClassByName(ctx.sourceFile, "SimpleProduct");
34
+ expect(classDecl).not.toBeNull();
35
+ expect(classDecl?.name?.text).toBe("SimpleProduct");
36
+ });
37
+ it("returns null for non-existent class", () => {
38
+ const ctx = createProgramContext(sampleFormsPath);
39
+ const classDecl = findClassByName(ctx.sourceFile, "NonExistentClass");
40
+ expect(classDecl).toBeNull();
41
+ });
42
+ });
43
+ describe("analyzeClass", () => {
44
+ it("analyzes InstallmentPlan fields", () => {
45
+ const ctx = createProgramContext(sampleFormsPath);
46
+ const classDecl = findClassByName(ctx.sourceFile, "InstallmentPlan");
47
+ if (!classDecl)
48
+ throw new Error("InstallmentPlan class not found");
49
+ const analysis = analyzeClass(classDecl, ctx.checker);
50
+ expect(analysis.name).toBe("InstallmentPlan");
51
+ expect(analysis.fields).toHaveLength(4);
52
+ const fieldNames = analysis.fields.map((f) => f.name);
53
+ expect(fieldNames).toContain("status");
54
+ expect(fieldNames).toContain("amount");
55
+ expect(fieldNames).toContain("customerEmail");
56
+ expect(fieldNames).toContain("installments");
57
+ });
58
+ it("detects optional fields", () => {
59
+ const ctx = createProgramContext(sampleFormsPath);
60
+ const classDecl = findClassByName(ctx.sourceFile, "InstallmentPlan");
61
+ if (!classDecl)
62
+ throw new Error("InstallmentPlan class not found");
63
+ const analysis = analyzeClass(classDecl, ctx.checker);
64
+ const emailField = analysis.fields.find((f) => f.name === "customerEmail");
65
+ const amountField = analysis.fields.find((f) => f.name === "amount");
66
+ expect(emailField?.optional).toBe(true);
67
+ expect(amountField?.optional).toBe(false);
68
+ });
69
+ });
70
+ //# sourceMappingURL=analyzer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.test.js","sourceRoot":"","sources":["../../src/__tests__/analyzer.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tests for the codegen type generation functions (imported from @formspec/build).
3
+ *
4
+ * Comprehensive codegen tests are in @formspec/build.
5
+ * This file verifies the CLI can access the codegen API.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=codegen.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/codegen.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Tests for the codegen type generation functions (imported from @formspec/build).
3
+ *
4
+ * Comprehensive codegen tests are in @formspec/build.
5
+ * This file verifies the CLI can access the codegen API.
6
+ */
7
+ import { describe, it, expect } from "vitest";
8
+ import { generateCodegenOutput } from "@formspec/build";
9
+ function createTypeMetadata(overrides = {}) {
10
+ return {
11
+ type: "string",
12
+ ...overrides,
13
+ };
14
+ }
15
+ function createDecoratedClassInfo(overrides = {}) {
16
+ return {
17
+ name: "TestForm",
18
+ sourcePath: "./test-form",
19
+ typeMetadata: {},
20
+ isExported: true,
21
+ ...overrides,
22
+ };
23
+ }
24
+ describe("generateCodegenOutput (from @formspec/build)", () => {
25
+ it("generates correct schema type for primitive fields", () => {
26
+ const cls = createDecoratedClassInfo({
27
+ name: "UserForm",
28
+ typeMetadata: {
29
+ name: createTypeMetadata({ type: "string" }),
30
+ age: createTypeMetadata({ type: "number" }),
31
+ active: createTypeMetadata({ type: "boolean" }),
32
+ },
33
+ });
34
+ const output = generateCodegenOutput([cls], "/tmp/out.ts", "/tmp");
35
+ expect(output).toContain("export type UserFormSchema = {");
36
+ expect(output).toContain("name: string;");
37
+ expect(output).toContain("age: number;");
38
+ expect(output).toContain("active: boolean;");
39
+ });
40
+ it("generates typed accessor function", () => {
41
+ const cls = createDecoratedClassInfo({
42
+ name: "UserForm",
43
+ typeMetadata: {
44
+ name: createTypeMetadata({ type: "string" }),
45
+ },
46
+ });
47
+ const output = generateCodegenOutput([cls], "/tmp/out.ts", "/tmp");
48
+ expect(output).toContain("export function getUserFormFormSpec(): UserFormFormSpec {");
49
+ });
50
+ });
51
+ //# sourceMappingURL=codegen.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen.test.js","sourceRoot":"","sources":["../../src/__tests__/codegen.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAA8C,MAAM,iBAAiB,CAAC;AAEpG,SAAS,kBAAkB,CAAC,YAAmC,EAAE;IAC/D,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,SAAS,wBAAwB,CAAC,YAAyC,EAAE;IAC3E,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,UAAU,EAAE,aAAa;QACzB,YAAY,EAAE,EAAE;QAChB,UAAU,EAAE,IAAI;QAChB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,8CAA8C,EAAE,GAAG,EAAE;IAC5D,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,GAAG,GAAG,wBAAwB,CAAC;YACnC,IAAI,EAAE,UAAU;YAChB,YAAY,EAAE;gBACZ,IAAI,EAAE,kBAAkB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;gBAC5C,GAAG,EAAE,kBAAkB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;gBAC3C,MAAM,EAAE,kBAAkB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;aAChD;SACF,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,qBAAqB,CAAC,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;QAEnE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,gCAAgC,CAAC,CAAC;QAC3D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACzC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,GAAG,GAAG,wBAAwB,CAAC;YACnC,IAAI,EAAE,UAAU;YAChB,YAAY,EAAE;gBACZ,IAAI,EAAE,kBAAkB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;aAC7C;SACF,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,qBAAqB,CAAC,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;QAEnE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,2DAA2D,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Edge case and negative tests for CLI-specific components.
3
+ *
4
+ * Tests cover:
5
+ * - isFormSpec detection edge cases
6
+ * - File I/O error handling (output writer)
7
+ * - FormSpec loading failures
8
+ *
9
+ * Note: Analysis and generation edge cases are tested in @formspec/build.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=edge-cases.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edge-cases.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/edge-cases.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}