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

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 CHANGED
@@ -110,6 +110,33 @@ the module list visible to bundlers. For smaller registries, you can also
110
110
  import commands manually, declare `path` in each `defineCommand()` call, and
111
111
  pass those commands to `runProgram({ commands })`.
112
112
 
113
+ If you do not want to maintain the static module map by hand, generate one:
114
+
115
+ ~~~~ bash
116
+ optique-discover ./commands --output ./commands.generated.ts --extension .ts
117
+ ~~~~
118
+
119
+ Then import the generated module from your CLI entry point:
120
+
121
+ ~~~~ typescript
122
+ // cli.ts
123
+ import { runProgram } from "@optique/discover";
124
+ import { message } from "@optique/core/message";
125
+ import commands from "./commands.generated.ts";
126
+
127
+ await runProgram({
128
+ commands,
129
+ metadata: {
130
+ name: "admin",
131
+ version: "1.0.0",
132
+ brief: message`Administrative command-line tools.`,
133
+ },
134
+ });
135
+ ~~~~
136
+
137
+ Use `--watch` during development to regenerate only when command files are
138
+ added, removed, or renamed.
139
+
113
140
  By default, Deno and Bun discover `.ts`, `.mts`, `.js`, and `.mjs` files.
114
141
  Node.js discovers `.js`, `.mjs`, and `.cjs` files, plus `.ts`, `.mts`, and
115
142
  `.cts` when it reports native TypeScript support or runs with a recognized
