@optique/discover 1.1.0 → 1.2.0-dev.2167

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.
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnyCommand, AnyStaticCommand, Command, CommandDefinition, CommandMetadata, CommandPath, StaticCommand, defineCommand, isCommand } from "./command-2HtR3-TV.cjs";
1
+ import { AnyCommand, AnyStaticCommand, Command, CommandDefinition, CommandMetadata, CommandPath, StaticCommand, defineCommand, isCommand } from "./command-DSHBTa5c.cjs";
2
2
  import { Mode, Parser } from "@optique/core/parser";
3
3
  import { Message } from "@optique/core/message";
4
4
  import { ProgramMetadata } from "@optique/core/program";
@@ -64,6 +64,16 @@ interface DiscoverCommandsOptions {
64
64
  * @default Runtime-aware extension defaults from {@link getDefaultExtensions}
65
65
  */
66
66
  readonly extensions?: readonly string[];
67
+ /**
68
+ * File name that maps to the containing command path after extension
69
+ * stripping. For example, `stash/index.ts` maps to `stash`, and root
70
+ * `index.ts` maps to the root command. Pass `false` to treat matching files
71
+ * as ordinary command names.
72
+ *
73
+ * @default `"index"`
74
+ * @since 1.2.0
75
+ */
76
+ readonly entryFileName?: string | false;
67
77
  }
68
78
  /**
69
79
  * Runtime hint for {@link getDefaultExtensions}.
@@ -134,6 +144,14 @@ interface RunProgramDiscoveryOptions extends RunProgramBaseOptions {
134
144
  * File suffixes to include during discovery.
135
145
  */
136
146
  readonly extensions?: readonly string[];
147
+ /**
148
+ * File name that maps to the containing command path after extension
149
+ * stripping.
150
+ *
151
+ * @default `"index"`
152
+ * @since 1.2.0
153
+ */
154
+ readonly entryFileName?: string | false;
137
155
  /**
138
156
  * Static commands cannot be used together with `dir`.
139
157
  */
@@ -157,6 +175,10 @@ interface RunProgramStaticOptions extends RunProgramBaseOptions {
157
175
  * File suffixes are only used with file-system discovery.
158
176
  */
159
177
  readonly extensions?: never;
178
+ /**
179
+ * Entry file names are only used with file-system discovery.
180
+ */
181
+ readonly entryFileName?: never;
160
182
  }
161
183
  /**
162
184
  * Options for {@link runProgram}.
@@ -178,8 +200,8 @@ declare function getDefaultExtensions(options?: RuntimeExtensionOptions): readon
178
200
  * @param options Discovery options.
179
201
  * @returns Discovered commands sorted by command path.
180
202
  * @throws {TypeError} If options are invalid, discovery finds no commands,
181
- * command paths conflict, or a module does not default-export a
182
- * command created with `defineCommand()`.
203
+ * command paths are duplicated, or a module does not default-export
204
+ * a command created with `defineCommand()`.
183
205
  * @since 1.1.0
184
206
  */
185
207
  declare function discoverCommands(options: DiscoverCommandsOptions): Promise<readonly DiscoveredCommand[]>;
@@ -189,7 +211,8 @@ declare function discoverCommands(options: DiscoverCommandsOptions): Promise<rea
189
211
  * @param commands Commands to compose.
190
212
  * @param metadata Optional root documentation metadata.
191
213
  * @returns A parser that resolves to an internal command invocation.
192
- * @throws {TypeError} If no commands are provided or command paths conflict.
214
+ * @throws {TypeError} If no commands are provided or command paths are
215
+ * duplicated.
193
216
  * @since 1.1.0
194
217
  */
195
218
  declare function createProgramParser(commands: readonly Pick<DiscoveredCommand, "path" | "command">[], metadata?: ProgramHelpMetadata): Parser<Mode, ProgramInvocation, unknown>;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AnyCommand, AnyStaticCommand, Command, CommandDefinition, CommandMetadata, CommandPath, StaticCommand, defineCommand, isCommand } from "./command-BAVhIzfI.js";
1
+ import { AnyCommand, AnyStaticCommand, Command, CommandDefinition, CommandMetadata, CommandPath, StaticCommand, defineCommand, isCommand } from "./command-DyiVIMUh.js";
2
2
  import { RunOptions } from "@optique/run";
3
3
  import { Mode, Parser } from "@optique/core/parser";
4
4
  import { Message } from "@optique/core/message";
