skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
brianlovin/agent-config120 installs

electron-wrapper

Wrap a Bun web app into an Electron desktop app with native window management, auto-updates, code signing, and CI/CD distribution. Use when the user wants to create a native desktop application from an existing Bun-based web server, package it for macOS/Windows, set up auto-updating, or handle Electron UX concerns like drag regions and traffic lights. Also use when cutting releases or tagging versions for Electron apps.

How do I install this agent skill?

npx skills add https://github.com/brianlovin/agent-config --skill electron-wrapper
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    The skill provides a comprehensive and secure framework for wrapping a Bun-based web application into an Electron desktop app. It follows industry best practices for Electron security and uses trusted sources for external dependencies.

  • Socketpass

    No alerts

  • Snykwarn

    Risk: MEDIUM · 1 issue

What does this agent skill do?

Electron Wrapper for Bun Web Apps

This skill guides wrapping an existing Bun web server into a native desktop app using Electron. It's based on a proven implementation that solved every major integration challenge.

Architecture

"Electron as chrome, Bun as server" — Two runtimes working together:

  • Electron/Node.js handles window management, native menus, auto-updates, and IPC
  • Bun runs the actual web server with all your application logic

The Electron main process spawns a bundled Bun binary that runs your server, then loads http://localhost:{port} in a BrowserWindow. Your web app doesn't know or care that it's inside Electron — it's just a web page with an optional window.electronAPI bridge for native features.

