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 74ec22b895..b3c22c4402 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' @@ -30,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 @@ -42,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/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 8a54eb5f74..07f3eb0693 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 @@ -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 90d9bf05f4..0a6ff91dac 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 }} @@ -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: @@ -56,14 +56,14 @@ 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\",\"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 @@ -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- @@ -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 @@ -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/.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/CONTRIBUTING.md b/CONTRIBUTING.md index 3a3fceee08..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 @@ -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 @@ -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/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" diff --git a/package-lock.json b/package-lock.json index 91599cc6d3..e048e153ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "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", "@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..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", @@ -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,13 +44,13 @@ ], "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", - "@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", diff --git a/run-local-test.sh b/run-local-test.sh index 4cfa139e8c..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 ../next-runtime/tests/e2e-utils.patch || git apply ../next-runtime/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/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/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: [], } 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/netlify-deploy.ts b/tests/netlify-deploy.ts index 7a33f26dab..13664acebe 100644 --- a/tests/netlify-deploy.ts +++ b/tests/netlify-deploy.ts @@ -6,22 +6,13 @@ 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(), '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) @@ -44,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 @@ -59,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 }) @@ -133,7 +128,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 +137,30 @@ 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._shouldDeleteDeploy = !process.env.NEXT_TEST_SKIP_CLEANUP + 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}`) @@ -166,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() { 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/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/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index 7d728acef7..097bd4ac0d 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) => { @@ -428,4 +428,5 @@ export const fixtureFactories = { publishDirectory: 'apps/site/.next', smoke: true, }), + after: () => createE2EFixture('after'), } 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/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 } /** 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 } } }) 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'],