Skip to content

Commit 3c32bc8

Browse files
juliusmarmingeJulius Marminge
andauthored
[codex] Fix hosted channel bootstrap (#2613)
Co-authored-by: Julius Marminge <julius@macmini.local>
1 parent 6efdf67 commit 3c32bc8

6 files changed

Lines changed: 74 additions & 6 deletions

File tree

apps/web/src/hostedPairing.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,16 @@ describe("hostedPairing", () => {
7979
vi.stubEnv("VITE_HTTP_URL", "https://backend.example.com");
8080
expect(isHostedStaticApp(new URL("https://preview.t3.codes/"))).toBe(false);
8181
});
82+
83+
it("detects hosted channel aliases as static apps", () => {
84+
vi.stubEnv("VITE_HOSTED_APP_URL", "https://app.t3.codes");
85+
vi.stubEnv("VITE_HOSTED_APP_CHANNEL", "nightly");
86+
vi.stubEnv("VITE_HTTP_URL", "");
87+
vi.stubEnv("VITE_WS_URL", "");
88+
89+
expect(isHostedStaticApp(new URL("https://nightly.app.t3.codes/"))).toBe(true);
90+
91+
vi.stubEnv("VITE_HTTP_URL", "https://backend.example.com");
92+
expect(isHostedStaticApp(new URL("https://nightly.app.t3.codes/"))).toBe(false);
93+
});
8294
});

apps/web/src/hostedPairing.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ function configuredBackendUrl(): string {
1818
return import.meta.env.VITE_HTTP_URL?.trim() || import.meta.env.VITE_WS_URL?.trim() || "";
1919
}
2020

21+
function configuredHostedAppChannel(): HostedAppChannel | null {
22+
const channel = import.meta.env.VITE_HOSTED_APP_CHANNEL?.trim().toLowerCase();
23+
return channel === "latest" || channel === "nightly" ? channel : null;
24+
}
25+
2126
function originFromUrl(value: string): string | null {
2227
try {
2328
return new URL(value).origin;
@@ -31,6 +36,10 @@ export function isHostedStaticApp(url: URL = new URL(window.location.href)): boo
3136
return false;
3237
}
3338

39+
if (configuredHostedAppChannel()) {
40+
return true;
41+
}
42+
3443
const hostedOrigin = originFromUrl(configuredHostedAppUrl());
3544
return hostedOrigin !== null && url.origin === hostedOrigin;
3645
}

apps/web/vercel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function channelCookie(channel: "latest" | "nightly"): string {
1818

1919
export const config: VercelConfig = {
2020
buildCommand:
21-
"turbo build --filter @t3tools/web && bun ../../scripts/apply-web-brand-assets.ts production",
21+
'turbo build --filter @t3tools/web && bun ../../scripts/apply-web-brand-assets.ts --channel "${VITE_HOSTED_APP_CHANNEL:-latest}"',
2222
git: {
2323
deploymentEnabled: false,
2424
},

scripts/apply-web-brand-assets.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ import * as Effect from "effect/Effect";
66
import * as FileSystem from "effect/FileSystem";
77
import * as Option from "effect/Option";
88
import * as Path from "effect/Path";
9-
import { Argument, Command } from "effect/unstable/cli";
10-
import { resolveWebIconOverrides, type WebAssetBrand } from "./lib/brand-assets.ts";
9+
import { Argument, Command, Flag } from "effect/unstable/cli";
10+
import {
11+
resolveWebAssetBrandForChannel,
12+
resolveWebIconOverrides,
13+
WEB_ASSET_CHANNELS,
14+
type WebAssetBrand,
15+
} from "./lib/brand-assets.ts";
1116

1217
const WEB_ASSET_BRANDS = [
1318
"development",
19+
"nightly",
1420
"production",
1521
] as const satisfies ReadonlyArray<WebAssetBrand>;
1622

@@ -38,15 +44,25 @@ export const applyWebBrandAssetsCommand = Command.make(
3844
{
3945
brand: Argument.choice("brand", WEB_ASSET_BRANDS).pipe(
4046
Argument.withDescription("Asset brand to copy into the hosted web output directory."),
47+
Argument.optional,
48+
),
49+
channel: Flag.choice("channel", WEB_ASSET_CHANNELS).pipe(
50+
Flag.withDescription("Hosted release channel to map to a web asset brand."),
51+
Flag.optional,
4152
),
4253
targetDirectory: Argument.string("target-directory").pipe(
4354
Argument.withDescription("Output directory that contains the hosted web build assets."),
4455
Argument.optional,
4556
),
4657
},
47-
({ brand, targetDirectory }) =>
58+
({ brand, channel, targetDirectory }) =>
4859
applyWebBrandAssets(
49-
brand,
60+
Option.getOrElse(brand, () =>
61+
Option.match(channel, {
62+
onNone: () => "production" as const,
63+
onSome: resolveWebAssetBrandForChannel,
64+
}),
65+
),
5066
Option.getOrElse(targetDirectory, () => "apps/web/dist"),
5167
),
5268
).pipe(Command.withDescription("Copy web brand assets into a built hosted web app."));

scripts/lib/brand-assets.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
BRAND_ASSET_PATHS,
55
DEVELOPMENT_ICON_OVERRIDES,
66
PUBLISH_ICON_OVERRIDES,
7+
resolveWebAssetBrandForChannel,
78
resolveWebIconOverrides,
89
} from "./brand-assets.ts";
910

@@ -42,4 +43,16 @@ describe("brand-assets", () => {
4243
targetRelativePath: "apps/web/dist/apple-touch-icon.png",
4344
});
4445
});
46+
47+
it("maps hosted nightly web assets to nightly icons", () => {
48+
expect(resolveWebIconOverrides("nightly", "apps/web/dist")).toContainEqual({
49+
sourceRelativePath: BRAND_ASSET_PATHS.nightlyWebFaviconIco,
50+
targetRelativePath: "apps/web/dist/favicon.ico",
51+
});
52+
});
53+
54+
it("maps hosted release channels to web asset brands", () => {
55+
expect(resolveWebAssetBrandForChannel("latest")).toBe("production");
56+
expect(resolveWebAssetBrandForChannel("nightly")).toBe("nightly");
57+
});
4558
});

