@metaobjectsdev/cli 1.0.3 → 1.0.4

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
@@ -249,11 +249,10 @@ function rewriteImportSpecifiers(source: string, aliasMap: Record<string, string
249
249
  * than set to `undefined`, which is what lets `loadMemory` apply its own defaults.
250
250
  */
251
251
  export function loadMemoryOptionsFrom(
252
- cfg: Pick<MetaobjectsGenConfig, "providers" | "libraries"> | undefined,
253
- ): { providers?: readonly MetaDataTypeProvider[]; libraries?: readonly string[] } {
252
+ cfg: Pick<MetaobjectsGenConfig, "providers"> | undefined,
253
+ ): { providers?: readonly MetaDataTypeProvider[] } {
254
254
  return {
255
255
  ...(cfg?.providers !== undefined ? { providers: cfg.providers } : {}),
256
- ...(cfg?.libraries !== undefined ? { libraries: cfg.libraries } : {}),
257
256
  };
258
257
  }
259
258
 
@@ -518,24 +517,19 @@ export async function loadMetaobjectsConfig(projectRoot: string): Promise<Metaob
518
517
  if (!cfg || typeof cfg !== "object" || !Array.isArray(cfg.generators)) {
519
518
  throw new Error(`metaobjects.config.ts at ${fullPath} did not export a valid MetaobjectsGenConfig (missing 'generators' array).`);
520
519
  }
521
- // An unknown `libraries` name is a hard config error naming the valid ones, while
522
- // `librarySources` keeps skipping one silently for a programmatic caller. The two
523
- // are deliberately different: an API caller asking for a package this version does
524
- // not ship should still be able to load its own metadata, but a name a human typed
525
- // into a config file is a mistake worth failing on — skipped, it resurfaces later as
526
- // ERR_UNRESOLVED_SUPER pointing at the adopter's own metadata, which is the wrong
527
- // place to send someone looking. Python's `project_config` draws the same line in
528
- // the same place, and the two ports agreeing here is the point.
529
- if (cfg.libraries !== undefined && cfg.libraries.length > 0) {
530
- const { knownLibraryPackages } = await import("@metaobjectsdev/metadata/library");
531
- const available = knownLibraryPackages();
532
- const unknown = cfg.libraries.filter((n) => !available.includes(n));
533
- if (unknown.length > 0) {
534
- throw new Error(
535
- `metaobjects.config.ts at ${fullPath}: 'libraries' names unknown package(s) ` +
536
- `${JSON.stringify(unknown)}; available: ${JSON.stringify(available)}.`,
537
- );
538
- }
520
+ // `libraries` used to be validated here. It moved to `.metaobjects/config.json`
521
+ // (FR-043 Amendment 1), and so did its validation — see `assertKnownLibraries`,
522
+ // called from `resolveCollection`'s readers. A config still carrying the old key
523
+ // gets a pointed error rather than silence, because silence is what turns a moved
524
+ // key into "my library stopped loading and nothing said why".
525
+ if ("libraries" in (cfg as unknown as Record<string, unknown>)) {
526
+ throw new Error(
527
+ `metaobjects.config.ts at ${fullPath}: 'libraries' moved to ` +
528
+ `.metaobjects/config.json (FR-043). Which designs a project adopts is a fact ` +
529
+ `about the PROJECT, not about how one port generates code from it, and that ` +
530
+ `file is the port-neutral one every port already reads. Move the array across ` +
531
+ `verbatim — the tokens are unchanged.`,
532
+ );
539
533
  }
540
534
  return cfg;
541
535
  } finally {
@@ -42,6 +42,10 @@ import {
42
42
  type MetaRequirement,
43
43
  type RequirementStatus,
44
44
  } from "@metaobjectsdev/metadata";
45
+ // FR-043 §5.4 — the provenance key for coverage activation. A node-only subpath, which
46
+ // is why it is a second import rather than a name on the barrel above: the library
47
+ // module reaches the filesystem and the root barrel stays browser-safe.
48
+ import { libraryPackages } from "@metaobjectsdev/metadata/library";
45
49
 
46
50
  export type Severity = "error" | "warn";
47
51
 
@@ -82,8 +86,11 @@ export interface RequirementSummary {
82
86
  undecided: number;
83
87
  /** deferred entries naming no ticket, so nobody will be reminded. */
84
88
  deferredUntracked: number;
85
- entitiesTotal: number;
86
- entitiesClaimed: number;
89
+ /** ABSENT when coverage was not measured — FR-043 §5.4. A number here is a
90
+ * ratio the project is held to; absence is the honest reading of "this project
91
+ * has authored no requirement of its own, so it asked to be held to none". */
92
+ entitiesTotal?: number;
93
+ entitiesClaimed?: number;
87
94
  }
88
95
 
89
96
  /** Severity of the object-coverage gate. Promotion to `"error"` is a one-line
@@ -255,16 +262,61 @@ export interface RequirementScan {
255
262
  * Threaded through so BOTH `coverableEntities` call sites (the gate and the
256
263
  * summary) inherit the same narrowing — see `coverableEntities`. */
257
264
  readonly coverable?: (fqn: string) => boolean;
265
+ /** FR-043 §5.4 — whether object coverage applies at all on this run. See
266
+ * {@link projectAuthoredRequirements}. Read by BOTH the gate and the summary from
267
+ * this one scan, for the same reason `claimedObjects` is shared: a summary that
268
+ * printed a ratio the gate had not enforced would be a measurement nobody could
269
+ * reconcile with the diagnostics beneath it. */
270
+ readonly measureCoverage: boolean;
271
+ }
272
+
273
+ /**
274
+ * Did the ADOPTER author any of these requirements?
275
+ *
276
+ * FR-043 §5.4, and the rule exists because `checkRequirements` activates object
277
+ * coverage on the presence of any requirement at all. A library ships its own ledger
278
+ * — `iam` ships eleven entries — so without this, opting into a library would switch
279
+ * the unclaimed-entity gate on across a project that has never written a requirement,
280
+ * and report every entity in it. The gate would then be measuring the LIBRARY's
281
+ * decision to ship a ledger rather than anything the adopter did.
282
+ *
283
+ * Provenance is the library's declared PACKAGE. That is a manifest fact the standalone
284
+ * library gate resolves against the library loaded alone, whereas a node's source id
285
+ * differs between the on-disk dev layout (an absolute path) and the embedded one
286
+ * (`library:<ref>.yaml`) — a rule keyed on source would hold in this repo and stop
287
+ * holding in an installed build.
288
+ *
289
+ * An adopter OVERLAYING a library requirement (§5.5) is deliberately not authoring one:
290
+ * the overlay merges into the library's node, in the library's package, and disagreeing
291
+ * with a shipped verdict is a statement about the library's design rather than about
292
+ * what the adopter's own model is for. Treating it as activation would mean correcting
293
+ * one library entry silently acquired a coverage gate over the whole estate.
294
+ */
295
+ function projectAuthoredRequirements(addressed: readonly AddressedRequirement[]): boolean {
296
+ const libPkgs = libraryPackages();
297
+ return addressed.some((r) => {
298
+ const pkg = r.node.package ?? r.node.fileDefaultPackage ?? "";
299
+ return !libPkgs.has(pkg);
300
+ });
258
301
  }
259
302
 
260
303
  export function scanRequirements(
261
304
  root: MetaData,
262
- opts?: { coverable?: (fqn: string) => boolean },
305
+ opts?: {
306
+ coverable?: (fqn: string) => boolean;
307
+ /** Force coverage on or off instead of deriving it. The one caller that legitimately
308
+ * knows better is the gate over a SHIPPED library loaded standalone: there the
309
+ * library IS the project under test, and "every entity this library ships is claimed
310
+ * by its own ledger" is exactly what is being asserted — so the derivation would
311
+ * switch the check off precisely where it is the point. */
312
+ measureCoverage?: boolean;
313
+ },
263
314
  ): RequirementScan {
264
315
  const addressed = collectAddressedRequirements(root);
265
316
  return {
266
317
  addressed,
267
318
  claimedObjects: claimedObjectKeys(root, addressed.map((r) => r.node)),
319
+ measureCoverage: opts?.measureCoverage ?? projectAuthoredRequirements(addressed),
268
320
  // `exactOptionalPropertyTypes` — an omitted key, never an explicit `undefined`.
269
321
  ...(opts?.coverable !== undefined ? { coverable: opts.coverable } : {}),
270
322
  };
@@ -350,7 +402,7 @@ function coverableEntities(root: MetaData, coverable?: (fqn: string) => boolean)
350
402
  */
351
403
  export function checkRequirements(root: MetaData, scan: RequirementScan = scanRequirements(root)): Diagnostic[] {
352
404
  const out: Diagnostic[] = [];
353
- const { addressed, claimedObjects, coverable } = scan;
405
+ const { addressed, claimedObjects, coverable, measureCoverage } = scan;
354
406
  if (addressed.length === 0) return out; // opt-in by declaration — no requirements, nothing to say
355
407
 
356
408
  for (const { node: req, path: reqPath } of addressed) {
@@ -564,7 +616,9 @@ export function checkRequirements(root: MetaData, scan: RequirementScan = scanRe
564
616
  //
565
617
  // So a green run means "every entity is claimed by something", not "every node is
566
618
  // described". The stronger reading would be false.
567
- for (const ent of coverableEntities(root, coverable)) {
619
+ // ADOPTER-AUTHORED ONLY. FR-043 §5.4 — see `projectAuthoredRequirements`. A library's
620
+ // ledger is counted and checked, but it cannot volunteer you for coverage.
621
+ for (const ent of measureCoverage ? coverableEntities(root, coverable) : []) {
568
622
  const key = ent.resolutionKey();
569
623
  if (!claimedObjects.has(key)) {
570
624
  out.push({
@@ -601,8 +655,8 @@ export function summariseRequirements(
601
655
  byStatus: {},
602
656
  undecided: 0,
603
657
  deferredUntracked: 0,
604
- entitiesTotal: 0,
605
- entitiesClaimed: 0,
658
+ // The coverage pair is filled in below only when the run measures coverage —
659
+ // `exactOptionalPropertyTypes`, so the keys are absent rather than undefined.
606
660
  };
607
661
 
608
662
  for (const req of reqs) {
@@ -621,10 +675,16 @@ export function summariseRequirements(
621
675
  // Both sides of the ratio come from the SAME scan the gate read, so the printed
622
676
  // summary cannot disagree with the diagnostics printed beneath it — previously
623
677
  // the same helper, now literally the same result.
624
- const claimed = scan.claimedObjects;
625
- for (const ent of coverableEntities(root, scan.coverable)) {
626
- summary.entitiesTotal++;
627
- if (claimed.has(ent.resolutionKey())) summary.entitiesClaimed++;
678
+ if (scan.measureCoverage) {
679
+ const claimed = scan.claimedObjects;
680
+ let total = 0;
681
+ let claimedCount = 0;
682
+ for (const ent of coverableEntities(root, scan.coverable)) {
683
+ total++;
684
+ if (claimed.has(ent.resolutionKey())) claimedCount++;
685
+ }
686
+ summary.entitiesTotal = total;
687
+ summary.entitiesClaimed = claimedCount;
628
688
  }
629
689
 
630
690
  return summary;