Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"scripts": {
"start": "electron-vite dev --config ./electron.vite.config.ts --outDir=dist --watch",
"prestart": "npm -w wp-studio run build",
"prestart": "npm -w wp-studio run build && node ../../scripts/ensure-dev-bundle-id.mjs",
"make": "electron-vite build --config ./electron.vite.config.ts --outDir=dist && electron-forge make .",
"make:windows-x64": "electron-vite build --config ./electron.vite.config.ts --outDir=dist && electron-forge make . --arch=x64 --platform=win32",
"make:windows-arm64": "electron-vite build --config ./electron.vite.config.ts --outDir=dist && electron-forge make . --arch=arm64 --platform=win32",
Expand Down
42 changes: 0 additions & 42 deletions apps/studio/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {
dialog,
MessageBoxSyncOptions,
} from 'electron';
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import path from 'path';
import { pathToFileURL } from 'url';
import * as Sentry from '@sentry/electron/main';
Expand Down Expand Up @@ -135,47 +133,7 @@ function launchExtensionBackgroundWorkers( appSession = session.defaultSession )
);
}

// In dev mode, every unpackaged Electron binary shares the bundle ID
// "com.github.Electron", so protocol handlers across workspaces collide in
// Launch Services. Patch the Electron.app Info.plist once per workspace to
// give each dev instance a unique bundle ID derived from its path, then
// relaunch so the new ID takes effect. The patch is idempotent and only
// runs on the first boot after `npm install`.
function ensureUniqueDevBundleId(): void {
if ( process.platform !== 'darwin' || ! process.defaultApp ) {
return;
}

// process.execPath points to <Electron.app>/Contents/MacOS/Electron, so
// walking up three dirs gives the Electron.app bundle reliably regardless
// of how the main process was bundled (electron-vite rewrites
// `require.resolve('electron')` to the bundled output path in dev).
const electronAppPath = path.resolve( process.execPath, '..', '..', '..' );
const infoPlist = path.join( electronAppPath, 'Contents', 'Info.plist' );
const plistBuddy = '/usr/libexec/PlistBuddy';
const lsregister =
'/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister';

const currentId = execFileSync( plistBuddy, [ '-c', 'Print :CFBundleIdentifier', infoPlist ], {
encoding: 'utf8',
} ).trim();
if ( currentId !== 'com.github.Electron' ) {
return;
}

const uniqueId = `com.studio.dev.${ createHash( 'sha1' )
.update( electronAppPath )
.digest( 'hex' )
.slice( 0, 12 ) }`;
execFileSync( plistBuddy, [ '-c', `Set :CFBundleIdentifier ${ uniqueId }`, infoPlist ] );
execFileSync( lsregister, [ '-f', electronAppPath ] );
app.relaunch();
app.exit( 0 );
}

async function appBoot() {
ensureUniqueDevBundleId();

app.setName( packageJson.productName );

Menu.setApplicationMenu( null );
Expand Down
40 changes: 40 additions & 0 deletions scripts/ensure-dev-bundle-id.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env node

// Git worktrees allow you to have multiple working directories associated with a single repo,
// enabling you to work on different branches simultaneously. This is especially useful for AI
// agent threads. It doesn't work well with Electron's protocol client handler logic on macOS,
// though. That's because macOS uses the Info.plist bundle identifier to determine which binary
// should open the URL, and if you've started Studio in multiple workspaces, there are multiple
// potential `wp-studio://` binaries. This script fixes the problem by patching
// `node_modules/electron/dist/Electron.app/Contents/Info.plist` to contain a unique bundle
// identifier for each workspace. Runs as a `prestart` before electron-vite dev spawns Electron.

import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { createRequire } from 'node:module';
import path from 'node:path';

if ( process.platform !== 'darwin' ) {
process.exit( 0 );
}

const electronBinary = createRequire( import.meta.url )( 'electron' );
const electronAppPath = path.resolve( electronBinary, '..', '..', '..' );
const infoPlist = path.join( electronAppPath, 'Contents', 'Info.plist' );
const plistBuddy = '/usr/libexec/PlistBuddy';

const currentId = execFileSync( plistBuddy, [ '-c', 'Print :CFBundleIdentifier', infoPlist ], {
encoding: 'utf8',
} ).trim();

if ( currentId !== 'com.github.Electron' ) {
process.exit( 0 );
}

const uniqueId = `com.studio.dev.${ createHash( 'sha1' )
.update( electronAppPath )
.digest( 'hex' )
.slice( 0, 12 ) }`;

execFileSync( plistBuddy, [ '-c', `Set :CFBundleIdentifier ${ uniqueId }`, infoPlist ] );
console.log( `Patched Electron.app bundle ID -> ${ uniqueId }` );
Loading