@@ -35,6 +35,16 @@ interface BuildOptions {
35
35
* ```
36
36
*/
37
37
buildCommand ?: string ;
38
+ /**
39
+ * The path to the target folder of build output from the `buildCommand` option (the path which will contain the `.next` and `.open-next` folders). This path is relative from the current process.cwd().
40
+ * @default "."
41
+ */
42
+ buildOutputPath ?: string ;
43
+ /**
44
+ * The path to the root of the Next.js app's source code. This path is relative from the current process.cwd().
45
+ * @default "."
46
+ */
47
+ appPath ?: string ;
38
48
}
39
49
40
50
const require = topLevelCreateRequire ( import . meta. url ) ;
@@ -46,14 +56,17 @@ export type PublicFiles = {
46
56
} ;
47
57
48
58
export async function build ( opts : BuildOptions = { } ) {
59
+ const { root : monorepoRoot , packager } = findMonorepoRoot (
60
+ path . join ( process . cwd ( ) , opts . appPath || "." ) ,
61
+ ) ;
62
+
49
63
// Initialize options
50
- options = normalizeOptions ( opts ) ;
64
+ options = normalizeOptions ( opts , monorepoRoot ) ;
51
65
52
66
// Pre-build validation
53
67
checkRunningInsideNextjsApp ( ) ;
54
68
printNextjsVersion ( ) ;
55
69
printOpenNextVersion ( ) ;
56
- const { root : monorepoRoot , packager } = findMonorepoRoot ( ) ;
57
70
58
71
// Build Next.js app
59
72
printHeader ( "Building Next.js app" ) ;
@@ -74,13 +87,17 @@ export async function build(opts: BuildOptions = {}) {
74
87
}
75
88
}
76
89
77
- function normalizeOptions ( opts : BuildOptions ) {
78
- const appPath = process . cwd ( ) ;
79
- const outputDir = ".open-next" ;
90
+ function normalizeOptions ( opts : BuildOptions , root : string ) {
91
+ const appPath = path . join ( process . cwd ( ) , opts . appPath || "." ) ;
92
+ const buildOutputPath = path . join ( process . cwd ( ) , opts . buildOutputPath || "." ) ;
93
+ const outputDir = path . join ( buildOutputPath , ".open-next" ) ;
94
+ const nextPackageJsonPath = findNextPackageJsonPath ( appPath , root ) ;
80
95
return {
81
96
openNextVersion : getOpenNextVersion ( ) ,
82
- nextVersion : getNextVersion ( appPath ) ,
97
+ nextVersion : getNextVersion ( nextPackageJsonPath ) ,
98
+ nextPackageJsonPath,
83
99
appPath,
100
+ appBuildOutputPath : buildOutputPath ,
84
101
appPublicPath : path . join ( appPath , "public" ) ,
85
102
outputDir,
86
103
tempDir : path . join ( outputDir , ".build" ) ,
@@ -103,8 +120,7 @@ function checkRunningInsideNextjsApp() {
103
120
}
104
121
}
105
122
106
- function findMonorepoRoot ( ) {
107
- const { appPath } = options ;
123
+ function findMonorepoRoot ( appPath : string ) {
108
124
let currentPath = appPath ;
109
125
while ( currentPath !== "/" ) {
110
126
const found = [
@@ -128,6 +144,13 @@ function findMonorepoRoot() {
128
144
return { root : appPath , packager : "npm" as const } ;
129
145
}
130
146
147
+ function findNextPackageJsonPath ( appPath : string , root : string ) {
148
+ // This is needed for the case where the app is a single-version monorepo and the package.json is in the root of the monorepo
149
+ return fs . existsSync ( path . join ( appPath , "./package.json" ) )
150
+ ? path . join ( appPath , "./package.json" )
151
+ : path . join ( root , "./package.json" ) ;
152
+ }
153
+
131
154
function setStandaloneBuildMode ( monorepoRoot : string ) {
132
155
// Equivalent to setting `target: "standalone"` in next.config.js
133
156
process . env . NEXT_PRIVATE_STANDALONE = "true" ;
@@ -136,13 +159,13 @@ function setStandaloneBuildMode(monorepoRoot: string) {
136
159
}
137
160
138
161
function buildNextjsApp ( packager : "npm" | "yarn" | "pnpm" ) {
139
- const { appPath } = options ;
162
+ const { nextPackageJsonPath } = options ;
140
163
const command =
141
164
options . buildCommand ??
142
165
( packager === "npm" ? "npm run build" : `${ packager } build` ) ;
143
166
cp . execSync ( command , {
144
167
stdio : "inherit" ,
145
- cwd : appPath ,
168
+ cwd : path . dirname ( nextPackageJsonPath ) ,
146
169
} ) ;
147
170
}
148
171
@@ -226,7 +249,7 @@ async function minifyServerBundle() {
226
249
function createRevalidationBundle ( ) {
227
250
console . info ( `Bundling revalidation function...` ) ;
228
251
229
- const { appPath , outputDir } = options ;
252
+ const { appBuildOutputPath , outputDir } = options ;
230
253
231
254
// Create output folder
232
255
const outputPath = path . join ( outputDir , "revalidation-function" ) ;
@@ -241,15 +264,15 @@ function createRevalidationBundle() {
241
264
242
265
// Copy over .next/prerender-manifest.json file
243
266
fs . copyFileSync (
244
- path . join ( appPath , ".next" , "prerender-manifest.json" ) ,
267
+ path . join ( appBuildOutputPath , ".next" , "prerender-manifest.json" ) ,
245
268
path . join ( outputPath , "prerender-manifest.json" ) ,
246
269
) ;
247
270
}
248
271
249
272
function createImageOptimizationBundle ( ) {
250
273
console . info ( `Bundling image optimization function...` ) ;
251
274
252
- const { appPath, outputDir } = options ;
275
+ const { appPath, appBuildOutputPath , outputDir } = options ;
253
276
254
277
// Create output folder
255
278
const outputPath = path . join ( outputDir , "image-optimization-function" ) ;
@@ -289,7 +312,7 @@ function createImageOptimizationBundle() {
289
312
// Copy over .next/required-server-files.json file
290
313
fs . mkdirSync ( path . join ( outputPath , ".next" ) ) ;
291
314
fs . copyFileSync (
292
- path . join ( appPath , ".next/required-server-files.json" ) ,
315
+ path . join ( appBuildOutputPath , ".next/required-server-files.json" ) ,
293
316
path . join ( outputPath , ".next/required-server-files.json" ) ,
294
317
) ;
295
318
@@ -310,7 +333,7 @@ function createImageOptimizationBundle() {
310
333
function createStaticAssets ( ) {
311
334
console . info ( `Bundling static assets...` ) ;
312
335
313
- const { appPath , appPublicPath, outputDir } = options ;
336
+ const { appBuildOutputPath , appPublicPath, outputDir } = options ;
314
337
315
338
// Create output folder
316
339
const outputPath = path . join ( outputDir , "assets" ) ;
@@ -322,11 +345,11 @@ function createStaticAssets() {
322
345
// - .next/static => _next/static
323
346
// - public/* => *
324
347
fs . copyFileSync (
325
- path . join ( appPath , ".next/BUILD_ID" ) ,
348
+ path . join ( appBuildOutputPath , ".next/BUILD_ID" ) ,
326
349
path . join ( outputPath , "BUILD_ID" ) ,
327
350
) ;
328
351
fs . cpSync (
329
- path . join ( appPath , ".next/static" ) ,
352
+ path . join ( appBuildOutputPath , ".next/static" ) ,
330
353
path . join ( outputPath , "_next" , "static" ) ,
331
354
{ recursive : true } ,
332
355
) ;
@@ -338,12 +361,16 @@ function createStaticAssets() {
338
361
function createCacheAssets ( monorepoRoot : string ) {
339
362
console . info ( `Bundling cache assets...` ) ;
340
363
341
- const { appPath , outputDir } = options ;
342
- const packagePath = path . relative ( monorepoRoot , appPath ) ;
343
- const buildId = getBuildId ( appPath ) ;
364
+ const { appBuildOutputPath , outputDir } = options ;
365
+ const packagePath = path . relative ( monorepoRoot , appBuildOutputPath ) ;
366
+ const buildId = getBuildId ( appBuildOutputPath ) ;
344
367
345
368
// Copy pages to cache folder
346
- const dotNextPath = path . join ( appPath , ".next/standalone" , packagePath ) ;
369
+ const dotNextPath = path . join (
370
+ appBuildOutputPath ,
371
+ ".next/standalone" ,
372
+ packagePath ,
373
+ ) ;
347
374
const outputPath = path . join ( outputDir , "cache" , buildId ) ;
348
375
[ ".next/server/pages" , ".next/server/app" ]
349
376
. map ( ( dir ) => path . join ( dotNextPath , dir ) )
@@ -361,7 +388,10 @@ function createCacheAssets(monorepoRoot: string) {
361
388
) ;
362
389
363
390
// Copy fetch-cache to cache folder
364
- const fetchCachePath = path . join ( appPath , ".next/cache/fetch-cache" ) ;
391
+ const fetchCachePath = path . join (
392
+ appBuildOutputPath ,
393
+ ".next/cache/fetch-cache" ,
394
+ ) ;
365
395
if ( fs . existsSync ( fetchCachePath ) ) {
366
396
const fetchOutputPath = path . join ( outputDir , "cache" , "__fetch" , buildId ) ;
367
397
fs . mkdirSync ( fetchOutputPath , { recursive : true } ) ;
@@ -376,7 +406,7 @@ function createCacheAssets(monorepoRoot: string) {
376
406
async function createServerBundle ( monorepoRoot : string ) {
377
407
console . info ( `Bundling server function...` ) ;
378
408
379
- const { appPath, outputDir } = options ;
409
+ const { appPath, appBuildOutputPath , outputDir } = options ;
380
410
381
411
// Create output folder
382
412
const outputPath = path . join ( outputDir , "server-function" ) ;
@@ -388,12 +418,12 @@ async function createServerBundle(monorepoRoot: string) {
388
418
// `.next/standalone/package/path` (ie. `.next`, `server.js`).
389
419
// We need to output the handler file inside the package path.
390
420
const isMonorepo = monorepoRoot !== appPath ;
391
- const packagePath = path . relative ( monorepoRoot , appPath ) ;
421
+ const packagePath = path . relative ( monorepoRoot , appBuildOutputPath ) ;
392
422
393
423
// Copy over standalone output files
394
424
// note: if user uses pnpm as the package manager, node_modules contain
395
425
// symlinks. We don't want to resolve the symlinks when copying.
396
- fs . cpSync ( path . join ( appPath , ".next/standalone" ) , outputPath , {
426
+ fs . cpSync ( path . join ( appBuildOutputPath , ".next/standalone" ) , outputPath , {
397
427
recursive : true ,
398
428
verbatimSymlinks : true ,
399
429
} ) ;
@@ -685,9 +715,9 @@ function getOpenNextVersion() {
685
715
return require ( path . join ( __dirname , "../package.json" ) ) . version ;
686
716
}
687
717
688
- function getNextVersion ( appPath : string ) {
689
- const version = require ( path . join ( appPath , "./package.json" ) ) . dependencies
690
- . next ;
718
+ function getNextVersion ( nextPackageJsonPath : string ) {
719
+ const version = require ( nextPackageJsonPath ) . dependencies . next ;
720
+
691
721
// Drop the -canary.n suffix
692
722
return version . split ( "-" ) [ 0 ] ;
693
723
}
0 commit comments