@metaobjectsdev/cli 1.0.3 → 1.0.4-rc.1

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 (79) hide show
  1. package/README.md +13 -5
  2. package/dist/src/commands/eject.d.ts +2 -1
  3. package/dist/src/commands/eject.d.ts.map +1 -1
  4. package/dist/src/commands/eject.js +235 -75
  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 +119 -29
  8. package/dist/src/commands/gen.js.map +1 -1
  9. package/dist/src/commands/init.d.ts +5 -9
  10. package/dist/src/commands/init.d.ts.map +1 -1
  11. package/dist/src/commands/init.js +80 -384
  12. package/dist/src/commands/init.js.map +1 -1
  13. package/dist/src/commands/verify.d.ts.map +1 -1
  14. package/dist/src/commands/verify.js +40 -14
  15. package/dist/src/commands/verify.js.map +1 -1
  16. package/dist/src/index.d.ts.map +1 -1
  17. package/dist/src/index.js +36 -14
  18. package/dist/src/index.js.map +1 -1
  19. package/dist/src/lib/advisory.d.ts +6 -0
  20. package/dist/src/lib/advisory.d.ts.map +1 -1
  21. package/dist/src/lib/advisory.js +10 -0
  22. package/dist/src/lib/advisory.js.map +1 -1
  23. package/dist/src/lib/args.d.ts +18 -2
  24. package/dist/src/lib/args.d.ts.map +1 -1
  25. package/dist/src/lib/args.js +15 -3
  26. package/dist/src/lib/args.js.map +1 -1
  27. package/dist/src/lib/catalog-listing.d.ts +92 -0
  28. package/dist/src/lib/catalog-listing.d.ts.map +1 -0
  29. package/dist/src/lib/catalog-listing.js +268 -0
  30. package/dist/src/lib/catalog-listing.js.map +1 -0
  31. package/dist/src/lib/catalog.d.ts +41 -0
  32. package/dist/src/lib/catalog.d.ts.map +1 -0
  33. package/dist/src/lib/catalog.js +89 -0
  34. package/dist/src/lib/catalog.js.map +1 -0
  35. package/dist/src/lib/collection-load-options.d.ts +1 -1
  36. package/dist/src/lib/collection-load-options.d.ts.map +1 -1
  37. package/dist/src/lib/collection-load-options.js +6 -0
  38. package/dist/src/lib/collection-load-options.js.map +1 -1
  39. package/dist/src/lib/install-set.d.ts +31 -0
  40. package/dist/src/lib/install-set.d.ts.map +1 -0
  41. package/dist/src/lib/install-set.js +103 -0
  42. package/dist/src/lib/install-set.js.map +1 -0
  43. package/dist/src/lib/library-eject.d.ts +79 -0
  44. package/dist/src/lib/library-eject.d.ts.map +1 -0
  45. package/dist/src/lib/library-eject.js +241 -0
  46. package/dist/src/lib/library-eject.js.map +1 -0
  47. package/dist/src/lib/library-listing.d.ts +63 -0
  48. package/dist/src/lib/library-listing.d.ts.map +1 -0
  49. package/dist/src/lib/library-listing.js +178 -0
  50. package/dist/src/lib/library-listing.js.map +1 -0
  51. package/dist/src/lib/library-prefix-advisory.d.ts +17 -0
  52. package/dist/src/lib/library-prefix-advisory.d.ts.map +1 -0
  53. package/dist/src/lib/library-prefix-advisory.js +67 -0
  54. package/dist/src/lib/library-prefix-advisory.js.map +1 -0
  55. package/dist/src/lib/load-metaobjects-config.d.ts +1 -2
  56. package/dist/src/lib/load-metaobjects-config.d.ts.map +1 -1
  57. package/dist/src/lib/load-metaobjects-config.js +11 -17
  58. package/dist/src/lib/load-metaobjects-config.js.map +1 -1
  59. package/dist/src/lib/requirement-check.d.ts +17 -2
  60. package/dist/src/lib/requirement-check.d.ts.map +1 -1
  61. package/dist/src/lib/requirement-check.js +51 -9
  62. package/dist/src/lib/requirement-check.js.map +1 -1
  63. package/package.json +10 -10
  64. package/src/commands/eject.ts +304 -90
  65. package/src/commands/gen.ts +128 -28
  66. package/src/commands/init.ts +82 -390
  67. package/src/commands/verify.ts +47 -15
  68. package/src/index.ts +36 -14
  69. package/src/lib/advisory.ts +13 -0
  70. package/src/lib/args.ts +29 -5
  71. package/src/lib/catalog-listing.ts +369 -0
  72. package/src/lib/catalog.ts +107 -0
  73. package/src/lib/collection-load-options.ts +7 -1
  74. package/src/lib/install-set.ts +116 -0
  75. package/src/lib/library-eject.ts +298 -0
  76. package/src/lib/library-listing.ts +258 -0
  77. package/src/lib/library-prefix-advisory.ts +78 -0
  78. package/src/lib/load-metaobjects-config.ts +15 -21
  79. package/src/lib/requirement-check.ts +71 -11
@@ -2,7 +2,6 @@ import { mkdir, writeFile, readFile, readdir, stat, rm } from "node:fs/promises"
2
2
  import { join } from "node:path";
