fix(docker): support .mjs entrypoints in images and e2e

This commit is contained in:
Peter Steinberger
2026-02-06 17:18:10 -08:00
parent 2b6cf03b47
commit 80d42eb0ba
8 changed files with 116 additions and 59 deletions

View File

@@ -44,5 +44,5 @@ USER node
# #
# For container platforms requiring external health checks: # For container platforms requiring external health checks:
# 1. Set OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD env var # 1. Set OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD env var
# 2. Override CMD: ["node","dist/index.js","gateway","--allow-unconfigured","--bind","lan"] # 2. Override CMD: ["node","openclaw.mjs","gateway","--allow-unconfigured","--bind","lan"]
CMD ["node", "dist/index.js", "gateway", "--allow-unconfigured"] CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]

View File

@@ -11,4 +11,23 @@ if (module.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) {
} }
} }
await import("./dist/entry.js"); const tryImport = async (specifier) => {
try {
await import(specifier);
return true;
} catch (err) {
// Only swallow missing-module errors; rethrow real runtime errors.
if (err && typeof err === "object" && "code" in err && err.code === "ERR_MODULE_NOT_FOUND") {
return false;
}
throw err;
}
};
if (await tryImport("./dist/entry.js")) {
// OK
} else if (await tryImport("./dist/entry.mjs")) {
// OK
} else {
throw new Error("openclaw: missing dist/entry.(m)js (build output).");
}

View File

@@ -6,7 +6,7 @@ WORKDIR /app
ENV NODE_OPTIONS="--disable-warning=ExperimentalWarning" ENV NODE_OPTIONS="--disable-warning=ExperimentalWarning"
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json vitest.config.ts vitest.e2e.config.ts openclaw.mjs ./ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json tsdown.config.ts vitest.config.ts vitest.e2e.config.ts openclaw.mjs ./
COPY src ./src COPY src ./src
COPY test ./test COPY test ./test
COPY scripts ./scripts COPY scripts ./scripts

View File