@@ -64,6 +64,16 @@ interface DiscoverCommandsOptions {
64
64
  * @default Runtime-aware extension defaults from {@link getDefaultExtensions}
65
65
  */
66
66
  readonly extensions?: readonly string[];
67
+ /**
68
+ * File name that maps to the containing command path after extension
69
+ * stripping. For example, `stash/index.ts` maps to `stash`, and root
70
+ * `index.ts` maps to the root command. Pass `false` to treat matching files
71
+ * as ordinary command names.
72
+ *
73
+ * @default `"index"`
74
+ * @since 1.2.0
75
+ */
76
+ readonly entryFileName?: string | false;
67
77
  }
68
78
  /**
69
79
  * Runtime hint for {@link getDefaultExtensions}.
@@ -134,6 +144,14 @@ interface RunProgramDiscoveryOptions extends RunProgramBaseOptions {
134
144
  * File suffixes to include during discovery.
135
145
  */
136
146
  readonly extensions?: readonly string[];
147
+ /**
148
+ * File name that maps to the containing command path after extension
149
+ * stripping.
150
+ *
151
+ * @default `"index"`
152
+ * @since 1.2.0
153
+ */
154
+ readonly entryFileName?: string | false;
137
155
  /**
138
156
  * Static commands cannot be used together with `dir`.
139
157
  */
@@ -157,6 +175,10 @@ interface RunProgramStaticOptions extends RunProgramBaseOptions {
157
175
  * File suffixes are only used with file-system discovery.
158
176
  */
159
177
  readonly extensions?: never;
178
+ /**
179
+ * Entry file names are only used with file-system discovery.
180
+ */
181
+ readonly entryFileName?: never;
160
182
  }
161
183
  /**
162
184
  * Options for {@link runProgram}.
@@ -178,8 +200,8 @@ declare function getDefaultExtensions(options?: RuntimeExtensionOptions): readon
178
200
  * @param options Discovery options.
179
201
  * @returns Discovered commands sorted by command path.
180
202
  * @throws {TypeError} If options are invalid, discovery finds no commands,
181
- * command paths conflict, or a module does not default-export a
182
- * command created with `defineCommand()`.
203
+ * command paths are duplicated, or a module does not default-export
204
+ * a command created with `defineCommand()`.
183
205
  * @since 1.1.0
184
206
  */
185
207
  declare function discoverCommands(options: DiscoverCommandsOptions): Promise<readonly DiscoveredCommand[]>;
@@ -189,7 +211,8 @@ declare function discoverCommands(options: DiscoverCommandsOptions): Promise<rea
189
211
  * @param commands Commands to compose.
190
212
  * @param metadata Optional root documentation metadata.
191
213
  * @returns A parser that resolves to an internal command invocation.
192
- * @throws {TypeError} If no commands are provided or command paths conflict.
214
+ * @throws {TypeError} If no commands are provided or command paths are
215
+ * duplicated.
193
216
  * @since 1.1.0
194
217
  */
195
218
  declare function createProgramParser(commands: readonly Pick<DiscoveredCommand, "path" | "command">[], metadata?: ProgramHelpMetadata): Parser<Mode, ProgramInvocation, unknown>;
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
- import { defineCommand, isCommand } from "./command-y_A3hG0g.js";
2
- import { or } from "@optique/core/constructs";
1
+ import { defineCommand, isCommand } from "./command-DO5zgkvS.js";
2
+ import { longestMatch, or } from "@optique/core/constructs";
3
+ import { dispatchByMode, inheritAnnotations, mapModeValue, wrapForMode } from "@optique/core/extension";
3
4
  import { map } from "@optique/core/modifiers";
4
5
  import { command } from "@optique/core/primitives";
6
+ import { mergeHidden } from "@optique/core/usage";
5
7
  import { runAsync } from "@optique/run";
6
8
  import { readdir, realpath, stat } from "node:fs/promises";
7
9
  import { relative, resolve, sep } from "node:path";
