diff --git a/.changeset/doc-component-types-md-surface.md b/.changeset/doc-component-types-md-surface.md
new file mode 100644
index 000000000..07abfde41
--- /dev/null
+++ b/.changeset/doc-component-types-md-surface.md
@@ -0,0 +1,7 @@
+---
+---
+
+`scripts/check-doc-component-types.mjs` now collects `.md` pages under `content/docs`
+alongside `.mdx`, converging on the `DOC_EXTENSIONS` spelling
+`scripts/check-doc-snippet-types.mjs` already uses. No published package changes: the
+diff is the gate script, its test, its workflow comments, and six documentation pages.
diff --git a/.github/workflows/doc-component-types.yml b/.github/workflows/doc-component-types.yml
index 67f037387..fd357f17b 100644
--- a/.github/workflows/doc-component-types.yml
+++ b/.github/workflows/doc-component-types.yml
@@ -5,8 +5,9 @@ name: Doc Component Types
# precisely the shape both of those workflows skip. `ci.yml`'s `type-check` job
# decides whether to run its expensive steps with a `git diff` that excludes
# `content/**`, `'**/*.md'`, `docs/**` and `apps/site/**` — so a PR that edits
-# only `content/docs/**.mdx` reports the context and runs none of the gates
-# inside it. A gate against a wrong `type` in a teaching snippet, wired there,
+# only `content/docs/**` reports the context and runs none of the gates inside
+# it. Note the `'**/*.md'` term especially: since objectui#5342 this gate reads
+# the `.md` guides too, which is exactly the extension that diff excludes. A gate against a wrong `type` in a teaching snippet, wired there,
# would be blind to every change that can introduce one.
#
# This is the fifth instance of the shape in this repo and the reasoning is
@@ -23,9 +24,9 @@ name: Doc Component Types
# gate, one home.
#
# It needs no install and no build. The script reads the checkout with `node:fs`
-# only: 143 mdx files for the snippets, and the `packages/` + `apps/` sources for
-# the registered-key universe it compares them against. A few seconds. Keep it
-# that way if you add checks to it — the moment this needs `pnpm install` it
+# only: 183 pages for the snippets (143 `.mdx` + 40 `.md`, objectui#5342), and
+# the `packages/` + `apps/` sources for the registered-key universe it compares
+# them against. A few seconds. Keep it that way if you add checks to it — the moment this needs `pnpm install` it
# stops being cheap enough to run unfiltered, and the filter is the hole.
on:
@@ -65,7 +66,7 @@ jobs:
with:
node-version: '22.x'
- # A `type` string in a `content/docs/**.mdx` code block is not rendered,
+ # A `type` string in a `content/docs/**` code block is not rendered,
# not parsed and not compared against anything, so a snippet can name a
# component that does not exist and every check in the repo stays green —
# while a reader who copies it gets the renderer's red "Unknown component
diff --git a/content/docs/api/schema-reference.md b/content/docs/api/schema-reference.md
index f9aa192bc..38cd3a77b 100644
--- a/content/docs/api/schema-reference.md
+++ b/content/docs/api/schema-reference.md
@@ -680,7 +680,7 @@ A single-record detail view with grouped fields, actions, and tabs.
"title": "Customer Info",
"fields": [
{ "name": "customer", "label": "Customer", "type": "text" },
- { "name": "email", "label": "Email", "type": "link" },
+ { "name": "email", "label": "Email", "type": "email" },
{ "name": "created", "label": "Created", "type": "date", "format": "MMM d, yyyy" }
]
},
@@ -1109,7 +1109,7 @@ An enhanced detail view for a single record with sections, tabs, related records
"fields": [
{ "name": "firstName", "label": "First Name", "type": "text" },
{ "name": "lastName", "label": "Last Name", "type": "text" },
- { "name": "email", "label": "Email", "type": "link" },
+ { "name": "email", "label": "Email", "type": "email" },
{ "name": "avatar", "label": "Photo", "type": "image" }
]
}
diff --git a/content/docs/guide/building-crud-app.md b/content/docs/guide/building-crud-app.md
index 3c7e6951b..7da10239e 100644
--- a/content/docs/guide/building-crud-app.md
+++ b/content/docs/guide/building-crud-app.md
@@ -209,7 +209,7 @@ function App() {
{ setEditId(row.id); setShowForm(true); }}
/>
@@ -246,7 +246,7 @@ Add a "New Task" button and handle row clicks to open the edit form:
{showForm && (
void })
{
+ // objectui#5342. The collector used to walk `.mdx` only, so 40 `.md` guides
+ // under the SAME tree were neither judged nor declared. This asserts the
+ // walk, not the verdict: revert `DOC_EXTENSIONS` to `['.mdx']` and the
+ // `from-md` site disappears while every other test in this file stays green.
+ const { sites, counters } = withTree((write) => {
+ write('content/docs/a.mdx', ['```json', '{ "type": "from-mdx" }', '```'].join('\n'));
+ write('content/docs/guide/b.md', ['```json', '{ "type": "from-md" }', '```'].join('\n'));
+ // Not a page: the `meta.json` sidecars fumadocs keeps beside the prose.
+ write('content/docs/meta.json', '{ "pages": ["a"] }');
+ }, (dir) => scanDocs(dir));
+ expect(sites.map((s) => s.value).sort()).toEqual(['from-md', 'from-mdx']);
+ expect(counters.files).toBe(2);
+ });
+
+ it('judges a `.md` page by the same rule, so an unregistered type there is a finding', () => {
+ const { findings } = withTree((write) => {
+ write('packages/demo/src/index.tsx', "ComponentRegistry.register('div', C, { namespace: 'ui' });\n");
+ write('content/docs/guide/b.md', ['```json', '{ "type": "not-a-component" }', '```'].join('\n'));
+ }, (dir) => analyze(dir, BARE));
+ const f = findings as Finding[];
+ expect(f.map((x) => x.reason)).toContain('unregistered-doc-type');
+ expect(f.find((x) => x.reason === 'unregistered-doc-type')?.site).toBe('content/docs/guide/b.md:2');
+ });
+
it('reports an unterminated fence rather than guessing where code stops', () => {
const { findings } = withTree((write) => {
write('content/docs/x.mdx', ['```json', '{ "type": "div" }'].join('\n'));
@@ -367,6 +392,18 @@ describe('the scan cannot collapse quietly', () => {
expect(counters.registered).toBeGreaterThan(400);
});
+ it('really walks the `.md` half of this tree — the objectui#5342 widening, pinned', () => {
+ // A repo-level assertion because the fixture above proves only the
+ // mechanism. `content/docs` holds 143 `.mdx` and 40 `.md`; a revert to
+ // `.mdx`-only drops ~323 `type` literals out of the scan and this repository
+ // stays green while judging none of them.
+ const { sites, counters } = scanDocs(repoRoot);
+ const mdFiles = new Set(sites.filter((s: { file: string }) => s.file.endsWith('.md')).map((s: { file: string }) => s.file));
+ expect(mdFiles.size, 'no `.md` page carries a scanned `type` literal — the collector narrowed').toBeGreaterThan(15);
+ expect(mdFiles.has('content/docs/api/schema-reference.md')).toBe(true);
+ expect(counters.files).toBeGreaterThan(170);
+ });
+
it('this repository is green', () => {
const findings = analyze(repoRoot).findings as Finding[];
expect(findings.map((f) => `${f.reason} :: ${f.site} :: ${f.value ?? ''}`)).toEqual([]);
@@ -408,6 +445,49 @@ describe('the three snippets this gate found on its first run stay fixed', () =>
// ── 6. the wiring ────────────────────────────────────────────────────────────
+describe('objectui#5342 — the key errors the widened collector found stay fixed', () => {
+ // Named rather than left to the repo-wide green assertion, for the same reason
+ // the block above names its three: these are the live specimens the extension
+ // widening produced, and a revert would otherwise read as an unrelated
+ // regression somewhere in a 183-file scan. Each one rendered the OBJUI-001
+ // "Unknown component type" panel for a reader who copied it.
+ const read = (rel: string) => fs.readFileSync(path.join(repoRoot, rel), 'utf8');
+
+ it('the CRUD guide spells the registered keys, not the PascalCase component names', () => {
+ const body = read('content/docs/guide/building-crud-app.md');
+ for (const wrong of ['ObjectGrid', 'ObjectForm', 'ObjectDetail']) {
+ expect(body, `${wrong} is a component NAME; the registry key is lower-kebab`).not.toContain(
+ `type: '${wrong}'`,
+ );
+ }
+ expect(body).toContain("type: 'object-grid'");
+ expect(body).toContain("type: 'object-form'");
+ expect(body).toContain("type: 'detail-view'");
+ });
+
+ it('the two empty-state snippets spell `empty`, the key EmptySchema declares', () => {
+ // packages/types/src/feedback.ts declares `type: 'empty'` and
+ // packages/components/src/renderers/feedback/empty.tsx registers it.
+ for (const rel of ['content/docs/guide/expressions.md', 'content/docs/guide/schema-rendering.md']) {
+ expect(read(rel), `${rel} still teaches empty-state`).not.toContain('"type": "empty-state"');
+ expect(read(rel)).toContain('"type": "empty"');
+ }
+ });
+
+ it('the playground teaches `grid`, in the fenced snippet AND in the prose beside it', () => {
+ const body = read('content/docs/guide/schema-playground.md');
+ expect(body, 'nothing registers grid-layout').not.toContain('grid-layout');
+ expect(body).toContain('"type": "grid"');
+ });
+
+ it('the schema reference gives its Email field a field type that exists', () => {
+ // `link` is not in fieldWidgetMap; `email` and `url` are.
+ const body = read('content/docs/api/schema-reference.md');
+ expect(body).not.toContain('"label": "Email", "type": "link"');
+ expect(body).toContain('"label": "Email", "type": "email"');
+ });
+});
+
describe('wiring — the gate is reachable and a docs-only PR starts it', () => {
const workflowDir = path.join(repoRoot, '.github/workflows');
const workflowPath = path.join(workflowDir, 'doc-component-types.yml');
@@ -444,7 +524,7 @@ describe('wiring — the gate is reachable and a docs-only PR starts it', () =>
it('runs it in NO path-filtered workflow — the change that breaks it is docs-only', () => {
// The whole reason this is its own workflow. `ci.yml`'s type-check job
// excludes `content/**` from the diff that decides whether its gates run, so
- // a PR editing only `content/docs/**.mdx` would start this gate nowhere.
+ // a PR editing only `content/docs/**` would start this gate nowhere.
expect(workflowFiles.length, 'the workflow directory scan returned implausibly few files').toBeGreaterThan(5);
for (const file of workflowFiles) {
const yaml = yamlOf(file);
diff --git a/scripts/check-doc-component-types.mjs b/scripts/check-doc-component-types.mjs
index 2a88fdf45..e8ea8e4a6 100644
--- a/scripts/check-doc-component-types.mjs
+++ b/scripts/check-doc-component-types.mjs
@@ -1,8 +1,8 @@
#!/usr/bin/env node
/**
- * Every `type` string literal in a `content/docs/**.mdx` code block must name a
- * component the repository actually registers — or be declared, per file, as
- * belonging to some other vocabulary.
+ * Every `type` string literal in a `content/docs/**` code block — `.mdx` and
+ * `.md` alike — must name a component the repository actually registers, or be
+ * declared, per file, as belonging to some other vocabulary.
*
* Run: node scripts/check-doc-component-types.mjs (also `pnpm check:doc-types`)
* Exit: 0 = every teaching snippet names a registered type (or a declared
@@ -142,9 +142,29 @@ const scriptDir = dirname(fileURLToPath(import.meta.url));
// ── Configuration ────────────────────────────────────────────────────────────
-/** Where the teaching prose lives. */
+/** Where the teaching prose lives. This gate walks `content/docs` and nothing
+ * else: not `skills/**`, not the package READMEs (`check-doc-snippet-types.mjs`
+ * covers those for its own question), not `docs/**`. */
const DOCS_ROOT = 'content/docs';
+/** Page extensions collected under `DOCS_ROOT`. BOTH are collected, and that is
+ * the whole content of the scan surface: `content/docs` is authored in a mix of
+ * `.mdx` and `.md` — the same guide tree, the same renderer, the same reader —
+ * and an extension is not a coverage decision. Deliberately kept identical to
+ * `check-doc-snippet-types.mjs`'s `DOC_EXTENSIONS`: two collectors walking one
+ * tree two different ways is a defect one level up from either gate, and it is
+ * how a page ends up covered by the question it passes and invisible to the one
+ * it fails.
+ *
+ * Collecting only `.mdx` is what objectui#5342 measured, and unlike its sibling
+ * objectui#5174 this file was never lying about it — the sentence above used to
+ * say `.mdx` and the ledger was keyed by `.mdx` paths throughout. It was a
+ * stated coverage decision, not a broken promise, and the decision is now the
+ * other way: 40 `.md` pages sat outside the ledger, so they were neither
+ * covered nor declared ungated. Anything else under the tree (the `meta.json`
+ * sidecars) holds no prose and is not a page. */
+const DOC_EXTENSIONS = ['.mdx', '.md'];
+
/** Where registrations live. Every workspace source root that can register. */
const SOURCE_ROOTS = ['packages', 'apps', 'examples'];
@@ -211,12 +231,36 @@ const OPEN_REGISTRATION_SITES = {
* Per-file declarations that a `type` value in that page belongs to a
* vocabulary other than the SDUI component registry.
*
- * Keyed `` -> `` -> reason. The reason must
+ * Keyed `` -> `` -> reason. The reason must
* name the vocabulary and, where one exists, where it is declared — an
* exemption that only says "not a component" teaches the next reader nothing
* and cannot be re-checked.
*/
const DOC_TYPE_EXEMPTIONS = {
+ 'content/docs/api/schema-reference.md': {
+ action:
+ 'ActionSchema discriminant under a CRUD schema\'s ACTION LISTS, never a rendered child — ' +
+ '`toolbar.actions[]`, `rowActions[]`, `batchActions[]` and a detail page\'s `actions[]` are ' +
+ 'each typed `ActionSchema[]` (packages/types/src/crud.ts:175, 379, 480, 484, 561, 612), and ' +
+ 'that interface declares `type: \'action\'` at crud.ts:89. Same vocabulary as the ' +
+ '`core/enhanced-actions.mdx` entry below.',
+ crud:
+ 'CRUDSchema discriminant — packages/types/src/crud.ts:418 declares it, zod/crud.zod.ts:158 ' +
+ 'validates it, core/src/validation/schema-validator.ts:135 has a branch for it and ' +
+ 'builder/schema-builder.ts:170 constructs it. FOUR declaration faces and NO registered ' +
+ 'renderer, and unlike its siblings in this table it IS on the render path (CRUDComponentSchema ' +
+ 'is in the node union at types/src/index.ts:852), so a node spelling it paints OBJUI-001. ' +
+ 'Ledgered rather than re-spelled because there is no registered spelling to move to: register ' +
+ 'a renderer / retire CRUDSchema under ADR-0049 / demote it off the node union are three ' +
+ 'different edits to this page, and picking one is a contract decision objectui#5115 left open ' +
+ 'after PR objectui#5128 closed only its CLI half. Filed as objectui#5373. DELETE this entry ' +
+ 'when that lands — the gate reports a stale exemption, so it cannot be forgotten.',
+ string:
+ 'PageNodeSchema variable declaration\'s data type inside `variables[]`, next to `name` / ' +
+ '`defaultValue` — `PageVariable` (packages/types/src/layout.ts:566, re-exported from ' +
+ '@objectstack/spec\'s `PageVariableSchema`). Same vocabulary as blocks/block-schema.mdx\'s ' +
+ '`string`.',
+ },
'content/docs/blocks/authentication.mdx': {
submit:
'ActionSchema discriminant under a button\'s `action` key, not a node type. ' +
@@ -264,6 +308,13 @@ const DOC_TYPE_EXEMPTIONS = {
'First member of a TypeScript union of view-action ids (`\'share\' | \'settings\' | ' +
'\'duplicate\' | \'delete\'`) in a Schema API declaration, not a node type.',
},
+ 'content/docs/components/index.md': {
+ 'component-name':
+ 'Metasyntactic placeholder in the page\'s "Usage Pattern" template — the block shows the SHAPE ' +
+ 'every component schema has (`type` / `className` / component-specific props) and the value ' +
+ 'stands for whichever key the reader picked from the catalog below it. Nothing registers the ' +
+ 'literal string, by design.',
+ },
'content/docs/core/app-schema.mdx': {
item: 'AppSchema menu entry kind — a navigation item, sibling of `group`. Not a rendered node.',
group: 'AppSchema menu entry kind — a navigation group holding `children` items.',
@@ -299,11 +350,129 @@ const DOC_TYPE_EXEMPTIONS = {
array: 'JSON Schema property type inside a field\'s `schema.properties`, not a node type.',
string: 'JSON Schema property type inside a field\'s `schema.properties`, not a node type.',
},
+ 'content/docs/guide/architecture.md': {
+ 'my-grid':
+ 'Deliberate placeholder in the "register your component, then address it by key" contrast — ' +
+ 'the snippet\'s own line above spells `ComponentRegistry.register(\'my-grid\', MyGrid)`, so it ' +
+ 'is unregistered in this repository by design.',
+ string:
+ '`ComponentInput.type` in a `register(...)` call\'s `inputs[]` — a DESIGNER input\'s coarse ' +
+ 'control kind (packages/types/src/base.ts:386), sibling of `number` / `boolean` / `enum`. ' +
+ 'Not a node type.',
+ },
+ 'content/docs/guide/component-registry.md': {
+ custom:
+ 'Placeholder discriminant in a "type your custom component" `interface CustomSchema extends ' +
+ 'BaseSchema` declaration — the page is teaching the reader to declare their own schema ' +
+ 'interface, so the literal is theirs to register.',
+ 'my-component':
+ 'Deliberate placeholder in the "register a custom component" walkthrough — the page registers ' +
+ 'this key itself (`ComponentRegistry.register(\'my-component\', MyComponent, …)`) and then ' +
+ 'shows the JSON that addresses it.',
+ string:
+ '`ComponentInput.type` in a `register(...)` call\'s `inputs[]` — a designer input\'s coarse ' +
+ 'control kind (packages/types/src/base.ts:386), not a node type.',
+ },
+ 'content/docs/guide/console-architecture.md': {
+ delete:
+ '`ActionDef.type` passed to `useActionRunner().execute(...)` — a RunnableActionType, the ' +
+ 'action vocabulary declared at packages/core/src/actions/ActionRunner.ts:112. An action being ' +
+ 'run, not a node being rendered.',
+ },
+ 'content/docs/guide/dashboard-filters.md': {
+ bar: 'Dashboard widget kind under `widgets[]`, alongside `line` — same vocabulary as the ' +
+ 'plugins/plugin-dashboard.mdx entry below. Not a node type.',
+ line: 'Dashboard widget kind under `widgets[]`, alongside `bar` — same vocabulary as the ' +
+ 'plugins/plugin-dashboard.mdx entry below. Not a node type.',
+ },
'content/docs/guide/objectos-integration.mdx': {
'my-custom-widget':
'Deliberate placeholder in the "register a lazy custom widget" walkthrough — the reader ' +
'supplies this key.',
},
+ 'content/docs/guide/plugin-development.md': {
+ array:
+ '`ComponentInput.type` in this walkthrough\'s own `register(...)` `inputs[]` — a designer ' +
+ 'input\'s coarse control kind (packages/types/src/base.ts:386), not a node type.',
+ board:
+ 'The walkthrough\'s OWN plugin key. This page builds `@object-ui/plugin-board` end to end, so ' +
+ 'every `board` here — the `BoardSchema` interface, the `register(\'board\', BoardRenderer, …)` ' +
+ 'call, the test fixture and the final JSON — is the key the READER registers by following the ' +
+ 'page. Unregistered in this repository by design.',
+ enum: '`ComponentInput.type` in the walkthrough\'s `inputs[]` — the coarse kind that carries an ' +
+ '`enum` list of allowed values, not a node type.',
+ string:
+ '`ComponentInput.type` in the walkthrough\'s `inputs[]` (packages/types/src/base.ts:386), not ' +
+ 'a node type.',
+ },
+ 'content/docs/guide/plugins.md': {
+ module:
+ 'The `package.json` manifest\'s OWN `"type": "module"` field — Node\'s ESM switch, in a block ' +
+ 'showing the plugin package\'s manifest. Not a UI schema at all. objectui#5127 is the same ' +
+ 'collision measured on `objectui check`, which read every JSON file\'s root `type` as a ' +
+ 'component key and reported `module` as unknown in any Node project.',
+ 'my-feature':
+ 'Deliberate placeholder for the reader\'s own plugin schema — the page\'s `MyFeatureSchema ' +
+ 'extends BaseSchema` declaration in the "author your plugin\'s types" step.',
+ },
+ 'content/docs/guide/record-edit-modes.md': {
+ picklist:
+ 'ObjectStack object-metadata FIELD type inside a `fields` record, alongside `text` and ' +
+ '`lookup` — the picker/lookup family spelling packages/core/src/utils/record-title.ts:101 ' +
+ 'names explicitly. A field\'s data type, not a node type.',
+ },
+ 'content/docs/guide/schema-overview.md': {
+ action:
+ 'ActionSchema discriminant in a `const action: ActionSchema = { … }` declaration — this page ' +
+ 'tours each schema family by declaring one of each, so the literal is the document\'s own ' +
+ 'discriminant. Same vocabulary as core/enhanced-actions.mdx.',
+ block:
+ 'BlockSchema discriminant in a `const block: BlockSchema = { … }` declaration — ' +
+ 'packages/types/src/blocks.ts, validated by zod/blocks.zod.ts. A block definition is not a ' +
+ 'rendered node.',
+ group: 'AppSchema menu entry kind — a navigation group holding `children` items, same ' +
+ 'vocabulary as core/app-schema.mdx.',
+ item: 'AppSchema menu entry kind — a navigation item, sibling of `group`. Same vocabulary as ' +
+ 'core/app-schema.mdx. Not a rendered node.',
+ string:
+ 'BlockVariable.type in the BlockSchema tour\'s `variables[]` — a variable declaration\'s data ' +
+ 'type, next to `name` / `defaultValue`.',
+ theme:
+ 'ThemeSchema discriminant in a `const theme: ThemeComponentSchema = { … }` declaration — ' +
+ 'packages/types/src/theme.ts declares the theme document\'s own `type`, validated by ' +
+ 'zod/theme.zod.ts. Same vocabulary as core/theme-schema.mdx.',
+ },
+ 'content/docs/guide/schema-playground.md': {
+ reset:
+ 'ActionSchema discriminant under a form\'s `actions[]`, alongside `submit` — an action ' +
+ 'definition in a list, not a rendered child. Same vocabulary as blocks/authentication.mdx.',
+ submit:
+ 'ActionSchema discriminant under a form\'s `actions[]`, alongside `reset` — an action ' +
+ 'definition in a list, not a rendered child. Same vocabulary as blocks/authentication.mdx.',
+ },
+ 'content/docs/guide/schema-rendering.md': {
+ 'admin-panel':
+ 'Stand-in for one of the READER\'s own registered components in the "move logic to ' +
+ 'expressions" pattern block, whose subject is `visibleOn` — the two nodes exist to be shown ' +
+ 'and hidden, and nothing about the pattern depends on which components they are. Weaker than ' +
+ 'the `my-component` placeholder above it, which the same page registers in its own snippet: ' +
+ 'these two are never registered on the page, so the name alone does not announce that they ' +
+ 'are the reader\'s. Recorded here as the disclosed cost of leaving the block\'s subject alone.',
+ 'my-component':
+ 'Deliberate placeholder — the snippet\'s own line above spells ' +
+ '`ComponentRegistry.register(\'my-component\', MyComponent)`, then shows the schema that ' +
+ 'addresses it.',
+ 'user-panel':
+ 'Stand-in for one of the READER\'s own registered components in the `visibleOn` pattern block, ' +
+ 'the `${!user.isAdmin}` half of the pair — see the `admin-panel` entry above for the full ' +
+ 'reason and its known weakness.',
+ },
+ 'content/docs/plugins/index.md': {
+ 'plugin-component-name':
+ 'Metasyntactic placeholder in the page\'s "Usage Pattern" template — the block shows the shape ' +
+ 'every plugin node has and the value stands for whichever plugin key the reader picked from ' +
+ 'the table above it. Nothing registers the literal string, by design.',
+ },
'content/docs/plugins/plugin-dashboard.mdx': {
bar: 'Dashboard widget kind under `widgets[]`, alongside `line`. Not a node type.',
line: 'Dashboard widget kind under `widgets[]`, alongside `bar`. Not a node type.',
@@ -694,7 +863,7 @@ export function deriveRegistryKeys(root, options = {}) {
*/
export function scanDocs(root) {
const docsDir = join(root, DOCS_ROOT);
- const files = walkFiles(docsDir, (f) => f.endsWith('.mdx')).sort();
+ const files = walkFiles(docsDir, (f) => DOC_EXTENSIONS.some((ext) => f.endsWith(ext))).sort();
const sites = [];
const counters = { files: files.length, codeBlocks: 0, typeSites: 0 };
@@ -837,7 +1006,7 @@ const HINTS = {
'stale-indirect-registration':
'An INDIRECT_REGISTRATIONS entry no longer resolves to keys, so the universe lost them silently.',
'unterminated-code-fence':
- 'An mdx file has an unclosed ``` fence. The scan cannot separate code from prose past that point.',
+ 'A doc file has an unclosed ``` fence. The scan cannot separate code from prose past that point.',
};
const invokedDirectly = process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
@@ -875,7 +1044,8 @@ if (invokedDirectly) {
}
console.log(
- `Scanned ${counters.files} mdx file(s), ${counters.codeBlocks} code block(s), ` +
+ `Scanned ${counters.files} doc file(s) (${DOC_EXTENSIONS.join(' + ')}), ` +
+ `${counters.codeBlocks} code block(s), ` +
`${counters.typeSites} \`type\` literal(s) against ${counters.registryKeys} registered key(s) ` +
`derived from ${counters.sourceFiles} source file(s) (${counters.resolved} resolved call site(s), ` +
`${counters.indirect} indirect, ${counters.open} open): ` +