@optique/core 1.0.0-dev.908 → 1.0.0

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 (109) hide show
  1. package/dist/annotation-state.cjs +425 -0
  2. package/dist/annotation-state.d.cts +24 -0
  3. package/dist/annotation-state.d.ts +24 -0
  4. package/dist/annotation-state.js +414 -0
  5. package/dist/annotations.cjs +2 -248
  6. package/dist/annotations.d.cts +2 -137
  7. package/dist/annotations.d.ts +2 -137
  8. package/dist/annotations.js +2 -238
  9. package/dist/completion.cjs +611 -100
  10. package/dist/completion.d.cts +1 -1
  11. package/dist/completion.d.ts +1 -1
  12. package/dist/completion.js +611 -100
  13. package/dist/constructs.cjs +3338 -827
  14. package/dist/constructs.d.cts +48 -7
  15. package/dist/constructs.d.ts +48 -7
  16. package/dist/constructs.js +3338 -827
  17. package/dist/context.cjs +0 -23
  18. package/dist/context.d.cts +119 -53
  19. package/dist/context.d.ts +119 -53
  20. package/dist/context.js +0 -22
  21. package/dist/dependency-metadata.cjs +139 -0
  22. package/dist/dependency-metadata.d.cts +112 -0
  23. package/dist/dependency-metadata.d.ts +112 -0
  24. package/dist/dependency-metadata.js +138 -0
  25. package/dist/dependency-runtime.cjs +698 -0
  26. package/dist/dependency-runtime.d.cts +149 -0
  27. package/dist/dependency-runtime.d.ts +149 -0
  28. package/dist/dependency-runtime.js +687 -0
  29. package/dist/dependency.cjs +7 -928
  30. package/dist/dependency.d.cts +2 -794
  31. package/dist/dependency.d.ts +2 -794
  32. package/dist/dependency.js +2 -899
  33. package/dist/displaywidth.cjs +44 -0
  34. package/dist/displaywidth.js +43 -0
  35. package/dist/doc.cjs +285 -23
  36. package/dist/doc.d.cts +57 -2
  37. package/dist/doc.d.ts +57 -2
  38. package/dist/doc.js +283 -25
  39. package/dist/execution-context.cjs +56 -0
  40. package/dist/execution-context.js +53 -0
  41. package/dist/extension.cjs +87 -0
  42. package/dist/extension.d.cts +97 -0
  43. package/dist/extension.d.ts +97 -0
  44. package/dist/extension.js +76 -0
  45. package/dist/facade.cjs +718 -523
  46. package/dist/facade.d.cts +87 -18
  47. package/dist/facade.d.ts +87 -18
  48. package/dist/facade.js +718 -523
  49. package/dist/index.cjs +14 -29
  50. package/dist/index.d.cts +10 -10
  51. package/dist/index.d.ts +10 -10
  52. package/dist/index.js +7 -7
  53. package/dist/input-trace.cjs +56 -0
  54. package/dist/input-trace.d.cts +77 -0
  55. package/dist/input-trace.d.ts +77 -0
  56. package/dist/input-trace.js +55 -0
  57. package/dist/internal/annotations.cjs +316 -0
  58. package/dist/internal/annotations.d.cts +140 -0
  59. package/dist/internal/annotations.d.ts +140 -0
  60. package/dist/internal/annotations.js +306 -0
  61. package/dist/internal/dependency.cjs +984 -0
  62. package/dist/internal/dependency.d.cts +539 -0
  63. package/dist/internal/dependency.d.ts +539 -0
  64. package/dist/internal/dependency.js +964 -0
  65. package/dist/{mode-dispatch.cjs → internal/mode-dispatch.cjs} +1 -3
  66. package/dist/{mode-dispatch.d.cts → internal/mode-dispatch.d.cts} +3 -7
  67. package/dist/{mode-dispatch.d.ts → internal/mode-dispatch.d.ts} +3 -7
  68. package/dist/{mode-dispatch.js → internal/mode-dispatch.js} +1 -3
  69. package/dist/internal/parser.cjs +728 -0
  70. package/dist/internal/parser.d.cts +947 -0
  71. package/dist/internal/parser.d.ts +947 -0
  72. package/dist/internal/parser.js +711 -0
  73. package/dist/message.cjs +84 -26
  74. package/dist/message.d.cts +49 -9
  75. package/dist/message.d.ts +49 -9
  76. package/dist/message.js +84 -27
  77. package/dist/modifiers.cjs +1023 -240
  78. package/dist/modifiers.d.cts +42 -1
  79. package/dist/modifiers.d.ts +42 -1
  80. package/dist/modifiers.js +1023 -240
  81. package/dist/parser.cjs +11 -463
  82. package/dist/parser.d.cts +3 -537
  83. package/dist/parser.d.ts +3 -537
  84. package/dist/parser.js +2 -433
  85. package/dist/phase2-seed.cjs +59 -0
  86. package/dist/phase2-seed.js +56 -0
  87. package/dist/primitives.cjs +557 -208
  88. package/dist/primitives.d.cts +10 -14
  89. package/dist/primitives.d.ts +10 -14
  90. package/dist/primitives.js +557 -208
  91. package/dist/program.cjs +5 -1
  92. package/dist/program.d.cts +5 -3
  93. package/dist/program.d.ts +5 -3
  94. package/dist/program.js +6 -1
  95. package/dist/suggestion.cjs +22 -8
  96. package/dist/suggestion.js +22 -8
  97. package/dist/usage-internals.cjs +3 -2
  98. package/dist/usage-internals.js +4 -2
  99. package/dist/usage.cjs +195 -40
  100. package/dist/usage.d.cts +92 -11
  101. package/dist/usage.d.ts +92 -11
  102. package/dist/usage.js +194 -41
  103. package/dist/validate.cjs +170 -0
  104. package/dist/validate.js +164 -0
  105. package/dist/valueparser.cjs +1278 -191
  106. package/dist/valueparser.d.cts +330 -20
  107. package/dist/valueparser.d.ts +330 -20
  108. package/dist/valueparser.js +1277 -192
  109. package/package.json +9 -9
package/dist/facade.js CHANGED
@@ -1,295 +1,282 @@
1
- import { injectAnnotations } from "./annotations.js";
1
+ import { injectAnnotations, isInjectedAnnotationWrapper, unwrapInjectedAnnotationWrapper } from "./internal/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
+ import { validateCommandNames, validateContextIds, validateMetaNameCollisions, validateOptionNames, validateProgramName } from "./validate.js";
5
5
  import { formatUsage } from "./usage.js";
6
- import { group, longestMatch, object } from "./constructs.js";
7
6
  import { formatDocPage } from "./doc.js";
7
+ import { dispatchByMode } from "./internal/mode-dispatch.js";
8
+ import { createDependencyRuntimeContext } from "./dependency-runtime.js";
9
+ import { createInputTrace } from "./input-trace.js";
10
+ import { createParserContext, getDocPage, suggest, suggestAsync } from "./internal/parser.js";
11
+ import { completeOrExtractPhase2Seed } from "./phase2-seed.js";
12
+ import { group, longestMatch, object } from "./constructs.js";
8
13
  import { multiple, optional, withDefault } from "./modifiers.js";
9
14
  import { string } from "./valueparser.js";
10
15
  import { argument, command, constant, flag, option } from "./primitives.js";
11
- import { getDocPage, parseAsync, parseSync, suggest, suggestAsync } from "./parser.js";
12
16
 
13
17
  //#region src/facade.ts
14
- const phase1ConfigAnnotationsKey = Symbol.for("@optique/config/phase1PromptAnnotations");
15
- const phase2UndefinedParsedValueKey = Symbol.for("@optique/config/phase2UndefinedParsedValue");
16
- const deferredPromptValueKey = Symbol.for("@optique/inquirer/deferredPromptValue");
17
- function isDeferredPromptValue(value$1) {
18
- return value$1 != null && typeof value$1 === "object" && deferredPromptValueKey in value$1;
19
- }
18
+ const SuppressedErrorCtor = typeof SuppressedError === "function" ? SuppressedError : (() => {
19
+ class SuppressedErrorPolyfill extends Error {
20
+ error;
21
+ suppressed;
22
+ constructor(error, suppressed, message$1) {
23
+ super(message$1);
24
+ this.name = "SuppressedError";
25
+ this.error = error;
26
+ this.suppressed = suppressed;
27
+ }
28
+ }
29
+ return SuppressedErrorPolyfill;
30
+ })();
20
31
  function isPlainObject(value$1) {
21
32
  const proto = Object.getPrototypeOf(value$1);
22
33
  return proto === Object.prototype || proto === null;
23
34
  }
