@pattern-stack/codegen 0.12.1 → 0.12.2
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 +36 -0
- package/dist/src/cli/index.js +75 -58
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/index.d.ts +25 -15
- package/dist/src/index.js +43 -35
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,42 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.12.2] — 2026-05-31
|
|
8
|
+
|
|
9
|
+
Track D consumer-CLI fix. The 0.12.0/0.12.1 generator was correct in the
|
|
10
|
+
hermetic D7 path but broke for real consumers driving it through the CLI. Two
|
|
11
|
+
schema/loader bugs plus a DX gap that masked the first. No swe-brain YAML change
|
|
12
|
+
is required — consumer YAML already wrote the keys in the natural place; the
|
|
13
|
+
schema is now corrected to match.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **`entity.surface` / `entity.context` schema level.** `surface:` and
|
|
18
|
+
`context:` were defined at the ROOT of `EntityDefinitionSchema` (sibling of
|
|
19
|
+
`entity:`/`fields:`), but consumers naturally write them INSIDE the `entity:`
|
|
20
|
+
block (next to `pattern:`/`name:`/`table:`). Because the `entity:` block is
|
|
21
|
+
`.strict()`, those YAMLs were rejected with "Unrecognized key(s) in object:
|
|
22
|
+
'surface' at 'entity'". The fields now live in `EntityConfigSchema` and are
|
|
23
|
+
read as `entity.surface` / `entity.context`. Clean break — root-level
|
|
24
|
+
placement no longer validates. Read sites updated:
|
|
25
|
+
`collectEntitySurfaces` (`validate-providers.ts`), `collectEntitiesBySurface`
|
|
26
|
+
(`adapter-emission-generator.ts`), the provider surface cross-check
|
|
27
|
+
(`entity.ts`), and the clean-lite-ps output-subfolder consumer
|
|
28
|
+
(`prompt-extension.js` `buildCleanLitePsLocals`).
|
|
29
|
+
- **Entity `--all` discovery no longer globs `definitions/providers/`.** With
|
|
30
|
+
`entities_dir: definitions`, the recursive YAML walk pulled provider files
|
|
31
|
+
into the entity loader, where they fail entity validation. Entity discovery
|
|
32
|
+
(`findYamlFiles`, `loadEntities`, `listEntityYamls`) now excludes the
|
|
33
|
+
configured providers dir (`paths.providers`, default `definitions/providers`).
|
|
34
|
+
Provider files route only through `ProviderDefinitionSchema`.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- **`entity new --dry-run` surfaces the Zod detail.** The failure path printed
|
|
39
|
+
only "Validation failed for <file>"; it now emits the same per-issue Zod
|
|
40
|
+
diagnostics as `entity validate`, so a misplaced key reports which key/level
|
|
41
|
+
is wrong (the DX gap that hid the `entity.surface` rejection).
|
|
42
|
+
|
|
7
43
|
## [0.12.1] — 2026-05-31
|
|
8
44
|
|
|
9
45
|
Track D (provider/adapter integration codegen) discoverability fix. The 0.12.0
|
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
|
}
|
|
@@ -2312,7 +2315,37 @@ var EntityConfigSchema = z3.object({
|
|
|
2312
2315
|
// JOB-7: marks this entity as a valid scope target for job scoping.
|
|
2313
2316
|
// Drives the generated ScopeEntityType union in
|
|
2314
2317
|
// runtime/subsystems/jobs/generated/scope-entity-type.ts.
|
|
2315
|
-
scopeable: z3.boolean().optional()
|
|
2318
|
+
scopeable: z3.boolean().optional(),
|
|
2319
|
+
// RFC-0001 §1/§8: the integration *surface* this entity belongs to
|
|
2320
|
+
// (e.g. 'calendar', 'mail', 'crm'). Surfaces span provider contexts
|
|
2321
|
+
// (ADR-0006) — one Google OAuth feeds calendar+mail+transcript. The union
|
|
2322
|
+
// of `surface:` values across all entity YAML is the closed set that a
|
|
2323
|
+
// provider's `surfaces:` must be a subset of (cross-checked in
|
|
2324
|
+
// src/parser/validate-providers.ts). Optional: entities without an
|
|
2325
|
+
// integration surface omit it. The surface-package *emission* convention
|
|
2326
|
+
// is Track C (#329); this field is only the declarative input both tracks
|
|
2327
|
+
// read. Lives inside the `entity:` block (next to `pattern:`/`name:`/`table:`).
|
|
2328
|
+
surface: z3.string().optional(),
|
|
2329
|
+
// Bounded-context declaration (ADR-0004) — "which bounded context this
|
|
2330
|
+
// entity belongs to". This is the DURABLE decision; it is a plain
|
|
2331
|
+
// bounded-context slug, NOT a folder knob. Different features consume it:
|
|
2332
|
+
//
|
|
2333
|
+
// - #403 (the FIRST consumer): drives the generated code's
|
|
2334
|
+
// module output folder. clean-lite-ps nests the entity's module under
|
|
2335
|
+
// `<modules>/<context>/<entity>/` so same-context entities group
|
|
2336
|
+
// together; untagged entities stay flat (`<modules>/<entity>/`).
|
|
2337
|
+
// - ADR-0004 (deferred): a later `naming: prefix | schema` knob reads
|
|
2338
|
+
// this SAME field to drive the Postgres physical layout —
|
|
2339
|
+
// `prefix` → `pgTable('<context>__<table>')`, then the flip to
|
|
2340
|
+
// `schema` → `pgSchema('<context>').table('<table>')`. NOT wired here.
|
|
2341
|
+
//
|
|
2342
|
+
// Sibling to `surface:` and orthogonal to it (ADR-0006): context = model
|
|
2343
|
+
// cohesion (which domain), surface = vendor composition (which integration).
|
|
2344
|
+
// Lives inside the `entity:` block (next to `pattern:`/`name:`/`table:`).
|
|
2345
|
+
context: z3.string().regex(
|
|
2346
|
+
/^[a-z][a-z0-9_]*$/,
|
|
2347
|
+
"context must be lowercase snake_case (e.g. 'integration')"
|
|
2348
|
+
).optional()
|
|
2316
2349
|
}).strict().refine((d) => !(d.pattern && d.patterns), {
|
|
2317
2350
|
message: "'pattern' and 'patterns' are mutually exclusive"
|
|
2318
2351
|
});
|
|
@@ -2465,36 +2498,11 @@ var EntityDefinitionSchema = z3.object({
|
|
|
2465
2498
|
// appear in `integration.providers` — see the superRefine on
|
|
2466
2499
|
// `EntityDefinitionSchema` below.
|
|
2467
2500
|
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(),
|
|
2501
|
+
// NOTE: `surface:` and `context:` moved INTO EntityConfigSchema (the
|
|
2502
|
+
// `entity:` block) in 0.12.2 — consumers write them next to
|
|
2503
|
+
// `pattern:`/`name:`/`table:`, which is the natural place. They are
|
|
2504
|
+
// read via `entity.surface` / `entity.context`. Clean break: no
|
|
2505
|
+
// root-level placement is accepted.
|
|
2498
2506
|
// v2: Domain event declarations (CODEGEN-EVOLUTION-PLAN Phase 2)
|
|
2499
2507
|
// Generates typed event classes, handlers, and queue registration
|
|
2500
2508
|
events: z3.array(EventDeclarationSchema).optional(),
|
|
@@ -3409,13 +3417,13 @@ function loadErrorToIssue(error) {
|
|
|
3409
3417
|
}
|
|
3410
3418
|
return issues;
|
|
3411
3419
|
}
|
|
3412
|
-
function loadEntities(entitiesDir) {
|
|
3420
|
+
function loadEntities(entitiesDir, opts) {
|
|
3413
3421
|
const entities = [];
|
|
3414
3422
|
const issues = [];
|
|
3415
3423
|
const resolvedDir = resolve2(entitiesDir);
|
|
3416
3424
|
let files;
|
|
3417
3425
|
try {
|
|
3418
|
-
files = findYamlFiles(resolvedDir);
|
|
3426
|
+
files = findYamlFiles(resolvedDir, { excludeDirs: opts?.excludeDirs });
|
|
3419
3427
|
} catch (err) {
|
|
3420
3428
|
issues.push({
|
|
3421
3429
|
severity: "error",
|
|
@@ -3650,7 +3658,7 @@ import ts from "typescript";
|
|
|
3650
3658
|
function collectEntitySurfaces(entities) {
|
|
3651
3659
|
const surfaces = /* @__PURE__ */ new Set();
|
|
3652
3660
|
for (const e of entities) {
|
|
3653
|
-
if (e.surface) surfaces.add(e.surface);
|
|
3661
|
+
if (e.entity.surface) surfaces.add(e.entity.surface);
|
|
3654
3662
|
}
|
|
3655
3663
|
return surfaces;
|
|
3656
3664
|
}
|
|
@@ -6019,8 +6027,8 @@ function collectEntities(entitiesDir) {
|
|
|
6019
6027
|
entities.push({
|
|
6020
6028
|
name: def.entity.name,
|
|
6021
6029
|
plural: def.entity.plural,
|
|
6022
|
-
// #403:
|
|
6023
|
-
context: def.context
|
|
6030
|
+
// #403: `entity.context:` nests the module folder; undefined → flat.
|
|
6031
|
+
context: def.entity.context
|
|
6024
6032
|
});
|
|
6025
6033
|
}
|
|
6026
6034
|
entities.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -8297,10 +8305,11 @@ function generatedBanner(sourceDesc) {
|
|
|
8297
8305
|
function collectEntitiesBySurface(entities) {
|
|
8298
8306
|
const bySurface = /* @__PURE__ */ new Map();
|
|
8299
8307
|
for (const e of entities) {
|
|
8300
|
-
|
|
8301
|
-
|
|
8308
|
+
const surface = e.entity.surface;
|
|
8309
|
+
if (!surface) continue;
|
|
8310
|
+
const list = bySurface.get(surface) ?? [];
|
|
8302
8311
|
list.push(e.entity.name);
|
|
8303
|
-
bySurface.set(
|
|
8312
|
+
bySurface.set(surface, list);
|
|
8304
8313
|
}
|
|
8305
8314
|
for (const [surface, list] of bySurface) {
|
|
8306
8315
|
bySurface.set(surface, [...list].sort());
|
|
@@ -8668,9 +8677,15 @@ function printInfo(msg) {
|
|
|
8668
8677
|
}
|
|
8669
8678
|
|
|
8670
8679
|
// src/cli/commands/entity.ts
|
|
8671
|
-
function
|
|
8680
|
+
function resolveProvidersDir(ctx) {
|
|
8681
|
+
const fromConfig = ctx.config?.paths?.providers;
|
|
8682
|
+
return fromConfig != null ? path13.resolve(ctx.cwd, fromConfig) : path13.resolve(ctx.cwd, "definitions/providers");
|
|
8683
|
+
}
|
|
8684
|
+
function listEntityYamls2(dir, providersDir) {
|
|
8672
8685
|
if (!fs9.existsSync(dir)) return [];
|
|
8673
|
-
return findYamlFiles(dir
|
|
8686
|
+
return findYamlFiles(dir, {
|
|
8687
|
+
excludeDirs: providersDir ? [providersDir] : []
|
|
8688
|
+
});
|
|
8674
8689
|
}
|
|
8675
8690
|
function summarizePatternLabel(entity) {
|
|
8676
8691
|
if (typeof entity.pattern === "string" && entity.pattern.length > 0) {
|
|
@@ -8709,7 +8724,7 @@ async function summary(ctx) {
|
|
|
8709
8724
|
]
|
|
8710
8725
|
};
|
|
8711
8726
|
}
|
|
8712
|
-
const files = listEntityYamls2(ctx.entitiesDir);
|
|
8727
|
+
const files = listEntityYamls2(ctx.entitiesDir, resolveProvidersDir(ctx));
|
|
8713
8728
|
const rows = files.map(summarizeEntityFile).filter((r) => r !== null);
|
|
8714
8729
|
const patterns = new Set(rows.map((r) => r.pattern));
|
|
8715
8730
|
const queryCount = rows.reduce((sum, r) => sum + r.queries, 0);
|
|
@@ -8806,7 +8821,7 @@ var EntityNewCommand = class extends Command2 {
|
|
|
8806
8821
|
let targets = [];
|
|
8807
8822
|
if (this.all) {
|
|
8808
8823
|
const dir = ctx.entitiesDir ?? path13.resolve(ctx.cwd, "entities");
|
|
8809
|
-
targets = listEntityYamls2(dir);
|
|
8824
|
+
targets = listEntityYamls2(dir, resolveProvidersDir(ctx));
|
|
8810
8825
|
if (targets.length === 0) {
|
|
8811
8826
|
printError(`No entity YAML files found in ${dir}`);
|
|
8812
8827
|
return 1;
|
|
@@ -8824,12 +8839,15 @@ var EntityNewCommand = class extends Command2 {
|
|
|
8824
8839
|
if (result.success) {
|
|
8825
8840
|
validated.push({ file, name: result.definition.entity.name });
|
|
8826
8841
|
} else {
|
|
8827
|
-
invalid.push({ file, message: result.error });
|
|
8842
|
+
invalid.push({ file, message: result.error, details: result.details });
|
|
8828
8843
|
}
|
|
8829
8844
|
}
|
|
8830
8845
|
if (invalid.length > 0 && !this.continueOnError) {
|
|
8831
8846
|
for (const i of invalid) {
|
|
8832
8847
|
printError(`${path13.basename(i.file)} \u2014 ${i.message}`);
|
|
8848
|
+
for (const detail of i.details ?? []) {
|
|
8849
|
+
printError(` \u2022 ${detail}`);
|
|
8850
|
+
}
|
|
8833
8851
|
}
|
|
8834
8852
|
if (!isJsonMode()) {
|
|
8835
8853
|
return 1;
|
|
@@ -8837,7 +8855,9 @@ var EntityNewCommand = class extends Command2 {
|
|
|
8837
8855
|
}
|
|
8838
8856
|
const entitiesDirForEmits = ctx.entitiesDir ?? path13.resolve(ctx.cwd, "entities");
|
|
8839
8857
|
const eventsDirForEmits = resolveEventsDir(ctx);
|
|
8840
|
-
const allEntitiesForEmits = loadEntities(entitiesDirForEmits
|
|
8858
|
+
const allEntitiesForEmits = loadEntities(entitiesDirForEmits, {
|
|
8859
|
+
excludeDirs: [resolveProvidersDir(ctx)]
|
|
8860
|
+
}).entities;
|
|
8841
8861
|
const validatedNames = new Set(validated.map((v) => v.name));
|
|
8842
8862
|
const emitsTargetEntities = allEntitiesForEmits.filter(
|
|
8843
8863
|
(e) => validatedNames.has(e.name)
|
|
@@ -9169,19 +9189,16 @@ var EntityNewCommand = class extends Command2 {
|
|
|
9169
9189
|
}
|
|
9170
9190
|
let providerResult = null;
|
|
9171
9191
|
try {
|
|
9172
|
-
const providersDir = ctx
|
|
9173
|
-
ctx.cwd,
|
|
9174
|
-
ctx.config.paths.providers
|
|
9175
|
-
) : path13.resolve(ctx.cwd, "definitions/providers");
|
|
9192
|
+
const providersDir = resolveProvidersDir(ctx);
|
|
9176
9193
|
const providerOutputRoot = path13.resolve(
|
|
9177
9194
|
ctx.cwd,
|
|
9178
9195
|
backendSrcForHandlers,
|
|
9179
9196
|
"integrations/providers"
|
|
9180
9197
|
);
|
|
9181
9198
|
const entitySurfaces = fs9.existsSync(entitiesDir) ? collectEntitySurfaces(
|
|
9182
|
-
loadEntitiesFromYaml(
|
|
9183
|
-
(
|
|
9184
|
-
)
|
|
9199
|
+
loadEntitiesFromYaml(
|
|
9200
|
+
findYamlFiles(entitiesDir, { excludeDirs: [providersDir] })
|
|
9201
|
+
).successes.map((s) => s.definition)
|
|
9185
9202
|
) : /* @__PURE__ */ new Set();
|
|
9186
9203
|
const tsAliases = resolveTsconfigAliases(ctx.cwd);
|
|
9187
9204
|
providerResult = generateProviderModules({
|
|
@@ -9219,9 +9236,9 @@ var EntityNewCommand = class extends Command2 {
|
|
|
9219
9236
|
backendSrcForHandlers,
|
|
9220
9237
|
"integrations"
|
|
9221
9238
|
);
|
|
9222
|
-
const entityDefs = fs9.existsSync(entitiesDir) ? loadEntitiesFromYaml(
|
|
9223
|
-
(
|
|
9224
|
-
) : [];
|
|
9239
|
+
const entityDefs = fs9.existsSync(entitiesDir) ? loadEntitiesFromYaml(
|
|
9240
|
+
findYamlFiles(entitiesDir, { excludeDirs: [providersDir] })
|
|
9241
|
+
).successes.map((s) => s.definition) : [];
|
|
9225
9242
|
const loadedProviders = loadProvidersFromYaml(
|
|
9226
9243
|
findYamlFiles(providersDir)
|
|
9227
9244
|
).successes.map((s) => ({
|
|
@@ -9367,7 +9384,7 @@ var EntityListCommand = class extends Command2 {
|
|
|
9367
9384
|
printError("No entities directory found.");
|
|
9368
9385
|
return 1;
|
|
9369
9386
|
}
|
|
9370
|
-
const files = listEntityYamls2(ctx.entitiesDir);
|
|
9387
|
+
const files = listEntityYamls2(ctx.entitiesDir, resolveProvidersDir(ctx));
|
|
9371
9388
|
const rows = files.map(summarizeEntityFile).filter((r) => r !== null).filter((r) => this.pattern ? r.pattern === this.pattern : true);
|
|
9372
9389
|
if (isJsonMode()) {
|
|
9373
9390
|
printJson({
|