3
3
  import { dirname } from "node:path";
4
4
  import { existsSync as existsSyncWrap, readFileSync as readFileSyncWrap } from "node:fs";
5
- import { createRequire } from "node:module";
6
5
  import { DEFAULT_CONFIG, ConfigSchema, saveConfig, PACKAGE_MANIFEST_FILE, DEFAULT_METADATA_DIR, DEFAULT_METAOBJECTS_DIR, DEPS_DIR, LOCK_FILE } from "@metaobjectsdev/sdk";
7
6
  import { assemble, resolveAgentContextRoot, planScaffold, AGENT_CONTEXT_MANIFEST_PATH, } from "@metaobjectsdev/sdk/agent-context";
8
7
  import { assertKnownStackValues, resolveStack } from "../lib/detect-stack.js";
@@ -10,36 +9,20 @@ import { reportIgnoredScaffold } from "../lib/ignored-scaffold-check.js";
10
9
  import { parseInitArgs } from "../lib/args.js";
11
10
  import { log } from "../lib/log.js";
12
11
  import { cliVersion } from "../lib/version.js";
13
- import { declaredDependencyNames } from "../lib/package-manifest.js";
14
12
  import { findWranglerConfig, parseWranglerConfig } from "@metaobjectsdev/migrate-ts";
15
- import { DEFAULT_DOCS_DIR, readReferenceTemplate } from "@metaobjectsdev/codegen-ts";
13
+ import { DEFAULT_DOCS_DIR } from "@metaobjectsdev/codegen-ts";
16
14
  // ADR-0034 scaffold-and-own — `meta init` copies the codegen reference templates into
17
15
  // the consumer's repo so they OWN them; metaobjects.config.ts imports them locally.
18
16
  const OWNED_GENERATORS_DIR = "codegen/generators";
19
- // The FIVE reference generators `meta init` copies EAGERLY — deliberately an explicit
20
- // literal, not derived from @metaobjectsdev/codegen-ts's REFERENCE_GENERATOR_NAMES (the
21
- // full list of everything `meta eject` can copy). Looping over that array unconditionally
22
- // used to mean init scaffolded whatever it contained: when a later task registered
23
- // "routes-hono" there, init started writing an unwired Hono generator nothing in the
24
- // scaffolded config imports, on every fresh project, silently. This constant is the
25
- // scaffolded metaobjects.config.ts's import list (buildMetaobjectsConfigBody below) made
26
- // explicit and checkable — anything else is eject-on-demand via `meta eject <name>`, which
27
- // exists exactly so eager copying isn't the only way to take ownership of a template.
17
+ // The scaffolded config's outDir, as a named constant so the config template and
18
+ // anything derived from it cannot drift.
28
19
  //
29
- // "names" joined this list by spec §A5's ruling, not by falling out of the general rule
30
- // above: TypeScript is opt-in by construction under ADR-0034 (meta gen runs the adopter's
31
- // copy, so a packaged change to a generator can never reach anyone who has ejected), so
32
- // the honest maximum for the names artifact is every NEW `meta init` getting it and every
33
- // existing project adding one config line by hand.
34
- export const SCAFFOLDED_GENERATOR_NAMES = ["entity", "queries", "routes", "barrel", "names"];
35
- // The scaffolded config's outDir + dbImport, as named constants so the throwing-stub
36
- // path below is DERIVED from the same values the config template embeds rather than
37
- // duplicated as a second literal that could drift from it.
20
+ // `dbImport` is deliberately NOT here any more. It existed only because the scaffold
21
+ // wired `routesFile()`, whose output emits `import { db } from …`; with nothing wired
22
+ // there is no such import, so the throwing `src/db.ts` stub that made it resolve has
23
+ // gone too. `dbImport` is now a `configKey` on the `routes` catalog entry — reported by
24
+ // `meta gen --list` and by `meta eject routes`, to the adopter who actually chose it.
38
25
  const SCAFFOLD_OUT_DIR = "src/generated";
