UNPKG

packmate

Version:

Your smart and friendly assistant for dependency updates and cleanup.

102 lines (91 loc) 3.34 kB
import { execSync } from 'child_process'; import fs from 'fs'; import path from 'path'; import semver from 'semver'; import process from 'process'; import yaml from 'js-yaml'; // pnpm lock yaml 파싱 const pkgPath = path.resolve(process.cwd(), 'package.json'); const nodeModulesPath = path.resolve(process.cwd(), 'node_modules'); const lockPaths = { npm: path.resolve(process.cwd(), 'package-lock.json'), pnpm: path.resolve(process.cwd(), 'pnpm-lock.yaml'), yarn: path.resolve(process.cwd(), 'yarn.lock'), }; function getPkgJson() { if (!fs.existsSync(pkgPath)) throw new Error('package.json 없음'); return JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); } function getLockJson(packageManager) { try { if (packageManager === 'npm' && fs.existsSync(lockPaths.npm)) { return JSON.parse(fs.readFileSync(lockPaths.npm, 'utf-8')); } if (packageManager === 'pnpm' && fs.existsSync(lockPaths.pnpm)) { // pnpm lock yaml 파싱 예시 return yaml.load(fs.readFileSync(lockPaths.pnpm, 'utf-8')); } // yarn lock은 좀 더 복잡(별도 파서 필요) } catch (e) { console.warn(`lock file parse failed: ${e.message}`); return null; } return null; } function getInstalledVersion(pkgName, lockJson, pkgJson) { if (lockJson?.dependencies?.[pkgName]) { return lockJson.dependencies[pkgName].version; } try { const pkgModulePath = path.resolve(nodeModulesPath, pkgName, 'package.json'); if (fs.existsSync(pkgModulePath)) { const modPkg = JSON.parse(fs.readFileSync(pkgModulePath, 'utf-8')); if (modPkg.version) return modPkg.version; } } catch {} return pkgJson.dependencies?.[pkgName] || pkgJson.devDependencies?.[pkgName] || null; } // 여러 패키지 병렬 조회(최대 8개씩) async function getLatestVersions(pkgs) { const res = {}; const batchSize = 8; for (let i = 0; i < pkgs.length; i += batchSize) { const batch = pkgs.slice(i, i + batchSize); await Promise.all( batch.map(async (pkg) => { try { const version = execSync(`npm view ${pkg} version`, { encoding: 'utf-8' }).trim(); res[pkg] = version; } catch (e) { res[pkg] = null; console.error(`[npm view error] ${pkg}: ${e.message}`); } }), ); } return res; } export async function getUpdateCandidates(packageManager = 'npm') { const pkgJson = getPkgJson(); const lockJson = getLockJson(packageManager); const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies }; const candidates = []; const depNames = Object.keys(deps); // 최신버전 한번에 조회 const latests = await getLatestVersions(depNames); for (const pkgName of depNames) { const currentVersion = getInstalledVersion(pkgName, lockJson, pkgJson); if (!currentVersion || !semver.valid(semver.coerce(currentVersion))) continue; const latestVersion = latests[pkgName]; if (!latestVersion || !semver.valid(latestVersion)) continue; const currentSemVer = semver.coerce(currentVersion); if (semver.lt(currentSemVer, latestVersion)) { candidates.push({ name: pkgName, currentVersion: currentSemVer.version, latestVersion, updateType: semver.diff(currentSemVer.version, latestVersion), }); } } return candidates; }