desktop-packaging-electron-forge
Electron Forge build toolchain -- makers, publishers, code signing, fuses, hooks, CI/CD packaging
Install
npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/desktop-packaging-electron-forge/skills/desktop-packaging-electron-forge
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart
git clone https://github.com/agents-inc/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agents-inc/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Electron Forge Packaging
Quick Guide: Electron Forge v7 is the official Electron build toolchain. Configure via
forge.config.tswith typed imports from@electron-forge/shared-types. Use makers to produce platform-specific installers (Squirrel for Windows, DMG/ZIP for macOS, deb/rpm for Linux). Use publishers to upload artifacts (GitHub Releases, S3, Snapcraft). Always code-sign production builds -- macOS requires both signing and notarization. Enable Electron Fuses to harden the binary at package time. Use hooks (prePackage,postMake) for custom build logic. Electron itself MUST be adevDependency-- Forge bundles onlydependencies.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST place electron in devDependencies -- Forge provides the Electron binary during packaging; placing it in dependencies bloats the app by ~200MB)
(You MUST code-sign macOS builds with osxSign and osxNotarize in packagerConfig -- unsigned apps are blocked by Gatekeeper on macOS 10.15+)
(You MUST enable asar: true in packagerConfig -- without ASAR, your source code ships as plain-text files readable by any user)
(You MUST store signing credentials in environment variables -- never hardcode secrets in forge.config.ts)
(You MUST enable Fuses (FuseV1Options.RunAsNode: false, OnlyLoadAppFromAsar: true) for production security -- without them, attackers can bypass ASAR integrity and run arbitrary Node.js code)
</critical_requirements>
Auto-detection: Electron Forge, electron-forge, forge.config.ts, forge.config.js, @electron-forge, maker-squirrel, maker-dmg, maker-deb, maker-rpm, maker-zip, maker-flatpak, maker-snap, maker-appx, maker-wix, maker-pkg, maker-msix, publisher-github, publisher-s3, publisher-snapcraft, plugin-vite, plugin-webpack, plugin-fuses, FusesPlugin, osxSign, osxNotarize, electron-forge make, electron-forge publish, electron-forge package
When to use:
- Configuring
forge.config.tsfor packaging and distribution - Choosing and configuring makers for target platforms
- Setting up publishers for automated release distribution
- Code signing macOS (notarization) or Windows (Authenticode) builds
- Enabling Electron Fuses for binary hardening
- Adding build hooks for custom pre/post-packaging logic
- Setting up CI/CD pipelines for cross-platform builds
- Deciding between Electron Forge and electron-builder
When NOT to use:
- Electron app architecture (main/renderer process, IPC, preload) -- use the Electron framework skill
- Choosing or configuring a bundler for renderer code in isolation
- Auto-update implementation (that is an Electron framework concern, not a Forge concern)
- UI framework selection for renderers
Key patterns covered:
- forge.config.ts structure with typed configuration
- Platform-specific maker selection and configuration
- macOS code signing + notarization setup
- Windows Authenticode signing (traditional + Azure Trusted Signing)
- Fuses plugin for binary hardening
- Publisher configuration (GitHub, S3, Snapcraft)
- Build hooks and lifecycle
- CI/CD cross-platform build matrix
- Forge vs electron-builder decision framework
<decision_framework>
Decision Framework
Forge vs electron-builder
Choosing a build tool?
+-- Want first-party Electron support (ASAR integrity, universal macOS)?
| +-- YES --> Electron Forge (receives features same-day as Electron)
+-- Need YAML-based config, NSIS installer, or broad community support?
| +-- YES --> electron-builder (more installer targets, larger community)
+-- Need maximum customization for enterprise?
| +-- YES --> electron-builder (more config options, NSIS scripting)
+-- Starting a new project?
+-- YES --> Electron Forge (official recommendation, TypeScript config)
| Factor | Electron Forge | electron-builder |
|---|---|---|
| Maintainer | Electron team | Community |
| Config format | TypeScript / JavaScript | YAML / JSON / JS |
| New Electron features | Same-day | Delayed |
| Plugin ecosystem | Makers, publishers, plugins | Built-in monolith |
| Windows installers | Squirrel, WiX, MSIX, AppX | NSIS, Squirrel, MSI, AppX |
| macOS installers | DMG, ZIP, PKG | DMG, ZIP, PKG, MAS |
| npm downloads | ~50K/week | ~1.4M/week |
| Architecture | Composable packages | Monolithic |
Maker Selection
Which maker for your platform?
+-- Windows?
| +-- Auto-updating desktop app --> Squirrel.Windows
| +-- Enterprise/IT deployment --> WiX MSI
| +-- Microsoft Store --> AppX or MSIX
+-- macOS?
| +-- Direct distribution --> DMG (drag-to-install) + ZIP (for auto-updater)
| +-- Mac App Store --> PKG
+-- Linux?
| +-- Debian/Ubuntu --> deb
| +-- Fedora/RHEL --> RPM
| +-- Universal sandboxed --> Flatpak or Snap
</decision_framework>
Detailed resources:
- examples/core.md -- forge.config.ts setup, makers, Vite plugin, fuses, dependency management
- examples/signing.md -- macOS notarization, Windows Authenticode, Azure Trusted Signing, entitlements
- examples/publishers.md -- GitHub, S3, Snapcraft publishers with CI/CD patterns
- examples/hooks.md -- Build lifecycle hooks, custom makers, extending Forge
- reference.md -- Maker/publisher quick-reference tables, fuse options, CLI commands, Forge vs builder comparison
<red_flags>
RED FLAGS
Critical Issues:
- Placing
electronindependenciesinstead ofdevDependencies-- bloats the packaged app by ~200MB because Forge already provides the binary - Shipping without code signing -- macOS Gatekeeper blocks unsigned apps entirely; Windows SmartScreen shows scary warnings
- Hardcoding signing credentials in
forge.config.ts-- secrets end up in version control; always useprocess.env - Not enabling ASAR (
asar: false) -- ships your source code as readable plain-text files - Not setting
RunAsNode: falsefuse -- allowsELECTRON_RUN_AS_NODE=1to execute arbitrary code with your app's permissions
Architecture Issues:
- Running
electron-forge makeon macOS expecting Windows .exe output -- makers run only on the target OS (use CI/CD with per-platform runners) - Placing native modules (better-sqlite3, sharp) inside ASAR without
asarUnpack-- native addons cannot load from inside an ASAR archive - Not running
@electron/rebuildfor native modules -- modules compiled for system Node.js crash in Electron's Node.js runtime (Forge runs rebuild automatically during package, but manual installs need it) - Using
electron-forge packagefor distribution -- this produces an uninstallable app bundle; usemakefor distributable installers
Configuration Mistakes:
- Setting
asar: truewithoutasarUnpackfor native modules -- the app will crash at runtime trying to load the native addon - Forgetting the
platformsarray on makers -- maker runs on all platforms and fails on unsupported ones - Using
osxNotarizewithoutosxSign-- notarization requires a signed binary; Apple rejects unsigned submissions - Using your Apple ID password instead of an app-specific password for
osxNotarize-- regular passwords are rejected when 2FA is enabled
Gotchas & Edge Cases:
electron-forge startin dev mode does not run makers -- dev mode uses unpackaged source; always test withmakebefore release- Notarization takes 2-10 minutes per build -- factor this into CI/CD timeout settings
- Squirrel.Windows handles first-run events (shortcuts, desktop icons) -- your main process must handle Squirrel startup events or the app opens multiple times during install
__dirnameresolves to virtual ASAR paths in packaged builds -- useapp.isPackaged+process.resourcesPathfor resource file paths- The Vite plugin is experimental since v7.5.0 -- minor version bumps may include breaking changes to its config shape
- Azure Trusted Signing paths must not contain spaces -- signing fails silently if any path has spaces
- Forge hooks run in parallel, not sequentially -- do not rely on execution order between hooks of the same type
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST place electron in devDependencies -- Forge provides the Electron binary during packaging; placing it in dependencies bloats the app by ~200MB)
(You MUST code-sign macOS builds with osxSign and osxNotarize in packagerConfig -- unsigned apps are blocked by Gatekeeper on macOS 10.15+)
(You MUST enable asar: true in packagerConfig -- without ASAR, your source code ships as plain-text files readable by any user)
(You MUST store signing credentials in environment variables -- never hardcode secrets in forge.config.ts)
(You MUST enable Fuses (FuseV1Options.RunAsNode: false, OnlyLoadAppFromAsar: true) for production security -- without them, attackers can bypass ASAR integrity and run arbitrary Node.js code)
Failure to follow these rules will produce insecure, bloated, or unsigned builds that OS security mechanisms will block or warn users about.
</critical_reminders>
Files (skills)
-
examples
-
core.md 6.7 KB
# Electron Forge -- Core Patterns > forge.config.ts setup, maker configuration, Vite plugin, Fuses, dependency management. See [SKILL.md](../SKILL.md) for decision frameworks and red flags. See [signing.md](signing.md) for code signing. See [publishers.md](publishers.md) for release distribution. --- ## Complete forge.config.ts with Makers and Fuses ```typescript // forge.config.ts import type { ForgeConfig } from "@electron-forge/shared-types"; import { MakerSquirrel } from "@electron-forge/maker-squirrel"; import { MakerDMG } from "@electron-forge/maker-dmg"; import { MakerZIP } from "@electron-forge/maker-zip"; import { MakerDeb } from "@electron-forge/maker-deb"; import { MakerRPM } from "@electron-forge/maker-rpm"; import { FusesPlugin } from "@electron-forge/plugin-fuses"; import { FuseV1Options, FuseVersion } from "@electron/fuses"; const config: ForgeConfig = { packagerConfig: { asar: true, icon: "./assets/icon", // Forge picks .icns (macOS), .ico (Windows), .png (Linux) name: "MyApp", executableName: "my-app", appBundleId: "com.example.myapp", // macOS code signing -- see signing.md for full setup osxSign: {}, osxNotarize: { appleId: process.env.APPLE_ID!, appleIdPassword: process.env.APPLE_PASSWORD!, teamId: process.env.APPLE_TEAM_ID!, }, }, rebuildConfig: {}, makers: [ new MakerSquirrel({ setupIcon: "./assets/icon.ico", }), new MakerDMG({ icon: "./assets/icon.icns", }), new MakerZIP({}, ["darwin"]), new MakerDeb({ options: { icon: "./assets/icon.png", maintainer: "Your Name", homepage: "https://example.com", }, }), new MakerRPM({}), ], plugins: [ new FusesPlugin({ version: FuseVersion.V1, [FuseV1Options.RunAsNode]: false, [FuseV1Options.EnableCookieEncryption]: true, [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, [FuseV1Options.EnableNodeCliInspectArguments]: false, [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, [FuseV1Options.OnlyLoadAppFromAsar]: true, [FuseV1Options.GrantFileProtocolExtraPrivileges]: false, }), ], }; export default config; ``` **Why good:** TypeScript config with full type inference, all security fuses enabled, signing credentials from env vars, ASAR enabled, minimal maker set covers all three platforms --- ## Maker Configuration (Object-Style vs Class-Style) Forge v7 supports two maker configuration styles: object-based (JSON-serializable) and class-based (imported constructors). Both are equivalent. ```typescript // Class-based (recommended for TypeScript -- full type inference) import { MakerSquirrel } from "@electron-forge/maker-squirrel"; import { MakerDMG } from "@electron-forge/maker-dmg"; makers: [ new MakerSquirrel({ setupIcon: "./assets/icon.ico" }), new MakerDMG({ icon: "./assets/icon.icns" }), ], ``` ```typescript // Object-based (works in JS config or when you want JSON-serializable config) makers: [ { name: "@electron-forge/maker-squirrel", config: { setupIcon: "./assets/icon.ico" }, }, { name: "@electron-forge/maker-dmg", config: { icon: "./assets/icon.icns" }, }, ], ``` **Key point:** Class-based imports give you autocomplete and type-checking on the config object. Object-based is useful for dynamic configs loaded from JSON. --- ## Dynamic Maker Config by Architecture Makers accept a function for platform/arch-specific configuration. ```typescript new MakerSquirrel((arch) => ({ setupIcon: "./assets/icon.ico", // Different signing certificate for ARM vs x64 certificateFile: arch === "arm64" ? process.env.WIN_CSC_LINK_ARM : process.env.WIN_CSC_LINK, certificatePassword: process.env.WIN_CSC_KEY_PASSWORD, })), ``` --- ## Vite Plugin Setup ```typescript // forge.config.ts import { VitePlugin } from "@electron-forge/plugin-vite"; plugins: [ new VitePlugin({ build: [ { entry: "src/main.ts", config: "vite.main.config.mts" }, { entry: "src/preload.ts", config: "vite.preload.config.mts" }, ], renderer: [ { name: "main_window", config: "vite.renderer.config.mts" }, ], }), ], ``` ```typescript // src/main.ts -- loading the renderer const createWindow = () => { const mainWindow = new BrowserWindow({ width: 1200, height: 800, webPreferences: { preload: path.join(__dirname, "preload.js"), }, }); // In dev: load from Vite dev server (HMR) // In prod: load from built files if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); } else { mainWindow.loadFile( path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`), ); } }; ``` ```typescript // src/vite-env.d.ts -- declare the plugin-injected globals declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string | undefined; declare const MAIN_WINDOW_VITE_NAME: string; ``` **Why good:** Separate configs for main/preload/renderer, TypeScript declarations prevent type errors, conditional loading handles dev vs production paths correctly **Gotcha:** Native modules (better-sqlite3, sharp) must be marked as `external` in `vite.main.config.mts` rollupOptions -- Vite cannot bundle native addons. --- ## ASAR Unpacking for Native Modules Native Node.js addons cannot load from inside an ASAR archive. Unpack them explicitly. ```typescript packagerConfig: { asar: true, asarUnpack: [ "node_modules/better-sqlite3/**", "node_modules/sharp/**", "bin/**", // any bundled binary executables ], }, ``` **Why needed:** Unpacked files are placed in `app.asar.unpacked/` alongside `app.asar`. The app loads them from the filesystem. Keep the unpack list minimal -- every unpacked file is exposed on disk. **Runtime path resolution:** ```typescript const RESOURCES_PATH = app.isPackaged ? path.join(process.resourcesPath, "app.asar.unpacked") : __dirname; ``` --- ## Dependency Management ```json { "dependencies": { "electron-store": "^10.0.0" }, "devDependencies": { "electron": "^35.0.0", "@electron-forge/cli": "^7.11.0", "@electron-forge/maker-squirrel": "^7.11.0", "@electron-forge/maker-dmg": "^7.11.0", "@electron-forge/maker-zip": "^7.11.0", "@electron-forge/maker-deb": "^7.11.0", "@electron-forge/plugin-fuses": "^7.11.0", "@electron/fuses": "^1.8.0", "@electron/rebuild": "^3.7.0" } } ``` **Why critical:** Forge bundles `dependencies` into the final app but excludes `devDependencies`. Electron itself (~200MB) is provided by the packager -- placing it in `dependencies` ships a duplicate copy. **Common mistake:** Build tools, testing libraries, Forge packages, or `electron` itself in `dependencies` instead of `devDependencies`. -
hooks.md 4.7 KB
# Electron Forge -- Build Hooks & Extending > Lifecycle hooks, custom makers, extending Forge. See [SKILL.md](../SKILL.md) for decision frameworks. See [core.md](core.md) for forge.config.ts fundamentals. --- ## Build Lifecycle Overview Forge hooks fire at specific points during the three-step build process: ``` start / package: generateAssets --> prePackage --> [packager runs] --> packageAfterCopy --> packageAfterPrune --> packageAfterExtract --> postPackage make: preMake --> [makers run] --> postMake (mutating) publish: [publishers run] every step: readPackageJson (mutating -- fires on every package.json read) ``` --- ## Hook Examples ### prePackage: Validate or Generate Before Packaging ```typescript // forge.config.ts hooks: { prePackage: async (_config, platform, arch) => { console.log(`Packaging for ${platform}-${arch}`); // Generate license files, version stamps, or validate config await generateLicenseFile(); }, }, ``` ### postMake: Rename or Process Artifacts `postMake` is the only hook that can mutate the pipeline -- return a modified `MakeResult[]` to affect subsequent steps (like publishing). ```typescript import type { ForgeMakeResult } from "@electron-forge/shared-types"; hooks: { postMake: async (_config, makeResults: ForgeMakeResult[]) => { // Example: rename artifacts to include version for (const result of makeResults) { for (const artifact of result.artifacts) { console.log(`Created: ${artifact}`); } } // Return modified results to affect publish step return makeResults; }, }, ``` ### readPackageJson: Inject Build-Time Values Fires every time Forge reads `package.json`. Return a modified object to inject dynamic values. ```typescript hooks: { readPackageJson: async (_config, packageJson) => { // Inject build timestamp or commit hash packageJson.buildInfo = { timestamp: new Date().toISOString(), commit: process.env.GITHUB_SHA ?? "local", }; return packageJson; }, }, ``` ### packageAfterPrune: Post-Pruning Cleanup Runs after devDependencies are removed from the packaged app directory. ```typescript hooks: { packageAfterPrune: async (_config, buildPath) => { // Remove unnecessary files from the production bundle const fs = await import("node:fs/promises"); const path = await import("node:path"); const unnecessaryFiles = ["README.md", "CHANGELOG.md", ".eslintrc"]; for (const file of unnecessaryFiles) { const filePath = path.join(buildPath, file); await fs.rm(filePath, { force: true }); } }, }, ``` --- ## Custom Makers Custom makers extend `MakerBase` and implement `isSupportedOnCurrentPlatform()` plus the `make()` method. ```typescript import { MakerBase, type MakerOptions } from "@electron-forge/maker-base"; interface MyMakerConfig { outputName?: string; } export class MakerPortable extends MakerBase<MyMakerConfig> { name = "portable"; defaultPlatforms: string[] = ["win32"]; isSupportedOnCurrentPlatform(): boolean { return process.platform === "win32"; } async make({ dir, // path to packaged app directory makeDir, // path to output directory for artifacts targetArch, // target architecture (x64, arm64, etc.) appName, // application name }: MakerOptions): Promise<string[]> { const fs = await import("node:fs/promises"); const path = await import("node:path"); const outputName = this.config.outputName ?? `${appName}-portable.exe`; const outputPath = path.join(makeDir, outputName); // Your custom packaging logic here await fs.cp(dir, outputPath, { recursive: true }); // Return array of absolute paths to created artifacts return [outputPath]; } } ``` **Key rules:** - `make()` must return an array of absolute paths to the artifacts created - If an error occurs, reject the promise -- Forge stops the make process - `this.config` provides the maker's configuration from forge.config.ts - Register in forge.config.ts: `new MakerPortable({ outputName: "app-portable.exe" })` --- ## Hook Execution Order **Hooks of the same type run in parallel** -- do not depend on execution order between multiple hooks. If you need sequential execution, chain the logic within a single hook function. ```typescript // BAD: Two separate hooks that depend on each other hooks: { prePackage: async () => { await stepOne(); }, // This also fires as prePackage -- NO guaranteed order }, // GOOD: Sequential logic in one hook hooks: { prePackage: async (_config, platform, arch) => { await stepOne(); await stepTwo(); // guaranteed to run after stepOne }, }, ``` -
publishers.md 4.8 KB
# Electron Forge -- Publishers > GitHub, S3, Snapcraft publishers and CI/CD cross-platform build patterns. See [SKILL.md](../SKILL.md) for decision frameworks. See [signing.md](signing.md) for code signing in CI. --- ## GitHub Publisher Uploads make artifacts to GitHub Releases. The most common choice for open-source Electron apps. ```typescript // forge.config.ts publishers: [ { name: "@electron-forge/publisher-github", config: { repository: { owner: "my-org", name: "my-app", }, prerelease: false, draft: true, // create as draft -- review before publishing }, }, ], ``` **Authentication:** Set `GITHUB_TOKEN` environment variable with `contents: write` permission. ```bash GITHUB_TOKEN=ghp_xxxxxxxxxxxx electron-forge publish ``` **Key behavior:** If a release for the current `package.json` version already exists, Forge appends artifacts to it. This makes multi-platform CI builds work -- each runner uploads its platform's artifacts to the same release. --- ## S3 Publisher Uploads artifacts to an Amazon S3 bucket. Useful for private distribution or custom update servers. ```typescript publishers: [ { name: "@electron-forge/publisher-s3", config: { bucket: "my-app-releases", folder: "releases", // key prefix -- artifacts at releases/{platform}/{arch}/{name} public: true, // make objects publicly readable region: "us-east-1", }, }, ], ``` **Authentication:** Use AWS credentials via environment variables or shared credentials file (~/.aws/credentials). If not possible, pass `accessKeyId` and `secretAccessKey` in config (not recommended). --- ## Snapcraft Publisher Publishes `.snap` artifacts to the Snap Store. Linux only -- requires `snapcraft` CLI installed. ```typescript publishers: [ { name: "@electron-forge/publisher-snapcraft", config: { release: "[latest/edge]", // channel: latest/{stable,candidate,beta,edge} }, }, ], ``` **Prerequisite:** Log in with `snapcraft login` before publishing. In CI, use `SNAPCRAFT_STORE_CREDENTIALS` env var. --- ## CI/CD Cross-Platform Build Matrix Forge makers run only on the target OS. Cross-platform distribution requires per-platform CI runners. ```yaml # .github/workflows/release.yml name: Release on: push: tags: ["v*"] jobs: build: strategy: matrix: include: - os: macos-latest args: --arch=x64,arm64 - os: windows-latest args: "" - os: ubuntu-latest args: "" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm ci - name: Import macOS signing certificate if: runner.os == 'macOS' env: CERTIFICATE_P12: ${{ secrets.MAC_CERTIFICATE_P12 }} CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} run: | echo "$CERTIFICATE_P12" | base64 --decode > certificate.p12 security create-keychain -p "" build.keychain security import certificate.p12 -k build.keychain -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign security set-keychain-settings build.keychain security list-keychains -d user -s build.keychain login.keychain security set-key-partition-list -S apple-tool:,apple: -k "" build.keychain - name: Make and publish run: npx electron-forge publish ${{ matrix.args }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }} WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }} ``` **Key points:** - Each OS runner builds only its own platform's installers - All three runners upload to the same GitHub Release (Forge appends artifacts) - macOS runner must import the signing certificate into a temporary Keychain - Use `--arch=x64,arm64` on macOS to build universal binaries - Set notarization timeout in CI -- Apple's servers take 2-10 minutes per build - Tag-triggered workflow ensures `publish` only runs on release tags --- ## Draft Release Workflow A common pattern: CI creates a draft release, team reviews, then manually publishes. 1. Push a version tag: `git tag v1.2.0 && git push origin v1.2.0` 2. CI runs `electron-forge publish` on all three platforms 3. GitHub Release is created as draft (if `draft: true` in publisher config) 4. Team reviews release notes and artifacts 5. Click "Publish" on GitHub to make it live **Alternative:** Use `prerelease: true` instead of `draft: true` for automatic publication to a "pre-release" channel that users can opt into. -
signing.md 6 KB
# Electron Forge -- Code Signing > macOS notarization, Windows Authenticode, Azure Trusted Signing, entitlements. See [SKILL.md](../SKILL.md) for decision frameworks. See [core.md](core.md) for forge.config.ts setup. --- ## macOS: Signing + Notarization macOS requires two layers for distribution outside the Mac App Store: 1. **Code signing** -- certifies the app author's identity (requires Apple Developer Program certificate) 2. **Notarization** -- Apple's server-side malware scan (required since macOS 10.15 Catalina) ### Minimal Configuration ```typescript // forge.config.ts packagerConfig: { osxSign: {}, // empty object = use first valid signing identity from Keychain osxNotarize: { appleId: process.env.APPLE_ID!, appleIdPassword: process.env.APPLE_PASSWORD!, // app-specific password teamId: process.env.APPLE_TEAM_ID!, }, }, ``` **Prerequisite:** Install "Developer ID Application" certificate via Xcode. Verify with: ```bash security find-identity -p codesigning -v ``` ### Notarization Authentication Strategies #### Option 1: App-Specific Password (simplest) ```typescript osxNotarize: { appleId: process.env.APPLE_ID!, appleIdPassword: process.env.APPLE_PASSWORD!, // NOT your Apple ID password teamId: process.env.APPLE_TEAM_ID!, }, ``` Generate the app-specific password at appleid.apple.com. If your Apple ID password changes, regenerate it. #### Option 2: App Store Connect API Key (recommended for CI) ```typescript osxNotarize: { appleApiKey: process.env.APPLE_API_KEY_PATH!, // path to .p8 file appleApiKeyId: process.env.APPLE_API_KEY_ID!, // 10-char alphanumeric appleApiIssuer: process.env.APPLE_API_ISSUER!, // UUID }, ``` **Why for CI:** API keys do not expire and do not require 2FA -- ideal for headless environments. #### Option 3: Keychain Profile (pre-stored credentials) ```typescript osxNotarize: { keychainProfile: "my-app-notarize", // keychain: "/path/to/keychain" // optional -- auto-detected }, ``` Store credentials first: `xcrun notarytool store-credentials my-app-notarize` ### Custom Entitlements Entitlements define which OS capabilities your app can access (camera, microphone, USB, etc.). ```typescript osxSign: { optionsForFile: (filePath) => ({ entitlements: "build/entitlements.mac.plist", entitlementsInherit: "build/entitlements.mac.plist", }), }, ``` ```xml <!-- build/entitlements.mac.plist --> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.cs.allow-jit</key> <true/> <key>com.apple.security.cs.allow-unsigned-executable-memory</key> <true/> <key>com.apple.security.cs.allow-dyld-environment-variables</key> <true/> <key>com.apple.security.network.client</key> <true/> </dict> </plist> ``` **Why these entitlements:** Electron's Chromium engine requires JIT, unsigned executable memory, and dyld env vars to function under hardened runtime. The network client entitlement allows outbound HTTP requests. --- ## Windows: Authenticode Signing ### Traditional Certificate (.pfx) ```typescript // Squirrel maker new MakerSquirrel({ certificateFile: process.env.WIN_CSC_LINK!, certificatePassword: process.env.WIN_CSC_KEY_PASSWORD!, setupIcon: "./assets/icon.ico", }), // WiX MSI maker -- same fields new MakerWix({ certificateFile: process.env.WIN_CSC_LINK!, certificatePassword: process.env.WIN_CSC_KEY_PASSWORD!, }), ``` **Since June 2023:** Private keys must be stored on FIPS 140 Level 2 hardware (HSM or USB token). Software-only .pfx files are no longer accepted by certificate authorities for new purchases. ### Azure Trusted Signing (Modern Alternative) Azure Trusted Signing is Microsoft's cloud-based code signing service. It eliminates SmartScreen warnings immediately (no reputation building required). ```typescript // forge.config.ts packagerConfig: { windowsSign: { signToolPath: process.env.SIGNTOOL_PATH!, certificateFile: process.env.AZURE_METADATA_JSON!, certificatePassword: "", // not used with Azure additionalCertificateFile: process.env.AZURE_CODE_SIGNING_DLIB!, }, }, ``` **Requirements:** - US or Canada-based organization with 3+ years verifiable history, OR individual US/Canada developer - Azure subscription with Trusted Signing resource configured - All paths in env vars must NOT contain spaces (signing fails silently) ### Certificate Types Comparison | Type | SmartScreen | Cost | Setup | | ------------------------ | --------------------------- | ---------- | ------------------------- | | Standard (OV) | Builds reputation over time | ~$200/year | Buy from CA, install .pfx | | Extended Validation (EV) | Immediate trust | ~$400/year | HSM/USB token required | | Azure Trusted Signing | Immediate trust | ~$10/month | Azure subscription | --- ## CI/CD Signing Secrets Never commit signing credentials. Use CI/CD secret management: ```yaml # GitHub Actions example -- secrets stored in repository settings env: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }} WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }} ``` **macOS CI caveat:** The signing certificate must be imported into the CI runner's Keychain. Use a setup step: ```yaml - name: Import signing certificate env: CERTIFICATE_P12: ${{ secrets.MAC_CERTIFICATE_P12 }} CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }} run: | echo "$CERTIFICATE_P12" | base64 --decode > certificate.p12 security create-keychain -p "" build.keychain security import certificate.p12 -k build.keychain -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign security set-keychain-settings build.keychain security list-keychains -d user -s build.keychain login.keychain security set-key-partition-list -S apple-tool:,apple: -k "" build.keychain ```
-
-
reference.md 10 KB
# Electron Forge Reference > Quick-lookup tables, CLI commands, maker/publisher/fuse reference, Forge vs builder comparison. See [SKILL.md](SKILL.md) for decision frameworks and red flags. See [examples/](examples/) for full code examples. --- ## CLI Commands | Command | Action | Output | | ------------------------ | -------------------------------- | ------------------------------------------- | | `electron-forge start` | Launch in dev mode | Running app (with HMR if plugin configured) | | `electron-forge package` | Create app bundle | `.app` / `.exe` (no installer) | | `electron-forge make` | Create distributable installers | `.dmg`, `.exe`, `.deb`, etc. | | `electron-forge publish` | Upload artifacts to publisher | Artifacts on GitHub/S3/Snapcraft | | `electron-forge import` | Import existing Electron project | Adds Forge config and scripts | | `electron-forge init` | Create new project from template | Scaffolded project | --- ## Makers Quick Reference | Maker | Package | Platform | Output | Use Case | | ---------------- | ---------------- | ------------ | ---------- | ------------------------------------- | | Squirrel.Windows | `maker-squirrel` | Windows | `.exe` | Auto-updating desktop app | | WiX MSI | `maker-wix` | Windows | `.msi` | Enterprise IT deployment | | MSIX | `maker-msix` | Windows | `.msix` | Modern Windows packaging | | AppX | `maker-appx` | Windows | `.appx` | Microsoft Store | | DMG | `maker-dmg` | macOS | `.dmg` | Direct distribution (drag-to-install) | | PKG | `maker-pkg` | macOS | `.pkg` | Mac App Store | | ZIP | `maker-zip` | macOS, Linux | `.zip` | Auto-updater feed, universal archive | | deb | `maker-deb` | Linux | `.deb` | Debian/Ubuntu | | RPM | `maker-rpm` | Linux | `.rpm` | Fedora/RHEL | | Flatpak | `maker-flatpak` | Linux | `.flatpak` | Sandboxed cross-distro | | Snap | `maker-snap` | Linux | `.snap` | Snap Store distribution | All packages are scoped under `@electron-forge/` (e.g., `@electron-forge/maker-squirrel`). --- ## Publishers Quick Reference | Publisher | Package | Target | Auth | | --------- | --------------------- | -------------------- | ---------------------- | | GitHub | `publisher-github` | GitHub Releases | `GITHUB_TOKEN` env var | | S3 | `publisher-s3` | Amazon S3 bucket | AWS credentials | | GCS | `publisher-gcs` | Google Cloud Storage | GCP credentials | | Snapcraft | `publisher-snapcraft` | Snap Store | `snapcraft login` | --- ## Plugins Quick Reference | Plugin | Package | Purpose | | ------------------- | -------------------------------------------- | ------------------------------------------------ | | Vite | `@electron-forge/plugin-vite` | Vite bundling for main + renderer (experimental) | | Webpack | `@electron-forge/plugin-webpack` | Webpack bundling for main + renderer | | Fuses | `@electron-forge/plugin-fuses` | Flip Electron Fuses at package time | | Auto Unpack Natives | `@electron-forge/plugin-auto-unpack-natives` | Auto-detect and unpack native modules | | Electronegativity | `@electron-forge/plugin-electronegativity` | Security audit during build | --- ## Fuses Quick Reference | Fuse | Recommended | Effect | | --------------------------------------- | ----------- | -------------------------------------- | | `RunAsNode` | `false` | Disables `ELECTRON_RUN_AS_NODE` | | `EnableCookieEncryption` | `true` | OS-level cookie encryption | | `EnableNodeOptionsEnvironmentVariable` | `false` | Disables `NODE_OPTIONS` | | `EnableNodeCliInspectArguments` | `false` | Disables `--inspect` | | `EnableEmbeddedAsarIntegrityValidation` | `true` | ASAR integrity check (macOS + Windows) | | `OnlyLoadAppFromAsar` | `true` | Prevents loose file loading | | `GrantFileProtocolExtraPrivileges` | `false` | Restricts `file://` privileges | | `LoadBrowserProcessSpecificV8Snapshot` | default | Separate V8 snapshot | Verify: `npx @electron/fuses read --app /path/to/app` --- ## forge.config.ts Top-Level Fields | Field | Type | Purpose | | ----------------- | ------------------- | -------------------------------------------------- | | `packagerConfig` | `PackagerConfig` | `@electron/packager` options (icon, signing, ASAR) | | `rebuildConfig` | `RebuildConfig` | `@electron/rebuild` options for native modules | | `makers` | `MakerConfig[]` | Platform-specific installer generators | | `publishers` | `PublisherConfig[]` | Artifact upload targets | | `plugins` | `PluginConfig[]` | Build plugins (Vite, Webpack, Fuses) | | `hooks` | `HooksConfig` | Custom lifecycle callbacks | | `buildIdentifier` | `string` | Build variant identifier (prod, beta) | | `outDir` | `string` | Output directory path | **Cannot override in packagerConfig:** `dir`, `arch`, `platform`, `out`, `electronVersion` (set by Forge internally). --- ## Build Hooks | Hook | When | Mutating? | | --------------------- | ------------------------------- | ---------------------------- | | `generateAssets` | Before start or package | No | | `preStart` | Before app launches | No | | `postStart` | After app launches | No | | `prePackage` | Before @electron/packager | No | | `packageAfterCopy` | After build dir copied | No | | `packageAfterPrune` | After devDependencies pruned | No | | `packageAfterExtract` | After Electron binary extracted | No | | `postPackage` | After package completes | No | | `preMake` | Before makers run | No | | `postMake` | After makers complete | Yes -- return `MakeResult[]` | | `readPackageJson` | Every package.json read | Yes -- return modified JSON | --- ## Forge vs electron-builder | Factor | Electron Forge | electron-builder | | --------------------- | -------------------------------- | ----------------------------------- | | Maintainer | Electron team (first-party) | Community | | Config | TypeScript / JavaScript | YAML / JSON / JS | | Architecture | Composable packages | Monolithic | | New Electron features | Same-day | Delayed | | Windows installers | Squirrel, WiX, MSIX, AppX | NSIS, Squirrel, MSI, AppX, portable | | macOS installers | DMG, ZIP, PKG | DMG, ZIP, PKG, MAS | | Linux installers | deb, RPM, Flatpak, Snap | deb, RPM, AppImage, Snap, Flatpak | | Auto-update | Via Squirrel + update server | Built-in electron-updater | | npm downloads | ~50K/week | ~1.4M/week | | Extensibility | Custom makers/publishers/plugins | Limited scripting hooks | | NSIS support | No (use WiX or Squirrel) | Yes (full NSIS scripting) | **Choose Forge when:** Starting new projects, want first-party support, need TypeScript config, value composability. **Choose electron-builder when:** Need NSIS installer, want YAML config, need broader community resources, migrating existing electron-builder project. --- ## Code Signing Checklist - [ ] `electron` is in `devDependencies` (not `dependencies`) - [ ] `asar: true` enabled in `packagerConfig` - [ ] macOS: `osxSign` + `osxNotarize` configured - [ ] macOS: Developer ID Application certificate installed in Keychain - [ ] macOS: Using app-specific password (not Apple ID password) - [ ] Windows: Certificate file or Azure Trusted Signing configured - [ ] Signing credentials stored in environment variables (not in config) - [ ] Fuses enabled: `RunAsNode: false`, `OnlyLoadAppFromAsar: true` - [ ] CI/CD imports signing certificate into build runner Keychain (macOS) - [ ] CI/CD timeout accounts for notarization delay (2-10 minutes) --- ## See Also - [Electron Forge Documentation](https://www.electronforge.io/) - [Electron Forge GitHub](https://github.com/electron/forge) - [Electron Code Signing Guide](https://www.electronjs.org/docs/latest/tutorial/code-signing) - [Electron Fuses Tutorial](https://www.electronjs.org/docs/latest/tutorial/fuses) - [Electron ASAR Integrity](https://www.electronjs.org/docs/latest/tutorial/asar-integrity) -
SKILL.md 19.2 KB
--- name: desktop-packaging-electron-forge description: Electron Forge build toolchain -- makers, publishers, code signing, fuses, hooks, CI/CD packaging --- # Electron Forge Packaging > **Quick Guide:** Electron Forge v7 is the official Electron build toolchain. Configure via `forge.config.ts` with typed imports from `@electron-forge/shared-types`. Use **makers** to produce platform-specific installers (Squirrel for Windows, DMG/ZIP for macOS, deb/rpm for Linux). Use **publishers** to upload artifacts (GitHub Releases, S3, Snapcraft). Always code-sign production builds -- macOS requires both signing and notarization. Enable Electron **Fuses** to harden the binary at package time. Use **hooks** (`prePackage`, `postMake`) for custom build logic. Electron itself MUST be a `devDependency` -- Forge bundles only `dependencies`. --- <critical_requirements> ## CRITICAL: Before Using This Skill > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants) **(You MUST place `electron` in `devDependencies` -- Forge provides the Electron binary during packaging; placing it in `dependencies` bloats the app by ~200MB)** **(You MUST code-sign macOS builds with `osxSign` and `osxNotarize` in `packagerConfig` -- unsigned apps are blocked by Gatekeeper on macOS 10.15+)** **(You MUST enable `asar: true` in `packagerConfig` -- without ASAR, your source code ships as plain-text files readable by any user)** **(You MUST store signing credentials in environment variables -- never hardcode secrets in `forge.config.ts`)** **(You MUST enable Fuses (`FuseV1Options.RunAsNode: false`, `OnlyLoadAppFromAsar: true`) for production security -- without them, attackers can bypass ASAR integrity and run arbitrary Node.js code)** </critical_requirements> --- **Auto-detection:** Electron Forge, electron-forge, forge.config.ts, forge.config.js, @electron-forge, maker-squirrel, maker-dmg, maker-deb, maker-rpm, maker-zip, maker-flatpak, maker-snap, maker-appx, maker-wix, maker-pkg, maker-msix, publisher-github, publisher-s3, publisher-snapcraft, plugin-vite, plugin-webpack, plugin-fuses, FusesPlugin, osxSign, osxNotarize, electron-forge make, electron-forge publish, electron-forge package **When to use:** - Configuring `forge.config.ts` for packaging and distribution - Choosing and configuring makers for target platforms - Setting up publishers for automated release distribution - Code signing macOS (notarization) or Windows (Authenticode) builds - Enabling Electron Fuses for binary hardening - Adding build hooks for custom pre/post-packaging logic - Setting up CI/CD pipelines for cross-platform builds - Deciding between Electron Forge and electron-builder **When NOT to use:** - Electron app architecture (main/renderer process, IPC, preload) -- use the Electron framework skill - Choosing or configuring a bundler for renderer code in isolation - Auto-update implementation (that is an Electron framework concern, not a Forge concern) - UI framework selection for renderers **Key patterns covered:** - forge.config.ts structure with typed configuration - Platform-specific maker selection and configuration - macOS code signing + notarization setup - Windows Authenticode signing (traditional + Azure Trusted Signing) - Fuses plugin for binary hardening - Publisher configuration (GitHub, S3, Snapcraft) - Build hooks and lifecycle - CI/CD cross-platform build matrix - Forge vs electron-builder decision framework --- <philosophy> ## Philosophy Electron Forge is a **unified build pipeline** that composes first-party Electron tools (`@electron/packager`, `@electron/rebuild`, `@electron/osx-sign`, `@electron/notarize`, `@electron/fuses`) into a single workflow. Rather than reimplementing build logic, Forge orchestrates existing tools through three steps: 1. **Package** -- `@electron/packager` creates the platform-specific app bundle (.app, .exe) 2. **Make** -- Makers transform the bundle into distributable installers (.dmg, .msi, .deb) 3. **Publish** -- Publishers upload make artifacts to distribution targets (GitHub, S3) **Why Forge over alternatives:** - First-party: maintained by the Electron team, receives new features (ASAR integrity, universal macOS builds) as soon as they ship - Composable: makers, publishers, and plugins are independent npm packages - TypeScript-native: `forge.config.ts` with full type inference since v7 **Key constraint:** Forge runs makers only for the current host OS by default. Cross-platform builds require CI/CD with per-platform runners (macOS for .dmg, Windows for .exe, Linux for .deb). </philosophy> --- <patterns> ## Core Patterns ### Pattern 1: forge.config.ts Structure The configuration file defines packaging options, makers, publishers, plugins, and hooks. All fields are optional. ```typescript import type { ForgeConfig } from "@electron-forge/shared-types"; import { FusesPlugin } from "@electron-forge/plugin-fuses"; import { FuseV1Options, FuseVersion } from "@electron/fuses"; const config: ForgeConfig = { packagerConfig: { asar: true, icon: "./assets/icon", // omit extension -- Forge picks .icns/.ico/.png name: "MyApp", executableName: "my-app", appBundleId: "com.example.myapp", }, makers: [ /* see Pattern 2 */ ], publishers: [ /* see Pattern 5 */ ], plugins: [ /* see Pattern 4 */ ], hooks: { /* see Pattern 6 */ }, }; export default config; ``` **Key constraint:** You cannot override `dir`, `arch`, `platform`, `out`, or `electronVersion` in `packagerConfig` -- Forge sets these internally. See [examples/core.md](examples/core.md) for full configuration with makers, signing, and fuses. --- ### Pattern 2: Maker Selection by Platform Each maker produces a specific installer format for a target OS. Install only the makers you need. | Maker | Package | Platform | Output | | ---------------- | -------------------------------- | ------------ | ------------------------- | | Squirrel.Windows | `@electron-forge/maker-squirrel` | Windows | `.exe` (auto-updating) | | WiX MSI | `@electron-forge/maker-wix` | Windows | `.msi` | | MSIX | `@electron-forge/maker-msix` | Windows | `.msix` | | AppX | `@electron-forge/maker-appx` | Windows | `.appx` (Microsoft Store) | | DMG | `@electron-forge/maker-dmg` | macOS | `.dmg` | | PKG | `@electron-forge/maker-pkg` | macOS | `.pkg` (Mac App Store) | | ZIP | `@electron-forge/maker-zip` | macOS, Linux | `.zip` | | deb | `@electron-forge/maker-deb` | Linux | `.deb` (Debian/Ubuntu) | | RPM | `@electron-forge/maker-rpm` | Linux | `.rpm` (Fedora/RHEL) | | Flatpak | `@electron-forge/maker-flatpak` | Linux | `.flatpak` | | Snap | `@electron-forge/maker-snap` | Linux | `.snap` | **Recommended starter set:** Squirrel (Windows) + DMG + ZIP (macOS) + deb (Linux). See [examples/core.md](examples/core.md) for maker configuration examples. --- ### Pattern 3: Code Signing macOS and Windows both require code signing for distribution. Without it, OS security warnings block or discourage installation. #### macOS (Sign + Notarize) ```typescript packagerConfig: { osxSign: {}, // empty object activates defaults -- signs with first valid identity osxNotarize: { appleId: process.env.APPLE_ID, appleIdPassword: process.env.APPLE_PASSWORD, // app-specific password, NOT Apple ID password teamId: process.env.APPLE_TEAM_ID, }, }, ``` **Requirements:** Apple Developer Program membership, "Developer ID Application" certificate in Keychain, `hardenedRuntime: true` (required for notarization). #### Windows (Authenticode) ```typescript // Squirrel maker with traditional certificate { name: "@electron-forge/maker-squirrel", config: { certificateFile: process.env.WIN_CSC_LINK, certificatePassword: process.env.WIN_CSC_KEY_PASSWORD, }, }, ``` **Key point:** Since June 2023, private keys for code signing certificates must be stored on FIPS 140 Level 2 hardware. Azure Trusted Signing is the modern alternative for Windows -- see [examples/signing.md](examples/signing.md). See [examples/signing.md](examples/signing.md) for full signing configuration, notarization strategies, and Azure Trusted Signing setup. --- ### Pattern 4: Fuses Plugin (Binary Hardening) Fuses are bits in the Electron binary flipped at package time to enable/disable features permanently. ```typescript import { FusesPlugin } from "@electron-forge/plugin-fuses"; import { FuseV1Options, FuseVersion } from "@electron/fuses"; plugins: [ new FusesPlugin({ version: FuseVersion.V1, [FuseV1Options.RunAsNode]: false, [FuseV1Options.EnableCookieEncryption]: true, [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, [FuseV1Options.EnableNodeCliInspectArguments]: false, [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, [FuseV1Options.OnlyLoadAppFromAsar]: true, [FuseV1Options.GrantFileProtocolExtraPrivileges]: false, }), ], ``` **Why critical:** Without `RunAsNode: false`, attackers can set `ELECTRON_RUN_AS_NODE=1` and run arbitrary code. Without `OnlyLoadAppFromAsar: true`, ASAR integrity validation can be bypassed by placing files alongside the archive. **Verification:** `npx @electron/fuses read --app /path/to/packaged/app` See [examples/core.md](examples/core.md) for the full fuses configuration with explanations. --- ### Pattern 5: Publishers Publishers upload make artifacts to distribution targets. ```typescript publishers: [ { name: "@electron-forge/publisher-github", config: { repository: { owner: "my-org", name: "my-app" }, prerelease: true, }, }, ], ``` | Publisher | Package | Target | | --------- | ------------------------------------- | -------------------- | | GitHub | `@electron-forge/publisher-github` | GitHub Releases | | S3 | `@electron-forge/publisher-s3` | Amazon S3 bucket | | Snapcraft | `@electron-forge/publisher-snapcraft` | Snap Store | | GCS | `@electron-forge/publisher-gcs` | Google Cloud Storage | **Authentication:** Use `GITHUB_TOKEN` env var for GitHub publisher. Use AWS credentials (env vars or shared credentials file) for S3. See [examples/publishers.md](examples/publishers.md) for publisher configuration with CI/CD integration. --- ### Pattern 6: Build Hooks Hooks insert custom logic at specific points in the build lifecycle. ```typescript hooks: { prePackage: async (config, platform, arch) => { // Run before @electron/packager -- generate assets, validate config }, postMake: async (config, makeResults) => { // Run after all makers -- rename artifacts, upload to CDN, notify // Return modified makeResults array to affect subsequent steps return makeResults; }, }, ``` | Hook | When | Can Mutate? | | ------------------- | ------------------------------- | ------------------------------------- | | `generateAssets` | Before start or package | No | | `prePackage` | Before @electron/packager | No | | `packageAfterCopy` | After packager copies build dir | No | | `packageAfterPrune` | After devDependencies pruned | No | | `postPackage` | After package completes | No | | `preMake` | Before makers run | No | | `postMake` | After makers complete | Yes -- return modified `MakeResult[]` | | `readPackageJson` | Every package.json read | Yes -- return modified package.json | See [examples/hooks.md](examples/hooks.md) for hook implementation examples. --- ### Pattern 7: Bundler Plugins (Vite / Webpack) Forge plugins integrate bundlers for compiling main and renderer process code with HMR. ```typescript import { VitePlugin } from "@electron-forge/plugin-vite"; plugins: [ new VitePlugin({ build: [ { entry: "src/main.ts", config: "vite.main.config.mts" }, { entry: "src/preload.ts", config: "vite.preload.config.mts" }, ], renderer: [ { name: "main_window", config: "vite.renderer.config.mts" }, ], }), ], ``` **Status:** The Vite plugin is marked **experimental** as of v7.5.0 -- minor versions may include breaking changes. **Key detail:** The plugin injects global variables (`MAIN_WINDOW_VITE_DEV_SERVER_URL`, `MAIN_WINDOW_VITE_NAME`) for loading the renderer in dev vs production. Declare these in a `.d.ts` file for TypeScript. See [examples/core.md](examples/core.md) for Vite plugin setup and global variable declarations. </patterns> --- <decision_framework> ## Decision Framework ### Forge vs electron-builder ``` Choosing a build tool? +-- Want first-party Electron support (ASAR integrity, universal macOS)? | +-- YES --> Electron Forge (receives features same-day as Electron) +-- Need YAML-based config, NSIS installer, or broad community support? | +-- YES --> electron-builder (more installer targets, larger community) +-- Need maximum customization for enterprise? | +-- YES --> electron-builder (more config options, NSIS scripting) +-- Starting a new project? +-- YES --> Electron Forge (official recommendation, TypeScript config) ``` | Factor | Electron Forge | electron-builder | | --------------------- | --------------------------- | ------------------------- | | Maintainer | Electron team | Community | | Config format | TypeScript / JavaScript | YAML / JSON / JS | | New Electron features | Same-day | Delayed | | Plugin ecosystem | Makers, publishers, plugins | Built-in monolith | | Windows installers | Squirrel, WiX, MSIX, AppX | NSIS, Squirrel, MSI, AppX | | macOS installers | DMG, ZIP, PKG | DMG, ZIP, PKG, MAS | | npm downloads | ~50K/week | ~1.4M/week | | Architecture | Composable packages | Monolithic | ### Maker Selection ``` Which maker for your platform? +-- Windows? | +-- Auto-updating desktop app --> Squirrel.Windows | +-- Enterprise/IT deployment --> WiX MSI | +-- Microsoft Store --> AppX or MSIX +-- macOS? | +-- Direct distribution --> DMG (drag-to-install) + ZIP (for auto-updater) | +-- Mac App Store --> PKG +-- Linux? | +-- Debian/Ubuntu --> deb | +-- Fedora/RHEL --> RPM | +-- Universal sandboxed --> Flatpak or Snap ``` </decision_framework> --- **Detailed resources:** - [examples/core.md](examples/core.md) -- forge.config.ts setup, makers, Vite plugin, fuses, dependency management - [examples/signing.md](examples/signing.md) -- macOS notarization, Windows Authenticode, Azure Trusted Signing, entitlements - [examples/publishers.md](examples/publishers.md) -- GitHub, S3, Snapcraft publishers with CI/CD patterns - [examples/hooks.md](examples/hooks.md) -- Build lifecycle hooks, custom makers, extending Forge - [reference.md](reference.md) -- Maker/publisher quick-reference tables, fuse options, CLI commands, Forge vs builder comparison --- <red_flags> ## RED FLAGS **Critical Issues:** - Placing `electron` in `dependencies` instead of `devDependencies` -- bloats the packaged app by ~200MB because Forge already provides the binary - Shipping without code signing -- macOS Gatekeeper blocks unsigned apps entirely; Windows SmartScreen shows scary warnings - Hardcoding signing credentials in `forge.config.ts` -- secrets end up in version control; always use `process.env` - Not enabling ASAR (`asar: false`) -- ships your source code as readable plain-text files - Not setting `RunAsNode: false` fuse -- allows `ELECTRON_RUN_AS_NODE=1` to execute arbitrary code with your app's permissions **Architecture Issues:** - Running `electron-forge make` on macOS expecting Windows .exe output -- makers run only on the target OS (use CI/CD with per-platform runners) - Placing native modules (better-sqlite3, sharp) inside ASAR without `asarUnpack` -- native addons cannot load from inside an ASAR archive - Not running `@electron/rebuild` for native modules -- modules compiled for system Node.js crash in Electron's Node.js runtime (Forge runs rebuild automatically during package, but manual installs need it) - Using `electron-forge package` for distribution -- this produces an uninstallable app bundle; use `make` for distributable installers **Configuration Mistakes:** - Setting `asar: true` without `asarUnpack` for native modules -- the app will crash at runtime trying to load the native addon - Forgetting the `platforms` array on makers -- maker runs on all platforms and fails on unsupported ones - Using `osxNotarize` without `osxSign` -- notarization requires a signed binary; Apple rejects unsigned submissions - Using your Apple ID password instead of an app-specific password for `osxNotarize` -- regular passwords are rejected when 2FA is enabled **Gotchas & Edge Cases:** - `electron-forge start` in dev mode does not run makers -- dev mode uses unpackaged source; always test with `make` before release - Notarization takes 2-10 minutes per build -- factor this into CI/CD timeout settings - Squirrel.Windows handles first-run events (shortcuts, desktop icons) -- your main process must handle Squirrel startup events or the app opens multiple times during install - `__dirname` resolves to virtual ASAR paths in packaged builds -- use `app.isPackaged` + `process.resourcesPath` for resource file paths - The Vite plugin is experimental since v7.5.0 -- minor version bumps may include breaking changes to its config shape - Azure Trusted Signing paths must not contain spaces -- signing fails silently if any path has spaces - Forge hooks run in parallel, not sequentially -- do not rely on execution order between hooks of the same type </red_flags> --- <critical_reminders> ## CRITICAL REMINDERS > **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants) **(You MUST place `electron` in `devDependencies` -- Forge provides the Electron binary during packaging; placing it in `dependencies` bloats the app by ~200MB)** **(You MUST code-sign macOS builds with `osxSign` and `osxNotarize` in `packagerConfig` -- unsigned apps are blocked by Gatekeeper on macOS 10.15+)** **(You MUST enable `asar: true` in `packagerConfig` -- without ASAR, your source code ships as plain-text files readable by any user)** **(You MUST store signing credentials in environment variables -- never hardcode secrets in `forge.config.ts`)** **(You MUST enable Fuses (`FuseV1Options.RunAsNode: false`, `OnlyLoadAppFromAsar: true`) for production security -- without them, attackers can bypass ASAR integrity and run arbitrary Node.js code)** **Failure to follow these rules will produce insecure, bloated, or unsigned builds that OS security mechanisms will block or warn users about.** </critical_reminders>
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.