From b3ec238ebdb4734f32681769caeb561da8c483ba Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Wed, 2 Sep 2026 10:35:04 -0700 Subject: [PATCH] Cut desktop releases every six hours --- .github/workflows/publish.yml | 12 ++++++------ claude.md | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8bbf8367..6e2c6100 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,9 +1,9 @@ name: Publish # Three ways to cut a stable release: -# 1. The nightly schedule, 03:00 UTC. Skipped when main has not moved since -# the latest stable release. This is the normal path: a build ships -# every night without anyone dispatching it. +# 1. The schedule, every six hours. Skipped when main has not moved since +# the latest stable release. This is the normal path: a build ships a +# few hours after a change lands without anyone dispatching it. # 2. Actions → Publish → "Run workflow" — version optional (empty = next # patch above the highest existing release). Fully CI, no local git. # 3. Push a vX.Y.Z tag (the original path) — still works identically. @@ -12,7 +12,7 @@ name: Publish # feed and the website's download button follow. on: schedule: - - cron: '0 3 * * *' + - cron: '0 */6 * * *' push: tags: - 'v*' @@ -32,8 +32,8 @@ concurrency: jobs: check: # Only the scheduled run can be pointless. It compares main against the - # latest stable release rather than a 24h window so a night the cron - # misses is caught up the next night instead of waiting for a new commit. + # latest stable release rather than a fixed window so a run the cron + # misses is caught up by the next one instead of waiting for a new commit. if: github.event_name != 'schedule' || github.repository == 'Gilbert09/talyn' runs-on: ubuntu-latest outputs: diff --git a/claude.md b/claude.md index fc9a6318..761a53be 100644 --- a/claude.md +++ b/claude.md @@ -21,11 +21,11 @@ Every push to main deploys — treat a push as a production release. All deploy/ - **`test.yml`** — every push + PR. 3-OS matrix (macOS/Windows/Ubuntu): full builds → `typecheck` → `lint` → `npm test`. The only gate — nothing blocks a deploy on it, so don't push red. - **`deploy-backend.yml`** — push to main touching `packages/backend|shared`, `Dockerfile`, `railway.toml` (+ `workflow_dispatch`). Deploys to Railway via CLI token; cutover is health-gated (`/health` does a real DB check and 503s while draining), so a boot-refusing build keeps the old one serving. NOTE: every deploy briefly overlaps old+new instances — the pg advisory locks (`services/advisoryLock.ts`) exist for exactly that window. - **`deploy-marketing.yml`** — push to main touching `apps/marketing/**`. Lint + typecheck gate, then Vercel prebuilt deploy to www.talyn.dev. -- **`publish.yml`** — the **stable release**, cut **every night at 03:00 UTC** and on demand. The scheduled run is skipped when main has not moved since the latest stable release (`/releases/latest` + the compare API, so a night the cron misses is caught up the next night). On demand: Actions → Publish → "Run workflow" (version input optional; empty auto-picks the next patch, and electron-builder creates the release AND the tag, so no local git needed) or push a `vX.Y.Z` tag. Builds **macOS arm64+x64 (signed + notarized), Windows NSIS, and Linux AppImage**, publishing a **full release**, which is what the updater and the talyn.dev download button follow. The tag/input is the single source of truth for the app version (baked into `release/app/package.json` at build time, never committed). A `concurrency` group queues a manual dispatch behind a scheduled run: the `version` job reads the release list at run time, so two runs in flight would stamp the same version. (`nightly.yml`, which shipped arm64-only pre-releases on the same cron to nightly-channel users only, was folded into this in Session 96.) +- **`publish.yml`** — the **stable release**, cut **every six hours** and on demand. The scheduled run is skipped when main has not moved since the latest stable release (`/releases/latest` + the compare API, so a run the cron misses is caught up by the next one). On demand: Actions → Publish → "Run workflow" (version input optional; empty auto-picks the next patch, and electron-builder creates the release AND the tag, so no local git needed) or push a `vX.Y.Z` tag. Builds **macOS arm64+x64 (signed + notarized), Windows NSIS, and Linux AppImage**, publishing a **full release**, which is what the updater and the talyn.dev download button follow. The tag/input is the single source of truth for the app version (baked into `release/app/package.json` at build time, never committed). A `concurrency` group queues a manual dispatch behind a scheduled run: the `version` job reads the release list at run time, so two runs in flight would stamp the same version. (`nightly.yml`, which shipped arm64-only pre-releases on the same cron to nightly-channel users only, was folded into this in Session 96.) **Job shape**: a `version` job resolves the version ONCE and fans it out (three legs each computing "next patch above the latest release" would race), then the **macOS leg runs alone** — it's what creates the GitHub Release (and the tag, on the schedule and dispatch paths) — and only then does a `windows-latest`/`ubuntu-latest` matrix upload into it. Keep that ordering: parallelising all three races three electron-builder processes to create the same release, and chaining means a Windows/Linux failure can't take down a macOS release that already published. **Windows ships unsigned** until an EV cert is bought (SmartScreen warns on first install); setting `CSC_LINK`/`CSC_KEY_PASSWORD` fixes it with no workflow change. -**Update channels**: the desktop picker (Settings → About; persisted in userData via `src/main/updateChannel.ts`, default `stable`) maps to electron-updater's `allowPrerelease`. Since Session 96 nothing publishes a pre-release, so both channels receive the same nightly stable build. The picker is kept so a pre-release track can come back without a client change; removing it (and the "every build as it lands" copy) is an open follow-up. +**Update channels**: the desktop picker (Settings → About; persisted in userData via `src/main/updateChannel.ts`, default `stable`) maps to electron-updater's `allowPrerelease`. Since Session 96 nothing publishes a pre-release, so both channels receive the same stable build. The picker is kept so a pre-release track can come back without a client change; removing it (and the "every build as it lands" copy) is an open follow-up. ## Testing — run only the relevant tests while iterating