Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .github/actions/setup-node-cache/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Setup Node.js with cache
description: Sets up Node.js, caches node_modules and npm, and installs dependencies if needed.

runs:
using: composite
steps:
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc

- name: Compute node modules cache key
id: nodeModulesCacheKey
shell: bash
run: echo "value=$(sha256sum package-lock.json | cut -d ' ' -f 1)" >> $GITHUB_OUTPUT

- name: Cache node modules
id: cacheNodeModules
uses: actions/cache@v5
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules20-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules20-

- name: Get npm cache directory path
id: npmCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
shell: bash
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT

- name: Cache npm directory
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
uses: actions/cache@v5
with:
path: ${{ steps.npmCacheDirPath.outputs.dir }}
key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-npmCacheDir-

- name: Install system dependencies
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
shell: bash
run: |
sudo apt update
sudo apt install -y libxkbfile-dev pkg-config libkrb5-dev libxss1

- name: Install npm dependencies
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
shell: bash
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
run: npm ci --ignore-scripts
176 changes: 176 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# CI pipeline for pull requests targeting main.
# Runs four parallel jobs:
# - frontend-lint: ESLint, Stylelint, and hygiene checks
# - frontend-compile: TypeScript compilation and layer validation
# - backend-lint: Clippy for the Tauri/Rust backend
# - backend-format: rustfmt check for the Tauri/Rust backend
#
# Uses dorny/paths-filter so that each job runs only when relevant files change,
# while remaining compatible with branch protection required status checks.
name: CI

on:
pull_request:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true

permissions: {}

jobs:
changes:
name: Detect Changes
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
pull-requests: read
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false

- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
frontend:
- 'src/**'
- 'extensions/**'
- 'build/**'
- 'test/**'
- 'package.json'
- 'package-lock.json'
- '.nvmrc'
- 'eslint.config.js'
- '.eslint-ignore'
- '.eslint-plugin-local/**'
- 'tsfmt.json'
- '.editorconfig'
- '.github/actions/**'
- '.github/workflows/ci.yml'
backend:
- 'src-tauri/**'
- '.github/workflows/ci.yml'

frontend-lint:
name: Frontend Lint
needs: changes
if: ${{ needs.changes.outputs.frontend == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 30
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false

- uses: ./.github/actions/setup-node-cache

- name: ESLint
run: npm run eslint

- name: Stylelint
run: npm run stylelint

- name: Hygiene
run: npm run hygiene

frontend-compile:
name: Frontend Compile Check
needs: changes
if: ${{ needs.changes.outputs.frontend == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 30
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false

- uses: ./.github/actions/setup-node-cache

- name: TypeScript compile check
run: npm run compile-check-ts-native

- name: Valid layers check
run: npm run valid-layers-check

backend-lint:
name: Backend Lint
needs: changes
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

# Create empty out/ directory so tauri::generate_context!() can resolve frontendDist: "../out"
- name: Create frontend dist stub
run: mkdir -p out

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf

- uses: dtolnay/rust-toolchain@stable
with:
components: clippy

- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri

- name: Clippy
working-directory: src-tauri
run: cargo clippy -- -D warnings

backend-format:
name: Backend Format
needs: changes
if: ${{ needs.changes.outputs.backend == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
src-tauri

- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt

- name: Check format
working-directory: src-tauri
run: cargo fmt --check

# Gate job: aggregates all job results into a single required status check.
# This job always runs (even when other jobs are skipped by paths-filter),
# making it safe to use as a required status check in branch protection.
ci-gate:
name: CI Gate
if: ${{ always() }}
needs: [frontend-lint, frontend-compile, backend-lint, backend-format]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check job results
run: |
results=("${{ needs.frontend-lint.result }}" "${{ needs.frontend-compile.result }}" "${{ needs.backend-lint.result }}" "${{ needs.backend-format.result }}")
for result in "${results[@]}"; do
if [[ "$result" == "failure" || "$result" == "cancelled" ]]; then
echo "::error::One or more CI jobs failed or were cancelled."
exit 1
fi
done
echo "All CI jobs passed or were skipped."
Loading