Skip to content
Draft
6 changes: 5 additions & 1 deletion src/commands/dev-exec/dev-exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ export const devExec = async (cmd: string, options: OptionValues, command: BaseC

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const withEnvelopeEnvVars = await getEnvelopeEnv({ api, context: options.context, env: cachedConfig.env, siteInfo })
const env = await getDotEnvVariables({ devConfig: { ...config.dev }, env: withEnvelopeEnvVars, site })
const env = await getDotEnvVariables({
devConfig: { framework: '#auto', ...config.dev },
env: withEnvelopeEnvVars,
site,
})

const { capabilities, siteUrl } = await getSiteInformation({
offline: false,
Expand Down
2 changes: 1 addition & 1 deletion src/commands/functions/functions-serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const functionsServe = async (options: OptionValues, command: BaseCommand

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

env = await getDotEnvVariables({ devConfig: { ...config.dev }, env, site })
env = await getDotEnvVariables({ devConfig: { framework: '#auto', ...config.dev }, env, site })

const { accountId, capabilities, siteUrl, timeouts } = await getSiteInformation({
offline: options.offline,
Expand Down
24 changes: 15 additions & 9 deletions src/utils/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { supportsBackgroundFunctions } from '../lib/account.js'

import { NETLIFYDEVLOG, chalk, logAndThrowError, log, warn, APIError } from './command-helpers.js'
import { loadDotEnvFiles } from './dot-env.js'
import type { DevConfig } from '../commands/dev/types.js'
import type { EnvironmentVariables, SiteInfo } from './types.js'

// Possible sources of environment variables. For the purpose of printing log messages only. Order does not matter.
Expand Down Expand Up @@ -41,8 +42,7 @@ const ENV_VAR_SOURCES = {
const ERROR_CALL_TO_ACTION =
"Double-check your login status with 'netlify status' or contact support with details of your error."

// @ts-expect-error TS(7031) FIXME: Binding element 'site' implicitly has an 'any' typ... Remove this comment to see the full error message
const validateSiteInfo = ({ site, siteInfo }) => {
const validateSiteInfo = ({ site, siteInfo }: { site: { id?: string }; siteInfo: SiteInfo }) => {
if (isEmpty(siteInfo)) {
return logAndThrowError(
`Failed to retrieve project information for project ${chalk.yellow(site.id)}. ${ERROR_CALL_TO_ACTION}`,
Expand Down Expand Up @@ -192,10 +192,17 @@ const getEnvSourceName = (source) => {
/**
* @param {{devConfig: any, env: Record<string, { sources: string[], value: string}>, site: any}} param0
*/
// @ts-expect-error TS(7031) FIXME: Binding element 'devConfig' implicitly has an 'any... Remove this comment to see the full error message
export const getDotEnvVariables = async ({ devConfig, env, site }): Promise<EnvironmentVariables> => {
const dotEnvFiles = await loadDotEnvFiles({ envFiles: devConfig.envFiles, projectDir: site.root })
// @ts-expect-error TS(2339) FIXME: Property 'env' does not exist on type '{ warning: ... Remove this comment to see the full error message
export const getDotEnvVariables = async ({
devConfig,
env,
site,
}: {
devConfig: DevConfig
env: EnvironmentVariables
site: { root?: string }
}): Promise<EnvironmentVariables> => {
const dotEnvFiles = await loadDotEnvFiles({ envFiles: devConfig.envFiles, projectDir: site.root || '' })

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

Expand Down Expand Up @@ -272,9 +279,8 @@ export const acquirePort = async ({
return acquiredPort
}

// @ts-expect-error TS(7006) FIXME: Parameter 'fn' implicitly has an 'any' type.
export const processOnExit = (fn) => {
const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP', 'exit']
export const processOnExit = (fn: (...args: unknown[]) => unknown) => {
const signals: (NodeJS.Signals | 'exit')[] = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP', 'exit']
signals.forEach((signal) => {
process.on(signal, fn)
})
Expand Down
55 changes: 38 additions & 17 deletions src/utils/dot-env.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,65 @@
import { readFile } from 'fs/promises'
import path from 'path'

import dotenv from 'dotenv'
import dotenv, { type DotenvParseOutput } from 'dotenv'

import { isFileAsync } from '../lib/fs.js'

import { warn } from './command-helpers.js'

// @ts-expect-error TS(7031) FIXME: Binding element 'envFiles' implicitly has an 'any'... Remove this comment to see the full error message
export const loadDotEnvFiles = async function ({ envFiles, projectDir }) {
interface LoadDotEnvFilesOptions {
envFiles?: string[]
projectDir: string
}

export interface DotEnvFile {
file: string
env: DotenvParseOutput
}

export interface DotEnvWarning {
warning: string
}

type DotEnvResult = DotEnvFile | DotEnvWarning

export const loadDotEnvFiles = async function ({
envFiles,
projectDir,
}: LoadDotEnvFilesOptions): Promise<DotEnvFile[]> {
const response = await tryLoadDotEnvFiles({ projectDir, dotenvFiles: envFiles })

// @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'.
const filesWithWarning = response.filter((el) => el.warning)
filesWithWarning.forEach((el) => {
// @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'.
warn(el.warning)
const filesWithWarning = response.filter((result): result is DotEnvWarning => 'warning' in result)
filesWithWarning.forEach((result) => {
warn(result.warning)
})

// @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'.
return response.filter((el) => el.file && el.env)
return response.filter((result): result is DotEnvFile => 'file' in result && 'env' in result)
}

// in the user configuration, the order is highest to lowest
const defaultEnvFiles = ['.env.development.local', '.env.local', '.env.development', '.env']

// @ts-expect-error TS(7031) FIXME: Binding element 'projectDir' implicitly has an 'an... Remove this comment to see the full error message
export const tryLoadDotEnvFiles = async ({ dotenvFiles = defaultEnvFiles, projectDir }) => {
interface TryLoadDotEnvFilesOptions {
dotenvFiles?: string[]
projectDir: string
}

export const tryLoadDotEnvFiles = async ({
dotenvFiles = defaultEnvFiles,
projectDir,
}: TryLoadDotEnvFilesOptions): Promise<DotEnvResult[]> => {
const results = await Promise.all(
dotenvFiles.map(async (file) => {
dotenvFiles.map(async (file): Promise<DotEnvResult | undefined> => {
const filepath = path.resolve(projectDir, file)
try {
const isFile = await isFileAsync(filepath)
if (!isFile) {
return
return undefined
}
} catch (error) {
return {
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
warning: `Failed reading env variables from file: ${filepath}: ${error.message}`,
warning: `Failed reading env variables from file: ${filepath}: ${(error as Error).message}`,
}
}
const content = await readFile(filepath, 'utf-8')
Expand All @@ -48,5 +69,5 @@ export const tryLoadDotEnvFiles = async ({ dotenvFiles = defaultEnvFiles, projec
)

// we return in order of lowest to highest priority
return results.filter(Boolean).reverse()
return results.filter((result): result is DotEnvResult => result !== undefined).reverse()
}
2 changes: 1 addition & 1 deletion src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export interface Template {
}

type EnvironmentVariableScope = 'builds' | 'functions' | 'runtime' | 'post_processing'
export type EnvironmentVariableSource = 'account' | 'addons' | 'configFile' | 'general' | 'internal' | 'ui'
export type EnvironmentVariableSource = 'account' | 'addons' | 'configFile' | 'general' | 'internal' | 'ui' | string

export type EnvironmentVariables = Record<
string,
Expand Down
Loading