authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-05-11 01:41:32-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-05-11 10:41:32+02:00
loge64c19f1af672e4f1737d81fbe83906de3547f03
treeb118fbbcd2d140547ec93b1c3a88bc40dd06161c
parent6ab8f93a23465d0d92e55d2a9173307fa2295d9c
signature Signed by PGP key B5690EEEBB952194

Alias stable releases to the router domain (#2636)


6 files changed, 41 insertions(+), 16 deletions(-)

.github/workflows/release.yml+10
......@@ -625,6 +625,9 @@ jobs:
625625 router_url="${T3CODE_WEB_ROUTER_URL:-https://app.t3.codes}"
626626 latest_domain="${T3CODE_WEB_LATEST_DOMAIN:-latest.app.t3.codes}"
627627 nightly_domain="${T3CODE_WEB_NIGHTLY_DOMAIN:-nightly.app.t3.codes}"
628 router_domain="${router_url#http://}"
629 router_domain="${router_domain#https://}"
630 router_domain="${router_domain%%/*}"
628631
629632 if [[ "${{ needs.preflight.outputs.release_channel }}" == "stable" ]]; then
630633 channel_domain="$latest_domain"
......@@ -655,6 +658,13 @@ jobs:
655658 --token "$VERCEL_TOKEN" \
656659 "${vercel_scope_args[@]}"
657660
661 if [[ "$channel_name" == "latest" && -n "$router_domain" && "$router_domain" != "$channel_domain" ]]; then
662 echo "Aliasing $deployment_url to router domain $router_domain."
663 bunx vercel@53.1.1 alias set "$deployment_url" "$router_domain" \
664 --token "$VERCEL_TOKEN" \
665 "${vercel_scope_args[@]}"
666 fi
667
658668 finalize:
659669 name: Finalize release
660670 if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'stable' }}
apps/web/src/branding.test.ts+2
......@@ -42,6 +42,8 @@ describe("branding", () => {
4242
4343 expect(branding.HOSTED_APP_CHANNEL).toBe("nightly");
4444 expect(branding.HOSTED_APP_CHANNEL_LABEL).toBe("Nightly");
45 expect(branding.APP_STAGE_LABEL).toBe("Nightly");
46 expect(branding.APP_DISPLAY_NAME).toBe("T3 Code (Nightly)");
4547 });
4648
4749 it("ignores unknown hosted app channels", async () => {
apps/web/src/branding.ts+7-5
......@@ -11,13 +11,15 @@ function readInjectedDesktopAppBranding(): DesktopAppBranding | null {
1111const injectedDesktopAppBranding = readInjectedDesktopAppBranding();
1212const hostedAppChannel = import.meta.env.VITE_HOSTED_APP_CHANNEL?.trim().toLowerCase();
1313
14export const HOSTED_APP_CHANNEL =
15 hostedAppChannel === "latest" || hostedAppChannel === "nightly" ? hostedAppChannel : null;
16export const HOSTED_APP_CHANNEL_LABEL =
17 HOSTED_APP_CHANNEL === "nightly" ? "Nightly" : HOSTED_APP_CHANNEL === "latest" ? "Latest" : null;
1418export const APP_BASE_NAME = injectedDesktopAppBranding?.baseName ?? "T3 Code";
1519export const APP_STAGE_LABEL =
16 injectedDesktopAppBranding?.stageLabel ?? (import.meta.env.DEV ? "Dev" : "Alpha");
20 injectedDesktopAppBranding?.stageLabel ??
21 HOSTED_APP_CHANNEL_LABEL ??
22 (import.meta.env.DEV ? "Dev" : "Alpha");
1723export const APP_DISPLAY_NAME =
1824 injectedDesktopAppBranding?.displayName ?? `${APP_BASE_NAME} (${APP_STAGE_LABEL})`;
1925export const APP_VERSION = import.meta.env.APP_VERSION || "0.0.0";
20export const HOSTED_APP_CHANNEL =
21 hostedAppChannel === "latest" || hostedAppChannel === "nightly" ? hostedAppChannel : null;
22export const HOSTED_APP_CHANNEL_LABEL =
23 HOSTED_APP_CHANNEL === "nightly" ? "Nightly" : HOSTED_APP_CHANNEL === "latest" ? "Latest" : null;
apps/web/src/components/settings/SettingsPanels.tsx+1-1
......@@ -892,7 +892,7 @@ export function GeneralSettingsPanel() {
892892 </SettingsSection>
893893
894894 <SettingsSection title="About">
895 {isElectron ? (
895 {isElectron || HOSTED_APP_CHANNEL ? (
896896 <AboutVersionSection />
897897 ) : (
898898 <SettingsRow
apps/web/vercel.ts+10-1
......@@ -1,9 +1,16 @@
1import { matchers, routes, type VercelConfig } from "@vercel/config/v1";
1import { matchers, routes, type Transform, type VercelConfig } from "@vercel/config/v1";
22
33const ROUTER_HOST = "app.t3.codes";
44const HOSTED_WEB_CHANNEL_COOKIE = "t3code_web_channel";
55const LATEST_ORIGIN = "https://latest.app.t3.codes";
66const NIGHTLY_ORIGIN = "https://nightly.app.t3.codes";
7const CLEAN_CHANNEL_QUERY_TRANSFORMS = [
8 {
9 type: "request.query",
10 op: "delete",
11 target: { key: "channel" },
12 },
13] satisfies Transform[];
714
815function channelCookie(channel: "latest" | "nightly"): string {
916 return [
......@@ -28,6 +35,7 @@ export const config: VercelConfig = {
2835 {
2936 src: "/__t3code/channel",
3037 has: [matchers.query("channel", "nightly")],
38 transforms: CLEAN_CHANNEL_QUERY_TRANSFORMS,
3139 headers: {
3240 Location: "/",
3341 "Set-Cookie": channelCookie("nightly"),
......@@ -36,6 +44,7 @@ export const config: VercelConfig = {
3644 },
3745 {
3846 src: "/__t3code/channel",
47 transforms: CLEAN_CHANNEL_QUERY_TRANSFORMS,
3948 headers: {
4049 Location: "/",
4150 "Set-Cookie": channelCookie("latest"),
docs/release.md+11-9
......@@ -51,7 +51,7 @@ Optional GitHub Actions variables:
5151
5252Required Vercel domains:
5353
54- `app.t3.codes`: the stable router domain users open.
54- `app.t3.codes`: the router domain users open, updated by stable releases.
5555- `latest.app.t3.codes`: channel alias updated by stable releases.
5656- `nightly.app.t3.codes`: channel alias updated by nightly releases.
5757
......@@ -62,11 +62,13 @@ visiting `/__t3code/channel?channel=latest` or
6262the matching channel alias.
6363
6464The release deploy job rewrites release package versions before upload so the
65hosted app's About panel renders the release version. It also passes
66`VITE_HOSTED_APP_CHANNEL=latest|nightly`, which renders the hosted update track
67selector in the About panel. Changing the selector navigates through
68`/__t3code/channel` on the router domain so the user's channel cookie is updated
69before redirecting to the hosted app root.
65hosted app's About panel renders the release version. Stable deploys alias the
66same deployment to both the `latest` channel and the router domain so the router
67rules stay current. Nightly deploys only alias the `nightly` channel. The job
68also passes `VITE_HOSTED_APP_CHANNEL=latest|nightly`, which renders the hosted
69update track selector in the About panel. Changing the selector navigates
70through `/__t3code/channel` on the router domain so the user's channel cookie is
71updated before redirecting to the hosted app root.
7072
7173One-time Vercel dashboard setup:
7274
......@@ -75,9 +77,9 @@ One-time Vercel dashboard setup:
75773. Disable automatic Git deployments in the dashboard if desired; the committed
7678 `vercel.ts` setting is the source-of-truth, but disconnecting Git in the
7779 dashboard is also safe.
784. Promote or alias one deployment containing the router rules in `apps/web/vercel.ts` to
79 `app.t3.codes` once. Future release jobs should only update the channel
80 aliases.
804. Run one stable release deployment, or manually alias the current stable
81 deployment, so `app.t3.codes` points at a deployment containing the router
82 rules in `apps/web/vercel.ts`. Future stable releases keep this alias current.
8183
8284## Nightly builds
8385