diff --git a/docs/netlify-dev.md b/docs/netlify-dev.md index 2d0448466c5..a57406f4d89 100644 --- a/docs/netlify-dev.md +++ b/docs/netlify-dev.md @@ -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 diff --git a/src/commands/dev/dev-exec.js b/src/commands/dev/dev-exec.js index 95e2196fcd0..86454f73f85 100644 --- a/src/commands/dev/dev-exec.js +++ b/src/commands/dev/dev-exec.js @@ -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', diff --git a/src/commands/dev/dev.js b/src/commands/dev/dev.js index 0011733ed31..bb6f06c7715 100644 --- a/src/commands/dev/dev.js +++ b/src/commands/dev/dev.js @@ -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 diff --git a/src/commands/dev/types.d.ts b/src/commands/dev/types.d.ts index 99c1a06bbf2..d6b83fab302 100644 --- a/src/commands/dev/types.d.ts +++ b/src/commands/dev/types.d.ts @@ -17,5 +17,6 @@ export type DevConfig = { https?: { keyFile: string certFile: string - } + }, + envFiles?:string[] } diff --git a/src/commands/functions/functions-create.js b/src/commands/functions/functions-create.js index 1d8fec0c54f..f38b5cc610c 100644 --- a/src/commands/functions/functions-create.js +++ b/src/commands/functions/functions-create.js @@ -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) } } @@ -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 } @@ -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) } diff --git a/src/commands/functions/functions-serve.js b/src/commands/functions/functions-serve.js index dd1f9037e27..8dc434ca71e 100644 --- a/src/commands/functions/functions-serve.js +++ b/src/commands/functions/functions-serve.js @@ -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, diff --git a/src/utils/dev.js b/src/utils/dev.js index 0457ff21fe5..dac2bd09ee1 100644 --- a/src/utils/dev.js +++ b/src/utils/dev.js @@ -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) => { diff --git a/src/utils/dot-env.js b/src/utils/dot-env.js index 90d5d1645d1..ee5efc8f064 100644 --- a/src/utils/dot-env.js +++ b/src/utils/dot-env.js @@ -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) => { @@ -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) @@ -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 } diff --git a/tests/integration/300.command.dev.test.js b/tests/integration/300.command.dev.test.js index 7ed8ae86f7c..d9179cc608b 100644 --- a/tests/integration/300.command.dev.test.js +++ b/tests/integration/300.command.dev.test.js @@ -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 */