feat: remove portless in favor of fixed development ports - #151
feat: remove portless in favor of fixed development ports#151adelrodriguez wants to merge 3 commits into
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
ℹ️ No blocking issues. Two rough edges inline, one design consequence worth a decision.
Reviewed changes — full read of the 34-file diff for commit b5ff345, plus the surrounding auth, Hono, Turbo, and template-script code that the deleted PORTLESS_URL plumbing fed into.
- Portless deleted repo-wide —
portlessdropped from rootdevDependenciesandbun.lock; every workspace's"dev": "portless"+"dev:app"pair collapses into onedevscript and loses itsportless: { name, script }block. PORTLESS_URLplumbing removed — gone from both.env.schemafiles, bothenv.generated.ts, andturbo.jsonbuild.env; the fallbacks inapps/api/src/shared/utils.ts,apps/app/src/shared/utils.ts, andapps/app/src/features/auth/server/index.tscollapse to plain env reads.- Dev URLs rewritten to fixed ports —
.env.developmentfor api/app/docs/web, plusapps/docs/src/shared/constants.tsand both Turbo generators, move fromhttps://*.init.localhosttohttp://localhost:<port>. - Workflows and Studio dev scripts rewritten —
packages/workflowsbecomesinngest dev -u http://localhost:3000/workflows;packages/dbdrops--port ${PORT:-4983}and takes drizzle-kit's default. - Template tooling simplified —
scripts/template/portless.tsdeleted,rename.tsno longer text-replaces<scope>.localhost,add.tsno longer callsrestorePortlessWorkspaces. - Docs rewritten —
docs/development.md,docs/getting-started.md,docs/generators.md, anddocs/template-commands.mdreplace the Portless topology with a fixed-port table.
I verified the mechanical parts hold up: bunx oxlint scripts/ turbo/ reports 0 errors after the deletions, no dangling imports or unused exports remain, normalizedSourceScope in rename.ts is still live, no workspace has a studio script so removing that turbo.json task is safe, -u is the documented short form of --sdk-url with polling on by default (docs), and the ten stated ports match what each config actually produces with no collision against the Compose host ports (8000–8003).
ℹ️ The app and API auth instances no longer have isolated cookie jars
Cookies are scoped by host, never by port. apps/app (:3001, basePath /api/auth) and apps/api (:3000, basePath /auth) both run better-auth with the shared cookiePrefix: "init" from packages/auth/src/constants.ts, no domain, and the default path / — so they now write the same init.session_token into one jar on host localhost. The distinct app.init.localhost / api.init.localhost hostnames used to keep them apart. Because both dev configs share AUTH_SECRET and DATABASE_URL, the clobbered cookie still validates, so this is confusing rather than broken — but signing out on one signs the other out, and concurrent OAuth flows overwrite each other's state cookie.
Technical details
# Shared cookie jar between the two better-auth instances
## Affected sites
- `apps/api/.env.development:1` — `BASE_URL=http://localhost:3000`
- `apps/app/.env.development:1` — `PUBLIC_BASE_URL=http://localhost:3001`
- `packages/auth/src/constants.ts:6` — `AUTH_ADVANCED_OPTIONS.cookiePrefix = "init"`, shared by
`apps/api/src/shared/auth.ts:16` and `apps/app/src/features/auth/server/index.ts:18`
## Required outcome
- A developer running `bun run dev` at the root and using both `:3000` and `:3001` in one browser
profile should not have one server's session, cookie-cache, or OAuth-state cookie silently
replace the other's — or, if that is acceptable for a template, the trade-off should be stated
next to the one already documented in the PR description.
## Open questions for the human
- Are `apps/app` and `apps/api` intended to be run together, or are they alternative backends a
user picks between during `bun template setup`? If they are alternatives, this is a non-issue
and only worth a docs line.
- If they are meant to coexist, is a per-app `cookiePrefix` (rather than the single shared
constant) the right lever, given `packages/auth` deliberately centralizes it today?ℹ️ Nitpicks
packages/db/package.json:19—drizzle-kit studiolost its${PORT:-4983}override whilepackages/emailandapps/mobilekept theirs. Studio's port is now the only one in the fleet that cannot be changed without editing the script.packages/workflows/package.json:18— the api's port is hardcoded here. OverridingPORTinapps/api/.env.localsilently stops workflow syncing with no error at either end.apps/docs/README.md:47-48still says development "derives the matchingweb.<project>.localhostURL", but this PR changedDEVELOPMENT_MARKETING_URLinapps/docs/src/shared/constants.tsto a literalhttp://localhost:3006.docs/plans/19-cross-platform-component-catalog.md:82still refers to "the repository's normal Portless/Turbo conventions". Low impact —docs/plansis ininit.cleanupPaths, so template consumers never receive it.
Claude Opus | 𝕏
There was a problem hiding this comment.
ℹ️ One follow-up on the new 4000-block assignment.
Reviewed changes — delta since my review of b5ff345. The new commit dcd0ebb reassigns the three package dev servers into a sequential 4000 block.
- Drizzle Studio 4983 → 4000 —
packages/db/package.json:19regains the${PORT:-N}override thatb5ff345had dropped, now defaulting to4000. - Email preview 4000 → 4001 —
packages/email/package.json:15. - Inngest 8288 → 4002 —
packages/workflows/package.json:18adds--port ${PORT:-4002}alongside the existing-uflag, andapps/api/.env.development:17follows withINNGEST_BASE_URL=http://localhost:4002. - Docs updated — the tables in
docs/development.mdanddocs/getting-started.mdnow describe the 4000 block.
I checked the fallout: apps/api is the only workspace importing @init/workflows, so INNGEST_BASE_URL needed updating in exactly one place and it was; no 4983 or 8288 reference survives anywhere in the tree; and the three new ports do not collide with 3000–3006 or the Compose host ports. The ${PORT:-4983} regression I flagged on packages/db last round is resolved.
My earlier points on the shared localhost cookie jar, the .env.schema port claim in the docs, Inngest auto-discovery, and the hardcoded http://localhost:3000 in the workflows script all still apply unchanged — not repeated here.
Claude Opus | 𝕏
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Addressed the review in 1c23875:
Two deliberate non-changes: the hardcoded |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — delta since my review of dcd0ebb. The new commit 1c23875 is a pure review-feedback commit and closes every open point from the two prior rounds.
- API auth cookies namespaced —
apps/api/src/shared/auth.ts:20spreadsAUTH_ADVANCED_OPTIONSand overridescookiePrefixto`${AUTH_COOKIE_PREFIX}-api`, so the api writesinit-api.*whileapps/appkeepsinit.*. - Inngest auto-discovery disabled —
packages/workflows/package.json:18adds--no-discovery. - Drizzle Studio URLs corrected —
docs/development.md:58,62anddocs/getting-started.md:97now carry?port=4000. - Port-source claim scoped —
docs/development.md:47anddocs/getting-started.md:88,124now say application workspaces declarePORTin.env.schemawhile the Mobile server and package dev servers set theirs in thedevscript. - Docs README de-staled —
apps/docs/README.md:47-48replacesweb.<project>.localhostwith the literalhttp://localhost:3006thatDEVELOPMENT_MARKETING_URLactually holds.
I verified each fix rather than taking it on trust. The cookie change is complete: better-auth@1.6.25/dist/cookies/index.mjs:26 funnels every cookie the instance issues through advanced.cookiePrefix — session_token, session_data, dont_remember (:47-50) and oauth_state (:205) — so the two jars on host localhost no longer overlap, and nothing in the repo hardcodes a cookie name that the rename would strand. --no-discovery is the documented flag and the same page confirms the 3000–3010 scan range that motivated it (docs). The qualified .env.schema wording now matches reality for all ten servers. oxfmt --check and oxlint are clean on the changed TypeScript.
ℹ️ The Expo client cannot see the api's cookies, before or after the rename
Not a regression — flagging it because this commit is the natural place a reader would assume mobile was covered too. packages/auth/src/expo/client.ts passes scheme and storagePrefix but never cookiePrefix, so @better-auth/expo falls back to "better-auth" and its startsWith gate rejects the api's cookies. That gate guards all cookie persistence, so the generated Expo client never stores a session. It failed the same way on init.*; the only thing this commit changes is that the eventual fix must use init-api, not AUTH_COOKIE_PREFIX.
Technical details
# Expo `cookiePrefix` is unset, so mobile never persists an api session
## Affected sites
- `packages/auth/src/expo/client.ts:4-10` — forwards `scheme` and `storagePrefix`, both
`AUTH_COOKIE_PREFIX`, but not `cookiePrefix`
- `turbo/generators/commands/connect-backend.ts:534` — points the generated Expo client at
`http://localhost:3000`, i.e. `apps/api`, which now issues `init-api.*`
## Evidence
`@better-auth/expo@1.6.25/dist/client.js`:
```js
const cookiePrefix = opts?.cookiePrefix || "better-auth"; // :269
// ...
if (hasBetterAuthCookies(setCookie, cookiePrefix)) { // :327 — gates ALL persistence
```
`hasBetterAuthCookies` (`:174-186`) is a `startsWith(prefix)` test on each `Set-Cookie` name
after stripping `__Secure-`. Neither `init.session_token` nor `init-api.session_token`
matches `"better-auth"`, so `storage.setItem(cookieName, ...)` never runs and
`$sessionSignal` never fires. `getOAuthStateValue` (`:116-129`) reads
`<prefix>.oauth_state` under the same assumption.
## Required outcome
- The Expo client should recognize the cookies of whichever instance it is pointed at, or the
gap should be tracked so it is not mistaken for fixed by this commit.
## Suggested approach (optional)
- Forward a `cookiePrefix` through `packages/auth/src/expo/client.ts`. The option accepts an
array, so `[AUTH_COOKIE_PREFIX, \`${AUTH_COOKIE_PREFIX}-api\`]` covers both backends without
the caller having to know which one it talks to.
- Do NOT rename `AUTH_COOKIE_PREFIX` itself to reach this: `packages/auth/src/expo/client.ts:6`
reuses it as the deep-link `scheme`, which must keep matching `APP_ID` in
`apps/mobile/app.config.js`. The api-side override in this commit correctly avoids that trap.
## Open questions for the human
- Is the Expo auth template exercised end to end anywhere, or is it copy-once scaffolding that
a consumer is expected to finish? If the latter, a line in the generator output may be a
better home for this than a code change.Claude Opus | 𝕏

