Skip to content

Commit 9066166

Browse files
authored
feat: inject environment variables from .env files into edge functions locally (#5620)
* feat: inject environment variables from .env files into edge functions locally * chore: test * chore: rename function * chore: fix test on windows
1 parent f30d409 commit 9066166

File tree

8 files changed

+54
-27
lines changed

8 files changed

+54
-27
lines changed

src/commands/dev/dev-exec.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import execa from 'execa'
22

3-
import { injectEnvVariables } from '../../utils/dev.mjs'
3+
import { getDotEnvVariables, injectEnvVariables } from '../../utils/dev.mjs'
44
import { getEnvelopeEnv, normalizeContext } from '../../utils/env/index.mjs'
55

66
/**
@@ -16,7 +16,8 @@ const devExec = async (cmd, options, command) => {
1616
env = await getEnvelopeEnv({ api, context: options.context, env, siteInfo })
1717
}
1818

19-
await injectEnvVariables({ devConfig: { ...config.dev }, env, site })
19+
env = await getDotEnvVariables({ devConfig: { ...config.dev }, env, site })
20+
injectEnvVariables(env)
2021

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

src/commands/dev/dev.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
normalizeConfig,
1919
} from '../../utils/command-helpers.mjs'
2020
import detectServerSettings, { getConfigWithPlugins } from '../../utils/detect-server-settings.mjs'
21-
import { getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
21+
import { getDotEnvVariables, getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
2222
import { getEnvelopeEnv, normalizeContext } from '../../utils/env/index.mjs'
2323
import { ensureNetlifyIgnore } from '../../utils/gitignore.mjs'
2424
import { startNetlifyGraph, startPollingForAPIAuthentication } from '../../utils/graph.mjs'
@@ -96,7 +96,8 @@ const dev = async (options, command) => {
9696
log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`)
9797
}
9898

99-
await injectEnvVariables({ devConfig, env, site })
99+
env = await getDotEnvVariables({ devConfig, env, site })
100+
injectEnvVariables(env)
100101
await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state })
101102

102103
const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({

src/commands/functions/functions-create.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import ora from 'ora'
1919
import { fileExistsAsync } from '../../lib/fs.mjs'
2020
import { getAddons, getCurrentAddon, getSiteData } from '../../utils/addons/prepare.mjs'
2121
import { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, error, log } from '../../utils/command-helpers.mjs'
22-
import { injectEnvVariables } from '../../utils/dev.mjs'
22+
import { getDotEnvVariables, injectEnvVariables } from '../../utils/dev.mjs'
2323
import execa from '../../utils/execa.mjs'
2424
import { readRepoURL, validateRepoURL } from '../../utils/read-repo-url.mjs'
2525

@@ -549,11 +549,12 @@ const handleOnComplete = async ({ command, onComplete }) => {
549549
const { config } = command.netlify
550550

551551
if (onComplete) {
552-
await injectEnvVariables({
552+
const env = await getDotEnvVariables({
553553
devConfig: { ...config.dev },
554554
env: command.netlify.cachedConfig.env,
555555
site: command.netlify.site,
556556
})
557+
injectEnvVariables(env)
557558
await onComplete.call(command)
558559
}
559560
}

src/commands/functions/functions-serve.mjs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { join } from 'path'
33

44
import { startFunctionsServer } from '../../lib/functions/server.mjs'
5-
import { acquirePort, getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
5+
import { acquirePort, getDotEnvVariables, getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
66
import { getFunctionsDir } from '../../utils/functions/index.mjs'
77

88
const DEFAULT_PORT = 9999
@@ -16,11 +16,12 @@ const functionsServe = async (options, command) => {
1616
const { api, config, site, siteInfo } = command.netlify
1717

1818
const functionsDir = getFunctionsDir({ options, config }, join('netlify', 'functions'))
19-
const { env } = command.netlify.cachedConfig
19+
let { env } = command.netlify.cachedConfig
2020

2121
env.NETLIFY_DEV = { sources: ['internal'], value: 'true' }
2222

23-
await injectEnvVariables({ devConfig: { ...config.dev }, env, site })
23+
env = await getDotEnvVariables({ devConfig: { ...config.dev }, env, site })
24+
injectEnvVariables(env)
2425

2526
const { capabilities, siteUrl, timeouts } = await getSiteInformation({
2627
offline: options.offline,

src/commands/serve/serve.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
normalizeConfig,
1717
} from '../../utils/command-helpers.mjs'
1818
import detectServerSettings, { getConfigWithPlugins } from '../../utils/detect-server-settings.mjs'
19-
import { getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
19+
import { getDotEnvVariables, getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
2020
import { getEnvelopeEnv, normalizeContext } from '../../utils/env/index.mjs'
2121
import { getInternalFunctionsDir } from '../../utils/functions/functions.mjs'
2222
import { ensureNetlifyIgnore } from '../../utils/gitignore.mjs'
@@ -52,7 +52,8 @@ const serve = async (options, command) => {
5252
log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`)
5353
}
5454

55-
await injectEnvVariables({ devConfig, env, site })
55+
env = await getDotEnvVariables({ devConfig, env, site })
56+
injectEnvVariables(env)
5657
await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state })
5758

5859
const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({

src/lib/edge-functions/registry.mjs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class EdgeFunctionsRegistry {
1515
* @param {object} opts.config
1616
* @param {string} opts.configPath
1717
* @param {string[]} opts.directories
18-
* @param {Record<string, string>} opts.env
18+
* @param {Record<string, { sources: string[], value: string}>} opts.env
1919
* @param {() => Promise<object>} opts.getUpdatedConfig
2020
* @param {Declaration[]} opts.internalFunctions
2121
* @param {string} opts.projectDir
@@ -178,14 +178,20 @@ export class EdgeFunctionsRegistry {
178178
return edgeFunctions
179179
}
180180

181+
/**
182+
*
183+
* @param {Record<string, { sources: string[], value: string}>} envConfig
184+
* @returns {Record<string, string>}
185+
*/
181186
static getEnvironmentVariables(envConfig) {
182187
const env = Object.create(null)
183188
Object.entries(envConfig).forEach(([key, variable]) => {
184189
if (
185190
variable.sources.includes('ui') ||
186191
variable.sources.includes('account') ||
187192
variable.sources.includes('addons') ||
188-
variable.sources.includes('internal')
193+
variable.sources.includes('internal') ||
194+
variable.sources.some((source) => source.startsWith('.env'))
189195
) {
190196
env[key] = variable.value
191197
}

src/utils/dev.mjs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,30 +137,40 @@ const getEnvSourceName = (source) => {
137137
return printFn(name)
138138
}
139139

140-
// Takes a set of environment variables in the format provided by @netlify/config, augments it with variables from both
141-
// dot-env files and the process itself, and injects into `process.env`.
142-
export const injectEnvVariables = async ({ devConfig, env, site }) => {
143-
const environment = new Map(Object.entries(env))
140+
/**
141+
* @param {{devConfig: any, env: Record<string, { sources: string[], value: string}>, site: any}} param0
142+
* @returns {Promise<Record<string, { sources: string[], value: string}>>}
143+
*/
144+
export const getDotEnvVariables = async ({ devConfig, env, site }) => {
144145
const dotEnvFiles = await loadDotEnvFiles({ envFiles: devConfig.envFiles, projectDir: site.root })
145-
146146
dotEnvFiles.forEach(({ env: fileEnv, file }) => {
147+
const newSourceName = `${file} file`
148+
147149
Object.keys(fileEnv).forEach((key) => {
148-
const newSourceName = `${file} file`
149-
const sources = environment.has(key) ? [newSourceName, ...environment.get(key).sources] : [newSourceName]
150+
const sources = key in env ? [newSourceName, ...env[key].sources] : [newSourceName]
150151

151152
if (sources.includes('internal')) {
152153
return
153154
}
154155

155-
environment.set(key, {
156+
env[key] = {
156157
sources,
157158
value: fileEnv[key],
158-
})
159+
}
159160
})
160161
})
161162

163+
return env
164+
}
165+
166+
/**
167+
* Takes a set of environment variables in the format provided by @netlify/config and injects them into `process.env`
168+
* @param {Record<string, { sources: string[], value: string}>} env
169+
* @return {void}
170+
*/
171+
export const injectEnvVariables = (env) => {
162172
// eslint-disable-next-line fp/no-loops
163-
for (const [key, variable] of environment) {
173+
for (const [key, variable] of Object.entries(env)) {
164174
const existsInProcess = process.env[key] !== undefined
165175
const [usedSource, ...overriddenSources] = existsInProcess ? ['process', ...variable.sources] : variable.sources
166176
const usedSourceName = getEnvSourceName(usedSource)

tests/integration/100.command.dev.test.cjs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// Handlers are meant to be async outside tests
21
const path = require('path')
32

43
// eslint-disable-next-line ava/use-test
@@ -1020,6 +1019,10 @@ test('should have only allowed environment variables set', async (t) => {
10201019
handler: () => new Response(`${JSON.stringify(Deno.env.toObject())}`),
10211020
name: 'env',
10221021
})
1022+
.withContentFile({
1023+
content: 'FROM_ENV="YAS"',
1024+
path: '.env',
1025+
})
10231026

10241027
await builder.buildAsync()
10251028

@@ -1040,16 +1043,19 @@ test('should have only allowed environment variables set', async (t) => {
10401043
)
10411044
const envKeys = Object.keys(response)
10421045

1043-
t.false(envKeys.includes('DENO_DEPLOYMENT_ID'))
1044-
// t.true(envKeys.includes('DENO_DEPLOYMENT_ID'))
1045-
// t.is(response.DENO_DEPLOYMENT_ID, 'xxx=')
10461046
t.true(envKeys.includes('DENO_REGION'))
10471047
t.is(response.DENO_REGION, 'local')
1048+
10481049
t.true(envKeys.includes('NETLIFY_DEV'))
10491050
t.is(response.NETLIFY_DEV, 'true')
1051+
10501052
t.true(envKeys.includes('SECRET_ENV'))
10511053
t.is(response.SECRET_ENV, 'true')
10521054

1055+
t.true(envKeys.includes('FROM_ENV'))
1056+
t.is(response.FROM_ENV, 'YAS')
1057+
1058+
t.false(envKeys.includes('DENO_DEPLOYMENT_ID'))
10531059
t.false(envKeys.includes('NODE_ENV'))
10541060
t.false(envKeys.includes('DEPLOY_URL'))
10551061
t.false(envKeys.includes('URL'))

0 commit comments

Comments
 (0)