| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- #!/usr/bin/env node
- /**
- * Node.js 依赖:安装(package.json → nodejs/node/node_modules)与同步(→ dependencies.txt)
- * 用法:node nodejs-dependencies-install.js [--update]
- * 无参数:检查 package.json,安装缺失依赖,并同步到 dependencies.txt
- * --update:仅对比 node_modules 与 dependencies.txt,不一致时更新 dependencies.txt
- */
- const fs = require('fs');
- const path = require('path');
- const { execSync } = require('child_process');
- const scriptDir = __dirname;
- const nodejsDir = path.dirname(path.dirname(scriptDir));
- const projectRoot = path.dirname(nodejsDir);
- const nodeDir = path.join(nodejsDir, 'node');
- const nodeModulesPath = path.join(nodeDir, 'node_modules');
- const packageJsonPath = path.join(projectRoot, 'package.json');
- const dependenciesFile = path.join(scriptDir, 'dependencies.txt');
- const nodeExe = path.join(nodeDir, process.platform === 'win32' ? 'node.exe' : 'node');
- const npmCli = path.join(nodeDir, 'node_modules', 'npm', 'bin', 'npm-cli.js');
- if (!fs.existsSync(nodeDir) || !fs.existsSync(npmCli)) {
- console.error('[X] nodejs/node or nodejs/node/node_modules/npm not found. Run nodejs/install-node-modules.bat first.');
- process.exit(1);
- }
- process.env.PATH = nodeDir + path.delimiter + (process.env.PATH || '');
- 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 读取已安装包,返回 { [nameLower]: 'name==version' },排除 npm、corepack */
- function getInstalledPackages() {
- const out = {};
- if (!fs.existsSync(nodeModulesPath)) return out;
- try {
- const entries = fs.readdirSync(nodeModulesPath, { withFileTypes: true });
- for (const entry of entries) {
- if (!entry.isDirectory()) continue;
- const packageName = entry.name;
- if (packageName.startsWith('.') || packageName === 'node_modules' || packageName === 'npm' || packageName === 'corepack') continue;
- const pkgPath = path.join(nodeModulesPath, packageName, 'package.json');
- if (fs.existsSync(pkgPath)) {
- try {
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
- if (pkg.name && pkg.version) out[pkg.name.toLowerCase()] = `${pkg.name}==${pkg.version}`;
- } catch (_) {}
- }
- if (packageName.startsWith('@')) {
- try {
- const scopedPath = path.join(nodeModulesPath, packageName);
- const sub = fs.readdirSync(scopedPath, { withFileTypes: true });
- for (const e of sub) {
- if (!e.isDirectory()) continue;
- const subPath = path.join(scopedPath, e.name, 'package.json');
- if (fs.existsSync(subPath)) {
- try {
- const pkg = JSON.parse(fs.readFileSync(subPath, 'utf-8'));
- if (pkg.name && pkg.version) out[pkg.name.toLowerCase()] = `${pkg.name}==${pkg.version}`;
- } catch (_) {}
- }
- }
- } catch (_) {}
- }
- }
- } catch (_) {}
- return out;
- }
- /** 读取 dependencies.txt 为 { [nameLower]: line } */
- function readDependenciesFile() {
- const out = {};
- if (!fs.existsSync(dependenciesFile)) return out;
- const content = fs.readFileSync(dependenciesFile, 'utf-8');
- for (const line of content.split('\n')) {
- const t = line.trim();
- if (!t || t.startsWith('#')) continue;
- const name = t.includes('==') ? t.split('==', 2)[0].trim() : t.split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].trim();
- if (name) out[name.toLowerCase()] = t;
- }
- return out;
- }
- /** 将已安装列表写入 dependencies.txt */
- function writeDependenciesFile(installed) {
- const lines = [...new Set(Object.values(installed))].sort();
- fs.writeFileSync(dependenciesFile, lines.join('\n') + '\n', 'utf-8');
- }
- /** 仅更新模式:对比 node_modules 与 dependencies.txt,不一致则写回 */
- function runUpdateOnly() {
- log('Comparing node_modules with dependencies.txt...', 'cyan');
- const installed = getInstalledPackages();
- if (Object.keys(installed).length === 0) {
- log('[ERROR] nodejs/node/node_modules not found or empty.', 'red');
- process.exit(1);
- }
- const filePkgs = readDependenciesFile();
- const instSet = new Set(Object.keys(installed));
- const fileSet = new Set(Object.keys(filePkgs));
- const added = [...instSet].filter(k => !fileSet.has(k));
- const removed = [...fileSet].filter(k => !instSet.has(k));
- const changed = [...instSet].filter(k => fileSet.has(k) && installed[k] !== filePkgs[k]);
- if (added.length) {
- log(`\n[+] Added (${added.length}):`, 'green');
- added.sort().slice(0, 10).forEach(k => log(` + ${installed[k]}`, 'green'));
- if (added.length > 10) log(` ... and ${added.length - 10} more`, 'green');
- }
- if (removed.length) {
- log(`\n[-] Removed (${removed.length}):`, 'red');
- removed.sort().slice(0, 10).forEach(k => log(` - ${filePkgs[k]}`, 'red'));
- if (removed.length > 10) log(` ... and ${removed.length - 10} more`, 'red');
- }
- if (changed.length) {
- log(`\n[~] Changed (${changed.length}):`, 'yellow');
- changed.sort().slice(0, 10).forEach(k => log(` ~ ${filePkgs[k]} -> ${installed[k]}`, 'yellow'));
- if (changed.length > 10) log(` ... and ${changed.length - 10} more`, 'yellow');
- }
- if (added.length === 0 && removed.length === 0 && changed.length === 0) {
- log(`\n[OK] dependencies.txt is up to date (${Object.keys(installed).length} packages)`, 'green');
- return;
- }
- log(`\nUpdating ${dependenciesFile}...`, 'cyan');
- writeDependenciesFile(installed);
- log(`[OK] dependencies.txt updated (${Object.keys(installed).length} packages)`, 'green');
- }
- function main() {
- const updateOnly = process.argv.includes('--update');
- if (updateOnly) {
- runUpdateOnly();
- process.exit(0);
- }
- if (!fs.existsSync(packageJsonPath)) {
- log('[X] package.json not found', 'red');
- process.exit(1);
- }
- 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' };
- });
- }
- const installed = getInstalledPackages();
- const depNames = Object.keys(allDependencies).sort();
- const missingDependencies = depNames.filter(depName => !installed[depName.toLowerCase()]);
- let installedCount = depNames.length - missingDependencies.length;
- let missingCount = missingDependencies.length;
- if (missingCount > 0) {
- log(`[X] Missing ${missingCount} package(s) out of ${depNames.length}`, 'red');
- log('Missing dependencies:', 'yellow');
- missingDependencies.forEach(m => log(` - ${m}`, 'red'));
- } else {
- log(`[OK] All dependencies are installed (${depNames.length} packages)`, 'green');
- writeDependenciesFile(getInstalledPackages());
- process.exit(0);
- }
- let maxRetries = 5;
- let retryCount = 0;
- let currentMissing = [...missingDependencies];
- const npmInstallPrefix = `--prefix ${path.join(nodeDir).replace(/\\/g, '/')}`;
- 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' }
- ];
- while (currentMissing.length > 0 && retryCount < maxRetries) {
- if (retryCount > 0) log(`\nRetry ${retryCount}/${maxRetries - 1}...`, 'cyan');
- log('\nInstalling missing dependencies...', 'yellow');
- process.chdir(projectRoot);
- let installSuccess = false;
- for (const registry of registries) {
- log(`\nTrying ${registry.name} registry...`, 'cyan');
- try {
- execSync(`npm config set registry ${registry.url}`, { stdio: 'inherit', cwd: projectRoot, env: process.env });
- execSync(`npm install ${npmInstallPrefix} --ignore-engines`, { stdio: 'inherit', cwd: projectRoot, encoding: 'utf-8', env: process.env });
- } catch (_) {}
- const after = getInstalledPackages();
- const stillMissing = depNames.filter(depName => !after[depName.toLowerCase()]);
- if (stillMissing.length === 0) {
- installSuccess = true;
- log(`\n[OK] Installation successful using ${registry.name}`, 'green');
- break;
- }
- log(`\n[~] ${stillMissing.length} still missing, trying next registry...`, 'yellow');
- }
- if (!installSuccess) {
- try {
- log('\nTrying default npm registry...', 'cyan');
- execSync('npm config set registry https://registry.npmjs.org/', { stdio: 'inherit', cwd: projectRoot, env: process.env });
- execSync(`npm install ${npmInstallPrefix} --ignore-engines`, { stdio: 'inherit', cwd: projectRoot, env: process.env });
- } catch (_) {}
- }
- const after = getInstalledPackages();
- const stillMissing = depNames.filter(depName => !after[depName.toLowerCase()]);
- if (stillMissing.length === 0) {
- log('[OK] All dependencies installed successfully', 'green');
- break;
- }
- if (stillMissing.length < currentMissing.length) {
- log(`[~] Progress: ${currentMissing.length - stillMissing.length} installed, ${stillMissing.length} remaining`, 'yellow');
- currentMissing = stillMissing;
- retryCount++;
- } else {
- log(`[X] Still missing ${stillMissing.length} package(s)`, 'red');
- stillMissing.slice(0, 10).forEach(m => log(` - ${m}`, 'red'));
- currentMissing = stillMissing;
- retryCount++;
- }
- }
- const finalInstalled = getInstalledPackages();
- const stillMissing = depNames.filter(depName => !finalInstalled[depName.toLowerCase()]);
- if (stillMissing.length > 0) {
- log(`\n[X] Failed to install ${stillMissing.length} package(s)`, 'red');
- stillMissing.forEach(m => log(` - ${m}`, 'red'));
- process.exit(1);
- }
- writeDependenciesFile(finalInstalled);
- process.exit(0);
- }
- main();
|