@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,71 @@
1
+ 'use strict';
2
+ import { copyFile } from 'node:fs/promises';
3
+ import { join, resolve } from 'node:path';
4
+ import { parseArgs } from '../lib/args.js';
5
+ import { exists } from '../lib/copy-template.js';
6
+ import { findRepoRoot } from '../lib/repo-root.js';
7
+ import { PACKAGE_NAME, RESOURCE, OVERRIDE_FILENAME, resolveBundledComposeFile } from '../lib/kroki-compose.js';
8
+ // `docouture eject kroki` copies the Kroki + mermaid-companion `docker compose`
9
+ // definition @inditextech/docouture-antora-extensions' `kroki-prewarm.js` starts
10
+ // automatically (GH-44) out to the site's own repository, as
11
+ // `docs/kroki-compose.yml`. kroki-prewarm.js checks that exact path first,
12
+ // before its own bundled default — see its own header, and kroki-docker.js's
13
+ // — so a site that runs this once and edits the result (a different image
14
+ // version, a companion for another diagram type, ...) never needs to fork
15
+ // or patch the package itself; the auto-start logic just picks up whatever
16
+ // is there. `docouture teardown kroki` is the matching stop side — see that
17
+ // command's own header.
18
+ //
19
+ // A site that never added `kroki-enabled: true` (and so never installed
20
+ // @inditextech/docouture-antora-extensions, or has an old version predating this
21
+ // resource) gets a clear error telling it what to add, rather than a raw
22
+ // `MODULE_NOT_FOUND` — see lib/kroki-compose.js's own header for why
23
+ // resolution goes through the site's own install rather than this CLI's.
24
+ const SUPPORTED_TARGETS = {
25
+ kroki: {
26
+ resolve: resolveBundledComposeFile,
27
+ description: 'Kroki + mermaid-companion docker compose definition (docs/kroki-compose.yml)',
28
+ },
29
+ };
30
+ export async function runEject(argv, deps = {}) {
31
+ const { positional, flags } = parseArgs(argv);
32
+ const target = positional[0];
33
+ if (!target || !(target in SUPPORTED_TARGETS)) {
34
+ console.error('usage: docouture eject <target> [--dir <path>]');
35
+ console.error('supported targets:');
36
+ for (const [name, { description }] of Object.entries(SUPPORTED_TARGETS)) {
37
+ console.error(` ${name} — ${description}`);
38
+ }
39
+ return 1;
40
+ }
41
+ const startDir = typeof flags.dir === 'string' ? resolve(flags.dir) : resolve(process.cwd());
42
+ const repoRoot = await findRepoRoot(startDir);
43
+ const siteRoot = join(repoRoot, 'docs');
44
+ const packageJsonFile = join(siteRoot, 'package.json');
45
+ if (!(await exists(packageJsonFile))) {
46
+ console.error(`no package.json found at '${siteRoot}'`);
47
+ console.error('pass --dir <path> to a component root (the parent of docs/), or run docouture new first');
48
+ return 1;
49
+ }
50
+ const destFile = join(siteRoot, OVERRIDE_FILENAME);
51
+ if (await exists(destFile)) {
52
+ console.error(`'${destFile}' already exists`);
53
+ console.error('remove it first if you want to re-eject the bundled default');
54
+ return 1;
55
+ }
56
+ const resolveFile = deps.resolveBundledComposeFile || resolveBundledComposeFile;
57
+ let sourceFile;
58
+ try {
59
+ sourceFile = resolveFile(packageJsonFile);
60
+ }
61
+ catch {
62
+ console.error(`could not find ${RESOURCE} in ${PACKAGE_NAME}`);
63
+ console.error(`add it to ${packageJsonFile}'s devDependencies, e.g.: npm install --save-dev ${PACKAGE_NAME}`);
64
+ return 1;
65
+ }
66
+ await copyFile(sourceFile, destFile);
67
+ console.log(`wrote ${destFile}`);
68
+ console.log('edit it freely — the next build automatically prefers it over the bundled default');
69
+ return 0;
70
+ }
71
+ //# sourceMappingURL=eject.js.map
@@ -0,0 +1,512 @@
1
+ 'use strict';
2
+ import { input, select, confirm } from '@inquirer/prompts';
3
+ import { execFileSync } from 'node:child_process';
4
+ import { readFile, writeFile } from 'node:fs/promises';
5
+ import { dirname, join, relative, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { theme } from '../lib/theme.js';
8
+ import { parseArgs } from '../lib/args.js';
9
+ import { readCliInfo } from '../lib/cli-info.js';
10
+ import { copyTemplate, exists, isEmptyOrMissing, renderTemplateFile, writeTemplateFile } from '../lib/copy-template.js';
11
+ import { detectPackageManager, packageManagerPlan, } from '../lib/detect-package-manager.js';
12
+ import { findRepoRoot } from '../lib/repo-root.js';
13
+ import { AGENTS_MD_FILENAME, hasManagedSection, mergeAgentsMd } from '../lib/agents-md.js';
14
+ // Matches the rule an npm package name (and, not coincidentally, an Antora
15
+ // component name — both end up as URL segments) can safely be: this is
16
+ // stricter than npm's own rule, which also allows dots and a leading `@scope/`
17
+ // that makes no sense for a directory name here.
18
+ const NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
19
+ // The only two versioning shapes `docouture new` scaffolds — both real,
20
+ // releasable configurations handled by the templated docouture-release.yml — see
21
+ // the docs-site-package skill's reference/versioning-modes.md. 'standalone'
22
+ // (the default): `main` always builds as the prerelease/preview version, and
23
+ // a release just force-moves a rolling `stable` tag — no historical archive,
24
+ // appropriate for a product where only "now" and "what's next" matter.
25
+ // 'versioned': `main` builds as the prerelease version too — docs/antora.yml
26
+ // is identical to the standalone shape on main, for both modes — but every
27
+ // release is instead its own immutable `vX.Y.Z` git tag, kept forever —
28
+ // appropriate for a library/SDK whose consumers pin an old version. (A bare,
29
+ // unversioned checkout — no prerelease/stable split at all — is not offered
30
+ // here: it only ever comes up as an ad-hoc `docouture dev` preview before a mode
31
+ // is chosen, never as something worth releasing.)
32
+ const MODES = ['standalone', 'versioned'];
33
+ const PACKAGE_MANAGERS = ['npm', 'pnpm'];
34
+ // Filenames docouture-publish.yml / docouture-publish-prerelease.yml /
35
+ // docouture-release.yml / docouture-release-preview.yml / docouture-pr-verify.yml /
36
+ // docouture-kroki-cache-warm.yml are templated under (see templates/workflows/)
37
+ // — kept as a literal list here so the pre-flight conflict check below can
38
+ // name exactly which ones would be overwritten without having to read the
39
+ // template directory to find out.
40
+ const WORKFLOW_NAMES = [
41
+ 'docouture-publish.yml',
42
+ 'docouture-publish-prerelease.yml',
43
+ 'docouture-release.yml',
44
+ 'docouture-release-preview.yml',
45
+ 'docouture-pr-verify.yml',
46
+ 'docouture-kroki-cache-warm.yml',
47
+ ];
48
+ // Repo-root-relative paths `templates/agent-support/` lands under (see
49
+ // new.ts's own copyTemplate call below) — kept as a literal list, same
50
+ // reasoning as WORKFLOW_NAMES above, so the pre-flight conflict check can
51
+ // name exactly what would be overwritten. `docs-versioning` is deliberately
52
+ // absent here: it is scaffolded only under `--mode versioned` (see the
53
+ // `.versioned`-marker copy further down), so an existing standalone-mode
54
+ // site with no such directory yet is never blocked by this check on it.
55
+ const AGENT_SUPPORT_PATHS = [
56
+ join('.opencode', 'skills', 'documenting-your-repo'),
57
+ join('.opencode', 'skills', 'writing-docs-pages'),
58
+ join('.opencode', 'skills', 'docs-internals'),
59
+ join('.claude', 'skills', 'documenting-your-repo'),
60
+ join('.claude', 'skills', 'writing-docs-pages'),
61
+ join('.claude', 'skills', 'docs-internals'),
62
+ ];
63
+ function titleCase(name) {
64
+ return name
65
+ .split('-')
66
+ .filter(Boolean)
67
+ .map((word) => word[0].toUpperCase() + word.slice(1))
68
+ .join(' ');
69
+ }
70
+ function isInsideGitWorkTree(dir) {
71
+ try {
72
+ execFileSync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: dir, stdio: 'ignore' });
73
+ return true;
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }
79
+ // origin's remote URL as git reports it, or undefined if there isn't one
80
+ // configured yet (a fresh `git init` with no remote added) — the shared
81
+ // starting point for both repoWebUrl and githubPagesUrl below, so there's
82
+ // only one execFileSync call to fail/mock, not two.
83
+ function originRemoteUrl(dir) {
84
+ try {
85
+ return execFileSync('git', ['remote', 'get-url', 'origin'], { cwd: dir, stdio: ['ignore', 'pipe', 'ignore'] })
86
+ .toString()
87
+ .trim();
88
+ }
89
+ catch {
90
+ return undefined;
91
+ }
92
+ }
93
+ // The web (https) form of this repo's `origin` remote, or undefined if
94
+ // there isn't one configured yet. Baked into the scaffolded package.json's
95
+ // `docouture.checkLinks.ignore` (see TemplateValues.repoIgnoreGlob, and
96
+ // scripts/check-links.mjs's own comment on that key) so the repo-link.hbs
97
+ // header/nav link — which 404s to an anonymous crawler whenever this repo
98
+ // is private, indistinguishable from one that doesn't exist — doesn't fail
99
+ // docouture-pr-verify.yml/docouture-release.yml out of the box. Converts an SSH
100
+ // remote (`git@host:owner/repo.git`) to its https equivalent the same way
101
+ // an https remote is just stripped of its trailing `.git`; anything else
102
+ // unparseable is treated the same as "no remote yet".
103
+ function repoWebUrl(dir) {
104
+ const remote = originRemoteUrl(dir);
105
+ if (remote === undefined)
106
+ return undefined;
107
+ const ssh = /^git@([^:]+):(.+?)(\.git)?$/.exec(remote);
108
+ if (ssh)
109
+ return `https://${ssh[1]}/${ssh[2]}`;
110
+ if (/^https?:\/\//.test(remote))
111
+ return remote.replace(/\.git$/, '');
112
+ return undefined;
113
+ }
114
+ // The GitHub Pages project-site URL 'docouture publish gh-pages' will produce
115
+ // once published — GitHub's own convention is
116
+ // https://<owner>.github.io/<repo>/, derived here from origin's remote
117
+ // rather than asked for, so printNextSteps can tell a first-time user what
118
+ // to expect before they've published anything. Only predictable when
119
+ // origin is actually hosted on github.com (not an enterprise host, and not
120
+ // "no remote configured yet"), same restriction repoWebUrl operates under.
121
+ // The owner is lower-cased: GitHub Pages hostnames are always served
122
+ // lower-case (hostnames are case-insensitive, and GitHub canonicalizes to
123
+ // lower-case) regardless of the org/user's actual display case — e.g.
124
+ // 'InditexTech' the GitHub org still serves from inditextech.github.io.
125
+ // The repo path segment is left exactly as the remote spells it, since
126
+ // that part of the URL is case-sensitive and this is already the
127
+ // spelling the remote itself uses.
128
+ function githubPagesUrl(dir) {
129
+ const remote = originRemoteUrl(dir);
130
+ if (remote === undefined)
131
+ return undefined;
132
+ const ssh = /^git@github\.com:([^/]+)\/(.+?)(\.git)?$/.exec(remote);
133
+ if (ssh)
134
+ return `https://${ssh[1].toLowerCase()}.github.io/${ssh[2]}/`;
135
+ const https = /^https:\/\/github\.com\/([^/]+)\/(.+?)(\.git)?$/.exec(remote);
136
+ if (https)
137
+ return `https://${https[1].toLowerCase()}.github.io/${https[2]}/`;
138
+ return undefined;
139
+ }
140
+ // TemplateValues.repoIgnoreGlob is always present as its own array element
141
+ // in the scaffolded package.json (see copy-template.ts's own comment on why
142
+ // that array can't ever shrink an element away) — when there's no `origin`
143
+ // remote yet to derive a real glob from, this sentinel fills the same slot
144
+ // instead of an empty string. It has no `*`/`?`, so globToRegExp
145
+ // (check-links.mjs) compiles it to a literal-substring match that will
146
+ // never occur inside a real URL, rather than an empty pattern (which would
147
+ // match — and silently ignore — every single link).
148
+ const NO_REPO_REMOTE_GLOB = 'docouture-new:no-origin-remote-configured';
149
+ function repoIgnoreGlob(dir) {
150
+ const url = repoWebUrl(dir);
151
+ return url !== undefined ? `${url}*` : NO_REPO_REMOTE_GLOB;
152
+ }
153
+ function defaultIO() {
154
+ return {
155
+ input: process.stdin,
156
+ output: process.stdout,
157
+ isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
158
+ };
159
+ }
160
+ // @inquirer/prompts pipes its own internal stream into whatever output it's
161
+ // given and calls .end() on ours when each individual prompt finishes —
162
+ // harmless against process.stdout (Node refuses to let that be ended,
163
+ // which is why chaining prompts against a real terminal works fine) but
164
+ // fatal against a plain custom stream like a test's PassThrough: it would
165
+ // go dark after the very first of promptWizard's three sequential prompts.
166
+ // Wrapping every method through except a no-op .end() makes any writable
167
+ // stream survive being reused across multiple prompts, the same as stdout
168
+ // already does.
169
+ function keepOutputOpen(output) {
170
+ return new Proxy(output, {
171
+ get(target, prop, receiver) {
172
+ if (prop === 'end') {
173
+ return (...args) => {
174
+ const cb = typeof args[args.length - 1] === 'function' ? args.pop() : undefined;
175
+ cb?.();
176
+ return receiver;
177
+ };
178
+ }
179
+ const value = Reflect.get(target, prop, target);
180
+ return typeof value === 'function' ? value.bind(target) : value;
181
+ },
182
+ });
183
+ }
184
+ // Fills in only whatever `initial` didn't already supply — a flag or
185
+ // positional argument always wins over a prompt, so scripting one piece
186
+ // (say, --mode) while leaving the rest to the wizard works as expected.
187
+ // Each @inquirer/prompts call gets io's streams via its `context` argument
188
+ // rather than touching process.stdin/stdout directly, so tests can hand it
189
+ // a scripted stream pair instead of a real TTY — same substitution point
190
+ // the old hand-rolled readline wizard used.
191
+ async function promptWizard(io, initial, defaults) {
192
+ const context = { input: io.input, output: keepOutputOpen(io.output) };
193
+ // Each message ends with its own '\n' — @inquirer/prompts joins
194
+ // [prefix, message, ...] with plain spaces, and a message that itself
195
+ // ends in a newline pushes the typed/selected answer onto its own line
196
+ // below the question instead of trailing it inline (confirmed against
197
+ // @inquirer/input's and @inquirer/select's own render functions, which
198
+ // both just re-join on every keystroke — a literal '\n' inside the
199
+ // message survives that untouched).
200
+ const name = (await input({
201
+ message: 'Site slug (lowercase, hyphenated — names package.json and, unless opted in below, the Antora component too):\n',
202
+ default: initial.name,
203
+ validate: (value) => NAME_PATTERN.test(value.trim()) || "expected lowercase letters, digits and hyphens, e.g. 'my-project-docs'",
204
+ }, context)).trim();
205
+ const defaultTitle = titleCase(name);
206
+ const title = (await input({
207
+ message: 'Site title (shown in the page title and the nav header):\n',
208
+ default: initial.title ?? defaultTitle,
209
+ }, context)).trim();
210
+ // Off by default: GitHub Pages project sites already publish under
211
+ // https://<org>.github.io/<repo>/, and Antora adds a second /<name>/
212
+ // segment per documentation component on top of that — most repos
213
+ // scaffold exactly one site/component, so that second segment is pure
214
+ // depth most users don't want. Answering yes here keeps the historical
215
+ // behaviour (docs/antora.yml's `name` is the site name, a real URL
216
+ // segment); answering no (the default) sets it to Antora's reserved
217
+ // `ROOT` component name instead, which Antora special-cases to
218
+ // contribute no segment at all — see the docs-site-package skill's
219
+ // reference/playbook.md for the mechanism. Either way the site title
220
+ // (already asked above) is unaffected — it's a separate, always-present
221
+ // value.
222
+ const urlSegment = initial.urlSegment ??
223
+ (await confirm({
224
+ message: 'Add the site name as an extra URL path segment when published?\n' +
225
+ 'GitHub Pages project sites already publish under https://<org>.github.io/<repo>/ — ' +
226
+ 'Antora would add a further /<name>/ segment per documentation component on top of ' +
227
+ 'that. Off by default; turn it on only if this site will host more than one component later.\n',
228
+ default: false,
229
+ }, context));
230
+ const mode = initial.mode ??
231
+ (await select({
232
+ message: 'Versioning mode:\n',
233
+ default: 'standalone',
234
+ choices: [
235
+ {
236
+ name: 'Standalone (Stable + Prerelease)',
237
+ value: 'standalone',
238
+ description: "main always builds as the prerelease/preview version; a release moves a rolling 'stable' tag — no historical archive kept.",
239
+ },
240
+ {
241
+ name: 'Versioned (Full History)',
242
+ value: 'versioned',
243
+ description: 'main always builds as the prerelease/preview version; every release is an immutable vX.Y.Z git tag.',
244
+ },
245
+ ],
246
+ }, context));
247
+ const pm = initial.pm ??
248
+ (await select({
249
+ message: 'Package manager:\n',
250
+ default: defaults.pm,
251
+ choices: [
252
+ { name: 'npm', value: 'npm' },
253
+ { name: 'pnpm', value: 'pnpm' },
254
+ ],
255
+ }, context));
256
+ return { name, title: title.length > 0 ? title : defaultTitle, urlSegment, mode, pm };
257
+ }
258
+ export async function runNew(argv, io = defaultIO()) {
259
+ const { positional, flags } = parseArgs(argv);
260
+ let name = positional[0];
261
+ let title = typeof flags.title === 'string' ? flags.title : undefined;
262
+ // Boolean-only flag (no --no-url-segment counterpart): the default is
263
+ // already "off", so the only thing worth scripting is turning it on.
264
+ let urlSegment = flags['url-segment'] === true ? true : undefined;
265
+ let mode;
266
+ let pmChoice;
267
+ if (typeof flags.mode === 'string') {
268
+ if (!MODES.includes(flags.mode)) {
269
+ console.error(`invalid --mode: '${flags.mode}' — expected 'standalone' or 'versioned'`);
270
+ return 1;
271
+ }
272
+ mode = flags.mode;
273
+ }
274
+ if (typeof flags.pm === 'string') {
275
+ if (!PACKAGE_MANAGERS.includes(flags.pm)) {
276
+ console.error(`invalid --pm: '${flags.pm}' — expected 'npm' or 'pnpm'`);
277
+ return 1;
278
+ }
279
+ pmChoice = flags.pm;
280
+ }
281
+ // Best-effort guess for the wizard's own default (an existing
282
+ // packageManager field/lockfile at --dir/cwd, or how docouture itself was
283
+ // invoked) — see lib/detect-package-manager.ts. Computed against
284
+ // --dir/cwd directly rather than the eventual repo root (not resolved
285
+ // until after the wizard runs, see below): a reasonable guess either way,
286
+ // and the user can always override it in the prompt or with --pm.
287
+ const pmGuess = detectPackageManager(typeof flags.dir === 'string' ? resolve(flags.dir) : resolve(process.cwd()));
288
+ // Wizard runs only in an interactive terminal, and only when not
289
+ // explicitly skipped with --yes — a script or CI pipe (io.isTTY false)
290
+ // gets exactly today's non-interactive behaviour, defaults and all.
291
+ const skipWizard = flags.yes === true || !io.isTTY;
292
+ if (!skipWizard) {
293
+ const answers = await promptWizard(io, { name, title, urlSegment, mode, pm: pmChoice }, { pm: pmGuess });
294
+ name = answers.name;
295
+ title = answers.title;
296
+ urlSegment = answers.urlSegment;
297
+ mode = answers.mode;
298
+ pmChoice = answers.pm;
299
+ io.output.write('\n');
300
+ }
301
+ mode = mode ?? 'standalone';
302
+ pmChoice = pmChoice ?? pmGuess;
303
+ urlSegment = urlSegment ?? false;
304
+ if (!name) {
305
+ console.error('usage: docouture new <name> [--dir <path>] [--title <title>] [--url-segment] [--mode standalone|versioned] [--pm npm|pnpm]');
306
+ return 1;
307
+ }
308
+ if (!NAME_PATTERN.test(name)) {
309
+ console.error(`invalid name: '${name}'`);
310
+ console.error('expected lowercase letters, digits and hyphens, e.g. my-project-docs');
311
+ return 1;
312
+ }
313
+ title = title ?? titleCase(name);
314
+ // The target is always an EXISTING repository's root, not a fresh
315
+ // directory `docouture new` creates. --dir/cwd can be anywhere inside that
316
+ // repository — findRepoRoot walks up to the actual top-level, same as
317
+ // every other command that operates on a whole repo (dev/build/doctor/
318
+ // eject/teardown/publish) — rather than requiring --dir/cwd to already
319
+ // be the root itself.
320
+ const startDir = typeof flags.dir === 'string' ? resolve(flags.dir) : resolve(process.cwd());
321
+ if (!isInsideGitWorkTree(startDir)) {
322
+ console.error(`'${startDir}' is not inside a git repository`);
323
+ console.error('docouture new scaffolds into an existing repository — run it from your repo root (after git init), or pass --dir <path> to one');
324
+ return 1;
325
+ }
326
+ const target = await findRepoRoot(startDir);
327
+ const docsDir = join(target, 'docs');
328
+ const workflowsDir = join(target, '.github', 'workflows');
329
+ const agentsMdFile = join(target, AGENTS_MD_FILENAME);
330
+ if ((await exists(docsDir)) && !(await isEmptyOrMissing(docsDir))) {
331
+ console.error(`'${docsDir}' already exists and is not empty`);
332
+ return 1;
333
+ }
334
+ const existingWorkflows = [];
335
+ for (const workflowName of WORKFLOW_NAMES) {
336
+ if (await exists(join(workflowsDir, workflowName))) {
337
+ existingWorkflows.push(join('.github', 'workflows', workflowName));
338
+ }
339
+ }
340
+ const existingSkills = [];
341
+ for (const relativePath of AGENT_SUPPORT_PATHS) {
342
+ const absolutePath = join(target, relativePath);
343
+ if ((await exists(absolutePath)) && !(await isEmptyOrMissing(absolutePath)))
344
+ existingSkills.push(relativePath);
345
+ }
346
+ // AGENTS.md is never an all-or-nothing conflict the way a workflow file or
347
+ // a skill directory is: a foreign/human-written file (no docouture-managed
348
+ // block yet — see lib/agents-md.ts) is always safe to append docouture' own
349
+ // section to without asking. Only a file that already HAS a managed
350
+ // block counts as something this run would actually overwrite.
351
+ let existingAgentsMd;
352
+ let agentsMdConflict = false;
353
+ if (await exists(agentsMdFile)) {
354
+ existingAgentsMd = await readFile(agentsMdFile, 'utf8');
355
+ agentsMdConflict = hasManagedSection(existingAgentsMd);
356
+ }
357
+ const conflicts = [...existingWorkflows, ...existingSkills, ...(agentsMdConflict ? [AGENTS_MD_FILENAME] : [])];
358
+ if (conflicts.length > 0) {
359
+ if (skipWizard) {
360
+ // Non-interactive (--yes, or no TTY) — nobody to confirm with, so this
361
+ // stays a hard refusal, same as every other pre-flight check above,
362
+ // rather than silently overwriting something already there.
363
+ console.error(`refusing to overwrite existing file(s)/dir(s) under '${target}':`);
364
+ console.error(` ${conflicts.join(', ')}`);
365
+ console.error("run 'docouture upgrade' instead if you want to re-sync them");
366
+ return 1;
367
+ }
368
+ console.log(theme.bold('Already exist and would be overwritten:'));
369
+ for (const path of conflicts)
370
+ console.log(` ${path}`);
371
+ console.log('');
372
+ const proceed = await confirm({ message: 'Overwrite them?', default: false }, { input: io.input, output: keepOutputOpen(io.output) });
373
+ if (!proceed) {
374
+ console.log('aborted — nothing written');
375
+ return 1;
376
+ }
377
+ io.output.write('\n');
378
+ }
379
+ // build/commands/new.js -> build/templates/{starter,workflows,agent-support}
380
+ // — see scripts/copy-templates.mjs, which puts the templates/ directory
381
+ // here at build time. Resolved from import.meta.url so this works
382
+ // regardless of the directory docouture is invoked from.
383
+ const here = dirname(fileURLToPath(import.meta.url));
384
+ const templatesRoot = join(here, '..', 'templates');
385
+ const starterDir = join(templatesRoot, 'starter');
386
+ const workflowsTemplateDir = join(templatesRoot, 'workflows');
387
+ const agentSupportDir = join(templatesRoot, 'agent-support');
388
+ // build/commands/new.js -> build/ -> package root, 2 levels up — see
389
+ // readCliInfo's own comment. This is the exact version a scaffolded
390
+ // site's devDependency on @inditextech/docouture-cli gets pinned to below, so
391
+ // it always matches whatever CLI actually generated it, snapshot/local
392
+ // releases included.
393
+ const { version: cliVersion } = await readCliInfo(import.meta.url, 2);
394
+ // The user's own choice (--pm, wizard answer, or the auto-guess computed
395
+ // above if neither was given) — never re-detected against `target`, so
396
+ // whatever was actually chosen/confirmed is what the workflows and
397
+ // printed next-steps agree on.
398
+ const pm = packageManagerPlan(pmChoice);
399
+ const values = {
400
+ name,
401
+ title,
402
+ // Antora's own component `name` — deliberately a separate value from
403
+ // the site name above once `urlSegment` is false (the default):
404
+ // `ROOT` is Antora's reserved component name, special-cased to
405
+ // contribute no segment to a page's published URL at all (see
406
+ // how-antora-builds-urls's "Component segment" section) — which is
407
+ // exactly what dropping the extra GitHub Pages path segment requires.
408
+ // package.json's own `name` stays `name` above either way; `docouture
409
+ // doctor`'s checkNamesAgree knows to skip the package-name-matches-
410
+ // component-name check when this is the literal 'ROOT'.
411
+ componentName: urlSegment ? name : 'ROOT',
412
+ cliVersion,
413
+ pmName: pm.pm,
414
+ pmCacheName: pm.cacheName,
415
+ pmLockfile: pm.lockfile,
416
+ pmCiCmd: pm.ciCmd,
417
+ pmSetupStepYaml: pm.setupStepYaml,
418
+ repoIgnoreGlob: repoIgnoreGlob(target),
419
+ };
420
+ // The whole starter subtree — package.json, antora-playbook.yml, its own
421
+ // nested src/antora.yml — lands under <repo-root>/docs/ as one piece,
422
+ // unchanged in shape. Only .github/workflows/ is peeled out to a second
423
+ // copy at the true repo root, since GitHub Actions never discovers
424
+ // workflows anywhere else.
425
+ await copyTemplate(starterDir, docsDir, values);
426
+ await copyTemplate(workflowsTemplateDir, workflowsDir, values);
427
+ // AGENTS.md and both platforms' skill directories also land at the true
428
+ // repo root, same as workflows — an agent reads them from there, not from
429
+ // inside docs/. copyTemplate's own VERSIONED_MARKER/SKIP_FILENAMES skips
430
+ // (see copy-template.ts) leave docs-versioning.versioned and AGENTS.md
431
+ // untouched here; docs-versioning is copied under its real name below,
432
+ // only in versioned mode, and AGENTS.md is merged separately next — see
433
+ // lib/agents-md.ts for why it can't go through the generic overwrite walk
434
+ // every other file here does.
435
+ await copyTemplate(agentSupportDir, target, values);
436
+ const renderedAgentsMd = await renderTemplateFile(join(agentSupportDir, AGENTS_MD_FILENAME), values);
437
+ await writeFile(agentsMdFile, mergeAgentsMd(existingAgentsMd, renderedAgentsMd), 'utf8');
438
+ if (mode === 'versioned') {
439
+ await writeTemplateFile(join(starterDir, 'antora-playbook.versioned.yml'), join(docsDir, 'antora-playbook.yml'), values);
440
+ await writeTemplateFile(join(starterDir, 'src', 'release-version.versioned'), join(docsDir, '.release-version'), values);
441
+ for (const platform of ['.opencode', '.claude']) {
442
+ await copyTemplate(join(agentSupportDir, platform, 'skills', 'docs-versioning.versioned'), join(target, platform, 'skills', 'docs-versioning'), values);
443
+ }
444
+ }
445
+ printNextSteps({ mode, pm, target, docsDir, workflowsDir, ghPagesUrl: githubPagesUrl(target) });
446
+ return 0;
447
+ }
448
+ // Everything printed after scaffolding finishes, grouped into labeled
449
+ // sections so it reads as a short runbook rather than a flat log of
450
+ // "created X" lines. Headers use the shared theme's bold, matching bin.ts's
451
+ // own banner — see lib/theme.ts for why that's not picocolors directly.
452
+ function printNextSteps(args) {
453
+ const { mode, pm, target, docsDir, workflowsDir, ghPagesUrl } = args;
454
+ const created = (path) => ` ${theme.success('✓')} ${path}`;
455
+ // Relative to `target` (the repo root), not process.cwd() — cwd may be a
456
+ // nested subdirectory findRepoRoot walked up from (see runNew), in which
457
+ // case relative(cwd, ...) produces a useless, alarming-looking chain of
458
+ // '../../..' for paths that are actually just 'docs', '.github/workflows'
459
+ // etc. at the repo root.
460
+ const writtenAt = (path) => relative(target, path) || '.';
461
+ // 'Written' rather than 'Created': a conflict confirmed above (see
462
+ // runNew) means some of these were overwritten or merged into, not
463
+ // created fresh — this header stays accurate either way.
464
+ console.log(theme.bold('Written:'));
465
+ console.log(created(writtenAt(docsDir)));
466
+ console.log(created(writtenAt(workflowsDir)));
467
+ console.log(created(writtenAt(join(target, 'AGENTS.md'))));
468
+ console.log(created(writtenAt(join(target, '.opencode', 'skills'))));
469
+ console.log(created(writtenAt(join(target, '.claude', 'skills'))));
470
+ console.log('');
471
+ console.log(theme.bold('Next steps:'));
472
+ console.log(' cd docs');
473
+ console.log(` ${pm.installCmd}`);
474
+ console.log(` ${pm.devCmd}`);
475
+ console.log('');
476
+ if (mode === 'versioned') {
477
+ console.log(theme.bold('Versioning: Versioned (Full History)'));
478
+ console.log(' main is the prerelease channel.');
479
+ console.log('');
480
+ console.log(' To cut your first release:');
481
+ console.log(" 1. Create the 'docs/release' label (once): gh label create docs/release");
482
+ console.log(' 2. Open a PR that sets the target version in docs/.release-version (e.g. "1.0.0")');
483
+ console.log(" 3. Label the PR 'docs/release'");
484
+ console.log(' 4. Merge it — docouture-release.yml runs automatically and tags vX.Y.Z');
485
+ console.log('');
486
+ console.log(' (or skip the label/PR entirely: run docouture-release.yml manually via workflow_dispatch');
487
+ console.log(' and type the version)');
488
+ console.log('');
489
+ console.log(' See the docs-versioning skill (.opencode/skills, .claude/skills) for the full mechanism.');
490
+ }
491
+ else {
492
+ console.log(theme.bold('Versioning: Standalone (Stable + Prerelease)'));
493
+ console.log(' main is the prerelease channel.');
494
+ console.log('');
495
+ console.log(' To cut your first stable release:');
496
+ console.log(" 1. Create the 'docs/release' label (once): gh label create docs/release");
497
+ console.log(" 2. Merge any PR labeled 'docs/release' into main — docouture-release.yml runs automatically");
498
+ console.log('');
499
+ console.log(' (or skip the label/PR entirely: run docouture-release.yml manually via workflow_dispatch,');
500
+ console.log(' default input is fine)');
501
+ }
502
+ console.log('');
503
+ console.log(theme.bold('Before your first publish:'));
504
+ console.log(' See docs/src/modules/main/pages/prerequisites.adoc for what a public GitHub Pages site');
505
+ console.log(' needs before its first publish.');
506
+ if (ghPagesUrl !== undefined) {
507
+ console.log('');
508
+ console.log(' Once you cut your first release, the site publishes automatically to the');
509
+ console.log(` \`gh-pages\` branch — once public, it will be live at ${ghPagesUrl}`);
510
+ }
511
+ }
512
+ //# sourceMappingURL=new.js.map