feat(updater): nightly builds and a stable/nightly update channel (#1608)

* feat(updater): nightly builds and a stable/nightly update channel

Every master push publishes its artifacts as a prerelease of
4gray/iptvnator-nightly instead of the rolling test-master draft, with a
version of <next patch>-nightly.<commit date>.<run number> applied in
every build job. Settings → About gains an Update channel switch;
AppUpdateService re-points electron-updater per check (feed repository,
allowPrerelease, channel name, allowDowngrade reset) and reads release
notes from the repository the requested version belongs to. Channel
switches are forward-only: a nightly build stays until a newer stable
release exists.

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

* fix(updater): compute the nightly version once and keep re-runs safe

Review follow-ups: the nightly version is resolved by a leading job and
handed to every build job, and the patch is bumped only when the base
tag already exists so the release-cut window stays below the imminent
release. A re-run never deletes a published nightly; only a draft left
by a failed run is replaced. Typed update-status literals in the
remaining specs carry the new channel fields.

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

* test(packaging): expect the nightly-version prerequisite in the build workflow graph

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5.1 authored and GitHub committed 2026-09-15 17:49:22 +02:00
1 parent 7f724494b9
commit 6f7973a9fb
57 files changed
+2249 -229

No files matched your search

+87
View File
@@ -276,6 +276,93 @@ References: [AppImage desktop keys](https://docs.appimage.org/reference/desktop-
[AppManager desktop parser](https://github.com/kem-a/AppManager/blob/v3.8.0/src/core/desktop_entry.vala),
[AppManager updater](https://github.com/kem-a/AppManager/blob/v3.8.0/src/core/updater.vala).
## Nightly channel
Every push to `master` of `4gray/iptvnator` is also a nightly. The same
`build-and-make.yaml` run that builds the matrix publishes its artifacts as a
**prerelease of `4gray/iptvnator-nightly`** instead of the rolling
`test-master` draft: a draft is invisible to anyone without write access and
to electron-updater, while a published prerelease is what the desktop app's
**Nightly** update channel installs. PR builds keep their `test-pr-<n>`
drafts; tag builds are unaffected.
**Version.** Each build job rewrites the `package.json` version before the
frontend and backend builds and before electron-builder reads it
(`tools/release/nightly-version.mjs --apply`):
```
0.23.0 → 0.23.1-nightly.20260915.1234
└ next patch ┘ └ commit date ┘ └ run number ┘
```
- Greater than the released `0.23.0`, so a stable user who switches channels
is offered it; smaller than `0.23.1` and `0.24.0`, so the next stable
release is offered to nightly users on either channel.
- The run number only grows, so nightlies order correctly within a day.
- electron-builder derives the updater channel files from the prerelease
tag: `nightly-mac.yml`, `nightly.yml`, `nightly-linux.yml`. The artifact
upload globs and the macOS metadata merge accept both names.
- The root `package.json` is an Nx `sharedGlobals` input, so the rewritten
version reaches the `web` and `electron-backend` bundles (which embed it)
instead of a cache hit built from the released version.
- The base is the version in `package.json`. The patch is bumped only when
`v<base>` already exists on origin. A release cut commits the bump before
(or together with) its tag, and while that tag is missing the base is the
UPCOMING release, so the nightly keeps its patch
(`0.23.1` untagged → `0.23.1-nightly.<date>.<run>`): still above every
earlier nightly, still below the imminent `0.23.1`, so nightly users are
offered that release instead of skipping it.
- The version is computed once, in the leading `nightly-version` job, and
handed to every build job as `--version` — a tag pushed while the matrix
runs cannot give one run two different versions. The release job reads
the same output to name the tag `v<version>`.
**Publication** (steps at the end of the `create-release` job):
1. `NIGHTLY_RELEASE_TOKEN` — a fine-grained PAT with *Contents: read/write*
on `4gray/iptvnator-nightly` — is required. Without it the run only warns;
`GITHUB_TOKEN` cannot write to another repository. The nightly repository
needs one commit on its default branch, because `gh release create`
creates the release tag there.
2. The notes list the master commits since the previous nightly. That
nightly's source commit is read back from the `<!-- iptvnator-commit: … -->`
marker its own notes carry, then `compare` on the main repository (with
`GITHUB_TOKEN`) lists the range. A missing marker or a rewritten history
only drops the list.
3. The release is created as a draft, assets are uploaded, then it is
published in one edit, so electron-updater never sees a release whose
channel file is still missing. Missing `nightly-mac.yml`,
`nightly.yml` or `nightly-linux.yml` fails the step instead. A published
release is never deleted by a re-run: re-running after a successful
publish is a no-op, and only a draft left behind by a failed run is
replaced.
4. The release job is serialized per ref, but two master runs can finish out
of order. electron-updater takes the newest feed entry, so a nightly older
than the newest published one is dropped rather than published.
5. Only the newest `NIGHTLY_KEEP_RELEASES` (20) nightlies are kept; older
ones are deleted together with their tags.
**In the app.** `Settings.updateChannel` (`stable` / `nightly`, default
`stable`) is a normal renderer setting mirrored into the main-process config
by `SETTINGS_UPDATE` (`APP_UPDATE_CHANNEL`), because the startup check runs
before the renderer exists. `AppUpdateService` re-points electron-updater on
every check (`app-update-feed.ts`): the GitHub feed URL of the channel's
repository, `allowPrerelease` only for nightly, the channel name (`nightly`,
or the explicit `latest` for stable — the setter refuses `null` once set),
and `allowDowngrade = false` reset afterwards, since assigning a channel
silently enables downgrades and the constructor enables prereleases for any
prerelease build. Release notes and the manual-install fallback (Linux
without AppImage) read the channel's release list; notes for a nightly
version always come from the nightly repository, so a nightly build on the
stable channel still shows its own notes.
Switching is forward-only on purpose: a nightly build stays installed until
a newer stable release exists, because a downgrade could land on a release
that does not understand the database schema a nightly migration already
applied. Nightly users therefore accept that everything merged into master
is de facto shipped — a migration on master can only be followed by another
migration, never reworked.
## After verification
Publishing the GitHub release is manual. That publication automatically