@specverse/engines 6.33.0 → 6.34.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/dist/libs/instance-factories/communication/rabbitmq-events.yaml +11 -0
- package/dist/libs/instance-factories/controllers/fastify.yaml +11 -0
- package/dist/libs/instance-factories/managed/stripe-rest-sdk.yaml +78 -0
- package/dist/libs/instance-factories/managed/templates/stripe/stripe-client-generator.js +8 -0
- package/dist/libs/instance-factories/orms/prisma.yaml +11 -0
- package/dist/realize/index.d.ts.map +1 -1
- package/dist/realize/index.js +261 -4
- package/dist/realize/index.js.map +1 -1
- package/dist/realize/library/loader.d.ts.map +1 -1
- package/dist/realize/library/loader.js +81 -1
- package/dist/realize/library/loader.js.map +1 -1
- package/dist/realize/per-action-emitter.d.ts +9 -0
- package/dist/realize/per-action-emitter.d.ts.map +1 -1
- package/dist/realize/per-action-emitter.js.map +1 -1
- package/dist/realize/per-action-llm-emit.d.ts.map +1 -1
- package/dist/realize/per-action-llm-emit.js +5 -1
- package/dist/realize/per-action-llm-emit.js.map +1 -1
- package/dist/realize/per-action-runner.d.ts +45 -0
- package/dist/realize/per-action-runner.d.ts.map +1 -1
- package/dist/realize/per-action-runner.js +132 -0
- package/dist/realize/per-action-runner.js.map +1 -1
- package/dist/realize/resolver/index.d.ts +157 -0
- package/dist/realize/resolver/index.d.ts.map +1 -0
- package/dist/realize/resolver/index.js +307 -0
- package/dist/realize/resolver/index.js.map +1 -0
- package/dist/realize/runtime-emitters/dispatcher.d.ts +176 -0
- package/dist/realize/runtime-emitters/dispatcher.d.ts.map +1 -0
- package/dist/realize/runtime-emitters/dispatcher.js +76 -0
- package/dist/realize/runtime-emitters/dispatcher.js.map +1 -0
- package/dist/realize/runtime-emitters/executable.d.ts +57 -0
- package/dist/realize/runtime-emitters/executable.d.ts.map +1 -0
- package/dist/realize/runtime-emitters/executable.js +316 -0
- package/dist/realize/runtime-emitters/executable.js.map +1 -0
- package/dist/realize/runtime-emitters/library.d.ts +52 -0
- package/dist/realize/runtime-emitters/library.d.ts.map +1 -0
- package/dist/realize/runtime-emitters/library.js +349 -0
- package/dist/realize/runtime-emitters/library.js.map +1 -0
- package/dist/realize/runtime-emitters/managed.d.ts +44 -0
- package/dist/realize/runtime-emitters/managed.d.ts.map +1 -0
- package/dist/realize/runtime-emitters/managed.js +283 -0
- package/dist/realize/runtime-emitters/managed.js.map +1 -0
- package/dist/realize/runtime-emitters/messaging.d.ts +77 -0
- package/dist/realize/runtime-emitters/messaging.d.ts.map +1 -0
- package/dist/realize/runtime-emitters/messaging.js +423 -0
- package/dist/realize/runtime-emitters/messaging.js.map +1 -0
- package/dist/realize/runtime-emitters/service.d.ts +42 -0
- package/dist/realize/runtime-emitters/service.d.ts.map +1 -0
- package/dist/realize/runtime-emitters/service.js +355 -0
- package/dist/realize/runtime-emitters/service.js.map +1 -0
- package/dist/realize/types/instance-factory.d.ts +1 -1
- package/dist/realize/types/instance-factory.d.ts.map +1 -1
- package/libs/instance-factories/communication/rabbitmq-events.yaml +11 -0
- package/libs/instance-factories/controllers/fastify.yaml +11 -0
- package/libs/instance-factories/managed/stripe-rest-sdk.yaml +78 -0
- package/libs/instance-factories/managed/templates/stripe/stripe-client-generator.ts +26 -0
- package/libs/instance-factories/orms/prisma.yaml +11 -0
- package/package.json +1 -1
|
@@ -40,6 +40,17 @@ dependencies:
|
|
|
40
40
|
- name: "typescript"
|
|
41
41
|
version: "^5.2.0"
|
|
42
42
|
|
|
43
|
+
# V2 factory metadata — Component Dependencies V2 proposal Section 4
|
|
44
|
+
v2metadata:
|
|
45
|
+
topology: messaging # AMQP broker pub/sub; deploys as communications: instance
|
|
46
|
+
protocol: amqp # AMQP 0-9-1 (RabbitMQ)
|
|
47
|
+
authentication: { kind: bearer, mechanism: PLAIN } # RabbitMQ credential pair (user/password)
|
|
48
|
+
supportedTopologies: [messaging]
|
|
49
|
+
serverTemplate: templates/rabbitmq/connection-generator.ts # broker connection setup
|
|
50
|
+
clientTemplate: templates/rabbitmq/publisher-generator.ts # publisher client for producers
|
|
51
|
+
emitsDeploymentInstance: true # generates a communications: instance entry
|
|
52
|
+
deploymentCategory: communications # lands in communications: category
|
|
53
|
+
|
|
43
54
|
codeTemplates:
|
|
44
55
|
publisher:
|
|
45
56
|
engine: typescript
|
|
@@ -52,6 +52,17 @@ dependencies:
|
|
|
52
52
|
- name: "tsx"
|
|
53
53
|
version: "^4.0.0"
|
|
54
54
|
|
|
55
|
+
# V2 factory metadata — Component Dependencies V2 proposal Section 4
|
|
56
|
+
v2metadata:
|
|
57
|
+
topology: service # generates a Fastify HTTP server; deploys as services: instance
|
|
58
|
+
protocol: rest # HTTP/REST transport
|
|
59
|
+
authentication: { kind: bearer } # typical JWT bearer auth; overridable per deployment
|
|
60
|
+
supportedTopologies: [service]
|
|
61
|
+
serverTemplate: templates/fastify/server-generator.ts # server bind code (main.ts / server.ts)
|
|
62
|
+
clientTemplate: null # clients generated from TypeScript SDK factory (separate concern)
|
|
63
|
+
emitsDeploymentInstance: true # generates a services: instance entry in deployment
|
|
64
|
+
deploymentCategory: services # lands in services: category
|
|
65
|
+
|
|
55
66
|
codeTemplates:
|
|
56
67
|
routes:
|
|
57
68
|
engine: typescript
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
name: StripeRestSDK
|
|
2
|
+
version: "1.0.0"
|
|
3
|
+
category: infrastructure
|
|
4
|
+
description: "Stripe payments SDK adapter — wraps the Stripe Node.js SDK with a typed SpecVerse client for managed payment processing"
|
|
5
|
+
|
|
6
|
+
metadata:
|
|
7
|
+
author: "SpecVerse Team"
|
|
8
|
+
license: "MIT"
|
|
9
|
+
tags: [stripe, payments, managed, rest, sdk, infrastructure]
|
|
10
|
+
|
|
11
|
+
compatibility:
|
|
12
|
+
specverse: ">=6.33.0"
|
|
13
|
+
node: ">=18.0.0"
|
|
14
|
+
|
|
15
|
+
capabilities:
|
|
16
|
+
provides:
|
|
17
|
+
- "managed.payments"
|
|
18
|
+
- "managed.payments.stripe"
|
|
19
|
+
requires: [] # external SaaS — no transitive SpecVerse deps
|
|
20
|
+
|
|
21
|
+
technology:
|
|
22
|
+
runtime: "node"
|
|
23
|
+
language: "typescript"
|
|
24
|
+
framework: "stripe-sdk"
|
|
25
|
+
version: "^14.0.0"
|
|
26
|
+
|
|
27
|
+
# V2 factory metadata — Component Dependencies V2 proposal Section 4
|
|
28
|
+
# Forward-looking stub: Round 2 realize emitters will consume these fields.
|
|
29
|
+
v2metadata:
|
|
30
|
+
topology: managed # external SaaS via SDK; deploys as infrastructure: instance
|
|
31
|
+
protocol: rest # Stripe API uses HTTPS/REST under the hood
|
|
32
|
+
sdk: "stripe" # npm package name: https://www.npmjs.com/package/stripe
|
|
33
|
+
authentication: { kind: api-key, envVar: STRIPE_SECRET_KEY } # Stripe uses API keys
|
|
34
|
+
supportedTopologies: [managed]
|
|
35
|
+
clientTemplate: templates/stripe/stripe-client-generator.ts # typed wrapper exposing Charge.create etc.
|
|
36
|
+
serverTemplate: null # external SaaS — no server bind code emitted
|
|
37
|
+
emitsDeploymentInstance: true # generates an infrastructure: instance entry in deployment
|
|
38
|
+
deploymentCategory: infrastructure # lands in infrastructure: category (external SaaS)
|
|
39
|
+
|
|
40
|
+
dependencies:
|
|
41
|
+
runtime:
|
|
42
|
+
- name: "stripe"
|
|
43
|
+
version: "^14.0.0"
|
|
44
|
+
|
|
45
|
+
dev:
|
|
46
|
+
- name: "@types/node"
|
|
47
|
+
version: "^20.8.0"
|
|
48
|
+
- name: "typescript"
|
|
49
|
+
version: "^5.2.0"
|
|
50
|
+
|
|
51
|
+
# Minimal stub: codeTemplates to be fleshed out in Round 2 (per-runtime emitters)
|
|
52
|
+
codeTemplates:
|
|
53
|
+
client:
|
|
54
|
+
engine: typescript
|
|
55
|
+
generator: "libs/instance-factories/managed/templates/stripe/stripe-client-generator.ts"
|
|
56
|
+
outputPattern: "{backendDir}/src/clients/StripeClient.ts"
|
|
57
|
+
|
|
58
|
+
configuration:
|
|
59
|
+
envVar: "STRIPE_SECRET_KEY"
|
|
60
|
+
apiVersion: "2024-06-20"
|
|
61
|
+
webhookSecret: null # optional: STRIPE_WEBHOOK_SECRET for signature verification
|
|
62
|
+
|
|
63
|
+
requirements:
|
|
64
|
+
dependencies:
|
|
65
|
+
npm:
|
|
66
|
+
dependencies:
|
|
67
|
+
"stripe": "^14.0.0"
|
|
68
|
+
environment:
|
|
69
|
+
- name: "STRIPE_SECRET_KEY"
|
|
70
|
+
description: "Stripe secret API key"
|
|
71
|
+
example: "sk_live_..."
|
|
72
|
+
required: true
|
|
73
|
+
category: "Payments"
|
|
74
|
+
- name: "STRIPE_WEBHOOK_SECRET"
|
|
75
|
+
description: "Stripe webhook signing secret (required only if handling webhooks)"
|
|
76
|
+
example: "whsec_..."
|
|
77
|
+
required: false
|
|
78
|
+
category: "Payments"
|
|
@@ -40,6 +40,17 @@ dependencies:
|
|
|
40
40
|
- name: "typescript"
|
|
41
41
|
version: "^5.2.0"
|
|
42
42
|
|
|
43
|
+
# V2 factory metadata — Component Dependencies V2 proposal Section 4
|
|
44
|
+
v2metadata:
|
|
45
|
+
topology: library # in-process ORM client; no deployment instance emitted
|
|
46
|
+
# protocol: omitted — not applicable for library topology
|
|
47
|
+
authentication: { kind: none } # auth is the DB connection string, not a protocol-level scheme
|
|
48
|
+
supportedTopologies: [library]
|
|
49
|
+
clientTemplate: null # client is the generated Prisma client itself
|
|
50
|
+
serverTemplate: null # no server component
|
|
51
|
+
emitsDeploymentInstance: false # library-shaped; consumer manages DB config via storage: instance
|
|
52
|
+
deploymentCategory: storage # informational: if a storage: instance existed, it would land here
|
|
53
|
+
|
|
43
54
|
codeTemplates:
|
|
44
55
|
schema:
|
|
45
56
|
engine: typescript
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/realize/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,kBAAkB,CAAC;AAGjC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,uBAAuB,CAAC;AAItC,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAMlE,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/realize/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,kBAAkB,CAAC;AAGjC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,uBAAuB,CAAC;AAItC,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAGvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAMlE,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AA2DnF,cAAM,sBAAuB,YAAW,aAAa;IACnD,IAAI,SAAa;IACjB,OAAO,SAAW;IAClB,YAAY,WAA+E;IAE3F,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,WAAW,CAAS;IAEtB,UAAU,CAAC,MAAM,CAAC,EAAE;QACxB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwDjB,OAAO,IAAI,UAAU;IAIrB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG;IAK1B,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC;IAKvF;;;OAGG;IACG,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAomC9F;;;OAGG;YACW,UAAU;IA8DxB,OAAO,CAAC,oBAAoB;CAuB7B;AAED,eAAO,MAAM,MAAM,wBAA+B,CAAC;AACnD,eAAe,MAAM,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
|
package/dist/realize/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export { createCodeGenerator } from './engines/code-generator.js';
|
|
|
17
17
|
import { writeFileSync, existsSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'fs';
|
|
18
18
|
import { dirname, join, basename } from 'path';
|
|
19
19
|
import { fileURLToPath } from 'url';
|
|
20
|
+
import yaml from 'js-yaml';
|
|
20
21
|
import { createDefaultLibrary } from './library/library.js';
|
|
21
22
|
import { createResolver } from './library/resolver.js';
|
|
22
23
|
import { createCodeGenerator } from './engines/code-generator.js';
|
|
@@ -354,20 +355,39 @@ class SpecVerseRealizeEngine {
|
|
|
354
355
|
}
|
|
355
356
|
}
|
|
356
357
|
// ─── Per-action path (default and only path since Phase 3b) ───
|
|
357
|
-
const { runPerActionEmission } = await import('./per-action-runner.js');
|
|
358
|
+
const { runPerActionEmission, buildAvailableClientsByOwner } = await import('./per-action-runner.js');
|
|
358
359
|
const { createRealizeActionLlmEmit } = await import('./per-action-llm-emit.js');
|
|
359
360
|
// Prefer the ORM-specific shared matcher when available; fall back to
|
|
360
361
|
// the prisma matchAgainstConventions with the loaded convention array.
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
362
|
+
// Use resolveGenPath (defined above) which handles published-package
|
|
363
|
+
// structure correctly: it checks dist/libs/ first (compiled .js for
|
|
364
|
+
// installed packages) before falling back to libs/ (source .ts for
|
|
365
|
+
// dev). The previous inline `.replace(/\.ts$/, '.js')` only checked
|
|
366
|
+
// libs/.../step-matching.js which doesn't exist in either location —
|
|
367
|
+
// published packages have it at dist/libs/.../step-matching.js.
|
|
368
|
+
let sharedMatcher = matcher;
|
|
369
|
+
if (!sharedMatcher && conventions) {
|
|
370
|
+
const sharedMatcherTsPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'services', 'templates', '_shared', 'step-matching.ts');
|
|
371
|
+
const sharedMatcherSrc = resolveGenPath(sharedMatcherTsPath);
|
|
372
|
+
if (sharedMatcherSrc) {
|
|
373
|
+
const sharedMod = await import(sharedMatcherSrc);
|
|
374
|
+
sharedMatcher = sharedMod.matchAgainstConventions;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
364
377
|
// LLM emit hook — only fires when an AI provider is configured.
|
|
365
378
|
const llmEmit = createRealizeActionLlmEmit({});
|
|
379
|
+
// Round 3 follow-up: thread V2 imports → LLM prompt's
|
|
380
|
+
// `availableClients` slot. Built from spec.components[].import,
|
|
381
|
+
// keyed by owner so the runner can drop the per-component string
|
|
382
|
+
// into RealizeContext as it walks each owner. Empty map when no
|
|
383
|
+
// component declares V2 imports — runner is a no-op then.
|
|
384
|
+
const availableClientsByOwner = buildAvailableClientsByOwner(spec);
|
|
366
385
|
const result = await runPerActionEmission({
|
|
367
386
|
spec, outputDir,
|
|
368
387
|
matcher: sharedMatcher,
|
|
369
388
|
conventions,
|
|
370
389
|
llmEmit,
|
|
390
|
+
availableClientsByOwner,
|
|
371
391
|
silent: false,
|
|
372
392
|
});
|
|
373
393
|
if (result.totals.actionCount > 0) {
|
|
@@ -378,6 +398,243 @@ class SpecVerseRealizeEngine {
|
|
|
378
398
|
errors.push(`AI Behaviors: ${e.message}`);
|
|
379
399
|
}
|
|
380
400
|
}
|
|
401
|
+
// 2.6 V2 imports — per-component runtime client emission
|
|
402
|
+
//
|
|
403
|
+
// For each component's `import:` block, resolve the `from:` reference via
|
|
404
|
+
// the V2 dependency resolver (4-step chain: local → npm → registry library
|
|
405
|
+
// → registry factory) and dispatch to the right per-runtime emitter
|
|
406
|
+
// (library / service / managed / executable / messaging) keyed by factory
|
|
407
|
+
// v2metadata.topology. This is the wire-up that makes V2 P3's framework
|
|
408
|
+
// actually drive code emission end-to-end.
|
|
409
|
+
//
|
|
410
|
+
// Factory binding (R3 follow-up): for each import the provider's deployment
|
|
411
|
+
// instance is looked up in the spec by `instance.component === resolved.resolvedName`
|
|
412
|
+
// across all 8 instance categories. The first `advertises:` capability is
|
|
413
|
+
// then resolved via manifest.capabilityMappings to an instanceFactory name,
|
|
414
|
+
// which is fetched from the InstanceFactoryLibrary. If the factory has
|
|
415
|
+
// v2metadata, it drives dispatch; otherwise we fall back to a synthetic
|
|
416
|
+
// library-topology factory (in-process default) so consumers without
|
|
417
|
+
// explicit topology bindings still get a typed wrapper.
|
|
418
|
+
//
|
|
419
|
+
// Output: clients/<provider>.ts (and servers/<provider>-server.ts for
|
|
420
|
+
// service topology when serverTemplate is set) under <outputDir>/backend/src/<ConsumerComponent>/.
|
|
421
|
+
//
|
|
422
|
+
// Resolver registry steps (3+4) are stubbed in Round 1; offline mode and
|
|
423
|
+
// local + npm sources work fully.
|
|
424
|
+
//
|
|
425
|
+
// Deliberately no-op when a component has no `import:` entries — V2 is
|
|
426
|
+
// additive; existing specs are unaffected.
|
|
427
|
+
// Helper: walk all deployments in the spec, find the first instance whose
|
|
428
|
+
// `component:` matches the resolved provider name. Searches across the 8
|
|
429
|
+
// instance categories (controllers / services / views / communications /
|
|
430
|
+
// storage / security / infrastructure / monitoring).
|
|
431
|
+
const findDeploymentInstanceForProvider = (providerName) => {
|
|
432
|
+
const deployments = Array.isArray(spec.deployments)
|
|
433
|
+
? spec.deployments
|
|
434
|
+
: Object.values(spec.deployments ?? {});
|
|
435
|
+
for (const dep of deployments) {
|
|
436
|
+
const instances = dep.instances ?? {};
|
|
437
|
+
for (const categoryKey of Object.keys(instances)) {
|
|
438
|
+
const category = instances[categoryKey];
|
|
439
|
+
if (!category || typeof category !== 'object')
|
|
440
|
+
continue;
|
|
441
|
+
for (const instanceKey of Object.keys(category)) {
|
|
442
|
+
const inst = category[instanceKey];
|
|
443
|
+
if (inst && typeof inst === 'object' && inst.component === providerName) {
|
|
444
|
+
return inst;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return null;
|
|
450
|
+
};
|
|
451
|
+
// Helper: pick a factory for an import. Priority chain:
|
|
452
|
+
// 1. Resolved provider's deployment instance → first `advertises:` capability
|
|
453
|
+
// → manifest.capabilityMappings → library.get(instanceFactory).
|
|
454
|
+
// 2. Synthetic library-topology default (the safe in-process fallback).
|
|
455
|
+
const syntheticLibraryFactory = {
|
|
456
|
+
name: 'LibraryRuntimeDefault',
|
|
457
|
+
version: '1.0.0',
|
|
458
|
+
category: 'service',
|
|
459
|
+
capabilities: { provides: [], requires: [] },
|
|
460
|
+
technology: { runtime: 'node', framework: 'typescript', language: 'typescript' },
|
|
461
|
+
v2metadata: { topology: 'library', emitsDeploymentInstance: false },
|
|
462
|
+
};
|
|
463
|
+
// Helper: strip a trailing 'Client' suffix from an emitter-returned
|
|
464
|
+
// specifier so the v2Clients aggregator keys are clean (e.g. specifier
|
|
465
|
+
// 'profitCalculatorClient' → key 'profitCalculator').
|
|
466
|
+
const stripTrailingClient = (specifier) => specifier.endsWith('Client') ? specifier.slice(0, -'Client'.length) : specifier;
|
|
467
|
+
const pickFactoryForImport = (resolved) => {
|
|
468
|
+
const providerName = resolved.resolvedName ?? resolved.fromName;
|
|
469
|
+
if (!providerName)
|
|
470
|
+
return syntheticLibraryFactory;
|
|
471
|
+
const deployInstance = findDeploymentInstanceForProvider(providerName);
|
|
472
|
+
const advertises = deployInstance?.advertises;
|
|
473
|
+
const firstCap = Array.isArray(advertises) ? advertises[0] : undefined;
|
|
474
|
+
if (typeof firstCap !== 'string' || firstCap.length === 0) {
|
|
475
|
+
return syntheticLibraryFactory;
|
|
476
|
+
}
|
|
477
|
+
const mappings = this.manifest?.capabilityMappings ?? this.manifest?.manifests?.[0]?.capabilityMappings ?? [];
|
|
478
|
+
const mapping = (Array.isArray(mappings) ? mappings : []).find((m) => m?.capability === firstCap);
|
|
479
|
+
const factoryName = mapping?.instanceFactory;
|
|
480
|
+
if (typeof factoryName !== 'string' || factoryName.length === 0) {
|
|
481
|
+
return syntheticLibraryFactory;
|
|
482
|
+
}
|
|
483
|
+
// Look up the factory in the library. If it has v2metadata, use it;
|
|
484
|
+
// otherwise fall back to the synthetic default. Pre-V2 factories without
|
|
485
|
+
// v2metadata can't drive topology-specific emitters; the loader's
|
|
486
|
+
// applyV2MetadataDefaults fills v2metadata for canonical factories, so
|
|
487
|
+
// this fallback is the safety net for malformed manifests.
|
|
488
|
+
const factory = this.library?.get?.(factoryName);
|
|
489
|
+
if (factory && factory.v2metadata) {
|
|
490
|
+
return factory;
|
|
491
|
+
}
|
|
492
|
+
return syntheticLibraryFactory;
|
|
493
|
+
};
|
|
494
|
+
try {
|
|
495
|
+
const { createDependencyResolver } = await import('./resolver/index.js');
|
|
496
|
+
const { dispatchRuntimeEmitter, buildDefaultEmitters } = await import('./runtime-emitters/dispatcher.js');
|
|
497
|
+
const dependencyResolver = createDependencyResolver();
|
|
498
|
+
const runtimeEmitters = await buildDefaultEmitters();
|
|
499
|
+
let importsEmitted = 0;
|
|
500
|
+
// V2 P3 R3 follow-up: collect structured deployment-instance
|
|
501
|
+
// suggestions across every consumer component so the run produces
|
|
502
|
+
// a single aggregated v2-deployment-suggestions.yaml at the
|
|
503
|
+
// top-level outputDir. Library-topology emissions don't contribute.
|
|
504
|
+
const allDeploymentSuggestions = [];
|
|
505
|
+
const componentsList = Array.isArray(spec.components)
|
|
506
|
+
? spec.components
|
|
507
|
+
: Object.values(spec.components ?? {});
|
|
508
|
+
for (const consumerComp of componentsList) {
|
|
509
|
+
const imports = consumerComp.imports ?? [];
|
|
510
|
+
if (!Array.isArray(imports) || imports.length === 0)
|
|
511
|
+
continue;
|
|
512
|
+
// Track per-component imports for the aggregator file (clients/index.ts).
|
|
513
|
+
// Each entry: { from: file path (without .js), specifier: client const name }
|
|
514
|
+
const aggregatorEntries = [];
|
|
515
|
+
const componentName = consumerComp.name ?? 'unknown';
|
|
516
|
+
const componentOutputDir = join(outputDir, 'backend', 'src', componentName);
|
|
517
|
+
for (const importEntry of imports) {
|
|
518
|
+
try {
|
|
519
|
+
const fromName = importEntry.from;
|
|
520
|
+
if (typeof fromName !== 'string' || fromName.length === 0)
|
|
521
|
+
continue;
|
|
522
|
+
const resolved = await dependencyResolver.resolve(fromName, importEntry.version, { spec, offline: !!process.env.SPECVERSE_OFFLINE });
|
|
523
|
+
if (!resolved)
|
|
524
|
+
continue;
|
|
525
|
+
const factory = pickFactoryForImport(resolved);
|
|
526
|
+
const emitter = dispatchRuntimeEmitter(factory, runtimeEmitters);
|
|
527
|
+
const result = await emitter.emit({
|
|
528
|
+
consumerComponent: consumerComp,
|
|
529
|
+
importEntry: {
|
|
530
|
+
from: fromName,
|
|
531
|
+
version: importEntry.version,
|
|
532
|
+
select: importEntry.select ?? [],
|
|
533
|
+
},
|
|
534
|
+
resolved,
|
|
535
|
+
factory,
|
|
536
|
+
outputDir: componentOutputDir,
|
|
537
|
+
spec: spec,
|
|
538
|
+
});
|
|
539
|
+
for (const file of result.files) {
|
|
540
|
+
const filePath = join(componentOutputDir, file.path);
|
|
541
|
+
writeOutput({ filePath, code: file.content });
|
|
542
|
+
importsEmitted += 1;
|
|
543
|
+
}
|
|
544
|
+
// Collect the consumer-import declarations the emitter returned so
|
|
545
|
+
// we can write the per-component clients/index.ts aggregator below.
|
|
546
|
+
for (const consumerImport of (result.imports ?? [])) {
|
|
547
|
+
aggregatorEntries.push(consumerImport);
|
|
548
|
+
}
|
|
549
|
+
// Collect structured deployment-instance suggestion (V2 P3 R3
|
|
550
|
+
// follow-up). Library emitters omit this field; only service /
|
|
551
|
+
// managed / executable / messaging contribute.
|
|
552
|
+
if (result.deploymentSuggestion) {
|
|
553
|
+
allDeploymentSuggestions.push(result.deploymentSuggestion);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
catch (importErr) {
|
|
557
|
+
const compName = consumerComp.name ?? '<unknown>';
|
|
558
|
+
const fromName = importEntry.from ?? '<unknown>';
|
|
559
|
+
errors.push(`V2 import ${compName}.${fromName}: ${importErr.message}`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
// Per-component aggregator: clients/index.ts re-exports every V2
|
|
563
|
+
// client emitted for this consumer + a combined `v2Clients` constant
|
|
564
|
+
// so consumers can `import { v2Clients } from './clients'` and spread
|
|
565
|
+
// it into their deps object. This is the cleanest seam between the
|
|
566
|
+
// V2 emission phase and the per-action emitter (L3 P3) without
|
|
567
|
+
// requiring controller-template surgery — the consumer's existing
|
|
568
|
+
// code just imports + spreads.
|
|
569
|
+
if (aggregatorEntries.length > 0) {
|
|
570
|
+
const lines = [];
|
|
571
|
+
lines.push('// FACTORY-EMITTED — DO NOT EDIT (R8)');
|
|
572
|
+
lines.push('// V2 P3 imports aggregator. Re-exports every emitted client + a combined map.');
|
|
573
|
+
lines.push('// Consumer usage:');
|
|
574
|
+
lines.push('// import { v2Clients } from \'./clients\';');
|
|
575
|
+
lines.push('// const deps = { ...existing, ...v2Clients };');
|
|
576
|
+
lines.push('');
|
|
577
|
+
// Static imports.
|
|
578
|
+
for (const entry of aggregatorEntries) {
|
|
579
|
+
const localPath = entry.from.replace(/^\.\/clients\//, './');
|
|
580
|
+
lines.push(`import { ${entry.specifier} } from '${localPath}.js';`);
|
|
581
|
+
}
|
|
582
|
+
lines.push('');
|
|
583
|
+
// Re-exports.
|
|
584
|
+
for (const entry of aggregatorEntries) {
|
|
585
|
+
lines.push(`export { ${entry.specifier} };`);
|
|
586
|
+
}
|
|
587
|
+
lines.push('');
|
|
588
|
+
// Aggregated map keyed by client name with trailing 'Client' stripped
|
|
589
|
+
// (v2Clients.profitCalculator rather than v2Clients.profitCalculatorClient).
|
|
590
|
+
lines.push('export const v2Clients = {');
|
|
591
|
+
for (const entry of aggregatorEntries) {
|
|
592
|
+
lines.push(` ${stripTrailingClient(entry.specifier)}: ${entry.specifier},`);
|
|
593
|
+
}
|
|
594
|
+
lines.push('} as const;');
|
|
595
|
+
lines.push('');
|
|
596
|
+
lines.push('export type V2Clients = typeof v2Clients;');
|
|
597
|
+
lines.push('');
|
|
598
|
+
const aggregatorPath = join(componentOutputDir, 'clients', 'index.ts');
|
|
599
|
+
writeOutput({ filePath: aggregatorPath, code: lines.join('\n') });
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (importsEmitted > 0) {
|
|
603
|
+
console.log(` ✅ V2 imports: ${importsEmitted} client file(s) emitted`);
|
|
604
|
+
}
|
|
605
|
+
// V2 P3 R3 follow-up: write v2-deployment-suggestions.yaml when any
|
|
606
|
+
// emitter returned a structured suggestion. Auto-merge into the
|
|
607
|
+
// user's deployments block isn't safe (env-specific values, secrets,
|
|
608
|
+
// scaling parameters), so this is review-then-paste material.
|
|
609
|
+
if (allDeploymentSuggestions.length > 0) {
|
|
610
|
+
const yamlBody = yaml.dump({ suggestions: allDeploymentSuggestions }, { indent: 2, lineWidth: 100, noRefs: true, sortKeys: false });
|
|
611
|
+
const header = [
|
|
612
|
+
'# FACTORY-EMITTED — DO NOT EDIT (R8)',
|
|
613
|
+
'# V2 P3 R3 follow-up: deployment-instance suggestions extracted from runtime emitters.',
|
|
614
|
+
'#',
|
|
615
|
+
'# Each entry corresponds to one V2 import that produced a non-library client.',
|
|
616
|
+
'# Merge these into your deployments.<env>.instances.<category> block manually:',
|
|
617
|
+
'#',
|
|
618
|
+
'# deployments:',
|
|
619
|
+
'# production:',
|
|
620
|
+
'# instances:',
|
|
621
|
+
'# <category>:',
|
|
622
|
+
'# <key>:',
|
|
623
|
+
'# <config keys from this file>',
|
|
624
|
+
'#',
|
|
625
|
+
'# Auto-merge is intentionally NOT performed: deployment shape is user-controlled',
|
|
626
|
+
'# (env-specific values, secrets, scaling parameters). Review each suggestion before',
|
|
627
|
+
'# pasting; placeholder markers like <url> indicate values you must supply.',
|
|
628
|
+
'',
|
|
629
|
+
].join('\n');
|
|
630
|
+
const suggestionsPath = join(outputDir, 'v2-deployment-suggestions.yaml');
|
|
631
|
+
writeOutput({ filePath: suggestionsPath, code: header + yamlBody });
|
|
632
|
+
console.log(` ✅ V2 deployment suggestions: ${allDeploymentSuggestions.length} entry(ies) → v2-deployment-suggestions.yaml`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
catch (e) {
|
|
636
|
+
errors.push(`V2 imports phase: ${e.message}`);
|
|
637
|
+
}
|
|
381
638
|
// 3. Services
|
|
382
639
|
const servicesList = Array.isArray(spec.services) ? spec.services : Object.values(spec.services || {});
|
|
383
640
|
if (ctrlResolved?.instanceFactory?.codeTemplates?.services) {
|