@metaobjectsdev/cli 1.0.13 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
package/src/commands/verify.ts
CHANGED
|
@@ -30,10 +30,12 @@ import { scanForRemovedProps, type RemovedPropFinding } from "../lib/removed-pro
|
|
|
30
30
|
import { replayRemedy } from "../lib/replay-remedy.js";
|
|
31
31
|
import { FileProvider, mustacheTagLine } from "../lib/file-provider.js";
|
|
32
32
|
import { derivePayloadFieldTree } from "../lib/payload-field-tree.js";
|
|
33
|
-
import { loadMemoryOptionsFrom, loadMetaobjectsConfig, resolveGenCollection, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
|
|
33
|
+
import { loadMemoryOptionsFrom, loadMetaobjectsConfig, RemovedNamingConfigError, resolveGenCollection, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
|
|
34
34
|
import { collectionLoadOptions } from "../lib/collection-load-options.js";
|
|
35
35
|
import { computeCodegenDrift } from "../lib/codegen-drift.js";
|
|
36
|
-
import {
|
|
36
|
+
import { hasLegacyDocsBlock, legacyDocsConfigMessage, REMOVED_VERIFY_DOCS } from "../lib/docs-removed.js";
|
|
37
|
+
import { buildAgentSchemaInput } from "../lib/agent-schema.js";
|
|
38
|
+
import { sourceRootsOf } from "./gen.js";
|
|
37
39
|
import {
|
|
38
40
|
checkRequirements, summariseRequirements, scanRequirements, type Diagnostic,
|
|
39
41
|
} from "../lib/requirement-check.js";
|
|
@@ -41,8 +43,9 @@ import { lintRequirements } from "../lib/requirement-lint.js";
|
|
|
41
43
|
import { lintOverlays } from "../lib/overlay-lint.js";
|
|
42
44
|
import { lintNodeNames } from "../lib/name-lint.js";
|
|
43
45
|
import { lintDeprecatedReferences } from "../lib/deprecation-lint.js";
|
|
46
|
+
import { lintDuplicateFields, lintReferenceFields } from "../lib/field-lint.js";
|
|
44
47
|
import { FileSource } from "@metaobjectsdev/metadata/core";
|
|
45
|
-
import { resolveD1Config, resolveMigrateConfig } from "../lib/config.js";
|
|
48
|
+
import { resolveD1Config, resolveMigrateConfig, type ResolvedMigrateConfig } from "../lib/config.js";
|
|
46
49
|
import {
|
|
47
50
|
buildWranglerExecuteArgs,
|
|
48
51
|
defaultWranglerRunner,
|
|
@@ -50,7 +53,7 @@ import {
|
|
|
50
53
|
type WranglerRunner,
|
|
51
54
|
} from "../lib/wrangler.js";
|
|
52
55
|
import type { MetaobjectsGenConfig } from "@metaobjectsdev/codegen-ts";
|
|
53
|
-
import { buildProjectionViews
|
|
56
|
+
import { buildProjectionViews } from "@metaobjectsdev/codegen-ts";
|
|
54
57
|
import { buildKyselyFromUrl, inferDialect, type Dialect } from "../lib/kysely.js";
|
|
55
58
|
import { tokensToAllowOptions, describeChange } from "../lib/allow.js";
|
|
56
59
|
import {
|
|
@@ -69,6 +72,7 @@ import {
|
|
|
69
72
|
verifyReplay,
|
|
70
73
|
introspect,
|
|
71
74
|
diff,
|
|
75
|
+
withoutViewRecreates,
|
|
72
76
|
readSnapshot,
|
|
73
77
|
snapshotPath,
|
|
74
78
|
type SchemaSnapshot,
|
|
@@ -80,6 +84,8 @@ import {
|
|
|
80
84
|
type D1Binding,
|
|
81
85
|
type D1Runner,
|
|
82
86
|
type DriftResult,
|
|
87
|
+
postgresViewReplaceProbe,
|
|
88
|
+
withoutFlywayHistory,
|
|
83
89
|
} from "@metaobjectsdev/migrate-ts";
|
|
84
90
|
import {
|
|
85
91
|
DEFAULT_METAOBJECTS_DIR,
|
|
@@ -95,6 +101,7 @@ import {
|
|
|
95
101
|
TEMPLATE_SUBTYPE_PROMPT,
|
|
96
102
|
TEMPLATE_SUBTYPE_OUTPUT,
|
|
97
103
|
TEMPLATE_ATTR_PAYLOAD_REF,
|
|
104
|
+
refTarget,
|
|
98
105
|
TEMPLATE_ATTR_TEXT_REF,
|
|
99
106
|
TEMPLATE_ATTR_REQUIRED_SLOTS,
|
|
100
107
|
TEMPLATE_ATTR_REQUIRED_TAGS,
|
|
@@ -105,6 +112,7 @@ import {
|
|
|
105
112
|
TEMPLATE_ATTR_HTML_BODY_REF,
|
|
106
113
|
TEMPLATE_ATTR_TEXT_BODY_REF,
|
|
107
114
|
REQUIREMENT_STATUSES,
|
|
115
|
+
physicalNamingOf,
|
|
108
116
|
} from "@metaobjectsdev/metadata";
|
|
109
117
|
import { verify, ERR_REQUIRED_SLOT_UNUSED, ERR_PARTIAL_UNRESOLVED } from "@metaobjectsdev/render";
|
|
110
118
|
import { describeLoadError, reportLoadError } from "../lib/load-error.js";
|
|
@@ -119,9 +127,11 @@ const ERR_UNKNOWN_ATTR = "ERR_UNKNOWN_ATTR";
|
|
|
119
127
|
|
|
120
128
|
/**
|
|
121
129
|
* A no-flags MigrateFlags, so `resolveMigrateConfig` yields exactly what `meta migrate`
|
|
122
|
-
* would use with nothing passed on the command line — config value, else default.
|
|
130
|
+
* would use with nothing passed on the command line — config value, else default. The one
|
|
131
|
+
* migrate flag verify also takes, `--migration-format`, is laid over it per invocation, so
|
|
132
|
+
* the format resolves flag > config > default exactly as it does for `meta migrate`.
|
|
123
133
|
*
|
|
124
|
-
* verify consumes `outDir` (#292) and — for the replay gate ONLY — `dialect`. The #292
|
|
134
|
+
* verify consumes `outDir` (#292), `format` and — for the replay gate ONLY — `dialect`. The #292
|
|
125
135
|
* restriction that reading anything else "would be reaching into migrate's decisions"
|
|
126
136
|
* was written about the DRIFT gate, whose dialect comes from the live `--db` URL. The
|
|
127
137
|
* replay gate has no `--db` at all, and the dialect a committed chain was EMITTED for
|
|
@@ -172,6 +182,15 @@ export async function verifyCommand(
|
|
|
172
182
|
emitStructured({ error: msg, hint: "run `meta verify --help` for the accepted flags" }, fmt);
|
|
173
183
|
return 2;
|
|
174
184
|
}
|
|
185
|
+
// ADR-0034 Amendment 9: documentation is checked by `--codegen` with everything else the
|
|
186
|
+
// generator list declares. The old flag still parses, so it can say where it went. It says
|
|
187
|
+
// so once, as plain text on stderr, like every other removed command: a structured copy on
|
|
188
|
+
// stdout printed the same sentence a second time, escaped (`error: "\`meta verify ...`).
|
|
189
|
+
if (flags.docs) {
|
|
190
|
+
log.error(REMOVED_VERIFY_DOCS);
|
|
191
|
+
return 2;
|
|
192
|
+
}
|
|
193
|
+
const migrateFlags: MigrateFlags = { ...EMPTY_MIGRATE_FLAGS, format: flags.migrationFormat };
|
|
175
194
|
|
|
176
195
|
// ADR-0021 D2 — explicit verify subverbs. Each flag selects one drift mode;
|
|
177
196
|
// any combination runs each and the overall exit code is the MAX (non-zero on
|
|
@@ -182,16 +201,15 @@ export async function verifyCommand(
|
|
|
182
201
|
// The schema gate is selected by the presence of --db, or --dialect d1 (#225 —
|
|
183
202
|
// D1 has no URL connection); that check lives inside runSchemaVerify.
|
|
184
203
|
const runCodegen = flags.codegen;
|
|
185
|
-
|
|
186
|
-
// Task 15 — same shape as --codegen/--docs: selected ONLY by its own flag,
|
|
204
|
+
// Task 15 — same shape as --codegen: selected ONLY by its own flag,
|
|
187
205
|
// never folded into the bare-verify default (it needs the publisher
|
|
188
206
|
// reachable, which CI may not have — see the VerifyFlags doc on `deps`).
|
|
189
207
|
const runDeps = flags.deps;
|
|
190
208
|
if (!flags.anyExplicit) {
|
|
191
209
|
say(
|
|
192
210
|
"meta verify — running --templates (default). Explicit subverbs: " +
|
|
193
|
-
"--templates (prompt drift), --db/--dialect d1 (schema drift),
|
|
194
|
-
"--
|
|
211
|
+
"--templates (prompt drift), --db/--dialect d1 (schema drift), " +
|
|
212
|
+
"--codegen (drift of everything `generators` declares, documentation included), --deps (dependency drift), " +
|
|
195
213
|
"--replay/--replay-snapshot (the committed migration chain replays from empty).",
|
|
196
214
|
);
|
|
197
215
|
}
|
|
@@ -259,7 +277,13 @@ export async function verifyCommand(
|
|
|
259
277
|
let forgeConfig: MetaobjectsGenConfig | undefined;
|
|
260
278
|
try {
|
|
261
279
|
forgeConfig = await loadMetaobjectsConfig(genConfigDir);
|
|
262
|
-
} catch {
|
|
280
|
+
} catch (e) {
|
|
281
|
+
// ADR-0058: the one config problem verify cannot fall back from — the default column
|
|
282
|
+
// rule would report every column of a database built with the old value as drift.
|
|
283
|
+
if (e instanceof RemovedNamingConfigError) {
|
|
284
|
+
log.error(e.message);
|
|
285
|
+
return 2;
|
|
286
|
+
}
|
|
263
287
|
forgeConfig = undefined;
|
|
264
288
|
}
|
|
265
289
|
// Both of the gen config's contributions to the load, together — see
|
|
@@ -360,6 +384,8 @@ export async function verifyCommand(
|
|
|
360
384
|
// #305 — the deprecated-reference authoring lint. Its own section, same reason.
|
|
361
385
|
let deprecationSection: AdvisorySection<AdvisoryDiagnosticRow> =
|
|
362
386
|
skippedSection("the deprecated-reference lint did not run");
|
|
387
|
+
let fieldSection: AdvisorySection<AdvisoryDiagnosticRow> =
|
|
388
|
+
skippedSection("the field lint did not run");
|
|
363
389
|
// The ledger counts `meta verify` prints on every run. Undefined for a project
|
|
364
390
|
// declaring no requirement.* node at all (opt-in by declaration) — the payload
|
|
365
391
|
// then omits the block rather than reporting zeroes that would read as an empty
|
|
@@ -376,9 +402,14 @@ export async function verifyCommand(
|
|
|
376
402
|
// subverb so ANY kind of drift fails CI. Each gate only runs when its mode is
|
|
377
403
|
// selected; an unselected gate contributes 0.
|
|
378
404
|
const templateExit = runTemplates ? runTemplateVerify() : 0;
|
|
405
|
+
// Set when the schema gate could not reach or read the database: a failure, but not
|
|
406
|
+
// drift, and the payload must not report it as drift.
|
|
407
|
+
let schemaRunError: string | undefined;
|
|
408
|
+
// The schema gate's findings, for the structured payload: what differs, not only that
|
|
409
|
+
// something does. Left undefined when the gate did not compare anything.
|
|
410
|
+
let schemaDrift: SchemaDriftSection | undefined;
|
|
379
411
|
const schemaExit = await runSchemaVerify();
|
|
380
412
|
const codegenExit = runCodegen ? await runCodegenVerify() : 0;
|
|
381
|
-
const docsExit = runDocs ? await runDocsVerify() : 0;
|
|
382
413
|
const depsExit = runDeps ? await runDepsVerify() : 0;
|
|
383
414
|
// Requirements have no subverb: `requirement.*` nodes are metadata, so they
|
|
384
415
|
// are checked on every `meta verify`. Opt-in by DECLARATION — a model with no
|
|
@@ -405,6 +436,11 @@ export async function verifyCommand(
|
|
|
405
436
|
// warnings ONLY, never changes the exit code.
|
|
406
437
|
runDeprecationLintAdvisory();
|
|
407
438
|
|
|
439
|
+
// A reference identity over a field the object lacks, and a field name declared
|
|
440
|
+
// twice in one children list — both load clean on every port. Runs on every
|
|
441
|
+
// `meta verify`; warnings ONLY, never changes the exit code.
|
|
442
|
+
await runFieldLintAdvisory();
|
|
443
|
+
|
|
408
444
|
// Advisory verify-as-teacher pass: surface hand-rolled work the metadata could
|
|
409
445
|
// model. Warnings ONLY — never changes the exit code (bias to under-flagging).
|
|
410
446
|
// Suppressed with --no-antipatterns or META_NO_ANTIPATTERNS=1 for the rare
|
|
@@ -415,7 +451,6 @@ export async function verifyCommand(
|
|
|
415
451
|
templateExit,
|
|
416
452
|
schemaExit,
|
|
417
453
|
codegenExit,
|
|
418
|
-
docsExit,
|
|
419
454
|
depsExit,
|
|
420
455
|
requirementExit,
|
|
421
456
|
replayExit,
|
|
@@ -429,7 +464,6 @@ export async function verifyCommand(
|
|
|
429
464
|
// failure mode this whole change exists to remove.
|
|
430
465
|
{ gate: "schema", ran: ranSchemaGate, ok: schemaExit === 0 },
|
|
431
466
|
{ gate: "codegen", ran: runCodegen, ok: codegenExit === 0 },
|
|
432
|
-
{ gate: "docs", ran: runDocs, ok: docsExit === 0 },
|
|
433
467
|
{ gate: "deps", ran: runDeps, ok: depsExit === 0 },
|
|
434
468
|
{ gate: "requirements", ran: true, ok: requirementExit === 0 },
|
|
435
469
|
{ gate: "replay", ran: flags.replay || flags.replaySnapshot, ok: replayExit === 0 },
|
|
@@ -455,6 +489,9 @@ export async function verifyCommand(
|
|
|
455
489
|
overlays: overlaySection,
|
|
456
490
|
names: nameSection,
|
|
457
491
|
deprecations: deprecationSection,
|
|
492
|
+
fields: fieldSection,
|
|
493
|
+
...(schemaDrift !== undefined ? { schemaDrift } : {}),
|
|
494
|
+
errors: schemaRunError !== undefined ? [{ gate: "schema", error: schemaRunError }] : [],
|
|
458
495
|
}),
|
|
459
496
|
fmt,
|
|
460
497
|
);
|
|
@@ -483,7 +520,7 @@ export async function verifyCommand(
|
|
|
483
520
|
// Resolve the migrations directory and the chain's dialect through MIGRATE's own
|
|
484
521
|
// precedence, never a second derivation — verify must not look somewhere migrate
|
|
485
522
|
// does not write, nor assume a dialect the chain was not emitted for.
|
|
486
|
-
const migrateConfig = await resolveMigrateConfig(
|
|
523
|
+
const migrateConfig = await resolveMigrateConfig(migrateFlags, projectRoot);
|
|
487
524
|
|
|
488
525
|
if (migrateConfig.format === "flyway") {
|
|
489
526
|
log.error(
|
|
@@ -624,11 +661,9 @@ export async function verifyCommand(
|
|
|
624
661
|
// passes no `governed` and gets the comparison exactly as it was.
|
|
625
662
|
let governed: GovernedScope | undefined;
|
|
626
663
|
if (schemaScope !== undefined) {
|
|
627
|
-
const viewStrategy = forgeConfig?.columnNamingStrategy ?? "snake_case";
|
|
628
664
|
const built = buildExpectedSchemaWithProvenance(root, {
|
|
629
665
|
dialect,
|
|
630
|
-
|
|
631
|
-
views: buildProjectionViews(root, { dialect, columnNamingStrategy: viewStrategy }),
|
|
666
|
+
views: buildProjectionViews(root, { dialect }),
|
|
632
667
|
});
|
|
633
668
|
governed = scopeExpectedSchema(built, schemaScope, importedOption(collection));
|
|
634
669
|
}
|
|
@@ -671,7 +706,14 @@ export async function verifyCommand(
|
|
|
671
706
|
// ONE scan for all three passes. The gate and the summary each used to walk the
|
|
672
707
|
// model AND resolve every @implementedBy claim for themselves — the resolution
|
|
673
708
|
// being the expensive half — and the lint added a third walk on top.
|
|
674
|
-
|
|
709
|
+
// The strict switch (ADR-0057) is decided here, once, and travels on the scan: the
|
|
710
|
+
// flag or META_REQUIRE_IMPLEMENTERS=1 — a flag-or-variable pair, as
|
|
711
|
+
// --no-requirement-lint has below, so a CI job can turn it on without editing a
|
|
712
|
+
// command line it shares with local runs.
|
|
713
|
+
const scan = scanRequirements(root, {
|
|
714
|
+
coverable: collection.inCoverage,
|
|
715
|
+
requireImplementers: flags.requireImplementers || process.env.META_REQUIRE_IMPLEMENTERS === "1",
|
|
716
|
+
});
|
|
675
717
|
const diags = [...checkRequirements(root, scan)];
|
|
676
718
|
|
|
677
719
|
// Printed on EVERY run, clean or not — a gate that says nothing when it
|
|
@@ -852,6 +894,41 @@ export async function verifyCommand(
|
|
|
852
894
|
}
|
|
853
895
|
}
|
|
854
896
|
|
|
897
|
+
// -- field authoring lint (advisory) ----------------------------------------
|
|
898
|
+
// Reads the LOADED model for the reference half and the project's OWN raw files
|
|
899
|
+
// for the duplicate half (see field-lint.ts) — a dependency artifact is not the
|
|
900
|
+
// adopter's to edit. Its own section, its own cap, and a skip that carries its
|
|
901
|
+
// reason rather than looking like a clean scan.
|
|
902
|
+
async function runFieldLintAdvisory(): Promise<void> {
|
|
903
|
+
if (flags.noFieldLint) {
|
|
904
|
+
fieldSection = skippedSection("suppressed by --no-field-lint");
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
if (process.env.META_NO_FIELD_LINT === "1") {
|
|
908
|
+
fieldSection = skippedSection("suppressed by META_NO_FIELD_LINT=1");
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
let findings: Diagnostic[];
|
|
912
|
+
try {
|
|
913
|
+
findings = [
|
|
914
|
+
...lintReferenceFields(root),
|
|
915
|
+
...(await lintDuplicateFields(collection.ownFiles, (path) => new FileSource(path))),
|
|
916
|
+
];
|
|
917
|
+
} catch (err) {
|
|
918
|
+
// Never let an advisory scan break verify — and never report it as clean.
|
|
919
|
+
fieldSection = skippedSection(`the field lint failed: ${describeError(err)}`);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
fieldSection = ranSection(findings.map((d) => toDiagnosticRow(d, "lint")));
|
|
923
|
+
if (findings.length > 0) {
|
|
924
|
+
log.warn(
|
|
925
|
+
`meta verify — fields: ${findings.length} authoring warning(s) ` +
|
|
926
|
+
`(advisory — does not fail the build):`,
|
|
927
|
+
);
|
|
928
|
+
warnCapped(findings.map(formatDiagnostic), flags.limit, { structured });
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
855
932
|
// -- deprecated-reference authoring lint (advisory) -------------------------
|
|
856
933
|
// #305 — reads the LOADED model (see deprecation-lint.ts). Its own section,
|
|
857
934
|
// its own cap, and a skip that carries its reason rather than looking clean.
|
|
@@ -942,7 +1019,6 @@ export async function verifyCommand(
|
|
|
942
1019
|
if (scanDialect !== "mysql") {
|
|
943
1020
|
unindexedFks = scanForUnindexedForeignKeys(root, {
|
|
944
1021
|
dialect: scanDialect,
|
|
945
|
-
columnNamingStrategy: forgeConfig?.columnNamingStrategy ?? "snake_case",
|
|
946
1022
|
skip: collection.imported,
|
|
947
1023
|
});
|
|
948
1024
|
}
|
|
@@ -1059,7 +1135,7 @@ export async function verifyCommand(
|
|
|
1059
1135
|
|
|
1060
1136
|
// @payloadRef must resolve to a loaded object.value (drives the field tree
|
|
1061
1137
|
// every render-engine drift check runs against).
|
|
1062
|
-
const fieldTree = derivePayloadFieldTree(
|
|
1138
|
+
const fieldTree = derivePayloadFieldTree(refTarget(tmpl, TEMPLATE_ATTR_PAYLOAD_REF));
|
|
1063
1139
|
if (fieldTree.length === 0) {
|
|
1064
1140
|
log.error(
|
|
1065
1141
|
`[${tmpl.name}] (${tmpl.subType}) ${ERR_PARTIAL_UNRESOLVED}: ` +
|
|
@@ -1180,11 +1256,9 @@ export async function verifyCommand(
|
|
|
1180
1256
|
// consults it.
|
|
1181
1257
|
const scopeMismatch = migrateScopeMismatch(collection, () => {
|
|
1182
1258
|
const dialect: Dialect = usingD1 ? "d1" : (flags.dialect ?? inferDialect(flags.db as string));
|
|
1183
|
-
const viewStrategy = forgeConfig?.columnNamingStrategy ?? "snake_case";
|
|
1184
1259
|
return buildExpectedSchemaWithProvenance(root, {
|
|
1185
1260
|
dialect,
|
|
1186
|
-
|
|
1187
|
-
views: buildProjectionViews(root, { dialect, columnNamingStrategy: viewStrategy }),
|
|
1261
|
+
views: buildProjectionViews(root, { dialect }),
|
|
1188
1262
|
}).provenance;
|
|
1189
1263
|
});
|
|
1190
1264
|
if (scopeMismatch !== undefined) {
|
|
@@ -1225,16 +1299,21 @@ export async function verifyCommand(
|
|
|
1225
1299
|
try {
|
|
1226
1300
|
kysely = await buildKyselyFromUrl(flags.db as string, flags.dialect as Dialect | undefined);
|
|
1227
1301
|
} catch (err) {
|
|
1228
|
-
|
|
1302
|
+
schemaRunError = describeError(err);
|
|
1303
|
+
log.error(`verify: ${schemaRunError}`);
|
|
1229
1304
|
return 1;
|
|
1230
1305
|
}
|
|
1231
1306
|
|
|
1232
1307
|
try {
|
|
1233
1308
|
const allow = tokensToAllowOptions(flags.allow);
|
|
1309
|
+
// Resolve the migrate config once for this whole path: `tryLoadConfig` is uncached,
|
|
1310
|
+
// and the flyway filter below and checkCommittedSnapshot's migrations-dir read want
|
|
1311
|
+
// the same value — resolving twice would read and parse `.metaobjects/config.json`
|
|
1312
|
+
// twice per `meta verify --db`.
|
|
1313
|
+
const migrateConfig = await resolveMigrateConfig(migrateFlags, projectRoot);
|
|
1234
1314
|
// Expected views from the single view-SQL source (codegen-ts), so view-body
|
|
1235
1315
|
// drift is detected against the live DB.
|
|
1236
|
-
const
|
|
1237
|
-
const expectedViews = buildProjectionViews(root, { dialect: kysely.dialect, columnNamingStrategy: viewStrategy });
|
|
1316
|
+
const expectedViews = buildProjectionViews(root, { dialect: kysely.dialect });
|
|
1238
1317
|
let driftResult;
|
|
1239
1318
|
let actual: SchemaSnapshot;
|
|
1240
1319
|
try {
|
|
@@ -1242,22 +1321,29 @@ export async function verifyCommand(
|
|
|
1242
1321
|
// `actual` this drift comparison uses, and re-introspecting for it would both
|
|
1243
1322
|
// cost a second round trip and open a window where the two could disagree.
|
|
1244
1323
|
actual = await introspect(kysely.db, kysely.dialect);
|
|
1324
|
+
// Flyway owns its history table; it is no drift under the flyway format, as in `meta migrate`.
|
|
1325
|
+
if (migrateConfig.format === "flyway") {
|
|
1326
|
+
actual = withoutFlywayHistory(actual);
|
|
1327
|
+
}
|
|
1245
1328
|
driftResult = await computeDriftFromActual(actual, kysely.dialect, root, {
|
|
1246
1329
|
allow,
|
|
1247
1330
|
views: expectedViews,
|
|
1331
|
+
// The same question `meta migrate` asks, so the report names the change it plans.
|
|
1332
|
+
...(kysely.dialect === "postgres" ? { viewReplaceProbe: postgresViewReplaceProbe(kysely.db) } : {}),
|
|
1248
1333
|
...(schemaScope !== undefined ? { inScope: schemaScope } : {}),
|
|
1249
1334
|
// The import exclusion reaches scopeExpectedSchema through this option bag —
|
|
1250
1335
|
// there is no direct call on this path to attach it to.
|
|
1251
1336
|
...importedOption(collection),
|
|
1252
1337
|
});
|
|
1253
1338
|
} catch (err) {
|
|
1254
|
-
|
|
1339
|
+
schemaRunError = `failed to introspect ${kysely.displayUrl}: ${describeError(err)}`;
|
|
1340
|
+
log.error(`verify: ${schemaRunError}`);
|
|
1255
1341
|
return 1;
|
|
1256
1342
|
}
|
|
1257
1343
|
|
|
1258
1344
|
const snapshotDrift =
|
|
1259
1345
|
driftResult.changes.length === 0
|
|
1260
|
-
? await checkCommittedSnapshot(actual, kysely.dialect, kysely.displayUrl, driftResult)
|
|
1346
|
+
? await checkCommittedSnapshot(actual, kysely.dialect, kysely.displayUrl, driftResult, migrateConfig)
|
|
1261
1347
|
: [];
|
|
1262
1348
|
|
|
1263
1349
|
return reportSchemaDrift(driftResult, [...ledgerDrift, ...snapshotDrift], kysely.displayUrl);
|
|
@@ -1327,8 +1413,7 @@ export async function verifyCommand(
|
|
|
1327
1413
|
}
|
|
1328
1414
|
|
|
1329
1415
|
const allow = tokensToAllowOptions(flags.allow);
|
|
1330
|
-
const
|
|
1331
|
-
const expectedViews = buildProjectionViews(root, { dialect: "d1", columnNamingStrategy: viewStrategy });
|
|
1416
|
+
const expectedViews = buildProjectionViews(root, { dialect: "d1" });
|
|
1332
1417
|
let driftResult;
|
|
1333
1418
|
try {
|
|
1334
1419
|
driftResult = await computeDriftFromActual(actual, "d1", root, {
|
|
@@ -1382,13 +1467,15 @@ export async function verifyCommand(
|
|
|
1382
1467
|
dialect: Dialect,
|
|
1383
1468
|
displayUrl: string,
|
|
1384
1469
|
governed: GovernedScope,
|
|
1470
|
+
// Resolved by the caller and threaded through — see the `verify --db` branch: the same
|
|
1471
|
+
// run's flyway filter reads this config too, and `tryLoadConfig` does not cache.
|
|
1472
|
+
migrateConfig: ResolvedMigrateConfig,
|
|
1385
1473
|
): Promise<string[]> {
|
|
1386
1474
|
if (dialect === "d1") return []; // d1 keeps migrations Wrangler-native; no offline snapshot
|
|
1387
|
-
//
|
|
1388
|
-
//
|
|
1389
|
-
//
|
|
1390
|
-
// migrate's business.
|
|
1391
|
-
const migrateConfig = await resolveMigrateConfig(EMPTY_MIGRATE_FLAGS, projectRoot);
|
|
1475
|
+
// The migrations dir comes from migrate's OWN precedence (flag > config > default)
|
|
1476
|
+
// rather than a re-derivation here, so verify can never look somewhere migrate does
|
|
1477
|
+
// not write. Only `outDir` and `format` are consumed; the rest of the resolved config
|
|
1478
|
+
// is migrate's business.
|
|
1392
1479
|
const dir = resolvePath(projectRoot, migrateConfig.outDir);
|
|
1393
1480
|
let snapshot: SchemaSnapshot | null;
|
|
1394
1481
|
try {
|
|
@@ -1397,6 +1484,7 @@ export async function verifyCommand(
|
|
|
1397
1484
|
return [];
|
|
1398
1485
|
}
|
|
1399
1486
|
if (snapshot === null) return [];
|
|
1487
|
+
if (migrateConfig.format === "flyway") snapshot = withoutFlywayHistory(snapshot);
|
|
1400
1488
|
|
|
1401
1489
|
// Out-of-scope objects leave BOTH sides of this comparison, and the schema pin
|
|
1402
1490
|
// comes from the scope decision the DRIFT comparison already made — one door
|
|
@@ -1421,7 +1509,9 @@ export async function verifyCommand(
|
|
|
1421
1509
|
// MERGED with the out-of-scope set, and a second key would silently drop that half.
|
|
1422
1510
|
dialect,
|
|
1423
1511
|
});
|
|
1424
|
-
|
|
1512
|
+
// A view recreated only around a table change matches the snapshot (D3) — not a difference.
|
|
1513
|
+
const changes = withoutViewRecreates(result.changes);
|
|
1514
|
+
if (changes.length === 0) return [];
|
|
1425
1515
|
|
|
1426
1516
|
return [
|
|
1427
1517
|
// `meta migrate --from-db` is NOT the repair: it writes a snapshot only when it has
|
|
@@ -1431,10 +1521,10 @@ export async function verifyCommand(
|
|
|
1431
1521
|
// been told everything is in sync. `baseline --from-db` rewrites it unconditionally,
|
|
1432
1522
|
// which is the whole point of the subcommand.
|
|
1433
1523
|
`the committed schema snapshot disagrees with ${displayUrl} ` +
|
|
1434
|
-
`(${
|
|
1524
|
+
`(${changes.length} difference(s)) — the next 'meta migrate' would emit DDL from it ` +
|
|
1435
1525
|
`and fail at apply. Re-derive it with ` +
|
|
1436
1526
|
`'meta migrate baseline --from-db --db <url> --dialect ${dialect}'.`,
|
|
1437
|
-
...summarizeDrift(
|
|
1527
|
+
...summarizeDrift(changes),
|
|
1438
1528
|
];
|
|
1439
1529
|
}
|
|
1440
1530
|
|
|
@@ -1458,6 +1548,7 @@ export async function verifyCommand(
|
|
|
1458
1548
|
}
|
|
1459
1549
|
|
|
1460
1550
|
const changes = driftResult.changes;
|
|
1551
|
+
schemaDrift = { changes: changes.map(toSchemaDriftRow), findings: ledgerDrift };
|
|
1461
1552
|
if (changes.length === 0 && ledgerDrift.length === 0) {
|
|
1462
1553
|
say(`meta verify — schema in sync with ${displayUrl}.`);
|
|
1463
1554
|
return 0;
|
|
@@ -1517,13 +1608,28 @@ export async function verifyCommand(
|
|
|
1517
1608
|
// and the loader's own remedies. It printed a bare message plus a hand-rolled
|
|
1518
1609
|
// suggestions read — the half-true rule this file's sibling comment warns about.
|
|
1519
1610
|
reportLoadError(log, "verify --codegen: failed to load this package's metadata", err);
|
|
1520
|
-
return
|
|
1611
|
+
return 1;
|
|
1521
1612
|
}
|
|
1522
1613
|
}
|
|
1523
1614
|
|
|
1615
|
+
// A 1.0 `docs` block configured a command that no longer exists; regenerating without it
|
|
1616
|
+
// would check no documentation and pass. Refused before anything is written, as `meta gen`
|
|
1617
|
+
// refuses it.
|
|
1618
|
+
if (hasLegacyDocsBlock(forgeConfig)) {
|
|
1619
|
+
log.error(legacyDocsConfigMessage("meta verify"));
|
|
1620
|
+
return 2;
|
|
1621
|
+
}
|
|
1622
|
+
const config = forgeConfig;
|
|
1524
1623
|
let result;
|
|
1525
1624
|
try {
|
|
1526
|
-
result = await computeCodegenDrift(
|
|
1625
|
+
result = await computeCodegenDrift(config, codegenRoot, genConfigDir, genCollection.inScope, genCollection.ownFiles, {
|
|
1626
|
+
sourceRoots: sourceRootsOf(genCollection),
|
|
1627
|
+
agentSchema: () => buildAgentSchemaInput(
|
|
1628
|
+
codegenRoot,
|
|
1629
|
+
config.dialect,
|
|
1630
|
+
(m) => log.warn(`verify --codegen: [agent-docs] ${m}`),
|
|
1631
|
+
),
|
|
1632
|
+
});
|
|
1527
1633
|
} catch (err) {
|
|
1528
1634
|
log.error(`verify --codegen: regeneration failed: ${describeError(err)}`);
|
|
1529
1635
|
return 1;
|
|
@@ -1539,6 +1645,17 @@ export async function verifyCommand(
|
|
|
1539
1645
|
// contribution is current" and nothing else. Reported on every run; fails the build
|
|
1540
1646
|
// only under --forbid-hand-edits.
|
|
1541
1647
|
const handEditExit = reportHandEdits(result.handEdited);
|
|
1648
|
+
// Say how much documentation was NOT checked, on the pass and the fail line alike, so a
|
|
1649
|
+
// gate that compared two pages of 589 cannot read as one that compared all of them.
|
|
1650
|
+
if (result.docsIgnored > 0) {
|
|
1651
|
+
say(`meta verify — ${result.docsIgnored} generated documentation page(s) are git-ignored and were not checked.`);
|
|
1652
|
+
}
|
|
1653
|
+
for (const dir of result.docsIgnoredDirs) {
|
|
1654
|
+
say(`meta verify — ${dir}: the repository git-ignores every documentation page generated there, so they were not compared.`);
|
|
1655
|
+
}
|
|
1656
|
+
if (result.docsIgnoreUnavailable !== undefined) {
|
|
1657
|
+
say(`meta verify — git could not say which documentation pages are ignored (${result.docsIgnoreUnavailable}), so every page was checked.`);
|
|
1658
|
+
}
|
|
1542
1659
|
|
|
1543
1660
|
if (result.clean) {
|
|
1544
1661
|
// Says what it CHECKED, not more. Since 0.24.3 this gate asks whether each file's
|
|
@@ -1558,9 +1675,18 @@ export async function verifyCommand(
|
|
|
1558
1675
|
// it MERGES the edit and exits 0 — so prescribing it there was the unbreakable loop
|
|
1559
1676
|
// the hand-edit exemption exists to end, reintroduced for the one file the exemption
|
|
1560
1677
|
// deliberately excludes. Both lines print when the drift set holds both kinds.
|
|
1561
|
-
if (result.driftedFiles.length > result.handEditedNames.length) {
|
|
1678
|
+
if (result.driftedFiles.length > result.handEditedNames.length + result.editedOrphans.length) {
|
|
1562
1679
|
log.error("Run 'meta gen' to regenerate, then commit the result.");
|
|
1563
1680
|
}
|
|
1681
|
+
// A hand-edited orphan is the other kind `meta gen` cannot fix: it deletes an untouched
|
|
1682
|
+
// file no generator emits any more, and refuses an edited one.
|
|
1683
|
+
if (result.editedOrphans.length > 0) {
|
|
1684
|
+
log.error(
|
|
1685
|
+
"For the file(s) above that no generator emits any more and that were edited by hand, " +
|
|
1686
|
+
"'meta gen' will NOT delete them. Delete each one, or move your edits into a file of " +
|
|
1687
|
+
"your own first.",
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1564
1690
|
if (result.handEditedNames.length > 0) {
|
|
1565
1691
|
log.error(
|
|
1566
1692
|
"For the names artifact(s) above, 'meta gen' will NOT help — it preserves a hand " +
|
|
@@ -1588,90 +1714,6 @@ export async function verifyCommand(
|
|
|
1588
1714
|
return 0;
|
|
1589
1715
|
}
|
|
1590
1716
|
|
|
1591
|
-
// -- docs drift -------------------------------------------------------------
|
|
1592
|
-
// Gated on --docs. Runs `meta docs` into a temp dir and diffs the committed docs
|
|
1593
|
-
// tree. See lib/docs-drift.ts for the deliberate differences from --codegen: a byte
|
|
1594
|
-
// difference IS drift here; a committed file a regen would not emit is reported ONLY
|
|
1595
|
-
// under `agent/` and only when it opens with the @generated marker (`docs.outDir` is
|
|
1596
|
-
// otherwise full of files MetaObjects did not write); and a page the project
|
|
1597
|
-
// GIT-IGNORES is exempt, because `docs.outDir` is a directory rather than a namespace
|
|
1598
|
-
// MetaObjects owns.
|
|
1599
|
-
//
|
|
1600
|
-
// It needs a config for the same reason `--codegen` does: `docs.outDir` says which
|
|
1601
|
-
// tree to compare against, and the `agent` surface will not even materialise
|
|
1602
|
-
// without one.
|
|
1603
|
-
async function runDocsVerify(): Promise<number> {
|
|
1604
|
-
if (forgeConfig === undefined) {
|
|
1605
|
-
log.error(
|
|
1606
|
-
"verify --docs: no metaobjects.config.ts found (or it is invalid) — " +
|
|
1607
|
-
"cannot locate the committed docs tree to diff against. " +
|
|
1608
|
-
"Run 'meta init' to scaffold one, or run without --docs.",
|
|
1609
|
-
);
|
|
1610
|
-
return 2;
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
// Resolved through the SAME resolver `meta docs` uses, so a project that moved its
|
|
1614
|
-
// docs elsewhere is checked where its docs actually are. `outputLayout` is the
|
|
1615
|
-
// documented fallback for `docs.layout`.
|
|
1616
|
-
const docsCfg = resolveDocsConfig(forgeConfig.docs, {}, forgeConfig.outputLayout ?? "flat");
|
|
1617
|
-
let result;
|
|
1618
|
-
try {
|
|
1619
|
-
result = await computeDocsDrift({
|
|
1620
|
-
projectRoot: genConfigDir,
|
|
1621
|
-
docsDir: resolvePath(genConfigDir, docsCfg.outDir),
|
|
1622
|
-
cwd: genConfigDir,
|
|
1623
|
-
});
|
|
1624
|
-
} catch (err) {
|
|
1625
|
-
log.error(`verify --docs: regeneration failed: ${describeError(err)}`);
|
|
1626
|
-
return 1;
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
if (result.error !== undefined) {
|
|
1630
|
-
log.error(result.error);
|
|
1631
|
-
return 2;
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
// What the gate did NOT check, on BOTH the passing and the failing line. A gate
|
|
1635
|
-
// that quietly compared two of 589 pages and reported "no drift" would be
|
|
1636
|
-
// indistinguishable from one that compared everything.
|
|
1637
|
-
const notChecked =
|
|
1638
|
-
result.ignoreReason !== undefined
|
|
1639
|
-
? `; .gitignore not consulted — ${result.ignoreReason} — so every page a fresh run emits was expected to be committed`
|
|
1640
|
-
: result.ignored > 0
|
|
1641
|
-
? `; ${result.ignored} git-ignored page(s) not checked`
|
|
1642
|
-
: "";
|
|
1643
|
-
|
|
1644
|
-
if (result.clean) {
|
|
1645
|
-
// The denominator is what was actually COMPARED, so the passing line and the
|
|
1646
|
-
// failing line below count the same set. A gate whose two halves divide by
|
|
1647
|
-
// different numbers is how `verify --templates` came to report seven templates
|
|
1648
|
-
// vanishing between a red run and a green one.
|
|
1649
|
-
say(
|
|
1650
|
-
`meta verify — ${result.checked} docs page(s) match a fresh 'meta docs' ` +
|
|
1651
|
-
`(no docs drift${notChecked}).`,
|
|
1652
|
-
);
|
|
1653
|
-
return 0;
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
log.error(
|
|
1657
|
-
`meta verify — docs drift (${result.driftedFiles.length} of ${result.checked} page(s) ` +
|
|
1658
|
-
`differ from a fresh 'meta docs'${notChecked}):`,
|
|
1659
|
-
);
|
|
1660
|
-
for (const line of result.lines) log.error(` ${line}`);
|
|
1661
|
-
log.error("Run 'meta docs' to regenerate, then commit the result.");
|
|
1662
|
-
// The one-command path to green, named at the point of conviction — a page reported
|
|
1663
|
-
// as uncommitted is the one case where regenerating is NOT the answer.
|
|
1664
|
-
if (result.lines.some((l) => l.startsWith("+ "))) {
|
|
1665
|
-
log.error(
|
|
1666
|
-
result.ignoreReason === undefined
|
|
1667
|
-
? "A page you deliberately do not commit is exempt once it is git-ignored."
|
|
1668
|
-
: `.gitignore was not consulted (${result.ignoreReason}), so a page you ` +
|
|
1669
|
-
`deliberately do not commit is reported above.`,
|
|
1670
|
-
);
|
|
1671
|
-
}
|
|
1672
|
-
return 1;
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
1717
|
// -- dependency drift (Task 15, FR-023) --------------------------------------
|
|
1676
1718
|
// Gated on --deps. Re-resolves each declared dependency exactly as `meta deps
|
|
1677
1719
|
// check` does — never part of the bare-verify default (it needs the publisher
|
|
@@ -1765,6 +1807,28 @@ function summarizeDrift(changes: Change[]): string[] {
|
|
|
1765
1807
|
});
|
|
1766
1808
|
}
|
|
1767
1809
|
|
|
1810
|
+
/** One schema difference in the structured payload — the same text the stderr summary prints. */
|
|
1811
|
+
interface SchemaDriftRow {
|
|
1812
|
+
kind: Change["kind"];
|
|
1813
|
+
object: string;
|
|
1814
|
+
detail: string;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
/**
|
|
1818
|
+
* The schema gate's findings in the structured payload. An adopter whose `verify --db` failed
|
|
1819
|
+
* on views could not tell from `--format json` what differed (D3): the payload carried the
|
|
1820
|
+
* verdict only. `changes` is the metadata↔database comparison; `findings` are the
|
|
1821
|
+
* migration-ledger and committed-snapshot lines, which are text by construction.
|
|
1822
|
+
*/
|
|
1823
|
+
interface SchemaDriftSection {
|
|
1824
|
+
changes: SchemaDriftRow[];
|
|
1825
|
+
findings: string[];
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
function toSchemaDriftRow(c: Change): SchemaDriftRow {
|
|
1829
|
+
return { kind: c.kind, object: DRIFT_PRESENTATION[c.kind].noun, detail: describeChange(c) };
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1768
1832
|
// ---------------------------------------------------------------------------
|
|
1769
1833
|
// structured output (--format toon|json)
|
|
1770
1834
|
// ---------------------------------------------------------------------------
|
|
@@ -1819,7 +1883,6 @@ const GATE_SELECTOR: Record<string, string> = {
|
|
|
1819
1883
|
templates: "--templates",
|
|
1820
1884
|
schema: "--db <url>",
|
|
1821
1885
|
codegen: "--codegen",
|
|
1822
|
-
docs: "--docs",
|
|
1823
1886
|
deps: "--deps",
|
|
1824
1887
|
replay: "--replay",
|
|
1825
1888
|
};
|
|
@@ -1890,14 +1953,26 @@ function buildVerifyPayload(input: {
|
|
|
1890
1953
|
overlays: AdvisorySection<AdvisoryDiagnosticRow>;
|
|
1891
1954
|
names: AdvisorySection<AdvisoryDiagnosticRow>;
|
|
1892
1955
|
deprecations: AdvisorySection<AdvisoryDiagnosticRow>;
|
|
1956
|
+
fields: AdvisorySection<AdvisoryDiagnosticRow>;
|
|
1957
|
+
/** What the schema gate found, when it compared anything. */
|
|
1958
|
+
schemaDrift?: SchemaDriftSection;
|
|
1959
|
+
/** Gates that could not run at all (an unreachable database) — failures, not drift. */
|
|
1960
|
+
errors?: readonly { gate: string; error: string }[];
|
|
1893
1961
|
}): Record<string, unknown> {
|
|
1894
1962
|
const ran = input.gates.filter((g) => g.ran);
|
|
1895
1963
|
const failed = ran.filter((g) => !g.ok);
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1964
|
+
const errors = input.errors ?? [];
|
|
1965
|
+
const drifted = failed.filter((g) => !errors.some((e) => e.gate === g.gate));
|
|
1966
|
+
const parts: string[] = failed.length === 0
|
|
1967
|
+
? [`${ran.length} gate(s) ran, all clean`]
|
|
1968
|
+
: [
|
|
1969
|
+
...(drifted.length > 0
|
|
1970
|
+
? [`${drifted.length} of ${ran.length} gate(s) failed (${drifted.map((g) => g.gate).join(", ")})`]
|
|
1971
|
+
: []),
|
|
1972
|
+
...(errors.length > 0
|
|
1973
|
+
? [`${errors.length} gate(s) could not run (${errors.map((e) => e.gate).join(", ")})`]
|
|
1974
|
+
: []),
|
|
1975
|
+
];
|
|
1901
1976
|
if (input.antiPatterns.status === "ran" && input.antiPatterns.total > 0) {
|
|
1902
1977
|
// "advisory", not "anti-pattern": the section also carries unindexed foreign keys, a
|
|
1903
1978
|
// missing baseUrl and the other non-scanner rows — `help` breaks the count down by kind.
|
|
@@ -1915,9 +1990,18 @@ function buildVerifyPayload(input: {
|
|
|
1915
1990
|
if (input.deprecations.status === "ran" && input.deprecations.total > 0) {
|
|
1916
1991
|
parts.push(`${input.deprecations.total} deprecated-reference finding(s)`);
|
|
1917
1992
|
}
|
|
1993
|
+
if (input.fields.status === "ran" && input.fields.total > 0) {
|
|
1994
|
+
parts.push(`${input.fields.total} field authoring finding(s)`);
|
|
1995
|
+
}
|
|
1918
1996
|
|
|
1919
|
-
const help: string[] =
|
|
1920
|
-
|
|
1997
|
+
const help: string[] = errors.map((e) =>
|
|
1998
|
+
`the ${e.gate} gate could not run, which is not drift: ${e.error} — fix the connection and re-run`);
|
|
1999
|
+
if (drifted.some((g) => g.gate === "schema") && input.schemaDrift !== undefined) {
|
|
2000
|
+
help.push(
|
|
2001
|
+
`the schema gate's differences are in schemaDrift — changes[] (metadata vs database, one row per change) and findings[] (migration ledger and committed snapshot)`,
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
2004
|
+
if (drifted.some((g) => g.gate !== "schema")) {
|
|
1921
2005
|
help.push(
|
|
1922
2006
|
`the failing gate's drift DETAIL is printed as text on stderr — this payload carries the verdict only`,
|
|
1923
2007
|
);
|
|
@@ -1940,19 +2024,26 @@ function buildVerifyPayload(input: {
|
|
|
1940
2024
|
`${input.deprecations.total} reference(s) to a deprecated node — see deprecations.rows[]; each names the deprecated target and, where declared, its replacement`,
|
|
1941
2025
|
);
|
|
1942
2026
|
}
|
|
2027
|
+
if (input.fields.total > 0) {
|
|
2028
|
+
help.push(
|
|
2029
|
+
`${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`,
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
1943
2032
|
if (
|
|
1944
2033
|
failed.length === 0 &&
|
|
1945
2034
|
input.antiPatterns.total === 0 &&
|
|
1946
2035
|
input.requirements.total === 0 &&
|
|
1947
2036
|
input.overlays.total === 0 &&
|
|
1948
2037
|
input.names.total === 0 &&
|
|
1949
|
-
input.deprecations.total === 0
|
|
2038
|
+
input.deprecations.total === 0 &&
|
|
2039
|
+
input.fields.total === 0
|
|
1950
2040
|
) {
|
|
1951
2041
|
help.push("no drift and nothing advisory to answer — nothing to do");
|
|
1952
2042
|
}
|
|
1953
2043
|
|
|
1954
2044
|
return {
|
|
1955
2045
|
verify: input.gates,
|
|
2046
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
1956
2047
|
exitCode: input.exitCode,
|
|
1957
2048
|
summary: parts.join("; "),
|
|
1958
2049
|
help,
|
|
@@ -1961,12 +2052,14 @@ function buildVerifyPayload(input: {
|
|
|
1961
2052
|
overlays: input.overlays,
|
|
1962
2053
|
names: input.names,
|
|
1963
2054
|
deprecations: input.deprecations,
|
|
2055
|
+
fields: input.fields,
|
|
1964
2056
|
...(input.requirementCounts !== undefined ? { requirementCounts: input.requirementCounts } : {}),
|
|
2057
|
+
...(input.schemaDrift !== undefined ? { schemaDrift: input.schemaDrift } : {}),
|
|
1965
2058
|
// The honest boundary. Everything named here is REACHABLE — it is printed as
|
|
1966
2059
|
// text on stderr — but it is not in this document, and a reader must not have
|
|
1967
2060
|
// to discover that by its absence.
|
|
1968
2061
|
notRepresented: [
|
|
1969
|
-
"per-gate drift detail (which template variable drifted, which
|
|
2062
|
+
"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",
|
|
1970
2063
|
"the loader's own warnings and the agent-context/manifest advisories — printed as text on stderr",
|
|
1971
2064
|
],
|
|
1972
2065
|
};
|