@vibe-validate/cli 0.14.1 → 0.14.3
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/README.md +2 -2
- package/config-templates/README.md +1 -1
- package/dist/bin.js +23 -17
- package/dist/bin.js.map +1 -1
- package/dist/commands/cleanup.d.ts.map +1 -1
- package/dist/commands/cleanup.js +32 -19
- package/dist/commands/cleanup.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +23 -12
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +221 -198
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/generate-workflow.d.ts.map +1 -1
- package/dist/commands/generate-workflow.js +22 -12
- package/dist/commands/generate-workflow.js.map +1 -1
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +46 -34
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/init.js +17 -19
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pre-commit.d.ts.map +1 -1
- package/dist/commands/pre-commit.js +2 -1
- package/dist/commands/pre-commit.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +8 -9
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +2 -1
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/watch-pr.d.ts.map +1 -1
- package/dist/commands/watch-pr.js +43 -36
- package/dist/commands/watch-pr.js.map +1 -1
- package/dist/schemas/watch-pr-schema.d.ts +31 -31
- package/dist/scripts/generate-watch-pr-schema.js +3 -3
- package/dist/scripts/generate-watch-pr-schema.js.map +1 -1
- package/dist/services/ci-providers/github-actions.d.ts +25 -0
- package/dist/services/ci-providers/github-actions.d.ts.map +1 -1
- package/dist/services/ci-providers/github-actions.js +83 -46
- package/dist/services/ci-providers/github-actions.js.map +1 -1
- package/dist/utils/check-validation.d.ts.map +1 -1
- package/dist/utils/check-validation.js +9 -5
- package/dist/utils/check-validation.js.map +1 -1
- package/dist/utils/config-error-reporter.d.ts +59 -0
- package/dist/utils/config-error-reporter.d.ts.map +1 -0
- package/dist/utils/config-error-reporter.js +105 -0
- package/dist/utils/config-error-reporter.js.map +1 -0
- package/dist/utils/config-loader.js +5 -5
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/git-detection.d.ts +0 -22
- package/dist/utils/git-detection.d.ts.map +1 -1
- package/dist/utils/git-detection.js +64 -56
- package/dist/utils/git-detection.js.map +1 -1
- package/dist/utils/pid-lock.d.ts.map +1 -1
- package/dist/utils/pid-lock.js +10 -7
- package/dist/utils/pid-lock.js.map +1 -1
- package/dist/utils/project-id.d.ts.map +1 -1
- package/dist/utils/project-id.js +9 -6
- package/dist/utils/project-id.js.map +1 -1
- package/dist/utils/run-validation-with-cache.d.ts +48 -0
- package/dist/utils/run-validation-with-cache.d.ts.map +1 -0
- package/dist/utils/run-validation-with-cache.js +123 -0
- package/dist/utils/run-validation-with-cache.js.map +1 -0
- package/dist/utils/runner-adapter.js +1 -1
- package/dist/utils/runner-adapter.js.map +1 -1
- package/dist/utils/setup-checks/hooks-check.js +3 -3
- package/dist/utils/setup-checks/hooks-check.js.map +1 -1
- package/dist/utils/setup-checks/workflow-check.js +3 -3
- package/dist/utils/setup-checks/workflow-check.js.map +1 -1
- package/dist/utils/template-discovery.d.ts.map +1 -1
- package/dist/utils/template-discovery.js +13 -19
- package/dist/utils/template-discovery.js.map +1 -1
- package/dist/utils/validate-workflow.d.ts.map +1 -1
- package/dist/utils/validate-workflow.js +164 -150
- package/dist/utils/validate-workflow.js.map +1 -1
- package/dist/utils/validation-cache.d.ts +30 -0
- package/dist/utils/validation-cache.d.ts.map +1 -0
- package/dist/utils/validation-cache.js +57 -0
- package/dist/utils/validation-cache.js.map +1 -0
- package/package.json +7 -7
package/dist/commands/doctor.js
CHANGED
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @packageDocumentation
|
|
12
12
|
*/
|
|
13
|
-
import { existsSync, readFileSync } from 'fs';
|
|
14
|
-
import { execSync } from 'child_process';
|
|
13
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
14
|
+
import { execSync } from 'node:child_process';
|
|
15
15
|
import { stringify as stringifyYaml } from 'yaml';
|
|
16
16
|
import { loadConfig, findConfigPath, loadConfigWithErrors } from '../utils/config-loader.js';
|
|
17
|
+
import { formatDoctorConfigError } from '../utils/config-error-reporter.js';
|
|
17
18
|
import { checkSync, ciConfigToWorkflowOptions } from './generate-workflow.js';
|
|
18
19
|
import { getMainBranch, getRemoteOrigin } from '@vibe-validate/config';
|
|
19
20
|
import { formatTemplateList } from '../utils/template-discovery.js';
|
|
@@ -26,28 +27,24 @@ const DEPRECATED_STATE_FILE = '.vibe-validate-state.yaml';
|
|
|
26
27
|
function checkNodeVersion() {
|
|
27
28
|
try {
|
|
28
29
|
const version = execSync('node --version', { encoding: 'utf8' }).trim();
|
|
29
|
-
const majorVersion = parseInt(version.replace('v', '').split('.')[0]);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
passed: false,
|
|
41
|
-
message: `${version} is too old. Node.js 20+ required.`,
|
|
42
|
-
suggestion: 'Upgrade Node.js: https://nodejs.org/ or use nvm',
|
|
43
|
-
};
|
|
44
|
-
}
|
|
30
|
+
const majorVersion = Number.parseInt(version.replace('v', '').split('.')[0]);
|
|
31
|
+
return majorVersion >= 20 ? {
|
|
32
|
+
name: 'Node.js version',
|
|
33
|
+
passed: true,
|
|
34
|
+
message: `${version} (meets requirement: >=20.0.0)`,
|
|
35
|
+
} : {
|
|
36
|
+
name: 'Node.js version',
|
|
37
|
+
passed: false,
|
|
38
|
+
message: `${version} is too old. Node.js 20+ required.`,
|
|
39
|
+
suggestion: 'Upgrade Node.js: https://nodejs.org/ or use nvm',
|
|
40
|
+
};
|
|
45
41
|
}
|
|
46
|
-
catch (
|
|
42
|
+
catch (error) {
|
|
43
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
47
44
|
return {
|
|
48
45
|
name: 'Node.js version',
|
|
49
46
|
passed: false,
|
|
50
|
-
message:
|
|
47
|
+
message: `Failed to detect Node.js version: ${errorMessage}`,
|
|
51
48
|
suggestion: 'Install Node.js: https://nodejs.org/',
|
|
52
49
|
};
|
|
53
50
|
}
|
|
@@ -64,11 +61,12 @@ function checkGitInstalled() {
|
|
|
64
61
|
message: version,
|
|
65
62
|
};
|
|
66
63
|
}
|
|
67
|
-
catch (
|
|
64
|
+
catch (error) {
|
|
65
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
68
66
|
return {
|
|
69
67
|
name: 'Git installed',
|
|
70
68
|
passed: false,
|
|
71
|
-
message:
|
|
69
|
+
message: `Git is not installed: ${errorMessage}`,
|
|
72
70
|
suggestion: 'Install Git: https://git-scm.com/',
|
|
73
71
|
};
|
|
74
72
|
}
|
|
@@ -85,11 +83,12 @@ function checkGitRepository() {
|
|
|
85
83
|
message: 'Current directory is a git repository',
|
|
86
84
|
};
|
|
87
85
|
}
|
|
88
|
-
catch (
|
|
86
|
+
catch (error) {
|
|
87
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
89
88
|
return {
|
|
90
89
|
name: 'Git repository',
|
|
91
90
|
passed: false,
|
|
92
|
-
message:
|
|
91
|
+
message: `Current directory is not a git repository: ${errorMessage}`,
|
|
93
92
|
suggestion: 'Run: git init',
|
|
94
93
|
};
|
|
95
94
|
}
|
|
@@ -99,21 +98,16 @@ function checkGitRepository() {
|
|
|
99
98
|
*/
|
|
100
99
|
function checkConfigFile() {
|
|
101
100
|
const yamlConfig = 'vibe-validate.config.yaml';
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
passed: false,
|
|
113
|
-
message: 'Configuration file not found',
|
|
114
|
-
suggestion: 'Run: npx vibe-validate init',
|
|
115
|
-
};
|
|
116
|
-
}
|
|
101
|
+
return existsSync(yamlConfig) ? {
|
|
102
|
+
name: 'Configuration file',
|
|
103
|
+
passed: true,
|
|
104
|
+
message: `Found: ${yamlConfig}`,
|
|
105
|
+
} : {
|
|
106
|
+
name: 'Configuration file',
|
|
107
|
+
passed: false,
|
|
108
|
+
message: 'Configuration file not found',
|
|
109
|
+
suggestion: 'Run: npx vibe-validate init',
|
|
110
|
+
};
|
|
117
111
|
}
|
|
118
112
|
/**
|
|
119
113
|
* Check if configuration is valid
|
|
@@ -126,24 +120,23 @@ async function checkConfigValid(config, configWithErrors) {
|
|
|
126
120
|
if (!config) {
|
|
127
121
|
// Check if we have detailed error information
|
|
128
122
|
if (configWithErrors?.errors && configWithErrors.filePath) {
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Need to filter empty strings, not just null/undefined
|
|
129
124
|
const fileName = configWithErrors.filePath.split('/').pop() || 'vibe-validate.config.yaml';
|
|
130
|
-
const
|
|
131
|
-
|
|
125
|
+
const { message, suggestion } = formatDoctorConfigError({
|
|
126
|
+
fileName,
|
|
127
|
+
errors: configWithErrors.errors
|
|
128
|
+
});
|
|
132
129
|
return {
|
|
133
130
|
name: 'Configuration valid',
|
|
134
131
|
passed: false,
|
|
135
|
-
message
|
|
136
|
-
suggestion
|
|
137
|
-
'Fix validation errors shown above',
|
|
138
|
-
'See configuration docs: https://github.com/jdutton/vibe-validate/blob/main/docs/configuration-reference.md',
|
|
139
|
-
'JSON Schema for IDE validation: https://raw.githubusercontent.com/jdutton/vibe-validate/main/packages/config/vibe-validate.schema.json',
|
|
140
|
-
'Example YAML configs: https://github.com/jdutton/vibe-validate/tree/main/config-templates'
|
|
141
|
-
].join('\n '),
|
|
132
|
+
message,
|
|
133
|
+
suggestion,
|
|
142
134
|
};
|
|
143
135
|
}
|
|
144
136
|
// Fallback: try to find config file path
|
|
145
137
|
const configPath = findConfigPath();
|
|
146
138
|
if (configPath) {
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Need to filter empty strings, not just null/undefined
|
|
147
140
|
const fileName = configPath.split('/').pop() || 'vibe-validate.config.yaml';
|
|
148
141
|
return {
|
|
149
142
|
name: 'Configuration valid',
|
|
@@ -153,7 +146,7 @@ async function checkConfigValid(config, configWithErrors) {
|
|
|
153
146
|
`Fix syntax/validation errors in ${fileName}`,
|
|
154
147
|
'See configuration docs: https://github.com/jdutton/vibe-validate/blob/main/docs/configuration-reference.md',
|
|
155
148
|
'JSON Schema for IDE validation: https://raw.githubusercontent.com/jdutton/vibe-validate/main/packages/config/vibe-validate.schema.json',
|
|
156
|
-
'Example YAML configs: https://github.com/jdutton/vibe-validate/tree/main/config-templates'
|
|
149
|
+
'Example YAML configs: https://github.com/jdutton/vibe-validate/tree/main/packages/cli/config-templates'
|
|
157
150
|
].join('\n '),
|
|
158
151
|
};
|
|
159
152
|
}
|
|
@@ -166,7 +159,7 @@ async function checkConfigValid(config, configWithErrors) {
|
|
|
166
159
|
message: 'No configuration file found',
|
|
167
160
|
suggestion: [
|
|
168
161
|
'Copy a config template from GitHub:',
|
|
169
|
-
'https://github.com/jdutton/vibe-validate/tree/main/config-templates',
|
|
162
|
+
'https://github.com/jdutton/vibe-validate/tree/main/packages/cli/config-templates',
|
|
170
163
|
'',
|
|
171
164
|
'Available templates:',
|
|
172
165
|
...templateList.map(line => line),
|
|
@@ -187,11 +180,11 @@ async function checkConfigValid(config, configWithErrors) {
|
|
|
187
180
|
message: `Loaded successfully (${config.validation.phases.length} phases)`,
|
|
188
181
|
};
|
|
189
182
|
}
|
|
190
|
-
catch (
|
|
183
|
+
catch (error) {
|
|
191
184
|
return {
|
|
192
185
|
name: 'Configuration valid',
|
|
193
186
|
passed: false,
|
|
194
|
-
message: `Invalid configuration: ${
|
|
187
|
+
message: `Invalid configuration: ${error instanceof Error ? error.message : String(error)}`,
|
|
195
188
|
suggestion: 'Check syntax in vibe-validate.config.yaml',
|
|
196
189
|
};
|
|
197
190
|
}
|
|
@@ -210,7 +203,7 @@ async function checkPackageManager(config) {
|
|
|
210
203
|
};
|
|
211
204
|
}
|
|
212
205
|
// Detect package manager from config commands
|
|
213
|
-
const firstCommand = config.validation.phases[0]?.steps[0]?.command
|
|
206
|
+
const firstCommand = config.validation.phases[0]?.steps[0]?.command ?? '';
|
|
214
207
|
const pm = firstCommand.startsWith('pnpm ') ? 'pnpm' : 'npm';
|
|
215
208
|
try {
|
|
216
209
|
const version = execSync(`${pm} --version`, { encoding: 'utf8' }).trim();
|
|
@@ -220,22 +213,24 @@ async function checkPackageManager(config) {
|
|
|
220
213
|
message: `${pm} ${version} is available`,
|
|
221
214
|
};
|
|
222
215
|
}
|
|
223
|
-
catch (
|
|
216
|
+
catch (error) {
|
|
217
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
224
218
|
return {
|
|
225
219
|
name: 'Package manager',
|
|
226
220
|
passed: false,
|
|
227
|
-
message: `${pm} not found (required by config commands)`,
|
|
221
|
+
message: `${pm} not found (required by config commands): ${errorMessage}`,
|
|
228
222
|
suggestion: pm === 'pnpm'
|
|
229
223
|
? 'Install pnpm: npm install -g pnpm'
|
|
230
224
|
: 'npm should be installed with Node.js',
|
|
231
225
|
};
|
|
232
226
|
}
|
|
233
227
|
}
|
|
234
|
-
catch (
|
|
228
|
+
catch (error) {
|
|
229
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
235
230
|
return {
|
|
236
231
|
name: 'Package manager',
|
|
237
232
|
passed: true,
|
|
238
|
-
message:
|
|
233
|
+
message: `Skipped (config check failed): ${errorMessage}`,
|
|
239
234
|
};
|
|
240
235
|
}
|
|
241
236
|
}
|
|
@@ -262,27 +257,22 @@ async function checkWorkflowSync(config) {
|
|
|
262
257
|
// Use CI config from vibe-validate config
|
|
263
258
|
const generateOptions = ciConfigToWorkflowOptions(config);
|
|
264
259
|
const { inSync, diff } = checkSync(config, generateOptions);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
passed: false,
|
|
276
|
-
message: `Workflow is out of sync: ${diff || 'differs from config'}`,
|
|
277
|
-
suggestion: 'Manual: npx vibe-validate generate-workflow\n 💡 Or run: vibe-validate init --setup-workflow',
|
|
278
|
-
};
|
|
279
|
-
}
|
|
260
|
+
return inSync ? {
|
|
261
|
+
name: 'GitHub Actions workflow',
|
|
262
|
+
passed: true,
|
|
263
|
+
message: 'Workflow is in sync with config',
|
|
264
|
+
} : {
|
|
265
|
+
name: 'GitHub Actions workflow',
|
|
266
|
+
passed: false,
|
|
267
|
+
message: `Workflow is out of sync: ${diff ?? 'differs from config'}`,
|
|
268
|
+
suggestion: 'Manual: npx vibe-validate generate-workflow\n 💡 Or run: vibe-validate init --setup-workflow',
|
|
269
|
+
};
|
|
280
270
|
}
|
|
281
|
-
catch (
|
|
271
|
+
catch (error) {
|
|
282
272
|
return {
|
|
283
273
|
name: 'GitHub Actions workflow',
|
|
284
274
|
passed: false,
|
|
285
|
-
message: `Failed to check workflow sync: ${
|
|
275
|
+
message: `Failed to check workflow sync: ${error instanceof Error ? error.message : String(error)}`,
|
|
286
276
|
suggestion: 'Verify workflow file syntax',
|
|
287
277
|
};
|
|
288
278
|
}
|
|
@@ -336,11 +326,12 @@ async function checkPreCommitHook(config) {
|
|
|
336
326
|
};
|
|
337
327
|
}
|
|
338
328
|
}
|
|
339
|
-
catch (
|
|
329
|
+
catch (error) {
|
|
330
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
340
331
|
return {
|
|
341
332
|
name: 'Pre-commit hook',
|
|
342
333
|
passed: false,
|
|
343
|
-
message:
|
|
334
|
+
message: `Pre-commit hook exists but unreadable: ${errorMessage}`,
|
|
344
335
|
suggestion: 'Fix file permissions or set hooks.preCommit.enabled=false',
|
|
345
336
|
};
|
|
346
337
|
}
|
|
@@ -398,20 +389,22 @@ async function checkVersion() {
|
|
|
398
389
|
};
|
|
399
390
|
}
|
|
400
391
|
}
|
|
401
|
-
catch (
|
|
392
|
+
catch (npmError) {
|
|
402
393
|
// npm registry unavailable - not a critical error
|
|
394
|
+
const errorMessage = npmError instanceof Error ? npmError.message : String(npmError);
|
|
403
395
|
return {
|
|
404
396
|
name: 'vibe-validate version',
|
|
405
397
|
passed: true,
|
|
406
|
-
message: `Current version: ${currentVersion} (unable to check for updates)`,
|
|
398
|
+
message: `Current version: ${currentVersion} (unable to check for updates: ${errorMessage})`,
|
|
407
399
|
};
|
|
408
400
|
}
|
|
409
401
|
}
|
|
410
|
-
catch (
|
|
402
|
+
catch (error) {
|
|
403
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
411
404
|
return {
|
|
412
405
|
name: 'vibe-validate version',
|
|
413
406
|
passed: true,
|
|
414
|
-
message:
|
|
407
|
+
message: `Unable to determine version: ${errorMessage}`,
|
|
415
408
|
};
|
|
416
409
|
}
|
|
417
410
|
}
|
|
@@ -420,6 +413,7 @@ async function checkVersion() {
|
|
|
420
413
|
*/
|
|
421
414
|
function checkGitignoreStateFile() {
|
|
422
415
|
const gitignorePath = '.gitignore';
|
|
416
|
+
// eslint-disable-next-line sonarjs/deprecation -- Intentionally checking deprecated file location for migration guidance
|
|
423
417
|
const stateFileName = DEPRECATED_STATE_FILE;
|
|
424
418
|
// Check if .gitignore exists
|
|
425
419
|
if (!existsSync(gitignorePath)) {
|
|
@@ -436,7 +430,9 @@ function checkGitignoreStateFile() {
|
|
|
436
430
|
return {
|
|
437
431
|
name: 'Gitignore state file (deprecated)',
|
|
438
432
|
passed: false,
|
|
433
|
+
// eslint-disable-next-line sonarjs/deprecation -- Intentionally checking deprecated file location for migration guidance
|
|
439
434
|
message: `${DEPRECATED_STATE_FILE} in .gitignore (deprecated - can be removed)`,
|
|
435
|
+
// eslint-disable-next-line sonarjs/deprecation -- Intentionally checking deprecated file location for migration guidance
|
|
440
436
|
suggestion: `Remove from .gitignore: sed -i.bak '/${DEPRECATED_STATE_FILE}/d' .gitignore && rm .gitignore.bak\n ℹ️ Validation now uses git notes instead of state file`,
|
|
441
437
|
};
|
|
442
438
|
}
|
|
@@ -448,11 +444,12 @@ function checkGitignoreStateFile() {
|
|
|
448
444
|
};
|
|
449
445
|
}
|
|
450
446
|
}
|
|
451
|
-
catch (
|
|
447
|
+
catch (error) {
|
|
448
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
452
449
|
return {
|
|
453
450
|
name: 'Gitignore state file',
|
|
454
451
|
passed: false,
|
|
455
|
-
message:
|
|
452
|
+
message: `.gitignore exists but is unreadable: ${errorMessage}`,
|
|
456
453
|
suggestion: 'Fix file permissions: chmod 644 .gitignore',
|
|
457
454
|
};
|
|
458
455
|
}
|
|
@@ -461,13 +458,16 @@ function checkGitignoreStateFile() {
|
|
|
461
458
|
* Check if deprecated validation state file exists
|
|
462
459
|
*/
|
|
463
460
|
function checkValidationState() {
|
|
461
|
+
// eslint-disable-next-line sonarjs/deprecation -- Intentionally checking deprecated file location for migration guidance
|
|
464
462
|
const statePath = DEPRECATED_STATE_FILE;
|
|
465
463
|
// Check if deprecated state file exists
|
|
466
464
|
if (existsSync(statePath)) {
|
|
467
465
|
return {
|
|
468
466
|
name: 'Validation state (deprecated)',
|
|
469
467
|
passed: false,
|
|
468
|
+
// eslint-disable-next-line sonarjs/deprecation -- Intentionally checking deprecated file location for migration guidance
|
|
470
469
|
message: `${DEPRECATED_STATE_FILE} found (deprecated file - safe to remove)`,
|
|
470
|
+
// eslint-disable-next-line sonarjs/deprecation -- Intentionally checking deprecated file location for migration guidance
|
|
471
471
|
suggestion: `Remove deprecated state file: rm ${DEPRECATED_STATE_FILE}\n ℹ️ Validation now uses git notes for improved caching`,
|
|
472
472
|
};
|
|
473
473
|
}
|
|
@@ -500,12 +500,13 @@ async function checkHistoryHealth() {
|
|
|
500
500
|
suggestion: 'Prune old history: vibe-validate history prune --older-than "90 days"',
|
|
501
501
|
};
|
|
502
502
|
}
|
|
503
|
-
catch (
|
|
503
|
+
catch (error) {
|
|
504
504
|
// Git notes not available or other error - not critical
|
|
505
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
505
506
|
return {
|
|
506
507
|
name: 'Validation history',
|
|
507
508
|
passed: true,
|
|
508
|
-
message:
|
|
509
|
+
message: `History unavailable (not in git repo or no validation runs yet): ${errorMessage}`,
|
|
509
510
|
};
|
|
510
511
|
}
|
|
511
512
|
}
|
|
@@ -532,8 +533,9 @@ async function checkMainBranch(config) {
|
|
|
532
533
|
message: `Branch '${mainBranch}' exists locally`,
|
|
533
534
|
};
|
|
534
535
|
}
|
|
535
|
-
catch (
|
|
536
|
+
catch (localError) {
|
|
536
537
|
// Local branch doesn't exist, check for remote branch
|
|
538
|
+
const localErrorMsg = localError instanceof Error ? localError.message : String(localError);
|
|
537
539
|
try {
|
|
538
540
|
execSync(`git rev-parse --verify ${remoteOrigin}/${mainBranch}`, { stdio: 'pipe' });
|
|
539
541
|
return {
|
|
@@ -542,21 +544,23 @@ async function checkMainBranch(config) {
|
|
|
542
544
|
message: `Branch '${mainBranch}' exists on remote '${remoteOrigin}' (fetch-depth: 0 required in CI)`,
|
|
543
545
|
};
|
|
544
546
|
}
|
|
545
|
-
catch (
|
|
547
|
+
catch (remoteError) {
|
|
548
|
+
const remoteErrorMsg = remoteError instanceof Error ? remoteError.message : String(remoteError);
|
|
546
549
|
return {
|
|
547
550
|
name: 'Git main branch',
|
|
548
551
|
passed: false,
|
|
549
|
-
message: `Configured main branch '${mainBranch}' does not exist locally or on remote '${remoteOrigin}'`,
|
|
552
|
+
message: `Configured main branch '${mainBranch}' does not exist locally (${localErrorMsg}) or on remote '${remoteOrigin}' (${remoteErrorMsg})`,
|
|
550
553
|
suggestion: `Create branch: git checkout -b ${mainBranch} OR update config to use existing branch (e.g., 'master', 'develop')`,
|
|
551
554
|
};
|
|
552
555
|
}
|
|
553
556
|
}
|
|
554
557
|
}
|
|
555
|
-
catch (
|
|
558
|
+
catch (error) {
|
|
559
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
556
560
|
return {
|
|
557
561
|
name: 'Git main branch',
|
|
558
562
|
passed: true,
|
|
559
|
-
message:
|
|
563
|
+
message: `Skipped (config or git error): ${errorMessage}`,
|
|
560
564
|
};
|
|
561
565
|
}
|
|
562
566
|
}
|
|
@@ -595,20 +599,22 @@ async function checkRemoteOrigin(config) {
|
|
|
595
599
|
};
|
|
596
600
|
}
|
|
597
601
|
}
|
|
598
|
-
catch (
|
|
602
|
+
catch (error) {
|
|
603
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
599
604
|
return {
|
|
600
605
|
name: 'Git remote origin',
|
|
601
606
|
passed: false,
|
|
602
|
-
message:
|
|
607
|
+
message: `Failed to list git remotes: ${errorMessage}`,
|
|
603
608
|
suggestion: 'Verify git repository is initialized',
|
|
604
609
|
};
|
|
605
610
|
}
|
|
606
611
|
}
|
|
607
|
-
catch (
|
|
612
|
+
catch (error) {
|
|
613
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
608
614
|
return {
|
|
609
615
|
name: 'Git remote origin',
|
|
610
616
|
passed: true,
|
|
611
|
-
message:
|
|
617
|
+
message: `Skipped (config or git error): ${errorMessage}`,
|
|
612
618
|
};
|
|
613
619
|
}
|
|
614
620
|
}
|
|
@@ -647,20 +653,64 @@ async function checkRemoteMainBranch(config) {
|
|
|
647
653
|
message: `Branch '${mainBranch}' exists on remote '${remoteOrigin}'`,
|
|
648
654
|
};
|
|
649
655
|
}
|
|
650
|
-
catch (
|
|
656
|
+
catch (error) {
|
|
657
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
651
658
|
return {
|
|
652
659
|
name: 'Git remote main branch',
|
|
653
660
|
passed: false,
|
|
654
|
-
message: `Branch '${mainBranch}' does not exist on remote '${remoteOrigin}'`,
|
|
661
|
+
message: `Branch '${mainBranch}' does not exist on remote '${remoteOrigin}': ${errorMessage}`,
|
|
655
662
|
suggestion: `Push branch: git push ${remoteOrigin} ${mainBranch} OR update config to match remote branch name`,
|
|
656
663
|
};
|
|
657
664
|
}
|
|
658
665
|
}
|
|
659
|
-
catch (
|
|
666
|
+
catch (error) {
|
|
667
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
660
668
|
return {
|
|
661
669
|
name: 'Git remote main branch',
|
|
662
670
|
passed: true,
|
|
663
|
-
message:
|
|
671
|
+
message: `Skipped (config or git error): ${errorMessage}`,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Get tool version by trying multiple version flag variants
|
|
677
|
+
*/
|
|
678
|
+
function getToolVersion(toolName) {
|
|
679
|
+
try {
|
|
680
|
+
return execSync(`${toolName} version`, { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
681
|
+
}
|
|
682
|
+
catch (versionError) {
|
|
683
|
+
console.debug(`${toolName} version failed: ${versionError instanceof Error ? versionError.message : String(versionError)}`);
|
|
684
|
+
return execSync(`${toolName} --version`, { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Check if scanning tool is available
|
|
689
|
+
*/
|
|
690
|
+
function checkScanningToolAvailable(toolName) {
|
|
691
|
+
try {
|
|
692
|
+
const version = getToolVersion(toolName);
|
|
693
|
+
return {
|
|
694
|
+
name: 'Pre-commit secret scanning',
|
|
695
|
+
passed: true,
|
|
696
|
+
message: `Secret scanning enabled with ${toolName} ${version}`,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
701
|
+
const isCI = process.env.CI === 'true' || process.env.CI === '1';
|
|
702
|
+
if (isCI) {
|
|
703
|
+
return {
|
|
704
|
+
name: 'Pre-commit secret scanning',
|
|
705
|
+
passed: true,
|
|
706
|
+
message: `Secret scanning enabled (pre-commit only, not needed in CI)`,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
name: 'Pre-commit secret scanning',
|
|
711
|
+
passed: true,
|
|
712
|
+
message: `Secret scanning enabled but '${toolName}' not found: ${errorMessage}`,
|
|
713
|
+
suggestion: `Install ${toolName}:\n • gitleaks: brew install gitleaks\n • Or disable: set hooks.preCommit.secretScanning.enabled=false in config`,
|
|
664
714
|
};
|
|
665
715
|
}
|
|
666
716
|
}
|
|
@@ -677,7 +727,6 @@ async function checkSecretScanning(config) {
|
|
|
677
727
|
};
|
|
678
728
|
}
|
|
679
729
|
const secretScanning = config.hooks?.preCommit?.secretScanning;
|
|
680
|
-
// If not configured at all, recommend enabling it
|
|
681
730
|
if (!secretScanning) {
|
|
682
731
|
return {
|
|
683
732
|
name: 'Pre-commit secret scanning',
|
|
@@ -686,7 +735,6 @@ async function checkSecretScanning(config) {
|
|
|
686
735
|
suggestion: 'Recommended: Enable secret scanning to prevent credential leaks\n • Add to config: hooks.preCommit.secretScanning.enabled=true\n • scanCommand: "gitleaks protect --staged --verbose"\n • Install gitleaks: brew install gitleaks',
|
|
687
736
|
};
|
|
688
737
|
}
|
|
689
|
-
// If explicitly disabled, acknowledge user choice
|
|
690
738
|
if (secretScanning.enabled === false) {
|
|
691
739
|
return {
|
|
692
740
|
name: 'Pre-commit secret scanning',
|
|
@@ -694,58 +742,23 @@ async function checkSecretScanning(config) {
|
|
|
694
742
|
message: 'Secret scanning disabled in config (user preference)',
|
|
695
743
|
};
|
|
696
744
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
try {
|
|
705
|
-
version = execSync(`${toolName} version`, { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
706
|
-
}
|
|
707
|
-
catch (_versionError) {
|
|
708
|
-
// Try --version flag
|
|
709
|
-
version = execSync(`${toolName} --version`, { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
710
|
-
}
|
|
711
|
-
return {
|
|
712
|
-
name: 'Pre-commit secret scanning',
|
|
713
|
-
passed: true,
|
|
714
|
-
message: `Secret scanning enabled with ${toolName} ${version}`,
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
catch (_error) {
|
|
718
|
-
// Tool not found
|
|
719
|
-
// In CI, this is expected (secret scanning is pre-commit only, not needed in CI)
|
|
720
|
-
const isCI = process.env.CI === 'true' || process.env.CI === '1';
|
|
721
|
-
if (isCI) {
|
|
722
|
-
return {
|
|
723
|
-
name: 'Pre-commit secret scanning',
|
|
724
|
-
passed: true,
|
|
725
|
-
message: `Secret scanning enabled (pre-commit only, not needed in CI)`,
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
return {
|
|
729
|
-
name: 'Pre-commit secret scanning',
|
|
730
|
-
passed: true, // Advisory only, never fails
|
|
731
|
-
message: `Secret scanning enabled but '${toolName}' not found`,
|
|
732
|
-
suggestion: `Install ${toolName}:\n • gitleaks: brew install gitleaks\n • Or disable: set hooks.preCommit.secretScanning.enabled=false in config`,
|
|
733
|
-
};
|
|
734
|
-
}
|
|
745
|
+
if (!secretScanning.scanCommand) {
|
|
746
|
+
return {
|
|
747
|
+
name: 'Pre-commit secret scanning',
|
|
748
|
+
passed: true,
|
|
749
|
+
message: 'Secret scanning enabled but no scanCommand configured',
|
|
750
|
+
suggestion: 'Add hooks.preCommit.secretScanning.scanCommand to config',
|
|
751
|
+
};
|
|
735
752
|
}
|
|
736
|
-
|
|
737
|
-
return
|
|
738
|
-
name: 'Pre-commit secret scanning',
|
|
739
|
-
passed: true,
|
|
740
|
-
message: 'Secret scanning enabled but no scanCommand configured',
|
|
741
|
-
suggestion: 'Add hooks.preCommit.secretScanning.scanCommand to config',
|
|
742
|
-
};
|
|
753
|
+
const toolName = secretScanning.scanCommand.split(' ')[0];
|
|
754
|
+
return checkScanningToolAvailable(toolName);
|
|
743
755
|
}
|
|
744
|
-
catch (
|
|
756
|
+
catch (error) {
|
|
757
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
745
758
|
return {
|
|
746
759
|
name: 'Pre-commit secret scanning',
|
|
747
760
|
passed: true,
|
|
748
|
-
message:
|
|
761
|
+
message: `Skipped (config or execution error): ${errorMessage}`,
|
|
749
762
|
};
|
|
750
763
|
}
|
|
751
764
|
}
|
|
@@ -762,8 +775,10 @@ export async function runDoctor(options = {}) {
|
|
|
762
775
|
config = await loadConfig();
|
|
763
776
|
configWithErrors = await loadConfigWithErrors();
|
|
764
777
|
}
|
|
765
|
-
catch (
|
|
778
|
+
catch (error) {
|
|
766
779
|
// Config load error will be caught by checkConfigValid
|
|
780
|
+
// Intentionally suppressing error here as it will be reported by checkConfigValid
|
|
781
|
+
console.debug(`Config load failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
767
782
|
config = null;
|
|
768
783
|
configWithErrors = { config: null, errors: null, filePath: null };
|
|
769
784
|
}
|
|
@@ -803,6 +818,61 @@ export async function runDoctor(options = {}) {
|
|
|
803
818
|
passedChecks,
|
|
804
819
|
};
|
|
805
820
|
}
|
|
821
|
+
/**
|
|
822
|
+
* Output doctor results in YAML format
|
|
823
|
+
*/
|
|
824
|
+
async function outputDoctorYaml(result) {
|
|
825
|
+
// Small delay to ensure stderr is flushed
|
|
826
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
827
|
+
// RFC 4627 separator
|
|
828
|
+
process.stdout.write('---\n');
|
|
829
|
+
// Write pure YAML
|
|
830
|
+
process.stdout.write(stringifyYaml(result));
|
|
831
|
+
// CRITICAL: Wait for stdout to flush before exiting
|
|
832
|
+
await new Promise(resolve => {
|
|
833
|
+
if (process.stdout.write('')) {
|
|
834
|
+
resolve();
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
process.stdout.once('drain', resolve);
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Display doctor results in human-friendly format
|
|
843
|
+
*/
|
|
844
|
+
function displayDoctorResults(result) {
|
|
845
|
+
console.log('🩺 vibe-validate Doctor\n');
|
|
846
|
+
const modeMessage = result.verboseMode
|
|
847
|
+
? 'Running diagnostic checks (verbose mode)...\n'
|
|
848
|
+
: 'Running diagnostic checks...\n';
|
|
849
|
+
console.log(modeMessage);
|
|
850
|
+
// Print each check
|
|
851
|
+
for (const check of result.checks) {
|
|
852
|
+
const icon = check.passed ? '✅' : '❌';
|
|
853
|
+
console.log(`${icon} ${check.name}`);
|
|
854
|
+
console.log(` ${check.message}`);
|
|
855
|
+
if (check.suggestion) {
|
|
856
|
+
console.log(` 💡 ${check.suggestion}`);
|
|
857
|
+
}
|
|
858
|
+
console.log('');
|
|
859
|
+
}
|
|
860
|
+
// Summary
|
|
861
|
+
console.log(`📊 Results: ${result.passedChecks}/${result.totalChecks} checks passed\n`);
|
|
862
|
+
if (result.allPassed) {
|
|
863
|
+
console.log('✨ All checks passed! Your vibe-validate setup looks healthy.');
|
|
864
|
+
if (!result.verboseMode) {
|
|
865
|
+
console.log(' (Use --verbose to see all checks)');
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
else {
|
|
869
|
+
console.log('⚠️ Some checks failed. See suggestions above to fix.');
|
|
870
|
+
if (!result.verboseMode) {
|
|
871
|
+
console.log(' (Use --verbose to see all checks including passing ones)');
|
|
872
|
+
}
|
|
873
|
+
process.exit(1);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
806
876
|
/**
|
|
807
877
|
* Main command handler for Commander.js
|
|
808
878
|
*/
|
|
@@ -817,62 +887,15 @@ export function doctorCommand(program) {
|
|
|
817
887
|
try {
|
|
818
888
|
const result = await runDoctor({ verbose });
|
|
819
889
|
if (options.yaml) {
|
|
820
|
-
|
|
821
|
-
// Small delay to ensure stderr is flushed
|
|
822
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
823
|
-
// RFC 4627 separator
|
|
824
|
-
process.stdout.write('---\n');
|
|
825
|
-
// Write pure YAML
|
|
826
|
-
process.stdout.write(stringifyYaml(result));
|
|
827
|
-
// CRITICAL: Wait for stdout to flush before exiting
|
|
828
|
-
await new Promise(resolve => {
|
|
829
|
-
if (process.stdout.write('')) {
|
|
830
|
-
resolve();
|
|
831
|
-
}
|
|
832
|
-
else {
|
|
833
|
-
process.stdout.once('drain', resolve);
|
|
834
|
-
}
|
|
835
|
-
});
|
|
890
|
+
await outputDoctorYaml(result);
|
|
836
891
|
}
|
|
837
892
|
else {
|
|
838
|
-
|
|
839
|
-
console.log('🩺 vibe-validate Doctor\n');
|
|
840
|
-
if (result.verboseMode) {
|
|
841
|
-
console.log('Running diagnostic checks (verbose mode)...\n');
|
|
842
|
-
}
|
|
843
|
-
else {
|
|
844
|
-
console.log('Running diagnostic checks...\n');
|
|
845
|
-
}
|
|
846
|
-
// Print each check
|
|
847
|
-
for (const check of result.checks) {
|
|
848
|
-
const icon = check.passed ? '✅' : '❌';
|
|
849
|
-
console.log(`${icon} ${check.name}`);
|
|
850
|
-
console.log(` ${check.message}`);
|
|
851
|
-
if (check.suggestion) {
|
|
852
|
-
console.log(` 💡 ${check.suggestion}`);
|
|
853
|
-
}
|
|
854
|
-
console.log('');
|
|
855
|
-
}
|
|
856
|
-
// Summary
|
|
857
|
-
console.log(`📊 Results: ${result.passedChecks}/${result.totalChecks} checks passed\n`);
|
|
858
|
-
if (result.allPassed) {
|
|
859
|
-
console.log('✨ All checks passed! Your vibe-validate setup looks healthy.');
|
|
860
|
-
if (!result.verboseMode) {
|
|
861
|
-
console.log(' (Use --verbose to see all checks)');
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
else {
|
|
865
|
-
console.log('⚠️ Some checks failed. See suggestions above to fix.');
|
|
866
|
-
if (!result.verboseMode) {
|
|
867
|
-
console.log(' (Use --verbose to see all checks including passing ones)');
|
|
868
|
-
}
|
|
869
|
-
process.exit(1);
|
|
870
|
-
}
|
|
893
|
+
displayDoctorResults(result);
|
|
871
894
|
}
|
|
872
895
|
}
|
|
873
|
-
catch (
|
|
896
|
+
catch (error) {
|
|
874
897
|
console.error('❌ Doctor check failed:');
|
|
875
|
-
console.error(
|
|
898
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
876
899
|
process.exit(1);
|
|
877
900
|
}
|
|
878
901
|
});
|