@nexical/cli 0.10.0 → 0.11.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.
Files changed (76) hide show
  1. package/.github/workflows/deploy.yml +1 -1
  2. package/.husky/pre-commit +1 -0
  3. package/.prettierignore +8 -0
  4. package/.prettierrc +7 -0
  5. package/GEMINI.md +199 -0
  6. package/README.md +85 -56
  7. package/dist/chunk-AC4B3HPJ.js +93 -0
  8. package/dist/chunk-AC4B3HPJ.js.map +1 -0
  9. package/dist/{chunk-JYASTIIW.js → chunk-PJIOCW2A.js} +1 -1
  10. package/dist/chunk-PJIOCW2A.js.map +1 -0
  11. package/dist/{chunk-WKERTCM6.js → chunk-Q7YLW5HJ.js} +5 -2
  12. package/dist/chunk-Q7YLW5HJ.js.map +1 -0
  13. package/dist/index.js +41 -12
  14. package/dist/index.js.map +1 -1
  15. package/dist/src/commands/init.d.ts +4 -1
  16. package/dist/src/commands/init.js +15 -10
  17. package/dist/src/commands/init.js.map +1 -1
  18. package/dist/src/commands/module/add.d.ts +3 -1
  19. package/dist/src/commands/module/add.js +27 -16
  20. package/dist/src/commands/module/add.js.map +1 -1
  21. package/dist/src/commands/module/list.js +9 -5
  22. package/dist/src/commands/module/list.js.map +1 -1
  23. package/dist/src/commands/module/remove.d.ts +3 -1
  24. package/dist/src/commands/module/remove.js +13 -7
  25. package/dist/src/commands/module/remove.js.map +1 -1
  26. package/dist/src/commands/module/update.d.ts +3 -1
  27. package/dist/src/commands/module/update.js +7 -5
  28. package/dist/src/commands/module/update.js.map +1 -1
  29. package/dist/src/commands/run.d.ts +4 -1
  30. package/dist/src/commands/run.js +10 -2
  31. package/dist/src/commands/run.js.map +1 -1
  32. package/dist/src/commands/setup.d.ts +8 -0
  33. package/dist/src/commands/setup.js +75 -0
  34. package/dist/src/commands/setup.js.map +1 -0
  35. package/dist/src/utils/discovery.js +1 -1
  36. package/dist/src/utils/git.js +1 -1
  37. package/dist/src/utils/url-resolver.js +1 -1
  38. package/eslint.config.mjs +67 -0
  39. package/index.ts +34 -20
  40. package/package.json +57 -33
  41. package/src/commands/init.ts +79 -75
  42. package/src/commands/module/add.ts +158 -148
  43. package/src/commands/module/list.ts +61 -50
  44. package/src/commands/module/remove.ts +59 -54
  45. package/src/commands/module/update.ts +44 -42
  46. package/src/commands/run.ts +89 -81
  47. package/src/commands/setup.ts +92 -0
  48. package/src/utils/discovery.ts +98 -113
  49. package/src/utils/git.ts +35 -28
  50. package/src/utils/url-resolver.ts +50 -45
  51. package/test/e2e/lifecycle.e2e.test.ts +139 -130
  52. package/test/integration/commands/init.integration.test.ts +64 -61
  53. package/test/integration/commands/module.integration.test.ts +122 -122
  54. package/test/integration/commands/run.integration.test.ts +70 -63
  55. package/test/integration/utils/command-loading.integration.test.ts +40 -53
  56. package/test/unit/commands/init.test.ts +163 -128
  57. package/test/unit/commands/module/add.test.ts +312 -245
  58. package/test/unit/commands/module/list.test.ts +108 -91
  59. package/test/unit/commands/module/remove.test.ts +74 -67
  60. package/test/unit/commands/module/update.test.ts +74 -70
  61. package/test/unit/commands/run.test.ts +253 -201
  62. package/test/unit/commands/setup.test.ts +187 -0
  63. package/test/unit/utils/command-discovery.test.ts +138 -125
  64. package/test/unit/utils/git.test.ts +135 -117
  65. package/test/unit/utils/integration-helpers.test.ts +59 -49
  66. package/test/unit/utils/url-resolver.test.ts +46 -34
  67. package/test/utils/integration-helpers.ts +36 -29
  68. package/tsconfig.json +15 -25
  69. package/tsup.config.ts +14 -14
  70. package/vitest.config.ts +10 -10
  71. package/vitest.e2e.config.ts +6 -6
  72. package/vitest.integration.config.ts +17 -17
  73. package/dist/chunk-JYASTIIW.js.map +0 -1
  74. package/dist/chunk-OKXOCNXP.js +0 -105
  75. package/dist/chunk-OKXOCNXP.js.map +0 -1
  76. package/dist/chunk-WKERTCM6.js.map +0 -1
