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
JavaScript
/**
* 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');
}