#!/usr/bin/env node
/**
* Prune platform-specific prebuilds from node_modules
*
* This removes prebuilds for platforms other than the current one,
* reducing package size from ~300MB to ~40MB (85% reduction)
*
* Run after: npm install
* Run before: pear stage
*/
const fs = require('fs')
const path = require('path')
const os = require('os')
// Detect current platform
const platform = os.platform() // 'linux', 'darwin', 'win32'
const arch = os.arch() // 'x64', 'arm64', etc.
const currentPlatform = `${platform}-${arch}`
console.log(`🔍 Current platform: ${currentPlatform}`)
console.log(`🗑️ Removing prebuilds for other platforms...\n`)
let totalRemoved = 0
let totalKept = 0
let packagesProcessed = 0
/**
* Recursively find and prune prebuilds directories
*/
function pruneDirectory(dir, depth = 0) {
if (depth > 10) return // Prevent infinite recursion
try {
const entries = fs.readdirSync(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) {
if (entry.name === 'prebuilds') {
// Found a prebuilds directory - prune it
prunePrebuild(fullPath)
packagesProcessed++
} else if (entry.name === 'node_modules') {
// Recurse into nested node_modules
pruneDirectory(fullPath, depth + 1)
} else if (entry.name.startsWith('@')) {
// Scoped package namespace - recurse into it
pruneDirectory(fullPath, depth)
} else {
// Check every directory for prebuilds and nested node_modules
const prebuildsPath = path.join(fullPath, 'prebuilds')
if (fs.existsSync(prebuildsPath)) {
prunePrebuild(prebuildsPath)
packagesProcessed++
}
// Also check nested node_modules
const nestedNodeModules = path.join(fullPath, 'node_modules')
if (fs.existsSync(nestedNodeModules)) {
pruneDirectory(nestedNodeModules, depth + 1)
}
}
}
}
} catch (err) {
// Ignore permission errors, etc.
console.error(`⚠️ Error reading ${dir}: ${err.message}`)
}
}
/**
* Recursively remove directory (compatible with Node v10+)
*/
function removeDir(dir) {
if (!fs.existsSync(dir)) return
const entries = fs.readdirSync(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) {
removeDir(fullPath)
} else {
fs.unlinkSync(fullPath)
}
}
fs.rmdirSync(dir)
}
/**
* Prune a specific prebuilds directory
*/
function prunePrebuild(prebuildsPath) {
try {
const platforms = fs.readdirSync(prebuildsPath)
const packageName = path.basename(path.dirname(prebuildsPath))
let removedPlatforms = []
let keptPlatforms = []
for (const platform of platforms) {
const platformPath = path.join(prebuildsPath, platform)
let stat
try {
stat = fs.statSync(platformPath)
} catch (err) {
continue
}
if (!stat.isDirectory()) continue
if (platform === currentPlatform) {
// Keep current platform
keptPlatforms.push(platform)
totalKept++
} else {
// Remove other platforms
removeDir(platformPath)
removedPlatforms.push(platform)
totalRemoved++
}
}
if (removedPlatforms.length > 0) {
console.log(`📦 ${packageName}:`)
console.log(` ✅ Kept: ${keptPlatforms.join(', ') || 'none'}`)
console.log(` ❌ Removed: ${removedPlatforms.join(', ')}`)
}
} catch (err) {
console.error(`⚠️ Error processing ${prebuildsPath}: ${err.message}`)
}
}
/**
* Get directory size
*/
function getDirSize(dir) {
let size = 0
try {
const entries = fs.readdirSync(dir, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) {
size += getDirSize(fullPath)
} else {
const stat = fs.statSync(fullPath)
size += stat.size
}
}
} catch (err) {
// Ignore errors
}
return size
}
/**
* Format bytes to human-readable
*/
function formatBytes(bytes) {
if (bytes < 1024) return bytes + ' B'
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
}
// Main execution
const nodeModulesPath = path.join(__dirname, 'node_modules')
if (!fs.existsSync(nodeModulesPath)) {
console.error('❌ node_modules not found. Run npm install first.')
process.exit(1)
}
// Measure before
const sizeBefore = getDirSize(nodeModulesPath)
console.log(`📊 Size before: ${formatBytes(sizeBefore)}\n`)
// Prune
const startTime = Date.now()
pruneDirectory(nodeModulesPath)
const duration = ((Date.now() - startTime) / 1000).toFixed(2)
// Measure after
const sizeAfter = getDirSize(nodeModulesPath)
const saved = sizeBefore - sizeAfter
const percentSaved = ((saved / sizeBefore) * 100).toFixed(1)
// Summary
console.log(`\n✅ Pruning complete in ${duration}s`)
console.log(`\n📊 Summary:`)
console.log(` Packages processed: ${packagesProcessed}`)
console.log(` Platforms kept: ${totalKept}`)
console.log(` Platforms removed: ${totalRemoved}`)
console.log(` Size before: ${formatBytes(sizeBefore)}`)
console.log(` Size after: ${formatBytes(sizeAfter)}`)
console.log(` Saved: ${formatBytes(saved)} (${percentSaved}%)`)
if (saved > 1024 * 1024) {
console.log(`\n🎉 Saved ${formatBytes(saved)}! Ready for staging.`)
} else {
console.log(`\n⚠️ Minimal savings. Prebuilds may already be pruned.`)
}