refactor(test): split e2e fixture helpers

This commit is contained in:
Peter Steinberger
2026-04-29 11:48:52 +01:00
parent 0c9f84451a
commit 4f73baf7d7
18 changed files with 466 additions and 467 deletions

View File

@@ -12,63 +12,9 @@ export OPENCLAW_DISABLE_BUNDLED_PLUGINS=1
# Stub systemd/loginctl so doctor + daemon flows work in Docker.
export PATH="/tmp/openclaw-bin:$PATH"
mkdir -p /tmp/openclaw-bin
cat >/tmp/openclaw-bin/systemctl <<"SYSTEMCTL"
#!/usr/bin/env bash
set -euo pipefail
args=("$@")
if [[ "${args[0]:-}" == "--user" ]]; then
args=("${args[@]:1}")
fi
cmd="${args[0]:-}"
case "$cmd" in
status)
exit 0
;;
is-active)
echo "inactive" >&2
exit 3
;;
is-enabled)
unit="${args[1]:-}"
unit_path="$HOME/.config/systemd/user/${unit}"
if [ -f "$unit_path" ]; then
echo "enabled"
exit 0
fi
echo "disabled" >&2
exit 1
;;
show)
echo "ActiveState=inactive"
echo "SubState=dead"
echo "MainPID=0"
echo "ExecMainStatus=0"
echo "ExecMainCode=0"
exit 0
;;
*)
exit 0
;;
esac
SYSTEMCTL
chmod +x /tmp/openclaw-bin/systemctl
cat >/tmp/openclaw-bin/loginctl <<"LOGINCTL"
#!/usr/bin/env bash
set -euo pipefail
if [[ "$*" == *"show-user"* ]]; then
echo "Linger=yes"
exit 0
fi
if [[ "$*" == *"enable-linger"* ]]; then
exit 0
fi
exit 0
LOGINCTL
chmod +x /tmp/openclaw-bin/loginctl
cp scripts/e2e/lib/doctor-install-switch/shims/systemctl /tmp/openclaw-bin/systemctl
cp scripts/e2e/lib/doctor-install-switch/shims/loginctl /tmp/openclaw-bin/loginctl
chmod +x /tmp/openclaw-bin/systemctl /tmp/openclaw-bin/loginctl
package_tgz="${OPENCLAW_CURRENT_PACKAGE_TGZ:?missing OPENCLAW_CURRENT_PACKAGE_TGZ}"
git_root="/tmp/openclaw-git"

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
case "$*" in
*show-user*) echo "Linger=yes" ;;
*enable-linger*) ;;
esac

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -euo pipefail
args=("$@")
if [[ "${args[0]:-}" == "--user" ]]; then
args=("${args[@]:1}")
fi
cmd="${args[0]:-}"
case "$cmd" in
status) ;;
is-active)
echo "inactive" >&2
exit 3
;;
is-enabled)
unit="${args[1]:-}"
unit_path="$HOME/.config/systemd/user/${unit}"
if [ -f "$unit_path" ]; then
echo "enabled"
exit 0
fi
echo "disabled" >&2
exit 1
;;
show)
printf "%s\n" \
"ActiveState=inactive" \
"SubState=dead" \
"MainPID=0" \
"ExecMainStatus=0" \
"ExecMainCode=0"
;;
esac

View File

