Docs

Maintainers

Release Guide

This guide covers release packaging for Windows, macOS, and Linux, plus how GitHub Actions publishes artifacts.

Pre-Flight Checks

Run these before creating a beta or release tag.

Quick check (fast, native-runtime sanity)

npm run preflight:release:quick

Runs everything in the full pre-flight below except the dist build and packaged-artifact checks (--skip-dist): native-module loading (sharp, better-sqlite3, @napi-rs/canvas, lz4-napi, node-llama-cpp), the critical-path baseline comparison and commercial compliance. It also needs a clean git worktree. Use it to catch native-binding breakage and regressions early.

npm run preflight:release

It requires a clean git worktree before it begins. On the current platform this runs:

  1. Native module verification (verify:native-modules)
  2. Critical-path baseline comparison (test:critical-path:twice, unless --skip-critical)
  3. Commercial compliance verification (compliance:commercial:verify, unless --skip-commercial)
  4. Clean release/build
  5. Platform dist build (e.g. dist:win on Windows)
  6. Packaged artifact verification (verify:packaged-artifacts)

What test:critical-path:twice actually does

scripts/stability/compare-baseline.js checks out two temporary git worktrees under .tmp/stability-worktrees/ — the previous release tag (the baseline) and HEAD (the target) — and:

  1. Runs the fixed smoke suite (scripts/stability/run-smoke-suite.js) on the baseline
  2. Runs the same smoke suite on the target
  3. Runs the threshold check (scripts/stability/check-release-thresholds.js), catching:
    • Required-journey failures
    • Baseline-pass to target-fail regressions
    • ≥50% latency regressions from baseline to target

If system-metrics exports are provided to the threshold script, it also enforces fallback-rate guardrails for search and Uncategorized routing.

Wizard regression checks

npx jest --config test/jest.config.js test/components/WelcomePhase.test.js test/components/ModelSetupWizard.test.js
npx jest --config test/jest.config.js test/components/PhaseRenderer.test.js test/phaseTransitions.test.js

Pass criteria — do not tag until all are true

CI workflows that enforce these checks

Release Checklist

  1. Update release metadata
    • Bump version in package.json
    • Move release notes from CHANGELOG.md [Unreleased] to the new version section
  2. Run quality gates
    • npm run ci
  3. Smoke-test installers
    • Windows: run npm run dist:win, install StratoSortStack-Setup-*.exe
    • macOS: on a macOS host, run npm run dist:mac:arm64, open StratoSortStack-*.dmg
    • Linux: on a Linux host, run npm run dist:linux, launch the AppImage or install the .deb
    • Confirm the installed app actually launches after install, not just that packaging succeeded
  4. Verify first-run experience
    • AI setup confirms bundled OCR runtime availability
    • Base-model download works from the app UI
  5. Disposable profile (temp demo, Windows) — optional, for demos or screen recordings without touching your real $env:APPDATA\StratoSort Stack data. See Exact walkthrough: temp demo profile (Windows) below.
  6. Confirm release branding inputs
    • Either keep assets/icons/StratoSortStackLogo.svg (or legacy assets/stratosort-logo.png) in the repo so workflows can regenerate icons/installer graphics
    • Or commit the generated files under assets/icons/ and assets/installer/
  7. Confirm docs
    • Ensure docs/INSTALL_GUIDE.md matches current installer names and OS prompts
    • Ensure release references are updated for the upcoming tag:
      • README.md version badge
      • CHANGELOG.md compare links ([Unreleased] and new version anchor)
      • .github/WORKFLOW_README.md artifact names/checksum filenames

Older macOS startup workaround

Packaged macOS builds intentionally skip Electron's single-instance lock on macOS 13.x and older to avoid a launch-time Electron crash seen on older Intel macOS systems.

Tradeoff:

Release guidance:

Disposable profile (Windows demo)

Use a throwaway --user-data-dir when you need first-run–style behavior for a demo or recording, without changing your normal profile under $env:APPDATA\StratoSort Stack (same folder as %APPDATA%\StratoSort Stack in Explorer).

Exact walkthrough: temp demo profile (Windows)