@@ -31,4 +31,4 @@ jobs:
31
31
  - name: Publish to NPM
32
32
  run: npm publish --access public
33
33
  env:
34
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
34
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1 @@
1
+ npx lint-staged
@@ -0,0 +1,8 @@
1
+ dist/
2
+ coverage/
3
+ node_modules/
4
+ .astro/
5
+ .next/
6
+ pnpm-lock.yaml
7
+ package-lock.json
8
+ yarn.lock
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": true,
4
+ "tabWidth": 2,
5
+ "trailingComma": "all",
6
+ "printWidth": 100
7
+ }
package/GEMINI.md ADDED
@@ -0,0 +1,199 @@
1
+ # Nexical CLI Core Development Guide
2
+
3
+ This guide details how to build command libraries and CLIs using the `@nexical/cli-core` framework within this project.
4
+
5
+ ## Overview
6
+
7
+ The project is configured to use `@nexical/cli-core` as its CLI framework. usage entails:
8
+
9
+ 1. **Entry Point**: `index.ts` initializes the `CLI` instance.
10
+ 2. **Command Discovery**: The CLI automatically scans `src/commands` for command files.
11
+ 3. **Command Implementation**: Commands are TypeScript classes extending `BaseCommand`.
12
+
13
+ ## Directory Structure
14
+
15
+ Commands are defined in `src/commands`. The file structure directly maps to the command hierarchy.
16
+
17
+ | File Path | Command | Description |
18
+ | :---------------------------- | :---------------- | :-------------------------------- |
19
+ | `src/commands/init.ts` | `app init` | Root level command |
20
+ | `src/commands/user/create.ts` | `app user create` | Subcommand |
21
+ | `src/commands/user/index.ts` | `app user` | Parent command handler (optional) |
22
+
23
+ > **Note**: The CLI name (`app`) is configured in `index.ts`.
24
+
25
+ ## Creating a New Command
26
+
27
+ To create a new command, add a `.ts` file in `src/commands`.
28
+
29
+ ### 1. Basic Command Template
30
+
31
+ Create `src/commands/hello.ts`:
32
+
33
+ ```typescript
34
+ import { BaseCommand } from '@nexical/cli-core';
35
+
36
+ export default class HelloCommand extends BaseCommand {
37
+ // Description displayed in help menus
38
+ static description = 'Prints a hello message';
39
+
40
+ // The main execution method
41
+ async run(options: any) {
42
+ this.success('Hello World!');
43
+ }
44
+ }
45
+ ```
46
+
47
+ **Run it:**
48
+
49
+ ```bash
50
+ npm run cli hello
51
+ ```
52
+
53
+ ### 2. Arguments and Options
54
+
55
+ Use the static `args` property to define inputs.
56
+
57
+ ```typescript
58
+ import { BaseCommand } from '@nexical/cli-core';
59
+
60
+ export default class GreetCommand extends BaseCommand {
61
+ static description = 'Greets a user';
62
+
63
+ static args = {
64
+ // Positional Arguments
65
+ args: [
66
+ {
67
+ name: 'name',
68
+ description: 'Name of the user',
69
+ required: true,
70
+ },
71
+ ],
72
+ // Options (Flags)
73
+ options: [
74
+ {
75
+ name: '--loud',
76
+ description: 'Print in uppercase',
77
+ default: false,
78
+ },
79
+ {
80
+ name: '-c, --count <n>',
81
+ description: 'Number of times to greet',
82
+ default: 1,
83
+ },
84
+ ],
85
+ };
86
+
87
+ async run(options: any) {
88
+ // 'name' is mapped from args
89
+ // 'loud' and 'count' are mapped from options
90
+ const { name, loud, count } = options;
91
+
92
+ let message = `Hello, ${name}`;
93
+ if (loud) message = message.toUpperCase();
94
+
95
+ for (let i = 0; i < count; i++) {
96
+ this.info(message);
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ **Run it:**
103
+
104
+ ```bash
105
+ npm run cli greet Adrian --loud --count 3
106
+ ```
107
+
108
+ ### 3. User Input & Prompts
109
+
110
+ `BaseCommand` provides built-in methods for interactivity.
111
+
112
+ ```typescript
113
+ export default class InteractiveCommand extends BaseCommand {
114
+ async run() {
115
+ // Simple confirmation or input
116
+ const name = await this.prompt('What is your name?');
117
+
118
+ this.success(`Nice to meet you, ${name}`);
119
+ }
120
+ }
121
+ ```
122
+
123
+ ### 4. Output Helpers
124
+
125
+ Use the built-in helper methods for consistent logging:
126
+
127
+ - `this.success(msg)`: Green checkmark (✔ Success)
128
+ - `this.error(msg)`: Red cross (✖ Error) - **Exits process**
129
+ - `this.warn(msg)`: Yellow warning (⚠ Warning)
130
+ - `this.info(msg)`: Standard log
131
+ - `this.notice(msg)`: Blue notice (📢 Note)
132
+ - `this.input(msg)`: Cyan input prompt (? Question)
133
+
134
+ ## Subcommands
135
+
136
+ To create grouped commands (e.g., `user create`, `user list`), use directories.
137
+
138
+ 1. **Create Directory**: `src/commands/user/`
139
+ 2. **Add Commands**:
140
+ - `src/commands/user/create.ts` -> `app user create`
141
+ - `src/commands/user/list.ts` -> `app user list`
142
+
143
+ ### Index Commands (Container Commands)
144
+
145
+ If you need the parent command `app user` to do something (or just provide a description for the group), create `src/commands/user/index.ts`.
146
+
147
+ ```typescript
148
+ // src/commands/user/index.ts
149
+ import { BaseCommand } from '@nexical/cli-core';
150
+
151
+ export default class UserCommand extends BaseCommand {
152
+ static description = 'Manage users';
153
+
154
+ async run() {
155
+ // This runs when user types 'app user' without a subcommand
156
+ // Often used to show help
157
+ this.cli.getRawCLI().outputHelp();
158
+ }
159
+ }
160
+ ```
161
+
162
+ ## Internal API Reference
163
+
164
+ ### `BaseCommand`
165
+
166
+ All commands inherit from `BaseCommand`.
167
+
168
+ **Properties:**
169
+
170
+ - `cli`: Access to the main `CLI` instance.
171
+ - `projectRoot`: Path to the project root (if detected).
172
+ - `config`: Loaded configuration from `{cliName}.yml`.
173
+ - `globalOptions`: Global flags passed to the CLI (e.g., `--debug`).
174
+
175
+ **Methods:**
176
+
177
+ - `init()`: Called before `run()`. Useful for setup.
178
+ - `run(options)`: Abstract method. Must be implemented.
179
+ - `prompt(message)`: Async, returns string.
180
+
181
+ **Static Properties:**
182
+
183
+ - `description`: String. Shown in help.
184
+ - `args`: Object. Defines arguments and options.
185
+ - `args`: Array of `{ name, description, required, default }`.
186
+ - `options`: Array of `{ name, description, default }`.
187
+ - `requiresProject`: Boolean. If true, command fails if not run inside a project with a config file.
188
+
189
+ ## Best Practices
190
+
191
+ 1. **Type Safety**: While `options` is `any` in `run()`, validate inputs early.
192
+ 2. **Error Handling**: Use `this.error()` for fatal errors to ensure proper exit codes.
193
+ 3. **Clean Output**: Use the helper methods (`success`, `info`, etc.) instead of `console.log` for a consistent UI.
194
+ 4. **Async**: The `run` method is async. Always `await` asynchronous operations.
195
+
196
+ ## Troubleshooting
197
+
198
+ - **Command not found**: Ensure the file exports a class leveraging `export default` and extends `BaseCommand`.
199
+ - **Changes not reflected**: If using `tsup`, ensure you are building or running in dev mode (`npm run dev`). For `npm run cli` using `ts-node` (via `cli.ts` or similar), changes should be instant.
package/README.md CHANGED
@@ -11,9 +11,9 @@ This project serves as the primary entry point for managing Nexical projects, ha
11
11
  - [Getting Started](#getting-started)
12
12
  - [Project Structure](#project-structure)
13
13
  - [Development Workflow](#development-workflow)
14
- - [Prerequisites](#prerequisites)
15
- - [Setup](#setup)
16
- - [Running Tests](#running-tests)
14
+ - [Prerequisites](#prerequisites)
15
+ - [Setup](#setup)
16
+ - [Running Tests](#running-tests)
17
17
  - [Adding New Commands](#adding-new-commands)
18
18
  - [Contributing](#contributing)
19
19
  - [License](#license)
@@ -23,6 +23,7 @@ This project serves as the primary entry point for managing Nexical projects, ha
23
23
  ## Purpose
24
24
 
25
25
  The Nexical CLI allows developers to:
26
+
26
27
  1. **Initialize** new Nexical projects with best practices built-in.
27
28
  2. **Manage** project configuration and dependencies.
28
29
  3. **Extend** the framework functionality through modular commands.
@@ -34,27 +35,30 @@ It acts as a unification layer, bringing together various tools and configuratio
34
35
  This CLI is built with **TypeScript** and follows a **Class-Based Command Pattern** to ensure type safety and maintainability.
35
36
 
36
37
  ### Key Technologies
37
- * **[CAC (Command And Conquer)](https://github.com/cacjs/cac)**: A lightweight, robust framework for building CLIs. It handles argument parsing, help generation, and command registration.
38
- * **[Consola](https://github.com/unjs/consola)**: Elegant console logging with fallback and structured output capabilities.
39
- * **[Lilconfig](https://github.com/antonk52/lilconfig)**: Configuration loading (searching for `nexical.yml`, `nexical.yaml`) akin to `cosmiconfig` but lighter.
40
- * **Vitest**: A blazing fast unit test framework powered by Vite.
38
+
39
+ - **[CAC (Command And Conquer)](https://github.com/cacjs/cac)**: A lightweight, robust framework for building CLIs. It handles argument parsing, help generation, and command registration.
40
+ - **[Consola](https://github.com/unjs/consola)**: Elegant console logging with fallback and structured output capabilities.
41
+ - **[Lilconfig](https://github.com/antonk52/lilconfig)**: Configuration loading (searching for `nexical.yml`, `nexical.yaml`) akin to `cosmiconfig` but lighter.
42
+ - **Vitest**: A blazing fast unit test framework powered by Vite.
41
43
 
42
44
  ### Core Components
45
+
43
46
  1. **`CLI` Class** (`src/core/CLI.ts`): The orchestrator. It initializes the CAC instance, discovers commands using the `CommandLoader`, registers them, and handles the execution lifecycle.
44
47
  2. **`CommandLoader`** (`src/core/CommandLoader.ts`): Responsible for dynamically discovering and importing command files from the filesystem. It supports:
45
- * Recursive directory scanning.
46
- * Nested commands (e.g., `module/add.ts` -> `module add`).
47
- * Index files as parent commands (e.g., `module/index.ts` -> `module`).
48
+ - Recursive directory scanning.
49
+ - Nested commands (e.g., `module/add.ts` -> `module add`).
50
+ - Index files as parent commands (e.g., `module/index.ts` -> `module`).
48
51
  3. **`BaseCommand`** (`src/core/BaseCommand.ts`): The abstract base class that all commands MUST extend. It provides:
49
- * Standardized `init()` and `run()` lifecycle methods.
50
- * Built-in access to global options (like `--root-dir`).
51
- * Helper methods for logging (`this.log`, `this.warn`, `this.error`).
52
- * Project root detection (`this.projectRoot`).
52
+ - Standardized `init()` and `run()` lifecycle methods.
53
+ - Built-in access to global options (like `--root-dir`).
54
+ - Helper methods for logging (`this.log`, `this.warn`, `this.error`).
55
+ - Project root detection (`this.projectRoot`).
53
56
 
54
57
  ### Design Goals
55
- * **Zero-Config Defaults**: It should work out of the box but allow rich configuration via `nexical.yaml`.
56
- * **Extensibility**: Adding a command should be as simple as adding a file.
57
- * **Testability**: Every component is designed to be unit-testable, with dependency injection where appropriate (e.g., `CommandLoader` importer).
58
+
59
+ - **Zero-Config Defaults**: It should work out of the box but allow rich configuration via `nexical.yaml`.
60
+ - **Extensibility**: Adding a command should be as simple as adding a file.
61
+ - **Testability**: Every component is designed to be unit-testable, with dependency injection where appropriate (e.g., `CommandLoader` importer).
58
62
 
59
63
  ---
60
64
 
@@ -94,19 +98,23 @@ npx nexical help module add
94
98
  Initializes a new Nexical project by cloning a starter repository, setting up dependencies, and preparing a fresh git history.
95
99
 
96
100
  **Usage:**
101
+
97
102
  ```bash