39
- const SCAFFOLD_DB_IMPORT = "../db";
40
- // "src/generated" + "../db" -> "src/db" -> "src/db.ts" (dbImport resolves relative
41
- // to outDir, same as the module specifier a generated route file emits).
42
- const DB_STUB_REL_PATH = `${join(SCAFFOLD_OUT_DIR, SCAFFOLD_DB_IMPORT)}.ts`;
43
26
  const META_COMMON_JSON = JSON.stringify({
44
27
  metadata: {
45
28
  package: "",
@@ -93,129 +76,62 @@ dist/
93
76
  `;
94
77
  function buildMetaobjectsConfigBody(dialect = "sqlite") {
95
78
  return `import { defineConfig } from "@metaobjectsdev/cli";
96
- // Owned codegen generators (ADR-0034 scaffold-and-own). \`meta init\` copied these
97
- // reference templates into ./codegen/generators/ — they are YOURS to edit, and
98
- // \`meta gen\` runs from these local copies, not from the package. Read each file's
99
- // header doc-block for what it emits and how to customize it.
100
- import { entityFile } from "./codegen/generators/entity.js";
101
- import { queriesFile } from "./codegen/generators/queries.js";
102
- import { routesFile } from "./codegen/generators/routes.js";
103
- import { namesFile } from "./codegen/generators/names.js";
104
- import { barrel } from "./codegen/generators/barrel.js";
105
79
 
106
80
  export default defineConfig({
107
- outDir: "${SCAFFOLD_OUT_DIR}",
108
- extStyle: "js", // ".js"-extensioned relative imports — correct for Node ESM and \`tsc\` with
109
- // nodenext, which is what a fresh project has. BUNDLERS DISAGREE: this fails
110
- // outright under Turbopack (even between two generated files, so the whole
111
- // generated tree goes unresolvable), while Vite and esbuild accept it and
112
- // webpack needs \`resolve.extensionAlias\`. If a generated import fails to
113
- // resolve, set "none" and retest — do not assume this line covers your bundler.
114
- dbImport: "${SCAFFOLD_DB_IMPORT}", // routesFile() below emits \`import { db } from …\` — meta init
115
- // scaffolded ${DB_STUB_REL_PATH} as a THROWING STUB (types clean, no
116
- // driver chosen) so meta gen and tsc pass; replace it with your real
117
- // Drizzle connection before running the app.
118
- // (queriesFile() takes db as a parameter and never reads this.)
119
- dialect: "${dialect}",
120
- apiPrefix: "", // set to "/api" if your routes mount under /api
121
- generators: [
122
- entityFile(),
123
- queriesFile(),
124
- routesFile(),
125
- namesFile(), // <Entity>Names — physical table/column constants (spec §A1/§A5)
126
- barrel(),
127
- ],
81
+ outDir: "${SCAFFOLD_OUT_DIR}",
82
+ dialect: "${dialect}",
83
+
84
+ // NOTHING IS GENERATED UNTIL YOU CHOOSE IT.
85
+ //
86
+ // MetaObjects does not decide which code your application needs — you do, or the
87
+ // agent working in this repo does. The catalog is:
88
+ //
89
+ // meta gen --list --format json --probe
90
+ //
91
+ // \`--probe\` runs every generator against YOUR model and reports how many files each
92
+ // would emit, so you can see what your metadata is already asking for. Group by
93
+ // \`layer\`: model / persistence / api / client / docs / capability.
94
+ //
95
+ // Then take the ones you want:
96
+ //
97
+ // meta eject entity queries routes barrel
98
+ //
99
+ // That copies each generator into ./codegen/generators/ — YOURS to edit, and what
100
+ // \`meta gen\` runs — and prints the import line to add here, the entry to add below,
101
+ // what to install, and any config keys those generators read (\`dbImport\`,
102
+ // \`apiPrefix\`, \`extStyle\`, …). Add them here as you go.
103
+ generators: [],
104
+
128
105
  docs: {
129
- outDir: "${DEFAULT_DOCS_DIR}", // every surface lands here (run: meta docs).
106
+ outDir: "${DEFAULT_DOCS_DIR}", // every surface lands here (run: meta docs).
130
107
  // A SUB-directory on purpose: these pages are regenerated
131
108
  // and overwritten, and docs/ itself is usually yours.
132
- layout: "flat", // or "package" for multi-package models
109
+ layout: "flat", // or "package" for multi-package models
133
110
  // surfaces defaults to ["model", "api", "requirements", "agent"] — all four.
134
- // Deliberately NOT narrowed here: the requirements and agent surfaces emit ZERO
135
- // files for a project that has nothing for them to describe, and the always-on
136
- // agent context points at the agent/ pages by name. Scaffolding a narrower list
137
- // turned that pointer into a pointer at nothing. Narrow it yourself if you want
138
- // fewer docs — but note it says what your docs ARE, not which of them you commit
139
- // (a page you do not commit is exempt from verify --docs once it is git-ignored).
111
+ // Documentation is the one thing that IS on by default, because the requirements
112
+ // and agent surfaces emit ZERO files for a project with nothing to describe, and
113
+ // the always-on agent context points at the agent/ pages by name.
140
114
  },
141
115
  });
142
116
  `;
143
117
  }
144
- // The throwing-stub scaffolded at `dbImport`'s resolved path (DB_STUB_REL_PATH,
145
- // "src/db.ts" by default). It exists so `meta gen` and a fresh project's FIRST
146
- // `tsc` both succeed with no driver chosen and no dependency added — deliberately
147
- // NOT a real connection. Every generated route only ever passes `db` straight
148
- // through to `mountCrudRoutes(...)`; nothing reads a property off it at import
149
- // time, so a value typed `unknown` (not `any`) satisfies every call site while
150
- // making a genuine runtime use (mountCrudRoutes calling `db.select()` etc.) throw
151
- // immediately with an actionable message instead of failing to resolve at all.
152
- // Built as an array of plain single-quoted lines (not a template literal) so the
153
- // backticks and quotes inside the comment/message need no escaping.
154
- const DB_STUB_BODY = [
155
- "// `meta init` scaffolded this file because the generated Fastify routes",
156
- '// `import { db } from "../db.js"` (see `dbImport` in metaobjects.config.ts) —',
157
- "// a module that has to exist for `meta gen` and `tsc` to succeed. MetaObjects",
158
- "// cannot fill it in for real without choosing a database driver on your",
159
- "// behalf (better-sqlite3 vs @libsql/client vs pg vs postgres.js) and adding a",
160
- "// dependency you may not want, so this is a STUB, not a connection.",
161
- "//",
162
- "// It type-checks and satisfies every generated import, but throws the first",
163
- "// time anything actually touches `db` at runtime. Replace the export below",
164
- "// with your real Drizzle connection, e.g.:",
165
- "//",
166
- '// import { drizzle } from "drizzle-orm/better-sqlite3";',
167
- '// import Database from "better-sqlite3";',
168
- '// export const db = drizzle(new Database("dev.sqlite"));',
169
- "//",
170
- "// (swap the driver import for your dialect — see",
171
- "// https://github.com/metaobjectsdev/metaobjects/blob/main/docs/recipes/wiring-generated-queries.md",
172
- "// for SQLite/libsql, Cloudflare D1, Postgres and multi-tenant setups.)",
173
- "",
174
- "const UNWIRED_MESSAGE =",
175
- ' "src/db.ts is still the scaffolded stub meta init wrote — it cannot choose " +',
176
- " \"a database driver for you. Replace 'export const db = ...' below with \" +",
177
- ' "your real Drizzle connection, e.g.:\\n\\n" +',
178
- " \" import { drizzle } from 'drizzle-orm/better-sqlite3';\\n\" +",
179
- " \" import Database from 'better-sqlite3';\\n\" +",
180
- " \" export const db = drizzle(new Database('dev.sqlite'));\\n\";",
181
- "",
182
- "function unwired(): never {",
183
- " throw new Error(UNWIRED_MESSAGE);",
184
- "}",
185
- "",
186
- "/**",
187
- " * Stand-in for your real Drizzle database connection. Generated code only",
188
- " * ever passes `db` straight through to `mountCrudRoutes(...)` — it never",
189
- " * reads a property off it at import time — so this typechecks everywhere",
190
- " * `db` is used, and throws the message above the first time anything really",
191
- " * touches it.",
192
- " */",
193
- "export const db: unknown = new Proxy({}, { get: unwired });",
194
- "",
195
- ].join("\n");
196
- // Printed only when the stub was ACTUALLY written this run. It is gated on having just
197
- // written the scaffolded config (see the db-stub block in `init`), so a re-run in a
198
- // project that keeps its own config writes nothing — and a block claiming otherwise is
199
- // the same "asserting things about its own scaffold that aren't true" defect the rest of
200
- // this file was corrected for.
201
- const DB_STUB_NOTE = `Also scaffolded ${DB_STUB_REL_PATH}: a THROWING STUB standing in for your database
202
- connection, so the generated routes' \`db\` import resolves and the first tsc is clean.
203
- Replace it with a real connection before running the app — until you do, the first
204
- request that touches \`db\` throws with instructions.
205
- `;
206
118
  const SCAFFOLD_SUMMARY = `
207
119
  Initialized metaobjects/ + .metaobjects/ + metaobjects.config.ts
208
- Codegen generators copied to codegen/generators/ — they're YOURS to edit (ADR-0034 scaffold-and-own).
120
+ codegen/generators/ is EMPTY on purpose: no code is generated until you choose it.
209
121
  `;
210
122
  const NEXT_STEPS = `
211
123
  Next steps:
212
124
  0. Everything here is ESM — package.json needs "type": "module" (init sets it
213
125
  unless the project has CommonJS sources; without it the first tsc fails).
214
126
  1. Author entities under metaobjects/ (start from the scaffolded meta.common.json)
215
- 2. meta gen # generate idiomatic TypeScript from your entities
216
- meta gen --dry-run # ...preview without writing
217
- 3. meta docs # neutral model + API docs
218
- 4. Create your tables: meta migrate --from-db --db file:dev.sqlite --dialect sqlite --slug init --apply
127
+ 2. meta gen --list --probe # the catalog: every generator, grouped by layer, with
128
+ # how many files each would emit for YOUR model
129
+ 3. meta eject <name>... # take the ones you want — copies them into
130
+ # codegen/generators/ (yours to edit) and prints the
131
+ # import, the entry to wire, and what to install
132
+ 4. meta gen # generate from exactly what you wired
133
+ 5. meta docs # neutral model + API docs (on by default)
134
+ 6. Create your tables: meta migrate --from-db --db file:dev.sqlite --dialect sqlite --slug init --apply
219
135
 
220
136
  Ship in later sub-projects: meta ingest (propose entities from existing code),
221
137
  meta serve (local viewer), meta install-hooks (MCP server + Claude Code hooks).
@@ -402,29 +318,19 @@ async function wireRootMemory(cwd, result, dryRun = false) {
402
318
  }
403
319
  }
404
320
  /**
405
- * ADR-0034 — copy the codegen reference templates into the consumer's repo at
406
- * `codegen/generators/<name>.ts` so they own them. Each file is written only if absent,
407
- * so a re-run with --force never clobbers a hand-edited generator. The scaffolded
408
- * metaobjects.config.ts imports these local copies (not the package `/generators` export).
321
+ * Scaffold the owned-codegen TIER: the directory and its tsconfig, and nothing in it.
409
322
  *
410
- * Copies SCAFFOLDED_GENERATOR_NAMES only — the five the scaffolded config actually
411
- * wires — not every name @metaobjectsdev/codegen-ts happens to register. Anything else
412
- * (routes-hono, and any UI-tier template from codegen-ts-react/-tanstack) is reached with
413
- * `meta eject <name>`, not by eager copying.
323
+ * `meta init` used to copy five reference generators here eagerly and wire all five in
324
+ * the scaffolded config. Codegen is opt-in now, so it copies NONE: the directory exists
325
+ * because it is part of the layout ADR-0034 promises (and `tsconfig.codegen.json`
326
+ * covers it the moment something lands), and `meta eject <name>...` is the door.
327
+ *
328
+ * The empty directory is not a placeholder for a decision deferred — it is the decision.
329
+ * A scaffold that wires a suite pre-empts the one judgement this design exists to leave
330
+ * to whoever is building the app.
414
331
  */
415
332
  async function writeOwnedGenerators(opts, result) {
416
- const dir = join(opts.cwd, OWNED_GENERATORS_DIR);
417
- await mkdir(dir, { recursive: true });
418
- for (const name of SCAFFOLDED_GENERATOR_NAMES) {
419
- const rel = `${OWNED_GENERATORS_DIR}/${name}.ts`;
420
- const abs = join(dir, `${name}.ts`);
421
- if (await fileExists(abs)) {
422
- result.preserved.push(rel);
423
- continue;
424
- }
425
- await writeFile(abs, readReferenceTemplate(name), "utf8");
426
- result.created.push(rel);
427
- }
333
+ await mkdir(join(opts.cwd, OWNED_GENERATORS_DIR), { recursive: true });
428
334
  await writeCodegenTsconfig(opts, result);
429
335
  }
430
336
  /** The owned codegen tier's own tsconfig — see writeCodegenTsconfig. */
@@ -595,10 +501,8 @@ export async function init(opts) {
595
501
  result.created.push(d);
596
502
  result.created.push("metaobjects/meta.common.json", ".metaobjects/config.json", ".metaobjects/.gitignore");
597
503
  result.created.push(".metaobjects/AGENTS.md", ".metaobjects/CLAUDE.md", ".claude/skills/metaobjects-*", AGENT_CONTEXT_MANIFEST_PATH);
598
- for (const name of SCAFFOLDED_GENERATOR_NAMES)
599
- result.created.push(`${OWNED_GENERATORS_DIR}/${name}.ts`);
600
- result.created.push(CODEGEN_TSCONFIG_REL);
601
- result.created.push("metaobjects.config.ts", DB_STUB_REL_PATH, ".gitignore");
504
+ result.created.push(OWNED_GENERATORS_DIR, CODEGEN_TSCONFIG_REL);
505
+ result.created.push("metaobjects.config.ts", ".gitignore");
602
506
  return result;
603
507
  }
604
508
  for (const d of dirs) {
@@ -629,57 +533,15 @@ export async function init(opts) {
629
533
  result.warnings.push(`note: .metaobjects/${PACKAGE_MANIFEST_FILE} is deprecated (nothing reads it; removed in 2.0) — see docs/features/metadata-dependencies.md`);
630
534
  }
631
535
  await writeAgentContext(opts, result);
632
- // ADR-0034 — scaffold the OWNED codegen generators that metaobjects.config.ts imports
633
- // locally. Done before the config so the import targets exist on first `meta gen`.
536
+ // ADR-0034 — the owned-codegen TIER: the directory and its tsconfig. Nothing is
537
+ // copied into it; `meta eject <name>...` is the door (see writeOwnedGenerators).
634
538
  await writeOwnedGenerators(opts, result);
635
539
  // Scaffold metaobjects.config.ts at the project root. Never overwrite if it exists.
636
540
  const forgeConfigPath = join(opts.cwd, "metaobjects.config.ts");
637
- const wroteScaffoldedConfig = !(await fileExists(forgeConfigPath));
638
- if (wroteScaffoldedConfig) {
541
+ if (!(await fileExists(forgeConfigPath))) {
639
542
  await writeFile(forgeConfigPath, buildMetaobjectsConfigBody(opts.d1 ? "d1" : "sqlite"), "utf8");
640
543
  result.created.push("metaobjects.config.ts");
641
544
  }
642
- // Scaffold the `dbImport` throwing stub at DB_STUB_REL_PATH ("src/db.ts" by
643
- // default) — ONLY if absent, so a re-run never clobbers a user's real db module
644
- // (same "write once" precedent as writeOwnedGenerators above). Without this, the
645
- // scaffolded config declares `dbImport: "../db"` pointing at a module `meta init`
646
- // never creates, and a fresh project's FIRST `tsc` fails to resolve it.
647
- //
648
- // Gated on having just WRITTEN that config, not merely on the stub being absent.
649
- // DB_STUB_REL_PATH is derived from SCAFFOLD_OUT_DIR + SCAFFOLD_DB_IMPORT — the
650
- // scaffold's own constants — so it describes where the SCAFFOLDED config points and
651
- // nowhere else. Re-running `meta init` in a project that already has a config with
652
- // its own `outDir`/`dbImport` preserves that config (above) and would otherwise still
653
- // drop a src/db.ts that nothing in the project references: a stray file, in the
654
- // adopter's application source, answering a question they had already answered.
655
- const dbStubPath = join(opts.cwd, DB_STUB_REL_PATH);
656
- const dbStubExists = await fileExists(dbStubPath);
657
- if (wroteScaffoldedConfig && !dbStubExists) {
658
- await mkdir(dirname(dbStubPath), { recursive: true });
659
- await writeFile(dbStubPath, DB_STUB_BODY, "utf8");
660
- result.created.push(DB_STUB_REL_PATH);
661
- }
662
- else if (dbStubExists) {
663
- // "preserved" means a file we would have written was left alone. Skipping because
664
- // this project keeps its own config is not preservation — there is nothing there.
665
- result.preserved.push(DB_STUB_REL_PATH);
666
- }
667
- else {
668
- // Neither written nor preserved: this project keeps its own config AND has no stub.
669
- // Usually correct — its `dbImport` points at a real module somewhere else. But it is
670
- // ALSO what a scaffolded project looks like after someone deletes or moves src/db.ts,
671
- // and `meta init --force` cannot tell those apart: `wroteScaffoldedConfig` is only
672
- // "no config existed", so it is false for the config init itself wrote. Silently
673
- // doing nothing there leaves the scaffolded `dbImport: "../db"` and the generated
674
- // routes' `import { db } from "../db.js"` pointing at nothing, and the adopter meets
675
- // it as a TS2307 from `tsc` with no word from the command that could have said so.
676
- // Say it here rather than writing: dropping a file into a project that owns its
677
- // config is what the branch above deliberately refuses.
678
- result.warnings.push(`${DB_STUB_REL_PATH} was not scaffolded — this project has its own ` +
679
- "metaobjects.config.ts, so init leaves the database module to it. If that config's " +
680
- `\`dbImport\` resolves to ${DB_STUB_REL_PATH}, create it or the generated routes will ` +
681
- "not resolve.");
682
- }
683
545
  // Scaffold a minimal root .gitignore ONLY when the project has none — never
684
546
  // clobber a user's existing one (they may have their own rules).
685
547
  const rootGitignorePath = join(opts.cwd, ".gitignore");
@@ -777,187 +639,23 @@ async function prepareManifestForScaffold(cwd, result) {
777
639
  "which a CommonJS project cannot compile.";
778
640
  }
779
641
  }
