1+ import { mkdir , writeFile } from 'node:fs/promises'
2+ import { dirname } from 'node:path'
3+
14import type { NextAdapter } from 'next-with-adapters'
5+ import type { RemotePattern } from 'next-with-adapters/dist/shared/lib/image-config.js'
6+ import { makeRe } from 'picomatch'
7+
8+ const NETLIFY_FRAMEWORKS_API_CONFIG_PATH = '.netlify/v1/config.json'
9+ const NETLIFY_IMAGE_LOADER_FILE = '@netlify/plugin-nextjs/dist/next-image-loader.cjs'
10+
11+ function generateRegexFromPattern ( pattern : string ) : string {
12+ return makeRe ( pattern ) . source
13+ }
214
315const adapter : NextAdapter = {
416 name : 'Netlify' ,
@@ -10,13 +22,90 @@ const adapter: NextAdapter = {
1022 // Set up Netlify Image CDN image's loaderFile
1123 // see https://nextjs.org/docs/app/api-reference/config/next-config-js/images
1224 config . images . loader = 'custom'
13- config . images . loaderFile = '@netlify/plugin-nextjs/dist/next-image-loader.cjs'
25+ config . images . loaderFile = NETLIFY_IMAGE_LOADER_FILE
1426 }
1527
1628 return config
1729 } ,
1830 async onBuildComplete ( ctx ) {
1931 console . log ( 'onBuildComplete hook called' )
32+
33+ let frameworksAPIConfig : any = null
34+ const { images } = ctx . config
35+ if ( images . loader === 'custom' && images . loaderFile === NETLIFY_IMAGE_LOADER_FILE ) {
36+ const { remotePatterns, domains } = images
37+ // if Netlify image loader is used, configure allowed remote image sources
38+ const remoteImageSources : string [ ] = [ ]
39+ if ( remotePatterns && remotePatterns . length !== 0 ) {
40+ // convert images.remotePatterns to regexes for Frameworks API
41+ for ( const remotePattern of remotePatterns ) {
42+ if ( remotePattern instanceof URL ) {
43+ // Note: even if URL notation is used in next.config.js, This will result in RemotePattern
44+ // object here, so types for the complete config should not have URL as an possible type
45+ throw new TypeError ( 'Received not supported URL instance in remotePatterns' )
46+ }
47+ let { protocol, hostname, port, pathname } : RemotePattern = remotePattern
48+
49+ if ( pathname ) {
50+ pathname = pathname . startsWith ( '/' ) ? pathname : `/${ pathname } `
51+ }
52+
53+ const combinedRemotePattern = `${ protocol ?? 'http?(s)' } ://${ hostname } ${
54+ port ? `:${ port } ` : ''
55+ } ${ pathname ?? '/**' } `
56+
57+ try {
58+ remoteImageSources . push ( generateRegexFromPattern ( combinedRemotePattern ) )
59+ } catch ( error ) {
60+ throw new Error (
61+ `Failed to generate Image CDN remote image regex from Next.js remote pattern: ${ JSON . stringify (
62+ { remotePattern, combinedRemotePattern } ,
63+ null ,
64+ 2 ,
65+ ) } `,
66+ {
67+ cause : error ,
68+ } ,
69+ )
70+ }
71+ }
72+ }
73+
74+ if ( domains && domains . length !== 0 ) {
75+ for ( const domain of domains ) {
76+ const patternFromDomain = `http?(s)://${ domain } /**`
77+ try {
78+ remoteImageSources . push ( generateRegexFromPattern ( patternFromDomain ) )
79+ } catch ( error ) {
80+ throw new Error (
81+ `Failed to generate Image CDN remote image regex from Next.js domain: ${ JSON . stringify (
82+ { domain, patternFromDomain } ,
83+ null ,
84+ 2 ,
85+ ) } `,
86+ { cause : error } ,
87+ )
88+ }
89+ }
90+ }
91+
92+ if ( remoteImageSources . length !== 0 ) {
93+ // https://docs.netlify.com/build/frameworks/frameworks-api/#images
94+ frameworksAPIConfig ??= { }
95+ frameworksAPIConfig . images ??= { }
96+ frameworksAPIConfig . images . remote_images = remoteImageSources
97+ }
98+ }
99+
100+ if ( frameworksAPIConfig ) {
101+ // write out config if there is any
102+ // https://docs.netlify.com/build/frameworks/frameworks-api/#netlifyv1configjson
103+ await mkdir ( dirname ( NETLIFY_FRAMEWORKS_API_CONFIG_PATH ) , { recursive : true } )
104+ await writeFile (
105+ NETLIFY_FRAMEWORKS_API_CONFIG_PATH ,
106+ JSON . stringify ( frameworksAPIConfig , null , 2 ) ,
107+ )
108+ }
20109 } ,
21110}
22111
0 commit comments