@specverse/engines 4.2.0 → 4.2.2

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 (87) hide show
  1. package/assets/templates/default/specs/main.specly +65 -0
  2. package/dist/libs/instance-factories/CURVED-INTERFACE.md +278 -0
  3. package/dist/libs/instance-factories/README.md +73 -0
  4. package/dist/libs/instance-factories/applications/README.md +51 -0
  5. package/dist/libs/instance-factories/applications/generic-app.yaml +52 -0
  6. package/dist/libs/instance-factories/applications/react-app-runtime.yaml +139 -0
  7. package/dist/libs/instance-factories/applications/react-app-starter.yaml +143 -0
  8. package/dist/libs/instance-factories/applications/templates/react/env-example-generator.js +24 -2
  9. package/dist/libs/instance-factories/applications/templates/react/vite-config-generator.js +54 -33
  10. package/dist/libs/instance-factories/applications/templates/react-starter/README.md +211 -0
  11. package/dist/libs/instance-factories/applications/templates/react-starter/api-types-starter-generator.js +69 -0
  12. package/dist/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.js +1 -1
  13. package/dist/libs/instance-factories/applications/templates/react-starter/belongs-to.js +40 -0
  14. package/dist/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.js +11 -3
  15. package/dist/libs/instance-factories/applications/templates/react-starter/detail-body-composer.js +18 -16
  16. package/dist/libs/instance-factories/applications/templates/react-starter/form-body-composer.js +50 -23
  17. package/dist/libs/instance-factories/applications/templates/react-starter/helpers-emitter.js +9 -3
  18. package/dist/libs/instance-factories/applications/templates/react-starter/list-body-composer.js +17 -7
  19. package/dist/libs/instance-factories/applications/templates/react-starter/orchestrator.js +16 -5
  20. package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +49 -0
  21. package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +96 -0
  22. package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +116 -0
  23. package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +74 -0
  24. package/dist/libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.js +95 -0
  25. package/dist/libs/instance-factories/applications/templates/react-starter/view-emitter.js +26 -1
  26. package/dist/libs/instance-factories/archived/fastify-prisma.yaml +104 -0
  27. package/dist/libs/instance-factories/cli/README.md +43 -0
  28. package/dist/libs/instance-factories/cli/commander-js.yaml +55 -0
  29. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +49 -1
  30. package/dist/libs/instance-factories/communication/README.md +47 -0
  31. package/dist/libs/instance-factories/communication/event-emitter.yaml +60 -0
  32. package/dist/libs/instance-factories/communication/rabbitmq-events.yaml +87 -0
  33. package/dist/libs/instance-factories/controllers/README.md +42 -0
  34. package/dist/libs/instance-factories/controllers/fastify.yaml +139 -0
  35. package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +29 -2
  36. package/dist/libs/instance-factories/infrastructure/README.md +29 -0
  37. package/dist/libs/instance-factories/infrastructure/docker-k8s.yaml +61 -0
  38. package/dist/libs/instance-factories/orms/README.md +54 -0
  39. package/dist/libs/instance-factories/orms/prisma.yaml +89 -0
  40. package/dist/libs/instance-factories/orms/templates/prisma/schema-generator.js +2 -2
  41. package/dist/libs/instance-factories/scaffolding/README.md +49 -0
  42. package/dist/libs/instance-factories/scaffolding/generic-scaffold.yaml +65 -0
  43. package/dist/libs/instance-factories/sdks/README.md +28 -0
  44. package/dist/libs/instance-factories/sdks/python-sdk.yaml +66 -0
  45. package/dist/libs/instance-factories/sdks/typescript-sdk.yaml +59 -0
  46. package/dist/libs/instance-factories/services/README.md +55 -0
  47. package/dist/libs/instance-factories/services/prisma-services.yaml +71 -0
  48. package/dist/libs/instance-factories/storage/README.md +34 -0
  49. package/dist/libs/instance-factories/storage/mongodb.yaml +79 -0
  50. package/dist/libs/instance-factories/storage/postgresql.yaml +75 -0
  51. package/dist/libs/instance-factories/storage/redis.yaml +79 -0
  52. package/dist/libs/instance-factories/testing/README.md +40 -0
  53. package/dist/libs/instance-factories/testing/vitest-tests.yaml +63 -0
  54. package/dist/libs/instance-factories/tools/README.md +70 -0
  55. package/dist/libs/instance-factories/tools/mcp.yaml +36 -0
  56. package/dist/libs/instance-factories/tools/vscode.yaml +35 -0
  57. package/dist/libs/instance-factories/validation/README.md +38 -0
  58. package/dist/libs/instance-factories/validation/zod.yaml +56 -0
  59. package/dist/realize/engines/code-generator.d.ts.map +1 -1
  60. package/dist/realize/engines/code-generator.js +3 -0
  61. package/dist/realize/engines/code-generator.js.map +1 -1
  62. package/libs/instance-factories/applications/react-app-starter.yaml +10 -17
  63. package/libs/instance-factories/applications/templates/react/env-example-generator.ts +24 -2
  64. package/libs/instance-factories/applications/templates/react/vite-config-generator.ts +54 -33
  65. package/libs/instance-factories/applications/templates/react-starter/__tests__/detail-body-composer.test.ts +5 -4
  66. package/libs/instance-factories/applications/templates/react-starter/__tests__/form-body-composer.test.ts +18 -5
  67. package/libs/instance-factories/applications/templates/react-starter/__tests__/orchestrator.test.ts +83 -62
  68. package/libs/instance-factories/applications/templates/react-starter/api-types-starter-generator.ts +98 -0
  69. package/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.ts +1 -1
  70. package/libs/instance-factories/applications/templates/react-starter/belongs-to.ts +82 -0
  71. package/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.ts +20 -5
  72. package/libs/instance-factories/applications/templates/react-starter/detail-body-composer.ts +33 -33
  73. package/libs/instance-factories/applications/templates/react-starter/form-body-composer.ts +107 -30
  74. package/libs/instance-factories/applications/templates/react-starter/helpers-emitter.ts +9 -3
  75. package/libs/instance-factories/applications/templates/react-starter/list-body-composer.ts +34 -8
  76. package/libs/instance-factories/applications/templates/react-starter/orchestrator.ts +41 -26
  77. package/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +2 -0
  78. package/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +2 -0
  79. package/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +2 -0
  80. package/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +2 -0
  81. package/libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.ts +124 -0
  82. package/libs/instance-factories/applications/templates/react-starter/view-emitter.ts +58 -0
  83. package/libs/instance-factories/cli/templates/commander/command-generator.ts +49 -1
  84. package/libs/instance-factories/controllers/fastify.yaml +7 -0
  85. package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +36 -2
  86. package/libs/instance-factories/orms/templates/prisma/schema-generator.ts +11 -4
  87. package/package.json +2 -1
