@rahul05ranjan/dhruv-cli 1.4.6 → 1.6.0
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/.github/workflows/ci.yml +18 -238
- package/.github/workflows/contribution.yml +6 -141
- package/.github/workflows/dependabot-auto-merge.yml +1 -0
- package/.github/workflows/labeler.yml +1 -0
- package/.github/workflows/security.yml +3 -0
- package/CHANGELOG.md +16 -4
- package/README.md +145 -40
- package/__tests__/cli-contract.test.ts +170 -0
- package/__tests__/core.test.ts +193 -1
- package/__tests__/diagnostics.test.ts +179 -0
- package/__tests__/file-workflows.test.ts +234 -0
- package/__tests__/interactive.test.ts +134 -0
- package/__tests__/setup.ts +1 -0
- package/__tests__/workflows.test.ts +27 -4
- package/dist/commands/generate.d.ts +6 -1
- package/dist/commands/generate.js +44 -11
- package/dist/commands/health.d.ts +4 -1
- package/dist/commands/health.js +59 -16
- package/dist/commands/init.js +18 -7
- package/dist/commands/menu.js +125 -100
- package/dist/commands/metrics.d.ts +5 -1
- package/dist/commands/metrics.js +49 -10
- package/dist/commands/optimize.js +1 -1
- package/dist/commands/review.d.ts +4 -1
- package/dist/commands/review.js +66 -9
- package/dist/commands/security-check.d.ts +4 -1
- package/dist/commands/security-check.js +100 -6
- package/dist/commands/status.js +44 -4
- package/dist/config/config.d.ts +5 -1
- package/dist/config/config.js +24 -7
- package/dist/core/ai.d.ts +11 -0
- package/dist/core/ai.js +33 -9
- package/dist/core/command-catalog.d.ts +10 -0
- package/dist/core/command-catalog.js +27 -0
- package/dist/core/command-runner.js +106 -21
- package/dist/core/logger.js +1 -0
- package/dist/core/metrics.d.ts +28 -0
- package/dist/core/metrics.js +79 -0
- package/dist/index.js +98 -24
- package/dist/utils/projectType.d.ts +7 -1
- package/dist/utils/projectType.js +91 -13
- package/docs/api/assets/highlight.css +4 -4
- package/docs/api/index.html +161 -39
- package/docs/api/media/CONTRIBUTING.md +60 -0
- package/docs/api/media/SECURITY.md +8 -0
- package/docs/api/media/dhruv-cli-preview.svg +42 -0
- package/docs/api/media/publishing-fix.md +34 -0
- package/docs/dhruv-cli-preview.svg +42 -0
- package/docs/index.html +631 -533
- package/docs/publishing-fix.md +34 -0
- package/package.json +1 -1
- package/src/commands/generate.ts +50 -11
- package/src/commands/health.ts +62 -17
- package/src/commands/init.ts +18 -7
- package/src/commands/menu.ts +54 -30
- package/src/commands/metrics.ts +53 -9
- package/src/commands/optimize.ts +1 -1
- package/src/commands/review.ts +72 -9
- package/src/commands/security-check.ts +111 -6
- package/src/commands/status.ts +43 -5
- package/src/config/config.ts +26 -7
- package/src/core/ai.ts +36 -8
- package/src/core/command-catalog.ts +37 -0
- package/src/core/command-runner.ts +108 -22
- package/src/core/logger.ts +1 -0
- package/src/core/metrics.ts +105 -0
- package/src/index.ts +97 -24
- package/src/utils/projectType.ts +85 -9
- package/tsconfig.json +1 -1
- package/.github/workflows/auto-assign.yml +0 -14
- package/.github/workflows/build-publish.yml +0 -154
- package/.github/workflows/deploy.yml +0 -336
- package/.github/workflows/monitoring.yml +0 -270
- package/PUBLISHING_FIX.md +0 -92
- package/logs/.8a99b6cf655346317fdbf29f4fffcf91131432f3-audit.json +0 -15
- package/logs/.eee104bf8fff5ecd38a6a2842df260de6470a7c3-audit.json +0 -15
package/src/core/metrics.ts
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
import promClient from 'prom-client';
|
|
2
2
|
import { logger } from './logger.js';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
export interface CommandSummary {
|
|
7
|
+
runs: number;
|
|
8
|
+
successes: number;
|
|
9
|
+
failures: number;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ModelSummary {
|
|
14
|
+
requests: number;
|
|
15
|
+
successes: number;
|
|
16
|
+
failures: number;
|
|
17
|
+
durationMs: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CacheSummary {
|
|
21
|
+
hits: number;
|
|
22
|
+
misses: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface MetricsSummary {
|
|
26
|
+
sessions: number;
|
|
27
|
+
commands: Record<string, CommandSummary>;
|
|
28
|
+
errors: Record<string, number>;
|
|
29
|
+
models: Record<string, ModelSummary>;
|
|
30
|
+
cache: CacheSummary;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function emptySummary(): MetricsSummary {
|
|
34
|
+
return { sessions: 0, commands: {}, errors: {}, models: {}, cache: { hits: 0, misses: 0 } };
|
|
35
|
+
}
|
|
3
36
|
|
|
4
37
|
// Create a Registry which registers the metrics
|
|
5
38
|
const register = new promClient.Registry();
|
|
@@ -122,6 +155,14 @@ export class MetricsCollector {
|
|
|
122
155
|
try {
|
|
123
156
|
metrics.commandDuration.observe({ command, success: success.toString() }, duration / 1000);
|
|
124
157
|
metrics.commandCount.inc({ command, success: success.toString() });
|
|
158
|
+
this.updatePersistent((summary) => {
|
|
159
|
+
const current = summary.commands[command] ?? { runs: 0, successes: 0, failures: 0, durationMs: 0 };
|
|
160
|
+
current.runs += 1;
|
|
161
|
+
if (success) current.successes += 1;
|
|
162
|
+
else current.failures += 1;
|
|
163
|
+
current.durationMs += duration;
|
|
164
|
+
summary.commands[command] = current;
|
|
165
|
+
});
|
|
125
166
|
} catch (error) {
|
|
126
167
|
logger.error('Failed to record command metrics', error as Error);
|
|
127
168
|
}
|
|
@@ -137,6 +178,14 @@ export class MetricsCollector {
|
|
|
137
178
|
if (tokensUsed) {
|
|
138
179
|
metrics.aiTokensUsed.inc({ model, command_type: commandType }, tokensUsed);
|
|
139
180
|
}
|
|
181
|
+
this.updatePersistent((summary) => {
|
|
182
|
+
const current = summary.models[model] ?? { requests: 0, successes: 0, failures: 0, durationMs: 0 };
|
|
183
|
+
current.requests += 1;
|
|
184
|
+
if (success) current.successes += 1;
|
|
185
|
+
else current.failures += 1;
|
|
186
|
+
current.durationMs += duration;
|
|
187
|
+
summary.models[model] = current;
|
|
188
|
+
});
|
|
140
189
|
} catch (error) {
|
|
141
190
|
logger.error('Failed to record AI request metrics', error as Error);
|
|
142
191
|
}
|
|
@@ -147,6 +196,9 @@ export class MetricsCollector {
|
|
|
147
196
|
|
|
148
197
|
try {
|
|
149
198
|
metrics.cacheHitCount.inc({ cache_type: cacheType });
|
|
199
|
+
this.updatePersistent((summary) => {
|
|
200
|
+
summary.cache.hits += 1;
|
|
201
|
+
});
|
|
150
202
|
} catch (error) {
|
|
151
203
|
logger.error('Failed to record cache hit metrics', error as Error);
|
|
152
204
|
}
|
|
@@ -157,6 +209,9 @@ export class MetricsCollector {
|
|
|
157
209
|
|
|
158
210
|
try {
|
|
159
211
|
metrics.cacheMissCount.inc({ cache_type: cacheType });
|
|
212
|
+
this.updatePersistent((summary) => {
|
|
213
|
+
summary.cache.misses += 1;
|
|
214
|
+
});
|
|
160
215
|
} catch (error) {
|
|
161
216
|
logger.error('Failed to record cache miss metrics', error as Error);
|
|
162
217
|
}
|
|
@@ -177,6 +232,10 @@ export class MetricsCollector {
|
|
|
177
232
|
|
|
178
233
|
try {
|
|
179
234
|
metrics.errorCount.inc({ error_type: errorType, command: command || 'unknown' });
|
|
235
|
+
this.updatePersistent((summary) => {
|
|
236
|
+
const key = command ? `${errorType}:${command}` : errorType;
|
|
237
|
+
summary.errors[key] = (summary.errors[key] ?? 0) + 1;
|
|
238
|
+
});
|
|
180
239
|
} catch (error) {
|
|
181
240
|
logger.error('Failed to record error metrics', error as Error);
|
|
182
241
|
}
|
|
@@ -201,6 +260,9 @@ export class MetricsCollector {
|
|
|
201
260
|
|
|
202
261
|
try {
|
|
203
262
|
metrics.sessionCount.inc();
|
|
263
|
+
this.updatePersistent((summary) => {
|
|
264
|
+
summary.sessions += 1;
|
|
265
|
+
});
|
|
204
266
|
} catch (error) {
|
|
205
267
|
logger.error('Failed to record session metrics', error as Error);
|
|
206
268
|
}
|
|
@@ -216,6 +278,49 @@ export class MetricsCollector {
|
|
|
216
278
|
}
|
|
217
279
|
}
|
|
218
280
|
|
|
281
|
+
public getSummary(): MetricsSummary {
|
|
282
|
+
return this.readPersistent();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public resetPersistent(): void {
|
|
286
|
+
try {
|
|
287
|
+
fs.unlinkSync(this.persistentPath());
|
|
288
|
+
} catch (error) {
|
|
289
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
290
|
+
logger.debug('Failed to unlink persistent metrics', { error: (error as Error).message });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private persistentPath(): string {
|
|
296
|
+
return path.join(process.cwd(), '.dhruv-metrics.json');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private readPersistent(): MetricsSummary {
|
|
300
|
+
try {
|
|
301
|
+
const parsed = JSON.parse(fs.readFileSync(this.persistentPath(), 'utf8')) as Partial<MetricsSummary>;
|
|
302
|
+
return {
|
|
303
|
+
sessions: typeof parsed.sessions === 'number' ? parsed.sessions : 0,
|
|
304
|
+
commands: parsed.commands ?? {},
|
|
305
|
+
errors: parsed.errors ?? {},
|
|
306
|
+
models: parsed.models ?? {},
|
|
307
|
+
cache: parsed.cache ?? { hits: 0, misses: 0 },
|
|
308
|
+
};
|
|
309
|
+
} catch {
|
|
310
|
+
return emptySummary();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private updatePersistent(update: (summary: MetricsSummary) => void): void {
|
|
315
|
+
try {
|
|
316
|
+
const summary = this.readPersistent();
|
|
317
|
+
update(summary);
|
|
318
|
+
fs.writeFileSync(this.persistentPath(), JSON.stringify(summary, null, 2));
|
|
319
|
+
} catch (error) {
|
|
320
|
+
logger.debug('Failed to persist metrics', { error: (error as Error).message });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
219
324
|
public async getMetrics(): Promise<string> {
|
|
220
325
|
return register.metrics();
|
|
221
326
|
}
|
package/src/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { createRequire } from 'module';
|
|
|
20
20
|
import { logger, logCommand, logInfo, logError } from './core/logger.js';
|
|
21
21
|
import { metricsCollector } from './core/metrics.js';
|
|
22
22
|
import { securityManager } from './core/security.js';
|
|
23
|
+
import { commandDescription, completionCommands, completionOptions } from './core/command-catalog.js';
|
|
23
24
|
const require = createRequire(import.meta.url);
|
|
24
25
|
const pkg = require('../package.json');
|
|
25
26
|
|
|
@@ -32,65 +33,73 @@ program
|
|
|
32
33
|
|
|
33
34
|
program
|
|
34
35
|
.command('explain <query>')
|
|
35
|
-
.description('
|
|
36
|
+
.description(commandDescription('explain'))
|
|
36
37
|
.addHelpText('after', '\nExamples:\n $ dhruv explain "What is async/await?"\n $ dhruv explain "Docker containers vs VMs"')
|
|
37
38
|
.action(explain);
|
|
38
39
|
|
|
39
40
|
program
|
|
40
41
|
.command('suggest <query>')
|
|
41
|
-
.description('
|
|
42
|
+
.description(commandDescription('suggest'))
|
|
42
43
|
.addHelpText('after', '\nExamples:\n $ dhruv suggest "React performance optimization"\n $ dhruv suggest "Node.js project structure"')
|
|
43
44
|
.action(suggest);
|
|
44
45
|
|
|
45
46
|
program
|
|
46
47
|
.command('fix <query>')
|
|
47
|
-
.description('
|
|
48
|
+
.description(commandDescription('fix'))
|
|
48
49
|
.addHelpText('after', '\nExamples:\n $ dhruv fix "TypeError: Cannot read property of undefined"\n $ dhruv fix "CORS error in Express.js"')
|
|
49
50
|
.action(fix);
|
|
50
51
|
|
|
51
52
|
program
|
|
52
53
|
.command('review <fileOrDir>')
|
|
53
|
-
.description('
|
|
54
|
-
.
|
|
54
|
+
.description(commandDescription('review'))
|
|
55
|
+
.option('--diff', 'Review the current uncommitted git diff')
|
|
56
|
+
.action((fileOrDir: string, options: Record<string, any>) => review(fileOrDir, options));
|
|
55
57
|
|
|
56
58
|
program
|
|
57
59
|
.command('optimize <file>')
|
|
58
|
-
.description(
|
|
60
|
+
.description(commandDescription('optimize'))
|
|
59
61
|
.action(optimize);
|
|
60
62
|
|
|
61
63
|
program
|
|
62
64
|
.command('security-check [fileOrDir]')
|
|
63
|
-
.description('
|
|
64
|
-
.
|
|
65
|
+
.description(commandDescription('security-check'))
|
|
66
|
+
.option('--strict', 'Exit with failure when high-confidence findings are detected')
|
|
67
|
+
.action((fileOrDir: string | undefined, options: Record<string, any>) => securityCheck(fileOrDir, options));
|
|
65
68
|
|
|
66
69
|
program
|
|
67
70
|
.command('generate <type> <target>')
|
|
68
|
-
.description('
|
|
69
|
-
.
|
|
71
|
+
.description(commandDescription('generate'))
|
|
72
|
+
.option('--apply', 'Write generated tests to disk (preview is the default)')
|
|
73
|
+
.option('--output <path>', 'Write generated tests to this path')
|
|
74
|
+
.option('--overwrite', 'Allow replacing an existing output file')
|
|
75
|
+
.action((type: string, target: string, options: Record<string, any>) => generate(type, target, options));
|
|
70
76
|
|
|
71
77
|
program
|
|
72
78
|
.command('init')
|
|
73
|
-
.description('
|
|
79
|
+
.description(commandDescription('init'))
|
|
74
80
|
.action(init);
|
|
75
81
|
|
|
76
82
|
program
|
|
77
83
|
.command('status')
|
|
78
|
-
.description('
|
|
84
|
+
.description(commandDescription('status'))
|
|
79
85
|
.action(status);
|
|
80
86
|
|
|
81
87
|
program
|
|
82
88
|
.command('health')
|
|
83
|
-
.description('
|
|
84
|
-
.
|
|
89
|
+
.description(commandDescription('health'))
|
|
90
|
+
.option('--details', 'Show every health check and diagnostic detail')
|
|
91
|
+
.action((options: Record<string, any>) => health(options));
|
|
85
92
|
|
|
86
93
|
program
|
|
87
94
|
.command('metrics')
|
|
88
|
-
.description('
|
|
89
|
-
.
|
|
95
|
+
.description(commandDescription('metrics'))
|
|
96
|
+
.option('--raw', 'Export raw Prometheus metrics')
|
|
97
|
+
.option('--reset', 'Clear persisted local metrics')
|
|
98
|
+
.action((options: Record<string, any>) => metrics(options));
|
|
90
99
|
|
|
91
100
|
program
|
|
92
101
|
.command('project-type')
|
|
93
|
-
.description('
|
|
102
|
+
.description(commandDescription('project-type'))
|
|
94
103
|
.action(() => {
|
|
95
104
|
const type = detectProjectType();
|
|
96
105
|
console.log(chalk.blue(`Detected project type: ${type}`));
|
|
@@ -98,20 +107,22 @@ program
|
|
|
98
107
|
|
|
99
108
|
program
|
|
100
109
|
.command('menu')
|
|
101
|
-
.description('
|
|
110
|
+
.description(commandDescription('menu'))
|
|
102
111
|
.action(menu);
|
|
103
112
|
|
|
104
113
|
program
|
|
105
114
|
.option('--model <model>', 'Set Ollama model')
|
|
106
115
|
.option('--verbose', 'Enable verbose output')
|
|
107
116
|
.option('--json', 'Output in JSON format')
|
|
117
|
+
.option('--timeout <milliseconds>', 'Set the AI request timeout')
|
|
108
118
|
.hook('preAction', async (thisCommand) => {
|
|
109
119
|
const opts = thisCommand.opts();
|
|
110
|
-
if (opts.model || opts.verbose || opts.json) {
|
|
120
|
+
if (opts.model || opts.verbose || opts.json || opts.timeout) {
|
|
111
121
|
const config: Record<string, unknown> = {};
|
|
112
122
|
if (opts.model) config.model = opts.model;
|
|
113
123
|
if (opts.verbose) config.verbose = true;
|
|
114
124
|
if (opts.json) config.responseFormat = 'json';
|
|
125
|
+
if (opts.timeout) config.timeoutMs = Number(opts.timeout);
|
|
115
126
|
// Save config for session
|
|
116
127
|
const configModule = await import('./config/config.js');
|
|
117
128
|
configModule.saveConfig(config);
|
|
@@ -165,23 +176,85 @@ async function loadPlugins(program: unknown) {
|
|
|
165
176
|
// Autocomplete: Generate shell completion scripts
|
|
166
177
|
program
|
|
167
178
|
.command('completion')
|
|
168
|
-
.description('
|
|
179
|
+
.description(commandDescription('completion'))
|
|
169
180
|
.argument('[shell]', 'shell type (bash|zsh|fish)', 'bash')
|
|
170
181
|
.action((shell: string) => {
|
|
182
|
+
const commands = completionCommands();
|
|
183
|
+
const options = completionOptions();
|
|
171
184
|
let script = '';
|
|
172
185
|
switch (shell) {
|
|
173
186
|
case 'zsh':
|
|
174
|
-
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`;
|
|
175
217
|
break;
|
|
176
218
|
case 'fish':
|
|
177
|
-
script = `
|
|
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}'`;
|
|
178
220
|
break;
|
|
179
|
-
|
|
221
|
+
case 'bash':
|
|
180
222
|
script = String.raw`#!/bin/bash
|
|
181
223
|
_dhruv_completion() {
|
|
182
|
-
|
|
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
|
|
183
251
|
}
|
|
184
252
|
complete -F _dhruv_completion dhruv`;
|
|
253
|
+
break;
|
|
254
|
+
default:
|
|
255
|
+
console.error(chalk.red(`Unsupported shell "${shell}". Choose bash, zsh, or fish.`));
|
|
256
|
+
process.exitCode = 2;
|
|
257
|
+
return;
|
|
185
258
|
}
|
|
186
259
|
console.log(script);
|
|
187
260
|
console.log(`\n# To enable tab completion, add the above to your shell profile or source it directly.`);
|
package/src/utils/projectType.ts
CHANGED
|
@@ -1,14 +1,90 @@
|
|
|
1
1
|
// Use .js extension for ESM compatibility
|
|
2
2
|
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { logger } from '../core/logger.js';
|
|
3
5
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
export interface ProjectContext {
|
|
7
|
+
type: string;
|
|
8
|
+
framework?: string;
|
|
9
|
+
diagnostic?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function detectProjectDetails(directory: string = process.cwd()): ProjectContext {
|
|
13
|
+
const file = (name: string) => path.join(directory, name);
|
|
14
|
+
|
|
15
|
+
if (fs.existsSync(file('package.json'))) {
|
|
16
|
+
let pkg: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> };
|
|
17
|
+
try {
|
|
18
|
+
pkg = JSON.parse(fs.readFileSync(file('package.json'), 'utf-8'));
|
|
19
|
+
} catch (err) {
|
|
20
|
+
const diagnostic = `Malformed package.json in ${directory}: ${(err as Error).message}`;
|
|
21
|
+
logger.warn(diagnostic);
|
|
22
|
+
return { type: 'unknown', diagnostic };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const dependencies = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
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}`;
|
|
10
88
|
}
|
|
11
|
-
|
|
12
|
-
if (fs.existsSync('pyproject.toml')) return 'python';
|
|
13
|
-
return 'unknown';
|
|
89
|
+
return details.type;
|
|
14
90
|
}
|
package/tsconfig.json
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
name: Auto Assign
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
types: [opened, reopened]
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
add-assignees:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
steps:
|
|
11
|
-
- uses: kentaro-m/auto-assign-action@v1.2.4
|
|
12
|
-
with:
|
|
13
|
-
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
14
|
-
configuration-path: .github/auto_assign.yml
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
name: Build & Publish
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
# Automatic main releases are owned by release.yml.
|
|
5
|
-
workflow_dispatch:
|
|
6
|
-
inputs:
|
|
7
|
-
version_type:
|
|
8
|
-
description: 'Version bump type'
|
|
9
|
-
required: true
|
|
10
|
-
default: 'patch'
|
|
11
|
-
type: choice
|
|
12
|
-
options:
|
|
13
|
-
- patch
|
|
14
|
-
- minor
|
|
15
|
-
- major
|
|
16
|
-
- prerelease
|
|
17
|
-
|
|
18
|
-
permissions:
|
|
19
|
-
contents: write
|
|
20
|
-
packages: write
|
|
21
|
-
id-token: write
|
|
22
|
-
|
|
23
|
-
jobs:
|
|
24
|
-
build-and-publish:
|
|
25
|
-
name: Build and Publish
|
|
26
|
-
runs-on: ubuntu-latest
|
|
27
|
-
timeout-minutes: 20
|
|
28
|
-
environment: npm-publish
|
|
29
|
-
steps:
|
|
30
|
-
- name: Harden Runner
|
|
31
|
-
uses: step-security/harden-runner@v2
|
|
32
|
-
with:
|
|
33
|
-
egress-policy: audit
|
|
34
|
-
|
|
35
|
-
- uses: actions/checkout@v4
|
|
36
|
-
with:
|
|
37
|
-
fetch-depth: 0
|
|
38
|
-
token: ${{ secrets.GITHUB_TOKEN }}
|
|
39
|
-
|
|
40
|
-
- name: Set up Node.js
|
|
41
|
-
uses: actions/setup-node@v4
|
|
42
|
-
with:
|
|
43
|
-
node-version: '22.14.0'
|
|
44
|
-
cache: 'npm'
|
|
45
|
-
registry-url: 'https://registry.npmjs.org'
|
|
46
|
-
|
|
47
|
-
- name: Install npm with trusted publishing support
|
|
48
|
-
run: npm install --global npm@11.17.0
|
|
49
|
-
|
|
50
|
-
- name: Install dependencies
|
|
51
|
-
run: npm ci --prefer-offline --no-audit
|
|
52
|
-
|
|
53
|
-
- name: Run tests
|
|
54
|
-
run: npm run test:ci
|
|
55
|
-
|
|
56
|
-
- name: Run linting
|
|
57
|
-
run: npm run lint
|
|
58
|
-
|
|
59
|
-
- name: Build project
|
|
60
|
-
run: npm run build
|
|
61
|
-
|
|
62
|
-
- name: Generate documentation
|
|
63
|
-
run: npm run docs
|
|
64
|
-
|
|
65
|
-
- name: Configure Git
|
|
66
|
-
run: |
|
|
67
|
-
git config --global user.name "GitHub Actions"
|
|
68
|
-
git config --global user.email "actions@github.com"
|
|
69
|
-
|
|
70
|
-
- name: Check if version needs update
|
|
71
|
-
id: check_version
|
|
72
|
-
run: |
|
|
73
|
-
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
|
74
|
-
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
|
75
|
-
|
|
76
|
-
# Check if this version exists on NPM
|
|
77
|
-
if npm view @rahul05ranjan/dhruv-cli@$CURRENT_VERSION version 2>/dev/null; then
|
|
78
|
-
echo "needs_version_bump=true" >> $GITHUB_OUTPUT
|
|
79
|
-
echo "Version $CURRENT_VERSION already exists on NPM, needs bump"
|
|
80
|
-
else
|
|
81
|
-
echo "needs_version_bump=false" >> $GITHUB_OUTPUT
|
|
82
|
-
echo "Version $CURRENT_VERSION is new, can publish"
|
|
83
|
-
fi
|
|
84
|
-
|
|
85
|
-
- name: Manual version bump
|
|
86
|
-
if: github.event_name == 'workflow_dispatch'
|
|
87
|
-
run: |
|
|
88
|
-
npm version ${{ github.event.inputs.version_type }} --no-git-tag-version
|
|
89
|
-
git add package.json package-lock.json
|
|
90
|
-
git commit -m "chore: bump version to $(node -p "require('./package.json').version") [skip ci]"
|
|
91
|
-
git push
|
|
92
|
-
|
|
93
|
-
- name: Get final version
|
|
94
|
-
id: final_version
|
|
95
|
-
run: |
|
|
96
|
-
VERSION=$(node -p "require('./package.json').version")
|
|
97
|
-
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
98
|
-
echo "Final version to publish: $VERSION"
|
|
99
|
-
|
|
100
|
-
- name: Create Git tag
|
|
101
|
-
run: |
|
|
102
|
-
TAG="v${{ steps.final_version.outputs.version }}"
|
|
103
|
-
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
|
104
|
-
echo "Tag $TAG already exists, skipping tag creation"
|
|
105
|
-
else
|
|
106
|
-
git tag "$TAG"
|
|
107
|
-
git push origin "$TAG"
|
|
108
|
-
fi
|
|
109
|
-
|
|
110
|
-
- name: Publish to NPM
|
|
111
|
-
# npm 11.5.1+ uses the workflow's OIDC identity without a stored token.
|
|
112
|
-
run: |
|
|
113
|
-
echo "Publishing version ${{ steps.final_version.outputs.version }}"
|
|
114
|
-
npm publish --access public --tag latest --provenance
|
|
115
|
-
|
|
116
|
-
- name: Create GitHub Release
|
|
117
|
-
uses: softprops/action-gh-release@v2
|
|
118
|
-
with:
|
|
119
|
-
tag_name: v${{ steps.final_version.outputs.version }}
|
|
120
|
-
name: Release v${{ steps.final_version.outputs.version }}
|
|
121
|
-
body: |
|
|
122
|
-
## What's Changed
|
|
123
|
-
|
|
124
|
-
Version ${{ steps.final_version.outputs.version }} of dhruv-cli
|
|
125
|
-
|
|
126
|
-
### Installation
|
|
127
|
-
```bash
|
|
128
|
-
npm install -g @rahul05ranjan/dhruv-cli@${{ steps.final_version.outputs.version }}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
**Full Changelog**: https://github.com/${{ github.repository }}/compare/v${{ steps.check_version.outputs.current_version }}...v${{ steps.final_version.outputs.version }}
|
|
132
|
-
files: |
|
|
133
|
-
dist/**
|
|
134
|
-
docs/api/**
|
|
135
|
-
draft: false
|
|
136
|
-
prerelease: false
|
|
137
|
-
env:
|
|
138
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
139
|
-
|
|
140
|
-
- name: Update documentation
|
|
141
|
-
uses: peaceiris/actions-gh-pages@v4
|
|
142
|
-
with:
|
|
143
|
-
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
144
|
-
publish_dir: ./docs/api
|
|
145
|
-
publish_branch: gh-pages
|
|
146
|
-
enable_jekyll: false
|
|
147
|
-
|
|
148
|
-
- name: Notify success
|
|
149
|
-
if: success()
|
|
150
|
-
run: |
|
|
151
|
-
echo "✅ Successfully published @rahul05ranjan/dhruv-cli@${{ steps.final_version.outputs.version }}"
|
|
152
|
-
echo "📦 NPM: https://www.npmjs.com/package/@rahul05ranjan/dhruv-cli"
|
|
153
|
-
echo "📖 Documentation: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}"
|
|
154
|
-
echo "🏷️ Release: https://github.com/${{ github.repository }}/releases/tag/v${{ steps.final_version.outputs.version }}"
|