| ... | @@ -20,7 +20,7 @@ export const APP_BUNDLE_ID = isDevelopment | ... | @@ -20,7 +20,7 @@ export const APP_BUNDLE_ID = isDevelopment |
| 20 | ? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}` | 20 | ? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}` |
| 21 | : "com.t3tools.t3code"; | 21 | : "com.t3tools.t3code"; |
| 22 | const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"]; | 22 | const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"]; |
| 23 | const LAUNCHER_VERSION = 12; | 23 | const LAUNCHER_VERSION = 14; |
| 24 | const defaultIconPath = NodePath.join(desktopDir, "resources", "icon.icns"); | 24 | const defaultIconPath = NodePath.join(desktopDir, "resources", "icon.icns"); |
| 25 | const developmentMacIconPngPath = NodePath.join( | 25 | const developmentMacIconPngPath = NodePath.join( |
| 26 | repoRoot, | 26 | repoRoot, |
| ... | @@ -220,11 +220,12 @@ function ensureDevelopmentIconIcns(runtimeDir) { | ... | @@ -220,11 +220,12 @@ function ensureDevelopmentIconIcns(runtimeDir) { |
| 220 | } | 220 | } |
| 221 | } | 221 | } |
| 222 | | 222 | |
| 223 | function patchMainBundleInfoPlist(appBundlePath, iconPath) { | 223 | function patchMainBundleInfoPlist(appBundlePath, iconPath, executableName) { |
| 224 | const infoPlistPath = NodePath.join(appBundlePath, "Contents", "Info.plist"); | 224 | const infoPlistPath = NodePath.join(appBundlePath, "Contents", "Info.plist"); |
| 225 | setPlistString(infoPlistPath, "CFBundleDisplayName", APP_DISPLAY_NAME); | 225 | setPlistString(infoPlistPath, "CFBundleDisplayName", APP_DISPLAY_NAME); |
| 226 | setPlistString(infoPlistPath, "CFBundleName", APP_DISPLAY_NAME); | 226 | setPlistString(infoPlistPath, "CFBundleName", APP_DISPLAY_NAME); |
| 227 | setPlistString(infoPlistPath, "CFBundleIdentifier", APP_BUNDLE_ID); | 227 | setPlistString(infoPlistPath, "CFBundleIdentifier", APP_BUNDLE_ID); |
| | 228 | setPlistString(infoPlistPath, "CFBundleExecutable", executableName); |
| 228 | setPlistString(infoPlistPath, "CFBundleIconFile", "icon.icns"); | 229 | setPlistString(infoPlistPath, "CFBundleIconFile", "icon.icns"); |
| 229 | setPlistJson(infoPlistPath, "CFBundleURLTypes", [ | 230 | setPlistJson(infoPlistPath, "CFBundleURLTypes", [ |
| 230 | { | 231 | { |
| ... | @@ -277,11 +278,25 @@ function readJson(path) { | ... | @@ -277,11 +278,25 @@ function readJson(path) { |
| 277 | } | 278 | } |
| 278 | } | 279 | } |
| 279 | | 280 | |
| | 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 | |
| 280 | function buildMacLauncher(electronBinaryPath) { | 291 | function buildMacLauncher(electronBinaryPath) { |
| 281 | const sourceAppBundlePath = NodePath.resolve(NodePath.dirname(electronBinaryPath), "../.."); | 292 | const sourceAppBundlePath = NodePath.resolve(NodePath.dirname(electronBinaryPath), "../.."); |
| 282 | const runtimeDir = NodePath.join(desktopDir, ".electron-runtime"); | 293 | const runtimeDir = NodePath.join(desktopDir, ".electron-runtime"); |
| 283 | const targetAppBundlePath = NodePath.join(runtimeDir, `${APP_DISPLAY_NAME}.app`); | 294 | 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; |
| 285 | const iconPath = isDevelopment ? ensureDevelopmentIconIcns(runtimeDir) : defaultIconPath; | 300 | const iconPath = isDevelopment ? ensureDevelopmentIconIcns(runtimeDir) : defaultIconPath; |
| 286 | const metadataPath = NodePath.join(runtimeDir, "metadata.json"); | 301 | const metadataPath = NodePath.join(runtimeDir, "metadata.json"); |
| 287 | | 302 | |
| ... | @@ -298,7 +313,8 @@ function buildMacLauncher(electronBinaryPath) { | ... | @@ -298,7 +313,8 @@ function buildMacLauncher(electronBinaryPath) { |
| 298 | | 313 | |
| 299 | const currentMetadata = readJson(metadataPath); | 314 | const currentMetadata = readJson(metadataPath); |
| 300 | if ( | 315 | if ( |
| 301 | NodeFS.existsSync(targetBinaryPath) && | 316 | NodeFS.existsSync(launcherBinaryPath) && |
| | 317 | (!isDevelopment || NodeFS.existsSync(runtimeElectronBinaryPath)) && |
| 302 | currentMetadata && | 318 | currentMetadata && |
| 303 | JSON.stringify(currentMetadata) === JSON.stringify(expectedMetadata) | 319 | JSON.stringify(currentMetadata) === JSON.stringify(expectedMetadata) |
| 304 | ) { | 320 | ) { |
| ... | @@ -306,10 +322,10 @@ function buildMacLauncher(electronBinaryPath) { | ... | @@ -306,10 +322,10 @@ function buildMacLauncher(electronBinaryPath) { |
| 306 | // The launcher also handles protocol activations outside the dev runner, | 322 | // The launcher also handles protocol activations outside the dev runner, |
| 307 | // so refresh its fallback environment on every launch. Never let a value | 323 | // so refresh its fallback environment on every launch. Never let a value |
| 308 | // captured by an older parent app override the live dev-runner environment. | 324 | // captured by an older parent app override the live dev-runner environment. |
| 309 | writeDevelopmentLauncherScript(targetBinaryPath, electronBinaryPath); | 325 | writeDevelopmentLauncherScript(launcherBinaryPath, runtimeElectronBinaryPath); |
| 310 | } | 326 | } |
| 311 | registerMacLauncherBundle(targetAppBundlePath); | 327 | registerMacLauncherBundle(targetAppBundlePath); |
| 312 | return targetBinaryPath; | 328 | return launcherBinaryPath; |
| 313 | } | 329 | } |
| 314 | | 330 | |
| 315 | NodeFS.rmSync(targetAppBundlePath, { recursive: true, force: true }); | 331 | NodeFS.rmSync(targetAppBundlePath, { recursive: true, force: true }); |
| ... | @@ -321,15 +337,24 @@ function buildMacLauncher(electronBinaryPath) { | ... | @@ -321,15 +337,24 @@ function buildMacLauncher(electronBinaryPath) { |
| 321 | recursive: true, | 337 | recursive: true, |
| 322 | verbatimSymlinks: true, | 338 | verbatimSymlinks: true, |
| 323 | }); | 339 | }); |
| 324 | patchMainBundleInfoPlist(targetAppBundlePath, iconPath); | 340 | patchMainBundleInfoPlist( |
| | 341 | targetAppBundlePath, |
| | 342 | iconPath, |
| | 343 | isDevelopment ? developmentPaths.launcherExecutableName : "Electron", |
| | 344 | ); |
| 325 | patchHelperBundleInfoPlists(targetAppBundlePath); | 345 | patchHelperBundleInfoPlists(targetAppBundlePath); |
| 326 | if (isDevelopment) { | 346 | 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); |
| 328 | } | 353 | } |
| 329 | NodeFS.writeFileSync(metadataPath, `${JSON.stringify(expectedMetadata, null, 2)}\n`); | 354 | NodeFS.writeFileSync(metadataPath, `${JSON.stringify(expectedMetadata, null, 2)}\n`); |
| 330 | registerMacLauncherBundle(targetAppBundlePath); | 355 | registerMacLauncherBundle(targetAppBundlePath); |
| 331 | | 356 | |
| 332 | return targetBinaryPath; | 357 | return launcherBinaryPath; |
| 333 | } | 358 | } |
| 334 | | 359 | |
| 335 | function isLinuxSetuidSandboxConfigured(electronBinaryPath) { | 360 | function isLinuxSetuidSandboxConfigured(electronBinaryPath) { |