authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-08-24 21:19:22-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-24 21:19:22-07:00
log63eb0429f49fad5f8722407ea971e3199e6c4514
tree8019c4f30e059db59a1551c06148e3877ab17a94
parent143341b0bc4e3e81066ab2cd86ddd199574bfd83
signature Signed by PGP key B5690EEEBB952194

perf(desktop): cut macOS signing calls by 81% (#8093)


6 files changed, 140 insertions(+), 18 deletions(-)

pnpm-lock.yaml+17
...@@ -927,6 +927,9 @@ importers:...@@ -927,6 +927,9 @@ importers:
927 '@electron/asar':927 '@electron/asar':
928 specifier: ^3.4.1928 specifier: ^3.4.1
929 version: 3.4.1929 version: 3.4.1
930 '@electron/osx-sign':
931 specifier: 2.7.0
932 version: 2.7.0
930 '@t3tools/contracts':933 '@t3tools/contracts':
931 specifier: workspace:*934 specifier: workspace:*
932 version: link:../packages/contracts935 version: link:../packages/contracts
...@@ -2050,6 +2053,11 @@ packages:...@@ -2050,6 +2053,11 @@ packages:
2050 engines: {node: '>=12.0.0'}2053 engines: {node: '>=12.0.0'}
2051 hasBin: true2054 hasBin: true
20522055
2056 '@electron/osx-sign@2.7.0':
2057 resolution: {integrity: sha512-9DGhNqKMl6ibkhUoXbN7OHX2gZznfY10L3ZwG0u6r667Kfb6kec4JEfFTXftoqzmOfZ+OzwDbr4p/nKBMHnz0g==}
2058 engines: {node: '>=22.12.0'}
2059 hasBin: true
2060
2053 '@electron/rebuild@4.0.4':2061 '@electron/rebuild@4.0.4':
2054 resolution: {integrity: sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==}2062 resolution: {integrity: sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==}
2055 engines: {node: '>=22.12.0'}2063 engines: {node: '>=22.12.0'}
...@@ -11900,6 +11908,15 @@ snapshots:...@@ -11900,6 +11908,15 @@ snapshots:
11900 transitivePeerDependencies:11908 transitivePeerDependencies:
11901 - supports-color11909 - supports-color
1190211910
11911 '@electron/osx-sign@2.7.0':
11912 dependencies:
11913 debug: 4.4.3
11914 isbinaryfile: 4.0.10
11915 plist: 3.1.1
11916 semver: 7.8.5
11917 transitivePeerDependencies:
11918 - supports-color
11919
11903 '@electron/rebuild@4.0.4':11920 '@electron/rebuild@4.0.4':
11904 dependencies:11921 dependencies:
11905 '@malept/cross-spawn-promise': 2.0.011922 '@malept/cross-spawn-promise': 2.0.0
scripts/build-desktop-artifact.test.ts+43-1
...@@ -20,6 +20,7 @@ import {...@@ -20,6 +20,7 @@ import {
20 DESKTOP_ELECTRON_LANGUAGES,20 DESKTOP_ELECTRON_LANGUAGES,
21 DESKTOP_FILE_EXCLUSIONS,21 DESKTOP_FILE_EXCLUSIONS,
22 DESKTOP_EXTRA_RESOURCES,22 DESKTOP_EXTRA_RESOURCES,
23 MAC_FILE_EXCLUSIONS,
23 InvalidMacPasskeyRpDomainError,24 InvalidMacPasskeyRpDomainError,
24 InvalidMacPasskeyPublishableKeyError,25 InvalidMacPasskeyPublishableKeyError,
25 InvalidMockUpdateServerPortError,26 InvalidMockUpdateServerPortError,
...@@ -33,6 +34,7 @@ import {...@@ -33,6 +34,7 @@ import {
33 resolveClerkPasskeyNativeArtifacts,34 resolveClerkPasskeyNativeArtifacts,
34 resolveMacPasskeySigningConfiguration,35 resolveMacPasskeySigningConfiguration,
35 resolveDesktopRuntimeDependencies,36 resolveDesktopRuntimeDependencies,
37 resolveMacStageDependencies,
36 resolveFffNativeDependencies,38 resolveFffNativeDependencies,
37 resolveBuildOptions,39 resolveBuildOptions,
38 resolveDesktopBuildIconAssets,40 resolveDesktopBuildIconAssets,
...@@ -483,13 +485,52 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -483,13 +485,52 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
483 assert.deepStrictEqual((linux.linux as Record<string, unknown>).protocols, [485 assert.deepStrictEqual((linux.linux as Record<string, unknown>).protocols, [
484 { name: "T3 Code", schemes: ["t3code", "t3code-dev"] },486 { name: "T3 Code", schemes: ["t3code", "t3code-dev"] },
485 ]);487 ]);
486 for (const config of [mac, linux, win]) {488 assert.deepStrictEqual(mac.files, [...DESKTOP_FILE_EXCLUSIONS, ...MAC_FILE_EXCLUSIONS]);
489 assert.notProperty(mac.mac as Record<string, unknown>, "sign");
490 for (const config of [linux, win]) {
487 assert.deepStrictEqual(config.electronLanguages, DESKTOP_ELECTRON_LANGUAGES);491 assert.deepStrictEqual(config.electronLanguages, DESKTOP_ELECTRON_LANGUAGES);
488 assert.deepStrictEqual(config.files, DESKTOP_FILE_EXCLUSIONS);492 assert.deepStrictEqual(config.files, DESKTOP_FILE_EXCLUSIONS);
489 }493 }
494 assert.deepStrictEqual(mac.electronLanguages, DESKTOP_ELECTRON_LANGUAGES);
490 }).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })))),495 }).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })))),
491 );496 );
492497
498 it("excludes Windows terminal binaries only from macOS packages", () => {
499 assert.deepStrictEqual(MAC_FILE_EXCLUSIONS, [
500 "!**/node_modules/node-pty/prebuilds/win32-*/**/*",
501 "!**/node_modules/node-pty/third_party/conpty/**/*",
502 ]);
503 });
504
505 it("stages only server runtime externals in macOS packages", () => {
506 assert.deepStrictEqual(
507 resolveMacStageDependencies({
508 serverDependencies: {
509 "@anthropic-ai/claude-agent-sdk": "^0.3.170",
510 "@ff-labs/fff-node": "0.9.4",
511 "@opencode-ai/sdk": "^1.3.15",
512 "@pierre/diffs": "1.3.0",
513 "msgpackr-extract": "3.0.4",
514 "node-pty": "1.1.0",
515 },
516 desktopDependencies: {
517 "@clerk/electron": "0.0.34",
518 effect: "4.0.0-beta.103",
519 },
520 arch: "arm64",
521 fffNodeVersion: "0.9.4",
522 }),
523 {
524 "@ff-labs/fff-node": "0.9.4",
525 "msgpackr-extract": "3.0.4",
526 "node-pty": "1.1.0",
527 "@clerk/electron": "0.0.34",
528 effect: "4.0.0-beta.103",
529 "@ff-labs/fff-bin-darwin-arm64": "0.9.4",
530 },
531 );
532 });
533
493 it("excludes node-pty binaries for the other Windows architecture", () => {534 it("excludes node-pty binaries for the other Windows architecture", () => {
494 assert.deepStrictEqual(resolveWindowsServerAsarIgnoreGlobs("x64"), [535 assert.deepStrictEqual(resolveWindowsServerAsarIgnoreGlobs("x64"), [
495 ...WINDOWS_SERVER_ASAR_IGNORE_GLOBS,536 ...WINDOWS_SERVER_ASAR_IGNORE_GLOBS,
...@@ -1112,6 +1153,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -1112,6 +1153,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
1112 assert.equal(config.appId, "com.t3tools.t3code");1153 assert.equal(config.appId, "com.t3tools.t3code");
1113 assert.equal(mac.entitlements, "/tmp/entitlements.mac.plist");1154 assert.equal(mac.entitlements, "/tmp/entitlements.mac.plist");
1114 assert.equal(mac.provisioningProfile, "/tmp/t3code.provisionprofile");1155 assert.equal(mac.provisioningProfile, "/tmp/t3code.provisionprofile");
1156 assert.match(String(mac.sign), /\/scripts\/sign-macos\.ts$/);
1115 assert.deepStrictEqual(mac.protocols, [1157 assert.deepStrictEqual(mac.protocols, [
1116 { name: "T3 Code", schemes: ["t3code", "t3code-dev"] },1158 { name: "T3 Code", schemes: ["t3code", "t3code-dev"] },
1117 ]);1159 ]);
scripts/build-desktop-artifact.ts+47-17
...@@ -796,6 +796,11 @@ export const DESKTOP_FILE_EXCLUSIONS = [...@@ -796,6 +796,11 @@ export const DESKTOP_FILE_EXCLUSIONS = [
796 "!apps/desktop/prod-resources/windows-server",796 "!apps/desktop/prod-resources/windows-server",
797 "!apps/desktop/prod-resources/windows-server/**/*",797 "!apps/desktop/prod-resources/windows-server/**/*",
798] as const;798] as const;
799// Windows terminal helpers cannot run on macOS and slow signing and notarization.
800export const MAC_FILE_EXCLUSIONS = [
801 "!**/node_modules/node-pty/prebuilds/win32-*/**/*",
802 "!**/node_modules/node-pty/third_party/conpty/**/*",
803] as const;
799// Windows ships the server tree (bundle + node_modules) as a separate804// Windows ships the server tree (bundle + node_modules) as a separate
800// resources/server.asar sidecar instead of loose files: the NSIS installer805// resources/server.asar sidecar instead of loose files: the NSIS installer
801// then extracts a handful of large archives instead of thousands of small806// then extracts a handful of large archives instead of thousands of small
...@@ -1097,6 +1102,19 @@ export function resolveFffNativeDependencies(...@@ -1097,6 +1102,19 @@ export function resolveFffNativeDependencies(
1097 );1102 );
1098}1103}
10991104
1105export function resolveMacStageDependencies(input: {
1106 readonly serverDependencies: Record<string, string>;
1107 readonly desktopDependencies: Record<string, string>;
1108 readonly arch: typeof BuildArch.Type;
1109 readonly fffNodeVersion: string;
1110}) {
1111 return {
1112 ...selectCliRuntimeExternalDependencies(input.serverDependencies),
1113 ...input.desktopDependencies,
1114 ...resolveFffNativeDependencies("mac", input.arch, input.fffNodeVersion),
1115 };
1116}
1117
1100export interface ClerkPasskeyNativeArtifact {1118export interface ClerkPasskeyNativeArtifact {
1101 readonly packageName: string;1119 readonly packageName: string;
1102 readonly binaryFileName: string;1120 readonly binaryFileName: string;
...@@ -2061,7 +2079,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* (...@@ -2061,7 +2079,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* (
2061 productName: resolveDesktopProductName(version),2079 productName: resolveDesktopProductName(version),
2062 artifactName: "T3-Code-${version}-${arch}.${ext}",2080 artifactName: "T3-Code-${version}-${arch}.${ext}",
2063 electronLanguages: [...DESKTOP_ELECTRON_LANGUAGES],2081 electronLanguages: [...DESKTOP_ELECTRON_LANGUAGES],
2064 files: [...DESKTOP_FILE_EXCLUSIONS],2082 files: [...DESKTOP_FILE_EXCLUSIONS, ...(platform === "mac" ? MAC_FILE_EXCLUSIONS : [])],
2065 directories: {2083 directories: {
2066 buildResources: "apps/desktop/resources",2084 buildResources: "apps/desktop/resources",
2067 },2085 },
...@@ -2088,6 +2106,8 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* (...@@ -2088,6 +2106,8 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* (
2088 }2106 }
20892107
2090 if (platform === "mac") {2108 if (platform === "mac") {
2109 const path = yield* Path.Path;
2110 const repoRoot = yield* RepoRoot;
2091 buildConfig.mac = {2111 buildConfig.mac = {
2092 target: target === "dmg" ? [target, "zip"] : [target],2112 target: target === "dmg" ? [target, "zip"] : [target],
2093 icon: "icon.icns",2113 icon: "icon.icns",
...@@ -2098,6 +2118,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* (...@@ -2098,6 +2118,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* (
2098 schemes: ["t3code", "t3code-dev"],2118 schemes: ["t3code", "t3code-dev"],
2099 },2119 },
2100 ],2120 ],
2121 ...(signed ? { sign: path.join(repoRoot, "scripts/sign-macos.ts") } : {}),
2101 ...(macPasskeySigning2122 ...(macPasskeySigning
2102 ? {2123 ? {
2103 entitlements: macPasskeySigning.entitlementsPath,2124 entitlements: macPasskeySigning.entitlementsPath,
...@@ -2899,21 +2920,28 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (...@@ -2899,21 +2920,28 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
28992920
2900 // Windows splits dependencies per process: app.asar carries only the2921 // Windows splits dependencies per process: app.asar carries only the
2901 // desktop main-process runtime deps, while the server bundle's deps live in2922 // desktop main-process runtime deps, while the server bundle's deps live in
2902 // the server.asar sidecar (see stageWindowsServerSidecar). macOS and Linux2923 // the server.asar sidecar (see stageWindowsServerSidecar). macOS adds only
2903 // keep the single merged tree — their primary resolves everything from2924 // server packages that remain external to its merged app.asar. Linux retains
2904 // app.asar and there is no second consumer.2925 // its existing full dependency tree.
2905 const stageDependencies =2926 const stageDependencies =
2906 options.platform === "win"2927 options.platform === "win"
2907 ? { ...resolvedDesktopRuntimeDependencies }2928 ? { ...resolvedDesktopRuntimeDependencies }
2908 : {2929 : options.platform === "mac"
2909 ...resolvedServerDependencies,2930 ? resolveMacStageDependencies({
2910 ...resolvedDesktopRuntimeDependencies,2931 serverDependencies: resolvedServerDependencies,
2911 ...resolveFffNativeDependencies(2932 desktopDependencies: resolvedDesktopRuntimeDependencies,
2912 options.platform,2933 arch: options.arch,
2913 options.arch,2934 fffNodeVersion: serverPackageJson.dependencies["@ff-labs/fff-node"],
2914 serverPackageJson.dependencies["@ff-labs/fff-node"],2935 })
2915 ),2936 : {
2916 };2937 ...resolvedServerDependencies,
2938 ...resolvedDesktopRuntimeDependencies,
2939 ...resolveFffNativeDependencies(
2940 options.platform,
2941 options.arch,
2942 serverPackageJson.dependencies["@ff-labs/fff-node"],
2943 ),
2944 };
2917 const stagePatchedDependencies = createStagePatchedDependencies(2945 const stagePatchedDependencies = createStagePatchedDependencies(
2918 workspacePatchedDependencies,2946 workspacePatchedDependencies,
2919 stageDependencies,2947 stageDependencies,
...@@ -3034,10 +3062,12 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (...@@ -3034,10 +3062,12 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
3034 buildEnv.GYP_MSVS_VERSION = buildEnv.GYP_MSVS_VERSION ?? "2022";3062 buildEnv.GYP_MSVS_VERSION = buildEnv.GYP_MSVS_VERSION ?? "2022";
3035 }3063 }
3036 if (options.verbose) {3064 if (options.verbose) {
3037 buildEnv.DEBUG =3065 const debugNamespaces = [
3038 buildEnv.DEBUG === undefined3066 "electron-builder",
3039 ? "electron-builder,electron-builder:*"3067 "electron-builder:*",
3040 : `${buildEnv.DEBUG},electron-builder,electron-builder:*`;3068 ...(options.platform === "mac" ? ["electron-osx-sign*", "electron-notarize*"] : []),
3069 ];
3070 buildEnv.DEBUG = [buildEnv.DEBUG, ...debugNamespaces].filter(Boolean).join(",");
3041 }3071 }
30423072
3043 yield* Effect.log(3073 yield* Effect.log(
scripts/package.json+1
...@@ -9,6 +9,7 @@...@@ -9,6 +9,7 @@
9 "dependencies": {9 "dependencies": {
10 "@effect/platform-node": "catalog:",10 "@effect/platform-node": "catalog:",
11 "@electron/asar": "^3.4.1",11 "@electron/asar": "^3.4.1",
12 "@electron/osx-sign": "2.7.0",
12 "@t3tools/contracts": "workspace:*",13 "@t3tools/contracts": "workspace:*",
13 "@t3tools/shared": "workspace:*",14 "@t3tools/shared": "workspace:*",
14 "@t3tools/tailscale": "workspace:*",15 "@t3tools/tailscale": "workspace:*",
scripts/sign-macos.test.ts created+26
...@@ -0,0 +1,26 @@
1import { sign as signApplication, type SignOptions } from "@electron/osx-sign";
2import { expect, it, vi } from "vite-plus/test";
3
4import sign from "./sign-macos.ts";
5
6vi.mock("@electron/osx-sign", () => ({ sign: vi.fn() }));
7
8it("batches codesign calls without changing existing signing options", async () => {
9 const options = {
10 app: "/tmp/T3 Code.app",
11 identity: "Developer ID Application: T3 Tools, Inc.",
12 keychain: "/tmp/t3code.keychain",
13 provisioningProfile: "/tmp/t3code.provisionprofile",
14 optionsForFile: () => ({
15 entitlements: "/tmp/t3code.entitlements.plist",
16 hardenedRuntime: true,
17 }),
18 } satisfies SignOptions;
19
20 await sign(options);
21
22 expect(signApplication).toHaveBeenCalledExactlyOnceWith({
23 ...options,
24 batchCodesignCalls: true,
25 });
26});
scripts/sign-macos.ts created+6
...@@ -0,0 +1,6 @@
1import { sign as signApplication, type SignOptions } from "@electron/osx-sign";
2
3/** Sign files with matching options together instead of spawning codesign for each file. */
4export default async function sign(options: SignOptions): Promise<void> {
5 await signApplication({ ...options, batchCodesignCalls: true });
6}