1- import { Buffer } from 'node:buffer '
1+ import { readFile } from 'node:fs/promises '
22import { join } from 'node:path'
33import { inspect } from 'node:util'
44
55import type { NetlifyPluginOptions } from '@netlify/build'
66import glob from 'fast-glob'
7+ import type { PrerenderManifest } from 'next/dist/build/index.js'
78import { beforeEach , describe , expect , Mock , test , vi } from 'vitest'
89
9- import { mockFileSystem } from '../../../tests/index.js'
10+ import { decodeBlobKey , encodeBlobKey , mockFileSystem } from '../../../tests/index.js'
1011import { type FixtureTestContext } from '../../../tests/utils/contexts.js'
1112import { createFsFixture } from '../../../tests/utils/fixture.js'
1213import { PluginContext , RequiredServerFilesManifest } from '../plugin-context.js'
@@ -21,7 +22,19 @@ type Context = FixtureTestContext & {
2122const createFsFixtureWithBasePath = (
2223 fixture : Record < string , string > ,
2324 ctx : Omit < Context , 'pluginContext' > ,
24- basePath = '' ,
25+
26+ {
27+ basePath = '' ,
28+ // eslint-disable-next-line unicorn/no-useless-undefined
29+ i18n = undefined ,
30+ dynamicRoutes = { } ,
31+ } : {
32+ basePath ?: string
33+ i18n ?: Pick < NonNullable < RequiredServerFilesManifest [ 'config' ] [ 'i18n' ] > , 'locales' >
34+ dynamicRoutes ?: {
35+ [ route : string ] : Pick < PrerenderManifest [ 'dynamicRoutes' ] [ '' ] , 'fallback' >
36+ }
37+ } = { } ,
2538) => {
2639 return createFsFixture (
2740 {
@@ -32,8 +45,10 @@ const createFsFixtureWithBasePath = (
3245 appDir : ctx . relativeAppDir ,
3346 config : {
3447 distDir : ctx . publishDir ,
48+ i18n,
3549 } ,
3650 } as Pick < RequiredServerFilesManifest , 'relativeAppDir' | 'appDir' > ) ,
51+ [ join ( ctx . publishDir , 'prerender-manifest.json' ) ] : JSON . stringify ( { dynamicRoutes } ) ,
3752 } ,
3853 ctx ,
3954 )
@@ -121,7 +136,7 @@ describe('Regular Repository layout', () => {
121136 '.next/static/sub-dir/test2.js' : '' ,
122137 } ,
123138 ctx ,
124- '/base/path' ,
139+ { basePath : '/base/path' } ,
125140 )
126141
127142 await copyStaticAssets ( pluginContext )
@@ -168,7 +183,7 @@ describe('Regular Repository layout', () => {
168183 'public/another-asset.json' : '' ,
169184 } ,
170185 ctx ,
171- '/base/path' ,
186+ { basePath : '/base/path' } ,
172187 )
173188
174189 await copyStaticAssets ( pluginContext )
@@ -182,26 +197,100 @@ describe('Regular Repository layout', () => {
182197 )
183198 } )
184199
185- test < Context > ( 'should copy the static pages to the publish directory if there are no corresponding JSON files' , async ( {
186- pluginContext,
187- ...ctx
188- } ) => {
189- await createFsFixtureWithBasePath (
190- {
191- '.next/server/pages/test.html' : '' ,
192- '.next/server/pages/test2.html' : '' ,
193- '.next/server/pages/test3.json' : '' ,
194- } ,
195- ctx ,
196- )
200+ describe ( 'should copy the static pages to the publish directory if there are no corresponding JSON files and mark wether html file is a fallback' , ( ) => {
201+ test < Context > ( 'no i18n' , async ( { pluginContext, ...ctx } ) => {
202+ await createFsFixtureWithBasePath (
203+ {
204+ '.next/server/pages/test.html' : '' ,
205+ '.next/server/pages/test2.html' : '' ,
206+ '.next/server/pages/test3.json' : '' ,
207+ '.next/server/pages/blog/[slug].html' : '' ,
208+ } ,
209+ ctx ,
210+ {
211+ dynamicRoutes : {
212+ '/blog/[slug]' : {
213+ fallback : '/blog/[slug].html' ,
214+ } ,
215+ } ,
216+ } ,
217+ )
197218
198- await copyStaticContent ( pluginContext )
199- const files = await glob ( '**/*' , { cwd : pluginContext . blobDir , dot : true } )
219+ await copyStaticContent ( pluginContext )
220+ const files = await glob ( '**/*' , { cwd : pluginContext . blobDir , dot : true } )
221+
222+ const expectedStaticPages = [ 'blog/[slug].html' , 'test.html' , 'test2.html' ]
223+ const expectedFallbacks = new Set ( [ 'blog/[slug].html' ] )
224+
225+ expect ( files . map ( ( path ) => decodeBlobKey ( path ) ) . sort ( ) ) . toEqual ( expectedStaticPages )
226+
227+ for ( const page of expectedStaticPages ) {
228+ const expectedIsFallback = expectedFallbacks . has ( page )
229+
230+ const blob = JSON . parse (
231+ await readFile ( join ( pluginContext . blobDir , await encodeBlobKey ( page ) ) , 'utf-8' ) ,
232+ )
200233
201- expect ( files . map ( ( path ) => Buffer . from ( path , 'base64' ) . toString ( 'utf-8' ) ) . sort ( ) ) . toEqual ( [
202- 'test.html' ,
203- 'test2.html' ,
204- ] )
234+ expect ( blob , `${ page } should ${ expectedIsFallback ? '' : 'not ' } be a fallback` ) . toEqual ( {
235+ html : '' ,
236+ isFallback : expectedIsFallback ,
237+ } )
238+ }
239+ } )
240+
241+ test < Context > ( 'with i18n' , async ( { pluginContext, ...ctx } ) => {
242+ await createFsFixtureWithBasePath (
243+ {
244+ '.next/server/pages/de/test.html' : '' ,
245+ '.next/server/pages/de/test2.html' : '' ,
246+ '.next/server/pages/de/test3.json' : '' ,
247+ '.next/server/pages/de/blog/[slug].html' : '' ,
248+ '.next/server/pages/en/test.html' : '' ,
249+ '.next/server/pages/en/test2.html' : '' ,
250+ '.next/server/pages/en/test3.json' : '' ,
251+ '.next/server/pages/en/blog/[slug].html' : '' ,
252+ } ,
253+ ctx ,
254+ {
255+ dynamicRoutes : {
256+ '/blog/[slug]' : {
257+ fallback : '/blog/[slug].html' ,
258+ } ,
259+ } ,
260+ i18n : {
261+ locales : [ 'en' , 'de' ] ,
262+ } ,
263+ } ,
264+ )
265+
266+ await copyStaticContent ( pluginContext )
267+ const files = await glob ( '**/*' , { cwd : pluginContext . blobDir , dot : true } )
268+
269+ const expectedStaticPages = [
270+ 'de/blog/[slug].html' ,
271+ 'de/test.html' ,
272+ 'de/test2.html' ,
273+ 'en/blog/[slug].html' ,
274+ 'en/test.html' ,
275+ 'en/test2.html' ,
276+ ]
277+ const expectedFallbacks = new Set ( [ 'en/blog/[slug].html' , 'de/blog/[slug].html' ] )
278+
279+ expect ( files . map ( ( path ) => decodeBlobKey ( path ) ) . sort ( ) ) . toEqual ( expectedStaticPages )
280+
281+ for ( const page of expectedStaticPages ) {
282+ const expectedIsFallback = expectedFallbacks . has ( page )
283+
284+ const blob = JSON . parse (
285+ await readFile ( join ( pluginContext . blobDir , await encodeBlobKey ( page ) ) , 'utf-8' ) ,
286+ )
287+
288+ expect ( blob , `${ page } should ${ expectedIsFallback ? '' : 'not ' } be a fallback` ) . toEqual ( {
289+ html : '' ,
290+ isFallback : expectedIsFallback ,
291+ } )
292+ }
293+ } )
205294 } )
206295
207296 test < Context > ( 'should not copy the static pages to the publish directory if there are corresponding JSON files' , async ( {
@@ -269,7 +358,7 @@ describe('Mono Repository', () => {
269358 'apps/app-1/.next/static/sub-dir/test2.js' : '' ,
270359 } ,
271360 ctx ,
272- '/base/path' ,
361+ { basePath : '/base/path' } ,
273362 )
274363
275364 await copyStaticAssets ( pluginContext )
@@ -316,7 +405,7 @@ describe('Mono Repository', () => {
316405 'apps/app-1/public/another-asset.json' : '' ,
317406 } ,
318407 ctx ,
319- '/base/path' ,
408+ { basePath : '/base/path' } ,
320409 )
321410
322411 await copyStaticAssets ( pluginContext )
@@ -330,26 +419,100 @@ describe('Mono Repository', () => {
330419 )
331420 } )
332421
333- test < Context > ( 'should copy the static pages to the publish directory if there are no corresponding JSON files' , async ( {
334- pluginContext,
335- ...ctx
336- } ) => {
337- await createFsFixtureWithBasePath (
338- {
339- 'apps/app-1/.next/server/pages/test.html' : '' ,
340- 'apps/app-1/.next/server/pages/test2.html' : '' ,
341- 'apps/app-1/.next/server/pages/test3.json' : '' ,
342- } ,
343- ctx ,
344- )
422+ describe ( 'should copy the static pages to the publish directory if there are no corresponding JSON files and mark wether html file is a fallback' , ( ) => {
423+ test < Context > ( 'no i18n' , async ( { pluginContext, ...ctx } ) => {
424+ await createFsFixtureWithBasePath (
425+ {
426+ 'apps/app-1/.next/server/pages/test.html' : '' ,
427+ 'apps/app-1/.next/server/pages/test2.html' : '' ,
428+ 'apps/app-1/.next/server/pages/test3.json' : '' ,
429+ 'apps/app-1/.next/server/pages/blog/[slug].html' : '' ,
430+ } ,
431+ ctx ,
432+ {
433+ dynamicRoutes : {
434+ '/blog/[slug]' : {
435+ fallback : '/blog/[slug].html' ,
436+ } ,
437+ } ,
438+ } ,
439+ )
345440
346- await copyStaticContent ( pluginContext )
347- const files = await glob ( '**/*' , { cwd : pluginContext . blobDir , dot : true } )
441+ await copyStaticContent ( pluginContext )
442+ const files = await glob ( '**/*' , { cwd : pluginContext . blobDir , dot : true } )
443+
444+ const expectedStaticPages = [ 'blog/[slug].html' , 'test.html' , 'test2.html' ]
445+ const expectedFallbacks = new Set ( [ 'blog/[slug].html' ] )
446+
447+ expect ( files . map ( ( path ) => decodeBlobKey ( path ) ) . sort ( ) ) . toEqual ( expectedStaticPages )
448+
449+ for ( const page of expectedStaticPages ) {
450+ const expectedIsFallback = expectedFallbacks . has ( page )
451+
452+ const blob = JSON . parse (
453+ await readFile ( join ( pluginContext . blobDir , await encodeBlobKey ( page ) ) , 'utf-8' ) ,
454+ )
348455
349- expect ( files . map ( ( path ) => Buffer . from ( path , 'base64' ) . toString ( 'utf-8' ) ) . sort ( ) ) . toEqual ( [
350- 'test.html' ,
351- 'test2.html' ,
352- ] )
456+ expect ( blob , `${ page } should ${ expectedIsFallback ? '' : 'not ' } be a fallback` ) . toEqual ( {
457+ html : '' ,
458+ isFallback : expectedIsFallback ,
459+ } )
460+ }
461+ } )
462+
463+ test < Context > ( 'with i18n' , async ( { pluginContext, ...ctx } ) => {
464+ await createFsFixtureWithBasePath (
465+ {
466+ 'apps/app-1/.next/server/pages/de/test.html' : '' ,
467+ 'apps/app-1/.next/server/pages/de/test2.html' : '' ,
468+ 'apps/app-1/.next/server/pages/de/test3.json' : '' ,
469+ 'apps/app-1/.next/server/pages/de/blog/[slug].html' : '' ,
470+ 'apps/app-1/.next/server/pages/en/test.html' : '' ,
471+ 'apps/app-1/.next/server/pages/en/test2.html' : '' ,
472+ 'apps/app-1/.next/server/pages/en/test3.json' : '' ,
473+ 'apps/app-1/.next/server/pages/en/blog/[slug].html' : '' ,
474+ } ,
475+ ctx ,
476+ {
477+ dynamicRoutes : {
478+ '/blog/[slug]' : {
479+ fallback : '/blog/[slug].html' ,
480+ } ,
481+ } ,
482+ i18n : {
483+ locales : [ 'en' , 'de' ] ,
484+ } ,
485+ } ,
486+ )
487+
488+ await copyStaticContent ( pluginContext )
489+ const files = await glob ( '**/*' , { cwd : pluginContext . blobDir , dot : true } )
490+
491+ const expectedStaticPages = [
492+ 'de/blog/[slug].html' ,
493+ 'de/test.html' ,
494+ 'de/test2.html' ,
495+ 'en/blog/[slug].html' ,
496+ 'en/test.html' ,
497+ 'en/test2.html' ,
498+ ]
499+ const expectedFallbacks = new Set ( [ 'en/blog/[slug].html' , 'de/blog/[slug].html' ] )
500+
501+ expect ( files . map ( ( path ) => decodeBlobKey ( path ) ) . sort ( ) ) . toEqual ( expectedStaticPages )
502+
503+ for ( const page of expectedStaticPages ) {
504+ const expectedIsFallback = expectedFallbacks . has ( page )
505+
506+ const blob = JSON . parse (
507+ await readFile ( join ( pluginContext . blobDir , await encodeBlobKey ( page ) ) , 'utf-8' ) ,
508+ )
509+
510+ expect ( blob , `${ page } should ${ expectedIsFallback ? '' : 'not ' } be a fallback` ) . toEqual ( {
511+ html : '' ,
512+ isFallback : expectedIsFallback ,
513+ } )
514+ }
515+ } )
353516 } )
354517
355518 test < Context > ( 'should not copy the static pages to the publish directory if there are corresponding JSON files' , async ( {
0 commit comments