#!/usr/bin/env node /** * Node.js 依赖安装和同步脚本 * 功能:检查、安装 package.json 中的依赖,然后同步所有已安装的包到 dependencies.txt */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); // 获取脚本所在目录和项目根目录 const scriptDir = __dirname; const projectRoot = path.dirname(path.dirname(scriptDir)); const packageJsonPath = path.join(projectRoot, 'package.json'); 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}`); } // 检查 package.json 是否存在 if (!fs.existsSync(packageJsonPath)) { log('[X] package.json not found', 'red'); process.exit(1); } // 读取 package.json const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); const allDependencies = {}; // 收集所有依赖 if (packageJson.dependencies) { Object.keys(packageJson.dependencies).forEach(depName => { allDependencies[depName] = { version: packageJson.dependencies[depName], type: 'dependency' }; }); } if (packageJson.devDependencies) { Object.keys(packageJson.devDependencies).forEach(depName => { allDependencies[depName] = { version: packageJson.devDependencies[depName], type: 'devDependency' }; }); } // 快速获取已安装的包列表(直接从 node_modules 文件夹读取) function getInstalledPackagesFromFilesystem() { const installedPackages = new Set(); if (!fs.existsSync(nodeModulesPath)) { return installedPackages; } 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; } // 检查是否是有效的包(有 package.json) const packageJsonPath = path.join(nodeModulesPath, packageName, 'package.json'); if (fs.existsSync(packageJsonPath)) { installedPackages.add(packageName.toLowerCase()); } // 处理 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 scopedPackageName = `${packageName}/${scopedEntry.name}`; const scopedPackageJsonPath = path.join(scopedPath, scopedEntry.name, 'package.json'); if (fs.existsSync(scopedPackageJsonPath)) { installedPackages.add(scopedPackageName.toLowerCase()); } } } } catch (error) { // 忽略错误 } } } } } catch (error) { // 忽略错误 } return installedPackages; } // 快速检查缺失的依赖(使用文件系统) const missingDependencies = []; let installedCount = 0; let missingCount = 0; // 一次性获取所有已安装的包(只检查一次文件系统) const installedPackagesSet = getInstalledPackagesFromFilesystem(); const depNames = Object.keys(allDependencies).sort(); for (const depName of depNames) { const depNameLower = depName.toLowerCase(); // 快速检查(使用已获取的集合) if (installedPackagesSet.has(depNameLower)) { installedCount++; } else { missingDependencies.push(depName); missingCount++; } } // Install missing dependencies with retry loop let maxRetries = 5; let retryCount = 0; let currentMissing = [...missingDependencies]; while (currentMissing.length > 0 && retryCount < maxRetries) { if (retryCount > 0) { log(`\nRetry ${retryCount}/${maxRetries - 1}: Re-checking missing dependencies...`, 'cyan'); } else { log(`[X] Missing ${currentMissing.length} package(s) out of ${Object.keys(allDependencies).length}`, 'red'); log('Missing dependencies:', 'yellow'); currentMissing.forEach(missing => { log(` - ${missing}`, 'red'); }); } log('\nInstalling missing dependencies...', 'yellow'); // Switch to project root directory process.chdir(projectRoot); // Try multiple npm registries in order const registries = [ { name: 'Tencent Cloud', url: 'https://mirrors.cloud.tencent.com/npm/' }, { name: 'Huawei Cloud', url: 'https://repo.huaweicloud.com/repository/npm/' }, { name: 'Taobao Mirror', url: 'https://registry.npmmirror.com' } ]; let installSuccess = false; for (const registry of registries) { log(`\nTrying ${registry.name} registry...`, 'cyan'); execSync(`npm config set registry ${registry.url}`, { stdio: 'inherit', cwd: projectRoot }); // Run npm install with output visible const installResult = execSync('npm install', { stdio: 'inherit', cwd: projectRoot, encoding: 'utf-8' }); // Check if installation was successful by re-checking packages const installedPackagesSetAfterInstall = getInstalledPackagesFromFilesystem(); const stillMissing = []; for (const depName of depNames) { const depNameLower = depName.toLowerCase(); if (!installedPackagesSetAfterInstall.has(depNameLower)) { stillMissing.push(depName); } } if (stillMissing.length === 0) { installSuccess = true; log(`\n[OK] Installation successful using ${registry.name} registry`, 'green'); break; } else { log(`\n[~] ${stillMissing.length} package(s) still missing, trying next registry...`, 'yellow'); } } if (!installSuccess) { // Final attempt with original registry log('\nTrying default npm registry...', 'cyan'); execSync('npm config set registry https://registry.npmjs.org/', { stdio: 'inherit', cwd: projectRoot }); execSync('npm install', { stdio: 'inherit', cwd: projectRoot }); } // Re-check installed packages after installation log('\nRe-checking installed packages...', 'cyan'); const installedPackagesSetAfterInstall = getInstalledPackagesFromFilesystem(); const stillMissing = []; for (const depName of depNames) { const depNameLower = depName.toLowerCase(); if (!installedPackagesSetAfterInstall.has(depNameLower)) { stillMissing.push(depName); } } if (stillMissing.length === 0) { log('[OK] All dependencies installed successfully', 'green'); currentMissing = []; break; } else if (stillMissing.length < currentMissing.length) { log(`[~] Progress: ${currentMissing.length - stillMissing.length} package(s) installed, ${stillMissing.length} remaining`, 'yellow'); currentMissing = stillMissing; retryCount++; } else { log(`[X] Still missing ${stillMissing.length} package(s)`, 'red'); stillMissing.forEach(missing => { log(` - ${missing}`, 'red'); }); retryCount++; currentMissing = stillMissing; } } if (currentMissing.length > 0) { log(`\n[X] Failed to install ${currentMissing.length} package(s) after ${retryCount} attempts:`, 'red'); currentMissing.forEach(missing => { log(` - ${missing}`, 'red'); }); log('\n[WARN] Some packages may require additional setup', 'yellow'); process.exit(1); } else if (missingCount === 0) { log(`[OK] All dependencies are installed (${Object.keys(allDependencies).length} packages)`, 'green'); } // 快速同步所有已安装的依赖到 dependencies.txt(只读取根级包,静默执行) function syncInstalledPackagesToFile() { const syncResult = []; if (!fs.existsSync(nodeModulesPath)) { return syncResult; } 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) { syncResult.push(`${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) { syncResult.push(`${pkg.name}==${pkg.version}`); } } catch (error) { // 忽略解析错误 } } } } } catch (error) { // 忽略错误 } } } } } catch (error) { // 忽略错误 } return syncResult; } // 同步所有已安装的依赖到 dependencies.txt(快速方法,静默执行) const syncResult = syncInstalledPackagesToFile(); // 去重并排序 const uniqueResult = [...new Set(syncResult)].sort(); // 写入文件(UTF-8 编码) fs.writeFileSync(dependenciesFile, uniqueResult.join('\n') + '\n', 'utf-8'); process.exit(0);