politty 0.0.1 → 0.1.1

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