@metaobjectsdev/cli 1.0.9-rc.4 → 1.0.9-rc.6

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.
Files changed (63) hide show
  1. package/README.md +15 -0
  2. package/dist/src/commands/eject.d.ts +17 -0
  3. package/dist/src/commands/eject.d.ts.map +1 -1
  4. package/dist/src/commands/eject.js +87 -2
  5. package/dist/src/commands/eject.js.map +1 -1
  6. package/dist/src/commands/gen.d.ts.map +1 -1
  7. package/dist/src/commands/gen.js +18 -5
  8. package/dist/src/commands/gen.js.map +1 -1
  9. package/dist/src/commands/generator-help.d.ts +21 -0
  10. package/dist/src/commands/generator-help.d.ts.map +1 -0
  11. package/dist/src/commands/generator-help.js +43 -0
  12. package/dist/src/commands/generator-help.js.map +1 -0
  13. package/dist/src/commands/generator.d.ts +36 -0
  14. package/dist/src/commands/generator.d.ts.map +1 -0
  15. package/dist/src/commands/generator.js +333 -0
  16. package/dist/src/commands/generator.js.map +1 -0
  17. package/dist/src/commands/init.d.ts.map +1 -1
  18. package/dist/src/commands/init.js +10 -1
  19. package/dist/src/commands/init.js.map +1 -1
  20. package/dist/src/commands/verify.d.ts.map +1 -1
  21. package/dist/src/commands/verify.js +5 -3
  22. package/dist/src/commands/verify.js.map +1 -1
  23. package/dist/src/index.d.ts.map +1 -1
  24. package/dist/src/index.js +8 -0
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/lib/advisory.d.ts +14 -0
  27. package/dist/src/lib/advisory.d.ts.map +1 -1
  28. package/dist/src/lib/advisory.js +64 -5
  29. package/dist/src/lib/advisory.js.map +1 -1
  30. package/dist/src/lib/catalog-listing.d.ts +14 -4
  31. package/dist/src/lib/catalog-listing.d.ts.map +1 -1
  32. package/dist/src/lib/catalog-listing.js +56 -9
  33. package/dist/src/lib/catalog-listing.js.map +1 -1
  34. package/dist/src/lib/install-set.d.ts +12 -2
  35. package/dist/src/lib/install-set.d.ts.map +1 -1
  36. package/dist/src/lib/install-set.js +14 -2
  37. package/dist/src/lib/install-set.js.map +1 -1
  38. package/dist/src/lib/output.d.ts +1 -0
  39. package/dist/src/lib/output.d.ts.map +1 -1
  40. package/dist/src/lib/output.js +2 -0
  41. package/dist/src/lib/output.js.map +1 -1
  42. package/dist/src/lib/runtime-eject.d.ts +80 -0
  43. package/dist/src/lib/runtime-eject.d.ts.map +1 -0
  44. package/dist/src/lib/runtime-eject.js +212 -0
  45. package/dist/src/lib/runtime-eject.js.map +1 -0
  46. package/dist/src/lib/unknown-attr-warnings.d.ts +18 -0
  47. package/dist/src/lib/unknown-attr-warnings.d.ts.map +1 -0
  48. package/dist/src/lib/unknown-attr-warnings.js +43 -0
  49. package/dist/src/lib/unknown-attr-warnings.js.map +1 -0
  50. package/package.json +10 -10
  51. package/src/commands/eject.ts +103 -3
  52. package/src/commands/gen.ts +18 -5
  53. package/src/commands/generator-help.ts +47 -0
  54. package/src/commands/generator.ts +390 -0
  55. package/src/commands/init.ts +10 -1
  56. package/src/commands/verify.ts +5 -4
  57. package/src/index.ts +8 -0
  58. package/src/lib/advisory.ts +71 -5
  59. package/src/lib/catalog-listing.ts +68 -7
  60. package/src/lib/install-set.ts +19 -2
  61. package/src/lib/output.ts +3 -0
  62. package/src/lib/runtime-eject.ts +263 -0
  63. package/src/lib/unknown-attr-warnings.ts +48 -0