24
- function shouldSkipCollectionOwnKey(value$1, key) {
25
- if (Array.isArray(value$1)) return key === "length" || typeof key === "string" && Number.isInteger(Number(key)) && String(Number(key)) === key;
26
- return false;
27
- }
28
- function containsDeferredPromptValuesInOwnProperties(value$1, seen) {
29
- for (const key of Reflect.ownKeys(value$1)) {
30
- if (shouldSkipCollectionOwnKey(value$1, key)) continue;
31
- const descriptor = Object.getOwnPropertyDescriptor(value$1, key);
32
- if (descriptor != null && "value" in descriptor && containsDeferredPromptValuesForContexts(descriptor.value, seen)) return true;
33
- }
34
- return false;
35
- }
36
- function copySanitizedOwnProperties(source, target, seen) {
37
- for (const key of Reflect.ownKeys(source)) {
38
- if (shouldSkipCollectionOwnKey(source, key)) continue;
39
- const descriptor = Object.getOwnPropertyDescriptor(source, key);
40
- if (descriptor == null) continue;
41
- if ("value" in descriptor) descriptor.value = stripDeferredPromptValuesForContexts(descriptor.value, seen);
42
- Object.defineProperty(target, key, descriptor);
43
- }
44
- }
45
- function createArrayCloneLike(value$1) {
46
- try {
47
- const arrayCtor = value$1.constructor;
48
- return Reflect.construct(Array, [value$1.length], arrayCtor);
49
- } catch {
50
- return new Array(value$1.length);
35
+ function prepareParsedForContexts(parsed, deferred, deferredKeys) {
36
+ if (!deferred) return parsed;
37
+ if (deferredKeys != null && deferredKeys.size === 0 && parsed != null && typeof parsed === "object") return void 0;
38
+ if (deferredKeys != null && deferredKeys.size > 0 && parsed != null && typeof parsed === "object" && !isPlainObject(parsed) && !Array.isArray(parsed)) return void 0;
39
+ if (deferredKeys != null && deferredKeys.size > 0 && parsed != null && typeof parsed === "object" && (isPlainObject(parsed) || Array.isArray(parsed))) {
40
+ const getDeferredEntry = (key) => {
41
+ const entry = deferredKeys.get(key);
42
+ if (entry !== void 0) return entry;
43
+ if (typeof key === "string") {
44
+ const num = Number(key);
45
+ if (Number.isInteger(num)) return deferredKeys.get(num);
46
+ } else if (typeof key === "number") return deferredKeys.get(String(key));
47
+ return void 0;
48
+ };
49
+ const ownKeys = Reflect.ownKeys(parsed);
50
+ let hasMatchingKey = false;
51
+ for (const key of ownKeys) if (getDeferredEntry(key) !== void 0) {
52
+ hasMatchingKey = true;
53
+ break;
54
+ }
55
+ if (hasMatchingKey) {
56
+ const isArray = Array.isArray(parsed);
57
+ let allDeferred = true;
58
+ for (const key of ownKeys) {
59
+ if (isArray && key === "length") continue;
60
+ const desc = Object.getOwnPropertyDescriptor(parsed, key);
61
+ if (desc != null && "value" in desc && getDeferredEntry(key) === void 0) {
62
+ allDeferred = false;
63
+ break;
64
+ }
65
+ }
66
+ if (allDeferred) return void 0;
67
+ const clone = isArray ? new Array(parsed.length) : Object.create(Object.getPrototypeOf(parsed));
68
+ for (const key of ownKeys) {
69
+ const desc = Object.getOwnPropertyDescriptor(parsed, key);
70
+ if (desc == null) continue;
71
+ const entry = getDeferredEntry(key);
72
+ if ("value" in desc && entry !== void 0) if (entry === null) Object.defineProperty(clone, key, {
73
+ ...desc,
74
+ value: void 0
75
+ });
76
+ else Object.defineProperty(clone, key, {
77
+ ...desc,
78
+ value: prepareParsedForContexts(desc.value, true, entry)
79
+ });
80
+ else Object.defineProperty(clone, key, desc);
81
+ }
82
+ return clone;
83
+ }
84
+ return void 0;
51
85
  }
86
+ if (parsed == null || typeof parsed !== "object") return void 0;
87
+ return parsed;
52
88
  }
53
- function createSetCloneLike(value$1) {
54
- try {
55
- const setCtor = value$1.constructor;
56
- return Reflect.construct(Set, [], setCtor);
57
- } catch {
58
- return /* @__PURE__ */ new Set();
59
- }
89
+ function isBufferUnchanged(previous, current) {
90
+ return current.length > 0 && current.length === previous.length && current.every((item, i) => item === previous[i]);
60
91
  }
61
- function createMapCloneLike(value$1) {
62
- try {
63
- const mapCtor = value$1.constructor;
64
- return Reflect.construct(Map, [], mapCtor);
65
- } catch {
66
- return /* @__PURE__ */ new Map();
67
- }
92
+ function getFailureProgress(args, buffer, consumedInStep) {
93
+ return {
94
+ remainingArgs: buffer.slice(consumedInStep),
95
+ consumedCount: args.length - buffer.length + consumedInStep
96
+ };
68
97
  }
69
- function containsDeferredPromptValuesForContexts(value$1, seen = /* @__PURE__ */ new WeakSet()) {
70
- if (isDeferredPromptValue(value$1)) return true;
71
- if (value$1 == null || typeof value$1 !== "object") return false;
72
- if (seen.has(value$1)) return false;
73
- seen.add(value$1);
74
- if (Array.isArray(value$1)) {
75
- if (value$1.some((item) => containsDeferredPromptValuesForContexts(item, seen))) return true;
76
- return containsDeferredPromptValuesInOwnProperties(value$1, seen);
77
- }
78
- if (value$1 instanceof Set) {
79
- for (const entryValue of value$1) if (containsDeferredPromptValuesForContexts(entryValue, seen)) return true;
80
- return containsDeferredPromptValuesInOwnProperties(value$1, seen);
81
- }
82
- if (value$1 instanceof Map) {
83
- for (const [key, entryValue] of value$1) if (containsDeferredPromptValuesForContexts(key, seen) || containsDeferredPromptValuesForContexts(entryValue, seen)) return true;
84
- return containsDeferredPromptValuesInOwnProperties(value$1, seen);
85
- }
86
- return containsDeferredPromptValuesInOwnProperties(value$1, seen);
98
+ function createParseExec(parser) {
99
+ return {
100
+ usage: parser.usage,
101
+ phase: "parse",
102
+ path: [],
103
+ commandPath: [],
104
+ trace: createInputTrace()
105
+ };
87
106
  }
88
- function stripDeferredPromptValuesForContexts(value$1, seen = /* @__PURE__ */ new WeakMap()) {
89
- if (isDeferredPromptValue(value$1)) return void 0;
90
- if (value$1 == null || typeof value$1 !== "object") return value$1;
91
- const cached = seen.get(value$1);
92
- if (cached !== void 0) return cached;
93
- if (Array.isArray(value$1)) {
94
- if (!containsDeferredPromptValuesForContexts(value$1)) return value$1;
95
- const clone$1 = createArrayCloneLike(value$1);
96
- seen.set(value$1, clone$1);
97
- for (let i = 0; i < value$1.length; i++) clone$1[i] = stripDeferredPromptValuesForContexts(value$1[i], seen);
98
- copySanitizedOwnProperties(value$1, clone$1, seen);
99
- return clone$1;
100
- }
101
- if (value$1 instanceof Set) {
102
- if (!containsDeferredPromptValuesForContexts(value$1)) return value$1;
103
- const clone$1 = createSetCloneLike(value$1);
104
- seen.set(value$1, clone$1);
105
- for (const entryValue of value$1) clone$1.add(stripDeferredPromptValuesForContexts(entryValue, seen));
106
- copySanitizedOwnProperties(value$1, clone$1, seen);
107
- return clone$1;
108
- }
109
- if (value$1 instanceof Map) {
110
- if (!containsDeferredPromptValuesForContexts(value$1)) return value$1;
111
- const clone$1 = createMapCloneLike(value$1);
112
- seen.set(value$1, clone$1);
113
- for (const [key, entryValue] of value$1) clone$1.set(stripDeferredPromptValuesForContexts(key, seen), stripDeferredPromptValuesForContexts(entryValue, seen));
114
- copySanitizedOwnProperties(value$1, clone$1, seen);
115
- return clone$1;
116
- }
117
- if (!isPlainObject(value$1)) {
118
- if (!containsDeferredPromptValuesForContexts(value$1)) return value$1;
119
- return createSanitizedNonPlainContextView(value$1, seen);
120
- }
121
- const clone = Object.create(Object.getPrototypeOf(value$1));
122
- seen.set(value$1, clone);
123
- for (const key of Reflect.ownKeys(value$1)) {
124
- const descriptor = Object.getOwnPropertyDescriptor(value$1, key);
125
- if (descriptor == null) continue;
126
- if ("value" in descriptor) descriptor.value = stripDeferredPromptValuesForContexts(descriptor.value, seen);
127
- Object.defineProperty(clone, key, descriptor);
128
- }
129
- return clone;
107
+ function getCommandPath(exec) {
108
+ return exec?.commandPath ?? [];
130
109
  }
131
- function finalizeParsedForContext(context, parsed) {
132
- if (parsed !== void 0 || !Reflect.has(context, phase1ConfigAnnotationsKey)) return parsed;
133
- return { [phase2UndefinedParsedValueKey]: true };
110
+ function createCompleteExec(exec, context) {
111
+ const runtime = createDependencyRuntimeContext();
112
+ return {
113
+ ...exec,
114
+ phase: "complete",
115
+ dependencyRuntime: runtime,
116
+ dependencyRegistry: runtime.registry,
117
+ commandPath: getCommandPath(context.exec) ?? exec.commandPath,
118
+ trace: context.exec?.trace ?? context.trace ?? exec.trace
119
+ };
134
120
  }
135
- const SANITIZE_FAILED = Symbol("sanitizeFailed");
136
- const activeSanitizations = /* @__PURE__ */ new WeakMap();
137
- function callWithSanitizedOwnProperties(target, fn, args, strip, seen) {
138
- let active = activeSanitizations.get(target);
139
- if (active != null) active.count++;
140
- else {
141
- const saved = /* @__PURE__ */ new Map();
142
- const sanitizedValues = /* @__PURE__ */ new Map();
143
- for (const key of Reflect.ownKeys(target)) {
144
- const desc = Object.getOwnPropertyDescriptor(target, key);
145
- if (desc != null && "value" in desc) {
146
- let stripped;
147
- try {
148
- stripped = strip(desc.value, seen);
149
- } catch {
150
- for (const [k, d] of saved) try {
151
- Object.defineProperty(target, k, d);
152
- } catch {}
153
- return SANITIZE_FAILED;
154
- }
155
- if (stripped !== desc.value) try {
156
- Object.defineProperty(target, key, {
157
- ...desc,
158
- value: stripped
159
- });
160
- saved.set(key, desc);
161
- sanitizedValues.set(key, stripped);
162
- } catch {
163
- for (const [k, d] of saved) try {
164
- Object.defineProperty(target, k, d);
165
- } catch {}
166
- return SANITIZE_FAILED;
167
- }
168
- }
121
+ function attemptParseSync(parser, args, mode = "complete") {
122
+ const shouldUnwrapAnnotatedValue = isInjectedAnnotationWrapper(parser.initialState);
123
+ const exec = createParseExec(parser);
124
+ let context = createParserContext({
125
+ buffer: args,
126
+ state: parser.initialState,
127
+ optionsTerminated: false
128
+ }, exec);
129
+ do {
130
+ const result = parser.parse(context);
131
+ if (!result.success) {
132
+ const progress = getFailureProgress(args, context.buffer, result.consumed);
133
+ return {
134
+ kind: "failure",
135
+ error: result.error,
136
+ remainingArgs: progress.remainingArgs,
137
+ consumedCount: progress.consumedCount,
138
+ optionsTerminated: context.optionsTerminated,
139
+ commandPath: getCommandPath(context.exec)
140
+ };
169
141
  }
170
- active = {
171
- saved,
172
- sanitizedValues,
173
- count: 1
174
- };
175
- activeSanitizations.set(target, active);
176
- }
177
- function release() {
178
- active.count--;
179
- if (active.count === 0) {
180
- activeSanitizations.delete(target);
181
- for (const [key, desc] of active.saved) try {
182
- const current = Object.getOwnPropertyDescriptor(target, key);
183
- if (current == null) continue;
184
- if ("value" in current && current.value !== active.sanitizedValues.get(key)) continue;
185
- Object.defineProperty(target, key, desc);
186
- } catch {}
142
+ const previousBuffer = context.buffer;
143
+ context = result.next;
144
+ if (isBufferUnchanged(previousBuffer, context.buffer)) {
145
+ const progress = getFailureProgress(args, previousBuffer, result.consumed.length);
146
+ return {
147
+ kind: "failure",
148
+ error: message`Unexpected option or argument: ${context.buffer[0]}.`,
149
+ remainingArgs: progress.remainingArgs,
150
+ consumedCount: progress.consumedCount,
151
+ optionsTerminated: context.optionsTerminated,
152
+ commandPath: getCommandPath(context.exec)
153
+ };
187
154
  }
188
- }
189
- let result;
190
- try {
191
- result = fn.apply(target, args);
192
- } catch (e) {
193
- release();
194
- throw e;
195
- }
196
- if (result instanceof Promise) return result.then((v) => {
197
- release();
198
- return strip(v, seen);
199
- }, (e) => {
200
- release();
201
- throw e;
202
- });
203
- release();
204
- return strip(result, seen);
205
- }
206
- function callMethodOnSanitizedTarget(fn, proxy, target, args, strip, seen) {
207
- const result = callWithSanitizedOwnProperties(target, fn, args, strip, seen);
208
- if (result !== SANITIZE_FAILED) return result;
209
- const fallback = fn.apply(proxy, args);
210
- if (fallback instanceof Promise) return fallback.then((v) => strip(v, seen));
211
- return strip(fallback, seen);
155
+ } while (context.buffer.length > 0);
156
+ if (mode === "parse-only") return {
157
+ kind: "success",
158
+ value: void 0
159
+ };
160
+ const endResult = parser.complete(context.state, createCompleteExec(exec, context));
161
+ if (!endResult.success) return {
162
+ kind: "failure",
163
+ error: endResult.error,
164
+ remainingArgs: [],
165
+ consumedCount: args.length,
166
+ optionsTerminated: context.optionsTerminated,
167
+ commandPath: getCommandPath(context.exec)
168
+ };
169
+ return {
170
+ kind: "success",
171
+ value: shouldUnwrapAnnotatedValue ? unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value
172
+ };
212
173
  }
213
- function createSanitizedNonPlainContextView(value$1, seen) {
214
- const methodCache = /* @__PURE__ */ new Map();
215
- const proxy = new Proxy(value$1, {
216
- get(target, key, receiver) {
217
- const descriptor = Object.getOwnPropertyDescriptor(target, key);
218
- if (descriptor != null && "value" in descriptor) {
219
- if (!descriptor.configurable && !descriptor.writable) return descriptor.value;
220
- const val = stripDeferredPromptValuesForContexts(descriptor.value, seen);
221
- if (typeof val === "function") {
222
- if (!descriptor.configurable && !descriptor.writable || /^class[\s{]/.test(Function.prototype.toString.call(val))) return val;
223
- const cached = methodCache.get(key);
224
- if (cached != null && cached.fn === val) return cached.wrapper;
225
- const wrapper = function(...args) {
226
- if (this !== proxy) return stripDeferredPromptValuesForContexts(val.apply(this, args), seen);
227
- return callMethodOnSanitizedTarget(val, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
228
- };
229
- methodCache.set(key, {
230
- fn: val,
231
- wrapper
232
- });
233
- return wrapper;
234
- }
235
- return val;
236
- }
237
- let isAccessor = false;
238
- for (let proto = target; proto != null; proto = Object.getPrototypeOf(proto)) {
239
- const d = Object.getOwnPropertyDescriptor(proto, key);
240
- if (d != null) {
241
- isAccessor = "get" in d;
242
- break;
243
- }
244
- }
245
- const result = Reflect.get(target, key, receiver);
246
- if (typeof result === "function") {
247
- if (/^class[\s{]/.test(Function.prototype.toString.call(result))) return result;
248
- if (!isAccessor) {
249
- const cached = methodCache.get(key);
250
- if (cached != null && cached.fn === result) return cached.wrapper;
251
- const wrapper = function(...args) {
252
- if (this !== proxy) return stripDeferredPromptValuesForContexts(result.apply(this, args), seen);
253
- return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
254
- };
255
- methodCache.set(key, {
256
- fn: result,
257
- wrapper
258
- });
259
- return wrapper;
260
- }
261
- return function(...args) {
262
- if (this !== proxy) return stripDeferredPromptValuesForContexts(result.apply(this, args), seen);
263
- return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValuesForContexts, seen);
264
- };
265
- }
266
- return stripDeferredPromptValuesForContexts(result, seen);
267
- },
268
- getOwnPropertyDescriptor(target, key) {
269
- const descriptor = Object.getOwnPropertyDescriptor(target, key);
270
- if (descriptor == null || !("value" in descriptor)) return descriptor;
271
- if (!descriptor.configurable && !descriptor.writable) return descriptor;
174
+ async function attemptParseAsync(parser, args, mode = "complete") {
175
+ const shouldUnwrapAnnotatedValue = isInjectedAnnotationWrapper(parser.initialState);
176
+ const exec = createParseExec(parser);
177
+ let context = createParserContext({
178
+ buffer: args,
179
+ state: parser.initialState,
180
+ optionsTerminated: false
181
+ }, exec);
182
+ do {
183
+ const result = await parser.parse(context);
184
+ if (!result.success) {
185
+ const progress = getFailureProgress(args, context.buffer, result.consumed);
272
186
  return {
273
- ...descriptor,
274
- value: stripDeferredPromptValuesForContexts(descriptor.value, seen)
187
+ kind: "failure",
188
+ error: result.error,
189
+ remainingArgs: progress.remainingArgs,
190
+ consumedCount: progress.consumedCount,
191
+ optionsTerminated: context.optionsTerminated,
192
+ commandPath: getCommandPath(context.exec)
275
193
  };
276
194
  }
277
- });
278
- seen.set(value$1, proxy);
279
- return proxy;
195
+ const previousBuffer = context.buffer;
196
+ context = result.next;
197
+ if (isBufferUnchanged(previousBuffer, context.buffer)) {
198
+ const progress = getFailureProgress(args, previousBuffer, result.consumed.length);
199
+ return {
200
+ kind: "failure",
201
+ error: message`Unexpected option or argument: ${context.buffer[0]}.`,
202
+ remainingArgs: progress.remainingArgs,
203
+ consumedCount: progress.consumedCount,
204
+ optionsTerminated: context.optionsTerminated,
205
+ commandPath: getCommandPath(context.exec)
206
+ };
207
+ }
208
+ } while (context.buffer.length > 0);
209
+ if (mode === "parse-only") return {
210
+ kind: "success",
211
+ value: void 0
212
+ };
213
+ const endResult = await parser.complete(context.state, createCompleteExec(exec, context));
214
+ if (!endResult.success) return {
215
+ kind: "failure",
216
+ error: endResult.error,
217
+ remainingArgs: [],
218
+ consumedCount: args.length,
219
+ optionsTerminated: context.optionsTerminated,
220
+ commandPath: getCommandPath(context.exec)
221
+ };
222
+ return {
223
+ kind: "success",
224
+ value: shouldUnwrapAnnotatedValue ? unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value
225
+ };
280
226
  }
281
- function prepareParsedForContexts(parsed) {
282
- if (parsed == null || typeof parsed !== "object") return stripDeferredPromptValuesForContexts(parsed);
283
- if (isDeferredPromptValue(parsed)) return void 0;
284
- if (Array.isArray(parsed) || isPlainObject(parsed) || parsed instanceof Set || parsed instanceof Map) {
285
- if (!containsDeferredPromptValuesForContexts(parsed)) return parsed;
286
- return stripDeferredPromptValuesForContexts(parsed);
287
- }
288
- if (!containsDeferredPromptValuesForContexts(parsed)) return parsed;
289
- return createSanitizedNonPlainContextView(parsed, /* @__PURE__ */ new WeakMap());
227
+ function createPhase2SeedExec(parser, context) {
228
+ const exec = {
229
+ usage: parser.usage,
230
+ phase: "parse",
231
+ path: [],
232
+ commandPath: [],
233
+ trace: createInputTrace()
234
+ };
235
+ const runtime = createDependencyRuntimeContext();
236
+ return {
237
+ ...exec,
238
+ phase: "complete",
239
+ dependencyRuntime: runtime,
240
+ dependencyRegistry: runtime.registry,
241
+ commandPath: getCommandPath(context.exec),
242
+ trace: context.exec?.trace ?? context.trace ?? exec.trace
243
+ };
244
+ }
245
+ function createPhase2SeedContext(parser, args) {
246
+ const exec = {
247
+ usage: parser.usage,
248
+ phase: "parse",
249
+ path: [],
250
+ commandPath: [],
251
+ trace: createInputTrace()
252
+ };
253
+ return createParserContext({
254
+ buffer: args,
255
+ state: parser.initialState,
256
+ optionsTerminated: false
257
+ }, exec);
258
+ }
259
+ function extractPhase2SeedSync(parser, args) {
260
+ let context = createPhase2SeedContext(parser, args);
261
+ do {
262
+ const result = parser.parse(context);
263
+ if (!result.success) return completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
264
+ const previousBuffer = context.buffer;
265
+ context = result.next;
266
+ if (isBufferUnchanged(previousBuffer, context.buffer)) return completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
267
+ } while (context.buffer.length > 0);
268
+ return completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
290
269
  }
291
- function withPreparedParsedForContext(context, preparedParsed, run) {
292
- return run(finalizeParsedForContext(context, preparedParsed));
270
+ async function extractPhase2SeedAsync(parser, args) {
271
+ let context = createPhase2SeedContext(parser, args);
272
+ do {
273
+ const result = await parser.parse(context);
274
+ if (!result.success) return await completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
275
+ const previousBuffer = context.buffer;
276
+ context = result.next;
277
+ if (isBufferUnchanged(previousBuffer, context.buffer)) return await completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
278
+ } while (context.buffer.length > 0);
279
+ return await completeOrExtractPhase2Seed(parser, context.state, createPhase2SeedExec(parser, context));
293
280
  }
294
281
  /**
295
282
  * Creates help parsers based on the sub-config.
@@ -348,9 +335,6 @@ function createVersionParser(commandConfig, optionConfig) {
348
335
  };
349
336
  }
350
337
  const metaResultBrand = Symbol("@optique/core/facade/meta");
351
- function isMetaParseResult(value$1) {
352
- return typeof value$1 === "object" && value$1 != null && metaResultBrand in value$1;
353
- }
354
338
  /**
355
339
  * Creates completion parsers based on the sub-config.
356
340
  */
@@ -406,11 +390,13 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
406
390
  const effectiveVersionOptionNames = versionOptionNames ?? ["--version"];
407
391
  if (helpParsers.helpOption) {
408
392
  const lenientHelpParser = {
409
- $mode: "sync",
393
+ mode: "sync",
410
394
  $valueType: [],
411
395
  $stateType: [],
412
396
  priority: 200,
413
397
  usage: helpParsers.helpOption.usage,
398
+ leadingNames: helpParsers.helpOption.leadingNames,
399
+ acceptingAnyToken: false,
414
400
  initialState: null,
415
401
  parse(context) {
416
402
  const { buffer, optionsTerminated } = context;
@@ -485,11 +471,13 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
485
471
  }
486
472
  if (versionParsers.versionOption) {
487
473
  const lenientVersionParser = {
488
- $mode: "sync",
474
+ mode: "sync",
489
475
  $valueType: [],
490
476
  $stateType: [],
491
477
  priority: 200,
492
478
  usage: versionParsers.versionOption.usage,
479
+ leadingNames: versionParsers.versionOption.leadingNames,
480
+ acceptingAnyToken: false,
493
481
  initialState: null,
494
482
  parse(context) {
495
483
  const { buffer, optionsTerminated } = context;
@@ -614,54 +602,115 @@ function combineWithHelpVersion(originalParser, helpParsers, versionParsers, com
614
602
  }
615
603
  return combined;
616
604
  }
617
- /**
618
- * Classifies the parsing result into a discriminated union for cleaner
619
- * handling.
620
- */
621
- function classifyResult(result, args, helpOptionNames, helpCommandNames, versionOptionNames, versionCommandNames, completionCommandNames) {
622
- if (!result.success) return {
623
- type: "error",
624
- error: result.error
625
- };
626
- const value$1 = result.value;
627
- if (isMetaParseResult(value$1)) {
628
- const parsedValue = value$1;
629
- const hasVersionOption = versionOptionNames.some((n) => args.includes(n));
630
- const hasVersionCommand = args.length > 0 && versionCommandNames.includes(args[0]);
631
- const hasHelpOption = helpOptionNames.some((n) => args.includes(n));
632
- const hasHelpCommand = args.length > 0 && helpCommandNames.includes(args[0]);
633
- const hasCompletionCommand = args.length > 0 && completionCommandNames.includes(args[0]);
634
- if (hasVersionCommand && hasHelpOption && parsedValue.helpFlag) return {
635
- type: "help",
636
- commands: [args[0]]
605
+ function classifyOptionMeta(scanArgs, fullArgs, helpOptionNames, versionOptionNames, completionOptionNames) {
606
+ let lastHelpVersion;
607
+ for (let i = 0; i < scanArgs.length; i++) {
608
+ const arg = scanArgs[i];
609
+ if (helpOptionNames.includes(arg)) lastHelpVersion = {
610
+ index: i,
611
+ kind: "help"
637
612
  };
638
- if (hasCompletionCommand && hasHelpOption && parsedValue.helpFlag) return {
639
- type: "help",
640
- commands: [args[0]]
613
+ else if (versionOptionNames.includes(arg)) lastHelpVersion = {
614
+ index: i,
615
+ kind: "version"
616
+ };
617
+ const equalsMatch = completionOptionNames.find((name) => arg.startsWith(name + "="));
618
+ if (equalsMatch != null) return {
619
+ lastHelpVersion,
620
+ completion: {
621
+ index: i,
622
+ shell: arg.slice(equalsMatch.length + 1),
623
+ args: fullArgs.slice(i + 1)
624
+ }
641
625
  };
642
- if (parsedValue.help && (hasHelpOption || hasHelpCommand)) {
643
- let commandContext = [];
644
- if (Array.isArray(parsedValue.commands)) commandContext = parsedValue.commands;
645
- else if (typeof parsedValue.commands === "object" && parsedValue.commands != null && "length" in parsedValue.commands) commandContext = parsedValue.commands;
626
+ if (completionOptionNames.includes(arg)) {
627
+ const shell = scanArgs[i + 1] ?? "";
646
628
  return {
647
- type: "help",
648
- commands: commandContext
629
+ lastHelpVersion,
630
+ completion: {
631
+ index: i,
632
+ shell,
633
+ args: shell === "" ? [] : fullArgs.slice(i + 2)
634
+ }
649
635
  };
650
636
  }
651
- if ((hasVersionOption || hasVersionCommand) && (parsedValue.version || parsedValue.versionFlag)) return { type: "version" };
652
- if (parsedValue.completion && parsedValue.completionData) return {
637
+ }
638
+ return { lastHelpVersion };
639
+ }
640
+ function getHelpCommandContext(commandPath, args, helpIndex) {
641
+ const commands = [...commandPath];
642
+ for (let i = 0; i < helpIndex; i++) {
643
+ const arg = args[i];
644
+ if (!arg.startsWith("-")) commands.push(arg);
645
+ }
646
+ return commands;
647
+ }
648
+ function classifyParseFailure(failure, helpOptionNames, helpCommandNames, versionOptionNames, versionCommandNames, completionOptionNames, completionCommandNames) {
649
+ if (failure.remainingArgs.length < 1) return {
650
+ type: "error",
651
+ error: failure.error
652
+ };
653
+ const hasConsumedPrefix = failure.consumedCount > 0;
654
+ const firstArg = failure.remainingArgs[0];
655
+ if (!hasConsumedPrefix && completionCommandNames.includes(firstArg)) {
656
+ const secondArg = failure.remainingArgs[1];
657
+ if (helpOptionNames.includes(secondArg)) return {
658
+ type: "help",
659
+ commands: [firstArg]
660
+ };
661
+ return {
653
662
  type: "completion",
654
- shell: parsedValue.completionData.shell ?? "",
655
- args: parsedValue.completionData.args ?? []
663
+ source: "command",
664
+ shell: secondArg ?? "",
665
+ args: failure.remainingArgs.slice(2)
666
+ };
667
+ }
668
+ const optionArgs = failure.optionsTerminated ? [] : (() => {
669
+ const terminatorIndex = failure.remainingArgs.indexOf("--");
670
+ return terminatorIndex >= 0 ? failure.remainingArgs.slice(0, terminatorIndex) : failure.remainingArgs;
671
+ })();
672
+ const { lastHelpVersion, completion } = classifyOptionMeta(optionArgs, failure.remainingArgs, helpOptionNames, versionOptionNames, completionOptionNames);
673
+ if (!hasConsumedPrefix && versionCommandNames.includes(firstArg)) {
674
+ const secondArg = failure.remainingArgs[1];
675
+ if (helpOptionNames.includes(secondArg)) return {
676
+ type: "help",
677
+ commands: [firstArg]
656
678
  };
679
+ const isCompletionImmediatelyAfter = completion?.index === 1;
680
+ if (secondArg == null || isCompletionImmediatelyAfter || lastHelpVersion?.index === 1 && lastHelpVersion.kind === "version") return { type: "version" };
657
681
  return {
658
- type: "success",
659
- value: "result" in parsedValue ? parsedValue.result : value$1
682
+ type: "error",
683
+ error: failure.error
660
684
  };
661
685
  }
686
+ let commandAction;
687
+ if (!hasConsumedPrefix && helpCommandNames.includes(firstArg)) commandAction = {
688
+ index: 0,
689
+ kind: "help"
690
+ };
691
+ const winner = lastHelpVersion == null ? commandAction : commandAction == null || lastHelpVersion.index >= commandAction.index ? lastHelpVersion : commandAction;
692
+ if (winner?.kind === "help") {
693
+ if (winner === commandAction) return {
694
+ type: "help",
695
+ commands: failure.remainingArgs.slice(1)
696
+ };
697
+ return {
698
+ type: "help",
699
+ commands: getHelpCommandContext(failure.commandPath, failure.remainingArgs, winner.index),
700
+ preferUserCommandDocs: failure.commandPath.length > 0
701
+ };
702
+ }
703
+ if (winner?.kind === "version") return { type: "version" };
704
+ if (completion != null) return {
705
+ type: "completion",
706
+ source: "option",
707
+ shell: completion.shell,
708
+ commandPath: failure.commandPath,
709
+ args: completion.args
710
+ };
662
711
  return {
663
- type: "success",
664
- value: value$1
712
+ type: "error",
713
+ error: failure.error
665
714
  };
666
715
  }
667
716
  /**
@@ -676,7 +725,7 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
676
725
  if (!shellName) {
677
726
  stderr("Error: Missing shell name for completion.\n");
678
727
  if (completionParser) {
679
- const displayName = completionCommandDisplayName ?? "completion";
728
+ const displayName = isOptionMode ? completionOptionDisplayName ?? "--completion" : completionCommandDisplayName ?? "completion";
680
729
  const doc = getDocPage(completionParser, [displayName]);
681
730
  if (doc) stderr(formatDocPage(programName, doc, {
682
731
  colors,
@@ -684,7 +733,11 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
684
733
  sectionOrder
685
734
  }));
686
735
  }
687
- return dispatchByMode(parser.$mode, () => callOnError(1), () => Promise.resolve(callOnError(1)));
736
+ return dispatchByMode(parser.mode, () => {
737
+ const result = callOnError(1);
738
+ if (result instanceof Promise) throw new RunParserError("Synchronous parser returned async result.");
739
+ return result;
740
+ }, async () => callOnError(1));
688
741
  }
689
742
  const shell = availableShells[shellName];
690
743
  if (!shell) {
@@ -697,25 +750,52 @@ function handleCompletion(completionArgs, programName, parser, completionParser,
697
750
  colors,
698
751
  quotes: !colors
699
752
  }));
700
- return dispatchByMode(parser.$mode, () => callOnError(1), () => Promise.resolve(callOnError(1)));
753
+ return dispatchByMode(parser.mode, () => {
754
+ const result = callOnError(1);
755
+ if (result instanceof Promise) throw new RunParserError("Synchronous parser returned async result.");
756
+ return result;
757
+ }, async () => callOnError(1));
701
758
  }
702
759
  if (args.length === 0) {
703
760
  const completionArg = isOptionMode ? completionOptionDisplayName ?? "--completion" : completionCommandDisplayName ?? "completion";
704
761
  const script = shell.generateScript(programName, [completionArg, shellName]);
705
762
  stdout(script);
706
- return dispatchByMode(parser.$mode, () => callOnCompletion(0), () => Promise.resolve(callOnCompletion(0)));
763
+ return dispatchByMode(parser.mode, () => {
764
+ const result = callOnCompletion(0);
765
+ if (result instanceof Promise) throw new RunParserError("Synchronous parser returned async result.");
766
+ return result;
767
+ }, async () => callOnCompletion(0));
707
768
  }
708
- return dispatchByMode(parser.$mode, () => {
769
+ return dispatchByMode(parser.mode, () => {
709
770
  const syncParser = parser;
710
771
  const suggestions = suggest(syncParser, args);
711
772
  for (const chunk of shell.encodeSuggestions(suggestions)) stdout(chunk);
712
- return callOnCompletion(0);
773
+ const result = callOnCompletion(0);
774
+ if (result instanceof Promise) throw new RunParserError("Synchronous parser returned async result.");
775
+ return result;
713
776
  }, async () => {
714
777
  const suggestions = await suggestAsync(parser, args);
715
778
  for (const chunk of shell.encodeSuggestions(suggestions)) stdout(chunk);
716
779
  return callOnCompletion(0);
717
780
  });
718
781
  }
782
+ /**
783
+ * Validates the configured version value.
784
+ *
785
+ * @param value Runtime version value from configuration.
786
+ * @returns The validated version string.
787
+ * @throws {TypeError} If the value is not a string, is empty, or contains
788
+ * ASCII control characters.
789
+ */
790
+ function validateVersionValue(value$1) {
791
+ if (typeof value$1 !== "string") {
792
+ const type = Array.isArray(value$1) ? "array" : typeof value$1;
793
+ throw new TypeError(`Expected version value to be a string, but got ${type}.`);
794
+ }
795
+ if (value$1 === "") throw new TypeError("Version value must not be empty.");
796
+ if (/[\x00-\x1f\x7f]/.test(value$1)) throw new TypeError("Version value must not contain control characters.");
797
+ return value$1;
798
+ }
719
799
  function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsParam) {
720
800
  const isProgram = typeof programNameOrArgs !== "string";
721
801
  let parser;
@@ -743,6 +823,7 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
743
823
  args = argsOrOptions;
744
824
  options = optionsParam ?? {};
745
825
  }
826
+ validateProgramName(programName);
746
827
  const { colors, maxWidth, showDefault, showChoices, sectionOrder, aboveError = "usage", onError = () => {
747
828
  throw new RunParserError("Failed to parse command line arguments.");
748
829
  }, stderr = console.error, stdout = console.log, brief, description, examples, author, bugs, footer } = options;
@@ -752,19 +833,58 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
752
833
  const onHelp = options.help?.onShow ?? (() => ({}));
753
834
  const versionCommandConfig = norm(options.version?.command);
754
835
  const versionOptionConfig = norm(options.version?.option);
755
- const versionValue = options.version?.value ?? "";
836
+ const versionValue = options.version ? validateVersionValue(options.version.value) : void 0;
756
837
  const onVersion = options.version?.onShow ?? (() => ({}));
757
838
  const completionCommandConfig = norm(options.completion?.command);
758
839
  const completionOptionConfig = norm(options.completion?.option);
759
840
  const onCompletion = options.completion?.onShow ?? (() => ({}));
760
841
  const onCompletionResult = (code) => onCompletion(code);
761
842
  const onErrorResult = (code) => onError(code);
843
+ if (helpOptionConfig?.names) validateOptionNames(helpOptionConfig.names, "Help option");
844
+ if (helpCommandConfig?.names) validateCommandNames(helpCommandConfig.names, "Help command");
845
+ if (versionOptionConfig?.names) validateOptionNames(versionOptionConfig.names, "Version option");
846
+ if (versionCommandConfig?.names) validateCommandNames(versionCommandConfig.names, "Version command");
847
+ if (completionOptionConfig?.names) validateOptionNames(completionOptionConfig.names, "Completion option");
848
+ if (completionCommandConfig?.names) validateCommandNames(completionCommandConfig.names, "Completion command");
762
849
  const helpOptionNames = helpOptionConfig?.names ?? ["--help"];
763
850
  const helpCommandNames = helpCommandConfig?.names ?? ["help"];
764
851
  const versionOptionNames = versionOptionConfig?.names ?? ["--version"];
765
852
  const versionCommandNames = versionCommandConfig?.names ?? ["version"];
766
853
  const completionCommandNames = completionCommandConfig?.names ?? ["completion"];
767
854
  const completionOptionNames = completionOptionConfig?.names ?? ["--completion"];
855
+ const activeMetaEntries = [];
856
+ if (options.help && helpOptionConfig) activeMetaEntries.push([
857
+ "option",
858
+ "help option",
859
+ helpOptionNames
860
+ ]);
861
+ if (options.help && helpCommandConfig) activeMetaEntries.push([
862
+ "command",
863
+ "help command",
864
+ helpCommandNames
865
+ ]);
866
+ if (options.version && versionOptionConfig) activeMetaEntries.push([
867
+ "option",
868
+ "version option",
869
+ versionOptionNames
870
+ ]);
871
+ if (options.version && versionCommandConfig) activeMetaEntries.push([
872
+ "command",
873
+ "version command",
874
+ versionCommandNames
875
+ ]);
876
+ if (options.completion && completionOptionConfig) activeMetaEntries.push([
877
+ "option",
878
+ "completion option",
879
+ completionOptionNames,
880
+ true
881
+ ]);
882
+ if (options.completion && completionCommandConfig) activeMetaEntries.push([
883
+ "command",
884
+ "completion command",
885
+ completionCommandNames
886
+ ]);
887
+ validateMetaNameCollisions(activeMetaEntries);
768
888
  const defaultShells = {
769
889
  bash,
770
890
  fish,
@@ -788,25 +908,6 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
788
908
  completionCommand: null,
789
909
  completionOption: null
790
910
  };
791
- if (options.completion) {
792
- const hasHelpOption = helpOptionConfig ? helpOptionNames.some((n) => args.includes(n)) : false;
793
- 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);
794
- if (completionOptionConfig) for (let i = 0; i < args.length; i++) {
795
- const arg = args[i];
796
- const equalsMatch = completionOptionNames.find((n) => arg.startsWith(n + "="));
797
- if (equalsMatch) {
798
- const shell = arg.slice(equalsMatch.length + 1);
799
- const completionArgs = args.slice(i + 1);
800
- return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletionResult, onErrorResult, availableShells, colors, maxWidth, completionCommandNames[0], completionOptionNames[0], true, sectionOrder);
801
- }
802
- const exactMatch = completionOptionNames.includes(arg);
803
- if (exactMatch) {
804
- const shell = i + 1 < args.length ? args[i + 1] : "";
805
- const completionArgs = i + 1 < args.length ? args.slice(i + 2) : [];
806
- return handleCompletion([shell, ...completionArgs], programName, parser, completionParsers.completionCommand, stdout, stderr, onCompletionResult, onErrorResult, availableShells, colors, maxWidth, completionCommandNames[0], completionOptionNames[0], true, sectionOrder);
807
- }
808
- }
809
- }
810
911
  const augmentedParser = !options.help && !options.version && !options.completion ? parser : combineWithHelpVersion(parser, helpParsers, versionParsers, completionParsers, {
811
912
  helpCommandGroup: helpCommandConfig?.group,
812
913
  helpOptionGroup: helpOptionConfig?.group,
@@ -815,14 +916,17 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
815
916
  completionCommandGroup: completionCommandConfig?.group,
816
917
  completionOptionGroup: completionOptionConfig?.group
817
918
  }, helpOptionConfig ? [...helpOptionNames] : void 0, versionOptionConfig ? [...versionOptionNames] : void 0);
818
- const handleResult = (result) => {
819
- const classified = classifyResult(result, args, helpOptionConfig ? [...helpOptionNames] : [], helpCommandConfig ? [...helpCommandNames] : [], versionOptionConfig ? [...versionOptionNames] : [], versionCommandConfig ? [...versionCommandNames] : [], completionCommandConfig ? [...completionCommandNames] : []);
919
+ const handleResult = (classified) => {
820
920
  switch (classified.type) {
821
921
  case "success": return classified.value;
822
922
  case "version":
823
923
  stdout(versionValue);
824
924
  return onVersion(0);
825
- case "completion": throw new RunParserError("Completion should be handled by early return");
925
+ case "completion": return handleCompletion([
926
+ classified.shell,
927
+ ...classified.commandPath ?? [],
928
+ ...classified.args
929
+ ], programName, parser, classified.source === "command" ? completionParsers.completionCommand : completionParsers.completionOption, stdout, stderr, onCompletionResult, onErrorResult, availableShells, colors, maxWidth, completionCommandNames[0], completionOptionNames[0], classified.source === "option", sectionOrder);
826
930
  case "help": {
827
931
  let helpGeneratorParser;
828
932
  const helpAsCommand = helpCommandConfig != null;
@@ -832,9 +936,9 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
832
936
  const versionAsOption = versionOptionConfig != null;
833
937
  const completionAsOption = completionOptionConfig != null;
834
938
  const requestedCommand = classified.commands[0];
835
- if (requestedCommand != null && completionCommandNames.includes(requestedCommand) && completionAsCommand && completionParsers.completionCommand) helpGeneratorParser = completionParsers.completionCommand;
836
- else if (requestedCommand != null && helpCommandNames.includes(requestedCommand) && helpAsCommand && helpParsers.helpCommand) helpGeneratorParser = helpParsers.helpCommand;
837
- else if (requestedCommand != null && versionCommandNames.includes(requestedCommand) && versionAsCommand && versionParsers.versionCommand) helpGeneratorParser = versionParsers.versionCommand;
939
+ if (requestedCommand != null && !classified.preferUserCommandDocs && completionCommandNames.includes(requestedCommand) && completionAsCommand && completionParsers.completionCommand) helpGeneratorParser = completionParsers.completionCommand;
940
+ else if (requestedCommand != null && !classified.preferUserCommandDocs && helpCommandNames.includes(requestedCommand) && helpAsCommand && helpParsers.helpCommand) helpGeneratorParser = helpParsers.helpCommand;
941
+ else if (requestedCommand != null && !classified.preferUserCommandDocs && versionCommandNames.includes(requestedCommand) && versionAsCommand && versionParsers.versionCommand) helpGeneratorParser = versionParsers.versionCommand;
838
942
  else {
839
943
  const commandParsers = [parser];
840
944
  const groupedMeta = {};
@@ -926,8 +1030,8 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
926
1030
  while (validationResult === "continue") {
927
1031
  const stepResult = helpGeneratorParser.parse(validationContext);
928
1032
  if (stepResult instanceof Promise) {
929
- const asyncValidate = async (result$1) => {
930
- let res = processStep(result$1);
1033
+ const asyncValidate = async (result) => {
1034
+ let res = processStep(result);
931
1035
  while (res === "continue") {
932
1036
  const next = helpGeneratorParser.parse(validationContext);
933
1037
  const resolved = next instanceof Promise ? await next : next;
@@ -991,15 +1095,23 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
991
1095
  default: throw new RunParserError("Unexpected parse result type");
992
1096
  }
993
1097
  };
994
- const parserMode = parser.$mode;
1098
+ const parserMode = parser.mode;
995
1099
  return dispatchByMode(parserMode, () => {
996
- const result = parseSync(augmentedParser, args);
997
- const handled = handleResult(result);
1100
+ const attempted = attemptParseSync(parser, args);
1101
+ const classified = attempted.kind === "success" ? {
1102
+ type: "success",
1103
+ value: attempted.value
1104
+ } : classifyParseFailure(attempted, helpOptionConfig ? [...helpOptionNames] : [], helpCommandConfig ? [...helpCommandNames] : [], versionOptionConfig ? [...versionOptionNames] : [], versionCommandConfig ? [...versionCommandNames] : [], completionOptionConfig ? [...completionOptionNames] : [], completionCommandConfig ? [...completionCommandNames] : []);
1105
+ const handled = handleResult(classified);
998
1106
  if (handled instanceof Promise) throw new RunParserError("Synchronous parser returned async result.");
999
1107
  return handled;
1000
1108
  }, async () => {
1001
- const result = await parseAsync(augmentedParser, args);
1002
- const handled = handleResult(result);
1109
+ const attempted = await attemptParseAsync(parser, args);
1110
+ const classified = attempted.kind === "success" ? {
1111
+ type: "success",
1112
+ value: attempted.value
1113
+ } : classifyParseFailure(attempted, helpOptionConfig ? [...helpOptionNames] : [], helpCommandConfig ? [...helpCommandNames] : [], versionOptionConfig ? [...versionOptionNames] : [], versionCommandConfig ? [...versionCommandNames] : [], completionOptionConfig ? [...completionOptionNames] : [], completionCommandConfig ? [...completionCommandNames] : []);
1114
+ const handled = handleResult(classified);
1003
1115
  return handled instanceof Promise ? await handled : handled;
1004
1116
  });
1005
1117
  }
@@ -1018,9 +1130,12 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
1018
1130
  * @param args The command-line arguments to parse.
1019
1131
  * @param options Configuration options for customizing behavior.
1020
1132
  * @returns The parsed result if successful.
1133
+ * @throws {TypeError} If an async parser is passed at runtime. Use
1134
+ * {@link runParser} or {@link runParserAsync} for async parsers.
1021
1135
  * @since 0.9.0
1022
1136
  */
1023
1137
  function runParserSync(parser, programName, args, options) {
1138
+ if (parser.mode !== "sync") throw new TypeError("Cannot use an async parser with runParserSync(). Use runParser() or runParserAsync() instead.");
1024
1139
  return runParser(parser, programName, args, options);
1025
1140
  }
1026
1141
  /**
@@ -1057,47 +1172,38 @@ var RunParserError = class extends Error {
1057
1172
  function indentLines(text$1, indent) {
1058
1173
  return text$1.split("\n").join("\n" + " ".repeat(indent));
1059
1174
  }
1175
+ function isMetaEarlyExit(classified) {
1176
+ return classified.type === "help" || classified.type === "version" || classified.type === "completion";
1177
+ }
1178
+ function classifyEarlyExitFailure(failure, options) {
1179
+ const norm = (c) => c === true ? {} : c;
1180
+ const helpOptionConfig = norm(options.help?.option);
1181
+ const helpCommandConfig = norm(options.help?.command);
1182
+ const versionOptionConfig = norm(options.version?.option);
1183
+ const versionCommandConfig = norm(options.version?.command);
1184
+ const completionOptionConfig = norm(options.completion?.option);
1185
+ const completionCommandConfig = norm(options.completion?.command);
1186
+ return classifyParseFailure(failure, helpOptionConfig ? [...helpOptionConfig.names ?? ["--help"]] : [], helpCommandConfig ? [...helpCommandConfig.names ?? ["help"]] : [], versionOptionConfig ? [...versionOptionConfig.names ?? ["--version"]] : [], versionCommandConfig ? [...versionCommandConfig.names ?? ["version"]] : [], completionOptionConfig ? [...completionOptionConfig.names ?? ["--completion"]] : [], completionCommandConfig ? [...completionCommandConfig.names ?? ["completion"]] : []);
1187
+ }
1188
+ function shouldProbeEarlyExit(options, needsTwoPhase) {
1189
+ return needsTwoPhase && (options.help != null || options.version != null || options.completion != null);
1190
+ }
1060
1191
  /**
1061
- * Checks if the arguments contain help, version, or completion requests
1062
- * that should be handled immediately without context processing.
1063
- *
1064
- * This enables early exit optimization: when users request help, version,
1065
- * or completion, we skip annotation collection and context processing
1066
- * entirely, delegating directly to runParser().
1067
- *
1068
- * @param args Command-line arguments to check.
1069
- * @param options Run options containing help/version/completion configuration.
1070
- * @returns `true` if early exit should be performed, `false` otherwise.
1192
+ * Checks if the arguments contain parser-visible meta requests that can be
1193
+ * handled without collecting source-context annotations first.
1071
1194
  */
1072
- function needsEarlyExit(args, options) {
1073
- const norm = (c) => c === true ? {} : c;
1074
- if (options.help) {
1075
- const helpOptionConfig = norm(options.help.option);
1076
- const helpCommandConfig = norm(options.help.command);
1077
- const helpOptionNames = helpOptionConfig?.names ?? ["--help"];
1078
- const helpCommandNames = helpCommandConfig?.names ?? ["help"];
1079
- if (helpOptionConfig && helpOptionNames.some((n) => args.includes(n))) return true;
1080
- if (helpCommandConfig && helpCommandNames.includes(args[0])) return true;
1081
- }
1082
- if (options.version) {
1083
- const versionOptionConfig = norm(options.version.option);
1084
- const versionCommandConfig = norm(options.version.command);
1085
- const versionOptionNames = versionOptionConfig?.names ?? ["--version"];
1086
- const versionCommandNames = versionCommandConfig?.names ?? ["version"];
1087
- if (versionOptionConfig && versionOptionNames.some((n) => args.includes(n))) return true;
1088
- if (versionCommandConfig && versionCommandNames.includes(args[0])) return true;
1089
- }
1090
- if (options.completion) {
1091
- const completionCommandConfig = norm(options.completion.command);
1092
- const completionOptionConfig = norm(options.completion.option);
1093
- const completionCommandNames = completionCommandConfig?.names ?? ["completion"];
1094
- const completionOptionNames = completionOptionConfig?.names ?? ["--completion"];
1095
- if (completionCommandConfig && completionCommandNames.includes(args[0])) return true;
1096
- if (completionOptionConfig) {
1097
- for (const arg of args) for (const name of completionOptionNames) if (arg === name || arg.startsWith(name + "=")) return true;
1098
- }
1099
- }
1100
- return false;
1195
+ function needsEarlyExitSync(parser, args, options) {
1196
+ const attempted = attemptParseSync(parser, args, "parse-only");
1197
+ if (attempted.kind === "success") return false;
1198
+ return isMetaEarlyExit(classifyEarlyExitFailure(attempted, options));
1199
+ }
1200
+ /**
1201
+ * Async variant of {@link needsEarlyExitSync}.
1202
+ */
1203
+ async function needsEarlyExitAsync(parser, args, options) {
1204
+ const attempted = await attemptParseAsync(parser, args, "parse-only");
1205
+ if (attempted.kind === "success") return false;
1206
+ return isMetaEarlyExit(classifyEarlyExitFailure(attempted, options));
1101
1207
  }
1102
1208
  /**
1103
1209
  * Merges multiple annotation objects, with earlier contexts having priority.
@@ -1116,56 +1222,73 @@ function mergeAnnotations(annotationsList) {
1116
1222
  }
1117
1223
  return result;
1118
1224
  }
1225
+ function validateContextPhases(contexts) {
1226
+ for (const context of contexts) {
1227
+ const phase = context.phase;
1228
+ if (phase !== "single-pass" && phase !== "two-pass") throw new TypeError(`Context ${String(context.id)} must declare phase as "single-pass" or "two-pass".`);
1229
+ }
1230
+ }
1119
1231
  /**
1120
1232
  * Collects phase 1 annotations from all contexts and determines whether
1121
1233
  * two-phase parsing is needed.
1122
1234
  *
1123
1235
  * @param contexts Source contexts to collect annotations from.
1124
1236
  * @param options Optional context-required options to pass to each context.
1125
- * @returns Promise with merged annotations and dynamic-context hint.
1237
+ * @returns Promise with merged annotations, per-context snapshots, and a
1238
+ * two-phase hint.
1126
1239
  */
1127
1240
  async function collectPhase1Annotations(contexts, options) {
1128
1241
  const annotationsList = [];
1129
- let hasDynamic = false;
1242
+ let snapshots;
1130
1243
  for (const context of contexts) {
1131
- const result = context.getAnnotations(void 0, options);
1132
- hasDynamic ||= needsTwoPhaseContext(context, result);
1244
+ const request = { phase: "phase1" };
1245
+ const result = context.getAnnotations(request, options);
1133
1246
  const annotations = result instanceof Promise ? await result : result;
1134
- const internalAnnotationsGetter = Reflect.get(context, phase1ConfigAnnotationsKey);
1135
- const internalAnnotations = typeof internalAnnotationsGetter === "function" ? internalAnnotationsGetter.call(context, void 0, annotations) : void 0;
1136
- annotationsList.push(internalAnnotations == null ? annotations : mergeAnnotations([annotations, internalAnnotations]));
1247
+ const internalAnnotations = context.getInternalAnnotations?.(request, annotations);
1248
+ const snapshot = internalAnnotations == null ? annotations : mergeAnnotations([annotations, internalAnnotations]);
1249
+ annotationsList.push(snapshot);
1250
+ if (snapshots != null) snapshots.push(snapshot);
1251
+ else if (context.phase === "two-pass") snapshots = [...annotationsList];
1137
1252
  }
1138
1253
  return {
1139
1254
  annotations: mergeAnnotations(annotationsList),
1140
- annotationsList,
1141
- hasDynamic
1255
+ needsTwoPhase: snapshots != null,
1256
+ snapshots: snapshots ?? []
1142
1257
  };
1143
1258
  }
1144
1259
  /**
1145
- * Collects annotations from all contexts.
1260
+ * Collects final annotations from all contexts.
1261
+ *
1262
+ * `single-pass` contexts reuse their phase-1 snapshot. `two-pass` contexts
1263
+ * are recollected with the parsed value and replace their own phase-1
1264
+ * snapshot in the final merge.
1146
1265
  *
1147
1266
  * @param contexts Source contexts to collect annotations from.
1267
+ * @param phase1Snapshots Per-context snapshots collected during phase 1.
1148
1268
  * @param parsed Optional parsed result from a previous parse pass.
1149
1269
  * @param options Optional context-required options to pass to each context.
1150
1270
  * @returns Promise that resolves to merged annotations.
1151
1271
  */
1152
- async function collectAnnotations(contexts, parsed, options) {
1272
+ async function collectFinalAnnotations(contexts, phase1Snapshots, parsed, options, deferred, deferredKeys) {
1153
1273
  const annotationsList = [];
1154
- const preparedParsed = prepareParsedForContexts(parsed);
1155
- for (const context of contexts) {
1156
- const mergedAnnotations = await withPreparedParsedForContext(context, preparedParsed, async (contextParsed) => {
1157
- const result = context.getAnnotations(contextParsed, options);
1158
- const annotations = result instanceof Promise ? await result : result;
1159
- const internalAnnotationsGetter = Reflect.get(context, phase1ConfigAnnotationsKey);
1160
- const internalAnnotations = typeof internalAnnotationsGetter === "function" ? internalAnnotationsGetter.call(context, contextParsed, annotations) : void 0;
1161
- return internalAnnotations == null ? annotations : mergeAnnotations([annotations, internalAnnotations]);
1162
- });
1274
+ const preparedParsed = prepareParsedForContexts(parsed, deferred, deferredKeys);
1275
+ for (let index = 0; index < contexts.length; index++) {
1276
+ const context = contexts[index];
1277
+ if (context.phase === "single-pass") {
1278
+ annotationsList.push(phase1Snapshots[index]);
1279
+ continue;
1280
+ }
1281
+ const request = {
1282
+ phase: "phase2",
1283
+ parsed: preparedParsed
1284
+ };
1285
+ const result = context.getAnnotations(request, options);
1286
+ const annotations = result instanceof Promise ? await result : result;
1287
+ const internalAnnotations = context.getInternalAnnotations?.(request, annotations);
1288
+ const mergedAnnotations = internalAnnotations == null ? annotations : mergeAnnotations([annotations, internalAnnotations]);
1163
1289
  annotationsList.push(mergedAnnotations);
1164
1290
  }
1165
- return {
1166
- annotations: mergeAnnotations(annotationsList),
1167
- annotationsList
1168
- };
1291
+ return { annotations: mergeAnnotations(annotationsList) };
1169
1292
  }
1170
1293
  /**
1171
1294
  * Collects phase 1 annotations from all contexts synchronously and determines
@@ -1173,70 +1296,62 @@ async function collectAnnotations(contexts, parsed, options) {
1173
1296
  *
1174
1297
  * @param contexts Source contexts to collect annotations from.
1175
1298
  * @param options Optional context-required options to pass to each context.
1176
- * @returns Merged annotations with dynamic-context hint.
1177
- * @throws Error if any context returns a Promise.
1299
+ * @returns Merged annotations, per-context snapshots, and a two-phase hint.
1300
+ * @throws {TypeError} If any context returns a Promise.
1178
1301
  */
1179
1302
  function collectPhase1AnnotationsSync(contexts, options) {
1180
1303
  const annotationsList = [];
1181
- let hasDynamic = false;
1304
+ let snapshots;
1182
1305
  for (const context of contexts) {
1183
- const result = context.getAnnotations(void 0, options);
1184
- if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
1185
- hasDynamic ||= needsTwoPhaseContext(context, result);
1186
- const internalAnnotationsGetter = Reflect.get(context, phase1ConfigAnnotationsKey);
1187
- const internalAnnotations = typeof internalAnnotationsGetter === "function" ? internalAnnotationsGetter.call(context, void 0, result) : void 0;
1188
- annotationsList.push(internalAnnotations == null ? result : mergeAnnotations([result, internalAnnotations]));
1306
+ const request = { phase: "phase1" };
1307
+ const result = context.getAnnotations(request, options);
1308
+ if (result instanceof Promise) throw new TypeError(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
1309
+ const internalAnnotations = context.getInternalAnnotations?.(request, result);
1310
+ const snapshot = internalAnnotations == null ? result : mergeAnnotations([result, internalAnnotations]);
1311
+ annotationsList.push(snapshot);
1312
+ if (snapshots != null) snapshots.push(snapshot);
1313
+ else if (context.phase === "two-pass") snapshots = [...annotationsList];
1189
1314
  }
1190
1315
  return {
1191
1316
  annotations: mergeAnnotations(annotationsList),
1192
- annotationsList,
1193
- hasDynamic
1317
+ needsTwoPhase: snapshots != null,
1318
+ snapshots: snapshots ?? []
1194
1319
  };
1195
1320
  }
1196
1321
  /**
1197
- * Determines whether a context requires a second parse pass.
1322
+ * Collects final annotations from all contexts synchronously.
1198
1323
  *
1199
- * Explicit `mode` declarations take precedence over legacy heuristics so
1200
- * static contexts are not forced into two-phase parsing when they return
1201
- * empty annotations or a Promise.
1202
- */
1203
- function needsTwoPhaseContext(context, result) {
1204
- if (context.mode !== void 0) return context.mode === "dynamic";
1205
- if (result instanceof Promise) return true;
1206
- return Object.getOwnPropertySymbols(result).length === 0;
1207
- }
1208
- /**
1209
- * Collects annotations from all contexts synchronously.
1324
+ * `single-pass` contexts reuse their phase-1 snapshot. `two-pass` contexts
1325
+ * are recollected with the parsed value and replace their own phase-1
1326
+ * snapshot in the final merge.
1210
1327
  *
1211
1328
  * @param contexts Source contexts to collect annotations from.
1329
+ * @param phase1Snapshots Per-context snapshots collected during phase 1.
1212
1330
  * @param parsed Optional parsed result from a previous parse pass.
1213
1331
  * @param options Optional context-required options to pass to each context.
1214
1332
  * @returns Merged annotations.
1215
- * @throws Error if any context returns a Promise.
1333
+ * @throws {TypeError} If any context returns a Promise.
1216
1334
  */
1217
- function collectAnnotationsSync(contexts, parsed, options) {
1335
+ function collectFinalAnnotationsSync(contexts, phase1Snapshots, parsed, options, deferred, deferredKeys) {
1218
1336
  const annotationsList = [];
1219
- const preparedParsed = prepareParsedForContexts(parsed);
1220
- for (const context of contexts) {
1221
- const mergedAnnotations = withPreparedParsedForContext(context, preparedParsed, (contextParsed) => {
1222
- const result = context.getAnnotations(contextParsed, options);
1223
- if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
1224
- const internalAnnotationsGetter = Reflect.get(context, phase1ConfigAnnotationsKey);
1225
- const internalAnnotations = typeof internalAnnotationsGetter === "function" ? internalAnnotationsGetter.call(context, contextParsed, result) : void 0;
1226
- return internalAnnotations == null ? result : mergeAnnotations([result, internalAnnotations]);
1227
- });
1337
+ const preparedParsed = prepareParsedForContexts(parsed, deferred, deferredKeys);
1338
+ for (let index = 0; index < contexts.length; index++) {
1339
+ const context = contexts[index];
1340
+ if (context.phase === "single-pass") {
1341
+ annotationsList.push(phase1Snapshots[index]);
1342
+ continue;
1343
+ }
1344
+ const request = {
1345
+ phase: "phase2",
1346
+ parsed: preparedParsed
1347
+ };
1348
+ const result = context.getAnnotations(request, options);
1349
+ if (result instanceof Promise) throw new TypeError(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
1350
+ const internalAnnotations = context.getInternalAnnotations?.(request, result);
1351
+ const mergedAnnotations = internalAnnotations == null ? result : mergeAnnotations([result, internalAnnotations]);
1228
1352
  annotationsList.push(mergedAnnotations);
1229
1353
  }
1230
- return {
1231
- annotations: mergeAnnotations(annotationsList),
1232
- annotationsList
1233
- };
1234
- }
1235
- function mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList) {
1236
- const mergedPerContext = [];
1237
- const length = Math.max(phase1AnnotationsList.length, phase2AnnotationsList.length);
1238
- for (let i = 0; i < length; i++) mergedPerContext.push(mergeAnnotations([phase2AnnotationsList[i] ?? {}, phase1AnnotationsList[i] ?? {}]));
1239
- return mergeAnnotations(mergedPerContext);
1354
+ return { annotations: mergeAnnotations(annotationsList) };
1240
1355
  }
1241
1356
  /**
1242
1357
  * Disposes all contexts that implement `AsyncDisposable` or `Disposable`.
@@ -1276,22 +1391,63 @@ function disposeContextsSync(contexts) {
1276
1391
  if (errors.length > 1) throw new AggregateError(errors, "Failed to dispose one or more source contexts.");
1277
1392
  }
1278
1393
  /**
1394
+ * Body of {@link runWith}, extracted so that the caller can handle
1395
+ * disposal outside a `finally` block (avoiding `no-unsafe-finally` lint).
1396
+ */
1397
+ async function runWithBody(parser, programName, contexts, args, options) {
1398
+ validateContextIds(contexts);
1399
+ validateContextPhases(contexts);
1400
+ const ctxOptions = options.contextOptions;
1401
+ const { annotations: phase1Annotations, needsTwoPhase, snapshots: phase1Snapshots } = await collectPhase1Annotations(contexts, ctxOptions);
1402
+ if (shouldProbeEarlyExit(options, needsTwoPhase)) {
1403
+ const earlyExitParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1404
+ if (await needsEarlyExitAsync(earlyExitParser, args, options)) {
1405
+ if (parser.mode === "async") return runParser(earlyExitParser, programName, args, options);
1406
+ return Promise.resolve(runParser(earlyExitParser, programName, args, options));
1407
+ }
1408
+ }
1409
+ const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1410
+ if (!needsTwoPhase) {
1411
+ if (parser.mode === "async") return runParser(augmentedParser1, programName, args, options);
1412
+ return Promise.resolve(runParser(augmentedParser1, programName, args, options));
1413
+ }
1414
+ const firstPassSeed = await dispatchByMode(parser.mode, () => extractPhase2SeedSync(augmentedParser1, args), () => extractPhase2SeedAsync(augmentedParser1, args));
1415
+ if (firstPassSeed == null) {
1416
+ const fallbackParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1417
+ if (parser.mode === "async") return runParser(fallbackParser, programName, args, options);
1418
+ return Promise.resolve(runParser(fallbackParser, programName, args, options));
1419
+ }
1420
+ const { annotations: finalAnnotations } = await collectFinalAnnotations(contexts, phase1Snapshots, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
1421
+ const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1422
+ if (parser.mode === "async") return runParser(augmentedParser2, programName, args, options);
1423
+ return Promise.resolve(runParser(augmentedParser2, programName, args, options));
1424
+ }
1425
+ /**
1279
1426
  * Runs a parser with multiple source contexts.
1280
1427
  *
1281
- * This function automatically handles static and dynamic contexts with proper
1282
- * priority. Earlier contexts in the array override later ones.
1428
+ * This function automatically handles single-pass and two-pass contexts with
1429
+ * proper priority. Earlier contexts in the array override later ones.
1283
1430
  *
1284
1431
  * The function uses a smart two-phase approach:
1285
1432
  *
1286
- * 1. *Phase 1*: Collect annotations from all contexts (static contexts return
1287
- * their data, dynamic contexts may return empty).
1288
- * 2. *First parse*: Parse with Phase 1 annotations.
1289
- * 3. *Phase 2*: Call `getAnnotations(parsed)` on all contexts with the first
1290
- * parse result.
1291
- * 4. *Second parse*: Parse again with merged annotations from both phases.
1433
+ * 1. *Phase 1*: Collect annotations from all contexts.
1434
+ * 2. *First parse*: Parse with Phase 1 annotations. If that pass finishes
1435
+ * successfully, its value becomes the phase-two input. If the parser
1436
+ * reaches a usable intermediate state but still does not complete
1437
+ * successfully, the runner extracts a best-effort seed from that state
1438
+ * instead.
1439
+ * 3. *Phase 2*: Call `getAnnotations({ phase: "phase2", parsed })` on all
1440
+ * two-pass contexts with the first pass value. Deferred or otherwise
1441
+ * unresolved fields in `parsed` may be `undefined`. Each two-pass
1442
+ * context's phase-two return
1443
+ * value replaces its own phase-one contribution for the final parse, so
1444
+ * returning `{}` clears any annotations that context provided during
1445
+ * phase 1. Single-pass contexts reuse their phase-one snapshot.
1446
+ * 4. *Second parse*: Parse again with the merged phase-two annotations.
1292
1447
  *
1293
- * If all contexts are static (no dynamic contexts), the second parse is skipped
1294
- * for optimization.
1448
+ * If all contexts are single-pass, the second parse is skipped for
1449
+ * optimization. Phase 2 is also skipped when the first pass does not yield
1450
+ * any usable seed at all.
1295
1451
  *
1296
1452
  * @template TParser The parser type.
1297
1453
  * @template THelp Return type when help is shown.
@@ -1301,6 +1457,13 @@ function disposeContextsSync(contexts) {
1301
1457
  * @param contexts Source contexts to use (priority: earlier overrides later).
1302
1458
  * @param options Run options including args, help, version, etc.
1303
1459
  * @returns Promise that resolves to the parsed result.
1460
+ * @throws {TypeError} If two or more contexts share the same
1461
+ * {@link SourceContext.id}.
1462
+ * @throws {TypeError} If any context omits `phase` or declares an invalid
1463
+ * phase value.
1464
+ * @throws {SuppressedError} If the runner throws and a context's disposal
1465
+ * also throws. The original error is available via `.suppressed` and the
1466
+ * disposal error via `.error`.
1304
1467
  * @since 0.10.0
1305
1468
  *
1306
1469
  * @example
@@ -1310,6 +1473,7 @@ function disposeContextsSync(contexts) {
1310
1473
  *
1311
1474
  * const envContext: SourceContext = {
1312
1475
  * id: Symbol.for("@myapp/env"),
1476
+ * phase: "single-pass",
1313
1477
  * getAnnotations() {
1314
1478
  * return { [Symbol.for("@myapp/env")]: process.env };
1315
1479
  * }
@@ -1325,54 +1489,61 @@ function disposeContextsSync(contexts) {
1325
1489
  */
1326
1490
  async function runWith(parser, programName, contexts, options) {
1327
1491
  const args = options?.args ?? [];
1328
- if (needsEarlyExit(args, options)) {
1329
- if (parser.$mode === "async") return runParser(parser, programName, args, options);
1330
- return Promise.resolve(runParser(parser, programName, args, options));
1331
- }
1332
1492
  if (contexts.length === 0) {
1333
- if (parser.$mode === "async") return runParser(parser, programName, args, options);
1493
+ if (parser.mode === "async") return runParser(parser, programName, args, options);
1334
1494
  return Promise.resolve(runParser(parser, programName, args, options));
1335
1495
  }
1496
+ let result;
1497
+ let primaryError;
1498
+ let hasPrimaryError = false;
1499
+ try {
1500
+ result = await runWithBody(parser, programName, contexts, args, options);
1501
+ } catch (error) {
1502
+ hasPrimaryError = true;
1503
+ primaryError = error;
1504
+ }
1336
1505
  try {
1337
- const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts, options);
1338
- if (!needsTwoPhase) {
1339
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1340
- if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1341
- return Promise.resolve(runParser(augmentedParser, programName, args, options));
1342
- }
1343
- const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1344
- let firstPassResult;
1345
- let firstPassFailed = false;
1346
- try {
1347
- if (parser.$mode === "async") firstPassResult = await parseAsync(augmentedParser1, args);
1348
- else firstPassResult = parseSync(augmentedParser1, args);
1349
- if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
1350
- const result = firstPassResult;
1351
- if (result.success) firstPassResult = result.value;
1352
- else firstPassFailed = true;
1353
- }
1354
- } catch {
1355
- firstPassFailed = true;
1356
- }
1357
- if (firstPassFailed) {
1358
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1359
- if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1360
- return Promise.resolve(runParser(augmentedParser, programName, args, options));
1361
- }
1362
- const { annotationsList: phase2AnnotationsList } = await collectAnnotations(contexts, firstPassResult, options);
1363
- const finalAnnotations = mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList);
1364
- const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1365
- if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
1366
- return Promise.resolve(runParser(augmentedParser2, programName, args, options));
1367
- } finally {
1368
1506
  await disposeContexts(contexts);
1507
+ } catch (disposeError) {
1508
+ if (hasPrimaryError) throw new SuppressedErrorCtor(disposeError, primaryError, "An error was suppressed during context disposal.");
1509
+ throw disposeError;
1369
1510
  }
1511
+ if (hasPrimaryError) throw primaryError;
1512
+ return result;
1513
+ }
1514
+ /**
1515
+ * Body of {@link runWithSync}, extracted so that the caller can handle
1516
+ * disposal outside a `finally` block (avoiding `no-unsafe-finally` lint).
1517
+ */
1518
+ function runWithSyncBody(parser, programName, contexts, args, options) {
1519
+ validateContextIds(contexts);
1520
+ validateContextPhases(contexts);
1521
+ const ctxOptions = options.contextOptions;
1522
+ const { annotations: phase1Annotations, needsTwoPhase, snapshots: phase1Snapshots } = collectPhase1AnnotationsSync(contexts, ctxOptions);
1523
+ if (shouldProbeEarlyExit(options, needsTwoPhase)) {
1524
+ const earlyExitParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1525
+ if (needsEarlyExitSync(earlyExitParser, args, options)) return runParser(earlyExitParser, programName, args, options);
1526
+ }
1527
+ const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1528
+ if (!needsTwoPhase) return runParser(augmentedParser1, programName, args, options);
1529
+ const firstPassSeed = extractPhase2SeedSync(augmentedParser1, args);
1530
+ if (firstPassSeed == null) {
1531
+ const fallbackParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1532
+ return runParser(fallbackParser, programName, args, options);
1533
+ }
1534
+ const { annotations: finalAnnotations } = collectFinalAnnotationsSync(contexts, phase1Snapshots, firstPassSeed.value, ctxOptions, firstPassSeed.deferred, firstPassSeed.deferredKeys);
1535
+ const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1536
+ return runParser(augmentedParser2, programName, args, options);
1370
1537
  }
1371
1538
  /**
1372
1539
  * Runs a synchronous parser with multiple source contexts.
1373
1540
  *
1374
1541
  * This is the sync-only variant of {@link runWith}. All contexts must return
1375
- * annotations synchronously (not Promises).
1542
+ * annotations synchronously (not Promises). It uses the same two-phase
1543
+ * best-effort seed extraction as {@link runWith} when two-pass contexts are
1544
+ * present. In two-phase runs, each two-pass context's phase-two return value
1545
+ * replaces that context's phase-one contribution for the final parse, so
1546
+ * returning `{}` clears any annotations that context provided during phase 1.
1376
1547
  *
1377
1548
  * @template TParser The sync parser type.
1378
1549
  * @template THelp Return type when help is shown.
@@ -1382,36 +1553,40 @@ async function runWith(parser, programName, contexts, options) {
1382
1553
  * @param contexts Source contexts to use (priority: earlier overrides later).
1383
1554
  * @param options Run options including args, help, version, etc.
1384
1555
  * @returns The parsed result.
1385
- * @throws Error if any context returns a Promise or if a context's
1556
+ * @throws {TypeError} If an async parser is passed at runtime. Use
1557
+ * {@link runWith} or {@link runWithAsync} for async parsers.
1558
+ * @throws {TypeError} If two or more contexts share the same
1559
+ * {@link SourceContext.id}.
1560
+ * @throws {TypeError} If any context omits `phase` or declares an invalid
1561
+ * phase value.
1562
+ * @throws {TypeError} If any context returns a Promise or if a context's
1386
1563
  * `[Symbol.asyncDispose]` returns a Promise.
1564
+ * @throws {SuppressedError} If the runner throws and a context's disposal
1565
+ * also throws. The original error is available via `.suppressed` and the
1566
+ * disposal error via `.error`.
1387
1567
  * @since 0.10.0
1388
1568
  */
1389
1569
  function runWithSync(parser, programName, contexts, options) {
1570
+ if (parser.mode !== "sync") throw new TypeError("Cannot use an async parser with runWithSync(). Use runWith() or runWithAsync() instead.");
1390
1571
  const args = options?.args ?? [];
1391
- if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
1392
1572
  if (contexts.length === 0) return runParser(parser, programName, args, options);
1573
+ let result;
1574
+ let primaryError;
1575
+ let hasPrimaryError = false;
1576
+ try {
1577
+ result = runWithSyncBody(parser, programName, contexts, args, options);
1578
+ } catch (error) {
1579
+ hasPrimaryError = true;
1580
+ primaryError = error;
1581
+ }
1393
1582
  try {
1394
- const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = collectPhase1AnnotationsSync(contexts, options);
1395
- if (!needsTwoPhase) {
1396
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1397
- return runParser(augmentedParser, programName, args, options);
1398
- }
1399
- const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1400
- let firstPassResult;
1401
- try {
1402
- const result = parseSync(augmentedParser1, args);
1403
- if (result.success) firstPassResult = result.value;
1404
- else return runParser(augmentedParser1, programName, args, options);
1405
- } catch {
1406
- return runParser(augmentedParser1, programName, args, options);
1407
- }
1408
- const { annotationsList: phase2AnnotationsList } = collectAnnotationsSync(contexts, firstPassResult, options);
1409
- const finalAnnotations = mergeTwoPhaseAnnotations(phase1AnnotationsList, phase2AnnotationsList);
1410
- const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1411
- return runParser(augmentedParser2, programName, args, options);
1412
- } finally {
1413
1583
  disposeContextsSync(contexts);
1584
+ } catch (disposeError) {
1585
+ if (hasPrimaryError) throw new SuppressedErrorCtor(disposeError, primaryError, "An error was suppressed during context disposal.");
1586
+ throw disposeError;
1414
1587
  }
1588
+ if (hasPrimaryError) throw primaryError;
1589
+ return result;
1415
1590
  }
1416
1591
  /**
1417
1592
  * Runs any parser asynchronously with multiple source contexts.
@@ -1427,6 +1602,11 @@ function runWithSync(parser, programName, contexts, options) {
1427
1602
  * @param contexts Source contexts to use (priority: earlier overrides later).
1428
1603
  * @param options Run options including args, help, version, etc.
1429
1604
  * @returns Promise that resolves to the parsed result.
1605
+ * @throws {TypeError} If two or more contexts share the same
1606
+ * {@link SourceContext.id}.
1607
+ * @throws {SuppressedError} If the runner throws and a context's disposal
1608
+ * also throws. The original error is available via `.suppressed` and the
1609
+ * disposal error via `.error`.
1430
1610
  * @since 0.10.0
1431
1611
  */
1432
1612
  function runWithAsync(parser, programName, contexts, options) {
@@ -1441,10 +1621,25 @@ function runWithAsync(parser, programName, contexts, options) {
1441
1621
  */
1442
1622
  function injectAnnotationsIntoParser(parser, annotations) {
1443
1623
  const newInitialState = injectAnnotations(parser.initialState, annotations);
1444
- return {
1445
- ...parser,
1446
- initialState: newInitialState
1624
+ const descriptors = { ...Object.getOwnPropertyDescriptors(parser) };
1625
+ const initialState = descriptors.initialState;
1626
+ descriptors.initialState = initialState == null ? {
1627
+ value: newInitialState,
1628
+ writable: true,
1629
+ enumerable: true,
1630
+ configurable: true
1631
+ } : "get" in initialState || "set" in initialState ? {
1632
+ value: newInitialState,
1633
+ writable: true,
1634
+ enumerable: initialState.enumerable ?? true,
1635
+ configurable: initialState.configurable ?? true
1636
+ } : {
1637
+ value: newInitialState,
1638
+ writable: initialState.writable ?? true,
1639
+ enumerable: initialState.enumerable ?? true,
1640
+ configurable: initialState.configurable ?? true
1447
1641
  };
1642
+ return Object.create(Object.getPrototypeOf(parser), descriptors);
1448
1643
  }
1449
1644
 
1450
1645
  //#endregion