Skip to content

Commit 2ae2d06

Browse files
authored
feat(cli): move builders back into bundle (#15059)
2 commits related moving builders out of the bundle: - #15023 - #15041 Since it's isn't a clean revert, I've: - moved builders back into dependencies, and reverted import-builders and its tests to the commit at 3cd0b55 (parent of 127547d). No other commits have landed to these files since then, so it seems like the best way of doing this. This commit also contains a timestamp update on all builders `.deploy` files. This is just a way to invalidate the builders so all e2e tests run on this PR.
1 parent bf25c16 commit 2ae2d06

24 files changed

Lines changed: 108 additions & 299 deletions

File tree

.changeset/young-sloths-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'vercel': patch
3+
---
4+
5+
Move builders back into bundle

.github/workflows/test-e2e.yml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -197,16 +197,6 @@ jobs:
197197
run: node utils/gen.js && node_modules/.bin/turbo run build --cache-dir=".turbo" --log-order=stream --filter=${{matrix.packageName}}...
198198
env:
199199
FORCE_COLOR: '1'
200-
- name: Build vercel peer dependencies
201-
if: matrix.packageName == 'vercel'
202-
shell: bash
203-
run: |
204-
PEER_FILTERS=$(node utils/get-cli-peer-deps.js | xargs -I {} echo --filter={})
205-
if [ -n "$PEER_FILTERS" ]; then
206-
node utils/gen.js && node_modules/.bin/turbo run build --cache-dir=".turbo" --log-order=stream $PEER_FILTERS
207-
fi
208-
env:
209-
FORCE_COLOR: '1'
210200
- name: Resolve Python runtime wheel URL
211201
id: python-wheel
212202
run: |

.github/workflows/test.yml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -325,16 +325,6 @@ jobs:
325325
run: node utils/gen.js && node_modules/.bin/turbo run build --cache-dir=".turbo" --log-order=stream --filter=${{matrix.packageName}}...
326326
env:
327327
FORCE_COLOR: '1'
328-
- name: Build vercel peer dependencies
329-
if: matrix.packageName == 'vercel'
330-
shell: bash
331-
run: |
332-
PEER_FILTERS=$(node utils/get-cli-peer-deps.js | xargs -I {} echo --filter={})
333-
if [ -n "$PEER_FILTERS" ]; then
334-
node utils/gen.js && node_modules/.bin/turbo run build --cache-dir=".turbo" --log-order=stream $PEER_FILTERS
335-
fi
336-
env:
337-
FORCE_COLOR: '1'
338328
- name: Test ${{matrix.packageName}}
339329
run: node utils/gen.js && node_modules/.bin/turbo run ${{matrix.testScript}} --summarize --cache-dir=".turbo" --log-order=stream --filter=${{matrix.packageName}} -- ${{ join(matrix.testPaths, ' ') }}
340330
shell: bash

packages/backends/.deploy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Thu Feb 12 23:04:47 CST 2026
1+
Sat Feb 14 08:34:45 CST 2026

packages/cli/package.json

Lines changed: 1 addition & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@
4444
"form-data": "^4.0.0",
4545
"jose": "5.9.6",
4646
"luxon": "^3.4.0",
47-
"proxy-agent": "6.4.0"
48-
},
49-
"peerDependencies": {
47+
"proxy-agent": "6.4.0",
5048
"@vercel/backends": "workspace:*",
5149
"@vercel/elysia": "workspace:*",
5250
"@vercel/express": "workspace:*",
@@ -66,62 +64,6 @@
6664
"@vercel/rust": "workspace:*",
6765
"@vercel/static-build": "workspace:*"
6866
},
69-
"peerDependenciesMeta": {
70-
"@vercel/backends": {
71-
"optional": true
72-
},
73-
"@vercel/elysia": {
74-
"optional": true
75-
},
76-
"@vercel/express": {
77-
"optional": true
78-
},
79-
"@vercel/fastify": {
80-
"optional": true
81-
},
82-
"@vercel/go": {
83-
"optional": true
84-
},
85-
"@vercel/h3": {
86-
"optional": true
87-
},
88-
"@vercel/hono": {
89-
"optional": true
90-
},
91-
"@vercel/hydrogen": {
92-
"optional": true
93-
},
94-
"@vercel/koa": {
95-
"optional": true
96-
},
97-
"@vercel/nestjs": {
98-
"optional": true
99-
},
100-
"@vercel/next": {
101-
"optional": true
102-
},
103-
"@vercel/node": {
104-
"optional": true
105-
},
106-
"@vercel/python": {
107-
"optional": true
108-
},
109-
"@vercel/redwood": {
110-
"optional": true
111-
},
112-
"@vercel/remix-builder": {
113-
"optional": true
114-
},
115-
"@vercel/ruby": {
116-
"optional": true
117-
},
118-
"@vercel/rust": {
119-
"optional": true
120-
},
121-
"@vercel/static-build": {
122-
"optional": true
123-
}
124-
},
12567
"devDependencies": {
12668
"@alex_neo/jest-expect-message": "1.0.5",
12769
"@edge-runtime/node-utils": "2.3.0",

packages/cli/src/util/build/import-builders.ts

Lines changed: 75 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -48,24 +48,20 @@ export async function importBuilders(
4848
): Promise<Map<string, BuilderWithPkg>> {
4949
const buildersDir = join(cwd, VERCEL_DIR, 'builders');
5050

51-
let importResult = await resolveBuilders(cwd, buildersDir, builderSpecs);
51+
let importResult = await resolveBuilders(buildersDir, builderSpecs);
5252

5353
if ('buildersToAdd' in importResult) {
5454
const installResult = await installBuilders(
5555
buildersDir,
5656
importResult.buildersToAdd
5757
);
5858

59-
// resolve builders again, after they've been installed
60-
// with specs from the newly installed builders.
6159
importResult = await resolveBuilders(
62-
cwd,
6360
buildersDir,
6461
builderSpecs,
6562
installResult.resolvedSpecs
6663
);
6764

68-
// We shouldn't get buildersToAdd a second time from resolveBuilders.
6965
if ('buildersToAdd' in importResult) {
7066
throw new Error('Something went wrong!');
7167
}
@@ -81,44 +77,19 @@ export async function importBuilders(
8177
return importResult.builders;
8278
}
8379

84-
// Cache for CLI package.json peerDependencies
85-
let peerDependencies: Record<string, string> | undefined;
86-
87-
function getPeerDependencies(): Record<string, string> {
88-
if (!peerDependencies) {
89-
try {
90-
const cliPkgPath = require_.resolve('vercel/package.json', {
91-
paths: [__dirname],
92-
});
93-
const cliPkg = require_(cliPkgPath) as PackageJson;
94-
peerDependencies =
95-
(cliPkg.peerDependencies as Record<string, string>) || {};
96-
} catch (e) {
97-
output.error(
98-
'Failed to parse peer dependencies from vercel/package.json'
99-
);
100-
peerDependencies = {};
101-
}
102-
}
103-
104-
return peerDependencies;
105-
}
106-
10780
export async function resolveBuilders(
108-
cwd: string,
10981
buildersDir: string,
11082
builderSpecs: Set<string>,
11183
resolvedSpecs?: Map<string, string>
11284
): Promise<ResolveBuildersResult> {
11385
const builders = new Map<string, BuilderWithPkg>();
11486
const buildersToAdd = new Set<string>();
115-
const peerDeps = getPeerDependencies();
11687

11788
for (const spec of builderSpecs) {
11889
const resolvedSpec = resolvedSpecs?.get(spec) || spec;
11990
const parsed = npa(resolvedSpec);
120-
const { name } = parsed;
12191

92+
const { name } = parsed;
12293
if (!name) {
12394
// A URL was specified - will need to install it and resolve the
12495
// proper package name from the written `package.json` file
@@ -137,103 +108,93 @@ export async function resolveBuilders(
137108
continue;
138109
}
139110

140-
// Resolution priority:
141-
// 1. .vercel/builders (where we dynamically install builders)
142-
// 2. CLI's node_modules (it looks at wherever this file and associated node modules)
143-
// 3. Install into .vercel/builders if not found in either
111+
try {
112+
let pkgPath: string | undefined;
113+
let builderPkg: PackageJson | undefined;
144114

145-
let pkgPath: string | undefined;
146-
let builderPkg: PackageJson | undefined;
147-
const peerVersion = peerDeps[name];
115+
try {
116+
// First try `.vercel/builders`. The package name should always be available
117+
// at the top-level of `node_modules` since CLI is installing those directly.
118+
pkgPath = join(buildersDir, 'node_modules', name, 'package.json');
119+
builderPkg = await readJSON(pkgPath);
120+
} catch (error: unknown) {
121+
if (!isErrnoException(error)) {
122+
throw error;
123+
}
124+
if (error.code !== 'ENOENT') {
125+
throw error;
126+
}
148127

149-
// 1. Try .vercel/builders (where we install builders)
150-
try {
151-
pkgPath = join(buildersDir, 'node_modules', name, 'package.json');
152-
const cachedPkg: PackageJson = await readJSON(pkgPath);
153-
output.debug(`"${name}@${cachedPkg.version}" found in .vercel/builders`);
128+
// If `pkgPath` wasn't found in `.vercel/builders` then try as a CLI local
129+
// dependency. `require.resolve()` will throw if the Builder is not a CLI
130+
// dep, in which case we'll install it into `.vercel/builders`.
131+
pkgPath = require_.resolve(`${name}/package.json`, {
132+
paths: [__dirname],
133+
});
134+
builderPkg = await readJSON(pkgPath);
135+
}
154136

155-
// Verify cached version matches peerDeps exactly
156-
if (peerVersion && cachedPkg.version !== peerVersion) {
137+
if (!builderPkg || !pkgPath) {
138+
throw new Error(`Failed to load \`package.json\` for "${name}"`);
139+
}
140+
141+
if (typeof builderPkg.version !== 'string') {
142+
throw new Error(
143+
`\`package.json\` for "${name}" does not contain a "version" field`
144+
);
145+
}
146+
147+
if (parsed.type === 'version' && parsed.rawSpec !== builderPkg.version) {
148+
// An explicit Builder version was specified but it does
149+
// not match the version that is currently installed
157150
output.debug(
158-
`"${name}@${cachedPkg.version}" does not match peerDep "${peerVersion}", will reinstall`
151+
`Installed version "${name}@${builderPkg.version}" does not match "${parsed.rawSpec}"`
159152
);
160-
buildersToAdd.add(`${name}@${peerVersion}`);
153+
buildersToAdd.add(spec);
161154
continue;
162155
}
163-
builderPkg = cachedPkg;
164-
} catch (err: unknown) {
165-
if (!isErrnoException(err) || err.code !== 'ENOENT') {
166-
throw err;
167-
}
168-
output.debug(`"${name}@${peerVersion}" not found in .vercel/builders`);
169-
}
170156

171-
// 2. Try CLI's node_modules (wherever CLI is installed - globally or as a project dep)
172-
if (!builderPkg) {
173-
try {
174-
pkgPath = require_.resolve(`${name}/package.json`, {
175-
paths: [__dirname],
176-
});
177-
builderPkg = await readJSON(pkgPath);
178-
output.debug(`Found "${name}" in CLI's node_modules`);
179-
} catch (err: unknown) {
180-
if (!isErrnoException(err) || err.code !== 'MODULE_NOT_FOUND') {
181-
throw err;
182-
}
183-
// Not found in cache or CLI - install (or error on second run if no peerDep)
184-
if (resolvedSpecs) {
185-
throw new Error(`Builder "${name}" not found`);
186-
}
187-
output.debug(`"${name}" not found anywhere, will install`);
188-
buildersToAdd.add(peerVersion ? `${name}@${peerVersion}` : spec);
157+
if (
158+
parsed.type === 'range' &&
159+
!satisfies(builderPkg.version, parsed.rawSpec)
160+
) {
161+
// An explicit Builder range was specified but it is not
162+
// compatible with the version that is currently installed
163+
output.debug(
164+
`Installed version "${name}@${builderPkg.version}" is not compatible with "${parsed.rawSpec}"`
165+
);
166+
buildersToAdd.add(spec);
189167
continue;
190168
}
191-
}
192169

193-
if (!builderPkg || !pkgPath) {
194-
throw new Error(`Failed to load \`package.json\` for "${name}"`);
195-
}
170+
// TODO: handle `parsed.type === 'tag'` ("latest" vs. anything else?)
196171

197-
if (typeof builderPkg.version !== 'string') {
198-
throw new Error(
199-
`\`package.json\` for "${name}" does not contain a "version" field`
200-
);
201-
}
172+
const path = join(dirname(pkgPath), builderPkg.main || 'index.js');
202173

203-
// Validate explicit version/range requirements from spec
204-
if (parsed.type === 'version' && parsed.rawSpec !== builderPkg.version) {
205-
output.debug(
206-
`Installed version "${name}@${builderPkg.version}" does not match "${parsed.rawSpec}"`
207-
);
208-
buildersToAdd.add(spec);
209-
continue;
210-
}
174+
const builder = require_(path);
211175

212-
if (
213-
parsed.type === 'range' &&
214-
!satisfies(builderPkg.version, parsed.rawSpec)
215-
) {
216-
output.debug(
217-
`Installed version "${name}@${builderPkg.version}" is not compatible with "${parsed.rawSpec}"`
218-
);
219-
buildersToAdd.add(spec);
220-
continue;
176+
builders.set(spec, {
177+
builder,
178+
pkg: {
179+
name,
180+
...builderPkg,
181+
},
182+
path,
183+
pkgPath,
184+
});
185+
output.debug(`Imported Builder "${name}" from "${dirname(pkgPath)}"`);
186+
} catch (err: any) {
187+
// `resolvedSpecs` is only passed into this function on the 2nd run,
188+
// so if MODULE_NOT_FOUND happens in that case then we don't want to
189+
// try to install again. Instead just pass through the error to the user
190+
if (err.code === 'MODULE_NOT_FOUND' && !resolvedSpecs) {
191+
output.debug(`Failed to import "${name}": ${err}`);
192+
buildersToAdd.add(spec);
193+
} else {
194+
err.message = `Importing "${name}": ${err.message}`;
195+
throw err;
196+
}
221197
}
222-
223-
// TODO: handle `parsed.type === 'tag'` ("latest" vs. anything else?)
224-
const path = join(dirname(pkgPath), builderPkg.main || 'index.js');
225-
const builder = require_(path);
226-
227-
builders.set(spec, {
228-
builder,
229-
pkg: {
230-
name,
231-
...builderPkg,
232-
},
233-
path,
234-
pkgPath,
235-
});
236-
output.debug(`Imported Builder "${name}" from "${dirname(pkgPath)}"`);
237198
}
238199

239200
// Add any Builders that are not yet present into `.vercel/builders`
@@ -249,9 +210,6 @@ async function installBuilders(
249210
buildersToAdd: Set<string>
250211
) {
251212
const resolvedSpecs = new Map<string, string>();
252-
253-
// First create an empty package.json in the cache dir where
254-
// we store our downloaded builders.
255213
const buildersPkgPath = join(buildersDir, 'package.json');
256214
try {
257215
const emptyPkgJson = {
@@ -265,7 +223,6 @@ async function installBuilders(
265223
if (err.code !== 'EEXIST') throw err;
266224
}
267225

268-
// Then npm install the list of packages we need to install.
269226
output.log(
270227
`Installing ${plural('Builder', buildersToAdd.size)}: ${Array.from(
271228
buildersToAdd
@@ -314,6 +271,7 @@ async function installBuilders(
314271
// Symlink `@now/build-utils` -> `@vercel/build-utils` to support legacy Builders
315272
const nowScopePath = join(buildersDir, 'node_modules/@now');
316273
await mkdirp(nowScopePath);
274+
317275
try {
318276
await symlink('../@vercel/build-utils', join(nowScopePath, 'build-utils'));
319277
} catch (err: unknown) {

0 commit comments

Comments
 (0)