History

Stage production icons for desktop builds and publish (#168)

* Stage production icons for desktop builds and publish - add shared brand asset paths and a Windows .ico asset - stage Linux/Windows icons in desktop artifact builds - override and restore publish favicon during server CLI publish * 'blip' * Fix PR review issues for publish icon staging * Refactor brand assets and harden Windows desktop build setup * Apply remaining server orchestration updates * Fix production mac icon asset Co-authored-by: Theo Browne <t3dotgg@users.noreply.github.com> ---------

Julius Marminge committed 8 months ago

Commit d249e621 parent 40fcb6b42 files · +237 −60Browse files

Unified · Split

42 files
  1. apps/server/integration/OrchestrationEngineHarness.integration.ts +5 −10
  2. apps/server/scripts/cli.ts +95 −6
  3. apps/server/src/orchestration/Layers/CheckpointReactor.test.ts +0 −1
  4. apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts +0 −1
  5. apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts +0 −1
  6. apps/server/src/provider/Layers/ProviderService.test.ts +3 −16
  7. apps/server/src/provider/Layers/ProviderService.ts +7 −2
  8. apps/server/src/provider/Services/ProviderService.ts +0 −10
  9. apps/server/src/serverLogger.ts +3 −6
  10. apps/server/src/wsServer.test.ts +0 −1
  11. apps/server/src/wsServer.ts +0 −6
  12. apps/web/public/apple-touch-icon.png +0 −0
  13. apps/web/public/favicon-16x16.png +0 −0
  14. apps/web/public/favicon-32x32.png +0 −0
  15. apps/web/public/favicon.ico +0 −0
  16. apps/web/public/favicon.svg +0 −4
  17. assets/T3 Code blueprint-macOS-Default-1024x1024@1x.png +0 −0
  18. assets/T3 Code logo.svg +0 −4
  19. assets/T3 Code logo@16x.png +0 −0
  20. assets/T3 Code logo@32x.png +0 −0
  21. assets/T3 Code-macOS-Default-1024x1024@4x.png +0 −0
  22. assets/dev/blueprint-ios-1024.png +0 −0
  23. assets/dev/blueprint-macos-1024.png +0 −0
  24. assets/dev/blueprint-universal-1024.png +0 −0
  25. assets/dev/blueprint-web-apple-touch-180.png +0 −0
  26. assets/dev/blueprint-web-favicon-16x16.png +0 −0
  27. assets/dev/blueprint-web-favicon-32x32.png +0 −0
  28. assets/dev/blueprint-web-favicon.ico +0 −0
  29. assets/dev/blueprint-windows.ico +0 −0
  30. assets/logo-1024.png +0 −0
  31. assets/macos-icon-1024.png +0 −0
  32. assets/prod/black-ios-1024.png +0 −0
  33. assets/prod/black-macos-1024.png +0 −0
  34. assets/prod/black-universal-1024.png +0 −0
  35. assets/prod/logo.svg +0 −0
  36. assets/prod/t3-black-web-apple-touch-180.png +0 −0
  37. assets/prod/t3-black-web-favicon-16x16.png +0 −0
  38. assets/prod/t3-black-web-favicon-32x32.png +0 −0
  39. assets/prod/t3-black-web-favicon.ico +0 −0
  40. assets/prod/t3-black-windows.ico +0 −0
  41. scripts/build-desktop-artifact.ts +93 −18
  42. scripts/lib/brand-assets.ts +57 −0
apps/server/integration/OrchestrationEngineHarness.integration.ts+5−10
@@ -406,19 +406,14 @@ export const makeOrchestrationIntegrationHarness = ( · 405 unmodified lines
406406 disposed = true;
407407
408408 const shutdown = Effect.gen(function* () {
409− const stopAllExit = yield* Effect.exit(
410− Effect.promise(() => runtime.runPromise(providerService.stopAll())),
411− );
412409 const closeScopeExit = yield* Effect.exit(Scope.close(scope, Exit.void));
413410 const disposeRuntimeExit = yield* Effect.exit(Effect.promise(() => runtime.dispose()));
414411
415− const failureCause = Exit.isFailure(stopAllExit)
416− ? stopAllExit.cause
417− : Exit.isFailure(closeScopeExit)
418− ? closeScopeExit.cause
419− : Exit.isFailure(disposeRuntimeExit)
420− ? disposeRuntimeExit.cause
421− : null;
412+ const failureCause = Exit.isFailure(closeScopeExit)
413+ ? closeScopeExit.cause
414+ : Exit.isFailure(disposeRuntimeExit)
415+ ? disposeRuntimeExit.cause
416+ : null;
422417
423418 if (failureCause) {
424419 return yield* Effect.failCause(failureCause);
apps/server/scripts/cli.ts+95−6
@@ -6,6 +6,7 @@ import { Data, Effect, FileSystem, Logger, Option, Path } from "effect"; · 5 unmodified lines
66 import { Command, Flag } from "effect/unstable/cli";
77 import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
88
9+import { DEVELOPMENT_ICON_OVERRIDES, PUBLISH_ICON_OVERRIDES } from "../../../scripts/lib/brand-assets.ts";
910 import { resolveCatalogDependencies } from "../../../scripts/lib/resolve-catalog.ts";
1011 import rootPackageJson from "../../../package.json" with { type: "json" };
1112 import serverPackageJson from "../package.json" with { type: "json" };
@@ -31,6 +32,84 @@ const runCommand = Effect.fn("runCommand")(function* (command: ChildProcess.Comm · 19 unmodified lines
3132 }
3233 });
3334
35+interface PublishIconBackup {
36+ readonly targetPath: string;
37+ readonly backupPath: string;
38+}
39+
40+const applyPublishIconOverrides = Effect.fn("applyPublishIconOverrides")(function* (
41+ repoRoot: string,
42+ serverDir: string,
43+) {
44+ const path = yield* Path.Path;
45+ const fs = yield* FileSystem.FileSystem;
46+ const backups: PublishIconBackup[] = [];
47+
48+ for (const override of PUBLISH_ICON_OVERRIDES) {
49+ const sourcePath = path.join(repoRoot, override.sourceRelativePath);
50+ const targetPath = path.join(serverDir, override.targetRelativePath);
51+ const backupPath = `${targetPath}.publish-bak`;
52+
53+ if (!(yield* fs.exists(sourcePath))) {
54+ return yield* new CliError({
55+ message: `Missing publish icon source: ${sourcePath}`,
56+ });
57+ }
58+ if (!(yield* fs.exists(targetPath))) {
59+ return yield* new CliError({
60+ message: `Missing publish icon target: ${targetPath}. Run the build subcommand first.`,
61+ });
62+ }
63+
64+ yield* fs.copyFile(targetPath, backupPath);
65+ yield* fs.copyFile(sourcePath, targetPath);
66+ backups.push({ targetPath, backupPath });
67+ }
68+
69+ yield* Effect.log("[cli] Applied publish icon overrides to dist/client");
70+ return backups as ReadonlyArray<PublishIconBackup>;
71+});
72+
73+const restorePublishIconOverrides = Effect.fn("restorePublishIconOverrides")(function* (
74+ backups: ReadonlyArray<PublishIconBackup>,
75+) {
76+ const fs = yield* FileSystem.FileSystem;
77+ for (const backup of backups) {
78+ if (!(yield* fs.exists(backup.backupPath))) {
79+ continue;
80+ }
81+ yield* fs.rename(backup.backupPath, backup.targetPath);
82+ }
83+});
84+
85+const applyDevelopmentIconOverrides = Effect.fn("applyDevelopmentIconOverrides")(function* (
86+ repoRoot: string,
87+ serverDir: string,
88+) {
89+ const path = yield* Path.Path;
90+ const fs = yield* FileSystem.FileSystem;
91+
92+ for (const override of DEVELOPMENT_ICON_OVERRIDES) {
93+ const sourcePath = path.join(repoRoot, override.sourceRelativePath);
94+ const targetPath = path.join(serverDir, override.targetRelativePath);
95+
96+ if (!(yield* fs.exists(sourcePath))) {
97+ return yield* new CliError({
98+ message: `Missing development icon source: ${sourcePath}`,
99+ });
100+ }
101+ if (!(yield* fs.exists(targetPath))) {
102+ return yield* new CliError({
103+ message: `Missing development icon target: ${targetPath}. Build web first.`,
104+ });
105+ }
106+
107+ yield* fs.copyFile(sourcePath, targetPath);
108+ }
109+
110+ yield* Effect.log("[cli] Applied development icon overrides to dist/client");
111+});
112+
34113 // ---------------------------------------------------------------------------
35114 // build subcommand
36115 // ---------------------------------------------------------------------------
@@ -61,6 +140,7 @@ const buildCmd = Command.make( · 24 unmodified lines
61140
62141 if (yield* fs.exists(webDist)) {
63142 yield* fs.copy(webDist, clientTarget);
143+ yield* applyDevelopmentIconOverrides(repoRoot, serverDir);
64144 yield* Effect.log("[cli] Bundled web app into dist/client");
65145 } else {
66146 yield* Effect.logWarning("[cli] Web dist not found — skipping client bundle.");
@@ -104,10 +184,8 @@ const publishCmd = Command.make( · 37 unmodified lines
104184 yield* Effect.acquireUseRelease(
105185 // Acquire: backup package.json, resolve catalog: deps, strip devDependencies/scripts
106186 Effect.gen(function* () {
107− const original = yield* fs.readFileString(packageJsonPath);
108− yield* fs.writeFileString(backupPath, original);
109−
110− // Build package.json for publish
187+ // Resolve catalog dependencies before any file mutations. If this throws,
188+ // acquire fails and no release hook runs, so filesystem must still be untouched.
111189 const version = Option.getOrElse(config.appVersion, () => serverPackageJson.version);
112190 const pkg = {
113191 name: serverPackageJson.name,
@@ -120,15 +198,19 @@ const publishCmd = Command.make( · 6 unmodified lines
120198 dependencies: serverPackageJson.dependencies as Record<string, unknown>,
121199 };
122200
123− // Resolve catalog: entries in production dependencies
124201 pkg.dependencies = resolveCatalogDependencies(
125202 pkg.dependencies,
126203 rootPackageJson.workspaces.catalog,
127204 "apps/server dependencies",
128205 );
129206
207+ const original = yield* fs.readFileString(packageJsonPath);
208+ yield* fs.writeFileString(backupPath, original);
130209 yield* fs.writeFileString(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
131210 yield* Effect.log("[cli] Resolved package.json for publish");
211+
212+ const iconBackups = yield* applyPublishIconOverrides(repoRoot, serverDir);
213+ return { iconBackups };
132214 }),
133215 // Use: npm publish
134216 () =>
@@ -147,8 +229,15 @@ const publishCmd = Command.make( · 12 unmodified lines
147229 );
148230 }),
149231 // Release: restore
150− () =>
232+ (resource: { readonly iconBackups: ReadonlyArray<PublishIconBackup> }) =>
151233 Effect.gen(function* () {
234+ yield* restorePublishIconOverrides(resource.iconBackups).pipe(
235+ Effect.catch((error) =>
236+ Effect.logError(
237+ `[cli] Failed to restore publish icon overrides: ${String(error)}`,
238+ ),
239+ ),
240+ );
152241 yield* fs.rename(backupPath, packageJsonPath);
153242 if (config.verbose) yield* Effect.log("[cli] Restored original package.json");
154243 }),
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+0−1
@@ -91,7 +91,6 @@ function createProviderServiceHarness( · 90 unmodified lines
9191 listSessions,
9292 getCapabilities: () => Effect.succeed({ sessionModelSwitch: "in-session" }),
9393 rollbackConversation,
94− stopAll: () => Effect.void,
9594 streamEvents: Stream.fromPubSub(runtimeEventPubSub),
9695 };
9796
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+0−1
@@ -188,7 +188,6 @@ describe("ProviderCommandReactor", () => { · 187 unmodified lines
188188 sessionModelSwitch: provider === "codex" ? "in-session" : "in-session",
189189 }),
190190 rollbackConversation: () => unsupported(),
191− stopAll: () => Effect.void,
192191 streamEvents: Stream.fromPubSub(runtimeEventPubSub),
193192 };
194193
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts+0−1
@@ -65,7 +65,6 @@ function createProviderServiceHarness() { · 64 unmodified lines
6565 listSessions: () => Effect.succeed([]),
6666 getCapabilities: () => Effect.succeed({ sessionModelSwitch: "in-session" }),
6767 rollbackConversation: () => unsupported(),
68− stopAll: () => Effect.void,
6968 streamEvents: Stream.fromPubSub(runtimeEventPubSub),
7069 };
7170
apps/server/src/provider/Layers/ProviderService.test.ts+3−16
@@ -356,11 +356,6 @@ it.effect( · 355 unmodified lines
356356 });
357357 }).pipe(Effect.provide(firstProviderLayer));
358358
359− yield* Effect.gen(function* () {
360− const provider = yield* ProviderService;
361− yield* provider.stopAll();
362− }).pipe(Effect.provide(firstProviderLayer));
363−
364359 const persistedAfterStopAll = yield* Effect.gen(function* () {
365360 const repository = yield* ProviderSessionRuntimeRepository;
366361 return yield* repository.getByThreadId({ threadId: startedSession.threadId });
@@ -549,7 +544,7 @@ routing.layer("ProviderServiceLive routing", (it) => { · 182 unmodified lines
549544 runtimeMode: "full-access",
550545 });
551546
552− yield* provider.stopAll();
547+ yield* routing.codex.stopAll();
553548 routing.codex.startSession.mockClear();
554549 routing.codex.sendTurn.mockClear();
555550
@@ -578,7 +573,7 @@ routing.layer("ProviderServiceLive routing", (it) => { · 22 unmodified lines
578573 }),
579574 );
580575
581− it.effect("stops all sessions and clears adapter runtime", () =>
576+ it.effect("lists no sessions after adapter runtime clears", () =>
582577 Effect.gen(function* () {
583578 const provider = yield* ProviderService;
584579
@@ -593,7 +588,7 @@ routing.layer("ProviderServiceLive routing", (it) => { · 8 unmodified lines
593588 runtimeMode: "full-access",
594589 });
595590
596− yield* provider.stopAll();
591+ yield* routing.codex.stopAll();
597592
598593 const remaining = yield* provider.listSessions();
599594 assert.equal(remaining.length, 0);
@@ -641,14 +636,6 @@ routing.layer("ProviderServiceLive routing", (it) => { · 41 unmodified lines
641636 }
642637 }
643638
644− yield* provider.stopAll();
645− const stoppedRuntime = yield* runtimeRepository.getByThreadId({
646− threadId: session.threadId,
647− });
648− assert.equal(Option.isSome(stoppedRuntime), true);
649− if (Option.isSome(stoppedRuntime)) {
650− assert.equal(stoppedRuntime.value.status, "stopped");
651− }
652639 }),
653640 );
654641 });
apps/server/src/provider/Layers/ProviderService.ts+7−2
@@ -471,7 +471,7 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => · 470 unmodified lines
471471 });
472472 });
473473
474− const stopAll: ProviderServiceShape["stopAll"] = () =>
474+ const runStopAll = () =>
475475 Effect.gen(function* () {
476476 const threadIds = yield* directory.listThreadIds();
477477 yield* Effect.forEach(adapters, (adapter) => adapter.stopAll()).pipe(Effect.asVoid);
@@ -497,6 +497,12 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => · 19 unmodified lines
497497 yield* analytics.flush;
498498 });
499499
500+ yield* Effect.addFinalizer(() =>
501+ Effect.catch(runStopAll(), (cause) =>
502+ Effect.logWarning("failed to stop provider service", { cause }),
503+ ),
504+ );
505+
500506 return {
501507 startSession,
502508 sendTurn,
@@ -507,7 +513,6 @@ const makeProviderService = (options?: ProviderServiceLiveOptions) => · 4 unmodified lines
507513 listSessions,
508514 getCapabilities,
509515 rollbackConversation,
510− stopAll,
511516 streamEvents: Stream.fromPubSub(runtimeEventPubSub),
512517 } satisfies ProviderServiceShape;
513518 });
apps/server/src/provider/Services/ProviderService.ts+0−10
@@ -99,16 +99,6 @@ export interface ProviderServiceShape { · 98 unmodified lines
9999 readonly numTurns: number;
100100 }) => Effect.Effect<void, ProviderServiceError>;
101101
102− /**
103− * Stop all active provider sessions.
104− *
105− * Persisted session bindings remain so stale sessions can be resumed after
106− * process restart.
107− *
108− * Runtime alias mappings are cleared as part of shutdown.
109− */
110− readonly stopAll: () => Effect.Effect<void, ProviderServiceError>;
111−
112102 /**
113103 * Canonical provider runtime event stream.
114104 *
apps/server/src/serverLogger.ts+3−6
@@ -1,18 +1,15 @@
11 import fs from "node:fs";
22 import path from "node:path";
33
4−import { Effect, Logger, Option } from "effect";
4+import { Effect, Logger } from "effect";
55 import * as Layer from "effect/Layer";
66
77 import { ServerConfig } from "./config";
88
99 export const ServerLoggerLive = Effect.gen(function* () {
10− const config = yield* Effect.serviceOption(ServerConfig);
11− if (Option.isNone(config)) {
12− return Logger.layer([Logger.defaultLogger]);
13− }
10+ const config = yield* ServerConfig;
1411
15− const logDir = path.join(config.value.stateDir, "logs");
12+ const logDir = path.join(config.stateDir, "logs");
1613 const logPath = path.join(logDir, "server.log");
1714
1815 yield* Effect.sync(() => {
apps/server/src/wsServer.test.ts+0−1
@@ -1171,7 +1171,6 @@ describe("WebSocket Server", () => { · 1170 unmodified lines
11711171 listSessions: () => Effect.succeed([]),
11721172 getCapabilities: () => Effect.succeed({ sessionModelSwitch: "in-session" }),
11731173 rollbackConversation: () => unsupported(),
1174− stopAll: () => Effect.void,
11751174 streamEvents: Stream.fromPubSub(runtimeEventPubSub),
11761175 };
11771176 const providerLayer = Layer.succeed(ProviderService, providerService);
apps/server/src/wsServer.ts+0−6
@@ -568,7 +568,6 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< · 567 unmodified lines
568568 const orchestrationEngine = yield* OrchestrationEngineService;
569569 const projectionReadModelQuery = yield* ProjectionSnapshotQuery;
570570 const checkpointDiffQuery = yield* CheckpointDiffQuery;
571− const liveProviderService = yield* ProviderService;
572571 const orchestrationReactor = yield* OrchestrationReactor;
573572 const { openInEditor } = yield* Open;
574573
@@ -660,11 +659,6 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< · 85 unmodified lines
660659 }
661660
662661 const runPromise = yield* Effect.map(Effect.services<never>(), Effect.runPromiseWith);
663− yield* Effect.addFinalizer(() =>
664− Effect.catch(liveProviderService.stopAll(), (cause) =>
665− Effect.logWarning("failed to stop provider service", { cause }),
666− ),
667− );
668662
669663 const unsubscribeTerminalEvents = yield* terminalManager.subscribe(
670664 (event) => void Effect.runPromise(onTerminalEvent(event)),
apps/web/public/apple-touch-icon.png+0−0

Binary file not shown.

apps/web/public/favicon-16x16.png+0−0

Binary file not shown.

apps/web/public/favicon-32x32.png+0−0

Binary file not shown.

apps/web/public/favicon.ico+0−0

Binary file not shown.

apps/web/public/favicon.svg+0−4
@@ -1,4 +0,0 @@
1−<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2−<path d="M0 10C0 4.47715 4.47715 0 10 0H118C123.523 0 128 4.47715 128 10V118C128 123.523 123.523 128 118 128H10C4.47715 128 0 123.523 0 118V10Z" fill="black"/>
3−<path d="M33.4509 93V47.56H15.5309V37H64.3309V47.56H46.4109V93H33.4509ZM86.7253 93.96C82.832 93.96 78.9653 93.4533 75.1253 92.44C71.2853 91.3733 68.032 89.88 65.3653 87.96L70.4053 78.04C72.5386 79.5867 75.0186 80.8133 77.8453 81.72C80.672 82.6267 83.5253 83.08 86.4053 83.08C89.6586 83.08 92.2186 82.44 94.0853 81.16C95.952 79.88 96.8853 78.12 96.8853 75.88C96.8853 73.7467 96.0586 72.0667 94.4053 70.84C92.752 69.6133 90.0853 69 86.4053 69H80.4853V60.44L96.0853 42.76L97.5253 47.4H68.1653V37H107.365V45.4L91.8453 63.08L85.2853 59.32H89.0453C95.9253 59.32 101.125 60.8667 104.645 63.96C108.165 67.0533 109.925 71.0267 109.925 75.88C109.925 79.0267 109.099 81.9867 107.445 84.76C105.792 87.48 103.259 89.6933 99.8453 91.4C96.432 93.1067 92.0586 93.96 86.7253 93.96Z" fill="white"/>
4−</svg>
assets/T3 Code blueprint-macOS-Default-1024x1024@1x.png+0−0

Binary file not shown.

assets/T3 Code logo.svg+0−4
@@ -1,4 +0,0 @@
1−<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2−<path d="M0 10C0 4.47715 4.47715 0 10 0H118C123.523 0 128 4.47715 128 10V118C128 123.523 123.523 128 118 128H10C4.47715 128 0 123.523 0 118V10Z" fill="black"/>
3−<path d="M33.4509 93V47.56H15.5309V37H64.3309V47.56H46.4109V93H33.4509ZM86.7253 93.96C82.832 93.96 78.9653 93.4533 75.1253 92.44C71.2853 91.3733 68.032 89.88 65.3653 87.96L70.4053 78.04C72.5386 79.5867 75.0186 80.8133 77.8453 81.72C80.672 82.6267 83.5253 83.08 86.4053 83.08C89.6586 83.08 92.2186 82.44 94.0853 81.16C95.952 79.88 96.8853 78.12 96.8853 75.88C96.8853 73.7467 96.0586 72.0667 94.4053 70.84C92.752 69.6133 90.0853 69 86.4053 69H80.4853V60.44L96.0853 42.76L97.5253 47.4H68.1653V37H107.365V45.4L91.8453 63.08L85.2853 59.32H89.0453C95.9253 59.32 101.125 60.8667 104.645 63.96C108.165 67.0533 109.925 71.0267 109.925 75.88C109.925 79.0267 109.099 81.9867 107.445 84.76C105.792 87.48 103.259 89.6933 99.8453 91.4C96.432 93.1067 92.0586 93.96 86.7253 93.96Z" fill="white"/>
4−</svg>
assets/T3 Code logo@16x.png+0−0

Binary file not shown.

assets/T3 Code logo@32x.png+0−0

Binary file not shown.

assets/T3 Code-macOS-Default-1024x1024@4x.png+0−0

Binary file not shown.

assets/dev/blueprint-ios-1024.png+0−0

Binary file not shown.

assets/dev/blueprint-macos-1024.pngrenamed from assets/T3 Code blueprint-macOS-Default-1024x1024@2x.png+0−0

File renamed without changes.

assets/dev/blueprint-universal-1024.png+0−0

Binary file not shown.

assets/dev/blueprint-web-apple-touch-180.png+0−0

Binary file not shown.

assets/dev/blueprint-web-favicon-16x16.png+0−0

Binary file not shown.

assets/dev/blueprint-web-favicon-32x32.png+0−0

Binary file not shown.

assets/dev/blueprint-web-favicon.ico+0−0

Binary file not shown.

assets/dev/blueprint-windows.ico+0−0

Binary file not shown.

assets/logo-1024.png+0−0

Binary file not shown.

assets/macos-icon-1024.png+0−0

Binary file not shown.

assets/prod/black-ios-1024.png+0−0

Binary file not shown.

assets/prod/black-macos-1024.pngrenamed from assets/T3 Code-macOS-Default-1024x1024@1x.png+0−0

File renamed without changes.

assets/prod/black-universal-1024.pngrenamed from assets/T3 Code logo@8x.png+0−0

File renamed without changes.

assets/prod/logo.svgrenamed from assets/logo.svg+0−0

File renamed without changes.

assets/prod/t3-black-web-apple-touch-180.png+0−0

Binary file not shown.

assets/prod/t3-black-web-favicon-16x16.png+0−0

Binary file not shown.

assets/prod/t3-black-web-favicon-32x32.png+0−0

Binary file not shown.

assets/prod/t3-black-web-favicon.ico+0−0

Binary file not shown.

assets/prod/t3-black-windows.ico+0−0

Binary file not shown.

scripts/build-desktop-artifact.ts+93−18
@@ -1,11 +1,14 @@
11 #!/usr/bin/env node
22
33 import { spawnSync } from "node:child_process";
4+import { existsSync } from "node:fs";
5+import { join } from "node:path";
46
57 import rootPackageJson from "../package.json" with { type: "json" };
68 import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" };
79 import serverPackageJson from "../apps/server/package.json" with { type: "json" };
810
11+import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts";
912 import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts";
1013
1114 import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
@@ -20,8 +23,18 @@ const BuildArch = Schema.Literals(["arm64", "x64", "universal"]); · 8 unmodified lines
2023 const RepoRoot = Effect.service(Path.Path).pipe(
2124 Effect.flatMap((path) => path.fromFileUrl(new URL("..", import.meta.url))),
2225 );
23−const IconSource = Effect.zipWith(RepoRoot, Effect.service(Path.Path), (repoRoot, path) =>
24− path.join(repoRoot, "assets/macos-icon-1024.png"),
26+const ProductionMacIconSource = Effect.zipWith(RepoRoot, Effect.service(Path.Path), (repoRoot, path) =>
27+ path.join(repoRoot, BRAND_ASSET_PATHS.productionMacIconPng),
28+);
29+const ProductionLinuxIconSource = Effect.zipWith(
30+ RepoRoot,
31+ Effect.service(Path.Path),
32+ (repoRoot, path) => path.join(repoRoot, BRAND_ASSET_PATHS.productionLinuxIconPng),
33+);
34+const ProductionWindowsIconSource = Effect.zipWith(
35+ RepoRoot,
36+ Effect.service(Path.Path),
37+ (repoRoot, path) => path.join(repoRoot, BRAND_ASSET_PATHS.productionWindowsIconIco),
2538 );
2639 const encodeJsonString = Schema.encodeEffect(Schema.UnknownFromJsonString);
2740
@@ -104,6 +117,39 @@ function resolveGitCommitHash(repoRoot: string): string { · 76 unmodified lines
104117 return hash.toLowerCase();
105118 }
106119
120+function resolvePythonForNodeGyp(): string | undefined {
121+ const configured = process.env.npm_config_python ?? process.env.PYTHON;
122+ if (configured && existsSync(configured)) {
123+ return configured;
124+ }
125+
126+ if (process.platform === "win32") {
127+ const localAppData = process.env.LOCALAPPDATA;
128+ if (localAppData) {
129+ for (const version of ["Python313", "Python312", "Python311", "Python310"]) {
130+ const candidate = join(localAppData, "Programs", "Python", version, "python.exe");
131+ if (existsSync(candidate)) {
132+ return candidate;
133+ }
134+ }
135+ }
136+ }
137+
138+ const probe = spawnSync("python", ["-c", "import sys;print(sys.executable)"], {
139+ encoding: "utf8",
140+ });
141+ if (probe.status !== 0) {
142+ return undefined;
143+ }
144+
145+ const executable = probe.stdout.trim();
146+ if (!executable || !existsSync(executable)) {
147+ return undefined;
148+ }
149+
150+ return executable;
151+}
152+
107153 interface ResolvedBuildOptions {
108154 readonly platform: typeof BuildPlatform.Type;
109155 readonly target: string;
@@ -261,7 +307,7 @@ function stageMacIcons(stageResourcesDir: string, verbose: boolean) { · 151 unmodified lines
261307 return Effect.gen(function* () {
262308 const fs = yield* FileSystem.FileSystem;
263309 const path = yield* Path.Path;
264− const iconSource = yield* IconSource;
310+ const iconSource = yield* ProductionMacIconSource;
265311 if (!(yield* fs.exists(iconSource))) {
266312 return yield* new BuildScriptError({
267313 message: `Production icon source is missing at ${iconSource}`,
@@ -285,6 +331,38 @@ function stageMacIcons(stageResourcesDir: string, verbose: boolean) { · 17 unmodified lines
285331 });
286332 }
287333
334+function stageLinuxIcons(stageResourcesDir: string) {
335+ return Effect.gen(function* () {
336+ const fs = yield* FileSystem.FileSystem;
337+ const path = yield* Path.Path;
338+ const iconSource = yield* ProductionLinuxIconSource;
339+ if (!(yield* fs.exists(iconSource))) {
340+ return yield* new BuildScriptError({
341+ message: `Production icon source is missing at ${iconSource}`,
342+ });
343+ }
344+
345+ const iconPath = path.join(stageResourcesDir, "icon.png");
346+ yield* fs.copyFile(iconSource, iconPath);
347+ });
348+}
349+
350+function stageWindowsIcons(stageResourcesDir: string) {
351+ return Effect.gen(function* () {
352+ const fs = yield* FileSystem.FileSystem;
353+ const path = yield* Path.Path;
354+ const iconSource = yield* ProductionWindowsIconSource;
355+ if (!(yield* fs.exists(iconSource))) {
356+ return yield* new BuildScriptError({
357+ message: `Production Windows icon source is missing at ${iconSource}`,
358+ });
359+ }
360+
361+ const iconPath = path.join(stageResourcesDir, "icon.ico");
362+ yield* fs.copyFile(iconSource, iconPath);
363+ });
364+}
365+
288366 function validateBundledClientAssets(clientDir: string) {
289367 return Effect.gen(function* () {
290368 const fs = yield* FileSystem.FileSystem;
@@ -416,31 +494,18 @@ const assertPlatformBuildResources = Effect.fn("assertPlatformBuildResources")(f · 125 unmodified lines
416494 stageResourcesDir: string,
417495 verbose: boolean,
418496 ) {
419− const fs = yield* FileSystem.FileSystem;
420− const path = yield* Path.Path;
421−
422497 if (platform === "mac") {
423498 yield* stageMacIcons(stageResourcesDir, verbose);
424499 return;
425500 }
426501
427502 if (platform === "linux") {
428− const iconPath = path.join(stageResourcesDir, "icon.png");
429− if (!(yield* fs.exists(iconPath))) {
430− return yield* new BuildScriptError({
431− message: `Missing Linux icon at ${iconPath}`,
432− });
433− }
503+ yield* stageLinuxIcons(stageResourcesDir);
434504 return;
435505 }
436506
437507 if (platform === "win") {
438− const iconPath = path.join(stageResourcesDir, "icon.ico");
439− if (!(yield* fs.exists(iconPath))) {
440− return yield* new BuildScriptError({
441− message: `Missing Windows icon at ${iconPath}`,
442− });
443− }
508+ yield* stageWindowsIcons(stageResourcesDir);
444509 }
445510 });
446511
@@ -597,6 +662,16 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( · 150 unmodified lines
597662 delete buildEnv.APPLE_API_ISSUER;
598663 }
599664
665+ if (process.platform === "win32") {
666+ const python = resolvePythonForNodeGyp();
667+ if (python) {
668+ buildEnv.PYTHON = python;
669+ buildEnv.npm_config_python = python;
670+ }
671+ buildEnv.npm_config_msvs_version = buildEnv.npm_config_msvs_version ?? "2022";
672+ buildEnv.GYP_MSVS_VERSION = buildEnv.GYP_MSVS_VERSION ?? "2022";
673+ }
674+
600675 yield* Effect.log(
601676 `[desktop-artifact] Building ${options.platform}/${options.target} (arch=${options.arch}, version=${appVersion})...`,
602677 );
scripts/lib/brand-assets.ts+57−0
@@ -0,0 +1,57 @@
1+export const BRAND_ASSET_PATHS = {
2+ productionMacIconPng: "assets/prod/black-macos-1024.png",
3+ productionLinuxIconPng: "assets/prod/black-universal-1024.png",
4+ productionWindowsIconIco: "assets/prod/t3-black-windows.ico",
5+ productionWebFaviconIco: "assets/prod/t3-black-web-favicon.ico",
6+ productionWebFavicon16Png: "assets/prod/t3-black-web-favicon-16x16.png",
7+ productionWebFavicon32Png: "assets/prod/t3-black-web-favicon-32x32.png",
8+ productionWebAppleTouchIconPng: "assets/prod/t3-black-web-apple-touch-180.png",
9+ developmentWindowsIconIco: "assets/dev/blueprint-windows.ico",
10+ developmentWebFaviconIco: "assets/dev/blueprint-web-favicon.ico",
11+ developmentWebFavicon16Png: "assets/dev/blueprint-web-favicon-16x16.png",
12+ developmentWebFavicon32Png: "assets/dev/blueprint-web-favicon-32x32.png",
13+ developmentWebAppleTouchIconPng: "assets/dev/blueprint-web-apple-touch-180.png",
14+} as const;
15+
16+export interface IconOverride {
17+ readonly sourceRelativePath: string;
18+ readonly targetRelativePath: string;
19+}
20+
21+export const DEVELOPMENT_ICON_OVERRIDES: ReadonlyArray<IconOverride> = [
22+ {
23+ sourceRelativePath: BRAND_ASSET_PATHS.developmentWebFaviconIco,
24+ targetRelativePath: "dist/client/favicon.ico",
25+ },
26+ {
27+ sourceRelativePath: BRAND_ASSET_PATHS.developmentWebFavicon16Png,
28+ targetRelativePath: "dist/client/favicon-16x16.png",
29+ },
30+ {
31+ sourceRelativePath: BRAND_ASSET_PATHS.developmentWebFavicon32Png,
32+ targetRelativePath: "dist/client/favicon-32x32.png",
33+ },
34+ {
35+ sourceRelativePath: BRAND_ASSET_PATHS.developmentWebAppleTouchIconPng,
36+ targetRelativePath: "dist/client/apple-touch-icon.png",
37+ },
38+];
39+
40+export const PUBLISH_ICON_OVERRIDES: ReadonlyArray<IconOverride> = [
41+ {
42+ sourceRelativePath: BRAND_ASSET_PATHS.productionWebFaviconIco,
43+ targetRelativePath: "dist/client/favicon.ico",
44+ },
45+ {
46+ sourceRelativePath: BRAND_ASSET_PATHS.productionWebFavicon16Png,
47+ targetRelativePath: "dist/client/favicon-16x16.png",
48+ },
49+ {
50+ sourceRelativePath: BRAND_ASSET_PATHS.productionWebFavicon32Png,
51+ targetRelativePath: "dist/client/favicon-32x32.png",
52+ },
53+ {
54+ sourceRelativePath: BRAND_ASSET_PATHS.productionWebAppleTouchIconPng,
55+ targetRelativePath: "dist/client/apple-touch-icon.png",
56+ },
57+];