threepipe
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

utils.mjs 6.7KB

1 år sedan
11 månader sedan
1 år sedan
1 år sedan
11 månader sedan
2 år sedan
11 månader sedan
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import path from 'node:path';
  2. import fs from 'node:fs';
  3. import {execSync, spawn} from 'node:child_process';
  4. export function loopPluginDirs (callback) {
  5. const pathname = new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:\/)/, '$1')
  6. const __dirname = path.dirname(pathname);
  7. const pluginsDir = path.resolve(__dirname, '../plugins')
  8. const pluginFolders = fs.readdirSync(pluginsDir)
  9. for (const pluginFolder of pluginFolders) {
  10. const pluginDir = path.join(pluginsDir, pluginFolder)
  11. const packageJsonPath = path.join(pluginDir, 'package.json')
  12. if (!fs.existsSync(packageJsonPath)) continue;
  13. callback(pluginDir, pluginFolder)
  14. }
  15. }
  16. export function execEachPlugin (command, templates = false) {
  17. loopPluginDirs((pluginDir, pluginFolder) => {
  18. if (!templates && pluginFolder.startsWith('plugin-template-')) return;
  19. console.log(`Executing ${command} in ${pluginDir}`)
  20. execSync(command, {cwd: pluginDir, stdio: 'inherit'})
  21. })
  22. }
  23. // New parallel execution functions
  24. export function getPluginDependencies (templates = false) {
  25. const plugins = new Map();
  26. loopPluginDirs((pluginDir, pluginFolder) => {
  27. if (!templates && pluginFolder.startsWith('plugin-template-')) return;
  28. const packageJsonPath = path.join(pluginDir, 'package.json');
  29. const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
  30. const dependencies = packageJson.dependencies || {};
  31. const devDependencies = packageJson.devDependencies || {};
  32. // Find local file dependencies (other plugins)
  33. const localDeps = [];
  34. const allDeps = {...dependencies, ...devDependencies};
  35. for (const [depName, depVersion] of Object.entries(allDeps)) {
  36. if (depVersion.startsWith('file:./../')) {
  37. // Extract the plugin name from the file path
  38. const filePath = depVersion.replace('file:./../', '');
  39. const packageName = filePath.replace(/\/src\/$/, '')
  40. if(!packageName.includes('/') && !packageName.includes('..'))
  41. localDeps.push(packageName);
  42. }
  43. }
  44. plugins.set(pluginFolder, {
  45. name: pluginFolder,
  46. dir: pluginDir,
  47. deps: localDeps,
  48. });
  49. });
  50. return plugins;
  51. }
  52. export function createInstallPlan (plugins) {
  53. const plan = [];
  54. const processed = new Set();
  55. const processing = new Set();
  56. function addToLevel (pluginName, level = 0) {
  57. if (processed.has(pluginName) || processing.has(pluginName)) {
  58. return level;
  59. }
  60. processing.add(pluginName);
  61. const plugin = plugins.get(pluginName);
  62. if (!plugin) {
  63. processing.delete(pluginName);
  64. return level;
  65. }
  66. let maxDepLevel = -1;
  67. // Process dependencies first
  68. for (const dep of plugin.deps) {
  69. const depLevel = addToLevel(dep, level);
  70. maxDepLevel = Math.max(maxDepLevel, depLevel);
  71. }
  72. const currentLevel = maxDepLevel + 1;
  73. // Ensure we have enough levels in the plan
  74. while (plan.length <= currentLevel) {
  75. plan.push([]);
  76. }
  77. plan[currentLevel].push(plugin);
  78. processed.add(pluginName);
  79. processing.delete(pluginName);
  80. return currentLevel;
  81. }
  82. // Add all plugins to the plan
  83. for (const pluginName of plugins.keys()) {
  84. addToLevel(pluginName);
  85. }
  86. return plan.filter(level => level.length > 0);
  87. }
  88. export async function execCommand (command, cwd) {
  89. return new Promise((resolve, reject) => {
  90. const child = spawn('npm', command.split(' ').slice(1), {
  91. cwd,
  92. stdio: 'pipe',
  93. shell: true,
  94. });
  95. let stdout = '';
  96. let stderr = '';
  97. child.stdout.on('data', (data) => {
  98. stdout += data.toString();
  99. });
  100. child.stderr.on('data', (data) => {
  101. stderr += data.toString();
  102. });
  103. child.on('close', (code) => {
  104. if (code === 0) {
  105. resolve({stdout, stderr});
  106. } else {
  107. reject(new Error(`Command failed with code ${code}: ${stderr}`));
  108. }
  109. });
  110. });
  111. }
  112. export async function execEachPluginParallel (command, templates = false) {
  113. console.log('Building dependency graph for parallel execution...');
  114. const plugins = getPluginDependencies(templates);
  115. const installPlan = createInstallPlan(plugins);
  116. console.log(`\nInstallation plan (${installPlan.length} levels):`);
  117. installPlan.forEach((level, index) => {
  118. console.log(`Level ${index + 1}: ${level.map(p => p.name).join(', ')}`);
  119. });
  120. console.log(`\nExecuting '${command}' in parallel...`);
  121. for (let levelIndex = 0; levelIndex < installPlan.length; levelIndex++) {
  122. const level = installPlan[levelIndex];
  123. console.log(`\n--- Level ${levelIndex + 1} (${level.length} plugins) ---`);
  124. const run = async (plugin) => {
  125. const startTime = Date.now();
  126. console.log(`Starting: ${command} in ${plugin.name}`);
  127. try {
  128. const result = await execCommand(`npm ${command}`, plugin.dir);
  129. const duration = Date.now() - startTime;
  130. console.log(`✓ Completed: ${plugin.name} (${duration}ms)`);
  131. return {plugin: plugin.name, success: true, duration};
  132. } catch (error) {
  133. const duration = Date.now() - startTime;
  134. console.error(`✗ Failed: ${plugin.name} (${duration}ms)`);
  135. console.error(`Error: ${error.message}`);
  136. return {plugin: plugin.name, success: false, duration, error: error.message};
  137. }
  138. }
  139. const n = 6;
  140. const groups = []
  141. for (let i = 0; i < level.length; i += n) {
  142. groups.push(level.slice(i, i + n));
  143. }
  144. const results = []
  145. // Execute all plugins in this level in parallel
  146. for (const group of groups) {
  147. results.push(...await Promise.all(group.map(run)));
  148. }
  149. // Check if any failed
  150. const failures = results.filter(r => !r.success);
  151. if (failures.length > 0) {
  152. console.error(`\n❌ ${failures.length} plugin(s) failed in level ${levelIndex + 1}:`);
  153. failures.forEach(failure => {
  154. console.error(` - ${failure.plugin}: ${failure.error}`);
  155. });
  156. throw new Error(`Installation failed for ${failures.length} plugin(s)`);
  157. }
  158. const totalTime = Math.max(...results.map(r => r.duration));
  159. console.log(`✓ Level ${levelIndex + 1} completed in ${totalTime}ms`);
  160. }
  161. console.log('\n🎉 All plugins processed successfully!');
  162. }