Skip to content

Commit 7465720

Browse files
committed
ci: add option to use next.js code changes
1 parent 044791e commit 7465720

2 files changed

Lines changed: 186 additions & 6 deletions

File tree

.github/workflows/adapter-e2e.yml

Lines changed: 128 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,30 @@ on:
1414
description: "Next.js repo ref (branch/tag/SHA)"
1515
default: "canary"
1616
type: string
17+
# Only consulted on workflow_dispatch. A push always builds from source —
18+
# see BUILD_NEXTJS on the build job. Uncheck this to test a fixture or
19+
# adapter change against published next without paying for a Rust compile.
20+
buildNextjs:
21+
description: "Build Next.js + next-swc from source (default: install published next from npm)"
22+
default: false
23+
type: boolean
1724
# schedule:
1825
# - cron: '0 2 * * *'
1926

2027
jobs:
2128
build:
2229
name: Build Next.js + adapter
2330
runs-on: ubuntu-latest
24-
timeout-minutes: 30
31+
# Compiling next-swc from source dominates this job: ~25-40min cold, a few
32+
# minutes once the cargo cache is warm.
33+
timeout-minutes: 90
34+
env:
35+
# Whether to test next.js from this checkout instead of the published npm
36+
# release. `inputs` is empty on a push, and a plain `inputs.buildNextjs ||
37+
# 'true'` fallback would be wrong here: an explicitly UNCHECKED box is
38+
# `false`, which is falsy, so it would silently build anyway. Hence the
39+
# event check — dispatch obeys the box, everything else builds.
40+
BUILD_NEXTJS: ${{ github.event_name != 'workflow_dispatch' || inputs.buildNextjs }}
2541
steps:
2642
- uses: actions/checkout@v6
2743
with:
@@ -48,9 +64,93 @@ jobs:
4864
- name: Setup pnpm
4965
run: npm i -g corepack && corepack enable
5066