@@ -38,37 +40,37 @@ function getDefaultExtensions(options = {}) {
38
40
  * @param options Discovery options.
39
41
  * @returns Discovered commands sorted by command path.
40
42
  * @throws {TypeError} If options are invalid, discovery finds no commands,
41
- * command paths conflict, or a module does not default-export a
42
- * command created with `defineCommand()`.
43
+ * command paths are duplicated, or a module does not default-export
44
+ * a command created with `defineCommand()`.
43
45
  * @since 1.1.0
44
46
  */
45
47
  async function discoverCommands(options) {
46
48
  const dir = pathFromDir(options.dir);
47
49
  const extensions = normalizeExtensions(options.extensions ?? getDefaultExtensions());
50
+ const entryFileName = normalizeEntryFileName(options.entryFileName);
48
51
  const files = await collectCommandFiles(dir, extensions);
49
52
  if (files.length < 1) throw new TypeError(`No command modules found in ${dir}.`);
50
53
  const seen = /* @__PURE__ */ new Map();
51
54
  const discovered = [];
52
55
  for (const filePath of files) {
53
- const path = commandPathFromFile(dir, filePath, extensions);
56
+ const path = commandPathFromFile(dir, filePath, extensions, entryFileName);
54
57
  const key = commandPathKey(path);
55
58
  const previous = seen.get(key);
56
59
  if (previous != null) {
57
- const displayPath = path.join(" ");
60
+ const displayPath = displayCommandPath(path);
58
61
  throw new TypeError(`Duplicate command path "${displayPath}" from ${previous} and ${filePath}.`);
59
62
  }
60
63
  seen.set(key, filePath);
61
64
  const mod = await import(pathToFileURL(filePath).href);
62
65
  const commandDefinition = unwrapCommandExport(mod.default);
63
66
  if (commandDefinition == null) throw new TypeError(`Module ${filePath} default export must be created with defineCommand().`);
64
- if (commandDefinition.path != null && commandPathKey(commandDefinition.path) !== commandPathKey(path)) throw new TypeError(`Module ${filePath} declares command path "${commandDefinition.path.join(" ")}" but file path defines "${path.join(" ")}".`);
67
+ if (commandDefinition.path != null && commandPathKey(commandDefinition.path) !== commandPathKey(path)) throw new TypeError(`Module ${filePath} declares command path "${displayCommandPath(commandDefinition.path)}" but file path defines "${displayCommandPath(path)}".`);
65
68
  discovered.push({
66
69
  path,
67
70
  filePath,
68
71
  command: commandDefinition
69
72
  });
70
73
  }
71
- rejectPathConflicts(discovered);
72
74
  return sortCommands(discovered);
73
75
  }
74
76
  /**
@@ -77,7 +79,8 @@ async function discoverCommands(options) {
77
79
  * @param commands Commands to compose.
78
80
  * @param metadata Optional root documentation metadata.
79
81
  * @returns A parser that resolves to an internal command invocation.
80
- * @throws {TypeError} If no commands are provided or command paths conflict.
82
+ * @throws {TypeError} If no commands are provided or command paths are
83
+ * duplicated.
81
84
  * @since 1.1.0
82
85
  */
83
86
  function createProgramParser(commands, metadata = {}) {
@@ -85,11 +88,7 @@ function createProgramParser(commands, metadata = {}) {
85
88
  const sortedCommands = sortCommands(commands);
86
89
  rejectDuplicatePaths(sortedCommands.map((entry) => ({
87
90
  path: entry.path,
88
- filePath: entry.path.join("/")
89
- })));
90
- rejectPathConflicts(sortedCommands.map((entry) => ({
91
- path: entry.path,
92
- filePath: entry.path.join("/")
91
+ filePath: displayCommandPath(entry.path)
93
92
  })));
94
93
  const rootNode = buildCommandTree(sortedCommands);
95
94
  const parser = buildNodeParser(rootNode);
@@ -109,7 +108,8 @@ async function runProgram(options) {
109
108
  if (isStaticRunProgramOptions(options)) commands = staticCommandsToEntries(options.commands);
110
109
  else commands = await discoverCommands({
111
110
  dir: options.dir,
112
- extensions: options.extensions
111
+ extensions: options.extensions,
112
+ entryFileName: options.entryFileName
113
113
  });
114
114
  const parser = createProgramParser(commands, options.metadata);
115
115
  const invocation = await runAsync(parser, buildRunOptions(options));
@@ -140,6 +140,14 @@ function normalizeExtensions(extensions) {
140
140
  }
141
141
  return normalized.toSorted((a, b) => b.length - a.length || a.localeCompare(b));
142
142
  }
143
+ function normalizeEntryFileName(entryFileName) {
144
+ if (entryFileName === void 0) return "index";
145
+ if (entryFileName === false) return false;
146
+ if (typeof entryFileName !== "string") throw new TypeError(`Command entry file name must be a non-empty file name: ${entryFileName}`);
147
+ const normalized = entryFileName;
148
+ if (normalized.length < 1 || normalized.includes("/") || normalized.includes("\\")) throw new TypeError(`Command entry file name must be a non-empty file name: ${normalized}`);
149
+ return normalized;
150
+ }
143
151
  async function collectCommandFiles(dir, extensions, activeDirs = /* @__PURE__ */ new Set()) {
144
152
  const canonicalDir = await realpath(dir);
145
153
  if (activeDirs.has(canonicalDir)) return [];
@@ -171,29 +179,24 @@ async function getCommandFileEntryType(path, entry) {
171
179
  function isDeclarationFile(fileName) {
172
180
  return /\.d\.[cm]?ts$/.test(fileName);
173
181
  }
174
- function commandPathFromFile(rootDir, filePath, extensions) {
182
+ function commandPathFromFile(rootDir, filePath, extensions, entryFileName) {
175
183
  const matchedExtension = extensions.find((ext) => filePath.endsWith(ext));
176
184
  if (matchedExtension == null) throw new TypeError(`No configured extension matches ${filePath}.`);
177
185
  const withoutExtension = filePath.slice(0, -matchedExtension.length);
178
186
  const relativePath = relative(rootDir, withoutExtension);
179
187
  const path = relativePath.split(sep).filter((segment) => segment.length > 0);
180
- const [first, ...rest] = path;
181
- if (first == null) throw new TypeError(`Command file ${filePath} does not define a path.`);
182
- return [first, ...rest];
188
+ if (path.length < 1) throw new TypeError(`Command file ${filePath} does not define a path.`);
189
+ if (entryFileName !== false && path[path.length - 1] === entryFileName) return path.slice(0, -1);
190
+ return path;
183
191
  }
184
192
  function commandPathKey(path) {
185
193
  return path.join("\0");
186
194
  }
195
+ function displayCommandPath(path) {
196
+ return path.length < 1 ? "<root>" : path.join(" ");
197
+ }
187
198
  function isCommandPath(path) {
188
- return Array.isArray(path) && path.length > 0 && path.every((segment) => typeof segment === "string" && segment.length > 0);
189
- }
190
- function rejectPathConflicts(commands) {
191
- const paths = new Map(commands.map((entry) => [commandPathKey(entry.path), entry]));
192
- for (const entry of commands) for (let i = 1; i < entry.path.length; i++) {
193
- const parent = entry.path.slice(0, i);
194
- const parentEntry = paths.get(commandPathKey(parent));
195
- if (parentEntry != null) throw new TypeError(`Command path "${parent.join(" ")}" conflicts with nested command "${entry.path.join(" ")}".`);
196
- }
199
+ return Array.isArray(path) && path.every((segment) => typeof segment === "string" && segment.length > 0);
197
200
  }
198
201
  function rejectDuplicatePaths(commands) {
199
202
  const seen = /* @__PURE__ */ new Map();
@@ -201,7 +204,7 @@ function rejectDuplicatePaths(commands) {
201
204
  const key = commandPathKey(entry.path);
202
205
  const previous = seen.get(key);
203
206
  if (previous != null) {
204
- const displayPath = entry.path.join(" ");
207
+ const displayPath = displayCommandPath(entry.path);
205
208
  throw new TypeError(`Duplicate command path "${displayPath}" from ${previous} and ${entry.filePath}.`);
206
209
  }
207
210
  seen.set(key, entry.filePath);
@@ -219,7 +222,7 @@ function isStaticRunProgramOptions(options) {
219
222
  function staticCommandsToEntries(commands) {
220
223
  return commands.map((command$1) => {
221
224
  if (!isCommand(command$1)) throw new TypeError("Static command entries must be created with defineCommand().");
222
- if (!isCommandPath(command$1.path)) throw new TypeError("Static command entries must declare a non-empty path.");
225
+ if (!isCommandPath(command$1.path)) throw new TypeError("Static command entries must declare a path.");
223
226
  return {
224
227
  path: command$1.path,
225
228
  command: command$1
@@ -250,41 +253,354 @@ function buildCommandTree(commands) {
250
253
  }
251
254
  return root;
252
255
  }
253
- function buildNodeParser(node) {
256
+ function buildNodeParser(node, inheritedHidden) {
257
+ const childParser = buildChildrenParser(node, inheritedHidden);
258
+ if (childParser != null && node.command != null) return createExecutableNodeParser(childParser, node.command);
259
+ if (childParser != null) return childParser;
260
+ if (node.command != null) return createLeafParser(node.command);
261
+ throw new TypeError("Command tree node must contain a command.");
262
+ }
263
+ function createExecutableNodeParser(childParser, commandDefinition) {
264
+ const leafParser = createLeafParser(commandDefinition, true);
265
+ const branchParsers = [childParser, leafParser];
266
+ const parser = longestMatch(childParser, leafParser);
267
+ const phase2SeedHook = findPhase2SeedHook(parser);
268
+ const executableParser = {
269
+ ...parser,
270
+ $valueType: [],
271
+ $stateType: [],
272
+ initialState: void 0,
273
+ parse(context) {
274
+ const activeState = normalizeExecutableNodeState(context.state);
275
+ if (activeState?.committed === true && activeState.result.success) {
276
+ const branchParser = branchParsers[activeState.branch];
277
+ const result$1 = branchParser.parse(withExecutableNodeChildContext(context, activeState.branch, inheritAnnotations(context.state, activeState.result.next.state), branchParser));
278
+ return mapModeValue(parser.mode, wrapForMode(parser.mode, result$1), (resolved) => wrapBranchParseResult(context, activeState, resolved));
279
+ }
280
+ const result = parser.parse({
281
+ ...context,
282
+ state: toExclusiveState(activeState, context.state)
283
+ });
284
+ return mapModeValue(parser.mode, wrapForMode(parser.mode, result), (resolved) => wrapInitialParseResult(context, resolved));
285
+ },
286
+ complete(state, exec) {
287
+ const activeState = normalizeExecutableNodeState(state);
288
+ if (activeState?.result.success === true) return wrapForMode(parser.mode, branchParsers[activeState.branch].complete(inheritAnnotations(state, activeState.result.next.state), withExecutableNodeChildExecPath(exec, activeState.branch)));
289
+ if (activeState == null) return wrapForMode(parser.mode, completeExecutableNodeLeaf(state, exec, leafParser));
290
+ return wrapForMode(parser.mode, parser.complete(toExclusiveState(activeState, state), exec));
291
+ },
292
+ suggest(context, prefix) {
293
+ const activeState = normalizeExecutableNodeState(context.state);
294
+ if (activeState?.committed === true && activeState.result.success) {
295
+ const branchParser = branchParsers[activeState.branch];
296
+ return branchParser.suggest(withExecutableNodeChildContext(context, activeState.branch, inheritAnnotations(context.state, activeState.result.next.state), branchParser), prefix);
297
+ }
298
+ return parser.suggest({
299
+ ...context,
300
+ state: toExclusiveState(activeState, context.state)
301
+ }, prefix);
302
+ },
303
+ getSuggestRuntimeNodes(state, path) {
304
+ const activeState = normalizeExecutableNodeState(state);
305
+ if (activeState == null) {
306
+ const branchPath$1 = [...path, 1];
307
+ const branchState$1 = inheritAnnotations(state, leafParser.initialState);
308
+ return getExecutableNodeBranchSuggestRuntimeNodes(leafParser, branchState$1, branchPath$1);
309
+ }
310
+ if (activeState?.result.success !== true) return parser.getSuggestRuntimeNodes?.(toExclusiveState(activeState, state), path) ?? [];
311
+ const branchParser = branchParsers[activeState.branch];
312
+ const branchPath = [...path, activeState.branch];
313
+ const branchState = inheritAnnotations(state, activeState.result.next.state);
314
+ return getExecutableNodeBranchSuggestRuntimeNodes(branchParser, branchState, branchPath);
315
+ },
316
+ getDocFragments(state, defaultValue) {
317
+ const activeState = state.kind === "available" ? normalizeExecutableNodeState(state.state) : void 0;
318
+ const fragments = parser.getDocFragments(state.kind === "available" ? {
319
+ kind: "available",
320
+ state: toExclusiveState(activeState, state.state)
321
+ } : state, defaultValue);
322
+ if (activeState == null) return withCommandDocMetadata(fragments, commandDefinition.metadata);
323
+ return fragments;
324
+ }
325
+ };
326
+ if (phase2SeedHook != null) Object.defineProperty(executableParser, phase2SeedHook.key, {
327
+ value(state, exec) {
328
+ return extractExecutableNodePhase2Seed(state, exec, leafParser, phase2SeedHook);
329
+ },
330
+ configurable: true,
331
+ enumerable: true
332
+ });
333
+ return executableParser;
334
+ }
335
+ function getExecutableNodeBranchSuggestRuntimeNodes(parser, state, path) {
336
+ return parser.getSuggestRuntimeNodes?.(state, path) ?? (parser.dependencyMetadata?.source != null ? [{
337
+ path,
338
+ parser,
339
+ state
340
+ }] : []);
341
+ }
342
+ const phase2SeedSymbolDescription = "@optique/core/extractPhase2Seed";
343
+ function findPhase2SeedHook(parser) {
344
+ for (const key of Object.getOwnPropertySymbols(parser)) {
345
+ if (key.description !== phase2SeedSymbolDescription) continue;
346
+ const value = Reflect.get(parser, key);
347
+ if (typeof value !== "function") continue;
348
+ return {
349
+ key,
350
+ extract(state, exec) {
351
+ const seed = Reflect.apply(value, parser, [state, exec]);
352
+ return seed;
353
+ }
354
+ };
355
+ }
356
+ return void 0;
357
+ }
358
+ function completeExecutableNodeLeaf(state, exec, leafParser) {
359
+ const result = parseExecutableNodeLeaf(state, exec, leafParser);
360
+ return dispatchByMode(leafParser.mode, () => wrapForMode("sync", completeParsedExecutableNodeLeaf(state, exec, leafParser, wrapForMode("sync", result))), () => Promise.resolve(wrapForMode("async", result)).then((resolved) => wrapForMode("async", completeParsedExecutableNodeLeaf(state, exec, leafParser, resolved))));
361
+ }
362
+ function completeParsedExecutableNodeLeaf(state, exec, leafParser, result) {
363
+ const childExec = withExecutableNodeChildExecPath(exec, 1);
364
+ const nextExec = result.success ? mergeExecutableNodeChildExec(childExec, result.next.exec) : childExec;
365
+ const nextState = result.success ? inheritAnnotations(state, result.next.state) : inheritAnnotations(state, leafParser.initialState);
366
+ return leafParser.complete(nextState, nextExec);
367
+ }
368
+ function extractExecutableNodePhase2Seed(state, exec, leafParser, phase2SeedHook) {
369
+ const activeState = normalizeExecutableNodeState(state);
370
+ if (activeState != null) return phase2SeedHook.extract(toExclusiveState(activeState, state), exec);
371
+ const result = parseExecutableNodeLeaf(state, exec, leafParser);
372
+ return dispatchByMode(leafParser.mode, () => extractParsedExecutableNodePhase2Seed(state, exec, wrapForMode("sync", result), phase2SeedHook), () => Promise.resolve(wrapForMode("async", result)).then((resolved) => extractParsedExecutableNodePhase2Seed(state, exec, resolved, phase2SeedHook)));
373
+ }
374
+ function extractParsedExecutableNodePhase2Seed(state, exec, result, phase2SeedHook) {
375
+ if (!result.success) return phase2SeedHook.extract(toExclusiveState(void 0, state), exec);
376
+ const executableState = inheritAnnotations(state, {
377
+ branch: 1,
378
+ result,
379
+ committed: false
380
+ });
381
+ return phase2SeedHook.extract(toExclusiveState(executableState, state), exec);
382
+ }
383
+ function parseExecutableNodeLeaf(state, exec, leafParser) {
384
+ const childExec = withExecutableNodeChildExecPath(exec, 1);
385
+ const childContext = {
386
+ buffer: [],
387
+ optionsTerminated: false,
388
+ usage: leafParser.usage,
389
+ state: inheritAnnotations(state, leafParser.initialState),
390
+ ...childExec != null ? {
391
+ exec: childExec,
392
+ dependencyRegistry: childExec.dependencyRegistry
393
+ } : {}
394
+ };
395
+ return leafParser.parse(childContext);
396
+ }
397
+ function normalizeExecutableNodeState(state) {
398
+ if (state == null || typeof state !== "object" || !("branch" in state) || !("result" in state)) return void 0;
399
+ const branch = state.branch;
400
+ if (branch !== 0 && branch !== 1) return void 0;
401
+ const result = state.result;
402
+ if (result == null || typeof result !== "object" || typeof result.success !== "boolean") return void 0;
403
+ return inheritAnnotations(state, {
404
+ branch,
405
+ result,
406
+ committed: state.committed === true
407
+ });
408
+ }
409
+ function toExclusiveState(state, sourceState = state) {
410
+ const exclusiveState = state == null ? void 0 : [state.branch, state.result];
411
+ return inheritAnnotations(sourceState, exclusiveState);
412
+ }
413
+ function fromExclusiveState(state) {
414
+ if (!Array.isArray(state) || state.length !== 2 || state[0] !== 0 && state[0] !== 1) return void 0;
415
+ return inheritAnnotations(state, {
416
+ branch: state[0],
417
+ result: state[1],
418
+ committed: isCommittedResult(state[1])
419
+ });
420
+ }
421
+ function isCommittedResult(result) {
422
+ return result != null && typeof result === "object" && result.success === true && Array.isArray(result.consumed) && result.consumed.length > 0;
423
+ }
424
+ function wrapInitialParseResult(_context, result) {
425
+ if (!result.success) return result;
426
+ return {
427
+ success: true,
428
+ consumed: result.consumed,
429
+ provisional: result.provisional,
430
+ next: {
431
+ ...result.next,
432
+ state: fromExclusiveState(result.next.state)
433
+ }
434
+ };
435
+ }
436
+ function wrapBranchParseResult(context, activeState, result) {
437
+ if (!result.success) return result;
438
+ const mergedExec = mergeExecutableNodeChildExec(context.exec, result.next.exec);
439
+ const dependencyRegistry = mergedExec?.dependencyRegistry ?? result.next.dependencyRegistry ?? context.dependencyRegistry;
440
+ const nextState = inheritAnnotations(result.next.state, {
441
+ branch: activeState.branch,
442
+ result,
443
+ committed: activeState.committed || result.consumed.length > 0
444
+ });
445
+ return {
446
+ success: true,
447
+ consumed: result.consumed,
448
+ provisional: result.provisional,
449
+ next: {
450
+ ...context,
451
+ buffer: result.next.buffer,
452
+ optionsTerminated: result.next.optionsTerminated,
453
+ state: inheritAnnotations(context.state, nextState),
454
+ ...mergedExec != null ? {
455
+ exec: mergedExec,
456
+ trace: mergedExec.trace
457
+ } : {},
458
+ ...dependencyRegistry != null ? { dependencyRegistry } : {}
459
+ }
460
+ };
461
+ }
462
+ function withExecutableNodeChildContext(context, branch, state, parser) {
463
+ const exec = withExecutableNodeChildExecPath(context.exec, branch);
464
+ const dependencyRegistry = context.dependencyRegistry ?? exec?.dependencyRegistry;
465
+ return {
466
+ ...context,
467
+ state,
468
+ usage: parser.usage,
469
+ ...exec != null ? {
470
+ exec: dependencyRegistry === exec.dependencyRegistry ? exec : {
471
+ ...exec,
472
+ dependencyRegistry
473
+ },
474
+ dependencyRegistry
475
+ } : {}
476
+ };
477
+ }
478
+ function withExecutableNodeChildExecPath(exec, branch) {
479
+ if (exec == null) return void 0;
480
+ return {
481
+ ...exec,
482
+ path: [...exec.path ?? [], branch]
483
+ };
484
+ }
485
+ function mergeExecutableNodeChildExec(parent, child) {
486
+ if (parent == null) return child;
487
+ if (child == null) return parent;
488
+ return {
489
+ ...parent,
490
+ trace: child.trace ?? parent.trace,
491
+ dependencyRuntime: child.dependencyRuntime ?? parent.dependencyRuntime,
492
+ dependencyRegistry: child.dependencyRegistry ?? parent.dependencyRegistry,
493
+ commandPath: child.commandPath ?? parent.commandPath,
494
+ preCompletedByParser: child.preCompletedByParser ?? parent.preCompletedByParser,
495
+ excludedSourceFields: child.excludedSourceFields ?? parent.excludedSourceFields
496
+ };
497
+ }
498
+ function withCommandDocMetadata(fragments, metadata) {
499
+ if (metadata == null) return fragments;
500
+ return {
501
+ ...fragments,
502
+ brief: fragments.brief ?? metadata.brief,
503
+ description: fragments.description ?? metadata.description,
504
+ footer: fragments.footer ?? metadata.footer
505
+ };
506
+ }
507
+ function buildChildrenParser(node, inheritedHidden) {
254
508
  const parsers = [];
255
509
  for (const [name, child] of node.children) {
256
- const childParser = child.command != null ? createLeafParser(child.command) : buildNodeParser(child);
257
- const metadata = child.command?.metadata;
258
- parsers.push(command(name, childParser, metadata));
510
+ const childHidden = mergeHidden(inheritedHidden, child.command?.metadata?.hidden);
511
+ const childParser = buildNodeParser(child, childHidden);
512
+ if (child.children.size > 0) parsers.push(createNamespaceCommandParser(name, childParser, child.command, inheritedHidden));
513
+ else parsers.push(command(name, childParser, commandMetadataWithInheritedHidden(child.command?.metadata, inheritedHidden)));
259
514
  }
515
+ if (parsers.length < 1) return void 0;
260
516
  if (parsers.length === 1) return parsers[0];
261
517
  return or(...parsers);
262
518
  }
263
- function createLeafParser(commandDefinition) {
264
- return map(commandDefinition.parser, (value) => ({
519
+ function createNamespaceCommandParser(name, childParser, commandDefinition, inheritedHidden) {
520
+ const metadata = commandDefinition?.metadata;
521
+ const parser = command(name, childParser, namespaceCommandMetadata(metadata, inheritedHidden));
522
+ const description = metadata?.brief ?? metadata?.description;
523
+ if (description == null) return parser;
524
+ return {
525
+ ...parser,
526
+ getDocFragments(state, defaultValue) {
527
+ const fragments = parser.getDocFragments(state, defaultValue);
528
+ if (state.kind !== "unavailable" && (state.kind !== "available" || !Object.is(state.state, parser.initialState))) return fragments;
529
+ return withNamespaceListDocDescription(fragments, name, description);
530
+ }
531
+ };
532
+ }
533
+ function withNamespaceListDocDescription(fragments, name, description) {
534
+ return {
535
+ ...fragments,
536
+ fragments: fragments.fragments.map((fragment) => {
537
+ if (fragment.type !== "entry" || fragment.term.type !== "command" || fragment.term.name !== name) return fragment;
538
+ return {
539
+ ...fragment,
540
+ description: fragment.description ?? description
541
+ };
542
+ })
543
+ };
544
+ }
545
+ function namespaceCommandMetadata(metadata, inheritedHidden) {
546
+ const hidden = mergeHidden(inheritedHidden, metadata?.hidden);
547
+ if (metadata?.aliases == null && metadata?.errors == null && hidden == null && metadata?.usageLine == null) return void 0;
548
+ return {
549
+ ...metadata?.aliases != null && { aliases: metadata.aliases },
550
+ ...metadata?.errors != null && { errors: metadata.errors },
551
+ ...hidden != null && { hidden },
552
+ ...metadata?.usageLine != null && { usageLine: metadata.usageLine }
553
+ };
554
+ }
555
+ function commandMetadataWithInheritedHidden(metadata, inheritedHidden) {
556
+ const hidden = mergeHidden(inheritedHidden, metadata?.hidden);
557
+ if (metadata == null) return hidden == null ? void 0 : { hidden };
558
+ if (hidden === metadata.hidden) return metadata;
559
+ return {
560
+ ...metadata,
561
+ ...hidden != null && { hidden }
562
+ };
563
+ }
564
+ function createLeafParser(commandDefinition, includeMetadata = false) {
565
+ const parser = map(commandDefinition.parser, (value) => ({
265
566
  command: commandDefinition,
266
567
  value,
267
568
  handler: commandDefinition.handler
268
569
  }));
570
+ if (!includeMetadata) return parser;
571
+ return {
572
+ ...parser,
573
+ getDocFragments(state, defaultValue) {
574
+ const fragments = parser.getDocFragments(state, defaultValue);
575
+ return withCommandDocMetadata(fragments, commandDefinition.metadata);
576
+ }
577
+ };
269
578
  }
270
579
  function withRootDocs(parser, commands, metadata) {
271
580
  const rootState = parser.initialState;
272
- const rootDocs = () => ({
273
- brief: metadata.brief,
274
- description: metadata.description,
275
- footer: metadata.footer,
276
- fragments: [{
581
+ const rootCommand = commands.find((entry) => entry.path.length < 1);
582
+ const listedCommands = commands.filter((entry) => entry.path.length > 0);
583
+ const commandsByPath = new Map(commands.map((entry) => [commandPathKey(entry.path), entry.command]));
584
+ const rootDocs = () => {
585
+ const fragments = [...rootCommand?.command.parser.getDocFragments({ kind: "unavailable" }).fragments ?? []];
586
+ if (listedCommands.length > 0) fragments.push({
277
587
  type: "section",
278
- entries: commands.map((entry) => ({
588
+ entries: listedCommands.map((entry) => ({
279
589
  term: {
280
590
  type: "command",
281
591
  name: entry.path.join(" "),
282
- hidden: entry.command.metadata?.hidden
592
+ hidden: commandPathHidden(entry.path, commandsByPath)
283
593
  },
284
594
  description: entry.command.metadata?.brief ?? entry.command.metadata?.description
285
595
  }))
286
- }]
287
- });
596
+ });
597
+ return {
598
+ brief: metadata.brief,
599
+ description: metadata.description,
600
+ footer: metadata.footer,
601
+ fragments
602
+ };
603
+ };
288
604
  return {
289
605
  ...parser,
290
606
  getDocFragments(state, defaultValue) {
@@ -293,9 +609,14 @@ function withRootDocs(parser, commands, metadata) {
293
609
  }
294
610
  };
295
611
  }
612
+ function commandPathHidden(path, commandsByPath) {
613
+ let hidden;
614
+ for (let length = 1; length <= path.length; length++) hidden = mergeHidden(hidden, commandsByPath.get(commandPathKey(path.slice(0, length)))?.metadata?.hidden);
615
+ return hidden;
616
+ }
296
617
  function buildRunOptions(options) {
297
618
  const metadata = options.metadata;
298
- const { dir: _dir, commands: _commands, extensions: _extensions, metadata: _metadata, help, version, completion,...rest } = options;
619
+ const { dir: _dir, commands: _commands, extensions: _extensions, entryFileName: _entryFileName, metadata: _metadata, help, version, completion,...rest } = options;
299
620
  const runOptions = {
300
621
  ...rest,
301
622
  contexts: unwrapProgramContexts(rest.contexts),