@rahul05ranjan/dhruv-cli 1.5.0 → 1.6.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/src/index.ts CHANGED
@@ -53,7 +53,7 @@ program
53
53
  .command('review <fileOrDir>')
54
54
  .description(commandDescription('review'))
55
55
  .option('--diff', 'Review the current uncommitted git diff')
56
- .action((fileOrDir: string, command: Command) => review(fileOrDir, command.opts()));
56
+ .action((fileOrDir: string, options: Record<string, any>) => review(fileOrDir, options));
57
57
 
58
58
  program
59
59
  .command('optimize <file>')
@@ -64,7 +64,7 @@ program
64
64
  .command('security-check [fileOrDir]')
65
65
  .description(commandDescription('security-check'))
66
66
  .option('--strict', 'Exit with failure when high-confidence findings are detected')
67
- .action((fileOrDir: string | undefined, command: Command) => securityCheck(fileOrDir, command.opts()));
67
+ .action((fileOrDir: string | undefined, options: Record<string, any>) => securityCheck(fileOrDir, options));
68
68
 
69
69
  program
70
70
  .command('generate <type> <target>')
@@ -72,7 +72,7 @@ program
72
72
  .option('--apply', 'Write generated tests to disk (preview is the default)')
73
73
  .option('--output <path>', 'Write generated tests to this path')
74
74
  .option('--overwrite', 'Allow replacing an existing output file')
75
- .action((type: string, target: string, command: Command) => generate(type, target, command.opts()));
75
+ .action((type: string, target: string, options: Record<string, any>) => generate(type, target, options));
76
76
 
77
77
  program
78
78
  .command('init')
@@ -88,14 +88,14 @@ program
88
88
  .command('health')
89
89
  .description(commandDescription('health'))
90
90
  .option('--details', 'Show every health check and diagnostic detail')
91
- .action((command: Command) => health(command.opts()));
91
+ .action((options: Record<string, any>) => health(options));
92
92
 
93
93
  program
94
94
  .command('metrics')
95
95
  .description(commandDescription('metrics'))
96
96
  .option('--raw', 'Export raw Prometheus metrics')
97
97
  .option('--reset', 'Clear persisted local metrics')
98
- .action((command: Command) => metrics(command.opts()));
98
+ .action((options: Record<string, any>) => metrics(options));
99
99
 
100
100
  program
101
101
  .command('project-type')
@@ -123,9 +123,9 @@ program
123
123
  if (opts.verbose) config.verbose = true;
124
124
  if (opts.json) config.responseFormat = 'json';
125
125
  if (opts.timeout) config.timeoutMs = Number(opts.timeout);
126
- // Save config for session
126
+ // Set in-memory config overrides for the session
127
127
  const configModule = await import('./config/config.js');
128
- configModule.saveConfig(config);
128
+ configModule.setSessionConfig(config);
129
129
  }
130
130
  });
131
131
 
@@ -184,18 +184,70 @@ program
184
184
  let script = '';
