@optique/core 1.0.0-dev.1116 → 1.0.0-dev.1129

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.
@@ -41,6 +41,17 @@ function encodePattern(pattern) {
41
41
  return pattern.replace(/%/g, "%25").replace(/:/g, "%3A");
42
42
  }
43
43
  /**
44
+ * Replaces control characters that would corrupt shell completion protocols.
45
+ * Shell completion formats use tabs as field delimiters and newlines as record
46
+ * delimiters. Null bytes are used as delimiters in zsh's format.
47
+ * @param description The description string to sanitize.
48
+ * @returns The sanitized description with control characters replaced by spaces.
49
+ * @internal
50
+ */
51
+ function sanitizeDescription(description) {
52
+ return description.replace(/[\t\n\r\0]/g, " ");
53
+ }
54
+ /**
44
55
  * The Bash shell completion generator.
45
56
  * @since 0.6.0
46
57
  */
@@ -183,7 +194,7 @@ function _${programName} () {
183
194
  done < <(${programName} ${escapedArgs} "\${prev[@]}" "$current" 2>/dev/null)
184
195
  }
185
196
 
186
- complete -F _${programName} ${programName}
197
+ complete -F _${programName} -- ${programName}
187
198
  `;
188
199
  },
189
200
  *encodeSuggestions(suggestions) {
@@ -314,12 +325,12 @@ compdef _${programName.replace(/[^a-zA-Z0-9]/g, "_")} ${programName}
314
325
  },
315
326
  *encodeSuggestions(suggestions) {
316
327
  for (const suggestion of suggestions) if (suggestion.kind === "literal") {
317
- const description = suggestion.description == null ? "" : require_message.formatMessage(suggestion.description, { colors: false });
328
+ const description = suggestion.description == null ? "" : sanitizeDescription(require_message.formatMessage(suggestion.description, { colors: false }));
318
329
  yield `${suggestion.text}\0${description}\0`;
319
330
  } else {
320
331
  const extensions = suggestion.extensions?.join(",") || "";
321
332
  const hidden = suggestion.includeHidden ? "1" : "0";
322
- const description = suggestion.description == null ? "" : require_message.formatMessage(suggestion.description, { colors: false });
333
+ const description = suggestion.description == null ? "" : sanitizeDescription(require_message.formatMessage(suggestion.description, { colors: false }));
323
334
  const pattern = encodePattern(suggestion.pattern ?? "");
324
335
  yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}\0${description}\0`;
325
336
  }
@@ -482,12 +493,12 @@ complete -c ${programName} -f -a '(${functionName})'
482
493
  for (const suggestion of suggestions) {
483
494
  if (i > 0) yield "\n";
484
495
  if (suggestion.kind === "literal") {
485
- const description = suggestion.description == null ? "" : require_message.formatMessage(suggestion.description, { colors: false });
496
+ const description = suggestion.description == null ? "" : sanitizeDescription(require_message.formatMessage(suggestion.description, { colors: false }));
486
497
  yield `${suggestion.text}\t${description}`;
487
498
  } else {
488
499
  const extensions = suggestion.extensions?.join(",") || "";
489
500
  const hidden = suggestion.includeHidden ? "1" : "0";
490
- const description = suggestion.description == null ? "" : require_message.formatMessage(suggestion.description, { colors: false });
501
+ const description = suggestion.description == null ? "" : sanitizeDescription(require_message.formatMessage(suggestion.description, { colors: false }));
491
502
  const pattern = encodePattern(suggestion.pattern ?? "");
492
503
  yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}\t${description}`;
493
504
  }
@@ -726,12 +737,12 @@ ${functionName}-external
726
737
  for (const suggestion of suggestions) {
727
738
  if (i > 0) yield "\n";
728
739
  if (suggestion.kind === "literal") {
729
- const description = suggestion.description == null ? "" : require_message.formatMessage(suggestion.description, { colors: false });
740
+ const description = suggestion.description == null ? "" : sanitizeDescription(require_message.formatMessage(suggestion.description, { colors: false }));
730
741
  yield `${suggestion.text}\t${description}`;
731
742
  } else {
732
743
  const extensions = suggestion.extensions?.join(",") || "";
733
744
  const hidden = suggestion.includeHidden ? "1" : "0";
734
- const description = suggestion.description == null ? "" : require_message.formatMessage(suggestion.description, { colors: false });
745
+ const description = suggestion.description == null ? "" : sanitizeDescription(require_message.formatMessage(suggestion.description, { colors: false }));
735
746
  const pattern = encodePattern(suggestion.pattern ?? "");
736
747
  yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}\t${description}`;
737
748
  }
