@optique/man 1.4.0-dev.2899 → 1.4.0-dev.2935

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.
package/README.md CHANGED
@@ -98,6 +98,23 @@ const manPage = generateManPage(parser, {
98
98
  ~~~~
99
99
 
100
100
 
101
+ Markdown command references
102
+ ---------------------------
103
+
104
+ Import `formatCommandReference()` from `@optique/man/markdown` for one linked
105
+ reference containing explicitly supplied command pages. Supported built-in
106
+ definitions can supply pages through `collectDocPages()` from
107
+ `@optique/core/doc-pages`, with strict reporting for unsupported scopes. Neither
108
+ subpath is imported by ordinary parser or runner entry points.
109
+
110
+ See the [Markdown reference guide] and the [runnable example]. Coverage is
111
+ author-declared, including whether runner-added help/version/completion forms
112
+ are documented.
113
+
114
+ [Markdown reference guide]: https://optique.dev/concepts/markdown-reference
115
+ [runnable example]: https://github.com/dahlia/optique/tree/main/packages/man/test/fixtures/markdown-reference
116
+
117
+
101
118
  Documentation
102
119
  -------------
103
120
 
@@ -0,0 +1,30 @@
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") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+
25
+ Object.defineProperty(exports, '__toESM', {
26
+ enumerable: true,
27
+ get: function () {
28
+ return __toESM;
29
+ }
30
+ });
package/dist/cli.cjs CHANGED
@@ -1,29 +1,31 @@
1
1
  #!/usr/bin/env node
2
- const require_man = require('./man-Dp2YQIdn.cjs');
2
+ const require_chunk = require('./chunk-CUT6urMc.cjs');
3
3
  require('./roff-C8MvQ2tL.cjs');
4
- const require_generator = require('./generator-OV4-0NLg.cjs');
5
- const __optique_core_constructs = require_man.__toESM(require("@optique/core/constructs"));
6
- const __optique_core_primitives = require_man.__toESM(require("@optique/core/primitives"));
7
- const __optique_core_valueparser = require_man.__toESM(require("@optique/core/valueparser"));
8
- const __optique_core_modifiers = require_man.__toESM(require("@optique/core/modifiers"));
9
- const __optique_core_message = require_man.__toESM(require("@optique/core/message"));
10
- const __optique_core_program = require_man.__toESM(require("@optique/core/program"));
11
- const __optique_run = require_man.__toESM(require("@optique/run"));
12
- const node_fs = require_man.__toESM(require("node:fs"));
13
- const node_fs_promises = require_man.__toESM(require("node:fs/promises"));
14
- const node_path = require_man.__toESM(require("node:path"));
15
- const node_process = require_man.__toESM(require("node:process"));
16
- const node_url = require_man.__toESM(require("node:url"));
4
+ require('./man-ChbwsIhG.cjs');
5
+ const require_generator = require('./generator-DYAqAqpo.cjs');
6
+ const __optique_core_constructs = require_chunk.__toESM(require("@optique/core/constructs"));
7
+ const __optique_core_primitives = require_chunk.__toESM(require("@optique/core/primitives"));
8
+ const __optique_core_valueparser = require_chunk.__toESM(require("@optique/core/valueparser"));
9
+ const __optique_core_modifiers = require_chunk.__toESM(require("@optique/core/modifiers"));
10
+ const __optique_core_message = require_chunk.__toESM(require("@optique/core/message"));
11
+ const __optique_core_program = require_chunk.__toESM(require("@optique/core/program"));
12
+ const __optique_run = require_chunk.__toESM(require("@optique/run"));
13
+ const node_fs = require_chunk.__toESM(require("node:fs"));
14
+ const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
15
+ const node_path = require_chunk.__toESM(require("node:path"));
16
+ const node_process = require_chunk.__toESM(require("node:process"));
17
+ const node_url = require_chunk.__toESM(require("node:url"));
17
18
 
18
19
  //#region deno.json
19
20
  var name = "@optique/man";
20
- var version = "1.4.0-dev.2899+9c2973b0";
21
+ var version = "1.4.0-dev.2935+fc126fd1";
21
22
  var license = "MIT";
22
23
  var exports$1 = {
23
24
  ".": "./src/index.ts",
24
25
  "./roff": "./src/roff.ts",
25
26
  "./man": "./src/man.ts",
26
- "./cli": "./src/cli.ts"
27
+ "./cli": "./src/cli.ts",
28
+ "./markdown": "./src/markdown.ts"
27
29
  };
28
30
  var imports = {
29
31
  "#src/": "./src/",
@@ -47,6 +49,7 @@ var tasks = {
47
49
  "test:bun"
48
50
  ] }
49
51
  };
52
+ var publish = { "exclude": ["test/prototypes/", "test/fixtures/markdown-reference/"] };
50
53
  var deno_default = {
51
54
  name,
52
55
  version,
@@ -54,7 +57,8 @@ var deno_default = {
54
57
  exports: exports$1,
55
58
  imports,
56
59
  exclude,
57
- tasks
60
+ tasks,
61
+ publish
58
62
  };
59
63
 
60
64
  //#endregion
package/dist/cli.js CHANGED
@@ -17,13 +17,14 @@ import { fileURLToPath, pathToFileURL } from "node:url";
17
17
 
18
18
  //#region deno.json
19
19
  var name = "@optique/man";
20
- var version = "1.4.0-dev.2899+9c2973b0";
20
+ var version = "1.4.0-dev.2935+fc126fd1";
21
21
  var license = "MIT";
22
22
  var exports = {
23
23
  ".": "./src/index.ts",
24
24
  "./roff": "./src/roff.ts",
25
25
  "./man": "./src/man.ts",
26
- "./cli": "./src/cli.ts"
26
+ "./cli": "./src/cli.ts",
27
+ "./markdown": "./src/markdown.ts"
27
28
  };
28
29
  var imports = {
29
30
  "#src/": "./src/",
@@ -47,6 +48,7 @@ var tasks = {
47
48
  "test:bun"
48
49
  ] }
49
50
  };
51
+ var publish = { "exclude": ["test/prototypes/", "test/fixtures/markdown-reference/"] };
50
52
  var deno_default = {
51
53
  name,
52
54
  version,
@@ -54,7 +56,8 @@ var deno_default = {
54
56
  exports,
55
57
  imports,
56
58
  exclude,
57
- tasks
59
+ tasks,
60
+ publish
58
61
  };
59
62
 
60
63
  //#endregion
@@ -1,5 +1,6 @@
1
- const require_man = require('./man-Dp2YQIdn.cjs');
2
- const __optique_core_parser = require_man.__toESM(require("@optique/core/parser"));
1
+ const require_chunk = require('./chunk-CUT6urMc.cjs');
2
+ const require_man = require('./man-ChbwsIhG.cjs');
3
+ const __optique_core_parser = require_chunk.__toESM(require("@optique/core/parser"));
3
4
 
4
5
  //#region src/generator.ts
5
6
  /**
@@ -1,6 +1,6 @@
1
- require('./man-Dp2YQIdn.cjs');
2
1
  require('./roff-C8MvQ2tL.cjs');
3
- const require_generator = require('./generator-OV4-0NLg.cjs');
2
+ require('./man-ChbwsIhG.cjs');
3
+ const require_generator = require('./generator-DYAqAqpo.cjs');
4
4
 
5
5
  exports.generateManPage = require_generator.generateManPage;
6
6
  exports.generateManPageAsync = require_generator.generateManPageAsync;
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
- const require_man = require('./man-Dp2YQIdn.cjs');
2
1
  const require_roff = require('./roff-C8MvQ2tL.cjs');
3
- const require_generator = require('./generator-OV4-0NLg.cjs');
2
+ const require_man = require('./man-ChbwsIhG.cjs');
3
+ const require_generator = require('./generator-DYAqAqpo.cjs');
4
4
 
5
5
  exports.escapeHyphens = require_roff.escapeHyphens;
6
6
  exports.escapeQuotedValue = require_roff.escapeQuotedValue;
@@ -1,30 +1,8 @@
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") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
-
23
- //#endregion
1
+ const require_chunk = require('./chunk-CUT6urMc.cjs');
24
2
  const require_roff = require('./roff-C8MvQ2tL.cjs');
25
- const __optique_core_doc = __toESM(require("@optique/core/doc"));
26
- const __optique_core_usage = __toESM(require("@optique/core/usage"));
27
- const __optique_core_internal_usage = __toESM(require("@optique/core/internal/usage"));
3
+ const __optique_core_doc = require_chunk.__toESM(require("@optique/core/doc"));
4
+ const __optique_core_usage = require_chunk.__toESM(require("@optique/core/usage"));
5
+ const __optique_core_internal_usage = require_chunk.__toESM(require("@optique/core/internal/usage"));
28
6
 
29
7
  //#region src/man.ts
30
8
  /**
@@ -449,12 +427,6 @@ function formatDocPageAsMan(page, options) {
449
427
  }
450
428
 
451
429
  //#endregion
452
- Object.defineProperty(exports, '__toESM', {
453
- enumerable: true,
454
- get: function () {
455
- return __toESM;
456
- }
457
- });
458
430
  Object.defineProperty(exports, 'formatDateForMan', {
459
431
  enumerable: true,
460
432
  get: function () {
package/dist/man.cjs CHANGED
@@ -1,5 +1,5 @@
1
- const require_man = require('./man-Dp2YQIdn.cjs');
2
1
  require('./roff-C8MvQ2tL.cjs');
2
+ const require_man = require('./man-ChbwsIhG.cjs');
3
3
 
4
4
  exports.formatDateForMan = require_man.formatDateForMan;
5
5
  exports.formatDocPageAsMan = require_man.formatDocPageAsMan;
@@ -0,0 +1,248 @@
1
+ const require_chunk = require('./chunk-CUT6urMc.cjs');
2
+ const __optique_core_usage = require_chunk.__toESM(require("@optique/core/usage"));
3
+
4
+ //#region src/markdown.ts
5
+ function validateToken(token) {
6
+ if (typeof token !== "string" || token.length === 0 || /[\s\x00-\x1f\x7f-\x9f]/u.test(token) || !token.isWellFormed()) throw new TypeError("Command paths and aliases must contain nonempty tokens without whitespace or control characters.");
7
+ }
8
+ function pathKey(path) {
9
+ for (const token of path) validateToken(token);
10
+ return JSON.stringify(path);
11
+ }
12
+ /**
13
+ * Returns an injective anchor from the UTF-8 bytes of a JSON canonical path.
14
+ * The dot separates these IDs from GitHub/VitePress heading slugs.
15
+ * @param path Canonical command tokens; an empty array identifies root.
16
+ * @returns The explicit anchor ID, without a leading hash.
17
+ * @throws {TypeError} If a token is empty, ill-formed, or contains whitespace
18
+ * or control characters.
19
+ * @since 1.4.0
20
+ */
21
+ function getCommandReferenceAnchor(path) {
22
+ const bytes = new TextEncoder().encode(pathKey(path));
23
+ return "optique-command." + Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
24
+ }
25
+ function singleLine(input) {
26
+ return input.replace(/[\r\n\t]/g, " ").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/g, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
27
+ }
28
+ function code(input) {
29
+ const content = singleLine(input);
30
+ if (content === "") return "<code></code>";
31
+ const fence = "`".repeat(Math.max(0, ...Array.from(content.matchAll(/`+/g), (match) => match[0].length)) + 1);
32
+ const padding = content.startsWith("`") || content.endsWith("`") || /^ .* $/u.test(content) ? " " : "";
33
+ return fence + padding + content + padding + fence;
34
+ }
35
+ function prose(input) {
36
+ return input.replace(/\r\n?/g, "\n").split("\n").map((line) => {
37
+ const escaped = singleLine(line).replace(/[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/g, (char) => {
38
+ if (char === "&") return "&amp;";
39
+ if (char === "<") return "&lt;";
40
+ if (char === ">") return "&gt;";
41
+ return "\\" + char;
42
+ });
43
+ return escaped.replace(/^ +/, (spaces) => "&#32;".repeat(spaces.length));
44
+ }).join("\\\n");
45
+ }
46
+ function formatMessageTerm(term) {
47
+ switch (term.type) {
48
+ case "text": return prose(term.text);
49
+ case "optionName": return code(term.optionName);
50
+ case "optionNames": return term.optionNames.map(code).join(", ");
51
+ case "metavar": return code(term.metavar);
52
+ case "value": return code(term.value);
53
+ case "values": return term.values.map(code).join(" ");
54
+ case "envVar": return code(term.envVar);
55
+ case "commandLine": return code(term.commandLine);
56
+ case "url": return code(term.url.href);
57
+ case "lineBreak": return "\\\n";
58
+ }
59
+ }
60
+ function formatMessage(message) {
61
+ let previousCode = false;
62
+ return message.map((term) => {
63
+ const content = formatMessageTerm(term);
64
+ const semanticCode = term.type !== "text" && term.type !== "lineBreak";
65
+ const separator = previousCode && semanticCode && content !== "" ? "<!-- -->" : "";
66
+ if (content !== "") previousCode = semanticCode;
67
+ return separator + content;
68
+ }).join("").replace(/(?:\\\n)+$/, "");
69
+ }
70
+ function link(label, path) {
71
+ return `<a href="#${getCommandReferenceAnchor(path)}"><code>${prose(singleLine(label))}</code></a>`;
72
+ }
73
+ function visibleAliases(entry) {
74
+ const hidden = new Set(entry.hiddenAliases);
75
+ return [...new Set(entry.aliases)].filter((alias) => !hidden.has(alias) && alias !== entry.path.at(-1));
76
+ }
77
+ function hiddenInTree(entry, index) {
78
+ for (let length = 1; length <= entry.path.length; length++) if ((0, __optique_core_usage.isDocHidden)(index.get(pathKey(entry.path.slice(0, length)))?.hidden)) return true;
79
+ return false;
80
+ }
81
+ function validateEntries(entries) {
82
+ const index = /* @__PURE__ */ new Map();
83
+ const spellings = /* @__PURE__ */ new Map();
84
+ for (const entry of entries) {
85
+ const key = pathKey(entry.path);
86
+ if (index.has(key)) throw new TypeError(`Duplicate command path: ${key}`);
87
+ index.set(key, entry);
88
+ for (const alias of [...entry.aliases ?? [], ...entry.hiddenAliases ?? []]) validateToken(alias);
89
+ if (entry.path.length === 0) {
90
+ if (entry.aliases?.length || entry.hiddenAliases?.length || entry.hidden) throw new TypeError("The root page cannot have aliases or hidden visibility.");
91
+ continue;
92
+ }
93
+ for (const name of [
94
+ entry.path.at(-1),
95
+ ...entry.aliases ?? [],
96
+ ...entry.hiddenAliases ?? []
97
+ ]) {
98
+ const spelling = pathKey([...entry.path.slice(0, -1), name]);
99
+ const destination = spellings.get(spelling);
100
+ if (destination != null && destination !== key) throw new TypeError(`Ambiguous command spelling: ${spelling}`);
101
+ spellings.set(spelling, key);
102
+ }
103
+ }
104
+ return index;
105
+ }
106
+ function commandTarget(entry, parent, index) {
107
+ return entry.term.type === "command" ? index.get(pathKey([...parent.path, entry.term.name])) : void 0;
108
+ }
109
+ function entryHidden(entry) {
110
+ return "hidden" in entry.term && (0, __optique_core_usage.isDocHidden)(entry.term.hidden);
111
+ }
112
+ function validateCoverage(coverage) {
113
+ if (coverage == null || !["all-visible", "partial"].includes(coverage.commands) || [
114
+ coverage.help,
115
+ coverage.version,
116
+ coverage.completion
117
+ ].some((value) => value !== "included" && value !== "excluded")) throw new TypeError("Reference coverage must declare commands and runner-added help, version, and completion inputs.");
118
+ }
119
+ /**
120
+ * Renders supplied command pages in caller order as one Markdown reference.
121
+ *
122
+ * Coverage is always author-declared. Visible child references and canonical
123
+ * prefixes are checked, but commands absent from every supplied page cannot be
124
+ * discovered. The renderer never collects pages or evaluates parser callbacks.
125
+ * Messages are documentation text rather than Markdown. Generated anchors and
126
+ * internal links require raw HTML support; bare web/email text may be linkified
127
+ * by a consumer. Every page's usage already includes its command tokens.
128
+ *
129
+ * @param entries Canonical command pages, with explicit aliases and visibility.
130
+ * @param options Program metadata, coverage declaration and presentation policy.
131
+ * @returns A deterministic Markdown document ending in one newline.
132
+ * @throws {TypeError} If paths/aliases are invalid or ambiguous, a canonical
133
+ * path is duplicated, metadata is invalid, or declared complete coverage has
134
+ * missing visible command pages.
135
+ * @since 1.4.0
136
+ */
137
+ function formatCommandReference(entries, options) {
138
+ (0, __optique_core_usage.formatUsage)(options.name, [], { colors: false });
139
+ validateCoverage(options.coverage);
140
+ const index = validateEntries(entries);
141
+ const visible = entries.filter((entry) => !hiddenInTree(entry, index));
142
+ const unresolved = options.unresolved?.filter((gap) => !hiddenInTree({
143
+ path: gap.path,
144
+ page: { sections: [] }
145
+ }, index)) ?? [];
146
+ for (const gap of unresolved) {
147
+ pathKey(gap.path);
148
+ if (gap.scope !== "page" && gap.scope !== "subtree") throw new TypeError("An unresolved scope must be page or subtree.");
149
+ }
150
+ if (unresolved.length > 0 && options.coverage.commands === "all-visible") throw new TypeError("Unresolved command documentation requires partial coverage.");
151
+ const missing = /* @__PURE__ */ new Map();
152
+ if (!index.has("[]")) missing.set("[]", []);
153
+ for (const entry of visible) {
154
+ for (let length = 0; length < entry.path.length; length++) {
155
+ const prefix = entry.path.slice(0, length);
156
+ if (!index.has(pathKey(prefix))) missing.set(pathKey(prefix), prefix);
157
+ }
158
+ for (const section of entry.page.sections) for (const item of section.entries) {
159
+ if (entryHidden(item) || item.term.type !== "command") continue;
160
+ const target = commandTarget(item, entry, index);
161
+ if (target == null) {
162
+ const path = [...entry.path, item.term.name];
163
+ missing.set(pathKey(path), path);
164
+ }
165
+ }
166
+ }
167
+ if (missing.size > 0 && options.coverage.commands === "all-visible") throw new TypeError(`Missing command pages: ${[...missing.values()].map((path) => JSON.stringify(path)).join(", ")}`);
168
+ const output = [`# ${code(options.name)} command reference`];
169
+ const addMessage = (content) => {
170
+ if (content != null && content.length > 0) output.push(formatMessage(content));
171
+ };
172
+ if (options.version != null) output.push(`Version: ${code(options.version)}`);
173
+ addMessage(options.brief);
174
+ addMessage(options.description);
175
+ output.push(`Coverage: author-declared ${options.coverage.commands === "all-visible" ? "all visible commands" : "partial command reference"}.`);
176
+ output.push("Runner inputs: " + [
177
+ "help",
178
+ "version",
179
+ "completion"
180
+ ].map((feature) => `${feature}: ${options.coverage[feature] === "included" ? "included" : "not documented here (if configured)"}`).join("; ") + ".");
181
+ if (missing.size > 0) output.push("Missing command pages: " + [...missing.values()].map((path) => code([options.name, ...path].join(" "))).join(", ") + ".");
182
+ if (unresolved.length > 0) output.push("## Unresolved documentation", unresolved.map((gap) => ` - ${code([options.name, ...gap.path].join(" "))} (${gap.scope}): ${code(gap.reason)}`).join("\n"));
183
+ output.push("## Contents");
184
+ output.push(visible.map((entry) => {
185
+ const fullName = [options.name, ...entry.path].join(" ");
186
+ const aliases = entry.showAliases ?? options.showAliases ?? false ? visibleAliases(entry).map((alias) => link([
187
+ options.name,
188
+ ...entry.path.slice(0, -1),
189
+ alias
190
+ ].join(" "), entry.path)) : [];
191
+ return ` - ${link(fullName, entry.path)}${aliases.length > 0 ? ` (aliases: ${aliases.join(", ")})` : ""}`;
192
+ }).join("\n"));
193
+ for (const entry of visible) {
194
+ const page = entry.page;
195
+ output.push(`<a id="${getCommandReferenceAnchor(entry.path)}"></a>`);
196
+ output.push(`## ${code([options.name, ...entry.path].join(" "))}`);
197
+ addMessage(page.brief);
198
+ if ((options.showUsage ?? page.showUsage ?? true) && page.usage != null) {
199
+ const usage = (0, __optique_core_usage.formatUsage)(options.name, page.usage, { colors: false }).replace(/\r\n?/g, "\n").split("\n").map(singleLine).join("\n");
200
+ const fence = "~".repeat(Math.max(3, ...Array.from(usage.matchAll(/~+/g), (match) => match[0].length + 1)));
201
+ output.push("### Usage", `${fence} text\n${usage}\n${fence}`);
202
+ }
203
+ addMessage(page.description);
204
+ for (const section of page.sections) {
205
+ const items = [];
206
+ for (const item of section.entries) {
207
+ if (entryHidden(item)) continue;
208
+ const target = commandTarget(item, entry, index);
209
+ if (target != null && hiddenInTree(target, index)) continue;
210
+ const label = (0, __optique_core_usage.formatUsageTerm)(item.term, {
211
+ context: "doc",
212
+ colors: false
213
+ });
214
+ if (label === "") continue;
215
+ const aliases = target != null && (item.showAliases ?? target.showAliases ?? options.showAliases ?? false) ? visibleAliases(target).map((alias) => link(alias, target.path)) : [];
216
+ let content = " - " + (target == null ? code(label) : link(label, target.path));
217
+ if (aliases.length > 0) content += ` (aliases: ${aliases.join(", ")})`;
218
+ const details = [];
219
+ if (item.description != null) details.push(formatMessage(item.description));
220
+ if (item.default != null) details.push("Default: " + formatMessage(item.default));
221
+ if (item.choices != null) details.push("Choices: " + formatMessage(item.choices));
222
+ if (item.envVars?.length) details.push("Environment: " + item.envVars.map(code).join(", "));
223
+ if (details.length > 0) content += "\n\n" + details.join("\n\n").split("\n").map((line) => " " + line).join("\n");
224
+ items.push(content);
225
+ }
226
+ if (items.length > 0) output.push("### " + prose(singleLine(section.title ?? "Inputs")), items.join("\n\n"));
227
+ }
228
+ const bindings = page.environmentBindings?.filter((binding) => !(0, __optique_core_usage.isDocHidden)(binding.hidden));
229
+ if (bindings?.length) output.push("### Environment", bindings.map((binding) => ` - ${code(binding.name)}${binding.description == null ? "" : ": " + formatMessage(binding.description).replaceAll("\n", "\n ")}`).join("\n"));
230
+ for (const [title, content] of [
231
+ ["Examples", page.examples],
232
+ ["Author", page.author],
233
+ ["Bugs", page.bugs],
234
+ ["Notes", page.footer]
235
+ ]) if (content != null && content.length > 0) output.push(`### ${title}`, formatMessage(content));
236
+ }
237
+ for (const [title, content] of [
238
+ ["Examples", options.examples],
239
+ ["Author", options.author],
240
+ ["Bugs", options.bugs],
241
+ ["Notes", options.footer]
242
+ ]) if (content != null && content.length > 0) output.push(`## ${title}`, formatMessage(content));
243
+ return output.filter((part) => part !== "").join("\n\n") + "\n";
244
+ }
245
+
246
+ //#endregion
247
+ exports.formatCommandReference = formatCommandReference;
248
+ exports.getCommandReferenceAnchor = getCommandReferenceAnchor;
@@ -0,0 +1,81 @@
1
+ import { ProgramMetadata } from "@optique/core/program";
2
+ import { DocPage } from "@optique/core/doc";
3
+ import { HiddenVisibility } from "@optique/core/usage";
4
+
5
+ //#region src/markdown.d.ts
6
+
7
+ /** A supplied page and its canonical identity. @since 1.4.0 */
8
+ interface MarkdownCommandEntry {
9
+ /** Canonical command tokens, excluding the program name. Empty for root. */
10
+ readonly path: readonly string[];
11
+ /** Already constructed documentation; usage includes command tokens. */
12
+ readonly page: DocPage;
13
+ /** Visible alternative names of this command under its canonical parent. */
14
+ readonly aliases?: readonly string[];
15
+ /** Accepted names that must never be displayed. */
16
+ readonly hiddenAliases?: readonly string[];
17
+ /** Documentation-hidden parents suppress every supplied descendant. */
18
+ readonly hidden?: HiddenVisibility;
19
+ /** Alias display override for this command's contents entry. */
20
+ readonly showAliases?: boolean;
21
+ }
22
+ /** Author assertions about the supplied reference's scope. @since 1.4.0 */
23
+ interface MarkdownReferenceCoverage {
24
+ /** Whether the author has supplied every visible command in this scope. */
25
+ readonly commands: "all-visible" | "partial";
26
+ /** Whether all configured help forms in this scope are documented. */
27
+ readonly help: "included" | "excluded";
28
+ /** Whether all configured version forms in this scope are documented. */
29
+ readonly version: "included" | "excluded";
30
+ /** Whether all configured completion forms in this scope are documented. */
31
+ readonly completion: "included" | "excluded";
32
+ }
33
+ /** Program metadata and presentation options for a reference. @since 1.4.0 */
34
+ interface MarkdownReferenceOptions extends ProgramMetadata {
35
+ /** Required author declaration, printed in the artifact. */
36
+ readonly coverage: MarkdownReferenceCoverage;
37
+ /** Default alias display, overridden by command entries and DocEntries. */
38
+ readonly showAliases?: boolean;
39
+ /** Override each supplied page's showUsage setting. Defaults to that page. */
40
+ readonly showUsage?: boolean;
41
+ /** Collection omissions to print in a partial reference. Hidden scopes skip them. */
42
+ readonly unresolved?: readonly {
43
+ /** Visible enclosing canonical path. */
44
+ readonly path: readonly string[];
45
+ /** Whether the gap affects one page or an opaque subtree. */
46
+ readonly scope: "page" | "subtree";
47
+ /** Documentation-only diagnostic, rendered as text. */
48
+ readonly reason: string;
49
+ }[];
50
+ }
51
+ /**
52
+ * Returns an injective anchor from the UTF-8 bytes of a JSON canonical path.
53
+ * The dot separates these IDs from GitHub/VitePress heading slugs.
54
+ * @param path Canonical command tokens; an empty array identifies root.
55
+ * @returns The explicit anchor ID, without a leading hash.
56
+ * @throws {TypeError} If a token is empty, ill-formed, or contains whitespace
57
+ * or control characters.
58
+ * @since 1.4.0
59
+ */
60
+ declare function getCommandReferenceAnchor(path: readonly string[]): string;
61
+ /**
62
+ * Renders supplied command pages in caller order as one Markdown reference.
63
+ *
64
+ * Coverage is always author-declared. Visible child references and canonical
65
+ * prefixes are checked, but commands absent from every supplied page cannot be
66
+ * discovered. The renderer never collects pages or evaluates parser callbacks.
67
+ * Messages are documentation text rather than Markdown. Generated anchors and
68
+ * internal links require raw HTML support; bare web/email text may be linkified
69
+ * by a consumer. Every page's usage already includes its command tokens.
70
+ *
71
+ * @param entries Canonical command pages, with explicit aliases and visibility.
72
+ * @param options Program metadata, coverage declaration and presentation policy.
73
+ * @returns A deterministic Markdown document ending in one newline.
74
+ * @throws {TypeError} If paths/aliases are invalid or ambiguous, a canonical
75
+ * path is duplicated, metadata is invalid, or declared complete coverage has
76
+ * missing visible command pages.
77
+ * @since 1.4.0
78
+ */
79
+ declare function formatCommandReference(entries: readonly MarkdownCommandEntry[], options: MarkdownReferenceOptions): string;
80
+ //#endregion
81
+ export { MarkdownCommandEntry, MarkdownReferenceCoverage, MarkdownReferenceOptions, formatCommandReference, getCommandReferenceAnchor };
@@ -0,0 +1,81 @@
1
+ import { ProgramMetadata } from "@optique/core/program";
2
+ import { DocPage } from "@optique/core/doc";
3
+ import { HiddenVisibility } from "@optique/core/usage";
4
+
5
+ //#region src/markdown.d.ts
6
+
7
+ /** A supplied page and its canonical identity. @since 1.4.0 */
8
+ interface MarkdownCommandEntry {
9
+ /** Canonical command tokens, excluding the program name. Empty for root. */
10
+ readonly path: readonly string[];
11
+ /** Already constructed documentation; usage includes command tokens. */
12
+ readonly page: DocPage;
13
+ /** Visible alternative names of this command under its canonical parent. */
14
+ readonly aliases?: readonly string[];
15
+ /** Accepted names that must never be displayed. */
16
+ readonly hiddenAliases?: readonly string[];
17
+ /** Documentation-hidden parents suppress every supplied descendant. */
18
+ readonly hidden?: HiddenVisibility;
19
+ /** Alias display override for this command's contents entry. */
20
+ readonly showAliases?: boolean;
21
+ }
22
+ /** Author assertions about the supplied reference's scope. @since 1.4.0 */
23
+ interface MarkdownReferenceCoverage {
24
+ /** Whether the author has supplied every visible command in this scope. */
25
+ readonly commands: "all-visible" | "partial";
26
+ /** Whether all configured help forms in this scope are documented. */
27
+ readonly help: "included" | "excluded";
28
+ /** Whether all configured version forms in this scope are documented. */
29
+ readonly version: "included" | "excluded";
30
+ /** Whether all configured completion forms in this scope are documented. */
31
+ readonly completion: "included" | "excluded";
32
+ }
33
+ /** Program metadata and presentation options for a reference. @since 1.4.0 */
34
+ interface MarkdownReferenceOptions extends ProgramMetadata {
35
+ /** Required author declaration, printed in the artifact. */
36
+ readonly coverage: MarkdownReferenceCoverage;
37
+ /** Default alias display, overridden by command entries and DocEntries. */
38
+ readonly showAliases?: boolean;
39
+ /** Override each supplied page's showUsage setting. Defaults to that page. */
40
+ readonly showUsage?: boolean;
41
+ /** Collection omissions to print in a partial reference. Hidden scopes skip them. */
42
+ readonly unresolved?: readonly {
43
+ /** Visible enclosing canonical path. */
44
+ readonly path: readonly string[];
45
+ /** Whether the gap affects one page or an opaque subtree. */
46
+ readonly scope: "page" | "subtree";
47
+ /** Documentation-only diagnostic, rendered as text. */
48
+ readonly reason: string;
49
+ }[];
50
+ }
51
+ /**
52
+ * Returns an injective anchor from the UTF-8 bytes of a JSON canonical path.
53
+ * The dot separates these IDs from GitHub/VitePress heading slugs.
54
+ * @param path Canonical command tokens; an empty array identifies root.
55
+ * @returns The explicit anchor ID, without a leading hash.
56
+ * @throws {TypeError} If a token is empty, ill-formed, or contains whitespace
57
+ * or control characters.
58
+ * @since 1.4.0
59
+ */
60
+ declare function getCommandReferenceAnchor(path: readonly string[]): string;
61
+ /**
62
+ * Renders supplied command pages in caller order as one Markdown reference.
63
+ *
64
+ * Coverage is always author-declared. Visible child references and canonical
65
+ * prefixes are checked, but commands absent from every supplied page cannot be
66
+ * discovered. The renderer never collects pages or evaluates parser callbacks.
67
+ * Messages are documentation text rather than Markdown. Generated anchors and
68
+ * internal links require raw HTML support; bare web/email text may be linkified
69
+ * by a consumer. Every page's usage already includes its command tokens.
70
+ *
71
+ * @param entries Canonical command pages, with explicit aliases and visibility.
72
+ * @param options Program metadata, coverage declaration and presentation policy.
73
+ * @returns A deterministic Markdown document ending in one newline.
74
+ * @throws {TypeError} If paths/aliases are invalid or ambiguous, a canonical
75
+ * path is duplicated, metadata is invalid, or declared complete coverage has
76
+ * missing visible command pages.
77
+ * @since 1.4.0
78
+ */
79
+ declare function formatCommandReference(entries: readonly MarkdownCommandEntry[], options: MarkdownReferenceOptions): string;
80
+ //#endregion
81
+ export { MarkdownCommandEntry, MarkdownReferenceCoverage, MarkdownReferenceOptions, formatCommandReference, getCommandReferenceAnchor };
@@ -0,0 +1,246 @@
1
+ import { formatUsage, formatUsageTerm, isDocHidden } from "@optique/core/usage";
2
+
3
+ //#region src/markdown.ts
4
+ function validateToken(token) {
5
+ if (typeof token !== "string" || token.length === 0 || /[\s\x00-\x1f\x7f-\x9f]/u.test(token) || !token.isWellFormed()) throw new TypeError("Command paths and aliases must contain nonempty tokens without whitespace or control characters.");
6
+ }
7
+ function pathKey(path) {
8
+ for (const token of path) validateToken(token);
9
+ return JSON.stringify(path);
10
+ }
11
+ /**
12
+ * Returns an injective anchor from the UTF-8 bytes of a JSON canonical path.
13
+ * The dot separates these IDs from GitHub/VitePress heading slugs.
14
+ * @param path Canonical command tokens; an empty array identifies root.
15
+ * @returns The explicit anchor ID, without a leading hash.
16
+ * @throws {TypeError} If a token is empty, ill-formed, or contains whitespace
17
+ * or control characters.
18
+ * @since 1.4.0
19
+ */
20
+ function getCommandReferenceAnchor(path) {
21
+ const bytes = new TextEncoder().encode(pathKey(path));
22
+ return "optique-command." + Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
23
+ }
24
+ function singleLine(input) {
25
+ return input.replace(/[\r\n\t]/g, " ").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/g, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
26
+ }
27
+ function code(input) {
28
+ const content = singleLine(input);
29
+ if (content === "") return "<code></code>";
30
+ const fence = "`".repeat(Math.max(0, ...Array.from(content.matchAll(/`+/g), (match) => match[0].length)) + 1);
31
+ const padding = content.startsWith("`") || content.endsWith("`") || /^ .* $/u.test(content) ? " " : "";
32
+ return fence + padding + content + padding + fence;
33
+ }
34
+ function prose(input) {
35
+ return input.replace(/\r\n?/g, "\n").split("\n").map((line) => {
36
+ const escaped = singleLine(line).replace(/[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/g, (char) => {
37
+ if (char === "&") return "&amp;";
38
+ if (char === "<") return "&lt;";
39
+ if (char === ">") return "&gt;";
40
+ return "\\" + char;
41
+ });
42
+ return escaped.replace(/^ +/, (spaces) => "&#32;".repeat(spaces.length));
43
+ }).join("\\\n");
44
+ }
45
+ function formatMessageTerm(term) {
46
+ switch (term.type) {
47
+ case "text": return prose(term.text);
48
+ case "optionName": return code(term.optionName);
49
+ case "optionNames": return term.optionNames.map(code).join(", ");
50
+ case "metavar": return code(term.metavar);
51
+ case "value": return code(term.value);
52
+ case "values": return term.values.map(code).join(" ");
53
+ case "envVar": return code(term.envVar);
54
+ case "commandLine": return code(term.commandLine);
55
+ case "url": return code(term.url.href);
56
+ case "lineBreak": return "\\\n";
57
+ }
58
+ }
59
+ function formatMessage(message) {
60
+ let previousCode = false;
61
+ return message.map((term) => {
62
+ const content = formatMessageTerm(term);
63
+ const semanticCode = term.type !== "text" && term.type !== "lineBreak";
64
+ const separator = previousCode && semanticCode && content !== "" ? "<!-- -->" : "";
65
+ if (content !== "") previousCode = semanticCode;
66
+ return separator + content;
67
+ }).join("").replace(/(?:\\\n)+$/, "");
68
+ }
69
+ function link(label, path) {
70
+ return `<a href="#${getCommandReferenceAnchor(path)}"><code>${prose(singleLine(label))}</code></a>`;
71
+ }
72
+ function visibleAliases(entry) {
73
+ const hidden = new Set(entry.hiddenAliases);
74
+ return [...new Set(entry.aliases)].filter((alias) => !hidden.has(alias) && alias !== entry.path.at(-1));
75
+ }
76
+ function hiddenInTree(entry, index) {
77
+ for (let length = 1; length <= entry.path.length; length++) if (isDocHidden(index.get(pathKey(entry.path.slice(0, length)))?.hidden)) return true;
78
+ return false;
79
+ }
80
+ function validateEntries(entries) {
81
+ const index = /* @__PURE__ */ new Map();
82
+ const spellings = /* @__PURE__ */ new Map();
83
+ for (const entry of entries) {
84
+ const key = pathKey(entry.path);
85
+ if (index.has(key)) throw new TypeError(`Duplicate command path: ${key}`);
86
+ index.set(key, entry);
87
+ for (const alias of [...entry.aliases ?? [], ...entry.hiddenAliases ?? []]) validateToken(alias);
88
+ if (entry.path.length === 0) {
89
+ if (entry.aliases?.length || entry.hiddenAliases?.length || entry.hidden) throw new TypeError("The root page cannot have aliases or hidden visibility.");
90
+ continue;
91
+ }
92
+ for (const name of [
93
+ entry.path.at(-1),
94
+ ...entry.aliases ?? [],
95
+ ...entry.hiddenAliases ?? []
96
+ ]) {
97
+ const spelling = pathKey([...entry.path.slice(0, -1), name]);
98
+ const destination = spellings.get(spelling);
99
+ if (destination != null && destination !== key) throw new TypeError(`Ambiguous command spelling: ${spelling}`);
100
+ spellings.set(spelling, key);
101
+ }
102
+ }
103
+ return index;
104
+ }
105
+ function commandTarget(entry, parent, index) {
106
+ return entry.term.type === "command" ? index.get(pathKey([...parent.path, entry.term.name])) : void 0;
107
+ }
108
+ function entryHidden(entry) {
109
+ return "hidden" in entry.term && isDocHidden(entry.term.hidden);
110
+ }
111
+ function validateCoverage(coverage) {
112
+ if (coverage == null || !["all-visible", "partial"].includes(coverage.commands) || [
113
+ coverage.help,
114
+ coverage.version,
115
+ coverage.completion
116
+ ].some((value) => value !== "included" && value !== "excluded")) throw new TypeError("Reference coverage must declare commands and runner-added help, version, and completion inputs.");
117
+ }
118
+ /**
119
+ * Renders supplied command pages in caller order as one Markdown reference.
120
+ *
121
+ * Coverage is always author-declared. Visible child references and canonical
122
+ * prefixes are checked, but commands absent from every supplied page cannot be
123
+ * discovered. The renderer never collects pages or evaluates parser callbacks.
124
+ * Messages are documentation text rather than Markdown. Generated anchors and
125
+ * internal links require raw HTML support; bare web/email text may be linkified
126
+ * by a consumer. Every page's usage already includes its command tokens.
127
+ *
128
+ * @param entries Canonical command pages, with explicit aliases and visibility.
129
+ * @param options Program metadata, coverage declaration and presentation policy.
130
+ * @returns A deterministic Markdown document ending in one newline.
131
+ * @throws {TypeError} If paths/aliases are invalid or ambiguous, a canonical
132
+ * path is duplicated, metadata is invalid, or declared complete coverage has
133
+ * missing visible command pages.
134
+ * @since 1.4.0
135
+ */
136
+ function formatCommandReference(entries, options) {
137
+ formatUsage(options.name, [], { colors: false });
138
+ validateCoverage(options.coverage);
139
+ const index = validateEntries(entries);
140
+ const visible = entries.filter((entry) => !hiddenInTree(entry, index));
141
+ const unresolved = options.unresolved?.filter((gap) => !hiddenInTree({
142
+ path: gap.path,
143
+ page: { sections: [] }
144
+ }, index)) ?? [];
145
+ for (const gap of unresolved) {
146
+ pathKey(gap.path);
147
+ if (gap.scope !== "page" && gap.scope !== "subtree") throw new TypeError("An unresolved scope must be page or subtree.");
148
+ }
149
+ if (unresolved.length > 0 && options.coverage.commands === "all-visible") throw new TypeError("Unresolved command documentation requires partial coverage.");
150
+ const missing = /* @__PURE__ */ new Map();
151
+ if (!index.has("[]")) missing.set("[]", []);
152
+ for (const entry of visible) {
153
+ for (let length = 0; length < entry.path.length; length++) {
154
+ const prefix = entry.path.slice(0, length);
155
+ if (!index.has(pathKey(prefix))) missing.set(pathKey(prefix), prefix);
156
+ }
157
+ for (const section of entry.page.sections) for (const item of section.entries) {
158
+ if (entryHidden(item) || item.term.type !== "command") continue;
159
+ const target = commandTarget(item, entry, index);
160
+ if (target == null) {
161
+ const path = [...entry.path, item.term.name];
162
+ missing.set(pathKey(path), path);
163
+ }
164
+ }
165
+ }
166
+ if (missing.size > 0 && options.coverage.commands === "all-visible") throw new TypeError(`Missing command pages: ${[...missing.values()].map((path) => JSON.stringify(path)).join(", ")}`);
167
+ const output = [`# ${code(options.name)} command reference`];
168
+ const addMessage = (content) => {
169
+ if (content != null && content.length > 0) output.push(formatMessage(content));
170
+ };
171
+ if (options.version != null) output.push(`Version: ${code(options.version)}`);
172
+ addMessage(options.brief);
173
+ addMessage(options.description);
174
+ output.push(`Coverage: author-declared ${options.coverage.commands === "all-visible" ? "all visible commands" : "partial command reference"}.`);
175
+ output.push("Runner inputs: " + [
176
+ "help",
177
+ "version",
178
+ "completion"
179
+ ].map((feature) => `${feature}: ${options.coverage[feature] === "included" ? "included" : "not documented here (if configured)"}`).join("; ") + ".");
180
+ if (missing.size > 0) output.push("Missing command pages: " + [...missing.values()].map((path) => code([options.name, ...path].join(" "))).join(", ") + ".");
181
+ if (unresolved.length > 0) output.push("## Unresolved documentation", unresolved.map((gap) => ` - ${code([options.name, ...gap.path].join(" "))} (${gap.scope}): ${code(gap.reason)}`).join("\n"));
182
+ output.push("## Contents");
183
+ output.push(visible.map((entry) => {
184
+ const fullName = [options.name, ...entry.path].join(" ");
185
+ const aliases = entry.showAliases ?? options.showAliases ?? false ? visibleAliases(entry).map((alias) => link([
186
+ options.name,
187
+ ...entry.path.slice(0, -1),
188
+ alias
189
+ ].join(" "), entry.path)) : [];
190
+ return ` - ${link(fullName, entry.path)}${aliases.length > 0 ? ` (aliases: ${aliases.join(", ")})` : ""}`;
191
+ }).join("\n"));
192
+ for (const entry of visible) {
193
+ const page = entry.page;
194
+ output.push(`<a id="${getCommandReferenceAnchor(entry.path)}"></a>`);
195
+ output.push(`## ${code([options.name, ...entry.path].join(" "))}`);
196
+ addMessage(page.brief);
197
+ if ((options.showUsage ?? page.showUsage ?? true) && page.usage != null) {
198
+ const usage = formatUsage(options.name, page.usage, { colors: false }).replace(/\r\n?/g, "\n").split("\n").map(singleLine).join("\n");
199
+ const fence = "~".repeat(Math.max(3, ...Array.from(usage.matchAll(/~+/g), (match) => match[0].length + 1)));
200
+ output.push("### Usage", `${fence} text\n${usage}\n${fence}`);
201
+ }
202
+ addMessage(page.description);
203
+ for (const section of page.sections) {
204
+ const items = [];
205
+ for (const item of section.entries) {
206
+ if (entryHidden(item)) continue;
207
+ const target = commandTarget(item, entry, index);
208
+ if (target != null && hiddenInTree(target, index)) continue;
209
+ const label = formatUsageTerm(item.term, {
210
+ context: "doc",
211
+ colors: false
212
+ });
213
+ if (label === "") continue;
214
+ const aliases = target != null && (item.showAliases ?? target.showAliases ?? options.showAliases ?? false) ? visibleAliases(target).map((alias) => link(alias, target.path)) : [];
215
+ let content = " - " + (target == null ? code(label) : link(label, target.path));
216
+ if (aliases.length > 0) content += ` (aliases: ${aliases.join(", ")})`;
217
+ const details = [];
218
+ if (item.description != null) details.push(formatMessage(item.description));
219
+ if (item.default != null) details.push("Default: " + formatMessage(item.default));
220
+ if (item.choices != null) details.push("Choices: " + formatMessage(item.choices));
221
+ if (item.envVars?.length) details.push("Environment: " + item.envVars.map(code).join(", "));
222
+ if (details.length > 0) content += "\n\n" + details.join("\n\n").split("\n").map((line) => " " + line).join("\n");
223
+ items.push(content);
224
+ }
225
+ if (items.length > 0) output.push("### " + prose(singleLine(section.title ?? "Inputs")), items.join("\n\n"));
226
+ }
227
+ const bindings = page.environmentBindings?.filter((binding) => !isDocHidden(binding.hidden));
228
+ if (bindings?.length) output.push("### Environment", bindings.map((binding) => ` - ${code(binding.name)}${binding.description == null ? "" : ": " + formatMessage(binding.description).replaceAll("\n", "\n ")}`).join("\n"));
229
+ for (const [title, content] of [
230
+ ["Examples", page.examples],
231
+ ["Author", page.author],
232
+ ["Bugs", page.bugs],
233
+ ["Notes", page.footer]
234
+ ]) if (content != null && content.length > 0) output.push(`### ${title}`, formatMessage(content));
235
+ }
236
+ for (const [title, content] of [
237
+ ["Examples", options.examples],
238
+ ["Author", options.author],
239
+ ["Bugs", options.bugs],
240
+ ["Notes", options.footer]
241
+ ]) if (content != null && content.length > 0) output.push(`## ${title}`, formatMessage(content));
242
+ return output.filter((part) => part !== "").join("\n\n") + "\n";
243
+ }
244
+
245
+ //#endregion
246
+ export { formatCommandReference, getCommandReferenceAnchor };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@optique/man",
3
- "version": "1.4.0-dev.2899",
4
- "description": "Man page generator for Optique CLI parsers",
3
+ "version": "1.4.0-dev.2935",
4
+ "description": "Man pages and Markdown command references for Optique",
5
5
  "keywords": [
6
6
  "CLI",
7
7
  "command-line",
@@ -77,6 +77,14 @@
77
77
  },
78
78
  "import": "./dist/cli.js",
79
79
  "require": "./dist/cli.cjs"
80
+ },
81
+ "./markdown": {
82
+ "types": {
83
+ "import": "./dist/markdown.d.ts",
84
+ "require": "./dist/markdown.d.cts"
85
+ },
86
+ "import": "./dist/markdown.js",
87
+ "require": "./dist/markdown.cjs"
80
88
  }
81
89
  },
82
90
  "imports": {
@@ -90,11 +98,11 @@
90
98
  "optique-man": "./dist/cli.js"
91
99
  },
92
100
  "dependencies": {
93
- "@optique/core": "1.4.0-dev.2899+9c2973b0",
94
- "@optique/run": "1.4.0-dev.2899+9c2973b0"
101
+ "@optique/core": "1.4.0-dev.2935+fc126fd1",
102
+ "@optique/run": "1.4.0-dev.2935+fc126fd1"
95
103
  },
96
104
  "devDependencies": {
97
- "@optique/testing": "1.4.0-dev.2899+9c2973b0",
105
+ "@optique/testing": "1.4.0-dev.2935+fc126fd1",
98
106
  "@types/node": "^24.0.0",
99
107
  "tsdown": "^0.13.0",
100
108
  "tsx": "^4.21.0",