Files
openclaw/scripts/e2e/plugin-update-unchanged-docker.sh
2026-04-28 13:41:52 -07:00

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."