smartupdater
Version:
An efficient and automated solution to identify and update outdated packages in your Node.js projects with minimal effort.
179 lines (150 loc) • 6.44 kB
JavaScript
import chalk from 'chalk';
import { execa } from 'execa';
import ora from 'ora';
import fs from 'fs';
import path from 'path';
// Path to your project
const projectDir = process.cwd();
// Function to scan for outdated packages
const scanPackages = async () => {
const spinner = ora('Scanning for outdated packages...').start();
try {
const result = await execa('npm', ['outdated', '--json'], { cwd: projectDir });
// Handle case where stdout is empty
if (!result.stdout || result.stdout === '{}') {
spinner.fail('No outdated packages found.');
console.log(chalk.green('All packages are up to date! 🎉'));
return;
}
const outdatedPackages = JSON.parse(result.stdout);
spinner.succeed('Scan complete!\n');
console.log(chalk.bold('Outdated Dependencies:\n'));
if (Object.keys(outdatedPackages).length === 0) {
console.log(chalk.green('All packages are up to date! 🎉'));
return;
}
for (const pkg in outdatedPackages) {
const { current, wanted, latest } = outdatedPackages[pkg];
console.log(
`${chalk.yellow(pkg)}: ${chalk.red(current)} → ${chalk.green(latest)}`
);
console.log(` Compatible update: ${chalk.blue(wanted)}\n`);
}
// Proceed with updating outdated dependencies
await updateDependencies(outdatedPackages);
} catch (error) {
if (error.exitCode === 1 && error.stdout) {
// Handle the case where outdated packages are found (exit code 1 is normal here)
const outdatedPackages = JSON.parse(error.stdout);
spinner.succeed('Scan complete!\n');
console.log(chalk.bold('Outdated Dependencies:\n'));
for (const pkg in outdatedPackages) {
const { current, wanted, latest } = outdatedPackages[pkg];
console.log(
`${chalk.yellow(pkg)}: ${chalk.red(current)} → ${chalk.green(latest)}`
);
console.log(` Compatible update: ${chalk.blue(wanted)}\n`);
}
// Proceed with updating outdated dependencies
await updateDependencies(outdatedPackages);
} else {
// Handle other unexpected errors
spinner.fail('An error occurred while scanning for outdated packages.');
console.error(chalk.red('Failed to execute npm outdated:', error.message));
}
}
};
// Function to update dependencies
const updateDependencies = async (outdatedPackages) => {
const spinner = ora('Updating outdated dependencies...').start();
try {
// Check if outdated packages exist
if (Object.keys(outdatedPackages).length === 0) {
console.log(chalk.green('No outdated packages to update.'));
return;
}
// Run npm install to update the dependencies
await execa('npm', ['install', ...Object.keys(outdatedPackages)], {
cwd: projectDir,
});
spinner.succeed('Dependencies updated successfully!');
console.log(chalk.green('Updated Dependencies:\n'));
// Update the package.json file with the latest versions
const packageJsonPath = path.join(projectDir, 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
// Ensure the dependencies object exists
if (!packageJson.dependencies) {
packageJson.dependencies = {}; // Create empty dependencies if not present
}
for (const pkg in outdatedPackages) {
const { latest } = outdatedPackages[pkg];
// Update dependencies
if (packageJson.dependencies[pkg]) {
packageJson.dependencies[pkg] = `^${latest}`; // Correctly use template literals
console.log(`${chalk.yellow(pkg)}: Updated to ${chalk.green(latest)}`);
} else if (packageJson.devDependencies && packageJson.devDependencies[pkg]) {
packageJson.devDependencies[pkg] = `^${latest}`;
console.log(`${chalk.yellow(pkg)}: Updated to ${chalk.green(latest)}`);
}
}
// Write the updated package.json back to disk
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
console.log(chalk.green('package.json updated successfully!'));
console.log(chalk.green('All outdated packages have been updated! 🎉'));
} catch (error) {
console.error(chalk.red('Failed to update dependencies:', error.message));
spinner.fail('An error occurred while updating dependencies.');
await restoreBackup();
}
};
// Function to create a backup of important files (package.json, package-lock.json)
const backupFiles = async () => {
const backupDir = path.join(projectDir, 'backup');
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir); // Create the 'backup' folder if it doesn't exist
}
const filesToBackup = ['package.json', 'package-lock.json'];
try {
for (const file of filesToBackup) {
const srcPath = path.join(projectDir, file);
const destPath = path.join(backupDir, file);
if (fs.existsSync(srcPath)) {
fs.copyFileSync(srcPath, destPath);
console.log(`${chalk.green(file)} backed up successfully!`);
} else {
console.log(chalk.red(`${file} not found in the project.`));
}
}
} catch (error) {
console.error(chalk.red('Failed to backup files:', error.message));
}
};
// Function to restore the backup if the update fails
const restoreBackup = async () => {
const backupDir = path.join(projectDir, 'backup');
const filesToRestore = ['package.json', 'package-lock.json'];
try {
for (const file of filesToRestore) {
const srcPath = path.join(backupDir, file);
const destPath = path.join(projectDir, file);
if (fs.existsSync(srcPath)) {
fs.copyFileSync(srcPath, destPath);
console.log(`${chalk.green(file)} restored successfully!`);
} else {
console.log(chalk.red(`${file} not found in the backup.`));
}
}
} catch (error) {
console.error(chalk.red('Failed to restore backup:', error.message));
}
};
// Entry point for the CLI
(async () => {
console.log(chalk.cyan('Welcome to SmartUpdater! 🚀'));
// Backup current files before attempting updates
await backupFiles();
console.log(chalk.cyan('\nStarting the update process...\n'));
// Scan for outdated dependencies and update them if necessary
await scanPackages();
})();