@metaobjectsdev/cli 1.0.13 → 1.1.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.
Files changed (186) hide show
  1. package/README.md +32 -22
  2. package/dist/bin/meta.js +14 -2
  3. package/dist/bin/meta.js.map +1 -1
  4. package/dist/src/commands/docs.d.ts +1 -14
  5. package/dist/src/commands/docs.d.ts.map +1 -1
  6. package/dist/src/commands/docs.js +11 -835
  7. package/dist/src/commands/docs.js.map +1 -1
  8. package/dist/src/commands/eject.d.ts +24 -1
  9. package/dist/src/commands/eject.d.ts.map +1 -1
  10. package/dist/src/commands/eject.js +240 -69
  11. package/dist/src/commands/eject.js.map +1 -1
  12. package/dist/src/commands/gen.d.ts +7 -0
  13. package/dist/src/commands/gen.d.ts.map +1 -1
  14. package/dist/src/commands/gen.js +91 -21
  15. package/dist/src/commands/gen.js.map +1 -1
  16. package/dist/src/commands/generator.js +2 -2
  17. package/dist/src/commands/generator.js.map +1 -1
  18. package/dist/src/commands/init.d.ts +7 -0
  19. package/dist/src/commands/init.d.ts.map +1 -1
  20. package/dist/src/commands/init.js +71 -36
  21. package/dist/src/commands/init.js.map +1 -1
  22. package/dist/src/commands/migrate.d.ts +1 -1
  23. package/dist/src/commands/migrate.d.ts.map +1 -1
  24. package/dist/src/commands/migrate.js +224 -125
  25. package/dist/src/commands/migrate.js.map +1 -1
  26. package/dist/src/commands/types.d.ts.map +1 -1
  27. package/dist/src/commands/types.js +16 -2
  28. package/dist/src/commands/types.js.map +1 -1
  29. package/dist/src/commands/upgrade.d.ts.map +1 -1
  30. package/dist/src/commands/upgrade.js +4 -1
  31. package/dist/src/commands/upgrade.js.map +1 -1
  32. package/dist/src/commands/verify.d.ts.map +1 -1
  33. package/dist/src/commands/verify.js +189 -125
  34. package/dist/src/commands/verify.js.map +1 -1
  35. package/dist/src/index.d.ts.map +1 -1
  36. package/dist/src/index.js +59 -72
  37. package/dist/src/index.js.map +1 -1
  38. package/dist/src/lib/agent-context-staleness.d.ts +8 -1
  39. package/dist/src/lib/agent-context-staleness.d.ts.map +1 -1
  40. package/dist/src/lib/agent-context-staleness.js +20 -5
  41. package/dist/src/lib/agent-context-staleness.js.map +1 -1
  42. package/dist/src/lib/agent-schema.d.ts +9 -0
  43. package/dist/src/lib/agent-schema.d.ts.map +1 -0
  44. package/dist/src/lib/agent-schema.js +61 -0
  45. package/dist/src/lib/agent-schema.js.map +1 -0
  46. package/dist/src/lib/allow.d.ts +6 -0
  47. package/dist/src/lib/allow.d.ts.map +1 -1
  48. package/dist/src/lib/allow.js +49 -5
  49. package/dist/src/lib/allow.js.map +1 -1
  50. package/dist/src/lib/anti-patterns.d.ts.map +1 -1
  51. package/dist/src/lib/anti-patterns.js +4 -2
  52. package/dist/src/lib/anti-patterns.js.map +1 -1
  53. package/dist/src/lib/args.d.ts +51 -10
  54. package/dist/src/lib/args.d.ts.map +1 -1
  55. package/dist/src/lib/args.js +44 -18
  56. package/dist/src/lib/args.js.map +1 -1
  57. package/dist/src/lib/catalog-listing.d.ts +21 -1
  58. package/dist/src/lib/catalog-listing.d.ts.map +1 -1
  59. package/dist/src/lib/catalog-listing.js +85 -16
  60. package/dist/src/lib/catalog-listing.js.map +1 -1
  61. package/dist/src/lib/catalog.d.ts +30 -0
  62. package/dist/src/lib/catalog.d.ts.map +1 -1
  63. package/dist/src/lib/catalog.js +40 -5
  64. package/dist/src/lib/catalog.js.map +1 -1
  65. package/dist/src/lib/codegen-drift.d.ts +31 -2
  66. package/dist/src/lib/codegen-drift.d.ts.map +1 -1
  67. package/dist/src/lib/codegen-drift.js +109 -4
  68. package/dist/src/lib/codegen-drift.js.map +1 -1
  69. package/dist/src/lib/collection-load-options.d.ts +1 -1
  70. package/dist/src/lib/collection-load-options.d.ts.map +1 -1
  71. package/dist/src/lib/collection-load-options.js +1 -0
  72. package/dist/src/lib/collection-load-options.js.map +1 -1
  73. package/dist/src/lib/config.d.ts +4 -2
  74. package/dist/src/lib/config.d.ts.map +1 -1
  75. package/dist/src/lib/config.js +4 -2
  76. package/dist/src/lib/config.js.map +1 -1
  77. package/dist/src/lib/decimal-precision-advisory.d.ts +10 -0
  78. package/dist/src/lib/decimal-precision-advisory.d.ts.map +1 -0
  79. package/dist/src/lib/decimal-precision-advisory.js +34 -0
  80. package/dist/src/lib/decimal-precision-advisory.js.map +1 -0
  81. package/dist/src/lib/deprecation-lint.d.ts.map +1 -1
  82. package/dist/src/lib/deprecation-lint.js +17 -26
  83. package/dist/src/lib/deprecation-lint.js.map +1 -1
  84. package/dist/src/lib/docs-removed.d.ts +13 -0
  85. package/dist/src/lib/docs-removed.d.ts.map +1 -0
  86. package/dist/src/lib/docs-removed.js +31 -0
  87. package/dist/src/lib/docs-removed.js.map +1 -0
  88. package/dist/src/lib/docs-site-eject.d.ts +13 -0
  89. package/dist/src/lib/docs-site-eject.d.ts.map +1 -0
  90. package/dist/src/lib/docs-site-eject.js +40 -0
  91. package/dist/src/lib/docs-site-eject.js.map +1 -0
  92. package/dist/src/lib/field-lint.d.ts +25 -0
  93. package/dist/src/lib/field-lint.d.ts.map +1 -0
  94. package/dist/src/lib/field-lint.js +102 -0
  95. package/dist/src/lib/field-lint.js.map +1 -0
  96. package/dist/src/lib/fk-index-advisory.d.ts +1 -2
  97. package/dist/src/lib/fk-index-advisory.d.ts.map +1 -1
  98. package/dist/src/lib/fk-index-advisory.js +4 -3
  99. package/dist/src/lib/fk-index-advisory.js.map +1 -1
  100. package/dist/src/lib/format.d.ts +7 -0
  101. package/dist/src/lib/format.d.ts.map +1 -1
  102. package/dist/src/lib/format.js +13 -0
  103. package/dist/src/lib/format.js.map +1 -1
  104. package/dist/src/lib/gen-migrate-advice.d.ts +3 -1
  105. package/dist/src/lib/gen-migrate-advice.d.ts.map +1 -1
  106. package/dist/src/lib/gen-migrate-advice.js +3 -4
  107. package/dist/src/lib/gen-migrate-advice.js.map +1 -1
  108. package/dist/src/lib/install-set.d.ts +10 -1
  109. package/dist/src/lib/install-set.d.ts.map +1 -1
  110. package/dist/src/lib/install-set.js +20 -2
  111. package/dist/src/lib/install-set.js.map +1 -1
  112. package/dist/src/lib/load-metaobjects-config.d.ts +13 -11
  113. package/dist/src/lib/load-metaobjects-config.d.ts.map +1 -1
  114. package/dist/src/lib/load-metaobjects-config.js +53 -75
  115. package/dist/src/lib/load-metaobjects-config.js.map +1 -1
  116. package/dist/src/lib/manifest-ignored-check.d.ts +4 -1
  117. package/dist/src/lib/manifest-ignored-check.d.ts.map +1 -1
  118. package/dist/src/lib/manifest-ignored-check.js +9 -4
  119. package/dist/src/lib/manifest-ignored-check.js.map +1 -1
  120. package/dist/src/lib/output.d.ts +15 -0
  121. package/dist/src/lib/output.d.ts.map +1 -1
  122. package/dist/src/lib/output.js +9 -1
  123. package/dist/src/lib/output.js.map +1 -1
  124. package/dist/src/lib/payload-field-tree.d.ts +4 -5
  125. package/dist/src/lib/payload-field-tree.d.ts.map +1 -1
  126. package/dist/src/lib/payload-field-tree.js +16 -31
  127. package/dist/src/lib/payload-field-tree.js.map +1 -1
  128. package/dist/src/lib/requirement-check.d.ts +7 -0
  129. package/dist/src/lib/requirement-check.d.ts.map +1 -1
  130. package/dist/src/lib/requirement-check.js +14 -7
  131. package/dist/src/lib/requirement-check.js.map +1 -1
  132. package/dist/src/lib/strict-args.d.ts +28 -0
  133. package/dist/src/lib/strict-args.d.ts.map +1 -0
  134. package/dist/src/lib/strict-args.js +49 -0
  135. package/dist/src/lib/strict-args.js.map +1 -0
  136. package/dist/src/lib/template-eject.d.ts +108 -0
  137. package/dist/src/lib/template-eject.d.ts.map +1 -0
  138. package/dist/src/lib/template-eject.js +438 -0
  139. package/dist/src/lib/template-eject.js.map +1 -0
  140. package/dist/src/lib/version.d.ts +9 -0
  141. package/dist/src/lib/version.d.ts.map +1 -1
  142. package/dist/src/lib/version.js +15 -0
  143. package/dist/src/lib/version.js.map +1 -1
  144. package/package.json +10 -10
  145. package/src/commands/docs.ts +11 -952
  146. package/src/commands/eject.ts +318 -73
  147. package/src/commands/gen.ts +94 -22
  148. package/src/commands/generator.ts +2 -2
  149. package/src/commands/init.ts +80 -38
  150. package/src/commands/migrate.ts +248 -117
  151. package/src/commands/types.ts +18 -2
  152. package/src/commands/upgrade.ts +5 -1
  153. package/src/commands/verify.ts +229 -136
  154. package/src/index.ts +59 -72
  155. package/src/lib/agent-context-staleness.ts +20 -5
  156. package/src/lib/agent-schema.ts +74 -0
  157. package/src/lib/allow.ts +57 -6
  158. package/src/lib/anti-patterns.ts +4 -2
  159. package/src/lib/args.ts +83 -28
  160. package/src/lib/catalog-listing.ts +105 -25
  161. package/src/lib/catalog.ts +51 -6
  162. package/src/lib/codegen-drift.ts +138 -3
  163. package/src/lib/collection-load-options.ts +2 -1
  164. package/src/lib/config.ts +5 -3
  165. package/src/lib/decimal-precision-advisory.ts +50 -0
  166. package/src/lib/deprecation-lint.ts +16 -24
  167. package/src/lib/docs-removed.ts +41 -0
  168. package/src/lib/docs-site-eject.ts +48 -0
  169. package/src/lib/field-lint.ts +122 -0
  170. package/src/lib/fk-index-advisory.ts +4 -4
  171. package/src/lib/format.ts +12 -0
  172. package/src/lib/gen-migrate-advice.ts +6 -4
  173. package/src/lib/install-set.ts +20 -1
  174. package/src/lib/load-metaobjects-config.ts +57 -78
  175. package/src/lib/manifest-ignored-check.ts +14 -10
  176. package/src/lib/output.ts +18 -1
  177. package/src/lib/payload-field-tree.ts +18 -35
  178. package/src/lib/requirement-check.ts +21 -6
  179. package/src/lib/strict-args.ts +52 -0
  180. package/src/lib/template-eject.ts +526 -0
  181. package/src/lib/version.ts +18 -0
  182. package/dist/src/lib/docs-drift.d.ts +0 -42
  183. package/dist/src/lib/docs-drift.d.ts.map +0 -1
  184. package/dist/src/lib/docs-drift.js +0 -264
  185. package/dist/src/lib/docs-drift.js.map +0 -1
  186. package/src/lib/docs-drift.ts +0 -312
