Skip to content

Commit 88be608

Browse files
authoredFeb 22, 2022
feat: support envFiles in netlify.toml [dev] block (#4321)
1 parent 57b199e commit 88be608

File tree

9 files changed

+65
-14
lines changed

9 files changed

+65
-14
lines changed
 

‎docs/netlify-dev.md

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ Netlify Dev is meant to work with zero config for the majority of users, by usin
121121
jwtSecret = "secret" # The secret used to verify tokens for JWT based redirects
122122
jwtRolePath = "app_metadata.authorization.roles" # Object path we should look for role values for JWT based redirects
123123
autoLaunch = true # a Boolean value that determines if Netlify Dev launches the local server address in your browser
124+
envFiles = [".env.development", ".env"] # The env files to use, ordered by priority (left - highest, right - lowest)
124125
# to start an https server instead of an http one, configure a certificate and key files
125126
[dev.https]
126127
certFile = "cert.pem" # path to the certificate file

‎src/commands/dev/dev-exec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ const { injectEnvVariables } = require('../../utils')
88
* @param {import('../base-command').BaseCommand} command
99
*/
1010
const devExec = async (cmd, options, command) => {
11-
const { cachedConfig, site } = command.netlify
12-
await injectEnvVariables({ env: cachedConfig.env, site })
11+
const { cachedConfig, config, site } = command.netlify
12+
await injectEnvVariables({ devConfig: { ...config.dev }, env: cachedConfig.env, site })
1313

1414
await execa(cmd, command.args.slice(1), {
1515
stdio: 'inherit',

‎src/commands/dev/dev.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ const dev = async (options, command) => {
326326
)
327327
}
328328

329-
await injectEnvVariables({ env: command.netlify.cachedConfig.env, site })
329+
await injectEnvVariables({ devConfig, env: command.netlify.cachedConfig.env, site })
330330

331331
const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
332332
// inherited from base command --offline

‎src/commands/dev/types.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ export type DevConfig = {
1717
https?: {
1818
keyFile: string
1919
certFile: string
20-
}
20+
},
21+
envFiles?:string[]
2122
}

‎src/commands/functions/functions-create.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -477,8 +477,14 @@ const createFunctionAddon = async function ({ addonName, addons, api, siteData,
477477
* @param {(command: import('../base-command').BaseCommand) => any} config.onComplete
478478
*/
479479
const handleOnComplete = async ({ command, onComplete }) => {
480+
const { config } = command.netlify
481+
480482
if (onComplete) {
481-
await injectEnvVariables({ env: command.netlify.cachedConfig.env, site: command.netlify.site })
483+
await injectEnvVariables({
484+
devConfig: { ...config.dev },
485+
env: command.netlify.cachedConfig.env,
486+
site: command.netlify.site,
487+
})
482488
await onComplete.call(command)
483489
}
484490
}
@@ -491,6 +497,8 @@ const handleOnComplete = async ({ command, onComplete }) => {
491497
* @param {string} config.fnPath
492498
*/
493499
const handleAddonDidInstall = async ({ addonCreated, addonDidInstall, command, fnPath }) => {
500+
const { config } = command.netlify
501+
494502
if (!addonCreated || !addonDidInstall) {
495503
return
496504
}
@@ -508,7 +516,11 @@ const handleAddonDidInstall = async ({ addonCreated, addonDidInstall, command, f
508516
return
509517
}
510518

511-
await injectEnvVariables({ env: command.netlify.cachedConfig.env, site: command.netlify.site })
519+
await injectEnvVariables({
520+
devConfig: { ...config.dev },
521+
env: command.netlify.cachedConfig.env,
522+
site: command.netlify.site,
523+
})
512524
addonDidInstall(fnPath)
513525
}
514526

‎src/commands/functions/functions-serve.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const functionsServe = async (options, command) => {
1717

1818
const functionsDir = getFunctionsDir({ options, config }, join('netlify', 'functions'))
1919

20-
await injectEnvVariables({ env: command.netlify.cachedConfig.env, site })
20+
await injectEnvVariables({ devConfig: { ...config.dev }, env: command.netlify.cachedConfig.env, site })
2121

2222
const { capabilities, siteUrl, timeouts } = await getSiteInformation({
2323
offline: options.offline,

‎src/utils/dev.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,9 @@ const getEnvSourceName = (source) => {
139139

140140
// Takes a set of environment variables in the format provided by @netlify/config, augments it with variables from both
141141
// dot-env files and the process itself, and injects into `process.env`.
142-
const injectEnvVariables = async ({ env, site }) => {
142+
const injectEnvVariables = async ({ devConfig, env, site }) => {
143143
const environment = new Map(Object.entries(env))
144-
const dotEnvFiles = await loadDotEnvFiles({ projectDir: site.root })
144+
const dotEnvFiles = await loadDotEnvFiles({ envFiles: devConfig.envFiles, projectDir: site.root })
145145

146146
dotEnvFiles.forEach(({ env: fileEnv, file }) => {
147147
Object.keys(fileEnv).forEach((key) => {

‎src/utils/dot-env.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ const { isFileAsync } = require('../lib/fs')
88

99
const { warn } = require('./command-helpers')
1010

11-
const loadDotEnvFiles = async function ({ projectDir }) {
12-
const response = await tryLoadDotEnvFiles({ projectDir })
11+
const loadDotEnvFiles = async function ({ envFiles, projectDir }) {
12+
const response = await tryLoadDotEnvFiles({ projectDir, dotenvFiles: envFiles })
1313

1414
const filesWithWarning = response.filter((el) => el.warning)
1515
filesWithWarning.forEach((el) => {
@@ -19,8 +19,10 @@ const loadDotEnvFiles = async function ({ projectDir }) {
1919
return response.filter((el) => el.file && el.env)
2020
}
2121

22-
const tryLoadDotEnvFiles = async ({ projectDir }) => {
23-
const dotenvFiles = ['.env', '.env.development']
22+
// in the user configuration, the order is highest to lowest
23+
const defaultEnvFiles = ['.env.development', '.env']
24+
25+
const tryLoadDotEnvFiles = async ({ projectDir, dotenvFiles = defaultEnvFiles }) => {
2426
const results = await Promise.all(
2527
dotenvFiles.map(async (file) => {
2628
const filepath = path.resolve(projectDir, file)
@@ -40,7 +42,8 @@ const tryLoadDotEnvFiles = async ({ projectDir }) => {
4042
}),
4143
)
4244

43-
return results.filter(Boolean)
45+
// we return in order of lowest to highest priority
46+
return results.filter(Boolean).reverse()
4447
}
4548

4649
module.exports = { loadDotEnvFiles, tryLoadDotEnvFiles }

‎tests/integration/300.command.dev.test.js

+34
Original file line numberDiff line numberDiff line change
@@ -258,5 +258,39 @@ testMatrix.forEach(({ args }) => {
258258
})
259259
})
260260
})
261+
262+
test(testName('should inject env vars based on [dev].envFiles file order', args), async (t) => {
263+
await withSiteBuilder('site-with-env-files', async (builder) => {
264+
builder
265+
.withNetlifyToml({
266+
config: {
267+
dev: { envFiles: ['.env.production', '.env.development', '.env'] },
268+
functions: { directory: 'functions' },
269+
},
270+
})
271+
.withEnvFile({ path: '.env.production', env: { TEST: 'FROM_PRODUCTION_FILE' } })
272+
.withEnvFile({
273+
path: '.env.development',
274+
env: { TEST: 'FROM_DEVELOPMENT_FILE', TEST2: 'FROM_DEVELOPMENT_FILE' },
275+
})
276+
.withEnvFile({ path: '.env', env: { TEST: 'FROM_DEFAULT_FILE', TEST2: 'FROM_DEFAULT_FILE' } })
277+
.withFunction({
278+
path: 'env.js',
279+
handler: async () => ({
280+
statusCode: 200,
281+
body: `${process.env.TEST}__${process.env.TEST2}`,
282+
}),
283+
})
284+
285+
await builder.buildAsync()
286+
287+
await withDevServer({ cwd: builder.directory, args }, async (server) => {
288+
const response = await got(`${server.url}/.netlify/functions/env`).text()
289+
t.is(response, 'FROM_PRODUCTION_FILE__FROM_DEVELOPMENT_FILE')
290+
t.true(server.output.includes('Ignored .env.development file'))
291+
t.true(server.output.includes('Ignored .env file'))
292+
})
293+
})
294+
})
261295
})
262296
/* eslint-enable require-await */

1 commit comments

Comments
 (1)

github-actions[bot] commented on Feb 22, 2022

@github-actions[bot]

📊 Benchmark results

Package size: 443 MB

Please sign in to comment.