| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- #!/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);
|