This directory documents the Electron release topology for NeverWrite.
Electron now owns the signed desktop release path:
- manual installers live in
GitHub Releases - updater metadata lives in
gh-pages - updater downloads still resolve to
GitHub Releases
GitHub Pages publishes one feed per channel, platform, and architecture:
<channel>/<feed-target>/latest-mac.yml
<channel>/<feed-target>/latest.yml
Current feed targets:
| Build target | Feed target | Metadata file |
|---|---|---|
universal-apple-darwin |
darwin-universal |
latest-mac.yml |
aarch64-pc-windows-msvc |
windows-arm64 |
latest.yml |
x86_64-pc-windows-msvc |
windows-x64 |
latest.yml |
Example published URLs:
https://jsgrrchg.github.io/NeverWrite/stable/darwin-universal/latest-mac.yml
https://jsgrrchg.github.io/NeverWrite/stable/windows-x64/latest.yml
The updater metadata always points back to versioned assets on GitHub Releases.
Each build target uploads:
- one manual installer for humans
- one updater asset for
electron-updater - one blockmap for differential updates
Public naming remains stable per target:
| Build target | Manual asset | Updater asset |
|---|---|---|
universal-apple-darwin |
NeverWrite_<version>_macOS_Universal.dmg |
NeverWrite_<version>_macOS_Universal.zip |
aarch64-pc-windows-msvc |
NeverWrite_<version>_Windows_ARM64_Setup.exe |
NeverWrite_<version>_Windows_ARM64_Setup.exe |
x86_64-pc-windows-msvc |
NeverWrite_<version>_Windows_x64_Setup.exe |
NeverWrite_<version>_Windows_x64_Setup.exe |
The architecture suffix is mandatory for Windows. macOS publishes a universal package and a single universal updater feed. We do not publish shared Windows latest.yml metadata for multiple architectures in the same directory because electron-builder would otherwise collide on Windows metadata names.
The release workflow supports either notarization mode accepted by electron-builder:
- App Store Connect API key
APPLE_API_KEYAPPLE_API_KEY_IDAPPLE_API_ISSUER
- Apple ID + app-specific password
APPLE_IDAPPLE_APP_SPECIFIC_PASSWORDAPPLE_TEAM_ID
macOS code signing also requires:
CSC_LINKCSC_KEY_PASSWORD
Windows releases are distributed unsigned for now. The release workflow disables
certificate auto-discovery for Windows builds and does not require WIN_CSC_*,
Azure Trusted Signing, or other Windows signing secrets.
Unsigned Windows installers can trigger SmartScreen or Defender warnings until a future signed distribution path builds reputation.
The production release entrypoint is:
.github/workflows/release-desktop.yml
High-level flow:
- validate version identity and changelog
- build one target per matrix entry
- smoke the packaged native sidecar
- stage release assets and target metadata as internal workflow artifacts
- publish all release files to
GitHub Releasesafter every target succeeds - publish feeds to
gh-pages - generate a platform validation pack
The release tag is the source of truth for GitHub Actions, but the tag must
match the desktop metadata committed at that tag. Before pushing vX.Y.Z, make
sure these files all refer to X.Y.Z:
apps/desktop/package.jsonapps/desktop/package-lock.jsonapps/desktop/native-backend/Cargo.tomlCHANGELOG.md
Use the helper from the repository root to update the package and native backend version files:
scripts/bump-version.sh X.Y.ZThen add the matching CHANGELOG.md release entry and run the same validation
that the release workflow runs:
node scripts/validate-release-metadata.mjs --tag vX.Y.ZDo not push the tag until this check passes. The app's user-visible Electron
version comes from apps/desktop/package.json; the lockfile and native backend
version are kept aligned so packaging and release validation remain
deterministic.
From apps/desktop:
npm run electron:build
npm run electron:package:unsigned
npm run electron:dist:mac
npm run electron:dist:win -- --arch x64
npm run electron:dist:win -- --arch arm64The release wrapper is target-aware and stages the correct Rust sidecar for the selected architecture before calling electron-builder. For universal macOS builds, CI downloads both Node runtimes and stages a lipo'd embedded Node binary via NEVERWRITE_EMBEDDED_NODE_BIN_ARM64 and NEVERWRITE_EMBEDDED_NODE_BIN_X64.
The runtime updater is intentionally strict:
- packaged builds only allow production
httpsfeeds by default - non-packaged builds only allow loopback or
file:feeds by default - feed hosts and download hosts can be allowlisted explicitly
- production downloads default to
github.com
Runtime knobs:
- Packaged builds default to
https://jsgrrchg.github.io/NeverWrite. - The env vars below are overrides for local validation, staging feeds, or one-off diagnostics.
NEVERWRITE_UPDATER_BASE_URLNEVERWRITE_UPDATER_ENDPOINTNEVERWRITE_UPDATER_CHANNELNEVERWRITE_UPDATER_ALLOWED_FEED_HOSTSNEVERWRITE_UPDATER_ALLOWED_DOWNLOAD_HOSTSNEVERWRITE_UPDATER_ALLOW_PRODUCTION_ENDPOINTS_IN_NON_PROD
Validation pack generation:
node scripts/build-platform-validation-pack.mjs \
--version 0.2.0 \
--tag v0.2.0 \
--channel stable \
--feeds-dir .artifacts/feeds \
--metadata-dir .artifacts/release-targets \
--output-dir dist/platform-validation/v0.2.0The validation pack includes:
- target-specific valid feeds
- target-specific invalid-checksum fixtures
- a checklist for clean install, update, target routing, and sensitive-state confirmation
Rollback means publishing feed metadata that no longer points to the defective version.
Because the updater reads target-specific feeds, rollback can be:
- global for every target in a channel, or
- scoped to one target if only one architecture is affected
Do not delete release assets as the first reaction. First stop advertising the bad version from the published feed for the affected targets.