Use Windows PowerShell (not Command Prompt). The & operator is required to run an executable whose path contains spaces.

  1. Quit StratoSort completely (including the system tray if it stays open).

  2. Create an empty folder for the disposable profile (adjust the path if you prefer):

    New-Item -ItemType Directory -Force -Path C:\temp\stratosort-demo-profile
    
  3. Launch the app with --user-data-dir pointing at that folder. Use one of these:

    A — Packaged app (change the .exe path if your install location differs):

    & "C:\Program Files\StratoSort Stack\StratoSort Stack.exe" --user-data-dir=C:\temp\stratosort-demo-profile
    

    B — From the repo (after a normal dev build, from the repo root):

    cd <path-to-your-clone>
    npx cross-env NODE_ENV=development electron . --enable-logging --user-data-dir=C:\temp\stratosort-demo-profile
    
  4. Confirm you’re on the temp profile — open C:\temp\stratosort-demo-profile in Explorer. After the UI loads, you should see new files (for example settings.json, logs, and later models if you download models). Your usual profile folder ($env:APPDATA\StratoSort Stack) should stay unchanged for this run.

  5. Record your demo — you should get first-run–style behavior. Allow time if large models download into this profile’s models folder.

  6. Quit the app when done.

  7. Optional: delete the temp profile

    Remove-Item -Recurse -Force C:\temp\stratosort-demo-profile
    

    Your real profile under $env:APPDATA\StratoSort Stack was not used for that session.

Tip: If something feels stuck or locked, use a new folder name (for example stratosort-demo-profile-2) for the next take.

Push a semver tag:

git tag vX.Y.Z
git push origin vX.Y.Z

Replace X.Y.Z with the version from package.json after you bump it (for example v5.0.0).

This triggers:

Both also support workflow_dispatch against an existing tag for re-runs (e.g. after adding signing secrets or fixing a transient build failure).

Release workflows are packaging/publish workflows, not full CI replacements. Run npm run ci before tagging, then use the release workflows to build and upload artifacts.

Published Artifacts

Windows workflow publishes:

macOS workflow publishes:

Linux workflow publishes:

Application auto-updates

Shipped installers check the public GitHub Releases feed for updates by default, so the repository and its releases must stay public (private releases are not visible to electron-updater without auth). STRATOSORT_ENABLE_AUTO_UPDATES=false disables the updater; optional STRATOSORT_UPDATE_FEED_URL points at a generic HTTPS directory with latest.yml. See CONFIG.md and UPDATE_HOST.md.

Updater live smoke (run before every update-enabled release)

The renderer/main wiring is oracled in unit + e2e suites (UpdateIndicator.test.js, autoUpdater.test.js), but only a real installed build proves the end-to-end chain. Once per update-enabled release:

  1. Build TWO versions: the release candidate v(N) and a throwaway v(N+1) (bump package.json version, rebuild). Both signed.
  2. Stage v(N+1)'s latest.yml + installer + .blockmap in an HTTPS directory with a valid certificate (Cloudflare R2 / any static host). scripts/verify-updater-metadata.js must pass on the staged files.
  3. On a clean Windows VM: install v(N), then launch with STRATOSORT_UPDATE_FEED_URL=<staging URL>, STRATOSORT_UPDATE_FEED_ALLOWED_HOSTS=<staging host>.
  4. Verify the full chain: update event → background download → "Update Ready" indicator → Apply → in-flight-op drain → relaunch as v(N+1).
  5. Negative checks: the portable build refuses with the portable reason; a Microsoft Store build (process.windowsStore) refuses with the Store reason; a wrong ALLOWED_HOSTS value blocks the feed.
  6. Uninstall/reinstall v(N) to confirm the rollback story.

Post-Install Smoke (Windows NSIS)

Use this after npm run dist:win or an official CI-built StratoSortStack-Setup-*.exe to confirm the install and first-run path matches INSTALL_GUIDE.md.

Preconditions

Automated checks (developer machine)

From the repo root after a Windows dist build:

node scripts/verify-updater-metadata.js --platform=win --build-root=release/build
npm test -- test/autoUpdater.test.js --runInBand --no-coverage

