@optique/discover 1.1.0 → 1.2.0-dev.2169

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.js CHANGED
@@ -1,10 +1,12 @@
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
- import { relative, resolve, sep } from "node:path";
9
+ import { posix, relative, resolve, sep } from "node:path";
8
10
  import process from "node:process";
9
11
  import { fileURLToPath, pathToFileURL } from "node:url";
10
12
 
@@ -38,37 +40,81 @@ 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
- const commandDefinition = unwrapCommandExport(mod.default);
63
- 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(" ")}".`);
65
+ const commandDefinition = commandFromModuleExport(filePath, mod.default);
66
+ validateDeclaredCommandPath(commandDefinition, path, filePath, "file path");
65
67
  discovered.push({
66
68
  path,
67
69
  filePath,
68
70
  command: commandDefinition
69
71
  });
70
72
  }
71
- rejectPathConflicts(discovered);
73
+ return sortCommands(discovered);
74
+ }
75
+ /**
76
+ * Converts a static module map into command entries.
77
+ *
78
+ * This is useful for bundlers and single-file packagers that can statically
79
+ * see module maps, such as `import.meta.glob(..., { eager: true })`, while
80
+ * still deriving command paths from file-like module keys.
81
+ *
82
+ * @param modules Static module map keyed by module path.
83
+ * @param options Module path derivation options.
84
+ * @returns Command entries sorted by command path.
85
+ * @throws {TypeError} If options are invalid, no command modules are found,
86
+ * command paths are duplicated, a module does not default-export a
87
+ * command created with `defineCommand()`, or an explicit command
88
+ * `path` does not match the module-derived path.
89
+ * @since 1.2.0
90
+ */
91
+ function commandsFromModules(modules, options = {}) {
92
+ if (modules == null || typeof modules !== "object") throw new TypeError("commandsFromModules() requires a module map object.");
93
+ const base = normalizeModuleBase(options.base);
94
+ const extensions = normalizeExtensions(options.extensions ?? getDefaultExtensions());
95
+ const entryFileName = normalizeEntryFileName(options.entryFileName);
96
+ const modulePaths = Object.keys(modules).toSorted((a, b) => a.localeCompare(b));
97
+ const seen = /* @__PURE__ */ new Map();
98
+ const discovered = [];
99
+ for (const modulePath of modulePaths) {
100
+ if (isDeclarationFile(posix.basename(modulePath)) || !extensions.some((ext) => modulePath.endsWith(ext))) continue;
101
+ const path = commandPathFromModulePath(base, modulePath, extensions, entryFileName);
102
+ const key = commandPathKey(path);
103
+ const previous = seen.get(key);
104
+ if (previous != null) {
105
+ const displayPath = displayCommandPath(path);
106
+ throw new TypeError(`Duplicate command path "${displayPath}" from ${previous} and ${modulePath}.`);
107
+ }
108
+ seen.set(key, modulePath);
109
+ const commandDefinition = commandFromModuleExport(modulePath, modules[modulePath]);
110
+ validateDeclaredCommandPath(commandDefinition, path, modulePath, "module path");
111
+ discovered.push({
112
+ path,
113
+ modulePath,
114
+ command: commandDefinition
115
+ });
116
+ }
117
+ if (discovered.length < 1) throw new TypeError("No command modules found in module map.");
72
118
  return sortCommands(discovered);
73
119
  }
74
120
  /**
@@ -77,7 +123,8 @@ async function discoverCommands(options) {
77
123
  * @param commands Commands to compose.
78
124
  * @param metadata Optional root documentation metadata.
79
125
  * @returns A parser that resolves to an internal command invocation.
80
- * @throws {TypeError} If no commands are provided or command paths conflict.
126
+ * @throws {TypeError} If no commands are provided or command paths are
127
+ * duplicated.
81
128
  * @since 1.1.0
82
129
  */
