@specverse/engines 4.1.30 → 4.2.0
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/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/applications/templates/react-starter/app-tsx-generator.js +110 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.js +121 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/detail-body-composer.js +78 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/form-body-composer.js +190 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/helpers-emitter.js +45 -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 +46 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/orchestrator.js +30 -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/view-emitter.js +56 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/views-generator.js +66 -0
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +14 -11
- 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 +150 -0
- 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 +145 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/form-body-composer.test.ts +175 -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 +163 -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/app-tsx-generator.ts +141 -0
- package/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.ts +174 -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 +306 -0
- package/libs/instance-factories/applications/templates/react-starter/helpers-emitter.ts +60 -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 +120 -0
- package/libs/instance-factories/applications/templates/react-starter/orchestrator.ts +80 -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 +47 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +94 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +114 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +72 -0
- package/libs/instance-factories/applications/templates/react-starter/view-emitter.ts +151 -0
- package/libs/instance-factories/applications/templates/react-starter/views-generator.ts +137 -0
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +14 -11
- 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/react-app.yaml +0 -186
- 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
|
@@ -1,758 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SpecVerse Code Realization
|
|
3
|
-
*
|
|
4
|
-
* Main entry point for code generation (realization) from SpecVerse specifications.
|
|
5
|
-
* Transforms minimal specs into production-ready implementations.
|
|
6
|
-
*/
|
|
7
|
-
// Types (TemplateContext, etc.)
|
|
8
|
-
export * from './types/index.js';
|
|
9
|
-
// Utilities
|
|
10
|
-
export * from './utils/index.js';
|
|
11
|
-
// Generators
|
|
12
|
-
export * from './generators/index.js';
|
|
13
|
-
// Library
|
|
14
|
-
export { InstanceFactoryLibrary, createDefaultLibrary } from './library/library.js';
|
|
15
|
-
export { createResolver } from './library/resolver.js';
|
|
16
|
-
// Code generator
|
|
17
|
-
export { createCodeGenerator } from './engines/code-generator.js';
|
|
18
|
-
import { writeFileSync, existsSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'fs';
|
|
19
|
-
import { dirname, join, basename } from 'path';
|
|
20
|
-
import { fileURLToPath } from 'url';
|
|
21
|
-
import { createDefaultLibrary } from './library/library.js';
|
|
22
|
-
import { createResolver } from './library/resolver.js';
|
|
23
|
-
import { createCodeGenerator } from './engines/code-generator.js';
|
|
24
|
-
import { loadManifest } from './utils/manifest-loader.js';
|
|
25
|
-
class SpecVerseRealizeEngine {
|
|
26
|
-
name = 'realize';
|
|
27
|
-
version = '3.5.2';
|
|
28
|
-
capabilities = ['realize', 'code-generation', 'manifest-resolution', 'instance-factories'];
|
|
29
|
-
library = null;
|
|
30
|
-
resolver = null;
|
|
31
|
-
codeGenerator = null;
|
|
32
|
-
manifest = null;
|
|
33
|
-
initialized = false;
|
|
34
|
-
async initialize(config) {
|
|
35
|
-
const workingDir = config?.workingDir || process.cwd();
|
|
36
|
-
// Load instance factory library
|
|
37
|
-
this.library = await createDefaultLibrary(workingDir);
|
|
38
|
-
// Load and resolve manifest if provided
|
|
39
|
-
if (config?.manifestPath) {
|
|
40
|
-
this.manifest = loadManifest(config.manifestPath);
|
|
41
|
-
this.resolver = createResolver(this.library, this.manifest);
|
|
42
|
-
}
|
|
43
|
-
// Create code generator
|
|
44
|
-
this.codeGenerator = createCodeGenerator();
|
|
45
|
-
this.initialized = true;
|
|
46
|
-
}
|
|
47
|
-
getInfo() {
|
|
48
|
-
return { name: this.name, version: this.version, capabilities: this.capabilities };
|
|
49
|
-
}
|
|
50
|
-
resolve(capability) {
|
|
51
|
-
if (!this.resolver)
|
|
52
|
-
throw new Error('Realize engine not initialized with manifest.');
|
|
53
|
-
return this.resolver.resolveCapability(capability);
|
|
54
|
-
}
|
|
55
|
-
async generate(resolved, template, context) {
|
|
56
|
-
if (!this.codeGenerator)
|
|
57
|
-
throw new Error('Realize engine not initialized.');
|
|
58
|
-
return this.codeGenerator.generateFromTemplate(resolved, template, context);
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Realize all code from an AI-optimized spec.
|
|
62
|
-
* This is the full pipeline equivalent of the hand-written CLI's `case 'all':`.
|
|
63
|
-
*/
|
|
64
|
-
async realizeAll(spec, outputDir) {
|
|
65
|
-
if (!this.initialized || !this.resolver || !this.codeGenerator) {
|
|
66
|
-
throw new Error('Realize engine not initialized. Call initialize() with manifestPath.');
|
|
67
|
-
}
|
|
68
|
-
const files = [];
|
|
69
|
-
const errors = [];
|
|
70
|
-
const allModels = Object.values(spec.models || {});
|
|
71
|
-
// L3 verification (Quint guards) is NOT run here. It lives in
|
|
72
|
-
// `validate --verify` so users can opt into it explicitly and run
|
|
73
|
-
// it on any spec — raw or inferred — without also generating code.
|
|
74
|
-
// See engines/src/inference/verification.ts.
|
|
75
|
-
const writeOutput = (output) => {
|
|
76
|
-
// A generator can signal "skip this file" by returning an empty string.
|
|
77
|
-
if (output.code === '')
|
|
78
|
-
return;
|
|
79
|
-
const dir = dirname(output.filePath);
|
|
80
|
-
if (!existsSync(dir))
|
|
81
|
-
mkdirSync(dir, { recursive: true });
|
|
82
|
-
writeFileSync(output.filePath, output.code, 'utf-8');
|
|
83
|
-
files.push(basename(output.filePath));
|
|
84
|
-
};
|
|
85
|
-
// Prefer compiled .js in dist/libs/ over .ts in libs/ (Node v24+ can't strip types from node_modules)
|
|
86
|
-
const resolveGenPath = (tsPath) => {
|
|
87
|
-
// Try dist/libs/ compiled JS first
|
|
88
|
-
if (tsPath.includes('/libs/')) {
|
|
89
|
-
const distJsPath = tsPath.replace('/libs/', '/dist/libs/').replace(/\.tsx?$/, '.js');
|
|
90
|
-
if (existsSync(distJsPath))
|
|
91
|
-
return distJsPath;
|
|
92
|
-
}
|
|
93
|
-
// Fall back to .js alongside .ts
|
|
94
|
-
const jsPath = tsPath.replace(/\.tsx?$/, '.js');
|
|
95
|
-
if (existsSync(jsPath))
|
|
96
|
-
return jsPath;
|
|
97
|
-
// Fall back to .ts (works in dev, fails in node_modules on Node v24+)
|
|
98
|
-
if (existsSync(tsPath))
|
|
99
|
-
return tsPath;
|
|
100
|
-
return null;
|
|
101
|
-
};
|
|
102
|
-
const tryResolve = (capability) => {
|
|
103
|
-
try {
|
|
104
|
-
return this.resolver.resolveCapability(capability);
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
// 1. ORM Schema
|
|
111
|
-
const ormResolved = tryResolve('orm.schema');
|
|
112
|
-
if (ormResolved?.instanceFactory?.codeTemplates?.schema) {
|
|
113
|
-
try {
|
|
114
|
-
const output = await this.codeGenerator.generateFromTemplate(ormResolved, 'schema', { spec, models: allModels }, { outputDir });
|
|
115
|
-
writeOutput(output);
|
|
116
|
-
console.log(` ✅ ORM schema: ${output.filePath}`);
|
|
117
|
-
}
|
|
118
|
-
catch (e) {
|
|
119
|
-
errors.push(`ORM: ${e.message}`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
// 1.5 Event infrastructure (types + bus + websocket bridge)
|
|
123
|
-
const eventResolved = tryResolve('messaging.events');
|
|
124
|
-
if (eventResolved?.instanceFactory?.codeTemplates) {
|
|
125
|
-
const eventFiles = [];
|
|
126
|
-
// Generate in dependency order: types first, then bus, then websocket
|
|
127
|
-
const orderedTemplates = ['types', 'bus', 'websocket'].filter(t => eventResolved.instanceFactory.codeTemplates[t]);
|
|
128
|
-
// Also generate any other templates (publisher, subscriber)
|
|
129
|
-
for (const t of Object.keys(eventResolved.instanceFactory.codeTemplates)) {
|
|
130
|
-
if (!orderedTemplates.includes(t))
|
|
131
|
-
orderedTemplates.push(t);
|
|
132
|
-
}
|
|
133
|
-
for (const templateName of orderedTemplates) {
|
|
134
|
-
try {
|
|
135
|
-
const output = await this.codeGenerator.generateFromTemplate(eventResolved, templateName, { spec, models: allModels, manifest: this.manifest }, { outputDir });
|
|
136
|
-
writeOutput(output);
|
|
137
|
-
eventFiles.push(basename(output.filePath));
|
|
138
|
-
}
|
|
139
|
-
catch (e) {
|
|
140
|
-
errors.push(`Event ${templateName}: ${e.message}`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (eventFiles.length)
|
|
144
|
-
console.log(` ✅ Events: ${eventFiles.join(', ')}`);
|
|
145
|
-
}
|
|
146
|
-
// 2. Controllers (per model) — use spec controllers if available, else generate from model
|
|
147
|
-
const ctrlResolved = tryResolve('service.controller');
|
|
148
|
-
if (ctrlResolved?.instanceFactory?.codeTemplates?.controllers) {
|
|
149
|
-
// Build controller lookup from spec
|
|
150
|
-
const specControllers = Array.isArray(spec.controllers)
|
|
151
|
-
? spec.controllers
|
|
152
|
-
: Object.values(spec.controllers || {});
|
|
153
|
-
const controllerLookup = {};
|
|
154
|
-
for (const c of specControllers)
|
|
155
|
-
controllerLookup[c.name] = c;
|
|
156
|
-
for (const model of allModels) {
|
|
157
|
-
try {
|
|
158
|
-
const ctrlName = `${model.name}Controller`;
|
|
159
|
-
const specCtrl = controllerLookup[ctrlName];
|
|
160
|
-
const controller = specCtrl ? {
|
|
161
|
-
...specCtrl,
|
|
162
|
-
model: specCtrl.modelReference || specCtrl.model || model.name,
|
|
163
|
-
modelReference: specCtrl.modelReference || specCtrl.model || model.name,
|
|
164
|
-
cured: specCtrl.cured || { create: {}, retrieve: {}, update: {}, validate: {}, evolve: {}, delete: {} },
|
|
165
|
-
} : {
|
|
166
|
-
name: ctrlName,
|
|
167
|
-
model: model.name,
|
|
168
|
-
modelReference: model.name,
|
|
169
|
-
cured: { create: {}, retrieve: {}, update: {}, validate: {}, evolve: {}, delete: {} },
|
|
170
|
-
};
|
|
171
|
-
const output = await this.codeGenerator.generateFromTemplate(ctrlResolved, 'controllers', { spec, model, controller, models: allModels }, { outputDir });
|
|
172
|
-
writeOutput(output);
|
|
173
|
-
}
|
|
174
|
-
catch (e) {
|
|
175
|
-
errors.push(`Controller ${model.name}: ${e.message}`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
console.log(` ✅ Controllers: ${allModels.length} controller(s)`);
|
|
179
|
-
// 2.5 AI Behaviors — generate .ai.ts files for unmatched steps
|
|
180
|
-
try {
|
|
181
|
-
const aiBehaviorsGenPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'services', 'templates', 'prisma', 'ai-behaviors-generator.ts');
|
|
182
|
-
const aiBehaviorsGen = resolveGenPath(aiBehaviorsGenPath);
|
|
183
|
-
if (aiBehaviorsGen) {
|
|
184
|
-
const { default: generateAiBehaviors } = await import(aiBehaviorsGen);
|
|
185
|
-
let aiBehaviorCount = 0;
|
|
186
|
-
for (const model of allModels) {
|
|
187
|
-
const ctrlName = `${model.name}Controller`;
|
|
188
|
-
const specCtrl = controllerLookup[ctrlName];
|
|
189
|
-
if (specCtrl?.actions && Object.keys(specCtrl.actions).length > 0) {
|
|
190
|
-
const code = await generateAiBehaviors({ spec, model, controller: specCtrl, models: allModels });
|
|
191
|
-
if (code) {
|
|
192
|
-
const filePath = join(outputDir, 'backend', 'src', 'behaviors', `${ctrlName}.ai.ts`);
|
|
193
|
-
const dir = dirname(filePath);
|
|
194
|
-
if (!existsSync(dir))
|
|
195
|
-
mkdirSync(dir, { recursive: true });
|
|
196
|
-
writeFileSync(filePath, code);
|
|
197
|
-
aiBehaviorCount++;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (aiBehaviorCount > 0) {
|
|
202
|
-
console.log(` ✅ AI Behaviors: ${aiBehaviorCount} file(s) — review in behaviors/*.ai.ts`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
catch (e) {
|
|
207
|
-
errors.push(`AI Behaviors: ${e.message}`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
// 3. Services
|
|
211
|
-
const servicesList = Array.isArray(spec.services) ? spec.services : Object.values(spec.services || {});
|
|
212
|
-
if (ctrlResolved?.instanceFactory?.codeTemplates?.services) {
|
|
213
|
-
// Load helpers for pre-scanning unmatched steps + generating AI behavior files
|
|
214
|
-
let scanUnmatched = null;
|
|
215
|
-
let generateAiFile = null;
|
|
216
|
-
try {
|
|
217
|
-
const scanPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'services', 'templates', 'prisma', 'step-conventions.ts');
|
|
218
|
-
const scanResolved = resolveGenPath(scanPath);
|
|
219
|
-
if (scanResolved) {
|
|
220
|
-
const mod = await import(scanResolved);
|
|
221
|
-
scanUnmatched = mod.matchStep;
|
|
222
|
-
}
|
|
223
|
-
const aiGenPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'services', 'templates', 'prisma', 'ai-behaviors-generator.ts');
|
|
224
|
-
const aiResolved = resolveGenPath(aiGenPath);
|
|
225
|
-
if (aiResolved) {
|
|
226
|
-
const mod = await import(aiResolved);
|
|
227
|
-
generateAiFile = mod.generateAiBehaviorsFile;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
catch { /* non-fatal */ }
|
|
231
|
-
/**
|
|
232
|
-
* Pre-scan a service's operations to determine which steps don't match
|
|
233
|
-
* conventions and will need AI-generated behavior functions. Runs the
|
|
234
|
-
* same matchStep logic the service-generator uses, but in this pipeline's
|
|
235
|
-
* module context so we don't need cross-module state.
|
|
236
|
-
*/
|
|
237
|
-
const scanServiceUnmatched = (service) => {
|
|
238
|
-
if (!scanUnmatched)
|
|
239
|
-
return [];
|
|
240
|
-
const operations = service.operations || {};
|
|
241
|
-
const entries = Array.isArray(operations)
|
|
242
|
-
? operations.map((op) => [op.name, op])
|
|
243
|
-
: Object.entries(operations);
|
|
244
|
-
const unmatched = [];
|
|
245
|
-
const seenFunctions = new Set();
|
|
246
|
-
for (const [opName, operation] of entries) {
|
|
247
|
-
const steps = operation.steps || operation.implementation?.steps || [];
|
|
248
|
-
const parameterNames = Object.keys(operation.parameters || {});
|
|
249
|
-
const declaredVars = new Set();
|
|
250
|
-
// Simulate precondition variable declarations
|
|
251
|
-
const preconditions = operation.requires || operation.preconditions || [];
|
|
252
|
-
for (const pc of preconditions) {
|
|
253
|
-
const m = typeof pc === 'string' ? pc.match(/^(\w+)\s+(?:exists|is\s+\w+)$/i) : null;
|
|
254
|
-
if (m)
|
|
255
|
-
declaredVars.add(m[1].charAt(0).toLowerCase() + m[1].slice(1));
|
|
256
|
-
}
|
|
257
|
-
for (let i = 0; i < steps.length; i++) {
|
|
258
|
-
const stepInput = steps[i];
|
|
259
|
-
const stepText = typeof stepInput === 'string' ? stepInput : stepInput?.step;
|
|
260
|
-
const stepAs = typeof stepInput === 'object' ? stepInput?.as : undefined;
|
|
261
|
-
const stepReturns = typeof stepInput === 'object' ? stepInput?.returns : undefined;
|
|
262
|
-
if (typeof stepText !== 'string')
|
|
263
|
-
continue;
|
|
264
|
-
const ctx = {
|
|
265
|
-
modelName: service.name.replace(/Service$/, ''),
|
|
266
|
-
prismaModel: service.name.replace(/Service$/, '').charAt(0).toLowerCase() + service.name.replace(/Service$/, '').slice(1),
|
|
267
|
-
serviceName: service.name,
|
|
268
|
-
operationName: opName,
|
|
269
|
-
stepNum: i + 1,
|
|
270
|
-
parameterNames,
|
|
271
|
-
declaredVars,
|
|
272
|
-
resultName: stepAs,
|
|
273
|
-
};
|
|
274
|
-
const result = scanUnmatched(stepText, ctx);
|
|
275
|
-
if (!result.matched && result.functionName && !seenFunctions.has(result.functionName)) {
|
|
276
|
-
seenFunctions.add(result.functionName);
|
|
277
|
-
unmatched.push({
|
|
278
|
-
step: stepText,
|
|
279
|
-
functionName: result.functionName,
|
|
280
|
-
operationName: opName,
|
|
281
|
-
parameterNames,
|
|
282
|
-
inputs: result.inputs || [],
|
|
283
|
-
returns: stepReturns,
|
|
284
|
-
modelName: service.name, // AI file uses service name as owner
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return unmatched;
|
|
290
|
-
};
|
|
291
|
-
let svcAiCount = 0;
|
|
292
|
-
for (const service of servicesList) {
|
|
293
|
-
try {
|
|
294
|
-
const svcName = service.name || 'Service';
|
|
295
|
-
const output = await this.codeGenerator.generateFromTemplate(ctrlResolved, 'services', { spec, service: { name: svcName, ...service } }, { outputDir });
|
|
296
|
-
writeOutput(output);
|
|
297
|
-
// Pre-scan this service for unmatched steps and generate AI behaviors file if any
|
|
298
|
-
if (generateAiFile) {
|
|
299
|
-
const unmatched = scanServiceUnmatched(service);
|
|
300
|
-
if (unmatched.length > 0) {
|
|
301
|
-
const aiFile = await generateAiFile({
|
|
302
|
-
ownerName: svcName,
|
|
303
|
-
unmatchedFunctions: unmatched,
|
|
304
|
-
availableModels: allModels.map((m) => m.name),
|
|
305
|
-
spec,
|
|
306
|
-
});
|
|
307
|
-
if (aiFile) {
|
|
308
|
-
const filePath = join(outputDir, 'backend', 'src', 'behaviors', `${svcName}.ai.ts`);
|
|
309
|
-
const dir = dirname(filePath);
|
|
310
|
-
if (!existsSync(dir))
|
|
311
|
-
mkdirSync(dir, { recursive: true });
|
|
312
|
-
writeFileSync(filePath, aiFile);
|
|
313
|
-
svcAiCount++;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
catch (e) {
|
|
319
|
-
errors.push(`Service: ${e.message}`);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
console.log(` ✅ Services: ${servicesList.length} service(s)${svcAiCount > 0 ? ` (${svcAiCount} AI behaviors file${svcAiCount > 1 ? 's' : ''})` : ''}`);
|
|
323
|
-
}
|
|
324
|
-
// 4. Routes (per model) — use spec controllers for endpoint data
|
|
325
|
-
const routeResolved = tryResolve('api.rest');
|
|
326
|
-
if (routeResolved?.instanceFactory?.codeTemplates?.routes) {
|
|
327
|
-
const specControllers2 = Array.isArray(spec.controllers)
|
|
328
|
-
? spec.controllers
|
|
329
|
-
: Object.values(spec.controllers || {});
|
|
330
|
-
const ctrlLookup2 = {};
|
|
331
|
-
for (const c of specControllers2)
|
|
332
|
-
ctrlLookup2[c.name] = c;
|
|
333
|
-
for (const model of allModels) {
|
|
334
|
-
try {
|
|
335
|
-
const ctrlName = `${model.name}Controller`;
|
|
336
|
-
const specCtrl = ctrlLookup2[ctrlName];
|
|
337
|
-
const controller = specCtrl || {
|
|
338
|
-
name: ctrlName,
|
|
339
|
-
model: model.name,
|
|
340
|
-
modelReference: model.name,
|
|
341
|
-
cured: { create: {}, retrieve: {}, update: {}, validate: {}, evolve: {}, delete: {} },
|
|
342
|
-
};
|
|
343
|
-
const output = await this.codeGenerator.generateFromTemplate(routeResolved, 'routes', { spec, model, controller, models: allModels }, { outputDir });
|
|
344
|
-
writeOutput(output);
|
|
345
|
-
}
|
|
346
|
-
catch (e) {
|
|
347
|
-
errors.push(`Route ${model.name}: ${e.message}`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
console.log(` ✅ Routes: ${allModels.length} route handler(s)`);
|
|
351
|
-
}
|
|
352
|
-
// 4.5 Service routes — HTTP endpoints for service operations
|
|
353
|
-
// POST /api/services/{ServiceName}/{operationName}
|
|
354
|
-
// Mirrors app-demo's dynamic runtime URL shape (see
|
|
355
|
-
// specverse-app-demo/src/api/http-server.ts:611) so smoke-parity
|
|
356
|
-
// tests can hit both backends identically.
|
|
357
|
-
if (routeResolved?.instanceFactory?.codeTemplates?.serviceRoutes && servicesList.length > 0) {
|
|
358
|
-
let svcRouteCount = 0;
|
|
359
|
-
for (const service of servicesList) {
|
|
360
|
-
try {
|
|
361
|
-
const svcName = service.name || 'Service';
|
|
362
|
-
const output = await this.codeGenerator.generateFromTemplate(routeResolved, 'serviceRoutes', { spec, service: { name: svcName, ...service }, models: allModels }, { outputDir });
|
|
363
|
-
writeOutput(output);
|
|
364
|
-
svcRouteCount++;
|
|
365
|
-
}
|
|
366
|
-
catch (e) {
|
|
367
|
-
errors.push(`Service route ${service?.name}: ${e.message}`);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
if (svcRouteCount > 0)
|
|
371
|
-
console.log(` ✅ Service routes: ${svcRouteCount} file(s)`);
|
|
372
|
-
}
|
|
373
|
-
// 5. Views, Forms, Hooks
|
|
374
|
-
const viewsResolved = tryResolve('ui.components');
|
|
375
|
-
if (viewsResolved && spec.views) {
|
|
376
|
-
const views = Array.isArray(spec.views) ? spec.views : Object.values(spec.views);
|
|
377
|
-
let viewCount = 0, formCount = 0, hookCount = 0;
|
|
378
|
-
for (const viewData of views) {
|
|
379
|
-
const modelRef = viewData.primaryModel || viewData.model;
|
|
380
|
-
const model = modelRef ? allModels.find((m) => m.name === modelRef) : undefined;
|
|
381
|
-
// View component
|
|
382
|
-
if (viewsResolved.instanceFactory.codeTemplates?.components) {
|
|
383
|
-
try {
|
|
384
|
-
const output = await this.codeGenerator.generateFromTemplate(viewsResolved, 'components', { spec, view: viewData, model, models: allModels }, { outputDir });
|
|
385
|
-
writeOutput(output);
|
|
386
|
-
viewCount++;
|
|
387
|
-
}
|
|
388
|
-
catch (e) {
|
|
389
|
-
errors.push(`View ${viewData?.name || 'unknown'}: ${e.message}`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
// Form
|
|
393
|
-
if (model && viewsResolved.instanceFactory.codeTemplates?.forms) {
|
|
394
|
-
try {
|
|
395
|
-
const output = await this.codeGenerator.generateFromTemplate(viewsResolved, 'forms', { spec, model, view: viewData, models: allModels }, { outputDir });
|
|
396
|
-
writeOutput(output);
|
|
397
|
-
formCount++;
|
|
398
|
-
}
|
|
399
|
-
catch (e) {
|
|
400
|
-
errors.push(`Form ${model?.name || 'unknown'}: ${e.message}`);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
// Hook
|
|
404
|
-
if (model && viewsResolved.instanceFactory.codeTemplates?.hooks) {
|
|
405
|
-
try {
|
|
406
|
-
const output = await this.codeGenerator.generateFromTemplate(viewsResolved, 'hooks', { spec, model, view: viewData, models: allModels }, { outputDir });
|
|
407
|
-
writeOutput(output);
|
|
408
|
-
hookCount++;
|
|
409
|
-
}
|
|
410
|
-
catch (e) {
|
|
411
|
-
errors.push(`Hook ${model?.name || 'unknown'}: ${e.message}`);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
if (viewCount)
|
|
416
|
-
console.log(` ✅ Views: ${viewCount} component(s)`);
|
|
417
|
-
if (formCount)
|
|
418
|
-
console.log(` ✅ Forms: ${formCount} form(s)`);
|
|
419
|
-
if (hookCount)
|
|
420
|
-
console.log(` ✅ Hooks: ${hookCount} hook(s)`);
|
|
421
|
-
}
|
|
422
|
-
// 6. Types (per model)
|
|
423
|
-
if (viewsResolved?.instanceFactory?.codeTemplates?.types) {
|
|
424
|
-
for (const model of allModels) {
|
|
425
|
-
try {
|
|
426
|
-
const output = await this.codeGenerator.generateFromTemplate(viewsResolved, 'types', { spec, model, models: allModels }, { outputDir });
|
|
427
|
-
writeOutput(output);
|
|
428
|
-
}
|
|
429
|
-
catch (e) {
|
|
430
|
-
errors.push(`Type ${model?.name || 'unknown'}: ${e.message}`);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
console.log(` ✅ Types: ${allModels.length} model type(s)`);
|
|
434
|
-
}
|
|
435
|
-
// 7. Scaffolding (package.json, tsconfig, etc.)
|
|
436
|
-
const scaffoldResolved = tryResolve('project.scaffold');
|
|
437
|
-
if (scaffoldResolved?.instanceFactory?.codeTemplates) {
|
|
438
|
-
const scaffoldFiles = [];
|
|
439
|
-
for (const [templateName] of Object.entries(scaffoldResolved.instanceFactory.codeTemplates)) {
|
|
440
|
-
try {
|
|
441
|
-
const output = await this.codeGenerator.generateFromTemplate(scaffoldResolved, templateName, { spec, models: allModels, manifest: this.manifest, instanceFactories: [] }, { outputDir });
|
|
442
|
-
writeOutput(output);
|
|
443
|
-
scaffoldFiles.push(basename(output.filePath));
|
|
444
|
-
}
|
|
445
|
-
catch (e) {
|
|
446
|
-
errors.push(`Scaffold: ${e.message}`);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
if (scaffoldFiles.length)
|
|
450
|
-
console.log(` ✅ Project scaffolding: ${scaffoldFiles.join(', ')}`);
|
|
451
|
-
}
|
|
452
|
-
// 8. Backend app entry point + Fastify server with route wiring
|
|
453
|
-
const appResolved = tryResolve('app.entrypoint');
|
|
454
|
-
if (appResolved?.instanceFactory?.codeTemplates) {
|
|
455
|
-
const appFiles = [];
|
|
456
|
-
for (const [templateName] of Object.entries(appResolved.instanceFactory.codeTemplates)) {
|
|
457
|
-
try {
|
|
458
|
-
const output = await this.codeGenerator.generateFromTemplate(appResolved, templateName, { spec, models: allModels, manifest: this.manifest }, { outputDir });
|
|
459
|
-
writeOutput(output);
|
|
460
|
-
appFiles.push(basename(output.filePath));
|
|
461
|
-
}
|
|
462
|
-
catch (e) {
|
|
463
|
-
errors.push(`App ${templateName}: ${e.message}`);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
// Generate Fastify server with auto-wired routes
|
|
467
|
-
try {
|
|
468
|
-
const serverGenPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'controllers', 'templates', 'fastify', 'server-generator.ts');
|
|
469
|
-
const serverGen = resolveGenPath(serverGenPath);
|
|
470
|
-
if (serverGen) {
|
|
471
|
-
const genPath = serverGen;
|
|
472
|
-
const { default: generateServer } = await import(genPath);
|
|
473
|
-
const serverCode = generateServer({ spec, models: allModels });
|
|
474
|
-
const serverPath = join(outputDir, 'backend', 'src', 'main.ts');
|
|
475
|
-
const serverDir = dirname(serverPath);
|
|
476
|
-
if (!existsSync(serverDir))
|
|
477
|
-
mkdirSync(serverDir, { recursive: true });
|
|
478
|
-
writeFileSync(serverPath, serverCode);
|
|
479
|
-
appFiles.push('main.ts');
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
catch (e) {
|
|
483
|
-
errors.push(`Server: ${e.message}`);
|
|
484
|
-
}
|
|
485
|
-
if (appFiles.length)
|
|
486
|
-
console.log(` ✅ Backend application: ${appFiles.join(', ')}`);
|
|
487
|
-
}
|
|
488
|
-
// 9. Frontend app
|
|
489
|
-
const frontendResolved = tryResolve('app.frontend');
|
|
490
|
-
if (frontendResolved?.instanceFactory?.codeTemplates) {
|
|
491
|
-
const frontendFiles = [];
|
|
492
|
-
for (const [templateName] of Object.entries(frontendResolved.instanceFactory.codeTemplates)) {
|
|
493
|
-
try {
|
|
494
|
-
const output = await this.codeGenerator.generateFromTemplate(frontendResolved, templateName, { spec, models: allModels, views: spec.views, manifest: this.manifest }, { outputDir });
|
|
495
|
-
writeOutput(output);
|
|
496
|
-
frontendFiles.push(basename(output.filePath));
|
|
497
|
-
}
|
|
498
|
-
catch (e) {
|
|
499
|
-
errors.push(`Frontend ${templateName}: ${e.message}`);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
if (frontendFiles.length)
|
|
503
|
-
console.log(` ✅ Frontend application: ${frontendFiles.join(', ')}`);
|
|
504
|
-
// 9a. Generate shared view utilities and Tailwind config.
|
|
505
|
-
// The frontend factory may declare outputStructure=standalone with
|
|
506
|
-
// frontendDir="." — in that case views/lib live at the output root
|
|
507
|
-
// instead of under `frontend/`.
|
|
508
|
-
const frontendCfg = frontendResolved?.configuration || frontendResolved?.instanceFactory?.configuration || {};
|
|
509
|
-
const frontendOutputStructure = frontendCfg.outputStructure || 'monorepo';
|
|
510
|
-
const frontendRelDir = frontendOutputStructure === 'standalone'
|
|
511
|
-
? '.'
|
|
512
|
-
: (frontendCfg.frontendDir || 'frontend');
|
|
513
|
-
const frontendDir = frontendRelDir === '.' ? outputDir : join(outputDir, frontendRelDir);
|
|
514
|
-
try {
|
|
515
|
-
const sharedUtilsGen = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'views', 'templates', 'react', 'shared-utils-generator.ts');
|
|
516
|
-
const sharedUtilsResolved = resolveGenPath(sharedUtilsGen);
|
|
517
|
-
if (sharedUtilsResolved) {
|
|
518
|
-
const genPath = sharedUtilsResolved;
|
|
519
|
-
const { default: generateSharedUtils } = await import(genPath);
|
|
520
|
-
const result = generateSharedUtils({ spec });
|
|
521
|
-
const libDir = join(frontendDir, 'src', 'lib');
|
|
522
|
-
if (!existsSync(libDir))
|
|
523
|
-
mkdirSync(libDir, { recursive: true });
|
|
524
|
-
for (const file of result.files) {
|
|
525
|
-
writeFileSync(join(libDir, file.path), file.content);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
// Tailwind config
|
|
529
|
-
writeFileSync(join(frontendDir, 'tailwind.config.js'), `import path from 'path';
|
|
530
|
-
import { createRequire } from 'module';
|
|
531
|
-
const require = createRequire(import.meta.url);
|
|
532
|
-
|
|
533
|
-
/** @type {import('tailwindcss').Config} */
|
|
534
|
-
export default {
|
|
535
|
-
darkMode: 'class',
|
|
536
|
-
content: [
|
|
537
|
-
'./index.html',
|
|
538
|
-
'./src/**/*.{js,ts,jsx,tsx}',
|
|
539
|
-
path.join(path.dirname(require.resolve('@specverse/runtime/package.json')), 'dist/**/*.js'),
|
|
540
|
-
],
|
|
541
|
-
theme: { extend: {} },
|
|
542
|
-
plugins: [],
|
|
543
|
-
};
|
|
544
|
-
`);
|
|
545
|
-
writeFileSync(join(frontendDir, 'postcss.config.js'), `export default {
|
|
546
|
-
plugins: {
|
|
547
|
-
tailwindcss: {},
|
|
548
|
-
autoprefixer: {},
|
|
549
|
-
},
|
|
550
|
-
};
|
|
551
|
-
`);
|
|
552
|
-
}
|
|
553
|
-
catch (e) {
|
|
554
|
-
errors.push(`SharedUtils: ${e.message}`);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
// 9b. Quint guards are no longer emitted to the generated backend.
|
|
558
|
-
// They are L3 verification assertions about the SPEC, not runtime
|
|
559
|
-
// checks on the user's data — so they run at realize time (see the
|
|
560
|
-
// L3 verification gate at the start of realizeAll) and don't ship
|
|
561
|
-
// to the user's project.
|
|
562
|
-
// 10. CLI commands (Commander.js)
|
|
563
|
-
try {
|
|
564
|
-
const cliDir = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'cli', 'templates', 'commander');
|
|
565
|
-
const entryGen = join(cliDir, 'cli-entry-generator.ts');
|
|
566
|
-
const cmdGen = join(cliDir, 'command-generator.ts');
|
|
567
|
-
const entryGenResolved = resolveGenPath(entryGen);
|
|
568
|
-
const cmdGenResolved = resolveGenPath(cmdGen);
|
|
569
|
-
if (entryGenResolved && cmdGenResolved) {
|
|
570
|
-
// Extract commands from spec — check each component for a commands section
|
|
571
|
-
const components = spec.components || [];
|
|
572
|
-
const componentList = Array.isArray(components)
|
|
573
|
-
? components
|
|
574
|
-
: Object.entries(components).map(([name, c]) => ({ name, ...c }));
|
|
575
|
-
let commands = [];
|
|
576
|
-
let cliComponentVersion;
|
|
577
|
-
for (const comp of componentList) {
|
|
578
|
-
if (comp.commands) {
|
|
579
|
-
commands = Array.isArray(comp.commands)
|
|
580
|
-
? comp.commands
|
|
581
|
-
: Object.entries(comp.commands).map(([name, def]) => ({ name, ...def }));
|
|
582
|
-
cliComponentVersion = comp.version;
|
|
583
|
-
break;
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
if (commands.length > 0) {
|
|
587
|
-
const cliOutputDir = join(outputDir, 'backend', 'src', 'cli');
|
|
588
|
-
if (!existsSync(cliOutputDir))
|
|
589
|
-
mkdirSync(cliOutputDir, { recursive: true });
|
|
590
|
-
// Generate entry point
|
|
591
|
-
const { default: generateCLIEntry } = await import(entryGenResolved);
|
|
592
|
-
const entryCode = generateCLIEntry({ spec, commands, componentVersion: cliComponentVersion });
|
|
593
|
-
const entryPath = join(cliOutputDir, 'index.ts');
|
|
594
|
-
writeFileSync(entryPath, entryCode);
|
|
595
|
-
// Generate individual command files
|
|
596
|
-
const { default: generateCommand } = await import(cmdGenResolved);
|
|
597
|
-
const cmdOutputDir = join(cliOutputDir, 'commands');
|
|
598
|
-
if (!existsSync(cmdOutputDir))
|
|
599
|
-
mkdirSync(cmdOutputDir, { recursive: true });
|
|
600
|
-
// The root command's subcommands are the actual CLI commands
|
|
601
|
-
const rootCommand = commands[0];
|
|
602
|
-
const subcommands = rootCommand.subcommands || {};
|
|
603
|
-
let cmdCount = 0;
|
|
604
|
-
for (const [cmdName, cmdDef] of Object.entries(subcommands)) {
|
|
605
|
-
try {
|
|
606
|
-
const code = generateCommand({ spec, command: { name: cmdName, ...cmdDef } });
|
|
607
|
-
writeFileSync(join(cmdOutputDir, `${cmdName}.ts`), code);
|
|
608
|
-
cmdCount++;
|
|
609
|
-
}
|
|
610
|
-
catch (e) {
|
|
611
|
-
errors.push(`CLI ${cmdName}: ${e.message}`);
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
console.log(` ✅ CLI: ${cmdCount} command(s) generated`);
|
|
615
|
-
files.push('index.ts', ...Object.keys(subcommands).map(n => `${n}.ts`));
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
catch (e) {
|
|
620
|
-
errors.push(`CLI generation: ${e.message}`);
|
|
621
|
-
}
|
|
622
|
-
// 11. Developer tools (VSCode extension, MCP server)
|
|
623
|
-
// Only generate when the manifest maps tools.vscode or tools.mcp capabilities
|
|
624
|
-
try {
|
|
625
|
-
const toolsDir = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'tools', 'templates');
|
|
626
|
-
// VSCode extension — only if manifest maps tools.vscode
|
|
627
|
-
if (tryResolve('tools.vscode')) {
|
|
628
|
-
const vscodeGen = resolveGenPath(join(toolsDir, 'vscode', 'vscode-extension-generator.ts'));
|
|
629
|
-
if (vscodeGen) {
|
|
630
|
-
try {
|
|
631
|
-
const { default: generateVSCodeExtension } = await import(vscodeGen);
|
|
632
|
-
const result = generateVSCodeExtension({ spec, outputDir, models: allModels });
|
|
633
|
-
console.log(` ✅ VSCode extension: ${result}`);
|
|
634
|
-
}
|
|
635
|
-
catch (e) {
|
|
636
|
-
errors.push(`VSCode: ${e.message}`);
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
// MCP server — only if manifest maps tools.mcp
|
|
641
|
-
if (tryResolve('tools.mcp')) {
|
|
642
|
-
const mcpGen = resolveGenPath(join(toolsDir, 'mcp', 'mcp-server-generator.ts'));
|
|
643
|
-
if (mcpGen) {
|
|
644
|
-
try {
|
|
645
|
-
const { default: generateMCPServer } = await import(mcpGen);
|
|
646
|
-
const result = generateMCPServer({ spec, outputDir, models: allModels });
|
|
647
|
-
console.log(` ✅ MCP server: ${result}`);
|
|
648
|
-
}
|
|
649
|
-
catch (e) {
|
|
650
|
-
errors.push(`MCP: ${e.message}`);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
catch (e) {
|
|
656
|
-
errors.push(`Tools: ${e.message}`);
|
|
657
|
-
}
|
|
658
|
-
// 12. Ship assets (templates, examples, schema) from engine packages
|
|
659
|
-
try {
|
|
660
|
-
const assetsCopied = await this.copyAssets(outputDir);
|
|
661
|
-
if (assetsCopied.length > 0) {
|
|
662
|
-
console.log(` ✅ Assets: ${assetsCopied.join(', ')}`);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
catch (e) {
|
|
666
|
-
errors.push(`Assets: ${e.message}`);
|
|
667
|
-
}
|
|
668
|
-
console.log(`\n✅ All code generated in: ${outputDir}`);
|
|
669
|
-
if (errors.length) {
|
|
670
|
-
console.warn(`⚠️ ${errors.length} warning(s) during generation`);
|
|
671
|
-
for (const err of errors)
|
|
672
|
-
console.warn(` ${err}`);
|
|
673
|
-
}
|
|
674
|
-
return { files, errors };
|
|
675
|
-
}
|
|
676
|
-
/**
|
|
677
|
-
* Copy static assets (templates, examples, schema) from engine packages
|
|
678
|
-
* into the output directory so the generated project is self-contained.
|
|
679
|
-
*/
|
|
680
|
-
async copyAssets(outputDir) {
|
|
681
|
-
const copied = [];
|
|
682
|
-
const copyDir = (src, dest, label) => {
|
|
683
|
-
if (!existsSync(src))
|
|
684
|
-
return;
|
|
685
|
-
if (!existsSync(dest))
|
|
686
|
-
mkdirSync(dest, { recursive: true });
|
|
687
|
-
const copyRecursive = (s, d) => {
|
|
688
|
-
for (const entry of readdirSync(s)) {
|
|
689
|
-
const srcPath = join(s, entry);
|
|
690
|
-
const destPath = join(d, entry);
|
|
691
|
-
if (statSync(srcPath).isDirectory()) {
|
|
692
|
-
if (!existsSync(destPath))
|
|
693
|
-
mkdirSync(destPath, { recursive: true });
|
|
694
|
-
copyRecursive(srcPath, destPath);
|
|
695
|
-
}
|
|
696
|
-
else {
|
|
697
|
-
copyFileSync(srcPath, destPath);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
};
|
|
701
|
-
copyRecursive(src, dest);
|
|
702
|
-
copied.push(label);
|
|
703
|
-
};
|
|
704
|
-
// Assets live in engines/assets/ (co-located with this package)
|
|
705
|
-
const enginesAssets = this.resolvePackageAssets('@specverse/engines', 'assets');
|
|
706
|
-
if (enginesAssets) {
|
|
707
|
-
copyDir(join(enginesAssets, 'examples'), join(outputDir, 'examples'), 'examples');
|
|
708
|
-
copyDir(join(enginesAssets, 'prompts'), join(outputDir, 'prompts'), 'prompts');
|
|
709
|
-
}
|
|
710
|
-
// Copy composed schema from engine-entities (single source of truth)
|
|
711
|
-
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
712
|
-
const schemaCandidates = [];
|
|
713
|
-
try {
|
|
714
|
-
const { createRequire } = await import('module');
|
|
715
|
-
const require = createRequire(import.meta.url);
|
|
716
|
-
const entitiesPkg = dirname(require.resolve('@specverse/entities/package.json'));
|
|
717
|
-
schemaCandidates.push(join(entitiesPkg, 'schema', 'SPECVERSE-SCHEMA.json'));
|
|
718
|
-
}
|
|
719
|
-
catch { /* engine-entities not available */ }
|
|
720
|
-
schemaCandidates.push(join(thisDir, '..', 'schema', 'SPECVERSE-SCHEMA.json'), join(thisDir, '../..', 'schema', 'SPECVERSE-SCHEMA.json'));
|
|
721
|
-
for (const schemaFile of schemaCandidates) {
|
|
722
|
-
if (existsSync(schemaFile)) {
|
|
723
|
-
const destSchema = join(outputDir, 'backend', 'schema');
|
|
724
|
-
if (!existsSync(destSchema))
|
|
725
|
-
mkdirSync(destSchema, { recursive: true });
|
|
726
|
-
copyFileSync(schemaFile, join(destSchema, 'SPECVERSE-SCHEMA.json'));
|
|
727
|
-
copied.push('schema');
|
|
728
|
-
break;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
return copied;
|
|
732
|
-
}
|
|
733
|
-
resolvePackageAssets(packageName, subdir) {
|
|
734
|
-
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
735
|
-
const isSelf = packageName === '@specverse/engines' || packageName === '@specverse/engine-realize';
|
|
736
|
-
const candidates = [];
|
|
737
|
-
if (isSelf) {
|
|
738
|
-
// For our own package, look relative to this file
|
|
739
|
-
candidates.push(join(thisDir, '..', subdir));
|
|
740
|
-
candidates.push(join(thisDir, '../..', subdir));
|
|
741
|
-
}
|
|
742
|
-
// Try via node_modules (workspace layout and npm install)
|
|
743
|
-
for (let i = 2; i <= 5; i++) {
|
|
744
|
-
const up = Array(i).fill('..').join('/');
|
|
745
|
-
candidates.push(join(thisDir, up, 'node_modules', ...packageName.split('/'), subdir));
|
|
746
|
-
}
|
|
747
|
-
candidates.push(join(process.cwd(), 'node_modules', ...packageName.split('/'), subdir));
|
|
748
|
-
for (const candidate of candidates) {
|
|
749
|
-
if (existsSync(candidate))
|
|
750
|
-
return candidate;
|
|
751
|
-
}
|
|
752
|
-
return null;
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
export const engine = new SpecVerseRealizeEngine();
|
|
756
|
-
export default engine;
|
|
757
|
-
export { SpecVerseRealizeEngine };
|
|
758
|
-
//# sourceMappingURL=index.js.map
|