Repository navigation
feat(dev): front the app's dev server with the SDK header contract - #659
Open
netanelgilad wants to merge 1 commit into
Open
netanelgilad wants to merge 1 commit into
netanelgilad wants to merge 1 commit into
Conversation
`base44 dev` now serves the whole app on one origin. It answers `/api/apps` itself and forwards everything else, including the app's own `/api` routes and its HMR websocket, to the app dev server. Forwarded requests carry `Base44-App-Id`, `Base44-Api-Url` and a service-role credential, and the caller's copies of those headers are dropped. Full-stack apps can then build an SDK client with `createClientFromRequest` locally, as they do in production. Websockets are relayed at the frame level through `ws`, because the Bun compiled binary never writes bytes from a node:http upgrade socket to the wire. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tmb4PCrrV2SLRpBjkGAgXJ
Contributor
🚀 Package Preview Available!Install this PR's preview build with npm: npm i @base44-preview/cli@0.1.32-pr.659.5613fbcPrefer not to change any import paths? Install using npm alias so your code still imports npm i "base44@npm:@base44-preview/cli@0.1.32-pr.659.5613fbc"Or add it to your {
"dependencies": {
"base44": "npm:@base44-preview/cli@0.1.32-pr.659.5613fbc"
}
}
Preview published to npm registry — try new features instantly! |
netanelgilad
pushed a commit
that referenced
this pull request
Oct 11, 2026
The dev-server front door and its tests now live in #659. This branch keeps only the deploy routing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tmb4PCrrV2SLRpBjkGAgXJ
netanelgilad
added a commit
that referenced
this pull request
Oct 21, 2026
…h it (#643) * feat(dev): front the app's dev server with the SDK header contract base44 dev now serves the whole app on one origin. /api/apps stays with the local backend; everything else — the app's own /api routes included — is forwarded to its dev server carrying Base44-App-Id, Base44-Api-Url and a service-role credential, with the caller's copies of the platform-owned headers dropped first. Full-stack apps can build a client with createClientFromRequest locally, the way they do in production, instead of 500ing on every SSR loader and server function. The dev server's port is read off the URL it announces at startup (the front door's own port skipped, since the Vite plugin logs it as its proxy target), and a request that comes back marked as forwarded answers 508 naming the Vite proxy rather than looping. Websocket upgrades follow the same split, so HMR reaches the app and the entity-events socket stays local. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tmb4PCrrV2SLRpBjkGAgXJ * feat(deploy): ship every site through the deployments API, and publish it `BASE44_DEPLOYMENTS_API` is gone. The lane it gated is now the only transport `site deploy` has, and `--git-hash` / `--concurrency` are always registered rather than appearing only when an env var was set. Setting the variable does nothing; the sandbox that sets it needs no change. `base44 deploy` ships its site through the same lane instead of the archive upload, so a full-stack project's worker is no longer silently dropped, and a build carries the commit it was made at. Because that lane only builds, the deploy then publishes what it deployed (`--no-publish` opts out) — the archive upload it replaces always left the app live, and a deploy that quietly stopped doing so would be a worse default than a flag. `site deploy` deliberately does NOT publish, and gains `--publish` instead: the platform's own build step runs it against builder-managed apps and repoints production itself, so publishing there would fail exactly those apps. When the server does refuse a publish for that reason (`app_publishes_from_builder`), the deploy reports it and still succeeds — its resources and its build did land. The archive upload keeps the two callers that have no commit to be addressed by: `base44 create`'s scaffold deploy, and a `base44 deploy` in a project with no git history, which warns and falls back. A worker build has no archive form, so there a missing commit is fatal. Depends on base44-dev/apper#26921 for the publish endpoint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tmb4PCrrV2SLRpBjkGAgXJ * fix(dev): relay the app's websockets through `ws`, so the binary forwards them too The compiled binary answered every HMR upgrade with an error: under Bun, bytes written to the socket a node:http `upgrade` listener receives never reach the wire, so neither http-proxy's tunnel nor a raw-socket pipe can complete a handshake from it. `ws` is the one path Bun maps onto its native websockets — it is how Socket.IO's realtime socket already worked there — so the front door now accepts the browser's socket with it and relays frames to a `ws` client connected to the dev server, carrying the same SDK headers the HTTP path does. The two paths now share one `sdkHeaders()` definition. The HMR test asserted only that the socket opened, which a relay satisfies on its own, so it now waits for the dev server's own frame and checks what that frame says the upgrade arrived with. The fixture also tolerates a reset socket, as a real dev server does: Bun's client resets rather than closing, and the unhandled error killed the fixture and with it the dev environment. Adds `ws` (8.21.0, already locked through miniflare) and `@types/ws` as devDependencies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tmb4PCrrV2SLRpBjkGAgXJ * chore: move the `base44 dev` front door to its own PR The dev-server front door and its tests now live in #659. This branch keeps only the deploy routing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tmb4PCrrV2SLRpBjkGAgXJ * feat(deploy): publish a commit through the builder's /deploy The publish moved server-side (base44-dev/apper#26921): instead of `POST deployments/{git_hash}/publish`, the CLI calls the builder's own `POST /deploy` with `{ git_commit_hash }`, then reads the app's URL from `published-url` (the deploy response carries none). A builder-managed app now refuses with `error.code` `git_commit_hash_not_supported`, which is what "production left where it was" keys on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tmb4PCrrV2SLRpBjkGAgXJ * fix(deploy): don't read the publish response body `POST /deploy` answers a logged-in user (a `base44 login` token) with the editor's full app, and only an API key gets the slim record carrying `git_commit_hash` and `deployed_at`. Requiring those fields failed `base44 deploy` after the publish had already succeeded. The CLI knows the commit it sent and reads the URL from `published-url`, so it no longer parses the body. The testkit's `/deploy` mock now answers with the full-app shape. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tmb4PCrrV2SLRpBjkGAgXJ * refactor(cli): share one --git-hash parser `deploy`, `site deploy` and the versions commands each carried the same argParser; it now lives in `cli/utils/git-hash.ts`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tmb4PCrrV2SLRpBjkGAgXJ * refactor(cli): drop --no-publish from base44 deploy It read as covering every resource deploy ships, but only the site waits on a publish. site deploy keeps --publish as its opt-in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tmb4PCrrV2SLRpBjkGAgXJ * fix(cli): site deploy says when it didn't publish, and deploys a commitless static build as before Without a commit, a static build goes through the archive upload and goes live, as site deploy did before; a worker build still needs one. When it leaves production alone, it points at --publish. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tmb4PCrrV2SLRpBjkGAgXJ * refactor(cli): take the deployments API only for an explicit --git-hash No commit is inferred from the local checkout any more. Without --git-hash, deploy and site deploy keep the archive upload and go live as they always did; a full-stack build, which that upload cannot carry, fails asking for the flag, and deploy checks that before shipping any resource. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tmb4PCrrV2SLRpBjkGAgXJ --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Description
base44 devnow acts as the single front door for the whole app, the way the platform serves it in production: the CLI origin answers/api/apps/**locally (entities, functions, auth, media) and forwards everything else — including the app's own/apiroutes and the HMR websocket — to the app's dev server. Forwarded requests carry the SDK header contract (Base44-App-Id,Base44-Api-Url, a locally minted service-roleBase44-Service-Authorization) after the caller's copies of the platform-owned headers are dropped, so full-stack apps can callcreateClientFromRequestlocally just as they do in production. Previously those headers were absent underbase44 dev, so SSR pages and server functions could not build an SDK client at all.Related Issue
None (internal: base44-dev/apper#25003). Split out of #643, which keeps the deploy-routing changes.
Type of Change
Changes Made
createAppServerProxy(src/cli/dev/dev-server/routes/app-server.ts): HTTP proxy + websocket relay into app-owned code. Strips the visitor'sBase44-App-Id/Base44-Api-Url/Base44-Service-Authorization/Base44-State/Base44-Functions-Version/X-Base44-App-Url/X-Data-Envbefore setting the CLI's own, so a caller cannot point the app's SDK (and the credential travelling with it) at a host they chose.Authorizationpasses through untouched — it is the visitor the app's server code acts as.main.ts:PLATFORM_PATH_PATTERN(/api/apps…) stays local; everything else goes to the app dev server. Websocket upgrades follow the same split —REALTIME_PATH(now exported fromrealtime.ts) keeps the entity-events socket, all other upgrades are relayed.wsrather than tunnelled: under Bun (the compiled binary), bytes written to anode:httpupgrade socket never reach the wire. Client frames sent before the upstream opens are buffered and flushed onopen.Base44-Dev-Forwarded; seeing it come back answers508with the fix (narrow the Vite proxy to/api/apps, or update@base44/vite-plugin) instead of looping.serve-runner.ts):ServeRunnerreads the firsthttp://localhost:<port>the child announces on startup, stripping ANSI first (Vite bolds the port) and skipping the front door's own port, which the Vite plugin echoes as its proxy target.serve-command-runner.tsthreadsonOrigin/ignorePortthrough.dev.ts: wires the origin promise (120s startup timeout with a clear error), passesappServertocreateDevServer, and the outro now printsYour app is available at <cli url>instead of telling users to open the frontend dev server's URL.docs/dev-server.mdtopic guide, linked fromdocs/AGENTS.md; CHANGELOG entry under Changed.ws+@types/wsadded as devDependencies (bundled, per the zero-dependency distribution rule).Testing
npm test)New
tests/cli/dev-app-server.spec.tsdrives the realdevcommand against a newwith-app-serverfixture (a node dev server that announces a Vite-shaped, ANSI-bolded URL and echoes what each request arrived with). Six cases: SDK header injection, dropping the caller's copies, the app's own/api/timeroute being forwarded,/api/appsstaying local, the HMR socket relaying with headers, and the entity-events socket staying local.waitForDevServerin the testkit was updated for the new outro wording. Typecheck, lint and knip are clean locally; the dev specs pass except two pre-existingdev.speccases that also fail onmainin this sandbox (no IPv6) — hence the unchecked box.Checklist
docs/(AGENTS.md) if I made architectural changesAdditional Notes
User-visible workflow change: open the URL
base44 devprints, not the frontend dev server's own URL. Opening the frontend dev server directly still works for pure-frontend apps but bypasses the header contract, so SSR/server-function code will not get an SDK client.🤖 Generated by Claude | 2026-10-11 12:22 UTC | 5613fbc