🤖 refactor: simplify CI and tests folder structure #5762
Workflow file for this run
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: | |
| pull_request: | |
| branches: ["**"] | |
| merge_group: | |
| workflow_dispatch: | |
| inputs: | |
| test_filter: | |
| description: 'Optional test filter (e.g., "workspace", "tests/file.test.ts", or "-t pattern")' | |
| required: false | |
| type: string | |
| # This filter is passed to unit tests, integration tests, e2e tests, and storybook tests | |
| # to enable faster iteration when debugging specific test failures in CI | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Detect what files changed to enable selective test execution | |
| changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| docs-only: ${{ steps.filter.outputs.docs-only }} | |
| browser-only: ${{ steps.filter.outputs.browser-only }} | |
| node-backend: ${{ steps.filter.outputs.node-backend }} | |
| ci-config: ${{ steps.filter.outputs.ci-config }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Detect file changes | |
| uses: dorny/paths-filter@v3 | |
| id: filter | |
| with: | |
| filters: | | |
| docs-only: | |
| - 'docs/**' | |
| - '*.md' | |
| - 'LICENSE' | |
| - '.vscode/**' | |
| browser-only: | |
| - 'src/browser/**' | |
| - 'src/styles/**' | |
| - 'src/components/**' | |
| - 'src/hooks/**' | |
| - '*.stories.tsx' | |
| - '.storybook/**' | |
| node-backend: | |
| - 'src/node/**' | |
| - 'src/cli/**' | |
| - 'src/desktop/**' | |
| - 'tests/integration/**' | |
| ci-config: | |
| - '.github/**' | |
| - 'jest.config.cjs' | |
| - 'babel.config.cjs' | |
| - 'package.json' | |
| - 'bun.lockb' | |
| - 'tsconfig*.json' | |
| static-check: | |
| name: Static Checks (lint + typecheck + fmt) | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Required for git describe to find tags | |
| - uses: ./.github/actions/setup-mux | |
| - name: Generate version file | |
| run: ./scripts/generate-version.sh | |
| - name: Cache shfmt | |
| id: cache-shfmt | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.local/bin/shfmt | |
| # We install latest via webinstall; reflect that in the cache key to avoid pinning mismatches | |
| key: ${{ runner.os }}-shfmt-latest | |
| restore-keys: | | |
| ${{ runner.os }}-shfmt- | |
| - name: Install and setup shfmt | |
| run: | | |
| # Install shfmt if not cached or if cached binary is broken | |
| if [[ ! -f "$HOME/.local/bin/shfmt" ]] || ! "$HOME/.local/bin/shfmt" --version >/dev/null 2>&1; then | |
| curl -sS https://webinstall.dev/shfmt | bash | |
| fi | |
| echo "$HOME/.local/bin" >> $GITHUB_PATH | |
| # Verify shfmt is available | |
| "$HOME/.local/bin/shfmt" --version | |
| - name: Install Nix | |
| uses: cachix/install-nix-action@v27 | |
| with: | |
| extra_nix_config: | | |
| experimental-features = nix-command flakes | |
| - name: Install uv | |
| run: curl -LsSf https://astral.sh/uv/install.sh | sh | |
| - name: Add uv to PATH | |
| run: echo "$HOME/.local/bin" >> $GITHUB_PATH | |
| - name: Run static checks | |
| run: make -j3 static-check | |
| test: | |
| name: Unit Tests | |
| needs: [changes] | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Required for git describe to find tags | |
| - name: Skip (docs-only changes) | |
| if: needs.changes.outputs.docs-only == 'true' && needs.changes.outputs.node-backend != 'true' && needs.changes.outputs.browser-only != 'true' && needs.changes.outputs.ci-config != 'true' | |
| run: | | |
| echo "::notice::Skipping unit tests - docs-only changes" | |
| echo "## Unit Tests Skipped" >> $GITHUB_STEP_SUMMARY | |
| echo "Only documentation files changed." >> $GITHUB_STEP_SUMMARY | |
| - uses: ./.github/actions/setup-mux | |
| if: needs.changes.outputs.docs-only != 'true' || needs.changes.outputs.node-backend == 'true' || needs.changes.outputs.browser-only == 'true' || needs.changes.outputs.ci-config == 'true' | |
| - name: Build worker files | |
| if: needs.changes.outputs.docs-only != 'true' || needs.changes.outputs.node-backend == 'true' || needs.changes.outputs.browser-only == 'true' || needs.changes.outputs.ci-config == 'true' | |
| run: make build-main | |
| - name: Run tests with coverage | |
| if: needs.changes.outputs.docs-only != 'true' || needs.changes.outputs.node-backend == 'true' || needs.changes.outputs.browser-only == 'true' || needs.changes.outputs.ci-config == 'true' | |
| run: bun test --coverage --coverage-reporter=lcov ${{ github.event.inputs.test_filter || 'src' }} | |
| - name: Upload coverage to Codecov | |
| if: needs.changes.outputs.docs-only != 'true' || needs.changes.outputs.node-backend == 'true' || needs.changes.outputs.browser-only == 'true' || needs.changes.outputs.ci-config == 'true' | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: ./coverage/lcov.info | |
| flags: unit-tests | |
| fail_ci_if_error: false | |
| integration-test: | |
| name: Integration Tests | |
| needs: [changes] | |
| timeout-minutes: 10 | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Required for git describe to find tags | |
| # Skip conditions: docs-only OR browser-only (no backend changes) | |
| - name: Check if tests should run | |
| id: should-run | |
| run: | | |
| DOCS_ONLY="${{ needs.changes.outputs.docs-only }}" | |
| BROWSER_ONLY="${{ needs.changes.outputs.browser-only }}" | |
| NODE_BACKEND="${{ needs.changes.outputs.node-backend }}" | |
| CI_CONFIG="${{ needs.changes.outputs.ci-config }}" | |
| # Run if: manual filter provided, OR node-backend changed, OR ci-config changed | |
| # Skip if: docs-only OR (browser-only AND NOT node-backend AND NOT ci-config) | |
| if [[ -n "${{ github.event.inputs.test_filter }}" ]]; then | |
| echo "run=true" >> $GITHUB_OUTPUT | |
| echo "reason=Manual test filter provided" >> $GITHUB_OUTPUT | |
| elif [[ "$NODE_BACKEND" == "true" ]] || [[ "$CI_CONFIG" == "true" ]]; then | |
| echo "run=true" >> $GITHUB_OUTPUT | |
| echo "reason=Backend or CI config changes detected" >> $GITHUB_OUTPUT | |
| elif [[ "$DOCS_ONLY" == "true" ]]; then | |
| echo "run=false" >> $GITHUB_OUTPUT | |
| echo "reason=Docs-only changes" >> $GITHUB_OUTPUT | |
| elif [[ "$BROWSER_ONLY" == "true" ]]; then | |
| echo "run=false" >> $GITHUB_OUTPUT | |
| echo "reason=Browser-only changes (no backend impact)" >> $GITHUB_OUTPUT | |
| else | |
| # Default: run tests (safety fallback) | |
| echo "run=true" >> $GITHUB_OUTPUT | |
| echo "reason=Default - running all tests" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Skip integration tests | |
| if: steps.should-run.outputs.run != 'true' | |
| run: | | |
| echo "::notice::Skipping integration tests - ${{ steps.should-run.outputs.reason }}" | |
| echo "## Integration Tests Skipped" >> $GITHUB_STEP_SUMMARY | |
| echo "${{ steps.should-run.outputs.reason }}" >> $GITHUB_STEP_SUMMARY | |
| - uses: ./.github/actions/setup-mux | |
| if: steps.should-run.outputs.run == 'true' | |
| - name: Build worker files | |
| if: steps.should-run.outputs.run == 'true' | |
| run: make build-main | |
| - name: Run integration tests | |
| if: steps.should-run.outputs.run == 'true' | |
| # --silent suppresses per-test output (17+ test files × workers = overwhelming logs) | |
| run: TEST_INTEGRATION=1 bun x jest --coverage --maxWorkers=100% --silent ${{ github.event.inputs.test_filter || 'tests' }} | |
| env: | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} | |
| - name: Upload coverage to Codecov | |
| if: steps.should-run.outputs.run == 'true' | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: ./coverage/lcov.info | |
| flags: integration-tests | |
| fail_ci_if_error: false | |
| storybook-test: | |
| name: Storybook Interaction Tests | |
| needs: [changes] | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Required for git describe to find tags | |
| - name: Check if tests should run | |
| id: should-run | |
| run: | | |
| DOCS_ONLY="${{ needs.changes.outputs.docs-only }}" | |
| BROWSER_ONLY="${{ needs.changes.outputs.browser-only }}" | |
| NODE_BACKEND="${{ needs.changes.outputs.node-backend }}" | |
| CI_CONFIG="${{ needs.changes.outputs.ci-config }}" | |
| MANUAL_FILTER="${{ github.event.inputs.test_filter }}" | |
| # Skip if manual filter (user wants specific tests) or docs-only | |
| if [[ -n "$MANUAL_FILTER" ]]; then | |
| echo "run=false" >> $GITHUB_OUTPUT | |
| echo "reason=Manual test filter provided" >> $GITHUB_OUTPUT | |
| elif [[ "$DOCS_ONLY" == "true" ]] && [[ "$BROWSER_ONLY" != "true" ]] && [[ "$NODE_BACKEND" != "true" ]] && [[ "$CI_CONFIG" != "true" ]]; then | |
| echo "run=false" >> $GITHUB_OUTPUT | |
| echo "reason=Docs-only changes" >> $GITHUB_OUTPUT | |
| else | |
| echo "run=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Skip storybook tests | |
| if: steps.should-run.outputs.run != 'true' | |
| run: | | |
| echo "::notice::Skipping storybook tests - ${{ steps.should-run.outputs.reason }}" | |
| echo "## Storybook Tests Skipped" >> $GITHUB_STEP_SUMMARY | |
| echo "${{ steps.should-run.outputs.reason }}" >> $GITHUB_STEP_SUMMARY | |
| - uses: ./.github/actions/setup-mux | |
| if: steps.should-run.outputs.run == 'true' | |
| - uses: ./.github/actions/setup-playwright | |
| if: steps.should-run.outputs.run == 'true' | |
| - name: Build Storybook | |
| if: steps.should-run.outputs.run == 'true' | |
| run: make storybook-build | |
| - name: Serve Storybook | |
| if: steps.should-run.outputs.run == 'true' | |
| run: | | |
| bun x http-server storybook-static -p 6006 & | |
| sleep 5 | |
| - name: Run Storybook tests | |
| if: steps.should-run.outputs.run == 'true' | |
| run: make test-storybook | |
| e2e-test: | |
| name: E2E Tests (${{ matrix.os }}) | |
| needs: [changes] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # Linux: comprehensive E2E tests | |
| - os: linux | |
| runner: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| test_scope: "all" | |
| # macOS: window lifecycle and platform-dependent tests only | |
| - os: macos | |
| runner: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest' }} | |
| test_scope: "window-lifecycle" | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Required for git describe to find tags | |
| - name: Check if tests should run | |
| id: should-run | |
| run: | | |
| DOCS_ONLY="${{ needs.changes.outputs.docs-only }}" | |
| BROWSER_ONLY="${{ needs.changes.outputs.browser-only }}" | |
| NODE_BACKEND="${{ needs.changes.outputs.node-backend }}" | |
| CI_CONFIG="${{ needs.changes.outputs.ci-config }}" | |
| MANUAL_FILTER="${{ github.event.inputs.test_filter }}" | |
| # Skip if manual filter or docs-only | |
| if [[ -n "$MANUAL_FILTER" ]]; then | |
| echo "run=false" >> $GITHUB_OUTPUT | |
| echo "reason=Manual test filter provided" >> $GITHUB_OUTPUT | |
| elif [[ "$DOCS_ONLY" == "true" ]] && [[ "$BROWSER_ONLY" != "true" ]] && [[ "$NODE_BACKEND" != "true" ]] && [[ "$CI_CONFIG" != "true" ]]; then | |
| echo "run=false" >> $GITHUB_OUTPUT | |
| echo "reason=Docs-only changes" >> $GITHUB_OUTPUT | |
| else | |
| echo "run=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Skip E2E tests | |
| if: steps.should-run.outputs.run != 'true' | |
| run: | | |
| echo "::notice::Skipping E2E tests - ${{ steps.should-run.outputs.reason }}" | |
| echo "## E2E Tests Skipped" >> $GITHUB_STEP_SUMMARY | |
| echo "${{ steps.should-run.outputs.reason }}" >> $GITHUB_STEP_SUMMARY | |
| - uses: ./.github/actions/setup-mux | |
| if: steps.should-run.outputs.run == 'true' | |
| - name: Install xvfb (Linux) | |
| if: steps.should-run.outputs.run == 'true' && matrix.os == 'linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y xvfb | |
| - uses: ./.github/actions/setup-playwright | |
| if: steps.should-run.outputs.run == 'true' | |
| - name: Run comprehensive e2e tests (Linux) | |
| if: steps.should-run.outputs.run == 'true' && matrix.os == 'linux' | |
| run: xvfb-run -a make test-e2e | |
| env: | |
| ELECTRON_DISABLE_SANDBOX: 1 | |
| - name: Run window lifecycle e2e tests (macOS) | |
| if: steps.should-run.outputs.run == 'true' && matrix.os == 'macos' | |
| run: make test-e2e PLAYWRIGHT_ARGS="tests/e2e/scenarios/windowLifecycle.spec.ts" | |
| docker-smoke-test: | |
| name: Docker Smoke Test | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| # Only run in merge queue (Docker builds are slow) | |
| if: github.event_name == 'merge_group' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Required for git describe to find tags | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build Docker image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| load: true | |
| tags: mux-server:test | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Start container | |
| run: | | |
| docker run -d --name mux-test -p 3000:3000 mux-server:test | |
| # Wait for server to be ready | |
| for i in {1..30}; do | |
| if curl -sf http://localhost:3000/health; then | |
| echo "Server is ready" | |
| break | |
| fi | |
| echo "Waiting for server... ($i/30)" | |
| sleep 1 | |
| done | |
| - name: Health check | |
| run: | | |
| response=$(curl -sf http://localhost:3000/health) | |
| echo "Health response: $response" | |
| if ! echo "$response" | grep -q '"status":"ok"'; then | |
| echo "Health check failed" | |
| exit 1 | |
| fi | |
| - name: Version check | |
| run: | | |
| response=$(curl -sf http://localhost:3000/version) | |
| echo "Version response: $response" | |
| # Verify response contains expected fields | |
| if ! echo "$response" | grep -q '"mode":"server"'; then | |
| echo "Version check failed: missing mode=server" | |
| exit 1 | |
| fi | |
| if ! echo "$response" | grep -q '"git_describe"'; then | |
| echo "Version check failed: missing git_describe field" | |
| exit 1 | |
| fi | |
| - name: Show container logs on failure | |
| if: failure() | |
| run: docker logs mux-test | |
| - name: Cleanup | |
| if: always() | |
| run: docker rm -f mux-test || true | |
| mux-server-smoke-test: | |
| name: Mux Server Smoke Test | |
| needs: [changes] | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Required for git describe to find tags | |
| - name: Check if tests should run | |
| id: should-run | |
| run: | | |
| DOCS_ONLY="${{ needs.changes.outputs.docs-only }}" | |
| NODE_BACKEND="${{ needs.changes.outputs.node-backend }}" | |
| CI_CONFIG="${{ needs.changes.outputs.ci-config }}" | |
| # Skip if docs-only, run if backend or CI changes | |
| if [[ "$DOCS_ONLY" == "true" ]] && [[ "$NODE_BACKEND" != "true" ]] && [[ "$CI_CONFIG" != "true" ]]; then | |
| echo "run=false" >> $GITHUB_OUTPUT | |
| echo "reason=Docs-only changes" >> $GITHUB_OUTPUT | |
| else | |
| echo "run=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Skip smoke test | |
| if: steps.should-run.outputs.run != 'true' | |
| run: | | |
| echo "::notice::Skipping mux server smoke test - ${{ steps.should-run.outputs.reason }}" | |
| echo "## Mux Server Smoke Test Skipped" >> $GITHUB_STEP_SUMMARY | |
| echo "${{ steps.should-run.outputs.reason }}" >> $GITHUB_STEP_SUMMARY | |
| - uses: ./.github/actions/setup-mux | |
| if: steps.should-run.outputs.run == 'true' | |
| - name: Build application | |
| if: steps.should-run.outputs.run == 'true' | |
| run: make build | |
| - name: Pack npm package | |
| if: steps.should-run.outputs.run == 'true' | |
| run: npm pack | |
| - name: Run smoke test | |
| if: steps.should-run.outputs.run == 'true' | |
| env: | |
| SERVER_PORT: 3001 | |
| SERVER_HOST: localhost | |
| STARTUP_TIMEOUT: 30 | |
| run: | | |
| # Find the actual tarball name (version may vary) | |
| TARBALL=$(ls mux-*.tgz | head -1) | |
| PACKAGE_TARBALL="$TARBALL" ./scripts/smoke-test.sh | |
| - name: Upload server logs on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: mux-server-smoke-test-logs | |
| path: /tmp/tmp.*/server.log | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| check-codex-comments: | |
| name: Check Codex Comments | |
| runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }} | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Required for git describe to find tags | |
| - name: Check for unresolved Codex comments | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| ./scripts/check_codex_comments.sh ${{ github.event.pull_request.number }} | |
| # Trigger CI run |