mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 14:19:56 +00:00
fix: address PR #373 review feedback
- postuninstall.js: add file logging for debugging on error - profile-hook-injector.ts: use 'wx' flag for atomic marker creation - profile-hook-injector.ts: include parse error message in debug log - install-command.ts: use actual counts for consistent semantics - Windows tests: align Section 7 with per-profile hook architecture
This commit is contained in:
@@ -34,8 +34,14 @@ function cleanupCcsFiles() {
|
|||||||
// Note: Do NOT touch ~/.claude/settings.json
|
// Note: Do NOT touch ~/.claude/settings.json
|
||||||
// Per-profile hooks in ~/.ccs/*.settings.json will be cleaned up
|
// Per-profile hooks in ~/.ccs/*.settings.json will be cleaned up
|
||||||
// when the user removes ~/.ccs/ directory.
|
// when the user removes ~/.ccs/ directory.
|
||||||
} catch {
|
} catch (err) {
|
||||||
// Silent fail - not critical
|
// Silent fail - not critical, but log for debugging
|
||||||
|
try {
|
||||||
|
const logPath = path.join(CCS_DIR, 'uninstall.log');
|
||||||
|
fs.appendFileSync(logPath, `${new Date().toISOString()}: ${err.message}\n`);
|
||||||
|
} catch {
|
||||||
|
// Ignore logging failures
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,15 +39,13 @@ export async function handleUninstallCommand(): Promise<void> {
|
|||||||
const hookRemoved = uninstallWebSearchHook();
|
const hookRemoved = uninstallWebSearchHook();
|
||||||
if (hookRemoved) {
|
if (hookRemoved) {
|
||||||
console.log(ok('Removed WebSearch hook'));
|
console.log(ok('Removed WebSearch hook'));
|
||||||
removed++;
|
removed += 1; // Count as 1 item (the hook file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Remove symlinks from ~/.claude/
|
// 2. Remove symlinks from ~/.claude/
|
||||||
const symlinkManager = new ClaudeSymlinkManager();
|
const symlinkManager = new ClaudeSymlinkManager();
|
||||||
const symlinksRemoved = symlinkManager.uninstall();
|
const symlinksRemoved = symlinkManager.uninstall();
|
||||||
if (symlinksRemoved > 0) {
|
removed += symlinksRemoved; // Add actual count of symlinks removed
|
||||||
removed++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Summary
|
// 3. Summary
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ function migrateGlobalHook(): void {
|
|||||||
if (!fs.existsSync(ccsDir)) {
|
if (!fs.existsSync(ccsDir)) {
|
||||||
fs.mkdirSync(ccsDir, { recursive: true, mode: 0o700 });
|
fs.mkdirSync(ccsDir, { recursive: true, mode: 0o700 });
|
||||||
}
|
}
|
||||||
// Create marker file
|
// Create marker file atomically (wx = fail if exists, prevents race condition)
|
||||||
fs.writeFileSync(markerPath, new Date().toISOString(), 'utf8');
|
fs.writeFileSync(markerPath, new Date().toISOString(), { encoding: 'utf8', flag: 'wx' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (process.env.CCS_DEBUG) {
|
if (process.env.CCS_DEBUG) {
|
||||||
console.error(warn(`Migration failed: ${(error as Error).message}`));
|
console.error(warn(`Migration failed: ${(error as Error).message}`));
|
||||||
@@ -114,9 +114,11 @@ export function ensureProfileHooks(profileName: string): boolean {
|
|||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(settingsPath, 'utf8');
|
const content = fs.readFileSync(settingsPath, 'utf8');
|
||||||
settings = JSON.parse(content);
|
settings = JSON.parse(content);
|
||||||
} catch {
|
} catch (parseError) {
|
||||||
if (process.env.CCS_DEBUG) {
|
if (process.env.CCS_DEBUG) {
|
||||||
console.error(warn(`Malformed ${profileName}.settings.json - creating fresh hooks`));
|
console.error(
|
||||||
|
warn(`Malformed ${profileName}.settings.json: ${(parseError as Error).message}`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Continue with empty settings, will add hooks
|
// Continue with empty settings, will add hooks
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -428,42 +428,15 @@ Test-Case "Edge case: Missing parent directories" "Handles missing .claude direc
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# TEST SECTION 7: SETTINGS.JSON HOOK CLEANUP
|
# TEST SECTION 7: PER-PROFILE HOOK BEHAVIOR (Global settings untouched)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-ColorOutput "========================================" "Yellow"
|
Write-ColorOutput "========================================" "Yellow"
|
||||||
Write-ColorOutput "SECTION 7: SETTINGS.JSON HOOK CLEANUP" "Yellow"
|
Write-ColorOutput "SECTION 7: PER-PROFILE HOOK BEHAVIOR" "Yellow"
|
||||||
Write-ColorOutput "========================================" "Yellow"
|
Write-ColorOutput "========================================" "Yellow"
|
||||||
|
|
||||||
Test-Case "Hook cleanup: Removes CCS WebSearch hook from settings.json" "Hook removed" {
|
Test-Case "Per-profile: Uninstall does NOT touch global settings.json" "Global settings unchanged" {
|
||||||
# Setup: Create settings.json with CCS hook
|
|
||||||
New-Item -ItemType Directory -Path $TestClaudeDir -Force | Out-Null
|
|
||||||
$settingsPath = Join-Path $TestClaudeDir "settings.json"
|
|
||||||
$settingsContent = @'
|
|
||||||
{
|
|
||||||
"hooks": {
|
|
||||||
"PreToolUse": [
|
|
||||||
{ "matcher": "WebSearch", "hooks": [{ "command": "node ~/.ccs/hooks/websearch-transformer.cjs" }] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'@
|
|
||||||
Set-Content -Path $settingsPath -Value $settingsContent
|
|
||||||
|
|
||||||
# Install then uninstall
|
|
||||||
$env:HOME = $TestHome
|
|
||||||
try {
|
|
||||||
& $CcsPath --install | Out-Null
|
|
||||||
& $CcsPath --uninstall | Out-Null
|
|
||||||
} catch { }
|
|
||||||
|
|
||||||
# Check hook removed
|
|
||||||
$content = Get-Content -Path $settingsPath -Raw
|
|
||||||
-not ($content -match "websearch-transformer")
|
|
||||||
}
|
|
||||||
|
|
||||||
Test-Case "Hook cleanup: Preserves user-defined WebSearch hooks" "User hooks kept" {
|
|
||||||
# Setup: Create settings.json with user hook
|
# Setup: Create settings.json with user hook
|
||||||
New-Item -ItemType Directory -Path $TestClaudeDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $TestClaudeDir -Force | Out-Null
|
||||||
$settingsPath = Join-Path $TestClaudeDir "settings.json"
|
$settingsPath = Join-Path $TestClaudeDir "settings.json"
|
||||||
@@ -484,13 +457,13 @@ Test-Case "Hook cleanup: Preserves user-defined WebSearch hooks" "User hooks kep
|
|||||||
& $CcsPath --uninstall | Out-Null
|
& $CcsPath --uninstall | Out-Null
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
# Check user hook preserved
|
# Verify global settings unchanged
|
||||||
$content = Get-Content -Path $settingsPath -Raw
|
$content = Get-Content -Path $settingsPath -Raw
|
||||||
$content -match "my-custom-hook"
|
$content -match "my-custom-hook"
|
||||||
}
|
}
|
||||||
|
|
||||||
Test-Case "Hook cleanup: Handles mixed CCS and user hooks" "Only CCS hook removed" {
|
Test-Case "Per-profile: Uninstall preserves CCS hooks in global settings (not touched)" "Both hooks preserved" {
|
||||||
# Setup: Create settings.json with both CCS and user hooks
|
# Setup: Create settings.json with CCS hook (old format - shouldn't be touched)
|
||||||
New-Item -ItemType Directory -Path $TestClaudeDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $TestClaudeDir -Force | Out-Null
|
||||||
$settingsPath = Join-Path $TestClaudeDir "settings.json"
|
$settingsPath = Join-Path $TestClaudeDir "settings.json"
|
||||||
$settingsContent = @'
|
$settingsContent = @'
|
||||||
@@ -498,28 +471,41 @@ Test-Case "Hook cleanup: Handles mixed CCS and user hooks" "Only CCS hook remove
|
|||||||
"hooks": {
|
"hooks": {
|
||||||
"PreToolUse": [
|
"PreToolUse": [
|
||||||
{ "matcher": "WebSearch", "hooks": [{ "command": "node ~/.ccs/hooks/websearch-transformer.cjs" }] },
|
{ "matcher": "WebSearch", "hooks": [{ "command": "node ~/.ccs/hooks/websearch-transformer.cjs" }] },
|
||||||
{ "matcher": "WebSearch", "hooks": [{ "command": "my-custom-hook.sh" }] },
|
{ "matcher": "WebSearch", "hooks": [{ "command": "my-custom-hook.sh" }] }
|
||||||
{ "matcher": "Bash", "hooks": [{ "command": "bash-hook.sh" }] }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'@
|
'@
|
||||||
Set-Content -Path $settingsPath -Value $settingsContent
|
Set-Content -Path $settingsPath -Value $settingsContent
|
||||||
|
|
||||||
|
# Uninstall - should NOT modify global settings
|
||||||
|
$env:HOME = $TestHome
|
||||||
|
try {
|
||||||
|
& $CcsPath --uninstall | Out-Null
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
# Both hooks should still be in global settings (not touched)
|
||||||
|
$content = Get-Content -Path $settingsPath -Raw
|
||||||
|
($content -match "websearch-transformer") -and ($content -match "my-custom-hook")
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Case "Per-profile: Migration marker cleanup on uninstall" "Marker file removed" {
|
||||||
|
# Setup: Create ~/.ccs/.hook-migrated marker
|
||||||
|
$ccsDir = Join-Path $TestHome ".ccs"
|
||||||
|
New-Item -ItemType Directory -Path $ccsDir -Force | Out-Null
|
||||||
|
Set-Content -Path (Join-Path $ccsDir ".hook-migrated") -Value "2026-01-25T00:00:00.000Z"
|
||||||
|
|
||||||
# Uninstall
|
# Uninstall
|
||||||
$env:HOME = $TestHome
|
$env:HOME = $TestHome
|
||||||
try {
|
try {
|
||||||
& $CcsPath --uninstall | Out-Null
|
& $CcsPath --uninstall | Out-Null
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
# Check CCS hook removed, user hooks preserved
|
# Verify marker removed
|
||||||
$content = Get-Content -Path $settingsPath -Raw
|
-not (Test-Path (Join-Path $ccsDir ".hook-migrated"))
|
||||||
(-not ($content -match "websearch-transformer")) -and
|
|
||||||
($content -match "my-custom-hook") -and
|
|
||||||
($content -match "bash-hook")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Test-Case "Hook cleanup: Handles missing settings.json" "No error when settings.json missing" {
|
Test-Case "Per-profile: Handles missing settings.json" "No error when settings.json missing" {
|
||||||
# Ensure settings.json doesn't exist
|
# Ensure settings.json doesn't exist
|
||||||
$settingsPath = Join-Path $TestClaudeDir "settings.json"
|
$settingsPath = Join-Path $TestClaudeDir "settings.json"
|
||||||
if (Test-Path $settingsPath) {
|
if (Test-Path $settingsPath) {
|
||||||
|
|||||||
Reference in New Issue
Block a user