@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/CHANGELOG.md +2 -9
- package/__tests__/cli-contract.test.ts +100 -1
- package/__tests__/core.test.ts +131 -2
- package/__tests__/diagnostics.test.ts +24 -0
- package/__tests__/file-workflows.test.ts +39 -0
- package/__tests__/interactive.test.ts +15 -0
- package/dist/commands/generate.js +26 -7
- package/dist/commands/init.js +8 -6
- package/dist/commands/metrics.js +12 -2
- package/dist/commands/review.js +19 -2
- package/dist/commands/security-check.js +35 -2
- package/dist/commands/status.js +4 -2
- package/dist/config/config.d.ts +3 -0
- package/dist/config/config.js +22 -6
- package/dist/core/command-runner.js +8 -2
- package/dist/core/metrics.js +3 -2
- package/dist/index.js +65 -13
- package/dist/utils/projectType.d.ts +6 -0
- package/dist/utils/projectType.js +76 -17
- package/package.json +1 -1
- package/src/commands/generate.ts +27 -7
- package/src/commands/init.ts +8 -6
- package/src/commands/metrics.ts +11 -2
- package/src/commands/review.ts +20 -2
- package/src/commands/security-check.ts +33 -2
- package/src/commands/status.ts +4 -2
- package/src/config/config.ts +24 -4
- package/src/core/command-runner.ts +8 -2
- package/src/core/metrics.ts +3 -1
- package/src/index.ts +65 -13
- package/src/utils/projectType.ts +75 -14
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,
|
|
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,
|
|
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,
|
|
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((
|
|
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((
|
|
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
|
-
//
|
|
126
|
+
// Set in-memory config overrides for the session
|
|
127
127
|
const configModule = await import('./config/config.js');
|
|
128
|
-
configModule.
|
|
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
|
|
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
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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;
|
package/src/utils/projectType.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
20
|
-
if (dependencies
|
|
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
|
-
|
|
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
|
}
|