@optique/core 1.3.0-dev.2375 → 1.3.0-dev.2379

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/dist/doc.cjs CHANGED
@@ -213,7 +213,6 @@ function defaultSectionOrder(a, b) {
213
213
  function formatDocPage(programName, page, options = {}) {
214
214
  require_validate.validateProgramName(programName);
215
215
  const termIndent = options.termIndent ?? 2;
216
- const termWidth = options.termWidth ?? 26;
217
216
  const showUsage = options.showUsage ?? true;
218
217
  if (options.maxWidth != null && (!Number.isFinite(options.maxWidth) || !Number.isInteger(options.maxWidth))) throw new TypeError(`maxWidth must be a finite integer, got ${options.maxWidth}.`);
219
218
  const filteredSections = page.sections.map((s) => ({
@@ -232,9 +231,32 @@ function formatDocPage(programName, page, options = {}) {
232
231
  if (maxItems < 1) throw new RangeError(`showChoices.maxItems must be at least 1, but got ${maxItems}.`);
233
232
  }
234
233
  const hasContent = (msg) => Array.isArray(msg) && msg.length > 0;
234
+ const needsDescriptionColumn = (entry) => hasContent(entry.description) || (options.showDefault === true || typeof options.showDefault === "object") && hasContent(entry.default) || (options.showChoices === true || typeof options.showChoices === "object") && hasContent(entry.choices);
235
+ const automaticTermWidth = () => {
236
+ let widest;
237
+ for (const section of page.sections) for (const entry of section.entries) {
238
+ if (!needsDescriptionColumn(entry)) continue;
239
+ const rendered = require_usage.formatUsageTerm(entry.term, {
240
+ colors: options.colors,
241
+ optionsSeparator: ", ",
242
+ context: "doc"
243
+ });
244
+ const width = Math.max(...rendered.split("\n").map((line) => require_displaywidth.getDisplayWidth(line)));
245
+ widest = widest == null ? width : Math.max(widest, width);
246
+ }
247
+ return widest;
248
+ };
249
+ const termWidth = options.termWidth === "auto" ? automaticTermWidth() ?? 26 : options.termWidth ?? 26;
250
+ const hasEntries = page.sections.some((s) => s.entries.length > 0);
251
+ const needsDescColumn = hasEntries && page.sections.some((s) => s.entries.some(needsDescriptionColumn));
252
+ let effectiveTermWidth;
253
+ if (options.maxWidth == null) effectiveTermWidth = termWidth;
254
+ else {
255
+ const availableForColumns = options.maxWidth - termIndent - 2;
256
+ const evenlySplitTermWidth = Math.max(1, Math.floor(availableForColumns / 2));
257
+ effectiveTermWidth = options.termWidth === "auto" && needsDescColumn ? Math.min(termWidth, evenlySplitTermWidth) : availableForColumns >= termWidth + 1 ? termWidth : evenlySplitTermWidth;
258
+ }
235
259
  if (options.maxWidth != null) {
236
- const hasEntries = page.sections.some((s) => s.entries.length > 0);
237
- const needsDescColumn = hasEntries && page.sections.some((s) => s.entries.some((e) => hasContent(e.description) || options.showDefault && hasContent(e.default) || options.showChoices && hasContent(e.choices)));
238
260
  let minDescWidth = 1;
239
261
  if (needsDescColumn) {
240
262
  if (options.showDefault && page.sections.some((s) => s.entries.some((e) => hasContent(e.default)))) {
@@ -260,20 +282,13 @@ function formatDocPage(programName, page, options = {}) {
260
282
  if (options.maxWidth < minWidth) throw new RangeError(`maxWidth must be at least ${minWidth}, got ${options.maxWidth}.`);
261
283
  if (needsDescColumn && minDescWidth > 1) {
262
284
  const avail = options.maxWidth - termIndent - 2;
263
- const effTW = avail >= termWidth + 1 ? termWidth : Math.max(1, Math.floor(avail / 2));
264
- const descW = avail - effTW;
285
+ const descW = avail - effectiveTermWidth;
265
286
  if (descW < minDescWidth) {
266
- const needed = termIndent + termWidth + 2 + minDescWidth;
287
+ const needed = termIndent + effectiveTermWidth + 2 + minDescWidth;
267
288
  throw new RangeError(`maxWidth must be at least ${needed}, got ${options.maxWidth}.`);
268
289
  }
269
290
  }
270
291
  }
271
- let effectiveTermWidth;
272
- if (options.maxWidth == null) effectiveTermWidth = termWidth;
273
- else {
274
- const availableForColumns = options.maxWidth - termIndent - 2;
275
- effectiveTermWidth = availableForColumns >= termWidth + 1 ? termWidth : Math.max(1, Math.floor(availableForColumns / 2));
276
- }
277
292
  let output = "";
278
293
  if (hasContent(page.brief)) {
279
294
  output += require_message.formatMessage(page.brief, {
package/dist/doc.d.cts CHANGED
@@ -217,10 +217,14 @@ interface DocPageFormatOptions {
217
217
  */
218
218
  termIndent?: number;
219
219
  /**
220
- * Width allocated for terms before descriptions start.
220
+ * Width allocated for terms before descriptions start. Set to `"auto"`
221
+ * to align descriptions after the widest visible term that has content.
222
+ * Terminal display width is used for automatic measurement.
223
+ *
221
224
  * @default `26`
225
+ * @since 1.3.0 Added automatic term width.
222
226
  */
223
- termWidth?: number;
227
+ termWidth?: number | "auto";
224
228
  /**
225
229
  * Maximum width of the entire formatted output.
226
230
  */
package/dist/doc.d.ts CHANGED
@@ -217,10 +217,14 @@ interface DocPageFormatOptions {
217
217
  */
218
218
  termIndent?: number;
219
219
  /**
220
- * Width allocated for terms before descriptions start.
220
+ * Width allocated for terms before descriptions start. Set to `"auto"`
221
+ * to align descriptions after the widest visible term that has content.
222
+ * Terminal display width is used for automatic measurement.
223
+ *
221
224
  * @default `26`
225
+ * @since 1.3.0 Added automatic term width.
222
226
  */
223
- termWidth?: number;
227
+ termWidth?: number | "auto";
224
228
  /**
225
229
  * Maximum width of the entire formatted output.
226
230
  */
package/dist/doc.js CHANGED
@@ -213,7 +213,6 @@ function defaultSectionOrder(a, b) {
213
213
  function formatDocPage(programName, page, options = {}) {
214
214
  validateProgramName(programName);
215
215
  const termIndent = options.termIndent ?? 2;
216
- const termWidth = options.termWidth ?? 26;
217
216
  const showUsage = options.showUsage ?? true;
218
217
  if (options.maxWidth != null && (!Number.isFinite(options.maxWidth) || !Number.isInteger(options.maxWidth))) throw new TypeError(`maxWidth must be a finite integer, got ${options.maxWidth}.`);
219
218
  const filteredSections = page.sections.map((s) => ({
@@ -232,9 +231,32 @@ function formatDocPage(programName, page, options = {}) {
232
231
  if (maxItems < 1) throw new RangeError(`showChoices.maxItems must be at least 1, but got ${maxItems}.`);
233
232
  }
234
233
  const hasContent = (msg) => Array.isArray(msg) && msg.length > 0;
234
+ const needsDescriptionColumn = (entry) => hasContent(entry.description) || (options.showDefault === true || typeof options.showDefault === "object") && hasContent(entry.default) || (options.showChoices === true || typeof options.showChoices === "object") && hasContent(entry.choices);
235
+ const automaticTermWidth = () => {
236
+ let widest;
237
+ for (const section of page.sections) for (const entry of section.entries) {
238
+ if (!needsDescriptionColumn(entry)) continue;
239
+ const rendered = formatUsageTerm(entry.term, {
240
+ colors: options.colors,
241
+ optionsSeparator: ", ",
242
+ context: "doc"
243
+ });
244
+ const width = Math.max(...rendered.split("\n").map((line) => getDisplayWidth(line)));
245
+ widest = widest == null ? width : Math.max(widest, width);
246
+ }
247
+ return widest;
248
+ };
249
+ const termWidth = options.termWidth === "auto" ? automaticTermWidth() ?? 26 : options.termWidth ?? 26;
250
+ const hasEntries = page.sections.some((s) => s.entries.length > 0);
251
+ const needsDescColumn = hasEntries && page.sections.some((s) => s.entries.some(needsDescriptionColumn));
252
+ let effectiveTermWidth;
253
+ if (options.maxWidth == null) effectiveTermWidth = termWidth;
254
+ else {
255
+ const availableForColumns = options.maxWidth - termIndent - 2;
256
+ const evenlySplitTermWidth = Math.max(1, Math.floor(availableForColumns / 2));
257
+ effectiveTermWidth = options.termWidth === "auto" && needsDescColumn ? Math.min(termWidth, evenlySplitTermWidth) : availableForColumns >= termWidth + 1 ? termWidth : evenlySplitTermWidth;
258
+ }
235
259
  if (options.maxWidth != null) {
236
- const hasEntries = page.sections.some((s) => s.entries.length > 0);
237
- const needsDescColumn = hasEntries && page.sections.some((s) => s.entries.some((e) => hasContent(e.description) || options.showDefault && hasContent(e.default) || options.showChoices && hasContent(e.choices)));
238
260
  let minDescWidth = 1;
239
261
  if (needsDescColumn) {
240
262
  if (options.showDefault && page.sections.some((s) => s.entries.some((e) => hasContent(e.default)))) {
@@ -260,20 +282,13 @@ function formatDocPage(programName, page, options = {}) {
260
282
  if (options.maxWidth < minWidth) throw new RangeError(`maxWidth must be at least ${minWidth}, got ${options.maxWidth}.`);
261
283
  if (needsDescColumn && minDescWidth > 1) {
262
284
  const avail = options.maxWidth - termIndent - 2;
263
- const effTW = avail >= termWidth + 1 ? termWidth : Math.max(1, Math.floor(avail / 2));
264
- const descW = avail - effTW;
285
+ const descW = avail - effectiveTermWidth;
265
286
  if (descW < minDescWidth) {
266
- const needed = termIndent + termWidth + 2 + minDescWidth;
287
+ const needed = termIndent + effectiveTermWidth + 2 + minDescWidth;
267
288
  throw new RangeError(`maxWidth must be at least ${needed}, got ${options.maxWidth}.`);
268
289
  }
269
290
  }
270
291
  }
271
- let effectiveTermWidth;
272
- if (options.maxWidth == null) effectiveTermWidth = termWidth;
273
- else {
274
- const availableForColumns = options.maxWidth - termIndent - 2;
275
- effectiveTermWidth = availableForColumns >= termWidth + 1 ? termWidth : Math.max(1, Math.floor(availableForColumns / 2));
276
- }
277
292
  let output = "";
278
293
  if (hasContent(page.brief)) {
279
294
  output += formatMessage(page.brief, {
package/dist/facade.cjs CHANGED
@@ -611,7 +611,7 @@ function classifyParseFailure(failure, helpOptionNames, helpCommandNames, versio
611
611
  * Handles shell completion requests.
612
612
  * @since 0.6.0
613
613
  */
614
- function handleCompletion(completionArgs, programName, parser, completionParser, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth, completionCommandDisplayName, completionOptionDisplayName, isOptionMode, sectionOrder, showUsage, rootOptionSuggestions = []) {
614
+ function handleCompletion(completionArgs, programName, parser, completionParser, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth, termWidth, completionCommandDisplayName, completionOptionDisplayName, isOptionMode, sectionOrder, showUsage, rootOptionSuggestions = []) {
615
615
  const shellName = completionArgs[0] || "";
616
616
  const args = completionArgs.slice(1);
617
617
  const callOnError = (code) => onError(code);
@@ -624,6 +624,7 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
624
624
  if (doc) stderr(require_doc.formatDocPage(programName, doc, {
625
625
  colors,
626
626
  maxWidth,
627
+ termWidth,
627
628
  sectionOrder,
628
629
  showUsage
629
630
  }));
@@ -952,7 +953,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
952
953
  options = optionsParam ?? {};
953
954
  }
954
955
  require_validate.validateProgramName(programName);
955
- const { colors, maxWidth, showDefault, showChoices, sectionOrder, showUsage, usageLine, commandList = "recursive", aboveError = "usage", onError = () => {
956
+ const { colors, maxWidth, termWidth, showDefault, showChoices, sectionOrder, showUsage, usageLine, commandList = "recursive", aboveError = "usage", onError = () => {
956
957
  throw new RunParserError("Failed to parse command line arguments.");
957
958
  }, stderr = console.error, stdout = console.log, brief, description, examples, author, bugs, footer } = options;
958
959
  const norm = (c) => c === true ? {} : c;
@@ -1090,7 +1091,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1090
1091
  classified.shell,
1091
1092
  ...classified.commandPath ?? [],
1092
1093
  ...classified.args
1093
- ], programName, parser, classified.source === "command" ? completionParsers.completionCommand : completionParsers.completionOption, stdout, stderr, onCompletionResult, onErrorResult, availableShells, colors, maxWidth, completionCommandNames[0], completionOptionNames[0], classified.source === "option", sectionOrder, showUsage, rootOptionSuggestions);
1094
+ ], programName, parser, classified.source === "command" ? completionParsers.completionCommand : completionParsers.completionOption, stdout, stderr, onCompletionResult, onErrorResult, availableShells, colors, maxWidth, termWidth, completionCommandNames[0], completionOptionNames[0], classified.source === "option", sectionOrder, showUsage, rootOptionSuggestions);
1094
1095
  case "help": {
1095
1096
  let helpGeneratorParser;
1096
1097
  let docGeneratorParser;
@@ -1140,6 +1141,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1140
1141
  stdout(require_doc.formatDocPage(programName, renderedDoc, {
1141
1142
  colors,
1142
1143
  maxWidth,
1144
+ termWidth,
1143
1145
  showDefault,
1144
1146
  showChoices,
1145
1147
  sectionOrder,
@@ -1212,6 +1214,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1212
1214
  stderr(require_doc.formatDocPage(programName, renderedDoc, {
1213
1215
  colors,
1214
1216
  maxWidth,
1217
+ termWidth,
1215
1218
  showDefault,
1216
1219
  showChoices,
1217
1220
  sectionOrder,
package/dist/facade.d.cts CHANGED
@@ -89,6 +89,14 @@ interface RunOptions<THelp, TError> {
89
89
  * this width. If not specified, text will not be wrapped.
90
90
  */
91
91
  readonly maxWidth?: number;
92
+ /**
93
+ * Width allocated for help terms before descriptions start. Set to
94
+ * `"auto"` to align descriptions after the widest visible term.
95
+ *
96
+ * @default `26`
97
+ * @since 1.3.0
98
+ */
99
+ readonly termWidth?: number | "auto";
92
100
  /**
93
101
  * Whether and how to display default values for options and arguments.
94
102
  *
package/dist/facade.d.ts CHANGED
@@ -89,6 +89,14 @@ interface RunOptions<THelp, TError> {
89
89
  * this width. If not specified, text will not be wrapped.
90
90
  */
91
91
  readonly maxWidth?: number;
92
+ /**
93
+ * Width allocated for help terms before descriptions start. Set to
94
+ * `"auto"` to align descriptions after the widest visible term.
95
+ *
96
+ * @default `26`
97
+ * @since 1.3.0
98
+ */
99
+ readonly termWidth?: number | "auto";
92
100
  /**
93
101
  * Whether and how to display default values for options and arguments.
94
102
  *
package/dist/facade.js CHANGED
@@ -611,7 +611,7 @@ function classifyParseFailure(failure, helpOptionNames, helpCommandNames, versio
611
611
  * Handles shell completion requests.
612
612
  * @since 0.6.0
613
613
  */
614
- function handleCompletion(completionArgs, programName, parser, completionParser, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth, completionCommandDisplayName, completionOptionDisplayName, isOptionMode, sectionOrder, showUsage, rootOptionSuggestions = []) {
614
+ function handleCompletion(completionArgs, programName, parser, completionParser, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth, termWidth, completionCommandDisplayName, completionOptionDisplayName, isOptionMode, sectionOrder, showUsage, rootOptionSuggestions = []) {
615
615
  const shellName = completionArgs[0] || "";
616
616
  const args = completionArgs.slice(1);
617
617
  const callOnError = (code) => onError(code);
@@ -624,6 +624,7 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
624
624
  if (doc) stderr(formatDocPage(programName, doc, {
625
625
  colors,
626
626
  maxWidth,
627
+ termWidth,
627
628
  sectionOrder,
628
629
  showUsage
629
630
  }));
@@ -952,7 +953,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
952
953
  options = optionsParam ?? {};
953
954
  }
954
955
  validateProgramName(programName);
955
- const { colors, maxWidth, showDefault, showChoices, sectionOrder, showUsage, usageLine, commandList = "recursive", aboveError = "usage", onError = () => {
956
+ const { colors, maxWidth, termWidth, showDefault, showChoices, sectionOrder, showUsage, usageLine, commandList = "recursive", aboveError = "usage", onError = () => {
956
957
  throw new RunParserError("Failed to parse command line arguments.");
957
958
  }, stderr = console.error, stdout = console.log, brief, description, examples, author, bugs, footer } = options;
958
959
  const norm = (c) => c === true ? {} : c;
@@ -1090,7 +1091,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1090
1091
  classified.shell,
1091
1092
  ...classified.commandPath ?? [],
1092
1093
  ...classified.args
1093
- ], programName, parser, classified.source === "command" ? completionParsers.completionCommand : completionParsers.completionOption, stdout, stderr, onCompletionResult, onErrorResult, availableShells, colors, maxWidth, completionCommandNames[0], completionOptionNames[0], classified.source === "option", sectionOrder, showUsage, rootOptionSuggestions);
1094
+ ], programName, parser, classified.source === "command" ? completionParsers.completionCommand : completionParsers.completionOption, stdout, stderr, onCompletionResult, onErrorResult, availableShells, colors, maxWidth, termWidth, completionCommandNames[0], completionOptionNames[0], classified.source === "option", sectionOrder, showUsage, rootOptionSuggestions);
1094
1095
  case "help": {
1095
1096
  let helpGeneratorParser;
1096
1097
  let docGeneratorParser;
@@ -1140,6 +1141,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1140
1141
  stdout(formatDocPage(programName, renderedDoc, {
1141
1142
  colors,
1142
1143
  maxWidth,
1144
+ termWidth,
1143
1145
  showDefault,
1144
1146
  showChoices,
1145
1147
  sectionOrder,
@@ -1212,6 +1214,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1212
1214
  stderr(formatDocPage(programName, renderedDoc, {
1213
1215
  colors,
1214
1216
  maxWidth,
1217
+ termWidth,
1215
1218
  showDefault,
1216
1219
  showChoices,
1217
1220
  sectionOrder,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.3.0-dev.2375",
3
+ "version": "1.3.0-dev.2379",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -221,7 +221,7 @@
221
221
  },
222
222
  "sideEffects": false,
223
223
  "devDependencies": {
224
- "@optique/env": "1.3.0-dev.2375+820d01c0",
224
+ "@optique/env": "1.3.0-dev.2379+3e20cbf3",
225
225
  "@types/node": "^24.0.0",
226
226
  "fast-check": "^4.7.0",
227
227
  "tsdown": "^0.13.0",
@@ -64,6 +64,9 @@ Core rules
64
64
  brief and command or option sections without the `Usage:` synopsis.
65
65
  For deeply nested command trees, add `commandList: "top-level"` when root
66
66
  help should list only first-level command groups.
67
+ - Use `termWidth: "auto"` in runner options when descriptions should align
68
+ after the widest visible help term. Optique measures terminal display
69
+ width after adding built-in help/version/completion entries.
67
70
 
68
71
 
69
72
  Canonical app shape
@@ -97,6 +100,7 @@ const config = run(parser, {
97
100
  brief: message`Process a file.`,
98
101
  completion: "both",
99
102
  showDefault: true,
103
+ termWidth: "auto",
100
104
  });
101
105
 
102
106
  console.log(`Processing ${config.input} on port ${config.port}.`);