@@ -1,283 +1,16 @@
import fs from "node:fs";
import path from "node:path";
import { configCommands } from "./fixtures/config.mjs";
import { pluginCommands } from "./fixtures/plugins.mjs";
import { workspaceCommands } from "./fixtures/workspace.mjs";
const [command, ...args] = process.argv.slice(2);
const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
const write = (file, contents) => {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, contents);
};
const writeJson = (file, value) => write(file, json(value));
const requireArg = (value, name) => {
if (!value) {
throw new Error(`${name} is required`);
}
return value;
};
const assert = (condition, message) => {
if (!condition) {
throw new Error(message);
}
};
function writePluginManifest(file, id) {
writeJson(file, { id, configSchema: { type: "object", properties: {} } });
const handler = {
...pluginCommands,
...configCommands,
...workspaceCommands,
}[command];
if (!handler) {
throw new Error(`unknown fixture command: ${command}`);
}
function writePluginDemo([dir]) {
write(
path.join(requireArg(dir, "dir"), "index.js"),
'module.exports = { id: "demo-plugin", name: "Demo Plugin", description: "Docker E2E demo plugin", register(api) { api.registerTool(() => null, { name: "demo_tool" }); api.registerGatewayMethod("demo.ping", async () => ({ ok: true })); api.registerCli(() => {}, { commands: ["demo"] }); api.registerService({ id: "demo-service", start: () => {} }); }, };\n',
);
writePluginManifest(path.join(dir, "openclaw.plugin.json"), "demo-plugin");
}
function writePlugin([dir, id, version, method, name]) {
for (const [value, label] of [
[dir, "dir"],
[id, "id"],
[version, "version"],
[method, "method"],
[name, "name"],
]) {
requireArg(value, label);
}
writeJson(path.join(dir, "package.json"), {
name: `@openclaw/${id}`,
version,
openclaw: { extensions: ["./index.js"] },
});
write(
path.join(dir, "index.js"),
`module.exports = { id: ${JSON.stringify(id)}, name: ${JSON.stringify(name)}, register(api) { api.registerGatewayMethod(${JSON.stringify(method)}, async () => ({ ok: true })); }, };\n`,
);
writePluginManifest(path.join(dir, "openclaw.plugin.json"), id);
}
function writeClaudeBundle([root]) {
root = requireArg(root, "root");
writeJson(path.join(root, ".claude-plugin", "plugin.json"), { name: "claude-bundle-e2e" });
write(
path.join(root, "commands", "office-hours.md"),
"---\ndescription: Help with architecture and rollout planning\n---\nAct as an engineering advisor.\n\nFocus on:\n$ARGUMENTS\n",
);
}
function writePluginMarketplace([root]) {
root = requireArg(root, "root");
writeJson(path.join(root, ".claude-plugin", "marketplace.json"), {
name: "Fixture Marketplace",
version: "1.0.0",
plugins: [
{
name: "marketplace-shortcut",
version: "0.0.1",
description: "Shortcut install fixture",
source: "./plugins/marketplace-shortcut",
},
{
name: "marketplace-direct",
version: "0.0.1",
description: "Explicit marketplace fixture",
source: { type: "path", path: "./plugins/marketplace-direct" },
},
],
});
writeJson(path.join(process.env.HOME, ".claude", "plugins", "known_marketplaces.json"), {
"claude-fixtures": {
installLocation: root,
source: { type: "github", repo: "openclaw/fixture-marketplace" },
},
});
}
function writeConfig(kind) {
const configPath = requireArg(process.env.OPENCLAW_CONFIG_PATH, "OPENCLAW_CONFIG_PATH");
const port = Number(process.env.PORT ?? 18789);
const config =
kind === "config-reload"
? {
gateway: {
port,
auth: {
mode: "token",
token: { source: "env", provider: "default", id: "GATEWAY_AUTH_TOKEN_REF" },
},
channelHealthCheckMinutes: 1,
controlUi: { enabled: false },
reload: { mode: "hybrid", debounceMs: 0 },
},
}
: kind === "browser-cdp"
? {
gateway: {
port,
auth: {
mode: "token",
token: requireArg(process.env.OPENCLAW_GATEWAY_TOKEN, "OPENCLAW_GATEWAY_TOKEN"),
},
controlUi: { enabled: false },
},
browser: {
enabled: true,
defaultProfile: "docker-cdp",
ssrfPolicy: { allowedHostnames: ["127.0.0.1"] },
profiles: {
"docker-cdp": {
cdpUrl: `http://127.0.0.1:${Number(process.env.CDP_PORT ?? 19222)}`,
color: "#FF4500",
},
},
},
}
: null;
writeJson(configPath, requireArg(config, "known config kind"));
}
function writeOpenAiWebSearchMinimalConfig() {
writeJson(path.join(process.env.OPENCLAW_STATE_DIR, "openclaw.json"), {
agents: {
defaults: {
model: { primary: "openai/gpt-5" },
models: {
"openai/gpt-5": {
params: { transport: "sse", openaiWsWarmup: false },
},
},
},
},
models: {
providers: {
openai: {
api: "openai-responses",
baseUrl: "http://api.openai.com/v1",
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
request: { allowPrivateNetwork: true },
models: [
{
id: "gpt-5",
name: "gpt-5",
api: "openai-responses",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
contextTokens: 96000,
maxTokens: 4096,
},
],
},
},
},
tools: { web: { search: { enabled: true, maxResults: 3 } } },
plugins: { enabled: true, allow: ["openai"], entries: { openai: { enabled: true } } },
gateway: { auth: { mode: "token", token: process.env.OPENCLAW_GATEWAY_TOKEN } },
});
}
function writeOpenWebUiConfig([openaiApiKey]) {
const batchPath = requireArg(
process.env.OPENCLAW_CONFIG_BATCH_PATH,
"OPENCLAW_CONFIG_BATCH_PATH",
);
writeJson(batchPath, [
{ path: "models.providers.openai.apiKey", value: requireArg(openaiApiKey, "OpenAI API key") },
{
path: "models.providers.openai.baseUrl",
value: (process.env.OPENAI_BASE_URL || "https://api.openai.com/v1").trim(),
},
{ path: "models.providers.openai.models", value: [] },
{ path: "gateway.controlUi.enabled", value: false },
{ path: "gateway.mode", value: "local" },
{ path: "gateway.bind", value: "lan" },
{ path: "gateway.auth.mode", value: "token" },
{ path: "gateway.auth.token", value: process.env.OPENCLAW_GATEWAY_TOKEN },
{ path: "gateway.http.endpoints.chatCompletions.enabled", value: true },
{ path: "agents.defaults.model.primary", value: process.env.OPENCLAW_OPENWEBUI_MODEL },
]);
}
function writeOpenWebUiWorkspace() {
const workspace =
process.env.OPENCLAW_WORKSPACE_DIR || path.join(process.env.HOME, ".openclaw", "workspace");
write(
path.join(workspace, "IDENTITY.md"),
"# Identity\n\n- Name: OpenClaw\n- Purpose: Open WebUI Docker compatibility smoke test assistant.\n",
);
writeJson(path.join(workspace, ".openclaw", "workspace-state.json"), {
version: 1,
setupCompletedAt: "2026-01-01T00:00:00.000Z",
});
fs.rmSync(path.join(workspace, "BOOTSTRAP.md"), { force: true });
}
function writeAgentsDeleteConfig() {
const stateDir = requireArg(process.env.OPENCLAW_STATE_DIR, "OPENCLAW_STATE_DIR");
const sharedWorkspace = requireArg(process.env.SHARED_WORKSPACE, "SHARED_WORKSPACE");
fs.mkdirSync(sharedWorkspace, { recursive: true });
writeJson(path.join(stateDir, "openclaw.json"), {
agents: {
list: [
{ id: "main", workspace: sharedWorkspace },
{ id: "ops", workspace: sharedWorkspace },
],
},
});
}
function assertAgentsDeleteResult([outputPath]) {
let parsed;
try {
parsed = readJson(requireArg(outputPath, "outputPath"));
} catch (error) {
console.error("agents delete --json did not emit valid JSON:");
console.error(fs.readFileSync(outputPath, "utf8").trim());
throw error;
}
for (const [actual, expected, label] of [
[parsed.agentId, "ops", "agentId"],
[parsed.workspace, process.env.SHARED_WORKSPACE, "workspace"],
[parsed.workspaceRetained, true, "workspaceRetained"],
[parsed.workspaceRetainedReason, "shared", "workspaceRetainedReason"],
]) {
assert(actual === expected, `${label} mismatch: ${JSON.stringify(actual)}`);
}
assert(
Array.isArray(parsed.workspaceSharedWith) && parsed.workspaceSharedWith.includes("main"),
"missing shared-with main marker",
);
assert(fs.existsSync(process.env.SHARED_WORKSPACE), "shared workspace was removed");
const remaining =
readJson(path.join(process.env.OPENCLAW_STATE_DIR, "openclaw.json"))?.agents?.list ?? [];
assert(Array.isArray(remaining), "agents list missing after delete");
assert(!remaining.some((entry) => entry?.id === "ops"), "deleted agent remained in config");
assert(
remaining.some((entry) => entry?.id === "main"),
"main agent missing after delete",
);
console.log("agents delete shared workspace smoke ok");
}
const commands = {
"plugin-demo": writePluginDemo,
plugin: writePlugin,
"plugin-manifest": ([file, id]) =>
writePluginManifest(requireArg(file, "file"), requireArg(id, "id")),
"claude-bundle": writeClaudeBundle,
marketplace: writePluginMarketplace,
"config-reload": () => writeConfig("config-reload"),
"browser-cdp": () => writeConfig("browser-cdp"),
"openai-web-search-minimal-config": writeOpenAiWebSearchMinimalConfig,
"openwebui-config": writeOpenWebUiConfig,
"openwebui-workspace": writeOpenWebUiWorkspace,
"agents-delete-config": writeAgentsDeleteConfig,
"agents-delete-assert": assertAgentsDeleteResult,
};
(
commands[command] ??
(() => {
throw new Error(`unknown fixture command: ${command}`);
})
)(args);
handler(args);

View File

@@ -0,0 +1,24 @@
import fs from "node:fs";
import path from "node:path";
export const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
export const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
export const write = (file, contents) => {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, contents);
};
export const writeJson = (file, value) => write(file, json(value));
export const requireArg = (value, name) => {
if (!value) {
throw new Error(`${name} is required`);
}
return value;
};
export const assert = (condition, message) => {
if (!condition) {
throw new Error(message);
}
};