@@ -0,0 +1,30 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+
25
+ Object.defineProperty(exports, '__toESM', {
26
+ enumerable: true,
27
+ get: function () {
28
+ return __toESM;
29
+ }
30
+ });
package/dist/cli.cjs ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ const require_chunk = require('./chunk-CUT6urMc.cjs');
3
+ require('./command-CUn2_NIA.cjs');
4
+ require('./src-BYGxdHGJ.cjs');
5
+ const require_generator = require('./generator-e4RwnDKG.cjs');
6
+ const require_main_check = require('./main-check-CwunSNpK.cjs');
7
+ const __optique_core_constructs = require_chunk.__toESM(require("@optique/core/constructs"));
8
+ const __optique_core_message = require_chunk.__toESM(require("@optique/core/message"));
9
+ const __optique_core_modifiers = require_chunk.__toESM(require("@optique/core/modifiers"));
10
+ const __optique_core_primitives = require_chunk.__toESM(require("@optique/core/primitives"));
11
+ const __optique_core_program = require_chunk.__toESM(require("@optique/core/program"));
12
+ const __optique_core_valueparser = require_chunk.__toESM(require("@optique/core/valueparser"));
13
+ const __optique_run = require_chunk.__toESM(require("@optique/run"));
14
+ const node_process = require_chunk.__toESM(require("node:process"));
15
+
16
+ //#region deno.json
17
+ var name = "@optique/discover";
18
+ var version = "1.2.0-dev.2180+f59f99d2";
19
+ var license = "MIT";
20
+ var exports$1 = {
21
+ ".": "./src/index.ts",
22
+ "./command": "./src/command.ts",
23
+ "./generator": "./src/generator.ts",
24
+ "./cli": "./src/cli.ts"
25
+ };
26
+ var imports = { "#src/": "./src/" };
27
+ var exclude = [
28
+ "dist/",
29
+ "node_modules",
30
+ "tsdown.config.ts"
31
+ ];
32
+ var tasks = {
33
+ "build": "pnpm build",
34
+ "test": "deno test --allow-read --allow-write --allow-env",
35
+ "test:node": {
36
+ "dependencies": ["build"],
37
+ "command": "node --test"
38
+ },
39
+ "test:bun": {
40
+ "dependencies": ["build"],
41
+ "command": "bun test"
42
+ },
43
+ "test-all": { "dependencies": [
44
+ "test",
45
+ "test:node",
46
+ "test:bun"
47
+ ] }
48
+ };
49
+ var deno_default = {
50
+ name,
51
+ version,
52
+ license,
53
+ exports: exports$1,
54
+ imports,
55
+ exclude,
56
+ tasks
57
+ };
58
+
59
+ //#endregion
60
+ //#region src/cli.ts
61
+ const EXIT_GENERATION_FAILED = 1;
62
+ const EXIT_INVALID_OPTIONS = 2;
63
+ const cliProgram = (0, __optique_core_program.defineProgram)({
64
+ parser: (0, __optique_core_constructs.object)({
65
+ dir: (0, __optique_core_primitives.argument)((0, __optique_core_valueparser.string)({ metavar: "DIR" }), { description: __optique_core_message.message`Directory containing command modules.` }),
66
+ outputFile: (0, __optique_core_primitives.option)("-o", "--output", (0, __optique_core_valueparser.string)({ metavar: "FILE" }), { description: __optique_core_message.message`Generated TypeScript module path.` }),
67
+ base: (0, __optique_core_modifiers.optional)((0, __optique_core_primitives.option)("--base", (0, __optique_core_valueparser.string)({ metavar: "PATH" }), { description: __optique_core_message.message`Module map base path passed to commandsFromModules().
68
+ Defaults to the command directory relative to the generated module.` })),
69
+ extensions: (0, __optique_core_modifiers.withDefault)((0, __optique_core_modifiers.multiple)((0, __optique_core_primitives.option)("--extension", (0, __optique_core_valueparser.string)({ metavar: "EXT" }))), []),
70
+ entryFileName: (0, __optique_core_modifiers.optional)((0, __optique_core_primitives.option)("--entry-file-name", (0, __optique_core_valueparser.string)({ metavar: "NAME" }), { description: __optique_core_message.message`File name that maps to the containing command path.
71
+ Defaults to commandsFromModules() behavior.` })),
72
+ noEntryFileName: (0, __optique_core_primitives.option)("--no-entry-file-name", { description: __optique_core_message.message`Treat entry files as ordinary command names.` }),
73
+ watch: (0, __optique_core_primitives.option)("--watch", { description: __optique_core_message.message`Regenerate when command files are added, removed,
74
+ or renamed.` })
75
+ }),
76
+ metadata: {
77
+ name: "optique-discover",
78
+ version: deno_default.version,
79
+ brief: __optique_core_message.message`Generate static command modules for @optique/discover`
80
+ }
81
+ });
82
+ /**
83
+ * Runs the optique-discover command-line interface.
84
+ *
85
+ * @returns A promise that resolves after generation or watch mode finishes.
86
+ * @since 1.2.0
87
+ */
88
+ async function main() {
89
+ const args = (0, __optique_run.runSync)(cliProgram, {
90
+ help: "option",
91
+ version: {
92
+ value: deno_default.version,
93
+ option: true
94
+ }
95
+ });
96
+ if (args.noEntryFileName && args.entryFileName != null) {
97
+ (0, __optique_run.printError)(__optique_core_message.message`Use either --entry-file-name or --no-entry-file-name, not both.`, { exitCode: EXIT_INVALID_OPTIONS });
98
+ return;
99
+ }
100
+ const options = {
101
+ dir: args.dir,
102
+ outputFile: args.outputFile,
103
+ ...args.base != null ? { base: args.base } : {},
104
+ ...args.extensions.length > 0 ? { extensions: args.extensions } : {},
105
+ ...args.noEntryFileName ? { entryFileName: false } : args.entryFileName != null ? { entryFileName: args.entryFileName } : {}
106
+ };
107
+ try {
108
+ if (args.watch) await require_generator.watchCommandsModule({
109
+ ...options,
110
+ onGenerate(result) {
111
+ writeGeneratedMessage(result.files.length);
112
+ },
113
+ onError(error) {
114
+ printGenerationError(error);
115
+ }
116
+ });
117
+ else {
118
+ const result = await require_generator.writeCommandsModule(options);
119
+ writeGeneratedMessage(result.files.length);
120
+ }
121
+ } catch (error) {
122
+ printGenerationError(error, EXIT_GENERATION_FAILED);
123
+ }
124
+ }
125
+ function printGenerationError(error, exitCode) {
126
+ const messageText = error instanceof Error ? error.message : String(error);
127
+ if (exitCode == null) (0, __optique_run.printError)(__optique_core_message.message`Failed to generate command module: ${messageText}`);
128
+ else (0, __optique_run.printError)(__optique_core_message.message`Failed to generate command module: ${messageText}`, { exitCode });
129
+ }
130
+ function writeGeneratedMessage(count) {
131
+ const noun = count === 1 ? "command module" : "command modules";
132
+ console.log(`Generated ${count} ${noun}.`);
133
+ }
134
+ const isMain = require_main_check.isMainModuleUrl({
135
+ importMetaMain: "main" in import.meta ? void 0 : void 0,
136
+ moduleUrl: require("url").pathToFileURL(__filename).href,
137
+ argvEntry: node_process.default.argv[1]
138
+ });
139
+ if (isMain) main().catch((error) => {
140
+ printGenerationError(error, EXIT_GENERATION_FAILED);
141
+ });
142
+
143
+ //#endregion
144
+ exports.main = main;
package/dist/cli.d.cts ADDED
@@ -0,0 +1,10 @@
1
+ //#region src/cli.d.ts
2
+ /**
3
+ * Runs the optique-discover command-line interface.
4
+ *
5
+ * @returns A promise that resolves after generation or watch mode finishes.
6
+ * @since 1.2.0
7
+ */
8
+ declare function main(): Promise<void>;
9
+ //#endregion
10
+ export { main };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ //#region src/cli.d.ts
2
+ /**
3
+ * Runs the optique-discover command-line interface.
4
+ *
5
+ * @returns A promise that resolves after generation or watch mode finishes.
6
+ * @since 1.2.0
7
+ */
8
+ declare function main(): Promise<void>;
9
+ //#endregion
10
+ export { main };
package/dist/cli.js ADDED
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ import "./command-DO5zgkvS.js";
3
+ import "./src-Ci75oZo-.js";
4
+ import { watchCommandsModule, writeCommandsModule } from "./generator-C12pIuFe.js";
5
+ import { isMainModuleUrl } from "./main-check-mMnKfUZs.js";
6
+ import { object } from "@optique/core/constructs";
7
+ import { message } from "@optique/core/message";
8
+ import { multiple, optional, withDefault } from "@optique/core/modifiers";
9
+ import { argument, option } from "@optique/core/primitives";
10
+ import { defineProgram } from "@optique/core/program";
11
+ import { string } from "@optique/core/valueparser";
12
+ import { printError, runSync } from "@optique/run";
13
+ import process from "node:process";
14
+
15
+ //#region deno.json
16
+ var name = "@optique/discover";
17
+ var version = "1.2.0-dev.2180+f59f99d2";
18
+ var license = "MIT";
19
+ var exports = {
20
+ ".": "./src/index.ts",
21
+ "./command": "./src/command.ts",
22
+ "./generator": "./src/generator.ts",
23
+ "./cli": "./src/cli.ts"
24
+ };
25
+ var imports = { "#src/": "./src/" };
26
+ var exclude = [
27
+ "dist/",
28
+ "node_modules",
29
+ "tsdown.config.ts"
30
+ ];
31
+ var tasks = {
32
+ "build": "pnpm build",
33
+ "test": "deno test --allow-read --allow-write --allow-env",
34
+ "test:node": {
35
+ "dependencies": ["build"],
36
+ "command": "node --test"
37
+ },
38
+ "test:bun": {
39
+ "dependencies": ["build"],
40
+ "command": "bun test"
41
+ },
42
+ "test-all": { "dependencies": [
43
+ "test",
44
+ "test:node",
45
+ "test:bun"
46
+ ] }
47
+ };
48
+ var deno_default = {
49
+ name,
50
+ version,
51
+ license,
52
+ exports,
53
+ imports,
54
+ exclude,
55
+ tasks
56
+ };
57
+
58
+ //#endregion
59
+ //#region src/cli.ts
60
+ const EXIT_GENERATION_FAILED = 1;
61
+ const EXIT_INVALID_OPTIONS = 2;
62
+ const cliProgram = defineProgram({
63
+ parser: object({
64
+ dir: argument(string({ metavar: "DIR" }), { description: message`Directory containing command modules.` }),
65
+ outputFile: option("-o", "--output", string({ metavar: "FILE" }), { description: message`Generated TypeScript module path.` }),
66
+ base: optional(option("--base", string({ metavar: "PATH" }), { description: message`Module map base path passed to commandsFromModules().
67
+ Defaults to the command directory relative to the generated module.` })),
68
+ extensions: withDefault(multiple(option("--extension", string({ metavar: "EXT" }))), []),
69
+ entryFileName: optional(option("--entry-file-name", string({ metavar: "NAME" }), { description: message`File name that maps to the containing command path.
70
+ Defaults to commandsFromModules() behavior.` })),
71
+ noEntryFileName: option("--no-entry-file-name", { description: message`Treat entry files as ordinary command names.` }),
72
+ watch: option("--watch", { description: message`Regenerate when command files are added, removed,
73
+ or renamed.` })
74
+ }),
75
+ metadata: {
76
+ name: "optique-discover",
77
+ version: deno_default.version,
78
+ brief: message`Generate static command modules for @optique/discover`
79
+ }
80
+ });
81
+ /**
82
+ * Runs the optique-discover command-line interface.
83
+ *
84
+ * @returns A promise that resolves after generation or watch mode finishes.
85
+ * @since 1.2.0
86
+ */
87
+ async function main() {
88
+ const args = runSync(cliProgram, {
89
+ help: "option",
90
+ version: {
91
+ value: deno_default.version,
92
+ option: true
93
+ }
94
+ });
95
+ if (args.noEntryFileName && args.entryFileName != null) {
96
+ printError(message`Use either --entry-file-name or --no-entry-file-name, not both.`, { exitCode: EXIT_INVALID_OPTIONS });
97
+ return;
98
+ }
99
+ const options = {
100
+ dir: args.dir,
101
+ outputFile: args.outputFile,
102
+ ...args.base != null ? { base: args.base } : {},
103
+ ...args.extensions.length > 0 ? { extensions: args.extensions } : {},
104
+ ...args.noEntryFileName ? { entryFileName: false } : args.entryFileName != null ? { entryFileName: args.entryFileName } : {}
105
+ };
106
+ try {
107
+ if (args.watch) await watchCommandsModule({
108
+ ...options,
109
+ onGenerate(result) {
110
+ writeGeneratedMessage(result.files.length);
111
+ },
112
+ onError(error) {
113
+ printGenerationError(error);
114
+ }
115
+ });
116
+ else {
117
+ const result = await writeCommandsModule(options);
118
+ writeGeneratedMessage(result.files.length);
119
+ }
120
+ } catch (error) {
121
+ printGenerationError(error, EXIT_GENERATION_FAILED);
122
+ }
123
+ }
124
+ function printGenerationError(error, exitCode) {
125
+ const messageText = error instanceof Error ? error.message : String(error);
126
+ if (exitCode == null) printError(message`Failed to generate command module: ${messageText}`);
127
+ else printError(message`Failed to generate command module: ${messageText}`, { exitCode });
128
+ }
129
+ function writeGeneratedMessage(count) {
130
+ const noun = count === 1 ? "command module" : "command modules";
131
+ console.log(`Generated ${count} ${noun}.`);
132
+ }
133
+ const isMain = isMainModuleUrl({
134
+ importMetaMain: "main" in import.meta ? import.meta.main : void 0,
135
+ moduleUrl: import.meta.url,
136
+ argvEntry: process.argv[1]
137
+ });
138
+ if (isMain) main().catch((error) => {
139
+ printGenerationError(error, EXIT_GENERATION_FAILED);
140
+ });
141
+
142
+ //#endregion
143
+ export { main };
package/dist/command.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_command = require('./command-B0lV6NBO.cjs');
1
+ const require_command = require('./command-CUn2_NIA.cjs');
2
2
 
