ci: release only for v* tags or manual runs; CI on main; Android SDK packages

- main is the long-lived development branch: release.yml no longer runs on
  a push to it, only on v* tags and workflow_dispatch. ci.yml now runs on
  every branch push, main included.
- android-actions/setup-android's default package list includes `tools`,
  which current command-line tools no longer carry, so sdkmanager failed;
  install exactly platform-tools, android-35 and build-tools 35.0.1.
- reader-chrome.test.ts left its React roots mounted; a scheduled commit
  could land after jsdom teardown and fail the run with an unhandled
  "Right-hand side of 'instanceof' is not an object" (all tests passing).
  Unmount in afterEach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
hectorqinandClaude Opus 5.5 committed 2026-10-27 10:19:56 +00:00
1 parent 5ce7b2f682
commit 4e149c73d7
4 files changed
+22 -11

No files matched your search

+9 -6
View File
@@ -1,14 +1,14 @@
# Build and test: server (Node/TypeScript), web client, UI review, Android shell.
#
# Runs on pull requests and on pushes to any branch except main. Pushes to main and
# release tags run it through release.yml (workflow_call), before any image is
# published, so main is tested exactly once per push.
# Runs on pull requests and on pushes to every branch, main included (main is the
# long-lived development branch). Release tags and manual releases run it through
# release.yml (workflow_call) before any image is published.
name: CI
on:
pull_request:
push:
branches-ignore: [main]
branches: ['**']
workflow_call:
workflow_dispatch:
@@ -112,9 +112,12 @@ jobs:
distribution: temurin
java-version: 17
cache: gradle
# The hosted runner ships the Android SDK; this only accepts and adds what the
# build asks for (platform 35, build-tools 35.0.1).
# The hosted runner ships the Android SDK; this accepts the licences and adds
# exactly what the build asks for. The action's default package list includes
# `tools`, which current command-line tools no longer carry (sdkmanager fails).
- uses: android-actions/setup-android@v3
with:
packages: platform-tools platforms;android-35 build-tools;35.0.1
- run: ./gradlew --no-daemon :app:assembleDebug
working-directory: android
- uses: actions/upload-artifact@v4
+4 -3
View File
@@ -1,8 +1,10 @@
# Release the single-container image (server + web) to Docker Hub for amd64 and arm64.
#
# push to main -> hectorqin/reader-pro:main, :sha-<short>
# tag v1.2.3 -> hectorqin/reader-pro:1.2.3, :1.2, :latest, :sha-<short>
# manual run -> tags for the chosen ref, as above
# manual run -> the chosen branch's name (e.g. :main) and :sha-<short>
#
# Never on a plain push: main is the long-lived development branch, and a release
# is a decision (a tag, or "Run workflow" in the Actions tab), not a side effect.
#
# CI (ci.yml) runs first; nothing is published from a red build. Each architecture
# is built on its own native runner — the arm64 image under QEMU would run npm ci
@@ -15,7 +17,6 @@ name: Release
on:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
+1 -1
View File
@@ -14,7 +14,7 @@ Docs (mostly Chinese) live in `docs/`; `docs/architecture.md` explains *why* thi
## Commands
Run from repo root (or `cd` into the package). CI runs install → typecheck → test → build per package: GitHub Actions `.github/workflows/ci.yml` (PRs and non-main pushes; also server, UI review, Android shell check + debug APK) and CNB `.cnb.yml`. `.github/workflows/release.yml` runs CI on main pushes and `v*` tags, then publishes the multi-arch (amd64 + arm64, native runners) image to Docker Hub `hectorqin/reader-pro` (`main`/`sha-*`, or `x.y.z`/`x.y`/`latest` for tags); it needs the `DOCKER_USERNAME`/`DOCKER_PASSWORD` secrets. `android/scripts/check-shell.sh` checks the JS bridge contract: only `@JavascriptInterface` methods count, and each must be declared in `web/src/android-bridge.d.ts` (method syntax, `name?(): T` for optional).
Run from repo root (or `cd` into the package). CI runs install → typecheck → test → build per package: GitHub Actions `.github/workflows/ci.yml` (PRs and every branch push; also UI review, Android shell check + debug APK) and CNB `.cnb.yml`. `main` is the long-lived development branch and never releases on push: `.github/workflows/release.yml` runs only for `v*` tags or a manual run, runs CI first, then publishes the multi-arch (amd64 + arm64, native runners) image to Docker Hub `hectorqin/reader-pro` (`x.y.z`/`x.y`/`latest` + `sha-*` for tags; branch name + `sha-*` for manual runs); it needs the `DOCKER_USERNAME`/`DOCKER_PASSWORD` secrets. `android/scripts/check-shell.sh` checks the JS bridge contract: only `@JavascriptInterface` methods count, and each must be declared in `web/src/android-bridge.d.ts` (method syntax, `name?(): T` for optional).
```sh
npm ci --prefix server && npm ci --prefix web
+8 -1
View File
@@ -12,7 +12,7 @@ import { render } from '../src/shared/ui/render-root.ts';
* of them — stop — was pushed off the right edge and the reader could not stop
* the voice at all (没法停止).
*/
import { beforeEach, describe, expect, it } from 'vitest';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { createElement as h } from 'react';
import { ReaderChrome, type ChromeState, type ChromeHandlers, type ReaderThemeState } from '../src/ui/reader-chrome.tsx';
import { defaultThemePreference, type ThemePreference } from '../src/shared/theme/theme.ts';
@@ -124,6 +124,13 @@ describe('ReaderChrome', () => {
container = document.createElement('div');
document.body.append(container);
});
// Unmount before jsdom is torn down: a root left mounted can still commit a
// scheduled update afterwards, which surfaced in CI as an unhandled
// "Right-hand side of 'instanceof' is not an object" from react-dom.
afterEach(() => {
render(null, container);
container.remove();
});
function paint(state: Partial<ChromeState>): HTMLElement {
const stage = document.createElement('div');