98
103
  npx nexical init <directory> [options]
99
104
  ```
100
105
 
101
106
  **Arguments:**
107
+
102
108
  - `directory` (Required): The directory to initialize the project in. If the directory does not exist, it will be created. If it does exist, it must be empty.
103
109
 
104
110
  **Options:**
111
+
105
112
  - `--repo <url>` (Default: `https://github.com/nexical/app-core`): The URL of the starter repository to clone.
106
- - Supports standard Git URLs (e.g., `https://github.com/user/repo.git`).
107
- - Supports GitHub short syntax `gh@owner/repo` (e.g., `gh@nexical/app-core`).
113
+ - Supports standard Git URLs (e.g., `https://github.com/user/repo.git`).
114
+ - Supports GitHub short syntax `gh@owner/repo` (e.g., `gh@nexical/app-core`).
108
115
 
109
116
  **What it does:**
117
+
110
118
  1. **Clones** the specified starter repository (recursively, including submodules) into the target directory.
111
119
  2. **Updates** all submodules to their latest `main` branch.
112
120
  3. **Installs** dependencies using `npm install`.
@@ -118,6 +126,7 @@ npx nexical init <directory> [options]
118
126
  - Removes the `origin` remote to prevent accidental pushes to the starter repo.
119
127
 
120
128
  **Output:**
