@metaobjectsdev/cli 1.0.11 → 1.0.13
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/fmt.d.ts +2 -0
- package/dist/src/commands/fmt.d.ts.map +1 -0
- package/dist/src/commands/fmt.js +80 -0
- package/dist/src/commands/fmt.js.map +1 -0
- package/dist/src/commands/verify.d.ts.map +1 -1
- package/dist/src/commands/verify.js +37 -1
- package/dist/src/commands/verify.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +65 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/args.d.ts +25 -0
- package/dist/src/lib/args.d.ts.map +1 -1
- package/dist/src/lib/args.js +18 -0
- package/dist/src/lib/args.js.map +1 -1
- package/dist/src/lib/deprecation-lint.d.ts +11 -0
- package/dist/src/lib/deprecation-lint.d.ts.map +1 -0
- package/dist/src/lib/deprecation-lint.js +273 -0
- package/dist/src/lib/deprecation-lint.js.map +1 -0
- package/dist/src/lib/fmt-engine.d.ts +22 -0
- package/dist/src/lib/fmt-engine.d.ts.map +1 -0
- package/dist/src/lib/fmt-engine.js +128 -0
- package/dist/src/lib/fmt-engine.js.map +1 -0
- package/package.json +10 -10
- package/src/commands/fmt.ts +90 -0
- package/src/commands/verify.ts +45 -1
- package/src/index.ts +65 -0
- package/src/lib/args.ts +38 -0
- package/src/lib/deprecation-lint.ts +327 -0
- package/src/lib/fmt-engine.ts +179 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// server/typescript/packages/cli/src/lib/fmt-engine.ts
|
|
2
|
+
//
|
|
3
|
+
// `meta fmt` (#304) orchestration: resolve the project's own metadata files
|
|
4
|
+
// (resolveCollection — never a hardcoded `metaobjects/`), format each one
|
|
5
|
+
// standalone (@metaobjectsdev/metadata's formatMetadataFile — own-mode,
|
|
6
|
+
// declared-here layer only), and gate every rewrite behind a whole-project
|
|
7
|
+
// reload-and-compare safety check before it ever touches disk.
|
|
8
|
+
//
|
|
9
|
+
// The safety check (design rule 4): swap a candidate in via an in-memory
|
|
10
|
+
// source carrying the SAME file id the real load would use, reload the WHOLE
|
|
11
|
+
// project, and require both that it loads with no errors AND that its
|
|
12
|
+
// canonical serialization is byte-identical to the untouched baseline. Only
|
|
13
|
+
// then is the candidate written — so `--check` (which skips the write) and a
|
|
14
|
+
// real run share one code path, and a bug in the formatter can only ever
|
|
15
|
+
// leave a file unchanged plus an error, never a corrupted file.
|
|
16
|
+
import { basename, extname, resolve as resolvePath } from "node:path";
|
|
17
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
18
|
+
import { composeRegistry, MetaDataLoader, InMemoryStringSource, canonicalSerialize, formatMetadataFile, } from "@metaobjectsdev/metadata";
|
|
19
|
+
import { FileSource } from "@metaobjectsdev/metadata/core";
|
|
20
|
+
import { resolveCollection, defaultLoadMemoryProviders } from "@metaobjectsdev/sdk";
|
|
21
|
+
import { loadMetaobjectsConfig, loadMemoryOptionsFrom } from "./load-metaobjects-config.js";
|
|
22
|
+
async function projectProviders(collection) {
|
|
23
|
+
try {
|
|
24
|
+
const cfg = await loadMetaobjectsConfig(collection.configDir);
|
|
25
|
+
return loadMemoryOptionsFrom(cfg).providers ?? [];
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// No metaobjects.config.ts (common for a non-TS-server project running
|
|
29
|
+
// `meta fmt` just for schema-neutral formatting) — format against the
|
|
30
|
+
// core vocabulary only, exactly like a project with no custom providers.
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function runFmt(opts) {
|
|
35
|
+
let collection;
|
|
36
|
+
try {
|
|
37
|
+
collection = await resolveCollection(opts.projectRoot);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
return { files: [], fatal: err instanceof Error ? err.message : String(err) };
|
|
41
|
+
}
|
|
42
|
+
const providers = await projectProviders(collection);
|
|
43
|
+
const registry = composeRegistry([...defaultLoadMemoryProviders, ...providers]);
|
|
44
|
+
let targets;
|
|
45
|
+
if (opts.files !== undefined && opts.files.length > 0) {
|
|
46
|
+
const want = new Set(opts.files.map((f) => resolvePath(opts.projectRoot, f)));
|
|
47
|
+
const owned = new Set(collection.ownFiles);
|
|
48
|
+
const missing = [...want].filter((f) => !owned.has(f));
|
|
49
|
+
if (missing.length > 0) {
|
|
50
|
+
return {
|
|
51
|
+
files: [],
|
|
52
|
+
fatal: `not among this project's resolved metadata sources: ${missing.join(", ")}\n` +
|
|
53
|
+
"meta fmt only formats files resolveCollection() already resolves — pass no " +
|
|
54
|
+
"arguments to format every one of them.",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
targets = collection.ownFiles.filter((f) => want.has(f));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
targets = collection.ownFiles;
|
|
61
|
+
}
|
|
62
|
+
const sourceId = (p) => collection.fileIds.get(p) ?? basename(p);
|
|
63
|
+
const libSources = collection.libraries.length > 0
|
|
64
|
+
? (await import("@metaobjectsdev/metadata/library")).librarySources([...collection.libraries])
|
|
65
|
+
: [];
|
|
66
|
+
const buildSources = (overridePath, overrideText) => [
|
|
67
|
+
...libSources,
|
|
68
|
+
...collection.files.map((p) => {
|
|
69
|
+
const id = sourceId(p);
|
|
70
|
+
return overridePath !== undefined && p === overridePath
|
|
71
|
+
? new InMemoryStringSource(overrideText ?? "", { id })
|
|
72
|
+
: new FileSource(p, { id });
|
|
73
|
+
}),
|
|
74
|
+
];
|
|
75
|
+
const baseline = await new MetaDataLoader({ registry }).load(buildSources());
|
|
76
|
+
if (baseline.errors.length > 0) {
|
|
77
|
+
return {
|
|
78
|
+
files: [],
|
|
79
|
+
fatal: "this project's metadata does not currently load cleanly — fix the error(s) below, " +
|
|
80
|
+
"then re-run fmt:\n" +
|
|
81
|
+
baseline.errors.map((e) => ` ${e.message}`).join("\n"),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const baselineCanonical = canonicalSerialize(baseline.root);
|
|
85
|
+
const reports = [];
|
|
86
|
+
for (const path of targets) {
|
|
87
|
+
const ext = extname(path).toLowerCase();
|
|
88
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
89
|
+
reports.push({
|
|
90
|
+
path,
|
|
91
|
+
status: "skipped-yaml",
|
|
92
|
+
detail: "no canonical YAML emitter exists (ADR-0006: JSON is the canonical interchange form) — left untouched",
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const content = await readFile(path, "utf8");
|
|
97
|
+
const formatted = formatMetadataFile(content, { registry, sourceId: sourceId(path) });
|
|
98
|
+
if (!formatted.ok) {
|
|
99
|
+
reports.push(formatted.overlay
|
|
100
|
+
? { path, status: "skipped-overlay", detail: formatted.message }
|
|
101
|
+
: { path, status: "error", detail: formatted.message });
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (formatted.text === content) {
|
|
105
|
+
reports.push({ path, status: "unchanged" });
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const testLoad = await new MetaDataLoader({ registry }).load(buildSources(path, formatted.text));
|
|
109
|
+
const safe = testLoad.errors.length === 0 && canonicalSerialize(testLoad.root) === baselineCanonical;
|
|
110
|
+
if (!safe) {
|
|
111
|
+
reports.push({
|
|
112
|
+
path,
|
|
113
|
+
status: "error",
|
|
114
|
+
detail: "formatting this file would change the loaded model's meaning — left unchanged",
|
|
115
|
+
});
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (opts.check) {
|
|
119
|
+
reports.push({ path, status: "would-format" });
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
await writeFile(path, formatted.text, "utf8");
|
|
123
|
+
reports.push({ path, status: "formatted" });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { files: reports };
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=fmt-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fmt-engine.js","sourceRoot":"","sources":["../../../src/lib/fmt-engine.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metaobjectsdev/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "CLI for MetaObjects: scaffold, codegen, migrate, and drift-detection commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
@@ -51,15 +51,15 @@
|
|
|
51
51
|
"@biomejs/js-api": "^0.7.0",
|
|
52
52
|
"@biomejs/wasm-nodejs": "^1.9.4",
|
|
53
53
|
"@libsql/kysely-libsql": "^0.4.0",
|
|
54
|
-
"@metaobjectsdev/codegen-ts": "1.0.
|
|
55
|
-
"@metaobjectsdev/codegen-ts-react": "1.0.
|
|
56
|
-
"@metaobjectsdev/codegen-ts-tanstack": "1.0.
|
|
57
|
-
"@metaobjectsdev/docs-site": "1.0.
|
|
58
|
-
"@metaobjectsdev/metadata": "1.0.
|
|
59
|
-
"@metaobjectsdev/migrate-ts": "1.0.
|
|
60
|
-
"@metaobjectsdev/render": "1.0.
|
|
61
|
-
"@metaobjectsdev/runtime-ts": "1.0.
|
|
62
|
-
"@metaobjectsdev/sdk": "1.0.
|
|
54
|
+
"@metaobjectsdev/codegen-ts": "1.0.13",
|
|
55
|
+
"@metaobjectsdev/codegen-ts-react": "1.0.13",
|
|
56
|
+
"@metaobjectsdev/codegen-ts-tanstack": "1.0.13",
|
|
57
|
+
"@metaobjectsdev/docs-site": "1.0.13",
|
|
58
|
+
"@metaobjectsdev/metadata": "1.0.13",
|
|
59
|
+
"@metaobjectsdev/migrate-ts": "1.0.13",
|
|
60
|
+
"@metaobjectsdev/render": "1.0.13",
|
|
61
|
+
"@metaobjectsdev/runtime-ts": "1.0.13",
|
|
62
|
+
"@metaobjectsdev/sdk": "1.0.13",
|
|
63
63
|
"@toon-format/toon": "^2.3.0",
|
|
64
64
|
"jiti": "^2.4.0"
|
|
65
65
|
},
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// `meta fmt` (#304) — rewrite metadata files into the canonical form the
|
|
2
|
+
// cross-port canonical serializer already produces. `--check` lists the
|
|
3
|
+
// files that are not canonical and exits non-zero without changing anything.
|
|
4
|
+
//
|
|
5
|
+
// All of the actual work — file resolution, standalone per-file formatting,
|
|
6
|
+
// the YAML skip, and the whole-project reload-and-compare safety check — lives
|
|
7
|
+
// in `../lib/fmt-engine.js`, kept framework-free so it is unit-testable without
|
|
8
|
+
// spawning the CLI. This module is the thin text/exit-code front end.
|
|
9
|
+
import { relative } from "node:path";
|
|
10
|
+
import { parseFmtArgs } from "../lib/args.js";
|
|
11
|
+
import { log } from "../lib/log.js";
|
|
12
|
+
import { describeError } from "../lib/error-text.js";
|
|
13
|
+
import { runFmt, type FmtFileReport } from "../lib/fmt-engine.js";
|
|
14
|
+
|
|
15
|
+
function describe(report: FmtFileReport, cwd: string, check: boolean): string {
|
|
16
|
+
const rel = relative(cwd, report.path) || report.path;
|
|
17
|
+
switch (report.status) {
|
|
18
|
+
case "formatted":
|
|
19
|
+
return ` reformatted ${rel}`;
|
|
20
|
+
case "would-format":
|
|
21
|
+
return ` not canonical ${rel}`;
|
|
22
|
+
case "unchanged":
|
|
23
|
+
return ` ok ${rel}`;
|
|
24
|
+
case "skipped-yaml":
|
|
25
|
+
return ` skipped (yaml) ${rel} — ${report.detail}`;
|
|
26
|
+
case "skipped-overlay":
|
|
27
|
+
return ` skipped (overlay) ${rel} — this file declares an overlay fmt cannot resolve standalone`;
|
|
28
|
+
case "error":
|
|
29
|
+
return ` error ${rel} — ${report.detail}`;
|
|
30
|
+
default: {
|
|
31
|
+
const _exhaustive: never = report.status;
|
|
32
|
+
return _exhaustive;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function fmtCommand(args: string[], cwd: string): Promise<number> {
|
|
38
|
+
let flags;
|
|
39
|
+
try {
|
|
40
|
+
flags = parseFmtArgs(args);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
log.error(describeError(err));
|
|
43
|
+
return 2;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = await runFmt({
|
|
47
|
+
projectRoot: cwd,
|
|
48
|
+
check: flags.check,
|
|
49
|
+
files: flags.files,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (result.fatal !== undefined) {
|
|
53
|
+
log.error(result.fatal);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (result.files.length === 0) {
|
|
58
|
+
log.info("meta fmt — no metadata files to format.");
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const report of result.files) {
|
|
63
|
+
log.info(describe(report, cwd, flags.check));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const errored = result.files.filter((f) => f.status === "error");
|
|
67
|
+
const needsFormat = result.files.filter((f) => f.status === "would-format");
|
|
68
|
+
const reformatted = result.files.filter((f) => f.status === "formatted");
|
|
69
|
+
|
|
70
|
+
if (flags.check) {
|
|
71
|
+
if (needsFormat.length > 0 || errored.length > 0) {
|
|
72
|
+
log.error(
|
|
73
|
+
`meta fmt --check — ${needsFormat.length} file(s) not canonical` +
|
|
74
|
+
(errored.length > 0 ? `, ${errored.length} error(s)` : "") +
|
|
75
|
+
`. Run \`meta fmt\` to fix.`,
|
|
76
|
+
);
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
log.info("meta fmt --check — every file is already canonical.");
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (errored.length > 0) {
|
|
84
|
+
log.error(`meta fmt — ${errored.length} file(s) could not be formatted safely (left unchanged).`);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
log.info(`meta fmt — ${reformatted.length} file(s) reformatted.`);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
package/src/commands/verify.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
import { lintRequirements } from "../lib/requirement-lint.js";
|
|
41
41
|
import { lintOverlays } from "../lib/overlay-lint.js";
|
|
42
42
|
import { lintNodeNames } from "../lib/name-lint.js";
|
|
43
|
+
import { lintDeprecatedReferences } from "../lib/deprecation-lint.js";
|
|
43
44
|
import { FileSource } from "@metaobjectsdev/metadata/core";
|
|
44
45
|
import { resolveD1Config, resolveMigrateConfig } from "../lib/config.js";
|
|
45
46
|
import {
|
|
@@ -356,6 +357,9 @@ export async function verifyCommand(
|
|
|
356
357
|
// off the end of a capped run.
|
|
357
358
|
let nameSection: AdvisorySection<AdvisoryDiagnosticRow> =
|
|
358
359
|
skippedSection("the name lint did not run");
|
|
360
|
+
// #305 — the deprecated-reference authoring lint. Its own section, same reason.
|
|
361
|
+
let deprecationSection: AdvisorySection<AdvisoryDiagnosticRow> =
|
|
362
|
+
skippedSection("the deprecated-reference lint did not run");
|
|
359
363
|
// The ledger counts `meta verify` prints on every run. Undefined for a project
|
|
360
364
|
// declaring no requirement.* node at all (opt-in by declaration) — the payload
|
|
361
365
|
// then omits the block rather than reporting zeroes that would read as an empty
|
|
@@ -396,6 +400,11 @@ export async function verifyCommand(
|
|
|
396
400
|
// Runs on every `meta verify`; warnings ONLY, never changes the exit code.
|
|
397
401
|
runNameLintAdvisory();
|
|
398
402
|
|
|
403
|
+
// #305 — a node that depends (extends/@objectRef/@references/origin
|
|
404
|
+
// @from/@of/@via) on a `deprecated` node. Runs on every `meta verify`;
|
|
405
|
+
// warnings ONLY, never changes the exit code.
|
|
406
|
+
runDeprecationLintAdvisory();
|
|
407
|
+
|
|
399
408
|
// Advisory verify-as-teacher pass: surface hand-rolled work the metadata could
|
|
400
409
|
// model. Warnings ONLY — never changes the exit code (bias to under-flagging).
|
|
401
410
|
// Suppressed with --no-antipatterns or META_NO_ANTIPATTERNS=1 for the rare
|
|
@@ -445,6 +454,7 @@ export async function verifyCommand(
|
|
|
445
454
|
antiPatterns: antiPatternSection,
|
|
446
455
|
overlays: overlaySection,
|
|
447
456
|
names: nameSection,
|
|
457
|
+
deprecations: deprecationSection,
|
|
448
458
|
}),
|
|
449
459
|
fmt,
|
|
450
460
|
);
|
|
@@ -842,6 +852,29 @@ export async function verifyCommand(
|
|
|
842
852
|
}
|
|
843
853
|
}
|
|
844
854
|
|
|
855
|
+
// -- deprecated-reference authoring lint (advisory) -------------------------
|
|
856
|
+
// #305 — reads the LOADED model (see deprecation-lint.ts). Its own section,
|
|
857
|
+
// its own cap, and a skip that carries its reason rather than looking clean.
|
|
858
|
+
function runDeprecationLintAdvisory(): void {
|
|
859
|
+
if (flags.noDeprecationLint) {
|
|
860
|
+
deprecationSection = skippedSection("suppressed by --no-deprecation-lint");
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
if (process.env.META_NO_DEPRECATION_LINT === "1") {
|
|
864
|
+
deprecationSection = skippedSection("suppressed by META_NO_DEPRECATION_LINT=1");
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
const findings = lintDeprecatedReferences(root);
|
|
868
|
+
deprecationSection = ranSection(findings.map((d) => toDiagnosticRow(d, "lint")));
|
|
869
|
+
if (findings.length > 0) {
|
|
870
|
+
log.warn(
|
|
871
|
+
`meta verify — deprecations: ${findings.length} reference(s) to a deprecated node ` +
|
|
872
|
+
`(advisory — does not fail the build):`,
|
|
873
|
+
);
|
|
874
|
+
warnCapped(findings.map(formatDiagnostic), flags.limit, { structured });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
845
878
|
// -- verify-as-teacher (advisory) ------------------------------------------
|
|
846
879
|
// Records its result EITHER WAY — a skip carries its reason rather than looking
|
|
847
880
|
// like a clean scan. Warnings only; nothing here reaches the exit code (the
|
|
@@ -1856,6 +1889,7 @@ function buildVerifyPayload(input: {
|
|
|
1856
1889
|
antiPatterns: AdvisorySection<AdvisoryFindingRow>;
|
|
1857
1890
|
overlays: AdvisorySection<AdvisoryDiagnosticRow>;
|
|
1858
1891
|
names: AdvisorySection<AdvisoryDiagnosticRow>;
|
|
1892
|
+
deprecations: AdvisorySection<AdvisoryDiagnosticRow>;
|
|
1859
1893
|
}): Record<string, unknown> {
|
|
1860
1894
|
const ran = input.gates.filter((g) => g.ran);
|
|
1861
1895
|
const failed = ran.filter((g) => !g.ok);
|
|
@@ -1878,6 +1912,9 @@ function buildVerifyPayload(input: {
|
|
|
1878
1912
|
if (input.names.status === "ran" && input.names.total > 0) {
|
|
1879
1913
|
parts.push(`${input.names.total} name authoring finding(s)`);
|
|
1880
1914
|
}
|
|
1915
|
+
if (input.deprecations.status === "ran" && input.deprecations.total > 0) {
|
|
1916
|
+
parts.push(`${input.deprecations.total} deprecated-reference finding(s)`);
|
|
1917
|
+
}
|
|
1881
1918
|
|
|
1882
1919
|
const help: string[] = [];
|
|
1883
1920
|
if (failed.length > 0) {
|
|
@@ -1898,12 +1935,18 @@ function buildVerifyPayload(input: {
|
|
|
1898
1935
|
`${input.names.total} node name(s) containing whitespace — see names.rows[]; rename each node, and pin @column on a field whose physical column must keep its name`,
|
|
1899
1936
|
);
|
|
1900
1937
|
}
|
|
1938
|
+
if (input.deprecations.total > 0) {
|
|
1939
|
+
help.push(
|
|
1940
|
+
`${input.deprecations.total} reference(s) to a deprecated node — see deprecations.rows[]; each names the deprecated target and, where declared, its replacement`,
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1901
1943
|
if (
|
|
1902
1944
|
failed.length === 0 &&
|
|
1903
1945
|
input.antiPatterns.total === 0 &&
|
|
1904
1946
|
input.requirements.total === 0 &&
|
|
1905
1947
|
input.overlays.total === 0 &&
|
|
1906
|
-
input.names.total === 0
|
|
1948
|
+
input.names.total === 0 &&
|
|
1949
|
+
input.deprecations.total === 0
|
|
1907
1950
|
) {
|
|
1908
1951
|
help.push("no drift and nothing advisory to answer — nothing to do");
|
|
1909
1952
|
}
|
|
@@ -1917,6 +1960,7 @@ function buildVerifyPayload(input: {
|
|
|
1917
1960
|
requirements: input.requirements,
|
|
1918
1961
|
overlays: input.overlays,
|
|
1919
1962
|
names: input.names,
|
|
1963
|
+
deprecations: input.deprecations,
|
|
1920
1964
|
...(input.requirementCounts !== undefined ? { requirementCounts: input.requirementCounts } : {}),
|
|
1921
1965
|
// The honest boundary. Everything named here is REACHABLE — it is printed as
|
|
1922
1966
|
// text on stderr — but it is not in this document, and a reader must not have
|
package/src/index.ts
CHANGED
|
@@ -41,6 +41,7 @@ COMMANDS:
|
|
|
41
41
|
deps list One line per locked dependency: name, version, hash, node/package summary
|
|
42
42
|
types [query] Search the metadata vocabulary (types, subtypes, @attrs) by name or description
|
|
43
43
|
export Flatten loaded metadata to one canonical JSON artifact
|
|
44
|
+
fmt [<file>...] Rewrite metadata files into canonical form; --check lists drift, exits non-zero
|
|
44
45
|
docs [<project-root>] --out <dir> Generate neutral metadata documentation (entity + template pages; --site for HTML site)
|
|
45
46
|
verify Drift gate — subverbs: --templates / --db / --codegen / --docs / --deps (bare = --templates)
|
|
46
47
|
upgrade Rewrite retired metadata vocabulary (previews; --apply writes)
|
|
@@ -86,6 +87,21 @@ GEN FLAGS:
|
|
|
86
87
|
EXPORT FLAGS:
|
|
87
88
|
--out <file> Write output to a file (default: stdout)
|
|
88
89
|
|
|
90
|
+
FMT FLAGS:
|
|
91
|
+
[<file>...] Optional explicit file(s) to format — must be members of this
|
|
92
|
+
project's resolved metadata sources (resolveCollection()); omit
|
|
93
|
+
to format every one of them.
|
|
94
|
+
--check List files that are not canonical and exit non-zero; changes nothing.
|
|
95
|
+
A file whose formatting would change the LOADED MODEL's meaning
|
|
96
|
+
is never rewritten — reported as an error instead (safety check).
|
|
97
|
+
A YAML file is always reported as skipped: no canonical YAML
|
|
98
|
+
emitter exists yet (ADR-0006 — JSON is the canonical interchange
|
|
99
|
+
form). A file declaring 'overlay: true' anywhere — whether or not
|
|
100
|
+
a same-file base exists to merge into — is skipped too: a
|
|
101
|
+
formatter never changes structure, and buildTree's ordinary
|
|
102
|
+
find-or-reuse behavior would otherwise silently fold a same-file
|
|
103
|
+
base + overlay into one node, dropping the overlay marker.
|
|
104
|
+
|
|
89
105
|
DOCS FLAGS:
|
|
90
106
|
[<project-root>] PROJECT ROOT to resolve metadata from — the directory that CONTAINS
|
|
91
107
|
your metadata, NOT the metadata directory. Passing it SCOPES the run
|
|
@@ -129,6 +145,8 @@ VERIFY FLAGS (ADR-0021 D2 — explicit subverbs; combine any; exit 1 on ANY drif
|
|
|
129
145
|
(never a gate — this lint can't fail the build)
|
|
130
146
|
--no-name-lint Suppress the advisory node-name AUTHORING lint (whitespace
|
|
131
147
|
in a name; never a gate — this lint can't fail the build)
|
|
148
|
+
--no-deprecation-lint Suppress the advisory deprecated-reference AUTHORING lint
|
|
149
|
+
(never a gate — this lint can't fail the build)
|
|
132
150
|
--limit <n|all> How many advisory lines TEXT output prints per section before
|
|
133
151
|
it truncates (default 20). Never applies to --format
|
|
134
152
|
toon/json, which carry every finding and every diagnostic.
|
|
@@ -324,6 +342,8 @@ FLAGS:
|
|
|
324
342
|
never a gate; this lint can't fail the build
|
|
325
343
|
--no-name-lint Suppress the advisory node-name AUTHORING lint (whitespace in
|
|
326
344
|
a name) — never a gate; this lint can't fail the build
|
|
345
|
+
--no-deprecation-lint Suppress the advisory deprecated-reference AUTHORING lint —
|
|
346
|
+
never a gate; this lint can't fail the build
|
|
327
347
|
--limit <n|all> Advisory lines TEXT output prints PER SECTION before truncating
|
|
328
348
|
(default 20; per-section so the authoring lint can never push
|
|
329
349
|
the gate's own warnings off the end)
|
|
@@ -361,6 +381,14 @@ Leading or trailing whitespace is almost certainly a copy-paste accident — ren
|
|
|
361
381
|
the node, and pin @column on a field whose physical column must keep its name.
|
|
362
382
|
Requirement names are left to the requirement lint above. Warnings only — it can
|
|
363
383
|
never fail the build. Opt out with --no-name-lint or META_NO_NAME_LINT=1.
|
|
384
|
+
|
|
385
|
+
verify also prints an ADVISORY deprecated-reference authoring lint, in its own
|
|
386
|
+
section: any node that depends on a node carrying the documentation @deprecated
|
|
387
|
+
attr, via extends, @objectRef, @references, or an origin's @from/@of/@via. Where
|
|
388
|
+
the deprecated node also carries @replacedBy, the finding names the replacement.
|
|
389
|
+
A node referencing itself (a recursive FK, a same-entity passthrough) is not a
|
|
390
|
+
finding. Warnings only — it can never fail the build. Opt out with
|
|
391
|
+
--no-deprecation-lint or META_NO_DEPRECATION_LINT=1.
|
|
364
392
|
`,
|
|
365
393
|
export: `meta export — flatten loaded metadata to one canonical JSON artifact
|
|
366
394
|
|
|
@@ -370,6 +398,39 @@ USAGE:
|
|
|
370
398
|
FLAGS:
|
|
371
399
|
--out <file> Write output to a file (default: stdout)
|
|
372
400
|
--help, -h Print this help
|
|
401
|
+
`,
|
|
402
|
+
fmt: `meta fmt — rewrite metadata files into canonical form
|
|
403
|
+
|
|
404
|
+
USAGE:
|
|
405
|
+
meta fmt [<file>...] [flags]
|
|
406
|
+
|
|
407
|
+
Formats every JSON metadata file this project's resolved sources contain
|
|
408
|
+
(resolveCollection() — never a hardcoded default directory), or just the
|
|
409
|
+
files named. Each file is formatted as its OWN content (own-mode,
|
|
410
|
+
declared-here layer only) — never inlining an inherited member or another
|
|
411
|
+
file's overlay.
|
|
412
|
+
|
|
413
|
+
FLAGS:
|
|
414
|
+
[<file>...] Optional explicit file(s) — must be members of this project's
|
|
415
|
+
resolved metadata sources; omit to format every one of them.
|
|
416
|
+
--check List files that are not canonical and exit non-zero; changes
|
|
417
|
+
nothing on disk.
|
|
418
|
+
--help, -h Print this help
|
|
419
|
+
|
|
420
|
+
SAFETY: before writing any file, fmt reloads the whole project with that file's
|
|
421
|
+
candidate content swapped in and requires the loaded model's canonical form to
|
|
422
|
+
be byte-identical to the untouched baseline. A file that fails this check is
|
|
423
|
+
left exactly as it was and reported as an error — fmt never changes meaning.
|
|
424
|
+
|
|
425
|
+
YAML: left untouched and reported as skipped — no canonical YAML emitter
|
|
426
|
+
exists yet (ADR-0006: JSON is the canonical interchange form; YAML is an
|
|
427
|
+
authoring format).
|
|
428
|
+
|
|
429
|
+
OVERLAY: a file declaring 'overlay: true' ANYWHERE in its tree is reported as
|
|
430
|
+
skipped — not only when no base exists in the same file. A formatter never
|
|
431
|
+
changes structure: even when a same-file base IS present, merging it with
|
|
432
|
+
the overlay (what the ordinary loader would do) would drop the overlay
|
|
433
|
+
marker from the output, so fmt refuses that too and leaves the file as-is.
|
|
373
434
|
`,
|
|
374
435
|
docs: `meta docs — generate neutral metadata documentation (entity + template pages)
|
|
375
436
|
|
|
@@ -645,6 +706,10 @@ export async function run(argv: string[]): Promise<number> {
|
|
|
645
706
|
const { exportCommand } = await import("./commands/export.js");
|
|
646
707
|
return exportCommand(rest, cwd);
|
|
647
708
|
}
|
|
709
|
+
case "fmt": {
|
|
710
|
+
const { fmtCommand } = await import("./commands/fmt.js");
|
|
711
|
+
return fmtCommand(rest, cwd);
|
|
712
|
+
}
|
|
648
713
|
case "types": {
|
|
649
714
|
const { typesCommand } = await import("./commands/types.js");
|
|
650
715
|
return typesCommand(rest, explicitFormat);
|
package/src/lib/args.ts
CHANGED
|
@@ -190,6 +190,35 @@ export function parseExportArgs(argv: string[]): ExportFlags {
|
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// fmt flags (#304)
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
export interface FmtFlags {
|
|
198
|
+
check: boolean;
|
|
199
|
+
/** Optional explicit file list — narrows the run to these files (must be
|
|
200
|
+
* members of the project's resolved metadata sources). Empty = every file. */
|
|
201
|
+
files: string[];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** The flag table `parseFmtArgs` parses. Exported so the help text can be gated against it. */
|
|
205
|
+
export const FMT_OPTIONS = {
|
|
206
|
+
check: { type: "boolean", default: false },
|
|
207
|
+
} as const;
|
|
208
|
+
|
|
209
|
+
export function parseFmtArgs(argv: string[]): FmtFlags {
|
|
210
|
+
const { values, positionals } = parseArgs({
|
|
211
|
+
args: argv,
|
|
212
|
+
options: FMT_OPTIONS,
|
|
213
|
+
strict: true,
|
|
214
|
+
allowPositionals: true,
|
|
215
|
+
});
|
|
216
|
+
return {
|
|
217
|
+
check: !!values.check,
|
|
218
|
+
files: positionals,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
193
222
|
// ---------------------------------------------------------------------------
|
|
194
223
|
// shared DB-connection vocab (used by both verify --db and migrate)
|
|
195
224
|
// ---------------------------------------------------------------------------
|
|
@@ -369,6 +398,13 @@ export interface VerifyFlags {
|
|
|
369
398
|
* the build.
|
|
370
399
|
*/
|
|
371
400
|
noNameLint: boolean;
|
|
401
|
+
/**
|
|
402
|
+
* Suppress the advisory deprecated-REFERENCE authoring lint (#305) — a node
|
|
403
|
+
* depending (extends/@objectRef/@references/origin @from/@of/@via) on a
|
|
404
|
+
* `deprecated` node. Advisory only, like its siblings: it has no gate half
|
|
405
|
+
* and can never fail the build.
|
|
406
|
+
*/
|
|
407
|
+
noDeprecationLint: boolean;
|
|
372
408
|
/**
|
|
373
409
|
* ADR-0023 strict-attr load opt-OUT (#96). `verify` is strict-by-default — an
|
|
374
410
|
* undeclared/typo'd own `@attr` fails verify (ERR_UNKNOWN_ATTR). `--lax`
|
|
@@ -411,6 +447,7 @@ export const VERIFY_OPTIONS = {
|
|
|
411
447
|
"no-requirement-lint": { type: "boolean", default: false },
|
|
412
448
|
"no-overlay-lint": { type: "boolean", default: false },
|
|
413
449
|
"no-name-lint": { type: "boolean", default: false },
|
|
450
|
+
"no-deprecation-lint": { type: "boolean", default: false },
|
|
414
451
|
lax: { type: "boolean", default: false },
|
|
415
452
|
"d1": { type: "string" },
|
|
416
453
|
"remote": { type: "boolean", default: false },
|
|
@@ -496,6 +533,7 @@ export function parseVerifyArgs(argv: string[]): VerifyFlags {
|
|
|
496
533
|
noRequirementLint: !!values["no-requirement-lint"],
|
|
497
534
|
noOverlayLint: !!values["no-overlay-lint"],
|
|
498
535
|
noNameLint: !!values["no-name-lint"],
|
|
536
|
+
noDeprecationLint: !!values["no-deprecation-lint"],
|
|
499
537
|
lax: !!values.lax,
|
|
500
538
|
d1: values.d1 as string | undefined,
|
|
501
539
|
remote: !!values.remote,
|