From ec69eebba9fcfbbc12a9a65bf818ad2f5ce64d3c Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Sun, 14 Sep 2025 11:11:52 +0200 Subject: [PATCH 1/2] test: add node middleware with pnpm test case --- tests/e2e/middleware.test.ts | 83 ++++++++++++++++++++++--------- tests/utils/create-e2e-fixture.ts | 4 ++ 2 files changed, 63 insertions(+), 24 deletions(-) diff --git a/tests/e2e/middleware.test.ts b/tests/e2e/middleware.test.ts index 505434b849..a25de675a4 100644 --- a/tests/e2e/middleware.test.ts +++ b/tests/e2e/middleware.test.ts @@ -569,33 +569,68 @@ for (const { expectedRuntime, isNodeMiddleware, label, testWithSwitchableMiddlew if (isNodeMiddleware) { // Node.js Middleware specific tests to test features not available in Edge Runtime test.describe('Node.js Middleware specific', () => { - test('node:crypto module', async ({ middlewareNodeRuntimeSpecific }) => { - const response = await fetch(`${middlewareNodeRuntimeSpecific.url}/test/crypto`) - expect(response.status).toBe(200) - const body = await response.json() - expect( - body.random, - 'random should have 16 random bytes generated with `randomBytes` function from node:crypto in hex format', - ).toMatch(/[0-9a-f]{32}/) - }) + test.describe('npm package manager', () => { + test('node:crypto module', async ({ middlewareNodeRuntimeSpecific }) => { + const response = await fetch(`${middlewareNodeRuntimeSpecific.url}/test/crypto`) + expect(response.status).toBe(200) + const body = await response.json() + expect( + body.random, + 'random should have 16 random bytes generated with `randomBytes` function from node:crypto in hex format', + ).toMatch(/[0-9a-f]{32}/) + }) - test('node:http(s) module', async ({ middlewareNodeRuntimeSpecific }) => { - const response = await fetch(`${middlewareNodeRuntimeSpecific.url}/test/http`) - expect(response.status).toBe(200) - const body = await response.json() - expect( - body.proxiedWithHttpRequest, - 'proxiedWithHttpRequest should be the result of `http.request` from node:http fetching static asset', - ).toStrictEqual({ hello: 'world' }) + test('node:http(s) module', async ({ middlewareNodeRuntimeSpecific }) => { + const response = await fetch(`${middlewareNodeRuntimeSpecific.url}/test/http`) + expect(response.status).toBe(200) + const body = await response.json() + expect( + body.proxiedWithHttpRequest, + 'proxiedWithHttpRequest should be the result of `http.request` from node:http fetching static asset', + ).toStrictEqual({ hello: 'world' }) + }) + + test('node:path module', async ({ middlewareNodeRuntimeSpecific }) => { + const response = await fetch(`${middlewareNodeRuntimeSpecific.url}/test/path`) + expect(response.status).toBe(200) + const body = await response.json() + expect( + body.joined, + 'joined should be the result of `join` function from node:path', + ).toBe('a/b') + }) }) - test('node:path module', async ({ middlewareNodeRuntimeSpecific }) => { - const response = await fetch(`${middlewareNodeRuntimeSpecific.url}/test/path`) - expect(response.status).toBe(200) - const body = await response.json() - expect(body.joined, 'joined should be the result of `join` function from node:path').toBe( - 'a/b', - ) + test.describe('pnpm package manager', () => { + test('node:crypto module', async ({ middlewareNodeRuntimeSpecificPnpm }) => { + const response = await fetch(`${middlewareNodeRuntimeSpecificPnpm.url}/test/crypto`) + expect(response.status).toBe(200) + const body = await response.json() + expect( + body.random, + 'random should have 16 random bytes generated with `randomBytes` function from node:crypto in hex format', + ).toMatch(/[0-9a-f]{32}/) + }) + + test('node:http(s) module', async ({ middlewareNodeRuntimeSpecificPnpm }) => { + const response = await fetch(`${middlewareNodeRuntimeSpecificPnpm.url}/test/http`) + expect(response.status).toBe(200) + const body = await response.json() + expect( + body.proxiedWithHttpRequest, + 'proxiedWithHttpRequest should be the result of `http.request` from node:http fetching static asset', + ).toStrictEqual({ hello: 'world' }) + }) + + test('node:path module', async ({ middlewareNodeRuntimeSpecificPnpm }) => { + const response = await fetch(`${middlewareNodeRuntimeSpecificPnpm.url}/test/path`) + expect(response.status).toBe(200) + const body = await response.json() + expect( + body.joined, + 'joined should be the result of `join` function from node:path', + ).toBe('a/b') + }) }) }) } diff --git a/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index 475e4ab7a9..fe2546da2e 100644 --- a/tests/utils/create-e2e-fixture.ts +++ b/tests/utils/create-e2e-fixture.ts @@ -345,6 +345,10 @@ export const fixtureFactories = { publishDirectory: '.next-node-middleware', }), middlewareNodeRuntimeSpecific: () => createE2EFixture('middleware-node-runtime-specific'), + middlewareNodeRuntimeSpecificPnpm: () => + createE2EFixture('middleware-node-runtime-specific', { + packageManger: 'pnpm', + }), middlewareI18n: () => createE2EFixture('middleware-i18n'), middlewareI18nNode: () => createE2EFixture('middleware-i18n', { From 0bf460a3b5297b33760c3623086ff06483c38771 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Sun, 14 Sep 2025 11:54:56 +0200 Subject: [PATCH 2/2] fix: handle node middleware bundling when pmpm is used --- src/build/functions/edge.ts | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/build/functions/edge.ts b/src/build/functions/edge.ts index 5dfae48b7e..354887d92e 100644 --- a/src/build/functions/edge.ts +++ b/src/build/functions/edge.ts @@ -1,4 +1,4 @@ -import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises' +import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises' import { dirname, join, relative } from 'node:path/posix' import type { Manifest, ManifestFunction } from '@netlify/edge-functions' @@ -259,14 +259,26 @@ const copyHandlerDependenciesForNodeMiddleware = async (ctx: PluginContext) => { parts.push(`const virtualModules = new Map();`) - for (const file of files) { - const srcPath = join(srcDir, file) + const handleFileOrDirectory = async (fileOrDir: string) => { + const srcPath = join(srcDir, fileOrDir) - const content = await readFile(srcPath, 'utf8') + const stats = await stat(srcPath) + if (stats.isDirectory()) { + const filesInDir = await readdir(srcPath) + for (const fileInDir of filesInDir) { + await handleFileOrDirectory(join(fileOrDir, fileInDir)) + } + } else { + const content = await readFile(srcPath, 'utf8') - parts.push( - `virtualModules.set(${JSON.stringify(join(commonPrefix, file))}, ${JSON.stringify(content)});`, - ) + parts.push( + `virtualModules.set(${JSON.stringify(join(commonPrefix, fileOrDir))}, ${JSON.stringify(content)});`, + ) + } + } + + for (const file of files) { + await handleFileOrDirectory(file) } parts.push(`registerCJSModules(import.meta.url, virtualModules);