View File

@@ -0,0 +1,115 @@
import path from "node:path";
import { requireArg, writeJson } from "./common.mjs";
function writeConfig(kind) {
const configPath = requireArg(process.env.OPENCLAW_CONFIG_PATH, "OPENCLAW_CONFIG_PATH");
const port = Number(process.env.PORT ?? 18789);
const config =
kind === "config-reload"
? {
gateway: {
port,
auth: {
mode: "token",
token: { source: "env", provider: "default", id: "GATEWAY_AUTH_TOKEN_REF" },
},
channelHealthCheckMinutes: 1,
controlUi: { enabled: false },
reload: { mode: "hybrid", debounceMs: 0 },
},
}
: kind === "browser-cdp"
? {
gateway: {
port,
auth: {
mode: "token",
token: requireArg(process.env.OPENCLAW_GATEWAY_TOKEN, "OPENCLAW_GATEWAY_TOKEN"),
},
controlUi: { enabled: false },
},
browser: {
enabled: true,
defaultProfile: "docker-cdp",
ssrfPolicy: { allowedHostnames: ["127.0.0.1"] },
profiles: {
"docker-cdp": {
cdpUrl: `http://127.0.0.1:${Number(process.env.CDP_PORT ?? 19222)}`,
color: "#FF4500",
},
},
},
}
: null;
writeJson(configPath, requireArg(config, "known config kind"));
}
function writeOpenAiWebSearchMinimalConfig() {
writeJson(path.join(process.env.OPENCLAW_STATE_DIR, "openclaw.json"), {
agents: {
defaults: {
model: { primary: "openai/gpt-5" },
models: {
"openai/gpt-5": {
params: { transport: "sse", openaiWsWarmup: false },
},
},
},
},
models: {
providers: {
openai: {
api: "openai-responses",
baseUrl: "http://api.openai.com/v1",
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
request: { allowPrivateNetwork: true },
models: [
{
id: "gpt-5",
name: "gpt-5",
api: "openai-responses",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
contextTokens: 96000,
maxTokens: 4096,
},
],
},
},
},
tools: { web: { search: { enabled: true, maxResults: 3 } } },
plugins: { enabled: true, allow: ["openai"], entries: { openai: { enabled: true } } },
gateway: { auth: { mode: "token", token: process.env.OPENCLAW_GATEWAY_TOKEN } },
});
}
function writeOpenWebUiConfig([openaiApiKey]) {
const batchPath = requireArg(
process.env.OPENCLAW_CONFIG_BATCH_PATH,
"OPENCLAW_CONFIG_BATCH_PATH",
);
writeJson(batchPath, [
{ path: "models.providers.openai.apiKey", value: requireArg(openaiApiKey, "OpenAI API key") },
{
path: "models.providers.openai.baseUrl",
value: (process.env.OPENAI_BASE_URL || "https://api.openai.com/v1").trim(),
},
{ path: "models.providers.openai.models", value: [] },
{ path: "gateway.controlUi.enabled", value: false },
{ path: "gateway.mode", value: "local" },
{ path: "gateway.bind", value: "lan" },
{ path: "gateway.auth.mode", value: "token" },
{ path: "gateway.auth.token", value: process.env.OPENCLAW_GATEWAY_TOKEN },
{ path: "gateway.http.endpoints.chatCompletions.enabled", value: true },
{ path: "agents.defaults.model.primary", value: process.env.OPENCLAW_OPENWEBUI_MODEL },
]);
}
export const configCommands = {
"config-reload": () => writeConfig("config-reload"),
"browser-cdp": () => writeConfig("browser-cdp"),
"openai-web-search-minimal-config": writeOpenAiWebSearchMinimalConfig,
"openwebui-config": writeOpenWebUiConfig,
};

