From 81815ef06c96f5cd33036f9f878a7eb365fe68f0 Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Mon, 29 Jun 2026 22:41:17 -0400 Subject: [PATCH] remove hard coded routes --- .claude/settings.json | 5 ++ Justfile | 7 -- README.md | 11 +-- apps/accounts/wrangler.jsonc | 6 -- apps/api/src/api.app.ts | 6 +- apps/api/src/context.ts | 6 ++ apps/api/wrangler.jsonc | 9 +-- apps/auth/wrangler.jsonc | 6 -- apps/cdn/wrangler.jsonc | 6 -- apps/chat/wrangler.jsonc | 6 -- apps/clubs/wrangler.jsonc | 6 -- apps/commerce/wrangler.jsonc | 6 -- apps/datacollection/wrangler.jsonc | 6 -- apps/econ/wrangler.jsonc | 6 -- apps/img/wrangler.jsonc | 6 -- apps/match/wrangler.jsonc | 6 -- apps/notify/wrangler.jsonc | 6 -- apps/ns/README.md | 17 ++--- apps/ns/src/context.ts | 7 +- apps/ns/src/endpoints.ts | 52 ++++++++++++++ apps/ns/src/ns.app.ts | 11 +-- apps/ns/src/test/integration/api.test.ts | 7 +- apps/ns/static/endpoints.json | 38 ---------- apps/ns/wrangler.jsonc | 9 +-- apps/playersettings/wrangler.jsonc | 6 -- apps/rooms/wrangler.jsonc | 6 -- packages/tools/bin/run-wrangler-deploy | 21 +++++- packages/tools/src/bin/runx.cmd.ts | 2 - packages/tools/src/cmd/sync.cmd.ts | 92 ------------------------ 29 files changed, 122 insertions(+), 256 deletions(-) create mode 100644 .claude/settings.json create mode 100644 apps/ns/src/endpoints.ts delete mode 100644 apps/ns/static/endpoints.json delete mode 100644 packages/tools/src/cmd/sync.cmd.ts diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..67d42ec --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,5 @@ +{ + "permissions": { + "allow": ["mcp__plugin_cloudflare_cloudflare-docs__search_cloudflare_documentation"] + } +} diff --git a/Justfile b/Justfile index 85e747b..e0137b5 100644 --- a/Justfile +++ b/Justfile @@ -100,13 +100,6 @@ new-package *args: update *args: bun runx update "$@" -# Sync generated config (wrangler routes, etc.) from env.json -[group('4. utility')] -[positional-arguments] -[no-cd] -sync *args: - bun runx sync "$@" - # CLI in packages/tools for running commands in the repo. [group('4. utility')] [positional-arguments] diff --git a/README.md b/README.md index ec6e306..c6ac6a4 100644 --- a/README.md +++ b/README.md @@ -47,12 +47,15 @@ own copy. ```bash cp env.example.json env.json # edit env.json and set "domain" to your domain -just sync ``` -`just sync` regenerates the derived config (wrangler route patterns, the `ns` -service-discovery document, and the api share-link base URL) from `env.json`. -Re-run it whenever you change `env.json`. +`just deploy` reads `env.json` at deploy time and passes the base domain to +wrangler — each worker is attached to its custom domain via `--domain +.`, and the base domain is injected as the `DOMAIN` var so +the `ns` service-discovery document and the api share-link base URL are built at +runtime. Nothing in version control is rewritten; committed `wrangler.jsonc` +files have no routes, and per-app subdomain overrides live under `subdomains` in +`env.json`. **Run Development Server:** diff --git a/apps/accounts/wrangler.jsonc b/apps/accounts/wrangler.jsonc index bbd8da4..24ae558 100644 --- a/apps/accounts/wrangler.jsonc +++ b/apps/accounts/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/accounts.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "accounts.rec.example.com", - "custom_domain": true - } - ], // Shared D1 database. The `accounts` table schema/migrations are owned by the // `auth` worker; this worker binds it read/write to look up and create accounts. "d1_databases": [ diff --git a/apps/api/src/api.app.ts b/apps/api/src/api.app.ts index bb72e30..b974dd6 100644 --- a/apps/api/src/api.app.ts +++ b/apps/api/src/api.app.ts @@ -174,7 +174,11 @@ const app = new Hono({ strict: false }) VersionRegex: '.*', }) ) - .get('/api/config/v2', (c) => c.json(apiConfigV2)) + // ShareBaseUrl is derived from the deploy-time base domain; the rest of the + // config is static. + .get('/api/config/v2', (c) => + c.json({ ...apiConfigV2, ShareBaseUrl: `https://www.${c.env.DOMAIN}/{0}` }) + ) .get('/api/versioncheck/v4', (c) => c.json({ VersionStatus: 0, diff --git a/apps/api/src/context.ts b/apps/api/src/context.ts index c2a2bc0..7e4064c 100644 --- a/apps/api/src/context.ts +++ b/apps/api/src/context.ts @@ -2,6 +2,12 @@ import type { HonoApp } from '@repo/hono-helpers' import type { SharedHonoEnv, SharedHonoVariables } from '@repo/hono-helpers/src/types' export type Env = SharedHonoEnv & { + /** + * Base domain the share-link URL is derived from, e.g. `rec.example.com`. + * Injected at deploy time via `--var DOMAIN`; defaults in `wrangler.jsonc` + * for local dev and tests. + */ + DOMAIN: string // Shared rooms database (schema/migrations owned by the `rooms` worker). Used // read-only here for the /roomserver/rooms/* endpoints. DB: D1Database diff --git a/apps/api/wrangler.jsonc b/apps/api/wrangler.jsonc index 0b8351b..83fbca0 100644 --- a/apps/api/wrangler.jsonc +++ b/apps/api/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/api.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "api.rec.example.com", - "custom_domain": true - } - ], // Shared rooms DB (created + migrated by the `rooms` worker; bound read-only here). "d1_databases": [ { @@ -36,6 +30,7 @@ }, "vars": { "ENVIRONMENT": "development", // overridden during deployment - "SENTRY_RELEASE": "unknown" // overridden during deployment + "SENTRY_RELEASE": "unknown", // overridden during deployment + "DOMAIN": "rec.example.com" // base domain; overridden during deployment } } diff --git a/apps/auth/wrangler.jsonc b/apps/auth/wrangler.jsonc index b151c2b..394d28e 100644 --- a/apps/auth/wrangler.jsonc +++ b/apps/auth/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/auth.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "auth.rec.example.com", - "custom_domain": true - } - ], // Shared D1 database (also used by the rooms worker). The `auth` worker owns // the `accounts` table; a dedicated migrations_table keeps its migration // history separate from the rooms worker's migrations on the same database. diff --git a/apps/cdn/wrangler.jsonc b/apps/cdn/wrangler.jsonc index f3b52c5..8ef2a74 100644 --- a/apps/cdn/wrangler.jsonc +++ b/apps/cdn/wrangler.jsonc @@ -6,12 +6,6 @@ "compatibility_flags": [ "nodejs_compat" ], - "routes": [ - { - "pattern": "cdn.rec.example.com", - "custom_domain": true - } - ], // CDN binaries (signature blobs + room build data) are stored as R2 objects // and streamed back by key. Keys are prefixed `sigs/` and `room/`. "r2_buckets": [ diff --git a/apps/chat/wrangler.jsonc b/apps/chat/wrangler.jsonc index 7ca3c0f..73792ab 100644 --- a/apps/chat/wrangler.jsonc +++ b/apps/chat/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/chat.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "chat.rec.example.com", - "custom_domain": true - } - ], "upload_source_maps": true, "observability": { "logs": { diff --git a/apps/clubs/wrangler.jsonc b/apps/clubs/wrangler.jsonc index dadcad0..0191509 100644 --- a/apps/clubs/wrangler.jsonc +++ b/apps/clubs/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/clubs.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "clubs.rec.example.com", - "custom_domain": true - } - ], "logpush": false, "upload_source_maps": true, "observability": { diff --git a/apps/commerce/wrangler.jsonc b/apps/commerce/wrangler.jsonc index a45a568..387a149 100644 --- a/apps/commerce/wrangler.jsonc +++ b/apps/commerce/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/commerce.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "commerce.rec.example.com", - "custom_domain": true - } - ], "upload_source_maps": true, "observability": { "logs": { diff --git a/apps/datacollection/wrangler.jsonc b/apps/datacollection/wrangler.jsonc index 8184b2f..edaf001 100644 --- a/apps/datacollection/wrangler.jsonc +++ b/apps/datacollection/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/datacollection.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "datacollection.rec.example.com", - "custom_domain": true - } - ], "upload_source_maps": true, "observability": { "logs": { diff --git a/apps/econ/wrangler.jsonc b/apps/econ/wrangler.jsonc index 3022c7a..40d158f 100644 --- a/apps/econ/wrangler.jsonc +++ b/apps/econ/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/econ.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "econ.rec.example.com", - "custom_domain": true - } - ], "logpush": false, "upload_source_maps": true, "observability": { diff --git a/apps/img/wrangler.jsonc b/apps/img/wrangler.jsonc index 308b828..d2a12ee 100644 --- a/apps/img/wrangler.jsonc +++ b/apps/img/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/img.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "img.rec.example.com", - "custom_domain": true - } - ], // Images are stored as objects in an R2 bucket and streamed back by key. "r2_buckets": [ { diff --git a/apps/match/wrangler.jsonc b/apps/match/wrangler.jsonc index f3d454d..d88901c 100644 --- a/apps/match/wrangler.jsonc +++ b/apps/match/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/match.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "match.rec.example.com", - "custom_domain": true - } - ], // Per-player presence store (room instance the player is currently in). // Create with `wrangler kv namespace create MATCH_PRESENCE` and set the id. "kv_namespaces": [ diff --git a/apps/notify/wrangler.jsonc b/apps/notify/wrangler.jsonc index ba9b3b0..7292691 100644 --- a/apps/notify/wrangler.jsonc +++ b/apps/notify/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/notify.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "notify.rec.example.com", - "custom_domain": true - } - ], "durable_objects": { "bindings": [{ "name": "NOTIFICATIONS_HUB", "class_name": "NotificationsHub" }] }, diff --git a/apps/ns/README.md b/apps/ns/README.md index b4a6855..6df9afe 100644 --- a/apps/ns/README.md +++ b/apps/ns/README.md @@ -6,17 +6,12 @@ Name-server / service-discovery worker served on the `ns` subdomain. discover every service host (Accounts, API, Auth, Econ, Matchmaking, Notifications, …). -The document is served from `static/endpoints.json`, whose hosts are generated -from the repo-root `env.json` by `runx sync` — every entry is derived from the -configured base `domain`. +Each host is built at runtime from the `DOMAIN` var (the base domain) plus the +service → subdomain map in `src/endpoints.ts`. `DOMAIN` is injected at deploy +time from the repo-root `env.json` (see `run-wrangler-deploy`) and defaults to +`rec.example.com` in `wrangler.jsonc` for local dev. ## Updating endpoints -Change `domain` in `env.json` (or edit the host map in `static/endpoints.json`), -then regenerate: - -```sh -just sync -``` - -(Bundled at build time, so a redeploy is required for changes to take effect.) +- To change the base domain, edit `domain` in `env.json` and redeploy. +- To add or rename a service host, edit the map in `src/endpoints.ts`. diff --git a/apps/ns/src/context.ts b/apps/ns/src/context.ts index 329ac40..3174ff3 100644 --- a/apps/ns/src/context.ts +++ b/apps/ns/src/context.ts @@ -2,7 +2,12 @@ import type { HonoApp } from '@repo/hono-helpers' import type { SharedHonoEnv, SharedHonoVariables } from '@repo/hono-helpers/src/types' export type Env = SharedHonoEnv & { - // add additional Bindings here + /** + * Base domain the service hosts are derived from, e.g. `rec.example.com`. + * Injected at deploy time via `--var DOMAIN`; defaults in `wrangler.jsonc` + * for local dev and tests. + */ + DOMAIN: string } /** Variables can be extended */ diff --git a/apps/ns/src/endpoints.ts b/apps/ns/src/endpoints.ts new file mode 100644 index 0000000..283d8ce --- /dev/null +++ b/apps/ns/src/endpoints.ts @@ -0,0 +1,52 @@ +/** + * Service-discovery map: service label → subdomain. The game client fetches the + * generated `{ label: "https://." }` document from `/`. + * + * The base domain is injected at deploy time via the `DOMAIN` var (see + * `run-wrangler-deploy`), so the real domain never lives in a versioned file. + */ +const SERVICE_SUBDOMAINS = { + Accounts: 'accounts', + AI: 'ai', + API: 'api', + Auth: 'auth', + BugReporting: 'bugreporting', + Cards: 'cards', + CDN: 'cdn', + Chat: 'chat', + Clubs: 'clubs', + CMS: 'cms', + Commerce: 'commerce', + Data: 'data', + DataCollection: 'datacollection', + Discovery: 'discovery', + Econ: 'econ', + GameLogs: 'gamelogs', + Geo: 'geo', + Images: 'img', + Leaderboard: 'leaderboard', + Link: 'link', + Lists: 'lists', + Matchmaking: 'match', + Moderation: 'api', + Notifications: 'notify', + PlatformNotifications: 'platformnotifications', + PlayerSettings: 'playersettings', + RoomComments: 'roomcomments', + RoomieIntegrations: 'roomieintegrations', + Rooms: 'rooms', + Storage: 'storage', + Strings: 'strings', + StringsCDN: 'strings-cdn', + Studio: 'studio', + Thorn: 'thorn', + Videos: 'videos', + WWW: 'www', +} as const + +/** Builds the endpoints document for `domain`, e.g. `rec.example.com`. */ +export function buildEndpoints(domain: string): Record { + return Object.fromEntries( + Object.entries(SERVICE_SUBDOMAINS).map(([label, sub]) => [label, `https://${sub}.${domain}`]) + ) +} diff --git a/apps/ns/src/ns.app.ts b/apps/ns/src/ns.app.ts index 42c43ad..259c069 100644 --- a/apps/ns/src/ns.app.ts +++ b/apps/ns/src/ns.app.ts @@ -3,15 +3,16 @@ import { useWorkersLogger } from 'workers-tagged-logger' import { withNotFound, withOnError } from '@repo/hono-helpers' -import endpoints from '../static/endpoints.json' +import { buildEndpoints } from './endpoints' import type { App } from './context' /** * Name-server / service-discovery worker served at the apex domain. * Returns the endpoints document the game client fetches to discover every - * service host. Generated from `env.json` by `runx sync` — run it after - * changing the domain (see `apps/ns/static/endpoints.json`). + * service host. Hosts are derived from the `DOMAIN` var, which is injected at + * deploy time (see `run-wrangler-deploy`) and defaults in `wrangler.jsonc` for + * local dev. */ const app = new Hono() .use( @@ -27,7 +28,7 @@ const app = new Hono() .onError(withOnError()) .notFound(withNotFound()) - // Endpoints document. TODO: generate this dynamically per environment. - .get('/', (c) => c.json(endpoints)) + // Endpoints document, derived from the deploy-time base domain. + .get('/', (c) => c.json(buildEndpoints(c.env.DOMAIN))) export default app diff --git a/apps/ns/src/test/integration/api.test.ts b/apps/ns/src/test/integration/api.test.ts index fdd0584..e7cde02 100644 --- a/apps/ns/src/test/integration/api.test.ts +++ b/apps/ns/src/test/integration/api.test.ts @@ -2,16 +2,19 @@ import { exports } from 'cloudflare:workers' import { describe, expect, test } from 'vitest' import '../../ns.app' -import endpoints from '../../../static/endpoints.json' +import { buildEndpoints } from '../../endpoints' const ORIGIN = 'https://example.com' +// Must match the DOMAIN var default in apps/ns/wrangler.jsonc. +const TEST_DOMAIN = 'rec.example.com' + describe('ns endpoints', () => { test('GET / returns the endpoints document', async () => { const res = await exports.default.fetch(`${ORIGIN}/`) expect(res.status).toBe(200) const body = await res.json() - expect(body).toEqual(endpoints) + expect(body).toEqual(buildEndpoints(TEST_DOMAIN)) }) test('unknown path returns 404', async () => { diff --git a/apps/ns/static/endpoints.json b/apps/ns/static/endpoints.json deleted file mode 100644 index da68746..0000000 --- a/apps/ns/static/endpoints.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "Accounts": "https://accounts.rec.example.com", - "AI": "https://ai.rec.example.com", - "API": "https://api.rec.example.com", - "Auth": "https://auth.rec.example.com", - "BugReporting": "https://bugreporting.rec.example.com", - "Cards": "https://cards.rec.example.com", - "CDN": "https://cdn.rec.example.com", - "Chat": "https://chat.rec.example.com", - "Clubs": "https://clubs.rec.example.com", - "CMS": "https://cms.rec.example.com", - "Commerce": "https://commerce.rec.example.com", - "Data": "https://data.rec.example.com", - "DataCollection": "https://datacollection.rec.example.com", - "Discovery": "https://discovery.rec.example.com", - "Econ": "https://econ.rec.example.com", - "GameLogs": "https://gamelogs.rec.example.com", - "Geo": "https://geo.rec.example.com", - "Images": "https://img.rec.example.com", - "Leaderboard": "https://leaderboard.rec.example.com", - "Link": "https://link.rec.example.com", - "Lists": "https://lists.rec.example.com", - "Matchmaking": "https://match.rec.example.com", - "Moderation": "https://api.rec.example.com", - "Notifications": "https://notify.rec.example.com", - "PlatformNotifications": "https://platformnotifications.rec.example.com", - "PlayerSettings": "https://playersettings.rec.example.com", - "RoomComments": "https://roomcomments.rec.example.com", - "RoomieIntegrations": "https://roomieintegrations.rec.example.com", - "Rooms": "https://rooms.rec.example.com", - "Storage": "https://storage.rec.example.com", - "Strings": "https://strings.rec.example.com", - "StringsCDN": "https://strings-cdn.rec.example.com", - "Studio": "https://studio.rec.example.com", - "Thorn": "https://thorn.rec.example.com", - "Videos": "https://videos.rec.example.com", - "WWW": "https://www.rec.example.com" -} diff --git a/apps/ns/wrangler.jsonc b/apps/ns/wrangler.jsonc index 2c8e612..644c281 100644 --- a/apps/ns/wrangler.jsonc +++ b/apps/ns/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/ns.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "ns.rec.example.com", - "custom_domain": true - } - ], "logpush": false, "upload_source_maps": true, "observability": { @@ -20,6 +14,7 @@ }, "vars": { "ENVIRONMENT": "development", // overridden during deployment - "SENTRY_RELEASE": "unknown" // overridden during deployment + "SENTRY_RELEASE": "unknown", // overridden during deployment + "DOMAIN": "rec.example.com" // base domain; overridden during deployment } } diff --git a/apps/playersettings/wrangler.jsonc b/apps/playersettings/wrangler.jsonc index 51cf3ea..29b5f06 100644 --- a/apps/playersettings/wrangler.jsonc +++ b/apps/playersettings/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/playersettings.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "playersettings.rec.example.com", - "custom_domain": true - } - ], // Per-player settings persistence. "kv_namespaces": [ { diff --git a/apps/rooms/wrangler.jsonc b/apps/rooms/wrangler.jsonc index af9390d..92f4e87 100644 --- a/apps/rooms/wrangler.jsonc +++ b/apps/rooms/wrangler.jsonc @@ -4,12 +4,6 @@ "main": "src/rooms.app.ts", "compatibility_date": "2025-09-20", "compatibility_flags": ["nodejs_compat"], - "routes": [ - { - "pattern": "rooms.rec.example.com", - "custom_domain": true - } - ], // Room storage. Create with `wrangler d1 create rec-rooms`, set the id, then // `wrangler d1 migrations apply rec-rooms --remote`. "d1_databases": [ diff --git a/packages/tools/bin/run-wrangler-deploy b/packages/tools/bin/run-wrangler-deploy index 0928da5..79dfb81 100755 --- a/packages/tools/bin/run-wrangler-deploy +++ b/packages/tools/bin/run-wrangler-deploy @@ -5,10 +5,29 @@ set -eu NAME=$(jq -r '.name' package.json) VERSION=$(get-version) +# Resolve the base domain (and this worker's subdomain) from the gitignored +# env.json at the repo root, then deploy onto the custom domain via `--domain`. +# This keeps the real domain out of versioned files — committed wrangler.jsonc +# has no routes, and the base domain is passed as the DOMAIN var at runtime. +ROOT=$(git rev-parse --show-toplevel) +ENV_JSON="$ROOT/env.json" +if [ ! -f "$ENV_JSON" ]; then + echo "error: $ENV_JSON not found — copy env.example.json and set your domain" >&2 + exit 1 +fi + +DIR=$(basename "$PWD") +DOMAIN=$(jq -r '.domain' "$ENV_JSON") +# Per-app subdomain override, falling back to the worker's directory name. +SUBDOMAIN=$(jq -r --arg d "$DIR" '.subdomains[$d] // $d' "$ENV_JSON") +HOST="$SUBDOMAIN.$DOMAIN" + # Deploy with wrangler using the extracted values as binding variables -echo "Deploying worker $NAME version $VERSION" +echo "Deploying worker $NAME version $VERSION to $HOST" exec wrangler deploy \ --var NAME:"$NAME" \ --var SENTRY_RELEASE:"$VERSION" \ + --var DOMAIN:"$DOMAIN" \ + --domain "$HOST" \ --minify \ "$@" diff --git a/packages/tools/src/bin/runx.cmd.ts b/packages/tools/src/bin/runx.cmd.ts index c2230c7..c4def41 100644 --- a/packages/tools/src/bin/runx.cmd.ts +++ b/packages/tools/src/bin/runx.cmd.ts @@ -9,7 +9,6 @@ import { ciCmd } from '../cmd/ci.cmd' import { devCmd } from '../cmd/dev.cmd' import { fixCmd } from '../cmd/fix.cmd' import { shfmtCmd } from '../cmd/shfmt.cmd' -import { syncCmd } from '../cmd/sync.cmd' import { updateCmd } from '../cmd/update.cmd' program @@ -26,7 +25,6 @@ program .addCommand(ciCmd) .addCommand(updateCmd) .addCommand(shfmtCmd) - .addCommand(syncCmd) // Don't hang for unresolved promises .hook('postAction', () => process.exit(0)) diff --git a/packages/tools/src/cmd/sync.cmd.ts b/packages/tools/src/cmd/sync.cmd.ts deleted file mode 100644 index 6c284f7..0000000 --- a/packages/tools/src/cmd/sync.cmd.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Command } from '@commander-js/extra-typings' -import { z } from 'zod' - -import { getRepoRoot } from '../path' - -const Env = z.object({ - /** Base domain that all service hosts are derived from, e.g. `rec.example.com`. */ - domain: z.string().min(1), - /** - * Optional per-app subdomain overrides, keyed by the app's directory name. - * Defaults to the directory name when not set. - */ - subdomains: z.record(z.string(), z.string()).optional(), -}) - -type Edit = { - label: string - file: string - /** Rewrites the file's derived values; a no-op when nothing needs changing. */ - transform: (text: string) => string -} - -export const syncCmd = new Command('sync') - .description('Sync generated config (wrangler routes, ns endpoints, etc.) from env.json') - .option('--check', `Exit non-zero if any file is out of sync (don't write changes)`, false) - .action(async ({ check }) => { - const repoRoot = getRepoRoot() - const env = Env.parse(await fs.readJson(path.join(repoRoot, 'env.json'))) - - const edits: Edit[] = [] - - // Worker custom-domain routes: `.`, derived from the app dir name. - const wranglerConfigs = await glob('apps/*/wrangler.jsonc', { cwd: repoRoot, absolute: true }) - for (const file of wranglerConfigs.sort()) { - const dir = path.basename(path.dirname(file)) - const subdomain = env.subdomains?.[dir] ?? dir - const pattern = `${subdomain}.${env.domain}` - edits.push({ - label: `apps/${dir}/wrangler.jsonc → ${pattern}`, - file, - // Only matches when the file has a route; otherwise leaves the file untouched. - transform: (t) => t.replace(/("pattern":\s*")[^"]*(")/, `$1${pattern}$2`), - }) - } - - // ns service-discovery document — every host is one of our own subdomains, so swap - // the base domain while preserving each entry's subdomain label. - const endpointsFile = path.join(repoRoot, 'apps/ns/static/endpoints.json') - if (await fs.pathExists(endpointsFile)) { - edits.push({ - label: 'apps/ns/static/endpoints.json', - file: endpointsFile, - transform: (t) => - t.replace(/("https:\/\/[a-z0-9-]+\.)[a-z0-9.-]+(")/g, `$1${env.domain}$2`), - }) - } - - // Share-link base URL in the api worker's static config (only this one field is a - // host of ours; other URLs in the file are third-party and must be left alone). - const apiConfig = path.join(repoRoot, 'apps/api/static/api-config-v2.json') - if (await fs.pathExists(apiConfig)) { - edits.push({ - label: 'apps/api/static/api-config-v2.json (ShareBaseUrl)', - file: apiConfig, - transform: (t) => - t.replace(/("ShareBaseUrl":\s*"https:\/\/[a-z0-9-]+\.)[a-z0-9.-]+(\/)/, `$1${env.domain}$2`), - }) - } - - const outOfSync: string[] = [] - for (const { label, file, transform } of edits) { - const text = await fs.readFile(file, 'utf8') - const next = transform(text) - if (next === text) continue - outOfSync.push(label) - if (!check) await fs.writeFile(file, next) - } - - if (outOfSync.length === 0) { - echo(chalk.green('✓ generated config in sync')) - return - } - - if (check) { - echo(chalk.red('✗ generated config out of sync. Run `just sync` to fix:')) - for (const line of outOfSync) echo(` ${line}`) - process.exit(1) - } - - echo(chalk.green(`✓ synced ${outOfSync.length} file(s):`)) - for (const line of outOfSync) echo(` ${line}`) - })