@@ -10,7 +10,7 @@ import {
1010writeFile ,
1111} from 'node:fs/promises'
1212import { createRequire } from 'node:module'
13- import { dirname , join , resolve , sep } from 'node:path'
13+ import { dirname , join , relative , sep } from 'node:path'
1414import { join as posixJoin , sep as posixSep } from 'node:path/posix'
1515
1616import { trace } from '@opentelemetry/api'
@@ -145,9 +145,11 @@ export const copyNextServerCode = async (ctx: PluginContext): Promise<void> => {
145145await cp ( srcPath , destPath , { recursive :true , force :true } )
146146} )
147147
148- // this is different node_modules thanone handled `copyNextDependencies`
149- // this is under the standalone/.next folder (not standalone/node_modules)
148+ // this is different node_modules thanones handled by `copyNextDependencies`
149+ // this is under the standalone/.next folder (not standalone/node_modules or standalone/<some-workspace/node_modules )
150150// and started to be created by Next.js in some cases in next@16.1.0-canary.3
151+ // this node_modules is artificially created and doesn't have equivalent in the repo
152+ // so we only copy it, without additional symlinks handling
151153if ( existsSync ( join ( srcDir , 'node_modules' ) ) ) {
152154const filter = ctx . constants . IS_LOCAL ?undefined :nodeModulesFilter
153155const src = join ( srcDir , 'node_modules' )
@@ -158,26 +160,6 @@ export const copyNextServerCode = async (ctx: PluginContext): Promise<void> => {
158160force :true ,
159161 filter,
160162} )
161-
162- // const workspaceNodeModulesDir = ctx.resolveFromSiteDir('node_modules')
163- // const rootNodeModulesDir = resolve('node_modules')
164-
165- // // chain trying to fix potentially broken symlinks first using workspace node_modules if it exist
166- // // and later root node_modules for monorepo cases
167- // const workspacePromise = existsSync(workspaceNodeModulesDir)
168- // ? recreateNodeModuleSymlinks(workspaceNodeModulesDir, dest)
169- // : Promise.resolve()
170-
171- // promises.push(
172- // workspacePromise.then(() => {
173- // if (
174- // rootNodeModulesDir !== workspaceNodeModulesDir &&
175- // existsSync(resolve('node_modules'))
176- // ) {
177- // return recreateNodeModuleSymlinks(rootNodeModulesDir, dest)
178- // }
179- // }),
180- // )
181163}
182164
183165await Promise . all ( promises )
@@ -325,42 +307,41 @@ async function patchNextModules(
325307
326308export const copyNextDependencies = async ( ctx :PluginContext ) :Promise < void > => {
327309await tracer . withActiveSpan ( 'copyNextDependencies' , async ( ) => {
328- const entries = await readdir ( ctx . standaloneDir )
329- const filter = ctx . constants . IS_LOCAL ?undefined :nodeModulesFilter
310+ const promises :Promise < void > [ ] = [ ]
311+
312+ const nodeModulesLocationsInStandalone = new Set < string > ( )
313+ const commonFilter = ctx . constants . IS_LOCAL ?undefined :nodeModulesFilter
314+
315+ const dotNextDir = join ( ctx . standaloneDir , ctx . nextDistDir )
316+
317+ await cp ( ctx . standaloneRootDir , ctx . serverHandlerRootDir , {
318+ recursive :true ,
319+ verbatimSymlinks :true ,
320+ force :true ,
321+ filter :async ( sourcePath :string ) => {
322+ if ( sourcePath === dotNextDir ) {
323+ // copy all except the distDir (.next) folder as this is handled in a separate function
324+ // this will include the node_modules folder as well
325+ return false
326+ }
330327
331- const promises :Promise < void > [ ] = entries . map ( async ( entry ) => {
332- // copy all except the distDir (.next) folder as this is handled in a separate function
333- // this will include the node_modules folder as well
334- if ( entry === ctx . nextDistDir ) {
335- return
336- }
337- const src = join ( ctx . standaloneDir , entry )
338- const dest = join ( ctx . serverHandlerDir , entry )
339- await cp ( src , dest , {
340- recursive :true ,
341- verbatimSymlinks :true ,
342- force :true ,
343- filter,
344- } )
328+ if ( sourcePath . endsWith ( 'node_modules' ) ) {
329+ // keep track of node_modules as we might need to recreate symlinks
330+ // we are still copying them
331+ nodeModulesLocationsInStandalone . add ( sourcePath )
332+ }
345333
346- if ( entry === 'node_modules' ) {
347- await recreateNodeModuleSymlinks ( ctx . resolveFromSiteDir ( 'node_modules' ) , dest )
348- }
334+ // finally apply common filter if defined
335+ return commonFilter ?. ( sourcePath ) ?? true
336+ } ,
349337} )
350338
351- // inside a monorepo there is a root `node_modules` folder that contains all the dependencies
352- const rootSrcDir = join ( ctx . standaloneRootDir , 'node_modules' )
353- const rootDestDir = join ( ctx . serverHandlerRootDir , 'node_modules' )
354-
355- // use the node_modules tree from the process.cwd() and not the one from the standalone output
356- // as the standalone node_modules are already wrongly assembled by Next.js.
357- // see: https://github.com/vercel/next.js/issues/50072
358- if ( existsSync ( rootSrcDir ) && ctx . standaloneRootDir !== ctx . standaloneDir ) {
359- promises . push (
360- cp ( rootSrcDir , rootDestDir , { recursive :true , verbatimSymlinks :true , filter} ) . then ( ( ) =>
361- recreateNodeModuleSymlinks ( resolve ( 'node_modules' ) , rootDestDir ) ,
362- ) ,
363- )
339+ for ( const nodeModulesLocationInStandalone of nodeModulesLocationsInStandalone ) {
340+ const relativeToRoot = relative ( ctx . standaloneRootDir , nodeModulesLocationInStandalone )
341+ const locationInProject = join ( ctx . outputFileTracingRoot , relativeToRoot )
342+ const locationInServerHandler = join ( ctx . serverHandlerRootDir , relativeToRoot )
343+
344+ promises . push ( recreateNodeModuleSymlinks ( locationInProject , locationInServerHandler ) )
364345}
365346
366347await Promise . all ( promises )
@@ -486,7 +467,7 @@ export const verifyHandlerDirStructure = async (ctx: PluginContext) => {
486467// https://github.com/pnpm/pnpm/issues/9654
487468// https://github.com/pnpm/pnpm/issues/5928
488469// https://github.com/pnpm/pnpm/issues/7362 (persisting even though ticket is closed)
489- const nodeModulesFilter = async ( sourcePath :string ) => {
470+ const nodeModulesFilter = ( sourcePath :string ) => {
490471// Filtering rule for the following packages:
491472// - @rspack+binding-linux-x64-musl
492473// - @swc+core-linux-x64-musl