Skip to content

Commit 1eab336

Browse files
authored
fix: Use a pnpm preinstall script to fix catch-22 in next binary creation (#91016)
We've had a longstanding issue where you'd have to run `pnpm i` _twice_ on a fresh checkout because `pnpm` wouldn't set up binary symlinks until after the binary exists, but the binary wouldn't exist until after `pnpm build` was done: https://github.com/vercel/next.js/blob/canary/.github/workflows/build_reusable.yml#L303-L311 This solves that problem using a pnpm preinstall script that creates stub binaries for these files if they don't exist yet (because we haven't run `pnpm build`). ## Test Plan ``` git clean -fxd pnpm i pnpm build pnpm test-start test/e2e/app-dir/next-image/ pnpm test-start-turbo test/e2e/app-dir/next-image/ ``` I tested this both locally and inside of a fresh ubuntu VM. Running `pnpm next` after `pnpm i` but before `pnpm build` gives a helpful error message: <img width="946" height="125" alt="Screenshot 2026-03-06 at 9 37 14 PM" src="https://github.com/user-attachments/assets/4517e8bd-60ca-44f2-a34c-f7f3ec56c634" />
1 parent 110ed22 commit 1eab336

File tree

4 files changed

+49
-11
lines changed

4 files changed

+49
-11
lines changed

.github/workflows/build_reusable.yml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -300,16 +300,6 @@ jobs:
300300
- run: ANALYZE=1 pnpm build
301301
if: ${{ inputs.skipInstallBuild != 'yes' }}
302302

303-
# Some packages e.g. `devlow-bench` depend on `pnpm build` to generate
304-
# their `dist` directory. The first run of `pnpm install` will generate
305-
# warnings because these don't exist yet.
306-
#
307-
# We need to run `pnpm install` a _second_ time to fix this. Fortunately,
308-
# this second run is very fast and cheap.
309-
- name: Re-run pnpm install to link built packages into node_modules/.bin
310-
run: pnpm install
311-
if: ${{ inputs.skipInstallBuild != 'yes' }}
312-
313303
- run: pnpm playwright install --with-deps ${{ inputs.browser }}
314304
if: ${{ inputs.skipInstallBuild != 'yes' }}
315305

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
"clean-trace-jaeger": "node scripts/rm.mjs test/integration/basic/.next && TRACE_TARGET=JAEGER pnpm next build test/integration/basic",
9595
"debug": "cross-env NEXT_PRIVATE_LOCAL_DEV=1 NEXT_TELEMETRY_DISABLED=1 node --inspect --trace-deprecation --enable-source-maps packages/next/dist/bin/next",
9696
"debug-brk": "cross-env NEXT_PRIVATE_LOCAL_DEV=1 NEXT_TELEMETRY_DISABLED=1 node --inspect-brk --trace-deprecation --enable-source-maps packages/next/dist/bin/next",
97+
"pnpm:devPreinstall": "node scripts/create-next-bin-placeholder.mjs",
9798
"postinstall": "node scripts/git-configure.mjs && node scripts/install-native.mjs",
9899
"version": "pnpm install --no-frozen-lockfile && git add .",
99100
"prepare": "husky",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import fs from 'node:fs'
2+
import path from 'node:path'
3+
import { fileURLToPath } from 'node:url'
4+
5+
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
6+
const repoRoot = path.join(scriptDir, '..')
7+
8+
const placeholders = [
9+
path.join(repoRoot, 'packages', 'next', 'dist', 'bin', 'next'),
10+
path.join(repoRoot, 'packages', 'create-next-app', 'dist', 'index.js'),
11+
path.join(
12+
repoRoot,
13+
'turbopack',
14+
'packages',
15+
'devlow-bench',
16+
'dist',
17+
'cli.js'
18+
),
19+
]
20+
21+
for (const binPath of placeholders) {
22+
if (fs.existsSync(binPath)) {
23+
continue
24+
}
25+
26+
fs.mkdirSync(path.dirname(binPath), { recursive: true })
27+
28+
fs.writeFileSync(
29+
binPath,
30+
`#!/usr/bin/env node
31+
console.error(
32+
"Local workspace has not been built yet. Run 'pnpm build' first."
33+
)
34+
process.exit(1)
35+
`,
36+
'utf8'
37+
)
38+
39+
if (process.platform !== 'win32') {
40+
fs.chmodSync(binPath, 0o755)
41+
}
42+
}

scripts/install-native.mjs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ import fsp from 'fs/promises'
6060
fs.writeFileSync(path.join(tmpdir, 'package.json'), JSON.stringify(pkgJson))
6161
fs.writeFileSync(path.join(tmpdir, '.npmrc'), 'node-linker=hoisted')
6262

63-
const args = ['add', `next@${nextVersion}`]
63+
const args = [
64+
'add',
65+
`next@${nextVersion}`,
66+
'--lockfile=false',
67+
'--ignore-workspace',
68+
]
6469
if (preferOffline) {
6570
args.push('--prefer-offline')
6671
}

0 commit comments

Comments
 (0)