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 @@
1#!/usr/bin/env node1#!/usr/bin/env node
2 2
3import { spawnSync } from "node:child_process";3import { spawnSync } from "node:child_process";
4import { existsSync } from "node:fs";
5import { join } from "node:path";
4 6
5import rootPackageJson from "../package.json" with { type: "json" };7import rootPackageJson from "../package.json" with { type: "json" };
6import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" };8import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" };
7import serverPackageJson from "../apps/server/package.json" with { type: "json" };9import serverPackageJson from "../apps/server/package.json" with { type: "json" };
8 10
11import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts";
9import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts";12import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts";
10 13
11import * as NodeRuntime from "@effect/platform-node/NodeRuntime";14import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
@@ -20,8 +23,18 @@ const BuildArch = Schema.Literals(["arm64", "x64", "universal"]); · 8 unmodified lines
20const RepoRoot = Effect.service(Path.Path).pipe(23const RepoRoot = Effect.service(Path.Path).pipe(
21 Effect.flatMap((path) => path.fromFileUrl(new URL("..", import.meta.url))),24 Effect.flatMap((path) => path.fromFileUrl(new URL("..", import.meta.url))),
22);25);
23const IconSource = Effect.zipWith(RepoRoot, Effect.service(Path.Path), (repoRoot, path) =>26const ProductionMacIconSource = Effect.zipWith(RepoRoot, Effect.service(Path.Path), (repoRoot, path) =>
24 path.join(repoRoot, "assets/macos-icon-1024.png"),27 path.join(repoRoot, BRAND_ASSET_PATHS.productionMacIconPng),
28);
29const ProductionLinuxIconSource = Effect.zipWith(
30 RepoRoot,
31 Effect.service(Path.Path),
32 (repoRoot, path) => path.join(repoRoot, BRAND_ASSET_PATHS.productionLinuxIconPng),
33);
34const ProductionWindowsIconSource = Effect.zipWith(
35 RepoRoot,
36 Effect.service(Path.Path),
37 (repoRoot, path) => path.join(repoRoot, BRAND_ASSET_PATHS.productionWindowsIconIco),
25);38);
26const encodeJsonString = Schema.encodeEffect(Schema.UnknownFromJsonString);39const encodeJsonString = Schema.encodeEffect(Schema.UnknownFromJsonString);
27 40
@@ -104,6 +117,39 @@ function resolveGitCommitHash(repoRoot: string): string { · 76 unmodified lines
104 return hash.toLowerCase();117 return hash.toLowerCase();
105}118}
106 119
120function 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
107interface ResolvedBuildOptions {153interface ResolvedBuildOptions {
108 readonly platform: typeof BuildPlatform.Type;154 readonly platform: typeof BuildPlatform.Type;
109 readonly target: string;155 readonly target: string;
@@ -261,7 +307,7 @@ function stageMacIcons(stageResourcesDir: string, verbose: boolean) { · 151 unmodified lines
261 return Effect.gen(function* () {307 return Effect.gen(function* () {
262 const fs = yield* FileSystem.FileSystem;308 const fs = yield* FileSystem.FileSystem;
263 const path = yield* Path.Path;309 const path = yield* Path.Path;
264 const iconSource = yield* IconSource;310 const iconSource = yield* ProductionMacIconSource;
265 if (!(yield* fs.exists(iconSource))) {311 if (!(yield* fs.exists(iconSource))) {
266 return yield* new BuildScriptError({312 return yield* new BuildScriptError({
267 message: `Production icon source is missing at ${iconSource}`,313 message: `Production icon source is missing at ${iconSource}`,
@@ -285,6 +331,38 @@ function stageMacIcons(stageResourcesDir: string, verbose: boolean) { · 17 unmodified lines
285 });331 });
286}332}
287 333
334function 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
350function 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
288function validateBundledClientAssets(clientDir: string) {366function validateBundledClientAssets(clientDir: string) {
289 return Effect.gen(function* () {367 return Effect.gen(function* () {
290 const fs = yield* FileSystem.FileSystem;368 const fs = yield* FileSystem.FileSystem;
@@ -416,31 +494,18 @@ const assertPlatformBuildResources = Effect.fn("assertPlatformBuildResources")(f · 125 unmodified lines
416 stageResourcesDir: string,494 stageResourcesDir: string,
417 verbose: boolean,495 verbose: boolean,
418) {496) {
419 const fs = yield* FileSystem.FileSystem;
420 const path = yield* Path.Path;
421
422 if (platform === "mac") {497 if (platform === "mac") {
423 yield* stageMacIcons(stageResourcesDir, verbose);498 yield* stageMacIcons(stageResourcesDir, verbose);
424 return;499 return;
425 }500 }
426 501
427 if (platform === "linux") {502 if (platform === "linux") {
428 const iconPath = path.join(stageResourcesDir, "icon.png");503 yield* stageLinuxIcons(stageResourcesDir);
429 if (!(yield* fs.exists(iconPath))) {
430 return yield* new BuildScriptError({
431 message: `Missing Linux icon at ${iconPath}`,
432 });
433 }
434 return;504 return;
435 }505 }
436 506
437 if (platform === "win") {507 if (platform === "win") {
438 const iconPath = path.join(stageResourcesDir, "icon.ico");508 yield* stageWindowsIcons(stageResourcesDir);
439 if (!(yield* fs.exists(iconPath))) {
440 return yield* new BuildScriptError({
441 message: `Missing Windows icon at ${iconPath}`,
442 });
443 }
444 }509 }
445});510});
446 511
@@ -597,6 +662,16 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( · 150 unmodified lines
597 delete buildEnv.APPLE_API_ISSUER;662 delete buildEnv.APPLE_API_ISSUER;
598 }663 }
599 664
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
600 yield* Effect.log(675 yield* Effect.log(
601 `[desktop-artifact] Building ${options.platform}/${options.target} (arch=${options.arch}, version=${appVersion})...`,676 `[desktop-artifact] Building ${options.platform}/${options.target} (arch=${options.arch}, version=${appVersion})...`,
602 );677 );
scripts/lib/brand-assets.ts+57−0
@@ -0,0 +1,57 @@
1export 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
16export interface IconOverride {
17 readonly sourceRelativePath: string;
18 readonly targetRelativePath: string;
19}
20
21export 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
40export 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];