UNPKG

@handykit/cli

Version:
215 lines β€’ 7.86 kB
import fs from "fs/promises"; import path from "path"; import { exec } from "child_process"; import util from "util"; const execPromise = util.promisify(exec); /** * Display outdated dependencies in a clean, readable format, * including their location and who depends on them. */ export async function showOutdatedPackages() { try { const { stdout } = await execPromise("npm outdated --long --json", { cwd: process.cwd(), }); const data = stdout.trim(); if (!data) { console.log("βœ… All dependencies are up to date.\n"); return; } const outdated = JSON.parse(data); const entries = Object.entries(outdated); if (entries.length === 0) { console.log("βœ… All dependencies are up to date.\n"); return; } console.log("πŸ“¦ Outdated dependencies:\n"); for (const [pkg, info] of entries) { console.log(`β€’ ${pkg}`); console.log(` β€’ Current: ${info.current}`); console.log(` β€’ Wanted: ${info.wanted}`); console.log(` β€’ Latest: ${info.latest}`); if (info.location) console.log(` β€’ Location: ${info.location}`); if (info["depended by"]) console.log(` β€’ Depended by: ${info["depended by"]}`); if (info.type) console.log(` β€’ Type: ${info.type}`); console.log(""); // blank line for spacing } } catch (err) { // Handle non-zero exit code but valid stdout if (err.stdout) { try { const outdated = JSON.parse(err.stdout); const entries = Object.entries(outdated); if (entries.length === 0) { console.log("βœ… All dependencies are up to date.\n"); return; } console.log("πŸ“¦ Outdated dependencies:\n"); for (const [pkg, info] of entries) { console.log(`β€’ ${pkg}`); console.log(` β€’ Current: ${info.current}`); console.log(` β€’ Wanted: ${info.wanted}`); console.log(` β€’ Latest: ${info.latest}`); if (info.location) console.log(` β€’ Location: ${info.location}`); if (info["depended by"]) console.log(` β€’ Depended by: ${info["depended by"]}`); if (info.type) console.log(` β€’ Type: ${info.type}`); console.log(""); } return; } catch { // Parsing failedβ€”fall through to error } } console.error("❌ Failed to get outdated packages:", err.stderr || err.message); } } /** * Upgrade dependencies by type: standard (npm update), minor, or major. * Supports dry-run mode to preview changes. */ export async function manageDependencies(upgradeType, dryRun = false) { const pkgPath = path.resolve(process.cwd(), "package.json"); let pkg; try { const pkgRaw = await fs.readFile(pkgPath, "utf8"); pkg = JSON.parse(pkgRaw); } catch (err) { console.error("❌ Failed to read package.json at", pkgPath); return; } // Fetch outdated packages upfront let outdated = {}; try { const { stdout } = await execPromise("npm outdated --json", { cwd: process.cwd(), }); if (stdout.trim()) { outdated = JSON.parse(stdout); } } catch (err) { if (err.stdout) { try { const parsed = JSON.parse(err.stdout); outdated = Object.keys(parsed).length === 0 ? {} : parsed; } catch { console.error("❌ Failed to parse npm outdated output:", err.stdout); return; } } else { console.error("❌ Failed to get outdated packages:", err.stderr || err.message); return; } } const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}), }; if (upgradeType === "standard") { // npm update respects semver ranges and upgrades to 'wanted' const updatable = Object.entries(outdated).filter(([_, info]) => info.wanted !== info.current); if (dryRun) { if (updatable.length === 0) { console.log("βœ… All dependencies are up to date. Nothing to update."); return; } console.log("πŸ§ͺ Dry-run: The following packages would be updated with `npm update`:\n"); for (const [pkg, info] of updatable) { console.log(`β€’ ${pkg}: ${info.current} β†’ ${info.wanted}`); } console.log(""); } else { console.log("πŸ” Running npm update..."); await runCommand("npm update --save"); } return; } // For minor and major upgrades: filter packages by semver difference const upgradesToApply = []; // [pkg, current, latest] for (const [dep, currentVersion] of Object.entries(allDeps)) { const outdatedInfo = outdated[dep]; if (!outdatedInfo) continue; // up to date or not listed const currentMajor = getMajor(currentVersion); const currentMinor = getMinor(currentVersion); const latestVersion = outdatedInfo.latest; const latestMajor = getMajor(latestVersion); const latestMinor = getMinor(latestVersion); const isMinor = latestMajor === currentMajor && latestMinor > currentMinor; const isMajor = latestMajor > currentMajor; if ((upgradeType === "minor" && isMinor) || (upgradeType === "major" && isMajor)) { upgradesToApply.push([dep, currentVersion, latestVersion]); } } if (dryRun) { if (upgradesToApply.length === 0) { console.log(`βœ… All dependencies are up to date for ${upgradeType} upgrades.`); return; } console.log(`πŸ§ͺ Dry-run: The following packages would be upgraded (${upgradeType}):\n`); for (const [dep, current, latest] of upgradesToApply) { console.log(`β€’ ${dep}: ${current} β†’ ${latest}`); } console.log(""); } else { for (const [dep, , latest] of upgradesToApply) { console.log(`⬆️ Upgrading ${dep} to version ${latest}...`); await runCommand(`npm install ${dep}@${latest} --save-exact`); } console.log("\nβœ… Dependency upgrades complete.\n"); } } /** * Parse major version number from semver string. */ function getMajor(version) { return parseInt(version.replace(/^[^\d]*/, "").split(".")[0] || "0", 10); } /** * Parse minor version number from semver string. */ function getMinor(version) { return parseInt(version.replace(/^[^\d]*/, "").split(".")[1] || "0", 10); } /** * Get the latest published version of a package. */ async function getLatestVersion(pkgName) { try { const { stdout } = await execPromise(`npm view ${pkgName} version`, { cwd: process.cwd(), }); return stdout.trim(); } catch { return null; } } /** * Run a command in the CWD and stream output to console. */ async function runCommand(cmd) { console.log(`> ${cmd}`); const { stdout, stderr } = await execPromise(cmd, { cwd: process.cwd(), }); if (stdout) process.stdout.write(stdout); if (stderr) process.stderr.write(stderr); } //# sourceMappingURL=deps-manage.js.map