@optique/core 0.10.7 → 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.
Files changed (56) hide show
  1. package/README.md +4 -6
  2. package/dist/annotations.cjs +209 -1
  3. package/dist/annotations.d.cts +78 -1
  4. package/dist/annotations.d.ts +78 -1
  5. package/dist/annotations.js +201 -1
  6. package/dist/completion.cjs +186 -50
  7. package/dist/completion.js +186 -50
  8. package/dist/constructs.cjs +310 -78
  9. package/dist/constructs.d.cts +525 -644
  10. package/dist/constructs.d.ts +525 -644
  11. package/dist/constructs.js +311 -79
  12. package/dist/context.cjs +43 -3
  13. package/dist/context.d.cts +113 -5
  14. package/dist/context.d.ts +113 -5
  15. package/dist/context.js +41 -3
  16. package/dist/dependency.cjs +172 -66
  17. package/dist/dependency.d.cts +22 -2
  18. package/dist/dependency.d.ts +22 -2
  19. package/dist/dependency.js +172 -66
  20. package/dist/doc.cjs +46 -1
  21. package/dist/doc.d.cts +24 -0
  22. package/dist/doc.d.ts +24 -0
  23. package/dist/doc.js +46 -1
  24. package/dist/facade.cjs +702 -322
  25. package/dist/facade.d.cts +124 -190
  26. package/dist/facade.d.ts +124 -190
  27. package/dist/facade.js +703 -323
  28. package/dist/index.cjs +5 -0
  29. package/dist/index.d.cts +5 -5
  30. package/dist/index.d.ts +5 -5
  31. package/dist/index.js +3 -3
  32. package/dist/message.cjs +7 -4
  33. package/dist/message.js +7 -4
  34. package/dist/mode-dispatch.cjs +23 -1
  35. package/dist/mode-dispatch.d.cts +55 -0
  36. package/dist/mode-dispatch.d.ts +55 -0
  37. package/dist/mode-dispatch.js +21 -1
  38. package/dist/modifiers.cjs +210 -55
  39. package/dist/modifiers.js +211 -56
  40. package/dist/parser.cjs +80 -47
  41. package/dist/parser.d.cts +18 -3
  42. package/dist/parser.d.ts +18 -3
  43. package/dist/parser.js +82 -50
  44. package/dist/primitives.cjs +102 -37
  45. package/dist/primitives.d.cts +81 -24
  46. package/dist/primitives.d.ts +81 -24
  47. package/dist/primitives.js +103 -39
  48. package/dist/usage.cjs +88 -6
  49. package/dist/usage.d.cts +51 -13
  50. package/dist/usage.d.ts +51 -13
  51. package/dist/usage.js +85 -7
  52. package/dist/valueparser.cjs +371 -99
  53. package/dist/valueparser.d.cts +56 -7
  54. package/dist/valueparser.d.ts +56 -7
  55. package/dist/valueparser.js +371 -99
  56. package/package.json +10 -1
package/dist/facade.js CHANGED
@@ -1,8 +1,10 @@
1
- import { annotationKey } from "./annotations.js";
1
+ import { injectAnnotations } from "./annotations.js";
2
2
  import { commandLine, formatMessage, lineBreak, message, optionName, text, value } from "./message.js";
3
3
  import { bash, fish, nu, pwsh, zsh } from "./completion.js";
4
+ import { dispatchByMode } from "./mode-dispatch.js";
4
5
  import { formatUsage } from "./usage.js";
5
6
  import { group, longestMatch, object } from "./constructs.js";
7
+ import { isPlaceholderValue } from "./context.js";
6
8
  import { formatDocPage } from "./doc.js";
7
9
  import { multiple, optional, withDefault } from "./modifiers.js";
8
10
  import { string } from "./valueparser.js";
@@ -10,52 +12,345 @@ import { argument, command, constant, flag, option } from "./primitives.js";
10
12
  import { getDocPage, parseAsync, parseSync, suggest, suggestAsync } from "./parser.js";
11
13
 
12
14
  //#region src/facade.ts
