* 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> ---------
Showing the changes to scripts (2 of 42 files). Show all
Unified · Split
| @@ -1,11 +1,14 @@ | |||
| 1 | #!/usr/bin/env node | 1 | #!/usr/bin/env node |
| 2 | 2 | ||
| 3 | import { spawnSync } from "node:child_process"; | 3 | import { spawnSync } from "node:child_process"; |
| 4 | import { existsSync } from "node:fs"; | ||
| 5 | import { join } from "node:path"; | ||
| 4 | 6 | ||
| 5 | import rootPackageJson from "../package.json" with { type: "json" }; | 7 | import rootPackageJson from "../package.json" with { type: "json" }; |
| 6 | import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" }; | 8 | import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" }; |
| 7 | import serverPackageJson from "../apps/server/package.json" with { type: "json" }; | 9 | import serverPackageJson from "../apps/server/package.json" with { type: "json" }; |
| 8 | 10 | ||
| 11 | import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts"; | ||
| 9 | import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts"; | 12 | import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts"; |
| 10 | 13 | ||
| 11 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; | 14 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| @@ -20,8 +23,18 @@ const BuildArch = Schema.Literals(["arm64", "x64", "universal"]); · 8 unmodified lines | |||
| 20 | const RepoRoot = Effect.service(Path.Path).pipe( | 23 | const 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 | ); |
| 23 | const IconSource = Effect.zipWith(RepoRoot, Effect.service(Path.Path), (repoRoot, path) => | 26 | const 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 | ); | ||
| 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), | ||
| 25 | ); | 38 | ); |
| 26 | const encodeJsonString = Schema.encodeEffect(Schema.UnknownFromJsonString); | 39 | const 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 | ||
| 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 | |||
| 107 | interface ResolvedBuildOptions { | 153 | interface 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 | ||
| 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 | |||
| 288 | function validateBundledClientAssets(clientDir: string) { | 366 | function 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 | ); |
| @@ -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 | ]; | ||