diff --git a/.agents/skills/openclaw-release-maintainer/SKILL.md b/.agents/skills/openclaw-release-maintainer/SKILL.md index 6532077d84d..ed887dc78b6 100644 --- a/.agents/skills/openclaw-release-maintainer/SKILL.md +++ b/.agents/skills/openclaw-release-maintainer/SKILL.md @@ -90,6 +90,9 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts now fails the candidate update tarball when npm reports an oversized `unpackedSize`, so release-time e2e cannot miss pack bloat that would risk low-memory install/startup failures. +- Keep direct npm global coverage enabled in install smoke. It exercises plain + `npm install -g ` fresh installs and npm-driven update installs, + because many users install with npm even when docs prefer pnpm. - Use `pnpm test:live:media video` for bounded video-provider smoke when video generation is in release scope. The default video smoke skips `fal`, runs one text-to-video attempt per provider with a one-second lobster prompt, and caps diff --git a/scripts/docker/install-sh-smoke/run.sh b/scripts/docker/install-sh-smoke/run.sh index 8e55f1e9075..4378996a43b 100755 --- a/scripts/docker/install-sh-smoke/run.sh +++ b/scripts/docker/install-sh-smoke/run.sh @@ -14,6 +14,7 @@ UPDATE_BASELINE_TAG_URL="${OPENCLAW_INSTALL_UPDATE_BASELINE_TAG_URL:-}" UPDATE_EXPECT_VERSION="${OPENCLAW_INSTALL_UPDATE_EXPECT_VERSION:-}" UPDATE_TAG_URL="${OPENCLAW_INSTALL_UPDATE_TAG_URL:-}" HEARTBEAT_INTERVAL="${OPENCLAW_INSTALL_SMOKE_HEARTBEAT_INTERVAL:-60}" +INSTALL_COMMAND_TIMEOUT="${OPENCLAW_INSTALL_SMOKE_COMMAND_TIMEOUT:-300}" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # shellcheck source=../install-sh-common/cli-verify.sh @@ -108,12 +109,26 @@ run_with_heartbeat() { return "$status" } +npm_install_global() { + local label="$1" + shift + run_with_heartbeat "$label" \ + timeout --foreground "${INSTALL_COMMAND_TIMEOUT}s" \ + npm \ + --loglevel=error \ + --logs-max=0 \ + --no-update-notifier \ + --no-fund \ + --no-audit \ + --no-progress \ + install -g "$@" +} + run_install_smoke() { if [[ -n "$FRESH_VERSION" && -n "$FRESH_TAG_URL" ]]; then echo "package=$PACKAGE_NAME latest=$FRESH_VERSION source=$FRESH_TAG_URL" echo "==> Install latest release tarball" - run_with_heartbeat "install latest release tarball" \ - quiet_npm install -g --omit=optional "$FRESH_TAG_URL" + npm_install_global "install latest release tarball" --omit=optional "$FRESH_TAG_URL" print_install_audit "fresh install" echo "==> Verify installed version" @@ -167,8 +182,7 @@ NODE echo "==> Skip preinstall previous (OPENCLAW_INSTALL_SMOKE_SKIP_PREVIOUS=1)" else echo "==> Preinstall previous (forces installer upgrade path)" - run_with_heartbeat "preinstall previous release" \ - quiet_npm install -g "${PACKAGE_NAME}@${PREVIOUS_VERSION}" + npm_install_global "preinstall previous release" "${PACKAGE_NAME}@${PREVIOUS_VERSION}" print_install_audit "previous install" fi @@ -197,11 +211,9 @@ run_update_smoke() { echo "package=$PACKAGE_NAME baseline=$UPDATE_BASELINE_VERSION target=$UPDATE_EXPECT_VERSION" echo "==> Install baseline release" if [[ -n "$UPDATE_BASELINE_TAG_URL" ]]; then - run_with_heartbeat "install baseline release" \ - quiet_npm install -g --omit=optional "$UPDATE_BASELINE_TAG_URL" + npm_install_global "install baseline release" --omit=optional "$UPDATE_BASELINE_TAG_URL" else - run_with_heartbeat "install baseline release" \ - quiet_npm install -g --omit=optional "${PACKAGE_NAME}@${UPDATE_BASELINE_VERSION}" + npm_install_global "install baseline release" --omit=optional "${PACKAGE_NAME}@${UPDATE_BASELINE_VERSION}" fi print_install_audit "baseline install" verify_installed_cli "$PACKAGE_NAME" "$UPDATE_BASELINE_VERSION" @@ -275,6 +287,39 @@ NODE echo "OK" } +run_npm_global_smoke() { + if [[ -z "$UPDATE_EXPECT_VERSION" ]]; then + echo "ERROR: OPENCLAW_INSTALL_UPDATE_EXPECT_VERSION is required for npm-global mode" >&2 + return 1 + fi + if [[ -z "$UPDATE_TAG_URL" ]]; then + echo "ERROR: OPENCLAW_INSTALL_UPDATE_TAG_URL is required for npm-global mode" >&2 + return 1 + fi + + echo "package=$PACKAGE_NAME baseline=$UPDATE_BASELINE_VERSION target=$UPDATE_EXPECT_VERSION" + echo "==> Direct npm global install candidate" + npm_install_global "direct npm global install candidate" "$UPDATE_TAG_URL" + print_install_audit "direct npm fresh install" + verify_installed_cli "$PACKAGE_NAME" "$UPDATE_EXPECT_VERSION" + + echo "==> Direct npm global install baseline" + if [[ -n "$UPDATE_BASELINE_TAG_URL" ]]; then + npm_install_global "direct npm global install baseline" "$UPDATE_BASELINE_TAG_URL" + else + npm_install_global "direct npm global install baseline" "${PACKAGE_NAME}@${UPDATE_BASELINE_VERSION}" + fi + print_install_audit "direct npm baseline install" + verify_installed_cli "$PACKAGE_NAME" "$UPDATE_BASELINE_VERSION" + + echo "==> Direct npm global update candidate" + npm_install_global "direct npm global update candidate" "$UPDATE_TAG_URL" + print_install_audit "direct npm updated install" + verify_installed_cli "$PACKAGE_NAME" "$UPDATE_EXPECT_VERSION" + + echo "OK" +} + case "$SMOKE_MODE" in install) run_install_smoke @@ -282,6 +327,9 @@ case "$SMOKE_MODE" in update) run_update_smoke ;; + npm-global) + run_npm_global_smoke + ;; *) echo "ERROR: unsupported OPENCLAW_INSTALL_SMOKE_MODE=$SMOKE_MODE" >&2 exit 1 diff --git a/scripts/test-install-sh-docker.sh b/scripts/test-install-sh-docker.sh index 19bf96520e4..c4df79937d7 100755 --- a/scripts/test-install-sh-docker.sh +++ b/scripts/test-install-sh-docker.sh @@ -147,6 +147,7 @@ SKIP_NONROOT="${OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT:-0}" SKIP_SMOKE_IMAGE_BUILD="${OPENCLAW_INSTALL_SMOKE_SKIP_IMAGE_BUILD:-0}" SKIP_NONROOT_IMAGE_BUILD="${OPENCLAW_INSTALL_NONROOT_SKIP_IMAGE_BUILD:-0}" SKIP_UPDATE="${OPENCLAW_INSTALL_SMOKE_SKIP_UPDATE:-0}" +SKIP_NPM_GLOBAL="${OPENCLAW_INSTALL_SMOKE_SKIP_NPM_GLOBAL:-0}" UPDATE_BASELINE_VERSION="${OPENCLAW_INSTALL_SMOKE_UPDATE_BASELINE:-2026.4.10}" UPDATE_PACKAGE_SPEC="${OPENCLAW_INSTALL_SMOKE_UPDATE_PACKAGE_SPEC:-}" UPDATE_SKIP_LOCAL_BUILD="${OPENCLAW_INSTALL_SMOKE_UPDATE_SKIP_LOCAL_BUILD:-0}" @@ -344,6 +345,25 @@ else -e OPENCLAW_NO_PROMPT=1 \ -e DEBIAN_FRONTEND=noninteractive \ "$SMOKE_IMAGE" + + if [[ "$SKIP_NPM_GLOBAL" == "1" ]]; then + echo "==> Skip direct npm global smoke (OPENCLAW_INSTALL_SMOKE_SKIP_NPM_GLOBAL=1)" + else + echo "==> Run direct npm global smoke (${UPDATE_BASELINE_VERSION} -> ${UPDATE_EXPECT_VERSION})" + docker run --rm -t \ + --platform "$SMOKE_PLATFORM" \ + "${UPDATE_DOCKER_HOST_ARGS[@]}" \ + -e OPENCLAW_INSTALL_PACKAGE="$PACKAGE_NAME" \ + -e OPENCLAW_INSTALL_SMOKE_MODE=npm-global \ + -e OPENCLAW_INSTALL_UPDATE_BASELINE="$UPDATE_BASELINE_VERSION" \ + -e OPENCLAW_INSTALL_UPDATE_BASELINE_TAG_URL="$BASELINE_TAG_URL" \ + -e OPENCLAW_INSTALL_UPDATE_EXPECT_VERSION="$UPDATE_EXPECT_VERSION" \ + -e OPENCLAW_INSTALL_UPDATE_TAG_URL="$UPDATE_TAG_URL" \ + -e OPENCLAW_NO_ONBOARD=1 \ + -e OPENCLAW_NO_PROMPT=1 \ + -e DEBIAN_FRONTEND=noninteractive \ + "$SMOKE_IMAGE" + fi fi LATEST_VERSION="${LATEST_VERSION:-}" diff --git a/test/scripts/test-install-sh-docker.test.ts b/test/scripts/test-install-sh-docker.test.ts index 7fbe2a1ae3f..50a8f8ba2ea 100644 --- a/test/scripts/test-install-sh-docker.test.ts +++ b/test/scripts/test-install-sh-docker.test.ts @@ -53,10 +53,27 @@ describe("install-sh smoke runner", () => { expect(script).toContain( 'HEARTBEAT_INTERVAL="${OPENCLAW_INSTALL_SMOKE_HEARTBEAT_INTERVAL:-60}"', ); + expect(script).toContain( + 'INSTALL_COMMAND_TIMEOUT="${OPENCLAW_INSTALL_SMOKE_COMMAND_TIMEOUT:-300}"', + ); expect(script).toContain("run_with_heartbeat"); + expect(script).toContain("npm_install_global"); + expect(script).toContain('timeout --foreground "${INSTALL_COMMAND_TIMEOUT}s"'); expect(script).toContain("==> Still running"); expect(script).toContain("print_install_audit"); - expect(script).toContain("quiet_npm install -g --omit=optional"); + expect(script).toContain('install -g "$@"'); expect(script).toContain("openclaw update --tag"); }); + + it("covers plain npm global installs and npm-driven updates", () => { + const script = readFileSync(SCRIPT_PATH, "utf8"); + const runner = readFileSync(SMOKE_RUNNER_PATH, "utf8"); + + expect(script).toContain('SKIP_NPM_GLOBAL="${OPENCLAW_INSTALL_SMOKE_SKIP_NPM_GLOBAL:-0}"'); + expect(script).toContain("==> Run direct npm global smoke"); + expect(script).toContain("OPENCLAW_INSTALL_SMOKE_MODE=npm-global"); + expect(runner).toContain("run_npm_global_smoke"); + expect(runner).toContain("==> Direct npm global install candidate"); + expect(runner).toContain("==> Direct npm global update candidate"); + }); });