@@ -0,0 +1,35 @@
1
+ name: VSCodeExtension
2
+ version: "4.0.0"
3
+ category: tools
4
+ description: "VSCode extension with language support, commands, themes, and schema validation"
5
+
6
+ metadata:
7
+ author: "SpecVerse Team"
8
+ license: "MIT"
9
+ tags: [vscode, ide, extension, language-support]
10
+
11
+ compatibility:
12
+ specverse: "^4.0.0"
13
+ node: ">=18.0.0"
14
+
15
+ capabilities:
16
+ provides:
17
+ - "tools.vscode"
18
+ - "tools.ide"
19
+ requires: []
20
+
21
+ technology:
22
+ runtime: "node"
23
+ language: "typescript"
24
+ framework: "vscode"
25
+
26
+ dependencies:
27
+ runtime:
28
+ - name: "vscode"
29
+ version: "^1.85.0"
30
+
31
+ codeTemplates:
32
+ vscode-extension:
33
+ engine: typescript
34
+ generator: "libs/instance-factories/tools/templates/vscode/vscode-extension-generator.ts"
35
+ outputPattern: "tools/vscode-extension/"
@@ -0,0 +1,38 @@
1
+ # Validation Instance Factory
2
+
3
+ Generates Zod runtime validation schemas from SpecVerse model definitions, with optional JSON Schema output for Fastify integration.
4
+
5
+ ## Definition
6
+
7
+ - **`zod.yaml`** -- Single definition (category: `service`, no external dependencies required).
8
+
9
+ ## Generator
10
+
11
+ - `templates/zod/validation-generator.ts` -- Wraps `generate-validation.js`. Produces per-model validation files.
12
+
13
+ ## Capabilities
14
+
15
+ | Capability | Description |
16
+ |---|---|
17
+ | `validation.runtime` | Zod schemas for runtime type checking |
18
+ | `validation.zod` | Zod-specific schema generation |
19
+ | `validation.jsonschema` | JSON Schema output (for Fastify route validation) |
20
+
21
+ ## Output Pattern
22
+
23
+ ```
24
+ src/validation/{model}.validation.ts
25
+ ```
26
+
27
+ Each file contains Zod schema definitions with inferred TypeScript types, plus optional JSON Schema equivalents.
28
+
29
+ ## Default Configuration
30
+
31
+ - **Framework**: Zod 3.22+
32
+ - **JSON Schema generation**: Enabled
33
+ - **Strip unknown keys**: Enabled
34
+ - **Abort early**: Disabled (collect all errors)
35
+
36
+ ## Status
37
+
38
+ Wrapper implementation. Future TODO to split into dedicated generators for Zod schemas, JSON Schemas, and validation helper functions.
@@ -0,0 +1,56 @@
1
+ name: ZodValidation
2
+ version: "1.0.0"
3
+ category: service
4
+ description: "Zod schema generator for runtime validation with type inference"
5
+
6
+ metadata:
7
+ author: "SpecVerse Team"
8
+ license: "MIT"
9
+ tags: [zod, validation, typescript, schema]
10
+
11
+ compatibility:
12
+ specverse: ">=3.3.0"
13
+ node: ">=18.0.0"
14
+
15
+ capabilities:
16
+ provides:
17
+ - "validation.runtime"
18
+ - "validation.zod"
19
+ - "validation.jsonschema"
20
+ requires: [] # Fixed: removed incorrect api.rest dependency
21
+
22
+ technology:
23
+ runtime: "node"
24
+ language: "typescript"
25
+ framework: "zod"
26
+ validation: "zod"
27
+
28
+ dependencies:
29
+ runtime:
30
+ - name: "zod"
31
+ version: "^3.22.0"
32
+ dev:
33
+ - name: "@types/node"
34
+ version: "^20.8.0"
35
+ - name: "typescript"
36
+ version: "^5.2.0"
37
+
38
+ codeTemplates:
39
+ validation:
40
+ engine: typescript
41
+ generator: "libs/instance-factories/validation/templates/zod/validation-generator.ts"
42
+ outputPattern: "src/validation/{model}.validation.ts"
43
+
44
+ configuration:
45
+ framework: "zod"
46
+ generateJsonSchema: true
47
+ stripUnknown: true
48
+ abortEarly: false
49
+
50
+ # NOTE: This implementation type wraps the existing generate-validation.js script
51
+ # TODO: Future enhancement - Convert generate-validation.js to native TypeScript template generator
52
+ # The current script generates Zod schemas, JSON schemas, and validation functions.
53
+ # Converting to native templates would require breaking it into multiple template files:
54
+ # - zod-schema-generator.ts (Zod schema definitions)
55
+ # - json-schema-generator.ts (JSON Schema for Fastify)
56
+ # - validation-function-generator.ts (validation helper functions)
@@ -1 +1 @@
1
- {"version":3,"file":"code-generator.d.ts","sourceRoot":"","sources":["../../../src/realize/engines/code-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,sBAAsB,EAAyB,MAAM,sBAAsB,CAAC;AACrF,OAAO,KAAK,EACV,sBAAsB,EACtB,eAAe,EAEhB,MAAM,mBAAmB,CAAC;AAE3B;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IAEb,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAC;IAEjB,6BAA6B;IAC7B,YAAY,EAAE,MAAM,CAAC;IAErB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,oDAAoD;IACpD,cAAc,CAAC,EAAE,sBAAsB,CAAC;IAExC,oDAAoD;IACpD,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,kCAAkC;IAClC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACzC;AAED;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,GAAE,qBAA0B;IAK/C;;OAEG;IACG,oBAAoB,CACxB,QAAQ,EAAE,sBAAsB,EAChC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,EACjC,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,eAAe,CAAC;IA4C3B;;OAEG;IACG,WAAW,CACf,QAAQ,EAAE,sBAAsB,EAChC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,EACjC,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,eAAe,EAAE,CAAC;IAwB7B;;OAEG;IACG,qBAAqB,CACzB,QAAQ,EAAE,sBAAsB,EAAE,EAClC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,EACjC,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;IAW1C;;;;;;;;;OASG;IACH,OAAO,CAAC,iBAAiB;IAwCzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAevB;;OAEG;IACH,iBAAiB,IAAI,sBAAsB;IAI3C;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,YAAY,IAAI,MAAM;CAGvB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,aAAa,CAElF"}
1
+ {"version":3,"file":"code-generator.d.ts","sourceRoot":"","sources":["../../../src/realize/engines/code-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,sBAAsB,EAAyB,MAAM,sBAAsB,CAAC;AACrF,OAAO,KAAK,EACV,sBAAsB,EACtB,eAAe,EAEhB,MAAM,mBAAmB,CAAC;AAE3B;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IAEb,uBAAuB;IACvB,QAAQ,EAAE,MAAM,CAAC;IAEjB,6BAA6B;IAC7B,YAAY,EAAE,MAAM,CAAC;IAErB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,oDAAoD;IACpD,cAAc,CAAC,EAAE,sBAAsB,CAAC;IAExC,oDAAoD;IACpD,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB,kCAAkC;IAClC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACzC;AAED;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,GAAE,qBAA0B;IAK/C;;OAEG;IACG,oBAAoB,CACxB,QAAQ,EAAE,sBAAsB,EAChC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,EACjC,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,eAAe,CAAC;IA+C3B;;OAEG;IACG,WAAW,CACf,QAAQ,EAAE,sBAAsB,EAChC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,EACjC,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,eAAe,EAAE,CAAC;IAwB7B;;OAEG;IACG,qBAAqB,CACzB,QAAQ,EAAE,sBAAsB,EAAE,EAClC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,EACjC,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;IAW1C;;;;;;;;;OASG;IACH,OAAO,CAAC,iBAAiB;IAwCzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAevB;;OAEG;IACH,iBAAiB,IAAI,sBAAsB;IAI3C;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,YAAY,IAAI,MAAM;CAGvB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,aAAa,CAElF"}
@@ -37,6 +37,9 @@ export class CodeGenerator {
37
37
  workspaceRoot: process.cwd(),
38
38
  backendDir: 'backend',
39
39
  frontendDir: 'frontend',
40
+ // Realize-time output root, so multi-file generators (orchestrators
41
+ // that write their own files) know where the project is on disk.
42
+ outputDir: options.outputDir,
40
43
  ...(resolved.configuration || {}),
41
44
  ...(options.additionalContext || {}),
42
45
  ...context
@@ -1 +1 @@
1
- {"version":3,"file":"code-generator.js","sourceRoot":"","sources":["../../../src/realize/engines/code-generator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"code-generator.js","sourceRoot":"","sources":["../../../src/realize/engines/code-generator.ts"],"names":[],"mappings":"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"}
@@ -18,9 +18,9 @@ capabilities:
18
18
  - "app.frontend"
19
19
  - "ui.scaffolding"
20
20
  - "ui.router"
21
- - "ui.static-views"
22
- - "ui.static-forms"
23
- - "ui.static-components"
21
+ - "ui.staticviews"
22
+ - "ui.staticforms"
23
+ - "ui.staticcomponents"
24
24
  requires: []
25
25
 
26
26
  technology:
@@ -99,21 +99,19 @@ codeTemplates:
99
99
  generator: "libs/instance-factories/applications/templates/react/env-example-generator.ts"
100
100
  outputPattern: "{frontendDir}/.env.example"
101
101
 
102
- # API wiring — generated per project (endpoint paths / fetch client
103
- # are instance-factory specific).
104
- api-client:
105
- engine: typescript
106
- generator: "libs/instance-factories/applications/templates/react/api-client-generator.ts"
107
- outputPattern: "{frontendDir}/src/lib/apiClient.ts"
108
-
102
+ # API wiring — starter-specific. The starter's hooks inline their
103
+ # own fetch transport so there is NO apiClient.ts (the runtime
104
+ # apiClient imports runtime-only types that this factory doesn't
105
+ # emit). Users wanting a shared transport layer can add one by
106
+ # hand; the hooks file is intentionally small enough to edit.
109
107
  use-api-hooks:
110
108
  engine: typescript
111
- generator: "libs/instance-factories/applications/templates/react/use-api-hooks-generator.ts"
109
+ generator: "libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.ts"
112
110
  outputPattern: "{frontendDir}/src/hooks/useApi.ts"
113
111
 
114
112
  api-types:
115
113
  engine: typescript
116
- generator: "libs/instance-factories/applications/templates/react/api-types-generator.ts"
114
+ generator: "libs/instance-factories/applications/templates/react-starter/api-types-starter-generator.ts"
117
115
  outputPattern: "{frontendDir}/src/types/api.ts"
118
116
 
119
117
  # === Starter-specific: emitted view components + helpers + App + package.json ===
@@ -143,8 +141,3 @@ configuration:
143
141
  host: "localhost"
144
142
  proxy:
145
143
  "/api": "http://localhost:3000"
146
-
147
- notes:
148
- - "Generated code is a starter — user edits are preserved across regeneration via content-hashing in .specverse-gen/hashes.json."
149
- - "To accept an upstream regeneration for a file you've edited: delete the file first, then re-run `spv realize`."
150
- - "No @specverse/runtime dep — the generated project is fully forkable; delete the .specverse-gen directory and rely only on the emitted source."
@@ -11,8 +11,30 @@ export default function generateEnvExample(context: TemplateContext): string {
11
11
  const pathConfig = getPathConfig(context);
12
12
  const defaultApiUrl = getApiBaseUrl(pathConfig);
13
13
 
14
- return `# API Configuration
15
- # Base URL for backend API
14
+ return `# ─── Ports ────────────────────────────────────────────────────
15
+ # Running more than one SpecVerse project at once? Each project reads
16
+ # its own .env — copy this file to .env and bump the numbers so the
17
+ # projects don't collide.
18
+ #
19
+ # Suggested ranges (shared across the ecosystem):
20
+ # 3000-3049 : generated project backends (one per project)
21
+ # 3050-3099 : app-demo per-spec runtime backends
22
+ # 5173-5199 : vite frontends (one per project)
23
+ # 9000+ : app-demo Server Manager
24
+
25
+ # Backend (Fastify) port — read by \`npm run dev:backend\`.
26
+ PORT=3000
27
+
28
+ # Frontend (vite) port — read by \`npm run dev:frontend\`.
29
+ VITE_PORT=5173
30
+
31
+ # Frontend host — '0.0.0.0' if you want LAN access.
32
+ VITE_HOST=localhost
33
+
34
+ # ─── API wiring ────────────────────────────────────────────────
35
+ # Base URL for backend API. Used by the generated apiClient AND by
36
+ # vite's dev-server proxy (so /api and /ws requests from the browser
37
+ # are forwarded to your backend). Keep this in sync with PORT.
16
38
  VITE_API_BASE_URL=${defaultApiUrl}
17
39
 
18
40
  # API Path Prefix
@@ -16,45 +16,66 @@ export default function generateViteConfig(context: TemplateContext): string {
16
16
  const apiBaseUrl = getApiBaseUrl(pathConfig);
17
17
  const apiProxy = configuration?.vite?.proxy?.['/api'] || apiBaseUrl;
18
18
 
19
- return `import { defineConfig } from 'vite';
19
+ return `import { defineConfig, loadEnv } from 'vite';
20
20
  import react from '@vitejs/plugin-react';
21
21
  import path from 'path';
22
22
 
23
- // https://vitejs.dev/config/
24
- export default defineConfig({
25
- plugins: [react()],
26
- server: {
27
- port: ${vitePort},
28
- host: '${viteHost}',
29
- proxy: {
30
- '/api': {
31
- target: '${apiProxy}',
32
- changeOrigin: true,
33
- },
34
- // WebSocket proxy — the runtime's useEntitySync hook opens
35
- // ws://<host>/ws to receive entity mutation events from the
36
- // backend event bus. Without this proxy the socket tries to
37
- // connect to the vite dev server on /ws and fails, forcing
38
- // state-sync to fall back to React Query's refetchInterval
39
- // polling (usable but slow).
40
- '/ws': {
41
- target: '${apiProxy.replace(/^http/, 'ws')}',
42
- ws: true,
43
- changeOrigin: true,
23
+ /**
24
+ * Ports and proxy targets are env-overridable via this project's
25
+ * .env (and .env.local). Lets you run several SpecVerse projects
26
+ * side-by-side without port collisions:
27
+ *
28
+ * # project-a/.env
29
+ * PORT=3001
30
+ * VITE_PORT=5174
31
+ * VITE_API_BASE_URL=http://localhost:3001
32
+ *
33
+ * # project-b/.env
34
+ * PORT=3002
35
+ * VITE_PORT=5175
36
+ * VITE_API_BASE_URL=http://localhost:3002
37
+ */
38
+ export default defineConfig(({ mode }) => {
39
+ const env = loadEnv(mode, process.cwd(), '');
40
+ const port = Number(env.VITE_PORT ?? ${vitePort});
41
+ const host = env.VITE_HOST ?? '${viteHost}';
42
+ const apiTarget = env.VITE_API_BASE_URL ?? '${apiProxy}';
43
+
44
+ return {
45
+ plugins: [react()],
46
+ server: {
47
+ port,
48
+ host,
49
+ proxy: {
50
+ '/api': {
51
+ target: apiTarget,
52
+ changeOrigin: true,
53
+ },
54
+ // WebSocket proxy — the runtime's useEntitySync hook opens
55
+ // ws://<host>/ws to receive entity mutation events from the
56
+ // backend event bus. Without this proxy the socket tries to
57
+ // connect to the vite dev server on /ws and fails, forcing
58
+ // state-sync to fall back to React Query's refetchInterval
59
+ // polling (usable but slow).
60
+ '/ws': {
61
+ target: apiTarget.replace(/^http/, 'ws'),
62
+ ws: true,
63
+ changeOrigin: true,
64
+ },
44
65
  },
45
66
  },
46
- },
47
- build: {
48
- outDir: 'dist',
49
- sourcemap: true,
50
- },
51
- resolve: {
52
- alias: {
53
- '@': path.resolve(__dirname, './src'),
67
+ build: {
68
+ outDir: 'dist',
69
+ sourcemap: true,
70
+ },
71
+ resolve: {
72
+ alias: {
73
+ '@': path.resolve(__dirname, './src'),
74
+ },
75
+ // Prevent React duplication when using local packages
76
+ dedupe: ['react', 'react-dom'],
54
77
  },
55
- // Prevent React duplication when using local packages
56
- dedupe: ['react', 'react-dom'],
57
- },
78
+ };
58
79
  });
59
80
  `;
60
81
  }
@@ -57,10 +57,11 @@ describe('composeDetailBody — direct output', () => {
57
57
  expect(body).toContain('>Title<');
58
58
  expect(body).toContain('>Body<');
59
59
 
60
- // belongsTo section — surfaces authorId and notes the FK→name gap
60
+ // belongsTo section — surfaces author as a resolved name via
61
+ // resolveEntityDisplayName against the authorOptions array wired
62
+ // in by view-emitter's RELATED_HOOKS substitution.
61
63
  expect(body).toContain('>Author<');
62
- expect(body).toContain('.authorId ??');
63
- expect(body).toContain('TODO: resolve FK ids');
64
+ expect(body).toContain('resolveEntityDisplayName((item as any).authorId, authorOptions)');
64
65
 
65
66
  // Metadata section (id / createdAt / publishedAt) — muted styling
66
67
  expect(body).toContain('>Id<');
@@ -113,7 +114,7 @@ describe('composeDetailBody — direct output', () => {
113
114
  viewName: 'SoloDetailView',
114
115
  modelSchemas: { Solo: solo },
115
116
  }));
116
- expect(body).not.toContain('TODO: resolve FK');
117
+ expect(body).not.toContain('resolveEntityDisplayName');
117
118
  expect(body).toContain('>Name<');
118
119
  });
119
120
  });
@@ -15,6 +15,7 @@ function makeContext(overrides: Partial<EmitContext> = {}): EmitContext {
15
15
  email: { type: 'Email', required: false },
16
16
  status: { type: 'String', required: false, values: ['draft', 'live', 'archived'] },
17
17
  authorId: { type: 'UUID', required: true }, // belongsTo FK
18
+ externalId: { type: 'UUID', required: false }, // non-FK UUID
18
19
  createdAt: { type: 'DateTime', required: false }, // metadata
19
20
  },
20
21
  relationships: {
@@ -121,8 +122,11 @@ describe('composeFormBody — input type per attribute', () => {
121
122
  });
122
123
 
123
124
  it('UUID → text input (treated like string)', () => {
125
+ // externalId is a plain UUID attribute — no belongsTo relationship
126
+ // shadows it, so it renders as a text input. (FK UUIDs like
127
+ // authorId render as a <select> instead; see the belongsTo suite.)
124
128
  const body = composeFormBody(makeContext());
125
- expect(body).toMatch(/id="authorId"[\s\S]*type="text"/);
129
+ expect(body).toMatch(/id="externalId"[\s\S]*type="text"/);
126
130
  });
127
131
  });
128
132
 
@@ -145,11 +149,20 @@ describe('composeFormBody — required markers', () => {
145
149
  });
146
150
 
147
151
  describe('composeFormBody — belongsTo section', () => {
148
- it('surfaces belongsTo FK inputs with a TODO comment', () => {
152
+ it('emits a <select> for each belongsTo, iterating ${relName}Options', () => {
149
153
  const body = composeFormBody(makeContext());
150
- expect(body).toContain('TODO: swap these text inputs for <select>s');
151
- // authorId is emitted as a text input
152
- expect(body).toMatch(/id="authorId"[\s\S]*?type="text"/);
154
+ // Author belongsTo User author is the relationship name, so the
155
+ // options array is authorOptions and the FK column is authorId.
156
+ expect(body).toMatch(/<select[\s\S]*?id="authorId"/);
157
+ expect(body).toContain('authorOptions.map');
158
+ expect(body).toContain('getEntityDisplayName(opt)');
159
+ });
160
+
161
+ it('does not emit plain text inputs for FK columns shadowed by a belongsTo', () => {
162
+ // authorId is shadowed by the author belongsTo relationship — it
163
+ // should only render once (as the <select>), never as a text input.
164
+ const body = composeFormBody(makeContext());
165
+ expect(body).not.toMatch(/<input[\s\S]*?id="authorId"[\s\S]*?type="text"/);
153
166
  });
154
167
  });
155
168
 
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync, existsSync } from 'fs';
3
3
  import { tmpdir } from 'os';
4
4
  import { join } from 'path';
5
5
  import * as ts from 'typescript';
@@ -55,22 +55,36 @@ function assertValidTsx(source: string, label: string): void {
55
55
  }
56
56
 
57
57
  /**
58
- * Apply an orchestrator output to disk simulates what realize does
59
- * when it receives the multi-file generator result.
58
+ * Drive the orchestrator with the per-test tmp dir as the effective
59
+ * frontend root. `frontendDir='.'` collapses the
60
+ * `${outputDir}/${frontendDir}` join so files land directly under
61
+ * `projectRoot`, which keeps test assertions terse.
60
62
  */
61
- function applyOutput(output: Record<string, string>, root: string): void {
62
- for (const [relPath, content] of Object.entries(output)) {
63
- const abs = join(root, relPath);
64
- mkdirSync(join(abs, '..'), { recursive: true });
65
- writeFileSync(abs, content, 'utf8');
63
+ async function runGenerate(spec = makeSpec()) {
64
+ return generate({ spec, outputDir: projectRoot, frontendDir: '.' });
65
+ }
66
+
67
+ /** List every file the orchestrator wrote under projectRoot. */
68
+ function walk(dir: string, prefix = ''): string[] {
69
+ const { readdirSync, statSync } = require('fs');
70
+ const out: string[] = [];
71
+ for (const entry of readdirSync(dir)) {
72
+ const abs = join(dir, entry);
73
+ const rel = prefix ? `${prefix}/${entry}` : entry;
74
+ if (statSync(abs).isDirectory()) out.push(...walk(abs, rel));
75
+ else out.push(rel);
66
76
  }
77
+ return out.sort();
67
78
  }
68
79
 
69
80
  describe('orchestrator — first-run (empty project)', () => {
70
- it('returns the full set of files: views + helpers + App.tsx + package.json + hash manifest', async () => {
71
- const output = await generate({ spec: makeSpec(), projectRoot });
72
- const paths = Object.keys(output).sort();
73
- expect(paths).toEqual([
81
+ it('writes the full set of files: views + helpers + App.tsx + package.json + hash manifest', async () => {
82
+ const ret = await runGenerate();
83
+ // Contract: returns '' (realize's single-file writeOutput skips it).
84
+ expect(ret).toBe('');
85
+
86
+ const written = walk(projectRoot);
87
+ expect(written).toEqual([
74
88
  `${HASHES_DIR}/${HASHES_FILE}`,
75
89
  'package.json',
76
90
  'src/App.tsx',
@@ -82,82 +96,89 @@ describe('orchestrator — first-run (empty project)', () => {
82
96
  ]);
83
97
  });
84
98
 
85
- it('does not write to disk (that is realize\'s job)', async () => {
86
- await generate({ spec: makeSpec(), projectRoot });
87
- // Orchestrator is pure — nothing on disk yet.
88
- expect(existsSync(join(projectRoot, 'package.json'))).toBe(false);
89
- expect(existsSync(join(projectRoot, 'src/App.tsx'))).toBe(false);
90
- });
91
-
92
99
  it('every emitted .tsx parses as valid TSX', async () => {
93
- const output = await generate({ spec: makeSpec(), projectRoot });
94
- for (const [path, source] of Object.entries(output)) {
95
- if (path.endsWith('.tsx')) assertValidTsx(source, path);
100
+ await runGenerate();
101
+ for (const path of walk(projectRoot)) {
102
+ if (path.endsWith('.tsx')) {
103
+ const source = readFileSync(join(projectRoot, path), 'utf8');
104
+ assertValidTsx(source, path);
105
+ }
96
106
  }
97
107
  });
98
108
 
99
109
  it('package.json has no @specverse/runtime dependency', async () => {
100
- const output = await generate({ spec: makeSpec(), projectRoot });
101
- const pkg = JSON.parse(output['package.json']);
110
+ await runGenerate();
111
+ const pkg = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8'));
102
112
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
103
113
  expect(deps['@specverse/runtime']).toBeUndefined();
104
114
  });
105
115
 
106
116
  it('the emitted hash manifest records every approved file', async () => {
107
- const output = await generate({ spec: makeSpec(), projectRoot });
108
- const manifestKey = `${HASHES_DIR}/${HASHES_FILE}`;
109
- const manifest = JSON.parse(output[manifestKey]);
110
- // Every returned file (except the manifest itself) should have a hash.
111
- for (const path of Object.keys(output)) {
112
- if (path === manifestKey) continue;
113
- expect(manifest[path]).toBe(sha256(output[path]));
117
+ await runGenerate();
118
+ const manifestPath = join(projectRoot, HASHES_DIR, HASHES_FILE);
119
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
120
+ for (const path of walk(projectRoot)) {
121
+ if (path === `${HASHES_DIR}/${HASHES_FILE}`) continue;
122
+ const content = readFileSync(join(projectRoot, path), 'utf8');
123
+ expect(manifest[path]).toBe(sha256(content));
114
124
  }
115
125
  });
116
126
  });
117
127
 
118
128
  describe('orchestrator — regeneration safety', () => {
119
129
  it('re-approves pristine files on a second run', async () => {
120
- // First run — write outputs to disk to simulate realize.
121
- const firstOutput = await generate({ spec: makeSpec(), projectRoot });
122
- applyOutput(firstOutput, projectRoot);
123
-
124
- // Second run — everything is pristine, should be re-approved.
125
- const secondOutput = await generate({ spec: makeSpec(), projectRoot });
126
- // The view files etc. should reappear in the output (realize will overwrite).
127
- expect(secondOutput['src/views/PostListView.tsx']).toBeDefined();
130
+ await runGenerate();
131
+
132
+ // Record the first-run hash of one of the view files.
133
+ const listPath = 'src/views/PostListView.tsx';
134
+ const listAbs = join(projectRoot, listPath);
135
+ const firstContent = readFileSync(listAbs, 'utf8');
136
+
137
+ // Second run. Pristine files should be rewritten (the orchestrator
138
+ // writes them, possibly with identical bytes).
139
+ await runGenerate();
140
+ expect(existsSync(listAbs)).toBe(true);
141
+ expect(readFileSync(listAbs, 'utf8')).toBe(firstContent);
128
142
  });
129
143
 
130
- it('omits a user-edited file from the output', async () => {
131
- const firstOutput = await generate({ spec: makeSpec(), projectRoot });
132
- applyOutput(firstOutput, projectRoot);
144
+ it('preserves a user-edited file across regeneration', async () => {
145
+ await runGenerate();
133
146
 
134
- // User edits one of the files
135
147
  const editedPath = 'src/views/PostListView.tsx';
136
- writeFileSync(join(projectRoot, editedPath), '/* edited */', 'utf8');
137
-
138
- const secondOutput = await generate({ spec: makeSpec(), projectRoot });
139
-
140
- // User-edited file not in the output → realize won't overwrite it
141
- expect(secondOutput[editedPath]).toBeUndefined();
142
- // Other files still present
143
- expect(secondOutput['src/views/PostDetailView.tsx']).toBeDefined();
144
- // Manifest preserves the OLD hash for the skipped file
145
- const manifest = JSON.parse(secondOutput[`${HASHES_DIR}/${HASHES_FILE}`]);
146
- const oldManifest = JSON.parse(firstOutput[`${HASHES_DIR}/${HASHES_FILE}`]);
147
- expect(manifest[editedPath]).toBe(oldManifest[editedPath]);
148
+ const editedAbs = join(projectRoot, editedPath);
149
+ const userEdit = '/* user edit */';
150
+ writeFileSync(editedAbs, userEdit, 'utf8');
151
+
152
+ await runGenerate();
153
+
154
+ // File on disk is unchanged — user edit survived.
155
+ expect(readFileSync(editedAbs, 'utf8')).toBe(userEdit);
156
+ // Manifest still records the ORIGINAL (pre-edit) hash so a future
157
+ // un-edit can re-sync cleanly.
158
+ const manifest = JSON.parse(
159
+ readFileSync(join(projectRoot, HASHES_DIR, HASHES_FILE), 'utf8')
160
+ );
161
+ expect(manifest[editedPath]).toBeDefined();
162
+ expect(manifest[editedPath]).not.toBe(sha256(userEdit));
148
163
  });
149
164
 
150
165
  it('is cautious when the user deletes the hash manifest', async () => {
151
- const firstOutput = await generate({ spec: makeSpec(), projectRoot });
152
- applyOutput(firstOutput, projectRoot);
166
+ await runGenerate();
153
167
 
154
- // User deletes the hash manifest
168
+ // User deletes the hash manifest but keeps the view files.
155
169
  rmSync(join(projectRoot, HASHES_DIR), { recursive: true, force: true });
156
170
 
157
- const secondOutput = await generate({ spec: makeSpec(), projectRoot });
171
+ // Record the existing file contents (may be identical to what the
172
+ // orchestrator would emit, but we're treating them as unknown origin).
173
+ const listPath = join(projectRoot, 'src/views/PostListView.tsx');
174
+ const before = readFileSync(listPath, 'utf8');
175
+
176
+ await runGenerate();
158
177
 
159
- // All existing files are now "unknown origin" skipped.
160
- // Output should only contain the fresh hash manifest.
161
- expect(Object.keys(secondOutput)).toEqual([`${HASHES_DIR}/${HASHES_FILE}`]);
178
+ // No hash manifest means we can't tell user edits from originals, so
179
+ // existing files are skipped. Content is unchanged.
180
+ expect(readFileSync(listPath, 'utf8')).toBe(before);
181
+ // A fresh hash manifest was written.
182
+ expect(existsSync(join(projectRoot, HASHES_DIR, HASHES_FILE))).toBe(true);
162
183
  });
163
184
  });