name: CI on: push: branches: [main] pull_request: branches: [main] types: [opened, synchronize, reopened] workflow_dispatch: permissions: contents: read concurrency: group: ci-${{ github.ref }} # A newer push supersedes the run in progress, on main too. cancel-in-progress: true env: CARGO_TERM_COLOR: always CARGO_INCREMENTAL: 0 # Line tables keep test backtraces readable; full debuginfo only adds build time and cache size. CARGO_PROFILE_DEV_DEBUG: line-tables-only jobs: # Which parts a pull request touches, so jobs it can't affect skip and leave the # shared runners to others. Pushes and manual runs always run everything. changes: name: Changes runs-on: ubuntu-latest timeout-minutes: 5 permissions: pull-requests: read outputs: rust: ${{ steps.classify.outputs.rust }} frontend: ${{ steps.classify.outputs.frontend }} steps: - id: classify env: GH_TOKEN: ${{ github.token }} PR: ${{ github.event.pull_request.number }} run: | set -euo pipefail if [ "$GITHUB_EVENT_NAME" != pull_request ]; then echo "rust=true" >> "$GITHUB_OUTPUT" echo "frontend=true" >> "$GITHUB_OUTPUT" exit 0 fi gh api --paginate "repos/$GITHUB_REPOSITORY/pulls/$PR/files" \ --jq '.[] | .filename, (.previous_filename // empty)' > files.txt cat files.txt docs='^(docs/|\.agents/|\.claude/|\.idea/|\.github/ISSUE_TEMPLATE/|\.github/pull_request_template\.md$|[^/]+\.md$|LICENSE$|NOTICE$)' # Frontend-only paths. The Rust code embeds a few files under src/; those stay out. web='^(src/|e2e/|perf/|public/|scripts/|smoke/|index\.html$|vite\.config\.ts$|tsconfig[^/]*\.json$|knip\.ts$|playwright\.config\.ts$|\.oxlintrc\.json$|\.oxfmtrc\.json$)' embedded='^src/(bindings\.ts|styles/tokens\.css|features/plugins/views/frame-bridge\.js|platform/keymaps/)' # A job runs when any changed file falls outside the paths that can't affect it. rust=false if grep -Ev "$docs|$web" files.txt | grep -q . || grep -Eq "$embedded" files.txt; then rust=true; fi frontend=false if grep -Ev "$docs" files.txt | grep -q .; then frontend=true; fi echo "rust=$rust frontend=$frontend" echo "rust=$rust" >> "$GITHUB_OUTPUT" echo "frontend=$frontend" >> "$GITHUB_OUTPUT" rust: name: Rust (${{ matrix.os }}) needs: changes if: needs.changes.outputs.rust == 'true' timeout-minutes: 60 strategy: fail-fast: false matrix: os: [macos-latest, ubuntu-22.04, windows-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v5 - uses: pnpm/action-setup@v5 with: { version: 11 } - uses: actions/setup-node@v5 with: { node-version: 22, cache: pnpm } - uses: dtolnay/rust-toolchain@stable with: { components: "clippy, rustfmt" } # Only main saves the cache (saving takes minutes on Windows); PRs restore main's. - uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/main' }} - uses: taiki-e/install-action@nextest - name: Linux deps if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev libxdo-dev libssl-dev patchelf # tauri-build checks that frontendDist exists. - run: pnpm install --frozen-lockfile && mkdir -p dist shell: bash - if: runner.os == 'Linux' run: cargo fmt --all --check - run: cargo clippy --workspace --all-targets -- -D warnings # Test fixtures write LF files; keep Git from converting them on checkout. - run: git config --global core.autocrlf false - run: cargo nextest run --workspace --no-fail-fast # nextest ran export_bindings, which rewrites src/bindings.ts. - name: Bindings are up to date if: runner.os == 'Linux' run: git diff --exit-code src/bindings.ts # The remote development server ships as a static Linux binary (release.yml): # keep that build working. server: name: Remote server (linux musl) needs: changes if: needs.changes.outputs.rust == 'true' timeout-minutes: 40 runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: { targets: x86_64-unknown-linux-musl } - uses: Swatinem/rust-cache@v2 with: { key: server-musl } - run: sudo apt-get update && sudo apt-get install -y musl-tools - run: cargo build --locked -p ide-host --bin openidea-server --target x86_64-unknown-linux-musl - run: target/x86_64-unknown-linux-musl/debug/openidea-server --version # The plugin SDK, the official plugins and the test fixtures build in their # own workspaces (the root excludes them), so the jobs above don't see them. plugins: name: Plugins (SDK, official, fixtures) needs: changes if: needs.changes.outputs.rust == 'true' timeout-minutes: 30 runs-on: ubuntu-22.04 env: CARGO_TARGET_DIR: ${{ github.workspace }}/target-plugins steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable with: { targets: wasm32-wasip2, components: "clippy, rustfmt" } - uses: Swatinem/rust-cache@v2 with: key: plugins cache-directories: target-plugins save-if: ${{ github.ref == 'refs/heads/main' }} - name: Format, lint (host and wasm) and test each crate shell: bash run: | set -euo pipefail for dir in sdk/rust/openidea-plugin plugins/official/*/ crates/ide-plugin/tests/fixtures/*/; do [ -f "$dir/Cargo.toml" ] || continue echo "::group::$dir" cargo fmt --check --manifest-path "$dir/Cargo.toml" cargo clippy --locked --all-targets --manifest-path "$dir/Cargo.toml" -- -D warnings cargo clippy --locked --target wasm32-wasip2 --manifest-path "$dir/Cargo.toml" -- -D warnings cargo test --locked --manifest-path "$dir/Cargo.toml" echo "::endgroup::" done - uses: pnpm/action-setup@v5 with: { version: 11 } - uses: actions/setup-node@v5 with: { node-version: 22 } # Plugin web pages (`web-src/`, their own pnpm projects): typecheck and test. - name: Web pages typecheck and test shell: bash run: | set -euo pipefail for dir in plugins/official/*/web-src/; do [ -f "$dir/package.json" ] || continue echo "::group::$dir" pnpm --dir "$dir" install --frozen-lockfile --ignore-workspace pnpm --dir "$dir" exec tsc --noEmit pnpm --dir "$dir" test echo "::endgroup::" done # The host's tests run the checked-in components: they must be built from these sources. # Web pages aren't checked in: this builds them and checks the result. - name: Checked-in components are up to date, web pages build run: plugins/build.sh --check deny: name: Cargo deny needs: changes if: needs.changes.outputs.rust == 'true' timeout-minutes: 15 runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - uses: taiki-e/install-action@cargo-deny - run: cargo deny check frontend: name: Frontend needs: changes if: needs.changes.outputs.frontend == 'true' timeout-minutes: 30 runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v5 - uses: pnpm/action-setup@v5 with: { version: 11 } - uses: actions/setup-node@v5 with: { node-version: 22, cache: pnpm } - run: pnpm install --frozen-lockfile - run: pnpm typecheck - run: pnpm lint - run: pnpm format:check - run: pnpm knip - run: pnpm test - run: pnpm build e2e: name: E2E needs: changes if: needs.changes.outputs.frontend == 'true' timeout-minutes: 30 runs-on: ubuntu-24.04 # Browsers and their system libraries are preinstalled; keep the tag in step with @playwright/test. container: mcr.microsoft.com/playwright:v1.64.1-noble steps: - uses: actions/checkout@v5 - uses: pnpm/action-setup@v5 with: { version: 11 } - uses: actions/setup-node@v5 with: { node-version: 22, cache: pnpm } - run: pnpm install --frozen-lockfile - run: pnpm e2e env: HOME: /root - uses: actions/upload-artifact@v6 if: failure() with: name: playwright-report path: playwright-report/ retention-days: 7