mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-08 21:09:23 +08:00
fix(docker): support .mjs entrypoints in images and e2e
This commit is contained in:
@@ -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"]
|
||||||
|
|||||||
21
openclaw.mjs
21
openclaw.mjs
@@ -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).");
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -80,10 +80,20 @@ LOGINCTL
|
|||||||
fi
|
fi
|
||||||
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"
|
||||||
git_entry="/app/dist/index.js"
|
if [ -f "$npm_root/dist/index.mjs" ]; then
|
||||||
git_cli="/app/openclaw.mjs"
|
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"
|
||||||
|
fi
|
||||||
|
git_cli="/app/openclaw.mjs"
|
||||||
|
|
||||||
assert_entrypoint() {
|
assert_entrypoint() {
|
||||||
local unit_path="$1"
|
local unit_path="$1"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -10,9 +10,20 @@ docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR"
|
|||||||
echo "Running onboarding E2E..."
|
echo "Running onboarding E2E..."
|
||||||
docker run --rm -t "$IMAGE_NAME" bash -lc '
|
docker run --rm -t "$IMAGE_NAME" bash -lc '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
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"
|
||||||
@@ -82,10 +93,10 @@ TRASH
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
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="$!"
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_for_gateway() {
|
wait_for_gateway() {
|
||||||
for _ in $(seq 1 20); do
|
for _ in $(seq 1 20); do
|
||||||
@@ -184,9 +195,9 @@ TRASH
|
|||||||
local send_fn="$3"
|
local send_fn="$3"
|
||||||
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() {
|
||||||
mktemp -d "/tmp/openclaw-e2e-$1.XXXXXX"
|
mktemp -d "/tmp/openclaw-e2e-$1.XXXXXX"
|
||||||
@@ -263,14 +274,14 @@ TRASH
|
|||||||
send "" 1.0
|
send "" 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
run_case_local_basic() {
|
run_case_local_basic() {
|
||||||
local home_dir
|
local home_dir
|
||||||
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 \
|
||||||
--mode local \
|
--mode local \
|
||||||
--skip-channels \
|
--skip-channels \
|
||||||
@@ -343,11 +354,11 @@ NODE
|
|||||||
local home_dir
|
local home_dir
|
||||||
home_dir="$(make_home remote-non-interactive)"
|
home_dir="$(make_home remote-non-interactive)"
|
||||||
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 \
|
||||||
--skip-skills \
|
--skip-skills \
|
||||||
--skip-health
|
--skip-health
|
||||||
@@ -388,7 +399,7 @@ NODE
|
|||||||
export HOME="$home_dir"
|
export HOME="$home_dir"
|
||||||
mkdir -p "$HOME/.openclaw"
|
mkdir -p "$HOME/.openclaw"
|
||||||
# Seed a remote config to exercise reset path.
|
# Seed a remote config to exercise reset path.
|
||||||
cat > "$HOME/.openclaw/openclaw.json" <<'"'"'JSON'"'"'
|
cat > "$HOME/.openclaw/openclaw.json" <<'"'"'JSON'"'"'
|
||||||
{
|
{
|
||||||
"agents": { "defaults": { "workspace": "/root/old" } },
|
"agents": { "defaults": { "workspace": "/root/old" } },
|
||||||
"gateway": {
|
"gateway": {
|
||||||
@@ -398,9 +409,9 @@ NODE
|
|||||||
}
|
}
|
||||||
JSON
|
JSON
|
||||||
|
|
||||||
node dist/index.js onboard \
|
node "$OPENCLAW_ENTRY" onboard \
|
||||||
--non-interactive \
|
--non-interactive \
|
||||||
--accept-risk \
|
--accept-risk \
|
||||||
--flow quickstart \
|
--flow quickstart \
|
||||||
--mode local \
|
--mode local \
|
||||||
--reset \
|
--reset \
|
||||||
@@ -438,10 +449,10 @@ NODE
|
|||||||
}
|
}
|
||||||
|
|
||||||
run_case_channels() {
|
run_case_channels() {
|
||||||
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"
|
||||||
@@ -483,7 +494,7 @@ NODE
|
|||||||
export HOME="$home_dir"
|
export HOME="$home_dir"
|
||||||
mkdir -p "$HOME/.openclaw"
|
mkdir -p "$HOME/.openclaw"
|
||||||
# Seed skills config to ensure it survives the wizard.
|
# Seed skills config to ensure it survives the wizard.
|
||||||
cat > "$HOME/.openclaw/openclaw.json" <<'"'"'JSON'"'"'
|
cat > "$HOME/.openclaw/openclaw.json" <<'"'"'JSON'"'"'
|
||||||
{
|
{
|
||||||
"skills": {
|
"skills": {
|
||||||
"allowBundled": ["__none__"],
|
"allowBundled": ["__none__"],
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -8,11 +8,21 @@ echo "Building Docker image..."
|
|||||||
docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR"
|
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"
|
||||||
mkdir -p "$HOME/.openclaw/extensions/demo-plugin"
|
mkdir -p "$HOME/.openclaw/extensions/demo-plugin"
|
||||||
|
|
||||||
cat > "$HOME/.openclaw/extensions/demo-plugin/index.js" <<'"'"'JS'"'"'
|
cat > "$HOME/.openclaw/extensions/demo-plugin/index.js" <<'"'"'JS'"'"'
|
||||||
@@ -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");
|
||||||
|
|||||||
@@ -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)),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user