@metaobjectsdev/cli 0.24.0 → 0.24.2
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/dist/src/commands/docs.d.ts.map +1 -1
- package/dist/src/commands/docs.js +13 -13
- package/dist/src/commands/docs.js.map +1 -1
- package/dist/src/commands/gen.d.ts.map +1 -1
- package/dist/src/commands/gen.js +9 -4
- package/dist/src/commands/gen.js.map +1 -1
- package/dist/src/commands/upgrade.d.ts.map +1 -1
- package/dist/src/commands/upgrade.js +55 -25
- 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 +72 -12
- package/dist/src/commands/verify.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +20 -8
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/args.d.ts +8 -0
- package/dist/src/lib/args.d.ts.map +1 -1
- package/dist/src/lib/args.js +2 -0
- package/dist/src/lib/args.js.map +1 -1
- package/dist/src/lib/load-metaobjects-config.d.ts +31 -0
- package/dist/src/lib/load-metaobjects-config.d.ts.map +1 -1
- package/dist/src/lib/load-metaobjects-config.js +43 -0
- package/dist/src/lib/load-metaobjects-config.js.map +1 -1
- package/dist/src/lib/requirement-check.d.ts +61 -7
- package/dist/src/lib/requirement-check.d.ts.map +1 -1
- package/dist/src/lib/requirement-check.js +113 -29
- package/dist/src/lib/requirement-check.js.map +1 -1
- package/dist/src/lib/requirement-lint.d.ts +29 -0
- package/dist/src/lib/requirement-lint.d.ts.map +1 -0
- package/dist/src/lib/requirement-lint.js +318 -0
- package/dist/src/lib/requirement-lint.js.map +1 -0
- package/package.json +10 -10
- package/src/commands/docs.ts +22 -18
- package/src/commands/gen.ts +10 -4
- package/src/commands/upgrade.ts +57 -28
- package/src/commands/verify.ts +77 -10
- package/src/index.ts +20 -8
- package/src/lib/args.ts +10 -0
- package/src/lib/load-metaobjects-config.ts +45 -0
- package/src/lib/requirement-check.ts +150 -32
- package/src/lib/requirement-lint.ts +356 -0
package/src/commands/upgrade.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// server/typescript/packages/cli/src/commands/upgrade.ts
|
|
2
2
|
//
|
|
3
|
-
// `meta upgrade` — rewrite
|
|
3
|
+
// `meta upgrade` — rewrite what the current loader no longer accepts in this project's
|
|
4
|
+
// metadata: RETIRED vocabulary (`retired-vocabulary.ts`) and ATTRIBUTE CONTRADICTIONS —
|
|
5
|
+
// pairs of live attributes that may not sit on one node (`attr-contradictions.ts`).
|
|
4
6
|
//
|
|
5
7
|
// DELIBERATELY NOT `meta migrate`. That command owns DATABASE SCHEMA (ADR-0015) and an
|
|
6
8
|
// adopter reading `migrate` expects DDL. Overloading it with a metadata rewrite would make
|
|
@@ -22,8 +24,8 @@ import { resolveCollection } from "@metaobjectsdev/sdk";
|
|
|
22
24
|
import { rewriteDocument } from "@metaobjectsdev/metadata";
|
|
23
25
|
import { log } from "../lib/log.js";
|
|
24
26
|
|
|
25
|
-
/**
|
|
26
|
-
const
|
|
27
|
+
/** YAML authoring (ADR-0006). Rewritten by the `yaml`-backed arm, loaded on demand below. */
|
|
28
|
+
const YAML_EXTENSIONS = new Set([".yaml", ".yml"]);
|
|
27
29
|
|
|
28
30
|
interface UpgradeFlags {
|
|
29
31
|
apply: boolean;
|
|
@@ -56,8 +58,11 @@ export async function upgradeCommand(args: string[], cwd: string): Promise<numbe
|
|
|
56
58
|
if ((err as Error).message === "__help__") {
|
|
57
59
|
log.info(
|
|
58
60
|
"meta upgrade [<project>] [--to <version>] [--apply]\n\n" +
|
|
59
|
-
" Rewrites
|
|
60
|
-
"
|
|
61
|
+
" Rewrites metadata the current loader no longer accepts, in JSON and YAML alike:\n" +
|
|
62
|
+
" retired vocabulary, and pairs of live attributes that may no longer sit together.\n" +
|
|
63
|
+
" Previews by default; --apply writes.\n" +
|
|
64
|
+
" Changes needing a human decision are REFUSED and listed with their guide.\n\n" +
|
|
65
|
+
" Exit: 0 clean · 1 refusals remain · 2 bad usage · 3 some files could not be read.",
|
|
61
66
|
);
|
|
62
67
|
return 0;
|
|
63
68
|
}
|
|
@@ -78,23 +83,38 @@ export async function upgradeCommand(args: string[], cwd: string): Promise<numbe
|
|
|
78
83
|
let totalChanges = 0;
|
|
79
84
|
let totalRefusals = 0;
|
|
80
85
|
let filesChanged = 0;
|
|
81
|
-
|
|
86
|
+
let checked = 0;
|
|
87
|
+
// Files we could not READ AT ALL. Distinct from "checked and clean" in every report and in
|
|
88
|
+
// the exit code — conflating them is the whole of #339.
|
|
89
|
+
const notChecked: string[] = [];
|
|
90
|
+
|
|
91
|
+
// The YAML arm carries the `yaml` package, so it lives behind its own subpath and is
|
|
92
|
+
// loaded only when the estate actually contains YAML. Importing it eagerly would pull a
|
|
93
|
+
// Node-only dependency into every `meta` invocation.
|
|
94
|
+
const hasYaml = files.some((f) => YAML_EXTENSIONS.has(extname(f).toLowerCase()));
|
|
95
|
+
const rewriteYaml = hasYaml
|
|
96
|
+
? (await import("@metaobjectsdev/metadata/vocabulary-rewrite-yaml")).rewriteYamlDocument
|
|
97
|
+
: undefined;
|
|
98
|
+
|
|
99
|
+
const opts = flags.maxVersion !== undefined ? { maxVersion: flags.maxVersion } : {};
|
|
82
100
|
|
|
83
101
|
for (const file of files) {
|
|
84
102
|
const rel = relative(projectRoot, file);
|
|
103
|
+
const before = await readFile(file, "utf8");
|
|
85
104
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
let r;
|
|
106
|
+
if (YAML_EXTENSIONS.has(extname(file).toLowerCase())) {
|
|
107
|
+
const y = rewriteYaml?.(before, opts);
|
|
108
|
+
// A document that does not parse was not examined, and must never be counted as clean.
|
|
109
|
+
if (y === undefined || y.unparseable) {
|
|
110
|
+
notChecked.push(rel);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
r = y;
|
|
114
|
+
} else {
|
|
115
|
+
r = rewriteDocument(before, opts);
|
|
92
116
|
}
|
|
93
|
-
|
|
94
|
-
const before = await readFile(file, "utf8");
|
|
95
|
-
const r = rewriteDocument(before, {
|
|
96
|
-
...(flags.maxVersion !== undefined ? { maxVersion: flags.maxVersion } : {}),
|
|
97
|
-
});
|
|
117
|
+
checked++;
|
|
98
118
|
if (r.changes.length === 0 && r.refusals.length === 0) continue;
|
|
99
119
|
|
|
100
120
|
log.info(`\n${rel}`);
|
|
@@ -115,19 +135,23 @@ export async function upgradeCommand(args: string[], cwd: string): Promise<numbe
|
|
|
115
135
|
}
|
|
116
136
|
|
|
117
137
|
log.info("");
|
|
118
|
-
if (
|
|
138
|
+
if (notChecked.length > 0) {
|
|
119
139
|
log.warn(
|
|
120
|
-
`${
|
|
121
|
-
`
|
|
140
|
+
`${notChecked.length} file(s) could not be parsed and were NOT checked — fix these ` +
|
|
141
|
+
`first, then re-run:\n ${notChecked.join("\n ")}`,
|
|
122
142
|
);
|
|
123
143
|
}
|
|
124
144
|
|
|
145
|
+
// Every conclusion states how many files it is a conclusion ABOUT. A bare "nothing found"
|
|
146
|
+
// read on its own says the estate is clean, and it is the last line, so it is the one that
|
|
147
|
+
// sticks — on the estate that reported this, it was the opposite of the truth.
|
|
148
|
+
//
|
|
149
|
+
// It says "nothing to rewrite", not "your metadata loads": this command knows about retired
|
|
150
|
+
// vocabulary and attribute contradictions, and nothing else. `meta verify` owns the verdict.
|
|
151
|
+
const scope = `${checked} file(s) checked${notChecked.length > 0 ? `, ${notChecked.length} NOT checked` : ""}`;
|
|
152
|
+
|
|
125
153
|
if (totalChanges === 0 && totalRefusals === 0) {
|
|
126
|
-
log.info(
|
|
127
|
-
skipped.length > 0
|
|
128
|
-
? "meta upgrade — no retired vocabulary found in the JSON metadata."
|
|
129
|
-
: "meta upgrade — no retired vocabulary found.",
|
|
130
|
-
);
|
|
154
|
+
log.info(`meta upgrade — nothing to rewrite (${scope}).`);
|
|
131
155
|
} else if (totalChanges === 0) {
|
|
132
156
|
// Refusals only. Reporting "rewrote 0 declarations", or advertising `--apply`, both
|
|
133
157
|
// promise an action guaranteed to change nothing and bury the fact that the remaining
|
|
@@ -143,8 +167,7 @@ export async function upgradeCommand(args: string[], cwd: string): Promise<numbe
|
|
|
143
167
|
}
|
|
144
168
|
|
|
145
169
|
// Non-zero while ANY refusal stands, applied or not. A partial upgrade that exited 0 would
|
|
146
|
-
// let CI record the migration as done while metadata still fails to load.
|
|
147
|
-
// not read at all counts the same way, for the same reason.
|
|
170
|
+
// let CI record the migration as done while metadata still fails to load.
|
|
148
171
|
if (totalRefusals > 0) {
|
|
149
172
|
log.error(
|
|
150
173
|
`${totalRefusals} declaration(s) need a human decision and were left untouched — ` +
|
|
@@ -152,5 +175,11 @@ export async function upgradeCommand(args: string[], cwd: string): Promise<numbe
|
|
|
152
175
|
);
|
|
153
176
|
return 1;
|
|
154
177
|
}
|
|
155
|
-
|
|
178
|
+
|
|
179
|
+
// "I could not look" gets its OWN code. It used to share exit 1 with "work remains", so a
|
|
180
|
+
// script could not tell an estate needing decisions from one the tool never opened — and
|
|
181
|
+
// an adopter whose whole estate was skipped got a failure exit next to a message saying
|
|
182
|
+
// nothing was found.
|
|
183
|
+
if (notChecked.length > 0) return 3;
|
|
184
|
+
return 0;
|
|
156
185
|
}
|
package/src/commands/verify.ts
CHANGED
|
@@ -15,9 +15,12 @@ import { warnIfManifestIgnored } from "../lib/manifest-ignored-check.js";
|
|
|
15
15
|
import { scanSourceForAntiPatterns } from "../lib/anti-patterns.js";
|
|
16
16
|
import { FileProvider } from "../lib/file-provider.js";
|
|
17
17
|
import { derivePayloadFieldTree } from "../lib/payload-field-tree.js";
|
|
18
|
-
import { loadMemoryOptionsFrom, loadMetaobjectsConfig, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
|
|
18
|
+
import { loadMemoryOptionsFrom, loadMetaobjectsConfig, resolveGenCollection, resolveGenConfigDir } from "../lib/load-metaobjects-config.js";
|
|
19
19
|
import { computeCodegenDrift } from "../lib/codegen-drift.js";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
checkRequirements, summariseRequirements, scanRequirements, type Diagnostic,
|
|
22
|
+
} from "../lib/requirement-check.js";
|
|
23
|
+
import { lintRequirements } from "../lib/requirement-lint.js";
|
|
21
24
|
import { resolveD1Config, resolveMigrateConfig } from "../lib/config.js";
|
|
22
25
|
import {
|
|
23
26
|
buildWranglerExecuteArgs,
|
|
@@ -461,12 +464,16 @@ export async function verifyCommand(
|
|
|
461
464
|
// rule reaches the semantic cases. FR-038 retires the attribute rather than
|
|
462
465
|
// narrowing it; the replacement generates the test FROM the requirement, so
|
|
463
466
|
// the link is structural instead of a string the author picks.
|
|
464
|
-
|
|
467
|
+
// ONE scan for all three passes. The gate and the summary each used to walk the
|
|
468
|
+
// model AND resolve every @implementedBy claim for themselves — the resolution
|
|
469
|
+
// being the expensive half — and the lint added a third walk on top.
|
|
470
|
+
const scan = scanRequirements(root);
|
|
471
|
+
const diags = [...checkRequirements(root, scan)];
|
|
465
472
|
|
|
466
473
|
// Printed on EVERY run, clean or not — a gate that says nothing when it
|
|
467
474
|
// passes cannot be told apart from a gate that checked nothing, and the
|
|
468
475
|
// recorded-gap counts are the whole reason to keep a ledger.
|
|
469
|
-
const s = summariseRequirements(root);
|
|
476
|
+
const s = summariseRequirements(root, scan);
|
|
470
477
|
if (s !== undefined) {
|
|
471
478
|
const order = [...REQUIREMENT_STATUSES];
|
|
472
479
|
const parts = order
|
|
@@ -493,15 +500,51 @@ export async function verifyCommand(
|
|
|
493
500
|
}
|
|
494
501
|
}
|
|
495
502
|
|
|
496
|
-
if (diags.length === 0) return 0;
|
|
497
503
|
const errors = diags.filter((d) => d.severity === "error");
|
|
498
504
|
const warns = diags.filter((d) => d.severity === "warn");
|
|
499
505
|
const CAP = 20;
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
506
|
+
const fmt = (d: Diagnostic): string =>
|
|
507
|
+
` ${d.code}${d.path !== undefined ? ` [${d.path}]` : ""}: ${d.message}`;
|
|
508
|
+
/** Print a capped run of warnings. Capped per SECTION, never across them: a
|
|
509
|
+
* ledger of a few hundred entries can produce hundreds of prose findings, and a
|
|
510
|
+
* shared cap would let the advisory lint push every gate warning off the end. */
|
|
511
|
+
const warnCapped = (ds: readonly Diagnostic[]): void => {
|
|
512
|
+
for (const d of ds.slice(0, CAP)) log.warn(fmt(d));
|
|
513
|
+
if (ds.length > CAP) log.warn(` …and ${ds.length - CAP} more.`);
|
|
514
|
+
};
|
|
515
|
+
for (const d of errors) log.error(fmt(d));
|
|
516
|
+
warnCapped(warns);
|
|
517
|
+
|
|
518
|
+
// -- the authoring lint: its own section, its own cap ----------------------
|
|
519
|
+
// Separate from the gate above because it makes a different claim. The gate
|
|
520
|
+
// says the ledger DISAGREES WITH THE MODEL; the lint says it agrees but
|
|
521
|
+
// records less than its author thinks — a name that is not an address, two
|
|
522
|
+
// slots holding one sentence, prose written where no surface reads it.
|
|
523
|
+
//
|
|
524
|
+
// The separate cap is the load-bearing part. A ledger of a few hundred
|
|
525
|
+
// entries can produce hundreds of prose findings, and under one shared cap
|
|
526
|
+
// those would push every WARN_REQUIREMENT_OBJECT_UNCLAIMED off the end of the
|
|
527
|
+
// list — the lint would silence the gate it was added beside. Nothing here
|
|
528
|
+
// reaches the exit code: every lint finding is a warning by construction.
|
|
529
|
+
// Muted with --no-requirement-lint or META_NO_REQUIREMENT_LINT=1, the same pair
|
|
530
|
+
// its sibling advisory offers. It mutes the ADVISORY half only — the gate above
|
|
531
|
+
// still runs and can still fail the build, which is the point of the split.
|
|
532
|
+
// `s === undefined` means the model declares no requirements at all, which the
|
|
533
|
+
// two passes above have already established: opt-in by declaration, decided
|
|
534
|
+
// without a third walk to rediscover it.
|
|
535
|
+
const lint = s === undefined
|
|
536
|
+
|| flags.noRequirementLint
|
|
537
|
+
|| process.env.META_NO_REQUIREMENT_LINT === "1"
|
|
538
|
+
? []
|
|
539
|
+
: lintRequirements(root, scan.addressed);
|
|
540
|
+
if (lint.length > 0) {
|
|
541
|
+
log.warn(
|
|
542
|
+
`meta verify — requirements: ${lint.length} authoring warning(s) ` +
|
|
543
|
+
`(advisory — does not fail the build):`,
|
|
544
|
+
);
|
|
545
|
+
warnCapped(lint);
|
|
503
546
|
}
|
|
504
|
-
|
|
547
|
+
|
|
505
548
|
if (errors.length > 0) {
|
|
506
549
|
log.error(`meta verify — requirements: ${errors.length} error(s).`);
|
|
507
550
|
return 1;
|
|
@@ -954,9 +997,33 @@ export async function verifyCommand(
|
|
|
954
997
|
// question 3) — a `gen` that committed under a narrowed scope and a
|
|
955
998
|
// `verify --codegen` that regenerates unscoped would disagree about which
|
|
956
999
|
// files should exist, reporting every out-of-scope entity as drift.
|
|
1000
|
+
//
|
|
1001
|
+
// The same argument governs the SOURCE SET (#340), and it is the reason this
|
|
1002
|
+
// resolves its own collection instead of reusing the outer one: `gen` in a
|
|
1003
|
+
// sub-project generates from that package's own sources, so a `--codegen` gate
|
|
1004
|
+
// that regenerated from the ancestor's wider set would report every file the
|
|
1005
|
+
// ancestor contributes as drift — turning the #340 fix into a broken gate. It is
|
|
1006
|
+
// re-resolved rather than hoisted because `verify`'s subverbs COMPOSE: `--db` and
|
|
1007
|
+
// `--templates` are answering a question about the whole declared collection, and
|
|
1008
|
+
// narrowing the outer `root` would silently change what they check.
|
|
1009
|
+
const genCollection = await resolveGenCollection(collection, genConfigDir);
|
|
1010
|
+
let codegenRoot = root;
|
|
1011
|
+
if (genCollection !== collection) {
|
|
1012
|
+
try {
|
|
1013
|
+
codegenRoot = await loadMemory(genCollection.configDir, {
|
|
1014
|
+
files: genCollection.files,
|
|
1015
|
+
...configLoadOptions,
|
|
1016
|
+
strict: !flags.lax,
|
|
1017
|
+
});
|
|
1018
|
+
} catch (err) {
|
|
1019
|
+
log.error(`verify --codegen: failed to load this package's metadata: ${(err as Error).message}`);
|
|
1020
|
+
return 2;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
957
1024
|
let result;
|
|
958
1025
|
try {
|
|
959
|
-
result = await computeCodegenDrift(forgeConfig,
|
|
1026
|
+
result = await computeCodegenDrift(forgeConfig, codegenRoot, genConfigDir, genCollection.inScope);
|
|
960
1027
|
} catch (err) {
|
|
961
1028
|
log.error(`verify --codegen: regeneration failed: ${(err as Error).message}`);
|
|
962
1029
|
return 1;
|
package/src/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ COMMANDS:
|
|
|
21
21
|
gen [<entity>...] Codegen TS targets from your declared metadata
|
|
22
22
|
types [query] Search the metadata vocabulary (types, subtypes, @attrs) by name or description
|
|
23
23
|
export Flatten loaded metadata to one canonical JSON artifact
|
|
24
|
-
docs <
|
|
24
|
+
docs [<project-root>] --out <dir> Generate neutral metadata documentation (entity + template pages; --site for HTML site)
|
|
25
25
|
verify Drift gate — subverbs: --templates / --db / --codegen (bare = --templates)
|
|
26
26
|
upgrade Rewrite retired metadata vocabulary (previews; --apply writes)
|
|
27
27
|
prompt-snapshot Snapshot rendered template.* output; --check gates drift
|
|
@@ -42,9 +42,11 @@ EXPORT FLAGS:
|
|
|
42
42
|
--out <file> Write output to a file (default: stdout)
|
|
43
43
|
|
|
44
44
|
DOCS FLAGS:
|
|
45
|
-
<
|
|
45
|
+
[<project-root>] PROJECT ROOT to resolve metadata from — the directory that CONTAINS
|
|
46
|
+
your metadata, NOT the metadata directory. Passing it SCOPES the run
|
|
47
|
+
(default: cwd)
|
|
46
48
|
--out <dir>, -o Output directory for the pages (default: ./docs)
|
|
47
|
-
--templates <dir> Project root to resolve adopter templates/ overrides (default: <
|
|
49
|
+
--templates <dir> Project root to resolve adopter templates/ overrides (default: <project-root>)
|
|
48
50
|
--prompts <dir> Extra dir holding prompt .mustache sources for --site (e.g. data/templates/)
|
|
49
51
|
|
|
50
52
|
VERIFY FLAGS (ADR-0021 D2 — explicit subverbs; combine any; exit 1 on ANY drift):
|
|
@@ -143,6 +145,7 @@ FLAGS:
|
|
|
143
145
|
--remote Target remote D1 instead of local (only with --dialect d1) —
|
|
144
146
|
the ONLY way to verify the actual deployed D1 database
|
|
145
147
|
--no-antipatterns Suppress the advisory "hand-rolled what MetaObjects can model" pass
|
|
148
|
+
--no-requirement-lint Suppress the advisory requirement AUTHORING lint (not the gate)
|
|
146
149
|
--help, -h Print this help
|
|
147
150
|
|
|
148
151
|
A bare 'meta verify' also runs an ADVISORY anti-pattern pass: it scans your authored
|
|
@@ -150,6 +153,12 @@ source for hand-rolled aggregates, money-as-float, and CHECK-IN enums and points
|
|
|
150
153
|
at the construct that models them (origin.aggregate / field.currency / field.enum).
|
|
151
154
|
Warnings only — it never fails the build. Opt out with --no-antipatterns or
|
|
152
155
|
META_NO_ANTIPATTERNS=1.
|
|
156
|
+
|
|
157
|
+
If the project declares requirement.* nodes, verify also prints an ADVISORY authoring
|
|
158
|
+
lint in its own section: names that are not addressable, prose slots holding one
|
|
159
|
+
sentence twice, content written where no surface reads it. Warnings only — it can
|
|
160
|
+
never fail the build. Opt out with --no-requirement-lint or META_NO_REQUIREMENT_LINT=1.
|
|
161
|
+
The requirements GATE itself (dangling refs, link floor, levels) always runs.
|
|
153
162
|
`,
|
|
154
163
|
export: `meta export — flatten loaded metadata to one canonical JSON artifact
|
|
155
164
|
|
|
@@ -163,12 +172,15 @@ FLAGS:
|
|
|
163
172
|
docs: `meta docs — generate neutral metadata documentation (entity + template pages)
|
|
164
173
|
|
|
165
174
|
USAGE:
|
|
166
|
-
meta docs [<
|
|
175
|
+
meta docs [<project-root>] [flags]
|
|
167
176
|
|
|
168
177
|
FLAGS:
|
|
169
|
-
<
|
|
170
|
-
|
|
171
|
-
|
|
178
|
+
[<project-root>] PROJECT ROOT to resolve metadata from — the directory that CONTAINS
|
|
179
|
+
your metadata, NOT the metadata directory itself. (The Python and C#
|
|
180
|
+
'docs' positionals mean the metadata dir; this one does not.) Passing
|
|
181
|
+
it SCOPES the run to that directory's own sources; no ancestor
|
|
182
|
+
.metaobjects/config.json is consulted. Omitted (default), the project
|
|
183
|
+
is discovered by walking up.
|
|
172
184
|
--out <dir>, -o Output directory for the pages (default: ./docs)
|
|
173
185
|
--model Emit the markdown model surface (entity + template pages)
|
|
174
186
|
--api Emit the markdown api surface (generated SDK reference)
|
|
@@ -177,7 +189,7 @@ FLAGS:
|
|
|
177
189
|
--metamodel Document the built-in metamodel vocabulary (no metadata needed)
|
|
178
190
|
--site Generate the browsable HTML documentation site (<out>/site/)
|
|
179
191
|
--scaffold-site Copy the site's templates + assets into codegen/docs-site/ to own (theme) them
|
|
180
|
-
--templates <dir> Project root to resolve adopter templates/ overrides (default: <
|
|
192
|
+
--templates <dir> Project root to resolve adopter templates/ overrides (default: <project-root>)
|
|
181
193
|
--prompts <dir> Extra dir holding prompt .mustache sources (for --site) when they
|
|
182
194
|
live outside the metadata sources or templates/ (e.g. data/templates/)
|
|
183
195
|
--help, -h Print this help
|
package/src/lib/args.ts
CHANGED
|
@@ -259,6 +259,14 @@ export interface VerifyFlags {
|
|
|
259
259
|
anyExplicit: boolean;
|
|
260
260
|
/** Suppress the advisory anti-pattern (verify-as-teacher) pass. */
|
|
261
261
|
noAntipatterns: boolean;
|
|
262
|
+
/**
|
|
263
|
+
* Suppress the advisory requirement AUTHORING lint — the second, prose-quality
|
|
264
|
+
* section, never the gate above it. Its own findings argue that a noisy advisory
|
|
265
|
+
* gets switched off wholesale, and a ledger mid-migration can print its capped
|
|
266
|
+
* twenty lines on every run for weeks; a mute for the advisory half is what keeps
|
|
267
|
+
* the half that CAN fail a build switched on. Same shape as --no-antipatterns.
|
|
268
|
+
*/
|
|
269
|
+
noRequirementLint: boolean;
|
|
262
270
|
/**
|
|
263
271
|
* ADR-0023 strict-attr load opt-OUT (#96). `verify` is strict-by-default — an
|
|
264
272
|
* undeclared/typo'd own `@attr` fails verify (ERR_UNKNOWN_ATTR). `--lax`
|
|
@@ -281,6 +289,7 @@ export function parseVerifyArgs(argv: string[]): VerifyFlags {
|
|
|
281
289
|
replay: { type: "boolean", default: false },
|
|
282
290
|
"replay-snapshot": { type: "boolean", default: false },
|
|
283
291
|
"no-antipatterns": { type: "boolean", default: false },
|
|
292
|
+
"no-requirement-lint": { type: "boolean", default: false },
|
|
284
293
|
lax: { type: "boolean", default: false },
|
|
285
294
|
"d1": { type: "string" },
|
|
286
295
|
"remote": { type: "boolean", default: false },
|
|
@@ -330,6 +339,7 @@ export function parseVerifyArgs(argv: string[]): VerifyFlags {
|
|
|
330
339
|
replaySnapshot,
|
|
331
340
|
anyExplicit,
|
|
332
341
|
noAntipatterns: !!values["no-antipatterns"],
|
|
342
|
+
noRequirementLint: !!values["no-requirement-lint"],
|
|
333
343
|
lax: !!values.lax,
|
|
334
344
|
d1: values.d1 as string | undefined,
|
|
335
345
|
remote: !!values.remote,
|
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { randomBytes } from "node:crypto";
|
|
7
7
|
import { createJiti } from "jiti";
|
|
8
8
|
import type { MetaDataTypeProvider, MetaobjectsGenConfig } from "@metaobjectsdev/codegen-ts";
|
|
9
|
+
import { resolveCollection, type Collection } from "@metaobjectsdev/sdk";
|
|
9
10
|
|
|
10
11
|
const CONFIG_FILE = "metaobjects.config.ts";
|
|
11
12
|
|
|
@@ -212,6 +213,50 @@ export function resolveGenConfigDir(startDir: string, fallback: string): string
|
|
|
212
213
|
return fallback;
|
|
213
214
|
}
|
|
214
215
|
|
|
216
|
+
/**
|
|
217
|
+
* The collection a TypeScript package GENERATES FROM (#340).
|
|
218
|
+
*
|
|
219
|
+
* #326/#327 established that the two config files answer different questions, and gave
|
|
220
|
+
* `metaobjects.config.ts` its own walk. This is the remaining half of the same split:
|
|
221
|
+
* a sub-project whose TS config sits below the collection root was still LOADING the
|
|
222
|
+
* ancestor's whole source set, so its `src/generated` absorbed metadata belonging to
|
|
223
|
+
* unrelated parts of the repository — one adopter's web app went from 376 files to 831,
|
|
224
|
+
* the surplus being another module's server-side prompt payload DTOs. It fails OPEN
|
|
225
|
+
* (`tsc` passes, tests pass), so the only symptom is a directory that quietly doubled.
|
|
226
|
+
*
|
|
227
|
+
* The rule: an ancestor `.metaobjects/config.json` is the DEFAULT for a package that
|
|
228
|
+
* declares no sources of its own, never an ADDITION to one that does. So when the TS
|
|
229
|
+
* config sits somewhere the collection did not, that directory is re-resolved as a
|
|
230
|
+
* collection in its own right, and it wins if it actually resolves any metadata.
|
|
231
|
+
*
|
|
232
|
+
* It can only ever NARROW, and only in a shape that could not have worked before:
|
|
233
|
+
* - the two directories coincide (every `meta init` project, and every run from a
|
|
234
|
+
* project root) — returns the original, untouched, without a second resolve;
|
|
235
|
+
* - the sub-project declares no sources — the pinned resolve throws
|
|
236
|
+
* `ERR_SOURCE_UNRESOLVED` or comes back empty, and the ancestor stands, so a
|
|
237
|
+
* package that genuinely lives off an ancestor tree keeps working;
|
|
238
|
+
* - the sub-project has its own metadata — it generates from exactly that, which is
|
|
239
|
+
* what it did before source resolution learned to walk upward.
|
|
240
|
+
*
|
|
241
|
+
* Deliberately NOT applied to `.metaobjects/` STATE. Migrations, snapshots and the
|
|
242
|
+
* operational block stay keyed on the discovered collection's directory (#326 settled
|
|
243
|
+
* that); this narrows what is LOADED, and nothing about where state lives.
|
|
244
|
+
*/
|
|
245
|
+
export async function resolveGenCollection(
|
|
246
|
+
collection: Collection,
|
|
247
|
+
genConfigDir: string,
|
|
248
|
+
): Promise<Collection> {
|
|
249
|
+
if (resolve(genConfigDir) === resolve(collection.configDir)) return collection;
|
|
250
|
+
try {
|
|
251
|
+
const pinned = await resolveCollection(genConfigDir, { explicitDir: genConfigDir });
|
|
252
|
+
return pinned.files.length > 0 ? pinned : collection;
|
|
253
|
+
} catch {
|
|
254
|
+
// The sub-project declares nothing resolvable of its own — inherit, exactly as a
|
|
255
|
+
// package with no config always has.
|
|
256
|
+
return collection;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
215
260
|
export async function loadMetaobjectsConfig(projectRoot: string): Promise<MetaobjectsGenConfig> {
|
|
216
261
|
const fullPath = resolve(projectRoot, CONFIG_FILE);
|
|
217
262
|
if (!existsSync(fullPath)) {
|