Skip to content

Instantly share code, notes, and snippets.

@shoveller
Last active September 25, 2025 05:16
Show Gist options
  • Save shoveller/b89842389db9c629bb24761a10d6aadb to your computer and use it in GitHub Desktop.
Save shoveller/b89842389db9c629bb24761a10d6aadb to your computer and use it in GitHub Desktop.
npm 패키지 설치여부 체크
# PowerShell Package Checker Script (v2 - Exact Version Matching)
# Compatible with PowerShell 3.0+ and PowerShell Core
# Encoding: UTF-8 with BOM
# Encoding settings (prevent character encoding issues)
try {
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
if ($Host.UI.RawUI) {
$Host.UI.RawUI.OutputEncoding = [System.Text.Encoding]::UTF8
}
} catch {
# Ignore encoding setup failures and continue
}
param(
[string]$InputFile = "list.md"
)
# PowerShell version compatibility check
$PSVersionMajor = $PSVersionTable.PSVersion.Major
Write-Host "PowerShell Version: $($PSVersionTable.PSVersion)" -ForegroundColor Cyan
Write-Host "Multi Package Manager Checker Script (PowerShell v2)" -ForegroundColor Cyan
Write-Host "===================================================" -ForegroundColor Cyan
# Determine input source
$inputSource = $null
$packages = @()
if (Test-Path $InputFile) {
Write-Host "Reading package list from $InputFile file." -ForegroundColor Green
$packages = Get-Content $InputFile
}
elseif (-not [Console]::IsInputRedirected) {
Write-Host "ERROR: Package list not found." -ForegroundColor Red
Write-Host "Usage:"
Write-Host " 1) Run with file: .\check_packages.ps1"
Write-Host " 2) Specify file: .\check_packages.ps1 -InputFile 'mylist.txt'"
Write-Host " 3) Use pipeline: gc list.md | .\check_packages.ps1"
exit 1
}
else {
Write-Host "Reading package list from standard input." -ForegroundColor Green
$packages = $input
}
# Detect package managers
function Get-AvailablePackageManagers {
$managers = @()
if (Get-Command npm -ErrorAction SilentlyContinue) {
$managers += "npm"
}
if (Get-Command pnpm -ErrorAction SilentlyContinue) {
$managers += "pnpm"
}
if (Get-Command yarn -ErrorAction SilentlyContinue) {
$managers += "yarn"
}
return $managers
}
# JSON parsing function (backward compatibility)
function Parse-JsonSafely {
param([string]$JsonString)
if ([string]::IsNullOrWhiteSpace($JsonString)) {
return $null
}
try {
# Use ConvertFrom-Json in PowerShell 3.0+
if ($PSVersionMajor -ge 3) {
# Handle differences between PowerShell 5.1 and Core
if ($PSVersionMajor -eq 5) {
# PowerShell 5.1 may handle arrays differently
$result = $JsonString | ConvertFrom-Json -ErrorAction Stop
return $result
} else {
return $JsonString | ConvertFrom-Json -ErrorAction Stop
}
}
else {
# PowerShell 2.0 fallback (using JavaScriptSerializer)
Add-Type -AssemblyName "System.Web.Extensions"
$serializer = New-Object System.Web.Script.Serialization.JavaScriptSerializer
$serializer.MaxJsonLength = 4194304 # 4MB limit
return $serializer.DeserializeObject($JsonString)
}
}
catch {
Write-Warning "JSON parsing failed: $($_.Exception.Message)"
return $null
}
}
# Check local packages
function Test-LocalPackage {
param([string]$PackageWithVersion, [string]$Manager)
# Split package name and version by @ symbol (considering scoped packages)
$atIndex = $PackageWithVersion.LastIndexOf('@')
if ($atIndex -le 0) {
Write-Warning "Invalid package format: $PackageWithVersion"
return $false
}
$packageName = $PackageWithVersion.Substring(0, $atIndex)
$expectedVersion = $PackageWithVersion.Substring($atIndex + 1)
switch ($Manager) {
"npm" {
try {
$jsonOutput = & npm ls --json 2>$null
if ($jsonOutput) {
# npm ls [package] --json으로 특정 패키지 확인
$specificOutput = & npm ls $packageName --json 2>$null
if ($specificOutput) {
$specificParsed = Parse-JsonSafely -JsonString ($specificOutput -join "`n")
if ($specificParsed -and $specificParsed.dependencies -and $specificParsed.dependencies.$packageName) {
# version 필드가 있으면 사용
$actualVersion = $specificParsed.dependencies.$packageName.version
if ([string]::IsNullOrWhiteSpace($actualVersion) -or $actualVersion -eq "null") {
# resolved URL에서 버전 추출
$resolvedUrl = $specificParsed.dependencies.$packageName.resolved
if ($resolvedUrl -match '(\d+\.\d+\.\d+)') {
$actualVersion = $matches[1]
}
}
return $actualVersion -eq $expectedVersion
}
}
else {
# 전체 목록에서 확인
$parsedJson = Parse-JsonSafely -JsonString ($jsonOutput -join "`n")
if ($parsedJson -and $parsedJson.dependencies -and $parsedJson.dependencies.$packageName) {
$actualVersion = $parsedJson.dependencies.$packageName.version
return $actualVersion -eq $expectedVersion
}
}
}
return $false
}
catch {
Write-Warning "npm local check failed: $($_.Exception.Message)"
return $false
}
}
"pnpm" {
try {
$jsonOutput = & pnpm ls --json 2>$null
if ($jsonOutput) {
$parsedJson = Parse-JsonSafely -JsonString ($jsonOutput -join "`n")
# pnpm returns array format, so handle it safely
if ($parsedJson) {
$firstItem = if ($parsedJson -is [Array] -and $parsedJson.Count -gt 0) { $parsedJson[0] } else { $parsedJson }
if ($firstItem -and $firstItem.dependencies -and $firstItem.dependencies.$packageName) {
$actualVersion = $firstItem.dependencies.$packageName.version
if (![string]::IsNullOrWhiteSpace($actualVersion) -and $actualVersion -ne "null") {
return $actualVersion -eq $expectedVersion
}
}
}
}
return $false
}
catch {
Write-Warning "pnpm local check failed: $($_.Exception.Message)"
return $false
}
}
"yarn" {
try {
$jsonOutput = & yarn list --json 2>$null
if ($jsonOutput) {
$parsedJson = Parse-JsonSafely -JsonString ($jsonOutput -join "`n")
if ($parsedJson -and $parsedJson.data -and $parsedJson.data.trees) {
$matchingPackage = $parsedJson.data.trees | Where-Object { $_.name -like "$packageName@*" }
if ($matchingPackage) {
$actualVersion = $matchingPackage.name.Split('@')[-1]
if (![string]::IsNullOrWhiteSpace($actualVersion) -and $actualVersion -ne "null") {
return $actualVersion -eq $expectedVersion
}
}
}
}
return $false
}
catch {
Write-Warning "yarn local check failed: $($_.Exception.Message)"
return $false
}
}
}
return $false
}
# Check global packages
function Test-GlobalPackage {
param([string]$PackageWithVersion, [string]$Manager)
# Split package name and version by @ symbol (considering scoped packages)
$atIndex = $PackageWithVersion.LastIndexOf('@')
if ($atIndex -le 0) {
Write-Warning "Invalid package format: $PackageWithVersion"
return $false
}
$packageName = $PackageWithVersion.Substring(0, $atIndex)
$expectedVersion = $PackageWithVersion.Substring($atIndex + 1)
switch ($Manager) {
"npm" {
try {
$jsonOutput = & npm ls -g --json 2>$null
if ($jsonOutput) {
$parsedJson = Parse-JsonSafely -JsonString ($jsonOutput -join "`n")
if ($parsedJson -and $parsedJson.dependencies -and $parsedJson.dependencies.$packageName) {
$actualVersion = $parsedJson.dependencies.$packageName.version
if (![string]::IsNullOrWhiteSpace($actualVersion) -and $actualVersion -ne "null") {
return $actualVersion -eq $expectedVersion
}
}
}
return $false
}
catch {
Write-Warning "npm global check failed: $($_.Exception.Message)"
return $false
}
}
"pnpm" {
try {
$jsonOutput = & pnpm ls -g --json 2>$null
if ($jsonOutput) {
$parsedJson = Parse-JsonSafely -JsonString ($jsonOutput -join "`n")
# pnpm returns array format, so handle it safely
if ($parsedJson) {
$firstItem = if ($parsedJson -is [Array] -and $parsedJson.Count -gt 0) { $parsedJson[0] } else { $parsedJson }
if ($firstItem -and $firstItem.dependencies -and $firstItem.dependencies.$packageName) {
$actualVersion = $firstItem.dependencies.$packageName.version
if (![string]::IsNullOrWhiteSpace($actualVersion) -and $actualVersion -ne "null") {
return $actualVersion -eq $expectedVersion
}
}
}
}
return $false
}
catch {
Write-Warning "pnpm global check failed: $($_.Exception.Message)"
return $false
}
}
"yarn" {
try {
$jsonOutput = & yarn global list --json 2>$null
if ($jsonOutput) {
$parsedJson = Parse-JsonSafely -JsonString ($jsonOutput -join "`n")
if ($parsedJson -and $parsedJson.data -and $parsedJson.data.trees) {
$matchingPackage = $parsedJson.data.trees | Where-Object { $_.name -like "$packageName@*" }
if ($matchingPackage) {
$actualVersion = $matchingPackage.name.Split('@')[-1]
if (![string]::IsNullOrWhiteSpace($actualVersion) -and $actualVersion -ne "null") {
return $actualVersion -eq $expectedVersion
}
}
}
}
return $false
}
catch {
Write-Warning "yarn global check failed: $($_.Exception.Message)"
return $false
}
}
}
return $false
}
# Check available package managers
$availableManagers = Get-AvailablePackageManagers
if ($availableManagers.Count -eq 0) {
Write-Host "ERROR: No package managers found (npm, pnpm, yarn)." -ForegroundColor Red
exit 1
}
Write-Host "Detected package managers: $($availableManagers -join ', ')" -ForegroundColor Yellow
Write-Host ""
# Hash table for storing results (PowerShell 2.0 compatible)
if ($PSVersionMajor -ge 3) {
$results = @{}
} else {
$results = New-Object System.Collections.Hashtable
}
foreach ($line in $packages) {
# Remove numbers, arrows, parentheses and keep package@version format
# Handle arrow characters as unicode for PowerShell 5.1 compatibility
if ([string]::IsNullOrWhiteSpace($line)) {
continue
}
$package = $line -replace '^\s*\d*[).]\s*', '' -replace '^\d*[\u2192\u27A4]*\d*\.\s*', ''
$package = $package.Trim()
if ([string]::IsNullOrWhiteSpace($package)) {
continue
}
$foundLocal = $false
$foundGlobal = $false
$localManagers = @()
$globalManagers = @()
# Check local/global in each package manager
foreach ($manager in $availableManagers) {
if (Test-LocalPackage $package $manager) {
$foundLocal = $true
$localManagers += $manager
}
if (Test-GlobalPackage $package $manager) {
$foundGlobal = $true
$globalManagers += $manager
}
}
# Output results
if ($foundLocal -and $foundGlobal) {
$statusMsg = "local: $($localManagers -join ', '), global: $($globalManagers -join ', ')"
Write-Host "FOUND: $package ($statusMsg)" -ForegroundColor Green
$results[$package] = "installed"
}
elseif ($foundLocal) {
$statusMsg = "local: $($localManagers -join ', ')"
Write-Host "FOUND: $package ($statusMsg)" -ForegroundColor Green
$results[$package] = "installed"
}
elseif ($foundGlobal) {
$statusMsg = "global: $($globalManagers -join ', ')"
Write-Host "FOUND: $package ($statusMsg)" -ForegroundColor Green
$results[$package] = "installed"
}
else {
Write-Host "MISSING: $package (not found in local/global)" -ForegroundColor Red
$results[$package] = "not_found"
}
}
Write-Host ""
Write-Host "Summary:" -ForegroundColor Cyan
# Result aggregation (considering PowerShell version compatibility)
$installedCount = 0
$notFoundCount = 0
if ($PSVersionMajor -ge 3) {
$installedCount = ($results.Values | Where-Object { $_ -eq "installed" }).Count
$notFoundCount = ($results.Values | Where-Object { $_ -eq "not_found" }).Count
} else {
# PowerShell 2.0 compatibility
foreach ($value in $results.Values) {
if ($value -eq "installed") {
$installedCount++
} elseif ($value -eq "not_found") {
$notFoundCount++
}
}
}
Write-Host "Installed: $installedCount" -ForegroundColor Green
Write-Host "Not found: $notFoundCount" -ForegroundColor Red
# Script exit
exit 0
#!/bin/bash
echo "다중 패키지 매니저 확인 스크립트"
echo "================================"
# 입력 소스 확인 (파일 또는 stdin)
input_source=""
if [ -f "list.md" ]; then
input_source="list.md"
echo "📄 list.md 파일에서 패키지 목록을 읽습니다."
elif [ ! -t 0 ]; then
input_source="/dev/stdin"
echo "📥 표준입력에서 패키지 목록을 읽습니다."
else
echo "❌ 패키지 목록을 찾을 수 없습니다."
echo "사용법:"
echo " 1) list.md 파일과 함께 실행: ./check_packages.sh"
echo " 2) 표준입력 사용: curl -s https://gist.../raw | bash < list.md"
echo " 3) 리디렉션 사용: ./check_packages.sh < list.md"
exit 1
fi
# 패키지 매니저 감지 함수
detect_package_managers() {
managers=()
if command -v npm &> /dev/null; then
managers+=("npm")
fi
if command -v pnpm &> /dev/null; then
managers+=("pnpm")
fi
if command -v yarn &> /dev/null; then
managers+=("yarn")
fi
echo "${managers[@]}"
}
# 패키지 설치 확인 함수 (로컬)
check_package_local() {
local package_with_version=$1
local manager=$2
local package_name=$(echo "$package_with_version" | cut -d'@' -f1)
local expected_version=$(echo "$package_with_version" | cut -d'@' -f2)
case $manager in
"npm")
local json_output=$(npm ls --json 2>/dev/null)
if [ -n "$json_output" ]; then
# npm ls [package] --json으로 특정 패키지 확인
local specific_output=$(npm ls "$package_name" --json 2>/dev/null)
if [ -n "$specific_output" ]; then
# version 필드가 있으면 사용, 없으면 resolved URL에서 추출
local actual_version=$(echo "$specific_output" | jq -r --arg pkg "$package_name" '.dependencies[$pkg].version // empty' 2>/dev/null)
if [ -z "$actual_version" ] || [ "$actual_version" = "null" ]; then
# resolved URL에서 버전 추출
actual_version=$(echo "$specific_output" | jq -r --arg pkg "$package_name" '.dependencies[$pkg].resolved' 2>/dev/null | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1)
fi
[ -n "$actual_version" ] && [ "$actual_version" != "null" ] && [ "$actual_version" = "$expected_version" ]
else
# 전체 목록에서 확인
local actual_version=$(echo "$json_output" | jq -r --arg pkg "$package_name" '.dependencies[$pkg].version // empty' 2>/dev/null)
[ -n "$actual_version" ] && [ "$actual_version" != "null" ] && [ "$actual_version" = "$expected_version" ]
fi
else
return 1
fi
;;
"pnpm")
local json_output=$(pnpm ls --json 2>/dev/null)
if [ -n "$json_output" ]; then
local actual_version=$(echo "$json_output" | jq -r --arg pkg "$package_name" '.[0].dependencies[$pkg].version // empty' 2>/dev/null)
[ -n "$actual_version" ] && [ "$actual_version" != "null" ] && [ "$actual_version" = "$expected_version" ]
else
return 1
fi
;;
"yarn")
local json_output=$(yarn list --json 2>/dev/null)
if [ -n "$json_output" ]; then
local actual_version=$(echo "$json_output" | jq -r --arg pkg "$package_name" --arg expected "$expected_version" '.data.trees[] | select(.name | startswith($pkg + "@")) | .name | split("@")[1] // empty' 2>/dev/null)
[ -n "$actual_version" ] && [ "$actual_version" != "null" ] && [ "$actual_version" = "$expected_version" ]
else
return 1
fi
;;
esac
}
# 패키지 설치 확인 함수 (글로벌)
check_package_global() {
local package_with_version=$1
local manager=$2
local package_name=$(echo "$package_with_version" | cut -d'@' -f1)
local expected_version=$(echo "$package_with_version" | cut -d'@' -f2)
case $manager in
"npm")
local json_output=$(npm ls -g --json 2>/dev/null)
if [ -n "$json_output" ]; then
local actual_version=$(echo "$json_output" | jq -r --arg pkg "$package_name" '.dependencies[$pkg].version // empty' 2>/dev/null)
[ -n "$actual_version" ] && [ "$actual_version" != "null" ] && [ "$actual_version" = "$expected_version" ]
else
return 1
fi
;;
"pnpm")
local json_output=$(pnpm ls -g --json 2>/dev/null)
if [ -n "$json_output" ]; then
local actual_version=$(echo "$json_output" | jq -r --arg pkg "$package_name" '.[0].dependencies[$pkg].version // empty' 2>/dev/null)
[ -n "$actual_version" ] && [ "$actual_version" != "null" ] && [ "$actual_version" = "$expected_version" ]
else
return 1
fi
;;
"yarn")
local json_output=$(yarn global list --json 2>/dev/null)
if [ -n "$json_output" ]; then
local actual_version=$(echo "$json_output" | jq -r --arg pkg "$package_name" --arg expected "$expected_version" '.data.trees[] | select(.name | startswith($pkg + "@")) | .name | split("@")[1] // empty' 2>/dev/null)
[ -n "$actual_version" ] && [ "$actual_version" != "null" ] && [ "$actual_version" = "$expected_version" ]
else
return 1
fi
;;
esac
}
# 사용 가능한 패키지 매니저 확인
available_managers=($(detect_package_managers))
if [ ${#available_managers[@]} -eq 0 ]; then
echo "❌ npm, pnpm, yarn 중 어떤 패키지 매니저도 찾을 수 없습니다."
exit 1
fi
echo "감지된 패키지 매니저: ${available_managers[*]}"
echo ""
# 결과 카운터 (연관배열 대신 사용)
installed_count=0
not_found_count=0
while IFS= read -r line || [ -n "$line" ]; do
# 숫자와 화살표, 괄호 제거하고 패키지명@버전 형태 유지
package=$(echo "$line" | sed 's/^[[:space:]]*[0-9]*[).]*[[:space:]]*//' | sed 's/^[0-9]*→*[0-9]*\.//' | xargs)
if [ -n "$package" ] && [ "$package" != "" ]; then
found_local=false
found_global=false
local_managers=""
global_managers=""
# 각 패키지 매니저에서 로컬/글로벌 확인
for manager in "${available_managers[@]}"; do
if check_package_local "$package" "$manager"; then
found_local=true
if [ -z "$local_managers" ]; then
local_managers="$manager"
else
local_managers="$local_managers, $manager"
fi
fi
if check_package_global "$package" "$manager"; then
found_global=true
if [ -z "$global_managers" ]; then
global_managers="$manager"
else
global_managers="$global_managers, $manager"
fi
fi
done
# 결과 출력
status_msg=""
if [ "$found_local" = true ] && [ "$found_global" = true ]; then
status_msg="로컬: $local_managers, 글로벌: $global_managers"
echo "✅ $package ($status_msg)"
installed_count=$((installed_count + 1))
elif [ "$found_local" = true ]; then
status_msg="로컬: $local_managers"
echo "✅ $package ($status_msg)"
installed_count=$((installed_count + 1))
elif [ "$found_global" = true ]; then
status_msg="글로벌: $global_managers"
echo "✅ $package ($status_msg)"
installed_count=$((installed_count + 1))
else
echo "❌ $package (로컬/글로벌 모두에서 찾을 수 없음)"
not_found_count=$((not_found_count + 1))
fi
fi
done < "$input_source"
echo ""
echo "결과 요약:"
echo "설치됨: $installed_count"
echo "설치되지 않음: $not_found_count"
# 스크립트 종료 (추가 내용 실행 방지)
exit 0
@shoveller
Copy link
Author

shoveller commented Sep 24, 2025

패키지 설치 여부를 조사하는 스크립트

  • 주요 npm 패키지가 오염되었을 때 빠르게 조사할 수 있다.
  • 실행하는 프로젝트 루트에 list.md 를 두어야 동작한다. 이것이 블랙리스트 역활을 한다.

쉘 스크립트 버전 원라인 스크립트

curl -s https://gist.github.com/shoveller/b89842389db9c629bb24761a10d6aadb/raw/7cfeb30e6e31231a699afc48eec5253dd66c461e/check_packages.sh | bash < list.md

파워쉘 버전 원라인 스크립트

Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process; iwr -Uri "https://gist.github.com/shoveller/b89842389db9c629bb24761a10d6aadb/raw/7cfeb30e6e31231a699afc48eec5253dd66c461e/check_packages.ps1" -OutFile "check_packages.ps1"; gc list.md | .\check_packages.ps1

파워쉘 버전 스크립트 뭉치

# 1. 실행 정책 임시 변경
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process

# 2. 스크립트 실행
gc list.md | .\check_packages.ps1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment