How Modly Syncs Built‑in Extensions to the User Data Directory
Modly synchronizes built‑in extensions by comparing version markers between the application bundle and the user data directory, then atomically copying files from resources/extensions to the user data path using the syncBuiltinExtensions() function defined in electron/main/builtin-sync.ts.
The lightningpixel/modly repository implements a robust extension management system that ensures users always receive the latest bundled functionality without manual intervention. Understanding how Modly syncs built‑in extensions to the user data directory reveals a careful balance between seamless updates, data integrity, and error resilience in Electron applications.
The Synchronization Architecture
The sync process operates between two distinct locations: the application bundle (read‑only source) and the user data directory (writable target). When the Electron main process initializes, it resolves these paths using native Electron APIs and Node.js utilities to establish the source and destination for the copy operation.
- Source Directory: Located at
process.resourcesPath/extensionsinside the packaged application, this folder contains the canonical versions of all built‑in extensions shipped with the app. - Target Directory: Resolved via
app.getPath('userData'), typically resulting in a path like~/Modly/extensions(platform‑dependent), where the synchronized copies reside and where the extension loader expects to find available extensions.
Step‑by‑Step Implementation
The core logic resides in electron/main/builtin-sync.ts, which exports an async function that orchestrates the entire process.
Locating Source and Target Directories
The function first resolves absolute paths for both the source and target directories. It uses Electron’s app.getPath('userData') to determine the user‑specific data location and constructs the extensions subdirectory. The source path points to the resources/extensions folder embedded within the application bundle.
// electron/main/builtin-sync.ts
import { app } from 'electron';
import { promises as fs } from 'fs';
import path from 'path';
import { logger } from './logger';
export async function syncBuiltinExtensions() {
const userData = app.getPath('userData');
const targetDir = path.join(userData, 'extensions');
const sourceDir = path.join(process.resourcesPath, 'extensions');
await fs.mkdir(targetDir, { recursive: true });
// ... version check and copy logic
}
Version Validation Strategy
To avoid unnecessary writes and detect updates, the routine performs a lightweight version check using a builtin-version.txt file present in both the source and target directories. The function reads both files and compares their contents.
If the versions match, the process exits early, preserving existing files. If they differ—indicating an application update or first‑time launch—the target directory is cleared and rebuilt from the source to ensure consistency.
const versionFile = 'builtin-version.txt';
const [srcVersion, tgtVersion] = await Promise.all([
fs.readFile(path.join(sourceDir, versionFile), 'utf8').catch(() => ''),
fs.readFile(path.join(targetDir, versionFile), 'utf8').catch(() => ''),
]);
if (srcVersion !== tgtVersion) {
logger.info('Updating built‑in extensions...');
await fs.rm(targetDir, { recursive: true, force: true });
await copyDirectory(sourceDir, targetDir);
}
Recursive File Copy Operations
When an update is required, the system performs a recursive copy using Node.js fs.promises APIs. A helper function iterates over directory entries, preserving the folder structure and file permissions while copying each item from the source to the target.
async function copyDirectory(src: string, dst: string) {
const entries = await fs.readdir(src, { withFileTypes: true });
await fs.mkdir(dst, { recursive: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const dstPath = path.join(dst, entry.name);
if (entry.isDirectory()) {
await copyDirectory(srcPath, dstPath);
} else {
await fs.copyFile(srcPath, dstPath);
}
}
}
Error Handling and Logging
All operations are wrapped in try‑catch blocks (implied by the error handling description) that pipe failures to the centralized logger at electron/main/logger.ts. If any step fails—such as permission errors or missing source files—the sync aborts cleanly without modifying the existing user data, preventing corruption of the user’s extension environment.
Integration with the Main Process
The synchronization triggers early in the application lifecycle. In electron/main/index.ts, the syncBuiltinExtensions() function is awaited during the app.whenReady() event, ensuring extensions are fully deployed before the UI initializes.
// electron/main/index.ts
import { app } from 'electron';
import { syncBuiltinExtensions } from './builtin-sync';
app.whenReady().then(async () => {
await syncBuiltinExtensions(); // Ensures extensions are up‑to‑date
createMainWindow();
});
This guarantees that the extension system is ready before any renderer process or user interaction occurs.
Safety and Path Validation
While builtin-sync.ts handles the copying logic, the repository includes electron/main/extension-path-guard.ts to validate all extension paths and prevent directory traversal attacks. This module works in tandem with the sync process to ensure that no operation escapes the designated user data directory, adding a security layer to file system operations.
Summary
- Modly copies built‑in extensions from
process.resourcesPath/extensionstoapp.getPath('userData')/extensionsduring startup. - The
syncBuiltinExtensions()function inelectron/main/builtin-sync.tscomparesbuiltin-version.txtfiles to determine if an update is necessary. - Version mismatches trigger a complete replacement of the target directory to ensure consistency with the bundled application version.
- Failures are logged via
electron/main/logger.tsand abort cleanly without corrupting existing user data. - The process is invoked in
electron/main/index.tsbefore the main window creation to ensure extensions are available immediately.
Frequently Asked Questions
What is the source directory for built‑in Modly extensions?
Built‑in extensions are stored in the resources/extensions folder inside the Electron application bundle, accessed at runtime via process.resourcesPath. This read‑only location serves as the authoritative source for all synchronization operations.
How does Modly determine when to update synced extensions?
The system compares a builtin-version.txt file located in both the source and target directories. If the version strings differ, Modly assumes the bundled extensions are newer and performs a full re‑sync to update the user data directory.
What happens if the extension sync fails during startup?
The error handling in electron/main/builtin-sync.ts catches file system errors and logs them via the internal logger. The process aborts without modifying the existing extensions folder, ensuring that a failed update does not corrupt the user’s environment or remove working extensions.
Where is the extension synchronization logic located?
All synchronization logic is encapsulated in electron/main/builtin-sync.ts. The entry point electron/main/index.ts imports and executes this module during the Electron app.whenReady() lifecycle event, while electron/main/logger.ts provides the logging infrastructure used during the sync process.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →