@hatem427/code-guard-ci 2.2.1 → 2.2.2
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/dist/scripts/cli.js +272 -161
- package/dist/scripts/cli.js.map +1 -1
- package/package.json +1 -1
- package/scripts/cli.ts +282 -168
package/dist/scripts/cli.js
CHANGED
|
@@ -900,20 +900,52 @@ function promptYesNo(question) {
|
|
|
900
900
|
async function uninstallCodeGuard() {
|
|
901
901
|
showBanner();
|
|
902
902
|
console.log(c.bold('🗑️ Code Guardian Uninstall\n'));
|
|
903
|
+
const cwd = process.cwd();
|
|
904
|
+
// ── Files created entirely by Code Guardian (always delete) ──────────────
|
|
903
905
|
const filesToRemove = [
|
|
904
|
-
'.husky/_/husky.sh',
|
|
905
|
-
'eslint.config.js',
|
|
906
|
-
'prettier.config.js',
|
|
907
906
|
'.editorconfig',
|
|
907
|
+
'.lintstagedrc.json',
|
|
908
908
|
'lint-staged.config.js',
|
|
909
909
|
'tsconfig.strict.json',
|
|
910
|
+
'.prettierignore',
|
|
911
|
+
];
|
|
912
|
+
// ── Files that may have had a pre-existing version (backup/restore) ──────
|
|
913
|
+
// During init these get renamed to <file>.backup before we overwrite.
|
|
914
|
+
// On uninstall: if .backup exists → restore it, else → delete our file.
|
|
915
|
+
const restorableConfigs = [
|
|
916
|
+
// ESLint
|
|
917
|
+
{ created: 'eslint.config.mjs', backups: [
|
|
918
|
+
'eslint.config.mjs.backup', 'eslint.config.js.backup', 'eslint.config.cjs.backup',
|
|
919
|
+
'.eslintrc.backup', '.eslintrc.js.backup', '.eslintrc.cjs.backup',
|
|
920
|
+
'.eslintrc.json.backup', '.eslintrc.yml.backup',
|
|
921
|
+
] },
|
|
922
|
+
// Prettier
|
|
923
|
+
{ created: '.prettierrc.json', backups: [
|
|
924
|
+
'.prettierrc.json.backup', '.prettierrc.backup', '.prettierrc.js.backup',
|
|
925
|
+
'.prettierrc.cjs.backup', '.prettierrc.yml.backup', '.prettierrc.yaml.backup',
|
|
926
|
+
'.prettierrc.toml.backup', 'prettier.config.js.backup',
|
|
927
|
+
] },
|
|
928
|
+
// ESLint ignore (old format — we delete and backup during init)
|
|
929
|
+
{ created: '.eslintignore', backups: ['.eslintignore.backup'] },
|
|
930
|
+
];
|
|
931
|
+
// ── VS Code files created by Code Guardian ───────────────────────────────
|
|
932
|
+
const vscodeFiles = [
|
|
933
|
+
'.vscode/settings.json',
|
|
934
|
+
'.vscode/extensions.json',
|
|
935
|
+
'.vscode/tasks.json',
|
|
936
|
+
'.vscode/launch.json',
|
|
937
|
+
'.vscode/mcp.json',
|
|
938
|
+
];
|
|
939
|
+
// ── MCP config files ─────────────────────────────────────────────────────
|
|
940
|
+
const mcpFiles = [
|
|
941
|
+
'.cursor/mcp.json',
|
|
942
|
+
'.gemini/settings.json',
|
|
910
943
|
];
|
|
944
|
+
// ── Directories to remove entirely ───────────────────────────────────────
|
|
911
945
|
const dirsToRemove = [
|
|
912
|
-
'config',
|
|
913
|
-
'templates',
|
|
914
|
-
'docs',
|
|
915
946
|
'.code-guardian',
|
|
916
947
|
];
|
|
948
|
+
// ── package.json scripts to remove ───────────────────────────────────────
|
|
917
949
|
const scriptsToRemove = [
|
|
918
950
|
'precommit-check',
|
|
919
951
|
'auto-fix',
|
|
@@ -924,10 +956,9 @@ async function uninstallCodeGuard() {
|
|
|
924
956
|
'view-bypass-log',
|
|
925
957
|
'delete-bypass-logs',
|
|
926
958
|
];
|
|
927
|
-
// ── Detect AI config files via registry
|
|
959
|
+
// ── Detect AI config files via registry ──────────────────────────────────
|
|
928
960
|
const { defaultRegistry } = requireUtil('ai-config-registry');
|
|
929
961
|
const aiTemplates = defaultRegistry.getAll();
|
|
930
|
-
const cwd = process.cwd();
|
|
931
962
|
const aiActions = [];
|
|
932
963
|
for (const t of aiTemplates) {
|
|
933
964
|
const dir = t.directory ? path.join(cwd, t.directory) : cwd;
|
|
@@ -937,43 +968,65 @@ async function uninstallCodeGuard() {
|
|
|
937
968
|
continue;
|
|
938
969
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
939
970
|
if (!content.includes(t.marker))
|
|
940
|
-
continue;
|
|
941
|
-
// Determine if the file was created by us or if we appended
|
|
942
|
-
// If appended, there will be a --- separator before our marker with user content above
|
|
971
|
+
continue;
|
|
943
972
|
const markerIdx = content.indexOf(t.marker);
|
|
944
973
|
const beforeMarker = content.substring(0, markerIdx).trimEnd();
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
const stripped = beforeMarker.replace(/^---[\s\S]*?---/, '').trim(); // strip MDC frontmatter
|
|
949
|
-
const hasUserContent = stripped.length > 0 && !stripped.endsWith('---');
|
|
950
|
-
// A more robust check: if content before marker (minus separator) has real text
|
|
951
|
-
const withoutTrailingSep = beforeMarker.replace(/\n*---\s*$/, '').trim();
|
|
952
|
-
const realUserContent = withoutTrailingSep.replace(/^---[\s\S]*?---/, '').trim(); // strip frontmatter
|
|
953
|
-
if (realUserContent.length > 0) {
|
|
974
|
+
const stripped = beforeMarker.replace(/^---[\s\S]*?---/, '').trim();
|
|
975
|
+
const withoutTrailingSep = stripped.replace(/\n*---\s*$/, '').trim();
|
|
976
|
+
if (withoutTrailingSep.length > 0) {
|
|
954
977
|
aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'strip' });
|
|
955
978
|
}
|
|
956
979
|
else {
|
|
957
980
|
aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'delete' });
|
|
958
981
|
}
|
|
959
982
|
}
|
|
960
|
-
//
|
|
983
|
+
// ── Gather what exists ───────────────────────────────────────────────────
|
|
961
984
|
const existingFiles = filesToRemove.filter(f => fs.existsSync(path.join(cwd, f)));
|
|
985
|
+
const existingVSCodeFiles = vscodeFiles.filter(f => fs.existsSync(path.join(cwd, f)));
|
|
986
|
+
const existingMCPFiles = mcpFiles.filter(f => fs.existsSync(path.join(cwd, f)));
|
|
962
987
|
const existingDirs = dirsToRemove.filter(d => fs.existsSync(path.join(cwd, d)));
|
|
963
|
-
|
|
988
|
+
const restorables = [];
|
|
989
|
+
for (const rc of restorableConfigs) {
|
|
990
|
+
const createdPath = path.join(cwd, rc.created);
|
|
991
|
+
if (!fs.existsSync(createdPath))
|
|
992
|
+
continue;
|
|
993
|
+
// Find the first backup that exists
|
|
994
|
+
const foundBackup = rc.backups.find(b => fs.existsSync(path.join(cwd, b))) || null;
|
|
995
|
+
restorables.push({ created: rc.created, backupFile: foundBackup });
|
|
996
|
+
}
|
|
997
|
+
const totalFound = existingFiles.length + existingVSCodeFiles.length + existingMCPFiles.length
|
|
998
|
+
+ existingDirs.length + aiActions.length + restorables.length;
|
|
999
|
+
if (totalFound === 0) {
|
|
964
1000
|
console.log(c.yellow('⚠️ No Code Guardian files found to remove.\n'));
|
|
965
1001
|
return;
|
|
966
1002
|
}
|
|
967
|
-
// Show what will be removed
|
|
968
|
-
console.log(c.bold('The following will be
|
|
1003
|
+
// ── Show what will be removed ────────────────────────────────────────────
|
|
1004
|
+
console.log(c.bold('The following will be cleaned up:\n'));
|
|
969
1005
|
if (existingFiles.length > 0) {
|
|
970
|
-
console.log(c.bold('Files:'));
|
|
1006
|
+
console.log(c.bold('Config Files (created by Code Guardian — will be deleted):'));
|
|
971
1007
|
existingFiles.forEach(f => console.log(` ${c.red('✗')} ${f}`));
|
|
972
1008
|
console.log('');
|
|
973
1009
|
}
|
|
974
|
-
if (
|
|
975
|
-
console.log(c.bold('
|
|
976
|
-
|
|
1010
|
+
if (restorables.length > 0) {
|
|
1011
|
+
console.log(c.bold('Config Files (backup/restore):'));
|
|
1012
|
+
for (const r of restorables) {
|
|
1013
|
+
if (r.backupFile) {
|
|
1014
|
+
console.log(` ${c.yellow('↩')} ${r.created} ${c.dim(`→ restore from ${r.backupFile}`)}`);
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
console.log(` ${c.red('✗')} ${r.created} ${c.dim('(no backup found — will delete)')}`);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
console.log('');
|
|
1021
|
+
}
|
|
1022
|
+
if (existingVSCodeFiles.length > 0) {
|
|
1023
|
+
console.log(c.bold('VS Code Files (created by Code Guardian):'));
|
|
1024
|
+
existingVSCodeFiles.forEach(f => console.log(` ${c.red('✗')} ${f}`));
|
|
1025
|
+
console.log('');
|
|
1026
|
+
}
|
|
1027
|
+
if (existingMCPFiles.length > 0) {
|
|
1028
|
+
console.log(c.bold('MCP Config Files:'));
|
|
1029
|
+
existingMCPFiles.forEach(f => console.log(` ${c.red('✗')} ${f}`));
|
|
977
1030
|
console.log('');
|
|
978
1031
|
}
|
|
979
1032
|
const aiDeletes = aiActions.filter(a => a.action === 'delete');
|
|
@@ -988,51 +1041,70 @@ async function uninstallCodeGuard() {
|
|
|
988
1041
|
aiStrips.forEach(a => console.log(` ${c.yellow('⚠')} ${a.relativePath} ${c.dim('(keeping your original content)')}`));
|
|
989
1042
|
console.log('');
|
|
990
1043
|
}
|
|
1044
|
+
if (existingDirs.length > 0) {
|
|
1045
|
+
console.log(c.bold('Directories:'));
|
|
1046
|
+
existingDirs.forEach(d => console.log(` ${c.red('✗')} ${d}/`));
|
|
1047
|
+
console.log('');
|
|
1048
|
+
}
|
|
1049
|
+
// ── Confirm ──────────────────────────────────────────────────────────────
|
|
991
1050
|
if (!hasFlag('yes') && !hasFlag('y')) {
|
|
992
|
-
const
|
|
993
|
-
|
|
994
|
-
input: process.stdin,
|
|
995
|
-
output: process.stdout
|
|
996
|
-
});
|
|
997
|
-
const answer = await new Promise((resolve) => {
|
|
998
|
-
rl.question(c.yellow('⚠️ Continue with removal? (y/N): '), (ans) => {
|
|
999
|
-
rl.close();
|
|
1000
|
-
resolve(ans.trim().toLowerCase());
|
|
1001
|
-
});
|
|
1002
|
-
});
|
|
1003
|
-
if (answer !== 'y' && answer !== 'yes') {
|
|
1051
|
+
const confirmed = await promptYesNo(c.yellow('⚠️ Continue with removal? (y/N): '));
|
|
1052
|
+
if (!confirmed) {
|
|
1004
1053
|
console.log(c.dim('\nCancelled.\n'));
|
|
1005
1054
|
return;
|
|
1006
1055
|
}
|
|
1007
1056
|
}
|
|
1008
1057
|
let removedCount = 0;
|
|
1009
1058
|
console.log('');
|
|
1010
|
-
//
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
fs.
|
|
1024
|
-
console.log(` ${c.blue('📦')} Backed up ${action.relativePath}`);
|
|
1025
|
-
hasBackup = true;
|
|
1026
|
-
}
|
|
1027
|
-
catch (error) {
|
|
1028
|
-
console.log(` ${c.yellow('⚠')} Failed to backup ${action.relativePath}: ${error.message}`);
|
|
1059
|
+
// ── 1. Restore or delete restorable configs ──────────────────────────────
|
|
1060
|
+
console.log(c.bold('Restoring / removing config files...\n'));
|
|
1061
|
+
for (const r of restorables) {
|
|
1062
|
+
try {
|
|
1063
|
+
const createdPath = path.join(cwd, r.created);
|
|
1064
|
+
if (r.backupFile) {
|
|
1065
|
+
// Restore: copy backup over the Code Guardian file, then delete backup
|
|
1066
|
+
const backupPath = path.join(cwd, r.backupFile);
|
|
1067
|
+
// The backup might have a different name (e.g., .eslintrc.json.backup → restore as .eslintrc.json)
|
|
1068
|
+
const restoredName = r.backupFile.replace(/\.backup$/, '');
|
|
1069
|
+
const restoredPath = path.join(cwd, restoredName);
|
|
1070
|
+
// Remove the Code Guardian file first
|
|
1071
|
+
if (fs.existsSync(createdPath)) {
|
|
1072
|
+
fs.unlinkSync(createdPath);
|
|
1029
1073
|
}
|
|
1074
|
+
// Restore the backup
|
|
1075
|
+
fs.copyFileSync(backupPath, restoredPath);
|
|
1076
|
+
fs.unlinkSync(backupPath);
|
|
1077
|
+
console.log(` ${c.green('✓')} Restored ${restoredName} ${c.dim(`(from ${r.backupFile})`)}`);
|
|
1078
|
+
removedCount++;
|
|
1079
|
+
}
|
|
1080
|
+
else {
|
|
1081
|
+
// No backup → just delete our file
|
|
1082
|
+
fs.unlinkSync(createdPath);
|
|
1083
|
+
console.log(` ${c.green('✓')} Removed ${r.created}`);
|
|
1084
|
+
removedCount++;
|
|
1030
1085
|
}
|
|
1031
1086
|
}
|
|
1032
|
-
|
|
1087
|
+
catch (error) {
|
|
1088
|
+
console.log(` ${c.red('✗')} Failed to handle ${r.created}: ${error.message}`);
|
|
1089
|
+
}
|
|
1033
1090
|
}
|
|
1034
|
-
//
|
|
1035
|
-
|
|
1091
|
+
// Also clean up any leftover .backup files for configs that might not have
|
|
1092
|
+
// been caught above (e.g., user already manually restored but .backup remains)
|
|
1093
|
+
const allBackupPatterns = restorableConfigs.flatMap(rc => rc.backups);
|
|
1094
|
+
for (const bp of allBackupPatterns) {
|
|
1095
|
+
const bpPath = path.join(cwd, bp);
|
|
1096
|
+
if (fs.existsSync(bpPath)) {
|
|
1097
|
+
try {
|
|
1098
|
+
// Check if the original (non-backup) already exists and is NOT a Code Guardian file
|
|
1099
|
+
// If so, just delete the orphan backup
|
|
1100
|
+
fs.unlinkSync(bpPath);
|
|
1101
|
+
console.log(` ${c.green('✓')} Cleaned up orphan backup ${bp}`);
|
|
1102
|
+
}
|
|
1103
|
+
catch { }
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
// ── 2. Remove simple config files ────────────────────────────────────────
|
|
1107
|
+
console.log(c.bold('\nRemoving Code Guardian files...\n'));
|
|
1036
1108
|
for (const file of filesToRemove) {
|
|
1037
1109
|
const fullPath = path.join(cwd, file);
|
|
1038
1110
|
if (fs.existsSync(fullPath)) {
|
|
@@ -1046,76 +1118,99 @@ async function uninstallCodeGuard() {
|
|
|
1046
1118
|
}
|
|
1047
1119
|
}
|
|
1048
1120
|
}
|
|
1049
|
-
// Remove
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1121
|
+
// ── 3. Remove VS Code files ──────────────────────────────────────────────
|
|
1122
|
+
if (existingVSCodeFiles.length > 0) {
|
|
1123
|
+
console.log(c.bold('\nRemoving VS Code files...\n'));
|
|
1124
|
+
for (const file of existingVSCodeFiles) {
|
|
1125
|
+
const fullPath = path.join(cwd, file);
|
|
1126
|
+
if (fs.existsSync(fullPath)) {
|
|
1127
|
+
try {
|
|
1128
|
+
fs.unlinkSync(fullPath);
|
|
1129
|
+
console.log(` ${c.green('✓')} Removed ${file}`);
|
|
1130
|
+
removedCount++;
|
|
1131
|
+
}
|
|
1132
|
+
catch (error) {
|
|
1133
|
+
console.log(` ${c.red('✗')} Failed to remove ${file}: ${error.message}`);
|
|
1134
|
+
}
|
|
1058
1135
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1136
|
+
}
|
|
1137
|
+
// Remove .vscode/ if empty
|
|
1138
|
+
cleanupEmptyDir(path.join(cwd, '.vscode'), cwd);
|
|
1139
|
+
}
|
|
1140
|
+
// ── 4. Remove MCP config files ───────────────────────────────────────────
|
|
1141
|
+
if (existingMCPFiles.length > 0) {
|
|
1142
|
+
console.log(c.bold('\nRemoving MCP config files...\n'));
|
|
1143
|
+
for (const file of existingMCPFiles) {
|
|
1144
|
+
const fullPath = path.join(cwd, file);
|
|
1145
|
+
if (fs.existsSync(fullPath)) {
|
|
1146
|
+
try {
|
|
1147
|
+
fs.unlinkSync(fullPath);
|
|
1148
|
+
console.log(` ${c.green('✓')} Removed ${file}`);
|
|
1149
|
+
removedCount++;
|
|
1150
|
+
}
|
|
1151
|
+
catch (error) {
|
|
1152
|
+
console.log(` ${c.red('✗')} Failed to remove ${file}: ${error.message}`);
|
|
1153
|
+
}
|
|
1061
1154
|
}
|
|
1062
1155
|
}
|
|
1156
|
+
// Clean up empty .cursor/ and .gemini/ directories
|
|
1157
|
+
cleanupEmptyDir(path.join(cwd, '.cursor'), cwd);
|
|
1158
|
+
cleanupEmptyDir(path.join(cwd, '.gemini'), cwd);
|
|
1063
1159
|
}
|
|
1064
|
-
// Handle AI config files
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1160
|
+
// ── 5. Handle AI config files ────────────────────────────────────────────
|
|
1161
|
+
if (aiActions.length > 0) {
|
|
1162
|
+
console.log(c.bold('\nCleaning AI config files...\n'));
|
|
1163
|
+
for (const action of aiActions) {
|
|
1164
|
+
try {
|
|
1165
|
+
if (action.action === 'delete') {
|
|
1166
|
+
fs.unlinkSync(action.filePath);
|
|
1167
|
+
console.log(` ${c.green('✓')} Removed ${action.relativePath}`);
|
|
1168
|
+
removedCount++;
|
|
1169
|
+
// Clean up empty parent directories
|
|
1170
|
+
const parentDir = path.dirname(action.filePath);
|
|
1171
|
+
cleanupEmptyDir(parentDir, cwd);
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
// Strip only Code Guardian section
|
|
1175
|
+
const content = fs.readFileSync(action.filePath, 'utf-8');
|
|
1176
|
+
const markerIdx = content.indexOf(action.marker);
|
|
1177
|
+
if (markerIdx === -1)
|
|
1178
|
+
continue;
|
|
1179
|
+
let cutStart = markerIdx;
|
|
1180
|
+
const beforeMarker = content.substring(0, markerIdx);
|
|
1181
|
+
const sepMatch = beforeMarker.match(/\n*\s*---\s*\n*$/);
|
|
1182
|
+
if (sepMatch && sepMatch.index !== undefined) {
|
|
1183
|
+
cutStart = sepMatch.index;
|
|
1088
1184
|
}
|
|
1089
|
-
|
|
1185
|
+
const cleaned = content.substring(0, cutStart).trimEnd() + '\n';
|
|
1186
|
+
fs.writeFileSync(action.filePath, cleaned);
|
|
1187
|
+
console.log(` ${c.green('✓')} Stripped Code Guardian rules from ${action.relativePath} ${c.dim('(your content preserved)')}`);
|
|
1188
|
+
removedCount++;
|
|
1090
1189
|
}
|
|
1091
1190
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
const content = fs.readFileSync(action.filePath, 'utf-8');
|
|
1095
|
-
const markerIdx = content.indexOf(action.marker);
|
|
1096
|
-
if (markerIdx === -1)
|
|
1097
|
-
continue;
|
|
1098
|
-
// Find the separator (--- on its own line) before the marker
|
|
1099
|
-
// When we append, we add "\n\n---\n\n" before our content
|
|
1100
|
-
let cutStart = markerIdx;
|
|
1101
|
-
const beforeMarker = content.substring(0, markerIdx);
|
|
1102
|
-
// Look for the --- separator we added
|
|
1103
|
-
const sepMatch = beforeMarker.match(/\n*\s*---\s*\n*$/);
|
|
1104
|
-
if (sepMatch && sepMatch.index !== undefined) {
|
|
1105
|
-
cutStart = sepMatch.index;
|
|
1106
|
-
}
|
|
1107
|
-
// Remove from cutStart to end of file (our content is always appended at the end)
|
|
1108
|
-
const cleaned = content.substring(0, cutStart).trimEnd() + '\n';
|
|
1109
|
-
fs.writeFileSync(action.filePath, cleaned);
|
|
1110
|
-
console.log(` ${c.green('✓')} Stripped Code Guardian rules from ${action.relativePath} ${c.dim('(your content preserved)')}`);
|
|
1111
|
-
removedCount++;
|
|
1191
|
+
catch (error) {
|
|
1192
|
+
console.log(` ${c.red('✗')} Failed to clean ${action.relativePath}: ${error.message}`);
|
|
1112
1193
|
}
|
|
1113
1194
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1195
|
+
}
|
|
1196
|
+
// ── 6. Remove directories ────────────────────────────────────────────────
|
|
1197
|
+
if (existingDirs.length > 0) {
|
|
1198
|
+
console.log(c.bold('\nRemoving directories...\n'));
|
|
1199
|
+
for (const dir of dirsToRemove) {
|
|
1200
|
+
const fullPath = path.join(cwd, dir);
|
|
1201
|
+
if (fs.existsSync(fullPath)) {
|
|
1202
|
+
try {
|
|
1203
|
+
fs.rmSync(fullPath, { recursive: true, force: true });
|
|
1204
|
+
console.log(` ${c.green('✓')} Removed ${dir}/`);
|
|
1205
|
+
removedCount++;
|
|
1206
|
+
}
|
|
1207
|
+
catch (error) {
|
|
1208
|
+
console.log(` ${c.red('✗')} Failed to remove ${dir}: ${error.message}`);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1116
1211
|
}
|
|
1117
1212
|
}
|
|
1118
|
-
// Clean
|
|
1213
|
+
// ── 7. Clean git hooks ───────────────────────────────────────────────────
|
|
1119
1214
|
console.log(c.bold('\nCleaning git hooks...\n'));
|
|
1120
1215
|
const huskyDir = path.join(cwd, '.husky');
|
|
1121
1216
|
const huskyHookFiles = ['pre-commit'];
|
|
@@ -1126,49 +1221,43 @@ async function uninstallCodeGuard() {
|
|
|
1126
1221
|
if (!fs.existsSync(hookPath))
|
|
1127
1222
|
continue;
|
|
1128
1223
|
const content = fs.readFileSync(hookPath, 'utf-8');
|
|
1129
|
-
if (
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1224
|
+
if (content.includes(HOOK_START)) {
|
|
1225
|
+
const startIdx = content.indexOf(HOOK_START);
|
|
1226
|
+
const endIdx = content.indexOf(HOOK_END);
|
|
1227
|
+
if (endIdx === -1)
|
|
1228
|
+
continue;
|
|
1229
|
+
const beforeHook = content.substring(0, startIdx).trimEnd();
|
|
1230
|
+
const afterHook = content.substring(endIdx + HOOK_END.length).trimEnd();
|
|
1231
|
+
const remaining = (beforeHook.replace(/^#!\/usr\/bin\/env\s+sh\s*/, '').trim() + afterHook.trim()).trim();
|
|
1232
|
+
if (remaining.length === 0) {
|
|
1233
|
+
fs.unlinkSync(hookPath);
|
|
1234
|
+
console.log(` ${c.green('✓')} Removed .husky/${hookFile}`);
|
|
1235
|
+
removedCount++;
|
|
1236
|
+
}
|
|
1237
|
+
else {
|
|
1238
|
+
const cleaned = (beforeHook + afterHook).trimEnd() + '\n';
|
|
1239
|
+
fs.writeFileSync(hookPath, cleaned);
|
|
1240
|
+
try {
|
|
1241
|
+
fs.chmodSync(hookPath, '755');
|
|
1242
|
+
}
|
|
1243
|
+
catch { }
|
|
1244
|
+
console.log(` ${c.green('✓')} Stripped Code Guardian hooks from .husky/${hookFile} ${c.dim('(your hooks preserved)')}`);
|
|
1245
|
+
removedCount++;
|
|
1246
|
+
}
|
|
1144
1247
|
}
|
|
1145
1248
|
else {
|
|
1146
|
-
//
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1249
|
+
// No markers — check if the hook was entirely written by Code Guardian
|
|
1250
|
+
// (contains "code-guard check" or "code-guard" references)
|
|
1251
|
+
if (content.includes('code-guard') || content.includes('code_guard')) {
|
|
1252
|
+
fs.unlinkSync(hookPath);
|
|
1253
|
+
console.log(` ${c.green('✓')} Removed .husky/${hookFile}`);
|
|
1254
|
+
removedCount++;
|
|
1151
1255
|
}
|
|
1152
|
-
catch { }
|
|
1153
|
-
console.log(` ${c.green('✓')} Stripped Code Guardian hooks from .husky/${hookFile} ${c.dim('(your hooks preserved)')}`);
|
|
1154
|
-
removedCount++;
|
|
1155
1256
|
}
|
|
1156
1257
|
}
|
|
1157
1258
|
// Clean up empty .husky directory
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
const huskyContents = fs.readdirSync(huskyDir);
|
|
1161
|
-
if (huskyContents.length === 0) {
|
|
1162
|
-
fs.rmdirSync(huskyDir);
|
|
1163
|
-
console.log(` ${c.green('✓')} Removed empty .husky/ directory`);
|
|
1164
|
-
}
|
|
1165
|
-
else {
|
|
1166
|
-
console.log(` ${c.dim('○')} .husky/ has other files — keeping directory`);
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
catch { }
|
|
1170
|
-
}
|
|
1171
|
-
// Remove scripts from package.json
|
|
1259
|
+
cleanupEmptyDir(huskyDir, cwd);
|
|
1260
|
+
// ── 8. Clean package.json scripts ────────────────────────────────────────
|
|
1172
1261
|
console.log(c.bold('\nCleaning package.json scripts...\n'));
|
|
1173
1262
|
const packageJsonPath = path.join(cwd, 'package.json');
|
|
1174
1263
|
if (fs.existsSync(packageJsonPath)) {
|
|
@@ -1183,6 +1272,13 @@ async function uninstallCodeGuard() {
|
|
|
1183
1272
|
removedCount++;
|
|
1184
1273
|
}
|
|
1185
1274
|
}
|
|
1275
|
+
// Also remove "prepare": "husky" if we added it
|
|
1276
|
+
if (packageJson.scripts?.prepare === 'husky' || packageJson.scripts?.prepare === 'husky install') {
|
|
1277
|
+
delete packageJson.scripts.prepare;
|
|
1278
|
+
console.log(` ${c.green('✓')} Removed script: prepare (husky)`);
|
|
1279
|
+
scriptRemoved = true;
|
|
1280
|
+
removedCount++;
|
|
1281
|
+
}
|
|
1186
1282
|
if (scriptRemoved) {
|
|
1187
1283
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
1188
1284
|
console.log(` ${c.green('✓')} Updated package.json`);
|
|
@@ -1195,16 +1291,31 @@ async function uninstallCodeGuard() {
|
|
|
1195
1291
|
console.log(` ${c.red('✗')} Failed to clean package.json: ${error.message}`);
|
|
1196
1292
|
}
|
|
1197
1293
|
}
|
|
1294
|
+
// ── Done ─────────────────────────────────────────────────────────────────
|
|
1198
1295
|
console.log('');
|
|
1199
1296
|
console.log(c.green(`✅ Cleanup complete! Removed ${removedCount} item(s).`));
|
|
1200
|
-
if (hasBackup) {
|
|
1201
|
-
console.log(c.blue(`📦 Backup saved to: ${path.basename(backupDir)}/`));
|
|
1202
|
-
}
|
|
1203
1297
|
console.log('');
|
|
1204
1298
|
console.log(c.dim('To completely remove the package, run:'));
|
|
1205
1299
|
console.log(c.dim(' npm uninstall @hatem427/code-guard-ci'));
|
|
1206
1300
|
console.log('');
|
|
1207
1301
|
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Recursively remove empty directories up to (but not including) the project root.
|
|
1304
|
+
*/
|
|
1305
|
+
function cleanupEmptyDir(dirPath, projectRoot) {
|
|
1306
|
+
if (!fs.existsSync(dirPath) || dirPath === projectRoot)
|
|
1307
|
+
return;
|
|
1308
|
+
try {
|
|
1309
|
+
const contents = fs.readdirSync(dirPath);
|
|
1310
|
+
if (contents.length === 0) {
|
|
1311
|
+
fs.rmdirSync(dirPath);
|
|
1312
|
+
// Check parent too (e.g., .cursor/rules/ → .cursor/)
|
|
1313
|
+
const parent = path.dirname(dirPath);
|
|
1314
|
+
cleanupEmptyDir(parent, projectRoot);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
catch { }
|
|
1318
|
+
}
|
|
1208
1319
|
// ── Main ────────────────────────────────────────────────────────────────────
|
|
1209
1320
|
(async () => {
|
|
1210
1321
|
switch (command) {
|