2021-10-18 09:03:39 +00:00
|
|
|
import { promises as fsp, existsSync } from 'fs'
|
2021-09-27 12:49:36 +00:00
|
|
|
import { resolve } from 'pathe'
|
2021-10-12 16:21:01 +00:00
|
|
|
import dotenv from 'dotenv'
|
2021-09-25 08:18:01 +00:00
|
|
|
import { LoadNuxtConfigOptions } from './load'
|
2021-03-28 20:14:04 +00:00
|
|
|
|
|
|
|
export interface LoadDotEnvOptions {
|
2021-04-15 18:49:29 +00:00
|
|
|
/** The project root directory (either absolute or relative to the current working directory). */
|
2021-03-28 20:14:04 +00:00
|
|
|
rootDir: string
|
2021-04-15 18:49:29 +00:00
|
|
|
/**
|
|
|
|
* What file to look in for environment variables (either absolute or relative
|
|
|
|
* to the current working directory). For example, `.env`.
|
|
|
|
*/
|
2021-09-25 08:18:01 +00:00
|
|
|
dotenvFile: string | false
|
2021-04-15 18:49:29 +00:00
|
|
|
/**
|
|
|
|
* Whether to interpolate variables within .env.
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
* ```env
|
|
|
|
* BASE_DIR="/test"
|
|
|
|
* # resolves to "/test/further"
|
|
|
|
* ANOTHER_DIR="${BASE_DIR}/further"
|
|
|
|
* ```
|
|
|
|
*/
|
2021-03-28 20:14:04 +00:00
|
|
|
expand: boolean
|
2021-04-15 18:49:29 +00:00
|
|
|
/** An object describing environment variables (key, value pairs). */
|
|
|
|
env: NodeJS.ProcessEnv
|
2021-03-28 20:14:04 +00:00
|
|
|
}
|
|
|
|
|
2021-04-15 18:49:29 +00:00
|
|
|
/**
|
|
|
|
* Load and interpolate environment variables into `process.env`.
|
|
|
|
* If you need more control (or access to the values), consider using `loadDotenv` instead
|
|
|
|
*
|
|
|
|
* @param rootDir - The project root directory (either absolute or relative to the current working directory).
|
|
|
|
*/
|
2021-09-25 08:18:01 +00:00
|
|
|
export async function loadEnv (rootDir: string, options: LoadNuxtConfigOptions['envConfig'] = {}) {
|
|
|
|
const targetEnv = options.env ?? process.env
|
|
|
|
|
2021-03-28 20:14:04 +00:00
|
|
|
// Load env
|
2021-04-15 18:49:29 +00:00
|
|
|
const env = await loadDotenv({
|
2021-03-28 20:14:04 +00:00
|
|
|
rootDir,
|
2021-09-25 08:18:01 +00:00
|
|
|
dotenvFile: options.dotenv ?? '.env',
|
|
|
|
env: targetEnv,
|
|
|
|
expand: options.expand ?? true
|
2021-03-28 20:14:04 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
// Fill process.env so it is accessible in nuxt.config
|
|
|
|
for (const key in env) {
|
2021-09-25 08:18:01 +00:00
|
|
|
if (!key.startsWith('_') && targetEnv[key] === undefined) {
|
|
|
|
targetEnv[key] = env[key]
|
2021-03-28 20:14:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:49:29 +00:00
|
|
|
/** Load environment variables into an object. */
|
|
|
|
export async function loadDotenv (opts: LoadDotEnvOptions) {
|
2021-09-25 08:18:01 +00:00
|
|
|
if (!opts.dotenvFile) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-03-28 20:14:04 +00:00
|
|
|
const env = Object.create(null)
|
|
|
|
|
|
|
|
const dotenvFile = resolve(opts.rootDir, opts.dotenvFile)
|
|
|
|
|
2021-04-15 18:49:29 +00:00
|
|
|
if (existsSync(dotenvFile)) {
|
2021-10-12 16:21:01 +00:00
|
|
|
const parsed = dotenv.parse(await fsp.readFile(dotenvFile, 'utf-8'))
|
2021-03-28 20:14:04 +00:00
|
|
|
Object.assign(env, parsed)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply process.env
|
|
|
|
if (!opts.env._applied) {
|
|
|
|
Object.assign(env, opts.env)
|
|
|
|
env._applied = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Interpolate env
|
|
|
|
if (opts.expand) {
|
|
|
|
expand(env)
|
|
|
|
}
|
|
|
|
|
|
|
|
return env
|
|
|
|
}
|
|
|
|
|
|
|
|
// Based on https://github.com/motdotla/dotenv-expand
|
2021-04-15 18:49:29 +00:00
|
|
|
function expand (target: Record<string, any>, source: Record<string, any> = {}, parse = (v: any) => v) {
|
|
|
|
function getValue (key: string) {
|
2021-03-28 20:14:04 +00:00
|
|
|
// Source value 'wins' over target value
|
|
|
|
return source[key] !== undefined ? source[key] : target[key]
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:49:29 +00:00
|
|
|
function interpolate (value: unknown, parents: string[] = []) {
|
2021-03-28 20:14:04 +00:00
|
|
|
if (typeof value !== 'string') {
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
const matches = value.match(/(.?\${?(?:[a-zA-Z0-9_:]+)?}?)/g) || []
|
|
|
|
return parse(matches.reduce((newValue, match) => {
|
|
|
|
const parts = /(.?)\${?([a-zA-Z0-9_:]+)?}?/g.exec(match)
|
|
|
|
const prefix = parts[1]
|
|
|
|
|
2021-04-15 18:49:29 +00:00
|
|
|
let value, replacePart: string
|
2021-03-28 20:14:04 +00:00
|
|
|
|
|
|
|
if (prefix === '\\') {
|
|
|
|
replacePart = parts[0]
|
|
|
|
value = replacePart.replace('\\$', '$')
|
|
|
|
} else {
|
|
|
|
const key = parts[2]
|
|
|
|
replacePart = parts[0].substring(prefix.length)
|
|
|
|
|
|
|
|
// Avoid recursion
|
|
|
|
if (parents.includes(key)) {
|
|
|
|
console.warn(`Please avoid recursive environment variables ( loop: ${parents.join(' > ')} > ${key} )`)
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
|
|
|
|
value = getValue(key)
|
|
|
|
|
|
|
|
// Resolve recursive interpolations
|
|
|
|
value = interpolate(value, [...parents, key])
|
|
|
|
}
|
|
|
|
|
|
|
|
return value !== undefined ? newValue.replace(replacePart, value) : newValue
|
|
|
|
}, value))
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const key in target) {
|
|
|
|
target[key] = interpolate(getValue(key))
|
|
|
|
}
|
|
|
|
}
|