@pattern-stack/codegen 0.12.1 → 0.13.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/CHANGELOG.md +102 -0
- package/README.md +44 -0
- package/dist/runtime/subsystems/index.d.ts +6 -2
- package/dist/runtime/subsystems/index.js +171 -1
- package/dist/runtime/subsystems/index.js.map +1 -1
- package/dist/runtime/subsystems/integration/detection-config.schema.d.ts +110 -1
- package/dist/runtime/subsystems/integration/detection-config.schema.js +25 -2
- package/dist/runtime/subsystems/integration/detection-config.schema.js.map +1 -1
- package/dist/runtime/subsystems/integration/incremental-read.d.ts +221 -0
- package/dist/runtime/subsystems/integration/incremental-read.js +146 -0
- package/dist/runtime/subsystems/integration/incremental-read.js.map +1 -0
- package/dist/runtime/subsystems/integration/index.d.ts +2 -1
- package/dist/runtime/subsystems/integration/index.js +169 -2
- package/dist/runtime/subsystems/integration/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +1 -1
- package/dist/src/cli/index.js +704 -93
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/index.d.ts +103 -15
- package/dist/src/index.js +54 -36
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
- package/runtime/subsystems/index.ts +34 -0
- package/runtime/subsystems/integration/detection-config.schema.ts +55 -0
- package/runtime/subsystems/integration/incremental-read.ts +345 -0
- package/runtime/subsystems/integration/index.ts +15 -0
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +2 -2
package/dist/src/cli/index.js
CHANGED
|
@@ -30,14 +30,17 @@ import { join, resolve } from "path";
|
|
|
30
30
|
function isYaml(name) {
|
|
31
31
|
return name.endsWith(".yaml") || name.endsWith(".yml");
|
|
32
32
|
}
|
|
33
|
-
function findYamlFiles(dir) {
|
|
33
|
+
function findYamlFiles(dir, opts) {
|
|
34
34
|
const root = resolve(dir);
|
|
35
35
|
const out = [];
|
|
36
|
+
const excluded = new Set((opts?.excludeDirs ?? []).map((d) => resolve(d)));
|
|
36
37
|
const walk = (current) => {
|
|
37
38
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
38
39
|
if (entry.isDirectory()) {
|
|
39
40
|
if (entry.name.startsWith(".")) continue;
|
|
40
|
-
|
|
41
|
+
const child = join(current, entry.name);
|
|
42
|
+
if (excluded.has(resolve(child))) continue;
|
|
43
|
+
walk(child);
|
|
41
44
|
} else if (isYaml(entry.name)) {
|
|
42
45
|
out.push(join(current, entry.name));
|
|
43
46
|
}
|
|
@@ -1117,12 +1120,33 @@ var EventIdCursorSchema = z2.object({
|
|
|
1117
1120
|
kind: z2.literal("eventId"),
|
|
1118
1121
|
field: z2.string().min(1)
|
|
1119
1122
|
});
|
|
1123
|
+
var HistoryIdCursorSchema = z2.object({
|
|
1124
|
+
kind: z2.literal("historyId"),
|
|
1125
|
+
field: z2.string().min(1)
|
|
1126
|
+
});
|
|
1127
|
+
var SyncTokenCursorSchema = z2.object({
|
|
1128
|
+
kind: z2.literal("syncToken"),
|
|
1129
|
+
field: z2.string().min(1)
|
|
1130
|
+
});
|
|
1120
1131
|
var CursorStrategySchema = z2.discriminatedUnion("kind", [
|
|
1121
1132
|
SystemModstampCursorSchema,
|
|
1122
1133
|
ReplayIdCursorSchema,
|
|
1123
1134
|
TimestampCursorSchema,
|
|
1124
|
-
EventIdCursorSchema
|
|
1135
|
+
EventIdCursorSchema,
|
|
1136
|
+
HistoryIdCursorSchema,
|
|
1137
|
+
SyncTokenCursorSchema
|
|
1125
1138
|
]);
|
|
1139
|
+
var CURSOR_DIVISIBILITY = {
|
|
1140
|
+
systemModstamp: true,
|
|
1141
|
+
timestamp: true,
|
|
1142
|
+
replayId: true,
|
|
1143
|
+
eventId: false,
|
|
1144
|
+
historyId: false,
|
|
1145
|
+
syncToken: false
|
|
1146
|
+
};
|
|
1147
|
+
function isDivisibleCursor(kind) {
|
|
1148
|
+
return CURSOR_DIVISIBILITY[kind];
|
|
1149
|
+
}
|
|
1126
1150
|
var PollDetectionSchema = z2.object({
|
|
1127
1151
|
cursor: CursorStrategySchema,
|
|
1128
1152
|
provenance: z2.enum(["poll", "cdc"]).optional()
|
|
@@ -2312,7 +2336,37 @@ var EntityConfigSchema = z3.object({
|
|
|
2312
2336
|
// JOB-7: marks this entity as a valid scope target for job scoping.
|
|
2313
2337
|
// Drives the generated ScopeEntityType union in
|
|
2314
2338
|
// runtime/subsystems/jobs/generated/scope-entity-type.ts.
|
|
2315
|
-
scopeable: z3.boolean().optional()
|
|
2339
|
+
scopeable: z3.boolean().optional(),
|
|
2340
|
+
// RFC-0001 §1/§8: the integration *surface* this entity belongs to
|
|
2341
|
+
// (e.g. 'calendar', 'mail', 'crm'). Surfaces span provider contexts
|
|
2342
|
+
// (ADR-0006) — one Google OAuth feeds calendar+mail+transcript. The union
|
|
2343
|
+
// of `surface:` values across all entity YAML is the closed set that a
|
|
2344
|
+
// provider's `surfaces:` must be a subset of (cross-checked in
|
|
2345
|
+
// src/parser/validate-providers.ts). Optional: entities without an
|
|
2346
|
+
// integration surface omit it. The surface-package *emission* convention
|
|
2347
|
+
// is Track C (#329); this field is only the declarative input both tracks
|
|
2348
|
+
// read. Lives inside the `entity:` block (next to `pattern:`/`name:`/`table:`).
|
|
2349
|
+
surface: z3.string().optional(),
|
|
2350
|
+
// Bounded-context declaration (ADR-0004) — "which bounded context this
|
|
2351
|
+
// entity belongs to". This is the DURABLE decision; it is a plain
|
|
2352
|
+
// bounded-context slug, NOT a folder knob. Different features consume it:
|
|
2353
|
+
//
|
|
2354
|
+
// - #403 (the FIRST consumer): drives the generated code's
|
|
2355
|
+
// module output folder. clean-lite-ps nests the entity's module under
|
|
2356
|
+
// `<modules>/<context>/<entity>/` so same-context entities group
|
|
2357
|
+
// together; untagged entities stay flat (`<modules>/<entity>/`).
|
|
2358
|
+
// - ADR-0004 (deferred): a later `naming: prefix | schema` knob reads
|
|
2359
|
+
// this SAME field to drive the Postgres physical layout —
|
|
2360
|
+
// `prefix` → `pgTable('<context>__<table>')`, then the flip to
|
|
2361
|
+
// `schema` → `pgSchema('<context>').table('<table>')`. NOT wired here.
|
|
2362
|
+
//
|
|
2363
|
+
// Sibling to `surface:` and orthogonal to it (ADR-0006): context = model
|
|
2364
|
+
// cohesion (which domain), surface = vendor composition (which integration).
|
|
2365
|
+
// Lives inside the `entity:` block (next to `pattern:`/`name:`/`table:`).
|
|
2366
|
+
context: z3.string().regex(
|
|
2367
|
+
/^[a-z][a-z0-9_]*$/,
|
|
2368
|
+
"context must be lowercase snake_case (e.g. 'integration')"
|
|
2369
|
+
).optional()
|
|
2316
2370
|
}).strict().refine((d) => !(d.pattern && d.patterns), {
|
|
2317
2371
|
message: "'pattern' and 'patterns' are mutually exclusive"
|
|
2318
2372
|
});
|
|
@@ -2465,36 +2519,11 @@ var EntityDefinitionSchema = z3.object({
|
|
|
2465
2519
|
// appear in `integration.providers` — see the superRefine on
|
|
2466
2520
|
// `EntityDefinitionSchema` below.
|
|
2467
2521
|
detection: z3.record(z3.string(), DetectionConfigSchema).optional(),
|
|
2468
|
-
//
|
|
2469
|
-
//
|
|
2470
|
-
//
|
|
2471
|
-
//
|
|
2472
|
-
//
|
|
2473
|
-
// src/parser/validate-providers.ts). Optional: entities without an
|
|
2474
|
-
// integration surface omit it. The surface-package *emission* convention
|
|
2475
|
-
// is Track C (#329); this field is only the declarative input both tracks
|
|
2476
|
-
// read.
|
|
2477
|
-
surface: z3.string().optional(),
|
|
2478
|
-
// Bounded-context declaration (ADR-0004) — "which bounded context this
|
|
2479
|
-
// entity belongs to". This is the DURABLE decision; it is a plain
|
|
2480
|
-
// bounded-context slug, NOT a folder knob. Different features consume it:
|
|
2481
|
-
//
|
|
2482
|
-
// - #403 (this PR, the FIRST consumer): drives the generated code's
|
|
2483
|
-
// module output folder. clean-lite-ps nests the entity's module under
|
|
2484
|
-
// `<modules>/<context>/<entity>/` so same-context entities group
|
|
2485
|
-
// together; untagged entities stay flat (`<modules>/<entity>/`).
|
|
2486
|
-
// - ADR-0004 (deferred): a later `naming: prefix | schema` knob reads
|
|
2487
|
-
// this SAME field to drive the Postgres physical layout —
|
|
2488
|
-
// `prefix` → `pgTable('<context>__<table>')`, then the flip to
|
|
2489
|
-
// `schema` → `pgSchema('<context>').table('<table>')`. NOT wired here;
|
|
2490
|
-
// #403 makes no table/column/schema changes.
|
|
2491
|
-
//
|
|
2492
|
-
// Sibling to `surface:` and orthogonal to it (ADR-0006): context = model
|
|
2493
|
-
// cohesion (which domain), surface = vendor composition (which integration).
|
|
2494
|
-
context: z3.string().regex(
|
|
2495
|
-
/^[a-z][a-z0-9_]*$/,
|
|
2496
|
-
"context must be lowercase snake_case (e.g. 'integration')"
|
|
2497
|
-
).optional(),
|
|
2522
|
+
// NOTE: `surface:` and `context:` moved INTO EntityConfigSchema (the
|
|
2523
|
+
// `entity:` block) in 0.12.2 — consumers write them next to
|
|
2524
|
+
// `pattern:`/`name:`/`table:`, which is the natural place. They are
|
|
2525
|
+
// read via `entity.surface` / `entity.context`. Clean break: no
|
|
2526
|
+
// root-level placement is accepted.
|
|
2498
2527
|
// v2: Domain event declarations (CODEGEN-EVOLUTION-PLAN Phase 2)
|
|
2499
2528
|
// Generates typed event classes, handlers, and queue registration
|
|
2500
2529
|
events: z3.array(EventDeclarationSchema).optional(),
|
|
@@ -3409,13 +3438,13 @@ function loadErrorToIssue(error) {
|
|
|
3409
3438
|
}
|
|
3410
3439
|
return issues;
|
|
3411
3440
|
}
|
|
3412
|
-
function loadEntities(entitiesDir) {
|
|
3441
|
+
function loadEntities(entitiesDir, opts) {
|
|
3413
3442
|
const entities = [];
|
|
3414
3443
|
const issues = [];
|
|
3415
3444
|
const resolvedDir = resolve2(entitiesDir);
|
|
3416
3445
|
let files;
|
|
3417
3446
|
try {
|
|
3418
|
-
files = findYamlFiles(resolvedDir);
|
|
3447
|
+
files = findYamlFiles(resolvedDir, { excludeDirs: opts?.excludeDirs });
|
|
3419
3448
|
} catch (err) {
|
|
3420
3449
|
issues.push({
|
|
3421
3450
|
severity: "error",
|
|
@@ -3650,7 +3679,7 @@ import ts from "typescript";
|
|
|
3650
3679
|
function collectEntitySurfaces(entities) {
|
|
3651
3680
|
const surfaces = /* @__PURE__ */ new Set();
|
|
3652
3681
|
for (const e of entities) {
|
|
3653
|
-
if (e.surface) surfaces.add(e.surface);
|
|
3682
|
+
if (e.entity.surface) surfaces.add(e.entity.surface);
|
|
3654
3683
|
}
|
|
3655
3684
|
return surfaces;
|
|
3656
3685
|
}
|
|
@@ -6019,8 +6048,8 @@ function collectEntities(entitiesDir) {
|
|
|
6019
6048
|
entities.push({
|
|
6020
6049
|
name: def.entity.name,
|
|
6021
6050
|
plural: def.entity.plural,
|
|
6022
|
-
// #403:
|
|
6023
|
-
context: def.context
|
|
6051
|
+
// #403: `entity.context:` nests the module folder; undefined → flat.
|
|
6052
|
+
context: def.entity.context
|
|
6024
6053
|
});
|
|
6025
6054
|
}
|
|
6026
6055
|
entities.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -8233,6 +8262,310 @@ import {
|
|
|
8233
8262
|
writeFileSync as writeFileSync3
|
|
8234
8263
|
} from "fs";
|
|
8235
8264
|
import { dirname as dirname2, join as join12 } from "path";
|
|
8265
|
+
|
|
8266
|
+
// src/cli/shared/sink-emission-generator.ts
|
|
8267
|
+
var SCAFFOLD_SENTINEL = "// <CODEGEN-SCAFFOLD-V1>";
|
|
8268
|
+
var USER_ID_FIELD = "userId";
|
|
8269
|
+
function sinkNames(entityClass) {
|
|
8270
|
+
return {
|
|
8271
|
+
sinkClass: `${entityClass}Sink`,
|
|
8272
|
+
canonicalType: `${entityClass}Canonical`,
|
|
8273
|
+
repoClass: `${entityClass}Repository`,
|
|
8274
|
+
projectionType: `${entityClass}IntegrationProjection`,
|
|
8275
|
+
writeType: `${entityClass}IntegrationWrite`
|
|
8276
|
+
};
|
|
8277
|
+
}
|
|
8278
|
+
function generateDefaultSink(input) {
|
|
8279
|
+
if (input.pattern !== "Integrated") {
|
|
8280
|
+
throw new Error(
|
|
8281
|
+
`cannot emit default integration sink for entity '${input.entityName}': it is 'pattern: ${input.pattern}', but the default sink is emittable only for 'pattern: Integrated' entities (the only family with the integrationUpsertOne / findByExternalIdProjected projection path). Add 'pattern: Integrated' to the entity or provide a hand-authored sink.`
|
|
8282
|
+
);
|
|
8283
|
+
}
|
|
8284
|
+
const n = sinkNames(input.entityClass);
|
|
8285
|
+
const hasUserIdField = input.copyThroughFields.some(
|
|
8286
|
+
(f) => f.camelName === USER_ID_FIELD
|
|
8287
|
+
);
|
|
8288
|
+
const copyThroughLines = input.copyThroughFields.filter((f) => f.camelName !== USER_ID_FIELD).map((f) => ` ${f.camelName}: record.${f.camelName},`);
|
|
8289
|
+
const fkTodoLines = input.fkExternalKeys.map(
|
|
8290
|
+
(fk) => ` // ${fk.writeKey}: /* TODO(author): external id of the related ${relationLabel(fk.writeKey)} */ null,`
|
|
8291
|
+
);
|
|
8292
|
+
const writeBodyLines = [
|
|
8293
|
+
` externalId: record.externalId,`
|
|
8294
|
+
];
|
|
8295
|
+
if (copyThroughLines.length > 0) {
|
|
8296
|
+
writeBodyLines.push(
|
|
8297
|
+
` // copy-through fields (one line per \`fields:\` entry):`,
|
|
8298
|
+
...copyThroughLines
|
|
8299
|
+
);
|
|
8300
|
+
}
|
|
8301
|
+
if (fkTodoLines.length > 0) {
|
|
8302
|
+
writeBodyLines.push(
|
|
8303
|
+
` // FK external join-keys \u2014 projection has no external key; supply from your canonical record:`,
|
|
8304
|
+
...fkTodoLines
|
|
8305
|
+
);
|
|
8306
|
+
}
|
|
8307
|
+
if (hasUserIdField) {
|
|
8308
|
+
writeBodyLines.push(` userId,`);
|
|
8309
|
+
}
|
|
8310
|
+
const writeBody = writeBodyLines.join("\n");
|
|
8311
|
+
return `${SCAFFOLD_SENTINEL}
|
|
8312
|
+
// Scaffolded once by @pattern-stack/codegen, then author-owned. Re-running codegen
|
|
8313
|
+
// detects the sentinel above and SKIPS this file \u2014 your edits are safe.
|
|
8314
|
+
//
|
|
8315
|
+
// Default IIntegrationSink over the generated ${n.repoClass}. The PLUMBING
|
|
8316
|
+
// (constructor, provider-match assert, repo delegation, userId scoping, return
|
|
8317
|
+
// shapes) is generated. The canonical<->local FIELD MAPPING is the author seam:
|
|
8318
|
+
// the canonical type is whatever your adapter's changeSource yields \u2014 the same
|
|
8319
|
+
// seam as the IChangeSource.listChanges fetch body. For FK-free entities the
|
|
8320
|
+
// generated ${n.projectionType} IS the canonical shape (passthrough);
|
|
8321
|
+
// for entities with external FK join-keys, fill the marked TODO(s) below.
|
|
8322
|
+
// Source: definitions entity '${input.entityName}' (surface: ${input.surface}).
|
|
8323
|
+
import { Injectable } from '@nestjs/common';
|
|
8324
|
+
import type { IIntegrationSink } from '@pattern-stack/codegen/subsystems';
|
|
8325
|
+
import {
|
|
8326
|
+
${n.repoClass},
|
|
8327
|
+
type ${n.projectionType},
|
|
8328
|
+
type ${n.writeType},
|
|
8329
|
+
} from '${input.repoImportSpecifier}';
|
|
8330
|
+
|
|
8331
|
+
/** Canonical type the orchestrator diffs. Defaults to the generated projection;
|
|
8332
|
+
* widen to your adapter's canonical shape if it carries fields the projection
|
|
8333
|
+
* does not (e.g. external FK join-keys). */
|
|
8334
|
+
export type ${n.canonicalType} = ${n.projectionType};
|
|
8335
|
+
|
|
8336
|
+
@Injectable()
|
|
8337
|
+
export class ${n.sinkClass} implements IIntegrationSink<${n.canonicalType}> {
|
|
8338
|
+
constructor(
|
|
8339
|
+
private readonly repo: ${n.repoClass},
|
|
8340
|
+
private readonly provider: string,
|
|
8341
|
+
) {}
|
|
8342
|
+
|
|
8343
|
+
async findByExternalId(userId: string, externalId: string): Promise<${n.canonicalType} | null> {
|
|
8344
|
+
const row = await this.repo.findByExternalIdProjected(externalId, this.provider);
|
|
8345
|
+
// The repo lookup is (provider, externalId)-scoped. If your external_id is not
|
|
8346
|
+
// globally unique, enforce ownership here (e.g. row.userId === userId).
|
|
8347
|
+
return row;
|
|
8348
|
+
}
|
|
8349
|
+
|
|
8350
|
+
async upsertByExternalId(
|
|
8351
|
+
userId: string,
|
|
8352
|
+
record: ${n.canonicalType},
|
|
8353
|
+
provider: string,
|
|
8354
|
+
): Promise<{ id: string; saved: ${n.canonicalType} }> {
|
|
8355
|
+
if (provider !== this.provider) {
|
|
8356
|
+
throw new Error(\`${n.sinkClass}: bound provider '\${this.provider}' != run provider '\${provider}'\`);
|
|
8357
|
+
}
|
|
8358
|
+
const write: ${n.writeType} = {
|
|
8359
|
+
${writeBody}
|
|
8360
|
+
};
|
|
8361
|
+
const proj = await this.repo.integrationUpsertOne(write, this.provider);
|
|
8362
|
+
return { id: proj.id, saved: record };
|
|
8363
|
+
}
|
|
8364
|
+
|
|
8365
|
+
async softDeleteByExternalId(_userId: string, externalId: string): Promise<{ id: string } | null> {
|
|
8366
|
+
return this.repo.softDeleteByExternalId(externalId, this.provider);
|
|
8367
|
+
}
|
|
8368
|
+
}
|
|
8369
|
+
`;
|
|
8370
|
+
}
|
|
8371
|
+
function relationLabel(writeKey) {
|
|
8372
|
+
const stripped = writeKey.replace(/ExternalId$/, "");
|
|
8373
|
+
return stripped || "related entity";
|
|
8374
|
+
}
|
|
8375
|
+
|
|
8376
|
+
// src/cli/shared/assembly-emission-generator.ts
|
|
8377
|
+
import { relative, resolve as resolve6, sep } from "path";
|
|
8378
|
+
function generatedBanner(sourceDesc) {
|
|
8379
|
+
return `// @generated by @pattern-stack/codegen from ${sourceDesc} \u2014 DO NOT EDIT.
|
|
8380
|
+
// Hand edits are overwritten on re-emit. Regenerate with \`bun run codegen\`.`;
|
|
8381
|
+
}
|
|
8382
|
+
function entityPascalCase(name) {
|
|
8383
|
+
return name.split("_").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
8384
|
+
}
|
|
8385
|
+
function entityConstantCase(name) {
|
|
8386
|
+
return name.replace(/-/g, "_").toUpperCase();
|
|
8387
|
+
}
|
|
8388
|
+
function integrationUseCaseToken(entityName, providerSlug) {
|
|
8389
|
+
return `${entityConstantCase(entityName)}_INTEGRATION_USE_CASE__${providerConstantCase(providerSlug)}`;
|
|
8390
|
+
}
|
|
8391
|
+
function assemblyModuleClass(entityName, providerSlug) {
|
|
8392
|
+
return `${entityPascalCase(entityName)}IntegrationModule__${providerPascalCase(providerSlug)}`;
|
|
8393
|
+
}
|
|
8394
|
+
function tokenSymbolKey(surface, entityName, providerSlug) {
|
|
8395
|
+
return `@app/integrations/${surface}.${entityName}-integration-use-case.${providerSlug}`;
|
|
8396
|
+
}
|
|
8397
|
+
function generateAssemblyModule(input) {
|
|
8398
|
+
const providerPascal = providerPascalCase(input.provider);
|
|
8399
|
+
const surfacePascal = providerPascalCase(input.surface);
|
|
8400
|
+
const adapterClass = `${providerPascal}${surfacePascal}Adapter`;
|
|
8401
|
+
const adapterModuleClass = `${adapterClass}Module`;
|
|
8402
|
+
const adapterImport = `../../adapters/${input.provider}/${input.provider}-${input.surface}.adapter`;
|
|
8403
|
+
const adapterModuleImport = `${adapterImport}.module`;
|
|
8404
|
+
const sinkClass = `${input.entityClass}Sink`;
|
|
8405
|
+
const sinkImport = `../../sinks/${input.entityName}.sink`;
|
|
8406
|
+
const token = integrationUseCaseToken(input.entityName, input.provider);
|
|
8407
|
+
const moduleClass = assemblyModuleClass(input.entityName, input.provider);
|
|
8408
|
+
const tokensImport = `../../${input.surface}-integration.tokens`;
|
|
8409
|
+
return `${generatedBanner(input.sourceDesc)}
|
|
8410
|
+
import { Module } from '@nestjs/common';
|
|
8411
|
+
import {
|
|
8412
|
+
ExecuteIntegrationUseCase,
|
|
8413
|
+
INTEGRATION_CHANGE_SOURCE,
|
|
8414
|
+
INTEGRATION_SINK,
|
|
8415
|
+
} from '@pattern-stack/codegen/subsystems';
|
|
8416
|
+
import { ${adapterClass} } from '${adapterImport}';
|
|
8417
|
+
import { ${adapterModuleClass} } from '${adapterModuleImport}';
|
|
8418
|
+
import { ${sinkClass} } from '${sinkImport}';
|
|
8419
|
+
import { ${input.repoClass} } from '${input.repoImportSpecifier}';
|
|
8420
|
+
import { ${input.moduleClass} } from '${input.moduleImportSpecifier}';
|
|
8421
|
+
import { ${token} } from '${tokensImport}';
|
|
8422
|
+
|
|
8423
|
+
/**
|
|
8424
|
+
* ${moduleClass} \u2014 the ${input.surface}/${input.entityName} \u2190 ${input.provider}
|
|
8425
|
+
* inbound-integration assembly (RFC-0002 \xA72, Option A).
|
|
8426
|
+
*
|
|
8427
|
+
* Binds this module's INTEGRATION_CHANGE_SOURCE from the adapter's
|
|
8428
|
+
* \`changeSources['${input.entityName}']\` and INTEGRATION_SINK from
|
|
8429
|
+
* ${sinkClass}, provides a local ExecuteIntegrationUseCase, and aliases+exports
|
|
8430
|
+
* it under ${token} (the bare class token is ambiguous at app root \u2014 every
|
|
8431
|
+
* assembly provides it). The substrate (cursor store, run recorder, differ,
|
|
8432
|
+
* multi-tenant flag) comes from the global IntegrationModule.forRoot(...) in
|
|
8433
|
+
* AppModule, never re-bound here.
|
|
8434
|
+
*/
|
|
8435
|
+
@Module({
|
|
8436
|
+
imports: [${input.moduleClass}, ${adapterModuleClass}],
|
|
8437
|
+
providers: [
|
|
8438
|
+
{
|
|
8439
|
+
provide: INTEGRATION_CHANGE_SOURCE,
|
|
8440
|
+
useFactory: (adapter: ${adapterClass}) => adapter.changeSources['${input.entityName}'],
|
|
8441
|
+
inject: [${adapterClass}],
|
|
8442
|
+
},
|
|
8443
|
+
{
|
|
8444
|
+
provide: INTEGRATION_SINK,
|
|
8445
|
+
useFactory: (repo: ${input.repoClass}) => new ${sinkClass}(repo, '${input.provider}'),
|
|
8446
|
+
inject: [${input.repoClass}],
|
|
8447
|
+
},
|
|
8448
|
+
ExecuteIntegrationUseCase,
|
|
8449
|
+
{ provide: ${token}, useExisting: ExecuteIntegrationUseCase },
|
|
8450
|
+
],
|
|
8451
|
+
exports: [${token}],
|
|
8452
|
+
})
|
|
8453
|
+
export class ${moduleClass} {}
|
|
8454
|
+
`;
|
|
8455
|
+
}
|
|
8456
|
+
function generateIntegrationTokens(surface, entries) {
|
|
8457
|
+
const sorted = [...entries].sort(
|
|
8458
|
+
(a, b) => a.entityName === b.entityName ? a.provider < b.provider ? -1 : 1 : a.entityName < b.entityName ? -1 : 1
|
|
8459
|
+
);
|
|
8460
|
+
const tokenLines = sorted.map((e) => {
|
|
8461
|
+
const token = integrationUseCaseToken(e.entityName, e.provider);
|
|
8462
|
+
const key = tokenSymbolKey(surface, e.entityName, e.provider);
|
|
8463
|
+
return `/** Unique handle for the ${e.entityName} \u2190 ${e.provider} integration use-case
|
|
8464
|
+
* (${assemblyModuleClass(e.entityName, e.provider)}). A trigger grabs this to run it. */
|
|
8465
|
+
export const ${token} = Symbol.for('${key}');`;
|
|
8466
|
+
}).join("\n\n");
|
|
8467
|
+
return `${generatedBanner(`surface: ${surface}`)}
|
|
8468
|
+
/**
|
|
8469
|
+
* Use-case handles for the \`${surface}\` surface \u2014 one per (entity, provider)
|
|
8470
|
+
* assembly. Each is a Symbol \`provide:\` token a per-entity
|
|
8471
|
+
* \`ExecuteIntegrationUseCase\` is aliased under (the bare class token is
|
|
8472
|
+
* ambiguous at app root) and exported for the consuming trigger to grab.
|
|
8473
|
+
*/
|
|
8474
|
+
|
|
8475
|
+
${tokenLines || "// no (entity, provider) integration assemblies on this surface yet"}
|
|
8476
|
+
`;
|
|
8477
|
+
}
|
|
8478
|
+
function generateIntegrationAggregator(surface, entries) {
|
|
8479
|
+
const surfacePascal = providerPascalCase(surface);
|
|
8480
|
+
const aggregatorClass = `${surfacePascal}IntegrationModule`;
|
|
8481
|
+
const sorted = [...entries].sort(
|
|
8482
|
+
(a, b) => a.provider === b.provider ? a.entityName < b.entityName ? -1 : 1 : a.provider < b.provider ? -1 : 1
|
|
8483
|
+
);
|
|
8484
|
+
const moduleClasses = sorted.map(
|
|
8485
|
+
(e) => assemblyModuleClass(e.entityName, e.provider)
|
|
8486
|
+
);
|
|
8487
|
+
const importLines = sorted.map((e) => {
|
|
8488
|
+
const cls = assemblyModuleClass(e.entityName, e.provider);
|
|
8489
|
+
const path34 = `./modules/${e.provider}/${e.entityName}-integration.module`;
|
|
8490
|
+
return `import { ${cls} } from '${path34}';`;
|
|
8491
|
+
}).join("\n");
|
|
8492
|
+
const membersInline = moduleClasses.join(", ");
|
|
8493
|
+
return `${generatedBanner(`surface: ${surface}`)}
|
|
8494
|
+
import { Module } from '@nestjs/common';
|
|
8495
|
+
${importLines || "// no (entity, provider) integration assemblies on this surface yet"}
|
|
8496
|
+
|
|
8497
|
+
/**
|
|
8498
|
+
* ${aggregatorClass} \u2014 the \`${surface}\` integration aggregator. AppModule
|
|
8499
|
+
* imports THIS (not ${surfacePascal}AdaptersModule) to wire every per-entity
|
|
8500
|
+
* integration on the surface. Re-exports each assembly module so their
|
|
8501
|
+
* <ENTITY>_INTEGRATION_USE_CASE__<PROVIDER> tokens are available to the app (the
|
|
8502
|
+
* trigger/consumer grabs them). Per RFC-0002 \xA77 q3, the run path goes through
|
|
8503
|
+
* these assembly modules, keeping the collision-prone ${surfacePascal}AdaptersModule
|
|
8504
|
+
* fold off the graph.
|
|
8505
|
+
*/
|
|
8506
|
+
@Module({
|
|
8507
|
+
imports: [${membersInline}],
|
|
8508
|
+
exports: [${membersInline}],
|
|
8509
|
+
})
|
|
8510
|
+
export class ${aggregatorClass} {}
|
|
8511
|
+
`;
|
|
8512
|
+
}
|
|
8513
|
+
function pluralPascalCase(plural) {
|
|
8514
|
+
return plural.split("_").filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
8515
|
+
}
|
|
8516
|
+
function toImportSpecifier(targetFileAbs, fromDirAbs, aliases) {
|
|
8517
|
+
const noExt = targetFileAbs.replace(/\.ts$/, "");
|
|
8518
|
+
let best = null;
|
|
8519
|
+
for (const [key, dirRaw] of Object.entries(aliases)) {
|
|
8520
|
+
const dir = dirRaw.endsWith(sep) ? dirRaw.slice(0, -1) : dirRaw;
|
|
8521
|
+
const prefix = dir + sep;
|
|
8522
|
+
if (noExt === dir || noExt.startsWith(prefix)) {
|
|
8523
|
+
const rest = noExt === dir ? "" : noExt.slice(prefix.length);
|
|
8524
|
+
if (!best || dir.length > aliases[best.key].length) {
|
|
8525
|
+
best = { key, rest };
|
|
8526
|
+
}
|
|
8527
|
+
}
|
|
8528
|
+
}
|
|
8529
|
+
if (best) {
|
|
8530
|
+
const restPosix = best.rest.split(sep).join("/");
|
|
8531
|
+
return restPosix ? `${best.key}/${restPosix}` : best.key;
|
|
8532
|
+
}
|
|
8533
|
+
let rel2 = relative(fromDirAbs, noExt).split(sep).join("/");
|
|
8534
|
+
if (!rel2.startsWith(".")) rel2 = `./${rel2}`;
|
|
8535
|
+
return rel2;
|
|
8536
|
+
}
|
|
8537
|
+
function resolveEntityModuleImports(input) {
|
|
8538
|
+
const entityClass = entityPascalCase(input.entityName);
|
|
8539
|
+
const repoClass = `${entityClass}Repository`;
|
|
8540
|
+
const moduleClass = `${pluralPascalCase(input.entityPlural)}Module`;
|
|
8541
|
+
const moduleGroupSegs = input.context ? ["modules", input.context, input.entityPlural] : ["modules", input.entityPlural];
|
|
8542
|
+
const repoFileAbs = resolve6(
|
|
8543
|
+
input.backendSrcAbs,
|
|
8544
|
+
...moduleGroupSegs,
|
|
8545
|
+
`${input.entityName}.repository.ts`
|
|
8546
|
+
);
|
|
8547
|
+
const moduleFileAbs = resolve6(
|
|
8548
|
+
input.backendSrcAbs,
|
|
8549
|
+
...moduleGroupSegs,
|
|
8550
|
+
`${input.entityPlural}.module.ts`
|
|
8551
|
+
);
|
|
8552
|
+
const assemblyDirAbs = resolve6(
|
|
8553
|
+
input.backendSrcAbs,
|
|
8554
|
+
"integrations",
|
|
8555
|
+
input.surface,
|
|
8556
|
+
"modules",
|
|
8557
|
+
input.provider
|
|
8558
|
+
);
|
|
8559
|
+
return {
|
|
8560
|
+
entityClass,
|
|
8561
|
+
repoClass,
|
|
8562
|
+
moduleClass,
|
|
8563
|
+
repoImportSpecifier: toImportSpecifier(repoFileAbs, assemblyDirAbs, input.aliases),
|
|
8564
|
+
moduleImportSpecifier: toImportSpecifier(moduleFileAbs, assemblyDirAbs, input.aliases)
|
|
8565
|
+
};
|
|
8566
|
+
}
|
|
8567
|
+
|
|
8568
|
+
// src/cli/shared/adapter-emission-generator.ts
|
|
8236
8569
|
var SURFACE_REGISTRY = {
|
|
8237
8570
|
crm: {
|
|
8238
8571
|
packageName: "@pattern-stack/codegen-crm",
|
|
@@ -8272,35 +8605,39 @@ var SURFACE_REGISTRY = {
|
|
|
8272
8605
|
portType: "CalendarPort",
|
|
8273
8606
|
capabilitiesType: "CalendarCapabilities",
|
|
8274
8607
|
noCapsConst: "NO_CALENDAR_CAPABILITIES",
|
|
8275
|
-
l2Ports: []
|
|
8608
|
+
l2Ports: [],
|
|
8609
|
+
readPrimitive: true
|
|
8276
8610
|
},
|
|
8277
8611
|
mail: {
|
|
8278
8612
|
packageName: "@pattern-stack/codegen-mail",
|
|
8279
8613
|
portType: "MailPort",
|
|
8280
8614
|
capabilitiesType: "MailCapabilities",
|
|
8281
8615
|
noCapsConst: "NO_MAIL_CAPABILITIES",
|
|
8282
|
-
l2Ports: []
|
|
8616
|
+
l2Ports: [],
|
|
8617
|
+
readPrimitive: true
|
|
8283
8618
|
},
|
|
8284
8619
|
transcript: {
|
|
8285
8620
|
packageName: "@pattern-stack/codegen-transcript",
|
|
8286
8621
|
portType: "TranscriptPort",
|
|
8287
8622
|
capabilitiesType: "TranscriptCapabilities",
|
|
8288
8623
|
noCapsConst: "NO_TRANSCRIPT_CAPABILITIES",
|
|
8289
|
-
l2Ports: []
|
|
8624
|
+
l2Ports: [],
|
|
8625
|
+
readPrimitive: true
|
|
8290
8626
|
}
|
|
8291
8627
|
};
|
|
8292
|
-
var
|
|
8293
|
-
function
|
|
8628
|
+
var SCAFFOLD_SENTINEL2 = "// <CODEGEN-SCAFFOLD-V1>";
|
|
8629
|
+
function generatedBanner2(sourceDesc) {
|
|
8294
8630
|
return `// @generated by @pattern-stack/codegen from ${sourceDesc} \u2014 DO NOT EDIT.
|
|
8295
8631
|
// Hand edits are overwritten on re-emit. Regenerate with \`bun run codegen\`.`;
|
|
8296
8632
|
}
|
|
8297
8633
|
function collectEntitiesBySurface(entities) {
|
|
8298
8634
|
const bySurface = /* @__PURE__ */ new Map();
|
|
8299
8635
|
for (const e of entities) {
|
|
8300
|
-
|
|
8301
|
-
|
|
8636
|
+
const surface = e.entity.surface;
|
|
8637
|
+
if (!surface) continue;
|
|
8638
|
+
const list = bySurface.get(surface) ?? [];
|
|
8302
8639
|
list.push(e.entity.name);
|
|
8303
|
-
bySurface.set(
|
|
8640
|
+
bySurface.set(surface, list);
|
|
8304
8641
|
}
|
|
8305
8642
|
for (const [surface, list] of bySurface) {
|
|
8306
8643
|
bySurface.set(surface, [...list].sort());
|
|
@@ -8327,17 +8664,139 @@ function names(providerSlug, surface) {
|
|
|
8327
8664
|
entitySourcesToken: `${surfaceConst}_ENTITY_SOURCES`
|
|
8328
8665
|
};
|
|
8329
8666
|
}
|
|
8330
|
-
function
|
|
8667
|
+
function entityPascalCase2(name) {
|
|
8668
|
+
return name.split(/[-_]/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
8669
|
+
}
|
|
8670
|
+
function entityConstCase(name) {
|
|
8671
|
+
return name.replace(/-/g, "_").toUpperCase();
|
|
8672
|
+
}
|
|
8673
|
+
function serializeFilterArray(filters) {
|
|
8674
|
+
if (filters.length === 0) return "[]";
|
|
8675
|
+
const items = filters.map(
|
|
8676
|
+
(f) => ` { field: ${JSON.stringify(f.field)}, op: ${JSON.stringify(f.op)}, value: ${JSON.stringify(f.value)} },`
|
|
8677
|
+
);
|
|
8678
|
+
return `[
|
|
8679
|
+
${items.join("\n")}
|
|
8680
|
+
]`;
|
|
8681
|
+
}
|
|
8682
|
+
function buildReadPrimitiveEmission(providerSlug, providerPascal, surface, entities, entityDetection, clientExportName) {
|
|
8683
|
+
const canonicalTypes = [];
|
|
8684
|
+
const blocks = [];
|
|
8685
|
+
const entries = [];
|
|
8686
|
+
for (const entity of entities) {
|
|
8687
|
+
const pascal = entityPascalCase2(entity);
|
|
8688
|
+
const canonical = `Canonical${pascal}`;
|
|
8689
|
+
const className = `${providerPascal}${pascal}IncrementalRead`;
|
|
8690
|
+
const constName = `${entityConstCase(entity)}_DETECTION_FILTERS`;
|
|
8691
|
+
canonicalTypes.push(canonical);
|
|
8692
|
+
const det = entityDetection?.get(entity);
|
|
8693
|
+
const filters = det?.filters ?? [];
|
|
8694
|
+
const cursorKind = det && det.mode === "poll" ? det.poll.cursor.kind : void 0;
|
|
8695
|
+
const atomic = cursorKind !== void 0 && !isDivisibleCursor(cursorKind);
|
|
8696
|
+
const cursorOverride = atomic ? `
|
|
8697
|
+
// \`${cursorKind}\` is an ATOMIC cursor (RFC-0003 \xA73): its next value only exists
|
|
8698
|
+
// at end-of-walk, so per-ref cursors are withheld and only the final record
|
|
8699
|
+
// carries the token \u2014 a mid-walk crash never persists an unresumable value.
|
|
8700
|
+
protected override readonly cursorDivisible = false;` : "";
|
|
8701
|
+
blocks.push(`/**
|
|
8702
|
+
* \`detection.filters\` for \`${entity}\`, emitted from YAML as a static
|
|
8703
|
+
* \`ResolvedFilter[]\` (RFC-0003 \xA74 fork #2); \`filterFor()\` returns it.
|
|
8704
|
+
*/
|
|
8705
|
+
const ${constName}: ResolvedFilter[] = ${serializeFilterArray(filters)};
|
|
8706
|
+
|
|
8707
|
+
// Emit-once read primitive (author-owned). Fill the three vendor methods below.
|
|
8708
|
+
export class ${className} extends IncrementalReadBase<${canonical}, ResolvedFilter[]> {
|
|
8709
|
+
readonly label = '${providerSlug}-${surface}-${entity}';
|
|
8710
|
+
// Flip to \`true\` if your \`enumerate\` pushes the request filter to the vendor
|
|
8711
|
+
// (e.g. Gmail \`q=\`); leave \`false\` to filter post-hydrate via \`matchesRecord\`.
|
|
8712
|
+
protected override readonly filterPushdown = false;${cursorOverride}
|
|
8713
|
+
|
|
8714
|
+
constructor(
|
|
8715
|
+
private readonly auth: IAuthStrategy,
|
|
8716
|
+
private readonly client: ${clientExportName},
|
|
8717
|
+
) {
|
|
8718
|
+
super();
|
|
8719
|
+
}
|
|
8720
|
+
|
|
8721
|
+
/** TODO: walk the vendor list endpoint \u2192 pages of \`Ref\` (id + cursor + meta). */
|
|
8722
|
+
protected async *enumerate(
|
|
8723
|
+
_mode: ReadMode,
|
|
8724
|
+
_filter?: ResolvedFilter[],
|
|
8725
|
+
_pageSize?: number,
|
|
8726
|
+
): AsyncIterable<Ref[]> {
|
|
8727
|
+
throw new Error('not implemented: ${className}.enumerate');
|
|
8728
|
+
}
|
|
8729
|
+
|
|
8730
|
+
/** TODO: batched fetch-by-id \u2192 \`Map<id, raw>\` (\`mapConcurrent\`, or a vendor /batch). */
|
|
8731
|
+
protected async hydrate(_ids: string[]): Promise<Map<string, unknown>> {
|
|
8732
|
+
throw new Error('not implemented: ${className}.hydrate');
|
|
8733
|
+
}
|
|
8734
|
+
|
|
8735
|
+
/** TODO: vendor payload \u2192 \`${canonical}\` (return \`null\` to drop). */
|
|
8736
|
+
protected toCanonical(_raw: unknown): ${canonical} | null {
|
|
8737
|
+
throw new Error('not implemented: ${className}.toCanonical');
|
|
8738
|
+
}
|
|
8739
|
+
|
|
8740
|
+
protected override filterFor(
|
|
8741
|
+
_subscription: IntegrationSubscriptionView,
|
|
8742
|
+
): ResolvedFilter[] {
|
|
8743
|
+
return ${constName};
|
|
8744
|
+
}
|
|
8745
|
+
}`);
|
|
8746
|
+
entries.push(` ${entity}: new ${className}(this.auth, this.client),`);
|
|
8747
|
+
}
|
|
8748
|
+
return { canonicalTypes, preamble: blocks.join("\n\n"), changeSourceEntries: entries };
|
|
8749
|
+
}
|
|
8750
|
+
function generateAdapterScaffold(def, surface, entities, entityDetection) {
|
|
8331
8751
|
const spec = SURFACE_REGISTRY[surface];
|
|
8332
8752
|
if (!spec) throw new Error(`no surface package for '${surface}'`);
|
|
8333
8753
|
const n = names(def.slug, surface);
|
|
8334
8754
|
const client = parseImportRef(def.client.class);
|
|
8335
8755
|
const entitiesLiteral = entities.length ? `[${entities.map((e) => `'${e}'`).join(", ")}]` : "[]";
|
|
8756
|
+
const readPrimitive = !!spec.readPrimitive && entities.length > 0;
|
|
8757
|
+
const rp = readPrimitive ? buildReadPrimitiveEmission(
|
|
8758
|
+
def.slug,
|
|
8759
|
+
n.providerPascal,
|
|
8760
|
+
surface,
|
|
8761
|
+
entities,
|
|
8762
|
+
entityDetection,
|
|
8763
|
+
client.exportName
|
|
8764
|
+
) : null;
|
|
8336
8765
|
const surfaceTypeImports = [
|
|
8337
8766
|
spec.portType,
|
|
8338
8767
|
...spec.l2Ports.map((p) => p.type),
|
|
8339
|
-
spec.capabilitiesType
|
|
8768
|
+
spec.capabilitiesType,
|
|
8769
|
+
...rp ? rp.canonicalTypes : []
|
|
8340
8770
|
].map((t) => ` ${t},`).join("\n");
|
|
8771
|
+
const subsystemValueImport = rp ? `import { IncrementalReadBase } from '@pattern-stack/codegen/subsystems';
|
|
8772
|
+
` : "";
|
|
8773
|
+
const subsystemTypeImports = [
|
|
8774
|
+
"IAuthStrategy",
|
|
8775
|
+
"IChangeSource",
|
|
8776
|
+
...rp ? ["IntegrationSubscriptionView", "ReadMode", "Ref", "ResolvedFilter"] : []
|
|
8777
|
+
].map((t) => ` ${t},`).join("\n");
|
|
8778
|
+
const changeSourcesAssign = rp ? `
|
|
8779
|
+
this.changeSources = {
|
|
8780
|
+
${rp.changeSourceEntries.join("\n")}
|
|
8781
|
+
};
|
|
8782
|
+
` : "";
|
|
8783
|
+
const changeSourcesDecl = rp ? ` /**
|
|
8784
|
+
* Per-entity change sources contributed to the ${surface} registry, keyed by
|
|
8785
|
+
* entity name. The surface aggregator folds these into the
|
|
8786
|
+
* \`IEntityChangeSourceRegistry\` bound under \`${n.entitySourcesToken}\`.
|
|
8787
|
+
* Emit-once: edit the \`IncrementalReadBase\` subclasses above, not this map.
|
|
8788
|
+
*/
|
|
8789
|
+
readonly changeSources: Record<string, IChangeSource<unknown>>;` : ` /**
|
|
8790
|
+
* Per-entity change sources this adapter contributes to the ${surface}
|
|
8791
|
+
* registry (ADR-033 \`buildChangeSource\`), keyed by entity name. The
|
|
8792
|
+
* surface aggregator folds these into the \`IEntityChangeSourceRegistry\`
|
|
8793
|
+
* bound under \`${n.entitySourcesToken}\`. Author-owned \u2014 populate one entry
|
|
8794
|
+
* per entity in \`capabilities.entities\`.
|
|
8795
|
+
*/
|
|
8796
|
+
readonly changeSources: Record<string, IChangeSource<unknown>> = {};`;
|
|
8797
|
+
const preambleSection = rp ? `
|
|
8798
|
+
${rp.preamble}
|
|
8799
|
+
` : "";
|
|
8341
8800
|
const capabilityBody = [
|
|
8342
8801
|
` ...${spec.noCapsConst},`,
|
|
8343
8802
|
...spec.l2Ports.map((p) => ` ${p.capFlag}: true,`),
|
|
@@ -8354,7 +8813,7 @@ function generateAdapterScaffold(def, surface, entities) {
|
|
|
8354
8813
|
const l2Section = l2Members ? `
|
|
8355
8814
|
${l2Members}
|
|
8356
8815
|
` : "";
|
|
8357
|
-
return `${
|
|
8816
|
+
return `${SCAFFOLD_SENTINEL2}
|
|
8358
8817
|
// Scaffolded once by @pattern-stack/codegen, then author-owned. Re-running
|
|
8359
8818
|
// codegen detects the sentinel above and SKIPS this file \u2014 your edits are safe.
|
|
8360
8819
|
// Source: definitions/providers/${def.slug}.yaml (surface: ${surface}).
|
|
@@ -8363,15 +8822,12 @@ import type {
|
|
|
8363
8822
|
${surfaceTypeImports}
|
|
8364
8823
|
} from '${spec.packageName}';
|
|
8365
8824
|
import { ${spec.noCapsConst} } from '${spec.packageName}';
|
|
8366
|
-
import type {
|
|
8367
|
-
|
|
8368
|
-
IChangeSource,
|
|
8369
|
-
IEntityChangeSourceRegistry,
|
|
8825
|
+
${subsystemValueImport}import type {
|
|
8826
|
+
${subsystemTypeImports}
|
|
8370
8827
|
} from '@pattern-stack/codegen/subsystems';
|
|
8371
8828
|
import type { ${client.exportName} } from '${client.path}';
|
|
8372
8829
|
import { ${n.strategyToken}, ${n.clientToken} } from '../../../providers/${def.slug}/${def.slug}.provider.module';
|
|
8373
|
-
|
|
8374
|
-
|
|
8830
|
+
${preambleSection}
|
|
8375
8831
|
@Injectable()
|
|
8376
8832
|
export class ${n.adapterClass} implements ${spec.portType} {
|
|
8377
8833
|
/** Declared capabilities. \`entities\` derives from \`surface: ${surface}\` entity YAML. */
|
|
@@ -8382,17 +8838,9 @@ ${capabilityBody}
|
|
|
8382
8838
|
constructor(
|
|
8383
8839
|
@Inject(${n.strategyToken}) readonly auth: IAuthStrategy,
|
|
8384
8840
|
@Inject(${n.clientToken}) private readonly client: ${client.exportName},
|
|
8385
|
-
|
|
8386
|
-
) {}
|
|
8841
|
+
) {${changeSourcesAssign}}
|
|
8387
8842
|
${l2Section}
|
|
8388
|
-
|
|
8389
|
-
* Per-entity change sources this adapter contributes to the ${surface}
|
|
8390
|
-
* registry (ADR-033 \`buildChangeSource\`), keyed by entity name. The
|
|
8391
|
-
* surface aggregator folds these into the \`IEntityChangeSourceRegistry\`
|
|
8392
|
-
* bound under \`${n.entitySourcesToken}\`. Author-owned \u2014 populate one entry
|
|
8393
|
-
* per entity in \`capabilities.entities\`.
|
|
8394
|
-
*/
|
|
8395
|
-
readonly changeSources: Record<string, IChangeSource<unknown>> = {};
|
|
8843
|
+
${changeSourcesDecl}
|
|
8396
8844
|
|
|
8397
8845
|
// surface-only methods (optional on ${spec.portType}): add here
|
|
8398
8846
|
}
|
|
@@ -8400,7 +8848,7 @@ ${l2Section}
|
|
|
8400
8848
|
}
|
|
8401
8849
|
function generateAdapterModule(def, surface) {
|
|
8402
8850
|
const n = names(def.slug, surface);
|
|
8403
|
-
return `${
|
|
8851
|
+
return `${generatedBanner2(`definitions/providers/${def.slug}.yaml (surface: ${surface})`)}
|
|
8404
8852
|
import { Module } from '@nestjs/common';
|
|
8405
8853
|
import { ${n.providerModuleClass} } from '../../../providers/${def.slug}/${def.slug}.provider.module';
|
|
8406
8854
|
import { ${n.adapterClass} } from './${def.slug}-${surface}.adapter';
|
|
@@ -8418,13 +8866,13 @@ function generateAdaptersBarrel(surface, providerSlugs) {
|
|
|
8418
8866
|
const n = names(slug, surface);
|
|
8419
8867
|
return `export { ${n.adapterModuleClass} } from './${slug}/${slug}-${surface}.adapter.module';`;
|
|
8420
8868
|
}).join("\n");
|
|
8421
|
-
return `${
|
|
8869
|
+
return `${generatedBanner2(`definitions/providers/*.yaml (surface: ${surface})`)}
|
|
8422
8870
|
${lines}
|
|
8423
8871
|
`;
|
|
8424
8872
|
}
|
|
8425
8873
|
function generateSurfaceTokens(surface) {
|
|
8426
8874
|
const n = names("__placeholder__", surface);
|
|
8427
|
-
return `${
|
|
8875
|
+
return `${generatedBanner2(`surface: ${surface}`)}
|
|
8428
8876
|
import type { IChangeSource } from '@pattern-stack/codegen/subsystems';
|
|
8429
8877
|
|
|
8430
8878
|
/** The assembled list of every ${surface} adapter's contribution. */
|
|
@@ -8463,7 +8911,7 @@ function generateSurfaceAggregator(surface, providerSlugs) {
|
|
|
8463
8911
|
return `${lowerFirst(p.adapterClass)}: ${p.adapterClass}`;
|
|
8464
8912
|
}).join(", ");
|
|
8465
8913
|
const injectTokens = per.map((p) => p.adapterClass).join(", ");
|
|
8466
|
-
return `${
|
|
8914
|
+
return `${generatedBanner2(`surface: ${surface}`)}
|
|
8467
8915
|
import { Module } from '@nestjs/common';
|
|
8468
8916
|
import {
|
|
8469
8917
|
MemoryEntityChangeSourceRegistry,
|
|
@@ -8535,7 +8983,7 @@ function generateTypedView(surface, providerSlugs, entities) {
|
|
|
8535
8983
|
const providerUnion = slugs.length ? slugs.map((s) => `'${s}'`).join(" | ") : "never";
|
|
8536
8984
|
const entityUnion = ents.length ? ents.map((e) => `'${e}'`).join(" | ") : "never";
|
|
8537
8985
|
const mapEntries = slugs.map((s) => ` ${jsKey(s)}: ${surfacePascal}Entity;`).join("\n");
|
|
8538
|
-
return `${
|
|
8986
|
+
return `${generatedBanner2(`surface: ${surface}`)}
|
|
8539
8987
|
/**
|
|
8540
8988
|
* Per-consumer typed view for the \`${surface}\` surface. Surface-scoped unions
|
|
8541
8989
|
* + a (provider, entity) validity map for compile-time-checked consumer
|
|
@@ -8562,9 +9010,14 @@ function emitAdapters(opts) {
|
|
|
8562
9010
|
written: [],
|
|
8563
9011
|
scaffoldsWritten: [],
|
|
8564
9012
|
scaffoldsSkipped: [],
|
|
8565
|
-
skippedSurfaces: []
|
|
9013
|
+
skippedSurfaces: [],
|
|
9014
|
+
assembliesWritten: [],
|
|
9015
|
+
tokensWritten: [],
|
|
9016
|
+
integrationAggregatorsWritten: [],
|
|
9017
|
+
skippedAssemblies: []
|
|
8566
9018
|
};
|
|
8567
9019
|
const entitiesBySurface = collectEntitiesBySurface(opts.entities);
|
|
9020
|
+
const entityByName = new Map(opts.entities.map((e) => [e.entity.name, e]));
|
|
8568
9021
|
const bySurface = /* @__PURE__ */ new Map();
|
|
8569
9022
|
for (const { definition } of opts.providers) {
|
|
8570
9023
|
for (const surface of definition.surfaces) {
|
|
@@ -8593,10 +9046,17 @@ function emitAdapters(opts) {
|
|
|
8593
9046
|
if (existsSync10(scaffoldPath)) {
|
|
8594
9047
|
result.scaffoldsSkipped.push(scaffoldPath);
|
|
8595
9048
|
} else {
|
|
9049
|
+
const surfaceEntityNames = entitiesBySurface.get(surface) ?? [];
|
|
9050
|
+
const entityDetection = /* @__PURE__ */ new Map();
|
|
9051
|
+
for (const name of surfaceEntityNames) {
|
|
9052
|
+
const det = entityByName.get(name)?.entity.detection?.[slug];
|
|
9053
|
+
if (det) entityDetection.set(name, det);
|
|
9054
|
+
}
|
|
8596
9055
|
const content = generateAdapterScaffold(
|
|
8597
9056
|
def,
|
|
8598
9057
|
surface,
|
|
8599
|
-
|
|
9058
|
+
surfaceEntityNames,
|
|
9059
|
+
entityDetection
|
|
8600
9060
|
);
|
|
8601
9061
|
if (!opts.dryRun) writeFile(scaffoldPath, content);
|
|
8602
9062
|
result.scaffoldsWritten.push(scaffoldPath);
|
|
@@ -8619,9 +9079,141 @@ function emitAdapters(opts) {
|
|
|
8619
9079
|
if (!opts.dryRun) writeIfChanged2(path34, content);
|
|
8620
9080
|
result.written.push(path34);
|
|
8621
9081
|
}
|
|
9082
|
+
if (opts.backendSrcAbs) {
|
|
9083
|
+
const aliases = opts.aliases ?? {};
|
|
9084
|
+
const surfaceEntities = entitiesBySurface.get(surface) ?? [];
|
|
9085
|
+
const tokenEntries = [];
|
|
9086
|
+
const assemblyEntries = [];
|
|
9087
|
+
const sinksDir = join12(surfaceDir, "sinks");
|
|
9088
|
+
const modulesDir = join12(surfaceDir, "modules");
|
|
9089
|
+
for (const entityName of surfaceEntities) {
|
|
9090
|
+
const def = entityByName.get(entityName);
|
|
9091
|
+
const pattern = def?.entity.pattern ?? (Array.isArray(def?.entity.patterns) ? def?.entity.patterns?.[0] : void 0);
|
|
9092
|
+
if (pattern !== "Integrated") {
|
|
9093
|
+
result.skippedAssemblies.push({
|
|
9094
|
+
surface,
|
|
9095
|
+
entity: entityName,
|
|
9096
|
+
reason: `entity '${entityName}' declares surface '${surface}' but is not 'pattern: Integrated'${pattern ? ` (got 'pattern: ${pattern}')` : " (no pattern declared)"} \u2014 the integration assembly + default sink need the Integrated projection/upsert path. Add 'pattern: Integrated' (or provide a hand-authored sink + assembly).`
|
|
9097
|
+
});
|
|
9098
|
+
continue;
|
|
9099
|
+
}
|
|
9100
|
+
const plural = def?.entity.plural ?? `${entityName}s`;
|
|
9101
|
+
const context = def?.entity.context ?? null;
|
|
9102
|
+
const loc = resolveEntityModuleImports({
|
|
9103
|
+
entityName,
|
|
9104
|
+
entityPlural: plural,
|
|
9105
|
+
context,
|
|
9106
|
+
surface,
|
|
9107
|
+
// The sink+repo import is provider-agnostic; pick any provider's
|
|
9108
|
+
// module dir for the relative-path base (all share the same parent).
|
|
9109
|
+
provider: slugs[0],
|
|
9110
|
+
backendSrcAbs: opts.backendSrcAbs,
|
|
9111
|
+
aliases
|
|
9112
|
+
});
|
|
9113
|
+
const sinkPath = join12(sinksDir, `${entityName}.sink.ts`);
|
|
9114
|
+
if (existsSync10(sinkPath)) {
|
|
9115
|
+
result.scaffoldsSkipped.push(sinkPath);
|
|
9116
|
+
} else {
|
|
9117
|
+
const sinkInput = buildSinkInput(def, surface, slugs[0], loc.repoImportSpecifier);
|
|
9118
|
+
const sinkContent = generateDefaultSink(sinkInput);
|
|
9119
|
+
if (!opts.dryRun) writeFile(sinkPath, sinkContent);
|
|
9120
|
+
result.scaffoldsWritten.push(sinkPath);
|
|
9121
|
+
}
|
|
9122
|
+
for (const slug of slugs) {
|
|
9123
|
+
const assemblyPath = join12(
|
|
9124
|
+
modulesDir,
|
|
9125
|
+
slug,
|
|
9126
|
+
`${entityName}-integration.module.ts`
|
|
9127
|
+
);
|
|
9128
|
+
const assemblyContent = generateAssemblyModule({
|
|
9129
|
+
surface,
|
|
9130
|
+
provider: slug,
|
|
9131
|
+
entityName,
|
|
9132
|
+
entityClass: loc.entityClass,
|
|
9133
|
+
moduleImportSpecifier: loc.moduleImportSpecifier,
|
|
9134
|
+
moduleClass: loc.moduleClass,
|
|
9135
|
+
repoImportSpecifier: loc.repoImportSpecifier,
|
|
9136
|
+
repoClass: loc.repoClass,
|
|
9137
|
+
sourceDesc: `definitions/providers/${slug}.yaml`
|
|
9138
|
+
});
|
|
9139
|
+
if (!opts.dryRun) writeIfChanged2(assemblyPath, assemblyContent);
|
|
9140
|
+
result.assembliesWritten.push(assemblyPath);
|
|
9141
|
+
tokenEntries.push({ entityName, entityClass: loc.entityClass, provider: slug });
|
|
9142
|
+
assemblyEntries.push({ entityName, provider: slug });
|
|
9143
|
+
}
|
|
9144
|
+
}
|
|
9145
|
+
const integrationTokensPath = join12(
|
|
9146
|
+
surfaceDir,
|
|
9147
|
+
`${surface}-integration.tokens.ts`
|
|
9148
|
+
);
|
|
9149
|
+
const tokensContent = generateIntegrationTokens(surface, tokenEntries);
|
|
9150
|
+
if (!opts.dryRun) writeIfChanged2(integrationTokensPath, tokensContent);
|
|
9151
|
+
result.tokensWritten.push(integrationTokensPath);
|
|
9152
|
+
if (assemblyEntries.length > 0) {
|
|
9153
|
+
const integrationAggregatorPath = join12(
|
|
9154
|
+
surfaceDir,
|
|
9155
|
+
`${surface}-integration.module.ts`
|
|
9156
|
+
);
|
|
9157
|
+
const aggregatorContent = generateIntegrationAggregator(
|
|
9158
|
+
surface,
|
|
9159
|
+
assemblyEntries
|
|
9160
|
+
);
|
|
9161
|
+
if (!opts.dryRun) writeIfChanged2(integrationAggregatorPath, aggregatorContent);
|
|
9162
|
+
result.integrationAggregatorsWritten.push(integrationAggregatorPath);
|
|
9163
|
+
}
|
|
9164
|
+
}
|
|
8622
9165
|
}
|
|
8623
9166
|
return result;
|
|
8624
9167
|
}
|
|
9168
|
+
function buildSinkInput(def, surface, provider, repoImportSpecifier) {
|
|
9169
|
+
const fields = def.fields ?? {};
|
|
9170
|
+
const relationships = def.relationships ?? {};
|
|
9171
|
+
const fkColumns = /* @__PURE__ */ new Set();
|
|
9172
|
+
for (const rel2 of Object.values(relationships)) {
|
|
9173
|
+
if (rel2.type === "belongs_to" && typeof rel2.foreign_key === "string") {
|
|
9174
|
+
fkColumns.add(rel2.foreign_key);
|
|
9175
|
+
}
|
|
9176
|
+
}
|
|
9177
|
+
const copyThroughFields = Object.entries(fields).filter(([name]) => name !== "id" && !fkColumns.has(name)).map(([name, f]) => ({
|
|
9178
|
+
camelName: snakeToCamel(name),
|
|
9179
|
+
tsType: tsTypeFor(f.type, f.nullable)
|
|
9180
|
+
}));
|
|
9181
|
+
const fkExternalKeys = Object.entries(relationships).filter(([, rel2]) => rel2.type === "belongs_to").map(([relName, rel2]) => {
|
|
9182
|
+
const target = rel2.target ?? relName;
|
|
9183
|
+
return { writeKey: `${snakeToCamel(target)}ExternalId` };
|
|
9184
|
+
});
|
|
9185
|
+
return {
|
|
9186
|
+
entityName: def.entity.name,
|
|
9187
|
+
entityClass: pascalFromSnake(def.entity.name),
|
|
9188
|
+
surface,
|
|
9189
|
+
pattern: "Integrated",
|
|
9190
|
+
provider,
|
|
9191
|
+
copyThroughFields,
|
|
9192
|
+
fkExternalKeys,
|
|
9193
|
+
repoImportSpecifier
|
|
9194
|
+
};
|
|
9195
|
+
}
|
|
9196
|
+
var TS_TYPE_FOR_SINK = {
|
|
9197
|
+
string: "string",
|
|
9198
|
+
integer: "number",
|
|
9199
|
+
decimal: "string",
|
|
9200
|
+
boolean: "boolean",
|
|
9201
|
+
uuid: "string",
|
|
9202
|
+
date: "Date",
|
|
9203
|
+
datetime: "Date",
|
|
9204
|
+
json: "unknown"
|
|
9205
|
+
};
|
|
9206
|
+
function tsTypeFor(type, nullable) {
|
|
9207
|
+
const base = TS_TYPE_FOR_SINK[type ?? "string"] ?? "unknown";
|
|
9208
|
+
return nullable ? `${base} | null` : base;
|
|
9209
|
+
}
|
|
9210
|
+
function snakeToCamel(s) {
|
|
9211
|
+
return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
9212
|
+
}
|
|
9213
|
+
function pascalFromSnake(s) {
|
|
9214
|
+
const camel = snakeToCamel(s);
|
|
9215
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
9216
|
+
}
|
|
8625
9217
|
function writeFile(outPath, content) {
|
|
8626
9218
|
mkdirSync3(dirname2(outPath), { recursive: true });
|
|
8627
9219
|
writeFileSync3(outPath, content);
|
|
@@ -8668,9 +9260,15 @@ function printInfo(msg) {
|
|
|
8668
9260
|
}
|
|
8669
9261
|
|
|
8670
9262
|
// src/cli/commands/entity.ts
|
|
8671
|
-
function
|
|
9263
|
+
function resolveProvidersDir(ctx) {
|
|
9264
|
+
const fromConfig = ctx.config?.paths?.providers;
|
|
9265
|
+
return fromConfig != null ? path13.resolve(ctx.cwd, fromConfig) : path13.resolve(ctx.cwd, "definitions/providers");
|
|
9266
|
+
}
|
|
9267
|
+
function listEntityYamls2(dir, providersDir) {
|
|
8672
9268
|
if (!fs9.existsSync(dir)) return [];
|
|
8673
|
-
return findYamlFiles(dir
|
|
9269
|
+
return findYamlFiles(dir, {
|
|
9270
|
+
excludeDirs: providersDir ? [providersDir] : []
|
|
9271
|
+
});
|
|
8674
9272
|
}
|
|
8675
9273
|
function summarizePatternLabel(entity) {
|
|
8676
9274
|
if (typeof entity.pattern === "string" && entity.pattern.length > 0) {
|
|
@@ -8709,7 +9307,7 @@ async function summary(ctx) {
|
|
|
8709
9307
|
]
|
|
8710
9308
|
};
|
|
8711
9309
|
}
|
|
8712
|
-
const files = listEntityYamls2(ctx.entitiesDir);
|
|
9310
|
+
const files = listEntityYamls2(ctx.entitiesDir, resolveProvidersDir(ctx));
|
|
8713
9311
|
const rows = files.map(summarizeEntityFile).filter((r) => r !== null);
|
|
8714
9312
|
const patterns = new Set(rows.map((r) => r.pattern));
|
|
8715
9313
|
const queryCount = rows.reduce((sum, r) => sum + r.queries, 0);
|
|
@@ -8806,7 +9404,7 @@ var EntityNewCommand = class extends Command2 {
|
|
|
8806
9404
|
let targets = [];
|
|
8807
9405
|
if (this.all) {
|
|
8808
9406
|
const dir = ctx.entitiesDir ?? path13.resolve(ctx.cwd, "entities");
|
|
8809
|
-
targets = listEntityYamls2(dir);
|
|
9407
|
+
targets = listEntityYamls2(dir, resolveProvidersDir(ctx));
|
|
8810
9408
|
if (targets.length === 0) {
|
|
8811
9409
|
printError(`No entity YAML files found in ${dir}`);
|
|
8812
9410
|
return 1;
|
|
@@ -8824,12 +9422,15 @@ var EntityNewCommand = class extends Command2 {
|
|
|
8824
9422
|
if (result.success) {
|
|
8825
9423
|
validated.push({ file, name: result.definition.entity.name });
|
|
8826
9424
|
} else {
|
|
8827
|
-
invalid.push({ file, message: result.error });
|
|
9425
|
+
invalid.push({ file, message: result.error, details: result.details });
|
|
8828
9426
|
}
|
|
8829
9427
|
}
|
|
8830
9428
|
if (invalid.length > 0 && !this.continueOnError) {
|
|
8831
9429
|
for (const i of invalid) {
|
|
8832
9430
|
printError(`${path13.basename(i.file)} \u2014 ${i.message}`);
|
|
9431
|
+
for (const detail of i.details ?? []) {
|
|
9432
|
+
printError(` \u2022 ${detail}`);
|
|
9433
|
+
}
|
|
8833
9434
|
}
|
|
8834
9435
|
if (!isJsonMode()) {
|
|
8835
9436
|
return 1;
|
|
@@ -8837,7 +9438,9 @@ var EntityNewCommand = class extends Command2 {
|
|
|
8837
9438
|
}
|
|
8838
9439
|
const entitiesDirForEmits = ctx.entitiesDir ?? path13.resolve(ctx.cwd, "entities");
|
|
8839
9440
|
const eventsDirForEmits = resolveEventsDir(ctx);
|
|
8840
|
-
const allEntitiesForEmits = loadEntities(entitiesDirForEmits
|
|
9441
|
+
const allEntitiesForEmits = loadEntities(entitiesDirForEmits, {
|
|
9442
|
+
excludeDirs: [resolveProvidersDir(ctx)]
|
|
9443
|
+
}).entities;
|
|
8841
9444
|
const validatedNames = new Set(validated.map((v) => v.name));
|
|
8842
9445
|
const emitsTargetEntities = allEntitiesForEmits.filter(
|
|
8843
9446
|
(e) => validatedNames.has(e.name)
|
|
@@ -9169,19 +9772,16 @@ var EntityNewCommand = class extends Command2 {
|
|
|
9169
9772
|
}
|
|
9170
9773
|
let providerResult = null;
|
|
9171
9774
|
try {
|
|
9172
|
-
const providersDir = ctx
|
|
9173
|
-
ctx.cwd,
|
|
9174
|
-
ctx.config.paths.providers
|
|
9175
|
-
) : path13.resolve(ctx.cwd, "definitions/providers");
|
|
9775
|
+
const providersDir = resolveProvidersDir(ctx);
|
|
9176
9776
|
const providerOutputRoot = path13.resolve(
|
|
9177
9777
|
ctx.cwd,
|
|
9178
9778
|
backendSrcForHandlers,
|
|
9179
9779
|
"integrations/providers"
|
|
9180
9780
|
);
|
|
9181
9781
|
const entitySurfaces = fs9.existsSync(entitiesDir) ? collectEntitySurfaces(
|
|
9182
|
-
loadEntitiesFromYaml(
|
|
9183
|
-
(
|
|
9184
|
-
)
|
|
9782
|
+
loadEntitiesFromYaml(
|
|
9783
|
+
findYamlFiles(entitiesDir, { excludeDirs: [providersDir] })
|
|
9784
|
+
).successes.map((s) => s.definition)
|
|
9185
9785
|
) : /* @__PURE__ */ new Set();
|
|
9186
9786
|
const tsAliases = resolveTsconfigAliases(ctx.cwd);
|
|
9187
9787
|
providerResult = generateProviderModules({
|
|
@@ -9219,19 +9819,22 @@ var EntityNewCommand = class extends Command2 {
|
|
|
9219
9819
|
backendSrcForHandlers,
|
|
9220
9820
|
"integrations"
|
|
9221
9821
|
);
|
|
9222
|
-
const entityDefs = fs9.existsSync(entitiesDir) ? loadEntitiesFromYaml(
|
|
9223
|
-
(
|
|
9224
|
-
) : [];
|
|
9822
|
+
const entityDefs = fs9.existsSync(entitiesDir) ? loadEntitiesFromYaml(
|
|
9823
|
+
findYamlFiles(entitiesDir, { excludeDirs: [providersDir] })
|
|
9824
|
+
).successes.map((s) => s.definition) : [];
|
|
9225
9825
|
const loadedProviders = loadProvidersFromYaml(
|
|
9226
9826
|
findYamlFiles(providersDir)
|
|
9227
9827
|
).successes.map((s) => ({
|
|
9228
9828
|
definition: s.definition,
|
|
9229
9829
|
filePath: s.filePath
|
|
9230
9830
|
}));
|
|
9831
|
+
const assemblyTsAliases = resolveTsconfigAliases(ctx.cwd);
|
|
9231
9832
|
const adapterResult = emitAdapters({
|
|
9232
9833
|
providers: loadedProviders,
|
|
9233
9834
|
entities: entityDefs,
|
|
9234
|
-
outputRoot: adapterOutputRoot
|
|
9835
|
+
outputRoot: adapterOutputRoot,
|
|
9836
|
+
backendSrcAbs: path13.resolve(ctx.cwd, backendSrcForHandlers),
|
|
9837
|
+
aliases: assemblyTsAliases?.aliases ?? {}
|
|
9235
9838
|
});
|
|
9236
9839
|
if (!isJsonMode()) {
|
|
9237
9840
|
if (adapterResult.written.length || adapterResult.scaffoldsWritten.length) {
|
|
@@ -9239,12 +9842,20 @@ var EntityNewCommand = class extends Command2 {
|
|
|
9239
9842
|
`adapter codegen: ${adapterResult.scaffoldsWritten.length} scaffold(s) + ${adapterResult.written.length} @generated \u2192 ${adapterOutputRoot}`
|
|
9240
9843
|
);
|
|
9241
9844
|
}
|
|
9845
|
+
if (adapterResult.assembliesWritten.length || adapterResult.tokensWritten.length) {
|
|
9846
|
+
printInfo(
|
|
9847
|
+
`integration assembly codegen: ${adapterResult.assembliesWritten.length} module(s) + ${adapterResult.tokensWritten.length} tokens file(s) + ${adapterResult.integrationAggregatorsWritten.length} aggregator(s)`
|
|
9848
|
+
);
|
|
9849
|
+
}
|
|
9242
9850
|
for (const s of adapterResult.scaffoldsSkipped) {
|
|
9243
9851
|
printInfo(`skipped scaffold ${s} (author-owned)`);
|
|
9244
9852
|
}
|
|
9245
9853
|
for (const s of adapterResult.skippedSurfaces) {
|
|
9246
9854
|
printWarning(`adapter codegen: ${s.reason} (provider ${s.provider})`);
|
|
9247
9855
|
}
|
|
9856
|
+
for (const s of adapterResult.skippedAssemblies) {
|
|
9857
|
+
printWarning(`integration assembly: ${s.reason}`);
|
|
9858
|
+
}
|
|
9248
9859
|
}
|
|
9249
9860
|
}
|
|
9250
9861
|
} catch (err) {
|
|
@@ -9367,7 +9978,7 @@ var EntityListCommand = class extends Command2 {
|
|
|
9367
9978
|
printError("No entities directory found.");
|
|
9368
9979
|
return 1;
|
|
9369
9980
|
}
|
|
9370
|
-
const files = listEntityYamls2(ctx.entitiesDir);
|
|
9981
|
+
const files = listEntityYamls2(ctx.entitiesDir, resolveProvidersDir(ctx));
|
|
9371
9982
|
const rows = files.map(summarizeEntityFile).filter((r) => r !== null).filter((r) => this.pattern ? r.pattern === this.pattern : true);
|
|
9372
9983
|
if (isJsonMode()) {
|
|
9373
9984
|
printJson({
|
|
@@ -13097,10 +13708,10 @@ var ProjectInitCommand = class extends Command7 {
|
|
|
13097
13708
|
};
|
|
13098
13709
|
function askConfirm(question) {
|
|
13099
13710
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
13100
|
-
return new Promise((
|
|
13711
|
+
return new Promise((resolve7) => {
|
|
13101
13712
|
rl.question(`${question} [Y/n] `, (answer) => {
|
|
13102
13713
|
rl.close();
|
|
13103
|
-
|
|
13714
|
+
resolve7(answer.trim().toLowerCase() !== "n");
|
|
13104
13715
|
});
|
|
13105
13716
|
});
|
|
13106
13717
|
}
|