@optique/core 1.0.0-dev.429 → 1.0.0-dev.431

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.
@@ -46,6 +46,10 @@ type ParserValuePlaceholder = {
46
46
  * - *Dynamic*: Data depends on parsing results (e.g., config files whose path
47
47
  * is determined by a CLI option)
48
48
  *
49
+ * Contexts may optionally implement `Disposable` or `AsyncDisposable` for
50
+ * cleanup. When present, `runWith()` and `runWithSync()` call the dispose
51
+ * method in a `finally` block after parsing completes.
52
+ *
49
53
  * @template TRequiredOptions Additional options that `runWith()` must provide
50
54
  * when this context is used. Use `void` (the default) for contexts that
51
55
  * don't require extra options. Use {@link ParserValuePlaceholder} in option
@@ -88,7 +92,7 @@ interface SourceContext<TRequiredOptions = void> {
88
92
  * Type-level marker for the required options. Not used at runtime.
89
93
  * @internal
90
94
  */
91
- readonly _requiredOptions?: TRequiredOptions;
95
+ readonly $requiredOptions?: TRequiredOptions;
92
96
  /**
93
97
  * Get annotations to inject into parsing.
94
98
  *
@@ -103,10 +107,24 @@ interface SourceContext<TRequiredOptions = void> {
103
107
  * @param parsed Optional parsed result from a previous parse pass.
104
108
  * Static contexts can ignore this parameter.
105
109
  * Dynamic contexts use this to extract necessary data.
110
+ * @param options Optional context-required options provided by the caller
111
+ * of `runWith()`. These are the options declared via the
112
+ * `TRequiredOptions` type parameter.
106
113
  * @returns Annotations to merge into the parsing session. Can be a Promise
107
114
  * for async operations (e.g., loading config files).
108
115
  */
109
- getAnnotations(parsed?: unknown): Promise<Annotations> | Annotations;
116
+ getAnnotations(parsed?: unknown, options?: unknown): Promise<Annotations> | Annotations;
117
+ /**
118
+ * Optional synchronous cleanup method. Called by `runWith()` and
119
+ * `runWithSync()` in a `finally` block after parsing completes.
120
+ */
121
+ [Symbol.dispose]?(): void;
122
+ /**
123
+ * Optional asynchronous cleanup method. Called by `runWith()` in a
124
+ * `finally` block after parsing completes. Takes precedence over
125
+ * `[Symbol.dispose]` in async runners.
126
+ */
127
+ [Symbol.asyncDispose]?(): void | PromiseLike<void>;
110
128
  }
111
129
  /**
112
130
  * Checks whether a context is static (returns annotations without needing
package/dist/context.d.ts CHANGED
@@ -46,6 +46,10 @@ type ParserValuePlaceholder = {
46
46
  * - *Dynamic*: Data depends on parsing results (e.g., config files whose path
47
47
  * is determined by a CLI option)
48
48
  *
49
+ * Contexts may optionally implement `Disposable` or `AsyncDisposable` for
50
+ * cleanup. When present, `runWith()` and `runWithSync()` call the dispose
51
+ * method in a `finally` block after parsing completes.
52
+ *
49
53
  * @template TRequiredOptions Additional options that `runWith()` must provide
50
54
  * when this context is used. Use `void` (the default) for contexts that
51
55
  * don't require extra options. Use {@link ParserValuePlaceholder} in option
@@ -88,7 +92,7 @@ interface SourceContext<TRequiredOptions = void> {
88
92
  * Type-level marker for the required options. Not used at runtime.
89
93
  * @internal
90
94
  */
91
- readonly _requiredOptions?: TRequiredOptions;
95
+ readonly $requiredOptions?: TRequiredOptions;
92
96
  /**
93
97
  * Get annotations to inject into parsing.
94
98
  *
@@ -103,10 +107,24 @@ interface SourceContext<TRequiredOptions = void> {
103
107
  * @param parsed Optional parsed result from a previous parse pass.
104
108
  * Static contexts can ignore this parameter.
105
109
  * Dynamic contexts use this to extract necessary data.
110
+ * @param options Optional context-required options provided by the caller
111
+ * of `runWith()`. These are the options declared via the
112
+ * `TRequiredOptions` type parameter.
106
113
  * @returns Annotations to merge into the parsing session. Can be a Promise
107
114
  * for async operations (e.g., loading config files).
108
115
  */
109
- getAnnotations(parsed?: unknown): Promise<Annotations> | Annotations;
116
+ getAnnotations(parsed?: unknown, options?: unknown): Promise<Annotations> | Annotations;
117
+ /**
118
+ * Optional synchronous cleanup method. Called by `runWith()` and
119
+ * `runWithSync()` in a `finally` block after parsing completes.
120
+ */
121
+ [Symbol.dispose]?(): void;
122
+ /**
123
+ * Optional asynchronous cleanup method. Called by `runWith()` in a
124
+ * `finally` block after parsing completes. Takes precedence over
125
+ * `[Symbol.dispose]` in async runners.
126
+ */
127
+ [Symbol.asyncDispose]?(): void | PromiseLike<void>;
110
128
  }
