@optique/discover 1.1.0-dev.0

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,226 @@
1
+ import { AnyCommand, AnyStaticCommand, Command, CommandDefinition, CommandMetadata, CommandPath, StaticCommand, defineCommand, isCommand } from "./command-BAVhIzfI.js";
2
+ import { RunOptions } from "@optique/run";
3
+ import { Mode, Parser } from "@optique/core/parser";
4
+ import { Message } from "@optique/core/message";
5
+ import { ProgramMetadata } from "@optique/core/program";
6
+
7
+ //#region src/index.d.ts
8
+
9
+ /**
10
+ * The parsed command selected by a discovered command parser.
11
+ *
12
+ * Most applications receive this only indirectly through {@link runProgram},
13
+ * which calls the handler automatically.
14
+ *
15
+ * @since 1.1.0
16
+ */
17
+ interface ProgramInvocation {
18
+ /**
19
+ * The command definition that matched the input.
20
+ */
21
+ readonly command: AnyCommand;
22
+ /**
23
+ * Parsed value produced by the command parser.
24
+ */
25
+ readonly value: unknown;
26
+ /**
27
+ * Handler to call with {@link ProgramInvocation.value}.
28
+ */
29
+ readonly handler: (value: unknown) => void | Promise<void>;
30
+ }
31
+ /**
32
+ * A command found on disk.
33
+ *
34
+ * @since 1.1.0
35
+ */
36
+ interface DiscoveredCommand {
37
+ /**
38
+ * Command path derived from the module's relative path.
39
+ */
40
+ readonly path: CommandPath;
41
+ /**
42
+ * Absolute path to the command module.
43
+ */
44
+ readonly filePath: string;
45
+ /**
46
+ * The command exported by the module.
47
+ */
48
+ readonly command: AnyCommand;
49
+ }
50
+ /**
51
+ * Options for {@link discoverCommands}.
52
+ *
53
+ * @since 1.1.0
54
+ */
55
+ interface DiscoverCommandsOptions {
56
+ /**
57
+ * Directory to scan recursively.
58
+ */
59
+ readonly dir: string | URL;
60
+ /**
61
+ * File suffixes to include. Compound suffixes such as `.cmd.ts` are
62
+ * supported.
63
+ *
64
+ * @default Runtime-aware extension defaults from {@link getDefaultExtensions}
65
+ */
66
+ readonly extensions?: readonly string[];
67
+ }
68
+ /**
69
+ * Runtime hint for {@link getDefaultExtensions}.
70
+ *
71
+ * @since 1.1.0
72
+ */
73
+ interface RuntimeExtensionOptions {
74
+ /**
75
+ * Runtime to model.
76
+ */
77
+ readonly runtime?: "node" | "deno" | "bun";
78
+ /**
79
+ * Node.js execution arguments used for TypeScript loader detection.
80
+ */
81
+ readonly execArgv?: readonly string[];
82
+ /**
83
+ * The `NODE_OPTIONS` value used for TypeScript loader detection.
84
+ */
85
+ readonly nodeOptions?: string;
86
+ /**
87
+ * Whether the Node.js runtime supports TypeScript files without a custom
88
+ * loader or flag.
89
+ */
90
+ readonly nodeTypeScriptSupport?: boolean;
91
+ }
92
+ interface RunProgramBaseOptions extends Omit<RunOptions, "help" | "version" | "completion" | "programName"> {
93
+ /**
94
+ * Root program metadata.
95
+ */
96
+ readonly metadata: ProgramMetadata;
97
+ /**
98
+ * Program name override for help, errors, and completion scripts.
99
+ *
100
+ * @default `metadata.name`
101
+ */
102
+ readonly programName?: string;
103
+ /**
104
+ * Help configuration. Pass `false` to disable built-in help.
105
+ *
106
+ * @default `"both"`
107
+ */
108
+ readonly help?: RunOptions["help"] | false;
109
+ /**
110
+ * Version configuration. Pass `false` to disable built-in version output.
111
+ *
112
+ * @default `metadata.version` as `--version`, when present
113
+ */
114
+ readonly version?: RunOptions["version"] | false;
115
+ /**
116
+ * Shell completion configuration. Pass `false` to disable built-in
117
+ * completion.
118
+ *
119
+ * @default `"both"`
120
+ */
121
+ readonly completion?: RunOptions["completion"] | false;
122
+ }
123
+ /**
124
+ * Options for {@link runProgram} when discovering commands from files.
125
+ *
126
+ * @since 1.1.0
127
+ */
128
+ interface RunProgramDiscoveryOptions extends RunProgramBaseOptions {
129
+ /**
130
+ * Directory containing command modules.
131
+ */
132
+ readonly dir: string | URL;
133
+ /**
134
+ * File suffixes to include during discovery.
135
+ */
136
+ readonly extensions?: readonly string[];
137
+ /**
138
+ * Static commands cannot be used together with `dir`.
139
+ */
140
+ readonly commands?: never;
141
+ }
142
+ /**
143
+ * Options for {@link runProgram} when commands are imported manually.
144
+ *
145
+ * @since 1.1.0
146
+ */
147
+ interface RunProgramStaticOptions extends RunProgramBaseOptions {
148
+ /**
149
+ * Commands to compose without file-system discovery.
150
+ */
151
+ readonly commands: readonly AnyStaticCommand[];
152
+ /**
153
+ * File-system discovery cannot be used together with `commands`.
154
+ */
155
+ readonly dir?: never;
156
+ /**
157
+ * File suffixes are only used with file-system discovery.
158
+ */
159
+ readonly extensions?: never;
160
+ }
161
+ /**
162
+ * Options for {@link runProgram}.
163
+ *
164
+ * @since 1.1.0
165
+ */
166
+ type RunProgramOptions = RunProgramDiscoveryOptions | RunProgramStaticOptions;
167
+ /**
168
+ * Returns runtime-aware command module suffixes.
169
+ *
170
+ * @param options Runtime detection overrides for testing or custom launchers.
171
+ * @returns File suffixes to scan.
172
+ * @since 1.1.0
173
+ */
174
+ declare function getDefaultExtensions(options?: RuntimeExtensionOptions): readonly string[];
175
+ /**
176
+ * Discovers command modules under a directory.
177
+ *
178
+ * @param options Discovery options.
179
+ * @returns Discovered commands sorted by command path.
180
+ * @throws {TypeError} If options are invalid, discovery finds no commands,
181
+ * command paths conflict, or a module does not default-export a
182
+ * command created with `defineCommand()`.
183
+ * @since 1.1.0
184
+ */
185
+ declare function discoverCommands(options: DiscoverCommandsOptions): Promise<readonly DiscoveredCommand[]>;
186
+ /**
187
+ * Builds a parser that dispatches to discovered command handlers.
188
+ *
189
+ * @param commands Commands to compose.
190
+ * @param metadata Optional root documentation metadata.
191
+ * @returns A parser that resolves to an internal command invocation.
192
+ * @throws {TypeError} If no commands are provided or command paths conflict.
193
+ * @since 1.1.0
194
+ */
195
+ declare function createProgramParser(commands: readonly Pick<DiscoveredCommand, "path" | "command">[], metadata?: ProgramHelpMetadata): Parser<Mode, ProgramInvocation, unknown>;
196
+ /**
197
+ * Discovers and runs a command program.
198
+ *
199
+ * @param options Program options.
200
+ * @returns A promise that resolves after the selected command handler
201
+ * completes.
202
+ * @throws {TypeError} If discovery or command loading fails.
203
+ * @since 1.1.0
204
+ */
205
+ declare function runProgram(options: RunProgramOptions): Promise<void>;
206
+ /**
207
+ * Root help metadata for {@link createProgramParser}.
208
+ *
209
+ * @since 1.1.0
210
+ */
211
+ interface ProgramHelpMetadata {
212
+ /**
213
+ * Brief text shown before the command list.
214
+ */
215
+ readonly brief?: Message;
216
+ /**
217
+ * Detailed text shown before the command list.
218
+ */
219
+ readonly description?: Message;
220
+ /**
221
+ * Footer text shown after the command list.
222
+ */
223
+ readonly footer?: Message;
224
+ }
225
+ //#endregion
226
+ export { type AnyCommand, type AnyStaticCommand, type Command, type CommandDefinition, type CommandMetadata, type CommandPath, DiscoverCommandsOptions, DiscoveredCommand, ProgramHelpMetadata, ProgramInvocation, RunProgramDiscoveryOptions, RunProgramOptions, RunProgramStaticOptions, RuntimeExtensionOptions, type StaticCommand, createProgramParser, defineCommand, discoverCommands, getDefaultExtensions, isCommand, runProgram };
package/dist/index.js ADDED
@@ -0,0 +1,348 @@
1
+ import { defineCommand, isCommand } from "./command-y_A3hG0g.js";
2
+ import { 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 { readdir, realpath, stat } from "node:fs/promises";
7
+ import { relative, resolve, sep } from "node:path";
8
+ import process from "node:process";
9
+ import { fileURLToPath, pathToFileURL } from "node:url";
10
+
11
+ //#region src/index.ts
12
+ /**
13
+ * Returns runtime-aware command module suffixes.
14
+ *
15
+ * @param options Runtime detection overrides for testing or custom launchers.
16
+ * @returns File suffixes to scan.
17
+ * @since 1.1.0
18
+ */
19
+ function getDefaultExtensions(options = {}) {
20
+ const runtime = options.runtime ?? getRuntime();
21
+ if (runtime === "deno" || runtime === "bun") return [
22
+ ".ts",
23
+ ".mts",
24
+ ".js",
25
+ ".mjs"
26
+ ];
27
+ const extensions = [
28
+ ".js",
29
+ ".mjs",
30
+ ".cjs"
31
+ ];
32
+ if (hasNodeTypeScriptLoader(options.execArgv ?? process.execArgv, options.nodeOptions ?? process.env.NODE_OPTIONS ?? "", options.nodeTypeScriptSupport ?? hasNativeNodeTypeScriptSupport())) extensions.push(".ts", ".mts", ".cts");
33
+ return extensions;
34
+ }
35
+ /**
36
+ * Discovers command modules under a directory.
37
+ *
38
+ * @param options Discovery options.
39
+ * @returns Discovered commands sorted by command path.
40
+ * @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
+ * @since 1.1.0
44
+ */
45
+ async function discoverCommands(options) {
46
+ const dir = pathFromDir(options.dir);
47
+ const extensions = normalizeExtensions(options.extensions ?? getDefaultExtensions());
48
+ const files = await collectCommandFiles(dir, extensions);
49
+ if (files.length < 1) throw new TypeError(`No command modules found in ${dir}.`);
50
+ const seen = /* @__PURE__ */ new Map();
51
+ const discovered = [];
52
+ for (const filePath of files) {
53
+ const path = commandPathFromFile(dir, filePath, extensions);
54
+ const key = commandPathKey(path);
55
+ const previous = seen.get(key);
56
+ if (previous != null) {
57
+ const displayPath = path.join(" ");
58
+ throw new TypeError(`Duplicate command path "${displayPath}" from ${previous} and ${filePath}.`);
59
+ }
60
+ seen.set(key, filePath);
61
+ 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
+ discovered.push({
66
+ path,
67
+ filePath,
68
+ command: commandDefinition
69
+ });
70
+ }
71
+ rejectPathConflicts(discovered);
72
+ return sortCommands(discovered);
73
+ }
74
+ /**
75
+ * Builds a parser that dispatches to discovered command handlers.
76
+ *
77
+ * @param commands Commands to compose.
78
+ * @param metadata Optional root documentation metadata.
79
+ * @returns A parser that resolves to an internal command invocation.
80
+ * @throws {TypeError} If no commands are provided or command paths conflict.
81
+ * @since 1.1.0
82
+ */
83
+ function createProgramParser(commands, metadata = {}) {
84
+ if (commands.length < 1) throw new TypeError("createProgramParser() requires at least one command.");
85
+ const sortedCommands = sortCommands(commands);
86
+ rejectDuplicatePaths(sortedCommands.map((entry) => ({
87
+ path: entry.path,
88
+ filePath: entry.path.join("/")
89
+ })));
90
+ rejectPathConflicts(sortedCommands.map((entry) => ({
91
+ path: entry.path,
92
+ filePath: entry.path.join("/")
93
+ })));
94
+ const rootNode = buildCommandTree(sortedCommands);
95
+ const parser = buildNodeParser(rootNode);
96
+ return withRootDocs(parser, sortedCommands, metadata);
97
+ }
98
+ /**
99
+ * Discovers and runs a command program.
100
+ *
101
+ * @param options Program options.
102
+ * @returns A promise that resolves after the selected command handler
103
+ * completes.
104
+ * @throws {TypeError} If discovery or command loading fails.
105
+ * @since 1.1.0
106
+ */
107
+ async function runProgram(options) {
108
+ let commands;
109
+ if (isStaticRunProgramOptions(options)) commands = staticCommandsToEntries(options.commands);
110
+ else commands = await discoverCommands({
111
+ dir: options.dir,
112
+ extensions: options.extensions
113
+ });
114
+ const parser = createProgramParser(commands, options.metadata);
115
+ const invocation = await runAsync(parser, buildRunOptions(options));
116
+ await invocation.handler(invocation.value);
117
+ }
118
+ function getRuntime() {
119
+ if ("Deno" in globalThis) return "deno";
120
+ if ("Bun" in globalThis) return "bun";
121
+ return "node";
122
+ }
123
+ function hasNodeTypeScriptLoader(execArgv, nodeOptions, nativeSupport) {
124
+ const haystack = [...execArgv, nodeOptions].join(" ");
125
+ return nativeSupport || /\b(?:tsx|ts-node|tsimp|jiti)\b/.test(haystack) || /--(?:experimental-)?transform-types\b/.test(haystack) || /--experimental-strip-types\b/.test(haystack);
126
+ }
127
+ function hasNativeNodeTypeScriptSupport() {
128
+ const features = process.features;
129
+ return features?.typescript === "strip" || features?.typescript === "transform";
130
+ }
131
+ function pathFromDir(dir) {
132
+ return typeof dir === "string" ? resolve(dir) : fileURLToPath(dir);
133
+ }
134
+ function normalizeExtensions(extensions) {
135
+ if (extensions.length < 1) throw new TypeError("At least one command file extension is required.");
136
+ const normalized = [];
137
+ for (const extension of extensions) {
138
+ if (!extension.startsWith(".") || extension.length < 2) throw new TypeError(`Command file extension must start with a dot: ${extension}`);
139
+ if (!normalized.includes(extension)) normalized.push(extension);
140
+ }
141
+ return normalized.toSorted((a, b) => b.length - a.length || a.localeCompare(b));
142
+ }
143
+ async function collectCommandFiles(dir, extensions, activeDirs = /* @__PURE__ */ new Set()) {
144
+ const canonicalDir = await realpath(dir);
145
+ if (activeDirs.has(canonicalDir)) return [];
146
+ const nextActiveDirs = new Set(activeDirs);
147
+ nextActiveDirs.add(canonicalDir);
148
+ const entries = await readdir(dir, { withFileTypes: true });
149
+ const files = [];
150
+ for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
151
+ const path = resolve(dir, entry.name);
152
+ const entryType = await getCommandFileEntryType(path, entry);
153
+ if (entryType === "directory") files.push(...await collectCommandFiles(path, extensions, nextActiveDirs));
154
+ else if (entryType === "file" && !isDeclarationFile(entry.name) && extensions.some((ext) => entry.name.endsWith(ext))) files.push(path);
155
+ }
156
+ return files;
157
+ }
158
+ async function getCommandFileEntryType(path, entry) {
159
+ if (entry.isDirectory()) return "directory";
160
+ if (entry.isFile()) return "file";
161
+ if (!entry.isSymbolicLink()) return void 0;
162
+ try {
163
+ const target = await stat(path);
164
+ if (target.isDirectory()) return "directory";
165
+ if (target.isFile()) return "file";
166
+ return void 0;
167
+ } catch {
168
+ return void 0;
169
+ }
170
+ }
171
+ function isDeclarationFile(fileName) {
172
+ return /\.d\.[cm]?ts$/.test(fileName);
173
+ }
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);
178
+ const relativePath = relative(rootDir, withoutExtension);
179
+ 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];
183
+ }
184
+ function commandPathKey(path) {
185
+ return path.join("\0");
186
+ }
187
+ 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
+ }
197
+ }
198
+ function rejectDuplicatePaths(commands) {
199
+ const seen = /* @__PURE__ */ new Map();
200
+ for (const entry of commands) {
201
+ const key = commandPathKey(entry.path);
202
+ const previous = seen.get(key);
203
+ if (previous != null) {
204
+ const displayPath = entry.path.join(" ");
205
+ throw new TypeError(`Duplicate command path "${displayPath}" from ${previous} and ${entry.filePath}.`);
206
+ }
207
+ seen.set(key, entry.filePath);
208
+ }
209
+ }
210
+ function sortCommands(commands) {
211
+ return commands.toSorted((a, b) => commandPathKey(a.path).localeCompare(commandPathKey(b.path)));
212
+ }
213
+ function isStaticRunProgramOptions(options) {
214
+ const hasCommands = "commands" in options && options.commands != null;
215
+ const hasDir = "dir" in options && options.dir != null;
216
+ if (hasCommands === hasDir) throw new TypeError("runProgram() requires exactly one of dir or commands.");
217
+ return hasCommands;
218
+ }
219
+ 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.");
223
+ return {
224
+ path: command$1.path,
225
+ command: command$1
226
+ };
227
+ });
228
+ }
229
+ 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;
234
+ }
235
+ return void 0;
236
+ }
237
+ function buildCommandTree(commands) {
238
+ const root = { children: /* @__PURE__ */ new Map() };
239
+ for (const entry of commands) {
240
+ let current = root;
241
+ for (const segment of entry.path) {
242
+ let child = current.children.get(segment);
243
+ if (child == null) {
244
+ child = { children: /* @__PURE__ */ new Map() };
245
+ current.children.set(segment, child);
246
+ }
247
+ current = child;
248
+ }
249
+ current.command = entry.command;
250
+ }
251
+ return root;
252
+ }
253
+ function buildNodeParser(node) {
254
+ const parsers = [];
255
+ 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));
259
+ }
260
+ if (parsers.length === 1) return parsers[0];
261
+ return or(...parsers);
262
+ }
263
+ function createLeafParser(commandDefinition) {
264
+ return map(commandDefinition.parser, (value) => ({
265
+ command: commandDefinition,
266
+ value,
267
+ handler: commandDefinition.handler
268
+ }));
269
+ }
270
+ function withRootDocs(parser, commands, metadata) {
271
+ const rootState = parser.initialState;
272
+ const rootDocs = () => ({
273
+ brief: metadata.brief,
274
+ description: metadata.description,
275
+ footer: metadata.footer,
276
+ fragments: [{
277
+ type: "section",
278
+ entries: commands.map((entry) => ({
279
+ term: {
280
+ type: "command",
281
+ name: entry.path.join(" "),
282
+ hidden: entry.command.metadata?.hidden
283
+ },
284
+ description: entry.command.metadata?.brief ?? entry.command.metadata?.description
285
+ }))
286
+ }]
287
+ });
288
+ return {
289
+ ...parser,
290
+ getDocFragments(state, defaultValue) {
291
+ if (state.kind === "unavailable" || state.kind === "available" && Object.is(state.state, rootState)) return rootDocs();
292
+ return parser.getDocFragments(state, defaultValue);
293
+ }
294
+ };
295
+ }
296
+ function buildRunOptions(options) {
297
+ const metadata = options.metadata;
298
+ const { dir: _dir, commands: _commands, extensions: _extensions, metadata: _metadata, help, version, completion,...rest } = options;
299
+ const runOptions = {
300
+ ...rest,
301
+ contexts: unwrapProgramContexts(rest.contexts),
302
+ programName: options.programName ?? metadata.name,
303
+ brief: options.brief ?? metadata.brief,
304
+ description: options.description ?? metadata.description,
305
+ examples: options.examples ?? metadata.examples,
306
+ author: options.author ?? metadata.author,
307
+ bugs: options.bugs ?? metadata.bugs,
308
+ footer: options.footer ?? metadata.footer,
309
+ help: help === false ? void 0 : help ?? "both",
310
+ version: version === false ? void 0 : version ?? (metadata.version == null ? void 0 : metadata.version),
311
+ completion: completion === false ? void 0 : completion ?? "both"
312
+ };
313
+ return runOptions;
314
+ }
315
+ function unwrapProgramContexts(contexts) {
316
+ if (contexts == null) return void 0;
317
+ return contexts.map(wrapProgramContext);
318
+ }
319
+ function wrapProgramContext(context) {
320
+ const wrapped = {
321
+ id: context.id,
322
+ phase: context.phase,
323
+ getAnnotations(request, options) {
324
+ return context.getAnnotations(unwrapProgramContextRequest(request), options);
325
+ }
326
+ };
327
+ if (context.getInternalAnnotations != null) wrapped.getInternalAnnotations = (request, annotations) => {
328
+ return context.getInternalAnnotations(unwrapProgramContextRequest(request) ?? request, annotations);
329
+ };
330
+ if (context[Symbol.dispose] != null) wrapped[Symbol.dispose] = () => context[Symbol.dispose]();
331
+ if (context[Symbol.asyncDispose] != null) wrapped[Symbol.asyncDispose] = () => context[Symbol.asyncDispose]();
332
+ return wrapped;
333
+ }
334
+ function unwrapProgramContextRequest(request) {
335
+ if (request?.phase === "phase2" && isProgramInvocation(request.parsed)) return {
336
+ phase: "phase2",
337
+ parsed: request.parsed.value
338
+ };
339
+ return request;
340
+ }
341
+ function isProgramInvocation(value) {
342
+ if (value == null || typeof value !== "object") return false;
343
+ const candidate = value;
344
+ return "value" in value && isCommand(candidate.command) && typeof candidate.handler === "function";
345
+ }
346
+
347
+ //#endregion
348
+ export { createProgramParser, defineCommand, discoverCommands, getDefaultExtensions, isCommand, runProgram };
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@optique/discover",
3
+ "version": "1.1.0-dev.0",
4
+ "description": "Runtime-aware command discovery for Optique CLI programs",
5
+ "keywords": [
6
+ "CLI",
7
+ "command-line",
8
+ "commandline",
9
+ "parser",
10
+ "commands",
11
+ "discovery"
12
+ ],
13
+ "license": "MIT",
14
+ "author": {
15
+ "name": "Hong Minhee",
16
+ "email": "hong@minhee.org",
17
+ "url": "https://hongminhee.org/"
18
+ },
19
+ "homepage": "https://optique.dev/",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/dahlia/optique.git",
23
+ "directory": "packages/discover/"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/dahlia/optique/issues"
27
+ },
28
+ "funding": [
29
+ "https://github.com/sponsors/dahlia"
30
+ ],
31
+ "engines": {
32
+ "node": ">=20.0.0",
33
+ "bun": ">=1.2.0",
34
+ "deno": ">=2.3.0"
35
+ },
36
+ "files": [
37
+ "dist/",
38
+ "package.json",
39
+ "README.md"
40
+ ],
41
+ "type": "module",
42
+ "module": "./dist/index.js",
43
+ "main": "./dist/index.cjs",
44
+ "types": "./dist/index.d.ts",
45
+ "exports": {
46
+ ".": {
47
+ "types": {
48
+ "import": "./dist/index.d.ts",
49
+ "require": "./dist/index.d.cts"
50
+ },
51
+ "import": "./dist/index.js",
52
+ "require": "./dist/index.cjs"
53
+ },
54
+ "./command": {
55
+ "types": {
56
+ "import": "./dist/command.d.ts",
57
+ "require": "./dist/command.d.cts"
58
+ },
59
+ "import": "./dist/command.js",
60
+ "require": "./dist/command.cjs"
61
+ }
62
+ },
63
+ "sideEffects": false,
64
+ "dependencies": {
65
+ "@optique/core": "1.1.0",
66
+ "@optique/run": "1.1.0"
67
+ },
68
+ "devDependencies": {
69
+ "@types/node": "^20.19.9",
70
+ "tsdown": "^0.13.0",
71
+ "typescript": "^5.8.3"
72
+ },
73
+ "scripts": {
74
+ "build": "tsdown",
75
+ "prepublish": "tsdown",
76
+ "test": "node --experimental-transform-types --test",
77
+ "test:bun": "bun test",
78
+ "test:deno": "deno test --allow-read --allow-write --allow-env",
79
+ "test-all": "tsdown && node --experimental-transform-types --test && bun test && deno test --allow-read --allow-write --allow-env"
80
+ }
81
+ }