Skip to main content
Glama
runTask.ts7.07 kB
//------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ import { Writable } from 'node:stream'; //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** * Remove the given value from the array. */ const remove = <T>(array: T[], x: T): void => { const index = array.indexOf(x); if (index !== -1) { array.splice(index, 1); } }; const signals: Record<string, number> = { // Signal name mappings to their respective standard numeric codes. // See: https://man7.org/linux/man-pages/man7/signal.7.html SIGABRT: 6, // Abort signal from abort(3) SIGALRM: 14, // Timer signal from alarm(2) SIGBUS: 10, // Bus error (bad memory access) SIGCHLD: 20, // Child stopped or terminated SIGCONT: 19, // Continue if stopped SIGFPE: 8, // Floating point exception SIGHUP: 1, // Hangup detected on controlling terminal or death of controlling process SIGILL: 4, // Illegal Instruction SIGINT: 2, // Interrupt from keyboard (Ctrl+C) SIGKILL: 9, // Kill signal (cannot be caught or ignored) SIGPIPE: 13, // Broken pipe: write to pipe with no readers SIGQUIT: 3, // Quit from keyboard (Ctrl+\) SIGSEGV: 11, // Invalid memory reference (segmentation fault) SIGSTOP: 17, // Stop process (cannot be caught or ignored) SIGTERM: 15, // Termination signal SIGTRAP: 5, // Trace/breakpoint trap SIGTSTP: 18, // Stop typed at tty (Ctrl+Z) SIGTTIN: 21, // tty input for background process SIGTTOU: 22, // tty output for background process SIGUSR1: 30, // User-defined signal 1 SIGUSR2: 31, // User-defined signal 2 }; /** * Converts a signal name to a number. */ const convert = (signal: string): number => { return signals[signal] || 0; }; /** * Simple in-memory writable stream */ class MemoryStream extends Writable { private chunks: Buffer[] = []; _write( chunk: Buffer, _encoding: string, callback: (error?: Error | null) => void ): void { this.chunks.push(chunk); callback(); } toString(): string { return Buffer.concat(this.chunks).toString('utf8'); } } //------------------------------------------------------------------------------ // Types //------------------------------------------------------------------------------ interface TaskResult { name: string; code: number | null; signal?: string | null; } interface TaskQueueItem { name: string; index: number; } interface RunTaskOptions { stdout: NodeJS.WritableStream; stderr?: NodeJS.WritableStream; aggregateOutput?: boolean; continueOnError?: boolean; race?: boolean; maxParallel?: number; } interface TaskPromise extends Promise<TaskResult> { abort?: () => void; } //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ /** * Run npm-scripts of given names in parallel. * * If a npm-script exited with a non-zero code, this aborts other all npm-scripts. * * Note: This is a simplified version for our use case. * The full implementation would require the actual runTask function from npm-run-all. */ export const runTasks = ( tasks: string[], options: RunTaskOptions ): Promise<TaskResult[]> => { return new Promise((resolve, reject) => { if (tasks.length === 0) { resolve([]); return; } const results: TaskResult[] = tasks.map((task) => ({ name: task, code: undefined as any, })); const queue: TaskQueueItem[] = tasks.map((task, index) => ({ name: task, index, })); const promises: TaskPromise[] = []; let error: Error | null = null; let aborted = false; /** * Done. */ const done = (): void => { if (error == null) { resolve(results); } else { reject(error); } }; /** * Aborts all tasks. */ const abort = (): void => { if (aborted) { return; } aborted = true; if (promises.length === 0) { done(); } else { for (const p of promises) { p.abort?.(); } Promise.all(promises).then(done, reject); } }; /** * Runs a next task. */ const next = (): void => { if (aborted) { return; } if (queue.length === 0) { if (promises.length === 0) { done(); } return; } const originalOutputStream = options.stdout; const optionsClone = { ...options }; const writer = new MemoryStream(); if (options.aggregateOutput) { optionsClone.stdout = writer as any; } const task = queue.shift()!; // Note: This requires the actual runTask implementation from npm-run-all // For now, this is a placeholder that would need to be implemented const promise = Promise.resolve({ name: task.name, code: 0, signal: null, }) as TaskPromise; promises.push(promise); promise.then( (result) => { remove(promises, promise); if (aborted) { return; } if (options.aggregateOutput) { originalOutputStream.write(writer.toString()); } // Check if the task failed as a result of a signal, and // amend the exit code as a result. if ( result.code === null && result.signal !== null && result.signal !== undefined ) { // An exit caused by a signal must return a status code // of 128 plus the value of the signal code. // Ref: https://nodejs.org/api/process.html#process_exit_codes result.code = 128 + convert(result.signal); } // Save the result. results[task.index].code = result.code; // Aborts all tasks if it's an error. if (result.code) { error = new Error( `Task ${result.name} failed with code ${result.code}` ); if (!options.continueOnError) { abort(); return; } } // Aborts all tasks if options.race is true. if (options.race && !result.code) { abort(); return; } // Call the next task. next(); }, (thisError: Error) => { remove(promises, promise); if (!options.continueOnError || options.race) { error = thisError; abort(); return; } next(); } ); }; const max = options.maxParallel; const end = typeof max === 'number' && max > 0 ? Math.min(tasks.length, max) : tasks.length; for (let i = 0; i < end; ++i) { next(); } }); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server