@optique/run 0.10.7-dev.485 → 1.0.0-dev.1109

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.
@@ -9,6 +9,12 @@ import { ValueParser } from "@optique/core/valueparser";
9
9
  * @since 0.5.0
10
10
  */
11
11
  interface PathErrorOptions {
12
+ /**
13
+ * Custom error message when the path is empty or contains only whitespace.
14
+ * Can be a static message or a function that receives the raw input string.
15
+ * @since 1.0.0
16
+ */
17
+ emptyPath?: Message | ((input: string) => Message);
12
18
  /**
13
19
  * Custom error message when file extension is invalid.
14
20
  * Can be a static message or a function that receives input, expected
@@ -143,6 +149,17 @@ type PathOptions = PathOptionsMustExist | PathOptionsMustNotExist | PathOptionsN
143
149
  *
144
150
  * @param options Configuration options for path validation.
145
151
  * @returns A ValueParser that validates and returns string paths.
152
+ * @throws {TypeError} If {@link PathOptionsBase.type} is not one of
153
+ * `"file"`, `"directory"`, or `"either"`.
154
+ * @throws {TypeError} If any entry in {@link PathOptionsBase.extensions} does
155
+ * not start with a dot (e.g., `"json"` instead of `".json"`).
156
+ * @throws {TypeError} If {@link PathOptionsMustExist.mustExist} is not a
157
+ * boolean.
158
+ * @throws {TypeError} If {@link PathOptionsMustNotExist.mustNotExist} is not a
159
+ * boolean.
160
+ * @throws {TypeError} If {@link PathOptionsBase.allowCreate} is not a boolean.
161
+ * @throws {TypeError} If both {@link PathOptionsMustExist.mustExist} and
162
+ * {@link PathOptionsMustNotExist.mustNotExist} are `true`.
146
163
  *
147
164
  * @example
148
165
  * ```typescript
@@ -9,6 +9,12 @@ import { ValueParser } from "@optique/core/valueparser";
9
9
  * @since 0.5.0
10
10
  */
11
11
  interface PathErrorOptions {
12
+ /**
13
+ * Custom error message when the path is empty or contains only whitespace.
14
+ * Can be a static message or a function that receives the raw input string.
15
+ * @since 1.0.0
16
+ */
17
+ emptyPath?: Message | ((input: string) => Message);
12
18
  /**
13
19
  * Custom error message when file extension is invalid.
14
20
  * Can be a static message or a function that receives input, expected
@@ -143,6 +149,17 @@ type PathOptions = PathOptionsMustExist | PathOptionsMustNotExist | PathOptionsN
143
149
  *
144
150
  * @param options Configuration options for path validation.
145
151
  * @returns A ValueParser that validates and returns string paths.
152
+ * @throws {TypeError} If {@link PathOptionsBase.type} is not one of
153
+ * `"file"`, `"directory"`, or `"either"`.
154
+ * @throws {TypeError} If any entry in {@link PathOptionsBase.extensions} does
155
+ * not start with a dot (e.g., `"json"` instead of `".json"`).
156
+ * @throws {TypeError} If {@link PathOptionsMustExist.mustExist} is not a
157
+ * boolean.
158
+ * @throws {TypeError} If {@link PathOptionsMustNotExist.mustNotExist} is not a
159
+ * boolean.
160
+ * @throws {TypeError} If {@link PathOptionsBase.allowCreate} is not a boolean.
161
+ * @throws {TypeError} If both {@link PathOptionsMustExist.mustExist} and
162
+ * {@link PathOptionsMustNotExist.mustNotExist} are `true`.
146
163
  *
147
164
  * @example
148
165
  * ```typescript
@@ -1,4 +1,4 @@
1
- import { dirname, extname } from "node:path";
1
+ import { basename, dirname, extname } from "node:path";
2
2
  import { message, text } from "@optique/core/message";
3
3
  import { ensureNonEmptyString } from "@optique/core/nonempty";
4
4
  import { existsSync, statSync } from "node:fs";
@@ -13,6 +13,17 @@ import { existsSync, statSync } from "node:fs";
13
13
  *
14
14
  * @param options Configuration options for path validation.
15
15
  * @returns A ValueParser that validates and returns string paths.
16
+ * @throws {TypeError} If {@link PathOptionsBase.type} is not one of
17
+ * `"file"`, `"directory"`, or `"either"`.
18
+ * @throws {TypeError} If any entry in {@link PathOptionsBase.extensions} does
19
+ * not start with a dot (e.g., `"json"` instead of `".json"`).
20
+ * @throws {TypeError} If {@link PathOptionsMustExist.mustExist} is not a
21
+ * boolean.
22
+ * @throws {TypeError} If {@link PathOptionsMustNotExist.mustNotExist} is not a
23
+ * boolean.
24
+ * @throws {TypeError} If {@link PathOptionsBase.allowCreate} is not a boolean.
25
+ * @throws {TypeError} If both {@link PathOptionsMustExist.mustExist} and
26
+ * {@link PathOptionsMustNotExist.mustNotExist} are `true`.
16
27
  *
17
28
  * @example
18
29
  * ```typescript
@@ -42,16 +53,30 @@ import { existsSync, statSync } from "node:fs";
42
53
  function path(options = {}) {
43
54
  const { metavar = "PATH", type = "either", allowCreate = false, extensions } = options;
44
55
  ensureNonEmptyString(metavar);
56
+ if (type !== "file" && type !== "directory" && type !== "either") throw new TypeError(`Unsupported path type: ${JSON.stringify(type)}. Expected "file", "directory", or "either".`);
57
+ if (extensions) {
58
+ for (const ext of extensions) if (!ext.startsWith(".")) throw new TypeError(`Each extension must start with a dot, got: ${JSON.stringify(ext)}`);
59
+ }
60
+ if (options.allowCreate !== void 0 && typeof options.allowCreate !== "boolean") throw new TypeError(`Expected allowCreate to be a boolean, but got ${typeof options.allowCreate}: ${String(options.allowCreate)}.`);
61
+ const rawOptions = options;
62
+ if ("mustExist" in options && rawOptions.mustExist !== void 0 && typeof rawOptions.mustExist !== "boolean") throw new TypeError(`Expected mustExist to be a boolean, but got ${typeof rawOptions.mustExist}: ${String(rawOptions.mustExist)}.`);
63
+ if ("mustNotExist" in options && rawOptions.mustNotExist !== void 0 && typeof rawOptions.mustNotExist !== "boolean") throw new TypeError(`Expected mustNotExist to be a boolean, but got ${typeof rawOptions.mustNotExist}: ${String(rawOptions.mustNotExist)}.`);
45
64
  const mustExist = "mustExist" in options ? options.mustExist : false;
46
65
  const mustNotExist = "mustNotExist" in options ? options.mustNotExist : false;
66
+ if (mustExist && mustNotExist) throw new TypeError("Options mustExist and mustNotExist are mutually exclusive.");
47
67
  return {
48
68
  $mode: "sync",
49
69
  metavar,
50
70
  parse(input) {
51
- if (extensions && extensions.length > 0) {
52
- const ext = extname(input);
53
- if (!extensions.includes(ext)) {
54
- const actualExt = ext || "no extension";
71
+ if (input.trim() === "") return {
72
+ success: false,
73
+ error: options.errors?.emptyPath ? typeof options.errors.emptyPath === "function" ? options.errors.emptyPath(input) : options.errors.emptyPath : message`Path must not be empty.`
74
+ };
75
+ if (type !== "directory" && extensions && extensions.length > 0) {
76
+ const base = /[/\\]$/.test(input) ? "" : basename(input);
77
+ if (!extensions.some((ext) => base.endsWith(ext))) {
78
+ const ext = extname(input);
79
+ const actualExt = ext || (base.startsWith(".") ? base : "no extension");
55
80
  return {
56
81
  success: false,
57
82
  error: options.errors?.invalidExtension ? typeof options.errors.invalidExtension === "function" ? options.errors.invalidExtension(input, extensions, actualExt) : options.errors.invalidExtension : message`Expected file with extension ${text(extensions.join(", "))}, got ${text(actualExt)}.`
@@ -111,7 +136,7 @@ function path(options = {}) {
111
136
  pattern: prefix,
112
137
  type: type === "either" ? "any" : type,
113
138
  extensions,
114
- includeHidden: prefix.startsWith("."),
139
+ includeHidden: basename(prefix).startsWith("."),
115
140
  description: type === "directory" ? message`Directory` : type === "file" ? message`File` : message`File or directory`
116
141
  };
117
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/run",
3
- "version": "0.10.7-dev.485+0a30b635",
3
+ "version": "1.0.0-dev.1109+fa132665",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -70,7 +70,7 @@
70
70
  },
71
71
  "sideEffects": false,
72
72
  "dependencies": {
73
- "@optique/core": "0.10.7-dev.485+0a30b635"
73
+ "@optique/core": "1.0.0-dev.1109+fa132665"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@types/node": "^20.19.9",