politty 0.0.1 → 0.1.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 (56) hide show
  1. package/README.md +297 -28
  2. package/dist/arg-registry-ClI2WGgH.d.cts +89 -0
  3. package/dist/arg-registry-ClI2WGgH.d.cts.map +1 -0
  4. package/dist/arg-registry-D4NsqcNZ.d.ts +89 -0
  5. package/dist/arg-registry-D4NsqcNZ.d.ts.map +1 -0
  6. package/dist/augment.cjs +0 -0
  7. package/dist/augment.d.cts +17 -0
  8. package/dist/augment.d.cts.map +1 -0
  9. package/dist/augment.d.ts +17 -0
  10. package/dist/augment.d.ts.map +1 -0
  11. package/dist/augment.js +1 -0
  12. package/dist/command-Bgd-yIwv.cjs +25 -0
  13. package/dist/command-Bgd-yIwv.cjs.map +1 -0
  14. package/dist/command-CvKyk4ag.js +20 -0
  15. package/dist/command-CvKyk4ag.js.map +1 -0
  16. package/dist/completion/index.cjs +595 -0
  17. package/dist/completion/index.cjs.map +1 -0
  18. package/dist/completion/index.d.cts +153 -0
  19. package/dist/completion/index.d.cts.map +1 -0
  20. package/dist/completion/index.d.ts +153 -0
  21. package/dist/completion/index.d.ts.map +1 -0
  22. package/dist/completion/index.js +588 -0
  23. package/dist/completion/index.js.map +1 -0
  24. package/dist/docs/index.cjs +1239 -0
  25. package/dist/docs/index.cjs.map +1 -0
  26. package/dist/docs/index.d.cts +500 -0
  27. package/dist/docs/index.d.cts.map +1 -0
  28. package/dist/docs/index.d.ts +500 -0
  29. package/dist/docs/index.d.ts.map +1 -0
  30. package/dist/docs/index.js +1182 -0
  31. package/dist/docs/index.js.map +1 -0
  32. package/dist/index.cjs +28 -0
  33. package/dist/index.d.cts +439 -0
  34. package/dist/index.d.cts.map +1 -0
  35. package/dist/index.d.ts +439 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +5 -0
  38. package/dist/runner-C8rhhx6L.js +1372 -0
  39. package/dist/runner-C8rhhx6L.js.map +1 -0
  40. package/dist/runner-C_m6ve-j.js +4 -0
  41. package/dist/runner-CmjYWlam.cjs +1486 -0
  42. package/dist/runner-CmjYWlam.cjs.map +1 -0
  43. package/dist/runner-pmoTWUXY.cjs +4 -0
  44. package/dist/schema-extractor-B9D3Rf22.cjs +354 -0
  45. package/dist/schema-extractor-B9D3Rf22.cjs.map +1 -0
  46. package/dist/schema-extractor-D-Eo7I77.d.cts +303 -0
  47. package/dist/schema-extractor-D-Eo7I77.d.cts.map +1 -0
  48. package/dist/schema-extractor-Dk5Z0Iei.js +324 -0
  49. package/dist/schema-extractor-Dk5Z0Iei.js.map +1 -0
  50. package/dist/schema-extractor-kkajLb9E.d.ts +303 -0
  51. package/dist/schema-extractor-kkajLb9E.d.ts.map +1 -0
  52. package/dist/subcommand-router-BiSvDXHg.js +153 -0
  53. package/dist/subcommand-router-BiSvDXHg.js.map +1 -0
  54. package/dist/subcommand-router-Vf-0w9P4.cjs +189 -0
  55. package/dist/subcommand-router-Vf-0w9P4.cjs.map +1 -0
  56. package/package.json +108 -6
