mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:50:43 +00:00
ci: use packaged tarball for docker e2e
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
#
|
||||
# Shared Docker E2E image.
|
||||
# `bare` is a clean Node/Git runner for install/update lanes. `functional`
|
||||
# installs the prepared OpenClaw npm tarball into /app for built-app lanes.
|
||||
|
||||
FROM node:24-bookworm-slim@sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb AS e2e-runner
|
||||
|
||||
@@ -7,12 +11,14 @@ RUN apt-get update \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN corepack enable
|
||||
RUN npm install -g tsx@4.21.0 --no-fund --no-audit
|
||||
|
||||
RUN useradd --create-home --shell /bin/bash appuser \
|
||||
&& mkdir -p /app \
|
||||
&& chown appuser:appuser /app
|
||||
|
||||
ENV HOME="/home/appuser"
|
||||
ENV PATH="/home/appuser/.local/bin:${PATH}"
|
||||
ENV NODE_OPTIONS="--disable-warning=ExperimentalWarning"
|
||||
# Docker E2E lanes start many loopback gateways concurrently; mDNS advertising
|
||||
# is unrelated to those checks and can flap under container CPU/network load.
|
||||
@@ -21,48 +27,23 @@ ENV OPENCLAW_DISABLE_BONJOUR="1"
|
||||
USER appuser
|
||||
WORKDIR /app
|
||||
|
||||
FROM e2e-runner AS deps
|
||||
|
||||
COPY --chown=appuser:appuser package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
||||
COPY --chown=appuser:appuser ui/package.json ./ui/package.json
|
||||
COPY --chown=appuser:appuser patches ./patches
|
||||
COPY --chown=appuser:appuser scripts/postinstall-bundled-plugins.mjs scripts/preinstall-package-manager-warning.mjs scripts/npm-runner.mjs scripts/windows-cmd-helpers.mjs ./scripts/
|
||||
RUN --mount=type=bind,source=extensions,target=/tmp/extensions,readonly \
|
||||
find /tmp/extensions -mindepth 2 -maxdepth 2 -name package.json -print | \
|
||||
while IFS= read -r manifest; do \
|
||||
dest="${manifest#/tmp/}"; \
|
||||
mkdir -p "$(dirname "$dest")"; \
|
||||
cp "$manifest" "$dest"; \
|
||||
done
|
||||
|
||||
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/home/appuser/.local/share/pnpm/store,sharing=locked \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
FROM deps AS build
|
||||
|
||||
COPY --chown=appuser:appuser .oxlintrc.json tsconfig.json tsconfig.plugin-sdk.dts.json tsconfig.oxlint*.json tsdown.config.ts vitest.config.ts openclaw.mjs ./
|
||||
COPY --chown=appuser:appuser src ./src
|
||||
COPY --chown=appuser:appuser test ./test
|
||||
COPY --chown=appuser:appuser scripts ./scripts
|
||||
COPY --chown=appuser:appuser docs ./docs
|
||||
COPY --chown=appuser:appuser packages ./packages
|
||||
COPY --chown=appuser:appuser skills ./skills
|
||||
COPY --chown=appuser:appuser ui ./ui
|
||||
COPY --chown=appuser:appuser extensions ./extensions
|
||||
COPY --chown=appuser:appuser vendor/a2ui/renderers/lit ./vendor/a2ui/renderers/lit
|
||||
COPY --chown=appuser:appuser apps/shared/OpenClawKit/Sources/OpenClawKit/Resources ./apps/shared/OpenClawKit/Sources/OpenClawKit/Resources
|
||||
COPY --chown=appuser:appuser apps/shared/OpenClawKit/Tools/CanvasA2UI ./apps/shared/OpenClawKit/Tools/CanvasA2UI
|
||||
|
||||
RUN pnpm build
|
||||
# Onboard Docker E2E does not exercise the Control UI itself; it only needs the
|
||||
# asset-existence check to pass so configure/onboard can continue.
|
||||
RUN mkdir -p dist/control-ui \
|
||||
&& printf '%s\n' '<!doctype html><title>OpenClaw Control UI</title>' > dist/control-ui/index.html
|
||||
FROM e2e-runner AS bare
|
||||
|
||||
CMD ["bash"]
|
||||
|
||||
FROM build AS functional
|
||||
|
||||
RUN node scripts/stage-bundled-plugin-runtime-deps.mjs
|
||||
FROM bare AS build
|
||||
|
||||
CMD ["bash"]
|
||||
|
||||
FROM bare AS functional
|
||||
|
||||
# The app under test enters through the named BuildKit context, not by copying
|
||||
# checkout sources into the image.
|
||||
COPY --from=openclaw_package --chown=appuser:appuser openclaw-current.tgz /tmp/openclaw-current.tgz
|
||||
RUN npm install -g --prefix /tmp/openclaw-prefix /tmp/openclaw-current.tgz --no-fund --no-audit \
|
||||
&& cp -a /tmp/openclaw-prefix/lib/node_modules/openclaw/. /app/ \
|
||||
&& mkdir -p "$HOME/.local/bin" \
|
||||
&& ln -sf /app/openclaw.mjs "$HOME/.local/bin/openclaw" \
|
||||
&& rm -rf /tmp/openclaw-prefix /tmp/openclaw-current.tgz
|
||||
|
||||
CMD ["bash"]
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs bundled plugin runtime-dependency Docker scenarios from a mounted OpenClaw
|
||||
# npm tarball. The default image is a clean runner; each scenario installs the
|
||||
# tarball so package install behavior is what gets tested.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-bundled-channel-deps-e2e" OPENCLAW_BUNDLED_CHANNEL_DEPS_E2E_IMAGE)"
|
||||
UPDATE_BASELINE_VERSION="${OPENCLAW_BUNDLED_CHANNEL_UPDATE_BASELINE_VERSION:-2026.4.20}"
|
||||
DOCKER_TARGET="${OPENCLAW_BUNDLED_CHANNEL_DOCKER_TARGET:-e2e-runner}"
|
||||
DOCKER_TARGET="${OPENCLAW_BUNDLED_CHANNEL_DOCKER_TARGET:-bare}"
|
||||
HOST_BUILD="${OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD:-1}"
|
||||
PACKAGE_TGZ="${OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ:-}"
|
||||
RUN_CHANNEL_SCENARIOS="${OPENCLAW_BUNDLED_CHANNEL_SCENARIOS:-1}"
|
||||
@@ -22,32 +26,14 @@ docker_e2e_build_or_reuse "$IMAGE_NAME" bundled-channel-deps "$ROOT_DIR/scripts/
|
||||
|
||||
prepare_package_tgz() {
|
||||
if [ -n "$PACKAGE_TGZ" ]; then
|
||||
if [ ! -f "$PACKAGE_TGZ" ]; then
|
||||
echo "OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ does not exist: $PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(cd "$(dirname "$PACKAGE_TGZ")" && pwd)/$(basename "$PACKAGE_TGZ")"
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz bundled-channel-deps "$PACKAGE_TGZ")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$HOST_BUILD" != "0" ]; then
|
||||
echo "Building host package artifacts..."
|
||||
run_logged bundled-channel-deps-host-build pnpm build
|
||||
else
|
||||
echo "Skipping host build (OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD=0)"
|
||||
fi
|
||||
|
||||
echo "Writing package inventory and packing once..."
|
||||
run_logged bundled-channel-deps-inventory node --import tsx --input-type=module -e 'const { writePackageDistInventory } = await import("./src/infra/package-dist-inventory.ts"); await writePackageDistInventory(process.cwd());'
|
||||
local pack_dir
|
||||
pack_dir="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-bundled-channel-pack.XXXXXX")"
|
||||
run_logged bundled-channel-deps-pack npm pack --ignore-scripts --pack-destination "$pack_dir"
|
||||
PACKAGE_TGZ="$(find "$pack_dir" -maxdepth 1 -name 'openclaw-*.tgz' -print -quit)"
|
||||
if [ -z "$PACKAGE_TGZ" ]; then
|
||||
echo "missing packed OpenClaw tarball" >&2
|
||||
if [ "$HOST_BUILD" = "0" ] && [ -z "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ]; then
|
||||
echo "OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD=0 requires OPENCLAW_CURRENT_PACKAGE_TGZ or OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(cd "$(dirname "$PACKAGE_TGZ")" && pwd)/$(basename "$PACKAGE_TGZ")"
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz bundled-channel-deps)"
|
||||
}
|
||||
|
||||
prepare_package_tgz
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// Crestodian first-run Docker harness.
|
||||
// Imports packaged dist modules so the Docker lane verifies the npm tarball,
|
||||
// while this small test driver stays mounted from the checkout.
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { runCli, shouldStartCrestodianForBareRoot } from "../../src/cli/run-main.js";
|
||||
import { clearConfigCache } from "../../src/config/config.js";
|
||||
import type { OpenClawConfig } from "../../src/config/types.openclaw.js";
|
||||
import { runCrestodian } from "../../src/crestodian/crestodian.js";
|
||||
import type { RuntimeEnv } from "../../src/runtime.js";
|
||||
import { runCli, shouldStartCrestodianForBareRoot } from "../../dist/cli/run-main.js";
|
||||
import { clearConfigCache } from "../../dist/config/config.js";
|
||||
import type { OpenClawConfig } from "../../dist/config/types.openclaw.js";
|
||||
import { runCrestodian } from "../../dist/crestodian/crestodian.js";
|
||||
import type { RuntimeEnv } from "../../dist/runtime.js";
|
||||
|
||||
type CrestodianFirstRunCommand = {
|
||||
id: string;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs the Crestodian first-run Docker smoke against the package-installed
|
||||
# functional E2E image, with only the test harness mounted from the checkout.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -16,11 +18,13 @@ trap cleanup EXIT
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" crestodian-first-run
|
||||
|
||||
echo "Running in-container Crestodian first-run smoke..."
|
||||
# Harness files are mounted read-only; the app under test comes from /app/dist.
|
||||
set +e
|
||||
docker run --rm \
|
||||
--name "$CONTAINER_NAME" \
|
||||
-e "OPENCLAW_STATE_DIR=/tmp/openclaw-state" \
|
||||
-e "OPENCLAW_CONFIG_PATH=/tmp/openclaw-state/openclaw.json" \
|
||||
-v "$ROOT_DIR/scripts/e2e:/app/scripts/e2e:ro" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc "set -euo pipefail
|
||||
node --import tsx scripts/e2e/crestodian-first-run-docker-client.ts
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// Crestodian planner Docker harness.
|
||||
// Imports packaged dist modules so the Docker lane verifies the npm tarball,
|
||||
// while this small test driver stays mounted from the checkout.
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { clearConfigCache } from "../../src/config/config.js";
|
||||
import type { OpenClawConfig } from "../../src/config/types.openclaw.js";
|
||||
import { runCrestodian } from "../../src/crestodian/crestodian.js";
|
||||
import type { RuntimeEnv } from "../../src/runtime.js";
|
||||
import { clearConfigCache } from "../../dist/config/config.js";
|
||||
import type { OpenClawConfig } from "../../dist/config/types.openclaw.js";
|
||||
import { runCrestodian } from "../../dist/crestodian/crestodian.js";
|
||||
import type { RuntimeEnv } from "../../dist/runtime.js";
|
||||
|
||||
function assert(condition: unknown, message: string): asserts condition {
|
||||
if (!condition) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs the Crestodian planner fallback Docker smoke against the package-installed
|
||||
# functional E2E image, with only the test harness mounted from the checkout.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -16,11 +18,13 @@ trap cleanup EXIT
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" crestodian-planner
|
||||
|
||||
echo "Running in-container Crestodian planner fallback smoke..."
|
||||
# Harness files are mounted read-only; the app under test comes from /app/dist.
|
||||
set +e
|
||||
docker run --rm \
|
||||
--name "$CONTAINER_NAME" \
|
||||
-e "OPENCLAW_STATE_DIR=/tmp/openclaw-state" \
|
||||
-e "OPENCLAW_CONFIG_PATH=/tmp/openclaw-state/openclaw.json" \
|
||||
-v "$ROOT_DIR/scripts/e2e:/app/scripts/e2e:ro" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc "set -euo pipefail
|
||||
node --import tsx scripts/e2e/crestodian-planner-docker-client.ts
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// Crestodian rescue-message Docker harness.
|
||||
// Imports packaged dist modules so the Docker lane verifies the npm tarball,
|
||||
// while this small test driver stays mounted from the checkout.
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { handleCrestodianCommand } from "../../src/auto-reply/reply/commands-crestodian.js";
|
||||
import { clearConfigCache } from "../../src/config/config.js";
|
||||
import type { OpenClawConfig } from "../../src/config/types.openclaw.js";
|
||||
import { runCrestodianRescueMessage } from "../../src/crestodian/rescue-message.js";
|
||||
import { handleCrestodianCommand } from "../../dist/auto-reply/reply/commands-crestodian.js";
|
||||
import { clearConfigCache } from "../../dist/config/config.js";
|
||||
import type { OpenClawConfig } from "../../dist/config/types.openclaw.js";
|
||||
import { runCrestodianRescueMessage } from "../../dist/crestodian/rescue-message.js";
|
||||
|
||||
type CommandResult = Awaited<ReturnType<typeof handleCrestodianCommand>>;
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs the Crestodian rescue-message Docker smoke against the package-installed
|
||||
# functional E2E image, with only the test harness mounted from the checkout.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -16,11 +18,13 @@ trap cleanup EXIT
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" crestodian-rescue
|
||||
|
||||
echo "Running in-container Crestodian rescue smoke..."
|
||||
# Harness files are mounted read-only; the app under test comes from /app/dist.
|
||||
set +e
|
||||
docker run --rm \
|
||||
--name "$CONTAINER_NAME" \
|
||||
-e "OPENCLAW_STATE_DIR=/tmp/openclaw-state" \
|
||||
-e "OPENCLAW_CONFIG_PATH=/tmp/openclaw-state/openclaw.json" \
|
||||
-v "$ROOT_DIR/scripts/e2e:/app/scripts/e2e:ro" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc "set -euo pipefail
|
||||
node --import tsx scripts/e2e/crestodian-rescue-docker-client.ts
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Starts Gateway plus seeded cron/subagent MCP work in Docker, then verifies MCP
|
||||
# child-process cleanup through a mounted test harness.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -18,6 +20,7 @@ trap cleanup EXIT
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" cron-mcp-cleanup
|
||||
|
||||
echo "Running in-container cron/subagent MCP cleanup smoke..."
|
||||
# Harness files are mounted read-only; the app under test comes from /app/dist.
|
||||
set +e
|
||||
docker run --rm \
|
||||
--name "$CONTAINER_NAME" \
|
||||
@@ -33,6 +36,7 @@ docker run --rm \
|
||||
-e "GW_URL=ws://127.0.0.1:$PORT" \
|
||||
-e "GW_TOKEN=$TOKEN" \
|
||||
-e "OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1" \
|
||||
-v "$ROOT_DIR/scripts/e2e:/app/scripts/e2e:ro" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc "set -euo pipefail
|
||||
entry=dist/index.mjs
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Shared Docker E2E OpenAI provider config seed helper.
|
||||
// Uses packaged plugin-sdk runtime modules so seeded configs match the npm tarball.
|
||||
import {
|
||||
applyProviderConfigWithDefaultModelPreset,
|
||||
type ModelDefinitionConfig,
|
||||
type OpenClawConfig,
|
||||
} from "../../src/plugin-sdk/provider-onboard.ts";
|
||||
} from "../../dist/plugin-sdk/provider-onboard.js";
|
||||
|
||||
export type { OpenClawConfig };
|
||||
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verifies doctor/daemon repair switches service entrypoints between package and
|
||||
# git installs. Both fixtures come from the same prepared OpenClaw npm tarball.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-doctor-install-switch-e2e" OPENCLAW_DOCTOR_INSTALL_SWITCH_E2E_IMAGE)"
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz doctor-switch "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
|
||||
# Bare lanes mount the package artifact instead of baking app sources into the image.
|
||||
docker_e2e_package_mount_args "$PACKAGE_TGZ"
|
||||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" doctor-switch
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" doctor-switch "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "bare"
|
||||
|
||||
echo "Running doctor install switch E2E..."
|
||||
docker run --rm -e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 "$IMAGE_NAME" bash -lc '
|
||||
docker run --rm \
|
||||
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc '
|
||||
set -euo pipefail
|
||||
|
||||
# Keep logs focused; the npm global install step can emit noisy deprecation warnings.
|
||||
@@ -74,15 +84,23 @@ exit 0
|
||||
LOGINCTL
|
||||
chmod +x /tmp/openclaw-bin/loginctl
|
||||
|
||||
# Install the npm-global variant from the local /app source.
|
||||
# `npm pack` can emit script output; keep only the tarball name.
|
||||
pkg_tgz="$(npm pack --ignore-scripts --silent /app | tail -n 1 | tr -d '\r')"
|
||||
if [ ! -f "/app/$pkg_tgz" ]; then
|
||||
echo "npm pack failed (expected /app/$pkg_tgz)"
|
||||
exit 1
|
||||
fi
|
||||
package_tgz="${OPENCLAW_CURRENT_PACKAGE_TGZ:?missing OPENCLAW_CURRENT_PACKAGE_TGZ}"
|
||||
git_root="/tmp/openclaw-git"
|
||||
mkdir -p "$git_root"
|
||||
# The git-style install fixture is unpacked from the tarball so this lane does
|
||||
# not depend on checkout source files being present in the Docker image.
|
||||
tar -xzf "$package_tgz" -C "$git_root" --strip-components=1
|
||||
(
|
||||
cd "$git_root"
|
||||
npm install --omit=optional --no-fund --no-audit >/tmp/openclaw-git-install.log 2>&1
|
||||
git init -q
|
||||
git config user.email "docker-e2e@openclaw.local"
|
||||
git config user.name "OpenClaw Docker E2E"
|
||||
git add -A
|
||||
git commit -qm "test fixture"
|
||||
)
|
||||
npm_log="/tmp/openclaw-doctor-switch-npm-install.log"
|
||||
if ! npm install -g --prefix /tmp/npm-prefix "/app/$pkg_tgz" >"$npm_log" 2>&1; then
|
||||
if ! npm install -g --prefix /tmp/npm-prefix "$package_tgz" >"$npm_log" 2>&1; then
|
||||
cat "$npm_log"
|
||||
exit 1
|
||||
fi
|
||||
@@ -95,12 +113,12 @@ LOGINCTL
|
||||
npm_entry="$npm_root/dist/index.js"
|
||||
fi
|
||||
|
||||
if [ -f "/app/dist/index.mjs" ]; then
|
||||
git_entry="/app/dist/index.mjs"
|
||||
if [ -f "$git_root/dist/index.mjs" ]; then
|
||||
git_entry="$git_root/dist/index.mjs"
|
||||
else
|
||||
git_entry="/app/dist/index.js"
|
||||
git_entry="$git_root/dist/index.js"
|
||||
fi
|
||||
git_cli="/app/openclaw.mjs"
|
||||
git_cli="$git_root/openclaw.mjs"
|
||||
|
||||
assert_entrypoint() {
|
||||
local unit_path="$1"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs a Docker Gateway plus MCP stdio bridge smoke with seeded conversations and
|
||||
# raw Claude notification-frame assertions.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -18,6 +20,7 @@ trap cleanup EXIT
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" mcp-channels
|
||||
|
||||
echo "Running in-container gateway + MCP smoke..."
|
||||
# Harness files are mounted read-only; the app under test comes from /app/dist.
|
||||
set +e
|
||||
docker run --rm \
|
||||
--name "$CONTAINER_NAME" \
|
||||
@@ -33,6 +36,7 @@ docker run --rm \
|
||||
-e "GW_URL=ws://127.0.0.1:$PORT" \
|
||||
-e "GW_TOKEN=$TOKEN" \
|
||||
-e "OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1" \
|
||||
-v "$ROOT_DIR/scripts/e2e:/app/scripts/e2e:ro" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc "set -euo pipefail
|
||||
entry=dist/index.mjs
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Shared MCP-channel Docker E2E harness helpers.
|
||||
// The mounted test harness imports packaged dist modules so bridge assertions run
|
||||
// against the OpenClaw npm tarball installed in the functional image.
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
import process from "node:process";
|
||||
@@ -6,10 +9,10 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
import { WebSocket } from "ws";
|
||||
import { z } from "zod";
|
||||
import { PROTOCOL_VERSION } from "../../src/gateway/protocol/index.ts";
|
||||
import { formatErrorMessage } from "../../src/infra/errors.ts";
|
||||
import { rawDataToString } from "../../src/infra/ws.ts";
|
||||
import { readStringValue } from "../../src/shared/string-coerce.ts";
|
||||
import { PROTOCOL_VERSION } from "../../dist/gateway/protocol/index.js";
|
||||
import { formatErrorMessage } from "../../dist/infra/errors.js";
|
||||
import { rawDataToString } from "../../dist/infra/ws.js";
|
||||
import { readStringValue } from "../../dist/shared/string-coerce.js";
|
||||
|
||||
export const ClaudeChannelNotificationSchema = z.object({
|
||||
method: z.literal("notifications/claude/channel"),
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Installs a prepared OpenClaw npm tarball in Docker, runs non-interactive
|
||||
# onboarding for a channel, and verifies one mocked model turn through Gateway.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-npm-onboard-channel-agent-e2e" OPENCLAW_NPM_ONBOARD_E2E_IMAGE)"
|
||||
DOCKER_TARGET="${OPENCLAW_NPM_ONBOARD_DOCKER_TARGET:-e2e-runner}"
|
||||
DOCKER_TARGET="${OPENCLAW_NPM_ONBOARD_DOCKER_TARGET:-bare}"
|
||||
HOST_BUILD="${OPENCLAW_NPM_ONBOARD_HOST_BUILD:-1}"
|
||||
PACKAGE_TGZ="${OPENCLAW_NPM_ONBOARD_PACKAGE_TGZ:-}"
|
||||
CHANNEL="${OPENCLAW_NPM_ONBOARD_CHANNEL:-telegram}"
|
||||
@@ -22,32 +25,14 @@ docker_e2e_build_or_reuse "$IMAGE_NAME" npm-onboard-channel-agent "$ROOT_DIR/scr
|
||||
|
||||
prepare_package_tgz() {
|
||||
if [ -n "$PACKAGE_TGZ" ]; then
|
||||
if [ ! -f "$PACKAGE_TGZ" ]; then
|
||||
echo "OPENCLAW_NPM_ONBOARD_PACKAGE_TGZ does not exist: $PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(cd "$(dirname "$PACKAGE_TGZ")" && pwd)/$(basename "$PACKAGE_TGZ")"
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz npm-onboard-channel-agent "$PACKAGE_TGZ")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$HOST_BUILD" != "0" ]; then
|
||||
echo "Building host package artifacts..."
|
||||
run_logged npm-onboard-channel-agent-host-build pnpm build
|
||||
else
|
||||
echo "Skipping host build (OPENCLAW_NPM_ONBOARD_HOST_BUILD=0)"
|
||||
fi
|
||||
|
||||
echo "Writing package inventory and packing once..."
|
||||
run_logged npm-onboard-channel-agent-inventory node --import tsx --input-type=module -e 'const { writePackageDistInventory } = await import("./src/infra/package-dist-inventory.ts"); await writePackageDistInventory(process.cwd());'
|
||||
local pack_dir
|
||||
pack_dir="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-npm-onboard-pack.XXXXXX")"
|
||||
run_logged npm-onboard-channel-agent-pack npm pack --ignore-scripts --pack-destination "$pack_dir"
|
||||
PACKAGE_TGZ="$(find "$pack_dir" -maxdepth 1 -name 'openclaw-*.tgz' -print -quit)"
|
||||
if [ -z "$PACKAGE_TGZ" ]; then
|
||||
echo "missing packed OpenClaw tarball" >&2
|
||||
if [ "$HOST_BUILD" = "0" ] && [ -z "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}" ]; then
|
||||
echo "OPENCLAW_NPM_ONBOARD_HOST_BUILD=0 requires OPENCLAW_CURRENT_PACKAGE_TGZ or OPENCLAW_NPM_ONBOARD_PACKAGE_TGZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_TGZ="$(cd "$(dirname "$PACKAGE_TGZ")" && pwd)/$(basename "$PACKAGE_TGZ")"
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz npm-onboard-channel-agent)"
|
||||
}
|
||||
|
||||
prepare_package_tgz
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Installs a published OpenClaw npm package in Docker, performs Telegram
|
||||
# onboarding/doctor recovery, then runs the Telegram QA live harness.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -141,9 +143,12 @@ command -v openclaw
|
||||
openclaw --version
|
||||
EOF
|
||||
|
||||
# Mount only test harness/plugin QA sources; the SUT itself is the npm install.
|
||||
run_logged docker run --rm \
|
||||
"${docker_env[@]}" \
|
||||
-v "$ROOT_DIR/.artifacts:/app/.artifacts" \
|
||||
-v "$ROOT_DIR/scripts/e2e:/app/scripts/e2e:ro" \
|
||||
-v "$ROOT_DIR/extensions:/app/extensions:ro" \
|
||||
-v "$npm_prefix_host:/npm-global" \
|
||||
-i "$IMAGE_NAME" bash -s <<'EOF'
|
||||
set -euo pipefail
|
||||
@@ -171,6 +176,10 @@ trap 'status=$?; dump_hotpath_logs "$status"; exit "$status"' ERR
|
||||
|
||||
command -v openclaw
|
||||
openclaw --version
|
||||
# The mounted QA harness imports openclaw/plugin-sdk; point that package import
|
||||
# at the installed npm package without copying source into the test image.
|
||||
mkdir -p /app/node_modules
|
||||
ln -sfn /npm-global/lib/node_modules/openclaw /app/node_modules/openclaw
|
||||
|
||||
echo "Running installed npm onboarding recovery hot path..."
|
||||
OPENAI_API_KEY="${OPENAI_API_KEY:-sk-openclaw-npm-telegram-hotpath}" openclaw onboard --non-interactive --accept-risk \
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#!/usr/bin/env -S node --import tsx
|
||||
// Telegram npm-live Docker harness.
|
||||
// Runs QA live transport code against the published package installed in Docker.
|
||||
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { formatErrorMessage } from "../../dist/infra/errors.js";
|
||||
import { runTelegramQaLive } from "../../extensions/qa-lab/src/live-transports/telegram/telegram-live.runtime.ts";
|
||||
import { formatErrorMessage } from "../../src/infra/errors.ts";
|
||||
|
||||
function parseBoolean(value: string | undefined) {
|
||||
const normalized = value?.trim().toLowerCase();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs a mocked OpenAI image-generation auth smoke inside Docker against the
|
||||
# package-installed functional E2E image.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -10,9 +12,11 @@ SKIP_BUILD="${OPENCLAW_OPENAI_IMAGE_AUTH_E2E_SKIP_BUILD:-0}"
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" openai-image-auth "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "" "$SKIP_BUILD"
|
||||
|
||||
echo "Running OpenAI image auth Docker E2E..."
|
||||
# Harness files are mounted read-only; the app under test comes from /app/dist.
|
||||
run_logged openai-image-auth docker run --rm \
|
||||
-e "OPENAI_API_KEY=sk-openclaw-image-auth-e2e" \
|
||||
-e "OPENCLAW_QA_ALLOW_LOCAL_IMAGE_PROVIDER=1" \
|
||||
-v "$ROOT_DIR/scripts/e2e:/app/scripts/e2e:ro" \
|
||||
-i "$IMAGE_NAME" bash -lc '
|
||||
set -euo pipefail
|
||||
export HOME="$(mktemp -d "/tmp/openclaw-openai-image-auth.XXXXXX")"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs Open WebUI against a Dockerized OpenClaw Gateway and verifies the proxied
|
||||
# chat path with a real OpenAI-compatible request.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -55,6 +57,7 @@ echo "Creating Docker network..."
|
||||
docker_cmd docker network create "$NET_NAME" >/dev/null
|
||||
|
||||
echo "Starting gateway container..."
|
||||
# Harness files are mounted read-only; the app under test comes from /app/dist.
|
||||
docker_cmd docker run -d \
|
||||
--name "$GW_NAME" \
|
||||
--network "$NET_NAME" \
|
||||
@@ -66,6 +69,7 @@ docker_cmd docker run -d \
|
||||
-e "OPENCLAW_SKIP_CANVAS_HOST=1" \
|
||||
-e OPENAI_API_KEY \
|
||||
${OPENAI_BASE_URL_VALUE:+-e OPENAI_BASE_URL} \
|
||||
-v "$ROOT_DIR/scripts/e2e:/app/scripts/e2e:ro" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc '
|
||||
set -euo pipefail
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// Pi bundle MCP tools Docker harness.
|
||||
// Imports packaged dist modules so tool materialization is verified against the
|
||||
// npm tarball installed in the functional image.
|
||||
import { randomUUID } from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import { createRequire } from "node:module";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { materializeBundleMcpToolsForRun } from "../../src/agents/pi-bundle-mcp-materialize.ts";
|
||||
import { materializeBundleMcpToolsForRun } from "../../dist/agents/pi-bundle-mcp-materialize.js";
|
||||
import {
|
||||
disposeAllSessionMcpRuntimes,
|
||||
getOrCreateSessionMcpRuntime,
|
||||
} from "../../src/agents/pi-bundle-mcp-runtime.ts";
|
||||
import { applyFinalEffectiveToolPolicy } from "../../src/agents/pi-embedded-runner/effective-tool-policy.ts";
|
||||
import type { OpenClawConfig } from "../../src/config/types.openclaw.ts";
|
||||
import { getPluginToolMeta } from "../../src/plugins/tools.ts";
|
||||
} from "../../dist/agents/pi-bundle-mcp-runtime.js";
|
||||
import { applyFinalEffectiveToolPolicy } from "../../dist/agents/pi-embedded-runner/effective-tool-policy.js";
|
||||
import type { OpenClawConfig } from "../../dist/config/types.openclaw.js";
|
||||
import { getPluginToolMeta } from "../../dist/plugins/tools.js";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verifies embedded Pi bundle MCP tool materialization and tool-policy behavior
|
||||
# inside the package-installed functional E2E image.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -16,10 +18,12 @@ trap cleanup EXIT
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" pi-bundle-mcp-tools
|
||||
|
||||
echo "Running in-container Pi bundle MCP tool availability smoke..."
|
||||
# Harness files are mounted read-only; the app under test comes from /app/dist.
|
||||
set +e
|
||||
docker run --rm \
|
||||
--name "$CONTAINER_NAME" \
|
||||
-e "OPENCLAW_STATE_DIR=/tmp/openclaw-state" \
|
||||
-v "$ROOT_DIR/scripts/e2e:/app/scripts/e2e:ro" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc "set -euo pipefail
|
||||
node --import tsx scripts/e2e/pi-bundle-mcp-tools-docker-client.ts
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verifies `openclaw plugins update` is a no-op for an already-current plugin.
|
||||
# The CLI under test is installed from the prepared npm tarball in a bare runner.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-plugin-update-e2e" OPENCLAW_PLUGIN_UPDATE_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_PLUGIN_UPDATE_E2E_SKIP_BUILD:-0}"
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz plugin-update "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
|
||||
# Bare lanes mount the package artifact instead of baking app sources into the image.
|
||||
docker_e2e_package_mount_args "$PACKAGE_TGZ"
|
||||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" plugin-update "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "" "$SKIP_BUILD"
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" plugin-update "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "bare" "$SKIP_BUILD"
|
||||
|
||||
echo "Running unchanged plugin update smoke..."
|
||||
docker run --rm \
|
||||
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
-e OPENCLAW_SKIP_CHANNELS=1 \
|
||||
-e OPENCLAW_SKIP_PROVIDERS=1 \
|
||||
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc "set -euo pipefail
|
||||
entry=dist/index.mjs
|
||||
[ -f \"\$entry\" ] || entry=dist/index.js
|
||||
package_tgz=\"\${OPENCLAW_CURRENT_PACKAGE_TGZ:?missing OPENCLAW_CURRENT_PACKAGE_TGZ}\"
|
||||
npm install -g --prefix /tmp/npm-prefix \"\$package_tgz\" --no-fund --no-audit >/tmp/openclaw-install.log 2>&1
|
||||
entry=\"/tmp/npm-prefix/lib/node_modules/openclaw/dist/index.mjs\"
|
||||
[ -f \"\$entry\" ] || entry=/tmp/npm-prefix/lib/node_modules/openclaw/dist/index.js
|
||||
export NPM_CONFIG_REGISTRY=http://127.0.0.1:4873
|
||||
export PATH=\"/tmp/npm-prefix/bin:\$PATH\"
|
||||
|
||||
mkdir -p \"\$HOME/.openclaw/extensions/lossless-claw\"
|
||||
cat > \"\$HOME/.openclaw/extensions/lossless-claw/package.json\" <<'JSON'
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Session runtime-context Docker harness.
|
||||
// Imports packaged dist modules so transcript behavior is verified against the
|
||||
// npm tarball installed in the functional image.
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
@@ -6,7 +9,7 @@ import { SessionManager } from "@mariozechner/pi-coding-agent";
|
||||
import {
|
||||
queueRuntimeContextForNextTurn,
|
||||
resolveRuntimeContextPromptParts,
|
||||
} from "../../src/agents/pi-embedded-runner/run/runtime-context-prompt.js";
|
||||
} from "../../dist/agents/pi-embedded-runner/run/runtime-context-prompt.js";
|
||||
|
||||
type TranscriptEntry = {
|
||||
type?: string;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verifies hidden runtime context transcript persistence in Docker using the
|
||||
# package-installed functional E2E image.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
@@ -17,10 +19,12 @@ trap cleanup EXIT
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" session-runtime-context
|
||||
|
||||
echo "Running session runtime context Docker E2E..."
|
||||
# Harness files are mounted read-only; the app under test comes from /app/dist.
|
||||
set +e
|
||||
docker run --rm \
|
||||
--name "$CONTAINER_NAME" \
|
||||
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
-v "$ROOT_DIR/scripts/e2e:/app/scripts/e2e:ro" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc 'set -euo pipefail; node --import tsx scripts/e2e/session-runtime-context-docker-client.ts' \
|
||||
>"$RUN_LOG" 2>&1
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# Exercises package-to-git and git-to-package update channel switching in Docker.
|
||||
# Both package and git fixtures are derived from the same prepared npm tarball.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
|
||||
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-update-channel-switch-e2e" OPENCLAW_UPDATE_CHANNEL_SWITCH_E2E_IMAGE)"
|
||||
SKIP_BUILD="${OPENCLAW_UPDATE_CHANNEL_SWITCH_E2E_SKIP_BUILD:-0}"
|
||||
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz update-channel-switch "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
|
||||
# Bare lanes mount the package artifact instead of baking app sources into the image.
|
||||
docker_e2e_package_mount_args "$PACKAGE_TGZ"
|
||||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" update-channel-switch "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "" "$SKIP_BUILD"
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" update-channel-switch "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "bare" "$SKIP_BUILD"
|
||||
|
||||
echo "Running update channel switch E2E..."
|
||||
docker run --rm \
|
||||
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
-e OPENCLAW_SKIP_CHANNELS=1 \
|
||||
-e OPENCLAW_SKIP_PROVIDERS=1 \
|
||||
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -lc 'set -euo pipefail
|
||||
|
||||
@@ -29,32 +36,26 @@ export OPENCLAW_DISABLE_BUNDLED_PLUGINS=1
|
||||
export OPENCLAW_NO_ONBOARD=1
|
||||
export OPENCLAW_NO_PROMPT=1
|
||||
|
||||
cat > /app/.gitignore <<'"'"'GITIGNORE'"'"'
|
||||
node_modules
|
||||
**/node_modules/
|
||||
dist
|
||||
dist-runtime
|
||||
.turbo
|
||||
coverage
|
||||
GITIGNORE
|
||||
|
||||
node --import tsx scripts/write-package-dist-inventory.ts
|
||||
package_tgz="${OPENCLAW_CURRENT_PACKAGE_TGZ:?missing OPENCLAW_CURRENT_PACKAGE_TGZ}"
|
||||
git_root="/tmp/openclaw-git"
|
||||
mkdir -p "$git_root"
|
||||
# Build the fake git install from the packed package contents, not the checkout.
|
||||
tar -xzf "$package_tgz" -C "$git_root" --strip-components=1
|
||||
(
|
||||
cd "$git_root"
|
||||
npm install --omit=optional --no-fund --no-audit >/tmp/openclaw-git-install.log 2>&1
|
||||
)
|
||||
|
||||
git config --global user.email "docker-e2e@openclaw.local"
|
||||
git config --global user.name "OpenClaw Docker E2E"
|
||||
git config --global gc.auto 0
|
||||
git -C /app init -q
|
||||
git -C /app config gc.auto 0
|
||||
git -C /app add -A
|
||||
git -C /app commit -qm "test fixture"
|
||||
fixture_sha="$(git -C /app rev-parse HEAD)"
|
||||
git -C "$git_root" init -q
|
||||
git -C "$git_root" config gc.auto 0
|
||||
git -C "$git_root" add -A
|
||||
git -C "$git_root" commit -qm "test fixture"
|
||||
fixture_sha="$(git -C "$git_root" rev-parse HEAD)"
|
||||
|
||||
pkg_tgz="$(npm pack --ignore-scripts --silent --pack-destination /tmp /app | tail -n 1 | tr -d "\r")"
|
||||
pkg_tgz_path="/tmp/$pkg_tgz"
|
||||
if [ ! -f "$pkg_tgz_path" ]; then
|
||||
echo "npm pack failed (expected $pkg_tgz_path)"
|
||||
exit 1
|
||||
fi
|
||||
pkg_tgz_path="$package_tgz"
|
||||
|
||||
npm install -g --prefix /tmp/npm-prefix --omit=optional "$pkg_tgz_path"
|
||||
|
||||
@@ -70,7 +71,7 @@ cat > "$HOME/.openclaw/openclaw.json" <<'"'"'JSON'"'"'
|
||||
}
|
||||
JSON
|
||||
|
||||
export OPENCLAW_GIT_DIR=/app
|
||||
export OPENCLAW_GIT_DIR="$git_root"
|
||||
export OPENCLAW_UPDATE_DEV_TARGET_REF="$fixture_sha"
|
||||
|
||||
echo "==> package -> git dev channel"
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Shared Docker E2E image resolver/builder.
|
||||
# Suite-specific scripts call this to resolve overrides, reuse pulled images, or
|
||||
# build the runner/functional images with the prepared OpenClaw package tarball.
|
||||
|
||||
DOCKER_E2E_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="${ROOT_DIR:-$(cd "$DOCKER_E2E_LIB_DIR/../.." && pwd)}"
|
||||
|
||||
source "$DOCKER_E2E_LIB_DIR/docker-e2e-logs.sh"
|
||||
source "$DOCKER_E2E_LIB_DIR/docker-build.sh"
|
||||
source "$DOCKER_E2E_LIB_DIR/docker-e2e-package.sh"
|
||||
|
||||
docker_e2e_resolve_image() {
|
||||
local default_image="$1"
|
||||
@@ -34,6 +39,11 @@ docker_e2e_build_or_reuse() {
|
||||
local context="${4:-$ROOT_DIR}"
|
||||
local target="${5:-}"
|
||||
local skip_build="${6:-0}"
|
||||
if [ -z "$target" ] && [ "$dockerfile" = "$ROOT_DIR/scripts/e2e/Dockerfile" ]; then
|
||||
# The generic E2E image defaults to the package-installed app image; tests
|
||||
# that need a clean install runner pass target=bare explicitly.
|
||||
target="functional"
|
||||
fi
|
||||
|
||||
if [ "${OPENCLAW_SKIP_DOCKER_BUILD:-0}" = "1" ] || [ "$skip_build" = "1" ]; then
|
||||
echo "Reusing Docker image: $image_name"
|
||||
@@ -53,6 +63,15 @@ docker_e2e_build_or_reuse() {
|
||||
if [ -n "$target" ]; then
|
||||
build_args+=(--target "$target")
|
||||
fi
|
||||
if [ "$target" = "functional" ]; then
|
||||
local package_tgz
|
||||
local package_context
|
||||
package_tgz="$(docker_e2e_prepare_package_tgz "$label")"
|
||||
package_context="$(docker_e2e_prepare_package_context "$package_tgz")"
|
||||
# The Dockerfile never sees repo sources as app input; functional installs
|
||||
# exactly this tarball through a named BuildKit context.
|
||||
build_args+=(--build-context "openclaw_package=$package_context")
|
||||
fi
|
||||
build_args+=(-t "$image_name" -f "$dockerfile" "$context")
|
||||
docker_build_run "$label-build" "${build_args[@]}"
|
||||
}
|
||||
|
||||
63
scripts/lib/docker-e2e-package.sh
Normal file
63
scripts/lib/docker-e2e-package.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Shared package helpers for Docker E2E scripts.
|
||||
# Builds or resolves one OpenClaw npm tarball and exposes mount/build-context
|
||||
# helpers so Docker lanes test the package artifact instead of repo sources.
|
||||
|
||||
DOCKER_E2E_PACKAGE_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="${ROOT_DIR:-$(cd "$DOCKER_E2E_PACKAGE_LIB_DIR/../.." && pwd)}"
|
||||
|
||||
if ! declare -F run_logged >/dev/null 2>&1; then
|
||||
source "$DOCKER_E2E_PACKAGE_LIB_DIR/docker-e2e-logs.sh"
|
||||
fi
|
||||
|
||||
docker_e2e_abs_path() {
|
||||
local file="$1"
|
||||
(cd "$(dirname "$file")" && printf '%s/%s\n' "$(pwd)" "$(basename "$file")")
|
||||
}
|
||||
|
||||
docker_e2e_prepare_package_tgz() {
|
||||
local label="$1"
|
||||
local package_tgz="${2:-${OPENCLAW_CURRENT_PACKAGE_TGZ:-}}"
|
||||
|
||||
if [ -n "$package_tgz" ]; then
|
||||
if [ ! -f "$package_tgz" ]; then
|
||||
echo "OpenClaw package tarball does not exist: $package_tgz" >&2
|
||||
return 1
|
||||
fi
|
||||
docker_e2e_abs_path "$package_tgz"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Building OpenClaw package artifacts..."
|
||||
run_logged "$label-host-build" pnpm build
|
||||
echo "Writing package inventory and packing OpenClaw once..."
|
||||
run_logged "$label-inventory" node --import tsx --input-type=module -e 'const { writePackageDistInventory } = await import("./src/infra/package-dist-inventory.ts"); await writePackageDistInventory(process.cwd());'
|
||||
|
||||
local pack_dir
|
||||
pack_dir="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-docker-e2e-pack.XXXXXX")"
|
||||
run_logged "$label-pack" npm pack --ignore-scripts --pack-destination "$pack_dir"
|
||||
|
||||
package_tgz="$(find "$pack_dir" -maxdepth 1 -name 'openclaw-*.tgz' -print -quit)"
|
||||
if [ -z "$package_tgz" ]; then
|
||||
echo "missing packed OpenClaw tarball" >&2
|
||||
return 1
|
||||
fi
|
||||
docker_e2e_abs_path "$package_tgz"
|
||||
}
|
||||
|
||||
docker_e2e_prepare_package_context() {
|
||||
local package_tgz="$1"
|
||||
local context_dir
|
||||
context_dir="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-docker-e2e-package-context.XXXXXX")"
|
||||
# BuildKit named contexts must be directories, so expose the tarball as a
|
||||
# stable filename inside a tiny temporary context.
|
||||
cp "$package_tgz" "$context_dir/openclaw-current.tgz"
|
||||
printf '%s\n' "$context_dir"
|
||||
}
|
||||
|
||||
docker_e2e_package_mount_args() {
|
||||
local package_tgz="$1"
|
||||
local target="${2:-/tmp/openclaw-current.tgz}"
|
||||
DOCKER_E2E_PACKAGE_ARGS=(-v "$package_tgz:$target:ro" -e "OPENCLAW_CURRENT_PACKAGE_TGZ=$target")
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
// Docker E2E aggregate scheduler.
|
||||
// Builds shared Docker images, prepares one OpenClaw npm tarball, assigns lanes
|
||||
// to bare/functional images, and runs lanes through weighted resource pools.
|
||||
import { spawn } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import { mkdir, readFile } from "node:fs/promises";
|
||||
@@ -661,8 +664,12 @@ function buildLaneRerunCommand(name, baseEnv) {
|
||||
["OPENCLAW_DOCKER_E2E_IMAGE", image || DEFAULT_E2E_IMAGE],
|
||||
["OPENCLAW_DOCKER_E2E_BARE_IMAGE", baseEnv.OPENCLAW_DOCKER_E2E_BARE_IMAGE],
|
||||
["OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE", baseEnv.OPENCLAW_DOCKER_E2E_FUNCTIONAL_IMAGE],
|
||||
["OPENCLAW_CURRENT_PACKAGE_TGZ", baseEnv.OPENCLAW_CURRENT_PACKAGE_TGZ],
|
||||
];
|
||||
return `${env.map(([key, value]) => `${key}=${shellQuote(value)}`).join(" ")} pnpm test:docker:all`;
|
||||
return `${env
|
||||
.filter(([, value]) => value !== undefined && value !== "")
|
||||
.map(([key, value]) => `${key}=${shellQuote(value)}`)
|
||||
.join(" ")} pnpm test:docker:all`;
|
||||
}
|
||||
|
||||
function findLaneByName(name) {
|
||||
@@ -805,11 +812,8 @@ function printLaneManifest(label, poolLanes, timingStore) {
|
||||
}
|
||||
}
|
||||
|
||||
function lanesNeedBundledPackage(poolLanes) {
|
||||
return poolLanes.some(
|
||||
(poolLane) =>
|
||||
poolLane.name === "npm-onboard-channel-agent" || poolLane.name.startsWith("bundled-channel"),
|
||||
);
|
||||
function lanesNeedOpenClawPackage(poolLanes) {
|
||||
return poolLanes.some((poolLane) => poolLane.e2eImageKind);
|
||||
}
|
||||
|
||||
function dockerPreflightContainerNames(raw) {
|
||||
@@ -1011,30 +1015,33 @@ async function runDockerPreflight(baseEnv, options) {
|
||||
console.log(`==> Docker preflight run: ${elapsedSeconds}s`);
|
||||
}
|
||||
|
||||
async function prepareBundledChannelPackage(baseEnv, logDir) {
|
||||
if (baseEnv.OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ) {
|
||||
console.log(`==> Bundled channel package: ${baseEnv.OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ}`);
|
||||
async function prepareOpenClawPackage(baseEnv, logDir) {
|
||||
const existing =
|
||||
baseEnv.OPENCLAW_CURRENT_PACKAGE_TGZ ||
|
||||
baseEnv.OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ ||
|
||||
baseEnv.OPENCLAW_NPM_ONBOARD_PACKAGE_TGZ;
|
||||
if (existing) {
|
||||
const packageTgz = path.resolve(existing);
|
||||
baseEnv.OPENCLAW_CURRENT_PACKAGE_TGZ = packageTgz;
|
||||
baseEnv.OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ ||= packageTgz;
|
||||
baseEnv.OPENCLAW_NPM_ONBOARD_PACKAGE_TGZ ||= packageTgz;
|
||||
baseEnv.OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD = "0";
|
||||
baseEnv.OPENCLAW_NPM_ONBOARD_HOST_BUILD = "0";
|
||||
console.log(`==> OpenClaw package: ${packageTgz}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const packDir = path.join(logDir, "bundled-channel-package");
|
||||
const packDir = path.join(logDir, "openclaw-package");
|
||||
await mkdir(packDir, { recursive: true });
|
||||
const packScript = [
|
||||
"set -euo pipefail",
|
||||
"node --import tsx --input-type=module -e \"const { writePackageDistInventory } = await import('./src/infra/package-dist-inventory.ts'); await writePackageDistInventory(process.cwd());\"",
|
||||
"npm pack --silent --ignore-scripts --pack-destination /tmp/openclaw-pack >/tmp/openclaw-pack.out",
|
||||
"cat /tmp/openclaw-pack.out",
|
||||
].join("\n");
|
||||
await runForeground("Build OpenClaw package artifacts once", "pnpm build", baseEnv);
|
||||
await runForeground(
|
||||
"Pack bundled channel package once from bare Docker E2E image",
|
||||
[
|
||||
"docker run --rm",
|
||||
"-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0",
|
||||
`-v ${shellQuote(packDir)}:/tmp/openclaw-pack`,
|
||||
shellQuote(baseEnv.OPENCLAW_DOCKER_E2E_BARE_IMAGE),
|
||||
"bash -lc",
|
||||
shellQuote(packScript),
|
||||
].join(" "),
|
||||
"Write OpenClaw package inventory",
|
||||
"node --import tsx --input-type=module -e \"const { writePackageDistInventory } = await import('./src/infra/package-dist-inventory.ts'); await writePackageDistInventory(process.cwd());\"",
|
||||
baseEnv,
|
||||
);
|
||||
await runForeground(
|
||||
"Pack OpenClaw package once",
|
||||
`npm pack --silent --ignore-scripts --pack-destination ${shellQuote(packDir)}`,
|
||||
baseEnv,
|
||||
);
|
||||
|
||||
@@ -1045,11 +1052,12 @@ async function prepareBundledChannelPackage(baseEnv, logDir) {
|
||||
if (!packed) {
|
||||
throw new Error(`missing packed OpenClaw tarball in ${packDir}`);
|
||||
}
|
||||
baseEnv.OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ = path.join(packDir, packed);
|
||||
baseEnv.OPENCLAW_CURRENT_PACKAGE_TGZ = path.join(packDir, packed);
|
||||
baseEnv.OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ = baseEnv.OPENCLAW_CURRENT_PACKAGE_TGZ;
|
||||
baseEnv.OPENCLAW_BUNDLED_CHANNEL_HOST_BUILD = "0";
|
||||
baseEnv.OPENCLAW_NPM_ONBOARD_PACKAGE_TGZ = baseEnv.OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ;
|
||||
baseEnv.OPENCLAW_NPM_ONBOARD_PACKAGE_TGZ = baseEnv.OPENCLAW_CURRENT_PACKAGE_TGZ;
|
||||
baseEnv.OPENCLAW_NPM_ONBOARD_HOST_BUILD = "0";
|
||||
console.log(`==> Bundled channel package: ${baseEnv.OPENCLAW_BUNDLED_CHANNEL_PACKAGE_TGZ}`);
|
||||
console.log(`==> OpenClaw package: ${baseEnv.OPENCLAW_CURRENT_PACKAGE_TGZ}`);
|
||||
}
|
||||
|
||||
function laneEnv(poolLane, baseEnv, logDir, cacheKey) {
|
||||
@@ -1530,10 +1538,17 @@ async function main() {
|
||||
});
|
||||
},
|
||||
);
|
||||
const scheduledLanes = [...orderedLanes, ...orderedTailLanes];
|
||||
if (lanesNeedOpenClawPackage(scheduledLanes)) {
|
||||
await runPhase(phases, "prepare-openclaw-package", {}, async () => {
|
||||
await prepareOpenClawPackage(baseEnv, logDir);
|
||||
});
|
||||
} else {
|
||||
console.log("==> OpenClaw package: not needed for selected lanes");
|
||||
}
|
||||
|
||||
if (buildEnabled) {
|
||||
const buildEntries = [];
|
||||
const scheduledLanes = [...orderedLanes, ...orderedTailLanes];
|
||||
if (scheduledLanes.some((poolLane) => poolLane.live)) {
|
||||
buildEntries.push({
|
||||
command: "pnpm test:docker:live-build",
|
||||
@@ -1547,7 +1562,7 @@ async function main() {
|
||||
command: "pnpm test:docker:e2e-build",
|
||||
env: {
|
||||
OPENCLAW_DOCKER_E2E_IMAGE: baseEnv.OPENCLAW_DOCKER_E2E_BARE_IMAGE,
|
||||
OPENCLAW_DOCKER_E2E_TARGET: "build",
|
||||
OPENCLAW_DOCKER_E2E_TARGET: "bare",
|
||||
},
|
||||
label: `shared bare Docker E2E image once: ${baseEnv.OPENCLAW_DOCKER_E2E_BARE_IMAGE}`,
|
||||
phaseDetails: { image: baseEnv.OPENCLAW_DOCKER_E2E_BARE_IMAGE, imageKind: "bare" },
|
||||
@@ -1573,13 +1588,6 @@ async function main() {
|
||||
} else {
|
||||
console.log(`==> Shared Docker image builds: skipped`);
|
||||
}
|
||||
if (lanesNeedBundledPackage([...orderedLanes, ...orderedTailLanes])) {
|
||||
await runPhase(phases, "prepare-bundled-channel-package", { imageKind: "bare" }, async () => {
|
||||
await prepareBundledChannelPackage(baseEnv, logDir);
|
||||
});
|
||||
} else {
|
||||
console.log("==> Bundled channel package: not needed for selected lanes");
|
||||
}
|
||||
|
||||
const options = {
|
||||
...schedulerOptions,
|
||||
|
||||
Reference in New Issue
Block a user