diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97c94ddacd7a..789c6f163a22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1031,6 +1031,8 @@ jobs: if: needs.preflight.outputs.run_ui_tests == 'true' runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }} timeout-minutes: 20 + env: + COMPATIBILITY_TARGET: ${{ needs.preflight.outputs.compatibility_target }} steps: - *linux_node_checkout_step - name: Setup Node environment @@ -1046,7 +1048,14 @@ jobs: run: pnpm lint:ui:no-raw-window-open - name: Test Control UI - run: pnpm --dir ui test + run: | + if [[ "$COMPATIBILITY_TARGET" == "true" ]]; then + # Frozen targets can contain timing-sensitive tests fixed on current main. + # Retry only failures once; deterministic regressions still fail twice. + pnpm --dir ui test -- --retry=1 + else + pnpm --dir ui test + fi checks-fast-core: permissions: @@ -2082,8 +2091,25 @@ jobs: "$swift_tools_dir/swiftformat" --version "$swift_tools_dir/swiftlint" version elif [[ "$HISTORICAL_TARGET" == "true" ]]; then - # Frozen release targets before the pinned installer used Homebrew directly. - brew install xcodegen swiftlint swiftformat + # Frozen release targets before the pinned installer used SwiftFormat 0.61.1. + # Keep their lint contract stable instead of inheriting new Homebrew rules. + brew install xcodegen swiftlint + swiftformat_version="0.61.1" + swiftformat_checksum="b990400779aceb7d7020796eb9ba814d4480543f671d38fc0ff48cb72f04c584" + swiftformat_archive="$RUNNER_TEMP/swiftformat-$swiftformat_version.zip" + swift_tools_dir="$RUNNER_TEMP/openclaw-legacy-swift-tools" + curl --fail --location --silent --show-error --retry 3 \ + --output "$swiftformat_archive" \ + "https://github.com/nicklockwood/SwiftFormat/releases/download/$swiftformat_version/swiftformat.zip" + if [[ "$(shasum -a 256 "$swiftformat_archive" | awk '{print $1}')" != "$swiftformat_checksum" ]]; then + echo "SwiftFormat $swiftformat_version archive checksum mismatch" >&2 + exit 1 + fi + mkdir -p "$swift_tools_dir" + unzip -q "$swiftformat_archive" -d "$swift_tools_dir" + chmod +x "$swift_tools_dir/swiftformat" + echo "$swift_tools_dir" >> "$GITHUB_PATH" + [[ "$("$swift_tools_dir/swiftformat" --version)" == "$swiftformat_version" ]] else echo "Current CI targets must provide scripts/install-swift-tools.sh." >&2 exit 1 diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts index a5aa5701419b..61671c03ad7d 100644 --- a/test/scripts/ci-workflow-guards.test.ts +++ b/test/scripts/ci-workflow-guards.test.ts @@ -1559,8 +1559,10 @@ describe("ci workflow guards", () => { for (const installStep of [macosInstallStep, iosInstallStep]) { expect(installStep.run).toContain("if [[ -x ./scripts/install-swift-tools.sh ]]; then"); - expect(installStep.run).toContain("brew install xcodegen swiftlint swiftformat"); } + expect(macosInstallStep.run).toContain("brew install xcodegen swiftlint"); + expect(macosInstallStep.run).not.toContain("brew install xcodegen swiftlint swiftformat"); + expect(iosInstallStep.run).toContain("brew install xcodegen swiftlint swiftformat"); for (const lintStep of [macosLintStep, iosLintStep]) { expect(lintStep.run).toContain( "if [[ -x ./scripts/lint-swift.sh && -x ./scripts/format-swift.sh ]]; then", @@ -1828,7 +1830,18 @@ describe("ci workflow guards", () => { const swiftLint = workflow.jobs["macos-swift"].steps.find( (step: { name?: string }) => step.name === "Swift lint", ); - expect(swiftInstall.run).toContain("brew install xcodegen swiftlint swiftformat"); + expect(swiftInstall.run).toContain("brew install xcodegen swiftlint"); + expect(swiftInstall.run).not.toContain("brew install xcodegen swiftlint swiftformat"); + expect(swiftInstall.run).toContain( + "https://github.com/nicklockwood/SwiftFormat/releases/download/$swiftformat_version/swiftformat.zip", + ); + expect(swiftInstall.run).toContain( + 'swiftformat_checksum="b990400779aceb7d7020796eb9ba814d4480543f671d38fc0ff48cb72f04c584"', + ); + expect(swiftInstall.run).toContain('echo "$swift_tools_dir" >> "$GITHUB_PATH"'); + expect(swiftInstall.run).toContain( + '[[ "$("$swift_tools_dir/swiftformat" --version)" == "$swiftformat_version" ]]', + ); expect(workflow.jobs["macos-swift"].env.HISTORICAL_TARGET).toBe( "${{ needs.preflight.outputs.compatibility_target }}", ); @@ -1845,6 +1858,16 @@ describe("ci workflow guards", () => { expect(checkShard.run).toContain("pnpm tsgo:scripts"); expect(checkShard.run).toContain("pnpm tsgo:strict-ratchet"); expect(checkShard.run).toContain('elif [[ "$HISTORICAL_TARGET" != "true" ]]'); + + const uiTest = workflow.jobs["checks-ui"].steps.find( + (step: { name?: string }) => step.name === "Test Control UI", + ); + expect(workflow.jobs["checks-ui"].env.COMPATIBILITY_TARGET).toBe( + "${{ needs.preflight.outputs.compatibility_target }}", + ); + expect(uiTest.run).toContain('if [[ "$COMPATIBILITY_TARGET" == "true" ]]'); + expect(uiTest.run).toContain("pnpm --dir ui test -- --retry=1"); + expect(uiTest.run).toContain("pnpm --dir ui test"); }); it("does not rebuild Control UI after build:ci-artifacts", () => {