@metaobjectsdev/cli 1.0.2 → 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 (115) hide show
  1. package/README.md +13 -5
  2. package/dist/src/commands/deps.d.ts +3 -0
  3. package/dist/src/commands/deps.d.ts.map +1 -0
  4. package/dist/src/commands/deps.js +200 -0
  5. package/dist/src/commands/deps.js.map +1 -0
  6. package/dist/src/commands/docs.d.ts.map +1 -1
  7. package/dist/src/commands/docs.js +19 -3
  8. package/dist/src/commands/docs.js.map +1 -1
  9. package/dist/src/commands/eject.d.ts +2 -1
  10. package/dist/src/commands/eject.d.ts.map +1 -1
  11. package/dist/src/commands/eject.js +235 -75
  12. package/dist/src/commands/eject.js.map +1 -1
  13. package/dist/src/commands/gen.d.ts.map +1 -1
  14. package/dist/src/commands/gen.js +173 -30
  15. package/dist/src/commands/gen.js.map +1 -1
  16. package/dist/src/commands/init.d.ts +5 -9
  17. package/dist/src/commands/init.d.ts.map +1 -1
  18. package/dist/src/commands/init.js +92 -402
  19. package/dist/src/commands/init.js.map +1 -1
  20. package/dist/src/commands/migrate.d.ts.map +1 -1
  21. package/dist/src/commands/migrate.js +24 -19
  22. package/dist/src/commands/migrate.js.map +1 -1
  23. package/dist/src/commands/prompt-snapshot.d.ts.map +1 -1
  24. package/dist/src/commands/prompt-snapshot.js +2 -1
  25. package/dist/src/commands/prompt-snapshot.js.map +1 -1
  26. package/dist/src/commands/verify.d.ts.map +1 -1
  27. package/dist/src/commands/verify.js +186 -29
  28. package/dist/src/commands/verify.js.map +1 -1
  29. package/dist/src/index.d.ts.map +1 -1
  30. package/dist/src/index.js +97 -15
  31. package/dist/src/index.js.map +1 -1
  32. package/dist/src/lib/advisory.d.ts +6 -0
  33. package/dist/src/lib/advisory.d.ts.map +1 -1
  34. package/dist/src/lib/advisory.js +10 -0
  35. package/dist/src/lib/advisory.js.map +1 -1
  36. package/dist/src/lib/agent-context-staleness.d.ts.map +1 -1
  37. package/dist/src/lib/agent-context-staleness.js +7 -1
  38. package/dist/src/lib/agent-context-staleness.js.map +1 -1
  39. package/dist/src/lib/args.d.ts +75 -3
  40. package/dist/src/lib/args.d.ts.map +1 -1
  41. package/dist/src/lib/args.js +54 -5
  42. package/dist/src/lib/args.js.map +1 -1
  43. package/dist/src/lib/catalog-listing.d.ts +92 -0
  44. package/dist/src/lib/catalog-listing.d.ts.map +1 -0
  45. package/dist/src/lib/catalog-listing.js +268 -0
  46. package/dist/src/lib/catalog-listing.js.map +1 -0
  47. package/dist/src/lib/catalog.d.ts +41 -0
  48. package/dist/src/lib/catalog.d.ts.map +1 -0
  49. package/dist/src/lib/catalog.js +89 -0
  50. package/dist/src/lib/catalog.js.map +1 -0
  51. package/dist/src/lib/collection-load-options.d.ts +13 -0
  52. package/dist/src/lib/collection-load-options.d.ts.map +1 -0
  53. package/dist/src/lib/collection-load-options.js +25 -0
  54. package/dist/src/lib/collection-load-options.js.map +1 -0
  55. package/dist/src/lib/dependency-sync.d.ts +170 -0
  56. package/dist/src/lib/dependency-sync.d.ts.map +1 -0
  57. package/dist/src/lib/dependency-sync.js +457 -0
  58. package/dist/src/lib/dependency-sync.js.map +1 -0
  59. package/dist/src/lib/install-set.d.ts +31 -0
  60. package/dist/src/lib/install-set.d.ts.map +1 -0
  61. package/dist/src/lib/install-set.js +103 -0
  62. package/dist/src/lib/install-set.js.map +1 -0
  63. package/dist/src/lib/library-eject.d.ts +79 -0
  64. package/dist/src/lib/library-eject.d.ts.map +1 -0
  65. package/dist/src/lib/library-eject.js +241 -0
  66. package/dist/src/lib/library-eject.js.map +1 -0
  67. package/dist/src/lib/library-listing.d.ts +63 -0
  68. package/dist/src/lib/library-listing.d.ts.map +1 -0
  69. package/dist/src/lib/library-listing.js +178 -0
  70. package/dist/src/lib/library-listing.js.map +1 -0
  71. package/dist/src/lib/library-prefix-advisory.d.ts +17 -0
  72. package/dist/src/lib/library-prefix-advisory.d.ts.map +1 -0
  73. package/dist/src/lib/library-prefix-advisory.js +67 -0
  74. package/dist/src/lib/library-prefix-advisory.js.map +1 -0
  75. package/dist/src/lib/load-metaobjects-config.d.ts +1 -2
  76. package/dist/src/lib/load-metaobjects-config.d.ts.map +1 -1
  77. package/dist/src/lib/load-metaobjects-config.js +11 -17
  78. package/dist/src/lib/load-metaobjects-config.js.map +1 -1
  79. package/dist/src/lib/migrate-scope.d.ts +32 -1
  80. package/dist/src/lib/migrate-scope.d.ts.map +1 -1
  81. package/dist/src/lib/migrate-scope.js +51 -3
  82. package/dist/src/lib/migrate-scope.js.map +1 -1
  83. package/dist/src/lib/overlay-lint.d.ts +37 -0
  84. package/dist/src/lib/overlay-lint.d.ts.map +1 -0
  85. package/dist/src/lib/overlay-lint.js +102 -0
  86. package/dist/src/lib/overlay-lint.js.map +1 -0
  87. package/dist/src/lib/requirement-check.d.ts +26 -3
  88. package/dist/src/lib/requirement-check.d.ts.map +1 -1
  89. package/dist/src/lib/requirement-check.js +71 -13
  90. package/dist/src/lib/requirement-check.js.map +1 -1
  91. package/package.json +10 -10
  92. package/src/commands/deps.ts +231 -0
  93. package/src/commands/docs.ts +19 -3
  94. package/src/commands/eject.ts +304 -90
  95. package/src/commands/gen.ts +189 -29
  96. package/src/commands/init.ts +95 -407
  97. package/src/commands/migrate.ts +26 -16
  98. package/src/commands/prompt-snapshot.ts +2 -1
  99. package/src/commands/verify.ts +213 -30
  100. package/src/index.ts +97 -15
  101. package/src/lib/advisory.ts +13 -0
  102. package/src/lib/agent-context-staleness.ts +7 -1
  103. package/src/lib/args.ts +118 -8
  104. package/src/lib/catalog-listing.ts +369 -0
  105. package/src/lib/catalog.ts +107 -0
  106. package/src/lib/collection-load-options.ts +42 -0
  107. package/src/lib/dependency-sync.ts +656 -0
  108. package/src/lib/install-set.ts +116 -0
  109. package/src/lib/library-eject.ts +298 -0
  110. package/src/lib/library-listing.ts +258 -0
  111. package/src/lib/library-prefix-advisory.ts +78 -0
  112. package/src/lib/load-metaobjects-config.ts +15 -21
  113. package/src/lib/migrate-scope.ts +59 -4
  114. package/src/lib/overlay-lint.ts +129 -0
  115. package/src/lib/requirement-check.ts +100 -14
