@nexical/cli-core 0.1.0 → 0.1.2

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.
Files changed (52) hide show
  1. package/README.md +195 -0
  2. package/cli.ts +9 -0
  3. package/dist/chunk-2HJDWSDA.js +19 -0
  4. package/dist/chunk-2HJDWSDA.js.map +1 -0
  5. package/dist/chunk-C4IL52NB.js +2 -0
  6. package/dist/chunk-C4IL52NB.js.map +1 -0
  7. package/dist/chunk-CKAS75E6.js +67 -0
  8. package/dist/chunk-CKAS75E6.js.map +1 -0
  9. package/dist/chunk-IIOHHU2E.js +48 -0
  10. package/dist/chunk-IIOHHU2E.js.map +1 -0
  11. package/dist/chunk-LLXEFSKN.js +69 -0
  12. package/dist/chunk-LLXEFSKN.js.map +1 -0
  13. package/dist/chunk-SNTKHUTX.js +29 -0
  14. package/dist/chunk-SNTKHUTX.js.map +1 -0
  15. package/dist/chunk-Z4RQIDDY.js +255 -0
  16. package/dist/chunk-Z4RQIDDY.js.map +1 -0
  17. package/dist/cli.d.ts +1 -0
  18. package/dist/cli.js +15 -0
  19. package/dist/cli.js.map +1 -0
  20. package/dist/index.d.ts +7 -0
  21. package/dist/index.js +25 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/src/BaseCommand.d.ts +21 -0
  24. package/dist/src/BaseCommand.js +10 -0
  25. package/dist/src/BaseCommand.js.map +1 -0
  26. package/dist/src/CLI.d.ts +23 -0
  27. package/dist/src/CLI.js +10 -0
  28. package/dist/src/CLI.js.map +1 -0
  29. package/dist/src/CommandInterface.d.ts +21 -0
  30. package/dist/src/CommandInterface.js +3 -0
  31. package/dist/src/CommandInterface.js.map +1 -0
  32. package/dist/src/CommandLoader.d.ts +20 -0
  33. package/dist/src/CommandLoader.js +9 -0
  34. package/dist/src/CommandLoader.js.map +1 -0
  35. package/dist/src/commands/help.d.ts +19 -0
  36. package/dist/src/commands/help.js +134 -0
  37. package/dist/src/commands/help.js.map +1 -0
  38. package/dist/src/utils/config.d.ts +7 -0
  39. package/dist/src/utils/config.js +13 -0
  40. package/dist/src/utils/config.js.map +1 -0
  41. package/dist/src/utils/logger.d.ts +6 -0
  42. package/dist/src/utils/logger.js +10 -0
  43. package/dist/src/utils/logger.js.map +1 -0
  44. package/dist/src/utils/shell.d.ts +3 -0
  45. package/dist/src/utils/shell.js +9 -0
  46. package/dist/src/utils/shell.js.map +1 -0
  47. package/index.ts +5 -7
  48. package/package.json +7 -2
  49. package/src/utils/logger.ts +2 -2
  50. package/test/utils/integration-helpers.ts +1 -1
  51. package/tsconfig.json +2 -2
  52. package/tsup.config.ts +1 -1
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # @nexical/cli-core
2
+
3
+ The core framework for building powerful, extensible Command Line Interfaces (CLIs) within the Nexical ecosystem.
4
+
5
+ This package provides the foundational architecture for specialized CLI toolsets, including command discovery, execution orchestration, and a class-based command pattern. It is designed to be **agnostic**, allowing it to be used as the backbone for other CLI tools that need a similar structure and extensibility.
6
+
7
+ ## Table of Contents
8
+
9
+ - [Features](#features)
10
+ - [Installation](#installation)
11
+ - [Usage](#usage)
12
+ - [Configuration](#configuration)
13
+ - [Directory Structure](#directory-structure)
14
+ - [Creating Commands](#creating-commands)
15
+ - [The BaseCommand](#the-basecommand)
16
+ - [Defining Arguments & Options](#defining-arguments--options)
17
+ - [Command Discovery Rules](#command-discovery-rules)
18
+ - [Architecture](#architecture)
19
+ - [License](#license)
20
+
21
+ ---
22
+
23
+ ## Features
24
+
25
+ * **Class-Based Architecture**: Build commands as TypeScript classes with inheritance and lifecycle methods.
26
+ * **Dynamic Discovery**: Automatically recursively finds and registers commands from specified directories.
27
+ * **Type-Safe Definitions**: Declarative definition of arguments and options.
28
+ * **Built-in Help**: Automatic generation of help text for commands and subcommands.
29
+ * **Configuration Support**: Aware of project-level configuration (e.g., `{command_name}.yml`).
30
+ * **Robust Error Handling**: Standardized error reporting and debug modes.
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ This package is typically used as a dependency within a specific CLI implementation (like `@astrical/cli`).
37
+
38
+ ```bash
39
+ npm install @nexical/cli-core
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Usage
45
+
46
+ To use the core framework, you need to instantiate the `CLI` class and start it. This is typically done in your CLI's entry point (e.g., `index.ts`).
47
+
48
+ ### Configuration
49
+
50
+ The `CLI` class accepts a `CLIConfig` object to customize behavior:
51
+
52
+ ```typescript
53
+ import { CLI } from '@nexical/cli-core';
54
+ import path from 'node:path';
55
+ import { fileURLToPath } from 'node:url';
56
+
57
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
58
+
59
+ const app = new CLI({
60
+ // 1. The name of your binary/command (displayed in help)
61
+ commandName: 'my-cli',
62
+
63
+ // 2. Directories to recursively search for command files
64
+ searchDirectories: [
65
+ path.resolve(__dirname, 'commands'),
66
+ // You can add multiple directories, e.g., for plugins
67
+ path.resolve(process.cwd(), 'plugins/commands')
68
+ ]
69
+ });
70
+
71
+ app.start();
72
+ ```
73
+
74
+ ### Directory Structure
75
+
76
+ A typical project using `@nexical/cli-core` looks like this:
77
+
78
+ ```
79
+ my-cli/
80
+ ├── package.json
81
+ ├── src/
82
+ │ ├── index.ts <-- Entry point (initializes CLI)
83
+ │ └── commands/ <-- Command files
84
+ │ ├── init.ts
85
+ │ ├── build.ts
86
+ │ └── module/ <-- Subcommands
87
+ │ ├── add.ts
88
+ │ └── list.ts
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Creating Commands
94
+
95
+ The core framework itself only includes a **Help** command. All functional commands must be implemented by consuming libraries.
96
+
97
+ ### The BaseCommand
98
+
99
+ All commands must extend the `BaseCommand` abstract class exported by the core.
100
+
101
+ ```typescript
102
+ // src/commands/greet.ts
103
+ import { BaseCommand } from '@nexical/cli-core';
104
+
105
+ export default class GreetCommand extends BaseCommand {
106
+ // Description shown in help
107
+ static description = 'Greets the user';
108
+
109
+ // Implement the run method
110
+ async run(options: any) {
111
+ this.log('Hello from my-cli!');
112
+ }
113
+ }
114
+ ```
115
+
116
+ ### Defining Arguments & Options
117
+
118
+ You can define arguments and options using the static `args` property.
119
+
120
+ ```typescript
121
+ export default class GreetCommand extends BaseCommand {
122
+ static description = 'Greets the user with a custom message';
123
+
124
+ static args = {
125
+ // Positional arguments
126
+ args: [
127
+ {
128
+ name: 'name',
129
+ required: false,
130
+ description: 'Name to greet',
131
+ default: 'World'
132
+ }
133
+ ],
134
+ // Flags/Options
135
+ options: [
136
+ {
137
+ name: '--shout',
138
+ description: 'Print in uppercase',
139
+ default: false
140
+ },
141
+ {
142
+ name: '--count <n>',
143
+ description: 'Number of times to greet',
144
+ default: 1
145
+ }
146
+ ]
147
+ };
148
+
149
+ async run(options: any) {
150
+ // 'name' comes from args (mapped to options by name)
151
+ // 'shout' and 'count' come from options
152
+ const { name, shout, count } = options;
153
+
154
+ const message = `Hello, ${name}!`;
155
+ const finalMessage = shout ? message.toUpperCase() : message;
156
+
157
+ for (let i = 0; i < count; i++) {
158
+ this.log(finalMessage);
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ ### Command Discovery Rules
165
+
166
+ The `CommandLoader` uses the file structure to determine command names:
167
+
168
+ * **File Name = Command Name**:
169
+ * `commands/build.ts` -> `my-cli build`
170
+ * **Nested Directories = Subcommands**:
171
+ * `commands/user/create.ts` -> `my-cli user create`
172
+ * **Index Files = Parent Command**:
173
+ * `commands/user/index.ts` -> `my-cli user` (The handler for the root `user` command)
174
+
175
+ > **Note**: A file must default export a class extending `BaseCommand` to be registered.
176
+
177
+ ---
178
+
179
+ ## Architecture
180
+
181
+ The core is built around three main components:
182
+
183
+ 1. **`CLI`**: The main entry point. It wraps [CAC](https://github.com/cacjs/cac) to handle argument parsing and acts as the dependency injection container for commands.
184
+ 2. **`CommandLoader`**: Scans the filesystem for command files. It handles importing typescript files and validating that they export a valid command class.
185
+ 3. **`BaseCommand`**: Provides the interface for commands, including:
186
+ * `init()`: Async initialization hook (pre-run).
187
+ * `run()`: The main execution logic.
188
+ * `this.projectRoot`: Automatically resolved path to the project root (if running in a project context).
189
+ * Logging helpers (`this.success`, `this.warn`, `this.error`).
190
+
191
+ ---
192
+
193
+ ## License
194
+
195
+ Apache-2.0
package/cli.ts ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { CLI } from './src/CLI.js';
3
+
4
+ import { logger } from './src/utils/logger.js';
5
+
6
+ logger.debug('CLI ENTRY POINT HIT', process.argv);
7
+
8
+ const app = new CLI();
9
+ app.start();
@@ -0,0 +1,19 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+
3
+ // src/utils/logger.ts
4
+ import { consola, LogLevels } from "consola";
5
+ var logger = consola.create({
6
+ defaults: {
7
+ tag: "CLI"
8
+ },
9
+ level: LogLevels.info
10
+ });
11
+ function setDebugMode(enabled) {
12
+ logger.level = enabled ? LogLevels.debug : LogLevels.info;
13
+ }
14
+
15
+ export {
16
+ logger,
17
+ setDebugMode
18
+ };
19
+ //# sourceMappingURL=chunk-2HJDWSDA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/logger.ts"],"sourcesContent":["import { consola, LogLevels } from 'consola';\n\nexport const logger = consola.create({\n defaults: {\n tag: 'CLI',\n },\n level: LogLevels.info\n});\n\nexport function setDebugMode(enabled: boolean) {\n logger.level = enabled ? LogLevels.debug : LogLevels.info;\n}\n"],"mappings":";;;AAAA,SAAS,SAAS,iBAAiB;AAE5B,IAAM,SAAS,QAAQ,OAAO;AAAA,EACjC,UAAU;AAAA,IACN,KAAK;AAAA,EACT;AAAA,EACA,OAAO,UAAU;AACrB,CAAC;AAEM,SAAS,aAAa,SAAkB;AAC3C,SAAO,QAAQ,UAAU,UAAU,QAAQ,UAAU;AACzD;","names":[]}
@@ -0,0 +1,2 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ //# sourceMappingURL=chunk-C4IL52NB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,67 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ findProjectRoot,
4
+ loadConfig
5
+ } from "./chunk-IIOHHU2E.js";
6
+ import {
7
+ logger
8
+ } from "./chunk-2HJDWSDA.js";
9
+
10
+ // src/BaseCommand.ts
11
+ import process from "process";
12
+ var BaseCommand = class {
13
+ static usage = "";
14
+ static description = "";
15
+ static args = {};
16
+ // Configurable flags
17
+ static requiresProject = false;
18
+ projectRoot = null;
19
+ config = {};
20
+ globalOptions = {};
21
+ cli = null;
22
+ constructor(cli, globalOptions = {}) {
23
+ this.globalOptions = globalOptions;
24
+ this.cli = cli;
25
+ }
26
+ async init() {
27
+ if (this.globalOptions.rootDir) {
28
+ this.projectRoot = this.globalOptions.rootDir;
29
+ } else {
30
+ this.projectRoot = await findProjectRoot(this.cli.name, process.cwd());
31
+ }
32
+ const requiresProject = this.constructor.requiresProject;
33
+ if (requiresProject && !this.projectRoot) {
34
+ this.error("This command requires to be run within an app project (app.yml not found).", 1);
35
+ return;
36
+ }
37
+ if (this.projectRoot) {
38
+ this.config = await loadConfig(this.cli.name, this.projectRoot);
39
+ }
40
+ }
41
+ // Helpers
42
+ success(msg) {
43
+ logger.success(msg);
44
+ }
45
+ info(msg) {
46
+ logger.info(msg);
47
+ }
48
+ warn(msg) {
49
+ logger.warn(msg);
50
+ }
51
+ error(msg, code = 1) {
52
+ if (msg instanceof Error) {
53
+ logger.error(msg.message);
54
+ if (this.globalOptions.debug) {
55
+ logger.error(msg.stack);
56
+ }
57
+ } else {
58
+ logger.error(msg);
59
+ }
60
+ process.exit(code);
61
+ }
62
+ };
63
+
64
+ export {
65
+ BaseCommand
66
+ };
67
+ //# sourceMappingURL=chunk-CKAS75E6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/BaseCommand.ts"],"sourcesContent":["import { logger } from './utils/logger.js';\nimport { CommandDefinition, CommandInterface } from './CommandInterface.js';\nimport { findProjectRoot, loadConfig } from './utils/config.js';\nimport process from 'node:process';\n\nexport abstract class BaseCommand implements CommandInterface {\n static usage = '';\n static description = '';\n static args: CommandDefinition = {};\n\n // Configurable flags\n static requiresProject = false;\n\n protected projectRoot: string | null = null;\n protected config: any = {};\n protected globalOptions: any = {};\n protected cli: any = null;\n\n\n constructor(cli: any, globalOptions: any = {}) {\n this.globalOptions = globalOptions;\n this.cli = cli;\n }\n\n async init() {\n // 1. Root detection strategy\n if (this.globalOptions.rootDir) {\n this.projectRoot = this.globalOptions.rootDir;\n } else {\n this.projectRoot = await findProjectRoot(this.cli.name, process.cwd());\n }\n\n const requiresProject = (this.constructor as any).requiresProject;\n\n if (requiresProject && !this.projectRoot) {\n this.error('This command requires to be run within an app project (app.yml not found).', 1);\n return; // TS doesn't know error exits\n }\n\n if (this.projectRoot) {\n this.config = await loadConfig(this.cli.name, this.projectRoot);\n // logger.debug(`Loaded config from ${this.projectRoot}`);\n }\n }\n\n abstract run(options: any): Promise<void>;\n\n // Helpers\n success(msg: string) {\n logger.success(msg);\n }\n\n\n info(msg: string) {\n logger.info(msg);\n }\n\n warn(msg: string) {\n logger.warn(msg);\n }\n\n error(msg: string | Error, code = 1) {\n if (msg instanceof Error) {\n logger.error(msg.message);\n if (this.globalOptions.debug) {\n logger.error(msg.stack);\n }\n } else {\n logger.error(msg);\n }\n process.exit(code);\n }\n}\n"],"mappings":";;;;;;;;;;AAGA,OAAO,aAAa;AAEb,IAAe,cAAf,MAAuD;AAAA,EAC1D,OAAO,QAAQ;AAAA,EACf,OAAO,cAAc;AAAA,EACrB,OAAO,OAA0B,CAAC;AAAA;AAAA,EAGlC,OAAO,kBAAkB;AAAA,EAEf,cAA6B;AAAA,EAC7B,SAAc,CAAC;AAAA,EACf,gBAAqB,CAAC;AAAA,EACtB,MAAW;AAAA,EAGrB,YAAY,KAAU,gBAAqB,CAAC,GAAG;AAC3C,SAAK,gBAAgB;AACrB,SAAK,MAAM;AAAA,EACf;AAAA,EAEA,MAAM,OAAO;AAET,QAAI,KAAK,cAAc,SAAS;AAC5B,WAAK,cAAc,KAAK,cAAc;AAAA,IAC1C,OAAO;AACH,WAAK,cAAc,MAAM,gBAAgB,KAAK,IAAI,MAAM,QAAQ,IAAI,CAAC;AAAA,IACzE;AAEA,UAAM,kBAAmB,KAAK,YAAoB;AAElD,QAAI,mBAAmB,CAAC,KAAK,aAAa;AACtC,WAAK,MAAM,8EAA8E,CAAC;AAC1F;AAAA,IACJ;AAEA,QAAI,KAAK,aAAa;AAClB,WAAK,SAAS,MAAM,WAAW,KAAK,IAAI,MAAM,KAAK,WAAW;AAAA,IAElE;AAAA,EACJ;AAAA;AAAA,EAKA,QAAQ,KAAa;AACjB,WAAO,QAAQ,GAAG;AAAA,EACtB;AAAA,EAGA,KAAK,KAAa;AACd,WAAO,KAAK,GAAG;AAAA,EACnB;AAAA,EAEA,KAAK,KAAa;AACd,WAAO,KAAK,GAAG;AAAA,EACnB;AAAA,EAEA,MAAM,KAAqB,OAAO,GAAG;AACjC,QAAI,eAAe,OAAO;AACtB,aAAO,MAAM,IAAI,OAAO;AACxB,UAAI,KAAK,cAAc,OAAO;AAC1B,eAAO,MAAM,IAAI,KAAK;AAAA,MAC1B;AAAA,IACJ,OAAO;AACH,aAAO,MAAM,GAAG;AAAA,IACpB;AACA,YAAQ,KAAK,IAAI;AAAA,EACrB;AACJ;","names":[]}
@@ -0,0 +1,48 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ logger
4
+ } from "./chunk-2HJDWSDA.js";
5
+
6
+ // src/utils/config.ts
7
+ import { lilconfig } from "lilconfig";
8
+ import path from "path";
9
+ import YAML from "yaml";
10
+ var loadYaml = (filepath, content) => {
11
+ return YAML.parse(content);
12
+ };
13
+ async function findProjectRoot(commandName, startDir) {
14
+ const searchPlaces = [`${commandName}.yml`, `${commandName}.yaml`];
15
+ const explorer = lilconfig(commandName, {
16
+ searchPlaces,
17
+ loaders: {
18
+ ".yml": loadYaml,
19
+ ".yaml": loadYaml
20
+ }
21
+ });
22
+ const result = await explorer.search(startDir);
23
+ if (result) {
24
+ logger.debug(`Project root found at: ${path.dirname(result.filepath)}`);
25
+ return path.dirname(result.filepath);
26
+ }
27
+ return null;
28
+ }
29
+ async function loadConfig(commandName, rootDir) {
30
+ const searchPlaces = [`${commandName}.yml`, `${commandName}.yaml`];
31
+ const explorer = lilconfig(commandName, {
32
+ searchPlaces,
33
+ loaders: {
34
+ ".yml": loadYaml,
35
+ ".yaml": loadYaml
36
+ }
37
+ });
38
+ const result = await explorer.search(rootDir);
39
+ logger.debug(result ? `Loaded config from ${result.filepath}` : `No config found in ${rootDir}`);
40
+ return result ? result.config : {};
41
+ }
42
+
43
+ export {
44
+ loadYaml,
45
+ findProjectRoot,
46
+ loadConfig
47
+ };
48
+ //# sourceMappingURL=chunk-IIOHHU2E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/config.ts"],"sourcesContent":["import { lilconfig, type Loader } from 'lilconfig';\nimport path from 'node:path';\nimport YAML from 'yaml';\nimport { logger } from './logger.js';\n\nexport const loadYaml: Loader = (filepath, content) => {\n return YAML.parse(content);\n};\n\nexport async function findProjectRoot(commandName: string, startDir: string): Promise<string | null> {\n const searchPlaces = [`${commandName}.yml`, `${commandName}.yaml`];\n\n // We use lilconfig to find the file up the tree\n const explorer = lilconfig(commandName, {\n searchPlaces,\n loaders: {\n '.yml': loadYaml,\n '.yaml': loadYaml,\n }\n });\n\n const result = await explorer.search(startDir);\n if (result) {\n logger.debug(`Project root found at: ${path.dirname(result.filepath)}`);\n return path.dirname(result.filepath);\n }\n\n return null;\n}\n\nexport async function loadConfig(commandName: string, rootDir: string): Promise<any> {\n const searchPlaces = [`${commandName}.yml`, `${commandName}.yaml`];\n const explorer = lilconfig(commandName, {\n searchPlaces,\n loaders: {\n '.yml': loadYaml,\n '.yaml': loadYaml,\n }\n });\n const result = await explorer.search(rootDir);\n logger.debug(result ? `Loaded config from ${result.filepath}` : `No config found in ${rootDir}`);\n return result ? result.config : {};\n}\n"],"mappings":";;;;;;AAAA,SAAS,iBAA8B;AACvC,OAAO,UAAU;AACjB,OAAO,UAAU;AAGV,IAAM,WAAmB,CAAC,UAAU,YAAY;AACnD,SAAO,KAAK,MAAM,OAAO;AAC7B;AAEA,eAAsB,gBAAgB,aAAqB,UAA0C;AACjG,QAAM,eAAe,CAAC,GAAG,WAAW,QAAQ,GAAG,WAAW,OAAO;AAGjE,QAAM,WAAW,UAAU,aAAa;AAAA,IACpC;AAAA,IACA,SAAS;AAAA,MACL,QAAQ;AAAA,MACR,SAAS;AAAA,IACb;AAAA,EACJ,CAAC;AAED,QAAM,SAAS,MAAM,SAAS,OAAO,QAAQ;AAC7C,MAAI,QAAQ;AACR,WAAO,MAAM,0BAA0B,KAAK,QAAQ,OAAO,QAAQ,CAAC,EAAE;AACtE,WAAO,KAAK,QAAQ,OAAO,QAAQ;AAAA,EACvC;AAEA,SAAO;AACX;AAEA,eAAsB,WAAW,aAAqB,SAA+B;AACjF,QAAM,eAAe,CAAC,GAAG,WAAW,QAAQ,GAAG,WAAW,OAAO;AACjE,QAAM,WAAW,UAAU,aAAa;AAAA,IACpC;AAAA,IACA,SAAS;AAAA,MACL,QAAQ;AAAA,MACR,SAAS;AAAA,IACb;AAAA,EACJ,CAAC;AACD,QAAM,SAAS,MAAM,SAAS,OAAO,OAAO;AAC5C,SAAO,MAAM,SAAS,sBAAsB,OAAO,QAAQ,KAAK,sBAAsB,OAAO,EAAE;AAC/F,SAAO,SAAS,OAAO,SAAS,CAAC;AACrC;","names":[]}
@@ -0,0 +1,69 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ logger
4
+ } from "./chunk-2HJDWSDA.js";
5
+
6
+ // src/CommandLoader.ts
7
+ import fs from "fs";
8
+ import path from "path";
9
+ var CommandLoader = class {
10
+ cli = null;
11
+ commands = [];
12
+ importer;
13
+ constructor(cli, importer = (p) => import(p)) {
14
+ this.cli = cli;
15
+ this.importer = importer;
16
+ }
17
+ getCommands() {
18
+ return this.commands;
19
+ }
20
+ async load(commandsDir) {
21
+ logger.debug(`Loading commands from: ${commandsDir}`);
22
+ if (!fs.existsSync(commandsDir)) {
23
+ logger.debug(`Commands directory not found: ${commandsDir}`);
24
+ return [];
25
+ }
26
+ await this.scan(commandsDir, []);
27
+ return this.commands;
28
+ }
29
+ async scan(dir, prefix) {
30
+ const files = fs.readdirSync(dir);
31
+ for (const file of files) {
32
+ const fullPath = path.join(dir, file);
33
+ const stat = fs.statSync(fullPath);
34
+ if (stat.isDirectory()) {
35
+ await this.scan(fullPath, [...prefix, file]);
36
+ } else if ((file.endsWith(".ts") || file.endsWith(".js")) && !file.endsWith(".d.ts")) {
37
+ logger.debug(`Found potential command file: ${fullPath}`);
38
+ const name = path.basename(file, path.extname(file));
39
+ const commandParts = [...prefix];
40
+ if (name !== "index") {
41
+ commandParts.push(name);
42
+ } else if (commandParts.length === 0) {
43
+ continue;
44
+ }
45
+ try {
46
+ const module = await this.importer(fullPath);
47
+ const CommandClass = module.default;
48
+ if (CommandClass) {
49
+ const commandName = commandParts.join(" ");
50
+ logger.debug(`Registered command: ${commandName}`);
51
+ this.commands.push({
52
+ command: commandName,
53
+ path: fullPath,
54
+ instance: new CommandClass(this.cli),
55
+ class: CommandClass
56
+ });
57
+ }
58
+ } catch (e) {
59
+ logger.error(`Failed to load command at ${fullPath}`, e);
60
+ }
61
+ }
62
+ }
63
+ }
64
+ };
65
+
66
+ export {
67
+ CommandLoader
68
+ };
69
+ //# sourceMappingURL=chunk-LLXEFSKN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/CommandLoader.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { BaseCommand } from './BaseCommand.js';\nimport { logger } from './utils/logger.js';\n\nexport interface LoadedCommand {\n command: string;\n path: string;\n instance: BaseCommand;\n class: any;\n}\n\nexport class CommandLoader {\n private cli: any = null;\n private commands: LoadedCommand[] = [];\n private importer: (path: string) => Promise<any>;\n\n constructor(cli: any, importer: (path: string) => Promise<any> = (p) => import(p)) {\n this.cli = cli;\n this.importer = importer;\n }\n\n getCommands(): LoadedCommand[] {\n return this.commands;\n }\n\n async load(commandsDir: string): Promise<LoadedCommand[]> {\n logger.debug(`Loading commands from: ${commandsDir}`);\n if (!fs.existsSync(commandsDir)) {\n logger.debug(`Commands directory not found: ${commandsDir}`);\n return [];\n }\n\n await this.scan(commandsDir, []);\n return this.commands;\n }\n\n private async scan(dir: string, prefix: string[]) {\n const files = fs.readdirSync(dir);\n\n for (const file of files) {\n const fullPath = path.join(dir, file);\n const stat = fs.statSync(fullPath);\n\n if (stat.isDirectory()) {\n await this.scan(fullPath, [...prefix, file]);\n } else if ((file.endsWith('.ts') || file.endsWith('.js')) && !file.endsWith('.d.ts')) {\n // Ignore index files or non-command files if needed, but for now scan all.\n // Assuming \"index.ts\" might be the command for the directory path itself if we supported that,\n // but let's stick to \"create.ts\" -> \"create\"\n logger.debug(`Found potential command file: ${fullPath}`);\n\n const name = path.basename(file, path.extname(file));\n const commandParts = [...prefix];\n if (name !== 'index') {\n commandParts.push(name);\n } else if (commandParts.length === 0) {\n continue; // skip src/commands/index.ts if it exists and doesn't map to anything specific\n }\n\n // Import\n try {\n const module = await this.importer(fullPath);\n // Assume default export is the command class\n const CommandClass = module.default;\n\n if (CommandClass) { // Loose check for now to debug\n const commandName = commandParts.join(' ');\n logger.debug(`Registered command: ${commandName}`);\n this.commands.push({\n command: commandName,\n path: fullPath,\n instance: new CommandClass(this.cli),\n class: CommandClass\n });\n }\n } catch (e) {\n logger.error(`Failed to load command at ${fullPath}`, e);\n }\n }\n }\n }\n}\n"],"mappings":";;;;;;AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AAWV,IAAM,gBAAN,MAAoB;AAAA,EACf,MAAW;AAAA,EACX,WAA4B,CAAC;AAAA,EAC7B;AAAA,EAER,YAAY,KAAU,WAA2C,CAAC,MAAM,OAAO,IAAI;AAC/E,SAAK,MAAM;AACX,SAAK,WAAW;AAAA,EACpB;AAAA,EAEA,cAA+B;AAC3B,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,MAAM,KAAK,aAA+C;AACtD,WAAO,MAAM,0BAA0B,WAAW,EAAE;AACpD,QAAI,CAAC,GAAG,WAAW,WAAW,GAAG;AAC7B,aAAO,MAAM,iCAAiC,WAAW,EAAE;AAC3D,aAAO,CAAC;AAAA,IACZ;AAEA,UAAM,KAAK,KAAK,aAAa,CAAC,CAAC;AAC/B,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,MAAc,KAAK,KAAa,QAAkB;AAC9C,UAAM,QAAQ,GAAG,YAAY,GAAG;AAEhC,eAAW,QAAQ,OAAO;AACtB,YAAM,WAAW,KAAK,KAAK,KAAK,IAAI;AACpC,YAAM,OAAO,GAAG,SAAS,QAAQ;AAEjC,UAAI,KAAK,YAAY,GAAG;AACpB,cAAM,KAAK,KAAK,UAAU,CAAC,GAAG,QAAQ,IAAI,CAAC;AAAA,MAC/C,YAAY,KAAK,SAAS,KAAK,KAAK,KAAK,SAAS,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,GAAG;AAIlF,eAAO,MAAM,iCAAiC,QAAQ,EAAE;AAExD,cAAM,OAAO,KAAK,SAAS,MAAM,KAAK,QAAQ,IAAI,CAAC;AACnD,cAAM,eAAe,CAAC,GAAG,MAAM;AAC/B,YAAI,SAAS,SAAS;AAClB,uBAAa,KAAK,IAAI;AAAA,QAC1B,WAAW,aAAa,WAAW,GAAG;AAClC;AAAA,QACJ;AAGA,YAAI;AACA,gBAAM,SAAS,MAAM,KAAK,SAAS,QAAQ;AAE3C,gBAAM,eAAe,OAAO;AAE5B,cAAI,cAAc;AACd,kBAAM,cAAc,aAAa,KAAK,GAAG;AACzC,mBAAO,MAAM,uBAAuB,WAAW,EAAE;AACjD,iBAAK,SAAS,KAAK;AAAA,cACf,SAAS;AAAA,cACT,MAAM;AAAA,cACN,UAAU,IAAI,aAAa,KAAK,GAAG;AAAA,cACnC,OAAO;AAAA,YACX,CAAC;AAAA,UACL;AAAA,QACJ,SAAS,GAAG;AACR,iBAAO,MAAM,6BAA6B,QAAQ,IAAI,CAAC;AAAA,QAC3D;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACJ;","names":[]}
@@ -0,0 +1,29 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ logger
4
+ } from "./chunk-2HJDWSDA.js";
5
+
6
+ // src/utils/shell.ts
7
+ import { exec } from "child_process";
8
+ import { promisify } from "util";
9
+ var execAsync = promisify(exec);
10
+ async function runCommand(command, cwd) {
11
+ try {
12
+ logger.debug(`Executing command: ${command} in ${cwd || process.cwd()}`);
13
+ const { stdout } = await execAsync(command, { cwd });
14
+ if (stdout) {
15
+ console.log(stdout);
16
+ }
17
+ } catch (error) {
18
+ logger.error(`Command failed: ${command}`);
19
+ if (error.stderr) {
20
+ logger.error(error.stderr);
21
+ }
22
+ throw new Error(`Command failed: ${command}`);
23
+ }
24
+ }
25
+
26
+ export {
27
+ runCommand
28
+ };
29
+ //# sourceMappingURL=chunk-SNTKHUTX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/shell.ts"],"sourcesContent":["import { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { logger } from './logger.js';\n\nconst execAsync = promisify(exec);\n\nexport async function runCommand(command: string, cwd?: string): Promise<void> {\n try {\n logger.debug(`Executing command: ${command} in ${cwd || process.cwd()}`);\n const { stdout } = await execAsync(command, { cwd });\n if (stdout) {\n console.log(stdout);\n }\n } catch (error: any) {\n logger.error(`Command failed: ${command}`);\n if (error.stderr) {\n logger.error(error.stderr);\n }\n throw new Error(`Command failed: ${command}`);\n }\n}\n"],"mappings":";;;;;;AAAA,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAG1B,IAAM,YAAY,UAAU,IAAI;AAEhC,eAAsB,WAAW,SAAiB,KAA6B;AAC3E,MAAI;AACA,WAAO,MAAM,sBAAsB,OAAO,OAAO,OAAO,QAAQ,IAAI,CAAC,EAAE;AACvE,UAAM,EAAE,OAAO,IAAI,MAAM,UAAU,SAAS,EAAE,IAAI,CAAC;AACnD,QAAI,QAAQ;AACR,cAAQ,IAAI,MAAM;AAAA,IACtB;AAAA,EACJ,SAAS,OAAY;AACjB,WAAO,MAAM,mBAAmB,OAAO,EAAE;AACzC,QAAI,MAAM,QAAQ;AACd,aAAO,MAAM,MAAM,MAAM;AAAA,IAC7B;AACA,UAAM,IAAI,MAAM,mBAAmB,OAAO,EAAE;AAAA,EAChD;AACJ;","names":[]}