@@ -81,8 +81,18 @@ LOGINCTL
npm install -g --prefix /tmp/npm-prefix "/app/$pkg_tgz" npm install -g --prefix /tmp/npm-prefix "/app/$pkg_tgz"
npm_bin="/tmp/npm-prefix/bin/openclaw" npm_bin="/tmp/npm-prefix/bin/openclaw"
npm_entry="/tmp/npm-prefix/lib/node_modules/openclaw/dist/index.js" npm_root="/tmp/npm-prefix/lib/node_modules/openclaw"
if [ -f "$npm_root/dist/index.mjs" ]; then
npm_entry="$npm_root/dist/index.mjs"
else
npm_entry="$npm_root/dist/index.js"
fi
if [ -f "/app/dist/index.mjs" ]; then
git_entry="/app/dist/index.mjs"
else
git_entry="/app/dist/index.js" git_entry="/app/dist/index.js"
fi
git_cli="/app/openclaw.mjs" git_cli="/app/openclaw.mjs"
assert_entrypoint() { assert_entrypoint() {

View File

@@ -31,7 +31,7 @@ echo "Starting gateway container..."
-e "OPENCLAW_SKIP_CRON=1" \ -e "OPENCLAW_SKIP_CRON=1" \
-e "OPENCLAW_SKIP_CANVAS_HOST=1" \ -e "OPENCLAW_SKIP_CANVAS_HOST=1" \
"$IMAGE_NAME" \ "$IMAGE_NAME" \
bash -lc "node dist/index.js gateway --port $PORT --bind lan --allow-unconfigured > /tmp/gateway-net-e2e.log 2>&1" bash -lc "entry=dist/index.mjs; [ -f \"\$entry\" ] || entry=dist/index.js; node \"\$entry\" gateway --port $PORT --bind lan --allow-unconfigured > /tmp/gateway-net-e2e.log 2>&1"
echo "Waiting for gateway to come up..." echo "Waiting for gateway to come up..."
ready=0 ready=0
@@ -77,9 +77,9 @@ docker run --rm \
-e "GW_URL=ws://$GW_NAME:$PORT" \ -e "GW_URL=ws://$GW_NAME:$PORT" \
-e "GW_TOKEN=$TOKEN" \ -e "GW_TOKEN=$TOKEN" \
"$IMAGE_NAME" \ "$IMAGE_NAME" \
bash -lc "node - <<'NODE' bash -lc "node --import tsx - <<'NODE'
import { WebSocket } from \"ws\"; import { WebSocket } from \"ws\";
import { PROTOCOL_VERSION } from \"./dist/gateway/protocol/index.js\"; import { PROTOCOL_VERSION } from \"./src/gateway/protocol/index.ts\";
const url = process.env.GW_URL; const url = process.env.GW_URL;
const token = process.env.GW_TOKEN; const token = process.env.GW_TOKEN;

View File

@@ -13,6 +13,17 @@ docker run --rm -t "$IMAGE_NAME" bash -lc '
trap "" PIPE trap "" PIPE
export TERM=xterm-256color export TERM=xterm-256color
ONBOARD_FLAGS="--flow quickstart --auth-choice skip --skip-channels --skip-skills --skip-daemon --skip-ui" ONBOARD_FLAGS="--flow quickstart --auth-choice skip --skip-channels --skip-skills --skip-daemon --skip-ui"
# tsdown may emit dist/index.js or dist/index.mjs depending on runtime/bundler.
if [ -f dist/index.mjs ]; then
OPENCLAW_ENTRY="dist/index.mjs"
elif [ -f dist/index.js ]; then
OPENCLAW_ENTRY="dist/index.js"
else
echo "Missing dist/index.(m)js (build output):"
ls -la dist || true
exit 1
fi
export OPENCLAW_ENTRY
# Provide a minimal trash shim to avoid noisy "missing trash" logs in containers. # Provide a minimal trash shim to avoid noisy "missing trash" logs in containers.
export PATH="/tmp/openclaw-bin:$PATH" export PATH="/tmp/openclaw-bin:$PATH"
@@ -83,7 +94,7 @@ TRASH
} }
start_gateway() { start_gateway() {
node dist/index.js gateway --port 18789 --bind loopback --allow-unconfigured > /tmp/gateway-e2e.log 2>&1 & node "$OPENCLAW_ENTRY" gateway --port 18789 --bind loopback --allow-unconfigured > /tmp/gateway-e2e.log 2>&1 &
GATEWAY_PID="$!" GATEWAY_PID="$!"
} }
@@ -185,7 +196,7 @@ TRASH
local validate_fn="${4:-}" local validate_fn="${4:-}"
# Default onboarding command wrapper. # Default onboarding command wrapper.
run_wizard_cmd "$case_name" "$home_dir" "node dist/index.js onboard $ONBOARD_FLAGS" "$send_fn" true "$validate_fn" run_wizard_cmd "$case_name" "$home_dir" "node \"$OPENCLAW_ENTRY\" onboard $ONBOARD_FLAGS" "$send_fn" true "$validate_fn"
} }
make_home() { make_home() {
@@ -268,7 +279,7 @@ TRASH
home_dir="$(make_home local-basic)" home_dir="$(make_home local-basic)"
export HOME="$home_dir" export HOME="$home_dir"
mkdir -p "$HOME" mkdir -p "$HOME"
node dist/index.js onboard \ node "$OPENCLAW_ENTRY" onboard \
--non-interactive \ --non-interactive \
--accept-risk \ --accept-risk \
--flow quickstart \ --flow quickstart \
@@ -345,7 +356,7 @@ NODE
export HOME="$home_dir" export HOME="$home_dir"
mkdir -p "$HOME" mkdir -p "$HOME"
# Smoke test non-interactive remote config write. # Smoke test non-interactive remote config write.
node dist/index.js onboard --non-interactive --accept-risk \ node "$OPENCLAW_ENTRY" onboard --non-interactive --accept-risk \
--mode remote \ --mode remote \
--remote-url ws://gateway.local:18789 \ --remote-url ws://gateway.local:18789 \
--remote-token remote-token \ --remote-token remote-token \
@@ -398,7 +409,7 @@ NODE
} }
JSON JSON
node dist/index.js onboard \ node "$OPENCLAW_ENTRY" onboard \
--non-interactive \ --non-interactive \
--accept-risk \ --accept-risk \
--flow quickstart \ --flow quickstart \
@@ -441,7 +452,7 @@ NODE
local home_dir local home_dir
home_dir="$(make_home channels)" home_dir="$(make_home channels)"
# Channels-only configure flow. # Channels-only configure flow.
run_wizard_cmd channels "$home_dir" "node dist/index.js configure --section channels" send_channels_flow run_wizard_cmd channels "$home_dir" "node \"$OPENCLAW_ENTRY\" configure --section channels" send_channels_flow
config_path="$HOME/.openclaw/openclaw.json" config_path="$HOME/.openclaw/openclaw.json"
assert_file "$config_path" assert_file "$config_path"
@@ -492,7 +503,7 @@ NODE
} }
JSON JSON
run_wizard_cmd skills "$home_dir" "node dist/index.js configure --section skills" send_skills_flow run_wizard_cmd skills "$home_dir" "node \"$OPENCLAW_ENTRY\" configure --section skills" send_skills_flow
config_path="$HOME/.openclaw/openclaw.json" config_path="$HOME/.openclaw/openclaw.json"
assert_file "$config_path" assert_file "$config_path"

