Guidelines for AI agents working on the Vercel monorepo.
This is a pnpm monorepo containing 44+ packages for the Vercel CLI and runtimes:
/packages/*- Public npm packages (@vercel scope)/internals/*- Internal shared packages (@vercel-internals scope)/crates- Rust workspace/examples- Framework examples for testing/utils- Build and test utilities
pnpm install # Install dependencies
pnpm build # Build all packages
pnpm type-check # TypeScript validation
pnpm lint # ESLint check
pnpm test-unit # Run unit tests
pnpm test-e2e # Run e2e testsRun tests for a specific package:
cd packages/<name>
pnpm test-unitAlways create a changeset for all PRs.
pnpm changesetA changeset is a markdown file in .changeset/ with YAML frontmatter listing affected packages and their bump type (patch/minor/major).
- Every PR must include a changeset (use empty frontmatter for non-package changes).
- If your change modifies a package in
/packages/*, include it in the changeset frontmatter - If your change modifies a package in
/internals/*, include that@vercel-internals/...package.pnpm ci:versionadds the public packages that bundle it (vercel,@vercel/go,@vercel/rust) so you do not list those by hand. - If your change only affects non-package files (docs, config, examples,
/api), create a changeset with empty frontmatter - just the description - Packages in
/apiand/examplesare ignored by changesets (see.changeset/config.json)
Example changeset for a package change:
---
'@vercel/node': patch
---
Fixed edge case in serverless function bundling.Example changeset for an internal package (the public packages that bundle it are added at version time):
---
'@vercel-internals/runtimes': patch
---
Shared runtime helper used by builder detection.Example changeset for non-package changes:
---
---
Updated CI workflow configuration.- Formatting: Prettier with single quotes, trailing commas (es5), no parens for single arrow params
- Linting: ESLint with TypeScript rules
- No unused variables:
@typescript-eslint/no-unused-varsis enforced - No focused/disabled tests:
jest/no-focused-testsandjest/no-disabled-testsare errors - Use
slice()oversubstr(): substr is deprecated
Run before committing:
pnpm prettier --write .
pnpm lint- Unit tests: Jest or Vitest, located in
packages/<name>/test/unit/ - E2E tests: Fixture-based, test real deployments
- Affected testing: CI only runs tests for changed packages and dependents
When adding tests:
// Vitest style
import { describe, test, expect } from 'vitest';
describe('feature', () => {
test('should work', () => {
expect(true).toBe(true);
});
});Each package follows this structure:
packages/<name>/
├── src/
│ └── index.ts # Main entry
├── test/
├── package.json
├── tsconfig.json # Extends ../../tsconfig.base.json
└── build.mjs # esbuild config (if applicable)
Workspace dependencies use workspace:*:
{
"dependencies": {
"@vercel/build-utils": "workspace:*"
}
}Internal packages (@vercel-internals/*) are private, versioned by changesets, and never published to npm. Public packages must list them as devDependencies only and bundle them at build time (mark package.json dependencies as esbuild externals, the same pattern as the CLI and builders). Do not add @vercel-internals/* to dependencies or peerDependencies of a public package.
Changesets will not bump a package that only has a devDependency on the changed workspace package. utils/expand-internal-changesets.mjs runs before changeset version and adds the public packages that bundle the released internals.
When creating a new package, configure it for public npm access:
{
"name": "@vercel/new-package",
"publishConfig": {
"access": "public"
}
}Runtime packages (node, python, go, ruby, rust) implement the Builder API:
export const version = 3;
export async function build(options: BuildOptions): Promise<BuildResult> {
// Build implementation
}
// Optional exports
export async function prepareCache(
options: PrepareCacheOptions
): Promise<Files> {}
export async function startDevServer(
options: StartDevServerOptions
): Promise<StartDevServerResult> {}Test the CLI against an external repository:
cd packages/cli
pnpm vercel --cwd /path/to/external/repoThis runs your local CLI build against any project without needing to install it globally.
- Don't use
console.login CLI package -no-consolerule is enforced there - Don't skip CI hooks - Lint and type checks run in pre-commit
- Don't forget type-check - Run
pnpm type-checkbefore pushing - Don't modify examples for testing - They're used for integration tests
The Cloud Agent environment is defined by .cursor/environment.json, which runs
.cursor/install.sh after checkout. The default base image already provides Node
22, pnpm, Go, and Python, but two extra tools are required for a full build and
are installed by that script:
uv- Turborepo resolves the Python workspace (python/*) withuv. Without it,pnpm buildfails withThe \uv` binary is not available for native task resolution. Installed to~/.local/bin` (already on PATH via the base image's shell profile).- Rust >= 1.85 - the
crates/workspace usesedition = "2024"andresolver = "3". An older stable toolchain fails withfeature \edition2024` is required. The script runsrustup update stable` only when the active toolchain is below 1.85.
If you set up or debug the environment, see the env-setup skill. When you need
uv or a newer Rust in an ad-hoc shell, run bash .cursor/install.sh (it is
idempotent) or export PATH="$HOME/.local/bin:$PATH" for uv.
