@optique/discover 1.2.0-dev.2169 → 1.2.0-dev.2179

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.
@@ -0,0 +1,754 @@
1
+ import { isCommand } from "./command-DO5zgkvS.js";
2
+ import { longestMatch, or } from "@optique/core/constructs";
3
+ import { map } from "@optique/core/modifiers";
4
+ import { command } from "@optique/core/primitives";
5
+ import { runAsync } from "@optique/run";
6
+ import process from "node:process";
7
+ import { readdir, realpath, stat } from "node:fs/promises";
8
+ import { posix, relative, resolve, sep } from "node:path";
9
+ import { fileURLToPath, pathToFileURL } from "node:url";
10
+ import { dispatchByMode, inheritAnnotations, mapModeValue, wrapForMode } from "@optique/core/extension";
11
+ import { mergeHidden } from "@optique/core/usage";
12
+
13
+ //#region src/index.ts
14
+ /**
15
+ * Returns runtime-aware command module suffixes.
16
+ *
17
+ * @param options Runtime detection overrides for testing or custom launchers.
18
+ * @returns File suffixes to scan.
19
+ * @since 1.1.0
20
+ */
21
+ function getDefaultExtensions(options = {}) {
22
+ const runtime = options.runtime ?? getRuntime();
23
+ if (runtime === "deno" || runtime === "bun") return [
24
+ ".ts",
25
+ ".mts",
26
+ ".js",
27
+ ".mjs"
28
+ ];
29
+ const extensions = [
30
+ ".js",
31
+ ".mjs",
32
+ ".cjs"
33
+ ];
34
+ if (hasNodeTypeScriptLoader(options.execArgv ?? process.execArgv, options.nodeOptions ?? process.env.NODE_OPTIONS ?? "", options.nodeTypeScriptSupport ?? hasNativeNodeTypeScriptSupport())) extensions.push(".ts", ".mts", ".cts");
35
+ return extensions;
36
+ }
37
+ /**
38
+ * Discovers command modules under a directory.
39
+ *
40
+ * @param options Discovery options.
41
+ * @returns Discovered commands sorted by command path.
42
+ * @throws {TypeError} If options are invalid, discovery finds no commands,
43
+ * command paths are duplicated, or a module does not default-export
44
+ * a command created with `defineCommand()`.
45
+ * @since 1.1.0
46
+ */
47
+ async function discoverCommands(options) {
48
+ const dir = pathFromDir(options.dir);
49
+ const extensions = normalizeExtensions(options.extensions ?? getDefaultExtensions());
50
+ const entryFileName = normalizeEntryFileName(options.entryFileName);
51
+ const files = await collectCommandFiles(dir, extensions);
52
+ if (files.length < 1) throw new TypeError(`No command modules found in ${dir}.`);
53
+ const seen = /* @__PURE__ */ new Map();
54
+ const discovered = [];
55
+ for (const filePath of files) {
56
+ const path = commandPathFromFile(dir, filePath, extensions, entryFileName);
57
+ const key = commandPathKey(path);
58
+ const previous = seen.get(key);
59
+ if (previous != null) {
60
+ const displayPath = displayCommandPath(path);
61
+ throw new TypeError(`Duplicate command path "${displayPath}" from ${previous} and ${filePath}.`);
62
+ }
63
+ seen.set(key, filePath);
64
+ const mod = await import(pathToFileURL(filePath).href);
65
+ const commandDefinition = commandFromModuleExport(filePath, mod.default);
66
+ validateDeclaredCommandPath(commandDefinition, path, filePath, "file path");
67
+ discovered.push({
68
+ path,
69
+ filePath,
70
+ command: commandDefinition
71
+ });
72
+ }
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.");
118
+ return sortCommands(discovered);
119
+ }
120
+ /**
121
+ * Builds a parser that dispatches to discovered command handlers.
122
+ *
123
+ * @param commands Commands to compose.
124
+ * @param metadata Optional root documentation metadata.
125
+ * @returns A parser that resolves to an internal command invocation.
126
+ * @throws {TypeError} If no commands are provided or command paths are
127
+ * duplicated.
128
+ * @since 1.1.0
129
+ */
130
+ function createProgramParser(commands, metadata = {}) {
131
+ if (commands.length < 1) throw new TypeError("createProgramParser() requires at least one command.");
132
+ const sortedCommands = sortCommands(commands);
133
+ rejectDuplicatePaths(sortedCommands.map((entry) => ({
134
+ path: entry.path,
135
+ filePath: displayCommandPath(entry.path)
136
+ })));
137
+ const rootNode = buildCommandTree(sortedCommands);
138
+ const parser = buildNodeParser(rootNode);
139
+ return withRootDocs(parser, sortedCommands, metadata);
140
+ }
141
+ /**
142
+ * Discovers and runs a command program.
143
+ *
144
+ * @param options Program options.
145
+ * @returns A promise that resolves after the selected command handler
146
+ * completes.
147
+ * @throws {TypeError} If discovery or command loading fails.
148
+ * @since 1.1.0
149
+ */
150
+ async function runProgram(options) {
151
+ let commands;
152
+ if (isStaticRunProgramOptions(options)) commands = staticCommandsToEntries(options.commands);
153
+ else commands = await discoverCommands({
154
+ dir: options.dir,
155
+ extensions: options.extensions,
156
+ entryFileName: options.entryFileName
157
+ });
158
+ const parser = createProgramParser(commands, options.metadata);
159
+ const invocation = await runAsync(parser, buildRunOptions(options));
160
+ await invocation.handler(invocation.value);
161
+ }
162
+ function getRuntime() {
163
+ if ("Deno" in globalThis) return "deno";
164
+ if ("Bun" in globalThis) return "bun";
165
+ return "node";
166
+ }
167
+ function hasNodeTypeScriptLoader(execArgv, nodeOptions, nativeSupport) {
168
+ const haystack = [...execArgv, nodeOptions].join(" ");
169
+ return nativeSupport || /\b(?:tsx|ts-node|tsimp|jiti)\b/.test(haystack) || /--(?:experimental-)?transform-types\b/.test(haystack) || /--experimental-strip-types\b/.test(haystack);
170
+ }
171
+ function hasNativeNodeTypeScriptSupport() {
172
+ const features = process.features;
173
+ return features?.typescript === "strip" || features?.typescript === "transform";
174
+ }
175
+ function pathFromDir(dir) {
176
+ return typeof dir === "string" ? resolve(dir) : fileURLToPath(dir);
177
+ }
178
+ function normalizeExtensions(extensions) {
179
+ if (extensions.length < 1) throw new TypeError("At least one command file extension is required.");
180
+ const normalized = [];
181
+ for (const extension of extensions) {
182
+ if (!extension.startsWith(".") || extension.length < 2) throw new TypeError(`Command file extension must start with a dot: ${extension}`);
183
+ if (!normalized.includes(extension)) normalized.push(extension);
184
+ }
185
+ return normalized.toSorted((a, b) => b.length - a.length || a.localeCompare(b));
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
+ }
200
+ async function collectCommandFiles(dir, extensions, activeDirs = /* @__PURE__ */ new Set()) {
201
+ const canonicalDir = await realpath(dir);
202
+ if (activeDirs.has(canonicalDir)) return [];
203
+ const nextActiveDirs = new Set(activeDirs);
204
+ nextActiveDirs.add(canonicalDir);
205
+ const entries = await readdir(dir, { withFileTypes: true });
206
+ const files = [];
207
+ for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
208
+ const path = resolve(dir, entry.name);
209
+ const entryType = await getCommandFileEntryType(path, entry);
210
+ if (entryType === "directory") files.push(...await collectCommandFiles(path, extensions, nextActiveDirs));
211
+ else if (entryType === "file" && !isDeclarationFile(entry.name) && extensions.some((ext) => entry.name.endsWith(ext))) files.push(path);
212
+ }
213
+ return files;
214
+ }
215
+ async function getCommandFileEntryType(path, entry) {
216
+ if (entry.isDirectory()) return "directory";
217
+ if (entry.isFile()) return "file";
218
+ if (!entry.isSymbolicLink()) return void 0;
219
+ try {
220
+ const target = await stat(path);
221
+ if (target.isDirectory()) return "directory";
222
+ if (target.isFile()) return "file";
223
+ return void 0;
224
+ } catch {
225
+ return void 0;
226
+ }
227
+ }
228
+ function isDeclarationFile(fileName) {
229
+ return /\.d\.[cm]?ts$/.test(fileName);
230
+ }
231
+ function commandPathFromFile(rootDir, filePath, extensions, entryFileName) {
232
+ const withoutExtension = stripCommandExtension(filePath, extensions);
233
+ const relativePath = relative(rootDir, withoutExtension);
234
+ const path = relativePath.split(sep).filter((segment) => segment.length > 0);
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;
261
+ }
262
+ function commandPathKey(path) {
263
+ return path.join("\0");
264
+ }
265
+ function displayCommandPath(path) {
266
+ return path.length < 1 ? "<root>" : path.join(" ");
267
+ }
268
+ function isCommandPath(path) {
269
+ return Array.isArray(path) && path.every((segment) => typeof segment === "string" && segment.length > 0);
270
+ }
271
+ function rejectDuplicatePaths(commands) {
272
+ const seen = /* @__PURE__ */ new Map();
273
+ for (const entry of commands) {
274
+ const key = commandPathKey(entry.path);
275
+ const previous = seen.get(key);
276
+ if (previous != null) {
277
+ const displayPath = displayCommandPath(entry.path);
278
+ throw new TypeError(`Duplicate command path "${displayPath}" from ${previous} and ${entry.filePath}.`);
279
+ }
280
+ seen.set(key, entry.filePath);
281
+ }
282
+ }
283
+ function sortCommands(commands) {
284
+ return commands.toSorted((a, b) => commandPathKey(a.path).localeCompare(commandPathKey(b.path)));
285
+ }
286
+ function isStaticRunProgramOptions(options) {
287
+ const hasCommands = "commands" in options && options.commands != null;
288
+ const hasDir = "dir" in options && options.dir != null;
289
+ if (hasCommands === hasDir) throw new TypeError("runProgram() requires exactly one of dir or commands.");
290
+ return hasCommands;
291
+ }
292
+ function staticCommandsToEntries(commands) {
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.");
297
+ return {
298
+ path: entry.path,
299
+ command: entry
300
+ };
301
+ });
302
+ }
303
+ function isCommandEntry(value) {
304
+ return value != null && typeof value === "object" && isCommandPath(value.path) && isCommand(value.command);
305
+ }
306
+ function unwrapCommandExport(value) {
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;
314
+ }
315
+ return void 0;
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
+ }
325
+ function buildCommandTree(commands) {
326
+ const root = { children: /* @__PURE__ */ new Map() };
327
+ for (const entry of commands) {
328
+ let current = root;
329
+ for (const segment of entry.path) {
330
+ let child = current.children.get(segment);
331
+ if (child == null) {
332
+ child = { children: /* @__PURE__ */ new Map() };
333
+ current.children.set(segment, child);
334
+ }
335
+ current = child;
336
+ }
337
+ current.command = entry.command;
338
+ }
339
+ return root;
340
+ }
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) {
593
+ const parsers = [];
594
+ for (const [name, child] of node.children) {
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)));
599
+ }
600
+ if (parsers.length < 1) return void 0;
601
+ if (parsers.length === 1) return parsers[0];
602
+ return or(...parsers);
603
+ }
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) => ({
651
+ command: commandDefinition,
652
+ value,
653
+ handler: commandDefinition.handler
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
+ };
663
+ }
664
+ function withRootDocs(parser, commands, metadata) {
665
+ const rootState = parser.initialState;
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({
672
+ type: "section",
673
+ entries: listedCommands.map((entry) => ({
674
+ term: {
675
+ type: "command",
676
+ name: entry.path.join(" "),
677
+ hidden: commandPathHidden(entry.path, commandsByPath)
678
+ },
679
+ description: entry.command.metadata?.brief ?? entry.command.metadata?.description
680
+ }))
681
+ });
682
+ return {
683
+ brief: metadata.brief,
684
+ description: metadata.description,
685
+ footer: metadata.footer,
686
+ fragments
687
+ };
688
+ };
689
+ return {
690
+ ...parser,
691
+ getDocFragments(state, defaultValue) {
692
+ if (state.kind === "unavailable" || state.kind === "available" && Object.is(state.state, rootState)) return rootDocs();
693
+ return parser.getDocFragments(state, defaultValue);
694
+ }
695
+ };
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
+ }
702
+ function buildRunOptions(options) {
703
+ const metadata = options.metadata;
704
+ const { dir: _dir, commands: _commands, extensions: _extensions, entryFileName: _entryFileName, metadata: _metadata, help, version, completion,...rest } = options;
705
+ const runOptions = {
706
+ ...rest,
707
+ contexts: unwrapProgramContexts(rest.contexts),
708
+ programName: options.programName ?? metadata.name,
709
+ brief: options.brief ?? metadata.brief,
710
+ description: options.description ?? metadata.description,
711
+ examples: options.examples ?? metadata.examples,
712
+ author: options.author ?? metadata.author,
713
+ bugs: options.bugs ?? metadata.bugs,
714
+ footer: options.footer ?? metadata.footer,
715
+ help: help === false ? void 0 : help ?? "both",
716
+ version: version === false ? void 0 : version ?? (metadata.version == null ? void 0 : metadata.version),
717
+ completion: completion === false ? void 0 : completion ?? "both"
718
+ };
719
+ return runOptions;
720
+ }
721
+ function unwrapProgramContexts(contexts) {
722
+ if (contexts == null) return void 0;
723
+ return contexts.map(wrapProgramContext);
724
+ }
725
+ function wrapProgramContext(context) {
726
+ const wrapped = {
727
+ id: context.id,
728
+ phase: context.phase,
729
+ getAnnotations(request, options) {
730
+ return context.getAnnotations(unwrapProgramContextRequest(request), options);
731
+ }
732
+ };
733
+ if (context.getInternalAnnotations != null) wrapped.getInternalAnnotations = (request, annotations) => {
734
+ return context.getInternalAnnotations(unwrapProgramContextRequest(request) ?? request, annotations);
735
+ };
736
+ if (context[Symbol.dispose] != null) wrapped[Symbol.dispose] = () => context[Symbol.dispose]();
737
+ if (context[Symbol.asyncDispose] != null) wrapped[Symbol.asyncDispose] = () => context[Symbol.asyncDispose]();
738
+ return wrapped;
739
+ }
740
+ function unwrapProgramContextRequest(request) {
741
+ if (request?.phase === "phase2" && isProgramInvocation(request.parsed)) return {
742
+ phase: "phase2",
743
+ parsed: request.parsed.value
744
+ };
745
+ return request;
746
+ }
747
+ function isProgramInvocation(value) {
748
+ if (value == null || typeof value !== "object") return false;
749
+ const candidate = value;
750
+ return "value" in value && isCommand(candidate.command) && typeof candidate.handler === "function";
751
+ }
752
+
753
+ //#endregion
754
+ export { commandsFromModules, createProgramParser, discoverCommands, getDefaultExtensions, runProgram };