* 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 | 1 | #!/usr/bin/env node |
| 2 | 2 | |
| 3 | 3 | import { spawnSync } from "node:child_process"; |
| 4 | +import { existsSync } from "node:fs"; | |
| 5 | +import { join } from "node:path"; | |
| 4 | 6 | |
| 5 | 7 | import rootPackageJson from "../package.json" with { type: "json" }; |
| 6 | 8 | import desktopPackageJson from "../apps/desktop/package.json" with { type: "json" }; |
| 7 | 9 | import serverPackageJson from "../apps/server/package.json" with { type: "json" }; |
| 8 | 10 | |
| 11 | +import { BRAND_ASSET_PATHS } from "./lib/brand-assets.ts"; | |
| 9 | 12 | import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts"; |
| 10 | 13 | |
| 11 | 14 | import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; |
| @@ -20,8 +23,18 @@ const BuildArch = Schema.Literals(["arm64", "x64", "universal"]); · 8 unmodified lines | ||
| 20 | 23 | const RepoRoot = Effect.service(Path.Path).pipe( |
| 21 | 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) => | |
| 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), | |
| 25 | 38 | ); |
| 26 | 39 | const encodeJsonString = Schema.encodeEffect(Schema.UnknownFromJsonString); |
| 27 | 40 | |
| @@ -104,6 +117,39 @@ function resolveGitCommitHash(repoRoot: string): string { · 76 unmodified lines | ||
| 104 | 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 | 153 | interface ResolvedBuildOptions { |
| 108 | 154 | readonly platform: typeof BuildPlatform.Type; |
| 109 | 155 | readonly target: string; |
| @@ -261,7 +307,7 @@ function stageMacIcons(stageResourcesDir: string, verbose: boolean) { · 151 unmodified lines | ||
| 261 | 307 | return Effect.gen(function* () { |
| 262 | 308 | const fs = yield* FileSystem.FileSystem; |
| 263 | 309 | const path = yield* Path.Path; |
| 264 | − const iconSource = yield* IconSource; | |
| 310 | + const iconSource = yield* ProductionMacIconSource; | |
| 265 | 311 | if (!(yield* fs.exists(iconSource))) { |
| 266 | 312 | return yield* new BuildScriptError({ |
| 267 | 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 | 366 | function validateBundledClientAssets(clientDir: string) { |
| 289 | 367 | return Effect.gen(function* () { |
| 290 | 368 | const fs = yield* FileSystem.FileSystem; |
| @@ -416,31 +494,18 @@ const assertPlatformBuildResources = Effect.fn("assertPlatformBuildResources")(f · 125 unmodified lines | ||
| 416 | 494 | stageResourcesDir: string, |
| 417 | 495 | verbose: boolean, |
| 418 | 496 | ) { |
| 419 | − const fs = yield* FileSystem.FileSystem; | |
| 420 | − const path = yield* Path.Path; | |
| 421 | − | |
| 422 | 497 | if (platform === "mac") { |
| 423 | 498 | yield* stageMacIcons(stageResourcesDir, verbose); |
| 424 | 499 | return; |
| 425 | 500 | } |
| 426 | 501 | |
| 427 | 502 | 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); | |
| 434 | 504 | return; |
| 435 | 505 | } |
| 436 | 506 | |
| 437 | 507 | 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); | |
| 444 | 509 | } |
| 445 | 510 | }); |
| 446 | 511 | |
| @@ -597,6 +662,16 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( · 150 unmodified lines | ||
| 597 | 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 | 675 | yield* Effect.log( |
| 601 | 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 | +]; | |