129
+
121
130
  - A ready-to-use Nexical project in the specified directory, with fresh git history and installed dependencies.
122
131
 
123
132
  ---
@@ -127,13 +136,12 @@ npx nexical init <directory> [options]
127
136
  Starts the development server in ephemeral mode. It constructs a temporary build environment in `site` and runs the Astro dev server with Hot Module Replacement (HMR).
128
137
 
129
138
  **Usage:**
139
+
130
140
  ```bash
131
141
  npx nexical dev
132
142
  ```
133
143
 
134
- **What it does:**
135
- 2. **Starts** the Astro development server (accessible at `http://localhost:4321` by default).
136
- 3. **Watches** for changes in your project and updates the ephemeral build automatically.
144
+ **What it does:** 2. **Starts** the Astro development server (accessible at `http://localhost:4321` by default). 3. **Watches** for changes in your project and updates the ephemeral build automatically.
137
145
 
138
146
  ---
139
147
 
@@ -142,16 +150,19 @@ npx nexical dev
142
150
  Compiles the project for production. It assembles the final site structure in `site` and generates static assets.
143
151
 
144
152
  **Usage:**
153
+
145
154
  ```bash
146
155
  npx nexical build
147
156
  ```
148
157
 
149
158
  **What it does:**
159
+
150
160
  1. **Cleans** the `site` directory to ensure a fresh build.