@@ -0,0 +1,212 @@
1
+ // ADR-0034 Amendment 3 (2026-09-24 ruling) — ejecting a helper hands over ALL of its code.
2
+ //
3
+ // `meta eject routes` used to copy a generator whose OUTPUT still imported the HTTP adapter
4
+ // from `@metaobjectsdev/runtime-ts` — the mount helpers, filter parser, error envelopes and
5
+ // pagination, which is where most route defects have lived. Owning the generator while the
6
+ // code it emits calls into a package is half of owning it: a defect in the adapter still
7
+ // waits on an upstream release.
8
+ //
9
+ // So eject also copies the adapter SOURCE the output depends on — the transitive closure of
10
+ // relative imports from the entry module each generator's output names — into
11
+ // `codegen/runtime/`, laid out exactly as the package's own `src/` so every relative import
12
+ // in it resolves unchanged. Package imports are left as they are: what the closure reaches
13
+ // outside the package is `@metaobjectsdev/metadata` (core: the filter-op vocabulary and the
14
+ // canonical serializer) and third-party libraries (drizzle-orm, fastify, hono, zod, qs).
15
+ //
16
+ // The copies are VERBATIM. That is what makes "is my copy still the package's?" a byte
17
+ // comparison and "what did upstream change?" a plain `diff -r` against the installed
18
+ // package's `src/`, with no header to strip first.
19
+ import { createRequire } from "node:module";
20
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
21
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
22
+ import { dirname, join, relative, sep } from "node:path";
23
+ import { HTTP_RUNTIME_MODULES, HTTP_RUNTIME_PACKAGE, OWNED_RUNTIME_DIR, } from "@metaobjectsdev/codegen-ts";
24
+ import { compareOwnedCopy } from "./owned-copy.js";
25
+ import { cliVersion } from "./version.js";
26
+ /**
27
+ * The ejectable generators whose OUTPUT imports the adapter tier, and the adapter modules
28
+ * it imports. `entity` is here for its allowlist TYPES only, which is one file.
29
+ */
30
+ export const RUNTIME_ENTRIES = {
31
+ routes: ["drizzle-fastify"],
32
+ "routes-hono": ["hono"],
33
+ entity: ["allowlists"],
34
+ };
35
+ /** True iff ejecting `name` also copies adapter source. */
36
+ export function ejectsRuntime(name) {
37
+ return Object.hasOwn(RUNTIME_ENTRIES, name);
38
+ }
39
+ /**
40
+ * The installed `@metaobjectsdev/runtime-ts`: its `src/` and its manifest.
41
+ *
42
+ * Resolves the package ENTRY and walks up to the manifest that names it, never
43
+ * `"<pkg>/package.json"`: the exports map does not export that subpath, and under real Node
44
+ * asking for it throws ERR_PACKAGE_PATH_NOT_EXPORTED (see `install-set.ts`). `src/` ships
45
+ * in the published package (`files: ["dist", "src", ...]`).
46
+ */
47
+ export function resolveRuntimePackage() {
48
+ const req = createRequire(import.meta.url);
49
+ let dir = dirname(req.resolve(HTTP_RUNTIME_PACKAGE));
50
+ for (let hops = 0; hops < 8; hops++) {
51
+ const candidate = join(dir, "package.json");
52
+ if (existsSync(candidate)) {
53
+ const manifest = JSON.parse(readFileSync(candidate, "utf8"));
54
+ if (manifest.name === HTTP_RUNTIME_PACKAGE) {
55
+ const srcRoot = join(dir, "src");
56
+ if (!existsSync(srcRoot)) {
57
+ throw new Error(`${HTTP_RUNTIME_PACKAGE} at ${dir} ships no src/ — cannot copy the adapter source.`);
58
+ }
59
+ return { srcRoot, manifest };
60
+ }
61
+ }
62
+ const up = dirname(dir);
63
+ if (up === dir)
64
+ break;
65
+ dir = up;
66
+ }
67
+ throw new Error(`could not locate the installed ${HTTP_RUNTIME_PACKAGE} package.`);
68
+ }
69
+ /** Relative (`./`, `../`) and bare module specifiers a source file imports or re-exports. */
70
+ function specifiersOf(source) {
71
+ const out = [];
72
+ // `import … from "x"` and the re-export forms (`export { … } from`, `export * from`) only —
73
+ // a bare `export function f() { return Array.from("…") }` must not read as an import.
74
+ const STATEMENT = /(?:^|\n)\s*(?:import\s+[^;"']*?\s+from|export\s+(?:type\s+)?(?:\*(?:\s+as\s+\w+)?|\{[^}]*\})\s*from)\s*["']([^"']+)["']/g;
75
+ for (const m of source.matchAll(STATEMENT)) {
76
+ if (m[1] !== undefined)
77
+ out.push(m[1]);
78
+ }
79
+ for (const m of source.matchAll(/(?:^|\n)\s*import\s*["']([^"']+)["']/g)) {
80
+ if (m[1] !== undefined)
81
+ out.push(m[1]);
82
+ }
83
+ return out;
84
+ }
85
+ /** `@scope/name/sub` → `@scope/name`; `name/sub` → `name`. */
86
+ function packageNameOf(spec) {
87
+ const parts = spec.split("/");
88
+ return spec.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
89
+ }
90
+ function toPosix(p) {
91
+ return p.split(sep).join("/");
92
+ }
93
+ /** Every file the given adapter modules reach through relative imports, and what else they import. */
94
+ export function runtimeClosure(srcRoot, modules) {
95
+ const files = new Set();
96
+ const packages = new Set();
97
+ const queue = modules.map((m) => `${HTTP_RUNTIME_MODULES[m].file}.ts`);
98
+ while (queue.length > 0) {
99
+ const rel = queue.shift();
100
+ if (files.has(rel))
101
+ continue;
102
+ const abs = join(srcRoot, rel);
103
+ if (!existsSync(abs)) {
104
+ throw new Error(`${HTTP_RUNTIME_PACKAGE}: ${rel} is imported but not shipped in src/.`);
105
+ }
106
+ files.add(rel);
107
+ for (const spec of specifiersOf(readFileSync(abs, "utf8"))) {
108
+ if (spec.startsWith("./") || spec.startsWith("../")) {
109
+ const target = toPosix(join(dirname(rel), spec)).replace(/\.js$/, ".ts");
110
+ queue.push(target.endsWith(".ts") ? target : `${target}.ts`);
111
+ }
112
+ else if (!spec.startsWith("node:")) {
113
+ packages.add(packageNameOf(spec));
114
+ }
115
+ }
116
+ }
117
+ return { files: [...files].sort(), packages: [...packages].sort() };
118
+ }
119
+ /**
120
+ * Copy the adapter source `names` emit imports of into `<cwd>/codegen/runtime/`.
121
+ * Never overwrites a file without `force`, the same rule the generator copy keeps.
122
+ */
123
+ export async function ejectRuntime(cwd, names, force) {
124
+ const modules = [...new Set(names.flatMap((n) => RUNTIME_ENTRIES[n] ?? []))];
125
+ if (modules.length === 0)
126
+ return { files: [], packages: [] };
127
+ const { srcRoot } = resolveRuntimePackage();
128
+ const closure = runtimeClosure(srcRoot, modules);
129
+ const files = [];
130
+ for (const rel of closure.files) {
131
+ const source = await readFile(join(srcRoot, rel), "utf8");
132
+ const path = `${OWNED_RUNTIME_DIR}/${rel}`;
133
+ const abs = join(cwd, path);
134
+ const existing = existsSync(abs) ? await readFile(abs, "utf8") : undefined;
135
+ const comparison = existing === undefined ? undefined : await compareOwnedCopy(existing, source);
136
+ if (existing !== undefined && !force) {
137
+ files.push({ path, status: "preserved", comparison });
138
+ continue;
139
+ }
140
+ await mkdir(dirname(abs), { recursive: true });
141
+ await writeFile(abs, source, "utf8");
142
+ files.push({ path, status: existing === undefined ? "created" : "replaced", comparison });
143
+ }
144
+ return { files, packages: closure.packages };
145
+ }
146
+ /**
147
+ * The install lines the copied source needs, with ranges read from the runtime package's
148
+ * own manifest (never restated here). A `@metaobjectsdev/*` package moves with the CLI.
149
+ * A package that ships no types of its own gets its `@types/*` sibling as a dev dependency
150
+ * when the runtime package itself develops against one.
151
+ */
152
+ export function runtimeInstall(packages) {
153
+ const { manifest } = resolveRuntimePackage();
154
+ const known = { ...manifest.peerDependencies, ...manifest.dependencies };
155
+ const runtime = new Map();
156
+ const dev = new Map();
157
+ for (const p of packages) {
158
+ runtime.set(p, p.startsWith("@metaobjectsdev/") ? `^${cliVersion()}` : known[p]);
159
+ const typesPkg = `@types/${p.replace(/^@/, "").replace("/", "__")}`;
160
+ const typesRange = manifest.devDependencies?.[typesPkg];
161
+ if (typesRange !== undefined)
162
+ dev.set(typesPkg, typesRange);
163
+ }
164
+ return { runtime, dev };
165
+ }
166
+ function listTs(dir) {
167
+ const out = [];
168
+ for (const entry of readdirSync(dir)) {
169
+ const abs = join(dir, entry);
170
+ if (statSync(abs).isDirectory())
171
+ out.push(...listTs(abs));
172
+ else if (entry.endsWith(".ts"))
173
+ out.push(abs);
174
+ }
175
+ return out;
176
+ }
177
+ /**
178
+ * Every file under `<cwd>/codegen/runtime/`, compared to the installed package's source.
179
+ * Empty when the project owns no adapter copy. `not-in-package` is a file the package does
180
+ * not ship — your own addition, or one upstream deleted or renamed.
181
+ */
182
+ export async function runtimeCopyStatus(cwd) {
183
+ const root = join(cwd, OWNED_RUNTIME_DIR);
184
+ if (!existsSync(root))
185
+ return [];
186
+ let srcRoot;
187
+ try {
188
+ srcRoot = resolveRuntimePackage().srcRoot;
189
+ }
190
+ catch {
191
+ return [];
192
+ }
193
+ const rows = [];
194
+ for (const abs of listTs(root).sort()) {
195
+ const rel = toPosix(relative(root, abs));
196
+ const path = `${OWNED_RUNTIME_DIR}/${rel}`;
197
+ const upstream = join(srcRoot, rel);
198
+ if (!existsSync(upstream)) {
199
+ rows.push({ path, verdict: "not-in-package", localOnly: 0, referenceOnly: 0 });
200
+ continue;
201
+ }
202
+ const cmp = await compareOwnedCopy(readFileSync(abs, "utf8"), readFileSync(upstream, "utf8"));
203
+ rows.push({ path, verdict: cmp.verdict, localOnly: cmp.localOnly, referenceOnly: cmp.referenceOnly });
204
+ }
205
+ return rows;
206
+ }
207
+ /** The command that shows exactly how one owned file differs from the installed package's. */
208
+ export function runtimeDiffCommand(path) {
209
+ const rel = path.startsWith(`${OWNED_RUNTIME_DIR}/`) ? path.slice(OWNED_RUNTIME_DIR.length + 1) : path;
210
+ return `diff -u node_modules/${HTTP_RUNTIME_PACKAGE}/src/${rel} ${OWNED_RUNTIME_DIR}/${rel}`;
211
+ }
212
+ //# sourceMappingURL=runtime-eject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-eject.js","sourceRoot":"","sources":["../../../src/lib/runtime-eject.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,EAAE;AACF,4FAA4F;AAC5F,4FAA4F;AAC5F,2FAA2F;AAC3F,yFAAyF;AACzF,gCAAgC;AAChC,EAAE;AACF,4FAA4F;AAC5F,8EAA8E;AAC9E,4FAA4F;AAC5F,2FAA2F;AAC3F,4FAA4F;AAC5F,yFAAyF;AACzF,EAAE;AACF,uFAAuF;AACvF,qFAAqF;AACrF,mDAAmD;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,iBAAiB,GAElB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAwB,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAA2D;IACrF,MAAM,EAAE,CAAC,iBAAiB,CAAC;IAC3B,aAAa,EAAE,CAAC,MAAM,CAAC;IACvB,MAAM,EAAE,CAAC,YAAY,CAAC;CACvB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;AAC9C,CAAC;AAYD;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC;IACrD,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QAC5C,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAmD,CAAC;YAC/G,IAAI,QAAQ,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;gBAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBACjC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;oBACzB,MAAM,IAAI,KAAK,CAAC,GAAG,oBAAoB,OAAO,GAAG,kDAAkD,CAAC,CAAC;gBACvG,CAAC;gBACD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;YAC/B,CAAC;QACH,CAAC;QACD,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,EAAE,KAAK,GAAG;YAAE,MAAM;QACtB,GAAG,GAAG,EAAE,CAAC;IACX,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,kCAAkC,oBAAoB,WAAW,CAAC,CAAC;AACrF,CAAC;AAED,6FAA6F;AAC7F,SAAS,YAAY,CAAC,MAAc;IAClC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,4FAA4F;IAC5F,sFAAsF;IACtF,MAAM,SAAS,GACb,0HAA0H,CAAC;IAC7H,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3C,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,uCAAuC,CAAC,EAAE,CAAC;QACzE,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,8DAA8D;AAC9D,SAAS,aAAa,CAAC,IAAY;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9B,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC;AACxE,CAAC;AAED,SAAS,OAAO,CAAC,CAAS;IACxB,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC;AASD,sGAAsG;AACtG,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,OAAqC;IACnF,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC;IACvE,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,EAAG,CAAC;QAC3B,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,GAAG,oBAAoB,KAAK,GAAG,uCAAuC,CAAC,CAAC;QAC1F,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;YAC3D,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;gBACpD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;gBACzE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,CAAC,CAAC;YAC/D,CAAC;iBAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBACrC,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;AACtE,CAAC;AAkBD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAW,EAAE,KAAwB,EAAE,KAAc;IACtF,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC7D,MAAM,EAAE,OAAO,EAAE,GAAG,qBAAqB,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACjD,MAAM,KAAK,GAAwB,EAAE,CAAC;IACtC,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,GAAG,iBAAiB,IAAI,GAAG,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC5B,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3E,MAAM,UAAU,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACjG,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,CAAC;YACrC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;YACtD,SAAS;QACX,CAAC;QACD,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,MAAM,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,QAA2B;IAIxD,MAAM,EAAE,QAAQ,EAAE,GAAG,qBAAqB,EAAE,CAAC;IAC7C,MAAM,KAAK,GAAG,EAAE,GAAG,QAAQ,CAAC,gBAAgB,EAAE,GAAG,QAAQ,CAAC,YAAY,EAAE,CAAC;IACzE,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8B,CAAC;IACtD,MAAM,GAAG,GAAG,IAAI,GAAG,EAA8B,CAAC;IAClD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACjF,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC;QACpE,MAAM,UAAU,GAAG,QAAQ,CAAC,eAAe,EAAE,CAAC,QAAQ,CAAC,CAAC;QACxD,IAAI,UAAU,KAAK,SAAS;YAAE,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AAC1B,CAAC;AAUD,SAAS,MAAM,CAAC,GAAW;IACzB,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC7B,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;aACrD,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,GAAW;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,qBAAqB,EAAE,CAAC,OAAO,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,IAAI,GAAqB,EAAE,CAAC;IAClC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,GAAG,iBAAiB,IAAI,GAAG,EAAE,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC;YAC/E,SAAS;QACX,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;QAC9F,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,aAAa,EAAE,GAAG,CAAC,aAAa,EAAE,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,iBAAiB,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACvG,OAAO,wBAAwB,oBAAoB,QAAQ,GAAG,IAAI,iBAAiB,IAAI,GAAG,EAAE,CAAC;AAC/F,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type LoadMemoryOptions } from "@metaobjectsdev/sdk";
2
+ /**
3
+ * The trailing advice on every unknown-attribute warning `meta gen` prints. `gen` has no
4
+ * `--strict` flag of its own — `meta verify` is the strict door (ADR-0023), so that is what
5
+ * the warning names.
6
+ */
7
+ export declare const UNKNOWN_ATTR_GEN_ADVICE: string;
8
+ /**
9
+ * `meta gen`'s unknown-attribute warnings: one line per ADR-0023 finding a STRICT load of
10
+ * the same collection raises, naming the attribute, the node and the file. Empty when the
11
+ * model is clean; the caller prints {@link UNKNOWN_ATTR_GEN_ADVICE} once after them.
12
+ *
13
+ * `gen` itself keeps loading leniently and keeps its exit code; this only ends the silence
14
+ * between it and `meta verify`, which fails on the very same file. Files are printed
15
+ * relative to `projectRoot` when the loader recorded an absolute path.
16
+ */
17
+ export declare function unknownAttrWarnings(configDir: string, options: LoadMemoryOptions, projectRoot: string): Promise<string[]>;
18
+ //# sourceMappingURL=unknown-attr-warnings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unknown-attr-warnings.d.ts","sourceRoot":"","sources":["../../../src/lib/unknown-attr-warnings.ts"],"names":[],"mappings":"AACA,OAAO,EAA4B,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAEvF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,QAGG,CAAC;AAExC;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,iBAAiB,EAC1B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,EAAE,CAAC,CAqBnB"}
@@ -0,0 +1,43 @@
1
+ import { relative, isAbsolute } from "node:path";
2
+ import { unknownAttributeFindings } from "@metaobjectsdev/sdk";
3
+ /**
4
+ * The trailing advice on every unknown-attribute warning `meta gen` prints. `gen` has no
5
+ * `--strict` flag of its own — `meta verify` is the strict door (ADR-0023), so that is what
6
+ * the warning names.
7
+ */
8
+ export const UNKNOWN_ATTR_GEN_ADVICE = "meta gen loads leniently and generated anyway, but `meta verify` rejects this metadata " +
9
+ "(ADR-0023): fix or remove the attribute — a typo'd one (`isAbstrakt`, `@requird`) " +
10
+ "silently changes what is generated.";
11
+ /**
12
+ * `meta gen`'s unknown-attribute warnings: one line per ADR-0023 finding a STRICT load of
13
+ * the same collection raises, naming the attribute, the node and the file. Empty when the
14
+ * model is clean; the caller prints {@link UNKNOWN_ATTR_GEN_ADVICE} once after them.
15
+ *
16
+ * `gen` itself keeps loading leniently and keeps its exit code; this only ends the silence
17
+ * between it and `meta verify`, which fails on the very same file. Files are printed
18
+ * relative to `projectRoot` when the loader recorded an absolute path.
19
+ */
20
+ export async function unknownAttrWarnings(configDir, options, projectRoot) {
21
+ let findings;
22
+ try {
23
+ findings = await unknownAttributeFindings(configDir, options);
24
+ }
25
+ catch {
26
+ // The lenient load already succeeded; a strict re-load that cannot even complete has
27
+ // nothing more precise to say, and must never turn an advisory into a failure.
28
+ return [];
29
+ }
30
+ return findings.map((f) => {
31
+ const source = f.source;
32
+ const files = (source.files ?? [])
33
+ .filter((p) => p.length > 0)
34
+ .map((p) => (isAbsolute(p) ? relative(projectRoot, p) : p));
35
+ const where = [
36
+ files.length > 0 ? files.join(", ") : undefined,
37
+ source.jsonPath !== undefined && source.jsonPath.length > 0 ? `at ${source.jsonPath}` : undefined,
38
+ ].filter((w) => w !== undefined).join(" ");
39
+ const head = `${f.code}: ${f.message}`;
40
+ return where.length > 0 ? `${head}\n in ${where}` : head;
41
+ });
42
+ }
43
+ //# sourceMappingURL=unknown-attr-warnings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unknown-attr-warnings.js","sourceRoot":"","sources":["../../../src/lib/unknown-attr-warnings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,wBAAwB,EAA0B,MAAM,qBAAqB,CAAC;AAEvF;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAClC,yFAAyF;IACzF,oFAAoF;IACpF,qCAAqC,CAAC;AAExC;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,SAAiB,EACjB,OAA0B,EAC1B,WAAmB;IAEnB,IAAI,QAAQ,CAAC;IACb,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,wBAAwB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,qFAAqF;QACrF,+EAA+E;QAC/E,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACxB,MAAM,MAAM,GAAG,CAAC,CAAC,MAA0D,CAAC;QAC5E,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;aAC/B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;aAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG;YACZ,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;YAC/C,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS;SAClG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/cli",
3
- "version": "1.0.9-rc.4",
3
+ "version": "1.0.9-rc.6",
4
4
  "description": "CLI for MetaObjects: scaffold, codegen, migrate, and drift-detection commands.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -51,15 +51,15 @@
51
51
  "@biomejs/js-api": "^0.7.0",
52
52
  "@biomejs/wasm-nodejs": "^1.9.4",
53
53
  "@libsql/kysely-libsql": "^0.4.0",
54
- "@metaobjectsdev/codegen-ts": "1.0.9-rc.4",
55
- "@metaobjectsdev/codegen-ts-react": "1.0.9-rc.4",
56
- "@metaobjectsdev/codegen-ts-tanstack": "1.0.9-rc.4",
57
- "@metaobjectsdev/docs-site": "1.0.9-rc.4",
58
- "@metaobjectsdev/metadata": "1.0.9-rc.4",
59
- "@metaobjectsdev/migrate-ts": "1.0.9-rc.4",
60
- "@metaobjectsdev/render": "1.0.9-rc.4",
61
- "@metaobjectsdev/runtime-ts": "1.0.9-rc.4",
62
- "@metaobjectsdev/sdk": "1.0.9-rc.4",
54
+ "@metaobjectsdev/codegen-ts": "1.0.9-rc.6",
55
+ "@metaobjectsdev/codegen-ts-react": "1.0.9-rc.6",
56
+ "@metaobjectsdev/codegen-ts-tanstack": "1.0.9-rc.6",
57
+ "@metaobjectsdev/docs-site": "1.0.9-rc.6",
58
+ "@metaobjectsdev/metadata": "1.0.9-rc.6",
59
+ "@metaobjectsdev/migrate-ts": "1.0.9-rc.6",
60
+ "@metaobjectsdev/render": "1.0.9-rc.6",
61
+ "@metaobjectsdev/runtime-ts": "1.0.9-rc.6",
62
+ "@metaobjectsdev/sdk": "1.0.9-rc.6",
63
63
  "@toon-format/toon": "^2.3.0",
64
64
  "jiti": "^2.4.0"
65
65
  },
