name: CI on: push: branches: [main] pull_request: branches: [main] concurrency: group: ci-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} env: HUSKY: "0" jobs: pr-title: name: Conventional PR title if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - name: Validate title for Release Please env: PR_TITLE: ${{ github.event.pull_request.title }} run: >- node -e 'const title = process.env.PR_TITLE ?? ""; const conventional = /^(?:feat|fix|docs|refactor|perf|test|build|ci|chore|revert)(?:\([a-z0-9._/-]+\))?!?: .+/u; if (!conventional.test(title)) { console.error("PR title must use Conventional Commits, for example: feat: add historical source import"); process.exit(1); }' changes: name: Classify changes runs-on: ubuntu-latest outputs: code: ${{ steps.package.outputs.required }} package: ${{ steps.package.outputs.required }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Select package and Windows lanes id: package shell: bash env: BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: | if [[ "${{ github.event_name }}" != "pull_request" ]]; then echo "required=true" >> "${GITHUB_OUTPUT}" elif git diff --quiet "${BASE_SHA}" "${HEAD_SHA}" -- \ . \ ':(exclude)docs/**' \ ':(exclude)AGENTS.md' \ ':(exclude)CONTRIBUTING.md'; then echo "required=false" >> "${GITHUB_OUTPUT}" else echo "required=true" >> "${GITHUB_OUTPUT}" fi static: name: Static checks needs: changes if: ${{ always() }} runs-on: ubuntu-latest steps: - name: Require successful change classification env: CHANGES_RESULT: ${{ needs.changes.result }} run: test "${CHANGES_RESULT}" = "success" - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: .nvmrc cache: npm - run: npm ci - if: github.event_name == 'push' || needs.changes.outputs.code == 'true' run: npm run check:ci - if: github.event_name == 'pull_request' && needs.changes.outputs.code != 'true' run: npm run format:check test-shard: name: Test shard (${{ matrix.shard }}) needs: [changes, build] if: github.event_name == 'push' || needs.changes.outputs.code == 'true' runs-on: ubuntu-latest strategy: fail-fast: false matrix: shard: [1/2, 2/2] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: .nvmrc cache: npm - run: npm ci - uses: actions/download-artifact@v4 with: name: rea-dist path: dist - run: npm run test:ci:shard:run -- --shard=${{ matrix.shard }} - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: vitest-report-${{ strategy.job-index }} path: .vitest-reports/* include-hidden-files: true if-no-files-found: error overwrite: true retention-days: 1 build: name: Build package needs: changes if: github.event_name == 'push' || needs.changes.outputs.code == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: .nvmrc cache: npm - run: npm ci - run: npm run build:cached - uses: actions/upload-artifact@v4 with: name: rea-dist path: dist if-no-files-found: error retention-days: 1 test: name: Test (coverage + timing) needs: [changes, test-shard] if: ${{ always() }} runs-on: ubuntu-latest steps: - name: Require successful test shards env: CHANGES_RESULT: ${{ needs.changes.result }} CODE_REQUIRED: ${{ needs.changes.outputs.code }} SHARD_RESULT: ${{ needs.test-shard.result }} run: | test "${CHANGES_RESULT}" = "success" test "${CODE_REQUIRED}" != "true" || test "${SHARD_RESULT}" = "success" - uses: actions/checkout@v4 if: needs.changes.outputs.code == 'true' && success() - uses: actions/setup-node@v4 if: needs.changes.outputs.code == 'true' && success() with: node-version-file: .nvmrc cache: npm - if: needs.changes.outputs.code == 'true' && success() run: npm ci - uses: actions/download-artifact@v4 if: needs.changes.outputs.code == 'true' && success() with: path: .vitest-reports pattern: vitest-report-* merge-multiple: true - if: needs.changes.outputs.code == 'true' && success() run: npm run test:ci:merge - uses: actions/upload-artifact@v4 if: needs.changes.outputs.code == 'true' && always() with: name: test-results path: test-results.xml if-no-files-found: error retention-days: 30 package-e2e: name: Package E2E (${{ matrix.os }}) needs: changes if: github.event_name == 'push' || needs.changes.outputs.package == 'true' strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-14] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: .nvmrc cache: npm - run: npm ci - run: npm run verify:package windows-curated: name: Windows Ghidra P0 and package lane needs: changes if: github.event_name == 'push' || needs.changes.outputs.package == 'true' runs-on: windows-latest timeout-minutes: 25 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: .nvmrc cache: npm - run: npm ci - run: npm run build:cached - run: npm run typecheck - name: Record named Windows host capabilities run: node scripts/windows-capabilities.mjs | Out-File -Encoding utf8NoBOM windows-capabilities.json - name: Validate Windows host capability report run: >- node -e "const report = JSON.parse(require('node:fs').readFileSync('windows-capabilities.json', 'utf8')); if (report.platform !== 'win32' || report.architecture !== 'x64') throw new Error('expected a Windows x64 capability report');" - name: Run curated Windows P0 tests run: >- npx vitest run --coverage.enabled=false tests/binaryTarget.test.ts tests/windowsCapabilities.test.ts tests/ghidraWindowsFixture.test.ts tests/ghidraInstallation.test.ts tests/ghidraProvider.test.ts tests/ghidraTransport.test.ts tests/ghidraTargetSnapshot.test.ts tests/ghidraClient.test.ts tests/ghidraLauncher.test.ts tests/processOwnership.test.ts tests/realGhidraWindowsWorkflow.test.ts - run: npm run verify:package:windows - uses: actions/upload-artifact@v4 if: always() with: name: windows-capabilities path: windows-capabilities.json retention-days: 30 docs: name: Docs Generation if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version-file: .nvmrc cache: npm - run: npm ci - run: npm run docs:check - uses: actions/upload-artifact@v4 with: name: api-docs path: build/api-docs retention-days: 14