@hatem427/code-guard-ci 3.5.3 → 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 +404 -113
- package/dist/scripts/cli.js.map +1 -1
- package/package.json +1 -1
- package/scripts/cli.ts +438 -123
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,38 +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..."
|
|
521
|
+
|
|
522
|
+
# Skip lint-staged when BYPASS_RULES is set
|
|
523
|
+
if [ "$BYPASS_RULES" = "true" ] || [ "$BYPASS_RULES" = "1" ]; then
|
|
524
|
+
echo "⚡ BYPASS_RULES detected — skipping lint-staged."
|
|
525
|
+
exit 0
|
|
526
|
+
fi
|
|
383
527
|
|
|
384
528
|
# Run lint-staged (ESLint + Prettier auto-fix)
|
|
385
529
|
npx lint-staged
|
|
386
|
-
|
|
387
|
-
# Run Code Guardian custom rules
|
|
388
|
-
npm run precommit-check
|
|
389
530
|
# --- code-guardian-hook-end ---
|
|
390
531
|
`;
|
|
391
532
|
|
|
392
|
-
const preCommitPath = path.join(huskyDir, 'pre-commit');
|
|
393
533
|
if (fs.existsSync(preCommitPath)) {
|
|
394
534
|
const existing = fs.readFileSync(preCommitPath, 'utf-8');
|
|
395
535
|
if (!existing.includes('code-guardian-hook-start')) {
|
|
@@ -403,7 +543,7 @@ npm run precommit-check
|
|
|
403
543
|
// Create new hook file
|
|
404
544
|
const preCommitHook = `#!/usr/bin/env sh\n${preCommitContent}`;
|
|
405
545
|
fs.writeFileSync(preCommitPath, preCommitHook);
|
|
406
|
-
console.log(` ${c.green('✓')} Created .husky/pre-commit (lint-staged
|
|
546
|
+
console.log(` ${c.green('✓')} Created .husky/pre-commit (lint-staged only)`);
|
|
407
547
|
}
|
|
408
548
|
try { fs.chmodSync(preCommitPath, '755'); } catch {}
|
|
409
549
|
|
|
@@ -464,33 +604,49 @@ function buildInstallCommand(packageManager: string, deps: string[]): string {
|
|
|
464
604
|
|
|
465
605
|
// ── Update package.json ─────────────────────────────────────────────────────
|
|
466
606
|
|
|
467
|
-
function updatePackageJson(cwd: string, project: any): void {
|
|
607
|
+
function updatePackageJson(cwd: string, project: any, manifest?: any): void {
|
|
468
608
|
const packageJsonPath = path.join(cwd, 'package.json');
|
|
469
609
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
470
610
|
const packageName = '@hatem427/code-guard-ci';
|
|
471
611
|
|
|
472
612
|
packageJson.scripts = packageJson.scripts || {};
|
|
473
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
|
+
|
|
474
630
|
// Core Code Guardian scripts
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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');
|
|
480
636
|
|
|
481
637
|
// Lint and format scripts
|
|
482
638
|
const extensions = project.usesTypeScript ? '.ts,.tsx,.js,.jsx' : '.js,.jsx';
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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 .');
|
|
487
643
|
|
|
488
644
|
// Security scripts
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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`);
|
|
494
650
|
|
|
495
651
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
496
652
|
console.log(` ${c.green('✓')} Added npm scripts (lint, format, validate, precommit-check, etc.)`);
|
|
@@ -498,18 +654,35 @@ function updatePackageJson(cwd: string, project: any): void {
|
|
|
498
654
|
|
|
499
655
|
// ── Copy Code Guardian files ────────────────────────────────────────────────
|
|
500
656
|
|
|
501
|
-
function copyCodeGuardianFiles(cwd: string): void {
|
|
657
|
+
function copyCodeGuardianFiles(cwd: string, manifest?: any, projectType?: string): void {
|
|
502
658
|
// Determine source directories (handles both dev and built package layouts)
|
|
503
659
|
const distDir = path.join(__dirname, '..');
|
|
504
660
|
const pkgRootDir = path.join(__dirname, '..', '..');
|
|
505
661
|
|
|
506
|
-
// Copy config files
|
|
662
|
+
// Copy config files — only guidelines (always) + detected framework config
|
|
507
663
|
const configDir = path.join(cwd, 'config');
|
|
508
664
|
if (!fs.existsSync(configDir)) {
|
|
509
665
|
fs.mkdirSync(configDir, { recursive: true });
|
|
510
666
|
}
|
|
511
667
|
|
|
512
|
-
|
|
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
|
+
|
|
513
686
|
const sourceConfigDirs = [path.join(distDir, 'config'), path.join(pkgRootDir, 'config')];
|
|
514
687
|
|
|
515
688
|
for (const file of configFiles) {
|
|
@@ -524,6 +697,10 @@ function copyCodeGuardianFiles(cwd: string): void {
|
|
|
524
697
|
const dest = path.join(configDir, file);
|
|
525
698
|
if (src && !fs.existsSync(dest)) {
|
|
526
699
|
fs.copyFileSync(src, dest);
|
|
700
|
+
if (manifest) {
|
|
701
|
+
const { recordCreatedFile } = requireUtil('manifest');
|
|
702
|
+
recordCreatedFile(manifest, `config/${file}`);
|
|
703
|
+
}
|
|
527
704
|
console.log(` ${c.green('✓')} ${file}`);
|
|
528
705
|
}
|
|
529
706
|
}
|
|
@@ -549,6 +726,10 @@ function copyCodeGuardianFiles(cwd: string): void {
|
|
|
549
726
|
const dest = path.join(templatesDir, file);
|
|
550
727
|
if (src && !fs.existsSync(dest)) {
|
|
551
728
|
fs.copyFileSync(src, dest);
|
|
729
|
+
if (manifest) {
|
|
730
|
+
const { recordCreatedFile } = requireUtil('manifest');
|
|
731
|
+
recordCreatedFile(manifest, `templates/${file}`);
|
|
732
|
+
}
|
|
552
733
|
console.log(` ${c.green('✓')} ${file}`);
|
|
553
734
|
}
|
|
554
735
|
}
|
|
@@ -558,6 +739,10 @@ function copyCodeGuardianFiles(cwd: string): void {
|
|
|
558
739
|
if (!fs.existsSync(docsDir)) {
|
|
559
740
|
fs.mkdirSync(docsDir, { recursive: true });
|
|
560
741
|
fs.writeFileSync(path.join(docsDir, '.gitkeep'), '');
|
|
742
|
+
if (manifest) {
|
|
743
|
+
const { recordCreatedFile } = requireUtil('manifest');
|
|
744
|
+
recordCreatedFile(manifest, 'docs/features/.gitkeep');
|
|
745
|
+
}
|
|
561
746
|
console.log(` ${c.green('✓')} Created docs/features/`);
|
|
562
747
|
}
|
|
563
748
|
}
|
|
@@ -925,11 +1110,22 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
925
1110
|
showBanner();
|
|
926
1111
|
console.log(c.bold('🗑️ Code Guardian Uninstall\n'));
|
|
927
1112
|
|
|
928
|
-
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 = [
|
|
929
1121
|
'.husky/_/husky.sh',
|
|
1122
|
+
'eslint.config.mjs',
|
|
930
1123
|
'eslint.config.js',
|
|
1124
|
+
'.prettierrc.json',
|
|
1125
|
+
'.prettierignore',
|
|
931
1126
|
'prettier.config.js',
|
|
932
1127
|
'.editorconfig',
|
|
1128
|
+
'.lintstagedrc.json',
|
|
933
1129
|
'lint-staged.config.js',
|
|
934
1130
|
'tsconfig.strict.json',
|
|
935
1131
|
];
|
|
@@ -938,7 +1134,6 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
938
1134
|
'config',
|
|
939
1135
|
'templates',
|
|
940
1136
|
'docs',
|
|
941
|
-
'.code-guardian',
|
|
942
1137
|
];
|
|
943
1138
|
|
|
944
1139
|
const scriptsToRemove = [
|
|
@@ -950,12 +1145,17 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
950
1145
|
'set-admin-password',
|
|
951
1146
|
'view-bypass-log',
|
|
952
1147
|
'delete-bypass-logs',
|
|
1148
|
+
'lint',
|
|
1149
|
+
'lint:fix',
|
|
1150
|
+
'format',
|
|
1151
|
+
'format:check',
|
|
1152
|
+
'validate',
|
|
1153
|
+
'prepare',
|
|
953
1154
|
];
|
|
954
1155
|
|
|
955
1156
|
// ── Detect AI config files via registry ──────────────────────────────
|
|
956
1157
|
const { defaultRegistry } = requireUtil('ai-config-registry');
|
|
957
1158
|
const aiTemplates = defaultRegistry.getAll();
|
|
958
|
-
const cwd = process.cwd();
|
|
959
1159
|
|
|
960
1160
|
// Classify each AI config: full-delete vs strip-only
|
|
961
1161
|
interface AIConfigAction {
|
|
@@ -963,7 +1163,7 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
963
1163
|
filePath: string;
|
|
964
1164
|
relativePath: string;
|
|
965
1165
|
marker: string;
|
|
966
|
-
action: 'delete' | 'strip';
|
|
1166
|
+
action: 'delete' | 'strip';
|
|
967
1167
|
}
|
|
968
1168
|
const aiActions: AIConfigAction[] = [];
|
|
969
1169
|
|
|
@@ -975,48 +1175,111 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
975
1175
|
if (!fs.existsSync(filePath)) continue;
|
|
976
1176
|
|
|
977
1177
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
978
|
-
if (!content.includes(t.marker)) continue;
|
|
1178
|
+
if (!content.includes(t.marker)) continue;
|
|
979
1179
|
|
|
980
|
-
//
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
const stripped = beforeMarker.replace(/^---[\s\S]*?---/, '').trim(); // strip MDC frontmatter
|
|
989
|
-
const hasUserContent = stripped.length > 0 && !stripped.endsWith('---');
|
|
990
|
-
|
|
991
|
-
// A more robust check: if content before marker (minus separator) has real text
|
|
992
|
-
const withoutTrailingSep = beforeMarker.replace(/\n*---\s*$/, '').trim();
|
|
993
|
-
const realUserContent = withoutTrailingSep.replace(/^---[\s\S]*?---/, '').trim(); // strip frontmatter
|
|
994
|
-
|
|
995
|
-
if (realUserContent.length > 0) {
|
|
996
|
-
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
|
+
}
|
|
997
1188
|
} else {
|
|
998
|
-
|
|
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
|
+
}
|
|
999
1200
|
}
|
|
1000
1201
|
}
|
|
1001
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
|
+
|
|
1002
1215
|
// Check what exists
|
|
1003
|
-
const existingFiles =
|
|
1216
|
+
const existingFiles = filesToProcess.filter(f => fs.existsSync(path.join(cwd, f)));
|
|
1004
1217
|
const existingDirs = dirsToRemove.filter(d => fs.existsSync(path.join(cwd, d)));
|
|
1005
1218
|
|
|
1006
|
-
|
|
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) {
|
|
1007
1253
|
console.log(c.yellow('⚠️ No Code Guardian files found to remove.\n'));
|
|
1008
1254
|
return;
|
|
1009
1255
|
}
|
|
1010
1256
|
|
|
1011
|
-
// Show what will be
|
|
1257
|
+
// Show what will be done
|
|
1012
1258
|
console.log(c.bold('The following will be removed:\n'));
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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}`));
|
|
1017
1274
|
console.log('');
|
|
1018
1275
|
}
|
|
1019
|
-
|
|
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
|
+
|
|
1020
1283
|
if (existingDirs.length > 0) {
|
|
1021
1284
|
console.log(c.bold('Directories:'));
|
|
1022
1285
|
existingDirs.forEach(d => console.log(` ${c.red('✗')} ${d}/`));
|
|
@@ -1038,6 +1301,12 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1038
1301
|
console.log('');
|
|
1039
1302
|
}
|
|
1040
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
|
+
|
|
1041
1310
|
if (!hasFlag('yes') && !hasFlag('y')) {
|
|
1042
1311
|
const readline = require('readline');
|
|
1043
1312
|
const rl = readline.createInterface({
|
|
@@ -1061,48 +1330,71 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1061
1330
|
let removedCount = 0;
|
|
1062
1331
|
console.log('');
|
|
1063
1332
|
|
|
1064
|
-
//
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
+
}
|
|
1067
1350
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
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)) {
|
|
1074
1357
|
try {
|
|
1075
|
-
const
|
|
1076
|
-
const
|
|
1077
|
-
if (
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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++;
|
|
1081
1366
|
} catch (error: any) {
|
|
1082
|
-
console.log(` ${c.
|
|
1367
|
+
console.log(` ${c.red('✗')} Failed to clean ${file}: ${error.message}`);
|
|
1083
1368
|
}
|
|
1084
1369
|
}
|
|
1085
1370
|
}
|
|
1086
1371
|
console.log('');
|
|
1087
1372
|
}
|
|
1088
1373
|
|
|
1089
|
-
//
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
const
|
|
1093
|
-
|
|
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);
|
|
1094
1380
|
try {
|
|
1095
|
-
|
|
1096
|
-
|
|
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}`);
|
|
1097
1388
|
removedCount++;
|
|
1098
1389
|
} catch (error: any) {
|
|
1099
|
-
console.log(` ${c.red('✗')} Failed to
|
|
1390
|
+
console.log(` ${c.red('✗')} Failed to restore ${original}: ${error.message}`);
|
|
1100
1391
|
}
|
|
1101
1392
|
}
|
|
1393
|
+
console.log('');
|
|
1102
1394
|
}
|
|
1103
1395
|
|
|
1104
|
-
// Remove directories
|
|
1105
|
-
console.log(c.bold('
|
|
1396
|
+
// ── Phase 4: Remove directories ─────────────────────────────────────
|
|
1397
|
+
console.log(c.bold('Removing directories...\n'));
|
|
1106
1398
|
for (const dir of dirsToRemove) {
|
|
1107
1399
|
const fullPath = path.join(cwd, dir);
|
|
1108
1400
|
if (fs.existsSync(fullPath)) {
|
|
@@ -1116,12 +1408,11 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1116
1408
|
}
|
|
1117
1409
|
}
|
|
1118
1410
|
|
|
1119
|
-
// Handle AI config files intelligently
|
|
1411
|
+
// ── Phase 5: Handle AI config files intelligently ───────────────────
|
|
1120
1412
|
console.log(c.bold('\nCleaning AI config files...\n'));
|
|
1121
1413
|
for (const action of aiActions) {
|
|
1122
1414
|
try {
|
|
1123
1415
|
if (action.action === 'delete') {
|
|
1124
|
-
// Entire file was created by Code Guardian → delete it
|
|
1125
1416
|
fs.unlinkSync(action.filePath);
|
|
1126
1417
|
console.log(` ${c.green('✓')} Removed ${action.relativePath}`);
|
|
1127
1418
|
removedCount++;
|
|
@@ -1133,7 +1424,6 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1133
1424
|
const remaining = fs.readdirSync(parentDir);
|
|
1134
1425
|
if (remaining.length === 0) {
|
|
1135
1426
|
fs.rmdirSync(parentDir);
|
|
1136
|
-
// Check grandparent too (e.g., .windsurf/rules → .windsurf)
|
|
1137
1427
|
const grandparent = path.dirname(parentDir);
|
|
1138
1428
|
if (grandparent !== cwd && fs.existsSync(grandparent)) {
|
|
1139
1429
|
const gpRemaining = fs.readdirSync(grandparent);
|
|
@@ -1148,18 +1438,13 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1148
1438
|
const markerIdx = content.indexOf(action.marker);
|
|
1149
1439
|
if (markerIdx === -1) continue;
|
|
1150
1440
|
|
|
1151
|
-
// Find the separator (--- on its own line) before the marker
|
|
1152
|
-
// When we append, we add "\n\n---\n\n" before our content
|
|
1153
1441
|
let cutStart = markerIdx;
|
|
1154
1442
|
const beforeMarker = content.substring(0, markerIdx);
|
|
1155
|
-
|
|
1156
|
-
// Look for the --- separator we added
|
|
1157
1443
|
const sepMatch = beforeMarker.match(/\n*\s*---\s*\n*$/);
|
|
1158
1444
|
if (sepMatch && sepMatch.index !== undefined) {
|
|
1159
1445
|
cutStart = sepMatch.index;
|
|
1160
1446
|
}
|
|
1161
1447
|
|
|
1162
|
-
// Remove from cutStart to end of file (our content is always appended at the end)
|
|
1163
1448
|
const cleaned = content.substring(0, cutStart).trimEnd() + '\n';
|
|
1164
1449
|
fs.writeFileSync(action.filePath, cleaned);
|
|
1165
1450
|
console.log(` ${c.green('✓')} Stripped Code Guardian rules from ${action.relativePath} ${c.dim('(your content preserved)')}`);
|
|
@@ -1170,7 +1455,7 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1170
1455
|
}
|
|
1171
1456
|
}
|
|
1172
1457
|
|
|
1173
|
-
// Clean up .husky hooks smartly
|
|
1458
|
+
// ── Phase 6: Clean up .husky hooks smartly ──────────────────────────
|
|
1174
1459
|
console.log(c.bold('\nCleaning git hooks...\n'));
|
|
1175
1460
|
const huskyDir = path.join(cwd, '.husky');
|
|
1176
1461
|
const huskyHookFiles = ['pre-commit'];
|
|
@@ -1192,7 +1477,6 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1192
1477
|
const beforeHook = content.substring(0, startIdx).trimEnd();
|
|
1193
1478
|
const afterHook = content.substring(endIdx + HOOK_END.length).trimEnd();
|
|
1194
1479
|
|
|
1195
|
-
// Check if there's real user content outside of our markers
|
|
1196
1480
|
const remaining = (beforeHook.replace(/^#!\/usr\/bin\/env\s+sh\s*/, '').trim() + afterHook.trim()).trim();
|
|
1197
1481
|
|
|
1198
1482
|
if (remaining.length === 0) {
|
|
@@ -1223,23 +1507,47 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1223
1507
|
} catch {}
|
|
1224
1508
|
}
|
|
1225
1509
|
|
|
1226
|
-
//
|
|
1510
|
+
// ── Phase 7: Clean package.json scripts ─────────────────────────────
|
|
1227
1511
|
console.log(c.bold('\nCleaning package.json scripts...\n'));
|
|
1228
|
-
const packageJsonPath = path.join(cwd, 'package.json');
|
|
1512
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
1513
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1229
1514
|
try {
|
|
1230
1515
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
1231
|
-
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
|
+
}
|
|
1232
1528
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
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
|
+
}
|
|
1239
1547
|
}
|
|
1240
1548
|
}
|
|
1241
1549
|
|
|
1242
|
-
if (
|
|
1550
|
+
if (scriptChanged) {
|
|
1243
1551
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
1244
1552
|
console.log(` ${c.green('✓')} Updated package.json`);
|
|
1245
1553
|
} else {
|
|
@@ -1250,13 +1558,20 @@ async function uninstallCodeGuard(): Promise<void> {
|
|
|
1250
1558
|
}
|
|
1251
1559
|
}
|
|
1252
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
|
+
|
|
1253
1573
|
console.log('');
|
|
1254
1574
|
console.log(c.green(`✅ Cleanup complete! Removed ${removedCount} item(s).`));
|
|
1255
|
-
|
|
1256
|
-
if (hasBackup) {
|
|
1257
|
-
console.log(c.blue(`📦 Backup saved to: ${path.basename(backupDir)}/`));
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
1575
|
console.log('');
|
|
1261
1576
|
console.log(c.dim('To completely remove the package, run:'));
|
|
1262
1577
|
console.log(c.dim(' npm uninstall @hatem427/code-guard-ci'));
|