import { promises as fsp, existsSync } from 'fs' import { resolve } from 'pathe' import dotenv from 'dotenv' import { LoadNuxtConfigOptions } from './load' export interface LoadDotEnvOptions { /** The project root directory (either absolute or relative to the current working directory). */ rootDir: string /** * What file to look in for environment variables (either absolute or relative * to the current working directory). For example, `.env`. */ dotenvFile: string | false /** * Whether to interpolate variables within .env. * * @example * ```env * BASE_DIR="/test" * # resolves to "/test/further" * ANOTHER_DIR="${BASE_DIR}/further" * ``` */ expand: boolean /** An object describing environment variables (key, value pairs). */ env: NodeJS.ProcessEnv } /** * 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). */ export async function loadEnv (rootDir: string, options: LoadNuxtConfigOptions['envConfig'] = {}) { const targetEnv = options.env ?? process.env // Load env const env = await loadDotenv({ rootDir, dotenvFile: options.dotenv ?? '.env', env: targetEnv, expand: options.expand ?? true }) // Fill process.env so it is accessible in nuxt.config for (const key in env) { if (!key.startsWith('_') && targetEnv[key] === undefined) { targetEnv[key] = env[key] } } } /** Load environment variables into an object. */ export async function loadDotenv (opts: LoadDotEnvOptions) { if (!opts.dotenvFile) { return } const env = Object.create(null) const dotenvFile = resolve(opts.rootDir, opts.dotenvFile) if (existsSync(dotenvFile)) { const parsed = dotenv.parse(await fsp.readFile(dotenvFile, 'utf-8')) 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 function expand (target: Record, source: Record = {}, parse = (v: any) => v) { function getValue (key: string) { // Source value 'wins' over target value return source[key] !== undefined ? source[key] : target[key] } function interpolate (value: unknown, parents: string[] = []) { 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] let value, replacePart: string 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)) } }