windowsweep 1.0.1 → 1.2.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 +203 -0
- package/CHANGELOG.md +124 -0
- package/LICENSE +21 -21
- package/README.md +88 -49
- package/VERSION +1 -1
- package/lib/actions.ps1 +212 -209
- package/lib/config.ps1 +108 -99
- package/lib/constants.ps1 +103 -89
- package/lib/fs.ps1 +189 -180
- package/lib/log.ps1 +140 -140
- package/lib/safety.ps1 +541 -493
- package/lib/scan.ps1 +109 -94
- package/lib/ui.ps1 +275 -235
- 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 -0
- package/modules/health.ps1 +101 -101
- package/modules/installed_programs.ps1 +118 -0
- package/modules/menu.ps1 +63 -61
- package/modules/notify.ps1 +60 -0
- package/modules/orphaned_appdata.ps1 +189 -0
- package/modules/personal.ps1 +126 -124
- package/modules/pkg_managers.ps1 +73 -69
- package/modules/projects.ps1 +160 -143
- package/modules/release_helpers.ps1 +438 -434
- package/modules/reports.ps1 +212 -212
- package/modules/runner.ps1 +242 -190
- package/modules/self_test_extra.ps1 +537 -86
- package/modules/startup_audit.ps1 +126 -0
- 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 -112
- package/package.json +4 -3
- package/windowsweep.ps1 +322 -296
package/lib/actions.ps1
CHANGED
|
@@ -1,209 +1,212 @@
|
|
|
1
|
-
# actions.ps1 - the target-list engine every section drives, plus shared cache-layout helpers.
|
|
2
|
-
# A section is mostly a declaration: New-Target rows (path + mode + guards) run by Invoke-TargetList.
|
|
3
|
-
|
|
4
|
-
$Script:WS_CHROMIUM_PROFILE_CACHES = @('Cache', 'Code Cache', 'GPUCache', 'DawnCache', 'DawnGraphiteCache', 'DawnWebGPUCache', 'GrShaderCache', 'ShaderCache', 'Service Worker\ScriptCache')
|
|
5
|
-
$Script:WS_CHROMIUM_ROOT_CACHES = @('ShaderCache', 'GrShaderCache', 'GraphiteDawnCache', 'Crashpad\completed', 'Crashpad\reports', 'SwReporter')
|
|
6
|
-
$Script:WS_ELECTRON_CACHES = @('Cache', 'Code Cache', 'GPUCache', 'DawnCache', 'DawnGraphiteCache', 'DawnWebGPUCache', 'GrShaderCache', 'ShaderCache', 'Service Worker\CacheStorage', 'Service Worker\ScriptCache', 'Crashpad\completed', 'Crashpad\reports')
|
|
7
|
-
$Script:WS_EDITOR_CACHES = @('Cache', 'CachedData', 'CachedProfilesData', 'Code Cache', 'GPUCache', 'DawnCache', 'DawnGraphiteCache', 'DawnWebGPUCache', 'Service Worker\CacheStorage', 'Service Worker\ScriptCache', 'Crashpad\completed', 'Crashpad\reports')
|
|
8
|
-
$Script:WS_FIREFOX_CACHES = @('cache2', 'startupCache', 'thumbnails', 'jumpListCache', 'OfflineCache', 'shader-cache')
|
|
9
|
-
|
|
10
|
-
function Get-ChromiumProfileDirs {
|
|
11
|
-
<# .SYNOPSIS Profile folders of a Chromium "User Data" root. A root that itself holds Cache is its own profile (Opera, Electron). #>
|
|
12
|
-
param([string] $Root)
|
|
13
|
-
$out = @()
|
|
14
|
-
if (-not (Test-DirPresent $Root)) { return $out }
|
|
15
|
-
foreach ($e in (Get-ChildEntries $Root)) {
|
|
16
|
-
if (-not ($e -is [IO.DirectoryInfo])) { continue }
|
|
17
|
-
if ($e.Name -match '^(Default|Profile \d+|Guest Profile|System Profile)$') { $out += (Remove-LongPrefix $e.FullName) }
|
|
18
|
-
}
|
|
19
|
-
if ($out.Count -eq 0 -and (Test-DirPresent (Join-Path $Root 'Cache'))) { $out += (Get-FullPath $Root) }
|
|
20
|
-
return $out
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function Get-ChromiumCacheDirs {
|
|
24
|
-
<# .SYNOPSIS Existing regenerable cache folders under a Chromium root (per profile + root-level). Never profile data. #>
|
|
25
|
-
param([string] $Root)
|
|
26
|
-
$out = @()
|
|
27
|
-
foreach ($prof in (Get-ChromiumProfileDirs $Root)) {
|
|
28
|
-
foreach ($sub in $Script:WS_CHROMIUM_PROFILE_CACHES) { $p = Join-Path $prof $sub; if (Test-DirPresent $p) { $out += $p } }
|
|
29
|
-
}
|
|
30
|
-
foreach ($sub in $Script:WS_CHROMIUM_ROOT_CACHES) { $p = Join-Path $Root $sub; if (Test-DirPresent $p) { $out += $p } }
|
|
31
|
-
return @($out | Select-Object -Unique)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function Get-FirefoxCacheDirs {
|
|
35
|
-
param([string] $ProfilesRoot)
|
|
36
|
-
$out = @()
|
|
37
|
-
if (-not (Test-DirPresent $ProfilesRoot)) { return $out }
|
|
38
|
-
foreach ($prof in (Get-ChildEntries $ProfilesRoot)) {
|
|
39
|
-
if (-not ($prof -is [IO.DirectoryInfo])) { continue }
|
|
40
|
-
foreach ($sub in $Script:WS_FIREFOX_CACHES) { $p = Join-Path (Remove-LongPrefix $prof.FullName) $sub; if (Test-DirPresent $p) { $out += $p } }
|
|
41
|
-
}
|
|
42
|
-
return $out
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function Get-SubdirCacheDirs {
|
|
46
|
-
param([string] $Root, [string[]] $Subdirs)
|
|
47
|
-
$out = @()
|
|
48
|
-
if (-not (Test-DirPresent $Root)) { return $out }
|
|
49
|
-
foreach ($sub in $Subdirs) { $p = Join-Path $Root $sub; if (Test-DirPresent $p) { $out += $p } }
|
|
50
|
-
return $out
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function Get-TargetCachePaths {
|
|
54
|
-
<# .SYNOPSIS Concrete cache folders for the layout kinds (chromium | firefox | electron | editor). #>
|
|
55
|
-
param([pscustomobject] $Target)
|
|
56
|
-
$roots = @()
|
|
57
|
-
if ($Target.Path -match '[\*\?]') {
|
|
58
|
-
try { $roots = @(Get-Item -Path $Target.Path -Force -ErrorAction SilentlyContinue | Where-Object { $_ -is [IO.DirectoryInfo] } | ForEach-Object { $_.FullName }) } catch { $roots = @() }
|
|
59
|
-
} elseif (Test-DirPresent $Target.Path) { $roots = @((Get-FullPath $Target.Path)) }
|
|
60
|
-
$out = @()
|
|
61
|
-
foreach ($r in $roots) {
|
|
62
|
-
switch ($Target.Kind) {
|
|
63
|
-
'chromium' { $out += Get-ChromiumCacheDirs $r }
|
|
64
|
-
'firefox' { $out += Get-FirefoxCacheDirs $r }
|
|
65
|
-
'electron' { $out += Get-SubdirCacheDirs -Root $r -Subdirs $Script:WS_ELECTRON_CACHES }
|
|
66
|
-
'editor' { $out += Get-SubdirCacheDirs -Root $r -Subdirs $Script:WS_EDITOR_CACHES }
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return @($out)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function Show-TargetSizes {
|
|
73
|
-
<# .SYNOPSIS Table of the given targets with their current size on disk. Returns total bytes. Skipped when --quiet. #>
|
|
74
|
-
param([pscustomobject[]] $Targets)
|
|
75
|
-
$total = [long]0
|
|
76
|
-
if ($Script:WS.Quiet) { return $total }
|
|
77
|
-
Write-UiLine (" {0,-44} {1,10} {2}" -f 'TARGET', 'ON DISK', 'PATH') 'White'
|
|
78
|
-
foreach ($t in $Targets) {
|
|
79
|
-
if ($t.Kind -eq 'cmd') { Write-UiLine (" {0,-44} {1,10} {2}" -f $t.Label, '(cmd)', $t.Path) 'DarkGray'; continue }
|
|
80
|
-
$paths = @(Resolve-TargetPaths $t)
|
|
81
|
-
if ($paths.Count -eq 0) { Write-UiLine (" {0,-44} {1,10} {2}" -f $t.Label, 'absent', $t.Path) 'DarkGray'; continue }
|
|
82
|
-
$bytes = [long]0
|
|
83
|
-
foreach ($p in $paths) { $bytes += Get-DirectoryBytes $p }
|
|
84
|
-
$total += $bytes
|
|
85
|
-
$shown = $t.Path
|
|
86
|
-
if ($paths.Count -gt 1) { $shown = "$($t.Path) ($($paths.Count) folders)" }
|
|
87
|
-
Write-UiLine (" {0,-44} {1,10} {2}" -f $t.Label, (Format-Bytes $bytes), $shown) 'Gray'
|
|
88
|
-
}
|
|
89
|
-
Write-UiLine (" {0,-44} {1,10}" -f 'on disk now', (Format-Bytes $total)) 'Cyan'
|
|
90
|
-
return $total
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function Confirm-Section {
|
|
94
|
-
<# .SYNOPSIS One confirmation per section. Dry-run needs none; the walkthrough's step prompt counts as one. #>
|
|
95
|
-
param([string] $Prompt = 'Proceed with this section?', [string] $Default = 'y')
|
|
96
|
-
$ws = $Script:WS
|
|
97
|
-
if ($ws.DryRun) { Write-Note '[dry-run] estimating only - nothing is deleted'; return $true }
|
|
98
|
-
if ($ws.SectionPreConfirmed) { return $true }
|
|
99
|
-
return (Confirm-Ui -Prompt $Prompt -Default $Default)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function
|
|
103
|
-
<# .SYNOPSIS
|
|
104
|
-
param([
|
|
105
|
-
$
|
|
106
|
-
$
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
$
|
|
117
|
-
if ($
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
$
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if ($
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
$
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
'
|
|
141
|
-
'
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
$
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
1
|
+
# actions.ps1 - the target-list engine every section drives, plus shared cache-layout helpers.
|
|
2
|
+
# A section is mostly a declaration: New-Target rows (path + mode + guards) run by Invoke-TargetList.
|
|
3
|
+
|
|
4
|
+
$Script:WS_CHROMIUM_PROFILE_CACHES = @('Cache', 'Code Cache', 'GPUCache', 'DawnCache', 'DawnGraphiteCache', 'DawnWebGPUCache', 'GrShaderCache', 'ShaderCache', 'Service Worker\ScriptCache')
|
|
5
|
+
$Script:WS_CHROMIUM_ROOT_CACHES = @('ShaderCache', 'GrShaderCache', 'GraphiteDawnCache', 'Crashpad\completed', 'Crashpad\reports', 'SwReporter')
|
|
6
|
+
$Script:WS_ELECTRON_CACHES = @('Cache', 'Code Cache', 'GPUCache', 'DawnCache', 'DawnGraphiteCache', 'DawnWebGPUCache', 'GrShaderCache', 'ShaderCache', 'Service Worker\CacheStorage', 'Service Worker\ScriptCache', 'Crashpad\completed', 'Crashpad\reports')
|
|
7
|
+
$Script:WS_EDITOR_CACHES = @('Cache', 'CachedData', 'CachedProfilesData', 'Code Cache', 'GPUCache', 'DawnCache', 'DawnGraphiteCache', 'DawnWebGPUCache', 'Service Worker\CacheStorage', 'Service Worker\ScriptCache', 'Crashpad\completed', 'Crashpad\reports')
|
|
8
|
+
$Script:WS_FIREFOX_CACHES = @('cache2', 'startupCache', 'thumbnails', 'jumpListCache', 'OfflineCache', 'shader-cache')
|
|
9
|
+
|
|
10
|
+
function Get-ChromiumProfileDirs {
|
|
11
|
+
<# .SYNOPSIS Profile folders of a Chromium "User Data" root. A root that itself holds Cache is its own profile (Opera, Electron). #>
|
|
12
|
+
param([string] $Root)
|
|
13
|
+
$out = @()
|
|
14
|
+
if (-not (Test-DirPresent $Root)) { return $out }
|
|
15
|
+
foreach ($e in (Get-ChildEntries $Root)) {
|
|
16
|
+
if (-not ($e -is [IO.DirectoryInfo])) { continue }
|
|
17
|
+
if ($e.Name -match '^(Default|Profile \d+|Guest Profile|System Profile)$') { $out += (Remove-LongPrefix $e.FullName) }
|
|
18
|
+
}
|
|
19
|
+
if ($out.Count -eq 0 -and (Test-DirPresent (Join-Path $Root 'Cache'))) { $out += (Get-FullPath $Root) }
|
|
20
|
+
return $out
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function Get-ChromiumCacheDirs {
|
|
24
|
+
<# .SYNOPSIS Existing regenerable cache folders under a Chromium root (per profile + root-level). Never profile data. #>
|
|
25
|
+
param([string] $Root)
|
|
26
|
+
$out = @()
|
|
27
|
+
foreach ($prof in (Get-ChromiumProfileDirs $Root)) {
|
|
28
|
+
foreach ($sub in $Script:WS_CHROMIUM_PROFILE_CACHES) { $p = Join-Path $prof $sub; if (Test-DirPresent $p) { $out += $p } }
|
|
29
|
+
}
|
|
30
|
+
foreach ($sub in $Script:WS_CHROMIUM_ROOT_CACHES) { $p = Join-Path $Root $sub; if (Test-DirPresent $p) { $out += $p } }
|
|
31
|
+
return @($out | Select-Object -Unique)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function Get-FirefoxCacheDirs {
|
|
35
|
+
param([string] $ProfilesRoot)
|
|
36
|
+
$out = @()
|
|
37
|
+
if (-not (Test-DirPresent $ProfilesRoot)) { return $out }
|
|
38
|
+
foreach ($prof in (Get-ChildEntries $ProfilesRoot)) {
|
|
39
|
+
if (-not ($prof -is [IO.DirectoryInfo])) { continue }
|
|
40
|
+
foreach ($sub in $Script:WS_FIREFOX_CACHES) { $p = Join-Path (Remove-LongPrefix $prof.FullName) $sub; if (Test-DirPresent $p) { $out += $p } }
|
|
41
|
+
}
|
|
42
|
+
return $out
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function Get-SubdirCacheDirs {
|
|
46
|
+
param([string] $Root, [string[]] $Subdirs)
|
|
47
|
+
$out = @()
|
|
48
|
+
if (-not (Test-DirPresent $Root)) { return $out }
|
|
49
|
+
foreach ($sub in $Subdirs) { $p = Join-Path $Root $sub; if (Test-DirPresent $p) { $out += $p } }
|
|
50
|
+
return $out
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function Get-TargetCachePaths {
|
|
54
|
+
<# .SYNOPSIS Concrete cache folders for the layout kinds (chromium | firefox | electron | editor). #>
|
|
55
|
+
param([pscustomobject] $Target)
|
|
56
|
+
$roots = @()
|
|
57
|
+
if ($Target.Path -match '[\*\?]') {
|
|
58
|
+
try { $roots = @(Get-Item -Path $Target.Path -Force -ErrorAction SilentlyContinue | Where-Object { $_ -is [IO.DirectoryInfo] } | ForEach-Object { $_.FullName }) } catch { $roots = @() }
|
|
59
|
+
} elseif (Test-DirPresent $Target.Path) { $roots = @((Get-FullPath $Target.Path)) }
|
|
60
|
+
$out = @()
|
|
61
|
+
foreach ($r in $roots) {
|
|
62
|
+
switch ($Target.Kind) {
|
|
63
|
+
'chromium' { $out += Get-ChromiumCacheDirs $r }
|
|
64
|
+
'firefox' { $out += Get-FirefoxCacheDirs $r }
|
|
65
|
+
'electron' { $out += Get-SubdirCacheDirs -Root $r -Subdirs $Script:WS_ELECTRON_CACHES }
|
|
66
|
+
'editor' { $out += Get-SubdirCacheDirs -Root $r -Subdirs $Script:WS_EDITOR_CACHES }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return @($out)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function Show-TargetSizes {
|
|
73
|
+
<# .SYNOPSIS Table of the given targets with their current size on disk. Returns total bytes. Skipped when --quiet. #>
|
|
74
|
+
param([pscustomobject[]] $Targets)
|
|
75
|
+
$total = [long]0
|
|
76
|
+
if ($Script:WS.Quiet) { return $total }
|
|
77
|
+
Write-UiLine (" {0,-44} {1,10} {2}" -f 'TARGET', 'ON DISK', 'PATH') 'White'
|
|
78
|
+
foreach ($t in $Targets) {
|
|
79
|
+
if ($t.Kind -eq 'cmd') { Write-UiLine (" {0,-44} {1,10} {2}" -f $t.Label, '(cmd)', $t.Path) 'DarkGray'; continue }
|
|
80
|
+
$paths = @(Resolve-TargetPaths $t)
|
|
81
|
+
if ($paths.Count -eq 0) { Write-UiLine (" {0,-44} {1,10} {2}" -f $t.Label, 'absent', $t.Path) 'DarkGray'; continue }
|
|
82
|
+
$bytes = [long]0
|
|
83
|
+
foreach ($p in $paths) { $bytes += Get-DirectoryBytes $p }
|
|
84
|
+
$total += $bytes
|
|
85
|
+
$shown = $t.Path
|
|
86
|
+
if ($paths.Count -gt 1) { $shown = "$($t.Path) ($($paths.Count) folders)" }
|
|
87
|
+
Write-UiLine (" {0,-44} {1,10} {2}" -f $t.Label, (Format-Bytes $bytes), $shown) 'Gray'
|
|
88
|
+
}
|
|
89
|
+
Write-UiLine (" {0,-44} {1,10}" -f 'on disk now', (Format-Bytes $total)) 'Cyan'
|
|
90
|
+
return $total
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function Confirm-Section {
|
|
94
|
+
<# .SYNOPSIS One confirmation per section. Dry-run needs none; the walkthrough's step prompt counts as one. #>
|
|
95
|
+
param([string] $Prompt = 'Proceed with this section?', [string] $Default = 'y')
|
|
96
|
+
$ws = $Script:WS
|
|
97
|
+
if ($ws.DryRun) { Write-Note '[dry-run] estimating only - nothing is deleted'; return $true }
|
|
98
|
+
if ($ws.SectionPreConfirmed) { return $true }
|
|
99
|
+
return (Confirm-Ui -Prompt $Prompt -Default $Default)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function Test-KnownCacheLeaf {
|
|
103
|
+
<# .SYNOPSIS Second guard for the layout kinds: only a folder whose leaf name is a declared cache name may be cleared. #>
|
|
104
|
+
param([string] $Path)
|
|
105
|
+
$known = @($Script:WS_CHROMIUM_PROFILE_CACHES + $Script:WS_CHROMIUM_ROOT_CACHES + $Script:WS_ELECTRON_CACHES + $Script:WS_EDITOR_CACHES + $Script:WS_FIREFOX_CACHES | ForEach-Object { [IO.Path]::GetFileName($_) })
|
|
106
|
+
return ($known -contains [IO.Path]::GetFileName($Path))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function Invoke-TargetList {
|
|
110
|
+
<# .SYNOPSIS Run every target with its mode under the dry-run, developer, purge and running-app policies. Returns bytes freed/estimated. #>
|
|
111
|
+
param([pscustomobject[]] $Targets)
|
|
112
|
+
$ws = $Script:WS
|
|
113
|
+
$total = [long]0
|
|
114
|
+
foreach ($t in $Targets) {
|
|
115
|
+
if ($t.Kind -eq 'cmd') { continue }
|
|
116
|
+
$paths = @(Resolve-TargetPaths $t)
|
|
117
|
+
if ($paths.Count -eq 0) { Write-Info "$($t.Label) - absent"; continue }
|
|
118
|
+
if (@($t.Guard).Count -gt 0 -and (Test-ProcessRunning $t.Guard)) {
|
|
119
|
+
Write-Warn "$($t.Label) - skipped: $($t.Guard[0]) is running (close it and run this section again)"
|
|
120
|
+
$ws.Hints += "Close $($t.Guard[0]) and re-run: windowsweep --only $($t.Section) --yes (to clear $($t.Label))"
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
$mode = $t.Mode
|
|
124
|
+
if ($t.Kind -in 'chromium', 'firefox', 'electron', 'editor') {
|
|
125
|
+
$mode = 'clear'
|
|
126
|
+
# Second guard: a layout target may only ever clear a known cache folder, never a profile or app root.
|
|
127
|
+
$paths = @($paths | Where-Object { if (Test-KnownCacheLeaf $_) { $true } else { Write-Err "REFUSE (not a known cache folder for a $($t.Kind) layout): $_"; $false } })
|
|
128
|
+
}
|
|
129
|
+
if ($ws.PurgeAll -and $mode -in 'prune', 'units') { $mode = 'clear' }
|
|
130
|
+
if ($t.Dev -and $ws.Developer -eq $false -and $mode -in 'prune', 'units') { $mode = 'clear' }
|
|
131
|
+
$days = $t.Days
|
|
132
|
+
if ($days -le 0) { $days = $ws.Days }
|
|
133
|
+
# A target that resolves to several folders (every browser profile, every cache name) labels each result
|
|
134
|
+
# line with "parent\leaf", so ten "VS Code caches - cleared ..." lines become ten distinguishable ones.
|
|
135
|
+
$multi = ($paths.Count -gt 1)
|
|
136
|
+
foreach ($p in $paths) {
|
|
137
|
+
$label = "$($t.Label)"
|
|
138
|
+
if ($multi) { $label = "$($t.Label) [$(Split-Path -Leaf (Split-Path -Parent $p))\$([IO.Path]::GetFileName($p))]" }
|
|
139
|
+
switch ($mode) {
|
|
140
|
+
'clear' { $r = Clear-DirectoryContents -Path $p -Within $p -Label $label; $total += $r.Freed }
|
|
141
|
+
'clear-old' { $r = Clear-DirectoryContents -Path $p -Within $p -Label $label -OlderThanDays $days; $total += $r.Freed }
|
|
142
|
+
'prune' { $r = Remove-StaleFiles -Root $p -Within $p -Days $days -Label $label; $total += $r.Freed }
|
|
143
|
+
'units' { $r = Remove-StaleUnits -Root $p -Within $p -Days $days -KeepNewest:$t.KeepNewest -DirectoriesOnly -GroupBy $t.GroupBy -ExcludeNames $t.ExcludeNames -Label $label; $total += $r.Freed }
|
|
144
|
+
'file' {
|
|
145
|
+
$r = Remove-PathSafe -Path $p -Within (Split-Path -Parent $p) -Label "$($t.Label)"
|
|
146
|
+
if ($r.Removed -and -not $ws.DryRun -and $r.Bytes -gt 0) { Write-Ok ("$($t.Label) - removed " + (Format-Bytes $r.Bytes)) }
|
|
147
|
+
$total += $r.Bytes
|
|
148
|
+
}
|
|
149
|
+
default { Write-Warn "$($t.Label) - unknown mode '$mode'" }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return $total
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function Remove-SupersededVersions {
|
|
157
|
+
<# .SYNOPSIS Squirrel-style installs keep old app-x.y.z folders beside the current one; remove all but the highest version. #>
|
|
158
|
+
param([string] $Root, [string] $Prefix = 'app-', [string[]] $Guard = @(), [string] $Label = '')
|
|
159
|
+
if (-not (Test-DirPresent $Root)) { return [long]0 }
|
|
160
|
+
if ($Guard.Count -gt 0 -and (Test-ProcessRunning $Guard)) { Write-Warn "$Label - superseded versions skipped: $($Guard[0]) is running"; return [long]0 }
|
|
161
|
+
$versions = @()
|
|
162
|
+
foreach ($e in (Get-ChildEntries $Root)) {
|
|
163
|
+
if (-not ($e -is [IO.DirectoryInfo]) -or (Test-ReparsePoint $e)) { continue }
|
|
164
|
+
if ($e.Name -like "$Prefix*") {
|
|
165
|
+
$v = $null
|
|
166
|
+
try { $v = [version]($e.Name.Substring($Prefix.Length) -replace '[^0-9.].*$', '') } catch { $v = $null }
|
|
167
|
+
if ($v) { $versions += [pscustomobject]@{ Name = $e.Name; Path = (Remove-LongPrefix $e.FullName); Version = $v } }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if ($versions.Count -lt 2) { return [long]0 }
|
|
171
|
+
$newest = $versions | Sort-Object Version -Descending | Select-Object -First 1
|
|
172
|
+
$freed = [long]0
|
|
173
|
+
foreach ($v in $versions) {
|
|
174
|
+
if ($v.Path -eq $newest.Path) { Write-Note "kept $($v.Name) (current version)"; continue }
|
|
175
|
+
$r = Remove-PathSafe -Path $v.Path -Within $Root -Label $Label
|
|
176
|
+
if ($r.Removed) { $freed += $r.Bytes; if (-not $Script:WS.DryRun) { Write-Ok ("$Label - removed superseded $($v.Name) (" + (Format-Bytes $r.Bytes) + ')') } }
|
|
177
|
+
}
|
|
178
|
+
return $freed
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function ConvertFrom-SizeText {
|
|
182
|
+
<# .SYNOPSIS "2.891GB" / "12.5 MB" / "20.48kB" -> bytes (docker prints decimal units). #>
|
|
183
|
+
param([string] $Text)
|
|
184
|
+
if ($Text -match '([\d.]+)\s*(B|kB|KB|MB|GB|TB)') {
|
|
185
|
+
$n = [double]$Matches[1]
|
|
186
|
+
switch ($Matches[2]) { 'kB' { return [long]($n * 1000) } 'KB' { return [long]($n * 1000) } 'MB' { return [long]($n * 1e6) } 'GB' { return [long]($n * 1e9) } 'TB' { return [long]($n * 1e12) } default { return [long]$n } }
|
|
187
|
+
}
|
|
188
|
+
return [long]0
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function Get-NpmCacheDir {
|
|
192
|
+
$fallback = Join-Path $Script:P.L 'npm-cache'
|
|
193
|
+
if (-not (Test-CommandPresent 'npm')) { return $fallback }
|
|
194
|
+
try {
|
|
195
|
+
$out = & npm config get cache 2>$null
|
|
196
|
+
$line = ($out | Select-Object -Last 1)
|
|
197
|
+
if ($line -and (Test-DirPresent $line.Trim())) { return (Get-FullPath $line.Trim()) }
|
|
198
|
+
} catch { $null = $_ }
|
|
199
|
+
return $fallback
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function Write-SectionIntro {
|
|
203
|
+
<# .SYNOPSIS Print what a section does and the current strategy line. #>
|
|
204
|
+
param([string[]] $Lines, [bool] $Dev = $false)
|
|
205
|
+
$ws = $Script:WS
|
|
206
|
+
foreach ($l in $Lines) { Write-Plain " $l" }
|
|
207
|
+
if ($Dev) {
|
|
208
|
+
if ($ws.PurgeAll) { Write-Warn 'strategy: FULL PURGE (--purge-all) - every cache target is emptied, including recently used files' }
|
|
209
|
+
elseif ($ws.Developer -eq $false) { Write-Info 'strategy: developer mode OFF - these caches are cleared completely' }
|
|
210
|
+
else { Write-Info "strategy: developer mode ON - only files idle $($ws.Days)+ days go; the newest version of each tool cache is kept" }
|
|
211
|
+
}
|
|
212
|
+
}
|