windowsweep 1.1.0 → 1.3.0
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/AI-INTEGRATION-GUIDE.md +16 -12
- package/CHANGELOG.md +82 -0
- package/LICENSE +21 -21
- package/README.md +104 -69
- package/VERSION +1 -1
- package/lib/actions.ps1 +212 -212
- package/lib/config.ps1 +108 -99
- package/lib/constants.ps1 +103 -93
- package/lib/external.ps1 +87 -0
- package/lib/fs.ps1 +189 -180
- package/lib/log.ps1 +140 -140
- package/lib/safety.ps1 +467 -493
- package/lib/scan.ps1 +109 -100
- package/lib/ui.ps1 +275 -275
- package/modules/android_avd.ps1 +36 -36
- package/modules/app_caches.ps1 +58 -58
- package/modules/browsers.ps1 +33 -33
- package/modules/build_tools.ps1 +35 -35
- package/modules/crash_trap.ps1 +89 -89
- package/modules/disk_usage.ps1 +49 -49
- package/modules/docker.ps1 +93 -93
- package/modules/editors.ps1 +150 -150
- package/modules/globals_audit.ps1 +236 -236
- package/modules/health.ps1 +101 -101
- package/modules/installed_programs.ps1 +118 -118
- package/modules/menu.ps1 +63 -63
- package/modules/notify.ps1 +60 -60
- package/modules/orphaned_appdata.ps1 +189 -189
- package/modules/personal.ps1 +126 -126
- package/modules/pkg_managers.ps1 +73 -73
- package/modules/projects.ps1 +160 -160
- package/modules/release_helpers.ps1 +438 -438
- package/modules/reports.ps1 +212 -212
- package/modules/runner.ps1 +246 -239
- package/modules/self_test_contract.ps1 +195 -0
- package/modules/self_test_extra.ps1 +366 -360
- package/modules/startup_audit.ps1 +126 -126
- package/modules/system_admin.ps1 +220 -220
- package/modules/test_runners.ps1 +24 -24
- package/modules/walkthrough.ps1 +80 -79
- package/modules/windows_user.ps1 +118 -118
- package/package.json +3 -3
- package/windowsweep.ps1 +322 -316
package/modules/personal.ps1
CHANGED
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
# personal.ps1 - sections 18 and 19: personal files in Downloads. Interactive only; the Recycle Bin is the destination.
|
|
2
|
-
|
|
3
|
-
$Script:WS_PARTIAL_PATTERNS = @('*.crdownload', '*.part', '*.partial', '*.fdmdownload', '*.opdownload', '*.!ut', '*.aria2', '*.download', '*.bc!', '*.tmp')
|
|
4
|
-
|
|
5
|
-
function Get-PersonalRoots {
|
|
6
|
-
$roots = @()
|
|
7
|
-
$d = Join-Path $Script:P.U 'Downloads'
|
|
8
|
-
if (Test-DirPresent $d) { $roots += (Get-FullPath $d) }
|
|
9
|
-
return $roots
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function Get-Targets18 {
|
|
13
|
-
$t = @()
|
|
14
|
-
foreach ($r in (Get-PersonalRoots)) { $t += (New-Target 18 'Partial / orphan downloads' $r -Kind cmd -Note ($Script:WS_PARTIAL_PATTERNS -join ' ')) }
|
|
15
|
-
return $t
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function Get-Targets19 {
|
|
19
|
-
$t = @()
|
|
20
|
-
foreach ($r in (Get-PersonalRoots)) { $t += (New-Target 19 "Files >= $($Script:WS.LargeFileMb) MB idle $($Script:WS.Days)+ days" $r -Kind cmd -Note 'moved to the Recycle Bin after you pick them') }
|
|
21
|
-
return $t
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function Get-FilesUnder {
|
|
25
|
-
<# .SYNOPSIS Files under Root up to MaxDepth, never following links, never entering protected folders. #>
|
|
26
|
-
param([string] $Root, [int] $MaxDepth = 4)
|
|
27
|
-
$out = New-Object System.Collections.Generic.List[object]
|
|
28
|
-
$stack = New-Object System.Collections.Generic.Stack[object]
|
|
29
|
-
$stack.Push(@($Root, 0))
|
|
30
|
-
while ($stack.Count -gt 0) {
|
|
31
|
-
$item = $stack.Pop()
|
|
32
|
-
foreach ($e in (Get-ChildEntries ([string]$item[0]))) {
|
|
33
|
-
if (Test-ReparsePoint $e) { continue }
|
|
34
|
-
if ($e -is [IO.DirectoryInfo]) { if ([int]$item[1] -lt $MaxDepth) { $stack.Push(@((Remove-LongPrefix $e.FullName), [int]$item[1] + 1)) }; continue }
|
|
35
|
-
$out.Add($e)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return $out
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function Get-PartialDownloads {
|
|
42
|
-
$list = @()
|
|
43
|
-
foreach ($root in (Get-PersonalRoots)) {
|
|
44
|
-
foreach ($f in (Get-FilesUnder -Root $root -MaxDepth 4)) {
|
|
45
|
-
foreach ($pat in $Script:WS_PARTIAL_PATTERNS) {
|
|
46
|
-
if ($f.Name -like $pat) { $list += [pscustomobject]@{ Path = (Remove-LongPrefix $f.FullName); Bytes = [long]$f.Length; Idle = [int][math]::Floor(([datetime]::UtcNow - (Get-NewestTimestampUtc $f)).TotalDays); Root = $root }; break }
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return @($list | Sort-Object Bytes -Descending)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function Get-LargeStaleFiles {
|
|
54
|
-
$ws = $Script:WS
|
|
55
|
-
$min = [long]$ws.LargeFileMb * 1MB
|
|
56
|
-
$list = @()
|
|
57
|
-
foreach ($root in (Get-PersonalRoots)) {
|
|
58
|
-
foreach ($f in (Get-FilesUnder -Root $root -MaxDepth 4)) {
|
|
59
|
-
if ([long]$f.Length -lt $min) { continue }
|
|
60
|
-
$idle = [int][math]::Floor(([datetime]::UtcNow - (Get-NewestTimestampUtc $f)).TotalDays)
|
|
61
|
-
if ($idle -lt $ws.Days) { continue }
|
|
62
|
-
$list += [pscustomobject]@{ Path = (Remove-LongPrefix $f.FullName); Bytes = [long]$f.Length; Idle = $idle; Root = $root }
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return @($list | Sort-Object Bytes -Descending | Select-Object -First 100)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function Show-FileTable {
|
|
69
|
-
param([object[]] $Rows)
|
|
70
|
-
Write-UiLine (" {0,3} {1,10} {2,6} {3}" -f '#', 'SIZE', 'IDLE', 'FILE') 'White'
|
|
71
|
-
$i = 1
|
|
72
|
-
foreach ($r in $Rows) { Write-UiLine (" {0,3} {1,10} {2,5}d {3}" -f $i, (Format-Bytes $r.Bytes), $r.Idle, $r.Path) 'Gray'; $i++ }
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function Invoke-PersonalPicker {
|
|
76
|
-
param([object[]] $Rows, [string] $Prompt, [int] $Section = 0)
|
|
77
|
-
$ws = $Script:WS
|
|
78
|
-
$Rows = @($Rows)
|
|
79
|
-
if ($Rows.Count -eq 0) { Write-Info 'none found'; return }
|
|
80
|
-
Show-FileTable $Rows
|
|
81
|
-
$total = [long]0
|
|
82
|
-
$i = 1
|
|
83
|
-
foreach ($r in $Rows) { $total += $r.Bytes; Add-JsonCandidate -Section $Section -Index $i -Path $r.Path -Bytes $r.Bytes -IdleDays $r.Idle; $i++ }
|
|
84
|
-
Write-Info ("total: " + (Format-Bytes $total))
|
|
85
|
-
$dest = 'the Recycle Bin'
|
|
86
|
-
if ($ws.Permanent) { $dest = 'PERMANENT deletion (--permanent)' }
|
|
87
|
-
Write-Note "selected files go to $dest"
|
|
88
|
-
$picks = @(Read-MultiSelect -Total $Rows.Count -NoAutoYes -Candidates @($Rows | ForEach-Object { $_.Path }))
|
|
89
|
-
if ($picks.Count -eq 0) { Write-Info 'nothing selected'; return }
|
|
90
|
-
if (-not $ws.DryRun) {
|
|
91
|
-
# Personal files never auto-confirm: --yes does not apply here. An explicit --select / --select-file
|
|
92
|
-
# choice does, because it is a person naming these exact files in advance.
|
|
93
|
-
if (-not (Confirm-Ui -Prompt "$Prompt ($($picks.Count) file(s))?" -Default 'n' -NoAutoYes -ScriptedOk)) { Write-Info 'skipped'; return }
|
|
94
|
-
}
|
|
95
|
-
foreach ($k in $picks) { $null = Send-ToRecycleBin -Path $Rows[$k - 1].Path -Within $Rows[$k - 1].Root }
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function Invoke-Section18 {
|
|
99
|
-
Write-SectionIntro @(
|
|
100
|
-
'Half-finished downloads left behind by browsers and download managers (.crdownload, .part, .tmp, ...).',
|
|
101
|
-
'You pick what goes; it lands in the Recycle Bin. Desktop and cloud-synced folders are never scanned.'
|
|
102
|
-
)
|
|
103
|
-
if (@(Get-PersonalRoots).Count -eq 0) { Write-Info 'no Downloads folder found'; return }
|
|
104
|
-
Invoke-PersonalPicker -Rows @(Get-PartialDownloads) -Prompt 'Move the selected partial downloads to the Recycle Bin' -Section 18
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function Invoke-Section19 {
|
|
108
|
-
$ws = $Script:WS
|
|
109
|
-
Write-SectionIntro @(
|
|
110
|
-
"Files of $($ws.LargeFileMb) MB or more in Downloads that nothing has touched for $($ws.Days)+ days: old installers, ISOs,",
|
|
111
|
-
'archives you already extracted. You pick what goes; it lands in the Recycle Bin.'
|
|
112
|
-
)
|
|
113
|
-
if (@(Get-PersonalRoots).Count -eq 0) { Write-Info 'no Downloads folder found'; return }
|
|
114
|
-
Invoke-PersonalPicker -Rows @(Get-LargeStaleFiles) -Prompt 'Move the selected files to the Recycle Bin' -Section 19
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function Show-PersonalScan {
|
|
118
|
-
<# .SYNOPSIS Read-only listing used by --scan. #>
|
|
119
|
-
Write-Box 'Personal files (read-only)' 'Sections 18 and 19 are interactive only - nothing here is deleted'
|
|
120
|
-
Write-Section 'Partial / orphan downloads'
|
|
121
|
-
$p = @(Get-PartialDownloads)
|
|
122
|
-
if ($p.Count -eq 0) { Write-Info 'none found' } else { Show-FileTable @($p | Select-Object -First 30) }
|
|
123
|
-
Write-Section "Files >= $($Script:WS.LargeFileMb) MB idle $($Script:WS.Days)+ days in Downloads"
|
|
124
|
-
$l = @(Get-LargeStaleFiles)
|
|
125
|
-
if ($l.Count -eq 0) { Write-Info 'none found' } else { Show-FileTable @($l | Select-Object -First 30) }
|
|
126
|
-
}
|
|
1
|
+
# personal.ps1 - sections 18 and 19: personal files in Downloads. Interactive only; the Recycle Bin is the destination.
|
|
2
|
+
|
|
3
|
+
$Script:WS_PARTIAL_PATTERNS = @('*.crdownload', '*.part', '*.partial', '*.fdmdownload', '*.opdownload', '*.!ut', '*.aria2', '*.download', '*.bc!', '*.tmp')
|
|
4
|
+
|
|
5
|
+
function Get-PersonalRoots {
|
|
6
|
+
$roots = @()
|
|
7
|
+
$d = Join-Path $Script:P.U 'Downloads'
|
|
8
|
+
if (Test-DirPresent $d) { $roots += (Get-FullPath $d) }
|
|
9
|
+
return $roots
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function Get-Targets18 {
|
|
13
|
+
$t = @()
|
|
14
|
+
foreach ($r in (Get-PersonalRoots)) { $t += (New-Target 18 'Partial / orphan downloads' $r -Kind cmd -Note ($Script:WS_PARTIAL_PATTERNS -join ' ')) }
|
|
15
|
+
return $t
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function Get-Targets19 {
|
|
19
|
+
$t = @()
|
|
20
|
+
foreach ($r in (Get-PersonalRoots)) { $t += (New-Target 19 "Files >= $($Script:WS.LargeFileMb) MB idle $($Script:WS.Days)+ days" $r -Kind cmd -Note 'moved to the Recycle Bin after you pick them') }
|
|
21
|
+
return $t
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function Get-FilesUnder {
|
|
25
|
+
<# .SYNOPSIS Files under Root up to MaxDepth, never following links, never entering protected folders. #>
|
|
26
|
+
param([string] $Root, [int] $MaxDepth = 4)
|
|
27
|
+
$out = New-Object System.Collections.Generic.List[object]
|
|
28
|
+
$stack = New-Object System.Collections.Generic.Stack[object]
|
|
29
|
+
$stack.Push(@($Root, 0))
|
|
30
|
+
while ($stack.Count -gt 0) {
|
|
31
|
+
$item = $stack.Pop()
|
|
32
|
+
foreach ($e in (Get-ChildEntries ([string]$item[0]))) {
|
|
33
|
+
if (Test-ReparsePoint $e) { continue }
|
|
34
|
+
if ($e -is [IO.DirectoryInfo]) { if ([int]$item[1] -lt $MaxDepth) { $stack.Push(@((Remove-LongPrefix $e.FullName), [int]$item[1] + 1)) }; continue }
|
|
35
|
+
$out.Add($e)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return $out
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function Get-PartialDownloads {
|
|
42
|
+
$list = @()
|
|
43
|
+
foreach ($root in (Get-PersonalRoots)) {
|
|
44
|
+
foreach ($f in (Get-FilesUnder -Root $root -MaxDepth 4)) {
|
|
45
|
+
foreach ($pat in $Script:WS_PARTIAL_PATTERNS) {
|
|
46
|
+
if ($f.Name -like $pat) { $list += [pscustomobject]@{ Path = (Remove-LongPrefix $f.FullName); Bytes = [long]$f.Length; Idle = [int][math]::Floor(([datetime]::UtcNow - (Get-NewestTimestampUtc $f)).TotalDays); Root = $root }; break }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return @($list | Sort-Object Bytes -Descending)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function Get-LargeStaleFiles {
|
|
54
|
+
$ws = $Script:WS
|
|
55
|
+
$min = [long]$ws.LargeFileMb * 1MB
|
|
56
|
+
$list = @()
|
|
57
|
+
foreach ($root in (Get-PersonalRoots)) {
|
|
58
|
+
foreach ($f in (Get-FilesUnder -Root $root -MaxDepth 4)) {
|
|
59
|
+
if ([long]$f.Length -lt $min) { continue }
|
|
60
|
+
$idle = [int][math]::Floor(([datetime]::UtcNow - (Get-NewestTimestampUtc $f)).TotalDays)
|
|
61
|
+
if ($idle -lt $ws.Days) { continue }
|
|
62
|
+
$list += [pscustomobject]@{ Path = (Remove-LongPrefix $f.FullName); Bytes = [long]$f.Length; Idle = $idle; Root = $root }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return @($list | Sort-Object Bytes -Descending | Select-Object -First 100)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function Show-FileTable {
|
|
69
|
+
param([object[]] $Rows)
|
|
70
|
+
Write-UiLine (" {0,3} {1,10} {2,6} {3}" -f '#', 'SIZE', 'IDLE', 'FILE') 'White'
|
|
71
|
+
$i = 1
|
|
72
|
+
foreach ($r in $Rows) { Write-UiLine (" {0,3} {1,10} {2,5}d {3}" -f $i, (Format-Bytes $r.Bytes), $r.Idle, $r.Path) 'Gray'; $i++ }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function Invoke-PersonalPicker {
|
|
76
|
+
param([object[]] $Rows, [string] $Prompt, [int] $Section = 0)
|
|
77
|
+
$ws = $Script:WS
|
|
78
|
+
$Rows = @($Rows)
|
|
79
|
+
if ($Rows.Count -eq 0) { Write-Info 'none found'; return }
|
|
80
|
+
Show-FileTable $Rows
|
|
81
|
+
$total = [long]0
|
|
82
|
+
$i = 1
|
|
83
|
+
foreach ($r in $Rows) { $total += $r.Bytes; Add-JsonCandidate -Section $Section -Index $i -Path $r.Path -Bytes $r.Bytes -IdleDays $r.Idle; $i++ }
|
|
84
|
+
Write-Info ("total: " + (Format-Bytes $total))
|
|
85
|
+
$dest = 'the Recycle Bin'
|
|
86
|
+
if ($ws.Permanent) { $dest = 'PERMANENT deletion (--permanent)' }
|
|
87
|
+
Write-Note "selected files go to $dest"
|
|
88
|
+
$picks = @(Read-MultiSelect -Total $Rows.Count -NoAutoYes -Candidates @($Rows | ForEach-Object { $_.Path }))
|
|
89
|
+
if ($picks.Count -eq 0) { Write-Info 'nothing selected'; return }
|
|
90
|
+
if (-not $ws.DryRun) {
|
|
91
|
+
# Personal files never auto-confirm: --yes does not apply here. An explicit --select / --select-file
|
|
92
|
+
# choice does, because it is a person naming these exact files in advance.
|
|
93
|
+
if (-not (Confirm-Ui -Prompt "$Prompt ($($picks.Count) file(s))?" -Default 'n' -NoAutoYes -ScriptedOk)) { Write-Info 'skipped'; return }
|
|
94
|
+
}
|
|
95
|
+
foreach ($k in $picks) { $null = Send-ToRecycleBin -Path $Rows[$k - 1].Path -Within $Rows[$k - 1].Root }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function Invoke-Section18 {
|
|
99
|
+
Write-SectionIntro @(
|
|
100
|
+
'Half-finished downloads left behind by browsers and download managers (.crdownload, .part, .tmp, ...).',
|
|
101
|
+
'You pick what goes; it lands in the Recycle Bin. Desktop and cloud-synced folders are never scanned.'
|
|
102
|
+
)
|
|
103
|
+
if (@(Get-PersonalRoots).Count -eq 0) { Write-Info 'no Downloads folder found'; return }
|
|
104
|
+
Invoke-PersonalPicker -Rows @(Get-PartialDownloads) -Prompt 'Move the selected partial downloads to the Recycle Bin' -Section 18
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function Invoke-Section19 {
|
|
108
|
+
$ws = $Script:WS
|
|
109
|
+
Write-SectionIntro @(
|
|
110
|
+
"Files of $($ws.LargeFileMb) MB or more in Downloads that nothing has touched for $($ws.Days)+ days: old installers, ISOs,",
|
|
111
|
+
'archives you already extracted. You pick what goes; it lands in the Recycle Bin.'
|
|
112
|
+
)
|
|
113
|
+
if (@(Get-PersonalRoots).Count -eq 0) { Write-Info 'no Downloads folder found'; return }
|
|
114
|
+
Invoke-PersonalPicker -Rows @(Get-LargeStaleFiles) -Prompt 'Move the selected files to the Recycle Bin' -Section 19
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function Show-PersonalScan {
|
|
118
|
+
<# .SYNOPSIS Read-only listing used by --scan. #>
|
|
119
|
+
Write-Box 'Personal files (read-only)' 'Sections 18 and 19 are interactive only - nothing here is deleted'
|
|
120
|
+
Write-Section 'Partial / orphan downloads'
|
|
121
|
+
$p = @(Get-PartialDownloads)
|
|
122
|
+
if ($p.Count -eq 0) { Write-Info 'none found' } else { Show-FileTable @($p | Select-Object -First 30) }
|
|
123
|
+
Write-Section "Files >= $($Script:WS.LargeFileMb) MB idle $($Script:WS.Days)+ days in Downloads"
|
|
124
|
+
$l = @(Get-LargeStaleFiles)
|
|
125
|
+
if ($l.Count -eq 0) { Write-Info 'none found' } else { Show-FileTable @($l | Select-Object -First 30) }
|
|
126
|
+
}
|
package/modules/pkg_managers.ps1
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
# pkg_managers.ps1 - section 1: package-manager caches. All regenerable; the idle gate keeps recent work fast.
|
|
2
|
-
|
|
3
|
-
function Get-Targets01 {
|
|
4
|
-
$P = $Script:P
|
|
5
|
-
$npm = Get-NpmCacheDir
|
|
6
|
-
return @(
|
|
7
|
-
(New-Target 1 'npm cache (_cacache)' (Join-Path $npm '_cacache') -Mode prune -Dev $true -Note 'regenerated on the next npm install')
|
|
8
|
-
(New-Target 1 'npx package cache (_npx)' (Join-Path $npm '_npx') -Mode prune -Dev $true)
|
|
9
|
-
(New-Target 1 'npm logs' (Join-Path $npm '_logs') -Mode clear-old -Days 7)
|
|
10
|
-
(New-Target 1 'Yarn v1 cache' "$($P.L)\Yarn\Cache" -Mode prune -Dev $true -Note 'yarn classic offline mirror')
|
|
11
|
-
(New-Target 1 'Yarn Berry global cache' "$($P.L)\Yarn\Berry\cache" -Mode prune -Dev $true)
|
|
12
|
-
(New-Target 1 'pnpm content store' "$($P.L)\pnpm\store" -Mode prune -Dev $true -Note 'pnpm store prune is used instead when pnpm is installed')
|
|
13
|
-
(New-Target 1 'pnpm metadata cache' "$($P.L)\pnpm-cache" -Mode prune -Dev $true)
|
|
14
|
-
(New-Target 1 'bun install cache' "$($P.U)\.bun\install\cache" -Mode prune -Dev $true -Note 'global bun packages stay')
|
|
15
|
-
(New-Target 1 'deno deps cache' "$($P.L)\deno\deps" -Mode prune -Dev $true -Note 'installed deno scripts (~\.deno\bin) stay')
|
|
16
|
-
(New-Target 1 'deno gen cache' "$($P.L)\deno\gen" -Mode prune -Dev $true)
|
|
17
|
-
(New-Target 1 'deno npm cache' "$($P.L)\deno\npm" -Mode prune -Dev $true)
|
|
18
|
-
(New-Target 1 'deno remote cache' "$($P.L)\deno\remote" -Mode prune -Dev $true)
|
|
19
|
-
(New-Target 1 'pip cache' "$($P.L)\pip\cache" -Mode prune -Dev $true)
|
|
20
|
-
(New-Target 1 'uv cache' "$($P.L)\uv\cache" -Mode prune -Dev $true -Note 'uv cache prune runs first when uv is installed')
|
|
21
|
-
(New-Target 1 'poetry cache' "$($P.L)\pypoetry\Cache" -Mode prune -Dev $true)
|
|
22
|
-
(New-Target 1 'Composer cache (files)' "$($P.L)\Composer\files" -Mode prune -Dev $true)
|
|
23
|
-
(New-Target 1 'Composer cache (roaming)' "$($P.A)\Composer\cache" -Mode prune -Dev $true)
|
|
24
|
-
(New-Target 1 'NuGet http cache' "$($P.L)\NuGet\v3-cache" -Mode clear)
|
|
25
|
-
(New-Target 1 'NuGet plugins cache' "$($P.L)\NuGet\plugins-cache" -Mode clear)
|
|
26
|
-
(New-Target 1 'NuGet scratch' "$($P.L)\Temp\NuGetScratch" -Mode clear)
|
|
27
|
-
(New-Target 1 'NuGet global packages' "$($P.U)\.nuget\packages" -Mode prune -Dev $true -Note 'restored on the next dotnet restore')
|
|
28
|
-
(New-Target 1 'Cargo registry cache' "$($P.U)\.cargo\registry\cache" -Mode prune -Dev $true -Note 'installed cargo binaries (~\.cargo\bin) stay')
|
|
29
|
-
(New-Target 1 'Cargo registry sources' "$($P.U)\.cargo\registry\src" -Mode prune -Dev $true)
|
|
30
|
-
(New-Target 1 'Cargo git checkouts' "$($P.U)\.cargo\git\checkouts" -Mode prune -Dev $true)
|
|
31
|
-
(New-Target 1 'Go build cache' "$($P.L)\go-build" -Mode prune -Dev $true)
|
|
32
|
-
(New-Target 1 'Go module download cache' "$($P.U)\go\pkg\mod\cache\download" -Mode prune -Dev $true)
|
|
33
|
-
(New-Target 1 'Dart/Flutter pub cache' "$($P.L)\Pub\Cache" -Mode prune -Dev $true -Note 'redownloaded by flutter pub get')
|
|
34
|
-
(New-Target 1 'Dart/Flutter pub cache (legacy)' "$($P.A)\Pub\Cache" -Mode prune -Dev $true)
|
|
35
|
-
(New-Target 1 'Dart analysis server cache' "$($P.U)\.dartServer" -Mode prune -Dev $true)
|
|
36
|
-
(New-Target 1 'Electron download cache' "$($P.L)\electron\Cache" -Mode prune -Dev $true)
|
|
37
|
-
(New-Target 1 'electron-builder cache' "$($P.L)\electron-builder\Cache" -Mode prune -Dev $true)
|
|
38
|
-
(New-Target 1 'node-gyp headers cache' "$($P.L)\node-gyp\Cache" -Mode prune -Dev $true)
|
|
39
|
-
(New-Target 1 'Scoop installer cache' "$($P.U)\scoop\cache" -Mode prune -Dev $true)
|
|
40
|
-
# Hugging Face keeps revisions as snapshots\<rev> linking into blobs, so removing a whole snapshot orphans
|
|
41
|
-
# blobs: this prunes by the idle gate instead of keeping "the newest per model". AI agent state under
|
|
42
|
-
# ~\.cache (claude, codex, gemini, copilot) stays protected by lib/safety.ps1.
|
|
43
|
-
(New-Target 1 'Hugging Face model cache' "$($P.U)\.cache\huggingface\hub" -Mode prune -Dev $true -Note 'models are re-downloaded on next use; the idle gate keeps recent ones')
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function Invoke-Section01 {
|
|
48
|
-
$ws = $Script:WS
|
|
49
|
-
Write-SectionIntro @(
|
|
50
|
-
'Package-manager caches only re-download what a future install needs. Globally installed packages,',
|
|
51
|
-
'toolchain managers (nvm, Volta, corepack, pnpm global, cargo bin, go bin) are protected and never touched.'
|
|
52
|
-
) -Dev $true
|
|
53
|
-
$targets = Get-Targets01
|
|
54
|
-
$null = Show-TargetSizes $targets
|
|
55
|
-
if (-not (Confirm-Section 'Prune package-manager caches now?')) { Write-Info 'skipped'; return }
|
|
56
|
-
# Tool-native smart prunes first: they know which entries are still referenced.
|
|
57
|
-
if ((Test-CommandPresent 'pnpm') -and $ws.Developer -ne $false -and -not $ws.PurgeAll) {
|
|
58
|
-
Write-Info 'pnpm store prune - removes packages no project references'
|
|
59
|
-
$r = Invoke-External -FilePath 'pnpm' -ArgumentList @('store', 'prune') -Destructive -Quiet -Label 'pnpm store prune'
|
|
60
|
-
if ($r.Ran -and $r.ExitCode -eq 0) { Write-Ok 'pnpm store pruned' }
|
|
61
|
-
$targets = @($targets | Where-Object { $_.Label -ne 'pnpm content store' })
|
|
62
|
-
}
|
|
63
|
-
if ((Test-CommandPresent 'uv') -and $ws.Developer -ne $false -and -not $ws.PurgeAll) {
|
|
64
|
-
Write-Info 'uv cache prune - removes unreachable wheels and sources'
|
|
65
|
-
$r = Invoke-External -FilePath 'uv' -ArgumentList @('cache', 'prune') -Destructive -Quiet -Label 'uv cache prune'
|
|
66
|
-
if ($r.Ran -and $r.ExitCode -eq 0) { Write-Ok 'uv cache pruned' }
|
|
67
|
-
}
|
|
68
|
-
if ((Test-CommandPresent 'npm') -and $ws.PurgeAll) {
|
|
69
|
-
Write-Info 'npm cache clean --force'
|
|
70
|
-
$null = Invoke-External -FilePath 'npm' -ArgumentList @('cache', 'clean', '--force') -Destructive -Quiet -Label 'npm cache clean'
|
|
71
|
-
}
|
|
72
|
-
$null = Invoke-TargetList $targets
|
|
73
|
-
}
|
|
1
|
+
# pkg_managers.ps1 - section 1: package-manager caches. All regenerable; the idle gate keeps recent work fast.
|
|
2
|
+
|
|
3
|
+
function Get-Targets01 {
|
|
4
|
+
$P = $Script:P
|
|
5
|
+
$npm = Get-NpmCacheDir
|
|
6
|
+
return @(
|
|
7
|
+
(New-Target 1 'npm cache (_cacache)' (Join-Path $npm '_cacache') -Mode prune -Dev $true -Note 'regenerated on the next npm install')
|
|
8
|
+
(New-Target 1 'npx package cache (_npx)' (Join-Path $npm '_npx') -Mode prune -Dev $true)
|
|
9
|
+
(New-Target 1 'npm logs' (Join-Path $npm '_logs') -Mode clear-old -Days 7)
|
|
10
|
+
(New-Target 1 'Yarn v1 cache' "$($P.L)\Yarn\Cache" -Mode prune -Dev $true -Note 'yarn classic offline mirror')
|
|
11
|
+
(New-Target 1 'Yarn Berry global cache' "$($P.L)\Yarn\Berry\cache" -Mode prune -Dev $true)
|
|
12
|
+
(New-Target 1 'pnpm content store' "$($P.L)\pnpm\store" -Mode prune -Dev $true -Note 'pnpm store prune is used instead when pnpm is installed')
|
|
13
|
+
(New-Target 1 'pnpm metadata cache' "$($P.L)\pnpm-cache" -Mode prune -Dev $true)
|
|
14
|
+
(New-Target 1 'bun install cache' "$($P.U)\.bun\install\cache" -Mode prune -Dev $true -Note 'global bun packages stay')
|
|
15
|
+
(New-Target 1 'deno deps cache' "$($P.L)\deno\deps" -Mode prune -Dev $true -Note 'installed deno scripts (~\.deno\bin) stay')
|
|
16
|
+
(New-Target 1 'deno gen cache' "$($P.L)\deno\gen" -Mode prune -Dev $true)
|
|
17
|
+
(New-Target 1 'deno npm cache' "$($P.L)\deno\npm" -Mode prune -Dev $true)
|
|
18
|
+
(New-Target 1 'deno remote cache' "$($P.L)\deno\remote" -Mode prune -Dev $true)
|
|
19
|
+
(New-Target 1 'pip cache' "$($P.L)\pip\cache" -Mode prune -Dev $true)
|
|
20
|
+
(New-Target 1 'uv cache' "$($P.L)\uv\cache" -Mode prune -Dev $true -Note 'uv cache prune runs first when uv is installed')
|
|
21
|
+
(New-Target 1 'poetry cache' "$($P.L)\pypoetry\Cache" -Mode prune -Dev $true)
|
|
22
|
+
(New-Target 1 'Composer cache (files)' "$($P.L)\Composer\files" -Mode prune -Dev $true)
|
|
23
|
+
(New-Target 1 'Composer cache (roaming)' "$($P.A)\Composer\cache" -Mode prune -Dev $true)
|
|
24
|
+
(New-Target 1 'NuGet http cache' "$($P.L)\NuGet\v3-cache" -Mode clear)
|
|
25
|
+
(New-Target 1 'NuGet plugins cache' "$($P.L)\NuGet\plugins-cache" -Mode clear)
|
|
26
|
+
(New-Target 1 'NuGet scratch' "$($P.L)\Temp\NuGetScratch" -Mode clear)
|
|
27
|
+
(New-Target 1 'NuGet global packages' "$($P.U)\.nuget\packages" -Mode prune -Dev $true -Note 'restored on the next dotnet restore')
|
|
28
|
+
(New-Target 1 'Cargo registry cache' "$($P.U)\.cargo\registry\cache" -Mode prune -Dev $true -Note 'installed cargo binaries (~\.cargo\bin) stay')
|
|
29
|
+
(New-Target 1 'Cargo registry sources' "$($P.U)\.cargo\registry\src" -Mode prune -Dev $true)
|
|
30
|
+
(New-Target 1 'Cargo git checkouts' "$($P.U)\.cargo\git\checkouts" -Mode prune -Dev $true)
|
|
31
|
+
(New-Target 1 'Go build cache' "$($P.L)\go-build" -Mode prune -Dev $true)
|
|
32
|
+
(New-Target 1 'Go module download cache' "$($P.U)\go\pkg\mod\cache\download" -Mode prune -Dev $true)
|
|
33
|
+
(New-Target 1 'Dart/Flutter pub cache' "$($P.L)\Pub\Cache" -Mode prune -Dev $true -Note 'redownloaded by flutter pub get')
|
|
34
|
+
(New-Target 1 'Dart/Flutter pub cache (legacy)' "$($P.A)\Pub\Cache" -Mode prune -Dev $true)
|
|
35
|
+
(New-Target 1 'Dart analysis server cache' "$($P.U)\.dartServer" -Mode prune -Dev $true)
|
|
36
|
+
(New-Target 1 'Electron download cache' "$($P.L)\electron\Cache" -Mode prune -Dev $true)
|
|
37
|
+
(New-Target 1 'electron-builder cache' "$($P.L)\electron-builder\Cache" -Mode prune -Dev $true)
|
|
38
|
+
(New-Target 1 'node-gyp headers cache' "$($P.L)\node-gyp\Cache" -Mode prune -Dev $true)
|
|
39
|
+
(New-Target 1 'Scoop installer cache' "$($P.U)\scoop\cache" -Mode prune -Dev $true)
|
|
40
|
+
# Hugging Face keeps revisions as snapshots\<rev> linking into blobs, so removing a whole snapshot orphans
|
|
41
|
+
# blobs: this prunes by the idle gate instead of keeping "the newest per model". AI agent state under
|
|
42
|
+
# ~\.cache (claude, codex, gemini, copilot) stays protected by lib/safety.ps1.
|
|
43
|
+
(New-Target 1 'Hugging Face model cache' "$($P.U)\.cache\huggingface\hub" -Mode prune -Dev $true -Note 'models are re-downloaded on next use; the idle gate keeps recent ones')
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function Invoke-Section01 {
|
|
48
|
+
$ws = $Script:WS
|
|
49
|
+
Write-SectionIntro @(
|
|
50
|
+
'Package-manager caches only re-download what a future install needs. Globally installed packages,',
|
|
51
|
+
'toolchain managers (nvm, Volta, corepack, pnpm global, cargo bin, go bin) are protected and never touched.'
|
|
52
|
+
) -Dev $true
|
|
53
|
+
$targets = Get-Targets01
|
|
54
|
+
$null = Show-TargetSizes $targets
|
|
55
|
+
if (-not (Confirm-Section 'Prune package-manager caches now?')) { Write-Info 'skipped'; return }
|
|
56
|
+
# Tool-native smart prunes first: they know which entries are still referenced.
|
|
57
|
+
if ((Test-CommandPresent 'pnpm') -and $ws.Developer -ne $false -and -not $ws.PurgeAll) {
|
|
58
|
+
Write-Info 'pnpm store prune - removes packages no project references'
|
|
59
|
+
$r = Invoke-External -FilePath 'pnpm' -ArgumentList @('store', 'prune') -Destructive -Quiet -Label 'pnpm store prune'
|
|
60
|
+
if ($r.Ran -and $r.ExitCode -eq 0) { Write-Ok 'pnpm store pruned' }
|
|
61
|
+
$targets = @($targets | Where-Object { $_.Label -ne 'pnpm content store' })
|
|
62
|
+
}
|
|
63
|
+
if ((Test-CommandPresent 'uv') -and $ws.Developer -ne $false -and -not $ws.PurgeAll) {
|
|
64
|
+
Write-Info 'uv cache prune - removes unreachable wheels and sources'
|
|
65
|
+
$r = Invoke-External -FilePath 'uv' -ArgumentList @('cache', 'prune') -Destructive -Quiet -Label 'uv cache prune'
|
|
66
|
+
if ($r.Ran -and $r.ExitCode -eq 0) { Write-Ok 'uv cache pruned' }
|
|
67
|
+
}
|
|
68
|
+
if ((Test-CommandPresent 'npm') -and $ws.PurgeAll) {
|
|
69
|
+
Write-Info 'npm cache clean --force'
|
|
70
|
+
$null = Invoke-External -FilePath 'npm' -ArgumentList @('cache', 'clean', '--force') -Destructive -Quiet -Label 'npm cache clean'
|
|
71
|
+
}
|
|
72
|
+
$null = Invoke-TargetList $targets
|
|
73
|
+
}
|