@metaobjectsdev/cli 1.0.2 → 1.0.3
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.
- package/README.md +1 -1
- package/dist/src/commands/deps.d.ts +3 -0
- package/dist/src/commands/deps.d.ts.map +1 -0
- package/dist/src/commands/deps.js +200 -0
- package/dist/src/commands/deps.js.map +1 -0
- package/dist/src/commands/docs.d.ts.map +1 -1
- package/dist/src/commands/docs.js +19 -3
- package/dist/src/commands/docs.js.map +1 -1
- package/dist/src/commands/gen.d.ts.map +1 -1
- package/dist/src/commands/gen.js +54 -1
- package/dist/src/commands/gen.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +12 -18
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/migrate.d.ts.map +1 -1
- package/dist/src/commands/migrate.js +24 -19
- package/dist/src/commands/migrate.js.map +1 -1
- package/dist/src/commands/prompt-snapshot.d.ts.map +1 -1
- package/dist/src/commands/prompt-snapshot.js +2 -1
- package/dist/src/commands/prompt-snapshot.js.map +1 -1
- package/dist/src/commands/verify.d.ts.map +1 -1
- package/dist/src/commands/verify.js +154 -23
- package/dist/src/commands/verify.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +62 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/agent-context-staleness.d.ts.map +1 -1
- package/dist/src/lib/agent-context-staleness.js +7 -1
- package/dist/src/lib/agent-context-staleness.js.map +1 -1
- package/dist/src/lib/args.d.ts +57 -1
- package/dist/src/lib/args.d.ts.map +1 -1
- package/dist/src/lib/args.js +39 -2
- package/dist/src/lib/args.js.map +1 -1
- package/dist/src/lib/collection-load-options.d.ts +13 -0
- package/dist/src/lib/collection-load-options.d.ts.map +1 -0
- package/dist/src/lib/collection-load-options.js +19 -0
- package/dist/src/lib/collection-load-options.js.map +1 -0
- package/dist/src/lib/dependency-sync.d.ts +170 -0
- package/dist/src/lib/dependency-sync.d.ts.map +1 -0
- package/dist/src/lib/dependency-sync.js +457 -0
- package/dist/src/lib/dependency-sync.js.map +1 -0
- package/dist/src/lib/migrate-scope.d.ts +32 -1
- package/dist/src/lib/migrate-scope.d.ts.map +1 -1
- package/dist/src/lib/migrate-scope.js +51 -3
- package/dist/src/lib/migrate-scope.js.map +1 -1
- package/dist/src/lib/overlay-lint.d.ts +37 -0
- package/dist/src/lib/overlay-lint.d.ts.map +1 -0
- package/dist/src/lib/overlay-lint.js +102 -0
- package/dist/src/lib/overlay-lint.js.map +1 -0
- package/dist/src/lib/requirement-check.d.ts +9 -1
- package/dist/src/lib/requirement-check.d.ts.map +1 -1
- package/dist/src/lib/requirement-check.js +23 -7
- package/dist/src/lib/requirement-check.js.map +1 -1
- package/package.json +10 -10
- package/src/commands/deps.ts +231 -0
- package/src/commands/docs.ts +19 -3
- package/src/commands/gen.ts +61 -1
- package/src/commands/init.ts +13 -17
- package/src/commands/migrate.ts +26 -16
- package/src/commands/prompt-snapshot.ts +2 -1
- package/src/commands/verify.ts +174 -23
- package/src/index.ts +62 -2
- package/src/lib/agent-context-staleness.ts +7 -1
- package/src/lib/args.ts +89 -3
- package/src/lib/collection-load-options.ts +36 -0
- package/src/lib/dependency-sync.ts +656 -0
- package/src/lib/migrate-scope.ts +59 -4
- package/src/lib/overlay-lint.ts +129 -0
- package/src/lib/requirement-check.ts +33 -7
package/src/commands/verify.ts
CHANGED
|
@@ -24,12 +24,15 @@ import { replayRemedy } from "../lib/replay-remedy.js";
|
|
|
24
24
|
import { FileProvider } from "../lib/file-provider.js";
|
|
25
25
|
import { derivePayloadFieldTree } from "../lib/payload-field-tree.js";
|
|
26
26
|
import { loadMemoryOptionsFrom, loadMetaobjectsConfig, resolveGenCollection, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
|
|
27
|
+
import { collectionLoadOptions } from "../lib/collection-load-options.js";
|
|
27
28
|
import { computeCodegenDrift } from "../lib/codegen-drift.js";
|
|
28
29
|
import { computeDocsDrift } from "../lib/docs-drift.js";
|
|
29
30
|
import {
|
|
30
31
|
checkRequirements, summariseRequirements, scanRequirements, type Diagnostic,
|
|
31
32
|
} from "../lib/requirement-check.js";
|
|
32
33
|
import { lintRequirements } from "../lib/requirement-lint.js";
|
|
34
|
+
import { lintOverlays } from "../lib/overlay-lint.js";
|
|
35
|
+
import { FileSource } from "@metaobjectsdev/metadata/core";
|
|
33
36
|
import { resolveD1Config, resolveMigrateConfig } from "../lib/config.js";
|
|
34
37
|
import {
|
|
35
38
|
buildWranglerExecuteArgs,
|
|
@@ -69,8 +72,15 @@ import {
|
|
|
69
72
|
type D1Runner,
|
|
70
73
|
type DriftResult,
|
|
71
74
|
} from "@metaobjectsdev/migrate-ts";
|
|
72
|
-
import {
|
|
73
|
-
|
|
75
|
+
import {
|
|
76
|
+
DEFAULT_METAOBJECTS_DIR,
|
|
77
|
+
loadConfig,
|
|
78
|
+
loadMemory,
|
|
79
|
+
resolveCollection,
|
|
80
|
+
type DependencySpec,
|
|
81
|
+
} from "@metaobjectsdev/sdk";
|
|
82
|
+
import { checkDependencies, ERR_DEPENDENCY_UPSTREAM_DRIFT, formatCheckLine, readLockOrThrow } from "../lib/dependency-sync.js";
|
|
83
|
+
import { exclusionNotes, importedOption, migrateScopeMismatch } from "../lib/migrate-scope.js";
|
|
74
84
|
import {
|
|
75
85
|
TYPE_TEMPLATE,
|
|
76
86
|
TEMPLATE_SUBTYPE_PROMPT,
|
|
@@ -163,11 +173,15 @@ export async function verifyCommand(
|
|
|
163
173
|
// D1 has no URL connection); that check lives inside runSchemaVerify.
|
|
164
174
|
const runCodegen = flags.codegen;
|
|
165
175
|
const runDocs = flags.docs;
|
|
176
|
+
// Task 15 — same shape as --codegen/--docs: selected ONLY by its own flag,
|
|
177
|
+
// never folded into the bare-verify default (it needs the publisher
|
|
178
|
+
// reachable, which CI may not have — see the VerifyFlags doc on `deps`).
|
|
179
|
+
const runDeps = flags.deps;
|
|
166
180
|
if (!flags.anyExplicit) {
|
|
167
181
|
say(
|
|
168
182
|
"meta verify — running --templates (default). Explicit subverbs: " +
|
|
169
183
|
"--templates (prompt drift), --db/--dialect d1 (schema drift), --codegen (codegen drift), " +
|
|
170
|
-
"--docs (docs drift), " +
|
|
184
|
+
"--docs (docs drift), --deps (dependency drift), " +
|
|
171
185
|
"--replay/--replay-snapshot (the committed migration chain replays from empty).",
|
|
172
186
|
);
|
|
173
187
|
}
|
|
@@ -248,7 +262,7 @@ export async function verifyCommand(
|
|
|
248
262
|
let root: Awaited<ReturnType<typeof loadMemory>>;
|
|
249
263
|
try {
|
|
250
264
|
root = await loadMemory(collection.configDir, {
|
|
251
|
-
|
|
265
|
+
...collectionLoadOptions(collection),
|
|
252
266
|
...configLoadOptions,
|
|
253
267
|
strict: !flags.lax,
|
|
254
268
|
});
|
|
@@ -314,7 +328,7 @@ export async function verifyCommand(
|
|
|
314
328
|
const promptsDir = join(projectRoot, flags.prompts ?? DEFAULT_PROMPTS_DIR);
|
|
315
329
|
const provider = new FileProvider(promptsDir);
|
|
316
330
|
|
|
317
|
-
// The
|
|
331
|
+
// The advisory sections, captured as the gates run so the structured payload
|
|
318
332
|
// can carry them IN FULL. They were previously formatted straight to stderr and
|
|
319
333
|
// existed nowhere else, which is why 96% of a 239-finding report was unreachable
|
|
320
334
|
// by any flag, env var or format.
|
|
@@ -322,6 +336,12 @@ export async function verifyCommand(
|
|
|
322
336
|
skippedSection("the requirement pass did not run");
|
|
323
337
|
let antiPatternSection: AdvisorySection<AdvisoryFindingRow> =
|
|
324
338
|
skippedSection("the advisory anti-pattern pass did not run");
|
|
339
|
+
// FR-023 §11.1 item 4 (Task 17) — the overlay authoring lint. Its own section
|
|
340
|
+
// (never folded into `requirements`, which is about a different kind of node
|
|
341
|
+
// entirely) so the structured payload names what ran and what didn't the same
|
|
342
|
+
// way every other advisory pass does.
|
|
343
|
+
let overlaySection: AdvisorySection<AdvisoryDiagnosticRow> =
|
|
344
|
+
skippedSection("the overlay lint did not run");
|
|
325
345
|
// The ledger counts `meta verify` prints on every run. Undefined for a project
|
|
326
346
|
// declaring no requirement.* node at all (opt-in by declaration) — the payload
|
|
327
347
|
// then omits the block rather than reporting zeroes that would read as an empty
|
|
@@ -341,6 +361,7 @@ export async function verifyCommand(
|
|
|
341
361
|
const schemaExit = await runSchemaVerify();
|
|
342
362
|
const codegenExit = runCodegen ? await runCodegenVerify() : 0;
|
|
343
363
|
const docsExit = runDocs ? await runDocsVerify() : 0;
|
|
364
|
+
const depsExit = runDeps ? await runDepsVerify() : 0;
|
|
344
365
|
// Requirements have no subverb: `requirement.*` nodes are metadata, so they
|
|
345
366
|
// are checked on every `meta verify`. Opt-in by DECLARATION — a model with no
|
|
346
367
|
// requirement nodes is silent, not in drift.
|
|
@@ -351,6 +372,12 @@ export async function verifyCommand(
|
|
|
351
372
|
// would parse cleanly and do nothing at all.
|
|
352
373
|
const replayExit = flags.replay || flags.replaySnapshot ? await runReplayVerify() : 0;
|
|
353
374
|
|
|
375
|
+
// FR-023 §11.1 item 4 (Task 17) — the overlay authoring lint. Runs on every
|
|
376
|
+
// `meta verify`, not gated on any subverb (an unflagged cross-file
|
|
377
|
+
// redeclaration is a risk regardless of which drift gates were selected).
|
|
378
|
+
// Warnings ONLY — never changes the exit code.
|
|
379
|
+
await runOverlayLintAdvisory();
|
|
380
|
+
|
|
354
381
|
// Advisory verify-as-teacher pass: surface hand-rolled work the metadata could
|
|
355
382
|
// model. Warnings ONLY — never changes the exit code (bias to under-flagging).
|
|
356
383
|
// Suppressed with --no-antipatterns or META_NO_ANTIPATTERNS=1 for the rare
|
|
@@ -362,6 +389,7 @@ export async function verifyCommand(
|
|
|
362
389
|
schemaExit,
|
|
363
390
|
codegenExit,
|
|
364
391
|
docsExit,
|
|
392
|
+
depsExit,
|
|
365
393
|
requirementExit,
|
|
366
394
|
replayExit,
|
|
367
395
|
);
|
|
@@ -378,6 +406,7 @@ export async function verifyCommand(
|
|
|
378
406
|
{ gate: "schema", ran: ranSchemaGate, ok: schemaExit === 0 },
|
|
379
407
|
{ gate: "codegen", ran: runCodegen, ok: codegenExit === 0 },
|
|
380
408
|
{ gate: "docs", ran: runDocs, ok: docsExit === 0 },
|
|
409
|
+
{ gate: "deps", ran: runDeps, ok: depsExit === 0 },
|
|
381
410
|
{ gate: "requirements", ran: true, ok: requirementExit === 0 },
|
|
382
411
|
{ gate: "replay", ran: flags.replay || flags.replaySnapshot, ok: replayExit === 0 },
|
|
383
412
|
],
|
|
@@ -385,6 +414,7 @@ export async function verifyCommand(
|
|
|
385
414
|
requirements: requirementSection,
|
|
386
415
|
requirementCounts,
|
|
387
416
|
antiPatterns: antiPatternSection,
|
|
417
|
+
overlays: overlaySection,
|
|
388
418
|
}),
|
|
389
419
|
fmt,
|
|
390
420
|
);
|
|
@@ -553,7 +583,7 @@ export async function verifyCommand(
|
|
|
553
583
|
columnNamingStrategy: viewStrategy,
|
|
554
584
|
views: buildProjectionViews(root, { dialect, columnNamingStrategy: viewStrategy }),
|
|
555
585
|
});
|
|
556
|
-
governed = scopeExpectedSchema(built, schemaScope);
|
|
586
|
+
governed = scopeExpectedSchema(built, schemaScope, importedOption(collection));
|
|
557
587
|
}
|
|
558
588
|
|
|
559
589
|
// `verifyReplay` calls `applyPending` itself. That is NOT a second replay: the
|
|
@@ -594,7 +624,7 @@ export async function verifyCommand(
|
|
|
594
624
|
// ONE scan for all three passes. The gate and the summary each used to walk the
|
|
595
625
|
// model AND resolve every @implementedBy claim for themselves — the resolution
|
|
596
626
|
// being the expensive half — and the lint added a third walk on top.
|
|
597
|
-
const scan = scanRequirements(root);
|
|
627
|
+
const scan = scanRequirements(root, { coverable: collection.inScope });
|
|
598
628
|
const diags = [...checkRequirements(root, scan)];
|
|
599
629
|
|
|
600
630
|
// Printed on EVERY run, clean or not — a gate that says nothing when it
|
|
@@ -617,7 +647,14 @@ export async function verifyCommand(
|
|
|
617
647
|
`meta verify — requirements: ${s.total} entries (${s.functional} functional, ` +
|
|
618
648
|
`${s.architectural} architectural) — ${parts.join(", ")}; ` +
|
|
619
649
|
`${s.entitiesClaimed}/${s.entitiesTotal} entities claimed, ` +
|
|
620
|
-
`counted over ${collection.files.length} metadata file(s)
|
|
650
|
+
`counted over ${collection.files.length} metadata file(s)` +
|
|
651
|
+
// FR-023 — only when there ARE dependencies. A project that declares none
|
|
652
|
+
// must print the sentence it printed before dependencies existed, to the
|
|
653
|
+
// byte: "0 from dependencies" is noise on every existing project, and this
|
|
654
|
+
// line is a SURFACE the no-dependency guarantee covers like any other.
|
|
655
|
+
(collection.dependencies.length > 0
|
|
656
|
+
? `, ${collection.dependencies.length} from dependencies.`
|
|
657
|
+
: `.`),
|
|
621
658
|
);
|
|
622
659
|
if (s.undecided > 0) {
|
|
623
660
|
say(
|
|
@@ -645,18 +682,13 @@ export async function verifyCommand(
|
|
|
645
682
|
|
|
646
683
|
const errors = diags.filter((d) => d.severity === "error");
|
|
647
684
|
const warns = diags.filter((d) => d.severity === "warn");
|
|
648
|
-
// Named `fmtDiag`, not `fmt`: `fmt` is this command's OUTPUT FORMAT parameter,
|
|
649
|
-
// and a shadow of it inside the one function that must not confuse the two is
|
|
650
|
-
// how a structured run quietly reverts to text.
|
|
651
|
-
const fmtDiag = (d: Diagnostic): string =>
|
|
652
|
-
` ${d.code}${d.path !== undefined ? ` [${d.path}]` : ""}: ${d.message}`;
|
|
653
685
|
// Capped per SECTION, never across them: a ledger of a few hundred entries can
|
|
654
686
|
// produce hundreds of prose findings, and a shared budget would let the advisory
|
|
655
687
|
// lint push every gate warning off the end. The cap VALUE is now one shared
|
|
656
688
|
// constant (`--limit`), so raising it cannot miss a section — errors stay
|
|
657
689
|
// uncapped, as they always were.
|
|
658
|
-
for (const d of errors) log.error(
|
|
659
|
-
warnCapped(warns.map(
|
|
690
|
+
for (const d of errors) log.error(formatDiagnostic(d));
|
|
691
|
+
warnCapped(warns.map(formatDiagnostic), flags.limit, { structured });
|
|
660
692
|
|
|
661
693
|
// -- the authoring lint: its own section, its own cap ----------------------
|
|
662
694
|
// Separate from the gate above because it makes a different claim. The gate
|
|
@@ -685,7 +717,7 @@ export async function verifyCommand(
|
|
|
685
717
|
`meta verify — requirements: ${lint.length} authoring warning(s) ` +
|
|
686
718
|
`(advisory — does not fail the build):`,
|
|
687
719
|
);
|
|
688
|
-
warnCapped(lint.map(
|
|
720
|
+
warnCapped(lint.map(formatDiagnostic), flags.limit, { structured });
|
|
689
721
|
}
|
|
690
722
|
|
|
691
723
|
// Everything this pass found, uncapped, for the structured payload — gate
|
|
@@ -703,6 +735,44 @@ export async function verifyCommand(
|
|
|
703
735
|
return 0;
|
|
704
736
|
}
|
|
705
737
|
|
|
738
|
+
// -- overlay authoring lint (FR-023 §11.1 item 4, Task 17) ------------------
|
|
739
|
+
// Its own section, its own cap — same discipline as the requirement lint
|
|
740
|
+
// above: a noisy section must not push another section's findings off the
|
|
741
|
+
// end of a capped run. This lint has NO gate half at all (no
|
|
742
|
+
// "ledger disagrees with the model" claim to make), so nothing here can
|
|
743
|
+
// ever reach the exit code — records its result either way, same as the
|
|
744
|
+
// anti-pattern pass below.
|
|
745
|
+
//
|
|
746
|
+
// Reads raw file content (via `lintOverlays` + `declaredTopLevelKeys`), never
|
|
747
|
+
// the loaded/merged model: the merge has already lost which FILE contributed
|
|
748
|
+
// which declaration, and that is exactly what this lint needs to name.
|
|
749
|
+
async function runOverlayLintAdvisory(): Promise<void> {
|
|
750
|
+
if (flags.noOverlayLint) {
|
|
751
|
+
overlaySection = skippedSection("suppressed by --no-overlay-lint");
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (process.env.META_NO_OVERLAY_LINT === "1") {
|
|
755
|
+
overlaySection = skippedSection("suppressed by META_NO_OVERLAY_LINT=1");
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
let findings: Diagnostic[];
|
|
759
|
+
try {
|
|
760
|
+
findings = await lintOverlays(collection, (path) => new FileSource(path));
|
|
761
|
+
} catch (err) {
|
|
762
|
+
// Never let an advisory scan break verify — and never report it as clean.
|
|
763
|
+
overlaySection = skippedSection(`the overlay lint failed: ${(err as Error).message}`);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
overlaySection = ranSection(findings.map((d) => toDiagnosticRow(d, "lint")));
|
|
767
|
+
if (findings.length > 0) {
|
|
768
|
+
log.warn(
|
|
769
|
+
`meta verify — overlays: ${findings.length} unflagged cross-file redeclaration(s) ` +
|
|
770
|
+
`(advisory — does not fail the build):`,
|
|
771
|
+
);
|
|
772
|
+
warnCapped(findings.map(formatDiagnostic), flags.limit, { structured });
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
706
776
|
// -- verify-as-teacher (advisory) ------------------------------------------
|
|
707
777
|
// Records its result EITHER WAY — a skip carries its reason rather than looking
|
|
708
778
|
// like a clean scan. Warnings only; nothing here reaches the exit code (the
|
|
@@ -994,6 +1064,9 @@ export async function verifyCommand(
|
|
|
994
1064
|
allow,
|
|
995
1065
|
views: expectedViews,
|
|
996
1066
|
...(schemaScope !== undefined ? { inScope: schemaScope } : {}),
|
|
1067
|
+
// The import exclusion reaches scopeExpectedSchema through this option bag —
|
|
1068
|
+
// there is no direct call on this path to attach it to.
|
|
1069
|
+
...importedOption(collection),
|
|
997
1070
|
});
|
|
998
1071
|
} catch (err) {
|
|
999
1072
|
log.error(`verify: failed to introspect ${kysely.displayUrl}: ${(err as Error).message}`);
|
|
@@ -1080,6 +1153,8 @@ export async function verifyCommand(
|
|
|
1080
1153
|
allow,
|
|
1081
1154
|
views: expectedViews,
|
|
1082
1155
|
...(schemaScope !== undefined ? { inScope: schemaScope } : {}),
|
|
1156
|
+
// Same option bag on the D1 path, for the same reason.
|
|
1157
|
+
...importedOption(collection),
|
|
1083
1158
|
});
|
|
1084
1159
|
} catch (err) {
|
|
1085
1160
|
log.error(`verify: ${(err as Error).message}`);
|
|
@@ -1192,11 +1267,12 @@ export async function verifyCommand(
|
|
|
1192
1267
|
);
|
|
1193
1268
|
}
|
|
1194
1269
|
|
|
1195
|
-
// Same reasoning for the per-command scope: an object
|
|
1196
|
-
// was NOT checked, and silence would misreport it as
|
|
1197
|
-
// wording with `meta migrate` — one declaration, one
|
|
1198
|
-
|
|
1199
|
-
|
|
1270
|
+
// Same reasoning for the per-command scope and for imported metadata: an object
|
|
1271
|
+
// excluded either way was NOT checked, and silence would misreport it as
|
|
1272
|
+
// checked-and-clean. Shared wording with `meta migrate` — one declaration, one
|
|
1273
|
+
// sentence about it — and each object named once (`exclusionNotes`).
|
|
1274
|
+
for (const note of exclusionNotes("verify", driftResult.outOfScope, driftResult.importedOutOfScope ?? [])) {
|
|
1275
|
+
say(note);
|
|
1200
1276
|
}
|
|
1201
1277
|
|
|
1202
1278
|
const changes = driftResult.changes;
|
|
@@ -1249,7 +1325,7 @@ export async function verifyCommand(
|
|
|
1249
1325
|
if (genCollection !== collection) {
|
|
1250
1326
|
try {
|
|
1251
1327
|
codegenRoot = await loadMemory(genCollection.configDir, {
|
|
1252
|
-
|
|
1328
|
+
...collectionLoadOptions(genCollection),
|
|
1253
1329
|
...configLoadOptions,
|
|
1254
1330
|
strict: !flags.lax,
|
|
1255
1331
|
});
|
|
@@ -1390,6 +1466,55 @@ export async function verifyCommand(
|
|
|
1390
1466
|
}
|
|
1391
1467
|
return 1;
|
|
1392
1468
|
}
|
|
1469
|
+
|
|
1470
|
+
// -- dependency drift (Task 15, FR-023) --------------------------------------
|
|
1471
|
+
// Gated on --deps. Re-resolves each declared dependency exactly as `meta deps
|
|
1472
|
+
// check` does — never part of the bare-verify default (it needs the publisher
|
|
1473
|
+
// reachable, which CI may not have).
|
|
1474
|
+
async function runDepsVerify(): Promise<number> {
|
|
1475
|
+
// Read RAW declared specs (never `collection.dependencies`, which is the
|
|
1476
|
+
// already-`ResolvedDependency[]` the LOCK produced — it carries no
|
|
1477
|
+
// transport info, so it cannot be re-resolved). A project with no
|
|
1478
|
+
// config.json at all declares no dependencies.
|
|
1479
|
+
let depSpecs: readonly DependencySpec[] = [];
|
|
1480
|
+
try {
|
|
1481
|
+
const cfg = await loadConfig(join(collection.configDir, DEFAULT_METAOBJECTS_DIR));
|
|
1482
|
+
depSpecs = cfg.dependencies;
|
|
1483
|
+
} catch {
|
|
1484
|
+
depSpecs = [];
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
if (depSpecs.length === 0) {
|
|
1488
|
+
say("verify --deps: no dependencies declared — nothing to check.");
|
|
1489
|
+
return 0;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
let lock: Awaited<ReturnType<typeof readLockOrThrow>>;
|
|
1493
|
+
try {
|
|
1494
|
+
lock = await readLockOrThrow(collection.configDir);
|
|
1495
|
+
} catch (err) {
|
|
1496
|
+
log.error(`verify --deps: ${(err as Error).message}`);
|
|
1497
|
+
return 2;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
const results = await checkDependencies(collection.configDir, depSpecs, lock);
|
|
1501
|
+
const failing = results.filter((r) => r.status !== "current");
|
|
1502
|
+
|
|
1503
|
+
for (const r of results) {
|
|
1504
|
+
if (r.status === "current") say(formatCheckLine(r));
|
|
1505
|
+
else log.error(formatCheckLine(r));
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
if (failing.length > 0) {
|
|
1509
|
+
log.error(
|
|
1510
|
+
`verify --deps: ${failing.length} of ${results.length} dependenc` +
|
|
1511
|
+
`${results.length === 1 ? "y" : "ies"} drifted or unresolved (${ERR_DEPENDENCY_UPSTREAM_DRIFT}).`,
|
|
1512
|
+
);
|
|
1513
|
+
return 1;
|
|
1514
|
+
}
|
|
1515
|
+
say("verify --deps: every dependency's installed artifact matches the lock.");
|
|
1516
|
+
return 0;
|
|
1517
|
+
}
|
|
1393
1518
|
}
|
|
1394
1519
|
|
|
1395
1520
|
/**
|
|
@@ -1460,6 +1585,17 @@ interface VerifyGateRow {
|
|
|
1460
1585
|
ok: boolean;
|
|
1461
1586
|
}
|
|
1462
1587
|
|
|
1588
|
+
/**
|
|
1589
|
+
* Format one `Diagnostic` as a printed TEXT line. Named `formatDiagnostic`, not
|
|
1590
|
+
* `fmt`: `fmt` is this command's OUTPUT FORMAT parameter, and a shadow of it
|
|
1591
|
+
* anywhere near this code is how a structured run quietly reverts to text.
|
|
1592
|
+
* Shared by the requirement gate/lint AND the overlay lint — every advisory or
|
|
1593
|
+
* gate pass that speaks in `Diagnostic` prints it identically.
|
|
1594
|
+
*/
|
|
1595
|
+
function formatDiagnostic(d: Diagnostic): string {
|
|
1596
|
+
return ` ${d.code}${d.path !== undefined ? ` [${d.path}]` : ""}: ${d.message}`;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1463
1599
|
/** Project a requirement diagnostic into a payload row. */
|
|
1464
1600
|
function toDiagnosticRow(d: Diagnostic, source: "gate" | "lint"): AdvisoryDiagnosticRow {
|
|
1465
1601
|
return {
|
|
@@ -1491,6 +1627,7 @@ function buildVerifyPayload(input: {
|
|
|
1491
1627
|
requirements: AdvisorySection<AdvisoryDiagnosticRow>;
|
|
1492
1628
|
requirementCounts: RequirementCounts | undefined;
|
|
1493
1629
|
antiPatterns: AdvisorySection<AdvisoryFindingRow>;
|
|
1630
|
+
overlays: AdvisorySection<AdvisoryDiagnosticRow>;
|
|
1494
1631
|
}): Record<string, unknown> {
|
|
1495
1632
|
const ran = input.gates.filter((g) => g.ran);
|
|
1496
1633
|
const failed = ran.filter((g) => !g.ok);
|
|
@@ -1505,6 +1642,9 @@ function buildVerifyPayload(input: {
|
|
|
1505
1642
|
if (input.requirements.total > 0) {
|
|
1506
1643
|
parts.push(`${input.requirements.total} requirement diagnostic(s)`);
|
|
1507
1644
|
}
|
|
1645
|
+
if (input.overlays.status === "ran" && input.overlays.total > 0) {
|
|
1646
|
+
parts.push(`${input.overlays.total} overlay authoring finding(s)`);
|
|
1647
|
+
}
|
|
1508
1648
|
|
|
1509
1649
|
const help: string[] = [];
|
|
1510
1650
|
if (failed.length > 0) {
|
|
@@ -1517,7 +1657,17 @@ function buildVerifyPayload(input: {
|
|
|
1517
1657
|
`${input.antiPatterns.total} authored site(s) hand-roll what MetaObjects can model — see antiPatterns.rows[] and run \`meta types <construct>\``,
|
|
1518
1658
|
);
|
|
1519
1659
|
}
|
|
1520
|
-
if (
|
|
1660
|
+
if (input.overlays.total > 0) {
|
|
1661
|
+
help.push(
|
|
1662
|
+
`${input.overlays.total} unflagged cross-file redeclaration(s) — see overlays.rows[]; add overlay: true so a renamed or removed target fails loudly instead of silently becoming a new object`,
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
if (
|
|
1666
|
+
failed.length === 0 &&
|
|
1667
|
+
input.antiPatterns.total === 0 &&
|
|
1668
|
+
input.requirements.total === 0 &&
|
|
1669
|
+
input.overlays.total === 0
|
|
1670
|
+
) {
|
|
1521
1671
|
help.push("no drift and nothing advisory to answer — nothing to do");
|
|
1522
1672
|
}
|
|
1523
1673
|
|
|
@@ -1528,6 +1678,7 @@ function buildVerifyPayload(input: {
|
|
|
1528
1678
|
help,
|
|
1529
1679
|
antiPatterns: input.antiPatterns,
|
|
1530
1680
|
requirements: input.requirements,
|
|
1681
|
+
overlays: input.overlays,
|
|
1531
1682
|
...(input.requirementCounts !== undefined ? { requirementCounts: input.requirementCounts } : {}),
|
|
1532
1683
|
// The honest boundary. Everything named here is REACHABLE — it is printed as
|
|
1533
1684
|
// text on stderr — but it is not in this document, and a reader must not have
|
package/src/index.ts
CHANGED
|
@@ -17,7 +17,7 @@ const VERSION = cliVersion();
|
|
|
17
17
|
* human text. It is named ONCE and used by both the warning below and the help
|
|
18
18
|
* text above, so the two cannot drift apart.
|
|
19
19
|
*/
|
|
20
|
-
const FORMAT_AWARE_COMMANDS: readonly string[] = ["gen", "verify", "migrate", "types"];
|
|
20
|
+
const FORMAT_AWARE_COMMANDS: readonly string[] = ["gen", "verify", "migrate", "types", "deps"];
|
|
21
21
|
|
|
22
22
|
const HELP_TEXT = `meta — MetaObjects CLI (v${VERSION})
|
|
23
23
|
|
|
@@ -32,10 +32,13 @@ COMMANDS:
|
|
|
32
32
|
gen [<entity>...] Codegen TS targets from your declared metadata
|
|
33
33
|
eject <generator> Copy a reference generator into codegen/generators/ to own it (any time after init)
|
|
34
34
|
eject --list List every ejectable generator name, grouped by package
|
|
35
|
+
deps sync [<name>...] Resolve declared dependencies (path transport): sync the committed
|
|
36
|
+
snapshot + sha256 lock (.metaobjects/deps/, .metaobjects/deps.lock.json)
|
|
37
|
+
deps list One line per locked dependency: name, version, hash, node/package summary
|
|
35
38
|
types [query] Search the metadata vocabulary (types, subtypes, @attrs) by name or description
|
|
36
39
|
export Flatten loaded metadata to one canonical JSON artifact
|
|
37
40
|
docs [<project-root>] --out <dir> Generate neutral metadata documentation (entity + template pages; --site for HTML site)
|
|
38
|
-
verify Drift gate — subverbs: --templates / --db / --codegen / --docs (bare = --templates)
|
|
41
|
+
verify Drift gate — subverbs: --templates / --db / --codegen / --docs / --deps (bare = --templates)
|
|
39
42
|
upgrade Rewrite retired metadata vocabulary (previews; --apply writes)
|
|
40
43
|
prompt-snapshot Snapshot rendered template.* output; --check gates drift
|
|
41
44
|
migrate Diff metadata vs live DB; emit migration SQL files
|
|
@@ -94,6 +97,10 @@ VERIFY FLAGS (ADR-0021 D2 — explicit subverbs; combine any; exit 1 on ANY drif
|
|
|
94
97
|
run would emit; never reports an extra file, because docs.outDir
|
|
95
98
|
holds hand-written documentation MetaObjects did not write.
|
|
96
99
|
Needs metaobjects.config.ts; exit 2 if absent.
|
|
100
|
+
--deps Dependency drift — re-resolve each declared dependency and compare
|
|
101
|
+
its installed artifact's hash against .metaobjects/deps.lock.json
|
|
102
|
+
(the same check 'meta deps check' runs). Never part of the bare-verify
|
|
103
|
+
default — it needs the publisher reachable, which CI may not have.
|
|
97
104
|
--db <url> Schema drift — live DB URL enables the schema-drift gate.
|
|
98
105
|
Supports: file:, libsql:, postgres:, postgresql:. Omit to skip.
|
|
99
106
|
D1 has no URL — use --dialect d1 / --d1 <binding> instead.
|
|
@@ -108,6 +115,8 @@ VERIFY FLAGS (ADR-0021 D2 — explicit subverbs; combine any; exit 1 on ANY drif
|
|
|
108
115
|
--no-antipatterns Suppress the advisory "you hand-rolled what MetaObjects can
|
|
109
116
|
model" pass (aggregate/currency/enum hints; warnings only)
|
|
110
117
|
--no-requirement-lint Suppress the advisory requirement AUTHORING lint (not the gate)
|
|
118
|
+
--no-overlay-lint Suppress the advisory overlay-redeclaration AUTHORING lint
|
|
119
|
+
(never a gate — this lint can't fail the build)
|
|
111
120
|
--limit <n|all> How many advisory lines TEXT output prints per section before
|
|
112
121
|
it truncates (default 20). Never applies to --format
|
|
113
122
|
toon/json, which carry every finding and every diagnostic.
|
|
@@ -191,6 +200,38 @@ the same operation for ANY generator — one you skipped at init time, a UI-tier
|
|
|
191
200
|
generator like form/hooks/grid, or one a package gains later. It prints the
|
|
192
201
|
import line to paste into metaobjects.config.ts, and it never overwrites a
|
|
193
202
|
file you already own unless you pass --force.
|
|
203
|
+
`,
|
|
204
|
+
deps: `meta deps — sync a declared metadata dependency's committed snapshot
|
|
205
|
+
|
|
206
|
+
USAGE:
|
|
207
|
+
meta deps sync [<name>...] Resolve each declared dependency (path transport only in
|
|
208
|
+
this release — npm/python refuse by name), validate its
|
|
209
|
+
manifest + artifact, copy it into .metaobjects/deps/<name>/,
|
|
210
|
+
and pin it in .metaobjects/deps.lock.json. Naming one or more
|
|
211
|
+
<name>s syncs only those; bare 'sync' syncs everything
|
|
212
|
+
declared. A dependency removed from config is PRUNED from
|
|
213
|
+
the lock and its snapshot dir deleted, every run.
|
|
214
|
+
meta deps list One line per locked dependency: name, version, hash8,
|
|
215
|
+
node count, packages.
|
|
216
|
+
meta deps check Re-resolve each declared dependency (path transport, same
|
|
217
|
+
as sync steps 1-2) and compare its INSTALLED artifact's hash
|
|
218
|
+
against .metaobjects/deps.lock.json. Read-only — never
|
|
219
|
+
touches the lock or the snapshot. Exits 1 if any dependency
|
|
220
|
+
has drifted or cannot be resolved (ERR_DEPENDENCY_UPSTREAM_DRIFT).
|
|
221
|
+
|
|
222
|
+
FLAGS:
|
|
223
|
+
--dry-run Plan and report; write nothing (sync only)
|
|
224
|
+
--format <toon|json|text> Output format (global flag; default toon off-TTY)
|
|
225
|
+
--help, -h Print this help
|
|
226
|
+
|
|
227
|
+
Only the "path" transport resolves in this release — a "npm"/"python" dependency spec is
|
|
228
|
+
valid config (reserved for a future toolchain) but 'meta deps sync' refuses it by name:
|
|
229
|
+
"transport \`npm\` is not supported by this toolchain yet; use \`path\`".
|
|
230
|
+
|
|
231
|
+
The lock is the ONLY thing 'meta deps sync' writes to besides the snapshot directory —
|
|
232
|
+
sync never touches your own metadata files. 'meta deps check' compares the lock against
|
|
233
|
+
what is installed right now (the same check 'meta verify --deps' runs); 'meta deps sync'
|
|
234
|
+
only ever compares against what config DECLARES.
|
|
194
235
|
`,
|
|
195
236
|
verify: `meta verify — drift gate (templates / DB schema / codegen / migration replay)
|
|
196
237
|
|
|
@@ -210,6 +251,12 @@ FLAGS:
|
|
|
210
251
|
a namespace MetaObjects owns); the count of exempt pages is
|
|
211
252
|
reported either way, and a run where every page is ignored is
|
|
212
253
|
refused rather than reported clean.
|
|
254
|
+
--deps Dependency drift — re-resolve each declared dependency exactly as
|
|
255
|
+
'meta deps check' does and compare its installed artifact's hash
|
|
256
|
+
against .metaobjects/deps.lock.json. Exits 1 if any dependency has
|
|
257
|
+
drifted or cannot be resolved (ERR_DEPENDENCY_UPSTREAM_DRIFT). NEVER
|
|
258
|
+
part of the bare-verify default — it needs the publisher reachable,
|
|
259
|
+
which CI may not have.
|
|
213
260
|
--db <url> Schema drift — live DB URL enables the schema-drift gate.
|
|
214
261
|
Supports: file:, libsql:, postgres:, postgresql:
|
|
215
262
|
D1 has no URL — use --dialect d1 / --d1 <binding> instead.
|
|
@@ -237,6 +284,8 @@ FLAGS:
|
|
|
237
284
|
migrating a model onto a registered provider.
|
|
238
285
|
--no-antipatterns Suppress the advisory "hand-rolled what MetaObjects can model" pass
|
|
239
286
|
--no-requirement-lint Suppress the advisory requirement AUTHORING lint (not the gate)
|
|
287
|
+
--no-overlay-lint Suppress the advisory overlay-redeclaration AUTHORING lint —
|
|
288
|
+
never a gate; this lint can't fail the build
|
|
240
289
|
--limit <n|all> Advisory lines TEXT output prints PER SECTION before truncating
|
|
241
290
|
(default 20; per-section so the authoring lint can never push
|
|
242
291
|
the gate's own warnings off the end)
|
|
@@ -258,6 +307,13 @@ lint in its own section: names that are not addressable, prose slots holding one
|
|
|
258
307
|
sentence twice, content written where no surface reads it. Warnings only — it can
|
|
259
308
|
never fail the build. Opt out with --no-requirement-lint or META_NO_REQUIREMENT_LINT=1.
|
|
260
309
|
The requirements GATE itself (dangling refs, link floor, levels) always runs.
|
|
310
|
+
|
|
311
|
+
verify also prints an ADVISORY overlay authoring lint, in its own section: a
|
|
312
|
+
top-level declaration redeclared in two or more files where more than one
|
|
313
|
+
redeclaration lacks 'overlay: true' — today's default merge rule reuses it
|
|
314
|
+
silently, but the same unflagged redeclaration silently becomes a NEW object
|
|
315
|
+
the day the target is renamed or removed upstream. Warnings only — it can
|
|
316
|
+
never fail the build. Opt out with --no-overlay-lint or META_NO_OVERLAY_LINT=1.
|
|
261
317
|
`,
|
|
262
318
|
export: `meta export — flatten loaded metadata to one canonical JSON artifact
|
|
263
319
|
|
|
@@ -530,6 +586,10 @@ export async function run(argv: string[]): Promise<number> {
|
|
|
530
586
|
const { ejectCommand } = await import("./commands/eject.js");
|
|
531
587
|
return ejectCommand(rest, cwd);
|
|
532
588
|
}
|
|
589
|
+
case "deps": {
|
|
590
|
+
const { depsCommand } = await import("./commands/deps.js");
|
|
591
|
+
return depsCommand(rest, cwd, fmt);
|
|
592
|
+
}
|
|
533
593
|
case "export": {
|
|
534
594
|
const { exportCommand } = await import("./commands/export.js");
|
|
535
595
|
return exportCommand(rest, cwd);
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
|
|
3
|
+
// The agent-context subpath, not the root barrel: `@metaobjectsdev/sdk` exports TWO
|
|
4
|
+
// types named `Manifest` — this one (the scaffolded agent context) via `export *`,
|
|
5
|
+
// and FR-023's `metaobjects.pkg.json` explicitly. An explicit export shadows a
|
|
6
|
+
// star one, so the root barrel now hands out the dependency manifest under this
|
|
7
|
+
// name. `init.ts` already imports these from the subpath; this was the last site
|
|
8
|
+
// reading them from the root.
|
|
9
|
+
import { AGENT_CONTEXT_MANIFEST_PATH, agentContextStaleness, type Manifest } from "@metaobjectsdev/sdk/agent-context";
|
|
4
10
|
import { cliVersion } from "./version.js";
|
|
5
11
|
import { log } from "./log.js";
|
|
6
12
|
|