Skip to content

Commit

Permalink
feat: support envFiles in netlify.toml [dev] block (#4321)
Browse files Browse the repository at this point in the history
  • Loading branch information
iib0011 authored Feb 22, 2022
1 parent 57b199e commit 88be608
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 14 deletions.
1 change: 1 addition & 0 deletions docs/netlify-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ Netlify Dev is meant to work with zero config for the majority of users, by usin
jwtSecret = "secret" # The secret used to verify tokens for JWT based redirects
jwtRolePath = "app_metadata.authorization.roles" # Object path we should look for role values for JWT based redirects
autoLaunch = true # a Boolean value that determines if Netlify Dev launches the local server address in your browser
envFiles = [".env.development", ".env"] # The env files to use, ordered by priority (left - highest, right - lowest)
# to start an https server instead of an http one, configure a certificate and key files
[dev.https]
certFile = "cert.pem" # path to the certificate file
Expand Down
4 changes: 2 additions & 2 deletions src/commands/dev/dev-exec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ const { injectEnvVariables } = require('../../utils')
* @param {import('../base-command').BaseCommand} command
*/
const devExec = async (cmd, options, command) => {
const { cachedConfig, site } = command.netlify
await injectEnvVariables({ env: cachedConfig.env, site })
const { cachedConfig, config, site } = command.netlify
await injectEnvVariables({ devConfig: { ...config.dev }, env: cachedConfig.env, site })

await execa(cmd, command.args.slice(1), {
stdio: 'inherit',
Expand Down
2 changes: 1 addition & 1 deletion src/commands/dev/dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ const dev = async (options, command) => {
)
}

await injectEnvVariables({ env: command.netlify.cachedConfig.env, site })
await injectEnvVariables({ devConfig, env: command.netlify.cachedConfig.env, site })

const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
// inherited from base command --offline
Expand Down
3 changes: 2 additions & 1 deletion src/commands/dev/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export type DevConfig = {
https?: {
keyFile: string
certFile: string
}
},
envFiles?:string[]
}
16 changes: 14 additions & 2 deletions src/commands/functions/functions-create.js
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,14 @@ const createFunctionAddon = async function ({ addonName, addons, api, siteData,
* @param {(command: import('../base-command').BaseCommand) => any} config.onComplete
*/
const handleOnComplete = async ({ command, onComplete }) => {
const { config } = command.netlify

if (onComplete) {
await injectEnvVariables({ env: command.netlify.cachedConfig.env, site: command.netlify.site })
await injectEnvVariables({
devConfig: { ...config.dev },
env: command.netlify.cachedConfig.env,
site: command.netlify.site,
})
await onComplete.call(command)
}
}
Expand All @@ -491,6 +497,8 @@ const handleOnComplete = async ({ command, onComplete }) => {
* @param {string} config.fnPath
*/
const handleAddonDidInstall = async ({ addonCreated, addonDidInstall, command, fnPath }) => {
const { config } = command.netlify

if (!addonCreated || !addonDidInstall) {
return
}
Expand All @@ -508,7 +516,11 @@ const handleAddonDidInstall = async ({ addonCreated, addonDidInstall, command, f
return
}

await injectEnvVariables({ env: command.netlify.cachedConfig.env, site: command.netlify.site })
await injectEnvVariables({
devConfig: { ...config.dev },
env: command.netlify.cachedConfig.env,
site: command.netlify.site,
})
addonDidInstall(fnPath)
}

Expand Down
2 changes: 1 addition & 1 deletion src/commands/functions/functions-serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const functionsServe = async (options, command) => {

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

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

const { capabilities, siteUrl, timeouts } = await getSiteInformation({
offline: options.offline,
Expand Down
4 changes: 2 additions & 2 deletions src/utils/dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ const getEnvSourceName = (source) => {

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

dotEnvFiles.forEach(({ env: fileEnv, file }) => {
Object.keys(fileEnv).forEach((key) => {
Expand Down
13 changes: 8 additions & 5 deletions src/utils/dot-env.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ const { isFileAsync } = require('../lib/fs')

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

const loadDotEnvFiles = async function ({ projectDir }) {
const response = await tryLoadDotEnvFiles({ projectDir })
const loadDotEnvFiles = async function ({ envFiles, projectDir }) {
const response = await tryLoadDotEnvFiles({ projectDir, dotenvFiles: envFiles })

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

const tryLoadDotEnvFiles = async ({ projectDir }) => {
const dotenvFiles = ['.env', '.env.development']
// in the user configuration, the order is highest to lowest
const defaultEnvFiles = ['.env.development', '.env']

const tryLoadDotEnvFiles = async ({ projectDir, dotenvFiles = defaultEnvFiles }) => {
const results = await Promise.all(
dotenvFiles.map(async (file) => {
const filepath = path.resolve(projectDir, file)
Expand All @@ -40,7 +42,8 @@ const tryLoadDotEnvFiles = async ({ projectDir }) => {
}),
)

return results.filter(Boolean)
// we return in order of lowest to highest priority
return results.filter(Boolean).reverse()
}

module.exports = { loadDotEnvFiles, tryLoadDotEnvFiles }
34 changes: 34 additions & 0 deletions tests/integration/300.command.dev.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,5 +258,39 @@ testMatrix.forEach(({ args }) => {
})
})
})

test(testName('should inject env vars based on [dev].envFiles file order', args), async (t) => {
await withSiteBuilder('site-with-env-files', async (builder) => {
builder
.withNetlifyToml({
config: {
dev: { envFiles: ['.env.production', '.env.development', '.env'] },
functions: { directory: 'functions' },
},
})
.withEnvFile({ path: '.env.production', env: { TEST: 'FROM_PRODUCTION_FILE' } })
.withEnvFile({
path: '.env.development',
env: { TEST: 'FROM_DEVELOPMENT_FILE', TEST2: 'FROM_DEVELOPMENT_FILE' },
})
.withEnvFile({ path: '.env', env: { TEST: 'FROM_DEFAULT_FILE', TEST2: 'FROM_DEFAULT_FILE' } })
.withFunction({
path: 'env.js',
handler: async () => ({
statusCode: 200,
body: `${process.env.TEST}__${process.env.TEST2}`,
}),
})

await builder.buildAsync()

await withDevServer({ cwd: builder.directory, args }, async (server) => {
const response = await got(`${server.url}/.netlify/functions/env`).text()
t.is(response, 'FROM_PRODUCTION_FILE__FROM_DEVELOPMENT_FILE')
t.true(server.output.includes('Ignored .env.development file'))
t.true(server.output.includes('Ignored .env file'))
})
})
})
})
/* eslint-enable require-await */

1 comment on commit 88be608

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📊 Benchmark results

Package size: 443 MB

Please sign in to comment.