update-nodejs-dependencies.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. #!/usr/bin/env node
  2. /**
  3. * Node.js 依赖列表更新脚本
  4. * 功能:对比 node_modules 中已安装的包和 dependencies.txt,如果不一致则更新 dependencies.txt
  5. */
  6. const fs = require('fs');
  7. const path = require('path');
  8. // 获取脚本所在目录和项目根目录
  9. const scriptDir = __dirname;
  10. const projectRoot = path.dirname(path.dirname(path.dirname(scriptDir)));
  11. const dependenciesFile = path.join(scriptDir, 'dependencies.txt');
  12. const nodeModulesPath = path.join(projectRoot, 'node_modules');
  13. // 颜色输出函数
  14. const colors = {
  15. reset: '\x1b[0m',
  16. red: '\x1b[31m',
  17. green: '\x1b[32m',
  18. yellow: '\x1b[33m',
  19. cyan: '\x1b[36m',
  20. white: '\x1b[37m'
  21. };
  22. function log(message, color = 'reset') {
  23. console.log(`${colors[color]}${message}${colors.reset}`);
  24. }
  25. /**
  26. * 获取 node_modules 中已安装的包列表(包含版本号)
  27. */
  28. function getInstalledPackages() {
  29. const installedPackages = {};
  30. if (!fs.existsSync(nodeModulesPath)) {
  31. log('[ERROR] node_modules not found', 'red');
  32. return null;
  33. }
  34. try {
  35. const entries = fs.readdirSync(nodeModulesPath, { withFileTypes: true });
  36. for (const entry of entries) {
  37. if (entry.isDirectory()) {
  38. const packageName = entry.name;
  39. // 跳过特殊目录
  40. if (packageName.startsWith('.') || packageName === 'node_modules') {
  41. continue;
  42. }
  43. // 处理普通包
  44. const packageJsonPath = path.join(nodeModulesPath, packageName, 'package.json');
  45. if (fs.existsSync(packageJsonPath)) {
  46. try {
  47. const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
  48. if (pkg.name && pkg.version) {
  49. installedPackages[pkg.name.toLowerCase()] = `${pkg.name}==${pkg.version}`;
  50. }
  51. } catch (error) {
  52. // 忽略解析错误
  53. }
  54. }
  55. // 处理 scoped 包(如 @babel/core)
  56. if (packageName.startsWith('@')) {
  57. try {
  58. const scopedPath = path.join(nodeModulesPath, packageName);
  59. const scopedEntries = fs.readdirSync(scopedPath, { withFileTypes: true });
  60. for (const scopedEntry of scopedEntries) {
  61. if (scopedEntry.isDirectory()) {
  62. const scopedPackageJsonPath = path.join(scopedPath, scopedEntry.name, 'package.json');
  63. if (fs.existsSync(scopedPackageJsonPath)) {
  64. try {
  65. const pkg = JSON.parse(fs.readFileSync(scopedPackageJsonPath, 'utf-8'));
  66. if (pkg.name && pkg.version) {
  67. installedPackages[pkg.name.toLowerCase()] = `${pkg.name}==${pkg.version}`;
  68. }
  69. } catch (error) {
  70. // 忽略解析错误
  71. }
  72. }
  73. }
  74. }
  75. } catch (error) {
  76. // 忽略错误
  77. }
  78. }
  79. }
  80. }
  81. } catch (error) {
  82. log(`[ERROR] Failed to read node_modules: ${error.message}`, 'red');
  83. return null;
  84. }
  85. return installedPackages;
  86. }
  87. /**
  88. * 读取 dependencies.txt 中的包列表
  89. */
  90. function readDependenciesFile() {
  91. if (!fs.existsSync(dependenciesFile)) {
  92. log(`[WARN] ${dependenciesFile} not found, will create new one`, 'yellow');
  93. return {};
  94. }
  95. const packages = {};
  96. try {
  97. const content = fs.readFileSync(dependenciesFile, 'utf-8');
  98. const lines = content.split('\n');
  99. for (const line of lines) {
  100. const trimmed = line.trim();
  101. if (trimmed && !trimmed.startsWith('#')) {
  102. // 提取包名(支持 ==, >=, <=, >, <, ~= 等版本操作符)
  103. if (trimmed.includes('==')) {
  104. const parts = trimmed.split('==', 2);
  105. packages[parts[0].trim().toLowerCase()] = trimmed;
  106. } else {
  107. // 如果没有版本号,使用整行
  108. const pkgName = trimmed.split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].trim();
  109. packages[pkgName.toLowerCase()] = trimmed;
  110. }
  111. }
  112. }
  113. } catch (error) {
  114. log(`[ERROR] Failed to read dependencies.txt: ${error.message}`, 'red');
  115. return {};
  116. }
  117. return packages;
  118. }
  119. /**
  120. * 对比并更新 dependencies.txt
  121. */
  122. function compareAndUpdate() {
  123. log('Comparing installed packages with dependencies.txt...', 'cyan');
  124. log('='.repeat(60), 'cyan');
  125. // 获取已安装的包
  126. const installedPackages = getInstalledPackages();
  127. if (installedPackages === null) {
  128. process.exit(1);
  129. }
  130. // 读取 dependencies.txt 中的包
  131. const filePackages = readDependenciesFile();
  132. // 对比差异
  133. const installedSet = new Set(Object.keys(installedPackages));
  134. const fileSet = new Set(Object.keys(filePackages));
  135. const addedPackages = [];
  136. const removedPackages = [];
  137. const changedPackages = [];
  138. // 检查新增的包
  139. for (const pkgName of installedSet) {
  140. if (!fileSet.has(pkgName)) {
  141. addedPackages.push(pkgName);
  142. }
  143. }
  144. // 检查删除的包
  145. for (const pkgName of fileSet) {
  146. if (!installedSet.has(pkgName)) {
  147. removedPackages.push(pkgName);
  148. }
  149. }
  150. // 检查版本变化
  151. for (const pkgName of installedSet) {
  152. if (fileSet.has(pkgName)) {
  153. if (installedPackages[pkgName] !== filePackages[pkgName]) {
  154. changedPackages.push(pkgName);
  155. }
  156. }
  157. }
  158. // 显示差异
  159. if (addedPackages.length > 0) {
  160. log(`\n[+] Added packages (${addedPackages.length}):`, 'green');
  161. addedPackages.sort().forEach(pkg => {
  162. log(` + ${installedPackages[pkg]}`, 'green');
  163. });
  164. }
  165. if (removedPackages.length > 0) {
  166. log(`\n[-] Removed packages (${removedPackages.length}):`, 'red');
  167. removedPackages.sort().forEach(pkg => {
  168. log(` - ${filePackages[pkg]}`, 'red');
  169. });
  170. }
  171. if (changedPackages.length > 0) {
  172. log(`\n[~] Changed packages (${changedPackages.length}):`, 'yellow');
  173. changedPackages.sort().forEach(pkg => {
  174. log(` ~ ${filePackages[pkg]} -> ${installedPackages[pkg]}`, 'yellow');
  175. });
  176. }
  177. // 判断是否需要更新
  178. if (addedPackages.length === 0 && removedPackages.length === 0 && changedPackages.length === 0) {
  179. log(`\n[OK] dependencies.txt is up to date`, 'green');
  180. log(` Total packages: ${Object.keys(installedPackages).length}`, 'green');
  181. return true;
  182. }
  183. // 更新 dependencies.txt
  184. log(`\nUpdating ${dependenciesFile}...`, 'cyan');
  185. // 获取所有已安装的包(按名称排序)
  186. const allPackages = Object.values(installedPackages).sort();
  187. // 写入文件(UTF-8 编码)
  188. try {
  189. fs.writeFileSync(dependenciesFile, allPackages.join('\n') + '\n', 'utf-8');
  190. log(`[OK] ${dependenciesFile} updated successfully`, 'green');
  191. log(` Total packages: ${allPackages.length}`, 'green');
  192. return true;
  193. } catch (error) {
  194. log(`[ERROR] Failed to write dependencies.txt: ${error.message}`, 'red');
  195. return false;
  196. }
  197. }
  198. /**
  199. * 主函数
  200. */
  201. function main() {
  202. if (!compareAndUpdate()) {
  203. process.exit(1);
  204. }
  205. process.exit(0);
  206. }
  207. if (require.main === module) {
  208. main();
  209. }