ci(nightly): set the electron-builder publish channel for nightly builds (#1611)

This commit is contained in:
4gray authored and GitHub committed 2026-09-16 07:50:50 +02:00
1 parent d3dee05a84
commit 9a3aa63490
6 files changed
+123 -14

No files matched your search

+3 -1
View File
@@ -493,7 +493,9 @@ jobs:
# offer it, and it must be in package.json before the frontend
# and backend builds and electron-builder read it. The value
# comes from the nightly-version job so every job of this run
# builds the same version.
# builds the same version. The script also sets the electron-
# builder publish channel to "nightly", which names the updater
# metadata nightly-mac.yml / nightly.yml / nightly-linux.yml.
if: needs.nightly-version.outputs.version != ''
# Windows runners default to PowerShell, where "${NIGHTLY_VERSION}"
# expands to nothing and the script rejects the empty version.
+1 -1
View File
@@ -1715,7 +1715,7 @@ The factory pattern ensures a single codebase works in both environments without
CI injects the git commit into `apps/web/src/environments/build-commit.ts` via `tools/build/inject-build-commit.mjs` (same placeholder pattern as the TMDB key inject); `Settings > About` then shows `"<version> (<short-sha>)"`. The semver version itself stays untouched on PR and tag builds — a `-sha` suffix would flip electron-updater into prerelease mode and leak into installer/artifact version fields. Local/dev builds keep the placeholder empty and show the plain version.
**Nightly Builds And Update Channel**:
Master pushes are the nightly channel. The leading `nightly-version` job computes one `<patch>-nightly.<commit date>.<run number>` version per run (`tools/release/nightly-version.mjs`; the patch is bumped only when `v<package.json version>` already exists on origin, so the release-cut window stays below the imminent release), every build job writes it into `package.json` (`--apply --version`), and the `create-release` job publishes the artifacts as a prerelease of `4gray/iptvnator-nightly` (secret `NIGHTLY_RELEASE_TOKEN`; missing token only warns) instead of the rolling `test-master` draft, keeping the newest 20. electron-builder names the updater metadata after the prerelease tag (`nightly-mac.yml` / `nightly.yml` / `nightly-linux.yml`), so upload globs and the macOS merge accept both names. `Settings.updateChannel` (Settings → About, Electron only, default `stable`) is mirrored into the main-process config (`APP_UPDATE_CHANNEL`, `app-update-channel.ts`) for the startup check; `AppUpdateService` applies the channel to electron-updater before every check (`app-update-feed.ts`: feed repository, `allowPrerelease`, channel name, and `allowDowngrade` forced back to `false` — assigning a channel silently enables downgrades) and reads release notes from the repository the requested version belongs to (`app-update-release-catalog.ts`). Switching back to stable is forward-only: the nightly stays until a newer stable release exists, because a downgrade could hit a database schema a nightly migration already applied. Contract: `docs/architecture/release-pipeline.md` ("Nightly channel").
Master pushes are the nightly channel. The leading `nightly-version` job computes one `<patch>-nightly.<commit date>.<run number>` version per run (`tools/release/nightly-version.mjs`; the patch is bumped only when `v<package.json version>` already exists on origin, so the release-cut window stays below the imminent release), every build job writes it into `package.json` and sets `publish[0].channel: nightly` in `electron-builder.json` (`--apply --version`; electron-builder does not derive the channel from the prerelease tag for the GitHub provider), and the `create-release` job publishes the artifacts as a prerelease of `4gray/iptvnator-nightly` (secret `NIGHTLY_RELEASE_TOKEN`; missing token only warns) instead of the rolling `test-master` draft, keeping the newest 20. the explicit publish channel names the updater metadata `nightly-mac.yml` / `nightly.yml` / `nightly-linux.yml`, and upload globs plus the macOS merge accept both names. `Settings.updateChannel` (Settings → About, Electron only, default `stable`) is mirrored into the main-process config (`APP_UPDATE_CHANNEL`, `app-update-channel.ts`) for the startup check; `AppUpdateService` applies the channel to electron-updater before every check (`app-update-feed.ts`: feed repository, `allowPrerelease`, channel name, and `allowDowngrade` forced back to `false` — assigning a channel silently enables downgrades) and reads release notes from the repository the requested version belongs to (`app-update-release-catalog.ts`). Switching back to stable is forward-only: the nightly stays until a newer stable release exists, because a downgrade could hit a database schema a nightly migration already applied. Contract: `docs/architecture/release-pipeline.md` ("Nightly channel").
### Testing Strategy
+7 -3
View File
@@ -299,9 +299,13 @@ frontend and backend builds and before electron-builder reads it
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 same `--apply` sets `publish[0].channel: nightly` in
`electron-builder.json`, which names the updater metadata
`nightly-mac.yml`, `nightly.yml`, `nightly-linux.yml`. electron-builder does
not derive that name from the prerelease tag for the GitHub provider (the
first nightly run produced `latest-*.yml` and the publish step refused
it). 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.
@@ -24,9 +24,10 @@ export const DEFAULT_APP_UPDATE_CHANNEL: AppUpdateChannel = 'stable';
/**
* Prerelease identifier every nightly version carries
* (`0.23.1-nightly.20260915.1234`). electron-builder derives the updater
* channel file (`nightly-mac.yml`, `nightly.yml`, `nightly-linux.yml`) from
* it, and electron-updater matches release tags on the same identifier.
* (`0.23.1-nightly.20260915.1234`). The nightly build sets the same name as
* electron-builder's publish channel, which names the updater metadata
* (`nightly-mac.yml`, `nightly.yml`, `nightly-linux.yml`), and
* electron-updater matches release tags on the same identifier.
*/
export const NIGHTLY_PRERELEASE_TAG = 'nightly';
+52 -6
View File
@@ -16,8 +16,12 @@
* - The date is the commit's UTC date and the last identifier is the
* workflow run number, which only ever grows, so nightlies order
* correctly across days and within one day.
* - electron-builder derives the updater channel file names from the
* prerelease tag (`nightly-mac.yml`, `nightly.yml`, `nightly-linux.yml`).
* - `--apply` also sets `publish[0].channel` to `nightly` in
* `electron-builder.json`, which is what names the updater metadata
* `nightly-mac.yml` / `nightly.yml` / `nightly-linux.yml`. electron-builder
* does NOT derive that name from the prerelease tag for the GitHub
* provider: the first nightly run shipped `latest-*.yml` files and the
* publish step refused them.
*
* A release cut commits the new version to master before (or together
* with) its tag. While `v<package.json version>` does not exist yet, that
@@ -36,6 +40,7 @@
* Usage:
* node tools/release/nightly-version.mjs # print
* node tools/release/nightly-version.mjs --apply --version X # write X
* into package.json + electron-builder.json
*
* Inputs default to the repository `package.json`, the HEAD commit date,
* `GITHUB_RUN_NUMBER`, and a `git ls-remote` probe for the base tag;
@@ -108,6 +113,26 @@ export function parseBooleanFlag(value, flag) {
throw new Error(`${flag} must be "true" or "false", got "${value}".`);
}
/**
* Points electron-builder's updater metadata at the nightly channel. Written
* as 4-space JSON like `configure-linux-frame-copy-build.mjs`, which has
* already rewritten the file the same way on Linux by the time this runs.
*/
export function applyNightlyPublishChannel(electronBuilderJsonText) {
const config = JSON.parse(electronBuilderJsonText);
const publish = Array.isArray(config.publish) ? config.publish : null;
if (!publish || publish.length === 0 || publish[0]?.provider !== 'github') {
throw new Error(
'electron-builder.json must declare a github publish provider first.'
);
}
publish[0] = { ...publish[0], channel: NIGHTLY_TAG };
return `${JSON.stringify(config, null, 4)}\n`;
}
/** Replaces the `version` line in `package.json` text, formatting intact. */
export function applyNightlyVersion(packageJsonText, version) {
if (!PACKAGE_VERSION_LINE.test(packageJsonText)) {
@@ -239,11 +264,32 @@ function main(argv) {
}
if (options.apply) {
writeFileSync(
packageJsonPath,
applyNightlyVersion(packageJsonText, version)
// Resolve both replacements before touching either file, so a bad
// electron-builder.json cannot leave package.json half-applied.
const electronBuilderJsonPath = new URL(
'../../electron-builder.json',
import.meta.url
);
let nextPackageJson;
let nextElectronBuilderJson;
try {
nextPackageJson = applyNightlyVersion(packageJsonText, version);
nextElectronBuilderJson = applyNightlyPublishChannel(
readFileSync(electronBuilderJsonPath, 'utf8')
);
} catch (error) {
console.error(
error instanceof Error ? error.message : String(error)
);
return 1;
}
writeFileSync(packageJsonPath, nextPackageJson);
writeFileSync(electronBuilderJsonPath, nextElectronBuilderJson);
console.error(
`Applied nightly version ${version} to package.json and the nightly publish channel to electron-builder.json.`
);
console.error(`Applied nightly version ${version} to package.json.`);
}
console.log(version);
+56
View File
@@ -1,6 +1,8 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { describe, it } from 'node:test';
import {
applyNightlyPublishChannel,
applyNightlyVersion,
buildNightlyVersion,
isNightlyVersion,
@@ -164,6 +166,60 @@ describe('applyNightlyVersion', () => {
});
});
describe('applyNightlyPublishChannel', () => {
it('sets the github publish channel and keeps the rest of the config', () => {
const source = JSON.stringify(
{
appId: 'x',
publish: [{ provider: 'github', owner: '4gray', repo: 'iptvnator' }],
mac: { target: ['dmg'] },
},
null,
4
);
const result = applyNightlyPublishChannel(source);
assert.deepEqual(JSON.parse(result), {
appId: 'x',
publish: [
{
provider: 'github',
owner: '4gray',
repo: 'iptvnator',
channel: 'nightly',
},
],
mac: { target: ['dmg'] },
});
assert.ok(result.endsWith('}\n'));
assert.match(result, /^\{\n {4}"appId"/);
});
it('applies to the repository config as committed', () => {
const committed = readFileSync(
new URL('../../electron-builder.json', import.meta.url),
'utf8'
);
assert.equal(
JSON.parse(applyNightlyPublishChannel(committed)).publish[0].channel,
'nightly'
);
});
it('refuses a config without a github publish provider', () => {
assert.throws(
() => applyNightlyPublishChannel('{"publish":[{"provider":"generic"}]}'),
/github publish provider/
);
assert.throws(
() => applyNightlyPublishChannel('{}'),
/github publish provider/
);
});
});
describe('explicit version input', () => {
it('accepts only the shape the script itself produces', () => {
assert.equal(isNightlyVersion('0.23.1-nightly.20260915.1234'), true);