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