@metaobjectsdev/cli 1.0.12 → 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.
- package/README.md +32 -22
- package/dist/bin/meta.js +14 -2
- package/dist/bin/meta.js.map +1 -1
- package/dist/src/commands/docs.d.ts +1 -14
- package/dist/src/commands/docs.d.ts.map +1 -1
- package/dist/src/commands/docs.js +11 -835
- package/dist/src/commands/docs.js.map +1 -1
- package/dist/src/commands/eject.d.ts +24 -1
- package/dist/src/commands/eject.d.ts.map +1 -1
- package/dist/src/commands/eject.js +240 -69
- package/dist/src/commands/eject.js.map +1 -1
- package/dist/src/commands/gen.d.ts +7 -0
- package/dist/src/commands/gen.d.ts.map +1 -1
- package/dist/src/commands/gen.js +91 -21
- package/dist/src/commands/gen.js.map +1 -1
- package/dist/src/commands/generator.js +2 -2
- package/dist/src/commands/generator.js.map +1 -1
- package/dist/src/commands/init.d.ts +7 -0
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +71 -36
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/migrate.d.ts +1 -1
- package/dist/src/commands/migrate.d.ts.map +1 -1
- package/dist/src/commands/migrate.js +224 -125
- package/dist/src/commands/migrate.js.map +1 -1
- package/dist/src/commands/types.d.ts.map +1 -1
- package/dist/src/commands/types.js +16 -2
- package/dist/src/commands/types.js.map +1 -1
- package/dist/src/commands/upgrade.d.ts.map +1 -1
- package/dist/src/commands/upgrade.js +4 -1
- package/dist/src/commands/upgrade.js.map +1 -1
- package/dist/src/commands/verify.d.ts.map +1 -1
- package/dist/src/commands/verify.js +189 -125
- package/dist/src/commands/verify.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +59 -72
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/agent-context-staleness.d.ts +8 -1
- package/dist/src/lib/agent-context-staleness.d.ts.map +1 -1
- package/dist/src/lib/agent-context-staleness.js +20 -5
- package/dist/src/lib/agent-context-staleness.js.map +1 -1
- package/dist/src/lib/agent-schema.d.ts +9 -0
- package/dist/src/lib/agent-schema.d.ts.map +1 -0
- package/dist/src/lib/agent-schema.js +61 -0
- package/dist/src/lib/agent-schema.js.map +1 -0
- package/dist/src/lib/allow.d.ts +6 -0
- package/dist/src/lib/allow.d.ts.map +1 -1
- package/dist/src/lib/allow.js +49 -5
- package/dist/src/lib/allow.js.map +1 -1
- package/dist/src/lib/anti-patterns.d.ts.map +1 -1
- package/dist/src/lib/anti-patterns.js +4 -2
- package/dist/src/lib/anti-patterns.js.map +1 -1
- package/dist/src/lib/args.d.ts +51 -10
- package/dist/src/lib/args.d.ts.map +1 -1
- package/dist/src/lib/args.js +44 -18
- package/dist/src/lib/args.js.map +1 -1
- package/dist/src/lib/catalog-listing.d.ts +21 -1
- package/dist/src/lib/catalog-listing.d.ts.map +1 -1
- package/dist/src/lib/catalog-listing.js +85 -16
- package/dist/src/lib/catalog-listing.js.map +1 -1
- package/dist/src/lib/catalog.d.ts +30 -0
- package/dist/src/lib/catalog.d.ts.map +1 -1
- package/dist/src/lib/catalog.js +40 -5
- package/dist/src/lib/catalog.js.map +1 -1
- package/dist/src/lib/codegen-drift.d.ts +31 -2
- package/dist/src/lib/codegen-drift.d.ts.map +1 -1
- package/dist/src/lib/codegen-drift.js +109 -4
- package/dist/src/lib/codegen-drift.js.map +1 -1
- package/dist/src/lib/collection-load-options.d.ts +1 -1
- package/dist/src/lib/collection-load-options.d.ts.map +1 -1
- package/dist/src/lib/collection-load-options.js +1 -0
- package/dist/src/lib/collection-load-options.js.map +1 -1
- package/dist/src/lib/config.d.ts +4 -2
- package/dist/src/lib/config.d.ts.map +1 -1
- package/dist/src/lib/config.js +4 -2
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/decimal-precision-advisory.d.ts +10 -0
- package/dist/src/lib/decimal-precision-advisory.d.ts.map +1 -0
- package/dist/src/lib/decimal-precision-advisory.js +34 -0
- package/dist/src/lib/decimal-precision-advisory.js.map +1 -0
- package/dist/src/lib/deprecation-lint.d.ts.map +1 -1
- package/dist/src/lib/deprecation-lint.js +17 -26
- package/dist/src/lib/deprecation-lint.js.map +1 -1
- package/dist/src/lib/docs-removed.d.ts +13 -0
- package/dist/src/lib/docs-removed.d.ts.map +1 -0
- package/dist/src/lib/docs-removed.js +31 -0
- package/dist/src/lib/docs-removed.js.map +1 -0
- package/dist/src/lib/docs-site-eject.d.ts +13 -0
- package/dist/src/lib/docs-site-eject.d.ts.map +1 -0
- package/dist/src/lib/docs-site-eject.js +40 -0
- package/dist/src/lib/docs-site-eject.js.map +1 -0
- package/dist/src/lib/field-lint.d.ts +25 -0
- package/dist/src/lib/field-lint.d.ts.map +1 -0
- package/dist/src/lib/field-lint.js +102 -0
- package/dist/src/lib/field-lint.js.map +1 -0
- package/dist/src/lib/fk-index-advisory.d.ts +1 -2
- package/dist/src/lib/fk-index-advisory.d.ts.map +1 -1
- package/dist/src/lib/fk-index-advisory.js +4 -3
- package/dist/src/lib/fk-index-advisory.js.map +1 -1
- package/dist/src/lib/format.d.ts +7 -0
- package/dist/src/lib/format.d.ts.map +1 -1
- package/dist/src/lib/format.js +13 -0
- package/dist/src/lib/format.js.map +1 -1
- package/dist/src/lib/gen-migrate-advice.d.ts +3 -1
- package/dist/src/lib/gen-migrate-advice.d.ts.map +1 -1
- package/dist/src/lib/gen-migrate-advice.js +3 -4
- package/dist/src/lib/gen-migrate-advice.js.map +1 -1
- package/dist/src/lib/install-set.d.ts +10 -1
- package/dist/src/lib/install-set.d.ts.map +1 -1
- package/dist/src/lib/install-set.js +20 -2
- package/dist/src/lib/install-set.js.map +1 -1
- package/dist/src/lib/load-metaobjects-config.d.ts +13 -11
- package/dist/src/lib/load-metaobjects-config.d.ts.map +1 -1
- package/dist/src/lib/load-metaobjects-config.js +53 -75
- package/dist/src/lib/load-metaobjects-config.js.map +1 -1
- package/dist/src/lib/manifest-ignored-check.d.ts +4 -1
- package/dist/src/lib/manifest-ignored-check.d.ts.map +1 -1
- package/dist/src/lib/manifest-ignored-check.js +9 -4
- package/dist/src/lib/manifest-ignored-check.js.map +1 -1
- package/dist/src/lib/output.d.ts +15 -0
- package/dist/src/lib/output.d.ts.map +1 -1
- package/dist/src/lib/output.js +9 -1
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/payload-field-tree.d.ts +4 -5
- package/dist/src/lib/payload-field-tree.d.ts.map +1 -1
- package/dist/src/lib/payload-field-tree.js +16 -31
- package/dist/src/lib/payload-field-tree.js.map +1 -1
- package/dist/src/lib/requirement-check.d.ts +7 -0
- package/dist/src/lib/requirement-check.d.ts.map +1 -1
- package/dist/src/lib/requirement-check.js +14 -7
- package/dist/src/lib/requirement-check.js.map +1 -1
- package/dist/src/lib/strict-args.d.ts +28 -0
- package/dist/src/lib/strict-args.d.ts.map +1 -0
- package/dist/src/lib/strict-args.js +49 -0
- package/dist/src/lib/strict-args.js.map +1 -0
- package/dist/src/lib/template-eject.d.ts +108 -0
- package/dist/src/lib/template-eject.d.ts.map +1 -0
- package/dist/src/lib/template-eject.js +438 -0
- package/dist/src/lib/template-eject.js.map +1 -0
- package/dist/src/lib/version.d.ts +9 -0
- package/dist/src/lib/version.d.ts.map +1 -1
- package/dist/src/lib/version.js +15 -0
- package/dist/src/lib/version.js.map +1 -1
- package/package.json +10 -10
- package/src/commands/docs.ts +11 -952
- package/src/commands/eject.ts +318 -73
- package/src/commands/gen.ts +94 -22
- package/src/commands/generator.ts +2 -2
- package/src/commands/init.ts +80 -38
- package/src/commands/migrate.ts +248 -117
- package/src/commands/types.ts +18 -2
- package/src/commands/upgrade.ts +5 -1
- package/src/commands/verify.ts +229 -136
- package/src/index.ts +59 -72
- package/src/lib/agent-context-staleness.ts +20 -5
- package/src/lib/agent-schema.ts +74 -0
- package/src/lib/allow.ts +57 -6
- package/src/lib/anti-patterns.ts +4 -2
- package/src/lib/args.ts +83 -28
- package/src/lib/catalog-listing.ts +105 -25
- package/src/lib/catalog.ts +51 -6
- package/src/lib/codegen-drift.ts +138 -3
- package/src/lib/collection-load-options.ts +2 -1
- package/src/lib/config.ts +5 -3
- package/src/lib/decimal-precision-advisory.ts +50 -0
- package/src/lib/deprecation-lint.ts +16 -24
- package/src/lib/docs-removed.ts +41 -0
- package/src/lib/docs-site-eject.ts +48 -0
- package/src/lib/field-lint.ts +122 -0
- package/src/lib/fk-index-advisory.ts +4 -4
- package/src/lib/format.ts +12 -0
- package/src/lib/gen-migrate-advice.ts +6 -4
- package/src/lib/install-set.ts +20 -1
- package/src/lib/load-metaobjects-config.ts +57 -78
- package/src/lib/manifest-ignored-check.ts +14 -10
- package/src/lib/output.ts +18 -1
- package/src/lib/payload-field-tree.ts +18 -35
- package/src/lib/requirement-check.ts +21 -6
- package/src/lib/strict-args.ts +52 -0
- package/src/lib/template-eject.ts +526 -0
- package/src/lib/version.ts +18 -0
- package/dist/src/lib/docs-drift.d.ts +0 -42
- package/dist/src/lib/docs-drift.d.ts.map +0 -1
- package/dist/src/lib/docs-drift.js +0 -264
- package/dist/src/lib/docs-drift.js.map +0 -1
- package/src/lib/docs-drift.ts +0 -312
|
@@ -22,26 +22,29 @@ import { scanForRemovedProps } from "../lib/removed-prop-advisory.js";
|
|
|
22
22
|
import { replayRemedy } from "../lib/replay-remedy.js";
|
|
23
23
|
import { FileProvider, mustacheTagLine } from "../lib/file-provider.js";
|
|
24
24
|
import { derivePayloadFieldTree } from "../lib/payload-field-tree.js";
|
|
25
|
-
import { loadMemoryOptionsFrom, loadMetaobjectsConfig, resolveGenCollection, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
|
|
25
|
+
import { loadMemoryOptionsFrom, loadMetaobjectsConfig, RemovedNamingConfigError, resolveGenCollection, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
|
|
26
26
|
import { collectionLoadOptions } from "../lib/collection-load-options.js";
|
|
27
27
|
import { computeCodegenDrift } from "../lib/codegen-drift.js";
|
|
28
|
-
import {
|
|
28
|
+
import { hasLegacyDocsBlock, legacyDocsConfigMessage, REMOVED_VERIFY_DOCS } from "../lib/docs-removed.js";
|
|
29
|
+
import { buildAgentSchemaInput } from "../lib/agent-schema.js";
|
|
30
|
+
import { sourceRootsOf } from "./gen.js";
|
|
29
31
|
import { checkRequirements, summariseRequirements, scanRequirements, } from "../lib/requirement-check.js";
|
|
30
32
|
import { lintRequirements } from "../lib/requirement-lint.js";
|
|
31
33
|
import { lintOverlays } from "../lib/overlay-lint.js";
|
|
32
34
|
import { lintNodeNames } from "../lib/name-lint.js";
|
|
33
35
|
import { lintDeprecatedReferences } from "../lib/deprecation-lint.js";
|
|
36
|
+
import { lintDuplicateFields, lintReferenceFields } from "../lib/field-lint.js";
|
|
34
37
|
import { FileSource } from "@metaobjectsdev/metadata/core";
|
|
35
38
|
import { resolveD1Config, resolveMigrateConfig } from "../lib/config.js";
|
|
36
39
|
import { buildWranglerExecuteArgs, defaultWranglerRunner, isWranglerLocalD1StatePath, } from "../lib/wrangler.js";
|
|
37
|
-
import { buildProjectionViews
|
|
40
|
+
import { buildProjectionViews } from "@metaobjectsdev/codegen-ts";
|
|
38
41
|
import { buildKyselyFromUrl, inferDialect } from "../lib/kysely.js";
|
|
39
42
|
import { tokensToAllowOptions, describeChange } from "../lib/allow.js";
|
|
40
|
-
import { computeDrift, computeDriftFromActual, collectUnmanagedNames, excludeFromSnapshot, scopedDiffInputs, scopeExpectedSchema, buildExpectedSchemaWithProvenance, applyPending, openReplayEngine, verifyReplay, introspect, diff, readSnapshot, snapshotPath, introspectD1, findWranglerConfig, parseWranglerConfig, resolveD1Binding, } from "@metaobjectsdev/migrate-ts";
|
|
43
|
+
import { computeDrift, computeDriftFromActual, collectUnmanagedNames, excludeFromSnapshot, scopedDiffInputs, scopeExpectedSchema, buildExpectedSchemaWithProvenance, applyPending, openReplayEngine, verifyReplay, introspect, diff, withoutViewRecreates, readSnapshot, snapshotPath, introspectD1, findWranglerConfig, parseWranglerConfig, resolveD1Binding, postgresViewReplaceProbe, withoutFlywayHistory, } from "@metaobjectsdev/migrate-ts";
|
|
41
44
|
import { DEFAULT_METAOBJECTS_DIR, loadConfig, loadMemory, resolveCollection, } from "@metaobjectsdev/sdk";
|
|
42
45
|
import { checkDependencies, ERR_DEPENDENCY_UPSTREAM_DRIFT, formatCheckLine, readLockOrThrow } from "../lib/dependency-sync.js";
|
|
43
46
|
import { exclusionNotes, importedOption, migrateScopeMismatch } from "../lib/migrate-scope.js";
|
|
44
|
-
import { TYPE_TEMPLATE, TEMPLATE_SUBTYPE_PROMPT, TEMPLATE_SUBTYPE_OUTPUT, TEMPLATE_ATTR_PAYLOAD_REF, TEMPLATE_ATTR_TEXT_REF, TEMPLATE_ATTR_REQUIRED_SLOTS, TEMPLATE_ATTR_REQUIRED_TAGS, TEMPLATE_ATTR_KIND, TEMPLATE_KIND_EMAIL, TEMPLATE_KIND_DEFAULT, TEMPLATE_ATTR_SUBJECT_REF, TEMPLATE_ATTR_HTML_BODY_REF, TEMPLATE_ATTR_TEXT_BODY_REF, REQUIREMENT_STATUSES, } from "@metaobjectsdev/metadata";
|
|
47
|
+
import { TYPE_TEMPLATE, TEMPLATE_SUBTYPE_PROMPT, TEMPLATE_SUBTYPE_OUTPUT, TEMPLATE_ATTR_PAYLOAD_REF, refTarget, TEMPLATE_ATTR_TEXT_REF, TEMPLATE_ATTR_REQUIRED_SLOTS, TEMPLATE_ATTR_REQUIRED_TAGS, TEMPLATE_ATTR_KIND, TEMPLATE_KIND_EMAIL, TEMPLATE_KIND_DEFAULT, TEMPLATE_ATTR_SUBJECT_REF, TEMPLATE_ATTR_HTML_BODY_REF, TEMPLATE_ATTR_TEXT_BODY_REF, REQUIREMENT_STATUSES, physicalNamingOf, } from "@metaobjectsdev/metadata";
|
|
45
48
|
import { verify, ERR_REQUIRED_SLOT_UNUSED, ERR_PARTIAL_UNRESOLVED } from "@metaobjectsdev/render";
|
|
46
49
|
import { describeLoadError, reportLoadError } from "../lib/load-error.js";
|
|
47
50
|
import { describeError } from "../lib/error-text.js";
|
|
@@ -52,9 +55,11 @@ const DEFAULT_PROMPTS_DIR = "prompts";
|
|
|
52
55
|
const ERR_UNKNOWN_ATTR = "ERR_UNKNOWN_ATTR";
|
|
53
56
|
/**
|
|
54
57
|
* A no-flags MigrateFlags, so `resolveMigrateConfig` yields exactly what `meta migrate`
|
|
55
|
-
* would use with nothing passed on the command line — config value, else default.
|
|
58
|
+
* would use with nothing passed on the command line — config value, else default. The one
|
|
59
|
+
* migrate flag verify also takes, `--migration-format`, is laid over it per invocation, so
|
|
60
|
+
* the format resolves flag > config > default exactly as it does for `meta migrate`.
|
|
56
61
|
*
|
|
57
|
-
* verify consumes `outDir` (#292) and — for the replay gate ONLY — `dialect`. The #292
|
|
62
|
+
* verify consumes `outDir` (#292), `format` and — for the replay gate ONLY — `dialect`. The #292
|
|
58
63
|
* restriction that reading anything else "would be reaching into migrate's decisions"
|
|
59
64
|
* was written about the DRIFT gate, whose dialect comes from the live `--db` URL. The
|
|
60
65
|
* replay gate has no `--db` at all, and the dialect a committed chain was EMITTED for
|
|
@@ -106,6 +111,15 @@ fmt = "text") {
|
|
|
106
111
|
emitStructured({ error: msg, hint: "run `meta verify --help` for the accepted flags" }, fmt);
|
|
107
112
|
return 2;
|
|
108
113
|
}
|
|
114
|
+
// ADR-0034 Amendment 9: documentation is checked by `--codegen` with everything else the
|
|
115
|
+
// generator list declares. The old flag still parses, so it can say where it went. It says
|
|
116
|
+
// so once, as plain text on stderr, like every other removed command: a structured copy on
|
|
117
|
+
// stdout printed the same sentence a second time, escaped (`error: "\`meta verify ...`).
|
|
118
|
+
if (flags.docs) {
|
|
119
|
+
log.error(REMOVED_VERIFY_DOCS);
|
|
120
|
+
return 2;
|
|
121
|
+
}
|
|
122
|
+
const migrateFlags = { ...EMPTY_MIGRATE_FLAGS, format: flags.migrationFormat };
|
|
109
123
|
// ADR-0021 D2 — explicit verify subverbs. Each flag selects one drift mode;
|
|
110
124
|
// any combination runs each and the overall exit code is the MAX (non-zero on
|
|
111
125
|
// any drift). A bare `verify` (no explicit subverb) keeps its documented
|
|
@@ -115,15 +129,14 @@ fmt = "text") {
|
|
|
115
129
|
// The schema gate is selected by the presence of --db, or --dialect d1 (#225 —
|
|
116
130
|
// D1 has no URL connection); that check lives inside runSchemaVerify.
|
|
117
131
|
const runCodegen = flags.codegen;
|
|
118
|
-
|
|
119
|
-
// Task 15 — same shape as --codegen/--docs: selected ONLY by its own flag,
|
|
132
|
+
// Task 15 — same shape as --codegen: selected ONLY by its own flag,
|
|
120
133
|
// never folded into the bare-verify default (it needs the publisher
|
|
121
134
|
// reachable, which CI may not have — see the VerifyFlags doc on `deps`).
|
|
122
135
|
const runDeps = flags.deps;
|
|
123
136
|
if (!flags.anyExplicit) {
|
|
124
137
|
say("meta verify — running --templates (default). Explicit subverbs: " +
|
|
125
|
-
"--templates (prompt drift), --db/--dialect d1 (schema drift),
|
|
126
|
-
"--
|
|
138
|
+
"--templates (prompt drift), --db/--dialect d1 (schema drift), " +
|
|
139
|
+
"--codegen (drift of everything `generators` declares, documentation included), --deps (dependency drift), " +
|
|
127
140
|
"--replay/--replay-snapshot (the committed migration chain replays from empty).");
|
|
128
141
|
}
|
|
129
142
|
// Where the metadata lives is `resolveCollection`'s decision, not a hardcoded
|
|
@@ -187,7 +200,13 @@ fmt = "text") {
|
|
|
187
200
|
try {
|
|
188
201
|
forgeConfig = await loadMetaobjectsConfig(genConfigDir);
|
|
189
202
|
}
|
|
190
|
-
catch {
|
|
203
|
+
catch (e) {
|
|
204
|
+
// ADR-0058: the one config problem verify cannot fall back from — the default column
|
|
205
|
+
// rule would report every column of a database built with the old value as drift.
|
|
206
|
+
if (e instanceof RemovedNamingConfigError) {
|
|
207
|
+
log.error(e.message);
|
|
208
|
+
return 2;
|
|
209
|
+
}
|
|
191
210
|
forgeConfig = undefined;
|
|
192
211
|
}
|
|
193
212
|
// Both of the gen config's contributions to the load, together — see
|
|
@@ -281,6 +300,7 @@ fmt = "text") {
|
|
|
281
300
|
let nameSection = skippedSection("the name lint did not run");
|
|
282
301
|
// #305 — the deprecated-reference authoring lint. Its own section, same reason.
|
|
283
302
|
let deprecationSection = skippedSection("the deprecated-reference lint did not run");
|
|
303
|
+
let fieldSection = skippedSection("the field lint did not run");
|
|
284
304
|
// The ledger counts `meta verify` prints on every run. Undefined for a project
|
|
285
305
|
// declaring no requirement.* node at all (opt-in by declaration) — the payload
|
|
286
306
|
// then omits the block rather than reporting zeroes that would read as an empty
|
|
@@ -295,9 +315,14 @@ fmt = "text") {
|
|
|
295
315
|
// subverb so ANY kind of drift fails CI. Each gate only runs when its mode is
|
|
296
316
|
// selected; an unselected gate contributes 0.
|
|
297
317
|
const templateExit = runTemplates ? runTemplateVerify() : 0;
|
|
318
|
+
// Set when the schema gate could not reach or read the database: a failure, but not
|
|
319
|
+
// drift, and the payload must not report it as drift.
|
|
320
|
+
let schemaRunError;
|
|
321
|
+
// The schema gate's findings, for the structured payload: what differs, not only that
|
|
322
|
+
// something does. Left undefined when the gate did not compare anything.
|
|
323
|
+
let schemaDrift;
|
|
298
324
|
const schemaExit = await runSchemaVerify();
|
|
299
325
|
const codegenExit = runCodegen ? await runCodegenVerify() : 0;
|
|
300
|
-
const docsExit = runDocs ? await runDocsVerify() : 0;
|
|
301
326
|
const depsExit = runDeps ? await runDepsVerify() : 0;
|
|
302
327
|
// Requirements have no subverb: `requirement.*` nodes are metadata, so they
|
|
303
328
|
// are checked on every `meta verify`. Opt-in by DECLARATION — a model with no
|
|
@@ -320,12 +345,16 @@ fmt = "text") {
|
|
|
320
345
|
// @from/@of/@via) on a `deprecated` node. Runs on every `meta verify`;
|
|
321
346
|
// warnings ONLY, never changes the exit code.
|
|
322
347
|
runDeprecationLintAdvisory();
|
|
348
|
+
// A reference identity over a field the object lacks, and a field name declared
|
|
349
|
+
// twice in one children list — both load clean on every port. Runs on every
|
|
350
|
+
// `meta verify`; warnings ONLY, never changes the exit code.
|
|
351
|
+
await runFieldLintAdvisory();
|
|
323
352
|
// Advisory verify-as-teacher pass: surface hand-rolled work the metadata could
|
|
324
353
|
// model. Warnings ONLY — never changes the exit code (bias to under-flagging).
|
|
325
354
|
// Suppressed with --no-antipatterns or META_NO_ANTIPATTERNS=1 for the rare
|
|
326
355
|
// noisy project (both opt-outs work on `meta verify` and `meta gen`).
|
|
327
356
|
await runAntiPatternAdvisory();
|
|
328
|
-
const exitCode = Math.max(templateExit, schemaExit, codegenExit,
|
|
357
|
+
const exitCode = Math.max(templateExit, schemaExit, codegenExit, depsExit, requirementExit, replayExit);
|
|
329
358
|
const gates = [
|
|
330
359
|
{ gate: "templates", ran: runTemplates, ok: templateExit === 0 },
|
|
331
360
|
// The schema gate decides internally whether it is selected (--db, or
|
|
@@ -334,7 +363,6 @@ fmt = "text") {
|
|
|
334
363
|
// failure mode this whole change exists to remove.
|
|
335
364
|
{ gate: "schema", ran: ranSchemaGate, ok: schemaExit === 0 },
|
|
336
365
|
{ gate: "codegen", ran: runCodegen, ok: codegenExit === 0 },
|
|
337
|
-
{ gate: "docs", ran: runDocs, ok: docsExit === 0 },
|
|
338
366
|
{ gate: "deps", ran: runDeps, ok: depsExit === 0 },
|
|
339
367
|
{ gate: "requirements", ran: true, ok: requirementExit === 0 },
|
|
340
368
|
{ gate: "replay", ran: flags.replay || flags.replaySnapshot, ok: replayExit === 0 },
|
|
@@ -358,6 +386,9 @@ fmt = "text") {
|
|
|
358
386
|
overlays: overlaySection,
|
|
359
387
|
names: nameSection,
|
|
360
388
|
deprecations: deprecationSection,
|
|
389
|
+
fields: fieldSection,
|
|
390
|
+
...(schemaDrift !== undefined ? { schemaDrift } : {}),
|
|
391
|
+
errors: schemaRunError !== undefined ? [{ gate: "schema", error: schemaRunError }] : [],
|
|
361
392
|
}), fmt);
|
|
362
393
|
}
|
|
363
394
|
return exitCode;
|
|
@@ -382,7 +413,7 @@ fmt = "text") {
|
|
|
382
413
|
// Resolve the migrations directory and the chain's dialect through MIGRATE's own
|
|
383
414
|
// precedence, never a second derivation — verify must not look somewhere migrate
|
|
384
415
|
// does not write, nor assume a dialect the chain was not emitted for.
|
|
385
|
-
const migrateConfig = await resolveMigrateConfig(
|
|
416
|
+
const migrateConfig = await resolveMigrateConfig(migrateFlags, projectRoot);
|
|
386
417
|
if (migrateConfig.format === "flyway") {
|
|
387
418
|
log.error(`meta verify --replay is not supported with --migration-format flyway — run 'flyway migrate' against a scratch database to replay`);
|
|
388
419
|
return 2;
|
|
@@ -507,11 +538,9 @@ fmt = "text") {
|
|
|
507
538
|
// passes no `governed` and gets the comparison exactly as it was.
|
|
508
539
|
let governed;
|
|
509
540
|
if (schemaScope !== undefined) {
|
|
510
|
-
const viewStrategy = forgeConfig?.columnNamingStrategy ?? "snake_case";
|
|
511
541
|
const built = buildExpectedSchemaWithProvenance(root, {
|
|
512
542
|
dialect,
|
|
513
|
-
|
|
514
|
-
views: buildProjectionViews(root, { dialect, columnNamingStrategy: viewStrategy }),
|
|
543
|
+
views: buildProjectionViews(root, { dialect }),
|
|
515
544
|
});
|
|
516
545
|
governed = scopeExpectedSchema(built, schemaScope, importedOption(collection));
|
|
517
546
|
}
|
|
@@ -550,7 +579,14 @@ fmt = "text") {
|
|
|
550
579
|
// ONE scan for all three passes. The gate and the summary each used to walk the
|
|
551
580
|
// model AND resolve every @implementedBy claim for themselves — the resolution
|
|
552
581
|
// being the expensive half — and the lint added a third walk on top.
|
|
553
|
-
|
|
582
|
+
// The strict switch (ADR-0057) is decided here, once, and travels on the scan: the
|
|
583
|
+
// flag or META_REQUIRE_IMPLEMENTERS=1 — a flag-or-variable pair, as
|
|
584
|
+
// --no-requirement-lint has below, so a CI job can turn it on without editing a
|
|
585
|
+
// command line it shares with local runs.
|
|
586
|
+
const scan = scanRequirements(root, {
|
|
587
|
+
coverable: collection.inCoverage,
|
|
588
|
+
requireImplementers: flags.requireImplementers || process.env.META_REQUIRE_IMPLEMENTERS === "1",
|
|
589
|
+
});
|
|
554
590
|
const diags = [...checkRequirements(root, scan)];
|
|
555
591
|
// Printed on EVERY run, clean or not — a gate that says nothing when it
|
|
556
592
|
// passes cannot be told apart from a gate that checked nothing, and the
|
|
@@ -715,6 +751,39 @@ fmt = "text") {
|
|
|
715
751
|
warnCapped(findings.map(formatDiagnostic), flags.limit, { structured });
|
|
716
752
|
}
|
|
717
753
|
}
|
|
754
|
+
// -- field authoring lint (advisory) ----------------------------------------
|
|
755
|
+
// Reads the LOADED model for the reference half and the project's OWN raw files
|
|
756
|
+
// for the duplicate half (see field-lint.ts) — a dependency artifact is not the
|
|
757
|
+
// adopter's to edit. Its own section, its own cap, and a skip that carries its
|
|
758
|
+
// reason rather than looking like a clean scan.
|
|
759
|
+
async function runFieldLintAdvisory() {
|
|
760
|
+
if (flags.noFieldLint) {
|
|
761
|
+
fieldSection = skippedSection("suppressed by --no-field-lint");
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (process.env.META_NO_FIELD_LINT === "1") {
|
|
765
|
+
fieldSection = skippedSection("suppressed by META_NO_FIELD_LINT=1");
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
let findings;
|
|
769
|
+
try {
|
|
770
|
+
findings = [
|
|
771
|
+
...lintReferenceFields(root),
|
|
772
|
+
...(await lintDuplicateFields(collection.ownFiles, (path) => new FileSource(path))),
|
|
773
|
+
];
|
|
774
|
+
}
|
|
775
|
+
catch (err) {
|
|
776
|
+
// Never let an advisory scan break verify — and never report it as clean.
|
|
777
|
+
fieldSection = skippedSection(`the field lint failed: ${describeError(err)}`);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
fieldSection = ranSection(findings.map((d) => toDiagnosticRow(d, "lint")));
|
|
781
|
+
if (findings.length > 0) {
|
|
782
|
+
log.warn(`meta verify — fields: ${findings.length} authoring warning(s) ` +
|
|
783
|
+
`(advisory — does not fail the build):`);
|
|
784
|
+
warnCapped(findings.map(formatDiagnostic), flags.limit, { structured });
|
|
785
|
+
}
|
|
786
|
+
}
|
|
718
787
|
// -- deprecated-reference authoring lint (advisory) -------------------------
|
|
719
788
|
// #305 — reads the LOADED model (see deprecation-lint.ts). Its own section,
|
|
720
789
|
// its own cap, and a skip that carries its reason rather than looking clean.
|
|
@@ -802,7 +871,6 @@ fmt = "text") {
|
|
|
802
871
|
if (scanDialect !== "mysql") {
|
|
803
872
|
unindexedFks = scanForUnindexedForeignKeys(root, {
|
|
804
873
|
dialect: scanDialect,
|
|
805
|
-
columnNamingStrategy: forgeConfig?.columnNamingStrategy ?? "snake_case",
|
|
806
874
|
skip: collection.imported,
|
|
807
875
|
});
|
|
808
876
|
}
|
|
@@ -904,7 +972,7 @@ fmt = "text") {
|
|
|
904
972
|
continue;
|
|
905
973
|
// @payloadRef must resolve to a loaded object.value (drives the field tree
|
|
906
974
|
// every render-engine drift check runs against).
|
|
907
|
-
const fieldTree = derivePayloadFieldTree(
|
|
975
|
+
const fieldTree = derivePayloadFieldTree(refTarget(tmpl, TEMPLATE_ATTR_PAYLOAD_REF));
|
|
908
976
|
if (fieldTree.length === 0) {
|
|
909
977
|
log.error(`[${tmpl.name}] (${tmpl.subType}) ${ERR_PARTIAL_UNRESOLVED}: ` +
|
|
910
978
|
`@payloadRef "${payloadRef}" did not resolve to a loaded object.value`);
|
|
@@ -1023,11 +1091,9 @@ fmt = "text") {
|
|
|
1023
1091
|
// consults it.
|
|
1024
1092
|
const scopeMismatch = migrateScopeMismatch(collection, () => {
|
|
1025
1093
|
const dialect = usingD1 ? "d1" : (flags.dialect ?? inferDialect(flags.db));
|
|
1026
|
-
const viewStrategy = forgeConfig?.columnNamingStrategy ?? "snake_case";
|
|
1027
1094
|
return buildExpectedSchemaWithProvenance(root, {
|
|
1028
1095
|
dialect,
|
|
1029
|
-
|
|
1030
|
-
views: buildProjectionViews(root, { dialect, columnNamingStrategy: viewStrategy }),
|
|
1096
|
+
views: buildProjectionViews(root, { dialect }),
|
|
1031
1097
|
}).provenance;
|
|
1032
1098
|
});
|
|
1033
1099
|
if (scopeMismatch !== undefined) {
|
|
@@ -1063,15 +1129,20 @@ fmt = "text") {
|
|
|
1063
1129
|
kysely = await buildKyselyFromUrl(flags.db, flags.dialect);
|
|
1064
1130
|
}
|
|
1065
1131
|
catch (err) {
|
|
1066
|
-
|
|
1132
|
+
schemaRunError = describeError(err);
|
|
1133
|
+
log.error(`verify: ${schemaRunError}`);
|
|
1067
1134
|
return 1;
|
|
1068
1135
|
}
|
|
1069
1136
|
try {
|
|
1070
1137
|
const allow = tokensToAllowOptions(flags.allow);
|
|
1138
|
+
// Resolve the migrate config once for this whole path: `tryLoadConfig` is uncached,
|
|
1139
|
+
// and the flyway filter below and checkCommittedSnapshot's migrations-dir read want
|
|
1140
|
+
// the same value — resolving twice would read and parse `.metaobjects/config.json`
|
|
1141
|
+
// twice per `meta verify --db`.
|
|
1142
|
+
const migrateConfig = await resolveMigrateConfig(migrateFlags, projectRoot);
|
|
1071
1143
|
// Expected views from the single view-SQL source (codegen-ts), so view-body
|
|
1072
1144
|
// drift is detected against the live DB.
|
|
1073
|
-
const
|
|
1074
|
-
const expectedViews = buildProjectionViews(root, { dialect: kysely.dialect, columnNamingStrategy: viewStrategy });
|
|
1145
|
+
const expectedViews = buildProjectionViews(root, { dialect: kysely.dialect });
|
|
1075
1146
|
let driftResult;
|
|
1076
1147
|
let actual;
|
|
1077
1148
|
try {
|
|
@@ -1079,9 +1150,15 @@ fmt = "text") {
|
|
|
1079
1150
|
// `actual` this drift comparison uses, and re-introspecting for it would both
|
|
1080
1151
|
// cost a second round trip and open a window where the two could disagree.
|
|
1081
1152
|
actual = await introspect(kysely.db, kysely.dialect);
|
|
1153
|
+
// Flyway owns its history table; it is no drift under the flyway format, as in `meta migrate`.
|
|
1154
|
+
if (migrateConfig.format === "flyway") {
|
|
1155
|
+
actual = withoutFlywayHistory(actual);
|
|
1156
|
+
}
|
|
1082
1157
|
driftResult = await computeDriftFromActual(actual, kysely.dialect, root, {
|
|
1083
1158
|
allow,
|
|
1084
1159
|
views: expectedViews,
|
|
1160
|
+
// The same question `meta migrate` asks, so the report names the change it plans.
|
|
1161
|
+
...(kysely.dialect === "postgres" ? { viewReplaceProbe: postgresViewReplaceProbe(kysely.db) } : {}),
|
|
1085
1162
|
...(schemaScope !== undefined ? { inScope: schemaScope } : {}),
|
|
1086
1163
|
// The import exclusion reaches scopeExpectedSchema through this option bag —
|
|
1087
1164
|
// there is no direct call on this path to attach it to.
|
|
@@ -1089,11 +1166,12 @@ fmt = "text") {
|
|
|
1089
1166
|
});
|
|
1090
1167
|
}
|
|
1091
1168
|
catch (err) {
|
|
1092
|
-
|
|
1169
|
+
schemaRunError = `failed to introspect ${kysely.displayUrl}: ${describeError(err)}`;
|
|
1170
|
+
log.error(`verify: ${schemaRunError}`);
|
|
1093
1171
|
return 1;
|
|
1094
1172
|
}
|
|
1095
1173
|
const snapshotDrift = driftResult.changes.length === 0
|
|
1096
|
-
? await checkCommittedSnapshot(actual, kysely.dialect, kysely.displayUrl, driftResult)
|
|
1174
|
+
? await checkCommittedSnapshot(actual, kysely.dialect, kysely.displayUrl, driftResult, migrateConfig)
|
|
1097
1175
|
: [];
|
|
1098
1176
|
return reportSchemaDrift(driftResult, [...ledgerDrift, ...snapshotDrift], kysely.displayUrl);
|
|
1099
1177
|
}
|
|
@@ -1160,8 +1238,7 @@ fmt = "text") {
|
|
|
1160
1238
|
return 1;
|
|
1161
1239
|
}
|
|
1162
1240
|
const allow = tokensToAllowOptions(flags.allow);
|
|
1163
|
-
const
|
|
1164
|
-
const expectedViews = buildProjectionViews(root, { dialect: "d1", columnNamingStrategy: viewStrategy });
|
|
1241
|
+
const expectedViews = buildProjectionViews(root, { dialect: "d1" });
|
|
1165
1242
|
let driftResult;
|
|
1166
1243
|
try {
|
|
1167
1244
|
driftResult = await computeDriftFromActual(actual, "d1", root, {
|
|
@@ -1209,14 +1286,16 @@ fmt = "text") {
|
|
|
1209
1286
|
// Fails OPEN when there is no snapshot on disk (a project that has never generated one
|
|
1210
1287
|
// offline is not in an error state) and when the file cannot be read or parsed (that is
|
|
1211
1288
|
// migrate's error to raise, with its own message, not a drift verdict).
|
|
1212
|
-
async function checkCommittedSnapshot(actual, dialect, displayUrl, governed
|
|
1289
|
+
async function checkCommittedSnapshot(actual, dialect, displayUrl, governed,
|
|
1290
|
+
// Resolved by the caller and threaded through — see the `verify --db` branch: the same
|
|
1291
|
+
// run's flyway filter reads this config too, and `tryLoadConfig` does not cache.
|
|
1292
|
+
migrateConfig) {
|
|
1213
1293
|
if (dialect === "d1")
|
|
1214
1294
|
return []; // d1 keeps migrations Wrangler-native; no offline snapshot
|
|
1215
|
-
//
|
|
1216
|
-
//
|
|
1217
|
-
//
|
|
1218
|
-
// migrate's business.
|
|
1219
|
-
const migrateConfig = await resolveMigrateConfig(EMPTY_MIGRATE_FLAGS, projectRoot);
|
|
1295
|
+
// The migrations dir comes from migrate's OWN precedence (flag > config > default)
|
|
1296
|
+
// rather than a re-derivation here, so verify can never look somewhere migrate does
|
|
1297
|
+
// not write. Only `outDir` and `format` are consumed; the rest of the resolved config
|
|
1298
|
+
// is migrate's business.
|
|
1220
1299
|
const dir = resolvePath(projectRoot, migrateConfig.outDir);
|
|
1221
1300
|
let snapshot;
|
|
1222
1301
|
try {
|
|
@@ -1227,6 +1306,8 @@ fmt = "text") {
|
|
|
1227
1306
|
}
|
|
1228
1307
|
if (snapshot === null)
|
|
1229
1308
|
return [];
|
|
1309
|
+
if (migrateConfig.format === "flyway")
|
|
1310
|
+
snapshot = withoutFlywayHistory(snapshot);
|
|
1230
1311
|
// Out-of-scope objects leave BOTH sides of this comparison, and the schema pin
|
|
1231
1312
|
// comes from the scope decision the DRIFT comparison already made — one door
|
|
1232
1313
|
// (migrate-ts's `excludeFromSnapshot` + `scopedDiffInputs`), not a fifth
|
|
@@ -1250,7 +1331,9 @@ fmt = "text") {
|
|
|
1250
1331
|
// MERGED with the out-of-scope set, and a second key would silently drop that half.
|
|
1251
1332
|
dialect,
|
|
1252
1333
|
});
|
|
1253
|
-
|
|
1334
|
+
// A view recreated only around a table change matches the snapshot (D3) — not a difference.
|
|
1335
|
+
const changes = withoutViewRecreates(result.changes);
|
|
1336
|
+
if (changes.length === 0)
|
|
1254
1337
|
return [];
|
|
1255
1338
|
return [
|
|
1256
1339
|
// `meta migrate --from-db` is NOT the repair: it writes a snapshot only when it has
|
|
@@ -1260,10 +1343,10 @@ fmt = "text") {
|
|
|
1260
1343
|
// been told everything is in sync. `baseline --from-db` rewrites it unconditionally,
|
|
1261
1344
|
// which is the whole point of the subcommand.
|
|
1262
1345
|
`the committed schema snapshot disagrees with ${displayUrl} ` +
|
|
1263
|
-
`(${
|
|
1346
|
+
`(${changes.length} difference(s)) — the next 'meta migrate' would emit DDL from it ` +
|
|
1264
1347
|
`and fail at apply. Re-derive it with ` +
|
|
1265
1348
|
`'meta migrate baseline --from-db --db <url> --dialect ${dialect}'.`,
|
|
1266
|
-
...summarizeDrift(
|
|
1349
|
+
...summarizeDrift(changes),
|
|
1267
1350
|
];
|
|
1268
1351
|
}
|
|
1269
1352
|
function reportSchemaDrift(driftResult, ledgerDrift, displayUrl) {
|
|
@@ -1282,6 +1365,7 @@ fmt = "text") {
|
|
|
1282
1365
|
say(note);
|
|
1283
1366
|
}
|
|
1284
1367
|
const changes = driftResult.changes;
|
|
1368
|
+
schemaDrift = { changes: changes.map(toSchemaDriftRow), findings: ledgerDrift };
|
|
1285
1369
|
if (changes.length === 0 && ledgerDrift.length === 0) {
|
|
1286
1370
|
say(`meta verify — schema in sync with ${displayUrl}.`);
|
|
1287
1371
|
return 0;
|
|
@@ -1339,12 +1423,23 @@ fmt = "text") {
|
|
|
1339
1423
|
// and the loader's own remedies. It printed a bare message plus a hand-rolled
|
|
1340
1424
|
// suggestions read — the half-true rule this file's sibling comment warns about.
|
|
1341
1425
|
reportLoadError(log, "verify --codegen: failed to load this package's metadata", err);
|
|
1342
|
-
return
|
|
1426
|
+
return 1;
|
|
1343
1427
|
}
|
|
1344
1428
|
}
|
|
1429
|
+
// A 1.0 `docs` block configured a command that no longer exists; regenerating without it
|
|
1430
|
+
// would check no documentation and pass. Refused before anything is written, as `meta gen`
|
|
1431
|
+
// refuses it.
|
|
1432
|
+
if (hasLegacyDocsBlock(forgeConfig)) {
|
|
1433
|
+
log.error(legacyDocsConfigMessage("meta verify"));
|
|
1434
|
+
return 2;
|
|
1435
|
+
}
|
|
1436
|
+
const config = forgeConfig;
|
|
1345
1437
|
let result;
|
|
1346
1438
|
try {
|
|
1347
|
-
result = await computeCodegenDrift(
|
|
1439
|
+
result = await computeCodegenDrift(config, codegenRoot, genConfigDir, genCollection.inScope, genCollection.ownFiles, {
|
|
1440
|
+
sourceRoots: sourceRootsOf(genCollection),
|
|
1441
|
+
agentSchema: () => buildAgentSchemaInput(codegenRoot, config.dialect, (m) => log.warn(`verify --codegen: [agent-docs] ${m}`)),
|
|
1442
|
+
});
|
|
1348
1443
|
}
|
|
1349
1444
|
catch (err) {
|
|
1350
1445
|
log.error(`verify --codegen: regeneration failed: ${describeError(err)}`);
|
|
@@ -1359,6 +1454,17 @@ fmt = "text") {
|
|
|
1359
1454
|
// contribution is current" and nothing else. Reported on every run; fails the build
|
|
1360
1455
|
// only under --forbid-hand-edits.
|
|
1361
1456
|
const handEditExit = reportHandEdits(result.handEdited);
|
|
1457
|
+
// Say how much documentation was NOT checked, on the pass and the fail line alike, so a
|
|
1458
|
+
// gate that compared two pages of 589 cannot read as one that compared all of them.
|
|
1459
|
+
if (result.docsIgnored > 0) {
|
|
1460
|
+
say(`meta verify — ${result.docsIgnored} generated documentation page(s) are git-ignored and were not checked.`);
|
|
1461
|
+
}
|
|
1462
|
+
for (const dir of result.docsIgnoredDirs) {
|
|
1463
|
+
say(`meta verify — ${dir}: the repository git-ignores every documentation page generated there, so they were not compared.`);
|
|
1464
|
+
}
|
|
1465
|
+
if (result.docsIgnoreUnavailable !== undefined) {
|
|
1466
|
+
say(`meta verify — git could not say which documentation pages are ignored (${result.docsIgnoreUnavailable}), so every page was checked.`);
|
|
1467
|
+
}
|
|
1362
1468
|
if (result.clean) {
|
|
1363
1469
|
// Says what it CHECKED, not more. Since 0.24.3 this gate asks whether each file's
|
|
1364
1470
|
// GENERATED contribution is current — a hand edit `meta gen` preserves is exempt —
|
|
@@ -1375,9 +1481,16 @@ fmt = "text") {
|
|
|
1375
1481
|
// it MERGES the edit and exits 0 — so prescribing it there was the unbreakable loop
|
|
1376
1482
|
// the hand-edit exemption exists to end, reintroduced for the one file the exemption
|
|
1377
1483
|
// deliberately excludes. Both lines print when the drift set holds both kinds.
|
|
1378
|
-
if (result.driftedFiles.length > result.handEditedNames.length) {
|
|
1484
|
+
if (result.driftedFiles.length > result.handEditedNames.length + result.editedOrphans.length) {
|
|
1379
1485
|
log.error("Run 'meta gen' to regenerate, then commit the result.");
|
|
1380
1486
|
}
|
|
1487
|
+
// A hand-edited orphan is the other kind `meta gen` cannot fix: it deletes an untouched
|
|
1488
|
+
// file no generator emits any more, and refuses an edited one.
|
|
1489
|
+
if (result.editedOrphans.length > 0) {
|
|
1490
|
+
log.error("For the file(s) above that no generator emits any more and that were edited by hand, " +
|
|
1491
|
+
"'meta gen' will NOT delete them. Delete each one, or move your edits into a file of " +
|
|
1492
|
+
"your own first.");
|
|
1493
|
+
}
|
|
1381
1494
|
if (result.handEditedNames.length > 0) {
|
|
1382
1495
|
log.error("For the names artifact(s) above, 'meta gen' will NOT help — it preserves a hand " +
|
|
1383
1496
|
"edit by design. Revert the edit (or delete the file and re-run 'meta gen' to " +
|
|
@@ -1404,77 +1517,6 @@ fmt = "text") {
|
|
|
1404
1517
|
log.warn(line);
|
|
1405
1518
|
return 0;
|
|
1406
1519
|
}
|
|
1407
|
-
// -- docs drift -------------------------------------------------------------
|
|
1408
|
-
// Gated on --docs. Runs `meta docs` into a temp dir and diffs the committed docs
|
|
1409
|
-
// tree. See lib/docs-drift.ts for the deliberate differences from --codegen: a byte
|
|
1410
|
-
// difference IS drift here; a committed file a regen would not emit is reported ONLY
|
|
1411
|
-
// under `agent/` and only when it opens with the @generated marker (`docs.outDir` is
|
|
1412
|
-
// otherwise full of files MetaObjects did not write); and a page the project
|
|
1413
|
-
// GIT-IGNORES is exempt, because `docs.outDir` is a directory rather than a namespace
|
|
1414
|
-
// MetaObjects owns.
|
|
1415
|
-
//
|
|
1416
|
-
// It needs a config for the same reason `--codegen` does: `docs.outDir` says which
|
|
1417
|
-
// tree to compare against, and the `agent` surface will not even materialise
|
|
1418
|
-
// without one.
|
|
1419
|
-
async function runDocsVerify() {
|
|
1420
|
-
if (forgeConfig === undefined) {
|
|
1421
|
-
log.error("verify --docs: no metaobjects.config.ts found (or it is invalid) — " +
|
|
1422
|
-
"cannot locate the committed docs tree to diff against. " +
|
|
1423
|
-
"Run 'meta init' to scaffold one, or run without --docs.");
|
|
1424
|
-
return 2;
|
|
1425
|
-
}
|
|
1426
|
-
// Resolved through the SAME resolver `meta docs` uses, so a project that moved its
|
|
1427
|
-
// docs elsewhere is checked where its docs actually are. `outputLayout` is the
|
|
1428
|
-
// documented fallback for `docs.layout`.
|
|
1429
|
-
const docsCfg = resolveDocsConfig(forgeConfig.docs, {}, forgeConfig.outputLayout ?? "flat");
|
|
1430
|
-
let result;
|
|
1431
|
-
try {
|
|
1432
|
-
result = await computeDocsDrift({
|
|
1433
|
-
projectRoot: genConfigDir,
|
|
1434
|
-
docsDir: resolvePath(genConfigDir, docsCfg.outDir),
|
|
1435
|
-
cwd: genConfigDir,
|
|
1436
|
-
});
|
|
1437
|
-
}
|
|
1438
|
-
catch (err) {
|
|
1439
|
-
log.error(`verify --docs: regeneration failed: ${describeError(err)}`);
|
|
1440
|
-
return 1;
|
|
1441
|
-
}
|
|
1442
|
-
if (result.error !== undefined) {
|
|
1443
|
-
log.error(result.error);
|
|
1444
|
-
return 2;
|
|
1445
|
-
}
|
|
1446
|
-
// What the gate did NOT check, on BOTH the passing and the failing line. A gate
|
|
1447
|
-
// that quietly compared two of 589 pages and reported "no drift" would be
|
|
1448
|
-
// indistinguishable from one that compared everything.
|
|
1449
|
-
const notChecked = result.ignoreReason !== undefined
|
|
1450
|
-
? `; .gitignore not consulted — ${result.ignoreReason} — so every page a fresh run emits was expected to be committed`
|
|
1451
|
-
: result.ignored > 0
|
|
1452
|
-
? `; ${result.ignored} git-ignored page(s) not checked`
|
|
1453
|
-
: "";
|
|
1454
|
-
if (result.clean) {
|
|
1455
|
-
// The denominator is what was actually COMPARED, so the passing line and the
|
|
1456
|
-
// failing line below count the same set. A gate whose two halves divide by
|
|
1457
|
-
// different numbers is how `verify --templates` came to report seven templates
|
|
1458
|
-
// vanishing between a red run and a green one.
|
|
1459
|
-
say(`meta verify — ${result.checked} docs page(s) match a fresh 'meta docs' ` +
|
|
1460
|
-
`(no docs drift${notChecked}).`);
|
|
1461
|
-
return 0;
|
|
1462
|
-
}
|
|
1463
|
-
log.error(`meta verify — docs drift (${result.driftedFiles.length} of ${result.checked} page(s) ` +
|
|
1464
|
-
`differ from a fresh 'meta docs'${notChecked}):`);
|
|
1465
|
-
for (const line of result.lines)
|
|
1466
|
-
log.error(` ${line}`);
|
|
1467
|
-
log.error("Run 'meta docs' to regenerate, then commit the result.");
|
|
1468
|
-
// The one-command path to green, named at the point of conviction — a page reported
|
|
1469
|
-
// as uncommitted is the one case where regenerating is NOT the answer.
|
|
1470
|
-
if (result.lines.some((l) => l.startsWith("+ "))) {
|
|
1471
|
-
log.error(result.ignoreReason === undefined
|
|
1472
|
-
? "A page you deliberately do not commit is exempt once it is git-ignored."
|
|
1473
|
-
: `.gitignore was not consulted (${result.ignoreReason}), so a page you ` +
|
|
1474
|
-
`deliberately do not commit is reported above.`);
|
|
1475
|
-
}
|
|
1476
|
-
return 1;
|
|
1477
|
-
}
|
|
1478
1520
|
// -- dependency drift (Task 15, FR-023) --------------------------------------
|
|
1479
1521
|
// Gated on --deps. Re-resolves each declared dependency exactly as `meta deps
|
|
1480
1522
|
// check` does — never part of the bare-verify default (it needs the publisher
|
|
@@ -1561,6 +1603,9 @@ function summarizeDrift(changes) {
|
|
|
1561
1603
|
return `${p.glyph} ${p.noun} ${describeChange(c)}`;
|
|
1562
1604
|
});
|
|
1563
1605
|
}
|
|
1606
|
+
function toSchemaDriftRow(c) {
|
|
1607
|
+
return { kind: c.kind, object: DRIFT_PRESENTATION[c.kind].noun, detail: describeChange(c) };
|
|
1608
|
+
}
|
|
1564
1609
|
/**
|
|
1565
1610
|
* The hand-edit report for `verify --codegen`: a header with the count, one indented line
|
|
1566
1611
|
* per path, then how to see an edit. Exported for its test.
|
|
@@ -1589,7 +1634,6 @@ const GATE_SELECTOR = {
|
|
|
1589
1634
|
templates: "--templates",
|
|
1590
1635
|
schema: "--db <url>",
|
|
1591
1636
|
codegen: "--codegen",
|
|
1592
|
-
docs: "--docs",
|
|
1593
1637
|
deps: "--deps",
|
|
1594
1638
|
replay: "--replay",
|
|
1595
1639
|
};
|
|
@@ -1648,11 +1692,18 @@ function toDiagnosticRow(d, source) {
|
|
|
1648
1692
|
function buildVerifyPayload(input) {
|
|
1649
1693
|
const ran = input.gates.filter((g) => g.ran);
|
|
1650
1694
|
const failed = ran.filter((g) => !g.ok);
|
|
1651
|
-
const
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1695
|
+
const errors = input.errors ?? [];
|
|
1696
|
+
const drifted = failed.filter((g) => !errors.some((e) => e.gate === g.gate));
|
|
1697
|
+
const parts = failed.length === 0
|
|
1698
|
+
? [`${ran.length} gate(s) ran, all clean`]
|
|
1699
|
+
: [
|
|
1700
|
+
...(drifted.length > 0
|
|
1701
|
+
? [`${drifted.length} of ${ran.length} gate(s) failed (${drifted.map((g) => g.gate).join(", ")})`]
|
|
1702
|
+
: []),
|
|
1703
|
+
...(errors.length > 0
|
|
1704
|
+
? [`${errors.length} gate(s) could not run (${errors.map((e) => e.gate).join(", ")})`]
|
|
1705
|
+
: []),
|
|
1706
|
+
];
|
|
1656
1707
|
if (input.antiPatterns.status === "ran" && input.antiPatterns.total > 0) {
|
|
1657
1708
|
// "advisory", not "anti-pattern": the section also carries unindexed foreign keys, a
|
|
1658
1709
|
// missing baseUrl and the other non-scanner rows — `help` breaks the count down by kind.
|
|
@@ -1670,8 +1721,14 @@ function buildVerifyPayload(input) {
|
|
|
1670
1721
|
if (input.deprecations.status === "ran" && input.deprecations.total > 0) {
|
|
1671
1722
|
parts.push(`${input.deprecations.total} deprecated-reference finding(s)`);
|
|
1672
1723
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1724
|
+
if (input.fields.status === "ran" && input.fields.total > 0) {
|
|
1725
|
+
parts.push(`${input.fields.total} field authoring finding(s)`);
|
|
1726
|
+
}
|
|
1727
|
+
const help = errors.map((e) => `the ${e.gate} gate could not run, which is not drift: ${e.error} — fix the connection and re-run`);
|
|
1728
|
+
if (drifted.some((g) => g.gate === "schema") && input.schemaDrift !== undefined) {
|
|
1729
|
+
help.push(`the schema gate's differences are in schemaDrift — changes[] (metadata vs database, one row per change) and findings[] (migration ledger and committed snapshot)`);
|
|
1730
|
+
}
|
|
1731
|
+
if (drifted.some((g) => g.gate !== "schema")) {
|
|
1675
1732
|
help.push(`the failing gate's drift DETAIL is printed as text on stderr — this payload carries the verdict only`);
|
|
1676
1733
|
}
|
|
1677
1734
|
if (input.antiPatterns.total > 0) {
|
|
@@ -1686,16 +1743,21 @@ function buildVerifyPayload(input) {
|
|
|
1686
1743
|
if (input.deprecations.total > 0) {
|
|
1687
1744
|
help.push(`${input.deprecations.total} reference(s) to a deprecated node — see deprecations.rows[]; each names the deprecated target and, where declared, its replacement`);
|
|
1688
1745
|
}
|
|
1746
|
+
if (input.fields.total > 0) {
|
|
1747
|
+
help.push(`${input.fields.total} field authoring finding(s) — see fields.rows[]; each names a reference identity listing a field its object lacks, or a field name declared twice in one children list`);
|
|
1748
|
+
}
|
|
1689
1749
|
if (failed.length === 0 &&
|
|
1690
1750
|
input.antiPatterns.total === 0 &&
|
|
1691
1751
|
input.requirements.total === 0 &&
|
|
1692
1752
|
input.overlays.total === 0 &&
|
|
1693
1753
|
input.names.total === 0 &&
|
|
1694
|
-
input.deprecations.total === 0
|
|
1754
|
+
input.deprecations.total === 0 &&
|
|
1755
|
+
input.fields.total === 0) {
|
|
1695
1756
|
help.push("no drift and nothing advisory to answer — nothing to do");
|
|
1696
1757
|
}
|
|
1697
1758
|
return {
|
|
1698
1759
|
verify: input.gates,
|
|
1760
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
1699
1761
|
exitCode: input.exitCode,
|
|
1700
1762
|
summary: parts.join("; "),
|
|
1701
1763
|
help,
|
|
@@ -1704,12 +1766,14 @@ function buildVerifyPayload(input) {
|
|
|
1704
1766
|
overlays: input.overlays,
|
|
1705
1767
|
names: input.names,
|
|
1706
1768
|
deprecations: input.deprecations,
|
|
1769
|
+
fields: input.fields,
|
|
1707
1770
|
...(input.requirementCounts !== undefined ? { requirementCounts: input.requirementCounts } : {}),
|
|
1771
|
+
...(input.schemaDrift !== undefined ? { schemaDrift: input.schemaDrift } : {}),
|
|
1708
1772
|
// The honest boundary. Everything named here is REACHABLE — it is printed as
|
|
1709
1773
|
// text on stderr — but it is not in this document, and a reader must not have
|
|
1710
1774
|
// to discover that by its absence.
|
|
1711
1775
|
notRepresented: [
|
|
1712
|
-
"per-gate drift detail (which template variable drifted, which
|
|
1776
|
+
"per-gate drift detail for every gate but schema (which template variable drifted, which generated file differs, which migration failed to replay) — printed as text on stderr; this payload carries each gate's pass/fail verdict, and the schema gate's differences in schemaDrift",
|
|
1713
1777
|
"the loader's own warnings and the agent-context/manifest advisories — printed as text on stderr",
|
|
1714
1778
|
],
|
|
1715
1779
|
};
|