(runId: string, filePath: string)
| 62 | } |
| 63 | |
| 64 | async function captureStream(runId: string, filePath: string) { |
| 65 | await ensureOutputDir(filePath) |
| 66 | const streamUrl = `${API_BASE_URL}/api/v1/workflows/runs/${runId}/stream` |
| 67 | const response = await fetch(streamUrl, { |
| 68 | headers: COMMON_HEADERS, |
| 69 | }) |
| 70 | if (!response.ok || !response.body) { |
| 71 | const text = await response.text().catch(() => '') |
| 72 | throw new Error( |
| 73 | `Failed to subscribe to stream ${streamUrl}: ${response.status} ${response.statusText} - ${text}`, |
| 74 | ) |
| 75 | } |
| 76 | |
| 77 | const file = createWriteStream(filePath, { flags: 'w' }) |
| 78 | file.write(`# Workflow ${WORKFLOW_ID} run ${runId}\n`) |
| 79 | file.write(`# Stream URL: ${streamUrl}\n`) |
| 80 | file.write(`# Captured at: ${new Date().toISOString()}\n\n`) |
| 81 | |
| 82 | const reader = response.body.getReader() |
| 83 | const decoder = new TextDecoder() |
| 84 | let buffer = '' |
| 85 | let finished = false |
| 86 | |
| 87 | while (!finished) { |
| 88 | const { value, done } = await reader.read() |
| 89 | if (done) { |
| 90 | break |
| 91 | } |
| 92 | buffer += decoder.decode(value, { stream: true }) |
| 93 | let index: number |
| 94 | while ((index = buffer.indexOf('\n\n')) >= 0) { |
| 95 | const rawEvent = buffer.slice(0, index).replace(/\r/g, '') |
| 96 | buffer = buffer.slice(index + 2) |
| 97 | if (rawEvent.trim().length === 0) { |
| 98 | continue |
| 99 | } |
| 100 | file.write(rawEvent + '\n\n') |
| 101 | if (rawEvent.startsWith('event: complete')) { |
| 102 | finished = true |
| 103 | break |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | file.end() |
| 109 | return filePath |
| 110 | } |
| 111 | |
| 112 | async function main() { |
| 113 | console.log( |
no test coverage detected