@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.
- package/README.md +195 -0
- package/cli.ts +9 -0
- package/dist/chunk-2HJDWSDA.js +19 -0
- package/dist/chunk-2HJDWSDA.js.map +1 -0
- package/dist/chunk-C4IL52NB.js +2 -0
- package/dist/chunk-C4IL52NB.js.map +1 -0
- package/dist/chunk-CKAS75E6.js +67 -0
- package/dist/chunk-CKAS75E6.js.map +1 -0
- package/dist/chunk-IIOHHU2E.js +48 -0
- package/dist/chunk-IIOHHU2E.js.map +1 -0
- package/dist/chunk-LLXEFSKN.js +69 -0
- package/dist/chunk-LLXEFSKN.js.map +1 -0
- package/dist/chunk-SNTKHUTX.js +29 -0
- package/dist/chunk-SNTKHUTX.js.map +1 -0
- package/dist/chunk-Z4RQIDDY.js +255 -0
- package/dist/chunk-Z4RQIDDY.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/src/BaseCommand.d.ts +21 -0
- package/dist/src/BaseCommand.js +10 -0
- package/dist/src/BaseCommand.js.map +1 -0
- package/dist/src/CLI.d.ts +23 -0
- package/dist/src/CLI.js +10 -0
- package/dist/src/CLI.js.map +1 -0
- package/dist/src/CommandInterface.d.ts +21 -0
- package/dist/src/CommandInterface.js +3 -0
- package/dist/src/CommandInterface.js.map +1 -0
- package/dist/src/CommandLoader.d.ts +20 -0
- package/dist/src/CommandLoader.js +9 -0
- package/dist/src/CommandLoader.js.map +1 -0
- package/dist/src/commands/help.d.ts +19 -0
- package/dist/src/commands/help.js +134 -0
- package/dist/src/commands/help.js.map +1 -0
- package/dist/src/utils/config.d.ts +7 -0
- package/dist/src/utils/config.js +13 -0
- package/dist/src/utils/config.js.map +1 -0
- package/dist/src/utils/logger.d.ts +6 -0
- package/dist/src/utils/logger.js +10 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/dist/src/utils/shell.d.ts +3 -0
- package/dist/src/utils/shell.js +9 -0
- package/dist/src/utils/shell.js.map +1 -0
- package/index.ts +5 -7
- package/package.json +7 -2
- package/src/utils/logger.ts +2 -2
- package/test/utils/integration-helpers.ts +1 -1
- package/tsconfig.json +2 -2
- 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,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 @@
|
|
|
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":[]}
|