151
161
  2. **Copies** all necessary source files (`src/`, `modules`, `src/content`, `public`) into `site`.
152
162
  3. **Runs** `astro build` to generate the production output in `site/dist`.
153
163
 
154
164
  **Output:**
165
+
155
166
  - A production-ready static site in `site/dist`.
156
167
 
157
168
  ---
@@ -161,14 +172,17 @@ npx nexical build
161
172
  Previews the locally built production site. This is useful for verifying the output of `nexical build` before deploying.
162
173
 
163
174
  **Usage:**
175
+
164
176
  ```bash
165
177
  npx nexical preview
166
178
  ```
167
179
 
168
180
  **Prerequisites:**
181
+
169
182
  - You must run `nexical build` first.
170
183
 
171
184
  **What it does:**
185
+
172
186
  - Starts a local web server serving the static files from `site/dist`.
173
187
 
174
188
  ---
@@ -178,11 +192,13 @@ npx nexical preview
178
192
  Removes generated build artifacts and temporary directories to ensure a clean state.
179
193
 
180
194
  **Usage:**
195
+
181
196
  ```bash
182
197
  npx nexical clean
183
198
  ```
184
199
 
185
200
  **What it does:**
201
+
186
202
  - Deletes `site`, `dist`, and `node_modules/.vite`.
