UNPKG

@xec-sh/core

Version:

Universal shell execution engine

174 lines 6.54 kB
export async function parallel(commands, engine, options = {}) { const { maxConcurrency = Infinity, stopOnError = false, timeout, onProgress } = options; const startTime = Date.now(); const results = []; const succeeded = []; const failed = []; const isProcessPromise = (obj) => obj && typeof obj.then === 'function' && 'pipe' in obj && 'nothrow' in obj; const promises = commands.map(cmd => { if (isProcessPromise(cmd)) { return cmd; } else { const normalizedCmd = typeof cmd === 'string' ? { command: cmd } : cmd; return executeWithTimeout(engine, normalizedCmd, timeout); } }); if (maxConcurrency === Infinity) { const settled = await Promise.allSettled(promises); for (let i = 0; i < settled.length; i++) { const result = settled[i]; if (result && result.status === 'fulfilled') { results.push(result.value); succeeded.push(result.value); } else if (result && result.status === 'rejected') { results.push(result.reason); failed.push(result.reason); if (stopOnError) break; } if (onProgress) { onProgress(i + 1, commands.length, succeeded.length, failed.length); } } } else { const executing = []; let index = 0; let shouldStop = false; async function executeNext() { if (shouldStop || index >= commands.length) return; const currentIndex = index++; const cmd = commands[currentIndex]; if (!cmd) return; try { let result; if (isProcessPromise(cmd)) { result = await cmd; } else { const normalizedCmd = typeof cmd === 'string' ? { command: cmd } : cmd; result = await executeWithTimeout(engine, normalizedCmd, timeout); } results[currentIndex] = result; succeeded.push(result); } catch (error) { results[currentIndex] = error; failed.push(error); if (stopOnError) { shouldStop = true; } } if (onProgress) { const completed = succeeded.length + failed.length; onProgress(completed, commands.length, succeeded.length, failed.length); } if (!shouldStop && index < commands.length) { await executeNext(); } } for (let i = 0; i < Math.min(maxConcurrency, commands.length); i++) { executing.push(executeNext()); } await Promise.all(executing); } return { results, succeeded, failed, duration: Date.now() - startTime }; } async function executeWithTimeout(engine, command, timeout) { if (!timeout) { return engine.execute(command); } const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const commandWithSignal = { ...command, signal: controller.signal }; return await engine.execute(commandWithSignal); } finally { clearTimeout(timeoutId); } } export class ParallelEngine { constructor(engine) { this.engine = engine; } async all(commands, options) { const result = await parallel(commands, this.engine, { ...options, stopOnError: true }); if (result.failed.length > 0) { throw result.failed[0]; } return result.succeeded; } async settled(commands, options) { return parallel(commands, this.engine, options); } async race(commands) { const isProcessPromise = (obj) => obj && typeof obj.then === 'function' && 'pipe' in obj && 'nothrow' in obj; const promises = commands.map(cmd => { if (isProcessPromise(cmd)) { return cmd; } const normalizedCmd = typeof cmd === 'string' ? { command: cmd } : cmd; return this.engine.execute(normalizedCmd); }); return Promise.race(promises); } async map(items, fn, options) { const commands = items.map((item, index) => fn(item, index)); return parallel(commands, this.engine, options); } async filter(items, fn, options) { const isProcessPromise = (obj) => obj && typeof obj.then === 'function' && 'pipe' in obj && 'nothrow' in obj; const commandsWithItems = items.map((item, index) => ({ item, command: fn(item, index) })); const results = await Promise.allSettled(commandsWithItems.map(({ command }) => { if (isProcessPromise(command)) { return command; } const normalizedCmd = typeof command === 'string' ? { command } : command; return this.engine.execute(normalizedCmd); })); return commandsWithItems .filter((_, index) => { const result = results[index]; return result && result.status === 'fulfilled' && result.value.exitCode === 0; }) .map(({ item }) => item); } async some(commands, options) { const isProcessPromise = (obj) => obj && typeof obj.then === 'function' && 'pipe' in obj && 'nothrow' in obj; const promises = commands.map(cmd => { if (isProcessPromise(cmd)) { return cmd.then(() => true).catch(() => false); } const normalizedCmd = typeof cmd === 'string' ? { command: cmd } : cmd; return this.engine.execute(normalizedCmd) .then(() => true) .catch(() => false); }); const results = await Promise.race([ Promise.any(promises.map((p, index) => p.then(success => success ? index : Promise.reject()))), Promise.all(promises).then(() => false) ]); return typeof results === 'number'; } async every(commands, options) { const result = await parallel(commands, this.engine, { ...options, stopOnError: true }); return result.failed.length === 0; } } //# sourceMappingURL=parallel.js.map