185
185
  switch (shell) {
186
186
  case 'zsh':
187
- script = `#compdef dhruv\n_dhruv_completion() {\n _arguments '1:command:(${commands})' '*:option:(${options})'\n}\ncompdef _dhruv_completion dhruv`;
187
+ script = `#compdef dhruv
188
+ _dhruv_completion() {
189
+ local -a commands
190
+ commands=(${commands})
191
+ _arguments -C \\
192
+ '1:command:->cmds' \\
193
+ '*::options:->args'
194
+ case "$state" in
195
+ cmds)
196
+ _describe -t commands 'dhruv command' commands
197
+ ;;
198
+ args)
199
+ case $words[1] in
200
+ generate)
201
+ _arguments '1:type:(tests documentation docs component)' '*:file:_files'
202
+ ;;
203
+ review|optimize|security-check)
204
+ _arguments '*:file:_files'
205
+ ;;
206
+ completion)
207
+ _arguments '1:shell:(bash zsh fish)'
208
+ ;;
209
+ *)
210
+ _arguments '*:options:(${options})'
211
+ ;;
212
+ esac
213
+ ;;
214
+ esac
215
+ }
216
+ compdef _dhruv_completion dhruv`;
188
217
  break;
189
218
  case 'fish':
190
- script = `complete -c dhruv -f -n '__fish_use_subcommand' -a '${commands}'\ncomplete -c dhruv -f -n 'not __fish_use_subcommand' -a '${options}'`;
219
+ script = `complete -c dhruv -f -n '__fish_use_subcommand' -a '${commands}'\ncomplete -c dhruv -f -n '__fish_seen_subcommand_from generate' -a 'tests documentation docs component'\ncomplete -c dhruv -f -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish'\ncomplete -c dhruv -f -n 'not __fish_use_subcommand' -a '${options}'`;
191
220
  break;
192
221
  case 'bash':
193
222
  script = String.raw`#!/bin/bash
194
223
  _dhruv_completion() {
195
- local commands="${commands}"
196
- local options="${options}"
197
- local choices="$commands $options"
198
- COMPREPLY=( $(compgen -W "$choices" -- "\${COMP_WORDS[COMP_CWORD]}") )
224
+ local cur prev commands options
225
+ COMPREPLY=()
226
+ cur="\${COMP_WORDS[COMP_CWORD]}"
227
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
228
+ commands="${commands}"
229
+ options="${options}"
230
+
231
+ if [[ "$prev" == "generate" ]]; then
232
+ COMPREPLY=( $(compgen -W "tests documentation docs component" -- "$cur") )
233
+ return 0
234
+ fi
235
+ if [[ "$prev" == "completion" ]]; then
236
+ COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
237
+ return 0
238
+ fi
239
+ if [[ "$prev" == "review" || "$prev" == "optimize" || "$prev" == "security-check" ]]; then
240
+ COMPREPLY=( $(compgen -f -- "$cur") )
241
+ return 0
242
+ fi
243
+
244
+ if [[ "$cur" == -* ]]; then
245
+ COMPREPLY=( $(compgen -W "$options" -- "$cur") )
246
+ elif [[ $COMP_CWORD -eq 1 ]]; then
247
+ COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
248
+ else
249
+ COMPREPLY=( $(compgen -W "$commands $options" -- "$cur") )
250
+ fi
199
251
  }
200
252
  complete -F _dhruv_completion dhruv`;
201
253
  break;
@@ -1,29 +1,90 @@
1
1
  // Use .js extension for ESM compatibility
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
+ import { logger } from '../core/logger.js';
4
5
 
5
- export function detectProjectType(directory: string = process.cwd()): string {
6
+ export interface ProjectContext {
7
+ type: string;
8
+ framework?: string;
9
+ diagnostic?: string;
10
+ }
11
+
12
+ export function detectProjectDetails(directory: string = process.cwd()): ProjectContext {
6
13
  const file = (name: string) => path.join(directory, name);
7
14
 
8
15
  if (fs.existsSync(file('package.json'))) {
9
16
  let pkg: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> };
10
17
  try {
11
18
  pkg = JSON.parse(fs.readFileSync(file('package.json'), 'utf-8'));
12
- } catch {
13
- return 'unknown';
19
+ } catch (err) {
20
+ const diagnostic = `Malformed package.json in ${directory}: ${(err as Error).message}`;
21
+ logger.warn(diagnostic);
22
+ return { type: 'unknown', diagnostic };
14
23
  }
15
24
 
16
25
  const dependencies = { ...pkg.dependencies, ...pkg.devDependencies };
17
- if (dependencies.react) return 'react';
18
- if (dependencies.next) return 'nextjs';
19
- if (dependencies.express) return 'node-express';
20
- if (dependencies.typescript || fs.existsSync(file('tsconfig.json'))) return 'node-typescript';
21
- return 'node';
26
+ if (dependencies.react) return { type: 'node', framework: 'react' };
27
+ if (dependencies.next) return { type: 'node', framework: 'nextjs' };
28
+ if (dependencies.vue) return { type: 'node', framework: 'vue' };
29
+ if (dependencies['@angular/core']) return { type: 'node', framework: 'angular' };
30
+ if (dependencies.svelte) return { type: 'node', framework: 'svelte' };
31
+ if (dependencies['@nestjs/core']) return { type: 'node', framework: 'nestjs' };
32
+ if (dependencies.express) return { type: 'node', framework: 'node-express' };
33
+ if (dependencies.typescript || fs.existsSync(file('tsconfig.json'))) return { type: 'node-typescript' };
34
+ return { type: 'node' };
35
+ }
36
+
37
+ if (fs.existsSync(file('tsconfig.json'))) {
38
+ return { type: 'node-typescript' };
39
+ }
40
+
41
+ if (fs.existsSync(file('requirements.txt')) || fs.existsSync(file('pyproject.toml')) || fs.existsSync(file('Pipfile')) || fs.existsSync(file('setup.py'))) {
42
+ let framework: string | undefined;
43
+ if (fs.existsSync(file('manage.py'))) {
44
+ framework = 'django';
45
+ } else if (fs.existsSync(file('requirements.txt'))) {
46
+ try {
47
+ const reqs = fs.readFileSync(file('requirements.txt'), 'utf-8');
48
+ if (/fastapi/i.test(reqs)) framework = 'fastapi';
49
+ else if (/flask/i.test(reqs)) framework = 'flask';
50
+ else if (/django/i.test(reqs)) framework = 'django';
51
+ } catch (err) {
52
+ const diagnostic = `Error reading requirements.txt: ${(err as Error).message}`;
53
+ logger.warn(diagnostic);
54
+ return { type: 'python', diagnostic };
55
+ }
56
+ }
57
+ return { type: 'python', framework };
58
+ }
59
+
60
+ if (fs.existsSync(file('go.mod'))) return { type: 'go' };
61
+ if (fs.existsSync(file('Cargo.toml'))) return { type: 'rust' };
62
+ if (fs.existsSync(file('pom.xml')) || fs.existsSync(file('build.gradle')) || fs.existsSync(file('build.gradle.kts'))) {
63
+ let framework: string | undefined;
64
+ try {
65
+ const pomPath = file('pom.xml');
66
+ const gradlePath = file('build.gradle');
67
+ const content = fs.existsSync(pomPath)
68
+ ? fs.readFileSync(pomPath, 'utf-8')
69
+ : (fs.existsSync(gradlePath) ? fs.readFileSync(gradlePath, 'utf-8') : '');
70
+ if (/spring-boot/i.test(content)) framework = 'spring-boot';
71
+ } catch {
72
+ // safe fallback
73
+ }
74
+ return { type: 'java', framework };
75
+ }
76
+
77
+ return { type: 'unknown' };
78
+ }
79
+
80
+ export function detectProjectType(directory: string = process.cwd()): string {
81
+ const details = detectProjectDetails(directory);
82
+ if (details.type === 'unknown') return 'unknown';
83
+ if (details.framework) {
84
+ if (details.framework === 'react' || details.framework === 'nextjs' || details.framework === 'node-express') {
85
+ return details.framework;
86
+ }
87
+ return `${details.type}-${details.framework}`;
22
88
  }
23
- if (fs.existsSync(file('requirements.txt'))) return 'python';
24
- if (fs.existsSync(file('pyproject.toml'))) return 'python';
25
- if (fs.existsSync(file('go.mod'))) return 'go';
26
- if (fs.existsSync(file('Cargo.toml'))) return 'rust';
27
- if (fs.existsSync(file('pom.xml')) || fs.existsSync(file('build.gradle'))) return 'java';
28
- return 'unknown';
89
+ return details.type;
29
90
  }