83
130
  function createProgramParser(commands, metadata = {}) {
@@ -85,11 +132,7 @@ function createProgramParser(commands, metadata = {}) {
85
132
  const sortedCommands = sortCommands(commands);
86
133
  rejectDuplicatePaths(sortedCommands.map((entry) => ({
87
134
  path: entry.path,
88
- filePath: entry.path.join("/")
89
- })));
90
- rejectPathConflicts(sortedCommands.map((entry) => ({
91
- path: entry.path,
92
- filePath: entry.path.join("/")
135
+ filePath: displayCommandPath(entry.path)
93
136
  })));
94
137
  const rootNode = buildCommandTree(sortedCommands);
95
138
  const parser = buildNodeParser(rootNode);
@@ -109,7 +152,8 @@ async function runProgram(options) {
109
152
  if (isStaticRunProgramOptions(options)) commands = staticCommandsToEntries(options.commands);
110
153
  else commands = await discoverCommands({
111
154
  dir: options.dir,
112
- extensions: options.extensions
155
+ extensions: options.extensions,
156
+ entryFileName: options.entryFileName
113
157
  });
114
158
  const parser = createProgramParser(commands, options.metadata);
115
159
  const invocation = await runAsync(parser, buildRunOptions(options));
@@ -140,6 +184,19 @@ function normalizeExtensions(extensions) {
140
184
  }
141
185
  return normalized.toSorted((a, b) => b.length - a.length || a.localeCompare(b));
142
186
  }
187
+ function normalizeEntryFileName(entryFileName) {
188
+ if (entryFileName === void 0) return "index";
189
+ if (entryFileName === false) return false;
190
+ if (typeof entryFileName !== "string") throw new TypeError(`Command entry file name must be a non-empty file name: ${entryFileName}`);
191
+ const normalized = entryFileName;
192
+ if (normalized.length < 1 || normalized.includes("/") || normalized.includes("\\")) throw new TypeError(`Command entry file name must be a non-empty file name: ${normalized}`);
193
+ return normalized;
194
+ }
195
+ function normalizeModuleBase(base) {
196
+ if (base === void 0) return ".";
197
+ if (typeof base !== "string" || base.length < 1) throw new TypeError(`Module base path must be a non-empty string: ${base}`);
198
+ return normalizeModulePath(base);
199
+ }
143
200
  async function collectCommandFiles(dir, extensions, activeDirs = /* @__PURE__ */ new Set()) {
144
201
  const canonicalDir = await realpath(dir);
145
202
  if (activeDirs.has(canonicalDir)) return [];
@@ -171,29 +228,45 @@ async function getCommandFileEntryType(path, entry) {
171
228
  function isDeclarationFile(fileName) {
172
229
  return /\.d\.[cm]?ts$/.test(fileName);
173
230
  }
174
- function commandPathFromFile(rootDir, filePath, extensions) {
175
- const matchedExtension = extensions.find((ext) => filePath.endsWith(ext));
176
- if (matchedExtension == null) throw new TypeError(`No configured extension matches ${filePath}.`);
177
- const withoutExtension = filePath.slice(0, -matchedExtension.length);
231
+ function commandPathFromFile(rootDir, filePath, extensions, entryFileName) {
232
+ const withoutExtension = stripCommandExtension(filePath, extensions);
178
233
  const relativePath = relative(rootDir, withoutExtension);
179
234
  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];
235
+ return commandPathFromSegments(path, filePath, entryFileName);
236
+ }
237
+ function commandPathFromModulePath(base, modulePath, extensions, entryFileName) {
238
+ const withoutExtension = stripCommandExtension(modulePath, extensions);
239
+ const relativePath = relativeModulePath(base, withoutExtension, modulePath);
240
+ const path = relativePath.split("/").filter((segment) => segment.length > 0);
241
+ return commandPathFromSegments(path, modulePath, entryFileName);
242
+ }
243
+ function stripCommandExtension(path, extensions) {
244
+ const matchedExtension = extensions.find((ext) => path.endsWith(ext));
245
+ if (matchedExtension == null) throw new TypeError(`No configured extension matches ${path}.`);
246
+ return path.slice(0, -matchedExtension.length);
247
+ }
248
+ function relativeModulePath(base, modulePath, originalModulePath) {
249
+ const normalizedPath = normalizeModulePath(modulePath);
250
+ const relativePath = posix.relative(base, normalizedPath);
251
+ if (relativePath.length < 1 || relativePath === ".." || relativePath.startsWith("../") || posix.isAbsolute(relativePath)) throw new TypeError(`Module path ${originalModulePath} is not under base path ${base}.`);
252
+ return relativePath;
253
+ }
254
+ function normalizeModulePath(path) {
255
+ return posix.normalize(path.replaceAll("\\", "/"));
256
+ }
257
+ function commandPathFromSegments(path, source, entryFileName) {
258
+ if (path.length < 1) throw new TypeError(`Command module ${source} does not define a path.`);
259
+ if (entryFileName !== false && path[path.length - 1] === entryFileName) return path.slice(0, -1);
260
+ return path;
183
261
  }
184
262
  function commandPathKey(path) {
185
263
  return path.join("\0");
186
264
  }
265
+ function displayCommandPath(path) {
266
+ return path.length < 1 ? "<root>" : path.join(" ");
267
+ }
187
268
  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
- }
269
+ return Array.isArray(path) && path.every((segment) => typeof segment === "string" && segment.length > 0);
197
270
  }
198
271
  function rejectDuplicatePaths(commands) {
199
272
  const seen = /* @__PURE__ */ new Map();
@@ -201,7 +274,7 @@ function rejectDuplicatePaths(commands) {
201
274
  const key = commandPathKey(entry.path);
202
275
  const previous = seen.get(key);
203
276
  if (previous != null) {
204
- const displayPath = entry.path.join(" ");
277
+ const displayPath = displayCommandPath(entry.path);
205
278
  throw new TypeError(`Duplicate command path "${displayPath}" from ${previous} and ${entry.filePath}.`);
206
279
  }
207
280
  seen.set(key, entry.filePath);
@@ -217,23 +290,38 @@ function isStaticRunProgramOptions(options) {
217
290
  return hasCommands;
218
291
  }
219
292
  function staticCommandsToEntries(commands) {
220
- return commands.map((command$1) => {
221
- 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.");
293
+ return commands.map((entry) => {
294
+ if (isCommandEntry(entry)) return entry;
295
+ if (!isCommand(entry)) throw new TypeError("Static command entries must be created with defineCommand().");
296
+ if (!isCommandPath(entry.path)) throw new TypeError("Static command entries must declare a path.");
223
297
  return {
224
- path: command$1.path,
225
- command: command$1
298
+ path: entry.path,
299
+ command: entry
226
300
  };
227
301
  });
228
302
  }
303
+ function isCommandEntry(value) {
304
+ return value != null && typeof value === "object" && isCommandPath(value.path) && isCommand(value.command);
305
+ }
229
306
  function unwrapCommandExport(value) {
230
- if (isCommand(value)) return value;
231
- if (value != null && typeof value === "object") {
232
- const nestedDefault = value.default;
233
- if (isCommand(nestedDefault)) return nestedDefault;
307
+ let current = value;
308
+ for (let depth = 0; depth < 3; depth++) {
309
+ if (isCommand(current)) return current;
310
+ if (current == null || typeof current !== "object") return void 0;
311
+ const nestedDefault = current.default;
312
+ if (Object.is(nestedDefault, current)) return void 0;
313
+ current = nestedDefault;
234
314
  }
235
315
  return void 0;
236
316
  }
317
+ function commandFromModuleExport(source, value) {
318
+ const commandDefinition = unwrapCommandExport(value);
319
+ if (commandDefinition == null) throw new TypeError(`Module ${source} default export must be created with defineCommand().`);
320
+ return commandDefinition;
321
+ }
322
+ function validateDeclaredCommandPath(commandDefinition, path, source, sourcePathLabel) {
323
+ if (commandDefinition.path != null && commandPathKey(commandDefinition.path) !== commandPathKey(path)) throw new TypeError(`Module ${source} declares command path "${displayCommandPath(commandDefinition.path)}" but ${sourcePathLabel} defines "${displayCommandPath(path)}".`);
324
+ }
237
325
  function buildCommandTree(commands) {
238
326
  const root = { children: /* @__PURE__ */ new Map() };
239
327
  for (const entry of commands) {
@@ -250,41 +338,354 @@ function buildCommandTree(commands) {
250
338
  }
251
339
  return root;
252
340
  }
253
- function buildNodeParser(node) {
341
+ function buildNodeParser(node, inheritedHidden) {
342
+ const childParser = buildChildrenParser(node, inheritedHidden);
343
+ if (childParser != null && node.command != null) return createExecutableNodeParser(childParser, node.command);
344
+ if (childParser != null) return childParser;
345
+ if (node.command != null) return createLeafParser(node.command);
346
+ throw new TypeError("Command tree node must contain a command.");
347
+ }
348
+ function createExecutableNodeParser(childParser, commandDefinition) {
349
+ const leafParser = createLeafParser(commandDefinition, true);
350
+ const branchParsers = [childParser, leafParser];
351
+ const parser = longestMatch(childParser, leafParser);
352
+ const phase2SeedHook = findPhase2SeedHook(parser);
353
+ const executableParser = {
354
+ ...parser,
355
+ $valueType: [],
356
+ $stateType: [],
357
+ initialState: void 0,
358
+ parse(context) {
359
+ const activeState = normalizeExecutableNodeState(context.state);
360
+ if (activeState?.committed === true && activeState.result.success) {
361
+ const branchParser = branchParsers[activeState.branch];
362
+ const result$1 = branchParser.parse(withExecutableNodeChildContext(context, activeState.branch, inheritAnnotations(context.state, activeState.result.next.state), branchParser));
363
+ return mapModeValue(parser.mode, wrapForMode(parser.mode, result$1), (resolved) => wrapBranchParseResult(context, activeState, resolved));
364
+ }
365
+ const result = parser.parse({
366
+ ...context,
367
+ state: toExclusiveState(activeState, context.state)
368
+ });
369
+ return mapModeValue(parser.mode, wrapForMode(parser.mode, result), (resolved) => wrapInitialParseResult(context, resolved));
370
+ },
371
+ complete(state, exec) {
372
+ const activeState = normalizeExecutableNodeState(state);
373
+ if (activeState?.result.success === true) return wrapForMode(parser.mode, branchParsers[activeState.branch].complete(inheritAnnotations(state, activeState.result.next.state), withExecutableNodeChildExecPath(exec, activeState.branch)));
374
+ if (activeState == null) return wrapForMode(parser.mode, completeExecutableNodeLeaf(state, exec, leafParser));
375
+ return wrapForMode(parser.mode, parser.complete(toExclusiveState(activeState, state), exec));
376
+ },
377
+ suggest(context, prefix) {
378
+ const activeState = normalizeExecutableNodeState(context.state);
379
+ if (activeState?.committed === true && activeState.result.success) {
380
+ const branchParser = branchParsers[activeState.branch];
381
+ return branchParser.suggest(withExecutableNodeChildContext(context, activeState.branch, inheritAnnotations(context.state, activeState.result.next.state), branchParser), prefix);
382
+ }
383
+ return parser.suggest({
384
+ ...context,
385
+ state: toExclusiveState(activeState, context.state)
386
+ }, prefix);
387
+ },
388
+ getSuggestRuntimeNodes(state, path) {
389
+ const activeState = normalizeExecutableNodeState(state);
390
+ if (activeState == null) {
391
+ const branchPath$1 = [...path, 1];
392
+ const branchState$1 = inheritAnnotations(state, leafParser.initialState);
393
+ return getExecutableNodeBranchSuggestRuntimeNodes(leafParser, branchState$1, branchPath$1);
394
+ }
395
+ if (activeState?.result.success !== true) return parser.getSuggestRuntimeNodes?.(toExclusiveState(activeState, state), path) ?? [];
396
+ const branchParser = branchParsers[activeState.branch];
397
+ const branchPath = [...path, activeState.branch];
398
+ const branchState = inheritAnnotations(state, activeState.result.next.state);
399
+ return getExecutableNodeBranchSuggestRuntimeNodes(branchParser, branchState, branchPath);
400
+ },
401
+ getDocFragments(state, defaultValue) {
402
+ const activeState = state.kind === "available" ? normalizeExecutableNodeState(state.state) : void 0;
403
+ const fragments = parser.getDocFragments(state.kind === "available" ? {
404
+ kind: "available",
405
+ state: toExclusiveState(activeState, state.state)
406
+ } : state, defaultValue);
407
+ if (activeState == null) return withCommandDocMetadata(fragments, commandDefinition.metadata);
408
+ return fragments;
409
+ }
410
+ };
411
+ if (phase2SeedHook != null) Object.defineProperty(executableParser, phase2SeedHook.key, {
412
+ value(state, exec) {
413
+ return extractExecutableNodePhase2Seed(state, exec, leafParser, phase2SeedHook);
414
+ },
415
+ configurable: true,
416
+ enumerable: true
417
+ });
418
+ return executableParser;
419
+ }
420
+ function getExecutableNodeBranchSuggestRuntimeNodes(parser, state, path) {
421
+ return parser.getSuggestRuntimeNodes?.(state, path) ?? (parser.dependencyMetadata?.source != null ? [{
422
+ path,
423
+ parser,
424
+ state
425
+ }] : []);
426
+ }
427
+ const phase2SeedSymbolDescription = "@optique/core/extractPhase2Seed";
428
+ function findPhase2SeedHook(parser) {
429
+ for (const key of Object.getOwnPropertySymbols(parser)) {
430
+ if (key.description !== phase2SeedSymbolDescription) continue;
431
+ const value = Reflect.get(parser, key);
432
+ if (typeof value !== "function") continue;
433
+ return {
434
+ key,
435
+ extract(state, exec) {
436
+ const seed = Reflect.apply(value, parser, [state, exec]);
437
+ return seed;
438
+ }
439
+ };
440
+ }
441
+ return void 0;
442
+ }
443
+ function completeExecutableNodeLeaf(state, exec, leafParser) {
444
+ const result = parseExecutableNodeLeaf(state, exec, leafParser);
445
+ 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))));
446
+ }
447
+ function completeParsedExecutableNodeLeaf(state, exec, leafParser, result) {
448
+ const childExec = withExecutableNodeChildExecPath(exec, 1);
449
+ const nextExec = result.success ? mergeExecutableNodeChildExec(childExec, result.next.exec) : childExec;
450
+ const nextState = result.success ? inheritAnnotations(state, result.next.state) : inheritAnnotations(state, leafParser.initialState);
451
+ return leafParser.complete(nextState, nextExec);
452
+ }
453
+ function extractExecutableNodePhase2Seed(state, exec, leafParser, phase2SeedHook) {
454
+ const activeState = normalizeExecutableNodeState(state);
455
+ if (activeState != null) return phase2SeedHook.extract(toExclusiveState(activeState, state), exec);
456
+ const result = parseExecutableNodeLeaf(state, exec, leafParser);
457
+ return dispatchByMode(leafParser.mode, () => extractParsedExecutableNodePhase2Seed(state, exec, wrapForMode("sync", result), phase2SeedHook), () => Promise.resolve(wrapForMode("async", result)).then((resolved) => extractParsedExecutableNodePhase2Seed(state, exec, resolved, phase2SeedHook)));
458
+ }
459
+ function extractParsedExecutableNodePhase2Seed(state, exec, result, phase2SeedHook) {
460
+ if (!result.success) return phase2SeedHook.extract(toExclusiveState(void 0, state), exec);
461
+ const executableState = inheritAnnotations(state, {
462
+ branch: 1,
463
+ result,
464
+ committed: false
465
+ });
466
+ return phase2SeedHook.extract(toExclusiveState(executableState, state), exec);
467
+ }
468
+ function parseExecutableNodeLeaf(state, exec, leafParser) {
469
+ const childExec = withExecutableNodeChildExecPath(exec, 1);
470
+ const childContext = {
471
+ buffer: [],
472
+ optionsTerminated: false,
473
+ usage: leafParser.usage,
474
+ state: inheritAnnotations(state, leafParser.initialState),
475
+ ...childExec != null ? {
476
+ exec: childExec,
477
+ dependencyRegistry: childExec.dependencyRegistry
478
+ } : {}
479
+ };
480
+ return leafParser.parse(childContext);
481
+ }
482
+ function normalizeExecutableNodeState(state) {
483
+ if (state == null || typeof state !== "object" || !("branch" in state) || !("result" in state)) return void 0;
484
+ const branch = state.branch;
485
+ if (branch !== 0 && branch !== 1) return void 0;
486
+ const result = state.result;
487
+ if (result == null || typeof result !== "object" || typeof result.success !== "boolean") return void 0;
488
+ return inheritAnnotations(state, {
489
+ branch,
490
+ result,
491
+ committed: state.committed === true
492
+ });
493
+ }
494
+ function toExclusiveState(state, sourceState = state) {
495
+ const exclusiveState = state == null ? void 0 : [state.branch, state.result];
496
+ return inheritAnnotations(sourceState, exclusiveState);
497
+ }
498
+ function fromExclusiveState(state) {
499
+ if (!Array.isArray(state) || state.length !== 2 || state[0] !== 0 && state[0] !== 1) return void 0;
500
+ return inheritAnnotations(state, {
501
+ branch: state[0],
502
+ result: state[1],
503
+ committed: isCommittedResult(state[1])
504
+ });
505
+ }
506
+ function isCommittedResult(result) {
507
+ return result != null && typeof result === "object" && result.success === true && Array.isArray(result.consumed) && result.consumed.length > 0;
508
+ }
509
+ function wrapInitialParseResult(_context, result) {
510
+ if (!result.success) return result;
511
+ return {
512
+ success: true,
513
+ consumed: result.consumed,
514
+ provisional: result.provisional,
515
+ next: {
516
+ ...result.next,
517
+ state: fromExclusiveState(result.next.state)
518
+ }
519
+ };
520
+ }
521
+ function wrapBranchParseResult(context, activeState, result) {
522
+ if (!result.success) return result;
523
+ const mergedExec = mergeExecutableNodeChildExec(context.exec, result.next.exec);
524
+ const dependencyRegistry = mergedExec?.dependencyRegistry ?? result.next.dependencyRegistry ?? context.dependencyRegistry;
525
+ const nextState = inheritAnnotations(result.next.state, {
526
+ branch: activeState.branch,
527
+ result,
528
+ committed: activeState.committed || result.consumed.length > 0
529
+ });
530
+ return {
531
+ success: true,
532
+ consumed: result.consumed,
533
+ provisional: result.provisional,
534
+ next: {
535
+ ...context,
536
+ buffer: result.next.buffer,
537
+ optionsTerminated: result.next.optionsTerminated,
538
+ state: inheritAnnotations(context.state, nextState),
539
+ ...mergedExec != null ? {
540
+ exec: mergedExec,
541
+ trace: mergedExec.trace
542
+ } : {},
543
+ ...dependencyRegistry != null ? { dependencyRegistry } : {}
544
+ }
545
+ };
546
+ }
547
+ function withExecutableNodeChildContext(context, branch, state, parser) {
548
+ const exec = withExecutableNodeChildExecPath(context.exec, branch);
549
+ const dependencyRegistry = context.dependencyRegistry ?? exec?.dependencyRegistry;
550
+ return {
551
+ ...context,
552
+ state,
553
+ usage: parser.usage,
554
+ ...exec != null ? {
555
+ exec: dependencyRegistry === exec.dependencyRegistry ? exec : {
556
+ ...exec,
557
+ dependencyRegistry
558
+ },
559
+ dependencyRegistry
560
+ } : {}
561
+ };
562
+ }
563
+ function withExecutableNodeChildExecPath(exec, branch) {
564
+ if (exec == null) return void 0;
565
+ return {
566
+ ...exec,
567
+ path: [...exec.path ?? [], branch]
568
+ };
569
+ }
570
+ function mergeExecutableNodeChildExec(parent, child) {
571
+ if (parent == null) return child;
572
+ if (child == null) return parent;
573
+ return {
574
+ ...parent,
575
+ trace: child.trace ?? parent.trace,
576
+ dependencyRuntime: child.dependencyRuntime ?? parent.dependencyRuntime,
577
+ dependencyRegistry: child.dependencyRegistry ?? parent.dependencyRegistry,
578
+ commandPath: child.commandPath ?? parent.commandPath,
579
+ preCompletedByParser: child.preCompletedByParser ?? parent.preCompletedByParser,
580
+ excludedSourceFields: child.excludedSourceFields ?? parent.excludedSourceFields
581
+ };
582
+ }
583
+ function withCommandDocMetadata(fragments, metadata) {
584
+ if (metadata == null) return fragments;
585
+ return {
586
+ ...fragments,
587
+ brief: fragments.brief ?? metadata.brief,
588
+ description: fragments.description ?? metadata.description,
589
+ footer: fragments.footer ?? metadata.footer
590
+ };
591
+ }
592
+ function buildChildrenParser(node, inheritedHidden) {
254
593
  const parsers = [];
255
594
  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));
595
+ const childHidden = mergeHidden(inheritedHidden, child.command?.metadata?.hidden);
596
+ const childParser = buildNodeParser(child, childHidden);
597
+ if (child.children.size > 0) parsers.push(createNamespaceCommandParser(name, childParser, child.command, inheritedHidden));
598
+ else parsers.push(command(name, childParser, commandMetadataWithInheritedHidden(child.command?.metadata, inheritedHidden)));
259
599
  }
600
+ if (parsers.length < 1) return void 0;
260
601
  if (parsers.length === 1) return parsers[0];
261
602
  return or(...parsers);
262
603
  }
263
- function createLeafParser(commandDefinition) {
264
- return map(commandDefinition.parser, (value) => ({
604
+ function createNamespaceCommandParser(name, childParser, commandDefinition, inheritedHidden) {
605
+ const metadata = commandDefinition?.metadata;
606
+ const parser = command(name, childParser, namespaceCommandMetadata(metadata, inheritedHidden));
607
+ const description = metadata?.brief ?? metadata?.description;
608
+ if (description == null) return parser;
609
+ return {
610
+ ...parser,
611
+ getDocFragments(state, defaultValue) {
612
+ const fragments = parser.getDocFragments(state, defaultValue);
613
+ if (state.kind !== "unavailable" && (state.kind !== "available" || !Object.is(state.state, parser.initialState))) return fragments;
614
+ return withNamespaceListDocDescription(fragments, name, description);
615
+ }
616
+ };
617
+ }
618
+ function withNamespaceListDocDescription(fragments, name, description) {
619
+ return {
620
+ ...fragments,
621
+ fragments: fragments.fragments.map((fragment) => {
622
+ if (fragment.type !== "entry" || fragment.term.type !== "command" || fragment.term.name !== name) return fragment;
623
+ return {
624
+ ...fragment,
625
+ description: fragment.description ?? description
626
+ };
627
+ })
628
+ };
629
+ }
630
+ function namespaceCommandMetadata(metadata, inheritedHidden) {
631
+ const hidden = mergeHidden(inheritedHidden, metadata?.hidden);
632
+ if (metadata?.aliases == null && metadata?.errors == null && hidden == null && metadata?.usageLine == null) return void 0;
633
+ return {
634
+ ...metadata?.aliases != null && { aliases: metadata.aliases },
635
+ ...metadata?.errors != null && { errors: metadata.errors },
636
+ ...hidden != null && { hidden },
637
+ ...metadata?.usageLine != null && { usageLine: metadata.usageLine }
638
+ };
639
+ }
640
+ function commandMetadataWithInheritedHidden(metadata, inheritedHidden) {
641
+ const hidden = mergeHidden(inheritedHidden, metadata?.hidden);
642
+ if (metadata == null) return hidden == null ? void 0 : { hidden };
643
+ if (hidden === metadata.hidden) return metadata;
644
+ return {
645
+ ...metadata,
646
+ ...hidden != null && { hidden }
647
+ };
648
+ }
649
+ function createLeafParser(commandDefinition, includeMetadata = false) {
650
+ const parser = map(commandDefinition.parser, (value) => ({
265
651
  command: commandDefinition,
266
652
  value,
267
653
  handler: commandDefinition.handler
268
654
  }));
655
+ if (!includeMetadata) return parser;
656
+ return {
657
+ ...parser,
658
+ getDocFragments(state, defaultValue) {
659
+ const fragments = parser.getDocFragments(state, defaultValue);
660
+ return withCommandDocMetadata(fragments, commandDefinition.metadata);
661
+ }
662
+ };
269
663
  }
270
664
  function withRootDocs(parser, commands, metadata) {
271
665
  const rootState = parser.initialState;
272
- const rootDocs = () => ({
273
- brief: metadata.brief,
274
- description: metadata.description,
275
- footer: metadata.footer,
276
- fragments: [{
666
+ const rootCommand = commands.find((entry) => entry.path.length < 1);
667
+ const listedCommands = commands.filter((entry) => entry.path.length > 0);
668
+ const commandsByPath = new Map(commands.map((entry) => [commandPathKey(entry.path), entry.command]));
669
+ const rootDocs = () => {
670
+ const fragments = [...rootCommand?.command.parser.getDocFragments({ kind: "unavailable" }).fragments ?? []];
671
+ if (listedCommands.length > 0) fragments.push({
277
672
  type: "section",
278
- entries: commands.map((entry) => ({
673
+ entries: listedCommands.map((entry) => ({
279
674
  term: {
280
675
  type: "command",
281
676
  name: entry.path.join(" "),
282
- hidden: entry.command.metadata?.hidden
677
+ hidden: commandPathHidden(entry.path, commandsByPath)
283
678
  },
284
679
  description: entry.command.metadata?.brief ?? entry.command.metadata?.description
285
680
  }))
286
- }]
287
- });
681
+ });
682
+ return {
683
+ brief: metadata.brief,
684
+ description: metadata.description,
685
+ footer: metadata.footer,
686
+ fragments
687
+ };
688
+ };
288
689
  return {
289
690
  ...parser,
290
691
  getDocFragments(state, defaultValue) {
@@ -293,9 +694,14 @@ function withRootDocs(parser, commands, metadata) {
293
694
  }
294
695
  };
295
696
  }
697
+ function commandPathHidden(path, commandsByPath) {
698
+ let hidden;
699
+ for (let length = 1; length <= path.length; length++) hidden = mergeHidden(hidden, commandsByPath.get(commandPathKey(path.slice(0, length)))?.metadata?.hidden);
700
+ return hidden;
701
+ }
296
702
  function buildRunOptions(options) {
297
703
  const metadata = options.metadata;
298
- const { dir: _dir, commands: _commands, extensions: _extensions, metadata: _metadata, help, version, completion,...rest } = options;
704
+ const { dir: _dir, commands: _commands, extensions: _extensions, entryFileName: _entryFileName, metadata: _metadata, help, version, completion,...rest } = options;
299
705
  const runOptions = {
300
706
  ...rest,
301
707
  contexts: unwrapProgramContexts(rest.contexts),
@@ -345,4 +751,4 @@ function isProgramInvocation(value) {
345
751
  }
346
752
 
347
753
  //#endregion
348
- export { createProgramParser, defineCommand, discoverCommands, getDefaultExtensions, isCommand, runProgram };
754
+ export { commandsFromModules, createProgramParser, defineCommand, discoverCommands, getDefaultExtensions, isCommand, runProgram };