@metaobjectsdev/codegen-ts 0.24.4 → 0.25.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/README.md +37 -12
- package/dist/api-surface.d.ts +29 -0
- package/dist/api-surface.d.ts.map +1 -1
- package/dist/api-surface.js +47 -0
- package/dist/api-surface.js.map +1 -1
- package/dist/client-directive.d.ts +18 -0
- package/dist/client-directive.d.ts.map +1 -0
- package/dist/client-directive.js +37 -0
- package/dist/client-directive.js.map +1 -0
- package/dist/column-mapper.d.ts +12 -0
- package/dist/column-mapper.d.ts.map +1 -1
- package/dist/column-mapper.js +1 -0
- package/dist/column-mapper.js.map +1 -1
- package/dist/constants.d.ts +24 -11
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +54 -19
- package/dist/constants.js.map +1 -1
- package/dist/db-emitting.d.ts +6 -0
- package/dist/db-emitting.d.ts.map +1 -0
- package/dist/db-emitting.js +26 -0
- package/dist/db-emitting.js.map +1 -0
- package/dist/generator-registry.d.ts.map +1 -1
- package/dist/generator-registry.js +7 -1
- package/dist/generator-registry.js.map +1 -1
- package/dist/generator.d.ts +5 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js.map +1 -1
- package/dist/generators/agent-docs-file.d.ts +19 -0
- package/dist/generators/agent-docs-file.d.ts.map +1 -0
- package/dist/generators/agent-docs-file.js +257 -0
- package/dist/generators/agent-docs-file.js.map +1 -0
- package/dist/generators/agent-requirements-page.d.ts +22 -0
- package/dist/generators/agent-requirements-page.d.ts.map +1 -0
- package/dist/generators/agent-requirements-page.js +175 -0
- package/dist/generators/agent-requirements-page.js.map +1 -0
- package/dist/generators/agent-schema-input.d.ts +69 -0
- package/dist/generators/agent-schema-input.d.ts.map +1 -0
- package/dist/generators/agent-schema-input.js +23 -0
- package/dist/generators/agent-schema-input.js.map +1 -0
- package/dist/generators/agent-schema-page.d.ts +21 -0
- package/dist/generators/agent-schema-page.d.ts.map +1 -0
- package/dist/generators/agent-schema-page.js +241 -0
- package/dist/generators/agent-schema-page.js.map +1 -0
- package/dist/generators/agent-ui-page.d.ts +22 -0
- package/dist/generators/agent-ui-page.d.ts.map +1 -0
- package/dist/generators/agent-ui-page.js +253 -0
- package/dist/generators/agent-ui-page.js.map +1 -0
- package/dist/generators/api-docs-file.d.ts.map +1 -1
- package/dist/generators/api-docs-file.js +13 -6
- package/dist/generators/api-docs-file.js.map +1 -1
- package/dist/generators/api-model.d.ts +6 -0
- package/dist/generators/api-model.d.ts.map +1 -1
- package/dist/generators/api-model.js +54 -35
- package/dist/generators/api-model.js.map +1 -1
- package/dist/generators/callable-file.js +1 -1
- package/dist/generators/callable-file.js.map +1 -1
- package/dist/generators/docs-data-builder.d.ts.map +1 -1
- package/dist/generators/docs-data-builder.js +47 -2
- package/dist/generators/docs-data-builder.js.map +1 -1
- package/dist/generators/docs-data.d.ts +6 -2
- package/dist/generators/docs-data.d.ts.map +1 -1
- package/dist/generators/index.d.ts +3 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +4 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/names-file.d.ts +17 -0
- package/dist/generators/names-file.d.ts.map +1 -0
- package/dist/generators/names-file.js +95 -0
- package/dist/generators/names-file.js.map +1 -0
- package/dist/generators/requirements-markdown.d.ts +18 -1
- package/dist/generators/requirements-markdown.d.ts.map +1 -1
- package/dist/generators/requirements-markdown.js +11 -9
- package/dist/generators/requirements-markdown.js.map +1 -1
- package/dist/generators/routes-file-hono.d.ts +17 -2
- package/dist/generators/routes-file-hono.d.ts.map +1 -1
- package/dist/generators/routes-file-hono.js +15 -12
- package/dist/generators/routes-file-hono.js.map +1 -1
- package/dist/generators/routes-file.d.ts +17 -2
- package/dist/generators/routes-file.d.ts.map +1 -1
- package/dist/generators/routes-file.js +10 -8
- package/dist/generators/routes-file.js.map +1 -1
- package/dist/generators/template-generator.d.ts.map +1 -1
- package/dist/generators/template-generator.js +20 -6
- package/dist/generators/template-generator.js.map +1 -1
- package/dist/index.d.ts +21 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -4
- package/dist/index.js.map +1 -1
- package/dist/metaobjects-config.d.ts +67 -1
- package/dist/metaobjects-config.d.ts.map +1 -1
- package/dist/metaobjects-config.js +10 -1
- package/dist/metaobjects-config.js.map +1 -1
- package/dist/names.d.ts +275 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +434 -0
- package/dist/names.js.map +1 -0
- package/dist/naming.d.ts +17 -0
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +21 -0
- package/dist/naming.js.map +1 -1
- package/dist/overwrite-policy.d.ts.map +1 -1
- package/dist/overwrite-policy.js +15 -3
- package/dist/overwrite-policy.js.map +1 -1
- package/dist/payload-codegen.d.ts.map +1 -1
- package/dist/payload-codegen.js +4 -3
- package/dist/payload-codegen.js.map +1 -1
- package/dist/projection/extract-view-spec.d.ts +25 -1
- package/dist/projection/extract-view-spec.d.ts.map +1 -1
- package/dist/projection/extract-view-spec.js +79 -6
- package/dist/projection/extract-view-spec.js.map +1 -1
- package/dist/prompt-generator-gate.d.ts +27 -0
- package/dist/prompt-generator-gate.d.ts.map +1 -0
- package/dist/prompt-generator-gate.js +62 -0
- package/dist/prompt-generator-gate.js.map +1 -0
- package/dist/reference-templates.d.ts +22 -5
- package/dist/reference-templates.d.ts.map +1 -1
- package/dist/reference-templates.js +55 -21
- package/dist/reference-templates.js.map +1 -1
- package/dist/render-context.d.ts +20 -1
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js +2 -0
- package/dist/render-context.js.map +1 -1
- package/dist/retired-codegen-attrs.d.ts +32 -0
- package/dist/retired-codegen-attrs.d.ts.map +1 -0
- package/dist/retired-codegen-attrs.js +47 -0
- package/dist/retired-codegen-attrs.js.map +1 -0
- package/dist/routes-expose.d.ts +63 -0
- package/dist/routes-expose.d.ts.map +1 -0
- package/dist/routes-expose.js +57 -0
- package/dist/routes-expose.js.map +1 -0
- package/dist/runner.d.ts +18 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +115 -7
- package/dist/runner.js.map +1 -1
- package/dist/templates/callable-file.d.ts +7 -2
- package/dist/templates/callable-file.d.ts.map +1 -1
- package/dist/templates/callable-file.js +54 -9
- package/dist/templates/callable-file.js.map +1 -1
- package/dist/templates/drizzle-schema.d.ts.map +1 -1
- package/dist/templates/drizzle-schema.js +236 -65
- package/dist/templates/drizzle-schema.js.map +1 -1
- package/dist/templates/entity-constants.d.ts +15 -6
- package/dist/templates/entity-constants.d.ts.map +1 -1
- package/dist/templates/entity-constants.js +85 -161
- package/dist/templates/entity-constants.js.map +1 -1
- package/dist/templates/entity-file.d.ts.map +1 -1
- package/dist/templates/entity-file.js +38 -4
- package/dist/templates/entity-file.js.map +1 -1
- package/dist/templates/entity-meta-file.d.ts +15 -1
- package/dist/templates/entity-meta-file.d.ts.map +1 -1
- package/dist/templates/entity-meta-file.js +12 -2
- package/dist/templates/entity-meta-file.js.map +1 -1
- package/dist/templates/entity-ui-descriptor.d.ts +112 -0
- package/dist/templates/entity-ui-descriptor.d.ts.map +1 -0
- package/dist/templates/entity-ui-descriptor.js +238 -0
- package/dist/templates/entity-ui-descriptor.js.map +1 -0
- package/dist/templates/field-meta.d.ts +46 -9
- package/dist/templates/field-meta.d.ts.map +1 -1
- package/dist/templates/field-meta.js +95 -21
- package/dist/templates/field-meta.js.map +1 -1
- package/dist/templates/names-decl.d.ts +38 -0
- package/dist/templates/names-decl.d.ts.map +1 -0
- package/dist/templates/names-decl.js +104 -0
- package/dist/templates/names-decl.js.map +1 -0
- package/dist/templates/output-parser.d.ts.map +1 -1
- package/dist/templates/output-parser.js +54 -8
- package/dist/templates/output-parser.js.map +1 -1
- package/dist/templates/projection-decl.d.ts +21 -0
- package/dist/templates/projection-decl.d.ts.map +1 -1
- package/dist/templates/projection-decl.js +53 -29
- package/dist/templates/projection-decl.js.map +1 -1
- package/dist/templates/queries-file.d.ts.map +1 -1
- package/dist/templates/queries-file.js +17 -2
- package/dist/templates/queries-file.js.map +1 -1
- package/dist/templates/render-helper.d.ts.map +1 -1
- package/dist/templates/render-helper.js +3 -1
- package/dist/templates/render-helper.js.map +1 -1
- package/dist/templates/requirement-test.d.ts.map +1 -1
- package/dist/templates/requirement-test.js +48 -21
- package/dist/templates/requirement-test.js.map +1 -1
- package/dist/templates/routes-file-hono.d.ts +2 -1
- package/dist/templates/routes-file-hono.d.ts.map +1 -1
- package/dist/templates/routes-file-hono.js +11 -2
- package/dist/templates/routes-file-hono.js.map +1 -1
- package/dist/templates/routes-file.d.ts +2 -1
- package/dist/templates/routes-file.d.ts.map +1 -1
- package/dist/templates/routes-file.js +54 -21
- package/dist/templates/routes-file.js.map +1 -1
- package/dist/templates/value-object-file.d.ts.map +1 -1
- package/dist/templates/value-object-file.js +15 -1
- package/dist/templates/value-object-file.js.map +1 -1
- package/dist/templates/view-decl.d.ts +37 -1
- package/dist/templates/view-decl.d.ts.map +1 -1
- package/dist/templates/view-decl.js +27 -3
- package/dist/templates/view-decl.js.map +1 -1
- package/dist/templates/zod-validators.d.ts +21 -0
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +40 -5
- package/dist/templates/zod-validators.js.map +1 -1
- package/dist/view-context.d.ts +24 -0
- package/dist/view-context.d.ts.map +1 -0
- package/dist/view-context.js +81 -0
- package/dist/view-context.js.map +1 -0
- package/package.json +6 -6
- package/src/api-surface.ts +53 -0
- package/src/client-directive.ts +36 -0
- package/src/column-mapper.ts +13 -0
- package/src/constants.ts +91 -16
- package/src/db-emitting.ts +31 -0
- package/src/generator-registry.ts +7 -0
- package/src/generator.ts +5 -0
- package/src/generators/agent-docs-file.ts +297 -0
- package/src/generators/agent-requirements-page.ts +199 -0
- package/src/generators/agent-schema-input.ts +88 -0
- package/src/generators/agent-schema-page.ts +297 -0
- package/src/generators/agent-ui-page.ts +290 -0
- package/src/generators/api-docs-file.ts +13 -6
- package/src/generators/api-model.ts +63 -34
- package/src/generators/callable-file.ts +1 -1
- package/src/generators/docs-data-builder.ts +46 -2
- package/src/generators/docs-data.ts +6 -2
- package/src/generators/index.ts +10 -0
- package/src/generators/names-file.ts +98 -0
- package/src/generators/requirements-markdown.ts +36 -2
- package/src/generators/routes-file-hono.ts +26 -15
- package/src/generators/routes-file.ts +20 -7
- package/src/generators/template-generator.ts +20 -6
- package/src/index.ts +63 -5
- package/src/metaobjects-config.ts +78 -2
- package/src/names.ts +630 -0
- package/src/naming.ts +23 -0
- package/src/overwrite-policy.ts +15 -3
- package/src/payload-codegen.ts +4 -3
- package/src/projection/extract-view-spec.ts +85 -6
- package/src/prompt-generator-gate.ts +73 -0
- package/src/reference/barrel.ts +3 -0
- package/src/reference/entity.ts +28 -2
- package/src/reference/names.ts +125 -0
- package/src/reference/queries.ts +14 -2
- package/src/reference/routes-hono.ts +113 -0
- package/src/reference/routes.ts +30 -9
- package/src/reference-templates.ts +61 -21
- package/src/render-context.ts +22 -1
- package/src/retired-codegen-attrs.ts +52 -0
- package/src/routes-expose.ts +86 -0
- package/src/runner.ts +129 -11
- package/src/templates/callable-file.ts +60 -8
- package/src/templates/drizzle-schema.ts +248 -60
- package/src/templates/entity-constants.ts +95 -184
- package/src/templates/entity-file.ts +46 -4
- package/src/templates/entity-meta-file.ts +16 -3
- package/src/templates/entity-ui-descriptor.ts +305 -0
- package/src/templates/field-meta.ts +100 -19
- package/src/templates/names-decl.ts +165 -0
- package/src/templates/output-parser.ts +54 -7
- package/src/templates/projection-decl.ts +72 -31
- package/src/templates/queries-file.ts +17 -2
- package/src/templates/render-helper.ts +3 -1
- package/src/templates/requirement-test.ts +49 -18
- package/src/templates/routes-file-hono.ts +14 -2
- package/src/templates/routes-file.ts +69 -24
- package/src/templates/value-object-file.ts +15 -1
- package/src/templates/view-decl.ts +53 -3
- package/src/templates/zod-validators.ts +41 -5
- package/src/view-context.ts +88 -0
package/src/reference/routes.ts
CHANGED
|
@@ -6,15 +6,26 @@
|
|
|
6
6
|
// shebang is `#!/usr/bin/env node` — so it runs under NODE even in a Bun project. Do not
|
|
7
7
|
// reach for `Bun.*` globals here; they are undefined and take the whole run down with
|
|
8
8
|
// `Bun is not defined`. Use `node:` builtins instead.
|
|
9
|
+
// targets: Fastify. The emitted file imports `mountCrudRoutes` from
|
|
10
|
+
// `@metaobjectsdev/runtime-ts/drizzle-fastify` and binds a
|
|
11
|
+
// module-singleton `db`. THIS is the file to retarget for another HTTP
|
|
12
|
+
// framework; see also the routes-hono template, whose deps-injected shape
|
|
13
|
+
// ports more easily to hosts that hand you a request.
|
|
9
14
|
// use-when: you want generated Fastify REST routes per entity. Drop it and hand-write routes
|
|
10
|
-
// if you need bespoke endpoints — or keep it and
|
|
15
|
+
// if you need bespoke endpoints — or keep it and register extra handlers YOURSELF
|
|
16
|
+
// alongside the generated `register<Entity>Routes(app)` call. Nothing here
|
|
17
|
+
// discovers a sibling module: a `<Entity>.extra.ts` next to the output is a naming
|
|
18
|
+
// convention, not a plugin point, so its handlers only mount if your server calls them.
|
|
11
19
|
// emits: <target>/<Entity>.routes.ts — full CRUD for write-through entities, read-only
|
|
12
20
|
// (GET list + GET :id) for projections, polymorphic + per-subtype for TPH bases.
|
|
13
21
|
// Skipped for any sourceless object (incl. every object.value, source-less by
|
|
14
22
|
// value purity) and for TPH subtypes — no source.rdb means no table/allowlist
|
|
15
23
|
// for a routes file to import (#248 R2).
|
|
16
|
-
// customize: this generator (filter, output path,
|
|
17
|
-
//
|
|
24
|
+
// customize: this generator (filter, output path, target) is YOURS — edit it freely.
|
|
25
|
+
// Decide per generator what you consume: wire only the generators whose
|
|
26
|
+
// output you actually import, and narrow this one with its `filter`. There
|
|
27
|
+
// is no `@emit*` metadata attribute — those were never registered
|
|
28
|
+
// vocabulary, so `meta verify` rejects them (ERR_UNKNOWN_ATTR). The route *composition* itself is richer than the others
|
|
18
29
|
// (M:N junction traversal, TPH per-subtype route sets), so it stays in the engine via
|
|
19
30
|
// `renderRoutesFile`. To own the composition too, copy `renderRoutesFile`'s body out
|
|
20
31
|
// of the package source — it dispatches projection → mountReadOnlyCrudRoutes,
|
|
@@ -29,15 +40,26 @@ import {
|
|
|
29
40
|
type Generator,
|
|
30
41
|
type GeneratorFactory,
|
|
31
42
|
renderRoutesFile,
|
|
43
|
+
resolveExpose,
|
|
44
|
+
type ExposeOption,
|
|
32
45
|
isTphSubtype,
|
|
33
46
|
hasAnyRdbSource,
|
|
34
47
|
formatTs,
|
|
35
48
|
entityOutputPath,
|
|
36
|
-
CODEGEN_ATTR_EMIT_ROUTES,
|
|
37
49
|
} from "@metaobjectsdev/codegen-ts";
|
|
38
50
|
|
|
39
51
|
export interface RoutesFileOpts {
|
|
40
52
|
filter?: (entity: MetaObject) => boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Which CRUD verbs the emitted file mounts (#348). Verbs, or a per-entity function;
|
|
55
|
+
* absent means all five and emits byte-identical output.
|
|
56
|
+
*
|
|
57
|
+
* routesFile({ expose: (e) => e.name === "AuditEntry" ? ["list", "get"] : undefined })
|
|
58
|
+
*
|
|
59
|
+
* A `filter` cannot express this — it decides whether the file emits AT ALL, so it can
|
|
60
|
+
* only remove the whole surface, not restrict it to a subset of verbs.
|
|
61
|
+
*/
|
|
62
|
+
expose?: ExposeOption;
|
|
41
63
|
target?: string;
|
|
42
64
|
}
|
|
43
65
|
|
|
@@ -45,21 +67,20 @@ export const routesFile = function routesFile(opts?: RoutesFileOpts): Generator
|
|
|
45
67
|
const userFilter = opts?.filter ?? (() => true);
|
|
46
68
|
const generator: Generator = {
|
|
47
69
|
name: "routes-file",
|
|
48
|
-
//
|
|
49
|
-
//
|
|
70
|
+
// TPH subtypes get no standalone routes file (their routes live in the discriminator
|
|
71
|
+
// base's); AND-composed with your filter.
|
|
50
72
|
// #248 R2: an object with no declared/inherited source.rdb (of ANY kind) isn't
|
|
51
73
|
// backed by any store — routes against it would import Drizzle table/allowlist
|
|
52
74
|
// exports the entity file never emits. Gated by hasAnyRdbSource.
|
|
53
75
|
filter: (e: MetaObject) =>
|
|
54
|
-
|
|
55
|
-
e.attr(CODEGEN_ATTR_EMIT_ROUTES) !== false && hasAnyRdbSource(e) && !isTphSubtype(e) && userFilter(e),
|
|
76
|
+
hasAnyRdbSource(e) && !isTphSubtype(e) && userFilter(e),
|
|
56
77
|
generate: perEntity(async (entity, ctx) => {
|
|
57
78
|
if (!ctx.renderContext) {
|
|
58
79
|
throw new Error("routes-file: renderContext is required (provided by runGen)");
|
|
59
80
|
}
|
|
60
81
|
return {
|
|
61
82
|
path: entityOutputPath(ctx.config.outputLayout ?? "flat", entity.package, `${entity.name}.routes.ts`),
|
|
62
|
-
content: await formatTs(renderRoutesFile(entity, ctx.renderContext)),
|
|
83
|
+
content: await formatTs(renderRoutesFile(entity, ctx.renderContext, resolveExpose(entity, opts?.expose))),
|
|
63
84
|
};
|
|
64
85
|
}),
|
|
65
86
|
};
|
|
@@ -13,37 +13,77 @@ import { dirname, join } from "node:path";
|
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
|
|
15
15
|
/** Basenames (no extension) of the copyable reference generators shipped in `src/reference/`. */
|
|
16
|
-
export const REFERENCE_GENERATOR_NAMES = ["entity", "queries", "routes", "barrel"] as const;
|
|
16
|
+
export const REFERENCE_GENERATOR_NAMES = ["entity", "queries", "routes", "routes-hono", "barrel", "names"] as const;
|
|
17
17
|
export type ReferenceGeneratorName = (typeof REFERENCE_GENERATOR_NAMES)[number];
|
|
18
18
|
|
|
19
|
-
/** A directory is
|
|
20
|
-
function isReferenceRoot(dir: string): boolean {
|
|
21
|
-
return existsSync(join(dir,
|
|
19
|
+
/** A directory is a reference root iff it holds the first template the reader was told to expect. */
|
|
20
|
+
function isReferenceRoot(dir: string, sentinel: string): boolean {
|
|
21
|
+
return existsSync(join(dir, `${sentinel}.ts`));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* FR-040 §4.1 — build a reference-template reader for ONE package. `moduleUrl` is the
|
|
26
|
+
* calling module's `import.meta.url`, so each package resolves its OWN `src/reference/`.
|
|
27
|
+
* `names[0]` is the sentinel that identifies the directory.
|
|
28
|
+
*
|
|
29
|
+
* Works in dev (module runs from `src/`, templates at `./reference/`) and in a published
|
|
30
|
+
* install (module runs from `dist/`, templates at `../src/reference/`, since `src/` ships
|
|
31
|
+
* alongside `dist/`). Walks up checking both layouts at each level.
|
|
29
32
|
*/
|
|
30
|
-
export function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
export function makeReferenceReader(moduleUrl: string, names: readonly [string, ...string[]]) {
|
|
34
|
+
// A non-empty tuple type, so `names[0]` is `string` outright — this used to need a
|
|
35
|
+
// runtime empty-check plus a re-bound const, because `noUncheckedIndexedAccess`
|
|
36
|
+
// narrowing does not survive into the closures below. Every call site already passes
|
|
37
|
+
// an `as const` tuple or an array literal, so the type costs nothing and deletes both.
|
|
38
|
+
const sentinel: string = names[0];
|
|
39
|
+
|
|
40
|
+
function resolveReferenceRoot(): string {
|
|
41
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
42
|
+
for (let i = 0; i < 8; i++) {
|
|
43
|
+
for (const candidate of [join(dir, "reference"), join(dir, "src", "reference")]) {
|
|
44
|
+
if (isReferenceRoot(candidate, sentinel)) return candidate;
|
|
45
|
+
}
|
|
46
|
+
const parent = dirname(dir);
|
|
47
|
+
if (parent === dir) break;
|
|
48
|
+
dir = parent;
|
|
35
49
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
throw new Error(
|
|
51
|
+
"reference templates not found — looked for `reference/` and `src/reference/` " +
|
|
52
|
+
`walking up from ${dir}.`,
|
|
53
|
+
);
|
|
39
54
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
resolveReferenceRoot,
|
|
58
|
+
/**
|
|
59
|
+
* Read one template by name. The name is checked against `names` FIRST: this is a
|
|
60
|
+
* public export, so `name` is an untrusted `string` at runtime (the per-package
|
|
61
|
+
* wrappers narrow to a literal union, but types are erased). Without the check it
|
|
62
|
+
* interpolates straight into a path, so any caller threading a CLI argument through
|
|
63
|
+
* reads whatever file the string points at. The allowlist is already a parameter —
|
|
64
|
+
* enforcing it here makes the guarantee structural instead of something every call
|
|
65
|
+
* site has to remember, and it is the reason `meta eject` needs no check of its own.
|
|
66
|
+
*/
|
|
67
|
+
readReferenceTemplate: (name: string): string => {
|
|
68
|
+
if (!names.includes(name)) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`unknown reference template "${name}". Available: ${names.join(", ")}.`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return readFileSync(join(resolveReferenceRoot(), `${name}.ts`), "utf8");
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// This package's own reader — the back-compatible named exports `meta init` uses.
|
|
79
|
+
const ownReader = makeReferenceReader(import.meta.url, REFERENCE_GENERATOR_NAMES);
|
|
80
|
+
|
|
81
|
+
/** Resolve the `src/reference/` directory holding this package's reference generators. */
|
|
82
|
+
export function resolveReferenceRoot(): string {
|
|
83
|
+
return ownReader.resolveReferenceRoot();
|
|
44
84
|
}
|
|
45
85
|
|
|
46
86
|
/** Read the raw source of one reference generator (e.g. `"entity"` → the text of `entity.ts`). */
|
|
47
87
|
export function readReferenceTemplate(name: ReferenceGeneratorName): string {
|
|
48
|
-
return
|
|
88
|
+
return ownReader.readReferenceTemplate(name);
|
|
49
89
|
}
|
package/src/render-context.ts
CHANGED
|
@@ -52,12 +52,29 @@ export interface RenderContext {
|
|
|
52
52
|
* doc comment on `MetaobjectsGenConfig.timestampMode` in metaobjects-config.ts.
|
|
53
53
|
*/
|
|
54
54
|
timestampMode: "date" | "string";
|
|
55
|
+
/**
|
|
56
|
+
* Prepend `"use client";` to generated CLIENT artifacts (forms, hooks, grid
|
|
57
|
+
* columns, grid hooks). Defaults to false. A bundler-topology fact the adopter
|
|
58
|
+
* declares — see `MetaobjectsGenConfig.clientDirective` for why it is config
|
|
59
|
+
* rather than metadata, and why the default is off. FR-040 §6.4.
|
|
60
|
+
*/
|
|
61
|
+
clientDirective: boolean;
|
|
55
62
|
/** Path prefix applied to generated route registrations + hook fetch URLs. Defaults to "". */
|
|
56
63
|
apiPrefix: string;
|
|
57
64
|
/** Whether abstract entities emit their shape artifact (type-only interface / value-object file). Defaults to true. Instance/write artifacts are never emitted for abstract entities regardless. */
|
|
58
65
|
emitAbstractShapes: boolean;
|
|
59
66
|
/** Output layout mode: "flat" (default) — all files in outDir; "package" — sub-paths from entity metadata package. */
|
|
60
67
|
outputLayout: OutputLayout;
|
|
68
|
+
/**
|
|
69
|
+
* §A6 — whether the run emits the `<Entity>Names` artifact (the runner aggregates the
|
|
70
|
+
* suite's `emitsNames` markers; `ctx.config.includeNames` carries the same fact for
|
|
71
|
+
* generators). A template may reference those constants ONLY when this is true: the
|
|
72
|
+
* names generator is opt-in under ADR-0034 scaffold-and-own, so an unconditional
|
|
73
|
+
* import would break every project that has not enabled it. Defaults to FALSE, which
|
|
74
|
+
* is what keeps output byte-identical for such a project — and for every bare-context
|
|
75
|
+
* caller (unit tests, generators invoked outside runGen).
|
|
76
|
+
*/
|
|
77
|
+
includeNames: boolean;
|
|
61
78
|
/**
|
|
62
79
|
* Resolve an entity name to its Drizzle collection (table) variable name,
|
|
63
80
|
* applying the project's pluralization config + per-entity overrides. Every
|
|
@@ -101,14 +118,16 @@ export interface RenderContext {
|
|
|
101
118
|
}
|
|
102
119
|
|
|
103
120
|
/** Optional shape — `extStyle`, `omImport`, `columnNamingStrategy`, `apiPrefix`, `outputLayout`, and `packageOf` default if omitted. `packageOf` defaults to an empty Map (correct for flat layout; `runGen` always provides the real map). `collectionName` is built from `pluralizeCollections` + `collectionNameOverrides` (both default to always-pluralize). */
|
|
104
|
-
export type RenderContextInput = Omit<RenderContext, "extStyle" | "omImport" | "columnNamingStrategy" | "timestampMode" | "apiPrefix" | "emitAbstractShapes" | "outputLayout" | "packageOf" | "valueObjectNames" | "valueObjectEmittedName" | "resolveValueObjectName" | "selfTarget" | "entityModuleTarget" | "collectionName"> & {
|
|
121
|
+
export type RenderContextInput = Omit<RenderContext, "extStyle" | "omImport" | "columnNamingStrategy" | "timestampMode" | "clientDirective" | "apiPrefix" | "emitAbstractShapes" | "outputLayout" | "includeNames" | "packageOf" | "valueObjectNames" | "valueObjectEmittedName" | "resolveValueObjectName" | "selfTarget" | "entityModuleTarget" | "collectionName"> & {
|
|
105
122
|
extStyle?: ExtStyle;
|
|
106
123
|
omImport?: string;
|
|
107
124
|
columnNamingStrategy?: ColumnNamingStrategy;
|
|
108
125
|
timestampMode?: "date" | "string";
|
|
126
|
+
clientDirective?: boolean;
|
|
109
127
|
apiPrefix?: string;
|
|
110
128
|
emitAbstractShapes?: boolean;
|
|
111
129
|
outputLayout?: OutputLayout;
|
|
130
|
+
includeNames?: boolean;
|
|
112
131
|
packageOf?: Map<string, string | undefined>;
|
|
113
132
|
/** ADR-0044/#228 value-object emitted-name map (resolutionKey → emitted name).
|
|
114
133
|
* Defaults to an empty Map — bare names, byte-identical to pre-#228 output.
|
|
@@ -184,9 +203,11 @@ export function makeRenderContext(opts: RenderContextInput): RenderContext {
|
|
|
184
203
|
// a unit test or a generator invoked outside `runGen`, must get the same
|
|
185
204
|
// safe-no-op guarantee). See MetaobjectsGenConfig.timestampMode's doc comment.
|
|
186
205
|
timestampMode: opts.dialect === "sqlite" ? "string" : (opts.timestampMode ?? "string"),
|
|
206
|
+
clientDirective: opts.clientDirective ?? false,
|
|
187
207
|
apiPrefix: opts.apiPrefix ?? "",
|
|
188
208
|
emitAbstractShapes: opts.emitAbstractShapes ?? true,
|
|
189
209
|
outputLayout,
|
|
210
|
+
includeNames: opts.includeNames ?? false,
|
|
190
211
|
packageOf: opts.packageOf ?? new Map(),
|
|
191
212
|
valueObjectNames,
|
|
192
213
|
valueObjectEmittedName: (obj: MetaData) => valueObjectNames.get(obj.resolutionKey()) ?? obj.name,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { MetaObject } from "@metaobjectsdev/metadata";
|
|
2
|
+
import { RETIRED_CODEGEN_ATTRS } from "./constants.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `@emitRoutes` / `@emitTanstack` / `@emitForm` / `@emitGrid` / `@emitAngular` were read
|
|
6
|
+
* off metadata by generator filters and were never registered metamodel vocabulary. Under
|
|
7
|
+
* the strict loader — which is what `meta verify` runs — every one of them is
|
|
8
|
+
* `ERR_UNKNOWN_ATTR`, while `meta gen` loads non-strict and honoured them. That is the
|
|
9
|
+
* defect: an adopter who authored the documented opt-out got working suppression AND a red
|
|
10
|
+
* `meta verify`, with nothing connecting the two.
|
|
11
|
+
*
|
|
12
|
+
* The reads are gone (see constants.ts for why registering them was refused rather than
|
|
13
|
+
* the other way round). But a project can be sitting on a WORKING `@emitRoutes: false`
|
|
14
|
+
* today, and deleting the read alone would start writing that suppressed file with no
|
|
15
|
+
* explanation — a silent behaviour change on upgrade, which is the one outcome not open
|
|
16
|
+
* to us. So the run says it.
|
|
17
|
+
*
|
|
18
|
+
* This follows the `layout.dataGrid` precedent (#287, data-grid-gate.ts) and the prompt
|
|
19
|
+
* generator gate beside it: tell the adopter at `meta gen` time rather than in a doc line
|
|
20
|
+
* that gets missed the same way the original one was. Warning only — the exit code is
|
|
21
|
+
* untouched, and `--dry-run` reports it too, since generators run before the write phase
|
|
22
|
+
* branches.
|
|
23
|
+
*
|
|
24
|
+
* It lives in the RUNNER, not in a generator, for two reasons. It is a model-level
|
|
25
|
+
* observation, so it must fire exactly ONCE per run rather than once per generator that
|
|
26
|
+
* happens to be wired; and the attribute is equally stale whether or not the generator it
|
|
27
|
+
* used to suppress is wired at all, so a generator-local check would go quiet in exactly
|
|
28
|
+
* the project that dropped the generator and kept the attribute.
|
|
29
|
+
*
|
|
30
|
+
* Self-extinguishing: removing the attribute from the metadata silences it forever. That
|
|
31
|
+
* is also the fix, since the same edit is what makes `meta verify` pass.
|
|
32
|
+
*/
|
|
33
|
+
export function warnRetiredCodegenAttrs(
|
|
34
|
+
entities: readonly MetaObject[],
|
|
35
|
+
warn: (msg: string) => void,
|
|
36
|
+
): void {
|
|
37
|
+
for (const { name, replacement } of RETIRED_CODEGEN_ATTRS) {
|
|
38
|
+
// ADR-0039: resolving — an INHERITED flag suppressed emission exactly as an own one
|
|
39
|
+
// did, so an own-only read here would leave the inheriting adopter unwarned while
|
|
40
|
+
// their output silently changed. `hasAttr` rather than a value comparison: an
|
|
41
|
+
// `@emitForm: true` was a no-op that still fails `meta verify`, so it is just as
|
|
42
|
+
// stale as the `false` that did something.
|
|
43
|
+
const carriers = entities.filter((e) => e.hasAttr(name));
|
|
44
|
+
if (carriers.length === 0) continue;
|
|
45
|
+
warn(
|
|
46
|
+
`@${name} on ${carriers.map((e) => e.name).join(", ")} is no longer read by any ` +
|
|
47
|
+
`generator — it was never registered metamodel vocabulary, so \`meta verify\` ` +
|
|
48
|
+
`rejects it outright with ERR_UNKNOWN_ATTR. Decide per generator what you ` +
|
|
49
|
+
`consume: ${replacement}. Remove the attribute to silence this.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { MetaObject } from "@metaobjectsdev/metadata";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The five CRUD verbs a generated routes file can mount.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors `CrudVerb` in `@metaobjectsdev/runtime-ts` — both the fastify and hono mount
|
|
7
|
+
* modules declare it identically. It is RESTATED rather than imported because codegen-ts
|
|
8
|
+
* does not depend on runtime-ts: codegen emits a call to that helper, it never links
|
|
9
|
+
* against it. Pinned against both runtime declarations by test/routes-expose.test.ts, so
|
|
10
|
+
* the two cannot drift the way the cell-renderer keys drifted from the view registry.
|
|
11
|
+
*/
|
|
12
|
+
export const CRUD_VERBS = ["list", "get", "create", "update", "delete"] as const;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The verbs a TPH discriminator BASE serves at its own path.
|
|
16
|
+
*
|
|
17
|
+
* Read-only BY CONSTRUCTION: the discriminated union has no single writable shape, so the
|
|
18
|
+
* base mount can never carry `create`/`update`/`delete` — an author-supplied `expose`
|
|
19
|
+
* INTERSECTS with this set rather than replacing it, and may narrow to just `list`.
|
|
20
|
+
* Writes live on the per-subtype mounts at `<base path>/<segment>`.
|
|
21
|
+
*
|
|
22
|
+
* Named here because two places need it and a second literal would be a second answer:
|
|
23
|
+
* `routes-file.ts` emits the mount, and `api-model.ts` documents it. They disagreed —
|
|
24
|
+
* the api surface documented POST/PATCH/DELETE on a base path that serves none, under a
|
|
25
|
+
* comment claiming the documented paths "match the generated routes exactly".
|
|
26
|
+
*/
|
|
27
|
+
export const TPH_POLYMORPHIC_VERBS = ["list", "get"] as const;
|
|
28
|
+
export type CrudVerb = (typeof CRUD_VERBS)[number];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Which CRUD verbs a generated routes file mounts (#348).
|
|
32
|
+
*
|
|
33
|
+
* A `filter` cannot express this. `filter` decides whether the file emits AT ALL, per
|
|
34
|
+
* entity, so it can only remove the whole surface; restricting to a SUBSET of verbs is a
|
|
35
|
+
* different axis. That is why this is a generator option rather than the "narrow it with
|
|
36
|
+
* `filter`" remedy that answered the retired `@emit*` attributes — the same reasoning that
|
|
37
|
+
* made a TPH subtype's opt-IN grid `tphSubtypeGrids` rather than a filter.
|
|
38
|
+
*
|
|
39
|
+
* It is deliberately NOT metadata. Which verbs a deployment exposes is a property of the
|
|
40
|
+
* app, not of the model: the same entity is read-only in one service and writable in
|
|
41
|
+
* another, and an attribute would force one answer into the shared spine.
|
|
42
|
+
*
|
|
43
|
+
* Absent — or a function returning `undefined` for an entity — means all five, and emits
|
|
44
|
+
* output byte-identical to before this option existed.
|
|
45
|
+
*/
|
|
46
|
+
export type ExposeOption =
|
|
47
|
+
| readonly CrudVerb[]
|
|
48
|
+
| ((entity: MetaObject) => readonly CrudVerb[] | undefined);
|
|
49
|
+
|
|
50
|
+
/** Resolve the option for one entity. `undefined` means "mount all five". */
|
|
51
|
+
export function resolveExpose(
|
|
52
|
+
entity: MetaObject,
|
|
53
|
+
expose: ExposeOption | undefined,
|
|
54
|
+
): readonly CrudVerb[] | undefined {
|
|
55
|
+
if (expose === undefined) return undefined;
|
|
56
|
+
return typeof expose === "function" ? expose(entity) : expose;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Narrow a mount whose verb set is already fixed by construction.
|
|
61
|
+
*
|
|
62
|
+
* A TPH polymorphic mount is read-only (`["list", "get"]`) because the discriminated union
|
|
63
|
+
* has no single writable shape. An author-supplied `expose` may narrow that further but
|
|
64
|
+
* must never widen it, so this INTERSECTS rather than replaces: mounting `create` on a
|
|
65
|
+
* surface that cannot serve it would emit a route that fails at runtime, and a wrong
|
|
66
|
+
* endpoint is worse than a missing one.
|
|
67
|
+
*/
|
|
68
|
+
export function intersectExpose(
|
|
69
|
+
fixed: readonly CrudVerb[],
|
|
70
|
+
requested: readonly CrudVerb[] | undefined,
|
|
71
|
+
): readonly CrudVerb[] {
|
|
72
|
+
if (requested === undefined) return fixed;
|
|
73
|
+
const want = new Set<string>(requested);
|
|
74
|
+
return fixed.filter((v) => want.has(v));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The `expose: [...]` line for a mount call, or "" when every verb mounts.
|
|
79
|
+
*
|
|
80
|
+
* Emitting nothing rather than the full list keeps output byte-identical for every
|
|
81
|
+
* project that does not use the option.
|
|
82
|
+
*/
|
|
83
|
+
export function exposeLine(verbs: readonly CrudVerb[] | undefined, indent: string): string {
|
|
84
|
+
if (verbs === undefined) return "";
|
|
85
|
+
return `\n${indent}expose: [${verbs.map((v) => JSON.stringify(v)).join(", ")}],`;
|
|
86
|
+
}
|
package/src/runner.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { join, relative, resolve, isAbsolute, dirname } from "node:path";
|
|
2
|
+
import { warnMissingPromptGenerators } from "./prompt-generator-gate.js";
|
|
3
|
+
import { warnRetiredCodegenAttrs } from "./retired-codegen-attrs.js";
|
|
2
4
|
import { tmpdir } from "node:os";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -6,6 +8,7 @@ import type { MetaData, MetaObject } from "@metaobjectsdev/metadata";
|
|
|
6
8
|
import { isMetaRoot, OBJECT_SUBTYPE_VALUE, FIELD_SUBTYPE_TIMESTAMP, FIELD_ATTR_FILTERABLE } from "@metaobjectsdev/metadata";
|
|
7
9
|
import { assignEmittedNames } from "./naming/collision-names.js";
|
|
8
10
|
import { isAbstract } from "./instance-artifacts.js";
|
|
11
|
+
import { dbEmittingObjects, missingDialectMessage } from "./db-emitting.js";
|
|
9
12
|
import { hasAnyRdbSource } from "./source-detect.js";
|
|
10
13
|
import type { Generator, GenContext, EmittedFile } from "./generator.js";
|
|
11
14
|
import type { MetaobjectsGenConfig } from "./metaobjects-config.js";
|
|
@@ -127,6 +130,50 @@ function engineVersion(): string | undefined {
|
|
|
127
130
|
}
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
/** The release in which `$apiPrefix` left the generated entity descriptor. */
|
|
134
|
+
const BASE_URL_MOVE_VERSION = "0.25.0";
|
|
135
|
+
|
|
136
|
+
/** `N.N.N` only. Anything else — a prerelease tag, a sentinel — is deliberately unordered. */
|
|
137
|
+
function orderable(v: string | undefined): [number, number, number] | undefined {
|
|
138
|
+
if (v === undefined) return undefined;
|
|
139
|
+
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v);
|
|
140
|
+
return m === null ? undefined : [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* True exactly on the FIRST gen after upgrading past the release that moved the API base
|
|
145
|
+
* URL out of the entity descriptor, and only for a project that actually had a prefix to
|
|
146
|
+
* move. Exported for test.
|
|
147
|
+
*
|
|
148
|
+
* Keyed on the #232 engine stamp rather than on `apiPrefix` alone so the note is
|
|
149
|
+
* self-extinguishing: whether the app passed `baseUrl` to its provider is runtime code
|
|
150
|
+
* `meta gen` cannot see, so a prefix-only trigger would nag for ever with no way to
|
|
151
|
+
* satisfy it — the cry-wolf failure that got the `timestampMode` warning deleted from
|
|
152
|
+
* this file (see `codegen-ts-tanstack/src/data-grid-gate.ts`).
|
|
153
|
+
*
|
|
154
|
+
* Silence is the default for an absent or unorderable recorded version. That is the
|
|
155
|
+
* OPPOSITE call to the 0.24.5 agent-context staleness nudge, and deliberately: there an
|
|
156
|
+
* unknown version meant "cannot prove it is fresh, so nudge"; here it means "no gen
|
|
157
|
+
* history, so nothing to migrate", and a false nag costs more than a missed one when the
|
|
158
|
+
* compile break already routes the adopter to the same line.
|
|
159
|
+
*/
|
|
160
|
+
export function shouldNoteBaseUrlMove(
|
|
161
|
+
apiPrefix: string,
|
|
162
|
+
recordedEngine: string | undefined,
|
|
163
|
+
moveVersion: string = BASE_URL_MOVE_VERSION,
|
|
164
|
+
): boolean {
|
|
165
|
+
if (apiPrefix === "") return false;
|
|
166
|
+
const was = orderable(recordedEngine);
|
|
167
|
+
const move = orderable(moveVersion);
|
|
168
|
+
if (was === undefined || move === undefined) return false;
|
|
169
|
+
for (let i = 0; i < 3; i++) {
|
|
170
|
+
const a = was[i] as number;
|
|
171
|
+
const b = move[i] as number;
|
|
172
|
+
if (a !== b) return a < b;
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
130
177
|
export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
131
178
|
const warnings: string[] = [];
|
|
132
179
|
const strategy = opts.mergeStrategy ?? "overwrite";
|
|
@@ -259,16 +306,9 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
259
306
|
// projects — the error even told them their model "generates database code"
|
|
260
307
|
// that in fact imported nothing. It is demanded below at the point of USE, by
|
|
261
308
|
// the generator that reads it, which the runner names in the thrown message.
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (dbEmittingObjects.length > 0 && opts.config.dialect === undefined) {
|
|
266
|
-
const names = dbEmittingObjects.map((e) => e.name).join(", ");
|
|
267
|
-
throw new Error(
|
|
268
|
-
`codegen config is missing dialect — required because this model ` +
|
|
269
|
-
`generates database code for: ${names}. Set dialect in ` +
|
|
270
|
-
`metaobjects.config.ts. (A model of only value objects and/or sourceless projections may omit it.)`,
|
|
271
|
-
);
|
|
309
|
+
const dbEmitting = dbEmittingObjects(safeEntities);
|
|
310
|
+
if (dbEmitting.length > 0 && opts.config.dialect === undefined) {
|
|
311
|
+
throw new Error(missingDialectMessage(dbEmitting));
|
|
272
312
|
}
|
|
273
313
|
|
|
274
314
|
/** Did the AUTHOR declare a dbImport reachable by this target? Tracked, never
|
|
@@ -281,6 +321,19 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
281
321
|
// 2. Resolve targets + entity-module target.
|
|
282
322
|
const config = normalizeConfig(opts.config);
|
|
283
323
|
|
|
324
|
+
// The compile break (`value` → `fetcher` on the provider) sends an adopter to the right
|
|
325
|
+
// line; this names the value to put there, once, for the projects that had one.
|
|
326
|
+
if (shouldNoteBaseUrlMove(config.apiPrefix, recordedEngine)) {
|
|
327
|
+
warnings.push(
|
|
328
|
+
`apiPrefix ${JSON.stringify(config.apiPrefix)} no longer reaches the client — ` +
|
|
329
|
+
`generated hooks and services emit entity-relative paths. Supply it once at the ` +
|
|
330
|
+
`provider: <EntityFetcherProvider fetcher={...} baseUrl=${JSON.stringify(config.apiPrefix)}> ` +
|
|
331
|
+
`(Angular: provideEntityFetcher({ fetcher, baseUrl: ${JSON.stringify(config.apiPrefix)} })). ` +
|
|
332
|
+
`Server routes still mount under apiPrefix and are unaffected. ` +
|
|
333
|
+
`See docs/features/migrations/api-base-url-leaves-the-entity-descriptor.md`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
284
337
|
// (Historical: a warning stood here for a @filterable timestamp under
|
|
285
338
|
// timestampMode:"date", which used to throw at REQUEST time in runtime-ts's
|
|
286
339
|
// filter parser. That limitation is fixed — the generated allowlist now carries
|
|
@@ -366,6 +419,36 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
366
419
|
// CRUD surface it actually emits (rather than silently omitting it).
|
|
367
420
|
const includeHonoRoutes = config.generators.some((g) => g.emitsHonoRoutes === true);
|
|
368
421
|
|
|
422
|
+
// §A6 — same auto-detection for the OPT-IN names generator. The entity tier may only
|
|
423
|
+
// REFERENCE `<Entity>Names` when something in this run actually emits it; the names
|
|
424
|
+
// generator is opt-in under ADR-0034 (meta gen runs the adopter's own copies), so an
|
|
425
|
+
// unconditional import would break every project that has not added it. Surfaced BOTH
|
|
426
|
+
// on ctx.config (the field reference/names.ts documents, and the shape a third-party
|
|
427
|
+
// generator reads) and on every RenderContext below (what the templates can actually
|
|
428
|
+
// see) — one aggregation, two consumers.
|
|
429
|
+
//
|
|
430
|
+
// Scoped BY TARGET, unlike includeHonoRoutes, because the two flags are read for
|
|
431
|
+
// different reasons: api-docs asks "is this surface in the run?", while a template
|
|
432
|
+
// referencing these constants emits a RELATIVE import at its own target's path. The
|
|
433
|
+
// names artifact is not a registered cross-target module (there is no importBase route
|
|
434
|
+
// to it), so `entityFile({ target: "db" })` beside a default-target `namesFile()` would
|
|
435
|
+
// otherwise emit `./<Entity>.names` from a directory that does not hold one.
|
|
436
|
+
const namesTargets = new Set(
|
|
437
|
+
config.generators.filter((g) => g.emitsNames === true).map((g) => targetOf(g).name),
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
// A declared template.prompt with no prompt generator wired emits nothing and, before
|
|
441
|
+
// this, said nothing — while `meta verify` reported the template "clean". See
|
|
442
|
+
// prompt-generator-gate.ts. Self-extinguishing; warning only.
|
|
443
|
+
warnMissingPromptGenerators(root, config.generators, (m) => warnings.push(m));
|
|
444
|
+
|
|
445
|
+
// A retired `@emit*` codegen flag still sitting in the metadata suppresses nothing now.
|
|
446
|
+
// Named here rather than left to be discovered as a file that reappeared. Scoped to
|
|
447
|
+
// the run's own entity set (the same set every generator sees), so `meta gen <entity>`
|
|
448
|
+
// reports on what it just generated rather than on objects it was told to skip.
|
|
449
|
+
// See retired-codegen-attrs.ts. Self-extinguishing; warning only.
|
|
450
|
+
warnRetiredCodegenAttrs(safeEntities, (m) => warnings.push(m));
|
|
451
|
+
|
|
369
452
|
// 4. Run each generator with a per-target render context; collect with full path.
|
|
370
453
|
const emitted: { fullPath: string; content: string; generatedBy: string }[] = [];
|
|
371
454
|
// FR-038 §8 — generators that opted into orphan reconciliation, paired with the
|
|
@@ -411,9 +494,11 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
411
494
|
pluralizeCollections: config.pluralizeCollections,
|
|
412
495
|
collectionNameOverrides: config.collectionNameOverrides,
|
|
413
496
|
timestampMode: config.timestampMode,
|
|
497
|
+
clientDirective: config.clientDirective,
|
|
414
498
|
apiPrefix: config.apiPrefix,
|
|
415
499
|
emitAbstractShapes: config.emitAbstractShapes,
|
|
416
500
|
outputLayout: selfTarget.outputLayout,
|
|
501
|
+
includeNames: namesTargets.has(selfTarget.name),
|
|
417
502
|
pkMap,
|
|
418
503
|
relationMap,
|
|
419
504
|
packageOf,
|
|
@@ -433,6 +518,7 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
433
518
|
dialect: config.dialect,
|
|
434
519
|
outputLayout: selfTarget.outputLayout,
|
|
435
520
|
includeHonoRoutes,
|
|
521
|
+
includeNames: namesTargets.has(selfTarget.name),
|
|
436
522
|
},
|
|
437
523
|
renderContext,
|
|
438
524
|
...(projectRoot !== undefined && { projectRoot }),
|
|
@@ -463,7 +549,11 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
463
549
|
files = await generator.generate(ctx);
|
|
464
550
|
} catch (err) {
|
|
465
551
|
const msg = err instanceof Error ? err.message : String(err);
|
|
466
|
-
throw
|
|
552
|
+
// `cause` preserves the original throw. Without it a `runGen` caller sees a
|
|
553
|
+
// plain Error carrying only the prefixed message, so it cannot tell a
|
|
554
|
+
// CodegenError (a metadata/config problem it can report) from a genuine bug
|
|
555
|
+
// in a generator, and every stack trace stops at this line.
|
|
556
|
+
throw new Error(`[${generator.name}] ${msg}`, { cause: err });
|
|
467
557
|
}
|
|
468
558
|
|
|
469
559
|
for (const file of files) {
|
|
@@ -537,6 +627,34 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
|
|
|
537
627
|
`Refused to overwrite ${w.path}: ${w.conflictHint ?? "content differs and could not be verified as generated."}`,
|
|
538
628
|
);
|
|
539
629
|
}
|
|
630
|
+
|
|
631
|
+
// The recovery, ONCE — for the same reason the no-manifest branch above aggregates:
|
|
632
|
+
// it is the same three commands whether one file refused or sixteen, and repeating
|
|
633
|
+
// it per file buries it.
|
|
634
|
+
//
|
|
635
|
+
// It is stated as a SEQUENCE rather than a menu because there is exactly one way to
|
|
636
|
+
// keep an edit here and it is not obvious: a three-way merge needs a base, the
|
|
637
|
+
// `.gen-state` bodies are gitignored, and this machine has none — so the base has to
|
|
638
|
+
// be manufactured first. `--baseline=fresh` writes fresh output AND seeds the missing
|
|
639
|
+
// snapshot; restoring the file from git then puts the edit back with a base now
|
|
640
|
+
// present, and the next run merges it. Verified end to end (refused → overwrite →
|
|
641
|
+
// merged, edit intact).
|
|
642
|
+
//
|
|
643
|
+
// Two remedies were removed from the per-file hint and are not restored here as
|
|
644
|
+
// universal advice. "Move the edit into a non-generated file" holds only where the
|
|
645
|
+
// edit CAN live elsewhere — a `requirementTests()` stub's body cannot, because the
|
|
646
|
+
// test name is the link to the requirement and the stub's own header forbids renaming
|
|
647
|
+
// it. "--baseline=fresh" alone is a discard, and is named as one.
|
|
648
|
+
warnings.push(
|
|
649
|
+
`To KEEP your version of the file(s) above, the edit must be committed first — ` +
|
|
650
|
+
`then: 'meta gen --baseline=fresh' (writes fresh output over them and seeds the ` +
|
|
651
|
+
`missing .gen-state snapshot), 'git checkout -- <paths>' to bring your version back, ` +
|
|
652
|
+
`then 'meta gen' again — the snapshot now exists, so the edit merges. ` +
|
|
653
|
+
`To DISCARD your version instead, '--baseline=fresh' on its own is the whole answer. ` +
|
|
654
|
+
`Moving the edit into a non-generated file works only where the edit can live outside ` +
|
|
655
|
+
`the generated one; a requirementTests() stub's body cannot, since the test name is ` +
|
|
656
|
+
`its link to the requirement.`,
|
|
657
|
+
);
|
|
540
658
|
};
|
|
541
659
|
|
|
542
660
|
const sweep = (dryRun: boolean): void => {
|