15
+ function isPlainObject(value$1) {
16
+ const proto = Object.getPrototypeOf(value$1);
17
+ return proto === Object.prototype || proto === null;
18
+ }
19
+ function shouldSkipCollectionOwnKey(value$1, key) {
20
+ if (Array.isArray(value$1)) return key === "length" || typeof key === "string" && Number.isInteger(Number(key)) && String(Number(key)) === key;
21
+ return false;
22
+ }
23
+ function containsPlaceholderValuesInOwnProperties(value$1, seen) {
24
+ for (const key of Reflect.ownKeys(value$1)) {
25
+ if (shouldSkipCollectionOwnKey(value$1, key)) continue;
26
+ const descriptor = Object.getOwnPropertyDescriptor(value$1, key);
27
+ if (descriptor != null && "value" in descriptor && containsPlaceholderValues(descriptor.value, seen)) return true;
28
+ }
29
+ return false;
30
+ }
31
+ function copySanitizedOwnProperties(source, target, seen) {
32
+ for (const key of Reflect.ownKeys(source)) {
33
+ if (shouldSkipCollectionOwnKey(source, key)) continue;
34
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
35
+ if (descriptor == null) continue;
36
+ if ("value" in descriptor) descriptor.value = stripPlaceholderValues(descriptor.value, seen);
37
+ Object.defineProperty(target, key, descriptor);
38
+ }
39
+ }
40
+ function createArrayCloneLike(value$1) {
41
+ try {
42
+ const arrayCtor = value$1.constructor;
43
+ return Reflect.construct(Array, [value$1.length], arrayCtor);
44
+ } catch {
45
+ return new Array(value$1.length);
46
+ }
47
+ }
48
+ function createSetCloneLike(value$1) {
49
+ try {
50
+ const setCtor = value$1.constructor;
51
+ return Reflect.construct(Set, [], setCtor);
52
+ } catch {
53
+ return /* @__PURE__ */ new Set();
54
+ }
55
+ }
56
+ function createMapCloneLike(value$1) {
57
+ try {
58
+ const mapCtor = value$1.constructor;
59
+ return Reflect.construct(Map, [], mapCtor);
60
+ } catch {
61
+ return /* @__PURE__ */ new Map();
62
+ }
63
+ }
64
+ function containsPlaceholderValues(value$1, seen = /* @__PURE__ */ new WeakSet()) {
65
+ if (isPlaceholderValue(value$1)) return true;
66
+ if (value$1 == null || typeof value$1 !== "object") return false;
67
+ if (seen.has(value$1)) return false;
68
+ seen.add(value$1);
69
+ if (Array.isArray(value$1)) {
70
+ if (value$1.some((item) => containsPlaceholderValues(item, seen))) return true;
71
+ return containsPlaceholderValuesInOwnProperties(value$1, seen);
72
+ }
73
+ if (value$1 instanceof Set) {
74
+ for (const entryValue of value$1) if (containsPlaceholderValues(entryValue, seen)) return true;
75
+ return containsPlaceholderValuesInOwnProperties(value$1, seen);
76
+ }
77
+ if (value$1 instanceof Map) {
78
+ for (const [key, entryValue] of value$1) if (containsPlaceholderValues(key, seen) || containsPlaceholderValues(entryValue, seen)) return true;
79
+ return containsPlaceholderValuesInOwnProperties(value$1, seen);
80
+ }
81
+ return containsPlaceholderValuesInOwnProperties(value$1, seen);
82
+ }
83
+ function stripPlaceholderValues(value$1, seen = /* @__PURE__ */ new WeakMap()) {
84
+ if (isPlaceholderValue(value$1)) return void 0;
85
+ if (value$1 == null || typeof value$1 !== "object") return value$1;
86
+ const cached = seen.get(value$1);
87
+ if (cached !== void 0) return cached;
88
+ if (Array.isArray(value$1)) {
89
+ if (!containsPlaceholderValues(value$1)) return value$1;
90
+ const clone$1 = createArrayCloneLike(value$1);
91
+ seen.set(value$1, clone$1);
92
+ for (let i = 0; i < value$1.length; i++) clone$1[i] = stripPlaceholderValues(value$1[i], seen);
93
+ copySanitizedOwnProperties(value$1, clone$1, seen);
94
+ return clone$1;
95
+ }
96
+ if (value$1 instanceof Set) {
97
+ if (!containsPlaceholderValues(value$1)) return value$1;
98
+ const clone$1 = createSetCloneLike(value$1);
99
+ seen.set(value$1, clone$1);
100
+ for (const entryValue of value$1) clone$1.add(stripPlaceholderValues(entryValue, seen));
101
+ copySanitizedOwnProperties(value$1, clone$1, seen);
102
+ return clone$1;
103
+ }
104
+ if (value$1 instanceof Map) {
105
+ if (!containsPlaceholderValues(value$1)) return value$1;
106
+ const clone$1 = createMapCloneLike(value$1);
107
+ seen.set(value$1, clone$1);
108
+ for (const [key, entryValue] of value$1) clone$1.set(stripPlaceholderValues(key, seen), stripPlaceholderValues(entryValue, seen));
109
+ copySanitizedOwnProperties(value$1, clone$1, seen);
110
+ return clone$1;
111
+ }
112
+ if (!isPlainObject(value$1)) {
113
+ if (!containsPlaceholderValues(value$1)) return value$1;
114
+ return createSanitizedNonPlainView(value$1, seen);
115
+ }
116
+ const clone = Object.create(Object.getPrototypeOf(value$1));
117
+ seen.set(value$1, clone);
118
+ for (const key of Reflect.ownKeys(value$1)) {
119
+ const descriptor = Object.getOwnPropertyDescriptor(value$1, key);
120
+ if (descriptor == null) continue;
121
+ if ("value" in descriptor) descriptor.value = stripPlaceholderValues(descriptor.value, seen);
122
+ Object.defineProperty(clone, key, descriptor);
123
+ }
124
+ return clone;
125
+ }
126
+ function finalizeParsedForContext(context, parsed) {
127
+ return context.finalizeParsed != null ? context.finalizeParsed(parsed) : parsed;
128
+ }
129
+ const SANITIZE_FAILED = Symbol("sanitizeFailed");
130
+ const activeSanitizations = /* @__PURE__ */ new WeakMap();
131
+ function callWithSanitizedOwnProperties(target, fn, args, strip, seen) {
132
+ let active = activeSanitizations.get(target);
133
+ if (active != null) active.count++;
134
+ else {
135
+ const saved = /* @__PURE__ */ new Map();
136
+ const sanitizedValues = /* @__PURE__ */ new Map();
137
+ for (const key of Reflect.ownKeys(target)) {
138
+ const desc = Object.getOwnPropertyDescriptor(target, key);
139
+ if (desc != null && "value" in desc) {
140
+ let stripped;
141
+ try {
142
+ stripped = strip(desc.value, seen);
143
+ } catch {
144
+ for (const [k, d] of saved) try {
145
+ Object.defineProperty(target, k, d);
146
+ } catch {}
147
+ return SANITIZE_FAILED;
148
+ }
149
+ if (stripped !== desc.value) try {
150
+ Object.defineProperty(target, key, {
151
+ ...desc,
152
+ value: stripped
153
+ });
154
+ saved.set(key, desc);
155
+ sanitizedValues.set(key, stripped);
156
+ } catch {
157
+ for (const [k, d] of saved) try {
158
+ Object.defineProperty(target, k, d);
159
+ } catch {}
160
+ return SANITIZE_FAILED;
161
+ }
162
+ }
163
+ }
164
+ active = {
165
+ saved,
166
+ sanitizedValues,
167
+ count: 1
168
+ };
169
+ activeSanitizations.set(target, active);
170
+ }
171
+ function release() {
172
+ active.count--;
173
+ if (active.count === 0) {
174
+ activeSanitizations.delete(target);
175
+ for (const [key, desc] of active.saved) try {
176
+ const current = Object.getOwnPropertyDescriptor(target, key);
177
+ if (current == null) continue;
178
+ if ("value" in current && current.value !== active.sanitizedValues.get(key)) continue;
179
+ Object.defineProperty(target, key, desc);
180
+ } catch {}
181
+ }
182
+ }
183
+ let result;
184
+ try {
185
+ result = fn.apply(target, args);
186
+ } catch (e) {
187
+ release();
188
+ throw e;
189
+ }
190
+ if (result instanceof Promise) return result.then((v) => {
191
+ release();
192
+ return strip(v, seen);
193
+ }, (e) => {
194
+ release();
195
+ throw e;
196
+ });
197
+ release();
198
+ return strip(result, seen);
199
+ }
200
+ function callMethodOnSanitizedTarget(fn, proxy, target, args, strip, seen) {
201
+ const result = callWithSanitizedOwnProperties(target, fn, args, strip, seen);
202
+ if (result !== SANITIZE_FAILED) return result;
203
+ const fallback = fn.apply(proxy, args);
204
+ if (fallback instanceof Promise) return fallback.then((v) => strip(v, seen));
205
+ return strip(fallback, seen);
206
+ }
207
+ function createSanitizedNonPlainView(value$1, seen) {
208
+ const methodCache = /* @__PURE__ */ new Map();
209
+ const proxy = new Proxy(value$1, {
210
+ get(target, key, receiver) {
211
+ const descriptor = Object.getOwnPropertyDescriptor(target, key);
212
+ if (descriptor != null && "value" in descriptor) {
213
+ if (!descriptor.configurable && !descriptor.writable) return descriptor.value;
214
+ const val = stripPlaceholderValues(descriptor.value, seen);
215
+ if (typeof val === "function") {
216
+ if (!descriptor.configurable && !descriptor.writable || /^class[\s{]/.test(Function.prototype.toString.call(val))) return val;
217
+ const cached = methodCache.get(key);
218
+ if (cached != null && cached.fn === val) return cached.wrapper;
219
+ const wrapper = function(...args) {
220
+ if (this !== proxy) return stripPlaceholderValues(val.apply(this, args), seen);
221
+ return callMethodOnSanitizedTarget(val, proxy, target, args, stripPlaceholderValues, seen);
222
+ };
223
+ methodCache.set(key, {
224
+ fn: val,
225
+ wrapper
226
+ });
227
+ return wrapper;
228
+ }
229
+ return val;
230
+ }
231
+ let isAccessor = false;
232
+ for (let proto = target; proto != null; proto = Object.getPrototypeOf(proto)) {
233
+ const d = Object.getOwnPropertyDescriptor(proto, key);
234
+ if (d != null) {
235
+ isAccessor = "get" in d;
236
+ break;
237
+ }
238
+ }
239
+ const result = Reflect.get(target, key, receiver);
240
+ if (typeof result === "function") {
241
+ if (/^class[\s{]/.test(Function.prototype.toString.call(result))) return result;
242
+ if (!isAccessor) {
243
+ const cached = methodCache.get(key);
244
+ if (cached != null && cached.fn === result) return cached.wrapper;
245
+ const wrapper = function(...args) {
246
+ if (this !== proxy) return stripPlaceholderValues(result.apply(this, args), seen);
247
+ return callMethodOnSanitizedTarget(result, proxy, target, args, stripPlaceholderValues, seen);
248
+ };
249
+ methodCache.set(key, {
250
+ fn: result,
251
+ wrapper
252
+ });
253
+ return wrapper;
254
+ }
255
+ return function(...args) {
256
+ if (this !== proxy) return stripPlaceholderValues(result.apply(this, args), seen);
257
+ return callMethodOnSanitizedTarget(result, proxy, target, args, stripPlaceholderValues, seen);
258
+ };
259
+ }
260
+ return stripPlaceholderValues(result, seen);
261
+ },
262
+ getOwnPropertyDescriptor(target, key) {
263
+ const descriptor = Object.getOwnPropertyDescriptor(target, key);
264
+ if (descriptor == null || !("value" in descriptor)) return descriptor;
265
+ if (!descriptor.configurable && !descriptor.writable) return descriptor;
266
+ return {
267
+ ...descriptor,
268
+ value: stripPlaceholderValues(descriptor.value, seen)
269
+ };
270
+ }
271
+ });
272
+ seen.set(value$1, proxy);
273
+ return proxy;
274
+ }
275
+ function prepareParsedForContexts(parsed) {
276
+ if (parsed == null || typeof parsed !== "object") return stripPlaceholderValues(parsed);
277
+ if (isPlaceholderValue(parsed)) return void 0;
278
+ if (Array.isArray(parsed) || isPlainObject(parsed) || parsed instanceof Set || parsed instanceof Map) {
279
+ if (!containsPlaceholderValues(parsed)) return parsed;
280
+ return stripPlaceholderValues(parsed);
281
+ }
282
+ if (!containsPlaceholderValues(parsed)) return parsed;
283
+ return createSanitizedNonPlainView(parsed, /* @__PURE__ */ new WeakMap());
284
+ }
285
+ function withPreparedParsedForContext(context, preparedParsed, run) {
286
+ return run(finalizeParsedForContext(context, preparedParsed));
287
+ }
13
288
  /**
14
- * Creates help parsers based on the specified mode.
289
+ * Creates help parsers based on the sub-config.
15
290
  */
16
- function createHelpParser(mode) {
17
- const helpCommand = command("help", multiple(argument(string({ metavar: "COMMAND" }), { description: message`Command name to show help for.` })), { description: message`Show help information.` });
18
- const helpOption = flag("--help", { description: message`Show help information.` });
19
- switch (mode) {
20
- case "command": return {
21
- helpCommand,
22
- helpOption: null
23
- };
24
- case "option": return {
25
- helpCommand: null,
26
- helpOption
27
- };
28
- case "both": return {
29
- helpCommand,
30
- helpOption
31
- };
291
+ function createHelpParser(commandConfig, optionConfig) {
292
+ let helpCommand = null;
293
+ let helpOption = null;
294
+ if (commandConfig) {
295
+ const names = commandConfig.names ?? ["help"];
296
+ const innerParser = multiple(argument(string({ metavar: "COMMAND" }), { description: message`Command name to show help for.` }));
297
+ const commandParsers = [];
298
+ for (let i = 0; i < names.length; i++) commandParsers.push(command(names[i], innerParser, {
299
+ description: message`Show help information.`,
300
+ hidden: i === 0 ? commandConfig.hidden : true
301
+ }));
302
+ helpCommand = commandParsers.length === 1 ? commandParsers[0] : longestMatch(...commandParsers);
303
+ }
304
+ if (optionConfig) {
305
+ const names = optionConfig.names ?? ["--help"];
306
+ helpOption = flag(...names, {
307
+ description: message`Show help information.`,
308
+ hidden: optionConfig.hidden
309
+ });
32
310
  }
311
+ return {
312
+ helpCommand,
313
+ helpOption
314
+ };
33
315
  }
34
316
  /**
35
- * Creates version parsers based on the specified mode.
317
+ * Creates version parsers based on the sub-config.
36
318
  */
37
- function createVersionParser(mode) {
38
- const versionCommand = command("version", object({}), { description: message`Show version information.` });
39
- const versionOption = flag("--version", { description: message`Show version information.` });
40
- switch (mode) {
41
- case "command": return {
42
- versionCommand,
43
- versionOption: null
44
- };
45
- case "option": return {
46
- versionCommand: null,
47
- versionOption
48
- };
49
- case "both": return {
50
- versionCommand,
51
- versionOption
52
- };
319
+ function createVersionParser(commandConfig, optionConfig) {
320
+ let versionCommand = null;
321
+ let versionOption = null;
322
+ if (commandConfig) {
323
+ const names = commandConfig.names ?? ["version"];
324
+ const innerParser = object({});
325
+ const commandParsers = [];
326
+ for (let i = 0; i < names.length; i++) commandParsers.push(command(names[i], innerParser, {
327
+ description: message`Show version information.`,
328
+ hidden: i === 0 ? commandConfig.hidden : true
329
+ }));
330
+ versionCommand = commandParsers.length === 1 ? commandParsers[0] : longestMatch(...commandParsers);
53
331
  }
332
+ if (optionConfig) {
333
+ const names = optionConfig.names ?? ["--version"];
334
+ versionOption = flag(...names, {
335
+ description: message`Show version information.`,
336
+ hidden: optionConfig.hidden
337
+ });
338
+ }
339
+ return {
340
+ versionCommand,
341
+ versionOption
342
+ };
343
+ }
344
+ const metaResultBrand = Symbol("@optique/core/facade/meta");
345
+ function isMetaParseResult(value$1) {
346
+ return typeof value$1 === "object" && value$1 != null && metaResultBrand in value$1;
54
347
  }
55
348
  /**
56
- * Creates completion parsers based on the specified mode.
349
+ * Creates completion parsers based on the sub-config.
57
350
  */
58
- function createCompletionParser(mode, programName, availableShells, name = "both", helpVisibility = name) {
351
+ function createCompletionParser(programName, availableShells, commandConfig, optionConfig) {
352
+ let completionCommand = null;
353
+ let completionOption = null;
59
354
  const shellList = [];
60
355
  for (const shell in availableShells) {
61
356
  if (shellList.length > 0) shellList.push(text(", "));
@@ -65,56 +360,44 @@ function createCompletionParser(mode, programName, availableShells, name = "both
65
360
  shell: optional(argument(string({ metavar: "SHELL" }), { description: message`Shell type (${shellList}). Generate completion script when used alone, or provide completions when followed by arguments.` })),
66
361
  args: multiple(argument(string({ metavar: "ARG" }), { description: message`Command line arguments for completion suggestions (used by shell integration; you usually don't need to provide this).` }))
67
362
  });
68
- const commandName = name === "plural" ? "completions" : "completion";
69
- const completionCommandConfig = {
70
- brief: message`Generate shell completion script or provide completions.`,
71
- description: message`Generate shell completion script or provide completions.`,
72
- footer: message`Examples:${lineBreak()} Bash: ${commandLine(`eval "$(${programName} ${commandName} bash)"`)}${lineBreak()} zsh: ${commandLine(`eval "$(${programName} ${commandName} zsh)"`)}${lineBreak()} fish: ${commandLine(`eval "$(${programName} ${commandName} fish)"`)}${lineBreak()} PowerShell: ${commandLine(`${programName} ${commandName} pwsh > ${programName}-completion.ps1; . ./${programName}-completion.ps1`)}${lineBreak()} Nushell: ${commandLine(`${programName} ${commandName} nu | save ${programName}-completion.nu; source ./${programName}-completion.nu`)}`
73
- };
74
- const completionCommands = [];
75
- const showSingular = helpVisibility === "singular" || helpVisibility === "both";
76
- const showPlural = helpVisibility === "plural" || helpVisibility === "both";
77
- if (name === "singular" || name === "both") completionCommands.push(command("completion", completionInner, {
78
- ...completionCommandConfig,
79
- hidden: !showSingular
80
- }));
81
- if (name === "plural" || name === "both") completionCommands.push(command("completions", completionInner, {
82
- ...completionCommandConfig,
83
- hidden: !showPlural
84
- }));
85
- const completionCommand = longestMatch(...completionCommands);
86
- const completionOptions = [];
87
- if (name === "singular" || name === "both") completionOptions.push(option("--completion", string({ metavar: "SHELL" }), {
88
- description: message`Generate shell completion script.`,
89
- hidden: !showSingular
90
- }));
91
- if (name === "plural" || name === "both") completionOptions.push(option("--completions", string({ metavar: "SHELL" }), {
92
- description: message`Generate shell completion script.`,
93
- hidden: !showPlural
94
- }));
95
- const completionOption = completionOptions.length === 1 ? completionOptions[0] : longestMatch(completionOptions[0], completionOptions[1]);
96
- const argsParser = withDefault(multiple(argument(string({ metavar: "ARG" }), { description: message`Command line arguments for completion suggestions (used by shell integration; you usually don't need to provide this).` })), []);
97
- const optionParser = object({
98
- shell: completionOption,
99
- args: argsParser
100
- });
101
- switch (mode) {
102
- case "command": return {
103
- completionCommand,
104
- completionOption: null
105
- };
106
- case "option": return {
107
- completionCommand: null,
108
- completionOption: optionParser
109
- };
110
- case "both": return {
111
- completionCommand,
112
- completionOption: optionParser
363
+ if (commandConfig) {
364
+ const names = commandConfig.names ?? ["completion"];
365
+ const displayName = names[0];
366
+ const completionCommandConfig = {
367
+ brief: message`Generate shell completion script or provide completions.`,
368
+ description: message`Generate shell completion script or provide completions.`,
369
+ footer: message`Examples:${lineBreak()} Bash: ${commandLine(`eval "$(${programName} ${displayName} bash)"`)}${lineBreak()} zsh: ${commandLine(`eval "$(${programName} ${displayName} zsh)"`)}${lineBreak()} fish: ${commandLine(`eval "$(${programName} ${displayName} fish)"`)}${lineBreak()} PowerShell: ${commandLine(`${programName} ${displayName} pwsh > ${programName}-completion.ps1; . ./${programName}-completion.ps1`)}${lineBreak()} Nushell: ${commandLine(`${programName} ${displayName} nu | save ${programName}-completion.nu; source ./${programName}-completion.nu`)}`
113
370
  };
371
+ const commandParsers = [];
372
+ for (let i = 0; i < names.length; i++) commandParsers.push(command(names[i], completionInner, {
373
+ ...completionCommandConfig,
374
+ hidden: i === 0 ? commandConfig.hidden : true
375
+ }));
376
+ completionCommand = commandParsers.length === 1 ? commandParsers[0] : longestMatch(...commandParsers);
377
+ }
378
+ if (optionConfig) {
379
+ const names = optionConfig.names ?? ["--completion"];
380
+ const completionOptions = [];
381
+ for (const name of names) completionOptions.push(option(name, string({ metavar: "SHELL" }), {
382
+ description: message`Generate shell completion script.`,
383
+ hidden: optionConfig.hidden
384
+ }));
385
+ const completionOptionParser = completionOptions.length === 1 ? completionOptions[0] : longestMatch(...completionOptions);
386
+ const argsParser = withDefault(multiple(argument(string({ metavar: "ARG" }), { description: message`Command line arguments for completion suggestions (used by shell integration; you usually don't need to provide this).` })), []);
387
+ completionOption = object({
388
+ shell: completionOptionParser,
389
+ args: argsParser
390
+ });
114
391
  }
392
+ return {
393
+ completionCommand,
394
+ completionOption
395
+ };
115
396
  }
116
- function combineWithHelpVersion(originalParser, helpParsers, versionParsers, completionParsers, groups) {
397
+ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, completionParsers, groups, helpOptionNames, versionOptionNames) {
117
398
  const parsers = [];
399
+ const effectiveHelpOptionNames = helpOptionNames ?? ["--help"];
400
+ const effectiveVersionOptionNames = versionOptionNames ?? ["--version"];
118
401
  if (helpParsers.helpOption) {
119
402
  const lenientHelpParser = {
120
403
  $mode: "sync",
@@ -135,11 +418,11 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
135
418
  let versionIndex = -1;
136
419
  for (let i = 0; i < buffer.length; i++) {
137
420
  if (buffer[i] === "--") break;
138
- if (buffer[i] === "--help") {
421
+ if (effectiveHelpOptionNames.includes(buffer[i])) {
139
422
  helpFound = true;
140
423
  helpIndex = i;
141
424
  }
142
- if (buffer[i] === "--version") versionIndex = i;
425
+ if (effectiveVersionOptionNames.includes(buffer[i])) versionIndex = i;
143
426
  }
144
427
  if (helpFound && versionIndex > helpIndex) return {
145
428
  success: false,
@@ -158,8 +441,10 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
158
441
  ...context,
159
442
  buffer: [],
160
443
  state: {
444
+ [metaResultBrand]: true,
161
445
  help: true,
162
446
  version: false,
447
+ completion: false,
163
448
  commands,
164
449
  helpFlag: true
165
450
  }
@@ -169,7 +454,7 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
169
454
  }
170
455
  return {
171
456
  success: false,
172
- error: message`Flag ${optionName("--help")} not found.`,
457
+ error: message`Flag ${optionName(effectiveHelpOptionNames[0])} not found.`,
173
458
  consumed: 0
174
459
  };
175
460
  },
@@ -180,16 +465,17 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
180
465
  };
181
466
  },
182
467
  *suggest(_context, prefix) {
183
- if ("--help".startsWith(prefix)) yield {
468
+ for (const name of effectiveHelpOptionNames) if (name.startsWith(prefix)) yield {
184
469
  kind: "literal",
185
- text: "--help"
470
+ text: name
186
471
  };
187
472
  },
188
473
  getDocFragments(state) {
189
474
  return helpParsers.helpOption?.getDocFragments(state) ?? { fragments: [] };
190
475
  }
191
476
  };
192
- parsers.push(lenientHelpParser);
477
+ const wrappedHelp = groups?.helpOptionGroup ? group(groups.helpOptionGroup, lenientHelpParser) : lenientHelpParser;
478
+ parsers.push(wrappedHelp);
193
479
  }
194
480
  if (versionParsers.versionOption) {
195
481
  const lenientVersionParser = {
@@ -211,11 +497,11 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
211
497
  let helpIndex = -1;
212
498
  for (let i = 0; i < buffer.length; i++) {
213
499
  if (buffer[i] === "--") break;
214
- if (buffer[i] === "--version") {
500
+ if (effectiveVersionOptionNames.includes(buffer[i])) {
215
501
  versionFound = true;
216
502
  versionIndex = i;
217
503
  }
218
- if (buffer[i] === "--help") helpIndex = i;
504
+ if (effectiveHelpOptionNames.includes(buffer[i])) helpIndex = i;
219
505
  }
220
506
  if (versionFound && helpIndex > versionIndex) return {
221
507
  success: false,
@@ -228,8 +514,10 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
228
514
  ...context,
229
515
  buffer: [],
230
516
  state: {
517
+ [metaResultBrand]: true,
231
518
  help: false,
232
519
  version: true,
520
+ completion: false,
233
521
  versionFlag: true
234
522
  }
235
523
  },
@@ -237,7 +525,7 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
237
525
  };
238
526
  return {
239
527
  success: false,
240
- error: message`Flag ${optionName("--version")} not found.`,
528
+ error: message`Flag ${optionName(effectiveVersionOptionNames[0])} not found.`,
241
529
  consumed: 0
242
530
  };
243
531
  },
@@ -248,47 +536,52 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
248
536
  };
249
537
  },
250
538
  *suggest(_context, prefix) {
251
- if ("--version".startsWith(prefix)) yield {
539
+ for (const name of effectiveVersionOptionNames) if (name.startsWith(prefix)) yield {
252
540
  kind: "literal",
253
- text: "--version"
541
+ text: name
254
542
  };
255
543
  },
256
544
  getDocFragments(state) {
257
545
  return versionParsers.versionOption?.getDocFragments(state) ?? { fragments: [] };
258
546
  }
259
547
  };
260
- parsers.push(lenientVersionParser);
548
+ const wrappedVersion = groups?.versionOptionGroup ? group(groups.versionOptionGroup, lenientVersionParser) : lenientVersionParser;
549
+ parsers.push(wrappedVersion);
261
550
  }
262
551
  if (versionParsers.versionCommand) {
263
552
  const versionParser = object({
553
+ [metaResultBrand]: constant(true),
264
554
  help: constant(false),
265
555
  version: constant(true),
266
556
  completion: constant(false),
267
557
  result: versionParsers.versionCommand,
268
558
  helpFlag: helpParsers.helpOption ? optional(helpParsers.helpOption) : constant(false)
269
559
  });
270
- parsers.push(groups?.versionGroup ? group(groups.versionGroup, versionParser) : versionParser);
560
+ parsers.push(groups?.versionCommandGroup ? group(groups.versionCommandGroup, versionParser) : versionParser);
271
561
  }
272
562
  if (completionParsers.completionCommand) {
273
563
  const completionParser = object({
564
+ [metaResultBrand]: constant(true),
274
565
  help: constant(false),
275
566
  version: constant(false),
276
567
  completion: constant(true),
277
568
  completionData: completionParsers.completionCommand,
278
569
  helpFlag: helpParsers.helpOption ? optional(helpParsers.helpOption) : constant(false)
279
570
  });
280
- parsers.push(groups?.completionGroup ? group(groups.completionGroup, completionParser) : completionParser);
571
+ parsers.push(groups?.completionCommandGroup ? group(groups.completionCommandGroup, completionParser) : completionParser);
281
572
  }
282
573
  if (helpParsers.helpCommand) {
283
574
  const helpParser = object({
575
+ [metaResultBrand]: constant(true),
284
576
  help: constant(true),
285
577
  version: constant(false),
286
578
  completion: constant(false),
287
579
  commands: helpParsers.helpCommand
288
580
  });
289
- parsers.push(groups?.helpGroup ? group(groups.helpGroup, helpParser) : helpParser);
581
+ parsers.push(groups?.helpCommandGroup ? group(groups.helpCommandGroup, helpParser) : helpParser);
290
582
  }
291
583
  parsers.push(object({
584
+ [metaResultBrand]: constant(true),
292
585
  help: constant(false),
293
586
  version: constant(false),
294
587
  completion: constant(false),
@@ -316,29 +609,29 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
316
609
  return combined;
317
610
  }
318
611
  /**
319
- * Classifies the parsing result into a discriminated union for cleaner handling.
612
+ * Classifies the parsing result into a discriminated union for cleaner
613
+ * handling.
320
614
  */
321
- function classifyResult(result, args) {
615
+ function classifyResult(result, args, helpOptionNames, helpCommandNames, versionOptionNames, versionCommandNames, completionCommandNames) {
322
616
  if (!result.success) return {
323
617
  type: "error",
324
618
  error: result.error
325
619
  };
326
620
  const value$1 = result.value;
327
- if (typeof value$1 === "object" && value$1 != null && "help" in value$1 && "version" in value$1) {
621
+ if (isMetaParseResult(value$1)) {
328
622
  const parsedValue = value$1;
329
- const hasVersionOption = args.includes("--version");
330
- const hasVersionCommand = args.length > 0 && args[0] === "version";
331
- const hasHelpOption = args.includes("--help");
332
- const hasHelpCommand = args.length > 0 && args[0] === "help";
333
- const hasCompletionCommand = args.length > 0 && args[0] === "completion";
334
- if (hasVersionOption && hasHelpOption && !hasVersionCommand && !hasHelpCommand) {}
623
+ const hasVersionOption = versionOptionNames.some((n) => args.includes(n));
624
+ const hasVersionCommand = args.length > 0 && versionCommandNames.includes(args[0]);
625
+ const hasCompletionCommand = args.length > 0 && completionCommandNames.includes(args[0]);
626
+ const hasHelpOption = hasCompletionCommand ? helpOptionNames.length > 0 && args.length >= 2 && helpOptionNames.includes(args[1]) : helpOptionNames.some((n) => args.includes(n));
627
+ const hasHelpCommand = args.length > 0 && helpCommandNames.includes(args[0]);
335
628
  if (hasVersionCommand && hasHelpOption && parsedValue.helpFlag) return {
336
629
  type: "help",
337
- commands: ["version"]
630
+ commands: [args[0]]
338
631
  };
339
632
  if (hasCompletionCommand && hasHelpOption && parsedValue.helpFlag) return {
340
633
  type: "help",
341
- commands: ["completion"]
634
+ commands: [args[0]]
342
635
  };
343
636
  if (parsedValue.help && (hasHelpOption || hasHelpCommand)) {
344
637
  let commandContext = [];
@@ -352,12 +645,12 @@ function classifyResult(result, args) {
352
645
  if ((hasVersionOption || hasVersionCommand) && (parsedValue.version || parsedValue.versionFlag)) return { type: "version" };
353
646
  if (parsedValue.completion && parsedValue.completionData) return {
354
647
  type: "completion",
355
- shell: parsedValue.completionData.shell || "",
356
- args: parsedValue.completionData.args || []
648
+ shell: parsedValue.completionData.shell ?? "",
649
+ args: parsedValue.completionData.args ?? []
357
650
  };
358
651
  return {
359
652
  type: "success",
360
- value: parsedValue.result ?? value$1
653
+ value: "result" in parsedValue ? parsedValue.result : value$1
361
654
  };
362
655
  }
363
656
  return {
@@ -369,34 +662,23 @@ function classifyResult(result, args) {
369
662
  * Handles shell completion requests.
370
663
  * @since 0.6.0
371
664
  */
372
- function handleCompletion(completionArgs, programName, parser, completionParser, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth, completionMode, completionName) {
665
+ function handleCompletion(completionArgs, programName, parser, completionParser, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth, completionCommandDisplayName, completionOptionDisplayName, isOptionMode, sectionOrder) {
373
666
  const shellName = completionArgs[0] || "";
374
667
  const args = completionArgs.slice(1);
375
- const callOnError = (code) => {
376
- try {
377
- return onError(code);
378
- } catch {
379
- return onError();
380
- }
381
- };
382
- const callOnCompletion = (code) => {
383
- try {
384
- return onCompletion(code);
385
- } catch {
386
- return onCompletion();
387
- }
388
- };
668
+ const callOnError = (code) => onError(code);
669
+ const callOnCompletion = (code) => onCompletion(code);
389
670
  if (!shellName) {
390
671
  stderr("Error: Missing shell name for completion.\n");
391
672
  if (completionParser) {
392
- const doc = getDocPage(completionParser, ["completion"]);
673
+ const displayName = completionCommandDisplayName ?? "completion";
674
+ const doc = getDocPage(completionParser, [displayName]);
393
675
  if (doc) stderr(formatDocPage(programName, doc, {
394
676
  colors,
395
- maxWidth
677
+ maxWidth,
678
+ sectionOrder
396
679
  }));
397
680
  }
398
- if (parser.$mode === "async") return Promise.resolve(callOnError(1));
399
- return callOnError(1);
681
+ return dispatchByMode(parser.$mode, () => callOnError(1), () => Promise.resolve(callOnError(1)));
400
682
  }
401
683
  const shell = availableShells[shellName];
402
684
  if (!shell) {
@@ -409,26 +691,24 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
409
691
  colors,
410
692
  quotes: !colors
411
693
  }));
412
- if (parser.$mode === "async") return Promise.resolve(callOnError(1));
413
- return callOnError(1);
694
+ return dispatchByMode(parser.$mode, () => callOnError(1), () => Promise.resolve(callOnError(1)));
414
695
  }
415
696
  if (args.length === 0) {
416
- const usePlural = completionName === "plural";
417
- const completionArg = completionMode === "option" ? usePlural ? "--completions" : "--completion" : usePlural ? "completions" : "completion";
697
+ const completionArg = isOptionMode ? completionOptionDisplayName ?? "--completion" : completionCommandDisplayName ?? "completion";
418
698
  const script = shell.generateScript(programName, [completionArg, shellName]);
419
699
  stdout(script);
420
- if (parser.$mode === "async") return Promise.resolve(callOnCompletion(0));
421
- return callOnCompletion(0);
700
+ return dispatchByMode(parser.$mode, () => callOnCompletion(0), () => Promise.resolve(callOnCompletion(0)));
422
701
  }
423
- if (parser.$mode === "async") return (async () => {
424
- const suggestions$1 = await suggestAsync(parser, args);
425
- for (const chunk of shell.encodeSuggestions(suggestions$1)) stdout(chunk);
702
+ return dispatchByMode(parser.$mode, () => {
703
+ const syncParser = parser;
704
+ const suggestions = suggest(syncParser, args);
705
+ for (const chunk of shell.encodeSuggestions(suggestions)) stdout(chunk);
426
706
  return callOnCompletion(0);
427
- })();
428
- const syncParser = parser;
429
- const suggestions = suggest(syncParser, args);
430
- for (const chunk of shell.encodeSuggestions(suggestions)) stdout(chunk);
431
- return callOnCompletion(0);
707
+ }, async () => {
708
+ const suggestions = await suggestAsync(parser, args);
709
+ for (const chunk of shell.encodeSuggestions(suggestions)) stdout(chunk);
710
+ return callOnCompletion(0);
711
+ });
432
712
  }
433
713
  function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsParam) {
434
714
  const isProgram = typeof programNameOrArgs !== "string";
@@ -457,21 +737,28 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
457
737
  args = argsOrOptions;
458
738
  options = optionsParam ?? {};
459
739
  }
460
- const { colors, maxWidth, showDefault, showChoices, aboveError = "usage", onError = () => {
740
+ const { colors, maxWidth, showDefault, showChoices, sectionOrder, aboveError = "usage", onError = () => {
461
741
  throw new RunParserError("Failed to parse command line arguments.");
462
742
  }, stderr = console.error, stdout = console.log, brief, description, examples, author, bugs, footer } = options;
463
- const helpMode = options.help?.mode ?? "option";
743
+ const norm = (c) => c === true ? {} : c;
744
+ const helpCommandConfig = norm(options.help?.command);
745
+ const helpOptionConfig = norm(options.help?.option);
464
746
  const onHelp = options.help?.onShow ?? (() => ({}));
465
- const helpGroup = options.help?.group;
466
- const versionMode = options.version?.mode ?? "option";
747
+ const versionCommandConfig = norm(options.version?.command);
748
+ const versionOptionConfig = norm(options.version?.option);
467
749
  const versionValue = options.version?.value ?? "";
468
750
  const onVersion = options.version?.onShow ?? (() => ({}));
469
- const versionGroup = options.version?.group;
470
- const completionMode = options.completion?.mode ?? "both";
471
- const completionName = options.completion?.name ?? "both";
472
- const completionHelpVisibility = options.completion?.helpVisibility ?? completionName;
751
+ const completionCommandConfig = norm(options.completion?.command);
752
+ const completionOptionConfig = norm(options.completion?.option);
473
753
  const onCompletion = options.completion?.onShow ?? (() => ({}));
474
- const completionGroup = options.completion?.group;
754
+ const onCompletionResult = (code) => onCompletion(code);
755
+ const onErrorResult = (code) => onError(code);
756
+ const helpOptionNames = helpOptionConfig?.names ?? ["--help"];
757
+ const helpCommandNames = helpCommandConfig?.names ?? ["help"];
758
+ const versionOptionNames = versionOptionConfig?.names ?? ["--version"];
759
+ const versionCommandNames = versionCommandConfig?.names ?? ["version"];
760
+ const completionCommandNames = completionCommandConfig?.names ?? ["completion"];
761
+ const completionOptionNames = completionOptionConfig?.names ?? ["--completion"];
475
762
  const defaultShells = {
476
763
  bash,
477
764
  fish,
@@ -483,72 +770,65 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
483
770
  ...defaultShells,
484
771
  ...options.completion.shells
485
772
  } : defaultShells;
486
- const help = options.help ? helpMode : "none";
487
- const version = options.version ? versionMode : "none";
488
- const completion = options.completion ? completionMode : "none";
489
- const helpParsers = help === "none" ? {
773
+ const helpParsers = options.help ? createHelpParser(helpCommandConfig, helpOptionConfig) : {
490
774
  helpCommand: null,
491
775
  helpOption: null
492
- } : createHelpParser(help);
493
- const versionParsers = version === "none" ? {
776
+ };
777
+ const versionParsers = options.version ? createVersionParser(versionCommandConfig, versionOptionConfig) : {
494
778
  versionCommand: null,
495
779
  versionOption: null
496
- } : createVersionParser(version);
497
- const completionParsers = completion === "none" ? {
780
+ };
781
+ const completionParsers = options.completion ? createCompletionParser(programName, availableShells, completionCommandConfig, completionOptionConfig) : {
498
782
  completionCommand: null,
499
783
  completionOption: null
500
- } : createCompletionParser(completion, programName, availableShells, completionName, completionHelpVisibility);
784
+ };
501
785
  if (options.completion) {
502
- const hasHelpOption = args.includes("--help");
503
- if ((completionMode === "command" || completionMode === "both") && args.length >= 1 && ((completionName === "singular" || completionName === "both" ? args[0] === "completion" : false) || (completionName === "plural" || completionName === "both" ? args[0] === "completions" : false)) && !hasHelpOption) return handleCompletion(args.slice(1), programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth, completionMode, completionName);
504
- if (completionMode === "option" || completionMode === "both") for (let i = 0; i < args.length; i++) {
786
+ const hasHelpOption = helpOptionConfig ? completionCommandConfig && completionCommandNames.includes(args[0]) ? args.length >= 2 && helpOptionNames.includes(args[1]) : helpOptionNames.some((n) => args.includes(n)) : false;
787
+ if (completionCommandConfig && args.length >= 1 && completionCommandNames.includes(args[0]) && !hasHelpOption) return handleCompletion(args.slice(1), programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletionResult, onErrorResult, availableShells, colors, maxWidth, completionCommandNames[0], completionOptionNames[0], false, sectionOrder);
788
+ if (completionOptionConfig) for (let i = 0; i < args.length; i++) {
505
789
  const arg = args[i];
506
- const singularMatch = completionName === "singular" || completionName === "both" ? arg.startsWith("--completion=") : false;
507
- const pluralMatch = completionName === "plural" || completionName === "both" ? arg.startsWith("--completions=") : false;
508
- if (singularMatch || pluralMatch) {
509
- const shell = arg.slice(arg.indexOf("=") + 1);
790
+ const equalsMatch = completionOptionNames.find((n) => arg.startsWith(n + "="));
791
+ if (equalsMatch) {
792
+ const shell = arg.slice(equalsMatch.length + 1);
510
793
  const completionArgs = args.slice(i + 1);
511
- return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth, completionMode, completionName);
512
- } else {
513
- const singularMatchExact = completionName === "singular" || completionName === "both" ? arg === "--completion" : false;
514
- const pluralMatchExact = completionName === "plural" || completionName === "both" ? arg === "--completions" : false;
515
- if (singularMatchExact || pluralMatchExact) {
516
- const shell = i + 1 < args.length ? args[i + 1] : "";
517
- const completionArgs = i + 1 < args.length ? args.slice(i + 2) : [];
518
- return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletion, onError, availableShells, colors, maxWidth, completionMode, completionName);
519
- }
794
+ return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletionResult, onErrorResult, availableShells, colors, maxWidth, completionCommandNames[0], completionOptionNames[0], true, sectionOrder);
795
+ }
796
+ const exactMatch = completionOptionNames.includes(arg);
797
+ if (exactMatch) {
798
+ const shell = i + 1 < args.length ? args[i + 1] : "";
799
+ const completionArgs = i + 1 < args.length ? args.slice(i + 2) : [];
800
+ return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletionResult, onErrorResult, availableShells, colors, maxWidth, completionCommandNames[0], completionOptionNames[0], true, sectionOrder);
520
801
  }
521
802
  }
522
803
  }
523
- const augmentedParser = help === "none" && version === "none" && completion === "none" ? parser : combineWithHelpVersion(parser, helpParsers, versionParsers, completionParsers, {
524
- helpGroup,
525
- versionGroup,
526
- completionGroup
527
- });
804
+ const augmentedParser = !options.help && !options.version && !options.completion ? parser : combineWithHelpVersion(parser, helpParsers, versionParsers, completionParsers, {
805
+ helpCommandGroup: helpCommandConfig?.group,
806
+ helpOptionGroup: helpOptionConfig?.group,
807
+ versionCommandGroup: versionCommandConfig?.group,
808
+ versionOptionGroup: versionOptionConfig?.group,
809
+ completionCommandGroup: completionCommandConfig?.group,
810
+ completionOptionGroup: completionOptionConfig?.group
811
+ }, helpOptionConfig ? [...helpOptionNames] : void 0, versionOptionConfig ? [...versionOptionNames] : void 0);
528
812
  const handleResult = (result) => {
529
- const classified = classifyResult(result, args);
813
+ const classified = classifyResult(result, args, helpOptionConfig ? [...helpOptionNames] : [], helpCommandConfig ? [...helpCommandNames] : [], versionOptionConfig ? [...versionOptionNames] : [], versionCommandConfig ? [...versionCommandNames] : [], completionCommandConfig ? [...completionCommandNames] : []);
530
814
  switch (classified.type) {
531
815
  case "success": return classified.value;
532
816
  case "version":
533
817
  stdout(versionValue);
534
- try {
535
- return onVersion(0);
536
- } catch {
537
- return onVersion();
538
- }
818
+ return onVersion(0);
539
819
  case "completion": throw new RunParserError("Completion should be handled by early return");
540
820
  case "help": {
541
821
  let helpGeneratorParser;
542
- const helpAsCommand = help === "command" || help === "both";
543
- const versionAsCommand = version === "command" || version === "both";
544
- const completionAsCommand = completion === "command" || completion === "both";
545
- const helpAsOption = help === "option" || help === "both";
546
- const versionAsOption = version === "option" || version === "both";
547
- const completionAsOption = completion === "option" || completion === "both";
822
+ const helpAsCommand = helpCommandConfig != null;
823
+ const versionAsCommand = versionCommandConfig != null;
824
+ const completionAsCommand = completionCommandConfig != null;
825
+ const helpAsOption = helpOptionConfig != null;
826
+ const versionAsOption = versionOptionConfig != null;
827
+ const completionAsOption = completionOptionConfig != null;
548
828
  const requestedCommand = classified.commands[0];
549
- if ((requestedCommand === "completion" || requestedCommand === "completions") && completionAsCommand && completionParsers.completionCommand) helpGeneratorParser = completionParsers.completionCommand;
550
- else if (requestedCommand === "help" && helpAsCommand && helpParsers.helpCommand) helpGeneratorParser = helpParsers.helpCommand;
551
- else if (requestedCommand === "version" && versionAsCommand && versionParsers.versionCommand) helpGeneratorParser = versionParsers.versionCommand;
829
+ if (requestedCommand != null && completionCommandNames.includes(requestedCommand) && completionAsCommand && completionParsers.completionCommand) helpGeneratorParser = completionParsers.completionCommand;
830
+ else if (requestedCommand != null && helpCommandNames.includes(requestedCommand) && helpAsCommand && helpParsers.helpCommand) helpGeneratorParser = helpParsers.helpCommand;
831
+ else if (requestedCommand != null && versionCommandNames.includes(requestedCommand) && versionAsCommand && versionParsers.versionCommand) helpGeneratorParser = versionParsers.versionCommand;
552
832
  else {
553
833
  const commandParsers = [parser];
554
834
  const groupedMeta = {};
@@ -557,15 +837,24 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
557
837
  if (groupLabel) (groupedMeta[groupLabel] ??= []).push(p);
558
838
  else ungroupedMeta.push(p);
559
839
  };
560
- if (helpAsCommand && helpParsers.helpCommand) addMeta(helpParsers.helpCommand, helpGroup);
561
- if (versionAsCommand && versionParsers.versionCommand) addMeta(versionParsers.versionCommand, versionGroup);
562
- if (completionAsCommand && completionParsers.completionCommand) addMeta(completionParsers.completionCommand, completionGroup);
840
+ if (helpAsCommand && helpParsers.helpCommand) addMeta(helpParsers.helpCommand, helpCommandConfig?.group);
841
+ if (versionAsCommand && versionParsers.versionCommand) addMeta(versionParsers.versionCommand, versionCommandConfig?.group);
842
+ if (completionAsCommand && completionParsers.completionCommand) addMeta(completionParsers.completionCommand, completionCommandConfig?.group);
563
843
  commandParsers.push(...ungroupedMeta);
564
844
  for (const [label, parsers] of Object.entries(groupedMeta)) if (parsers.length === 1) commandParsers.push(group(label, parsers[0]));
565
845
  else commandParsers.push(group(label, longestMatch(...parsers)));
566
- if (helpAsOption && helpParsers.helpOption) commandParsers.push(helpParsers.helpOption);
567
- if (versionAsOption && versionParsers.versionOption) commandParsers.push(versionParsers.versionOption);
568
- if (completionAsOption && completionParsers.completionOption) commandParsers.push(completionParsers.completionOption);
846
+ const groupedMetaOptions = {};
847
+ const ungroupedMetaOptions = [];
848
+ const addMetaOption = (p, groupLabel) => {
849
+ if (groupLabel) (groupedMetaOptions[groupLabel] ??= []).push(p);
850
+ else ungroupedMetaOptions.push(p);
851
+ };
852
+ if (helpAsOption && helpParsers.helpOption) addMetaOption(helpParsers.helpOption, helpOptionConfig?.group);
853
+ if (versionAsOption && versionParsers.versionOption) addMetaOption(versionParsers.versionOption, versionOptionConfig?.group);
854
+ if (completionAsOption && completionParsers.completionOption) addMetaOption(completionParsers.completionOption, completionOptionConfig?.group);
855
+ commandParsers.push(...ungroupedMetaOptions);
856
+ for (const [label, optParsers] of Object.entries(groupedMetaOptions)) if (optParsers.length === 1) commandParsers.push(group(label, optParsers[0]));
857
+ else commandParsers.push(group(label, longestMatch(...optParsers)));
569
858
  if (commandParsers.length === 1) helpGeneratorParser = commandParsers[0];
570
859
  else if (commandParsers.length === 2) helpGeneratorParser = longestMatch(commandParsers[0], commandParsers[1]);
571
860
  else helpGeneratorParser = longestMatch(...commandParsers);
@@ -583,6 +872,31 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
583
872
  stderr(`Error: ${errorMessage}`);
584
873
  return onError(1);
585
874
  };
875
+ const displayHelp = (doc) => {
876
+ if (doc != null) {
877
+ const isMetaCommandHelp = requestedCommand != null && completionCommandNames.includes(requestedCommand) || requestedCommand != null && helpCommandNames.includes(requestedCommand) || requestedCommand != null && versionCommandNames.includes(requestedCommand);
878
+ const isSubcommandHelp = classified.commands.length > 0;
879
+ const isTopLevel = !isSubcommandHelp;
880
+ const shouldOverride = !isMetaCommandHelp && !isSubcommandHelp;
881
+ const augmentedDoc = {
882
+ ...doc,
883
+ brief: shouldOverride ? brief ?? doc.brief : doc.brief,
884
+ description: shouldOverride ? description ?? doc.description : doc.description,
885
+ examples: isTopLevel && !isMetaCommandHelp ? examples ?? doc.examples : void 0,
886
+ author: isTopLevel && !isMetaCommandHelp ? author ?? doc.author : void 0,
887
+ bugs: isTopLevel && !isMetaCommandHelp ? bugs ?? doc.bugs : void 0,
888
+ footer: shouldOverride ? footer ?? doc.footer : doc.footer ?? footer
889
+ };
890
+ stdout(formatDocPage(programName, augmentedDoc, {
891
+ colors,
892
+ maxWidth,
893
+ showDefault,
894
+ showChoices,
895
+ sectionOrder
896
+ }));
897
+ }
898
+ return onHelp(0);
899
+ };
586
900
  if (classified.commands.length > 0) {
587
901
  let validationContext = {
588
902
  buffer: [...classified.commands],
@@ -623,34 +937,6 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
623
937
  }
624
938
  if (validationResult != null) return reportInvalidHelpCommand(validationResult);
625
939
  }
626
- const displayHelp = (doc) => {
627
- if (doc != null) {
628
- const isMetaCommandHelp = (completionName === "singular" || completionName === "both" ? requestedCommand === "completion" : false) || (completionName === "plural" || completionName === "both" ? requestedCommand === "completions" : false) || requestedCommand === "help" || requestedCommand === "version";
629
- const isSubcommandHelp = classified.commands.length > 0;
630
- const isTopLevel = !isSubcommandHelp;
631
- const shouldOverride = !isMetaCommandHelp && !isSubcommandHelp;
632
- const augmentedDoc = {
633
- ...doc,
634
- brief: shouldOverride ? brief ?? doc.brief : doc.brief,
635
- description: shouldOverride ? description ?? doc.description : doc.description,
636
- examples: isTopLevel && !isMetaCommandHelp ? examples ?? doc.examples : void 0,
637
- author: isTopLevel && !isMetaCommandHelp ? author ?? doc.author : void 0,
638
- bugs: isTopLevel && !isMetaCommandHelp ? bugs ?? doc.bugs : void 0,
639
- footer: shouldOverride ? footer ?? doc.footer : doc.footer ?? footer
640
- };
641
- stdout(formatDocPage(programName, augmentedDoc, {
642
- colors,
643
- maxWidth,
644
- showDefault,
645
- showChoices
646
- }));
647
- }
648
- try {
649
- return onHelp(0);
650
- } catch {
651
- return onHelp();
652
- }
653
- };
654
940
  const docOrPromise = getDocPage(helpGeneratorParser, classified.commands);
655
941
  if (docOrPromise instanceof Promise) return docOrPromise.then(displayHelp);
656
942
  return displayHelp(docOrPromise);
@@ -699,11 +985,17 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
699
985
  default: throw new RunParserError("Unexpected parse result type");
700
986
  }
701
987
  };
702
- if (parser.$mode === "async") return parseAsync(augmentedParser, args).then(handleResult);
703
- else {
988
+ const parserMode = parser.$mode;
989
+ return dispatchByMode(parserMode, () => {
704
990
  const result = parseSync(augmentedParser, args);
705
- return handleResult(result);
706
- }
991
+ const handled = handleResult(result);
992
+ if (handled instanceof Promise) throw new RunParserError("Synchronous parser returned async result.");
993
+ return handled;
994
+ }, async () => {
995
+ const result = await parseAsync(augmentedParser, args);
996
+ const handled = handleResult(result);
997
+ return handled instanceof Promise ? await handled : handled;
998
+ });
707
999
  }
708
1000
  /**
709
1001
  * Runs a synchronous command-line parser with the given options.
@@ -772,26 +1064,31 @@ function indentLines(text$1, indent) {
772
1064
  * @returns `true` if early exit should be performed, `false` otherwise.
773
1065
  */
774
1066
  function needsEarlyExit(args, options) {
1067
+ const norm = (c) => c === true ? {} : c;
775
1068
  if (options.help) {
776
- const helpMode = options.help.mode ?? "option";
777
- if ((helpMode === "option" || helpMode === "both") && args.includes("--help")) return true;
778
- if ((helpMode === "command" || helpMode === "both") && args[0] === "help") return true;
1069
+ const helpOptionConfig = norm(options.help.option);
1070
+ const helpCommandConfig = norm(options.help.command);
1071
+ const helpOptionNames = helpOptionConfig?.names ?? ["--help"];
1072
+ const helpCommandNames = helpCommandConfig?.names ?? ["help"];
1073
+ if (helpOptionConfig && helpOptionNames.some((n) => args.includes(n))) return true;
1074
+ if (helpCommandConfig && helpCommandNames.includes(args[0])) return true;
779
1075
  }
780
1076
  if (options.version) {
781
- const versionMode = options.version.mode ?? "option";
782
- if ((versionMode === "option" || versionMode === "both") && args.includes("--version")) return true;
783
- if ((versionMode === "command" || versionMode === "both") && args[0] === "version") return true;
1077
+ const versionOptionConfig = norm(options.version.option);
1078
+ const versionCommandConfig = norm(options.version.command);
1079
+ const versionOptionNames = versionOptionConfig?.names ?? ["--version"];
1080
+ const versionCommandNames = versionCommandConfig?.names ?? ["version"];
1081
+ if (versionOptionConfig && versionOptionNames.some((n) => args.includes(n))) return true;
1082
+ if (versionCommandConfig && versionCommandNames.includes(args[0])) return true;
784
1083
  }
785
1084
  if (options.completion) {
786
- const completionMode = options.completion.mode ?? "both";
787
- const completionName = options.completion.name ?? "both";
788
- if (completionMode === "command" || completionMode === "both") {
789
- if ((completionName === "singular" || completionName === "both") && args[0] === "completion") return true;
790
- if ((completionName === "plural" || completionName === "both") && args[0] === "completions") return true;
791
- }
792
- if (completionMode === "option" || completionMode === "both") for (const arg of args) {
793
- if ((completionName === "singular" || completionName === "both") && (arg === "--completion" || arg.startsWith("--completion="))) return true;
794
- if ((completionName === "plural" || completionName === "both") && (arg === "--completions" || arg.startsWith("--completions="))) return true;
1085
+ const completionCommandConfig = norm(options.completion.command);
1086
+ const completionOptionConfig = norm(options.completion.option);
1087
+ const completionCommandNames = completionCommandConfig?.names ?? ["completion"];
1088
+ const completionOptionNames = completionOptionConfig?.names ?? ["--completion"];
1089
+ if (completionCommandConfig && completionCommandNames.includes(args[0])) return true;
1090
+ if (completionOptionConfig) {
1091
+ for (const arg of args) for (const name of completionOptionNames) if (arg === name || arg.startsWith(name + "=")) return true;
795
1092
  }
796
1093
  }
797
1094
  return false;
@@ -818,23 +1115,22 @@ function mergeAnnotations(annotationsList) {
818
1115
  * two-phase parsing is needed.
819
1116
  *
820
1117
  * @param contexts Source contexts to collect annotations from.
1118
+ * @param options Optional context-required options to pass to each context.
821
1119
  * @returns Promise with merged annotations and dynamic-context hint.
822
1120
  */
823
- async function collectPhase1Annotations(contexts) {
1121
+ async function collectPhase1Annotations(contexts, options) {
824
1122
  const annotationsList = [];
825
1123
  let hasDynamic = false;
826
1124
  for (const context of contexts) {
827
- const result = context.getAnnotations();
828
- if (result instanceof Promise) {
829
- hasDynamic = true;
830
- annotationsList.push(await result);
831
- } else {
832
- if (Object.getOwnPropertySymbols(result).length === 0) hasDynamic = true;
833
- annotationsList.push(result);
834
- }
1125
+ const result = context.getAnnotations(void 0, options);
1126
+ hasDynamic ||= needsTwoPhaseContext(context, result);
1127
+ const annotations = result instanceof Promise ? await result : result;
1128
+ const internalAnnotations = context.getInternalAnnotations?.(void 0, annotations);
1129
+ annotationsList.push(internalAnnotations == null ? annotations : mergeAnnotations([annotations, internalAnnotations]));
835
1130
  }
836
1131
  return {
837
1132
  annotations: mergeAnnotations(annotationsList),
1133
+ annotationsList,
838
1134
  hasDynamic
839
1135
  };
840
1136
  }
@@ -843,54 +1139,131 @@ async function collectPhase1Annotations(contexts) {
843
1139
  *
844
1140
  * @param contexts Source contexts to collect annotations from.
845
1141
  * @param parsed Optional parsed result from a previous parse pass.
1142
+ * @param options Optional context-required options to pass to each context.
846
1143
  * @returns Promise that resolves to merged annotations.
847
1144
  */
848
- async function collectAnnotations(contexts, parsed) {
1145
+ async function collectAnnotations(contexts, parsed, options) {
849
1146
  const annotationsList = [];
1147
+ const preparedParsed = prepareParsedForContexts(parsed);
850
1148
  for (const context of contexts) {
851
- const result = context.getAnnotations(parsed);
852
- annotationsList.push(result instanceof Promise ? await result : result);
1149
+ const mergedAnnotations = await withPreparedParsedForContext(context, preparedParsed, async (contextParsed) => {
1150
+ const result = context.getAnnotations(contextParsed, options);
1151
+ const annotations = result instanceof Promise ? await result : result;
1152
+ const internalAnnotations = context.getInternalAnnotations?.(contextParsed, annotations);
1153
+ return internalAnnotations == null ? annotations : mergeAnnotations([annotations, internalAnnotations]);
1154
+ });
1155
+ annotationsList.push(mergedAnnotations);
853
1156
  }
854
- return mergeAnnotations(annotationsList);
1157
+ return {
1158
+ annotations: mergeAnnotations(annotationsList),
1159
+ annotationsList
1160
+ };
855
1161
  }
856
1162
  /**
857
1163
  * Collects phase 1 annotations from all contexts synchronously and determines
858
1164
  * whether two-phase parsing is needed.
859
1165
  *
860
1166
  * @param contexts Source contexts to collect annotations from.
1167
+ * @param options Optional context-required options to pass to each context.
861
1168
  * @returns Merged annotations with dynamic-context hint.
862
1169
  * @throws Error if any context returns a Promise.
863
1170
  */
864
- function collectPhase1AnnotationsSync(contexts) {
1171
+ function collectPhase1AnnotationsSync(contexts, options) {
865
1172
  const annotationsList = [];
866
1173
  let hasDynamic = false;
867
1174
  for (const context of contexts) {
868
- const result = context.getAnnotations();
1175
+ const result = context.getAnnotations(void 0, options);
869
1176
  if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
870
- if (Object.getOwnPropertySymbols(result).length === 0) hasDynamic = true;
871
- annotationsList.push(result);
1177
+ hasDynamic ||= needsTwoPhaseContext(context, result);
1178
+ const internalAnnotations = context.getInternalAnnotations?.(void 0, result);
1179
+ annotationsList.push(internalAnnotations == null ? result : mergeAnnotations([result, internalAnnotations]));
872
1180
  }
873
1181
  return {
874
1182
  annotations: mergeAnnotations(annotationsList),
1183
+ annotationsList,
875
1184
  hasDynamic
876
1185
  };
877
1186
  }
878
1187
  /**
1188
+ * Determines whether a context requires a second parse pass.
1189
+ *
1190
+ * Explicit `mode` declarations take precedence over legacy heuristics so
1191
+ * static contexts are not forced into two-phase parsing when they return
1192
+ * empty annotations or a Promise.
1193
+ */
1194
+ function needsTwoPhaseContext(context, result) {
1195
+ if (context.mode !== void 0) return context.mode === "dynamic";
1196
+ if (result instanceof Promise) return true;
1197
+ return Object.getOwnPropertySymbols(result).length === 0;
1198
+ }
1199
+ /**
879
1200
  * Collects annotations from all contexts synchronously.
880
1201
  *
881
1202
  * @param contexts Source contexts to collect annotations from.
882
1203
  * @param parsed Optional parsed result from a previous parse pass.
1204
+ * @param options Optional context-required options to pass to each context.
883
1205
  * @returns Merged annotations.
884
1206
  * @throws Error if any context returns a Promise.
885
1207
  */
886
- function collectAnnotationsSync(contexts, parsed) {
1208
+ function collectAnnotationsSync(contexts, parsed, options) {
887
1209
  const annotationsList = [];
1210
+ const preparedParsed = prepareParsedForContexts(parsed);
888
1211
  for (const context of contexts) {
889
- const result = context.getAnnotations(parsed);
890
- if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
891
- annotationsList.push(result);
1212
+ const mergedAnnotations = withPreparedParsedForContext(context, preparedParsed, (contextParsed) => {
1213
+ const result = context.getAnnotations(contextParsed, options);
1214
+ if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
1215
+ const internalAnnotations = context.getInternalAnnotations?.(contextParsed, result);
1216
+ return internalAnnotations == null ? result : mergeAnnotations([result, internalAnnotations]);
1217
+ });
1218
+ annotationsList.push(mergedAnnotations);
892
1219
  }
893
- return mergeAnnotations(annotationsList);
1220
+ return {
1221
+ annotations: mergeAnnotations(annotationsList),
1222
+ annotationsList
1223
+ };
1224
+ }
1225
+ function mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList) {
1226
+ const mergedPerContext = [];
1227
+ const length = Math.max(phase1AnnotationsList.length, phase2AnnotationsList.length);
1228
+ for (let i = 0; i < length; i++) mergedPerContext.push(mergeAnnotations([phase2AnnotationsList[i] ?? {}, phase1AnnotationsList[i] ?? {}]));
1229
+ return mergeAnnotations(mergedPerContext);
1230
+ }
1231
+ /**
1232
+ * Disposes all contexts that implement `AsyncDisposable` or `Disposable`.
1233
+ * Prefers `[Symbol.asyncDispose]` over `[Symbol.dispose]`.
1234
+ *
1235
+ * @param contexts Source contexts to dispose.
1236
+ */
1237
+ async function disposeContexts(contexts) {
1238
+ const errors = [];
1239
+ for (const context of contexts) try {
1240
+ if (Symbol.asyncDispose in context && typeof context[Symbol.asyncDispose] === "function") await context[Symbol.asyncDispose]();
1241
+ else if (Symbol.dispose in context && typeof context[Symbol.dispose] === "function") context[Symbol.dispose]();
1242
+ } catch (error) {
1243
+ errors.push(error);
1244
+ }
1245
+ if (errors.length === 1) throw errors[0];
1246
+ if (errors.length > 1) throw new AggregateError(errors, "Failed to dispose one or more source contexts.");
1247
+ }
1248
+ /**
1249
+ * Disposes all contexts that implement `Disposable` synchronously.
1250
+ * Falls back to `[Symbol.asyncDispose]` when it completes synchronously.
1251
+ *
1252
+ * @param contexts Source contexts to dispose.
1253
+ */
1254
+ function disposeContextsSync(contexts) {
1255
+ const errors = [];
1256
+ for (const context of contexts) try {
1257
+ if (Symbol.dispose in context && typeof context[Symbol.dispose] === "function") context[Symbol.dispose]();
1258
+ else if (Symbol.asyncDispose in context && typeof context[Symbol.asyncDispose] === "function") {
1259
+ const result = context[Symbol.asyncDispose]();
1260
+ if (typeof result === "object" && result !== null && "then" in result && typeof result.then === "function") throw new TypeError(`Context ${String(context.id)} returned a Promise from Symbol.asyncDispose in sync mode. Use runWith() or runWithAsync() for async disposal.`);
1261
+ }
1262
+ } catch (error) {
1263
+ errors.push(error);
1264
+ }
1265
+ if (errors.length === 1) throw errors[0];
1266
+ if (errors.length > 1) throw new AggregateError(errors, "Failed to dispose one or more source contexts.");
894
1267
  }
895
1268
  /**
896
1269
  * Runs a parser with multiple source contexts.
@@ -950,36 +1323,41 @@ async function runWith(parser, programName, contexts, options) {
950
1323
  if (parser.$mode === "async") return runParser(parser, programName, args, options);
951
1324
  return Promise.resolve(runParser(parser, programName, args, options));
952
1325
  }
953
- const { annotations: phase1Annotations, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts);
954
- if (!needsTwoPhase) {
955
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
956
- if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
957
- return Promise.resolve(runParser(augmentedParser, programName, args, options));
958
- }
959
- const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
960
- let firstPassResult;
961
- let firstPassFailed = false;
962
1326
  try {
963
- if (parser.$mode === "async") firstPassResult = await parseAsync(augmentedParser1, args);
964
- else firstPassResult = parseSync(augmentedParser1, args);
965
- if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
966
- const result = firstPassResult;
967
- if (result.success) firstPassResult = result.value;
968
- else firstPassFailed = true;
1327
+ const ctxOptions = options?.contextOptions;
1328
+ const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts, ctxOptions);
1329
+ if (!needsTwoPhase) {
1330
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1331
+ if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1332
+ return Promise.resolve(runParser(augmentedParser, programName, args, options));
969
1333
  }
970
- } catch {
971
- firstPassFailed = true;
972
- }
973
- if (firstPassFailed) {
974
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
975
- if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
976
- return Promise.resolve(runParser(augmentedParser, programName, args, options));
1334
+ const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1335
+ let firstPassResult;
1336
+ let firstPassFailed = false;
1337
+ try {
1338
+ if (parser.$mode === "async") firstPassResult = await parseAsync(augmentedParser1, args);
1339
+ else firstPassResult = parseSync(augmentedParser1, args);
1340
+ if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
1341
+ const result = firstPassResult;
1342
+ if (result.success) firstPassResult = result.value;
1343
+ else firstPassFailed = true;
1344
+ }
1345
+ } catch {
1346
+ firstPassFailed = true;
1347
+ }
1348
+ if (firstPassFailed) {
1349
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1350
+ if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1351
+ return Promise.resolve(runParser(augmentedParser, programName, args, options));
1352
+ }
1353
+ const { annotationsList: phase2AnnotationsList } = await collectAnnotations(contexts, firstPassResult, ctxOptions);
1354
+ const finalAnnotations = mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList);
1355
+ const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1356
+ if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
1357
+ return Promise.resolve(runParser(augmentedParser2, programName, args, options));
1358
+ } finally {
1359
+ await disposeContexts(contexts);
977
1360
  }
978
- const phase2Annotations = await collectAnnotations(contexts, firstPassResult);
979
- const finalAnnotations = mergeAnnotations([phase1Annotations, phase2Annotations]);
980
- const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
981
- if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
982
- return Promise.resolve(runParser(augmentedParser2, programName, args, options));
983
1361
  }
984
1362
  /**
985
1363
  * Runs a synchronous parser with multiple source contexts.
@@ -995,31 +1373,37 @@ async function runWith(parser, programName, contexts, options) {
995
1373
  * @param contexts Source contexts to use (priority: earlier overrides later).
996
1374
  * @param options Run options including args, help, version, etc.
997
1375
  * @returns The parsed result.
998
- * @throws Error if any context returns a Promise.
1376
+ * @throws Error if any context returns a Promise or if a context's
1377
+ * `[Symbol.asyncDispose]` returns a Promise.
999
1378
  * @since 0.10.0
1000
1379
  */
1001
1380
  function runWithSync(parser, programName, contexts, options) {
1002
1381
  const args = options?.args ?? [];
1003
1382
  if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
1004
1383
  if (contexts.length === 0) return runParser(parser, programName, args, options);
1005
- const { annotations: phase1Annotations, hasDynamic: needsTwoPhase } = collectPhase1AnnotationsSync(contexts);
1006
- if (!needsTwoPhase) {
1007
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1008
- return runParser(augmentedParser, programName, args, options);
1009
- }
1010
- const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1011
- let firstPassResult;
1012
1384
  try {
1013
- const result = parseSync(augmentedParser1, args);
1014
- if (result.success) firstPassResult = result.value;
1015
- else return runParser(augmentedParser1, programName, args, options);
1016
- } catch {
1017
- return runParser(augmentedParser1, programName, args, options);
1385
+ const ctxOptions = options?.contextOptions;
1386
+ const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = collectPhase1AnnotationsSync(contexts, ctxOptions);
1387
+ if (!needsTwoPhase) {
1388
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1389
+ return runParser(augmentedParser, programName, args, options);
1390
+ }
1391
+ const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1392
+ let firstPassResult;
1393
+ try {
1394
+ const result = parseSync(augmentedParser1, args);
1395
+ if (result.success) firstPassResult = result.value;
1396
+ else return runParser(augmentedParser1, programName, args, options);
1397
+ } catch {
1398
+ return runParser(augmentedParser1, programName, args, options);
1399
+ }
1400
+ const { annotationsList: phase2AnnotationsList } = collectAnnotationsSync(contexts, firstPassResult, ctxOptions);
1401
+ const finalAnnotations = mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList);
1402
+ const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1403
+ return runParser(augmentedParser2, programName, args, options);
1404
+ } finally {
1405
+ disposeContextsSync(contexts);
1018
1406
  }
1019
- const phase2Annotations = collectAnnotationsSync(contexts, firstPassResult);
1020
- const finalAnnotations = mergeAnnotations([phase1Annotations, phase2Annotations]);
1021
- const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1022
- return runParser(augmentedParser2, programName, args, options);
1023
1407
  }
1024
1408
  /**
1025
1409
  * Runs any parser asynchronously with multiple source contexts.
@@ -1048,11 +1432,7 @@ function runWithAsync(parser, programName, contexts, options) {
1048
1432
  * @returns A new parser with annotations in its initial state.
1049
1433
  */
1050
1434
  function injectAnnotationsIntoParser(parser, annotations) {
1051
- if (parser.initialState == null) return parser;
1052
- const newInitialState = {
1053
- ...parser.initialState,
1054
- [annotationKey]: annotations
1055
- };
1435
+ const newInitialState = injectAnnotations(parser.initialState, annotations);
1056
1436
  return {
1057
1437
  ...parser,
1058
1438
  initialState: newInitialState