Skip to content

Commit f0746ea

Browse files
committed
fix: ensure that we don't skip files in standalone root dir that are outside of project workspace
1 parent 7d85c94 commit f0746ea

File tree

2 files changed

+47
-56
lines changed

2 files changed

+47
-56
lines changed

src/build/content/server.ts

Lines changed: 36 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
writeFile,
1111
} from 'node:fs/promises'
1212
import { createRequire } from 'node:module'
13-
import { dirname, join, resolve, sep } from 'node:path'
13+
import { dirname, join, relative, sep } from 'node:path'
1414
import { join as posixJoin, sep as posixSep } from 'node:path/posix'
1515

1616
import { trace } from '@opentelemetry/api'
@@ -145,9 +145,11 @@ export const copyNextServerCode = async (ctx: PluginContext): Promise<void> => {
145145
await cp(srcPath, destPath, { recursive: true, force: true })
146146
})
147147

148-
// this is different node_modules than one handled `copyNextDependencies`
149-
// this is under the standalone/.next folder (not standalone/node_modules)
148+
// this is different node_modules than ones 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
151153
if (existsSync(join(srcDir, 'node_modules'))) {
152154
const filter = ctx.constants.IS_LOCAL ? undefined : nodeModulesFilter
153155
const src = join(srcDir, 'node_modules')
@@ -158,26 +160,6 @@ export const copyNextServerCode = async (ctx: PluginContext): Promise<void> => {
158160
force: 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

183165
await Promise.all(promises)
@@ -325,42 +307,40 @@ async function patchNextModules(
325307

326308
export const copyNextDependencies = async (ctx: PluginContext): Promise<void> => {
327309
await 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 nodeModulesLocations = 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+
nodeModulesLocations.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 nodeModulesLocation of nodeModulesLocations) {
340+
const relativeToRoot = relative(ctx.standaloneRootDir, nodeModulesLocation)
341+
const locationInProject = join(ctx.outputFileTracingRoot, relativeToRoot)
342+
343+
promises.push(recreateNodeModuleSymlinks(locationInProject, nodeModulesLocation))
364344
}
365345

366346
await Promise.all(promises)
@@ -486,7 +466,7 @@ export const verifyHandlerDirStructure = async (ctx: PluginContext) => {
486466
// https://github.com/pnpm/pnpm/issues/9654
487467
// https://github.com/pnpm/pnpm/issues/5928
488468
// https://github.com/pnpm/pnpm/issues/7362 (persisting even though ticket is closed)
489-
const nodeModulesFilter = async (sourcePath: string) => {
469+
const nodeModulesFilter = (sourcePath: string) => {
490470
// Filtering rule for the following packages:
491471
// - @rspack+binding-linux-x64-musl
492472
// - @swc+core-linux-x64-musl

src/build/plugin-context.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,17 @@ export class PluginContext {
8484
return this.requiredServerFiles.relativeAppDir ?? ''
8585
}
8686

87+
/**
88+
* The root directory for output file tracing. Paths inside standalone directory preserve paths of project, relative to this directory.
89+
*/
90+
get outputFileTracingRoot(): string {
91+
return (
92+
this.requiredServerFiles.config.outputFileTracingRoot ??
93+
// fallback for older Next.js versions that don't have outputFileTracingRoot in the config, but had it in config.experimental
94+
this.requiredServerFiles.config.experimental.outputFileTracingRoot
95+
)
96+
}
97+
8798
/**
8899
* The working directory inside the lambda that is used for monorepos to execute the serverless function
89100
*/

0 commit comments

Comments
 (0)