#!/usr/bin/env node /** * Node.js 依赖:按需执行 npm install,并同步 dependencies.txt * 用法:node nodejs-dependencies-install.js [--update] * 无参数:若无 node_modules 或缺少依赖则 npm install,否则跳过 * --update:仅对比 node_modules 与 dependencies.txt,不一致时更新 dependencies.txt */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const scriptDir = __dirname; const projectRoot = path.resolve(scriptDir, '..', '..', '..'); const config = require(path.join(projectRoot, 'configs', 'config.js')); const nodeDir = config.nodeDir || path.dirname(config.nodejsPath); const nodeModulesPath = path.join(nodeDir, 'node_modules'); const dependenciesFile = path.join(scriptDir, 'dependencies.txt'); const nodeExe = path.join(nodeDir, process.platform === 'win32' ? 'node.exe' : 'node'); const npmCmd = config.npmCmdPath || path.join(nodeDir, process.platform === 'win32' ? 'npm.cmd' : 'npm'); if (!fs.existsSync(nodeDir)) { console.error('[X] nodejs/node not found.'); process.exit(1); } if (!fs.existsSync(nodeModulesPath)) { fs.mkdirSync(nodeModulesPath, { recursive: true }); } 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' }; function log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } function getRequiredPackages() { const pkgPath = path.join(projectRoot, 'package.json'); if (!fs.existsSync(pkgPath)) return []; try { const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }; return Object.keys(deps); } catch (_) { return []; } } function isPackageInstalled(name) { if (name.startsWith('@')) { const parts = name.slice(1).split('/'); const pkgPath = path.join(nodeModulesPath, '@' + parts[0], parts[1] || ''); return fs.existsSync(path.join(pkgPath, 'package.json')); } return fs.existsSync(path.join(nodeModulesPath, name, 'package.json')); } function needsInstall() { const installed = getInstalledPackages(); const installedCount = Object.keys(installed).length; const required = getRequiredPackages(); if (required.length === 0) return true; if (installedCount === 0) return true; for (const name of required) { if (!isPackageInstalled(name)) return true; } try { execSync(`"${npmCmd}" ls --prefix ${path.join(nodeDir).replace(/\\/g, '/')}`, { stdio: 'pipe', cwd: projectRoot, env: process.env }); } catch (_) { return true; } return false; } 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; } function writeDependenciesFile(installed) { const lines = [...new Set(Object.values(installed))].sort(); fs.writeFileSync(dependenciesFile, lines.join('\n') + '\n', 'utf-8'); } 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); } writeDependenciesFile(installed); log(`[OK] dependencies.txt updated (${Object.keys(installed).length} packages)`, 'green'); } function main() { if (process.argv.includes('--update')) { runUpdateOnly(); process.exit(0); } if (!fs.existsSync(path.join(projectRoot, 'package.json'))) { log('[X] package.json not found', 'red'); process.exit(1); } if (!needsInstall()) { log('[OK] Dependencies complete, skipping npm install', 'green'); const installed = getInstalledPackages(); if (Object.keys(installed).length > 0) { writeDependenciesFile(installed); } return; } log('Running npm install...', 'cyan'); process.chdir(projectRoot); const npmInstallPrefix = `--prefix ${path.join(nodeDir).replace(/\\/g, '/')}`; try { execSync(`"${npmCmd}" install ${npmInstallPrefix} --ignore-engines --no-audit`, { stdio: 'inherit', cwd: projectRoot, env: process.env }); } catch (e) { log('[X] npm install failed', 'red'); process.exit(1); } const installed = getInstalledPackages(); writeDependenciesFile(installed); log(`[OK] Installed and synced to dependencies.txt (${Object.keys(installed).length} packages)`, 'green'); } main();