111
129
  /**
112
130
  * Checks whether a context is static (returns annotations without needing
package/dist/facade.cjs CHANGED
@@ -820,13 +820,14 @@ function mergeAnnotations(annotationsList) {
820
820
  * two-phase parsing is needed.
821
821
  *
822
822
  * @param contexts Source contexts to collect annotations from.
823
+ * @param options Optional context-required options to pass to each context.
823
824
  * @returns Promise with merged annotations and dynamic-context hint.
824
825
  */
825
- async function collectPhase1Annotations(contexts) {
826
+ async function collectPhase1Annotations(contexts, options) {
826
827
  const annotationsList = [];
827
828
  let hasDynamic = false;
828
829
  for (const context of contexts) {
829
- const result = context.getAnnotations();
830
+ const result = context.getAnnotations(void 0, options);
830
831
  if (result instanceof Promise) {
831
832
  hasDynamic = true;
832
833
  annotationsList.push(await result);
@@ -845,12 +846,13 @@ async function collectPhase1Annotations(contexts) {
845
846
  *
846
847
  * @param contexts Source contexts to collect annotations from.
847
848
  * @param parsed Optional parsed result from a previous parse pass.
849
+ * @param options Optional context-required options to pass to each context.
848
850
  * @returns Promise that resolves to merged annotations.
849
851
  */
850
- async function collectAnnotations(contexts, parsed) {
852
+ async function collectAnnotations(contexts, parsed, options) {
851
853
  const annotationsList = [];
852
854
  for (const context of contexts) {
853
- const result = context.getAnnotations(parsed);
855
+ const result = context.getAnnotations(parsed, options);
854
856
  annotationsList.push(result instanceof Promise ? await result : result);
855
857
  }
856
858
  return mergeAnnotations(annotationsList);
@@ -860,14 +862,15 @@ async function collectAnnotations(contexts, parsed) {
860
862
  * whether two-phase parsing is needed.
861
863
  *
862
864
  * @param contexts Source contexts to collect annotations from.
865
+ * @param options Optional context-required options to pass to each context.
863
866
  * @returns Merged annotations with dynamic-context hint.
864
867
  * @throws Error if any context returns a Promise.
865
868
  */
866
- function collectPhase1AnnotationsSync(contexts) {
869
+ function collectPhase1AnnotationsSync(contexts, options) {
867
870
  const annotationsList = [];
868
871
  let hasDynamic = false;
869
872
  for (const context of contexts) {
870
- const result = context.getAnnotations();
873
+ const result = context.getAnnotations(void 0, options);
871
874
  if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
872
875
  if (Object.getOwnPropertySymbols(result).length === 0) hasDynamic = true;
873
876
  annotationsList.push(result);
@@ -882,19 +885,38 @@ function collectPhase1AnnotationsSync(contexts) {
882
885
  *
883
886
  * @param contexts Source contexts to collect annotations from.
884
887
  * @param parsed Optional parsed result from a previous parse pass.
888
+ * @param options Optional context-required options to pass to each context.
885
889
  * @returns Merged annotations.
886
890
  * @throws Error if any context returns a Promise.
887
891
  */
888
- function collectAnnotationsSync(contexts, parsed) {
892
+ function collectAnnotationsSync(contexts, parsed, options) {
889
893
  const annotationsList = [];
890
894
  for (const context of contexts) {
891
- const result = context.getAnnotations(parsed);
895
+ const result = context.getAnnotations(parsed, options);
892
896
  if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
893
897
  annotationsList.push(result);
894
898
  }
895
899
  return mergeAnnotations(annotationsList);
896
900
  }
897
901
  /**
902
+ * Disposes all contexts that implement `AsyncDisposable` or `Disposable`.
903
+ * Prefers `[Symbol.asyncDispose]` over `[Symbol.dispose]`.
904
+ *
905
+ * @param contexts Source contexts to dispose.
906
+ */
907
+ async function disposeContexts(contexts) {
908
+ for (const context of contexts) if (Symbol.asyncDispose in context && typeof context[Symbol.asyncDispose] === "function") await context[Symbol.asyncDispose]();
909
+ else if (Symbol.dispose in context && typeof context[Symbol.dispose] === "function") context[Symbol.dispose]();
910
+ }
911
+ /**
912
+ * Disposes all contexts that implement `Disposable` synchronously.
913
+ *
914
+ * @param contexts Source contexts to dispose.
915
+ */
916
+ function disposeContextsSync(contexts) {
917
+ for (const context of contexts) if (Symbol.dispose in context && typeof context[Symbol.dispose] === "function") context[Symbol.dispose]();
918
+ }
919
+ /**
898
920
  * Runs a parser with multiple source contexts.
899
921
  *
900
922
  * This function automatically handles static and dynamic contexts with proper
@@ -952,36 +974,40 @@ async function runWith(parser, programName, contexts, options) {
952
974
  if (parser.$mode === "async") return runParser(parser, programName, args, options);
953
975
  return Promise.resolve(runParser(parser, programName, args, options));
954
976
  }
955
- const { annotations: phase1Annotations, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts);
956
- if (!needsTwoPhase) {
957
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
958
- if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
959
- return Promise.resolve(runParser(augmentedParser, programName, args, options));
960
- }
961
- const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
962
- let firstPassResult;
963
- let firstPassFailed = false;
964
977
  try {
965
- if (parser.$mode === "async") firstPassResult = await require_parser.parseAsync(augmentedParser1, args);
966
- else firstPassResult = require_parser.parseSync(augmentedParser1, args);
967
- if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
968
- const result = firstPassResult;
969
- if (result.success) firstPassResult = result.value;
970
- else firstPassFailed = true;
978
+ const { annotations: phase1Annotations, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts, options);
979
+ if (!needsTwoPhase) {
980
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
981
+ if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
982
+ return Promise.resolve(runParser(augmentedParser, programName, args, options));
971
983
  }
972
- } catch {
973
- firstPassFailed = true;
974
- }
975
- if (firstPassFailed) {
976
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
977
- if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
978
- return Promise.resolve(runParser(augmentedParser, programName, args, options));
984
+ const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
985
+ let firstPassResult;
986
+ let firstPassFailed = false;
987
+ try {
988
+ if (parser.$mode === "async") firstPassResult = await require_parser.parseAsync(augmentedParser1, args);
989
+ else firstPassResult = require_parser.parseSync(augmentedParser1, args);
990
+ if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
991
+ const result = firstPassResult;
992
+ if (result.success) firstPassResult = result.value;
993
+ else firstPassFailed = true;
994
+ }
995
+ } catch {
996
+ firstPassFailed = true;
997
+ }
998
+ if (firstPassFailed) {
999
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1000
+ if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1001
+ return Promise.resolve(runParser(augmentedParser, programName, args, options));
1002
+ }
1003
+ const phase2Annotations = await collectAnnotations(contexts, firstPassResult, options);
1004
+ const finalAnnotations = mergeAnnotations([phase1Annotations, phase2Annotations]);
1005
+ const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1006
+ if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
1007
+ return Promise.resolve(runParser(augmentedParser2, programName, args, options));
1008
+ } finally {
1009
+ await disposeContexts(contexts);
979
1010
  }
980
- const phase2Annotations = await collectAnnotations(contexts, firstPassResult);
981
- const finalAnnotations = mergeAnnotations([phase1Annotations, phase2Annotations]);
982
- const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
983
- if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
984
- return Promise.resolve(runParser(augmentedParser2, programName, args, options));
985
1011
  }
986
1012
  /**
987
1013
  * Runs a synchronous parser with multiple source contexts.
@@ -1004,24 +1030,28 @@ function runWithSync(parser, programName, contexts, options) {
1004
1030
  const args = options?.args ?? [];
1005
1031
  if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
1006
1032
  if (contexts.length === 0) return runParser(parser, programName, args, options);
1007
- const { annotations: phase1Annotations, hasDynamic: needsTwoPhase } = collectPhase1AnnotationsSync(contexts);
1008
- if (!needsTwoPhase) {
1009
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1010
- return runParser(augmentedParser, programName, args, options);
1011
- }
1012
- const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1013
- let firstPassResult;
1014
1033
  try {
1015
- const result = require_parser.parseSync(augmentedParser1, args);
1016
- if (result.success) firstPassResult = result.value;
1017
- else return runParser(augmentedParser1, programName, args, options);
1018
- } catch {
1019
- return runParser(augmentedParser1, programName, args, options);
1034
+ const { annotations: phase1Annotations, hasDynamic: needsTwoPhase } = collectPhase1AnnotationsSync(contexts, options);
1035
+ if (!needsTwoPhase) {
1036
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1037
+ return runParser(augmentedParser, programName, args, options);
1038
+ }
1039
+ const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1040
+ let firstPassResult;
1041
+ try {
1042
+ const result = require_parser.parseSync(augmentedParser1, args);
1043
+ if (result.success) firstPassResult = result.value;
1044
+ else return runParser(augmentedParser1, programName, args, options);
1045
+ } catch {
1046
+ return runParser(augmentedParser1, programName, args, options);
1047
+ }
1048
+ const phase2Annotations = collectAnnotationsSync(contexts, firstPassResult, options);
1049
+ const finalAnnotations = mergeAnnotations([phase1Annotations, phase2Annotations]);
1050
+ const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1051
+ return runParser(augmentedParser2, programName, args, options);
1052
+ } finally {
1053
+ disposeContextsSync(contexts);
1020
1054
  }
1021
- const phase2Annotations = collectAnnotationsSync(contexts, firstPassResult);
1022
- const finalAnnotations = mergeAnnotations([phase1Annotations, phase2Annotations]);
1023
- const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1024
- return runParser(augmentedParser2, programName, args, options);
1025
1055
  }
1026
1056
  /**
1027
1057
  * Runs any parser asynchronously with multiple source contexts.
package/dist/facade.js CHANGED
@@ -820,13 +820,14 @@ function mergeAnnotations(annotationsList) {
820
820
  * two-phase parsing is needed.
821
821
  *
822
822
  * @param contexts Source contexts to collect annotations from.
823
+ * @param options Optional context-required options to pass to each context.
823
824
  * @returns Promise with merged annotations and dynamic-context hint.
824
825
  */
825
- async function collectPhase1Annotations(contexts) {
826
+ async function collectPhase1Annotations(contexts, options) {
826
827
  const annotationsList = [];
827
828
  let hasDynamic = false;
828
829
  for (const context of contexts) {
829
- const result = context.getAnnotations();
830
+ const result = context.getAnnotations(void 0, options);
830
831
  if (result instanceof Promise) {
831
832
  hasDynamic = true;
832
833
  annotationsList.push(await result);
@@ -845,12 +846,13 @@ async function collectPhase1Annotations(contexts) {
845
846
  *
846
847
  * @param contexts Source contexts to collect annotations from.
847
848
  * @param parsed Optional parsed result from a previous parse pass.
849
+ * @param options Optional context-required options to pass to each context.
848
850
  * @returns Promise that resolves to merged annotations.
849
851
  */
850
- async function collectAnnotations(contexts, parsed) {
852
+ async function collectAnnotations(contexts, parsed, options) {
851
853
  const annotationsList = [];
852
854
  for (const context of contexts) {
853
- const result = context.getAnnotations(parsed);
855
+ const result = context.getAnnotations(parsed, options);
854
856
  annotationsList.push(result instanceof Promise ? await result : result);
855
857
  }
856
858
  return mergeAnnotations(annotationsList);
@@ -860,14 +862,15 @@ async function collectAnnotations(contexts, parsed) {
860
862
  * whether two-phase parsing is needed.
861
863
  *
862
864
  * @param contexts Source contexts to collect annotations from.
865
+ * @param options Optional context-required options to pass to each context.
863
866
  * @returns Merged annotations with dynamic-context hint.
864
867
  * @throws Error if any context returns a Promise.
865
868
  */
866
- function collectPhase1AnnotationsSync(contexts) {
869
+ function collectPhase1AnnotationsSync(contexts, options) {
867
870
  const annotationsList = [];
868
871
  let hasDynamic = false;
869
872
  for (const context of contexts) {
870
- const result = context.getAnnotations();
873
+ const result = context.getAnnotations(void 0, options);
871
874
  if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
872
875
  if (Object.getOwnPropertySymbols(result).length === 0) hasDynamic = true;
873
876
  annotationsList.push(result);
@@ -882,19 +885,38 @@ function collectPhase1AnnotationsSync(contexts) {
882
885
  *
883
886
  * @param contexts Source contexts to collect annotations from.
884
887
  * @param parsed Optional parsed result from a previous parse pass.
888
+ * @param options Optional context-required options to pass to each context.
885
889
  * @returns Merged annotations.
886
890
  * @throws Error if any context returns a Promise.
887
891
  */
888
- function collectAnnotationsSync(contexts, parsed) {
892
+ function collectAnnotationsSync(contexts, parsed, options) {
889
893
  const annotationsList = [];
890
894
  for (const context of contexts) {
891
- const result = context.getAnnotations(parsed);
895
+ const result = context.getAnnotations(parsed, options);
892
896
  if (result instanceof Promise) throw new Error(`Context ${String(context.id)} returned a Promise in sync mode. Use runWith() or runWithAsync() for async contexts.`);
893
897
  annotationsList.push(result);
894
898
  }
895
899
  return mergeAnnotations(annotationsList);
896
900
  }
897
901
  /**
902
+ * Disposes all contexts that implement `AsyncDisposable` or `Disposable`.
903
+ * Prefers `[Symbol.asyncDispose]` over `[Symbol.dispose]`.
904
+ *
905
+ * @param contexts Source contexts to dispose.
906
+ */
907
+ async function disposeContexts(contexts) {
908
+ for (const context of contexts) if (Symbol.asyncDispose in context && typeof context[Symbol.asyncDispose] === "function") await context[Symbol.asyncDispose]();
909
+ else if (Symbol.dispose in context && typeof context[Symbol.dispose] === "function") context[Symbol.dispose]();
910
+ }
911
+ /**
912
+ * Disposes all contexts that implement `Disposable` synchronously.
913
+ *
914
+ * @param contexts Source contexts to dispose.
915
+ */
916
+ function disposeContextsSync(contexts) {
917
+ for (const context of contexts) if (Symbol.dispose in context && typeof context[Symbol.dispose] === "function") context[Symbol.dispose]();
918
+ }
919
+ /**
898
920
  * Runs a parser with multiple source contexts.
899
921
  *
900
922
  * This function automatically handles static and dynamic contexts with proper
@@ -952,36 +974,40 @@ async function runWith(parser, programName, contexts, options) {
952
974
  if (parser.$mode === "async") return runParser(parser, programName, args, options);
953
975
  return Promise.resolve(runParser(parser, programName, args, options));
954
976
  }
955
- const { annotations: phase1Annotations, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts);
956
- if (!needsTwoPhase) {
957
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
958
- if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
959
- return Promise.resolve(runParser(augmentedParser, programName, args, options));
960
- }
961
- const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
962
- let firstPassResult;
963
- let firstPassFailed = false;
964
977
  try {
965
- if (parser.$mode === "async") firstPassResult = await parseAsync(augmentedParser1, args);
966
- else firstPassResult = parseSync(augmentedParser1, args);
967
- if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
968
- const result = firstPassResult;
969
- if (result.success) firstPassResult = result.value;
970
- else firstPassFailed = true;
978
+ const { annotations: phase1Annotations, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts, options);
979
+ if (!needsTwoPhase) {
980
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
981
+ if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
982
+ return Promise.resolve(runParser(augmentedParser, programName, args, options));
971
983
  }
972
- } catch {
973
- firstPassFailed = true;
974
- }
975
- if (firstPassFailed) {
976
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
977
- if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
978
- return Promise.resolve(runParser(augmentedParser, programName, args, options));
984
+ const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
985
+ let firstPassResult;
986
+ let firstPassFailed = false;
987
+ try {
988
+ if (parser.$mode === "async") firstPassResult = await parseAsync(augmentedParser1, args);
989
+ else firstPassResult = parseSync(augmentedParser1, args);
990
+ if (typeof firstPassResult === "object" && firstPassResult !== null && "success" in firstPassResult) {
991
+ const result = firstPassResult;
992
+ if (result.success) firstPassResult = result.value;
993
+ else firstPassFailed = true;
994
+ }
995
+ } catch {
996
+ firstPassFailed = true;
997
+ }
998
+ if (firstPassFailed) {
999
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1000
+ if (parser.$mode === "async") return runParser(augmentedParser, programName, args, options);
1001
+ return Promise.resolve(runParser(augmentedParser, programName, args, options));
1002
+ }
1003
+ const phase2Annotations = await collectAnnotations(contexts, firstPassResult, options);
1004
+ const finalAnnotations = mergeAnnotations([phase1Annotations, phase2Annotations]);
1005
+ const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1006
+ if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
1007
+ return Promise.resolve(runParser(augmentedParser2, programName, args, options));
1008
+ } finally {
1009
+ await disposeContexts(contexts);
979
1010
  }
980
- const phase2Annotations = await collectAnnotations(contexts, firstPassResult);
981
- const finalAnnotations = mergeAnnotations([phase1Annotations, phase2Annotations]);
982
- const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
983
- if (parser.$mode === "async") return runParser(augmentedParser2, programName, args, options);
984
- return Promise.resolve(runParser(augmentedParser2, programName, args, options));
985
1011
  }
986
1012
  /**
987
1013
  * Runs a synchronous parser with multiple source contexts.
@@ -1004,24 +1030,28 @@ function runWithSync(parser, programName, contexts, options) {
1004
1030
  const args = options?.args ?? [];
1005
1031
  if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
1006
1032
  if (contexts.length === 0) return runParser(parser, programName, args, options);
1007
- const { annotations: phase1Annotations, hasDynamic: needsTwoPhase } = collectPhase1AnnotationsSync(contexts);
1008
- if (!needsTwoPhase) {
1009
- const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1010
- return runParser(augmentedParser, programName, args, options);
1011
- }
1012
- const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1013
- let firstPassResult;
1014
1033
  try {
1015
- const result = parseSync(augmentedParser1, args);
1016
- if (result.success) firstPassResult = result.value;
1017
- else return runParser(augmentedParser1, programName, args, options);
1018
- } catch {
1019
- return runParser(augmentedParser1, programName, args, options);
1034
+ const { annotations: phase1Annotations, hasDynamic: needsTwoPhase } = collectPhase1AnnotationsSync(contexts, options);
1035
+ if (!needsTwoPhase) {
1036
+ const augmentedParser = injectAnnotationsIntoParser(parser, phase1Annotations);
1037
+ return runParser(augmentedParser, programName, args, options);
1038
+ }
1039
+ const augmentedParser1 = injectAnnotationsIntoParser(parser, phase1Annotations);
1040
+ let firstPassResult;
1041
+ try {
1042
+ const result = parseSync(augmentedParser1, args);
1043
+ if (result.success) firstPassResult = result.value;
1044
+ else return runParser(augmentedParser1, programName, args, options);
1045
+ } catch {
1046
+ return runParser(augmentedParser1, programName, args, options);
1047
+ }
1048
+ const phase2Annotations = collectAnnotationsSync(contexts, firstPassResult, options);
1049
+ const finalAnnotations = mergeAnnotations([phase1Annotations, phase2Annotations]);
1050
+ const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1051
+ return runParser(augmentedParser2, programName, args, options);
1052
+ } finally {
1053
+ disposeContextsSync(contexts);
1020
1054
  }
1021
- const phase2Annotations = collectAnnotationsSync(contexts, firstPassResult);
1022
- const finalAnnotations = mergeAnnotations([phase1Annotations, phase2Annotations]);
1023
- const augmentedParser2 = injectAnnotationsIntoParser(parser, finalAnnotations);
1024
- return runParser(augmentedParser2, programName, args, options);
1025
1055
  }
1026
1056
  /**
1027
1057
  * Runs any parser asynchronously with multiple source contexts.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.429+66edc8ed",
3
+ "version": "1.0.0-dev.431+01c1abaf",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",