@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
|
@@ -1,839 +1,15 @@
|
|
|
1
|
-
// `meta docs
|
|
1
|
+
// `meta docs` was removed in 1.1.0 (ADR-0034 Amendment 9). Documentation is written by the
|
|
2
|
+
// generators a project lists in `generators`, which `meta gen` runs and `meta verify
|
|
3
|
+
// --codegen` checks: `docsFile()`, `apiDocsFile()`, `requirementsFile()`, `agentDocsFile()`,
|
|
4
|
+
// `metamodelDocsFile()` and `docsSiteFile()`. `--scaffold-site` is `meta eject docs-site`.
|
|
2
5
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// the compiled `meta` binary.
|
|
7
|
-
//
|
|
8
|
-
// DRY: this command does NOT re-walk objects/templates. It builds the same
|
|
9
|
-
// GenContext the codegen runner builds for the `docsFile()` generator and
|
|
10
|
-
// calls that generator, then writes the returned files. The neutrality of the
|
|
11
|
-
// output is therefore guaranteed — it is byte-for-byte the same generator the
|
|
12
|
-
// `meta gen` pipeline runs (gated by the docs conformance fixture).
|
|
13
|
-
import { resolve as resolvePath, basename, dirname } from "node:path";
|
|
14
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
6
|
+
// The command stays only to say so, exiting 2, because "unknown command" names neither the
|
|
7
|
+
// replacement nor the migration section. `meta agent-docs` (the agent context) is a different
|
|
8
|
+
// command and is unchanged.
|
|
15
9
|
import { log } from "../lib/log.js";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import { join } from "node:path";
|
|
21
|
-
import { makeRenderContext, buildPkMap, buildRelationMap, buildProjectionViews, resolveDocsConfig, apiLabel, } from "@metaobjectsdev/codegen-ts";
|
|
22
|
-
import { docsFile, apiDocsFile, requirementsFile, agentDocsFile } from "@metaobjectsdev/codegen-ts/generators";
|
|
23
|
-
import { composeRegistry, coreProviders, DEFAULT_COLUMN_NAMING_STRATEGY, renderCoreMetamodelDocs, } from "@metaobjectsdev/metadata";
|
|
24
|
-
import { dbEmittingObjects, DEFAULT_DIALECT, DEFAULT_DOCS_DIR, missingDialectMessage, resolveGenerators, runEmitsHonoRoutes, runEmitsUiTier } from "@metaobjectsdev/codegen-ts";
|
|
25
|
-
import { generateSite, SITE_TEMPLATE_NAMES, SITE_ASSET_NAMES, readSiteFile } from "@metaobjectsdev/docs-site";
|
|
26
|
-
// The `agent` schema surface takes the physical schema as an ARGUMENT with its resolvers
|
|
27
|
-
// injected — codegen-ts deliberately owns none of it (see agent-schema-input.ts). THIS is
|
|
28
|
-
// where the two packages meet: `meta docs` already depends on both.
|
|
29
|
-
import { buildExpectedSchemaWithProvenance, columnTypeSql, qualifiedDbName, } from "@metaobjectsdev/migrate-ts";
|
|
30
|
-
import { reportLoadError } from "../lib/load-error.js";
|
|
31
|
-
import { describeError } from "../lib/error-text.js";
|
|
32
|
-
function parseLayout(v, flag) {
|
|
33
|
-
if (v === undefined)
|
|
34
|
-
throw new Error(`${flag} requires flat|package`);
|
|
35
|
-
if (v !== "flat" && v !== "package") {
|
|
36
|
-
throw new Error(`${flag} must be "flat" or "package" (got "${v}")`);
|
|
37
|
-
}
|
|
38
|
-
return v;
|
|
39
|
-
}
|
|
40
|
-
function parseDocsArgs(argv, cwd) {
|
|
41
|
-
let projectRoot;
|
|
42
|
-
let out;
|
|
43
|
-
let templates;
|
|
44
|
-
let prompts;
|
|
45
|
-
let layout;
|
|
46
|
-
let baseUrl;
|
|
47
|
-
let wantModel = false;
|
|
48
|
-
let wantApi = false;
|
|
49
|
-
let wantRequirements = false;
|
|
50
|
-
let wantAgent = false;
|
|
51
|
-
let wantMetamodel = false;
|
|
52
|
-
let wantSite = false;
|
|
53
|
-
let wantScaffoldSite = false;
|
|
54
|
-
let outProvided = false;
|
|
55
|
-
let layoutProvided = false;
|
|
56
|
-
for (let i = 0; i < argv.length; i++) {
|
|
57
|
-
const a = argv[i];
|
|
58
|
-
if (a === "--out" || a === "-o") {
|
|
59
|
-
const v = argv[++i];
|
|
60
|
-
if (v === undefined)
|
|
61
|
-
throw new Error(`${a} requires a directory argument`);
|
|
62
|
-
out = v;
|
|
63
|
-
outProvided = true;
|
|
64
|
-
}
|
|
65
|
-
else if (a.startsWith("--out=")) {
|
|
66
|
-
out = a.slice("--out=".length);
|
|
67
|
-
outProvided = true;
|
|
68
|
-
}
|
|
69
|
-
else if (a === "--layout") {
|
|
70
|
-
layout = parseLayout(argv[++i], a);
|
|
71
|
-
layoutProvided = true;
|
|
72
|
-
}
|
|
73
|
-
else if (a.startsWith("--layout=")) {
|
|
74
|
-
layout = parseLayout(a.slice("--layout=".length), "--layout");
|
|
75
|
-
layoutProvided = true;
|
|
76
|
-
}
|
|
77
|
-
else if (a === "--model") {
|
|
78
|
-
wantModel = true;
|
|
79
|
-
}
|
|
80
|
-
else if (a === "--api") {
|
|
81
|
-
wantApi = true;
|
|
82
|
-
}
|
|
83
|
-
else if (a === "--requirements") {
|
|
84
|
-
wantRequirements = true;
|
|
85
|
-
}
|
|
86
|
-
else if (a === "--agent") {
|
|
87
|
-
wantAgent = true;
|
|
88
|
-
}
|
|
89
|
-
else if (a === "--metamodel") {
|
|
90
|
-
wantMetamodel = true;
|
|
91
|
-
}
|
|
92
|
-
else if (a === "--site") {
|
|
93
|
-
wantSite = true;
|
|
94
|
-
}
|
|
95
|
-
else if (a === "--scaffold-site") {
|
|
96
|
-
wantScaffoldSite = true;
|
|
97
|
-
}
|
|
98
|
-
else if (a === "--base-url") {
|
|
99
|
-
const v = argv[++i];
|
|
100
|
-
if (v === undefined)
|
|
101
|
-
throw new Error(`${a} requires a URL argument`);
|
|
102
|
-
baseUrl = v;
|
|
103
|
-
}
|
|
104
|
-
else if (a.startsWith("--base-url=")) {
|
|
105
|
-
baseUrl = a.slice("--base-url=".length);
|
|
106
|
-
}
|
|
107
|
-
else if (a === "--templates") {
|
|
108
|
-
const v = argv[++i];
|
|
109
|
-
if (v === undefined)
|
|
110
|
-
throw new Error(`${a} requires a directory argument`);
|
|
111
|
-
templates = v;
|
|
112
|
-
}
|
|
113
|
-
else if (a.startsWith("--templates=")) {
|
|
114
|
-
templates = a.slice("--templates=".length);
|
|
115
|
-
}
|
|
116
|
-
else if (a === "--prompts") {
|
|
117
|
-
const v = argv[++i];
|
|
118
|
-
if (v === undefined)
|
|
119
|
-
throw new Error(`${a} requires a directory argument`);
|
|
120
|
-
prompts = v;
|
|
121
|
-
}
|
|
122
|
-
else if (a.startsWith("--prompts=")) {
|
|
123
|
-
prompts = a.slice("--prompts=".length);
|
|
124
|
-
}
|
|
125
|
-
else if (a.startsWith("-")) {
|
|
126
|
-
throw new Error(`unknown flag: ${a}`);
|
|
127
|
-
}
|
|
128
|
-
else if (projectRoot === undefined) {
|
|
129
|
-
projectRoot = a;
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
throw new Error(`unexpected argument: ${a}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// --model and/or --api narrow the MARKDOWN surfaces; both flags (or neither)
|
|
136
|
-
// leave the surfaces unset so the resolved `docs:` config decides (default
|
|
137
|
-
// both). `--site` is a separate (additive) surface: when it is the ONLY
|
|
138
|
-
// surface flag we pass an explicit EMPTY markdown surface list so markdown
|
|
139
|
-
// emission is suppressed (resolveDocsConfig honors `[]` via `??`); combined
|
|
140
|
-
// with --model/--api the requested markdown surfaces still emit alongside it.
|
|
141
|
-
const surfaces = [];
|
|
142
|
-
if (wantModel)
|
|
143
|
-
surfaces.push("model");
|
|
144
|
-
if (wantApi)
|
|
145
|
-
surfaces.push("api");
|
|
146
|
-
if (wantRequirements)
|
|
147
|
-
surfaces.push("requirements");
|
|
148
|
-
if (wantAgent)
|
|
149
|
-
surfaces.push("agent");
|
|
150
|
-
return {
|
|
151
|
-
// `<project-root>` is the project root that CONTAINS the metadata; default
|
|
152
|
-
// cwd (mirrors how migrate/gen treat the working directory as the root).
|
|
153
|
-
projectRoot: projectRoot ?? cwd,
|
|
154
|
-
projectRootProvided: projectRoot !== undefined,
|
|
155
|
-
// Default out dir, resolved against the project root below. ONE spelling of the
|
|
156
|
-
// default, shared with resolveDocsConfig — two spellings is how a flag default and
|
|
157
|
-
// a config default come to disagree about where the pages are.
|
|
158
|
-
//
|
|
159
|
-
// --metamodel keeps its own sub-path: the renderer writes under <out>/metamodel/,
|
|
160
|
-
// and it is a different artifact (the metamodel reference, not this project's
|
|
161
|
-
// pages), so it is not swept into the generated-docs directory.
|
|
162
|
-
out: out ?? (wantMetamodel ? "./docs/metamodel" : DEFAULT_DOCS_DIR),
|
|
163
|
-
// Default flat preserves today's single-package output (+ existing goldens).
|
|
164
|
-
layout: layout ?? "flat",
|
|
165
|
-
metamodel: wantMetamodel,
|
|
166
|
-
site: wantSite,
|
|
167
|
-
scaffoldSite: wantScaffoldSite,
|
|
168
|
-
outProvided,
|
|
169
|
-
layoutProvided,
|
|
170
|
-
...(surfaces.length > 0 || wantSite ? { surfaces } : {}),
|
|
171
|
-
...(baseUrl !== undefined ? { baseUrl } : {}),
|
|
172
|
-
...(templates !== undefined ? { templates } : {}),
|
|
173
|
-
...(prompts !== undefined ? { prompts } : {}),
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* The physical schema `agent/schema.md` renders from, built by the package that OWNS it
|
|
178
|
-
* (`migrate-ts`) with its own resolvers handed across — see codegen-ts's
|
|
179
|
-
* agent-schema-input.ts for why the docs generator refuses to compute any of this itself.
|
|
180
|
-
*
|
|
181
|
-
* Returns undefined, after warning, when the page is to be SKIPPED — and a skipped page is
|
|
182
|
-
* not a silent pass: `verify --docs` convicts a committed `agent/<page>.md` that a fresh
|
|
183
|
-
* run no longer emits (see lib/docs-drift.ts), so a page describing the previous schema
|
|
184
|
-
* fails the gate rather than surviving it on exactly the change it most needs to flag.
|
|
185
|
-
*/
|
|
186
|
-
function buildAgentSchemaInput(root, configured, strategy) {
|
|
187
|
-
// THE RUNNER'S OWN GUARD DECIDES THIS, not a default applied here.
|
|
188
|
-
//
|
|
189
|
-
// `DEFAULT_DIALECT` is INERT: `runGen` throws when a model emits database code and the
|
|
190
|
-
// config declares no dialect, and it throws BEFORE `normalizeConfig` fills that default
|
|
191
|
-
// in, precisely so a DB project that forgot one gets a named error instead of
|
|
192
|
-
// "a Postgres project quietly emitting sqlite". So a persisted model with no dialect is
|
|
193
|
-
// not a sqlite project — it is a project `meta gen` REFUSES. Documenting it as sqlite
|
|
194
|
-
// would state an answer the toolchain never gave, about a schema it will not build.
|
|
195
|
-
//
|
|
196
|
-
// Two wrong answers were tried here before this one, and both were ASSERTIONS. First a
|
|
197
|
-
// hardcoded `?? "sqlite"`; then a skip whenever `dialect` was absent, on the theory that
|
|
198
|
-
// an undeclared dialect is an unknown one. The compute answer is to ask the predicate
|
|
199
|
-
// `runGen` asks — `dbEmittingObjects` — and skip only when that guard would fire. A model
|
|
200
|
-
// with no persisted object needs no dialect and renders an empty schema page anyway, so
|
|
201
|
-
// the inert default is correct for exactly the projects the guard lets through.
|
|
202
|
-
const dbEmitting = dbEmittingObjects(root.objects());
|
|
203
|
-
if (dbEmitting.length > 0 && configured === undefined) {
|
|
204
|
-
log.warn(`docs: agent/schema.md skipped — ${missingDialectMessage(dbEmitting)} ` +
|
|
205
|
-
`('meta gen' refuses this model for the same reason.)`);
|
|
206
|
-
return undefined;
|
|
207
|
-
}
|
|
208
|
-
// MySQL: MetaObjects does not own the schema, so there is no migrate-built schema to
|
|
209
|
-
// describe. The generated Drizzle tables are the description.
|
|
210
|
-
if (configured === "mysql") {
|
|
211
|
-
log.warn("docs: agent/schema.md skipped — MetaObjects does not own a MySQL schema (the generated Drizzle tables describe it).");
|
|
212
|
-
return undefined;
|
|
213
|
-
}
|
|
214
|
-
const dialect = configured ?? DEFAULT_DIALECT;
|
|
215
|
-
try {
|
|
216
|
-
const built = buildExpectedSchemaWithProvenance(root, {
|
|
217
|
-
dialect,
|
|
218
|
-
columnNamingStrategy: strategy,
|
|
219
|
-
// Views come from codegen-ts (migrate-ts never generates view DDL), exactly as
|
|
220
|
-
// `verify --db` threads them.
|
|
221
|
-
views: buildProjectionViews(root, { dialect, columnNamingStrategy: strategy }),
|
|
222
|
-
});
|
|
223
|
-
return {
|
|
224
|
-
dialect,
|
|
225
|
-
tables: built.snapshot.tables,
|
|
226
|
-
views: built.snapshot.views,
|
|
227
|
-
provenance: built.provenance,
|
|
228
|
-
// The structural `SchemaColumnLike` is migrate-ts's own ColumnDescriptor,
|
|
229
|
-
// narrowed to what the page reads; the cast hands the full descriptor back to
|
|
230
|
-
// the renderer that produced it.
|
|
231
|
-
columnType: (c) => columnTypeSql(c, dialect),
|
|
232
|
-
qualify: qualifiedDbName,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
catch (err) {
|
|
236
|
-
// A model the schema builder refuses is a real condition (a primary-key move, a
|
|
237
|
-
// duplicate physical name) that `meta migrate` will report properly. Docs must not
|
|
238
|
-
// be the command that fails on it, so the schema page is skipped and the other two
|
|
239
|
-
// agent pages still emit.
|
|
240
|
-
log.warn(`docs: agent/schema.md skipped — the expected schema could not be built ` +
|
|
241
|
-
`(${describeError(err)}). Run 'meta migrate' for the full diagnosis.`);
|
|
242
|
-
return undefined;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
export async function docsCommand(args, cwd, opts) {
|
|
246
|
-
// Informational output only. `log.warn` / `log.error` are deliberately NOT routed
|
|
247
|
-
// through this: a skipped surface or a failed render is a finding either way.
|
|
248
|
-
const info = opts?.silent === true ? (_m) => { } : log.info;
|
|
249
|
-
let flags;
|
|
250
|
-
try {
|
|
251
|
-
flags = parseDocsArgs(args, cwd);
|
|
252
|
-
}
|
|
253
|
-
catch (err) {
|
|
254
|
-
log.error(`docs: ${describeError(err)}`);
|
|
255
|
-
return 2;
|
|
256
|
-
}
|
|
257
|
-
// FR-033 S3 — `--metamodel`: document the BUILT-IN metamodel (type/subtype/
|
|
258
|
-
// attr vocabulary) from the strict registry. Unlike --model/--api this needs
|
|
259
|
-
// NEITHER a user's metadata NOR a config — there is nothing to load. It writes
|
|
260
|
-
// the renderer's files under <out>/metamodel/ (default ./docs/metamodel).
|
|
261
|
-
if (flags.metamodel) {
|
|
262
|
-
// `--site` builds HTML from a MODEL — docs-site's own loader and templates over the
|
|
263
|
-
// user's metadata. The metamodel surface is a different renderer entirely, over the
|
|
264
|
-
// registry, and it emits markdown; there is no renderer here to bridge them. The flag
|
|
265
|
-
// used to be parsed, accepted and then dropped by this very return: the command wrote
|
|
266
|
-
// sixteen markdown files, printed a success line and exited 0, so asking for a site
|
|
267
|
-
// produced no site and no complaint.
|
|
268
|
-
//
|
|
269
|
-
// It refuses rather than growing an HTML renderer, which would put a markdown-
|
|
270
|
-
// rendering dependency into a published package for one surface. The website renders
|
|
271
|
-
// it instead, keeping that dependency dev-only and giving the pages the metaobjects.dev
|
|
272
|
-
// look rather than the docs-site adopter theme.
|
|
273
|
-
// `--scaffold-site` is here for the same reason and was missed the first time: it is
|
|
274
|
-
// the OTHER flag that asks for files to be written, and this same early return dropped
|
|
275
|
-
// it identically — `--metamodel --scaffold-site` wrote 16 markdown pages, scaffolded no
|
|
276
|
-
// theme anywhere, and exited 0. Fixing one of a matched pair leaves the defect wearing
|
|
277
|
-
// a different flag name.
|
|
278
|
-
const dropped = [
|
|
279
|
-
...(flags.site ? ["--site"] : []),
|
|
280
|
-
...(flags.scaffoldSite ? ["--scaffold-site"] : []),
|
|
281
|
-
];
|
|
282
|
-
if (dropped.length > 0) {
|
|
283
|
-
log.error(`docs: ${dropped.join(" and ")} ${dropped.length > 1 ? "are" : "is"} not supported ` +
|
|
284
|
-
"with --metamodel. The metamodel reference is markdown; the rendered form is " +
|
|
285
|
-
"published at https://metaobjects.dev/reference");
|
|
286
|
-
return 2;
|
|
287
|
-
}
|
|
288
|
-
return metamodelDocsCommand(cwd, flags.out);
|
|
289
|
-
}
|
|
290
|
-
const metaRoot = resolvePath(cwd, flags.projectRoot);
|
|
291
|
-
// Absolute prompt-source dir for the site (--prompts), for a project whose
|
|
292
|
-
// templates live outside metaobjects/ or templates/ (e.g. data/templates/).
|
|
293
|
-
const promptsDir = flags.prompts !== undefined ? resolvePath(cwd, flags.prompts) : undefined;
|
|
294
|
-
// `--scaffold-site`: copy the docs-site templates + assets into codegen/docs-site/
|
|
295
|
-
// so the consumer owns them (ADR-0034 scaffold-and-own). Scaffold and return —
|
|
296
|
-
// it does not also generate. `resolveConfigDir` rather than `resolveCollection`:
|
|
297
|
-
// scaffolding needs no metadata, but it must write where `emitSite` will READ
|
|
298
|
-
// (below, under the resolved project root), and that is the same walk.
|
|
299
|
-
if (flags.scaffoldSite) {
|
|
300
|
-
// `resolveGenConfigDir` over the bare walk: `emitSite` reads the owned theme
|
|
301
|
-
// from the directory holding `metaobjects.config.ts`, so scaffolding must write
|
|
302
|
-
// to the same one or the theme is scaffolded where nothing will ever read it.
|
|
303
|
-
return scaffoldSiteCommand(resolveGenConfigDir(metaRoot, await resolveConfigDir(metaRoot)));
|
|
304
|
-
}
|
|
305
|
-
// Discovery and load are two separate failure modes, kept in separate try
|
|
306
|
-
// blocks deliberately — same reasoning as `meta gen` (gen.ts): a broad
|
|
307
|
-
// catch around both would swallow a genuine ParseError as "no metaobjects/
|
|
308
|
-
// found", masking the real failure.
|
|
309
|
-
//
|
|
310
|
-
// Discovery runs BEFORE the config read, deliberately, and `meta gen` calls
|
|
311
|
-
// out the same ordering as the thing it fixed: the project root is whichever
|
|
312
|
-
// directory `resolveCollection` decided the metadata belongs to, so
|
|
313
|
-
// everything project-relative — `metaobjects.config.ts` and its providers,
|
|
314
|
-
// the `docs.outDir` it names, the adopter `templates/` overrides, the owned
|
|
315
|
-
// `codegen/docs-site/` theme — has to come from that same directory. Reading
|
|
316
|
-
// the config from the ambient `<project-root>` argument while the metadata came
|
|
317
|
-
// from an ancestor renders the ancestor's model with the subdirectory's
|
|
318
|
-
// (absent) providers. For a run at the project root the two are the same path.
|
|
319
|
-
//
|
|
320
|
-
// An EXPLICIT `<project-root>` positional pins the collection rather than seeding a
|
|
321
|
-
// walk (#327). The argument has always meant "document this": before sources were
|
|
322
|
-
// resolvable it read `<path>/metaobjects/` and nothing else, and passing it to
|
|
323
|
-
// discovery turned it into a starting point, so the nearest ancestor
|
|
324
|
-
// `.metaobjects/config.json` was found and ITS sources were unioned in — a command
|
|
325
|
-
// whose entire purpose is documenting one subset silently documenting the whole
|
|
326
|
-
// repo, at exit 0. Pinned, `<path>` governs: its own config if it has one, the
|
|
327
|
-
// default `<path>/metaobjects` if not. A bare `meta docs` still discovers, which is
|
|
328
|
-
// the right default for "document the project I am standing in".
|
|
329
|
-
let collection;
|
|
330
|
-
try {
|
|
331
|
-
collection = await resolveCollection(metaRoot, flags.projectRootProvided ? { explicitDir: metaRoot } : undefined);
|
|
332
|
-
}
|
|
333
|
-
catch (err) {
|
|
334
|
-
log.error(`docs: ${describeError(err)}`);
|
|
335
|
-
return 2;
|
|
336
|
-
}
|
|
337
|
-
// Which directory's `metaobjects.config.ts` governs is its OWN nearest-ancestor
|
|
338
|
-
// walk, not the collection's: the two files answer different questions (design
|
|
339
|
-
// §4.6), and in a Maven- or pip-rooted monorepo the collection is declared at the
|
|
340
|
-
// repo root while the TS config sits in the app. Reading the second from the
|
|
341
|
-
// first made docs silently drop that app's providers and skip its api surface
|
|
342
|
-
// entirely (#326). Everything that config names follows it — the `docs.outDir` it
|
|
343
|
-
// carries, the adopter `templates/` overrides and the owned `codegen/docs-site/`
|
|
344
|
-
// theme beside it. Same directory whenever the two files sit together, which is
|
|
345
|
-
// every `meta init` project.
|
|
346
|
-
const genConfigDir = resolveGenConfigDir(metaRoot, collection.configDir);
|
|
347
|
-
// The project root used to resolve adopter `templates/` overrides; the
|
|
348
|
-
// framework defaults sit underneath via projectProvider's chain. `--templates`
|
|
349
|
-
// is the one explicit override.
|
|
350
|
-
const projectRoot = flags.templates !== undefined
|
|
351
|
-
? resolvePath(cwd, flags.templates)
|
|
352
|
-
: genConfigDir;
|
|
353
|
-
// Best-effort load of metaobjects.config.ts to pick up consumer-supplied
|
|
354
|
-
// providers (e.g. a project's custom field/object subtypes). Unlike `gen`,
|
|
355
|
-
// docs does NOT require a config — the Tier-2 "metadata alone" promise must
|
|
356
|
-
// hold for config-less projects. If the config is absent or invalid, fall
|
|
357
|
-
// back to defaults; the loader still surfaces a stable unknown-subtype error
|
|
358
|
-
// if the metadata genuinely uses an unregistered type.
|
|
359
|
-
let loadedConfig;
|
|
360
|
-
let configProviders;
|
|
361
|
-
// The same config's contributions to the metadata LOAD — providers plus the shipped
|
|
362
|
-
// `libraries` a project opts into (#333). `configProviders` stays separate because the
|
|
363
|
-
// site surface has its own loader and takes providers alone.
|
|
364
|
-
let configLoadOptions = {};
|
|
365
|
-
// hasConfig gates the api surface: api docs describe the GENERATED REST
|
|
366
|
-
// surface, which only exists when there is a (loadable) gen config. A config
|
|
367
|
-
// that EXISTS but fails to load degrades to model-only with a warning.
|
|
368
|
-
const hasConfig = existsSync(join(genConfigDir, "metaobjects.config.ts"));
|
|
369
|
-
// Only attempt the load when the file is actually present: absence is the
|
|
370
|
-
// expected config-less case (stay silent), but a config that EXISTS yet fails
|
|
371
|
-
// to load is surfaced as a warning rather than silently degrading to
|
|
372
|
-
// provider-less docs — otherwise a custom-type project would later fail with a
|
|
373
|
-
// cryptic unknown-subtype error instead of the real config error.
|
|
374
|
-
if (hasConfig) {
|
|
375
|
-
try {
|
|
376
|
-
loadedConfig = await loadMetaobjectsConfig(genConfigDir);
|
|
377
|
-
configProviders = loadedConfig.providers;
|
|
378
|
-
configLoadOptions = loadMemoryOptionsFrom(loadedConfig);
|
|
379
|
-
}
|
|
380
|
-
catch (err) {
|
|
381
|
-
log.warn(`docs: metaobjects.config.ts failed to load (${describeError(err)}); ` +
|
|
382
|
-
`generating docs without its providers`);
|
|
383
|
-
loadedConfig = undefined;
|
|
384
|
-
configProviders = undefined;
|
|
385
|
-
configLoadOptions = {};
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
// Merge the config `docs:` block with CLI overrides over documented defaults.
|
|
389
|
-
// CLI --out/--layout only override when explicitly passed; surfaces/baseUrl
|
|
390
|
-
// override whenever present. The resolver supplies defaults (outDir ./docs/generated,
|
|
391
|
-
// layout = fallback, surfaces = both) so config-less + flag-less runs are
|
|
392
|
-
// unchanged.
|
|
393
|
-
const cliOverrides = {
|
|
394
|
-
...(flags.outProvided ? { outDir: flags.out } : {}),
|
|
395
|
-
...(flags.layoutProvided ? { layout: flags.layout } : {}),
|
|
396
|
-
...(flags.surfaces ? { surfaces: flags.surfaces } : {}),
|
|
397
|
-
...(flags.baseUrl !== undefined ? { baseUrl: flags.baseUrl } : {}),
|
|
398
|
-
};
|
|
399
|
-
// Layout fallback chain: --layout (override, gated above) → docs.layout block →
|
|
400
|
-
// the project's top-level outputLayout → flat. So docs default to the SAME page
|
|
401
|
-
// placement as codegen when neither the docs block nor the CLI sets it.
|
|
402
|
-
const docsCfg = resolveDocsConfig(loadedConfig?.docs, cliOverrides, loadedConfig?.outputLayout ?? "flat");
|
|
403
|
-
// Against `genConfigDir`, not the collection's: `docs.outDir` is declared in
|
|
404
|
-
// `metaobjects.config.ts`, so it resolves against the directory that config lives
|
|
405
|
-
// in — the same rule `meta gen` applies to its own `outDir`. Identical whenever
|
|
406
|
-
// the two files sit together.
|
|
407
|
-
const outDir = resolvePath(genConfigDir, docsCfg.outDir);
|
|
408
|
-
// SITE surface has its OWN model loader (docs-site's loadModel — NOT the sdk
|
|
409
|
-
// loadMemory below) and needs no gen config. When the site is the ONLY
|
|
410
|
-
// requested surface (no markdown surfaces resolved), emit it and return
|
|
411
|
-
// WITHOUT building the markdown GenContext — decoupled and one fewer failure
|
|
412
|
-
// surface. Combined with --model/--api it is emitted after them (below).
|
|
413
|
-
if (flags.site && docsCfg.surfaces.length === 0) {
|
|
414
|
-
return emitSite(collection, projectRoot, genConfigDir, outDir, configProviders, promptsDir);
|
|
415
|
-
}
|
|
416
|
-
// Load metadata standalone — same loader path as migrate/gen. Threads any
|
|
417
|
-
// consumer providers from the config so custom types resolve.
|
|
418
|
-
let root;
|
|
419
|
-
try {
|
|
420
|
-
root = await loadMemory(collection.configDir, {
|
|
421
|
-
...collectionLoadOptions(collection),
|
|
422
|
-
...configLoadOptions,
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
catch (err) {
|
|
426
|
-
reportLoadError(log, "docs: failed to load metadata", err);
|
|
427
|
-
return 2;
|
|
428
|
-
}
|
|
429
|
-
// Build the same GenContext the codegen runner builds for docsFile(). The
|
|
430
|
-
// dialect only affects column-type hints on the entity page; "sqlite" is the
|
|
431
|
-
// neutral default (migrate's offline path uses the same fallback chain).
|
|
432
|
-
const renderContext = makeRenderContext({
|
|
433
|
-
dialect: "sqlite",
|
|
434
|
-
loadedRoot: root,
|
|
435
|
-
outDir,
|
|
436
|
-
dbImport: "",
|
|
437
|
-
// The project's own prefix, not the "" default. `agent/ui.md` documents an ENDPOINT,
|
|
438
|
-
// and the routes generator mounts every one of them inside
|
|
439
|
-
// `fastify.register(…, { prefix: apiPrefix })` — so a project configuring "/api" is
|
|
440
|
-
// served at /api/authors and was being told, by the page whose whole job is to be
|
|
441
|
-
// right about addresses, that it was /authors. `meta gen` threads this (runner.ts);
|
|
442
|
-
// `meta docs` is the OTHER door onto the same page and did not.
|
|
443
|
-
apiPrefix: loadedConfig?.apiPrefix ?? "",
|
|
444
|
-
pkMap: buildPkMap(root),
|
|
445
|
-
relationMap: buildRelationMap(root),
|
|
446
|
-
});
|
|
447
|
-
// The generator suite, with stable-name STRINGS resolved to the generators they name
|
|
448
|
-
// (ADR-0021 #1 allows `generators: ["routes-hono"]`). Both aggregations below read a
|
|
449
|
-
// generator MARKER, and a string carries none — so reading the raw array answered
|
|
450
|
-
// "no Hono routes / no UI tier" for a project that wires them by name, which is the
|
|
451
|
-
// same two-doors-one-question defect these flags exist to close. `meta gen` resolves
|
|
452
|
-
// the identical array through `normalizeConfig`.
|
|
453
|
-
//
|
|
454
|
-
// An unknown name throws (the message names it and lists the valid ones). `meta gen`
|
|
455
|
-
// fails on it; `meta docs` has never had to load the suite at all, so it degrades to
|
|
456
|
-
// the already-typed entries rather than becoming a new failure — and SAYS it did,
|
|
457
|
-
// because a page quietly losing a section is exactly what this whole fix is about.
|
|
458
|
-
let suite;
|
|
459
|
-
try {
|
|
460
|
-
suite = resolveGenerators(loadedConfig?.generators ?? []);
|
|
461
|
-
}
|
|
462
|
-
catch (err) {
|
|
463
|
-
log.warn(`docs: could not resolve the generator suite (${describeError(err)}) — the agent ` +
|
|
464
|
-
`pages fall back to the generators declared as values, so a tier wired by name may ` +
|
|
465
|
-
`be missing from them.`);
|
|
466
|
-
suite = (loadedConfig?.generators ?? []).filter((g) => typeof g !== "string");
|
|
467
|
-
}
|
|
468
|
-
const ctx = {
|
|
469
|
-
entities: root.objects(),
|
|
470
|
-
loadedRoot: root,
|
|
471
|
-
matches: () => true,
|
|
472
|
-
config: {
|
|
473
|
-
outDir,
|
|
474
|
-
extStyle: "none",
|
|
475
|
-
dbImport: "",
|
|
476
|
-
dialect: "sqlite",
|
|
477
|
-
outputLayout: docsCfg.layout,
|
|
478
|
-
// api-docs reads this to decide whether to document the opt-in Hono CRUD
|
|
479
|
-
// surface. Aggregate it from the generator set exactly as the gen runner
|
|
480
|
-
// does (a generator opts in via emitsHonoRoutes), so `meta docs` auto-detects
|
|
481
|
-
// routesFileHono() rather than relying on a field users don't normally set.
|
|
482
|
-
includeHonoRoutes: loadedConfig?.includeHonoRoutes ?? runEmitsHonoRoutes(suite),
|
|
483
|
-
// `agent/ui.md` describes forms, grids and the endpoints their hooks call. Whether
|
|
484
|
-
// any of that is emitted is a GENERATOR fact — the page's own predicate is
|
|
485
|
-
// metadata-only and answered "could a UI be generated?", so a project with
|
|
486
|
-
// `generators: []` got a confident page naming endpoints nothing serves. Aggregate
|
|
487
|
-
// it through the same helper the gen runner uses, so `meta docs` and `meta gen` —
|
|
488
|
-
// the two doors onto this page — cannot answer it differently.
|
|
489
|
-
includeUiTier: loadedConfig?.includeUiTier ?? runEmitsUiTier(suite),
|
|
490
|
-
},
|
|
491
|
-
renderContext,
|
|
492
|
-
projectRoot,
|
|
493
|
-
warn: (msg) => log.warn(`docs: ${msg}`),
|
|
494
|
-
};
|
|
495
|
-
const emit = [];
|
|
496
|
-
let modelFiles = [];
|
|
497
|
-
// The declared api surfaces, each tagged with its human label (apiLabel maps
|
|
498
|
-
// the language key → label; never hardcode labels). The model page links one
|
|
499
|
-
// reference per declared surface — across ALL ports, not just the surfaces
|
|
500
|
-
// THIS command emits — so a polyglot model page points at every port's docs.
|
|
501
|
-
const labeled = docsCfg.apiSurfaces.map((s) => ({ ...s, label: apiLabel(s.lang) }));
|
|
502
|
-
// The api surface only materializes with a loadable gen config (there is
|
|
503
|
-
// nothing generated to document otherwise), so gate api emit + cross-linking
|
|
504
|
-
// on that. When false, the model surface emits its historical standalone form.
|
|
505
|
-
const apiSelected = docsCfg.surfaces.includes("api") && loadedConfig !== undefined;
|
|
506
|
-
// MODEL surface — the neutral metadata pages (<Entity>.md / <Template>.md +
|
|
507
|
-
// README.md). Keep the render-error handling tight around docsFile() only.
|
|
508
|
-
if (docsCfg.surfaces.includes("model")) {
|
|
509
|
-
// When api is selected, link EVERY declared surface from the model page (so a
|
|
510
|
-
// polyglot model page references each port's api docs). `labeled` already
|
|
511
|
-
// carries {label, subDir, baseUrl?} (docsFile ignores the extra `lang`), so
|
|
512
|
-
// pass it straight through. Otherwise no api refs.
|
|
513
|
-
const modelOpts = apiSelected ? { apiSurfaces: labeled } : {};
|
|
514
|
-
try {
|
|
515
|
-
modelFiles = await docsFile(modelOpts).generate(ctx);
|
|
516
|
-
}
|
|
517
|
-
catch (err) {
|
|
518
|
-
const msg = describeError(err);
|
|
519
|
-
// Duplicate output path (silent-overwrite backstop): the generator already
|
|
520
|
-
// names both colliding FQNs + the path and starts with "docs:". Surface it
|
|
521
|
-
// verbatim as a clean non-zero exit (no double prefix, no stack trace).
|
|
522
|
-
if (msg.startsWith("docs: duplicate output path")) {
|
|
523
|
-
log.error(msg);
|
|
524
|
-
return 1;
|
|
525
|
-
}
|
|
526
|
-
// The framework templates resolve from disk; inside the compiled `meta`
|
|
527
|
-
// binary they live on a virtual fs the provider cannot read. Surface that
|
|
528
|
-
// as an actionable message rather than a cryptic render failure.
|
|
529
|
-
if (/entity-page|template-page|failed rendering|ENOENT|not found/i.test(msg)) {
|
|
530
|
-
log.error(`docs: failed to render — templates not found. ` +
|
|
531
|
-
`Run 'meta docs' from an installed package layout (with on-disk ` +
|
|
532
|
-
`templates/), not the standalone binary, OR drop your own ` +
|
|
533
|
-
`templates/docs/entity-page.md.mustache + template-page.md.mustache. (${msg})`);
|
|
534
|
-
}
|
|
535
|
-
else {
|
|
536
|
-
log.error(`docs: ${msg}`);
|
|
537
|
-
}
|
|
538
|
-
return 1;
|
|
539
|
-
}
|
|
540
|
-
emit.push(...modelFiles);
|
|
541
|
-
}
|
|
542
|
-
// REQUIREMENTS surface — the declared ledger as documentation (requirements.md +
|
|
543
|
-
// requirements.toon). Metadata-alone like the model surface: it reads ctx.loadedRoot
|
|
544
|
-
// and nothing else, so it is NOT gated on a loadable gen config the way `api` is.
|
|
545
|
-
//
|
|
546
|
-
// Emits ZERO files when the project declares no `requirement.*` node, which is what
|
|
547
|
-
// makes this surface safe to default ON — a project without a ledger sees no change.
|
|
548
|
-
if (docsCfg.surfaces.includes("requirements")) {
|
|
549
|
-
emit.push(...(await requirementsFile().generate(ctx)));
|
|
550
|
-
}
|
|
551
|
-
// API surface — the SDK reference for the GENERATED REST surface, side by side
|
|
552
|
-
// under each surface's subDir. THIS command only OWNS the surfaces it can
|
|
553
|
-
// generate — i.e. its own port (lang "ts"). Surfaces owned by other ports are
|
|
554
|
-
// linked (above) but produced by running that port's docs command; we just log
|
|
555
|
-
// a pointer. Only meaningful with a loadable gen config, so skip when absent.
|
|
556
|
-
let apiFiles = [];
|
|
557
|
-
if (docsCfg.surfaces.includes("api")) {
|
|
558
|
-
if (loadedConfig !== undefined) {
|
|
559
|
-
try {
|
|
560
|
-
// Emit every surface THIS port owns (loop so it generalizes beyond the
|
|
561
|
-
// single ts surface owned today).
|
|
562
|
-
for (const s of labeled.filter((s) => s.lang === "ts")) {
|
|
563
|
-
apiFiles.push(...(await apiDocsFile({
|
|
564
|
-
subDir: s.subDir,
|
|
565
|
-
modelSurface: docsCfg.surfaces.includes("model"),
|
|
566
|
-
}).generate(ctx)));
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
catch (err) {
|
|
570
|
-
const msg = describeError(err);
|
|
571
|
-
if (msg.startsWith("docs: duplicate output path")) {
|
|
572
|
-
log.error(msg);
|
|
573
|
-
return 1;
|
|
574
|
-
}
|
|
575
|
-
log.error(`docs: ${msg}`);
|
|
576
|
-
return 1;
|
|
577
|
-
}
|
|
578
|
-
emit.push(...apiFiles);
|
|
579
|
-
// Surfaces owned by other ports: link only, with a pointer to where they
|
|
580
|
-
// get produced.
|
|
581
|
-
for (const s of labeled.filter((s) => s.lang !== "ts")) {
|
|
582
|
-
info(`meta docs: api surface '${s.lang}' (${s.subDir}) is produced by that port's docs command — run it to populate those pages.`);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
else if (hasConfig) {
|
|
586
|
-
// Config present but failed to load — already warned above; don't claim an
|
|
587
|
-
// api surface we couldn't build.
|
|
588
|
-
info("meta docs: api surface skipped — metaobjects.config.ts failed to load.");
|
|
589
|
-
}
|
|
590
|
-
else {
|
|
591
|
-
info("meta docs: api surface skipped — no metaobjects.config.ts (nothing generated to document).");
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
// AGENT surface — three pages an agent reads BEFORE touching a tier (`agent/schema.md`
|
|
595
|
-
// before persistence, `agent/ui.md` before a form or grid, `agent/requirements.md`
|
|
596
|
-
// before adding a capability). The fourth file the always-on pointer names,
|
|
597
|
-
// `api/AGENT-API.md`, belongs to the api surface above.
|
|
598
|
-
//
|
|
599
|
-
// Gated on a loadable gen config exactly as `api` is, and for a stronger reason: the
|
|
600
|
-
// physical schema depends on the project's DIALECT and column-naming strategy, and the
|
|
601
|
-
// neutral model surface above runs on a placeholder because it documents no SQL at all.
|
|
602
|
-
// The dialect here is the project's own, resolved by the same default `meta gen` and
|
|
603
|
-
// `meta migrate` apply — see `buildAgentSchemaInput`.
|
|
604
|
-
if (docsCfg.surfaces.includes("agent")) {
|
|
605
|
-
if (loadedConfig !== undefined) {
|
|
606
|
-
const strategy = loadedConfig.columnNamingStrategy ?? DEFAULT_COLUMN_NAMING_STRATEGY;
|
|
607
|
-
const schema = buildAgentSchemaInput(root, loadedConfig.dialect, strategy);
|
|
608
|
-
emit.push(...(await agentDocsFile({
|
|
609
|
-
...(schema !== undefined && { schema }),
|
|
610
|
-
columnNamingStrategy: strategy,
|
|
611
|
-
}).generate(ctx)));
|
|
612
|
-
}
|
|
613
|
-
else if (hasConfig) {
|
|
614
|
-
info("meta docs: agent surface skipped — metaobjects.config.ts failed to load.");
|
|
615
|
-
}
|
|
616
|
-
else {
|
|
617
|
-
info("meta docs: agent surface skipped — no metaobjects.config.ts (the physical schema " +
|
|
618
|
-
"and the generated UI are what it describes).");
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
try {
|
|
622
|
-
await mkdir(outDir, { recursive: true });
|
|
623
|
-
for (const f of emit) {
|
|
624
|
-
const path = resolvePath(outDir, f.path);
|
|
625
|
-
await mkdir(resolvePath(path, ".."), { recursive: true });
|
|
626
|
-
await writeFile(path, f.content, "utf8");
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
catch (err) {
|
|
630
|
-
log.error(`docs: failed to write pages: ${describeError(err)}`);
|
|
631
|
-
return 1;
|
|
632
|
-
}
|
|
633
|
-
// SITE surface (additive) — emit after the markdown surfaces so both coexist.
|
|
634
|
-
if (flags.site) {
|
|
635
|
-
const siteRc = await emitSite(collection, projectRoot, genConfigDir, outDir, configProviders, promptsDir);
|
|
636
|
-
if (siteRc !== 0)
|
|
637
|
-
return siteRc;
|
|
638
|
-
}
|
|
639
|
-
// Summary: docsFile() emits ONE overview/index page (README.md) plus one page
|
|
640
|
-
// per entity and one per template.output. The entity count is the matched
|
|
641
|
-
// object count; the remaining non-overview model pages are template pages.
|
|
642
|
-
const entityCount = root.objects().filter(ctx.matches).length;
|
|
643
|
-
const modelOverview = modelFiles.filter((f) => f.path === "README.md").length;
|
|
644
|
-
const modelTemplates = modelFiles.length > 0
|
|
645
|
-
? modelFiles.length - entityCount - modelOverview
|
|
646
|
-
: 0;
|
|
647
|
-
const modelSummary = docsCfg.surfaces.includes("model")
|
|
648
|
-
? `${modelOverview} overview + ${entityCount} entity page(s) + ${modelTemplates} template page(s)`
|
|
649
|
-
: "model surface skipped";
|
|
650
|
-
const apiSummary = apiFiles.length > 0
|
|
651
|
-
? `${apiFiles.length} api page(s)`
|
|
652
|
-
: "no api pages";
|
|
653
|
-
// Reported only when it actually wrote something. A project with no ledger emits no
|
|
654
|
-
// requirements file, and saying "0 requirement pages" would advertise a surface that
|
|
655
|
-
// did not run — the opposite of the silence the empty-ledger guard exists to produce.
|
|
656
|
-
const requirementFiles = emit.filter((f) => f.path.startsWith("requirements.")).length;
|
|
657
|
-
const reqSummary = requirementFiles > 0 ? `; ${requirementFiles} requirement file(s)` : "";
|
|
658
|
-
// Same rule as the requirements line: NAMED rather than counted, and only when written.
|
|
659
|
-
// Each agent page is skipped when its tier has nothing to describe, so "3 agent pages"
|
|
660
|
-
// would leave a reader unable to tell which three — and the pages are the thing the
|
|
661
|
-
// always-on agent context points at by name.
|
|
662
|
-
const agentPages = emit
|
|
663
|
-
.filter((f) => f.path.startsWith("agent/"))
|
|
664
|
-
.map((f) => basename(f.path));
|
|
665
|
-
const agentSummary = agentPages.length > 0 ? `; agent/${agentPages.sort().join(" + agent/")}` : "";
|
|
666
|
-
info(`meta docs — wrote ${modelSummary}; ${apiSummary}${reqSummary}${agentSummary} → ${outDir}`);
|
|
667
|
-
return 0;
|
|
668
|
-
}
|
|
669
|
-
/**
|
|
670
|
-
* ADR-0034 scaffold-and-own for the docs-site: copy the bundled templates + assets
|
|
671
|
-
* into `<root>/codegen/docs-site/{templates,assets}`, writing each file ONLY if
|
|
672
|
-
* absent so a re-run never clobbers a hand-edited file.
|
|
673
|
-
*/
|
|
674
|
-
async function scaffoldSiteCommand(projectRoot) {
|
|
675
|
-
const tplDir = join(projectRoot, "codegen/docs-site/templates");
|
|
676
|
-
const astDir = join(projectRoot, "codegen/docs-site/assets");
|
|
677
|
-
const created = [];
|
|
678
|
-
const preserved = [];
|
|
679
|
-
try {
|
|
680
|
-
await mkdir(tplDir, { recursive: true });
|
|
681
|
-
await mkdir(astDir, { recursive: true });
|
|
682
|
-
for (const name of SITE_TEMPLATE_NAMES) {
|
|
683
|
-
const abs = join(tplDir, name);
|
|
684
|
-
const rel = `codegen/docs-site/templates/${name}`;
|
|
685
|
-
if (existsSync(abs)) {
|
|
686
|
-
preserved.push(rel);
|
|
687
|
-
continue;
|
|
688
|
-
}
|
|
689
|
-
await writeFile(abs, readSiteFile("template", name), "utf8");
|
|
690
|
-
created.push(rel);
|
|
691
|
-
}
|
|
692
|
-
for (const name of SITE_ASSET_NAMES) {
|
|
693
|
-
const abs = join(astDir, name);
|
|
694
|
-
const rel = `codegen/docs-site/assets/${name}`;
|
|
695
|
-
if (existsSync(abs)) {
|
|
696
|
-
preserved.push(rel);
|
|
697
|
-
continue;
|
|
698
|
-
}
|
|
699
|
-
await writeFile(abs, readSiteFile("asset", name), "utf8");
|
|
700
|
-
created.push(rel);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
catch (err) {
|
|
704
|
-
log.error(`docs: failed to scaffold site templates: ${describeError(err)}`);
|
|
705
|
-
return 1;
|
|
706
|
-
}
|
|
707
|
-
log.info(`meta docs --scaffold-site — ${created.length} created, ${preserved.length} preserved ` +
|
|
708
|
-
`→ ${join(projectRoot, "codegen/docs-site")} (edit these to own your theme)`);
|
|
709
|
-
return 0;
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* Emit the browsable HTML documentation site via `@metaobjectsdev/docs-site`.
|
|
713
|
-
* The site loads the model with its OWN loader from the collection's declared
|
|
714
|
-
* source ROOTS (whole directories, one page group each) rather than from the
|
|
715
|
-
* per-file list the sdk `loadMemory` path takes, so this is independent of the
|
|
716
|
-
* markdown surfaces. Writes under `<outDir>/site` so it can coexist with the markdown
|
|
717
|
-
* output. Scaffold-and-own: when the consumer has copied templates/assets
|
|
718
|
-
* into `<projectRoot>/codegen/docs-site/` (via `--scaffold-site`), those win
|
|
719
|
-
* over the bundled defaults.
|
|
720
|
-
*
|
|
721
|
-
* Takes the ALREADY-RESOLVED collection: `docsCommand` resolved it to read the
|
|
722
|
-
* config from the right directory, and resolving a second time here made the
|
|
723
|
-
* combined `--model --site` path do the whole discovery-and-config walk twice.
|
|
724
|
-
*/
|
|
725
|
-
async function emitSite(collection, projectRoot,
|
|
726
|
-
/** Directory holding `metaobjects.config.ts` — where `--scaffold-site` writes the
|
|
727
|
-
* owned theme, so where this must read it from. Distinct from `collection.configDir`
|
|
728
|
-
* since #326: in a polyglot monorepo the collection is declared at the repo root
|
|
729
|
-
* while the TS package (and its owned theme) sits in the app. */
|
|
730
|
-
genConfigDir, outDir, configProviders, promptsDir) {
|
|
731
|
-
const siteOutDir = resolvePath(outDir, "site");
|
|
732
|
-
// The resolved metadata source dir(s) are REQUIRED (the site loads the
|
|
733
|
-
// model from them) and always first. Prompt `.mustache` source is
|
|
734
|
-
// additionally searched in the conventional <root>/templates/ and any
|
|
735
|
-
// explicit --prompts dir (for a project whose templates live elsewhere,
|
|
736
|
-
// e.g. data/templates/) — else the site can't show the prompt TEXT and
|
|
737
|
-
// prints a "source missing" note. Only existing dirs are added.
|
|
738
|
-
//
|
|
739
|
-
// Deduped by resolved PATH, not by basename. Two DIFFERENT directories that
|
|
740
|
-
// happen to share a basename are a legitimate multi-source project (`metaobjects`
|
|
741
|
-
// plus `../shared-model/metaobjects`); `loadModel` disambiguates their site
|
|
742
|
-
// group names, so refusing the pair here — which a basename key did, by
|
|
743
|
-
// dropping the second — would break the feature this branch exists to ship.
|
|
744
|
-
// The same directory named twice is the real hazard: it would be symlinked
|
|
745
|
-
// and loaded twice.
|
|
746
|
-
// The DECLARED source roots, not directories re-derived from the resolved
|
|
747
|
-
// files: a declared source directory holding no metadata yet would otherwise
|
|
748
|
-
// vanish from the site's group list entirely, and `sourceDirs` could come back
|
|
749
|
-
// empty where the pre-branch code always passed `<root>/metaobjects`.
|
|
750
|
-
// `collection.sourceRoots` deliberately EXCLUDES dependency artifacts (sdk's
|
|
751
|
-
// collection.ts: "a dependency's artifact is not a source root"). But
|
|
752
|
-
// `generateSite` below does NOT reuse the already-loaded `collection` root —
|
|
753
|
-
// it runs its own independent `loadModel` over exactly the dirs handed to
|
|
754
|
-
// it. Without the resolved snapshot dirs appended here, a consumer that
|
|
755
|
-
// `extends` an imported abstract or `overlay: true`s an imported node — the
|
|
756
|
-
// two constructs entities.md and abstracts-and-inheritance.md recommend for
|
|
757
|
-
// this exact boundary — hits `ERR_UNRESOLVED_SUPER` / `ERR_OVERLAY_NO_TARGET`
|
|
758
|
-
// in THIS loader even though the same metadata resolves fine through
|
|
759
|
-
// `loadMemory` above. `loadModel` handles a dependency's snapshot dir
|
|
760
|
-
// natively: each holds exactly the one artifact file, which its own
|
|
761
|
-
// `.(json|ya?ml)$` file walk picks up like any other source dir.
|
|
762
|
-
const sourceDirs = [
|
|
763
|
-
...collection.sourceRoots,
|
|
764
|
-
...new Set(collection.dependencies.map((d) => dirname(d.artifactPath))),
|
|
765
|
-
];
|
|
766
|
-
const seenDirs = new Set(sourceDirs);
|
|
767
|
-
if (promptsDir !== undefined && !existsSync(promptsDir)) {
|
|
768
|
-
log.warn(`docs: --prompts dir does not exist: ${promptsDir}`);
|
|
769
|
-
}
|
|
770
|
-
for (const d of [join(projectRoot, "templates"), ...(promptsDir !== undefined ? [promptsDir] : [])]) {
|
|
771
|
-
const abs = resolvePath(d);
|
|
772
|
-
if (existsSync(abs) && !seenDirs.has(abs)) {
|
|
773
|
-
sourceDirs.push(abs);
|
|
774
|
-
seenDirs.add(abs);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
// Scaffold-and-own: when the consumer has copied templates/assets into
|
|
778
|
-
// codegen/docs-site/ (via --scaffold-site), use those; else the bundled defaults.
|
|
779
|
-
// Keyed on `genConfigDir`, NOT `projectRoot`: `--templates` redirects the adopter
|
|
780
|
-
// RENDER template chain (the `templates/` above), and letting it also move the
|
|
781
|
-
// docs-site theme would read it from somewhere `--scaffold-site` never writes.
|
|
782
|
-
const ownedTemplates = join(genConfigDir, "codegen/docs-site/templates");
|
|
783
|
-
const ownedAssets = join(genConfigDir, "codegen/docs-site/assets");
|
|
784
|
-
try {
|
|
785
|
-
const r = await generateSite({
|
|
786
|
-
sourceDirs,
|
|
787
|
-
outDir: siteOutDir,
|
|
788
|
-
title: basename(collection.configDir) || "Metadata",
|
|
789
|
-
stamp: new Date().toISOString().slice(0, 10),
|
|
790
|
-
commit: "",
|
|
791
|
-
core: { n: 15 },
|
|
792
|
-
// Thread any consumer providers from metaobjects.config.ts so the site's
|
|
793
|
-
// own loader resolves custom field/view/object subtypes — same providers
|
|
794
|
-
// the markdown surfaces get via loadMemory.
|
|
795
|
-
...(configProviders !== undefined ? { extraProviders: configProviders } : {}),
|
|
796
|
-
...(existsSync(ownedTemplates) ? { templatesDir: ownedTemplates } : {}),
|
|
797
|
-
...(existsSync(ownedAssets) ? { assetsDir: ownedAssets } : {}),
|
|
798
|
-
});
|
|
799
|
-
log.info(`meta docs --site — wrote ${r.pages.length} page(s) → ${siteOutDir}`);
|
|
800
|
-
return 0;
|
|
801
|
-
}
|
|
802
|
-
catch (err) {
|
|
803
|
-
log.error(`docs: failed to generate site: ${describeError(err)}`);
|
|
804
|
-
return 1;
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
/**
|
|
808
|
-
* FR-033 S3 — emit the metamodel reference docs (INDEX.md + per-type pages +
|
|
809
|
-
* providers.md) from the BUILT-IN strict registry. No metadata, no config:
|
|
810
|
-
* `composeRegistry(coreProviders)` IS the source. Files land under
|
|
811
|
-
* `<out>` (which defaults to `./docs/metamodel`), preserving the renderer's
|
|
812
|
-
* `types/<family>.md` subtree.
|
|
813
|
-
*/
|
|
814
|
-
async function metamodelDocsCommand(cwd, out) {
|
|
815
|
-
const outDir = resolvePath(cwd, out);
|
|
816
|
-
let docs;
|
|
817
|
-
try {
|
|
818
|
-
docs = renderCoreMetamodelDocs(composeRegistry(coreProviders));
|
|
819
|
-
}
|
|
820
|
-
catch (err) {
|
|
821
|
-
log.error(`docs: failed to render metamodel docs: ${describeError(err)}`);
|
|
822
|
-
return 1;
|
|
823
|
-
}
|
|
824
|
-
try {
|
|
825
|
-
await mkdir(outDir, { recursive: true });
|
|
826
|
-
for (const [rel, content] of docs) {
|
|
827
|
-
const path = resolvePath(outDir, rel);
|
|
828
|
-
await mkdir(resolvePath(path, ".."), { recursive: true });
|
|
829
|
-
await writeFile(path, content, "utf8");
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
catch (err) {
|
|
833
|
-
log.error(`docs: failed to write metamodel pages: ${describeError(err)}`);
|
|
834
|
-
return 1;
|
|
835
|
-
}
|
|
836
|
-
log.info(`meta docs --metamodel — wrote ${docs.size} page(s) → ${outDir}`);
|
|
837
|
-
return 0;
|
|
10
|
+
import { REMOVED_DOCS_COMMAND } from "../lib/docs-removed.js";
|
|
11
|
+
export async function docsCommand(_args, _cwd) {
|
|
12
|
+
log.error(REMOVED_DOCS_COMMAND);
|
|
13
|
+
return 2;
|
|
838
14
|
}
|
|
839
15
|
//# sourceMappingURL=docs.js.map
|