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";
44import * as Effect from "effect/Effect";
55import * as Layer from "effect/Layer";
66import * as Option from "effect/Option";
7import * as Sink from "effect/Sink";
8import * as Stream from "effect/Stream";
9import { ChildProcessSpawner } from "effect/unstable/process";
710
811import {
912 BuildScriptError,
......@@ -14,6 +17,7 @@ import {
1417 InvalidMacPasskeyRpDomainError,
1518 InvalidMacPasskeyPublishableKeyError,
1619 isMacPasskeySigningConfigurationError,
20 LinuxIconResizeError,
1721 MissingMacPasskeyProvisioningProfileError,
1822 renderMacPasskeyEntitlements,
1923 resolveClerkPasskeyNativeArtifacts,
......@@ -27,11 +31,49 @@ import {
2731 resolveGitHubPublishConfig,
2832 resolveMockUpdateServerPort,
2933 resolveMockUpdateServerUrl,
34 stageLinuxIconSize,
3035 STAGE_INSTALL_ARGS,
3136} from "./build-desktop-artifact.ts";
3237import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts";
3338import { 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
3577it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
3678 it("resolves the dedicated nightly updater channel from nightly versions", () => {
3779 assert.equal(resolveDesktopUpdateChannel("0.0.17-nightly.20260413.42"), "nightly");
......@@ -184,6 +226,40 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
184226 assert.deepStrictEqual(DESKTOP_ASAR_UNPACK, ["node_modules/@ff-labs/fff-bin-*/**/*"]);
185227 });
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
187263 it("derives macOS passkey signing configuration from the Clerk publishable key", () => {
188264 const configuration = resolveMacPasskeySigningConfiguration({
189265 T3CODE_APPLE_TEAM_ID: "abc1234567",
scripts/build-desktop-artifact.ts+28-6
......@@ -142,6 +142,21 @@ export class BuildScriptError extends Data.TaggedError("BuildScriptError")<{
142142 }
143143}
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
145160const collectStreamAsString = <E>(stream: Stream.Stream<Uint8Array, E>): Effect.Effect<string, E> =>
146161 stream.pipe(
147162 Stream.decodeText(),
......@@ -877,7 +892,7 @@ function stageLinuxIcons(stageResourcesDir: string, sourcePng: string, verbose:
877892 });
878893}
879894
880function stageLinuxIconSize(
895export function stageLinuxIconSize(
881896 sourcePng: string,
882897 targetPng: string,
883898 iconSize: number,
......@@ -890,13 +905,20 @@ function stageLinuxIconSize(
890905 );
891906
892907 return resize("magick").pipe(
893 Effect.catch(() =>
908 Effect.catch((primaryCause) =>
894909 resize("convert").pipe(
895910 Effect.mapError(
896 () =>
897 new BuildScriptError({
898 message:
899 "ImageMagick is required to generate Linux desktop icon sizes. Install ImageMagick so either `magick` or `convert` is available.",
911 (fallbackCause) =>
912 new LinuxIconResizeError({
913 operation: "resize",
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 ),
900922 }),
901923 ),
902924 ),