Skip to content

Commit

Permalink
feat: inject environment variables from .env files into edge function…
Browse files Browse the repository at this point in the history
…s locally (#5620)

* feat: inject environment variables from .env files into edge functions locally

* chore: test

* chore: rename function

* chore: fix test on windows
  • Loading branch information
danez authored Apr 17, 2023
1 parent f30d409 commit 9066166
Show file tree
Hide file tree
Showing 8 changed files with 54 additions and 27 deletions.
5 changes: 3 additions & 2 deletions src/commands/dev/dev-exec.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import execa from 'execa'

import { injectEnvVariables } from '../../utils/dev.mjs'
import { getDotEnvVariables, injectEnvVariables } from '../../utils/dev.mjs'
import { getEnvelopeEnv, normalizeContext } from '../../utils/env/index.mjs'

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

await injectEnvVariables({ devConfig: { ...config.dev }, env, site })
env = await getDotEnvVariables({ devConfig: { ...config.dev }, env, site })
injectEnvVariables(env)

await execa(cmd, command.args.slice(1), {
stdio: 'inherit',
Expand Down
5 changes: 3 additions & 2 deletions src/commands/dev/dev.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
normalizeConfig,
} from '../../utils/command-helpers.mjs'
import detectServerSettings, { getConfigWithPlugins } from '../../utils/detect-server-settings.mjs'
import { getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
import { getDotEnvVariables, getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
import { getEnvelopeEnv, normalizeContext } from '../../utils/env/index.mjs'
import { ensureNetlifyIgnore } from '../../utils/gitignore.mjs'
import { startNetlifyGraph, startPollingForAPIAuthentication } from '../../utils/graph.mjs'
Expand Down Expand Up @@ -96,7 +96,8 @@ const dev = async (options, command) => {
log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`)
}

await injectEnvVariables({ devConfig, env, site })
env = await getDotEnvVariables({ devConfig, env, site })
injectEnvVariables(env)
await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state })

const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
Expand Down
5 changes: 3 additions & 2 deletions src/commands/functions/functions-create.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import ora from 'ora'
import { fileExistsAsync } from '../../lib/fs.mjs'
import { getAddons, getCurrentAddon, getSiteData } from '../../utils/addons/prepare.mjs'
import { NETLIFYDEVERR, NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, error, log } from '../../utils/command-helpers.mjs'
import { injectEnvVariables } from '../../utils/dev.mjs'
import { getDotEnvVariables, injectEnvVariables } from '../../utils/dev.mjs'
import execa from '../../utils/execa.mjs'
import { readRepoURL, validateRepoURL } from '../../utils/read-repo-url.mjs'

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

if (onComplete) {
await injectEnvVariables({
const env = await getDotEnvVariables({
devConfig: { ...config.dev },
env: command.netlify.cachedConfig.env,
site: command.netlify.site,
})
injectEnvVariables(env)
await onComplete.call(command)
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/commands/functions/functions-serve.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { join } from 'path'

import { startFunctionsServer } from '../../lib/functions/server.mjs'
import { acquirePort, getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
import { acquirePort, getDotEnvVariables, getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
import { getFunctionsDir } from '../../utils/functions/index.mjs'

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

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

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

await injectEnvVariables({ devConfig: { ...config.dev }, env, site })
env = await getDotEnvVariables({ devConfig: { ...config.dev }, env, site })
injectEnvVariables(env)

const { capabilities, siteUrl, timeouts } = await getSiteInformation({
offline: options.offline,
Expand Down
5 changes: 3 additions & 2 deletions src/commands/serve/serve.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
normalizeConfig,
} from '../../utils/command-helpers.mjs'
import detectServerSettings, { getConfigWithPlugins } from '../../utils/detect-server-settings.mjs'
import { getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
import { getDotEnvVariables, getSiteInformation, injectEnvVariables } from '../../utils/dev.mjs'
import { getEnvelopeEnv, normalizeContext } from '../../utils/env/index.mjs'
import { getInternalFunctionsDir } from '../../utils/functions/functions.mjs'
import { ensureNetlifyIgnore } from '../../utils/gitignore.mjs'
Expand Down Expand Up @@ -52,7 +52,8 @@ const serve = async (options, command) => {
log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`)
}

await injectEnvVariables({ devConfig, env, site })
env = await getDotEnvVariables({ devConfig, env, site })
injectEnvVariables(env)
await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state })

const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
Expand Down
10 changes: 8 additions & 2 deletions src/lib/edge-functions/registry.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class EdgeFunctionsRegistry {
* @param {object} opts.config
* @param {string} opts.configPath
* @param {string[]} opts.directories
* @param {Record<string, string>} opts.env
* @param {Record<string, { sources: string[], value: string}>} opts.env
* @param {() => Promise<object>} opts.getUpdatedConfig
* @param {Declaration[]} opts.internalFunctions
* @param {string} opts.projectDir
Expand Down Expand Up @@ -178,14 +178,20 @@ export class EdgeFunctionsRegistry {
return edgeFunctions
}

/**
*
* @param {Record<string, { sources: string[], value: string}>} envConfig
* @returns {Record<string, string>}
*/
static getEnvironmentVariables(envConfig) {
const env = Object.create(null)
Object.entries(envConfig).forEach(([key, variable]) => {
if (
variable.sources.includes('ui') ||
variable.sources.includes('account') ||
variable.sources.includes('addons') ||
variable.sources.includes('internal')
variable.sources.includes('internal') ||
variable.sources.some((source) => source.startsWith('.env'))
) {
env[key] = variable.value
}
Expand Down
30 changes: 20 additions & 10 deletions src/utils/dev.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -137,30 +137,40 @@ const getEnvSourceName = (source) => {
return printFn(name)
}

// 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`.
export const injectEnvVariables = async ({ devConfig, env, site }) => {
const environment = new Map(Object.entries(env))
/**
* @param {{devConfig: any, env: Record<string, { sources: string[], value: string}>, site: any}} param0
* @returns {Promise<Record<string, { sources: string[], value: string}>>}
*/
export const getDotEnvVariables = async ({ devConfig, env, site }) => {
const dotEnvFiles = await loadDotEnvFiles({ envFiles: devConfig.envFiles, projectDir: site.root })

dotEnvFiles.forEach(({ env: fileEnv, file }) => {
const newSourceName = `${file} file`

Object.keys(fileEnv).forEach((key) => {
const newSourceName = `${file} file`
const sources = environment.has(key) ? [newSourceName, ...environment.get(key).sources] : [newSourceName]
const sources = key in env ? [newSourceName, ...env[key].sources] : [newSourceName]

if (sources.includes('internal')) {
return
}

environment.set(key, {
env[key] = {
sources,
value: fileEnv[key],
})
}
})
})

return env
}

/**
* Takes a set of environment variables in the format provided by @netlify/config and injects them into `process.env`
* @param {Record<string, { sources: string[], value: string}>} env
* @return {void}
*/
export const injectEnvVariables = (env) => {
// eslint-disable-next-line fp/no-loops
for (const [key, variable] of environment) {
for (const [key, variable] of Object.entries(env)) {
const existsInProcess = process.env[key] !== undefined
const [usedSource, ...overriddenSources] = existsInProcess ? ['process', ...variable.sources] : variable.sources
const usedSourceName = getEnvSourceName(usedSource)
Expand Down
14 changes: 10 additions & 4 deletions tests/integration/100.command.dev.test.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// Handlers are meant to be async outside tests
const path = require('path')

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

await builder.buildAsync()

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

t.false(envKeys.includes('DENO_DEPLOYMENT_ID'))
// t.true(envKeys.includes('DENO_DEPLOYMENT_ID'))
// t.is(response.DENO_DEPLOYMENT_ID, 'xxx=')
t.true(envKeys.includes('DENO_REGION'))
t.is(response.DENO_REGION, 'local')

t.true(envKeys.includes('NETLIFY_DEV'))
t.is(response.NETLIFY_DEV, 'true')

t.true(envKeys.includes('SECRET_ENV'))
t.is(response.SECRET_ENV, 'true')

t.true(envKeys.includes('FROM_ENV'))
t.is(response.FROM_ENV, 'YAS')

t.false(envKeys.includes('DENO_DEPLOYMENT_ID'))
t.false(envKeys.includes('NODE_ENV'))
t.false(envKeys.includes('DEPLOY_URL'))
t.false(envKeys.includes('URL'))
Expand Down

1 comment on commit 9066166

@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: 302 MB

Please sign in to comment.