Skip to content

Packaging & Releases

Kangentic uses electron-builder to create platform-specific installers. Configuration lives in electron-builder.yml at the project root.

PlatformFormatInstaller Type
Windows.exeNSIS (Nullsoft Scriptable Install System)
macOS.dmg, .zipDMG disk image (drag-to-install); the .zip feeds the macOS auto-updater
Linux.deb, .rpmNative package manager formats

Several native modules require special handling during packaging:

ModuleStrategy
better-sqlite3Rebuilt for the target Electron version manually via scripts/rebuild-native.js (electron-builder’s own npmRebuild is disabled)
node-ptyShips NAPI prebuilt binaries; no rebuild needed
sherpa-onnx-nodeShips prebuilt native binaries for local voice Dictation; shipped and unpacked from ASAR (native addons can’t be dlopen’d from inside the archive)
font-listEnumerates installed fonts for the terminal font picker (shells out to fc-list on Linux and a PowerShell script on Windows). Shipped and unpacked from ASAR because its bundled macOS binary is spawned via child_process, which cannot exec a path inside the archive
sqlite-vecThe loadable SQLite vector extension for Conversation Memory. Ships per-platform binary packages, unpacked from ASAR because SQLite’s dlopen can’t read a loadable extension from inside the archive
onnxruntime-node + @huggingface/transformersThe local embedding runtime for Conversation Memory semantic search (plus onnxruntime.dll / DirectML.dll on Windows). Shipped and unpacked so the embed worker resolves them via plain node_modules resolution
Bridge and worker scriptsevent-bridge.js, status-bridge.js, line-count-worker.js, embed-worker.js, and plugins/** are selectively unpacked from ASAR so they can execute from real file paths

The app is packaged as an ASAR archive for faster loading and to prevent casual modification. Selective unpacking is configured for:

  • Native .node modules that require filesystem access
  • The bridge and worker scripts used for process spawning: event-bridge.js, status-bridge.js, line-count-worker.js, embed-worker.js, and plugins/**
  • The dictation runtime (sherpa-onnx-node)
  • The Conversation Memory runtime: the sqlite-vec loadable extension, onnxruntime-node (with its Windows DLLs), and @huggingface/transformers, along with the embed-worker.js that runs embedding inference in an isolated process

Production builds set Electron security fuses to harden the application:

FuseSettingPurpose
RunAsNodeDisabledPrevents ELECTRON_RUN_AS_NODE environment variable
NodeOptionsDisabledPrevents NODE_OPTIONS injection
InspectionDisabledPrevents --inspect debugging in production
Cookie EncryptionEnabledEncrypts cookies at rest
ASAR IntegrityEnabledValidates ASAR archive integrity on load
Only Load App From ASAREnabledRefuses to run app code from anywhere but the ASAR archive

Windows builds are signed using Azure Trusted Signing. The CI pipeline authenticates with Azure credentials and signs the NSIS installer during the build step.

macOS builds use:

  • Hardened runtime - required for notarization
  • Apple notarization - the DMG is submitted to Apple’s notary service and stapled before release

Releases follow a structured process:

  1. Trigger: Run /release patch|minor|major in the repo - this bumps the version and pushes a git tag
  2. Draft Release: A dedicated create-draft-release job pre-creates the single draft GitHub Release before any platform build starts, and the build matrix waits on it. (Letting each platform job race to create the draft once produced three separate drafts and a partial release, so creation is now serialized up front)
  3. CI Build: The tag push then builds on all three platforms (ubuntu, windows, macos), each job attaching its artifacts to that one draft. Every platform verifies app-update.yml is packaged; the Linux job additionally fresh-installs the built .rpm on Fedora and openSUSE containers and the .deb on Debian and Ubuntu, then runs a second, narrower gate that upgrades from the previous published release - the .rpm on Fedora and the .deb on Debian. Upgrading is the path auto-update actually takes, and it can fail where a fresh install succeeds (file conflicts between versions, scriptlet ordering, a dependency only the old package pulled in). That gate downloads the real previous release asset, so a renamed artifact fails it rather than silently skipping
  4. Publish: Once all three platform builds succeed, a publish-release job first runs scripts/verify-release-assets.js as a hard gate: the tag must resolve to exactly one release, carrying all eleven expected assets, all fully uploaded. Only then does it clear the draft flag and set the release body from RELEASE_NOTES.md in one call, making the release visible to the auto-updater. Build success on its own proves nothing about a release’s contents, which is what the gate exists for. A draft left unpublished means the gate failed; publishing it by hand would bypass the one check standing between a partial release and every user’s auto-updater
  5. npm Publish: The launcher package is published to npm (with provenance, via OIDC trusted publishing; the step skips itself if the version is already on the registry) so npx kangentic downloads the new version. The @kangentic/protocol workspace package is published separately by its own workflow, on its own version line and protocol-vX.Y.Z tag namespace, so protocol releases do not require a desktop release

electron-builder.yml also sets releaseInfo.releaseNotesFile: RELEASE_NOTES.md, which bakes the release notes into the generated latest*.yml update manifests at build time. The in-app release-notes dialog shown before an update restart reads its notes straight from that manifest - no extra network fetch, and no fallback to GitHub’s unsanitized Atom feed content.

Each platform build job runs under its own GitHub environment (release-linux, release-windows, release-macos-arm64), which is where the signing and notarization secrets live. Any protection rules configured on those environments, such as required reviewers, therefore gate the build itself rather than the publish step.

RunnerPlatformArtifact
ubuntu-latestLinux.deb, .rpm
windows-latestWindowsNSIS .exe
macos-latestmacOS.dmg, .zip
Terminal window
# Build the app without creating an installer
npm run package
# Create distributable installers locally - the same artifacts the
# publish pipeline builds, just without uploading them anywhere
npm run make

See also:

  • Contributing - prerequisites and project structure
  • Testing - tests that run before each release

Kangentic is free and open source. A star helps other people find it.

Star on GitHub