From 90bbac44db9832e8c41c39d7e3e1c958fe4f008c Mon Sep 17 00:00:00 2001 From: Damien Simonin Feugas Date: Thu, 21 Jul 2022 16:53:23 +0200 Subject: [PATCH] fix(edge): error handling for edge route and middleware is inconsistent (#38401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What’s in there? This PR brings more consistency in how errors and warnings are reported when running code in the Edge Runtime: - Dynamic code evaluation (`eval()`, `new Function()`, `WebAssembly.instantiate()`, `WebAssembly.compile()`…) - Usage of Node.js global APIs (`BroadcastChannel`, `Buffer`, `TextDecoderStream`, `setImmediate()`...) - Usage of Node.js modules (`fs`, `path`, `child_process`…) The new error messages should mention *Edge Runtime* instead of *Middleware*, so they are valid in both cases. It also fixes a bug where the process polyfill would issue a warning for `process.cwd` (which is `undefined` but legit). Now, one has to invoke the function `process.cwd()` to trigger the error. It finally fixes the react-dev-overlay, where links from middleware and Edge API route files could not be opened because of the `(middleware)/` prefix in their name. About the later, please note that we can’t easily remove the prefix or change it for Edge API routes. It comes from the Webpack layer, which is the same for both. We may consider renaming it to *edge* instead in the future. ## How to test? These changes are almost fully covered with tests: ```bash pnpm testheadless --testPathPattern runtime-dynamic pnpm testheadless --testPathPattern runtime-with-node pnpm testheadless --testPathPattern runtime-module pnpm testheadless --testPathPattern middleware-dev-errors ``` To try them out manually, you can write a middleware and Edge route files like these: ```jsx // middleware.js import { NextResponse } from 'next/server' import { basename } from 'path' export default async function middleware() { eval('2+2') setImmediate(() => {}) basename() return NextResponse.next() } export const config = { matcher: '/' } ``` ```jsx // pages/api/route.js import { basename } from 'path' export default async function handle() { eval('2+2') setImmediate(() => {}) basename() return Response.json({ ok: true }) } export const config = { runtime: 'experimental-edge' } ``` The expected behaviours are: - [x] dev, middleware/edge route is using a node.js module: error at runtime (logs + read-dev-overlay): ```bash error - (middleware)/pages/api/route.js (1:0) @ Object.handle [as handler] error - The edge runtime does not support Node.js 'path' module. Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime > 1 | import { basename } from "path"; 2 | export default async function handle() { ``` - [x] build, middleware/edge route is using a node.js module: warning but succeeds ```bash warn - Compiled with warnings ./middleware.js A Node.js module is loaded ('path' at line 4) which is not supported in the Edge Runtime. Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime ./pages/api/route.js A Node.js module is loaded ('path' at line 1) which is not supported in the Edge Runtime. Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime ``` - [x] production, middleware/edge route is using a node.js module: error at runtime (logs + 500 error) ```bash Error: The edge runtime does not support Node.js 'path' module. Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime at (file:///Users/damien/dev/next.js/packages/next/server/web/sandbox/context.ts:149) ``` - [x] dev, middleware/edge route is using a node.js global API: error at runtime (logs + read-dev-overlay): ```bash error - (middleware)/pages/api/route.js (4:2) @ Object.handle [as handler] error - A Node.js API is used (setImmediate) which is not supported in the Edge Runtime. Learn more: https://nextjs.org/docs/api-reference/edge-runtime 2 | 3 | export default async function handle() { > 4 | setImmediate(() => {}) | ^ ``` - [x] build, middleware/edge route is using a node.js global API: warning but succeeds ```bash warn - Compiled with warnings ./middleware.js A Node.js API is used (setImmediate at line: 6) which is not supported in the Edge Runtime. Learn more: https://nextjs.org/docs/api-reference/edge-runtime ./pages/api/route.js A Node.js API is used (setImmediate at line: 3) which is not supported in the Edge Runtime. Learn more: https://nextjs.org/docs/api-reference/edge-runtime ``` - [x] production, middleware/edge route is using a node.js module: error at runtime (logs + 500 error) ```bash Error: A Node.js API is used (setImmediate) which is not supported in the Edge Runtime. Learn more: https://nextjs.org/docs/api-reference/edge-runtime at (file:///Users/damien/dev/next.js/packages/next/server/web/sandbox/context.ts:330) ``` - [x] dev, middleware/edge route is loading dynamic code: warning at runtime (logs + read-dev-overlay) and request succeeds (we allow dynamic code in dev only): ```bash warn - (middleware)/middleware.js (7:2) @ Object.middleware [as handler] warn - Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime 5 | 6 | export default async function middleware() { > 7 | eval('2+2') ``` - [x] build, middleware/edge route is loading dynamic code: build fails with error: ```bash Failed to compile. ./middleware.js Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime Used by default ./pages/api/route.js Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime Used by default ``` ## Notes to reviewers Edge-related errors are either issued from `next/server/web/sandbox/context.ts` file (runtime errors) or from `next/build/webpack/plugins/middleware-plugin.ts` (webpack compilation). The previous implementation (I’m pleading guilty here) was way too verbose: some errors (Node.js global APIs like using `process.cwd()`) could be reported several times, and the previous mechanism to dedupe them (in middleware-plugin) wasn’t really effective. Changes in tests are due to renaming existing tests such as `test/integration/middleware-with-node.js-apis` into `test/integration/edge-runtime-with-node.js-apis`. I extended them to cover Edge API route. @hanneslund I’ve pushed the improvement you did in https://github.com/vercel/next.js/pull/38289/ one step further to avoid duplication. --- .../webpack/plugins/middleware-plugin.ts | 7 +- .../error-overlay/format-webpack-messages.js | 2 +- packages/next/server/dev/next-dev-server.ts | 21 +- packages/next/server/next-server.ts | 15 +- packages/next/server/web/sandbox/context.ts | 51 ++--- packages/next/server/web/sandbox/sandbox.ts | 4 +- packages/react-dev-overlay/src/middleware.ts | 6 +- .../lib/square.wasm | Bin .../lib/utils.js | 8 + .../lib/wasm.js | 0 .../middleware.js | 21 +- .../next.config.js | 0 .../pages/api/route.js | 26 +++ .../pages/index.js | 0 .../test/index.test.js | 180 ++++++++++++++++++ .../lib/utils.js | 53 ++++++ .../middleware.js | 9 + .../pages/api/route.js | 8 + .../pages/index.js | 0 .../test/index.test.ts | 77 +++++--- .../middleware-dev-errors/test/index.test.js | 12 +- .../test/index.test.js | 139 -------------- .../test/index.test.ts | 6 +- .../middleware.js | 70 ------- .../index.test.ts | 158 --------------- .../middleware-with-dynamic-code/square.wasm | Bin 63 -> 0 bytes 26 files changed, 403 insertions(+), 470 deletions(-) rename test/integration/{middleware-dynamic-code => edge-runtime-dynamic-code}/lib/square.wasm (100%) rename test/integration/{middleware-dynamic-code => edge-runtime-dynamic-code}/lib/utils.js (53%) rename test/integration/{middleware-dynamic-code => edge-runtime-dynamic-code}/lib/wasm.js (100%) rename test/integration/{middleware-dynamic-code => edge-runtime-dynamic-code}/middleware.js (58%) rename test/integration/{middleware-dynamic-code => edge-runtime-dynamic-code}/next.config.js (100%) create mode 100644 test/integration/edge-runtime-dynamic-code/pages/api/route.js rename test/integration/{middleware-dynamic-code => edge-runtime-dynamic-code}/pages/index.js (100%) create mode 100644 test/integration/edge-runtime-dynamic-code/test/index.test.js create mode 100644 test/integration/edge-runtime-with-node.js-apis/lib/utils.js create mode 100644 test/integration/edge-runtime-with-node.js-apis/middleware.js create mode 100644 test/integration/edge-runtime-with-node.js-apis/pages/api/route.js rename test/integration/{middleware-with-node.js-apis => edge-runtime-with-node.js-apis}/pages/index.js (100%) rename test/integration/{middleware-with-node.js-apis => edge-runtime-with-node.js-apis}/test/index.test.ts (56%) delete mode 100644 test/integration/middleware-dynamic-code/test/index.test.js delete mode 100644 test/integration/middleware-with-node.js-apis/middleware.js delete mode 100644 test/production/middleware-with-dynamic-code/index.test.ts delete mode 100644 test/production/middleware-with-dynamic-code/square.wasm diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index eedd8d31b1c3..ebdd8a173a35 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -380,7 +380,10 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`, } } }) - registerUnsupportedApiHooks(parser, compilation) + if (!dev) { + // do not issue compilation warning on dev: invoking code will provide details + registerUnsupportedApiHooks(parser, compilation) + } } } @@ -445,7 +448,7 @@ function getExtractMetadata(params: { compilation.errors.push( buildWebpackError({ - message: `Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Middleware ${entryName}${ + message: `Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime ${ typeof buildInfo.usingIndirectEval !== 'boolean' ? `\nUsed by ${Array.from(buildInfo.usingIndirectEval).join( ', ' diff --git a/packages/next/client/dev/error-overlay/format-webpack-messages.js b/packages/next/client/dev/error-overlay/format-webpack-messages.js index b99ea8e0692f..ed32aa2c0294 100644 --- a/packages/next/client/dev/error-overlay/format-webpack-messages.js +++ b/packages/next/client/dev/error-overlay/format-webpack-messages.js @@ -44,7 +44,7 @@ function formatMessage(message, verbose) { message.moduleTrace && message.moduleTrace.filter( (trace) => - !/next-(middleware|client-pages|flight-(client|server))-loader\.js/.test( + !/next-(middleware|client-pages|edge-function|flight-(client|server))-loader\.js/.test( trace.originName ) ) diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index f190a610ec41..5594c252cbd4 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -13,7 +13,6 @@ import type { RoutingItem } from '../base-server' import crypto from 'crypto' import fs from 'fs' -import chalk from 'next/dist/compiled/chalk' import { Worker } from 'next/dist/compiled/jest-worker' import findUp from 'next/dist/compiled/find-up' import { join as pathJoin, relative, resolve as pathResolve, sep } from 'path' @@ -714,7 +713,12 @@ export default class DevServer extends Server { page: string }) { try { - return super.runEdgeFunction(params) + return super.runEdgeFunction({ + ...params, + onWarning: (warn) => { + this.logErrorWithOriginalStack(warn, 'warning') + }, + }) } catch (error) { if (error instanceof DecodeError) { throw error @@ -797,7 +801,9 @@ export default class DevServer extends Server { const frames = parseStack(err.stack!) const frame = frames.find( ({ file }) => - !file?.startsWith('eval') && !file?.includes('web/adapter') + !file?.startsWith('eval') && + !file?.includes('web/adapter') && + !file?.includes('sandbox/context') )! if (frame.lineNumber && frame?.file) { @@ -838,12 +844,9 @@ export default class DevServer extends Server { `${file} (${lineNumber}:${column}) @ ${methodName}` ) if (src === 'edge-server') { - console[type === 'warning' ? 'warn' : 'error']( - `${(type === 'warning' ? chalk.yellow : chalk.red)( - err.name - )}: ${err.message}` - ) - } else if (type === 'warning') { + err = err.message + } + if (type === 'warning') { Log.warn(err) } else if (type) { Log.error(`${type}:`, err) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index f3e4ec22c14b..1b0c90806f9f 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -1247,12 +1247,7 @@ export default class NextNodeServer extends BaseServer { body: originalBody?.cloneBodyStream(), }, useCache: !this.nextConfig.experimental.runtime, - onWarning: (warning: Error) => { - if (params.onWarning) { - warning.message += ` "./${middlewareInfo.name}"` - params.onWarning(warning) - } - }, + onWarning: params.onWarning, }) for (let [key, value] of result.response.headers) { @@ -1531,6 +1526,7 @@ export default class NextNodeServer extends BaseServer { query: ParsedUrlQuery params: Params | undefined page: string + onWarning?: (warning: Error) => void }): Promise { let middlewareInfo: ReturnType | undefined @@ -1584,12 +1580,7 @@ export default class NextNodeServer extends BaseServer { : requestToBodyStream(nodeReq.originalRequest), }, useCache: !this.nextConfig.experimental.runtime, - onWarning: (_warning: Error) => { - // if (params.onWarning) { - // warning.message += ` "./${middlewareInfo.name}"` - // params.onWarning(warning) - // } - }, + onWarning: params.onWarning, }) params.res.statusCode = result.response.status diff --git a/packages/next/server/web/sandbox/context.ts b/packages/next/server/web/sandbox/context.ts index f166fe7a3f96..969135521116 100644 --- a/packages/next/server/web/sandbox/context.ts +++ b/packages/next/server/web/sandbox/context.ts @@ -120,7 +120,7 @@ async function createModuleContext(options: ModuleContextOptions) { if (!warnedEvals.has(key)) { const warning = getServerError( new Error( - `Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Middleware` + `Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime` ), 'edge-server' ) @@ -137,7 +137,7 @@ async function createModuleContext(options: ModuleContextOptions) { const key = fn.toString() if (!warnedWasmCodegens.has(key)) { const warning = getServerError( - new Error(`Dynamic WASM code generation (e. g. 'WebAssembly.compile') not allowed in Middleware. + new Error(`Dynamic WASM code generation (e. g. 'WebAssembly.compile') not allowed in Edge Runtime. Learn More: https://nextjs.org/docs/messages/middleware-dynamic-wasm-compilation`), 'edge-server' ) @@ -164,7 +164,7 @@ Learn More: https://nextjs.org/docs/messages/middleware-dynamic-wasm-compilation const key = fn.toString() if (instantiatedFromBuffer && !warnedWasmCodegens.has(key)) { const warning = getServerError( - new Error(`Dynamic WASM code generation ('WebAssembly.instantiate' with a buffer parameter) not allowed in Middleware. + new Error(`Dynamic WASM code generation ('WebAssembly.instantiate' with a buffer parameter) not allowed in Edge Runtime. Learn More: https://nextjs.org/docs/messages/middleware-dynamic-wasm-compilation`), 'edge-server' ) @@ -240,7 +240,7 @@ Learn More: https://nextjs.org/docs/messages/middleware-dynamic-wasm-compilation } for (const name of EDGE_UNSUPPORTED_NODE_APIS) { - addStub(context, name, options) + addStub(context, name) } Object.assign(context, wasm) @@ -285,9 +285,7 @@ function buildEnvironmentVariablesFrom( return env } -function createProcessPolyfill( - options: Pick -) { +function createProcessPolyfill(options: Pick) { const env = buildEnvironmentVariablesFrom(options.env) const processPolyfill = { env } @@ -296,8 +294,13 @@ function createProcessPolyfill( if (key === 'env') continue Object.defineProperty(processPolyfill, key, { get() { - emitWarning(`process.${key}`, options) - return overridenValue[key] + if (overridenValue[key]) { + return overridenValue[key] + } + if (typeof (process as any)[key] === 'function') { + return () => throwUnsupportedAPIError(`process.${key}`) + } + return undefined }, set(value) { overridenValue[key] = value @@ -308,35 +311,23 @@ function createProcessPolyfill( return processPolyfill } -const warnedAlready = new Set() - -function addStub( - context: Primitives, - name: string, - contextOptions: Pick -) { +function addStub(context: Primitives, name: string) { Object.defineProperty(context, name, { get() { - emitWarning(name, contextOptions) - return undefined + return function () { + throwUnsupportedAPIError(name) + } }, enumerable: false, }) } -function emitWarning( - name: string, - contextOptions: Pick -) { - if (!warnedAlready.has(name)) { - const warning = - new Error(`A Node.js API is used (${name}) which is not supported in the Edge Runtime. +function throwUnsupportedAPIError(name: string) { + const error = + new Error(`A Node.js API is used (${name}) which is not supported in the Edge Runtime. Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) - warning.name = 'NodejsRuntimeApiInMiddlewareWarning' - contextOptions.onWarning(warning) - console.warn(warning.message) - warnedAlready.add(name) - } + decorateServerError(error, 'edge-server') + throw error } function decorateUnhandledError(error: any) { diff --git a/packages/next/server/web/sandbox/sandbox.ts b/packages/next/server/web/sandbox/sandbox.ts index 0390a8f12275..62f42c491f53 100644 --- a/packages/next/server/web/sandbox/sandbox.ts +++ b/packages/next/server/web/sandbox/sandbox.ts @@ -8,7 +8,7 @@ export const ErrorSource = Symbol('SandboxError') type RunnerFn = (params: { name: string env: string[] - onWarning: (warn: Error) => void + onWarning?: (warn: Error) => void paths: string[] request: RequestData useCache: boolean @@ -19,7 +19,7 @@ type RunnerFn = (params: { export const run = withTaggedErrors(async (params) => { const { runtime, evaluateInContext } = await getModuleContext({ moduleName: params.name, - onWarning: params.onWarning, + onWarning: params.onWarning ?? (() => {}), useCache: params.useCache !== false, env: params.env, edgeFunctionEntry: params.edgeFunctionEntry, diff --git a/packages/react-dev-overlay/src/middleware.ts b/packages/react-dev-overlay/src/middleware.ts index 7f91741f2e54..32bbc67daf3f 100644 --- a/packages/react-dev-overlay/src/middleware.ts +++ b/packages/react-dev-overlay/src/middleware.ts @@ -344,7 +344,11 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) { return res.end() } - const filePath = path.resolve(options.rootDirectory, frameFile) + // frame files may start with their webpack layer, like (middleware)/middleware.js + const filePath = path.resolve( + options.rootDirectory, + frameFile.replace(/^\([^)]+\)\//, '') + ) const fileExists = await fs.access(filePath, FS.F_OK).then( () => true, () => false diff --git a/test/integration/middleware-dynamic-code/lib/square.wasm b/test/integration/edge-runtime-dynamic-code/lib/square.wasm similarity index 100% rename from test/integration/middleware-dynamic-code/lib/square.wasm rename to test/integration/edge-runtime-dynamic-code/lib/square.wasm diff --git a/test/integration/middleware-dynamic-code/lib/utils.js b/test/integration/edge-runtime-dynamic-code/lib/utils.js similarity index 53% rename from test/integration/middleware-dynamic-code/lib/utils.js rename to test/integration/edge-runtime-dynamic-code/lib/utils.js index 60fdfbe82411..94c8b93d719b 100644 --- a/test/integration/middleware-dynamic-code/lib/utils.js +++ b/test/integration/edge-runtime-dynamic-code/lib/utils.js @@ -1,3 +1,11 @@ +export const useCases = { + eval: 'using-eval', + noEval: 'not-using-eval', + wasmCompile: 'using-webassembly-compile', + wasmInstanciate: 'using-webassembly-instantiate', + wasmBufferInstanciate: 'using-webassembly-instantiate-with-buffer', +} + export async function usingEval() { // eslint-disable-next-line no-eval return { value: eval('100') } diff --git a/test/integration/middleware-dynamic-code/lib/wasm.js b/test/integration/edge-runtime-dynamic-code/lib/wasm.js similarity index 100% rename from test/integration/middleware-dynamic-code/lib/wasm.js rename to test/integration/edge-runtime-dynamic-code/lib/wasm.js diff --git a/test/integration/middleware-dynamic-code/middleware.js b/test/integration/edge-runtime-dynamic-code/middleware.js similarity index 58% rename from test/integration/middleware-dynamic-code/middleware.js rename to test/integration/edge-runtime-dynamic-code/middleware.js index da82cfa24bbb..68cfd8b418a4 100644 --- a/test/integration/middleware-dynamic-code/middleware.js +++ b/test/integration/edge-runtime-dynamic-code/middleware.js @@ -1,4 +1,5 @@ -import { notUsingEval, usingEval } from './lib/utils' +import { NextResponse } from 'next/server' +import { useCases, notUsingEval, usingEval } from './lib/utils' import { usingWebAssemblyCompile, usingWebAssemblyInstantiate, @@ -6,37 +7,41 @@ import { } from './lib/wasm' export async function middleware(request) { - if (request.nextUrl.pathname === '/using-eval') { + if (request.nextUrl.pathname === `/${useCases.eval}`) { return new Response(null, { headers: { data: JSON.stringify(await usingEval()) }, }) } - if (request.nextUrl.pathname === '/not-using-eval') { + if (request.nextUrl.pathname === `/${useCases.noEval}`) { return new Response(null, { headers: { data: JSON.stringify(await notUsingEval()) }, }) } - if (request.nextUrl.pathname === '/using-webassembly-compile') { + if (request.nextUrl.pathname === `/${useCases.wasmCompile}`) { return new Response(null, { headers: { data: JSON.stringify(await usingWebAssemblyCompile(9)) }, }) } - if (request.nextUrl.pathname === '/using-webassembly-instantiate') { + if (request.nextUrl.pathname === `/${useCases.wasmInstanciate}`) { return new Response(null, { headers: { data: JSON.stringify(await usingWebAssemblyInstantiate(9)) }, }) } - if ( - request.nextUrl.pathname === '/using-webassembly-instantiate-with-buffer' - ) { + if (request.nextUrl.pathname === `/${useCases.wasmBufferInstanciate}`) { return new Response(null, { headers: { data: JSON.stringify(await usingWebAssemblyInstantiateWithBuffer(9)), }, }) } + + return NextResponse.next() +} + +export const config = { + matcher: Object.values(useCases).map((route) => `/${route}`), } diff --git a/test/integration/middleware-dynamic-code/next.config.js b/test/integration/edge-runtime-dynamic-code/next.config.js similarity index 100% rename from test/integration/middleware-dynamic-code/next.config.js rename to test/integration/edge-runtime-dynamic-code/next.config.js diff --git a/test/integration/edge-runtime-dynamic-code/pages/api/route.js b/test/integration/edge-runtime-dynamic-code/pages/api/route.js new file mode 100644 index 000000000000..1d73702aec60 --- /dev/null +++ b/test/integration/edge-runtime-dynamic-code/pages/api/route.js @@ -0,0 +1,26 @@ +import { useCases, notUsingEval, usingEval } from '../../lib/utils' +import { + usingWebAssemblyCompile, + usingWebAssemblyInstantiate, + usingWebAssemblyInstantiateWithBuffer, +} from '../../lib/wasm' + +export default async function handler(request) { + const useCase = request.nextUrl.searchParams.get('case') + + return Response.json( + useCase === useCases.eval + ? await usingEval() + : useCase === useCases.noEval + ? await notUsingEval() + : useCase === useCases.wasmCompile + ? await usingWebAssemblyCompile(9) + : useCase === useCases.wasmInstanciate + ? await usingWebAssemblyInstantiate(9) + : useCase === useCases.wasmBufferInstanciate + ? await usingWebAssemblyInstantiateWithBuffer(9) + : { ok: true } + ) +} + +export const config = { runtime: 'experimental-edge' } diff --git a/test/integration/middleware-dynamic-code/pages/index.js b/test/integration/edge-runtime-dynamic-code/pages/index.js similarity index 100% rename from test/integration/middleware-dynamic-code/pages/index.js rename to test/integration/edge-runtime-dynamic-code/pages/index.js diff --git a/test/integration/edge-runtime-dynamic-code/test/index.test.js b/test/integration/edge-runtime-dynamic-code/test/index.test.js new file mode 100644 index 000000000000..f9b41e549b34 --- /dev/null +++ b/test/integration/edge-runtime-dynamic-code/test/index.test.js @@ -0,0 +1,180 @@ +/* eslint-env jest */ + +import stripAnsi from 'next/dist/compiled/strip-ansi' +import { join } from 'path' +import { + fetchViaHTTP, + findPort, + killApp, + launchApp, + nextBuild, + renderViaHTTP, + waitFor, +} from 'next-test-utils' + +const EVAL_ERROR = `Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime` +const DYNAMIC_CODE_ERROR = `Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime` +const WASM_COMPILE_ERROR = `Dynamic WASM code generation (e. g. 'WebAssembly.compile') not allowed in Edge Runtime` +const WASM_INSTANTIATE_ERROR = `Dynamic WASM code generation ('WebAssembly.instantiate' with a buffer parameter) not allowed in Edge Runtime` + +jest.setTimeout(1000 * 60 * 2) +const context = { + appDir: join(__dirname, '../'), +} + +describe('Page using eval in dev mode', () => { + let output = '' + + beforeAll(async () => { + context.appPort = await findPort() + context.app = await launchApp(context.appDir, context.appPort, { + env: { __NEXT_TEST_WITH_DEVTOOL: 1 }, + onStdout(msg) { + output += msg + }, + onStderr(msg) { + output += msg + }, + }) + }) + + beforeEach(() => (output = '')) + afterAll(() => killApp(context.app)) + + it('does issue dynamic code evaluation warnings', async () => { + const html = await renderViaHTTP(context.appPort, '/') + expect(html).toMatch(/>.*?100.*?and.*?100.*?<\//) + await waitFor(500) + expect(output).not.toContain(EVAL_ERROR) + expect(output).not.toContain(DYNAMIC_CODE_ERROR) + expect(output).not.toContain(WASM_COMPILE_ERROR) + expect(output).not.toContain(WASM_INSTANTIATE_ERROR) + }) +}) + +describe.each([ + { + title: 'Middleware', + computeRoute(useCase) { + return `/${useCase}` + }, + async extractValue(response) { + return JSON.parse(response.headers.get('data')).value + }, + }, + { + title: 'Edge route', + computeRoute(useCase) { + return `/api/route?case=${useCase}` + }, + async extractValue(response) { + return (await response.json()).value + }, + }, +])( + '$title usage of dynamic code evaluation', + ({ extractValue, computeRoute }) => { + describe('dev mode', () => { + let output = '' + + beforeAll(async () => { + context.appPort = await findPort() + context.app = await launchApp(context.appDir, context.appPort, { + env: { __NEXT_TEST_WITH_DEVTOOL: 1 }, + onStdout(msg) { + output += msg + }, + onStderr(msg) { + output += msg + }, + }) + }) + + beforeEach(() => (output = '')) + afterAll(() => killApp(context.app)) + + it('shows a warning when running code with eval', async () => { + const res = await fetchViaHTTP( + context.appPort, + computeRoute('using-eval') + ) + expect(await extractValue(res)).toEqual(100) + await waitFor(500) + expect(output).toContain(EVAL_ERROR) + // TODO check why that has a backslash on windows + expect(output).toMatch(/lib[\\/]utils\.js/) + expect(output).toContain('usingEval') + expect(stripAnsi(output)).toContain("value: eval('100')") + }) + + it('does not show warning when no code uses eval', async () => { + const res = await fetchViaHTTP( + context.appPort, + computeRoute('not-using-eval') + ) + expect(await extractValue(res)).toEqual(100) + await waitFor(500) + expect(output).not.toContain('Dynamic Code Evaluation') + }) + + it('shows a warning when running WebAssembly.compile', async () => { + const res = await fetchViaHTTP( + context.appPort, + computeRoute('using-webassembly-compile') + ) + expect(await extractValue(res)).toEqual(81) + await waitFor(500) + expect(output).toContain(WASM_COMPILE_ERROR) + expect(output).toMatch(/lib[\\/]wasm\.js/) + expect(output).toContain('usingWebAssemblyCompile') + expect(stripAnsi(output)).toContain( + 'await WebAssembly.compile(SQUARE_WASM_BUFFER)' + ) + }) + + it('shows a warning when running WebAssembly.instantiate with a buffer parameter', async () => { + const res = await fetchViaHTTP( + context.appPort, + computeRoute('using-webassembly-instantiate-with-buffer') + ) + expect(await extractValue(res)).toEqual(81) + await waitFor(500) + expect(output).toContain(WASM_INSTANTIATE_ERROR) + expect(output).toMatch(/lib[\\/]wasm\.js/) + expect(output).toContain('usingWebAssemblyInstantiateWithBuffer') + expect(stripAnsi(output)).toContain( + 'await WebAssembly.instantiate(SQUARE_WASM_BUFFER, {})' + ) + }) + + it('does not show a warning when running WebAssembly.instantiate with a module parameter', async () => { + const res = await fetchViaHTTP( + context.appPort, + computeRoute('using-webassembly-instantiate') + ) + expect(await extractValue(res)).toEqual(81) + await waitFor(500) + expect(output).not.toContain(WASM_INSTANTIATE_ERROR) + expect(output).not.toContain('DynamicWasmCodeGenerationWarning') + }) + }) + + describe('production mode', () => { + let buildResult + + beforeAll(async () => { + buildResult = await nextBuild(context.appDir, undefined, { + stderr: true, + stdout: true, + }) + }) + + it('should have middleware warning during build', () => { + expect(buildResult.stderr).toContain(`Failed to compile`) + expect(buildResult.stderr).toContain(`Used by usingEval, usingEvalSync`) + expect(buildResult.stderr).toContain(`Used by usingWebAssemblyCompile`) + expect(buildResult.stderr).toContain(DYNAMIC_CODE_ERROR) + }) + }) + } +) diff --git a/test/integration/edge-runtime-with-node.js-apis/lib/utils.js b/test/integration/edge-runtime-with-node.js-apis/lib/utils.js new file mode 100644 index 000000000000..9206e7a9086f --- /dev/null +++ b/test/integration/edge-runtime-with-node.js-apis/lib/utils.js @@ -0,0 +1,53 @@ +/* eslint-disable no-undef */ +export function invokeNodeAPI(useCase) { + let handle + if (useCase === 'Buffer') { + Buffer.from('') + } else if (useCase === 'setImmediate') { + handle = setImmediate(() => {}) + } else if (useCase === 'clearImmediate') { + clearImmediate(handle) + } else if (useCase === 'process.cwd') { + console.log(process.cwd()) + } else if (useCase === 'process.getuid') { + console.log(process.getuid()) + } else if (useCase === 'process.cpuUsage') { + console.log(process.cpuUsage()) + } else if (useCase === 'BroadcastChannel') { + new BroadcastChannel() + } else if (useCase === 'ByteLengthQueuingStrategy') { + new ByteLengthQueuingStrategy() + } else if (useCase === 'CompressionStream') { + new CompressionStream() + } else if (useCase === 'CountQueuingStrategy') { + new CountQueuingStrategy() + } else if (useCase === 'DecompressionStream') { + new DecompressionStream() + } else if (useCase === 'DomException') { + new DomException() + } else if (useCase === 'MessageChannel') { + new MessageChannel() + } else if (useCase === 'MessageEvent') { + new MessageEvent() + } else if (useCase === 'MessagePort') { + new MessagePort() + } else if (useCase === 'ReadableByteStreamController') { + new ReadableByteStreamController() + } else if (useCase === 'ReadableStreamBYOBRequest') { + new ReadableStreamBYOBRequest() + } else if (useCase === 'ReadableStreamDefaultController') { + new ReadableStreamDefaultController() + } else if (useCase === 'TextDecoderStream') { + new TextDecoderStream() + } else if (useCase === 'TextEncoderStream') { + new TextEncoderStream() + } else if (useCase === 'TransformStreamDefaultController') { + new TransformStreamDefaultController() + } else if (useCase === 'WritableStreamDefaultController') { + new WritableStreamDefaultController() + } else if (useCase === 'process.version') { + console.log(process.version) + } else if (useCase === 'process.arch') { + console.log(process.arch) + } +} diff --git a/test/integration/edge-runtime-with-node.js-apis/middleware.js b/test/integration/edge-runtime-with-node.js-apis/middleware.js new file mode 100644 index 000000000000..01aa356a4723 --- /dev/null +++ b/test/integration/edge-runtime-with-node.js-apis/middleware.js @@ -0,0 +1,9 @@ +import { NextResponse } from 'next/server' +import { invokeNodeAPI } from './lib/utils' + +export default function middleware({ nextUrl }) { + invokeNodeAPI(nextUrl.pathname.slice(1)) + return nextUrl.pathname.startsWith('/api') + ? NextResponse.next() + : NextResponse.rewrite(new URL('/', nextUrl)) +} diff --git a/test/integration/edge-runtime-with-node.js-apis/pages/api/route.js b/test/integration/edge-runtime-with-node.js-apis/pages/api/route.js new file mode 100644 index 000000000000..8c3802625615 --- /dev/null +++ b/test/integration/edge-runtime-with-node.js-apis/pages/api/route.js @@ -0,0 +1,8 @@ +import { invokeNodeAPI } from '../../lib/utils' + +export default async function handler(request) { + invokeNodeAPI(request.nextUrl.searchParams.get('case')) + return Response.json({ ok: true }) +} + +export const config = { runtime: 'experimental-edge' } diff --git a/test/integration/middleware-with-node.js-apis/pages/index.js b/test/integration/edge-runtime-with-node.js-apis/pages/index.js similarity index 100% rename from test/integration/middleware-with-node.js-apis/pages/index.js rename to test/integration/edge-runtime-with-node.js-apis/pages/index.js diff --git a/test/integration/middleware-with-node.js-apis/test/index.test.ts b/test/integration/edge-runtime-with-node.js-apis/test/index.test.ts similarity index 56% rename from test/integration/middleware-with-node.js-apis/test/index.test.ts rename to test/integration/edge-runtime-with-node.js-apis/test/index.test.ts index 2f68e908c9d2..090395b2afd4 100644 --- a/test/integration/middleware-with-node.js-apis/test/index.test.ts +++ b/test/integration/edge-runtime-with-node.js-apis/test/index.test.ts @@ -1,8 +1,8 @@ /* eslint-env jest */ import { remove } from 'fs-extra' +import stripAnsi from 'next/dist/compiled/strip-ansi' import { - check, fetchViaHTTP, findPort, killApp, @@ -19,8 +19,14 @@ const unsupportedFunctions = [ 'clearImmediate', // no need to test all of the process methods 'process.cwd', + 'process.cpuUsage', 'process.getuid', ] +const undefinedPropertoes = [ + // no need to test all of the process properties + 'process.arch', + 'process.version', +] const unsupportedClasses = [ 'BroadcastChannel', 'ByteLengthQueuingStrategy', @@ -40,7 +46,20 @@ const unsupportedClasses = [ 'WritableStreamDefaultController', ] -describe('Middleware using Node.js API', () => { +describe.each([ + { + title: 'Middleware', + computeRoute(useCase) { + return `/${useCase}` + }, + }, + { + title: 'Edge route', + computeRoute(useCase) { + return `/api/route?case=${useCase}` + }, + }, +])('$title using Node.js API', ({ computeRoute }) => { const appDir = join(__dirname, '..') describe('dev mode', () => { @@ -64,37 +83,33 @@ describe('Middleware using Node.js API', () => { afterAll(() => killApp(app)) + it.each(undefinedPropertoes.map((api) => ({ api })))( + 'does not throw on using $api', + async ({ api }) => { + const res = await fetchViaHTTP(appPort, computeRoute(api)) + expect(res.status).toBe(200) + await waitFor(500) + expect(output).not.toInclude(`A Node.js API is used (${api})`) + } + ) + it.each([ - { - api: 'Buffer', - error: process.version.startsWith('v16') - ? `Cannot read properties of undefined (reading 'from')` - : `Cannot read property 'from' of undefined`, - }, ...unsupportedFunctions.map((api) => ({ api, - error: `${api} is not a function`, + errorHighlight: `${api}(`, })), ...unsupportedClasses.map((api) => ({ api, - error: `${api} is not a constructor`, + errorHighlight: `new ${api}(`, })), - ])(`shows error when using $api`, async ({ api, error }) => { - const res = await fetchViaHTTP(appPort, `/${api}`) - await waitFor(500) + ])(`throws error when using $api`, async ({ api, errorHighlight }) => { + const res = await fetchViaHTTP(appPort, computeRoute(api)) expect(res.status).toBe(500) - await check( - () => - output.includes(`A Node.js API is used (${api}) which is not supported in the Edge Runtime. + await waitFor(500) + expect(output) + .toInclude(`A Node.js API is used (${api}) which is not supported in the Edge Runtime. Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) - ? 'success' - : output, - 'success' - ) - await check( - () => (output.includes(`TypeError: ${error}`) ? 'success' : output), - 'success' - ) + expect(stripAnsi(output)).toInclude(errorHighlight) }) }) @@ -113,13 +128,17 @@ Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) ['Buffer', ...unsupportedFunctions, ...unsupportedClasses].map( (api, index) => ({ api, - line: 5 + index * 3, }) ) - )(`warns for $api during build`, ({ api, line }) => { - expect(buildResult.stderr) - .toContain(`A Node.js API is used (${api} at line: ${line}) which is not supported in the Edge Runtime. -Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) + )(`warns for $api during build`, ({ api }) => { + expect(buildResult.stderr).toContain(`A Node.js API is used (${api}`) }) + + it.each(undefinedPropertoes.map((api) => ({ api })))( + 'does not warn on using $api', + ({ api }) => { + expect(buildResult.stderr).toContain(`A Node.js API is used (${api}`) + } + ) }) }) diff --git a/test/integration/middleware-dev-errors/test/index.test.js b/test/integration/middleware-dev-errors/test/index.test.js index 1045179f897f..1d8f32ee27b0 100644 --- a/test/integration/middleware-dev-errors/test/index.test.js +++ b/test/integration/middleware-dev-errors/test/index.test.js @@ -58,7 +58,7 @@ describe('Middleware development errors', () => { const output = stripAnsi(context.logs.output) expect(output).toMatch( new RegExp( - `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ Object.__WEBPACK_DEFAULT_EXPORT__ \\[as handler\\]\nError: boom`, + `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ Object.__WEBPACK_DEFAULT_EXPORT__ \\[as handler\\]\nerror - boom`, 'm' ) ) @@ -93,7 +93,7 @@ describe('Middleware development errors', () => { const output = stripAnsi(context.logs.output) expect(output).toMatch( new RegExp( - `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ throwError\nError: async boom!`, + `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ throwError\nerror - unhandledRejection: async boom!`, 'm' ) ) @@ -124,7 +124,7 @@ describe('Middleware development errors', () => { const output = stripAnsi(context.logs.output) expect(output).toMatch( new RegExp( - `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ eval\nReferenceError: test is not defined`, + `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ eval\nerror - test is not defined`, 'm' ) ) @@ -157,7 +157,7 @@ describe('Middleware development errors', () => { const output = stripAnsi(context.logs.output) expect(output).toMatch( new RegExp( - `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ \nError: booooom!`, + `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ \nerror - booooom!`, 'm' ) ) @@ -195,7 +195,7 @@ describe('Middleware development errors', () => { const output = stripAnsi(context.logs.output) expect(output).toMatch( new RegExp( - `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ eval\nError: you shall see me`, + `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ eval\nerror - unhandledRejection: you shall see me`, 'm' ) ) @@ -227,7 +227,7 @@ describe('Middleware development errors', () => { const output = stripAnsi(context.logs.output) expect(output).toMatch( new RegExp( - `error - \\(middleware\\)/lib/unhandled.js \\(\\d+:\\d+\\) @ Timeout.eval \\[as _onTimeout\\]\nError: This file asynchronously fails while loading`, + `error - \\(middleware\\)/lib/unhandled.js \\(\\d+:\\d+\\) @ Timeout.eval \\[as _onTimeout\\]\nerror - uncaughtException: This file asynchronously fails while loading`, 'm' ) ) diff --git a/test/integration/middleware-dynamic-code/test/index.test.js b/test/integration/middleware-dynamic-code/test/index.test.js deleted file mode 100644 index 0f113d468161..000000000000 --- a/test/integration/middleware-dynamic-code/test/index.test.js +++ /dev/null @@ -1,139 +0,0 @@ -/* eslint-env jest */ - -import stripAnsi from 'next/dist/compiled/strip-ansi' -import { join } from 'path' -import { - fetchViaHTTP, - findPort, - killApp, - launchApp, - nextBuild, - renderViaHTTP, - waitFor, -} from 'next-test-utils' - -const context = {} -const EVAL_ERROR = `Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Middleware` -const DYNAMIC_CODE_ERROR = `Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Middleware` -const WASM_COMPILE_ERROR = `Dynamic WASM code generation (e. g. 'WebAssembly.compile') not allowed in Middleware` -const WASM_INSTANTIATE_ERROR = `Dynamic WASM code generation ('WebAssembly.instantiate' with a buffer parameter) not allowed in Middleware` - -jest.setTimeout(1000 * 60 * 2) -context.appDir = join(__dirname, '../') - -describe('Middleware usage of dynamic code evaluation', () => { - describe('dev mode', () => { - let output = '' - - beforeAll(async () => { - context.appPort = await findPort() - context.app = await launchApp(context.appDir, context.appPort, { - env: { __NEXT_TEST_WITH_DEVTOOL: 1 }, - onStdout(msg) { - output += msg - }, - onStderr(msg) { - output += msg - }, - }) - }) - - beforeEach(() => (output = '')) - afterAll(() => killApp(context.app)) - - it('shows a warning when running code with eval', async () => { - const res = await fetchViaHTTP(context.appPort, `/using-eval`) - const json = JSON.parse(res.headers.get('data')) - await waitFor(500) - expect(json.value).toEqual(100) - expect(output).toContain(EVAL_ERROR) - expect(output).toContain('DynamicCodeEvaluationWarning') - expect(output).toContain('./middleware') - // TODO check why that has a backslash on windows - expect(output).toMatch(/lib[\\/]utils\.js/) - expect(output).toContain('usingEval') - expect(stripAnsi(output)).toContain("value: eval('100')") - }) - - it('does not show warning when no code uses eval', async () => { - const res = await fetchViaHTTP(context.appPort, `/not-using-eval`) - const json = JSON.parse(res.headers.get('data')) - await waitFor(500) - expect(json.value).toEqual(100) - expect(output).not.toContain('Dynamic Code Evaluation') - }) - - it('does not has problems with eval in page or server code', async () => { - const html = await renderViaHTTP(context.appPort, `/`) - expect(html).toMatch(/>.*?100.*?and.*?100.*?<\//) - await waitFor(500) - expect(output).not.toContain('Dynamic Code Evaluation') - }) - - it('shows a warning when running WebAssembly.compile', async () => { - const res = await fetchViaHTTP( - context.appPort, - `/using-webassembly-compile` - ) - const json = JSON.parse(res.headers.get('data')) - await waitFor(500) - expect(json.value).toEqual(81) - expect(output).toContain(WASM_COMPILE_ERROR) - expect(output).toContain('DynamicWasmCodeGenerationWarning') - expect(output).toContain('./middleware') - expect(output).toMatch(/lib[\\/]wasm\.js/) - expect(output).toContain('usingWebAssemblyCompile') - expect(stripAnsi(output)).toContain( - 'await WebAssembly.compile(SQUARE_WASM_BUFFER)' - ) - }) - - it('shows a warning when running WebAssembly.instantiate w/ a buffer parameter', async () => { - const res = await fetchViaHTTP( - context.appPort, - `/using-webassembly-instantiate-with-buffer` - ) - const json = JSON.parse(res.headers.get('data')) - await waitFor(500) - expect(json.value).toEqual(81) - expect(output).toContain(WASM_INSTANTIATE_ERROR) - expect(output).toContain('DynamicWasmCodeGenerationWarning') - expect(output).toContain('./middleware') - expect(output).toMatch(/lib[\\/]wasm\.js/) - expect(output).toContain('usingWebAssemblyInstantiateWithBuffer') - expect(stripAnsi(output)).toContain( - 'await WebAssembly.instantiate(SQUARE_WASM_BUFFER, {})' - ) - }) - - it('does not show a warning when running WebAssembly.instantiate w/ a module parameter', async () => { - const res = await fetchViaHTTP( - context.appPort, - `/using-webassembly-instantiate` - ) - const json = JSON.parse(res.headers.get('data')) - await waitFor(500) - expect(json.value).toEqual(81) - expect(output).not.toContain(WASM_INSTANTIATE_ERROR) - expect(output).not.toContain('DynamicWasmCodeGenerationWarning') - }) - }) - - describe('production mode', () => { - let buildResult - - beforeAll(async () => { - buildResult = await nextBuild(context.appDir, undefined, { - stderr: true, - stdout: true, - }) - }) - - it('should have middleware warning during build', () => { - expect(buildResult.stderr).toContain(`Failed to compile`) - expect(buildResult.stderr).toContain(`Used by usingEval`) - expect(buildResult.stderr).toContain(`./middleware.js`) - expect(buildResult.stderr).toContain(DYNAMIC_CODE_ERROR) - }) - }) -}) diff --git a/test/integration/middleware-overrides-node.js-api/test/index.test.ts b/test/integration/middleware-overrides-node.js-api/test/index.test.ts index 31baba32f499..c0ec255d6b1e 100644 --- a/test/integration/middleware-overrides-node.js-api/test/index.test.ts +++ b/test/integration/middleware-overrides-node.js-api/test/index.test.ts @@ -32,14 +32,14 @@ describe('Middleware overriding a Node.js API', () => { afterAll(() => killApp(context.app)) - it('shows a warning but allows overriding', async () => { + it('does not show a warning and allows overriding', async () => { const res = await fetchViaHTTP(context.appPort, '/') await waitFor(500) expect(res.status).toBe(200) - expect(output).toContain('A Node.js API is used (process.cwd') expect(output).toContain('fixed-value') expect(output).not.toContain('TypeError') - expect(output).not.toContain(`A Node.js API is used (process.env`) + expect(output).not.toContain('A Node.js API is used (process.env') + expect(output).not.toContain('A Node.js API is used (process.cwd') }) }) }) diff --git a/test/integration/middleware-with-node.js-apis/middleware.js b/test/integration/middleware-with-node.js-apis/middleware.js deleted file mode 100644 index c635eb43807a..000000000000 --- a/test/integration/middleware-with-node.js-apis/middleware.js +++ /dev/null @@ -1,70 +0,0 @@ -/* eslint-disable no-undef */ -import { NextResponse } from 'next/server' - -export default function middleware({ nextUrl: { pathname } }) { - let handle - if (pathname === '/Buffer') { - Buffer.from('') - } - if (pathname === '/setImmediate') { - handle = setImmediate(() => {}) - } - if (pathname === '/clearImmediate') { - clearImmediate(handle) - } - if (pathname === '/process.cwd') { - console.log(process.cwd()) - } - if (pathname === '/process.getuid') { - console.log(process.getuid()) - } - if (pathname === '/BroadcastChannel') { - new BroadcastChannel() - } - if (pathname === '/ByteLengthQueuingStrategy') { - new ByteLengthQueuingStrategy() - } - if (pathname === '/CompressionStream') { - new CompressionStream() - } - if (pathname === '/CountQueuingStrategy') { - new CountQueuingStrategy() - } - if (pathname === '/DecompressionStream') { - new DecompressionStream() - } - if (pathname === '/DomException') { - new DomException() - } - if (pathname === '/MessageChannel') { - new MessageChannel() - } - if (pathname === '/MessageEvent') { - new MessageEvent() - } - if (pathname === '/MessagePort') { - new MessagePort() - } - if (pathname === '/ReadableByteStreamController') { - new ReadableByteStreamController() - } - if (pathname === '/ReadableStreamBYOBRequest') { - new ReadableStreamBYOBRequest() - } - if (pathname === '/ReadableStreamDefaultController') { - new ReadableStreamDefaultController() - } - if (pathname === '/TextDecoderStream') { - new TextDecoderStream() - } - if (pathname === '/TextEncoderStream') { - new TextEncoderStream() - } - if (pathname === '/TransformStreamDefaultController') { - new TransformStreamDefaultController() - } - if (pathname === '/WritableStreamDefaultController') { - new WritableStreamDefaultController() - } - return NextResponse.next() -} diff --git a/test/production/middleware-with-dynamic-code/index.test.ts b/test/production/middleware-with-dynamic-code/index.test.ts deleted file mode 100644 index 26c7cc9ffa4a..000000000000 --- a/test/production/middleware-with-dynamic-code/index.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { createNext, FileRef } from 'e2e-utils' -import { join } from 'path' -import { NextInstance } from 'test/lib/next-modes/base' - -describe('Middleware with Dynamic code invocations', () => { - const DYNAMIC_CODE_EVAL_ERROR = `Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Middleware middleware` - - let next: NextInstance - - beforeAll(async () => { - next = await createNext({ - files: { - 'lib/utils.js': '', - 'lib/square.wasm': new FileRef(join(__dirname, 'square.wasm')), - 'pages/index.js': ` - export default function () { return
Hello, world!
} - `, - 'middleware.js': ` - import './lib/utils' - export default function middleware() { - return new Response() - } - `, - }, - dependencies: { - '@apollo/react-hooks': '3.1.5', - '@aws-sdk/client-s3': 'latest', - 'apollo-client': 'latest', - graphql: 'latest', - 'graphql-tag': 'latest', - has: 'latest', - qs: 'latest', - }, - installCommand: 'yarn install', - }) - await next.stop() - }) - - afterAll(() => next.destroy()) - beforeEach(() => next.stop()) - - it('detects dynamic code nested in @apollo/react-hooks', async () => { - await next.patchFile( - 'lib/utils.js', - ` - import { useQuery } from '@apollo/react-hooks' - import gql from 'graphql-tag' - - export default function useGreeting() { - return useQuery( - gql\` - query getGreeting($language: String!) { - greeting(language: $language) { - message - } - } - \`, - { variables: { language: 'english' } } - ) - } - ` - ) - await expect(next.start()).rejects.toThrow() - expect(next.cliOutput).toContain(` -./node_modules/ts-invariant/lib/invariant.esm.js -${DYNAMIC_CODE_EVAL_ERROR}`) - }) - - it('detects dynamic code nested in has', async () => { - await next.patchFile( - 'lib/utils.js', - ` - import has from 'has' - has(Object.prototype, 'hasOwnProperty') - ` - ) - await expect(next.start()).rejects.toThrow() - expect(next.cliOutput).toContain(` -./node_modules/function-bind/implementation.js -${DYNAMIC_CODE_EVAL_ERROR}`) - expect(next.cliOutput).toContain(` -./node_modules/has/src/index.js -${DYNAMIC_CODE_EVAL_ERROR}`) - }) - - it('detects dynamic code nested in qs', async () => { - await next.patchFile( - 'lib/utils.js', - ` - import qs from 'qs' - qs.parse('a=c') - ` - ) - await expect(next.start()).rejects.toThrow() - expect(next.cliOutput).toContain(` -./node_modules/get-intrinsic/index.js -${DYNAMIC_CODE_EVAL_ERROR}`) - }) - - it('does not detects dynamic code nested in @aws-sdk/client-s3 (legit Function.bind)', async () => { - await next.patchFile( - 'lib/utils.js', - ` - import { S3Client, AbortMultipartUploadCommand } from '@aws-sdk/client-s3' - new S3Client().send(new AbortMultipartUploadCommand({})) - ` - ) - // this previously threw from a module not found error - // although this is fixed now - await next.start() - - expect(next.cliOutput).not.toContain( - `./node_modules/@aws-sdk/smithy-client/dist-es/lazy-json.js` - ) - expect(next.cliOutput).not.toContain(DYNAMIC_CODE_EVAL_ERROR) - }) - - it('does not determine WebAssembly.instantiate with a module parameter as dynamic code execution (legit)', async () => { - await next.patchFile( - 'lib/utils.js', - ` - import wasm from './square.wasm?module' - const instance = WebAssembly.instantiate(wasm) - ` - ) - await next.start() - - expect(next.cliOutput).not.toContain(DYNAMIC_CODE_EVAL_ERROR) - }) - - // Actually this causes a dynamic code evaluation however, we can't determine the type of - // first parameter of WebAssembly.instanntiate statically. - it('does not determine WebAssembly.instantiate with a buffer parameter as dynamic code execution', async () => { - await next.patchFile( - 'lib/utils.js', - ` - const instance = WebAssembly.instantiate(new Uint8Array([0, 1, 2, 3])) - ` - ) - await next.start() - - expect(next.cliOutput).not.toContain(DYNAMIC_CODE_EVAL_ERROR) - }) - - it('detects use of WebAssembly.compile', async () => { - await next.patchFile( - 'lib/utils.js', - ` - const module = WebAssembly.compile(new Uint8Array([0, 1, 2, 3])) - ` - ) - - await expect(next.start()).rejects.toThrow() - expect(next.cliOutput).toContain(` -./lib/utils.js -${DYNAMIC_CODE_EVAL_ERROR}`) - }) -}) diff --git a/test/production/middleware-with-dynamic-code/square.wasm b/test/production/middleware-with-dynamic-code/square.wasm deleted file mode 100644 index f836887dda8004a4f54c3897c1d349b676293119..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63 zcmWN