/** * This adapter is not modifying outputs or the test * it is just adding additional assertions ensuring * we provide the expected outputs and file paths are valid */ import fs from 'fs' // @ts-check /** @type {import('next').NextAdapter } */ const myAdapter = { name: 'my-custom-adapter', modifyConfig: (config, { phase }) => { if (process.env.NODE_ENV !== 'production') return config if (typeof phase !== 'string') { throw new Error(`invalid phase value provided to modifyConfig ${phase}`) } console.log('called modify config in adapter with phase', phase) return config }, onBuildComplete: async (ctx) => { console.log('onBuildComplete called') // Validate all output file paths exist on the filesystem const allOutputs = [ ...ctx.outputs.pages, ...ctx.outputs.pagesApi, ...ctx.outputs.appPages, ...ctx.outputs.appRoutes, ...ctx.outputs.prerenders, ...ctx.outputs.staticFiles, ] if (ctx.outputs.middleware) { allOutputs.push(ctx.outputs.middleware) } const validationErrors = [] // Check that all filePaths in outputs exist for (const output of allOutputs) { if (output.filePath) { try { await fs.promises.access(output.filePath, fs.constants.F_OK) } catch (err) { validationErrors.push( `Missing file for output ${output.id}: ${output.filePath}` ) } } // Check fallback filePath for prerenders if (output.type === 'PRERENDER' && output.fallback) { if (output.fallback.filePath) { try { await fs.promises.access( output.fallback.filePath, fs.constants.F_OK ) } catch (err) { validationErrors.push( `Missing fallback file for prerender ${output.id}: ${JSON.stringify(output, null, 2)}` ) } } } // Check assets if (output.assets) { for (const [key, assetPath] of Object.entries(output.assets)) { try { await fs.promises.access(assetPath, fs.constants.F_OK) } catch (err) { validationErrors.push( `Missing asset file for output ${output.id} (${key}): ${assetPath}` ) } } } // Check wasmAssets if (output.wasmAssets) { for (const [key, wasmPath] of Object.entries(output.wasmAssets)) { try { await fs.promises.access(wasmPath, fs.constants.F_OK) } catch (err) { validationErrors.push( `Missing wasm file for output ${output.id} (${key}): ${wasmPath}` ) } } } } // Validate that segment routes are present in routing.dynamicRoutes // Segment routes match the pattern: .segments/.+.segment.rsc const segmentRoutes = ctx.routing.dynamicRoutes.filter((route) => { // Check if the source or destination contains segment routes return ( route.sourceRegex.includes('.segments/') || route.sourceRegex.includes('.segment.rsc') ) }) // Ensure we have segment routes when we have app pages if (ctx.outputs.appPages.length > 0) { if (segmentRoutes.length === 0) { validationErrors.push( 'Expected segment routes in routing.dynamicRoutes when app pages exist' ) } else { console.log( `Found ${segmentRoutes.length} segment routes in routing.dynamicRoutes` ) } } // Validate that all appPages have matching .rsc and non .rsc pathnames const appPagePathnames = new Map() for (const appPage of ctx.outputs.appPages) { const pathname = appPage.pathname if (pathname.endsWith('.rsc')) { const basePathname = pathname.slice(0, -4) // Remove .rsc extension if (!appPagePathnames.has(basePathname)) { appPagePathnames.set(basePathname, { rsc: false, nonRsc: false }) } appPagePathnames.get(basePathname).rsc = true } else { if (!appPagePathnames.has(pathname)) { appPagePathnames.set(pathname, { rsc: false, nonRsc: false }) } appPagePathnames.get(pathname).nonRsc = true } } // Check that each pathname has both .rsc and non .rsc versions for (const [pathname, versions] of appPagePathnames.entries()) { if (!versions.rsc) { validationErrors.push( `App page ${pathname} is missing corresponding .rsc pathname` ) } if (!versions.nonRsc) { validationErrors.push( `App page ${pathname}.rsc is missing corresponding non .rsc pathname` ) } } if (appPagePathnames.size > 0) { console.log( `Validated ${appPagePathnames.size} app page pathname(s) have matching .rsc and non .rsc versions` ) } if (validationErrors.length > 0) { console.error('Validation errors:') for (const error of validationErrors) { console.error(` - ${error}`) } throw new Error( `Adapter validation failed with ${validationErrors.length} error(s)` ) } console.log('Validation passed: All output files exist on filesystem') console.log( `Segment routes validated: ${segmentRoutes.length} routes found` ) await fs.promises.writeFile( 'build-complete.json', JSON.stringify(ctx, null, 2) ) }, } export default myAdapter