View File

@@ -0,0 +1,82 @@
import path from "node:path";
import { requireArg, write, writeJson } from "./common.mjs";
function writePluginManifest(file, id) {
writeJson(file, { id, configSchema: { type: "object", properties: {} } });
}
function writePluginDemo([dir]) {
write(
path.join(requireArg(dir, "dir"), "index.js"),
'module.exports = { id: "demo-plugin", name: "Demo Plugin", description: "Docker E2E demo plugin", register(api) { api.registerTool(() => null, { name: "demo_tool" }); api.registerGatewayMethod("demo.ping", async () => ({ ok: true })); api.registerCli(() => {}, { commands: ["demo"] }); api.registerService({ id: "demo-service", start: () => {} }); }, };\n',
);
writePluginManifest(path.join(dir, "openclaw.plugin.json"), "demo-plugin");
}
function writePlugin([dir, id, version, method, name]) {
for (const [value, label] of [
[dir, "dir"],
[id, "id"],
[version, "version"],
[method, "method"],
[name, "name"],
]) {
requireArg(value, label);
}
writeJson(path.join(dir, "package.json"), {
name: `@openclaw/${id}`,
version,
openclaw: { extensions: ["./index.js"] },
});
write(
path.join(dir, "index.js"),
`module.exports = { id: ${JSON.stringify(id)}, name: ${JSON.stringify(name)}, register(api) { api.registerGatewayMethod(${JSON.stringify(method)}, async () => ({ ok: true })); }, };\n`,
);
writePluginManifest(path.join(dir, "openclaw.plugin.json"), id);
}
function writeClaudeBundle([root]) {
root = requireArg(root, "root");
writeJson(path.join(root, ".claude-plugin", "plugin.json"), { name: "claude-bundle-e2e" });
write(
path.join(root, "commands", "office-hours.md"),
"---\ndescription: Help with architecture and rollout planning\n---\nAct as an engineering advisor.\n\nFocus on:\n$ARGUMENTS\n",
);
}
function writePluginMarketplace([root]) {
root = requireArg(root, "root");
writeJson(path.join(root, ".claude-plugin", "marketplace.json"), {
name: "Fixture Marketplace",
version: "1.0.0",
plugins: [
{
name: "marketplace-shortcut",
version: "0.0.1",
description: "Shortcut install fixture",
source: "./plugins/marketplace-shortcut",
},
{
name: "marketplace-direct",
version: "0.0.1",
description: "Explicit marketplace fixture",
source: { type: "path", path: "./plugins/marketplace-direct" },
},
],
});
writeJson(path.join(process.env.HOME, ".claude", "plugins", "known_marketplaces.json"), {
"claude-fixtures": {
installLocation: root,
source: { type: "github", repo: "openclaw/fixture-marketplace" },
},
});
}
export const pluginCommands = {
"plugin-demo": writePluginDemo,
plugin: writePlugin,
"plugin-manifest": ([file, id]) =>
writePluginManifest(requireArg(file, "file"), requireArg(id, "id")),
"claude-bundle": writeClaudeBundle,
marketplace: writePluginMarketplace,
};