187
203
 
188
204
  ---
@@ -192,17 +208,20 @@ npx nexical clean
192
208
  Executes a script within the Nexical environment context. This handles path resolution and environment variable setup for you.
193
209
 
194
210
  **Usage:**
211
+
195
212
  ```bash
196
213
  npx nexical run <script> [args...]
197
214
  ```
198
215
 
199
216
  **Arguments:**
217
+
200
218
  - `script` (Required): The name of the script to run.
201
- - Can be a standard `package.json` script (e.g., `test`).
202
- - Can be a module-specific script using `module:script` syntax (e.g., `blog:sync`).
219
+ - Can be a standard `package.json` script (e.g., `test`).
220
+ - Can be a module-specific script using `module:script` syntax (e.g., `blog:sync`).
203
221
  - `args` (Optional): Additional arguments to pass to the script.
204
222
 
205
223
  **Examples:**
224
+
206
225
  ```bash
207
226
  # Run a core project script
208
227
  npx nexical run test
@@ -222,16 +241,19 @@ Manages the modular architecture of your Nexical project. Allows you to add, rem
222
241
  Adds a new module as a Git submodule.
223
242
 
224
243
  **Usage:**
244
+
225
245
  ```bash
226
246
  npx nexical module add <url> [name]
227
247
  ```
228
248
 
229
249
  **Arguments:**
250
+
230
251
  - `url` (Required): The Git repository URL of the module.
231
- - Supports `gh@owner/repo` shorthand.
252
+ - Supports `gh@owner/repo` shorthand.
232
253
  - `name` (Optional): The folder name for the module. Defaults to the repository name.
233
254
 
234
255
  **What it does:**
256
+
235
257
  1. Adds the repository as a git submodule in `src/modules/<name>`.
236
258
  2. Installs any new dependencies via `npm install`.
237
259
 
@@ -240,11 +262,13 @@ npx nexical module add <url> [name]
240
262
  Lists all installed modules in the project.
241
263
 
242
264
  **Usage:**
265
+
243
266
  ```bash
244
267
  npx nexical module list
245
268
  ```
246
269
 
247
270
  **Output:**
271
+
248
272
  - A table showing the name, version, and description of each installed module found in `src/modules`.
249
273
 
250
274
  ##### `module update`
@@ -252,14 +276,17 @@ npx nexical module list
252
276
  Updates one or all modules to their latest remote commit.
253
277
 
254
278
  **Usage:**
279
+
255
280
  ```bash
256
281
  npx nexical module update [name]
257
282
  ```
258
283
 
259
284
  **Arguments:**
285
+
260
286
  - `name` (Optional): The specific module to update. If omitted, all modules are updated.
261
287
 
262
288
  **What it does:**
289
+
263
290
  1. Runs `git submodule update --remote --merge` for the target(s).
264
291
  2. Re-installs dependencies to ensure `package-lock.json` is consistent.
265
292
 
@@ -268,14 +295,17 @@ npx nexical module update [name]
268
295
  Removes an installed module and cleans up references.
269
296
 
270
297
  **Usage:**
298
+
271
299
  ```bash
