Packaging & Releases
Kangentic uses electron-builder to create platform-specific installers. Configuration lives in electron-builder.yml at the project root.
Platform Targets
Section titled “Platform Targets”| Platform | Format | Installer Type |
|---|---|---|
| Windows | .exe | NSIS (Nullsoft Scriptable Install System) |
| macOS | .dmg, .zip | DMG disk image (drag-to-install); the .zip feeds the macOS auto-updater |
| Linux | .deb, .rpm | Native package manager formats |
Native Module Handling
Section titled “Native Module Handling”Several native modules require special handling during packaging:
| Module | Strategy |
|---|---|
| better-sqlite3 | Rebuilt for the target Electron version manually via scripts/rebuild-native.js (electron-builder’s own npmRebuild is disabled) |
| node-pty | Ships NAPI prebuilt binaries; no rebuild needed |
| sherpa-onnx-node | Ships prebuilt native binaries for local voice Dictation; shipped and unpacked from ASAR (native addons can’t be dlopen’d from inside the archive) |
| font-list | Enumerates 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-vec | The 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/transformers | The 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 scripts | event-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 |
ASAR Packaging
Section titled “ASAR Packaging”The app is packaged as an ASAR archive for faster loading and to prevent casual modification. Selective unpacking is configured for:
- Native
.nodemodules 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, andplugins/** - The dictation runtime (
sherpa-onnx-node) - The Conversation Memory runtime: the
sqlite-vecloadable extension,onnxruntime-node(with its Windows DLLs), and@huggingface/transformers, along with theembed-worker.jsthat runs embedding inference in an isolated process
Security Fuses
Section titled “Security Fuses”Production builds set Electron security fuses to harden the application:
| Fuse | Setting | Purpose |
|---|---|---|
| RunAsNode | Disabled | Prevents ELECTRON_RUN_AS_NODE environment variable |
| NodeOptions | Disabled | Prevents NODE_OPTIONS injection |
| Inspection | Disabled | Prevents --inspect debugging in production |
| Cookie Encryption | Enabled | Encrypts cookies at rest |
| ASAR Integrity | Enabled | Validates ASAR archive integrity on load |
| Only Load App From ASAR | Enabled | Refuses to run app code from anywhere but the ASAR archive |
Code Signing
Section titled “Code Signing”Windows
Section titled “Windows”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
Release Workflow
Section titled “Release Workflow”Releases follow a structured process:
- Trigger: Run
/release patch|minor|majorin the repo - this bumps the version and pushes a git tag - Draft Release: A dedicated
create-draft-releasejob 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) - 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.ymlis packaged; the Linux job additionally fresh-installs the built.rpmon Fedora and openSUSE containers and the.debon Debian and Ubuntu, then runs a second, narrower gate that upgrades from the previous published release - the.rpmon Fedora and the.debon 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 - Publish: Once all three platform builds succeed, a
publish-releasejob first runsscripts/verify-release-assets.jsas 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 fromRELEASE_NOTES.mdin 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 - 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 kangenticdownloads the new version. The@kangentic/protocolworkspace package is published separately by its own workflow, on its own version line andprotocol-vX.Y.Ztag 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.
Environment Protection
Section titled “Environment Protection”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.
CI Build Matrix
Section titled “CI Build Matrix”| Runner | Platform | Artifact |
|---|---|---|
ubuntu-latest | Linux | .deb, .rpm |
windows-latest | Windows | NSIS .exe |
macos-latest | macOS | .dmg, .zip |
Local Testing
Section titled “Local Testing”# Build the app without creating an installernpm run package
# Create distributable installers locally - the same artifacts the# publish pipeline builds, just without uploading them anywherenpm run makeNext steps
Section titled “Next steps”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