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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion apps/desktop/scripts/electron-launcher.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
rmSync,
statSync,
writeFileSync,
Expand All @@ -19,7 +20,7 @@ import { fileURLToPath } from "node:url";
const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL);
const APP_DISPLAY_NAME = isDevelopment ? "T3 Code (Dev)" : "T3 Code (Alpha)";
const APP_BUNDLE_ID = isDevelopment ? "com.t3tools.t3code.dev" : "com.t3tools.t3code";
const LAUNCHER_VERSION = 2;
const LAUNCHER_VERSION = 3;

const __dirname = dirname(fileURLToPath(import.meta.url));
export const desktopDir = resolve(__dirname, "..");
Expand Down Expand Up @@ -120,6 +121,41 @@ function patchMainBundleInfoPlist(appBundlePath, iconPath) {
copyFileSync(iconPath, join(resourcesDir, "electron.icns"));
}

function patchHelperBundleInfoPlists(appBundlePath) {
const frameworksDir = join(appBundlePath, "Contents", "Frameworks");
if (!existsSync(frameworksDir)) {
return;
}

for (const entry of readdirSync(frameworksDir, { withFileTypes: true })) {
if (
!entry.isDirectory() ||
!entry.name.startsWith("Electron Helper") ||
!entry.name.endsWith(".app")
) {
continue;
}

const helperPlistPath = join(frameworksDir, entry.name, "Contents", "Info.plist");
if (!existsSync(helperPlistPath)) {
continue;
}

const suffix = entry.name.replace("Electron Helper", "").replace(".app", "").trim();
const helperName = suffix
? `${APP_DISPLAY_NAME} Helper ${suffix}`
: `${APP_DISPLAY_NAME} Helper`;
const helperIdSuffix = suffix.replace(/[()]/g, "").trim().toLowerCase().replace(/\s+/g, "-");
const helperBundleId = helperIdSuffix
? `${APP_BUNDLE_ID}.helper.${helperIdSuffix}`
: `${APP_BUNDLE_ID}.helper`;

setPlistString(helperPlistPath, "CFBundleDisplayName", helperName);
setPlistString(helperPlistPath, "CFBundleName", helperName);
setPlistString(helperPlistPath, "CFBundleIdentifier", helperBundleId);
}
}

function readJson(path) {
try {
return JSON.parse(readFileSync(path, "utf8"));
Expand Down Expand Up @@ -157,6 +193,7 @@ function buildMacLauncher(electronBinaryPath) {
rmSync(targetAppBundlePath, { recursive: true, force: true });
cpSync(sourceAppBundlePath, targetAppBundlePath, { recursive: true });
patchMainBundleInfoPlist(targetAppBundlePath, iconPath);
patchHelperBundleInfoPlists(targetAppBundlePath);
writeFileSync(metadataPath, `${JSON.stringify(expectedMetadata, null, 2)}\n`);

return targetBinaryPath;
Expand Down
15 changes: 15 additions & 0 deletions scripts/build-desktop-artifact.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { assert, it } from "@effect/vitest";
import { ConfigProvider, Effect, Option } from "effect";

import {
createBuildConfig,
MAC_HELPER_BUNDLE_IDS,
resolveBuildOptions,
resolveDesktopBuildIconAssets,
resolveDesktopProductName,
Expand Down Expand Up @@ -37,6 +39,19 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
});
});

it.effect("pins macOS Electron helper bundle IDs to the T3 Code app identity", () =>
Effect.gen(function* () {
const buildConfig = yield* createBuildConfig("mac", "dmg", "0.0.21", false, false, undefined);

assert.deepStrictEqual(buildConfig.mac, {
target: ["dmg", "zip"],
icon: "icon.icns",
category: "public.app-category.developer-tools",
...MAC_HELPER_BUNDLE_IDS,
});
}),
);

it("falls back to the default mock update port when the configured port is blank", () => {
assert.equal(resolveMockUpdateServerUrl(undefined), "http://localhost:3000");
assert.equal(resolveMockUpdateServerUrl(4123), "http://localhost:4123");
Expand Down
16 changes: 14 additions & 2 deletions scripts/build-desktop-artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ const PLATFORM_CONFIG: Record<typeof BuildPlatform.Type, PlatformConfig> = {
},
};

const MAC_APP_BUNDLE_ID = "com.t3tools.t3code";

export const MAC_HELPER_BUNDLE_IDS = {
helperBundleId: `${MAC_APP_BUNDLE_ID}.helper`,
helperEHBundleId: `${MAC_APP_BUNDLE_ID}.helper.eh`,
helperGPUBundleId: `${MAC_APP_BUNDLE_ID}.helper.gpu`,
helperNPBundleId: `${MAC_APP_BUNDLE_ID}.helper.np`,
helperPluginBundleId: `${MAC_APP_BUNDLE_ID}.helper.plugin`,
helperRendererBundleId: `${MAC_APP_BUNDLE_ID}.helper.renderer`,
} as const;

interface BuildCliInput {
readonly platform: Option.Option<typeof BuildPlatform.Type>;
readonly target: Option.Option<string>;
Expand Down Expand Up @@ -558,7 +569,7 @@ export function resolveDesktopProductName(version: string): string {
: (desktopPackageJson.productName ?? "T3 Code");
}

const createBuildConfig = Effect.fn("createBuildConfig")(function* (
export const createBuildConfig = Effect.fn("createBuildConfig")(function* (
platform: typeof BuildPlatform.Type,
target: string,
version: string,
Expand All @@ -567,7 +578,7 @@ const createBuildConfig = Effect.fn("createBuildConfig")(function* (
mockUpdateServerPort: number | undefined,
) {
const buildConfig: Record<string, unknown> = {
appId: "com.t3tools.t3code",
appId: MAC_APP_BUNDLE_ID,
productName: resolveDesktopProductName(version),
artifactName: "T3-Code-${version}-${arch}.${ext}",
directories: {
Expand All @@ -592,6 +603,7 @@ const createBuildConfig = Effect.fn("createBuildConfig")(function* (
target: target === "dmg" ? [target, "zip"] : [target],
icon: "icon.icns",
category: "public.app-category.developer-tools",
...MAC_HELPER_BUNDLE_IDS,
};
}

Expand Down
Loading