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