mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix: restore release validation on main (#102516)
* test(qqbot): assert approval fence exactly * fix(release): wait for ClawHub publish logging * fix(ci): repair Bun global install smoke * test(slack): fix app identity fixture
This commit is contained in:
parent
32de666163
commit
42a8679c4d
6 changed files with 278 additions and 106 deletions
|
|
@ -24,6 +24,7 @@ DIST_IMAGE="${OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE:-}"
|
|||
PACKAGE_TGZ="${OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ:-}"
|
||||
COMMAND_TIMEOUT_MS="$(read_positive_int_env OPENCLAW_BUN_GLOBAL_SMOKE_TIMEOUT_MS 180000)"
|
||||
DOCKER_COMMAND_TIMEOUT="${DOCKER_COMMAND_TIMEOUT:-${OPENCLAW_BUN_GLOBAL_SMOKE_DOCKER_COMMAND_TIMEOUT:-600s}}"
|
||||
AI_PACKAGE_TGZ=""
|
||||
SMOKE_DIR=""
|
||||
PACK_DIR=""
|
||||
|
||||
|
|
@ -36,6 +37,39 @@ cleanup() {
|
|||
fi
|
||||
}
|
||||
|
||||
prepare_ai_candidate() {
|
||||
local ai_manifest
|
||||
local ai_package_dir
|
||||
local ai_tarballs
|
||||
local root_manifest
|
||||
|
||||
if [ -z "$PACK_DIR" ]; then
|
||||
PACK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-bun-pack.XXXXXX")"
|
||||
fi
|
||||
echo "==> Extract bundled candidate @openclaw/ai package"
|
||||
ai_package_dir="$PACK_DIR/ai-candidate"
|
||||
mkdir -p "$ai_package_dir"
|
||||
tar -xzf "$PACKAGE_TGZ" \
|
||||
-C "$ai_package_dir" \
|
||||
--strip-components=4 \
|
||||
package/node_modules/@openclaw/ai
|
||||
root_manifest="$PACK_DIR/openclaw-package.json"
|
||||
ai_manifest="$ai_package_dir/package.json"
|
||||
tar -xOf "$PACKAGE_TGZ" package/package.json >"$root_manifest"
|
||||
node scripts/e2e/lib/bun-global-install/assertions.mjs \
|
||||
assert-release-versions \
|
||||
"$root_manifest" \
|
||||
"$ai_manifest" \
|
||||
>/dev/null
|
||||
npm pack --ignore-scripts --silent --pack-destination "$PACK_DIR" "$ai_package_dir" >/dev/null
|
||||
ai_tarballs=("$PACK_DIR"/openclaw-ai-*.tgz)
|
||||
if [ "${#ai_tarballs[@]}" -ne 1 ] || [ ! -f "${ai_tarballs[0]}" ]; then
|
||||
echo "expected one packed @openclaw/ai candidate in $PACK_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
AI_PACKAGE_TGZ="${ai_tarballs[0]}"
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
run_with_timeout() {
|
||||
|
|
@ -44,52 +78,50 @@ run_with_timeout() {
|
|||
node scripts/e2e/lib/bun-global-install/assertions.mjs run-with-timeout "$timeout_ms" "$@"
|
||||
}
|
||||
|
||||
resolve_pack_tarball_path() {
|
||||
local pack_json_file="$1"
|
||||
local pack_dir="$2"
|
||||
node -e '
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const raw = fs.readFileSync(process.argv[1], "utf8") || "[]";
|
||||
const parsed = JSON.parse(raw);
|
||||
const last = Array.isArray(parsed) ? parsed.at(-1) : null;
|
||||
const filename = typeof last?.filename === "string" ? last.filename.trim() : "";
|
||||
if (
|
||||
!filename.endsWith(".tgz") ||
|
||||
filename.includes("\0") ||
|
||||
filename !== path.basename(filename) ||
|
||||
filename !== path.win32.basename(filename)
|
||||
) {
|
||||
console.error(`ERROR: npm pack reported unsafe tarball filename ${JSON.stringify(filename)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
process.stdout.write(path.resolve(process.argv[2], filename));
|
||||
' "$pack_json_file" "$pack_dir"
|
||||
}
|
||||
|
||||
restore_dist_from_image() {
|
||||
local image="$1"
|
||||
local ai_backup_dir=""
|
||||
local ai_dist_installed=0
|
||||
local backup_dir=""
|
||||
local container_id=""
|
||||
local swapped=0
|
||||
local dist_installed=0
|
||||
local restore_complete=0
|
||||
local temp_dir=""
|
||||
|
||||
cleanup_restore_dist() {
|
||||
if [ -n "$container_id" ]; then
|
||||
docker_e2e_docker_cmd rm -f "$container_id" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ "$swapped" != "1" ] && [ -n "$backup_dir" ] && [ -d "$backup_dir" ]; then
|
||||
rm -rf "$ROOT_DIR/dist" >/dev/null 2>&1 || true
|
||||
if [ ! -e "$ROOT_DIR/dist" ] && mv "$backup_dir" "$ROOT_DIR/dist" >/dev/null 2>&1; then
|
||||
backup_dir=""
|
||||
# Both build trees come from one image. A partial swap must restore both or
|
||||
# the following package step could mix artifacts from different builds.
|
||||
if [ "$restore_complete" != "1" ]; then
|
||||
if [ "$dist_installed" = "1" ]; then
|
||||
rm -rf "$ROOT_DIR/dist" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ -n "$backup_dir" ] && [ -d "$backup_dir" ]; then
|
||||
if [ ! -e "$ROOT_DIR/dist" ] && mv "$backup_dir" "$ROOT_DIR/dist" >/dev/null 2>&1; then
|
||||
backup_dir=""
|
||||
fi
|
||||
fi
|
||||
if [ "$ai_dist_installed" = "1" ]; then
|
||||
rm -rf "$ROOT_DIR/packages/ai/dist" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ -n "$ai_backup_dir" ] && [ -d "$ai_backup_dir" ]; then
|
||||
if [ ! -e "$ROOT_DIR/packages/ai/dist" ] && \
|
||||
mv "$ai_backup_dir" "$ROOT_DIR/packages/ai/dist" >/dev/null 2>&1; then
|
||||
ai_backup_dir=""
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ -n "$temp_dir" ]; then
|
||||
rm -rf "$temp_dir"
|
||||
fi
|
||||
if [ "$swapped" = "1" ] && [ -n "$backup_dir" ]; then
|
||||
if [ "$restore_complete" = "1" ] && [ -n "$backup_dir" ]; then
|
||||
rm -rf "$backup_dir"
|
||||
fi
|
||||
if [ "$restore_complete" = "1" ] && [ -n "$ai_backup_dir" ]; then
|
||||
rm -rf "$ai_backup_dir"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "==> Reuse dist/ from Docker image: $image"
|
||||
|
|
@ -105,6 +137,12 @@ restore_dist_from_image() {
|
|||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
if ! docker_e2e_docker_cmd cp \
|
||||
"${container_id}:/app/node_modules/@openclaw/ai/dist" \
|
||||
"$temp_dir/ai-dist"; then
|
||||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
if [ -e "$ROOT_DIR/dist" ]; then
|
||||
if ! backup_dir="$(mktemp -d "$ROOT_DIR/.dist-backup.XXXXXX")"; then
|
||||
cleanup_restore_dist
|
||||
|
|
@ -123,7 +161,27 @@ restore_dist_from_image() {
|
|||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
swapped=1
|
||||
dist_installed=1
|
||||
if [ -e "$ROOT_DIR/packages/ai/dist" ]; then
|
||||
if ! ai_backup_dir="$(mktemp -d "$ROOT_DIR/packages/ai/.dist-backup.XXXXXX")"; then
|
||||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
if ! rmdir "$ai_backup_dir"; then
|
||||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
if ! mv "$ROOT_DIR/packages/ai/dist" "$ai_backup_dir"; then
|
||||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
if ! mv "$temp_dir/ai-dist" "$ROOT_DIR/packages/ai/dist"; then
|
||||
cleanup_restore_dist
|
||||
return 1
|
||||
fi
|
||||
ai_dist_installed=1
|
||||
restore_complete=1
|
||||
cleanup_restore_dist
|
||||
}
|
||||
|
||||
|
|
@ -151,16 +209,15 @@ resolve_package_tgz() {
|
|||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Write package inventory"
|
||||
node --import tsx scripts/write-package-dist-inventory.ts
|
||||
|
||||
local pack_json_file
|
||||
PACK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-bun-pack.XXXXXX")"
|
||||
pack_json_file="$PACK_DIR/pack.json"
|
||||
|
||||
echo "==> Pack OpenClaw tarball"
|
||||
npm pack --ignore-scripts --json --pack-destination "$PACK_DIR" >"$pack_json_file"
|
||||
PACKAGE_TGZ="$(resolve_pack_tarball_path "$pack_json_file" "$PACK_DIR")"
|
||||
PACKAGE_TGZ="$(
|
||||
node scripts/package-openclaw-for-docker.mjs \
|
||||
--skip-build \
|
||||
--output-dir "$PACK_DIR" \
|
||||
--output-name openclaw-current.tgz
|
||||
)"
|
||||
if [ -z "$PACKAGE_TGZ" ] || [ ! -f "$PACKAGE_TGZ" ]; then
|
||||
echo "missing packed OpenClaw tarball" >&2
|
||||
exit 1
|
||||
|
|
@ -176,6 +233,7 @@ main() {
|
|||
fi
|
||||
|
||||
resolve_package_tgz
|
||||
prepare_ai_candidate
|
||||
|
||||
local bun_path
|
||||
local openclaw_bin
|
||||
|
|
@ -188,8 +246,21 @@ main() {
|
|||
export OPENCLAW_NO_ONBOARD=1
|
||||
export OPENCLAW_DISABLE_UPDATE_CHECK=1
|
||||
export NO_COLOR=1
|
||||
mkdir -p "$HOME" "$BUN_INSTALL/bin" "$XDG_CACHE_HOME"
|
||||
mkdir -p "$HOME" "$BUN_INSTALL/bin" "$BUN_INSTALL/install/global" "$XDG_CACHE_HOME"
|
||||
export PATH="$BUN_INSTALL/bin:$(dirname "$(command -v node)"):$PATH"
|
||||
# Release publishes @openclaw/ai first. Bun 1.3.14 ignores bundled deps in
|
||||
# local tarballs, so resolve that one package from the exact candidate bytes.
|
||||
node --input-type=module - \
|
||||
"$BUN_INSTALL/install/global/package.json" \
|
||||
"$AI_PACKAGE_TGZ" <<'NODE'
|
||||
import fs from "node:fs";
|
||||
|
||||
const [, , packageJsonPath, aiPackageTarball] = process.argv;
|
||||
fs.writeFileSync(
|
||||
packageJsonPath,
|
||||
`${JSON.stringify({ private: true, overrides: { "@openclaw/ai": `file:${aiPackageTarball}` } })}\n`,
|
||||
);
|
||||
NODE
|
||||
|
||||
echo "==> Bun version"
|
||||
"$bun_path" --version
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
// Assertions for Bun global install E2E validation.
|
||||
import { spawn } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
|
||||
const DEFAULT_TIMEOUT_KILL_GRACE_MS = 30_000;
|
||||
const PARENT_TERMINATION_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
||||
|
||||
const usage = () => {
|
||||
console.error("Usage: assertions.mjs <run-with-timeout|assert-image-providers> [...]");
|
||||
console.error(
|
||||
"Usage: assertions.mjs <run-with-timeout|assert-image-providers|assert-release-versions> [...]",
|
||||
);
|
||||
process.exit(2);
|
||||
};
|
||||
|
||||
|
|
@ -204,4 +207,28 @@ if (mode === "assert-image-providers") {
|
|||
process.exit(0);
|
||||
}
|
||||
|
||||
if (mode === "assert-release-versions") {
|
||||
const [rootManifestPath, aiManifestPath] = args;
|
||||
if (!rootManifestPath || !aiManifestPath) {
|
||||
usage();
|
||||
}
|
||||
const rootManifest = JSON.parse(fs.readFileSync(rootManifestPath, "utf8"));
|
||||
const aiManifest = JSON.parse(fs.readFileSync(aiManifestPath, "utf8"));
|
||||
const rootVersion = rootManifest.version;
|
||||
const aiVersion = aiManifest.version;
|
||||
const rootAiVersion = rootManifest.dependencies?.["@openclaw/ai"];
|
||||
if (
|
||||
typeof rootVersion !== "string" ||
|
||||
typeof aiVersion !== "string" ||
|
||||
rootVersion !== aiVersion ||
|
||||
rootAiVersion !== aiVersion
|
||||
) {
|
||||
throw new Error(
|
||||
`candidate version mismatch: openclaw=${String(rootVersion)}, dependency=${String(rootAiVersion)}, @openclaw/ai=${String(aiVersion)}`,
|
||||
);
|
||||
}
|
||||
process.stdout.write(aiVersion);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
usage();
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ fi
|
|||
|
||||
publish_log="${pack_dir}/publish.log"
|
||||
for attempt in $(seq 1 "${OPENCLAW_CLAWHUB_PUBLISH_ATTEMPTS:-8}"); do
|
||||
if CLAWHUB_WORKDIR="${clawhub_workdir}" "${publish_cmd[@]}" > >(tee "${publish_log}") 2>&1; then
|
||||
if CLAWHUB_WORKDIR="${clawhub_workdir}" "${publish_cmd[@]}" 2>&1 | tee "${publish_log}"; then
|
||||
exit 0
|
||||
fi
|
||||
if ! grep -Eqi "rate limit|too many requests|\\b429\\b" "${publish_log}"; then
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue