@@ -3,6 +3,7 @@ import js from 'dedent'
33import execa from 'execa'
44import getPort from 'get-port'
55import fetch from 'node-fetch'
6+ import semver from 'semver'
67import { describe , test } from 'vitest'
78import waitPort from 'wait-port'
89
@@ -203,6 +204,155 @@ describe.concurrent('functions:serve command', () => {
203204 } )
204205 } )
205206
207+ test ( 'should thread env vars from user env to function execution environment' , async ( t ) => {
208+ const port = await getPort ( )
209+ await withSiteBuilder ( t , async ( builder ) => {
210+ await builder
211+ . withContentFile ( {
212+ path : 'netlify/functions/get-env.js' ,
213+ content : `
214+ export default async () => Response.json(process.env)
215+ export const config = { path: "/get-env" }
216+ ` ,
217+ } )
218+ . build ( )
219+
220+ await withFunctionsServer ( { builder, args : [ '--port' , port . toString ( ) ] , port, env : { foo : 'bar' } } , async ( ) => {
221+ const response = await fetch ( `http://localhost:${ port . toString ( ) } /get-env` )
222+ t . expect ( await response . json ( ) ) . toMatchObject ( t . expect . objectContaining ( { foo : 'bar' } ) )
223+ } )
224+ } )
225+ } )
226+
227+ test ( 'should thread `NODE_OPTIONS` if set in user env to function execution environment' , async ( t ) => {
228+ const port = await getPort ( )
229+ await withSiteBuilder ( t , async ( builder ) => {
230+ await builder
231+ . withContentFile ( {
232+ path : 'netlify/functions/get-env.js' ,
233+ content : `
234+ export default async () => new Response(process.env.NODE_OPTIONS)
235+ export const config = { path: "/get-env" }
236+ ` ,
237+ } )
238+ . build ( )
239+
240+ await withFunctionsServer (
241+ {
242+ builder,
243+ args : [ '--port' , port . toString ( ) ] ,
244+ port,
245+ env : { NODE_OPTIONS : '--abort-on-uncaught-exception --trace-exit' } ,
246+ } ,
247+ async ( ) => {
248+ const response = await fetch ( `http://localhost:${ port . toString ( ) } /get-env` )
249+ t . expect ( await response . text ( ) ) . toContain ( '--abort-on-uncaught-exception --trace-exit' )
250+ } ,
251+ )
252+ } )
253+ } )
254+
255+ // Testing just 22.12.0+ for simplicity. The real range is quite complex.
256+ test . runIf ( semver . gte ( process . versions . node , '22.12.0' ) ) (
257+ 'should add AWS Lambda compat `NODE_OPTIONS` to function execution environment' ,
258+ async ( t ) => {
259+ const port = await getPort ( )
260+ await withSiteBuilder ( t , async ( builder ) => {
261+ await builder
262+ . withContentFile ( {
263+ path : 'netlify/functions/get-env.js' ,
264+ content : `
265+ export default async () => new Response(process.env.NODE_OPTIONS)
266+ export const config = { path: "/get-env" }
267+ ` ,
268+ } )
269+ . build ( )
270+
271+ await withFunctionsServer (
272+ {
273+ builder,
274+ args : [ '--port' , port . toString ( ) ] ,
275+ port,
276+ env : { NODE_OPTIONS : '--abort-on-uncaught-exception --trace-exit' } ,
277+ } ,
278+ async ( ) => {
279+ const response = await fetch ( `http://localhost:${ port . toString ( ) } /get-env` )
280+ const body = await response . text ( )
281+ t . expect ( body ) . toContain ( '--no-experimental-require-module' )
282+ t . expect ( body ) . toContain ( '--no-experimental-detect-module' )
283+ t . expect ( body ) . toContain ( '--abort-on-uncaught-exception --trace-exit' )
284+ } ,
285+ )
286+ } )
287+ } ,
288+ )
289+
290+ test . runIf (
291+ process . allowedNodeEnvironmentFlags . has ( '--no-experimental-require-module' ) ||
292+ process . allowedNodeEnvironmentFlags . has ( '--experimental-require-module' ) ,
293+ ) ( 'should allow user to re-enable experimental require module feature' , async ( t ) => {
294+ const port = await getPort ( )
295+ await withSiteBuilder ( t , async ( builder ) => {
296+ await builder
297+ . withContentFile ( {
298+ path : 'netlify/functions/get-env.js' ,
299+ content : `
300+ export default async () => new Response(process.env.NODE_OPTIONS)
301+ export const config = { path: "/get-env" }
302+ ` ,
303+ } )
304+ . build ( )
305+
306+ await withFunctionsServer (
307+ {
308+ builder,
309+ args : [ '--port' , port . toString ( ) ] ,
310+ port,
311+ env : { NODE_OPTIONS : '--experimental-require-module' } ,
312+ } ,
313+ async ( ) => {
314+ const response = await fetch ( `http://localhost:${ port . toString ( ) } /get-env` )
315+ const body = await response . text ( )
316+ t . expect ( body ) . toContain ( '--experimental-require-module' )
317+ t . expect ( body ) . not . toContain ( '--no-experimental-require-module' )
318+ } ,
319+ )
320+ } )
321+ } )
322+
323+ test . runIf (
324+ process . allowedNodeEnvironmentFlags . has ( '--no-experimental-detect-module' ) ||
325+ process . allowedNodeEnvironmentFlags . has ( '--experimental-detect-module' ) ,
326+ ) ( 'should allow user to re-enable experimental detect module feature' , async ( t ) => {
327+ const port = await getPort ( )
328+ await withSiteBuilder ( t , async ( builder ) => {
329+ await builder
330+ . withContentFile ( {
331+ path : 'netlify/functions/get-env.js' ,
332+ content : `
333+ export default async () => new Response(process.env.NODE_OPTIONS)
334+ export const config = { path: "/get-env" }
335+ ` ,
336+ } )
337+ . build ( )
338+
339+ await withFunctionsServer (
340+ {
341+ builder,
342+ args : [ '--port' , port . toString ( ) ] ,
343+ port,
344+ env : { NODE_OPTIONS : '--experimental-detect-module' } ,
345+ } ,
346+ async ( ) => {
347+ const response = await fetch ( `http://localhost:${ port . toString ( ) } /get-env` )
348+ const body = await response . text ( )
349+ t . expect ( body ) . toContain ( '--experimental-detect-module' )
350+ t . expect ( body ) . not . toContain ( '--no-experimental-detect-module' )
351+ } ,
352+ )
353+ } )
354+ } )
355+
206356 test ( 'should inject AI Gateway when linked site and online' , async ( t ) => {
207357 await withSiteBuilder ( t , async ( builder ) => {
208358 const { siteInfo, aiGatewayToken, routes } = createAIGatewayTestData ( )
0 commit comments