@@ -14,13 +14,17 @@ import { createRequire } from 'node:module'
14
14
import { dirname , join , resolve , sep } from 'node:path'
15
15
import { sep as posixSep , join as posixJoin } from 'node:path/posix'
16
16
17
+ import { trace } from '@opentelemetry/api'
18
+ import { wrapTracer } from '@opentelemetry/api/experimental'
17
19
import glob from 'fast-glob'
18
20
import { prerelease , lt as semverLowerThan , lte as semverLowerThanOrEqual } from 'semver'
19
21
20
22
import { RUN_CONFIG } from '../../run/constants.js'
21
23
import { PluginContext } from '../plugin-context.js'
22
24
import { verifyNextVersion } from '../verification.js'
23
25
26
+ const tracer = wrapTracer ( trace . getTracer ( 'Next runtime' ) )
27
+
24
28
const toPosixPath = ( path : string ) => path . split ( sep ) . join ( posixSep )
25
29
26
30
function isError ( error : unknown ) : error is NodeJS . ErrnoException {
@@ -31,86 +35,88 @@ function isError(error: unknown): error is NodeJS.ErrnoException {
31
35
* Copy App/Pages Router Javascript needed by the server handler
32
36
*/
33
37
export const copyNextServerCode = async ( ctx : PluginContext ) : Promise < void > => {
34
- // update the dist directory inside the required-server-files.json to work with
35
- // nx monorepos and other setups where the dist directory is modified
36
- const reqServerFilesPath = join (
37
- ctx . standaloneRootDir ,
38
- ctx . relativeAppDir ,
39
- ctx . requiredServerFiles . config . distDir ,
40
- 'required-server-files.json' ,
41
- )
42
- try {
43
- await access ( reqServerFilesPath )
44
- } catch ( error ) {
45
- if ( isError ( error ) && error . code === 'ENOENT' ) {
46
- // this error at this point is problem in runtime and not user configuration
47
- ctx . failBuild (
48
- `Failed creating server handler. required-server-files.json file not found at expected location "${ reqServerFilesPath } ". Your repository setup is currently not yet supported.` ,
49
- )
50
- } else {
51
- throw error
38
+ await tracer . withActiveSpan ( 'copyNextServerCode' , async ( ) => {
39
+ // update the dist directory inside the required-server-files.json to work with
40
+ // nx monorepos and other setups where the dist directory is modified
41
+ const reqServerFilesPath = join (
42
+ ctx . standaloneRootDir ,
43
+ ctx . relativeAppDir ,
44
+ ctx . requiredServerFiles . config . distDir ,
45
+ 'required-server-files.json' ,
46
+ )
47
+ try {
48
+ await access ( reqServerFilesPath )
49
+ } catch ( error ) {
50
+ if ( isError ( error ) && error . code === 'ENOENT' ) {
51
+ // this error at this point is problem in runtime and not user configuration
52
+ ctx . failBuild (
53
+ `Failed creating server handler. required-server-files.json file not found at expected location "${ reqServerFilesPath } ". Your repository setup is currently not yet supported.` ,
54
+ )
55
+ } else {
56
+ throw error
57
+ }
52
58
}
53
- }
54
- const reqServerFiles = JSON . parse ( await readFile ( reqServerFilesPath , 'utf-8' ) )
55
-
56
- // if the resolved dist folder does not match the distDir of the required-server-files.json
57
- // this means the path got altered by a plugin like nx and contained ../../ parts so we have to reset it
58
- // to point to the correct lambda destination
59
- if (
60
- toPosixPath ( ctx . distDir ) . replace ( new RegExp ( `^${ ctx . relativeAppDir } /?` ) , '' ) !==
61
- reqServerFiles . config . distDir
62
- ) {
63
- // set the distDir to the latest path portion of the publish dir
64
- reqServerFiles . config . distDir = ctx . nextDistDir
65
- await writeFile ( reqServerFilesPath , JSON . stringify ( reqServerFiles ) )
66
- }
59
+ const reqServerFiles = JSON . parse ( await readFile ( reqServerFilesPath , 'utf-8' ) )
67
60
68
- // ensure the directory exists before writing to it
69
- await mkdir ( ctx . serverHandlerDir , { recursive : true } )
70
- // write our run-config.json to the root dir so that we can easily get the runtime config of the required-server-files.json
71
- // without the need to know about the monorepo or distDir configuration upfront.
72
- await writeFile (
73
- join ( ctx . serverHandlerDir , RUN_CONFIG ) ,
74
- JSON . stringify ( reqServerFiles . config ) ,
75
- 'utf-8' ,
76
- )
61
+ // if the resolved dist folder does not match the distDir of the required-server-files.json
62
+ // this means the path got altered by a plugin like nx and contained ../../ parts so we have to reset it
63
+ // to point to the correct lambda destination
64
+ if (
65
+ toPosixPath ( ctx . distDir ) . replace ( new RegExp ( `^${ ctx . relativeAppDir } /?` ) , '' ) !==
66
+ reqServerFiles . config . distDir
67
+ ) {
68
+ // set the distDir to the latest path portion of the publish dir
69
+ reqServerFiles . config . distDir = ctx . nextDistDir
70
+ await writeFile ( reqServerFilesPath , JSON . stringify ( reqServerFiles ) )
71
+ }
77
72
78
- const srcDir = join ( ctx . standaloneDir , ctx . nextDistDir )
79
- // if the distDir got resolved and altered use the nextDistDir instead
80
- const nextFolder =
81
- toPosixPath ( ctx . distDir ) === toPosixPath ( ctx . buildConfig . distDir )
82
- ? ctx . distDir
83
- : ctx . nextDistDir
84
- const destDir = join ( ctx . serverHandlerDir , nextFolder )
85
-
86
- const paths = await glob (
87
- [ `*` , `server/*` , `server/chunks/*` , `server/edge-chunks/*` , `server/+(app|pages)/**/*.js` ] ,
88
- {
89
- cwd : srcDir ,
90
- extglob : true ,
91
- } ,
92
- )
73
+ // ensure the directory exists before writing to it
74
+ await mkdir ( ctx . serverHandlerDir , { recursive : true } )
75
+ // write our run-config.json to the root dir so that we can easily get the runtime config of the required-server-files.json
76
+ // without the need to know about the monorepo or distDir configuration upfront.
77
+ await writeFile (
78
+ join ( ctx . serverHandlerDir , RUN_CONFIG ) ,
79
+ JSON . stringify ( reqServerFiles . config ) ,
80
+ 'utf-8' ,
81
+ )
93
82
94
- await Promise . all (
95
- paths . map ( async ( path : string ) => {
96
- const srcPath = join ( srcDir , path )
97
- const destPath = join ( destDir , path )
83
+ const srcDir = join ( ctx . standaloneDir , ctx . nextDistDir )
84
+ // if the distDir got resolved and altered use the nextDistDir instead
85
+ const nextFolder =
86
+ toPosixPath ( ctx . distDir ) === toPosixPath ( ctx . buildConfig . distDir )
87
+ ? ctx . distDir
88
+ : ctx . nextDistDir
89
+ const destDir = join ( ctx . serverHandlerDir , nextFolder )
90
+
91
+ const paths = await glob (
92
+ [ `*` , `server/*` , `server/chunks/*` , `server/edge-chunks/*` , `server/+(app|pages)/**/*.js` ] ,
93
+ {
94
+ cwd : srcDir ,
95
+ extglob : true ,
96
+ } ,
97
+ )
98
98
99
- // If this is the middleware manifest file, replace it with an empty
100
- // manifest to avoid running middleware again in the server handler.
101
- if ( path === 'server/middleware-manifest.json' ) {
102
- try {
103
- await replaceMiddlewareManifest ( srcPath , destPath )
104
- } catch ( error ) {
105
- throw new Error ( 'Could not patch middleware manifest file' , { cause : error } )
106
- }
99
+ await Promise . all (
100
+ paths . map ( async ( path : string ) => {
101
+ const srcPath = join ( srcDir , path )
102
+ const destPath = join ( destDir , path )
103
+
104
+ // If this is the middleware manifest file, replace it with an empty
105
+ // manifest to avoid running middleware again in the server handler.
106
+ if ( path === 'server/middleware-manifest.json' ) {
107
+ try {
108
+ await replaceMiddlewareManifest ( srcPath , destPath )
109
+ } catch ( error ) {
110
+ throw new Error ( 'Could not patch middleware manifest file' , { cause : error } )
111
+ }
107
112
108
- return
109
- }
113
+ return
114
+ }
110
115
111
- await cp ( srcPath , destPath , { recursive : true , force : true } )
112
- } ) ,
113
- )
116
+ await cp ( srcPath , destPath , { recursive : true , force : true } )
117
+ } ) ,
118
+ )
119
+ } )
114
120
}
115
121
116
122
/**
@@ -238,69 +244,71 @@ async function patchNextModules(
238
244
}
239
245
240
246
export const copyNextDependencies = async ( ctx : PluginContext ) : Promise < void > => {
241
- const entries = await readdir ( ctx . standaloneDir )
242
- const promises : Promise < void > [ ] = entries . map ( async ( entry ) => {
243
- // copy all except the package.json and distDir (.next) folder as this is handled in a separate function
244
- // this will include the node_modules folder as well
245
- if ( entry === 'package.json' || entry === ctx . nextDistDir ) {
246
- return
247
- }
248
- const src = join ( ctx . standaloneDir , entry )
249
- const dest = join ( ctx . serverHandlerDir , entry )
250
- await cp ( src , dest , { recursive : true , verbatimSymlinks : true , force : true } )
247
+ await tracer . withActiveSpan ( 'copyNextDependencies' , async ( ) => {
248
+ const entries = await readdir ( ctx . standaloneDir )
249
+ const promises : Promise < void > [ ] = entries . map ( async ( entry ) => {
250
+ // copy all except the package.json and distDir (.next) folder as this is handled in a separate function
251
+ // this will include the node_modules folder as well
252
+ if ( entry === 'package.json' || entry === ctx . nextDistDir ) {
253
+ return
254
+ }
255
+ const src = join ( ctx . standaloneDir , entry )
256
+ const dest = join ( ctx . serverHandlerDir , entry )
257
+ await cp ( src , dest , { recursive : true , verbatimSymlinks : true , force : true } )
251
258
252
- if ( entry === 'node_modules' ) {
253
- await recreateNodeModuleSymlinks ( ctx . resolveFromSiteDir ( 'node_modules' ) , dest )
259
+ if ( entry === 'node_modules' ) {
260
+ await recreateNodeModuleSymlinks ( ctx . resolveFromSiteDir ( 'node_modules' ) , dest )
261
+ }
262
+ } )
263
+
264
+ // inside a monorepo there is a root `node_modules` folder that contains all the dependencies
265
+ const rootSrcDir = join ( ctx . standaloneRootDir , 'node_modules' )
266
+ const rootDestDir = join ( ctx . serverHandlerRootDir , 'node_modules' )
267
+
268
+ // use the node_modules tree from the process.cwd() and not the one from the standalone output
269
+ // as the standalone node_modules are already wrongly assembled by Next.js.
270
+ // see: https://github.com/vercel/next.js/issues/50072
271
+ if ( existsSync ( rootSrcDir ) && ctx . standaloneRootDir !== ctx . standaloneDir ) {
272
+ promises . push (
273
+ cp ( rootSrcDir , rootDestDir , { recursive : true , verbatimSymlinks : true } ) . then ( ( ) =>
274
+ recreateNodeModuleSymlinks ( resolve ( 'node_modules' ) , rootDestDir ) ,
275
+ ) ,
276
+ )
254
277
}
255
- } )
256
278
257
- // inside a monorepo there is a root `node_modules` folder that contains all the dependencies
258
- const rootSrcDir = join ( ctx . standaloneRootDir , 'node_modules' )
259
- const rootDestDir = join ( ctx . serverHandlerRootDir , 'node_modules' )
260
-
261
- // use the node_modules tree from the process.cwd() and not the one from the standalone output
262
- // as the standalone node_modules are already wrongly assembled by Next.js.
263
- // see: https://github.com/vercel/next.js/issues/50072
264
- if ( existsSync ( rootSrcDir ) && ctx . standaloneRootDir !== ctx . standaloneDir ) {
265
- promises . push (
266
- cp ( rootSrcDir , rootDestDir , { recursive : true , verbatimSymlinks : true } ) . then ( ( ) =>
267
- recreateNodeModuleSymlinks ( resolve ( 'node_modules' ) , rootDestDir ) ,
268
- ) ,
269
- )
270
- }
271
-
272
- await Promise . all ( promises )
279
+ await Promise . all ( promises )
273
280
274
- // detect if it might lead to a runtime issue and throw an error upfront on build time instead of silently failing during runtime
275
- const serverHandlerRequire = createRequire ( posixJoin ( ctx . serverHandlerDir , ':internal:' ) )
281
+ // detect if it might lead to a runtime issue and throw an error upfront on build time instead of silently failing during runtime
282
+ const serverHandlerRequire = createRequire ( posixJoin ( ctx . serverHandlerDir , ':internal:' ) )
276
283
277
- let nextVersion : string | undefined
278
- try {
279
- const { version } = serverHandlerRequire ( 'next/package.json' )
280
- if ( version ) {
281
- nextVersion = version as string
284
+ let nextVersion : string | undefined
285
+ try {
286
+ const { version } = serverHandlerRequire ( 'next/package.json' )
287
+ if ( version ) {
288
+ nextVersion = version as string
289
+ }
290
+ } catch {
291
+ // failed to resolve package.json - currently this is resolvable in all known next versions, but if next implements
292
+ // exports map it still might be a problem in the future, so we are not breaking here
282
293
}
283
- } catch {
284
- // failed to resolve package.json - currently this is resolvable in all known next versions, but if next implements
285
- // exports map it still might be a problem in the future, so we are not breaking here
286
- }
287
294
288
- if ( nextVersion ) {
289
- verifyNextVersion ( ctx , nextVersion )
295
+ if ( nextVersion ) {
296
+ verifyNextVersion ( ctx , nextVersion )
290
297
291
- await patchNextModules ( ctx , nextVersion , serverHandlerRequire . resolve )
292
- }
298
+ await patchNextModules ( ctx , nextVersion , serverHandlerRequire . resolve )
299
+ }
293
300
294
- try {
295
- const nextEntryAbsolutePath = serverHandlerRequire . resolve ( 'next' )
296
- const nextRequire = createRequire ( nextEntryAbsolutePath )
297
- nextRequire . resolve ( 'styled-jsx' )
298
- } catch {
299
- throw new Error (
300
- 'node_modules are not installed correctly, if you are using pnpm please set the public hoist pattern to: `public-hoist-pattern[]=*`.\n' +
301
- 'Refer to your docs for more details: https://docs.netlify.com/integrations/frameworks/next-js/overview/#pnpm-support' ,
302
- )
303
- }
301
+ try {
302
+ const nextEntryAbsolutePath = serverHandlerRequire . resolve ( 'next' )
303
+ const nextRequire = createRequire ( nextEntryAbsolutePath )
304
+ nextRequire . resolve ( 'styled-jsx' )
305
+ } catch {
306
+ throw new Error (
307
+ 'node_modules are not installed correctly, if you are using pnpm please set the public hoist pattern to: `public-hoist-pattern[]=*`.\n' +
308
+ 'Refer to your docs for more details: https://docs.netlify.com/integrations/frameworks/next-js/overview/#pnpm-support' ,
309
+ )
310
+ }
311
+ } )
304
312
}
305
313
306
314
export const writeTagsManifest = async ( ctx : PluginContext ) : Promise < void > => {
0 commit comments