This architecture means:

  • Zero changes to your server code (it's still a standard Bun HTTP server)
  • The web app works identically in a browser or in Electron
  • Electron handles only what browsers can't: window chrome, system tray, auto-updates, file system access
  • Two separate node_modules — Electron uses npm, your web app uses Bun

Phase 1: Project Setup

Create the Electron subproject alongside your existing Bun web app.

What to create:

  • electron/ directory with its own package.json (npm, not Bun), two tsconfigs (ESM for main, CJS for preload), electron-builder.yml, and macOS entitlements
  • scripts/build-server.ts for bundling the server
  • scripts/download-bun.ts for downloading platform-specific Bun binaries
  • Parent project changes: new scripts, tsconfig excludes, .gitignore entries

Reference: project-setup.md

Phase 2: Main Process

Build the Electron main process — the entry point, server spawning, window management, auto-updater, and preload bridge.

Files to create:

FilePurpose
electron/src/main/index.tsApp lifecycle, dev/prod mode, single-instance lock, IPC handlers
electron/src/main/bun-server.tsSpawn bundled Bun, port selection, health polling, env var injection
electron/src/main/window.tsBrowserWindow config, bounds persistence, security settings, navigation guards
electron/src/main/updater.tselectron-updater setup, event forwarding to renderer
electron/src/preload/index.tscontextBridge API with invoke/on patterns and unsubscribe support

Key decisions:

  • Dev mode uses ELECTRON_DEV_URL env var to connect to the external dev server (no internal Bun spawn)
  • autoDownload: false — let users choose when to download updates
  • Preload exposes only specific methods, never raw ipcRenderer
  • Window persists bounds via electron-store

Reference: main-process.md

Phase 3: Web App Adaptation

Adapt the existing web app to detect and respond to the Electron environment while remaining fully functional as a standalone web app.

Changes to the web app:

ChangeDetails
Electron detection utilityisElectron(), getElectronPlatform(), isMacElectron(), applyElectronDocumentAttributes()
Type declarationswindow.electronAPI with all properties optional
CSS drag regions.app-window-drag/.app-window-no-drag classes, auto-exclude interactive elements
Traffic light spacing--electron-traffic-left CSS variable (72px on macOS, 0px elsewhere)
Storage pathEnv var for data directory, falling back to CWD
Static asset servingEnv var for static dir in production mode
Auto-update hookuseElectronUpdater() React hook with download/install controls
Update notificationPill component showing available → downloading → ready states
Feature gatingDisable demo mode, hosted features when in Electron

Reference: web-adaptation.md

Phase 4: Build & Distribution

Bundle everything, set up CI/CD, and handle code signing.

Build pipeline:

  1. Build web app (bun run build)
  2. Bundle server to single file (Bun.build() → resources/server/index.js)
  3. Download platform-specific Bun binaries → resources/bun/{platform}-{arch}/
  4. Compile Electron TypeScript (two passes: main ESM + preload CJS)
  5. electron-builder packages everything with extraResources

CI/CD:

  • GitHub Actions triggered by version tags (e.g., v*, clippy-v*)
  • Matrix builds: macOS arm64/x64 on macos-14, Windows x64 on windows-latest
  • --publish never in build step, separate publish job creates draft GitHub release
  • Apple certificate import and notarization in CI

Code signing:

  • macOS: Developer ID Application certificate, exported as base64 .p12
  • Notarization via Apple ID + app-specific password
  • 5 GitHub secrets required: APPLE_CERTIFICATE, APPLE_CERTIFICATE_PASSWORD, APPLE_ID, APPLE_PASSWORD, APPLE_TEAM_ID

Icons:

  • macOS: sips + iconutil from source PNG → .icns
  • Windows: png-to-ico npm package → .ico

Reference: build-and-distribute.md

Cutting a Release

Never build release artifacts locally. CI has the signing certificates and notarization credentials. Local builds produce unsigned apps that macOS Gatekeeper will block.

Release workflow:

  1. Bump version in electron/package.json, commit, and merge to main
  2. Find the tag pattern the CI workflow expects:
    grep -A2 'tags:' .github/workflows/*.yml
    
  3. Tag the merged commit on main:
    git tag <pattern><version> origin/main
    git push origin <pattern><version>
    
  4. Monitor CI:
    gh run list --workflow=<workflow>.yml --limit=1
    
  5. Review and publish the draft release on GitHub

Common mistakes:

  • Running electron-builder --publish always locally — no notarization
  • Using gh release create with local artifacts — unsigned
  • Tagging before the version bump is merged — wrong version in build
  • Tagging a feature branch instead of origin/main

See pitfalls.md §13 for full details.

Critical Pitfalls

Quick-reference list — see pitfalls.md for full details with symptoms and code examples.

#PitfallOne-line fix
1ESM/CJS conflicts"type": "module" + default import pattern for CJS packages
2Preload must be CJSSeparate tsconfig with "module": "CommonJS"
3__dirname unavailablefileURLToPath(import.meta.url) polyfill
4Dev mode MIME errorsConnect to external dev server via ELECTRON_DEV_URL
5Bun version mismatchPin version in download script, match dev version
6nvm PATH issuesbash -lc for spawned processes
7Wrong storage pathEnv var + app.getPath("userData")
8White flash on openshow: false + ready-to-show + dark backgroundColor
11${platform} != process.platformPut Bun extraResources in mac:/win: sections with darwin-/win32- prefixes
12Bun workspace hoists depsBundle main with esbuild + createRequire banner, or use npm for electron dir
13Local builds aren't notarizedAlways release via CI tags, never electron-builder --publish locally

Dev Workflow

The electron:dev command runs the full development environment:

npm run dev
  ├── concurrently
  │   ├── dev:web    → cd .. && bun run dev          (Bun dev server with HMR)
  │   └── dev:electron
  │       ├── wait-on http://localhost:3005           (wait for dev server)
  │       ├── npm run build                           (compile TS)
  │       └── ELECTRON_DEV_URL=... electron .         (launch Electron)
  • The web dev server runs with HMR — changes reflect instantly
  • Electron connects to the dev server instead of spawning its own Bun
  • Preload and main process changes require restarting electron:dev
  • Web app changes hot-reload automatically

To test production-like behavior locally:

bun run electron:pack    # builds everything, packages without installer
# Output in electron/release/

Customization Checklist

When adapting this for a new project, update these project-specific values:

  • App name in electron-builder.yml (productName, appId)
  • Window title in window.ts
  • Dev server port in dev:electron script and dev script
  • Environment variable names (e.g., APP_DATA_DIR, APP_STATIC_DIR)
  • backgroundColor in window config to match your app's theme
  • category in electron-builder.yml mac section
  • Repository URL in electron/package.json
  • Bun version in download-bun.ts
  • Icon assets in electron/assets/

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/brianlovin/agent-config/electron-wrapper">View electron-wrapper on skillZs</a>