272
300
  npx nexical module remove <name>
273
301
  ```
274
302
 
275
303
  **Arguments:**
304
+
276
305
  - `name` (Required): The name of the module to remove.
277
306
 
278
307
  **What it does:**
308
+
279
309
  1. De-initializes the git submodule.
280
310
  2. Removes the module directory from `src/modules`.
281
311
  3. Cleans up internal git metadata (`.git/modules`).
@@ -298,22 +328,24 @@ graph TD
298
328
  utils-->logger.ts
299
329
  ```
300
330
 
301
- * **`src/commands/`**: Contains the implementations of individual CLI commands. File names correspond to command names.
302
- * **`src/core/`**: The framework logic (Command loading, Base class, CLI orchestration).
303
- * **`src/utils/`**: Shared utilities (Logging, Configuration parsing).
304
- * **`test/unit/`**: Co-located unit tests. Mirrors the `src` structure.
331
+ - **`src/commands/`**: Contains the implementations of individual CLI commands. File names correspond to command names.
332
+ - **`src/core/`**: The framework logic (Command loading, Base class, CLI orchestration).
333
+ - **`src/utils/`**: Shared utilities (Logging, Configuration parsing).
334
+ - **`test/unit/`**: Co-located unit tests. Mirrors the `src` structure.
305
335
 
306
336
  ---
307
337
 
308
338
  ## Development Workflow
309
339
 
310
340
  ### Prerequisites
311
- * Node.js (v18+ recommended)
312
- * NPM
341
+
342
+ - Node.js (v18+ recommended)
343
+ - NPM
313
344
 
314
345
  ### Setup
315
346
 
316
347
  1. **Install Dependencies**:
348
+
317
349
  ```bash
318
350
  npm install
319
351
  ```
@@ -347,43 +379,40 @@ To create a new command, add a TypeScript file to `src/commands/`.
347
379
  import { BaseCommand } from '../core/BaseCommand.js';
348
380
 
