Skip to main content
Glama
utils.ts3.3 kB
// src/utils.ts import type { Container } from 'dockerode'; import type { Readable } from 'stream'; import fs from 'fs'; /** * Stream a container’s stdout+stderr and resolve as soon as we see `pattern`. * Rejects if we hit the timeout first. */ export function waitForLog( container: Container, pattern: RegExp, timeoutMs: number ): Promise<void> { return new Promise((resolve, reject) => { let logStream: Readable; const timer = setTimeout(() => { // on timeout, destroy the stream to stop Docker pulling logs logStream.destroy(); reject(new Error(`Timed out waiting for log ${pattern}`)); }, timeoutMs); // cast to any so TS won't try to pick the wrong overload (container.logs as any)( { stdout: true, stderr: true, follow: true, since: 0, }, (err: any, stream: any) => { if (err) { clearTimeout(timer); return reject(err); } // At runtime Dockerode gives a Node Readable here logStream = stream as Readable; logStream.on('data', (chunk: Buffer) => { const text = chunk.toString('utf8'); if (pattern.test(text)) { clearTimeout(timer); logStream.destroy(); resolve(); } }); logStream.on('error', (streamErr: Error) => { clearTimeout(timer); reject(streamErr); }); } ); }); } export function extractText(result: any): string { const blocks = result?.content; if (!Array.isArray(blocks)) return ''; return blocks .filter( (b: any): b is { type: string; text: string } => b && typeof b === 'object' && b.type === 'text' && typeof b.text === 'string' ) .map((b) => b.text) .join(''); } /** * Expand ${VAR} or $VAR placeholders using the current process.env. * Throws if a referenced VAR is not set to avoid silent misconfiguration. */ export function expandEnvVars(input: string, env: NodeJS.ProcessEnv = process.env): string { if (!input) return input; // ${VAR} first let out = input.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => { const v = env[name]; if (v === undefined) throw new Error(`Environment variable ${name} is not set`); return String(v); }); // then $VAR (avoid $$ and already-expanded) out = out.replace(/(?<!\$)\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, name) => { const v = env[name]; if (v === undefined) throw new Error(`Environment variable ${name} is not set`); return String(v); }); return out; } /** Expand all string values in a key/value env map */ export function expandEnvMap<T extends Record<string, any> | undefined>( map: T, env: NodeJS.ProcessEnv = process.env ): Record<string, string> | undefined { if (!map) return undefined; const out: Record<string, string> = {}; for (const [k, v] of Object.entries(map)) { if (v === undefined || v === null) continue; out[k] = typeof v === 'string' ? expandEnvVars(v, env) : String(v); } return out; } // Create directory if missing; return true if created in this run export function ensureDir(dir: string): boolean { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); return true; } return false; }

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/TheLunarCompany/lunar'

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