170 lines
5.8 KiB
PowerShell
170 lines
5.8 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Clean all node_modules directories in the repository with optional cache and lockfile cleanup.
|
|
|
|
.DESCRIPTION
|
|
This script recursively finds and removes all 'node_modules' directories starting at the repo root.
|
|
Optionally, it can also remove build caches and lockfiles, and run 'pnpm store prune'.
|
|
|
|
.PARAMETER IncludeCache
|
|
Also remove common cache/build output directories (e.g., .next, .turbo, .vite, node_modules/.cache, dist, build, coverage, out, storybook-static).
|
|
|
|
.PARAMETER IncludeLock
|
|
Also remove lock files (pnpm-lock.yaml, package-lock.json, yarn.lock) in the repo.
|
|
|
|
.PARAMETER PruneStore
|
|
After deletion, try to run 'pnpm store prune' if pnpm is installed.
|
|
|
|
.PARAMETER Yes
|
|
Proceed without interactive confirmation (non-interactive mode).
|
|
|
|
.PARAMETER DryRun
|
|
Show what would be removed without deleting anything.
|
|
|
|
.EXAMPLE
|
|
# Preview what will be removed
|
|
./scripts/clean-node-modules.ps1 -DryRun
|
|
|
|
.EXAMPLE
|
|
# Clean node_modules only, no prompt
|
|
./scripts/clean-node-modules.ps1 -Yes
|
|
|
|
.EXAMPLE
|
|
# Deep clean including caches and lockfiles, and prune pnpm store
|
|
./scripts/clean-node-modules.ps1 -IncludeCache -IncludeLock -PruneStore -Yes
|
|
#>
|
|
|
|
[CmdletBinding(SupportsShouldProcess=$true)]
|
|
param(
|
|
[switch] $IncludeCache,
|
|
[switch] $IncludeLock,
|
|
[switch] $PruneStore,
|
|
[switch] $Yes,
|
|
[switch] $DryRun
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
function Write-Section($text) {
|
|
Write-Host "`n==== $text ====\n" -ForegroundColor Cyan
|
|
}
|
|
|
|
function Safe-RemoveDirectory {
|
|
param(
|
|
[Parameter(Mandatory=$true)][string] $Path,
|
|
[switch] $Preview
|
|
)
|
|
if (-not (Test-Path -LiteralPath $Path)) { return }
|
|
if ($Preview) { Write-Host "[dir] $Path"; return }
|
|
try {
|
|
# Use cmd rmdir for better handling of read-only/long paths on Windows PowerShell 5.1
|
|
& cmd.exe /c "rmdir /s /q \"$Path\"" | Out-Null
|
|
} catch {
|
|
try { Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop } catch {
|
|
Write-Warning "Failed to remove directory: $Path -> $($_.Exception.Message)"
|
|
}
|
|
}
|
|
}
|
|
|
|
function Safe-RemoveFile {
|
|
param(
|
|
[Parameter(Mandatory=$true)][string] $Path,
|
|
[switch] $Preview
|
|
)
|
|
if (-not (Test-Path -LiteralPath $Path)) { return }
|
|
if ($Preview) { Write-Host "[file] $Path"; return }
|
|
try { Remove-Item -LiteralPath $Path -Force -ErrorAction Stop } catch {
|
|
Write-Warning "Failed to remove file: $Path -> $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# Resolve repo root (this script lives in ./scripts)
|
|
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
|
Set-Location -LiteralPath $RepoRoot
|
|
|
|
Write-Host "Repo root: $RepoRoot" -ForegroundColor DarkGray
|
|
|
|
# Accumulators
|
|
$DirsToDelete = New-Object System.Collections.Generic.List[string]
|
|
$FilesToDelete = New-Object System.Collections.Generic.List[string]
|
|
|
|
# 1) node_modules everywhere (including root)
|
|
Write-Section 'Scanning node_modules directories'
|
|
$nodeModulesDirs = Get-ChildItem -LiteralPath $RepoRoot -Directory -Recurse -Force -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Name -eq 'node_modules' }
|
|
|
|
# Ensure root node_modules is included if present
|
|
$rootNM = Join-Path $RepoRoot 'node_modules'
|
|
if (Test-Path -LiteralPath $rootNM) {
|
|
$DirsToDelete.Add($rootNM)
|
|
}
|
|
foreach ($d in $nodeModulesDirs) {
|
|
if (-not $DirsToDelete.Contains($d.FullName)) { $DirsToDelete.Add($d.FullName) }
|
|
}
|
|
Write-Host ("Found {0} node_modules directory(ies)" -f $DirsToDelete.Count)
|
|
|
|
# 2) Optional caches/output folders
|
|
if ($IncludeCache) {
|
|
Write-Section 'Scanning cache/output directories'
|
|
$cacheNames = @(
|
|
'.next', '.turbo', '.vite', '.parcel-cache', '.cache',
|
|
'dist', 'build', 'coverage', 'out', 'storybook-static',
|
|
'.wrangler'
|
|
)
|
|
# node_modules/.cache is common; include it via name match too
|
|
$allDirs = Get-ChildItem -LiteralPath $RepoRoot -Directory -Recurse -Force -ErrorAction SilentlyContinue
|
|
foreach ($dir in $allDirs) {
|
|
if ($cacheNames -contains $dir.Name) {
|
|
if (-not $DirsToDelete.Contains($dir.FullName)) { $DirsToDelete.Add($dir.FullName) }
|
|
}
|
|
}
|
|
Write-Host ("Found {0} cache/output directory(ies)" -f ($DirsToDelete | Where-Object { Test-Path $_ }).Count)
|
|
}
|
|
|
|
# 3) Optional lockfiles
|
|
if ($IncludeLock) {
|
|
Write-Section 'Scanning lock files'
|
|
$lockGlobs = @('pnpm-lock.yaml', 'package-lock.json', 'yarn.lock')
|
|
foreach ($glob in $lockGlobs) {
|
|
$files = Get-ChildItem -LiteralPath $RepoRoot -Recurse -Force -File -Filter $glob -ErrorAction SilentlyContinue
|
|
foreach ($f in $files) { if (-not $FilesToDelete.Contains($f.FullName)) { $FilesToDelete.Add($f.FullName) } }
|
|
}
|
|
Write-Host ("Found {0} lock file(s)" -f $FilesToDelete.Count)
|
|
}
|
|
|
|
# 4) Summary
|
|
Write-Section 'Summary'
|
|
Write-Host ("Directories to delete: {0}" -f $DirsToDelete.Count)
|
|
Write-Host ("Files to delete: {0}" -f $FilesToDelete.Count)
|
|
|
|
$preview = $DryRun -or (-not $Yes)
|
|
if ($preview) {
|
|
Write-Host "Preview mode (no deletions). Use -Yes to confirm, or pass -DryRun:$false to hide this list." -ForegroundColor Yellow
|
|
foreach ($dir in $DirsToDelete) { Safe-RemoveDirectory -Path $dir -Preview }
|
|
foreach ($fil in $FilesToDelete) { Safe-RemoveFile -Path $fil -Preview }
|
|
if (-not $Yes) { Write-Host "\nRun again with -Yes to confirm deletion." -ForegroundColor Yellow }
|
|
exit 0
|
|
}
|
|
|
|
# 5) Deletion
|
|
Write-Section 'Deleting directories'
|
|
foreach ($dir in $DirsToDelete) { Safe-RemoveDirectory -Path $dir }
|
|
|
|
if ($FilesToDelete.Count -gt 0) {
|
|
Write-Section 'Deleting files'
|
|
foreach ($fil in $FilesToDelete) { Safe-RemoveFile -Path $fil }
|
|
}
|
|
|
|
# 6) Optional pnpm store prune
|
|
if ($PruneStore) {
|
|
Write-Section 'Pruning pnpm store'
|
|
$pnpm = Get-Command pnpm -ErrorAction SilentlyContinue
|
|
if ($null -ne $pnpm) {
|
|
try { & pnpm store prune } catch { Write-Warning "pnpm store prune failed: $($_.Exception.Message)" }
|
|
} else {
|
|
Write-Warning "pnpm not found on PATH; skipping 'pnpm store prune'."
|
|
}
|
|
}
|
|
|
|
Write-Host "\nDone." -ForegroundColor Green
|