@politty/zod 0.1.2 → 0.2.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 (59) hide show
  1. package/README.md +31 -0
  2. package/dist/{arg-registry-Be4GJADw.d.ts → arg-registry-BzQ-y-e0.d.ts} +2 -2
  3. package/dist/augment.d.ts +1 -1
  4. package/dist/augment.js +1 -1
  5. package/dist/cli-main-B4FX3h4J.js +1 -0
  6. package/dist/cli-main-CjYk6tAX.js +2 -0
  7. package/dist/cli-run-DN55A0JZ.js +1 -0
  8. package/dist/cli.js +1 -15
  9. package/dist/command-Mbdt0bmN.js +1 -0
  10. package/dist/compile-cache-VuUbFWIo.js +1 -0
  11. package/dist/compile-cache.js +1 -3
  12. package/dist/completion-D8DMrPqg.js +116 -0
  13. package/dist/completion-DUMcvXkT.js +1 -0
  14. package/dist/completion.d.ts +87 -7
  15. package/dist/completion.js +1 -4
  16. package/dist/docs.d.ts +1 -1
  17. package/dist/docs.js +84 -3063
  18. package/dist/dynamic-CMtee4tD.js +1 -0
  19. package/dist/dynamic-CrnlveHj.js +6 -0
  20. package/dist/field-meta-COGya7xp.js +1 -0
  21. package/dist/index.d.ts +3 -3
  22. package/dist/index.js +1 -27
  23. package/dist/log-collector-DICKib75.js +1 -0
  24. package/dist/logger-CJsyJ8sb.js +1 -0
  25. package/dist/prompt-CqmGq1_N.js +1 -0
  26. package/dist/prompt-clack.d.ts +1 -1
  27. package/dist/prompt-clack.js +1 -32
  28. package/dist/prompt-inquirer.d.ts +1 -1
  29. package/dist/prompt-inquirer.js +1 -47
  30. package/dist/prompt.d.ts +1 -1
  31. package/dist/prompt.js +1 -4
  32. package/dist/register-C1WbbeYH.js +1 -0
  33. package/dist/runner-BIdcntiz.js +29 -0
  34. package/dist/runner-Chaf_5lK.js +1 -0
  35. package/dist/schema-BiUP_KyV.js +1 -0
  36. package/dist/schema-extractor-DU0Vuhbd.js +1 -0
  37. package/dist/skill.d.ts +1 -1
  38. package/dist/skill.js +2 -1837
  39. package/dist/subcommand-router-D8GTMXYL.js +1 -0
  40. package/dist/{index-B4NylQOW.d.ts → with-completion-command-CmVdHFHX.d.ts} +3 -74
  41. package/dist/with-completion-command-DDohm8U6.js +3 -0
  42. package/package.json +3 -3
  43. package/dist/cli-main-CAa5KwCo.js +0 -314
  44. package/dist/cli-main-DIRMduj1.js +0 -3
  45. package/dist/cli-run-B--tZW_I.js +0 -8
  46. package/dist/command-k-4yAz4J.js +0 -42
  47. package/dist/compile-cache-BC65o7MH.js +0 -103
  48. package/dist/completion-Ot2qpNNw.js +0 -5616
  49. package/dist/field-meta-CO38jOvl.js +0 -148
  50. package/dist/log-collector-CoUkLVJB.js +0 -114
  51. package/dist/logger-i_bb-Jhc.js +0 -133
  52. package/dist/prompt-BjIZThsH.js +0 -169
  53. package/dist/register-DdcsbNwM.js +0 -440
  54. package/dist/runner-Bz3arOqh.js +0 -3051
  55. package/dist/runner-Cs-Mz9JN.js +0 -3
  56. package/dist/schema-extractor-CVNDs07l.js +0 -250
  57. package/dist/src-D4QGGgW1.js +0 -6
  58. package/dist/src-DhQVU2hG.js +0 -12
  59. package/dist/subcommand-router-Cskpofdk.js +0 -134
