feat: add Vercel deployment adapter#193
Conversation
Add @marko/run-adapter-vercel for deploying Marko Run apps to Vercel via
the Build Output API (v3), emitting a `.vercel/output` directory.
- Node.js Serverless Functions by default; `{ edge: true }` targets the
Edge runtime (webworker SSR target with the edge-light export condition)
- Node mode wraps the fetch handler with createMiddleware as the function
handler; static assets are served by Vercel's filesystem layer
- Generates functions/index.func with the appropriate .vc-config.json,
copies static assets to output/static, and writes a routing config.json
- Exposes platform info per runtime via VercelEdgePlatformInfo /
VercelNodePlatformInfo
- Adds run-package test fixtures for both modes (preview skipped; it needs
the Vercel CLI)
🦋 Changeset detectedLatest commit: 8406d06 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis PR introduces a new Changes
Sequence Diagram(s)sequenceDiagram
participant ViteBuild
participant vercelAdapter
participant FileSystem
participant PreviewEntry
participant MiddlewareEntry
ViteBuild->>vercelAdapter: buildEnd()
vercelAdapter->>FileSystem: copy static assets, write function files, .vc-config.json, config.json
PreviewEntry->>FileSystem: verify output/functions/index.func/index.js exists
PreviewEntry->>MiddlewareEntry: dynamic import(index.js)
MiddlewareEntry-->>PreviewEntry: handle request or decline
PreviewEntry-->>PreviewEntry: respond 404/500 on fallback
Related issues: None specified. Related PRs: None specified. Suggested labels: enhancement, package: adapter-vercel, documentation Suggested reviewers: DylanPiercey 🥕
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/adapters/vercel/package.json`:
- Around line 16-22: The package entrypoints in the vercel adapter are pointing
at src/index.ts, but only dist is published. Update the package.json
exports["."] and types fields to reference the built output in dist instead of
source so consumers can resolve the package entry and generated CJS/ESM
artifacts through the published files.
In `@packages/adapters/vercel/README.md`:
- Around line 68-72: The example GET edge handler uses the wrong second-argument
shape, so destructuring { platform } there will be undefined. Update the sample
so GET reads platform from the handler context argument instead of the next
argument, and keep the waitUntil(logRequest()) call using the platform value
obtained from that context.
In `@packages/adapters/vercel/scripts/build.ts`:
- Around line 11-12: The Vercel adapter is declaring a Node 14 target while
`packages/adapters/vercel/src/index.ts` relies on `fs.cp()`, which is not
available there. Update the adapter build/runtime contract by either raising the
supported Node version in `build.ts` along with the package `engines`/docs, or
replace the `fs.cp()` usage in the Vercel adapter with a Node 14-compatible copy
implementation so the declared target and runtime code match.
In `@packages/adapters/vercel/scripts/importMetaURL.js`:
- Around line 1-2: The injected CJS helper in importMetaURL.js is exporting a
URL object instead of a string, which breaks consumers like
path.dirname(import.meta.url) during initialization. Update the __importMetaURL
export in this helper to return a string value rather than a URL object, keeping
the change localized to the __importMetaURL symbol so downstream imports receive
the expected format.
In `@packages/adapters/vercel/src/index.ts`:
- Around line 185-193: The assertVercelCLI function currently calls
process.exit(1) when execSync("vercel --version") fails, which makes this
library-level failure uncatchable; update the catch block to throw an error
instead of exiting so callers can handle it through the normal error path. Keep
the existing warning context in assertVercelCLI, but replace the parent-process
termination with a thrown exception that includes the missing-CLI message.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 59c366dc-359e-4a9a-8ba0-b79e8b301b0f
⛔ Files ignored due to path filters (3)
package-lock.jsonis excluded by!**/package-lock.jsonand included by**packages/run/src/__tests__/fixtures/vercel-adapter-edge/__snapshots__/dev.expected.mdis excluded by!**/__snapshots__/**and included by**packages/run/src/__tests__/fixtures/vercel-adapter-node/__snapshots__/dev.expected.mdis excluded by!**/__snapshots__/**and included by**
📒 Files selected for processing (26)
.changeset/vercel-adapter.mdpackages/adapters/vercel/README.mdpackages/adapters/vercel/package.jsonpackages/adapters/vercel/scripts/build.tspackages/adapters/vercel/scripts/importMetaURL.jspackages/adapters/vercel/src/default-edge-entry.tspackages/adapters/vercel/src/default-node-entry.tspackages/adapters/vercel/src/index.tspackages/adapters/vercel/src/types.tspackages/adapters/vercel/tsconfig.jsonpackages/run/src/__tests__/fixtures/vercel-adapter-edge/.gitignorepackages/run/src/__tests__/fixtures/vercel-adapter-edge/.marko-run/routes.d.tspackages/run/src/__tests__/fixtures/vercel-adapter-edge/package.jsonpackages/run/src/__tests__/fixtures/vercel-adapter-edge/src/components/counter.markopackages/run/src/__tests__/fixtures/vercel-adapter-edge/src/routes/+page.markopackages/run/src/__tests__/fixtures/vercel-adapter-edge/test.config.tspackages/run/src/__tests__/fixtures/vercel-adapter-edge/tsconfig.jsonpackages/run/src/__tests__/fixtures/vercel-adapter-edge/vite.config.tspackages/run/src/__tests__/fixtures/vercel-adapter-node/.gitignorepackages/run/src/__tests__/fixtures/vercel-adapter-node/.marko-run/routes.d.tspackages/run/src/__tests__/fixtures/vercel-adapter-node/package.jsonpackages/run/src/__tests__/fixtures/vercel-adapter-node/src/components/counter.markopackages/run/src/__tests__/fixtures/vercel-adapter-node/src/routes/+page.markopackages/run/src/__tests__/fixtures/vercel-adapter-node/test.config.tspackages/run/src/__tests__/fixtures/vercel-adapter-node/tsconfig.jsonpackages/run/src/__tests__/fixtures/vercel-adapter-node/vite.config.ts
- Add package.toggle.json so publish points exports/types at dist/ - Make the injected import.meta.url shim a string (.href) for the CJS build - Fix the README platform example to read from context.platform - Replace fs.cp() with a recursive copy helper so the adapter works on older Node versions (fs.cp is unavailable before Node 16.7) - Throw instead of exiting the process when the Vercel CLI is missing
Use `shell: true` only on Windows (where `vercel` is a `.cmd` shim that requires it). On POSIX the CLI is spawned directly, so `close()` kills the CLI itself instead of an orphaned shell wrapper and argument values are no longer re-parsed by the shell. Also handle the child's error event.
Clarify that Marko Run automatically uses an installed adapter with no Vite config, and add a Deploying section right after installation covering the Git integration and `npm run build` + `npx vercel deploy --prebuilt`.
`marko-run preview` previously shelled out to `vercel dev`, which requires linking to a real Vercel project (hence the `--yes` prompt) and, worse, cannot serve a Build Output API v3 directory at all — it removes `.vercel/output` and rebuilds from source, failing when there is no build script it can run. Replace it with a small in-process server that serves the generated `.vercel/output` directly: static assets from `static/` (the `filesystem` handler) and everything else through the built Node/Edge function, mirroring the routing the adapter's `config.json` describes. Preview now runs entirely locally with no Vercel account, project link, or CLI required. Open sockets are tracked and destroyed on `close()` so keep-alive connections don't block shutdown. The node/edge preview fixtures no longer need to be skipped, so they now exercise this path in CI.
Vercel deprecated Edge Functions in 2025 in favor of the Node.js runtime on Fluid Compute, so the adapter's `edge` option targeted a runtime Vercel is winding down. Remove it and everything downstream: the edge entry, the webworker SSR build config, the edge `.vc-config.json` variant, the `VercelEdgePlatformInfo` type, and the edge preview path. The adapter now takes no options and always builds for the Node.js runtime. The preview server loses its runtime branch and always invokes the built function as a `(req, res)` listener, still serving static assets from the build output first.
Replace the hand-rolled in-process preview server with the same pattern the base adapter uses: `startPreview` delegates to the base adapter with a small `preview-entry.mjs`, which it spawns as a child process (gaining `--env` file loading and node args like `--inspect` for free). The entry mirrors the base `default-entry.mjs` — `serve-static` + `compression` over `.vercel/output/static` with immutable asset caching — and falls back to the built function imported from `.vercel/output/functions`, preserving Vercel's static-first routing. This deletes the custom HTTP server, static file resolver, and MIME map in favor of the off-the-shelf libraries already used elsewhere in the repo.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/adapters/vercel/src/index.ts (1)
49-71: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftCopy all SSR chunks into the Vercel function output.
builtEntries[0]only moves the SSR entry chunk; dynamicimport()calls can still emit sibling chunks, and those files are not copied into.vercel/output/functions/index.func/, so the deployed function can fail at runtime. Copy the full SSR output or force a single-file SSR bundle withinlineDynamicImports.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/adapters/vercel/src/index.ts` around lines 49 - 71, The Vercel adapter’s buildEnd currently only moves builtEntries[0] into the function output, so any SSR sibling chunks emitted by dynamic imports are left behind. Update the buildEnd flow in the Vercel adapter to copy the full SSR build output into .vercel/output/functions/index.func/ or change the SSR bundling strategy to produce a single file with inlineDynamicImports, making sure the server entry and all dependent chunks are included.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/adapters/vercel/src/preview-entry.mjs`:
- Around line 47-77: The dynamic import in preview-entry.mjs is not handling
rejection, so a module init failure from import(pathToFileURL(funcEntry).href)
can become an unhandled promise rejection and prevent the server from starting.
Add a rejection path around the existing top-level import(...).then(...) flow in
preview-entry.mjs that logs the import error clearly and exits or returns a
500-style failure instead of silently failing; keep the handling near the
import(pathToFileURL(funcEntry).href) and createServer bootstrap so the startup
error is surfaced to the user.
---
Outside diff comments:
In `@packages/adapters/vercel/src/index.ts`:
- Around line 49-71: The Vercel adapter’s buildEnd currently only moves
builtEntries[0] into the function output, so any SSR sibling chunks emitted by
dynamic imports are left behind. Update the buildEnd flow in the Vercel adapter
to copy the full SSR build output into .vercel/output/functions/index.func/ or
change the SSR bundling strategy to produce a single file with
inlineDynamicImports, making sure the server entry and all dependent chunks are
included.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 72032eb4-b87d-4ed4-9ed9-d1d894bbe0f7
⛔ Files ignored due to path filters (2)
package-lock.jsonis excluded by!**/package-lock.jsonand included by**packages/run/src/__tests__/fixtures/vercel-adapter-node/__snapshots__/preview.expected.mdis excluded by!**/__snapshots__/**and included by**
📒 Files selected for processing (7)
packages/adapters/vercel/README.mdpackages/adapters/vercel/package.jsonpackages/adapters/vercel/scripts/build.tspackages/adapters/vercel/src/index.tspackages/adapters/vercel/src/preview-entry.mjspackages/adapters/vercel/src/types.tspackages/run/src/__tests__/fixtures/vercel-adapter-node/test.config.ts
💤 Files with no reviewable changes (2)
- packages/adapters/vercel/src/types.ts
- packages/run/src/tests/fixtures/vercel-adapter-node/test.config.ts
✅ Files skipped from review due to trivial changes (1)
- packages/adapters/vercel/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/adapters/vercel/package.json
cc7e560 to
6141d34
Compare
Description
Adds a new adapter package,
@marko/run-adapter-vercel, for previewing and deploying Marko Run apps to Vercel via the Build Output API (v3). Runningmarko-run buildemits a.vercel/outputdirectory, which Vercel uses directly — no output directory configuration required.nodewithnoExternal: true; the function handler wraps the app'sfetchhandler withcreateMiddleware(the same helper the Node adapter uses) so it runs as a standard(req, res)listener. Static assets are served by Vercel's filesystem layer. (The Edge runtime is intentionally not supported — Vercel has deprecated Edge Functions in favor of the Node.js runtime on Fluid Compute.)buildEndassembles the Build Output API tree:functions/index.func/with anodejs20.x.vc-config.json(Nodejslauncher), apackage.json(type: module), and the bundled entry;static/containing the client assets; and a routingconfig.jsonthat serves the filesystem first and falls back to the function.marko-run previewserves the built.vercel/outputlocally with no Vercel account, project link, or CLI required. It delegates to the base adapter's preview with a small entry that servesstatic/viaserve-static+compression(immutable caching for/assets/*) and falls back to the built function — mirroring the static-first routing in the generatedconfig.json. This also means--envfiles and node args (e.g.--inspect) work like the other adapters.VercelNodePlatformInfotype.Also adds a test fixture to the
@marko/runpackage (vercel-adapter-node) covering both dev and preview — because preview runs on plain Node, CI exercises the full build → serve → hydrate flow with Playwright.Motivation and Context
Vercel is one of the most widely used deployment platforms, and Marko Run shipped no adapter for it. Targeting the Build Output API gives a zero-config deploy path alongside the existing Node, static, and Netlify adapters.
An earlier revision shelled out to
vercel devfor preview, butvercel devcannot serve Build Output API directories at all — it requires linking a real Vercel project, deletes.vercel/output, and rebuilds from source. Preview is now a small local server over the built output instead. Anedgeoption was also dropped since Vercel deprecated the Edge runtime.Notes for reviewers
The preview entry passes a
nextcallback to the function's middleware so unmatched routes get a404and errors a500instead of hanging the request.Screenshots (if appropriate):
Checklist: