From 4e149c73d7d092d1e38b2ec33dba3496bcee0cfc Mon Sep 17 00:00:00 2001 From: hectorqin Date: Tue, 27 Oct 2026 10:19:56 +0000 Subject: [PATCH] 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 --- .github/workflows/ci.yml | 15 +++++++++------ .github/workflows/release.yml | 7 ++++--- CLAUDE.md | 2 +- web/test/reader-chrome.test.ts | 9 ++++++++- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5f62d0..c139bcd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 156fe67..78fcc35 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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- # tag v1.2.3 -> hectorqin/reader-pro:1.2.3, :1.2, :latest, :sha- -# manual run -> tags for the chosen ref, as above +# manual run -> the chosen branch's name (e.g. :main) and :sha- +# +# 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: diff --git a/CLAUDE.md b/CLAUDE.md index af76efa..283f2bc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/web/test/reader-chrome.test.ts b/web/test/reader-chrome.test.ts index d15a404..edeb9d9 100644 --- a/web/test/reader-chrome.test.ts +++ b/web/test/reader-chrome.test.ts @@ -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): HTMLElement { const stage = document.createElement('div');