diff --git a/e2e-report/package-lock.json b/e2e-report/package-lock.json index 41b425b9d3..2857343ee3 100644 --- a/e2e-report/package-lock.json +++ b/e2e-report/package-lock.json @@ -464,6 +464,292 @@ "node": ">= 8" } }, + "node_modules/@parcel/watcher": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", + "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.4.1", + "@parcel/watcher-darwin-arm64": "2.4.1", + "@parcel/watcher-darwin-x64": "2.4.1", + "@parcel/watcher-freebsd-x64": "2.4.1", + "@parcel/watcher-linux-arm-glibc": "2.4.1", + "@parcel/watcher-linux-arm64-glibc": "2.4.1", + "@parcel/watcher-linux-arm64-musl": "2.4.1", + "@parcel/watcher-linux-x64-glibc": "2.4.1", + "@parcel/watcher-linux-x64-musl": "2.4.1", + "@parcel/watcher-win32-arm64": "2.4.1", + "@parcel/watcher-win32-ia32": "2.4.1", + "@parcel/watcher-win32-x64": "2.4.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz", + "integrity": "sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz", + "integrity": "sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz", + "integrity": "sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz", + "integrity": "sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz", + "integrity": "sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz", + "integrity": "sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz", + "integrity": "sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz", + "integrity": "sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz", + "integrity": "sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz", + "integrity": "sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz", + "integrity": "sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz", + "integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1042,7 +1328,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, + "devOptional": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -1437,6 +1723,19 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2175,7 +2474,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, + "devOptional": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2774,7 +3073,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -2819,7 +3118,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -2855,7 +3154,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.12.0" } @@ -3243,7 +3542,7 @@ "version": "4.0.7", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", - "dev": true, + "devOptional": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -3400,6 +3699,13 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -3695,7 +4001,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8.6" }, @@ -4165,12 +4471,13 @@ } }, "node_modules/sass": { - "version": "1.79.4", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.4.tgz", - "integrity": "sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==", + "version": "1.79.5", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.5.tgz", + "integrity": "sha512-W1h5kp6bdhqFh2tk3DsI771MoEJjvrSY/2ihJRJS4pjIyfJCw0nTsxqhnrUzaLMOJjFchj8rOvraI/YUVjtx5g==", "devOptional": true, "license": "MIT", "dependencies": { + "@parcel/watcher": "^2.4.1", "chokidar": "^4.0.0", "immutable": "^4.0.0", "source-map-js": ">=0.6.2 <2.0.0" @@ -4673,7 +4980,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "dependencies": { "is-number": "^7.0.0" }, diff --git a/package-lock.json b/package-lock.json index bedbdcbbfe..b2d800698e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,13 +11,13 @@ "devDependencies": { "@fastly/http-compute-js": "1.1.4", "@netlify/blobs": "^8.0.1", - "@netlify/build": "^29.55.1", + "@netlify/build": "^29.55.2", "@netlify/edge-bundler": "^12.2.3", "@netlify/edge-functions": "^2.11.0", "@netlify/eslint-config-node": "^7.0.1", "@netlify/functions": "^2.8.2", - "@netlify/serverless-functions-api": "^1.30.0", - "@netlify/zip-it-and-ship-it": "^9.40.0", + "@netlify/serverless-functions-api": "^1.30.1", + "@netlify/zip-it-and-ship-it": "^9.40.2", "@opentelemetry/api": "^1.8.0", "@opentelemetry/exporter-trace-otlp-http": "^0.51.0", "@opentelemetry/resources": "^1.24.0", @@ -3890,9 +3890,9 @@ } }, "node_modules/@netlify/build": { - "version": "29.55.1", - "resolved": "https://registry.npmjs.org/@netlify/build/-/build-29.55.1.tgz", - "integrity": "sha512-k/yVKIH3A7oUCetf6Jl6RvVsI0F14TmUsV3LGxdp1eYRofHIJIgb0vSeTzFCfhVUNl6HIlvVO/06iT4nuXEFDw==", + "version": "29.55.2", + "resolved": "https://registry.npmjs.org/@netlify/build/-/build-29.55.2.tgz", + "integrity": "sha512-sXWDIeKtRWc6S9+dG3lCdTSNSB9XfhNFk80kG600sI3ytkhF5rPk6ijJw4Y7drhruCrYFpsDwlPxGY9kBjshBw==", "dev": true, "dependencies": { "@bugsnag/js": "^7.0.0", @@ -3901,12 +3901,12 @@ "@netlify/config": "^20.19.0", "@netlify/edge-bundler": "12.2.3", "@netlify/framework-info": "^9.8.13", - "@netlify/functions-utils": "^5.2.90", + "@netlify/functions-utils": "^5.2.91", "@netlify/git-utils": "^5.1.1", "@netlify/opentelemetry-utils": "^1.2.1", "@netlify/plugins-list": "^6.80.0", "@netlify/run-utils": "^5.1.1", - "@netlify/zip-it-and-ship-it": "9.40.1", + "@netlify/zip-it-and-ship-it": "9.40.2", "@sindresorhus/slugify": "^2.0.0", "ansi-escapes": "^6.0.0", "chalk": "^5.0.0", @@ -4792,12 +4792,12 @@ } }, "node_modules/@netlify/functions-utils": { - "version": "5.2.90", - "resolved": "https://registry.npmjs.org/@netlify/functions-utils/-/functions-utils-5.2.90.tgz", - "integrity": "sha512-zd82WW3oGyWkGWAuIRk+hvWEg/soFoxqJesSMIkimeZ+jNyx3hKHb8QHtw/riXXlkhi3AW+jhNn4e+qB/f4tbQ==", + "version": "5.2.91", + "resolved": "https://registry.npmjs.org/@netlify/functions-utils/-/functions-utils-5.2.91.tgz", + "integrity": "sha512-PezRTuKzKzIbWu71tykioHS7W2Tk4wiGZqkkYzm92FeLRlubRERC9Dwv3Y5LFFNQsWXs701l7CVTojHrpSiA0w==", "dev": true, "dependencies": { - "@netlify/zip-it-and-ship-it": "9.40.1", + "@netlify/zip-it-and-ship-it": "9.40.2", "cpy": "^9.0.0", "path-exists": "^5.0.0" }, @@ -5029,9 +5029,9 @@ "dev": true }, "node_modules/@netlify/serverless-functions-api": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.30.0.tgz", - "integrity": "sha512-flmEwxe7JFqWDXmD2hyzPbmKzKZF0y2rH8IHGlD1mn9bXo9JBPm61IVLJx/A+Je4pbjgoIgYZeeZKCxnCRooHQ==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.30.1.tgz", + "integrity": "sha512-JkbaWFeydQdeDHz1mAy4rw+E3bl9YtbCgkntfTxq+IlNX/aIMv2/b1kZnQZcil4/sPoZGL831Dq6E374qRpU1A==", "dev": true, "dependencies": { "@netlify/node-cookies": "^0.1.0", @@ -5042,15 +5042,15 @@ } }, "node_modules/@netlify/zip-it-and-ship-it": { - "version": "9.40.1", - "resolved": "https://registry.npmjs.org/@netlify/zip-it-and-ship-it/-/zip-it-and-ship-it-9.40.1.tgz", - "integrity": "sha512-0TMFqfjtscAzmdjE9HYqAK8/l6Lo76BxAXc8ioIigHx6V4UGmgeIXxoXKReqfosCBvmSmUJGdFATwaNRzuGxtA==", + "version": "9.40.2", + "resolved": "https://registry.npmjs.org/@netlify/zip-it-and-ship-it/-/zip-it-and-ship-it-9.40.2.tgz", + "integrity": "sha512-CkAwLnqFqhV9hNJO8HxMjd+g2HYtGevjo4gP1P84Sf50HBFyBE2cavfXNaBa1TfNq5/92CixnXz4YptU1DIOOw==", "dev": true, "dependencies": { "@babel/parser": "^7.22.5", "@babel/types": "7.25.6", "@netlify/binary-info": "^1.0.0", - "@netlify/serverless-functions-api": "^1.30.0", + "@netlify/serverless-functions-api": "^1.30.1", "@vercel/nft": "^0.27.1", "archiver": "^7.0.0", "common-path-prefix": "^3.0.0", @@ -6587,12 +6587,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.47.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz", - "integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.0.tgz", + "integrity": "sha512-W5lhqPUVPqhtc/ySvZI5Q8X2ztBOUgZ8LbAFy0JQgrXZs2xaILrUcNO3rQjwbLPfGK13+rZsDa1FpG+tqYkT5w==", "dev": true, "dependencies": { - "playwright": "1.47.2" + "playwright": "1.48.0" }, "bin": { "playwright": "cli.js" @@ -7071,9 +7071,9 @@ } }, "node_modules/@types/node": { - "version": "20.16.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz", - "integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==", + "version": "20.16.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz", + "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", "dev": true, "dependencies": { "undici-types": "~6.19.2" @@ -16376,12 +16376,12 @@ } }, "node_modules/playwright": { - "version": "1.47.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz", - "integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.0.tgz", + "integrity": "sha512-qPqFaMEHuY/ug8o0uteYJSRfMGFikhUysk8ZvAtfKmUK3kc/6oNl/y3EczF8OFGYIi/Ex2HspMfzYArk6+XQSA==", "dev": true, "dependencies": { - "playwright-core": "1.47.2" + "playwright-core": "1.48.0" }, "bin": { "playwright": "cli.js" @@ -16394,9 +16394,9 @@ } }, "node_modules/playwright-core": { - "version": "1.47.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz", - "integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.0.tgz", + "integrity": "sha512-RBvzjM9rdpP7UUFrQzRwR8L/xR4HyC1QXMzGYTbf1vjw25/ya9NRAVnXi/0fvFopjebvyPzsmoK58xxeEOaVvA==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -18758,9 +18758,9 @@ } }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -22800,9 +22800,9 @@ "dev": true }, "@netlify/build": { - "version": "29.55.1", - "resolved": "https://registry.npmjs.org/@netlify/build/-/build-29.55.1.tgz", - "integrity": "sha512-k/yVKIH3A7oUCetf6Jl6RvVsI0F14TmUsV3LGxdp1eYRofHIJIgb0vSeTzFCfhVUNl6HIlvVO/06iT4nuXEFDw==", + "version": "29.55.2", + "resolved": "https://registry.npmjs.org/@netlify/build/-/build-29.55.2.tgz", + "integrity": "sha512-sXWDIeKtRWc6S9+dG3lCdTSNSB9XfhNFk80kG600sI3ytkhF5rPk6ijJw4Y7drhruCrYFpsDwlPxGY9kBjshBw==", "dev": true, "requires": { "@bugsnag/js": "^7.0.0", @@ -22811,12 +22811,12 @@ "@netlify/config": "^20.19.0", "@netlify/edge-bundler": "12.2.3", "@netlify/framework-info": "^9.8.13", - "@netlify/functions-utils": "^5.2.90", + "@netlify/functions-utils": "^5.2.91", "@netlify/git-utils": "^5.1.1", "@netlify/opentelemetry-utils": "^1.2.1", "@netlify/plugins-list": "^6.80.0", "@netlify/run-utils": "^5.1.1", - "@netlify/zip-it-and-ship-it": "9.40.1", + "@netlify/zip-it-and-ship-it": "9.40.2", "@sindresorhus/slugify": "^2.0.0", "ansi-escapes": "^6.0.0", "chalk": "^5.0.0", @@ -23388,12 +23388,12 @@ } }, "@netlify/functions-utils": { - "version": "5.2.90", - "resolved": "https://registry.npmjs.org/@netlify/functions-utils/-/functions-utils-5.2.90.tgz", - "integrity": "sha512-zd82WW3oGyWkGWAuIRk+hvWEg/soFoxqJesSMIkimeZ+jNyx3hKHb8QHtw/riXXlkhi3AW+jhNn4e+qB/f4tbQ==", + "version": "5.2.91", + "resolved": "https://registry.npmjs.org/@netlify/functions-utils/-/functions-utils-5.2.91.tgz", + "integrity": "sha512-PezRTuKzKzIbWu71tykioHS7W2Tk4wiGZqkkYzm92FeLRlubRERC9Dwv3Y5LFFNQsWXs701l7CVTojHrpSiA0w==", "dev": true, "requires": { - "@netlify/zip-it-and-ship-it": "9.40.1", + "@netlify/zip-it-and-ship-it": "9.40.2", "cpy": "^9.0.0", "path-exists": "^5.0.0" } @@ -23556,9 +23556,9 @@ } }, "@netlify/serverless-functions-api": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.30.0.tgz", - "integrity": "sha512-flmEwxe7JFqWDXmD2hyzPbmKzKZF0y2rH8IHGlD1mn9bXo9JBPm61IVLJx/A+Je4pbjgoIgYZeeZKCxnCRooHQ==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.30.1.tgz", + "integrity": "sha512-JkbaWFeydQdeDHz1mAy4rw+E3bl9YtbCgkntfTxq+IlNX/aIMv2/b1kZnQZcil4/sPoZGL831Dq6E374qRpU1A==", "dev": true, "requires": { "@netlify/node-cookies": "^0.1.0", @@ -23566,15 +23566,15 @@ } }, "@netlify/zip-it-and-ship-it": { - "version": "9.40.1", - "resolved": "https://registry.npmjs.org/@netlify/zip-it-and-ship-it/-/zip-it-and-ship-it-9.40.1.tgz", - "integrity": "sha512-0TMFqfjtscAzmdjE9HYqAK8/l6Lo76BxAXc8ioIigHx6V4UGmgeIXxoXKReqfosCBvmSmUJGdFATwaNRzuGxtA==", + "version": "9.40.2", + "resolved": "https://registry.npmjs.org/@netlify/zip-it-and-ship-it/-/zip-it-and-ship-it-9.40.2.tgz", + "integrity": "sha512-CkAwLnqFqhV9hNJO8HxMjd+g2HYtGevjo4gP1P84Sf50HBFyBE2cavfXNaBa1TfNq5/92CixnXz4YptU1DIOOw==", "dev": true, "requires": { "@babel/parser": "^7.22.5", "@babel/types": "7.25.6", "@netlify/binary-info": "^1.0.0", - "@netlify/serverless-functions-api": "^1.30.0", + "@netlify/serverless-functions-api": "^1.30.1", "@vercel/nft": "^0.27.1", "archiver": "^7.0.0", "common-path-prefix": "^3.0.0", @@ -24652,12 +24652,12 @@ "optional": true }, "@playwright/test": { - "version": "1.47.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz", - "integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.0.tgz", + "integrity": "sha512-W5lhqPUVPqhtc/ySvZI5Q8X2ztBOUgZ8LbAFy0JQgrXZs2xaILrUcNO3rQjwbLPfGK13+rZsDa1FpG+tqYkT5w==", "dev": true, "requires": { - "playwright": "1.47.2" + "playwright": "1.48.0" } }, "@protobufjs/aspromise": { @@ -25006,9 +25006,9 @@ } }, "@types/node": { - "version": "20.16.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz", - "integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==", + "version": "20.16.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.11.tgz", + "integrity": "sha512-y+cTCACu92FyA5fgQSAI8A1H429g7aSK2HsO7K4XYUWc4dY5IUz55JSDIYT6/VsOLfGy8vmvQYC2hfb0iF16Uw==", "dev": true, "requires": { "undici-types": "~6.19.2" @@ -31611,19 +31611,19 @@ } }, "playwright": { - "version": "1.47.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz", - "integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.0.tgz", + "integrity": "sha512-qPqFaMEHuY/ug8o0uteYJSRfMGFikhUysk8ZvAtfKmUK3kc/6oNl/y3EczF8OFGYIi/Ex2HspMfzYArk6+XQSA==", "dev": true, "requires": { "fsevents": "2.3.2", - "playwright-core": "1.47.2" + "playwright-core": "1.48.0" } }, "playwright-core": { - "version": "1.47.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz", - "integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.0.tgz", + "integrity": "sha512-RBvzjM9rdpP7UUFrQzRwR8L/xR4HyC1QXMzGYTbf1vjw25/ya9NRAVnXi/0fvFopjebvyPzsmoK58xxeEOaVvA==", "dev": true }, "pluralize": { @@ -33367,9 +33367,9 @@ } }, "typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true }, "ufo": { diff --git a/package.json b/package.json index a11fa5ad46..f2b2f89e1f 100644 --- a/package.json +++ b/package.json @@ -50,13 +50,13 @@ "devDependencies": { "@fastly/http-compute-js": "1.1.4", "@netlify/blobs": "^8.0.1", - "@netlify/build": "^29.55.1", + "@netlify/build": "^29.55.2", "@netlify/edge-bundler": "^12.2.3", "@netlify/edge-functions": "^2.11.0", "@netlify/eslint-config-node": "^7.0.1", "@netlify/functions": "^2.8.2", - "@netlify/serverless-functions-api": "^1.30.0", - "@netlify/zip-it-and-ship-it": "^9.40.0", + "@netlify/serverless-functions-api": "^1.30.1", + "@netlify/zip-it-and-ship-it": "^9.40.2", "@opentelemetry/api": "^1.8.0", "@opentelemetry/exporter-trace-otlp-http": "^0.51.0", "@opentelemetry/resources": "^1.24.0", diff --git a/src/run/handlers/server.ts b/src/run/handlers/server.ts index d7dd4c5c0a..cf0e8c63dd 100644 --- a/src/run/handlers/server.ts +++ b/src/run/handlers/server.ts @@ -112,7 +112,7 @@ export default async (request: Request, context: FutureContext) => { await adjustDateHeader({ headers: response.headers, request, span, tracer, requestContext }) - setCacheControlHeaders(response.headers, request, requestContext) + setCacheControlHeaders(response, request, requestContext) setCacheTagsHeaders(response.headers, requestContext) setVaryHeaders(response.headers, request, nextConfig) setCacheStatusHeader(response.headers) diff --git a/src/run/headers.test.ts b/src/run/headers.test.ts index 5afcd2e502..4ca423e402 100644 --- a/src/run/headers.test.ts +++ b/src/run/headers.test.ts @@ -199,43 +199,43 @@ describe('headers', () => { const givenHeaders = { 'cdn-cache-control': 'public, max-age=0, must-revalidate', } - const headers = new Headers(givenHeaders) const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response(null, { headers: givenHeaders }) + vi.spyOn(response.headers, 'set') const ctx: RequestContext = { ...createRequestContext(), routeHandlerRevalidate: false } - setCacheControlHeaders(headers, request, ctx) + setCacheControlHeaders(response, request, ctx) - expect(headers.set).toHaveBeenCalledTimes(0) + expect(response.headers.set).toHaveBeenCalledTimes(0) }) test('should not set any headers if "netlify-cdn-cache-control" is present', () => { const givenHeaders = { 'netlify-cdn-cache-control': 'public, max-age=0, must-revalidate', } - const headers = new Headers(givenHeaders) const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response(null, { headers: givenHeaders }) + vi.spyOn(response.headers, 'set') const ctx: RequestContext = { ...createRequestContext(), routeHandlerRevalidate: false } - setCacheControlHeaders(headers, request, ctx) + setCacheControlHeaders(response, request, ctx) - expect(headers.set).toHaveBeenCalledTimes(0) + expect(response.headers.set).toHaveBeenCalledTimes(0) }) test('should mark content as stale if "{netlify-,}cdn-cache-control" is not present and "x-nextjs-cache" is "STALE" (GET)', () => { const givenHeaders = { 'x-nextjs-cache': 'STALE', } - const headers = new Headers(givenHeaders) const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response(null, { headers: givenHeaders }) + vi.spyOn(response.headers, 'set') const ctx: RequestContext = { ...createRequestContext(), routeHandlerRevalidate: false } - setCacheControlHeaders(headers, request, ctx) + setCacheControlHeaders(response, request, ctx) - expect(headers.set).toHaveBeenCalledTimes(1) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenCalledTimes(1) + expect(response.headers.set).toHaveBeenNthCalledWith( 1, 'netlify-cdn-cache-control', 'public, max-age=0, must-revalidate, durable', @@ -246,15 +246,15 @@ describe('headers', () => { const givenHeaders = { 'x-nextjs-cache': 'STALE', } - const headers = new Headers(givenHeaders) const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response(null, { headers: givenHeaders }) + vi.spyOn(response.headers, 'set') const ctx: RequestContext = { ...createRequestContext(), routeHandlerRevalidate: false } - setCacheControlHeaders(headers, request, ctx) + setCacheControlHeaders(response, request, ctx) - expect(headers.set).toHaveBeenCalledTimes(1) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenCalledTimes(1) + expect(response.headers.set).toHaveBeenNthCalledWith( 1, 'netlify-cdn-cache-control', 'public, max-age=0, must-revalidate, durable', @@ -262,15 +262,15 @@ describe('headers', () => { }) test('should set durable SWC=1yr with 1yr TTL if "{netlify-,}cdn-cache-control" is not present and `revalidate` is `false` (HEAD)', () => { - const headers = new Headers() const request = new Request(defaultUrl, { method: 'HEAD' }) - vi.spyOn(headers, 'set') + const response = new Response() + vi.spyOn(response.headers, 'set') const ctx: RequestContext = { ...createRequestContext(), routeHandlerRevalidate: false } - setCacheControlHeaders(headers, request, ctx) + setCacheControlHeaders(response, request, ctx) - expect(headers.set).toHaveBeenCalledTimes(1) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenCalledTimes(1) + expect(response.headers.set).toHaveBeenNthCalledWith( 1, 'netlify-cdn-cache-control', 's-maxage=31536000, stale-while-revalidate=31536000, durable', @@ -278,15 +278,15 @@ describe('headers', () => { }) test('should set durable SWC=1yr with given TTL if "{netlify-,}cdn-cache-control" is not present and `revalidate` is a number (GET)', () => { - const headers = new Headers() const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response() + vi.spyOn(response.headers, 'set') const ctx: RequestContext = { ...createRequestContext(), routeHandlerRevalidate: 7200 } - setCacheControlHeaders(headers, request, ctx) + setCacheControlHeaders(response, request, ctx) - expect(headers.set).toHaveBeenCalledTimes(1) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenCalledTimes(1) + expect(response.headers.set).toHaveBeenNthCalledWith( 1, 'netlify-cdn-cache-control', 's-maxage=7200, stale-while-revalidate=31536000, durable', @@ -294,15 +294,15 @@ describe('headers', () => { }) test('should set durable SWC=1yr with 1yr TTL if "{netlify-,}cdn-cache-control" is not present and `revalidate` is a number (HEAD)', () => { - const headers = new Headers() const request = new Request(defaultUrl, { method: 'HEAD' }) - vi.spyOn(headers, 'set') + const response = new Response() + vi.spyOn(response.headers, 'set') const ctx: RequestContext = { ...createRequestContext(), routeHandlerRevalidate: 7200 } - setCacheControlHeaders(headers, request, ctx) + setCacheControlHeaders(response, request, ctx) - expect(headers.set).toHaveBeenCalledTimes(1) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenCalledTimes(1) + expect(response.headers.set).toHaveBeenNthCalledWith( 1, 'netlify-cdn-cache-control', 's-maxage=7200, stale-while-revalidate=31536000, durable', @@ -310,43 +310,65 @@ describe('headers', () => { }) test('should not set any headers on POST request', () => { - const headers = new Headers() const request = new Request(defaultUrl, { method: 'POST' }) - vi.spyOn(headers, 'set') + const response = new Response() + vi.spyOn(response.headers, 'set') const ctx: RequestContext = { ...createRequestContext(), routeHandlerRevalidate: false } - setCacheControlHeaders(headers, request, ctx) + setCacheControlHeaders(response, request, ctx) - expect(headers.set).toHaveBeenCalledTimes(0) + expect(response.headers.set).toHaveBeenCalledTimes(0) }) }) test('should not set any headers if "cache-control" is not set and "requestContext.usedFsReadForNonFallback" is not truthy', () => { - const headers = new Headers() const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response() + vi.spyOn(response.headers, 'set') - setCacheControlHeaders(headers, request, createRequestContext()) + setCacheControlHeaders(response, request, createRequestContext()) - expect(headers.set).toHaveBeenCalledTimes(0) + expect(response.headers.set).toHaveBeenCalledTimes(0) }) test('should set permanent, durable "netlify-cdn-cache-control" if "cache-control" is not set and "requestContext.usedFsReadForNonFallback" is truthy', () => { - const headers = new Headers() const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response() + vi.spyOn(response.headers, 'set') + + const requestContext = createRequestContext() + requestContext.usedFsReadForNonFallback = true + + setCacheControlHeaders(response, request, requestContext) + + expect(response.headers.set).toHaveBeenNthCalledWith( + 1, + 'cache-control', + 'public, max-age=0, must-revalidate', + ) + expect(response.headers.set).toHaveBeenNthCalledWith( + 2, + 'netlify-cdn-cache-control', + 'max-age=31536000, durable', + ) + }) + + test('should set permanent, durable "netlify-cdn-cache-control" if 404 response for URl ending in .php', () => { + const request = new Request(`${defaultUrl}/admin.php`) + const response = new Response(null, { status: 404 }) + vi.spyOn(response.headers, 'set') const requestContext = createRequestContext() requestContext.usedFsReadForNonFallback = true - setCacheControlHeaders(headers, request, requestContext) + setCacheControlHeaders(response, request, requestContext) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenNthCalledWith( 1, 'cache-control', 'public, max-age=0, must-revalidate', ) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenNthCalledWith( 2, 'netlify-cdn-cache-control', 'max-age=31536000, durable', @@ -358,13 +380,13 @@ describe('headers', () => { 'cache-control': 'public, max-age=0, must-revalidate', 'cdn-cache-control': 'public, max-age=0, must-revalidate', } - const headers = new Headers(givenHeaders) const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response(null, { headers: givenHeaders }) + vi.spyOn(response.headers, 'set') - setCacheControlHeaders(headers, request, createRequestContext()) + setCacheControlHeaders(response, request, createRequestContext()) - expect(headers.set).toHaveBeenCalledTimes(0) + expect(response.headers.set).toHaveBeenCalledTimes(0) }) test('should not set any headers if "cache-control" is set and "netlify-cdn-cache-control" is present', () => { @@ -372,31 +394,31 @@ describe('headers', () => { 'cache-control': 'public, max-age=0, must-revalidate', 'netlify-cdn-cache-control': 'public, max-age=0, must-revalidate', } - const headers = new Headers(givenHeaders) const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response(null, { headers: givenHeaders }) + vi.spyOn(response.headers, 'set') - setCacheControlHeaders(headers, request, createRequestContext()) + setCacheControlHeaders(response, request, createRequestContext()) - expect(headers.set).toHaveBeenCalledTimes(0) + expect(response.headers.set).toHaveBeenCalledTimes(0) }) test('should set expected headers if "cache-control" is set and "cdn-cache-control" and "netlify-cdn-cache-control" are not present (GET request)', () => { const givenHeaders = { 'cache-control': 'public, max-age=0, must-revalidate', } - const headers = new Headers(givenHeaders) const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response(null, { headers: givenHeaders }) + vi.spyOn(response.headers, 'set') - setCacheControlHeaders(headers, request, createRequestContext()) + setCacheControlHeaders(response, request, createRequestContext()) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenNthCalledWith( 1, 'cache-control', 'public, max-age=0, must-revalidate', ) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenNthCalledWith( 2, 'netlify-cdn-cache-control', 'public, max-age=0, must-revalidate, durable', @@ -407,18 +429,18 @@ describe('headers', () => { const givenHeaders = { 'cache-control': 'public, max-age=0, must-revalidate', } - const headers = new Headers(givenHeaders) const request = new Request(defaultUrl, { method: 'HEAD' }) - vi.spyOn(headers, 'set') + const response = new Response(null, { headers: givenHeaders }) + vi.spyOn(response.headers, 'set') - setCacheControlHeaders(headers, request, createRequestContext()) + setCacheControlHeaders(response, request, createRequestContext()) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenNthCalledWith( 1, 'cache-control', 'public, max-age=0, must-revalidate', ) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenNthCalledWith( 2, 'netlify-cdn-cache-control', 'public, max-age=0, must-revalidate, durable', @@ -429,27 +451,27 @@ describe('headers', () => { const givenHeaders = { 'cache-control': 'public, max-age=0, must-revalidate', } - const headers = new Headers(givenHeaders) const request = new Request(defaultUrl, { method: 'POST' }) - vi.spyOn(headers, 'set') + const response = new Response(null, { headers: givenHeaders }) + vi.spyOn(response.headers, 'set') - setCacheControlHeaders(headers, request, createRequestContext()) + setCacheControlHeaders(response, request, createRequestContext()) - expect(headers.set).toHaveBeenCalledTimes(0) + expect(response.headers.set).toHaveBeenCalledTimes(0) }) test('should remove "s-maxage" from "cache-control" header', () => { const givenHeaders = { 'cache-control': 'public, s-maxage=604800', } - const headers = new Headers(givenHeaders) const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response(null, { headers: givenHeaders }) + vi.spyOn(response.headers, 'set') - setCacheControlHeaders(headers, request, createRequestContext()) + setCacheControlHeaders(response, request, createRequestContext()) - expect(headers.set).toHaveBeenNthCalledWith(1, 'cache-control', 'public') - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenNthCalledWith(1, 'cache-control', 'public') + expect(response.headers.set).toHaveBeenNthCalledWith( 2, 'netlify-cdn-cache-control', 'public, s-maxage=604800, durable', @@ -460,14 +482,14 @@ describe('headers', () => { const givenHeaders = { 'cache-control': 'max-age=604800, stale-while-revalidate=86400', } - const headers = new Headers(givenHeaders) const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response(null, { headers: givenHeaders }) + vi.spyOn(response.headers, 'set') - setCacheControlHeaders(headers, request, createRequestContext()) + setCacheControlHeaders(response, request, createRequestContext()) - expect(headers.set).toHaveBeenNthCalledWith(1, 'cache-control', 'max-age=604800') - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenNthCalledWith(1, 'cache-control', 'max-age=604800') + expect(response.headers.set).toHaveBeenNthCalledWith( 2, 'netlify-cdn-cache-control', 'max-age=604800, stale-while-revalidate=86400, durable', @@ -478,18 +500,18 @@ describe('headers', () => { const givenHeaders = { 'cache-control': 's-maxage=604800, stale-while-revalidate=86400', } - const headers = new Headers(givenHeaders) const request = new Request(defaultUrl) - vi.spyOn(headers, 'set') + const response = new Response(null, { headers: givenHeaders }) + vi.spyOn(response.headers, 'set') - setCacheControlHeaders(headers, request, createRequestContext()) + setCacheControlHeaders(response, request, createRequestContext()) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenNthCalledWith( 1, 'cache-control', 'public, max-age=0, must-revalidate', ) - expect(headers.set).toHaveBeenNthCalledWith( + expect(response.headers.set).toHaveBeenNthCalledWith( 2, 'netlify-cdn-cache-control', 's-maxage=604800, stale-while-revalidate=86400, durable', diff --git a/src/run/headers.ts b/src/run/headers.ts index 53811b8169..12c56871a5 100644 --- a/src/run/headers.ts +++ b/src/run/headers.ts @@ -54,7 +54,10 @@ const generateNetlifyVaryValues = ({ } const getHeaderValueArray = (header: string): string[] => { - return header.split(',').map((value) => value.trim()) + return header + .split(',') + .map((value) => value.trim()) + .filter(Boolean) } const omitHeaderValues = (header: string, values: string[]): string => { @@ -210,7 +213,7 @@ export const adjustDateHeader = async ({ * assume the user knows what they are doing if CDN cache controls are set */ export const setCacheControlHeaders = ( - headers: Headers, + { headers, status }: Response, request: Request, requestContext: RequestContext, ) => { @@ -231,6 +234,13 @@ export const setCacheControlHeaders = ( return } + if (status === 404 && request.url.endsWith('.php')) { + // temporary CDN Cache Control handling for bot probes on PHP files + // https://linear.app/netlify/issue/FRB-1344/prevent-excessive-ssr-invocations-due-to-404-routes + headers.set('cache-control', 'public, max-age=0, must-revalidate') + headers.set('netlify-cdn-cache-control', `max-age=31536000, durable`) + } + const cacheControl = headers.get('cache-control') if ( cacheControl !== null && diff --git a/tests/e2e/on-demand-app.test.ts b/tests/e2e/on-demand-app.test.ts index a87d9cc265..cd26eb8a19 100644 --- a/tests/e2e/on-demand-app.test.ts +++ b/tests/e2e/on-demand-app.test.ts @@ -1,5 +1,6 @@ import { expect } from '@playwright/test' import { test } from '../utils/playwright-helpers.js' +import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs' test.describe('app router on-demand revalidation', () => { for (const { label, prerendered, pagePath, revalidateApiPath, expectedH1Content } of [ @@ -90,7 +91,9 @@ test.describe('app router on-demand revalidation', () => { expect(response1?.status()).toBe(200) expect(headers1['x-nextjs-cache']).toBeUndefined() expect(headers1['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const date1 = await page.textContent('[data-testid="date-now"]') @@ -118,7 +121,9 @@ test.describe('app router on-demand revalidation', () => { expect(headers2['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers2['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) // the page is cached @@ -148,7 +153,9 @@ test.describe('app router on-demand revalidation', () => { expect(response3?.status()).toBe(200) expect(headers3?.['x-nextjs-cache']).toBeUndefined() expect(headers3['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) // the page has now an updated date @@ -175,7 +182,9 @@ test.describe('app router on-demand revalidation', () => { expect(headers4['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers4['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) // the page is cached diff --git a/tests/e2e/page-router.test.ts b/tests/e2e/page-router.test.ts index 92cab2ac1b..a64d02de9f 100644 --- a/tests/e2e/page-router.test.ts +++ b/tests/e2e/page-router.test.ts @@ -163,7 +163,9 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { expect(headers1['netlify-cdn-cache-control']).toBe( fallbackWasServed ? undefined - : 's-maxage=31536000, stale-while-revalidate=31536000, durable', + : nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) if (fallbackWasServed) { @@ -196,7 +198,9 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { expect(headers1Json['x-nextjs-cache']).toBeUndefined() expect(headers1Json['netlify-cache-tag']).toBe(`_n_t_${encodeURI(pagePath).toLowerCase()}`) expect(headers1Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const data1 = (await response1Json?.json()) || {} expect(data1?.pageProps?.time).toBe(date1) @@ -221,7 +225,9 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { expect(headers2['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers2['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) // the page is cached @@ -252,7 +258,9 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { expect(headers2Json['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers2Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const data2 = (await response2Json?.json()) || {} @@ -307,7 +315,9 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { expect(response3Json?.status()).toBe(200) expect(headers3Json['x-nextjs-cache']).toBeUndefined() expect(headers3Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const data3 = (await response3Json?.json()) || {} @@ -422,7 +432,9 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { expect(await page.textContent('h1')).toBe('404') expect(headers['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) expect(headers['cache-control']).toBe('public,max-age=0,must-revalidate') }) @@ -607,7 +619,9 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers1ImplicitLocale['netlify-cdn-cache-control']).toBe( fallbackWasServedImplicitLocale ? undefined - : 's-maxage=31536000, stale-while-revalidate=31536000, durable', + : nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) if (fallbackWasServedImplicitLocale) { @@ -647,7 +661,9 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers1ExplicitLocale['netlify-cdn-cache-control']).toBe( fallbackWasServedExplicitLocale ? undefined - : 's-maxage=31536000, stale-while-revalidate=31536000, durable', + : nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) if (fallbackWasServedExplicitLocale) { @@ -686,7 +702,9 @@ test.describe('Page Router with basePath and i18n', () => { `_n_t_/en${encodeURI(pagePath).toLowerCase()}`, ) expect(headers1Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const data1 = (await response1Json?.json()) || {} expect(data1?.pageProps?.time).toBe(date1ImplicitLocale) @@ -714,7 +732,9 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers2ImplicitLocale['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers2ImplicitLocale['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) // the page is cached @@ -744,7 +764,9 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers2ExplicitLocale['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers2ExplicitLocale['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) // the page is cached @@ -775,7 +797,9 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers2Json['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers2Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const data2 = (await response2Json?.json()) || {} @@ -869,7 +893,9 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers3Json['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers3Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const data3 = (await response3Json?.json()) || {} @@ -958,7 +984,9 @@ test.describe('Page Router with basePath and i18n', () => { expect(response4Json?.status()).toBe(200) expect(headers4Json['x-nextjs-cache']).toBeUndefined() expect(headers4Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const data4 = (await response4Json?.json()) || {} @@ -1011,7 +1039,9 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers1['netlify-cdn-cache-control']).toBe( fallbackWasServed ? undefined - : 's-maxage=31536000, stale-while-revalidate=31536000, durable', + : nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) if (fallbackWasServed) { @@ -1047,7 +1077,9 @@ test.describe('Page Router with basePath and i18n', () => { `_n_t_/de${encodeURI(pagePath).toLowerCase()}`, ) expect(headers1Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const data1 = (await response1Json?.json()) || {} expect(data1?.pageProps?.time).toBe(date1) @@ -1075,7 +1107,9 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers2['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers2['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) // the page is cached @@ -1107,7 +1141,9 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers2Json['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers2Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const data2 = (await response2Json?.json()) || {} @@ -1174,7 +1210,9 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers3Json['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers3Json['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const data3 = (await response3Json?.json()) || {} @@ -1218,7 +1256,9 @@ test.describe('Page Router with basePath and i18n', () => { expect(await page.textContent('h1')).toBe('404') expect(headers['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) expect(headers['cache-control']).toBe('public,max-age=0,must-revalidate') }) diff --git a/tests/e2e/simple-app.test.ts b/tests/e2e/simple-app.test.ts index 98058eb3af..15f1f585fe 100644 --- a/tests/e2e/simple-app.test.ts +++ b/tests/e2e/simple-app.test.ts @@ -247,7 +247,9 @@ test('requesting a non existing page route that needs to be fetched from the blo expect(await page.textContent('h1')).toBe('404 Not Found') expect(headers['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) expect(headers['cache-control']).toBe('public,max-age=0,must-revalidate') }) diff --git a/tests/e2e/turborepo.test.ts b/tests/e2e/turborepo.test.ts index 9afecccd9d..604cfabc19 100644 --- a/tests/e2e/turborepo.test.ts +++ b/tests/e2e/turborepo.test.ts @@ -1,5 +1,6 @@ import { expect } from '@playwright/test' import { test } from '../utils/playwright-helpers.js' +import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs' // those tests have different fixtures and can run in parallel test.describe.configure({ mode: 'parallel' }) @@ -35,7 +36,9 @@ test.describe('[PNPM] Package manager', () => { expect(response1?.status()).toBe(200) expect(headers1['x-nextjs-cache']).toBeUndefined() expect(headers1['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const date1 = await page.textContent('[data-testid="date-now"]') @@ -65,7 +68,9 @@ test.describe('[PNPM] Package manager', () => { expect(headers2['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers2['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) // the page is cached @@ -139,7 +144,9 @@ test.describe('[NPM] Package manager', () => { expect(response1?.status()).toBe(200) expect(headers1['x-nextjs-cache']).toBeUndefined() expect(headers1['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) const date1 = await page.textContent('[data-testid="date-now"]') @@ -169,7 +176,9 @@ test.describe('[NPM] Package manager', () => { expect(headers2['cache-status']).toMatch(/"Next.js"; hit/m) } expect(headers2['netlify-cdn-cache-control']).toBe( - 's-maxage=31536000, stale-while-revalidate=31536000, durable', + nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) // the page is cached diff --git a/tests/integration/cache-handler.test.ts b/tests/integration/cache-handler.test.ts index 6dc7a41a37..22ba4397b8 100644 --- a/tests/integration/cache-handler.test.ts +++ b/tests/integration/cache-handler.test.ts @@ -17,6 +17,7 @@ import { getBlobEntries, startMockBlobStore, } from '../utils/helpers.js' +import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs' // Disable the verbose logging of the lambda-local runtime getLogger().level = 'alert' @@ -92,7 +93,9 @@ describe('page router', () => { ).toEqual( expect.objectContaining({ 'cache-status': '"Next.js"; hit', - 'netlify-cdn-cache-control': 's-maxage=5, stale-while-revalidate=31536000, durable', + 'netlify-cdn-cache-control': nextVersionSatisfies('>=15.0.0-canary.187') + ? `s-maxage=5, stale-while-revalidate=${31536000 - 5}, durable` + : 's-maxage=5, stale-while-revalidate=31536000, durable', }), ) expect( @@ -243,7 +246,9 @@ describe('app router', () => { // It will be hit instead of stale expect.objectContaining({ 'cache-status': '"Next.js"; hit', - 'netlify-cdn-cache-control': 's-maxage=31536000, stale-while-revalidate=31536000, durable', + 'netlify-cdn-cache-control': nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', }), ) expect( diff --git a/tests/integration/fetch-handler.test.ts b/tests/integration/fetch-handler.test.ts index 398f8355fe..b525dfd16e 100644 --- a/tests/integration/fetch-handler.test.ts +++ b/tests/integration/fetch-handler.test.ts @@ -7,6 +7,7 @@ import { afterAll, beforeAll, beforeEach, expect, test, vi } from 'vitest' import { type FixtureTestContext } from '../utils/contexts.js' import { createFixture, invokeFunction, runPlugin, runPluginStep } from '../utils/fixture.js' import { generateRandomObjectID, startMockBlobStore } from '../utils/helpers.js' +import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs' // Disable the verbose logging of the lambda-local runtime getLogger().level = 'alert' @@ -229,7 +230,9 @@ test('if the fetch call is cached correctly (cached page res ).toEqual( expect.objectContaining({ 'cache-status': '"Next.js"; hit', - 'netlify-cdn-cache-control': 's-maxage=5, stale-while-revalidate=31536000, durable', + 'netlify-cdn-cache-control': nextVersionSatisfies('>=15.0.0-canary.187') + ? `s-maxage=5, stale-while-revalidate=${31536000 - 5}, durable` + : 's-maxage=5, stale-while-revalidate=31536000, durable', }), ) @@ -295,7 +298,9 @@ test('if the fetch call is cached correctly (cached page res ).toEqual( expect.objectContaining({ 'cache-status': '"Next.js"; hit', - 'netlify-cdn-cache-control': 's-maxage=5, stale-while-revalidate=31536000, durable', + 'netlify-cdn-cache-control': nextVersionSatisfies('>=15.0.0-canary.187') + ? `s-maxage=5, stale-while-revalidate=${31536000 - 5}, durable` + : 's-maxage=5, stale-while-revalidate=31536000, durable', }), ) }) diff --git a/tests/integration/revalidate-path.test.ts b/tests/integration/revalidate-path.test.ts index 342872e1e3..f326eea0b6 100644 --- a/tests/integration/revalidate-path.test.ts +++ b/tests/integration/revalidate-path.test.ts @@ -10,6 +10,7 @@ import { getBlobServerGets, startMockBlobStore, } from '../utils/helpers.js' +import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs' function isTagManifest(key: string) { return key.startsWith('_N_T_') @@ -67,7 +68,9 @@ test('should revalidate a route by path', async (ctx) => { expect(post1.headers, 'a cache hit on the first invocation of a prerendered page').toEqual( expect.objectContaining({ 'cache-status': expect.stringMatching(/"Next.js"; hit/), - 'netlify-cdn-cache-control': 's-maxage=31536000, stale-while-revalidate=31536000, durable', + 'netlify-cdn-cache-control': nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', }), ) @@ -82,7 +85,9 @@ test('should revalidate a route by path', async (ctx) => { expect(post1Route2.headers, 'a cache hit on the first invocation of a prerendered page').toEqual( expect.objectContaining({ 'cache-status': expect.stringMatching(/"Next.js"; hit/), - 'netlify-cdn-cache-control': 's-maxage=31536000, stale-while-revalidate=31536000, durable', + 'netlify-cdn-cache-control': nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', }), ) @@ -110,7 +115,9 @@ test('should revalidate a route by path', async (ctx) => { expect(post2.headers, 'a cache miss on the on demand revalidated path /1').toEqual( expect.objectContaining({ 'cache-status': '"Next.js"; fwd=miss', - 'netlify-cdn-cache-control': 's-maxage=31536000, stale-while-revalidate=31536000, durable', + 'netlify-cdn-cache-control': nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', }), ) expect(post2Date).not.toBe(post1Date) @@ -127,7 +134,9 @@ test('should revalidate a route by path', async (ctx) => { expect(post2Route2.headers, 'a cache miss on the on demand revalidated path /2').toEqual( expect.objectContaining({ 'cache-status': '"Next.js"; fwd=miss', - 'netlify-cdn-cache-control': 's-maxage=31536000, stale-while-revalidate=31536000, durable', + 'netlify-cdn-cache-control': nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', }), ) diff --git a/tests/integration/revalidate-tags.test.ts b/tests/integration/revalidate-tags.test.ts index 9b48c23956..c93f45a980 100644 --- a/tests/integration/revalidate-tags.test.ts +++ b/tests/integration/revalidate-tags.test.ts @@ -10,6 +10,7 @@ import { getBlobServerGets, startMockBlobStore, } from '../utils/helpers.js' +import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs' function isTagManifest(key: string) { return key.startsWith('_N_T_') @@ -67,7 +68,9 @@ test('should revalidate a route by tag', async (ctx) => { expect(post1.headers, 'a cache hit on the first invocation of a prerendered page').toEqual( expect.objectContaining({ 'cache-status': expect.stringMatching(/"Next.js"; hit/), - 'netlify-cdn-cache-control': 's-maxage=31536000, stale-while-revalidate=31536000, durable', + 'netlify-cdn-cache-control': nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', }), ) @@ -94,7 +97,9 @@ test('should revalidate a route by tag', async (ctx) => { expect(post2.headers, 'a cache miss on the on demand revalidated page').toEqual( expect.objectContaining({ 'cache-status': '"Next.js"; fwd=miss', - 'netlify-cdn-cache-control': 's-maxage=31536000, stale-while-revalidate=31536000, durable', + 'netlify-cdn-cache-control': nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', }), ) expect(post2Date).not.toBe(post1Date) @@ -117,7 +122,9 @@ test('should revalidate a route by tag', async (ctx) => { expect(post3.headers, 'a cache hit on the revalidated and regenerated page').toEqual( expect.objectContaining({ 'cache-status': expect.stringMatching(/"Next.js"; hit/), - 'netlify-cdn-cache-control': 's-maxage=31536000, stale-while-revalidate=31536000, durable', + 'netlify-cdn-cache-control': nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', }), ) expect(post3Date).toBe(post2Date) @@ -146,7 +153,9 @@ test('should revalidate a route by tag', async (ctx) => { expect(post4.headers, 'a cache miss on the on demand revalidated page').toEqual( expect.objectContaining({ 'cache-status': '"Next.js"; fwd=miss', - 'netlify-cdn-cache-control': 's-maxage=31536000, stale-while-revalidate=31536000, durable', + 'netlify-cdn-cache-control': nextVersionSatisfies('>=15.0.0-canary.187') + ? 's-maxage=31536000, durable' + : 's-maxage=31536000, stale-while-revalidate=31536000, durable', }), ) expect(post4Date).not.toBe(post3Date) diff --git a/tests/integration/simple-app.test.ts b/tests/integration/simple-app.test.ts index 3b17f7130c..54044ada34 100644 --- a/tests/integration/simple-app.test.ts +++ b/tests/integration/simple-app.test.ts @@ -21,6 +21,7 @@ import { getBlobEntries, startMockBlobStore, } from '../utils/helpers.js' +import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs' const mockedCp = cp as Mock< Parameters<(typeof import('node:fs/promises'))['cp']>, @@ -149,13 +150,25 @@ test('index should be normalized within the cacheHandler and expect(index.headers?.['netlify-cache-tag']).toBe('_N_T_/layout,_N_T_/page,_N_T_/') }) -test('stale-while-revalidate headers should be normalized to include delta-seconds', async (ctx) => { +// with 15.0.0-canary.187 and later Next.js no longer produce `stale-while-revalidate` directive +// for permanently cached response +test.skipIf(nextVersionSatisfies('>=15.0.0-canary.187'))( + 'stale-while-revalidate headers should be normalized to include delta-seconds', + async (ctx) => { + await createFixture('simple', ctx) + await runPlugin(ctx) + const index = await invokeFunction(ctx, { url: '/' }) + expect(index.headers?.['netlify-cdn-cache-control']).toContain( + 'stale-while-revalidate=31536000, durable', + ) + }, +) + +test('404 responses for PHP pages should be cached indefinitely', async (ctx) => { await createFixture('simple', ctx) await runPlugin(ctx) - const index = await invokeFunction(ctx, { url: '/' }) - expect(index.headers?.['netlify-cdn-cache-control']).toContain( - 'stale-while-revalidate=31536000, durable', - ) + const index = await invokeFunction(ctx, { url: '/admin.php' }) + expect(index.headers?.['netlify-cdn-cache-control']).toContain('max-age=31536000, durable') }) test('handlers receive correct site domain', async (ctx) => {