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

Showing the changes to scripts (2 of 42 files). Show all

Unified · Split

2 files
  1. scripts/build-desktop-artifact.ts +93 −18
  2. scripts/lib/brand-assets.ts +57 −0
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+];