@@ -896,12 +907,12 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
896
907
  for (const suggestion of suggestions) {
897
908
  if (i > 0) yield "\n";
898
909
  if (suggestion.kind === "literal") {
899
- const description = suggestion.description == null ? "" : require_message.formatMessage(suggestion.description, { colors: false });
910
+ const description = suggestion.description == null ? "" : sanitizeDescription(require_message.formatMessage(suggestion.description, { colors: false }));
900
911
  yield `${suggestion.text}\t${suggestion.text}\t${description}`;
901
912
  } else {
902
913
  const extensions = suggestion.extensions?.join(",") || "";
903
914
  const hidden = suggestion.includeHidden ? "1" : "0";
904
- const description = suggestion.description == null ? "" : require_message.formatMessage(suggestion.description, { colors: false });
915
+ const description = suggestion.description == null ? "" : sanitizeDescription(require_message.formatMessage(suggestion.description, { colors: false }));
905
916
  const pattern = encodePattern(suggestion.pattern ?? "");
906
917
  yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}\t[file]\t${description}`;
907
918
  }
@@ -41,6 +41,17 @@ function encodePattern(pattern) {
41
41
  return pattern.replace(/%/g, "%25").replace(/:/g, "%3A");
42
42
  }
43
43
  /**
44
+ * Replaces control characters that would corrupt shell completion protocols.
45
+ * Shell completion formats use tabs as field delimiters and newlines as record
46
+ * delimiters. Null bytes are used as delimiters in zsh's format.
47
+ * @param description The description string to sanitize.
48
+ * @returns The sanitized description with control characters replaced by spaces.
49
+ * @internal
50
+ */
51
+ function sanitizeDescription(description) {
52
+ return description.replace(/[\t\n\r\0]/g, " ");
53
+ }
54
+ /**
44
55
  * The Bash shell completion generator.
45
56
  * @since 0.6.0
46
57
  */
@@ -183,7 +194,7 @@ function _${programName} () {
183
194
  done < <(${programName} ${escapedArgs} "\${prev[@]}" "$current" 2>/dev/null)
184
195
  }
185
196
 
186
- complete -F _${programName} ${programName}
197
+ complete -F _${programName} -- ${programName}
187
198
  `;
188
199
  },
189
200
  *encodeSuggestions(suggestions) {
@@ -314,12 +325,12 @@ compdef _${programName.replace(/[^a-zA-Z0-9]/g, "_")} ${programName}
314
325
  },
