@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/README.md +21 -19
- package/dist/{command-DZA06E08.cjs → command-B0lV6NBO.cjs} +2 -2
- package/dist/{command-y_A3hG0g.js → command-DO5zgkvS.js} +2 -2
- package/dist/{command-2HtR3-TV.d.cts → command-DSHBTa5c.d.cts} +5 -2
- package/dist/{command-BAVhIzfI.d.ts → command-DyiVIMUh.d.ts} +5 -2
- package/dist/command.cjs +1 -1
- package/dist/command.d.cts +1 -1
- package/dist/command.d.ts +1 -1
- package/dist/command.js +1 -1
- package/dist/index.cjs +465 -58
- package/dist/index.d.cts +119 -8
- package/dist/index.d.ts +119 -8
- package/dist/index.js +467 -61
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { defineCommand, isCommand } from "./command-
|
|
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
|
|
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
|
|
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 =
|
|
63
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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.
|
|
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
|
|
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((
|
|
221
|
-
if (
|
|
222
|
-
if (!
|
|
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:
|
|
225
|
-
command:
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (
|
|
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
|
|
257
|
-
const
|
|
258
|
-
parsers.push(
|
|
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
|
|
264
|
-
|
|
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
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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:
|
|
673
|
+
entries: listedCommands.map((entry) => ({
|
|
279
674
|
term: {
|
|
280
675
|
type: "command",
|
|
281
676
|
name: entry.path.join(" "),
|
|
282
|
-
hidden: entry.
|
|
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 };
|