From d8c9185f3ffeaa6aa508c1184ade53f7930880eb Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 22 Apr 2026 18:38:40 +0100 Subject: [PATCH] ci: add fast docker install smoke --- .github/workflows/install-smoke.yml | 5 +++++ docs/ci.md | 2 +- scripts/ci-changed-scope.mjs | 2 +- scripts/e2e/Dockerfile.qr-import | 2 ++ scripts/e2e/qr-import-docker.sh | 15 ++++++++++++++- src/scripts/ci-changed-scope.test.ts | 9 +++++++++ 6 files changed, 32 insertions(+), 3 deletions(-) diff --git a/.github/workflows/install-smoke.yml b/.github/workflows/install-smoke.yml index 8cc07483bb7..e9fbe7e71cd 100644 --- a/.github/workflows/install-smoke.yml +++ b/.github/workflows/install-smoke.yml @@ -91,6 +91,11 @@ jobs: # Blacksmith's builder owns the Docker layer cache; keep smoke builds off # explicit gha cache directives so local tags still load cleanly. + - name: Run QR package install smoke + env: + OPENCLAW_QR_SMOKE_FORCE_INSTALL: "1" + run: bash scripts/e2e/qr-import-docker.sh + - name: Build root Dockerfile smoke image uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2 with: diff --git a/docs/ci.md b/docs/ci.md index 36456b99a35..bdf9ac158f1 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -46,7 +46,7 @@ Jobs are ordered so cheap checks fail before expensive ones run: Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests in `src/scripts/ci-changed-scope.test.ts`. CI workflow edits validate the Node CI graph plus workflow linting, but do not force Windows, Android, or macOS native builds by themselves; those platform lanes stay scoped to platform source changes. -The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It computes `run_install_smoke` from the narrower changed-smoke signal, so Docker/install smoke only runs for install, packaging, and container-relevant changes. +The separate `install-smoke` workflow reuses the same scope script through its own `preflight` job. It computes `run_install_smoke` from the narrower changed-smoke signal, so Docker/install smoke only runs for install, packaging, and container-relevant changes. Its QR package smoke forces the Docker `pnpm install` layer to rerun while preserving the BuildKit pnpm store cache, so it still exercises installation without redownloading dependencies on every run. Local changed-lane logic lives in `scripts/changed-lanes.mjs` and is executed by `scripts/check-changed.mjs`. That local gate is stricter about architecture boundaries than the broad CI platform scope: core production changes run core prod typecheck plus core tests, core test-only changes run only core test typecheck/tests, extension production changes run extension prod typecheck plus extension tests, and extension test-only changes run only extension test typecheck/tests. Public Plugin SDK or plugin-contract changes expand to extension validation because extensions depend on those core contracts. Release metadata-only version bumps run targeted version/config/root-dependency checks. Unknown root/config changes fail safe to all lanes. diff --git a/scripts/ci-changed-scope.mjs b/scripts/ci-changed-scope.mjs index 1951fab1974..4f9eb9c5651 100644 --- a/scripts/ci-changed-scope.mjs +++ b/scripts/ci-changed-scope.mjs @@ -20,7 +20,7 @@ const CONTROL_UI_I18N_SCOPE_RE = const NATIVE_ONLY_RE = /^(apps\/android\/|apps\/ios\/|apps\/macos\/|apps\/macos-mlx-tts\/|apps\/shared\/|Swabble\/|appcast\.xml$)/; const CHANGED_SMOKE_SCOPE_RE = - /^(Dockerfile$|\.npmrc$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|scripts\/install\.sh$|scripts\/test-install-sh-docker\.sh$|scripts\/docker\/|extensions\/[^/]+\/package\.json$|\.github\/workflows\/install-smoke\.yml$|\.github\/actions\/setup-node-env\/action\.yml$)/; + /^(Dockerfile$|\.npmrc$|package\.json$|pnpm-lock\.yaml$|pnpm-workspace\.yaml$|scripts\/install\.sh$|scripts\/test-install-sh-docker\.sh$|scripts\/docker\/|scripts\/e2e\/(?:Dockerfile\.qr-import|qr-import-docker\.sh)$|extensions\/[^/]+\/package\.json$|\.github\/workflows\/install-smoke\.yml$|\.github\/actions\/setup-node-env\/action\.yml$)/; /** * @param {string[]} changedPaths diff --git a/scripts/e2e/Dockerfile.qr-import b/scripts/e2e/Dockerfile.qr-import index 6c536d18a61..bd23478d142 100644 --- a/scripts/e2e/Dockerfile.qr-import +++ b/scripts/e2e/Dockerfile.qr-import @@ -20,7 +20,9 @@ COPY --chown=appuser:appuser patches ./patches # This image only exercises the root qrcode-terminal dependency path. # Keep the pre-install copy set limited to the manifests needed for root # workspace resolution so unrelated extension edits do not bust the layer. +ARG OPENCLAW_QR_INSTALL_CACHE_BUSTER=stable RUN --mount=type=cache,id=openclaw-pnpm-store,target=/home/appuser/.local/share/pnpm/store,sharing=locked \ + printf '%s\n' "$OPENCLAW_QR_INSTALL_CACHE_BUSTER" >/tmp/openclaw-qr-install-cache-buster && \ if ! pnpm install --frozen-lockfile --ignore-scripts >/tmp/openclaw-qr-pnpm-install.log 2>&1; then \ cat /tmp/openclaw-qr-pnpm-install.log; \ exit 1; \ diff --git a/scripts/e2e/qr-import-docker.sh b/scripts/e2e/qr-import-docker.sh index 8d32bacd8b4..9cf6b63cd4e 100755 --- a/scripts/e2e/qr-import-docker.sh +++ b/scripts/e2e/qr-import-docker.sh @@ -4,9 +4,22 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" source "$ROOT_DIR/scripts/lib/docker-e2e-logs.sh" IMAGE_NAME="${OPENCLAW_QR_SMOKE_IMAGE:-openclaw-qr-smoke}" +DOCKER_BUILD_ARGS=() + +if [[ "${OPENCLAW_QR_SMOKE_FORCE_INSTALL:-0}" == "1" ]]; then + INSTALL_CACHE_BUSTER="${GITHUB_SHA:-manual}-${GITHUB_RUN_ID:-$(date +%s)}-${GITHUB_RUN_ATTEMPT:-0}" + DOCKER_BUILD_ARGS+=( + --build-arg + "OPENCLAW_QR_INSTALL_CACHE_BUSTER=${INSTALL_CACHE_BUSTER}" + ) +fi echo "Building Docker image..." -run_logged qr-import-build docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/scripts/e2e/Dockerfile.qr-import" "$ROOT_DIR" +run_logged qr-import-build docker build \ + "${DOCKER_BUILD_ARGS[@]}" \ + -t "$IMAGE_NAME" \ + -f "$ROOT_DIR/scripts/e2e/Dockerfile.qr-import" \ + "$ROOT_DIR" echo "Running qrcode-terminal import smoke..." run_logged qr-import-run docker run --rm -t "$IMAGE_NAME" node -e "import('qrcode-terminal').then((m)=>m.default.generate('qr-smoke',{small:true}))" diff --git a/src/scripts/ci-changed-scope.test.ts b/src/scripts/ci-changed-scope.test.ts index ce536d860ce..fef56ff525c 100644 --- a/src/scripts/ci-changed-scope.test.ts +++ b/src/scripts/ci-changed-scope.test.ts @@ -210,6 +210,15 @@ describe("detectChangedScope", () => { runChangedSmoke: true, runControlUiI18n: false, }); + expect(detectChangedScope(["scripts/e2e/qr-import-docker.sh"])).toEqual({ + runNode: true, + runMacos: false, + runAndroid: false, + runWindows: true, + runSkillsPython: false, + runChangedSmoke: true, + runControlUiI18n: false, + }); }); it("runs control-ui locale check only for control-ui i18n surfaces", () => {