View File

@@ -0,0 +1,70 @@
import fs from "node:fs";
import path from "node:path";
import { assert, readJson, requireArg, write, writeJson } from "./common.mjs";
function writeOpenWebUiWorkspace() {
const workspace =
process.env.OPENCLAW_WORKSPACE_DIR || path.join(process.env.HOME, ".openclaw", "workspace");
write(
path.join(workspace, "IDENTITY.md"),
"# Identity\n\n- Name: OpenClaw\n- Purpose: Open WebUI Docker compatibility smoke test assistant.\n",
);
writeJson(path.join(workspace, ".openclaw", "workspace-state.json"), {
version: 1,
setupCompletedAt: "2026-01-01T00:00:00.000Z",
});
fs.rmSync(path.join(workspace, "BOOTSTRAP.md"), { force: true });
}
function writeAgentsDeleteConfig() {
const stateDir = requireArg(process.env.OPENCLAW_STATE_DIR, "OPENCLAW_STATE_DIR");
const sharedWorkspace = requireArg(process.env.SHARED_WORKSPACE, "SHARED_WORKSPACE");
fs.mkdirSync(sharedWorkspace, { recursive: true });
writeJson(path.join(stateDir, "openclaw.json"), {
agents: {
list: [
{ id: "main", workspace: sharedWorkspace },
{ id: "ops", workspace: sharedWorkspace },
],
},
});
}
function assertAgentsDeleteResult([outputPath]) {
let parsed;
try {
parsed = readJson(requireArg(outputPath, "outputPath"));
} catch (error) {
console.error("agents delete --json did not emit valid JSON:");
console.error(fs.readFileSync(outputPath, "utf8").trim());
throw error;
}
for (const [actual, expected, label] of [
[parsed.agentId, "ops", "agentId"],
[parsed.workspace, process.env.SHARED_WORKSPACE, "workspace"],
[parsed.workspaceRetained, true, "workspaceRetained"],
[parsed.workspaceRetainedReason, "shared", "workspaceRetainedReason"],
]) {
assert(actual === expected, `${label} mismatch: ${JSON.stringify(actual)}`);
}
assert(
Array.isArray(parsed.workspaceSharedWith) && parsed.workspaceSharedWith.includes("main"),
"missing shared-with main marker",
);
assert(fs.existsSync(process.env.SHARED_WORKSPACE), "shared workspace was removed");
const remaining =
readJson(path.join(process.env.OPENCLAW_STATE_DIR, "openclaw.json"))?.agents?.list ?? [];
assert(Array.isArray(remaining), "agents list missing after delete");
assert(!remaining.some((entry) => entry?.id === "ops"), "deleted agent remained in config");
assert(
remaining.some((entry) => entry?.id === "main"),
"main agent missing after delete",
);
console.log("agents delete shared workspace smoke ok");
}
export const workspaceCommands = {
"openwebui-workspace": writeOpenWebUiWorkspace,
"agents-delete-config": writeAgentsDeleteConfig,
"agents-delete-assert": assertAgentsDeleteResult,
};