package/src/lib/args.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { parseArgs } from "node:util";
1
+ import { parseCommandArgs } from "./strict-args.js";
2
2
  import type { DeclaredRename } from "@metaobjectsdev/migrate-ts";
3
3
  import { parseAdvisoryLimit } from "./advisory.js";
4
4
 
@@ -36,7 +36,7 @@ export const INIT_OPTIONS = {
36
36
  } as const;
37
37
 
38
38
  export function parseInitArgs(argv: string[]): InitFlags {
39
- const { values } = parseArgs({
39
+ const { values } = parseCommandArgs("init", {
40
40
  args: argv,
41
41
  options: INIT_OPTIONS,
42
42
  strict: true,
@@ -79,7 +79,7 @@ export const AGENT_DOCS_OPTIONS = {
79
79
  } as const;
80
80
 
81
81
  export function parseAgentDocsArgs(argv: string[]): AgentDocsFlags {
82
- const { values } = parseArgs({
82
+ const { values } = parseCommandArgs("agent-docs", {
83
83
  args: argv,
84
84
  options: AGENT_DOCS_OPTIONS,
85
85
  strict: true,
@@ -122,6 +122,12 @@ export interface GenFlags {
122
122
  * a structured payload, which carries every finding.
123
123
  */
124
124
  limit: number;
125
+ /**
126
+ * `--generators <names>`: narrow the run to these declared generators, by stable name
127
+ * (ADR-0034 Amendment 9). Undefined runs everything the config declares. A name the
128
+ * config does not declare is refused; a narrowing never adds a generator.
129
+ */
130
+ generators?: string[];
125
131
  }
126
132
 
127
133
  /** The flag table `parseGenArgs` parses. Exported so the help text can be gated against it. */
@@ -132,10 +138,11 @@ export const GEN_OPTIONS = {
132
138
  "probe": { type: "boolean", default: false },
133
139
  "no-antipatterns": { type: "boolean", default: false },
134
140
  "limit": { type: "string" },
141
+ "generators": { type: "string" },
135
142
  } as const;
136
143
 
137
144
  export function parseGenArgs(argv: string[]): GenFlags {
138
- const { values, positionals } = parseArgs({
145
+ const { values, positionals } = parseCommandArgs("gen", {
139
146
  args: argv,
140
147
  options: GEN_OPTIONS,
141
148
  strict: true,
@@ -162,9 +169,19 @@ export function parseGenArgs(argv: string[]): GenFlags {
162
169
  // Throws on a bad value; the command layer reports it and exits 2, exactly as
163
170
  // it does for --baseline above.
164
171
  limit: parseAdvisoryLimit(values.limit as string | undefined),
172
+ ...(values.generators !== undefined && { generators: parseGeneratorNames(values.generators as string) }),
165
173
  };
166
174
  }
167
175
 
176
+ /** `docs,api-docs` → `["docs", "api-docs"]`; an empty list is refused, since it would run nothing. */
177
+ function parseGeneratorNames(raw: string): string[] {
178
+ const names = raw.split(",").map((n) => n.trim()).filter((n) => n !== "");
179
+ if (names.length === 0) {
180
+ throw new Error("--generators needs at least one generator name, e.g. --generators docs,api-docs");
181
+ }
182
+ return names;
183
+ }
184
+
168
185
  // ---------------------------------------------------------------------------
169
186
  // export flags
170
187
  // ---------------------------------------------------------------------------
@@ -179,7 +196,7 @@ export const EXPORT_OPTIONS = {
179
196
  } as const;
180
197
 
181
198
  export function parseExportArgs(argv: string[]): ExportFlags {
182
- const { values } = parseArgs({
199
+ const { values } = parseCommandArgs("export", {
183
200
  args: argv,
184
201
  options: EXPORT_OPTIONS,
185
202
  strict: true,
@@ -207,7 +224,7 @@ export const FMT_OPTIONS = {
207
224
  } as const;
208
225
 
209
226
  export function parseFmtArgs(argv: string[]): FmtFlags {
210
- const { values, positionals } = parseArgs({
227
+ const { values, positionals } = parseCommandArgs("fmt", {
211
228
  args: argv,
212
229
  options: FMT_OPTIONS,
213
230
  strict: true,
@@ -251,6 +268,20 @@ function assertSchemaDialect(dialect: string | undefined): void {
251
268
  export const MIGRATE_FORMATS = ["default", "flyway"] as const;
252
269
  export type MigrateFormat = (typeof MIGRATE_FORMATS)[number];
253
270
 
271
+ /**
272
+ * A `--migration-format` value, validated. One parser for `meta migrate` and `meta verify`,
273
+ * so the two accept the same formats and refuse a bad one with the same message.
274
+ *
275
+ * The flag is --migration-format, not --format: `--format` is already a GLOBAL cli flag
276
+ * selecting output rendering (toon|json|text), consumed in index.ts.
277
+ */
278
+ function parseMigrationFormat(raw: string | undefined): MigrateFormat | undefined {
279
+ if (raw !== undefined && !MIGRATE_FORMATS.includes(raw as MigrateFormat)) {
280
+ throw new Error(`invalid --migration-format '${raw}'; expected: ${MIGRATE_FORMATS.join(", ")}`);
281
+ }
282
+ return raw as MigrateFormat | undefined;
283
+ }
284
+
254
285
  // Exported (not just module-local) so allow-tokens-pinned.test.ts can pin it
255
286
  // against sdk's AllowTokenEnum (config.json's static migrate.allow validator)
256
287
  // — the two lists drifted silently before that test existed: sdk's enum was
@@ -308,6 +339,12 @@ export interface VerifyFlags {
308
339
  allow: AllowToken[];
309
340
  /** Skip the schema-drift gate even when --db is present. */
310
341
  skipSchema: boolean;
342
+ /**
343
+ * The migration file layout the project uses, spelled as `meta migrate` spells it and
344
+ * resolved with the same precedence (flag > `.metaobjects/config.json` > default). Under
345
+ * `flyway` the schema-drift gate leaves Flyway's own history table out of the comparison.
346
+ */
347
+ migrationFormat: MigrateFormat | undefined;
311
348
  /**
312
349
  * #225 — D1 binding name from wrangler.toml, spelled/documented the same way
313
350
  * `meta migrate --d1 <binding>` does. Gated on `--dialect d1` (D1 has no URL
@@ -334,16 +371,9 @@ export interface VerifyFlags {
334
371
  */
335
372
  forbidHandEdits: boolean;
336
373
  /**
337
- * Run the docs-drift gate: regenerate the `meta docs` surfaces into a temp dir and diff
338
- * them against the committed docs tree.
339
- *
340
- * A SEPARATE subverb rather than part of `--codegen`, because they check different
341
- * trees under different ownership rules: `--codegen` regenerates `outDir`/`targets` and
342
- * must respect the hand edits `meta gen` preserves, while the docs tree has no merge,
343
- * no manifest, and lives in a directory the adopter chose (`./docs/generated` by
344
- * default) that may hold hand-written files it does not own. Folding them together
345
- * would have to pick one of those rules
346
- * for both.
374
+ * `--docs`, removed in 1.1.0 (ADR-0034 Amendment 9): `--codegen` checks documentation with
375
+ * everything else the generator list declares. Still parsed, so the command can say where
376
+ * it went (exit 2) rather than "unknown flag".
347
377
  */
348
378
  docs: boolean;
349
379
  /**
@@ -381,6 +411,21 @@ export interface VerifyFlags {
381
411
  * the half that CAN fail a build switched on. Same shape as --no-antipatterns.
382
412
  */
383
413
  noRequirementLint: boolean;
414
+ /**
415
+ * The requirement gate's strict switch (ADR-0057): report a live functional
416
+ * requirement that nothing implements (`WARN_REQUIREMENT_NOTHING_IMPLEMENTS`) at
417
+ * severity `error`, so it fails the build. The code is unchanged and no other
418
+ * diagnostic moves. Off by default because a ledger authored ahead of its links is
419
+ * the normal incremental state.
420
+ *
421
+ * Named for what it requires, not `--strict`: `--lax` below is a different axis
422
+ * (ADR-0023 attribute strictness), and a `--strict` beside it would read as that
423
+ * flag's opposite rather than as a requirement-gate setting — the same reason
424
+ * `--replay-snapshot` above is a subverb and not a `--strict` modifier. It is not
425
+ * an explicit subverb either: the requirement gate runs on every `verify`, so this
426
+ * flag modifies a gate that is already selected.
427
+ */
428
+ requireImplementers: boolean;
384
429
  /**
385
430
  * Suppress the advisory overlay AUTHORING lint (FR-023 §11.1 item 4) — the
386
431
  * finding that an unflagged cross-file redeclaration works today only
@@ -405,6 +450,13 @@ export interface VerifyFlags {
405
450
  * and can never fail the build.
406
451
  */
407
452
  noDeprecationLint: boolean;
453
+ /**
454
+ * Suppress the advisory FIELD authoring lint — a reference identity whose
455
+ * `@fields` names a field the object lacks, and a field name declared twice in
456
+ * one children list. Advisory only, like its siblings: it has no gate half and
457
+ * can never fail the build.
458
+ */
459
+ noFieldLint: boolean;
408
460
  /**
409
461
  * ADR-0023 strict-attr load opt-OUT (#96). `verify` is strict-by-default — an
410
462
  * undeclared/typo'd own `@attr` fails verify (ERR_UNKNOWN_ATTR). `--lax`
@@ -436,18 +488,23 @@ export const VERIFY_OPTIONS = {
436
488
  dialect: { type: "string" },
437
489
  allow: { type: "string", multiple: true },
438
490
  "skip-schema": { type: "boolean", default: false },
491
+ "migration-format": { type: "string" },
439
492
  templates: { type: "boolean", default: false },
440
493
  codegen: { type: "boolean", default: false },
441
494
  "forbid-hand-edits": { type: "boolean", default: false },
495
+ // Removed in 1.1.0 (ADR-0034 Amendment 9). Still parsed, so `meta verify --docs` prints
496
+ // where the check went instead of an unknown-flag error.
442
497
  docs: { type: "boolean", default: false },
443
498
  deps: { type: "boolean", default: false },
444
499
  replay: { type: "boolean", default: false },
445
500
  "replay-snapshot": { type: "boolean", default: false },
446
501
  "no-antipatterns": { type: "boolean", default: false },
447
502
  "no-requirement-lint": { type: "boolean", default: false },
503
+ "require-implementers": { type: "boolean", default: false },
448
504
  "no-overlay-lint": { type: "boolean", default: false },
449
505
  "no-name-lint": { type: "boolean", default: false },
450
506
  "no-deprecation-lint": { type: "boolean", default: false },
507
+ "no-field-lint": { type: "boolean", default: false },
451
508
  lax: { type: "boolean", default: false },
452
509
  "d1": { type: "string" },
453
510
  "remote": { type: "boolean", default: false },
@@ -482,7 +539,7 @@ function parseAllowTokens(raw: string | string[] | undefined): AllowToken[] {
482
539
  }
483
540
 
484
541
  export function parseVerifyArgs(argv: string[]): VerifyFlags {
485
- const { values } = parseArgs({
542
+ const { values } = parseCommandArgs("verify", {
486
543
  args: argv,
487
544
  options: VERIFY_OPTIONS,
488
545
  strict: true,
@@ -521,6 +578,7 @@ export function parseVerifyArgs(argv: string[]): VerifyFlags {
521
578
  dialect: dialect as Dialect | undefined,
522
579
  allow: allowTokens as AllowToken[],
523
580
  skipSchema: !!values["skip-schema"],
581
+ migrationFormat: parseMigrationFormat(values["migration-format"] as string | undefined),
524
582
  templates,
525
583
  codegen,
526
584
  forbidHandEdits,
@@ -531,9 +589,11 @@ export function parseVerifyArgs(argv: string[]): VerifyFlags {
531
589
  anyExplicit,
532
590
  noAntipatterns: !!values["no-antipatterns"],
533
591
  noRequirementLint: !!values["no-requirement-lint"],
592
+ requireImplementers: !!values["require-implementers"],
534
593
  noOverlayLint: !!values["no-overlay-lint"],
535
594
  noNameLint: !!values["no-name-lint"],
536
595
  noDeprecationLint: !!values["no-deprecation-lint"],
596
+ noFieldLint: !!values["no-field-lint"],
537
597
  lax: !!values.lax,
538
598
  d1: values.d1 as string | undefined,
539
599
  remote: !!values.remote,
@@ -561,7 +621,7 @@ export const PROMPT_SNAPSHOT_OPTIONS = {
561
621
  } as const;
562
622
 
563
623
  export function parsePromptSnapshotArgs(argv: string[]): PromptSnapshotFlags {
564
- const { values } = parseArgs({
624
+ const { values } = parseCommandArgs("prompt-snapshot", {
565
625
  args: argv,
566
626
  options: PROMPT_SNAPSHOT_OPTIONS,
567
627
  strict: true,
@@ -632,7 +692,7 @@ export const MIGRATE_OPTIONS = {
632
692
  } as const;
633
693
 
634
694
  export function parseMigrateArgs(argv: string[]): MigrateFlags {
635
- const { values, positionals } = parseArgs({
695
+ const { values, positionals } = parseCommandArgs("migrate", {
636
696
  args: argv,
637
697
  options: MIGRATE_OPTIONS,
638
698
  strict: true,
@@ -652,12 +712,7 @@ export function parseMigrateArgs(argv: string[]): MigrateFlags {
652
712
  const dialect = values.dialect as string | undefined;
653
713
  assertSchemaDialect(dialect);
654
714
 
655
- // NOTE: the flag is --migration-format, not --format: `--format` is already a
656
- // GLOBAL cli flag selecting output rendering (toon|json|text), consumed in index.ts.
657
- const format = values["migration-format"] as string | undefined;
658
- if (format !== undefined && !MIGRATE_FORMATS.includes(format as MigrateFormat)) {
659
- throw new Error(`invalid --migration-format '${format}'; expected: ${MIGRATE_FORMATS.join(", ")}`);
660
- }
715
+ const format = parseMigrationFormat(values["migration-format"] as string | undefined);
661
716
 
662
717
  const onAmb = values["on-ambiguous"] as string | undefined;
663
718
  if (onAmb !== undefined && !ON_AMBIGUOUS.includes(onAmb as OnAmbiguous)) {
@@ -669,7 +724,7 @@ export function parseMigrateArgs(argv: string[]): MigrateFlags {
669
724
  return {
670
725
  db: values.db as string | undefined,
671
726
  dialect: dialect as Dialect | undefined,
672
- format: format as MigrateFormat | undefined,
727
+ format,
673
728
  outDir: values["out-dir"] as string | undefined,
674
729
  slug: values.slug as string | undefined,
675
730
  allow: allowTokens as AllowToken[],
@@ -749,7 +804,7 @@ export const EJECT_OPTIONS = {
749
804
  } as const;
750
805
 
751
806
  export function parseEjectArgs(argv: string[]): EjectFlags {
752
- const { values, positionals } = parseArgs({
807
+ const { values, positionals } = parseCommandArgs("eject", {
753
808
  args: argv,
754
809
  options: EJECT_OPTIONS,
755
810
  strict: true,
@@ -811,7 +866,7 @@ export const DEPS_OPTIONS = {
811
866
  } as const;
812
867
 
813
868
  export function parseDepsArgs(argv: string[]): DepsFlags {
814
- const { values, positionals } = parseArgs({
869
+ const { values, positionals } = parseCommandArgs("deps", {
815
870
  args: argv,
816
871
  options: DEPS_OPTIONS,
817
872
  strict: true,
@@ -25,11 +25,12 @@ import {
25
25
  type GeneratorRegistryEntry,
26
26
  type Layer,
27
27
  type MetaobjectsGenConfig,
28
+ type RunGenOpts,
28
29
  } from "@metaobjectsdev/codegen-ts";
29
30
  import * as coreTpl from "@metaobjectsdev/codegen-ts";
30
31
  import * as reactTpl from "@metaobjectsdev/codegen-ts-react";
31
32
  import * as tanstackTpl from "@metaobjectsdev/codegen-ts-tanstack";
32
- import { composeCatalog, listCatalog, packageOf } from "./catalog.js";
33
+ import { composeCatalog, listCatalog, packageOf, packagedWiring, type GeneratorWiring } from "./catalog.js";
33
34
  import {
34
35
  buildLibraryRows, renderLibraryText,
35
36
  type LibraryCatalogRow, type LibraryProjectContext,
@@ -39,7 +40,8 @@ import {
39
40
  RUNTIME_ENTRIES, resolveRuntimePackage, runtimeClosure, runtimeCopyStatus, runtimeInstall,
40
41
  type RuntimeCopyRow,
41
42
  } from "./runtime-eject.js";
42
- import { OWNED_RUNTIME_DIR } from "@metaobjectsdev/codegen-ts";
43
+ import { closureOf, packagedTemplates, templateCopyStatus, type TemplateCopyRow } from "./template-eject.js";
44
+ import { DEFAULT_DOCS_DIR, DOCS_TARGET_NAME, OWNED_RUNTIME_DIR } from "@metaobjectsdev/codegen-ts";
43
45
  import { readPackageManifest, declaredDependencyNames } from "./package-manifest.js";
44
46
  import { describeError } from "./error-text.js";
45
47
 
@@ -62,6 +64,8 @@ export interface GeneratorCatalogRow {
62
64
  tier: "native" | "neutral";
63
65
  package: string;
64
66
  description: string;
67
+ /** The registry's one line a reader needs before wiring this entry. Printed under the row. */
68
+ note?: string;
65
69
  /** From the reference template's `use-when:` header. Absent for a non-ejectable entry. */
66
70
  useWhen?: string;
67
71
  /** From the reference template's `emits:` header. Absent for a non-ejectable entry. */
@@ -69,6 +73,11 @@ export interface GeneratorCatalogRow {
69
73
  requires?: readonly string[];
70
74
  configKeys?: readonly string[];
71
75
  install: { dev: string[]; runtime: string[] };
76
+ /**
77
+ * How to run this generator AS SHIPPED: the import line for its package factory. No
78
+ * copy involved; `meta eject` prints the local import for an owned one.
79
+ */
80
+ wire?: GeneratorWiring;
72
81
  source: {
73
82
  kind: "reference-template" | "package-only";
74
83
  ejectable: boolean;
@@ -81,6 +90,12 @@ export interface GeneratorCatalogRow {
81
90
  * imports no adapter, or the project owns none of it.
82
91
  */
83
92
  runtimeCopy?: RuntimeCopyRow[];
93
+ /**
94
+ * ADR-0034 Amendment 8: the template modules this generator calls, as far as the project
95
+ * owns a copy of them under `codegen/templates/` — each file compared to what the
96
+ * installed packages would write there. Absent when the project owns none of them.
97
+ */
98
+ templateCopy?: TemplateCopyRow[];
84
99
  };
85
100
  project?: {
86
101
  /** Wired in this project's `generators: [...]`. */
@@ -191,6 +206,10 @@ export interface CatalogListingOpts {
191
206
  * "probe failed" on every fresh project, for a generator that would in fact run.
192
207
  */
193
208
  sourceFiles?: readonly string[];
209
+ /** The metadata source directories, as `meta gen` supplies them: the docs site loads them. */
210
+ sourceRoots?: readonly string[];
211
+ /** The physical schema `agent/schema.md` describes, as `meta gen` supplies it. */
212
+ agentSchema?: RunGenOpts["agentSchema"];
194
213
  };
195
214
  }
196
215
 
@@ -205,10 +224,9 @@ export interface CatalogListingOpts {
205
224
  async function probeOne(
206
225
  entry: GeneratorRegistryEntry,
207
226
  project: CatalogProject,
208
- metadata: MetaData,
209
- scope: ((fqn: string) => boolean) | undefined,
210
- sourceFiles: readonly string[] | undefined,
227
+ probe: NonNullable<CatalogListingOpts["probe"]>,
211
228
  ): Promise<{ count: number; needsConfig?: string[] } | { error: string }> {
229
+ const { metadata, scope, sourceFiles, sourceRoots, agentSchema } = probe;
212
230
  const count = async (config: MetaobjectsGenConfig): Promise<number> => {
213
231
  const result = await runGen({
214
232
  config: { ...config, generators: [entry.factory()] },
@@ -217,6 +235,8 @@ async function probeOne(
217
235
  dryRun: true,
218
236
  ...(scope !== undefined ? { scope } : {}),
219
237
  ...(sourceFiles !== undefined ? { sourceFiles } : {}),
238
+ ...(sourceRoots !== undefined ? { sourceRoots } : {}),
239
+ ...(agentSchema !== undefined ? { agentSchema } : {}),
220
240
  });
221
241
  return result.files.length;
222
242
  };
@@ -230,12 +250,23 @@ async function probeOne(
230
250
  // report the key as needed rather than dropping the count: the listing used to show
231
251
  // `routes … [fastify]` beside `routes-hono … [hono, would emit 3]` with no reason.
232
252
  // Only a retry that SUCCEEDS with the key supplied attributes the failure to it.
253
+ //
254
+ // A documentation generator refuses to run while `targets.docs` is unset (ADR-0034
255
+ // Amendment 9) — again exactly the fresh project `--probe` is asked about — and the
256
+ // directory changes no file count either, so it is supplied the same way.
257
+ const needsConfig: string[] = [];
258
+ let retry: MetaobjectsGenConfig = project.config;
233
259
  if (project.config.dbImport === undefined) {
260
+ retry = { ...retry, dbImport: PROBE_DB_IMPORT_PLACEHOLDER };
261
+ needsConfig.push("dbImport");
262
+ }
263
+ if (entry.factory().target === DOCS_TARGET_NAME && project.config.targets?.[DOCS_TARGET_NAME] === undefined) {
264
+ retry = { ...retry, targets: { ...retry.targets, [DOCS_TARGET_NAME]: { outDir: DEFAULT_DOCS_DIR } } };
265
+ needsConfig.push(`targets.${DOCS_TARGET_NAME}`);
266
+ }
267
+ if (needsConfig.length > 0) {
234
268
  try {
235
- return {
236
- count: await count({ ...project.config, dbImport: PROBE_DB_IMPORT_PLACEHOLDER }),
237
- needsConfig: ["dbImport"],
238
- };
269
+ return { count: await count(retry), needsConfig };
239
270
  } catch {
240
271
  // Fall through: the original error is the one worth reporting.
241
272
  }
@@ -275,12 +306,40 @@ async function ownedRuntimeByGenerator(
275
306
  return out;
276
307
  }
277
308
 
309
+ /**
310
+ * Per OWNED generator, the project's copies of the template modules it calls. Attributed by
311
+ * what each generator reaches and only for generators the project owns, as the adapter copy
312
+ * is: `queries` and `entity` share modules, and a project that ejected only one of them
313
+ * must not be told it owns the other's.
314
+ */
315
+ async function ownedTemplatesByGenerator(
316
+ projectRoot: string,
317
+ ownedNames: ReadonlySet<string>,
318
+ ): Promise<Map<string, TemplateCopyRow[]>> {
319
+ const out = new Map<string, TemplateCopyRow[]>();
320
+ const status = await templateCopyStatus(projectRoot, packagedTemplates());
321
+ if (status.length === 0) return out;
322
+ for (const name of ownedNames) {
323
+ let reached: Set<string>;
324
+ try {
325
+ reached = new Set(closureOf(name).templates.map((f) => f.path));
326
+ } catch {
327
+ continue; // not a reference generator
328
+ }
329
+ const files = status.filter((r) => reached.has(r.path));
330
+ if (files.length > 0) out.set(name, files);
331
+ }
332
+ return out;
333
+ }
334
+
278
335
  export async function buildCatalogListing(opts: CatalogListingOpts = {}): Promise<CatalogRow[]> {
279
336
  const rows: CatalogRow[] = [];
280
337
  const runtimeCopies = opts.project === undefined
281
338
  ? new Map<string, { files: RuntimeCopyRow[]; packages: string[] }>()
282
339
  : await ownedRuntimeByGenerator(opts.project.projectRoot, opts.project.ownedNames);
283
-
340
+ const templateCopies = opts.project === undefined
341
+ ? new Map<string, TemplateCopyRow[]>()
342
+ : await ownedTemplatesByGenerator(opts.project.projectRoot, opts.project.ownedNames);
284
343
  for (const entry of listCatalog()) {
285
344
  const template = entry.ejectable ? readTemplate(entry.name) : undefined;
286
345
  // A project that owns this generator's adapter copy installs what the COPY imports,
@@ -290,6 +349,11 @@ export async function buildCatalogListing(opts: CatalogListingOpts = {}): Promis
290
349
  ? installSetFor([entry])
291
350
  : installSetFor([entry], [], { generators: new Set([entry.name]), ...runtimeInstall(owned.packages) });
292
351
  const pkg = packageOf(entry.name) ?? "";
352
+ // The row keeps the stock wiring even when this project owns the copy: the JSON
353
+ // consumer reads `source.owned` to know what it runs today, and the package line is
354
+ // the fact it needs to go back to stock. `renderCatalogText` deliberately hides that
355
+ // line under an owned row — two imports in front of a human invite wiring both.
356
+ const wire = packagedWiring(entry);
293
357
 
294
358
  const row: GeneratorCatalogRow = {
295
359
  name: entry.name,
@@ -299,6 +363,7 @@ export async function buildCatalogListing(opts: CatalogListingOpts = {}): Promis
299
363
  tier: entry.tier,
300
364
  package: pkg,
301
365
  description: entry.description,
366
+ ...(entry.note !== undefined ? { note: entry.note } : {}),
302
367
  ...(template !== undefined && headerFacet(template, "use-when") !== undefined
303
368
  ? { useWhen: headerFacet(template, "use-when")! }
304
369
  : {}),
@@ -308,11 +373,13 @@ export async function buildCatalogListing(opts: CatalogListingOpts = {}): Promis
308
373
  ...(entry.requires !== undefined ? { requires: entry.requires } : {}),
309
374
  ...(entry.configKeys !== undefined ? { configKeys: entry.configKeys } : {}),
310
375
  install: { dev: install.dev, runtime: install.runtime },
376
+ ...(wire !== undefined ? { wire } : {}),
311
377
  source: {
312
378
  kind: entry.ejectable ? "reference-template" : "package-only",
313
379
  ejectable: entry.ejectable,
314
380
  owned: opts.project === undefined ? null : opts.project.ownedNames.has(entry.name),
315
381
  ...(owned !== undefined ? { runtimeCopy: owned.files } : {}),
382
+ ...(templateCopies.has(entry.name) ? { templateCopy: templateCopies.get(entry.name)! } : {}),
316
383
  },
317
384
  };
318
385
 
@@ -328,13 +395,7 @@ export async function buildCatalogListing(opts: CatalogListingOpts = {}): Promis
328
395
  let probeError: string | undefined;
329
396
  let needsConfig: string[] | undefined;
330
397
  if (opts.probe !== undefined) {
331
- const outcome = await probeOne(
332
- entry,
333
- project,
334
- opts.probe.metadata,
335
- opts.probe.scope,
336
- opts.probe.sourceFiles,
337
- );
398
+ const outcome = await probeOne(entry, project, opts.probe);
338
399
  if ("count" in outcome) {
339
400
  wouldEmit = outcome.count;
340
401
  needsConfig = outcome.needsConfig;
@@ -421,18 +482,20 @@ const LAYER_BLURB: Record<Layer, string> = {
421
482
  persistence: "how rows are read and written",
422
483
  api: "the HTTP surface — pick ONE framework",
423
484
  client: "the browser tier — form + hooks + grid compose, they do not conflict",
424
- docs: "on by default; the canonical door is `meta docs`",
485
+ docs: "pages about the model and the code — listed like the rest, written to the `docs` target",
425
486
  capability: "chosen by your MODEL, not by browsing — run --probe",
426
487
  };
427
488
 
428
489
  /**
429
490
  * ADR-0034 Amendment 3: every generator is a reference helper, not a guarantee. Said at
430
491
  * the top of the catalog because this is where an adopter decides what to depend on.
492
+ * Amendment 5: both ways to run one are named here — import it, or own a copy.
431
493
  */
432
494
  export const REFERENCE_HELPER_NOTE: readonly string[] = [
433
- "Each generator is a reference helper, not a guarantee: `meta eject <name>` copies it into",
434
- "your repo and the copy is yours to change. What MetaObjects guarantees (migrate, verify,",
435
- "render) is not a generator and is not listed here.",
495
+ "Each generator is a reference helper, not a guarantee. Import it from its package to run",
496
+ "it as shipped, or `meta eject <name>` to copy it into your repo and change what it emits.",
497
+ "What MetaObjects guarantees (migrate, verify, render) is not a generator and is not",
498
+ "listed here.",
436
499
  "Your database, store or framework is not listed (MongoDB, DynamoDB, Express, NestJS, Vue, …)?",
437
500
  "Model it anyway and write or retarget a generator: the metaobjects-codegen skill's",
438
501
  "references/any-stack.md has the procedure. MySQL is `dialect: \"mysql\"`.",
@@ -440,8 +503,8 @@ export const REFERENCE_HELPER_NOTE: readonly string[] = [
440
503
 
441
504
  /** The footer line explaining the `package-only` row mark. */
442
505
  export const PACKAGE_ONLY_NOTE =
443
- "package-only = no reference template ships for it yet, so `meta eject` cannot copy it; " +
444
- "import it from its package instead.";
506
+ "package-only = no reference template ships for it, so `meta eject` cannot copy it. " +
507
+ "Run one as shipped by importing its factory — the import sits under its row.";
445
508
 
446
509
  /** The human rendering: grouped by layer, because layer is the axis you select by. */
447
510
  export function renderCatalogText(rows: CatalogRow[], probed: boolean): string {
@@ -473,6 +536,15 @@ export function renderCatalogText(rows: CatalogRow[], probed: boolean): string {
473
536
  : `owned runtime: ${differing} file(s) DIFFER from the package — meta eject --list`,
474
537
  );
475
538
  }
539
+ if (r.source.templateCopy !== undefined) {
540
+ // The template modules this generator calls, compared to the packages.
541
+ const differing = r.source.templateCopy.filter((c) => c.verdict === "differs").length;
542
+ marks.push(
543
+ differing === 0
544
+ ? "owned templates: identical to the package"
545
+ : `owned templates: ${differing} file(s) DIFFER from the package — meta eject --list`,
546
+ );
547
+ }
476
548
  if (probed && r.project?.wouldEmit !== null && r.project?.wouldEmit !== undefined) {
477
549
  const needs = r.project.needsConfig;
478
550
  marks.push(
@@ -487,9 +559,16 @@ export function renderCatalogText(rows: CatalogRow[], probed: boolean): string {
487
559
  }
488
560
  const suffix = marks.length > 0 ? ` [${marks.join(", ")}]` : "";
489
561
  lines.push(` ${r.name.padEnd(width)} — ${r.description}${suffix}`);
562
+ if (r.note !== undefined) lines.push(` ${" ".repeat(width)} ${r.note}`);
490
563
  if (r.requires !== undefined && r.requires.length > 0) {
491
564
  lines.push(` ${" ".repeat(width)} requires: ${r.requires.join(", ")}`);
492
565
  }
566
+ // The import that runs it as shipped. Not shown once the project owns a copy: its
567
+ // wiring is then the local import `meta eject` printed, and showing the package's
568
+ // beside an `owned` mark invites wiring both.
569
+ if (r.wire !== undefined && r.source.owned !== true) {
570
+ lines.push(` ${" ".repeat(width)} ${r.wire.import}`);
571
+ }
493
572
  }
494
573
 
495
574
  // The library section sits between the generators and the footer: the footer's
@@ -498,8 +577,9 @@ export function renderCatalogText(rows: CatalogRow[], probed: boolean): string {
498
577
  lines.push(...renderLibraryText(rows.filter((r): r is LibraryCatalogRow => r.kind === "library")));
499
578
 
500
579
  lines.push("");
501
- lines.push("`meta eject <name...>` copies a generator into codegen/generators/ and prints");
502
- lines.push("the import to add, the entry to wire, and what to install.");
580
+ lines.push("To run a generator as shipped, add the import under its row and call it in");
581
+ lines.push("`generators: [...]`. To change what one emits, `meta eject <name...>` copies it into");
582
+ lines.push("codegen/generators/ and prints the import to swap in, the entry to wire, and what to install.");
503
583
  if (generators.some((r) => !r.source.ejectable)) lines.push(PACKAGE_ONLY_NOTE);
504
584
  if (!probed) {
505
585
  lines.push("Add --probe to see how many files each would emit for YOUR model.");
@@ -21,11 +21,24 @@ import {
21
21
  import { reactGeneratorRegistry } from "@metaobjectsdev/codegen-ts-react";
22
22
  import { tanstackGeneratorRegistry } from "@metaobjectsdev/codegen-ts-tanstack";
23
23
 
24
- /** The slices, in install-boundary order: the engine, then its framework packages. */
25
- const SLICES: ReadonlyArray<readonly [string, Record<string, GeneratorRegistryEntry>]> = [
26
- ["@metaobjectsdev/codegen-ts", generatorRegistry],
27
- ["@metaobjectsdev/codegen-ts-react", reactGeneratorRegistry],
28
- ["@metaobjectsdev/codegen-ts-tanstack", tanstackGeneratorRegistry],
24
+ /**
25
+ * The engine package. Its factories are served from a `/generators` subpath rather than
26
+ * its root (the root is the engine plus the primitives an owned generator composes); the
27
+ * framework packages serve theirs from the root. Exported because `eject.ts`'s source
28
+ * table names the same package — one definition per CLI package, not two.
29
+ */
30
+ export const CORE_CODEGEN_PACKAGE = "@metaobjectsdev/codegen-ts";
31
+
32
+ /**
33
+ * The slices, in install-boundary order: the engine, then its framework packages. The
34
+ * third element is the module that package serves factories from — a per-package fact
35
+ * declared at registration, so {@link factoryModuleOf} is a lookup and a new slice
36
+ * carries its own fact instead of adding a case to a name-keyed branch.
37
+ */
38
+ const SLICES: ReadonlyArray<readonly [string, Record<string, GeneratorRegistryEntry>, string]> = [
39
+ [CORE_CODEGEN_PACKAGE, generatorRegistry, `${CORE_CODEGEN_PACKAGE}/generators`],
40
+ ["@metaobjectsdev/codegen-ts-react", reactGeneratorRegistry, "@metaobjectsdev/codegen-ts-react"],
41
+ ["@metaobjectsdev/codegen-ts-tanstack", tanstackGeneratorRegistry, "@metaobjectsdev/codegen-ts-tanstack"],
29
42
  ];
30
43
 
31
44
  /**
@@ -44,6 +57,38 @@ export function catalogPackages(): string[] {
44
57
  return SLICES.map(([pkg]) => pkg);
45
58
  }
46
59
 
60
+ /**
61
+ * The module a catalog entry's factory is imported from to run it as shipped — the third
62
+ * element of the slice that registers the name. Undefined for a name no slice registers.
63
+ */
64
+ export function factoryModuleOf(name: string): string | undefined {
65
+ return SLICES.find(([, slice]) => name in slice)?.[2];
66
+ }
67
+
68
+ /**
69
+ * How one generator reaches a config as shipped: the import line for its factory. The
70
+ * call is not derived here — a factory's options are documented with the generator.
71
+ */
72
+ export interface GeneratorWiring {
73
+ import: string;
74
+ }
75
+
76
+ /**
77
+ * How to wire a catalog entry AS SHIPPED: import its factory from the package, no copy.
78
+ *
79
+ * This is the half of "import by default, eject to customize" the catalog owns. The
80
+ * other half is `meta eject`, which prints the import for the owned copy — the symbol is
81
+ * the same in both (gated), so moving between them is one import line.
82
+ *
83
+ * Every entry has one, documentation included (ADR-0034 Amendment 9): there is one way to
84
+ * generate, and an entry is wired the same way whatever it writes.
85
+ */
86
+ export function packagedWiring(entry: GeneratorRegistryEntry): GeneratorWiring | undefined {
87
+ const from = factoryModuleOf(entry.name);
88
+ if (from === undefined) return undefined;
89
+ return { import: `import { ${entry.factoryExport} } from "${from}";` };
90
+ }
91
+
47
92
  /**
48
93
  * The composed catalog, keyed by stable name.
49
94
  *
@@ -54,7 +99,7 @@ export function catalogPackages(): string[] {
54
99
  * another.
55
100
  */
56
101
  export function composeCatalog(): Record<string, GeneratorRegistryEntry> {
57
- return composeSlices(SLICES);
102
+ return composeSlices(SLICES.map(([pkg, slice]) => [pkg, slice] as const));
58
103
  }
59
104
 
60
105
  /**