Jeff See
90d · built 2026-07-24
90-day totals
- Commits
- 64
- Grow
- 5.4
- Maintenance
- 4.3
- Fixes
- 2.9
- Total ETV
- 12.6
30-day trajectory
Last 30 days vs. the 30 days before. Up arrows on Growth and ETV mean improvement; up arrow on Fixes share means more time on fixes (worse).
↑+15.8 %
vs 19 prior
↓-7.3 pp
recent vs prior
↑+6.1 pp
recent vs prior
Daily performance
Daily ETV, stacked by Growth, Maintenance and Fixes.
Work-mix over time
Share of Growth / Maintenance / Fixes over a rolling 7-day window. Reads as 'where is effort flowing right now'.
Repository spread
Where this developer's commits land. Concentrated work (top1 > 80%) vs polymath spread (top1 < 30%).
Most impactful commits
Top 20 by ETV in the 90-day window.
- 2.9ETVAdd experimental container runtime (#16648) ## Summary Adds an experimental **container service runtime** to the Vercel CLI monorepo. Services configured with `runtime: "container"` can either: - **Build from a Dockerfile** — build the image, push it to the Vercel Container Registry (VCR), wait for readiness, and emit a digest-pinned image reference as build output, or - **Use a prebuilt image** — pass through an existing registry reference via `image` / entrypoint without building locally. This wires the new `@vercel/container` builder into service detection, `vercel build` output collection, and the Build Output API. ## TLDR This is a small tweak to add a new runtime for the existing services api, the container package is isolated and only enabled when the runtime is `container`, so should be safe to merge with minimal impact to production codepaths. ## How it works 1. **Detection** (`@vercel/fs-detectors`) — resolves container services from `vercel.json` / service config; Dockerfile entrypoints (`Dockerfile`, `Containerfile`, `*.dockerfile`) trigger a build, otherwise the entrypoint is treated as a prebuilt image ref. 2. **Build** (`@vercel/container`) — authenticates to VCR with `VERCEL_OIDC_TOKEN`, ensures the target repository exists, then build → login → push. 3. **Output** — writes a container function to `.vercel/output` with `runtime: "container"` and a digest-pinned VCR image reference as the handler. 4. **Readiness** — polls until the pushed digest is usable (remote manifest inspection locally via docker; skopeo with buildah on Vercel). ## Toolchain | Environment | Engine | Notes | |---|---|---| | Local `vercel build` | **docker** | Uses the local Docker daemon (OrbStack, Docker Desktop, etc.) | | Vercel build container | **buildah** | Daemonless; includes storage-driver selection for nested build cells and a permissive `registries.conf` for unqualified `FROM` lines | Override with `VERCEL_CONTAINER_ENGINE=docker|buildah`. ## Packages touched - **`@vercel/container`** (new) — builder implementation, VCR push/readiness, pluggable `ContainerEngine` interface - **`@vercel/build-utils`** — `ContainerImage` output type for BOA collection - **`@vercel/fs-detectors`** — container service runtime resolution - **`vercel` CLI** — container output wiring in `write-build-result`, config validation, builder dependency ## Test plan - [x] `@vercel/container` unit tests (`pnpm test-unit` in `packages/container`) - [x] `@vercel/fs-detectors` container service detection tests - [ ] Local `vercel build --cwd <project-with-container-service>` with linked project + fresh OIDC token - [ ] Deploy to Vercel with a container service Dockerfile and confirm image lands in VCR - [ ] Verify buildah path in the Vercel build container (not just local docker) --------- Co-authored-by: Cursor <cursoragent@cursor.com>github.com-vercel-vercel · 186014d1 · 2026-06-24
- 0.8ETV[cli][container] Fix `vercel dev` for top-level container builds (#16833) ## Summary `vercel dev` did not work for the `container` framework when a container is used as a **top-level build** (outside of `services`). The services flow works because the orchestrator drives `startDevServer` directly, but the legacy (non-services) dev pipeline runs `build()` → zip → `fun` first, and the container builder was never wired up for that path. Fixing it surfaced a chain of issues, all addressed here. Reproduced with a project containing only a `Containerfile.vercel` and a near-empty `vercel.json` (`framework: container` via zero-config). ## What was broken (in the order you'd hit it) 1. **"did not match any source files"** — the dev server didn't map the container preset's `<detect>` sentinel to a real Dockerfile, so no `BuildMatch` was created. 2. **`` Error: `vercel dev` cannot build container images from a Dockerfile ``** — `@vercel/container`'s `build()` threw in dev and treated a *prebuilt image* as the expected input. Containers have no prebuilt-image input; the local build belongs in `startDevServer`. 3. **`output.createZip is not a function`** — the dev pipeline (`builder-worker.cjs` + `executeBuild`) treated the container's OCI-image output (`runtime: "container"`) as a zip-based Lambda and tried to zip it / run it under `fun`. 4. **`Container "undefined" exited (code 125)`** — the dev path only looked for a bare `Dockerfile`, so a project using a `Dockerfile.vercel` / `Containerfile.vercel` marker failed even though deploys worked; and Docker-not-running was surfaced as a cryptic exit code with `"undefined"` as the name. 5. **Rebuild + restart on every request** — the container `startDevServer` result was missing `persistent: true` and had no reuse cache, so each HTTP request rebuilt the image and started a fresh container. ## Changes **`packages/cli`** - `getBuildMatches`: map the `container` framework `<detect>` sentinel to a discovered Dockerfile so a build match is created (mirrors the existing `python`/`node`/`go` handlers). - `executeBuild` + `builder-worker.cjs`: skip the zip-size check, `createZip`, and `fun` `createFunction` for `runtime: "container"` outputs — they're served by `startDevServer`. **`packages/container`** - `build()` no longer throws in dev; returns a stable local tag without pushing to a registry. - `startDevServer` / `resolveDevImage` now discovers the `Dockerfile.vercel` / `Containerfile.vercel` markers via a `findDockerfile` helper now **shared** with the build path. - Fail fast with a clear message when the Docker daemon is unreachable; container start errors now name the real container and include Docker's stderr. - Reuse a live container across requests (module-level cache keyed by service/work dir) and return `persistent: true`, matching `@vercel/backends`. ## Testing - `@vercel/container` unit tests: **37 pass**, including new tests for dev `build()` not pushing, `<detect>` → `Containerfile.vercel` discovery, daemon-down messaging, the fixed container name, and container reuse across requests (one `docker build` + one `docker run` for two requests). - `vercel` `test-unit`, `type-check`, and Biome lint pass for the touched files. (Pre-existing unrelated `undici` type errors in `fetch.ts` / `fetch-proxy.ts` are not part of this change.) - Manually verified `vercel dev` against a top-level container project (`Containerfile.vercel`): builds/runs once, then proxies subsequent requests without rebuilding. ## Follow-up (not in this PR) The non-services pipeline still runs an eager `build()` on the container at startup before `startDevServer` takes over (because the builder has no `shouldServe`). A cleaner future change would let builders that expose `startDevServer` with a non-zippable output skip the eager `build()` entirely, unifying further with the services flow.github.com-vercel-vercel · 262e9350 · 2026-06-29
- 0.6ETV[static-build] Support Vite Environments API for SSR projects (#16354)github.com-vercel-vercel · 7923d34b · 2026-05-19
- 0.6ETV[backends] Trace runtime deps from rolldown output via virtual FS (#16361) ## Summary Elysia/Hono apps deployed from pnpm monorepos were crashing at runtime with `Cannot find module` for dependencies whose package `exports` map points at different files for the `import` and `require` conditions — most notably `@planetscale/database` (`dist/index.js` for `import`, `dist/cjs/index.js` for `require`). The CJS variant was never traced, so it never made it into the lambda. ## Root cause `@vercel/nft` chooses the `require` vs `import` exports condition per file, based on whether the parent that pulls the dependency in parses as CJS or ESM. The post-build trace ran against the rolldown **output** for the function, but those output chunks only existed in memory as `FileBlob`s — they were never written to disk under `repoRootPath`. NFT therefore couldn't see the real entrypoint/chunks, so it traced from whatever it *could* read on disk (the TypeScript sources, which parse as ESM) and resolved `@planetscale/database` to the `import` target `dist/index.js`. At runtime the emitted bundle is CJS and does `require('@planetscale/database')`, which resolves to `dist/cjs/index.js` — a file that was never traced or uploaded. Packages like `drizzle-orm` survived only incidentally, because their own CJS chunks contain `require()` calls that dragged the `.cjs` variants in downstream. Anything reached purely through ESM-parsed sources but loaded as CJS at runtime silently dropped out. ## Fix Make the in-memory rolldown output visible to NFT during the post-build trace, so it traces the files the function will actually load. When `traceFiles` is enabled, the JS-like output `FileBlob`s are registered in an in-memory `virtualFiles` map keyed by absolute path, and `nodeFileTrace` is given `stat`/`readlink`/`readFile` overrides that serve those entries from memory (falling back to the real filesystem otherwise). The virtualized outputs are also seeded as trace roots. Because NFT now parses the real CJS bundle, it selects the `require` condition and pulls in `dist/cjs/index.js`. Nothing is written to disk — the blobs are served straight from memory for the duration of the trace — and virtual paths are skipped when collecting the resulting file list so we don't try to `lstat`/upload a file that doesn't exist on disk. ## Customer workaround Adding `"type": "module"` to the api `package.json` makes the emitted bundle ESM, so the runtime `import` matches the already-traced `import` target. Worth mentioning to affected users while this lands. ## Tests - New `unit.nft.test.ts`: traces a single in-memory entry `FileBlob` that `require()`s a dual CJS/ESM package and asserts NFT picks the CJS target (`dist/cjs/index.js`) and not the ESM one. - `21-elysia-vercel-repro` fixture (trimmed to the minimal `api → @repro/data → @planetscale/database` chain) builds and pins `@planetscale/database/dist/cjs/index.js` in the lambda via `files.json`. The trim also cuts fixture install time from ~30s to ~5s, so the build test comfortably fits inside the test timeout. ## Test plan - [x] `@vercel/backends` unit tests pass locally (incl. the `21-elysia-vercel-repro` build and the new `unit.nft.test.ts`) - [ ] CI passes - [ ] Re-deploy the customer repro against this build and confirm the lambda boots without `Cannot find module` - [ ] Spot-check the repro's `.vc-config.json` filePathMap: `@planetscale/database/dist/cjs/index.js` is present alongside the ESM variant --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>github.com-vercel-vercel · aeb5bfac · 2026-06-03
- 0.5ETV[CLI] Add opt-in config for native binary trampoline (#17209) ## Summary PR 1 of 2 for building native CLI binaries into the release flow. This PR adds the **opt-in gate** so the native `@vercel/vc-native-*` binary is never spawned unless the user explicitly opts in — a prerequisite before PR 2 wires `optionalDependencies` (which would otherwise make every install run the binary). ## Changes - **Config:** add flat `useNativeBinary` key to the global config (`@vercel/cli-config`). Adds a zod-free `@vercel/cli-config/paths` subpath so `vc.js` can read the flag on the startup hot path without loading zod/schemas (~40ms saved). - **Trampoline gate (`vc.js`):** only spawn the native binary when opted in — via the config flag or the `VERCEL_CLI_USE_NATIVE_BINARY` env override. Any failure to read config is treated as "not opted in", so we never spawn a binary the user didn't ask for. The `resolveNative()` check runs first and short-circuits, so there is no added cost when no native package is present. - **`vercel upgrade --binary [true|false]`:** toggles the opt-in, with telemetry. - **Auto-opt-in:** members of the `vercel` team are auto-opted-in, matched by the globally-unique team slug (no hardcoded team id), and only when the user has no explicit preference (an opt-out via `--binary false` is always respected). - **Tests:** trampoline gating (opt-in / opt-out) and the upgrade flag. ## Hot path note The opt-in read only runs when a native binary is actually resolvable. Today (no `optionalDependencies`) that's never, so the gate costs ~0ms. Once PR 2 lands, the zod-free `paths` read is ~7ms (dominated by `xdg-app-paths`); can be inlined further in PR 2 if a startup benchmark shows it matters. ## Follow-up PR 2: build binaries in the release flow, populate `optionalDependencies` before publishing `vercel`, and fail the publish if a binary build fails.github.com-vercel-vercel · 6e297457 · 2026-07-23
- 0.5ETVFix scope resolution for Northstar accounts with username/team-slug collision (#16779) ## Problem A Northstar account's username can equal the slug of its default team (e.g. user `my-user` whose default team `team_…` also has slug `my-user`). This collision produced two bugs: 1. **`vc projects ls` reports "No projects found" despite the team having projects.** `get-scope` resolved the default team for *display* (`vc whoami` showing "Active team: my-user") but never wrote it to `client.config.currentTeam` — which is the only thing `client._fetch` uses to attach a `teamId`. The default is only persisted at login (`updateCurrentTeamAfterLogin`), so on any invocation without it, requests went out with **no `teamId`** and were silently scoped to the resource-less personal account while the UI claimed the team. 2. **`--scope my-user` is rejected as "You cannot set your Personal Account as the scope."** In `index.ts`, the personal-account identity check ran *before* the team lookup, so a team whose slug matches the username could never be selected by name. ## Fix - `packages/cli/src/util/get-scope.ts`: apply the Northstar `defaultTeamId` to `client.config.currentTeam` when it isn't already set, so the effective request scope matches what the CLI displays. - `packages/cli/src/index.ts`: resolve `--scope <name>` against the user's teams *before* falling back to personal-account handling. Team-load failures are tolerated when the scope already matches the user's own identity, preserving existing personal-account behavior. ## Testing - Added unit tests in `get-scope.test.ts`: the default team is applied to `currentTeam`, and an explicitly selected team is not overridden. - `get-scope` (7), `project/list` (12), `switch` (3), `teams/switch` (12) suites pass. - `northstar.test.ts` fails identically with and without this change in the local sandbox (pre-existing interactive-login harness timeout); not a regression. ## Changeset `vercel` patch — included.github.com-vercel-vercel · 1c5d3b3b · 2026-06-24
- 0.5ETVci: speed up test discovery and add Datadog spans (#16148) ## Summary This PR changes CI test orchestration so we can see test timing in Datadog and reduce the time spent before tests can start. - Adds Datadog CI Visibility instrumentation with `dd-trace/ci/init`, scoped through `utils/dd-trace-ci-init.js` so Jest/Vitest get test spans without leaking the preload into child runtime/CLI processes. - Replaces post-hoc JUnit upload with native test spans tagged as `service:vercel-cli`, while preserving the existing custom Datadog metric key fallback (`DD_API_KEY || DATADOG_API_KEY`). - Speeds up `Find Changes` by avoiding full `pnpm install`; it now installs only `turbo@2.5.0`, uses `turbo query` for package metadata, and discovers test files locally from package scripts/patterns. - Consolidates Unit and E2E matrix generation into one PR-triggered workflow so affected-package detection and chunking run once. - E2E jobs now wait for the preview deployment tarball via GitHub deployment/status metadata and validate `/tarballs/vercel.tgz` before running. ## Current behavior - `Find Changes` runs on `pull_request` and computes both `unitTests` and `e2eTests` matrices. - Unit jobs start immediately after setup. - E2E jobs install/build as before, then wait for the deployment tarball and Python wheels before test execution. - A single `Summary` status now reports the combined setup, Unit, and E2E result for branch protection compatibility. ## Notes / follow-ups - The separate `.github/workflows/test-e2e.yml` workflow was removed; E2E jobs now live in `.github/workflows/test.yml`. ## Validation - Verified new discovery produces the expected broad matrix shape locally: - Unit: 141 chunks across 28 packages - E2E: 52 chunks across 14 packages - Spot-checked old E2E `Find Changes` output against new package/script/chunk coverage. - Verified the E2E tarball wait can resolve a deployment URL and set `VERCEL_CLI_VERSION` from `/tarballs/vercel.tgz` in CI. - Ran `pnpm lint` locally; only pre-existing warnings were reported. Made-with: Cursorgithub.com-vercel-vercel · 43c3296d · 2026-04-30
- 0.5ETV[cli] fix self-upgrade for pnpm 11 global installs and post-upgrade prompt (#16903) ### What Fixes three bugs in the CLI self-upgrade flow (`vc upgrade` and the post-command upgrade prompt): 1. **pnpm 11 global installs are misdetected as local npm installs.** The upgrade runs `npm i vercel@latest` in the user's **current working directory** — polluting whatever project (or home directory) they're standing in with a stray `node_modules`/`package.json` — and then reports success while the real installation is untouched. Reproduced on a **fully clean machine** (fresh fnm-installed Node, fresh standalone pnpm 11, single vercel install, latest CLI): ``` > [debug] Executing: npm i vercel@latest (cwd: /Users/jeffsee) > Success! Vercel CLI has been upgraded to v54.18.7 successfully! $ vc -v 54.17.3 # unchanged ``` 2. **Unrecognized install layouts default to executing a local install in the cwd.** This is the dangerous fallback underlying bug 1: any future layout change reintroduces the same failure. Running inside a pnpm/yarn workspace also fails loudly with `EUNSUPPORTEDPROTOCOL: Unsupported URL Type "workspace:"` because npm tries to resolve the surrounding workspace's dependency tree. 3. **The update prompt re-fires immediately after a successful `vc upgrade`.** The process still holds the pre-upgrade version in memory, so it asks "Would you like to upgrade now?" right after printing the upgrade success message. ### Why detection fails on pnpm 11 pnpm 11 moved global installs to isolated directories (`{PNPM_HOME}/global/v11/{hash}/`) backed by the global virtual store, so the install's realpath lives under `{storeDir}/links/...` ([pnpm 11 release notes](https://github.com/pnpm/pnpm/releases/tag/v11.0.0)). All three detection strategies in `get-update-command.ts` miss: - `pnpm root -g` answers with the *parent* of the isolated install dirs (not a `node_modules`), or — on machines upgraded from pnpm ≤10 — a stale `global/5` directory that pnpm does not migrate ([pnpm/pnpm#11528](https://github.com/pnpm/pnpm/issues/11528)), so `detectGlobalCliType` never matches - no lockfile exists above the install dir inside the store, so the lockfile scan falls through to `npm` - the store path does not contain `/pnpm/global/`, so the path heuristic misses, and the npm prefix check misses too → `global: false` Result: `{ cliType: 'npm', global: false }` → `npm i vercel@latest` in `process.cwd()`. ### How - **`isPnpmHomeInstall()`**: classify as pnpm + global when the unresolved entrypoint (`process.argv[1]`) or the realpath'd install dir is inside `PNPM_HOME`. This is layout-version independent — pnpm ≤10, 11, and future layouts all live under `PNPM_HOME` — and requires no shelling out. Checked before the existing query-based detection. - **Store-links path heuristic**: `isGlobalByPath` now also recognizes the pnpm 11 global virtual store pattern (`…/pnpm/store/…/links/…`) as a safety net when `PNPM_HOME` is not in the environment. - **Never classify as local without positive evidence**: a local (project-dependency) install always has a lockfile above the CLI's install location. Without that evidence, the upgrade now degrades to `npm i -g vercel@latest` executed from a temp dir — a potentially suboptimal suggestion, but one that cannot mutate the user's cwd. - **Crash-proof entrypoint resolution**: `realpath(process.argv[1])` failures (e.g. virtual filesystem snapshot paths in the native binary — the class of crash fixed for natives in #16581) no longer throw out of detection; they degrade to the safe global fallback. - **Prompt guard**: the post-command update notifier is skipped when the command was `upgrade`, mirroring the existing `canAutoUpdate` guard. ### Impact Every pnpm 11 global install of the CLI is affected, on all released versions including current latest — and the population grows as pnpm 11 adoption spreads (each `pnpm add -g vercel` under pnpm 11 creates the broken-detection layout). #16534 fixed the pnpm ≤10 workspace case but predates pnpm 11's layout. ### Test Plan - Unit tests covering: pnpm 11 isolated layout, legacy `global/5` layout, `PNPM_HOME` prefix anchoring (no substring false-positives), entrypoints outside `PNPM_HOME`, and the unrecognized-layout degradation (34 tests passing in `get-update-command`, `updates`, and `upgrade` suites) - `tsc --noEmit`, biome lint, prettier: clean - Empirical: bug reproduced pre-fix on both a long-lived dev machine and a fully reset clean machine; failure mechanism verified step-by-step against pnpm 11.7/11.9 layouts ### Follow-ups (not in this PR) - Post-install version verification: the success message currently reports the pre-resolved target version, not what was actually installed (observed divergence when pnpm's `min-release-age` gated the newest version: "upgraded to v54.18.7" while 54.18.6 was installed) - bun/vlt global installs are not detected (`scanParentDirs` can return them but `GlobalCliType` doesn't model them) - Longer term: distribution-channel awareness baked in at build time (native binary self-replacing; PM-distributed builds printing rather than executing), eliminating install-method forensics entirelygithub.com-vercel-vercel · f03f0017 · 2026-07-07
- 0.5ETVUse repo link behavior when possible during `vc link` (#16238) ## Summary - Prefer Git-linked project matches before folder-name matches during plain `vc link` auto-detection. - Preserve the SSO-safe first pass by searching non-limited teams first, then let users explicitly search skipped SSO-protected teams. - Write repo-aware matches to `.vercel/repo.json` and keep folder-name-only matches on `.vercel/project.json`. ## How search works When `vc link` runs without an explicit `--scope`, the CLI now builds one first-pass team set from the user's non-limited teams. It searches that set in two ways at the same time: ```bash # Git-linked project search # 1. Find the local Git repo root. # 2. Resolve the Git remote, preferring origin when multiple remotes exist under --yes. # 3. Fetch Vercel projects linked to that repo URL for each searched team. # 4. Match projects whose rootDirectory contains the current cwd. # Folder-name search # 1. Use the current folder/project name and its slugified form. # 2. Look for projects by name/id across the same searched teams. ``` Git-linked matches are shown as `(linked by git)` and are listed before folder-name matches. If selected, the CLI writes or updates `.vercel/repo.json` so the repo/root-directory mapping is preserved. Folder-name matches keep the existing `.vercel/project.json` behavior. If the first pass skips limited teams, the prompt explains that: ```bash $ vc link > Searched teams: team-alpha, team-beta, team-gamma, and 2 more > Skipped 3 SSO-protected teams ? Found matching projects across teams. Which one do you want to link? ❯ acme/web-app (linked by git) personal-scope/web-app (folder name) labs-team/web-app (folder name) Not one of these projects ``` Choosing `Not one of these projects` asks which skipped SSO-protected teams to search, instead of immediately falling into manual project creation: ```bash ? Which SSO-protected teams should be searched? ❯◯ Enterprise Apps (enterprise-apps) ◯ Framework Testing (framework-testing) ◯ Internal Playground (internal-playground) ``` If that selected-team search finds no match, the CLI now says so before falling back to the manual scope/project flow: ```bash > No matching projects found in the selected SSO-protected teams. ? Which scope should contain your project? ``` ## Test plan - `pnpm exec vitest run packages/cli/test/unit/commands/link/index.test.ts` - `pnpm --filter vercel type-check` - `pnpm exec biome lint "packages/cli/src/util/projects/search-project-across-teams.ts" "packages/cli/src/util/link/repo.ts" "packages/cli/src/util/link/setup-and-link.ts" "packages/cli/src/commands/link/index.ts" "packages/cli/test/unit/commands/link/index.test.ts"` --------- Co-authored-by: Cursor <cursoragent@cursor.com>github.com-vercel-vercel · 2dac1cb2 · 2026-05-07
- 0.4ETV[services] Refine container entrypoint detection (#16823) ## What Reworks container detection for `services` / `experimentalServicesV2` in `@vercel/fs-detectors`. ### Behavior - **Entrypoint infers `runtime: container` (broad match):** a supplied `entrypoint` whose basename is `Dockerfile` / `Containerfile`, or is prefixed `Dockerfile.` / `Containerfile.` (e.g. `Dockerfile.prod`, `Containerfile.vercel`). - **`runtime: container` + no entrypoint auto-detects (narrow match):** probes only the four blessed names, **`.vercel` markers first**: `Dockerfile.vercel` → `Containerfile.vercel` → `Dockerfile` → `Containerfile`. A `.vercel` marker takes precedence over a plain `Dockerfile`, matching the `container` framework preset. - **Removed the prebuilt OCI image reference entrypoint.** An `entrypoint` must now name a Dockerfile/Containerfile: - `runtime: container` + a non-Dockerfile entrypoint → `INVALID_SERVICE_CONFIG` - `runtime: container` + no entrypoint and no blessed Dockerfile present → `MISSING_SERVICE_CONFIG` `resolveContainerServiceV2` is now async and receives the root-scoped filesystem so it can probe for auto-detection. ### Tests Adds `packages/fs-detectors/test/unit.detect-services-v2-container.test.ts`, driven by shareable fixtures under `test/fixtures/services-container/`. Each scenario is its own directory named `pass-*` / `fail-*`. The old inline container `describe` block was removed (coverage moved to fixtures). ## Validation - `pnpm type-check`: clean - New container suite: 12/12; both v2 service test files: 39/39 - Full `@vercel/fs-detectors` unit suite passes except a pre-existing, unrelated `should skip entry if socket` failure (sandbox Unix-socket `listen EINVAL`) ## Notes - The `@vercel/container` builder still contains its prebuilt-image (`handler`) passthrough; it's now unreachable from services but still serves root `<detect>` deploys. Left untouched to keep this scoped to services detection. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Breaking change for services that used prebuilt OCI image entrypoints or suffixed Dockerfiles; detection and framework selection behavior changes for multi-framework repos with container markers. > > **Overview** > **Container services** (`experimentalServicesV2` / `services`) now only treat four **blessed** Dockerfile basenames as container entrypoints: `Dockerfile`, `Containerfile`, `Dockerfile.vercel`, and `Containerfile.vercel`. Suffixed names like `Dockerfile.prod` are **rejected** (no longer matched via `*.dockerfile`). > > With **`runtime: "container"`** and no `entrypoint`, the resolver **auto-detects** those files in the service root, probing **`.vercel` markers first** (`Dockerfile.vercel` → `Containerfile.vercel` → `Dockerfile` → `Containerfile`). **Prebuilt OCI image references** as `entrypoint` are **removed** for services; non-Dockerfile entrypoints error with `INVALID_SERVICE_CONFIG`, and missing blessed files yield `MISSING_SERVICE_CONFIG`. `resolveContainerServiceV2` is **async** and uses the scoped service filesystem for probing. > > **`@vercel/container`** shares the same blessed-set logic via exported **`isDockerfileRef`** in `util.ts` (dropped local duplicates in `dev.ts` / `index.ts`) so the builder honors service-resolved Dockerfile paths instead of defaulting or misclassifying entrypoints. > > The **`container` framework preset** is **no longer experimental**—`Dockerfile.vercel` / `Containerfile.vercel` projects detect as container without `VERCEL_USE_EXPERIMENTAL_FRAMEWORKS`. Coverage moves to **`unit.detect-services-v2-container.test.ts`** and **`test/fixtures/services-container/`** (`pass-*` / `fail-*`). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5ed0b771133a8aff9b455a2ffe97cb0949eec1b7. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->github.com-vercel-vercel · 66be3e02 · 2026-06-26
- 0.3ETV[frameworks] Source the framework list from the frameworks manifest (#16975) ### What Replaces the hand-written framework array in `@vercel/frameworks` with `frameworks.json`, sourced from the frameworks manifest (`https://api-frameworks-two.vercel.sh/v1/frameworks.json`, see vercel/api#78614). - `frameworks.json` is a **build artifact**: fetched by `build.mjs`, shipped in `dist/`, not checked into git. The fetch is fatal only when no previously fetched copy exists (`FRAMEWORKS_SKIP_MANIFEST_REFRESH=1` reuses the existing copy). Unit tests assert the manifest exists and is fully interpretable. - `src/manifest.ts` interprets declarative descriptors (`outputDirName`, `defaultRoutes`) into the runtime functions the `Framework` type requires. Unknown descriptor types throw `UnsupportedFrameworkEntryError` — the forward-compat hook for later PRs. ### Compat All existing exports are unchanged in shape (`frameworks` is now typed `readonly Framework[]` instead of a literal tuple; no in-repo consumer relied on the literals). The manifest currently adds one entry over the previous list: `tanstack-start-lovable` (intentional — a platform-import variant carrying the new `platform` field, now typed on `Framework` and validated by the schema test). ### Part of a stack Extracted from #16939 (kept open as the reference artifact for the full design). Follow-ups: 1. **This PR** — manifest as build artifact + interpreter 2. Runtime resolver API (`resolveFrameworks()`, remote fetch + cache, `minCliVersion`/`failOnStale` gating) 3. CLI wiring (`vc build` consumes the resolved list, upgrade warnings, build-container detection) ### Testing - 7 new unit tests (`manifest.unit.test.ts`): pinned manifest exists + interpretable, descriptor interpretation, override precedence, manifest-only field stripping - Existing schema/logo/unique/example suites pass unchanged against the interpreted list (strict `additionalProperties` validation retained) - Also fixes the pre-existing Node 24 failure in `frameworks.unit.test.ts` (`util.isString` was removed; replaced with a type guard) --------- Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>github.com-vercel-vercel · 0f9723ac · 2026-07-09
- 0.3ETVfix(ci): install Go toolchain for packages depending on ipc-proxy and harden Go download (#17088) Fixes flaky CI failure unrelated to PR #17079. **Failing job:** https://github.com/vercel/vercel/actions/runs/29277311707/job/86909622217 – `Unit / CLI (mac/node20) [7/7]` **Root cause:** - `turbo run ... --filter=vercel` builds the CLI which depends on `@vercel/go` → `@vercel-internals/ipc-proxy`. - `ipc-proxy` compiles Go cross-arch binaries during its build. - Workflow only installed Go when `matrix.packageName == '@vercel-internals/ipc-proxy'`, not for `vercel` CLI. - On macOS runners without system Go, `internals/ipc-proxy/build.mjs` fell back to downloading Go via `fetch()`, which flaked: ``` Downloading Go 1.23.12: https://dl.google.com/go/... TypeError: terminated at Fetch.onAborted ``` leaving a corrupted `/tmp/vercel-ipc-proxy-go-1.23.12`. Retry checked only `bin/go` existence, so it reused the corrupt GOROOT: ``` proxy.go:17:2: package context is not in std (.../src/context) ``` → build failed → turbo failed → job failed after 2 attempts. This is reproducible on `main` for any PR that triggers CLI unit chunks on mac. **Fix:** 1. **`utils/chunk-tests.js`** – add `needsGo` detection mirroring `needsRust`: - `GO_BUILD_ROOTS = { '@vercel-internals/ipc-proxy', '@vercel/go' }` - Packages depending on those roots (notably `vercel` CLI) now get `needsGo:true` in the chunk matrix. 2. **`.github/workflows/test.yml`** – install Go when `matrix.needsGo == true` for both `unit-test` and `e2e-test` jobs, preventing the network fallback on CI. 3. **`internals/ipc-proxy/build.mjs`** – harden the fallback for local/dev: - Validate cached GOROOT by reading `src/context/context.go`; if missing, re-download. - Retry download 3× with exponential backoff and `AbortSignal.timeout(120s)`. - Clean dest dir on failure to avoid poisoning retries. - Add timeout to `hasSystemGo()`. **Test plan:** - [ ] Verify `TEST_TYPE=unit node utils/chunk-tests.js | jq '.[] | select(.packageName=="vercel" and .runner=="macos-14") | .needsGo' | uniq` → `true` - [ ] CI: mac unit jobs for CLI now install Go, no longer Downloading Go log line - [ ] Existing `.github/workflows/test.yml` still passes on linux/mac/win Resolves flake seen in https://github.com/vercel/vercel/actions/runs/29277311707/job/86909622217github.com-vercel-vercel · ad5ac9a4 · 2026-07-13
- 0.3ETV[frameworks][container] Add experimental container framework preset (#16787) Stacked on #16786. What Adds first-class support for deploying **any** project as a container by dropping a `Dockerfile.vercel` or `Containerfile.vercel` in the project root — even outside of `services`, and even when another framework is also present. > You might have a Next.js app, but if you have a `Dockerfile.vercel` it's a way of signifying you intend to deploy that with a container. ## How ### New experimental `container` framework preset (`@vercel/frameworks`) - Detects `Dockerfile.vercel` / `Containerfile.vercel` and maps to `useRuntime: { src: '<detect>', use: '@vercel/container' }`. - **Listed first** in `frameworkList`. Framework detection returns the first match in list order (`detectFramework`/`detectFrameworkRecord` both use `result.find(...)` after superseded removal, and the list is not re-sorted by `sort`), so this preset takes precedence over all other frameworks when a marker is present. - Marked **`experimental: true`**, so it is gated behind the experimental frameworks flag (`VERCEL_USE_EXPERIMENTAL_FRAMEWORKS`) and excluded from default detection while in development. Also `runtimeFramework: true` (like Python/Go/Node), so it's exempt from the "example required" test and isn't offered as a generic UI preset. - Slug/name: `container` (the marker filenames remain `Dockerfile.vercel` / `Containerfile.vercel`). ### `@vercel/container` builder - `isDockerfileRef` now recognizes the `.vercel` markers as buildable Dockerfiles. - When the entrypoint is `<detect>` (how the preset resolves), the builder discovers `Dockerfile.vercel` / `Containerfile.vercel` in the work dir. - Root (non-service) container deploys no longer throw for a missing service name — the registry repository falls back to a stable leaf (`app`), which is already namespaced by owner/project via the OIDC claims. ## Testing - `@vercel/frameworks`: schema / logo-exists / unique-slug / unique-sort all pass; added `logos/container.svg`. - `fs-detectors`: new detection tests — with experimental frameworks enabled the markers detect as `container` and win over a co-present Next.js app; without the flag they fall through to `nextjs`. All 124 framework-detector + 211 detect-builders + 60 example-detection tests pass. - `@vercel/container`: new tests for `.vercel` marker entrypoints, `<detect>` discovery, and root (no-service) deploys. 32 tests pass. - `pnpm type-check` and prettier clean. Because the preset is experimental, detection only selects `container` when `VERCEL_USE_EXPERIMENTAL_FRAMEWORKS` is set — set that when testing locally. ## Notes / for review - Precedence relies on **list position** (first) rather than enumerating every `supersedes` slug, which would be brittle. This is sufficient for the single-project build path; happy to add an explicit `supersedes` list if reviewers prefer belt-and-suspenders for plural `detectFrameworks` consumers. - Logo is a placeholder container-style mark; swap for the official asset if there's a preferred one. - A new framework slug must also be added to the API's framework allowlist before non-experimental users can deploy with it; the experimental gating keeps that decoupled for now.github.com-vercel-vercel · 09743c64 · 2026-06-24
- 0.3ETV[remix] Serve react-router prerendered routes from static files (#16363)github.com-vercel-vercel · 744e96c8 · 2026-05-19
- 0.3ETVFix React Router /__manifest returning HTML when index is prerendered (#16448) ## Summary - Fixes CICD-2715: when React Router apps use `prerender: true`, `/__manifest` returned prerendered `index.html` instead of JSON after CLI 54.2.0 - Regression introduced by #16363: skipping the index SSR function removed the catch-all handler, so runtime-only paths fell through to static HTML - Keeps the index SSR function for catch-all routing, adds an explicit `/` → `/index.html` prerender rewrite, and routes the React Router catch-all to the `index` output key ## Test plan - [x] `pnpm exec vitest run test/unit.find-prerendered-html-file.test.ts test/unit.get-react-router-data-paths.test.ts` (29 tests) - [x] Added unit tests for prerender rewrite helpers, `/__manifest` route resolution, and regression case mirroring CLI >=54.2.0 behavior - [ ] Deploy customer's `my-react-router-app` demo project and verify `curl -I /__manifest` returns `content-type: application/json` - [ ] Verify `/` and prerendered routes still serve static HTML Made with [Cursor](https://cursor.com) Co-authored-by: Cursor <cursoragent@cursor.com>github.com-vercel-vercel · 2d918b8a · 2026-05-29
- 0.2ETVReplace Jest with Vitest across workspace tests (#16215) ## Summary - Replace workspace Jest configs, scripts, and direct dependencies with Vitest-based test runners. - Migrate Jest globals/API usage to Vitest equivalents and update TypeScript test globals. - Refresh lockfiles and test tooling helpers for Vitest-only CI discovery/tracing. ## Test plan - pnpm test - pnpm --filter @vercel/related-projects test - pnpm --filter @vercel/edge test-unit - pnpm --filter @vercel/client test tests/unit.vercelignore.test.ts - pnpm --filter vercel test test/unit/commands/certs/add.test.ts - pnpm --filter @vercel/go test test/go-helpers.test.ts - pnpm --filter @vercel-internals/get-package-json test tests/unit/cache.test.ts - node utils/chunk-tests.js - ReadLints on edited areas Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com>github.com-vercel-vercel · da93fccb · 2026-05-06
- 0.2ETVfix(cli): skip external symlinks in standalone prebuilt deploys (PIPE-6621) (#16439) ## Summary - Fixes prebuilt deployments failing with `invalid relative path` when using `--standalone` in pnpm/Turborepo monorepos - Skips hoisted `node_modules` symlinks whose targets contain `../` from the shared output directory; traced dependency files are already included at their logical paths (e.g. `node_modules/next/dist/...`) - Updates `download()` to stop ignoring files inside external symlink directories when both the symlink and traced files are present in the file map Fixes PIPE-6621 ## What the previous fix (#15322) addressed — and what it missed [#15322](https://github.com/vercel/vercel/pull/15322) fixed a **build-time** failure: when multiple functions share `.vercel/output/shared/`, the second attempt to create the same symlink (e.g. `node_modules/next`) would throw `EEXIST`. That PR made symlink creation idempotent by skipping or replacing when the target already matches. That fix did **not** address the **deploy-time** failure customers were still hitting. In pnpm/Turborepo monorepos, hoisted `node_modules` symlinks retain targets like `../../node_modules/.pnpm/...`. With `--standalone`, those symlinks were copied into the shared output as-is. During `vercel deploy --prebuilt`, the platform uploads symlink contents and rejects targets containing `../`, producing: ``` invalid relative path: ../../node_modules/.pnpm/next@.../node_modules/next/dist/server/next-server.js ``` So #15322 unblocked the build from crashing on duplicate symlinks, but the resulting artifact still contained symlinks the deploy API considers invalid. ## What this PR does differently Instead of copying external symlinks into shared output, we **skip the symlink entry** and rely on the traced real files that already exist at logical paths (`node_modules/next/dist/...`). Those files are written to `.vercel/output/shared/` and referenced via `filePathMap` in each function's `.vc-config.json`, so they still hydrate into the lambda at deploy time — just without the broken symlink in between. ## Test plan - [x] `pnpm test test/unit.download.test.ts` in `packages/build-utils` - [x] `pnpm test test/unit/util/build/write-build-result.test.ts` in `packages/cli` - [ ] Reproduce with `vercel-support/turborepo-basic`: `vercel build --standalone && vercel deploy --prebuilt` --------- Co-authored-by: Cursor <cursoragent@cursor.com>github.com-vercel-vercel · b66bd3ed · 2026-05-27
- 0.2ETVReorder env add sensitive prompt before value and environment selection (#16430) ## Summary - Ask "Is the value a sensitive secret?" before collecting the value or selecting environments in interactive `vercel env add` - Hide Development when the value is sensitive; allow all environments in one add when it is not - On teams with the sensitive env policy, still prompt and limit non-sensitive adds to Development with clearer messaging ## Test plan - [x] `npx vitest run packages/cli/test/unit/commands/env/add.test.ts --pool=forks --poolOptions.forks.singleFork=true` (40/40 passing) - [ ] Manually run `vc env add` and confirm sensitive vs non-sensitive flows - [ ] On a policy-on team, confirm answering "no" only offers Development Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com>github.com-vercel-vercel · 7ba47137 · 2026-05-28
- 0.2ETVfeat(cli): native trampoline with JS fallback, drop postinstall (#17011) ## What Part 1 of 2. Make the CLI bin native-aware: resolve and spawn a `@vercel/vc-native-{platform}-{arch}` binary when present, otherwise no-op into the existing JS CLI. This release is a no-op — no `optionalDependencies` are wired, so `resolveNative()` always returns null and the CLI runs as JS exactly as before. The point is to confirm the JS path is unaffected at scale before part 2 wires the release flow to build/publish natives before `vercel`.github.com-vercel-vercel · fac79dc5 · 2026-07-13
- 0.2ETVVersion Packages (#16138) Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>github.com-vercel-vercel · 68edb7a1 · 2026-04-29