@vibe-validate/cli 0.10.3 → 0.12.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/README.md +84 -92
- package/dist/bin.js +137 -20
- package/dist/bin.js.map +1 -1
- package/dist/commands/cleanup.d.ts +4 -0
- package/dist/commands/cleanup.d.ts.map +1 -1
- package/dist/commands/cleanup.js +96 -15
- package/dist/commands/cleanup.js.map +1 -1
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +83 -15
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +385 -82
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/generate-workflow.d.ts +6 -2
- package/dist/commands/generate-workflow.d.ts.map +1 -1
- package/dist/commands/generate-workflow.js +188 -33
- package/dist/commands/generate-workflow.js.map +1 -1
- package/dist/commands/history.d.ts +13 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +415 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +252 -109
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pre-commit.d.ts +4 -0
- package/dist/commands/pre-commit.d.ts.map +1 -1
- package/dist/commands/pre-commit.js +158 -7
- package/dist/commands/pre-commit.js.map +1 -1
- package/dist/commands/state.d.ts +5 -1
- package/dist/commands/state.d.ts.map +1 -1
- package/dist/commands/state.js +192 -23
- package/dist/commands/state.js.map +1 -1
- package/dist/commands/sync-check.d.ts +4 -0
- package/dist/commands/sync-check.d.ts.map +1 -1
- package/dist/commands/sync-check.js +101 -14
- package/dist/commands/sync-check.js.map +1 -1
- package/dist/commands/validate.d.ts +5 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +184 -28
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/watch-pr.d.ts +10 -0
- package/dist/commands/watch-pr.d.ts.map +1 -0
- package/dist/commands/watch-pr.js +443 -0
- package/dist/commands/watch-pr.js.map +1 -0
- package/dist/schemas/watch-pr-schema.d.ts +261 -0
- package/dist/schemas/watch-pr-schema.d.ts.map +1 -0
- package/dist/schemas/watch-pr-schema.js +58 -0
- package/dist/schemas/watch-pr-schema.js.map +1 -0
- package/dist/scripts/generate-watch-pr-schema.d.ts +12 -0
- package/dist/scripts/generate-watch-pr-schema.d.ts.map +1 -0
- package/dist/scripts/generate-watch-pr-schema.js +35 -0
- package/dist/scripts/generate-watch-pr-schema.js.map +1 -0
- package/dist/services/ci-provider-registry.d.ts +38 -0
- package/dist/services/ci-provider-registry.d.ts.map +1 -0
- package/dist/services/ci-provider-registry.js +53 -0
- package/dist/services/ci-provider-registry.js.map +1 -0
- package/dist/services/ci-provider.d.ts +165 -0
- package/dist/services/ci-provider.d.ts.map +1 -0
- package/dist/services/ci-provider.js +11 -0
- package/dist/services/ci-provider.js.map +1 -0
- package/dist/services/ci-providers/github-actions.d.ts +41 -0
- package/dist/services/ci-providers/github-actions.d.ts.map +1 -0
- package/dist/services/ci-providers/github-actions.js +314 -0
- package/dist/services/ci-providers/github-actions.js.map +1 -0
- package/dist/utils/check-validation.d.ts +7 -4
- package/dist/utils/check-validation.d.ts.map +1 -1
- package/dist/utils/check-validation.js +129 -48
- package/dist/utils/check-validation.js.map +1 -1
- package/dist/utils/config-loader.d.ts +15 -3
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +61 -17
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/context-detector.d.ts +1 -1
- package/dist/utils/context-detector.js +1 -1
- package/dist/utils/normalize-line-endings.d.ts +53 -0
- package/dist/utils/normalize-line-endings.d.ts.map +1 -0
- package/dist/utils/normalize-line-endings.js +57 -0
- package/dist/utils/normalize-line-endings.js.map +1 -0
- 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.d.ts +1 -0
- package/dist/utils/runner-adapter.d.ts.map +1 -1
- package/dist/utils/runner-adapter.js +25 -17
- package/dist/utils/runner-adapter.js.map +1 -1
- package/dist/utils/setup-checks/gitignore-check.d.ts +10 -15
- package/dist/utils/setup-checks/gitignore-check.d.ts.map +1 -1
- package/dist/utils/setup-checks/gitignore-check.js +20 -138
- package/dist/utils/setup-checks/gitignore-check.js.map +1 -1
- package/dist/utils/template-discovery.d.ts +40 -0
- package/dist/utils/template-discovery.d.ts.map +1 -0
- package/dist/utils/template-discovery.js +136 -0
- package/dist/utils/template-discovery.js.map +1 -0
- package/dist/utils/validate-workflow.d.ts +28 -0
- package/dist/utils/validate-workflow.d.ts.map +1 -0
- package/dist/utils/validate-workflow.js +247 -0
- package/dist/utils/validate-workflow.js.map +1 -0
- 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 +19 -16
- package/watch-pr-result.schema.json +204 -0
- package/LICENSE +0 -21
package/dist/commands/doctor.js
CHANGED
|
@@ -12,9 +12,14 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { existsSync, readFileSync } from 'fs';
|
|
14
14
|
import { execSync } from 'child_process';
|
|
15
|
-
import {
|
|
15
|
+
import { stringify as stringifyYaml } from 'yaml';
|
|
16
|
+
import { loadConfig, findConfigPath, loadConfigWithErrors } from '../utils/config-loader.js';
|
|
16
17
|
import { checkSync, ciConfigToWorkflowOptions } from './generate-workflow.js';
|
|
17
18
|
import { getMainBranch, getRemoteOrigin } from '@vibe-validate/config';
|
|
19
|
+
import { formatTemplateList } from '../utils/template-discovery.js';
|
|
20
|
+
import { checkHistoryHealth as checkValidationHistoryHealth } from '@vibe-validate/history';
|
|
21
|
+
/** @deprecated State file deprecated in v0.12.0 - validation now uses git notes */
|
|
22
|
+
const DEPRECATED_STATE_FILE = '.vibe-validate-state.yaml';
|
|
18
23
|
/**
|
|
19
24
|
* Check Node.js version meets requirements
|
|
20
25
|
*/
|
|
@@ -93,16 +98,12 @@ function checkGitRepository() {
|
|
|
93
98
|
* Check if configuration file exists
|
|
94
99
|
*/
|
|
95
100
|
function checkConfigFile() {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
'vibe-validate.config.mjs', // Legacy (deprecated)
|
|
99
|
-
];
|
|
100
|
-
const found = configPatterns.find(pattern => existsSync(pattern));
|
|
101
|
-
if (found) {
|
|
101
|
+
const yamlConfig = 'vibe-validate.config.yaml';
|
|
102
|
+
if (existsSync(yamlConfig)) {
|
|
102
103
|
return {
|
|
103
104
|
name: 'Configuration file',
|
|
104
105
|
passed: true,
|
|
105
|
-
message: `Found: ${
|
|
106
|
+
message: `Found: ${yamlConfig}`,
|
|
106
107
|
};
|
|
107
108
|
}
|
|
108
109
|
else {
|
|
@@ -114,48 +115,71 @@ function checkConfigFile() {
|
|
|
114
115
|
};
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
|
-
/**
|
|
118
|
-
* Check if config format needs migration from .mjs to .yaml
|
|
119
|
-
*/
|
|
120
|
-
function checkConfigFormatMigration() {
|
|
121
|
-
const mjsConfig = 'vibe-validate.config.mjs';
|
|
122
|
-
const yamlConfig = 'vibe-validate.config.yaml';
|
|
123
|
-
// If using YAML, all good!
|
|
124
|
-
if (existsSync(yamlConfig)) {
|
|
125
|
-
return {
|
|
126
|
-
name: 'Config format',
|
|
127
|
-
passed: true,
|
|
128
|
-
message: 'Using modern YAML format',
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
// If using legacy .mjs format, suggest migration
|
|
132
|
-
if (existsSync(mjsConfig)) {
|
|
133
|
-
return {
|
|
134
|
-
name: 'Config format',
|
|
135
|
-
passed: false,
|
|
136
|
-
message: 'Using deprecated .mjs format (will be removed in v1.0)',
|
|
137
|
-
suggestion: '⚠️ Migrate to YAML:\n 1. Run: vibe-validate init --migrate\n 2. Test the new YAML config\n 3. Delete vibe-validate.config.mjs after migration',
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
// No config found (will be caught by checkConfigFile)
|
|
141
|
-
return {
|
|
142
|
-
name: 'Config format',
|
|
143
|
-
passed: true,
|
|
144
|
-
message: 'Skipped (no config file)',
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
118
|
/**
|
|
148
119
|
* Check if configuration is valid
|
|
120
|
+
*
|
|
121
|
+
* @param config - Config from loadConfig() (may be null)
|
|
122
|
+
* @param configWithErrors - Result from loadConfigWithErrors() with detailed error info
|
|
149
123
|
*/
|
|
150
|
-
async function checkConfigValid(config) {
|
|
124
|
+
async function checkConfigValid(config, configWithErrors) {
|
|
151
125
|
try {
|
|
152
126
|
if (!config) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
127
|
+
// Check if we have detailed error information
|
|
128
|
+
if (configWithErrors?.errors && configWithErrors.filePath) {
|
|
129
|
+
const fileName = configWithErrors.filePath.split('/').pop() || 'vibe-validate.config.yaml';
|
|
130
|
+
const errorList = configWithErrors.errors.slice(0, 5); // Show first 5 errors
|
|
131
|
+
const errorMessages = errorList.map(err => ` • ${err}`).join('\n');
|
|
132
|
+
return {
|
|
133
|
+
name: 'Configuration valid',
|
|
134
|
+
passed: false,
|
|
135
|
+
message: `Found ${fileName} but it contains validation errors:\n${errorMessages}`,
|
|
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 '),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// Fallback: try to find config file path
|
|
145
|
+
const configPath = findConfigPath();
|
|
146
|
+
if (configPath) {
|
|
147
|
+
const fileName = configPath.split('/').pop() || 'vibe-validate.config.yaml';
|
|
148
|
+
return {
|
|
149
|
+
name: 'Configuration valid',
|
|
150
|
+
passed: false,
|
|
151
|
+
message: `Found ${fileName} but it contains validation errors`,
|
|
152
|
+
suggestion: [
|
|
153
|
+
`Fix syntax/validation errors in ${fileName}`,
|
|
154
|
+
'See configuration docs: https://github.com/jdutton/vibe-validate/blob/main/docs/configuration-reference.md',
|
|
155
|
+
'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'
|
|
157
|
+
].join('\n '),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// No config file found
|
|
162
|
+
const templateList = formatTemplateList();
|
|
163
|
+
return {
|
|
164
|
+
name: 'Configuration valid',
|
|
165
|
+
passed: false,
|
|
166
|
+
message: 'No configuration file found',
|
|
167
|
+
suggestion: [
|
|
168
|
+
'Copy a config template from GitHub:',
|
|
169
|
+
'https://github.com/jdutton/vibe-validate/tree/main/config-templates',
|
|
170
|
+
'',
|
|
171
|
+
'Available templates:',
|
|
172
|
+
...templateList.map(line => line),
|
|
173
|
+
'',
|
|
174
|
+
'Quick start:',
|
|
175
|
+
'curl -o vibe-validate.config.yaml \\',
|
|
176
|
+
' https://raw.githubusercontent.com/jdutton/vibe-validate/main/config-templates/typescript-nodejs.yaml',
|
|
177
|
+
'',
|
|
178
|
+
'JSON Schema for IDE validation:',
|
|
179
|
+
'https://raw.githubusercontent.com/jdutton/vibe-validate/main/packages/config/vibe-validate.schema.json',
|
|
180
|
+
].join('\n '),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
159
183
|
}
|
|
160
184
|
return {
|
|
161
185
|
name: 'Configuration valid',
|
|
@@ -168,7 +192,7 @@ async function checkConfigValid(config) {
|
|
|
168
192
|
name: 'Configuration valid',
|
|
169
193
|
passed: false,
|
|
170
194
|
message: `Invalid configuration: ${_error instanceof Error ? _error.message : String(_error)}`,
|
|
171
|
-
suggestion: '
|
|
195
|
+
suggestion: 'Check syntax in vibe-validate.config.yaml',
|
|
172
196
|
};
|
|
173
197
|
}
|
|
174
198
|
}
|
|
@@ -235,7 +259,7 @@ async function checkWorkflowSync(config) {
|
|
|
235
259
|
message: 'Skipped (no config)',
|
|
236
260
|
};
|
|
237
261
|
}
|
|
238
|
-
// Use CI config from vibe-validate
|
|
262
|
+
// Use CI config from vibe-validate config
|
|
239
263
|
const generateOptions = ciConfigToWorkflowOptions(config);
|
|
240
264
|
const { inSync, diff } = checkSync(config, generateOptions);
|
|
241
265
|
if (inSync) {
|
|
@@ -336,6 +360,19 @@ async function checkVersion() {
|
|
|
336
360
|
encoding: 'utf8',
|
|
337
361
|
stdio: 'pipe',
|
|
338
362
|
}).trim();
|
|
363
|
+
// Compare versions (simple semver comparison)
|
|
364
|
+
const current = currentVersion.split('.').map(Number);
|
|
365
|
+
const latest = latestVersion.split('.').map(Number);
|
|
366
|
+
let isOutdated = false;
|
|
367
|
+
for (let i = 0; i < 3; i++) {
|
|
368
|
+
if (current[i] < latest[i]) {
|
|
369
|
+
isOutdated = true;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
else if (current[i] > latest[i]) {
|
|
373
|
+
break; // Current is newer
|
|
374
|
+
}
|
|
375
|
+
}
|
|
339
376
|
if (currentVersion === latestVersion) {
|
|
340
377
|
return {
|
|
341
378
|
name: 'vibe-validate version',
|
|
@@ -343,7 +380,8 @@ async function checkVersion() {
|
|
|
343
380
|
message: `Current version ${currentVersion} is up to date`,
|
|
344
381
|
};
|
|
345
382
|
}
|
|
346
|
-
else {
|
|
383
|
+
else if (isOutdated) {
|
|
384
|
+
// Only show suggestion if current version is behind npm
|
|
347
385
|
return {
|
|
348
386
|
name: 'vibe-validate version',
|
|
349
387
|
passed: true, // Warning only, not a failure
|
|
@@ -351,6 +389,14 @@ async function checkVersion() {
|
|
|
351
389
|
suggestion: `Upgrade: npm install -D vibe-validate@latest (or pnpm add -D vibe-validate@latest)\n 💡 After upgrade: Run 'vibe-validate doctor' to verify setup`,
|
|
352
390
|
};
|
|
353
391
|
}
|
|
392
|
+
else {
|
|
393
|
+
// Current version is ahead of npm (pre-release or unpublished)
|
|
394
|
+
return {
|
|
395
|
+
name: 'vibe-validate version',
|
|
396
|
+
passed: true,
|
|
397
|
+
message: `Current: ${currentVersion} (ahead of npm: ${latestVersion})`,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
354
400
|
}
|
|
355
401
|
catch (_npmError) {
|
|
356
402
|
// npm registry unavailable - not a critical error
|
|
@@ -370,36 +416,35 @@ async function checkVersion() {
|
|
|
370
416
|
}
|
|
371
417
|
}
|
|
372
418
|
/**
|
|
373
|
-
* Check if
|
|
419
|
+
* Check if deprecated state file is in .gitignore
|
|
374
420
|
*/
|
|
375
421
|
function checkGitignoreStateFile() {
|
|
376
422
|
const gitignorePath = '.gitignore';
|
|
377
|
-
const stateFileName =
|
|
423
|
+
const stateFileName = DEPRECATED_STATE_FILE;
|
|
378
424
|
// Check if .gitignore exists
|
|
379
425
|
if (!existsSync(gitignorePath)) {
|
|
380
426
|
return {
|
|
381
427
|
name: 'Gitignore state file',
|
|
382
|
-
passed:
|
|
383
|
-
message: '.gitignore file not found',
|
|
384
|
-
suggestion: 'Manual: echo ".vibe-validate-state.yaml" >> .gitignore\n 💡 Or run: vibe-validate init --fix-gitignore',
|
|
428
|
+
passed: true,
|
|
429
|
+
message: '.gitignore file not found (state file deprecated - using git notes)',
|
|
385
430
|
};
|
|
386
431
|
}
|
|
387
432
|
try {
|
|
388
433
|
const gitignoreContent = readFileSync(gitignorePath, 'utf8');
|
|
389
|
-
// Check if state file is
|
|
434
|
+
// Check if deprecated state file is in .gitignore
|
|
390
435
|
if (gitignoreContent.includes(stateFileName)) {
|
|
391
436
|
return {
|
|
392
|
-
name: 'Gitignore state file',
|
|
393
|
-
passed:
|
|
394
|
-
message:
|
|
437
|
+
name: 'Gitignore state file (deprecated)',
|
|
438
|
+
passed: false,
|
|
439
|
+
message: `${DEPRECATED_STATE_FILE} in .gitignore (deprecated - can be removed)`,
|
|
440
|
+
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`,
|
|
395
441
|
};
|
|
396
442
|
}
|
|
397
443
|
else {
|
|
398
444
|
return {
|
|
399
445
|
name: 'Gitignore state file',
|
|
400
|
-
passed:
|
|
401
|
-
message: '
|
|
402
|
-
suggestion: 'Manual: echo ".vibe-validate-state.yaml" >> .gitignore\n 💡 Or run: vibe-validate init --fix-gitignore',
|
|
446
|
+
passed: true,
|
|
447
|
+
message: 'No deprecated state file entries in .gitignore',
|
|
403
448
|
};
|
|
404
449
|
}
|
|
405
450
|
}
|
|
@@ -413,24 +458,54 @@ function checkGitignoreStateFile() {
|
|
|
413
458
|
}
|
|
414
459
|
}
|
|
415
460
|
/**
|
|
416
|
-
* Check if validation state file exists
|
|
461
|
+
* Check if deprecated validation state file exists
|
|
417
462
|
*/
|
|
418
463
|
function checkValidationState() {
|
|
419
|
-
const statePath =
|
|
420
|
-
//
|
|
421
|
-
// (fresh checkouts, CI environments, etc. won't have it until first validation run)
|
|
464
|
+
const statePath = DEPRECATED_STATE_FILE;
|
|
465
|
+
// Check if deprecated state file exists
|
|
422
466
|
if (existsSync(statePath)) {
|
|
423
467
|
return {
|
|
424
|
-
name: 'Validation state',
|
|
425
|
-
passed:
|
|
426
|
-
message:
|
|
468
|
+
name: 'Validation state (deprecated)',
|
|
469
|
+
passed: false,
|
|
470
|
+
message: `${DEPRECATED_STATE_FILE} found (deprecated file - safe to remove)`,
|
|
471
|
+
suggestion: `Remove deprecated state file: rm ${DEPRECATED_STATE_FILE}\n ℹ️ Validation now uses git notes for improved caching`,
|
|
427
472
|
};
|
|
428
473
|
}
|
|
429
474
|
else {
|
|
430
475
|
return {
|
|
431
476
|
name: 'Validation state',
|
|
432
|
-
passed: true,
|
|
433
|
-
message: '
|
|
477
|
+
passed: true,
|
|
478
|
+
message: 'No deprecated state file found (using git notes)',
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Check validation history health
|
|
484
|
+
*/
|
|
485
|
+
async function checkHistoryHealth() {
|
|
486
|
+
try {
|
|
487
|
+
const health = await checkValidationHistoryHealth();
|
|
488
|
+
if (!health.shouldWarn) {
|
|
489
|
+
return {
|
|
490
|
+
name: 'Validation history',
|
|
491
|
+
passed: true,
|
|
492
|
+
message: `${health.totalNotes} tree hashes tracked, history is healthy`,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
// Has warnings
|
|
496
|
+
return {
|
|
497
|
+
name: 'Validation history',
|
|
498
|
+
passed: false,
|
|
499
|
+
message: `${health.totalNotes} tree hashes tracked (${health.oldNotesCount} older than 90 days)`,
|
|
500
|
+
suggestion: 'Prune old history: vibe-validate history prune --older-than "90 days"',
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
catch (_error) {
|
|
504
|
+
// Git notes not available or other error - not critical
|
|
505
|
+
return {
|
|
506
|
+
name: 'Validation history',
|
|
507
|
+
passed: true,
|
|
508
|
+
message: 'History unavailable (not in git repo or no validation runs yet)',
|
|
434
509
|
};
|
|
435
510
|
}
|
|
436
511
|
}
|
|
@@ -589,6 +664,91 @@ async function checkRemoteMainBranch(config) {
|
|
|
589
664
|
};
|
|
590
665
|
}
|
|
591
666
|
}
|
|
667
|
+
/**
|
|
668
|
+
* Check if secret scanning is configured and tool is available
|
|
669
|
+
*/
|
|
670
|
+
async function checkSecretScanning(config) {
|
|
671
|
+
try {
|
|
672
|
+
if (!config) {
|
|
673
|
+
return {
|
|
674
|
+
name: 'Pre-commit secret scanning',
|
|
675
|
+
passed: true,
|
|
676
|
+
message: 'Skipped (no config)',
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
const secretScanning = config.hooks?.preCommit?.secretScanning;
|
|
680
|
+
// If not configured at all, recommend enabling it
|
|
681
|
+
if (!secretScanning) {
|
|
682
|
+
return {
|
|
683
|
+
name: 'Pre-commit secret scanning',
|
|
684
|
+
passed: true,
|
|
685
|
+
message: 'Secret scanning not configured',
|
|
686
|
+
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
|
+
};
|
|
688
|
+
}
|
|
689
|
+
// If explicitly disabled, acknowledge user choice
|
|
690
|
+
if (secretScanning.enabled === false) {
|
|
691
|
+
return {
|
|
692
|
+
name: 'Pre-commit secret scanning',
|
|
693
|
+
passed: true,
|
|
694
|
+
message: 'Secret scanning disabled in config (user preference)',
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
// If enabled, check if tool is available
|
|
698
|
+
if (secretScanning.scanCommand) {
|
|
699
|
+
// Extract tool name (first word of command)
|
|
700
|
+
const toolName = secretScanning.scanCommand.split(' ')[0];
|
|
701
|
+
try {
|
|
702
|
+
// Try to get tool version
|
|
703
|
+
let version;
|
|
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
|
+
}
|
|
735
|
+
}
|
|
736
|
+
// Enabled but no scanCommand (should be caught by schema validation)
|
|
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
|
+
};
|
|
743
|
+
}
|
|
744
|
+
catch (_error) {
|
|
745
|
+
return {
|
|
746
|
+
name: 'Pre-commit secret scanning',
|
|
747
|
+
passed: true,
|
|
748
|
+
message: 'Skipped (config or execution error)',
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
}
|
|
592
752
|
/**
|
|
593
753
|
* Run all doctor checks
|
|
594
754
|
*/
|
|
@@ -597,12 +757,15 @@ export async function runDoctor(options = {}) {
|
|
|
597
757
|
const allChecks = [];
|
|
598
758
|
// Load config once to avoid duplicate warnings
|
|
599
759
|
let config;
|
|
760
|
+
let configWithErrors;
|
|
600
761
|
try {
|
|
601
762
|
config = await loadConfig();
|
|
763
|
+
configWithErrors = await loadConfigWithErrors();
|
|
602
764
|
}
|
|
603
765
|
catch (_error) {
|
|
604
766
|
// Config load error will be caught by checkConfigValid
|
|
605
767
|
config = null;
|
|
768
|
+
configWithErrors = { config: null, errors: null, filePath: null };
|
|
606
769
|
}
|
|
607
770
|
// Run all checks
|
|
608
771
|
allChecks.push(await checkVersion());
|
|
@@ -610,16 +773,17 @@ export async function runDoctor(options = {}) {
|
|
|
610
773
|
allChecks.push(checkGitInstalled());
|
|
611
774
|
allChecks.push(checkGitRepository());
|
|
612
775
|
allChecks.push(checkConfigFile());
|
|
613
|
-
allChecks.push(
|
|
614
|
-
allChecks.push(await checkConfigValid(config));
|
|
776
|
+
allChecks.push(await checkConfigValid(config, configWithErrors));
|
|
615
777
|
allChecks.push(await checkPackageManager(config));
|
|
616
778
|
allChecks.push(await checkMainBranch(config));
|
|
617
779
|
allChecks.push(await checkRemoteOrigin(config));
|
|
618
780
|
allChecks.push(await checkRemoteMainBranch(config));
|
|
619
781
|
allChecks.push(await checkWorkflowSync(config));
|
|
620
782
|
allChecks.push(await checkPreCommitHook(config));
|
|
783
|
+
allChecks.push(await checkSecretScanning(config));
|
|
621
784
|
allChecks.push(checkGitignoreStateFile());
|
|
622
785
|
allChecks.push(checkValidationState());
|
|
786
|
+
allChecks.push(await checkHistoryHealth());
|
|
623
787
|
// Collect suggestions from failed checks
|
|
624
788
|
const suggestions = allChecks
|
|
625
789
|
.filter(c => !c.passed && c.suggestion)
|
|
@@ -627,8 +791,9 @@ export async function runDoctor(options = {}) {
|
|
|
627
791
|
const allPassed = allChecks.every(c => c.passed);
|
|
628
792
|
const totalChecks = allChecks.length;
|
|
629
793
|
const passedChecks = allChecks.filter(c => c.passed).length;
|
|
630
|
-
// In non-verbose mode,
|
|
631
|
-
|
|
794
|
+
// In non-verbose mode, show failing checks OR checks with recommendations
|
|
795
|
+
// In verbose mode, always show all checks
|
|
796
|
+
const checks = verbose ? allChecks : allChecks.filter(c => !c.passed || c.suggestion);
|
|
632
797
|
return {
|
|
633
798
|
allPassed,
|
|
634
799
|
checks,
|
|
@@ -644,15 +809,30 @@ export async function runDoctor(options = {}) {
|
|
|
644
809
|
export function doctorCommand(program) {
|
|
645
810
|
program
|
|
646
811
|
.command('doctor')
|
|
647
|
-
.description('Diagnose vibe-validate setup and environment')
|
|
648
|
-
.option('--
|
|
649
|
-
.
|
|
650
|
-
|
|
812
|
+
.description('Diagnose vibe-validate setup and environment (run after upgrading)')
|
|
813
|
+
.option('--yaml', 'Output YAML only (no human-friendly display)')
|
|
814
|
+
.action(async (options, command) => {
|
|
815
|
+
// Get verbose from global options (inherited from program)
|
|
816
|
+
const verbose = command.optsWithGlobals().verbose;
|
|
651
817
|
try {
|
|
652
|
-
const result = await runDoctor({ verbose
|
|
653
|
-
if (options.
|
|
654
|
-
//
|
|
655
|
-
|
|
818
|
+
const result = await runDoctor({ verbose });
|
|
819
|
+
if (options.yaml) {
|
|
820
|
+
// YAML mode: Output structured result to stdout
|
|
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
|
+
});
|
|
656
836
|
}
|
|
657
837
|
else {
|
|
658
838
|
// Human-friendly output
|
|
@@ -668,7 +848,7 @@ export function doctorCommand(program) {
|
|
|
668
848
|
const icon = check.passed ? '✅' : '❌';
|
|
669
849
|
console.log(`${icon} ${check.name}`);
|
|
670
850
|
console.log(` ${check.message}`);
|
|
671
|
-
if (check.suggestion
|
|
851
|
+
if (check.suggestion) {
|
|
672
852
|
console.log(` 💡 ${check.suggestion}`);
|
|
673
853
|
}
|
|
674
854
|
console.log('');
|
|
@@ -697,4 +877,127 @@ export function doctorCommand(program) {
|
|
|
697
877
|
}
|
|
698
878
|
});
|
|
699
879
|
}
|
|
880
|
+
/**
|
|
881
|
+
* Show verbose help with detailed documentation
|
|
882
|
+
*/
|
|
883
|
+
export function showDoctorVerboseHelp() {
|
|
884
|
+
console.log(`# doctor Command Reference
|
|
885
|
+
|
|
886
|
+
> Diagnose vibe-validate setup and environment (run after upgrading)
|
|
887
|
+
|
|
888
|
+
## Overview
|
|
889
|
+
|
|
890
|
+
The \`doctor\` command performs comprehensive diagnostics of your vibe-validate setup and environment. It checks Node.js version, git repository health, configuration validity, package manager availability, and CI/CD integration status.
|
|
891
|
+
|
|
892
|
+
## How It Works
|
|
893
|
+
|
|
894
|
+
1. Checks Node.js version (20+)
|
|
895
|
+
2. Verifies git repository exists
|
|
896
|
+
3. Checks package manager availability
|
|
897
|
+
4. Validates configuration file
|
|
898
|
+
5. Checks pre-commit hook setup
|
|
899
|
+
6. Verifies GitHub Actions workflow sync
|
|
900
|
+
|
|
901
|
+
## Options
|
|
902
|
+
|
|
903
|
+
- \`--yaml\` - Output YAML only (no human-friendly display)
|
|
904
|
+
|
|
905
|
+
## Exit Codes
|
|
906
|
+
|
|
907
|
+
- \`0\` - All critical checks passed
|
|
908
|
+
- \`1\` - One or more critical checks failed
|
|
909
|
+
|
|
910
|
+
## Examples
|
|
911
|
+
|
|
912
|
+
\`\`\`bash
|
|
913
|
+
# Run diagnostics
|
|
914
|
+
vibe-validate doctor
|
|
915
|
+
|
|
916
|
+
# YAML output only
|
|
917
|
+
vibe-validate doctor --yaml
|
|
918
|
+
\`\`\`
|
|
919
|
+
|
|
920
|
+
## Common Workflows
|
|
921
|
+
|
|
922
|
+
### After installing vibe-validate
|
|
923
|
+
|
|
924
|
+
\`\`\`bash
|
|
925
|
+
# Initialize configuration
|
|
926
|
+
vibe-validate init
|
|
927
|
+
|
|
928
|
+
# Verify setup
|
|
929
|
+
vibe-validate doctor
|
|
930
|
+
\`\`\`
|
|
931
|
+
|
|
932
|
+
### After upgrading vibe-validate
|
|
933
|
+
|
|
934
|
+
\`\`\`bash
|
|
935
|
+
# Upgrade package
|
|
936
|
+
npm install -D vibe-validate@latest
|
|
937
|
+
|
|
938
|
+
# CRITICAL: Always run doctor after upgrade
|
|
939
|
+
vibe-validate doctor
|
|
940
|
+
|
|
941
|
+
# Fix any issues reported
|
|
942
|
+
\`\`\`
|
|
943
|
+
|
|
944
|
+
### Troubleshooting validation issues
|
|
945
|
+
|
|
946
|
+
\`\`\`bash
|
|
947
|
+
# Diagnose environment
|
|
948
|
+
vibe-validate doctor
|
|
949
|
+
|
|
950
|
+
# Fix reported issues
|
|
951
|
+
|
|
952
|
+
# Retry validation
|
|
953
|
+
vibe-validate validate
|
|
954
|
+
\`\`\`
|
|
955
|
+
|
|
956
|
+
## Checks Performed
|
|
957
|
+
|
|
958
|
+
### Critical Checks (must pass)
|
|
959
|
+
|
|
960
|
+
- **Node.js version**: >=20.0.0 required
|
|
961
|
+
- **Git installed**: git command available
|
|
962
|
+
- **Git repository**: Current directory is a git repo
|
|
963
|
+
- **Configuration file**: vibe-validate.config.yaml exists
|
|
964
|
+
- **Configuration valid**: No syntax or schema errors
|
|
965
|
+
|
|
966
|
+
### Advisory Checks (warnings only)
|
|
967
|
+
|
|
968
|
+
- **Package manager**: npm or pnpm available
|
|
969
|
+
- **Pre-commit hook**: Husky pre-commit hook configured
|
|
970
|
+
- **GitHub Actions workflow**: Workflow in sync with config
|
|
971
|
+
- **Validation history**: Git notes health status
|
|
972
|
+
|
|
973
|
+
## Error Recovery
|
|
974
|
+
|
|
975
|
+
**If Node.js version check fails:**
|
|
976
|
+
\`\`\`bash
|
|
977
|
+
# Upgrade Node.js to 20+
|
|
978
|
+
# Via nvm:
|
|
979
|
+
nvm install 20
|
|
980
|
+
nvm use 20
|
|
981
|
+
|
|
982
|
+
# Or download from: https://nodejs.org/
|
|
983
|
+
\`\`\`
|
|
984
|
+
|
|
985
|
+
**If configuration check fails:**
|
|
986
|
+
\`\`\`bash
|
|
987
|
+
# Reinitialize with template
|
|
988
|
+
vibe-validate init --force
|
|
989
|
+
|
|
990
|
+
# Or manually fix YAML syntax errors
|
|
991
|
+
\`\`\`
|
|
992
|
+
|
|
993
|
+
**If workflow is out of sync:**
|
|
994
|
+
\`\`\`bash
|
|
995
|
+
# Regenerate workflow
|
|
996
|
+
vibe-validate generate-workflow
|
|
997
|
+
|
|
998
|
+
# Or reinitialize
|
|
999
|
+
vibe-validate init --setup-workflow
|
|
1000
|
+
\`\`\`
|
|
1001
|
+
`);
|
|
1002
|
+
}
|
|
700
1003
|
//# sourceMappingURL=doctor.js.map
|