51-
- name: Install & build Next.js
67+
# ---- testing next.js from source (BUILD_NEXTJS) -----------------------
68+
#
69+
# Why these steps exist: checking out next.js is NOT enough to test it. In
70+
# deploy mode the harness writes a plain version string into each
71+
# fixture's package.json (`next: NEXT_TEST_VERSION || <local version>`,
72+
# test/lib/next-modes/base.ts:304, reached via
73+
# createTestDir({skipInstall:true}) in next-deploy.ts:239), and a bare
74+
# version installs from the public npm registry. Upstream's own CI gets
75+
# away with that because it points npm at Vercel's preview-builds mirror,
76+
# which needs credentials we don't have — so without the steps below the
77+
# `nextjs` checkout supplies only the harness and the test files, and the
78+
# code under test is whatever npm published. Silently.
79+
#
80+
# These produce the two artifacts that override that, which the test job
81+
# picks up by their presence (see "Use Next.js built from source" there):
82+
# - packages/next-swc/native/*.node -> NEXT_TEST_NATIVE_DIR (Rust)
83+
# - next-packed.tgz -> NEXT_TEST_VERSION (JS)
84+
#
85+
# Read the pinned nightly out of next.js's own rust-toolchain.toml rather
86+
# than repeating the version here, where it would silently drift. The file
87+
# lives in the nested checkout, so the toolchain actions can't find it on
88+
# their own.
89+
- name: Read Rust toolchain
90+
id: rust-toolchain
91+
if: ${{ env.BUILD_NEXTJS == 'true' }}
92+
run: |
93+
CHANNEL="$(grep '^channel' nextjs/rust-toolchain.toml | cut -d'"' -f2)"
94+
echo "Using Rust $CHANNEL"
95+
echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT"
96+
97+
- uses: dtolnay/rust-toolchain@master
98+
if: ${{ env.BUILD_NEXTJS == 'true' }}
99+
with:
100+
toolchain: ${{ steps.rust-toolchain.outputs.channel }}
101+
102+
- uses: Swatinem/rust-cache@v2
103+
if: ${{ env.BUILD_NEXTJS == 'true' }}
104+
with:
105+
workspaces: nextjs
106+
107+
- name: Install Next.js dependencies
108+
working-directory: nextjs
109+
run: pnpm i
110+
111+
# next.js never builds its own native binary when CI is set
112+
# (scripts/install-native.mjs and packages/next-swc/maybe-build-native.mjs
113+
# both bail on `process.env.CI`), on the assumption — true upstream, false
114+
# here — that an earlier job produced it. So it has to be explicit.
115+
#
116+
# Release profile, not debug: a debug Turbopack is slow enough to push
117+
# test shards into their own timeouts.
118+
- name: Build next-swc (Rust)
119+
if: ${{ env.BUILD_NEXTJS == 'true' }}
120+
working-directory: nextjs
121+
run: pnpm --filter @next/swc run build-native-release
122+
123+
- name: Build Next.js
52124
working-directory: nextjs
53-
run: pnpm i && pnpm run build && pnpm i
125+
run: pnpm run build && pnpm i
126+
127+
# Pack next itself for the JS half. Lands inside the cached `nextjs` dir
128+
# so the test shards get it from the same cache as everything else.
129+
- name: Pack Next.js
130+
if: ${{ env.BUILD_NEXTJS == 'true' }}
131+
working-directory: nextjs
132+
run: |
133+
TARBALL="$(cd packages/next && pnpm pack --pack-destination "$PWD/../.." | tail -1)"
134+
mv "$TARBALL" next-packed.tgz
135+
ls -la next-packed.tgz
136+
137+
# Fail loudly rather than letting the shards quietly test published next —
138+
# the whole failure mode this input exists to fix is a silent one.
139+
- name: Verify Next.js was built from source
140+
if: ${{ env.BUILD_NEXTJS == 'true' }}
141+
working-directory: nextjs
142+
run: |
143+
ok=1
144+
if ! ls packages/next-swc/native/*.node >/dev/null 2>&1; then
145+
echo "::error::next-swc native binary missing; shards would use the published @next/swc instead of this checkout's Rust code"
146+
ok=0
147+
fi
148+
if [ ! -f next-packed.tgz ]; then
149+
echo "::error::next-packed.tgz missing; shards would use the published next instead of this checkout's JS"
150+
ok=0
151+
fi
152+
[ "$ok" -eq 1 ] || exit 1
153+
ls -la packages/next-swc/native/
54154
55155
- name: Install Playwright
56156
working-directory: nextjs
@@ -91,7 +191,8 @@ jobs:
91191
"nextjsRepo": "${{ inputs.nextjsRepo || 'vercel/next.js' }}",
92192
"nextjsRef": "${{ inputs.nextjsRef || 'canary' }}",
93193
"nextjsSha": "$NEXT_SHA",
94-
"nextjsVersion": "$NEXT_VERSION"
194+
"nextjsVersion": "$NEXT_VERSION",
195+
"nextjsFromSource": $BUILD_NEXTJS
95196
}
96197
JSON
97198
cat adapter/adapter-e2e-metadata.json
@@ -186,6 +287,29 @@ jobs:
186287
restore-keys: |
187288
next-test-passed-${{ strategy.job-index }}-${{ github.run_id }}-
188289
290+
# Keyed on what the build job actually produced rather than on
291+
# BUILD_NEXTJS, so the two can't disagree: if the artifacts aren't in
292+
# the restored cache, the fixtures install published next and these stay
293+
# unset. Both are read by next itself, not by the harness —
294+
# NEXT_TEST_NATIVE_DIR loads the .node directly (build/swc/index.ts:1552)
295+
# and NEXT_TEST_VERSION becomes the fixture's `next` dependency
296+
# (next-modes/base.ts:304).
297+
- name: Use Next.js built from source (if the build job produced it)
298+
working-directory: nextjs
299+
run: |
300+
if ls packages/next-swc/native/*.node >/dev/null 2>&1; then
301+
echo "NEXT_TEST_NATIVE_DIR=$PWD/packages/next-swc/native" >> "$GITHUB_ENV"
302+
echo "next-swc: local build"
303+
else
304+
echo "next-swc: published @next/swc (from npm)"
305+
fi
306+
if [ -f next-packed.tgz ]; then
307+
echo "NEXT_TEST_VERSION=file:$PWD/next-packed.tgz" >> "$GITHUB_ENV"
308+
echo "next: local pack"
309+
else
310+
echo "next: published (from npm)"
311+
fi
312+
189313
- name: Run deploy tests
190314
working-directory: nextjs
191315
env:

scripts/e2e-local.sh

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,21 +48,23 @@ NETLIFY_SITE_ID="${NETLIFY_SITE_ID:-1d5a5c76-d445-4ae5-b694-b0d3f2e2c395}"
4848
build=0
4949
traces=1
5050
manifest=0
51+
pack_next=0
5152
patterns=()
5253

5354
while [ $# -gt 0 ]; do
5455
case "$1" in
5556
--build) build=1 ;; # rebuild + repack the adapter before running
5657
--no-traces) traces=0 ;; # faster; loses the playwright trace
5758
--manifest) manifest=1 ;; # apply CI's exclusions (see below)
59+
--pack-next) pack_next=1 ;; # test local packages/next JS too (slow)
5860
-h|--help) sed -n '3,40p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
5961
*) patterns+=("$1") ;;
6062
esac
6163
shift
6264
done
6365

6466
if [ ${#patterns[@]} -eq 0 ]; then
65-
echo "Usage: $0 <test-file-or-pattern>... [--build] [--no-traces] [--manifest]" >&2
67+
echo "Usage: $0 <test-file-or-pattern>... [--build] [--no-traces] [--manifest] [--pack-next]" >&2
6668
exit 1
6769
fi
6870

@@ -125,6 +127,49 @@ export NODE_OPTIONS="--import $ADAPTER_DIR/tools/fetch-retry.mjs"
125127
export NEXT_TEST_SKIP_CLEANUP=1
126128
export ADAPTER_DEBUG_LOGS=1
127129

130+
# ---- making the run test YOUR next.js checkout ------------------------------
131+
# By default it does NOT. Deploy mode calls createTestDir({skipInstall: true})
132+
# (next-modes/next-deploy.ts:239), and that path writes a plain version string
133+
# into the fixture's package.json (`next: NEXT_TEST_VERSION || <local version>`,
134+
# next-modes/base.ts:304). A bare version resolves from the public npm registry.
135+
#
136+
# Upstream gets away with this because their CI points npm at Vercel's
137+
# preview-builds mirror, so the version resolves to the PR's own artifact
138+
# (writeMirrorNpmrcIfNecessary, next-deploy.ts:506). It needs
139+
# PREVIEW_BUILDS_READ_TOKEN + NEXT_TEST_PREVIEW_BUILDS_BASE_URL, which are
140+
# Vercel-internal. Without them the harness logs "Skipping .npmrc write" and
141+
# falls back to public npm — so the suite tests PUBLISHED next plus your
142+
# adapter, and nothing from $NEXTJS_DIR except the test files jest reads
143+
# directly. That failure is silent, which is what makes it worth this comment.
144+
#
145+
# NEXT_TEST_NATIVE_DIR is the supported override for the Rust half: next loads
146+
# `next-swc.<triple>.node` straight out of this directory instead of the
147+
# @next/swc npm package (build/swc/index.ts:1552 — "Use the binary directly to
148+
# skip `pnpm pack` for testing as it's slow because of the large native
149+
# binary"). That covers crates/ and turbopack/crates/, and costs nothing since
150+
# the binary is already built.
151+
if ls "$NEXTJS_DIR/packages/next-swc/native/"*.node >/dev/null 2>&1; then
152+
export NEXT_TEST_NATIVE_DIR="$NEXTJS_DIR/packages/next-swc/native"
153+
echo "→ next-swc: local build ($(cd "$NEXTJS_DIR" && git rev-parse --short HEAD))" >&2
154+
else
155+
echo "→ WARNING: no local next-swc binary — Rust changes in $NEXTJS_DIR will NOT be tested." >&2
156+
echo " Build it: (cd $NEXTJS_DIR && pnpm --filter @next/swc run build-native-release)" >&2
157+
fi
158+
159+
# The JS half. Opt-in because packing next is slow and most changes under
160+
# investigation are Rust or adapter-side; without it, packages/next/src edits in
161+
# your checkout are NOT exercised.
162+
if [ "$pack_next" -eq 1 ]; then
163+
echo "→ Packing next from $NEXTJS_DIR (this takes a minute)…" >&2
164+
next_tgz="$(cd "$NEXTJS_DIR/packages/next" && pnpm pack --pack-destination "$TMPDIR" 2>/dev/null | tail -1)"
165+
if [ ! -f "$next_tgz" ]; then
166+
echo "Error: pnpm pack did not produce a tarball (got '$next_tgz')" >&2
167+
exit 1
168+
fi
169+
export NEXT_TEST_VERSION="file:$next_tgz"
170+
echo "→ next: $NEXT_TEST_VERSION" >&2
171+
fi
172+
128173
# CI's manifest (test/deploy-tests-manifest.json) EXCLUDES individual cases it has
129174
# already recorded as failing or flaky. That's right for a green-vs-red signal and
130175
# exactly wrong for triage: the case you're investigating is quite likely one of the
@@ -155,8 +200,19 @@ fi
155200
cd "$NEXTJS_DIR"
156201
echo "→ Running ${patterns[*]} (deploy mode, site $NETLIFY_SITE_ID)" >&2
157202

203+
# --retries 0 (default is 2, run-tests.js:187). Two reasons, both about triage:
204+
#
205+
# A retry re-deploys the whole fixture, so a failing file costs three deploys and
206+
# three timeouts before it reports — and a test that fails once and passes on retry
207+
# is reported as PASSED, which is precisely the flake you were trying to catch.
208+
#
209+
# More urgently: before each retry run-tests.js runs `git clean -fdx` AND
210+
# `git checkout` on the test's directory (run-tests.js:886-897) to reset fixture
211+
# state. That is destructive to UNCOMMITTED work in the next.js checkout — edit a
212+
# test file, have it fail, and the retry silently reverts your edit. Nothing warns
213+
# you; the run just starts passing again against the old code.
158214
status=0
159-
node run-tests.js --type e2e --debug --test-pattern "${patterns[*]}" || status=$?
215+
node run-tests.js --type e2e --debug --retries 0 --test-pattern "${patterns[*]}" || status=$?
160216

161217
if [ "$traces" -eq 1 ] && [ -d "$NEXTJS_DIR/test/traces" ]; then
162218
echo >&2

0 commit comments

Comments
 (0)