From 729e60540468ef1661054eecf63d8482a163c7fe Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Mon, 21 Oct 2024 16:07:28 -0400 Subject: [PATCH 01/11] ci: adjust tested versions given next@15 is stable (#2688) --- .github/workflows/e2e-report.yml | 1 + .github/workflows/run-tests.yml | 2 +- .github/workflows/test-e2e.yml | 2 +- CONTRIBUTING.md | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e-report.yml b/.github/workflows/e2e-report.yml index 74ec22b895..09b079f7a7 100644 --- a/.github/workflows/e2e-report.yml +++ b/.github/workflows/e2e-report.yml @@ -13,6 +13,7 @@ on: options: - 'latest' - 'canary' + - '14.2.15' - '13.5.1' default: 'latest' diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 8a54eb5f74..3ff2d565bc 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -36,7 +36,7 @@ jobs: if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then echo "matrix=${{ github.event.inputs.versions }}" >> $GITHUB_OUTPUT elif [ "${{ github.event_name }}" = "schedule" ] || [ "${{ steps.check-labels.outputs.result }}" = "true" ]; then - echo "matrix=[\"latest\", \"canary\", \"13.5.1\"]" >> $GITHUB_OUTPUT + echo "matrix=[\"latest\", \"canary\", \"14.2.15\", \"13.5.1\"]" >> $GITHUB_OUTPUT else echo "matrix=[\"latest\"]" >> $GITHUB_OUTPUT fi diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 90d9bf05f4..0f0ff029d5 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -63,7 +63,7 @@ jobs: echo "group=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]" >> $GITHUB_OUTPUT echo "total=12" >> $GITHUB_OUTPUT else - VERSION_SELECTORS=[\"latest\",\"canary\",\"13.5.1\"] + VERSION_SELECTORS=[\"latest\",\"canary\","\14.2.15\",\"13.5.1\"] echo "group=[1, 2, 3, 4]" >> $GITHUB_OUTPUT echo "total=4" >> $GITHUB_OUTPUT fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3a3fceee08..aed7aa9949 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,8 +63,8 @@ tests against a specific version, set the `NEXT_VERSION` environment variable to version. By default, PRs will run the tests against the latest version of Next.js. To run tests against -`latest`, `canary` and `13.5.1`, apply the `test all versions` label to the PR when you create it. -These also run nightly and on release PRs. +`latest`, `canary`, `14.2.15`,`and`13.5.1`, apply the`test all versions` label to the PR when you +create it. These also run nightly and on release PRs. ### Integration testing From 83abe673eae408c2aef3eb99d3b7c7d4f0b06050 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 20:27:07 +0000 Subject: [PATCH 02/11] fix(deps): update dependency @netlify/plugin-nextjs to ^5.8.1 (#2689) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- e2e-report/package-lock.json | 8 ++++---- e2e-report/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e-report/package-lock.json b/e2e-report/package-lock.json index a3d21ad1b0..d1aca37af3 100644 --- a/e2e-report/package-lock.json +++ b/e2e-report/package-lock.json @@ -8,7 +8,7 @@ "name": "e2e-test-site", "version": "0.2.0", "dependencies": { - "@netlify/plugin-nextjs": "^5.8.0", + "@netlify/plugin-nextjs": "^5.8.1", "next": "^14.2.3", "react": "^18.3.1", "react-dom": "^18.3.1" @@ -263,9 +263,9 @@ } }, "node_modules/@netlify/plugin-nextjs": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/@netlify/plugin-nextjs/-/plugin-nextjs-5.8.0.tgz", - "integrity": "sha512-1hduTMFYkZRSqRAPK1+5A6T/uc543PfxX0erzJEVWb9fKgPlY24ZQMi2nXBwlQ6JlBYjZJcMEtN2vhhNqyjgXA==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@netlify/plugin-nextjs/-/plugin-nextjs-5.8.1.tgz", + "integrity": "sha512-WB1N0FslhWZ1yAVYTcB6CcFrFOUSQ0O2LfavYZrbAypeNxu2I+oO+cgmhfDgZ8Eoq1g4EMeoIGMkNoZ4ogZTsg==", "license": "MIT", "engines": { "node": ">=18.0.0" diff --git a/e2e-report/package.json b/e2e-report/package.json index 38b7c1e5c9..de3ef7a939 100644 --- a/e2e-report/package.json +++ b/e2e-report/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "@netlify/plugin-nextjs": "^5.8.0", + "@netlify/plugin-nextjs": "^5.8.1", "next": "^14.2.3", "react": "^18.3.1", "react-dom": "^18.3.1" From e9fa325ea7ec0ee76af864cb814ce0ce763f582f Mon Sep 17 00:00:00 2001 From: Mateusz Bocian Date: Mon, 21 Oct 2024 17:34:28 -0400 Subject: [PATCH 03/11] fix: small typo in test config (#2690) --- .github/workflows/test-e2e.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 0f0ff029d5..e515bb3afb 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -63,7 +63,7 @@ jobs: echo "group=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]" >> $GITHUB_OUTPUT echo "total=12" >> $GITHUB_OUTPUT else - VERSION_SELECTORS=[\"latest\",\"canary\","\14.2.15\",\"13.5.1\"] + VERSION_SELECTORS=[\"latest\",\"canary\",\"14.2.15\",\"13.5.1\"] echo "group=[1, 2, 3, 4]" >> $GITHUB_OUTPUT echo "total=4" >> $GITHUB_OUTPUT fi From 603e24e013c96a9faa8a5d8eea315f6ffc820966 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 02:43:39 +0000 Subject: [PATCH 04/11] chore(deps): update dependency @netlify/build to ^29.55.4 (#2691) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 91599cc6d3..410fb9d3b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "devDependencies": { "@fastly/http-compute-js": "1.1.4", "@netlify/blobs": "^8.1.0", - "@netlify/build": "^29.55.3", + "@netlify/build": "^29.55.4", "@netlify/edge-bundler": "^12.2.3", "@netlify/edge-functions": "^2.11.0", "@netlify/eslint-config-node": "^7.0.1", @@ -3890,9 +3890,9 @@ } }, "node_modules/@netlify/build": { - "version": "29.55.3", - "resolved": "https://registry.npmjs.org/@netlify/build/-/build-29.55.3.tgz", - "integrity": "sha512-oMLi3GLWpppsUjzkDsWklmkUuTcAAE5trBK1dOydKX6eG+3+5hKHNxLoRWeyzRDCmYduIELZQNiDRv8Q1CCCNQ==", + "version": "29.55.4", + "resolved": "https://registry.npmjs.org/@netlify/build/-/build-29.55.4.tgz", + "integrity": "sha512-J8ivGhxS4UrJOFGKXVtsD+2RRuJtvFQ0QFNgeAZsoBAlxyPqDeWLmomqA8Nf/z3YeKOru9pjArn9sK6omeTNpw==", "dev": true, "dependencies": { "@bugsnag/js": "^7.0.0", @@ -38898,9 +38898,9 @@ "dev": true }, "@netlify/build": { - "version": "29.55.3", - "resolved": "https://registry.npmjs.org/@netlify/build/-/build-29.55.3.tgz", - "integrity": "sha512-oMLi3GLWpppsUjzkDsWklmkUuTcAAE5trBK1dOydKX6eG+3+5hKHNxLoRWeyzRDCmYduIELZQNiDRv8Q1CCCNQ==", + "version": "29.55.4", + "resolved": "https://registry.npmjs.org/@netlify/build/-/build-29.55.4.tgz", + "integrity": "sha512-J8ivGhxS4UrJOFGKXVtsD+2RRuJtvFQ0QFNgeAZsoBAlxyPqDeWLmomqA8Nf/z3YeKOru9pjArn9sK6omeTNpw==", "dev": true, "requires": { "@bugsnag/js": "^7.0.0", diff --git a/package.json b/package.json index d005b57976..fb82d84bda 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "devDependencies": { "@fastly/http-compute-js": "1.1.4", "@netlify/blobs": "^8.1.0", - "@netlify/build": "^29.55.3", + "@netlify/build": "^29.55.4", "@netlify/edge-bundler": "^12.2.3", "@netlify/edge-functions": "^2.11.0", "@netlify/eslint-config-node": "^7.0.1", From eba1610065cc6ae4ab80e273e57b4ead91494c8a Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 22 Oct 2024 11:19:45 +0200 Subject: [PATCH 05/11] ci: use includePrerelease flag for version checks (#2692) --- tests/utils/next-version-helpers.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils/next-version-helpers.mjs b/tests/utils/next-version-helpers.mjs index 8d10d32dd5..52150e4904 100644 --- a/tests/utils/next-version-helpers.mjs +++ b/tests/utils/next-version-helpers.mjs @@ -21,7 +21,7 @@ export function nextVersionSatisfies(condition) { const isSemverVersion = valid(version) const checkVersion = isSemverVersion ? version : FUTURE_NEXT_PATCH_VERSION - return satisfies(checkVersion, condition) || version === condition + return satisfies(checkVersion, condition, { includePrerelease: true }) || version === condition } /** From c1a20f74a9eed9e7e0c7eeaa6fc52aad2429748d Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 23 Oct 2024 20:03:08 +0200 Subject: [PATCH 06/11] chore: handle repo rename (#2696) Co-authored-by: Philippe Serhal --- .github/pull_request_template.md | 2 +- .github/workflows/e2e-report.yml | 2 +- .github/workflows/test-e2e.yml | 12 ++++++------ CONTRIBUTING.md | 12 ++++++------ README.md | 5 +++-- package.json | 6 +++--- run-local-test.sh | 2 +- tests/netlify-deploy.ts | 4 ++-- tests/utils/create-e2e-fixture.ts | 2 +- tests/utils/fixture.ts | 6 +++--- tests/utils/helpers.ts | 2 +- tools/build.js | 2 +- 12 files changed, 29 insertions(+), 28 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c785cb9821..7d60388b42 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ - + ## Description diff --git a/.github/workflows/e2e-report.yml b/.github/workflows/e2e-report.yml index 09b079f7a7..61239180e5 100644 --- a/.github/workflows/e2e-report.yml +++ b/.github/workflows/e2e-report.yml @@ -43,7 +43,7 @@ jobs: version=${version:-latest} OUTPUT_DIR="e2e-report/data" OUTPUT_FILENAME="test-results.json" - echo "Downloading ${version} test results from run https://github.com/netlify/next-runtime/actions/runs/${{ steps.get-run-id.outputs.runId }}" + echo "Downloading ${version} test results from run https://github.com/opennextjs/opennextjs-netlify/actions/runs/${{ steps.get-run-id.outputs.runId }}" rm "${OUTPUT_DIR}/${OUTPUT_FILENAME}" artifact_name="${version}-test-results.json" # NOTE: The artifact name is not necessarily the artifact *file* name. The file name here diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index e515bb3afb..33f9e02663 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -39,7 +39,7 @@ env: NETLIFY_SITE_ID: 1d5a5c76-d445-4ae5-b694-b0d3f2e2c395 NEXT_TEST_CONTINUE_ON_ERROR: 1 next-path: next.js - runtime-path: next-runtime + runtime-path: opennextjs-netlify GH_TOKEN: ${{ github.token }} jobs: setup: @@ -136,7 +136,7 @@ jobs: uses: actions/cache@v4 with: path: ${{ steps.npm-cache.outputs.PATH }} - key: node-cache-${{ hashFiles('next-runtime/package-lock.json') }} + key: node-cache-${{ hashFiles('opennextjs-netlify/package-lock.json') }} restore-keys: | node-cache- @@ -186,11 +186,11 @@ jobs: run: | # This is when the manifest version was changed if [ `npx semver -p -r ">=14.0.4-canary.26" ${{ matrix.version_spec.version }}` ]; then - echo "filters=../next-runtime/tests/netlify-e2e.cjs" >> $GITHUB_OUTPUT - echo "skip-retry=../next-runtime/tests/e2e-skip-retry.json" >> $GITHUB_OUTPUT + echo "filters=../opennextjs-netlify/tests/netlify-e2e.cjs" >> $GITHUB_OUTPUT + echo "skip-retry=../opennextjs-netlify/tests/e2e-skip-retry.json" >> $GITHUB_OUTPUT else - echo "filters=../next-runtime/tests/netlify-e2e-legacy.json" >> $GITHUB_OUTPUT - echo "skip-retry=../next-runtime/tests/e2e-skip-retry-legacy.json" >> $GITHUB_OUTPUT + echo "filters=../opennextjs-netlify/tests/netlify-e2e-legacy.json" >> $GITHUB_OUTPUT + echo "skip-retry=../opennextjs-netlify/tests/e2e-skip-retry-legacy.json" >> $GITHUB_OUTPUT fi - name: run tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index aed7aa9949..e1e5e84440 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,8 +3,8 @@ 🎉 Thanks for considering contributing to this project! 🎉 When contributing to this repository, please first discuss the change you wish to make via an -[issue](https://github.com/netlify/next-runtime/issues/new/choose). Please use the issue templates. -They are there to help you and to help the maintainers gather information. +[issue](https://github.com/opennextjs/opennextjs-netlify/issues/new/choose). Please use the issue +templates. They are there to help you and to help the maintainers gather information. Before working on an issue, ask to be assigned to it. This makes it clear to other potential contributors that someone is working on the issue. @@ -31,7 +31,7 @@ Make sure everything is correctly setup with: npm test ``` -## Lambda Folder structure: +## Lambda Folder structure For a simple next.js app @@ -96,7 +96,7 @@ given prefix, run `npm run pretest -- `. The e2e tests can be invoked with `npm run e2e` and perform a full e2e test. This means they do the following: -1. Building the next-runtime (just running `npm run build` in the repository) +1. Building the adapter (just running `npm run build` in the repository) 2. Creating a temp directory and copying the provided fixture over to the directory. 3. Packing the runtime with `npm pack` to the temp directory. 4. Installing the runtime from the created zip artifact of `npm pack` (this is like installing a @@ -147,8 +147,8 @@ Most common commit message prefixes are: ## How to make a minimal reproduction A reproducible test case is a small Next.js site built to demonstrate a problem - often this problem -is caused by a bug in Next.js, next-runtime or user code. Your reproducible test case should contain -the bare minimum features needed to clearly demonstrate the bug. +is caused by a bug in Next.js, @opennextjs/netlify or user code. Your reproducible test case should +contain the bare minimum features needed to clearly demonstrate the bug. Steps to create a reproducible test case: diff --git a/README.md b/README.md index 8001a14420..952bf74a44 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,8 @@ To upgrade from v4 to v5, please visit ## Feedback If you think you have found a bug in Next.js on Netlify, -[please open an issue](https://github.com/netlify/next-runtime/issues). If you have comments or -feature requests, [see the discussion board](https://github.com/netlify/next-runtime/discussions) +[please open an issue](https://github.com/opennextjs/opennextjs-netlify/issues). If you have +comments or feature requests, +[see the discussion board](https://github.com/opennextjs/opennextjs-netlify/discussions) Please note that v4 will only receive security fixes and critical bug fixes. diff --git a/package.json b/package.json index fb82d84bda..f490872995 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/netlify/next-runtime.git" + "url": "git+https://github.com/opennextjs/opennextjs-netlify.git" }, "keywords": [ "nextjs", @@ -44,9 +44,9 @@ ], "license": "MIT", "bugs": { - "url": "https://github.com/netlify/next-runtime/issues" + "url": "https://github.com/opennextjs/opennextjs-netlify/issues" }, - "homepage": "https://github.com/netlify/next-runtime#readme", + "homepage": "https://opennext.js.org/netlify", "devDependencies": { "@fastly/http-compute-js": "1.1.4", "@netlify/blobs": "^8.1.0", diff --git a/run-local-test.sh b/run-local-test.sh index 4cfa139e8c..f3b8251f1e 100755 --- a/run-local-test.sh +++ b/run-local-test.sh @@ -15,7 +15,7 @@ export NEXT_TEST_MODE=deploy export RUNTIME_DIR=$(pwd) cp tests/netlify-deploy.ts ../next.js/test/lib/next-modes/netlify-deploy.ts cd ../next.js/ -git apply ../next-runtime/tests/e2e-utils.patch || git apply ../next-runtime/tests/e2e-utils-v2.patch +git apply ../opennextjs-netlify/tests/e2e-utils.patch || git apply ../opennextjs-netlify/tests/e2e-utils-v2.patch node run-tests.js --type e2e --debug --test-pattern $1 git checkout -- test/lib/e2e-utils.ts diff --git a/tests/netlify-deploy.ts b/tests/netlify-deploy.ts index 7a33f26dab..f68e525834 100644 --- a/tests/netlify-deploy.ts +++ b/tests/netlify-deploy.ts @@ -16,12 +16,12 @@ type NetlifyDeployResponse = { } async function packNextRuntimeImpl() { - const runtimePackDir = await fs.mkdtemp(path.join(tmpdir(), 'next-runtime-pack')) + const runtimePackDir = await fs.mkdtemp(path.join(tmpdir(), 'opennextjs-netlify-pack')) const { stdout } = await execa( 'npm', ['pack', '--json', '--ignore-scripts', `--pack-destination=${runtimePackDir}`], - { cwd: process.env.RUNTIME_DIR || `${process.cwd()}/../next-runtime` }, + { cwd: process.env.RUNTIME_DIR || `${process.cwd()}/../opennextjs-netlify` }, ) const [{ filename, name }] = JSON.parse(stdout) diff --git a/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index 7d728acef7..c8697d47ff 100644 --- a/tests/utils/create-e2e-fixture.ts +++ b/tests/utils/create-e2e-fixture.ts @@ -51,7 +51,7 @@ interface E2EConfig { * @param fixture name of the folder inside the fixtures folder */ export const createE2EFixture = async (fixture: string, config: E2EConfig = {}) => { - const isolatedFixtureRoot = await mkdtemp(join(tmpdir(), 'netlify-next-runtime-')) + const isolatedFixtureRoot = await mkdtemp(join(tmpdir(), 'opennextjs-netlify-')) let deployID: string let logs: string const _cleanup = (failure: boolean = false) => { diff --git a/tests/utils/fixture.ts b/tests/utils/fixture.ts index db86b9ea32..61a08c9080 100644 --- a/tests/utils/fixture.ts +++ b/tests/utils/fixture.ts @@ -95,7 +95,7 @@ export const createFixture = async (fixture: string, ctx: FixtureTestContext) => delete globalThis[Symbol.for('next-patch')] } - ctx.cwd = await mkdtemp(join(tmpdir(), 'netlify-next-runtime-')) + ctx.cwd = await mkdtemp(join(tmpdir(), 'opennextjs-netlify-')) vi.spyOn(process, 'cwd').mockReturnValue(ctx.cwd) ctx.cleanup = [] @@ -156,7 +156,7 @@ export const createFixture = async (fixture: string, ctx: FixtureTestContext) => } export const createFsFixture = async (fixture: Record, ctx: FixtureTestContext) => { - ctx.cwd = await mkdtemp(join(tmpdir(), 'netlify-next-runtime-')) + ctx.cwd = await mkdtemp(join(tmpdir(), 'opennextjs-netlify-')) vi.spyOn(process, 'cwd').mockReturnValue(ctx.cwd) ctx.cleanup = [ async () => { @@ -261,7 +261,7 @@ export async function runPlugin( } // create zip location in a new temp folder to avoid leaking node_modules through nodes resolve algorithm // that always looks up a parent directory for node_modules - ctx.functionDist = await mkdtemp(join(tmpdir(), 'netlify-next-runtime-dist')) + ctx.functionDist = await mkdtemp(join(tmpdir(), 'opennextjs-netlify-dist')) // bundle the function to get the bootstrap layer and all the important parts await zipFunctions([internalSrcFolder], ctx.functionDist, { basePath: ctx.cwd, diff --git a/tests/utils/helpers.ts b/tests/utils/helpers.ts index db6961734b..34d9545dfd 100644 --- a/tests/utils/helpers.ts +++ b/tests/utils/helpers.ts @@ -48,7 +48,7 @@ export const startMockBlobStore = async (ctx: FixtureTestContext) => { ctx.blobServer = new BlobsServer({ port, token: BLOB_TOKEN, - directory: await mkdtemp(join(tmpdir(), 'netlify-next-runtime-blob-')), + directory: await mkdtemp(join(tmpdir(), 'opennextjs-netlify-blob-')), }) await ctx.blobServer.start() ctx.blobServerGetSpy = vi.spyOn(ctx.blobServer, 'get') diff --git a/tools/build.js b/tools/build.js index 5c8a5d6274..5db351d152 100644 --- a/tools/build.js +++ b/tools/build.js @@ -36,7 +36,7 @@ async function bundle(entryPoints, format, watch) { name: 'mark-runtime-modules-as-external', setup(pluginBuild) { pluginBuild.onResolve({ filter: /^\..*\.c?js$/ }, (args) => { - if (args.importer.includes(join('next-runtime', 'src'))) { + if (args.importer.includes(join('opennextjs-netlify', 'src'))) { return { path: args.path, external: true } } }) From 3e745e4e95d2f9a53dc971a9d07a5cc876217dbf Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Thu, 24 Oct 2024 16:09:59 +0200 Subject: [PATCH 07/11] test: skip reporting CanaryOnly failures for stable version tests (#2698) * ci: decrease number of shards when running Vercel tests * test: use PWD instead of hardcoded repo directory * test: collect build logs in Vercel e2e tests (like we do in our own e2e tests) * test: skip esm-externals-false tests (they are just checking CLI output) * ci: skip reporting about tests that use canary only features when testing stable * test: fix typo when setting _cliOutput --- .github/workflows/test-e2e.yml | 8 ++++---- run-local-test.sh | 2 +- tests/netlify-deploy.ts | 35 ++++++++++++++++++---------------- tests/test-config.json | 8 +++++++- tools/deno/junit2json.ts | 7 +++++++ 5 files changed, 38 insertions(+), 22 deletions(-) diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 33f9e02663..6d6ab29334 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -56,12 +56,12 @@ jobs: run: | if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then VERSION_SELECTORS=[${{ github.event.inputs.versions }}] - echo "group=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]" >> $GITHUB_OUTPUT - echo "total=12" >> $GITHUB_OUTPUT + echo "group=[1, 2, 3, 4]" >> $GITHUB_OUTPUT + echo "total=4" >> $GITHUB_OUTPUT elif [ "${{ github.event_name }}" == "pull_request" ]; then VERSION_SELECTORS=[\"latest\"] - echo "group=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]" >> $GITHUB_OUTPUT - echo "total=12" >> $GITHUB_OUTPUT + echo "group=[1, 2, 3, 4]" >> $GITHUB_OUTPUT + echo "total=4" >> $GITHUB_OUTPUT else VERSION_SELECTORS=[\"latest\",\"canary\",\"14.2.15\",\"13.5.1\"] echo "group=[1, 2, 3, 4]" >> $GITHUB_OUTPUT diff --git a/run-local-test.sh b/run-local-test.sh index f3b8251f1e..5c1951975e 100755 --- a/run-local-test.sh +++ b/run-local-test.sh @@ -15,7 +15,7 @@ export NEXT_TEST_MODE=deploy export RUNTIME_DIR=$(pwd) cp tests/netlify-deploy.ts ../next.js/test/lib/next-modes/netlify-deploy.ts cd ../next.js/ -git apply ../opennextjs-netlify/tests/e2e-utils.patch || git apply ../opennextjs-netlify/tests/e2e-utils-v2.patch +git apply $RUNTIME_DIR/tests/e2e-utils.patch || git apply $RUNTIME_DIR/tests/e2e-utils-v2.patch node run-tests.js --type e2e --debug --test-pattern $1 git checkout -- test/lib/e2e-utils.ts diff --git a/tests/netlify-deploy.ts b/tests/netlify-deploy.ts index f68e525834..cbc6ab4982 100644 --- a/tests/netlify-deploy.ts +++ b/tests/netlify-deploy.ts @@ -6,15 +6,6 @@ import { tmpdir } from 'node:os' import path from 'path' import { NextInstance } from './base' -type NetlifyDeployResponse = { - name: string - site_id: string - site_name: string - deploy_id: string - deploy_url: string - logs: string -} - async function packNextRuntimeImpl() { const runtimePackDir = await fs.mkdtemp(path.join(tmpdir(), 'opennextjs-netlify-pack')) @@ -133,7 +124,7 @@ export class NextDeployInstance extends NextInstance { const deployRes = await execa( 'npx', - ['netlify', 'deploy', '--build', '--json', '--message', deployTitle ?? ''], + ['netlify', 'deploy', '--build', '--message', deployTitle ?? ''], { cwd: this.testDir, reject: false, @@ -142,17 +133,29 @@ export class NextDeployInstance extends NextInstance { if (deployRes.exitCode !== 0) { throw new Error( - `Failed to deploy project ${deployRes.stdout} ${deployRes.stderr} (${deployRes.exitCode})`, + `Failed to deploy project (${deployRes.exitCode}) ${deployRes.stdout} ${deployRes.stderr} `, ) } try { - const data: NetlifyDeployResponse = JSON.parse(deployRes.stdout) - this._url = data.deploy_url + const [url] = new RegExp(/https:.+\.netlify\.app/gm).exec(deployRes.stdout) || [] + if (!url) { + throw new Error('Could not extract the URL from the build logs') + } + const [deployID] = new URL(url).host.split('--') + this._url = url this._parsedUrl = new URL(this._url) - this._deployId = data.deploy_id - require('console').log(`Deployment URL: ${data.deploy_url}`) - require('console').log(`Logs: ${data.logs}`) + this._deployId = deployID + this._cliOutput = deployRes.stdout + deployRes.stderr + require('console').log(`Deployment URL: ${this._url}`) + + const [buildLogsUrl] = + new RegExp(/https:\/\/app\.netlify\.com\/sites\/.+\/deploys\/[0-9a-f]+/gm).exec( + deployRes.stdout, + ) || [] + if (buildLogsUrl) { + require('console').log(`Logs: ${buildLogsUrl}`) + } } catch (err) { console.error(err) throw new Error(`Failed to parse deploy output: ${deployRes.stdout}`) diff --git a/tests/test-config.json b/tests/test-config.json index 13cf56a21b..b8fd95db11 100644 --- a/tests/test-config.json +++ b/tests/test-config.json @@ -124,7 +124,9 @@ { "file": "test/e2e/app-dir/app-static/app-static.test.ts", "reason": "Uses CLI output", - "tests": ["app-dir static/dynamic handling should warn for too many cache tags"] + "tests": [ + "app-dir static/dynamic handling should warn for too many cache tags" + ] }, { "file": "test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts", @@ -358,6 +360,10 @@ { "file": "test/e2e/app-dir/dynamic-io-request-apis/dynamic-io-request-apis.test", "reason": "Uses CLI output" + }, + { + "file": "test/e2e/next-config-warnings/esm-externals-false/esm-externals-false.test.ts", + "reason": "Uses CLI output" } ], "failures": [ diff --git a/tools/deno/junit2json.ts b/tools/deno/junit2json.ts index d1c133ebf1..d38847056f 100644 --- a/tools/deno/junit2json.ts +++ b/tools/deno/junit2json.ts @@ -117,6 +117,13 @@ function junitToJson(xmlData: { testsuites: JUnitTestSuites }): Array if (skippedTestsForFile?.some(({ name }) => name === testCase['@name'])) { continue } + + // skip reporting on tests that even fail to deploy because they rely on experiments not available + // in currently tested version + if (testCase.failure?.includes('CanaryOnlyError')) { + continue + } + const status = testCase.failure ? 'failed' : 'passed' const test: TestCase = { name: testCase['@name'], From d3d2fb5c7a3f87af29602c884e81987819286e7c Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 29 Oct 2024 19:57:15 +0100 Subject: [PATCH 08/11] ci: add deploy cleanup step for Vercel tests (#2699) * test: add deploy cleanup step for Vercel tests * test: increase timeout for Vercel tests * test: bump timeout * ci: allow using test results from runs triggered by pull_request when deploying e2e report using branch results --- .github/workflows/e2e-report.yml | 2 +- .github/workflows/test-e2e.yml | 2 +- tests/netlify-deploy.ts | 31 +++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-report.yml b/.github/workflows/e2e-report.yml index 61239180e5..b3c22c4402 100644 --- a/.github/workflows/e2e-report.yml +++ b/.github/workflows/e2e-report.yml @@ -31,7 +31,7 @@ jobs: id: get-run-id run: | if [ "${{ inputs.use-branch }}" == "true" ]; then - E2E_RUN_ID=$(gh run list -w test-e2e.yml -e workflow_dispatch -s success -b $GITHUB_REF_NAME --json databaseId --jq ".[0].databaseId" --repo $GITHUB_REPOSITORY) + E2E_RUN_ID=$(gh run list -w test-e2e.yml -s success -b $GITHUB_REF_NAME --json databaseId --jq ".[0].databaseId" --repo $GITHUB_REPOSITORY) else E2E_RUN_ID=$(gh run list -w test-e2e.yml -e schedule -s success --json databaseId --jq ".[0].databaseId" --repo $GITHUB_REPOSITORY) fi diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 6d6ab29334..ddd2f6a534 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -28,7 +28,7 @@ env: DATADOG_TRACE_NEXTJS_TEST: true DATADOG_API_KEY: foo TEST_CONCURRENCY: 2 - NEXT_E2E_TEST_TIMEOUT: 300000 + NEXT_E2E_TEST_TIMEOUT: 600000 NEXT_TELEMETRY_DISABLED: 1 NEXT_SKIP_NATIVE_POSTINSTALL: 1 TURBO_API: ${{ secrets.TURBO_API }} diff --git a/tests/netlify-deploy.ts b/tests/netlify-deploy.ts index cbc6ab4982..13664acebe 100644 --- a/tests/netlify-deploy.ts +++ b/tests/netlify-deploy.ts @@ -35,6 +35,7 @@ export class NextDeployInstance extends NextInstance { private _cliOutput: string private _buildId: string private _deployId: string + private _shouldDeleteDeploy: boolean = false public get buildId() { // get deployment ID via fetch since we can't access @@ -50,6 +51,9 @@ export class NextDeployInstance extends NextInstance { this._buildId = process.env.BUILD_ID return } + + const setupStartTime = Date.now() + // create the test site await super.createTestDir({ parentSpan, skipInstall: true }) @@ -146,6 +150,7 @@ export class NextDeployInstance extends NextInstance { this._url = url this._parsedUrl = new URL(this._url) this._deployId = deployID + this._shouldDeleteDeploy = !process.env.NEXT_TEST_SKIP_CLEANUP this._cliOutput = deployRes.stdout + deployRes.stderr require('console').log(`Deployment URL: ${this._url}`) @@ -169,6 +174,32 @@ export class NextDeployInstance extends NextInstance { ).trim() require('console').log(`Got buildId: ${this._buildId}`) + require('console').log(`Setup time: ${(Date.now() - setupStartTime) / 1000.0}s`) + } + + public async destroy(): Promise { + if (this._shouldDeleteDeploy) { + require('console').log(`Deleting project with deploy_id ${this._deployId}`) + + const deleteResponse = await execa('npx', [ + 'ntl', + 'api', + 'deleteDeploy', + '--data', + `{ "deploy_id": "${this._deployId}" }`, + ]) + + if (deleteResponse.exitCode !== 0) { + require('console').error( + `Failed to delete deploy ${deleteResponse.stdout} ${deleteResponse.stderr} (${deleteResponse.exitCode})`, + ) + } else { + require('console').log(`Successfully deleted deploy with deploy_id ${this._deployId}`) + this._shouldDeleteDeploy = false + } + } + + await super.destroy() } public get cliOutput() { From 00e3a4be19bf8428ea6ce1ed2ae74a8ac8375532 Mon Sep 17 00:00:00 2001 From: Rob Stanford Date: Fri, 1 Nov 2024 17:29:17 +0000 Subject: [PATCH 09/11] fix: add data request query param to the cache key (#2701) * fix: vary cache key on __nextDataReq query param * test: update header tests to check for new vary param --- src/run/headers.test.ts | 14 +++++++------- src/run/headers.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/run/headers.test.ts b/src/run/headers.test.ts index 4ca423e402..f20bee6a41 100644 --- a/src/run/headers.test.ts +++ b/src/run/headers.test.ts @@ -40,7 +40,7 @@ describe('headers', () => { expect(headers.set).toBeCalledWith( 'netlify-vary', - 'header=x-nextjs-data|x-next-debug-logging,cookie=__prerender_bypass|__next_preview_data', + 'query=__nextDataReq,header=x-nextjs-data|x-next-debug-logging,cookie=__prerender_bypass|__next_preview_data', ) }) @@ -56,7 +56,7 @@ describe('headers', () => { expect(headers.set).toBeCalledWith( 'netlify-vary', - 'header=x-nextjs-data|x-next-debug-logging|Accept|Accept-Language,cookie=__prerender_bypass|__next_preview_data', + 'query=__nextDataReq,header=x-nextjs-data|x-next-debug-logging|Accept|Accept-Language,cookie=__prerender_bypass|__next_preview_data', ) }) @@ -77,7 +77,7 @@ describe('headers', () => { expect(headers.set).toBeCalledWith( 'netlify-vary', - 'header=x-nextjs-data|x-next-debug-logging,cookie=__prerender_bypass|__next_preview_data', + 'query=__nextDataReq,header=x-nextjs-data|x-next-debug-logging,cookie=__prerender_bypass|__next_preview_data', ) }) @@ -97,7 +97,7 @@ describe('headers', () => { expect(headers.set).toBeCalledWith( 'netlify-vary', - 'header=x-nextjs-data|x-next-debug-logging,cookie=__prerender_bypass|__next_preview_data', + 'query=__nextDataReq,header=x-nextjs-data|x-next-debug-logging,cookie=__prerender_bypass|__next_preview_data', ) }) @@ -117,7 +117,7 @@ describe('headers', () => { expect(headers.set).toBeCalledWith( 'netlify-vary', - 'header=x-nextjs-data|x-next-debug-logging,language=en|de|fr,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE', + 'query=__nextDataReq,header=x-nextjs-data|x-next-debug-logging,language=en|de|fr,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE', ) }) @@ -138,7 +138,7 @@ describe('headers', () => { expect(headers.set).toBeCalledWith( 'netlify-vary', - 'header=x-nextjs-data|x-next-debug-logging,language=en|de|fr,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE', + 'query=__nextDataReq,header=x-nextjs-data|x-next-debug-logging,language=en|de|fr,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE', ) }) @@ -185,7 +185,7 @@ describe('headers', () => { expect(headers.set).toBeCalledWith( 'netlify-vary', - 'query=item_id|page|per_page,header=x-nextjs-data|x-next-debug-logging|x-custom-header,language=en|de|fr|es,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE|ab_test,country=es', + 'query=__nextDataReq|item_id|page|per_page,header=x-nextjs-data|x-next-debug-logging|x-custom-header,language=en|de|fr|es,cookie=__prerender_bypass|__next_preview_data|NEXT_LOCALE|ab_test,country=es', ) }) }) diff --git a/src/run/headers.ts b/src/run/headers.ts index 12c56871a5..e1ad53e37d 100644 --- a/src/run/headers.ts +++ b/src/run/headers.ts @@ -81,7 +81,7 @@ export const setVaryHeaders = ( header: ['x-nextjs-data', 'x-next-debug-logging'], language: [], cookie: ['__prerender_bypass', '__next_preview_data'], - query: [], + query: ['__nextDataReq'], country: [], } From 51e5373e4914e9b76edf439e8de01c561742bdaa Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 9 Dec 2024 15:35:43 +0100 Subject: [PATCH 10/11] feat: support after() (#2717) * test: upgrade deno used in tests * test: e2e simple next/after test * feat: support waitUntil * use already existing trackBackgroundWork helper from request context to handle next/after * test: increase timeout for one of smoke tests due to team-wide extensions installation time making it timeout * test: move test to dedicated fixture * chore: update outdated comment * chore: clarify awaiting backgroundWorkPromise * test: update assertion for next >=15.0.4-canary.18 * fix: support changed shape of getRequestHandlers return --------- Co-authored-by: Matt Kane --- .github/workflows/pre-release.yml | 4 +- .github/workflows/release-please.yml | 4 +- .github/workflows/run-tests.yml | 10 ++--- .github/workflows/size-check.yml | 4 +- .github/workflows/test-e2e.yml | 4 +- src/build/templates/handler-monorepo.tmpl.js | 2 +- src/build/templates/handler.tmpl.js | 2 +- src/run/handlers/request-context.cts | 22 ++++++++-- src/run/handlers/server.ts | 35 +++++++--------- src/run/handlers/wait-until.cts | 26 ++++++++++++ src/run/next.cts | 7 +++- tests/e2e/after.test.ts | 42 +++++++++++++++++++ tests/fixtures/after/app/after/check/page.js | 10 +++++ .../fixtures/after/app/after/trigger/page.js | 17 ++++++++ tests/fixtures/after/app/layout.js | 12 ++++++ tests/fixtures/after/next.config.js | 12 ++++++ tests/fixtures/after/package.json | 20 +++++++++ tests/integration/simple-app.test.ts | 8 +++- tests/smoke/deploy.test.ts | 10 +++-- tests/utils/create-e2e-fixture.ts | 1 + 20 files changed, 208 insertions(+), 44 deletions(-) create mode 100644 src/run/handlers/wait-until.cts create mode 100644 tests/e2e/after.test.ts create mode 100644 tests/fixtures/after/app/after/check/page.js create mode 100644 tests/fixtures/after/app/after/trigger/page.js create mode 100644 tests/fixtures/after/app/layout.js create mode 100644 tests/fixtures/after/next.config.js create mode 100644 tests/fixtures/after/package.json diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 3791eb4603..05d79a9e81 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -23,8 +23,8 @@ jobs: - name: Install Deno uses: denoland/setup-deno@v1 with: - # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/e55f825bd985d3c92e21d1b765d71e70d5628fba/node/bridge.ts#L17 - deno-version: v1.37.0 + # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20 + deno-version: v1.44.4 - name: Extract tag and version id: extract run: |- diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index f1c8a204e3..a63fd1aa46 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -31,8 +31,8 @@ jobs: - name: Install Deno uses: denoland/setup-deno@v1 with: - # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/e55f825bd985d3c92e21d1b765d71e70d5628fba/node/bridge.ts#L17 - deno-version: v1.37.0 + # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20 + deno-version: v1.44.4 - name: Build run: npm run build if: ${{ steps.release.outputs.release_created }} diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 3ff2d565bc..07f3eb0693 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -65,8 +65,8 @@ jobs: - name: Install Deno uses: denoland/setup-deno@v1 with: - # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/main/node/bridge.ts#L17 - deno-version: v1.37.0 + # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20 + deno-version: v1.44.4 - name: 'Install dependencies' run: npm ci - name: 'Prepare Netlify CLI' @@ -134,7 +134,7 @@ jobs: uses: denoland/setup-deno@v1 with: # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/e55f825bd985d3c92e21d1b765d71e70d5628fba/node/bridge.ts#L17 - deno-version: v1.37.0 + deno-version: v1.44.4 - name: 'Install dependencies' run: npm ci - name: 'Build' @@ -198,8 +198,8 @@ jobs: - name: Install Deno uses: denoland/setup-deno@v1 with: - # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/main/node/bridge.ts#L17 - deno-version: v1.37.0 + # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20 + deno-version: v1.44.4 - name: 'Install dependencies' run: npm ci - name: 'Build' diff --git a/.github/workflows/size-check.yml b/.github/workflows/size-check.yml index 69d3b66f56..60b7e2d765 100644 --- a/.github/workflows/size-check.yml +++ b/.github/workflows/size-check.yml @@ -23,8 +23,8 @@ jobs: - name: Install Deno uses: denoland/setup-deno@v1 with: - # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/main/node/bridge.ts#L17 - deno-version: v1.37.0 + # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20 + deno-version: v1.44.4 - run: npm ci - name: Package size report diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index ddd2f6a534..0a6ff91dac 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -161,8 +161,8 @@ jobs: - name: Install Deno uses: denoland/setup-deno@v1 with: - # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/main/node/bridge.ts#L17 - deno-version: v1.37.0 + # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20 + deno-version: v1.44.4 - name: install runtime run: npm install --ignore-scripts diff --git a/src/build/templates/handler-monorepo.tmpl.js b/src/build/templates/handler-monorepo.tmpl.js index 6e9b9a9a56..89f4411d5e 100644 --- a/src/build/templates/handler-monorepo.tmpl.js +++ b/src/build/templates/handler-monorepo.tmpl.js @@ -16,7 +16,7 @@ export default async function (req, context) { tracing.start() } - const requestContext = createRequestContext(req) + const requestContext = createRequestContext(req, context) const tracer = getTracer() const handlerResponse = await runWithRequestContext(requestContext, () => { diff --git a/src/build/templates/handler.tmpl.js b/src/build/templates/handler.tmpl.js index 0b10bcd902..c86fe13131 100644 --- a/src/build/templates/handler.tmpl.js +++ b/src/build/templates/handler.tmpl.js @@ -13,7 +13,7 @@ export default async function handler(req, context) { if (process.env.NETLIFY_OTLP_TRACE_EXPORTER_URL) { tracing.start() } - const requestContext = createRequestContext(req) + const requestContext = createRequestContext(req, context) const tracer = getTracer() const handlerResponse = await runWithRequestContext(requestContext, () => { diff --git a/src/run/handlers/request-context.cts b/src/run/handlers/request-context.cts index b71969327e..cc67739242 100644 --- a/src/run/handlers/request-context.cts +++ b/src/run/handlers/request-context.cts @@ -1,11 +1,17 @@ import { AsyncLocalStorage } from 'node:async_hooks' +import type { Context } from '@netlify/functions' import { LogLevel, systemLogger } from '@netlify/functions/internal' import type { NetlifyCachedRouteValue } from '../../shared/cache-types.cjs' type SystemLogger = typeof systemLogger +// TODO: remove once public types are updated +export interface FutureContext extends Context { + waitUntil?: (promise: Promise) => void +} + export type RequestContext = { captureServerTiming: boolean responseCacheGetLastModified?: number @@ -16,11 +22,15 @@ export type RequestContext = { serverTiming?: string routeHandlerRevalidate?: NetlifyCachedRouteValue['revalidate'] /** - * Track promise running in the background and need to be waited for + * Track promise running in the background and need to be waited for. + * Uses `context.waitUntil` if available, otherwise stores promises to + * await on. */ trackBackgroundWork: (promise: Promise) => void /** - * Promise that need to be executed even if response was already sent + * Promise that need to be executed even if response was already sent. + * If `context.waitUntil` is available this promise will be always resolved + * because background work tracking was offloaded to `context.waitUntil`. */ backgroundWorkPromise: Promise logger: SystemLogger @@ -28,13 +38,17 @@ export type RequestContext = { type RequestContextAsyncLocalStorage = AsyncLocalStorage -export function createRequestContext(request?: Request): RequestContext { +export function createRequestContext(request?: Request, context?: FutureContext): RequestContext { const backgroundWorkPromises: Promise[] = [] return { captureServerTiming: request?.headers.has('x-next-debug-logging') ?? false, trackBackgroundWork: (promise) => { - backgroundWorkPromises.push(promise) + if (context?.waitUntil) { + context.waitUntil(promise) + } else { + backgroundWorkPromises.push(promise) + } }, get backgroundWorkPromise() { return Promise.allSettled(backgroundWorkPromises) diff --git a/src/run/handlers/server.ts b/src/run/handlers/server.ts index cf0e8c63dd..436411c812 100644 --- a/src/run/handlers/server.ts +++ b/src/run/handlers/server.ts @@ -1,7 +1,6 @@ import type { OutgoingHttpHeaders } from 'http' import { ComputeJsOutgoingMessage, toComputeResponse, toReqRes } from '@fastly/http-compute-js' -import { Context } from '@netlify/functions' import type { NextConfigComplete } from 'next/dist/server/config-shared.js' import type { WorkerRequestHandler } from 'next/dist/server/lib/types.js' @@ -16,9 +15,12 @@ import { nextResponseProxy } from '../revalidate.js' import { createRequestContext, getLogger, getRequestContext } from './request-context.cjs' import { getTracer } from './tracer.cjs' +import { setupWaitUntil } from './wait-until.cjs' const nextImportPromise = import('../next.cjs') +setupWaitUntil() + let nextHandler: WorkerRequestHandler, nextConfig: NextConfigComplete /** @@ -44,13 +46,7 @@ const disableFaultyTransferEncodingHandling = (res: ComputeJsOutgoingMessage) => } } -// TODO: remove once https://github.com/netlify/serverless-functions-api/pull/219 -// is released and public types are updated -interface FutureContext extends Context { - waitUntil?: (promise: Promise) => void -} - -export default async (request: Request, context: FutureContext) => { +export default async (request: Request) => { const tracer = getTracer() if (!nextHandler) { @@ -60,10 +56,10 @@ export default async (request: Request, context: FutureContext) => { nextConfig = await getRunConfig() setRunConfig(nextConfig) - const { getMockedRequestHandlers } = await nextImportPromise + const { getMockedRequestHandler } = await nextImportPromise const url = new URL(request.url) - ;[nextHandler] = await getMockedRequestHandlers({ + nextHandler = await getMockedRequestHandler({ port: Number(url.port) || 443, hostname: url.hostname, dir: process.cwd(), @@ -128,19 +124,20 @@ export default async (request: Request, context: FutureContext) => { return new Response(body || null, response) } - if (context.waitUntil) { - context.waitUntil(requestContext.backgroundWorkPromise) - } - const keepOpenUntilNextFullyRendered = new TransformStream({ async flush() { // it's important to keep the stream open until the next handler has finished await nextHandlerPromise - if (!context.waitUntil) { - // if waitUntil is not available, we have to keep response stream open until background promises are resolved - // to ensure that all background work executes - await requestContext.backgroundWorkPromise - } + + // Next.js relies on `close` event emitted by response to trigger running callback variant of `next/after` + // however @fastly/http-compute-js never actually emits that event - so we have to emit it ourselves, + // otherwise Next would never run the callback variant of `next/after` + res.emit('close') + + // We have to keep response stream open until tracked background promises that are don't use `context.waitUntil` + // are resolved. If `context.waitUntil` is available, `requestContext.backgroundWorkPromise` will be empty + // resolved promised and so awaiting it is no-op + await requestContext.backgroundWorkPromise }, }) diff --git a/src/run/handlers/wait-until.cts b/src/run/handlers/wait-until.cts new file mode 100644 index 0000000000..683196a47f --- /dev/null +++ b/src/run/handlers/wait-until.cts @@ -0,0 +1,26 @@ +import { getRequestContext } from './request-context.cjs' + +/** + * @see https://github.com/vercel/next.js/blob/canary/packages/next/src/server/after/builtin-request-context.ts + */ +const NEXT_REQUEST_CONTEXT_SYMBOL = Symbol.for('@next/request-context') + +export type NextJsRequestContext = { + get(): { waitUntil?: (promise: Promise) => void } | undefined +} + +type GlobalThisWithRequestContext = typeof globalThis & { + [NEXT_REQUEST_CONTEXT_SYMBOL]?: NextJsRequestContext +} + +/** + * Registers a `waitUntil` to be used by Next.js for next/after + */ +export function setupWaitUntil() { + // eslint-disable-next-line @typescript-eslint/no-extra-semi + ;(globalThis as GlobalThisWithRequestContext)[NEXT_REQUEST_CONTEXT_SYMBOL] = { + get() { + return { waitUntil: getRequestContext()?.trackBackgroundWork } + }, + } +} diff --git a/src/run/next.cts b/src/run/next.cts index 84f378ddb6..1712e991e2 100644 --- a/src/run/next.cts +++ b/src/run/next.cts @@ -85,7 +85,7 @@ export type HtmlBlob = { isFallback: boolean } -export async function getMockedRequestHandlers(...args: Parameters) { +export async function getMockedRequestHandler(...args: Parameters) { const tracer = getTracer() return tracer.withActiveSpan('mocked request handler', async () => { const ofs = { ...fs } @@ -131,6 +131,9 @@ export async function getMockedRequestHandlers(...args: Parameters { + test.skip(!nextVersionSatisfies('>=15.0.0'), 'This test is only for Next.js 15+') + + // trigger initial request to check page which might be stale and allow regenerating in background + await page.goto(`${after.url}/after/check`) + + await new Promise((resolve) => setTimeout(resolve, 5000)) + + // after it was possibly regenerated we can start checking actual content of the page + await page.goto(`${after.url}/after/check`) + const pageInfoLocator1 = await page.locator('#page-info') + const pageInfo1 = JSON.parse((await pageInfoLocator1.textContent()) ?? '{}') + + expect(typeof pageInfo1?.timestamp, 'Check page should have timestamp').toBe('number') + + await page.goto(`${after.url}/after/check`) + const pageInfoLocator2 = await page.locator('#page-info') + const pageInfo2 = JSON.parse((await pageInfoLocator2.textContent()) ?? '{}') + + expect(typeof pageInfo2?.timestamp, 'Check page should have timestamp').toBe('number') + + expect(pageInfo2.timestamp, 'Check page should be cached').toBe(pageInfo1.timestamp) + + await page.goto(`${after.url}/after/trigger`) + + // wait for next/after to trigger revalidation of check page + await new Promise((resolve) => setTimeout(resolve, 5000)) + + await page.goto(`${after.url}/after/check`) + const pageInfoLocator3 = await page.locator('#page-info') + const pageInfo3 = JSON.parse((await pageInfoLocator3.textContent()) ?? '{}') + + expect(typeof pageInfo3?.timestamp, 'Check page should have timestamp').toBe('number') + expect( + pageInfo3.timestamp, + 'Check page should be invalidated with newer timestamp', + ).toBeGreaterThan(pageInfo1.timestamp) +}) diff --git a/tests/fixtures/after/app/after/check/page.js b/tests/fixtures/after/app/after/check/page.js new file mode 100644 index 0000000000..da1103e97f --- /dev/null +++ b/tests/fixtures/after/app/after/check/page.js @@ -0,0 +1,10 @@ +export const revalidate = 3600 // arbitrarily long, just so that it doesn't happen during a test run + +export default async function Page() { + const data = { + timestamp: Date.now(), + } + console.log('/timestamp/key/[key] rendered', data) + + return
{JSON.stringify(data)}
+} diff --git a/tests/fixtures/after/app/after/trigger/page.js b/tests/fixtures/after/app/after/trigger/page.js new file mode 100644 index 0000000000..62f02d8148 --- /dev/null +++ b/tests/fixtures/after/app/after/trigger/page.js @@ -0,0 +1,17 @@ +import { revalidatePath } from 'next/cache' +import { unstable_after as after, connection } from 'next/server' + +export default async function Page() { + await connection() + after(async () => { + // this will run after response was sent + console.log('after() triggered') + console.log('after() sleep 1s') + await new Promise((resolve) => setTimeout(resolve, 1000)) + + console.log('after() revalidatePath /after/check') + revalidatePath('/after/check') + }) + + return
Page with after()
+} diff --git a/tests/fixtures/after/app/layout.js b/tests/fixtures/after/app/layout.js new file mode 100644 index 0000000000..6565e7bafd --- /dev/null +++ b/tests/fixtures/after/app/layout.js @@ -0,0 +1,12 @@ +export const metadata = { + title: 'Simple Next App', + description: 'Description for Simple Next App', +} + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} diff --git a/tests/fixtures/after/next.config.js b/tests/fixtures/after/next.config.js new file mode 100644 index 0000000000..d3a1abc892 --- /dev/null +++ b/tests/fixtures/after/next.config.js @@ -0,0 +1,12 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: 'standalone', + eslint: { + ignoreDuringBuilds: true, + }, + experimental: { + after: true, + }, +} + +module.exports = nextConfig diff --git a/tests/fixtures/after/package.json b/tests/fixtures/after/package.json new file mode 100644 index 0000000000..ebd15f679b --- /dev/null +++ b/tests/fixtures/after/package.json @@ -0,0 +1,20 @@ +{ + "name": "after", + "version": "0.1.0", + "private": true, + "scripts": { + "postinstall": "next build", + "dev": "next dev", + "build": "next build" + }, + "dependencies": { + "next": "latest", + "react": "18.2.0", + "react-dom": "18.2.0" + }, + "test": { + "dependencies": { + "next": ">=15.0.0" + } + } +} diff --git a/tests/integration/simple-app.test.ts b/tests/integration/simple-app.test.ts index 54044ada34..c082a8c480 100644 --- a/tests/integration/simple-app.test.ts +++ b/tests/integration/simple-app.test.ts @@ -83,7 +83,13 @@ test('Test that the simple next app is working', async (ctx) const notFound = await invokeFunction(ctx, { url: 'route-resolves-to-not-found' }) expect(notFound.statusCode).toBe(404) - expect(notFound.body).toContain('NEXT_NOT_FOUND') + // depending on Next version code found in 404 page can be either NEXT_NOT_FOUND or NEXT_HTTP_ERROR_FALLBACK + // see https://github.com/vercel/next.js/commit/997105d27ebc7bfe01b7e907cd659e5e056e637c that moved from NEXT_NOT_FOUND to NEXT_HTTP_ERROR_FALLBACK + expect( + notFound.body?.includes('NEXT_NOT_FOUND') || + notFound.body?.includes('NEXT_HTTP_ERROR_FALLBACK'), + '404 page should contain NEXT_NOT_FOUND or NEXT_HTTP_ERROR_FALLBACK code', + ).toBe(true) const notExisting = await invokeFunction(ctx, { url: 'non-exisitng' }) expect(notExisting.statusCode).toBe(404) diff --git a/tests/smoke/deploy.test.ts b/tests/smoke/deploy.test.ts index c60a37b808..de97122d9f 100644 --- a/tests/smoke/deploy.test.ts +++ b/tests/smoke/deploy.test.ts @@ -71,9 +71,13 @@ describe('version check', () => { ) }, ) - test('yarn monorepo multiple next versions site is compatible', { retry: 0 }, async () => { - await smokeTest(selfCleaningFixtureFactories.yarnMonorepoMultipleNextVersionsSiteCompatible) - }) + test( + 'yarn monorepo multiple next versions site is compatible', + { retry: 0, timeout: 1_000 * 60 * 5 }, + async () => { + await smokeTest(selfCleaningFixtureFactories.yarnMonorepoMultipleNextVersionsSiteCompatible) + }, + ) test( 'yarn monorepo multiple next versions site is incompatible should not deploy', diff --git a/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index c8697d47ff..097bd4ac0d 100644 --- a/tests/utils/create-e2e-fixture.ts +++ b/tests/utils/create-e2e-fixture.ts @@ -428,4 +428,5 @@ export const fixtureFactories = { publishDirectory: 'apps/site/.next', smoke: true, }), + after: () => createE2EFixture('after'), } From 0a6cc37a9a041ecf7264b62c06735479dc9780f1 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 9 Dec 2024 18:24:51 +0100 Subject: [PATCH 11/11] chore(main): release 5.9.0 (#2720) --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 51cd60763c..09242e06fa 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "5.8.1" + ".": "5.9.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index f399150fde..f13f5520cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [5.9.0](https://github.com/opennextjs/opennextjs-netlify/compare/v5.8.1...v5.9.0) (2024-12-09) + + +### Features + +* support after() ([#2717](https://github.com/opennextjs/opennextjs-netlify/issues/2717)) ([51e5373](https://github.com/opennextjs/opennextjs-netlify/commit/51e5373e4914e9b76edf439e8de01c561742bdaa)) + + +### Bug Fixes + +* add data request query param to the cache key ([#2701](https://github.com/opennextjs/opennextjs-netlify/issues/2701)) ([00e3a4b](https://github.com/opennextjs/opennextjs-netlify/commit/00e3a4be19bf8428ea6ce1ed2ae74a8ac8375532)) + ## [5.8.1](https://github.com/netlify/next-runtime/compare/v5.8.0...v5.8.1) (2024-10-21) diff --git a/package-lock.json b/package-lock.json index 410fb9d3b5..e048e153ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@netlify/plugin-nextjs", - "version": "5.8.1", + "version": "5.9.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@netlify/plugin-nextjs", - "version": "5.8.1", + "version": "5.9.0", "license": "MIT", "devDependencies": { "@fastly/http-compute-js": "1.1.4", diff --git a/package.json b/package.json index f490872995..62c2d8c065 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@netlify/plugin-nextjs", - "version": "5.8.1", + "version": "5.9.0", "description": "Run Next.js seamlessly on Netlify", "main": "./dist/index.js", "type": "module",