Skip to main content
Glama
run-command.ts3.95 kB
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process"; import { existsSync } from "node:fs"; import { resolve } from "node:path"; import { $ } from "bun"; import isInCi from "is-in-ci"; const commandsRunning: Record<string, ChildProcessWithoutNullStreams[]> = {}; const DEFAULT_ENV: Record<string, string> = { ...process.env, SETTLEMINT_ACCESS_TOKEN: process.env.SETTLEMINT_ACCESS_TOKEN!, SETTLEMINT_INSTANCE: process.env.SETTLEMINT_INSTANCE!, CI: isInCi ? "true" : "false", NODE_ENV: "development", CLI_WIDTH: "130", }; const CLI_DEV_ENTRY_POINT = resolve(__dirname, "../../sdk/cli/dist/cli.mjs"); const CLI_PROD_ENTRY_POINT = resolve(__dirname, "../../sdk/cli/dist/cli.js"); export type CommandResult = { output: string; cwd: string }; export function runCommand( testScope: string, args: string[], options: { env?: Record<string, string>; cwd?: string; stdin?: string } = {}, ) { let hasAccessTokenInOutput = false; const cwd = options.cwd ?? resolve(__dirname, "../../"); const cliEntry = existsSync(CLI_DEV_ENTRY_POINT) ? CLI_DEV_ENTRY_POINT : CLI_PROD_ENTRY_POINT; const cmds = [cliEntry, ...args]; const proc = spawn("node", cmds, { cwd, env: { ...DEFAULT_ENV, ...(options.env ?? {}), }, }); if (options.stdin) { proc.stdin.write(options.stdin); proc.stdin.end(); } const output: string[] = []; const errors: string[] = []; proc.stdout.on("data", (data) => { const dataString = data.toString(); if (!hasAccessTokenInOutput) { hasAccessTokenInOutput = checkOutputForAccessToken(dataString); } output.push(dataString); console.log(dataString); }); proc.stderr.on("data", (data) => { const dataString = data.toString(); if (!hasAccessTokenInOutput) { hasAccessTokenInOutput = checkOutputForAccessToken(dataString); } errors.push(dataString); console.error(dataString); }); if (!commandsRunning[testScope]) { commandsRunning[testScope] = []; } commandsRunning[testScope].push(proc); const p = new Promise<CommandResult>((resolve, reject) => { proc.on("close", (code: number) => { console.log(`child process exited with code ${code}`); const index = commandsRunning[testScope]?.indexOf(proc) ?? -1; if (index > -1) { commandsRunning[testScope]?.splice(index, 1); } if (hasAccessTokenInOutput) { reject(new Error("Access token found in output")); return; } if (code === 0 || code === null || code === 143) { resolve({ output: output.join("\n"), cwd }); } else { reject(new Error(`Command failed with code ${code}`)); } }); }); return { result: p, stdin: proc.stdin, stdout: proc.stdout, stderr: proc.stderr, kill: () => proc.pid && killProcess(proc.pid), }; } export function forceExitAllCommands(testScope: string) { if (Array.isArray(commandsRunning[testScope]) && commandsRunning[testScope].length > 0) { console.log( `[forceExitAllCommands] Killing all commands for test scope ${testScope} (count: ${commandsRunning[testScope].length})`, ); } // biome-ignore lint/complexity/noForEach: Iterating over array to kill processes commandsRunning[testScope]?.forEach((command) => { if (command.pid) killProcess(command.pid); }); commandsRunning[testScope] = []; } function killProcess(pid: number) { process.kill(pid, "SIGINT"); $`pkill -P ${pid}` .then(() => console.log(`[pkill] Killed process ${pid}`)) .catch((pkillError) => { $`kill -9 ${pid}` .then(() => console.log(`[kill] Killed process ${pid}`)) .catch((killError) => { console.error(`Failed to kill process ${pid}`, { pkillError, killError, }); }); }); } function checkOutputForAccessToken(output: string) { return /sm_(pat|aat|sat)_[0-9a-zA-Z]+/g.test(output); }

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/settlemint/sdk'

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