How Built-In Extensions Are Synchronized in the Modly Electron Main Process

Modly synchronizes built-in extensions by copying them from the application bundle to a writable user data directory via the syncBuiltinExtensions() function during the Electron app.whenReady() lifecycle event.

The lightningpixel/modly repository ships core functionality as built-in extensions bundled within the application. Because the renderer process requires write access to extension directories, the Electron main process performs a mandatory one-way synchronization on every startup, mirroring the bundled resources into the user's data folder.

The Synchronization Lifecycle

The synchronization logic follows a strict sequence defined in electron/main/builtin-sync.ts. This ensures the built-in extensions are always current with the application version while preventing file corruption or stale assets.

Source Directory Resolution

The getBuiltinResourcesDir() function determines where to read the built-in extensions from based on the execution context. According to lines 10-15 of electron/main/builtin-sync.ts, the logic branches as follows:

  • Packaged applications: Extensions reside at process.resourcesPath/builtin-extensions
  • Development mode: Extensions are compiled to out/builtin-extensions relative to the project root

Destination Directory Setup

The destination is always computed by getBuiltinExtensionsDir() (lines 6-8), which returns a path inside the user's data directory:

// Resolved to <userData>/builtin-extensions
const destDir = getBuiltinExtensionsDir();

This uses Electron's app.getPath('userData') to ensure the renderer has appropriate write permissions.

Atomic Copy and Cleanup Strategy

The syncBuiltinExtensions() function implements a destructive copy pattern to guarantee consistency. As implemented in lines 32-37:

  1. Validation: If the source directory does not exist, synchronization skips silently (supporting builds that omit built-ins)
  2. Cleanup: The destination directory is wiped entirely using rmSync to eliminate removed or outdated extensions
  3. Copy: The entire tree is recreated using cpSync to copy recursively from source to destination

This overwrite strategy ensures no stale extension files persist across version updates.

Startup Trigger

The synchronization is triggered automatically during application initialization. In electron/main/index.ts (lines 89-103), the call is positioned inside the app.whenReady() promise handler:

  • It executes after the Chromium cache is cleared
  • It completes before the Python backend bridge initializes
  • It precedes the creation of the main UI window

This sequencing guarantees the extensions are available before the renderer process attempts to load them.

IPC Integration and Extension Discovery

Once synchronized, built-in extensions are exposed to the renderer alongside user-installed extensions. The IPC handler extensions:list in electron/main/ipc-handlers.ts (lines 45-50) aggregates both sources by reading:

  1. The user extensions directory
  2. The built-in directory returned by getBuiltinExtensionsDir()

The renderer receives a unified list with built-in entries typically listed first.

Implementation Code Examples

Manual Triggering

While automatic, you can invoke synchronization manually during main process execution:

import { syncBuiltinExtensions } from './builtin-sync';

// Force re-synchronization of built-in extensions
syncBuiltinExtensions();

Reading Extensions from the Renderer

Access the synchronized extensions via the IPC channel:

// Renderer process
const allExtensions = await window.electronAPI.invoke('extensions:list');
console.log(allExtensions); // Built-ins appear first, followed by user extensions

Lifecycle Integration

The automatic trigger occurs during the standard Electron bootstrap sequence:

app.whenReady().then(async () => {
  // ... cache clearing and setup ...
  
  await syncBuiltinExtensions();  // Copy built-ins to user data
  
  // ... initialize Python backend, create window ...
});

Summary

  • Built-in extensions are bundled in the application resources at process.resourcesPath/builtin-extensions (production) or out/builtin-extensions (development)
  • Synchronization occurs via syncBuiltinExtensions() in electron/main/builtin-sync.ts
  • Destination is always <userData>/builtin-extensions to ensure write access
  • Cleanup uses rmSync to wipe the destination before cpSync copies the new tree, preventing stale files
  • Timing is controlled in electron/main/index.ts during app.whenReady(), after cache clearing but before backend initialization
  • Discovery combines built-in and user extensions through the extensions:list IPC handler in electron/main/ipc-handlers.ts

Frequently Asked Questions

When does Modly synchronize built-in extensions?

Modly triggers synchronization once during every application startup, specifically inside the app.whenReady() event handler in electron/main/index.ts. This occurs before the Python backend and UI window are created, ensuring extensions are ready when the renderer process loads.

Why does Modly delete the destination directory before copying?

The syncBuiltinExtensions() function uses rmSync to wipe <userData>/builtin-extensions before calling cpSync to prevent stale, renamed, or removed extensions from persisting across version updates. This guarantees the destination exactly mirrors the current application bundle.

How can renderer processes access the synchronized built-in extensions?

The renderer accesses built-ins through the extensions:list IPC channel defined in electron/main/ipc-handlers.ts. This handler reads both the user extensions directory and the synchronized built-in directory, returning a combined array where built-in extensions are listed first.

What happens if the built-in extensions source directory is missing?

If getBuiltinResourcesDir() returns a path that does not exist (such as in custom builds that omit built-in extensions), syncBuiltinExtensions() skips the copy operation silently without throwing an error, allowing the application to start normally without built-in extensions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →