ci: balance test shards and stabilize CI checks #2917
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: node-ci | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| types: [opened, edited, reopened, synchronize] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| validate-title: | |
| name: validate pull request | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| outputs: | |
| ci-mode: ${{ steps.scope.outputs.ci-mode }} | |
| steps: | |
| - name: Checkout pull request for change classification | |
| if: github.event_name == 'pull_request' && github.event.changes.base == null | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 2 | |
| persist-credentials: false | |
| - name: Decide CI mode | |
| id: scope | |
| shell: bash | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BASE_CHANGED: ${{ github.event.changes.base != null }} | |
| run: | | |
| set -euo pipefail | |
| ci_mode=full | |
| if [[ "$EVENT_NAME" == "pull_request" && | |
| "$BASE_CHANGED" != "true" ]]; then | |
| changed_files="$(mktemp)" | |
| trap 'rm -f "$changed_files"' EXIT | |
| git diff --no-renames --name-only -z HEAD^1 HEAD > "$changed_files" | |
| changed=false | |
| markdown_only=true | |
| while IFS= read -r -d '' path; do | |
| changed=true | |
| if [[ "$path" != *.md ]]; then | |
| markdown_only=false | |
| fi | |
| done < "$changed_files" | |
| if [[ "$changed" == "true" && "$markdown_only" == "true" ]]; then | |
| ci_mode=markdown | |
| fi | |
| fi | |
| printf 'ci-mode=%s\n' "$ci_mode" >> "$GITHUB_OUTPUT" | |
| - name: Require a Conventional Commit pull request title | |
| if: github.event_name == 'pull_request' | |
| shell: bash | |
| env: | |
| PR_TITLE: ${{ github.event.pull_request.title }} | |
| run: | | |
| set -euo pipefail | |
| conventional_title='^([a-z][a-z0-9-]*)(\([a-z0-9][a-z0-9._/-]*\))?(!)?:[ ]([^[:space:]].*[^[:space:]]|[^[:space:]])$' | |
| if [[ "$PR_TITLE" == *$'\n'* || "$PR_TITLE" == *$'\r'* || | |
| ! "$PR_TITLE" =~ $conventional_title ]]; then | |
| echo "Pull request title must follow <type>[optional scope][!]: <description>." >&2 | |
| exit 1 | |
| fi | |
| - name: Set up pnpm | |
| if: steps.scope.outputs.ci-mode == 'markdown' | |
| uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 | |
| with: | |
| package_json_file: sdk/typescript/package.json | |
| cache: true | |
| cache_dependency_path: sdk/typescript/pnpm-lock.yaml | |
| - name: Set up Node.js | |
| if: steps.scope.outputs.ci-mode == 'markdown' | |
| uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 | |
| with: | |
| node-version: "22.13.0" | |
| cache: npm | |
| cache-dependency-path: sdk/typescript/pnpm-lock.yaml | |
| - name: Install dependencies | |
| if: steps.scope.outputs.ci-mode == 'markdown' | |
| run: pnpm --dir sdk/typescript install --frozen-lockfile | |
| - name: Check Markdown formatting | |
| if: steps.scope.outputs.ci-mode == 'markdown' | |
| shell: bash | |
| run: | | |
| files=() | |
| while IFS= read -r -d '' path; do | |
| if [[ -f "$GITHUB_WORKSPACE/$path" && | |
| ! -L "$GITHUB_WORKSPACE/$path" ]]; then | |
| files+=("$GITHUB_WORKSPACE/$path") | |
| fi | |
| done < <(git diff --no-renames --name-only -z HEAD^1 HEAD) | |
| if ((${#files[@]} > 0)); then | |
| pnpm --dir sdk/typescript exec prettier --check "${files[@]}" | |
| fi | |
| test: | |
| name: tests / ${{ matrix.os }} / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }} | |
| needs: validate-title | |
| if: needs.validate-title.outputs.ci-mode == 'full' | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| node: ["22.13.0"] | |
| include: | |
| - os: ubuntu-latest | |
| node: "24.0.0" | |
| - os: ubuntu-latest | |
| node: "24" | |
| - os: ubuntu-latest | |
| node: "26.0.0" | |
| - os: ubuntu-latest | |
| node: "26" | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up pnpm | |
| uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 | |
| with: | |
| package_json_file: sdk/typescript/package.json | |
| cache: true | |
| cache_dependency_path: sdk/typescript/pnpm-lock.yaml | |
| - name: Set up Node.js | |
| uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 | |
| with: | |
| node-version: ${{ matrix.node }} | |
| - name: Set up Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: "1.3.14" | |
| - name: Install dependencies | |
| run: pnpm --dir sdk/typescript install --frozen-lockfile | |
| - name: Audit production dependencies | |
| if: matrix.os == 'ubuntu-latest' && matrix.node == '22.13.0' | |
| continue-on-error: true | |
| run: pnpm --dir sdk/typescript run audit:prod | |
| - name: Typecheck | |
| if: matrix.os == 'ubuntu-latest' && matrix.node == '22.13.0' | |
| run: pnpm --dir sdk/typescript run types | |
| - name: Test | |
| timeout-minutes: 10 | |
| env: | |
| TEMP: ${{ runner.temp }} | |
| TMP: ${{ runner.temp }} | |
| TMPDIR: ${{ runner.temp }} | |
| CODEX_SECURITY_ALLOW_MACHINE_POLICY_TEST: "false" | |
| working-directory: sdk/typescript | |
| run: ${{ matrix.os == 'ubuntu-latest' && matrix.node == '22.13.0' && 'pnpm run test:ci' || 'node scripts/run-ci-tests.mjs' }} | |
| - name: Upload test reports | |
| if: always() && matrix.os == 'ubuntu-latest' && matrix.node == '22.13.0' | |
| continue-on-error: true | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: node-22-test-reports | |
| overwrite: true | |
| path: | | |
| sdk/typescript/reports/junit-*.xml | |
| sdk/typescript/coverage/shard-*/lcov.info | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| - name: Check formatting | |
| if: matrix.os == 'ubuntu-latest' && matrix.node == '22.13.0' | |
| run: pnpm --dir sdk/typescript run format | |
| unix-verify: | |
| name: ${{ matrix.os }} / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }} / verify | |
| needs: validate-title | |
| if: needs.validate-title.outputs.ci-mode == 'full' | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| node: ["22.13.0"] | |
| include: | |
| - os: ubuntu-latest | |
| node: "24.0.0" | |
| - os: ubuntu-latest | |
| node: "24" | |
| - os: ubuntu-latest | |
| node: "26.0.0" | |
| - os: ubuntu-latest | |
| node: "26" | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up pnpm | |
| uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 | |
| with: | |
| package_json_file: sdk/typescript/package.json | |
| cache: true | |
| cache_dependency_path: sdk/typescript/pnpm-lock.yaml | |
| - name: Set up Node.js | |
| uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 | |
| with: | |
| node-version: ${{ matrix.node }} | |
| cache: npm | |
| cache-dependency-path: sdk/typescript/pnpm-lock.yaml | |
| - name: Install dependencies | |
| run: pnpm --dir sdk/typescript install --frozen-lockfile | |
| - name: Pack | |
| working-directory: sdk/typescript | |
| run: pnpm pack --pack-destination ../../dist | |
| - name: Inspect package | |
| working-directory: sdk/typescript | |
| shell: bash | |
| run: pnpm run check:package ../../dist/*.tgz | |
| required-test: | |
| name: ${{ matrix.os }} / node-22 | |
| needs: [validate-title, test, unix-verify] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| steps: | |
| - name: Require every Unix coverage job | |
| if: needs.validate-title.result != 'success' || (needs.validate-title.outputs.ci-mode == 'full' && (needs.test.result != 'success' || needs.unix-verify.result != 'success')) || (needs.validate-title.outputs.ci-mode != 'full' && needs.validate-title.outputs.ci-mode != 'markdown') | |
| run: exit 1 | |
| windows-test: | |
| name: windows-latest / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }} / tests-${{ matrix.shard }} | |
| needs: validate-title | |
| if: needs.validate-title.outputs.ci-mode == 'full' | |
| runs-on: windows-latest | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| node: ["22.13.0", "24"] | |
| shard: [1, 2, 3, 4, 5, 6, 7] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Node.js | |
| uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 | |
| with: | |
| node-version: ${{ matrix.node }} | |
| - name: Select pnpm version | |
| id: pnpm-version | |
| shell: bash | |
| run: | | |
| pnpm_pin=$(node -p 'require("./sdk/typescript/package.json").packageManager') | |
| printf 'pin=%s\npackage=%s\n' "$pnpm_pin" "${pnpm_pin%%+*}" >> "$GITHUB_OUTPUT" | |
| printf '%s/pnpm\n' "$RUNNER_TEMP" >> "$GITHUB_PATH" | |
| - name: Cache pnpm executable | |
| id: pnpm-tool | |
| continue-on-error: true | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ${{ runner.temp }}/pnpm | |
| key: pnpm-tool-${{ runner.os }}-${{ runner.arch }}-${{ steps.pnpm-version.outputs.pin }} | |
| - name: Set up pnpm | |
| id: pnpm | |
| shell: bash | |
| env: | |
| PNPM_PACKAGE: ${{ steps.pnpm-version.outputs.package }} | |
| PNPM_CACHE_HIT: ${{ steps.pnpm-tool.outputs.cache-hit }} | |
| run: | | |
| if [[ "$PNPM_CACHE_HIT" != 'true' ]]; then | |
| npm install --global --prefix "$RUNNER_TEMP/pnpm" "$PNPM_PACKAGE" --prefer-offline --no-audit --no-fund | |
| fi | |
| pnpm_store=$(pnpm store path --silent) | |
| pnpm_arch=$(node -p 'process.arch') | |
| printf 'store-path=%s\narch=%s\n' "$pnpm_store" "$pnpm_arch" >> "$GITHUB_OUTPUT" | |
| - name: Cache pnpm store | |
| continue-on-error: true | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ${{ steps.pnpm.outputs.store-path }} | |
| key: pnpm-cache-${{ runner.os }}-${{ steps.pnpm.outputs.arch }}-${{ hashFiles('sdk/typescript/pnpm-lock.yaml') }} | |
| - name: Set up Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: "1.3.14" | |
| - name: Install dependencies | |
| working-directory: sdk/typescript | |
| run: pnpm install --frozen-lockfile | |
| - name: Prepare private Windows test root | |
| id: windows-temp | |
| shell: pwsh | |
| run: ./sdk/typescript/scripts/prepare-windows-test-root.ps1 | |
| - name: Test shard ${{ matrix.shard }} | |
| timeout-minutes: 10 | |
| env: | |
| TEMP: ${{ steps.windows-temp.outputs.path }} | |
| TMP: ${{ steps.windows-temp.outputs.path }} | |
| TMPDIR: ${{ steps.windows-temp.outputs.path }} | |
| CODEX_SECURITY_ALLOW_MACHINE_POLICY_TEST: "false" | |
| run: node sdk/typescript/scripts/run-ci-tests.mjs ${{ matrix.shard }}/7 | |
| - name: Test machine-wide PowerShell policy | |
| if: matrix.shard == 3 && runner.environment == 'github-hosted' | |
| timeout-minutes: 5 | |
| working-directory: sdk/typescript | |
| env: | |
| TEMP: ${{ steps.windows-temp.outputs.path }} | |
| TMP: ${{ steps.windows-temp.outputs.path }} | |
| TMPDIR: ${{ steps.windows-temp.outputs.path }} | |
| CODEX_SECURITY_ALLOW_MACHINE_POLICY_TEST: "true" | |
| run: bun test --timeout 120000 ./tests-ts/windows-machine-policy.test.ts | |
| - name: Upload Windows test report | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: windows-test-report-node-${{ matrix.node }}-${{ matrix.shard }} | |
| overwrite: true | |
| path: sdk/typescript/reports/junit-*.xml | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| windows-verify: | |
| name: windows-latest / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }} / verify | |
| needs: validate-title | |
| if: needs.validate-title.outputs.ci-mode == 'full' | |
| runs-on: windows-latest | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| node: ["22.13.0", "24"] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Node.js | |
| uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 | |
| with: | |
| node-version: ${{ matrix.node }} | |
| cache: npm | |
| cache-dependency-path: sdk/typescript/pnpm-lock.yaml | |
| - name: Select pnpm version | |
| id: pnpm-version | |
| shell: bash | |
| run: | | |
| pnpm_pin=$(node -p 'require("./sdk/typescript/package.json").packageManager') | |
| printf 'pin=%s\npackage=%s\n' "$pnpm_pin" "${pnpm_pin%%+*}" >> "$GITHUB_OUTPUT" | |
| printf '%s/pnpm\n' "$RUNNER_TEMP" >> "$GITHUB_PATH" | |
| - name: Cache pnpm executable | |
| id: pnpm-tool | |
| continue-on-error: true | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ${{ runner.temp }}/pnpm | |
| key: pnpm-tool-${{ runner.os }}-${{ runner.arch }}-${{ steps.pnpm-version.outputs.pin }} | |
| - name: Set up pnpm | |
| id: pnpm | |
| shell: bash | |
| env: | |
| PNPM_PACKAGE: ${{ steps.pnpm-version.outputs.package }} | |
| PNPM_CACHE_HIT: ${{ steps.pnpm-tool.outputs.cache-hit }} | |
| run: | | |
| if [[ "$PNPM_CACHE_HIT" != 'true' ]]; then | |
| npm install --global --prefix "$RUNNER_TEMP/pnpm" "$PNPM_PACKAGE" --prefer-offline --no-audit --no-fund | |
| fi | |
| pnpm_store=$(pnpm store path --silent) | |
| pnpm_arch=$(node -p 'process.arch') | |
| printf 'store-path=%s\narch=%s\n' "$pnpm_store" "$pnpm_arch" >> "$GITHUB_OUTPUT" | |
| - name: Cache pnpm store | |
| continue-on-error: true | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ${{ steps.pnpm.outputs.store-path }} | |
| key: pnpm-cache-${{ runner.os }}-${{ steps.pnpm.outputs.arch }}-${{ hashFiles('sdk/typescript/pnpm-lock.yaml') }} | |
| - name: Install dependencies | |
| working-directory: sdk/typescript | |
| run: pnpm install --frozen-lockfile | |
| - name: Pack | |
| working-directory: sdk/typescript | |
| run: pnpm pack --pack-destination ../../dist | |
| - name: Prepare private Windows test root | |
| id: windows-temp | |
| shell: pwsh | |
| run: ./sdk/typescript/scripts/prepare-windows-test-root.ps1 | |
| - name: Inspect package | |
| working-directory: sdk/typescript | |
| shell: bash | |
| env: | |
| TEMP: ${{ steps.windows-temp.outputs.path }} | |
| TMP: ${{ steps.windows-temp.outputs.path }} | |
| npm_config_timing: "true" | |
| npm_config_loglevel: http | |
| UV_THREADPOOL_SIZE: "16" | |
| run: pnpm run check:package ../../dist/*.tgz | |
| windows: | |
| name: windows-latest / node-${{ matrix.node == '22.13.0' && '22' || matrix.node }} | |
| runs-on: ubuntu-latest | |
| if: always() | |
| needs: [validate-title, windows-test, windows-verify] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| node: ["22.13.0", "24"] | |
| steps: | |
| - name: Require every Windows coverage job | |
| if: needs.validate-title.result != 'success' || (needs.validate-title.outputs.ci-mode == 'full' && (needs.windows-test.result != 'success' || needs.windows-verify.result != 'success')) || (needs.validate-title.outputs.ci-mode != 'full' && needs.validate-title.outputs.ci-mode != 'markdown') | |
| run: exit 1 |