2020-09-25 20:14:28 +02:00
|
|
|
/* eslint-disable import/no-extraneous-dependencies */
|
|
|
|
import * as fs from 'fs'
|
|
|
|
import * as path from 'path'
|
|
|
|
import * as dotenv from 'dotenv'
|
2022-03-03 17:52:20 +01:00
|
|
|
import { expand as dotenvExpand } from 'dotenv-expand'
|
2020-03-26 13:32:41 +01:00
|
|
|
|
|
|
|
export type Env = { [key: string]: string }
|
2020-05-26 21:01:57 +02:00
|
|
|
export type LoadedEnvFiles = Array<{
|
|
|
|
path: string
|
|
|
|
contents: string
|
|
|
|
}>
|
2020-03-26 13:32:41 +01:00
|
|
|
|
2020-04-23 21:15:36 +02:00
|
|
|
let combinedEnv: Env | undefined = undefined
|
2020-06-01 23:00:22 +02:00
|
|
|
let cachedLoadedEnvFiles: LoadedEnvFiles = []
|
2020-04-23 21:15:36 +02:00
|
|
|
|
2020-09-25 20:14:28 +02:00
|
|
|
type Log = {
|
|
|
|
info: (...args: any[]) => void
|
|
|
|
error: (...args: any[]) => void
|
|
|
|
}
|
|
|
|
|
|
|
|
export function processEnv(
|
|
|
|
loadedEnvFiles: LoadedEnvFiles,
|
|
|
|
dir?: string,
|
|
|
|
log: Log = console
|
|
|
|
) {
|
2020-05-15 21:02:16 +02:00
|
|
|
// don't reload env if we already have since this breaks escaped
|
|
|
|
// environment values e.g. \$ENV_FILE_KEY
|
2020-09-25 20:14:28 +02:00
|
|
|
if (process.env.__NEXT_PROCESSED_ENV || loadedEnvFiles.length === 0) {
|
2020-05-26 21:01:57 +02:00
|
|
|
return process.env as Env
|
|
|
|
}
|
|
|
|
// flag that we processed the environment values in case a serverless
|
|
|
|
// function is re-used or we are running in `next start` mode
|
|
|
|
process.env.__NEXT_PROCESSED_ENV = 'true'
|
|
|
|
|
2020-08-02 22:15:11 +02:00
|
|
|
const origEnv = Object.assign({}, process.env)
|
|
|
|
const parsed: dotenv.DotenvParseOutput = {}
|
|
|
|
|
2020-05-26 21:01:57 +02:00
|
|
|
for (const envFile of loadedEnvFiles) {
|
|
|
|
try {
|
2020-09-25 20:14:28 +02:00
|
|
|
let result: dotenv.DotenvConfigOutput = {}
|
2020-05-26 21:01:57 +02:00
|
|
|
result.parsed = dotenv.parse(envFile.contents)
|
|
|
|
|
|
|
|
result = dotenvExpand(result)
|
|
|
|
|
|
|
|
if (result.parsed) {
|
|
|
|
log.info(`Loaded env from ${path.join(dir || '', envFile.path)}`)
|
|
|
|
}
|
|
|
|
|
2020-08-02 22:15:11 +02:00
|
|
|
for (const key of Object.keys(result.parsed || {})) {
|
|
|
|
if (
|
|
|
|
typeof parsed[key] === 'undefined' &&
|
|
|
|
typeof origEnv[key] === 'undefined'
|
|
|
|
) {
|
|
|
|
parsed[key] = result.parsed?.[key]!
|
|
|
|
}
|
|
|
|
}
|
2020-05-26 21:01:57 +02:00
|
|
|
} catch (err) {
|
|
|
|
log.error(
|
|
|
|
`Failed to load env from ${path.join(dir || '', envFile.path)}`,
|
|
|
|
err
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-02 22:15:11 +02:00
|
|
|
return Object.assign(process.env, parsed)
|
2020-05-26 21:01:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export function loadEnvConfig(
|
|
|
|
dir: string,
|
2020-09-25 20:14:28 +02:00
|
|
|
dev?: boolean,
|
|
|
|
log: Log = console
|
2020-05-26 21:01:57 +02:00
|
|
|
): {
|
|
|
|
combinedEnv: Env
|
|
|
|
loadedEnvFiles: LoadedEnvFiles
|
|
|
|
} {
|
|
|
|
// don't reload env if we already have since this breaks escaped
|
|
|
|
// environment values e.g. \$ENV_FILE_KEY
|
2020-06-01 23:00:22 +02:00
|
|
|
if (combinedEnv) return { combinedEnv, loadedEnvFiles: cachedLoadedEnvFiles }
|
2020-04-23 21:15:36 +02:00
|
|
|
|
2020-03-26 13:32:41 +01:00
|
|
|
const isTest = process.env.NODE_ENV === 'test'
|
|
|
|
const mode = isTest ? 'test' : dev ? 'development' : 'production'
|
|
|
|
const dotenvFiles = [
|
|
|
|
`.env.${mode}.local`,
|
|
|
|
// Don't include `.env.local` for `test` environment
|
|
|
|
// since normally you expect tests to produce the same
|
|
|
|
// results for everyone
|
|
|
|
mode !== 'test' && `.env.local`,
|
2020-05-22 19:13:16 +02:00
|
|
|
`.env.${mode}`,
|
2020-03-26 13:32:41 +01:00
|
|
|
'.env',
|
|
|
|
].filter(Boolean) as string[]
|
|
|
|
|
|
|
|
for (const envFile of dotenvFiles) {
|
|
|
|
// only load .env if the user provided has an env config file
|
|
|
|
const dotEnvPath = path.join(dir, envFile)
|
|
|
|
|
|
|
|
try {
|
2020-03-31 18:27:46 +02:00
|
|
|
const stats = fs.statSync(dotEnvPath)
|
|
|
|
|
|
|
|
// make sure to only attempt to read files
|
|
|
|
if (!stats.isFile()) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-03-26 13:32:41 +01:00
|
|
|
const contents = fs.readFileSync(dotEnvPath, 'utf8')
|
2020-06-01 23:00:22 +02:00
|
|
|
cachedLoadedEnvFiles.push({
|
2020-05-26 21:01:57 +02:00
|
|
|
path: envFile,
|
|
|
|
contents,
|
|
|
|
})
|
2021-09-16 18:06:57 +02:00
|
|
|
} catch (err: any) {
|
2020-03-26 13:32:41 +01:00
|
|
|
if (err.code !== 'ENOENT') {
|
2020-04-23 21:17:05 +02:00
|
|
|
log.error(`Failed to load env from ${envFile}`, err)
|
2020-03-26 13:32:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-02-25 20:30:38 +01:00
|
|
|
combinedEnv = processEnv(cachedLoadedEnvFiles, dir, log)
|
2020-06-01 23:00:22 +02:00
|
|
|
return { combinedEnv, loadedEnvFiles: cachedLoadedEnvFiles }
|
2020-03-26 13:32:41 +01:00
|
|
|
}
|