scripts/lib/brand-assets.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export const BRAND_ASSET_PATHS = {
1010
nightlyMacIconPng: "assets/nightly/blueprint-macos-1024.png",
1111
nightlyLinuxIconPng: "assets/nightly/blueprint-universal-1024.png",
1212
nightlyWindowsIconIco: "assets/nightly/blueprint-windows.ico",
13+
nightlyWebFaviconIco: "assets/nightly/blueprint-web-favicon.ico",
14+
nightlyWebFavicon16Png: "assets/nightly/blueprint-web-favicon-16x16.png",
15+
nightlyWebFavicon32Png: "assets/nightly/blueprint-web-favicon-32x32.png",
16+
nightlyWebAppleTouchIconPng: "assets/nightly/blueprint-web-apple-touch-180.png",
1317

1418
developmentDesktopIconPng: "assets/dev/blueprint-macos-1024.png",
1519
developmentWindowsIconIco: "assets/dev/blueprint-windows.ico",
@@ -19,7 +23,15 @@ export const BRAND_ASSET_PATHS = {
1923
developmentWebAppleTouchIconPng: "assets/dev/blueprint-web-apple-touch-180.png",
2024
} as const;
2125

22-
export type WebAssetBrand = "development" | "production";
26+
export type WebAssetBrand = "development" | "nightly" | "production";
27+
28+
export const WEB_ASSET_CHANNELS = ["latest", "nightly"] as const;
29+
30+
export type WebAssetChannel = (typeof WEB_ASSET_CHANNELS)[number];
31+
32+
export function resolveWebAssetBrandForChannel(channel: WebAssetChannel): WebAssetBrand {
33+
return channel === "nightly" ? "nightly" : "production";
34+
}
2335

2436
export interface IconOverride {
2537
readonly sourceRelativePath: string;
@@ -40,6 +52,12 @@ const WEB_ICON_SOURCE_PATHS_BY_BRAND = {
4052
favicon32Png: BRAND_ASSET_PATHS.developmentWebFavicon32Png,
4153
appleTouchIconPng: BRAND_ASSET_PATHS.developmentWebAppleTouchIconPng,
4254
},
55+
nightly: {
56+
faviconIco: BRAND_ASSET_PATHS.nightlyWebFaviconIco,
57+
favicon16Png: BRAND_ASSET_PATHS.nightlyWebFavicon16Png,
58+
favicon32Png: BRAND_ASSET_PATHS.nightlyWebFavicon32Png,
59+
appleTouchIconPng: BRAND_ASSET_PATHS.nightlyWebAppleTouchIconPng,
60+
},
4361
production: {
4462
faviconIco: BRAND_ASSET_PATHS.productionWebFaviconIco,
4563
favicon16Png: BRAND_ASSET_PATHS.productionWebFavicon16Png,

0 commit comments

Comments
 (0)