| author | |
| committer | |
| log | e64c19f1af672e4f1737d81fbe83906de3547f03 |
| tree | b118fbbcd2d140547ec93b1c3a88bc40dd06161c |
| parent | 6ab8f93a23465d0d92e55d2a9173307fa2295d9c |
| signature | Signed by PGP key B5690EEEBB952194 |
6 files changed, 41 insertions(+), 16 deletions(-)
.github/workflows/release.yml+10| ... | @@ -625,6 +625,9 @@ jobs: | ... | @@ -625,6 +625,9 @@ jobs: |
| 625 | router_url="${T3CODE_WEB_ROUTER_URL:-https://app.t3.codes}" | 625 | router_url="${T3CODE_WEB_ROUTER_URL:-https://app.t3.codes}" |
| 626 | latest_domain="${T3CODE_WEB_LATEST_DOMAIN:-latest.app.t3.codes}" | 626 | latest_domain="${T3CODE_WEB_LATEST_DOMAIN:-latest.app.t3.codes}" |
| 627 | nightly_domain="${T3CODE_WEB_NIGHTLY_DOMAIN:-nightly.app.t3.codes}" | 627 | 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%%/*}" | ||
| 628 | 631 | ||
| 629 | if [[ "${{ needs.preflight.outputs.release_channel }}" == "stable" ]]; then | 632 | if [[ "${{ needs.preflight.outputs.release_channel }}" == "stable" ]]; then |
| 630 | channel_domain="$latest_domain" | 633 | channel_domain="$latest_domain" |
| ... | @@ -655,6 +658,13 @@ jobs: | ... | @@ -655,6 +658,13 @@ jobs: |
| 655 | --token "$VERCEL_TOKEN" \ | 658 | --token "$VERCEL_TOKEN" \ |
| 656 | "${vercel_scope_args[@]}" | 659 | "${vercel_scope_args[@]}" |
| 657 | 660 | ||
| 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 | |||
| 658 | finalize: | 668 | finalize: |
| 659 | name: Finalize release | 669 | name: Finalize release |
| 660 | if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'stable' }} | 670 | 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", () => { | ... | @@ -42,6 +42,8 @@ describe("branding", () => { |
| 42 | 42 | ||
| 43 | expect(branding.HOSTED_APP_CHANNEL).toBe("nightly"); | 43 | expect(branding.HOSTED_APP_CHANNEL).toBe("nightly"); |
| 44 | expect(branding.HOSTED_APP_CHANNEL_LABEL).toBe("Nightly"); | 44 | 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)"); | ||
| 45 | }); | 47 | }); |
| 46 | 48 | ||
| 47 | it("ignores unknown hosted app channels", async () => { | 49 | it("ignores unknown hosted app channels", async () => { |
apps/web/src/branding.ts+7-5| ... | @@ -11,13 +11,15 @@ function readInjectedDesktopAppBranding(): DesktopAppBranding | null { | ... | @@ -11,13 +11,15 @@ function readInjectedDesktopAppBranding(): DesktopAppBranding | null { |
| 11 | const injectedDesktopAppBranding = readInjectedDesktopAppBranding(); | 11 | const injectedDesktopAppBranding = readInjectedDesktopAppBranding(); |
| 12 | const hostedAppChannel = import.meta.env.VITE_HOSTED_APP_CHANNEL?.trim().toLowerCase(); | 12 | const hostedAppChannel = import.meta.env.VITE_HOSTED_APP_CHANNEL?.trim().toLowerCase(); |
| 13 | 13 | ||
| 14 | export const HOSTED_APP_CHANNEL = | ||
| 15 | hostedAppChannel === "latest" || hostedAppChannel === "nightly" ? hostedAppChannel : null; | ||
| 16 | export const HOSTED_APP_CHANNEL_LABEL = | ||
| 17 | HOSTED_APP_CHANNEL === "nightly" ? "Nightly" : HOSTED_APP_CHANNEL === "latest" ? "Latest" : null; | ||
| 14 | export const APP_BASE_NAME = injectedDesktopAppBranding?.baseName ?? "T3 Code"; | 18 | export const APP_BASE_NAME = injectedDesktopAppBranding?.baseName ?? "T3 Code"; |
| 15 | export const APP_STAGE_LABEL = | 19 | export 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"); | ||
| 17 | export const APP_DISPLAY_NAME = | 23 | export const APP_DISPLAY_NAME = |
| 18 | injectedDesktopAppBranding?.displayName ?? `${APP_BASE_NAME} (${APP_STAGE_LABEL})`; | 24 | injectedDesktopAppBranding?.displayName ?? `${APP_BASE_NAME} (${APP_STAGE_LABEL})`; |
| 19 | export const APP_VERSION = import.meta.env.APP_VERSION || "0.0.0"; | 25 | export const APP_VERSION = import.meta.env.APP_VERSION || "0.0.0"; |
| 20 | export const HOSTED_APP_CHANNEL = | ||
| 21 | hostedAppChannel === "latest" || hostedAppChannel === "nightly" ? hostedAppChannel : null; | ||
| 22 | export 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() { | ... | @@ -892,7 +892,7 @@ export function GeneralSettingsPanel() { |
| 892 | </SettingsSection> | 892 | </SettingsSection> |
| 893 | 893 | ||
| 894 | <SettingsSection title="About"> | 894 | <SettingsSection title="About"> |
| 895 | {isElectron ? ( | 895 | {isElectron || HOSTED_APP_CHANNEL ? ( |
| 896 | <AboutVersionSection /> | 896 | <AboutVersionSection /> |
| 897 | ) : ( | 897 | ) : ( |
| 898 | <SettingsRow | 898 | <SettingsRow |
apps/web/vercel.ts+10-1| ... | @@ -1,9 +1,16 @@ | ... | @@ -1,9 +1,16 @@ |
| 1 | import { matchers, routes, type VercelConfig } from "@vercel/config/v1"; | 1 | import { matchers, routes, type Transform, type VercelConfig } from "@vercel/config/v1"; |
| 2 | 2 | ||
| 3 | const ROUTER_HOST = "app.t3.codes"; | 3 | const ROUTER_HOST = "app.t3.codes"; |
| 4 | const HOSTED_WEB_CHANNEL_COOKIE = "t3code_web_channel"; | 4 | const HOSTED_WEB_CHANNEL_COOKIE = "t3code_web_channel"; |
| 5 | const LATEST_ORIGIN = "https://latest.app.t3.codes"; | 5 | const LATEST_ORIGIN = "https://latest.app.t3.codes"; |
| 6 | const NIGHTLY_ORIGIN = "https://nightly.app.t3.codes"; | 6 | const NIGHTLY_ORIGIN = "https://nightly.app.t3.codes"; |
| 7 | const CLEAN_CHANNEL_QUERY_TRANSFORMS = [ | ||
| 8 | { | ||
| 9 | type: "request.query", | ||
| 10 | op: "delete", | ||
| 11 | target: { key: "channel" }, | ||
| 12 | }, | ||
| 13 | ] satisfies Transform[]; | ||
| 7 | 14 | ||
| 8 | function channelCookie(channel: "latest" | "nightly"): string { | 15 | function channelCookie(channel: "latest" | "nightly"): string { |
| 9 | return [ | 16 | return [ |
| ... | @@ -28,6 +35,7 @@ export const config: VercelConfig = { | ... | @@ -28,6 +35,7 @@ export const config: VercelConfig = { |
| 28 | { | 35 | { |
| 29 | src: "/__t3code/channel", | 36 | src: "/__t3code/channel", |
| 30 | has: [matchers.query("channel", "nightly")], | 37 | has: [matchers.query("channel", "nightly")], |
| 38 | transforms: CLEAN_CHANNEL_QUERY_TRANSFORMS, | ||
| 31 | headers: { | 39 | headers: { |
| 32 | Location: "/", | 40 | Location: "/", |
| 33 | "Set-Cookie": channelCookie("nightly"), | 41 | "Set-Cookie": channelCookie("nightly"), |
| ... | @@ -36,6 +44,7 @@ export const config: VercelConfig = { | ... | @@ -36,6 +44,7 @@ export const config: VercelConfig = { |
| 36 | }, | 44 | }, |
| 37 | { | 45 | { |
| 38 | src: "/__t3code/channel", | 46 | src: "/__t3code/channel", |
| 47 | transforms: CLEAN_CHANNEL_QUERY_TRANSFORMS, | ||
| 39 | headers: { | 48 | headers: { |
| 40 | Location: "/", | 49 | Location: "/", |
| 41 | "Set-Cookie": channelCookie("latest"), | 50 | "Set-Cookie": channelCookie("latest"), |
docs/release.md+11-9| ... | @@ -51,7 +51,7 @@ Optional GitHub Actions variables: | ... | @@ -51,7 +51,7 @@ Optional GitHub Actions variables: |
| 51 | 51 | ||
| 52 | Required Vercel domains: | 52 | Required Vercel domains: |
| 53 | 53 | ||
| 54 | - `app.t3.codes`: the stable router domain users open. | 54 | - `app.t3.codes`: the router domain users open, updated by stable releases. |
| 55 | - `latest.app.t3.codes`: channel alias updated by stable releases. | 55 | - `latest.app.t3.codes`: channel alias updated by stable releases. |
| 56 | - `nightly.app.t3.codes`: channel alias updated by nightly releases. | 56 | - `nightly.app.t3.codes`: channel alias updated by nightly releases. |
| 57 | 57 | ||
| ... | @@ -62,11 +62,13 @@ visiting `/__t3code/channel?channel=latest` or | ... | @@ -62,11 +62,13 @@ visiting `/__t3code/channel?channel=latest` or |
| 62 | the matching channel alias. | 62 | the matching channel alias. |
| 63 | 63 | ||
| 64 | The release deploy job rewrites release package versions before upload so the | 64 | The release deploy job rewrites release package versions before upload so the |
| 65 | hosted app's About panel renders the release version. It also passes | 65 | hosted app's About panel renders the release version. Stable deploys alias the |
| 66 | `VITE_HOSTED_APP_CHANNEL=latest|nightly`, which renders the hosted update track | 66 | same deployment to both the `latest` channel and the router domain so the router |
| 67 | selector in the About panel. Changing the selector navigates through | 67 | rules stay current. Nightly deploys only alias the `nightly` channel. The job |
| 68 | `/__t3code/channel` on the router domain so the user's channel cookie is updated | 68 | also passes `VITE_HOSTED_APP_CHANNEL=latest|nightly`, which renders the hosted |
| 69 | before redirecting to the hosted app root. | 69 | update track selector in the About panel. Changing the selector navigates |
| 70 | through `/__t3code/channel` on the router domain so the user's channel cookie is | ||
| 71 | updated before redirecting to the hosted app root. | ||
| 70 | 72 | ||
| 71 | One-time Vercel dashboard setup: | 73 | One-time Vercel dashboard setup: |
| 72 | 74 | ||
| ... | @@ -75,9 +77,9 @@ One-time Vercel dashboard setup: | ... | @@ -75,9 +77,9 @@ One-time Vercel dashboard setup: |
| 75 | 3. Disable automatic Git deployments in the dashboard if desired; the committed | 77 | 3. Disable automatic Git deployments in the dashboard if desired; the committed |
| 76 | `vercel.ts` setting is the source-of-truth, but disconnecting Git in the | 78 | `vercel.ts` setting is the source-of-truth, but disconnecting Git in the |
| 77 | dashboard is also safe. | 79 | dashboard is also safe. |
| 78 | 4. Promote or alias one deployment containing the router rules in `apps/web/vercel.ts` to | 80 | 4. Run one stable release deployment, or manually alias the current stable |
| 79 | `app.t3.codes` once. Future release jobs should only update the channel | 81 | deployment, so `app.t3.codes` points at a deployment containing the router |
| 80 | aliases. | 82 | rules in `apps/web/vercel.ts`. Future stable releases keep this alias current. |
| 81 | 83 | ||
| 82 | ## Nightly builds | 84 | ## Nightly builds |
| 83 | 85 |