@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.
Files changed (94) hide show
  1. package/build/bin.js +321 -0
  2. package/build/commands/build.js +22 -0
  3. package/build/commands/completion.js +69 -0
  4. package/build/commands/dev.js +67 -0
  5. package/build/commands/doctor.js +156 -0
  6. package/build/commands/eject.js +71 -0
  7. package/build/commands/new.js +512 -0
  8. package/build/commands/publish.js +111 -0
  9. package/build/commands/teardown.js +66 -0
  10. package/build/commands/upgrade.js +156 -0
  11. package/build/commands/version.js +60 -0
  12. package/build/lib/agents-md.js +67 -0
  13. package/build/lib/antora-log.js +36 -0
  14. package/build/lib/antora-yml.js +47 -0
  15. package/build/lib/args.js +26 -0
  16. package/build/lib/cli-context.js +17 -0
  17. package/build/lib/cli-info.js +20 -0
  18. package/build/lib/config-resolver.js +23 -0
  19. package/build/lib/copy-template.js +162 -0
  20. package/build/lib/debug-log.js +21 -0
  21. package/build/lib/detect-package-manager.js +79 -0
  22. package/build/lib/dev-server.js +337 -0
  23. package/build/lib/doctor-checks.js +240 -0
  24. package/build/lib/global-flags.js +31 -0
  25. package/build/lib/kroki-compose.js +54 -0
  26. package/build/lib/playbook-yml.js +90 -0
  27. package/build/lib/repo-root.js +21 -0
  28. package/build/lib/run-script.js +50 -0
  29. package/build/lib/theme.js +51 -0
  30. package/build/templates/agent-support/.claude/skills/docs-internals/SKILL.md +53 -0
  31. package/build/templates/agent-support/.claude/skills/docs-internals/reference/antora-extensions.md +54 -0
  32. package/build/templates/agent-support/.claude/skills/docs-internals/reference/naming.md +41 -0
  33. package/build/templates/agent-support/.claude/skills/docs-internals/reference/page-patterns.md +238 -0
  34. package/build/templates/agent-support/.claude/skills/docs-versioning.versioned/SKILL.md +37 -0
  35. package/build/templates/agent-support/.claude/skills/docs-versioning.versioned/reference/releasing.md +106 -0
  36. package/build/templates/agent-support/.claude/skills/documenting-your-repo/SKILL.md +55 -0
  37. package/build/templates/agent-support/.claude/skills/documenting-your-repo/reference/content-sourcing.md +55 -0
  38. package/build/templates/agent-support/.claude/skills/documenting-your-repo/reference/maintenance-loop.md +68 -0
  39. package/build/templates/agent-support/.claude/skills/documenting-your-repo/reference/structure-planning.md +102 -0
  40. package/build/templates/agent-support/.claude/skills/writing-docs-pages/SKILL.md +112 -0
  41. package/build/templates/agent-support/.claude/skills/writing-docs-pages/reference/docouture-blocks.md +170 -0
  42. package/build/templates/agent-support/.claude/skills/writing-docs-pages/reference/language-basics.md +402 -0
  43. package/build/templates/agent-support/.opencode/skills/docs-internals/SKILL.md +53 -0
  44. package/build/templates/agent-support/.opencode/skills/docs-internals/reference/antora-extensions.md +54 -0
  45. package/build/templates/agent-support/.opencode/skills/docs-internals/reference/naming.md +41 -0
  46. package/build/templates/agent-support/.opencode/skills/docs-internals/reference/page-patterns.md +238 -0
  47. package/build/templates/agent-support/.opencode/skills/docs-versioning.versioned/SKILL.md +37 -0
  48. package/build/templates/agent-support/.opencode/skills/docs-versioning.versioned/reference/releasing.md +106 -0
  49. package/build/templates/agent-support/.opencode/skills/documenting-your-repo/SKILL.md +55 -0
  50. package/build/templates/agent-support/.opencode/skills/documenting-your-repo/reference/content-sourcing.md +55 -0
  51. package/build/templates/agent-support/.opencode/skills/documenting-your-repo/reference/maintenance-loop.md +68 -0
  52. package/build/templates/agent-support/.opencode/skills/documenting-your-repo/reference/structure-planning.md +102 -0
  53. package/build/templates/agent-support/.opencode/skills/writing-docs-pages/SKILL.md +112 -0
  54. package/build/templates/agent-support/.opencode/skills/writing-docs-pages/reference/docouture-blocks.md +170 -0
  55. package/build/templates/agent-support/.opencode/skills/writing-docs-pages/reference/language-basics.md +402 -0
  56. package/build/templates/agent-support/AGENTS.md +72 -0
  57. package/build/templates/starter/antora-playbook.local.yml +99 -0
  58. package/build/templates/starter/antora-playbook.versioned.yml +167 -0
  59. package/build/templates/starter/antora-playbook.yml +189 -0
  60. package/build/templates/starter/gitignore +3 -0
  61. package/build/templates/starter/package.json +37 -0
  62. package/build/templates/starter/scripts/check-links.mjs +178 -0
  63. package/build/templates/starter/src/antora.yml +85 -0
  64. package/build/templates/starter/src/modules/ROOT/images/card-placeholder.png +0 -0
  65. package/build/templates/starter/src/modules/ROOT/images/feature-placeholder-dark.png +0 -0
  66. package/build/templates/starter/src/modules/ROOT/images/feature-placeholder.png +0 -0
  67. package/build/templates/starter/src/modules/ROOT/images/hero-placeholder.png +0 -0
  68. package/build/templates/starter/src/modules/ROOT/pages/index.adoc +124 -0
  69. package/build/templates/starter/src/modules/main/nav.adoc +19 -0
  70. package/build/templates/starter/src/modules/main/pages/app.adoc +5 -0
  71. package/build/templates/starter/src/modules/main/pages/architecture.adoc +6 -0
  72. package/build/templates/starter/src/modules/main/pages/changelog/index.adoc +9 -0
  73. package/build/templates/starter/src/modules/main/pages/components.adoc +701 -0
  74. package/build/templates/starter/src/modules/main/pages/contributing.adoc +6 -0
  75. package/build/templates/starter/src/modules/main/pages/getting-started.adoc +169 -0
  76. package/build/templates/starter/src/modules/main/pages/glossary.adoc +8 -0
  77. package/build/templates/starter/src/modules/main/pages/index.adoc +9 -0
  78. package/build/templates/starter/src/modules/main/pages/prerequisites.adoc +55 -0
  79. package/build/templates/starter/src/modules/main/pages/quickstart.adoc +21 -0
  80. package/build/templates/starter/src/modules/main/pages/release-notes/index.adoc +9 -0
  81. package/build/templates/starter/src/modules/main/pages/requirements.adoc +8 -0
  82. package/build/templates/starter/src/modules/main/pages/roadmap.adoc +4 -0
  83. package/build/templates/starter/src/modules/main/pages/structure.adoc +5 -0
  84. package/build/templates/starter/src/release-version.versioned +1 -0
  85. package/build/templates/starter/supplemental-ui/favicon.ico +0 -0
  86. package/build/templates/starter/supplemental-ui/product-logo-dark.png +0 -0
  87. package/build/templates/starter/supplemental-ui/product-logo.png +0 -0
  88. package/build/templates/workflows/docouture-kroki-cache-warm.yml +101 -0
  89. package/build/templates/workflows/docouture-pr-verify.yml +120 -0
  90. package/build/templates/workflows/docouture-publish-prerelease.yml +122 -0
  91. package/build/templates/workflows/docouture-publish.yml +138 -0
  92. package/build/templates/workflows/docouture-release-preview.yml +198 -0
  93. package/build/templates/workflows/docouture-release.yml +414 -0
  94. 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.
@@ -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.