Skip to main content
Glama
tilt-cli-fixture.ts8.61 kB
import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync, } from 'node:fs'; import net, { type AddressInfo } from 'node:net'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; type SessionBehavior = 'healthy' | 'refused' | 'hang' | 'invalid-json'; interface TiltFixtureState { expectedPort: number; expectedHost: string; sessionBehavior: SessionBehavior; sessionStdout: string; sessionStderr: string; hangMs: number; } export interface TiltCliFixture { port: number; host: string; tiltBinary: string; statePath: string; eventsPath: string; setBehavior: ( behavior: SessionBehavior, overrides?: Partial<TiltFixtureState>, ) => void; readEvents: () => TiltFixtureEvents; cleanup: () => void; } export interface TiltFixtureEvents { spawns: { args: string[]; timestamp: number }[]; signals: { signal: string; timestamp: number }[]; } function buildTiltShimSource(statePath: string, eventsPath: string): string { return `#!/usr/bin/env node const { existsSync, readFileSync, writeFileSync } = require('node:fs'); const statePath = ${JSON.stringify(statePath)}; const eventsPath = ${JSON.stringify(eventsPath)}; function readState() { const content = readFileSync(statePath, 'utf8'); return JSON.parse(content); } function readEvents() { if (!existsSync(eventsPath)) { return { spawns: [], signals: [] }; } try { const content = readFileSync(eventsPath, 'utf8'); return JSON.parse(content); } catch { return { spawns: [], signals: [] }; } } function recordEvent(event) { const events = readEvents(); if (event.type === 'spawn') { events.spawns.push({ args: event.args, timestamp: Date.now() }); } if (event.type === 'signal') { events.signals.push({ signal: event.signal, timestamp: Date.now() }); } writeFileSync(eventsPath, JSON.stringify(events)); } function exitWithError(message) { console.error(message); process.exit(1); } function main() { process.on('SIGTERM', () => { recordEvent({ type: 'signal', signal: 'SIGTERM' }); process.exit(143); }); const args = process.argv.slice(2); recordEvent({ type: 'spawn', args }); const state = readState(); const portIndex = args.indexOf('--port'); const hostIndex = args.indexOf('--host'); const portArg = portIndex !== -1 ? parseInt(args[portIndex + 1] || '', 10) : undefined; const hostArg = hostIndex !== -1 ? args[hostIndex + 1] : undefined; // Validate port/host for all commands if (typeof state.expectedPort === 'number' && portArg !== undefined && portArg !== state.expectedPort) { exitWithError(\`dial tcp \${hostArg}:\${portArg}: connection refused (expected port \${state.expectedPort})\`); } if (typeof state.expectedHost === 'string' && hostArg && hostArg !== state.expectedHost) { exitWithError(\`dial tcp \${hostArg}:\${portArg}: connection refused (expected host \${state.expectedHost})\`); } // Handle different behaviors based on state switch (state.sessionBehavior) { case 'refused': exitWithError(state.sessionStderr ?? 'dial tcp: connection refused'); case 'invalid-json': process.stdout.write('not json'); process.exit(0); case 'hang': // Keep process alive until the parent kills it (simulates CLI stall) setInterval(() => {}, 1000); return; case 'healthy': // Handle specific commands if (args[0] === 'get' && args[1] === 'session') { process.stdout.write(state.sessionStdout ?? '{"kind":"Session"}'); process.exit(0); } if (args[0] === 'get' && args[1] === 'uiresources') { // Try to use sessionStdout if it looks like JSON let output = state.sessionStdout; if (!output || (output && !output.trim().startsWith('{'))) { // sessionStdout is not JSON (probably log text), use default resource list output = JSON.stringify({ kind: 'UIResourceList', items: [ { metadata: { name: 'web-app' } }, { metadata: { name: 'test-service' } }, { metadata: { name: '(Tiltfile)' } }, ], }); } process.stdout.write(output); process.exit(0); } if (args[0] === 'get' && args[1]?.startsWith('uiresource/')) { process.stdout.write(state.sessionStdout ?? '{"kind":"UIResource","metadata":{"name":"test"}}'); process.exit(0); } if (args[0] === 'get' && args[1]?.startsWith('tiltfile/')) { process.stdout.write(state.sessionStdout ?? '{"kind":"Tiltfile","metadata":{"name":"(Tiltfile)"},"spec":{"args":[]}}'); process.exit(0); } if (args[0] === 'logs') { process.stdout.write(state.sessionStdout ?? 'log output'); process.exit(0); } if (args[0] === 'trigger') { process.stdout.write(state.sessionStdout ?? 'triggered'); process.exit(0); } if (args[0] === 'enable') { process.stdout.write(state.sessionStdout ?? 'enabled'); process.exit(0); } if (args[0] === 'disable') { process.stdout.write(state.sessionStdout ?? 'disabled'); process.exit(0); } if (args[0] === 'args') { process.stdout.write(state.sessionStdout ?? ''); process.exit(0); } if (args[0] === 'wait') { process.stdout.write(state.sessionStdout ?? ''); process.exit(0); } if (args[0] === 'dump' && args[1] === 'engine') { process.stdout.write(state.sessionStdout ?? '{"engine":"state"}'); process.exit(0); } // Fall through for unhandled commands break; default: exitWithError('Unsupported sessionBehavior: ' + state.sessionBehavior); } exitWithError('Unsupported tilt command: ' + args.join(' ')); } main(); `; } function buildFixtureState( host: string, port: number, behavior: SessionBehavior, ): TiltFixtureState { return { expectedHost: host, expectedPort: port, sessionBehavior: behavior, sessionStdout: '{"kind":"Session"}', sessionStderr: `dial tcp ${host}:${port}: connection refused`, hangMs: 60000, }; } function writeState(statePath: string, state: TiltFixtureState): void { writeFileSync(statePath, JSON.stringify(state)); } function readEvents(eventsPath: string): TiltFixtureEvents { if (!existsSync(eventsPath)) { return { spawns: [], signals: [] }; } const content = readFileSync(eventsPath, 'utf8'); try { return JSON.parse(content) as TiltFixtureEvents; } catch { return { spawns: [], signals: [] }; } } function findFreePort(host: string): Promise<number> { return new Promise((resolve, reject) => { const server = net.createServer(); server.unref(); server.on('error', reject); server.listen(0, host, () => { const address = server.address() as AddressInfo; const port = address.port; server.close(() => resolve(port)); }); }); } export async function createTiltCliFixture( options: { host?: string; behavior?: SessionBehavior; hangMs?: number; stdout?: string; stderr?: string; } = {}, ): Promise<TiltCliFixture> { const host = options.host ?? '127.0.0.1'; const port = await findFreePort(host); const behavior = options.behavior ?? 'healthy'; const dir = mkdtempSync(join(tmpdir(), 'tilt-cli-fixture-')); const statePath = join(dir, 'state.json'); const eventsPath = join(dir, 'events.json'); const tiltBinary = join(dir, 'tilt'); let currentState = buildFixtureState(host, port, behavior); if (options.hangMs !== undefined) { currentState.hangMs = options.hangMs; } if (options.stdout) { currentState.sessionStdout = options.stdout; } if (options.stderr) { currentState.sessionStderr = options.stderr; } writeState(statePath, currentState); writeFileSync(eventsPath, JSON.stringify({ spawns: [], signals: [] })); const shimSource = buildTiltShimSource(statePath, eventsPath); writeFileSync(tiltBinary, shimSource, { encoding: 'utf8' }); chmodSync(tiltBinary, 0o755); const setBehavior = ( sessionBehavior: SessionBehavior, overrides: Partial<TiltFixtureState> = {}, ) => { currentState = { ...currentState, ...overrides, sessionBehavior }; writeState(statePath, currentState); }; const cleanup = () => { rmSync(dir, { recursive: true, force: true }); }; return { port, host, tiltBinary, statePath, eventsPath, setBehavior, readEvents: () => readEvents(eventsPath), cleanup, }; }

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/0xBigBoss/tilt-mcp'

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