315
326
  *encodeSuggestions(suggestions) {
316
327
  for (const suggestion of suggestions) if (suggestion.kind === "literal") {
317
- const description = suggestion.description == null ? "" : formatMessage(suggestion.description, { colors: false });
328
+ const description = suggestion.description == null ? "" : sanitizeDescription(formatMessage(suggestion.description, { colors: false }));
318
329
  yield `${suggestion.text}\0${description}\0`;
319
330
  } else {
320
331
  const extensions = suggestion.extensions?.join(",") || "";
321
332
  const hidden = suggestion.includeHidden ? "1" : "0";
322
- const description = suggestion.description == null ? "" : formatMessage(suggestion.description, { colors: false });
333
+ const description = suggestion.description == null ? "" : sanitizeDescription(formatMessage(suggestion.description, { colors: false }));
323
334
  const pattern = encodePattern(suggestion.pattern ?? "");
324
335
  yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}\0${description}\0`;
325
336
  }
@@ -482,12 +493,12 @@ complete -c ${programName} -f -a '(${functionName})'
482
493
  for (const suggestion of suggestions) {
483
494
  if (i > 0) yield "\n";
484
495
  if (suggestion.kind === "literal") {
485
- const description = suggestion.description == null ? "" : formatMessage(suggestion.description, { colors: false });
496
+ const description = suggestion.description == null ? "" : sanitizeDescription(formatMessage(suggestion.description, { colors: false }));
486
497
  yield `${suggestion.text}\t${description}`;
487
498
  } else {
488
499
  const extensions = suggestion.extensions?.join(",") || "";
489
500
  const hidden = suggestion.includeHidden ? "1" : "0";
490
- const description = suggestion.description == null ? "" : formatMessage(suggestion.description, { colors: false });
501
+ const description = suggestion.description == null ? "" : sanitizeDescription(formatMessage(suggestion.description, { colors: false }));
491
502
  const pattern = encodePattern(suggestion.pattern ?? "");
492
503
  yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}\t${description}`;
493
504
  }
@@ -726,12 +737,12 @@ ${functionName}-external
726
737
  for (const suggestion of suggestions) {
727
738
  if (i > 0) yield "\n";
728
739
  if (suggestion.kind === "literal") {
729
- const description = suggestion.description == null ? "" : formatMessage(suggestion.description, { colors: false });
740
+ const description = suggestion.description == null ? "" : sanitizeDescription(formatMessage(suggestion.description, { colors: false }));
730
741
  yield `${suggestion.text}\t${description}`;
731
742
  } else {
732
743
  const extensions = suggestion.extensions?.join(",") || "";
733
744
  const hidden = suggestion.includeHidden ? "1" : "0";
734
- const description = suggestion.description == null ? "" : formatMessage(suggestion.description, { colors: false });
745
+ const description = suggestion.description == null ? "" : sanitizeDescription(formatMessage(suggestion.description, { colors: false }));
735
746
  const pattern = encodePattern(suggestion.pattern ?? "");
736
747
  yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}\t${description}`;
737
748
  }
@@ -896,12 +907,12 @@ ${escapedArgs ? ` \$completionArgs += @(${escapedArgs})
896
907
  for (const suggestion of suggestions) {
897
908
  if (i > 0) yield "\n";
898
909
  if (suggestion.kind === "literal") {
899
- const description = suggestion.description == null ? "" : formatMessage(suggestion.description, { colors: false });
910
+ const description = suggestion.description == null ? "" : sanitizeDescription(formatMessage(suggestion.description, { colors: false }));
900
911
  yield `${suggestion.text}\t${suggestion.text}\t${description}`;
901
912
  } else {
902
913
  const extensions = suggestion.extensions?.join(",") || "";
903
914
  const hidden = suggestion.includeHidden ? "1" : "0";
904
- const description = suggestion.description == null ? "" : formatMessage(suggestion.description, { colors: false });
915
+ const description = suggestion.description == null ? "" : sanitizeDescription(formatMessage(suggestion.description, { colors: false }));
905
916
  const pattern = encodePattern(suggestion.pattern ?? "");
906
917
  yield `__FILE__:${suggestion.type}:${extensions}:${pattern}:${hidden}\t[file]\t${description}`;
907
918
  }
package/dist/facade.cjs CHANGED
@@ -710,6 +710,23 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
710
710
  return callOnCompletion(0);
711
711
  });
712
712
  }
