@nexical/cli-core 0.1.10 → 0.1.12
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/dist/{chunk-SICPAVM2.js → chunk-TCK2GYT7.js} +15 -3
- package/dist/chunk-TCK2GYT7.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/src/CLI.js +1 -1
- package/package.json +6 -3
- package/src/CLI.ts +13 -49
- package/test/unit/core/CLI.nested.test.ts +128 -0
- package/dist/chunk-SICPAVM2.js.map +0 -1
|
@@ -17,12 +17,15 @@ import pc from "picocolors";
|
|
|
17
17
|
// package.json
|
|
18
18
|
var package_default = {
|
|
19
19
|
name: "@nexical/cli-core",
|
|
20
|
-
version: "0.1.
|
|
20
|
+
version: "0.1.12",
|
|
21
21
|
type: "module",
|
|
22
22
|
main: "dist/index.js",
|
|
23
23
|
types: "dist/index.d.ts",
|
|
24
24
|
exports: {
|
|
25
|
-
".":
|
|
25
|
+
".": {
|
|
26
|
+
types: "./dist/index.d.ts",
|
|
27
|
+
import: "./dist/index.js"
|
|
28
|
+
}
|
|
26
29
|
},
|
|
27
30
|
scripts: {
|
|
28
31
|
build: "tsup",
|
|
@@ -204,6 +207,15 @@ var CLI = class {
|
|
|
204
207
|
}
|
|
205
208
|
});
|
|
206
209
|
}
|
|
210
|
+
if (argsDef.options) {
|
|
211
|
+
argsDef.options.forEach((opt) => {
|
|
212
|
+
const name = opt.name.replace(/^-+/, "");
|
|
213
|
+
const camelName = name.split(" ")[0].replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
214
|
+
if (childOptions[camelName] === void 0 && opt.default !== void 0) {
|
|
215
|
+
childOptions[camelName] = opt.default;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
207
219
|
await this.runCommand(CommandClass, childOptions, cmdParts);
|
|
208
220
|
});
|
|
209
221
|
}
|
|
@@ -260,4 +272,4 @@ var CLI = class {
|
|
|
260
272
|
export {
|
|
261
273
|
CLI
|
|
262
274
|
};
|
|
263
|
-
//# sourceMappingURL=chunk-
|
|
275
|
+
//# sourceMappingURL=chunk-TCK2GYT7.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 version?: string;\n commandName?: string;\n searchDirectories?: string[];\n}\n\nexport class CLI {\n public name: string;\n public version: 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.version = this.config.version || pkg.version;\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 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 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 }\n }\n }\n\n // Always add core commands directory (where help command lives)\n const coreCommandsDir = path.resolve(__dirname, './commands');\n const coreCommandsDirSrc = path.resolve(__dirname, './src/commands');\n\n if (fs.existsSync(coreCommandsDir)) {\n commandsDirs.push(coreCommandsDir);\n } else if (fs.existsSync(coreCommandsDirSrc)) {\n // Fallback for tsup chunking which puts CLI in dist/ but keeps src/commands in dist/src/commands\n commandsDirs.push(coreCommandsDirSrc);\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 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 const argsDef = CommandClass.args || {};\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 if (argsDef.options) {\n argsDef.options.forEach((opt: any) => {\n const name = opt.name.replace(/^-+/, ''); // remove leading dashes\n const camelName = name.split(' ')[0].replace(/-([a-z])/g, (g: string) => g[1].toUpperCase());\n\n // Check both raw name and camelCase name\n // CAC usually provides camelCase options\n if (childOptions[camelName] === undefined && opt.default !== undefined) {\n childOptions[camelName] = opt.default;\n }\n });\n }\n\n await this.runCommand(CommandClass, childOptions, cmdParts);\n });\n }\n }\n\n // Manually register global help to ensure it's allowed\n this.cli.option('--help, -h', 'Display help');\n\n this.cli.version(this.version);\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 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.runInit(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.12\",\n \"type\": \"module\",\n \"main\": \"dist/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n }\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":[]}
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/dist/src/CLI.js
CHANGED
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexical/cli-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
9
12
|
},
|
|
10
13
|
"scripts": {
|
|
11
14
|
"build": "tsup",
|
|
@@ -35,4 +38,4 @@
|
|
|
35
38
|
"typescript": "^5.3.3",
|
|
36
39
|
"vitest": "^4.0.15"
|
|
37
40
|
}
|
|
38
|
-
}
|
|
41
|
+
}
|
package/src/CLI.ts
CHANGED
|
@@ -42,10 +42,6 @@ export class CLI {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
async start() {
|
|
45
|
-
// In built version, we are in dist/index.js (from cli/index.ts) -> core bundled? or just imported.
|
|
46
|
-
// The core logic is now in src/cli/core/src/CLI.ts or dist/core/src/CLI.js
|
|
47
|
-
|
|
48
|
-
// Check for debug flag early
|
|
49
45
|
if (process.argv.includes('--debug')) {
|
|
50
46
|
setDebugMode(true);
|
|
51
47
|
logger.debug('Debug mode enabled via --debug flag');
|
|
@@ -56,18 +52,6 @@ export class CLI {
|
|
|
56
52
|
if (this.config.searchDirectories && this.config.searchDirectories.length > 0) {
|
|
57
53
|
commandsDirs = [...this.config.searchDirectories];
|
|
58
54
|
} else {
|
|
59
|
-
// We assume the standard structure:
|
|
60
|
-
// cli/
|
|
61
|
-
// index.js
|
|
62
|
-
// commands/
|
|
63
|
-
// core/
|
|
64
|
-
// src/
|
|
65
|
-
// CLI.ts
|
|
66
|
-
|
|
67
|
-
// When running from source (ts-node src/cli/index.ts), specific commands are in src/cli/commands.
|
|
68
|
-
// core is in src/cli/core/src.
|
|
69
|
-
// Relative path from CLI.ts to commands: ../../../commands
|
|
70
|
-
|
|
71
55
|
const possibleDirs = [
|
|
72
56
|
path.resolve(__dirname, './src/commands'),
|
|
73
57
|
path.resolve(process.cwd(), 'commands') // Fallback relative to cwd
|
|
@@ -183,12 +167,6 @@ export class CLI {
|
|
|
183
167
|
const options = args.pop(); // last is options
|
|
184
168
|
|
|
185
169
|
if (!subcommand || options.help) {
|
|
186
|
-
// If --help is passed to 'module --help', subcommand might be caught as 'module' if args parsing is weird?
|
|
187
|
-
// ACTUALLY: cac parses 'module add --help' as subcommand="add".
|
|
188
|
-
// 'module --help' might trigger the command itself? No, 'module <subcommand>' expects a subcommand.
|
|
189
|
-
// If I run 'module --help', it might fail validation or parse 'help' as subcommand if unlucky,
|
|
190
|
-
// but likely it just prints help if we didn't override.
|
|
191
|
-
|
|
192
170
|
await this.runHelp([root, subcommand].filter(Boolean));
|
|
193
171
|
return;
|
|
194
172
|
}
|
|
@@ -205,17 +183,7 @@ export class CLI {
|
|
|
205
183
|
}
|
|
206
184
|
|
|
207
185
|
const CommandClass = cmd.class;
|
|
208
|
-
// Map remaining args?
|
|
209
|
-
// The args array contains positional args AFTER subcommand.
|
|
210
|
-
// But we didn't define them in CAC, so they are just strings.
|
|
211
|
-
// We need to map them manually to the Target Command's args definition.
|
|
212
|
-
// argsDef.args usually starts after the command.
|
|
213
|
-
// For 'module add <url>', <url> is the first arg after 'add'.
|
|
214
|
-
// So 'args' here corresponds to <url>.
|
|
215
|
-
|
|
216
186
|
const argsDef = CommandClass.args || {};
|
|
217
|
-
// If using [...args], the variadic args are collected into the first argument array
|
|
218
|
-
// args here is what remains after popping options.
|
|
219
187
|
const positionalArgs = (args.length > 0 && Array.isArray(args[0])) ? args[0] : args;
|
|
220
188
|
|
|
221
189
|
const childOptions = { ...options }; // Copy options
|
|
@@ -242,25 +210,29 @@ export class CLI {
|
|
|
242
210
|
});
|
|
243
211
|
}
|
|
244
212
|
|
|
213
|
+
if (argsDef.options) {
|
|
214
|
+
argsDef.options.forEach((opt: any) => {
|
|
215
|
+
const name = opt.name.replace(/^-+/, ''); // remove leading dashes
|
|
216
|
+
const camelName = name.split(' ')[0].replace(/-([a-z])/g, (g: string) => g[1].toUpperCase());
|
|
217
|
+
|
|
218
|
+
// Check both raw name and camelCase name
|
|
219
|
+
// CAC usually provides camelCase options
|
|
220
|
+
if (childOptions[camelName] === undefined && opt.default !== undefined) {
|
|
221
|
+
childOptions[camelName] = opt.default;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
245
226
|
await this.runCommand(CommandClass, childOptions, cmdParts);
|
|
246
227
|
});
|
|
247
228
|
}
|
|
248
229
|
}
|
|
249
|
-
// Disable default help
|
|
250
|
-
// this.cli.help();
|
|
251
230
|
|
|
252
231
|
// Manually register global help to ensure it's allowed
|
|
253
232
|
this.cli.option('--help, -h', 'Display help');
|
|
254
233
|
|
|
255
234
|
this.cli.version(this.version);
|
|
256
235
|
|
|
257
|
-
// Global help interception for root command
|
|
258
|
-
// If we run `app --help`, we need to catch it.
|
|
259
|
-
// CAC doesn't expose a clean global action without a command content.
|
|
260
|
-
// However, if we parse and no command matches, it usually errors or shows help.
|
|
261
|
-
// If we have default logic, we can put it here?
|
|
262
|
-
// Let's rely on standard parsing but maybe inspect raw args first?
|
|
263
|
-
|
|
264
236
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
265
237
|
// Inspect non-option args to see if there's a command?
|
|
266
238
|
const args = process.argv.slice(2).filter(a => !a.startsWith('-'));
|
|
@@ -280,14 +252,6 @@ export class CLI {
|
|
|
280
252
|
// Simple heuristic: find first non-flag arg as command
|
|
281
253
|
const args = process.argv.slice(2);
|
|
282
254
|
const potentialCommand = args.find(a => !a.startsWith('-'));
|
|
283
|
-
// If it matches a loaded command root, show help for it
|
|
284
|
-
// Otherwise show global help
|
|
285
|
-
|
|
286
|
-
// We need to match 'module add' etc?
|
|
287
|
-
// Just pass the potential command parts to runHelp.
|
|
288
|
-
// runHelp handles filtering itself? No, runHelp takes commandParts to pass to HelpCommand.
|
|
289
|
-
// HelpCommand expects `command` array.
|
|
290
|
-
|
|
291
255
|
const helpArgs = potentialCommand ? [potentialCommand] : [];
|
|
292
256
|
await this.runHelp(helpArgs);
|
|
293
257
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { CLI } from '../../../src/CLI.js';
|
|
3
|
+
import { BaseCommand } from '../../../src/BaseCommand.js';
|
|
4
|
+
import { cac } from 'cac';
|
|
5
|
+
|
|
6
|
+
// Mock CAC to capture command registration
|
|
7
|
+
vi.mock('cac');
|
|
8
|
+
|
|
9
|
+
class MockNestedCommand extends BaseCommand {
|
|
10
|
+
static usage = 'nested command';
|
|
11
|
+
static description = 'A nested command with defaults';
|
|
12
|
+
|
|
13
|
+
// Define args/options
|
|
14
|
+
static args = {
|
|
15
|
+
options: [
|
|
16
|
+
{
|
|
17
|
+
name: '--repo <url>',
|
|
18
|
+
description: 'Repository URL',
|
|
19
|
+
default: 'https://github.com/default/repo'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: '--force',
|
|
23
|
+
description: 'Force action',
|
|
24
|
+
default: false
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: '--dry-run',
|
|
28
|
+
description: 'Dry run',
|
|
29
|
+
default: false
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
async run(options: any) { }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('CLI Nested Command Defaults', () => {
|
|
38
|
+
let cli: CLI;
|
|
39
|
+
let mockCac: any;
|
|
40
|
+
let mockCommand: any;
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.clearAllMocks();
|
|
44
|
+
|
|
45
|
+
mockCommand = {
|
|
46
|
+
option: vi.fn().mockReturnThis(),
|
|
47
|
+
action: vi.fn(),
|
|
48
|
+
allowUnknownOptions: vi.fn().mockReturnThis(),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
mockCac = {
|
|
52
|
+
command: vi.fn().mockReturnValue(mockCommand),
|
|
53
|
+
help: vi.fn(),
|
|
54
|
+
version: vi.fn(),
|
|
55
|
+
parse: vi.fn(),
|
|
56
|
+
option: vi.fn().mockReturnThis(),
|
|
57
|
+
outputHelp: vi.fn(),
|
|
58
|
+
};
|
|
59
|
+
(cac as any).mockReturnValue(mockCac);
|
|
60
|
+
|
|
61
|
+
cli = new CLI({ commandName: 'test-cli' });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
vi.restoreAllMocks();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should apply default option values for nested subcommands', async () => {
|
|
69
|
+
const runCommandSpy = vi.spyOn(cli as any, 'runCommand');
|
|
70
|
+
vi.spyOn(MockNestedCommand.prototype, 'init').mockResolvedValue(undefined);
|
|
71
|
+
|
|
72
|
+
// Mock loader
|
|
73
|
+
vi.spyOn((cli as any).loader, 'load').mockResolvedValue(undefined);
|
|
74
|
+
vi.spyOn((cli as any).loader, 'getCommands').mockReturnValue([
|
|
75
|
+
{
|
|
76
|
+
command: 'nested command',
|
|
77
|
+
class: MockNestedCommand
|
|
78
|
+
}
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
// Start to register commands
|
|
82
|
+
await cli.start();
|
|
83
|
+
|
|
84
|
+
// Expect command to be registered
|
|
85
|
+
// "nested" is root, "nested [subcommand] [...args]" is registered
|
|
86
|
+
const commandCall = mockCac.command.mock.calls.find((call: any) => call[0].startsWith('nested'));
|
|
87
|
+
expect(commandCall).toBeDefined();
|
|
88
|
+
|
|
89
|
+
// Get the action handler
|
|
90
|
+
const actionFn = mockCommand.action.mock.calls[0][0];
|
|
91
|
+
|
|
92
|
+
// Simulate invocation: nested command (subcommand="command")
|
|
93
|
+
// options undefined/empty implies defaults should be applied
|
|
94
|
+
await actionFn('command', {});
|
|
95
|
+
|
|
96
|
+
expect(runCommandSpy).toHaveBeenCalled();
|
|
97
|
+
const calledOptions = runCommandSpy.mock.calls[0][1];
|
|
98
|
+
|
|
99
|
+
expect(calledOptions).toHaveProperty('repo', 'https://github.com/default/repo');
|
|
100
|
+
expect(calledOptions).toHaveProperty('force', false);
|
|
101
|
+
expect(calledOptions).toHaveProperty('dryRun', false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should allow overriding default values', async () => {
|
|
105
|
+
const runCommandSpy = vi.spyOn(cli as any, 'runCommand');
|
|
106
|
+
vi.spyOn(MockNestedCommand.prototype, 'init').mockResolvedValue(undefined);
|
|
107
|
+
|
|
108
|
+
vi.spyOn((cli as any).loader, 'load').mockResolvedValue(undefined);
|
|
109
|
+
vi.spyOn((cli as any).loader, 'getCommands').mockReturnValue([
|
|
110
|
+
{
|
|
111
|
+
command: 'nested command',
|
|
112
|
+
class: MockNestedCommand
|
|
113
|
+
}
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
await cli.start();
|
|
117
|
+
|
|
118
|
+
const actionFn = mockCommand.action.mock.calls[0][0];
|
|
119
|
+
|
|
120
|
+
// Simulate invocation with custom options
|
|
121
|
+
await actionFn('command', { repo: 'custom-repo' });
|
|
122
|
+
|
|
123
|
+
expect(runCommandSpy).toHaveBeenCalled();
|
|
124
|
+
const calledOptions = runCommandSpy.mock.calls[0][1];
|
|
125
|
+
|
|
126
|
+
expect(calledOptions).toHaveProperty('repo', 'custom-repo');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -1 +0,0 @@
|
|
|
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 version?: string;\n commandName?: string;\n searchDirectories?: string[];\n}\n\nexport class CLI {\n public name: string;\n public version: 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.version = this.config.version || pkg.version;\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 }\n }\n }\n\n // Always add core commands directory (where help command lives)\n const coreCommandsDir = path.resolve(__dirname, './commands');\n const coreCommandsDirSrc = path.resolve(__dirname, './src/commands');\n\n if (fs.existsSync(coreCommandsDir)) {\n commandsDirs.push(coreCommandsDir);\n } else if (fs.existsSync(coreCommandsDirSrc)) {\n // Fallback for tsup chunking which puts CLI in dist/ but keeps src/commands in dist/src/commands\n commandsDirs.push(coreCommandsDirSrc);\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(this.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.runInit(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.10\",\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}\n"],"mappings":"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","names":[]}
|