@hatem427/code-guard-ci 3.5.4 → 3.5.6
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/config/angular.config.ts +24 -24
- package/config/guidelines.config.ts +152 -68
- package/config/nextjs.config.ts +46 -1
- package/dist/config/angular.config.js +24 -22
- package/dist/config/angular.config.js.map +1 -1
- package/dist/config/guidelines.config.d.ts.map +1 -1
- package/dist/config/guidelines.config.js +139 -77
- package/dist/config/guidelines.config.js.map +1 -1
- package/dist/config/nextjs.config.d.ts.map +1 -1
- package/dist/config/nextjs.config.js +39 -1
- package/dist/config/nextjs.config.js.map +1 -1
- package/dist/scripts/cli.js +450 -115
- package/dist/scripts/cli.js.map +1 -1
- package/package.json +1 -1
- package/scripts/cli.ts +487 -125
package/scripts/cli.ts
CHANGED
|
@@ -73,9 +73,10 @@ function showHelp(): void {
|
|
|
73
73
|
code-guard <command> [options]
|
|
74
74
|
|
|
75
75
|
${c.bold('Commands:')}
|
|
76
|
-
${c.green('init')}
|
|
77
|
-
${c.green('check')}
|
|
78
|
-
${c.green('
|
|
76
|
+
${c.green('init')} Full automatic setup (zero prompts)
|
|
77
|
+
${c.green('check')} Run pre-commit checks manually
|
|
78
|
+
${c.green('check-vuln')} Run security scans (npm audit, RetireJS)
|
|
79
|
+
${c.green('report')} Generate a rich report with fix examples (opens in editor)
|
|
79
80
|
${c.green('doc')} Generate feature documentation
|
|
80
81
|
${c.green('checklist')} Generate PR checklist
|
|
81
82
|
${c.green('validate')} Validate structure & naming conventions
|
|
@@ -93,7 +94,6 @@ ${c.bold('Init Options:')}
|
|
|
93
94
|
|
|
94
95
|
${c.bold('Uninstall Options:')}
|
|
95
96
|
--yes, -y Skip confirmation prompt
|
|
96
|
-
--no-backup Don't create backup of AI configs
|
|
97
97
|
|
|
98
98
|
${c.bold('Examples:')}
|
|
99
99
|
${c.dim('# Full automatic setup')}
|
|
@@ -111,11 +111,11 @@ ${c.bold('Examples:')}
|
|
|
111
111
|
${c.dim('# Generate docs')}
|
|
112
112
|
code-guard doc --name="user-card" --type=ui
|
|
113
113
|
|
|
114
|
-
${c.dim('# Clean uninstall
|
|
114
|
+
${c.dim('# Clean uninstall')}
|
|
115
115
|
code-guard uninstall
|
|
116
116
|
|
|
117
117
|
${c.dim('# Force uninstall without prompts')}
|
|
118
|
-
code-guard uninstall --yes
|
|
118
|
+
code-guard uninstall --yes
|
|
119
119
|
`);
|
|
120
120
|
}
|
|
121
121
|
|
|
@@ -143,6 +143,16 @@ function initProject(): void {
|
|
|
143
143
|
const skipAI = hasFlag('skip-ai');
|
|
144
144
|
const skipHooks = hasFlag('skip-hooks');
|
|
145
145
|
|
|
146
|
+
// ── Initialize manifest for tracking ────────────────────────────────────
|
|
147
|
+
const {
|
|
148
|
+
createManifest, saveManifest, loadManifest,
|
|
149
|
+
recordCreatedFile, recordModifiedFile, recordBackup,
|
|
150
|
+
recordAddedScript, recordModifiedScript,
|
|
151
|
+
} = requireUtil('manifest');
|
|
152
|
+
|
|
153
|
+
// Load existing manifest or create new one
|
|
154
|
+
const manifest = loadManifest(cwd) || createManifest();
|
|
155
|
+
|
|
146
156
|
// ── Step 1: Detect Framework ────────────────────────────────────────────
|
|
147
157
|
console.log(c.bold('📡 Step 1: Detecting project...'));
|
|
148
158
|
|
|
@@ -186,9 +196,25 @@ function initProject(): void {
|
|
|
186
196
|
// ── Step 3: Generate ESLint Config ───────────────────────────────────────
|
|
187
197
|
console.log(c.bold('⚡ Step 3: Configuring ESLint...'));
|
|
188
198
|
try {
|
|
199
|
+
const eslintFile = 'eslint.config.mjs';
|
|
200
|
+
const eslintPath = path.join(cwd, eslintFile);
|
|
201
|
+
const eslintExisted = fs.existsSync(eslintPath);
|
|
202
|
+
|
|
189
203
|
const { generateEslintConfig } = requireGenerator('eslint-generator');
|
|
190
204
|
generateEslintConfig(project);
|
|
191
|
-
|
|
205
|
+
|
|
206
|
+
// Track: eslint-generator already creates .backup files for existing configs
|
|
207
|
+
if (eslintExisted) {
|
|
208
|
+
recordModifiedFile(manifest, eslintFile, 'eslint');
|
|
209
|
+
// The generator already backed up the old file — record it
|
|
210
|
+
if (fs.existsSync(eslintPath + '.backup')) {
|
|
211
|
+
recordBackup(manifest, eslintFile, eslintFile + '.backup');
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
recordCreatedFile(manifest, eslintFile);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(` ${c.green('✓')} Created eslint.config.mjs`);
|
|
192
218
|
console.log(` ${c.green('✓')} Created .eslintignore\n`);
|
|
193
219
|
} catch (error: any) {
|
|
194
220
|
console.warn(c.yellow(` ⚠️ ESLint config generation failed: ${error.message}\n`));
|
|
@@ -197,8 +223,33 @@ function initProject(): void {
|
|
|
197
223
|
// ── Step 4: Generate Prettier Config ──────────────────────────────────────
|
|
198
224
|
console.log(c.bold('🎨 Step 4: Configuring Prettier...'));
|
|
199
225
|
try {
|
|
226
|
+
const prettierFile = '.prettierrc.json';
|
|
227
|
+
const prettierPath = path.join(cwd, prettierFile);
|
|
228
|
+
const prettierExisted = fs.existsSync(prettierPath);
|
|
229
|
+
const ignoreFile = '.prettierignore';
|
|
230
|
+
const ignorePath = path.join(cwd, ignoreFile);
|
|
231
|
+
const ignoreExisted = fs.existsSync(ignorePath);
|
|
232
|
+
|
|
200
233
|
const { generatePrettierConfig } = requireGenerator('prettier-generator');
|
|
201
234
|
generatePrettierConfig(project);
|
|
235
|
+
|
|
236
|
+
// Track: prettier-generator already creates .backup files for existing configs
|
|
237
|
+
if (prettierExisted) {
|
|
238
|
+
recordModifiedFile(manifest, prettierFile, 'prettier');
|
|
239
|
+
// Check for any backed-up prettier files
|
|
240
|
+
const prettierFiles = ['.prettierrc', '.prettierrc.js', '.prettierrc.cjs', '.prettierrc.json', '.prettierrc.yml', '.prettierrc.yaml', '.prettierrc.toml', 'prettier.config.js'];
|
|
241
|
+
for (const pf of prettierFiles) {
|
|
242
|
+
if (fs.existsSync(path.join(cwd, pf + '.backup'))) {
|
|
243
|
+
recordBackup(manifest, pf, pf + '.backup');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} else {
|
|
247
|
+
recordCreatedFile(manifest, prettierFile);
|
|
248
|
+
}
|
|
249
|
+
if (!ignoreExisted && fs.existsSync(ignorePath)) {
|
|
250
|
+
recordCreatedFile(manifest, ignoreFile);
|
|
251
|
+
}
|
|
252
|
+
|
|
202
253
|
console.log(` ${c.green('✓')} Created .prettierrc.json`);
|
|
203
254
|
console.log(` ${c.green('✓')} Created .prettierignore\n`);
|
|
204
255
|
} catch (error: any) {
|
|
@@ -209,8 +260,19 @@ function initProject(): void {
|
|
|
209
260
|
if (project.usesTypeScript) {
|
|
210
261
|
console.log(c.bold('📘 Step 5: Configuring TypeScript...'));
|
|
211
262
|
try {
|
|
263
|
+
const tsFile = project.existingTooling.hasTypescript ? 'tsconfig.strict.json' : 'tsconfig.json';
|
|
264
|
+
const tsPath = path.join(cwd, tsFile);
|
|
265
|
+
const tsExisted = fs.existsSync(tsPath);
|
|
266
|
+
|
|
212
267
|
const { generateTypescriptConfig } = requireGenerator('typescript-generator');
|
|
213
268
|
generateTypescriptConfig(project);
|
|
269
|
+
|
|
270
|
+
if (tsExisted) {
|
|
271
|
+
recordModifiedFile(manifest, tsFile, 'typescript');
|
|
272
|
+
} else {
|
|
273
|
+
recordCreatedFile(manifest, tsFile);
|
|
274
|
+
}
|
|
275
|
+
|
|
214
276
|
if (project.existingTooling.hasTypescript) {
|
|
215
277
|
console.log(` ${c.green('✓')} Created tsconfig.strict.json (extends existing tsconfig)`);
|
|
216
278
|
} else {
|
|
@@ -227,18 +289,34 @@ function initProject(): void {
|
|
|
227
289
|
// ── Step 6: Generate lint-staged Config ───────────────────────────────────
|
|
228
290
|
console.log(c.bold('📋 Step 6: Configuring lint-staged...'));
|
|
229
291
|
try {
|
|
292
|
+
const lsFile = '.lintstagedrc.json';
|
|
293
|
+
const lsPath = path.join(cwd, lsFile);
|
|
294
|
+
const lsExisted = fs.existsSync(lsPath);
|
|
295
|
+
|
|
230
296
|
const { generateLintStagedConfig } = requireGenerator('lint-staged-generator');
|
|
231
297
|
generateLintStagedConfig(project);
|
|
298
|
+
|
|
299
|
+
if (!lsExisted && fs.existsSync(lsPath)) {
|
|
300
|
+
recordCreatedFile(manifest, lsFile);
|
|
301
|
+
}
|
|
232
302
|
console.log(` ${c.green('✓')} Created .lintstagedrc.json\n`);
|
|
233
303
|
} catch (error: any) {
|
|
234
304
|
console.warn(c.yellow(` ⚠️ lint-staged config generation failed: ${error.message}\n`));
|
|
235
305
|
}
|
|
236
306
|
|
|
237
307
|
// ── Step 7: Generate .editorconfig ────────────────────────────────────────
|
|
238
|
-
console.log(c.bold('📐 Step
|
|
308
|
+
console.log(c.bold('📐 Step 7: Creating .editorconfig...'));
|
|
239
309
|
try {
|
|
310
|
+
const ecFile = '.editorconfig';
|
|
311
|
+
const ecPath = path.join(cwd, ecFile);
|
|
312
|
+
const ecExisted = fs.existsSync(ecPath);
|
|
313
|
+
|
|
240
314
|
const { generateEditorConfig } = requireGenerator('editorconfig-generator');
|
|
241
315
|
generateEditorConfig(project);
|
|
316
|
+
|
|
317
|
+
if (!ecExisted && fs.existsSync(ecPath)) {
|
|
318
|
+
recordCreatedFile(manifest, ecFile);
|
|
319
|
+
}
|
|
242
320
|
console.log(` ${c.green('✓')} Created .editorconfig\n`);
|
|
243
321
|
} catch (error: any) {
|
|
244
322
|
console.warn(c.yellow(` ⚠️ EditorConfig generation failed: ${error.message}\n`));
|
|
@@ -247,11 +325,24 @@ function initProject(): void {
|
|
|
247
325
|
// ── Step 8: Generate VS Code Settings ─────────────────────────────────────
|
|
248
326
|
console.log(c.bold('💻 Step 8: Creating VS Code workspace settings...'));
|
|
249
327
|
try {
|
|
328
|
+
const vscodeFiles = ['.vscode/settings.json', '.vscode/extensions.json', '.vscode/tasks.json', '.vscode/launch.json'];
|
|
329
|
+
const vscodeExisted: Record<string, boolean> = {};
|
|
330
|
+
for (const vf of vscodeFiles) {
|
|
331
|
+
vscodeExisted[vf] = fs.existsSync(path.join(cwd, vf));
|
|
332
|
+
}
|
|
333
|
+
|
|
250
334
|
const { generateVSCodeSettings, generateVSCodeExtensions, generateVSCodeTasks, generateVSCodeLaunch } = requireGenerator('vscode-generator');
|
|
251
335
|
generateVSCodeSettings(project);
|
|
252
336
|
generateVSCodeExtensions(project);
|
|
253
337
|
generateVSCodeTasks(project);
|
|
254
338
|
generateVSCodeLaunch(project);
|
|
339
|
+
|
|
340
|
+
for (const vf of vscodeFiles) {
|
|
341
|
+
if (!vscodeExisted[vf] && fs.existsSync(path.join(cwd, vf))) {
|
|
342
|
+
recordCreatedFile(manifest, vf);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
255
346
|
console.log(` ${c.green('✓')} Created .vscode/settings.json`);
|
|
256
347
|
console.log(` ${c.green('✓')} Created .vscode/extensions.json`);
|
|
257
348
|
console.log(` ${c.green('✓')} Created .vscode/tasks.json`);
|
|
@@ -263,7 +354,14 @@ function initProject(): void {
|
|
|
263
354
|
// ── Step 9: Setup Git Hooks ───────────────────────────────────────────────
|
|
264
355
|
if (!skipHooks) {
|
|
265
356
|
console.log(c.bold('🐶 Step 9: Setting up Git hooks...'));
|
|
357
|
+
const hookFile = '.husky/pre-commit';
|
|
358
|
+
const hookExisted = fs.existsSync(path.join(cwd, hookFile));
|
|
266
359
|
setupGitHooks(cwd);
|
|
360
|
+
if (hookExisted) {
|
|
361
|
+
recordModifiedFile(manifest, hookFile, 'husky');
|
|
362
|
+
} else if (fs.existsSync(path.join(cwd, hookFile))) {
|
|
363
|
+
recordCreatedFile(manifest, hookFile);
|
|
364
|
+
}
|
|
267
365
|
console.log('');
|
|
268
366
|
} else {
|
|
269
367
|
console.log(c.dim('🐶 Step 9: Skipping git hooks (--skip-hooks)\n'));
|
|
@@ -275,17 +373,32 @@ function initProject(): void {
|
|
|
275
373
|
try {
|
|
276
374
|
const { loadCompanyRules } = requireUtil('custom-rules-loader');
|
|
277
375
|
const companyRules = loadCompanyRules(cwd);
|
|
278
|
-
const { generateAIConfigs } = requireGenerator('ai-config-generator');
|
|
279
|
-
generateAIConfigs(project, companyRules.aiGuidelines);
|
|
280
376
|
|
|
377
|
+
// Snapshot which AI files exist before generation
|
|
281
378
|
const { defaultRegistry } = requireUtil('ai-config-registry');
|
|
282
379
|
const templates = defaultRegistry.getAll();
|
|
380
|
+
const aiExisted: Record<string, boolean> = {};
|
|
381
|
+
for (const t of templates) {
|
|
382
|
+
const targetDir = t.directory ? path.join(cwd, t.directory) : cwd;
|
|
383
|
+
const targetPath = path.join(targetDir, t.fileName);
|
|
384
|
+
const relPath = t.directory ? `${t.directory}/${t.fileName}` : t.fileName;
|
|
385
|
+
aiExisted[relPath] = fs.existsSync(targetPath);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const { generateAIConfigs } = requireGenerator('ai-config-generator');
|
|
389
|
+
generateAIConfigs(project, companyRules.aiGuidelines);
|
|
390
|
+
|
|
283
391
|
for (const t of templates) {
|
|
284
392
|
const targetDir = t.directory ? path.join(cwd, t.directory) : cwd;
|
|
285
393
|
const targetPath = path.join(targetDir, t.fileName);
|
|
394
|
+
const relPath = t.directory ? `${t.directory}/${t.fileName}` : t.fileName;
|
|
286
395
|
if (fs.existsSync(targetPath)) {
|
|
287
|
-
|
|
288
|
-
|
|
396
|
+
if (aiExisted[relPath]) {
|
|
397
|
+
recordModifiedFile(manifest, relPath, t.marker);
|
|
398
|
+
} else {
|
|
399
|
+
recordCreatedFile(manifest, relPath);
|
|
400
|
+
}
|
|
401
|
+
console.log(` ${c.green('✓')} ${t.name}: ${relPath}`);
|
|
289
402
|
}
|
|
290
403
|
}
|
|
291
404
|
console.log('');
|
|
@@ -299,6 +412,12 @@ function initProject(): void {
|
|
|
299
412
|
// ── Step 11: Custom Rules Template ─────────────────────────────────────────
|
|
300
413
|
console.log(c.bold('🏢 Step 11: Creating custom rules templates...'));
|
|
301
414
|
try {
|
|
415
|
+
const cgFiles = ['.code-guardian/custom-rules.json', '.code-guardian/structure-rules.json', '.code-guardian/naming-rules.json'];
|
|
416
|
+
const cgExisted: Record<string, boolean> = {};
|
|
417
|
+
for (const cf of cgFiles) {
|
|
418
|
+
cgExisted[cf] = fs.existsSync(path.join(cwd, cf));
|
|
419
|
+
}
|
|
420
|
+
|
|
302
421
|
const { generateCustomRulesTemplate } = requireUtil('custom-rules-loader');
|
|
303
422
|
generateCustomRulesTemplate(cwd);
|
|
304
423
|
console.log(` ${c.green('✓')} Created .code-guardian/custom-rules.json`);
|
|
@@ -310,20 +429,30 @@ function initProject(): void {
|
|
|
310
429
|
const { generateNamingRulesTemplate } = requireUtil('naming-validator');
|
|
311
430
|
generateNamingRulesTemplate(cwd);
|
|
312
431
|
console.log(` ${c.green('✓')} Created .code-guardian/naming-rules.json\n`);
|
|
432
|
+
|
|
433
|
+
for (const cf of cgFiles) {
|
|
434
|
+
if (!cgExisted[cf] && fs.existsSync(path.join(cwd, cf))) {
|
|
435
|
+
recordCreatedFile(manifest, cf);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
313
438
|
} catch (error: any) {
|
|
314
439
|
console.warn(c.yellow(` ⚠️ Custom rules template generation failed: ${error.message}\n`));
|
|
315
440
|
}
|
|
316
441
|
|
|
317
442
|
// ── Step 12: Update package.json ───────────────────────────────────────────
|
|
318
443
|
console.log(c.bold('📦 Step 12: Updating package.json...'));
|
|
319
|
-
updatePackageJson(cwd, project);
|
|
444
|
+
updatePackageJson(cwd, project, manifest);
|
|
320
445
|
console.log('');
|
|
321
446
|
|
|
322
447
|
// ── Step 13: Copy Code Guardian configs and templates ──────────────────────
|
|
323
448
|
console.log(c.bold('📁 Step 13: Copying Code Guardian rules and templates...'));
|
|
324
|
-
copyCodeGuardianFiles(cwd);
|
|
449
|
+
copyCodeGuardianFiles(cwd, manifest, project.type);
|
|
325
450
|
console.log('');
|
|
326
451
|
|
|
452
|
+
// ── Save manifest ─────────────────────────────────────────────────────────
|
|
453
|
+
saveManifest(cwd, manifest);
|
|
454
|
+
console.log(` ${c.green('✓')} Saved manifest to .code-guardian/manifest.json\n`);
|
|
455
|
+
|
|
327
456
|
// ── Done! ─────────────────────────────────────────────────────────────────
|
|
328
457
|
console.log(c.bold(c.green('═══════════════════════════════════════════════════════════')));
|
|
329
458
|
console.log(c.bold(c.green(' ✅ Code Guardian initialized successfully!')));
|
|
@@ -358,44 +487,53 @@ function initProject(): void {
|
|
|
358
487
|
|
|
359
488
|
function setupGitHooks(cwd: string): void {
|
|
360
489
|
try {
|
|
361
|
-
|
|
490
|
+
const huskyDir = path.join(cwd, '.husky');
|
|
491
|
+
const preCommitPath = path.join(huskyDir, 'pre-commit');
|
|
492
|
+
|
|
493
|
+
// Save existing pre-commit content BEFORE husky init (which overwrites it)
|
|
494
|
+
let existingPreCommit: string | null = null;
|
|
495
|
+
if (fs.existsSync(preCommitPath)) {
|
|
496
|
+
existingPreCommit = fs.readFileSync(preCommitPath, 'utf-8');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Initialize Husky (creates .husky/ dir and default pre-commit)
|
|
362
500
|
try {
|
|
363
501
|
execSync('npx husky init', { stdio: 'pipe', cwd });
|
|
364
502
|
} catch {
|
|
365
503
|
// Fallback: manual setup
|
|
366
|
-
const huskyDir = path.join(cwd, '.husky');
|
|
367
504
|
if (!fs.existsSync(huskyDir)) {
|
|
368
505
|
fs.mkdirSync(huskyDir, { recursive: true });
|
|
369
506
|
}
|
|
370
507
|
}
|
|
371
508
|
|
|
372
|
-
const huskyDir = path.join(cwd, '.husky');
|
|
373
509
|
if (!fs.existsSync(huskyDir)) {
|
|
374
510
|
fs.mkdirSync(huskyDir, { recursive: true });
|
|
375
511
|
}
|
|
376
512
|
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
513
|
+
// Restore the original pre-commit content if husky init overwrote it
|
|
514
|
+
if (existingPreCommit !== null) {
|
|
515
|
+
fs.writeFileSync(preCommitPath, existingPreCommit);
|
|
516
|
+
}
|
|
380
517
|
|
|
518
|
+
// Create or append pre-commit hook (lint-staged + Code Guardian rule checks)
|
|
519
|
+
const preCommitContent = `
|
|
381
520
|
# --- code-guardian-hook-start ---
|
|
382
521
|
echo "🛡️ Code Guardian — Pre-commit checks..."
|
|
383
522
|
|
|
384
|
-
# Skip
|
|
523
|
+
# Skip all checks when BYPASS_RULES is set
|
|
385
524
|
if [ "$BYPASS_RULES" = "true" ] || [ "$BYPASS_RULES" = "1" ]; then
|
|
386
|
-
echo "⚡ BYPASS_RULES detected — skipping
|
|
525
|
+
echo "⚡ BYPASS_RULES detected — skipping checks."
|
|
387
526
|
exit 0
|
|
388
527
|
fi
|
|
389
528
|
|
|
390
529
|
# Run lint-staged (ESLint + Prettier auto-fix)
|
|
391
530
|
npx lint-staged
|
|
392
531
|
|
|
393
|
-
# Run Code Guardian
|
|
532
|
+
# Run Code Guardian rules
|
|
394
533
|
npm run precommit-check
|
|
395
534
|
# --- code-guardian-hook-end ---
|
|
396
535
|
`;
|
|
397
536
|
|
|
398
|
-
const preCommitPath = path.join(huskyDir, 'pre-commit');
|
|
399
537
|
if (fs.existsSync(preCommitPath)) {
|
|
400
538
|
const existing = fs.readFileSync(preCommitPath, 'utf-8');
|
|
401
539
|
if (!existing.includes('code-guardian-hook-start')) {
|
|
@@ -409,7 +547,7 @@ npm run precommit-check
|
|
|
409
547
|
// Create new hook file
|
|
410
548
|
const preCommitHook = `#!/usr/bin/env sh\n${preCommitContent}`;
|
|
411
549
|
fs.writeFileSync(preCommitPath, preCommitHook);
|
|
412
|
-
console.log(` ${c.green('✓')} Created .husky/pre-commit (lint-staged
|
|
550
|
+
console.log(` ${c.green('✓')} Created .husky/pre-commit (lint-staged only)`);
|
|
413
551
|
}
|
|
414
552
|
try { fs.chmodSync(preCommitPath, '755'); } catch {}
|
|
415
553
|
|
|
@@ -470,33 +608,50 @@ function buildInstallCommand(packageManager: string, deps: string[]): string {
|
|
|
470
608
|
|
|
471
609
|
// ── Update package.json ─────────────────────────────────────────────────────
|
|
472
610
|
|
|
473
|
-
function updatePackageJson(cwd: string, project: any): void {
|
|
611
|
+
function updatePackageJson(cwd: string, project: any, manifest?: any): void {
|
|
474
612
|
const packageJsonPath = path.join(cwd, 'package.json');
|
|
475
613
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
476
614
|
const packageName = '@hatem427/code-guard-ci';
|
|
477
615
|
|
|
478
616
|
packageJson.scripts = packageJson.scripts || {};
|
|
479
617
|
|
|
618
|
+
// Helper to track script changes in manifest
|
|
619
|
+
const setScript = (name: string, value: string) => {
|
|
620
|
+
if (manifest) {
|
|
621
|
+
if (packageJson.scripts[name] && packageJson.scripts[name] !== value) {
|
|
622
|
+
// Script existed with different value — record original
|
|
623
|
+
const { recordModifiedScript } = requireUtil('manifest');
|
|
624
|
+
recordModifiedScript(manifest, name, packageJson.scripts[name]);
|
|
625
|
+
} else if (!packageJson.scripts[name]) {
|
|
626
|
+
// New script
|
|
627
|
+
const { recordAddedScript } = requireUtil('manifest');
|
|
628
|
+
recordAddedScript(manifest, name);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
packageJson.scripts[name] = value;
|
|
632
|
+
};
|
|
633
|
+
|
|
480
634
|
// Core Code Guardian scripts
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
635
|
+
setScript('precommit-check', 'code-guard check');
|
|
636
|
+
setScript('check-vuln', 'code-guard check-vuln');
|
|
637
|
+
setScript('generate-doc', 'code-guard doc');
|
|
638
|
+
setScript('generate-pr-checklist', 'code-guard checklist');
|
|
639
|
+
setScript('validate', 'code-guard validate');
|
|
640
|
+
setScript('prepare', 'husky');
|
|
486
641
|
|
|
487
642
|
// Lint and format scripts
|
|
488
643
|
const extensions = project.usesTypeScript ? '.ts,.tsx,.js,.jsx' : '.js,.jsx';
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
644
|
+
setScript('lint', `eslint . --ext ${extensions}`);
|
|
645
|
+
setScript('lint:fix', `eslint . --ext ${extensions} --fix`);
|
|
646
|
+
setScript('format', 'prettier --write .');
|
|
647
|
+
setScript('format:check', 'prettier --check .');
|
|
493
648
|
|
|
494
649
|
// Security scripts
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
650
|
+
setScript('set-bypass-password', `node node_modules/${packageName}/dist/scripts/set-bypass-password.js`);
|
|
651
|
+
setScript('set-admin-password', `node node_modules/${packageName}/dist/scripts/set-admin-password.js`);
|
|
652
|
+
setScript('delete-bypass-logs', `node node_modules/${packageName}/dist/scripts/delete-bypass-logs.js`);
|
|
653
|
+
setScript('view-bypass-log', `node node_modules/${packageName}/dist/scripts/view-bypass-log.js`);
|
|
654
|
+
setScript('auto-fix', `node node_modules/${packageName}/dist/scripts/auto-fix.js`);
|
|
500
655
|
|
|
501
656
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
502
657
|
console.log(` ${c.green('✓')} Added npm scripts (lint, format, validate, precommit-check, etc.)`);
|
|
@@ -504,18 +659,35 @@ function updatePackageJson(cwd: string, project: any): void {
|
|
|
504
659
|
|
|
505
660
|
// ── Copy Code Guardian files ────────────────────────────────────────────────
|
|
506
661
|
|
|
507
|
-
function copyCodeGuardianFiles(cwd: string): void {
|
|
662
|
+
function copyCodeGuardianFiles(cwd: string, manifest?: any, projectType?: string): void {
|
|
508
663
|
// Determine source directories (handles both dev and built package layouts)
|
|
509
664
|
const distDir = path.join(__dirname, '..');
|
|
510
665
|
const pkgRootDir = path.join(__dirname, '..', '..');
|
|
511
666
|
|
|
512
|
-
// Copy config files
|
|
667
|
+
// Copy config files — only guidelines (always) + detected framework config
|
|
513
668
|
const configDir = path.join(cwd, 'config');
|
|
514
669
|
if (!fs.existsSync(configDir)) {
|
|
515
670
|
fs.mkdirSync(configDir, { recursive: true });
|
|
516
671
|
}
|
|
517
672
|
|
|
518
|
-
|
|
673
|
+
// Map project type to its config file
|
|
674
|
+
const frameworkConfigMap: Record<string, string> = {
|
|
675
|
+
angular: 'angular.config.ts',
|
|
676
|
+
react: 'react.config.ts',
|
|
677
|
+
nextjs: 'nextjs.config.ts',
|
|
678
|
+
node: 'node.config.ts',
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// Always include guidelines + detected framework config (+ react for nextjs since it inherits)
|
|
682
|
+
const configFiles = ['guidelines.config.ts'];
|
|
683
|
+
if (projectType && frameworkConfigMap[projectType]) {
|
|
684
|
+
configFiles.push(frameworkConfigMap[projectType]);
|
|
685
|
+
}
|
|
686
|
+
// Next.js inherits React rules, so include react config too
|
|
687
|
+
if (projectType === 'nextjs' && !configFiles.includes('react.config.ts')) {
|
|
688
|
+
configFiles.push('react.config.ts');
|
|
689
|
+
}
|
|
690
|
+
|
|
519
691
|
const sourceConfigDirs = [path.join(distDir, 'config'), path.join(pkgRootDir, 'config')];
|
|
520
692
|
|
|
521
693
|
for (const file of configFiles) {
|
|
@@ -530,6 +702,10 @@ function copyCodeGuardianFiles(cwd: string): void {
|
|
|
530
702
|
const dest = path.join(configDir, file);
|
|
531
703
|
if (src && !fs.existsSync(dest)) {
|
|
532
704
|
fs.copyFileSync(src, dest);
|
|
705
|
+
if (manifest) {
|
|
706
|
+
const { recordCreatedFile } = requireUtil('manifest');
|
|
707
|
+
recordCreatedFile(manifest, `config/${file}`);
|
|
708
|
+
}
|
|
533
709
|
console.log(` ${c.green('✓')} ${file}`);
|
|
534
710
|
}
|
|
535
711
|
}
|
|
@@ -555,6 +731,10 @@ function copyCodeGuardianFiles(cwd: string): void {
|
|
|
555
731
|
const dest = path.join(templatesDir, file);
|
|
556
732
|
if (src && !fs.existsSync(dest)) {
|
|
557
733
|
fs.copyFileSync(src, dest);
|
|
734
|
+
if (manifest) {
|
|
735
|
+
const { recordCreatedFile } = requireUtil('manifest');
|
|
736
|
+
recordCreatedFile(manifest, `templates/${file}`);
|
|
737
|
+
}
|
|
558
738
|
console.log(` ${c.green('✓')} ${file}`);
|
|
559
739
|
}
|
|
560
740
|
}
|
|
@@ -564,6 +744,10 @@ function copyCodeGuardianFiles(cwd: string): void {
|
|
|
564
744
|
if (!fs.existsSync(docsDir)) {
|
|
565
745
|
fs.mkdirSync(docsDir, { recursive: true });
|
|
566
746
|
fs.writeFileSync(path.join(docsDir, '.gitkeep'), '');
|
|
747
|
+
if (manifest) {
|
|
748
|
+
const { recordCreatedFile } = requireUtil('manifest');
|
|
749
|
+
recordCreatedFile(manifest, 'docs/features/.gitkeep');
|
|
750
|
+
}
|
|
567
751
|
console.log(` ${c.green('✓')} Created docs/features/`);
|
|
568
752
|
}
|
|
569
753
|
}
|
|
@@ -931,11 +1115,22 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
931
1115
|
showBanner();
|
|
932
1116
|
console.log(c.bold('🗑️ Code Guardian Uninstall\n'));
|
|
933
1117
|
|
|
934
|
-
const
|
|
1118
|
+
const cwd = process.cwd();
|
|
1119
|
+
|
|
1120
|
+
// ── Load manifest for smart uninstall ────────────────────────────────
|
|
1121
|
+
const { loadManifest, removeMarkedContent } = requireUtil('manifest');
|
|
1122
|
+
const manifest = loadManifest(cwd);
|
|
1123
|
+
|
|
1124
|
+
// Hardcoded fallbacks for files that Code Guardian always creates
|
|
1125
|
+
const fallbackFilesToRemove = [
|
|
935
1126
|
'.husky/_/husky.sh',
|
|
1127
|
+
'eslint.config.mjs',
|
|
936
1128
|
'eslint.config.js',
|
|
1129
|
+
'.prettierrc.json',
|
|
1130
|
+
'.prettierignore',
|
|
937
1131
|
'prettier.config.js',
|
|
938
1132
|
'.editorconfig',
|
|
1133
|
+
'.lintstagedrc.json',
|
|
939
1134
|
'lint-staged.config.js',
|
|
940
1135
|
'tsconfig.strict.json',
|
|
941
1136
|
];
|
|
@@ -944,11 +1139,11 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
944
1139
|
'config',
|
|
945
1140
|
'templates',
|
|
946
1141
|
'docs',
|
|
947
|
-
'.code-guardian',
|
|
948
1142
|
];
|
|
949
1143
|
|
|
950
1144
|
const scriptsToRemove = [
|
|
951
1145
|
'precommit-check',
|
|
1146
|
+
'check-vuln',
|
|
952
1147
|
'auto-fix',
|
|
953
1148
|
'generate-doc',
|
|
954
1149
|
'generate-pr-checklist',
|
|
@@ -956,12 +1151,17 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
956
1151
|
'set-admin-password',
|
|
957
1152
|
'view-bypass-log',
|
|
958
1153
|
'delete-bypass-logs',
|
|
1154
|
+
'lint',
|
|
1155
|
+
'lint:fix',
|
|
1156
|
+
'format',
|
|
1157
|
+
'format:check',
|
|
1158
|
+
'validate',
|
|
1159
|
+
'prepare',
|
|
959
1160
|
];
|
|
960
1161
|
|
|
961
1162
|
// ── Detect AI config files via registry ──────────────────────────────
|
|
962
1163
|
const { defaultRegistry } = requireUtil('ai-config-registry');
|
|
963
1164
|
const aiTemplates = defaultRegistry.getAll();
|
|
964
|
-
const cwd = process.cwd();
|
|
965
1165
|
|
|
966
1166
|
// Classify each AI config: full-delete vs strip-only
|
|
967
1167
|
interface AIConfigAction {
|
|
@@ -969,7 +1169,7 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
969
1169
|
filePath: string;
|
|
970
1170
|
relativePath: string;
|
|
971
1171
|
marker: string;
|
|
972
|
-
action: 'delete' | 'strip';
|
|
1172
|
+
action: 'delete' | 'strip';
|
|
973
1173
|
}
|
|
974
1174
|
const aiActions: AIConfigAction[] = [];
|
|
975
1175
|
|
|
@@ -981,48 +1181,111 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
981
1181
|
if (!fs.existsSync(filePath)) continue;
|
|
982
1182
|
|
|
983
1183
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
984
|
-
if (!content.includes(t.marker)) continue;
|
|
985
|
-
|
|
986
|
-
// Determine if the file was created by us or if we appended
|
|
987
|
-
// If appended, there will be a --- separator before our marker with user content above
|
|
988
|
-
const markerIdx = content.indexOf(t.marker);
|
|
989
|
-
const beforeMarker = content.substring(0, markerIdx).trimEnd();
|
|
990
|
-
|
|
991
|
-
// Check if there's substantial user content before our section
|
|
992
|
-
// When we append we add "\n\n---\n\n" before our content
|
|
993
|
-
// When we create, the marker is at or near the top (possibly after frontmatter)
|
|
994
|
-
const stripped = beforeMarker.replace(/^---[\s\S]*?---/, '').trim(); // strip MDC frontmatter
|
|
995
|
-
const hasUserContent = stripped.length > 0 && !stripped.endsWith('---');
|
|
1184
|
+
if (!content.includes(t.marker)) continue;
|
|
996
1185
|
|
|
997
|
-
//
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1186
|
+
// Use manifest to determine action if available
|
|
1187
|
+
if (manifest && manifest.files[relativePath]) {
|
|
1188
|
+
const record = manifest.files[relativePath];
|
|
1189
|
+
if (record.action === 'created') {
|
|
1190
|
+
aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'delete' });
|
|
1191
|
+
} else {
|
|
1192
|
+
aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'strip' });
|
|
1193
|
+
}
|
|
1003
1194
|
} else {
|
|
1004
|
-
|
|
1195
|
+
// Fallback: detect by checking for user content before marker
|
|
1196
|
+
const markerIdx = content.indexOf(t.marker);
|
|
1197
|
+
const beforeMarker = content.substring(0, markerIdx).trimEnd();
|
|
1198
|
+
const withoutTrailingSep = beforeMarker.replace(/\n*---\s*$/, '').trim();
|
|
1199
|
+
const realUserContent = withoutTrailingSep.replace(/^---[\s\S]*?---/, '').trim();
|
|
1200
|
+
|
|
1201
|
+
if (realUserContent.length > 0) {
|
|
1202
|
+
aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'strip' });
|
|
1203
|
+
} else {
|
|
1204
|
+
aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'delete' });
|
|
1205
|
+
}
|
|
1005
1206
|
}
|
|
1006
1207
|
}
|
|
1007
1208
|
|
|
1209
|
+
// Determine which files to remove (use manifest if available, fallback to hardcoded)
|
|
1210
|
+
let filesToProcess: string[];
|
|
1211
|
+
if (manifest) {
|
|
1212
|
+
// Use manifest — it knows exactly what we created vs modified
|
|
1213
|
+
filesToProcess = Object.keys(manifest.files).filter(f => {
|
|
1214
|
+
// Skip AI files — handled separately
|
|
1215
|
+
return !aiActions.some(a => a.relativePath === f);
|
|
1216
|
+
});
|
|
1217
|
+
} else {
|
|
1218
|
+
filesToProcess = fallbackFilesToRemove;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1008
1221
|
// Check what exists
|
|
1009
|
-
const existingFiles =
|
|
1222
|
+
const existingFiles = filesToProcess.filter(f => fs.existsSync(path.join(cwd, f)));
|
|
1010
1223
|
const existingDirs = dirsToRemove.filter(d => fs.existsSync(path.join(cwd, d)));
|
|
1011
1224
|
|
|
1012
|
-
|
|
1225
|
+
// Also check for backup files that need restoring
|
|
1226
|
+
const backupsToRestore: Array<{ original: string; backup: string }> = [];
|
|
1227
|
+
if (manifest && manifest.backups) {
|
|
1228
|
+
for (const [original, backup] of Object.entries(manifest.backups) as [string, string][]) {
|
|
1229
|
+
if (fs.existsSync(path.join(cwd, backup))) {
|
|
1230
|
+
backupsToRestore.push({ original, backup });
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
// Also find any .backup files from generators (eslint, prettier create them)
|
|
1235
|
+
for (const file of existingFiles) {
|
|
1236
|
+
const backupPath = path.join(cwd, file + '.backup');
|
|
1237
|
+
if (fs.existsSync(backupPath) && !backupsToRestore.some(b => b.backup === file + '.backup')) {
|
|
1238
|
+
backupsToRestore.push({ original: file, backup: file + '.backup' });
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
// Check common backup locations from generators
|
|
1242
|
+
const commonBackups = [
|
|
1243
|
+
'.eslintrc.backup', '.eslintrc.js.backup', '.eslintrc.cjs.backup', '.eslintrc.json.backup',
|
|
1244
|
+
'eslint.config.js.backup', 'eslint.config.mjs.backup', 'eslint.config.cjs.backup',
|
|
1245
|
+
'.prettierrc.backup', '.prettierrc.js.backup', '.prettierrc.cjs.backup', '.prettierrc.json.backup',
|
|
1246
|
+
'.prettierrc.yml.backup', '.prettierrc.yaml.backup', '.prettierrc.toml.backup', 'prettier.config.js.backup',
|
|
1247
|
+
'.eslintignore.backup',
|
|
1248
|
+
];
|
|
1249
|
+
for (const backup of commonBackups) {
|
|
1250
|
+
if (fs.existsSync(path.join(cwd, backup))) {
|
|
1251
|
+
const original = backup.replace(/\.backup$/, '');
|
|
1252
|
+
if (!backupsToRestore.some(b => b.backup === backup)) {
|
|
1253
|
+
backupsToRestore.push({ original, backup });
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
if (existingFiles.length === 0 && existingDirs.length === 0 && aiActions.length === 0 && backupsToRestore.length === 0) {
|
|
1013
1259
|
console.log(c.yellow('⚠️ No Code Guardian files found to remove.\n'));
|
|
1014
1260
|
return;
|
|
1015
1261
|
}
|
|
1016
1262
|
|
|
1017
|
-
// Show what will be
|
|
1263
|
+
// Show what will be done
|
|
1018
1264
|
console.log(c.bold('The following will be removed:\n'));
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1265
|
+
|
|
1266
|
+
// Separate created vs modified files
|
|
1267
|
+
const createdFiles: string[] = [];
|
|
1268
|
+
const modifiedFiles: string[] = [];
|
|
1269
|
+
for (const f of existingFiles) {
|
|
1270
|
+
if (manifest && manifest.files[f] && manifest.files[f].action === 'modified') {
|
|
1271
|
+
modifiedFiles.push(f);
|
|
1272
|
+
} else {
|
|
1273
|
+
createdFiles.push(f);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
if (createdFiles.length > 0) {
|
|
1278
|
+
console.log(c.bold('Files (created by Code Guardian — will be deleted):'));
|
|
1279
|
+
createdFiles.forEach(f => console.log(` ${c.red('✗')} ${f}`));
|
|
1023
1280
|
console.log('');
|
|
1024
1281
|
}
|
|
1025
|
-
|
|
1282
|
+
|
|
1283
|
+
if (modifiedFiles.length > 0) {
|
|
1284
|
+
console.log(c.bold('Files (modified — Code Guardian content will be stripped):'));
|
|
1285
|
+
modifiedFiles.forEach(f => console.log(` ${c.yellow('⚠')} ${f} ${c.dim('(your content preserved)')}`));
|
|
1286
|
+
console.log('');
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1026
1289
|
if (existingDirs.length > 0) {
|
|
1027
1290
|
console.log(c.bold('Directories:'));
|
|
1028
1291
|
existingDirs.forEach(d => console.log(` ${c.red('✗')} ${d}/`));
|
|
@@ -1044,6 +1307,12 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1044
1307
|
console.log('');
|
|
1045
1308
|
}
|
|
1046
1309
|
|
|
1310
|
+
if (backupsToRestore.length > 0) {
|
|
1311
|
+
console.log(c.bold('Backups to restore (original configs):'));
|
|
1312
|
+
backupsToRestore.forEach(b => console.log(` ${c.blue('↩')} ${b.backup} → ${b.original}`));
|
|
1313
|
+
console.log('');
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1047
1316
|
if (!hasFlag('yes') && !hasFlag('y')) {
|
|
1048
1317
|
const readline = require('readline');
|
|
1049
1318
|
const rl = readline.createInterface({
|
|
@@ -1067,48 +1336,71 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1067
1336
|
let removedCount = 0;
|
|
1068
1337
|
console.log('');
|
|
1069
1338
|
|
|
1070
|
-
//
|
|
1071
|
-
|
|
1072
|
-
|
|
1339
|
+
// ── Phase 1: Handle created files (delete) ──────────────────────────
|
|
1340
|
+
if (createdFiles.length > 0) {
|
|
1341
|
+
console.log(c.bold('Removing created files...\n'));
|
|
1342
|
+
for (const file of createdFiles) {
|
|
1343
|
+
const fullPath = path.join(cwd, file);
|
|
1344
|
+
if (fs.existsSync(fullPath)) {
|
|
1345
|
+
try {
|
|
1346
|
+
fs.unlinkSync(fullPath);
|
|
1347
|
+
console.log(` ${c.green('✓')} Removed ${file}`);
|
|
1348
|
+
removedCount++;
|
|
1349
|
+
} catch (error: any) {
|
|
1350
|
+
console.log(` ${c.red('✗')} Failed to remove ${file}: ${error.message}`);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
console.log('');
|
|
1355
|
+
}
|
|
1073
1356
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
if (fs.existsSync(
|
|
1357
|
+
// ── Phase 2: Handle modified files (strip marked content) ───────────
|
|
1358
|
+
if (modifiedFiles.length > 0) {
|
|
1359
|
+
console.log(c.bold('Stripping Code Guardian content from modified files...\n'));
|
|
1360
|
+
for (const file of modifiedFiles) {
|
|
1361
|
+
const fullPath = path.join(cwd, file);
|
|
1362
|
+
if (fs.existsSync(fullPath)) {
|
|
1080
1363
|
try {
|
|
1081
|
-
const
|
|
1082
|
-
const
|
|
1083
|
-
if (
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1364
|
+
const record = manifest?.files[file];
|
|
1365
|
+
const stripped = removeMarkedContent(fullPath, record?.marker);
|
|
1366
|
+
if (stripped) {
|
|
1367
|
+
console.log(` ${c.green('✓')} Stripped Code Guardian content from ${file}`);
|
|
1368
|
+
} else {
|
|
1369
|
+
console.log(` ${c.dim('○')} No marked content found in ${file}`);
|
|
1370
|
+
}
|
|
1371
|
+
removedCount++;
|
|
1087
1372
|
} catch (error: any) {
|
|
1088
|
-
console.log(` ${c.
|
|
1373
|
+
console.log(` ${c.red('✗')} Failed to clean ${file}: ${error.message}`);
|
|
1089
1374
|
}
|
|
1090
1375
|
}
|
|
1091
1376
|
}
|
|
1092
1377
|
console.log('');
|
|
1093
1378
|
}
|
|
1094
1379
|
|
|
1095
|
-
//
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1380
|
+
// ── Phase 3: Restore backup files ───────────────────────────────────
|
|
1381
|
+
if (backupsToRestore.length > 0) {
|
|
1382
|
+
console.log(c.bold('Restoring original config files...\n'));
|
|
1383
|
+
for (const { original, backup } of backupsToRestore) {
|
|
1384
|
+
const backupFullPath = path.join(cwd, backup);
|
|
1385
|
+
const originalFullPath = path.join(cwd, original);
|
|
1100
1386
|
try {
|
|
1101
|
-
|
|
1102
|
-
|
|
1387
|
+
// Remove the Code Guardian version first (if it exists)
|
|
1388
|
+
if (fs.existsSync(originalFullPath)) {
|
|
1389
|
+
fs.unlinkSync(originalFullPath);
|
|
1390
|
+
}
|
|
1391
|
+
// Restore the backup as the original
|
|
1392
|
+
fs.renameSync(backupFullPath, originalFullPath);
|
|
1393
|
+
console.log(` ${c.green('✓')} Restored ${original} from ${backup}`);
|
|
1103
1394
|
removedCount++;
|
|
1104
1395
|
} catch (error: any) {
|
|
1105
|
-
console.log(` ${c.red('✗')} Failed to
|
|
1396
|
+
console.log(` ${c.red('✗')} Failed to restore ${original}: ${error.message}`);
|
|
1106
1397
|
}
|
|
1107
1398
|
}
|
|
1399
|
+
console.log('');
|
|
1108
1400
|
}
|
|
1109
1401
|
|
|
1110
|
-
// Remove directories
|
|
1111
|
-
console.log(c.bold('
|
|
1402
|
+
// ── Phase 4: Remove directories ─────────────────────────────────────
|
|
1403
|
+
console.log(c.bold('Removing directories...\n'));
|
|
1112
1404
|
for (const dir of dirsToRemove) {
|
|
1113
1405
|
const fullPath = path.join(cwd, dir);
|
|
1114
1406
|
if (fs.existsSync(fullPath)) {
|
|
@@ -1122,12 +1414,11 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1122
1414
|
}
|
|
1123
1415
|
}
|
|
1124
1416
|
|
|
1125
|
-
// Handle AI config files intelligently
|
|
1417
|
+
// ── Phase 5: Handle AI config files intelligently ───────────────────
|
|
1126
1418
|
console.log(c.bold('\nCleaning AI config files...\n'));
|
|
1127
1419
|
for (const action of aiActions) {
|
|
1128
1420
|
try {
|
|
1129
1421
|
if (action.action === 'delete') {
|
|
1130
|
-
// Entire file was created by Code Guardian → delete it
|
|
1131
1422
|
fs.unlinkSync(action.filePath);
|
|
1132
1423
|
console.log(` ${c.green('✓')} Removed ${action.relativePath}`);
|
|
1133
1424
|
removedCount++;
|
|
@@ -1139,7 +1430,6 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1139
1430
|
const remaining = fs.readdirSync(parentDir);
|
|
1140
1431
|
if (remaining.length === 0) {
|
|
1141
1432
|
fs.rmdirSync(parentDir);
|
|
1142
|
-
// Check grandparent too (e.g., .windsurf/rules → .windsurf)
|
|
1143
1433
|
const grandparent = path.dirname(parentDir);
|
|
1144
1434
|
if (grandparent !== cwd && fs.existsSync(grandparent)) {
|
|
1145
1435
|
const gpRemaining = fs.readdirSync(grandparent);
|
|
@@ -1154,18 +1444,13 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1154
1444
|
const markerIdx = content.indexOf(action.marker);
|
|
1155
1445
|
if (markerIdx === -1) continue;
|
|
1156
1446
|
|
|
1157
|
-
// Find the separator (--- on its own line) before the marker
|
|
1158
|
-
// When we append, we add "\n\n---\n\n" before our content
|
|
1159
1447
|
let cutStart = markerIdx;
|
|
1160
1448
|
const beforeMarker = content.substring(0, markerIdx);
|
|
1161
|
-
|
|
1162
|
-
// Look for the --- separator we added
|
|
1163
1449
|
const sepMatch = beforeMarker.match(/\n*\s*---\s*\n*$/);
|
|
1164
1450
|
if (sepMatch && sepMatch.index !== undefined) {
|
|
1165
1451
|
cutStart = sepMatch.index;
|
|
1166
1452
|
}
|
|
1167
1453
|
|
|
1168
|
-
// Remove from cutStart to end of file (our content is always appended at the end)
|
|
1169
1454
|
const cleaned = content.substring(0, cutStart).trimEnd() + '\n';
|
|
1170
1455
|
fs.writeFileSync(action.filePath, cleaned);
|
|
1171
1456
|
console.log(` ${c.green('✓')} Stripped Code Guardian rules from ${action.relativePath} ${c.dim('(your content preserved)')}`);
|
|
@@ -1176,7 +1461,7 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1176
1461
|
}
|
|
1177
1462
|
}
|
|
1178
1463
|
|
|
1179
|
-
// Clean up .husky hooks smartly
|
|
1464
|
+
// ── Phase 6: Clean up .husky hooks smartly ──────────────────────────
|
|
1180
1465
|
console.log(c.bold('\nCleaning git hooks...\n'));
|
|
1181
1466
|
const huskyDir = path.join(cwd, '.husky');
|
|
1182
1467
|
const huskyHookFiles = ['pre-commit'];
|
|
@@ -1198,7 +1483,6 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1198
1483
|
const beforeHook = content.substring(0, startIdx).trimEnd();
|
|
1199
1484
|
const afterHook = content.substring(endIdx + HOOK_END.length).trimEnd();
|
|
1200
1485
|
|
|
1201
|
-
// Check if there's real user content outside of our markers
|
|
1202
1486
|
const remaining = (beforeHook.replace(/^#!\/usr\/bin\/env\s+sh\s*/, '').trim() + afterHook.trim()).trim();
|
|
1203
1487
|
|
|
1204
1488
|
if (remaining.length === 0) {
|
|
@@ -1229,23 +1513,47 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1229
1513
|
} catch {}
|
|
1230
1514
|
}
|
|
1231
1515
|
|
|
1232
|
-
//
|
|
1516
|
+
// ── Phase 7: Clean package.json scripts ─────────────────────────────
|
|
1233
1517
|
console.log(c.bold('\nCleaning package.json scripts...\n'));
|
|
1234
|
-
const packageJsonPath = path.join(cwd, 'package.json');
|
|
1518
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
1519
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1235
1520
|
try {
|
|
1236
1521
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
1237
|
-
let
|
|
1522
|
+
let scriptChanged = false;
|
|
1523
|
+
|
|
1524
|
+
if (manifest && manifest.packageJsonScripts) {
|
|
1525
|
+
// Restore modified scripts to original values
|
|
1526
|
+
for (const [scriptName, originalValue] of Object.entries(manifest.packageJsonScripts.modified)) {
|
|
1527
|
+
if (packageJson.scripts && packageJson.scripts[scriptName]) {
|
|
1528
|
+
packageJson.scripts[scriptName] = originalValue;
|
|
1529
|
+
console.log(` ${c.green('✓')} Restored script: ${scriptName} ${c.dim('(original value)')}`);
|
|
1530
|
+
scriptChanged = true;
|
|
1531
|
+
removedCount++;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1238
1534
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1535
|
+
// Remove scripts that were added by Code Guardian
|
|
1536
|
+
for (const scriptName of manifest.packageJsonScripts.added) {
|
|
1537
|
+
if (packageJson.scripts && packageJson.scripts[scriptName]) {
|
|
1538
|
+
delete packageJson.scripts[scriptName];
|
|
1539
|
+
console.log(` ${c.green('✓')} Removed script: ${scriptName}`);
|
|
1540
|
+
scriptChanged = true;
|
|
1541
|
+
removedCount++;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
} else {
|
|
1545
|
+
// Fallback: remove known Code Guardian scripts
|
|
1546
|
+
for (const script of scriptsToRemove) {
|
|
1547
|
+
if (packageJson.scripts && packageJson.scripts[script]) {
|
|
1548
|
+
delete packageJson.scripts[script];
|
|
1549
|
+
console.log(` ${c.green('✓')} Removed script: ${script}`);
|
|
1550
|
+
scriptChanged = true;
|
|
1551
|
+
removedCount++;
|
|
1552
|
+
}
|
|
1245
1553
|
}
|
|
1246
1554
|
}
|
|
1247
1555
|
|
|
1248
|
-
if (
|
|
1556
|
+
if (scriptChanged) {
|
|
1249
1557
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
1250
1558
|
console.log(` ${c.green('✓')} Updated package.json`);
|
|
1251
1559
|
} else {
|
|
@@ -1256,19 +1564,69 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1256
1564
|
}
|
|
1257
1565
|
}
|
|
1258
1566
|
|
|
1567
|
+
// ── Phase 8: Remove .code-guardian directory (including manifest) ────
|
|
1568
|
+
const codeGuardianDir = path.join(cwd, '.code-guardian');
|
|
1569
|
+
if (fs.existsSync(codeGuardianDir)) {
|
|
1570
|
+
try {
|
|
1571
|
+
fs.rmSync(codeGuardianDir, { recursive: true, force: true });
|
|
1572
|
+
console.log(`\n ${c.green('✓')} Removed .code-guardian/`);
|
|
1573
|
+
removedCount++;
|
|
1574
|
+
} catch (error: any) {
|
|
1575
|
+
console.log(` ${c.red('✗')} Failed to remove .code-guardian/: ${error.message}`);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1259
1579
|
console.log('');
|
|
1260
1580
|
console.log(c.green(`✅ Cleanup complete! Removed ${removedCount} item(s).`));
|
|
1261
|
-
|
|
1262
|
-
if (hasBackup) {
|
|
1263
|
-
console.log(c.blue(`📦 Backup saved to: ${path.basename(backupDir)}/`));
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
1581
|
console.log('');
|
|
1267
1582
|
console.log(c.dim('To completely remove the package, run:'));
|
|
1268
1583
|
console.log(c.dim(' npm uninstall @hatem427/code-guard-ci'));
|
|
1269
1584
|
console.log('');
|
|
1270
1585
|
}
|
|
1271
1586
|
|
|
1587
|
+
// ── Vulnerability Check ────────────────────────────────────────────────────
|
|
1588
|
+
|
|
1589
|
+
function runVulnerabilityCheck(): void {
|
|
1590
|
+
showBanner();
|
|
1591
|
+
console.log(c.bold('🔍 Code Guardian — Vulnerability Scan\n'));
|
|
1592
|
+
|
|
1593
|
+
const cwd = process.cwd();
|
|
1594
|
+
let hasIssues = false;
|
|
1595
|
+
|
|
1596
|
+
// ── npm audit ──────────────────────────────────────────────────────────────
|
|
1597
|
+
console.log(c.bold('📦 Running npm audit...'));
|
|
1598
|
+
try {
|
|
1599
|
+
execSync('npm audit --audit-level=moderate', { stdio: 'inherit', cwd });
|
|
1600
|
+
console.log(` ${c.green('✓')} No moderate/high vulnerabilities found\n`);
|
|
1601
|
+
} catch {
|
|
1602
|
+
console.log(` ${c.yellow('⚠')} npm audit found vulnerabilities. Run: npm audit fix\n`);
|
|
1603
|
+
hasIssues = true;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
// ── RetireJS ───────────────────────────────────────────────────────────────
|
|
1607
|
+
console.log(c.bold('🔒 Checking for retired/vulnerable JS libraries (RetireJS)...'));
|
|
1608
|
+
const retireResult = spawnSync('npx', ['retire', '--path', '.', '--outputformat', 'text'], {
|
|
1609
|
+
encoding: 'utf-8',
|
|
1610
|
+
cwd,
|
|
1611
|
+
});
|
|
1612
|
+
if (retireResult.error) {
|
|
1613
|
+
console.log(c.dim(' ○ RetireJS not available — skipping (install with: npm i -g retire)\n'));
|
|
1614
|
+
} else if (retireResult.status !== 0) {
|
|
1615
|
+
console.log(c.yellow(` ⚠ RetireJS found issues:\n${retireResult.stdout || retireResult.stderr}\n`));
|
|
1616
|
+
hasIssues = true;
|
|
1617
|
+
} else {
|
|
1618
|
+
console.log(` ${c.green('✓')} No retired libraries found\n`);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// ── Summary ────────────────────────────────────────────────────────────────
|
|
1622
|
+
if (hasIssues) {
|
|
1623
|
+
console.log(c.yellow('⚠️ Vulnerabilities found. Review and fix before deployment.'));
|
|
1624
|
+
process.exit(1);
|
|
1625
|
+
} else {
|
|
1626
|
+
console.log(c.green('✅ No vulnerabilities found!'));
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1272
1630
|
// ── Main ────────────────────────────────────────────────────────────────────
|
|
1273
1631
|
|
|
1274
1632
|
(async () => {
|
|
@@ -1281,6 +1639,10 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1281
1639
|
runCheck();
|
|
1282
1640
|
break;
|
|
1283
1641
|
|
|
1642
|
+
case 'check-vuln':
|
|
1643
|
+
runVulnerabilityCheck();
|
|
1644
|
+
break;
|
|
1645
|
+
|
|
1284
1646
|
case 'report':
|
|
1285
1647
|
runReport();
|
|
1286
1648
|
break;
|