reviewflow 3.19.0 → 3.19.2
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 +14 -0
- package/dist/main/cli.d.ts +1 -83
- package/dist/main/cli.d.ts.map +1 -1
- package/dist/main/cli.js +24 -606
- package/dist/main/cli.js.map +1 -1
- package/dist/main/commands/discover.command.d.ts +17 -0
- package/dist/main/commands/discover.command.d.ts.map +1 -0
- package/dist/main/commands/discover.command.js +76 -0
- package/dist/main/commands/discover.command.js.map +1 -0
- package/dist/main/commands/followupImportants.command.d.ts +14 -0
- package/dist/main/commands/followupImportants.command.d.ts.map +1 -0
- package/dist/main/commands/followupImportants.command.js +31 -0
- package/dist/main/commands/followupImportants.command.js.map +1 -0
- package/dist/main/commands/init.command.d.ts +29 -0
- package/dist/main/commands/init.command.d.ts.map +1 -0
- package/dist/main/commands/init.command.js +231 -0
- package/dist/main/commands/init.command.js.map +1 -0
- package/dist/main/commands/logs.command.d.ts +10 -0
- package/dist/main/commands/logs.command.d.ts.map +1 -0
- package/dist/main/commands/logs.command.js +42 -0
- package/dist/main/commands/logs.command.js.map +1 -0
- package/dist/main/commands/start.command.d.ts +21 -0
- package/dist/main/commands/start.command.d.ts.map +1 -0
- package/dist/main/commands/start.command.js +80 -0
- package/dist/main/commands/start.command.js.map +1 -0
- package/dist/main/commands/status.command.d.ts +9 -0
- package/dist/main/commands/status.command.d.ts.map +1 -0
- package/dist/main/commands/status.command.js +26 -0
- package/dist/main/commands/status.command.js.map +1 -0
- package/dist/main/commands/stop.command.d.ts +11 -0
- package/dist/main/commands/stop.command.d.ts.map +1 -0
- package/dist/main/commands/stop.command.js +30 -0
- package/dist/main/commands/stop.command.js.map +1 -0
- package/dist/main/commands/validate.command.d.ts +11 -0
- package/dist/main/commands/validate.command.d.ts.map +1 -0
- package/dist/main/commands/validate.command.js +54 -0
- package/dist/main/commands/validate.command.js.map +1 -0
- package/dist/main/shared/cliConstants.d.ts +5 -0
- package/dist/main/shared/cliConstants.d.ts.map +1 -0
- package/dist/main/shared/cliConstants.js +86 -0
- package/dist/main/shared/cliConstants.js.map +1 -0
- package/dist/modules/claude-invocation/usecases/runClaudeReviewJob.usecase.d.ts.map +1 -1
- package/dist/modules/claude-invocation/usecases/runClaudeReviewJob.usecase.js +8 -0
- package/dist/modules/claude-invocation/usecases/runClaudeReviewJob.usecase.js.map +1 -1
- package/dist/shared/services/pidFileManager.d.ts +6 -0
- package/dist/shared/services/pidFileManager.d.ts.map +1 -1
- package/dist/shared/services/pidFileManager.js.map +1 -1
- package/dist/tests/units/main/executeDiscover.test.js +1 -1
- package/dist/tests/units/main/executeDiscover.test.js.map +1 -1
- package/dist/tests/units/main/executeFollowupImportants.test.d.ts +2 -0
- package/dist/tests/units/main/executeFollowupImportants.test.d.ts.map +1 -0
- package/dist/tests/units/main/executeFollowupImportants.test.js +48 -0
- package/dist/tests/units/main/executeFollowupImportants.test.js.map +1 -0
- package/dist/tests/units/main/executeInit.test.js +1 -1
- package/dist/tests/units/main/executeInit.test.js.map +1 -1
- package/dist/tests/units/main/executeLogs.test.js +1 -1
- package/dist/tests/units/main/executeLogs.test.js.map +1 -1
- package/dist/tests/units/main/executeStart.test.js +1 -1
- package/dist/tests/units/main/executeStart.test.js.map +1 -1
- package/dist/tests/units/main/executeStatus.test.js +1 -1
- package/dist/tests/units/main/executeStatus.test.js.map +1 -1
- package/dist/tests/units/main/executeStop.test.js +1 -1
- package/dist/tests/units/main/executeStop.test.js.map +1 -1
- package/dist/tests/units/main/executeValidate.test.d.ts +2 -0
- package/dist/tests/units/main/executeValidate.test.d.ts.map +1 -0
- package/dist/tests/units/main/executeValidate.test.js +76 -0
- package/dist/tests/units/main/executeValidate.test.js.map +1 -0
- package/dist/tests/units/modules/claude-invocation/usecases/runClaudeReviewJob.usecase.test.js +21 -1
- package/dist/tests/units/modules/claude-invocation/usecases/runClaudeReviewJob.usecase.test.js.map +1 -1
- package/package.json +1 -1
package/dist/main/cli.js
CHANGED
|
@@ -1,438 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { execSync } from 'node:child_process';
|
|
6
|
-
import { homedir } from 'node:os';
|
|
7
5
|
import { parseCliArgs } from '../cli/parseCliArgs.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
6
|
+
import { executeStart, createStartDependencies } from '../main/commands/start.command.js';
|
|
7
|
+
import { executeStop, createStopDependencies } from '../main/commands/stop.command.js';
|
|
8
|
+
import { executeStatus, createStatusDependencies } from '../main/commands/status.command.js';
|
|
9
|
+
import { executeLogs, createLogsDependencies } from '../main/commands/logs.command.js';
|
|
10
|
+
import { executeFollowupImportants, createFollowupImportantsDependencies } from '../main/commands/followupImportants.command.js';
|
|
11
|
+
import { executeInit, createInitDependencies } from '../main/commands/init.command.js';
|
|
12
|
+
import { executeDiscover, createDiscoverDependencies } from '../main/commands/discover.command.js';
|
|
13
|
+
import { executeValidate, createValidateDependencies } from '../main/commands/validate.command.js';
|
|
14
|
+
import { readVersion, printHelp, getGitRemoteUrl } from '../main/shared/cliConstants.js';
|
|
15
15
|
import { readPidFile, writePidFile, removePidFile } from '../shared/services/pidFileManager.js';
|
|
16
16
|
import { isProcessRunning } from '../shared/services/processChecker.js';
|
|
17
|
-
import { PID_FILE_PATH
|
|
18
|
-
import { spawnDaemon } from '../shared/services/daemonSpawner.js';
|
|
19
|
-
import { logFileExists, readLastLines, watchLogFile } from '../shared/services/logFileReader.js';
|
|
20
|
-
import { green, red, yellow, dim, bold } from '../shared/services/ansiColors.js';
|
|
21
|
-
import { formatStartupBanner } from '../cli/startupBanner.js';
|
|
22
|
-
import { openInBrowser } from '../shared/services/browserOpener.js';
|
|
23
|
-
import { loadConfig } from '../frameworks/config/configLoader.js';
|
|
24
|
-
import { getConfigDir } from '../shared/services/configDir.js';
|
|
25
|
-
import { generateWebhookSecret, truncateSecret } from '../shared/services/secretGenerator.js';
|
|
26
|
-
import { DiscoverRepositoriesUseCase } from '../modules/cli-configuration/usecases/cli/discoverRepositories.usecase.js';
|
|
27
|
-
import { ConfigureMcpUseCase } from '../modules/cli-configuration/usecases/cli/configureMcp.usecase.js';
|
|
28
|
-
import { WriteInitConfigUseCase } from '../modules/cli-configuration/usecases/cli/writeInitConfig.usecase.js';
|
|
29
|
-
import { ValidateConfigUseCase } from '../modules/cli-configuration/usecases/cli/validateConfig.usecase.js';
|
|
30
|
-
import { AddRepositoriesToConfigUseCase } from '../modules/cli-configuration/usecases/cli/addRepositoriesToConfig.usecase.js';
|
|
31
|
-
import { formatInitSummary } from '../cli/formatters/initSummary.js';
|
|
32
|
-
import { resolveMcpServerPath } from '../frameworks/claude/claudeInvoker.js';
|
|
33
|
-
import { checkInitPrerequisites } from '../modules/cli-configuration/usecases/cli/checkInitPrerequisites.js';
|
|
34
|
-
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
35
|
-
function readVersion() {
|
|
36
|
-
const packageJsonPath = join(currentDir, '..', '..', 'package.json');
|
|
37
|
-
const raw = readFileSync(packageJsonPath, 'utf-8');
|
|
38
|
-
return JSON.parse(raw).version;
|
|
39
|
-
}
|
|
40
|
-
function printHelp() {
|
|
41
|
-
console.log(`reviewflow - Automated code review for GitLab/GitHub
|
|
42
|
-
|
|
43
|
-
Usage:
|
|
44
|
-
reviewflow [command] [options]
|
|
45
|
-
|
|
46
|
-
Commands:
|
|
47
|
-
init Interactive setup wizard
|
|
48
|
-
start Start the review server (default)
|
|
49
|
-
stop Stop the running daemon
|
|
50
|
-
status Show server status
|
|
51
|
-
logs Show daemon logs
|
|
52
|
-
validate Validate configuration
|
|
53
|
-
discover Scan and add repositories to existing config
|
|
54
|
-
followup-importants Trigger followups for pending-approval MRs with Important issues
|
|
55
|
-
|
|
56
|
-
Discover options:
|
|
57
|
-
--scan-path <path> Custom scan path (repeatable)
|
|
58
|
-
--max-depth <n> Max directory depth (default: 3)
|
|
59
|
-
|
|
60
|
-
Init options:
|
|
61
|
-
-y, --yes Accept all defaults (non-interactive)
|
|
62
|
-
--skip-mcp Skip MCP server configuration
|
|
63
|
-
--show-secrets Display full webhook secrets
|
|
64
|
-
--scan-path <path> Custom scan path (repeatable)
|
|
65
|
-
|
|
66
|
-
Start options:
|
|
67
|
-
-d, --daemon Run as background daemon
|
|
68
|
-
-p, --port <port> Server port (default: from config)
|
|
69
|
-
-o, --open Open dashboard in default browser
|
|
70
|
-
--skip-dependency-check Skip external dependency verification
|
|
71
|
-
|
|
72
|
-
Stop options:
|
|
73
|
-
-f, --force Force stop (SIGKILL instead of SIGTERM)
|
|
74
|
-
|
|
75
|
-
Status options:
|
|
76
|
-
--json Output status as JSON
|
|
77
|
-
|
|
78
|
-
Logs options:
|
|
79
|
-
-f, --follow Follow log output (tail -f)
|
|
80
|
-
-n, --lines <count> Number of lines to show (default: 20)
|
|
81
|
-
|
|
82
|
-
Validate options:
|
|
83
|
-
--fix Auto-fix correctable issues
|
|
84
|
-
|
|
85
|
-
Followup-importants options:
|
|
86
|
-
-p, --project <path> Scan specific project only
|
|
87
|
-
-y, --yes Skip confirmation prompt
|
|
88
|
-
|
|
89
|
-
General options:
|
|
90
|
-
-v, --version Show version
|
|
91
|
-
-h, --help Show this help
|
|
92
|
-
`);
|
|
93
|
-
}
|
|
94
|
-
function showBanner(port, daemonPid, open, deps) {
|
|
95
|
-
const { enabledPlatforms } = deps.loadStartupInfo();
|
|
96
|
-
const banner = formatStartupBanner({ port, enabledPlatforms, daemonPid });
|
|
97
|
-
for (const line of banner.lines) {
|
|
98
|
-
deps.log(line);
|
|
99
|
-
}
|
|
100
|
-
if (open) {
|
|
101
|
-
deps.openInBrowser(banner.dashboardUrl);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
export function executeStart(skipDependencyCheck, daemon, port, open, deps) {
|
|
105
|
-
const resolvedPort = port ?? deps.loadStartupInfo().defaultPort;
|
|
106
|
-
if (daemon) {
|
|
107
|
-
const usecase = new StartDaemonUseCase(deps.startDaemonDeps);
|
|
108
|
-
const result = usecase.execute({ daemon: true, port });
|
|
109
|
-
switch (result.status) {
|
|
110
|
-
case 'started':
|
|
111
|
-
showBanner(resolvedPort, result.pid, open, deps);
|
|
112
|
-
break;
|
|
113
|
-
case 'already-running':
|
|
114
|
-
deps.log(yellow(`Server already running (PID: ${result.pid}, port: ${result.port})`));
|
|
115
|
-
break;
|
|
116
|
-
case 'foreground':
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (!skipDependencyCheck) {
|
|
122
|
-
const missing = deps.validateDependencies();
|
|
123
|
-
if (missing.length > 0) {
|
|
124
|
-
deps.error('Missing dependencies:');
|
|
125
|
-
for (const dep of missing) {
|
|
126
|
-
deps.error(` - ${dep.name}: ${dep.installUrl}`);
|
|
127
|
-
}
|
|
128
|
-
deps.exit(1);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const startForeground = async () => {
|
|
133
|
-
try {
|
|
134
|
-
await deps.startServer(port);
|
|
135
|
-
showBanner(resolvedPort, null, open, deps);
|
|
136
|
-
}
|
|
137
|
-
catch (err) {
|
|
138
|
-
deps.error('Fatal error:', err);
|
|
139
|
-
deps.exit(1);
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
startForeground();
|
|
143
|
-
}
|
|
144
|
-
export function executeStop(force, deps) {
|
|
145
|
-
const usecase = new StopDaemonUseCase(deps.stopDaemonDeps);
|
|
146
|
-
const result = usecase.execute({ force });
|
|
147
|
-
switch (result.status) {
|
|
148
|
-
case 'stopped':
|
|
149
|
-
deps.log(green(`Server stopped (PID: ${result.pid})`));
|
|
150
|
-
break;
|
|
151
|
-
case 'not-running':
|
|
152
|
-
deps.log(yellow('Server is not running'));
|
|
153
|
-
break;
|
|
154
|
-
case 'failed':
|
|
155
|
-
deps.error(red(`Failed to stop server: ${result.reason}`));
|
|
156
|
-
deps.exit(1);
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
export function executeStatus(json, deps) {
|
|
161
|
-
const usecase = new QueryStatusUseCase(deps.queryStatusDeps);
|
|
162
|
-
const result = usecase.execute();
|
|
163
|
-
if (json) {
|
|
164
|
-
deps.log(JSON.stringify(result));
|
|
165
|
-
if (result.status === 'stopped')
|
|
166
|
-
deps.exit(1);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
if (result.status === 'running') {
|
|
170
|
-
deps.log(green(bold('ReviewFlow is running')));
|
|
171
|
-
deps.log(dim(` PID: ${result.pid}`));
|
|
172
|
-
deps.log(dim(` Port: ${result.port}`));
|
|
173
|
-
deps.log(dim(` Started at: ${result.startedAt}`));
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
deps.log(red('ReviewFlow is not running'));
|
|
177
|
-
deps.exit(1);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
export function executeLogs(follow, lines, deps) {
|
|
181
|
-
const onLine = (line) => deps.log(line);
|
|
182
|
-
const usecase = new ReadLogsUseCase(deps.readLogsDeps);
|
|
183
|
-
const result = usecase.execute({ follow, lines, onLine });
|
|
184
|
-
switch (result.status) {
|
|
185
|
-
case 'no-logs':
|
|
186
|
-
deps.error(yellow('No log file found. Start the daemon first.'));
|
|
187
|
-
deps.exit(1);
|
|
188
|
-
break;
|
|
189
|
-
case 'read':
|
|
190
|
-
for (const line of result.lines) {
|
|
191
|
-
deps.log(line);
|
|
192
|
-
}
|
|
193
|
-
break;
|
|
194
|
-
case 'following':
|
|
195
|
-
for (const line of result.initialLines) {
|
|
196
|
-
deps.log(line);
|
|
197
|
-
}
|
|
198
|
-
process.on('SIGINT', () => {
|
|
199
|
-
result.stop();
|
|
200
|
-
process.exit(0);
|
|
201
|
-
});
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
async function executeFollowupImportants(project) {
|
|
206
|
-
const pidData = readPidFile(PID_FILE_PATH);
|
|
207
|
-
if (!pidData || !isProcessRunning(pidData.pid)) {
|
|
208
|
-
console.error(red('Server is not running. Start with: reviewflow start'));
|
|
209
|
-
process.exit(1);
|
|
210
|
-
}
|
|
211
|
-
const usecase = new FollowupImportantsUseCase({
|
|
212
|
-
serverPort: pidData.port,
|
|
213
|
-
log: console.log,
|
|
214
|
-
error: console.error,
|
|
215
|
-
fetch: globalThis.fetch,
|
|
216
|
-
});
|
|
217
|
-
await usecase.execute({ project });
|
|
218
|
-
}
|
|
219
|
-
const DEFAULT_SCAN_PATHS = [
|
|
220
|
-
join(homedir(), 'Documents'),
|
|
221
|
-
join(homedir(), 'Projects'),
|
|
222
|
-
join(homedir(), 'Development'),
|
|
223
|
-
join(homedir(), 'dev'),
|
|
224
|
-
join(homedir(), 'repos'),
|
|
225
|
-
];
|
|
226
|
-
function getGitRemoteUrl(localPath) {
|
|
227
|
-
try {
|
|
228
|
-
const result = execSync('git remote get-url origin', {
|
|
229
|
-
cwd: localPath,
|
|
230
|
-
encoding: 'utf-8',
|
|
231
|
-
timeout: 5000,
|
|
232
|
-
});
|
|
233
|
-
return result.trim().replace(/\.git$/, '');
|
|
234
|
-
}
|
|
235
|
-
catch {
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
const WELCOME_BANNER = `
|
|
240
|
-
Welcome to ReviewFlow!
|
|
241
|
-
Automated code review powered by Claude Code.
|
|
242
|
-
`;
|
|
243
|
-
export async function executeInit(yes, skipMcp, showSecrets, scanPaths, deps) {
|
|
244
|
-
deps.log(WELCOME_BANNER);
|
|
245
|
-
const prereqResult = deps.checkPrerequisites();
|
|
246
|
-
if (prereqResult.status === 'node-version-too-low') {
|
|
247
|
-
deps.log(red(`Node.js >= ${prereqResult.required} is required (found: ${prereqResult.found})`));
|
|
248
|
-
deps.exit(1);
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
if (prereqResult.status === 'claude-not-installed') {
|
|
252
|
-
deps.log(red(`Claude CLI is not installed. Install it from: ${prereqResult.installUrl}`));
|
|
253
|
-
deps.exit(1);
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
const configDir = deps.getConfigDir();
|
|
257
|
-
const configPath = join(configDir, 'config.json');
|
|
258
|
-
if (deps.existsSync(configPath) && !yes) {
|
|
259
|
-
const overwrite = await deps.confirmOverwrite(configPath);
|
|
260
|
-
if (!overwrite) {
|
|
261
|
-
deps.log(yellow('Init cancelled.'));
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
let port = 3847;
|
|
266
|
-
let gitlabUsername = '';
|
|
267
|
-
let githubUsername = '';
|
|
268
|
-
if (yes) {
|
|
269
|
-
deps.log(dim('Non-interactive mode: using defaults'));
|
|
270
|
-
}
|
|
271
|
-
else {
|
|
272
|
-
const platform = await deps.promptPlatform();
|
|
273
|
-
port = await deps.promptPort();
|
|
274
|
-
if (platform === 'gitlab' || platform === 'both') {
|
|
275
|
-
gitlabUsername = await deps.promptGitlabUsername();
|
|
276
|
-
}
|
|
277
|
-
if (platform === 'github' || platform === 'both') {
|
|
278
|
-
githubUsername = await deps.promptGithubUsername();
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
const gitlabSecret = deps.generateWebhookSecret();
|
|
282
|
-
const githubSecret = deps.generateWebhookSecret();
|
|
283
|
-
deps.log('');
|
|
284
|
-
deps.log(bold('Webhook secrets generated:'));
|
|
285
|
-
if (showSecrets) {
|
|
286
|
-
deps.log(` GitLab: ${gitlabSecret}`);
|
|
287
|
-
deps.log(` GitHub: ${githubSecret}`);
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
deps.log(` GitLab: ${deps.truncateSecret(gitlabSecret, 16)}`);
|
|
291
|
-
deps.log(` GitHub: ${deps.truncateSecret(githubSecret, 16)}`);
|
|
292
|
-
deps.log(dim(' Use --show-secrets to display full values'));
|
|
293
|
-
}
|
|
294
|
-
const pathsToScan = scanPaths.length > 0 ? scanPaths : DEFAULT_SCAN_PATHS;
|
|
295
|
-
let selectedRepos = [];
|
|
296
|
-
const shouldScan = yes || await deps.confirmScanRepositories();
|
|
297
|
-
if (shouldScan) {
|
|
298
|
-
deps.log(dim('\nScanning for repositories...'));
|
|
299
|
-
const discovered = deps.discoverRepositories(pathsToScan, 3);
|
|
300
|
-
deps.log(` Found ${discovered.repositories.length} repositories`);
|
|
301
|
-
if (discovered.repositories.length > 0) {
|
|
302
|
-
if (yes) {
|
|
303
|
-
selectedRepos = discovered.repositories.map(r => ({
|
|
304
|
-
name: r.name,
|
|
305
|
-
localPath: r.localPath,
|
|
306
|
-
enabled: true,
|
|
307
|
-
}));
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
const selected = await deps.selectRepositories(discovered.repositories);
|
|
311
|
-
selectedRepos = selected.map(r => ({
|
|
312
|
-
name: r.name,
|
|
313
|
-
localPath: r.localPath,
|
|
314
|
-
enabled: true,
|
|
315
|
-
}));
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
let mcpStatus = 'skipped';
|
|
320
|
-
if (!skipMcp) {
|
|
321
|
-
deps.log(dim('\nConfiguring MCP server...'));
|
|
322
|
-
try {
|
|
323
|
-
mcpStatus = deps.configureMcp();
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
326
|
-
mcpStatus = 'failed';
|
|
327
|
-
}
|
|
328
|
-
deps.log(` MCP: ${mcpStatus}`);
|
|
329
|
-
}
|
|
330
|
-
const result = deps.writeConfig({
|
|
331
|
-
configDir,
|
|
332
|
-
port,
|
|
333
|
-
gitlabUsername,
|
|
334
|
-
githubUsername,
|
|
335
|
-
repositories: selectedRepos,
|
|
336
|
-
gitlabWebhookSecret: gitlabSecret,
|
|
337
|
-
githubWebhookSecret: githubSecret,
|
|
338
|
-
});
|
|
339
|
-
const summary = deps.formatSummary({
|
|
340
|
-
configPath: result.configPath,
|
|
341
|
-
envPath: result.envPath,
|
|
342
|
-
port,
|
|
343
|
-
repositoryCount: selectedRepos.length,
|
|
344
|
-
mcpStatus,
|
|
345
|
-
gitlabUsername,
|
|
346
|
-
githubUsername,
|
|
347
|
-
});
|
|
348
|
-
deps.log(green(summary));
|
|
349
|
-
}
|
|
350
|
-
export async function executeDiscover(scanPaths, maxDepth, deps) {
|
|
351
|
-
const configPath = deps.getConfigPath();
|
|
352
|
-
const pathsToScan = scanPaths.length > 0 ? scanPaths : DEFAULT_SCAN_PATHS;
|
|
353
|
-
deps.log(dim('\nScanning for repositories...'));
|
|
354
|
-
const discoverer = new DiscoverRepositoriesUseCase({
|
|
355
|
-
existsSync: deps.existsSync,
|
|
356
|
-
readdirSync: deps.readdirSync,
|
|
357
|
-
getGitRemoteUrl: deps.getGitRemoteUrl,
|
|
358
|
-
});
|
|
359
|
-
const discovered = discoverer.execute({ scanPaths: pathsToScan, maxDepth });
|
|
360
|
-
deps.log(` Found ${discovered.repositories.length} repositories`);
|
|
361
|
-
if (discovered.repositories.length === 0) {
|
|
362
|
-
deps.log(yellow('No new repositories found.'));
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
const selected = await deps.selectRepositories(discovered.repositories);
|
|
366
|
-
if (selected.length === 0) {
|
|
367
|
-
deps.log(yellow('No repositories selected.'));
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
const adder = new AddRepositoriesToConfigUseCase({
|
|
371
|
-
readFileSync: deps.readFileSync,
|
|
372
|
-
writeFileSync: deps.writeFileSync,
|
|
373
|
-
existsSync: deps.existsSync,
|
|
374
|
-
});
|
|
375
|
-
const result = adder.execute({
|
|
376
|
-
configPath,
|
|
377
|
-
newRepositories: selected.map(repo => ({
|
|
378
|
-
name: repo.name,
|
|
379
|
-
localPath: repo.localPath,
|
|
380
|
-
enabled: true,
|
|
381
|
-
})),
|
|
382
|
-
});
|
|
383
|
-
if (result.added.length > 0) {
|
|
384
|
-
deps.log(green(`\n Added ${result.added.length} repositories:`));
|
|
385
|
-
for (const repo of result.added) {
|
|
386
|
-
deps.log(green(` + ${repo.name} (${repo.localPath})`));
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
if (result.skipped.length > 0) {
|
|
390
|
-
deps.log(dim(` Skipped ${result.skipped.length} already configured`));
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
export function executeValidate(fix) {
|
|
394
|
-
const configDir = getConfigDir();
|
|
395
|
-
const configPath = join(configDir, 'config.json');
|
|
396
|
-
const envPath = join(configDir, '.env');
|
|
397
|
-
const cwdConfigPath = join(process.cwd(), 'config.json');
|
|
398
|
-
const resolvedConfigPath = existsSync(cwdConfigPath) ? cwdConfigPath : configPath;
|
|
399
|
-
const resolvedEnvPath = existsSync(join(process.cwd(), '.env')) ? join(process.cwd(), '.env') : envPath;
|
|
400
|
-
const validator = new ValidateConfigUseCase({
|
|
401
|
-
existsSync,
|
|
402
|
-
readFileSync,
|
|
403
|
-
});
|
|
404
|
-
const result = validator.execute({ configPath: resolvedConfigPath, envPath: resolvedEnvPath });
|
|
405
|
-
switch (result.status) {
|
|
406
|
-
case 'not-found':
|
|
407
|
-
console.log(red('No configuration found.'));
|
|
408
|
-
console.log(dim(`Looked in: ${resolvedConfigPath}`));
|
|
409
|
-
console.log(`Run ${bold('reviewflow init')} to create one.`);
|
|
410
|
-
process.exit(1);
|
|
411
|
-
break;
|
|
412
|
-
case 'valid':
|
|
413
|
-
console.log(green(bold('Configuration is valid!')));
|
|
414
|
-
console.log(dim(` Config: ${resolvedConfigPath}`));
|
|
415
|
-
console.log(dim(` Env: ${resolvedEnvPath}`));
|
|
416
|
-
break;
|
|
417
|
-
case 'invalid':
|
|
418
|
-
console.log(red(bold('Configuration has issues:')));
|
|
419
|
-
for (const issue of result.issues) {
|
|
420
|
-
const prefix = issue.severity === 'error' ? red('ERROR') : yellow('WARN');
|
|
421
|
-
console.log(` ${prefix} [${issue.field}]: ${issue.message}`);
|
|
422
|
-
}
|
|
423
|
-
if (fix) {
|
|
424
|
-
console.log(dim('\n--fix flag detected, but no auto-fixable issues implemented yet.'));
|
|
425
|
-
}
|
|
426
|
-
process.exit(1);
|
|
427
|
-
break;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
17
|
+
import { PID_FILE_PATH } from '../shared/services/daemonPaths.js';
|
|
430
18
|
function createPidFileDeps() {
|
|
431
19
|
return {
|
|
432
20
|
readPidFile: () => readPidFile(PID_FILE_PATH),
|
|
433
21
|
writePidFile: (content) => writePidFile(PID_FILE_PATH, content),
|
|
434
22
|
removePidFile: () => removePidFile(PID_FILE_PATH),
|
|
435
|
-
isProcessRunning
|
|
23
|
+
isProcessRunning,
|
|
436
24
|
};
|
|
437
25
|
}
|
|
438
26
|
const isDirectlyExecuted = process.argv[1] &&
|
|
@@ -446,199 +34,29 @@ if (isDirectlyExecuted) {
|
|
|
446
34
|
case 'help':
|
|
447
35
|
printHelp();
|
|
448
36
|
break;
|
|
449
|
-
case 'start':
|
|
450
|
-
|
|
451
|
-
executeStart(args.skipDependencyCheck, args.daemon, args.port, args.open, {
|
|
452
|
-
validateDependencies,
|
|
453
|
-
startServer: (port) => startServer({ portOverride: port }),
|
|
454
|
-
exit: process.exit,
|
|
455
|
-
error: console.error,
|
|
456
|
-
log: console.log,
|
|
457
|
-
startDaemonDeps: {
|
|
458
|
-
...pidDeps,
|
|
459
|
-
spawnDaemon,
|
|
460
|
-
},
|
|
461
|
-
loadStartupInfo: () => {
|
|
462
|
-
try {
|
|
463
|
-
const config = loadConfig();
|
|
464
|
-
const enabledPlatforms = [...new Set(config.repositories.filter(r => r.enabled).map(r => r.platform))];
|
|
465
|
-
return { enabledPlatforms, defaultPort: config.server.port };
|
|
466
|
-
}
|
|
467
|
-
catch {
|
|
468
|
-
return { enabledPlatforms: [], defaultPort: 3000 };
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
openInBrowser,
|
|
472
|
-
});
|
|
37
|
+
case 'start':
|
|
38
|
+
executeStart(args.skipDependencyCheck, args.daemon, args.port, args.open, createStartDependencies(createPidFileDeps()));
|
|
473
39
|
break;
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const pidDeps = createPidFileDeps();
|
|
477
|
-
executeStop(args.force, {
|
|
478
|
-
stopDaemonDeps: {
|
|
479
|
-
...pidDeps,
|
|
480
|
-
killProcess: (pid, signal) => process.kill(pid, signal),
|
|
481
|
-
},
|
|
482
|
-
log: console.log,
|
|
483
|
-
error: console.error,
|
|
484
|
-
exit: process.exit,
|
|
485
|
-
});
|
|
40
|
+
case 'stop':
|
|
41
|
+
executeStop(args.force, createStopDependencies(createPidFileDeps()));
|
|
486
42
|
break;
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
const pidDeps = createPidFileDeps();
|
|
490
|
-
executeStatus(args.json, {
|
|
491
|
-
queryStatusDeps: pidDeps,
|
|
492
|
-
log: console.log,
|
|
493
|
-
exit: process.exit,
|
|
494
|
-
});
|
|
43
|
+
case 'status':
|
|
44
|
+
executeStatus(args.json, createStatusDependencies(createPidFileDeps()));
|
|
495
45
|
break;
|
|
496
|
-
}
|
|
497
46
|
case 'logs':
|
|
498
|
-
executeLogs(args.follow, args.lines,
|
|
499
|
-
readLogsDeps: {
|
|
500
|
-
logFileExists: () => logFileExists(LOG_FILE_PATH),
|
|
501
|
-
readLastLines: (count) => readLastLines(LOG_FILE_PATH, count),
|
|
502
|
-
watchFile: (onLine) => watchLogFile(LOG_FILE_PATH, onLine),
|
|
503
|
-
},
|
|
504
|
-
log: console.log,
|
|
505
|
-
error: console.error,
|
|
506
|
-
exit: process.exit,
|
|
507
|
-
});
|
|
47
|
+
executeLogs(args.follow, args.lines, createLogsDependencies());
|
|
508
48
|
break;
|
|
509
49
|
case 'init':
|
|
510
|
-
executeInit(args.yes, args.skipMcp, args.showSecrets, args.scanPaths,
|
|
511
|
-
log: console.log,
|
|
512
|
-
exit: process.exit,
|
|
513
|
-
getConfigDir,
|
|
514
|
-
existsSync,
|
|
515
|
-
checkPrerequisites: () => checkInitPrerequisites({
|
|
516
|
-
executeCommand: execSync,
|
|
517
|
-
getNodeMajorVersion: () => Number(process.versions.node.split('.')[0]),
|
|
518
|
-
}),
|
|
519
|
-
confirmOverwrite: async (configPath) => {
|
|
520
|
-
const { confirm } = await import('@inquirer/prompts');
|
|
521
|
-
return confirm({
|
|
522
|
-
message: `Config already exists at ${configPath}. Overwrite?`,
|
|
523
|
-
default: false,
|
|
524
|
-
});
|
|
525
|
-
},
|
|
526
|
-
promptPlatform: async () => {
|
|
527
|
-
const { select } = await import('@inquirer/prompts');
|
|
528
|
-
return select({
|
|
529
|
-
message: 'Which platform(s) do you use?',
|
|
530
|
-
choices: [
|
|
531
|
-
{ name: 'GitLab', value: 'gitlab' },
|
|
532
|
-
{ name: 'GitHub', value: 'github' },
|
|
533
|
-
{ name: 'Both', value: 'both' },
|
|
534
|
-
],
|
|
535
|
-
});
|
|
536
|
-
},
|
|
537
|
-
promptPort: async () => {
|
|
538
|
-
const { number: numberPrompt } = await import('@inquirer/prompts');
|
|
539
|
-
const portAnswer = await numberPrompt({
|
|
540
|
-
message: 'Server port:',
|
|
541
|
-
default: 3847,
|
|
542
|
-
validate: (value) => {
|
|
543
|
-
if (value === undefined || value < 1 || value > 65535)
|
|
544
|
-
return 'Port must be between 1 and 65535';
|
|
545
|
-
return true;
|
|
546
|
-
},
|
|
547
|
-
});
|
|
548
|
-
return portAnswer ?? 3847;
|
|
549
|
-
},
|
|
550
|
-
promptGitlabUsername: async () => {
|
|
551
|
-
const { input } = await import('@inquirer/prompts');
|
|
552
|
-
return input({ message: 'GitLab username:', default: '' });
|
|
553
|
-
},
|
|
554
|
-
promptGithubUsername: async () => {
|
|
555
|
-
const { input } = await import('@inquirer/prompts');
|
|
556
|
-
return input({ message: 'GitHub username:', default: '' });
|
|
557
|
-
},
|
|
558
|
-
confirmScanRepositories: async () => {
|
|
559
|
-
const { confirm } = await import('@inquirer/prompts');
|
|
560
|
-
return confirm({ message: 'Scan for repositories?', default: true });
|
|
561
|
-
},
|
|
562
|
-
selectRepositories: async (repositories) => {
|
|
563
|
-
const { checkbox } = await import('@inquirer/prompts');
|
|
564
|
-
return checkbox({
|
|
565
|
-
message: 'Select repositories to configure:',
|
|
566
|
-
choices: repositories.map(r => ({
|
|
567
|
-
name: `${r.name} ${dim(`(${r.localPath})`)}${r.hasReviewConfig ? green(' [configured]') : ''}`,
|
|
568
|
-
value: r,
|
|
569
|
-
checked: r.hasReviewConfig,
|
|
570
|
-
})),
|
|
571
|
-
});
|
|
572
|
-
},
|
|
573
|
-
generateWebhookSecret,
|
|
574
|
-
truncateSecret,
|
|
575
|
-
discoverRepositories: (scanPaths, maxDepth) => {
|
|
576
|
-
const discoverer = new DiscoverRepositoriesUseCase({
|
|
577
|
-
existsSync,
|
|
578
|
-
readdirSync: (path) => readdirSync(path, { withFileTypes: true }).map(d => ({
|
|
579
|
-
name: d.name,
|
|
580
|
-
isDirectory: () => d.isDirectory(),
|
|
581
|
-
})),
|
|
582
|
-
getGitRemoteUrl,
|
|
583
|
-
});
|
|
584
|
-
return discoverer.execute({ scanPaths, maxDepth });
|
|
585
|
-
},
|
|
586
|
-
configureMcp: () => {
|
|
587
|
-
const mcpUseCase = new ConfigureMcpUseCase({
|
|
588
|
-
isClaudeInstalled: () => checkDependency({ name: 'Claude', command: 'claude --version' }),
|
|
589
|
-
readFileSync,
|
|
590
|
-
writeFileSync,
|
|
591
|
-
existsSync,
|
|
592
|
-
copyFileSync,
|
|
593
|
-
resolveMcpServerPath: () => {
|
|
594
|
-
try {
|
|
595
|
-
return resolveMcpServerPath();
|
|
596
|
-
}
|
|
597
|
-
catch {
|
|
598
|
-
return join(dirname(fileURLToPath(import.meta.url)), '..', 'mcpServer.js');
|
|
599
|
-
}
|
|
600
|
-
},
|
|
601
|
-
settingsPath: join(homedir(), '.claude', 'settings.json'),
|
|
602
|
-
});
|
|
603
|
-
return mcpUseCase.execute();
|
|
604
|
-
},
|
|
605
|
-
writeConfig: (input) => {
|
|
606
|
-
const writer = new WriteInitConfigUseCase({ mkdirSync, writeFileSync });
|
|
607
|
-
return writer.execute(input);
|
|
608
|
-
},
|
|
609
|
-
formatSummary: formatInitSummary,
|
|
610
|
-
});
|
|
50
|
+
executeInit(args.yes, args.skipMcp, args.showSecrets, args.scanPaths, createInitDependencies(getGitRemoteUrl));
|
|
611
51
|
break;
|
|
612
52
|
case 'discover':
|
|
613
|
-
executeDiscover(args.scanPaths, args.maxDepth,
|
|
614
|
-
existsSync,
|
|
615
|
-
readFileSync,
|
|
616
|
-
writeFileSync,
|
|
617
|
-
readdirSync: (path) => readdirSync(path, { withFileTypes: true }).map(d => ({
|
|
618
|
-
name: d.name,
|
|
619
|
-
isDirectory: () => d.isDirectory(),
|
|
620
|
-
})),
|
|
621
|
-
getGitRemoteUrl,
|
|
622
|
-
getConfigPath: () => join(getConfigDir(), 'config.json'),
|
|
623
|
-
log: console.log,
|
|
624
|
-
selectRepositories: async (repositories) => {
|
|
625
|
-
const { checkbox } = await import('@inquirer/prompts');
|
|
626
|
-
return checkbox({
|
|
627
|
-
message: 'Select repositories to add:',
|
|
628
|
-
choices: repositories.map(r => ({
|
|
629
|
-
name: `${r.name} ${dim(`(${r.localPath})`)}${r.hasReviewConfig ? green(' [configured]') : ''}`,
|
|
630
|
-
value: r,
|
|
631
|
-
checked: r.hasReviewConfig,
|
|
632
|
-
})),
|
|
633
|
-
});
|
|
634
|
-
},
|
|
635
|
-
});
|
|
53
|
+
executeDiscover(args.scanPaths, args.maxDepth, createDiscoverDependencies(getGitRemoteUrl));
|
|
636
54
|
break;
|
|
637
55
|
case 'validate':
|
|
638
|
-
executeValidate(args.fix);
|
|
56
|
+
executeValidate(args.fix, createValidateDependencies());
|
|
639
57
|
break;
|
|
640
58
|
case 'followup-importants':
|
|
641
|
-
executeFollowupImportants(args.project);
|
|
59
|
+
executeFollowupImportants(args.project, createFollowupImportantsDependencies());
|
|
642
60
|
break;
|
|
643
61
|
}
|
|
644
62
|
}
|