@@ -2,6 +2,7 @@ import { relative } from "node:path";
2
2
  import { parseGenArgs } from "../lib/args.js";
3
3
  import { resolveGenConfig } from "../lib/config.js";
4
4
  import { loadMemoryOptionsFrom, loadMetaobjectsConfig, resolveGenCollection, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
5
+ import { collectionLoadOptions } from "../lib/collection-load-options.js";
5
6
  import { formatGenResult, formatGenResultToon, type GenFileEntry, type GenFileStatus } from "../lib/output.js";
6
7
  import { formatGenResultJson } from "../lib/output-json.js";
7
8
  import type { OutputFormat } from "../lib/format.js";
@@ -14,9 +15,18 @@ import {
14
15
  type AdvisoryFindingRow, type AdvisorySection,
15
16
  } from "../lib/advisory.js";
16
17
  import { loadMemory, resolveCollection } from "@metaobjectsdev/sdk";
17
- import { runGen, listGenerators } from "@metaobjectsdev/codegen-ts";
18
+ import { runGen } from "@metaobjectsdev/codegen-ts";
18
19
  import type { WriteStatus } from "@metaobjectsdev/codegen-ts";
20
+ import { packageOfResolutionKey } from "@metaobjectsdev/metadata";
21
+ import type { MetaRoot } from "@metaobjectsdev/metadata";
22
+ import type { Collection } from "@metaobjectsdev/sdk";
19
23
  import { reportLoadError } from "../lib/load-error.js";
24
+ import {
25
+ buildCatalogListing, renderCatalogText, wiredGeneratorNames, ownedGeneratorNames,
26
+ declaredDepsOf,
27
+ } from "../lib/catalog-listing.js";
28
+ import { emitStructured } from "../lib/format.js";
29
+ import { composeCatalog } from "../lib/catalog.js";
20
30
 
21
31
  /**
22
32
  * Print a load failure with everything the loader's ADR-0009 envelope carried — the stable
@@ -52,10 +62,16 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
52
62
  try { flags = parseGenArgs(args); }
53
63
  catch (err) { log.error((err as Error).message); return 2; }
54
64
 
55
- // ADR-0021 D3 — `meta gen --list`: print the stable-name generator registry
56
- // and exit 0 WITHOUT running codegen (no config/metadata required).
65
+ // ADR-0021 D3 — `meta gen --list`: print the generator CATALOG and exit 0 WITHOUT
66
+ // running codegen. No project is required (like `meta types`): "what can this engine
67
+ // do" is a question about the installed engine. `--probe` adds "...and what would
68
+ // each emit for MY model", which does need one.
57
69
  if (flags.list) {
58
- return listGeneratorsCommand();
70
+ return listCatalogCommand(cwd, fmt, flags.probe);
71
+ }
72
+ if (flags.probe) {
73
+ log.error("--probe is only meaningful with --list. Run: meta gen --list --probe");
74
+ return 2;
59
75
  }
60
76
 
61
77
  const cliConfig = resolveGenConfig(flags);
@@ -122,7 +138,7 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
122
138
  let metadata;
123
139
  try {
124
140
  metadata = await loadMemory(genCollection.configDir, {
125
- files: genCollection.files,
141
+ ...collectionLoadOptions(genCollection),
126
142
  ...loadMemoryOptionsFrom(forgeConfig),
127
143
  });
128
144
  } catch (err) {
@@ -130,12 +146,28 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
130
146
  return 2;
131
147
  }
132
148
 
149
+ // FR-023 §11.1 item 2 — refuse a positional that names ONLY objects imported
150
+ // from a dependency and excluded from output by the default exclusion rule.
151
+ // Distinct from the runner's existing "no entities matched" WARNING
152
+ // (runner.ts): that path covers a name matching nothing loaded at all; this
153
+ // one covers a name that matches something real, just not something this
154
+ // project generates. A name matching nothing falls through untouched — the
155
+ // runner's own warning still covers it.
156
+ const importedRefusal = refuseImportedPositional(cliConfig.entities, metadata, genCollection);
157
+ if (importedRefusal !== undefined) {
158
+ log.error(importedRefusal);
159
+ return 2;
160
+ }
161
+
133
162
  let result;
134
163
  try {
135
164
  result = await runGen({
136
165
  config: forgeConfig,
137
166
  metadata,
138
167
  projectRoot,
168
+ // The COMPOSED catalog, so the requires / api-framework gates can see the react
169
+ // and tanstack entries too. codegen-ts only knows its own slice.
170
+ catalog: composeCatalog(),
139
171
  baseline: flags.baseline,
140
172
  // --dry-run must actually preview. This was previously passed only to the
141
173
  // display object below, so a "preview" run wrote every file.
@@ -145,6 +177,14 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
145
177
  // passed: an unconfigured project's predicate admits everything, so this
146
178
  // is a no-op for the common case, not a behavior change.
147
179
  scope: genCollection.inScope,
180
+ // FR-023 §4.3 — the collection's own source files (never a dependency's
181
+ // snapshot artifact), so sharedModelFile() defaults its `files` selection
182
+ // to exactly what this project itself declares.
183
+ sourceFiles: genCollection.ownFiles,
184
+ // FR-043 §6 — the shipped-library selection. Always passed, including as `[]`:
185
+ // that is what tells the post-selection audit "this project opted into none",
186
+ // which is a different statement from a programmatic caller that never said.
187
+ libraries: genCollection.libraries,
148
188
  ...(cliConfig.entities.length > 0 ? { entityFilter: cliConfig.entities } : {}),
149
189
  });
150
190
  } catch (err) {
@@ -154,6 +194,21 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
154
194
 
155
195
  for (const w of result.warnings) { log.warn(w); }
156
196
 
197
+ // A first-run POINTER, not a warning: an empty selection is the designed state of a
198
+ // fresh `meta init`, so calling it a problem would make every new project start with
199
+ // one. It says what to do next and disappears the moment anything is wired.
200
+ if ((forgeConfig.generators?.length ?? 0) === 0) {
201
+ log.info(
202
+ "\nNothing is generated until you choose it — `generators: []` is what `meta init` " +
203
+ "scaffolds, by design.\n" +
204
+ " meta gen --list --probe the catalog, with how many files each generator " +
205
+ "would emit for YOUR model\n" +
206
+ " meta eject <name>... take the ones you want; it prints the import, the " +
207
+ "entry to wire, and what to install\n" +
208
+ "(`meta docs` needs none of this — documentation is on by default.)",
209
+ );
210
+ }
211
+
157
212
  // result.files[].path is the absolute full path from decideAndWrite. With
158
213
  // per-target output, show each path relative to the project root so files in
159
214
  // different targets are distinguishable.
@@ -220,6 +275,45 @@ export async function genCommand(args: string[], cwd: string, fmt: OutputFormat
220
275
  return hasFailure ? 1 : 0;
221
276
  }
222
277
 
278
+ /**
279
+ * FR-023 §11.1 item 2 — the error message for `meta gen <Name>` when EVERY
280
+ * loaded object named `<Name>` is imported from a dependency and excluded from
281
+ * output (imported metadata is load-only by default; only the consumer's own
282
+ * `scope.include` naming the package literally opts it in). Returns undefined
283
+ * for every other case, INCLUDING a name matching nothing at all — that stays
284
+ * the runner's existing "no entities matched" warning, unchanged.
285
+ *
286
+ * A bare positional is a NAME, not a fully-qualified one (`cliConfig.entities`
287
+ * is matched against `entity.name` elsewhere in this file the same way), so two
288
+ * packages declaring the same short name are both candidates; the refusal
289
+ * fires only when NONE of them would generate.
290
+ */
291
+ function refuseImportedPositional(
292
+ entityNames: readonly string[],
293
+ metadata: MetaRoot,
294
+ collection: Collection,
295
+ ): string | undefined {
296
+ if (entityNames.length === 0) return undefined;
297
+ const allObjects = metadata.objects();
298
+ for (const name of entityNames) {
299
+ const matches = allObjects.filter((o) => o.name === name);
300
+ if (matches.length === 0) continue; // nothing loaded by this name — the runner's own warning covers it
301
+ const allExcludedImports = matches.every((o) => {
302
+ const fqn = o.resolutionKey();
303
+ return collection.imported(fqn) && !collection.inScope(fqn);
304
+ });
305
+ if (!allExcludedImports) continue;
306
+ const fqn = matches[0]!.resolutionKey();
307
+ const pkg = packageOfResolutionKey(fqn);
308
+ const dependencyName = collection.dependencies.find((d) => d.packages.includes(pkg))?.name ?? pkg;
309
+ return (
310
+ `meta gen: '${name}' (${fqn}) is imported from dependency '${dependencyName}' and is not ` +
311
+ `generated here — add its package to scope.include in .metaobjects/config.json to generate it`
312
+ );
313
+ }
314
+ return undefined;
315
+ }
316
+
223
317
  /**
224
318
  * Run the advisory verify-as-teacher scan (same pass `meta verify` runs): surface
225
319
  * authored source that hand-rolls what the metadata could model. `gen` is the
@@ -251,34 +345,100 @@ function runAntiPatternScan(
251
345
  }
252
346
 
253
347
  /**
254
- * `meta gen --list` — print the stable-name generator registry (ADR-0021 D3).
348
+ * `meta gen --list` — the generator catalog (ADR-0021 D3; opt-in-codegen design §D3).
255
349
  *
256
- * Generators are grouped by tier: the recommended native `meta gen` suite
257
- * first, then neutral artifacts (owned by `meta docs` per D1). Each line is
258
- * `<stable-name> — <description>` plus an options summary and, for neutral
259
- * entries, a note pointing at the canonical door. Exits 0; no codegen runs.
350
+ * Codegen is opt-in, so this is the door: the tool describes what it can do and the
351
+ * builder decides what the app needs. Rows are grouped by `layer`, the axis you select
352
+ * by; `--format json|toon` emits the same catalog as ONE document (nothing else on
353
+ * stdout, per `meta types`' purity rule).
354
+ *
355
+ * With `--probe`, every catalog generator is constructed and dry-run against this
356
+ * project's real model, so the `capability` layer stops being a list of labels and
357
+ * becomes `output-parser: 3, callable: 0, requirement-tests: 7` — information that
358
+ * cannot go stale, because it does not describe the generators, it runs them.
260
359
  */
261
- function listGeneratorsCommand(): number {
262
- const entries = listGenerators();
263
- const native = entries.filter((e) => e.tier === "native");
264
- const neutral = entries.filter((e) => e.tier === "neutral");
265
- const width = Math.max(...entries.map((e) => e.name.length));
360
+ async function listCatalogCommand(
361
+ cwd: string,
362
+ fmt: OutputFormat,
363
+ probe: boolean,
364
+ ): Promise<number> {
365
+ let opts: Parameters<typeof buildCatalogListing>[0] = {};
266
366
 
267
- const lines: string[] = [];
268
- lines.push("Available generators (select by stable name):");
269
- lines.push("");
270
- lines.push("Native (recommended `meta gen` suite):");
271
- for (const e of native) {
272
- lines.push(` ${e.name.padEnd(width)} — ${e.description}`);
273
- if (e.options) lines.push(` ${" ".repeat(width)} options: ${e.options}`);
274
- }
275
- lines.push("");
276
- lines.push("Neutral (owned by `meta docs`; not part of the native suite):");
277
- for (const e of neutral) {
278
- lines.push(` ${e.name.padEnd(width)} — ${e.description}`);
279
- if (e.note) lines.push(` ${" ".repeat(width)} ${e.note}`);
367
+ if (probe) {
368
+ // A probe reports what YOUR model would produce, so a project is mandatory here
369
+ // even though it is optional for a bare --list. Failing loudly beats a listing of
370
+ // zeros that reads like "none of these apply to you".
371
+ let collection;
372
+ try {
373
+ collection = await resolveCollection(cwd);
374
+ } catch (err) {
375
+ log.error(
376
+ `meta gen --list --probe needs a project to probe: ${(err as Error).message}\n` +
377
+ "Run `meta gen --list` (no --probe) for the catalog on its own.",
378
+ );
379
+ return 2;
380
+ }
381
+ const projectRoot = resolveGenConfigDir(cwd, collection.configDir);
382
+ const genCollection = await resolveGenCollection(collection, projectRoot);
383
+
384
+ let forgeConfig;
385
+ try {
386
+ forgeConfig = await loadMetaobjectsConfig(projectRoot);
387
+ } catch (err) {
388
+ log.error((err as Error).message);
389
+ return 2;
390
+ }
391
+
392
+ let metadata;
393
+ try {
394
+ metadata = await loadMemory(genCollection.configDir, {
395
+ ...collectionLoadOptions(genCollection),
396
+ ...loadMemoryOptionsFrom(forgeConfig),
397
+ });
398
+ } catch (err) {
399
+ reportLoadError(log, "failed to load metadata", err);
400
+ return 2;
401
+ }
402
+
403
+ opts = {
404
+ project: {
405
+ projectRoot,
406
+ config: forgeConfig,
407
+ wiredNames: wiredGeneratorNames(forgeConfig),
408
+ ownedNames: ownedGeneratorNames(projectRoot),
409
+ declaredDeps: declaredDepsOf(projectRoot),
410
+ // FR-043 — the library rows read the selection from the COLLECTION, which is
411
+ // where `libraries` lives now (`.metaobjects/config.json`), not from the
412
+ // codegen config it was moved out of.
413
+ libraries: genCollection.libraries,
414
+ },
415
+ probe: { metadata, scope: genCollection.inScope },
416
+ };
417
+ } else {
418
+ // No probe: still report `wired` / `owned` when a project happens to be here, since
419
+ // both are free. A directory with no config is not an error — the catalog is a fact
420
+ // about the installed engine.
421
+ try {
422
+ const collection = await resolveCollection(cwd);
423
+ const projectRoot = resolveGenConfigDir(cwd, collection.configDir);
424
+ const forgeConfig = await loadMetaobjectsConfig(projectRoot);
425
+ opts = {
426
+ project: {
427
+ projectRoot,
428
+ config: forgeConfig,
429
+ wiredNames: wiredGeneratorNames(forgeConfig),
430
+ ownedNames: ownedGeneratorNames(projectRoot),
431
+ declaredDeps: declaredDepsOf(projectRoot),
432
+ libraries: collection.libraries,
433
+ },
434
+ };
435
+ } catch {
436
+ // No project here — the catalog stands on its own.
437
+ }
280
438
  }
281
439
 
282
- log.info(lines.join("\n"));
440
+ const rows = await buildCatalogListing(opts);
441
+ if (fmt === "text") log.info(renderCatalogText(rows, probe));
442
+ else emitStructured(rows, fmt);
283
443
  return 0;
284
444
  }