@@ -25,7 +25,12 @@ import { log } from "../lib/log.js";
25
25
  import { declaredDependencyNames, readPackageManifest } from "../lib/package-manifest.js";
26
26
  import { compareOwnedCopy, type OwnedComparison } from "../lib/owned-copy.js";
27
27
  import { composeCatalog, packageOf } from "../lib/catalog.js";
28
- import { installSetFor, type InstallSet } from "../lib/install-set.js";
28
+ import { installSetFor, type InstallSet, type OwnedRuntimeInstall } from "../lib/install-set.js";
29
+ import {
30
+ ejectRuntime, ejectsRuntime, runtimeCopyStatus, runtimeDiffCommand, runtimeInstall,
31
+ type RuntimeEjectResult, type RuntimeFileResult,
32
+ } from "../lib/runtime-eject.js";
33
+ import { HTTP_RUNTIME_PACKAGE, OWNED_RUNTIME_DIR } from "@metaobjectsdev/codegen-ts";
29
34
  import { emitStructured, type OutputFormat } from "../lib/format.js";
30
35
  import {
31
36
  ejectLibrary, ejectableLibraryNames, isLibraryName, libraryStaleness,
@@ -159,6 +164,12 @@ export interface EjectResult {
159
164
  * cheapest thing that makes that condition observable.
160
165
  */
161
166
  comparison?: OwnedComparison | undefined;
167
+ /**
168
+ * ADR-0034 Amendment 3 (2026-09-24): the HTTP-adapter source this generator's OUTPUT
169
+ * imports, copied into `codegen/runtime/` so the adopter owns that too. Absent for a
170
+ * generator whose output imports no adapter.
171
+ */
172
+ runtime?: RuntimeEjectResult | undefined;
162
173
  }
163
174
 
164
175
  /** The `@metaobjectsdev/*` packages an ejected template imports, read from the file
@@ -241,8 +252,15 @@ export async function ejectGenerator(opts: EjectOptions): Promise<EjectResult> {
241
252
  const comparison =
242
253
  existing === undefined ? undefined : await compareOwnedCopy(existing, templateSource);
243
254
 
255
+ // The adapter source the OUTPUT imports is part of what is handed over — copied whether
256
+ // or not the generator file itself was already here, and under the same never-clobber
257
+ // rule, so re-running eject fills in a missing copy without touching a kept one.
258
+ const runtime = ejectsRuntime(opts.name)
259
+ ? await ejectRuntime(opts.cwd, [opts.name], opts.force === true)
260
+ : undefined;
261
+
244
262
  if (!opts.force && existing !== undefined) {
245
- return { ...common, status: "preserved", comparison };
263
+ return { ...common, status: "preserved", comparison, runtime };
246
264
  }
247
265
 
248
266
  await mkdir(join(opts.cwd, OWNED_GENERATORS_DIR), { recursive: true });
@@ -251,9 +269,45 @@ export async function ejectGenerator(opts: EjectOptions): Promise<EjectResult> {
251
269
  ...common,
252
270
  status: existing === undefined ? "created" : "replaced",
253
271
  comparison,
272
+ runtime,
254
273
  };
255
274
  }
256
275
 
276
+ /**
277
+ * The owned HTTP-adapter copy under `codegen/runtime/`, file by file, against the installed
278
+ * `@metaobjectsdev/runtime-ts` it was copied from. Shared by `meta eject --list` and
279
+ * `meta gen --list`. Empty when the project owns no adapter copy.
280
+ *
281
+ * `identical` says an upgrade can be taken by re-copying (`meta eject <name> --force`);
282
+ * `DIFFERS` says the copy is yours in a way the package is not — your fix, or upstream's —
283
+ * and names the diff that tells which.
284
+ */
285
+ export async function runtimeCopyLines(cwd: string): Promise<string[]> {
286
+ const rows = await runtimeCopyStatus(cwd);
287
+ if (rows.length === 0) return [];
288
+ const lines: string[] = [
289
+ `HTTP adapter source this project owns (${OWNED_RUNTIME_DIR}/), against the installed ${HTTP_RUNTIME_PACKAGE}:`,
290
+ ];
291
+ const differing: string[] = [];
292
+ for (const r of rows) {
293
+ if (r.verdict === "identical") lines.push(` ${r.path} [identical to the package]`);
294
+ else if (r.verdict === "reformatted") lines.push(` ${r.path} [same content as the package, your formatting]`);
295
+ else if (r.verdict === "not-in-package") lines.push(` ${r.path} [not in the package — yours, or removed upstream]`);
296
+ else {
297
+ differing.push(r.path);
298
+ lines.push(
299
+ ` ${r.path} [DIFFERS: ${r.referenceOnly} line(s) only in the package, ${r.localOnly} line(s) only in yours]`,
300
+ );
301
+ }
302
+ }
303
+ if (differing.length > 0) {
304
+ lines.push(" See the difference (upstream fix, or your own?) before taking an upgrade:");
305
+ for (const p of differing) lines.push(` ${runtimeDiffCommand(p)}`);
306
+ }
307
+ lines.push("");
308
+ return lines;
309
+ }
310
+
257
311
  /**
258
312
  * `--list` doubles as the staleness report for what this project ALREADY owns.
259
313
  *
@@ -315,6 +369,7 @@ async function listOutput(cwd: string): Promise<string> {
315
369
  );
316
370
  lines.push("");
317
371
  }
372
+ lines.push(...(await runtimeCopyLines(cwd)));
318
373
  // FR-043 — the shipped libraries, and how far this project's copies have drifted.
319
374
  lines.push("Shipped libraries (copy one into your own sources and own it):");
320
375
  for (const name of ejectableLibraryNames()) lines.push(` ${name}`);
@@ -369,6 +424,8 @@ interface EjectedRow {
369
424
  /** The two edits an adopter makes in `metaobjects.config.ts`. */
370
425
  wire: { import: string; entry: string };
371
426
  requires: readonly string[];
427
+ /** The adapter source copied beside it (ADR-0034 Amendment 3). Absent when none. */
428
+ runtime?: RuntimeFileResult[];
372
429
  }
