@open-agent-toolkit/cli 0.3.25 → 0.3.27
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 +6 -0
- package/assets/bundle-metadata.json +1 -1
- package/assets/docs/contributing/documentation.md +39 -6
- package/assets/docs/contributing/markdown-features.md +14 -2
- package/assets/docs/docs-tooling/add-docs-to-a-repo.md +26 -15
- package/assets/docs/docs-tooling/commands.md +13 -10
- package/assets/docs/docs-tooling/workflows.md +14 -10
- package/assets/docs/getting-started/tool-packs.md +5 -3
- package/assets/docs/reference/cli-reference.md +1 -1
- package/assets/docs/reference/configuration.md +2 -1
- package/assets/docs/reference/docs-index-contract.md +19 -6
- package/assets/docs/reference/oat-directory-structure.md +13 -0
- package/assets/docs/skills/index.md +8 -8
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-docs-analyze/SKILL.md +2 -2
- package/assets/skills/oat-docs-analyze/references/legacy-scaffold-checks.md +4 -0
- package/assets/skills/oat-docs-bootstrap/SKILL.md +49 -62
- package/assets/templates/docs-app-fuma/AGENTS.md +50 -0
- package/assets/templates/docs-app-fuma/app/[[...slug]]/page.tsx +7 -3
- package/assets/templates/docs-app-fuma/app/llms/[...slug]/route.ts +15 -0
- package/assets/templates/docs-app-fuma/app/llms-full.txt/route.ts +7 -0
- package/assets/templates/docs-app-fuma/app/llms.txt/route.ts +7 -0
- package/assets/templates/docs-app-fuma/docs/contributing/docs-toolchain.md +48 -0
- package/assets/templates/docs-app-fuma/docs/contributing/index.md +13 -0
- package/assets/templates/docs-app-fuma/docs/contributing.md +77 -41
- package/assets/templates/docs-app-fuma/docs/getting-started.md +3 -33
- package/assets/templates/docs-app-fuma/docs/index.md +2 -2
- package/assets/templates/docs-app-fuma/lib/source.ts +5 -0
- package/assets/templates/docs-app-fuma/package.json.template +1 -1
- package/assets/templates/docs-app-fuma/source.config.ts +6 -1
- package/assets/templates/docs-app-mkdocs/AGENTS.md +46 -0
- package/assets/templates/docs-app-mkdocs/docs/contributing/docs-toolchain.md +44 -0
- package/assets/templates/docs-app-mkdocs/docs/contributing/index.md +13 -0
- package/assets/templates/docs-app-mkdocs/docs/contributing.md +105 -21
- package/assets/templates/docs-app-mkdocs/docs/getting-started.md +7 -25
- package/assets/templates/docs-app-mkdocs/docs/index.md +7 -2
- package/assets/templates/docs-app-mkdocs/mkdocs.yml +1 -0
- package/assets/templates/docs-app-mkdocs/package.json.template +2 -2
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +21 -3
- package/dist/commands/docs/index-generate/generator.d.ts +4 -34
- package/dist/commands/docs/index-generate/generator.d.ts.map +1 -1
- package/dist/commands/docs/index-generate/generator.js +51 -157
- package/dist/commands/docs/index-generate/index.d.ts +1 -1
- package/dist/commands/docs/index-generate/index.d.ts.map +1 -1
- package/dist/commands/docs/index-generate/index.js +26 -8
- package/dist/commands/docs/init/docs-commands.d.ts +1 -0
- package/dist/commands/docs/init/docs-commands.d.ts.map +1 -1
- package/dist/commands/docs/init/docs-commands.js +8 -3
- package/dist/commands/docs/init/index.d.ts.map +1 -1
- package/dist/commands/docs/init/index.js +21 -1
- package/dist/commands/docs/init/scaffold.d.ts.map +1 -1
- package/dist/commands/docs/init/scaffold.js +37 -16
- package/dist/commands/docs/nav/contents.d.ts +13 -3
- package/dist/commands/docs/nav/contents.d.ts.map +1 -1
- package/dist/commands/docs/nav/contents.js +85 -24
- package/dist/commands/docs/nav/fumadocs.d.ts +1 -0
- package/dist/commands/docs/nav/fumadocs.d.ts.map +1 -1
- package/dist/commands/docs/nav/fumadocs.js +56 -17
- package/dist/commands/docs/nav/sync.d.ts +2 -0
- package/dist/commands/docs/nav/sync.d.ts.map +1 -1
- package/dist/commands/docs/nav/sync.js +21 -1
- package/dist/commands/docs/shared/exclusions.d.ts +37 -0
- package/dist/commands/docs/shared/exclusions.d.ts.map +1 -0
- package/dist/commands/docs/shared/exclusions.js +161 -0
- package/dist/config/oat-config.d.ts +2 -0
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +6 -3
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +1 -0
- package/package.json +2 -2
- package/assets/skills/oat-docs-bootstrap/assets/AGENTS.md.template +0 -64
|
@@ -2,10 +2,15 @@ import { readdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
2
2
|
import { basename, dirname, isAbsolute, join, relative, resolve, } from 'node:path';
|
|
3
3
|
import { dirExists, ensureDir, fileExists } from '../../../fs/io.js';
|
|
4
4
|
import { OAT_VERSION } from '../../../shared/oat-version.js';
|
|
5
|
-
import { buildDocsCommands } from './docs-commands.js';
|
|
5
|
+
import { buildDocsCommands, shellQuote } from './docs-commands.js';
|
|
6
6
|
import { applyMarkdownDocsPlan, planMarkdownDocs } from './markdown.js';
|
|
7
7
|
import { getTemplateDir } from './resolve-options.js';
|
|
8
8
|
const MKDOCS_TEMPLATE_FILES = [
|
|
9
|
+
{ source: 'AGENTS.md', destination: 'AGENTS.md' },
|
|
10
|
+
...['index.md', 'docs-toolchain.md'].map((name) => ({
|
|
11
|
+
source: join('docs', 'contributing', name),
|
|
12
|
+
destination: join('docs', 'contributing', name),
|
|
13
|
+
})),
|
|
9
14
|
{ source: '.gitignore', destination: '.gitignore' },
|
|
10
15
|
{ source: 'mkdocs.yml', destination: 'mkdocs.yml' },
|
|
11
16
|
{ source: 'package.json.template', destination: 'package.json' },
|
|
@@ -22,6 +27,15 @@ const MKDOCS_TEMPLATE_FILES = [
|
|
|
22
27
|
},
|
|
23
28
|
];
|
|
24
29
|
const FUMA_TEMPLATE_FILES = [
|
|
30
|
+
{ source: 'AGENTS.md', destination: 'AGENTS.md' },
|
|
31
|
+
...['index.md', 'docs-toolchain.md'].map((name) => ({
|
|
32
|
+
source: join('docs', 'contributing', name),
|
|
33
|
+
destination: join('docs', 'contributing', name),
|
|
34
|
+
})),
|
|
35
|
+
...['llms.txt', 'llms-full.txt', 'llms/[...slug]'].map((route) => ({
|
|
36
|
+
source: join('app', route, 'route.ts'),
|
|
37
|
+
destination: join('app', route, 'route.ts'),
|
|
38
|
+
})),
|
|
25
39
|
{ source: '.gitignore', destination: '.gitignore' },
|
|
26
40
|
{ source: 'next.config.js', destination: 'next.config.js' },
|
|
27
41
|
{ source: 'postcss.config.mjs', destination: 'postcss.config.mjs' },
|
|
@@ -135,16 +149,6 @@ export async function resolveOatDepContext(repoRoot, assetsRoot) {
|
|
|
135
149
|
};
|
|
136
150
|
return { isOatRepo, localPackages, oatPackageVersions };
|
|
137
151
|
}
|
|
138
|
-
function buildDevDependencies(lint, format) {
|
|
139
|
-
const entries = [];
|
|
140
|
-
if (lint === 'markdownlint-cli2') {
|
|
141
|
-
entries.push(' "markdownlint-cli2": "^0.13.0"');
|
|
142
|
-
}
|
|
143
|
-
if (format === 'oxfmt') {
|
|
144
|
-
entries.push(' "oxfmt": "^0.36.0"');
|
|
145
|
-
}
|
|
146
|
-
return entries.join(',\n');
|
|
147
|
-
}
|
|
148
152
|
function buildFumaDevDependencies(lint, format) {
|
|
149
153
|
const entries = [];
|
|
150
154
|
if (lint === 'markdownlint-cli2') {
|
|
@@ -158,9 +162,17 @@ function buildFumaDevDependencies(lint, format) {
|
|
|
158
162
|
}
|
|
159
163
|
return `,\n${entries.join(',\n')}`;
|
|
160
164
|
}
|
|
165
|
+
function jsonStringContent(value) {
|
|
166
|
+
return JSON.stringify(value).slice(1, -1);
|
|
167
|
+
}
|
|
168
|
+
function buildNavSyncCheckCmd(isOatRepo, targetDir) {
|
|
169
|
+
return isOatRepo
|
|
170
|
+
? `pnpm -w run cli:source -- docs nav sync --target-dir ${shellQuote(targetDir)} --check`
|
|
171
|
+
: 'oat docs nav sync --target-dir . --check';
|
|
172
|
+
}
|
|
161
173
|
function buildGenerateIndexCmd(isOatRepo, targetDir) {
|
|
162
174
|
if (isOatRepo) {
|
|
163
|
-
return `pnpm -w run cli:source -- docs generate-index --docs-dir ${targetDir}/docs --output ${targetDir}/index.md`;
|
|
175
|
+
return `pnpm -w run cli:source -- docs generate-index --docs-dir ${shellQuote(`${targetDir}/docs`)} --output ${shellQuote(`${targetDir}/index.md`)}`;
|
|
164
176
|
}
|
|
165
177
|
return 'oat docs generate-index --docs-dir docs --output index.md';
|
|
166
178
|
}
|
|
@@ -221,9 +233,10 @@ async function buildPackageManagerField(options, appRoot, dependencies) {
|
|
|
221
233
|
const version = normalizePnpmVersion(await dependencies.detectPnpmVersion());
|
|
222
234
|
return `,\n "packageManager": ${JSON.stringify(`pnpm@${version}`)}`;
|
|
223
235
|
}
|
|
224
|
-
function renderTemplate(template, options, depContext, packageManagerField, nodeRequirement) {
|
|
236
|
+
function renderTemplate(template, options, depContext, packageManagerField, nodeRequirement, packageJson) {
|
|
225
237
|
const repoName = basename(options.repoRoot);
|
|
226
238
|
const commands = buildDocsCommands(options.repoShape, options.targetDir, options.appName);
|
|
239
|
+
const command = packageJson ? jsonStringContent : (value) => value;
|
|
227
240
|
const replacements = {
|
|
228
241
|
'// {{TURBOPACK_ROOT_IMPORTS}}\n': options.repoShape === 'nested-standalone'
|
|
229
242
|
? "import { dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n"
|
|
@@ -231,6 +244,7 @@ function renderTemplate(template, options, depContext, packageManagerField, node
|
|
|
231
244
|
' // {{TURBOPACK_ROOT_OPTION}}\n': options.repoShape === 'nested-standalone'
|
|
232
245
|
? ' turbopack: { root: dirname(fileURLToPath(import.meta.url)) },\n'
|
|
233
246
|
: '',
|
|
247
|
+
'{{OAT_CONFIG_RELATIVE_PATH}}': `./${relative(resolve(options.repoRoot, options.targetDir), join(options.repoRoot, '.oat/config.json')).replaceAll('\\', '/')}`,
|
|
234
248
|
'{{APP_NAME}}': options.appName,
|
|
235
249
|
'{{NODE_REQUIREMENT}}': nodeRequirement,
|
|
236
250
|
'{{PACKAGE_NAME}}': options.appName,
|
|
@@ -252,11 +266,16 @@ function renderTemplate(template, options, depContext, packageManagerField, node
|
|
|
252
266
|
'{{DOCS_FORMAT_CHECK_SCRIPT}}': options.format === 'oxfmt'
|
|
253
267
|
? "oxfmt --check 'docs/**/*.md'"
|
|
254
268
|
: "echo 'docs format check disabled'",
|
|
255
|
-
'{{DEV_DEPENDENCIES}}':
|
|
269
|
+
'{{DEV_DEPENDENCIES}}': buildFumaDevDependencies(options.lint, options.format),
|
|
256
270
|
'{{FUMA_DEV_DEPENDENCIES}}': buildFumaDevDependencies(options.lint, options.format),
|
|
257
271
|
'{{REPO_NAME}}': repoName,
|
|
258
272
|
'{{APP_DIR}}': options.targetDir,
|
|
259
|
-
'{{
|
|
273
|
+
'{{APP_CWD}}': shellQuote(options.targetDir),
|
|
274
|
+
'{{LINT_STATUS}}': options.lint === 'none' ? 'disabled' : 'enabled (markdownlint-cli2)',
|
|
275
|
+
'{{FORMAT_STATUS}}': options.format === 'none' ? 'disabled' : 'enabled (oxfmt)',
|
|
276
|
+
'{{NAV_SYNC_CMD}}': command(buildNavSyncCheckCmd(depContext.isOatRepo, options.targetDir).replace(/ --check$/, '')),
|
|
277
|
+
'{{GENERATE_INDEX_CMD}}': command(buildGenerateIndexCmd(depContext.isOatRepo, options.targetDir)),
|
|
278
|
+
'{{NAV_SYNC_CHECK_CMD}}': command(buildNavSyncCheckCmd(depContext.isOatRepo, options.targetDir)),
|
|
260
279
|
'{{OAT_DOCS_CONFIG_DEP}}': oatDepVersion(depContext, 'docs-config'),
|
|
261
280
|
'{{OAT_DOCS_THEME_DEP}}': oatDepVersion(depContext, 'docs-theme'),
|
|
262
281
|
'{{OAT_DOCS_TRANSFORMS_DEP}}': oatDepVersion(depContext, 'docs-transforms'),
|
|
@@ -285,12 +304,14 @@ export function buildDocumentationConfig(framework, targetDir) {
|
|
|
285
304
|
return {
|
|
286
305
|
root: targetDir,
|
|
287
306
|
tooling: 'fumadocs',
|
|
307
|
+
navOwner: 'nav-sync',
|
|
288
308
|
index: join(targetDir, 'index.md'),
|
|
289
309
|
};
|
|
290
310
|
}
|
|
291
311
|
return {
|
|
292
312
|
root: targetDir,
|
|
293
313
|
tooling: 'mkdocs',
|
|
314
|
+
navOwner: 'nav-sync',
|
|
294
315
|
config: join(targetDir, 'mkdocs.yml'),
|
|
295
316
|
index: join(targetDir, 'mkdocs.yml'),
|
|
296
317
|
};
|
|
@@ -354,7 +375,7 @@ export async function scaffoldDocsApp(options, overrides = {}) {
|
|
|
354
375
|
const source = join(templateRoot, templateFile.source);
|
|
355
376
|
const destination = join(appRoot, templateFile.destination);
|
|
356
377
|
const template = await readFile(source, 'utf8');
|
|
357
|
-
const rendered = renderTemplate(template, options, depContext, packageManagerField, nodeRequirement);
|
|
378
|
+
const rendered = renderTemplate(template, options, depContext, packageManagerField, nodeRequirement, templateFile.destination === 'package.json');
|
|
358
379
|
await ensureDir(dirname(destination));
|
|
359
380
|
await writeFile(destination, rendered, 'utf8');
|
|
360
381
|
createdFiles.push(templateFile.destination);
|
|
@@ -1,21 +1,31 @@
|
|
|
1
|
+
import { type ExclusionMatcher } from '../../docs/shared/exclusions.js';
|
|
1
2
|
export interface DocsContentsEntry {
|
|
2
3
|
title: string;
|
|
3
4
|
href: string;
|
|
5
|
+
group?: {
|
|
6
|
+
title: string;
|
|
7
|
+
line: number;
|
|
8
|
+
};
|
|
4
9
|
}
|
|
5
|
-
|
|
10
|
+
/** Shared discovery lets inventory generation retain its no-map fallback. */
|
|
11
|
+
export declare function hasIndexContents(markdown: string): boolean;
|
|
12
|
+
export declare function parseIndexContents(markdown: string, sourcePath?: string, options?: {
|
|
13
|
+
allowEmpty?: boolean;
|
|
14
|
+
}): DocsContentsEntry[];
|
|
6
15
|
export type DocsNavTree = Array<Record<string, DocsNavTree | string> | string>;
|
|
7
16
|
interface BuildDocsNavTreeOptions {
|
|
8
17
|
docsRoot: string;
|
|
9
18
|
dirRelativePath?: string;
|
|
10
19
|
visitedDirectories?: Set<string>;
|
|
20
|
+
nonReaderPaths?: readonly string[];
|
|
11
21
|
}
|
|
12
22
|
export declare function resolveEntryTarget(docsRoot: string, directoryPath: string, dirRelativePath: string, entry: DocsContentsEntry,
|
|
13
23
|
/**
|
|
14
24
|
* Page extensions a Contents link may target. MkDocs renders only `.md`;
|
|
15
25
|
* Fumadocs also renders `.mdx` pages.
|
|
16
26
|
*/
|
|
17
|
-
pageExtensions?: readonly string[]): Promise<{
|
|
18
|
-
kind: 'page' | 'section';
|
|
27
|
+
pageExtensions?: readonly string[], matcher?: ExclusionMatcher): Promise<{
|
|
28
|
+
kind: 'page' | 'section' | 'excluded';
|
|
19
29
|
path: string;
|
|
20
30
|
}>;
|
|
21
31
|
export declare function buildDocsNavTree(options: BuildDocsNavTreeOptions): Promise<DocsNavTree>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contents.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/nav/contents.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"contents.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/nav/contents.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,kCAAkC,CAAC;AAG1C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC;AA8BD,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,UAAU,SAAa,EACvB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GACrC,iBAAiB,EAAE,CAoDrB;AAED,MAAM,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;AAE/E,UAAU,uBAAuB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACjC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAMD,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,KAAK,EAAE,iBAAiB;AACxB;;;GAGG;AACH,cAAc,GAAE,SAAS,MAAM,EAAY,EAC3C,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAuElE;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,WAAW,CAAC,CA0EtB"}
|
|
@@ -1,28 +1,75 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { basename, dirname, extname, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { createExclusionMatcher, isExcludedPath, } from '../../docs/shared/exclusions.js';
|
|
3
4
|
import { dirExists, fileExists } from '../../../fs/io.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/** Mask fenced examples without changing authored source line numbers. */
|
|
6
|
+
function unfencedLines(markdown) {
|
|
7
|
+
let fence;
|
|
8
|
+
return markdown.split(/\r?\n/).map((line) => {
|
|
9
|
+
if (fence) {
|
|
10
|
+
const closing = line.match(/^ {0,3}(`{3,}|~{3,})[ \t]*$/);
|
|
11
|
+
if (closing &&
|
|
12
|
+
closing[1][0] === fence.marker &&
|
|
13
|
+
closing[1].length >= fence.length) {
|
|
14
|
+
fence = undefined;
|
|
15
|
+
}
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
const opening = line.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
|
19
|
+
if (opening && (opening[1][0] === '~' || !opening[2].includes('`'))) {
|
|
20
|
+
fence = { marker: opening[1][0], length: opening[1].length };
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
return line;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function contentsHeadingIndex(lines) {
|
|
27
|
+
return lines.findIndex((line) => /^##\s+Contents\s*$/.test(line.trim()));
|
|
28
|
+
}
|
|
29
|
+
/** Shared discovery lets inventory generation retain its no-map fallback. */
|
|
30
|
+
export function hasIndexContents(markdown) {
|
|
31
|
+
return contentsHeadingIndex(unfencedLines(markdown)) >= 0;
|
|
32
|
+
}
|
|
33
|
+
export function parseIndexContents(markdown, sourcePath = 'index.md', options = {}) {
|
|
34
|
+
const lines = unfencedLines(markdown);
|
|
35
|
+
const headingIndex = contentsHeadingIndex(lines);
|
|
36
|
+
if (headingIndex < 0) {
|
|
8
37
|
throw new Error(`Missing required ## Contents section in ${sourcePath}`);
|
|
9
38
|
}
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
const entries = [];
|
|
40
|
+
let group;
|
|
41
|
+
let groupEntries = 0;
|
|
42
|
+
const checkGroup = () => {
|
|
43
|
+
if (group && groupEntries === 0) {
|
|
44
|
+
throw new Error(`${sourcePath}:${group.line}: Contents group "${group.title}" has no entries`);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
for (let i = headingIndex + 1; i < lines.length; i += 1) {
|
|
48
|
+
const line = lines[i];
|
|
49
|
+
if (/^##\s+/.test(line.trim()))
|
|
50
|
+
break;
|
|
51
|
+
const heading = line.match(/^###\s+(.+?)\s*$/);
|
|
52
|
+
if (heading) {
|
|
53
|
+
checkGroup();
|
|
54
|
+
group = { title: heading[1].trim(), line: i + 1 };
|
|
55
|
+
groupEntries = 0;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (/^\s*[*+]\s+\[[^\]]+\]\([^)]+\)/.test(line)) {
|
|
59
|
+
throw new Error(`${sourcePath}:${i + 1}: Contents links must use a dash (-) bullet`);
|
|
60
|
+
}
|
|
61
|
+
const match = line.match(/^\s*-\s+\[([^\]]+)\]\(([^)]+)\)/);
|
|
62
|
+
if (match) {
|
|
63
|
+
entries.push({
|
|
64
|
+
title: match[1].trim(),
|
|
65
|
+
href: match[2].trim(),
|
|
66
|
+
...(group ? { group } : {}),
|
|
67
|
+
});
|
|
68
|
+
groupEntries += 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
checkGroup();
|
|
72
|
+
if (entries.length === 0 && !options.allowEmpty) {
|
|
26
73
|
throw new Error(`No machine-readable links found under ## Contents in ${sourcePath}`);
|
|
27
74
|
}
|
|
28
75
|
return entries;
|
|
@@ -35,7 +82,7 @@ export async function resolveEntryTarget(docsRoot, directoryPath, dirRelativePat
|
|
|
35
82
|
* Page extensions a Contents link may target. MkDocs renders only `.md`;
|
|
36
83
|
* Fumadocs also renders `.mdx` pages.
|
|
37
84
|
*/
|
|
38
|
-
pageExtensions = ['.md']) {
|
|
85
|
+
pageExtensions = ['.md'], matcher) {
|
|
39
86
|
const href = entry.href.split('#', 1)[0]?.trim() ?? '';
|
|
40
87
|
if (!href) {
|
|
41
88
|
throw new Error(`Invalid contents link "${entry.href}" in ${join(docsRoot, dirRelativePath, 'index.md')}`);
|
|
@@ -53,6 +100,9 @@ pageExtensions = ['.md']) {
|
|
|
53
100
|
relativeTargetPath.length === 0) {
|
|
54
101
|
throw new Error(`Contents link "${entry.href}" escapes the docs directory from ${join(docsRoot, dirRelativePath, 'index.md')}`);
|
|
55
102
|
}
|
|
103
|
+
if (matcher && isExcludedPath(matcher, relativeTargetPath)) {
|
|
104
|
+
return { kind: 'excluded', path: relativeTargetPath };
|
|
105
|
+
}
|
|
56
106
|
if (!(await fileExists(targetPath))) {
|
|
57
107
|
throw new Error(`Contents link "${entry.href}" in ${join(docsRoot, dirRelativePath, 'index.md')} does not resolve to a Markdown file`);
|
|
58
108
|
}
|
|
@@ -80,17 +130,27 @@ export async function buildDocsNavTree(options) {
|
|
|
80
130
|
}
|
|
81
131
|
const indexMarkdown = await readFile(indexPath, 'utf8');
|
|
82
132
|
const contentsEntries = parseIndexContents(indexMarkdown, indexPath);
|
|
133
|
+
const matcher = createExclusionMatcher(options.nonReaderPaths ?? []);
|
|
83
134
|
const nav = dirRelativePath === '.'
|
|
84
135
|
? [{ Home: 'index.md' }]
|
|
85
136
|
: [toPosixPath(join(dirRelativePath, 'index.md'))];
|
|
137
|
+
let currentGroup;
|
|
138
|
+
let groupNav = nav;
|
|
86
139
|
for (const entry of contentsEntries) {
|
|
87
|
-
const resolvedTarget = await resolveEntryTarget(options.docsRoot, directoryPath, dirRelativePath, entry);
|
|
140
|
+
const resolvedTarget = await resolveEntryTarget(options.docsRoot, directoryPath, dirRelativePath, entry, ['.md'], matcher);
|
|
141
|
+
if (resolvedTarget.kind === 'excluded')
|
|
142
|
+
continue;
|
|
88
143
|
if (resolvedTarget.kind === 'section' &&
|
|
89
144
|
resolvedTarget.path === toPosixPath(join(dirRelativePath, 'index.md'))) {
|
|
90
145
|
continue;
|
|
91
146
|
}
|
|
147
|
+
if (entry.group !== currentGroup) {
|
|
148
|
+
currentGroup = entry.group;
|
|
149
|
+
groupNav = [];
|
|
150
|
+
nav.push({ [currentGroup.title]: groupNav });
|
|
151
|
+
}
|
|
92
152
|
if (resolvedTarget.kind === 'page') {
|
|
93
|
-
|
|
153
|
+
groupNav.push({ [entry.title]: resolvedTarget.path });
|
|
94
154
|
continue;
|
|
95
155
|
}
|
|
96
156
|
const childDirRelativePath = toPosixPath(dirname(resolvedTarget.path));
|
|
@@ -99,11 +159,12 @@ export async function buildDocsNavTree(options) {
|
|
|
99
159
|
}
|
|
100
160
|
const childVisitedDirectories = new Set(visitedDirectories);
|
|
101
161
|
childVisitedDirectories.add(childDirRelativePath);
|
|
102
|
-
|
|
162
|
+
groupNav.push({
|
|
103
163
|
[entry.title]: await buildDocsNavTree({
|
|
104
164
|
docsRoot: options.docsRoot,
|
|
105
165
|
dirRelativePath: childDirRelativePath,
|
|
106
166
|
visitedDirectories: childVisitedDirectories,
|
|
167
|
+
nonReaderPaths: options.nonReaderPaths,
|
|
107
168
|
}),
|
|
108
169
|
});
|
|
109
170
|
}
|
|
@@ -54,5 +54,6 @@ export declare function syncFumadocsNavigation(options: {
|
|
|
54
54
|
docsRoot: string;
|
|
55
55
|
/** Compute and compare only; never write. */
|
|
56
56
|
check?: boolean;
|
|
57
|
+
nonReaderPaths?: readonly string[];
|
|
57
58
|
}): Promise<SyncFumadocsNavigationResult>;
|
|
58
59
|
//# sourceMappingURL=fumadocs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fumadocs.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/nav/fumadocs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fumadocs.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/nav/fumadocs.ts"],"names":[],"mappings":"AAoBA;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,mEAAmE;IACnE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;OAGG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA6DD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQxD;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAoCvE;AAsCD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAqND;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC,GAAG,OAAO,CAAC,4BAA4B,CAAC,CA8ExC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { readdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { basename, extname, join, posix } from 'node:path';
|
|
3
3
|
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
import { createExclusionMatcher, } from '../../docs/shared/exclusions.js';
|
|
4
5
|
import { getFrontmatterBlock, parseFrontmatterScalarFields, } from '../../shared/frontmatter.js';
|
|
5
6
|
import { fileExists } from '../../../fs/io.js';
|
|
6
|
-
import { parseIndexContents, resolveEntryTarget } from './contents.js';
|
|
7
|
+
import { parseIndexContents, resolveEntryTarget, } from './contents.js';
|
|
7
8
|
const PAGE_EXTENSIONS = ['.md', '.mdx'];
|
|
8
9
|
const GROUP_FOLDER = /^\(.+\)$/;
|
|
9
10
|
// The `pages` item grammar of the fumadocs-core 16.10.2 page-tree builder
|
|
@@ -18,7 +19,7 @@ const FENCE = /^\s*(```|~~~)/;
|
|
|
18
19
|
function parentDir(path) {
|
|
19
20
|
return posix.dirname(path);
|
|
20
21
|
}
|
|
21
|
-
async function inventoryDocs(docsRoot) {
|
|
22
|
+
async function inventoryDocs(docsRoot, matcher) {
|
|
22
23
|
const directories = [];
|
|
23
24
|
const pages = [];
|
|
24
25
|
async function walk(relativeDir) {
|
|
@@ -32,11 +33,14 @@ async function inventoryDocs(docsRoot) {
|
|
|
32
33
|
}
|
|
33
34
|
const entryPath = posix.join(relativeDir, entry.name);
|
|
34
35
|
if (entry.isDirectory()) {
|
|
36
|
+
if (matcher.excludesDirectory(entryPath))
|
|
37
|
+
continue;
|
|
35
38
|
await walk(entryPath);
|
|
36
39
|
}
|
|
37
40
|
else if (entry.isFile() &&
|
|
38
41
|
PAGE_EXTENSIONS.includes(extname(entry.name))) {
|
|
39
|
-
|
|
42
|
+
if (!matcher.excludesFile(entryPath))
|
|
43
|
+
pages.push(entryPath);
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
}
|
|
@@ -100,7 +104,7 @@ export function resolveFolderTitle(markdown) {
|
|
|
100
104
|
* slugs plugin with the scaffolded `baseUrl: '/'`: group folders are dropped,
|
|
101
105
|
* segments are URI-encoded, and an `index` file maps to its folder.
|
|
102
106
|
*/
|
|
103
|
-
function pageUrl(pagePath) {
|
|
107
|
+
function pageUrl(pagePath, nonIndexUrls = new Set()) {
|
|
104
108
|
const slugs = parentDir(pagePath)
|
|
105
109
|
.split('/')
|
|
106
110
|
.filter((segment) => segment.length > 0 && segment !== '.')
|
|
@@ -110,7 +114,12 @@ function pageUrl(pagePath) {
|
|
|
110
114
|
if (name !== 'index') {
|
|
111
115
|
slugs.push(encodeURI(name));
|
|
112
116
|
}
|
|
113
|
-
|
|
117
|
+
const url = `/${slugs.join('/')}`;
|
|
118
|
+
// core16.10.2 defers index slugs and appends /index when a non-index
|
|
119
|
+
// page has already claimed the folder URL.
|
|
120
|
+
return name === 'index' && nonIndexUrls.has(url)
|
|
121
|
+
? `${url.replace(/\/$/, '')}/index`
|
|
122
|
+
: url;
|
|
114
123
|
}
|
|
115
124
|
/** Whether the Fumadocs page-tree builder reads `item` as a directive, not a path. */
|
|
116
125
|
function isFumadocsDirective(item) {
|
|
@@ -135,9 +144,11 @@ function hrefFragment(href) {
|
|
|
135
144
|
/**
|
|
136
145
|
* Build one folder's `pages` from its index.md Contents map.
|
|
137
146
|
*
|
|
138
|
-
* A page in this folder or an immediate subfolder is referenced by name.
|
|
139
|
-
*
|
|
140
|
-
*
|
|
147
|
+
* A page in this folder or an immediate subfolder is referenced by name.
|
|
148
|
+
* Local same-name page/folder collisions use a precise source-file item, as
|
|
149
|
+
* does `../contributing.md` from its own contributing/ map: pinned core16.10.2
|
|
150
|
+
* selects the folder first for an extensionless reference. Other non-local
|
|
151
|
+
* targets become link entries. There is never a `"..."` rest entry: pages the Contents map does
|
|
141
152
|
* not list stay out of the tree and are reported instead.
|
|
142
153
|
*/
|
|
143
154
|
/**
|
|
@@ -150,13 +161,15 @@ function hrefFragment(href) {
|
|
|
150
161
|
* only if `pages` lists it.
|
|
151
162
|
*/
|
|
152
163
|
function hasImplicitIndex(relativeDir, existing) {
|
|
153
|
-
if (relativeDir === '.' ||
|
|
164
|
+
if (relativeDir === '.' ||
|
|
165
|
+
existing?.root === true ||
|
|
166
|
+
(typeof existing?.root === 'string' && existing.root.trim().length > 0)) {
|
|
154
167
|
return false;
|
|
155
168
|
}
|
|
156
169
|
const pagesIndex = existing?.pagesIndex;
|
|
157
170
|
return pagesIndex === undefined || pagesIndex === 'index';
|
|
158
171
|
}
|
|
159
|
-
async function buildFolderPages(docsRoot, relativeDir, markdown, implicitIndex) {
|
|
172
|
+
async function buildFolderPages(docsRoot, relativeDir, markdown, implicitIndex, matcher, inventory, nonIndexUrls) {
|
|
160
173
|
const indexPath = join(docsRoot, relativeDir, 'index.md');
|
|
161
174
|
const entries = parseIndexContents(markdown, indexPath);
|
|
162
175
|
// A folder the loader gives no implicit index (the docs root, a `root: true`
|
|
@@ -164,11 +177,23 @@ async function buildFolderPages(docsRoot, relativeDir, markdown, implicitIndex)
|
|
|
164
177
|
// `index` would demote the folder index to an ordinary child, so it is left
|
|
165
178
|
// implicit.
|
|
166
179
|
const pages = implicitIndex ? [] : ['index'];
|
|
180
|
+
let currentGroup;
|
|
167
181
|
for (const entry of entries) {
|
|
168
|
-
const target = await resolveEntryTarget(docsRoot, join(docsRoot, relativeDir), relativeDir, entry, PAGE_EXTENSIONS);
|
|
182
|
+
const target = await resolveEntryTarget(docsRoot, join(docsRoot, relativeDir), relativeDir, entry, PAGE_EXTENSIONS, matcher);
|
|
183
|
+
if (target.kind === 'excluded')
|
|
184
|
+
continue;
|
|
169
185
|
const targetDir = parentDir(target.path);
|
|
170
186
|
let item;
|
|
171
|
-
if (target.kind === '
|
|
187
|
+
if (target.kind === 'page' &&
|
|
188
|
+
(targetDir === relativeDir ||
|
|
189
|
+
target.path.slice(0, -extname(target.path).length) === relativeDir) &&
|
|
190
|
+
inventory.directories.includes(target.path.slice(0, -extname(target.path).length))) {
|
|
191
|
+
// An extensionless reference selects the same-name folder first.
|
|
192
|
+
// Keep source identity only in local/owning placement. A nonlocal map
|
|
193
|
+
// needs an authored URL link: core16.10.2 globally dedups source refs.
|
|
194
|
+
item = toFumadocsPageItem(posix.relative(relativeDir, target.path));
|
|
195
|
+
}
|
|
196
|
+
else if (target.kind === 'section' && targetDir === relativeDir) {
|
|
172
197
|
continue;
|
|
173
198
|
}
|
|
174
199
|
else if (target.kind === 'section' &&
|
|
@@ -179,9 +204,15 @@ async function buildFolderPages(docsRoot, relativeDir, markdown, implicitIndex)
|
|
|
179
204
|
item = toFumadocsPageItem(basename(target.path, extname(target.path)));
|
|
180
205
|
}
|
|
181
206
|
else {
|
|
182
|
-
item = `[${entry.title}](${pageUrl(target.path)}${hrefFragment(entry.href)})`;
|
|
207
|
+
item = `[${entry.title}](${pageUrl(target.path, nonIndexUrls)}${hrefFragment(entry.href)})`;
|
|
183
208
|
}
|
|
184
209
|
if (!pages.includes(item)) {
|
|
210
|
+
if (entry.group !== currentGroup) {
|
|
211
|
+
currentGroup = entry.group;
|
|
212
|
+
// A leading space prevents the loader treating a literal [label] as an icon.
|
|
213
|
+
const title = currentGroup.title;
|
|
214
|
+
pages.push(`---${title.startsWith('[') ? ' ' : ''}${title}---`);
|
|
215
|
+
}
|
|
185
216
|
pages.push(item);
|
|
186
217
|
}
|
|
187
218
|
}
|
|
@@ -213,6 +244,7 @@ function mergeMeta(existing, meta) {
|
|
|
213
244
|
};
|
|
214
245
|
}
|
|
215
246
|
function collectUnlisted(inventory, metas, implicitIndexDirs) {
|
|
247
|
+
const pagePaths = new Set(inventory.pages);
|
|
216
248
|
const reachableDirs = new Set();
|
|
217
249
|
const reachablePages = new Set();
|
|
218
250
|
const pagesByFlattenPath = new Map(inventory.pages.map((page) => [
|
|
@@ -242,7 +274,9 @@ function collectUnlisted(inventory, metas, implicitIndexDirs) {
|
|
|
242
274
|
visit(itemPath);
|
|
243
275
|
continue;
|
|
244
276
|
}
|
|
245
|
-
const page =
|
|
277
|
+
const page = pagePaths.has(itemPath)
|
|
278
|
+
? itemPath
|
|
279
|
+
: pagesByFlattenPath.get(itemPath);
|
|
246
280
|
if (page) {
|
|
247
281
|
reachablePages.add(page);
|
|
248
282
|
}
|
|
@@ -274,13 +308,18 @@ function collectUnlisted(inventory, metas, implicitIndexDirs) {
|
|
|
274
308
|
*/
|
|
275
309
|
export async function syncFumadocsNavigation(options) {
|
|
276
310
|
const { docsRoot } = options;
|
|
277
|
-
const
|
|
311
|
+
const matcher = createExclusionMatcher(options.nonReaderPaths ?? []);
|
|
312
|
+
const inventory = await inventoryDocs(docsRoot, matcher);
|
|
313
|
+
const nonIndexUrls = new Set(inventory.pages
|
|
314
|
+
.filter((page) => basename(page, extname(page)) !== 'index')
|
|
315
|
+
.map((page) => pageUrl(page)));
|
|
278
316
|
const metas = new Map();
|
|
279
317
|
const existingMetas = new Map();
|
|
280
318
|
const implicitIndexDirs = new Set();
|
|
281
319
|
for (const relativeDir of inventory.directories) {
|
|
282
320
|
const indexPath = join(docsRoot, relativeDir, 'index.md');
|
|
283
|
-
if (
|
|
321
|
+
if (matcher.excludesFile(posix.join(relativeDir, 'index.md')) ||
|
|
322
|
+
!(await fileExists(indexPath))) {
|
|
284
323
|
continue;
|
|
285
324
|
}
|
|
286
325
|
// Index handling depends on the effective metadata, which keeps the
|
|
@@ -295,7 +334,7 @@ export async function syncFumadocsNavigation(options) {
|
|
|
295
334
|
const title = resolveFolderTitle(markdown);
|
|
296
335
|
metas.set(relativeDir, {
|
|
297
336
|
...(title === undefined ? {} : { title }),
|
|
298
|
-
pages: await buildFolderPages(docsRoot, relativeDir, markdown, implicitIndex),
|
|
337
|
+
pages: await buildFolderPages(docsRoot, relativeDir, markdown, implicitIndex, matcher, inventory, nonIndexUrls),
|
|
299
338
|
});
|
|
300
339
|
}
|
|
301
340
|
if (!metas.has('.')) {
|
|
@@ -12,6 +12,7 @@ interface SyncDocsNavigationOptions {
|
|
|
12
12
|
configuredTooling?: string | null;
|
|
13
13
|
/** Compute the navigation and compare it with the files; write nothing. */
|
|
14
14
|
check?: boolean;
|
|
15
|
+
nonReaderPaths?: readonly string[];
|
|
15
16
|
}
|
|
16
17
|
interface MkDocsNavigationResult {
|
|
17
18
|
framework: 'mkdocs';
|
|
@@ -35,6 +36,7 @@ type SyncDocsNavigationResult = MkDocsNavigationResult | FumadocsNavigationResul
|
|
|
35
36
|
interface DocsNavSyncDependencies {
|
|
36
37
|
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
37
38
|
resolveConfiguredTooling: (cwd: string, appRoot: string) => Promise<string | null>;
|
|
39
|
+
resolveNonReaderPaths?: (cwd: string, appRoot: string) => Promise<string[]>;
|
|
38
40
|
syncDocsNavigation: (options: SyncDocsNavigationOptions) => Promise<SyncDocsNavigationResult>;
|
|
39
41
|
}
|
|
40
42
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/nav/sync.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3E,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,QAAQ,CAAC;AAOrD,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/nav/sync.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3E,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,QAAQ,CAAC;AAOrD,UAAU,yBAAyB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,UAAU,sBAAsB;IAC9B,SAAS,EAAE,QAAQ,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,WAAW,CAAC;IACjB,uEAAuE;IACvE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,UAAU,wBAAwB;IAChC,SAAS,EAAE,UAAU,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,KAAK,wBAAwB,GACzB,sBAAsB,GACtB,wBAAwB,CAAC;AAE7B,UAAU,uBAAuB;IAC/B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,wBAAwB,EAAE,CACxB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5B,qBAAqB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5E,kBAAkB,EAAE,CAClB,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,wBAAwB,CAAC,CAAC;CACxC;AAsBD;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAChC,OAAO,CAAC,gBAAgB,CAAC,CAe3B;AAuED,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CA8CnC;AAuJD,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CA4BT"}
|
|
@@ -58,9 +58,24 @@ async function resolveConfiguredTooling(cwd, appRoot) {
|
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
async function resolveNonReaderPaths(cwd, appRoot) {
|
|
62
|
+
let repoRoot;
|
|
63
|
+
try {
|
|
64
|
+
repoRoot = await resolveProjectRoot(cwd);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return [];
|
|
68
|
+
} // Explicit app targets also work outside a configured repository.
|
|
69
|
+
const config = await readOatConfig(repoRoot);
|
|
70
|
+
const root = config.documentation?.root?.trim();
|
|
71
|
+
return root && resolve(repoRoot, root) === appRoot
|
|
72
|
+
? (config.documentation?.nonReaderPaths ?? [])
|
|
73
|
+
: [];
|
|
74
|
+
}
|
|
61
75
|
const DEFAULT_DEPENDENCIES = {
|
|
62
76
|
buildCommandContext,
|
|
63
77
|
resolveConfiguredTooling,
|
|
78
|
+
resolveNonReaderPaths,
|
|
64
79
|
syncDocsNavigation,
|
|
65
80
|
};
|
|
66
81
|
function replaceTopLevelYamlSection(source, key, replacement) {
|
|
@@ -90,6 +105,7 @@ export async function syncDocsNavigation(options) {
|
|
|
90
105
|
const result = await syncFumadocsNavigation({
|
|
91
106
|
docsRoot,
|
|
92
107
|
check: options.check,
|
|
108
|
+
nonReaderPaths: options.nonReaderPaths,
|
|
93
109
|
});
|
|
94
110
|
return {
|
|
95
111
|
framework,
|
|
@@ -99,7 +115,10 @@ export async function syncDocsNavigation(options) {
|
|
|
99
115
|
};
|
|
100
116
|
}
|
|
101
117
|
const mkdocsPath = join(options.appRoot, 'mkdocs.yml');
|
|
102
|
-
const nav = await buildDocsNavTree({
|
|
118
|
+
const nav = await buildDocsNavTree({
|
|
119
|
+
docsRoot,
|
|
120
|
+
nonReaderPaths: options.nonReaderPaths,
|
|
121
|
+
});
|
|
103
122
|
const mkdocsSource = await readFile(mkdocsPath, 'utf8');
|
|
104
123
|
const navSection = YAML.stringify({ nav }).trimEnd();
|
|
105
124
|
const updatedMkdocsSource = replaceTopLevelYamlSection(mkdocsSource, 'nav', navSection);
|
|
@@ -198,6 +217,7 @@ async function runDocsNavSyncCommand(context, options, dependencies) {
|
|
|
198
217
|
appRoot,
|
|
199
218
|
configuredTooling: await dependencies.resolveConfiguredTooling(context.cwd, appRoot),
|
|
200
219
|
check: options.check === true,
|
|
220
|
+
nonReaderPaths: await dependencies.resolveNonReaderPaths?.(context.cwd, appRoot),
|
|
201
221
|
});
|
|
202
222
|
if (options.check) {
|
|
203
223
|
process.exitCode = reportCheckResult(context, targetDir, result) ? 0 : 1;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A compiled exclusion list.
|
|
3
|
+
*
|
|
4
|
+
* Files and directories are asked separately so a trailing-slash pattern can
|
|
5
|
+
* mean "directories only" while every other pattern applies to both.
|
|
6
|
+
*/
|
|
7
|
+
export interface ExclusionMatcher {
|
|
8
|
+
excludesFile: (relativePath: string) => boolean;
|
|
9
|
+
excludesDirectory: (relativePath: string) => boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Compile exclusion globs matched against **docs-root-relative POSIX paths**
|
|
13
|
+
* (`api/auth.md`, `api/nested`), never against absolute or CWD-relative ones.
|
|
14
|
+
*
|
|
15
|
+
* Grammar, defined once and documented in
|
|
16
|
+
* `apps/oat-docs/docs/reference/oat-directory-structure.md`:
|
|
17
|
+
*
|
|
18
|
+
* - `*` matches any run of characters inside a single path segment.
|
|
19
|
+
* - `**` spans `/` only as a whole segment, where it covers zero or more
|
|
20
|
+
* segments: `**\/CLAUDE.md` matches `CLAUDE.md` and `api/CLAUDE.md`. Inside a
|
|
21
|
+
* segment (`a**b`) it is an ordinary single-segment wildcard.
|
|
22
|
+
* - Patterns are anchored at both ends against the whole relative path, so a
|
|
23
|
+
* bare `CLAUDE.md` matches only the root-level file. Use `**\/CLAUDE.md` for
|
|
24
|
+
* every depth.
|
|
25
|
+
* - A trailing `/` restricts a pattern to directories: `drafts/` prunes the
|
|
26
|
+
* `drafts` directory and everything beneath it, and never matches a file.
|
|
27
|
+
* Without it, a pattern that matches a directory path prunes it too.
|
|
28
|
+
* - Matching is case-sensitive, `/` is the separator on every platform, and
|
|
29
|
+
* `*` is the only metacharacter — `.`, `+`, and friends are literal.
|
|
30
|
+
*
|
|
31
|
+
* Entries are trimmed; a leading `./` or `/` is stripped so both spellings
|
|
32
|
+
* anchor at the docs root. Blank entries are ignored.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createExclusionMatcher(patterns: readonly string[]): ExclusionMatcher;
|
|
35
|
+
/** Explicit Contents targets must also respect pruned ancestor directories. */
|
|
36
|
+
export declare function isExcludedPath(matcher: ExclusionMatcher, path: string, directory?: boolean): boolean;
|
|
37
|
+
//# sourceMappingURL=exclusions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exclusions.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/shared/exclusions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;IAChD,iBAAiB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC;CACtD;AAmHD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,gBAAgB,CAwClB;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,MAAM,EACZ,SAAS,UAAQ,GAChB,OAAO,CAQT"}
|