@nexical/cli-core 0.1.0 → 0.1.1
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-2NZ4H42M.js +255 -0
- package/dist/chunk-2NZ4H42M.js.map +1 -0
- package/dist/chunk-5YGASH25.js +48 -0
- package/dist/chunk-5YGASH25.js.map +1 -0
- package/dist/chunk-6LSNSDTL.js +67 -0
- package/dist/chunk-6LSNSDTL.js.map +1 -0
- package/dist/chunk-C4IL52NB.js +2 -0
- package/dist/chunk-C4IL52NB.js.map +1 -0
- package/dist/chunk-HETFF3FQ.js +20 -0
- package/dist/chunk-HETFF3FQ.js.map +1 -0
- package/dist/chunk-PFEYDXIW.js +69 -0
- package/dist/chunk-PFEYDXIW.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 +4 -0
- package/dist/index.js +16 -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 +28 -0
- package/dist/src/utils/shell.js.map +1 -0
- package/index.ts +3 -9
- package/package.json +7 -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,255 @@
|
|
|
1
|
+
import { createRequire } from "module"; const require = createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
CommandLoader
|
|
4
|
+
} from "./chunk-PFEYDXIW.js";
|
|
5
|
+
import {
|
|
6
|
+
logger,
|
|
7
|
+
setDebugMode
|
|
8
|
+
} from "./chunk-HETFF3FQ.js";
|
|
9
|
+
|
|
10
|
+
// src/CLI.ts
|
|
11
|
+
import { cac } from "cac";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
import pc from "picocolors";
|
|
16
|
+
|
|
17
|
+
// package.json
|
|
18
|
+
var package_default = {
|
|
19
|
+
name: "@nexical/cli-core",
|
|
20
|
+
version: "0.1.1",
|
|
21
|
+
type: "module",
|
|
22
|
+
main: "dist/index.js",
|
|
23
|
+
types: "dist/index.d.ts",
|
|
24
|
+
exports: {
|
|
25
|
+
".": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
scripts: {
|
|
28
|
+
build: "tsup",
|
|
29
|
+
dev: "tsup --watch",
|
|
30
|
+
cli: "node dist/cli.js",
|
|
31
|
+
test: "npm run test:unit && npm run test:integration && npm run test:e2e",
|
|
32
|
+
"test:unit": "vitest run --config vitest.config.ts --coverage",
|
|
33
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
34
|
+
"test:e2e": "npm run build && vitest run --config vitest.e2e.config.ts",
|
|
35
|
+
"test:watch": "vitest"
|
|
36
|
+
},
|
|
37
|
+
dependencies: {
|
|
38
|
+
cac: "^6.7.14",
|
|
39
|
+
consola: "^3.4.2",
|
|
40
|
+
lilconfig: "^3.1.3",
|
|
41
|
+
picocolors: "^1.1.1",
|
|
42
|
+
yaml: "^2.8.2",
|
|
43
|
+
zod: "^3.22.4"
|
|
44
|
+
},
|
|
45
|
+
devDependencies: {
|
|
46
|
+
"@types/fs-extra": "^11.0.4",
|
|
47
|
+
"@types/node": "^20.10.0",
|
|
48
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
49
|
+
execa: "^9.6.1",
|
|
50
|
+
"fs-extra": "^11.3.2",
|
|
51
|
+
tsup: "^8.0.1",
|
|
52
|
+
typescript: "^5.3.3",
|
|
53
|
+
vitest: "^4.0.15"
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/CLI.ts
|
|
58
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
59
|
+
var CLI = class {
|
|
60
|
+
name;
|
|
61
|
+
cli;
|
|
62
|
+
loader;
|
|
63
|
+
HelpCommandClass;
|
|
64
|
+
config;
|
|
65
|
+
constructor(config = {}) {
|
|
66
|
+
this.config = config;
|
|
67
|
+
this.name = this.config.commandName || "app";
|
|
68
|
+
this.cli = cac(this.name);
|
|
69
|
+
this.loader = new CommandLoader(this);
|
|
70
|
+
}
|
|
71
|
+
loadedCommands = [];
|
|
72
|
+
getCommands() {
|
|
73
|
+
return this.loadedCommands;
|
|
74
|
+
}
|
|
75
|
+
getRawCLI() {
|
|
76
|
+
return this.cli;
|
|
77
|
+
}
|
|
78
|
+
async start() {
|
|
79
|
+
if (process.argv.includes("--debug")) {
|
|
80
|
+
setDebugMode(true);
|
|
81
|
+
logger.debug("Debug mode enabled via --debug flag");
|
|
82
|
+
}
|
|
83
|
+
let commandsDirs = [];
|
|
84
|
+
if (this.config.searchDirectories && this.config.searchDirectories.length > 0) {
|
|
85
|
+
commandsDirs = this.config.searchDirectories;
|
|
86
|
+
} else {
|
|
87
|
+
const possibleDirs = [
|
|
88
|
+
path.resolve(__dirname, "./src/commands"),
|
|
89
|
+
path.resolve(process.cwd(), "commands")
|
|
90
|
+
// Fallback relative to cwd?
|
|
91
|
+
];
|
|
92
|
+
for (const dir of possibleDirs) {
|
|
93
|
+
if (fs.existsSync(dir)) {
|
|
94
|
+
commandsDirs.push(dir);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (commandsDirs.length === 0) {
|
|
100
|
+
logger.debug("No commands directory found.");
|
|
101
|
+
}
|
|
102
|
+
for (const dir of commandsDirs) {
|
|
103
|
+
await this.loader.load(dir);
|
|
104
|
+
}
|
|
105
|
+
this.loadedCommands = this.loader.getCommands();
|
|
106
|
+
const commandGroups = {};
|
|
107
|
+
const helpCmd = this.loadedCommands.find((c) => c.command === "help");
|
|
108
|
+
if (helpCmd) {
|
|
109
|
+
this.HelpCommandClass = helpCmd.class;
|
|
110
|
+
}
|
|
111
|
+
for (const cmd of this.loadedCommands) {
|
|
112
|
+
const root = cmd.command.split(" ")[0];
|
|
113
|
+
if (!commandGroups[root]) commandGroups[root] = [];
|
|
114
|
+
commandGroups[root].push(cmd);
|
|
115
|
+
}
|
|
116
|
+
for (const [root, cmds] of Object.entries(commandGroups)) {
|
|
117
|
+
if (cmds.length === 1 && cmds[0].command === root) {
|
|
118
|
+
const cmd = cmds[0];
|
|
119
|
+
const CommandClass = cmd.class;
|
|
120
|
+
let commandName = cmd.command;
|
|
121
|
+
const argsDef = CommandClass.args || {};
|
|
122
|
+
if (argsDef.args) {
|
|
123
|
+
argsDef.args.forEach((arg) => {
|
|
124
|
+
const isVariadic = arg.name.endsWith("...");
|
|
125
|
+
const cleanName = isVariadic ? arg.name.slice(0, -3) : arg.name;
|
|
126
|
+
commandName += isVariadic ? ` [...${cleanName}]` : ` [${cleanName}]`;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const cacCommand = this.cli.command(commandName, CommandClass.description || "");
|
|
130
|
+
if (argsDef.options) {
|
|
131
|
+
argsDef.options.forEach((opt) => {
|
|
132
|
+
cacCommand.option(opt.name, opt.description, { default: opt.default });
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
this.registerGlobalOptions(cacCommand);
|
|
136
|
+
cacCommand.action(async (...args) => {
|
|
137
|
+
const options = args.pop();
|
|
138
|
+
if (options.help) {
|
|
139
|
+
await this.runHelp([cmd.command]);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const positionalArgs = args;
|
|
143
|
+
if (argsDef.args) {
|
|
144
|
+
argsDef.args.forEach((arg, index) => {
|
|
145
|
+
const isVariadic = arg.name.endsWith("...");
|
|
146
|
+
const name = isVariadic ? arg.name.slice(0, -3) : arg.name;
|
|
147
|
+
const val = positionalArgs[index];
|
|
148
|
+
if (val !== void 0) {
|
|
149
|
+
options[name] = val;
|
|
150
|
+
} else if (arg.required) {
|
|
151
|
+
console.error(pc.red(`Missing required argument: ${name}`));
|
|
152
|
+
this.runHelp([cmd.command]).then(() => process.exit(1));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
await this.runCommand(CommandClass, options, [cmd.command]);
|
|
158
|
+
});
|
|
159
|
+
} else {
|
|
160
|
+
const commandName = `${root} [subcommand] [...args]`;
|
|
161
|
+
const cacCommand = this.cli.command(commandName, `Manage ${root} commands`);
|
|
162
|
+
cacCommand.allowUnknownOptions();
|
|
163
|
+
this.registerGlobalOptions(cacCommand);
|
|
164
|
+
cacCommand.action(async (subcommand, ...args) => {
|
|
165
|
+
const options = args.pop();
|
|
166
|
+
if (!subcommand || options.help) {
|
|
167
|
+
await this.runHelp([root, subcommand].filter(Boolean));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const fullCommandName = `${root} ${subcommand}`;
|
|
171
|
+
const cmd = cmds.find((c) => c.command === fullCommandName);
|
|
172
|
+
if (!cmd) {
|
|
173
|
+
console.error(pc.red(`Unknown subcommand '${subcommand}' for '${root}'`));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
const CommandClass = cmd.class;
|
|
177
|
+
const argsDef = CommandClass.args || {};
|
|
178
|
+
const positionalArgs = args.length > 0 && Array.isArray(args[0]) ? args[0] : args;
|
|
179
|
+
const childOptions = { ...options };
|
|
180
|
+
const cmdParts = [root, subcommand];
|
|
181
|
+
if (argsDef.args) {
|
|
182
|
+
argsDef.args.forEach((arg, index) => {
|
|
183
|
+
const isVariadic = arg.name.endsWith("...");
|
|
184
|
+
const name = isVariadic ? arg.name.slice(0, -3) : arg.name;
|
|
185
|
+
const val = positionalArgs[index];
|
|
186
|
+
if (val !== void 0) {
|
|
187
|
+
if (isVariadic) {
|
|
188
|
+
childOptions[name] = positionalArgs.slice(index);
|
|
189
|
+
} else {
|
|
190
|
+
childOptions[name] = val;
|
|
191
|
+
}
|
|
192
|
+
} else if (arg.required) {
|
|
193
|
+
console.error(pc.red(`Missing required argument: ${name}`));
|
|
194
|
+
this.runHelp(cmdParts).then(() => process.exit(1));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
await this.runCommand(CommandClass, childOptions, cmdParts);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
this.cli.option("--help, -h", "Display help");
|
|
204
|
+
this.cli.version(package_default.version);
|
|
205
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
206
|
+
const args = process.argv.slice(2).filter((a) => !a.startsWith("-"));
|
|
207
|
+
if (args.length === 0) {
|
|
208
|
+
await this.runHelp([]);
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
this.cli.parse();
|
|
214
|
+
} catch (e) {
|
|
215
|
+
console.error(pc.red(e.message));
|
|
216
|
+
console.log("");
|
|
217
|
+
const args = process.argv.slice(2);
|
|
218
|
+
const potentialCommand = args.find((a) => !a.startsWith("-"));
|
|
219
|
+
const helpArgs = potentialCommand ? [potentialCommand] : [];
|
|
220
|
+
await this.runHelp(helpArgs);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
registerGlobalOptions(cacCommand) {
|
|
225
|
+
cacCommand.option("--root-dir <path>", "Override project root");
|
|
226
|
+
cacCommand.option("--debug", "Enable debug mode");
|
|
227
|
+
cacCommand.option("--help, -h", "Display help message");
|
|
228
|
+
}
|
|
229
|
+
async runHelp(commandParts) {
|
|
230
|
+
if (this.HelpCommandClass) {
|
|
231
|
+
const helpInstance = new this.HelpCommandClass(this);
|
|
232
|
+
await helpInstance.run({ command: commandParts });
|
|
233
|
+
} else {
|
|
234
|
+
this.cli.outputHelp();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async runCommand(CommandClass, options, commandParts = []) {
|
|
238
|
+
try {
|
|
239
|
+
const instance = new CommandClass(this, options);
|
|
240
|
+
await instance.init();
|
|
241
|
+
await instance.run(options);
|
|
242
|
+
} catch (e) {
|
|
243
|
+
console.error(pc.red(e.message));
|
|
244
|
+
if (options.debug) console.error(e.stack);
|
|
245
|
+
console.log("");
|
|
246
|
+
await this.runHelp(commandParts);
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
export {
|
|
253
|
+
CLI
|
|
254
|
+
};
|
|
255
|
+
//# sourceMappingURL=chunk-2NZ4H42M.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/CLI.ts","../package.json"],"sourcesContent":["import { cac } from 'cac';\nimport { CommandLoader } from './CommandLoader.js';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport pc from 'picocolors';\nimport pkg from '../package.json';\nimport { logger, setDebugMode } from './utils/logger.js';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nexport interface CLIConfig {\n commandName?: string;\n searchDirectories?: string[];\n}\n\nexport class CLI {\n public name: string;\n private cli: ReturnType<typeof cac>;\n private loader: CommandLoader;\n private HelpCommandClass: any;\n private config: CLIConfig;\n\n constructor(config: CLIConfig = {}) {\n this.config = config;\n this.name = this.config.commandName || 'app';\n this.cli = cac(this.name);\n this.loader = new CommandLoader(this);\n }\n\n private loadedCommands: any[] = [];\n\n getCommands() {\n return this.loadedCommands;\n }\n\n getRawCLI() {\n return this.cli;\n }\n\n async start() {\n // In built version, we are in dist/index.js (from cli/index.ts) -> core bundled? or just imported.\n // The core logic is now in src/cli/core/src/CLI.ts or dist/core/src/CLI.js\n\n // Check for debug flag early\n if (process.argv.includes('--debug')) {\n setDebugMode(true);\n logger.debug('Debug mode enabled via --debug flag');\n }\n\n let commandsDirs: string[] = [];\n\n if (this.config.searchDirectories && this.config.searchDirectories.length > 0) {\n commandsDirs = this.config.searchDirectories;\n } else {\n // We assume the standard structure:\n // cli/\n // index.js\n // commands/\n // core/\n // src/\n // CLI.ts\n\n // When running from source (ts-node src/cli/index.ts), specific commands are in src/cli/commands.\n // core is in src/cli/core/src.\n // Relative path from CLI.ts to commands: ../../../commands\n\n const possibleDirs = [\n path.resolve(__dirname, './src/commands'),\n path.resolve(process.cwd(), 'commands') // Fallback relative to cwd?\n ];\n\n for (const dir of possibleDirs) {\n if (fs.existsSync(dir)) {\n commandsDirs.push(dir);\n break; // Keep existing behavior: verify if we want multiple or just first found\n }\n }\n }\n\n // Fallback or error\n if (commandsDirs.length === 0) {\n logger.debug(\"No commands directory found.\");\n }\n\n for (const dir of commandsDirs) {\n // Loader accumulates commands\n await this.loader.load(dir);\n }\n this.loadedCommands = this.loader.getCommands();\n\n // Group commands by root command name\n const commandGroups: Record<string, any[]> = {};\n // Locate HelpCommand for fallback usage\n const helpCmd = this.loadedCommands.find(c => c.command === 'help');\n if (helpCmd) {\n this.HelpCommandClass = helpCmd.class;\n }\n\n for (const cmd of this.loadedCommands) {\n const root = cmd.command.split(' ')[0];\n if (!commandGroups[root]) commandGroups[root] = [];\n commandGroups[root].push(cmd);\n }\n\n for (const [root, cmds] of Object.entries(commandGroups)) {\n // Case 1: Single command, no subcommands (e.g. 'init')\n if (cmds.length === 1 && cmds[0].command === root) {\n const cmd = cmds[0];\n const CommandClass = cmd.class;\n // Original logic for single command\n let commandName = cmd.command;\n const argsDef = CommandClass.args || {};\n if (argsDef.args) {\n argsDef.args.forEach((arg: any) => {\n const isVariadic = arg.name.endsWith('...');\n const cleanName = isVariadic ? arg.name.slice(0, -3) : arg.name;\n // Always use optional brackets [] for CAC to allow --help to pass validation\n // We will enforce 'required' manually in the action handler\n commandName += isVariadic ? ` [...${cleanName}]` : ` [${cleanName}]`;\n });\n }\n const cacCommand = this.cli.command(commandName, CommandClass.description || '');\n\n // Register options\n if (argsDef.options) {\n argsDef.options.forEach((opt: any) => {\n cacCommand.option(opt.name, opt.description, { default: opt.default });\n });\n }\n this.registerGlobalOptions(cacCommand);\n\n cacCommand.action(async (...args: any[]) => {\n const options = args.pop();\n\n if (options.help) {\n await this.runHelp([cmd.command]);\n return;\n }\n\n const positionalArgs = args;\n\n if (argsDef.args) {\n argsDef.args.forEach((arg: any, index: number) => {\n const isVariadic = arg.name.endsWith('...');\n const name = isVariadic ? arg.name.slice(0, -3) : arg.name;\n const val = positionalArgs[index];\n\n if (val !== undefined) {\n options[name] = val;\n } else if (arg.required) {\n console.error(pc.red(`Missing required argument: ${name}`));\n this.runHelp([cmd.command]).then(() => process.exit(1));\n return;\n }\n });\n }\n await this.runCommand(CommandClass, options, [cmd.command]);\n });\n } else {\n // Case 2: Command with subcommands (e.g. 'module add')\n // Register 'module <subcommand>' catch-all\n const commandName = `${root} [subcommand] [...args]`;\n const cacCommand = this.cli.command(commandName, `Manage ${root} commands`);\n\n cacCommand.allowUnknownOptions(); // Pass options to subcommand\n this.registerGlobalOptions(cacCommand);\n\n cacCommand.action(async (subcommand: string, ...args: any[]) => {\n const options = args.pop(); // last is options\n\n if (!subcommand || options.help) {\n // If --help is passed to 'module --help', subcommand might be caught as 'module' if args parsing is weird? \n // ACTUALLY: cac parses 'module add --help' as subcommand=\"add\".\n // 'module --help' might trigger the command itself? No, 'module <subcommand>' expects a subcommand.\n // If I run 'module --help', it might fail validation or parse 'help' as subcommand if unlucky, \n // but likely it just prints help if we didn't override.\n\n await this.runHelp([root, subcommand].filter(Boolean));\n return;\n }\n\n\n // Find matching command\n // Match against \"root subcommand\"\n const fullCommandName = `${root} ${subcommand}`;\n const cmd = cmds.find(c => c.command === fullCommandName);\n\n if (!cmd) {\n console.error(pc.red(`Unknown subcommand '${subcommand}' for '${root}'`));\n process.exit(1);\n }\n\n const CommandClass = cmd.class;\n // Map remaining args? \n // The args array contains positional args AFTER subcommand.\n // But we didn't define them in CAC, so they are just strings.\n // We need to map them manually to the Target Command's args definition.\n // argsDef.args usually starts after the command.\n // For 'module add <url>', <url> is the first arg after 'add'.\n // So 'args' here corresponds to <url>.\n\n const argsDef = CommandClass.args || {};\n // If using [...args], the variadic args are collected into the first argument array\n // args here is what remains after popping options.\n const positionalArgs = (args.length > 0 && Array.isArray(args[0])) ? args[0] : args;\n\n const childOptions = { ...options }; // Copy options\n\n const cmdParts = [root, subcommand];\n\n if (argsDef.args) {\n argsDef.args.forEach((arg: any, index: number) => {\n const isVariadic = arg.name.endsWith('...');\n const name = isVariadic ? arg.name.slice(0, -3) : arg.name;\n const val = positionalArgs[index];\n\n if (val !== undefined) {\n if (isVariadic) {\n childOptions[name] = positionalArgs.slice(index);\n } else {\n childOptions[name] = val;\n }\n } else if (arg.required) {\n console.error(pc.red(`Missing required argument: ${name}`));\n this.runHelp(cmdParts).then(() => process.exit(1));\n return;\n }\n });\n }\n\n await this.runCommand(CommandClass, childOptions, cmdParts);\n });\n }\n }\n // Disable default help\n // this.cli.help(); \n\n // Manually register global help to ensure it's allowed\n this.cli.option('--help, -h', 'Display help');\n\n this.cli.version(pkg.version);\n\n // Global help interception for root command\n // If we run `app --help`, we need to catch it.\n // CAC doesn't expose a clean global action without a command content.\n // However, if we parse and no command matches, it usually errors or shows help.\n // If we have default logic, we can put it here?\n // Let's rely on standard parsing but maybe inspect raw args first?\n\n if (process.argv.includes('--help') || process.argv.includes('-h')) {\n // Inspect non-option args to see if there's a command?\n const args = process.argv.slice(2).filter(a => !a.startsWith('-'));\n if (args.length === 0) {\n await this.runHelp([]);\n process.exit(0);\n }\n }\n\n try {\n this.cli.parse();\n } catch (e: any) {\n console.error(pc.red(e.message));\n\n // Try to provide helpful context\n console.log('');\n // Simple heuristic: find first non-flag arg as command\n const args = process.argv.slice(2);\n const potentialCommand = args.find(a => !a.startsWith('-'));\n // If it matches a loaded command root, show help for it\n // Otherwise show global help\n\n // We need to match 'module add' etc?\n // Just pass the potential command parts to runHelp. \n // runHelp handles filtering itself? No, runHelp takes commandParts to pass to HelpCommand.\n // HelpCommand expects `command` array.\n\n const helpArgs = potentialCommand ? [potentialCommand] : [];\n await this.runHelp(helpArgs);\n\n process.exit(1);\n }\n }\n\n private registerGlobalOptions(cacCommand: any) {\n cacCommand.option('--root-dir <path>', 'Override project root');\n cacCommand.option('--debug', 'Enable debug mode');\n cacCommand.option('--help, -h', 'Display help message');\n }\n\n private async runHelp(commandParts: string[]) {\n if (this.HelpCommandClass) {\n const helpInstance = new this.HelpCommandClass(this);\n await helpInstance.run({ command: commandParts });\n } else {\n // Fallback if HelpCommand not loaded (shouldn't happen)\n this.cli.outputHelp();\n }\n }\n\n private async runCommand(CommandClass: any, options: any, commandParts: string[] = []) {\n try {\n const instance = new CommandClass(this, options);\n await instance.init();\n await instance.run(options);\n } catch (e: any) {\n console.error(pc.red(e.message));\n if (options.debug) console.error(e.stack);\n\n console.log(''); // spacer\n await this.runHelp(commandParts);\n\n process.exit(1);\n }\n }\n}\n","{\n \"name\": \"@nexical/cli-core\",\n \"version\": \"0.1.1\",\n \"type\": \"module\",\n \"main\": \"dist/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": \"./dist/index.js\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"cli\": \"node dist/cli.js\",\n \"test\": \"npm run test:unit && npm run test:integration && npm run test:e2e\",\n \"test:unit\": \"vitest run --config vitest.config.ts --coverage\",\n \"test:integration\": \"vitest run --config vitest.integration.config.ts\",\n \"test:e2e\": \"npm run build && vitest run --config vitest.e2e.config.ts\",\n \"test:watch\": \"vitest\"\n },\n \"dependencies\": {\n \"cac\": \"^6.7.14\",\n \"consola\": \"^3.4.2\",\n \"lilconfig\": \"^3.1.3\",\n \"picocolors\": \"^1.1.1\",\n \"yaml\": \"^2.8.2\",\n \"zod\": \"^3.22.4\"\n },\n \"devDependencies\": {\n \"@types/fs-extra\": \"^11.0.4\",\n \"@types/node\": \"^20.10.0\",\n \"@vitest/coverage-v8\": \"^4.0.15\",\n \"execa\": \"^9.6.1\",\n \"fs-extra\": \"^11.3.2\",\n \"tsup\": \"^8.0.1\",\n \"typescript\": \"^5.3.3\",\n \"vitest\": \"^4.0.15\"\n }\n}"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createRequire } from "module"; const require = createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
logger
|
|
4
|
+
} from "./chunk-HETFF3FQ.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-5YGASH25.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,67 @@
|
|
|
1
|
+
import { createRequire } from "module"; const require = createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
findProjectRoot,
|
|
4
|
+
loadConfig
|
|
5
|
+
} from "./chunk-5YGASH25.js";
|
|
6
|
+
import {
|
|
7
|
+
logger
|
|
8
|
+
} from "./chunk-HETFF3FQ.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-6LSNSDTL.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 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,20 @@
|
|
|
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: "DEBUG"
|
|
8
|
+
},
|
|
9
|
+
level: LogLevels.info
|
|
10
|
+
// Default to info
|
|
11
|
+
});
|
|
12
|
+
function setDebugMode(enabled) {
|
|
13
|
+
logger.level = enabled ? LogLevels.debug : LogLevels.info;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
logger,
|
|
18
|
+
setDebugMode
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=chunk-HETFF3FQ.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: 'DEBUG',\n },\n level: LogLevels.info, // Default to 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;AAAA;AACrB,CAAC;AAEM,SAAS,aAAa,SAAkB;AAC3C,SAAO,QAAQ,UAAU,UAAU,QAAQ,UAAU;AACzD;","names":[]}
|