Electron Forge packaging in slack-desktop — platform-specific makers, code signing, notarization, webpack production builds, and security fuses.
slack-desktop uses Electron Forge for packaging, with platform-specific makers and a multi-stage code signing pipeline.
# Development build + launch
yarn start
# Package for current platform
yarn package
# Package with options
yarn package --override-arch=arm64
yarn package --dmg=true
yarn package --msi=true
yarn package --appx=true
yarn package --installers=false # output folder only, no installers
forge.config.ts)The config is an async function returning ForgeConfig:
getPackagerOpts() in build/make-package.ts{ disablePreGypCopy: true, mode: 'parallel' }@electron-forge/maker-dmg, @electron-forge/maker-zipgenerateAssets hook)generateAssets, postPackage, postMakeMakers: DMG (UDBZ format, 600x440 window, custom background) + ZIP (for darwin and mas)
App bundle:
appBundleId: com.tinyspeck.slackmacgapElectronTeamID: BQR82RBBHLLSMinimumSystemVersion: 12.0slack://Icons: slack.icns (prod), slack-dogfood.icns (dogfood), slack-prototype.icns (prototype branches)
MAS builds: Set SLACK_DARWIN_PLATFORM=mas env var.
x64 makers (4):
electron-wix-msi.exe + .msi + .nupkg + RELEASES)arm64 makers (2): Only the two MSIX makers (no Squirrel or standalone MSI)
No Forge makers. Custom createDistroPackage() from build/linux-packaging.ts.
Signing is done after Forge's package step (not via osxSign). This is intentional — post-package hooks add binaries that need signing.
Flow:
codesign --deep for M1 compatibility (after fuse flipping invalidates Electron's signature)npm run codesign runs:
codesignMacApp() via @electron/osx-signcodesignNodeFiles() — signs all .node files individually--notarize triggers notarizeApp() via @electron/notarizeSigning identities:
Developer ID Application: SLACK TECHNOLOGIES L.L.C. (BQR82RBBHL)3rd Party Mac Developer Application: SLACK TECHNOLOGIES L.L.C. (BQR82RBBHL)Apple Development: Desktop Release (44TN467826)DDL builds: Hardened runtime enabled, custom designated requirements. MAS builds: Per-entitlements signing (separate entitlement files for Plugin, GPU, Renderer, inherit).
Tool: Jsign (Java-based) with AWS KMS as keystore.
KMS_REGION: us-east-1KMS_ALIAS: alias/code-signinghttp://timestamp.digicert.comWhen signing happens:
postPackage hook — scans and signs all unsigned .dll, .node, .exevendor/signtool.exe for Squirrel releasifywindows-sign-hook.js calls Jsign.msiSkip condition: Prototype builds (version ending .65535) skip signing unless on support-build/ branch.
Triggered by Forge's generateAssets hook calling createCompiledResources().
Architecture:
jest-worker threadssrc/common pre-bundled with esbuild before webpack runsesbuild-loader (target: esnext, JSX factory: h for Preact)EsbuildPlugin (ES2022 target, replaces Terser)Build targets (webpack/configs/):
| Target | Config | Process |
|---|---|---|
| Boot | boot/boot.ts | Startup |
| Browser | browser/main.ts | Main process |
| Preload (default) | preload/preload.default.ts | Preload |
| Preload (main) | preload/preload.main.ts | Main window preload |
| Renderer (default) | renderer/renderer.default.ts | Renderer |
| Window Chrome | renderer/windowchrome.ts | Window chrome renderer |
| Utility | utility/utility.default.ts | Utility process |
| Vendor DLL | vendor/vendor.dll.ts | Pre-built vendor bundle |
Post-build: .map files moved from dist/ to dist_sourcemap/ (uploaded to S3 separately, not shipped).
Set in build/hooks/common/flip-fuses.ts via @electron/fuses after Electron binary extraction:
| Fuse | Value | Purpose |
|---|---|---|
RunAsNode | false | Prevents using the app as a Node.js runtime |
EnableCookieEncryption | true | Encrypts cookies at rest |
EnableNodeCliInspectArguments | false | Disables --inspect in production |
EnableNodeOptionsEnvironmentVariable | false | Ignores NODE_OPTIONS env var |
EnableEmbeddedAsarIntegrityValidation | true (except Linux) | Validates ASAR integrity |
OnlyLoadAppFromAsar | true | Prevents loading app from plain directories |
GrantFileProtocolExtraPrivileges | false | Restricts file:// protocol |
WasmTrapHandlers | true | Enables Wasm trap handlers |
afterCopy (after app source copied):
stripSourcemapUrlsHook — strips sourcemap referencesdeleteStrayFilesHook — removes unnecessary filescopyInLicenseFilesHook — copies license filescopyInSoundFilesHook — copies sound assetscopyInCRTFilesHook — (Windows) C runtime filescopyInVisualElementsFilesHook — (Windows) visual elements manifestdeleteUnsupportedLanguageFilesHook — (Darwin) removes unsupported .lproj dirsresetUtimeHook — (Darwin) resets timestamps for reproducible buildsafterExtract: flipFusesHook (see Security Fuses above)
postPackage: signWindowsLibrariesHook (signs unsigned binaries)
postMake: copyOutArtifacts + maybeNotarize (DMG only)
| Variable | Purpose |
|---|---|
SLACK_OVERRIDE_ARCH | Target architecture (arm64 / x64) |
SLACK_DARWIN_PLATFORM | Set to mas for Mac App Store builds |
BUILD_TYPE | dogfood or release — changes icons and behavior |
BUILD_ONLY | Skip notarization |
SLACK_ELECTRON_ZIP_DIR | Custom local Electron zip |
WEBPACK_BUNDLE_ANALYZE | Generate bundle analysis .stats.json |
| File | Purpose |
|---|---|
forge.config.ts | Top-level Forge config |
build/package-cli.ts | Packaging CLI entry point |
build/make-package.ts | Packager options, hooks, createInitialPackages() |
build/makers/win/index.ts | Windows maker orchestration |
build/mac-code-signing.ts | macOS codesign logic |
build/windows-code-signing.ts | Windows Jsign + AWS KMS |
build/utils/apple-notarize.ts | Apple notarization |
build/hooks/common/flip-fuses.ts | Electron fuse configuration |
webpack/runner.ts | Webpack build orchestrator |
webpack/constants/base-config.ts | Shared webpack base config |