From 09c71133a38e27da8937fff9d56c3110e25671c4 Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:05:54 -0500 Subject: [PATCH] ci: collapse preflight routing jobs --- .github/workflows/ci-bun.yml | 14 +- .github/workflows/ci.yml | 189 +++++++++++--------------- .github/workflows/install-smoke.yml | 57 ++------ scripts/ci-write-manifest-outputs.mjs | 89 ++++++++---- test/scripts/test-parallel.test.ts | 74 ++++++++++ 5 files changed, 240 insertions(+), 183 deletions(-) diff --git a/.github/workflows/ci-bun.yml b/.github/workflows/ci-bun.yml index 3d691dfd973..9dd7bde809f 100644 --- a/.github/workflows/ci-bun.yml +++ b/.github/workflows/ci-bun.yml @@ -12,7 +12,7 @@ env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" jobs: - bun-manifest: + preflight: runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 20 outputs: @@ -43,11 +43,11 @@ jobs: OPENCLAW_CI_RUN_SKILLS_PYTHON: "false" OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: "false" OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: '{"include":[]}' - run: node scripts/ci-write-manifest-outputs.mjs + run: node scripts/ci-write-manifest-outputs.mjs --workflow ci-bun build-bun-artifacts: - needs: [bun-manifest] - if: needs.bun-manifest.outputs.run_bun_checks == 'true' + needs: [preflight] + if: needs.preflight.outputs.run_bun_checks == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 20 steps: @@ -73,13 +73,13 @@ jobs: bun-checks: name: ${{ matrix.check_name }} - needs: [bun-manifest, build-bun-artifacts] - if: needs.bun-manifest.outputs.run_bun_checks == 'true' + needs: [preflight, build-bun-artifacts] + if: needs.preflight.outputs.run_bun_checks == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 20 strategy: fail-fast: false - matrix: ${{ fromJson(needs.bun-manifest.outputs.bun_checks_matrix) }} + matrix: ${{ fromJson(needs.preflight.outputs.bun_checks_matrix) }} steps: - name: Checkout uses: actions/checkout@v6 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d30aead1fa8..3b8e1aa4985 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,26 +17,43 @@ env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" jobs: - # Scope: establish the fast global truth for this revision before the - # expensive platform and platform-specific lanes fan out. - # Detect docs-only changes to skip heavy jobs (test, build, Windows, macOS, Android). - # Keep this job focused on routing decisions so the rest of CI can fan out sooner. - # Fail-safe: if detection steps are skipped, downstream outputs fall back to - # conservative defaults that keep heavy lanes enabled. - scope: + # Preflight: establish routing truth and planner-owned 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.docs_scope.outputs.docs_only }} - docs_changed: ${{ steps.docs_scope.outputs.docs_changed }} - run_node: ${{ steps.changed_scope.outputs.run_node || 'false' }} - run_macos: ${{ steps.changed_scope.outputs.run_macos || 'false' }} - run_android: ${{ steps.changed_scope.outputs.run_android || 'false' }} - run_skills_python: ${{ steps.changed_scope.outputs.run_skills_python || 'false' }} - run_windows: ${{ steps.changed_scope.outputs.run_windows || 'false' }} - has_changed_extensions: ${{ steps.changed_extensions.outputs.has_changed_extensions || 'false' }} - changed_extensions_matrix: ${{ steps.changed_extensions.outputs.changed_extensions_matrix || '{"include":[]}' }} + 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_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_release_check: ${{ steps.manifest.outputs.run_release_check }} + run_checks_fast: ${{ steps.manifest.outputs.run_checks_fast }} + checks_fast_matrix: ${{ steps.manifest.outputs.checks_fast_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_lint: ${{ steps.manifest.outputs.run_macos_swift_lint }} + run_macos_swift_build: ${{ steps.manifest.outputs.run_macos_swift_build }} + run_macos_swift_test: ${{ steps.manifest.outputs.run_macos_swift_test }} + run_android_job: ${{ steps.manifest.outputs.run_android_job }} + android_matrix: ${{ steps.manifest.outputs.android_matrix }} steps: - name: Checkout uses: actions/checkout@v6 @@ -56,8 +73,6 @@ jobs: id: docs_scope uses: ./.github/actions/detect-docs-changes - # Detect which heavy areas are touched so CI can skip unrelated expensive jobs. - # Fail-safe: if skipped, downstream lanes run. - name: Detect changed scopes id: changed_scope if: steps.docs_scope.outputs.docs_only != 'true' @@ -104,61 +119,19 @@ jobs: appendFileSync(process.env.GITHUB_OUTPUT, `changed_extensions_matrix=${matrix}\n`, "utf8"); EOF - ci-manifest: - needs: [scope] - if: github.event_name != 'pull_request' || !github.event.pull_request.draft - runs-on: blacksmith-16vcpu-ubuntu-2404 - timeout-minutes: 20 - outputs: - run_build_artifacts: ${{ steps.manifest.outputs.run_build_artifacts }} - run_release_check: ${{ steps.manifest.outputs.run_release_check }} - run_checks_fast: ${{ steps.manifest.outputs.run_checks_fast }} - checks_fast_matrix: ${{ steps.manifest.outputs.checks_fast_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_skills_python: ${{ steps.manifest.outputs.run_skills_python }} - 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_lint: ${{ steps.manifest.outputs.run_macos_swift_lint }} - run_macos_swift_build: ${{ steps.manifest.outputs.run_macos_swift_build }} - run_macos_swift_test: ${{ steps.manifest.outputs.run_macos_swift_test }} - run_android: ${{ steps.manifest.outputs.run_android }} - android_matrix: ${{ steps.manifest.outputs.android_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" - install-deps: "false" - use-sticky-disk: "false" - - name: Build CI manifest id: manifest env: - OPENCLAW_CI_DOCS_ONLY: ${{ needs.scope.outputs.docs_only }} - OPENCLAW_CI_DOCS_CHANGED: ${{ needs.scope.outputs.docs_changed }} - OPENCLAW_CI_RUN_NODE: ${{ needs.scope.outputs.run_node }} - OPENCLAW_CI_RUN_MACOS: ${{ needs.scope.outputs.run_macos }} - OPENCLAW_CI_RUN_ANDROID: ${{ needs.scope.outputs.run_android }} - OPENCLAW_CI_RUN_WINDOWS: ${{ needs.scope.outputs.run_windows }} - OPENCLAW_CI_RUN_SKILLS_PYTHON: ${{ needs.scope.outputs.run_skills_python }} - OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: ${{ needs.scope.outputs.has_changed_extensions }} - OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: ${{ needs.scope.outputs.changed_extensions_matrix }} - run: node scripts/ci-write-manifest-outputs.mjs + 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 scripts/ci-write-manifest-outputs.mjs --workflow ci # 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. @@ -261,8 +234,8 @@ 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: [scope, ci-manifest] - if: needs.ci-manifest.outputs.run_build_artifacts == 'true' + needs: [preflight] + if: needs.preflight.outputs.run_build_artifacts == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 20 steps: @@ -308,8 +281,8 @@ jobs: # Validate npm pack contents after build (only on push to main, not PRs). release-check: - needs: [scope, ci-manifest, build-artifacts] - if: needs.ci-manifest.outputs.run_release_check == 'true' + needs: [preflight, build-artifacts] + if: needs.preflight.outputs.run_release_check == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 20 steps: @@ -336,13 +309,13 @@ jobs: checks-fast: name: ${{ matrix.check_name }} - needs: [scope, ci-manifest] - if: needs.ci-manifest.outputs.run_checks_fast == 'true' + needs: [preflight] + if: needs.preflight.outputs.run_checks_fast == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 20 strategy: fail-fast: false - matrix: ${{ fromJson(needs.ci-manifest.outputs.checks_fast_matrix) }} + matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_matrix) }} steps: - name: Checkout uses: actions/checkout@v6 @@ -361,13 +334,13 @@ jobs: checks: name: ${{ matrix.check_name }} - needs: [scope, ci-manifest, build-artifacts] - if: always() && needs.ci-manifest.outputs.run_checks == 'true' && needs.build-artifacts.result == 'success' + 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: 20 strategy: fail-fast: false - matrix: ${{ fromJson(needs.ci-manifest.outputs.checks_matrix) }} + 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' @@ -428,13 +401,13 @@ jobs: extension-fast: name: "extension-fast" - needs: [scope, ci-manifest] - if: needs.ci-manifest.outputs.run_extension_fast == 'true' + needs: [preflight] + if: needs.preflight.outputs.run_extension_fast == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 20 strategy: fail-fast: false - matrix: ${{ fromJson(needs.ci-manifest.outputs.extension_fast_matrix) }} + matrix: ${{ fromJson(needs.preflight.outputs.extension_fast_matrix) }} steps: - name: Checkout uses: actions/checkout@v6 @@ -456,8 +429,8 @@ jobs: # Types, lint, and format check. check: name: "check" - needs: [scope, ci-manifest] - if: always() && needs.ci-manifest.outputs.run_check == 'true' + needs: [preflight] + if: always() && needs.preflight.outputs.run_check == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 20 steps: @@ -481,8 +454,8 @@ jobs: check-additional: name: "check-additional" - needs: [scope, ci-manifest] - if: always() && needs.ci-manifest.outputs.run_check_additional == 'true' + needs: [preflight] + if: always() && needs.preflight.outputs.run_check_additional == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 20 steps: @@ -580,8 +553,8 @@ jobs: build-smoke: name: "build-smoke" - needs: [scope, ci-manifest, build-artifacts] - if: always() && needs.ci-manifest.outputs.run_build_smoke == 'true' && (github.event_name != 'push' || needs.build-artifacts.result == 'success') + 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: @@ -622,8 +595,8 @@ jobs: # Validate docs (format, lint, broken links) only when docs files changed. check-docs: - needs: [scope, ci-manifest] - if: needs.ci-manifest.outputs.run_check_docs == 'true' + needs: [preflight] + if: needs.preflight.outputs.run_check_docs == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 20 steps: @@ -643,8 +616,8 @@ jobs: run: pnpm check:docs skills-python: - needs: [scope, ci-manifest] - if: needs.ci-manifest.outputs.run_skills_python == 'true' + needs: [preflight] + if: needs.preflight.outputs.run_skills_python_job == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 timeout-minutes: 20 steps: @@ -672,8 +645,8 @@ jobs: checks-windows: name: ${{ matrix.check_name }} - needs: [scope, ci-manifest, build-artifacts] - if: always() && needs.ci-manifest.outputs.run_checks_windows == 'true' && needs.build-artifacts.result == 'success' + 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: 20 env: @@ -686,7 +659,7 @@ jobs: shell: bash strategy: fail-fast: false - matrix: ${{ fromJson(needs.ci-manifest.outputs.checks_windows_matrix) }} + matrix: ${{ fromJson(needs.preflight.outputs.checks_windows_matrix) }} steps: - name: Checkout uses: actions/checkout@v6 @@ -778,13 +751,13 @@ jobs: macos-node: name: ${{ matrix.check_name }} - needs: [scope, ci-manifest, build-artifacts] - if: always() && needs.ci-manifest.outputs.run_macos_node == 'true' && needs.build-artifacts.result == 'success' + 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.ci-manifest.outputs.macos_node_matrix) }} + matrix: ${{ fromJson(needs.preflight.outputs.macos_node_matrix) }} steps: - name: Checkout uses: actions/checkout@v6 @@ -821,8 +794,8 @@ jobs: macos-swift-lint: name: "macos-swift-lint" - needs: [scope, ci-manifest] - if: needs.ci-manifest.outputs.run_macos_swift_lint == 'true' + needs: [preflight] + if: needs.preflight.outputs.run_macos_swift_lint == 'true' runs-on: macos-latest timeout-minutes: 20 steps: @@ -853,8 +826,8 @@ jobs: macos-swift-build: name: "macos-swift-build" - needs: [scope, ci-manifest] - if: needs.ci-manifest.outputs.run_macos_swift_build == 'true' + needs: [preflight] + if: needs.preflight.outputs.run_macos_swift_build == 'true' runs-on: macos-latest timeout-minutes: 20 steps: @@ -894,8 +867,8 @@ jobs: macos-swift-test: name: "macos-swift-test" - needs: [scope, ci-manifest] - if: needs.ci-manifest.outputs.run_macos_swift_test == 'true' + needs: [preflight] + if: needs.preflight.outputs.run_macos_swift_test == 'true' runs-on: macos-latest timeout-minutes: 20 steps: @@ -935,13 +908,13 @@ jobs: android: name: ${{ matrix.check_name }} - needs: [scope, ci-manifest] - if: needs.ci-manifest.outputs.run_android == 'true' + 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.ci-manifest.outputs.android_matrix) }} + matrix: ${{ fromJson(needs.preflight.outputs.android_matrix) }} steps: - name: Checkout uses: actions/checkout@v6 diff --git a/.github/workflows/install-smoke.yml b/.github/workflows/install-smoke.yml index 6513bf601b6..8dc93dc09ba 100644 --- a/.github/workflows/install-smoke.yml +++ b/.github/workflows/install-smoke.yml @@ -15,49 +15,34 @@ env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" jobs: - docs-scope: + preflight: if: github.event_name != 'pull_request' || !github.event.pull_request.draft runs-on: blacksmith-16vcpu-ubuntu-2404 outputs: - docs_only: ${{ steps.check.outputs.docs_only }} + docs_only: ${{ steps.manifest.outputs.docs_only }} + run_install_smoke: ${{ steps.manifest.outputs.run_install_smoke }} steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 1 fetch-tags: false + persist-credentials: false + submodules: false - - name: Ensure docs-scope base commit + - 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: check + id: docs_scope uses: ./.github/actions/detect-docs-changes - changed-smoke: - needs: [docs-scope] - if: needs.docs-scope.outputs.docs_only != 'true' - runs-on: blacksmith-16vcpu-ubuntu-2404 - outputs: - run_changed_smoke: ${{ steps.scope.outputs.run_changed_smoke }} - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 1 - fetch-tags: false - - - name: Ensure changed-smoke 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 changed smoke scope - id: scope + id: changed_scope + if: steps.docs_scope.outputs.docs_only != 'true' shell: bash run: | set -euo pipefail @@ -70,20 +55,8 @@ jobs: node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD - ci-manifest: - needs: [docs-scope, changed-smoke] - if: github.event_name != 'pull_request' || !github.event.pull_request.draft - runs-on: blacksmith-16vcpu-ubuntu-2404 - outputs: - run_install_smoke: ${{ steps.manifest.outputs.run_install_smoke }} - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - persist-credentials: false - submodules: false - - name: Setup Node environment + if: steps.docs_scope.outputs.docs_only != 'true' uses: ./.github/actions/setup-node-env with: install-bun: "false" @@ -93,7 +66,7 @@ jobs: - name: Build install-smoke CI manifest id: manifest env: - OPENCLAW_CI_DOCS_ONLY: ${{ needs.docs-scope.outputs.docs_only }} + OPENCLAW_CI_DOCS_ONLY: ${{ steps.docs_scope.outputs.docs_only }} OPENCLAW_CI_DOCS_CHANGED: "false" OPENCLAW_CI_RUN_NODE: "false" OPENCLAW_CI_RUN_MACOS: "false" @@ -102,12 +75,12 @@ jobs: OPENCLAW_CI_RUN_SKILLS_PYTHON: "false" OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: "false" OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: '{"include":[]}' - OPENCLAW_CI_RUN_CHANGED_SMOKE: ${{ needs.changed-smoke.outputs.run_changed_smoke || 'false' }} - run: node scripts/ci-write-manifest-outputs.mjs + OPENCLAW_CI_RUN_CHANGED_SMOKE: ${{ steps.changed_scope.outputs.run_changed_smoke || 'false' }} + run: node scripts/ci-write-manifest-outputs.mjs --workflow install-smoke install-smoke: - needs: [docs-scope, changed-smoke, ci-manifest] - if: needs.ci-manifest.outputs.run_install_smoke == 'true' + needs: [preflight] + if: needs.preflight.outputs.run_install_smoke == 'true' runs-on: blacksmith-16vcpu-ubuntu-2404 env: DOCKER_BUILD_SUMMARY: "false" diff --git a/scripts/ci-write-manifest-outputs.mjs b/scripts/ci-write-manifest-outputs.mjs index 2cc4bca60af..7f13461937f 100644 --- a/scripts/ci-write-manifest-outputs.mjs +++ b/scripts/ci-write-manifest-outputs.mjs @@ -1,41 +1,78 @@ import { appendFileSync } from "node:fs"; import { buildCIExecutionManifest } from "./test-planner/planner.mjs"; +const WORKFLOWS = new Set(["ci", "install-smoke", "ci-bun"]); + +const parseArgs = (argv) => { + const parsed = { + workflow: "ci", + }; + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index]; + if (arg === "--workflow") { + const nextValue = argv[index + 1] ?? ""; + if (!WORKFLOWS.has(nextValue)) { + throw new Error( + `Unsupported --workflow value "${String(nextValue || "")}". Supported values: ci, install-smoke, ci-bun.`, + ); + } + parsed.workflow = nextValue; + index += 1; + } + } + return parsed; +}; + const outputPath = process.env.GITHUB_OUTPUT; if (!outputPath) { throw new Error("GITHUB_OUTPUT is required"); } +const { workflow } = parseArgs(process.argv.slice(2)); const manifest = buildCIExecutionManifest(undefined, { env: process.env }); const writeOutput = (name, value) => { appendFileSync(outputPath, `${name}=${value}\n`, "utf8"); }; -writeOutput("run_build_artifacts", String(manifest.jobs.buildArtifacts.enabled)); -writeOutput("run_release_check", String(manifest.jobs.releaseCheck.enabled)); -writeOutput("run_checks_fast", String(manifest.jobs.checksFast.enabled)); -writeOutput("checks_fast_matrix", JSON.stringify(manifest.jobs.checksFast.matrix)); -writeOutput("run_checks", String(manifest.jobs.checks.enabled)); -writeOutput("checks_matrix", JSON.stringify(manifest.jobs.checks.matrix)); -writeOutput("run_extension_fast", String(manifest.jobs.extensionFast.enabled)); -writeOutput("extension_fast_matrix", JSON.stringify(manifest.jobs.extensionFast.matrix)); -writeOutput("run_check", String(manifest.jobs.check.enabled)); -writeOutput("run_check_additional", String(manifest.jobs.checkAdditional.enabled)); -writeOutput("run_build_smoke", String(manifest.jobs.buildSmoke.enabled)); -writeOutput("run_check_docs", String(manifest.jobs.checkDocs.enabled)); -writeOutput("run_skills_python", String(manifest.jobs.skillsPython.enabled)); -writeOutput("run_checks_windows", String(manifest.jobs.checksWindows.enabled)); -writeOutput("checks_windows_matrix", JSON.stringify(manifest.jobs.checksWindows.matrix)); -writeOutput("run_macos_node", String(manifest.jobs.macosNode.enabled)); -writeOutput("macos_node_matrix", JSON.stringify(manifest.jobs.macosNode.matrix)); -writeOutput("run_macos_swift_lint", String(manifest.jobs.macosSwiftLint.enabled)); -writeOutput("run_macos_swift_build", String(manifest.jobs.macosSwiftBuild.enabled)); -writeOutput("run_macos_swift_test", String(manifest.jobs.macosSwiftTest.enabled)); -writeOutput("run_android", String(manifest.jobs.android.enabled)); -writeOutput("android_matrix", JSON.stringify(manifest.jobs.android.matrix)); -writeOutput("run_bun_checks", String(manifest.jobs.bunChecks.enabled)); -writeOutput("bun_checks_matrix", JSON.stringify(manifest.jobs.bunChecks.matrix)); -writeOutput("run_install_smoke", String(manifest.jobs.installSmoke.enabled)); -writeOutput("required_check_names", JSON.stringify(manifest.requiredCheckNames)); +if (workflow === "ci") { + writeOutput("docs_only", String(manifest.scope.docsOnly)); + writeOutput("docs_changed", String(manifest.scope.docsChanged)); + writeOutput("run_node", String(manifest.scope.runNode)); + writeOutput("run_macos", String(manifest.scope.runMacos)); + writeOutput("run_android", String(manifest.scope.runAndroid)); + writeOutput("run_skills_python", String(manifest.scope.runSkillsPython)); + writeOutput("run_windows", String(manifest.scope.runWindows)); + writeOutput("has_changed_extensions", String(manifest.scope.hasChangedExtensions)); + writeOutput("changed_extensions_matrix", JSON.stringify(manifest.scope.changedExtensionsMatrix)); + writeOutput("run_build_artifacts", String(manifest.jobs.buildArtifacts.enabled)); + writeOutput("run_release_check", String(manifest.jobs.releaseCheck.enabled)); + writeOutput("run_checks_fast", String(manifest.jobs.checksFast.enabled)); + writeOutput("checks_fast_matrix", JSON.stringify(manifest.jobs.checksFast.matrix)); + writeOutput("run_checks", String(manifest.jobs.checks.enabled)); + writeOutput("checks_matrix", JSON.stringify(manifest.jobs.checks.matrix)); + writeOutput("run_extension_fast", String(manifest.jobs.extensionFast.enabled)); + writeOutput("extension_fast_matrix", JSON.stringify(manifest.jobs.extensionFast.matrix)); + writeOutput("run_check", String(manifest.jobs.check.enabled)); + writeOutput("run_check_additional", String(manifest.jobs.checkAdditional.enabled)); + writeOutput("run_build_smoke", String(manifest.jobs.buildSmoke.enabled)); + writeOutput("run_check_docs", String(manifest.jobs.checkDocs.enabled)); + writeOutput("run_skills_python_job", String(manifest.jobs.skillsPython.enabled)); + writeOutput("run_checks_windows", String(manifest.jobs.checksWindows.enabled)); + writeOutput("checks_windows_matrix", JSON.stringify(manifest.jobs.checksWindows.matrix)); + writeOutput("run_macos_node", String(manifest.jobs.macosNode.enabled)); + writeOutput("macos_node_matrix", JSON.stringify(manifest.jobs.macosNode.matrix)); + writeOutput("run_macos_swift_lint", String(manifest.jobs.macosSwiftLint.enabled)); + writeOutput("run_macos_swift_build", String(manifest.jobs.macosSwiftBuild.enabled)); + writeOutput("run_macos_swift_test", String(manifest.jobs.macosSwiftTest.enabled)); + writeOutput("run_android_job", String(manifest.jobs.android.enabled)); + writeOutput("android_matrix", JSON.stringify(manifest.jobs.android.matrix)); + writeOutput("required_check_names", JSON.stringify(manifest.requiredCheckNames)); +} else if (workflow === "install-smoke") { + writeOutput("docs_only", String(manifest.scope.docsOnly)); + writeOutput("run_install_smoke", String(manifest.jobs.installSmoke.enabled)); +} else if (workflow === "ci-bun") { + writeOutput("run_bun_checks", String(manifest.jobs.bunChecks.enabled)); + writeOutput("bun_checks_matrix", JSON.stringify(manifest.jobs.bunChecks.matrix)); +} diff --git a/test/scripts/test-parallel.test.ts b/test/scripts/test-parallel.test.ts index c8fcf28253d..7e568e94e09 100644 --- a/test/scripts/test-parallel.test.ts +++ b/test/scripts/test-parallel.test.ts @@ -242,6 +242,80 @@ describe("scripts/test-parallel lane planning", () => { expect(manifest.jobs.checksWindows.enabled).toBe(true); }); + it("writes CI workflow outputs in ci mode", () => { + const repoRoot = path.resolve(import.meta.dirname, "../.."); + const outputPath = path.join(os.tmpdir(), `openclaw-ci-output-${Date.now()}.txt`); + + execFileSync("node", ["scripts/ci-write-manifest-outputs.mjs", "--workflow", "ci"], { + cwd: repoRoot, + env: { + ...clearPlannerShardEnv(process.env), + GITHUB_OUTPUT: outputPath, + GITHUB_EVENT_NAME: "pull_request", + OPENCLAW_CI_DOCS_ONLY: "false", + OPENCLAW_CI_DOCS_CHANGED: "false", + OPENCLAW_CI_RUN_NODE: "true", + OPENCLAW_CI_RUN_MACOS: "true", + OPENCLAW_CI_RUN_ANDROID: "true", + OPENCLAW_CI_RUN_WINDOWS: "true", + OPENCLAW_CI_RUN_SKILLS_PYTHON: "false", + OPENCLAW_CI_HAS_CHANGED_EXTENSIONS: "false", + OPENCLAW_CI_CHANGED_EXTENSIONS_MATRIX: '{"include":[]}', + }, + encoding: "utf8", + }); + + const outputs = fs.readFileSync(outputPath, "utf8"); + expect(outputs).toContain("run_build_artifacts=true"); + expect(outputs).toContain("run_checks_windows=true"); + expect(outputs).toContain("run_macos_node=true"); + expect(outputs).toContain("android_matrix="); + fs.rmSync(outputPath, { force: true }); + }); + + it("writes install-smoke outputs in install-smoke mode", () => { + const repoRoot = path.resolve(import.meta.dirname, "../.."); + const outputPath = path.join(os.tmpdir(), `openclaw-install-output-${Date.now()}.txt`); + + execFileSync("node", ["scripts/ci-write-manifest-outputs.mjs", "--workflow", "install-smoke"], { + cwd: repoRoot, + env: { + ...clearPlannerShardEnv(process.env), + GITHUB_OUTPUT: outputPath, + OPENCLAW_CI_DOCS_ONLY: "false", + OPENCLAW_CI_RUN_CHANGED_SMOKE: "true", + }, + encoding: "utf8", + }); + + const outputs = fs.readFileSync(outputPath, "utf8"); + expect(outputs).toContain("run_install_smoke=true"); + expect(outputs).not.toContain("run_checks="); + fs.rmSync(outputPath, { force: true }); + }); + + it("writes bun outputs in ci-bun mode", () => { + const repoRoot = path.resolve(import.meta.dirname, "../.."); + const outputPath = path.join(os.tmpdir(), `openclaw-bun-output-${Date.now()}.txt`); + + execFileSync("node", ["scripts/ci-write-manifest-outputs.mjs", "--workflow", "ci-bun"], { + cwd: repoRoot, + env: { + ...clearPlannerShardEnv(process.env), + GITHUB_OUTPUT: outputPath, + OPENCLAW_CI_DOCS_ONLY: "false", + OPENCLAW_CI_RUN_NODE: "true", + }, + encoding: "utf8", + }); + + const outputs = fs.readFileSync(outputPath, "utf8"); + expect(outputs).toContain("run_bun_checks=true"); + expect(outputs).toContain("bun_checks_matrix="); + expect(outputs).not.toContain("run_install_smoke="); + fs.rmSync(outputPath, { force: true }); + }); + it("passes through vitest --mode values that are not wrapper runtime overrides", () => { const repoRoot = path.resolve(import.meta.dirname, "../.."); const output = execFileSync(