readest/.github/workflows/pull-request.yml
Huang Xin 655c72ad5a
feat(linux): ship Readest on the tauri CEF runtime (#6074)
* feat(linux): build and ship Readest on the tauri CEF runtime

Linux now builds against upstream tauri `feat/cef` (git) and the plugins on
plugins-workspace `feat/cef`, while macOS/Windows/mobile keep the readest
fork untouched:

- `pnpm tauri:cef dev|build` (scripts/tauri-cef.mjs) runs @tauri-apps/cli-cef
  with `--features cef`, cargo `--no-default-features` and the Linux-only
  `[patch.crates-io]` in src-tauri/.cargo/cef.toml, swapping the committed
  Cargo.cef.lock in for the run so Cargo.lock stays in the fork state.
- The fork crates are no longer workspace members (a member clashes on
  `links` with the git copy); the tauri-plugins fork carried no changes and
  is removed, tauri-plugin-fs comes from crates.io at 2.5.1.
- App: `cef = ["tauri/cef"]` (the fork gets a no-op stub), explicit runtime
  type, `cef_entry_point`, `--no-first-run` for CEF, commands generic over
  the runtime, xdg-portal dialogs on Linux (rfd's GTK thread crashes CEF),
  rangefile reads and pointer-driven move/resize on the Linux CEF runtime,
  Chromium reported in the About dialog, deb/rpm depend on libwebkit2gtk
  while plugins still pull the wry runtime.
- Build-generated permission files are untracked; turso registers `batch`.
- CI Linux legs use `pnpm tauri:cef`.

Submodules: packages/tauri -> feat/cef-feature-stub, tauri-plugin-turso ->
fix/register-batch-command (both need pushing).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(linux): let the CEF wrapper run an unpacked tauri CLI for offline builds

Flatpak builds have no network, so `TAURI_CEF_CLI` points scripts/tauri-cef.mjs
at an unpacked copy of @tauri-apps/cli-cef instead of `pnpm dlx`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore(linux): take the CEF stack from the readest forks

- src-tauri/.cargo/cef.toml: tauri and the plugins now come from the readest
  forks' feat/cef branches (upstream feat/cef plus the DevTools-close fix in
  tauri-runtime-cef); Cargo.cef.lock re-resolved against them.
- tauri-plugin-turso: point at main, which merged the batch command fix.
- CI: STRACE_MODE=0 for the Linux legs so the CEF AppImage bundler
  (quick-sharun) does not launch the app under Xvfb and hang (#4906).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(linux): make the CEF runtime the default tauri build on Linux

`pnpm tauri` now goes through scripts/tauri.mjs (the former tauri-cef.mjs):
on Linux its dev, build and bundle commands run the feat/cef tauri CLI
with the CEF feature, cargo config and Cargo.cef.lock swap, and every
other platform or command passes straight through to @tauri-apps/cli.
The separate `tauri:cef` script goes away, so the release and nightly
workflows, the local Linux build script and the docs use plain
`pnpm tauri build` again.

The tauri webdriver harness (scripts/test-tauri.sh) still calls the
`tauri` binary directly and keeps the wry runtime on Linux, since
tauri-driver needs WebKitWebDriver.

Cargo.cef.lock picks up the quick-xml 0.41 bump from main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore(nix): refresh cargoHash for the reshaped Cargo.lock

Dropping the packages/tauri-plugins submodule and the tauri-plugin-fs
path patch changed the vendored cargo dependency set, so the Nix
fixed-output hash moves to the value the nix-deps-check job reported.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(linux): harden the CEF wrapper and pointer window drag

Review follow-ups on the CEF runtime work:

- scripts/tauri.mjs parks the wry Cargo.lock in Cargo.lock.wry, created
  exclusively, so two CEF commands in one checkout cannot swap lockfiles
  over each other, and a killed run leaves an obvious file to move back.
- The CEF runtime alias, entry point and runtime attributes are gated on
  Linux as well as the `cef` feature; elsewhere the feature is a no-op.
- Pointer-driven window move/resize applies the last pointer delta when
  the button is released before the pending animation frame, and does
  not start tracking when the button was already released while the
  window state was being read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 20:09:06 +02:00

456 lines
18 KiB
YAML

name: PR checks
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
# Evaluation only: the flake exposes no `checks`, so this verifies that every
# output still evaluates across systems without compiling anything. The full
# build runs in nix-build.yml on main, which is also what populates the cache.
# Only on pull_request: main pushes are already covered by nix-build.yml, and
# running both there would build and push the same paths twice.
nix_flake_check:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24
# skipPush is a cachix-action input, not an install-nix-action one. The
# cache is public, so pulling needs no token at all; omitting authToken
# keeps the push token out of PR-triggered builds entirely.
- uses: cachix/cachix-action@38b082610b782e7e93e209c35fd730d399dee866
with:
name: readest
skipPush: true
- name: check flake
run: nix flake check --all-systems -L
# The Rust format/clippy/test steps only run when src-tauri changed, so the
# ~45s toolchain + system-dep install (and the compile itself) is skipped on
# PRs that don't touch the Rust backend. The job still runs and reports
# success so it stays green as a required check.
rust_lint:
runs-on: ubuntu-latest
# pull-requests: read lets dorny/paths-filter list a PR's changed files
# via the REST API (the top-level grant is contents: read only).
permissions:
contents: read
pull-requests: read
env:
RUSTFLAGS: '-C target-cpu=skylake'
SCCACHE_GHA_ENABLED: 'true'
RUSTC_WRAPPER: sccache
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: 'true'
- name: detect src-tauri changes
id: changes
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4
with:
filters: |
tauri:
- 'apps/readest-app/src-tauri/**'
- name: setup sccache
if: steps.changes.outputs.tauri == 'true'
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
- name: Install minimal stable with clippy and rustfmt
if: steps.changes.outputs.tauri == 'true'
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
with:
toolchain: stable
override: true
components: rustfmt, clippy
- name: Cache apt packages
if: steps.changes.outputs.tauri == 'true'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /var/cache/apt/archives
key: apt-rust-lint-${{ runner.os }}
- name: Install system dependencies
if: steps.changes.outputs.tauri == 'true'
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libfontconfig-dev libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libsoup-3.0-dev
- name: Format check
if: steps.changes.outputs.tauri == 'true'
working-directory: apps/readest-app/src-tauri
run: cargo fmt --check
- name: Clippy Check
if: steps.changes.outputs.tauri == 'true'
working-directory: apps/readest-app/src-tauri
run: cargo clippy -p Readest --no-deps -- -D warnings
- name: Unit tests
if: steps.changes.outputs.tauri == 'true'
working-directory: apps/readest-app/src-tauri
run: cargo test -p Readest --lib
# apps/readest.koplugin/native/localsend-bin is a standalone Cargo crate
# (not part of the src-tauri workspace), built into the koplugin's release
# libs by the release workflow. Before this job it had no PR-time CI at
# all. Paths-filtered like rust_lint so most PRs skip the toolchain install.
koplugin_rust_lint:
runs-on: ubuntu-latest
# pull-requests: read lets dorny/paths-filter list a PR's changed files
# via the REST API (the top-level grant is contents: read only).
permissions:
contents: read
pull-requests: read
env:
SCCACHE_GHA_ENABLED: 'true'
RUSTC_WRAPPER: sccache
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: 'true'
- name: detect localsend-bin changes
id: changes
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4
with:
filters: |
native:
- 'apps/readest.koplugin/native/**'
- name: setup sccache
if: steps.changes.outputs.native == 'true'
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
- name: Install minimal stable with clippy and rustfmt
if: steps.changes.outputs.native == 'true'
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
with:
toolchain: stable
override: true
components: rustfmt, clippy
- name: Format check
if: steps.changes.outputs.native == 'true'
working-directory: apps/readest.koplugin/native/localsend-bin
run: cargo fmt --check
- name: Clippy Check
if: steps.changes.outputs.native == 'true'
working-directory: apps/readest.koplugin/native/localsend-bin
run: cargo clippy --all-targets -- -D warnings
- name: Unit tests
if: steps.changes.outputs.native == 'true'
working-directory: apps/readest.koplugin/native/localsend-bin
run: cargo test
build_web_app:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: 'true'
- name: setup pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
- name: setup node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6
with:
node-version: 24
cache: pnpm
- name: install Dependencies
working-directory: apps/readest-app
run: |
pnpm install --frozen-lockfile --prefer-offline && pnpm setup-vendors
- name: run format check
run: |
pnpm format:check || (pnpm format && git diff && exit 1)
# pnpm lint here is web-only (tsc + biome). The koplugin syntax check
# (lint:lua) runs in the test_extensions job, which installs LuaJIT only
# when the koplugin sources changed.
- name: run lint
working-directory: apps/readest-app
run: |
pnpm lint
- name: build the web app
working-directory: apps/readest-app
run: |
pnpm build-web && pnpm check:all
- name: cache playwright browsers
id: playwright-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: install playwright browsers
working-directory: apps/readest-app
run: |
if [ "${{ steps.playwright-cache.outputs.cache-hit }}" = 'true' ]; then
npx playwright install-deps chromium
else
npx playwright install --with-deps chromium
fi
- name: run web e2e tests
id: web_e2e
working-directory: apps/readest-app
run: pnpm test:e2e:web
- name: upload e2e report
if: ${{ failure() && steps.web_e2e.outcome == 'failure' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-report
path: apps/readest-app/playwright-report/
retention-days: 7
# The jsdom unit suite is the slowest part of the PR checks, so it is split
# across two parallel shards (vitest --shard). The browser tests need
# Playwright and run only on shard 1; koplugin + browser-extension tests
# moved to the test_extensions job.
test_web_app:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: 'true'
- name: setup pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
- name: setup node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6
with:
node-version: 24
cache: pnpm
- name: install Dependencies
working-directory: apps/readest-app
run: |
pnpm install --frozen-lockfile --prefer-offline && pnpm setup-vendors
# Playwright is only needed by the browser tests, which run on shard 1.
- name: cache playwright browsers
if: matrix.shard == 1
id: playwright-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
- name: install playwright browsers
if: matrix.shard == 1
working-directory: apps/readest-app
run: |
if [ "${{ steps.playwright-cache.outputs.cache-hit }}" = 'true' ]; then
npx playwright install-deps chromium
else
npx playwright install --with-deps chromium
fi
- name: run web unit tests (shard ${{ matrix.shard }}/2)
working-directory: apps/readest-app
run: pnpm test:pr:web:unit --shard=${{ matrix.shard }}/2
- name: run web browser tests
if: matrix.shard == 1
working-directory: apps/readest-app
run: pnpm test:browser
# Browser-extension tests + build always run. The koplugin lint + Lua tests
# (and the ~45s LuaJIT/busted install they need) only run when the koplugin
# sources changed, so most PRs skip that cost entirely.
test_extensions:
runs-on: ubuntu-latest
# pull-requests: read lets dorny/paths-filter list a PR's changed files
# via the REST API (the top-level grant is contents: read only).
permissions:
contents: read
pull-requests: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: 'true'
- name: detect koplugin changes
id: changes
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4
with:
filters: |
koplugin:
- 'apps/readest.koplugin/**'
- name: setup pnpm
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
- name: setup node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6
with:
node-version: 24
cache: pnpm
- name: install Dependencies
working-directory: apps/readest-app
run: |
pnpm install --frozen-lockfile --prefer-offline && pnpm setup-vendors
- name: run extension tests
working-directory: apps/readest-app
run: pnpm test:extension
- name: build browser extension
working-directory: apps/readest-app
run: pnpm build-browser-ext
- name: cache apt packages
if: steps.changes.outputs.koplugin == 'true'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /var/cache/apt/archives
key: apt-test-koplugin-${{ runner.os }}
- name: install LuaJIT + busted (for koplugin lint + tests)
if: steps.changes.outputs.koplugin == 'true'
run: |
sudo apt-get update
# luajit — pnpm lint:lua + pnpm test:lua
# luarocks/libsqlite3-dev — required to build lsqlite3complete
sudo apt-get install -y luajit luarocks libsqlite3-dev
# Install busted + the SQLite binding the LibraryStore specs use,
# both pinned to Lua 5.1 (LuaJIT-compatible). System-wide install
# so `luarocks --lua-version=5.1 path` (sourced by
# scripts/test-koplugin.mjs) picks them up.
sudo luarocks --lua-version=5.1 install busted
sudo luarocks --lua-version=5.1 install lsqlite3complete
- name: lint koplugin
if: steps.changes.outputs.koplugin == 'true'
working-directory: apps/readest-app
run: pnpm lint:lua
- name: run koplugin tests
if: steps.changes.outputs.koplugin == 'true'
working-directory: apps/readest-app
run: pnpm test:lua
# The desktop app build + webdriver tests need a full Rust compile, by far
# the slowest PR check, so every step after checkout is skipped when neither
# the Rust backend, the vendored native packages, nor the tauri test harness
# changed. Frontend-only PRs are covered by the web build/test jobs. The job
# still runs and reports success so it stays green as a required check.
build_tauri_app:
runs-on: ubuntu-latest
# pull-requests: read lets dorny/paths-filter list a PR's changed files
# via the REST API (the top-level grant is contents: read only).
permissions:
contents: read
pull-requests: read
env:
SCCACHE_GHA_ENABLED: 'true'
RUSTC_WRAPPER: sccache
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: 'true'
- name: detect tauri-related changes
id: changes
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4
with:
filters: |
tauri:
- 'apps/readest-app/src-tauri/**'
- 'apps/readest-app/src/**/*.tauri.test.ts'
- 'apps/readest-app/vitest.tauri.config.mts'
- 'apps/readest-app/vitest.tauri.setup.ts'
- 'apps/readest-app/scripts/test-tauri.sh'
- 'packages/**'
- 'pnpm-lock.yaml'
- '.github/workflows/pull-request.yml'
- name: setup pnpm
if: steps.changes.outputs.tauri == 'true'
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
- name: setup node
if: steps.changes.outputs.tauri == 'true'
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6
with:
node-version: 24
cache: pnpm
# The tauri tests run `next dev`, whose Turbopack cache lives in
# `.next/dev/cache` (a different path from the `next build` cache).
- name: cache Turbopack dev cache
if: steps.changes.outputs.tauri == 'true'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: apps/readest-app/.next/dev/cache
key: turbo-dev-tauri-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
turbo-dev-tauri-${{ runner.os }}-
- name: install Dependencies
if: steps.changes.outputs.tauri == 'true'
working-directory: apps/readest-app
run: |
pnpm install --frozen-lockfile --prefer-offline && pnpm setup-vendors
- name: setup sccache
if: steps.changes.outputs.tauri == 'true'
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
- name: install Rust toolchain
if: steps.changes.outputs.tauri == 'true'
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1
with:
toolchain: stable
# This action defaults RUSTFLAGS to -D warnings. Path-patched
# dependencies (packages/tao, packages/tauri) build without
# cap-lints, so upstream warnings in them (e.g. glib deprecations
# in tao's Linux backend) would fail the app build. Warnings in
# our own crate are enforced by the rust_lint job instead.
rustflags: ''
# Disable this action's built-in rust-cache so the explicit
# Swatinem/rust-cache below is the single cache (it's the one
# configured with cache-workspace-crates for the vendored tauri fork).
cache: false
- uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
if: steps.changes.outputs.tauri == 'true'
with:
# cache-workspace-crates caches the path/workspace crates too: the
# vendored tauri fork (packages/tauri, wired via [patch.crates-io])
# and the local src-tauri/plugins/*. These are
# workspace members that rust-cache prunes by default, so the whole
# tauri stack — plus every crates.io plugin that depends on the
# patched `tauri` — rebuilt on every run. They change only
# sporadically (pinned submodules), so caching them is a big win.
# The key is bumped (-ws) so the old workspace-crate-less cache is
# invalidated and the next run repopulates it with the workspace
# crates included.
key: tauri-cargo-ws
cache-all-crates: 'true'
cache-workspace-crates: 'true'
- name: Cache apt packages
if: steps.changes.outputs.tauri == 'true'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /var/cache/apt/archives
key: apt-tauri-${{ runner.os }}
- name: install system dependencies
if: steps.changes.outputs.tauri == 'true'
run: |
sudo apt-get update
sudo apt-get install -y pkg-config libfontconfig-dev libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libsoup-3.0-dev xvfb
- name: run tauri tests
if: steps.changes.outputs.tauri == 'true'
working-directory: apps/readest-app
run: xvfb-run pnpm test:pr:tauri