@inditextech/docouture-cli 0.1.0-SNAPSHOT.40.1
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/build/bin.js +321 -0
- package/build/commands/build.js +22 -0
- package/build/commands/completion.js +69 -0
- package/build/commands/dev.js +67 -0
- package/build/commands/doctor.js +156 -0
- package/build/commands/eject.js +71 -0
- package/build/commands/new.js +512 -0
- package/build/commands/publish.js +111 -0
- package/build/commands/teardown.js +66 -0
- package/build/commands/upgrade.js +156 -0
- package/build/commands/version.js +60 -0
- package/build/lib/agents-md.js +67 -0
- package/build/lib/antora-log.js +36 -0
- package/build/lib/antora-yml.js +47 -0
- package/build/lib/args.js +26 -0
- package/build/lib/cli-context.js +17 -0
- package/build/lib/cli-info.js +20 -0
- package/build/lib/config-resolver.js +23 -0
- package/build/lib/copy-template.js +162 -0
- package/build/lib/debug-log.js +21 -0
- package/build/lib/detect-package-manager.js +79 -0
- package/build/lib/dev-server.js +337 -0
- package/build/lib/doctor-checks.js +240 -0
- package/build/lib/global-flags.js +31 -0
- package/build/lib/kroki-compose.js +54 -0
- package/build/lib/playbook-yml.js +90 -0
- package/build/lib/repo-root.js +21 -0
- package/build/lib/run-script.js +50 -0
- package/build/lib/theme.js +51 -0
- package/build/templates/agent-support/.claude/skills/docs-internals/SKILL.md +53 -0
- package/build/templates/agent-support/.claude/skills/docs-internals/reference/antora-extensions.md +54 -0
- package/build/templates/agent-support/.claude/skills/docs-internals/reference/naming.md +41 -0
- package/build/templates/agent-support/.claude/skills/docs-internals/reference/page-patterns.md +238 -0
- package/build/templates/agent-support/.claude/skills/docs-versioning.versioned/SKILL.md +37 -0
- package/build/templates/agent-support/.claude/skills/docs-versioning.versioned/reference/releasing.md +106 -0
- package/build/templates/agent-support/.claude/skills/documenting-your-repo/SKILL.md +55 -0
- package/build/templates/agent-support/.claude/skills/documenting-your-repo/reference/content-sourcing.md +55 -0
- package/build/templates/agent-support/.claude/skills/documenting-your-repo/reference/maintenance-loop.md +68 -0
- package/build/templates/agent-support/.claude/skills/documenting-your-repo/reference/structure-planning.md +102 -0
- package/build/templates/agent-support/.claude/skills/writing-docs-pages/SKILL.md +112 -0
- package/build/templates/agent-support/.claude/skills/writing-docs-pages/reference/docouture-blocks.md +170 -0
- package/build/templates/agent-support/.claude/skills/writing-docs-pages/reference/language-basics.md +402 -0
- package/build/templates/agent-support/.opencode/skills/docs-internals/SKILL.md +53 -0
- package/build/templates/agent-support/.opencode/skills/docs-internals/reference/antora-extensions.md +54 -0
- package/build/templates/agent-support/.opencode/skills/docs-internals/reference/naming.md +41 -0
- package/build/templates/agent-support/.opencode/skills/docs-internals/reference/page-patterns.md +238 -0
- package/build/templates/agent-support/.opencode/skills/docs-versioning.versioned/SKILL.md +37 -0
- package/build/templates/agent-support/.opencode/skills/docs-versioning.versioned/reference/releasing.md +106 -0
- package/build/templates/agent-support/.opencode/skills/documenting-your-repo/SKILL.md +55 -0
- package/build/templates/agent-support/.opencode/skills/documenting-your-repo/reference/content-sourcing.md +55 -0
- package/build/templates/agent-support/.opencode/skills/documenting-your-repo/reference/maintenance-loop.md +68 -0
- package/build/templates/agent-support/.opencode/skills/documenting-your-repo/reference/structure-planning.md +102 -0
- package/build/templates/agent-support/.opencode/skills/writing-docs-pages/SKILL.md +112 -0
- package/build/templates/agent-support/.opencode/skills/writing-docs-pages/reference/docouture-blocks.md +170 -0
- package/build/templates/agent-support/.opencode/skills/writing-docs-pages/reference/language-basics.md +402 -0
- package/build/templates/agent-support/AGENTS.md +72 -0
- package/build/templates/starter/antora-playbook.local.yml +99 -0
- package/build/templates/starter/antora-playbook.versioned.yml +167 -0
- package/build/templates/starter/antora-playbook.yml +189 -0
- package/build/templates/starter/gitignore +3 -0
- package/build/templates/starter/package.json +37 -0
- package/build/templates/starter/scripts/check-links.mjs +178 -0
- package/build/templates/starter/src/antora.yml +85 -0
- package/build/templates/starter/src/modules/ROOT/images/card-placeholder.png +0 -0
- package/build/templates/starter/src/modules/ROOT/images/feature-placeholder-dark.png +0 -0
- package/build/templates/starter/src/modules/ROOT/images/feature-placeholder.png +0 -0
- package/build/templates/starter/src/modules/ROOT/images/hero-placeholder.png +0 -0
- package/build/templates/starter/src/modules/ROOT/pages/index.adoc +124 -0
- package/build/templates/starter/src/modules/main/nav.adoc +19 -0
- package/build/templates/starter/src/modules/main/pages/app.adoc +5 -0
- package/build/templates/starter/src/modules/main/pages/architecture.adoc +6 -0
- package/build/templates/starter/src/modules/main/pages/changelog/index.adoc +9 -0
- package/build/templates/starter/src/modules/main/pages/components.adoc +701 -0
- package/build/templates/starter/src/modules/main/pages/contributing.adoc +6 -0
- package/build/templates/starter/src/modules/main/pages/getting-started.adoc +169 -0
- package/build/templates/starter/src/modules/main/pages/glossary.adoc +8 -0
- package/build/templates/starter/src/modules/main/pages/index.adoc +9 -0
- package/build/templates/starter/src/modules/main/pages/prerequisites.adoc +55 -0
- package/build/templates/starter/src/modules/main/pages/quickstart.adoc +21 -0
- package/build/templates/starter/src/modules/main/pages/release-notes/index.adoc +9 -0
- package/build/templates/starter/src/modules/main/pages/requirements.adoc +8 -0
- package/build/templates/starter/src/modules/main/pages/roadmap.adoc +4 -0
- package/build/templates/starter/src/modules/main/pages/structure.adoc +5 -0
- package/build/templates/starter/src/release-version.versioned +1 -0
- package/build/templates/starter/supplemental-ui/favicon.ico +0 -0
- package/build/templates/starter/supplemental-ui/product-logo-dark.png +0 -0
- package/build/templates/starter/supplemental-ui/product-logo.png +0 -0
- package/build/templates/workflows/docouture-kroki-cache-warm.yml +101 -0
- package/build/templates/workflows/docouture-pr-verify.yml +120 -0
- package/build/templates/workflows/docouture-publish-prerelease.yml +122 -0
- package/build/templates/workflows/docouture-publish.yml +138 -0
- package/build/templates/workflows/docouture-release-preview.yml +198 -0
- package/build/templates/workflows/docouture-release.yml +414 -0
- package/package.json +38 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
function parseMajor(value) {
|
|
6
|
+
const match = /(\d+)/.exec(value);
|
|
7
|
+
return match ? Number(match[1]) : null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Compares the Node version actually running `docouture` (the same one `npm
|
|
11
|
+
* run build`/`dev` will use) against the site's own `engines.node` — a
|
|
12
|
+
* range like `>=24.0.0`. Only the major version is compared: this package
|
|
13
|
+
* has no semver dependency (see antora-yml.ts's own comment on a similar
|
|
14
|
+
* tradeoff) and a major-version floor is what `engines.node` communicates
|
|
15
|
+
* in practice.
|
|
16
|
+
*/
|
|
17
|
+
export function checkNodeVersion(engineRange, actualVersion) {
|
|
18
|
+
const label = 'node version';
|
|
19
|
+
if (!engineRange) {
|
|
20
|
+
return { ok: true, label, message: `${actualVersion} (no engines.node requirement declared)` };
|
|
21
|
+
}
|
|
22
|
+
const wantMajor = parseMajor(engineRange);
|
|
23
|
+
const haveMajor = parseMajor(actualVersion);
|
|
24
|
+
if (wantMajor === null || haveMajor === null) {
|
|
25
|
+
return { ok: true, label, message: `${actualVersion} (could not parse '${engineRange}', skipping)` };
|
|
26
|
+
}
|
|
27
|
+
if (haveMajor >= wantMajor) {
|
|
28
|
+
return { ok: true, label, message: `${actualVersion} satisfies ${engineRange}` };
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
label,
|
|
33
|
+
message: `${actualVersion} does not satisfy ${engineRange}`,
|
|
34
|
+
detail: `install Node ${wantMajor} or newer — this is what 'npm run build'/'docouture dev' will actually run under`,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The four names the docs-site-package skill documents as having to agree,
|
|
39
|
+
* or a site builds to zero pages, or dies on "start page not found" — see
|
|
40
|
+
* SKILL.md's "The four names that must agree" table. Returns one result per
|
|
41
|
+
* pair so a failure names exactly which two values drifted, not just that
|
|
42
|
+
* something, somewhere, did.
|
|
43
|
+
*/
|
|
44
|
+
export function checkNamesAgree(input) {
|
|
45
|
+
const results = [];
|
|
46
|
+
if (input.antoraYmlName && input.startPageComponent) {
|
|
47
|
+
results.push(input.antoraYmlName === input.startPageComponent
|
|
48
|
+
? { ok: true, label: 'component name', message: `'${input.antoraYmlName}' matches site.start_page` }
|
|
49
|
+
: {
|
|
50
|
+
ok: false,
|
|
51
|
+
label: 'component name',
|
|
52
|
+
message: `docs/antora.yml name '${input.antoraYmlName}' != site.start_page component '${input.startPageComponent}'`,
|
|
53
|
+
detail: "site.start_page must be '<name>::index.adoc' using docs/antora.yml's own name",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (input.startPath && input.descriptorPath) {
|
|
57
|
+
results.push(input.startPath === input.descriptorPath
|
|
58
|
+
? {
|
|
59
|
+
ok: true,
|
|
60
|
+
label: 'content path',
|
|
61
|
+
message: `start_path '${input.startPath}' matches docs/antora.yml's location`,
|
|
62
|
+
}
|
|
63
|
+
: {
|
|
64
|
+
ok: false,
|
|
65
|
+
label: 'content path',
|
|
66
|
+
message: `playbook start_path '${input.startPath}' != actual docs/antora.yml location '${input.descriptorPath}' (both repo-root relative)`,
|
|
67
|
+
detail: 'content.sources[0].start_path must be the repository-root-relative directory that directly contains antora.yml',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (input.packageName && input.antoraYmlName) {
|
|
71
|
+
if (input.antoraYmlName === 'ROOT') {
|
|
72
|
+
// `ROOT` is Antora's own reserved component name (dropped from every
|
|
73
|
+
// published URL — see how-antora-builds-urls's "Component segment"),
|
|
74
|
+
// set by `docouture new` when the "extra URL path segment" question/
|
|
75
|
+
// `--url-segment` flag is declined (the default) — see new.ts's own
|
|
76
|
+
// comment on TemplateValues.componentName. It is never derived from
|
|
77
|
+
// package.json's name, so the two are expected to differ here; only
|
|
78
|
+
// a real, chosen component name must still match package.json's own
|
|
79
|
+
// name, which is what the else branch below still enforces.
|
|
80
|
+
results.push({
|
|
81
|
+
ok: true,
|
|
82
|
+
label: 'package name',
|
|
83
|
+
message: `docs/antora.yml name is 'ROOT' (no URL segment) — package.json name '${input.packageName}' is independent`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
results.push(input.packageName === input.antoraYmlName
|
|
88
|
+
? {
|
|
89
|
+
ok: true,
|
|
90
|
+
label: 'package name',
|
|
91
|
+
message: `package.json name '${input.packageName}' matches component name`,
|
|
92
|
+
}
|
|
93
|
+
: {
|
|
94
|
+
ok: false,
|
|
95
|
+
label: 'package name',
|
|
96
|
+
message: `package.json name '${input.packageName}' != docs/antora.yml name '${input.antoraYmlName}'`,
|
|
97
|
+
detail: 'docouture new sets both from the same value — if one was renamed by hand, rename the other to match',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Antora reads content from git — a repository with no commits resolves the
|
|
105
|
+
* content source to nothing and the site builds with zero pages, reported
|
|
106
|
+
* only as "Start page specified for site not found". Mirrors `just doctor`'s
|
|
107
|
+
* own check in the monorepo justfile.
|
|
108
|
+
*/
|
|
109
|
+
export function checkGitHasCommit(dir) {
|
|
110
|
+
const label = 'git history';
|
|
111
|
+
return new Promise((resolvePromise) => {
|
|
112
|
+
execFile('git', ['rev-parse', 'HEAD'], { cwd: dir }, (err) => {
|
|
113
|
+
resolvePromise(err
|
|
114
|
+
? {
|
|
115
|
+
ok: false,
|
|
116
|
+
label,
|
|
117
|
+
message: 'repository has no commits',
|
|
118
|
+
detail: 'Antora reads content from git; with no commits the content source resolves to nothing and the site builds with zero pages — make an initial commit',
|
|
119
|
+
}
|
|
120
|
+
: { ok: true, label, message: 'repository has at least one commit' });
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Confirms the two things a build actually needs are installed locally —
|
|
126
|
+
* `npm install` was run, in other words — rather than letting Antora's own
|
|
127
|
+
* `MODULE_NOT_FOUND` be the first sign of it.
|
|
128
|
+
*/
|
|
129
|
+
export function checkAntoraAvailable(siteRoot) {
|
|
130
|
+
const label = 'antora CLI';
|
|
131
|
+
const bin = join(siteRoot, 'node_modules', '.bin', 'antora');
|
|
132
|
+
const pkg = join(siteRoot, 'node_modules', 'antora');
|
|
133
|
+
if (existsSync(bin) && existsSync(pkg)) {
|
|
134
|
+
return { ok: true, label, message: 'antora is installed in node_modules' };
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
ok: false,
|
|
138
|
+
label,
|
|
139
|
+
message: 'antora is not installed',
|
|
140
|
+
detail: "run 'npm install' in the site directory",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// Repo-root-relative paths `docouture new` scaffolds AGENTS.md/the skill
|
|
144
|
+
// directories under — see new.ts's own AGENT_SUPPORT_PATHS, which this
|
|
145
|
+
// mirrors. Kept as a separate literal here rather than imported: doctor-
|
|
146
|
+
// checks.ts is a plain library module with fixture-driven unit tests (see
|
|
147
|
+
// its own spec) and importing from commands/new.ts would pull the wizard
|
|
148
|
+
// (@inquirer/prompts) into that dependency graph for no reason.
|
|
149
|
+
const AGENT_SUPPORT_CHECK_PATHS = [
|
|
150
|
+
{ path: 'AGENTS.md', label: 'AGENTS.md' },
|
|
151
|
+
{ path: join('.opencode', 'skills', 'documenting-your-repo'), label: '.opencode/skills/documenting-your-repo' },
|
|
152
|
+
{ path: join('.opencode', 'skills', 'writing-docs-pages'), label: '.opencode/skills/writing-docs-pages' },
|
|
153
|
+
{ path: join('.opencode', 'skills', 'docs-internals'), label: '.opencode/skills/docs-internals' },
|
|
154
|
+
{ path: join('.claude', 'skills', 'documenting-your-repo'), label: '.claude/skills/documenting-your-repo' },
|
|
155
|
+
{ path: join('.claude', 'skills', 'writing-docs-pages'), label: '.claude/skills/writing-docs-pages' },
|
|
156
|
+
{ path: join('.claude', 'skills', 'docs-internals'), label: '.claude/skills/docs-internals' },
|
|
157
|
+
];
|
|
158
|
+
/**
|
|
159
|
+
* Whether AGENTS.md and the two platform-mirrored skill directories `docouture
|
|
160
|
+
* new` scaffolds are still present at the repository root — advisory only,
|
|
161
|
+
* this is presence, not a content/drift diff (a site legitimately edits its
|
|
162
|
+
* own skills after scaffolding), so `commands/doctor.ts` reports these
|
|
163
|
+
* without folding them into the overall exit code the way the checks above
|
|
164
|
+
* do. `docs-versioning` is intentionally not checked here: it exists only
|
|
165
|
+
* under `--mode versioned`, and doctor has no reliable, cheap way to tell
|
|
166
|
+
* which mode a site is on from this function alone (see
|
|
167
|
+
* `commands/doctor.ts`'s own mode-detection comment, which reads the
|
|
168
|
+
* playbook — a concern this function deliberately stays out of).
|
|
169
|
+
*/
|
|
170
|
+
/** The label `docouture-release.yml`'s `pull_request.closed` trigger requires — see that workflow's own `if:` condition. */
|
|
171
|
+
const RELEASE_LABEL = 'docs/release';
|
|
172
|
+
/**
|
|
173
|
+
* Whether the `docs/release` GitHub label exists on this repository —
|
|
174
|
+
* best-effort, via the `gh` CLI, since there is no other way to ask GitHub
|
|
175
|
+
* this from a local checkout. `docouture new` never creates this label (GitHub
|
|
176
|
+
* does not create labels referenced by a workflow's `if:` condition on its
|
|
177
|
+
* own, and scaffolding is not a GitHub API call), so a repository fresh out
|
|
178
|
+
* of `docouture new` is missing it until someone runs `gh label create
|
|
179
|
+
* docs/release` — see main's own prerequisites.adoc, which covers this
|
|
180
|
+
* alongside the repository-public/GitHub-Pages-enablement steps a fresh
|
|
181
|
+
* site also needs. Without it, `docouture-release.yml`'s automatic
|
|
182
|
+
* merge-triggers-a-release path is a silent no-op; only its
|
|
183
|
+
* `workflow_dispatch` path still works.
|
|
184
|
+
*
|
|
185
|
+
* Advisory only, same as checkAgentFilesPresent above: `gh` may not be
|
|
186
|
+
* installed, not authenticated, or this may not be a GitHub-hosted
|
|
187
|
+
* repository at all, none of which this function treats as a real failure —
|
|
188
|
+
* only an actual label list that is missing the label is reported as
|
|
189
|
+
* `ok: false`.
|
|
190
|
+
*/
|
|
191
|
+
export function checkReleaseLabelExists(repoRoot) {
|
|
192
|
+
const label = 'docs/release label';
|
|
193
|
+
return new Promise((resolvePromise) => {
|
|
194
|
+
execFile('gh', ['label', 'list', '--json', 'name'], { cwd: repoRoot }, (err, stdout) => {
|
|
195
|
+
if (err) {
|
|
196
|
+
resolvePromise({
|
|
197
|
+
ok: true,
|
|
198
|
+
label,
|
|
199
|
+
message: 'could not check (gh CLI unavailable, unauthenticated, or not a GitHub repo) — skipping',
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
let names;
|
|
204
|
+
try {
|
|
205
|
+
const parsed = JSON.parse(stdout);
|
|
206
|
+
names = parsed.map((entry) => entry.name);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
resolvePromise({ ok: true, label, message: "could not parse 'gh label list' output — skipping" });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (names.includes(RELEASE_LABEL)) {
|
|
213
|
+
resolvePromise({ ok: true, label, message: `'${RELEASE_LABEL}' exists` });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
resolvePromise({
|
|
217
|
+
ok: false,
|
|
218
|
+
label,
|
|
219
|
+
message: `'${RELEASE_LABEL}' does not exist`,
|
|
220
|
+
detail: "docouture-release.yml's merge-triggers-a-release path is a no-op without it — run " +
|
|
221
|
+
`'gh label create ${RELEASE_LABEL}', or use workflow_dispatch instead`,
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
export function checkAgentFilesPresent(repoRoot) {
|
|
227
|
+
return AGENT_SUPPORT_CHECK_PATHS.map(({ path: relativePath, label }) => {
|
|
228
|
+
const absolutePath = join(repoRoot, relativePath);
|
|
229
|
+
const present = existsSync(absolutePath);
|
|
230
|
+
return present
|
|
231
|
+
? { ok: true, label, message: 'present' }
|
|
232
|
+
: {
|
|
233
|
+
ok: false,
|
|
234
|
+
label,
|
|
235
|
+
message: 'missing',
|
|
236
|
+
detail: "run 'docouture upgrade' in this repository to regenerate it, or restore it from version control",
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=doctor-checks.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Recognises the three global flags (--json, --verbose, --no-color/--color)
|
|
4
|
+
* anywhere in argv — before or after the command name, so both
|
|
5
|
+
* `docouture --verbose dev` and `docouture dev --verbose` work — strips them out,
|
|
6
|
+
* and returns what's left for command dispatch. Kept separate from
|
|
7
|
+
* lib/args.ts's per-command parser: these are process-wide concerns (see
|
|
8
|
+
* lib/cli-context.ts), never a value a specific command's own flags need to
|
|
9
|
+
* see, and separate from bin.ts itself so it's testable without triggering
|
|
10
|
+
* bin.ts's own top-level `main()` call.
|
|
11
|
+
*/
|
|
12
|
+
export function extractGlobalFlags(argv) {
|
|
13
|
+
const rest = [];
|
|
14
|
+
let json = false;
|
|
15
|
+
let verbose = false;
|
|
16
|
+
let noColor = false;
|
|
17
|
+
for (const arg of argv) {
|
|
18
|
+
if (arg === '--json')
|
|
19
|
+
json = true;
|
|
20
|
+
else if (arg === '--verbose')
|
|
21
|
+
verbose = true;
|
|
22
|
+
else if (arg === '--no-color')
|
|
23
|
+
noColor = true;
|
|
24
|
+
else if (arg === '--color')
|
|
25
|
+
noColor = false;
|
|
26
|
+
else
|
|
27
|
+
rest.push(arg);
|
|
28
|
+
}
|
|
29
|
+
return { json, verbose, noColor, rest };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=global-flags.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { exists } from './copy-template.js';
|
|
5
|
+
// Shared by `docouture eject kroki` and `docouture teardown kroki` — GH-44. Both
|
|
6
|
+
// need to locate the `docker compose` file @inditextech/docouture-antora-
|
|
7
|
+
// extensions' `kroki-prewarm.js`/`kroki-docker.js` use to run Kroki; kept in
|
|
8
|
+
// one place so the two commands can never disagree about where that file is.
|
|
9
|
+
export const PACKAGE_NAME = '@inditextech/docouture-antora-extensions';
|
|
10
|
+
export const RESOURCE = 'resources/kroki-compose.yml';
|
|
11
|
+
export const OVERRIDE_FILENAME = 'kroki-compose.yml';
|
|
12
|
+
/**
|
|
13
|
+
* The package's own bundled default — what `docouture eject kroki` copies out,
|
|
14
|
+
* regardless of whether a site has already ejected/customized one of its
|
|
15
|
+
* own. Resolved from the SITE's own installed copy of the package (via
|
|
16
|
+
* `createRequire` against its `package.json`, exactly like `publish.ts`'s
|
|
17
|
+
* `loadDriver` resolves a publish driver), not from any copy this CLI
|
|
18
|
+
* itself might depend on — this CLI has no dependency on
|
|
19
|
+
* @inditextech/docouture-antora-extensions at all, since a plain `docouture` install
|
|
20
|
+
* has no reason to carry every package a scaffolded site might use.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} packageJsonFile - the SITE's `package.json` (`docs/package.json`).
|
|
23
|
+
* @returns {string} an absolute path to the bundled compose file.
|
|
24
|
+
* @throws if the package (or this resource within it) cannot be resolved —
|
|
25
|
+
* not installed, or an old version predating this feature.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveBundledComposeFile(packageJsonFile) {
|
|
28
|
+
return createRequire(packageJsonFile).resolve(`${PACKAGE_NAME}/${RESOURCE}`);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Whichever compose file a build actually used — `kroki-docker.js`'s own
|
|
32
|
+
* resolution order, reproduced here: an ejected override at the site root
|
|
33
|
+
* first, the bundled default otherwise. Used by `docouture teardown kroki`,
|
|
34
|
+
* which has to target the file that's actually running, not necessarily the
|
|
35
|
+
* bundled one.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} siteRoot - `docs/`, the directory containing `antora-playbook.yml`
|
|
38
|
+
* (== `playbook.dir` on the Antora side).
|
|
39
|
+
* @returns {Promise<string | null>} the resolved path, or `null` if neither
|
|
40
|
+
* an override nor a resolvable bundled default exists.
|
|
41
|
+
*/
|
|
42
|
+
export async function resolveEffectiveComposeFile(siteRoot) {
|
|
43
|
+
const override = join(siteRoot, OVERRIDE_FILENAME);
|
|
44
|
+
if (await exists(override))
|
|
45
|
+
return override;
|
|
46
|
+
const packageJsonFile = join(siteRoot, 'package.json');
|
|
47
|
+
try {
|
|
48
|
+
return resolveBundledComposeFile(packageJsonFile);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=kroki-compose.js.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Regex-based reader for the handful of `antora-playbook.yml` fields `docouture
|
|
3
|
+
// dev` and `docouture doctor` need — deliberately not a YAML parser, matching
|
|
4
|
+
// the style of antora-yml.ts (this package) and scripts/dev.mjs's own
|
|
5
|
+
// readSiteBasePath (the monorepo dev server this reasoning was lifted from).
|
|
6
|
+
// Every value here is only ever read, never rewritten, so a full parse/dump
|
|
7
|
+
// round trip buys nothing and would cost the file's comments.
|
|
8
|
+
//
|
|
9
|
+
// Values live at different nesting depths — `site.start_page` is a direct
|
|
10
|
+
// child of a top-level key, `content.sources[0].start_path` is a field on
|
|
11
|
+
// the first item of a list nested under one — but both are found the same
|
|
12
|
+
// way: narrow to the top-level key's own line range first (stopping at the
|
|
13
|
+
// next top-level key, so a same-named field belonging to a different block
|
|
14
|
+
// is never picked up by accident), then take the first indented line
|
|
15
|
+
// matching the field, whatever its exact depth.
|
|
16
|
+
function topLevelBlock(content, key) {
|
|
17
|
+
const lines = content.split('\n');
|
|
18
|
+
const out = [];
|
|
19
|
+
let inBlock = false;
|
|
20
|
+
for (const line of lines) {
|
|
21
|
+
if (/^\s*(?:#.*)?$/.test(line)) {
|
|
22
|
+
if (inBlock)
|
|
23
|
+
out.push(line);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (/^\S/.test(line)) {
|
|
27
|
+
if (inBlock)
|
|
28
|
+
break;
|
|
29
|
+
inBlock = new RegExp(`^${key}:`).test(line);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (inBlock)
|
|
33
|
+
out.push(line);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
function firstField(lines, key) {
|
|
38
|
+
// The optional `-\s*` accounts for a YAML list item's first field, e.g.
|
|
39
|
+
// `content.sources[0].url` is written ` - url: ..` — every other field
|
|
40
|
+
// on that same item (start_path, branches, ...) is indented the same but
|
|
41
|
+
// without the dash, which the `?` also covers.
|
|
42
|
+
const re = new RegExp(`^\\s*(?:-\\s*)?${key}:\\s*(.+?)\\s*$`);
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
const match = re.exec(line);
|
|
45
|
+
if (match?.[1])
|
|
46
|
+
return match[1].replace(/^['"]|['"]$/g, '');
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
/** `site.url` — unset while developing locally, so this is commonly null. */
|
|
51
|
+
export function readSiteUrl(content) {
|
|
52
|
+
return firstField(topLevelBlock(content, 'site'), 'url');
|
|
53
|
+
}
|
|
54
|
+
/** `site.start_page`, e.g. `my-site::index.adoc`. */
|
|
55
|
+
export function readStartPage(content) {
|
|
56
|
+
return firstField(topLevelBlock(content, 'site'), 'start_page');
|
|
57
|
+
}
|
|
58
|
+
/** The component-name prefix of `site.start_page`, before its `::`. */
|
|
59
|
+
export function readStartPageComponent(content) {
|
|
60
|
+
const startPage = readStartPage(content);
|
|
61
|
+
if (!startPage)
|
|
62
|
+
return null;
|
|
63
|
+
const separator = startPage.indexOf('::');
|
|
64
|
+
return separator === -1 ? null : startPage.slice(0, separator);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* `content.sources[0].start_path` — repository-root relative directory
|
|
68
|
+
* holding `docs/antora.yml`. Only the first source is read: every shape
|
|
69
|
+
* `docouture new` generates has exactly one, and doctor only needs to catch the
|
|
70
|
+
* common single-source misconfiguration, not aggregate every source in a
|
|
71
|
+
* hand-authored multi-source playbook.
|
|
72
|
+
*/
|
|
73
|
+
export function readStartPath(content) {
|
|
74
|
+
return firstField(topLevelBlock(content, 'content'), 'start_path');
|
|
75
|
+
}
|
|
76
|
+
/** `content.sources[0].url` — see this package's own comment on why it must
|
|
77
|
+
* be `..`, not `.`, when the playbook does not sit at the repository root. */
|
|
78
|
+
export function readSourceUrl(content) {
|
|
79
|
+
return firstField(topLevelBlock(content, 'content'), 'url');
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* `output.dir` — where `antora build` writes the built site. Unset in every
|
|
83
|
+
* playbook `docouture new` scaffolds (Antora's own default, `build/site`,
|
|
84
|
+
* applies), so `docouture publish` falls back to that same default rather than
|
|
85
|
+
* treating a missing value as an error.
|
|
86
|
+
*/
|
|
87
|
+
export function readOutputDir(content) {
|
|
88
|
+
return firstField(topLevelBlock(content, 'output'), 'dir');
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=playbook-yml.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the git repository root containing `startDir` — so `docouture dev`,
|
|
5
|
+
* `docouture build` and `docouture doctor` work from anywhere inside a scaffolded
|
|
6
|
+
* repository (its root, inside `docs/`, in a nested page directory, ...),
|
|
7
|
+
* not only when `--dir`/cwd happens to already be the repository root.
|
|
8
|
+
*
|
|
9
|
+
* Returns `startDir` itself when it is not inside a git repository at all —
|
|
10
|
+
* callers then fail with their own "no site found at ..." message instead of
|
|
11
|
+
* a git error, which is the more useful failure for someone who simply
|
|
12
|
+
* hasn't run `docouture new` yet.
|
|
13
|
+
*/
|
|
14
|
+
export function findRepoRoot(startDir) {
|
|
15
|
+
return new Promise((resolvePromise) => {
|
|
16
|
+
execFile('git', ['rev-parse', '--show-toplevel'], { cwd: startDir }, (err, stdout) => {
|
|
17
|
+
resolvePromise(err ? startDir : stdout.trim());
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=repo-root.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { debugLog } from './debug-log.js';
|
|
4
|
+
/**
|
|
5
|
+
* Runs `npm run <script>` in `cwd` with inherited stdio, resolving the exit
|
|
6
|
+
* code. Used by `docouture build` — a genuine thin wrapper, unlike `docouture dev`,
|
|
7
|
+
* which owns its server logic directly (see lib/dev-server.ts) rather than
|
|
8
|
+
* shelling out to a copy of it.
|
|
9
|
+
*/
|
|
10
|
+
export function runNpmScript(script, options) {
|
|
11
|
+
return new Promise((resolvePromise, reject) => {
|
|
12
|
+
const args = ['run', script, ...(options.args?.length ? ['--', ...options.args] : [])];
|
|
13
|
+
// On Windows, npm is installed as `npm.cmd` — a bare `spawn('npm', ...)`
|
|
14
|
+
// without `shell: true` resolves nothing and throws ENOENT. Using the
|
|
15
|
+
// platform-specific binary name avoids that without opting into a full
|
|
16
|
+
// shell (and its own quoting/escaping rules) just to launch one command.
|
|
17
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
18
|
+
debugLog(`spawning: ${npmCmd} ${args.join(' ')} (cwd=${options.cwd})`);
|
|
19
|
+
const child = spawn(npmCmd, args, {
|
|
20
|
+
cwd: options.cwd,
|
|
21
|
+
stdio: 'inherit',
|
|
22
|
+
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
23
|
+
});
|
|
24
|
+
// Forward SIGINT/SIGTERM to the child so `docouture build` sent a signal
|
|
25
|
+
// directly (not just a terminal Ctrl-C, which already reaches the whole
|
|
26
|
+
// foreground process group on its own) still gives npm/antora a chance
|
|
27
|
+
// to shut down cleanly instead of being silently orphaned.
|
|
28
|
+
const onSigint = () => {
|
|
29
|
+
child.kill('SIGINT');
|
|
30
|
+
};
|
|
31
|
+
const onSigterm = () => {
|
|
32
|
+
child.kill('SIGTERM');
|
|
33
|
+
};
|
|
34
|
+
process.once('SIGINT', onSigint);
|
|
35
|
+
process.once('SIGTERM', onSigterm);
|
|
36
|
+
function cleanup() {
|
|
37
|
+
process.removeListener('SIGINT', onSigint);
|
|
38
|
+
process.removeListener('SIGTERM', onSigterm);
|
|
39
|
+
}
|
|
40
|
+
child.on('close', (code) => {
|
|
41
|
+
cleanup();
|
|
42
|
+
resolvePromise(code ?? 1);
|
|
43
|
+
});
|
|
44
|
+
child.on('error', (err) => {
|
|
45
|
+
cleanup();
|
|
46
|
+
reject(err);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=run-script.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
import { getContext } from './cli-context.js';
|
|
3
|
+
// A small themed colour layer, replacing both bin.ts/new.ts's direct use of
|
|
4
|
+
// picocolors and doctor.ts's own hand-rolled raw ANSI escapes (see their old
|
|
5
|
+
// comments) with one place that: (a) has a single, consistent set of
|
|
6
|
+
// semantic roles (success/error/warn/info/muted/bold) instead of each file
|
|
7
|
+
// picking its own raw colour, and (b) decides whether colour is on fresh on
|
|
8
|
+
// every single call rather than once at import time.
|
|
9
|
+
//
|
|
10
|
+
// That second point is why this doesn't just re-export picocolors: picocolors
|
|
11
|
+
// (like most colour libraries) computes `isColorSupported` once, when the
|
|
12
|
+
// module is first imported — which happens before bin.ts has even parsed
|
|
13
|
+
// --no-color off argv. Re-checking `getContext().noColor` on every call
|
|
14
|
+
// means --no-color (and NO_COLOR/FORCE_COLOR, which can't change at runtime
|
|
15
|
+
// either way) are honoured for every single line printed, not just the ones
|
|
16
|
+
// printed by a module that happened to import picocolors after the flag was
|
|
17
|
+
// known.
|
|
18
|
+
const CODES = {
|
|
19
|
+
reset: '\u001b[0m',
|
|
20
|
+
bold: '\u001b[1m',
|
|
21
|
+
dim: '\u001b[2m',
|
|
22
|
+
red: '\u001b[31m',
|
|
23
|
+
green: '\u001b[32m',
|
|
24
|
+
yellow: '\u001b[33m',
|
|
25
|
+
cyan: '\u001b[36m',
|
|
26
|
+
};
|
|
27
|
+
function colourEnabled() {
|
|
28
|
+
if (getContext().noColor)
|
|
29
|
+
return false;
|
|
30
|
+
if (process.env.NO_COLOR)
|
|
31
|
+
return false;
|
|
32
|
+
if (process.env.FORCE_COLOR)
|
|
33
|
+
return true;
|
|
34
|
+
return process.stdout.isTTY === true;
|
|
35
|
+
}
|
|
36
|
+
function wrap(code) {
|
|
37
|
+
return (text) => (colourEnabled() ? `${code}${text}${CODES.reset}` : text);
|
|
38
|
+
}
|
|
39
|
+
export const theme = {
|
|
40
|
+
bold: wrap(CODES.bold),
|
|
41
|
+
dim: wrap(CODES.dim),
|
|
42
|
+
success: wrap(CODES.green),
|
|
43
|
+
error: wrap(CODES.red),
|
|
44
|
+
warn: wrap(CODES.yellow),
|
|
45
|
+
info: wrap(CODES.cyan),
|
|
46
|
+
};
|
|
47
|
+
/** Exposed for callers (doctor.ts) that need a plain yes/no rather than a wrapped string. */
|
|
48
|
+
export function isColourEnabled() {
|
|
49
|
+
return colourEnabled();
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docs-internals
|
|
3
|
+
description: "How this Antora documentation site is put together: the playbook, the docs/antora.yml component descriptor, the four names that must agree, mono-module vs. multi-module layout, and the home-page vs. content-page patterns. USE WHEN adding a page or module, renaming the site/component, choosing between a single-module and multi-module layout, building a landing/home page, or diagnosing a site that builds with zero pages or fails with 'start page not found'. EXAMPLES: 'add a new module', 'rename this docs site', 'build a marketing home page', 'the site builds but has no pages', 'start page not found', 'should this be one module or several'."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Site structure
|
|
7
|
+
|
|
8
|
+
This site was scaffolded by `docouture new` (`@inditextech/docouture-cli`). This skill covers the
|
|
9
|
+
pieces that make it hang together — where each name is set, how a page becomes reachable,
|
|
10
|
+
and the two page patterns (home vs. content) worth copying rather than reinventing.
|
|
11
|
+
|
|
12
|
+
- `reference/naming.md` — the four names that must agree, and how to fix them when they
|
|
13
|
+
don't (`docouture doctor` checks this automatically).
|
|
14
|
+
- `reference/page-patterns.md` — the home-page block structure and the recurring
|
|
15
|
+
content-page shapes, plus mono-module vs. multi-module layout.
|
|
16
|
+
- `reference/antora-extensions.md` — what `@inditextech/docouture-antora-extensions` (a
|
|
17
|
+
different kind of extension from the authoring blocks in `writing-docs-pages`) provides:
|
|
18
|
+
the module switcher, site footer, search index and `llms.txt` generation.
|
|
19
|
+
|
|
20
|
+
For AsciiDoc authoring itself — xrefs, admonitions, code blocks, this site's custom
|
|
21
|
+
blocks — see the `writing-docs-pages` skill.
|
|
22
|
+
|
|
23
|
+
## The moving pieces
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
docs/
|
|
27
|
+
antora-playbook.yml site title, content source, UI bundle, asciidoc/antora extensions
|
|
28
|
+
antora-playbook.local.yml same shape, but content.sources[] is just `branches: HEAD` —
|
|
29
|
+
what `docouture dev` and PR verification build against, since a
|
|
30
|
+
PR/feature-branch checkout doesn't have `main` or a release tag
|
|
31
|
+
package.json name, devDependencies (docouture-cli, ui-bundle, the two extension
|
|
32
|
+
packages), the `build`/`dev` scripts
|
|
33
|
+
src/
|
|
34
|
+
antora.yml component descriptor: name, title, version, nav
|
|
35
|
+
modules/<module>/nav.adoc navigation tree, one per module
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The whole starter template — `package.json`, both playbooks, the nested `src/` — was
|
|
39
|
+
copied under this repository's own `docs/`, so `antora-playbook.yml` lives at
|
|
40
|
+
`<repo-root>/docs/antora-playbook.yml` and the component descriptor ends up one level
|
|
41
|
+
further down, at `docs/src/antora.yml`. That's why `antora-playbook.yml`'s
|
|
42
|
+
`content.sources[0]` reads `url: ..` (this repo's root, one level up from `docs/`) and
|
|
43
|
+
`start_path: docs/src`.
|
|
44
|
+
|
|
45
|
+
## Versioning
|
|
46
|
+
|
|
47
|
+
`docs/antora.yml`'s `version`/`prerelease` fields are identical on `main` regardless of
|
|
48
|
+
which versioning mode this site uses (`version: prerelease`, `prerelease: true`) — what
|
|
49
|
+
differs is only which git refs `antora-playbook.yml`'s `content.sources[0]` aggregates
|
|
50
|
+
from (`tags: ['stable']` vs. `tags: ['v*']`). If this site was scaffolded with
|
|
51
|
+
`--mode versioned`, see the `docs-versioning` skill for cutting a release; a
|
|
52
|
+
**standalone**-mode site (the default) has no separate skill for this — `docouture doctor`
|
|
53
|
+
and the `docouture-release.yml` workflow are all that's needed.
|
package/build/templates/agent-support/.claude/skills/docs-internals/reference/antora-extensions.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @inditextech/docouture-antora-extensions
|
|
2
|
+
|
|
3
|
+
Registered under `antora-playbook.yml`'s **`antora.extensions`** key — a different key,
|
|
4
|
+
and a different kind of extension, from `writing-docs-pages`' `asciidoc.extensions`
|
|
5
|
+
(`@inditextech/docouture-asciidoc-extensions`). This one hooks Antora's own site-generation
|
|
6
|
+
pipeline rather than the AsciiDoc processor; listing either package under the other's key
|
|
7
|
+
makes Antora log a warning and skip it.
|
|
8
|
+
|
|
9
|
+
One package require registers five sub-extensions together — they can't be enabled
|
|
10
|
+
individually:
|
|
11
|
+
|
|
12
|
+
| sub-extension | reads | does |
|
|
13
|
+
| --------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
14
|
+
| module switcher | `nav_modules:` in `docs/src/antora.yml` | stamps each nav tree with its module/title/description/icon, so the UI can show one module's nav at a time with a switcher — see `reference/page-patterns.md`'s "Mono-module vs. multi-module" section. A no-op until `nav_modules:` is authored |
|
|
15
|
+
| site footer | `footer:` in `docs/src/antora.yml` | resolves a structured `footer: groups: […]` list of link groups and attaches it to the component version, for the UI's footer partial. A no-op until `footer:` is authored |
|
|
16
|
+
| search index | (nothing authored) | builds a full-text search index at build time, published per component version. **This is what the UI bundle's own search dialog reads** — without this extension registered, the search UI renders but has nothing to search |
|
|
17
|
+
| `llms.txt` generation | `llms:` in `docs/src/antora.yml` (optional `summary`/`exclude`) | generates `llms.txt` and `llms-full.txt` at the site root — a Markdown index and full dump, meant for LLM ingestion (see `https://llmstxt.org`), so an AI agent (or another tool) can read the site without scraping rendered HTML |
|
|
18
|
+
| Shiki prewarm | `asciidoc.attributes.source-highlighter` in `antora-playbook.yml` | pre-builds the Shiki syntax highlighter once, up front. Only useful if `source-highlighter: shiki` is actually set — this site keeps Antora's default `highlight.js` instead, so this sub-extension runs unconditionally but has nothing to prewarm for. Harmless, just a small unused build-time cost |
|
|
19
|
+
|
|
20
|
+
## Authoring the optional keys
|
|
21
|
+
|
|
22
|
+
All three authored keys (`nav_modules`, `footer`, `llms`) live in `docs/src/antora.yml`,
|
|
23
|
+
not the playbook — `site.keys` there is declared as a flat primitive map and can't carry
|
|
24
|
+
a nested list, so the component descriptor is the one place a nested structure can be
|
|
25
|
+
authored:
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
# docs/src/antora.yml
|
|
29
|
+
nav_modules:
|
|
30
|
+
- module: framework
|
|
31
|
+
title: Framework
|
|
32
|
+
description: One-line description.
|
|
33
|
+
icon: design/grid-outlined
|
|
34
|
+
|
|
35
|
+
footer:
|
|
36
|
+
groups:
|
|
37
|
+
- title: Resources
|
|
38
|
+
links:
|
|
39
|
+
- text: Home
|
|
40
|
+
url: ROOT:index.adoc
|
|
41
|
+
- text: Repository
|
|
42
|
+
url: https://github.com/example/example
|
|
43
|
+
|
|
44
|
+
llms:
|
|
45
|
+
summary: >-
|
|
46
|
+
One or two sentences describing what this site documents — becomes the
|
|
47
|
+
blockquote under the site title in the generated llms.txt.
|
|
48
|
+
# exclude:
|
|
49
|
+
# - module:some-internal-page.adoc
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`url`/`links[].url` values are either a page ID (the same string `xref:...[]` accepts) or
|
|
53
|
+
a literal URL — a page ID that resolves to nothing is dropped with a warning rather than
|
|
54
|
+
rendered dead.
|