Skip to content

Commit fe8dc05

Browse files
Preserve T3 Code identity in macOS development launcher
- Keep Electron’s native binary inside the branded app bundle - Add path resolution and launcher script coverage
1 parent 7b8d126 commit fe8dc05

2 files changed

Lines changed: 68 additions & 10 deletions

File tree

‎apps/desktop/scripts/electron-launcher.mjs‎

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const APP_BUNDLE_ID = isDevelopment
2020
? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}`
2121
: "com.t3tools.t3code";
2222
const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"];
23-
const LAUNCHER_VERSION = 12;
23+
const LAUNCHER_VERSION = 14;
2424
const defaultIconPath = NodePath.join(desktopDir, "resources", "icon.icns");
2525
const developmentMacIconPngPath = NodePath.join(
2626
repoRoot,
@@ -220,11 +220,12 @@ function ensureDevelopmentIconIcns(runtimeDir) {
220220
}
221221
}
222222

223-
function patchMainBundleInfoPlist(appBundlePath, iconPath) {
223+
function patchMainBundleInfoPlist(appBundlePath, iconPath, executableName) {
224224
const infoPlistPath = NodePath.join(appBundlePath, "Contents", "Info.plist");
225225
setPlistString(infoPlistPath, "CFBundleDisplayName", APP_DISPLAY_NAME);
226226
setPlistString(infoPlistPath, "CFBundleName", APP_DISPLAY_NAME);
227227
setPlistString(infoPlistPath, "CFBundleIdentifier", APP_BUNDLE_ID);
228+
setPlistString(infoPlistPath, "CFBundleExecutable", executableName);
228229
setPlistString(infoPlistPath, "CFBundleIconFile", "icon.icns");
229230
setPlistJson(infoPlistPath, "CFBundleURLTypes", [
230231
{
@@ -277,11 +278,25 @@ function readJson(path) {
277278
}
278279
}
279280

281+
export function resolveMacLauncherPaths(appBundlePath, displayName = APP_DISPLAY_NAME) {
282+
const executableDir = NodePath.join(appBundlePath, "Contents", "MacOS");
283+
const launcherExecutableName = `${displayName} Launcher`;
284+
return {
285+
launcherExecutableName,
286+
launcherBinaryPath: NodePath.join(executableDir, launcherExecutableName),
287+
runtimeElectronBinaryPath: NodePath.join(executableDir, "Electron"),
288+
};
289+
}
290+
280291
function buildMacLauncher(electronBinaryPath) {
281292
const sourceAppBundlePath = NodePath.resolve(NodePath.dirname(electronBinaryPath), "../..");
282293
const runtimeDir = NodePath.join(desktopDir, ".electron-runtime");
283294
const targetAppBundlePath = NodePath.join(runtimeDir, `${APP_DISPLAY_NAME}.app`);
284-
const targetBinaryPath = NodePath.join(targetAppBundlePath, "Contents", "MacOS", "Electron");
295+
const developmentPaths = resolveMacLauncherPaths(targetAppBundlePath);
296+
const runtimeElectronBinaryPath = developmentPaths.runtimeElectronBinaryPath;
297+
const launcherBinaryPath = isDevelopment
298+
? developmentPaths.launcherBinaryPath
299+
: runtimeElectronBinaryPath;
285300
const iconPath = isDevelopment ? ensureDevelopmentIconIcns(runtimeDir) : defaultIconPath;
286301
const metadataPath = NodePath.join(runtimeDir, "metadata.json");
287302

@@ -298,18 +313,19 @@ function buildMacLauncher(electronBinaryPath) {
298313

299314
const currentMetadata = readJson(metadataPath);
300315
if (
301-
NodeFS.existsSync(targetBinaryPath) &&
316+
NodeFS.existsSync(launcherBinaryPath) &&
317+
(!isDevelopment || NodeFS.existsSync(runtimeElectronBinaryPath)) &&
302318
currentMetadata &&
303319
JSON.stringify(currentMetadata) === JSON.stringify(expectedMetadata)
304320
) {
305321
if (isDevelopment) {
306322
// The launcher also handles protocol activations outside the dev runner,
307323
// so refresh its fallback environment on every launch. Never let a value
308324
// captured by an older parent app override the live dev-runner environment.
309-
writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath);
325+
writeDevelopmentLauncherScript(launcherBinaryPath, runtimeElectronBinaryPath);
310326
}
311327
registerMacLauncherBundle(targetAppBundlePath);
312-
return targetBinaryPath;
328+
return launcherBinaryPath;
313329
}
314330

315331
NodeFS.rmSync(targetAppBundlePath, { recursive: true, force: true });
@@ -321,15 +337,24 @@ function buildMacLauncher(electronBinaryPath) {
321337
recursive: true,
322338
verbatimSymlinks: true,
323339
});
324-
patchMainBundleInfoPlist(targetAppBundlePath, iconPath);
340+
patchMainBundleInfoPlist(
341+
targetAppBundlePath,
342+
iconPath,
343+
isDevelopment ? developmentPaths.launcherExecutableName : "Electron",
344+
);
325345
patchHelperBundleInfoPlists(targetAppBundlePath);
326346
if (isDevelopment) {
327-
writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath);
347+
// Keep Electron's native executable inside the branded bundle. Launching the
348+
// node_modules copy makes macOS associate the process (and Dock label) with
349+
// Electron.app even though this bundle's Info.plist has the T3 Code name.
350+
// Its conventional executable name also keeps Electron's default-app runtime
351+
// in development mode instead of making app.isPackaged report true.
352+
writeDevelopmentLauncherScript(launcherBinaryPath, runtimeElectronBinaryPath);
328353
}
329354
NodeFS.writeFileSync(metadataPath, `${JSON.stringify(expectedMetadata, null, 2)}\n`);
330355
registerMacLauncherBundle(targetAppBundlePath);
331356

332-
return targetBinaryPath;
357+
return launcherBinaryPath;
333358
}
334359

335360
function isLinuxSetuidSandboxConfigured(electronBinaryPath) {

‎apps/desktop/scripts/electron-launcher.test.mjs‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { assert, describe, it } from "vite-plus/test";
22

3-
import { makeDevelopmentLauncherScript, resolveElectronBinaryPath } from "./electron-launcher.mjs";
3+
import {
4+
makeDevelopmentLauncherScript,
5+
resolveElectronBinaryPath,
6+
resolveMacLauncherPaths,
7+
} from "./electron-launcher.mjs";
48

59
describe("electron development launcher", () => {
610
it("uses captured values only as fallbacks for a live runner environment", () => {
@@ -45,4 +49,33 @@ describe("electron development launcher", () => {
4549
);
4650
assert.deepEqual(calls, ["ensure", "require:electron"]);
4751
});
52+
53+
it("keeps the native Electron executable name inside the branded macOS bundle", () => {
54+
const paths = resolveMacLauncherPaths(
55+
"/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app",
56+
"T3 Code (Dev)",
57+
);
58+
59+
assert.equal(paths.launcherExecutableName, "T3 Code (Dev) Launcher");
60+
assert.equal(
61+
paths.launcherBinaryPath,
62+
"/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app/Contents/MacOS/T3 Code (Dev) Launcher",
63+
);
64+
assert.equal(
65+
paths.runtimeElectronBinaryPath,
66+
"/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app/Contents/MacOS/Electron",
67+
);
68+
69+
const script = makeDevelopmentLauncherScript({
70+
electronBinaryPath: paths.runtimeElectronBinaryPath,
71+
mainEntryPath: "/repo/apps/desktop/dist-electron/main.cjs",
72+
desktopRoot: "/repo/apps/desktop",
73+
environment: {},
74+
});
75+
assert.include(
76+
script,
77+
"exec '/repo/apps/desktop/.electron-runtime/T3 Code (Dev).app/Contents/MacOS/Electron'",
78+
);
79+
assert.notInclude(script, "node_modules/electron");
80+
});
4881
});

0 commit comments

Comments
 (0)