#!/usr/bin/env node /** * Node.js 依赖列表更新脚本 * 功能:对比 node_modules 中已安装的包和 dependencies.txt,如果不一致则更新 dependencies.txt */ const fs = require('fs'); const path = require('path'); // 获取脚本所在目录和项目根目录 const scriptDir = __dirname; const projectRoot = path.dirname(path.dirname(scriptDir)); const dependenciesFile = path.join(scriptDir, 'dependencies.txt'); const nodeModulesPath = path.join(projectRoot, 'node_modules'); // 颜色输出函数 const colors = { reset: '\x1b[0m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m', white: '\x1b[37m' }; function log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } /** * 获取 node_modules 中已安装的包列表(包含版本号) */ function getInstalledPackages() { const installedPackages = {}; if (!fs.existsSync(nodeModulesPath)) { log('[ERROR] node_modules not found', 'red'); return null; } try { const entries = fs.readdirSync(nodeModulesPath, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory()) { const packageName = entry.name; // 跳过特殊目录 if (packageName.startsWith('.') || packageName === 'node_modules') { continue; } // 处理普通包 const packageJsonPath = path.join(nodeModulesPath, packageName, 'package.json'); if (fs.existsSync(packageJsonPath)) { try { const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); if (pkg.name && pkg.version) { installedPackages[pkg.name.toLowerCase()] = `${pkg.name}==${pkg.version}`; } } catch (error) { // 忽略解析错误 } } // 处理 scoped 包(如 @babel/core) if (packageName.startsWith('@')) { try { const scopedPath = path.join(nodeModulesPath, packageName); const scopedEntries = fs.readdirSync(scopedPath, { withFileTypes: true }); for (const scopedEntry of scopedEntries) { if (scopedEntry.isDirectory()) { const scopedPackageJsonPath = path.join(scopedPath, scopedEntry.name, 'package.json'); if (fs.existsSync(scopedPackageJsonPath)) { try { const pkg = JSON.parse(fs.readFileSync(scopedPackageJsonPath, 'utf-8')); if (pkg.name && pkg.version) { installedPackages[pkg.name.toLowerCase()] = `${pkg.name}==${pkg.version}`; } } catch (error) { // 忽略解析错误 } } } } } catch (error) { // 忽略错误 } } } } } catch (error) { log(`[ERROR] Failed to read node_modules: ${error.message}`, 'red'); return null; } return installedPackages; } /** * 读取 dependencies.txt 中的包列表 */ function readDependenciesFile() { if (!fs.existsSync(dependenciesFile)) { log(`[WARN] ${dependenciesFile} not found, will create new one`, 'yellow'); return {}; } const packages = {}; try { const content = fs.readFileSync(dependenciesFile, 'utf-8'); const lines = content.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (trimmed && !trimmed.startsWith('#')) { // 提取包名(支持 ==, >=, <=, >, <, ~= 等版本操作符) if (trimmed.includes('==')) { const parts = trimmed.split('==', 2); packages[parts[0].trim().toLowerCase()] = trimmed; } else { // 如果没有版本号,使用整行 const pkgName = trimmed.split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].trim(); packages[pkgName.toLowerCase()] = trimmed; } } } } catch (error) { log(`[ERROR] Failed to read dependencies.txt: ${error.message}`, 'red'); return {}; } return packages; } /** * 对比并更新 dependencies.txt */ function compareAndUpdate() { log('Comparing installed packages with dependencies.txt...', 'cyan'); log('='.repeat(60), 'cyan'); // 获取已安装的包 const installedPackages = getInstalledPackages(); if (installedPackages === null) { process.exit(1); } // 读取 dependencies.txt 中的包 const filePackages = readDependenciesFile(); // 对比差异 const installedSet = new Set(Object.keys(installedPackages)); const fileSet = new Set(Object.keys(filePackages)); const addedPackages = []; const removedPackages = []; const changedPackages = []; // 检查新增的包 for (const pkgName of installedSet) { if (!fileSet.has(pkgName)) { addedPackages.push(pkgName); } } // 检查删除的包 for (const pkgName of fileSet) { if (!installedSet.has(pkgName)) { removedPackages.push(pkgName); } } // 检查版本变化 for (const pkgName of installedSet) { if (fileSet.has(pkgName)) { if (installedPackages[pkgName] !== filePackages[pkgName]) { changedPackages.push(pkgName); } } } // 显示差异 if (addedPackages.length > 0) { log(`\n[+] Added packages (${addedPackages.length}):`, 'green'); addedPackages.sort().forEach(pkg => { log(` + ${installedPackages[pkg]}`, 'green'); }); } if (removedPackages.length > 0) { log(`\n[-] Removed packages (${removedPackages.length}):`, 'red'); removedPackages.sort().forEach(pkg => { log(` - ${filePackages[pkg]}`, 'red'); }); } if (changedPackages.length > 0) { log(`\n[~] Changed packages (${changedPackages.length}):`, 'yellow'); changedPackages.sort().forEach(pkg => { log(` ~ ${filePackages[pkg]} -> ${installedPackages[pkg]}`, 'yellow'); }); } // 判断是否需要更新 if (addedPackages.length === 0 && removedPackages.length === 0 && changedPackages.length === 0) { log(`\n[OK] dependencies.txt is up to date`, 'green'); log(` Total packages: ${Object.keys(installedPackages).length}`, 'green'); return true; } // 更新 dependencies.txt log(`\nUpdating ${dependenciesFile}...`, 'cyan'); // 获取所有已安装的包(按名称排序) const allPackages = Object.values(installedPackages).sort(); // 写入文件(UTF-8 编码) try { fs.writeFileSync(dependenciesFile, allPackages.join('\n') + '\n', 'utf-8'); log(`[OK] ${dependenciesFile} updated successfully`, 'green'); log(` Total packages: ${allPackages.length}`, 'green'); return true; } catch (error) { log(`[ERROR] Failed to write dependencies.txt: ${error.message}`, 'red'); return false; } } /** * 主函数 */ function main() { if (!compareAndUpdate()) { process.exit(1); } process.exit(0); } if (require.main === module) { main(); }