Files
asepharyana-hub/scripts/clean-node-modules.ps1
T

170 lines
5.8 KiB
PowerShell
Raw Normal View History

2026-07-09 22:08:26 +07:00
<#
.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