@@ -0,0 +1,1182 @@
1
+ import { n as getExtractedFields } from "../schema-extractor-Dk5Z0Iei.js";
2
+ import { i as createLogCollector, n as resolveLazyCommand } from "../subcommand-router-BiSvDXHg.js";
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+
6
+ //#region src/docs/default-renderers.ts
7
+ /**
8
+ * Escape markdown special characters in table cells
9
+ */
10
+ function escapeTableCell(str) {
11
+ return str.replace(/\|/g, "\\|").replace(/\n/g, " ");
12
+ }
13
+ /**
14
+ * Format default value for display
15
+ */
16
+ function formatDefaultValue(value) {
17
+ if (value === void 0) return "-";
18
+ return `\`${JSON.stringify(value)}\``;
19
+ }
20
+ /**
21
+ * Render usage line
22
+ */
23
+ function renderUsage(info) {
24
+ const parts = [info.fullCommandPath];
25
+ if (info.options.length > 0) parts.push("[options]");
26
+ if (info.subCommands.length > 0) parts.push("[command]");
27
+ for (const arg of info.positionalArgs) if (arg.required) parts.push(`<${arg.name}>`);
28
+ else parts.push(`[${arg.name}]`);
29
+ return parts.join(" ");
30
+ }
31
+ /**
32
+ * Render arguments as table
33
+ */
34
+ function renderArgumentsTable(info) {
35
+ if (info.positionalArgs.length === 0) return "";
36
+ const lines = [];
37
+ lines.push("| Argument | Description | Required |");
38
+ lines.push("|----------|-------------|----------|");
39
+ for (const arg of info.positionalArgs) {
40
+ const desc = escapeTableCell(arg.description ?? "");
41
+ const required = arg.required ? "Yes" : "No";
42
+ lines.push(`| \`${arg.name}\` | ${desc} | ${required} |`);
43
+ }
44
+ return lines.join("\n");
45
+ }
46
+ /**
47
+ * Render arguments as list
48
+ */
49
+ function renderArgumentsList(info) {
50
+ if (info.positionalArgs.length === 0) return "";
51
+ const lines = [];
52
+ for (const arg of info.positionalArgs) {
53
+ const required = arg.required ? "(required)" : "(optional)";
54
+ const desc = arg.description ? ` - ${arg.description}` : "";
55
+ lines.push(`- \`${arg.name}\`${desc} ${required}`);
56
+ }
57
+ return lines.join("\n");
58
+ }
59
+ /**
60
+ * Format environment variable info for display
61
+ */
62
+ function formatEnvInfo(env) {
63
+ if (!env) return "";
64
+ return ` [env: ${(Array.isArray(env) ? env : [env]).join(", ")}]`;
65
+ }
66
+ /**
67
+ * Format option flags (uses kebab-case cliName)
68
+ */
69
+ function formatOptionFlags(opt) {
70
+ const parts = [];
71
+ const placeholder = opt.placeholder ?? opt.cliName.toUpperCase().replace(/-/g, "_");
72
+ const longFlag = opt.type === "boolean" ? `--${opt.cliName}` : `--${opt.cliName} <${placeholder}>`;
73
+ if (opt.alias) parts.push(`\`-${opt.alias}\`, \`${longFlag}\``);
74
+ else parts.push(`\`${longFlag}\``);
75
+ return parts.join("");
76
+ }
77
+ /**
78
+ * Render options as markdown table
79
+ *
80
+ * Features:
81
+ * - Uses kebab-case (cliName) for option names (e.g., `--dry-run` instead of `--dryRun`)
82
+ * - Automatically adds Env column when any option has env configured
83
+ * - Displays multiple env vars as comma-separated list
84
+ *
85
+ * @example
86
+ * | Option | Alias | Description | Default | Env |
87
+ * |--------|-------|-------------|---------|-----|
88
+ * | `--dry-run` | `-d` | Dry run mode | `false` | - |
89
+ * | `--port <PORT>` | - | Server port | - | `PORT`, `SERVER_PORT` |
90
+ */
91
+ function renderOptionsTable(info) {
92
+ if (info.options.length === 0) return "";
93
+ const hasEnv = info.options.some((opt) => opt.env);
94
+ const lines = [];
95
+ if (hasEnv) {
96
+ lines.push("| Option | Alias | Description | Default | Env |");
97
+ lines.push("|--------|-------|-------------|---------|-----|");
98
+ } else {
99
+ lines.push("| Option | Alias | Description | Default |");
100
+ lines.push("|--------|-------|-------------|---------|");
101
+ }
102
+ for (const opt of info.options) {
103
+ const placeholder = opt.placeholder ?? opt.cliName.toUpperCase().replace(/-/g, "_");
104
+ const optionName = opt.type === "boolean" ? `\`--${opt.cliName}\`` : `\`--${opt.cliName} <${placeholder}>\``;
105
+ const alias = opt.alias ? `\`-${opt.alias}\`` : "-";
106
+ const desc = escapeTableCell(opt.description ?? "");
107
+ const defaultVal = formatDefaultValue(opt.defaultValue);
108
+ if (hasEnv) {
109
+ const envNames = opt.env ? Array.isArray(opt.env) ? opt.env.map((e) => `\`${e}\``).join(", ") : `\`${opt.env}\`` : "-";
110
+ lines.push(`| ${optionName} | ${alias} | ${desc} | ${defaultVal} | ${envNames} |`);
111
+ } else lines.push(`| ${optionName} | ${alias} | ${desc} | ${defaultVal} |`);
112
+ }
113
+ return lines.join("\n");
114
+ }
115
+ /**
116
+ * Render options as markdown list
117
+ *
118
+ * Features:
119
+ * - Uses kebab-case (cliName) for option names (e.g., `--dry-run` instead of `--dryRun`)
120
+ * - Appends env info at the end of each option (e.g., `[env: PORT, SERVER_PORT]`)
121
+ *
122
+ * @example
123
+ * - `-d`, `--dry-run` - Dry run mode (default: false)
124
+ * - `--port <PORT>` - Server port [env: PORT, SERVER_PORT]
125
+ */
126
+ function renderOptionsList(info) {
127
+ if (info.options.length === 0) return "";
128
+ const lines = [];
129
+ for (const opt of info.options) {
130
+ const flags = formatOptionFlags(opt);
131
+ const desc = opt.description ? ` - ${opt.description}` : "";
132
+ const defaultVal = opt.defaultValue !== void 0 ? ` (default: ${JSON.stringify(opt.defaultValue)})` : "";
133
+ const envInfo = formatEnvInfo(opt.env);
134
+ lines.push(`- ${flags}${desc}${defaultVal}${envInfo}`);
135
+ }
136
+ return lines.join("\n");
137
+ }
138
+ /**
139
+ * Generate anchor from command path
140
+ */
141
+ function generateAnchor(commandPath) {
142
+ return commandPath.join("-").toLowerCase();
143
+ }
144
+ /**
145
+ * Generate relative path from one file to another
146
+ */
147
+ function getRelativePath(from, to) {
148
+ const fromParts = from.split("/").slice(0, -1);
149
+ const toParts = to.split("/");
150
+ let commonLength = 0;
151
+ while (commonLength < fromParts.length && commonLength < toParts.length - 1 && fromParts[commonLength] === toParts[commonLength]) commonLength++;
152
+ const upCount = fromParts.length - commonLength;
153
+ return [...Array(upCount).fill(".."), ...toParts.slice(commonLength)].join("/") || (toParts[toParts.length - 1] ?? "");
154
+ }
155
+ /**
156
+ * Render subcommands as table
157
+ */
158
+ function renderSubcommandsTable(info, generateAnchors = true) {
159
+ if (info.subCommands.length === 0) return "";
160
+ const lines = [];
161
+ lines.push("| Command | Description |");
162
+ lines.push("|---------|-------------|");
163
+ const currentFile = info.filePath;
164
+ const fileMap = info.fileMap;
165
+ for (const sub of info.subCommands) {
166
+ const fullName = sub.fullPath.join(" ");
167
+ const desc = escapeTableCell(sub.description ?? "");
168
+ const subCommandPath = sub.fullPath.join(" ");
169
+ if (generateAnchors) {
170
+ const anchor = generateAnchor(sub.fullPath);
171
+ const subFile = fileMap?.[subCommandPath];
172
+ if (currentFile && subFile && currentFile !== subFile) {
173
+ const relativePath = getRelativePath(currentFile, subFile);
174
+ lines.push(`| [\`${fullName}\`](${relativePath}#${anchor}) | ${desc} |`);
175
+ } else lines.push(`| [\`${fullName}\`](#${anchor}) | ${desc} |`);
176
+ } else lines.push(`| \`${fullName}\` | ${desc} |`);
177
+ }
178
+ return lines.join("\n");
179
+ }
180
+ /**
181
+ * Render options from array as table
182
+ */
183
+ function renderOptionsTableFromArray(options) {
184
+ if (options.length === 0) return "";
185
+ const hasEnv = options.some((opt) => opt.env);
186
+ const lines = [];
187
+ if (hasEnv) {
188
+ lines.push("| Option | Alias | Description | Default | Env |");
189
+ lines.push("|--------|-------|-------------|---------|-----|");
190
+ } else {
191
+ lines.push("| Option | Alias | Description | Default |");
192
+ lines.push("|--------|-------|-------------|---------|");
193
+ }
194
+ for (const opt of options) {
195
+ const placeholder = opt.placeholder ?? opt.cliName.toUpperCase().replace(/-/g, "_");
196
+ const optionName = opt.type === "boolean" ? `\`--${opt.cliName}\`` : `\`--${opt.cliName} <${placeholder}>\``;
197
+ const alias = opt.alias ? `\`-${opt.alias}\`` : "-";
198
+ const desc = escapeTableCell(opt.description ?? "");
199
+ const defaultVal = formatDefaultValue(opt.defaultValue);
200
+ if (hasEnv) {
201
+ const envNames = opt.env ? Array.isArray(opt.env) ? opt.env.map((e) => `\`${e}\``).join(", ") : `\`${opt.env}\`` : "-";
202
+ lines.push(`| ${optionName} | ${alias} | ${desc} | ${defaultVal} | ${envNames} |`);
203
+ } else lines.push(`| ${optionName} | ${alias} | ${desc} | ${defaultVal} |`);
204
+ }
205
+ return lines.join("\n");
206
+ }
207
+ /**
208
+ * Render options from array as list
209
+ */
210
+ function renderOptionsListFromArray(options) {
211
+ if (options.length === 0) return "";
212
+ const lines = [];
213
+ for (const opt of options) {
214
+ const flags = formatOptionFlags(opt);
215
+ const desc = opt.description ? ` - ${opt.description}` : "";
216
+ const defaultVal = opt.defaultValue !== void 0 ? ` (default: ${JSON.stringify(opt.defaultValue)})` : "";
217
+ const envInfo = formatEnvInfo(opt.env);
218
+ lines.push(`- ${flags}${desc}${defaultVal}${envInfo}`);
219
+ }
220
+ return lines.join("\n");
221
+ }
222
+ /**
223
+ * Render arguments from array as table
224
+ */
225
+ function renderArgumentsTableFromArray(args) {
226
+ if (args.length === 0) return "";
227
+ const lines = [];
228
+ lines.push("| Argument | Description | Required |");
229
+ lines.push("|----------|-------------|----------|");
230
+ for (const arg of args) {
231
+ const desc = escapeTableCell(arg.description ?? "");
232
+ const required = arg.required ? "Yes" : "No";
233
+ lines.push(`| \`${arg.name}\` | ${desc} | ${required} |`);
234
+ }
235
+ return lines.join("\n");
236
+ }
237
+ /**
238
+ * Render arguments from array as list
239
+ */
240
+ function renderArgumentsListFromArray(args) {
241
+ if (args.length === 0) return "";
242
+ const lines = [];
243
+ for (const arg of args) {
244
+ const required = arg.required ? "(required)" : "(optional)";
245
+ const desc = arg.description ? ` - ${arg.description}` : "";
246
+ lines.push(`- \`${arg.name}\`${desc} ${required}`);
247
+ }
248
+ return lines.join("\n");
249
+ }
250
+ /**
251
+ * Render subcommands from array as table
252
+ */
253
+ function renderSubcommandsTableFromArray(subcommands, info, generateAnchors = true) {
254
+ if (subcommands.length === 0) return "";
255
+ const lines = [];
256
+ lines.push("| Command | Description |");
257
+ lines.push("|---------|-------------|");
258
+ const currentFile = info.filePath;
259
+ const fileMap = info.fileMap;
260
+ for (const sub of subcommands) {
261
+ const fullName = sub.fullPath.join(" ");
262
+ const desc = escapeTableCell(sub.description ?? "");
263
+ const subCommandPath = sub.fullPath.join(" ");
264
+ if (generateAnchors) {
265
+ const anchor = generateAnchor(sub.fullPath);
266
+ const subFile = fileMap?.[subCommandPath];
267
+ if (currentFile && subFile && currentFile !== subFile) {
268
+ const relativePath = getRelativePath(currentFile, subFile);
269
+ lines.push(`| [\`${fullName}\`](${relativePath}#${anchor}) | ${desc} |`);
270
+ } else lines.push(`| [\`${fullName}\`](#${anchor}) | ${desc} |`);
271
+ } else lines.push(`| \`${fullName}\` | ${desc} |`);
272
+ }
273
+ return lines.join("\n");
274
+ }
275
+ /**
276
+ * Render examples as markdown
277
+ *
278
+ * @example
279
+ * **Basic usage**
280
+ *
281
+ * ```bash
282
+ * $ greet World
283
+ * ```
284
+ *
285
+ * Output:
286
+ * ```
287
+ * Hello, World!
288
+ * ```
289
+ */
290
+ function renderExamplesDefault(examples, results, opts) {
291
+ if (examples.length === 0) return "";
292
+ const showOutput = opts?.showOutput ?? true;
293
+ const lines = [];
294
+ for (let i = 0; i < examples.length; i++) {
295
+ const example = examples[i];
296
+ if (!example) continue;
297
+ const result = results?.[i];
298
+ lines.push(`**${example.desc}**`);
299
+ lines.push("");
300
+ lines.push("```bash");
301
+ lines.push(`$ ${example.cmd}`);
302
+ if (showOutput) {
303
+ if (result) {
304
+ if (result.stdout) lines.push(result.stdout);
305
+ if (result.stderr) lines.push(`[stderr] ${result.stderr}`);
306
+ } else if (example.output) lines.push(example.output);
307
+ }
308
+ lines.push("```");
309
+ lines.push("");
310
+ }
311
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
312
+ return lines.join("\n");
313
+ }
314
+ /**
315
+ * Create command renderer with options
316
+ */
317
+ function createCommandRenderer(options = {}) {
318
+ const { headingLevel = 1, optionStyle = "table", generateAnchors = true, includeSubcommandDetails = true, renderDescription: customRenderDescription, renderUsage: customRenderUsage, renderArguments: customRenderArguments, renderOptions: customRenderOptions, renderSubcommands: customRenderSubcommands, renderNotes: customRenderNotes, renderFooter: customRenderFooter, renderExamples: customRenderExamples } = options;
319
+ return (info) => {
320
+ const lines = [];
321
+ const effectiveLevel = Math.min(headingLevel + (info.depth - 1), 6);
322
+ const h = "#".repeat(effectiveLevel);
323
+ const title = info.commandPath || info.name;
324
+ lines.push(`${h} ${title}`);
325
+ lines.push("");
326
+ if (info.description) {
327
+ const context = {
328
+ content: info.description,
329
+ heading: "",
330
+ info
331
+ };
332
+ const content = customRenderDescription ? customRenderDescription(context) : context.content;
333
+ if (content) {
334
+ lines.push(content);
335
+ lines.push("");
336
+ }
337
+ }
338
+ {
339
+ const context = {
340
+ content: `**Usage**\n\n\`\`\`\n${renderUsage(info)}\n\`\`\``,
341
+ heading: "**Usage**",
342
+ info
343
+ };
344
+ const content = customRenderUsage ? customRenderUsage(context) : context.content;
345
+ if (content) {
346
+ lines.push(content);
347
+ lines.push("");
348
+ }
349
+ }
350
+ if (info.positionalArgs.length > 0) {
351
+ const renderArgs = (args, opts) => {
352
+ const style = opts?.style ?? optionStyle;
353
+ const withHeading = opts?.withHeading ?? true;
354
+ const content$1 = style === "table" ? renderArgumentsTableFromArray(args) : renderArgumentsListFromArray(args);
355
+ return withHeading ? `**Arguments**\n\n${content$1}` : content$1;
356
+ };
357
+ const context = {
358
+ args: info.positionalArgs,
359
+ render: renderArgs,
360
+ heading: "**Arguments**",
361
+ info
362
+ };
363
+ const content = customRenderArguments ? customRenderArguments(context) : renderArgs(context.args);
364
+ if (content) {
365
+ lines.push(content);
366
+ lines.push("");
367
+ }
368
+ }
369
+ if (info.options.length > 0) {
370
+ const renderOpts = (opts, renderOpts$1) => {
371
+ const style = renderOpts$1?.style ?? optionStyle;
372
+ const withHeading = renderOpts$1?.withHeading ?? true;
373
+ const content$1 = style === "table" ? renderOptionsTableFromArray(opts) : renderOptionsListFromArray(opts);
374
+ return withHeading ? `**Options**\n\n${content$1}` : content$1;
375
+ };
376
+ const context = {
377
+ options: info.options,
378
+ render: renderOpts,
379
+ heading: "**Options**",
380
+ info
381
+ };
382
+ const content = customRenderOptions ? customRenderOptions(context) : renderOpts(context.options);
383
+ if (content) {
384
+ lines.push(content);
385
+ lines.push("");
386
+ }
387
+ }
388
+ if (info.subCommands.length > 0) {
389
+ const effectiveAnchors = generateAnchors && includeSubcommandDetails;
390
+ const renderSubs = (subs, opts) => {
391
+ const anchors = opts?.generateAnchors ?? effectiveAnchors;
392
+ const withHeading = opts?.withHeading ?? true;
393
+ const content$1 = renderSubcommandsTableFromArray(subs, info, anchors);
394
+ return withHeading ? `**Commands**\n\n${content$1}` : content$1;
395
+ };
396
+ const context = {
397
+ subcommands: info.subCommands,
398
+ render: renderSubs,
399
+ heading: "**Commands**",
400
+ info
401
+ };
402
+ const content = customRenderSubcommands ? customRenderSubcommands(context) : renderSubs(context.subcommands);
403
+ if (content) {
404
+ lines.push(content);
405
+ lines.push("");
406
+ }
407
+ }
408
+ if (info.examples && info.examples.length > 0) {
409
+ const renderEx = (examples, results, opts) => {
410
+ const withHeading = opts?.withHeading ?? true;
411
+ const content$1 = renderExamplesDefault(examples, results, opts);
412
+ return withHeading ? `**Examples**\n\n${content$1}` : content$1;
413
+ };
414
+ const context = {
415
+ examples: info.examples,
416
+ results: info.exampleResults,
417
+ render: renderEx,
418
+ heading: "**Examples**",
419
+ info
420
+ };
421
+ const content = customRenderExamples ? customRenderExamples(context) : renderEx(context.examples, context.results);
422
+ if (content) {
423
+ lines.push(content);
424
+ lines.push("");
425
+ }
426
+ }
427
+ if (info.notes) {
428
+ const context = {
429
+ content: `**Notes**\n\n${info.notes}`,
430
+ heading: "**Notes**",
431
+ info
432
+ };
433
+ const content = customRenderNotes ? customRenderNotes(context) : context.content;
434
+ if (content) {
435
+ lines.push(content);
436
+ lines.push("");
437
+ }
438
+ }
439
+ {
440
+ const context = {
441
+ content: "",
442
+ heading: "",
443
+ info
444
+ };
445
+ const content = customRenderFooter ? customRenderFooter(context) : context.content;
446
+ if (content) {
447
+ lines.push(content);
448
+ lines.push("");
449
+ }
450
+ }
451
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
452
+ lines.push("");
453
+ return lines.join("\n");
454
+ };
455
+ }
456
+ /**
457
+ * Default renderers presets
458
+ */
459
+ const defaultRenderers = {
460
+ command: (options) => createCommandRenderer(options),
461
+ tableStyle: createCommandRenderer({ optionStyle: "table" }),
462
+ listStyle: createCommandRenderer({ optionStyle: "list" })
463
+ };
464
+
465
+ //#endregion
466
+ //#region src/docs/doc-comparator.ts
467
+ /**
468
+ * Compare generated content with existing file
469
+ */
470
+ function compareWithExisting(generatedContent, filePath) {
471
+ const absolutePath = path.resolve(filePath);
472
+ if (!fs.existsSync(absolutePath)) return {
473
+ match: false,
474
+ fileExists: false
475
+ };
476
+ const existingContent = fs.readFileSync(absolutePath, "utf-8");
477
+ if (generatedContent === existingContent) return {
478
+ match: true,
479
+ fileExists: true
480
+ };
481
+ return {
482
+ match: false,
483
+ diff: formatDiff(existingContent, generatedContent),
484
+ fileExists: true
485
+ };
486
+ }
487
+ /**
488
+ * Format diff between two strings in unified diff format
489
+ */
490
+ function formatDiff(expected, actual) {
491
+ const expectedLines = expected.split("\n");
492
+ const actualLines = actual.split("\n");
493
+ const result = [];
494
+ result.push("--- existing");
495
+ result.push("+++ generated");
496
+ result.push("");
497
+ const maxLines = Math.max(expectedLines.length, actualLines.length);
498
+ let inChunk = false;
499
+ let chunkStart = 0;
500
+ const chunk = [];
501
+ const flushChunk = () => {
502
+ if (chunk.length > 0) {
503
+ result.push(`@@ -${chunkStart + 1},${chunk.length} @@`);
504
+ result.push(...chunk);
505
+ chunk.length = 0;
506
+ }
507
+ inChunk = false;
508
+ };
509
+ for (let i = 0; i < maxLines; i++) {
510
+ const expectedLine = expectedLines[i];
511
+ const actualLine = actualLines[i];
512
+ if (expectedLine === actualLine) {
513
+ if (inChunk) {
514
+ chunk.push(` ${expectedLine ?? ""}`);
515
+ const lastChangeIndex = chunk.findIndex((line, idx) => (line.startsWith("-") || line.startsWith("+")) && chunk.slice(idx + 1).every((l) => l.startsWith(" ")));
516
+ if (lastChangeIndex !== -1 && chunk.length - lastChangeIndex > 3) flushChunk();
517
+ }
518
+ } else {
519
+ if (!inChunk) {
520
+ inChunk = true;
521
+ chunkStart = i;
522
+ const contextStart = Math.max(0, i - 3);
523
+ for (let j = contextStart; j < i; j++) chunk.push(` ${expectedLines[j] ?? ""}`);
524
+ }
525
+ if (expectedLine !== void 0 && (actualLine === void 0 || expectedLine !== actualLine)) chunk.push(`-${expectedLine}`);
526
+ if (actualLine !== void 0 && (expectedLine === void 0 || expectedLine !== actualLine)) chunk.push(`+${actualLine}`);
527
+ }
528
+ }
529
+ flushChunk();
530
+ return result.join("\n");
531
+ }
532
+ /**
533
+ * Write content to file, creating directories if needed
534
+ */
535
+ function writeFile(filePath, content) {
536
+ const absolutePath = path.resolve(filePath);
537
+ const dir = path.dirname(absolutePath);
538
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
539
+ fs.writeFileSync(absolutePath, content, "utf-8");
540
+ }
541
+ /**
542
+ * Read file content if it exists
543
+ * Returns null if file does not exist
544
+ */
545
+ function readFile(filePath) {
546
+ const absolutePath = path.resolve(filePath);
547
+ if (!fs.existsSync(absolutePath)) return null;
548
+ return fs.readFileSync(absolutePath, "utf-8");
549
+ }
550
+ /**
551
+ * Delete file if it exists
552
+ * @param filePath - Path to the file to delete
553
+ * @param fileSystem - Optional fs implementation (useful when fs is mocked)
554
+ */
555
+ function deleteFile(filePath, fileSystem = fs) {
556
+ const absolutePath = path.resolve(filePath);
557
+ if (fileSystem.existsSync(absolutePath)) fileSystem.unlinkSync(absolutePath);
558
+ }
559
+
560
+ //#endregion
561
+ //#region src/docs/doc-generator.ts
562
+ /**
563
+ * Build CommandInfo from a command
564
+ */
565
+ async function buildCommandInfo(command, rootName, commandPath = []) {
566
+ const extracted = getExtractedFields(command);
567
+ const positionalArgs = extracted?.fields.filter((f) => f.positional) ?? [];
568
+ const options = extracted?.fields.filter((f) => !f.positional) ?? [];
569
+ const subCommands = [];
570
+ if (command.subCommands) for (const [name, subCmd] of Object.entries(command.subCommands)) {
571
+ const resolved = await resolveLazyCommand(subCmd);
572
+ const fullPath = [...commandPath, name];
573
+ subCommands.push({
574
+ name,
575
+ description: resolved.description,
576
+ fullPath
577
+ });
578
+ }
579
+ return {
580
+ name: command.name ?? "",
581
+ description: command.description,
582
+ fullCommandPath: commandPath.length > 0 ? `${rootName} ${commandPath.join(" ")}` : rootName,
583
+ commandPath: commandPath.join(" "),
584
+ depth: commandPath.length + 1,
585
+ positionalArgs,
586
+ options,
587
+ subCommands,
588
+ extracted,
589
+ command,
590
+ notes: command.notes,
591
+ examples: command.examples
592
+ };
593
+ }
594
+ /**
595
+ * Collect all commands with their paths
596
+ * Returns a map of command path -> CommandInfo
597
+ */
598
+ async function collectAllCommands(command, rootName) {
599
+ const root = rootName ?? command.name ?? "command";
600
+ const result = /* @__PURE__ */ new Map();
601
+ async function traverse(cmd, path$1) {
602
+ const info = await buildCommandInfo(cmd, root, path$1);
603
+ const pathKey = path$1.join(" ");
604
+ result.set(pathKey, info);
605
+ if (cmd.subCommands) for (const [name, subCmd] of Object.entries(cmd.subCommands)) await traverse(await resolveLazyCommand(subCmd), [...path$1, name]);
606
+ }
607
+ await traverse(command, []);
608
+ return result;
609
+ }
610
+
611
+ //#endregion
612
+ //#region src/docs/example-executor.ts
613
+ /**
614
+ * Execute examples for a command and capture output
615
+ *
616
+ * @param examples - Examples to execute
617
+ * @param config - Execution configuration (mock setup/cleanup)
618
+ * @param rootCommand - Root command to execute against
619
+ * @param commandPath - Command path for subcommands (e.g., ["config", "get"])
620
+ * @returns Array of execution results with captured stdout/stderr
621
+ */
622
+ async function executeExamples(examples, config, rootCommand, commandPath = []) {
623
+ const results = [];
624
+ if (config.mock) await config.mock();
625
+ try {
626
+ for (const example of examples) {
627
+ const result = await executeSingleExample(example, rootCommand, commandPath);
628
+ results.push(result);
629
+ }
630
+ } finally {
631
+ if (config.cleanup) await config.cleanup();
632
+ }
633
+ return results;
634
+ }
635
+ /**
636
+ * Execute a single example and capture output
637
+ */
638
+ async function executeSingleExample(example, rootCommand, commandPath) {
639
+ const exampleArgs = parseExampleCmd(example.cmd);
640
+ const argv = [...commandPath, ...exampleArgs];
641
+ const collector = createLogCollector({ passthrough: false });
642
+ collector.start();
643
+ let success = true;
644
+ try {
645
+ const { runCommand } = await import("../runner-C_m6ve-j.js");
646
+ const result = await runCommand(rootCommand, argv);
647
+ success = result.success;
648
+ if (!result.success && result.error) console.error(result.error.message);
649
+ } catch (error) {
650
+ success = false;
651
+ console.error(error instanceof Error ? error.message : String(error));
652
+ } finally {
653
+ collector.stop();
654
+ }
655
+ const logs = collector.getLogs();
656
+ const stdout = logs.entries.filter((e) => e.stream === "stdout").map((e) => e.message).join("\n");
657
+ const stderr = logs.entries.filter((e) => e.stream === "stderr").map((e) => e.message).join("\n");
658
+ return {
659
+ cmd: example.cmd,
660
+ desc: example.desc,
661
+ expectedOutput: example.output,
662
+ stdout,
663
+ stderr,
664
+ success
665
+ };
666
+ }
667
+ /**
668
+ * Parse example command string into argv array
669
+ * Handles quoted strings (single and double quotes)
670
+ *
671
+ * @example
672
+ * parseExampleCmd('World') // ['World']
673
+ * parseExampleCmd('--name "John Doe"') // ['--name', 'John Doe']
674
+ * parseExampleCmd("--greeting 'Hello World'") // ['--greeting', 'Hello World']
675
+ */
676
+ function parseExampleCmd(cmd) {
677
+ const args = [];
678
+ let current = "";
679
+ let inQuote = false;
680
+ let quoteChar = "";
681
+ for (let i = 0; i < cmd.length; i++) {
682
+ const char = cmd[i];
683
+ if ((char === "\"" || char === "'") && !inQuote) {
684
+ inQuote = true;
685
+ quoteChar = char;
686
+ } else if (char === quoteChar && inQuote) {
687
+ inQuote = false;
688
+ quoteChar = "";
689
+ } else if (char === " " && !inQuote) {
690
+ if (current) {
691
+ args.push(current);
692
+ current = "";
693
+ }
694
+ } else current += char;
695
+ }
696
+ if (current) args.push(current);
697
+ return args;
698
+ }
699
+
700
+ //#endregion
701
+ //#region src/docs/types.ts
702
+ /**
703
+ * Environment variable name for update mode
704
+ */
705
+ const UPDATE_GOLDEN_ENV = "POLITTY_DOCS_UPDATE";
706
+ /**
707
+ * Marker prefix for command sections in generated documentation
708
+ * Format: <!-- politty:command:<path>:start --> ... <!-- politty:command:<path>:end -->
709
+ */
710
+ const COMMAND_MARKER_PREFIX = "politty:command";
711
+ /**
712
+ * Generate start marker for a command section
713
+ */
714
+ function commandStartMarker(commandPath) {
715
+ return `<!-- ${COMMAND_MARKER_PREFIX}:${commandPath}:start -->`;
716
+ }
717
+ /**
718
+ * Generate end marker for a command section
719
+ */
720
+ function commandEndMarker(commandPath) {
721
+ return `<!-- ${COMMAND_MARKER_PREFIX}:${commandPath}:end -->`;
722
+ }
723
+
724
+ //#endregion
725
+ //#region src/docs/golden-test.ts
726
+ /**
727
+ * Apply formatter to content if provided
728
+ * Supports both sync and async formatters
729
+ */
730
+ async function applyFormatter(content, formatter) {
731
+ if (!formatter) return content;
732
+ return await formatter(content);
733
+ }
734
+ /**
735
+ * Check if update mode is enabled via environment variable
736
+ */
737
+ function isUpdateMode() {
738
+ const value = process.env[UPDATE_GOLDEN_ENV];
739
+ return value === "true" || value === "1";
740
+ }
741
+ /**
742
+ * Normalize file mapping entry to FileConfig
743
+ */
744
+ function normalizeFileConfig(config) {
745
+ if (Array.isArray(config)) return { commands: config };
746
+ return config;
747
+ }
748
+ /**
749
+ * Check if a command path is a subcommand of another
750
+ */
751
+ function isSubcommandOf(childPath, parentPath) {
752
+ if (parentPath === "") return true;
753
+ if (childPath === parentPath) return true;
754
+ return childPath.startsWith(parentPath + " ");
755
+ }
756
+ /**
757
+ * Check if a pattern contains wildcards
758
+ */
759
+ function containsWildcard(pattern) {
760
+ return pattern.includes("*");
761
+ }
762
+ /**
763
+ * Check if a command path matches a wildcard pattern
764
+ * - `*` matches any single command segment
765
+ * - Pattern segments are space-separated
766
+ *
767
+ * @example
768
+ * matchesWildcard("config get", "* *") // true
769
+ * matchesWildcard("config", "* *") // false
770
+ * matchesWildcard("config get", "config *") // true
771
+ * matchesWildcard("greet", "*") // true
772
+ */
773
+ function matchesWildcard(path$1, pattern) {
774
+ const pathSegments = path$1 === "" ? [] : path$1.split(" ");
775
+ const patternSegments = pattern === "" ? [] : pattern.split(" ");
776
+ if (pathSegments.length !== patternSegments.length) return false;
777
+ for (let i = 0; i < patternSegments.length; i++) {
778
+ const patternSeg = patternSegments[i];
779
+ const pathSeg = pathSegments[i];
780
+ if (patternSeg !== "*" && patternSeg !== pathSeg) return false;
781
+ }
782
+ return true;
783
+ }
784
+ /**
785
+ * Expand a wildcard pattern to matching command paths
786
+ */
787
+ function expandWildcardPattern(pattern, allCommands) {
788
+ const matches = [];
789
+ for (const cmdPath of allCommands.keys()) if (matchesWildcard(cmdPath, pattern)) matches.push(cmdPath);
790
+ return matches;
791
+ }
792
+ /**
793
+ * Check if a path matches any ignore pattern (with wildcard support)
794
+ * For wildcard patterns, also ignores subcommands of matched commands
795
+ */
796
+ function matchesIgnorePattern(path$1, ignorePattern) {
797
+ if (containsWildcard(ignorePattern)) {
798
+ if (matchesWildcard(path$1, ignorePattern)) return true;
799
+ const pathSegments = path$1 === "" ? [] : path$1.split(" ");
800
+ const patternSegments = ignorePattern === "" ? [] : ignorePattern.split(" ");
801
+ if (pathSegments.length > patternSegments.length) return matchesWildcard(pathSegments.slice(0, patternSegments.length).join(" "), ignorePattern);
802
+ return false;
803
+ }
804
+ return isSubcommandOf(path$1, ignorePattern);
805
+ }
806
+ /**
807
+ * Expand command paths to include all subcommands (with wildcard support)
808
+ */
809
+ function expandCommandPaths(commandPaths, allCommands) {
810
+ const expanded = /* @__PURE__ */ new Set();
811
+ for (const cmdPath of commandPaths) if (containsWildcard(cmdPath)) {
812
+ const matches = expandWildcardPattern(cmdPath, allCommands);
813
+ for (const match of matches) {
814
+ expanded.add(match);
815
+ for (const path$1 of allCommands.keys()) if (isSubcommandOf(path$1, match)) expanded.add(path$1);
816
+ }
817
+ } else {
818
+ if (allCommands.has(cmdPath)) expanded.add(cmdPath);
819
+ for (const path$1 of allCommands.keys()) if (isSubcommandOf(path$1, cmdPath)) expanded.add(path$1);
820
+ }
821
+ return Array.from(expanded);
822
+ }
823
+ /**
824
+ * Filter out ignored commands (with wildcard support)
825
+ */
826
+ function filterIgnoredCommands(commandPaths, ignores) {
827
+ return commandPaths.filter((path$1) => {
828
+ return !ignores.some((ignorePattern) => matchesIgnorePattern(path$1, ignorePattern));
829
+ });
830
+ }
831
+ /**
832
+ * Validate that there are no conflicts between files and ignores (with wildcard support)
833
+ */
834
+ function validateNoConflicts(filesCommands, ignores, allCommands) {
835
+ const conflicts = [];
836
+ for (const filePattern of filesCommands) {
837
+ const filePaths = containsWildcard(filePattern) ? expandWildcardPattern(filePattern, allCommands) : [filePattern];
838
+ for (const filePath of filePaths) for (const ignorePattern of ignores) if (containsWildcard(ignorePattern)) {
839
+ if (matchesWildcard(filePath, ignorePattern)) conflicts.push(`"${filePath}" is both in files and ignored by "${ignorePattern}"`);
840
+ } else if (filePath === ignorePattern || isSubcommandOf(filePath, ignorePattern)) conflicts.push(`"${filePath}" is both in files and ignored by "${ignorePattern}"`);
841
+ }
842
+ if (conflicts.length > 0) throw new Error(`Conflict between files and ignores:\n - ${conflicts.join("\n - ")}`);
843
+ }
844
+ /**
845
+ * Validate that all ignored paths exist in the command tree (with wildcard support)
846
+ */
847
+ function validateIgnoresExist(ignores, allCommands) {
848
+ const nonExistent = [];
849
+ for (const ignorePattern of ignores) if (containsWildcard(ignorePattern)) {
850
+ if (expandWildcardPattern(ignorePattern, allCommands).length === 0) nonExistent.push(`"${ignorePattern}"`);
851
+ } else if (!allCommands.has(ignorePattern)) nonExistent.push(`"${ignorePattern}"`);
852
+ if (nonExistent.length > 0) throw new Error(`Ignored command paths do not exist: ${nonExistent.join(", ")}`);
853
+ }
854
+ /**
855
+ * Sort command paths in depth-first order while preserving the specified command order
856
+ * Parent commands are immediately followed by their subcommands
857
+ */
858
+ function sortDepthFirst(commandPaths, specifiedOrder) {
859
+ const pathSet = new Set(commandPaths);
860
+ const topLevelPaths = specifiedOrder.filter((cmd) => pathSet.has(cmd));
861
+ for (const path$1 of commandPaths) if ((path$1 === "" ? 0 : path$1.split(" ").length) === 1 && !topLevelPaths.includes(path$1)) topLevelPaths.push(path$1);
862
+ const result = [];
863
+ const visited = /* @__PURE__ */ new Set();
864
+ function addWithChildren(cmdPath) {
865
+ if (visited.has(cmdPath) || !pathSet.has(cmdPath)) return;
866
+ visited.add(cmdPath);
867
+ result.push(cmdPath);
868
+ const children = commandPaths.filter((p) => {
869
+ if (p === cmdPath || visited.has(p)) return false;
870
+ if (cmdPath === "") return p.split(" ").length === 1;
871
+ return p.startsWith(cmdPath + " ") && p.split(" ").length === cmdPath.split(" ").length + 1;
872
+ }).sort((a, b) => a.localeCompare(b));
873
+ for (const child of children) addWithChildren(child);
874
+ }
875
+ for (const topLevel of topLevelPaths) addWithChildren(topLevel);
876
+ for (const path$1 of commandPaths) if (!visited.has(path$1)) result.push(path$1);
877
+ return result;
878
+ }
879
+ /**
880
+ * Generate file header from FileConfig
881
+ */
882
+ function generateFileHeader(fileConfig) {
883
+ if (!fileConfig.title && !fileConfig.description) return null;
884
+ const parts = [];
885
+ if (fileConfig.title) parts.push(`# ${fileConfig.title}`);
886
+ if (fileConfig.description) {
887
+ parts.push("");
888
+ parts.push(fileConfig.description);
889
+ }
890
+ parts.push("");
891
+ return parts.join("\n");
892
+ }
893
+ /**
894
+ * Extract a command section from content using markers
895
+ * Returns the content between start and end markers (including markers)
896
+ */
897
+ function extractCommandSection(content, commandPath) {
898
+ const startMarker = commandStartMarker(commandPath);
899
+ const endMarker = commandEndMarker(commandPath);
900
+ const startIndex = content.indexOf(startMarker);
901
+ if (startIndex === -1) return null;
902
+ const endIndex = content.indexOf(endMarker, startIndex);
903
+ if (endIndex === -1) return null;
904
+ return content.slice(startIndex, endIndex + endMarker.length);
905
+ }
906
+ /**
907
+ * Replace a command section in content using markers
908
+ * Returns the updated content with the new section
909
+ */
910
+ function replaceCommandSection(content, commandPath, newSection) {
911
+ const startMarker = commandStartMarker(commandPath);
912
+ const endMarker = commandEndMarker(commandPath);
913
+ const startIndex = content.indexOf(startMarker);
914
+ if (startIndex === -1) return null;
915
+ const endIndex = content.indexOf(endMarker, startIndex);
916
+ if (endIndex === -1) return null;
917
+ return content.slice(0, startIndex) + newSection + content.slice(endIndex + endMarker.length);
918
+ }
919
+ /**
920
+ * Insert a command section at the correct position based on specified order
921
+ * Returns the updated content with the section inserted at the right position
922
+ */
923
+ function insertCommandSection(content, commandPath, newSection, specifiedOrder) {
924
+ const targetIndex = specifiedOrder.indexOf(commandPath);
925
+ if (targetIndex === -1) return content.trimEnd() + "\n\n" + newSection + "\n";
926
+ for (let i = targetIndex + 1; i < specifiedOrder.length; i++) {
927
+ const nextCmd = specifiedOrder[i];
928
+ if (nextCmd === void 0) continue;
929
+ const nextMarker = commandStartMarker(nextCmd);
930
+ const nextIndex = content.indexOf(nextMarker);
931
+ if (nextIndex !== -1) {
932
+ let insertPos = nextIndex;
933
+ while (insertPos > 0 && content[insertPos - 1] === "\n") insertPos--;
934
+ if (insertPos < nextIndex) insertPos++;
935
+ return content.slice(0, insertPos) + newSection + "\n" + content.slice(nextIndex);
936
+ }
937
+ }
938
+ for (let i = targetIndex - 1; i >= 0; i--) {
939
+ const prevCmd = specifiedOrder[i];
940
+ if (prevCmd === void 0) continue;
941
+ const prevEndMarker = commandEndMarker(prevCmd);
942
+ const prevEndIndex = content.indexOf(prevEndMarker);
943
+ if (prevEndIndex !== -1) {
944
+ const insertPos = prevEndIndex + prevEndMarker.length;
945
+ return content.slice(0, insertPos) + "\n" + newSection + content.slice(insertPos);
946
+ }
947
+ }
948
+ return content.trimEnd() + "\n" + newSection + "\n";
949
+ }
950
+ /**
951
+ * Find which file contains a specific command
952
+ */
953
+ function findFileForCommand(commandPath, files, allCommands, ignores) {
954
+ for (const [filePath, fileConfigRaw] of Object.entries(files)) {
955
+ const specifiedCommands = normalizeFileConfig(fileConfigRaw).commands;
956
+ if (filterIgnoredCommands(expandCommandPaths(specifiedCommands, allCommands), ignores).includes(commandPath)) return filePath;
957
+ }
958
+ return null;
959
+ }
960
+ /**
961
+ * Find which target commands are contained in a file
962
+ * Also expands each target command to include subcommands that are NOT explicitly in specifiedCommands
963
+ */
964
+ function findTargetCommandsInFile(targetCommands, filePath, files, allCommands, ignores) {
965
+ const fileConfigRaw = files[filePath];
966
+ if (!fileConfigRaw) return [];
967
+ const specifiedCommands = normalizeFileConfig(fileConfigRaw).commands;
968
+ const commandPaths = filterIgnoredCommands(expandCommandPaths(specifiedCommands, allCommands), ignores);
969
+ const expandedTargets = /* @__PURE__ */ new Set();
970
+ for (const targetCmd of targetCommands) {
971
+ if (!commandPaths.includes(targetCmd)) continue;
972
+ expandedTargets.add(targetCmd);
973
+ for (const cmdPath of commandPaths) if (isSubcommandOf(cmdPath, targetCmd) && !specifiedCommands.includes(cmdPath)) expandedTargets.add(cmdPath);
974
+ }
975
+ return Array.from(expandedTargets);
976
+ }
977
+ /**
978
+ * Generate a single command section with markers
979
+ */
980
+ function generateCommandSection(cmdPath, allCommands, render, filePath, fileMap) {
981
+ const info = allCommands.get(cmdPath);
982
+ if (!info) return null;
983
+ const renderedSection = render({
984
+ ...info,
985
+ filePath,
986
+ fileMap
987
+ });
988
+ return [
989
+ commandStartMarker(cmdPath),
990
+ renderedSection,
991
+ commandEndMarker(cmdPath)
992
+ ].join("\n");
993
+ }
994
+ /**
995
+ * Generate markdown for a file containing multiple commands
996
+ * Each command section is wrapped with markers for partial validation
997
+ */
998
+ function generateFileMarkdown(commandPaths, allCommands, render, filePath, fileMap, specifiedOrder, fileConfig) {
999
+ const sections = [];
1000
+ const header = fileConfig ? generateFileHeader(fileConfig) : null;
1001
+ if (header) sections.push(header);
1002
+ const sortedPaths = sortDepthFirst(commandPaths, specifiedOrder ?? []);
1003
+ for (const cmdPath of sortedPaths) {
1004
+ const section = generateCommandSection(cmdPath, allCommands, render, filePath, fileMap);
1005
+ if (section) sections.push(section);
1006
+ }
1007
+ return sections.join("\n");
1008
+ }
1009
+ /**
1010
+ * Build a map of command path to file path
1011
+ */
1012
+ function buildFileMap(files, allCommands, ignores) {
1013
+ const fileMap = {};
1014
+ for (const [filePath, fileConfigRaw] of Object.entries(files)) {
1015
+ const specifiedCommands = normalizeFileConfig(fileConfigRaw).commands;
1016
+ const commandPaths = filterIgnoredCommands(expandCommandPaths(specifiedCommands, allCommands), ignores);
1017
+ for (const cmdPath of commandPaths) fileMap[cmdPath] = filePath;
1018
+ }
1019
+ return fileMap;
1020
+ }
1021
+ /**
1022
+ * Execute examples for commands based on configuration
1023
+ */
1024
+ async function executeConfiguredExamples(allCommands, examplesConfig, rootCommand) {
1025
+ for (const [cmdPath, cmdConfig] of Object.entries(examplesConfig)) {
1026
+ const commandInfo = allCommands.get(cmdPath);
1027
+ if (!commandInfo?.examples?.length) continue;
1028
+ const config = cmdConfig === true ? {} : cmdConfig;
1029
+ const commandPath = cmdPath ? cmdPath.split(" ") : [];
1030
+ commandInfo.exampleResults = await executeExamples(commandInfo.examples, config, rootCommand, commandPath);
1031
+ }
1032
+ }
1033
+ /**
1034
+ * Generate documentation from command definition
1035
+ */
1036
+ async function generateDoc(config) {
1037
+ const { command, files, ignores = [], format = {}, formatter, examples: examplesConfig, targetCommands } = config;
1038
+ const updateMode = isUpdateMode();
1039
+ const allCommands = await collectAllCommands(command);
1040
+ if (examplesConfig) await executeConfiguredExamples(allCommands, examplesConfig, command);
1041
+ const allFilesCommands = [];
1042
+ for (const fileConfigRaw of Object.values(files)) {
1043
+ const fileConfig = normalizeFileConfig(fileConfigRaw);
1044
+ allFilesCommands.push(...fileConfig.commands);
1045
+ }
1046
+ validateIgnoresExist(ignores, allCommands);
1047
+ validateNoConflicts(allFilesCommands, ignores, allCommands);
1048
+ const fileMap = buildFileMap(files, allCommands, ignores);
1049
+ const results = [];
1050
+ let hasError = false;
1051
+ if (targetCommands && targetCommands.length > 0) {
1052
+ for (const targetCommand of targetCommands) if (!findFileForCommand(targetCommand, files, allCommands, ignores)) throw new Error(`Target command "${targetCommand}" not found in any file configuration`);
1053
+ }
1054
+ for (const [filePath, fileConfigRaw] of Object.entries(files)) {
1055
+ const fileConfig = normalizeFileConfig(fileConfigRaw);
1056
+ const specifiedCommands = fileConfig.commands;
1057
+ if (specifiedCommands.length === 0) continue;
1058
+ const commandPaths = filterIgnoredCommands(expandCommandPaths(specifiedCommands, allCommands), ignores);
1059
+ if (commandPaths.length === 0) continue;
1060
+ const minDepth = Math.min(...commandPaths.map((p) => allCommands.get(p)?.depth ?? 1));
1061
+ const adjustedHeadingLevel = Math.max(1, (format?.headingLevel ?? 1) - (minDepth - 1));
1062
+ const fileRenderer = createCommandRenderer({
1063
+ ...format,
1064
+ headingLevel: adjustedHeadingLevel
1065
+ });
1066
+ const render = fileConfig.render ?? fileRenderer;
1067
+ if (targetCommands !== void 0 && targetCommands.length > 0) {
1068
+ const fileTargetCommands = findTargetCommandsInFile(targetCommands, filePath, files, allCommands, ignores);
1069
+ if (fileTargetCommands.length === 0) continue;
1070
+ let existingContent = readFile(filePath);
1071
+ let fileStatus = "match";
1072
+ const diffs = [];
1073
+ for (const targetCommand of fileTargetCommands) {
1074
+ const rawSection = generateCommandSection(targetCommand, allCommands, render, filePath, fileMap);
1075
+ if (!rawSection) throw new Error(`Target command "${targetCommand}" not found in commands`);
1076
+ const header = targetCommand === "" && fileConfig ? generateFileHeader(fileConfig) : null;
1077
+ const generatedSection = await applyFormatter(header ? `${header}\n${rawSection}` : rawSection, formatter);
1078
+ if (!existingContent) {
1079
+ if (updateMode) {
1080
+ writeFile(filePath, generatedSection);
1081
+ existingContent = generatedSection;
1082
+ fileStatus = "created";
1083
+ } else {
1084
+ hasError = true;
1085
+ fileStatus = "diff";
1086
+ diffs.push(`File does not exist. Target command "${targetCommand}" section cannot be validated.`);
1087
+ }
1088
+ continue;
1089
+ }
1090
+ const existingSection = extractCommandSection(existingContent, targetCommand);
1091
+ const generatedSectionOnly = extractCommandSection(generatedSection, targetCommand);
1092
+ if (!generatedSectionOnly) throw new Error(`Generated content does not contain section for command "${targetCommand}"`);
1093
+ if (!existingSection) {
1094
+ if (updateMode) {
1095
+ existingContent = insertCommandSection(existingContent, targetCommand, generatedSectionOnly, specifiedCommands);
1096
+ writeFile(filePath, existingContent);
1097
+ if (fileStatus !== "created") fileStatus = "updated";
1098
+ } else {
1099
+ hasError = true;
1100
+ fileStatus = "diff";
1101
+ diffs.push(`Existing file does not contain section for command "${targetCommand}"`);
1102
+ }
1103
+ continue;
1104
+ }
1105
+ if (existingSection !== generatedSectionOnly) if (updateMode) {
1106
+ const updatedContent = replaceCommandSection(existingContent, targetCommand, generatedSectionOnly);
1107
+ if (updatedContent) {
1108
+ existingContent = updatedContent;
1109
+ writeFile(filePath, existingContent);
1110
+ if (fileStatus !== "created") fileStatus = "updated";
1111
+ } else throw new Error(`Failed to replace section for command "${targetCommand}"`);
1112
+ } else {
1113
+ hasError = true;
1114
+ fileStatus = "diff";
1115
+ diffs.push(formatDiff(existingSection, generatedSectionOnly));
1116
+ }
1117
+ }
1118
+ results.push({
1119
+ path: filePath,
1120
+ status: fileStatus,
1121
+ diff: diffs.length > 0 ? diffs.join("\n\n") : void 0
1122
+ });
1123
+ } else {
1124
+ const generatedMarkdown = await applyFormatter(generateFileMarkdown(commandPaths, allCommands, render, filePath, fileMap, specifiedCommands, fileConfig), formatter);
1125
+ const comparison = compareWithExisting(generatedMarkdown, filePath);
1126
+ if (comparison.match) results.push({
1127
+ path: filePath,
1128
+ status: "match"
1129
+ });
1130
+ else if (updateMode) {
1131
+ writeFile(filePath, generatedMarkdown);
1132
+ results.push({
1133
+ path: filePath,
1134
+ status: comparison.fileExists ? "updated" : "created"
1135
+ });
1136
+ } else {
1137
+ hasError = true;
1138
+ results.push({
1139
+ path: filePath,
1140
+ status: "diff",
1141
+ diff: comparison.diff
1142
+ });
1143
+ }
1144
+ }
1145
+ }
1146
+ return {
1147
+ success: !hasError,
1148
+ files: results,
1149
+ error: hasError ? `Documentation is out of date. Run with ${UPDATE_GOLDEN_ENV}=true to update.` : void 0
1150
+ };
1151
+ }
1152
+ /**
1153
+ * Assert that documentation matches golden files
1154
+ * Throws an error if there are differences and update mode is not enabled
1155
+ */
1156
+ async function assertDocMatch(config) {
1157
+ const result = await generateDoc(config);
1158
+ if (!result.success) {
1159
+ const diffMessages = result.files.filter((f) => f.status === "diff").map((f) => {
1160
+ let msg = `File: ${f.path}\n`;
1161
+ if (f.diff) msg += f.diff;
1162
+ return msg;
1163
+ }).join("\n\n");
1164
+ throw new Error(`Documentation does not match golden files.\n\n${diffMessages}\n\nRun with ${UPDATE_GOLDEN_ENV}=true to update the documentation.`);
1165
+ }
1166
+ }
1167
+ /**
1168
+ * Initialize documentation files by deleting them
1169
+ * Only deletes when update mode is enabled (POLITTY_DOCS_UPDATE=true)
1170
+ * Use this in beforeAll to ensure skipped tests don't leave stale sections
1171
+ * @param config - Config containing files to initialize, or a single file path
1172
+ * @param fileSystem - Optional fs implementation (useful when fs is mocked)
1173
+ */
1174
+ function initDocFile(config, fileSystem) {
1175
+ if (!isUpdateMode()) return;
1176
+ if (typeof config === "string") deleteFile(config, fileSystem);
1177
+ else for (const filePath of Object.keys(config.files)) deleteFile(filePath, fileSystem);
1178
+ }
1179
+
1180
+ //#endregion
1181
+ export { COMMAND_MARKER_PREFIX, UPDATE_GOLDEN_ENV, assertDocMatch, buildCommandInfo, collectAllCommands, commandEndMarker, commandStartMarker, compareWithExisting, createCommandRenderer, defaultRenderers, executeExamples, formatDiff, generateDoc, initDocFile, renderArgumentsList, renderArgumentsListFromArray, renderArgumentsTable, renderArgumentsTableFromArray, renderExamplesDefault, renderOptionsList, renderOptionsListFromArray, renderOptionsTable, renderOptionsTableFromArray, renderSubcommandsTable, renderSubcommandsTableFromArray, renderUsage, resolveLazyCommand, writeFile };
1182
+ //# sourceMappingURL=index.js.map