mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-01 06:05:05 +02:00
245 lines
9.0 KiB
Bash
Executable File
245 lines
9.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Verifies `openclaw plugins update` is a no-op for an already-current plugin.
|
|
# The CLI under test is installed from the prepared npm tarball in a bare runner.
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
|
|
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
|
|
|
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-plugin-update-e2e" OPENCLAW_PLUGIN_UPDATE_E2E_IMAGE)"
|
|
SKIP_BUILD="${OPENCLAW_PLUGIN_UPDATE_E2E_SKIP_BUILD:-0}"
|
|
PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz plugin-update "${OPENCLAW_CURRENT_PACKAGE_TGZ:-}")"
|
|
# Bare lanes mount the package artifact instead of baking app sources into the image.
|
|
docker_e2e_package_mount_args "$PACKAGE_TGZ"
|
|
|
|
docker_e2e_build_or_reuse "$IMAGE_NAME" plugin-update "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "bare" "$SKIP_BUILD"
|
|
OPENCLAW_TEST_STATE_SCRIPT_B64="$(docker_e2e_test_state_shell_b64 plugin-update empty)"
|
|
|
|
echo "Running unchanged plugin update smoke..."
|
|
docker run --rm \
|
|
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
|
-e OPENCLAW_SKIP_CHANNELS=1 \
|
|
-e OPENCLAW_SKIP_PROVIDERS=1 \
|
|
-e "OPENCLAW_TEST_STATE_SCRIPT_B64=$OPENCLAW_TEST_STATE_SCRIPT_B64" \
|
|
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
|
|
"$IMAGE_NAME" \
|
|
bash -lc "set -euo pipefail
|
|
eval \"\$(printf '%s' \"\${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}\" | base64 -d)\"
|
|
package_tgz=\"\${OPENCLAW_CURRENT_PACKAGE_TGZ:?missing OPENCLAW_CURRENT_PACKAGE_TGZ}\"
|
|
npm install -g --prefix /tmp/npm-prefix \"\$package_tgz\" --no-fund --no-audit >/tmp/openclaw-install.log 2>&1
|
|
entry=\"/tmp/npm-prefix/lib/node_modules/openclaw/dist/index.mjs\"
|
|
[ -f \"\$entry\" ] || entry=/tmp/npm-prefix/lib/node_modules/openclaw/dist/index.js
|
|
package_version=\$(node -p \"require('/tmp/npm-prefix/lib/node_modules/openclaw/package.json').version\")
|
|
OPENCLAW_PACKAGE_ACCEPTANCE_LEGACY_COMPAT=\$(PACKAGE_VERSION=\"\$package_version\" node -e 'const version = process.env.PACKAGE_VERSION || \"\"; const match = new RegExp(\"^(\\\\d{4})\\\\.(\\\\d{1,2})\\\\.(\\\\d{1,2})(?:[-+].*)?\").exec(version); if (!match) { console.log(\"0\"); process.exit(0); } const value = [Number(match[1]), Number(match[2]), Number(match[3])]; const max = [2026, 4, 25]; for (let i = 0; i < value.length; i += 1) { if (value[i] < max[i]) { console.log(\"1\"); process.exit(0); } if (value[i] > max[i]) { console.log(\"0\"); process.exit(0); } } console.log(\"1\");')
|
|
export OPENCLAW_PACKAGE_ACCEPTANCE_LEGACY_COMPAT
|
|
export NPM_CONFIG_REGISTRY=http://127.0.0.1:4873
|
|
export PATH=\"/tmp/npm-prefix/bin:\$PATH\"
|
|
|
|
mkdir -p \"\$HOME/.openclaw/extensions/lossless-claw\"
|
|
cat > \"\$HOME/.openclaw/extensions/lossless-claw/package.json\" <<'JSON'
|
|
{
|
|
\"name\": \"@example/lossless-claw\",
|
|
\"version\": \"0.9.0\"
|
|
}
|
|
JSON
|
|
cat > \"\$OPENCLAW_CONFIG_PATH\" <<'JSON'
|
|
{
|
|
\"plugins\": {}
|
|
}
|
|
JSON
|
|
mkdir -p \"\$HOME/.openclaw/plugins\"
|
|
cat > \"\$HOME/.openclaw/plugins/installs.json\" <<'JSON'
|
|
{
|
|
\"version\": 1,
|
|
\"warning\": \"DO NOT EDIT. This file is generated by OpenClaw plugin registry commands.\",
|
|
\"hostContractVersion\": \"docker-e2e\",
|
|
\"compatRegistryVersion\": \"docker-e2e\",
|
|
\"migrationVersion\": 1,
|
|
\"policyHash\": \"docker-e2e\",
|
|
\"generatedAtMs\": 1777118400000,
|
|
\"installRecords\": {
|
|
\"lossless-claw\": {
|
|
\"source\": \"npm\",
|
|
\"spec\": \"@example/lossless-claw@0.9.0\",
|
|
\"installPath\": \"~/.openclaw/extensions/lossless-claw\",
|
|
\"resolvedName\": \"@example/lossless-claw\",
|
|
\"resolvedVersion\": \"0.9.0\",
|
|
\"resolvedSpec\": \"@example/lossless-claw@0.9.0\",
|
|
\"integrity\": \"sha512-same\",
|
|
\"shasum\": \"same\"
|
|
}
|
|
},
|
|
\"plugins\": [],
|
|
\"diagnostics\": []
|
|
}
|
|
JSON
|
|
|
|
cat > /tmp/openclaw-e2e-registry.mjs <<'NODE'
|
|
import http from 'node:http';
|
|
|
|
const metadata = {
|
|
name: '@example/lossless-claw',
|
|
'dist-tags': { latest: '0.9.0' },
|
|
versions: {
|
|
'0.9.0': {
|
|
name: '@example/lossless-claw',
|
|
version: '0.9.0',
|
|
dist: {
|
|
integrity: 'sha512-same',
|
|
shasum: 'same',
|
|
tarball: 'http://127.0.0.1:4873/@example/lossless-claw/-/lossless-claw-0.9.0.tgz'
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const server = http.createServer((req, res) => {
|
|
if (req.url === '/@example%2flossless-claw' || req.url === '/@example%2Flossless-claw') {
|
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
res.end(JSON.stringify(metadata));
|
|
return;
|
|
}
|
|
res.writeHead(404, { 'content-type': 'text/plain' });
|
|
res.end('not found: ' + req.url);
|
|
});
|
|
|
|
server.listen(4873, '127.0.0.1');
|
|
NODE
|
|
node /tmp/openclaw-e2e-registry.mjs >/tmp/openclaw-e2e-registry.log 2>&1 &
|
|
registry_pid=\$!
|
|
trap 'kill \"\$registry_pid\" >/dev/null 2>&1 || true' EXIT
|
|
|
|
registry_ready=0
|
|
for _ in \$(seq 1 50); do
|
|
if node --input-type=module -e '
|
|
import http from \"node:http\";
|
|
const req = http.get(\"http://127.0.0.1:4873/@example%2flossless-claw\", (res) => {
|
|
process.exit(res.statusCode === 200 ? 0 : 1);
|
|
});
|
|
req.on(\"error\", () => process.exit(1));
|
|
req.setTimeout(200, () => {
|
|
req.destroy();
|
|
process.exit(1);
|
|
});
|
|
'; then
|
|
registry_ready=1
|
|
break
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
if [ \"\$registry_ready\" -ne 1 ]; then
|
|
echo \"Local npm metadata registry failed to start\"
|
|
cat /tmp/openclaw-e2e-registry.log || true
|
|
exit 1
|
|
fi
|
|
|
|
before_config_hash=\"\"
|
|
if [ \"\$OPENCLAW_PACKAGE_ACCEPTANCE_LEGACY_COMPAT\" != \"1\" ]; then
|
|
before_config_hash=\$(sha256sum \"\$OPENCLAW_CONFIG_PATH\" | awk '{print \$1}')
|
|
fi
|
|
plugin_update_timeout_seconds=\"\${OPENCLAW_PLUGIN_UPDATE_TIMEOUT_SECONDS:-180}\"
|
|
|
|
node --input-type=module > /tmp/plugin-update-before.json <<'NODE'
|
|
import fs from \"node:fs\";
|
|
import os from \"node:os\";
|
|
import path from \"node:path\";
|
|
|
|
const readJson = (file) => {
|
|
try {
|
|
return JSON.parse(fs.readFileSync(file, \"utf8\"));
|
|
} catch {
|
|
return {};
|
|
}
|
|
};
|
|
const home = os.homedir();
|
|
const config = readJson(path.join(home, \".openclaw\", \"openclaw.json\"));
|
|
const index = readJson(path.join(home, \".openclaw\", \"plugins\", \"installs.json\"));
|
|
const records = index.installRecords ?? index.records ?? config.plugins?.installs ?? {};
|
|
const record = records[\"lossless-claw\"] ?? records[\"@example/lossless-claw\"];
|
|
if (!record) {
|
|
throw new Error(\"missing seeded plugin install record\");
|
|
}
|
|
const snapshot = {
|
|
source: record.source,
|
|
spec: record.spec,
|
|
resolvedName: record.resolvedName,
|
|
resolvedVersion: record.resolvedVersion,
|
|
resolvedSpec: record.resolvedSpec,
|
|
integrity: record.integrity,
|
|
shasum: record.shasum
|
|
};
|
|
process.stdout.write(JSON.stringify(snapshot, null, 2));
|
|
NODE
|
|
|
|
set +e
|
|
timeout \"\${plugin_update_timeout_seconds}s\" node \"\$entry\" plugins update @example/lossless-claw > /tmp/plugin-update-output.log 2>&1
|
|
plugin_update_status=\$?
|
|
set -e
|
|
if [ \"\$plugin_update_status\" -ne 0 ]; then
|
|
echo \"Plugin update command failed or timed out after \${plugin_update_timeout_seconds}s (status \${plugin_update_status})\"
|
|
echo \"--- plugin update output ---\"
|
|
cat /tmp/plugin-update-output.log || true
|
|
echo \"--- local registry output ---\"
|
|
cat /tmp/openclaw-e2e-registry.log || true
|
|
exit \"\$plugin_update_status\"
|
|
fi
|
|
|
|
if [ -n \"\$before_config_hash\" ]; then
|
|
after_config_hash=\$(sha256sum \"\$OPENCLAW_CONFIG_PATH\" | awk '{print \$1}')
|
|
if [ \"\$before_config_hash\" != \"\$after_config_hash\" ]; then
|
|
echo \"Config changed unexpectedly for modern package \$package_version\"
|
|
cat /tmp/plugin-update-output.log
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
node --input-type=module <<'NODE'
|
|
import fs from \"node:fs\";
|
|
import os from \"node:os\";
|
|
import path from \"node:path\";
|
|
|
|
const readJson = (file) => {
|
|
try {
|
|
return JSON.parse(fs.readFileSync(file, \"utf8\"));
|
|
} catch {
|
|
return {};
|
|
}
|
|
};
|
|
const home = os.homedir();
|
|
const before = readJson(\"/tmp/plugin-update-before.json\");
|
|
const config = readJson(path.join(home, \".openclaw\", \"openclaw.json\"));
|
|
const index = readJson(path.join(home, \".openclaw\", \"plugins\", \"installs.json\"));
|
|
const records = index.installRecords ?? index.records ?? config.plugins?.installs ?? {};
|
|
const record = records[\"lossless-claw\"] ?? records[\"@example/lossless-claw\"];
|
|
if (!record) {
|
|
throw new Error(\"missing plugin install record after update\");
|
|
}
|
|
const after = {
|
|
source: record.source,
|
|
spec: record.spec,
|
|
resolvedName: record.resolvedName,
|
|
resolvedVersion: record.resolvedVersion,
|
|
resolvedSpec: record.resolvedSpec,
|
|
integrity: record.integrity,
|
|
shasum: record.shasum
|
|
};
|
|
if (JSON.stringify(before) !== JSON.stringify(after)) {
|
|
throw new Error(\"plugin install record changed unexpectedly: \" + JSON.stringify({ before, after }));
|
|
}
|
|
NODE
|
|
if grep -q 'Downloading @example/lossless-claw' /tmp/plugin-update-output.log; then
|
|
echo \"Unexpected npm download/reinstall path\"
|
|
cat /tmp/plugin-update-output.log
|
|
exit 1
|
|
fi
|
|
if ! grep -q 'lossless-claw is up to date (0.9.0).' /tmp/plugin-update-output.log; then
|
|
echo \"Expected up-to-date output missing\"
|
|
cat /tmp/plugin-update-output.log
|
|
exit 1
|
|
fi
|
|
cat /tmp/plugin-update-output.log
|
|
"
|
|
|
|
echo "Plugin update unchanged Docker E2E passed."
|