@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,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML → JSX transformer for ReactAppStarter
|
|
3
|
+
*
|
|
4
|
+
* Input: HTML strings produced by the canonical Tailwind adapter
|
|
5
|
+
* (`@specverse/runtime/views/tailwind`). Output: JSX-safe source
|
|
6
|
+
* that can be dropped between JSX tags in a .tsx template.
|
|
7
|
+
*
|
|
8
|
+
* Scope is deliberately narrow:
|
|
9
|
+
* - Controlled input — only the adapter's output shape matters.
|
|
10
|
+
* - String transformer with a small tokenizer, not a full HTML parser.
|
|
11
|
+
* - No SVG / MathML / script / style tags.
|
|
12
|
+
*
|
|
13
|
+
* If the adapter ever produces markup outside this scope, extend the
|
|
14
|
+
* ATTRIBUTE_RENAMES map / VOID_ELEMENTS set + add a focused test,
|
|
15
|
+
* don't broaden the tokenizer into a general HTML parser.
|
|
16
|
+
*
|
|
17
|
+
* See: README.md ("The html-to-jsx transformer — scope").
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* HTML void elements — must be self-closing in JSX.
|
|
22
|
+
*/
|
|
23
|
+
const VOID_ELEMENTS = new Set([
|
|
24
|
+
'area', 'base', 'br', 'col', 'embed', 'hr', 'img',
|
|
25
|
+
'input', 'link', 'meta', 'source', 'track', 'wbr',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* HTML attribute → JSX attribute renames. JSX is case-sensitive and
|
|
30
|
+
* camelCases several HTML attribute names. Only lists the ones the
|
|
31
|
+
* Tailwind adapter actually emits.
|
|
32
|
+
*/
|
|
33
|
+
const ATTRIBUTE_RENAMES: Record<string, string> = {
|
|
34
|
+
class: 'className',
|
|
35
|
+
for: 'htmlFor',
|
|
36
|
+
tabindex: 'tabIndex',
|
|
37
|
+
readonly: 'readOnly',
|
|
38
|
+
maxlength: 'maxLength',
|
|
39
|
+
minlength: 'minLength',
|
|
40
|
+
colspan: 'colSpan',
|
|
41
|
+
rowspan: 'rowSpan',
|
|
42
|
+
autofocus: 'autoFocus',
|
|
43
|
+
autocomplete: 'autoComplete',
|
|
44
|
+
cellpadding: 'cellPadding',
|
|
45
|
+
cellspacing: 'cellSpacing',
|
|
46
|
+
enctype: 'encType',
|
|
47
|
+
usemap: 'useMap',
|
|
48
|
+
accesskey: 'accessKey',
|
|
49
|
+
contenteditable: 'contentEditable',
|
|
50
|
+
spellcheck: 'spellCheck',
|
|
51
|
+
crossorigin: 'crossOrigin',
|
|
52
|
+
srcset: 'srcSet',
|
|
53
|
+
srcdoc: 'srcDoc',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Convert an HTML string to JSX-safe source.
|
|
58
|
+
*
|
|
59
|
+
* Idempotent on already-JSX-shaped input.
|
|
60
|
+
*/
|
|
61
|
+
export function htmlToJsx(html: string): string {
|
|
62
|
+
if (!html) return '';
|
|
63
|
+
|
|
64
|
+
const tokens = tokenize(html);
|
|
65
|
+
return tokens.map(transformToken).join('');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
69
|
+
// Tokenizer
|
|
70
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
type Token =
|
|
73
|
+
| { kind: 'text'; value: string }
|
|
74
|
+
| { kind: 'tag'; raw: string; tagName: string; isClosing: boolean; isSelfClosed: boolean };
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Split an HTML string into text and tag tokens. Quoted attribute
|
|
78
|
+
* values never close a tag, even if they contain '>'. The adapter
|
|
79
|
+
* does not emit unbalanced quotes, so a simple state machine is
|
|
80
|
+
* sufficient.
|
|
81
|
+
*/
|
|
82
|
+
function tokenize(input: string): Token[] {
|
|
83
|
+
const tokens: Token[] = [];
|
|
84
|
+
let i = 0;
|
|
85
|
+
const n = input.length;
|
|
86
|
+
|
|
87
|
+
while (i < n) {
|
|
88
|
+
const ch = input[i];
|
|
89
|
+
if (ch === '<') {
|
|
90
|
+
// Start of tag. Consume until the matching '>', respecting quotes.
|
|
91
|
+
const tagStart = i;
|
|
92
|
+
i++; // consume '<'
|
|
93
|
+
|
|
94
|
+
let quote: '"' | "'" | null = null;
|
|
95
|
+
while (i < n) {
|
|
96
|
+
const c = input[i];
|
|
97
|
+
if (quote) {
|
|
98
|
+
if (c === quote) quote = null;
|
|
99
|
+
i++;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (c === '"' || c === "'") {
|
|
103
|
+
quote = c;
|
|
104
|
+
i++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (c === '>') {
|
|
108
|
+
i++; // include the '>'
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const raw = input.slice(tagStart, i);
|
|
115
|
+
const isClosing = raw.startsWith('</');
|
|
116
|
+
const isSelfClosed = raw.endsWith('/>');
|
|
117
|
+
const tagName = parseTagName(raw);
|
|
118
|
+
tokens.push({ kind: 'tag', raw, tagName, isClosing, isSelfClosed });
|
|
119
|
+
} else {
|
|
120
|
+
// Text until the next '<'.
|
|
121
|
+
const textStart = i;
|
|
122
|
+
while (i < n && input[i] !== '<') i++;
|
|
123
|
+
tokens.push({ kind: 'text', value: input.slice(textStart, i) });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return tokens;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parseTagName(tagRaw: string): string {
|
|
131
|
+
// Handles '<foo', '</foo', '<foo ', '<foo/>', '<foo>' etc.
|
|
132
|
+
const m = tagRaw.match(/^<\/?([a-zA-Z][a-zA-Z0-9-]*)/);
|
|
133
|
+
return m ? m[1].toLowerCase() : '';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
137
|
+
// Transformation
|
|
138
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
function transformToken(token: Token): string {
|
|
141
|
+
if (token.kind === 'text') {
|
|
142
|
+
// Adapter output never contains raw '{' or '}' in text nodes in
|
|
143
|
+
// the current implementation. If that changes, escape them here
|
|
144
|
+
// via `{'{'}` / `{'}'}`. Flagged for future: a regression test
|
|
145
|
+
// in view-emitter.test.ts that feeds a spec containing literal
|
|
146
|
+
// braces and expects them round-tripped.
|
|
147
|
+
return token.value;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return transformTag(token.raw, token.tagName, token.isClosing, token.isSelfClosed);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Transform a single tag: rename attributes, self-close if void.
|
|
155
|
+
*
|
|
156
|
+
* Works inside-out: split into `<`, open-portion, attributes, close-portion, `>`.
|
|
157
|
+
* We only rewrite attribute NAMES (identifier tokens at attribute start),
|
|
158
|
+
* never attribute values or text. The tokenizer guarantees we see the
|
|
159
|
+
* full tag as one unit.
|
|
160
|
+
*/
|
|
161
|
+
function transformTag(
|
|
162
|
+
raw: string,
|
|
163
|
+
tagName: string,
|
|
164
|
+
isClosing: boolean,
|
|
165
|
+
isSelfClosed: boolean
|
|
166
|
+
): string {
|
|
167
|
+
if (isClosing) {
|
|
168
|
+
// '</div>' — nothing to rewrite
|
|
169
|
+
return raw;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Split the tag into the tag-name section and the attributes section.
|
|
173
|
+
// Strip the leading '<' and trailing '>' or '/>'.
|
|
174
|
+
const inner = isSelfClosed
|
|
175
|
+
? raw.slice(1, -2).trimEnd()
|
|
176
|
+
: raw.slice(1, -1);
|
|
177
|
+
|
|
178
|
+
// Separate tag name from attributes. The tag name is the first word.
|
|
179
|
+
const nameMatch = inner.match(/^([a-zA-Z][a-zA-Z0-9-]*)(.*)$/s);
|
|
180
|
+
if (!nameMatch) return raw;
|
|
181
|
+
|
|
182
|
+
const elementName = nameMatch[1];
|
|
183
|
+
const attrsSection = nameMatch[2];
|
|
184
|
+
|
|
185
|
+
// Parse attributes into structured form, rename names, re-serialise.
|
|
186
|
+
// This guarantees we never rewrite anything inside an attribute value.
|
|
187
|
+
const attrs = parseAttributes(attrsSection);
|
|
188
|
+
const renamedAttrs = attrs.map(renameAttribute);
|
|
189
|
+
const rebuiltAttrs = serializeAttributes(renamedAttrs);
|
|
190
|
+
|
|
191
|
+
const shouldSelfClose = VOID_ELEMENTS.has(tagName) && !isSelfClosed;
|
|
192
|
+
|
|
193
|
+
if (shouldSelfClose || isSelfClosed) {
|
|
194
|
+
return `<${elementName}${rebuiltAttrs} />`;
|
|
195
|
+
}
|
|
196
|
+
return `<${elementName}${rebuiltAttrs}>`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
200
|
+
// Attribute tokenizer
|
|
201
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
interface Attribute {
|
|
204
|
+
name: string;
|
|
205
|
+
/** undefined = boolean attribute, no value. */
|
|
206
|
+
value?: string;
|
|
207
|
+
/** How the original source quoted the value — preserve on output. */
|
|
208
|
+
quote?: '"' | "'";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Parse the attributes section of a tag (the substring after the tag
|
|
213
|
+
* name, before the closing `>` or `/>`) into a list of attributes.
|
|
214
|
+
* Whitespace is consumed between attributes; values inside quotes are
|
|
215
|
+
* opaque.
|
|
216
|
+
*/
|
|
217
|
+
function parseAttributes(section: string): Attribute[] {
|
|
218
|
+
const attrs: Attribute[] = [];
|
|
219
|
+
let i = 0;
|
|
220
|
+
const n = section.length;
|
|
221
|
+
|
|
222
|
+
while (i < n) {
|
|
223
|
+
// Skip whitespace
|
|
224
|
+
while (i < n && /\s/.test(section[i])) i++;
|
|
225
|
+
if (i >= n) break;
|
|
226
|
+
|
|
227
|
+
// Read name (letters, digits, hyphens, colons for namespaced attrs)
|
|
228
|
+
const nameStart = i;
|
|
229
|
+
while (i < n && /[a-zA-Z0-9:_-]/.test(section[i])) i++;
|
|
230
|
+
if (i === nameStart) {
|
|
231
|
+
// Nothing we recognise — advance to avoid infinite loop
|
|
232
|
+
i++;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const name = section.slice(nameStart, i);
|
|
236
|
+
|
|
237
|
+
// Look for '=value' or end-of-attr
|
|
238
|
+
// Skip whitespace before '='
|
|
239
|
+
let j = i;
|
|
240
|
+
while (j < n && /\s/.test(section[j])) j++;
|
|
241
|
+
if (j >= n || section[j] !== '=') {
|
|
242
|
+
// Boolean attribute (e.g. `readonly`, `disabled`)
|
|
243
|
+
attrs.push({ name });
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
// Consume '=' and optional whitespace
|
|
247
|
+
i = j + 1;
|
|
248
|
+
while (i < n && /\s/.test(section[i])) i++;
|
|
249
|
+
if (i >= n) {
|
|
250
|
+
// Malformed: attribute with '=' and no value. Treat as boolean.
|
|
251
|
+
attrs.push({ name });
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Read value — quoted or unquoted
|
|
256
|
+
const vc = section[i];
|
|
257
|
+
if (vc === '"' || vc === "'") {
|
|
258
|
+
const quote = vc as '"' | "'";
|
|
259
|
+
i++; // skip opening quote
|
|
260
|
+
const valStart = i;
|
|
261
|
+
while (i < n && section[i] !== quote) i++;
|
|
262
|
+
const value = section.slice(valStart, i);
|
|
263
|
+
if (i < n) i++; // skip closing quote
|
|
264
|
+
attrs.push({ name, value, quote });
|
|
265
|
+
} else {
|
|
266
|
+
// Unquoted value — run until whitespace or '/' or '>'
|
|
267
|
+
const valStart = i;
|
|
268
|
+
while (i < n && !/[\s/>]/.test(section[i])) i++;
|
|
269
|
+
const value = section.slice(valStart, i);
|
|
270
|
+
attrs.push({ name, value });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return attrs;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Apply HTML→JSX name rewrite and, for `style`, value conversion. */
|
|
278
|
+
function renameAttribute(attr: Attribute): Attribute {
|
|
279
|
+
const lower = attr.name.toLowerCase();
|
|
280
|
+
const newName = ATTRIBUTE_RENAMES[lower] ?? attr.name;
|
|
281
|
+
|
|
282
|
+
if (lower === 'style' && attr.value !== undefined) {
|
|
283
|
+
return { name: newName, value: cssStringToJsxObject(attr.value), quote: undefined };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return { ...attr, name: newName };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Serialise a list of attributes back into source form. Preserves the
|
|
291
|
+
* original quote style. A value of `{{ ... }}` (already a JSX object
|
|
292
|
+
* expression) is emitted unquoted (no surrounding `"`); it's already
|
|
293
|
+
* valid JSX syntax.
|
|
294
|
+
*/
|
|
295
|
+
function serializeAttributes(attrs: Attribute[]): string {
|
|
296
|
+
if (attrs.length === 0) return '';
|
|
297
|
+
const parts = attrs.map(attr => {
|
|
298
|
+
if (attr.value === undefined) return ` ${attr.name}`;
|
|
299
|
+
if (attr.value.startsWith('{{') && attr.value.endsWith('}}')) {
|
|
300
|
+
// JSX expression like {{ color: 'red' }} — no quotes.
|
|
301
|
+
return ` ${attr.name}=${attr.value}`;
|
|
302
|
+
}
|
|
303
|
+
const q = attr.quote ?? '"';
|
|
304
|
+
return ` ${attr.name}=${q}${attr.value}${q}`;
|
|
305
|
+
});
|
|
306
|
+
return parts.join('');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
310
|
+
// CSS → JSX style object
|
|
311
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
function cssStringToJsxObject(css: string): string {
|
|
314
|
+
const declarations = css
|
|
315
|
+
.split(';')
|
|
316
|
+
.map(d => d.trim())
|
|
317
|
+
.filter(d => d.length > 0);
|
|
318
|
+
|
|
319
|
+
if (declarations.length === 0) return '{{}}';
|
|
320
|
+
|
|
321
|
+
const entries = declarations
|
|
322
|
+
.map(decl => {
|
|
323
|
+
const colonIdx = decl.indexOf(':');
|
|
324
|
+
if (colonIdx === -1) return null;
|
|
325
|
+
const prop = decl.slice(0, colonIdx).trim();
|
|
326
|
+
const value = decl.slice(colonIdx + 1).trim();
|
|
327
|
+
const camelProp = prop.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
|
|
328
|
+
const escapedValue = value.replace(/'/g, "\\'");
|
|
329
|
+
return `${camelProp}: '${escapedValue}'`;
|
|
330
|
+
})
|
|
331
|
+
.filter((e): e is string => e !== null);
|
|
332
|
+
|
|
333
|
+
return `{{ ${entries.join(', ')} }}`;
|
|
334
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List-view body composer for ReactAppStarter
|
|
3
|
+
*
|
|
4
|
+
* Implements the `renderBody` contract of view-emitter.ts for list
|
|
5
|
+
* views. Uses the canonical Tailwind adapter from
|
|
6
|
+
* `@specverse/runtime/views/tailwind` to render the TABLE SHELL, then
|
|
7
|
+
* injects a JSX `.map()` expression into the tbody so the generated
|
|
8
|
+
* component renders rows from the `filtered` array defined by the
|
|
9
|
+
* skeleton.
|
|
10
|
+
*
|
|
11
|
+
* Why split it this way: the adapter produces static HTML (the same
|
|
12
|
+
* HTML app-demo uses). For Factory B we want React that maps over
|
|
13
|
+
* runtime data — so we take the adapter's shell and replace a sentinel
|
|
14
|
+
* inside the tbody with a `{filtered.map(...)}` expression. The shell
|
|
15
|
+
* stays canonical; only the row-iteration JSX is synthesized here.
|
|
16
|
+
*
|
|
17
|
+
* See README.md for the full architecture.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { createUniversalTailwindAdapter } from '@specverse/runtime/views/tailwind';
|
|
21
|
+
import { inferFieldsFromSchema } from '@specverse/runtime/views/core';
|
|
22
|
+
import { htmlToJsx } from './html-to-jsx.js';
|
|
23
|
+
import type { EmitContext, ModelSpec } from './view-emitter.js';
|
|
24
|
+
import { buildFKMap } from './belongs-to.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Sentinel token. Must be ASCII-safe (no curly braces) so it survives
|
|
28
|
+
* the html-to-jsx transform unchanged, and must be unique enough not
|
|
29
|
+
* to appear in real HTML output.
|
|
30
|
+
*/
|
|
31
|
+
const TBODY_SENTINEL = '__SPECVERSE_TBODY_ROWS__';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compose the list view's body as JSX-safe source. Returned string is
|
|
35
|
+
* meant to be dropped at `{{BODY}}` inside `skeletons/list.tsx.template`.
|
|
36
|
+
*/
|
|
37
|
+
export function composeListBody(context: EmitContext): string {
|
|
38
|
+
const fkMap = buildFKMap(context.model);
|
|
39
|
+
// Inference (`inferFieldsFromSchema`) reads attributes and doesn't
|
|
40
|
+
// see belongsTo relationships. For list views we want a column per
|
|
41
|
+
// belongsTo so users can scan "who owns this" at a glance —
|
|
42
|
+
// otherwise a Task table hides its Project and Assignee columns.
|
|
43
|
+
// Append any FK columns the inference missed, preserving order.
|
|
44
|
+
const inferred = inferColumns(context);
|
|
45
|
+
const inferredSet = new Set(inferred);
|
|
46
|
+
const extraFKs = [...fkMap.keys()].filter(k => !inferredSet.has(k));
|
|
47
|
+
const columns = [...inferred, ...extraFKs];
|
|
48
|
+
|
|
49
|
+
// Header label for an FK column should use the relationship name
|
|
50
|
+
// ("Owner") not the raw column name ("Owner Id"). The cell rendering
|
|
51
|
+
// in buildRowMap applies the matching FK→name resolution.
|
|
52
|
+
const headers = columns.map(col => {
|
|
53
|
+
const fk = fkMap.get(col);
|
|
54
|
+
return humanize(fk ? fk.name : col);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const adapter = createUniversalTailwindAdapter({ darkMode: true });
|
|
58
|
+
const shellHtml = adapter.components.table.render({
|
|
59
|
+
properties: { columns: headers },
|
|
60
|
+
children: TBODY_SENTINEL,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Transform the static HTML shell to JSX-safe source. The sentinel
|
|
64
|
+
// lands in a text position inside the <tbody> and survives the
|
|
65
|
+
// transform untouched.
|
|
66
|
+
const shellJsx = htmlToJsx(shellHtml);
|
|
67
|
+
|
|
68
|
+
// Build the row-iteration JSX that replaces the sentinel. The
|
|
69
|
+
// skeleton's useMemo produces `filtered: Model[]`, so the map
|
|
70
|
+
// expression binds over that. onSelect is a prop the skeleton
|
|
71
|
+
// declares. Delete action wires to the deleteItem mutation.
|
|
72
|
+
const rowMap = buildRowMap(columns, fkMap);
|
|
73
|
+
|
|
74
|
+
if (!shellJsx.includes(TBODY_SENTINEL)) {
|
|
75
|
+
// Defensive: catches adapter output changes that no longer flow
|
|
76
|
+
// `children` into the tbody. Caught at composer time, not runtime.
|
|
77
|
+
throw new Error(
|
|
78
|
+
'composeListBody: tbody sentinel not present in adapter output. ' +
|
|
79
|
+
'The canonical Tailwind adapter may have changed its table rendering.'
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return shellJsx.replace(TBODY_SENTINEL, rowMap);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Select the non-metadata attributes of a model. Delegates to the
|
|
88
|
+
* canonical pattern library so the column set is identical to what
|
|
89
|
+
* the runtime React adapter picks for the same model — parity test P3
|
|
90
|
+
* relies on this being one-and-the-same function.
|
|
91
|
+
*/
|
|
92
|
+
function inferColumns(context: EmitContext): string[] {
|
|
93
|
+
return inferFieldsFromSchema(context.modelSchemas, context.model.name);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Convert a camelCase attribute name to Title-Case words for display
|
|
98
|
+
* as a column header. Matches the convention used by runtime's
|
|
99
|
+
* pattern adapter (see `humanize` in runtime react-pattern-adapter).
|
|
100
|
+
*/
|
|
101
|
+
function humanize(name: string): string {
|
|
102
|
+
return name
|
|
103
|
+
.replace(/([A-Z])/g, ' $1')
|
|
104
|
+
.replace(/^./, c => c.toUpperCase())
|
|
105
|
+
.trim();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Emit the `{filtered.map(...)}` JSX expression that renders each
|
|
110
|
+
* row. Keep the output readable so users inspecting the generated
|
|
111
|
+
* file can follow what's happening.
|
|
112
|
+
*/
|
|
113
|
+
function buildRowMap(columns: string[], fkMap: Map<string, { name: string; target: string }>): string {
|
|
114
|
+
// Type-cast to `any` to avoid TS generic syntax (`<string, unknown>`)
|
|
115
|
+
// inside JSX expressions, which the TSX parser can't always
|
|
116
|
+
// disambiguate from a JSX opening tag. `any` is the right choice for
|
|
117
|
+
// starter-kit output anyway — the user will often reshape the row
|
|
118
|
+
// type after editing.
|
|
119
|
+
//
|
|
120
|
+
// FK columns render through `resolveEntityDisplayName(id, options)`
|
|
121
|
+
// so the user sees a name instead of a UUID. The options array
|
|
122
|
+
// (`${relName}Options`) is populated by the hook call wired into
|
|
123
|
+
// the list skeleton via view-emitter's RELATED_HOOKS substitution.
|
|
124
|
+
const cells = columns
|
|
125
|
+
.map(col => {
|
|
126
|
+
const fk = fkMap.get(col);
|
|
127
|
+
const expr = fk
|
|
128
|
+
? `{resolveEntityDisplayName((item as any).${col}, ${fk.name}Options)}`
|
|
129
|
+
: `{String((item as any).${col} ?? '')}`;
|
|
130
|
+
return ` <td className="px-6 py-4 text-sm text-gray-700 dark:text-gray-300">` +
|
|
131
|
+
`${expr}</td>`;
|
|
132
|
+
})
|
|
133
|
+
.join('\n');
|
|
134
|
+
|
|
135
|
+
return [
|
|
136
|
+
'{filtered.map((item, idx) => (',
|
|
137
|
+
' <tr',
|
|
138
|
+
' key={idx}',
|
|
139
|
+
' onClick={() => onSelect?.(item)}',
|
|
140
|
+
' className="hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer"',
|
|
141
|
+
' >',
|
|
142
|
+
cells,
|
|
143
|
+
' </tr>',
|
|
144
|
+
'))}',
|
|
145
|
+
].join('\n');
|
|
146
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory B top-level orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Single entry point the realize engine calls. Returns a map of
|
|
5
|
+
* `relativePath → content` for every file the factory decides to
|
|
6
|
+
* write, after applying regeneration-safety triage. Realize handles
|
|
7
|
+
* the actual filesystem writes via its normal multi-file generator
|
|
8
|
+
* pipeline.
|
|
9
|
+
*
|
|
10
|
+
* Skipped (user-edited) files are NOT in the returned map — realize
|
|
11
|
+
* never sees them, so user edits are safe. The hash manifest is
|
|
12
|
+
* emitted as one of the returned files (`.specverse-gen/hashes.json`)
|
|
13
|
+
* so it rolls through the same write pipeline.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
17
|
+
import { join, dirname } from 'path';
|
|
18
|
+
import { generate as generateViews } from './views-generator.js';
|
|
19
|
+
import { generate as generateAppTsx } from './app-tsx-generator.js';
|
|
20
|
+
import { generate as generatePackageJson } from './package-json-generator.js';
|
|
21
|
+
import {
|
|
22
|
+
loadHashManifest,
|
|
23
|
+
reconcileWrites,
|
|
24
|
+
summarize,
|
|
25
|
+
HASHES_DIR,
|
|
26
|
+
HASHES_FILE,
|
|
27
|
+
} from './regen-safety.js';
|
|
28
|
+
import type { ExpandedSpec } from './views-generator.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Context shape as it arrives from realize's code-generator. The
|
|
32
|
+
* generator protocol is "function receives context, returns string
|
|
33
|
+
* (the single file contents)". This orchestrator is special: instead
|
|
34
|
+
* of one file, it produces many (views × models, App.tsx,
|
|
35
|
+
* package.json, regen-safety manifest). It writes them directly and
|
|
36
|
+
* returns '' so realize's write pipeline skips the no-op.
|
|
37
|
+
*
|
|
38
|
+
* `outputDir` and `frontendDir` come from the realize-time context.
|
|
39
|
+
* `frontendDir` may be '.' for standalone layouts or 'frontend' for
|
|
40
|
+
* monorepos — either way, the resolved write root is
|
|
41
|
+
* `outputDir + frontendDir`.
|
|
42
|
+
*/
|
|
43
|
+
export interface OrchestratorContext {
|
|
44
|
+
spec: ExpandedSpec & { metadata?: { name?: string }; name?: string };
|
|
45
|
+
outputDir: string;
|
|
46
|
+
frontendDir?: string;
|
|
47
|
+
manifest?: unknown;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Produce the complete file set for a Factory B run, writing each
|
|
52
|
+
* file directly. Returns '' so the caller's single-file writeOutput
|
|
53
|
+
* short-circuits.
|
|
54
|
+
*/
|
|
55
|
+
export async function generate(context: OrchestratorContext): Promise<string> {
|
|
56
|
+
const { spec, outputDir } = context;
|
|
57
|
+
const frontendDir = context.frontendDir || 'frontend';
|
|
58
|
+
const projectRoot = frontendDir === '.' ? outputDir : join(outputDir, frontendDir);
|
|
59
|
+
|
|
60
|
+
// 1. Produce all file contents (pure composition — no I/O yet).
|
|
61
|
+
const proposed: Record<string, string> = {};
|
|
62
|
+
|
|
63
|
+
const viewFiles = await generateViews({ spec });
|
|
64
|
+
Object.assign(proposed, viewFiles);
|
|
65
|
+
|
|
66
|
+
proposed['src/App.tsx'] = await generateAppTsx({ spec });
|
|
67
|
+
proposed['package.json'] = await generatePackageJson({ spec });
|
|
68
|
+
|
|
69
|
+
// 2. Content-hashing triage against the prior manifest. Read-only.
|
|
70
|
+
const prevManifest = loadHashManifest(projectRoot);
|
|
71
|
+
const result = reconcileWrites(projectRoot, proposed, prevManifest);
|
|
72
|
+
|
|
73
|
+
// 3. Log the summary for operator visibility.
|
|
74
|
+
console.log(summarize(result, projectRoot));
|
|
75
|
+
|
|
76
|
+
// 4. Write the approved files + the updated hash manifest.
|
|
77
|
+
const toWrite: Record<string, string> = {
|
|
78
|
+
...result.approvedWrites,
|
|
79
|
+
[`${HASHES_DIR}/${HASHES_FILE}`]: JSON.stringify(result.manifest, null, 2) + '\n',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
for (const [rel, content] of Object.entries(toWrite)) {
|
|
83
|
+
const abs = join(projectRoot, rel);
|
|
84
|
+
const dir = dirname(abs);
|
|
85
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
86
|
+
writeFileSync(abs, content, 'utf-8');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Realize's single-file writeOutput skips empty strings (see
|
|
90
|
+
// realize/index.ts writeOutput). The orchestrator already wrote
|
|
91
|
+
// everything it needed.
|
|
92
|
+
return '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default generate;
|
package/libs/instance-factories/applications/templates/react-starter/package-json-generator.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* package.json generator for ReactAppStarter
|
|
3
|
+
*
|
|
4
|
+
* Deliberately does NOT include @specverse/runtime. The promise of
|
|
5
|
+
* Factory B is that the generated project is standalone — everything
|
|
6
|
+
* needed to render is emitted as local source.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface PackageJsonGeneratorContext {
|
|
10
|
+
spec: {
|
|
11
|
+
metadata?: { name?: string };
|
|
12
|
+
name?: string;
|
|
13
|
+
};
|
|
14
|
+
manifest?: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function generate(context: PackageJsonGeneratorContext): Promise<string> {
|
|
18
|
+
const appName = context.spec?.metadata?.name ?? context.spec?.name ?? 'specverse-starter-app';
|
|
19
|
+
const packageName = slugify(appName);
|
|
20
|
+
|
|
21
|
+
const pkg = {
|
|
22
|
+
name: packageName,
|
|
23
|
+
private: true,
|
|
24
|
+
version: '0.1.0',
|
|
25
|
+
type: 'module',
|
|
26
|
+
scripts: {
|
|
27
|
+
dev: 'vite',
|
|
28
|
+
build: 'tsc && vite build',
|
|
29
|
+
preview: 'vite preview',
|
|
30
|
+
typecheck: 'tsc --noEmit',
|
|
31
|
+
},
|
|
32
|
+
dependencies: {
|
|
33
|
+
react: '^18.2.0',
|
|
34
|
+
'react-dom': '^18.2.0',
|
|
35
|
+
'@tanstack/react-query': '^5.0.0',
|
|
36
|
+
},
|
|
37
|
+
devDependencies: {
|
|
38
|
+
'@types/react': '^18.2.0',
|
|
39
|
+
'@types/react-dom': '^18.2.0',
|
|
40
|
+
'@vitejs/plugin-react': '^4.2.0',
|
|
41
|
+
autoprefixer: '^10.4.20',
|
|
42
|
+
postcss: '^8.4.47',
|
|
43
|
+
tailwindcss: '^3.4.13',
|
|
44
|
+
typescript: '^5.2.0',
|
|
45
|
+
vite: '^6.0.0',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return JSON.stringify(pkg, null, 2) + '\n';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function slugify(s: string): string {
|
|
53
|
+
return s
|
|
54
|
+
.toLowerCase()
|
|
55
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
56
|
+
.replace(/^-+|-+$/g, '') || 'specverse-starter-app';
|
|
57
|
+
}
|