| ... | ... | @@ -4,6 +4,9 @@ import * as ConfigProvider from "effect/ConfigProvider"; |
| 4 | 4 | import * as Effect from "effect/Effect"; |
| 5 | 5 | import * as Layer from "effect/Layer"; |
| 6 | 6 | import * as Option from "effect/Option"; |
| 7 | import * as Sink from "effect/Sink"; |
| 8 | import * as Stream from "effect/Stream"; |
| 9 | import { ChildProcessSpawner } from "effect/unstable/process"; |
| 7 | 10 | |
| 8 | 11 | import { |
| 9 | 12 | BuildScriptError, |
| ... | ... | @@ -14,6 +17,7 @@ import { |
| 14 | 17 | InvalidMacPasskeyRpDomainError, |
| 15 | 18 | InvalidMacPasskeyPublishableKeyError, |
| 16 | 19 | isMacPasskeySigningConfigurationError, |
| 20 | LinuxIconResizeError, |
| 17 | 21 | MissingMacPasskeyProvisioningProfileError, |
| 18 | 22 | renderMacPasskeyEntitlements, |
| 19 | 23 | resolveClerkPasskeyNativeArtifacts, |
| ... | ... | @@ -27,11 +31,49 @@ import { |
| 27 | 31 | resolveGitHubPublishConfig, |
| 28 | 32 | resolveMockUpdateServerPort, |
| 29 | 33 | resolveMockUpdateServerUrl, |
| 34 | stageLinuxIconSize, |
| 30 | 35 | STAGE_INSTALL_ARGS, |
| 31 | 36 | } from "./build-desktop-artifact.ts"; |
| 32 | 37 | import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts"; |
| 33 | 38 | import { HostProcessArchitecture, HostProcessPlatform } from "@t3tools/shared/hostProcess"; |
| 34 | 39 | |
| 40 | function mockProcess(exitCode: number) { |
| 41 | return ChildProcessSpawner.makeHandle({ |
| 42 | pid: ChildProcessSpawner.ProcessId(1), |
| 43 | exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(exitCode)), |
| 44 | isRunning: Effect.succeed(false), |
| 45 | kill: () => Effect.void, |
| 46 | unref: Effect.succeed(Effect.void), |
| 47 | stdin: Sink.drain, |
| 48 | stdout: Stream.empty, |
| 49 | stderr: Stream.empty, |
| 50 | all: Stream.empty, |
| 51 | getInputFd: () => Sink.drain, |
| 52 | getOutputFd: () => Stream.empty, |
| 53 | }); |
| 54 | } |
| 55 | |
| 56 | function iconResizeSpawnerLayer( |
| 57 | commands: Array<{ readonly command: string; readonly args: ReadonlyArray<string> }>, |
| 58 | exitCodes: ReadonlyArray<number>, |
| 59 | ) { |
| 60 | let commandIndex = 0; |
| 61 | return Layer.succeed( |
| 62 | ChildProcessSpawner.ChildProcessSpawner, |
| 63 | ChildProcessSpawner.make((command) => { |
| 64 | const childProcess = command as unknown as { |
| 65 | readonly command: string; |
| 66 | readonly args: ReadonlyArray<string>; |
| 67 | }; |
| 68 | commands.push({ |
| 69 | command: childProcess.command, |
| 70 | args: childProcess.args, |
| 71 | }); |
| 72 | return Effect.succeed(mockProcess(exitCodes[commandIndex++] ?? 0)); |
| 73 | }), |
| 74 | ); |
| 75 | } |
| 76 | |
| 35 | 77 | it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { |
| 36 | 78 | it("resolves the dedicated nightly updater channel from nightly versions", () => { |
| 37 | 79 | assert.equal(resolveDesktopUpdateChannel("0.0.17-nightly.20260413.42"), "nightly"); |
| ... | ... | @@ -184,6 +226,40 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { |
| 184 | 226 | assert.deepStrictEqual(DESKTOP_ASAR_UNPACK, ["node_modules/@ff-labs/fff-bin-*/**/*"]); |
| 185 | 227 | }); |
| 186 | 228 | |
| 229 | it.effect("preserves both Linux icon resize failures with structural context", () => { |
| 230 | const commands: Array<{ readonly command: string; readonly args: ReadonlyArray<string> }> = []; |
| 231 | |
| 232 | return Effect.gen(function* () { |
| 233 | const error = yield* stageLinuxIconSize("source.png", "target.png", 512, false).pipe( |
| 234 | Effect.provide(iconResizeSpawnerLayer(commands, [1, 2])), |
| 235 | Effect.flip, |
| 236 | ); |
| 237 | |
| 238 | assert.instanceOf(error, LinuxIconResizeError); |
| 239 | assert.equal(error.operation, "resize"); |
| 240 | assert.equal(error.iconSize, 512); |
| 241 | assert.equal(error.primaryTool, "magick"); |
| 242 | assert.equal(error.fallbackTool, "convert"); |
| 243 | assert.include(error.message, "512x512"); |
| 244 | assert.include(error.message, "`magick`"); |
| 245 | assert.include(error.message, "`convert`"); |
| 246 | assert.notInclude(error.message, "non-zero exit code"); |
| 247 | |
| 248 | assert.instanceOf(error.cause, AggregateError); |
| 249 | const aggregateCause = error.cause as AggregateError; |
| 250 | assert.lengthOf(aggregateCause.errors, 2); |
| 251 | assert.strictEqual(aggregateCause.cause, aggregateCause.errors[0]); |
| 252 | assert.instanceOf(aggregateCause.errors[0], BuildScriptError); |
| 253 | assert.instanceOf(aggregateCause.errors[1], BuildScriptError); |
| 254 | assert.include((aggregateCause.errors[0] as BuildScriptError).message, "magick linux icon"); |
| 255 | assert.include((aggregateCause.errors[1] as BuildScriptError).message, "convert linux icon"); |
| 256 | assert.deepStrictEqual( |
| 257 | commands.map(({ command }) => command), |
| 258 | ["magick", "convert"], |
| 259 | ); |
| 260 | }); |
| 261 | }); |
| 262 | |
| 187 | 263 | it("derives macOS passkey signing configuration from the Clerk publishable key", () => { |
| 188 | 264 | const configuration = resolveMacPasskeySigningConfiguration({ |
| 189 | 265 | T3CODE_APPLE_TEAM_ID: "abc1234567", |