mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-28 20:46:57 +02:00
ci: add bun global install smoke
This commit is contained in:
8
.github/workflows/install-smoke.yml
vendored
8
.github/workflows/install-smoke.yml
vendored
@@ -192,9 +192,15 @@ jobs:
|
||||
- name: Setup Node environment for local pack smoke
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
install-bun: "true"
|
||||
install-deps: "true"
|
||||
|
||||
- name: Run Bun global install image-provider smoke
|
||||
env:
|
||||
OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE: openclaw-dockerfile-smoke:local
|
||||
OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD: "0"
|
||||
run: bash scripts/e2e/bun-global-install-smoke.sh
|
||||
|
||||
- name: Run installer docker tests
|
||||
env:
|
||||
OPENCLAW_INSTALL_URL: https://openclaw.ai/install.sh
|
||||
|
||||
@@ -975,6 +975,7 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or
|
||||
- Open WebUI live smoke: `pnpm test:docker:openwebui` (script: `scripts/e2e/openwebui-docker.sh`)
|
||||
- Onboarding wizard (TTY, full scaffolding): `pnpm test:docker:onboard` (script: `scripts/e2e/onboard-docker.sh`)
|
||||
- Npm tarball onboarding/channel/agent smoke: `pnpm test:docker:npm-onboard-channel-agent` installs the packed OpenClaw tarball globally in Docker, configures OpenAI via env-ref onboarding plus Telegram by default, verifies enabling the plugin installs its runtime deps on demand, runs doctor, and runs one mocked OpenAI agent turn. Reuse a prebuilt tarball with `OPENCLAW_NPM_ONBOARD_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host rebuild with `OPENCLAW_NPM_ONBOARD_HOST_BUILD=0`, or switch channel with `OPENCLAW_NPM_ONBOARD_CHANNEL=discord`.
|
||||
- Bun global install smoke: `bash scripts/e2e/bun-global-install-smoke.sh` packs the current tree, installs it with `bun install -g` in an isolated home, and verifies `openclaw infer image providers --json` returns bundled image providers instead of hanging. Reuse a prebuilt tarball with `OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host build with `OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD=0`, or copy `dist/` from a built Docker image with `OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE=openclaw-dockerfile-smoke:local`.
|
||||
- Gateway networking (two containers, WS auth + health): `pnpm test:docker:gateway-network` (script: `scripts/e2e/gateway-network-docker.sh`)
|
||||
- OpenAI Responses web_search minimal reasoning regression: `pnpm test:docker:openai-web-search-minimal` (script: `scripts/e2e/openai-web-search-minimal-docker.sh`) runs a mocked OpenAI server through Gateway, verifies `web_search` raises `reasoning.effort` from `minimal` to `low`, then forces the provider schema reject and checks the raw detail appears in Gateway logs.
|
||||
- MCP channel bridge (seeded Gateway + stdio bridge + raw Claude notification-frame smoke): `pnpm test:docker:mcp-channels` (script: `scripts/e2e/mcp-channels-docker.sh`)
|
||||
|
||||
191
scripts/e2e/bun-global-install-smoke.sh
Executable file
191
scripts/e2e/bun-global-install-smoke.sh
Executable file
@@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
BUN_BIN="${BUN_BIN:-bun}"
|
||||
HOST_BUILD="${OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD:-1}"
|
||||
DIST_IMAGE="${OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE:-}"
|
||||
PACKAGE_TGZ="${OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ:-}"
|
||||
COMMAND_TIMEOUT_MS="${OPENCLAW_BUN_GLOBAL_SMOKE_TIMEOUT_MS:-180000}"
|
||||
SMOKE_DIR=""
|
||||
PACK_DIR=""
|
||||
|
||||
cleanup() {
|
||||
if [ -n "${SMOKE_DIR:-}" ]; then
|
||||
rm -rf "$SMOKE_DIR"
|
||||
fi
|
||||
if [ -n "${PACK_DIR:-}" ]; then
|
||||
rm -rf "$PACK_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
run_with_timeout() {
|
||||
local timeout_ms="$1"
|
||||
shift
|
||||
node - "$timeout_ms" "$@" <<'NODE'
|
||||
const { spawnSync } = require("node:child_process");
|
||||
|
||||
const timeout = Number(process.argv[2]);
|
||||
const command = process.argv[3];
|
||||
const args = process.argv.slice(4);
|
||||
const result = spawnSync(command, args, {
|
||||
encoding: "utf8",
|
||||
env: process.env,
|
||||
timeout,
|
||||
});
|
||||
|
||||
if (result.stdout) {
|
||||
process.stdout.write(result.stdout);
|
||||
}
|
||||
if (result.stderr) {
|
||||
process.stderr.write(result.stderr);
|
||||
}
|
||||
if (result.error) {
|
||||
console.error(`command failed: ${command}: ${result.error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (result.signal) {
|
||||
console.error(`command terminated: ${command}: ${result.signal}`);
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(result.status ?? 0);
|
||||
NODE
|
||||
}
|
||||
|
||||
restore_dist_from_image() {
|
||||
local image="$1"
|
||||
local container_id
|
||||
|
||||
echo "==> Reuse dist/ from Docker image: $image"
|
||||
container_id="$(docker create "$image")"
|
||||
rm -rf "$ROOT_DIR/dist"
|
||||
if ! docker cp "${container_id}:/app/dist" "$ROOT_DIR/dist"; then
|
||||
docker rm -f "$container_id" >/dev/null 2>&1 || true
|
||||
return 1
|
||||
fi
|
||||
docker rm -f "$container_id" >/dev/null
|
||||
}
|
||||
|
||||
resolve_package_tgz() {
|
||||
if [ -n "$PACKAGE_TGZ" ]; then
|
||||
if [ ! -f "$PACKAGE_TGZ" ]; then
|
||||
echo "OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ does not exist: $PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(cd "$(dirname "$PACKAGE_TGZ")" && pwd)/$(basename "$PACKAGE_TGZ")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "$DIST_IMAGE" ]; then
|
||||
restore_dist_from_image "$DIST_IMAGE"
|
||||
elif [ "$HOST_BUILD" != "0" ]; then
|
||||
echo "==> Build host package artifacts"
|
||||
pnpm build
|
||||
else
|
||||
echo "==> Skipping host build (OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD=0)"
|
||||
fi
|
||||
|
||||
if [ ! -d "$ROOT_DIR/dist" ]; then
|
||||
echo "dist/ is missing; run pnpm build or set OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Write package inventory"
|
||||
node --import tsx scripts/write-package-dist-inventory.ts
|
||||
|
||||
local pack_json_file
|
||||
PACK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-bun-pack.XXXXXX")"
|
||||
pack_json_file="$PACK_DIR/pack.json"
|
||||
|
||||
echo "==> Pack OpenClaw tarball"
|
||||
npm pack --ignore-scripts --json --pack-destination "$PACK_DIR" >"$pack_json_file"
|
||||
PACKAGE_TGZ="$(
|
||||
node -e '
|
||||
const raw = require("node:fs").readFileSync(process.argv[1], "utf8") || "[]";
|
||||
const parsed = JSON.parse(raw);
|
||||
const last = Array.isArray(parsed) ? parsed.at(-1) : null;
|
||||
if (!last || typeof last.filename !== "string" || last.filename.length === 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
process.stdout.write(require("node:path").resolve(process.argv[2], last.filename));
|
||||
' "$pack_json_file" "$PACK_DIR"
|
||||
)"
|
||||
if [ -z "$PACKAGE_TGZ" ] || [ ! -f "$PACKAGE_TGZ" ]; then
|
||||
echo "missing packed OpenClaw tarball" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
if ! command -v "$BUN_BIN" >/dev/null 2>&1; then
|
||||
echo "Bun is required for bun global install smoke; set BUN_BIN or install bun." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
resolve_package_tgz
|
||||
|
||||
local bun_path
|
||||
local openclaw_bin
|
||||
bun_path="$(command -v "$BUN_BIN")"
|
||||
SMOKE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-bun-global.XXXXXX")"
|
||||
|
||||
export HOME="$SMOKE_DIR/home"
|
||||
export BUN_INSTALL="$HOME/.bun"
|
||||
export XDG_CACHE_HOME="$SMOKE_DIR/cache"
|
||||
export OPENCLAW_NO_ONBOARD=1
|
||||
export OPENCLAW_DISABLE_UPDATE_CHECK=1
|
||||
export NO_COLOR=1
|
||||
mkdir -p "$HOME" "$BUN_INSTALL/bin" "$XDG_CACHE_HOME"
|
||||
export PATH="$BUN_INSTALL/bin:$(dirname "$(command -v node)"):$PATH"
|
||||
|
||||
echo "==> Bun version"
|
||||
"$bun_path" --version
|
||||
|
||||
echo "==> Bun global install packed OpenClaw"
|
||||
"$bun_path" install -g "$PACKAGE_TGZ" --no-progress
|
||||
|
||||
openclaw_bin="$BUN_INSTALL/bin/openclaw"
|
||||
if [ ! -x "$openclaw_bin" ]; then
|
||||
openclaw_bin="$(command -v openclaw || true)"
|
||||
fi
|
||||
if [ -z "$openclaw_bin" ] || [ ! -x "$openclaw_bin" ]; then
|
||||
echo "Bun global install did not create an executable openclaw binary" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> OpenClaw version through Bun global install"
|
||||
run_with_timeout "$COMMAND_TIMEOUT_MS" "$openclaw_bin" --version
|
||||
|
||||
echo "==> OpenClaw image providers through Bun global install"
|
||||
local providers_json
|
||||
providers_json="$(run_with_timeout "$COMMAND_TIMEOUT_MS" "$openclaw_bin" infer image providers --json)"
|
||||
OPENCLAW_IMAGE_PROVIDERS_JSON="$providers_json" node - <<'NODE'
|
||||
const raw = process.env.OPENCLAW_IMAGE_PROVIDERS_JSON ?? "";
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch (error) {
|
||||
console.error(raw);
|
||||
throw new Error(`image providers output is not JSON: ${error.message}`);
|
||||
}
|
||||
if (!Array.isArray(parsed)) {
|
||||
throw new Error("image providers output must be a JSON array");
|
||||
}
|
||||
if (parsed.length === 0) {
|
||||
throw new Error("image providers output is empty");
|
||||
}
|
||||
const ids = new Set(parsed.map((entry) => entry && typeof entry.id === "string" ? entry.id : ""));
|
||||
for (const expected of ["google", "openai", "xai"]) {
|
||||
if (!ids.has(expected)) {
|
||||
throw new Error(`image providers output is missing bundled provider '${expected}'`);
|
||||
}
|
||||
}
|
||||
console.log(`bun-global-install-smoke: image providers OK (${parsed.length} providers)`);
|
||||
NODE
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -3,6 +3,8 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
const SCRIPT_PATH = "scripts/test-install-sh-docker.sh";
|
||||
const SMOKE_RUNNER_PATH = "scripts/docker/install-sh-smoke/run.sh";
|
||||
const BUN_GLOBAL_SMOKE_PATH = "scripts/e2e/bun-global-install-smoke.sh";
|
||||
const INSTALL_SMOKE_WORKFLOW_PATH = ".github/workflows/install-smoke.yml";
|
||||
|
||||
describe("test-install-sh-docker", () => {
|
||||
it("defaults local Apple Silicon smoke runs to native arm64 while keeping CI on amd64", () => {
|
||||
@@ -95,3 +97,26 @@ describe("install-sh smoke runner", () => {
|
||||
expect(runner).toContain("==> Direct npm global update candidate");
|
||||
});
|
||||
});
|
||||
|
||||
describe("bun global install smoke", () => {
|
||||
it("packs the current tree and verifies image-provider discovery through Bun", () => {
|
||||
const script = readFileSync(BUN_GLOBAL_SMOKE_PATH, "utf8");
|
||||
|
||||
expect(script).toContain("npm pack --ignore-scripts --json --pack-destination");
|
||||
expect(script).toContain('"$bun_path" install -g "$PACKAGE_TGZ" --no-progress');
|
||||
expect(script).toContain("infer image providers --json");
|
||||
expect(script).toContain("image providers output is missing bundled provider");
|
||||
expect(script).toContain("OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE");
|
||||
});
|
||||
|
||||
it("runs from the install-smoke workflow with Bun enabled", () => {
|
||||
const workflow = readFileSync(INSTALL_SMOKE_WORKFLOW_PATH, "utf8");
|
||||
|
||||
expect(workflow).toContain('install-bun: "true"');
|
||||
expect(workflow).toContain("Run Bun global install image-provider smoke");
|
||||
expect(workflow).toContain("bash scripts/e2e/bun-global-install-smoke.sh");
|
||||
expect(workflow).toContain(
|
||||
"OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE: openclaw-dockerfile-smoke:local",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user