Skip to content

ci: balance test shards and stabilize CI checks #2917

ci: balance test shards and stabilize CI checks

ci: balance test shards and stabilize CI checks #2917

Workflow file for this run

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