349
381
  export default class HelloCommand extends BaseCommand {
350
- // 1. Define command metadata
351
- static description = 'Say hello to the world';
352
-
353
- static args = {
354
- args: [
355
- { name: 'name', required: false, description: 'User name' }
356
- ],
357
- options: [
358
- { name: '--shout', description: 'Say it loud', default: false }
359
- ]
360
- };
361
-
362
- // 2. Implement the run method
363
- async run(options: any) {
364
- const name = options.name || 'World';
365
-
366
- if (options.shout) {
367
- this.success(`HELLO ${name.toUpperCase()}!`);
368
- } else {
369
- this.log(`Hello ${name}`);
370
- }
382
+ // 1. Define command metadata
383
+ static description = 'Say hello to the world';
384
+
385
+ static args = {
386
+ args: [{ name: 'name', required: false, description: 'User name' }],
387
+ options: [{ name: '--shout', description: 'Say it loud', default: false }],
388
+ };
389
+
390
+ // 2. Implement the run method
391
+ async run(options: any) {
392
+ const name = options.name || 'World';
393
+
394
+ if (options.shout) {
395
+ this.success(`HELLO ${name.toUpperCase()}!`);
396
+ } else {
397
+ this.log(`Hello ${name}`);
371
398
  }
399
+ }
372
400
  }
373
401
  ```
374
402
 
375
403
  **Key Requirement**: The file MUST default export a class extending `BaseCommand`.
376
404
 
377
- * **File Naming**:
378
- * `hello.ts` -> Command: `hello`
379
- * `users/create.ts` -> Command: `users create`
380
- * `users/index.ts` -> Command: `users` (Parent command)
405
+ - **File Naming**:
406
+ - `hello.ts` -> Command: `hello`
407
+ - `users/create.ts` -> Command: `users create`
408
+ - `users/index.ts` -> Command: `users` (Parent command)
381
409
 
382
410
  ---
383
411
 
384
412
  ## Contributing
385
413
 
386
414
  Contributions are welcome! Please follow these steps:
415
+
387
416
  1. Fork the repository.
388
417
  2. Create a feature branch.
389
418
  3. Add your changes and **ensure tests pass with 100% coverage**.
@@ -0,0 +1,93 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ init_esm_shims
4
+ } from "./chunk-OYFWMYPG.js";
5
+
6
+ // src/utils/discovery.ts
7
+ init_esm_shims();
8
+ import { logger } from "@nexical/cli-core";
9
+ import path from "path";
10
+ import fs from "fs";
11
+ function discoverCommandDirectories(projectRoot) {
12
+ const directories = [];
13
+ const visited = /* @__PURE__ */ new Set();
14
+ const isTsEnvironment = process.argv[1]?.endsWith(".ts") || process.argv[1]?.endsWith(".mts") || process.execArgv.some(
15
+ (arg) => arg.includes("tsx") || arg.includes("ts-node") || arg.includes("vitest")
16
+ ) || process.env.VITEST === "true" || process.env.NODE_ENV === "test";
17
+ const addDir = (dir) => {
18
+ const resolved = path.resolve(dir);
19
+ if (!fs.existsSync(resolved)) {
20
+ logger.debug(`Command directory not found (skipping): ${resolved}`);
21
+ return;
22
+ }
23
+ if (visited.has(resolved)) return;
24
+ const srcPattern = path.join(path.sep, "src", "commands");
25
+ const distPattern = path.join(path.sep, "dist");
26
+ const isSrcDir = resolved.endsWith(srcPattern) && !resolved.includes(distPattern);
27
+ if (isSrcDir) {
28
+ const distPath1 = resolved.replace(
29
+ srcPattern,
30
+ path.join(path.sep, "dist", "src", "commands")
31
+ );
32
+ const distPath2 = resolved.replace(srcPattern, path.join(path.sep, "dist", "commands"));
33
+ if (fs.existsSync(distPath1) || fs.existsSync(distPath2)) {
34
+ logger.debug(`Skipping src commands at ${resolved} because dist exists`);
35
+ return;
36
+ }
37
+ if (!isTsEnvironment) {
38
+ logger.debug(`Skipping src commands at ${resolved}: no TS loader detected`);
39
+ return;
40
+ }
41
+ }
42
+ logger.debug(`Found command directory: ${resolved}`);
43
+ directories.push(resolved);
44
+ visited.add(resolved);
45
+ };
46
+ const possibleCorePaths = [path.join(projectRoot, "src/commands")];
47
+ possibleCorePaths.forEach(addDir);
48
+ const searchRoots = [
49
+ path.join(projectRoot, "modules"),
50
+ path.join(projectRoot, "src", "modules"),
51
+ path.join(projectRoot, "packages")
52
+ ];
53
+ searchRoots.forEach((root) => {
54
+ if (!fs.existsSync(root)) return;
55
+ try {
56
+ const entries = fs.readdirSync(root);
57
+ for (const entry of entries) {
58
+ if (entry.startsWith(".")) continue;
59
+ const entryPath = path.join(root, entry);
60
+ if (!fs.statSync(entryPath).isDirectory()) continue;
61
+ const possiblePaths = [
62
+ path.join(entryPath, "dist/src/commands"),
63
+ path.join(entryPath, "dist/commands"),
64
+ path.join(entryPath, "src/commands")
65
+ ];
66
+ let foundDist = false;
67
+ for (const p of possiblePaths) {
68
+ if (fs.existsSync(p) && fs.statSync(p).isDirectory()) {
69
+ if (p.includes(path.sep + "dist" + path.sep)) {
70
+ addDir(p);
71
+ foundDist = true;
72
+ break;
73
+ }
74
+ }
75
+ }
76
+ if (!foundDist) {
77
+ const srcPath = path.join(entryPath, "src/commands");
78
+ if (fs.existsSync(srcPath) && fs.statSync(srcPath).isDirectory()) {
79
+ addDir(srcPath);
80
+ }
81
+ }
82
+ }
83
+ } catch (e) {
84
+ logger.debug(`Error scanning root ${root}: ${e instanceof Error ? e.message : String(e)}`);
85
+ }
86
+ });
87
+ return directories;
88
+ }
89
+
90
+ export {
91
+ discoverCommandDirectories
92
+ };
93
+ //# sourceMappingURL=chunk-AC4B3HPJ.js.map