You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Bun与Hono实现Shell脚本输出的实时流式传输?

实现Bun + Hono执行Shell脚本的实时流式输出

我正尝试使用Bun和Hono实现Shell脚本执行的实时流式输出。目前输出会被缓冲,仅在脚本完成后一次性显示,但我需要脚本产生输出时就进行实时流式传输。

Shell脚本(deploy-container.sh)

#!/bin/bash
# Script that deploys a container and shows progress
echo "🚀 Starting deployment process..."
echo "  → Deploying Docker container..."
# ... more echo statements and actual deployment logic
echo "✅ Deployed successfully!"

当前问题

当前使用Bun的$操作符执行命令时,会缓冲所有输出直到脚本完全执行完毕,无法实现实时流式输出。要解决这个问题,需要改用支持流式处理的进程调用方式,并实时将输出写入Hono的响应流。

修改后的实现

deploy.ts

import { spawn } from "bun";

export const runDeployment = async (
  imageName: string,
  subDomain: string,
  port: number,
  onOutput: (data: string) => Promise<void>
) => {
  try {
    // 使用Bun.spawn创建子进程,实时获取输出流
    const process = spawn(["sudo", "-S", "./deploy-container.sh", imageName, subDomain, port], {
      stdin: "pipe",
      stdout: "pipe",
      stderr: "pipe",
    });

    // 向sudo输入密码
    process.stdin.write(`${process.env.SYSTEM_PASSWORD}\n`);
    process.stdin.end();

    // 实时读取标准输出
    for await (const chunk of process.stdout) {
      await onOutput(chunk.toString());
    }

    // 实时读取标准错误
    for await (const chunk of process.stderr) {
      await onOutput(`[ERROR] ${chunk.toString()}`);
    }

    // 检查进程退出码
    const exitCode = await process.exited;
    if (exitCode !== 0) {
      throw new Error(`Deployment failed with exit code ${exitCode}`);
    }
  } catch (e) {
    console.error("Error running deployment:", e);
    throw {
      success: false,
      error: e instanceof Error ? e.message : String(e),
    };
  }
};

index.ts

import { stream } from "hono/streaming";
import { runDeployment } from "../lib/deploy";

app.post("/deployContainer/:repoName", async (c) => {
  try {
    const repoName = c.req.param("repoName");
    const imageName = `${repoName}:latest`;

    const port = generateRandomPort();
    const subDomain = generateSubdomain(repoName);

    return stream(c, async (clientStream) => {
      // 可选:心跳机制,防止长连接被中断
      const heartbeat = setInterval(async () => {
        try {
          await clientStream.write(" ");
        } catch (err) {
          console.error("Heartbeat failed:", err);
          clearInterval(heartbeat);
        }
      }, 1000);

      try {
        // 传入回调函数,实时处理脚本输出
        await runDeployment(imageName, subDomain, port, async (data) => {
          // 按行分割输出,模拟终端实时打印效果
          const lines = data.split("\n");
          for (const line of lines) {
            if (line.trim()) {
              await clientStream.writeln(line);
            }
          }
        });
      } catch (error) {
        const errorMsg = error instanceof Error ? error.message : String(error);
        await clientStream.write(`Error deploying container: ${errorMsg}\n`);
      } finally {
        clearInterval(heartbeat);
        await clientStream.close();
      }
    });
  } catch (e) {
    console.error(e);
    return c.json({ success: false, error: e }, 500);
  }
});

关键改动说明

  • 替换进程调用方式:用Bun.spawn替代$操作符,spawn允许实时访问子进程的输出流,而非缓冲所有内容到进程结束。
  • 实时输出回调:新增onOutput回调函数,每次捕获到脚本输出时立即触发,将内容写入Hono的响应流。
  • 拆分输出内容:将捕获到的二进制chunk转成字符串后按行分割,确保每一行输出都能实时发送,模拟终端的输出体验。
  • 处理错误流:同时监听标准错误输出,确保脚本执行中的错误信息也能实时展示。

内容的提问来源于stack exchange,提问作者user19126561

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 10:09:51