authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 17:09:11-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 17:09:11-07:00
log9243ead1c3eebb6316321012cf42a21737965a56
tree704fac23f7c0f0e08a9ace81f7baf923f014b3c4
parent803c2b77387bbaf5f10319f8ac6e5742b68d9612
signature Signed by PGP key B5690EEEBB952194

[codex] Preserve Linux icon resize fallback failures (#3447)

Co-authored-by: codex <codex@users.noreply.github.com>

2 files changed, 104 insertions(+), 6 deletions(-)

scripts/build-desktop-artifact.test.ts+76
...@@ -4,6 +4,9 @@ import * as ConfigProvider from "effect/ConfigProvider";...@@ -4,6 +4,9 @@ import * as ConfigProvider from "effect/ConfigProvider";
4import * as Effect from "effect/Effect";4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";5import * as Layer from "effect/Layer";
6import * as Option from "effect/Option";6import * as Option from "effect/Option";
7import * as Sink from "effect/Sink";
8import * as Stream from "effect/Stream";
9import { ChildProcessSpawner } from "effect/unstable/process";
710
8import {11import {
9 BuildScriptError,12 BuildScriptError,
...@@ -14,6 +17,7 @@ import {...@@ -14,6 +17,7 @@ import {
14 InvalidMacPasskeyRpDomainError,17 InvalidMacPasskeyRpDomainError,
15 InvalidMacPasskeyPublishableKeyError,18 InvalidMacPasskeyPublishableKeyError,
16 isMacPasskeySigningConfigurationError,19 isMacPasskeySigningConfigurationError,
20 LinuxIconResizeError,
17 MissingMacPasskeyProvisioningProfileError,21 MissingMacPasskeyProvisioningProfileError,
18 renderMacPasskeyEntitlements,22 renderMacPasskeyEntitlements,
19 resolveClerkPasskeyNativeArtifacts,23 resolveClerkPasskeyNativeArtifacts,
...@@ -27,11 +31,49 @@ import {...@@ -27,11 +31,49 @@ import {
27 resolveGitHubPublishConfig,31 resolveGitHubPublishConfig,
28 resolveMockUpdateServerPort,32 resolveMockUpdateServerPort,
29 resolveMockUpdateServerUrl,33 resolveMockUpdateServerUrl,
34 stageLinuxIconSize,
30 STAGE_INSTALL_ARGS,35 STAGE_INSTALL_ARGS,
31} from "./build-desktop-artifact.ts";36} from "./build-desktop-artifact.ts";
32import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts";37import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts";
33import { HostProcessArchitecture, HostProcessPlatform } from "@t3tools/shared/hostProcess";38import { HostProcessArchitecture, HostProcessPlatform } from "@t3tools/shared/hostProcess";
3439
40function 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
56function 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
35it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {77it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
36 it("resolves the dedicated nightly updater channel from nightly versions", () => {78 it("resolves the dedicated nightly updater channel from nightly versions", () => {
37 assert.equal(resolveDesktopUpdateChannel("0.0.17-nightly.20260413.42"), "nightly");79 assert.equal(resolveDesktopUpdateChannel("0.0.17-nightly.20260413.42"), "nightly");
...@@ -184,6 +226,40 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -184,6 +226,40 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
184 assert.deepStrictEqual(DESKTOP_ASAR_UNPACK, ["node_modules/@ff-labs/fff-bin-*/**/*"]);226 assert.deepStrictEqual(DESKTOP_ASAR_UNPACK, ["node_modules/@ff-labs/fff-bin-*/**/*"]);
185 });227 });
186228
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 it("derives macOS passkey signing configuration from the Clerk publishable key", () => {263 it("derives macOS passkey signing configuration from the Clerk publishable key", () => {
188 const configuration = resolveMacPasskeySigningConfiguration({264 const configuration = resolveMacPasskeySigningConfiguration({
189 T3CODE_APPLE_TEAM_ID: "abc1234567",265 T3CODE_APPLE_TEAM_ID: "abc1234567",
scripts/build-desktop-artifact.ts+28-6
...@@ -142,6 +142,21 @@ export class BuildScriptError extends Data.TaggedError("BuildScriptError")<{...@@ -142,6 +142,21 @@ export class BuildScriptError extends Data.TaggedError("BuildScriptError")<{
142 }142 }
143}143}
144144
145export class LinuxIconResizeError extends Schema.TaggedErrorClass<LinuxIconResizeError>()(
146 "LinuxIconResizeError",
147 {
148 operation: Schema.Literal("resize"),
149 iconSize: Schema.Int,
150 primaryTool: Schema.Literal("magick"),
151 fallbackTool: Schema.Literal("convert"),
152 cause: Schema.Defect(),
153 },
154) {
155 override get message(): string {
156 return `Failed to ${this.operation} the Linux desktop icon to ${this.iconSize}x${this.iconSize} with \`${this.primaryTool}\` or \`${this.fallbackTool}\`. Install ImageMagick so either tool is available.`;
157 }
158}
159
145const collectStreamAsString = <E>(stream: Stream.Stream<Uint8Array, E>): Effect.Effect<string, E> =>160const collectStreamAsString = <E>(stream: Stream.Stream<Uint8Array, E>): Effect.Effect<string, E> =>
146 stream.pipe(161 stream.pipe(
147 Stream.decodeText(),162 Stream.decodeText(),
...@@ -877,7 +892,7 @@ function stageLinuxIcons(stageResourcesDir: string, sourcePng: string, verbose:...@@ -877,7 +892,7 @@ function stageLinuxIcons(stageResourcesDir: string, sourcePng: string, verbose:
877 });892 });
878}893}
879894
880function stageLinuxIconSize(895export function stageLinuxIconSize(
881 sourcePng: string,896 sourcePng: string,
882 targetPng: string,897 targetPng: string,
883 iconSize: number,898 iconSize: number,
...@@ -890,13 +905,20 @@ function stageLinuxIconSize(...@@ -890,13 +905,20 @@ function stageLinuxIconSize(
890 );905 );
891906
892 return resize("magick").pipe(907 return resize("magick").pipe(
893 Effect.catch(() =>908 Effect.catch((primaryCause) =>
894 resize("convert").pipe(909 resize("convert").pipe(
895 Effect.mapError(910 Effect.mapError(
896 () =>911 (fallbackCause) =>
897 new BuildScriptError({912 new LinuxIconResizeError({
898 message:913 operation: "resize",
899 "ImageMagick is required to generate Linux desktop icon sizes. Install ImageMagick so either `magick` or `convert` is available.",914 iconSize,
915 primaryTool: "magick",
916 fallbackTool: "convert",
917 cause: new AggregateError(
918 [primaryCause, fallbackCause],
919 "Both Linux icon resize tool attempts failed.",
920 { cause: primaryCause },
921 ),
900 }),922 }),
901 ),923 ),
902 ),924 ),