mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 06:31:27 -07:00
mono updates
This commit is contained in:
+12
-2
@@ -1,9 +1,19 @@
|
|||||||
# Base domain all service hosts are derived from, e.g. accounts.<domain>.
|
# Base domain all service hosts are derived from, e.g. accounts.<domain>. Used by
|
||||||
|
# `just dev` too, so a locally-run worker hands out the same addresses it would deployed.
|
||||||
RECFLARE_DOMAIN=rec.example.com
|
RECFLARE_DOMAIN=rec.example.com
|
||||||
|
|
||||||
# Optional per-app subdomain overrides, as a compact JSON object keyed by the
|
# Optional per-app subdomain overrides, as a compact JSON object keyed by the
|
||||||
# worker's directory name. Defaults to the directory name when unset.
|
# worker's directory name. Defaults to the directory name when unset. Use "@" to
|
||||||
|
# put a worker on the APEX of the domain rather than a subdomain.
|
||||||
# RECFLARE_SUBDOMAINS='{"playersettings":"settings"}'
|
# RECFLARE_SUBDOMAINS='{"playersettings":"settings"}'
|
||||||
|
#
|
||||||
|
# The combined `mono` worker (an alternative to deploying the services separately:
|
||||||
|
# it mounts most of them in one deployable and routes on the first path segment, so
|
||||||
|
# every address is https://<domain>/rooms, https://<domain>/auth, …) belongs on the
|
||||||
|
# apex, and won't hand out the right addresses anywhere else. It ships only when you
|
||||||
|
# ask for it — `just deploy-mono`, never `just deploy` — since it's an alternative to
|
||||||
|
# the split set, not part of it:
|
||||||
|
# RECFLARE_SUBDOMAINS='{"mono":"@"}'
|
||||||
|
|
||||||
# Id of the shared `recflare` D1 database (create it manually with
|
# Id of the shared `recflare` D1 database (create it manually with
|
||||||
# `wrangler d1 create recflare`). All D1-backed workers bind this one database.
|
# `wrangler d1 create recflare`). All D1-backed workers bind this one database.
|
||||||
|
|||||||
@@ -71,6 +71,17 @@ preview:
|
|||||||
deploy *args:
|
deploy *args:
|
||||||
bun turbo deploy "$@"
|
bun turbo deploy "$@"
|
||||||
|
|
||||||
|
# Deploy the combined `mono` worker: every service in ONE Worker, routed on the first
|
||||||
|
# path segment (https://<domain>/rooms). It's an alternative to the split deployment
|
||||||
|
# above — for debugging, or for running the whole server as a single service — so it has
|
||||||
|
# its own command and `just deploy` leaves it alone. Put it on the apex of your domain
|
||||||
|
# with RECFLARE_SUBDOMAINS='{"mono":"@"}'; see .env.example.
|
||||||
|
[group('2. local dev')]
|
||||||
|
[positional-arguments]
|
||||||
|
[no-cd]
|
||||||
|
deploy-mono *args:
|
||||||
|
bun turbo -F mono deploy:mono "$@"
|
||||||
|
|
||||||
# Apply D1 migrations (rooms + auth own them). Defaults to --remote; pass `-- --local`
|
# Apply D1 migrations (rooms + auth own them). Defaults to --remote; pass `-- --local`
|
||||||
# for the dev db. Scope with -F, e.g. `just migrate -F rooms`.
|
# for the dev db. Scope with -F, e.g. `just migrate -F rooms`.
|
||||||
[group('2. local dev')]
|
[group('2. local dev')]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"check:lint": "run-oxlint",
|
"check:lint": "run-oxlint",
|
||||||
"check:types": "run-tsc",
|
"check:types": "run-tsc",
|
||||||
"check:workers-types": "run-wrangler-types --check",
|
"check:workers-types": "run-wrangler-types --check",
|
||||||
|
"deploy:mono": "run-wrangler-deploy",
|
||||||
"dev": "run-wrangler-dev",
|
"dev": "run-wrangler-dev",
|
||||||
"fix:workers-types": "run-wrangler-types",
|
"fix:workers-types": "run-wrangler-types",
|
||||||
"test": "run-vitest"
|
"test": "run-vitest"
|
||||||
|
|||||||
@@ -15,6 +15,19 @@ import type { NotificationsHub } from '../../notify/src/notifications-hub'
|
|||||||
* each app's narrower `Env`, so the sub-apps type-check unchanged.
|
* each app's narrower `Env`, so the sub-apps type-check unchanged.
|
||||||
*/
|
*/
|
||||||
export type Env = SharedHonoEnv & {
|
export type Env = SharedHonoEnv & {
|
||||||
|
/**
|
||||||
|
* Base domain this worker answers on, e.g. `rec.example.com` — injected from
|
||||||
|
* `RECFLARE_DOMAIN` by both `run-wrangler-dev` and `run-wrangler-deploy`, with a
|
||||||
|
* placeholder default in `wrangler.jsonc` for tests and an unconfigured checkout.
|
||||||
|
*
|
||||||
|
* Read by the mounted `ns` app to build the service-discovery document — the thing a
|
||||||
|
* client is pointed at — so it has to name the host that actually reaches this worker:
|
||||||
|
* the tunnel/LAN hostname when running it locally, and the apex of the domain when
|
||||||
|
* deployed (`RECFLARE_SUBDOMAINS='{"mono":"@"}'`, `just deploy-mono`). Every service
|
||||||
|
* mounted here is served from a PATH on that one host, so the document says
|
||||||
|
* `https://<domain>/rooms` and nothing else would answer there.
|
||||||
|
*/
|
||||||
|
DOMAIN: string
|
||||||
// HS256 JWT signing key (shared Secrets Store). Tokens signed by `auth` verify everywhere.
|
// HS256 JWT signing key (shared Secrets Store). Tokens signed by `auth` verify everywhere.
|
||||||
JWT_SECRET: SecretsStoreSecret
|
JWT_SECRET: SecretsStoreSecret
|
||||||
// Meta (Oculus) app secret, from the same store. Read only by `auth`, to validate a
|
// Meta (Oculus) app secret, from the same store. Read only by `auth`, to validate a
|
||||||
|
|||||||
+39
-13
@@ -3,22 +3,31 @@
|
|||||||
*
|
*
|
||||||
* Mounts each RecFlare worker inside a single deployable Worker WITHOUT modifying the
|
* Mounts each RecFlare worker inside a single deployable Worker WITHOUT modifying the
|
||||||
* originals: every app is imported by relative path and bundled by esbuild at build
|
* originals: every app is imported by relative path and bundled by esbuild at build
|
||||||
* time. Production routing mirrors the split deployment — requests are dispatched on
|
* time. A request selects its service two ways, and the sub-app paths (and therefore the
|
||||||
* the request's subdomain (`accounts.<domain>` -> the `accounts` app), so the sub-app
|
* client contract) are untouched either way.
|
||||||
* paths (and therefore the client contract) are untouched.
|
|
||||||
*
|
*
|
||||||
* Local dev has no subdomain, so the first path segment selects the service and is
|
* By PATH — how this worker is meant to be deployed, at the apex of `DOMAIN`, and the
|
||||||
* stripped before the request is forwarded, e.g.
|
* only way that works in local dev, which has no subdomain. The first path segment names
|
||||||
* http://localhost:8787/accounts/ -> accounts app sees /
|
* the service and is stripped before the request is forwarded, e.g.
|
||||||
* http://localhost:8787/match/player/login -> match app sees /player/login
|
* https://<domain>/accounts/ -> accounts app sees /
|
||||||
* http://localhost:8787/api/api/config/v2 -> api app sees /api/config/v2
|
* https://<domain>/match/player/login -> match app sees /player/login
|
||||||
|
* https://<domain>/api/api/config/v2 -> api app sees /api/config/v2
|
||||||
|
*
|
||||||
|
* By SUBDOMAIN — `accounts.<domain>` -> the `accounts` app, with the path forwarded
|
||||||
|
* unchanged. That mirrors the split deployment, so a client (or a stray DNS record) still
|
||||||
|
* pointed at the per-service hosts keeps working if they're routed here.
|
||||||
*
|
*
|
||||||
* A request with no path (just `/`) that selects no service serves the `ns` discovery
|
* A request with no path (just `/`) that selects no service serves the `ns` discovery
|
||||||
* document, so a bare hit to the facade root returns the service map to bootstrap from.
|
* document, so a bare hit to the facade root returns the service map to bootstrap from.
|
||||||
|
* The document is built in the PATH style (`https://<domain>/rooms`, every service on
|
||||||
|
* this one host) — see ENDPOINT_STYLE below — so deploy this worker at the apex of
|
||||||
|
* `DOMAIN` and point the client at nothing else.
|
||||||
*
|
*
|
||||||
* NOT mounted here: `www`, `img`, `econ`. Each binds a static `assets` directory and
|
* NOT mounted here: `www`, `img`, `econ`. Each binds a static `assets` directory and
|
||||||
* Cloudflare allows only one static-assets binding per Worker. Resolve that (serve
|
* Cloudflare allows only one static-assets binding per Worker. Resolve that (serve
|
||||||
* their static trees from R2, or keep those three as their own Workers) before adding.
|
* their static trees from R2, or keep those three as their own Workers) before adding.
|
||||||
|
* The discovery document still puts them on this host, since a single-service run is the
|
||||||
|
* whole point of this worker — so until they're mounted, their paths 404 here.
|
||||||
*/
|
*/
|
||||||
import accounts from '../../accounts/src/accounts.app'
|
import accounts from '../../accounts/src/accounts.app'
|
||||||
import api from '../../api/src/api.app'
|
import api from '../../api/src/api.app'
|
||||||
@@ -67,16 +76,24 @@ const services = {
|
|||||||
|
|
||||||
type ServiceName = keyof typeof services
|
type ServiceName = keyof typeof services
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This worker is one host, so its discovery document has to name one host: every service
|
||||||
|
* is advertised as `https://<domain>/<name>`, never `https://<name>.<domain>`. Handed to
|
||||||
|
* the mounted `ns` app, which defaults to the per-host document the split deployment wants.
|
||||||
|
*/
|
||||||
|
const ENDPOINT_STYLE = 'path'
|
||||||
|
|
||||||
function resolve(request: Request): { name: ServiceName; request: Request } | undefined {
|
function resolve(request: Request): { name: ServiceName; request: Request } | undefined {
|
||||||
const url = new URL(request.url)
|
const url = new URL(request.url)
|
||||||
|
|
||||||
// Production: dispatch on the leftmost DNS label — accounts.<domain> -> accounts.
|
// Dispatch on the leftmost DNS label — accounts.<domain> -> accounts. The path is
|
||||||
// The path is forwarded unchanged so the client contract is identical.
|
// forwarded unchanged so the client contract is identical to the split deployment.
|
||||||
const sub = url.hostname.split('.')[0]
|
const sub = url.hostname.split('.')[0]
|
||||||
if (sub in services) return { name: sub as ServiceName, request }
|
if (sub in services) return { name: sub as ServiceName, request }
|
||||||
|
|
||||||
// Local dev (no service subdomain): the first path segment selects the service and
|
// Apex (and local dev): the first path segment selects the service and is stripped
|
||||||
// is stripped before forwarding — /match/player/login -> match app sees /player/login.
|
// before forwarding — /match/player/login -> match app sees /player/login. This is
|
||||||
|
// what the discovery document advertises; see ENDPOINT_STYLE.
|
||||||
const [, first, ...rest] = url.pathname.split('/')
|
const [, first, ...rest] = url.pathname.split('/')
|
||||||
if (first !== undefined && first in services) {
|
if (first !== undefined && first in services) {
|
||||||
url.pathname = `/${rest.join('/')}`
|
url.pathname = `/${rest.join('/')}`
|
||||||
@@ -103,11 +120,20 @@ export default {
|
|||||||
{ status: 404 }
|
{ status: 404 }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// `ns` is the one mounted app whose answer depends on this worker's own shape: the
|
||||||
|
// addresses it hands out have to be paths on this host. Passed as a var — the same
|
||||||
|
// way a deploy would — so the app itself stays free of any knowledge of mono.
|
||||||
|
if (resolved.name === 'ns') return ns.fetch(resolved.request, { ...env, ENDPOINT_STYLE }, ctx)
|
||||||
|
|
||||||
return services[resolved.name].fetch(resolved.request, env, ctx)
|
return services[resolved.name].fetch(resolved.request, env, ctx)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Only `match` runs a cron in the split deployment; this worker owns its presence sweep.
|
// Only `match` runs a cron in the split deployment; this worker owns its presence sweep.
|
||||||
scheduled(controller: ScheduledController, env: Env, ctx: ExecutionContext): Promise<void> | void {
|
scheduled(
|
||||||
|
controller: ScheduledController,
|
||||||
|
env: Env,
|
||||||
|
ctx: ExecutionContext
|
||||||
|
): Promise<void> | void {
|
||||||
return matchScheduled(controller, env, ctx)
|
return matchScheduled(controller, env, ctx)
|
||||||
},
|
},
|
||||||
} satisfies ExportedHandler<Env>
|
} satisfies ExportedHandler<Env>
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ declare module 'cloudflare:test' {
|
|||||||
|
|
||||||
const ORIGIN = 'https://example.com'
|
const ORIGIN = 'https://example.com'
|
||||||
|
|
||||||
|
// Must match the DOMAIN var default in apps/mono/wrangler.jsonc.
|
||||||
|
const TEST_DOMAIN = 'rec.example.com'
|
||||||
|
|
||||||
// The facade's job is routing, not business logic, so one request that reaches a
|
// The facade's job is routing, not business logic, so one request that reaches a
|
||||||
// mounted app through the path prefix is enough to prove the wiring. `api` serves a
|
// mounted app through the path prefix is enough to prove the wiring. `api` serves a
|
||||||
// static game-config with no auth/DB, so it's a clean target. The api worker namespaces
|
// static game-config with no auth/DB, so it's a clean target. The api worker namespaces
|
||||||
@@ -24,8 +27,22 @@ describe('mono routing', () => {
|
|||||||
test('root path (no service, no prefix) serves the ns discovery document', async () => {
|
test('root path (no service, no prefix) serves the ns discovery document', async () => {
|
||||||
const res = await exports.default.fetch(`${ORIGIN}/`)
|
const res = await exports.default.fetch(`${ORIGIN}/`)
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
// The ns worker serves the service-discovery document.
|
// The ns worker serves the service-discovery document. This worker is one host, so
|
||||||
expect(await res.json()).toHaveProperty('Auth')
|
// every service in it is a path on the base domain (the DOMAIN var default in
|
||||||
|
// wrangler.jsonc) — no per-service subdomains anywhere in the document.
|
||||||
|
const doc = (await res.json()) as Record<string, string>
|
||||||
|
expect(doc).toMatchObject({
|
||||||
|
Auth: `https://${TEST_DOMAIN}/auth`,
|
||||||
|
Rooms: `https://${TEST_DOMAIN}/rooms`,
|
||||||
|
Matchmaking: `https://${TEST_DOMAIN}/match`,
|
||||||
|
})
|
||||||
|
expect(Object.values(doc).every((url) => url.startsWith(`https://${TEST_DOMAIN}/`))).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('the ns service prefix serves that same document', async () => {
|
||||||
|
const res = await exports.default.fetch(`${ORIGIN}/ns/`)
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
expect(await res.json()).toMatchObject({ Rooms: `https://${TEST_DOMAIN}/rooms` })
|
||||||
})
|
})
|
||||||
|
|
||||||
test('unknown service prefix returns the facade 404', async () => {
|
test('unknown service prefix returns the facade 404', async () => {
|
||||||
|
|||||||
@@ -76,6 +76,11 @@
|
|||||||
"vars": {
|
"vars": {
|
||||||
"NAME": "mono", // logging tag; split workers derive this per-app
|
"NAME": "mono", // logging tag; split workers derive this per-app
|
||||||
"ENVIRONMENT": "development", // overridden during deployment
|
"ENVIRONMENT": "development", // overridden during deployment
|
||||||
"SENTRY_RELEASE": "unknown" // overridden during deployment
|
"SENTRY_RELEASE": "unknown", // overridden during deployment
|
||||||
|
// Base domain the discovery document is built from; replaced with RECFLARE_DOMAIN by
|
||||||
|
// both `just dev` and `just deploy-mono`. It must name the host that actually reaches
|
||||||
|
// this worker, which serves every service it mounts from a path on that ONE host — so
|
||||||
|
// deployed, it belongs on the APEX of that domain (see src/context.ts).
|
||||||
|
"DOMAIN": "rec.example.com"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-3
@@ -7,11 +7,19 @@ discover every service host (Accounts, API, Auth, Econ, Matchmaking,
|
|||||||
Notifications, …).
|
Notifications, …).
|
||||||
|
|
||||||
Each host is built at runtime from the `DOMAIN` var (the base domain) plus the
|
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
|
service → subdomain map in `src/endpoints.ts`. `DOMAIN` is injected from
|
||||||
time from `RECFLARE_DOMAIN` (see `run-wrangler-deploy`) and defaults to
|
`RECFLARE_DOMAIN` by both `run-wrangler-dev` and `run-wrangler-deploy`, and
|
||||||
`rec.example.com` in `wrangler.jsonc` for local dev.
|
defaults to `rec.example.com` in `wrangler.jsonc` when that isn't set.
|
||||||
|
|
||||||
## Updating endpoints
|
## Updating endpoints
|
||||||
|
|
||||||
- To change the base domain, set `RECFLARE_DOMAIN` (in `.env`) and redeploy.
|
- To change the base domain, set `RECFLARE_DOMAIN` (in `.env`) and redeploy.
|
||||||
- To add or rename a service host, edit the map in `src/endpoints.ts`.
|
- To add or rename a service host, edit the map in `src/endpoints.ts`.
|
||||||
|
|
||||||
|
## ENDPOINT_STYLE
|
||||||
|
|
||||||
|
With `ENDPOINT_STYLE=path`, every service is advertised as `https://<domain>/<slug>`
|
||||||
|
instead of `https://<slug>.<domain>`. That's for the combined `mono` worker alone —
|
||||||
|
it's a single Worker that routes on the first path segment, so one host serves the
|
||||||
|
lot. Unset (the split deployment, where each service is its own Worker on its own
|
||||||
|
host) gives the subdomain document.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { HonoApp } from '@repo/hono-helpers'
|
import type { HonoApp } from '@repo/hono-helpers'
|
||||||
import type { SharedHonoEnv, SharedHonoVariables } from '@repo/hono-helpers/src/types'
|
import type { SharedHonoEnv, SharedHonoVariables } from '@repo/hono-helpers/src/types'
|
||||||
|
import type { EndpointStyle } from './endpoints'
|
||||||
|
|
||||||
export type Env = SharedHonoEnv & {
|
export type Env = SharedHonoEnv & {
|
||||||
/**
|
/**
|
||||||
@@ -8,6 +9,13 @@ export type Env = SharedHonoEnv & {
|
|||||||
* for local dev and tests.
|
* for local dev and tests.
|
||||||
*/
|
*/
|
||||||
DOMAIN: string
|
DOMAIN: string
|
||||||
|
/**
|
||||||
|
* `path` to serve every service from a path on `DOMAIN` (`https://<domain>/rooms`)
|
||||||
|
* instead of from its own subdomain. Set only by the combined `mono` worker, which is
|
||||||
|
* one Worker routing on that first path segment; anything else (the split deployment)
|
||||||
|
* leaves it unset and gets the per-service hosts.
|
||||||
|
*/
|
||||||
|
ENDPOINT_STYLE?: EndpointStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Variables can be extended */
|
/** Variables can be extended */
|
||||||
|
|||||||
@@ -44,9 +44,25 @@ const SERVICE_SUBDOMAINS = {
|
|||||||
WWW: 'www',
|
WWW: 'www',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where the services live, relative to the base domain:
|
||||||
|
*
|
||||||
|
* `subdomain` — one host each, `https://rooms.<domain>`. The split deployment, and the
|
||||||
|
* default, since that's what every worker in `apps/` is deployed as.
|
||||||
|
* `path` — one host, first path segment names the service: `https://<domain>/rooms`.
|
||||||
|
* Only the combined `mono` worker, which is a single Worker routing on that segment.
|
||||||
|
*/
|
||||||
|
export type EndpointStyle = 'subdomain' | 'path'
|
||||||
|
|
||||||
/** Builds the endpoints document for `domain`, e.g. `rec.example.com`. */
|
/** Builds the endpoints document for `domain`, e.g. `rec.example.com`. */
|
||||||
export function buildEndpoints(domain: string): Record<string, string> {
|
export function buildEndpoints(
|
||||||
|
domain: string,
|
||||||
|
style: EndpointStyle = 'subdomain'
|
||||||
|
): Record<string, string> {
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
Object.entries(SERVICE_SUBDOMAINS).map(([label, sub]) => [label, `https://${sub}.${domain}`])
|
Object.entries(SERVICE_SUBDOMAINS).map(([label, sub]) => [
|
||||||
|
label,
|
||||||
|
style === 'path' ? `https://${domain}/${sub}` : `https://${sub}.${domain}`,
|
||||||
|
])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ const app = new Hono<App>()
|
|||||||
.onError(withOnError())
|
.onError(withOnError())
|
||||||
.notFound(withNotFound())
|
.notFound(withNotFound())
|
||||||
|
|
||||||
// Endpoints document, derived from the deploy-time base domain.
|
// Endpoints document, derived from the deploy-time base domain. ENDPOINT_STYLE is set
|
||||||
.get('/', (c) => c.json(buildEndpoints(c.env.DOMAIN)))
|
// only by the combined `mono` worker, to advertise the services on paths of that one
|
||||||
|
// domain rather than on a host each; unset (the split deployment) means subdomains.
|
||||||
|
.get('/', (c) => c.json(buildEndpoints(c.env.DOMAIN, c.env.ENDPOINT_STYLE)))
|
||||||
|
|
||||||
export default app
|
export default app
|
||||||
|
|||||||
@@ -18,6 +18,20 @@ describe('ns endpoints', () => {
|
|||||||
expect(body).toEqual(buildEndpoints(TEST_DOMAIN))
|
expect(body).toEqual(buildEndpoints(TEST_DOMAIN))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('the path style puts every service on the base domain', async () => {
|
||||||
|
const doc = buildEndpoints(TEST_DOMAIN, 'path')
|
||||||
|
expect(doc.Rooms).toBe(`https://${TEST_DOMAIN}/rooms`)
|
||||||
|
expect(doc.Matchmaking).toBe(`https://${TEST_DOMAIN}/match`)
|
||||||
|
expect(doc.Images).toBe(`https://${TEST_DOMAIN}/img`)
|
||||||
|
expect(Object.values(doc).every((url) => url.startsWith(`https://${TEST_DOMAIN}/`))).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('the default style gives every service its own host', async () => {
|
||||||
|
const doc = buildEndpoints(TEST_DOMAIN)
|
||||||
|
expect(doc.Rooms).toBe(`https://rooms.${TEST_DOMAIN}`)
|
||||||
|
expect(doc.Images).toBe(`https://img.${TEST_DOMAIN}`)
|
||||||
|
})
|
||||||
|
|
||||||
test('unknown path returns 404', async () => {
|
test('unknown path returns 404', async () => {
|
||||||
const res = await exports.default.fetch(`${ORIGIN}/nope`)
|
const res = await exports.default.fetch(`${ORIGIN}/nope`)
|
||||||
expect(res.status).toBe(404)
|
expect(res.status).toBe(404)
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ recflare_load_env
|
|||||||
# custom domain via `--domain`. This keeps the real domain out of versioned files
|
# 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
|
# — committed wrangler.jsonc has no routes, and the base domain is passed as the
|
||||||
# DOMAIN var at runtime. Per-app subdomain overrides come from RECFLARE_SUBDOMAINS
|
# DOMAIN var at runtime. Per-app subdomain overrides come from RECFLARE_SUBDOMAINS
|
||||||
# (a JSON object, e.g. {"playersettings":"settings"}).
|
# (a JSON object, e.g. {"playersettings":"settings"}); an override of "@" (or "")
|
||||||
|
# puts the worker on the APEX of the domain instead of a subdomain, which is what
|
||||||
|
# the combined `mono` worker wants — it serves every service from a path on one host.
|
||||||
if [ -z "${RECFLARE_DOMAIN:-}" ]; then
|
if [ -z "${RECFLARE_DOMAIN:-}" ]; then
|
||||||
echo "error: RECFLARE_DOMAIN is not set — export it or add it to .env (see .env.example)" >&2
|
echo "error: RECFLARE_DOMAIN is not set — export it or add it to .env (see .env.example)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -29,7 +31,10 @@ SUBDOMAINS_JSON=${RECFLARE_SUBDOMAINS:-}
|
|||||||
DIR=$(basename "$PWD")
|
DIR=$(basename "$PWD")
|
||||||
# Per-app subdomain override, falling back to the worker's directory name.
|
# Per-app subdomain override, falling back to the worker's directory name.
|
||||||
SUBDOMAIN=$(printf '%s' "$SUBDOMAINS_JSON" | jq -r --arg d "$DIR" '.[$d] // $d')
|
SUBDOMAIN=$(printf '%s' "$SUBDOMAINS_JSON" | jq -r --arg d "$DIR" '.[$d] // $d')
|
||||||
HOST="$SUBDOMAIN.$DOMAIN"
|
case "$SUBDOMAIN" in
|
||||||
|
"" | "@") HOST="$DOMAIN" ;;
|
||||||
|
*) HOST="$SUBDOMAIN.$DOMAIN" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
# Splice deploy-time resource ids into a generated config. The committed
|
# Splice deploy-time resource ids into a generated config. The committed
|
||||||
# wrangler.jsonc carries "local" placeholders so it needs no per-developer edits;
|
# wrangler.jsonc carries "local" placeholders so it needs no per-developer edits;
|
||||||
|
|||||||
@@ -14,6 +14,15 @@ NAME=$(jq -r '.name' package.json)
|
|||||||
recflare_load_env
|
recflare_load_env
|
||||||
EXTRA_VARS=$(recflare_vars)
|
EXTRA_VARS=$(recflare_vars)
|
||||||
|
|
||||||
|
# The base domain isn't a tuning knob (recflare_vars skips the deploy inputs), so pass it
|
||||||
|
# the same way a deploy does. It matters locally for the workers that hand out addresses —
|
||||||
|
# `ns`, and the combined `mono` worker, whose discovery document IS the thing you point a
|
||||||
|
# client at. Unset, the worker keeps the placeholder default in its wrangler.jsonc; running
|
||||||
|
# mono behind a tunnel, set RECFLARE_DOMAIN to the hostname that reaches it and the document
|
||||||
|
# it serves names that host instead of a domain nothing here answers on.
|
||||||
|
DOMAIN_VAR=""
|
||||||
|
[ -z "${RECFLARE_DOMAIN:-}" ] || DOMAIN_VAR="--var DOMAIN:$RECFLARE_DOMAIN"
|
||||||
|
|
||||||
# Give each worker a stable, unique dev port so `turbo dev` can run them all in
|
# Give each worker a stable, unique dev port so `turbo dev` can run them all in
|
||||||
# parallel without colliding on wrangler's default 8787 (and its 9229 inspector
|
# parallel without colliding on wrangler's default 8787 (and its 9229 inspector
|
||||||
# port). The offset is the worker's alphabetical position among its siblings, so
|
# port). The offset is the worker's alphabetical position among its siblings, so
|
||||||
@@ -27,9 +36,11 @@ OFFSET=$(
|
|||||||
PORT=$((8787 + OFFSET - 1))
|
PORT=$((8787 + OFFSET - 1))
|
||||||
INSPECTOR_PORT=$((9229 + OFFSET - 1))
|
INSPECTOR_PORT=$((9229 + OFFSET - 1))
|
||||||
|
|
||||||
# $EXTRA_VARS is intentionally unquoted — it's a flag list to word-split on.
|
# $EXTRA_VARS and $DOMAIN_VAR are intentionally unquoted — they're flag lists to word-split
|
||||||
|
# on, and neither a knob value nor a domain contains whitespace.
|
||||||
exec wrangler dev \
|
exec wrangler dev \
|
||||||
--var NAME:"$NAME" \
|
--var NAME:"$NAME" \
|
||||||
|
$DOMAIN_VAR \
|
||||||
$EXTRA_VARS \
|
$EXTRA_VARS \
|
||||||
--port "$PORT" \
|
--port "$PORT" \
|
||||||
--inspector-port "$INSPECTOR_PORT" \
|
--inspector-port "$INSPECTOR_PORT" \
|
||||||
|
|||||||
+10
@@ -41,6 +41,16 @@
|
|||||||
"env": ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"],
|
"env": ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"],
|
||||||
"outputLogs": "new-only"
|
"outputLogs": "new-only"
|
||||||
},
|
},
|
||||||
|
// The combined `mono` worker's deploy (see apps/mono). Deliberately NOT called
|
||||||
|
// `deploy`: mono is a debugging deployable that runs every service in one Worker,
|
||||||
|
// an ALTERNATIVE to the split set rather than a member of it, so `just deploy`
|
||||||
|
// must not sweep it up. Ship it on demand with `just deploy-mono`.
|
||||||
|
"deploy:mono": {
|
||||||
|
"cache": false,
|
||||||
|
"dependsOn": ["build", "topo"],
|
||||||
|
"env": ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"],
|
||||||
|
"outputLogs": "new-only"
|
||||||
|
},
|
||||||
// Apply D1 migrations. Only workers that own migrations define a `migrate`
|
// Apply D1 migrations. Only workers that own migrations define a `migrate`
|
||||||
// script, so `turbo migrate` runs just those. No build needed.
|
// script, so `turbo migrate` runs just those. No build needed.
|
||||||
"migrate": {
|
"migrate": {
|
||||||
|
|||||||
Reference in New Issue
Block a user