373
430
 
374
431
  interface EjectPayload {
@@ -518,6 +575,33 @@ function reportOne(result: EjectResult, name: string): void {
518
575
  "AND the entry.",
519
576
  );
520
577
  for (const line of result.dependencyNotes) log.info(line);
578
+ if (result.runtime !== undefined && result.runtime.files.length > 0) reportRuntime(result.runtime.files);
579
+ }
580
+
581
+ /** What the adapter copy handed over, and how the emitted code reaches it. */
582
+ function reportRuntime(files: readonly RuntimeFileResult[]): void {
583
+ const written = files.filter((f) => f.status !== "preserved");
584
+ // A kept file that already matches the package is not news (ejecting `entity routes`
585
+ // copies the shared allowlist file once, then finds it) — only a kept file that differs is.
586
+ const kept = files.filter((f) => f.status === "preserved" && f.comparison?.verdict !== "identical");
587
+ log.info(
588
+ `The code this generator emits imports its HTTP adapter from ${OWNED_RUNTIME_DIR}/, not from ` +
589
+ `${HTTP_RUNTIME_PACKAGE}: the adapter, filter parser, error envelopes and pagination are yours too.`,
590
+ );
591
+ if (written.length > 0) {
592
+ log.info(` Copied ${written.length} file(s) into ${OWNED_RUNTIME_DIR}/ (verbatim from the installed package).`);
593
+ }
594
+ const keptDiffering = kept.filter((f) => f.comparison?.verdict === "differs");
595
+ if (kept.length > 0) {
596
+ log.info(
597
+ ` ${kept.length} file(s) were already there and were left untouched` +
598
+ (keptDiffering.length > 0 ? `, ${keptDiffering.length} of them differing from the package` : "") +
599
+ " (--force replaces them). `meta eject --list` compares each one.",
600
+ );
601
+ }
602
+ log.info(
603
+ ` To import the package instead, pass \`runtimeImport: "${HTTP_RUNTIME_PACKAGE}"\` to the generator.`,
604
+ );
521
605
  }