713
+ /**
714
+ * Validates the configured version value.
715
+ *
716
+ * @param value Runtime version value from configuration.
717
+ * @returns The validated version string.
718
+ * @throws {TypeError} If the value is not a string, is empty, or contains
719
+ * ASCII control characters.
720
+ */
721
+ function validateVersionValue(value$1) {
722
+ if (typeof value$1 !== "string") {
723
+ const type = Array.isArray(value$1) ? "array" : typeof value$1;
724
+ throw new TypeError(`Expected version value to be a string, but got ${type}.`);
725
+ }
726
+ if (value$1 === "") throw new TypeError("Version value must not be empty.");
727
+ if (/[\x00-\x1f\x7f]/.test(value$1)) throw new TypeError("Version value must not contain control characters.");
728
+ return value$1;
729
+ }
713
730
  function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsParam) {
714
731
  const isProgram = typeof programNameOrArgs !== "string";
715
732
  let parser;
@@ -746,7 +763,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
746
763
  const onHelp = options.help?.onShow ?? (() => ({}));
747
764
  const versionCommandConfig = norm(options.version?.command);
748
765
  const versionOptionConfig = norm(options.version?.option);
749
- const versionValue = options.version?.value ?? "";
766
+ const versionValue = options.version ? validateVersionValue(options.version.value) : void 0;
750
767
  const onVersion = options.version?.onShow ?? (() => ({}));
751
768
  const completionCommandConfig = norm(options.completion?.command);
752
769
  const completionOptionConfig = norm(options.completion?.option);
package/dist/facade.d.cts CHANGED
@@ -272,6 +272,8 @@ interface RunOptions<THelp, TError> {
272
272
  * @param options Configuration options for output formatting and callbacks.
273
273
  * @returns The parsed result value, or the return value of `onHelp`/`onError`
274
274
  * callbacks.
275
+ * @throws {TypeError} If `options.version.value` is not a non-empty string
276
+ * without ASCII control characters.
275
277
  * @throws {RunParserError} When parsing fails and no `onError` callback is
276
278
  * provided.
277
279
  * @since 0.10.0 Added support for {@link Program} objects.
package/dist/facade.d.ts CHANGED
@@ -272,6 +272,8 @@ interface RunOptions<THelp, TError> {
272
272
  * @param options Configuration options for output formatting and callbacks.
273
273
  * @returns The parsed result value, or the return value of `onHelp`/`onError`
274
274
  * callbacks.
275
+ * @throws {TypeError} If `options.version.value` is not a non-empty string
276
+ * without ASCII control characters.
275
277
  * @throws {RunParserError} When parsing fails and no `onError` callback is
276
278
  * provided.
277
279
  * @since 0.10.0 Added support for {@link Program} objects.
package/dist/facade.js CHANGED
@@ -710,6 +710,23 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
710
710
  return callOnCompletion(0);
711
711
  });
712
712
  }
713
+ /**
714
+ * Validates the configured version value.
715
+ *
716
+ * @param value Runtime version value from configuration.
717
+ * @returns The validated version string.
718
+ * @throws {TypeError} If the value is not a string, is empty, or contains
719
+ * ASCII control characters.
720
+ */
721
+ function validateVersionValue(value$1) {
722
+ if (typeof value$1 !== "string") {
723
+ const type = Array.isArray(value$1) ? "array" : typeof value$1;
724
+ throw new TypeError(`Expected version value to be a string, but got ${type}.`);
725
+ }
726
+ if (value$1 === "") throw new TypeError("Version value must not be empty.");
727
+ if (/[\x00-\x1f\x7f]/.test(value$1)) throw new TypeError("Version value must not contain control characters.");
728
+ return value$1;
729
+ }
713
730
  function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsParam) {
714
731
  const isProgram = typeof programNameOrArgs !== "string";
715
732
  let parser;
@@ -746,7 +763,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
746
763
  const onHelp = options.help?.onShow ?? (() => ({}));
747
764
  const versionCommandConfig = norm(options.version?.command);
748
765
  const versionOptionConfig = norm(options.version?.option);
749
- const versionValue = options.version?.value ?? "";
766
+ const versionValue = options.version ? validateVersionValue(options.version.value) : void 0;
750
767
  const onVersion = options.version?.onShow ?? (() => ({}));
751
768
  const completionCommandConfig = norm(options.completion?.command);
752
769
  const completionOptionConfig = norm(options.completion?.option);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.1116+6084dd3a",
3
+ "version": "1.0.0-dev.1129+30064af5",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",