























@@ -0,0 +1,228 @@
1+#!/usr/bin/env bash
2+set -euo pipefail
3+4+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
5+source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
6+IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-bundled-plugin-install-uninstall-e2e" OPENCLAW_BUNDLED_PLUGIN_INSTALL_UNINSTALL_E2E_IMAGE)"
7+8+docker_e2e_build_or_reuse "$IMAGE_NAME" bundled-plugin-install-uninstall
9+10+DOCKER_ENV_ARGS=(-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0)
11+for env_name in \
12+ OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL \
13+ OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX \
14+ OPENCLAW_BUNDLED_PLUGIN_SWEEP_IDS; do
15+ env_value="${!env_name:-}"
16+if [[ -n "$env_value" && "$env_value" != "undefined" && "$env_value" != "null" ]]; then
17+ DOCKER_ENV_ARGS+=(-e "$env_name")
18+fi
19+done
20+21+echo "Running bundled plugin install/uninstall Docker E2E..."
22+RUN_LOG="$(mktemp "${TMPDIR:-/tmp}/openclaw-bundled-plugin-install-uninstall.XXXXXX")"
23+if ! docker run --rm "${DOCKER_ENV_ARGS[@]}" -i "$IMAGE_NAME" bash -s >"$RUN_LOG" 2>&1 <<'EOF'
24+set -euo pipefail
25+26+if [ -f dist/index.mjs ]; then
27+ OPENCLAW_ENTRY="dist/index.mjs"
28+elif [ -f dist/index.js ]; then
29+ OPENCLAW_ENTRY="dist/index.js"
30+else
31+ echo "Missing dist/index.(m)js (build output):"
32+ ls -la dist || true
33+ exit 1
34+fi
35+export OPENCLAW_ENTRY
36+37+home_dir=$(mktemp -d "/tmp/openclaw-bundled-plugin-sweep.XXXXXX")
38+export HOME="$home_dir"
39+40+node - <<'NODE' > /tmp/bundled-plugin-sweep-ids
41+const fs = require("node:fs");
42+const path = require("node:path");
43+44+const explicit = (process.env.OPENCLAW_BUNDLED_PLUGIN_SWEEP_IDS || "")
45+ .split(/[,\s]+/u)
46+ .map((entry) => entry.trim())
47+ .filter(Boolean);
48+const extensionRoot = path.join(process.cwd(), "dist", "extensions");
49+const manifestEntries = fs
50+ .readdirSync(extensionRoot, { withFileTypes: true })
51+ .filter((entry) => entry.isDirectory())
52+ .map((entry) => {
53+ const manifestPath = path.join(extensionRoot, entry.name, "openclaw.plugin.json");
54+ if (!fs.existsSync(manifestPath)) {
55+ return null;
56+ }
57+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
58+ const id = typeof manifest.id === "string" ? manifest.id.trim() : "";
59+ if (!id) {
60+ throw new Error(`Bundled plugin manifest is missing id: ${manifestPath}`);
61+ }
62+ const required = manifest.configSchema?.required;
63+ return {
64+ id,
65+ dir: entry.name,
66+ requiresConfig:
67+ Array.isArray(required) && required.some((value) => typeof value === "string"),
68+ };
69+ })
70+ .filter(Boolean)
71+ .sort((a, b) => a.id.localeCompare(b.id));
72+const allEntries =
73+ explicit.length > 0
74+ ? explicit.map(
75+ (lookup) =>
76+ manifestEntries.find((entry) => entry.id === lookup || entry.dir === lookup) || {
77+ id: lookup,
78+ dir: lookup,
79+ requiresConfig: false,
80+ },
81+ )
82+ : manifestEntries;
83+84+const total = Number.parseInt(process.env.OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL || "1", 10);
85+const index = Number.parseInt(process.env.OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX || "0", 10);
86+if (!Number.isInteger(total) || total < 1) {
87+ throw new Error(`OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL must be >= 1, got ${process.env.OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL}`);
88+}
89+if (!Number.isInteger(index) || index < 0 || index >= total) {
90+ throw new Error(`OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX must be in [0, ${total - 1}], got ${process.env.OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX}`);
91+}
92+93+const selected = allEntries.filter((_, candidateIndex) => candidateIndex % total === index);
94+if (selected.length === 0) {
95+ throw new Error(`No bundled plugin ids selected for shard ${index}/${total}`);
96+}
97+98+for (const entry of selected) {
99+ console.log(`${entry.id}\t${entry.dir}\t${entry.requiresConfig ? "1" : "0"}`);
100+}
101+NODE
102+103+mapfile -t plugin_entries < /tmp/bundled-plugin-sweep-ids
104+selected_labels=()
105+for plugin_entry in "${plugin_entries[@]}"; do
106+ IFS=$'\t' read -r plugin_id plugin_dir _requires_config <<<"$plugin_entry"
107+ selected_labels+=("${plugin_id}@${plugin_dir}")
108+done
109+echo "Selected ${#plugin_entries[@]} bundled plugins for shard ${OPENCLAW_BUNDLED_PLUGIN_SWEEP_INDEX:-0}/${OPENCLAW_BUNDLED_PLUGIN_SWEEP_TOTAL:-1}: ${selected_labels[*]}"
110+111+assert_installed() {
112+ local plugin_id="$1"
113+ local plugin_dir="$2"
114+ local requires_config="$3"
115+ node - <<'NODE' "$plugin_id" "$plugin_dir" "$requires_config"
116+const fs = require("node:fs");
117+const path = require("node:path");
118+119+const pluginId = process.argv[2];
120+const pluginDir = process.argv[3];
121+const requiresConfig = process.argv[4] === "1";
122+const configPath = path.join(process.env.HOME, ".openclaw", "openclaw.json");
123+const indexPath = path.join(process.env.HOME, ".openclaw", "plugins", "installs.json");
124+const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
125+const index = JSON.parse(fs.readFileSync(indexPath, "utf8"));
126+const records = index.installRecords ?? index.records ?? {};
127+const record = records[pluginId];
128+if (!record) {
129+ throw new Error(`missing install record for ${pluginId}`);
130+}
131+if (record.source !== "path") {
132+ throw new Error(`expected bundled install record source=path for ${pluginId}, got ${record.source}`);
133+}
134+if (typeof record.sourcePath !== "string" || !record.sourcePath.includes(`/dist/extensions/${pluginDir}`)) {
135+ throw new Error(`unexpected bundled source path for ${pluginId}: ${record.sourcePath}`);
136+}
137+if (record.installPath !== record.sourcePath) {
138+ throw new Error(`bundled install path should equal source path for ${pluginId}`);
139+}
140+const paths = config.plugins?.load?.paths || [];
141+if (!paths.includes(record.sourcePath)) {
142+ throw new Error(`config load paths do not include bundled install path for ${pluginId}`);
143+}
144+if (requiresConfig && config.plugins?.entries?.[pluginId]?.enabled === true) {
145+ throw new Error(`plugin requiring config should not be enabled immediately after install for ${pluginId}`);
146+}
147+if (!requiresConfig && config.plugins?.entries?.[pluginId]?.enabled !== true) {
148+ throw new Error(`config entry is not enabled after install for ${pluginId}`);
149+}
150+const allow = config.plugins?.allow || [];
151+if (Array.isArray(allow) && allow.length > 0 && !allow.includes(pluginId)) {
152+ throw new Error(`existing allowlist does not include ${pluginId} after install`);
153+}
154+if ((config.plugins?.deny || []).includes(pluginId)) {
155+ throw new Error(`denylist contains ${pluginId} after install`);
156+}
157+NODE
158+}
159+160+assert_uninstalled() {
161+ local plugin_id="$1"
162+ local plugin_dir="$2"
163+ node - <<'NODE' "$plugin_id" "$plugin_dir"
164+const fs = require("node:fs");
165+const path = require("node:path");
166+167+const pluginId = process.argv[2];
168+const pluginDir = process.argv[3];
169+const configPath = path.join(process.env.HOME, ".openclaw", "openclaw.json");
170+const indexPath = path.join(process.env.HOME, ".openclaw", "plugins", "installs.json");
171+const config = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, "utf8")) : {};
172+const index = fs.existsSync(indexPath) ? JSON.parse(fs.readFileSync(indexPath, "utf8")) : {};
173+const records = index.installRecords ?? index.records ?? {};
174+if (records[pluginId]) {
175+ throw new Error(`install record still present after uninstall for ${pluginId}`);
176+}
177+const paths = config.plugins?.load?.paths || [];
178+if (paths.some((entry) => String(entry).includes(`/dist/extensions/${pluginDir}`))) {
179+ throw new Error(`load path still present after uninstall for ${pluginId}`);
180+}
181+if (config.plugins?.entries?.[pluginId]) {
182+ throw new Error(`config entry still present after uninstall for ${pluginId}`);
183+}
184+if ((config.plugins?.allow || []).includes(pluginId)) {
185+ throw new Error(`allowlist still contains ${pluginId} after uninstall`);
186+}
187+if ((config.plugins?.deny || []).includes(pluginId)) {
188+ throw new Error(`denylist still contains ${pluginId} after uninstall`);
189+}
190+const managedPath = path.join(process.env.HOME, ".openclaw", "extensions", pluginId);
191+if (fs.existsSync(managedPath)) {
192+ throw new Error(`managed install directory unexpectedly exists for bundled plugin ${pluginId}: ${managedPath}`);
193+}
194+NODE
195+}
196+197+plugin_index=0
198+for plugin_entry in "${plugin_entries[@]}"; do
199+ IFS=$'\t' read -r plugin_id plugin_dir requires_config <<<"$plugin_entry"
200+ install_log="/tmp/openclaw-install-${plugin_index}.log"
201+ uninstall_log="/tmp/openclaw-uninstall-${plugin_index}.log"
202+ echo "Installing bundled plugin: $plugin_id ($plugin_dir)"
203+ node "$OPENCLAW_ENTRY" plugins install "$plugin_id" >"$install_log" 2>&1 || {
204+ cat "$install_log"
205+ exit 1
206+ }
207+ assert_installed "$plugin_id" "$plugin_dir" "$requires_config"
208+209+ echo "Uninstalling bundled plugin: $plugin_id ($plugin_dir)"
210+ node "$OPENCLAW_ENTRY" plugins uninstall "$plugin_id" --force >"$uninstall_log" 2>&1 || {
211+ cat "$uninstall_log"
212+ exit 1
213+ }
214+ assert_uninstalled "$plugin_id" "$plugin_dir"
215+ plugin_index=$((plugin_index + 1))
216+done
217+218+echo "bundled plugin install/uninstall sweep passed (${#plugin_entries[@]} plugin(s))"
219+EOF
220+then
221+ cat "$RUN_LOG"
222+ rm -f "$RUN_LOG"
223+exit 1
224+fi
225+cat "$RUN_LOG"
226+rm -f "$RUN_LOG"
227+228+echo "OK"
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。