3
3
  exports.defineCommand = require_command.defineCommand;
4
4
  exports.isCommand = require_command.isCommand;
package/dist/command.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { AnyCommand, AnyStaticCommand, Command, CommandDefinition, CommandMetadata, CommandPath, StaticCommand, defineCommand, isCommand } from "./command-DyiVIMUh.js";
1
+ import { AnyCommand, AnyStaticCommand, Command, CommandDefinition, CommandMetadata, CommandPath, StaticCommand, defineCommand, isCommand } from "./command-DrmNW0HO.js";
2
2
  export { AnyCommand, AnyStaticCommand, Command, CommandDefinition, CommandMetadata, CommandPath, StaticCommand, defineCommand, isCommand };
@@ -0,0 +1,270 @@
1
+ import { getDefaultExtensions } from "./src-Ci75oZo-.js";
2
+ import { mkdir, readdir, realpath, stat, writeFile } from "node:fs/promises";
3
+ import { dirname, posix, relative, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ //#region src/generator.ts
7
+ /**
8
+ * Generates a TypeScript module that exports command entries.
9
+ *
10
+ * @param options Generation options.
11
+ * @returns The generated source code and command module metadata.
12
+ * @throws {TypeError} If options are invalid or no command modules are found.
13
+ * @since 1.2.0
14
+ */
15
+ async function generateCommandsModule(options) {
16
+ const normalized = normalizeGenerateOptions(options);
17
+ const files = await collectGeneratedCommandFiles(normalized);
18
+ return generateCommandsModuleFromFiles(normalized, files);
19
+ }
20
+ /**
21
+ * Writes a generated command module to disk.
22
+ *
23
+ * @param options Generation options.
24
+ * @returns The generated source code and command module metadata.
25
+ * @throws {TypeError} If options are invalid or no command modules are found.
26
+ * @throws {Error} If the generated module cannot be written.
27
+ * @since 1.2.0
28
+ */
29
+ async function writeCommandsModule(options) {
30
+ const result = await generateCommandsModule(options);
31
+ await mkdir(dirname(pathFromFile(options.outputFile)), { recursive: true });
32
+ await writeFile(pathFromFile(options.outputFile), result.code, "utf-8");
33
+ return result;
34
+ }
35
+ /**
36
+ * Watches the command file set and rewrites the generated module when it
37
+ * changes.
38
+ *
39
+ * Content-only edits do not trigger regeneration because they do not change
40
+ * the static module map.
41
+ *
42
+ * @param options Watch options.
43
+ * @returns A promise that resolves when the watch signal is aborted.
44
+ * @throws {TypeError} If options are invalid.
45
+ * @since 1.2.0
46
+ */
47
+ async function watchCommandsModule(options) {
48
+ const normalized = normalizeGenerateOptions(options);
49
+ const intervalMs = options.intervalMs ?? 250;
50
+ if (!Number.isInteger(intervalMs) || intervalMs < 1) throw new TypeError(`Watch interval must be a positive integer: ${intervalMs}`);
51
+ let previousSignature;
52
+ while (options.signal?.aborted !== true) {
53
+ try {
54
+ const files = await collectGeneratedCommandFiles(normalized, { allowEmpty: true });
55
+ const signature = files.map((file) => file.filePath).join("\0");
56
+ if (signature !== previousSignature) {
57
+ const result = generateCommandsModuleFromFiles(normalized, files);
58
+ await mkdir(dirname(normalized.outputFile), { recursive: true });
59
+ await writeFile(normalized.outputFile, result.code, "utf-8");
60
+ options.onGenerate?.(result);
61
+ previousSignature = signature;
62
+ }
63
+ } catch (error) {
64
+ options.onError?.(error);
65
+ }
66
+ await delay(intervalMs, options.signal);
67
+ }
68
+ }
69
+ async function collectGeneratedCommandFiles(options, collectOptions = {}) {
70
+ const filePaths = await collectCommandFiles(options.dir, options.extensions, options.outputFile);
71
+ if (filePaths.length < 1) {
72
+ if (collectOptions.allowEmpty === true) return [];
73
+ throw new TypeError(`No command modules found in ${options.dir}.`);
74
+ }
75
+ const files = filePaths.map((filePath, index) => {
76
+ const importSpecifier = relativeImportSpecifier(options.outputDir, filePath);
77
+ const defaultModulePath = relativeModuleSpecifier(options.outputDir, filePath);
78
+ const modulePath = options.baseWasProvided ? joinModulePath(options.base, normalizeRelativePath(relative(options.dir, filePath))) : defaultModulePath;
79
+ return {
80
+ filePath,
81
+ importSpecifier,
82
+ modulePath,
83
+ identifier: `cmd${index}`
84
+ };
85
+ });
86
+ rejectDuplicateCommandPaths(files, options);
87
+ return files;
88
+ }
89
+ function generateCommandsModuleFromFiles(options, files) {
90
+ if (files.length < 1) return {
91
+ code: "import type { ModuleCommand } from \"@optique/discover\";\n\nexport default [] satisfies readonly ModuleCommand[];\n",
92
+ files
93
+ };
94
+ const imports = files.map(formatCommandModuleImport).join("\n");
95
+ const entries = files.map((file) => ` ${JSON.stringify(file.modulePath)}: ${file.identifier},`).join("\n");
96
+ const optionEntries = [
97
+ ` base: ${JSON.stringify(options.base)},`,
98
+ ` extensions: ${formatStringArray(options.extensions)},`,
99
+ ...options.entryFileName === void 0 ? [] : [` entryFileName: ${JSON.stringify(options.entryFileName)},`]
100
+ ].join("\n");
101
+ return {
102
+ code: `import { commandsFromModules } from "@optique/discover";\n${imports}\n\nexport default commandsFromModules(\n {\n${entries}\n },\n {\n${optionEntries}\n },\n);\n`,
103
+ files
104
+ };
105
+ }
106
+ function formatCommandModuleImport(file) {
107
+ const importSpecifier = JSON.stringify(file.importSpecifier);
108
+ const importDeclaration = `import * as ${file.identifier} from ${importSpecifier};`;
109
+ if (requiresTypeScriptResolutionIgnore(file.importSpecifier)) return `// @ts-ignore: Percent-encoded import preserves URL-significant command paths.\n${importDeclaration}`;
110
+ return importDeclaration;
111
+ }
112
+ function requiresTypeScriptResolutionIgnore(specifier) {
113
+ return specifier.includes("%");
114
+ }
115
+ function normalizeGenerateOptions(options) {
116
+ const dir = resolve(pathFromFile(options.dir));
117
+ const outputFile = resolve(pathFromFile(options.outputFile));
118
+ const outputDir = dirname(outputFile);
119
+ const baseWasProvided = options.base !== void 0;
120
+ const base = normalizeBase(options.base ?? relativeModuleSpecifier(outputDir, dir));
121
+ return {
122
+ dir,
123
+ outputFile,
124
+ outputDir,
125
+ base,
126
+ baseWasProvided,
127
+ extensions: normalizeExtensions(options.extensions ?? getDefaultExtensions()),
128
+ entryFileName: normalizeEntryFileName(options.entryFileName)
129
+ };
130
+ }
131
+ function pathFromFile(path) {
132
+ return typeof path === "string" ? path : fileURLToPath(path);
133
+ }
134
+ function normalizeBase(base) {
135
+ if (typeof base !== "string" || base.length < 1) throw new TypeError(`Module base path must be a non-empty string: ${base}`);
136
+ return normalizeModulePath(base);
137
+ }
138
+ function normalizeExtensions(extensions) {
139
+ if (extensions.length < 1) throw new TypeError("At least one command file extension is required.");
140
+ const normalized = [];
141
+ for (const extension of extensions) {
142
+ if (!extension.startsWith(".") || extension.length < 2) throw new TypeError(`Command file extension must start with a dot: ${extension}`);
143
+ if (!normalized.includes(extension)) normalized.push(extension);
144
+ }
145
+ return normalized.toSorted((a, b) => b.length - a.length || a.localeCompare(b));
146
+ }
147
+ function normalizeEntryFileName(entryFileName) {
148
+ if (entryFileName === void 0) return void 0;
149
+ if (entryFileName === false) return false;
150
+ if (typeof entryFileName !== "string" || entryFileName.length < 1 || entryFileName.includes("/") || entryFileName.includes("\\")) throw new TypeError(`Command entry file name must be a non-empty file name: ${entryFileName}`);
151
+ return entryFileName;
152
+ }
153
+ async function collectCommandFiles(dir, extensions, excludedFile, activeDirs = /* @__PURE__ */ new Set()) {
154
+ const canonicalDir = await realpath(dir);
155
+ if (activeDirs.has(canonicalDir)) return [];
156
+ const nextActiveDirs = new Set(activeDirs);
157
+ nextActiveDirs.add(canonicalDir);
158
+ const entries = await readdir(dir, { withFileTypes: true });
159
+ const files = [];
160
+ for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
161
+ const path = resolve(dir, entry.name);
162
+ const entryType = await getCommandFileEntryType(path, entry);
163
+ if (entryType === "directory") files.push(...await collectCommandFiles(path, extensions, excludedFile, nextActiveDirs));
164
+ else if (entryType === "file" && path !== excludedFile && !isDeclarationFile(entry.name) && extensions.some((ext) => entry.name.endsWith(ext))) files.push(path);
165
+ }
166
+ return files;
167
+ }
168
+ async function getCommandFileEntryType(path, entry) {
169
+ if (entry.isDirectory()) return "directory";
170
+ if (entry.isFile()) return "file";
171
+ if (!entry.isSymbolicLink()) return void 0;
172
+ try {
173
+ const target = await stat(path);
174
+ if (target.isDirectory()) return "directory";
175
+ if (target.isFile()) return "file";
176
+ return void 0;
177
+ } catch {
178
+ return void 0;
179
+ }
180
+ }
181
+ function isDeclarationFile(fileName) {
182
+ return /\.d\.[cm]?ts$/.test(fileName);
183
+ }
184
+ function rejectDuplicateCommandPaths(files, options) {
185
+ const entryFileName = options.entryFileName ?? "index";
186
+ const seen = /* @__PURE__ */ new Map();
187
+ for (const file of files) {
188
+ const commandPath = commandPathFromModulePath(options.base, file.modulePath, options.extensions, entryFileName);
189
+ const key = commandPath.join("\0");
190
+ const previous = seen.get(key);
191
+ if (previous != null) throw new TypeError(`Duplicate command path "${displayCommandPath(commandPath)}" from ${previous} and ${file.modulePath}.`);
192
+ seen.set(key, file.modulePath);
193
+ }
194
+ }
195
+ function commandPathFromModulePath(base, modulePath, extensions, entryFileName) {
196
+ const withoutExtension = stripCommandExtension(modulePath, extensions);
197
+ const relativePath = relativeModulePath(base, withoutExtension, modulePath);
198
+ const path = relativePath.split("/").filter((segment) => segment.length > 0);
199
+ return commandPathFromSegments(path, modulePath, entryFileName);
200
+ }
201
+ function stripCommandExtension(path, extensions) {
202
+ const matchedExtension = extensions.find((ext) => path.endsWith(ext));
203
+ if (matchedExtension == null) throw new TypeError(`No configured extension matches ${path}.`);
204
+ return path.slice(0, -matchedExtension.length);
205
+ }
206
+ function relativeModulePath(base, modulePath, originalModulePath) {
207
+ const normalizedBase = normalizeDerivedModulePath(base);
208
+ const normalizedPath = normalizeDerivedModulePath(modulePath);
209
+ const relativePath = posix.relative(normalizedBase, normalizedPath);
210
+ if (relativePath.length < 1 || relativePath === ".." || relativePath.startsWith("../") || posix.isAbsolute(relativePath)) throw new TypeError(`Module path ${originalModulePath} is not under base path ${base}.`);
211
+ return relativePath;
212
+ }
213
+ function commandPathFromSegments(path, source, entryFileName) {
214
+ if (path.length < 1) throw new TypeError(`Command module ${source} does not define a path.`);
215
+ if (entryFileName !== false && path[path.length - 1] === entryFileName) return path.slice(0, -1);
216
+ return path;
217
+ }
218
+ function displayCommandPath(path) {
219
+ return path.length < 1 ? "<root>" : path.join(" ");
220
+ }
221
+ function relativeModuleSpecifier(fromDir, target) {
222
+ const relativePath = normalizeRelativePath(relative(fromDir, target));
223
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
224
+ }
225
+ function relativeImportSpecifier(fromDir, target) {
226
+ const specifier = relativeModuleSpecifier(fromDir, target);
227
+ if (requiresEncodedImportSpecifier(specifier)) return encodeImportSpecifier(specifier);
228
+ return specifier;
229
+ }
230
+ function requiresEncodedImportSpecifier(specifier) {
231
+ return specifier.includes("#") || specifier.includes("?") || specifier.includes("%");
232
+ }
233
+ function encodeImportSpecifier(specifier) {
234
+ return specifier.split("/").map((segment) => encodeURIComponent(segment)).join("/");
235
+ }
236
+ function normalizeRelativePath(path) {
237
+ return path.replaceAll("\\", "/");
238
+ }
239
+ function normalizeModulePath(path) {
240
+ return path.replaceAll("\\", "/");
241
+ }
242
+ function normalizeDerivedModulePath(path) {
243
+ return posix.normalize(normalizeModulePath(path));
244
+ }
245
+ function joinModulePath(base, relativePath) {
246
+ if (base === "." || base === "./") return `./${relativePath}`;
247
+ const prefix = base.endsWith("/") ? base.slice(0, -1) : base;
248
+ return `${prefix}/${relativePath}`;
249
+ }
250
+ function formatStringArray(values) {
251
+ return `[${values.map((value) => JSON.stringify(value)).join(", ")}]`;
252
+ }
253
+ function delay(ms, signal) {
254
+ if (signal?.aborted === true) return Promise.resolve();
255
+ return new Promise((resolve$1) => {
256
+ const onTimeout = () => {
257
+ signal?.removeEventListener("abort", onAbort);
258
+ resolve$1();
259
+ };
260
+ const onAbort = () => {
261
+ clearTimeout(timeout);
262
+ resolve$1();
263
+ };
264
+ const timeout = setTimeout(onTimeout, ms);
265
+ signal?.addEventListener("abort", onAbort, { once: true });
266
+ });
267
+ }
268
+
269
+ //#endregion
270
+ export { generateCommandsModule, watchCommandsModule, writeCommandsModule };