fix: preserve registered glm-5 variants (#48185) (thanks @haoyu-haoyu) #10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| types: [opened, reopened, synchronize, ready_for_review, converted_to_draft] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.event_name == 'pull_request' && format('{0}-{1}', github.workflow, github.event.pull_request.number) || format('{0}-{1}', github.workflow, github.run_id) }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| jobs: | |
| # Preflight: establish routing truth and job matrices once, then let real | |
| # work fan out from a single source of truth. | |
| preflight: | |
| if: github.event_name != 'pull_request' || !github.event.pull_request.draft | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 20 | |
| outputs: | |
| docs_only: ${{ steps.manifest.outputs.docs_only }} | |
| docs_changed: ${{ steps.manifest.outputs.docs_changed }} | |
| run_node: ${{ steps.manifest.outputs.run_node }} | |
| run_macos: ${{ steps.manifest.outputs.run_macos }} | |
| run_android: ${{ steps.manifest.outputs.run_android }} | |
| run_skills_python: ${{ steps.manifest.outputs.run_skills_python }} | |
| run_skills_python_job: ${{ steps.manifest.outputs.run_skills_python_job }} | |
| run_windows: ${{ steps.manifest.outputs.run_windows }} | |
| has_changed_extensions: ${{ steps.manifest.outputs.has_changed_extensions }} | |
| changed_extensions_matrix: ${{ steps.manifest.outputs.changed_extensions_matrix }} | |
| run_build_artifacts: ${{ steps.manifest.outputs.run_build_artifacts }} | |
| run_checks_fast: ${{ steps.manifest.outputs.run_checks_fast }} | |
| checks_fast_core_matrix: ${{ steps.manifest.outputs.checks_fast_core_matrix }} | |
| checks_fast_extensions_matrix: ${{ steps.manifest.outputs.checks_fast_extensions_matrix }} | |
| run_checks: ${{ steps.manifest.outputs.run_checks }} | |
| checks_matrix: ${{ steps.manifest.outputs.checks_matrix }} | |
| run_extension_fast: ${{ steps.manifest.outputs.run_extension_fast }} | |
| extension_fast_matrix: ${{ steps.manifest.outputs.extension_fast_matrix }} | |
| run_check: ${{ steps.manifest.outputs.run_check }} | |
| run_check_additional: ${{ steps.manifest.outputs.run_check_additional }} | |
| run_build_smoke: ${{ steps.manifest.outputs.run_build_smoke }} | |
| run_check_docs: ${{ steps.manifest.outputs.run_check_docs }} | |
| run_checks_windows: ${{ steps.manifest.outputs.run_checks_windows }} | |
| checks_windows_matrix: ${{ steps.manifest.outputs.checks_windows_matrix }} | |
| run_macos_node: ${{ steps.manifest.outputs.run_macos_node }} | |
| macos_node_matrix: ${{ steps.manifest.outputs.macos_node_matrix }} | |
| run_macos_swift: ${{ steps.manifest.outputs.run_macos_swift }} | |
| run_android_job: ${{ steps.manifest.outputs.run_android_job }} | |
| android_matrix: ${{ steps.manifest.outputs.android_matrix }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 1 | |
| fetch-tags: false | |
| persist-credentials: false | |
| submodules: false | |
| - name: Ensure preflight base commit | |
| uses: ./.github/actions/ensure-base-commit | |
| with: | |
| base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }} | |
| fetch-ref: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }} | |
| - name: Detect docs-only changes | |
| id: docs_scope | |
| uses: ./.github/actions/detect-docs-changes | |
| - name: Detect changed scopes | |
| id: changed_scope | |
| if: steps.docs_scope.outputs.docs_only != 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ github.event_name }}" = "push" ]; then | |
| BASE="${{ github.event.before }}" | |
| else | |
| BASE="${{ github.event.pull_request.base.sha }}" | |
| fi | |
| node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD | |
| - name: Setup Node environment | |
| if: steps.docs_scope.outputs.docs_only != 'true' | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| install-bun: "false" | |
| install-deps: "false" | |
| use-sticky-disk: "false" | |
| - name: Detect changed extensions | |
| id: changed_extensions | |
| if: steps.docs_scope.outputs.docs_only != 'true' && steps.changed_scope.outputs.run_node == 'true' | |
| env: | |
| BASE_SHA: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }} | |
| BASE_REF: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }} | |
| run: | | |
| node --input-type=module <<'EOF' | |
| import { appendFileSync } from "node:fs"; | |
| import { listChangedExtensionIds } from "./scripts/lib/changed-extensions.mjs"; | |
| const extensionIds = listChangedExtensionIds({ | |
| base: process.env.BASE_SHA, | |
| head: "HEAD", | |
| fallbackBaseRef: process.env.BASE_REF, | |
| unavailableBaseBehavior: "all", | |
| }); | |
| const matrix = JSON.stringify({ include: extensionIds.map((extension) => ({ extension })) }); | |
| appendFileSync(process.env.GITHUB_OUTPUT, `has_changed_extensions=${extensionIds.length > 0}\n`, "utf8"); | |
| appendFileSync(process.env.GITHUB_OUTPUT, `changed_extensions_matrix=${matrix}\n`, "utf8"); | |
| EOF | |
| - name: Build CI manifest | |
| id: manifest | |
| env: | |
| OPENCLAW_CI_DOCS_ONLY: ${{ steps.docs_scope.outputs.docs_only }} | |
| OPENCLAW_CI_DOCS_CHANGED: ${{ steps.docs_scope.outputs.docs_changed }} | |
| OPENCLAW_CI_RUN_NODE: ${{ steps.changed_scope.outputs.run_node || 'false' }} | |
| OPENCLAW_CI_RUN_MACOS: ${{ steps.changed_scope.outputs.run_macos || 'false' }} | |
| OPENCLAW_CI_RUN_ANDROID: ${{ steps.changed_scope.outputs.run_android || 'false' }} | |
| OPENCLAW_CI_RUN_WINDOWS: ${{ steps.changed_scope.outputs.run_windows || 'false' }} | |
| OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ steps.changed_scope.outputs.run_skills_python || 'false' }} | |
| OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: ${{ steps.changed_extensions.outputs.has_changed_extensions || 'false' }} | |
| OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: ${{ steps.changed_extensions.outputs.changed_extensions_matrix || '{"include":[]}' }} | |
| run: | | |
| node --input-type=module <<'EOF' | |
| import { appendFileSync } from "node:fs"; | |
| import { | |
| createExtensionTestShards, | |
| DEFAULT_EXTENSION_TEST_SHARD_COUNT, | |
| } from "./scripts/lib/extension-test-plan.mjs"; | |
| const parseBoolean = (value, fallback = false) => { | |
| if (value === undefined) return fallback; | |
| const normalized = value.trim().toLowerCase(); | |
| if (normalized === "true" || normalized === "1") return true; | |
| if (normalized === "false" || normalized === "0" || normalized === "") return false; | |
| return fallback; | |
| }; | |
| const parseJson = (value, fallback) => { | |
| try { | |
| return value ? JSON.parse(value) : fallback; | |
| } catch { | |
| return fallback; | |
| } | |
| }; | |
| const createMatrix = (include) => ({ include }); | |
| const outputPath = process.env.GITHUB_OUTPUT; | |
| const eventName = process.env.GITHUB_EVENT_NAME ?? "pull_request"; | |
| const isPush = eventName === "push"; | |
| const docsOnly = parseBoolean(process.env.OPENCLAW_CI_DOCS_ONLY); | |
| const docsChanged = parseBoolean(process.env.OPENCLAW_CI_DOCS_CHANGED); | |
| const runNode = parseBoolean(process.env.OPENCLAW_CI_RUN_NODE) && !docsOnly; | |
| const runMacos = parseBoolean(process.env.OPENCLAW_CI_RUN_MACOS) && !docsOnly; | |
| const runAndroid = parseBoolean(process.env.OPENCLAW_CI_RUN_ANDROID) && !docsOnly; | |
| const runWindows = parseBoolean(process.env.OPENCLAW_CI_RUN_WINDOWS) && !docsOnly; | |
| const runSkillsPython = parseBoolean(process.env.OPENCLAW_CI_RUN_SKILLS_PYTHON) && !docsOnly; | |
| const hasChangedExtensions = | |
| parseBoolean(process.env.OPENCLAW_CI_HAS_CHANGED_EXTENSIONS) && !docsOnly; | |
| const changedExtensionsMatrix = hasChangedExtensions | |
| ? parseJson(process.env.OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX, { include: [] }) | |
| : { include: [] }; | |
| const extensionShardMatrix = createMatrix( | |
| runNode | |
| ? createExtensionTestShards({ | |
| shardCount: DEFAULT_EXTENSION_TEST_SHARD_COUNT, | |
| }).map((shard) => ({ | |
| check_name: shard.checkName, | |
| extensions_csv: shard.extensionIds.join(","), | |
| shard_index: shard.index + 1, | |
| task: "extensions-batch", | |
| })) | |
| : [], | |
| ); | |
| const manifest = { | |
| docs_only: docsOnly, | |
| docs_changed: docsChanged, | |
| run_node: runNode, | |
| run_macos: runMacos, | |
| run_android: runAndroid, | |
| run_skills_python: runSkillsPython, | |
| run_windows: runWindows, | |
| has_changed_extensions: hasChangedExtensions, | |
| changed_extensions_matrix: changedExtensionsMatrix, | |
| run_build_artifacts: runNode, | |
| run_checks_fast: runNode, | |
| checks_fast_core_matrix: createMatrix( | |
| runNode | |
| ? [ | |
| { check_name: "checks-fast-bundled", runtime: "node", task: "bundled" }, | |
| { | |
| check_name: "checks-fast-contracts-protocol", | |
| runtime: "node", | |
| task: "contracts-protocol", | |
| }, | |
| ] | |
| : [], | |
| ), | |
| checks_fast_extensions_matrix: extensionShardMatrix, | |
| run_checks: runNode, | |
| checks_matrix: createMatrix( | |
| runNode | |
| ? [ | |
| { check_name: "checks-node-test", runtime: "node", task: "test" }, | |
| { check_name: "checks-node-channels", runtime: "node", task: "channels" }, | |
| ...(isPush | |
| ? [ | |
| { | |
| check_name: "checks-node-compat-node22", | |
| runtime: "node", | |
| task: "compat-node22", | |
| node_version: "22.x", | |
| cache_key_suffix: "node22", | |
| }, | |
| ] | |
| : []), | |
| ] | |
| : [], | |
| ), | |
| run_extension_fast: hasChangedExtensions, | |
| extension_fast_matrix: createMatrix( | |
| hasChangedExtensions | |
| ? (changedExtensionsMatrix.include ?? []).map((entry) => ({ | |
| check_name: `extension-fast-${entry.extension}`, | |
| extension: entry.extension, | |
| })) | |
| : [], | |
| ), | |
| run_check: runNode, | |
| run_check_additional: runNode, | |
| run_build_smoke: runNode, | |
| run_check_docs: docsChanged, | |
| run_skills_python_job: runSkillsPython, | |
| run_checks_windows: runWindows, | |
| checks_windows_matrix: createMatrix( | |
| runWindows | |
| ? [{ check_name: "checks-windows-node-test", runtime: "node", task: "test" }] | |
| : [], | |
| ), | |
| run_macos_node: runMacos, | |
| macos_node_matrix: createMatrix( | |
| runMacos ? [{ check_name: "macos-node", runtime: "node", task: "test" }] : [], | |
| ), | |
| run_macos_swift: runMacos, | |
| run_android_job: runAndroid, | |
| android_matrix: createMatrix( | |
| runAndroid | |
| ? [ | |
| { check_name: "android-test-play", task: "test-play" }, | |
| { check_name: "android-test-third-party", task: "test-third-party" }, | |
| { check_name: "android-build-play", task: "build-play" }, | |
| { check_name: "android-build-third-party", task: "build-third-party" }, | |
| ] | |
| : [], | |
| ), | |
| }; | |
| for (const [key, value] of Object.entries(manifest)) { | |
| appendFileSync( | |
| outputPath, | |
| `${key}=${typeof value === "string" ? value : JSON.stringify(value)}\n`, | |
| "utf8", | |
| ); | |
| } | |
| EOF | |
| # Run the fast security/SCM checks in parallel with scope detection so the | |
| # main Node jobs do not have to wait for Python/pre-commit setup. | |
| security-fast: | |
| if: github.event_name != 'pull_request' || !github.event.pull_request.draft | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 20 | |
| env: | |
| PRE_COMMIT_CACHE_KEY_SUFFIX: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.sha }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 1 | |
| fetch-tags: false | |
| persist-credentials: false | |
| submodules: false | |
| - name: Ensure security base commit | |
| uses: ./.github/actions/ensure-base-commit | |
| with: | |
| base-sha: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }} | |
| fetch-ref: ${{ github.event_name == 'push' && github.ref_name || github.event.pull_request.base.ref }} | |
| - name: Prepare trusted pre-commit config | |
| if: github.event_name == 'pull_request' | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| run: | | |
| set -euo pipefail | |
| trusted_config="$RUNNER_TEMP/pre-commit-base.yaml" | |
| git show "${BASE_SHA}:.pre-commit-config.yaml" > "$trusted_config" | |
| echo "PRE_COMMIT_CONFIG_PATH=$trusted_config" >> "$GITHUB_ENV" | |
| - name: Setup Node environment | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| install-bun: "false" | |
| install-deps: "false" | |
| use-sticky-disk: "false" | |
| - name: Setup Python | |
| id: setup-python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| cache: "pip" | |
| cache-dependency-path: | | |
| pyproject.toml | |
| .pre-commit-config.yaml | |
| .github/workflows/ci.yml | |
| - name: Restore pre-commit cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cache/pre-commit | |
| key: pre-commit-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}-${{ env.PRE_COMMIT_CACHE_KEY_SUFFIX }} | |
| restore-keys: | | |
| pre-commit-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}- | |
| - name: Install pre-commit | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install pre-commit==4.2.0 | |
| - name: Detect committed private keys | |
| run: pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" --all-files detect-private-key | |
| - name: Audit changed GitHub workflows with zizmor | |
| env: | |
| BASE_SHA: ${{ github.event_name == 'push' && github.event.before || github.event.pull_request.base.sha }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${BASE_SHA:-}" ] || [ "${BASE_SHA}" = "0000000000000000000000000000000000000000" ]; then | |
| echo "No usable base SHA detected; skipping zizmor." | |
| exit 0 | |
| fi | |
| if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then | |
| echo "Base SHA ${BASE_SHA} is unavailable; skipping zizmor." | |
| exit 0 | |
| fi | |
| mapfile -t workflow_files < <( | |
| git diff --name-only "${BASE_SHA}" HEAD -- '.github/workflows/*.yml' '.github/workflows/*.yaml' | |
| ) | |
| if [ "${#workflow_files[@]}" -eq 0 ]; then | |
| echo "No workflow changes detected; skipping zizmor." | |
| exit 0 | |
| fi | |
| printf 'Auditing workflow files:\n%s\n' "${workflow_files[@]}" | |
| pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" zizmor --files "${workflow_files[@]}" | |
| - name: Audit production dependencies | |
| run: pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" --all-files pnpm-audit-prod | |
| # Build dist once for Node-relevant changes and share it with downstream jobs. | |
| # Keep this overlapping with the fast correctness lanes so green PRs get heavy | |
| # test/build feedback sooner instead of waiting behind a full `check` pass. | |
| build-artifacts: | |
| needs: [preflight] | |
| if: needs.preflight.outputs.run_build_artifacts == 'true' | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Ensure secrets base commit (PR fast path) | |
| if: github.event_name == 'pull_request' | |
| uses: ./.github/actions/ensure-base-commit | |
| with: | |
| base-sha: ${{ github.event.pull_request.base.sha }} | |
| fetch-ref: ${{ github.event.pull_request.base.ref }} | |
| - name: Setup Node environment | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| install-bun: "false" | |
| use-sticky-disk: "false" | |
| - name: Build dist | |
| run: pnpm build | |
| - name: Build Control UI | |
| run: pnpm ui:build | |
| - name: Upload dist artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: dist-build | |
| path: dist/ | |
| retention-days: 1 | |
| - name: Upload A2UI bundle artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: canvas-a2ui-bundle | |
| path: src/canvas-host/a2ui/ | |
| include-hidden-files: true | |
| retention-days: 1 | |
| checks-fast-core: | |
| name: ${{ matrix.check_name }} | |
| needs: [preflight] | |
| if: needs.preflight.outputs.run_checks_fast == 'true' | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_core_matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Setup Node environment | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| install-bun: "false" | |
| use-sticky-disk: "false" | |
| - name: Run ${{ matrix.task }} (${{ matrix.runtime }}) | |
| env: | |
| TASK: ${{ matrix.task }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| case "$TASK" in | |
| bundled) | |
| pnpm test:bundled | |
| ;; | |
| contracts|contracts-protocol) | |
| pnpm build | |
| pnpm test:contracts | |
| pnpm protocol:check | |
| ;; | |
| *) | |
| echo "Unsupported checks-fast task: $TASK" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| checks-fast-extensions-shard: | |
| name: ${{ matrix.check_name }} | |
| needs: [preflight] | |
| if: needs.preflight.outputs.run_checks_fast == 'true' | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_extensions_matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Setup Node environment | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| install-bun: "false" | |
| use-sticky-disk: "false" | |
| - name: Run extension shard | |
| env: | |
| OPENCLAW_EXTENSION_BATCH: ${{ matrix.extensions_csv }} | |
| run: pnpm test:extensions:batch -- "$OPENCLAW_EXTENSION_BATCH" | |
| checks-fast-extensions: | |
| name: checks-fast-extensions | |
| needs: [preflight, checks-fast-extensions-shard] | |
| if: always() && needs.preflight.outputs.run_checks_fast == 'true' | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Verify extension shards | |
| env: | |
| SHARD_RESULT: ${{ needs.checks-fast-extensions-shard.result }} | |
| run: | | |
| if [ "$SHARD_RESULT" != "success" ]; then | |
| echo "Extension shard checks failed: $SHARD_RESULT" >&2 | |
| exit 1 | |
| fi | |
| checks: | |
| name: ${{ matrix.check_name }} | |
| needs: [preflight, build-artifacts] | |
| if: always() && needs.preflight.outputs.run_checks == 'true' && needs.build-artifacts.result == 'success' | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.preflight.outputs.checks_matrix) }} | |
| steps: | |
| - name: Skip compatibility lanes on pull requests | |
| if: github.event_name == 'pull_request' && matrix.task == 'compat-node22' | |
| run: echo "Skipping push-only lane on pull requests." | |
| - name: Checkout | |
| if: github.event_name != 'pull_request' || matrix.task != 'compat-node22' | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Setup Node environment | |
| if: github.event_name != 'pull_request' || matrix.task != 'compat-node22' | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| node-version: "${{ matrix.node_version || '24.x' }}" | |
| cache-key-suffix: "${{ matrix.cache_key_suffix || 'node24' }}" | |
| install-bun: "false" | |
| use-sticky-disk: "false" | |
| - name: Configure Node test resources | |
| if: (github.event_name != 'pull_request' || matrix.task != 'compat-node22') && matrix.runtime == 'node' && (matrix.task == 'test' || matrix.task == 'channels' || matrix.task == 'compat-node22') | |
| env: | |
| TASK: ${{ matrix.task }} | |
| run: | | |
| echo "OPENCLAW_VITEST_MAX_WORKERS=2" >> "$GITHUB_ENV" | |
| if [ "$TASK" = "channels" ]; then | |
| echo "OPENCLAW_VITEST_MAX_WORKERS=1" >> "$GITHUB_ENV" | |
| fi | |
| - name: Download dist artifact | |
| if: matrix.task == 'test' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: dist-build | |
| path: dist/ | |
| - name: Download A2UI bundle artifact | |
| if: matrix.task == 'test' || matrix.task == 'channels' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: canvas-a2ui-bundle | |
| path: src/canvas-host/a2ui/ | |
| - name: Run ${{ matrix.task }} (${{ matrix.runtime }}) | |
| if: github.event_name != 'pull_request' || matrix.task != 'compat-node22' | |
| env: | |
| TASK: ${{ matrix.task }} | |
| NODE_OPTIONS: --max-old-space-size=6144 | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| case "$TASK" in | |
| test) | |
| pnpm test | |
| ;; | |
| channels) | |
| pnpm test:channels | |
| ;; | |
| compat-node22) | |
| pnpm build | |
| pnpm ui:build | |
| node openclaw.mjs --help | |
| node openclaw.mjs status --json --timeout 1 | |
| pnpm test:build:singleton | |
| ;; | |
| *) | |
| echo "Unsupported checks task: $TASK" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| extension-fast: | |
| name: "extension-fast" | |
| needs: [preflight] | |
| if: needs.preflight.outputs.run_extension_fast == 'true' | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.preflight.outputs.extension_fast_matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Setup Node environment | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| install-bun: "false" | |
| use-sticky-disk: "false" | |
| - name: Run changed extension tests | |
| env: | |
| OPENCLAW_CHANGED_EXTENSION: ${{ matrix.extension }} | |
| run: pnpm test:extension "$OPENCLAW_CHANGED_EXTENSION" | |
| # Types, lint, and format check. | |
| check: | |
| name: "check" | |
| needs: [preflight] | |
| if: always() && needs.preflight.outputs.run_check == 'true' | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Setup Node environment | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| install-bun: "false" | |
| use-sticky-disk: "false" | |
| - name: Check types and lint and oxfmt | |
| env: | |
| OPENCLAW_LOCAL_CHECK: "0" | |
| run: pnpm check | |
| - name: Strict TS build smoke | |
| run: pnpm build:strict-smoke | |
| check-additional: | |
| name: "check-additional" | |
| needs: [preflight] | |
| if: always() && needs.preflight.outputs.run_check_additional == 'true' | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Setup Node environment | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| install-bun: "false" | |
| use-sticky-disk: "false" | |
| - name: Run plugin extension boundary guard | |
| id: plugin_extension_boundary | |
| continue-on-error: true | |
| run: pnpm run lint:plugins:no-extension-imports | |
| - name: Run no-random-messaging guard | |
| id: no_random_messaging | |
| continue-on-error: true | |
| run: pnpm run lint:tmp:no-random-messaging | |
| - name: Run channel-agnostic boundary guard | |
| id: channel_agnostic_boundaries | |
| continue-on-error: true | |
| run: pnpm run lint:tmp:channel-agnostic-boundaries | |
| - name: Run no-raw-channel-fetch guard | |
| id: no_raw_channel_fetch | |
| continue-on-error: true | |
| run: pnpm run lint:tmp:no-raw-channel-fetch | |
| - name: Run ingress owner guard | |
| id: ingress_owner | |
| continue-on-error: true | |
| run: pnpm run lint:agent:ingress-owner | |
| - name: Run no-register-http-handler guard | |
| id: no_register_http_handler | |
| continue-on-error: true | |
| run: pnpm run lint:plugins:no-register-http-handler | |
| - name: Run no-monolithic plugin-sdk entry import guard | |
| id: no_monolithic_plugin_sdk_entry_imports | |
| continue-on-error: true | |
| run: pnpm run lint:plugins:no-monolithic-plugin-sdk-entry-imports | |
| - name: Run no-extension-src-imports guard | |
| id: no_extension_src_imports | |
| continue-on-error: true | |
| run: pnpm run lint:plugins:no-extension-src-imports | |
| - name: Run no-extension-test-core-imports guard | |
| id: no_extension_test_core_imports | |
| continue-on-error: true | |
| run: pnpm run lint:plugins:no-extension-test-core-imports | |
| - name: Run plugin-sdk subpaths exported guard | |
| id: plugin_sdk_subpaths_exported | |
| continue-on-error: true | |
| run: pnpm run lint:plugins:plugin-sdk-subpaths-exported | |
| - name: Run web search provider boundary guard | |
| id: web_search_provider_boundary | |
| continue-on-error: true | |
| run: pnpm run lint:web-search-provider-boundaries | |
| - name: Run web fetch provider boundary guard | |
| id: web_fetch_provider_boundary | |
| continue-on-error: true | |
| run: pnpm run lint:web-fetch-provider-boundaries | |
| - name: Run extension src boundary guard | |
| id: extension_src_outside_plugin_sdk_boundary | |
| continue-on-error: true | |
| run: pnpm run lint:extensions:no-src-outside-plugin-sdk | |
| - name: Run extension plugin-sdk-internal guard | |
| id: extension_plugin_sdk_internal_boundary | |
| continue-on-error: true | |
| run: pnpm run lint:extensions:no-plugin-sdk-internal | |
| - name: Run extension relative-outside-package guard | |
| id: extension_relative_outside_package_boundary | |
| continue-on-error: true | |
| run: pnpm run lint:extensions:no-relative-outside-package | |
| - name: Enforce safe external URL opening policy | |
| id: no_raw_window_open | |
| continue-on-error: true | |
| run: pnpm lint:ui:no-raw-window-open | |
| - name: Run gateway watch regression harness | |
| id: gateway_watch_regression | |
| continue-on-error: true | |
| run: pnpm test:gateway:watch-regression | |
| - name: Upload gateway watch regression artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: gateway-watch-regression | |
| path: .local/gateway-watch-regression/ | |
| retention-days: 7 | |
| - name: Fail if any additional check failed | |
| if: always() | |
| env: | |
| PLUGIN_EXTENSION_BOUNDARY_OUTCOME: ${{ steps.plugin_extension_boundary.outcome }} | |
| NO_RANDOM_MESSAGING_OUTCOME: ${{ steps.no_random_messaging.outcome }} | |
| CHANNEL_AGNOSTIC_BOUNDARIES_OUTCOME: ${{ steps.channel_agnostic_boundaries.outcome }} | |
| NO_RAW_CHANNEL_FETCH_OUTCOME: ${{ steps.no_raw_channel_fetch.outcome }} | |
| INGRESS_OWNER_OUTCOME: ${{ steps.ingress_owner.outcome }} | |
| NO_REGISTER_HTTP_HANDLER_OUTCOME: ${{ steps.no_register_http_handler.outcome }} | |
| NO_MONOLITHIC_PLUGIN_SDK_ENTRY_IMPORTS_OUTCOME: ${{ steps.no_monolithic_plugin_sdk_entry_imports.outcome }} | |
| NO_EXTENSION_SRC_IMPORTS_OUTCOME: ${{ steps.no_extension_src_imports.outcome }} | |
| NO_EXTENSION_TEST_CORE_IMPORTS_OUTCOME: ${{ steps.no_extension_test_core_imports.outcome }} | |
| PLUGIN_SDK_SUBPATHS_EXPORTED_OUTCOME: ${{ steps.plugin_sdk_subpaths_exported.outcome }} | |
| WEB_SEARCH_PROVIDER_BOUNDARY_OUTCOME: ${{ steps.web_search_provider_boundary.outcome }} | |
| WEB_FETCH_PROVIDER_BOUNDARY_OUTCOME: ${{ steps.web_fetch_provider_boundary.outcome }} | |
| EXTENSION_SRC_OUTSIDE_PLUGIN_SDK_BOUNDARY_OUTCOME: ${{ steps.extension_src_outside_plugin_sdk_boundary.outcome }} | |
| EXTENSION_PLUGIN_SDK_INTERNAL_BOUNDARY_OUTCOME: ${{ steps.extension_plugin_sdk_internal_boundary.outcome }} | |
| EXTENSION_RELATIVE_OUTSIDE_PACKAGE_BOUNDARY_OUTCOME: ${{ steps.extension_relative_outside_package_boundary.outcome }} | |
| NO_RAW_WINDOW_OPEN_OUTCOME: ${{ steps.no_raw_window_open.outcome }} | |
| GATEWAY_WATCH_REGRESSION_OUTCOME: ${{ steps.gateway_watch_regression.outcome }} | |
| run: | | |
| failures=0 | |
| for result in \ | |
| "plugin-extension-boundary|$PLUGIN_EXTENSION_BOUNDARY_OUTCOME" \ | |
| "lint:tmp:no-random-messaging|$NO_RANDOM_MESSAGING_OUTCOME" \ | |
| "lint:tmp:channel-agnostic-boundaries|$CHANNEL_AGNOSTIC_BOUNDARIES_OUTCOME" \ | |
| "lint:tmp:no-raw-channel-fetch|$NO_RAW_CHANNEL_FETCH_OUTCOME" \ | |
| "lint:agent:ingress-owner|$INGRESS_OWNER_OUTCOME" \ | |
| "lint:plugins:no-register-http-handler|$NO_REGISTER_HTTP_HANDLER_OUTCOME" \ | |
| "lint:plugins:no-monolithic-plugin-sdk-entry-imports|$NO_MONOLITHIC_PLUGIN_SDK_ENTRY_IMPORTS_OUTCOME" \ | |
| "lint:plugins:no-extension-src-imports|$NO_EXTENSION_SRC_IMPORTS_OUTCOME" \ | |
| "lint:plugins:no-extension-test-core-imports|$NO_EXTENSION_TEST_CORE_IMPORTS_OUTCOME" \ | |
| "lint:plugins:plugin-sdk-subpaths-exported|$PLUGIN_SDK_SUBPATHS_EXPORTED_OUTCOME" \ | |
| "web-search-provider-boundary|$WEB_SEARCH_PROVIDER_BOUNDARY_OUTCOME" \ | |
| "web-fetch-provider-boundary|$WEB_FETCH_PROVIDER_BOUNDARY_OUTCOME" \ | |
| "extension-src-outside-plugin-sdk-boundary|$EXTENSION_SRC_OUTSIDE_PLUGIN_SDK_BOUNDARY_OUTCOME" \ | |
| "extension-plugin-sdk-internal-boundary|$EXTENSION_PLUGIN_SDK_INTERNAL_BOUNDARY_OUTCOME" \ | |
| "extension-relative-outside-package-boundary|$EXTENSION_RELATIVE_OUTSIDE_PACKAGE_BOUNDARY_OUTCOME" \ | |
| "lint:ui:no-raw-window-open|$NO_RAW_WINDOW_OPEN_OUTCOME" \ | |
| "gateway-watch-regression|$GATEWAY_WATCH_REGRESSION_OUTCOME"; do | |
| name="${result%%|*}" | |
| outcome="${result#*|}" | |
| if [ "$outcome" != "success" ]; then | |
| echo "::error title=${name} failed::${name} outcome: ${outcome}" | |
| failures=1 | |
| fi | |
| done | |
| exit "$failures" | |
| build-smoke: | |
| name: "build-smoke" | |
| needs: [preflight, build-artifacts] | |
| if: always() && needs.preflight.outputs.run_build_smoke == 'true' && (github.event_name != 'push' || needs.build-artifacts.result == 'success') | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Setup Node environment | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| install-bun: "false" | |
| use-sticky-disk: "false" | |
| - name: Download dist artifact | |
| if: github.event_name == 'push' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: dist-build | |
| path: dist/ | |
| - name: Build dist | |
| if: github.event_name != 'push' | |
| run: pnpm build | |
| - name: Smoke test CLI launcher help | |
| run: node openclaw.mjs --help | |
| - name: Smoke test CLI launcher status json | |
| run: node openclaw.mjs status --json --timeout 1 | |
| - name: Smoke test built bundled plugin singleton | |
| run: pnpm test:build:singleton | |
| - name: Check CLI startup memory | |
| run: pnpm test:startup:memory | |
| # Validate docs (format, lint, broken links) only when docs files changed. | |
| check-docs: | |
| needs: [preflight] | |
| if: needs.preflight.outputs.run_check_docs == 'true' | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Setup Node environment | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| install-bun: "false" | |
| use-sticky-disk: "false" | |
| - name: Check docs | |
| run: pnpm check:docs | |
| skills-python: | |
| needs: [preflight] | |
| if: needs.preflight.outputs.run_skills_python_job == 'true' | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Setup Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install Python tooling | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install pytest ruff pyyaml | |
| - name: Lint Python skill scripts | |
| run: python -m ruff check skills | |
| - name: Test skill Python scripts | |
| run: python -m pytest -q skills | |
| checks-windows: | |
| name: ${{ matrix.check_name }} | |
| needs: [preflight, build-artifacts] | |
| if: always() && needs.preflight.outputs.run_checks_windows == 'true' && needs.build-artifacts.result == 'success' | |
| runs-on: blacksmith-32vcpu-windows-2025 | |
| timeout-minutes: 60 | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=6144 | |
| # Keep total concurrency predictable on the 32 vCPU runner. | |
| OPENCLAW_VITEST_MAX_WORKERS: 1 | |
| defaults: | |
| run: | |
| shell: bash | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.preflight.outputs.checks_windows_matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Try to exclude workspace from Windows Defender (best-effort) | |
| shell: pwsh | |
| run: | | |
| $cmd = Get-Command Add-MpPreference -ErrorAction SilentlyContinue | |
| if (-not $cmd) { | |
| Write-Host "Add-MpPreference not available, skipping Defender exclusions." | |
| exit 0 | |
| } | |
| try { | |
| # Defender sometimes intercepts process spawning (vitest workers). If this fails | |
| # (eg hardened images), keep going and rely on worker limiting above. | |
| Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE" -ErrorAction Stop | |
| Add-MpPreference -ExclusionProcess "node.exe" -ErrorAction Stop | |
| Write-Host "Defender exclusions applied." | |
| } catch { | |
| Write-Warning "Failed to apply Defender exclusions, continuing. $($_.Exception.Message)" | |
| } | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: 24.x | |
| check-latest: false | |
| - name: Setup pnpm + cache store | |
| uses: ./.github/actions/setup-pnpm-store-cache | |
| with: | |
| pnpm-version: "10.32.1" | |
| cache-key-suffix: "node24" | |
| # Sticky disk mount currently retries/fails on every shard and adds ~50s | |
| # before install while still yielding zero pnpm store reuse. | |
| # Try exact-key actions/cache restores instead to recover store reuse | |
| # without the sticky-disk mount penalty. | |
| use-sticky-disk: "false" | |
| use-restore-keys: "false" | |
| use-actions-cache: "true" | |
| - name: Runtime versions | |
| run: | | |
| node -v | |
| npm -v | |
| pnpm -v | |
| - name: Capture node path | |
| run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV" | |
| - name: Install dependencies | |
| env: | |
| CI: true | |
| run: | | |
| export PATH="$NODE_BIN:$PATH" | |
| which node | |
| node -v | |
| pnpm -v | |
| # Persist Windows-native postinstall outputs in the pnpm store so restored | |
| # caches can skip repeated rebuild/download work on later shards/runs. | |
| pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true || pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true | |
| - name: Download dist artifact | |
| if: matrix.task == 'test' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: dist-build | |
| path: dist/ | |
| - name: Download A2UI bundle artifact | |
| if: matrix.task == 'test' | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: canvas-a2ui-bundle | |
| path: src/canvas-host/a2ui/ | |
| - name: Run ${{ matrix.task }} (${{ matrix.runtime }}) | |
| env: | |
| TASK: ${{ matrix.task }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| case "$TASK" in | |
| test) | |
| pnpm test | |
| ;; | |
| *) | |
| echo "Unsupported Windows checks task: $TASK" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| macos-node: | |
| name: ${{ matrix.check_name }} | |
| needs: [preflight, build-artifacts] | |
| if: always() && needs.preflight.outputs.run_macos_node == 'true' && needs.build-artifacts.result == 'success' | |
| runs-on: macos-latest | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.preflight.outputs.macos_node_matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Setup Node environment | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| install-bun: "false" | |
| - name: Download dist artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: dist-build | |
| path: dist/ | |
| - name: Download A2UI bundle artifact | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: canvas-a2ui-bundle | |
| path: src/canvas-host/a2ui/ | |
| - name: TS tests (macOS) | |
| env: | |
| NODE_OPTIONS: --max-old-space-size=4096 | |
| OPENCLAW_VITEST_MAX_WORKERS: 2 | |
| TASK: ${{ matrix.task }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| case "$TASK" in | |
| test) | |
| pnpm test | |
| ;; | |
| *) | |
| echo "Unsupported macOS node task: $TASK" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| macos-swift: | |
| name: "macos-swift" | |
| needs: [preflight] | |
| if: needs.preflight.outputs.run_macos_swift == 'true' | |
| runs-on: macos-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Select Xcode 26.1 | |
| run: | | |
| sudo xcode-select -s /Applications/Xcode_26.1.app | |
| xcodebuild -version | |
| - name: Install XcodeGen / SwiftLint / SwiftFormat | |
| run: brew install xcodegen swiftlint swiftformat | |
| - name: Cache SwiftPM | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/Library/Caches/org.swift.swiftpm | |
| key: ${{ runner.os }}-swiftpm-${{ hashFiles('apps/macos/Package.resolved') }} | |
| restore-keys: | | |
| ${{ runner.os }}-swiftpm- | |
| - name: Show toolchain | |
| run: | | |
| sw_vers | |
| xcodebuild -version | |
| swift --version | |
| - name: Swift lint | |
| run: | | |
| swiftlint --config .swiftlint.yml | |
| swiftformat --lint apps/macos/Sources --config .swiftformat | |
| - name: Swift build (release) | |
| run: | | |
| set -euo pipefail | |
| for attempt in 1 2 3; do | |
| if swift build --package-path apps/macos --configuration release; then | |
| exit 0 | |
| fi | |
| echo "swift build failed (attempt $attempt/3). Retrying…" | |
| sleep $((attempt * 20)) | |
| done | |
| exit 1 | |
| - name: Swift test | |
| run: | | |
| set -euo pipefail | |
| for attempt in 1 2 3; do | |
| if swift test --package-path apps/macos --parallel --enable-code-coverage --show-codecov-path; then | |
| exit 0 | |
| fi | |
| echo "swift test failed (attempt $attempt/3). Retrying…" | |
| sleep $((attempt * 20)) | |
| done | |
| exit 1 | |
| android: | |
| name: ${{ matrix.check_name }} | |
| needs: [preflight] | |
| if: needs.preflight.outputs.run_android_job == 'true' | |
| runs-on: blacksmith-16vcpu-ubuntu-2404 | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJson(needs.preflight.outputs.android_matrix) }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| submodules: false | |
| - name: Setup Java | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| # Keep sdkmanager on the stable JDK path for Linux CI runners. | |
| java-version: 17 | |
| - name: Setup Android SDK cmdline-tools | |
| run: | | |
| set -euo pipefail | |
| ANDROID_SDK_ROOT="$HOME/.android-sdk" | |
| CMDLINE_TOOLS_VERSION="12266719" | |
| ARCHIVE="commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip" | |
| URL="https://dl.google.com/android/repository/${ARCHIVE}" | |
| mkdir -p "$ANDROID_SDK_ROOT/cmdline-tools" | |
| curl -fsSL "$URL" -o "/tmp/${ARCHIVE}" | |
| rm -rf "$ANDROID_SDK_ROOT/cmdline-tools/latest" | |
| unzip -q "/tmp/${ARCHIVE}" -d "$ANDROID_SDK_ROOT/cmdline-tools" | |
| mv "$ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools" "$ANDROID_SDK_ROOT/cmdline-tools/latest" | |
| echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV" | |
| echo "ANDROID_HOME=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV" | |
| echo "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin" >> "$GITHUB_PATH" | |
| echo "$ANDROID_SDK_ROOT/platform-tools" >> "$GITHUB_PATH" | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5 | |
| with: | |
| gradle-version: 8.11.1 | |
| - name: Install Android SDK packages | |
| run: | | |
| yes | sdkmanager --sdk_root="${ANDROID_SDK_ROOT}" --licenses >/dev/null | |
| sdkmanager --sdk_root="${ANDROID_SDK_ROOT}" --install \ | |
| "platform-tools" \ | |
| "platforms;android-36" \ | |
| "build-tools;36.0.0" | |
| - name: Run Android ${{ matrix.task }} | |
| working-directory: apps/android | |
| env: | |
| TASK: ${{ matrix.task }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| case "$TASK" in | |
| test-play) | |
| ./gradlew --no-daemon :app:testPlayDebugUnitTest | |
| ;; | |
| test-third-party) | |
| ./gradlew --no-daemon :app:testThirdPartyDebugUnitTest | |
| ;; | |
| build-play) | |
| ./gradlew --no-daemon :app:assemblePlayDebug | |
| ;; | |
| build-third-party) | |
| ./gradlew --no-daemon :app:assembleThirdPartyDebug | |
| ;; | |
| *) | |
| echo "Unsupported Android task: $TASK" >&2 | |
| exit 1 | |
| ;; | |
| esac |