Selaa lähdekoodia

Parallel install plugins

master
Palash Bansal 11 kuukautta sitten
vanhempi
commit
42fdc28494
No account linked to committer's email address
2 muutettua tiedostoa jossa 206 lisäystä ja 9 poistoa
  1. 28
    6
      scripts/each-plugin.mjs
  2. 178
    3
      scripts/utils.mjs

+ 28
- 6
scripts/each-plugin.mjs Näytä tiedosto

@@ -1,9 +1,31 @@
import {execEachPlugin} from './utils.mjs';
import {execEachPlugin, execEachPluginParallel} from './utils.mjs';

const command = process.argv.slice(2).join(' ')
if(!command) throw new Error('Command is required')
console.log(`Executing '${command}' in all plugins`)
const args = process.argv.slice(2);
const command = args.join(' ');

// Each plugin should have "prepare" that will also build the plugin
execEachPlugin(`npm ${command}`) // install dependencies
if (!command) {
throw new Error('Command is required');
}

// Check if parallel flag is provided
const isParallel = args.includes('--parallel') || args.includes('-p');
const cleanCommand = command.replace(/--parallel|-p/g, '').trim();

if (!cleanCommand) {
throw new Error('Command is required');
}

console.log(`Executing '${cleanCommand}' in all plugins${isParallel ? ' (parallel mode)' : ''}`);

try {
if (isParallel) {
// Use parallel execution
await execEachPluginParallel(cleanCommand);
} else {
// Use sequential execution (original behavior)
execEachPlugin(`npm ${cleanCommand}`);
}
} catch (error) {
console.error('❌ Execution failed:', error.message);
process.exit(1);
}

+ 178
- 3
scripts/utils.mjs Näytä tiedosto

@@ -1,6 +1,6 @@
import path from 'node:path';
import fs from 'node:fs';
import {execSync} from 'node:child_process';
import {execSync, spawn} from 'node:child_process';

export function loopPluginDirs (callback) {
const pathname = new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:\/)/, '$1')
@@ -14,13 +14,188 @@ export function loopPluginDirs (callback) {
if (!fs.existsSync(packageJsonPath)) continue;
callback(pluginDir, pluginFolder)
}

}

export function execEachPlugin (command, templates = false) {
loopPluginDirs((pluginDir, pluginFolder) => {
if(!templates && pluginFolder.startsWith('plugin-template-')) return;
if (!templates && pluginFolder.startsWith('plugin-template-')) return;
console.log(`Executing ${command} in ${pluginDir}`)
execSync(command, {cwd: pluginDir, stdio: 'inherit'})
})
}

// New parallel execution functions
export function getPluginDependencies (templates = false) {
const plugins = new Map();

loopPluginDirs((pluginDir, pluginFolder) => {
if (!templates && pluginFolder.startsWith('plugin-template-')) return;

const packageJsonPath = path.join(pluginDir, 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));

const dependencies = packageJson.dependencies || {};
const devDependencies = packageJson.devDependencies || {};

// Find local file dependencies (other plugins)
const localDeps = [];
const allDeps = {...dependencies, ...devDependencies};

for (const [depName, depVersion] of Object.entries(allDeps)) {
if (depVersion.startsWith('file:./../')) {
// Extract the plugin name from the file path
const filePath = depVersion.replace('file:./../', '');
const packageName = filePath.replace(/\/src\/$/, '')
if(!packageName.includes('/') && !packageName.includes('..'))
localDeps.push(packageName);
}
}

plugins.set(pluginFolder, {
name: pluginFolder,
dir: pluginDir,
deps: localDeps,
});
});

return plugins;
}

export function createInstallPlan (plugins) {
const plan = [];
const processed = new Set();
const processing = new Set();

function addToLevel (pluginName, level = 0) {
if (processed.has(pluginName) || processing.has(pluginName)) {
return level;
}

processing.add(pluginName);
const plugin = plugins.get(pluginName);

if (!plugin) {
processing.delete(pluginName);
return level;
}

let maxDepLevel = -1;

// Process dependencies first
for (const dep of plugin.deps) {
const depLevel = addToLevel(dep, level);
maxDepLevel = Math.max(maxDepLevel, depLevel);
}

const currentLevel = maxDepLevel + 1;

// Ensure we have enough levels in the plan
while (plan.length <= currentLevel) {
plan.push([]);
}

plan[currentLevel].push(plugin);
processed.add(pluginName);
processing.delete(pluginName);

return currentLevel;
}

// Add all plugins to the plan
for (const pluginName of plugins.keys()) {
addToLevel(pluginName);
}

return plan.filter(level => level.length > 0);
}

export async function execCommand (command, cwd) {
return new Promise((resolve, reject) => {
const child = spawn('npm', command.split(' ').slice(1), {
cwd,
stdio: 'pipe',
shell: true,
});

let stdout = '';
let stderr = '';

child.stdout.on('data', (data) => {
stdout += data.toString();
});

child.stderr.on('data', (data) => {
stderr += data.toString();
});

child.on('close', (code) => {
if (code === 0) {
resolve({stdout, stderr});
} else {
reject(new Error(`Command failed with code ${code}: ${stderr}`));
}
});
});
}

export async function execEachPluginParallel (command, templates = false) {
console.log('Building dependency graph for parallel execution...');

const plugins = getPluginDependencies(templates);
const installPlan = createInstallPlan(plugins);

console.log(`\nInstallation plan (${installPlan.length} levels):`);
installPlan.forEach((level, index) => {
console.log(`Level ${index + 1}: ${level.map(p => p.name).join(', ')}`);
});

console.log(`\nExecuting '${command}' in parallel...`);

for (let levelIndex = 0; levelIndex < installPlan.length; levelIndex++) {
const level = installPlan[levelIndex];
console.log(`\n--- Level ${levelIndex + 1} (${level.length} plugins) ---`);

const run = async (plugin) => {
const startTime = Date.now();
console.log(`Starting: ${command} in ${plugin.name}`);

try {
const result = await execCommand(`npm ${command}`, plugin.dir);
const duration = Date.now() - startTime;
console.log(`✓ Completed: ${plugin.name} (${duration}ms)`);
return {plugin: plugin.name, success: true, duration};
} catch (error) {
const duration = Date.now() - startTime;
console.error(`✗ Failed: ${plugin.name} (${duration}ms)`);
console.error(`Error: ${error.message}`);
return {plugin: plugin.name, success: false, duration, error: error.message};
}
}

const n = 6;
const groups = []
for (let i = 0; i < level.length; i += n) {
groups.push(level.slice(i, i + n));
}
const results = []
// Execute all plugins in this level in parallel
for (const group of groups) {
results.push(...await Promise.all(group.map(run)));
}

// Check if any failed
const failures = results.filter(r => !r.success);
if (failures.length > 0) {
console.error(`\n❌ ${failures.length} plugin(s) failed in level ${levelIndex + 1}:`);
failures.forEach(failure => {
console.error(` - ${failure.plugin}: ${failure.error}`);
});
throw new Error(`Installation failed for ${failures.length} plugin(s)`);
}

const totalTime = Math.max(...results.map(r => r.duration));
console.log(`✓ Level ${levelIndex + 1} completed in ${totalTime}ms`);
}

console.log('\n🎉 All plugins processed successfully!');
}

Loading…
Peruuta
Tallenna