View File

@@ -10,6 +10,16 @@ docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR"
echo "Running plugins Docker E2E..." echo "Running plugins Docker E2E..."
docker run --rm -t "$IMAGE_NAME" bash -lc ' docker run --rm -t "$IMAGE_NAME" bash -lc '
set -euo pipefail set -euo pipefail
if [ -f dist/index.mjs ]; then
OPENCLAW_ENTRY="dist/index.mjs"
elif [ -f dist/index.js ]; then
OPENCLAW_ENTRY="dist/index.js"
else
echo "Missing dist/index.(m)js (build output):"
ls -la dist || true
exit 1
fi
export OPENCLAW_ENTRY
home_dir=$(mktemp -d "/tmp/openclaw-plugins-e2e.XXXXXX") home_dir=$(mktemp -d "/tmp/openclaw-plugins-e2e.XXXXXX")
export HOME="$home_dir" export HOME="$home_dir"
@@ -38,7 +48,7 @@ JS
} }
JSON JSON
node dist/index.js plugins list --json > /tmp/plugins.json node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins.json
node - <<'"'"'NODE'"'"' node - <<'"'"'NODE'"'"'
const fs = require("node:fs"); const fs = require("node:fs");
@@ -99,8 +109,8 @@ JS
JSON JSON
tar -czf /tmp/demo-plugin-tgz.tgz -C "$pack_dir" package tar -czf /tmp/demo-plugin-tgz.tgz -C "$pack_dir" package
node dist/index.js plugins install /tmp/demo-plugin-tgz.tgz node "$OPENCLAW_ENTRY" plugins install /tmp/demo-plugin-tgz.tgz
node dist/index.js plugins list --json > /tmp/plugins2.json node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins2.json
node - <<'"'"'NODE'"'"' node - <<'"'"'NODE'"'"'
const fs = require("node:fs"); const fs = require("node:fs");
@@ -145,8 +155,8 @@ JS
} }
JSON JSON
node dist/index.js plugins install "$dir_plugin" node "$OPENCLAW_ENTRY" plugins install "$dir_plugin"
node dist/index.js plugins list --json > /tmp/plugins3.json node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins3.json
node - <<'"'"'NODE'"'"' node - <<'"'"'NODE'"'"'
const fs = require("node:fs"); const fs = require("node:fs");
@@ -192,8 +202,8 @@ JS
} }
JSON JSON
node dist/index.js plugins install "file:$file_pack_dir/package" node "$OPENCLAW_ENTRY" plugins install "file:$file_pack_dir/package"
node dist/index.js plugins list --json > /tmp/plugins4.json node "$OPENCLAW_ENTRY" plugins list --json > /tmp/plugins4.json
node - <<'"'"'NODE'"'"' node - <<'"'"'NODE'"'"'
const fs = require("node:fs"); const fs = require("node:fs");

View File

@@ -7,9 +7,9 @@ import { join, resolve } from "node:path";
type PackFile = { path: string }; type PackFile = { path: string };
type PackResult = { files?: PackFile[] }; type PackResult = { files?: PackFile[] };
const requiredPaths = [ const requiredPathGroups = [
"dist/index.js", ["dist/index.js", "dist/index.mjs"],
"dist/entry.js", ["dist/entry.js", "dist/entry.mjs"],
"dist/plugin-sdk/index.js", "dist/plugin-sdk/index.js",
"dist/plugin-sdk/index.d.ts", "dist/plugin-sdk/index.d.ts",
"dist/build-info.json", "dist/build-info.json",
@@ -82,7 +82,14 @@ function main() {
const files = results.flatMap((entry) => entry.files ?? []); const files = results.flatMap((entry) => entry.files ?? []);
const paths = new Set(files.map((file) => file.path)); const paths = new Set(files.map((file) => file.path));
const missing = requiredPaths.filter((path) => !paths.has(path)); const missing = requiredPathGroups
.flatMap((group) => {
if (Array.isArray(group)) {
return group.some((path) => paths.has(path)) ? [] : [group.join(" or ")];
}
return paths.has(group) ? [] : [group];
})
.toSorted();
const forbidden = [...paths].filter((path) => const forbidden = [...paths].filter((path) =>
forbiddenPrefixes.some((prefix) => path.startsWith(prefix)), forbiddenPrefixes.some((prefix) => path.startsWith(prefix)),
); );