@politty/zod 0.1.1 → 0.2.0

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