diff --git a/package-lock.json b/package-lock.json index 293faa50434..17edc0bdd00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -208,7 +208,6 @@ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", "dev": true, - "peer": true, "engines": { "node": ">=6.9.0" } @@ -249,7 +248,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -287,7 +285,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, - "peer": true, "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", @@ -304,7 +301,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -336,7 +332,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, - "peer": true, "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", @@ -389,7 +384,6 @@ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, - "peer": true, "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" @@ -2079,7 +2073,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -4713,6 +4706,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.5.tgz", "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.2", @@ -4879,6 +4873,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.8.0.tgz", "integrity": "sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -5895,6 +5890,7 @@ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "dev": true, + "peer": true, "dependencies": { "@types/ms": "*" } @@ -5937,6 +5933,7 @@ "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -6072,6 +6069,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.11.tgz", "integrity": "sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -6388,6 +6386,7 @@ "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/types": "8.45.0", @@ -7943,6 +7942,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7990,6 +7990,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -8429,7 +8430,6 @@ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.7.tgz", "integrity": "sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ==", "dev": true, - "peer": true, "bin": { "baseline-browser-mapping": "dist/cli.js" } @@ -9046,8 +9046,7 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "peer": true + ] }, "node_modules/caseless": { "version": "0.12.0", @@ -10535,8 +10534,7 @@ "version": "1.5.223", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.223.tgz", "integrity": "sha512-qKm55ic6nbEmagFlTFczML33rF90aU+WtrJ9MdTCThrcvDNdUHN4p6QfVN78U06ZmguqXIyMPyYhw2TrbDUwPQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/emoji-regex": { "version": "10.5.0", @@ -10830,6 +10828,7 @@ "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -12207,7 +12206,6 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "peer": true, "engines": { "node": ">=6.9.0" } @@ -14031,7 +14029,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "peer": true, "bin": { "json5": "lib/cli.js" }, @@ -14682,7 +14679,6 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "peer": true, "dependencies": { "yallist": "^3.0.2" } @@ -15289,8 +15285,7 @@ "version": "2.0.21", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/node-source-walk": { "version": "7.0.1", @@ -16318,6 +16313,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -17033,6 +17029,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz", "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==", "devOptional": true, + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -18447,7 +18444,8 @@ "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "peer": true }, "node_modules/tsx": { "version": "4.20.6", @@ -18455,6 +18453,7 @@ "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -18539,6 +18538,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18850,7 +18850,6 @@ "url": "https://github.com/sponsors/ai" } ], - "peer": true, "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -19743,6 +19742,7 @@ "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -19839,6 +19839,7 @@ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -20430,8 +20431,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "peer": true + "dev": true }, "node_modules/yaml": { "version": "2.8.1", diff --git a/tests/integration/commands/deploy/deploy.test.ts b/tests/integration/commands/deploy/deploy.test.ts index bb7bb5e9086..e0b7e1bf0b8 100644 --- a/tests/integration/commands/deploy/deploy.test.ts +++ b/tests/integration/commands/deploy/deploy.test.ts @@ -9,6 +9,7 @@ import { afterAll, beforeAll, describe, expect, test } from 'vitest' import { callCli } from '../../utils/call-cli.js' import { createLiveTestSite, generateSiteName } from '../../utils/create-live-test-site.js' +import { fetchWithRetry } from '../../utils/fetch-with-retry.js' import { FixtureTestContext, setupFixtureTests } from '../../utils/fixture.js' import { pause } from '../../utils/pause.js' import { withSiteBuilder } from '../../utils/site-builder.js' @@ -863,7 +864,7 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co true, )) as unknown as Deploy - const response = await fetch(`${deployUrl}/.netlify/functions/hello`) + const response = await fetchWithRetry(`${deployUrl}/.netlify/functions/hello`) t.expect(await response.text()).toEqual('Hello') t.expect(response.status).toBe(200) }) @@ -973,19 +974,6 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co true, )) as unknown as Deploy - // Add retry logic for fetching deployed functions - const fetchWithRetry = async (url: string, maxRetries = 5) => { - for (let i = 0; i < maxRetries; i++) { - try { - return await fetch(url) - } catch (error) { - if (i === maxRetries - 1) throw error - await pause(2000 * (i + 1)) // Exponential backoff: 2s, 4s, 6s, 8s - } - } - throw new Error(`Failed to fetch ${url} after ${maxRetries} retries`) - } - const [response1, response2, response3, response4, response5, response6, response7] = await Promise.all([ fetchWithRetry(`${deployUrl}/.netlify/functions/func-1`).then((res) => res.text()), fetchWithRetry(`${deployUrl}/.netlify/functions/func-2`).then((res) => res.text()), @@ -1033,7 +1021,7 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co }, true, )) as unknown as Deploy - const response = await fetch(`${deployUrl}/.netlify/functions/func-1`).then((res) => res.text()) + const response = await fetchWithRetry(`${deployUrl}/.netlify/functions/func-1`).then((res) => res.text()) t.expect(response).toEqual('Internal') }) @@ -1176,7 +1164,9 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co }, true, )) as unknown as Deploy - const response = await fetch(`${deployUrl}/.netlify/functions/bundled-function-1`).then((res) => res.text()) + const response = await fetchWithRetry(`${deployUrl}/.netlify/functions/bundled-function-1`).then((res) => + res.text(), + ) expect(response).toEqual('Pre-bundled') }) }) @@ -1236,7 +1226,9 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co true, )) as unknown as Deploy - const response = await fetch(`${deployUrl}/.netlify/functions/bundled-function-1`).then((res) => res.text()) + const response = await fetchWithRetry(`${deployUrl}/.netlify/functions/bundled-function-1`).then((res) => + res.text(), + ) t.expect(response).toEqual('Bundled at deployment') }) }) @@ -1297,7 +1289,9 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co true, )) as unknown as { deploy_url: string } - const response = await fetch(`${deployUrl}/.netlify/functions/bundled-function-1`).then((res) => res.text()) + const response = await fetchWithRetry(`${deployUrl}/.netlify/functions/bundled-function-1`).then((res) => + res.text(), + ) t.expect(response).toEqual('Bundled at deployment') }) }) diff --git a/tests/integration/commands/dev/v2-api.test.ts b/tests/integration/commands/dev/v2-api.test.ts index 0c20f8df0be..a8a109ecb07 100644 --- a/tests/integration/commands/dev/v2-api.test.ts +++ b/tests/integration/commands/dev/v2-api.test.ts @@ -53,7 +53,7 @@ describe.runIf(gte(version, '20.12.2')).concurrent('v2 api', async () => { expect(thirdChunk.done).toBeTruthy() }) - test('receives context', async ({ devServer, expect }) => { + test('receives context', { retry: 3 }, async ({ devServer, expect }) => { const response = await fetch(`http://localhost:${devServer!.port}/.netlify/functions/context`, { headers: { Cookie: 'foo=bar;', diff --git a/tests/integration/utils/fetch-with-retry.ts b/tests/integration/utils/fetch-with-retry.ts new file mode 100644 index 00000000000..cab64f31cb9 --- /dev/null +++ b/tests/integration/utils/fetch-with-retry.ts @@ -0,0 +1,19 @@ +import { pause } from './pause.js' + +export const fetchWithRetry = async (url: string, options?: RequestInit, maxRetries = 5): Promise => { + for (let i = 0; i < maxRetries; i++) { + try { + const response = await fetch(url, options) + if (response.status !== 404) { + return response + } + if (i < maxRetries - 1) { + await pause(2000 * (i + 1)) + } + } catch (error) { + if (i === maxRetries - 1) throw error + await pause(2000 * (i + 1)) + } + } + return fetch(url, options) +}