Unified · Split
| @@ -29,9 +29,9 @@ jobs: · 28 unmodified lines | |||
| 29 | PLANETSCALE_ORGANIZATION: ${{ vars.PLANETSCALE_ORGANIZATION }} | 29 | PLANETSCALE_ORGANIZATION: ${{ vars.PLANETSCALE_ORGANIZATION }} |
| 30 | AXIOM_TOKEN: ${{ secrets.AXIOM_TOKEN }} | 30 | AXIOM_TOKEN: ${{ secrets.AXIOM_TOKEN }} |
| 31 | AXIOM_ORG_ID: ${{ vars.AXIOM_ORG_ID }} | 31 | AXIOM_ORG_ID: ${{ vars.AXIOM_ORG_ID }} |
| 32 | T3CODE_RELAY_DOMAIN: ${{ vars.T3CODE_RELAY_DOMAIN }} | 32 | RELAY_DOMAIN: ${{ vars.RELAY_DOMAIN }} |
| 33 | T3CODE_RELAY_ZONE_NAME: ${{ vars.T3CODE_RELAY_ZONE_NAME }} | 33 | RELAY_ZONE_NAME: ${{ vars.RELAY_ZONE_NAME }} |
| 34 | T3CODE_CLERK_PUBLISHABLE_KEY: ${{ vars.T3CODE_CLERK_PUBLISHABLE_KEY }} | 34 | CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }} |
| 35 | CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }} | 35 | CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }} |
| 36 | APNS_ENVIRONMENT: ${{ vars.APNS_ENVIRONMENT }} | 36 | APNS_ENVIRONMENT: ${{ vars.APNS_ENVIRONMENT }} |
| 37 | APNS_TEAM_ID: ${{ vars.APNS_TEAM_ID }} | 37 | APNS_TEAM_ID: ${{ vars.APNS_TEAM_ID }} |
| @@ -178,8 +178,8 @@ jobs: · 177 unmodified lines | |||
| 178 | clerk_publishable_key: ${{ steps.public_config.outputs.clerk_publishable_key }} | 178 | clerk_publishable_key: ${{ steps.public_config.outputs.clerk_publishable_key }} |
| 179 | relay_url: ${{ steps.public_config.outputs.relay_url }} | 179 | relay_url: ${{ steps.public_config.outputs.relay_url }} |
| 180 | env: | 180 | env: |
| 181 | T3CODE_RELAY_DOMAIN: ${{ vars.T3CODE_RELAY_DOMAIN }} | 181 | RELAY_DOMAIN: ${{ vars.RELAY_DOMAIN }} |
| 182 | T3CODE_CLERK_PUBLISHABLE_KEY: ${{ vars.T3CODE_CLERK_PUBLISHABLE_KEY }} | 182 | CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }} |
| 183 | steps: | 183 | steps: |
| 184 | - id: public_config | 184 | - id: public_config |
| 185 | name: Resolve production relay public config | 185 | name: Resolve production relay public config |
| @@ -188,8 +188,8 @@ jobs: · 2 unmodified lines | |||
| 188 | set -euo pipefail | 188 | set -euo pipefail |
| 189 | 189 | ||
| 190 | required=( | 190 | required=( |
| 191 | T3CODE_RELAY_DOMAIN | 191 | RELAY_DOMAIN |
| 192 | T3CODE_CLERK_PUBLISHABLE_KEY | 192 | CLERK_PUBLISHABLE_KEY |
| 193 | ) | 193 | ) |
| 194 | missing=() | 194 | missing=() |
| 195 | for name in "${required[@]}"; do | 195 | for name in "${required[@]}"; do |
| @@ -202,8 +202,8 @@ jobs: · 6 unmodified lines | |||
| 202 | exit 1 | 202 | exit 1 |
| 203 | fi | 203 | fi |
| 204 | 204 | ||
| 205 | echo "clerk_publishable_key=$T3CODE_CLERK_PUBLISHABLE_KEY" >> "$GITHUB_OUTPUT" | 205 | echo "clerk_publishable_key=$CLERK_PUBLISHABLE_KEY" >> "$GITHUB_OUTPUT" |
| 206 | echo "relay_url=https://$T3CODE_RELAY_DOMAIN" >> "$GITHUB_OUTPUT" | 206 | echo "relay_url=https://$RELAY_DOMAIN" >> "$GITHUB_OUTPUT" |
| 207 | 207 | ||
| 208 | build: | 208 | build: |
| 209 | name: Build ${{ matrix.label }} | 209 | name: Build ${{ matrix.label }} |
| @@ -56,10 +56,10 @@ Required repository secrets shared by relay deployments: · 55 unmodified lines | |||
| 56 | 56 | ||
| 57 | Required `production` environment variables: | 57 | Required `production` environment variables: |
| 58 | 58 | ||
| 59 | - `T3CODE_RELAY_DOMAIN` | 59 | - `RELAY_DOMAIN` |
| 60 | - `T3CODE_RELAY_ZONE_NAME` | 60 | - `RELAY_ZONE_NAME` |
| 61 | - `T3CODE_CLERK_PUBLISHABLE_KEY` | 61 | - `CLERK_PUBLISHABLE_KEY` |
| 62 | - `T3CODE_CLERK_CLI_OAUTH_CLIENT_ID` | 62 | - `CLERK_CLI_OAUTH_CLIENT_ID` |
| 63 | - `APNS_ENVIRONMENT` | 63 | - `APNS_ENVIRONMENT` |
| 64 | - `APNS_TEAM_ID` | 64 | - `APNS_TEAM_ID` |
| 65 | - `APNS_KEY_ID` | 65 | - `APNS_KEY_ID` |
| @@ -34,11 +34,11 @@ client-facing values in their EAS environment. · 33 unmodified lines | |||
| 34 | When either public value is absent, cloud UI is omitted. | 34 | When either public value is absent, cloud UI is omitted. |
| 35 | 35 | ||
| 36 | For a hosted relay deployment, copy `infra/relay/.env.example` to `infra/relay/.env`. The relay | 36 | For a hosted relay deployment, copy `infra/relay/.env.example` to `infra/relay/.env`. The relay |
| 37 | deployment reads `T3CODE_RELAY_DOMAIN`, `T3CODE_RELAY_ZONE_NAME`, and | 37 | deployment reads `RELAY_DOMAIN`, `RELAY_ZONE_NAME`, and `CLERK_PUBLISHABLE_KEY` through Effect |
| 38 | `T3CODE_CLERK_PUBLISHABLE_KEY` through Effect `Config`. There are no checked-in deployment defaults. | 38 | `Config`. There are no checked-in deployment defaults. |
| 39 | `bun --cwd infra/relay run deploy` invokes Alchemy from the relay directory, so Alchemy loads | 39 | `bun --cwd infra/relay run deploy` invokes Alchemy from the relay directory, so Alchemy loads |
| 40 | `infra/relay/.env`. After a successful deployment, the wrapper updates the repository-root `.env` | 40 | `infra/relay/.env`. After a successful deployment, the wrapper updates the repository-root `.env` |
| 41 | with the HTTPS relay URL derived from `T3CODE_RELAY_DOMAIN`. The relay still requires | 41 | with the HTTPS relay URL derived from `RELAY_DOMAIN`. The relay still requires |
| 42 | `CLERK_SECRET_KEY` as an Alchemy secret. Never put `CLERK_SECRET_KEY` in a client application | 42 | `CLERK_SECRET_KEY` as an Alchemy secret. Never put `CLERK_SECRET_KEY` in a client application |
| 43 | environment or commit it to the repository. | 43 | environment or commit it to the repository. |
| 44 | 44 | ||
| @@ -56,7 +56,7 @@ In **Clerk Dashboard > JWT templates**, create a template with: · 11 unmodified lines | |||
| 56 | | Claims | `{ "aud": "https://relay.example.com" }` | | 56 | | Claims | `{ "aud": "https://relay.example.com" }` | |
| 57 | 57 | ||
| 58 | The `aud` value must be the deployed relay public URL, with no trailing slash. It must match the | 58 | The `aud` value must be the deployed relay public URL, with no trailing slash. It must match the |
| 59 | client-facing `T3CODE_RELAY_URL` and the HTTPS URL derived from the deployment's `T3CODE_RELAY_DOMAIN`. If | 59 | client-facing `T3CODE_RELAY_URL` and the HTTPS URL derived from the deployment's `RELAY_DOMAIN`. If |
| 60 | the relay domain changes, update both values and the JWT template. | 60 | the relay domain changes, update both values and the JWT template. |
| 61 | 61 | ||
| 62 | ## Desktop OAuth Redirect Allowlist | 62 | ## Desktop OAuth Redirect Allowlist |
| @@ -1,15 +1,15 @@ | |||
| 1 | # Required: Relay domain | 1 | # Required: Relay domain |
| 2 | # Use the DNS zone managed in your Cloudflare account. Production deploys use | 2 | # Use the DNS zone managed in your Cloudflare account. Production deploys use |
| 3 | # relay.<zone>; personal stages use relay-<stage>.<zone>. | 3 | # relay.<zone>; personal stages use relay-<stage>.<zone>. |
| 4 | T3CODE_RELAY_ZONE_NAME=example.com | 4 | RELAY_ZONE_NAME=example.com |
| 5 | 5 | ||
| 6 | # Optional: Relay domain override | 6 | # Optional: Relay domain override |
| 7 | # Set this only when the derived relay hostname should not be used. | 7 | # Set this only when the derived relay hostname should not be used. |
| 8 | # T3CODE_RELAY_DOMAIN=relay.example.com | 8 | # RELAY_DOMAIN=relay.example.com |
| 9 | 9 | ||
| 10 | # Required: Clerk | 10 | # Required: Clerk |
| 11 | # Get both values from the Clerk Dashboard under API keys. | 11 | # Get both values from the Clerk Dashboard under API keys. |
| 12 | T3CODE_CLERK_PUBLISHABLE_KEY=pk_test_... | 12 | CLERK_PUBLISHABLE_KEY=pk_test_... |
| 13 | CLERK_SECRET_KEY=sk_test_... | 13 | CLERK_SECRET_KEY=sk_test_... |
| 14 | 14 | ||
| 15 | # Required: Apple Push Notification service | 15 | # Required: Apple Push Notification service |
| @@ -100,8 +100,8 @@ bun --cwd infra/relay run deploy -- --env-file .env.local · 99 unmodified lines | |||
| 100 | 100 | ||
| 101 | Alchemy defaults personal deployments to the `dev_$USER` stage. Relay custom domains apply the same | 101 | Alchemy defaults personal deployments to the `dev_$USER` stage. Relay custom domains apply the same |
| 102 | DNS-safe sanitization as Alchemy physical resource names, so `prod` uses | 102 | DNS-safe sanitization as Alchemy physical resource names, so `prod` uses |
| 103 | `relay.<T3CODE_RELAY_ZONE_NAME>` and `dev_julius` uses `relay-dev-julius.<T3CODE_RELAY_ZONE_NAME>`. | 103 | `relay.<RELAY_ZONE_NAME>` and `dev_julius` uses `relay-dev-julius.<RELAY_ZONE_NAME>`. |
| 104 | `T3CODE_RELAY_DOMAIN` remains available as an explicit override. | 104 | `RELAY_DOMAIN` remains available as an explicit override. |
| 105 | 105 | ||
| 106 | After a successful deploy, the wrapper updates the repository-root `.env` file with the derived relay | 106 | After a successful deploy, the wrapper updates the repository-root `.env` file with the derived relay |
| 107 | URL. That makes subsequent source builds point at the relay that was just deployed without copying | 107 | URL. That makes subsequent source builds point at the relay that was just deployed without copying |
| @@ -130,9 +130,9 @@ The repository must define these Actions secrets shared by relay deployments: · 22 unmodified lines | |||
| 130 | 130 | ||
| 131 | The `production` GitHub environment must define these Actions variables: | 131 | The `production` GitHub environment must define these Actions variables: |
| 132 | 132 | ||
| 133 | - `T3CODE_RELAY_ZONE_NAME` | 133 | - `RELAY_ZONE_NAME` |
| 134 | - `T3CODE_RELAY_DOMAIN` if overriding the derived production relay domain | 134 | - `RELAY_DOMAIN` if overriding the derived production relay domain |
| 135 | - `T3CODE_CLERK_PUBLISHABLE_KEY` | 135 | - `CLERK_PUBLISHABLE_KEY` |
| 136 | - `APNS_ENVIRONMENT` | 136 | - `APNS_ENVIRONMENT` |
| 137 | - `APNS_TEAM_ID` | 137 | - `APNS_TEAM_ID` |
| 138 | - `APNS_KEY_ID` | 138 | - `APNS_KEY_ID` |
| @@ -129,7 +129,7 @@ export default class Api extends Cloudflare.Worker<Api>()( · 128 unmodified lines | |||
| 129 | const axiomTracesEndpoint = yield* observability.traces.otelTracesEndpoint; | 129 | const axiomTracesEndpoint = yield* observability.traces.otelTracesEndpoint; |
| 130 | 130 | ||
| 131 | const clerkSecretKey = yield* Config.redacted("CLERK_SECRET_KEY"); | 131 | const clerkSecretKey = yield* Config.redacted("CLERK_SECRET_KEY"); |
| 132 | const clerkPublishableKey = yield* Config.string("T3CODE_CLERK_PUBLISHABLE_KEY"); | 132 | const clerkPublishableKey = yield* Config.string("CLERK_PUBLISHABLE_KEY"); |
| 133 | 133 | ||
| 134 | const cloudMintPrivateKey = yield* cloudMintKeyPair.privateKey; | 134 | const cloudMintPrivateKey = yield* cloudMintKeyPair.privateKey; |
| 135 | const cloudMintPublicKey = yield* cloudMintKeyPair.publicKey; | 135 | const cloudMintPublicKey = yield* cloudMintKeyPair.publicKey; |
| @@ -0,0 +1,50 @@ | |||
| 1 | import * as Alchemy from "alchemy"; | ||
| 2 | import * as ConfigProvider from "effect/ConfigProvider"; | ||
| 3 | import * as Effect from "effect/Effect"; | ||
| 4 | import * as Layer from "effect/Layer"; | ||
| 5 | import { describe, expect, it } from "vitest"; | ||
| 6 | |||
| 7 | import { RelayDeploymentConfig } from "./zone.ts"; | ||
| 8 | |||
| 9 | function resolveRelayDeploymentConfig(env: Record<string, string>, stage = "prod") { | ||
| 10 | return Effect.runPromise( | ||
| 11 | RelayDeploymentConfig.pipe( | ||
| 12 | Effect.provide( | ||
| 13 | Layer.mergeAll( | ||
| 14 | Layer.succeed(Alchemy.Stage, stage), | ||
| 15 | ConfigProvider.layer(ConfigProvider.fromEnv({ env })), | ||
| 16 | ), | ||
| 17 | ), | ||
| 18 | ), | ||
| 19 | ); | ||
| 20 | } | ||
| 21 | |||
| 22 | describe("RelayDeploymentConfig", () => { | ||
| 23 | it("derives the relay domain from the unprefixed zone name", async () => { | ||
| 24 | await expect(resolveRelayDeploymentConfig({ RELAY_ZONE_NAME: "example.com" })).resolves.toEqual( | ||
| 25 | { | ||
| 26 | stage: "prod", | ||
| 27 | relayPublicDomain: "relay.example.com", | ||
| 28 | relayPublicOrigin: "https://relay.example.com", | ||
| 29 | managedEndpointZoneName: "example.com", | ||
| 30 | }, | ||
| 31 | ); | ||
| 32 | }); | ||
| 33 | |||
| 34 | it("uses the unprefixed relay domain override", async () => { | ||
| 35 | await expect( | ||
| 36 | resolveRelayDeploymentConfig( | ||
| 37 | { | ||
| 38 | RELAY_ZONE_NAME: "example.com", | ||
| 39 | RELAY_DOMAIN: "relay.custom.example", | ||
| 40 | }, | ||
| 41 | "dev_julius", | ||
| 42 | ), | ||
| 43 | ).resolves.toEqual({ | ||
| 44 | stage: "dev_julius", | ||
| 45 | relayPublicDomain: "relay.custom.example", | ||
| 46 | relayPublicOrigin: "https://relay.custom.example", | ||
| 47 | managedEndpointZoneName: "example.com", | ||
| 48 | }); | ||
| 49 | }); | ||
| 50 | }); | ||
| @@ -9,8 +9,8 @@ import { relayPublicDomainForStage } from "./deploymentConfig.ts"; · 8 unmodified lines | |||
| 9 | 9 | ||
| 10 | export const RelayDeploymentConfig = Effect.gen(function* () { | 10 | export const RelayDeploymentConfig = Effect.gen(function* () { |
| 11 | const stage = yield* Alchemy.Stage; | 11 | const stage = yield* Alchemy.Stage; |
| 12 | const managedEndpointZoneName = yield* Config.nonEmptyString("T3CODE_RELAY_ZONE_NAME"); | 12 | const managedEndpointZoneName = yield* Config.nonEmptyString("RELAY_ZONE_NAME"); |
| 13 | const relayPublicDomainOverride = yield* Config.nonEmptyString("T3CODE_RELAY_DOMAIN").pipe( | 13 | const relayPublicDomainOverride = yield* Config.nonEmptyString("RELAY_DOMAIN").pipe( |
| 14 | Config.option, | 14 | Config.option, |
| 15 | ); | 15 | ); |
| 16 | const relayPublicDomain = Option.getOrElse(relayPublicDomainOverride, () => | 16 | const relayPublicDomain = Option.getOrElse(relayPublicDomainOverride, () => |