diff --git a/scripts/e2e/bun-global-install-smoke.sh b/scripts/e2e/bun-global-install-smoke.sh index 8638ecc6f2a..9bc3e2910e6 100755 --- a/scripts/e2e/bun-global-install-smoke.sh +++ b/scripts/e2e/bun-global-install-smoke.sh @@ -24,34 +24,7 @@ trap cleanup EXIT run_with_timeout() { local timeout_ms="$1" shift - node - "$timeout_ms" "$@" <<'NODE' -const { spawnSync } = require("node:child_process"); - -const timeout = Number(process.argv[2]); -const command = process.argv[3]; -const args = process.argv.slice(4); -const result = spawnSync(command, args, { - encoding: "utf8", - env: process.env, - timeout, -}); - -if (result.stdout) { - process.stdout.write(result.stdout); -} -if (result.stderr) { - process.stderr.write(result.stderr); -} -if (result.error) { - console.error(`command failed: ${command}: ${result.error.message}`); - process.exit(1); -} -if (result.signal) { - console.error(`command terminated: ${command}: ${result.signal}`); - process.exit(1); -} -process.exit(result.status ?? 0); -NODE + node scripts/e2e/lib/bun-global-install/assertions.mjs run-with-timeout "$timeout_ms" "$@" } restore_dist_from_image() { @@ -163,29 +136,7 @@ main() { echo "==> OpenClaw image providers through Bun global install" local providers_json providers_json="$(run_with_timeout "$COMMAND_TIMEOUT_MS" "$openclaw_bin" infer image providers --json)" - OPENCLAW_IMAGE_PROVIDERS_JSON="$providers_json" node - <<'NODE' -const raw = process.env.OPENCLAW_IMAGE_PROVIDERS_JSON ?? ""; -let parsed; -try { - parsed = JSON.parse(raw); -} catch (error) { - console.error(raw); - throw new Error(`image providers output is not JSON: ${error.message}`); -} -if (!Array.isArray(parsed)) { - throw new Error("image providers output must be a JSON array"); -} -if (parsed.length === 0) { - throw new Error("image providers output is empty"); -} -const ids = new Set(parsed.map((entry) => entry && typeof entry.id === "string" ? entry.id : "")); -for (const expected of ["google", "openai", "xai"]) { - if (!ids.has(expected)) { - throw new Error(`image providers output is missing bundled provider '${expected}'`); - } -} -console.log(`bun-global-install-smoke: image providers OK (${parsed.length} providers)`); -NODE + OPENCLAW_IMAGE_PROVIDERS_JSON="$providers_json" node scripts/e2e/lib/bun-global-install/assertions.mjs assert-image-providers } main "$@" diff --git a/scripts/e2e/lib/bun-global-install/assertions.mjs b/scripts/e2e/lib/bun-global-install/assertions.mjs new file mode 100644 index 00000000000..604969dd6ba --- /dev/null +++ b/scripts/e2e/lib/bun-global-install/assertions.mjs @@ -0,0 +1,57 @@ +import { spawnSync } from "node:child_process"; + +const usage = () => { + console.error("Usage: assertions.mjs [...]"); + process.exit(2); +}; + +const [mode, ...args] = process.argv.slice(2); + +if (mode === "run-with-timeout") { + const [timeoutMs, command, ...commandArgs] = args; + const timeout = Number(timeoutMs); + if (!Number.isFinite(timeout) || timeout <= 0 || !command) { + usage(); + } + + const result = spawnSync(command, commandArgs, { encoding: "utf8", env: process.env, timeout }); + process.stdout.write(result.stdout ?? ""); + process.stderr.write(result.stderr ?? ""); + if (result.error) { + console.error(`command failed: ${command}: ${result.error.message}`); + process.exit(1); + } + if (result.signal) { + console.error(`command terminated: ${command}: ${result.signal}`); + process.exit(1); + } + process.exit(result.status ?? 0); +} + +if (mode === "assert-image-providers") { + const raw = process.env.OPENCLAW_IMAGE_PROVIDERS_JSON ?? ""; + let parsed; + try { + parsed = JSON.parse(raw); + } catch (error) { + console.error(raw); + const message = error instanceof Error ? error.message : String(error); + throw new Error(`image providers output is not JSON: ${message}`, { cause: error }); + } + if (!Array.isArray(parsed)) { + throw new Error("image providers output must be a JSON array"); + } + if (parsed.length === 0) { + throw new Error("image providers output is empty"); + } + const ids = new Set(parsed.map((entry) => (typeof entry?.id === "string" ? entry.id : ""))); + for (const expected of ["google", "openai", "xai"]) { + if (!ids.has(expected)) { + throw new Error(`image providers output is missing bundled provider '${expected}'`); + } + } + console.log(`bun-global-install-smoke: image providers OK (${parsed.length} providers)`); + process.exit(0); +} + +usage(); diff --git a/scripts/e2e/lib/bundled-channel/load-failure.sh b/scripts/e2e/lib/bundled-channel/load-failure.sh index 0babce01617..72438052ab4 100644 --- a/scripts/e2e/lib/bundled-channel/load-failure.sh +++ b/scripts/e2e/lib/bundled-channel/load-failure.sh @@ -24,60 +24,7 @@ bundled_channel_install_package /tmp/openclaw-load-failure-install.log root="$(bundled_channel_package_root)" plugin_dir="$root/dist/extensions/load-failure-alpha" -mkdir -p "$plugin_dir" -cat >"$plugin_dir/package.json" <<'JSON' -{ - "name": "@openclaw/load-failure-alpha", - "version": "2026.4.21", - "private": true, - "type": "module", - "openclaw": { - "extensions": ["./index.js"], - "setupEntry": "./setup-entry.js" - } -} -JSON -cat >"$plugin_dir/openclaw.plugin.json" <<'JSON' -{ - "id": "load-failure-alpha", - "channels": ["load-failure-alpha"], - "configSchema": { - "type": "object", - "additionalProperties": false, - "properties": {} - } -} -JSON -cat >"$plugin_dir/index.js" <<'JS' -export default { - kind: "bundled-channel-entry", - id: "load-failure-alpha", - name: "Load Failure Alpha", - description: "Load Failure Alpha", - register() {}, - loadChannelSecrets() { - globalThis.__loadFailureSecrets = (globalThis.__loadFailureSecrets ?? 0) + 1; - throw new Error("synthetic channel secrets failure"); - }, - loadChannelPlugin() { - globalThis.__loadFailurePlugin = (globalThis.__loadFailurePlugin ?? 0) + 1; - throw new Error("synthetic channel plugin failure"); - } -}; -JS -cat >"$plugin_dir/setup-entry.js" <<'JS' -export default { - kind: "bundled-channel-setup-entry", - loadSetupSecrets() { - globalThis.__loadFailureSetupSecrets = (globalThis.__loadFailureSetupSecrets ?? 0) + 1; - throw new Error("synthetic setup secrets failure"); - }, - loadSetupPlugin() { - globalThis.__loadFailureSetup = (globalThis.__loadFailureSetup ?? 0) + 1; - throw new Error("synthetic setup plugin failure"); - } -}; -JS +node scripts/e2e/lib/bundled-channel/write-load-failure-fixture.mjs "$plugin_dir" echo "Loading synthetic failing bundled channel through packaged loader..." node scripts/e2e/lib/bundled-channel/loader-probe.mjs load-failure "$root" load-failure-alpha diff --git a/scripts/e2e/lib/bundled-channel/write-load-failure-fixture.mjs b/scripts/e2e/lib/bundled-channel/write-load-failure-fixture.mjs new file mode 100644 index 00000000000..9f022ec38c7 --- /dev/null +++ b/scripts/e2e/lib/bundled-channel/write-load-failure-fixture.mjs @@ -0,0 +1,42 @@ +import fs from "node:fs"; +import path from "node:path"; + +const [pluginDir] = process.argv.slice(2); +if (!pluginDir) { + throw new Error("usage: write-load-failure-fixture.mjs "); +} + +const writeJson = (filename, contents) => + fs.writeFileSync(path.join(pluginDir, filename), `${JSON.stringify(contents, null, 2)}\n`); + +fs.mkdirSync(pluginDir, { recursive: true }); +writeJson("package.json", { + name: "@openclaw/load-failure-alpha", + version: "2026.4.21", + private: true, + type: "module", + openclaw: { extensions: ["./index.js"], setupEntry: "./setup-entry.js" }, +}); +writeJson("openclaw.plugin.json", { + id: "load-failure-alpha", + channels: ["load-failure-alpha"], + configSchema: { type: "object", additionalProperties: false, properties: {} }, +}); +fs.writeFileSync( + path.join(pluginDir, "index.js"), + `export default { + kind: "bundled-channel-entry", id: "load-failure-alpha", name: "Load Failure Alpha", description: "Load Failure Alpha", register() {}, + loadChannelSecrets() { globalThis.__loadFailureSecrets = (globalThis.__loadFailureSecrets ?? 0) + 1; throw new Error("synthetic channel secrets failure"); }, + loadChannelPlugin() { globalThis.__loadFailurePlugin = (globalThis.__loadFailurePlugin ?? 0) + 1; throw new Error("synthetic channel plugin failure"); } +}; +`, +); +fs.writeFileSync( + path.join(pluginDir, "setup-entry.js"), + `export default { + kind: "bundled-channel-setup-entry", + loadSetupSecrets() { globalThis.__loadFailureSetupSecrets = (globalThis.__loadFailureSetupSecrets ?? 0) + 1; throw new Error("synthetic setup secrets failure"); }, + loadSetupPlugin() { globalThis.__loadFailureSetup = (globalThis.__loadFailureSetup ?? 0) + 1; throw new Error("synthetic setup plugin failure"); } +}; +`, +); diff --git a/scripts/e2e/lib/onboard/scenario.sh b/scripts/e2e/lib/onboard/scenario.sh index 09e8a193ad1..05af48765aa 100644 --- a/scripts/e2e/lib/onboard/scenario.sh +++ b/scripts/e2e/lib/onboard/scenario.sh @@ -161,6 +161,13 @@ run_wizard() { run_wizard_cmd "$case_name" "$state_ref" "node \"$OPENCLAW_ENTRY\" onboard $ONBOARD_FLAGS" "$send_fn" true "$validate_fn" } +assert_onboard_config() { + local scenario="$1" + shift + openclaw_e2e_assert_file "$OPENCLAW_CONFIG_PATH" + node scripts/e2e/lib/onboard/assert-config.mjs "$scenario" "$OPENCLAW_CONFIG_PATH" "$@" +} + set_isolated_openclaw_env() { local state_ref="$1" openclaw_test_state_create "$state_ref" empty @@ -230,16 +237,14 @@ run_case_local_basic() { # Assert config + workspace scaffolding. workspace_dir="$OPENCLAW_STATE_DIR/workspace" - config_path="$OPENCLAW_CONFIG_PATH" sessions_dir="$OPENCLAW_STATE_DIR/agents/main/sessions" - openclaw_e2e_assert_file "$config_path" openclaw_e2e_assert_dir "$sessions_dir" for file in AGENTS.md BOOTSTRAP.md IDENTITY.md SOUL.md TOOLS.md USER.md; do openclaw_e2e_assert_file "$workspace_dir/$file" done - node scripts/e2e/lib/onboard/assert-config.mjs local-basic "$config_path" "$workspace_dir" + assert_onboard_config local-basic "$workspace_dir" } @@ -253,25 +258,12 @@ run_case_remote_non_interactive() { --skip-skills \ --skip-health - config_path="$OPENCLAW_CONFIG_PATH" - openclaw_e2e_assert_file "$config_path" - - node scripts/e2e/lib/onboard/assert-config.mjs remote-non-interactive "$config_path" + assert_onboard_config remote-non-interactive } run_case_reset() { set_isolated_openclaw_env reset-config - # Seed a remote config to exercise reset path. - cat >"$OPENCLAW_CONFIG_PATH" <<'JSON' -{ -"meta": {}, -"agents": { "defaults": { "workspace": "/root/old" } }, -"gateway": { - "mode": "remote", - "remote": { "url": "ws://old.example:18789", "token": "old-token" } -} -} -JSON + node scripts/e2e/lib/onboard/write-config.mjs reset "$OPENCLAW_CONFIG_PATH" openclaw_e2e_run_logged reset-config node "$OPENCLAW_ENTRY" onboard \ --non-interactive \ @@ -285,43 +277,25 @@ JSON --skip-ui \ --skip-health - config_path="$OPENCLAW_CONFIG_PATH" - openclaw_e2e_assert_file "$config_path" - - node scripts/e2e/lib/onboard/assert-config.mjs reset "$config_path" + assert_onboard_config reset } run_case_channels() { # Channels-only configure flow. run_wizard_cmd channels channels "node \"$OPENCLAW_ENTRY\" configure --section channels" send_channels_flow - config_path="$OPENCLAW_CONFIG_PATH" - openclaw_e2e_assert_file "$config_path" - - node scripts/e2e/lib/onboard/assert-config.mjs channels "$config_path" + assert_onboard_config channels } run_case_skills() { local home_dir set_isolated_openclaw_env skills home_dir="$HOME" - # Seed skills config to ensure it survives the wizard. - cat >"$OPENCLAW_CONFIG_PATH" <<'JSON' -{ -"meta": {}, -"skills": { - "allowBundled": ["__none__"], - "install": { "nodeManager": "bun" } -} -} -JSON + node scripts/e2e/lib/onboard/write-config.mjs skills "$OPENCLAW_CONFIG_PATH" run_wizard_cmd skills "$home_dir" "node \"$OPENCLAW_ENTRY\" configure --section skills" send_skills_flow - config_path="$OPENCLAW_CONFIG_PATH" - openclaw_e2e_assert_file "$config_path" - - node scripts/e2e/lib/onboard/assert-config.mjs skills "$config_path" + assert_onboard_config skills } validate_local_basic_log() { diff --git a/scripts/e2e/lib/onboard/write-config.mjs b/scripts/e2e/lib/onboard/write-config.mjs new file mode 100644 index 00000000000..f4c8cea29d1 --- /dev/null +++ b/scripts/e2e/lib/onboard/write-config.mjs @@ -0,0 +1,20 @@ +import fs from "node:fs"; + +const [scenario, configPath] = process.argv.slice(2); +if (!scenario || !configPath) { + throw new Error("usage: write-config.mjs "); +} + +const config = { + reset: { + meta: {}, + agents: { defaults: { workspace: "/root/old" } }, + gateway: { mode: "remote", remote: { url: "ws://old.example:18789", token: "old-token" } }, + }, + skills: { meta: {}, skills: { allowBundled: ["__none__"], install: { nodeManager: "bun" } } }, +}[scenario]; +if (!config) { + throw new Error(`unknown config scenario: ${scenario}`); +} + +fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);