Manual steps

  1. Install — Run release\build\StratoSortStack-Setup-<version>.exe (or your tagged artifact).
  2. Profile path — Confirm app data lives under %APPDATA%\StratoSort Stack (not the legacy lowercase stratosort-core path that older postinstall scripts derived from the npm package name).
  3. First launch — App opens without an updater error notification; the startup update check against GitHub Releases succeeds (or reports "up to date").
  4. Settings → Application — "Background Update Checks" is on and can be switched off (see CONFIG.md and UPDATE_HOST.md).
  5. Models — Complete welcome / model setup; optional "Download Base Models" and confirm .gguf files appear under %APPDATA%\StratoSort Stack\models when downloads succeed.

Packaged-integrity checks

Run these once per release on a packaged build. Every path below exists only when packaged: main, preload and the renderer live inside the integrity-validated app.asar, while seven worker entry bundles are unpacked beside it (asarUnpack in electron-builder.json). A worker that resolves fine in npm run dev can still fail to load from app.asar.unpacked/dist/ — that is exactly how the settings-import worker broke in #441 — so development runs prove nothing here.

Two more release-time runtime items pair with this pass, both verifiable only on a packaged build:

Portable build

StratoSortStack-*-win-x64.exe does not use the same auto-update path as the NSIS installer; see INSTALL_GUIDE.md for portable vs full-installer behavior.

Signing And Notarization Requirements

Tag-triggered release workflows support both official signed releases and unsigned ad-hoc artifacts. If the required signing/notarization secrets are missing, the workflows continue in unsigned mode. As of v4.0.0, macOS releases are signed and notarized when all five Apple secrets below are set.

Required credentials:

Windows signing uses Azure Trusted Signing (account stratosort-signing, certificate profile stratosortcore-public-trust-cert, endpoint https://eus.codesigning.azure.net/, publisher Kennedy Applied Sciences, Inc.). Those four signing-identity values are public and stable, so they are hardcoded in scripts/configure-win-signing.js. The workflow injects win.azureSignOptions into the per-arch electron-builder config at build time via that script — the block is intentionally absent from the committed configs so local and fork builds stay unsigned without Azure credentials. Credential sources:

For the end-to-end Windows runbook that produces the five macOS secrets from a Windows 11 host without any Mac hardware (OpenSSL CSR + cert request + .p12 assembly + GitHub Secrets paste), see MAC_SIGNING.md. The runbook is wrapped by npm run setup:mac-signing (PowerShell helper at scripts/setup-mac-signing.ps1).

Unsigned macOS artifacts will not be notarized and may trigger Gatekeeper warnings on first launch. For one-off rebuilds or draft releases, you can still use the manual dist workflow or local dist commands instead of the tag-triggered workflows.

Recommended operator practice for ad-hoc unsigned builds:

  1. Ensure checksums-*.sha256 files are uploaded with each build.
  2. Verify install flows manually on clean Windows/macOS test machines.

Manual Dist Workflow

Use .github/workflows/manual-dist.yml (workflow_dispatch) when you need:

Local Dist Commands

npm ci
npm run dist:win
# Run on macOS only
npm run dist:mac:arm64

Windows cannot produce macOS DMG/ZIP artifacts locally. Build macOS distributions on a macOS host or via the mac-release.yml / manual-dist.yml GitHub Actions workflows, which run on macos-latest.

All artifacts are written to release/build/.

Local Checksum Commands

Windows checksum file:

Get-ChildItem release/build -File |
  Where-Object { $_.Name -match '^StratoSortStack-.*\.(exe|blockmap)$|^latest\.yml$' } |
  ForEach-Object {
    $hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash
    "$hash *$($_.Name)"
  } | Out-File release/build/checksums-windows-x64.sha256 -Encoding ASCII
# (CI generates per-arch files: checksums-windows-x64.sha256 / checksums-windows-arm64.sha256)

macOS checksum file:

cd release/build
shasum -a 256 StratoSortStack-*.dmg StratoSortStack-*.zip > checksums-macos.sha256

Notes on AI Packaging

Edit this page on GitHub