This started as "optimize the portless setup" and ended with deleting it. Investigating the optimization angles showed the proxy wasn't paying for its complexity: its flagship package route (
db.init.localhost) had never worked —drizzle-kit studioserves a headless 404 API whose real UI lives atlocal.drizzle.studio— and every workspace carried a wrapper script, a config block,PORTLESS_URLenv plumbing, and rename tooling just to keep the hostnames alive.Every app already declared a unique
PORTdefault in its.env.schema— the same assignments the repo used before portless landed in #129 — so dev servers now just run their framework commands on those fixed ports:Package dev servers take the 4000 block sequentially: Drizzle Studio on
4000(UI atlocal.drizzle.studio), email preview on4001, Inngest on4002. Docker Compose is untouched — redis, postgres, and minio on8000–8003, as before.Along the way:
packages/workflowsthat polls the api at a stablehttp://localhost:3000/workflows. A Docker Compose variant was built and tested first, then rejected: it requiresnetwork_mode: hostto call back into host dev servers, which is firewall- and platform-fragile for a distributed template. Drizzle Gateway in compose was likewise tested and rejected in favor of Studio's code-aware schema view.PORTLESS_URLfallback logic in the api base-url/CORS helpers and the app auth config collapsed to plain env reads; the schema declarations, turbo env entry, and generated types are gone.bun template renameandaddno longer rewrite hostnames or route names;scripts/template/portless.tsis deleted.One deliberate trade-off: two projects' dev fleets can no longer run at the same time without port collisions — that was portless's genuine feature, and this chooses simplicity over it.
Written by Claude Fable 5 via Claude Code.
🤖 Generated with Claude Code