package/dist/docs.js CHANGED
@@ -1,3073 +1,94 @@
1
- import { a as getValidatorAdapter } from "./field-meta-CO38jOvl.js";
2
- import "./register-DdcsbNwM.js";
3
- import { n as getExtractedFields, t as extractFields } from "./schema-extractor-CVNDs07l.js";
4
- import { t as createLogCollector } from "./log-collector-CoUkLVJB.js";
5
- import { r as resolveLazyCommand } from "./subcommand-router-Cskpofdk.js";
6
- import * as fs from "node:fs";
7
- import * as path$1 from "node:path";
8
- import path from "node:path";
9
- import { isDeepStrictEqual } from "node:util";
1
+ import{a as e}from"./field-meta-COGya7xp.js";import"./register-C1WbbeYH.js";import{n as t,t as n}from"./schema-extractor-DU0Vuhbd.js";import{t as r}from"./log-collector-DICKib75.js";import{r as i}from"./subcommand-router-D8GTMXYL.js";import*as a from"node:fs";import*as o from"node:path";import s from"node:path";import{isDeepStrictEqual as c}from"node:util";const l=[`option`,`alias`,`description`,`required`,`default`];function u(e){return`(↔ \`--${e.cliName}\`)`}function d(e){return e.placeholder??e.cliName.toUpperCase().replace(/-/g,`_`)}function f(e){let t=e.type===`boolean`?`--${e.cliName}`:`--${e.cliName} <${d(e)}>`,n=[];if(e.alias)for(let t of e.alias)n.push(t.length===1?`-${t}`:`--${t}`);let r=e.type===`boolean`&&!!e.negationDisplay,i=r&&!e.negationDescription?`--${e.negationDisplay}`:void 0,a=r&&e.negationDescription?{flag:`--${e.negationDisplay}`,description:e.negationDescription,relationMarker:u(e),required:e.required}:void 0;return{longFlag:t,aliases:n,inlineNegation:i,description:e.description,required:e.required,hasDefault:e.defaultValue!==void 0,defaultValue:e.defaultValue,env:e.env,negationRow:a}}function p(e){return e.map(f)}function m(e){return e.replace(/\r?\n/g,`<br>`)}function h(e){return m(e.replace(/\|/g,`\\|`))}function g(e){return`\`${e}\``}function _(e){return e.hasDefault?`\`${JSON.stringify(e.defaultValue)}\``:`-`}function v(e){return e?Array.isArray(e)?e.map(e=>`\`${e}\``).join(`, `):`\`${e}\``:`-`}function y(e){return e?` [env: ${(Array.isArray(e)?e:[e]).join(`, `)}]`:``}const b={option:{header:`Option`,separator:`--------`},alias:{header:`Alias`,separator:`-------`},description:{header:`Description`,separator:`-------------`},required:{header:`Required`,separator:`----------`},default:{header:`Default`,separator:`---------`},env:{header:`Env`,separator:`-----`}};function x(e,t){switch(t){case`option`:{let t=g(e.longFlag);return e.inlineNegation?`${t} / ${g(e.inlineNegation)}`:t}case`alias`:return e.aliases.length>0?e.aliases.map(g).join(`, `):`-`;case`description`:return h(e.description??``);case`required`:return e.required?`Yes`:`No`;case`default`:return _(e);case`env`:return v(e.env)}}function S(e,t){switch(t){case`option`:return g(e.flag);case`description`:return`${h(e.description)} ${e.relationMarker}`;case`required`:return e.required?`Yes`:`No`;case`alias`:case`default`:case`env`:return`-`}}function C(e,t){if(e.length===0)return``;let n=t??(e.some(e=>e.env)?[...l,`env`]:l),r=[];r.push(`| ${n.map(e=>b[e].header).join(` | `)} |`),r.push(`|${n.map(e=>b[e].separator).join(`|`)}|`);for(let t of e)r.push(`| ${n.map(e=>x(t,e)).join(` | `)} |`),t.negationRow&&r.push(`| ${n.map(e=>S(t.negationRow,e)).join(` | `)} |`);return r.join(`
2
+ `)}function w(e){if(e.length===0)return``;let t=[];for(let n of e){let e=n.aliases.filter(e=>!e.startsWith(`--`)),r=n.aliases.filter(e=>e.startsWith(`--`)),i=[...e,n.longFlag,...r].map(g).join(`, `);n.inlineNegation&&(i+=` / ${g(n.inlineNegation)}`);let a=n.description?` - ${m(n.description)}`:``,o=n.required?` (required)`:``,s=n.hasDefault?` (default: ${JSON.stringify(n.defaultValue)})`:``,c=y(n.env);t.push(`- ${i}${a}${o}${s}${c}`),n.negationRow&&t.push(`- ${g(n.negationRow.flag)} - ${m(n.negationRow.description)} ${n.negationRow.relationMarker}`)}return t.join(`
3
+ `)}const T=`POLITTY_DOCS_UPDATE`,E=`POLITTY_DOCS_DOCTOR`,D=[`heading`,`description`,`usage`,`arguments`,`options`,`global-options-link`,`subcommands`,`examples`,`notes`],O=`politty:command`;function k(e,t){return`<!-- ${O}:${t}:${e}:start -->`}function A(e,t){return`<!-- ${O}:${t}:${e}:end -->`}const j=`politty:global-options`;function M(){return`<!-- ${j}:start -->`}function N(){return`<!-- ${j}:end -->`}const P=`politty:root-header`;function ee(){return`<!-- ${P}:start -->`}function te(){return`<!-- ${P}:end -->`}const F=`politty:root-footer`;function ne(){return`<!-- ${F}:start -->`}function re(){return`<!-- ${F}:end -->`}const I=`politty:index`;function L(e){return`<!-- ${I}:${e}:start -->`}function ie(e){return`<!-- ${I}:${e}:end -->`}function R(e){return m(e.replace(/\|/g,`\\|`))}function z(e){let t=[e.fullCommandPath];e.options.length>0&&t.push(`[options]`),e.subCommands.length>0&&(e.command.run?t.push(`[command]`):t.push(`<command>`));for(let n of e.positionalArgs)n.required?t.push(`<${n.name}>`):t.push(`[${n.name}]`);return t.join(` `)}function B(e){if(e.positionalArgs.length===0)return``;let t=[];t.push(`| Argument | Description | Required |`),t.push(`|----------|-------------|----------|`);for(let n of e.positionalArgs){let e=R(n.description??``),r=n.required?`Yes`:`No`;t.push(`| \`${n.name}\` | ${e} | ${r} |`)}return t.join(`
4
+ `)}function V(e){if(e.positionalArgs.length===0)return``;let t=[];for(let n of e.positionalArgs){let e=n.required?`(required)`:`(optional)`,r=n.description?` - ${m(n.description)}`:``;t.push(`- \`${n.name}\`${r} ${e}`)}return t.join(`
5
+ `)}function ae(e){return C(p(e.options))}function oe(e){return w(p(e.options))}function se(e){return e.join(`-`).toLowerCase()}function ce(e,t){let n=e.replace(/\\/g,`/`),r=t.replace(/\\/g,`/`);return s.posix.relative(s.posix.dirname(n),r)}function le(e,t=!0){return me(e.subCommands,e,t)}function H(e){return C(p(e))}function ue(e,t=`table`){let n=e.unionOptions??[];if(n.length===0)return``;let r=[],i=new Set;for(let e of n)for(let t of e.fields)i.add(t.name);let a=new Set;for(let e of i)n.every(t=>t.fields.some(t=>t.name===e))&&a.add(e);let o=e.fields.filter(e=>a.has(e.name)&&!e.positional);o.length>0&&r.push(t===`table`?H(o):U(o)),r.push(`> One of the following option groups is required:`);for(let e=0;e<n.length;e++){let i=n[e];if(!i)continue;let o=i.fields.filter(e=>!a.has(e.name)&&!e.positional),s=i.description??`Variant ${e+1}`;if(o.length===0){r.push(`**${s}:**\n\n_no options_`);continue}let c=t===`table`?H(o):U(o);r.push(`**${s}:**\n\n${c}`)}return r.join(`
10
6
 
11
- //#region ../core/src/docs/option-rows.ts
12
- /** Canonical column order used when no explicit column subset is requested. */
13
- const DEFAULT_COLUMNS = [
14
- "option",
15
- "alias",
16
- "description",
17
- "required",
18
- "default"
19
- ];
20
- /**
21
- * Marker appended to a custom negation row/line so readers can see which
22
- * positive flag it negates (e.g. `--monochrome` → ``(↔ `--color`)``).
23
- */
24
- function negationRelationMarker(opt) {
25
- return `(↔ \`--${opt.cliName}\`)`;
26
- }
27
- /**
28
- * Resolve placeholder for an option (uses kebab-case cliName).
29
- */
30
- function resolvePlaceholder(opt) {
31
- return opt.placeholder ?? opt.cliName.toUpperCase().replace(/-/g, "_");
32
- }
33
- /**
34
- * Normalize a single {@link ResolvedFieldMeta} into an {@link OptionRow}.
35
- */
36
- function toOptionRow(opt) {
37
- const longFlag = opt.type === "boolean" ? `--${opt.cliName}` : `--${opt.cliName} <${resolvePlaceholder(opt)}>`;
38
- const aliases = [];
39
- if (opt.alias) for (const a of opt.alias) aliases.push(a.length === 1 ? `-${a}` : `--${a}`);
40
- const hasNegationDisplay = opt.type === "boolean" && !!opt.negationDisplay;
41
- const inlineNegation = hasNegationDisplay && !opt.negationDescription ? `--${opt.negationDisplay}` : void 0;
42
- const negationRow = hasNegationDisplay && opt.negationDescription ? {
43
- flag: `--${opt.negationDisplay}`,
44
- description: opt.negationDescription,
45
- relationMarker: negationRelationMarker(opt),
46
- required: opt.required
47
- } : void 0;
48
- return {
49
- longFlag,
50
- aliases,
51
- inlineNegation,
52
- description: opt.description,
53
- required: opt.required,
54
- hasDefault: opt.defaultValue !== void 0,
55
- defaultValue: opt.defaultValue,
56
- env: opt.env,
57
- negationRow
58
- };
59
- }
60
- /**
61
- * Build the normalized intermediate representation for a list of options.
62
- * This is the single source of truth for per-option display decisions; the
63
- * emitters below ({@link emitMarkdownTable}, {@link emitMarkdownList}) are pure
64
- * formatting.
65
- */
66
- function toOptionRows(options) {
67
- return options.map(toOptionRow);
68
- }
69
- /**
70
- * Convert hard line breaks in a description into `<br>` so they render as line
71
- * breaks inside a single markdown table cell or list item, instead of breaking
72
- * the surrounding table row / list structure.
73
- */
74
- function inlineMarkdownBreaks(str) {
75
- return str.replace(/\r?\n/g, "<br>");
76
- }
77
- /**
78
- * Escape markdown special characters in table cells. Embedded line breaks are
79
- * converted to `<br>` so multi-line descriptions stay within a single cell.
80
- */
81
- function escapeTableCell$2(str) {
82
- return inlineMarkdownBreaks(str.replace(/\|/g, "\\|"));
83
- }
84
- function backtick(value) {
85
- return `\`${value}\``;
86
- }
87
- /**
88
- * Format default value for table display.
89
- */
90
- function formatDefaultValue(row) {
91
- if (!row.hasDefault) return "-";
92
- return `\`${JSON.stringify(row.defaultValue)}\``;
93
- }
94
- /**
95
- * Format env variable names for a markdown table cell.
96
- */
97
- function formatEnvNames(env) {
98
- if (!env) return "-";
99
- if (Array.isArray(env)) return env.map((e) => `\`${e}\``).join(", ");
100
- return `\`${env}\``;
101
- }
102
- /**
103
- * Format env variable info for a markdown list item (e.g. `[env: PORT, SERVER_PORT]`).
104
- */
105
- function formatEnvInfo(env) {
106
- if (!env) return "";
107
- return ` [env: ${(Array.isArray(env) ? env : [env]).join(", ")}]`;
108
- }
109
- /** Header label + separator dashes for each column, matching legacy widths. */
110
- const COLUMN_META = {
111
- option: {
112
- header: "Option",
113
- separator: "--------"
114
- },
115
- alias: {
116
- header: "Alias",
117
- separator: "-------"
118
- },
119
- description: {
120
- header: "Description",
121
- separator: "-------------"
122
- },
123
- required: {
124
- header: "Required",
125
- separator: "----------"
126
- },
127
- default: {
128
- header: "Default",
129
- separator: "---------"
130
- },
131
- env: {
132
- header: "Env",
133
- separator: "-----"
134
- }
135
- };
136
- /**
137
- * Render the table cell for a base option row in the given column.
138
- */
139
- function tableCell(row, col) {
140
- switch (col) {
141
- case "option": {
142
- const name = backtick(row.longFlag);
143
- return row.inlineNegation ? `${name} / ${backtick(row.inlineNegation)}` : name;
144
- }
145
- case "alias": return row.aliases.length > 0 ? row.aliases.map(backtick).join(", ") : "-";
146
- case "description": return escapeTableCell$2(row.description ?? "");
147
- case "required": return row.required ? "Yes" : "No";
148
- case "default": return formatDefaultValue(row);
149
- case "env": return formatEnvNames(row.env);
150
- }
151
- }
152
- /**
153
- * Render the table cell for a negation row in the given column.
154
- */
155
- function negationTableCell(neg, col) {
156
- switch (col) {
157
- case "option": return backtick(neg.flag);
158
- case "description": return `${escapeTableCell$2(neg.description)} ${neg.relationMarker}`;
159
- case "required": return neg.required ? "Yes" : "No";
160
- case "alias":
161
- case "default":
162
- case "env": return "-";
163
- }
164
- }
165
- /**
166
- * Emit option rows as a markdown table.
167
- *
168
- * When `columns` is omitted, the canonical column set is used and the `Env`
169
- * column is appended automatically iff any row has env configured. When
170
- * `columns` is provided, exactly those columns are emitted, in that order.
171
- *
172
- * The separator row always uses the canonical fixed-width dashes from
173
- * {@link COLUMN_META}, so every table — default or column-filtered — shares a
174
- * single, consistent format.
175
- */
176
- function emitMarkdownTable(rows, columns) {
177
- if (rows.length === 0) return "";
178
- const cols = columns ?? (rows.some((r) => r.env) ? [...DEFAULT_COLUMNS, "env"] : DEFAULT_COLUMNS);
179
- const lines = [];
180
- lines.push(`| ${cols.map((c) => COLUMN_META[c].header).join(" | ")} |`);
181
- lines.push(`|${cols.map((c) => COLUMN_META[c].separator).join("|")}|`);
182
- for (const row of rows) {
183
- lines.push(`| ${cols.map((c) => tableCell(row, c)).join(" | ")} |`);
184
- if (row.negationRow) lines.push(`| ${cols.map((c) => negationTableCell(row.negationRow, c)).join(" | ")} |`);
185
- }
186
- return lines.join("\n");
187
- }
188
- /**
189
- * Emit option rows as a markdown list.
190
- *
191
- * Aliases are joined with `, ` (short flags first, then the long flag, then
192
- * long aliases); the inline negation is appended with ` / ` so it stays
193
- * visually distinct from aliases.
194
- */
195
- function emitMarkdownList(rows) {
196
- if (rows.length === 0) return "";
197
- const lines = [];
198
- for (const row of rows) {
199
- const shortAliases = row.aliases.filter((a) => !a.startsWith("--"));
200
- const longAliases = row.aliases.filter((a) => a.startsWith("--"));
201
- let flags = [
202
- ...shortAliases,
203
- row.longFlag,
204
- ...longAliases
205
- ].map(backtick).join(", ");
206
- if (row.inlineNegation) flags += ` / ${backtick(row.inlineNegation)}`;
207
- const desc = row.description ? ` - ${inlineMarkdownBreaks(row.description)}` : "";
208
- const required = row.required ? " (required)" : "";
209
- const defaultVal = row.hasDefault ? ` (default: ${JSON.stringify(row.defaultValue)})` : "";
210
- const envInfo = formatEnvInfo(row.env);
211
- lines.push(`- ${flags}${desc}${required}${defaultVal}${envInfo}`);
212
- if (row.negationRow) lines.push(`- ${backtick(row.negationRow.flag)} - ${inlineMarkdownBreaks(row.negationRow.description)} ${row.negationRow.relationMarker}`);
213
- }
214
- return lines.join("\n");
215
- }
7
+ `)}function de(e,t=`table`){let n=e.discriminator,r=e.variants??[];if(!n||r.length===0)return``;let i=[],a=new Set;for(let e of r)for(let t of e.fields)a.add(t.name);let o=new Set;for(let e of a)e!==n&&r.every(t=>t.fields.some(t=>t.name===e))&&o.add(e);let s=e.fields.find(e=>e.name===n),c=r.map(e=>e.discriminatorValue).join(`\\|`),l=[];s&&l.push({...s,placeholder:c});for(let t of o){let n=e.fields.find(e=>e.name===t);n&&!n.positional&&l.push(n)}l.length>0&&i.push(t===`table`?H(l):U(l));for(let e of r){let r=e.fields.filter(e=>e.name!==n&&!o.has(e.name)&&!e.positional);if(r.length===0)continue;let a=e.description?` ${e.description}`:``,s=`**When \`${n}\` = \`${e.discriminatorValue}\`:**${a}`,c=t===`table`?H(r):U(r);i.push(`${s}\n\n${c}`)}return i.join(`
216
8
 
217
- //#endregion
218
- //#region ../core/src/docs/types.ts
219
- /**
220
- * Environment variable name for update mode
221
- */
222
- const UPDATE_GOLDEN_ENV = "POLITTY_DOCS_UPDATE";
223
- /**
224
- * Environment variable name for doctor mode.
225
- * When enabled alone, detects and reports missing section markers (read-only).
226
- * When combined with POLITTY_DOCS_UPDATE=true, auto-inserts missing markers.
227
- */
228
- const DOCTOR_ENV = "POLITTY_DOCS_DOCTOR";
229
- /**
230
- * All section types in rendering order
231
- */
232
- const SECTION_TYPES = [
233
- "heading",
234
- "description",
235
- "usage",
236
- "arguments",
237
- "options",
238
- "global-options-link",
239
- "subcommands",
240
- "examples",
241
- "notes"
242
- ];
243
- /**
244
- * Marker prefix for command section markers in generated documentation
245
- * Format: <!-- politty:command:<scope>:<type>:start --> ... <!-- politty:command:<scope>:<type>:end -->
246
- */
247
- const SECTION_MARKER_PREFIX = "politty:command";
248
- /**
249
- * Generate start marker for a command section
250
- */
251
- function sectionStartMarker(type, scope) {
252
- return `<!-- ${SECTION_MARKER_PREFIX}:${scope}:${type}:start -->`;
253
- }
254
- /**
255
- * Generate end marker for a command section
256
- */
257
- function sectionEndMarker(type, scope) {
258
- return `<!-- ${SECTION_MARKER_PREFIX}:${scope}:${type}:end -->`;
259
- }
260
- /**
261
- * Marker prefix for global options sections in generated documentation
262
- * Format: <!-- politty:global-options:start --> ... <!-- politty:global-options:end -->
263
- */
264
- const GLOBAL_OPTIONS_MARKER_PREFIX = "politty:global-options";
265
- /**
266
- * Generate start marker for a global options section
267
- */
268
- function globalOptionsStartMarker() {
269
- return `<!-- ${GLOBAL_OPTIONS_MARKER_PREFIX}:start -->`;
270
- }
271
- /**
272
- * Generate end marker for a global options section
273
- */
274
- function globalOptionsEndMarker() {
275
- return `<!-- ${GLOBAL_OPTIONS_MARKER_PREFIX}:end -->`;
276
- }
277
- /**
278
- * Marker prefix for root header sections in generated documentation
279
- */
280
- const ROOT_HEADER_MARKER_PREFIX = "politty:root-header";
281
- function rootHeaderStartMarker() {
282
- return `<!-- ${ROOT_HEADER_MARKER_PREFIX}:start -->`;
283
- }
284
- function rootHeaderEndMarker() {
285
- return `<!-- ${ROOT_HEADER_MARKER_PREFIX}:end -->`;
286
- }
287
- /**
288
- * Marker prefix for root footer sections in generated documentation
289
- */
290
- const ROOT_FOOTER_MARKER_PREFIX = "politty:root-footer";
291
- function rootFooterStartMarker() {
292
- return `<!-- ${ROOT_FOOTER_MARKER_PREFIX}:start -->`;
293
- }
294
- function rootFooterEndMarker() {
295
- return `<!-- ${ROOT_FOOTER_MARKER_PREFIX}:end -->`;
296
- }
297
- /**
298
- * Marker prefix for index sections in generated documentation
299
- * Format: <!-- politty:index:<scope>:start --> ... <!-- politty:index:<scope>:end -->
300
- */
301
- const INDEX_MARKER_PREFIX = "politty:index";
302
- /**
303
- * Generate start marker for an index section
304
- */
305
- function indexStartMarker(scope) {
306
- return `<!-- ${INDEX_MARKER_PREFIX}:${scope}:start -->`;
307
- }
308
- /**
309
- * Generate end marker for an index section
310
- */
311
- function indexEndMarker(scope) {
312
- return `<!-- ${INDEX_MARKER_PREFIX}:${scope}:end -->`;
313
- }
9
+ `)}function U(e){return w(p(e))}function fe(e){if(e.length===0)return``;let t=[];t.push(`| Argument | Description | Required |`),t.push(`|----------|-------------|----------|`);for(let n of e){let e=R(n.description??``),r=n.required?`Yes`:`No`;t.push(`| \`${n.name}\` | ${e} | ${r} |`)}return t.join(`
10
+ `)}function pe(e){if(e.length===0)return``;let t=[];for(let n of e){let e=n.required?`(required)`:`(optional)`,r=n.description?` - ${m(n.description)}`:``;t.push(`- \`${n.name}\`${r} ${e}`)}return t.join(`
11
+ `)}function me(e,t,n=!0){if(e.length===0)return``;let r=e.some(e=>e.aliases&&e.aliases.length>0),i=[];r?(i.push(`| Command | Aliases | Description |`),i.push(`|---------|---------|-------------|`)):(i.push(`| Command | Description |`),i.push(`|---------|-------------|`));let a=t.filePath,o=t.fileMap;for(let t of e){let e=t.fullPath.join(` `),s=R(t.description??``),c=t.fullPath.join(` `),l=r?t.aliases&&t.aliases.length>0?t.aliases.map(e=>`\`${R(e)}\``).join(`, `):`-`:``,u;if(n){let n=se(t.fullPath),r=o!==void 0&&Object.prototype.hasOwnProperty.call(o,c),i=r?o[c]:void 0;u=a&&i&&a!==i?`[\`${e}\`](${ce(a,i)}#${n})`:o&&!r?`\`${e}\``:`[\`${e}\`](#${n})`}else u=`\`${e}\``;r?i.push(`| ${u} | ${l} | ${s} |`):i.push(`| ${u} | ${s} |`)}return i.join(`
12
+ `)}function he(e,t,n){if(e.length===0)return``;let r=n?.showOutput??!0,i=n?.commandPrefix?`${n.commandPrefix} `:``,a=[];for(let n=0;n<e.length;n++){let o=e[n];if(!o)continue;let s=t?.[n];a.push(`**${o.desc}**`),a.push(``),a.push("```bash"),a.push(`$ ${i}${o.cmd}`),r&&(s?(s.stdout&&a.push(s.stdout),s.stderr&&a.push(`[stderr] ${s.stderr}`)):o.output&&a.push(o.output)),a.push("```"),a.push(``)}for(;a.length>0&&a[a.length-1]===``;)a.pop();return a.join(`
13
+ `)}function ge(e,t,n){return`${k(e,t)}\n${n}\n${A(e,t)}`}function _e(e){return!e.hasGlobalOptions||e.commandPath===``?null:`See [Global Options](${e.rootDocPath&&e.filePath&&e.filePath!==e.rootDocPath?`${ce(e.filePath,e.rootDocPath)}#global-options`:`#global-options`}) for options available to all commands.`}function W(e={}){let{headingLevel:t=1,optionStyle:n=`table`,generateAnchors:r=!0,includeSubcommandDetails:i=!0,markerless:a=!1,renderDescription:o,renderUsage:s,renderArguments:c,renderOptions:l,renderSubcommands:u,renderNotes:d,renderFooter:f,renderExamples:p}=e,m=a?(e,t,n)=>n:ge;return e=>{let a=[],h=e.commandPath,g=Math.min(t+(e.depth-1),6),_=`#`.repeat(g),v=e.commandPath||e.name;a.push(m(`heading`,h,`${_} ${v}`));{let t=[];if(e.description&&t.push(e.description),e.aliases&&e.aliases.length>0&&t.push(`**Aliases:** ${e.aliases.map(e=>`\`${e}\``).join(`, `)}`),t.length>0){let n={content:t.join(`
314
14
 
315
- //#endregion
316
- //#region ../core/src/docs/default-renderers.ts
317
- /**
318
- * Escape markdown special characters in table cells. Embedded line breaks are
319
- * converted to `<br>` so multi-line descriptions stay within a single cell.
320
- */
321
- function escapeTableCell$1(str) {
322
- return inlineMarkdownBreaks(str.replace(/\|/g, "\\|"));
323
- }
324
- /**
325
- * Render usage line
326
- */
327
- function renderUsage(info) {
328
- const parts = [info.fullCommandPath];
329
- if (info.options.length > 0) parts.push("[options]");
330
- if (info.subCommands.length > 0) {
331
- if (info.command.run) parts.push("[command]");
332
- else parts.push("<command>");
333
- }
334
- for (const arg of info.positionalArgs) if (arg.required) parts.push(`<${arg.name}>`);
335
- else parts.push(`[${arg.name}]`);
336
- return parts.join(" ");
337
- }
338
- /**
339
- * Render arguments as table
340
- */
341
- function renderArgumentsTable(info) {
342
- if (info.positionalArgs.length === 0) return "";
343
- const lines = [];
344
- lines.push("| Argument | Description | Required |");
345
- lines.push("|----------|-------------|----------|");
346
- for (const arg of info.positionalArgs) {
347
- const desc = escapeTableCell$1(arg.description ?? "");
348
- const required = arg.required ? "Yes" : "No";
349
- lines.push(`| \`${arg.name}\` | ${desc} | ${required} |`);
350
- }
351
- return lines.join("\n");
352
- }
353
- /**
354
- * Render arguments as list
355
- */
356
- function renderArgumentsList(info) {
357
- if (info.positionalArgs.length === 0) return "";
358
- const lines = [];
359
- for (const arg of info.positionalArgs) {
360
- const required = arg.required ? "(required)" : "(optional)";
361
- const desc = arg.description ? ` - ${inlineMarkdownBreaks(arg.description)}` : "";
362
- lines.push(`- \`${arg.name}\`${desc} ${required}`);
363
- }
364
- return lines.join("\n");
365
- }
366
- /**
367
- * Render options as markdown table
368
- *
369
- * Features:
370
- * - Uses kebab-case (cliName) for option names (e.g., `--dry-run` instead of `--dryRun`)
371
- * - Automatically adds Env column when any option has env configured
372
- * - Displays multiple env vars as comma-separated list
373
- *
374
- * @example
375
- * | Option | Alias | Description | Required | Default | Env |
376
- * |--------|-------|-------------|----------|---------|-----|
377
- * | `--dry-run` | `-d` | Dry run mode | No | `false` | - |
378
- * | `--port <PORT>` | - | Server port | Yes | - | `PORT`, `SERVER_PORT` |
379
- */
380
- function renderOptionsTable(info) {
381
- return emitMarkdownTable(toOptionRows(info.options));
382
- }
383
- /**
384
- * Render options as markdown list
385
- *
386
- * Features:
387
- * - Uses kebab-case (cliName) for option names (e.g., `--dry-run` instead of `--dryRun`)
388
- * - Appends env info at the end of each option (e.g., `[env: PORT, SERVER_PORT]`)
389
- *
390
- * @example
391
- * - `-d`, `--dry-run` - Dry run mode (default: false)
392
- * - `--port <PORT>` - Server port (required) [env: PORT, SERVER_PORT]
393
- */
394
- function renderOptionsList(info) {
395
- return emitMarkdownList(toOptionRows(info.options));
396
- }
397
- /**
398
- * Generate anchor from command path
399
- */
400
- function generateAnchor$1(commandPath) {
401
- return commandPath.join("-").toLowerCase();
402
- }
403
- /**
404
- * Generate relative path from one file to another.
405
- * Always emits forward slashes so Markdown links remain portable across OSes.
406
- */
407
- function getRelativePath(from, to) {
408
- const fromPosix = from.replace(/\\/g, "/");
409
- const toPosix = to.replace(/\\/g, "/");
410
- return path.posix.relative(path.posix.dirname(fromPosix), toPosix);
411
- }
412
- /**
413
- * Render subcommands as table
414
- */
415
- function renderSubcommandsTable(info, generateAnchors = true) {
416
- return renderSubcommandsTableFromArray(info.subCommands, info, generateAnchors);
417
- }
418
- /**
419
- * Render options from array as table
420
- */
421
- function renderOptionsTableFromArray(options) {
422
- return emitMarkdownTable(toOptionRows(options));
423
- }
424
- /**
425
- * Render union/xor options as markdown with variant grouping
426
- */
427
- function renderUnionOptionsMarkdown(extracted, style = "table") {
428
- const unionOptions = extracted.unionOptions ?? [];
429
- if (unionOptions.length === 0) return "";
430
- const sections = [];
431
- const allFieldNames = /* @__PURE__ */ new Set();
432
- for (const option of unionOptions) for (const field of option.fields) allFieldNames.add(field.name);
433
- const commonFieldNames = /* @__PURE__ */ new Set();
434
- for (const fieldName of allFieldNames) if (unionOptions.every((o) => o.fields.some((f) => f.name === fieldName))) commonFieldNames.add(fieldName);
435
- const commonFields = extracted.fields.filter((f) => commonFieldNames.has(f.name) && !f.positional);
436
- if (commonFields.length > 0) sections.push(style === "table" ? renderOptionsTableFromArray(commonFields) : renderOptionsListFromArray(commonFields));
437
- sections.push("> One of the following option groups is required:");
438
- for (let i = 0; i < unionOptions.length; i++) {
439
- const option = unionOptions[i];
440
- if (!option) continue;
441
- const uniqueFields = option.fields.filter((f) => !commonFieldNames.has(f.name) && !f.positional);
442
- const label = option.description ?? `Variant ${i + 1}`;
443
- if (uniqueFields.length === 0) {
444
- sections.push(`**${label}:**\n\n_no options_`);
445
- continue;
446
- }
447
- const rendered = style === "table" ? renderOptionsTableFromArray(uniqueFields) : renderOptionsListFromArray(uniqueFields);
448
- sections.push(`**${label}:**\n\n${rendered}`);
449
- }
450
- return sections.join("\n\n");
451
- }
452
- /**
453
- * Render discriminatedUnion options as markdown with variant grouping
454
- */
455
- function renderDiscriminatedUnionOptionsMarkdown(extracted, style = "table") {
456
- const discriminator = extracted.discriminator;
457
- const variants = extracted.variants ?? [];
458
- if (!discriminator || variants.length === 0) return "";
459
- const sections = [];
460
- const allFieldNames = /* @__PURE__ */ new Set();
461
- for (const variant of variants) for (const field of variant.fields) allFieldNames.add(field.name);
462
- const commonFieldNames = /* @__PURE__ */ new Set();
463
- for (const fieldName of allFieldNames) {
464
- if (fieldName === discriminator) continue;
465
- if (variants.every((v) => v.fields.some((f) => f.name === fieldName))) commonFieldNames.add(fieldName);
466
- }
467
- const discriminatorField = extracted.fields.find((f) => f.name === discriminator);
468
- const variantValues = variants.map((v) => v.discriminatorValue).join("\\|");
469
- const topFields = [];
470
- if (discriminatorField) topFields.push({
471
- ...discriminatorField,
472
- placeholder: variantValues
473
- });
474
- for (const fieldName of commonFieldNames) {
475
- const field = extracted.fields.find((f) => f.name === fieldName);
476
- if (field && !field.positional) topFields.push(field);
477
- }
478
- if (topFields.length > 0) sections.push(style === "table" ? renderOptionsTableFromArray(topFields) : renderOptionsListFromArray(topFields));
479
- for (const variant of variants) {
480
- const uniqueFields = variant.fields.filter((f) => f.name !== discriminator && !commonFieldNames.has(f.name) && !f.positional);
481
- if (uniqueFields.length === 0) continue;
482
- const descSuffix = variant.description ? ` ${variant.description}` : "";
483
- const label = `**When \`${discriminator}\` = \`${variant.discriminatorValue}\`:**${descSuffix}`;
484
- const rendered = style === "table" ? renderOptionsTableFromArray(uniqueFields) : renderOptionsListFromArray(uniqueFields);
485
- sections.push(`${label}\n\n${rendered}`);
486
- }
487
- return sections.join("\n\n");
488
- }
489
- /**
490
- * Render options from array as list
491
- */
492
- function renderOptionsListFromArray(options) {
493
- return emitMarkdownList(toOptionRows(options));
494
- }
495
- /**
496
- * Render arguments from array as table
497
- */
498
- function renderArgumentsTableFromArray(args) {
499
- if (args.length === 0) return "";
500
- const lines = [];
501
- lines.push("| Argument | Description | Required |");
502
- lines.push("|----------|-------------|----------|");
503
- for (const arg of args) {
504
- const desc = escapeTableCell$1(arg.description ?? "");
505
- const required = arg.required ? "Yes" : "No";
506
- lines.push(`| \`${arg.name}\` | ${desc} | ${required} |`);
507
- }
508
- return lines.join("\n");
509
- }
510
- /**
511
- * Render arguments from array as list
512
- */
513
- function renderArgumentsListFromArray(args) {
514
- if (args.length === 0) return "";
515
- const lines = [];
516
- for (const arg of args) {
517
- const required = arg.required ? "(required)" : "(optional)";
518
- const desc = arg.description ? ` - ${inlineMarkdownBreaks(arg.description)}` : "";
519
- lines.push(`- \`${arg.name}\`${desc} ${required}`);
520
- }
521
- return lines.join("\n");
522
- }
523
- /**
524
- * Render subcommands from array as table
525
- */
526
- function renderSubcommandsTableFromArray(subcommands, info, generateAnchors = true) {
527
- if (subcommands.length === 0) return "";
528
- const hasAliases = subcommands.some((s) => s.aliases && s.aliases.length > 0);
529
- const lines = [];
530
- if (hasAliases) {
531
- lines.push("| Command | Aliases | Description |");
532
- lines.push("|---------|---------|-------------|");
533
- } else {
534
- lines.push("| Command | Description |");
535
- lines.push("|---------|-------------|");
536
- }
537
- const currentFile = info.filePath;
538
- const fileMap = info.fileMap;
539
- for (const sub of subcommands) {
540
- const fullName = sub.fullPath.join(" ");
541
- const desc = escapeTableCell$1(sub.description ?? "");
542
- const subCommandPath = sub.fullPath.join(" ");
543
- const aliasCell = hasAliases ? sub.aliases && sub.aliases.length > 0 ? sub.aliases.map((a) => `\`${escapeTableCell$1(a)}\``).join(", ") : "-" : "";
544
- let cmdCell;
545
- if (generateAnchors) {
546
- const anchor = generateAnchor$1(sub.fullPath);
547
- const hasSubFile = fileMap !== void 0 && Object.prototype.hasOwnProperty.call(fileMap, subCommandPath);
548
- const subFile = hasSubFile ? fileMap[subCommandPath] : void 0;
549
- if (currentFile && subFile && currentFile !== subFile) cmdCell = `[\`${fullName}\`](${getRelativePath(currentFile, subFile)}#${anchor})`;
550
- else if (fileMap && !hasSubFile) cmdCell = `\`${fullName}\``;
551
- else cmdCell = `[\`${fullName}\`](#${anchor})`;
552
- } else cmdCell = `\`${fullName}\``;
553
- if (hasAliases) lines.push(`| ${cmdCell} | ${aliasCell} | ${desc} |`);
554
- else lines.push(`| ${cmdCell} | ${desc} |`);
555
- }
556
- return lines.join("\n");
557
- }
558
- /**
559
- * Render examples as markdown
560
- *
561
- * @example
562
- * **Basic usage**
563
- *
564
- * ```bash
565
- * $ greet World
566
- * ```
567
- *
568
- * Output:
569
- * ```
570
- * Hello, World!
571
- * ```
572
- */
573
- function renderExamplesDefault(examples, results, opts) {
574
- if (examples.length === 0) return "";
575
- const showOutput = opts?.showOutput ?? true;
576
- const prefix = opts?.commandPrefix ? `${opts.commandPrefix} ` : "";
577
- const lines = [];
578
- for (let i = 0; i < examples.length; i++) {
579
- const example = examples[i];
580
- if (!example) continue;
581
- const result = results?.[i];
582
- lines.push(`**${example.desc}**`);
583
- lines.push("");
584
- lines.push("```bash");
585
- lines.push(`$ ${prefix}${example.cmd}`);
586
- if (showOutput) {
587
- if (result) {
588
- if (result.stdout) lines.push(result.stdout);
589
- if (result.stderr) lines.push(`[stderr] ${result.stderr}`);
590
- } else if (example.output) lines.push(example.output);
591
- }
592
- lines.push("```");
593
- lines.push("");
594
- }
595
- while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
596
- return lines.join("\n");
597
- }
598
- /**
599
- * Wrap content with section markers
600
- */
601
- function wrapWithMarker(type, scope, content) {
602
- return `${sectionStartMarker(type, scope)}\n${content}\n${sectionEndMarker(type, scope)}`;
603
- }
604
- /**
605
- * Generate a "See Global Options" link for subcommand documentation.
606
- * Returns null for root command or when no global options exist.
607
- */
608
- function getGlobalOptionsLink(info) {
609
- if (!info.hasGlobalOptions || info.commandPath === "") return null;
610
- return `See [Global Options](${info.rootDocPath && info.filePath && info.filePath !== info.rootDocPath ? `${getRelativePath(info.filePath, info.rootDocPath)}#global-options` : "#global-options"}) for options available to all commands.`;
611
- }
612
- function createCommandRenderer(options = {}) {
613
- const { headingLevel = 1, optionStyle = "table", generateAnchors = true, includeSubcommandDetails = true, markerless = false, renderDescription: customRenderDescription, renderUsage: customRenderUsage, renderArguments: customRenderArguments, renderOptions: customRenderOptions, renderSubcommands: customRenderSubcommands, renderNotes: customRenderNotes, renderFooter: customRenderFooter, renderExamples: customRenderExamples } = options;
614
- const wrap = markerless ? (_type, _scope, content) => content : wrapWithMarker;
615
- return (info) => {
616
- const sections = [];
617
- const scope = info.commandPath;
618
- const effectiveLevel = Math.min(headingLevel + (info.depth - 1), 6);
619
- const h = "#".repeat(effectiveLevel);
620
- const title = info.commandPath || info.name;
621
- sections.push(wrap("heading", scope, `${h} ${title}`));
622
- {
623
- const parts = [];
624
- if (info.description) parts.push(info.description);
625
- if (info.aliases && info.aliases.length > 0) parts.push(`**Aliases:** ${info.aliases.map((a) => `\`${a}\``).join(", ")}`);
626
- if (parts.length > 0) {
627
- const context = {
628
- content: parts.join("\n\n"),
629
- heading: "",
630
- info
631
- };
632
- const content = customRenderDescription ? customRenderDescription(context) : context.content;
633
- if (content) sections.push(wrap("description", scope, content));
634
- }
635
- }
636
- {
637
- const context = {
638
- content: `**Usage**\n\n\`\`\`\n${renderUsage(info)}\n\`\`\``,
639
- heading: "**Usage**",
640
- info
641
- };
642
- const content = customRenderUsage ? customRenderUsage(context) : context.content;
643
- if (content) sections.push(wrap("usage", scope, content));
644
- }
645
- if (info.positionalArgs.length > 0) {
646
- const renderArgs = (args, opts) => {
647
- const style = opts?.style ?? optionStyle;
648
- const withHeading = opts?.withHeading ?? true;
649
- const content = style === "table" ? renderArgumentsTableFromArray(args) : renderArgumentsListFromArray(args);
650
- return withHeading ? `**Arguments**\n\n${content}` : content;
651
- };
652
- const context = {
653
- args: info.positionalArgs,
654
- render: renderArgs,
655
- heading: "**Arguments**",
656
- info
657
- };
658
- const content = customRenderArguments ? customRenderArguments(context) : renderArgs(context.args);
659
- if (content) sections.push(wrap("arguments", scope, content));
660
- }
661
- if (info.options.length > 0) {
662
- const renderOpts = (opts, renderOpts) => {
663
- const style = renderOpts?.style ?? optionStyle;
664
- const withHeading = renderOpts?.withHeading ?? true;
665
- const extracted = info.extracted;
666
- let content;
667
- if (extracted && (extracted.schemaType === "union" || extracted.schemaType === "xor") && extracted.unionOptions) content = renderUnionOptionsMarkdown(extracted, style);
668
- else if (extracted && extracted.schemaType === "discriminatedUnion" && extracted.discriminator) content = renderDiscriminatedUnionOptionsMarkdown(extracted, style);
669
- else content = style === "table" ? renderOptionsTableFromArray(opts) : renderOptionsListFromArray(opts);
670
- return withHeading ? `**Options**\n\n${content}` : content;
671
- };
672
- const context = {
673
- options: info.options,
674
- render: renderOpts,
675
- heading: "**Options**",
676
- info
677
- };
678
- const content = customRenderOptions ? customRenderOptions(context) : renderOpts(context.options);
679
- if (content) sections.push(wrap("options", scope, content));
680
- }
681
- {
682
- const globalLink = getGlobalOptionsLink(info);
683
- if (globalLink) sections.push(wrap("global-options-link", scope, globalLink));
684
- }
685
- if (info.subCommands.length > 0) {
686
- const effectiveAnchors = generateAnchors && includeSubcommandDetails;
687
- const renderSubs = (subs, opts) => {
688
- const anchors = opts?.generateAnchors ?? effectiveAnchors;
689
- const withHeading = opts?.withHeading ?? true;
690
- const content = renderSubcommandsTableFromArray(subs, info, anchors);
691
- return withHeading ? `**Commands**\n\n${content}` : content;
692
- };
693
- const context = {
694
- subcommands: info.subCommands,
695
- render: renderSubs,
696
- heading: "**Commands**",
697
- info
698
- };
699
- const content = customRenderSubcommands ? customRenderSubcommands(context) : renderSubs(context.subcommands);
700
- if (content) sections.push(wrap("subcommands", scope, content));
701
- }
702
- if (info.examples && info.examples.length > 0) {
703
- const renderEx = (examples, results, opts) => {
704
- const withHeading = opts?.withHeading ?? true;
705
- const content = renderExamplesDefault(examples, results, {
706
- commandPrefix: info.fullCommandPath,
707
- ...opts
708
- });
709
- return withHeading ? `**Examples**\n\n${content}` : content;
710
- };
711
- const context = {
712
- examples: info.examples,
713
- results: info.exampleResults,
714
- render: renderEx,
715
- heading: "**Examples**",
716
- info
717
- };
718
- const content = customRenderExamples ? customRenderExamples(context) : renderEx(context.examples, context.results);
719
- if (content) sections.push(wrap("examples", scope, content));
720
- }
721
- if (info.notes) {
722
- const context = {
723
- content: `**Notes**\n\n${info.notes}`,
724
- heading: "**Notes**",
725
- info
726
- };
727
- const content = customRenderNotes ? customRenderNotes(context) : context.content;
728
- if (content) sections.push(wrap("notes", scope, content));
729
- }
730
- {
731
- const context = {
732
- content: "",
733
- heading: "",
734
- info
735
- };
736
- const content = customRenderFooter ? customRenderFooter(context) : context.content;
737
- if (content) sections.push(content);
738
- }
739
- return sections.join("\n\n") + "\n";
740
- };
741
- }
742
- /**
743
- * Default renderers presets
744
- */
745
- const defaultRenderers = {
746
- /** Standard command documentation */
747
- command: (options) => createCommandRenderer(options),
748
- /** Table style options (default) */
749
- tableStyle: createCommandRenderer({ optionStyle: "table" }),
750
- /** List style options */
751
- listStyle: createCommandRenderer({ optionStyle: "list" })
752
- };
15
+ `),heading:``,info:e},r=o?o(n):n.content;r&&a.push(m(`description`,h,r))}}{let t={content:`**Usage**\n\n\`\`\`\n${z(e)}\n\`\`\``,heading:`**Usage**`,info:e},n=s?s(t):t.content;n&&a.push(m(`usage`,h,n))}if(e.positionalArgs.length>0){let t=(e,t)=>{let r=t?.style??n,i=t?.withHeading??!0,a=r===`table`?fe(e):pe(e);return i?`**Arguments**\n\n${a}`:a},r={args:e.positionalArgs,render:t,heading:`**Arguments**`,info:e},i=c?c(r):t(r.args);i&&a.push(m(`arguments`,h,i))}if(e.options.length>0){let t=(t,r)=>{let i=r?.style??n,a=r?.withHeading??!0,o=e.extracted,s;return s=o&&(o.schemaType===`union`||o.schemaType===`xor`)&&o.unionOptions?ue(o,i):o&&o.schemaType===`discriminatedUnion`&&o.discriminator?de(o,i):i===`table`?H(t):U(t),a?`**Options**\n\n${s}`:s},r={options:e.options,render:t,heading:`**Options**`,info:e},i=l?l(r):t(r.options);i&&a.push(m(`options`,h,i))}{let t=_e(e);t&&a.push(m(`global-options-link`,h,t))}if(e.subCommands.length>0){let t=r&&i,n=(n,r)=>{let i=r?.generateAnchors??t,a=r?.withHeading??!0,o=me(n,e,i);return a?`**Commands**\n\n${o}`:o},o={subcommands:e.subCommands,render:n,heading:`**Commands**`,info:e},s=u?u(o):n(o.subcommands);s&&a.push(m(`subcommands`,h,s))}if(e.examples&&e.examples.length>0){let t=(t,n,r)=>{let i=r?.withHeading??!0,a=he(t,n,{commandPrefix:e.fullCommandPath,...r});return i?`**Examples**\n\n${a}`:a},n={examples:e.examples,results:e.exampleResults,render:t,heading:`**Examples**`,info:e},r=p?p(n):t(n.examples,n.results);r&&a.push(m(`examples`,h,r))}if(e.notes){let t={content:`**Notes**\n\n${e.notes}`,heading:`**Notes**`,info:e},n=d?d(t):t.content;n&&a.push(m(`notes`,h,n))}{let t={content:``,heading:``,info:e},n=f?f(t):t.content;n&&a.push(n)}return a.join(`
753
16
 
754
- //#endregion
755
- //#region ../core/src/docs/doc-comparator.ts
756
- /**
757
- * Compare generated content with existing file
758
- */
759
- function compareWithExisting(generatedContent, filePath) {
760
- const absolutePath = path$1.resolve(filePath);
761
- if (!fs.existsSync(absolutePath)) return {
762
- match: false,
763
- fileExists: false
764
- };
765
- const existingContent = fs.readFileSync(absolutePath, "utf-8");
766
- if (generatedContent === existingContent) return {
767
- match: true,
768
- fileExists: true
769
- };
770
- return {
771
- match: false,
772
- diff: formatDiff(existingContent, generatedContent),
773
- fileExists: true
774
- };
775
- }
776
- /**
777
- * Format diff between two strings in unified diff format
778
- */
779
- function formatDiff(expected, actual) {
780
- const expectedLines = expected.split("\n");
781
- const actualLines = actual.split("\n");
782
- const result = [];
783
- result.push("--- existing");
784
- result.push("+++ generated");
785
- result.push("");
786
- const maxLines = Math.max(expectedLines.length, actualLines.length);
787
- let inChunk = false;
788
- let chunkStart = 0;
789
- const chunk = [];
790
- const flushChunk = () => {
791
- if (chunk.length > 0) {
792
- result.push(`@@ -${chunkStart + 1},${chunk.length} @@`);
793
- result.push(...chunk);
794
- chunk.length = 0;
795
- }
796
- inChunk = false;
797
- };
798
- for (let i = 0; i < maxLines; i++) {
799
- const expectedLine = expectedLines[i];
800
- const actualLine = actualLines[i];
801
- if (expectedLine === actualLine) {
802
- if (inChunk) {
803
- chunk.push(` ${expectedLine ?? ""}`);
804
- const lastChangeIndex = chunk.findIndex((line, idx) => (line.startsWith("-") || line.startsWith("+")) && chunk.slice(idx + 1).every((l) => l.startsWith(" ")));
805
- if (lastChangeIndex !== -1 && chunk.length - lastChangeIndex > 3) flushChunk();
806
- }
807
- } else {
808
- if (!inChunk) {
809
- inChunk = true;
810
- chunkStart = i;
811
- const contextStart = Math.max(0, i - 3);
812
- for (let j = contextStart; j < i; j++) chunk.push(` ${expectedLines[j] ?? ""}`);
813
- }
814
- if (expectedLine !== void 0 && (actualLine === void 0 || expectedLine !== actualLine)) chunk.push(`-${expectedLine}`);
815
- if (actualLine !== void 0 && (expectedLine === void 0 || expectedLine !== actualLine)) chunk.push(`+${actualLine}`);
816
- }
817
- }
818
- flushChunk();
819
- return result.join("\n");
820
- }
821
- /**
822
- * Write content to file, creating directories if needed
823
- */
824
- function writeFile(filePath, content) {
825
- const absolutePath = path$1.resolve(filePath);
826
- const dir = path$1.dirname(absolutePath);
827
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
828
- fs.writeFileSync(absolutePath, content, "utf-8");
829
- }
830
- /**
831
- * Read file content if it exists
832
- * Returns null if file does not exist
833
- */
834
- function readFile(filePath) {
835
- const absolutePath = path$1.resolve(filePath);
836
- if (!fs.existsSync(absolutePath)) return null;
837
- return fs.readFileSync(absolutePath, "utf-8");
838
- }
839
- /**
840
- * Delete file if it exists
841
- * @param filePath - Path to the file to delete
842
- * @param fileSystem - Optional fs implementation (useful when fs is mocked)
843
- */
844
- function deleteFile(filePath, fileSystem = fs) {
845
- const absolutePath = path$1.resolve(filePath);
846
- if (fileSystem.existsSync(absolutePath)) fileSystem.unlinkSync(absolutePath);
847
- }
17
+ `)+`
18
+ `}}const ve={command:e=>W(e),tableStyle:W({optionStyle:`table`}),listStyle:W({optionStyle:`list`})};function ye(e,t){let n=o.resolve(t);if(!a.existsSync(n))return{match:!1,fileExists:!1};let r=a.readFileSync(n,`utf-8`);return e===r?{match:!0,fileExists:!0}:{match:!1,diff:G(r,e),fileExists:!0}}function G(e,t){let n=e.split(`
19
+ `),r=t.split(`
20
+ `),i=[];i.push(`--- existing`),i.push(`+++ generated`),i.push(``);let a=Math.max(n.length,r.length),o=!1,s=0,c=[],l=()=>{c.length>0&&(i.push(`@@ -${s+1},${c.length} @@`),i.push(...c),c.length=0),o=!1};for(let e=0;e<a;e++){let t=n[e],i=r[e];if(t===i){if(o){c.push(` ${t??``}`);let e=c.findIndex((e,t)=>(e.startsWith(`-`)||e.startsWith(`+`))&&c.slice(t+1).every(e=>e.startsWith(` `)));e!==-1&&c.length-e>3&&l()}}else{if(!o){o=!0,s=e;let t=Math.max(0,e-3);for(let r=t;r<e;r++)c.push(` ${n[r]??``}`)}t!==void 0&&(i===void 0||t!==i)&&c.push(`-${t}`),i!==void 0&&(t===void 0||t!==i)&&c.push(`+${i}`)}}return l(),i.join(`
21
+ `)}function K(e,t){let n=o.resolve(e),r=o.dirname(n);a.existsSync(r)||a.mkdirSync(r,{recursive:!0}),a.writeFileSync(n,t,`utf-8`)}function be(e){let t=o.resolve(e);return a.existsSync(t)?a.readFileSync(t,`utf-8`):null}function xe(e,t=a){let n=o.resolve(e);t.existsSync(n)&&t.unlinkSync(n)}async function Se(e,n,r=[]){let a=t(e),o=a?.fields.filter(e=>e.positional)??[],s=a?.fields.filter(e=>!e.positional)??[],c=[];if(e.subCommands)for(let[t,n]of Object.entries(e.subCommands)){let e=await i(n),a=[...r,t];c.push({name:t,description:e.description,aliases:e.aliases,fullPath:a})}return{name:e.name??``,description:e.description,aliases:e.aliases,fullCommandPath:r.length>0?`${n} ${r.join(` `)}`:n,commandPath:r.join(` `),depth:r.length+1,positionalArgs:o,options:s,subCommands:c,extracted:a,command:e,notes:e.notes,examples:e.examples}}async function Ce(e,t){let n=t??e.name??`command`,r=new Map;async function a(e,t){let o=await Se(e,n,t),s=t.join(` `);if(r.set(s,o),e.subCommands)for(let[n,r]of Object.entries(e.subCommands))await a(await i(r),[...t,n])}return await a(e,[]),r}async function we(e,t,n,r=[]){let i=[];t.mock&&await t.mock();try{for(let t of e){let e=await Te(t,n,r);i.push(e)}}finally{t.cleanup&&await t.cleanup()}return i}async function Te(e,t,n){let i=Ee(e.cmd),a=[...n,...i],o=r({passthrough:!1});o.start();let s=!0;try{let{runCommand:e}=await import(`./runner-Chaf_5lK.js`),n=await e(t,a);s=n.success,!n.success&&n.error&&console.error(n.error.message)}catch(e){s=!1,console.error(e instanceof Error?e.message:String(e))}finally{o.stop()}let c=o.getLogs(),l=c.entries.filter(e=>e.stream===`stdout`).map(e=>e.message).join(`
22
+ `),u=c.entries.filter(e=>e.stream===`stderr`).map(e=>e.message).join(`
23
+ `);return{cmd:e.cmd,desc:e.desc,expectedOutput:e.output,stdout:l,stderr:u,success:s}}function Ee(e){let t=[],n=``,r=!1,i=``;for(let a=0;a<e.length;a++){let o=e[a];(o===`"`||o===`'`)&&!r?(r=!0,i=o):o===i&&r?(r=!1,i=``):o===` `&&!r?n&&=(t.push(n),``):n+=o}return n&&t.push(n),t}function De(t){let n=e();return Object.entries(t).map(([e,t])=>n.resolveFieldMeta(e,t))}function Oe(e,t){let n=De(e).filter(e=>!e.positional);return n.length===0?``:C(p(n),t?.columns)}function ke(e){return e.replace(/\|/g,`\\|`).replace(/\n/g,` `)}function Ae(e){return e.replace(/\s+/g,`-`).toLowerCase()}function je(e){return e.subCommands.length===0}function Me(e,t){return e===t?!0:t===``?e!==``:e.startsWith(t+` `)}function Ne(e,t,n){let r=[];for(let i of e){let e=t.get(i);if(e){if(je(e))r.push(i);else for(let[e,a]of t)(i===``?e.length>0:e.startsWith(i+` `)||e===i)&&(je(a)||!n)&&r.push(e)}}return r}function Pe(e,t,n,r){let i=`#`.repeat(n),a=[];a.push(`${i} [${e.title}](${e.docPath})`),a.push(``),a.push(e.description),a.push(``);let o=e.noExpand?e.commands:Ne(e.commands,t,r),s=o,c=new Set;if(e.allowedCommands){let t=new Set(e.allowedCommands);s=o.filter(e=>t.has(e));for(let n of e.commands)t.has(n)&&!s.some(e=>Me(e,n))&&(s.push(n),c.add(n))}a.push(`| Command | Description |`),a.push(`|---------|-------------|`);for(let n of s){let i=t.get(n);if(!i||!e.noExpand&&r&&!c.has(n)&&!je(i))continue;let o=n||i.name,s=Ae(o),l=ke(i.description??``);a.push(`| [${o}](${e.docPath}#${s}) | ${l} |`)}return a.join(`
24
+ `)}async function Fe(e,t,n){let r=n?.headingLevel??3,i=n?.leafOnly??!0,a=await Ce(e),o=[];for(let e of t){let t=Pe(e,a,r,i);o.push(t)}return o.join(`
848
25
 
849
- //#endregion
850
- //#region ../core/src/docs/doc-generator.ts
851
- /**
852
- * Build CommandInfo from a command
853
- */
854
- async function buildCommandInfo(command, rootName, commandPath = []) {
855
- const extracted = getExtractedFields(command);
856
- const positionalArgs = extracted?.fields.filter((f) => f.positional) ?? [];
857
- const options = extracted?.fields.filter((f) => !f.positional) ?? [];
858
- const subCommands = [];
859
- if (command.subCommands) for (const [name, subCmd] of Object.entries(command.subCommands)) {
860
- const resolved = await resolveLazyCommand(subCmd);
861
- const fullPath = [...commandPath, name];
862
- subCommands.push({
863
- name,
864
- description: resolved.description,
865
- aliases: resolved.aliases,
866
- fullPath
867
- });
868
- }
869
- return {
870
- name: command.name ?? "",
871
- description: command.description,
872
- aliases: command.aliases,
873
- fullCommandPath: commandPath.length > 0 ? `${rootName} ${commandPath.join(" ")}` : rootName,
874
- commandPath: commandPath.join(" "),
875
- depth: commandPath.length + 1,
876
- positionalArgs,
877
- options,
878
- subCommands,
879
- extracted,
880
- command,
881
- notes: command.notes,
882
- examples: command.examples
883
- };
884
- }
885
- /**
886
- * Collect all commands with their paths
887
- * Returns a map of command path -> CommandInfo
888
- */
889
- async function collectAllCommands(command, rootName) {
890
- const root = rootName ?? command.name ?? "command";
891
- const result = /* @__PURE__ */ new Map();
892
- async function traverse(cmd, path) {
893
- const info = await buildCommandInfo(cmd, root, path);
894
- const pathKey = path.join(" ");
895
- result.set(pathKey, info);
896
- if (cmd.subCommands) for (const [name, subCmd] of Object.entries(cmd.subCommands)) await traverse(await resolveLazyCommand(subCmd), [...path, name]);
897
- }
898
- await traverse(command, []);
899
- return result;
900
- }
26
+ `)}async function q(e,t){if(!t)return e;let n=await t(e);return!e.endsWith(`
27
+ `)&&n.endsWith(`
28
+ `)?n.slice(0,-1):n}function Ie(e){let t=process.env[e];return t===`true`||t===`1`}function Le(e){let t=e.split(/\r?\n/);if(t[0]!==`---`)return null;let n=[];for(let e=1;e<t.length;e++){let r=t[e];if(r===`---`||r===`...`)return n.join(`
29
+ `);n.push(r??``)}return null}function Re(e){let t=ht(e),n=e.split(/\r?\n/);if(n[0]!==`---`)return e;let r=-1;for(let e=1;e<n.length;e++){let t=n[e];if(t===`---`||t===`...`){r=e;break}}if(r===-1)return e;let i=n.slice(1,r),a=[];for(let e=0;e<i.length;e++){let t=i[e]??``;if(!/^politty\s*:\s*(.*)$/.test(t)){a.push(t);continue}for(;e+1<i.length;){let t=i[e+1]??``;if(t.trim()!==``&&!t.startsWith(` `)&&!t.startsWith(` `))break;e++}}let o=n.slice(r+1);return a.some(e=>e.trim()!==``)?[`---`,...a,n[r]??`---`,...o].join(t):o.join(t).replace(RegExp(`^${t}`),``)}function ze(e){let t=e.trim();return t.length>=2&&(t.startsWith(`"`)&&t.endsWith(`"`)||t.startsWith(`'`)&&t.endsWith(`'`))?t.slice(1,-1):t}function Be(e){let t=ze(e);if(t===``)return null;let n=t.match(/^\{\{politty:([^{}]*)\}\}$/);return n?t=n[1]??``:t.startsWith(`politty:`)&&(t=t.slice(8)),t===``?null:t}function Ve(e){return e.slice(2,-2).slice(8)}function He(e){let t=e.trim();return!t.startsWith(`[`)||!t.endsWith(`]`)?[t]:t.slice(1,-1).split(`,`).map(e=>e.trim()).filter(e=>e.length>0)}function Ue(e,t){let n=Be(t);n!==null&&e.add(n)}function We(e){let t=new Set,n=Le(e);if(n===null)return t;let r=!1,i=!1,a=0;for(let e of n.split(/\r?\n/)){let n=e.trim();if(n===``||n.startsWith(`#`))continue;let o=e.match(/^politty\s*:\s*(.*)$/);if(o){r=(o[1]?.trim()??``)===``,i=!1;continue}if(!e.startsWith(` `)&&!e.startsWith(` `)){r=!1,i=!1;continue}if(!r)continue;let s=e.match(/^(\s+)(?:exclude|excludes)\s*:\s*(.*)$/);if(s){let e=s[2]?.trim()??``;if(e===``)i=!0,a=s[1]?.length??0;else{i=!1;for(let n of He(e))Ue(t,n)}continue}if(!i)continue;let c=e.match(/^(\s*)-\s*(.+)$/);if(!c||(c[1]?.length??0)<=a){i=!1;continue}Ue(t,c[2]??``)}return t}function Ge(e){let t=Le(e);if(t===null)return{};let n=!1,r=!1,i=0,a={};for(let e of t.split(/\r?\n/)){let t=e.trim();if(t===``||t.startsWith(`#`))continue;let o=e.match(/^politty\s*:\s*(.*)$/);if(o){n=(o[1]?.trim()??``)===``,r=!1;continue}if(!e.startsWith(` `)&&!e.startsWith(` `)){n=!1,r=!1;continue}if(!n)continue;let s=e.match(/^(\s+)index\s*:\s*(.*)$/);if(s){r=(s[2]?.trim()??``)===``,i=s[1]?.length??0;continue}if(!r)continue;if((e.match(/^(\s*)/)?.[1]?.length??0)<=i){r=!1;continue}let c=e.match(/^\s+(title|description)\s*:\s*(.+)$/);if(!c)continue;let l=c[1],u=ze(c[2]??``);l===`title`?a.title=u:l===`description`&&(a.description=u)}return a}function Ke(e){return{rawKeys:e,commandScopes:new Set,commandSections:new Map,globalOptions:!1,index:!1}}function qe(e,t,n){Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0})}function Je(e){if(Array.isArray(e))return{commands:e};if(!(`commands`in e)||!Array.isArray(e.commands))throw Error(`Invalid file config: object form must include a "commands" array. Use [] to skip generation intentionally.`);return e}function J(e,t){return t===``||e===t||e.startsWith(t+` `)}function Y(e){return e.includes(`*`)}function Ye(e,t){let n=e===``?[]:e.split(` `),r=t===``?[]:t.split(` `);if(n.length!==r.length)return!1;for(let e=0;e<r.length;e++){let t=r[e],i=n[e];if(t!==`*`&&t!==i)return!1}return!0}function Xe(e,t){let n=[];for(let r of t.keys())Ye(r,e)&&n.push(r);return n}function Ze(e,t){if(Y(t)){if(Ye(e,t))return!0;let n=e===``?[]:e.split(` `),r=t===``?[]:t.split(` `);return n.length>r.length&&Ye(n.slice(0,r.length).join(` `),t)}return J(e,t)}function Qe(e,t){let n=new Set,r=e.flatMap(e=>Y(e)?Xe(e,t):[e]);for(let e of r)for(let r of t.keys())J(r,e)&&n.add(r);return Array.from(n)}function $e(e,t){return e.filter(e=>!t.some(t=>Ze(e,t)))}function et(e,t){let n=[];for(let r of e)Y(r)?n.push(...Xe(r,t)):t.has(r)&&n.push(r);return n}function X(e,t,n){let r=Je(e),i=r.commands;return{fileConfig:r,specifiedCommands:i,commandPaths:$e(r.noExpand?i.filter(e=>t.has(e)):Qe(i,t),n),topLevelCommands:$e(et(i,t),n)}}function tt(e,t,n){let r=[];for(let i of e){let e=Y(i)?Xe(i,n):[i];for(let n of e)for(let e of t)Y(e)?Ye(n,e)&&r.push(`"${n}" is both in files and ignored by "${e}"`):(n===e||J(n,e))&&r.push(`"${n}" is both in files and ignored by "${e}"`)}if(r.length>0)throw Error(`Conflict between files and ignores:\n - ${r.join(`
30
+ - `)}`)}function nt(e,t){let n=[];for(let r of e)Y(r)?Xe(r,t).length===0&&n.push(`"${r}"`):t.has(r)||n.push(`"${r}"`);if(n.length>0)throw Error(`Ignored command paths do not exist: ${n.join(`, `)}`)}function rt(e,t){let n=new Set(e),r=t.filter(e=>n.has(e));for(let t of e)(t===``?0:t.split(` `).length)===1&&!r.includes(t)&&r.push(t);let i=[],a=new Set;function o(t){if(a.has(t)||!n.has(t))return;a.add(t),i.push(t);let r=e.filter(e=>e===t||a.has(e)?!1:t===``?e.split(` `).length===1:e.startsWith(t+` `)&&e.split(` `).length===t.split(` `).length+1).sort((e,t)=>e.localeCompare(t));for(let e of r)o(e)}for(let e of r)o(e);for(let t of e)a.has(t)||i.push(t);return i}function it(e){if(!e.title&&!e.description)return null;let t=[];if(e.title){let n=`#`.repeat(e.headingLevel??1);t.push(`${n} ${e.title}`)}return e.description&&(t.push(``),t.push(e.description)),t.push(``),t.join(`
31
+ `)}function at(e){if(!/^#{1,6} /.test(e))return null;let t=e.indexOf(`
32
+ `);if(t===-1)return e;let n=t+1;for(e[n]===`
33
+ `&&(n+=1);n<e.length;){let t=e.indexOf(`
34
+ `,n),r=t===-1?e.slice(n):e.slice(n,t);if(r.length===0||/^#{1,6}\s/.test(r)||r.startsWith(`<!-- politty:`))break;n=t===-1?e.length:t+1}return e.slice(0,n)}function ot(e,t,n){let r=it(t);if(!r||e.startsWith(r))return{content:e,hasError:!1,wasUpdated:!1};let i=at(e)??``;return n?{content:`${r}${(i?e.slice(i.length):e).replace(/^\n+/,``)}`,hasError:!1,wasUpdated:!0}:{content:e,diff:G(i,r),hasError:!0,wasUpdated:!1}}function Z(e){return e===``?`<root>`:e}function Q(e,t,n){return jt(e,k(t,n),A(t,n))}function st(e,t,n,r){return Mt(e,k(t,n),A(t,n),r)}function ct(e,t,n,r){let i=D.indexOf(t),a=k(t,n),o=A(t,n),s=null;for(let t=i-1;t>=0;t--){let r=D[t],i=A(r,n),a=e.indexOf(i);if(a!==-1){s=a+i.length;break}}let c=null;for(let t=i+1;t<D.length;t++){let r=D[t],i=k(r,n),a=e.indexOf(i);if(a!==-1){c=a;break}}if(s!=null&&c!=null){let t=a+e.slice(s,c).replace(/^\n+/,`
35
+ `).replace(/\n+$/,`
36
+ `)+o;return e.slice(0,s)+`
901
37
 
902
- //#endregion
903
- //#region ../core/src/docs/example-executor.ts
904
- /**
905
- * Execute examples for a command and capture output
906
- *
907
- * @param examples - Examples to execute
908
- * @param config - Execution configuration (mock setup/cleanup)
909
- * @param rootCommand - Root command to execute against
910
- * @param commandPath - Command path for subcommands (e.g., ["config", "get"])
911
- * @returns Array of execution results with captured stdout/stderr
912
- */
913
- async function executeExamples(examples, config, rootCommand, commandPath = []) {
914
- const results = [];
915
- if (config.mock) await config.mock();
916
- try {
917
- for (const example of examples) {
918
- const result = await executeSingleExample(example, rootCommand, commandPath);
919
- results.push(result);
920
- }
921
- } finally {
922
- if (config.cleanup) await config.cleanup();
923
- }
924
- return results;
925
- }
926
- /**
927
- * Execute a single example and capture output
928
- */
929
- async function executeSingleExample(example, rootCommand, commandPath) {
930
- const exampleArgs = parseExampleCmd(example.cmd);
931
- const argv = [...commandPath, ...exampleArgs];
932
- const collector = createLogCollector({ passthrough: false });
933
- collector.start();
934
- let success = true;
935
- try {
936
- const { runCommand } = await import("./runner-Cs-Mz9JN.js");
937
- const result = await runCommand(rootCommand, argv);
938
- success = result.success;
939
- if (!result.success && result.error) console.error(result.error.message);
940
- } catch (error) {
941
- success = false;
942
- console.error(error instanceof Error ? error.message : String(error));
943
- } finally {
944
- collector.stop();
945
- }
946
- const logs = collector.getLogs();
947
- const stdout = logs.entries.filter((e) => e.stream === "stdout").map((e) => e.message).join("\n");
948
- const stderr = logs.entries.filter((e) => e.stream === "stderr").map((e) => e.message).join("\n");
949
- return {
950
- cmd: example.cmd,
951
- desc: example.desc,
952
- expectedOutput: example.output,
953
- stdout,
954
- stderr,
955
- success
956
- };
957
- }
958
- /**
959
- * Parse example command string into argv array
960
- * Handles quoted strings (single and double quotes)
961
- *
962
- * @example
963
- * parseExampleCmd('World') // ['World']
964
- * parseExampleCmd('--name "John Doe"') // ['--name', 'John Doe']
965
- * parseExampleCmd("--greeting 'Hello World'") // ['--greeting', 'Hello World']
966
- */
967
- function parseExampleCmd(cmd) {
968
- const args = [];
969
- let current = "";
970
- let inQuote = false;
971
- let quoteChar = "";
972
- for (let i = 0; i < cmd.length; i++) {
973
- const char = cmd[i];
974
- if ((char === "\"" || char === "'") && !inQuote) {
975
- inQuote = true;
976
- quoteChar = char;
977
- } else if (char === quoteChar && inQuote) {
978
- inQuote = false;
979
- quoteChar = "";
980
- } else if (char === " " && !inQuote) {
981
- if (current) {
982
- args.push(current);
983
- current = "";
984
- }
985
- } else current += char;
986
- }
987
- if (current) args.push(current);
988
- return args;
989
- }
38
+ `+t+`
990
39
 
991
- //#endregion
992
- //#region ../core/src/docs/render-args.ts
993
- /**
994
- * Extract ResolvedFieldMeta array from ArgsShape
995
- *
996
- * This converts a raw args shape (like `commonArgs`) into the
997
- * ResolvedFieldMeta format used by politty's rendering functions.
998
- * Fields are resolved one by one — same result as wrapping the shape in
999
- * `z.object()` and extracting, without constructing a schema at runtime.
1000
- */
1001
- function extractArgsFields(args) {
1002
- const adapter = getValidatorAdapter();
1003
- return Object.entries(args).map(([name, fieldSchema]) => adapter.resolveFieldMeta(name, fieldSchema));
1004
- }
1005
- /**
1006
- * Render args definition as a markdown options table
1007
- *
1008
- * This function takes raw args definitions (like `commonArgs`) and
1009
- * renders them as a markdown table suitable for documentation.
1010
- *
1011
- * @example
1012
- * import { renderArgsTable } from "politty/docs";
1013
- * import { commonArgs, workspaceArgs } from "./args";
1014
- *
1015
- * const table = renderArgsTable({
1016
- * ...commonArgs,
1017
- * ...workspaceArgs,
1018
- * });
1019
- * // | Option | Alias | Description | Default |
1020
- * // |--------|-------|-------------|---------|
1021
- * // | `--env-file <ENV_FILE>` | `-e` | Path to environment file | - |
1022
- * // ...
1023
- *
1024
- * @param args - Args shape (record of field names to the CLI's schema-library schemas, with arg() metadata)
1025
- * @param options - Rendering options
1026
- * @returns Rendered markdown table string
1027
- */
1028
- function renderArgsTable(args, options) {
1029
- const optionFields = extractArgsFields(args).filter((f) => !f.positional);
1030
- if (optionFields.length === 0) return "";
1031
- return emitMarkdownTable(toOptionRows(optionFields), options?.columns);
1032
- }
40
+ `+e.slice(c)}if(s!=null){let t=s;for(;t<e.length&&e[t]===`
41
+ `;)t++;return e.slice(0,s)+`
1033
42
 
1034
- //#endregion
1035
- //#region ../core/src/docs/render-index.ts
1036
- /**
1037
- * Escape markdown special characters in table cells
1038
- */
1039
- function escapeTableCell(str) {
1040
- return str.replace(/\|/g, "\\|").replace(/\n/g, " ");
1041
- }
1042
- /**
1043
- * Generate anchor from command path
1044
- */
1045
- function generateAnchor(commandPath) {
1046
- return commandPath.replace(/\s+/g, "-").toLowerCase();
1047
- }
1048
- /**
1049
- * Check if a command is a leaf (has no subcommands)
1050
- */
1051
- function isLeafCommand(info) {
1052
- return info.subCommands.length === 0;
1053
- }
1054
- function isSubcommandOf$1(childPath, parentPath) {
1055
- if (childPath === parentPath) return true;
1056
- if (parentPath === "") return childPath !== "";
1057
- return childPath.startsWith(parentPath + " ");
1058
- }
1059
- /**
1060
- * Expand commands to include their subcommands
1061
- * If a command has subcommands, recursively find all commands under it
1062
- *
1063
- * @param commandPaths - Command paths to expand
1064
- * @param allCommands - Map of all available commands
1065
- * @param leafOnly - If true, only include leaf commands; if false, include all commands
1066
- */
1067
- function expandCommands(commandPaths, allCommands, leafOnly) {
1068
- const result = [];
1069
- for (const cmdPath of commandPaths) {
1070
- const info = allCommands.get(cmdPath);
1071
- if (!info) continue;
1072
- if (isLeafCommand(info)) result.push(cmdPath);
1073
- else for (const [path, pathInfo] of allCommands) if (cmdPath === "" ? path.length > 0 : path.startsWith(cmdPath + " ") || path === cmdPath) {
1074
- if (isLeafCommand(pathInfo) || !leafOnly) result.push(path);
1075
- }
1076
- }
1077
- return result;
1078
- }
1079
- /**
1080
- * Render a single category section
1081
- */
1082
- function renderCategory(category, allCommands, headingLevel, leafOnly) {
1083
- const h = "#".repeat(headingLevel);
1084
- const lines = [];
1085
- lines.push(`${h} [${category.title}](${category.docPath})`);
1086
- lines.push("");
1087
- lines.push(category.description);
1088
- lines.push("");
1089
- const commandPaths = category.noExpand ? category.commands : expandCommands(category.commands, allCommands, leafOnly);
1090
- let visibleCommandPaths = commandPaths;
1091
- const fallbackCommandPaths = /* @__PURE__ */ new Set();
1092
- if (category.allowedCommands) {
1093
- const allowed = new Set(category.allowedCommands);
1094
- visibleCommandPaths = commandPaths.filter((cmdPath) => allowed.has(cmdPath));
1095
- for (const configuredPath of category.commands) if (allowed.has(configuredPath) && !visibleCommandPaths.some((cmdPath) => isSubcommandOf$1(cmdPath, configuredPath))) {
1096
- visibleCommandPaths.push(configuredPath);
1097
- fallbackCommandPaths.add(configuredPath);
1098
- }
1099
- }
1100
- lines.push("| Command | Description |");
1101
- lines.push("|---------|-------------|");
1102
- for (const cmdPath of visibleCommandPaths) {
1103
- const info = allCommands.get(cmdPath);
1104
- if (!info) continue;
1105
- if (!category.noExpand && leafOnly && !fallbackCommandPaths.has(cmdPath) && !isLeafCommand(info)) continue;
1106
- const displayName = cmdPath || info.name;
1107
- const anchor = generateAnchor(displayName);
1108
- const desc = escapeTableCell(info.description ?? "");
1109
- lines.push(`| [${displayName}](${category.docPath}#${anchor}) | ${desc} |`);
1110
- }
1111
- return lines.join("\n");
1112
- }
1113
- /**
1114
- * Render command index from categories
1115
- *
1116
- * Generates a category-based index of commands with links to documentation.
1117
- *
1118
- * @example
1119
- * const categories: CommandCategory[] = [
1120
- * {
1121
- * title: "Application Commands",
1122
- * description: "Commands for managing applications.",
1123
- * commands: ["init", "generate", "apply"],
1124
- * docPath: "./cli/application.md",
1125
- * },
1126
- * ];
1127
- *
1128
- * const index = await renderCommandIndex(mainCommand, categories);
1129
- * // ### [Application Commands](./cli/application.md)
1130
- * //
1131
- * // Commands for managing applications.
1132
- * //
1133
- * // | Command | Description |
1134
- * // |---------|-------------|
1135
- * // | [init](./cli/application.md#init) | Initialize a project |
1136
- * // ...
1137
- *
1138
- * @param command - Root command to extract command information from
1139
- * @param categories - Category definitions for grouping commands
1140
- * @param options - Rendering options
1141
- * @returns Rendered markdown string
1142
- */
1143
- async function renderCommandIndex(command, categories, options) {
1144
- const headingLevel = options?.headingLevel ?? 3;
1145
- const leafOnly = options?.leafOnly ?? true;
1146
- const allCommands = await collectAllCommands(command);
1147
- const sections = [];
1148
- for (const category of categories) {
1149
- const section = renderCategory(category, allCommands, headingLevel, leafOnly);
1150
- sections.push(section);
1151
- }
1152
- return sections.join("\n\n");
1153
- }
43
+ `+r+(t<e.length?`
1154
44
 
1155
- //#endregion
1156
- //#region ../core/src/docs/golden-test.ts
1157
- /**
1158
- * Apply formatter to content if provided
1159
- * Supports both sync and async formatters
1160
- */
1161
- async function applyFormatter(content, formatter) {
1162
- if (!formatter) return content;
1163
- const formatted = await formatter(content);
1164
- if (!content.endsWith("\n") && formatted.endsWith("\n")) return formatted.slice(0, -1);
1165
- return formatted;
1166
- }
1167
- function isTruthyEnv(envKey) {
1168
- const value = process.env[envKey];
1169
- return value === "true" || value === "1";
1170
- }
1171
- function extractYamlFrontMatter(content) {
1172
- const lines = content.split(/\r?\n/);
1173
- if (lines[0] !== "---") return null;
1174
- const frontMatterLines = [];
1175
- for (let i = 1; i < lines.length; i++) {
1176
- const line = lines[i];
1177
- if (line === "---" || line === "...") return frontMatterLines.join("\n");
1178
- frontMatterLines.push(line ?? "");
1179
- }
1180
- return null;
1181
- }
1182
- function stripPolittyFrontMatterForOutput(content) {
1183
- const lineEnding = detectLineEnding(content);
1184
- const lines = content.split(/\r?\n/);
1185
- if (lines[0] !== "---") return content;
1186
- let endIndex = -1;
1187
- for (let i = 1; i < lines.length; i++) {
1188
- const line = lines[i];
1189
- if (line === "---" || line === "...") {
1190
- endIndex = i;
1191
- break;
1192
- }
1193
- }
1194
- if (endIndex === -1) return content;
1195
- const frontMatterLines = lines.slice(1, endIndex);
1196
- const keptFrontMatterLines = [];
1197
- for (let i = 0; i < frontMatterLines.length; i++) {
1198
- const line = frontMatterLines[i] ?? "";
1199
- if (!/^politty\s*:\s*(.*)$/.test(line)) {
1200
- keptFrontMatterLines.push(line);
1201
- continue;
1202
- }
1203
- while (i + 1 < frontMatterLines.length) {
1204
- const nextLine = frontMatterLines[i + 1] ?? "";
1205
- if (nextLine.trim() !== "" && !nextLine.startsWith(" ") && !nextLine.startsWith(" ")) break;
1206
- i++;
1207
- }
1208
- }
1209
- const bodyLines = lines.slice(endIndex + 1);
1210
- if (!keptFrontMatterLines.some((line) => line.trim() !== "")) return bodyLines.join(lineEnding).replace(new RegExp(`^${lineEnding}`), "");
1211
- return [
1212
- "---",
1213
- ...keptFrontMatterLines,
1214
- lines[endIndex] ?? "---",
1215
- ...bodyLines
1216
- ].join(lineEnding);
1217
- }
1218
- function stripYamlScalarQuotes(value) {
1219
- const trimmed = value.trim();
1220
- if (trimmed.length >= 2 && (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'"))) return trimmed.slice(1, -1);
1221
- return trimmed;
1222
- }
1223
- function normalizeTemplatePlaceholderKey(value) {
1224
- let normalized = stripYamlScalarQuotes(value);
1225
- if (normalized === "") return null;
1226
- const fullPlaceholder = normalized.match(/^\{\{politty:([^{}]*)\}\}$/);
1227
- if (fullPlaceholder) normalized = fullPlaceholder[1] ?? "";
1228
- else if (normalized.startsWith("politty:")) normalized = normalized.slice(8);
1229
- return normalized === "" ? null : normalized;
1230
- }
1231
- function templatePlaceholderKey(placeholder) {
1232
- return placeholder.slice(2, -2).slice(8);
1233
- }
1234
- function splitFrontMatterListValue(value) {
1235
- const trimmed = value.trim();
1236
- if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return [trimmed];
1237
- return trimmed.slice(1, -1).split(",").map((item) => item.trim()).filter((item) => item.length > 0);
1238
- }
1239
- function addTemplatePlaceholderExclusion(exclusions, value) {
1240
- const normalized = normalizeTemplatePlaceholderKey(value);
1241
- if (normalized !== null) exclusions.add(normalized);
1242
- }
1243
- function collectExcludedTemplatePlaceholders(templateContent) {
1244
- const exclusions = /* @__PURE__ */ new Set();
1245
- const frontMatter = extractYamlFrontMatter(templateContent);
1246
- if (frontMatter === null) return exclusions;
1247
- let inPolittyBlock = false;
1248
- let inExcludeList = false;
1249
- let excludeIndent = 0;
1250
- for (const line of frontMatter.split(/\r?\n/)) {
1251
- const trimmed = line.trim();
1252
- if (trimmed === "" || trimmed.startsWith("#")) continue;
1253
- const topLevelPolitty = line.match(/^politty\s*:\s*(.*)$/);
1254
- if (topLevelPolitty) {
1255
- inPolittyBlock = (topLevelPolitty[1]?.trim() ?? "") === "";
1256
- inExcludeList = false;
1257
- continue;
1258
- }
1259
- if (!line.startsWith(" ") && !line.startsWith(" ")) {
1260
- inPolittyBlock = false;
1261
- inExcludeList = false;
1262
- continue;
1263
- }
1264
- if (!inPolittyBlock) continue;
1265
- const excludeEntry = line.match(/^(\s+)(?:exclude|excludes)\s*:\s*(.*)$/);
1266
- if (excludeEntry) {
1267
- const value = excludeEntry[2]?.trim() ?? "";
1268
- if (value === "") {
1269
- inExcludeList = true;
1270
- excludeIndent = excludeEntry[1]?.length ?? 0;
1271
- } else {
1272
- inExcludeList = false;
1273
- for (const item of splitFrontMatterListValue(value)) addTemplatePlaceholderExclusion(exclusions, item);
1274
- }
1275
- continue;
1276
- }
1277
- if (!inExcludeList) continue;
1278
- const listItem = line.match(/^(\s*)-\s*(.+)$/);
1279
- if (!listItem || (listItem[1]?.length ?? 0) <= excludeIndent) {
1280
- inExcludeList = false;
1281
- continue;
1282
- }
1283
- addTemplatePlaceholderExclusion(exclusions, listItem[2] ?? "");
1284
- }
1285
- return exclusions;
1286
- }
1287
- function collectTemplateIndexMetadata(templateContent) {
1288
- const frontMatter = extractYamlFrontMatter(templateContent);
1289
- if (frontMatter === null) return {};
1290
- let inPolittyBlock = false;
1291
- let inIndexBlock = false;
1292
- let indexIndent = 0;
1293
- const metadata = {};
1294
- for (const line of frontMatter.split(/\r?\n/)) {
1295
- const trimmed = line.trim();
1296
- if (trimmed === "" || trimmed.startsWith("#")) continue;
1297
- const topLevelPolitty = line.match(/^politty\s*:\s*(.*)$/);
1298
- if (topLevelPolitty) {
1299
- inPolittyBlock = (topLevelPolitty[1]?.trim() ?? "") === "";
1300
- inIndexBlock = false;
1301
- continue;
1302
- }
1303
- if (!line.startsWith(" ") && !line.startsWith(" ")) {
1304
- inPolittyBlock = false;
1305
- inIndexBlock = false;
1306
- continue;
1307
- }
1308
- if (!inPolittyBlock) continue;
1309
- const indexEntry = line.match(/^(\s+)index\s*:\s*(.*)$/);
1310
- if (indexEntry) {
1311
- inIndexBlock = (indexEntry[2]?.trim() ?? "") === "";
1312
- indexIndent = indexEntry[1]?.length ?? 0;
1313
- continue;
1314
- }
1315
- if (!inIndexBlock) continue;
1316
- if ((line.match(/^(\s*)/)?.[1]?.length ?? 0) <= indexIndent) {
1317
- inIndexBlock = false;
1318
- continue;
1319
- }
1320
- const property = line.match(/^\s+(title|description)\s*:\s*(.+)$/);
1321
- if (!property) continue;
1322
- const key = property[1];
1323
- const value = stripYamlScalarQuotes(property[2] ?? "");
1324
- if (key === "title") metadata.title = value;
1325
- else if (key === "description") metadata.description = value;
1326
- }
1327
- return metadata;
1328
- }
1329
- function createTemplateExclusions(rawKeys) {
1330
- return {
1331
- rawKeys,
1332
- commandScopes: /* @__PURE__ */ new Set(),
1333
- commandSections: /* @__PURE__ */ new Map(),
1334
- globalOptions: false,
1335
- index: false
1336
- };
1337
- }
1338
- function setFileMapEntry(fileMap, commandPath, filePath) {
1339
- Object.defineProperty(fileMap, commandPath, {
1340
- value: filePath,
1341
- enumerable: true,
1342
- configurable: true,
1343
- writable: true
1344
- });
1345
- }
1346
- /**
1347
- * Normalize file mapping entry to FileConfig
1348
- */
1349
- function normalizeFileConfig(config) {
1350
- if (Array.isArray(config)) return { commands: config };
1351
- if (!("commands" in config) || !Array.isArray(config.commands)) throw new Error("Invalid file config: object form must include a \"commands\" array. Use [] to skip generation intentionally.");
1352
- return config;
1353
- }
1354
- /**
1355
- * Check if a command path is a subcommand of another
1356
- */
1357
- function isSubcommandOf(childPath, parentPath) {
1358
- if (parentPath === "") return true;
1359
- if (childPath === parentPath) return true;
1360
- return childPath.startsWith(parentPath + " ");
1361
- }
1362
- /**
1363
- * Check if a pattern contains wildcards
1364
- */
1365
- function containsWildcard(pattern) {
1366
- return pattern.includes("*");
1367
- }
1368
- /**
1369
- * Check if a command path matches a wildcard pattern
1370
- * - `*` matches any single command segment
1371
- * - Pattern segments are space-separated
1372
- *
1373
- * @example
1374
- * matchesWildcard("config get", "* *") // true
1375
- * matchesWildcard("config", "* *") // false
1376
- * matchesWildcard("config get", "config *") // true
1377
- * matchesWildcard("greet", "*") // true
1378
- */
1379
- function matchesWildcard(path, pattern) {
1380
- const pathSegments = path === "" ? [] : path.split(" ");
1381
- const patternSegments = pattern === "" ? [] : pattern.split(" ");
1382
- if (pathSegments.length !== patternSegments.length) return false;
1383
- for (let i = 0; i < patternSegments.length; i++) {
1384
- const patternSeg = patternSegments[i];
1385
- const pathSeg = pathSegments[i];
1386
- if (patternSeg !== "*" && patternSeg !== pathSeg) return false;
1387
- }
1388
- return true;
1389
- }
1390
- /**
1391
- * Expand a wildcard pattern to matching command paths
1392
- */
1393
- function expandWildcardPattern(pattern, allCommands) {
1394
- const matches = [];
1395
- for (const cmdPath of allCommands.keys()) if (matchesWildcard(cmdPath, pattern)) matches.push(cmdPath);
1396
- return matches;
1397
- }
1398
- /**
1399
- * Check if a path matches any ignore pattern (with wildcard support)
1400
- * For wildcard patterns, also ignores subcommands of matched commands
1401
- */
1402
- function matchesIgnorePattern(path, ignorePattern) {
1403
- if (containsWildcard(ignorePattern)) {
1404
- if (matchesWildcard(path, ignorePattern)) return true;
1405
- const pathSegments = path === "" ? [] : path.split(" ");
1406
- const patternSegments = ignorePattern === "" ? [] : ignorePattern.split(" ");
1407
- if (pathSegments.length > patternSegments.length) return matchesWildcard(pathSegments.slice(0, patternSegments.length).join(" "), ignorePattern);
1408
- return false;
1409
- }
1410
- return isSubcommandOf(path, ignorePattern);
1411
- }
1412
- /**
1413
- * Expand command paths to include all subcommands (with wildcard support)
1414
- */
1415
- function expandCommandPaths(commandPaths, allCommands) {
1416
- const expanded = /* @__PURE__ */ new Set();
1417
- const resolved = commandPaths.flatMap((cmdPath) => containsWildcard(cmdPath) ? expandWildcardPattern(cmdPath, allCommands) : [cmdPath]);
1418
- for (const cmdPath of resolved) for (const existingPath of allCommands.keys()) if (isSubcommandOf(existingPath, cmdPath)) expanded.add(existingPath);
1419
- return Array.from(expanded);
1420
- }
1421
- /**
1422
- * Filter out ignored commands (with wildcard support)
1423
- */
1424
- function filterIgnoredCommands(commandPaths, ignores) {
1425
- return commandPaths.filter((path) => {
1426
- return !ignores.some((ignorePattern) => matchesIgnorePattern(path, ignorePattern));
1427
- });
1428
- }
1429
- /**
1430
- * Resolve wildcards to direct matches without subcommand expansion.
1431
- * Returns the "top-level" commands for use in CommandCategory.commands,
1432
- * where expandCommands in render-index handles subcommand expansion.
1433
- */
1434
- function resolveTopLevelCommands(specifiedCommands, allCommands) {
1435
- const result = [];
1436
- for (const cmdPath of specifiedCommands) if (containsWildcard(cmdPath)) result.push(...expandWildcardPattern(cmdPath, allCommands));
1437
- else if (allCommands.has(cmdPath)) result.push(cmdPath);
1438
- return result;
1439
- }
1440
- /**
1441
- * Resolve file command configuration to concrete command paths.
1442
- * This applies wildcard/subcommand expansion and ignore filtering.
1443
- */
1444
- function resolveConfiguredCommandPaths(fileConfigRaw, allCommands, ignores) {
1445
- const fileConfig = normalizeFileConfig(fileConfigRaw);
1446
- const specifiedCommands = fileConfig.commands;
1447
- return {
1448
- fileConfig,
1449
- specifiedCommands,
1450
- commandPaths: filterIgnoredCommands(fileConfig.noExpand ? specifiedCommands.filter((p) => allCommands.has(p)) : expandCommandPaths(specifiedCommands, allCommands), ignores),
1451
- topLevelCommands: filterIgnoredCommands(resolveTopLevelCommands(specifiedCommands, allCommands), ignores)
1452
- };
1453
- }
1454
- /**
1455
- * Validate that there are no conflicts between files and ignores (with wildcard support)
1456
- */
1457
- function validateNoConflicts(filesCommands, ignores, allCommands) {
1458
- const conflicts = [];
1459
- for (const filePattern of filesCommands) {
1460
- const filePaths = containsWildcard(filePattern) ? expandWildcardPattern(filePattern, allCommands) : [filePattern];
1461
- for (const filePath of filePaths) for (const ignorePattern of ignores) if (containsWildcard(ignorePattern)) {
1462
- if (matchesWildcard(filePath, ignorePattern)) conflicts.push(`"${filePath}" is both in files and ignored by "${ignorePattern}"`);
1463
- } else if (filePath === ignorePattern || isSubcommandOf(filePath, ignorePattern)) conflicts.push(`"${filePath}" is both in files and ignored by "${ignorePattern}"`);
1464
- }
1465
- if (conflicts.length > 0) throw new Error(`Conflict between files and ignores:\n - ${conflicts.join("\n - ")}`);
1466
- }
1467
- /**
1468
- * Validate that all ignored paths exist in the command tree (with wildcard support)
1469
- */
1470
- function validateIgnoresExist(ignores, allCommands) {
1471
- const nonExistent = [];
1472
- for (const ignorePattern of ignores) if (containsWildcard(ignorePattern)) {
1473
- if (expandWildcardPattern(ignorePattern, allCommands).length === 0) nonExistent.push(`"${ignorePattern}"`);
1474
- } else if (!allCommands.has(ignorePattern)) nonExistent.push(`"${ignorePattern}"`);
1475
- if (nonExistent.length > 0) throw new Error(`Ignored command paths do not exist: ${nonExistent.join(", ")}`);
1476
- }
1477
- /**
1478
- * Sort command paths in depth-first order while preserving the specified command order
1479
- * Parent commands are immediately followed by their subcommands
1480
- */
1481
- function sortDepthFirst(commandPaths, specifiedOrder) {
1482
- const pathSet = new Set(commandPaths);
1483
- const topLevelPaths = specifiedOrder.filter((cmd) => pathSet.has(cmd));
1484
- for (const path of commandPaths) if ((path === "" ? 0 : path.split(" ").length) === 1 && !topLevelPaths.includes(path)) topLevelPaths.push(path);
1485
- const result = [];
1486
- const visited = /* @__PURE__ */ new Set();
1487
- function addWithChildren(cmdPath) {
1488
- if (visited.has(cmdPath) || !pathSet.has(cmdPath)) return;
1489
- visited.add(cmdPath);
1490
- result.push(cmdPath);
1491
- const children = commandPaths.filter((p) => {
1492
- if (p === cmdPath || visited.has(p)) return false;
1493
- if (cmdPath === "") return p.split(" ").length === 1;
1494
- return p.startsWith(cmdPath + " ") && p.split(" ").length === cmdPath.split(" ").length + 1;
1495
- }).sort((a, b) => a.localeCompare(b));
1496
- for (const child of children) addWithChildren(child);
1497
- }
1498
- for (const topLevel of topLevelPaths) addWithChildren(topLevel);
1499
- for (const path of commandPaths) if (!visited.has(path)) result.push(path);
1500
- return result;
1501
- }
1502
- function generateFileHeader(fileConfig) {
1503
- if (!fileConfig.title && !fileConfig.description) return null;
1504
- const parts = [];
1505
- if (fileConfig.title) {
1506
- const heading = "#".repeat(fileConfig.headingLevel ?? 1);
1507
- parts.push(`${heading} ${fileConfig.title}`);
1508
- }
1509
- if (fileConfig.description) {
1510
- parts.push("");
1511
- parts.push(fileConfig.description);
1512
- }
1513
- parts.push("");
1514
- return parts.join("\n");
1515
- }
1516
- /**
1517
- * Extract a leading file header (title and optional description paragraph)
1518
- */
1519
- function extractFileHeader(content) {
1520
- if (!/^#{1,6} /.test(content)) return null;
1521
- const titleEnd = content.indexOf("\n");
1522
- if (titleEnd === -1) return content;
1523
- let cursor = titleEnd + 1;
1524
- if (content[cursor] === "\n") cursor += 1;
1525
- while (cursor < content.length) {
1526
- const lineEnd = content.indexOf("\n", cursor);
1527
- const line = lineEnd === -1 ? content.slice(cursor) : content.slice(cursor, lineEnd);
1528
- if (line.length === 0 || /^#{1,6}\s/.test(line) || line.startsWith("<!-- politty:")) break;
1529
- cursor = lineEnd === -1 ? content.length : lineEnd + 1;
1530
- }
1531
- return content.slice(0, cursor);
1532
- }
1533
- /**
1534
- * Validate and optionally update configured file header
1535
- */
1536
- function processFileHeader(existingContent, fileConfig, updateMode) {
1537
- const generatedHeader = generateFileHeader(fileConfig);
1538
- if (!generatedHeader) return {
1539
- content: existingContent,
1540
- hasError: false,
1541
- wasUpdated: false
1542
- };
1543
- if (existingContent.startsWith(generatedHeader)) return {
1544
- content: existingContent,
1545
- hasError: false,
1546
- wasUpdated: false
1547
- };
1548
- const existingHeader = extractFileHeader(existingContent) ?? "";
1549
- if (!updateMode) return {
1550
- content: existingContent,
1551
- diff: formatDiff(existingHeader, generatedHeader),
1552
- hasError: true,
1553
- wasUpdated: false
1554
- };
1555
- return {
1556
- content: `${generatedHeader}${(existingHeader ? existingContent.slice(existingHeader.length) : existingContent).replace(/^\n+/, "")}`,
1557
- hasError: false,
1558
- wasUpdated: true
1559
- };
1560
- }
1561
- function formatCommandPath(commandPath) {
1562
- return commandPath === "" ? "<root>" : commandPath;
1563
- }
1564
- /**
1565
- * Extract a section marker's content from document content.
1566
- * Returns the content between start and end markers (including markers).
1567
- */
1568
- function extractSectionMarker(content, type, scope) {
1569
- return extractMarkerSection(content, sectionStartMarker(type, scope), sectionEndMarker(type, scope));
1570
- }
1571
- /**
1572
- * Replace a section marker's content in document content.
1573
- * Returns updated content, or null if marker not found.
1574
- */
1575
- function replaceSectionMarker(content, type, scope, newContent) {
1576
- return replaceMarkerSection(content, sectionStartMarker(type, scope), sectionEndMarker(type, scope), newContent);
1577
- }
1578
- /**
1579
- * Insert a new section marker into existing content at the correct position
1580
- * relative to other section markers for the same command, based on SECTION_TYPES order.
1581
- * Preserves any existing content between adjacent markers by wrapping it with the new markers
1582
- * instead of replacing it with generated content.
1583
- * @throws If no adjacent marker is found (unreachable when at least one marker exists for the command)
1584
- */
1585
- function insertSectionMarkerAtOrder(content, type, scope, generatedSection) {
1586
- const typeIndex = SECTION_TYPES.indexOf(type);
1587
- const startMarker = sectionStartMarker(type, scope);
1588
- const endMarker = sectionEndMarker(type, scope);
1589
- let prevBoundary = null;
1590
- for (let i = typeIndex - 1; i >= 0; i--) {
1591
- const prevType = SECTION_TYPES[i];
1592
- const prevEnd = sectionEndMarker(prevType, scope);
1593
- const prevEndIdx = content.indexOf(prevEnd);
1594
- if (prevEndIdx !== -1) {
1595
- prevBoundary = prevEndIdx + prevEnd.length;
1596
- break;
1597
- }
1598
- }
1599
- let nextBoundary = null;
1600
- for (let i = typeIndex + 1; i < SECTION_TYPES.length; i++) {
1601
- const nextType = SECTION_TYPES[i];
1602
- const nextStart = sectionStartMarker(nextType, scope);
1603
- const nextStartIdx = content.indexOf(nextStart);
1604
- if (nextStartIdx !== -1) {
1605
- nextBoundary = nextStartIdx;
1606
- break;
1607
- }
1608
- }
1609
- if (prevBoundary != null && nextBoundary != null) {
1610
- const wrapped = startMarker + content.slice(prevBoundary, nextBoundary).replace(/^\n+/, "\n").replace(/\n+$/, "\n") + endMarker;
1611
- return content.slice(0, prevBoundary) + "\n\n" + wrapped + "\n\n" + content.slice(nextBoundary);
1612
- }
1613
- if (prevBoundary != null) {
1614
- let afterPos = prevBoundary;
1615
- while (afterPos < content.length && content[afterPos] === "\n") afterPos++;
1616
- return content.slice(0, prevBoundary) + "\n\n" + generatedSection + (afterPos < content.length ? "\n\n" : "\n") + content.slice(afterPos);
1617
- }
1618
- if (nextBoundary != null) {
1619
- let beforePos = nextBoundary;
1620
- while (beforePos > 0 && content[beforePos - 1] === "\n") beforePos--;
1621
- const prefix = beforePos === 0 ? "" : "\n\n";
1622
- return content.slice(0, beforePos) + prefix + generatedSection + "\n\n" + content.slice(nextBoundary);
1623
- }
1624
- throw new Error(`No insertion point found for section "${type}" (scope="${scope}"). This should be unreachable when at least one marker exists for the command.`);
1625
- }
1626
- /**
1627
- * Collect all section types that have markers for a given command path.
1628
- */
1629
- function collectSectionMarkers(content, commandPath) {
1630
- const found = [];
1631
- for (const type of SECTION_TYPES) if (extractSectionMarker(content, type, commandPath) !== null) found.push(type);
1632
- return found;
1633
- }
1634
- /**
1635
- * Collect all command paths that have any section markers in the content.
1636
- */
1637
- function collectSectionMarkerPaths(content) {
1638
- const sectionTypes = SECTION_TYPES.join("|");
1639
- const markerPattern = new RegExp(`<!--\\s*politty:command:(.*?):(?:${sectionTypes}):start\\s*-->`, "g");
1640
- const paths = /* @__PURE__ */ new Set();
1641
- for (const match of content.matchAll(markerPattern)) paths.add(match[1] ?? "");
1642
- return Array.from(paths);
1643
- }
1644
- /**
1645
- * Insert command section markers at the correct position based on specified order.
1646
- * Uses the heading marker of adjacent commands as reference points.
1647
- */
1648
- function insertCommandSections(content, commandPath, newSection, specifiedOrder) {
1649
- const targetIndex = specifiedOrder.indexOf(commandPath);
1650
- if (targetIndex === -1) return content.trimEnd() + "\n\n" + newSection + "\n";
1651
- for (let i = targetIndex + 1; i < specifiedOrder.length; i++) {
1652
- const nextCmd = specifiedOrder[i];
1653
- if (nextCmd === void 0) continue;
1654
- const nextMarker = sectionStartMarker("heading", nextCmd);
1655
- const nextIndex = content.indexOf(nextMarker);
1656
- if (nextIndex !== -1) {
1657
- let insertPos = nextIndex;
1658
- while (insertPos > 0 && content[insertPos - 1] === "\n") insertPos--;
1659
- if (insertPos < nextIndex) insertPos++;
1660
- return content.slice(0, insertPos) + newSection + "\n" + content.slice(nextIndex);
1661
- }
1662
- }
1663
- for (let i = targetIndex - 1; i >= 0; i--) {
1664
- const prevCmd = specifiedOrder[i];
1665
- if (prevCmd === void 0) continue;
1666
- const prevMarkers = collectSectionMarkers(content, prevCmd);
1667
- if (prevMarkers.length > 0) {
1668
- const lastType = prevMarkers[prevMarkers.length - 1];
1669
- const prevEndMarker = sectionEndMarker(lastType, prevCmd);
1670
- const prevEndIndex = content.indexOf(prevEndMarker);
1671
- if (prevEndIndex !== -1) {
1672
- const insertPos = prevEndIndex + prevEndMarker.length;
1673
- return content.slice(0, insertPos) + "\n" + newSection + content.slice(insertPos);
1674
- }
1675
- }
1676
- }
1677
- return content.trimEnd() + "\n" + newSection + "\n";
1678
- }
1679
- /**
1680
- * Remove all section markers for a command from content.
1681
- * Returns the content with all markers for the command removed and excess blank lines cleaned up.
1682
- */
1683
- function removeCommandSections(content, commandPath) {
1684
- const markers = collectSectionMarkers(content, commandPath);
1685
- for (const type of markers) {
1686
- const start = sectionStartMarker(type, commandPath);
1687
- const end = sectionEndMarker(type, commandPath);
1688
- let startIndex = content.indexOf(start);
1689
- while (startIndex !== -1) {
1690
- const endIndex = content.indexOf(end, startIndex);
1691
- if (endIndex === -1) break;
1692
- content = content.slice(0, startIndex) + content.slice(endIndex + end.length);
1693
- startIndex = content.indexOf(start, startIndex);
1694
- }
1695
- }
1696
- content = content.replace(/\n{3,}/g, "\n\n");
1697
- return content;
1698
- }
1699
- /**
1700
- * Strip politty marker lines from content, then collapse the blank-line gaps the removed markers
1701
- * leave behind (outside fenced code blocks only, so intentional blank lines inside generated
1702
- * example/code blocks are preserved) and trim leading/trailing blank lines.
1703
- */
1704
- function stripPolittyMarkers(content) {
1705
- let result = collapseBlankLinesOutsideCodeFences(content.split("\n").filter((line) => !/^<!-- politty:.*-->$/.test(line.trim())).join("\n"));
1706
- result = result.replace(/^\n+/, "").replace(/\n+$/, "");
1707
- return result;
1708
- }
1709
- /**
1710
- * Collapse runs of 3+ newlines to 2, but only outside fenced code blocks so that intentional
1711
- * blank lines inside handwritten code samples are preserved. Fences are lines whose trimmed
1712
- * content starts with ``` or ~~~.
1713
- */
1714
- function collapseBlankLinesOutsideCodeFences(content) {
1715
- const lines = content.split("\n");
1716
- const out = [];
1717
- let inFence = false;
1718
- let blankRun = 0;
1719
- for (const line of lines) {
1720
- const trimmed = line.trim();
1721
- if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) {
1722
- inFence = !inFence;
1723
- blankRun = 0;
1724
- out.push(line);
1725
- continue;
1726
- }
1727
- if (!inFence && line.trim() === "") {
1728
- blankRun++;
1729
- if (blankRun >= 2) continue;
1730
- } else if (!inFence) blankRun = 0;
1731
- out.push(line);
1732
- }
1733
- return out.join("\n");
1734
- }
1735
- function detectLineEnding(content) {
1736
- return content.includes("\r\n") ? "\r\n" : "\n";
1737
- }
1738
- function countLineBreaks(value) {
1739
- return (value.match(/\n/g) ?? []).length;
1740
- }
1741
- /**
1742
- * Type guard for SectionType values parsed from template placeholders.
1743
- */
1744
- function isSectionType(value) {
1745
- return SECTION_TYPES.some((type) => type === value);
1746
- }
1747
- /**
1748
- * Clamp a numeric heading level to the valid HeadingLevel range (1–6).
1749
- * Uses a switch to return a literal union member, avoiding `as` assertions.
1750
- */
1751
- function clampHeadingLevel(level) {
1752
- switch (Math.min(6, Math.max(1, Math.trunc(level)))) {
1753
- case 1: return 1;
1754
- case 2: return 2;
1755
- case 3: return 3;
1756
- case 4: return 4;
1757
- case 5: return 5;
1758
- default: return 6;
1759
- }
1760
- }
1761
- function resolveTemplateCommandScope(tokens, allCommands) {
1762
- if (tokens.length === 0) return allCommands === void 0 || allCommands.has("") ? "" : null;
1763
- const exactScope = tokens.join(":");
1764
- if (allCommands?.has(exactScope)) return exactScope;
1765
- const colonSeparatedScope = tokens.join(" ");
1766
- if (allCommands?.has(colonSeparatedScope)) return colonSeparatedScope;
1767
- return allCommands === void 0 ? colonSeparatedScope : null;
1768
- }
1769
- function templateScopeFallback(tokens) {
1770
- return tokens.join(" ");
1771
- }
1772
- /**
1773
- * Parse a single {{politty:...}} placeholder string into a discriminated structure.
1774
- * The `placeholder` argument should be the full `{{politty:...}}` text.
1775
- *
1776
- * Uses String.match / String.replace internally (not .exec) to avoid lastIndex
1777
- * state issues from the shared TEMPLATE_PLACEHOLDER_REGEX constant.
1778
- */
1779
- function parsePlaceholder(placeholder, allCommands) {
1780
- const tokens = placeholder.slice(2, -2).split(":");
1781
- const directive = tokens[1];
1782
- if (directive === "command") {
1783
- const rest = tokens.slice(2);
1784
- if (rest.length === 1 && rest[0] === "") return {
1785
- kind: "invalid",
1786
- reason: `Trailing colon in "${placeholder}"; use {{politty:command}} for the root command.`
1787
- };
1788
- const fullScope = resolveTemplateCommandScope(rest, allCommands);
1789
- if (fullScope !== null) return {
1790
- kind: "command",
1791
- scope: fullScope,
1792
- type: void 0
1793
- };
1794
- if (rest.length >= 2) {
1795
- const last = rest[rest.length - 1];
1796
- const scopeTokens = rest.slice(0, -1);
1797
- const sectionScope = resolveTemplateCommandScope(scopeTokens, allCommands);
1798
- if (last !== void 0 && isSectionType(last)) return {
1799
- kind: "command",
1800
- scope: sectionScope ?? templateScopeFallback(scopeTokens),
1801
- type: last
1802
- };
1803
- if (last !== void 0 && sectionScope !== null) return {
1804
- kind: "invalid",
1805
- reason: `Unknown section type "${last}" for command scope "${formatCommandPath(sectionScope)}". Valid section types: ${SECTION_TYPES.join(", ")}`
1806
- };
1807
- }
1808
- return {
1809
- kind: "command",
1810
- scope: templateScopeFallback(rest),
1811
- type: void 0
1812
- };
1813
- }
1814
- if (directive === "global-options") {
1815
- if (tokens.length !== 2) return {
1816
- kind: "invalid",
1817
- reason: `Malformed placeholder "${placeholder}". Expected {{politty:global-options}}.`
1818
- };
1819
- return { kind: "global-options" };
1820
- }
1821
- if (directive === "index") {
1822
- if (tokens.length !== 2) return {
1823
- kind: "invalid",
1824
- reason: `Malformed placeholder "${placeholder}". Expected {{politty:index}}.`
1825
- };
1826
- return { kind: "index" };
1827
- }
1828
- return {
1829
- kind: "invalid",
1830
- reason: `Unknown politty directive "${directive ?? ""}" in "${placeholder}". Valid directives: command, global-options, index`
1831
- };
1832
- }
1833
- function buildTemplateExclusions(rawKeys, allCommands) {
1834
- const exclusions = createTemplateExclusions(rawKeys);
1835
- for (const key of rawKeys) {
1836
- const parsed = parsePlaceholder(`{{politty:${key}}}`, allCommands);
1837
- if (parsed.kind === "command") {
1838
- if (parsed.type === void 0) exclusions.commandScopes.add(parsed.scope);
1839
- else {
1840
- let sections = exclusions.commandSections.get(parsed.scope);
1841
- if (!sections) {
1842
- sections = /* @__PURE__ */ new Set();
1843
- exclusions.commandSections.set(parsed.scope, sections);
1844
- }
1845
- sections.add(parsed.type);
1846
- }
1847
- } else if (parsed.kind === "global-options") exclusions.globalOptions = true;
1848
- else if (parsed.kind === "index") exclusions.index = true;
1849
- }
1850
- return exclusions;
1851
- }
1852
- function isCommandScopeExcluded(commandPath, excludedCommandScopes) {
1853
- for (const excludedScope of excludedCommandScopes) if (isSubcommandOf(commandPath, excludedScope)) return true;
1854
- return false;
1855
- }
1856
- function isCommandSectionExcluded(commandPath, sectionType, exclusions) {
1857
- if (isCommandScopeExcluded(commandPath, exclusions.commandScopes)) return true;
1858
- return exclusions.commandSections.get(commandPath)?.has(sectionType) ?? false;
1859
- }
1860
- function getTemplateCommandTreePaths(commandPath, allCommands, ignores, exclusions) {
1861
- return sortDepthFirst(filterIgnoredCommands(expandCommandPaths([commandPath], allCommands), ignores).filter((path) => !isCommandScopeExcluded(path, exclusions.commandScopes)), [commandPath]);
1862
- }
1863
- function shouldSkipTemplatePlaceholder(placeholder, parsed, exclusions) {
1864
- if (exclusions.rawKeys.has(templatePlaceholderKey(placeholder))) return true;
1865
- if (parsed.kind === "command") {
1866
- if (isCommandScopeExcluded(parsed.scope, exclusions.commandScopes)) return true;
1867
- return parsed.type !== void 0 && (exclusions.commandSections.get(parsed.scope)?.has(parsed.type) ?? false);
1868
- }
1869
- if (parsed.kind === "global-options") return exclusions.globalOptions;
1870
- if (parsed.kind === "index") return exclusions.index;
1871
- return false;
1872
- }
1873
- function isRawCommandPlaceholderUnderExcludedScope(key, exclusions) {
1874
- if (!key.startsWith("command:")) return false;
1875
- const tokens = key.slice(8).split(":");
1876
- for (const excludedScope of exclusions.commandScopes) {
1877
- if (excludedScope === "") return true;
1878
- const spaceTokens = excludedScope.split(" ");
1879
- if (tokens.slice(0, spaceTokens.length).join(" ") === excludedScope) return true;
1880
- const colonTokens = excludedScope.split(":");
1881
- if (tokens.slice(0, colonTokens.length).join(":") === excludedScope) return true;
1882
- }
1883
- return false;
1884
- }
1885
- /**
1886
- * Regex matching {{politty:...}} placeholders.
1887
- * NOTE: only use with String.match / String.replace, never with .exec in a loop,
1888
- * because the /g flag makes the regex stateful via lastIndex.
1889
- */
1890
- const TEMPLATE_PLACEHOLDER_REGEX = /\{\{politty:[^{}]*\}\}/g;
1891
- function validateTemplatePlaceholderSyntax(templateContent, templatePath) {
1892
- const validPlaceholderStarts = /* @__PURE__ */ new Set();
1893
- for (const match of templateContent.matchAll(TEMPLATE_PLACEHOLDER_REGEX)) {
1894
- const start = match.index;
1895
- const end = start + match[0].length;
1896
- if (templateContent[start - 1] === "{" || templateContent[end] === "}") {
1897
- const snippet = templateContent.slice(Math.max(0, start - 1), Math.min(templateContent.length, end + 1)).split("\n")[0];
1898
- throw new Error(`Malformed politty placeholder in template "${templatePath}": "${snippet}". Expected {{politty:...}}.`);
1899
- }
1900
- validPlaceholderStarts.add(start);
1901
- }
1902
- let searchIndex = 0;
1903
- while (true) {
1904
- const placeholderStart = templateContent.indexOf("{{politty:", searchIndex);
1905
- if (placeholderStart === -1) return;
1906
- if (!validPlaceholderStarts.has(placeholderStart)) {
1907
- const snippet = templateContent.slice(placeholderStart, placeholderStart + 80).split("\n")[0];
1908
- throw new Error(`Malformed politty placeholder in template "${templatePath}": "${snippet}". Expected {{politty:...}}.`);
1909
- }
1910
- searchIndex = placeholderStart + 10;
1911
- }
1912
- }
1913
- function getUnknownSectionTypeError(scope, allCommands) {
1914
- const separatorIndex = scope.lastIndexOf(":");
1915
- if (separatorIndex === -1) return null;
1916
- const commandScope = scope.slice(0, separatorIndex);
1917
- const sectionType = scope.slice(separatorIndex + 1);
1918
- if (sectionType === "" || !allCommands.has(commandScope)) return null;
1919
- return `Unknown section type "${sectionType}" for command scope "${formatCommandPath(commandScope)}". Valid section types: ${SECTION_TYPES.join(", ")}`;
1920
- }
1921
- /**
1922
- * Extract a marker section from content
1923
- * Returns the content between start and end markers (including markers)
1924
- */
1925
- function extractMarkerSection(content, startMarker, endMarker) {
1926
- const startIndex = content.indexOf(startMarker);
1927
- if (startIndex === -1) return null;
1928
- const endIndex = content.indexOf(endMarker, startIndex);
1929
- if (endIndex === -1) return null;
1930
- return content.slice(startIndex, endIndex + endMarker.length);
1931
- }
1932
- /**
1933
- * Replace a marker section in content
1934
- * Returns the updated content with the new section
1935
- */
1936
- function replaceMarkerSection(content, startMarker, endMarker, newSection) {
1937
- const startIndex = content.indexOf(startMarker);
1938
- if (startIndex === -1) return null;
1939
- const endIndex = content.indexOf(endMarker, startIndex);
1940
- if (endIndex === -1) return null;
1941
- return content.slice(0, startIndex) + newSection + content.slice(endIndex + endMarker.length);
1942
- }
1943
- /**
1944
- * Whether a value is a Standard Schema, judged by the shape of its
1945
- * `~standard` marker rather than the key alone.
1946
- *
1947
- * `vendor` and `validate` are what the check relies on; `version` is
1948
- * deliberately not required, so a future spec revision bumping it does not
1949
- * make politty stop recognizing schemas.
1950
- */
1951
- function isStandardSchema(value) {
1952
- if (typeof value !== "object" || value === null) return false;
1953
- const marker = value["~standard"];
1954
- if (typeof marker !== "object" || marker === null) return false;
1955
- const { vendor, validate } = marker;
1956
- return typeof vendor === "string" && typeof validate === "function";
1957
- }
1958
- /**
1959
- * Check if config is the { args, options? } shape (not shorthand ArgsShape)
1960
- *
1961
- * Distinguishes between:
1962
- * - { args: ArgsShape, options?: ArgsTableOptions } → returns true
1963
- * - ArgsShape (e.g., { verbose: <schema>, args: <schema> }) → returns false
1964
- *
1965
- * The key insight is that in the { args, options? } shape, config.args is an
1966
- * ArgsShape (a record of the CLI's schema-library schemas), while in shorthand
1967
- * config itself is the ArgsShape and config.args would be a single schema if
1968
- * the user has an option named "args".
1969
- */
1970
- function isGlobalOptionsConfigWithOptions(config) {
1971
- if (typeof config !== "object" || config === null || !("args" in config)) return false;
1972
- return !isStandardSchema(config.args);
1973
- }
1974
- /**
1975
- * Collect option fields that are actually rendered by global options markers.
1976
- * Positional args are not rendered in args tables, so they must not be excluded.
1977
- */
1978
- function collectRenderableGlobalOptionFields(argsShape) {
1979
- const adapter = getValidatorAdapter();
1980
- return Object.entries(argsShape).map(([name, fieldSchema]) => adapter.resolveFieldMeta(name, fieldSchema)).filter((field) => !field.positional);
1981
- }
1982
- /**
1983
- * Compare option definitions for global-options compatibility.
1984
- */
1985
- function areGlobalOptionsEquivalent(a, b) {
1986
- const { schema: _aSchema, ...aRest } = a;
1987
- const { schema: _bSchema, ...bRest } = b;
1988
- return isDeepStrictEqual(aRest, bRest);
1989
- }
1990
- /**
1991
- * Normalize rootDoc.globalOptions to { args, options? } form.
1992
- */
1993
- function normalizeGlobalOptions(config) {
1994
- if (!config) return void 0;
1995
- return isGlobalOptionsConfigWithOptions(config) ? config : { args: config };
1996
- }
1997
- /**
1998
- * Derive an ArgsShape from a globalArgs schema, retaining only non-positional option fields.
1999
- * Returns undefined when globalArgs is undefined or contains no option fields.
2000
- * Used to build globalOptionDefinitions from globalArgs when rootDoc is not available.
2001
- */
2002
- function deriveGlobalArgsShape(globalArgs) {
2003
- if (!globalArgs) return void 0;
2004
- const optionFields = extractFields(globalArgs).fields.filter((f) => !f.positional);
2005
- if (optionFields.length === 0) return void 0;
2006
- return Object.fromEntries(optionFields.map((f) => [f.name, f.schema]));
2007
- }
2008
- /**
2009
- * Collect global option definitions from rootDoc.
2010
- * Global options are intentionally applied to all generated command sections.
2011
- */
2012
- function collectGlobalOptionDefinitions(rootDoc) {
2013
- const globalOptions = /* @__PURE__ */ new Map();
2014
- if (!rootDoc?.globalOptions) return globalOptions;
2015
- const normalized = normalizeGlobalOptions(rootDoc.globalOptions);
2016
- if (!normalized) return globalOptions;
2017
- for (const field of collectRenderableGlobalOptionFields(normalized.args)) globalOptions.set(field.name, field);
2018
- return globalOptions;
2019
- }
2020
- /**
2021
- * Derive CommandCategory[] from files mapping.
2022
- * Category title/description come from the first command in each file entry.
2023
- */
2024
- function deriveIndexFromFiles(files, rootDocPath, allCommands, ignores) {
2025
- const categories = [];
2026
- for (const [filePath, fileConfigRaw] of Object.entries(files)) {
2027
- const { commandPaths, topLevelCommands } = resolveConfiguredCommandPaths(fileConfigRaw, allCommands, ignores);
2028
- if (commandPaths.length === 0) continue;
2029
- const docPath = "./" + path$1.relative(path$1.dirname(rootDocPath), filePath).replace(/\\/g, "/");
2030
- const firstCmdPath = commandPaths[0];
2031
- const cmdInfo = firstCmdPath !== void 0 ? allCommands.get(firstCmdPath) : void 0;
2032
- const fileConfig = Array.isArray(fileConfigRaw) ? void 0 : fileConfigRaw;
2033
- categories.push({
2034
- title: fileConfig?.title ?? cmdInfo?.name ?? path$1.basename(filePath, path$1.extname(filePath)),
2035
- description: fileConfig?.description ?? cmdInfo?.description ?? "",
2036
- commands: topLevelCommands,
2037
- allowedCommands: commandPaths,
2038
- docPath
2039
- });
2040
- }
2041
- return categories;
2042
- }
2043
- /**
2044
- * Build index categories for the {{politty:index}} placeholder from other template outputs.
2045
- * Each category lists exactly the heading-producing scopes of that output (noExpand), so the
2046
- * index never links to commands that template mode did not render.
2047
- */
2048
- function deriveIndexFromTemplateOutputs(templateMeta, currentOutputPath, indexFilePath, allCommands) {
2049
- const normalizedCurrent = normalizeDocPathForComparison(currentOutputPath);
2050
- const categories = [];
2051
- for (const [outputPath, meta] of templateMeta.entries()) {
2052
- if (normalizeDocPathForComparison(outputPath) === normalizedCurrent) continue;
2053
- const scopes = meta.headingScopes;
2054
- if (scopes.length === 0) continue;
2055
- const docPath = "./" + path$1.relative(path$1.dirname(indexFilePath), outputPath).replace(/\\/g, "/");
2056
- const firstScope = scopes[0];
2057
- const cmdInfo = firstScope !== void 0 ? allCommands.get(firstScope) : void 0;
2058
- categories.push({
2059
- title: meta.indexTitle ?? cmdInfo?.name ?? path$1.basename(outputPath, path$1.extname(outputPath)),
2060
- description: meta.indexDescription ?? cmdInfo?.description ?? "",
2061
- commands: scopes,
2062
- docPath,
2063
- noExpand: true
2064
- });
2065
- }
2066
- return categories;
2067
- }
2068
- /**
2069
- * Collect command paths that are actually documented in configured files.
2070
- */
2071
- function collectDocumentedCommandPaths(files, allCommands, ignores) {
2072
- const documentedCommandPaths = /* @__PURE__ */ new Set();
2073
- for (const fileConfigRaw of Object.values(files)) {
2074
- const { commandPaths } = resolveConfiguredCommandPaths(fileConfigRaw, allCommands, ignores);
2075
- for (const commandPath of commandPaths) documentedCommandPaths.add(commandPath);
2076
- }
2077
- return documentedCommandPaths;
2078
- }
2079
- /**
2080
- * Collect command paths that are targeted in configured files.
2081
- */
2082
- function collectTargetDocumentedCommandPaths(targetCommands, files, allCommands, ignores) {
2083
- const documentedTargetCommandPaths = /* @__PURE__ */ new Set();
2084
- for (const filePath of Object.keys(files)) {
2085
- const targetCommandsInFile = findTargetCommandsInFile(targetCommands, filePath, files, allCommands, ignores);
2086
- for (const commandPath of targetCommandsInFile) documentedTargetCommandPaths.add(commandPath);
2087
- }
2088
- return documentedTargetCommandPaths;
2089
- }
2090
- function commandPathMatchesTarget(commandPath, targetCommands) {
2091
- return targetCommands.some((targetCommand) => isSubcommandOf(commandPath, targetCommand));
2092
- }
2093
- function templateMetaReferencesCommandTarget(meta, targetCommands) {
2094
- return meta.referencedScopes.some((scope) => commandPathMatchesTarget(scope, targetCommands));
2095
- }
2096
- function templateMetaShouldProcessForTarget(meta, targetCommands) {
2097
- return meta.emitsIndex || meta.emitsGlobalOptions || templateMetaReferencesCommandTarget(meta, targetCommands);
2098
- }
2099
- /**
2100
- * Validate that excluded command options match globalOptions definitions.
2101
- */
2102
- function validateGlobalOptionCompatibility(documentedCommandPaths, allCommands, globalOptions) {
2103
- if (globalOptions.size === 0) return;
2104
- const conflicts = [];
2105
- for (const commandPath of documentedCommandPaths) {
2106
- const info = allCommands.get(commandPath);
2107
- if (!info) continue;
2108
- for (const option of info.options) {
2109
- const globalOption = globalOptions.get(option.name);
2110
- if (!globalOption) continue;
2111
- if (!areGlobalOptionsEquivalent(globalOption, option)) conflicts.push(`Command "${formatCommandPath(commandPath)}" option "--${option.cliName}" does not match globalOptions definition for "${option.name}".`);
2112
- }
2113
- }
2114
- if (conflicts.length > 0) throw new Error(`Invalid globalOptions configuration:\n - ${conflicts.join("\n - ")}`);
2115
- }
2116
- /**
2117
- * Build global options content (anchor + args table) without markers
2118
- */
2119
- function buildGlobalOptionsContent(config) {
2120
- return ["<a id=\"global-options\"></a>", renderArgsTable(config.args, config.options)].join("\n");
2121
- }
2122
- /**
2123
- * Generate global options section content with markers
2124
- */
2125
- function generateGlobalOptionsSection(config) {
2126
- return [
2127
- globalOptionsStartMarker(),
2128
- buildGlobalOptionsContent(config),
2129
- globalOptionsEndMarker()
2130
- ].join("\n");
2131
- }
2132
- /**
2133
- * Generate index section content with markers
2134
- */
2135
- async function generateIndexSection(categories, command, scope, options) {
2136
- const startMarker = indexStartMarker(scope);
2137
- const endMarker = indexEndMarker(scope);
2138
- return [
2139
- startMarker,
2140
- await renderCommandIndex(command, categories, options),
2141
- endMarker
2142
- ].join("\n");
2143
- }
2144
- /**
2145
- * Normalize a doc file path for equivalence checks.
2146
- */
2147
- function normalizeDocPathForComparison(filePath) {
2148
- return path$1.resolve(filePath);
2149
- }
2150
- /**
2151
- * Process global options marker in file content
2152
- * Returns result with updated content and any diffs
2153
- */
2154
- async function processGlobalOptionsMarker(existingContent, globalOptionsConfig, updateMode, formatter, autoInsertIfMissing) {
2155
- let content = existingContent;
2156
- const diffs = [];
2157
- let hasError = false;
2158
- let wasUpdated = false;
2159
- const startMarker = globalOptionsStartMarker();
2160
- const endMarker = globalOptionsEndMarker();
2161
- const generatedSection = await applyFormatter(generateGlobalOptionsSection(globalOptionsConfig), formatter);
2162
- const existingSection = extractMarkerSection(content, startMarker, endMarker);
2163
- if (!existingSection) {
2164
- if (updateMode && autoInsertIfMissing) {
2165
- content = content.trimEnd() + "\n\n" + generatedSection + "\n";
2166
- wasUpdated = true;
2167
- return {
2168
- content,
2169
- diffs,
2170
- hasError,
2171
- wasUpdated
2172
- };
2173
- }
2174
- hasError = true;
2175
- diffs.push(`Global options marker not found in file. Expected markers:\n${startMarker}\n...\n${endMarker}`);
2176
- return {
2177
- content,
2178
- diffs,
2179
- hasError,
2180
- wasUpdated
2181
- };
2182
- }
2183
- if (existingSection !== generatedSection) {
2184
- if (updateMode) {
2185
- const updated = replaceMarkerSection(content, startMarker, endMarker, generatedSection);
2186
- if (updated) {
2187
- content = updated;
2188
- wasUpdated = true;
2189
- } else {
2190
- hasError = true;
2191
- diffs.push("Failed to replace global options section");
2192
- }
2193
- } else {
2194
- hasError = true;
2195
- diffs.push(formatDiff(existingSection, generatedSection));
2196
- }
2197
- }
2198
- return {
2199
- content,
2200
- diffs,
2201
- hasError,
2202
- wasUpdated
2203
- };
2204
- }
2205
- /**
2206
- * Process a static content marker (root-header or root-footer).
2207
- * Inserts/updates the marker section with the given content.
2208
- */
2209
- async function processStaticMarker(existingContent, markerLabel, startMarker, endMarker, rawContent, updateMode, formatter, autoInsertIfMissing) {
2210
- let content = existingContent;
2211
- const diffs = [];
2212
- let hasError = false;
2213
- let wasUpdated = false;
2214
- const generatedSection = [
2215
- startMarker,
2216
- await applyFormatter(rawContent, formatter),
2217
- endMarker
2218
- ].join("\n");
2219
- const existingSection = extractMarkerSection(content, startMarker, endMarker);
2220
- if (!existingSection) {
2221
- if (updateMode && autoInsertIfMissing) {
2222
- content = content.trimEnd() + "\n\n" + generatedSection + "\n";
2223
- wasUpdated = true;
2224
- return {
2225
- content,
2226
- diffs,
2227
- hasError,
2228
- wasUpdated
2229
- };
2230
- }
2231
- hasError = true;
2232
- diffs.push(`${markerLabel} marker not found in file. Expected markers:\n${startMarker}\n...\n${endMarker}`);
2233
- return {
2234
- content,
2235
- diffs,
2236
- hasError,
2237
- wasUpdated
2238
- };
2239
- }
2240
- if (existingSection !== generatedSection) {
2241
- if (updateMode) {
2242
- const updated = replaceMarkerSection(content, startMarker, endMarker, generatedSection);
2243
- if (updated) {
2244
- content = updated;
2245
- wasUpdated = true;
2246
- } else {
2247
- hasError = true;
2248
- diffs.push(`Failed to replace ${markerLabel} section`);
2249
- }
2250
- } else {
2251
- hasError = true;
2252
- diffs.push(formatDiff(existingSection, generatedSection));
2253
- }
2254
- }
2255
- return {
2256
- content,
2257
- diffs,
2258
- hasError,
2259
- wasUpdated
2260
- };
2261
- }
2262
- /**
2263
- * Process index marker in file content
2264
- * Returns result with updated content and any diffs.
2265
- * If the marker is not present in the file, the section is silently skipped.
2266
- */
2267
- async function processIndexMarker(existingContent, categories, command, scope, updateMode, formatter, indexOptions) {
2268
- let content = existingContent;
2269
- const diffs = [];
2270
- let hasError = false;
2271
- let wasUpdated = false;
2272
- const startMarker = indexStartMarker(scope);
2273
- const endMarker = indexEndMarker(scope);
2274
- const hasStartMarker = content.includes(startMarker);
2275
- const hasEndMarker = content.includes(endMarker);
2276
- if (!hasStartMarker && !hasEndMarker) return {
2277
- content,
2278
- diffs,
2279
- hasError,
2280
- wasUpdated
2281
- };
2282
- if (!hasStartMarker || !hasEndMarker) {
2283
- hasError = true;
2284
- diffs.push("Index marker section is malformed: both start and end markers are required.");
2285
- return {
2286
- content,
2287
- diffs,
2288
- hasError,
2289
- wasUpdated
2290
- };
2291
- }
2292
- const existingSection = extractMarkerSection(content, startMarker, endMarker);
2293
- if (!existingSection) {
2294
- hasError = true;
2295
- diffs.push("Index marker section is malformed: start marker must appear before end marker.");
2296
- return {
2297
- content,
2298
- diffs,
2299
- hasError,
2300
- wasUpdated
2301
- };
2302
- }
2303
- const generatedSection = await applyFormatter(await generateIndexSection(categories, command, scope, indexOptions), formatter);
2304
- if (existingSection !== generatedSection) {
2305
- if (updateMode) {
2306
- const updated = replaceMarkerSection(content, startMarker, endMarker, generatedSection);
2307
- if (updated) {
2308
- content = updated;
2309
- wasUpdated = true;
2310
- } else {
2311
- hasError = true;
2312
- diffs.push("Failed to replace index section");
2313
- }
2314
- } else {
2315
- hasError = true;
2316
- diffs.push(formatDiff(existingSection, generatedSection));
2317
- }
2318
- }
2319
- return {
2320
- content,
2321
- diffs,
2322
- hasError,
2323
- wasUpdated
2324
- };
2325
- }
2326
- /**
2327
- * Find which file contains a specific command
2328
- */
2329
- function findFileForCommand(commandPath, files, allCommands, ignores) {
2330
- for (const [filePath, fileConfigRaw] of Object.entries(files)) {
2331
- const { commandPaths } = resolveConfiguredCommandPaths(fileConfigRaw, allCommands, ignores);
2332
- if (commandPaths.includes(commandPath)) return filePath;
2333
- }
2334
- return null;
2335
- }
2336
- /**
2337
- * Find which target commands are contained in a file
2338
- * Also expands each target command to include subcommands that are NOT explicitly in specifiedCommands
2339
- */
2340
- function findTargetCommandsInFile(targetCommands, filePath, files, allCommands, ignores) {
2341
- const fileConfigRaw = files[filePath];
2342
- if (!fileConfigRaw) return [];
2343
- const { specifiedCommands, commandPaths } = resolveConfiguredCommandPaths(fileConfigRaw, allCommands, ignores);
2344
- const expandedTargets = /* @__PURE__ */ new Set();
2345
- for (const targetCmd of targetCommands) {
2346
- if (!commandPaths.includes(targetCmd)) continue;
2347
- expandedTargets.add(targetCmd);
2348
- for (const cmdPath of commandPaths) if (isSubcommandOf(cmdPath, targetCmd) && !specifiedCommands.includes(cmdPath)) expandedTargets.add(cmdPath);
2349
- }
2350
- return Array.from(expandedTargets);
2351
- }
2352
- /**
2353
- * Generate a single command section (already contains section markers from renderer)
2354
- */
2355
- function generateCommandSection(cmdPath, allCommands, render, filePath, fileMap, rootDocPath, hasGlobalOptions, ignores = [], excludeOptionNames, templateExclusions) {
2356
- const info = allCommands.get(cmdPath);
2357
- if (!info) return null;
2358
- if (templateExclusions && isCommandScopeExcluded(info.commandPath, templateExclusions.commandScopes)) return null;
2359
- const enriched = {
2360
- ...info,
2361
- filePath,
2362
- fileMap,
2363
- rootDocPath
2364
- };
2365
- if (ignores.length > 0 || templateExclusions && templateExclusions.commandScopes.size > 0) enriched.subCommands = info.subCommands.filter((sub) => {
2366
- const subCommandPath = sub.fullPath.join(" ");
2367
- if (ignores.some((pattern) => matchesIgnorePattern(subCommandPath, pattern))) return false;
2368
- return !(templateExclusions && isCommandScopeExcluded(subCommandPath, templateExclusions.commandScopes));
2369
- });
2370
- if (hasGlobalOptions !== void 0) enriched.hasGlobalOptions = hasGlobalOptions;
2371
- if (excludeOptionNames && excludeOptionNames.size > 0) {
2372
- enriched.options = info.options.filter((opt) => !excludeOptionNames.has(opt.name));
2373
- if (info.extracted) enriched.extracted = filterExtractedFields(info.extracted, excludeOptionNames);
2374
- }
2375
- let rendered = render(enriched);
2376
- if (templateExclusions) for (const [scope, sectionTypes] of templateExclusions.commandSections) {
2377
- if (scope !== info.commandPath) continue;
2378
- for (const sectionType of sectionTypes) {
2379
- const section = extractSectionMarker(rendered, sectionType, scope);
2380
- if (section !== null) rendered = rendered.replace(section, "");
2381
- }
2382
- rendered = collapseBlankLinesOutsideCodeFences(rendered);
2383
- }
2384
- return rendered;
2385
- }
2386
- function generateCommandTreeMarkdown(cmdPath, allCommands, render, ignores, filePath, fileMap, rootDocPath, hasGlobalOptions, excludeOptionNames, templateExclusions) {
2387
- const commandPaths = getTemplateCommandTreePaths(cmdPath, allCommands, ignores, templateExclusions);
2388
- const sections = [];
2389
- for (const commandPath of commandPaths) {
2390
- const section = generateCommandSection(commandPath, allCommands, render, filePath, fileMap, rootDocPath, hasGlobalOptions, ignores, excludeOptionNames, templateExclusions);
2391
- if (section !== null) sections.push(section);
2392
- }
2393
- return sections.length === 0 ? null : sections.join("\n");
2394
- }
2395
- /**
2396
- * Return a copy of ExtractedFields with the named options removed from every field collection
2397
- * (top-level fields, union options, and discriminated-union variants). Used to exclude global
2398
- * options from grouped option tables rendered directly from `extracted`.
2399
- */
2400
- function filterExtractedFields(extracted, excludeOptionNames) {
2401
- const result = {
2402
- ...extracted,
2403
- fields: extracted.fields.filter((f) => !excludeOptionNames.has(f.name))
2404
- };
2405
- if (extracted.unionOptions) result.unionOptions = extracted.unionOptions.map((opt) => filterExtractedFields(opt, excludeOptionNames));
2406
- if (extracted.variants) result.variants = extracted.variants.map((variant) => ({
2407
- ...variant,
2408
- fields: variant.fields.filter((f) => !excludeOptionNames.has(f.name))
2409
- }));
2410
- return result;
2411
- }
2412
- /**
2413
- * Generate markdown for a file containing multiple commands
2414
- * Each command section is wrapped with markers for partial validation
2415
- */
2416
- function generateFileMarkdown(commandPaths, allCommands, render, filePath, fileMap, specifiedOrder, fileConfig, rootDocPath, hasGlobalOptions, ignores = []) {
2417
- const sections = [];
2418
- const header = fileConfig ? generateFileHeader(fileConfig) : null;
2419
- if (header) sections.push(header);
2420
- const sortedPaths = sortDepthFirst(commandPaths, specifiedOrder ?? []);
2421
- for (const cmdPath of sortedPaths) {
2422
- const section = generateCommandSection(cmdPath, allCommands, render, filePath, fileMap, rootDocPath, hasGlobalOptions, ignores);
2423
- if (section) sections.push(section);
2424
- }
2425
- return `${sections.join("\n")}\n`;
2426
- }
2427
- /**
2428
- * Build a map of command path to file path
2429
- */
2430
- function buildFileMap(files, allCommands, ignores) {
2431
- const fileMap = {};
2432
- for (const [filePath, fileConfigRaw] of Object.entries(files)) {
2433
- const { commandPaths } = resolveConfiguredCommandPaths(fileConfigRaw, allCommands, ignores);
2434
- for (const cmdPath of commandPaths) setFileMapEntry(fileMap, cmdPath, filePath);
2435
- }
2436
- return fileMap;
2437
- }
2438
- /**
2439
- * Execute examples for commands based on configuration
2440
- */
2441
- async function executeConfiguredExamples(allCommands, examplesConfig, rootCommand) {
2442
- for (const [cmdPath, cmdConfig] of Object.entries(examplesConfig)) {
2443
- const commandInfo = allCommands.get(cmdPath);
2444
- if (!commandInfo?.examples?.length) continue;
2445
- const config = cmdConfig === true ? {} : cmdConfig;
2446
- const commandPath = cmdPath ? cmdPath.split(" ") : [];
2447
- commandInfo.exampleResults = await executeExamples(commandInfo.examples, config, rootCommand, commandPath);
2448
- }
2449
- }
2450
- /**
2451
- * Convert PathConfig to FileMapping with explicit command paths.
2452
- * Uses noExpand to prevent subcommand expansion since paths are pre-resolved.
2453
- */
2454
- function pathToFiles(pathConfig, allCommands) {
2455
- if (typeof pathConfig === "string") return {
2456
- files: { [pathConfig]: Array.from(allCommands.keys()) },
2457
- rootDocPath: pathConfig
2458
- };
2459
- const { root, commands = {} } = pathConfig;
2460
- const files = {};
2461
- const assignedToOtherFiles = /* @__PURE__ */ new Set();
2462
- const sortedEntries = Object.entries(commands).sort(([a], [b]) => b.split(" ").length - a.split(" ").length);
2463
- for (const [cmdPath, filePath] of sortedEntries) {
2464
- if (!files[filePath]) files[filePath] = {
2465
- commands: [],
2466
- noExpand: true
2467
- };
2468
- const fc = files[filePath];
2469
- for (const existingPath of allCommands.keys()) if ((existingPath === cmdPath || existingPath.startsWith(cmdPath + " ")) && !assignedToOtherFiles.has(existingPath)) {
2470
- fc.commands.push(existingPath);
2471
- assignedToOtherFiles.add(existingPath);
2472
- }
2473
- }
2474
- files[root] = {
2475
- commands: Array.from(allCommands.keys()).filter((p) => !assignedToOtherFiles.has(p)),
2476
- noExpand: true
2477
- };
2478
- return {
2479
- files,
2480
- rootDocPath: root
2481
- };
2482
- }
2483
- /**
2484
- * Generate documentation from command definition
2485
- */
2486
- async function generateDoc(config) {
2487
- const { command, ignores = [], format = {}, formatter, examples: examplesConfig, targetCommands, globalArgs, customizable = false } = config;
2488
- const allCommands = await collectAllCommands(command);
2489
- let files;
2490
- let usingPathConfig = false;
2491
- let resolvedRootDocPath;
2492
- if (config.path !== void 0) {
2493
- if (config.files !== void 0) throw new Error("Cannot specify both \"path\" and \"files\". Use one or the other.");
2494
- const converted = pathToFiles(config.path, allCommands);
2495
- files = converted.files;
2496
- resolvedRootDocPath = converted.rootDocPath;
2497
- usingPathConfig = true;
2498
- } else if (config.files !== void 0) files = config.files;
2499
- else if (config.templates !== void 0) files = {};
2500
- else throw new Error("Either \"path\", \"files\", or \"templates\" must be specified.");
2501
- let rootDoc = config.rootDoc;
2502
- if (!rootDoc && usingPathConfig && (globalArgs || config.rootInfo)) rootDoc = { path: resolvedRootDocPath };
2503
- if (globalArgs && rootDoc && !rootDoc.globalOptions) {
2504
- const optionFields = extractFields(globalArgs).fields.filter((f) => !f.positional);
2505
- if (optionFields.length > 0) {
2506
- const globalShape = Object.fromEntries(optionFields.map((f) => [f.name, f.schema]));
2507
- rootDoc = {
2508
- ...rootDoc,
2509
- globalOptions: globalShape
2510
- };
2511
- }
2512
- }
2513
- const updateMode = isTruthyEnv(UPDATE_GOLDEN_ENV);
2514
- const doctorMode = isTruthyEnv(DOCTOR_ENV);
2515
- let hasDoctorIssues = false;
2516
- if (rootDoc && !usingPathConfig) {
2517
- const normalizedRootDocPath = normalizeDocPathForComparison(rootDoc.path);
2518
- if (Object.keys(files).some((filePath) => normalizeDocPathForComparison(filePath) === normalizedRootDocPath)) throw new Error(`rootDoc.path "${rootDoc.path}" must not also appear as a key in files.`);
2519
- }
2520
- if (examplesConfig) await executeConfiguredExamples(allCommands, examplesConfig, command);
2521
- const hasTargetCommands = targetCommands !== void 0 && targetCommands.length > 0;
2522
- const globalOptionDefinitions = collectGlobalOptionDefinitions(rootDoc);
2523
- const templateGlobalOptionFields = /* @__PURE__ */ new Map();
2524
- if (config.templates) {
2525
- if (globalOptionDefinitions.size > 0) for (const [name, field] of globalOptionDefinitions) templateGlobalOptionFields.set(name, field);
2526
- else {
2527
- const shape = deriveGlobalArgsShape(globalArgs);
2528
- if (shape) for (const field of collectRenderableGlobalOptionFields(shape)) templateGlobalOptionFields.set(field.name, field);
2529
- }
2530
- }
2531
- const documentedCommandPaths = hasTargetCommands ? collectTargetDocumentedCommandPaths(targetCommands, files, allCommands, ignores) : collectDocumentedCommandPaths(files, allCommands, ignores);
2532
- const allFilesCommands = [];
2533
- for (const fileConfigRaw of Object.values(files)) {
2534
- const fileConfig = normalizeFileConfig(fileConfigRaw);
2535
- allFilesCommands.push(...fileConfig.commands);
2536
- }
2537
- validateIgnoresExist(ignores, allCommands);
2538
- validateNoConflicts(allFilesCommands, ignores, allCommands);
2539
- const fileMap = buildFileMap(files, allCommands, ignores);
2540
- const templateContents = /* @__PURE__ */ new Map();
2541
- const templateExclusions = /* @__PURE__ */ new Map();
2542
- if (config.templates) for (const [outputPath, templatePath] of Object.entries(config.templates)) {
2543
- const templateContent = readFile(templatePath);
2544
- templateContents.set(outputPath, templateContent);
2545
- if (templateContent !== null) templateExclusions.set(outputPath, buildTemplateExclusions(collectExcludedTemplatePlaceholders(templateContent), allCommands));
2546
- }
2547
- const templateEntries = Object.entries(config.templates ?? {});
2548
- const templateMeta = /* @__PURE__ */ new Map();
2549
- const templateValidationErrors = /* @__PURE__ */ new Map();
2550
- if (templateEntries.length > 0) {
2551
- const normalizedRootDocPath = rootDoc ? normalizeDocPathForComparison(rootDoc.path) : null;
2552
- const normalizedFileKeys = new Set(Object.keys(files).map(normalizeDocPathForComparison));
2553
- const normalizedTemplateOutputs = /* @__PURE__ */ new Set();
2554
- const allNormalizedTemplateOutputs = new Set(templateEntries.map(([outputPath]) => normalizeDocPathForComparison(outputPath)));
2555
- for (const [outputPath, templatePath] of templateEntries) {
2556
- const normalizedOutput = normalizeDocPathForComparison(outputPath);
2557
- const normalizedSource = normalizeDocPathForComparison(templatePath);
2558
- if (normalizedFileKeys.has(normalizedOutput)) throw new Error(`Template output path "${outputPath}" conflicts with an existing files key.`);
2559
- if (normalizedRootDocPath && normalizedOutput === normalizedRootDocPath) throw new Error(`Template output path "${outputPath}" conflicts with rootDoc.path "${rootDoc.path}".`);
2560
- if (normalizedTemplateOutputs.has(normalizedOutput)) throw new Error(`Duplicate template output path: "${outputPath}".`);
2561
- normalizedTemplateOutputs.add(normalizedOutput);
2562
- if (normalizedSource === normalizedOutput) throw new Error(`Template output path "${outputPath}" must not be the same as its source template path.`);
2563
- if (normalizedFileKeys.has(normalizedSource)) throw new Error(`Template source path "${templatePath}" conflicts with a files output key.`);
2564
- if (normalizedRootDocPath && normalizedSource === normalizedRootDocPath) throw new Error(`Template source path "${templatePath}" conflicts with rootDoc.path "${rootDoc.path}".`);
2565
- if (allNormalizedTemplateOutputs.has(normalizedSource)) throw new Error(`Template source path "${templatePath}" conflicts with a template output path.`);
2566
- }
2567
- const availableCommandPaths = Array.from(allCommands.keys()).join(", ");
2568
- for (const [outputPath, templatePath] of templateEntries) {
2569
- const templateContent = templateContents.get(outputPath) ?? null;
2570
- const validationErrors = [];
2571
- if (templateContent === null) {
2572
- templateMeta.set(outputPath, {
2573
- referencedScopes: [],
2574
- headingScopes: [],
2575
- commandTreeRoots: [],
2576
- emitsGlobalOptions: false,
2577
- emitsIndex: false
2578
- });
2579
- templateValidationErrors.set(outputPath, validationErrors);
2580
- continue;
2581
- }
2582
- try {
2583
- validateTemplatePlaceholderSyntax(templateContent, templatePath);
2584
- } catch (error) {
2585
- validationErrors.push(error instanceof Error ? error.message : String(error));
2586
- }
2587
- const placeholders = Array.from(new Set(templateContent.match(TEMPLATE_PLACEHOLDER_REGEX) ?? []));
2588
- const scopes = /* @__PURE__ */ new Set();
2589
- const headingScopes = /* @__PURE__ */ new Set();
2590
- const commandTreeRoots = /* @__PURE__ */ new Set();
2591
- let emitsGlobalOptions = false;
2592
- let emitsIndex = false;
2593
- const exclusions = templateExclusions.get(outputPath) ?? createTemplateExclusions(/* @__PURE__ */ new Set());
2594
- const indexMetadata = collectTemplateIndexMetadata(templateContent);
2595
- for (const placeholder of placeholders) {
2596
- const placeholderKey = templatePlaceholderKey(placeholder);
2597
- if (exclusions.rawKeys.has(placeholderKey) || isRawCommandPlaceholderUnderExcludedScope(placeholderKey, exclusions)) continue;
2598
- const parsed = parsePlaceholder(placeholder, allCommands);
2599
- if (shouldSkipTemplatePlaceholder(placeholder, parsed, exclusions)) continue;
2600
- if (parsed.kind === "invalid") {
2601
- validationErrors.push(`${parsed.reason} (in template "${templatePath}")`);
2602
- continue;
2603
- }
2604
- if (parsed.kind === "command") {
2605
- const { scope, type } = parsed;
2606
- if (!allCommands.has(scope)) {
2607
- const sectionTypeError = getUnknownSectionTypeError(scope, allCommands);
2608
- if (sectionTypeError) {
2609
- validationErrors.push(`${sectionTypeError} (in template "${templatePath}")`);
2610
- continue;
2611
- }
2612
- validationErrors.push(`Unknown command scope "${scope}" in template "${templatePath}". Available: ${availableCommandPaths}`);
2613
- continue;
2614
- }
2615
- if (ignores.some((pattern) => matchesIgnorePattern(scope, pattern))) {
2616
- validationErrors.push(`Command scope "${scope}" in template "${templatePath}" conflicts with ignores configuration.`);
2617
- continue;
2618
- }
2619
- if (type === void 0) {
2620
- const commandTreePaths = getTemplateCommandTreePaths(scope, allCommands, ignores, exclusions);
2621
- if (!isCommandSectionExcluded(scope, "heading", exclusions)) commandTreeRoots.add(scope);
2622
- for (const commandTreePath of commandTreePaths) {
2623
- scopes.add(commandTreePath);
2624
- if (!isCommandSectionExcluded(commandTreePath, "heading", exclusions)) headingScopes.add(commandTreePath);
2625
- }
2626
- } else {
2627
- scopes.add(scope);
2628
- if (type === "heading" && !isCommandSectionExcluded(scope, "heading", exclusions)) {
2629
- headingScopes.add(scope);
2630
- commandTreeRoots.add(scope);
2631
- }
2632
- }
2633
- } else if (parsed.kind === "global-options") emitsGlobalOptions = true;
2634
- else if (parsed.kind === "index") emitsIndex = true;
2635
- }
2636
- if (emitsGlobalOptions) {
2637
- if (!(!!rootDoc?.globalOptions || deriveGlobalArgsShape(globalArgs) !== void 0)) validationErrors.push(`Template "${templatePath}" uses {{politty:global-options}} but no global options are configured (neither rootDoc.globalOptions nor globalArgs with non-positional options).`);
2638
- }
2639
- templateMeta.set(outputPath, {
2640
- referencedScopes: Array.from(scopes),
2641
- headingScopes: Array.from(headingScopes),
2642
- commandTreeRoots: Array.from(commandTreeRoots),
2643
- emitsGlobalOptions,
2644
- emitsIndex,
2645
- ...indexMetadata.title !== void 0 ? { indexTitle: indexMetadata.title } : {},
2646
- ...indexMetadata.description !== void 0 ? { indexDescription: indexMetadata.description } : {}
2647
- });
2648
- templateValidationErrors.set(outputPath, validationErrors);
2649
- }
2650
- for (const meta of templateMeta.values()) {
2651
- if (hasTargetCommands && !templateMetaShouldProcessForTarget(meta, targetCommands)) continue;
2652
- for (const scope of meta.referencedScopes) documentedCommandPaths.add(scope);
2653
- }
2654
- }
2655
- if (hasTargetCommands) for (const targetCommand of targetCommands) {
2656
- const targetFilePath = findFileForCommand(targetCommand, files, allCommands, ignores);
2657
- const targetTemplatePath = Array.from(templateMeta.values()).some((meta) => templateMetaReferencesCommandTarget(meta, [targetCommand]));
2658
- if (!targetFilePath && !targetTemplatePath) throw new Error(`Target command "${targetCommand}" not found in any file or template configuration`);
2659
- }
2660
- const activeTemplateMeta = hasTargetCommands && config.templates ? new Map(Array.from(templateMeta.entries()).filter(([, meta]) => templateMetaShouldProcessForTarget(meta, targetCommands))) : templateMeta;
2661
- for (const [outputPath, validationErrors] of templateValidationErrors.entries()) if (validationErrors.length > 0 && activeTemplateMeta.has(outputPath)) throw new Error(validationErrors.join("\n"));
2662
- const templateGlobalOptionsProviderPaths = Array.from(templateMeta.entries()).filter(([, meta]) => meta.emitsGlobalOptions).map(([outputPath]) => outputPath);
2663
- const templateGlobalOptionsProviderPath = templateGlobalOptionsProviderPaths.length === 1 ? templateGlobalOptionsProviderPaths[0] : void 0;
2664
- validateGlobalOptionCompatibility(documentedCommandPaths, allCommands, globalOptionDefinitions);
2665
- if (globalOptionDefinitions.size === 0 && templateGlobalOptionFields.size > 0) {
2666
- const emittingTemplateScopes = /* @__PURE__ */ new Set();
2667
- for (const meta of activeTemplateMeta.values()) {
2668
- if (!meta.emitsGlobalOptions && templateGlobalOptionsProviderPath === void 0) continue;
2669
- for (const scope of meta.referencedScopes) emittingTemplateScopes.add(scope);
2670
- }
2671
- validateGlobalOptionCompatibility(emittingTemplateScopes, allCommands, templateGlobalOptionFields);
2672
- }
2673
- if (globalOptionDefinitions.size > 0) for (const info of allCommands.values()) {
2674
- info.options = info.options.filter((opt) => !globalOptionDefinitions.has(opt.name));
2675
- if (info.extracted) info.extracted = filterExtractedFields(info.extracted, new Set(globalOptionDefinitions.keys()));
2676
- }
2677
- const templateFileMap = {};
2678
- for (const [scope, outputPath] of Object.entries(fileMap)) setFileMapEntry(templateFileMap, scope, outputPath);
2679
- const scopeRootLength = (root) => root === "" ? 0 : root.split(" ").length;
2680
- const templateOwners = /* @__PURE__ */ new Map();
2681
- for (const [templateOutputPath, meta] of templateMeta.entries()) for (const scope of meta.headingScopes) {
2682
- if (Object.prototype.hasOwnProperty.call(fileMap, scope)) continue;
2683
- let bestRootLen = -1;
2684
- for (const root of meta.commandTreeRoots) if (isSubcommandOf(scope, root)) bestRootLen = Math.max(bestRootLen, scopeRootLength(root));
2685
- if (bestRootLen < 0) continue;
2686
- const existing = templateOwners.get(scope);
2687
- if (!existing || bestRootLen > existing.rootLen) templateOwners.set(scope, {
2688
- outputPath: templateOutputPath,
2689
- rootLen: bestRootLen
2690
- });
2691
- }
2692
- for (const [scope, { outputPath }] of templateOwners) setFileMapEntry(templateFileMap, scope, outputPath);
2693
- const results = [];
2694
- let hasError = false;
2695
- for (const [filePath, fileConfigRaw] of Object.entries(files)) {
2696
- const { fileConfig, specifiedCommands, commandPaths } = resolveConfiguredCommandPaths(fileConfigRaw, allCommands, ignores);
2697
- if (specifiedCommands.length === 0) continue;
2698
- if (commandPaths.length === 0) continue;
2699
- const fileTargetCommands = hasTargetCommands ? findTargetCommandsInFile(targetCommands, filePath, files, allCommands, ignores) : [];
2700
- if (hasTargetCommands && fileTargetCommands.length === 0) continue;
2701
- let fileStatus = "match";
2702
- const diffs = [];
2703
- const minDepth = Math.min(...commandPaths.map((p) => allCommands.get(p)?.depth ?? 1));
2704
- const adjustedHeadingLevel = Math.max(1, (format?.headingLevel ?? 1) - (minDepth - 1));
2705
- const isRootDocFile = usingPathConfig && rootDoc && normalizeDocPathForComparison(filePath) === normalizeDocPathForComparison(rootDoc.path);
2706
- const fileUsesMarkers = usingPathConfig || customizable;
2707
- const fileRenderer = createCommandRenderer({
2708
- ...format,
2709
- headingLevel: adjustedHeadingLevel,
2710
- markerless: !fileUsesMarkers
2711
- });
2712
- const render = fileConfig.render ?? fileRenderer;
2713
- if (Boolean(isRootDocFile) || hasTargetCommands && fileUsesMarkers) {
2714
- let existingContent = readFile(filePath);
2715
- const sortedCommandPaths = sortDepthFirst(commandPaths, specifiedCommands);
2716
- const effectiveTargetCommands = hasTargetCommands ? fileTargetCommands : commandPaths;
2717
- for (const targetCommand of effectiveTargetCommands) {
2718
- const rawSection = generateCommandSection(targetCommand, allCommands, render, filePath, templateFileMap, rootDoc?.path, globalOptionDefinitions.size > 0, ignores);
2719
- if (!rawSection) throw new Error(`Target command "${targetCommand}" not found in commands`);
2720
- const generatedSection = await applyFormatter(rawSection, formatter);
2721
- if (!existingContent) {
2722
- if (updateMode) {
2723
- const header = targetCommand === "" && fileConfig ? generateFileHeader(fileConfig) : null;
2724
- const fullContent = header ? `${header}\n${generatedSection}` : generatedSection;
2725
- writeFile(filePath, fullContent);
2726
- existingContent = fullContent;
2727
- fileStatus = "created";
2728
- } else {
2729
- hasError = true;
2730
- fileStatus = "diff";
2731
- diffs.push(`File does not exist. Target command "${targetCommand}" section cannot be validated.`);
2732
- }
2733
- continue;
2734
- }
2735
- const existingMarkers = collectSectionMarkers(existingContent, targetCommand);
2736
- if (existingMarkers.length === 0) {
2737
- if (updateMode) {
2738
- existingContent = insertCommandSections(existingContent, targetCommand, generatedSection, sortedCommandPaths);
2739
- writeFile(filePath, existingContent);
2740
- if (fileStatus !== "created") fileStatus = "updated";
2741
- } else {
2742
- hasError = true;
2743
- fileStatus = "diff";
2744
- diffs.push(`Existing file does not contain section markers for command "${targetCommand}"`);
2745
- }
2746
- continue;
2747
- }
2748
- for (const sectionType of existingMarkers) {
2749
- const existingSection = extractSectionMarker(existingContent, sectionType, targetCommand);
2750
- const generatedSectionPart = extractSectionMarker(generatedSection, sectionType, targetCommand);
2751
- if (!existingSection) continue;
2752
- if (!generatedSectionPart) {
2753
- const emptyMarker = sectionStartMarker(sectionType, targetCommand) + "\n" + sectionEndMarker(sectionType, targetCommand);
2754
- if (existingSection !== emptyMarker) {
2755
- if (updateMode) {
2756
- const updated = replaceSectionMarker(existingContent, sectionType, targetCommand, emptyMarker);
2757
- if (!updated) throw new Error(`Failed to replace stale ${sectionType} section for command "${targetCommand}"`);
2758
- existingContent = updated.replace(/\n{3,}/g, "\n\n");
2759
- writeFile(filePath, existingContent);
2760
- if (fileStatus !== "created") fileStatus = "updated";
2761
- } else {
2762
- hasError = true;
2763
- fileStatus = "diff";
2764
- diffs.push(formatDiff(existingSection, emptyMarker));
2765
- }
2766
- }
2767
- continue;
2768
- }
2769
- if (existingSection !== generatedSectionPart) {
2770
- if (updateMode) {
2771
- const updated = replaceSectionMarker(existingContent, sectionType, targetCommand, generatedSectionPart);
2772
- if (updated) {
2773
- existingContent = updated;
2774
- writeFile(filePath, existingContent);
2775
- if (fileStatus !== "created") fileStatus = "updated";
2776
- } else throw new Error(`Failed to replace ${sectionType} section for command "${targetCommand}"`);
2777
- } else {
2778
- hasError = true;
2779
- fileStatus = "diff";
2780
- diffs.push(formatDiff(existingSection, generatedSectionPart));
2781
- }
2782
- }
2783
- }
2784
- if (doctorMode || customizable) {
2785
- const generatedMarkers = collectSectionMarkers(generatedSection, targetCommand);
2786
- const existingMarkerSet = new Set(existingMarkers);
2787
- for (const sectionType of generatedMarkers) {
2788
- if (existingMarkerSet.has(sectionType)) continue;
2789
- const generatedSectionPart = extractSectionMarker(generatedSection, sectionType, targetCommand);
2790
- if (!generatedSectionPart) continue;
2791
- if (doctorMode && updateMode) {
2792
- existingContent = insertSectionMarkerAtOrder(existingContent, sectionType, targetCommand, generatedSectionPart);
2793
- writeFile(filePath, existingContent);
2794
- if (fileStatus !== "created") fileStatus = "updated";
2795
- } else if (doctorMode) {
2796
- hasError = true;
2797
- hasDoctorIssues = true;
2798
- fileStatus = "diff";
2799
- diffs.push(`[doctor] Missing section marker "${sectionType}" for command "${formatCommandPath(targetCommand)}". Run with ${DOCTOR_ENV}=true ${UPDATE_GOLDEN_ENV}=true to insert.\n${generatedSectionPart}`);
2800
- } else console.warn(`[politty] Missing "${sectionType}" section for command "${formatCommandPath(targetCommand)}" in ${filePath}. Run with ${DOCTOR_ENV}=true ${UPDATE_GOLDEN_ENV}=true to insert it, or leave it removed to opt that section out.`);
2801
- }
2802
- }
2803
- }
2804
- if (existingContent) {
2805
- const existingMarkerPaths = collectSectionMarkerPaths(existingContent);
2806
- const commandPathSet = new Set(commandPaths);
2807
- if (updateMode) {
2808
- let removedAny = false;
2809
- for (const markerPath of existingMarkerPaths) if (!commandPathSet.has(markerPath)) {
2810
- existingContent = removeCommandSections(existingContent, markerPath);
2811
- removedAny = true;
2812
- }
2813
- if (removedAny) {
2814
- writeFile(filePath, existingContent);
2815
- if (fileStatus !== "created") fileStatus = "updated";
2816
- }
2817
- } else for (const markerPath of existingMarkerPaths) if (!commandPathSet.has(markerPath)) {
2818
- hasError = true;
2819
- fileStatus = "diff";
2820
- diffs.push(`Found orphaned section markers for deleted command "${formatCommandPath(markerPath)}"`);
2821
- }
2822
- }
2823
- } else {
2824
- const generatedMarkdown = await applyFormatter(generateFileMarkdown(commandPaths, allCommands, render, filePath, templateFileMap, specifiedCommands, fileConfig, rootDoc?.path, globalOptionDefinitions.size > 0, ignores), formatter);
2825
- const comparison = compareWithExisting(generatedMarkdown, filePath);
2826
- if (comparison.match) {} else if (updateMode) {
2827
- writeFile(filePath, generatedMarkdown);
2828
- fileStatus = comparison.fileExists ? "updated" : "created";
2829
- } else {
2830
- hasError = true;
2831
- fileStatus = "diff";
2832
- if (comparison.diff) diffs.push(comparison.diff);
2833
- }
2834
- }
2835
- if (diffs.length > 0) fileStatus = "diff";
2836
- results.push({
2837
- path: filePath,
2838
- status: fileStatus,
2839
- diff: diffs.length > 0 ? diffs.join("\n\n") : void 0
2840
- });
2841
- }
2842
- let normalizedTemplateGlobalOptions;
2843
- if (rootDoc?.globalOptions) normalizedTemplateGlobalOptions = normalizeGlobalOptions(rootDoc.globalOptions);
2844
- else {
2845
- const shape = deriveGlobalArgsShape(globalArgs);
2846
- if (shape) normalizedTemplateGlobalOptions = { args: shape };
2847
- }
2848
- for (const [outputPath, templatePath] of templateEntries) {
2849
- if (!activeTemplateMeta.has(outputPath)) continue;
2850
- const templateContent = templateContents.get(outputPath) ?? null;
2851
- if (templateContent === null) {
2852
- hasError = true;
2853
- results.push({
2854
- path: outputPath,
2855
- status: "diff",
2856
- diff: `Template file not found: ${templatePath}`
2857
- });
2858
- continue;
2859
- }
2860
- const meta = templateMeta.get(outputPath);
2861
- const templateLineEnding = detectLineEnding(templateContent);
2862
- const outputTemplateContent = stripPolittyFrontMatterForOutput(templateContent);
2863
- const headingDepths = (meta?.headingScopes ?? []).map((s) => allCommands.get(s)?.depth ?? 1);
2864
- const minDepth = headingDepths.length > 0 ? Math.min(...headingDepths) : 1;
2865
- const adjustedHeadingLevel = clampHeadingLevel((format?.headingLevel ?? 1) - (minDepth - 1));
2866
- const templateRenderer = createCommandRenderer({
2867
- ...format,
2868
- headingLevel: adjustedHeadingLevel
2869
- });
2870
- const outputEmitsGlobalOptions = meta?.emitsGlobalOptions ?? false;
2871
- const excludeOptionNames = (rootDoc !== void 0 && globalOptionDefinitions.size > 0 || outputEmitsGlobalOptions || templateGlobalOptionsProviderPath !== void 0) && templateGlobalOptionFields.size > 0 ? new Set(templateGlobalOptionFields.keys()) : void 0;
2872
- const sectionHasGlobalOptions = excludeOptionNames !== void 0;
2873
- const effectiveRootDocPath = outputEmitsGlobalOptions ? outputPath : rootDoc?.path ?? templateGlobalOptionsProviderPath;
2874
- const placeholders = Array.from(new Set(outputTemplateContent.match(TEMPLATE_PLACEHOLDER_REGEX) ?? []));
2875
- const replacements = /* @__PURE__ */ new Map();
2876
- const exclusions = templateExclusions.get(outputPath) ?? createTemplateExclusions(/* @__PURE__ */ new Set());
2877
- for (const placeholder of placeholders) {
2878
- const placeholderKey = templatePlaceholderKey(placeholder);
2879
- if (exclusions.rawKeys.has(placeholderKey) || isRawCommandPlaceholderUnderExcludedScope(placeholderKey, exclusions)) {
2880
- replacements.set(placeholder, "");
2881
- continue;
2882
- }
2883
- const parsed = parsePlaceholder(placeholder, allCommands);
2884
- if (shouldSkipTemplatePlaceholder(placeholder, parsed, exclusions)) {
2885
- replacements.set(placeholder, "");
2886
- continue;
2887
- }
2888
- if (parsed.kind === "invalid") throw new Error(`Internal error: unresolved placeholder "${placeholder}" in template "${templatePath}": ${parsed.reason}`);
2889
- if (parsed.kind === "command") {
2890
- const { scope, type } = parsed;
2891
- if (type === void 0) {
2892
- const rawSection = generateCommandTreeMarkdown(scope, allCommands, templateRenderer, ignores, outputPath, templateFileMap, effectiveRootDocPath, sectionHasGlobalOptions, excludeOptionNames, exclusions);
2893
- if (rawSection === null) {
2894
- replacements.set(placeholder, "");
2895
- continue;
2896
- }
2897
- replacements.set(placeholder, stripPolittyMarkers(rawSection));
2898
- } else {
2899
- const rawSection = generateCommandSection(scope, allCommands, templateRenderer, outputPath, templateFileMap, effectiveRootDocPath, sectionHasGlobalOptions, ignores, excludeOptionNames, exclusions);
2900
- if (rawSection === null) {
2901
- replacements.set(placeholder, "");
2902
- continue;
2903
- }
2904
- const extracted = extractSectionMarker(rawSection, type, scope);
2905
- replacements.set(placeholder, extracted === null ? "" : stripPolittyMarkers(extracted));
2906
- }
2907
- } else if (parsed.kind === "global-options") {
2908
- if (normalizedTemplateGlobalOptions) replacements.set(placeholder, buildGlobalOptionsContent(normalizedTemplateGlobalOptions));
2909
- else replacements.set(placeholder, "");
2910
- } else if (parsed.kind === "index") {
2911
- const categories = [...deriveIndexFromFiles(files, outputPath, allCommands, ignores), ...deriveIndexFromTemplateOutputs(templateMeta, outputPath, outputPath, allCommands)];
2912
- const indexContent = await renderCommandIndex(command, categories, rootDoc?.index);
2913
- replacements.set(placeholder, indexContent);
2914
- }
2915
- }
2916
- let generated = outputTemplateContent.replace(/((?:\r?\n)*)([ \t]*)(\{\{politty:[^{}]*\}\})([ \t]*)((?:\r?\n)*)/g, (match, leadNl, leadWs, placeholder, trailWs, trailNl, offset, fullString) => {
2917
- const replacement = replacements.get(placeholder);
2918
- if (replacement === void 0) throw new Error(`Internal error: unresolved placeholder "${placeholder}" in template "${templatePath}".`);
2919
- const startsLine = leadNl !== "" || offset === 0 || fullString[offset - 1] === "\n";
2920
- const endsLine = trailNl !== "" || offset + match.length === fullString.length;
2921
- if (replacement === "" && startsLine && endsLine) {
2922
- if (leadNl === "" || trailNl === "") return "";
2923
- const leadBreaks = countLineBreaks(leadNl);
2924
- const trailBreaks = countLineBreaks(trailNl);
2925
- const widest = Math.max(leadBreaks, trailBreaks);
2926
- const lineEnding = leadBreaks >= trailBreaks ? detectLineEnding(leadNl) : detectLineEnding(trailNl);
2927
- return widest >= 2 ? lineEnding + lineEnding : widest === 1 ? lineEnding : "";
2928
- }
2929
- return `${leadNl}${leadWs}${replacement}${trailWs}${trailNl}`;
2930
- });
2931
- generated = `${generated.trimEnd()}${templateLineEnding}`;
2932
- generated = await applyFormatter(generated, formatter);
2933
- const comparison = compareWithExisting(generated, outputPath);
2934
- let templateStatus = "match";
2935
- let templateDiff;
2936
- if (comparison.match) {} else if (updateMode) {
2937
- writeFile(outputPath, generated);
2938
- templateStatus = comparison.fileExists ? "updated" : "created";
2939
- } else {
2940
- hasError = true;
2941
- templateStatus = "diff";
2942
- if (comparison.diff) templateDiff = comparison.diff;
2943
- }
2944
- results.push({
2945
- path: outputPath,
2946
- status: templateStatus,
2947
- diff: templateDiff
2948
- });
2949
- }
2950
- if (rootDoc) {
2951
- const rootDocFilePath = rootDoc.path;
2952
- let rootDocStatus = "match";
2953
- const rootDocDiffs = [];
2954
- const existingContent = readFile(rootDocFilePath);
2955
- if (existingContent === null) {
2956
- hasError = true;
2957
- rootDocStatus = "diff";
2958
- rootDocDiffs.push("File does not exist. Cannot validate rootDoc markers.");
2959
- } else {
2960
- let content = existingContent;
2961
- let markerUpdated = false;
2962
- const rootInfo = config.rootInfo;
2963
- const rootDocFileConfig = { title: rootInfo?.title ?? command.name };
2964
- if (rootDoc.headingLevel !== void 0) rootDocFileConfig.headingLevel = rootDoc.headingLevel;
2965
- const rootDescription = rootInfo?.description ?? command.description;
2966
- if (rootDescription !== void 0) rootDocFileConfig.description = rootDescription;
2967
- const headerResult = processFileHeader(content, rootDocFileConfig, updateMode);
2968
- content = headerResult.content;
2969
- if (headerResult.diff) rootDocDiffs.push(headerResult.diff);
2970
- if (headerResult.hasError) hasError = true;
2971
- if (headerResult.wasUpdated) markerUpdated = true;
2972
- if (rootInfo?.header) {
2973
- const headerMarkerResult = await processStaticMarker(content, "Root header", rootHeaderStartMarker(), rootHeaderEndMarker(), rootInfo.header, updateMode, formatter, usingPathConfig);
2974
- content = headerMarkerResult.content;
2975
- rootDocDiffs.push(...headerMarkerResult.diffs);
2976
- if (headerMarkerResult.hasError) hasError = true;
2977
- if (headerMarkerResult.wasUpdated) markerUpdated = true;
2978
- }
2979
- if (!usingPathConfig) {
2980
- const unexpectedSectionPaths = collectSectionMarkerPaths(content);
2981
- if (unexpectedSectionPaths.length > 0) {
2982
- if (updateMode) {
2983
- for (const commandPath of unexpectedSectionPaths) content = removeCommandSections(content, commandPath);
2984
- markerUpdated = true;
2985
- } else {
2986
- hasError = true;
2987
- rootDocDiffs.push(`Found unexpected section markers in rootDoc: ${unexpectedSectionPaths.map((commandPath) => `"${formatCommandPath(commandPath)}"`).join(", ")}.`);
2988
- }
2989
- }
2990
- }
2991
- const normalizedGlobalOptions = normalizeGlobalOptions(rootDoc.globalOptions);
2992
- if (normalizedGlobalOptions) {
2993
- const globalOptionsResult = await processGlobalOptionsMarker(content, normalizedGlobalOptions, updateMode, formatter, usingPathConfig);
2994
- content = globalOptionsResult.content;
2995
- rootDocDiffs.push(...globalOptionsResult.diffs);
2996
- if (globalOptionsResult.hasError) hasError = true;
2997
- if (globalOptionsResult.wasUpdated) markerUpdated = true;
2998
- }
2999
- const derivedCategories = deriveIndexFromFiles(files, rootDocFilePath, allCommands, ignores);
3000
- const indexScope = path$1.relative(process.cwd(), rootDocFilePath).replace(/\\/g, "/");
3001
- const indexResult = await processIndexMarker(content, derivedCategories, command, indexScope, updateMode, formatter, rootDoc.index);
3002
- content = indexResult.content;
3003
- rootDocDiffs.push(...indexResult.diffs);
3004
- if (indexResult.hasError) hasError = true;
3005
- if (indexResult.wasUpdated) markerUpdated = true;
3006
- if (rootInfo?.footer) {
3007
- const footerMarkerResult = await processStaticMarker(content, "Root footer", rootFooterStartMarker(), rootFooterEndMarker(), rootInfo.footer, updateMode, formatter, usingPathConfig);
3008
- content = footerMarkerResult.content;
3009
- rootDocDiffs.push(...footerMarkerResult.diffs);
3010
- if (footerMarkerResult.hasError) hasError = true;
3011
- if (footerMarkerResult.wasUpdated) markerUpdated = true;
3012
- }
3013
- if (updateMode && markerUpdated) {
3014
- writeFile(rootDocFilePath, content);
3015
- if (rootDocStatus === "match") rootDocStatus = "updated";
3016
- }
3017
- }
3018
- if (rootDocDiffs.length > 0) rootDocStatus = "diff";
3019
- results.push({
3020
- path: rootDocFilePath,
3021
- status: rootDocStatus,
3022
- diff: rootDocDiffs.length > 0 ? rootDocDiffs.join("\n\n") : void 0
3023
- });
3024
- }
3025
- const errorHint = hasDoctorIssues ? `Run with ${DOCTOR_ENV}=true ${UPDATE_GOLDEN_ENV}=true to fix missing markers.` : `Run with ${UPDATE_GOLDEN_ENV}=true to update.`;
3026
- return {
3027
- success: !hasError,
3028
- files: results,
3029
- error: hasError ? `Documentation is out of date. ${errorHint}` : void 0
3030
- };
3031
- }
3032
- /**
3033
- * Assert that documentation matches golden files
3034
- * Throws an error if there are differences and update mode is not enabled
3035
- */
3036
- async function assertDocMatch(config) {
3037
- const result = await generateDoc(config);
3038
- if (!result.success) {
3039
- const diffMessages = result.files.filter((f) => f.status === "diff").map((f) => {
3040
- let msg = `File: ${f.path}\n`;
3041
- if (f.diff) msg += f.diff;
3042
- return msg;
3043
- }).join("\n\n");
3044
- throw new Error(`Documentation does not match golden files.\n\n${diffMessages}\n\n` + (result.error ?? `Run with ${"POLITTY_DOCS_UPDATE"}=true to update the documentation.`));
3045
- }
3046
- }
3047
- /**
3048
- * Initialize documentation files by deleting them
3049
- * Only deletes when update mode is enabled (POLITTY_DOCS_UPDATE=true)
3050
- * Use this in beforeAll to ensure skipped tests don't leave stale sections
3051
- * @param config - Config containing files to initialize, or a single file path
3052
- * @param fileSystem - Optional fs implementation (useful when fs is mocked)
3053
- */
3054
- function initDocFile(config, fileSystem) {
3055
- if (!isTruthyEnv("POLITTY_DOCS_UPDATE")) return;
3056
- if (typeof config === "string") deleteFile(config, fileSystem);
3057
- else {
3058
- const protectedPaths = new Set(Object.values(config.templates ?? {}).map(normalizeDocPathForComparison));
3059
- if (config.rootDoc) protectedPaths.add(normalizeDocPathForComparison(config.rootDoc.path));
3060
- const isProtectedPath = (p) => protectedPaths.has(normalizeDocPathForComparison(p));
3061
- if (config.files) for (const filePath of Object.keys(config.files)) {
3062
- if (isProtectedPath(filePath)) continue;
3063
- deleteFile(filePath, fileSystem);
3064
- }
3065
- if (config.templates) for (const outputPath of Object.keys(config.templates)) {
3066
- if (isProtectedPath(outputPath)) continue;
3067
- deleteFile(outputPath, fileSystem);
3068
- }
3069
- }
3070
- }
45
+ `:`
46
+ `)+e.slice(t)}if(c!=null){let t=c;for(;t>0&&e[t-1]===`
47
+ `;)t--;let n=t===0?``:`
3071
48
 
3072
- //#endregion
3073
- export { DOCTOR_ENV, GLOBAL_OPTIONS_MARKER_PREFIX, INDEX_MARKER_PREFIX, ROOT_FOOTER_MARKER_PREFIX, ROOT_HEADER_MARKER_PREFIX, SECTION_MARKER_PREFIX, SECTION_TYPES, UPDATE_GOLDEN_ENV, assertDocMatch, buildCommandInfo, collectAllCommands, compareWithExisting, createCommandRenderer, defaultRenderers, executeExamples, formatDiff, generateDoc, globalOptionsEndMarker, globalOptionsStartMarker, indexEndMarker, indexStartMarker, initDocFile, renderArgsTable, renderArgumentsList, renderArgumentsListFromArray, renderArgumentsTable, renderArgumentsTableFromArray, renderCommandIndex, renderExamplesDefault, renderOptionsList, renderOptionsListFromArray, renderOptionsTable, renderOptionsTableFromArray, renderSubcommandsTable, renderSubcommandsTableFromArray, renderUsage, resolveLazyCommand, rootFooterEndMarker, rootFooterStartMarker, rootHeaderEndMarker, rootHeaderStartMarker, sectionEndMarker, sectionStartMarker, writeFile };
49
+ `;return e.slice(0,t)+n+r+`
50
+
51
+ `+e.slice(c)}throw Error(`No insertion point found for section "${t}" (scope="${n}"). This should be unreachable when at least one marker exists for the command.`)}function lt(e,t){let n=[];for(let r of D)Q(e,r,t)!==null&&n.push(r);return n}function ut(e){let t=D.join(`|`),n=RegExp(`<!--\\s*politty:command:(.*?):(?:${t}):start\\s*-->`,`g`),r=new Set;for(let t of e.matchAll(n))r.add(t[1]??``);return Array.from(r)}function dt(e,t,n,r){let i=r.indexOf(t);if(i===-1)return e.trimEnd()+`
52
+
53
+ `+n+`
54
+ `;for(let t=i+1;t<r.length;t++){let i=r[t];if(i===void 0)continue;let a=k(`heading`,i),o=e.indexOf(a);if(o!==-1){let t=o;for(;t>0&&e[t-1]===`
55
+ `;)t--;return t<o&&t++,e.slice(0,t)+n+`
56
+ `+e.slice(o)}}for(let t=i-1;t>=0;t--){let i=r[t];if(i===void 0)continue;let a=lt(e,i);if(a.length>0){let t=a[a.length-1],r=A(t,i),o=e.indexOf(r);if(o!==-1){let t=o+r.length;return e.slice(0,t)+`
57
+ `+n+e.slice(t)}}}return e.trimEnd()+`
58
+ `+n+`
59
+ `}function ft(e,t){let n=lt(e,t);for(let r of n){let n=k(r,t),i=A(r,t),a=e.indexOf(n);for(;a!==-1;){let t=e.indexOf(i,a);if(t===-1)break;e=e.slice(0,a)+e.slice(t+i.length),a=e.indexOf(n,a)}}return e=e.replace(/\n{3,}/g,`
60
+
61
+ `),e}function pt(e){let t=mt(e.split(`
62
+ `).filter(e=>!/^<!-- politty:.*-->$/.test(e.trim())).join(`
63
+ `));return t=t.replace(/^\n+/,``).replace(/\n+$/,``),t}function mt(e){let t=e.split(`
64
+ `),n=[],r=!1,i=0;for(let e of t){let t=e.trim();if(t.startsWith("```")||t.startsWith(`~~~`)){r=!r,i=0,n.push(e);continue}if(!r&&e.trim()===``){if(i++,i>=2)continue}else r||(i=0);n.push(e)}return n.join(`
65
+ `)}function ht(e){return e.includes(`\r
66
+ `)?`\r
67
+ `:`
68
+ `}function gt(e){return(e.match(/\n/g)??[]).length}function _t(e){return D.some(t=>t===e)}function vt(e){switch(Math.min(6,Math.max(1,Math.trunc(e)))){case 1:return 1;case 2:return 2;case 3:return 3;case 4:return 4;case 5:return 5;default:return 6}}function yt(e,t){if(e.length===0)return t===void 0||t.has(``)?``:null;let n=e.join(`:`);if(t?.has(n))return n;let r=e.join(` `);return t?.has(r)||t===void 0?r:null}function bt(e){return e.join(` `)}function xt(e,t){let n=e.slice(2,-2).split(`:`),r=n[1];if(r===`command`){let r=n.slice(2);if(r.length===1&&r[0]===``)return{kind:`invalid`,reason:`Trailing colon in "${e}"; use {{politty:command}} for the root command.`};let i=yt(r,t);if(i!==null)return{kind:`command`,scope:i,type:void 0};if(r.length>=2){let e=r[r.length-1],n=r.slice(0,-1),i=yt(n,t);if(e!==void 0&&_t(e))return{kind:`command`,scope:i??bt(n),type:e};if(e!==void 0&&i!==null)return{kind:`invalid`,reason:`Unknown section type "${e}" for command scope "${Z(i)}". Valid section types: ${D.join(`, `)}`}}return{kind:`command`,scope:bt(r),type:void 0}}return r===`global-options`?n.length===2?{kind:`global-options`}:{kind:`invalid`,reason:`Malformed placeholder "${e}". Expected {{politty:global-options}}.`}:r===`index`?n.length===2?{kind:`index`}:{kind:`invalid`,reason:`Malformed placeholder "${e}". Expected {{politty:index}}.`}:{kind:`invalid`,reason:`Unknown politty directive "${r??``}" in "${e}". Valid directives: command, global-options, index`}}function St(e,t){let n=Ke(e);for(let r of e){let e=xt(`{{politty:${r}}}`,t);if(e.kind===`command`){if(e.type===void 0)n.commandScopes.add(e.scope);else{let t=n.commandSections.get(e.scope);t||(t=new Set,n.commandSections.set(e.scope,t)),t.add(e.type)}}else e.kind===`global-options`?n.globalOptions=!0:e.kind===`index`&&(n.index=!0)}return n}function Ct(e,t){for(let n of t)if(J(e,n))return!0;return!1}function wt(e,t,n){return Ct(e,n.commandScopes)?!0:n.commandSections.get(e)?.has(t)??!1}function Tt(e,t,n,r){return rt($e(Qe([e],t),n).filter(e=>!Ct(e,r.commandScopes)),[e])}function Et(e,t,n){return n.rawKeys.has(Ve(e))?!0:t.kind===`command`?Ct(t.scope,n.commandScopes)?!0:t.type!==void 0&&(n.commandSections.get(t.scope)?.has(t.type)??!1):t.kind===`global-options`?n.globalOptions:t.kind===`index`&&n.index}function Dt(e,t){if(!e.startsWith(`command:`))return!1;let n=e.slice(8).split(`:`);for(let e of t.commandScopes){if(e===``)return!0;let t=e.split(` `);if(n.slice(0,t.length).join(` `)===e)return!0;let r=e.split(`:`);if(n.slice(0,r.length).join(`:`)===e)return!0}return!1}const Ot=/\{\{politty:[^{}]*\}\}/g;function kt(e,t){let n=new Set;for(let r of e.matchAll(Ot)){let i=r.index,a=i+r[0].length;if(e[i-1]===`{`||e[a]===`}`){let n=e.slice(Math.max(0,i-1),Math.min(e.length,a+1)).split(`
69
+ `)[0];throw Error(`Malformed politty placeholder in template "${t}": "${n}". Expected {{politty:...}}.`)}n.add(i)}let r=0;for(;;){let i=e.indexOf(`{{politty:`,r);if(i===-1)return;if(!n.has(i)){let n=e.slice(i,i+80).split(`
70
+ `)[0];throw Error(`Malformed politty placeholder in template "${t}": "${n}". Expected {{politty:...}}.`)}r=i+10}}function At(e,t){let n=e.lastIndexOf(`:`);if(n===-1)return null;let r=e.slice(0,n),i=e.slice(n+1);return i===``||!t.has(r)?null:`Unknown section type "${i}" for command scope "${Z(r)}". Valid section types: ${D.join(`, `)}`}function jt(e,t,n){let r=e.indexOf(t);if(r===-1)return null;let i=e.indexOf(n,r);return i===-1?null:e.slice(r,i+n.length)}function Mt(e,t,n,r){let i=e.indexOf(t);if(i===-1)return null;let a=e.indexOf(n,i);return a===-1?null:e.slice(0,i)+r+e.slice(a+n.length)}function Nt(e){if(typeof e!=`object`||!e)return!1;let t=e[`~standard`];if(typeof t!=`object`||!t)return!1;let{vendor:n,validate:r}=t;return typeof n==`string`&&typeof r==`function`}function Pt(e){return typeof e!=`object`||!e||!(`args`in e)?!1:!Nt(e.args)}function Ft(t){let n=e();return Object.entries(t).map(([e,t])=>n.resolveFieldMeta(e,t)).filter(e=>!e.positional)}function It(e,t){let{schema:n,...r}=e,{schema:i,...a}=t;return c(r,a)}function Lt(e){if(e)return Pt(e)?e:{args:e}}function Rt(e){if(!e)return;let t=n(e).fields.filter(e=>!e.positional);if(t.length!==0)return Object.fromEntries(t.map(e=>[e.name,e.schema]))}function zt(e){let t=new Map;if(!e?.globalOptions)return t;let n=Lt(e.globalOptions);if(!n)return t;for(let e of Ft(n.args))t.set(e.name,e);return t}function Bt(e,t,n,r){let i=[];for(let[a,s]of Object.entries(e)){let{commandPaths:e,topLevelCommands:c}=X(s,n,r);if(e.length===0)continue;let l=`./`+o.relative(o.dirname(t),a).replace(/\\/g,`/`),u=e[0],d=u===void 0?void 0:n.get(u),f=Array.isArray(s)?void 0:s;i.push({title:f?.title??d?.name??o.basename(a,o.extname(a)),description:f?.description??d?.description??``,commands:c,allowedCommands:e,docPath:l})}return i}function Vt(e,t,n,r){let i=$(t),a=[];for(let[t,s]of e.entries()){if($(t)===i)continue;let e=s.headingScopes;if(e.length===0)continue;let c=`./`+o.relative(o.dirname(n),t).replace(/\\/g,`/`),l=e[0],u=l===void 0?void 0:r.get(l);a.push({title:s.indexTitle??u?.name??o.basename(t,o.extname(t)),description:s.indexDescription??u?.description??``,commands:e,docPath:c,noExpand:!0})}return a}function Ht(e,t,n){let r=new Set;for(let i of Object.values(e)){let{commandPaths:e}=X(i,t,n);for(let t of e)r.add(t)}return r}function Ut(e,t,n,r){let i=new Set;for(let a of Object.keys(t)){let o=tn(e,a,t,n,r);for(let e of o)i.add(e)}return i}function Wt(e,t){return t.some(t=>J(e,t))}function Gt(e,t){return e.referencedScopes.some(e=>Wt(e,t))}function Kt(e,t){return e.emitsIndex||e.emitsGlobalOptions||Gt(e,t)}function qt(e,t,n){if(n.size===0)return;let r=[];for(let i of e){let e=t.get(i);if(e)for(let t of e.options){let e=n.get(t.name);e&&(It(e,t)||r.push(`Command "${Z(i)}" option "--${t.cliName}" does not match globalOptions definition for "${t.name}".`))}}if(r.length>0)throw Error(`Invalid globalOptions configuration:\n - ${r.join(`
71
+ - `)}`)}function Jt(e){return[`<a id="global-options"></a>`,Oe(e.args,e.options)].join(`
72
+ `)}function Yt(e){return[M(),Jt(e),N()].join(`
73
+ `)}async function Xt(e,t,n,r){let i=L(n),a=ie(n);return[i,await Fe(t,e,r),a].join(`
74
+ `)}function $(e){return o.resolve(e)}async function Zt(e,t,n,r,i){let a=e,o=[],s=!1,c=!1,l=M(),u=N(),d=await q(Yt(t),r),f=jt(a,l,u);if(!f)return n&&i?(a=a.trimEnd()+`
75
+
76
+ `+d+`
77
+ `,c=!0,{content:a,diffs:o,hasError:s,wasUpdated:c}):(s=!0,o.push(`Global options marker not found in file. Expected markers:\n${l}\n...\n${u}`),{content:a,diffs:o,hasError:s,wasUpdated:c});if(f!==d){if(n){let e=Mt(a,l,u,d);e?(a=e,c=!0):(s=!0,o.push(`Failed to replace global options section`))}else s=!0,o.push(G(f,d))}return{content:a,diffs:o,hasError:s,wasUpdated:c}}async function Qt(e,t,n,r,i,a,o,s){let c=e,l=[],u=!1,d=!1,f=[n,await q(i,o),r].join(`
78
+ `),p=jt(c,n,r);if(!p)return a&&s?(c=c.trimEnd()+`
79
+
80
+ `+f+`
81
+ `,d=!0,{content:c,diffs:l,hasError:u,wasUpdated:d}):(u=!0,l.push(`${t} marker not found in file. Expected markers:\n${n}\n...\n${r}`),{content:c,diffs:l,hasError:u,wasUpdated:d});if(p!==f){if(a){let e=Mt(c,n,r,f);e?(c=e,d=!0):(u=!0,l.push(`Failed to replace ${t} section`))}else u=!0,l.push(G(p,f))}return{content:c,diffs:l,hasError:u,wasUpdated:d}}async function $t(e,t,n,r,i,a,o){let s=e,c=[],l=!1,u=!1,d=L(r),f=ie(r),p=s.includes(d),m=s.includes(f);if(!p&&!m)return{content:s,diffs:c,hasError:l,wasUpdated:u};if(!p||!m)return l=!0,c.push(`Index marker section is malformed: both start and end markers are required.`),{content:s,diffs:c,hasError:l,wasUpdated:u};let h=jt(s,d,f);if(!h)return l=!0,c.push(`Index marker section is malformed: start marker must appear before end marker.`),{content:s,diffs:c,hasError:l,wasUpdated:u};let g=await q(await Xt(t,n,r,o),a);if(h!==g){if(i){let e=Mt(s,d,f,g);e?(s=e,u=!0):(l=!0,c.push(`Failed to replace index section`))}else l=!0,c.push(G(h,g))}return{content:s,diffs:c,hasError:l,wasUpdated:u}}function en(e,t,n,r){for(let[i,a]of Object.entries(t)){let{commandPaths:t}=X(a,n,r);if(t.includes(e))return i}return null}function tn(e,t,n,r,i){let a=n[t];if(!a)return[];let{specifiedCommands:o,commandPaths:s}=X(a,r,i),c=new Set;for(let t of e)if(s.includes(t)){c.add(t);for(let e of s)J(e,t)&&!o.includes(e)&&c.add(e)}return Array.from(c)}function nn(e,t,n,r,i,a,o,s=[],c,l){let u=t.get(e);if(!u||l&&Ct(u.commandPath,l.commandScopes))return null;let d={...u,filePath:r,fileMap:i,rootDocPath:a};(s.length>0||l&&l.commandScopes.size>0)&&(d.subCommands=u.subCommands.filter(e=>{let t=e.fullPath.join(` `);return!s.some(e=>Ze(t,e))&&!(l&&Ct(t,l.commandScopes))})),o!==void 0&&(d.hasGlobalOptions=o),c&&c.size>0&&(d.options=u.options.filter(e=>!c.has(e.name)),u.extracted&&(d.extracted=an(u.extracted,c)));let f=n(d);if(l){for(let[e,t]of l.commandSections)if(e===u.commandPath){for(let n of t){let t=Q(f,n,e);t!==null&&(f=f.replace(t,``))}f=mt(f)}}return f}function rn(e,t,n,r,i,a,o,s,c,l){let u=Tt(e,t,r,l),d=[];for(let e of u){let u=nn(e,t,n,i,a,o,s,r,c,l);u!==null&&d.push(u)}return d.length===0?null:d.join(`
82
+ `)}function an(e,t){let n={...e,fields:e.fields.filter(e=>!t.has(e.name))};return e.unionOptions&&(n.unionOptions=e.unionOptions.map(e=>an(e,t))),e.variants&&(n.variants=e.variants.map(e=>({...e,fields:e.fields.filter(e=>!t.has(e.name))}))),n}function on(e,t,n,r,i,a,o,s,c,l=[]){let u=[],d=o?it(o):null;d&&u.push(d);let f=rt(e,a??[]);for(let e of f){let a=nn(e,t,n,r,i,s,c,l);a&&u.push(a)}return`${u.join(`
83
+ `)}\n`}function sn(e,t,n){let r={};for(let[i,a]of Object.entries(e)){let{commandPaths:e}=X(a,t,n);for(let t of e)qe(r,t,i)}return r}async function cn(e,t,n){for(let[r,i]of Object.entries(t)){let t=e.get(r);if(!t?.examples?.length)continue;let a=i===!0?{}:i,o=r?r.split(` `):[];t.exampleResults=await we(t.examples,a,n,o)}}function ln(e,t){if(typeof e==`string`)return{files:{[e]:Array.from(t.keys())},rootDocPath:e};let{root:n,commands:r={}}=e,i={},a=new Set,o=Object.entries(r).sort(([e],[t])=>t.split(` `).length-e.split(` `).length);for(let[e,n]of o){i[n]||(i[n]={commands:[],noExpand:!0});let r=i[n];for(let n of t.keys())(n===e||n.startsWith(e+` `))&&!a.has(n)&&(r.commands.push(n),a.add(n))}return i[n]={commands:Array.from(t.keys()).filter(e=>!a.has(e)),noExpand:!0},{files:i,rootDocPath:n}}async function un(e){let{command:t,ignores:r=[],format:i={},formatter:a,examples:s,targetCommands:c,globalArgs:l,customizable:u=!1}=e,d=await Ce(t),f,p=!1,m;if(e.path!==void 0){if(e.files!==void 0)throw Error(`Cannot specify both "path" and "files". Use one or the other.`);let t=ln(e.path,d);f=t.files,m=t.rootDocPath,p=!0}else if(e.files!==void 0)f=e.files;else if(e.templates!==void 0)f={};else throw Error(`Either "path", "files", or "templates" must be specified.`);let h=e.rootDoc;if(!h&&p&&(l||e.rootInfo)&&(h={path:m}),l&&h&&!h.globalOptions){let e=n(l).fields.filter(e=>!e.positional);if(e.length>0){let t=Object.fromEntries(e.map(e=>[e.name,e.schema]));h={...h,globalOptions:t}}}let g=Ie(T),_=Ie(E),v=!1;if(h&&!p){let e=$(h.path);if(Object.keys(f).some(t=>$(t)===e))throw Error(`rootDoc.path "${h.path}" must not also appear as a key in files.`)}s&&await cn(d,s,t);let y=c!==void 0&&c.length>0,b=zt(h),x=new Map;if(e.templates){if(b.size>0)for(let[e,t]of b)x.set(e,t);else{let e=Rt(l);if(e)for(let t of Ft(e))x.set(t.name,t)}}let S=y?Ut(c,f,d,r):Ht(f,d,r),C=[];for(let e of Object.values(f)){let t=Je(e);C.push(...t.commands)}nt(r,d),tt(C,r,d);let w=sn(f,d,r),D=new Map,O=new Map;if(e.templates)for(let[t,n]of Object.entries(e.templates)){let e=be(n);D.set(t,e),e!==null&&O.set(t,St(We(e),d))}let j=Object.entries(e.templates??{}),M=new Map,N=new Map;if(j.length>0){let e=h?$(h.path):null,t=new Set(Object.keys(f).map($)),n=new Set,i=new Set(j.map(([e])=>$(e)));for(let[r,a]of j){let o=$(r),s=$(a);if(t.has(o))throw Error(`Template output path "${r}" conflicts with an existing files key.`);if(e&&o===e)throw Error(`Template output path "${r}" conflicts with rootDoc.path "${h.path}".`);if(n.has(o))throw Error(`Duplicate template output path: "${r}".`);if(n.add(o),s===o)throw Error(`Template output path "${r}" must not be the same as its source template path.`);if(t.has(s))throw Error(`Template source path "${a}" conflicts with a files output key.`);if(e&&s===e)throw Error(`Template source path "${a}" conflicts with rootDoc.path "${h.path}".`);if(i.has(s))throw Error(`Template source path "${a}" conflicts with a template output path.`)}let a=Array.from(d.keys()).join(`, `);for(let[e,t]of j){let n=D.get(e)??null,i=[];if(n===null){M.set(e,{referencedScopes:[],headingScopes:[],commandTreeRoots:[],emitsGlobalOptions:!1,emitsIndex:!1}),N.set(e,i);continue}try{kt(n,t)}catch(e){i.push(e instanceof Error?e.message:String(e))}let o=Array.from(new Set(n.match(Ot)??[])),s=new Set,c=new Set,u=new Set,f=!1,p=!1,m=O.get(e)??Ke(new Set),g=Ge(n);for(let e of o){let n=Ve(e);if(m.rawKeys.has(n)||Dt(n,m))continue;let o=xt(e,d);if(!Et(e,o,m)){if(o.kind===`invalid`){i.push(`${o.reason} (in template "${t}")`);continue}if(o.kind===`command`){let{scope:e,type:n}=o;if(!d.has(e)){let n=At(e,d);if(n){i.push(`${n} (in template "${t}")`);continue}i.push(`Unknown command scope "${e}" in template "${t}". Available: ${a}`);continue}if(r.some(t=>Ze(e,t))){i.push(`Command scope "${e}" in template "${t}" conflicts with ignores configuration.`);continue}if(n===void 0){let t=Tt(e,d,r,m);wt(e,`heading`,m)||u.add(e);for(let e of t)s.add(e),wt(e,`heading`,m)||c.add(e)}else s.add(e),n===`heading`&&!wt(e,`heading`,m)&&(c.add(e),u.add(e))}else o.kind===`global-options`?f=!0:o.kind===`index`&&(p=!0)}}f&&(h?.globalOptions||Rt(l)!==void 0||i.push(`Template "${t}" uses {{politty:global-options}} but no global options are configured (neither rootDoc.globalOptions nor globalArgs with non-positional options).`)),M.set(e,{referencedScopes:Array.from(s),headingScopes:Array.from(c),commandTreeRoots:Array.from(u),emitsGlobalOptions:f,emitsIndex:p,...g.title===void 0?{}:{indexTitle:g.title},...g.description===void 0?{}:{indexDescription:g.description}}),N.set(e,i)}for(let e of M.values())if(!(y&&!Kt(e,c)))for(let t of e.referencedScopes)S.add(t)}if(y)for(let e of c){let t=en(e,f,d,r),n=Array.from(M.values()).some(t=>Gt(t,[e]));if(!t&&!n)throw Error(`Target command "${e}" not found in any file or template configuration`)}let P=y&&e.templates?new Map(Array.from(M.entries()).filter(([,e])=>Kt(e,c))):M;for(let[e,t]of N.entries())if(t.length>0&&P.has(e))throw Error(t.join(`
84
+ `));let F=Array.from(M.entries()).filter(([,e])=>e.emitsGlobalOptions).map(([e])=>e),I=F.length===1?F[0]:void 0;if(qt(S,d,b),b.size===0&&x.size>0){let e=new Set;for(let t of P.values())if(!(!t.emitsGlobalOptions&&I===void 0))for(let n of t.referencedScopes)e.add(n);qt(e,d,x)}if(b.size>0)for(let e of d.values())e.options=e.options.filter(e=>!b.has(e.name)),e.extracted&&=an(e.extracted,new Set(b.keys()));let L={};for(let[e,t]of Object.entries(w))qe(L,e,t);let ie=e=>e===``?0:e.split(` `).length,R=new Map;for(let[e,t]of M.entries())for(let n of t.headingScopes){if(Object.prototype.hasOwnProperty.call(w,n))continue;let r=-1;for(let e of t.commandTreeRoots)J(n,e)&&(r=Math.max(r,ie(e)));if(r<0)continue;let i=R.get(n);(!i||r>i.rootLen)&&R.set(n,{outputPath:e,rootLen:r})}for(let[e,{outputPath:t}]of R)qe(L,e,t);let z=[],B=!1;for(let[e,t]of Object.entries(f)){let{fileConfig:n,specifiedCommands:o,commandPaths:s}=X(t,d,r);if(o.length===0||s.length===0)continue;let l=y?tn(c,e,f,d,r):[];if(y&&l.length===0)continue;let m=`match`,x=[],S=Math.min(...s.map(e=>d.get(e)?.depth??1)),C=Math.max(1,(i?.headingLevel??1)-(S-1)),w=p&&h&&$(e)===$(h.path),D=p||u,O=W({...i,headingLevel:C,markerless:!D}),j=n.render??O;if(w||y&&D){let t=be(e),i=rt(s,o),c=y?l:s;for(let o of c){let s=nn(o,d,j,e,L,h?.path,b.size>0,r);if(!s)throw Error(`Target command "${o}" not found in commands`);let c=await q(s,a);if(!t){if(g){let r=o===``&&n?it(n):null,i=r?`${r}\n${c}`:c;K(e,i),t=i,m=`created`}else B=!0,m=`diff`,x.push(`File does not exist. Target command "${o}" section cannot be validated.`);continue}let l=lt(t,o);if(l.length===0){g?(t=dt(t,o,c,i),K(e,t),m!==`created`&&(m=`updated`)):(B=!0,m=`diff`,x.push(`Existing file does not contain section markers for command "${o}"`));continue}for(let n of l){let r=Q(t,n,o),i=Q(c,n,o);if(r){if(!i){let i=k(n,o)+`
85
+ `+A(n,o);if(r!==i){if(g){let r=st(t,n,o,i);if(!r)throw Error(`Failed to replace stale ${n} section for command "${o}"`);t=r.replace(/\n{3,}/g,`
86
+
87
+ `),K(e,t),m!==`created`&&(m=`updated`)}else B=!0,m=`diff`,x.push(G(r,i))}continue}if(r!==i){if(g){let r=st(t,n,o,i);if(r)t=r,K(e,t),m!==`created`&&(m=`updated`);else throw Error(`Failed to replace ${n} section for command "${o}"`)}else B=!0,m=`diff`,x.push(G(r,i))}}}if(_||u){let n=lt(c,o),r=new Set(l);for(let i of n){if(r.has(i))continue;let n=Q(c,i,o);n&&(_&&g?(t=ct(t,i,o,n),K(e,t),m!==`created`&&(m=`updated`)):_?(B=!0,v=!0,m=`diff`,x.push(`[doctor] Missing section marker "${i}" for command "${Z(o)}". Run with ${E}=true ${T}=true to insert.\n${n}`)):console.warn(`[politty] Missing "${i}" section for command "${Z(o)}" in ${e}. Run with ${E}=true ${T}=true to insert it, or leave it removed to opt that section out.`))}}}if(t){let n=ut(t),r=new Set(s);if(g){let i=!1;for(let e of n)r.has(e)||(t=ft(t,e),i=!0);i&&(K(e,t),m!==`created`&&(m=`updated`))}else for(let e of n)r.has(e)||(B=!0,m=`diff`,x.push(`Found orphaned section markers for deleted command "${Z(e)}"`))}}else{let t=await q(on(s,d,j,e,L,o,n,h?.path,b.size>0,r),a),i=ye(t,e);i.match||(g?(K(e,t),m=i.fileExists?`updated`:`created`):(B=!0,m=`diff`,i.diff&&x.push(i.diff)))}x.length>0&&(m=`diff`),z.push({path:e,status:m,diff:x.length>0?x.join(`
88
+
89
+ `):void 0})}let V;if(h?.globalOptions)V=Lt(h.globalOptions);else{let e=Rt(l);e&&(V={args:e})}for(let[e,n]of j){if(!P.has(e))continue;let o=D.get(e)??null;if(o===null){B=!0,z.push({path:e,status:`diff`,diff:`Template file not found: ${n}`});continue}let s=M.get(e),c=ht(o),l=Re(o),u=(s?.headingScopes??[]).map(e=>d.get(e)?.depth??1),p=u.length>0?Math.min(...u):1,m=vt((i?.headingLevel??1)-(p-1)),_=W({...i,headingLevel:m}),v=s?.emitsGlobalOptions??!1,y=(h!==void 0&&b.size>0||v||I!==void 0)&&x.size>0?new Set(x.keys()):void 0,S=y!==void 0,C=v?e:h?.path??I,w=Array.from(new Set(l.match(Ot)??[])),T=new Map,E=O.get(e)??Ke(new Set);for(let i of w){let a=Ve(i);if(E.rawKeys.has(a)||Dt(a,E)){T.set(i,``);continue}let o=xt(i,d);if(Et(i,o,E)){T.set(i,``);continue}if(o.kind===`invalid`)throw Error(`Internal error: unresolved placeholder "${i}" in template "${n}": ${o.reason}`);if(o.kind===`command`){let{scope:t,type:n}=o;if(n===void 0){let n=rn(t,d,_,r,e,L,C,S,y,E);if(n===null){T.set(i,``);continue}T.set(i,pt(n))}else{let a=nn(t,d,_,e,L,C,S,r,y,E);if(a===null){T.set(i,``);continue}let o=Q(a,n,t);T.set(i,o===null?``:pt(o))}}else if(o.kind===`global-options`)V?T.set(i,Jt(V)):T.set(i,``);else if(o.kind===`index`){let n=await Fe(t,[...Bt(f,e,d,r),...Vt(M,e,e,d)],h?.index);T.set(i,n)}}let k=l.replace(/((?:\r?\n)*)([ \t]*)(\{\{politty:[^{}]*\}\})([ \t]*)((?:\r?\n)*)/g,(e,t,r,i,a,o,s,c)=>{let l=T.get(i);if(l===void 0)throw Error(`Internal error: unresolved placeholder "${i}" in template "${n}".`);let u=t!==``||s===0||c[s-1]===`
90
+ `,d=o!==``||s+e.length===c.length;if(l===``&&u&&d){if(t===``||o===``)return``;let e=gt(t),n=gt(o),r=Math.max(e,n),i=ht(e>=n?t:o);return r>=2?i+i:r===1?i:``}return`${t}${r}${l}${a}${o}`});k=`${k.trimEnd()}${c}`,k=await q(k,a);let A=ye(k,e),j=`match`,N;A.match||(g?(K(e,k),j=A.fileExists?`updated`:`created`):(B=!0,j=`diff`,A.diff&&(N=A.diff))),z.push({path:e,status:j,diff:N})}if(h){let n=h.path,i=`match`,s=[],c=be(n);if(c===null)B=!0,i=`diff`,s.push(`File does not exist. Cannot validate rootDoc markers.`);else{let l=c,u=!1,m=e.rootInfo,_={title:m?.title??t.name};h.headingLevel!==void 0&&(_.headingLevel=h.headingLevel);let v=m?.description??t.description;v!==void 0&&(_.description=v);let y=ot(l,_,g);if(l=y.content,y.diff&&s.push(y.diff),y.hasError&&(B=!0),y.wasUpdated&&(u=!0),m?.header){let e=await Qt(l,`Root header`,ee(),te(),m.header,g,a,p);l=e.content,s.push(...e.diffs),e.hasError&&(B=!0),e.wasUpdated&&(u=!0)}if(!p){let e=ut(l);if(e.length>0){if(g){for(let t of e)l=ft(l,t);u=!0}else B=!0,s.push(`Found unexpected section markers in rootDoc: ${e.map(e=>`"${Z(e)}"`).join(`, `)}.`)}}let b=Lt(h.globalOptions);if(b){let e=await Zt(l,b,g,a,p);l=e.content,s.push(...e.diffs),e.hasError&&(B=!0),e.wasUpdated&&(u=!0)}let x=Bt(f,n,d,r),S=o.relative(process.cwd(),n).replace(/\\/g,`/`),C=await $t(l,x,t,S,g,a,h.index);if(l=C.content,s.push(...C.diffs),C.hasError&&(B=!0),C.wasUpdated&&(u=!0),m?.footer){let e=await Qt(l,`Root footer`,ne(),re(),m.footer,g,a,p);l=e.content,s.push(...e.diffs),e.hasError&&(B=!0),e.wasUpdated&&(u=!0)}g&&u&&(K(n,l),i===`match`&&(i=`updated`))}s.length>0&&(i=`diff`),z.push({path:n,status:i,diff:s.length>0?s.join(`
91
+
92
+ `):void 0})}return{success:!B,files:z,error:B?`Documentation is out of date. ${v?`Run with ${E}=true ${T}=true to fix missing markers.`:`Run with ${T}=true to update.`}`:void 0}}async function dn(e){let t=await un(e);if(!t.success){let e=t.files.filter(e=>e.status===`diff`).map(e=>{let t=`File: ${e.path}\n`;return e.diff&&(t+=e.diff),t}).join(`
93
+
94
+ `);throw Error(`Documentation does not match golden files.\n\n${e}\n\n`+(t.error??`Run with POLITTY_DOCS_UPDATE=true to update the documentation.`))}}function fn(e,t){if(Ie(`POLITTY_DOCS_UPDATE`)){if(typeof e==`string`)xe(e,t);else{let n=new Set(Object.values(e.templates??{}).map($));e.rootDoc&&n.add($(e.rootDoc.path));let r=e=>n.has($(e));if(e.files)for(let n of Object.keys(e.files))r(n)||xe(n,t);if(e.templates)for(let n of Object.keys(e.templates))r(n)||xe(n,t)}}}export{E as DOCTOR_ENV,j as GLOBAL_OPTIONS_MARKER_PREFIX,I as INDEX_MARKER_PREFIX,F as ROOT_FOOTER_MARKER_PREFIX,P as ROOT_HEADER_MARKER_PREFIX,O as SECTION_MARKER_PREFIX,D as SECTION_TYPES,T as UPDATE_GOLDEN_ENV,dn as assertDocMatch,Se as buildCommandInfo,Ce as collectAllCommands,ye as compareWithExisting,W as createCommandRenderer,ve as defaultRenderers,we as executeExamples,G as formatDiff,un as generateDoc,N as globalOptionsEndMarker,M as globalOptionsStartMarker,ie as indexEndMarker,L as indexStartMarker,fn as initDocFile,Oe as renderArgsTable,V as renderArgumentsList,pe as renderArgumentsListFromArray,B as renderArgumentsTable,fe as renderArgumentsTableFromArray,Fe as renderCommandIndex,he as renderExamplesDefault,oe as renderOptionsList,U as renderOptionsListFromArray,ae as renderOptionsTable,H as renderOptionsTableFromArray,le as renderSubcommandsTable,me as renderSubcommandsTableFromArray,z as renderUsage,i as resolveLazyCommand,re as rootFooterEndMarker,ne as rootFooterStartMarker,te as rootHeaderEndMarker,ee as rootHeaderStartMarker,A as sectionEndMarker,k as sectionStartMarker,K as writeFile};