Commit 01e1c0d
authored
feat: add auto dev server download feature (#262)
* feat: auto dev server
* refactor: reorganize dev-server
* fix: restore env vars after dev-server tests to avoid polluting other test files
The dev-server test file was deleting QSTASH_TOKEN at module scope, which
permanently removed it from process.env for all subsequent test files in the
same bun test process, causing 48 test failures (401 auth errors).
* fix: reject spawnServer promise on any pre-readiness exit
Previously, if the dev server process exited with code 0 or was killed
by a signal before printing the readiness line, the promise would hang
forever. Now any pre-readiness exit rejects immediately.
* feat: add Windows support for dev server binary
* chore: add comment
* chore: update lock file
* chore: default dev server port to 8080
* fix: wipe dev server cache dir when cached version is stale
* feat: forward dev server stdout/stderr with dim [QStash CLI] prefix
* chore: drop duplicate 'Server ready' log after spawn
* fix: swallow stream errors on dev server stdio forwarding
* refactor: register dev server signal handlers once across spawnServer calls
* feat: surface qstash dev server startup failures with cleaned-up errors
* fix: detect qstash 2.36+ readiness line with no URL suffix
* fix: unref dev server child so it doesn't pin parent's event loop
A one-shot script that publishes once via devMode would hang forever
because the spawned dev server kept the loop alive. unref the child and
its stdio streams; signal/exit handlers still kill the child cleanly.
* feat: support devMode in Next.js verifySignature wrappers
verifySignature, verifySignatureEdge, and verifySignatureAppRouter now
accept a devMode flag that's forwarded to the underlying Receiver. When
on, the Receiver auto-fills the dev server's well-known signing keys, so
the wrappers can be used for local development without setting any
QSTASH_*_SIGNING_KEY env vars.
* chore: add dev mode demo routes and round-trip test to nextjs example
Three new app router routes: /dev publishes to example.com via
devMode: true, /dev/send publishes to /dev/receive (loops through the
local qstash dev server), and /dev/receive verifies signatures via
verifySignatureAppRouter({ devMode: true }). dev.test.ts hits all three
to confirm the publish/verify round trip works against a real next dev.
* fix: stop dev server in tests so port 8080 is free for later test files
Bun runs every src/**/*.test.ts in the same process. The integration
block spawned the QStash binary on the default port and never killed
it, so every subsequent test that bound 8080 (workflow test-utils)
failed with EADDRINUSE — 71 cascading CI failures and an orphan
qstash process at job cleanup.
* fix: hide dev-server Node-only globals from edge bundlers
The Next.js Edge Runtime statically scans for direct `process.X` references
and `process.ts`'s `process.stdout/process.on/process.exit` plus the
`process.release?.name` checks in `getRuntime` and `registerQStashDev`
flagged the dev-server bundle even though those branches are unreachable
at runtime.
Mirror the same evasion already used for `node:*` modules: lazy `import()`
the spawn-side `process.ts`, and read the `process` global through a
dynamically-keyed property so the analyzer can't follow it.
* fix: hide process.versions / process.env from edge bundlers in utils.getRuntime
Pre-existing telemetry helpers in src/client/utils.ts directly read
process.versions, process.version and process.env. Once nextjs.mjs no
longer fails earlier, Next.js's Edge Runtime analyzer flags these
references in chunks reachable from app/edge/route.ts and Vercel
deploys reject the build.
Same indirection as src/dev-server/index.ts: read the global through a
dynamically-keyed property so the analyzer can't follow it.
* fix: type request param in dev/receive example to fix nextjs build
* fix: route dev-server's process.X access through globalThis indirection
The previous fix used a dynamic `import("./process")` to keep process.ts
out of edge bundles, but tsup inlines that file into the same chunk and
the bundled output ended up with `import("./process")` against a path
that doesn't exist in the published package — Vercel's prerender step
crashed with ERR_MODULE_NOT_FOUND.
Use the same indirection that already worked elsewhere: read the
`process` global through a dynamically-keyed property
(`globalThis["pro" + "cess"]`) so Next's analyzer can't see direct
`process.stdout` / `process.on` / `process.exit` references at build
time, while keeping process.ts statically imported and bundled.
* fix: skip dev server spawn during next build / production
Next.js evaluates route modules during \`next build\` to prerender pages.
A top-level \`new Client({ devMode: true })\` therefore fired
ensureDevelopmentServer at build time, which downloaded and tried to
extract the QStash binary on Vercel's builder — and a corrupt extract
crashed the prerender step.
Short-circuit ensureDevelopmentServer when NEXT_PHASE is
phase-production-build or NODE_ENV is production so the dev server only
runs under \`next dev\`.
* fix: mark dev demo routes as dynamic so they don't run at build time
* chore: dim [QStash Dev] prefix and clean up dev-mode warnings
Console output now shares one visual style across SDK-emitted lines
([QStash Dev], dim) and stdout/stderr forwarded from the spawned
binary ([QStash CLI], dim). Reword the dev-mode 'ignoring config'
warnings without em dashes.
* ci: re-enable nextjs-local-build, run dev.test.ts end-to-end
The job was disabled in 0dd9dff due to a TypeScript build error in the
example (NextRequest-typed handler not assignable to verifySignatureAppRouter,
since its handler param accepts Request and NextRequest as a union — function
contravariance rejects the narrower type against the wider branch).
Switched the local-package install to `pnpm add @upstash/qstash@file:../../dist`
to match the cloudflare-workers job and workflow-js convention. The bare-path
form (`pnpm install @upstash/qstash@../../dist`) symlinks rather than copies,
which leaves two resolvable copies of `next` and breaks TS type identity.
Also marked /ci as force-dynamic so it doesn't hit the QStash API at build
time, and added a dev.test.ts step that exercises the full devMode chain
(SDK auto-spawn → published → signed delivery → receiver verify).
* ci: supply dummy signing keys to nextjs-local-build
The /serverless and /edge example routes call verifySignatureAppRouter()
at module load. Without QSTASH_CURRENT_SIGNING_KEY / QSTASH_NEXT_SIGNING_KEY
in env it throws on page-data collection, breaking the build. This is the
underlying cause of the original "local build issue" that disabled this
job in 0dd9dff. Vercel sets the secrets, so the deployed test never hit it.
The dummies don't matter — no signature verification runs in this job;
/dev/receive uses devMode: true which supplies its own dev keys.
* fix: address review feedback on dev-server flow
- platforms/nextjs.ts: signing-key guards now honor QSTASH_DEV env via
shouldUseDevelopmentMode, not just config.devMode
- src/dev-server/health.ts: simplify checkDevServerReachable — ping every
request, fail fast with a clear error, dedupe guidance log once per process
- src/client/utils.ts: explain the "pro" + "cess" split-string trick inline
- examples/nextjs/app/ci/route.ts: clarify why force-dynamic is needed
* test: bump dev.test.ts timeouts to 60s for dev-server cold-start
The first request triggers a binary download + spawn that easily exceeds
Bun's default 5s timeout on fresh CI runners.
* ci: pin pnpm to v9 in nextjs-deployed job
pnpm v10 rejects transitive postinstall scripts (unrs-resolver) without
explicit approval, breaking the deploy step. Matches the v9 pin already
used in nextjs-local-build.1 parent fa423ae commit 01e1c0d
28 files changed
Lines changed: 1497 additions & 24 deletions
File tree
- .github/workflows
- examples/nextjs
- app
- ci
- dev
- receive
- send
- edge
- serverless
- platforms
- src
- client
- multi-region
- dev-server
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
134 | 134 | | |
135 | 135 | | |
136 | 136 | | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | 137 | | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
141 | 146 | | |
142 | 147 | | |
143 | 148 | | |
| |||
168 | 173 | | |
169 | 174 | | |
170 | 175 | | |
171 | | - | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
172 | 182 | | |
173 | 183 | | |
174 | 184 | | |
| |||
179 | 189 | | |
180 | 190 | | |
181 | 191 | | |
182 | | - | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
183 | 201 | | |
184 | 202 | | |
185 | 203 | | |
186 | 204 | | |
187 | 205 | | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
188 | 215 | | |
189 | 216 | | |
190 | 217 | | |
| |||
206 | 233 | | |
207 | 234 | | |
208 | 235 | | |
209 | | - | |
| 236 | + | |
210 | 237 | | |
211 | 238 | | |
212 | 239 | | |
| |||
228 | 255 | | |
229 | 256 | | |
230 | 257 | | |
231 | | - | |
| 258 | + | |
232 | 259 | | |
233 | 260 | | |
234 | 261 | | |
| |||
Binary file not shown.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
| 88 | + | |
| 89 | + | |
88 | 90 | | |
89 | 91 | | |
90 | 92 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
4 | 7 | | |
5 | 8 | | |
6 | 9 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
2 | | - | |
| 1 | + | |
| 2 | + | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
0 commit comments