Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pnpm dev:stack:check --json # machine-readable readiness + session smoke proof
```

`pnpm dev:stack` runs through [portless](https://npmjs.com/portless), so WEB
gets `https://uploads.local.buildinternet.dev` instead of a bare port. Local
gets `https://local.uploadrouter.dev` instead of a bare port. Local
sign-in behaves like production — `/account/*` and `/admin/*` work in a
browser. [docs/local-dev.md](docs/local-dev.md#named-local-urls-portless) has
the origin table, OAuth redirect URIs, and a curl smoke test.
Expand Down
6 changes: 3 additions & 3 deletions apps/auth/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,10 @@ describe("local demo session", () => {
}
});

it("is available for the owned local.buildinternet.dev zone, including worktree-prefixed", async () => {
it("is available for the owned local.uploadrouter.dev zone, including worktree-prefixed", async () => {
for (const webOrigin of [
"https://uploads.local.buildinternet.dev",
"https://fix-ui.uploads.local.buildinternet.dev",
"https://local.uploadrouter.dev",
"https://fix-ui.local.uploadrouter.dev",
]) {
const env = localEnv({ BETTER_AUTH_URL: webOrigin, WEB_ORIGIN: webOrigin });
const res = await app.request(
Expand Down
10 changes: 5 additions & 5 deletions apps/auth/src/local-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@ import * as schema from "./schema";
export const LOCAL_STACK_WEB_ORIGIN = "http://127.0.0.1:4321";

/**
* Default portless hostname (TLD `dev` + name `uploads.local.buildinternet`).
* Default portless hostname (TLD `dev` + name `local.uploadrouter`).
* Keep in sync with `portless.json`, `PORTLESS_BASE` in
* `scripts/dev-stack-common.mjs`, and `isLocalDemoStack` in the web app.
*/
export const LOCAL_STACK_DEV_HOST = "uploads.local.buildinternet.dev";
export const LOCAL_STACK_DEV_HOST = "local.uploadrouter.dev";

/**
* True for a local-stack WEB origin shape: the raw stack's pinned loopback
* port, a leftover portless `*.localhost` origin (optionally worktree-
* prefixed, optionally on the sudo-less proxy port), or the owned
* `uploads.local.buildinternet.dev` zone (same sibling convention as Either;
* worktree prefix `fix-ui.uploads.local.buildinternet.dev`). Bare
* `local.uploadrouter.dev` zone (the project's dedicated dev domain;
* worktree prefix `fix-ui.local.uploadrouter.dev`). Bare
* `uploads.dev` is not local — that is the collision you get if the app name
* stays `uploads` under `--tld dev`.
*/
Expand Down Expand Up @@ -68,7 +68,7 @@ const DEMO_ORGANIZATION = { id: "local-dev-demo-org", slug: "dev-demo", name: "D
* see scripts/dev-stack.mjs), so this requires that equality PLUS a
* recognized local-stack web-origin shape (the raw stack's pinned loopback
* port, a leftover `*.localhost` origin, or the owned
* `uploads.local.buildinternet.dev` zone). Unrelated real TLDs never enable
* `local.uploadrouter.dev` zone). Unrelated real TLDs never enable
* the bypass, even if they happen to be same-origin.
*/
export function localDemoEnabled(env: AuthEnv): boolean {
Expand Down
4 changes: 2 additions & 2 deletions apps/auth/src/trusted-origins.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe("isTrustedOrigin", () => {
const worktree = { WEB_ORIGIN: "https://fix-ui.uploads.localhost", ENVIRONMENT: "development" };
expect(isTrustedOrigin("https://fix-ui.uploads.localhost", worktree)).toBe(true);

const zone = "uploads.local.buildinternet.dev";
const zone = "local.uploadrouter.dev";
const realTld = { WEB_ORIGIN: `https://${zone}`, ENVIRONMENT: "development" };
expect(isTrustedOrigin(`https://${zone}`, realTld)).toBe(true);
expect(isTrustedOrigin(`https://auth.${zone}`, realTld)).toBe(false);
Expand All @@ -66,7 +66,7 @@ describe("isTrustedOrigin", () => {
const env = { WEB_ORIGIN: "https://uploads.sh", ENVIRONMENT: "development" };
expect(isTrustedOrigin("https://evil.example", env)).toBe(false);
expect(isTrustedOrigin("https://uploads.localhost", env)).toBe(false);
expect(isTrustedOrigin("https://uploads.local.buildinternet.dev", env)).toBe(false);
expect(isTrustedOrigin("https://local.uploadrouter.dev", env)).toBe(false);
});

it("allows extra trusted origins from env in any environment", () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/.dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#
# NOTE: `pnpm dev:stack` runs through portless (see docs/local-dev.md) and
# injects the resolved auth/API upstream origins (plain loopback ports; only
# web gets https://uploads.local.buildinternet.dev) via process env at
# web gets https://local.uploadrouter.dev) via process env at
# startup — it does not need this file. If you run the portless stack and
# signed-in pages still resolve loopback/prod origins, a stale .dev.vars here
# is the usual culprit.
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/lib/auth-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -796,9 +796,9 @@ describe("isLocalDemoStack", () => {
expect(isLocalDemoStack("http://uploads.localhost:1355")).toBe(true);
});

it("matches the owned local.buildinternet.dev zone, including worktree-prefixed", () => {
expect(isLocalDemoStack("https://uploads.local.buildinternet.dev")).toBe(true);
expect(isLocalDemoStack("https://fix-ui.uploads.local.buildinternet.dev")).toBe(true);
it("matches the owned local.uploadrouter.dev zone, including worktree-prefixed", () => {
expect(isLocalDemoStack("https://local.uploadrouter.dev")).toBe(true);
expect(isLocalDemoStack("https://fix-ui.local.uploadrouter.dev")).toBe(true);
});

it("rejects production and other non-local-stack origins", () => {
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/lib/auth-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@ export type SessionResult =

const LOCAL_STACK_WEB_ORIGIN = "http://127.0.0.1:4321";
/** Keep in sync with `LOCAL_STACK_DEV_HOST` in `apps/auth/src/local-demo.ts`. */
const LOCAL_STACK_DEV_HOST = "uploads.local.buildinternet.dev";
const LOCAL_STACK_DEV_HOST = "local.uploadrouter.dev";

/**
* True only when `pageOrigin` is a recognized local-stack web origin: the
* raw stack's pinned loopback port, a leftover portless `*.localhost`
* origin (optionally worktree-prefixed), or the owned
* `uploads.local.buildinternet.dev` zone (worktree prefix
* `fix-ui.uploads.local.buildinternet.dev`).
* `local.uploadrouter.dev` zone (worktree prefix
* `fix-ui.local.uploadrouter.dev`).
*
* #731 phase C: this used to compare the injected auth origin against its
* own pinned loopback port, but Phase B made every injected auth origin `""`
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/lib/workspace-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function snapshot(overrides: Partial<WorkspaceSnapshot> = {}): WorkspaceSnapshot
slug: "buildinternet",
role: "owner",
hasPublicUrl: true,
publicBaseUrl: "https://media.buildinternet.dev",
publicBaseUrl: "https://media.uploadrouter.dev",
plan: "free",
usage: { bytes: 8_500_000, objects: 78, uploadsInPeriod: 15, maxStorageBytes: 10_000_000_000 },
...overrides,
Expand Down Expand Up @@ -120,7 +120,7 @@ describe("toWorkspaceSnapshot", () => {
organization: { id: "org_1", slug: "buildinternet", name: "BuildInternet" },
role: "owner",
hasPublicUrl: true,
publicBaseUrl: "https://media.buildinternet.dev",
publicBaseUrl: "https://media.uploadrouter.dev",
plan: "free",
},
{
Expand All @@ -140,7 +140,7 @@ describe("toWorkspaceSnapshot", () => {
slug: "buildinternet",
role: "owner",
hasPublicUrl: true,
publicBaseUrl: "https://media.buildinternet.dev",
publicBaseUrl: "https://media.uploadrouter.dev",
plan: "free",
usage: {
bytes: 8_500_000,
Expand Down
29 changes: 15 additions & 14 deletions docs/local-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ pnpm typecheck # wrangler types + tsc across workspaces
## Named local URLs (portless)

`pnpm dev:stack` runs through [portless](https://npmjs.com/portless) on the
shared `local.buildinternet.dev` infra zone, same as the sibling repos. WEB
gets a stable named HTTPS origin instead of a bare port. Google and Apple
reject `*.localhost` redirect URIs; this hostname is a real Public Suffix
domain that they accept.
project's own `local.uploadrouter.dev` zone. WEB gets a stable named HTTPS
origin instead of a bare port. Google (like most OAuth providers) rejects
`*.localhost` redirect URIs; this hostname is a real Public Suffix domain
that it accepts.

| Service | URL | Browser-visible? |
| ------- | ----------------------------------------- | ----------------------------------------------------- |
| web | `https://uploads.local.buildinternet.dev` | yes — the only origin the browser talks to |
| auth | plain loopback (dynamic) | no — internal upstream behind web's `/api/auth` proxy |
| api | plain loopback (dynamic) | no — internal upstream behind web's `/api` proxy |
| Service | URL | Browser-visible? |
| ------- | -------------------------------- | ----------------------------------------------------- |
| web | `https://local.uploadrouter.dev` | yes — the only origin the browser talks to |
| auth | plain loopback (dynamic) | no — internal upstream behind web's `/api/auth` proxy |
| api | plain loopback (dynamic) | no — internal upstream behind web's `/api` proxy |

The name and TLD live in the repo (`portless.json` plus `PORTLESS_TLD=dev` on
`dev:stack`). You do not set `PORTLESS_NAME` in a local env file. A short
Expand All @@ -45,20 +45,21 @@ as plain `127.0.0.1` loopback processes on ports assigned dynamically at boot
is host-only on the web origin (same shape as prod's host-only `uploads.sh`
cookie), and signed-in pages (`/account/*`, `/admin/*`) just work in a local
browser, including agent browser panels. In a linked git worktree, portless
prefixes the branch name (`fix-ui.uploads.local.buildinternet.dev`); nothing
prefixes the branch name (`fix-ui.local.uploadrouter.dev`); nothing
else changes. `dev:stack` prints the resolved `previewUrl` when ready, and
`pnpm dev:stack:check --json` reports it too.

The zone is deliberately not under uploads.sh. Prod's session cookie is
The zone is deliberately not under uploads.sh, and not shared with other
projects. Prod's session cookie is
host-only on `uploads.sh`, and keeping local dev off that host means a local
stack never shares an origin with production. DNS:
`local.buildinternet.dev` + `*.local.buildinternet.dev` are public DNS-only
`local.uploadrouter.dev` + `*.local.uploadrouter.dev` are public DNS-only
A records → `127.0.0.1` (never proxy them), so the names resolve to loopback
on any machine, worktree prefixes included.
`pnpm exec portless hosts sync` is only a fallback for offline work.

Register OAuth redirect URIs on the web origin:
`https://uploads.local.buildinternet.dev/api/auth/callback/<provider>`.
`https://local.uploadrouter.dev/api/auth/callback/<provider>`.
Auth has no named subdomain — it is a loopback upstream that web forwards
`/api/auth/*` to. `pnpm dev:stack:oauth` is an alias of `pnpm dev:stack`.

Expand Down Expand Up @@ -134,7 +135,7 @@ smoke test above sets up. This recipe uses the raw loopback stack
(`pnpm dev:stack:raw`, or `pnpm dev` + `pnpm dev:auth` + `pnpm dev:web`
separately) so thumbnail bytes can come from a loopback `publicBaseUrl`.
`pnpm dev:stack` also enables the `dev-session` bypass on
`https://uploads.local.buildinternet.dev`. A few steps here aren't obvious
`https://local.uploadrouter.dev`. A few steps here aren't obvious
from the API alone:

1. **Sign in with the `dev-session` bypass, from a page already on
Expand Down
15 changes: 15 additions & 0 deletions docs/ops.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ you run `workspace:limits`.

KV cache ~60s. Agents: `uploads usage`.

## Domains

`uploads.sh` is the only canonical domain. Two backup domains live in the same
Cloudflare account. Neither one serves app content.

| Zone | Use |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uploadrouter.com` | Alias. Apex and `www` are proxied placeholder `AAAA 100::` records. A zone Single Redirect sends every request to `https://uploads.sh` + path, query kept. |
| `uploadrouter.dev` | Local dev only. `local` and `*.local` are DNS-only `A 127.0.0.1` records for the portless stack ([local-dev.md](local-dev.md#named-local-urls-portless)). |

The `uploadrouter.com` redirect is a **302**, on purpose. It is a backup
domain, so keep it temporary until there is a decision to make it permanent.
Do not proxy the `uploadrouter.dev` records. The apex stays unassigned until
there is a staging environment to point it at.

## Dual public hosts (stable vs embed / GitHub Camo)

Shared-bucket objects are available on two custom domains of `uploads-default`
Expand Down
10 changes: 5 additions & 5 deletions portless.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "uploads.local.buildinternet",
"name": "local.uploadrouter",
"apps": {
"apps/web": { "name": "uploads.local.buildinternet" },
"apps/api": { "name": "api.uploads.local.buildinternet" },
"apps/auth": { "name": "auth.uploads.local.buildinternet" },
"apps/mcp": { "name": "mcp.uploads.local.buildinternet" }
"apps/web": { "name": "local.uploadrouter" },
"apps/api": { "name": "api.local.uploadrouter" },
"apps/auth": { "name": "auth.local.uploadrouter" },
"apps/mcp": { "name": "mcp.local.uploadrouter" }
}
}
10 changes: 5 additions & 5 deletions scripts/dev-stack-common.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import { homedir } from "node:os";
import { join } from "node:path";

// Portless (see the `portless` skill) gives WEB a stable named HTTPS origin
// (`https://uploads.local.buildinternet.dev`), the only origin the browser
// ever talks to. Same sibling-repo convention as Either: TLD `dev` plus the
// long name under the owned `local.buildinternet.dev` zone (OAuth providers
// (`https://local.uploadrouter.dev`), the only origin the browser
// ever talks to. TLD `dev` plus the name `local.uploadrouter`, under the
// project's own `uploadrouter.dev` domain (OAuth providers
// reject `*.localhost`). Since #731 auth and api are internal upstreams
// reached through web's same-origin `/api/auth` + `/api` proxies, so they
// need no hostname of their own — they run as plain loopback ports
// (dynamically assigned here so concurrent worktree stacks don't collide).
// `PORTLESS=0` falls back to the legacy pinned ports (also the path for the
// dev GitHub OAuth app, whose callback is pinned to 127.0.0.1:8788).
export const USE_PORTLESS = process.env.PORTLESS !== "0";
export const PORTLESS_BASE = process.env.PORTLESS_NAME || "uploads.local.buildinternet";
export const LOCAL_STACK_DEV_HOST = "uploads.local.buildinternet.dev";
export const PORTLESS_BASE = process.env.PORTLESS_NAME || "local.uploadrouter";
export const LOCAL_STACK_DEV_HOST = "local.uploadrouter.dev";

const PORTLESS_CA = join(process.env.PORTLESS_STATE_DIR || join(homedir(), ".portless"), "ca.pem");

Expand Down
2 changes: 1 addition & 1 deletion scripts/dev-stack.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
return token;
}

async function main() {

Check warning on line 107 in scripts/dev-stack.mjs

View workflow job for this annotation

GitHub Actions / Lint & Format

unicorn(consistent-function-scoping)

Function `portlessWrap` does not capture any variables from its parent scope
const shutdown = new Promise((finish) => {
resolveShutdown = finish;
});
Expand Down Expand Up @@ -240,7 +240,7 @@
if (stopping) return;
// The bypass is gated on WEB_ORIGIN's shape (auth's localDemoEnabled).
// AUTH_ORIGIN is always a bare loopback port and no longer signals the mode.
// The owned `uploads.local.buildinternet.dev` zone is local (DNS → 127.0.0.1)
// The owned `local.uploadrouter.dev` zone is local (DNS → 127.0.0.1)
// and is the default portless origin, so smoke runs there too.
const smoke = isLocalDemoWebOrigin(WEB_ORIGIN)
? await runSmoke()
Expand Down
Loading