522
606
 
523
607
  export async function ejectCommand(
@@ -575,6 +659,9 @@ export async function ejectCommand(
575
659
  const rows: EjectedRow[] = [];
576
660
  const libraries: LibraryEjectResult[] = [];
577
661
  const templatePackages = new Set<string>();
662
+ // ADR-0034 Amendment 3: the generators whose adapter source was copied, and what it imports.
663
+ const runtimeGenerators = new Set<string>();
664
+ const runtimePackages = new Set<string>();
578
665
  try {
579
666
  for (const name of flags.names) {
580
667
  if (isLibraryName(name)) {
@@ -585,12 +672,17 @@ export async function ejectCommand(
585
672
  }
586
673
  const result = await ejectGenerator({ cwd, name, force: flags.force });
587
674
  for (const p of result.templatePackages) templatePackages.add(p);
675
+ if (result.runtime !== undefined) {
676
+ runtimeGenerators.add(name);
677
+ for (const p of result.runtime.packages) runtimePackages.add(p);
678
+ }
588
679
  rows.push({
589
680
  name,
590
681
  path: result.path,
591
682
  status: result.status,
592
683
  wire: { import: result.importLine, entry: `${result.exportName}()` },
593
684
  requires: catalog[name]?.requires ?? [],
685
+ ...(result.runtime !== undefined ? { runtime: result.runtime.files } : {}),
594
686
  });
595
687
  if (fmt === "text") reportOne(result, name);
596
688
  }
@@ -610,7 +702,15 @@ export async function ejectCommand(
610
702
  .filter((n) => !isLibraryName(n))
611
703
  .map((n) => catalog[n])
612
704
  .filter((e) => e !== undefined);
613
- const install = installSetFor(entries, [...templatePackages]);
705
+ // The copied adapter source imports its own dependencies (qs, the framework, the core
706
+ // `@metaobjectsdev/metadata`), and the generated code no longer imports the runtime
707
+ // package — so the install line trades one for the other.
708
+ let ownedRuntime: OwnedRuntimeInstall | undefined;
709
+ if (runtimeGenerators.size > 0) {
710
+ const { runtime, dev } = runtimeInstall([...runtimePackages]);
711
+ ownedRuntime = { generators: runtimeGenerators, runtime, dev };
712
+ }
713
+ const install = installSetFor(entries, [...templatePackages], ownedRuntime);
614
714
  const configKeys = [...new Set(entries.flatMap((e) => e.configKeys ?? []))].sort();
615
715
 
616
716
  if (fmt === "text") {
@@ -22,6 +22,7 @@ import { packageOfResolutionKey } from "@metaobjectsdev/metadata";
22
22
  import type { MetaRoot } from "@metaobjectsdev/metadata";
23
23
  import type { Collection } from "@metaobjectsdev/sdk";
24
24
  import { reportLoadError } from "../lib/load-error.js";
25
+ import { UNKNOWN_ATTR_GEN_ADVICE, unknownAttrWarnings } from "../lib/unknown-attr-warnings.js";
25
26
  import {
26
27
  buildCatalogListing, renderCatalogText, wiredGeneratorNames, ownedGeneratorNames,
27
28
  declaredDepsOf,
@@ -140,16 +141,27 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
140
141
  }
141
142
 
142
143
  let metadata;
144
+ const loadOptions = {
145
+ ...collectionLoadOptions(genCollection),
146
+ ...loadMemoryOptionsFrom(forgeConfig),
147
+ };
143
148
  try {
144
- metadata = await loadMemory(genCollection.configDir, {
145
- ...collectionLoadOptions(genCollection),
146
- ...loadMemoryOptionsFrom(forgeConfig),
147
- });
149
+ metadata = await loadMemory(genCollection.configDir, loadOptions);
148
150
  } catch (err) {
149
151
  reportLoadError(log, "failed to load metadata", err);
150
152
  return 2;
151
153
  }
152
154
 
155
+ // ADR-0023: gen loads leniently, so an unknown attribute (`isAbstrakt: true`, a
156
+ // misspelt `@required`) used to pass without a word while `meta verify` rejected the
157
+ // same file — and the typo changed what was generated. Say so, naming attribute, node
158
+ // and file. Advisory: the exit code is unchanged.
159
+ const unknownAttrFindings = await unknownAttrWarnings(genCollection.configDir, loadOptions, projectRoot);
160
+ const unknownAttrs = unknownAttrFindings.length > 0
161
+ ? [...unknownAttrFindings, UNKNOWN_ATTR_GEN_ADVICE]
162
+ : [];
163
+ for (const w of unknownAttrs) log.warn(`warning: ${w}`);
164
+
153
165
  // FR-023 §11.1 item 2 — refuse a positional that names ONLY objects imported
154
166
  // from a dependency and excluded from output by the default exclusion rule.
155
167
  // Distinct from the runner's existing "no entities matched" WARNING
@@ -252,7 +264,8 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
252
264
  outDir: targetDirs.length > 1 ? targetDirs.join(", ") : forgeConfig.outDir,
253
265
  dialect: forgeConfig.dialect,
254
266
  dryRun: cliConfig.dryRun,
255
- warnings: [],
267
+ // Text mode already printed these to stderr above; the structured forms carry them.
268
+ warnings: fmt === "text" ? [] : unknownAttrs,
256
269
  antiPatterns,
257
270
  generatorCount: forgeConfig.generators?.length ?? 0,
258
271
  ...(migrateAdvice !== undefined ? { migrateAdvice } : {}),
@@ -0,0 +1,47 @@
1
+ // The dependency-free half of `meta generator` — its flags and help text — so the CLI entry
2
+ // point can print `meta generator --help` without loading the codegen packages the command
3
+ // itself needs.
4
+
5
+ /** Where owned generators live — the directory `meta init` scaffolds and `meta eject` fills. */
6
+ export const OWNED_GENERATORS_DIR = "codegen/generators";
7
+ export const CONFIG_FILE = "metaobjects.config.ts";
8
+
9
+ export const GENERATOR_SCOPES = ["entity", "package", "model"] as const;
10
+ export type GeneratorScope = (typeof GENERATOR_SCOPES)[number];
11
+
12
+ export const GENERATOR_OPTIONS = {
13
+ "scope": { type: "string", default: "entity" },
14
+ "force": { type: "boolean", default: false },
15
+ "no-wire": { type: "boolean", default: false },
16
+ } as const;
17
+
18
+ export const GENERATOR_HELP = `meta generator — write a generator of your own
19
+
20
+ USAGE:
21
+ meta generator new <name> [--scope entity|package|model] [--force] [--no-wire]
22
+
23
+ Writes ${OWNED_GENERATORS_DIR}/<name>.ts — a small, commented, WORKING generator — and
24
+ adds its import and entry to ${CONFIG_FILE}, so \`meta gen\` runs it and
25
+ \`meta verify --codegen\` gates its output straight away. Then edit the emit to produce
26
+ what you need: OpenAPI, JSON Schema, a client, DTOs, docs — anything the model describes.
27
+
28
+ This is the primary way to get an output MetaObjects does not ship. Reach for
29
+ \`meta eject <name>\` instead only when a reference generator already emits something
30
+ close to what you want (\`meta gen --list\` is the catalog).
31
+
32
+ <name> is kebab-case (json-schema, openapi, service-layer). It names the file, the
33
+ generator's \`name\` in diagnostics, and the exported factory (jsonSchemaGenerator).
34
+
35
+ FLAGS:
36
+ --scope <scope> What the generator walks, one file per unit (default: entity):
37
+ entity one file per object in the model
38
+ package one file per metadata package
39
+ model one file for the whole model
40
+ --force Overwrite an existing ${OWNED_GENERATORS_DIR}/<name>.ts
41
+ --no-wire Write the file only; print the import and entry to add yourself
42
+ --help, -h Print this help
43
+
44
+ Every port's shape, and JSON Schema + OpenAPI 3.1 examples to copy: the guide
45
+ docs/recipes/write-your-own-generator.md in the MetaObjects repository
46
+ (github.com/metaobjectsdev), and the metaobjects-codegen skill \`meta init\` installs.
47
+ `;