View File

@@ -0,0 +1,13 @@
import fs from "node:fs";
for (const packageJsonPath of process.argv.slice(2)) {
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
pkg.exports = pkg.exports && typeof pkg.exports === "object" ? pkg.exports : {};
if (!pkg.exports["./plugin-sdk/gateway-runtime"]) {
pkg.exports["./plugin-sdk/gateway-runtime"] = {
types: "./dist/plugin-sdk/gateway-runtime.d.ts",
default: "./dist/plugin-sdk/gateway-runtime.js",
};
}
fs.writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
}

View File

@@ -0,0 +1,32 @@
import fs from "node:fs";
const [file, needle] = process.argv.slice(2);
if (!file || !needle) {
process.exit(1);
}
let text = "";
try {
text = fs.readFileSync(file, "utf8");
} catch {
process.exit(1);
}
if (text.length > 120000) {
text = text.slice(-120000);
}
const normalizeScriptOutput = (value) => value.replace(/\r?\n/g, "").replace(/\r/g, "");
const oscPattern = new RegExp(String.raw`\u001b\][^\u0007]*(?:\u0007|\u001b\\)`, "g");
const csiPattern = new RegExp(String.raw`\u001b\[[0-?]*[ -/]*[@-~]`, "g");
const stripAnsi = (value) =>
normalizeScriptOutput(value).replace(oscPattern, "").replace(csiPattern, "");
const compact = (value) =>
stripAnsi(value)
.toLowerCase()
.replace(/[^a-z]+/g, "");
const compactNeedle = compact(needle);
process.exit(compactNeedle && compact(text).includes(compactNeedle) ? 0 : 1);

