|
|
|
|
|
|
|
|
import path from 'node:path'; |
|
|
import path from 'node:path'; |
|
|
import fs from 'node:fs'; |
|
|
import fs from 'node:fs'; |
|
|
import {execSync} from 'node:child_process'; |
|
|
|
|
|
|
|
|
import {execSync, spawn} from 'node:child_process'; |
|
|
|
|
|
|
|
|
export function loopPluginDirs (callback) { |
|
|
export function loopPluginDirs (callback) { |
|
|
const pathname = new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:\/)/, '$1') |
|
|
const pathname = new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:\/)/, '$1') |
|
|
|
|
|
|
|
|
if (!fs.existsSync(packageJsonPath)) continue; |
|
|
if (!fs.existsSync(packageJsonPath)) continue; |
|
|
callback(pluginDir, pluginFolder) |
|
|
callback(pluginDir, pluginFolder) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export function execEachPlugin (command, templates = false) { |
|
|
export function execEachPlugin (command, templates = false) { |
|
|
loopPluginDirs((pluginDir, pluginFolder) => { |
|
|
loopPluginDirs((pluginDir, pluginFolder) => { |
|
|
if(!templates && pluginFolder.startsWith('plugin-template-')) return; |
|
|
|
|
|
|
|
|
if (!templates && pluginFolder.startsWith('plugin-template-')) return; |
|
|
console.log(`Executing ${command} in ${pluginDir}`) |
|
|
console.log(`Executing ${command} in ${pluginDir}`) |
|
|
execSync(command, {cwd: pluginDir, stdio: 'inherit'}) |
|
|
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!'); |
|
|
|
|
|
} |