| author | |
| committer | |
| log | 63eb0429f49fad5f8722407ea971e3199e6c4514 |
| tree | 8019c4f30e059db59a1551c06148e3877ab17a94 |
| parent | 143341b0bc4e3e81066ab2cd86ddd199574bfd83 |
| signature | Signed by PGP key B5690EEEBB952194 |
6 files changed, 140 insertions(+), 18 deletions(-)
pnpm-lock.yaml+17| ... | ... | @@ -927,6 +927,9 @@ importers: |
| 927 | 927 | '@electron/asar': |
| 928 | 928 | specifier: ^3.4.1 |
| 929 | 929 | version: 3.4.1 |
| 930 | '@electron/osx-sign': | |
| 931 | specifier: 2.7.0 | |
| 932 | version: 2.7.0 | |
| 930 | 933 | '@t3tools/contracts': |
| 931 | 934 | specifier: workspace:* |
| 932 | 935 | version: link:../packages/contracts |
| ... | ... | @@ -2050,6 +2053,11 @@ packages: |
| 2050 | 2053 | engines: {node: '>=12.0.0'} |
| 2051 | 2054 | hasBin: true |
| 2052 | 2055 | |
| 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 | 2061 | '@electron/rebuild@4.0.4': |
| 2054 | 2062 | resolution: {integrity: sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==} |
| 2055 | 2063 | engines: {node: '>=22.12.0'} |
| ... | ... | @@ -11900,6 +11908,15 @@ snapshots: |
| 11900 | 11908 | transitivePeerDependencies: |
| 11901 | 11909 | - supports-color |
| 11902 | 11910 | |
| 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 | 11920 | '@electron/rebuild@4.0.4': |
| 11904 | 11921 | dependencies: |
| 11905 | 11922 | '@malept/cross-spawn-promise': 2.0.0 |
scripts/build-desktop-artifact.test.ts+43-1| ... | ... | @@ -20,6 +20,7 @@ import { |
| 20 | 20 | DESKTOP_ELECTRON_LANGUAGES, |
| 21 | 21 | DESKTOP_FILE_EXCLUSIONS, |
| 22 | 22 | DESKTOP_EXTRA_RESOURCES, |
| 23 | MAC_FILE_EXCLUSIONS, | |
| 23 | 24 | InvalidMacPasskeyRpDomainError, |
| 24 | 25 | InvalidMacPasskeyPublishableKeyError, |
| 25 | 26 | InvalidMockUpdateServerPortError, |
| ... | ... | @@ -33,6 +34,7 @@ import { |
| 33 | 34 | resolveClerkPasskeyNativeArtifacts, |
| 34 | 35 | resolveMacPasskeySigningConfiguration, |
| 35 | 36 | resolveDesktopRuntimeDependencies, |
| 37 | resolveMacStageDependencies, | |
| 36 | 38 | resolveFffNativeDependencies, |
| 37 | 39 | resolveBuildOptions, |
| 38 | 40 | resolveDesktopBuildIconAssets, |
| ... | ... | @@ -483,13 +485,52 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { |
| 483 | 485 | assert.deepStrictEqual((linux.linux as Record<string, unknown>).protocols, [ |
| 484 | 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 | 491 | assert.deepStrictEqual(config.electronLanguages, DESKTOP_ELECTRON_LANGUAGES); |
| 488 | 492 | assert.deepStrictEqual(config.files, DESKTOP_FILE_EXCLUSIONS); |
| 489 | 493 | } |
| 494 | assert.deepStrictEqual(mac.electronLanguages, DESKTOP_ELECTRON_LANGUAGES); | |
| 490 | 495 | }).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })))), |
| 491 | 496 | ); |
| 492 | 497 | |
| 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 | 534 | it("excludes node-pty binaries for the other Windows architecture", () => { |
| 494 | 535 | assert.deepStrictEqual(resolveWindowsServerAsarIgnoreGlobs("x64"), [ |
| 495 | 536 | ...WINDOWS_SERVER_ASAR_IGNORE_GLOBS, |
| ... | ... | @@ -1112,6 +1153,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { |
| 1112 | 1153 | assert.equal(config.appId, "com.t3tools.t3code"); |
| 1113 | 1154 | assert.equal(mac.entitlements, "/tmp/entitlements.mac.plist"); |
| 1114 | 1155 | assert.equal(mac.provisioningProfile, "/tmp/t3code.provisionprofile"); |
| 1156 | assert.match(String(mac.sign), /\/scripts\/sign-macos\.ts$/); | |
| 1115 | 1157 | assert.deepStrictEqual(mac.protocols, [ |
| 1116 | 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 | 796 | "!apps/desktop/prod-resources/windows-server", |
| 797 | 797 | "!apps/desktop/prod-resources/windows-server/**/*", |
| 798 | 798 | ] as const; |
| 799 | // Windows terminal helpers cannot run on macOS and slow signing and notarization. | |
| 800 | export const MAC_FILE_EXCLUSIONS = [ | |
| 801 | "!**/node_modules/node-pty/prebuilds/win32-*/**/*", | |
| 802 | "!**/node_modules/node-pty/third_party/conpty/**/*", | |
| 803 | ] as const; | |
| 799 | 804 | // Windows ships the server tree (bundle + node_modules) as a separate |
| 800 | 805 | // resources/server.asar sidecar instead of loose files: the NSIS installer |
| 801 | 806 | // then extracts a handful of large archives instead of thousands of small |
| ... | ... | @@ -1097,6 +1102,19 @@ export function resolveFffNativeDependencies( |
| 1097 | 1102 | ); |
| 1098 | 1103 | } |
| 1099 | 1104 | |
| 1105 | export 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 | ||
| 1100 | 1118 | export interface ClerkPasskeyNativeArtifact { |
| 1101 | 1119 | readonly packageName: string; |
| 1102 | 1120 | readonly binaryFileName: string; |
| ... | ... | @@ -2061,7 +2079,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( |
| 2061 | 2079 | productName: resolveDesktopProductName(version), |
| 2062 | 2080 | artifactName: "T3-Code-${version}-${arch}.${ext}", |
| 2063 | 2081 | electronLanguages: [...DESKTOP_ELECTRON_LANGUAGES], |
| 2064 | files: [...DESKTOP_FILE_EXCLUSIONS], | |
| 2082 | files: [...DESKTOP_FILE_EXCLUSIONS, ...(platform === "mac" ? MAC_FILE_EXCLUSIONS : [])], | |
| 2065 | 2083 | directories: { |
| 2066 | 2084 | buildResources: "apps/desktop/resources", |
| 2067 | 2085 | }, |
| ... | ... | @@ -2088,6 +2106,8 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( |
| 2088 | 2106 | } |
| 2089 | 2107 | |
| 2090 | 2108 | if (platform === "mac") { |
| 2109 | const path = yield* Path.Path; | |
| 2110 | const repoRoot = yield* RepoRoot; | |
| 2091 | 2111 | buildConfig.mac = { |
| 2092 | 2112 | target: target === "dmg" ? [target, "zip"] : [target], |
| 2093 | 2113 | icon: "icon.icns", |
| ... | ... | @@ -2098,6 +2118,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( |
| 2098 | 2118 | schemes: ["t3code", "t3code-dev"], |
| 2099 | 2119 | }, |
| 2100 | 2120 | ], |
| 2121 | ...(signed ? { sign: path.join(repoRoot, "scripts/sign-macos.ts") } : {}), | |
| 2101 | 2122 | ...(macPasskeySigning |
| 2102 | 2123 | ? { |
| 2103 | 2124 | entitlements: macPasskeySigning.entitlementsPath, |
| ... | ... | @@ -2899,21 +2920,28 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 2899 | 2920 | |
| 2900 | 2921 | // Windows splits dependencies per process: app.asar carries only the |
| 2901 | 2922 | // desktop main-process runtime deps, while the server bundle's deps live in |
| 2902 | // the server.asar sidecar (see stageWindowsServerSidecar). macOS and Linux | |
| 2903 | // keep the single merged tree — their primary resolves everything from | |
| 2904 | // app.asar and there is no second consumer. | |
| 2923 | // the server.asar sidecar (see stageWindowsServerSidecar). macOS adds only | |
| 2924 | // server packages that remain external to its merged app.asar. Linux retains | |
| 2925 | // its existing full dependency tree. | |
| 2905 | 2926 | const stageDependencies = |
| 2906 | 2927 | options.platform === "win" |
| 2907 | 2928 | ? { ...resolvedDesktopRuntimeDependencies } |
| 2908 | : { | |
| 2909 | ...resolvedServerDependencies, | |
| 2910 | ...resolvedDesktopRuntimeDependencies, | |
| 2911 | ...resolveFffNativeDependencies( | |
| 2912 | options.platform, | |
| 2913 | options.arch, | |
| 2914 | serverPackageJson.dependencies["@ff-labs/fff-node"], | |
| 2915 | ), | |
| 2916 | }; | |
| 2929 | : options.platform === "mac" | |
| 2930 | ? resolveMacStageDependencies({ | |
| 2931 | serverDependencies: resolvedServerDependencies, | |
| 2932 | desktopDependencies: resolvedDesktopRuntimeDependencies, | |
| 2933 | arch: options.arch, | |
| 2934 | fffNodeVersion: serverPackageJson.dependencies["@ff-labs/fff-node"], | |
| 2935 | }) | |
| 2936 | : { | |
| 2937 | ...resolvedServerDependencies, | |
| 2938 | ...resolvedDesktopRuntimeDependencies, | |
| 2939 | ...resolveFffNativeDependencies( | |
| 2940 | options.platform, | |
| 2941 | options.arch, | |
| 2942 | serverPackageJson.dependencies["@ff-labs/fff-node"], | |
| 2943 | ), | |
| 2944 | }; | |
| 2917 | 2945 | const stagePatchedDependencies = createStagePatchedDependencies( |
| 2918 | 2946 | workspacePatchedDependencies, |
| 2919 | 2947 | stageDependencies, |
| ... | ... | @@ -3034,10 +3062,12 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( |
| 3034 | 3062 | buildEnv.GYP_MSVS_VERSION = buildEnv.GYP_MSVS_VERSION ?? "2022"; |
| 3035 | 3063 | } |
| 3036 | 3064 | if (options.verbose) { |
| 3037 | buildEnv.DEBUG = | |
| 3038 | buildEnv.DEBUG === undefined | |
| 3039 | ? "electron-builder,electron-builder:*" | |
| 3040 | : `${buildEnv.DEBUG},electron-builder,electron-builder:*`; | |
| 3065 | const debugNamespaces = [ | |
| 3066 | "electron-builder", | |
| 3067 | "electron-builder:*", | |
| 3068 | ...(options.platform === "mac" ? ["electron-osx-sign*", "electron-notarize*"] : []), | |
| 3069 | ]; | |
| 3070 | buildEnv.DEBUG = [buildEnv.DEBUG, ...debugNamespaces].filter(Boolean).join(","); | |
| 3041 | 3071 | } |
| 3042 | 3072 | |
| 3043 | 3073 | yield* Effect.log( |
scripts/package.json+1| ... | ... | @@ -9,6 +9,7 @@ |
| 9 | 9 | "dependencies": { |
| 10 | 10 | "@effect/platform-node": "catalog:", |
| 11 | 11 | "@electron/asar": "^3.4.1", |
| 12 | "@electron/osx-sign": "2.7.0", | |
| 12 | 13 | "@t3tools/contracts": "workspace:*", |
| 13 | 14 | "@t3tools/shared": "workspace:*", |
| 14 | 15 | "@t3tools/tailscale": "workspace:*", |
scripts/sign-macos.test.ts created+26| ... | ... | @@ -0,0 +1,26 @@ |
| 1 | import { sign as signApplication, type SignOptions } from "@electron/osx-sign"; | |
| 2 | import { expect, it, vi } from "vite-plus/test"; | |
| 3 | ||
| 4 | import sign from "./sign-macos.ts"; | |
| 5 | ||
| 6 | vi.mock("@electron/osx-sign", () => ({ sign: vi.fn() })); | |
| 7 | ||
| 8 | it("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 @@ |
| 1 | import { sign as signApplication, type SignOptions } from "@electron/osx-sign"; | |
| 2 | ||
| 3 | /** Sign files with matching options together instead of spawning codesign for each file. */ | |
| 4 | export default async function sign(options: SignOptions): Promise<void> { | |
| 5 | await signApplication({ ...options, batchCodesignCalls: true }); | |
| 6 | } |