View File

@@ -23,8 +23,6 @@ wait_for_log() {
local needle="$1"
local timeout_s="${2:-45}"
local quiet_on_timeout="${3:-false}"
local needle_compact
needle_compact="$(printf "%s" "$needle" | tr -cd "[:alpha:]")"
local start_s
start_s="$(date +%s)"
while true; do
@@ -32,34 +30,7 @@ wait_for_log() {
if grep -a -F -q "$needle" "$WIZARD_LOG_PATH"; then
return 0
fi
if NEEDLE=\"$needle_compact\" node --input-type=module -e "
import fs from \"node:fs\";
const file = process.env.WIZARD_LOG_PATH;
const needle = process.env.NEEDLE ?? \"\";
let text = \"\";
try { text = fs.readFileSync(file, \"utf8\"); } catch { process.exit(1); }
// Clack/script output can include lots of control sequences; keep a larger tail and strip ANSI more robustly.
if (text.length > 120000) text = text.slice(-120000);
const normalizeScriptOutput = (value) =>
value
// util-linux script can emit each byte on its own CRLF-delimited line.
// Collapse those first so ANSI/control stripping works on real sequences.
.replace(/\\r?\\n/g, \"\")
.replace(/\\r/g, \"\");
const stripAnsi = (value) =>
normalizeScriptOutput(value)
// OSC: ESC ] ... BEL or ESC \\
.replace(/\\x1b\\][^\\x07]*(?:\\x07|\\x1b\\\\)/g, \"\")
// CSI: ESC [ ... cmd
.replace(/\\x1b\\[[0-?]*[ -/]*[@-~]/g, \"\");
// Letters-only: script output sometimes fragments ANSI sequences into digits/letters that
// can otherwise break substring matching.
const compact = (value) => stripAnsi(value).toLowerCase().replace(/[^a-z]+/g, \"\");
const haystack = compact(text);
const compactNeedle = compact(needle);
if (!compactNeedle) process.exit(1);
process.exit(haystack.includes(compactNeedle) ? 0 : 1);
"; then
if node scripts/e2e/lib/onboard/log-contains.mjs "$WIZARD_LOG_PATH" "$needle"; then
return 0
fi
fi

View File

@@ -1,16 +1,7 @@
#!/usr/bin/env bash
parallels_package_current_build_commit() {
python3 - <<'PY'
import json
import pathlib
path = pathlib.Path("dist/build-info.json")
if not path.exists():
print("")
else:
print(json.loads(path.read_text()).get("commit", ""))
PY
node scripts/e2e/lib/parallels-package/build-info-commit.mjs
}
parallels_package_acquire_build_lock() {
@@ -66,35 +57,9 @@ parallels_package_assert_no_generated_drift() {
}
parallels_log_progress_extract() {
local python_bin="$1"
local _python_bin="$1"
local log_path="$2"
"$python_bin" - "$log_path" <<'PY'
import pathlib
import sys
path = pathlib.Path(sys.argv[1])
if not path.exists():
print("")
raise SystemExit(0)
text = path.read_text(encoding="utf-8", errors="replace")
lines = [line.strip() for line in text.splitlines() if line.strip()]
for line in reversed(lines):
if line.startswith("==> "):
print(line[4:].strip())
raise SystemExit(0)
for line in reversed(lines):
if line.startswith("warn:") or line.startswith("error:"):
print(line)
raise SystemExit(0)
if lines:
print(lines[-1][:240])
else:
print("")
PY
node scripts/e2e/lib/parallels-package/log-progress-extract.mjs "$log_path"
}
parallels_child_job_running() {

View File

@@ -0,0 +1,9 @@
import fs from "node:fs";
const path = "dist/build-info.json";
if (!fs.existsSync(path)) {
console.log("");
} else {
const buildInfo = JSON.parse(fs.readFileSync(path, "utf8"));
console.log(buildInfo.commit ?? "");
}

View File

@@ -0,0 +1,18 @@
import fs from "node:fs";
const [logPath] = process.argv.slice(2);
if (!logPath || !fs.existsSync(logPath)) {
console.log("");
process.exit(0);
}
const text = fs.readFileSync(logPath, "utf8");
const lines = text
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean);
const reversed = lines.toReversed();
const progress = reversed.find((line) => line.startsWith("==> "));
const warning = reversed.find((line) => line.startsWith("warn:") || line.startsWith("error:"));
console.log(progress?.slice(4).trim() ?? warning ?? lines.at(-1)?.slice(0, 240) ?? "");

View File

@@ -233,24 +233,9 @@ ln -sfnT /app/extensions "$openclaw_package_dir/extensions"
mkdir -p /app/node_modules/@openclaw
rm -rf /app/node_modules/@openclaw/qa-channel
ln -sfnT /app/extensions/qa-channel /app/node_modules/@openclaw/qa-channel
node --input-type=module <<'NODE'
import fs from "node:fs";
for (const packageJsonPath of [
"/app/package.json",
"/app/node_modules/openclaw/package.json",
]) {
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
pkg.exports = pkg.exports && typeof pkg.exports === "object" ? pkg.exports : {};
if (!pkg.exports["./plugin-sdk/gateway-runtime"]) {
pkg.exports["./plugin-sdk/gateway-runtime"] = {
types: "./dist/plugin-sdk/gateway-runtime.d.ts",
default: "./dist/plugin-sdk/gateway-runtime.js",
};
}
fs.writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
}
NODE
node scripts/e2e/lib/npm-telegram-live/prepare-package.mjs \
/app/package.json \
/app/node_modules/openclaw/package.json
for deps_dir in "$openclaw_package_dir/node_modules" /npm-global/lib/node_modules; do
[ -d "$deps_dir" ] || continue
for dependency_dir in "$deps_dir"/*; do