UNPKG

codebase-asset-optimizer

Version:

Professional CLI development tool for optimizing and managing assets in codebases. Detects unused assets, optimizes images to WebP, optimizes videos, and automatically replaces asset references. GIFs are preserved unchanged to maintain animation functiona

305 lines (259 loc) • 10.5 kB
#!/usr/bin/env node /** * Post-install script for codebase-asset-optimizer * Prompts users to add convenient npm scripts to their package.json * * This version uses CommonJS for maximum compatibility and graceful error handling */ const fs = require('fs'); const path = require('path'); const SUGGESTED_SCRIPTS = { 'optimize:assets': 'npx asset-optimizer optimize', 'optimize:assets:dry': 'npx asset-optimizer optimize --dry-run', 'audit:assets': 'npx asset-optimizer audit', 'clean:assets': 'npx asset-optimizer clean', 'assets:interactive': 'npx asset-optimizer interactive', 'benchmark:assets': 'node node_modules/codebase-asset-optimizer/dist/scripts/benchmark.js' }; const SCRIPT_DESCRIPTIONS = { 'optimize:assets': 'Optimize all assets (images → WebP, video compression)', 'optimize:assets:dry': 'Preview what would be optimized (safe, no changes)', 'audit:assets': 'Analyze asset usage and optimization opportunities', 'clean:assets': 'Remove unused assets from your project', 'assets:interactive': 'Interactive mode with guided optimization steps', 'benchmark:assets': 'Show performance impact and potential savings' }; function findPackageJson() { // Look for package.json starting from current directory and going up let currentDir = process.cwd(); const root = path.parse(currentDir).root; // For Turborepo environments, also check common workspace patterns const potentialDirs = [ currentDir, process.env.INIT_CWD || '', // npm sets this to the directory where npm was invoked process.env.PWD || '', ...process.argv.slice(2).filter(arg => !arg.startsWith('-') && fs.existsSync(arg)) ].filter(Boolean); console.log('šŸ” Searching for package.json in:', potentialDirs.length, 'potential directories'); // Check all potential directories first for (const dir of potentialDirs) { if (fs.existsSync(dir)) { const packageJsonPath = path.join(dir, 'package.json'); if (fs.existsSync(packageJsonPath)) { try { const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); // Verify this is a real project (not just node_modules) if (pkg.name && !pkg.name.startsWith('_') && !dir.includes('node_modules')) { console.log('āœ… Found project package.json:', packageJsonPath); return packageJsonPath; } } catch (e) { // Continue searching } } } } // Fallback to traditional directory traversal while (currentDir !== root) { const packageJsonPath = path.join(currentDir, 'package.json'); if (fs.existsSync(packageJsonPath)) { try { const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); // Avoid node_modules directories if (pkg.name && !currentDir.includes('node_modules')) { console.log('āœ… Found package.json via traversal:', packageJsonPath); return packageJsonPath; } } catch (e) { // Continue searching } } currentDir = path.dirname(currentDir); } return null; } function detectEnvironmentContext(packageJsonPath) { const projectDir = path.dirname(packageJsonPath); // Check if this is the asset optimizer development environment const isAssetOptimizerDev = fs.existsSync(path.join(projectDir, 'src', 'cli.ts')); // Check if there's a test-project directory const hasTestProject = fs.existsSync(path.join(projectDir, 'test-project')); // Check for common asset directories const commonAssetDirs = ['public', 'assets', 'static', 'src/assets', 'public/assets']; const assetDirs = []; for (const dir of commonAssetDirs) { const fullPath = path.join(projectDir, dir); if (fs.existsSync(fullPath)) { assetDirs.push(dir); } } return { isAssetOptimizerDev, hasTestProject, assetDirs, projectDir }; } async function promptUser() { try { // Try to load inquirer, but don't fail if it's not available const inquirer = require('inquirer'); const { addScripts } = await inquirer.prompt([ { type: 'confirm', name: 'addScripts', message: 'Add convenient npm scripts to your package.json?', default: true } ]); if (!addScripts) { return null; } const { selectedScripts } = await inquirer.prompt([ { type: 'checkbox', name: 'selectedScripts', message: 'Which scripts would you like to add?', choices: Object.keys(SUGGESTED_SCRIPTS).map(scriptName => ({ name: `${scriptName} - ${SCRIPT_DESCRIPTIONS[scriptName]}`, value: scriptName, checked: true })) } ]); return selectedScripts; } catch (error) { // Fallback to non-interactive mode if inquirer fails console.log('šŸ“ Interactive prompts not available, adding recommended scripts...'); return Object.keys(SUGGESTED_SCRIPTS); } } function main() { try { console.log('\nšŸŽ‰ Thank you for installing Codebase Asset Optimizer!\n'); // Debug environment info console.log('šŸ” Environment debug:'); console.log(' CWD:', process.cwd()); console.log(' INIT_CWD:', process.env.INIT_CWD || 'not set'); console.log(' PWD:', process.env.PWD || 'not set'); console.log(' Node version:', process.version); console.log(''); // Find package.json const packageJsonPath = findPackageJson(); if (!packageJsonPath) { console.log('šŸ“¦ No package.json found in current directory or parent directories.'); console.log(' You can still use the tool directly with: npx asset-optimizer\n'); return; } // Check if we're in the development environment (skip postinstall) const context = detectEnvironmentContext(packageJsonPath); if (context.isAssetOptimizerDev) { // Skip postinstall in development environment return; } console.log(`šŸ“¦ Found package.json: ${path.relative(process.cwd(), packageJsonPath)}`); // Provide contextual suggestions if (context.assetDirs.length > 0) { console.log(`šŸŽÆ Found asset directories: ${context.assetDirs.join(', ')}`); console.log(' Ready to optimize your assets!'); } // Read existing package.json let packageJson; try { const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8'); packageJson = JSON.parse(packageJsonContent); } catch (error) { console.log('āŒ Error reading package.json:', error.message); return; } // Check which scripts already exist const existingScripts = packageJson.scripts || {}; const availableScripts = Object.entries(SUGGESTED_SCRIPTS).filter( ([scriptName]) => !existingScripts[scriptName] ); if (availableScripts.length === 0) { console.log('āœ… All suggested scripts already exist in your package.json!'); console.log('\nYou can run:'); Object.keys(SUGGESTED_SCRIPTS).forEach(scriptName => { console.log(` npm run ${scriptName}`); }); // Provide contextual next steps if (context.hasTestProject) { console.log('\nšŸ’” Test project detected! Try:'); console.log(' npm run audit:assets test-project'); } else if (context.assetDirs.length > 0) { console.log('\nšŸ’” Quick start:'); console.log(' npm run audit:assets'); } return; } // Show UX benefits console.log('\nšŸš€ Available npm scripts to add:\n'); availableScripts.forEach(([scriptName, command]) => { console.log(` npm run ${scriptName}`); console.log(` └─ ${SCRIPT_DESCRIPTIONS[scriptName]}\n`); }); // For non-interactive mode, add recommended scripts console.log('šŸ“ Adding recommended scripts to package.json...'); // Add scripts if (!packageJson.scripts) { packageJson.scripts = {}; } const scriptsToAdd = ['audit:assets', 'optimize:assets:dry', 'optimize:assets']; let addedCount = 0; scriptsToAdd.forEach(scriptName => { if (!packageJson.scripts[scriptName] && SUGGESTED_SCRIPTS[scriptName]) { packageJson.scripts[scriptName] = SUGGESTED_SCRIPTS[scriptName]; addedCount++; } }); if (addedCount > 0) { // Write updated package.json try { fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); console.log(`\nāœ… Added ${addedCount} npm scripts to package.json!`); console.log('\nšŸŽÆ Quick Start:'); scriptsToAdd.forEach(scriptName => { if (packageJson.scripts[scriptName]) { console.log(` npm run ${scriptName}`); } }); // Contextual recommendations console.log('\nšŸ’” Recommended first step:'); if (context.assetDirs.length > 0) { console.log(' npm run audit:assets'); console.log(` └─ Analyze assets in: ${context.assetDirs.join(', ')}`); } else { console.log(' npm run audit:assets'); console.log(' └─ See what assets can be optimized in your project'); } console.log('\nšŸ›”ļø Always use dry-run first:'); console.log(' npm run optimize:assets:dry'); console.log(' └─ Preview changes before applying them\n'); } catch (error) { console.log('āŒ Error writing package.json:', error.message); console.log('\nšŸ’” You can manually add these scripts to your package.json:'); scriptsToAdd.forEach(scriptName => { if (SUGGESTED_SCRIPTS[scriptName]) { console.log(` "${scriptName}": "${SUGGESTED_SCRIPTS[scriptName]}"`); } }); } } else { console.log('šŸ“‹ No new scripts to add (they already exist)'); } } catch (error) { console.log('āŒ Post-install script encountered an error:', error.message); console.log('\nšŸ’” You can still use the tool directly:'); console.log(' npx asset-optimizer interactive'); console.log(' npx asset-optimizer audit'); console.log(' npx asset-optimizer optimize --dry-run'); } } // Run with error handling - never fail npm install try { main(); } catch (error) { console.log('āŒ Post-install script failed:', error.message); console.log('\nšŸ’” You can still use the tool directly: npx asset-optimizer'); }