|
|
@@ -0,0 +1,327 @@
|
|
|
+#!/usr/bin/env node
|
|
|
+/**
|
|
|
+ * Node.js 依赖安装和同步脚本
|
|
|
+ * 功能:检查、安装 package.json 中的依赖,然后同步所有已安装的包到 dependencies.txt
|
|
|
+ */
|
|
|
+
|
|
|
+const fs = require('fs');
|
|
|
+const path = require('path');
|
|
|
+const { execSync } = require('child_process');
|
|
|
+
|
|
|
+// 获取脚本所在目录和项目根目录(scriptDir = nodejs/dependences/arm64,项目根 = 再上一级)
|
|
|
+const scriptDir = __dirname;
|
|
|
+const projectRoot = path.dirname(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);
|