780
- const added = addScaffoldDevDependencies(pkg);
781
- const addedRuntime = addScaffoldRuntimeDependencies(pkg);
782
- if (moduleSystemNote !== undefined || added.length > 0 || addedRuntime.length > 0) {
642
+ // NO DEPENDENCIES ARE ADDED. `meta init` used to declare five packages here —
643
+ // codegen-ts and metadata for the generators it copied, drizzle-orm / zod / fastify
644
+ // for the code those generators would write — and every one of them was a
645
+ // consequence of the scaffold WIRING a suite. It wires nothing now, so declaring
646
+ // anything would be declaring a dependency on code this project may never generate.
647
+ //
648
+ // The need did not vanish, it MOVED to the moment a generator is chosen:
649
+ // `meta eject <name>...` reports the exact install set for what you took, with
650
+ // third-party ranges read from the runtime package's own peerDependencies (see
651
+ // lib/install-set.ts), and `meta gen --list --format json` carries the same set per
652
+ // entry before you commit to anything.
653
+ if (moduleSystemNote !== undefined) {
783
654
  // Preserve the file's existing indentation rather than reformatting someone's manifest.
784
655
  const indent = /\n(\s+)"/.exec(raw)?.[1] ?? " ";
785
656
  await writeFile(pkgPath, `${JSON.stringify(pkg, null, indent)}\n`, "utf8");
786
- }
787
- if (moduleSystemNote !== undefined)
788
657
  result.warnings.push(moduleSystemNote);
789
- if (added.length > 0) {
790
- result.warnings.push(`added ${added.join(" + ")} to devDependencies — the scaffolded ` +
791
- "codegen/generators/ are YOUR source now (ADR-0034) and import them. " +
792
- "Run your package manager's install before `meta gen`.");
793
- }
794
- if (addedRuntime.length > 0) {
795
- result.warnings.push(`added ${addedRuntime.join(" + ")} to dependencies — the code \`meta gen\` writes ` +
796
- "imports them, so `npx tsc` reports TS2307 on the generated files until they are " +
797
- "installed. Run your package manager's install before `meta gen`.");
798
- }
799
- // Never degrade in silence. The first version of this returned `{}` on any failure to
800
- // read runtime-ts's peer ranges and skipped the three packages that come from them —
801
- // which is exactly what happened against the published package, and the run looked
802
- // successful while leaving the project with the six TS2307s this code exists to
803
- // prevent. A fallback that cannot be observed is indistinguishable from a bug.
804
- const undeclarable = SCAFFOLD_OUTPUT_PEERS.filter((name) => !declaredDependencyNames(pkg).has(name));
805
- if (undeclarable.length > 0) {
806
- result.warnings.push(`could NOT declare ${undeclarable.join(" + ")} — the version ranges are read from ` +
807
- "@metaobjectsdev/runtime-ts's own peerDependencies and that manifest could not be " +
808
- "read. Generated code imports them, so add them to dependencies by hand or " +
809
- "`npx tsc` will report TS2307 on files `meta gen` writes.");
810
- }
811
- }
812
- /**
813
- * ADR-0034 scaffold-and-own hands the project real source files under
814
- * `codegen/generators/`, and those files import `@metaobjectsdev/codegen-ts` and
815
- * `@metaobjectsdev/metadata`. Installing `@metaobjectsdev/cli` alone does not put
816
- * either of them where the project can resolve them, so the scaffold arrived
817
- * un-typecheckable: ten TS2307s on files `meta init` had just written.
818
- *
819
- * Declaring them is the honest fix — they are dependencies of code that now lives in
820
- * the adopter's repo. Deliberately NOT declared: `ts-poet`. The scaffolded templates
821
- * import the ts-poet combinators via @metaobjectsdev/codegen-ts (re-exported from its
822
- * own ts-poet instance) precisely so that the Code objects they compose share ONE
823
- * ts-poet copy with the engine's render* primitives — a project-local ts-poet is the
824
- * second physical copy that split the class identity under a globally-installed /
825
- * linked CLI (duplicate imports in generated files, TS2300 on first tsc; see the
826
- * gen-split-tree gate). Only ever ADDS a missing key: an existing pin is the user's.
827
- * Returns what it added so the caller can tell them to install.
828
- */
829
- function addScaffoldDevDependencies(pkg) {
830
- const version = cliVersion();
831
- const wanted = {
832
- "@metaobjectsdev/codegen-ts": `^${version}`,
833
- "@metaobjectsdev/metadata": `^${version}`,
834
- };
835
- const dev = (pkg.devDependencies ?? {});
836
- // "Already declared" spans all four dependency fields — the shared rule, so this
837
- // cannot drift from what `meta eject` means by the same words. It used to ask only
838
- // dependencies + devDependencies, which meant a project declaring codegen-ts as a
839
- // PEER dependency (correct for a library whose consumer supplies the version) got it
840
- // added to devDependencies as well: the same package pinned twice in one manifest,
841
- // and a second physical copy is the class-identity split this repo has been bitten
842
- // by twice.
843
- const declared = declaredDependencyNames(pkg);
844
- const added = [];
845
- for (const [name, range] of Object.entries(wanted)) {
846
- if (declared.has(name))
847
- continue;
848
- dev[name] = range;
849
- added.push(name);
850
- }
851
- if (added.length > 0) {
852
- pkg.devDependencies = Object.fromEntries(Object.entries(dev).sort(([a], [b]) => a.localeCompare(b)));
853
- }
854
- return added;
855
- }
856
- /**
857
- * The third-party packages the scaffolded suite's GENERATED OUTPUT imports, each named
858
- * with the artifact that imports it. This is the set, not the ranges — see
859
- * `scaffoldRuntimeDependencies` for where those come from.
860
- *
861
- * `@metaobjectsdev/runtime-ts` is deliberately not here: its range is the CLI's own
862
- * version, like the two build-time packages above, not a peer range read off itself.
863
- */
864
- const SCAFFOLD_OUTPUT_PEERS = [
865
- "drizzle-orm", // <Entity>.ts (the table + column builders) and <Entity>.queries.ts
866
- "zod", // <Entity>.ts — the Insert/Update schemas
867
- "fastify", // <Entity>.routes.ts — `import type { FastifyInstance }`
868
- ];
869
- /**
870
- * The runtime dependencies of the code `meta gen` will WRITE, for `dependencies`.
871
- *
872
- * `addScaffoldDevDependencies` above fixed this defect one layer in: the generator
873
- * SOURCES under `codegen/generators/` import `@metaobjectsdev/codegen-ts` and
874
- * `@metaobjectsdev/metadata`, nothing declared them, and the scaffold arrived
875
- * un-typecheckable. The same argument reaches one layer further out and was not
876
- * followed there. Generated `<Entity>.ts` / `.queries.ts` / `.routes.ts` import
877
- * drizzle-orm, zod, fastify and `@metaobjectsdev/runtime-ts/drizzle-fastify` — five
878
- * specifiers, none declared — so `npx tsc`, which is the next step `meta gen` itself
879
- * prints, reported NINE TS2307s on a brand-new project that had done nothing wrong.
880
- * npm hides four of the five by hoisting them out of the CLI's own tree; pnpm's strict
881
- * layout, which is the point of testing both, shows all five.
882
- *
883
- * `dependencies`, not `devDependencies`: generated routes and queries are application
884
- * source that runs in production. The two build-time packages stay where they are.
885
- *
886
- * The RANGES are read from `@metaobjectsdev/runtime-ts`'s own `peerDependencies` rather
887
- * than written here. That package already declares the versions its helpers are built
888
- * against, bounded above (the peer-range gate enforces the bound), so a second copy of
889
- * those ranges in the scaffolder is a second thing to keep in step — and the failure
890
- * mode of drift is an adopter installing a major nothing was tested against. If a range
891
- * cannot be read, the package is SKIPPED rather than guessed at, and `meta gen` still
892
- * type-checks for anyone whose manifest already declares it.
893
- */
894
- function scaffoldRuntimeDependencies() {
895
- const wanted = {
896
- "@metaobjectsdev/runtime-ts": `^${cliVersion()}`,
897
- };
898
- const peers = runtimeTsPeerRanges();
899
- for (const name of SCAFFOLD_OUTPUT_PEERS) {
900
- const range = peers[name];
901
- if (range !== undefined)
902
- wanted[name] = range;
903
- }
904
- return wanted;
905
- }
906
- /**
907
- * `@metaobjectsdev/runtime-ts`'s declared peer ranges, or `{}` if they cannot be read.
908
- *
909
- * Resolves the package's ENTRY and walks up to the nearest package.json, rather than
910
- * asking for `"@metaobjectsdev/runtime-ts/package.json"` directly. That direct form is
911
- * the obvious one and it is WRONG: a package's `exports` map gates every subpath, and
912
- * runtime-ts's exports are `.` / `./drivers` / `./fastify` / `./drizzle-fastify` /
913
- * `./hono` — no `./package.json`. Under real Node it throws
914
- * ERR_PACKAGE_PATH_NOT_EXPORTED. It appeared to work here only because the workspace
915
- * runs this under bun against a symlinked source tree; against the published package it
916
- * failed on the first try, silently, and `meta init` declared one package instead of
917
- * four. Resolving the entry is never gated — `.` is the one subpath every package
918
- * exports — so this form works under npm, pnpm, bun and a linked checkout alike.
919
- */
920
- function runtimeTsPeerRanges() {
921
- try {
922
- const req = createRequire(import.meta.url);
923
- let dir = dirname(req.resolve("@metaobjectsdev/runtime-ts"));
924
- // The entry sits under dist/; the manifest is at the package root above it.
925
- for (let hops = 0; hops < 8; hops++) {
926
- const candidate = join(dir, "package.json");
927
- if (existsSyncWrap(candidate)) {
928
- const manifest = JSON.parse(readFileSyncWrap(candidate, "utf8"));
929
- // Guard against stopping at a nested manifest that is not the package itself.
930
- if (manifest.name === "@metaobjectsdev/runtime-ts") {
931
- return (manifest.peerDependencies ?? {});
932
- }
933
- }
934
- const up = dirname(dir);
935
- if (up === dir)
936
- break;
937
- dir = up;
938
- }
939
- return {};
940
- }
941
- catch {
942
- return {};
943
- }
944
- }
945
- /** Adds any missing `scaffoldRuntimeDependencies()` to `dependencies`. Only ever ADDS a
946
- * missing key — an existing pin, in any of the four dependency fields, is the user's. */
947
- function addScaffoldRuntimeDependencies(pkg) {
948
- const deps = (pkg.dependencies ?? {});
949
- const declared = declaredDependencyNames(pkg);
950
- const added = [];
951
- for (const [name, range] of Object.entries(scaffoldRuntimeDependencies())) {
952
- if (declared.has(name))
953
- continue;
954
- deps[name] = range;
955
- added.push(name);
956
- }
957
- if (added.length > 0) {
958
- pkg.dependencies = Object.fromEntries(Object.entries(deps).sort(([a], [b]) => a.localeCompare(b)));
959
658
  }
960
- return added;
961
659
  }
