@specverse/engines 4.1.30 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/examples/manifests/frontend-only.yaml +3 -6
- package/assets/examples/manifests/fullstack-app.yaml +5 -7
- package/assets/examples/manifests/fullstack-monorepo.yaml +3 -6
- package/assets/templates/default/specs/main.specly +65 -0
- package/dist/inference/comprehensive-engine.d.ts.map +1 -1
- package/dist/inference/comprehensive-engine.js +3 -19
- package/dist/inference/comprehensive-engine.js.map +1 -1
- package/dist/inference/core/rule-engine.d.ts +31 -0
- package/dist/inference/core/rule-engine.d.ts.map +1 -1
- package/dist/inference/core/rule-engine.js +117 -33
- package/dist/inference/core/rule-engine.js.map +1 -1
- package/dist/inference/core/rule-file-types.d.ts +0 -2
- package/dist/inference/core/rule-file-types.d.ts.map +1 -1
- package/dist/inference/core/rule-file-types.js +3 -6
- package/dist/inference/core/rule-file-types.js.map +1 -1
- package/dist/inference/core/rule-loader.d.ts +5 -15
- package/dist/inference/core/rule-loader.d.ts.map +1 -1
- package/dist/inference/core/rule-loader.js +43 -132
- package/dist/inference/core/rule-loader.js.map +1 -1
- package/dist/inference/core/types.d.ts +0 -6
- package/dist/inference/core/types.d.ts.map +1 -1
- package/dist/inference/core/types.js +0 -4
- package/dist/inference/core/types.js.map +1 -1
- package/dist/inference/logical/generators/component-type-resolver.d.ts +0 -26
- package/dist/inference/logical/generators/component-type-resolver.d.ts.map +1 -1
- package/dist/inference/logical/generators/component-type-resolver.js +0 -19
- package/dist/inference/logical/generators/component-type-resolver.js.map +1 -1
- package/dist/inference/logical/generators/specialist-view-expander.d.ts +1 -17
- package/dist/inference/logical/generators/specialist-view-expander.d.ts.map +1 -1
- package/dist/inference/logical/generators/specialist-view-expander.js +0 -15
- package/dist/inference/logical/generators/specialist-view-expander.js.map +1 -1
- package/dist/inference/logical/generators/view-generator.d.ts +4 -14
- package/dist/inference/logical/generators/view-generator.d.ts.map +1 -1
- package/dist/inference/logical/generators/view-generator.js +6 -26
- package/dist/inference/logical/generators/view-generator.js.map +1 -1
- package/dist/inference/logical/index.d.ts +2 -2
- package/dist/inference/logical/index.d.ts.map +1 -1
- package/dist/inference/logical/logical-engine.d.ts.map +1 -1
- package/dist/inference/logical/logical-engine.js +17 -80
- package/dist/inference/logical/logical-engine.js.map +1 -1
- package/dist/inference/quint-transpiler.d.ts +5 -3
- package/dist/inference/quint-transpiler.d.ts.map +1 -1
- package/dist/inference/quint-transpiler.js +11 -6
- package/dist/inference/quint-transpiler.js.map +1 -1
- package/dist/libs/instance-factories/CURVED-INTERFACE.md +278 -0
- package/dist/libs/instance-factories/README.md +73 -0
- package/dist/libs/instance-factories/applications/README.md +51 -0
- package/dist/libs/instance-factories/applications/generic-app.yaml +52 -0
- package/{libs/instance-factories/applications/react-app.yaml → dist/libs/instance-factories/applications/react-app-runtime.yaml} +30 -77
- package/dist/libs/instance-factories/applications/react-app-starter.yaml +143 -0
- package/dist/libs/instance-factories/applications/templates/react/env-example-generator.js +24 -2
- package/dist/libs/instance-factories/applications/templates/react/vite-config-generator.js +54 -33
- package/dist/libs/instance-factories/applications/templates/react-starter/README.md +211 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/api-types-starter-generator.js +69 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.js +110 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/belongs-to.js +40 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.js +129 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/detail-body-composer.js +80 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/form-body-composer.js +217 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/helpers-emitter.js +51 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/html-to-jsx.js +192 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/list-body-composer.js +56 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/orchestrator.js +41 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/package-json-generator.js +38 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/regen-safety.js +89 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +49 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +96 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +116 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +74 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.js +95 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/view-emitter.js +81 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/views-generator.js +66 -0
- package/dist/libs/instance-factories/archived/fastify-prisma.yaml +104 -0
- package/dist/libs/instance-factories/cli/README.md +43 -0
- package/dist/libs/instance-factories/cli/commander-js.yaml +55 -0
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +63 -12
- package/dist/libs/instance-factories/communication/README.md +47 -0
- package/dist/libs/instance-factories/communication/event-emitter.yaml +60 -0
- package/dist/libs/instance-factories/communication/rabbitmq-events.yaml +87 -0
- package/dist/libs/instance-factories/controllers/README.md +42 -0
- package/dist/libs/instance-factories/controllers/fastify.yaml +139 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +29 -2
- package/dist/libs/instance-factories/infrastructure/README.md +29 -0
- package/dist/libs/instance-factories/infrastructure/docker-k8s.yaml +61 -0
- package/dist/libs/instance-factories/orms/README.md +54 -0
- package/dist/libs/instance-factories/orms/prisma.yaml +89 -0
- package/dist/libs/instance-factories/orms/templates/prisma/schema-generator.js +2 -2
- package/dist/libs/instance-factories/scaffolding/README.md +49 -0
- package/dist/libs/instance-factories/scaffolding/generic-scaffold.yaml +65 -0
- package/dist/libs/instance-factories/sdks/README.md +28 -0
- package/dist/libs/instance-factories/sdks/python-sdk.yaml +66 -0
- package/dist/libs/instance-factories/sdks/typescript-sdk.yaml +59 -0
- package/dist/libs/instance-factories/services/README.md +55 -0
- package/dist/libs/instance-factories/services/prisma-services.yaml +71 -0
- package/dist/libs/instance-factories/storage/README.md +34 -0
- package/dist/libs/instance-factories/storage/mongodb.yaml +79 -0
- package/dist/libs/instance-factories/storage/postgresql.yaml +75 -0
- package/dist/libs/instance-factories/storage/redis.yaml +79 -0
- package/dist/libs/instance-factories/testing/README.md +40 -0
- package/dist/libs/instance-factories/testing/vitest-tests.yaml +63 -0
- package/dist/libs/instance-factories/tools/README.md +70 -0
- package/dist/libs/instance-factories/tools/mcp.yaml +36 -0
- package/dist/libs/instance-factories/tools/vscode.yaml +35 -0
- package/dist/libs/instance-factories/validation/README.md +38 -0
- package/dist/libs/instance-factories/validation/zod.yaml +56 -0
- package/dist/realize/engines/code-generator.d.ts.map +1 -1
- package/dist/realize/engines/code-generator.js +3 -0
- package/dist/realize/engines/code-generator.js.map +1 -1
- package/dist/realize/index.d.ts.map +1 -1
- package/dist/realize/index.js +15 -22
- package/dist/realize/index.js.map +1 -1
- package/dist/registry/utils/manifest-adapter.d.ts +8 -1
- package/dist/registry/utils/manifest-adapter.d.ts.map +1 -1
- package/dist/registry/utils/manifest-adapter.js +8 -1
- package/dist/registry/utils/manifest-adapter.js.map +1 -1
- package/libs/instance-factories/applications/react-app-starter.yaml +143 -0
- package/libs/instance-factories/applications/templates/react/env-example-generator.ts +24 -2
- package/libs/instance-factories/applications/templates/react/vite-config-generator.ts +54 -33
- package/libs/instance-factories/applications/templates/react-starter/README.md +211 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/dashboard-body-composer.test.ts +153 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/detail-body-composer.test.ts +146 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/form-body-composer.test.ts +188 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/helpers-emitter.test.ts +55 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/html-to-jsx.test.ts +140 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/list-body-composer.test.ts +146 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/orchestrator.test.ts +184 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/parity-p2-factory-imports.test.ts +116 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/parity-p3-rendered-output.test.ts +183 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/regen-safety.test.ts +144 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/starter-generators.test.ts +114 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/view-emitter.test.ts +107 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/views-generator.test.ts +139 -0
- package/libs/instance-factories/applications/templates/react-starter/api-types-starter-generator.ts +98 -0
- package/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.ts +141 -0
- package/libs/instance-factories/applications/templates/react-starter/belongs-to.ts +82 -0
- package/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.ts +189 -0
- package/libs/instance-factories/applications/templates/react-starter/detail-body-composer.ts +135 -0
- package/libs/instance-factories/applications/templates/react-starter/form-body-composer.ts +383 -0
- package/libs/instance-factories/applications/templates/react-starter/helpers-emitter.ts +66 -0
- package/libs/instance-factories/applications/templates/react-starter/html-to-jsx.ts +334 -0
- package/libs/instance-factories/applications/templates/react-starter/list-body-composer.ts +146 -0
- package/libs/instance-factories/applications/templates/react-starter/orchestrator.ts +95 -0
- package/libs/instance-factories/applications/templates/react-starter/package-json-generator.ts +57 -0
- package/libs/instance-factories/applications/templates/react-starter/regen-safety.ts +157 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +49 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +96 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +116 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +74 -0
- package/libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.ts +124 -0
- package/libs/instance-factories/applications/templates/react-starter/view-emitter.ts +209 -0
- package/libs/instance-factories/applications/templates/react-starter/views-generator.ts +137 -0
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +63 -12
- package/libs/instance-factories/controllers/fastify.yaml +7 -0
- package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +36 -2
- package/libs/instance-factories/orms/templates/prisma/schema-generator.ts +11 -4
- package/package.json +3 -3
- package/dist/libs/instance-factories/applications/templates/react/_view-components-source.js +0 -530
- package/dist/libs/instance-factories/applications/templates/react/app-tsx-generator.js +0 -73
- package/dist/libs/instance-factories/applications/templates/react/field-helpers-generator.js +0 -99
- package/dist/libs/instance-factories/applications/templates/react/package-json-generator.js +0 -49
- package/dist/libs/instance-factories/applications/templates/react/pattern-adapter-generator.js +0 -156
- package/dist/libs/instance-factories/applications/templates/react/react-pattern-adapter.js +0 -935
- package/dist/libs/instance-factories/applications/templates/react/relationship-field-generator.js +0 -143
- package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.js +0 -646
- package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.js +0 -65
- package/dist/libs/instance-factories/applications/templates/react/view-dashboard-generator.js +0 -143
- package/dist/libs/instance-factories/applications/templates/react/view-detail-generator.js +0 -143
- package/dist/libs/instance-factories/applications/templates/react/view-form-generator.js +0 -355
- package/dist/libs/instance-factories/applications/templates/react/view-list-generator.js +0 -91
- package/dist/libs/instance-factories/applications/templates/react/view-router-generator.js +0 -79
- package/dist/libs/instance-factories/views/index.js +0 -48
- package/dist/libs/instance-factories/views/templates/react/adapters/antd-adapter.js +0 -742
- package/dist/libs/instance-factories/views/templates/react/adapters/mui-adapter.js +0 -824
- package/dist/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.js +0 -719
- package/dist/libs/instance-factories/views/templates/react/app-generator.js +0 -45
- package/dist/libs/instance-factories/views/templates/react/components-generator.js +0 -820
- package/dist/libs/instance-factories/views/templates/react/forms-generator.js +0 -275
- package/dist/libs/instance-factories/views/templates/react/frontend-package-json-generator.js +0 -46
- package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +0 -81
- package/dist/libs/instance-factories/views/templates/react/index-css-generator.js +0 -9
- package/dist/libs/instance-factories/views/templates/react/index-html-generator.js +0 -23
- package/dist/libs/instance-factories/views/templates/react/main-tsx-generator.js +0 -21
- package/dist/libs/instance-factories/views/templates/react/react-component-generator.js +0 -299
- package/dist/libs/instance-factories/views/templates/react/router-generator.js +0 -136
- package/dist/libs/instance-factories/views/templates/react/router-generic-generator.js +0 -107
- package/dist/libs/instance-factories/views/templates/react/shared-utils-generator.js +0 -187
- package/dist/libs/instance-factories/views/templates/react/spec-json-generator.js +0 -7
- package/dist/libs/instance-factories/views/templates/react/types-generator.js +0 -56
- package/dist/libs/instance-factories/views/templates/react/views-metadata-generator.js +0 -27
- package/dist/libs/instance-factories/views/templates/react/vite-config-generator.js +0 -29
- package/dist/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js +0 -261
- package/dist/libs/instance-factories/views/templates/shared/adapter-types.js +0 -34
- package/dist/libs/instance-factories/views/templates/shared/atomic-components-registry.js +0 -800
- package/dist/libs/instance-factories/views/templates/shared/base-generator.js +0 -305
- package/dist/libs/instance-factories/views/templates/shared/component-metadata.js +0 -517
- package/dist/libs/instance-factories/views/templates/shared/composite-pattern-types.js +0 -0
- package/dist/libs/instance-factories/views/templates/shared/composite-patterns.js +0 -445
- package/dist/libs/instance-factories/views/templates/shared/index.js +0 -80
- package/dist/libs/instance-factories/views/templates/shared/pattern-validator.js +0 -210
- package/dist/libs/instance-factories/views/templates/shared/property-mapper.js +0 -492
- package/dist/libs/instance-factories/views/templates/shared/syntax-mapper.js +0 -321
- package/dist/realize/index.js.bak +0 -758
- package/libs/instance-factories/applications/templates/react/_view-components-source.ts +0 -555
- package/libs/instance-factories/applications/templates/react/app-tsx-generator.ts +0 -94
- package/libs/instance-factories/applications/templates/react/field-helpers-generator.ts +0 -106
- package/libs/instance-factories/applications/templates/react/package-json-generator.ts +0 -57
- package/libs/instance-factories/applications/templates/react/pattern-adapter-generator.ts +0 -179
- package/libs/instance-factories/applications/templates/react/react-pattern-adapter.tsx +0 -1347
- package/libs/instance-factories/applications/templates/react/relationship-field-generator.ts +0 -150
- package/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.ts +0 -704
- package/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.ts +0 -84
- package/libs/instance-factories/applications/templates/react/view-dashboard-generator.ts +0 -150
- package/libs/instance-factories/applications/templates/react/view-detail-generator.ts +0 -150
- package/libs/instance-factories/applications/templates/react/view-form-generator.ts +0 -362
- package/libs/instance-factories/applications/templates/react/view-list-generator.ts +0 -98
- package/libs/instance-factories/applications/templates/react/view-router-generator.ts +0 -89
- package/libs/instance-factories/views/README.md +0 -62
- package/libs/instance-factories/views/index.d.ts +0 -13
- package/libs/instance-factories/views/index.d.ts.map +0 -1
- package/libs/instance-factories/views/index.js +0 -18
- package/libs/instance-factories/views/index.js.map +0 -1
- package/libs/instance-factories/views/index.ts +0 -45
- package/libs/instance-factories/views/react-components.yaml +0 -129
- package/libs/instance-factories/views/templates/ARCHITECTURE.md +0 -198
- package/libs/instance-factories/views/templates/react/adapters/antd-adapter.ts +0 -869
- package/libs/instance-factories/views/templates/react/adapters/mui-adapter.ts +0 -953
- package/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.ts +0 -806
- package/libs/instance-factories/views/templates/react/app-generator.ts +0 -55
- package/libs/instance-factories/views/templates/react/components-generator.ts +0 -938
- package/libs/instance-factories/views/templates/react/forms-generator.ts +0 -325
- package/libs/instance-factories/views/templates/react/frontend-package-json-generator.ts +0 -57
- package/libs/instance-factories/views/templates/react/hooks-generator.ts +0 -106
- package/libs/instance-factories/views/templates/react/index-css-generator.ts +0 -14
- package/libs/instance-factories/views/templates/react/index-html-generator.ts +0 -34
- package/libs/instance-factories/views/templates/react/main-tsx-generator.ts +0 -29
- package/libs/instance-factories/views/templates/react/react-component-generator.d.ts +0 -152
- package/libs/instance-factories/views/templates/react/react-component-generator.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/react/react-component-generator.js +0 -398
- package/libs/instance-factories/views/templates/react/react-component-generator.js.map +0 -1
- package/libs/instance-factories/views/templates/react/react-component-generator.ts +0 -533
- package/libs/instance-factories/views/templates/react/router-generator.ts +0 -197
- package/libs/instance-factories/views/templates/react/router-generic-generator.ts +0 -132
- package/libs/instance-factories/views/templates/react/shared-utils-generator.ts +0 -196
- package/libs/instance-factories/views/templates/react/spec-json-generator.ts +0 -17
- package/libs/instance-factories/views/templates/react/types-generator.ts +0 -76
- package/libs/instance-factories/views/templates/react/views-metadata-generator.ts +0 -42
- package/libs/instance-factories/views/templates/react/vite-config-generator.ts +0 -38
- package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js.map +0 -1
- package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.ts +0 -474
- package/libs/instance-factories/views/templates/shared/__tests__/composite-patterns.test.ts +0 -242
- package/libs/instance-factories/views/templates/shared/adapter-types.d.ts +0 -77
- package/libs/instance-factories/views/templates/shared/adapter-types.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/adapter-types.js +0 -47
- package/libs/instance-factories/views/templates/shared/adapter-types.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/adapter-types.ts +0 -142
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts +0 -63
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.js +0 -822
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.ts +0 -908
- package/libs/instance-factories/views/templates/shared/base-generator.d.ts +0 -247
- package/libs/instance-factories/views/templates/shared/base-generator.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/base-generator.js +0 -363
- package/libs/instance-factories/views/templates/shared/base-generator.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/base-generator.ts +0 -608
- package/libs/instance-factories/views/templates/shared/component-metadata.d.ts +0 -254
- package/libs/instance-factories/views/templates/shared/component-metadata.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/component-metadata.js +0 -602
- package/libs/instance-factories/views/templates/shared/component-metadata.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/component-metadata.ts +0 -803
- package/libs/instance-factories/views/templates/shared/composite-pattern-types.ts +0 -250
- package/libs/instance-factories/views/templates/shared/composite-patterns.ts +0 -535
- package/libs/instance-factories/views/templates/shared/index.ts +0 -68
- package/libs/instance-factories/views/templates/shared/pattern-validator.ts +0 -279
- package/libs/instance-factories/views/templates/shared/property-mapper.d.ts +0 -149
- package/libs/instance-factories/views/templates/shared/property-mapper.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/property-mapper.js +0 -580
- package/libs/instance-factories/views/templates/shared/property-mapper.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/property-mapper.ts +0 -700
- package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts +0 -143
- package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/syntax-mapper.js +0 -420
- package/libs/instance-factories/views/templates/shared/syntax-mapper.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/syntax-mapper.ts +0 -539
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReactAppStarter view emitter
|
|
3
|
+
*
|
|
4
|
+
* Given a view spec + model spec + expanded spec, emits a complete
|
|
5
|
+
* idiomatic React component source file as a string.
|
|
6
|
+
*
|
|
7
|
+
* Strategy 3 ("skeleton + rendered interior"): a hand-written
|
|
8
|
+
* skeleton template provides the idiomatic React outer structure
|
|
9
|
+
* (imports, hooks, props, layout). The canonical Tailwind adapter
|
|
10
|
+
* renders the pattern's interior as HTML. The html-to-jsx transformer
|
|
11
|
+
* converts the HTML to JSX. The emitter composes these.
|
|
12
|
+
*
|
|
13
|
+
* See README.md in this directory for the architecture.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync } from 'fs';
|
|
17
|
+
import { join, dirname } from 'path';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
import { extractBelongsToTargets, pluralize as pluralizeShared } from './belongs-to.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The minimal shape of a view spec this emitter needs. Matches the
|
|
23
|
+
* inferred spec format — `view.type` drives skeleton selection,
|
|
24
|
+
* `view.model` identifies the primary model, `view.uiComponents` (if
|
|
25
|
+
* present) overrides inference defaults.
|
|
26
|
+
*/
|
|
27
|
+
export interface ViewSpec {
|
|
28
|
+
type: string;
|
|
29
|
+
model?: string;
|
|
30
|
+
uiComponents?: Record<string, unknown>;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The minimal shape of a model spec this emitter needs. Field details
|
|
36
|
+
* flow through to the Tailwind adapter which decides column selection.
|
|
37
|
+
*/
|
|
38
|
+
export interface ModelSpec {
|
|
39
|
+
name: string;
|
|
40
|
+
attributes: Record<string, unknown>;
|
|
41
|
+
relationships?: Record<string, unknown>;
|
|
42
|
+
lifecycles?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface EmitContext {
|
|
46
|
+
view: ViewSpec;
|
|
47
|
+
viewName: string; // e.g. "PostListView"
|
|
48
|
+
model: ModelSpec;
|
|
49
|
+
modelSchemas: Record<string, ModelSpec>;
|
|
50
|
+
/** Pluggable so we can stub the renderer in unit tests. */
|
|
51
|
+
renderBody: RenderBodyFn;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Render the body of a view as JSX-safe source — the exact string
|
|
56
|
+
* that is dropped at `{{BODY}}` in the skeleton.
|
|
57
|
+
*
|
|
58
|
+
* The renderBody implementation is responsible for the whole body
|
|
59
|
+
* pipeline: invoke the canonical Tailwind adapter for static shell
|
|
60
|
+
* rendering, run that HTML through `htmlToJsx`, and inject any JSX
|
|
61
|
+
* expressions (`.map()`, event handlers). Keeping it here means
|
|
62
|
+
* view-emitter stays a pure orchestrator and can't accidentally
|
|
63
|
+
* double-transform JSX that's already JSX.
|
|
64
|
+
*/
|
|
65
|
+
export type RenderBodyFn = (context: EmitContext) => string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Emit a complete .tsx file for a single view. Pure function — all
|
|
69
|
+
* inputs come in via the context argument so it's easy to test.
|
|
70
|
+
*/
|
|
71
|
+
export function emitView(context: EmitContext): string {
|
|
72
|
+
const skeleton = loadSkeleton(context.view.type);
|
|
73
|
+
const bodyJsx = context.renderBody(context);
|
|
74
|
+
|
|
75
|
+
const substitutions = buildSubstitutions(context, bodyJsx);
|
|
76
|
+
return applySubstitutions(skeleton, substitutions);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
80
|
+
// Skeleton loading
|
|
81
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
const SKELETON_BY_VIEW_TYPE: Record<string, string> = {
|
|
84
|
+
list: 'list.tsx.template',
|
|
85
|
+
detail: 'detail.tsx.template',
|
|
86
|
+
form: 'form.tsx.template',
|
|
87
|
+
dashboard: 'dashboard.tsx.template',
|
|
88
|
+
// Specialist types (board, timeline, calendar, analytics, workflow,
|
|
89
|
+
// wizard, comparison, settings, map, feed, profile) come online in
|
|
90
|
+
// Phase 2e.
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function loadSkeleton(viewType: string): string {
|
|
94
|
+
const filename = SKELETON_BY_VIEW_TYPE[viewType.toLowerCase()];
|
|
95
|
+
if (!filename) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`No skeleton registered for view type "${viewType}". ` +
|
|
98
|
+
`Known types: ${Object.keys(SKELETON_BY_VIEW_TYPE).join(', ')}.`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
103
|
+
const path = join(here, 'skeletons', filename);
|
|
104
|
+
return readFileSync(path, 'utf8');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
108
|
+
// Substitution
|
|
109
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
interface Substitutions {
|
|
112
|
+
MODEL_NAME: string;
|
|
113
|
+
PLURAL_MODEL: string;
|
|
114
|
+
PLURAL_LOWER: string;
|
|
115
|
+
SINGULAR_LOWER: string;
|
|
116
|
+
BODY: string;
|
|
117
|
+
/**
|
|
118
|
+
* For form views: extra import lines that pull in the hooks and
|
|
119
|
+
* display-name helper needed to render belongsTo <select>s. Empty
|
|
120
|
+
* string for views / models with no belongsTo relationships.
|
|
121
|
+
*/
|
|
122
|
+
RELATED_IMPORTS: string;
|
|
123
|
+
/**
|
|
124
|
+
* For form views: extra hook calls inside the component body —
|
|
125
|
+
* one per belongsTo target — that populate the `${relName}Options`
|
|
126
|
+
* array each <select> iterates. Empty string when there are none.
|
|
127
|
+
*/
|
|
128
|
+
RELATED_HOOKS: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function buildSubstitutions(context: EmitContext, body: string): Substitutions {
|
|
132
|
+
const modelName = context.model.name;
|
|
133
|
+
const pluralModel = pluralize(modelName);
|
|
134
|
+
const { imports, hooks } = buildBelongsToWiring(context);
|
|
135
|
+
return {
|
|
136
|
+
MODEL_NAME: modelName,
|
|
137
|
+
PLURAL_MODEL: pluralModel,
|
|
138
|
+
PLURAL_LOWER: pluralModel.toLowerCase(),
|
|
139
|
+
SINGULAR_LOWER: modelName.toLowerCase(),
|
|
140
|
+
BODY: body,
|
|
141
|
+
RELATED_IMPORTS: imports,
|
|
142
|
+
RELATED_HOOKS: hooks,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Compute the belongsTo wiring for a view: one import line per unique
|
|
148
|
+
* target hook, one hook call per relationship (even if two relations
|
|
149
|
+
* share a target — each needs its own options variable).
|
|
150
|
+
*
|
|
151
|
+
* The entity-display helper import set is view-type-dependent to
|
|
152
|
+
* avoid unused-import TS errors:
|
|
153
|
+
* - form uses `getEntityDisplayName(opt)` (has whole entity in hand)
|
|
154
|
+
* - list / detail / dashboard use `resolveEntityDisplayName(id, list)`
|
|
155
|
+
* (has FK id, looks up in the list)
|
|
156
|
+
*/
|
|
157
|
+
function buildBelongsToWiring(context: EmitContext): { imports: string; hooks: string } {
|
|
158
|
+
const rels = extractBelongsToTargets(context.model);
|
|
159
|
+
if (rels.length === 0) return { imports: '', hooks: '' };
|
|
160
|
+
|
|
161
|
+
// Dedupe hook imports by target — two belongsTo to the same model
|
|
162
|
+
// share the same query hook.
|
|
163
|
+
const hookImportNames = new Set<string>();
|
|
164
|
+
for (const rel of rels) {
|
|
165
|
+
hookImportNames.add(`use${pluralizeShared(rel.target)}Query`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const helperImport = context.view.type.toLowerCase() === 'form'
|
|
169
|
+
? 'getEntityDisplayName'
|
|
170
|
+
: 'resolveEntityDisplayName';
|
|
171
|
+
|
|
172
|
+
const importLines: string[] = [];
|
|
173
|
+
importLines.push(
|
|
174
|
+
`import { ${[...hookImportNames].join(', ')} } from '../hooks/useApi';`
|
|
175
|
+
);
|
|
176
|
+
importLines.push(`import { ${helperImport} } from '../lib/entity-display';`);
|
|
177
|
+
|
|
178
|
+
const hookLines = rels.map(rel =>
|
|
179
|
+
` const { data: ${rel.name}Options = [] } = use${pluralizeShared(rel.target)}Query();`
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
imports: importLines.join('\n'),
|
|
184
|
+
hooks: hookLines.join('\n'),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function applySubstitutions(template: string, subs: Substitutions): string {
|
|
189
|
+
let out = template;
|
|
190
|
+
for (const [key, value] of Object.entries(subs)) {
|
|
191
|
+
// Replace every occurrence of {{KEY}} — plain string substitution,
|
|
192
|
+
// no expression evaluation. Escape regex special chars in the key
|
|
193
|
+
// (defensive; keys are ASCII constants).
|
|
194
|
+
const pattern = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
|
|
195
|
+
out = out.replace(pattern, value);
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Minimal English pluralizer matching the adapter's conventions.
|
|
202
|
+
* Good enough for generated code — users can edit the output if they
|
|
203
|
+
* want a different pluralization.
|
|
204
|
+
*/
|
|
205
|
+
function pluralize(s: string): string {
|
|
206
|
+
if (/[^aeiou]y$/i.test(s)) return s.slice(0, -1) + 'ies';
|
|
207
|
+
if (/(s|x|z|ch|sh)$/i.test(s)) return s + 'es';
|
|
208
|
+
return s + 's';
|
|
209
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View files generator for ReactAppStarter
|
|
3
|
+
*
|
|
4
|
+
* Called by the realize engine. Iterates over the expanded spec's
|
|
5
|
+
* models × view types, dispatches to the right composer, and returns
|
|
6
|
+
* a filename → source map. Realize writes each file.
|
|
7
|
+
*
|
|
8
|
+
* Also emits the local helpers library (src/lib/entity-display.ts)
|
|
9
|
+
* so generated projects have zero @specverse/runtime runtime deps.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { emitView, type EmitContext, type ModelSpec, type ViewSpec, type RenderBodyFn } from './view-emitter.js';
|
|
13
|
+
import { composeListBody } from './list-body-composer.js';
|
|
14
|
+
import { composeDetailBody } from './detail-body-composer.js';
|
|
15
|
+
import { composeFormBody } from './form-body-composer.js';
|
|
16
|
+
import { composeDashboardBody } from './dashboard-body-composer.js';
|
|
17
|
+
import { emitEntityDisplay } from './helpers-emitter.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The four primary view types Factory B currently emits. Specialist
|
|
21
|
+
* types (board, timeline, calendar, etc.) are not yet implemented —
|
|
22
|
+
* if the expanded spec declares one, we skip it and log a TODO.
|
|
23
|
+
* These users can add composers under Phase 2e.
|
|
24
|
+
*/
|
|
25
|
+
const PRIMARY_VIEW_TYPES = ['list', 'detail', 'form', 'dashboard'] as const;
|
|
26
|
+
type PrimaryViewType = typeof PRIMARY_VIEW_TYPES[number];
|
|
27
|
+
|
|
28
|
+
const COMPOSER_BY_TYPE: Record<PrimaryViewType, RenderBodyFn> = {
|
|
29
|
+
list: composeListBody,
|
|
30
|
+
detail: composeDetailBody,
|
|
31
|
+
form: composeFormBody,
|
|
32
|
+
dashboard: composeDashboardBody,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Generator contract — the shape realize expects. */
|
|
36
|
+
export interface ViewsGeneratorContext {
|
|
37
|
+
/** The expanded spec (post-inference). */
|
|
38
|
+
spec: ExpandedSpec;
|
|
39
|
+
/** The resolved manifest configuration. Not currently read by this
|
|
40
|
+
* generator but passed by realize for parity with other generators. */
|
|
41
|
+
manifest?: unknown;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ExpandedSpec {
|
|
45
|
+
/** Models keyed by name. */
|
|
46
|
+
models?: Record<string, ModelSpec>;
|
|
47
|
+
/** Views keyed by view name (e.g. "PostListView"). */
|
|
48
|
+
views?: Record<string, ViewSpec & { type: string; model?: string }>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface GeneratedFiles {
|
|
52
|
+
[relativePath: string]: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate all view files + local helpers.
|
|
57
|
+
*
|
|
58
|
+
* Paths are RELATIVE to the frontend root (`frontendDir` in the
|
|
59
|
+
* manifest). Realize prepends the absolute output path when writing.
|
|
60
|
+
*/
|
|
61
|
+
export async function generate(context: ViewsGeneratorContext): Promise<GeneratedFiles> {
|
|
62
|
+
const files: GeneratedFiles = {};
|
|
63
|
+
const spec = context.spec;
|
|
64
|
+
const models = spec.models ?? {};
|
|
65
|
+
const viewsBySpec = spec.views ?? {};
|
|
66
|
+
|
|
67
|
+
// Group the spec's views by model+type so we can decide, for each
|
|
68
|
+
// (model, type) pair, whether to use the user's view spec or emit
|
|
69
|
+
// a default.
|
|
70
|
+
const viewIndex = indexViews(viewsBySpec);
|
|
71
|
+
const skippedSpecialists: string[] = [];
|
|
72
|
+
|
|
73
|
+
for (const [modelName, model] of Object.entries(models)) {
|
|
74
|
+
for (const viewType of PRIMARY_VIEW_TYPES) {
|
|
75
|
+
const viewName = `${modelName}${capitalize(viewType)}View`;
|
|
76
|
+
// Prefer a user-defined view of that shape if one exists; fall
|
|
77
|
+
// back to a synthesized default.
|
|
78
|
+
const view = viewIndex.get(viewName) ?? synthesizeDefault(modelName, viewType);
|
|
79
|
+
const source = emitView({
|
|
80
|
+
view,
|
|
81
|
+
viewName,
|
|
82
|
+
model,
|
|
83
|
+
modelSchemas: models,
|
|
84
|
+
renderBody: COMPOSER_BY_TYPE[viewType],
|
|
85
|
+
});
|
|
86
|
+
files[`src/views/${viewName}.tsx`] = source;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Surface any specialist-type views the user declared but we can't
|
|
91
|
+
// emit yet. The runtime (ReactAppRuntime) handles these; in starter
|
|
92
|
+
// kit mode the user has to write them manually for now.
|
|
93
|
+
for (const [name, view] of Object.entries(viewsBySpec)) {
|
|
94
|
+
if (!PRIMARY_VIEW_TYPES.includes(view.type as PrimaryViewType)) {
|
|
95
|
+
skippedSpecialists.push(`${name} (type=${view.type})`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (skippedSpecialists.length > 0) {
|
|
99
|
+
console.warn(
|
|
100
|
+
`[ReactAppStarter] Skipped ${skippedSpecialists.length} specialist view(s) — ` +
|
|
101
|
+
`implement composers for: ${[...new Set(Object.values(viewsBySpec)
|
|
102
|
+
.filter(v => !PRIMARY_VIEW_TYPES.includes(v.type as PrimaryViewType))
|
|
103
|
+
.map(v => v.type))].join(', ')}. ` +
|
|
104
|
+
`Skipped: ${skippedSpecialists.join(', ')}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Local helpers
|
|
109
|
+
files['src/lib/entity-display.ts'] = emitEntityDisplay();
|
|
110
|
+
|
|
111
|
+
return files;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
115
|
+
// Helpers
|
|
116
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
function indexViews(
|
|
119
|
+
views: Record<string, ViewSpec & { type: string; model?: string }>
|
|
120
|
+
): Map<string, ViewSpec & { type: string; model?: string }> {
|
|
121
|
+
const out = new Map<string, ViewSpec & { type: string; model?: string }>();
|
|
122
|
+
for (const [name, view] of Object.entries(views)) {
|
|
123
|
+
out.set(name, view);
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function synthesizeDefault(modelName: string, viewType: PrimaryViewType): ViewSpec {
|
|
129
|
+
return {
|
|
130
|
+
type: viewType,
|
|
131
|
+
model: modelName,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function capitalize(s: string): string {
|
|
136
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
137
|
+
}
|
|
@@ -393,22 +393,25 @@ import type { ParserEngine, InferenceEngine, RealizeEngine } from '@specverse/ty
|
|
|
393
393
|
process.exit(1);
|
|
394
394
|
}
|
|
395
395
|
|
|
396
|
-
//
|
|
396
|
+
// --static ejects to a standalone starter kit (ReactAppStarter).
|
|
397
|
+
// Default: whatever the manifest declares (ReactAppRuntime in the
|
|
398
|
+
// shipped templates). The swap is a convenience only — users can
|
|
399
|
+
// achieve the same by editing their manifest directly.
|
|
397
400
|
let effectiveManifestPath = manifestPath;
|
|
398
|
-
if (
|
|
401
|
+
if (options.static) {
|
|
399
402
|
const manifestContent = readFileSync(manifestPath, 'utf8');
|
|
400
|
-
if (/instanceFactory:\\s*["']?
|
|
403
|
+
if (/instanceFactory:\\s*["']?ReactAppRuntime["']?\\s*$/m.test(manifestContent)) {
|
|
401
404
|
const { tmpdir } = await import('os');
|
|
402
|
-
const
|
|
403
|
-
/instanceFactory:\\s*["']?
|
|
404
|
-
'instanceFactory: "
|
|
405
|
+
const staticManifest = manifestContent.replace(
|
|
406
|
+
/instanceFactory:\\s*["']?ReactAppRuntime["']?\\s*$/gm,
|
|
407
|
+
'instanceFactory: "ReactAppStarter"'
|
|
405
408
|
);
|
|
406
|
-
effectiveManifestPath = join(tmpdir(), 'specverse-
|
|
407
|
-
writeFileSync(effectiveManifestPath,
|
|
408
|
-
console.log(' Using
|
|
409
|
+
effectiveManifestPath = join(tmpdir(), 'specverse-static-manifest.yaml');
|
|
410
|
+
writeFileSync(effectiveManifestPath, staticManifest, 'utf8');
|
|
411
|
+
console.log(' Using static mode (standalone starter kit — ReactAppStarter)');
|
|
412
|
+
} else {
|
|
413
|
+
console.log(' Using static mode (manifest factory unchanged)');
|
|
409
414
|
}
|
|
410
|
-
} else {
|
|
411
|
-
console.log(' Using static mode (full frontend generation)');
|
|
412
415
|
}
|
|
413
416
|
|
|
414
417
|
const realizeEngine = registry.getEngineForCapability('realize') as RealizeEngine;
|
|
@@ -557,8 +560,56 @@ import { fileURLToPath } from 'url';`,
|
|
|
557
560
|
}
|
|
558
561
|
|
|
559
562
|
copyDir(templateDir, destDir);
|
|
563
|
+
|
|
564
|
+
// If the copied template didn't ship a specs/main.specly, load
|
|
565
|
+
// the canonical default spec from @specverse/engines/assets —
|
|
566
|
+
// same file app-demo's Server Manager "new spec" action uses,
|
|
567
|
+
// so both entry points start a user on the same footing. The
|
|
568
|
+
// template can opt out of this by shipping its own spec.
|
|
569
|
+
const specDestPath = join(destDir, 'specs', 'main.specly');
|
|
570
|
+
if (!existsSync(specDestPath)) {
|
|
571
|
+
try {
|
|
572
|
+
const { createRequire } = await import('module');
|
|
573
|
+
const require = createRequire(import.meta.url);
|
|
574
|
+
const enginesPkg = require.resolve('@specverse/engines/package.json');
|
|
575
|
+
const canonicalSpec = join(
|
|
576
|
+
dirname(enginesPkg),
|
|
577
|
+
'assets', 'templates', 'default', 'specs', 'main.specly'
|
|
578
|
+
);
|
|
579
|
+
if (existsSync(canonicalSpec)) {
|
|
580
|
+
let content = readFileSync(canonicalSpec, 'utf8');
|
|
581
|
+
for (const [key, val] of Object.entries(vars)) {
|
|
582
|
+
content = content.split(key).join(val);
|
|
583
|
+
}
|
|
584
|
+
mkdirSync(join(destDir, 'specs'), { recursive: true });
|
|
585
|
+
writeFileSync(specDestPath, content);
|
|
586
|
+
}
|
|
587
|
+
} catch { /* engines not installed or template missing — proceed */ }
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// --static: flip any ReactAppRuntime mappings in the copied
|
|
591
|
+
// manifests to ReactAppStarter so the user gets standalone-starter
|
|
592
|
+
// output on first \`spv realize\` without having to re-pass the
|
|
593
|
+
// flag. Same semantic as \`spv realize --static\` — just applied
|
|
594
|
+
// at init time. Idempotent; no-op for templates without a frontend.
|
|
595
|
+
if (options.static) {
|
|
596
|
+
const manifestsDir = join(destDir, 'manifests');
|
|
597
|
+
if (existsSync(manifestsDir)) {
|
|
598
|
+
for (const f of readdirSync(manifestsDir)) {
|
|
599
|
+
if (!f.endsWith('.yaml') && !f.endsWith('.yml')) continue;
|
|
600
|
+
const p = join(manifestsDir, f);
|
|
601
|
+
const before = readFileSync(p, 'utf8');
|
|
602
|
+
const after = before.replace(
|
|
603
|
+
/instanceFactory:\\s*["']?ReactAppRuntime["']?/g,
|
|
604
|
+
'instanceFactory: "ReactAppStarter"'
|
|
605
|
+
);
|
|
606
|
+
if (after !== before) writeFileSync(p, after, 'utf8');
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
560
611
|
console.log('Project created: ' + destDir);
|
|
561
|
-
console.log('Template: ' + templateName);
|
|
612
|
+
console.log('Template: ' + templateName + (options.static ? ' (static / ReactAppStarter)' : ''));
|
|
562
613
|
console.log('');
|
|
563
614
|
|
|
564
615
|
// Build the "Next steps" hint from the actual scripts the
|
|
@@ -37,6 +37,12 @@ dependencies:
|
|
|
37
37
|
version: "^11.1.0"
|
|
38
38
|
- name: "@fastify/rate-limit"
|
|
39
39
|
version: "^9.0.0"
|
|
40
|
+
# .env loading — main.ts calls `import 'dotenv/config'` first so
|
|
41
|
+
# PORT and other env vars populate before any module reads them.
|
|
42
|
+
# Mirrors vite's loadEnv on the frontend side, so `npm run
|
|
43
|
+
# dev:backend` honours the project's .env without a set -a dance.
|
|
44
|
+
- name: "dotenv"
|
|
45
|
+
version: "^16.4.0"
|
|
40
46
|
|
|
41
47
|
dev:
|
|
42
48
|
- name: "@types/node"
|
|
@@ -99,6 +105,7 @@ requirements:
|
|
|
99
105
|
"fastify": "^5.8.3"
|
|
100
106
|
"@fastify/cors": "^10.0.0"
|
|
101
107
|
"yaml": "^2.3.0"
|
|
108
|
+
"dotenv": "^16.4.0"
|
|
102
109
|
devDependencies:
|
|
103
110
|
"tsx": "^4.0.0"
|
|
104
111
|
scripts:
|
|
@@ -49,6 +49,27 @@ export default function generateFastifyServer(context: TemplateContext): string
|
|
|
49
49
|
* Generated from SpecVerse specification
|
|
50
50
|
*/
|
|
51
51
|
|
|
52
|
+
// Load .env from the closest ancestor that has one. Walks up from
|
|
53
|
+
// this file's directory so the backend picks up the project-root
|
|
54
|
+
// .env whether it's running in the monorepo layout
|
|
55
|
+
// (generated/code/backend/src/main.ts with .env at generated/code/)
|
|
56
|
+
// or the standalone layout (generated/code/src/main.ts with .env at
|
|
57
|
+
// generated/code/). dotenv's default cwd-based behaviour doesn't
|
|
58
|
+
// handle the monorepo case because npm workspaces \`cd\`s into the
|
|
59
|
+
// workspace before running the script.
|
|
60
|
+
import { config as loadEnv } from 'dotenv';
|
|
61
|
+
import { existsSync } from 'fs';
|
|
62
|
+
import { resolve as resolvePath, dirname, join } from 'path';
|
|
63
|
+
import { fileURLToPath } from 'url';
|
|
64
|
+
{
|
|
65
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
66
|
+
while (dir !== '/' && dir !== dirname(dir)) {
|
|
67
|
+
const candidate = join(dir, '.env');
|
|
68
|
+
if (existsSync(candidate)) { loadEnv({ path: candidate }); break; }
|
|
69
|
+
dir = dirname(dir);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
import Fastify from 'fastify';
|
|
53
74
|
import cors from '@fastify/cors';
|
|
54
75
|
import { PrismaClient } from '@prisma/client';
|
|
@@ -112,9 +133,9 @@ ${hasEvents ? ` // Register WebSocket bridge for real-time frontend events
|
|
|
112
133
|
// fall back to 127.0.0.1 cleanly. Binding :: fixes both cases.
|
|
113
134
|
await fastify.listen({ port, host: '::' });
|
|
114
135
|
console.log(\`Server running at http://localhost:\${port}\`);
|
|
115
|
-
console.log(\`API endpoints: ${modelNames.map((n: string) => `/api/${n
|
|
136
|
+
console.log(\`API endpoints: ${modelNames.map((n: string) => `/api/${pluralizeLower(n)}`).join(', ')}\`);
|
|
116
137
|
${hasEvents ? ` console.log(\`WebSocket: ws://localhost:\${port}/ws\`);
|
|
117
|
-
console.log(\`Events: ${specEvents.
|
|
138
|
+
console.log(\`Events: ${specEvents.length} wired (GET /api/runtime/events for the full list)\`);` : ''}
|
|
118
139
|
} catch (err) {
|
|
119
140
|
fastify.log.error(err);
|
|
120
141
|
process.exit(1);
|
|
@@ -124,3 +145,16 @@ ${hasEvents ? ` console.log(\`WebSocket: ws://localhost:\${port}/ws\`);
|
|
|
124
145
|
start();
|
|
125
146
|
`;
|
|
126
147
|
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Minimal English pluralizer — matches the same rules the routes
|
|
151
|
+
* generator uses so the startup banner's route list agrees with the
|
|
152
|
+
* actual routes the server exposes. "Category" → "categories", not
|
|
153
|
+
* "categorys"; "Box" → "boxes"; "Item" → "items".
|
|
154
|
+
*/
|
|
155
|
+
function pluralizeLower(s: string): string {
|
|
156
|
+
const lower = s.toLowerCase();
|
|
157
|
+
if (/[^aeiou]y$/.test(lower)) return lower.slice(0, -1) + 'ies';
|
|
158
|
+
if (/(s|x|z|ch|sh)$/.test(lower)) return lower + 'es';
|
|
159
|
+
return lower + 's';
|
|
160
|
+
}
|
|
@@ -168,9 +168,11 @@ function buildMissingBackRefs(
|
|
|
168
168
|
r.target === target && (r.type === 'hasMany' || r.type === 'hasOne')
|
|
169
169
|
);
|
|
170
170
|
const needsFieldInFk = parentRelsToSameTarget.length > 1;
|
|
171
|
+
// Back-ref FK column — same camelCase convention as the forward
|
|
172
|
+
// belongsTo path above (see line ~455).
|
|
171
173
|
const fkSuffix = needsFieldInFk
|
|
172
|
-
?
|
|
173
|
-
:
|
|
174
|
+
? fieldName + 'Id'
|
|
175
|
+
: model.name.charAt(0).toLowerCase() + model.name.slice(1) + 'Id';
|
|
174
176
|
const fkName = fkSuffix;
|
|
175
177
|
const fkPadding = ' '.repeat(Math.max(1, 15 - fkName.length));
|
|
176
178
|
const refFieldName = needsFieldInFk
|
|
@@ -451,8 +453,13 @@ function generateRelationship(rel: any, model: any, relationMap: Map<string, str
|
|
|
451
453
|
|
|
452
454
|
switch (rel.type) {
|
|
453
455
|
case 'belongsTo':
|
|
454
|
-
// Foreign key field — use
|
|
455
|
-
|
|
456
|
+
// Foreign key field — use camelCase, matching the casing of
|
|
457
|
+
// every other attribute the generator emits. Earlier revisions
|
|
458
|
+
// used snake_case here, which inconsistently mixed
|
|
459
|
+
// `createdAt` (camel) with `category_id` (snake) in the same
|
|
460
|
+
// schema and leaked through to API responses, breaking
|
|
461
|
+
// camelCase FK lookups on the frontend.
|
|
462
|
+
const fkBase = rel.foreignKey || name + 'Id';
|
|
456
463
|
const fkPadding = ' '.repeat(Math.max(1, 15 - fkBase.length));
|
|
457
464
|
// Add @unique if the parent has a hasOne relation pointing to this model
|
|
458
465
|
const isUniqueFK = hasOneTargets.has(`${rel.target}->${model.name}`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@specverse/engines",
|
|
3
|
-
"version": "4.1
|
|
4
|
-
"description": "SpecVerse toolchain
|
|
3
|
+
"version": "4.2.1",
|
|
4
|
+
"description": "SpecVerse toolchain — parser, inference, realize, generators, AI, registry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -65,4 +65,4 @@
|
|
|
65
65
|
"access": "public"
|
|
66
66
|
},
|
|
67
67
|
"license": "MIT"
|
|
68
|
-
}
|
|
68
|
+
}
|