Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/web/src/hostedPairing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,16 @@ describe("hostedPairing", () => {
vi.stubEnv("VITE_HTTP_URL", "https://backend.example.com");
expect(isHostedStaticApp(new URL("https://preview.t3.codes/"))).toBe(false);
});

it("detects hosted channel aliases as static apps", () => {
vi.stubEnv("VITE_HOSTED_APP_URL", "https://app.t3.codes");
vi.stubEnv("VITE_HOSTED_APP_CHANNEL", "nightly");
vi.stubEnv("VITE_HTTP_URL", "");
vi.stubEnv("VITE_WS_URL", "");

expect(isHostedStaticApp(new URL("https://nightly.app.t3.codes/"))).toBe(true);

vi.stubEnv("VITE_HTTP_URL", "https://backend.example.com");
expect(isHostedStaticApp(new URL("https://nightly.app.t3.codes/"))).toBe(false);
});
});
9 changes: 9 additions & 0 deletions apps/web/src/hostedPairing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ function configuredBackendUrl(): string {
return import.meta.env.VITE_HTTP_URL?.trim() || import.meta.env.VITE_WS_URL?.trim() || "";
}

function configuredHostedAppChannel(): HostedAppChannel | null {
const channel = import.meta.env.VITE_HOSTED_APP_CHANNEL?.trim().toLowerCase();
return channel === "latest" || channel === "nightly" ? channel : null;
}

function originFromUrl(value: string): string | null {
try {
return new URL(value).origin;
Expand All @@ -31,6 +36,10 @@ export function isHostedStaticApp(url: URL = new URL(window.location.href)): boo
return false;
}

if (configuredHostedAppChannel()) {
return true;
}

const hostedOrigin = originFromUrl(configuredHostedAppUrl());
return hostedOrigin !== null && url.origin === hostedOrigin;
}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/vercel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function channelCookie(channel: "latest" | "nightly"): string {

export const config: VercelConfig = {
buildCommand:
"turbo build --filter @t3tools/web && bun ../../scripts/apply-web-brand-assets.ts production",
'turbo build --filter @t3tools/web && bun ../../scripts/apply-web-brand-assets.ts --channel "${VITE_HOSTED_APP_CHANNEL:-latest}"',
git: {
deploymentEnabled: false,
},
Expand Down
24 changes: 20 additions & 4 deletions scripts/apply-web-brand-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Option from "effect/Option";
import * as Path from "effect/Path";
import { Argument, Command } from "effect/unstable/cli";
import { resolveWebIconOverrides, type WebAssetBrand } from "./lib/brand-assets.ts";
import { Argument, Command, Flag } from "effect/unstable/cli";
import {
resolveWebAssetBrandForChannel,
resolveWebIconOverrides,
WEB_ASSET_CHANNELS,
type WebAssetBrand,
} from "./lib/brand-assets.ts";

const WEB_ASSET_BRANDS = [
"development",
"nightly",
"production",
] as const satisfies ReadonlyArray<WebAssetBrand>;

Expand Down Expand Up @@ -38,15 +44,25 @@ export const applyWebBrandAssetsCommand = Command.make(
{
brand: Argument.choice("brand", WEB_ASSET_BRANDS).pipe(
Argument.withDescription("Asset brand to copy into the hosted web output directory."),
Argument.optional,
),
channel: Flag.choice("channel", WEB_ASSET_CHANNELS).pipe(
Flag.withDescription("Hosted release channel to map to a web asset brand."),
Flag.optional,
),
targetDirectory: Argument.string("target-directory").pipe(
Argument.withDescription("Output directory that contains the hosted web build assets."),
Argument.optional,
),
},
({ brand, targetDirectory }) =>
({ brand, channel, targetDirectory }) =>
applyWebBrandAssets(
brand,
Option.getOrElse(brand, () =>
Option.match(channel, {
onNone: () => "production" as const,
onSome: resolveWebAssetBrandForChannel,
}),
),
Option.getOrElse(targetDirectory, () => "apps/web/dist"),
),
).pipe(Command.withDescription("Copy web brand assets into a built hosted web app."));
Expand Down
13 changes: 13 additions & 0 deletions scripts/lib/brand-assets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BRAND_ASSET_PATHS,
DEVELOPMENT_ICON_OVERRIDES,
PUBLISH_ICON_OVERRIDES,
resolveWebAssetBrandForChannel,
resolveWebIconOverrides,
} from "./brand-assets.ts";

Expand Down Expand Up @@ -42,4 +43,16 @@ describe("brand-assets", () => {
targetRelativePath: "apps/web/dist/apple-touch-icon.png",
});
});

it("maps hosted nightly web assets to nightly icons", () => {
expect(resolveWebIconOverrides("nightly", "apps/web/dist")).toContainEqual({
sourceRelativePath: BRAND_ASSET_PATHS.nightlyWebFaviconIco,
targetRelativePath: "apps/web/dist/favicon.ico",
});
});

it("maps hosted release channels to web asset brands", () => {
expect(resolveWebAssetBrandForChannel("latest")).toBe("production");
expect(resolveWebAssetBrandForChannel("nightly")).toBe("nightly");
});
});
20 changes: 19 additions & 1 deletion scripts/lib/brand-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export const BRAND_ASSET_PATHS = {
nightlyMacIconPng: "assets/nightly/blueprint-macos-1024.png",
nightlyLinuxIconPng: "assets/nightly/blueprint-universal-1024.png",
nightlyWindowsIconIco: "assets/nightly/blueprint-windows.ico",
nightlyWebFaviconIco: "assets/nightly/blueprint-web-favicon.ico",
nightlyWebFavicon16Png: "assets/nightly/blueprint-web-favicon-16x16.png",
nightlyWebFavicon32Png: "assets/nightly/blueprint-web-favicon-32x32.png",
nightlyWebAppleTouchIconPng: "assets/nightly/blueprint-web-apple-touch-180.png",

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

export type WebAssetBrand = "development" | "production";
export type WebAssetBrand = "development" | "nightly" | "production";

export const WEB_ASSET_CHANNELS = ["latest", "nightly"] as const;

export type WebAssetChannel = (typeof WEB_ASSET_CHANNELS)[number];

export function resolveWebAssetBrandForChannel(channel: WebAssetChannel): WebAssetBrand {
return channel === "nightly" ? "nightly" : "production";
}

export interface IconOverride {
readonly sourceRelativePath: string;
Expand All @@ -40,6 +52,12 @@ const WEB_ICON_SOURCE_PATHS_BY_BRAND = {
favicon32Png: BRAND_ASSET_PATHS.developmentWebFavicon32Png,
appleTouchIconPng: BRAND_ASSET_PATHS.developmentWebAppleTouchIconPng,
},
nightly: {
faviconIco: BRAND_ASSET_PATHS.nightlyWebFaviconIco,
favicon16Png: BRAND_ASSET_PATHS.nightlyWebFavicon16Png,
favicon32Png: BRAND_ASSET_PATHS.nightlyWebFavicon32Png,
appleTouchIconPng: BRAND_ASSET_PATHS.nightlyWebAppleTouchIconPng,
},
production: {
faviconIco: BRAND_ASSET_PATHS.productionWebFaviconIco,
favicon16Png: BRAND_ASSET_PATHS.productionWebFavicon16Png,
Expand Down
Loading