|
14 | 14 | description: "Next.js repo ref (branch/tag/SHA)" |
15 | 15 | default: "canary" |
16 | 16 | 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 |
17 | 24 | # schedule: |
18 | 25 | # - cron: '0 2 * * *' |
19 | 26 |
|
20 | 27 | jobs: |
21 | 28 | build: |
22 | 29 | name: Build Next.js + adapter |
23 | 30 | 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 }} |
25 | 41 | steps: |
26 | 42 | - uses: actions/checkout@v6 |
27 | 43 | with: |
|
48 | 64 | - name: Setup pnpm |
49 | 65 | run: npm i -g corepack && corepack enable |
50 | 66 |
|
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 |
52 | 124 | 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/ |
54 | 154 |
|
55 | 155 | - name: Install Playwright |
56 | 156 | working-directory: nextjs |
|
91 | 191 | "nextjsRepo": "${{ inputs.nextjsRepo || 'vercel/next.js' }}", |
92 | 192 | "nextjsRef": "${{ inputs.nextjsRef || 'canary' }}", |
93 | 193 | "nextjsSha": "$NEXT_SHA", |
94 | | - "nextjsVersion": "$NEXT_VERSION" |
| 194 | + "nextjsVersion": "$NEXT_VERSION", |
| 195 | + "nextjsFromSource": $BUILD_NEXTJS |
95 | 196 | } |
96 | 197 | JSON |
97 | 198 | cat adapter/adapter-e2e-metadata.json |
@@ -186,6 +287,29 @@ jobs: |
186 | 287 | restore-keys: | |
187 | 288 | next-test-passed-${{ strategy.job-index }}-${{ github.run_id }}- |
188 | 289 |
|
| 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 | +
|
189 | 313 | - name: Run deploy tests |
190 | 314 | working-directory: nextjs |
191 | 315 | env: |
|
0 commit comments