962
660
  /** True when the project has hand-written CommonJS at the root (excluding tooling dirs). */
963
661
  async function hasCommonJsSources(cwd) {
@@ -1015,15 +713,13 @@ function buildD1MigrateBlock(cwd) {
1015
713
  /**
1016
714
  * The post-init message.
1017
715
  *
1018
- * @param dbStubWritten whether THIS run wrote {@link DB_STUB_REL_PATH}. Required rather
1019
- * than defaulted, because a default would silently restore the bug this parameter
1020
- * exists to close: the note used to be part of one static string and so claimed the
1021
- * stub on every run, including the `meta init --force` in a project keeping its own
1022
- * `metaobjects.config.ts`, where the stub is deliberately not written. Callers pass
1023
- * `result.created.includes(DB_STUB_REL_PATH)` — the same list the message describes.
716
+ * Takes no arguments now. It used to take `dbStubWritten`, because the scaffold wrote a
717
+ * throwing `src/db.ts` on some runs and not others and a static string claimed it on
718
+ * every one. With nothing wired there is no `dbImport` and no stub, so the message is
719
+ * the same on every path again.
1024
720
  */
1025
- export function nextStepsBlock(dbStubWritten) {
1026
- return SCAFFOLD_SUMMARY + (dbStubWritten ? DB_STUB_NOTE : "") + NEXT_STEPS;
721
+ export function nextStepsBlock() {
722
+ return SCAFFOLD_SUMMARY + NEXT_STEPS;
1027
723
  }
1028
724
  async function dirExists(p) {
1029
725
  try {
@@ -1102,7 +798,7 @@ export async function initCommand(args, cwd) {
1102
798
  log.warn(w);
1103
799
  }
1104
800
  else {
1105
- log.info(nextStepsBlock(result.created.includes(DB_STUB_REL_PATH)));
801
+ log.info(nextStepsBlock());
1106
802
  // Surface any scaffold warnings (e.g. the #77 monorepo-subdir agent-context
1107
803
  // discovery warning) — these are otherwise dropped on the normal init path.
1108
804
  for (const w of result.warnings)