From 8c8faa02abef86db23d3ea65e3bf2380bffcb52e Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 2 Apr 2024 16:13:18 +0100 Subject: [PATCH 1/9] chore: annotate all skipped tests (#378) * chore: annotate all skipped tests * chore: generate test config from JSON file * chore: generate JSON results * chore: more annotation * chore: add more test explanations * chore: more test config * chore: more test config * chore: use reason --------- Co-authored-by: Michal Piechowiak --- .github/workflows/test-e2e.yml | 7 +- tests/netlify-e2e.cjs | 239 ++------------------ tests/test-config.json | 387 +++++++++++++++++++++++++++++++++ tools/deno/junit2json.ts | 182 ++++++++++++++++ 4 files changed, 599 insertions(+), 216 deletions(-) create mode 100644 tests/test-config.json create mode 100644 tools/deno/junit2json.ts diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 1fd77b2610..5941b836ef 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -241,7 +241,12 @@ jobs: echo "slackEvent<> $GITHUB_OUTPUT deno run -A tools/deno/junit2slack.ts --dir artifacts --version ${{matrix.version}} --runUrl ${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}} >> $GITHUB_OUTPUT echo "NETLIFY_EOF" >> $GITHUB_OUTPUT - + deno run -A tools/deno/junit2json.ts artifacts > report/test-results.json + - name: Upload Test JSON + uses: actions/upload-artifact@v4 + with: + name: ${{matrix.version}}-test-results.json + path: report/test-results.json - name: Notify Slack if: success() || failure() uses: slackapi/slack-github-action@v1.24.0 diff --git a/tests/netlify-e2e.cjs b/tests/netlify-e2e.cjs index 209e848a96..c02495c92b 100644 --- a/tests/netlify-e2e.cjs +++ b/tests/netlify-e2e.cjs @@ -1,220 +1,29 @@ -module.exports = { +// @ts-check +const config = { version: 2, - suites: { - 'test/e2e/app-dir/app-static/app-static.test.ts': { - failed: ['app-dir static/dynamic handling should warn for too many cache tags'], - }, - 'test/e2e/app-dir/headers-static-bailout/headers-static-bailout.test.ts': { - failed: [ - // Uses cli output - 'headers-static-bailout it provides a helpful link in case static generation bailout is uncaught', - ], - }, - 'test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts': { - failed: [], - flakey: [ - // Uses patch file - 'parallel-routes-and-interception parallel routes should gracefully handle when two page segments match the `children` parallel slot', - ], - }, - 'test/e2e/opentelemetry/opentelemetry.test.ts': { - failed: [ - 'opentelemetry root context app router should handle RSC with fetch', - 'opentelemetry incoming context propagation app router should handle RSC with fetch', - 'opentelemetry incoming context propagation app router should handle route handlers in app router', - ], - }, - 'test/e2e/app-dir/rsc-basic/rsc-basic.test.ts': { - failed: [ - 'app dir - rsc basics should render initial styles of css-in-js in edge SSR correctly', - 'app dir - rsc basics should render initial styles of css-in-js in nodejs SSR correctly', - 'app dir - rsc basics should render server components correctly', - ], - flakey: [ - 'app dir - rsc basics react@experimental should opt into the react@experimental when enabling ppr', - 'app dir - rsc basics react@experimental should opt into the react@experimental when enabling taint', - ], - }, - 'test/e2e/app-dir/navigation/navigation.test.ts': { - failed: [ - 'app dir - navigation redirect status code should respond with 308 status code if permanent flag is set', - 'app dir - navigation redirect status code should respond with 307 status code in client component', - 'app dir - navigation redirect status code should respond with 307 status code in server component', - 'app dir - navigation bots should block rendering for bots and return 404 status', - 'app dir - navigation navigation between pages and app should not continously initiate a mpa navigation to the same URL when router state changes', - ], - }, - 'test/production/app-dir/unexpected-error/unexpected-error.test.ts': { - failed: [ - 'unexpected-error should set response status to 500 for unexpected errors in ssr app route', - 'unexpected-error should set response status to 500 for unexpected errors in isr app route', - ], - }, - 'test/e2e/skip-trailing-slash-redirect/index.test.ts': { - flakey: [ - 'skip-trailing-slash-redirect should merge cookies from middleware and API routes correctly', - 'skip-trailing-slash-redirect should merge cookies from middleware and edge API routes correctly', - 'skip-trailing-slash-redirect should handle external rewrite correctly /chained-rewrite-ssr', - 'skip-trailing-slash-redirect should handle external rewrite correctly /chained-rewrite-static', - 'skip-trailing-slash-redirect should handle external rewrite correctly /chained-rewrite-ssg', - ], - }, - 'test/e2e/module-layer/index.test.ts': { - flakey: [ - 'module layer no server-only in server targets should render routes marked with restriction marks without errors', - 'module layer with server-only in server targets should render routes marked with restriction marks without errors', - ], - }, - 'test/e2e/getserversideprops/test/index.test.ts': { - flakey: [ - 'getServerSideProps should set default caching header', - 'getServerSideProps should respect custom caching header', - ], - }, - 'test/e2e/app-dir/metadata-dynamic-routes/index.test.ts': { - pending: [], - flakey: [ - 'app dir - metadata dynamic routes text routes should handle robots.[ext] dynamic routes', - 'app dir - metadata dynamic routes text routes should handle sitemap.[ext] dynamic routes', - 'app dir - metadata dynamic routes social image routes should handle manifest.[ext] dynamic routes', - 'app dir - metadata dynamic routes social image routes should render og image with opengraph-image dynamic routes', - 'app dir - metadata dynamic routes social image routes should render og image with twitter-image dynamic routes', - 'app dir - metadata dynamic routes icon image routes should render icon with dynamic routes', - 'app dir - metadata dynamic routes icon image routes should render apple icon with dynamic routes', - 'app dir - metadata dynamic routes should inject dynamic metadata properly to head', - 'app dir - metadata dynamic routes should use localhost for local prod and fallback to deployment url when metadataBase is falsy', - ], - }, - 'test/e2e/app-dir/metadata/metadata.test.ts': { - flakey: [ - 'app dir - metadata opengraph should pick up opengraph-image and twitter-image as static metadata files', - 'app dir - metadata static routes should have /favicon.ico as route', - 'app dir - metadata static routes should have icons as route', - ], - }, - 'test/e2e/basepath.test.ts': { - flakey: [ - 'basePath should not update URL for a 404', - 'basePath should handle 404 urls that start with basePath', - 'basePath should show 404 for page not under the /docs prefix', - ], - }, - 'test/e2e/app-dir/app/index.test.ts': { - flakey: [ - 'app dir - basic should return the `vary` header from edge runtime', - 'app dir - basic should return the `vary` header from pages for flight requests', - ], - }, - 'test/e2e/app-dir/conflicting-page-segments/conflicting-page-segments.test.ts': { - flakey: [ - 'conflicting-page-segments should throw an error when a route groups causes a conflict with a parallel segment', - ], - }, - 'test/e2e/app-dir/actions-navigation/index.test.ts': { - flakey: [ - 'app-dir action handling should handle actions correctly after following a relative link', - ], - }, - 'test/e2e/middleware-general/test/index.test.ts': { - flakey: [ - 'Middleware Runtime with i18n should redirect the same for direct visit and client-transition', - 'Middleware Runtime without i18n should redirect the same for direct visit and client-transition', - // These rely on Cloudflare-specific error wording - 'Middleware Runtime with i18n should allow to abort a fetch request', - 'Middleware Runtime without i18n should allow to abort a fetch request', - ], - }, - 'test/e2e/prerender.test.ts': { - flakey: [ - 'Prerender should handle on-demand revalidate for fallback: blocking', - // Header whitespace mismatch - 'Prerender should use correct caching headers for a revalidate page', - // Header whitespace mismatch - 'Prerender should use correct caching headers for a no-revalidate page', - ], - }, - 'test/e2e/app-dir/app-routes/app-custom-route-base-path.test.ts': { - flakey: [ - // Uses cli output - 'app-custom-routes no response returned should print an error when no response is returned', - 'app-custom-routes error conditions responds with 400 (Bad Request) when the requested method is not a valid HTTP method', - ], - }, - 'test/e2e/app-dir/app-routes/app-custom-routes.test.ts': { - flakey: [ - // Uses cli output - 'app-custom-routes no response returned should print an error when no response is returned', - 'app-custom-routes error conditions responds with 400 (Bad Request) when the requested method is not a valid HTTP method', - ], - }, - 'test/e2e/app-dir/actions/app-action.test.ts': { - flakey: [ - // Uses cli output - 'app-dir action handling should log a warning when a server action is not found but an id is provided', - 'app-dir action handling should work with interception routes', - ], - }, - }, + suites: {}, rules: { include: ['test/e2e/**/*.test.{t,j}s{,x}'], - exclude: [ - // This is a template, not a real test file - 'test/e2e/test-template/{{ toFileName name }}/{{ toFileName name }}.test.ts', - 'test/e2e/app-dir/next-font/**/*', - // We don't support PPR - 'test/e2e/app-dir/ppr/**/*', - 'test/e2e/app-dir/ppr-*/**/*', - 'test/e2e/app-dir/app-prefetch*/**/*', - 'test/e2e/app-dir/app-esm-js/index.test.ts', - 'test/e2e/app-dir/interception-middleware-rewrite/interception-middleware-rewrite.test.ts', - 'test/e2e/app-dir/searchparams-static-bailout/searchparams-static-bailout.test.ts', - 'test/e2e/app-dir/app-compilation/index.test.ts', - 'test/e2e/cancel-request/stream-cancel.test.ts', - 'test/e2e/favicon-short-circuit/favicon-short-circuit.test.ts', - 'test/e2e/edge-pages-support/edge-document.test.ts', - 'test/e2e/third-parties/index.test.ts', - // Uses cli output - 'test/e2e/swc-warnings/index.test.ts', - 'test/e2e/app-dir/externals/externals.test.ts', - 'test/e2e/app-dir/use-selected-layout-segment-s/use-selected-layout-segment-s.test.ts', - 'test/e2e/repeated-forward-slashes-error/repeated-forward-slashes-error.test.ts', - 'test/e2e/app-dir/with-exported-function-config/with-exported-function-config.test.ts', - 'test/e2e/app-dir/x-forwarded-headers/x-forwarded-headers.test.ts', - 'test/e2e/app-dir/third-parties/basic.test.ts', - 'test/e2e/app-dir/conflicting-page-segments/conflicting-page-segments.test.ts', - 'test/e2e/404-page-router/index.test.ts', - 'test/e2e/app-dir/app-client-cache/client-cache.test.ts', - 'test/e2e/app-dir/app-fetch-deduping/app-fetch-deduping.test.ts', - 'test/e2e/app-dir/app/experimental-compile.test.ts', - 'test/e2e/app-dir/app/standalone-gsp.test.ts', - 'test/e2e/app-dir/app/standalone.test.ts', - 'test/e2e/app-dir/app/vercel-speed-insights.test.ts', - 'test/e2e/app-dir/build-size/index.test.ts', - 'test/e2e/app-dir/create-root-layout/create-root-layout.test.ts', - 'test/e2e/app-dir/headers-static-bailout/headers-static-bailout.test.ts', - 'test/e2e/app-dir/rewrites-redirects/rewrites-redirects.test.ts', - 'test/e2e/edge-compiler-can-import-blob-assets/index.test.ts', - 'test/e2e/i18n-data-fetching-redirect/index.test.ts', - 'test/e2e/manual-client-base-path/index.test.ts', - 'test/e2e/no-eslint-warn-with-no-eslint-config/index.test.ts', - 'test/e2e/switchable-runtime/index.test.ts', - 'test/e2e/trailingslash-with-rewrite/index.test.ts', - 'test/e2e/transpile-packages/index.test.ts', - 'test/e2e/typescript-version-no-warning/typescript-version-no-warning.test.ts', - 'test/e2e/typescript-version-warning/typescript-version-warning.test.ts', - 'test/e2e/app-dir/app/useReportWebVitals.test.ts', - 'test/e2e/app-dir/app-static/app-static-custom-handler.test.ts', - // Tries to patch deployed files - 'test/e2e/app-dir/missing-suspense-with-csr-bailout/missing-suspense-with-csr-bailout.test.ts', - // Tries to patch deployed files - 'test/e2e/module-layer/module-layer.test.ts', - // Hard-coded localhost URL - 'test/e2e/app-dir/next-image/next-image-proxy.test.ts', - 'test/e2e/proxy-request-with-middleware/test/index.test.ts', - // Uses invalid WASM syntax - 'test/e2e/edge-can-use-wasm-files/index.test.ts', - // Expected behaviour does not match next start - 'test/e2e/i18n-data-route/i18n-data-route.test.ts', - ], + /** @type {string[]} */ + exclude: [], }, } + +const rules = require('./test-config.json') + +// Skip non-deploy tests +config.rules.exclude.push(...rules.ignored) + +for (const rule of rules.skipped) { + // Individually-skipped tests + if (rule.tests?.length) { + config.suites[rule.file] = { + failed: rule.tests, + } + } else { + // Entire suite skipped + config.rules.exclude.push(rule.file) + } +} + +module.exports = config diff --git a/tests/test-config.json b/tests/test-config.json new file mode 100644 index 0000000000..036c458468 --- /dev/null +++ b/tests/test-config.json @@ -0,0 +1,387 @@ +{ + "ignored": [ + "test/e2e/test-template/{{ toFileName name }}/{{ toFileName name }}.test.ts", + "test/e2e/app-dir/app-validation/validation.test.ts", + "test/e2e/app-dir/app/standalone-gsp.test.ts", + "test/e2e/app-dir/app/standalone.test.ts", + "test/e2e/app-dir/ppr-errors/ppr-errors.test.ts", + "test/e2e/app-dir/interception-middleware-rewrite/interception-middleware-rewrite.test.ts", + "test/e2e/app-dir/app-compilation/index.test.ts", + "test/e2e/favicon-short-circuit/favicon-short-circuit.test.ts", + "test/e2e/app-dir/with-exported-function-config/with-exported-function-config.test.ts", + "test/e2e/app-dir/conflicting-page-segments/conflicting-page-segments.test.ts", + "test/e2e/404-page-router/index.test.ts", + "test/e2e/app-dir/app-fetch-deduping/app-fetch-deduping.test.ts", + "test/e2e/app-dir/app/experimental-compile.test.ts", + "test/e2e/app-dir/app/standalone-gsp.test.ts", + "test/e2e/app-dir/app/standalone.test.ts", + "test/e2e/app-dir/build-size/index.test.ts", + "test/e2e/app-dir/create-root-layout/create-root-layout.test.ts", + "test/e2e/app-dir/rewrites-redirects/rewrites-redirects.test.ts", + "test/e2e/edge-compiler-can-import-blob-assets/index.test.ts", + "test/e2e/i18n-data-fetching-redirect/index.test.ts", + "test/e2e/manual-client-base-path/index.test.ts", + "test/e2e/no-eslint-warn-with-no-eslint-config/index.test.ts", + "test/e2e/switchable-runtime/index.test.ts", + "test/e2e/trailingslash-with-rewrite/index.test.ts", + "test/e2e/transpile-packages/index.test.ts", + "test/e2e/typescript-version-no-warning/typescript-version-no-warning.test.ts", + "test/e2e/typescript-version-warning/typescript-version-warning.test.ts" + ], + "skipped": [ + { + "file": "test/e2e/proxy-request-with-middleware/test/index.test.ts", + "reason": "Hard-coded localhost URL" + }, + { + "file": "test/e2e/app-dir/ppr/**/*", + "reason": "Relies on local test server" + }, + { + "file": "test/e2e/app-dir/ppr-*/**/*", + "reason": "Relies on local test server" + }, + { + "file": "test/e2e/app-dir/app-prefetch-false-loading/app-prefetch-false-loading.test.ts", + "reason": "Uses CLI output" + }, + { + "file": "test/e2e/cancel-request/stream-cancel.test.ts", + "reason": "Doesn't work for HTTPS URLs" + }, + { + "file": "test/e2e/edge-pages-support/edge-document.test.ts", + "reason": "Tries to patch deployed files" + }, + { + "file": "test/e2e/third-parties/index.test.ts", + "reason": "npm install doesn't work in this repo" + }, + { + "file": "test/e2e/swc-warnings/index.test.ts", + "reason": "Uses CLI output" + }, + { + "file": "test/e2e/repeated-forward-slashes-error/repeated-forward-slashes-error.test.ts", + "reason": "Uses CLI output" + }, + { + "file": "test/e2e/app-dir/x-forwarded-headers/x-forwarded-headers.test.ts", + "reason": "Whitespace mismatch" + }, + { + "file": "test/e2e/app-dir/third-parties/basic.test.ts", + "reason": "npm install doesn't work in this repo" + }, + { + "file": "test/e2e/app-dir/app/vercel-speed-insights.test.ts", + "reason": "Vercel-specific" + }, + { + "file": "test/e2e/app-dir/headers-static-bailout/headers-static-bailout.test.ts", + "reason": "Tries to patch deployed files" + }, + { + "file": "test/e2e/app-dir/app/useReportWebVitals.test.ts", + "reason": "Vercel-specific" + }, + { + "file": "test/e2e/app-dir/app-static/app-static-custom-handler.test.ts", + "reason": "Test not compatible" + }, + { + "file": "test/e2e/app-dir/missing-suspense-with-csr-bailout/missing-suspense-with-csr-bailout.test.ts", + "reason": "Tries to patch deployed files" + }, + { + "file": "test/e2e/module-layer/module-layer.test.ts", + "reason": "Tries to patch deployed files" + }, + { + "file": "test/e2e/next-image/next-image-proxy.test.ts", + "reason": "Hard-coded localhost URL" + }, + { + "file": "test/e2e/app-dir/next-image/next-image-proxy.test.ts", + "reason": "Hard-coded localhost URL" + }, + { + "file": "test/e2e/edge-can-use-wasm-files/index.test.ts", + "reason": "Uses invalid WASM syntax" + }, + { + "file": "test/e2e/i18n-data-route/i18n-data-route.test.ts", + "reason": "Expected behaviour does not match next start" + }, + { + "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"] + }, + { + "file": "test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts", + "reason": "Tries to patch deployed files", + "tests": [ + "parallel-routes-and-interception parallel routes should gracefully handle when two page segments match the `children` parallel slot" + ] + }, + { + "file": "test/e2e/app-dir/rsc-basic/rsc-basic.test.ts", + "reason": "Tries to patch deployed files", + "tests": [ + "app dir - rsc basics react@experimental should opt into the react@experimental when enabling ppr", + "app dir - rsc basics react@experimental should opt into the react@experimental when enabling taint" + ] + }, + { + "file": "test/e2e/skip-trailing-slash-redirect/index.test.ts", + "reason": "Header whitespace mismatch", + "tests": [ + "skip-trailing-slash-redirect should merge cookies from middleware and API routes correctly", + "skip-trailing-slash-redirect should merge cookies from middleware and edge API routes correctly", + "skip-trailing-slash-redirect should handle external rewrite correctly /chained-rewrite-ssr", + "skip-trailing-slash-redirect should handle external rewrite correctly /chained-rewrite-static", + "skip-trailing-slash-redirect should handle external rewrite correctly /chained-rewrite-ssg" + ] + }, + { + "file": "test/e2e/module-layer/index.test.ts", + "reason": "Tries to patch deployed files", + "tests": [ + "module layer no server-only in server targets should render routes marked with restriction marks without errors", + "module layer with server-only in server targets should render routes marked with restriction marks without errors" + ] + }, + { + "file": "test/e2e/getserversideprops/test/index.test.ts", + "reason": "Header whitespace mismatch", + "tests": [ + "getServerSideProps should set default caching header", + "getServerSideProps should respect custom caching header" + ] + }, + { + "file": "test/e2e/app-dir/metadata-dynamic-routes/index.test.ts", + "reason": "Header whitespace mismatch", + "tests": [ + "app dir - metadata dynamic routes text routes should handle robots.[ext] dynamic routes", + "app dir - metadata dynamic routes text routes should handle sitemap.[ext] dynamic routes", + "app dir - metadata dynamic routes social image routes should handle manifest.[ext] dynamic routes", + "app dir - metadata dynamic routes social image routes should render og image with opengraph-image dynamic routes", + "app dir - metadata dynamic routes social image routes should render og image with twitter-image dynamic routes", + "app dir - metadata dynamic routes icon image routes should render icon with dynamic routes", + "app dir - metadata dynamic routes icon image routes should render apple icon with dynamic routes", + "app dir - metadata dynamic routes should inject dynamic metadata properly to head", + "app dir - metadata dynamic routes should use localhost for local prod and fallback to deployment url when metadataBase is falsy" + ] + }, + { + "file": "test/e2e/app-dir/metadata/metadata.test.ts", + "reason": "Hard-coded Vercel URL", + "tests": [ + "app dir - metadata opengraph should pick up opengraph-image and twitter-image as static metadata files", + "app dir - metadata static routes should have /favicon.ico as route", + "app dir - metadata static routes should have icons as route" + ] + }, + { + "file": "test/e2e/basepath.test.ts", + "reason": "Hard-coded Vercel error message", + "tests": [ + "basePath should not update URL for a 404", + "basePath should handle 404 urls that start with basePath", + "basePath should show 404 for page not under the /docs prefix" + ] + }, + { + "file": "test/e2e/app-dir/app/index.test.ts", + "reason": "Whitespace mismatch", + "tests": [ + "app dir - basic should return the `vary` header from edge runtime", + "app dir - basic should return the `vary` header from pages for flight requests" + ] + }, + { + "file": "test/e2e/app-dir/conflicting-page-segments/conflicting-page-segments.test.ts", + "reason": "Uses CLI output", + "tests": [ + "conflicting-page-segments should throw an error when a route groups causes a conflict with a parallel segment" + ] + }, + { + "file": "test/e2e/app-dir/actions-navigation/index.test.ts", + "reason": "Uses CLI output", + "tests": [ + "app-dir action handling should handle actions correctly after following a relative link" + ] + }, + { + "file": "test/e2e/middleware-general/test/index.test.ts", + "reason": "Uses CLI output", + "tests": [ + "Middleware Runtime with i18n should redirect the same for direct visit and client-transition", + "Middleware Runtime without i18n should redirect the same for direct visit and client-transition", + { + "name": "Middleware Runtime with i18n should allow to abort a fetch request", + "reason": "Mismatched wording in runtime error message" + }, + { + "name": "Middleware Runtime without i18n should allow to abort a fetch request", + "reason": "Mismatched wording in runtime error message" + }, + { + "name": "Middleware Runtime with i18n with i18n allows to access env variables", + "reason": "Disabled for deploy tests" + }, + { + "name": "Middleware Runtime without i18n with i18n allows to access env variables", + "reason": "Disabled for deploy tests" + } + ] + }, + { + "file": "test/e2e/prerender.test.ts", + "reason": "Header whitespace mismatch", + "tests": [ + "Prerender should handle on-demand revalidate for fallback: blocking", + "Prerender should use correct caching headers for a revalidate page", + "Prerender should use correct caching headers for a no-revalidate page", + { + "name": "Prerender should show warning when large amount of page data is returned", + "reason": "Uses CLI output" + } + ] + }, + { + "file": "test/e2e/app-dir/app-routes/app-custom-route-base-path.test.ts", + "reason": "Uses CLI output", + "tests": [ + "app-custom-routes no response returned should print an error when no response is returned", + "app-custom-routes error conditions responds with 400 (Bad Request) when the requested method is not a valid HTTP method" + ] + }, + { + "file": "test/e2e/app-dir/app-routes/app-custom-routes.test.ts", + "reason": "Uses CLI output", + "tests": [ + "app-custom-routes no response returned should print an error when no response is returned", + "app-custom-routes error conditions responds with 400 (Bad Request) when the requested method is not a valid HTTP method" + ] + }, + { + "file": "test/e2e/app-dir/actions/app-action.test.ts", + "reason": "Uses CLI output", + "tests": [ + "app-dir action handling should log a warning when a server action is not found but an id is provided", + "app-dir action handling should work with interception routes" + ] + } + ], + "failures": [ + { + "name": "Middleware Runtime without i18n should trigger middleware for data requests", + "reason": "Requests for page data on pages router sites with middleware return HTML unless x-nextjs-data header is set", + "link": "https://github.com/netlify/next-runtime-minimal/issues/380" + }, + { + "name": "Middleware Runtime with i18n should trigger middleware for data requests", + "reason": "Requests for page data on pages router sites with middleware return HTML unless x-nextjs-data header is set", + "link": "https://github.com/netlify/next-runtime-minimal/issues/380" + }, + { + "name": "app dir - basic bootstrap scripts should successfully bootstrap even when using CSP", + "reason": "Nonce not automatically set in script tags when using CSP", + "link": "https://github.com/netlify/next-runtime-minimal/issues/381" + }, + { + "name": "Middleware Runtime with i18n should validate & parse request url from a dynamic route with params", + "reason": "Middleware in sites with i18n generating incorrect request.url pathname", + "link": "https://github.com/netlify/next-runtime-minimal/issues/382" + }, + { + "name": "Middleware Runtime with i18n should validate & parse request url from a dynamic route with params and no query", + "reason": "Middleware in sites with i18n generating incorrect request.url pathname", + "link": "https://github.com/netlify/next-runtime-minimal/issues/382" + }, + { + "name": "i18n-ignore-rewrite-source-locale with basepath get public file by skipping locale in rewrite, locale: ", + "reason": "Middleware on sites with i18n cannot rewrite to static files", + "link": "https://github.com/netlify/next-runtime-minimal/issues/383" + }, + { + "name": "i18n-ignore-rewrite-source-locale with basepath get public file by skipping locale in rewrite, locale: /en", + "reason": "Middleware on sites with i18n cannot rewrite to static files", + "link": "https://github.com/netlify/next-runtime-minimal/issues/383" + }, + { + "name": "i18n-ignore-rewrite-source-locale with basepath get public file by skipping locale in rewrite, locale: /nl", + "reason": "Middleware on sites with i18n cannot rewrite to static files", + "link": "https://github.com/netlify/next-runtime-minimal/issues/383" + }, + { + "name": "i18n-ignore-rewrite-source-locale with basepath get public file by skipping locale in rewrite, locale: /sv", + "reason": "Middleware on sites with i18n cannot rewrite to static files", + "link": "https://github.com/netlify/next-runtime-minimal/issues/383" + }, + { + "name": "i18n-ignore-rewrite-source-locale get public file by skipping locale in rewrite, locale: ", + "reason": "Middleware on sites with i18n cannot rewrite to static files", + "link": "https://github.com/netlify/next-runtime-minimal/issues/383" + }, + { + "name": "i18n-ignore-rewrite-source-locale get public file by skipping locale in rewrite, locale: /en", + "reason": "Middleware on sites with i18n cannot rewrite to static files", + "link": "https://github.com/netlify/next-runtime-minimal/issues/383" + }, + { + "name": "i18n-ignore-rewrite-source-locale get public file by skipping locale in rewrite, locale: /nl", + "reason": "Middleware on sites with i18n cannot rewrite to static files", + "link": "https://github.com/netlify/next-runtime-minimal/issues/383" + }, + { + "name": "i18n-ignore-rewrite-source-locale get public file by skipping locale in rewrite, locale: /sv", + "reason": "Middleware on sites with i18n cannot rewrite to static files", + "link": "https://github.com/netlify/next-runtime-minimal/issues/383" + }, + { + "name": "Middleware Runtime trailing slash should keep non data requests in their original shape", + "reason": "Middleware should not add trailing slashes to non-data requests in static dir", + "link": "https://github.com/netlify/next-runtime-minimal/issues/385" + }, + { + "name": "Middleware Redirect should implement internal redirects", + "reason": "Pages router middleware should return 302 status for redirected data requests", + "link": "https://github.com/netlify/next-runtime-minimal/issues/386" + }, + { + "name": "Middleware Redirect /fr should implement internal redirects", + "reason": "Pages router middleware should return 302 status for redirected data requests", + "link": "https://github.com/netlify/next-runtime-minimal/issues/386" + }, + { + "name": "Middleware Redirect should redirect to api route with locale", + "reason": "Pages router middleware should return 302 status for redirected data requests", + "link": "https://github.com/netlify/next-runtime-minimal/issues/386" + }, + { + "name": "Middleware Redirect /fr should redirect to api route with locale", + "reason": "Pages router middleware should return 302 status for redirected data requests", + "link": "https://github.com/netlify/next-runtime-minimal/issues/386" + }, + { + "name": "getServerSideProps should handle throw ENOENT correctly", + "reason": "Server error pages return encoded data without content-encoding header if accept-encoding is gzip", + "link": "https://github.com/netlify/next-runtime-minimal/issues/387" + }, + { + "name": "app-custom-routes works with generateStaticParams correctly responds correctly on /static/three/data.json", + "reason": "First request for generateStaticParams fallback route handler returns base64-encoded content", + "link": "https://github.com/netlify/next-runtime-minimal/issues/388" + }, + { + "name": "app-custom-routes works with generateStaticParams correctly revalidates correctly on /revalidate-1/three/data.json", + "reason": "First request for generateStaticParams fallback route handler returns base64-encoded content", + "link": "https://github.com/netlify/next-runtime-minimal/issues/388" + } + ] +} diff --git a/tools/deno/junit2json.ts b/tools/deno/junit2json.ts new file mode 100644 index 0000000000..0337b5d09f --- /dev/null +++ b/tools/deno/junit2json.ts @@ -0,0 +1,182 @@ +import { expandGlob } from 'https://deno.land/std/fs/mod.ts' +import { parse } from 'https://deno.land/x/xml/mod.ts' + +import testConfig from '../../tests/test-config.json' with { type: 'json' } + +interface JUnitTestCase { + '@classname': string + '@name': string + '@time': number + '@file': string + failure?: string +} + +interface JUnitTestSuite { + '@name': string + '@errors': number + '@failures': number + '@skipped': number + '@timestamp': string + '@time': number + '@tests': number + testcase: JUnitTestCase[] +} + +interface JUnitTestSuites { + '@name': string + '@tests': number + '@failures': number + '@errors': number + '@time': number + testsuite: JUnitTestSuite[] +} + +interface TestSuite { + name: string + file: string + passed: number + failed: number + skipped: number + testCases: TestCase[] +} + +interface SkippedTestSuite { + file: string + reason: string + skipped: true +} + +interface TestCase { + name: string + status: 'passed' | 'failed' | 'skipped' + reason?: string + link?: string +} + +async function parseXMLFile(filePath: string): Promise<{ testsuites: JUnitTestSuites }> { + const xmlContent = await Deno.readTextFile(filePath) + return parse(xmlContent) as unknown as { testsuites: JUnitTestSuites } +} + +const testCount = { + failed: 0, + skipped: 0, + passed: 0, +} + +function junitToJson(xmlData: { + testsuites: JUnitTestSuites +}): Array { + if (!xmlData.testsuites) { + return [] + } + + const testSuites = Array.isArray(xmlData.testsuites.testsuite) + ? xmlData.testsuites.testsuite + : [xmlData.testsuites.testsuite] + + return testSuites.map((suite) => { + const { '@tests': tests, '@failures': failed, '@name': name } = suite + + const passed = tests - failed - suite['@skipped'] + + const testCases = Array.isArray(suite.testcase) ? suite.testcase : [suite.testcase] + + const testSuite: TestSuite = { + name, + file: testCases[0]?.['@file'], + passed, + failed, + skipped: 0, + testCases: [], + } + const skippedTests = testConfig.skipped.find( + (skippedTest) => skippedTest.file === testSuite.file, + ) + + testSuite.skipped = skippedTests?.tests?.length ?? 0 + + for (const testCase of testCases) { + if ('skipped' in testCase) { + continue + } + const status = testCase.failure ? 'failed' : 'passed' + testCount[status]++ + const test: TestCase = { + name: testCase['@name'], + status, + } + if (status === 'failed') { + const failure = testConfig.failures.find((conf) => conf.name === test.name) + if (failure) { + test.reason = failure.reason + test.link = failure.link + } + } + testSuite.testCases.push(test) + } + + if (skippedTests?.tests) { + testCount.skipped += skippedTests.tests.length + testSuite.testCases.push( + ...skippedTests.tests.map((test): TestCase => { + if (typeof test === 'string') { + return { + name: test, + status: 'skipped', + reason: skippedTests.reason, + } + } + return { + name: test.name, + status: 'skipped', + reason: test.reason, + } + }), + ) + } + return testSuite + }) +} + +async function processJUnitFiles( + directoryPath: string, +): Promise> { + const results = [] + for await (const file of expandGlob(`${directoryPath}/**/*.xml`)) { + const xmlData = await parseXMLFile(file.path) + results.push(...junitToJson(xmlData)) + } + const skippedSuites = testConfig.skipped.map( + ({ file, reason }): SkippedTestSuite => ({ + file, + reason, + skipped: true, + }), + ) + + testCount.skipped += skippedSuites.length + results.push(...skippedSuites) + return results +} + +// Get the directory path from the command-line arguments +const directoryPath = Deno.args[0] + +// Check if the directory path is provided +if (!directoryPath) { + console.error('Please provide a directory path.') + Deno.exit(1) +} + +// Process the JUnit files in the provided directory +const results = await processJUnitFiles(directoryPath) + +const testResults = { + ...testCount, + total: testCount.passed + testCount.failed + testCount.skipped, + passRate: ((testCount.passed / (testCount.passed + testCount.failed)) * 100).toFixed(2) + '%', + results, +} + +console.log(JSON.stringify(testResults, undefined, 2)) From 035d0cc6e025ed851721a46a385f879eba9472c0 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 3 Apr 2024 11:26:50 +0200 Subject: [PATCH 2/9] chore: use separate GHA job for smoke tests (#393) * chore: use separate GHA job for smoke tests * chore: upgrade vitest * chore: install deno for smoke tests job * chore: actually run smoke tests in smoke tests job --------- Co-authored-by: Rob Stanford --- .github/workflows/run-tests.yml | 52 +- package-lock.json | 1044 ++++++++----------------------- package.json | 17 +- vitest.config.ts | 12 +- vitest.workspace.ts | 25 + 5 files changed, 327 insertions(+), 823 deletions(-) create mode 100644 vitest.workspace.ts diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index d50752a5c9..33126f12d5 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -95,7 +95,7 @@ jobs: echo "version=$RESOLVED_VERSION" >> $GITHUB_OUTPUT echo "Resolved Next.js version for 'next@${{ matrix.version }}' is '$RESOLVED_VERSION'" - name: Run Playwright tests - run: npm run e2e:ci -- --shard=${{ matrix.shard }}/4 + run: npm run test:ci:e2e -- --shard=${{ matrix.shard }}/4 env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_TOKEN }} NEXT_VERSION: ${{ matrix.version }} @@ -137,12 +137,6 @@ jobs: run: npm ci - name: 'Build' run: npm run build - - name: 'Netlify Login' - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_TOKEN }} - run: | - npm i -g netlify-cli - netlify login - name: Resolve Next.js version id: resolve-next-version shell: bash @@ -172,13 +166,53 @@ jobs: env: NEXT_VERSION: ${{ matrix.version }} NEXT_RESOLVED_VERSION: ${{ steps.resolve-next-version.outputs.version }} - - name: 'Test' - run: npm run test:ci -- --shard=${{ matrix.shard }}/8 + - name: 'Unit and integration tests' + run: npm run test:ci:unit-and-integration -- --shard=${{ matrix.shard }}/8 env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_TOKEN }} NEXT_VERSION: ${{ matrix.version }} NEXT_RESOLVED_VERSION: ${{ steps.resolve-next-version.outputs.version }} TEMP: ${{ github.workspace }}/.. + + smoke: + if: always() + needs: setup + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + version: ${{ fromJson(needs.setup.outputs.matrix) }} + steps: + - uses: actions/checkout@v4 + - name: 'Install Node' + uses: actions/setup-node@v4 + with: + node-version: '18.x' + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: setup pnpm/yarn + run: corepack enable + shell: bash + - 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 + - name: 'Install dependencies' + run: npm ci + - name: 'Build' + run: npm run build + - name: 'Netlify Login' + env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_TOKEN }} + run: | + npm i -g netlify-cli + netlify login + - name: 'Smoke tests' + run: npm run test:ci:smoke + env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_TOKEN }} + merge-reports: if: always() needs: [setup,e2e] diff --git a/package-lock.json b/package-lock.json index d94d986ff7..4456a21a00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "typescript": "^5.1.6", "unionfs": "^4.5.1", "uuid": "^9.0.1", - "vitest": "^1.2.2" + "vitest": "^1.4.0" }, "engines": { "node": ">=18.0.0" @@ -5516,9 +5516,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.10.0.tgz", - "integrity": "sha512-/MeDQmcD96nVoRumKUljsYOLqfv1YFJps+0pTrb2Z9Nl/w5qNUysMaWQsrd1mvAlNT4yza1iVyIu4Q4AgF6V3A==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz", + "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==", "cpu": [ "arm" ], @@ -5529,9 +5529,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.10.0.tgz", - "integrity": "sha512-lvu0jK97mZDJdpZKDnZI93I0Om8lSDaiPx3OiCk0RXn3E8CMPJNS/wxjAvSJJzhhZpfjXsjLWL8LnS6qET4VNQ==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz", + "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==", "cpu": [ "arm64" ], @@ -5542,9 +5542,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.10.0.tgz", - "integrity": "sha512-uFpayx8I8tyOvDkD7X6n0PriDRWxcqEjqgtlxnUA/G9oS93ur9aZ8c8BEpzFmsed1TH5WZNG5IONB8IiW90TQg==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz", + "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==", "cpu": [ "arm64" ], @@ -5555,9 +5555,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.10.0.tgz", - "integrity": "sha512-nIdCX03qFKoR/MwQegQBK+qZoSpO3LESurVAC6s6jazLA1Mpmgzo3Nj3H1vydXp/JM29bkCiuF7tDuToj4+U9Q==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz", + "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==", "cpu": [ "x64" ], @@ -5568,9 +5568,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.10.0.tgz", - "integrity": "sha512-Fz7a+y5sYhYZMQFRkOyCs4PLhICAnxRX/GnWYReaAoruUzuRtcf+Qnw+T0CoAWbHCuz2gBUwmWnUgQ67fb3FYw==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz", + "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==", "cpu": [ "arm" ], @@ -5581,9 +5581,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.10.0.tgz", - "integrity": "sha512-yPtF9jIix88orwfTi0lJiqINnlWo6p93MtZEoaehZnmCzEmLL0eqjA3eGVeyQhMtxdV+Mlsgfwhh0+M/k1/V7Q==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz", + "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==", "cpu": [ "arm64" ], @@ -5594,9 +5594,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.10.0.tgz", - "integrity": "sha512-9GW9yA30ib+vfFiwjX+N7PnjTnCMiUffhWj4vkG4ukYv1kJ4T9gHNg8zw+ChsOccM27G9yXrEtMScf1LaCuoWQ==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz", + "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==", "cpu": [ "arm64" ], @@ -5606,10 +5606,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz", + "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==", + "cpu": [ + "ppc64le" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.10.0.tgz", - "integrity": "sha512-X1ES+V4bMq2ws5fF4zHornxebNxMXye0ZZjUrzOrf7UMx1d6wMQtfcchZ8SqUnQPPHdOyOLW6fTcUiFgHFadRA==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz", + "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==", "cpu": [ "riscv64" ], @@ -5619,10 +5632,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz", + "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.10.0.tgz", - "integrity": "sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz", + "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==", "cpu": [ "x64" ], @@ -5633,9 +5659,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.10.0.tgz", - "integrity": "sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz", + "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==", "cpu": [ "x64" ], @@ -5646,9 +5672,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.10.0.tgz", - "integrity": "sha512-NrR6667wlUfP0BHaEIKgYM/2va+Oj+RjZSASbBMnszM9k+1AmliRjHc3lJIiOehtSSjqYiO7R6KLNrWOX+YNSQ==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz", + "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==", "cpu": [ "arm64" ], @@ -5659,9 +5685,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.10.0.tgz", - "integrity": "sha512-FV0Tpt84LPYDduIDcXvEC7HKtyXxdvhdAOvOeWMWbQNulxViH2O07QXkT/FffX4FqEI02jEbCJbr+YcuKdyyMg==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz", + "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==", "cpu": [ "ia32" ], @@ -5672,9 +5698,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.10.0.tgz", - "integrity": "sha512-OZoJd+o5TaTSQeFFQ6WjFCiltiYVjIdsXxwu/XZ8qRpsvMQr4UsVrE5UyT9RIvsnuF47DqkJKhhVZ2Q9YW9IpQ==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz", + "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==", "cpu": [ "x64" ], @@ -6202,13 +6228,13 @@ } }, "node_modules/@vitest/expect": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz", - "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", + "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", "dev": true, "dependencies": { - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/spy": "1.4.0", + "@vitest/utils": "1.4.0", "chai": "^4.3.10" }, "funding": { @@ -6216,12 +6242,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz", - "integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", + "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", "dev": true, "dependencies": { - "@vitest/utils": "1.3.1", + "@vitest/utils": "1.4.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -6245,9 +6271,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz", - "integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", + "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -6291,9 +6317,9 @@ "dev": true }, "node_modules/@vitest/spy": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz", - "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", + "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -6303,9 +6329,9 @@ } }, "node_modules/@vitest/utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz", - "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", + "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -15818,9 +15844,9 @@ } }, "node_modules/rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz", + "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -15833,19 +15859,21 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "@rollup/rollup-android-arm-eabi": "4.13.2", + "@rollup/rollup-android-arm64": "4.13.2", + "@rollup/rollup-darwin-arm64": "4.13.2", + "@rollup/rollup-darwin-x64": "4.13.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.2", + "@rollup/rollup-linux-arm64-gnu": "4.13.2", + "@rollup/rollup-linux-arm64-musl": "4.13.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2", + "@rollup/rollup-linux-riscv64-gnu": "4.13.2", + "@rollup/rollup-linux-s390x-gnu": "4.13.2", + "@rollup/rollup-linux-x64-gnu": "4.13.2", + "@rollup/rollup-linux-x64-musl": "4.13.2", + "@rollup/rollup-win32-arm64-msvc": "4.13.2", + "@rollup/rollup-win32-ia32-msvc": "4.13.2", + "@rollup/rollup-win32-x64-msvc": "4.13.2", "fsevents": "~2.3.2" } }, @@ -16131,9 +16159,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -17246,14 +17274,14 @@ } }, "node_modules/vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz", + "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -17301,9 +17329,9 @@ } }, "node_modules/vite-node": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz", - "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", + "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -17322,416 +17350,10 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" - } - }, "node_modules/vite/node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -17750,23 +17372,23 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/vitest": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz", - "integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", + "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", "dev": true, "dependencies": { - "@vitest/expect": "1.3.1", - "@vitest/runner": "1.3.1", - "@vitest/snapshot": "1.3.1", - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/expect": "1.4.0", + "@vitest/runner": "1.4.0", + "@vitest/snapshot": "1.4.0", + "@vitest/spy": "1.4.0", + "@vitest/utils": "1.4.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -17780,7 +17402,7 @@ "tinybench": "^2.5.1", "tinypool": "^0.8.2", "vite": "^5.0.0", - "vite-node": "1.3.1", + "vite-node": "1.4.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -17795,8 +17417,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.3.1", - "@vitest/ui": "1.3.1", + "@vitest/browser": "1.4.0", + "@vitest/ui": "1.4.0", "happy-dom": "*", "jsdom": "*" }, @@ -22165,93 +21787,107 @@ } }, "@rollup/rollup-android-arm-eabi": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.10.0.tgz", - "integrity": "sha512-/MeDQmcD96nVoRumKUljsYOLqfv1YFJps+0pTrb2Z9Nl/w5qNUysMaWQsrd1mvAlNT4yza1iVyIu4Q4AgF6V3A==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.2.tgz", + "integrity": "sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==", "dev": true, "optional": true }, "@rollup/rollup-android-arm64": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.10.0.tgz", - "integrity": "sha512-lvu0jK97mZDJdpZKDnZI93I0Om8lSDaiPx3OiCk0RXn3E8CMPJNS/wxjAvSJJzhhZpfjXsjLWL8LnS6qET4VNQ==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.2.tgz", + "integrity": "sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==", "dev": true, "optional": true }, "@rollup/rollup-darwin-arm64": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.10.0.tgz", - "integrity": "sha512-uFpayx8I8tyOvDkD7X6n0PriDRWxcqEjqgtlxnUA/G9oS93ur9aZ8c8BEpzFmsed1TH5WZNG5IONB8IiW90TQg==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.2.tgz", + "integrity": "sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==", "dev": true, "optional": true }, "@rollup/rollup-darwin-x64": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.10.0.tgz", - "integrity": "sha512-nIdCX03qFKoR/MwQegQBK+qZoSpO3LESurVAC6s6jazLA1Mpmgzo3Nj3H1vydXp/JM29bkCiuF7tDuToj4+U9Q==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.2.tgz", + "integrity": "sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.10.0.tgz", - "integrity": "sha512-Fz7a+y5sYhYZMQFRkOyCs4PLhICAnxRX/GnWYReaAoruUzuRtcf+Qnw+T0CoAWbHCuz2gBUwmWnUgQ67fb3FYw==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.2.tgz", + "integrity": "sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.10.0.tgz", - "integrity": "sha512-yPtF9jIix88orwfTi0lJiqINnlWo6p93MtZEoaehZnmCzEmLL0eqjA3eGVeyQhMtxdV+Mlsgfwhh0+M/k1/V7Q==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.2.tgz", + "integrity": "sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-musl": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.10.0.tgz", - "integrity": "sha512-9GW9yA30ib+vfFiwjX+N7PnjTnCMiUffhWj4vkG4ukYv1kJ4T9gHNg8zw+ChsOccM27G9yXrEtMScf1LaCuoWQ==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.2.tgz", + "integrity": "sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.13.2.tgz", + "integrity": "sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.10.0.tgz", - "integrity": "sha512-X1ES+V4bMq2ws5fF4zHornxebNxMXye0ZZjUrzOrf7UMx1d6wMQtfcchZ8SqUnQPPHdOyOLW6fTcUiFgHFadRA==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.2.tgz", + "integrity": "sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.13.2.tgz", + "integrity": "sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-gnu": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.10.0.tgz", - "integrity": "sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.2.tgz", + "integrity": "sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-musl": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.10.0.tgz", - "integrity": "sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.2.tgz", + "integrity": "sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==", "dev": true, "optional": true }, "@rollup/rollup-win32-arm64-msvc": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.10.0.tgz", - "integrity": "sha512-NrR6667wlUfP0BHaEIKgYM/2va+Oj+RjZSASbBMnszM9k+1AmliRjHc3lJIiOehtSSjqYiO7R6KLNrWOX+YNSQ==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.2.tgz", + "integrity": "sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==", "dev": true, "optional": true }, "@rollup/rollup-win32-ia32-msvc": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.10.0.tgz", - "integrity": "sha512-FV0Tpt84LPYDduIDcXvEC7HKtyXxdvhdAOvOeWMWbQNulxViH2O07QXkT/FffX4FqEI02jEbCJbr+YcuKdyyMg==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.2.tgz", + "integrity": "sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-msvc": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.10.0.tgz", - "integrity": "sha512-OZoJd+o5TaTSQeFFQ6WjFCiltiYVjIdsXxwu/XZ8qRpsvMQr4UsVrE5UyT9RIvsnuF47DqkJKhhVZ2Q9YW9IpQ==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.2.tgz", + "integrity": "sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==", "dev": true, "optional": true }, @@ -22646,23 +22282,23 @@ } }, "@vitest/expect": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz", - "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", + "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", "dev": true, "requires": { - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/spy": "1.4.0", + "@vitest/utils": "1.4.0", "chai": "^4.3.10" } }, "@vitest/runner": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz", - "integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", + "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", "dev": true, "requires": { - "@vitest/utils": "1.3.1", + "@vitest/utils": "1.4.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -22679,9 +22315,9 @@ } }, "@vitest/snapshot": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz", - "integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", + "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", "dev": true, "requires": { "magic-string": "^0.30.5", @@ -22715,18 +22351,18 @@ } }, "@vitest/spy": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz", - "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", + "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", "dev": true, "requires": { "tinyspy": "^2.2.0" } }, "@vitest/utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz", - "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", + "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", "dev": true, "requires": { "diff-sequences": "^29.6.3", @@ -29514,24 +29150,26 @@ } }, "rollup": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", - "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.10.0", - "@rollup/rollup-android-arm64": "4.10.0", - "@rollup/rollup-darwin-arm64": "4.10.0", - "@rollup/rollup-darwin-x64": "4.10.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", - "@rollup/rollup-linux-arm64-gnu": "4.10.0", - "@rollup/rollup-linux-arm64-musl": "4.10.0", - "@rollup/rollup-linux-riscv64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-gnu": "4.10.0", - "@rollup/rollup-linux-x64-musl": "4.10.0", - "@rollup/rollup-win32-arm64-msvc": "4.10.0", - "@rollup/rollup-win32-ia32-msvc": "4.10.0", - "@rollup/rollup-win32-x64-msvc": "4.10.0", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz", + "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.13.2", + "@rollup/rollup-android-arm64": "4.13.2", + "@rollup/rollup-darwin-arm64": "4.13.2", + "@rollup/rollup-darwin-x64": "4.13.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.2", + "@rollup/rollup-linux-arm64-gnu": "4.13.2", + "@rollup/rollup-linux-arm64-musl": "4.13.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.13.2", + "@rollup/rollup-linux-riscv64-gnu": "4.13.2", + "@rollup/rollup-linux-s390x-gnu": "4.13.2", + "@rollup/rollup-linux-x64-gnu": "4.13.2", + "@rollup/rollup-linux-x64-musl": "4.13.2", + "@rollup/rollup-win32-arm64-msvc": "4.13.2", + "@rollup/rollup-win32-ia32-msvc": "4.13.2", + "@rollup/rollup-win32-x64-msvc": "4.13.2", "@types/estree": "1.0.5", "fsevents": "~2.3.2" } @@ -29740,9 +29378,9 @@ "dev": true }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true }, "source-map-support": { @@ -30579,226 +30217,34 @@ } }, "vite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", - "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz", + "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==", "dev": true, "requires": { - "esbuild": "^0.19.3", + "esbuild": "^0.20.1", "fsevents": "~2.3.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "postcss": "^8.4.38", + "rollup": "^4.13.0" }, "dependencies": { - "@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "dev": true, - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "dev": true, - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "dev": true, - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "dev": true, - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "dev": true, - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "dev": true, - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "dev": true, - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "dev": true, - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "dev": true, - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "dev": true, - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "dev": true, - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "dev": true, - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "dev": true, - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "dev": true, - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", - "dev": true, - "optional": true - }, - "esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "dev": true, - "requires": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" - } - }, "postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "requires": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } } } }, "vite-node": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz", - "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", + "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", "dev": true, "requires": { "cac": "^6.7.14", @@ -30809,16 +30255,16 @@ } }, "vitest": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz", - "integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", + "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", "dev": true, "requires": { - "@vitest/expect": "1.3.1", - "@vitest/runner": "1.3.1", - "@vitest/snapshot": "1.3.1", - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/expect": "1.4.0", + "@vitest/runner": "1.4.0", + "@vitest/snapshot": "1.4.0", + "@vitest/spy": "1.4.0", + "@vitest/utils": "1.4.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -30832,7 +30278,7 @@ "tinybench": "^2.5.1", "tinypool": "^0.8.2", "vite": "^5.0.0", - "vite-node": "1.3.1", + "vite-node": "1.4.0", "why-is-node-running": "^2.2.2" } }, diff --git a/package.json b/package.json index 188a42b1f5..d25524958f 100644 --- a/package.json +++ b/package.json @@ -15,17 +15,22 @@ "scripts": { "prepack": "clean-package", "postpack": "clean-package restore", - "pretest": "npm run build && node tests/prepare.mjs", + "pretest": "npm run pretest:integration", + "pretest:integration": "npm run build && node tests/prepare.mjs", "build": "node ./tools/build.js", "build:watch": "node ./tools/build.js --watch", "lint": "eslint --cache --format=codeframe --max-warnings=0 --ext .ts,.cts,.js src", "format:fix": "prettier --write .", "format:check": "prettier --check .", "test": "vitest", - "test:ci": "vitest run --reporter=default --retry=3", - "typecheck": "tsc --noEmit", - "e2e": "playwright test", - "e2e:ci": "playwright test" + "test:unit": "vitest run --project unit", + "test:integration": "vitest run --project integration", + "test:smoke": "vitest run --project smoke", + "test:e2e": "playwright test", + "test:ci:unit-and-integration": "vitest run --reporter=default --retry=3 --project=unit --project=integration", + "test:ci:smoke": "vitest run --reporter=default --retry=3 --project=smoke", + "test:ci:e2e": "playwright test", + "typecheck": "tsc --noEmit" }, "repository": { "type": "git", @@ -85,7 +90,7 @@ "typescript": "^5.1.6", "unionfs": "^4.5.1", "uuid": "^9.0.1", - "vitest": "^1.2.2" + "vitest": "^1.4.0" }, "clean-package": { "indent": 2, diff --git a/vitest.config.ts b/vitest.config.ts index 54e601b49b..c16ca3e575 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,5 +1,4 @@ import { join, relative } from 'node:path' -import { platform } from 'node:process' import { defineConfig } from 'vitest/config' import { BaseSequencer, WorkspaceSpec } from 'vitest/node' @@ -12,11 +11,8 @@ const RUN_ISOLATED = new Set([ join('tests', 'integration', 'revalidate-path.test.ts'), join('tests', 'integration', 'cache-handler.test.ts'), join('tests', 'integration', 'edge-handler.test.ts'), - join('tests', 'smoke', 'deploy.test.ts'), ]) -const SKIP = new Set(platform === 'win32' ? [join('tests', 'smoke', 'deploy.test.ts')] : []) - class Sequencer extends BaseSequencer { async shard(projects: WorkspaceSpec[]): Promise { const { @@ -36,12 +32,10 @@ Increasing the node count of the sharding to --shard 1/${RUN_ISOLATED.size}`, ) } - const notSkipped = projects.filter((project) => !SKIP.has(relative(process.cwd(), project[1]))) - - const specialTests = notSkipped.filter((project) => + const specialTests = projects.filter((project) => RUN_ISOLATED.has(relative(process.cwd(), project[1])), ) - const regularTests = notSkipped.filter( + const regularTests = projects.filter( (project) => !RUN_ISOLATED.has(relative(process.cwd(), project[1])), ) @@ -66,7 +60,7 @@ Increasing the node count of the sharding to --shard 1/${RUN_ISOLATED.size}`, export default defineConfig({ root: '.', test: { - include: ['{tests/integration,tests/smoke,src}/**/*.test.ts'], + include: [], globals: true, restoreMocks: true, clearMocks: true, diff --git a/vitest.workspace.ts b/vitest.workspace.ts new file mode 100644 index 0000000000..5418c5ecef --- /dev/null +++ b/vitest.workspace.ts @@ -0,0 +1,25 @@ +import { defineWorkspace } from 'vitest/config' + +export default defineWorkspace([ + { + extends: './vitest.config.ts', + test: { + name: 'unit', + include: ['src/**/*.test.ts'], + }, + }, + { + extends: './vitest.config.ts', + test: { + name: 'integration', + include: ['tests/integration/**/*.test.ts'], + }, + }, + { + extends: './vitest.config.ts', + test: { + name: 'smoke', + include: ['tests/smoke/**/*.test.ts'], + }, + }, +]) From 3578bf1a42b311c40825ff258c678fa093b5ec9c Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 3 Apr 2024 17:15:53 +0200 Subject: [PATCH 3/9] test: add integration tests for pages router custom 404 (#379) * test: add integration tests for pages router custom 404 * test: more detailed test name and some extra comments explaining locale assertions * test: add assertion failure messages to provide more contextual information --- .../page-router-base-path-i18n/pages/404.js | 16 +++++++ tests/integration/page-router.test.ts | 44 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tests/fixtures/page-router-base-path-i18n/pages/404.js diff --git a/tests/fixtures/page-router-base-path-i18n/pages/404.js b/tests/fixtures/page-router-base-path-i18n/pages/404.js new file mode 100644 index 0000000000..0f42ed27ed --- /dev/null +++ b/tests/fixtures/page-router-base-path-i18n/pages/404.js @@ -0,0 +1,16 @@ +export default function NotFound({ locale }) { + return ( +

+ Custom 404 page for locale:

{locale}
+

+ ) +} + +/** @type {import('next').GetStaticProps} */ +export const getStaticProps = ({ locale }) => { + return { + props: { + locale, + }, + } +} diff --git a/tests/integration/page-router.test.ts b/tests/integration/page-router.test.ts index 5ea9bb6ff2..121dbe7ad0 100644 --- a/tests/integration/page-router.test.ts +++ b/tests/integration/page-router.test.ts @@ -129,3 +129,47 @@ test.skipIf(platform === 'win32')( expect(response.headers?.['cache-control']).toBe('public, max-age=0, must-revalidate') }, ) + +test('Should serve correct locale-aware custom 404 pages', async (ctx) => { + await createFixture('page-router-base-path-i18n', ctx) + await runPlugin(ctx) + + const responseImplicitDefaultLocale = await invokeFunction(ctx, { + url: '/base/path/not-existing-page', + }) + + expect( + responseImplicitDefaultLocale.statusCode, + 'Response for not existing route if locale is not explicitly used in pathname (after basePath) should have 404 status', + ).toBe(404) + expect( + load(responseImplicitDefaultLocale.body)('[data-testid="locale"]').text(), + 'Served 404 page content should use default locale if locale is not explicitly used in pathname (after basePath)', + ).toBe('en') + + const responseExplicitDefaultLocale = await invokeFunction(ctx, { + url: '/base/path/en/not-existing-page', + }) + + expect( + responseExplicitDefaultLocale.statusCode, + 'Response for not existing route if default locale is explicitly used in pathname (after basePath) should have 404 status', + ).toBe(404) + expect( + load(responseExplicitDefaultLocale.body)('[data-testid="locale"]').text(), + 'Served 404 page content should use default locale if default locale is explicitly used in pathname (after basePath)', + ).toBe('en') + + const responseNonDefaultLocale = await invokeFunction(ctx, { + url: '/base/path/fr/not-existing-page', + }) + + expect( + responseNonDefaultLocale.statusCode, + 'Response for not existing route if non-default locale is explicitly used in pathname (after basePath) should have 404 status', + ).toBe(404) + expect( + load(responseNonDefaultLocale.body)('[data-testid="locale"]').text(), + 'Served 404 page content should use non-default locale if non-default locale is explicitly used in pathname (after basePath)', + ).toBe('fr') +}) From f4c588c2aa01bebf36a87e8a3800b775a638e543 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 8 Apr 2024 19:52:00 +0200 Subject: [PATCH 4/9] feat: add cdn-cache-control headers to cacheable route handler responses (#399) * test: add test cases for cacheable route handlers * refactor: add cache related types modules and adjust ROUTE variant to match our current usage * feat: add cdn-cache-control headers to cacheable route handler responses * fix: set revalidate setting on request context for Route responses and set cdn-cache-control header based on that * chore: format with prettier * chore: pass just revalidate time not entire meta --------- Co-authored-by: pieh --- src/build/content/prerendered.ts | 30 ++++++---- src/run/handlers/cache.cts | 57 ++++++++++++------- src/run/handlers/request-context.cts | 3 + src/run/headers.ts | 19 +++++++ src/shared/cache-types.cts | 34 +++++++++++ .../app/api/cached-permanent/route.js | 11 ++++ .../app/api/cached-revalidate/route.js | 11 ++++ tests/integration/simple-app.test.ts | 31 ++++++++++ 8 files changed, 166 insertions(+), 30 deletions(-) create mode 100644 src/shared/cache-types.cts create mode 100644 tests/fixtures/simple-next-app/app/api/cached-permanent/route.js create mode 100644 tests/fixtures/simple-next-app/app/api/cached-revalidate/route.js diff --git a/src/build/content/prerendered.ts b/src/build/content/prerendered.ts index 309f09fbe3..d8d514d74a 100644 --- a/src/build/content/prerendered.ts +++ b/src/build/content/prerendered.ts @@ -5,17 +5,18 @@ import { join } from 'node:path' import { trace } from '@opentelemetry/api' import { wrapTracer } from '@opentelemetry/api/experimental' import { glob } from 'fast-glob' -import type { CacheHandlerValue } from 'next/dist/server/lib/incremental-cache/index.js' -import type { IncrementalCacheValue } from 'next/dist/server/response-cache/types.js' import pLimit from 'p-limit' import { encodeBlobKey } from '../../shared/blobkey.js' +import type { + CachedFetchValue, + CachedPageValue, + NetlifyCacheHandlerValue, + NetlifyCachedRouteValue, + NetlifyIncrementalCacheValue, +} from '../../shared/cache-types.cjs' import type { PluginContext } from '../plugin-context.js' -type CachedPageValue = Extract -type CachedRouteValue = Extract -type CachedFetchValue = Extract - const tracer = wrapTracer(trace.getTracer('Next runtime')) /** @@ -23,7 +24,7 @@ const tracer = wrapTracer(trace.getTracer('Next runtime')) */ const writeCacheEntry = async ( route: string, - value: IncrementalCacheValue, + value: NetlifyIncrementalCacheValue, lastModified: number, ctx: PluginContext, ): Promise => { @@ -31,7 +32,7 @@ const writeCacheEntry = async ( const entry = JSON.stringify({ lastModified, value, - } satisfies CacheHandlerValue) + } satisfies NetlifyCacheHandlerValue) await writeFile(path, entry, 'utf-8') } @@ -68,10 +69,14 @@ const buildAppCacheValue = async (path: string): Promise => { } } -const buildRouteCacheValue = async (path: string): Promise => ({ +const buildRouteCacheValue = async ( + path: string, + initialRevalidateSeconds: number | false, +): Promise => ({ kind: 'ROUTE', body: await readFile(`${path}.body`, 'base64'), ...JSON.parse(await readFile(`${path}.meta`, 'utf-8')), + revalidate: initialRevalidateSeconds, }) const buildFetchCacheValue = async (path: string): Promise => ({ @@ -100,7 +105,7 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise ? Date.now() - 31536000000 : Date.now() const key = routeToFilePath(route) - let value: IncrementalCacheValue + let value: NetlifyIncrementalCacheValue switch (true) { // Parallel route default layout has no prerendered page case meta.dataRoute?.endsWith('/default.rsc') && @@ -117,7 +122,10 @@ export const copyPrerenderedContent = async (ctx: PluginContext): Promise value = await buildAppCacheValue(join(ctx.publishDir, 'server/app', key)) break case meta.dataRoute === null: - value = await buildRouteCacheValue(join(ctx.publishDir, 'server/app', key)) + value = await buildRouteCacheValue( + join(ctx.publishDir, 'server/app', key), + meta.initialRevalidateSeconds, + ) break default: throw new Error(`Unrecognized content: ${route}`) diff --git a/src/run/handlers/cache.cts b/src/run/handlers/cache.cts index 71210d6e9a..0e232e474a 100644 --- a/src/run/handlers/cache.cts +++ b/src/run/handlers/cache.cts @@ -7,12 +7,15 @@ import { getDeployStore, Store } from '@netlify/blobs' import { purgeCache } from '@netlify/functions' import { type Span } from '@opentelemetry/api' import { NEXT_CACHE_TAGS_HEADER } from 'next/dist/lib/constants.js' + import type { CacheHandler, CacheHandlerContext, - CacheHandlerValue, IncrementalCache, -} from 'next/dist/server/lib/incremental-cache/index.js' + NetlifyCachedRouteValue, + NetlifyCacheHandlerValue, + NetlifyIncrementalCacheValue, +} from '../../shared/cache-types.cjs' import { getRequestContext } from './request-context.cjs' import { getTracer } from './tracer.cjs' @@ -43,7 +46,7 @@ export class NetlifyCacheHandler implements CacheHandler { } private captureResponseCacheLastModified( - cacheValue: CacheHandlerValue, + cacheValue: NetlifyCacheHandlerValue, key: string, getCacheKeySpan: Span, ) { @@ -90,6 +93,19 @@ export class NetlifyCacheHandler implements CacheHandler { } } + private captureRouteRevalidateAndRemoveFromObject( + cacheValue: NetlifyCachedRouteValue, + ): Omit { + const { revalidate, ...restOfRouteValue } = cacheValue + + const requestContext = getRequestContext() + if (requestContext) { + requestContext.routeHandlerRevalidate = revalidate + } + + return restOfRouteValue + } + async get(...args: Parameters): ReturnType { return this.tracer.withActiveSpan('get cache key', async (span) => { const [key, ctx = {}] = args @@ -103,7 +119,7 @@ export class NetlifyCacheHandler implements CacheHandler { return await this.blobStore.get(blobKey, { type: 'json', }) - })) as CacheHandlerValue | null + })) as NetlifyCacheHandlerValue | null // if blob is null then we don't have a cache entry if (!blob) { @@ -128,15 +144,19 @@ export class NetlifyCacheHandler implements CacheHandler { value: blob.value, } - case 'ROUTE': + case 'ROUTE': { span.addEvent('ROUTE', { lastModified: blob.lastModified, status: blob.value.status }) + + const valueWithoutRevalidate = this.captureRouteRevalidateAndRemoveFromObject(blob.value) + return { lastModified: blob.lastModified, value: { - ...blob.value, - body: Buffer.from(blob.value.body as unknown as string, 'base64'), + ...valueWithoutRevalidate, + body: Buffer.from(valueWithoutRevalidate.body as unknown as string, 'base64'), }, } + } case 'PAGE': span.addEvent('PAGE', { lastModified: blob.lastModified }) return { @@ -152,23 +172,22 @@ export class NetlifyCacheHandler implements CacheHandler { async set(...args: Parameters) { return this.tracer.withActiveSpan('set cache key', async (span) => { - const [key, data] = args + const [key, data, context] = args const blobKey = await this.encodeBlobKey(key) const lastModified = Date.now() span.setAttributes({ key, lastModified, blobKey }) console.debug(`[NetlifyCacheHandler.set]: ${key}`) - let value = data - - if (data?.kind === 'ROUTE') { - // don't mutate data, as it's used for the initial response - instead create a new object - value = { - ...data, - // @ts-expect-error gotta find a better solution for this - body: data.body.toString('base64'), - } - } + const value: NetlifyIncrementalCacheValue | null = + data?.kind === 'ROUTE' + ? // don't mutate data, as it's used for the initial response - instead create a new object + { + ...data, + revalidate: context.revalidate, + body: data.body.toString('base64'), + } + : data await this.blobStore.setJSON(blobKey, { lastModified, @@ -217,7 +236,7 @@ export class NetlifyCacheHandler implements CacheHandler { * Checks if a cache entry is stale through on demand revalidated tags */ private async checkCacheEntryStaleByTags( - cacheEntry: CacheHandlerValue, + cacheEntry: NetlifyCacheHandlerValue, tags: string[] = [], softTags: string[] = [], ) { diff --git a/src/run/handlers/request-context.cts b/src/run/handlers/request-context.cts index a2d9902214..28747a7d86 100644 --- a/src/run/handlers/request-context.cts +++ b/src/run/handlers/request-context.cts @@ -1,5 +1,7 @@ import { AsyncLocalStorage } from 'node:async_hooks' +import type { NetlifyCachedRouteValue } from '../../shared/cache-types.cjs' + export type RequestContext = { debug: boolean responseCacheGetLastModified?: number @@ -7,6 +9,7 @@ export type RequestContext = { usedFsRead?: boolean didPagesRouterOnDemandRevalidate?: boolean serverTiming?: string + routeHandlerRevalidate?: NetlifyCachedRouteValue['revalidate'] } type RequestContextAsyncLocalStorage = AsyncLocalStorage diff --git a/src/run/headers.ts b/src/run/headers.ts index e2f5ea092f..7af5522cb8 100644 --- a/src/run/headers.ts +++ b/src/run/headers.ts @@ -179,6 +179,22 @@ export const setCacheControlHeaders = ( request: Request, requestContext: RequestContext, ) => { + if ( + typeof requestContext.routeHandlerRevalidate !== 'undefined' && + ['GET', 'HEAD'].includes(request.method) && + !headers.has('netlify-cdn-cache-control') + ) { + // handle CDN Cache Control on Route Handler responses + const cdnCacheControl = + // if we are serving already stale response, instruct edge to not attempt to cache that response + headers.get('x-nextjs-cache') === 'STALE' + ? 'public, max-age=0, must-revalidate' + : `s-maxage=${requestContext.routeHandlerRevalidate === false ? 31536000 : requestContext.routeHandlerRevalidate}, stale-while-revalidate=31536000` + + headers.set('netlify-cdn-cache-control', cdnCacheControl) + return + } + const cacheControl = headers.get('cache-control') if ( cacheControl !== null && @@ -186,6 +202,7 @@ export const setCacheControlHeaders = ( !headers.has('cdn-cache-control') && !headers.has('netlify-cdn-cache-control') ) { + // handle CDN Cache Control on ISR and App Router page responses const browserCacheControl = omitHeaderValues(cacheControl, [ 's-maxage', 'stale-while-revalidate', @@ -200,6 +217,7 @@ export const setCacheControlHeaders = ( headers.set('cache-control', browserCacheControl || 'public, max-age=0, must-revalidate') headers.set('netlify-cdn-cache-control', cdnCacheControl) + return } if ( @@ -208,6 +226,7 @@ export const setCacheControlHeaders = ( !headers.has('netlify-cdn-cache-control') && requestContext.usedFsRead ) { + // handle CDN Cache Control on static files headers.set('cache-control', 'public, max-age=0, must-revalidate') headers.set('netlify-cdn-cache-control', `max-age=31536000`) } diff --git a/src/shared/cache-types.cts b/src/shared/cache-types.cts new file mode 100644 index 0000000000..ee7b14fbfc --- /dev/null +++ b/src/shared/cache-types.cts @@ -0,0 +1,34 @@ +import type { + CacheHandlerValue, + IncrementalCache, +} from 'next/dist/server/lib/incremental-cache/index.js' +import type { + IncrementalCacheValue, + CachedRouteValue, +} from 'next/dist/server/response-cache/types.js' + +export type { + CacheHandler, + CacheHandlerContext, + IncrementalCache, +} from 'next/dist/server/lib/incremental-cache/index.js' + +export type NetlifyCachedRouteValue = Omit & { + // Next.js stores body as buffer, while we store it as base64 encoded string + body: string + // Next.js doesn't produce cache-control tag we use to generate cdn cache control + // so store needed values as part of cached response data + revalidate: Parameters[2]['revalidate'] +} + +export type CachedPageValue = Extract +export type CachedFetchValue = Extract + +export type NetlifyIncrementalCacheValue = + | Exclude + | NetlifyCachedRouteValue + +type CachedRouteValueToNetlify = T extends CachedRouteValue ? NetlifyCachedRouteValue : T +type MapCachedRouteValueToNetlify = { [K in keyof T]: CachedRouteValueToNetlify } + +export type NetlifyCacheHandlerValue = MapCachedRouteValueToNetlify diff --git a/tests/fixtures/simple-next-app/app/api/cached-permanent/route.js b/tests/fixtures/simple-next-app/app/api/cached-permanent/route.js new file mode 100644 index 0000000000..15aefbaa6a --- /dev/null +++ b/tests/fixtures/simple-next-app/app/api/cached-permanent/route.js @@ -0,0 +1,11 @@ +import { NextResponse } from 'next/server' + +export async function GET() { + return NextResponse.json({ + message: + 'Route handler not using request and using force-static dynamic strategy with permanent caching', + }) +} +export const revalidate = false + +export const dynamic = 'force-static' diff --git a/tests/fixtures/simple-next-app/app/api/cached-revalidate/route.js b/tests/fixtures/simple-next-app/app/api/cached-revalidate/route.js new file mode 100644 index 0000000000..e26c645379 --- /dev/null +++ b/tests/fixtures/simple-next-app/app/api/cached-revalidate/route.js @@ -0,0 +1,11 @@ +import { NextResponse } from 'next/server' + +export async function GET() { + return NextResponse.json({ + message: + 'Route handler not using request and using force-static dynamic strategy with 15 seconds revalidate', + }) +} +export const revalidate = 15 + +export const dynamic = 'force-static' diff --git a/tests/integration/simple-app.test.ts b/tests/integration/simple-app.test.ts index da9ffa18f0..3fcf831b8b 100644 --- a/tests/integration/simple-app.test.ts +++ b/tests/integration/simple-app.test.ts @@ -57,6 +57,8 @@ test('Test that the simple next app is working', async (ctx) const blobEntries = await getBlobEntries(ctx) expect(blobEntries.map(({ key }) => decodeBlobKey(key)).sort()).toEqual([ '/404', + '/api/cached-permanent', + '/api/cached-revalidate', '/image/local', '/image/migration-from-v4-runtime', '/image/remote-domain', @@ -170,6 +172,35 @@ test('handlers can add cookies in route handlers with the co expect(setCookieHeader).toContain('test2=value2; Path=/handler; HttpOnly') }) +test('cacheable route handler is cached on cdn (revalidate=false / permanent caching)', async (ctx) => { + await createFixture('simple-next-app', ctx) + await runPlugin(ctx) + + const permanentlyCachedResponse = await invokeFunction(ctx, { url: '/api/cached-permanent' }) + expect(permanentlyCachedResponse.headers['netlify-cdn-cache-control']).toBe( + 's-maxage=31536000, stale-while-revalidate=31536000', + ) +}) + +test('cacheable route handler is cached on cdn (revalidate=15)', async (ctx) => { + await createFixture('simple-next-app', ctx) + await runPlugin(ctx) + + const firstTimeCachedResponse = await invokeFunction(ctx, { url: '/api/cached-revalidate' }) + // this will be "stale" response from build + expect(firstTimeCachedResponse.headers['netlify-cdn-cache-control']).toBe( + 'public, max-age=0, must-revalidate', + ) + + // allow server to regenerate fresh response in background + await new Promise((res) => setTimeout(res, 1_000)) + + const secondTimeCachedResponse = await invokeFunction(ctx, { url: '/api/cached-revalidate' }) + expect(secondTimeCachedResponse.headers['netlify-cdn-cache-control']).toBe( + 's-maxage=15, stale-while-revalidate=31536000', + ) +}) + // there's a bug where requests accept-encoding header // result in corrupted bodies // while that bug stands, we want to ignore accept-encoding From db5d4518bc98987db6ff682be38ec0f6670d7f9f Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Wed, 10 Apr 2024 17:33:16 +0200 Subject: [PATCH 5/9] chore: add rule to prevent 'floating' promises (#375) --- .eslintrc.cjs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index dfd9cd09f8..2ea9578a7a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,5 +1,6 @@ const { overrides } = require('@netlify/eslint-config-node') +/** @type {import('eslint').Linter.Config} */ module.exports = { extends: '@netlify/eslint-config-node', parserOptions: { @@ -33,6 +34,18 @@ module.exports = { }, overrides: [ ...overrides, + { + files: ['*.cts', '*mts', '*.ts', '*.tsx'], + excludedFiles: ['*.test.ts'], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + project: true, + }, + rules: { + '@typescript-eslint/no-floating-promises': 'error', + }, + }, { files: ['src/run/**'], rules: { From de18110a7367a7ead8e7cd0b1d9ce1c5cab9570a Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 12 Apr 2024 14:01:13 +0200 Subject: [PATCH 6/9] test: adjust cleanup for next change (#405) --- tests/utils/fixture.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/utils/fixture.ts b/tests/utils/fixture.ts index c8c6da07d3..ec7e0c9dab 100644 --- a/tests/utils/fixture.ts +++ b/tests/utils/fixture.ts @@ -76,9 +76,17 @@ export const createFixture = async (fixture: string, ctx: FixtureTestContext) => // invocations would not use fetch-cache at all - this restores the original fetch // and makes globalThis.fetch.__nextPatched falsy which will allow Next.js to apply // needed patch - // @ts-ignore fetch doesn't have __nextPatched property in types - if (globalThis.fetch.__nextPatched && globalThis._nextOriginalFetch) { - globalThis.fetch = globalThis._nextOriginalFetch + if ( + // @ts-ignore fetch doesn't have __nextPatched property in types + globalThis.fetch.__nextPatched && + // before https://github.com/vercel/next.js/pull/64088 original fetch was set on globalThis._nextOriginalFetch + // after it it is being set on globalThis.fetch._nextOriginalFetch + // so we check both to make sure tests continue to work regardless of next version + // @ts-ignore fetch doesn't have _nextOriginalFetch property in types + (globalThis._nextOriginalFetch || globalThis.fetch._nextOriginalFetch) + ) { + // @ts-ignore fetch doesn't have _nextOriginalFetch property in types + globalThis.fetch = globalThis._nextOriginalFetch || globalThis.fetch._nextOriginalFetch } ctx.cwd = await mkdtemp(join(tmpdir(), 'netlify-next-runtime-')) From 9445b79ce658b94121a01831432ca4a281ff304c Mon Sep 17 00:00:00 2001 From: Tatyana <43764894+taty2010@users.noreply.github.com> Date: Fri, 12 Apr 2024 13:03:30 -0500 Subject: [PATCH 7/9] test: update e2e skipped tests (#406) * update skipped testt * moved skipped test --- tests/test-config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test-config.json b/tests/test-config.json index 036c458468..c6977ba353 100644 --- a/tests/test-config.json +++ b/tests/test-config.json @@ -26,7 +26,8 @@ "test/e2e/trailingslash-with-rewrite/index.test.ts", "test/e2e/transpile-packages/index.test.ts", "test/e2e/typescript-version-no-warning/typescript-version-no-warning.test.ts", - "test/e2e/typescript-version-warning/typescript-version-warning.test.ts" + "test/e2e/typescript-version-warning/typescript-version-warning.test.ts", + "/test/e2e/revalidate-reason/revalidate-reason.test.ts" ], "skipped": [ { From 275f488de53b4eb1041812dd813ca2528e48bc02 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Tue, 16 Apr 2024 14:55:13 +0200 Subject: [PATCH 8/9] feat: fail the build when advanced api routes are used (#403) * test: add advanced routes fixture * feat: fail the build when advanced api routes are used * chore: update failBuild message with mention of migration and link to example * test: add integration test for build failing error message when advanced api routes are used * chore: drop console.logs inherited from v4 advanced api routes handling code * fix: lint * chore: use shortened link for migration example * test: update assertion * test: unrelated smoke test update --- src/build/advanced-api-routes.ts | 188 ++++++++++++++++++ src/build/verification.ts | 18 ++ src/index.ts | 8 +- .../advanced-api-routes/next-env.d.ts | 5 + .../advanced-api-routes/next.config.js | 10 + .../fixtures/advanced-api-routes/package.json | 19 ++ .../pages/api/hello-background.ts | 9 + .../pages/api/hello-scheduled.js | 10 + .../advanced-api-routes/tsconfig.json | 28 +++ tests/integration/advanced-api-routes.test.ts | 43 ++++ tests/utils/create-e2e-fixture.ts | 1 + 11 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 src/build/advanced-api-routes.ts create mode 100644 tests/fixtures/advanced-api-routes/next-env.d.ts create mode 100644 tests/fixtures/advanced-api-routes/next.config.js create mode 100644 tests/fixtures/advanced-api-routes/package.json create mode 100644 tests/fixtures/advanced-api-routes/pages/api/hello-background.ts create mode 100644 tests/fixtures/advanced-api-routes/pages/api/hello-scheduled.js create mode 100644 tests/fixtures/advanced-api-routes/tsconfig.json create mode 100644 tests/integration/advanced-api-routes.test.ts diff --git a/src/build/advanced-api-routes.ts b/src/build/advanced-api-routes.ts new file mode 100644 index 0000000000..6cd024424f --- /dev/null +++ b/src/build/advanced-api-routes.ts @@ -0,0 +1,188 @@ +import { existsSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import { join } from 'node:path' + +import type { PluginContext } from './plugin-context.js' + +interface FunctionsConfigManifest { + version: number + functions: Record> +} + +// eslint-disable-next-line no-shadow +export const enum ApiRouteType { + SCHEDULED = 'experimental-scheduled', + BACKGROUND = 'experimental-background', +} + +interface ApiStandardConfig { + type?: never + runtime?: 'nodejs' | 'experimental-edge' | 'edge' + schedule?: never +} + +interface ApiScheduledConfig { + type: ApiRouteType.SCHEDULED + runtime?: 'nodejs' + schedule: string +} + +interface ApiBackgroundConfig { + type: ApiRouteType.BACKGROUND + runtime?: 'nodejs' + schedule?: never +} + +type ApiConfig = ApiStandardConfig | ApiScheduledConfig | ApiBackgroundConfig + +export async function getAPIRoutesConfigs(ctx: PluginContext) { + const functionsConfigManifestPath = join( + ctx.publishDir, + 'server', + 'functions-config-manifest.json', + ) + if (!existsSync(functionsConfigManifestPath)) { + // before https://github.com/vercel/next.js/pull/60163 this file might not have been produced if there were no API routes at all + return [] + } + + const functionsConfigManifest = JSON.parse( + await readFile(functionsConfigManifestPath, 'utf-8'), + ) as FunctionsConfigManifest + + const appDir = ctx.resolveFromSiteDir('.') + const pagesDir = join(appDir, 'pages') + const srcPagesDir = join(appDir, 'src', 'pages') + const { pageExtensions } = ctx.requiredServerFiles.config + + return Promise.all( + Object.keys(functionsConfigManifest.functions).map(async (apiRoute) => { + const filePath = getSourceFileForPage(apiRoute, [pagesDir, srcPagesDir], pageExtensions) + + const sharedFields = { + apiRoute, + filePath, + config: {} as ApiConfig, + } + + if (filePath) { + const config = await extractConfigFromFile(filePath, appDir) + return { + ...sharedFields, + config, + } + } + + return sharedFields + }), + ) +} + +// Next.js already defines a default `pageExtensions` array in its `required-server-files.json` file +// In case it gets `undefined`, this is a fallback +const SOURCE_FILE_EXTENSIONS = ['js', 'jsx', 'ts', 'tsx'] + +/** + * Find the source file for a given page route + */ +const getSourceFileForPage = ( + page: string, + roots: string[], + pageExtensions = SOURCE_FILE_EXTENSIONS, +) => { + for (const root of roots) { + for (const extension of pageExtensions) { + const file = join(root, `${page}.${extension}`) + if (existsSync(file)) { + return file + } + + const fileAtFolderIndex = join(root, page, `index.${extension}`) + if (existsSync(fileAtFolderIndex)) { + return fileAtFolderIndex + } + } + } +} + +/** + * Given an array of base paths and candidate modules, return the first one that exists + */ +const findModuleFromBase = ({ + paths, + candidates, +}: { + paths: string[] + candidates: string[] +}): string | null => { + for (const candidate of candidates) { + try { + const modulePath = require.resolve(candidate, { paths }) + if (modulePath) { + return modulePath + } + } catch { + // Ignore the error + } + } + // if we couldn't find a module from paths, let's try to resolve from here + for (const candidate of candidates) { + try { + const modulePath = require.resolve(candidate) + if (modulePath) { + return modulePath + } + } catch { + // Ignore the error + } + } + return null +} + +let extractConstValue: typeof import('next/dist/build/analysis/extract-const-value.js') +let parseModule: typeof import('next/dist/build/analysis/parse-module.js').parseModule + +const extractConfigFromFile = async (apiFilePath: string, appDir: string): Promise => { + if (!apiFilePath || !existsSync(apiFilePath)) { + return {} + } + + const extractConstValueModulePath = findModuleFromBase({ + paths: [appDir], + candidates: ['next/dist/build/analysis/extract-const-value'], + }) + + const parseModulePath = findModuleFromBase({ + paths: [appDir], + candidates: ['next/dist/build/analysis/parse-module'], + }) + + if (!extractConstValueModulePath || !parseModulePath) { + // Old Next.js version + return {} + } + + if (!extractConstValue && extractConstValueModulePath) { + // eslint-disable-next-line import/no-dynamic-require, n/global-require + extractConstValue = require(extractConstValueModulePath) + } + if (!parseModule && parseModulePath) { + // eslint-disable-next-line prefer-destructuring, @typescript-eslint/no-var-requires, import/no-dynamic-require, n/global-require + parseModule = require(parseModulePath).parseModule + } + + const { extractExportedConstValue } = extractConstValue + + const fileContent = await readFile(apiFilePath, 'utf8') + // No need to parse if there's no "config" + if (!fileContent.includes('config')) { + return {} + } + const ast = await parseModule(apiFilePath, fileContent) + + try { + return extractExportedConstValue(ast, 'config') as ApiConfig + } catch { + return {} + } +} diff --git a/src/build/verification.ts b/src/build/verification.ts index 50ac7a0bb0..658d712311 100644 --- a/src/build/verification.ts +++ b/src/build/verification.ts @@ -2,6 +2,7 @@ import { existsSync } from 'node:fs' import { satisfies } from 'semver' +import { ApiRouteType, getAPIRoutesConfigs } from './advanced-api-routes.js' import type { PluginContext } from './plugin-context.js' const SUPPORTED_NEXT_VERSIONS = '>=13.5.0' @@ -66,3 +67,20 @@ export function verifyBuildConfig(ctx: PluginContext) { ) } } + +export async function verifyNoAdvancedAPIRoutes(ctx: PluginContext) { + const apiRoutesConfigs = await getAPIRoutesConfigs(ctx) + + const unsupportedAPIRoutes = apiRoutesConfigs.filter((apiRouteConfig) => { + return ( + apiRouteConfig.config.type === ApiRouteType.BACKGROUND || + apiRouteConfig.config.type === ApiRouteType.SCHEDULED + ) + }) + + if (unsupportedAPIRoutes.length !== 0) { + ctx.failBuild( + `@netlify/plugin-next@5 does not support advanced API routes. The following API routes should be migrated to Netlify background or scheduled functions:\n${unsupportedAPIRoutes.map((apiRouteConfig) => ` - ${apiRouteConfig.apiRoute} (type: "${apiRouteConfig.config.type}")`).join('\n')}\n\nRefer to https://ntl.fyi/next-scheduled-bg-function-migration as migration example.`, + ) + } +} diff --git a/src/index.ts b/src/index.ts index a9f312aa44..315a80af58 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,11 @@ import { createEdgeHandlers } from './build/functions/edge.js' import { createServerHandler } from './build/functions/server.js' import { setImageConfig } from './build/image-cdn.js' import { PluginContext } from './build/plugin-context.js' -import { verifyBuildConfig, verifyPublishDir } from './build/verification.js' +import { + verifyBuildConfig, + verifyNoAdvancedAPIRoutes, + verifyPublishDir, +} from './build/verification.js' const tracer = wrapTracer(trace.getTracer('Next.js runtime')) @@ -48,6 +52,8 @@ export const onBuild = async (options: NetlifyPluginOptions) => { return copyStaticExport(ctx) } + await verifyNoAdvancedAPIRoutes(ctx) + await Promise.all([ copyStaticAssets(ctx), copyStaticContent(ctx), diff --git a/tests/fixtures/advanced-api-routes/next-env.d.ts b/tests/fixtures/advanced-api-routes/next-env.d.ts new file mode 100644 index 0000000000..4f11a03dc6 --- /dev/null +++ b/tests/fixtures/advanced-api-routes/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/tests/fixtures/advanced-api-routes/next.config.js b/tests/fixtures/advanced-api-routes/next.config.js new file mode 100644 index 0000000000..6346ab0742 --- /dev/null +++ b/tests/fixtures/advanced-api-routes/next.config.js @@ -0,0 +1,10 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: 'standalone', + eslint: { + ignoreDuringBuilds: true, + }, + generateBuildId: () => 'build-id', +} + +module.exports = nextConfig diff --git a/tests/fixtures/advanced-api-routes/package.json b/tests/fixtures/advanced-api-routes/package.json new file mode 100644 index 0000000000..7cb8720db3 --- /dev/null +++ b/tests/fixtures/advanced-api-routes/package.json @@ -0,0 +1,19 @@ +{ + "name": "advanced-api-routes", + "version": "0.1.0", + "private": true, + "scripts": { + "postinstall": "next build", + "dev": "next dev", + "build": "next build" + }, + "dependencies": { + "@netlify/functions": "^2.5.1", + "next": "latest", + "react": "18.2.0", + "react-dom": "18.2.0" + }, + "devDependencies": { + "@types/react": "18.2.75" + } +} diff --git a/tests/fixtures/advanced-api-routes/pages/api/hello-background.ts b/tests/fixtures/advanced-api-routes/pages/api/hello-background.ts new file mode 100644 index 0000000000..58d17922a7 --- /dev/null +++ b/tests/fixtures/advanced-api-routes/pages/api/hello-background.ts @@ -0,0 +1,9 @@ +export default (req, res) => { + res.setHeader('Content-Type', 'application/json') + res.status(200) + res.json({ message: 'hello world :)' }) +} + +export const config = { + type: 'experimental-background', +} diff --git a/tests/fixtures/advanced-api-routes/pages/api/hello-scheduled.js b/tests/fixtures/advanced-api-routes/pages/api/hello-scheduled.js new file mode 100644 index 0000000000..e415230508 --- /dev/null +++ b/tests/fixtures/advanced-api-routes/pages/api/hello-scheduled.js @@ -0,0 +1,10 @@ +export default (req, res) => { + res.setHeader('Content-Type', 'application/json') + res.status(200) + res.json({ message: 'hello world :)' }) +} + +export const config = { + type: 'experimental-scheduled', + schedule: '@hourly', +} diff --git a/tests/fixtures/advanced-api-routes/tsconfig.json b/tests/fixtures/advanced-api-routes/tsconfig.json new file mode 100644 index 0000000000..65348e831c --- /dev/null +++ b/tests/fixtures/advanced-api-routes/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": false, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve" + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/tests/integration/advanced-api-routes.test.ts b/tests/integration/advanced-api-routes.test.ts new file mode 100644 index 0000000000..7eb66199b0 --- /dev/null +++ b/tests/integration/advanced-api-routes.test.ts @@ -0,0 +1,43 @@ +import { getLogger } from 'lambda-local' +import { v4 } from 'uuid' +import { beforeEach, vi, it, expect } from 'vitest' +import { createFixture, runPlugin, type FixtureTestContext } from '../utils/fixture.js' +import { generateRandomObjectID, startMockBlobStore } from '../utils/helpers.js' + +getLogger().level = 'alert' + +beforeEach(async (ctx) => { + // set for each test a new deployID and siteID + ctx.deployID = generateRandomObjectID() + ctx.siteID = v4() + vi.stubEnv('SITE_ID', ctx.siteID) + vi.stubEnv('DEPLOY_ID', ctx.deployID) + vi.stubEnv('NETLIFY_PURGE_API_TOKEN', 'fake-token') + // hide debug logs in tests + // vi.spyOn(console, 'debug').mockImplementation(() => {}) + + await startMockBlobStore(ctx) +}) + +it('test', async (ctx) => { + await createFixture('advanced-api-routes', ctx) + + const runPluginPromise = runPlugin(ctx) + + await expect(runPluginPromise).rejects.toThrow( + '@netlify/plugin-next@5 does not support advanced API routes. The following API routes should be migrated to Netlify background or scheduled functions:', + ) + + // list API routes to migrate + await expect(runPluginPromise).rejects.toThrow( + '/api/hello-scheduled (type: "experimental-scheduled")', + ) + await expect(runPluginPromise).rejects.toThrow( + '/api/hello-background (type: "experimental-background")', + ) + + // links to migration example + await expect(runPluginPromise).rejects.toThrow( + 'Refer to https://ntl.fyi/next-scheduled-bg-function-migration as migration example.', + ) +}) diff --git a/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index a0a5d67f54..bec4dd56b4 100644 --- a/tests/utils/create-e2e-fixture.ts +++ b/tests/utils/create-e2e-fixture.ts @@ -318,6 +318,7 @@ export const fixtureFactories = { buildCommand: 'yarn build', publishDirectory: 'apps/site/.next', smoke: true, + runtimeInstallationPath: '', }), npmMonorepoEmptyBaseNoPackagePath: () => createE2EFixture('npm-monorepo-empty-base', { From e087bf70aef689fc7057ee2d5088473b278e5533 Mon Sep 17 00:00:00 2001 From: "token-generator-app[bot]" <82042599+token-generator-app[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:43:50 +0200 Subject: [PATCH 9/9] chore(main): release 5.1.0 (#401) Co-authored-by: token-generator-app[bot] <82042599+token-generator-app[bot]@users.noreply.github.com> --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2b367cdbc..f9d1544b29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [5.1.0](https://github.com/netlify/next-runtime-minimal/compare/v5.0.0...v5.1.0) (2024-04-16) + + +### Features + +* add cdn-cache-control headers to cacheable route handler responses ([#399](https://github.com/netlify/next-runtime-minimal/issues/399)) ([f4c588c](https://github.com/netlify/next-runtime-minimal/commit/f4c588c2aa01bebf36a87e8a3800b775a638e543)) +* fail the build when advanced api routes are used ([#403](https://github.com/netlify/next-runtime-minimal/issues/403)) ([275f488](https://github.com/netlify/next-runtime-minimal/commit/275f488de53b4eb1041812dd813ca2528e48bc02)) + ## [5.0.0](https://github.com/netlify/next-runtime-minimal/compare/v5.0.0-alpha.25...v5.0.0) (2024-04-02) diff --git a/package-lock.json b/package-lock.json index 4456a21a00..458fccd2fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@netlify/plugin-nextjs", - "version": "5.0.0", + "version": "5.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@netlify/plugin-nextjs", - "version": "5.0.0", + "version": "5.1.0", "license": "MIT", "devDependencies": { "@fastly/http-compute-js": "1.1.4", diff --git a/package.json b/package.json index d25524958f..b3bd6768f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@netlify/plugin-nextjs", - "version": "5.0.0", + "version": "5.1.0", "description": "Run Next.js seamlessly on Netlify", "main": "./dist/index.js", "type": "module",