feat(core): update dotenv and load root env variables earlier (#18456)

Co-authored-by: FrozenPandaz <jasonjean1993@gmail.com>
This commit is contained in:
Colum Ferry 2023-08-09 15:27:03 -07:00 committed by GitHub
parent 118faf4e43
commit 0527925302
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 109 additions and 70 deletions

View File

@ -155,7 +155,7 @@
"cz-git": "^1.4.0", "cz-git": "^1.4.0",
"czg": "^1.4.0", "czg": "^1.4.0",
"detect-port": "^1.5.1", "detect-port": "^1.5.1",
"dotenv": "~10.0.0", "dotenv": "~16.3.1",
"ejs": "^3.1.7", "ejs": "^3.1.7",
"enhanced-resolve": "^5.8.3", "enhanced-resolve": "^5.8.3",
"esbuild": "^0.17.5", "esbuild": "^0.17.5",

View File

@ -19,8 +19,6 @@ export function initLocal(workspace: WorkspaceTypeAndRoot) {
try { try {
performance.mark('init-local'); performance.mark('init-local');
require('nx/src/utils/perf-logging');
monkeyPatchRequire(); monkeyPatchRequire();
if (workspace.type !== 'nx' && shouldDelegateToAngularCLI()) { if (workspace.type !== 'nx' && shouldDelegateToAngularCLI()) {

View File

@ -4,6 +4,7 @@ import {
WorkspaceTypeAndRoot, WorkspaceTypeAndRoot,
} from '../src/utils/find-workspace-root'; } from '../src/utils/find-workspace-root';
import * as chalk from 'chalk'; import * as chalk from 'chalk';
import { config as loadDotEnvFile } from 'dotenv';
import { initLocal } from './init-local'; import { initLocal } from './init-local';
import { output } from '../src/utils/output'; import { output } from '../src/utils/output';
import { import {
@ -16,6 +17,7 @@ import { readModulePackageJson } from '../src/utils/package-json';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import { join } from 'path'; import { join } from 'path';
import { assertSupportedPlatform } from '../src/native/assert-supported-platform'; import { assertSupportedPlatform } from '../src/native/assert-supported-platform';
import { performance } from 'perf_hooks';
function main() { function main() {
if ( if (
@ -26,6 +28,17 @@ function main() {
assertSupportedPlatform(); assertSupportedPlatform();
} }
require('nx/src/utils/perf-logging');
performance.mark('loading dotenv files:start');
loadDotEnvFiles();
performance.mark('loading dotenv files:end');
performance.measure(
'loading dotenv files',
'loading dotenv files:start',
'loading dotenv files:end'
);
const workspace = findWorkspaceRoot(process.cwd()); const workspace = findWorkspaceRoot(process.cwd());
// new is a special case because there is no local workspace to load // new is a special case because there is no local workspace to load
if ( if (
@ -88,6 +101,20 @@ function main() {
} }
} }
/**
* This loads dotenv files from:
* - .env
* - .local.env
* - .env.local
*/
function loadDotEnvFiles() {
for (const file of ['.env', '.local.env', '.env.local']) {
loadDotEnvFile({
path: file,
});
}
}
function handleNoWorkspace(globalNxVersion?: string) { function handleNoWorkspace(globalNxVersion?: string) {
output.log({ output.log({
title: `The current directory isn't part of an Nx workspace.`, title: `The current directory isn't part of an Nx workspace.`,

View File

@ -41,7 +41,7 @@
"cli-cursor": "3.1.0", "cli-cursor": "3.1.0",
"cli-spinners": "2.6.1", "cli-spinners": "2.6.1",
"cliui": "^7.0.2", "cliui": "^7.0.2",
"dotenv": "~10.0.0", "dotenv": "~16.3.1",
"enquirer": "~2.3.6", "enquirer": "~2.3.6",
"fast-glob": "3.2.7", "fast-glob": "3.2.7",
"figures": "3.2.0", "figures": "3.2.0",

View File

@ -1,5 +1,5 @@
import { readFileSync, writeFileSync } from 'fs'; import { readFileSync, writeFileSync } from 'fs';
import * as dotenv from 'dotenv'; import { config as loadDotEnvFile } from 'dotenv';
import { ChildProcess, fork, Serializable } from 'child_process'; import { ChildProcess, fork, Serializable } from 'child_process';
import * as chalk from 'chalk'; import * as chalk from 'chalk';
import * as logTransformer from 'strong-log-transformer'; import * as logTransformer from 'strong-log-transformer';
@ -301,8 +301,6 @@ export class ForkedProcessTaskRunner {
// region Environment Variables // region Environment Variables
private getEnvVariablesForProcess() { private getEnvVariablesForProcess() {
return { return {
// Start With Dotenv Variables
...this.getDotenvVariablesForForkedProcess(),
// User Process Env Variables override Dotenv Variables // User Process Env Variables override Dotenv Variables
...process.env, ...process.env,
// Nx Env Variables overrides everything // Nx Env Variables overrides everything
@ -318,11 +316,15 @@ export class ForkedProcessTaskRunner {
outputPath: string, outputPath: string,
streamOutput: boolean streamOutput: boolean
) { ) {
// Unload any dot env files at the root of the workspace that were loaded on init of Nx.
const taskEnv = this.unloadDotEnvFiles({ ...process.env });
const res = { const res = {
// Start With Dotenv Variables // Start With Dotenv Variables
...this.getDotenvVariablesForTask(task), ...(process.env.NX_LOAD_DOT_ENV_FILES === 'true'
// User Process Env Variables override Dotenv Variables ? this.loadDotEnvFilesForTask(task, taskEnv)
...process.env, : // If not loading dot env files, ensure env vars created by system are still loaded
taskEnv),
// Nx Env Variables overrides everything // Nx Env Variables overrides everything
...this.getNxEnvVariablesForTask( ...this.getNxEnvVariablesForTask(
task, task,
@ -397,57 +399,76 @@ export class ForkedProcessTaskRunner {
}; };
} }
private getDotenvVariablesForForkedProcess() { private loadDotEnvFilesForTask(
return { task: Task,
...parseEnv('.env'), environmentVariables: NodeJS.ProcessEnv
...parseEnv('.local.env'), ) {
...parseEnv('.env.local'), // Collect dot env files that may pertain to a task
}; const dotEnvFiles = [
// Load DotEnv Files for a configuration in the project root
...(task.target.configuration
? [
`${task.projectRoot}/.env.${task.target.target}.${task.target.configuration}`,
`${task.projectRoot}/.env.${task.target.configuration}`,
`${task.projectRoot}/.${task.target.target}.${task.target.configuration}.env`,
`${task.projectRoot}/.${task.target.configuration}.env`,
]
: []),
// Load DotEnv Files for a target in the project root
`${task.projectRoot}/.env.${task.target.target}`,
`${task.projectRoot}/.${task.target.target}.env`,
`${task.projectRoot}/.env.local`,
`${task.projectRoot}/.local.env`,
`${task.projectRoot}/.env`,
// Load DotEnv Files for a configuration in the workspace root
...(task.target.configuration
? [
`.env.${task.target.target}.${task.target.configuration}`,
`.env.${task.target.configuration}`,
`.${task.target.target}.${task.target.configuration}.env`,
`.${task.target.configuration}.env`,
]
: []),
// Load DotEnv Files for a target in the workspace root
`.env.${task.target.target}`,
`.${task.target.target}.env`,
// Load base DotEnv Files at workspace root
`.env`,
`.local.env`,
`.env.local`,
];
for (const file of dotEnvFiles) {
loadDotEnvFile({
path: file,
processEnv: environmentVariables,
// Do not override existing env variables as we load
override: false,
});
}
return environmentVariables;
} }
private getDotenvVariablesForTask(task: Task) { private unloadDotEnvFiles(environmentVariables: NodeJS.ProcessEnv) {
if (process.env.NX_LOAD_DOT_ENV_FILES == 'true') { const unloadDotEnvFile = (filename: string) => {
return { let parsedDotEnvFile: NodeJS.ProcessEnv = {};
...this.getDotenvVariablesForForkedProcess(), loadDotEnvFile({ path: filename, processEnv: parsedDotEnvFile });
...parseEnv(`.${task.target.target}.env`), Object.keys(parsedDotEnvFile).forEach((envVarKey) => {
...parseEnv(`.env.${task.target.target}`), if (environmentVariables[envVarKey] === parsedDotEnvFile[envVarKey]) {
...(task.target.configuration delete environmentVariables[envVarKey];
? { }
...parseEnv(`.${task.target.configuration}.env`), });
...parseEnv( };
`.${task.target.target}.${task.target.configuration}.env`
), for (const file of ['.env', '.local.env', '.env.local']) {
...parseEnv(`.env.${task.target.configuration}`), unloadDotEnvFile(file);
...parseEnv(
`.env.${task.target.target}.${task.target.configuration}`
),
}
: {}),
...parseEnv(`${task.projectRoot}/.env`),
...parseEnv(`${task.projectRoot}/.local.env`),
...parseEnv(`${task.projectRoot}/.env.local`),
...parseEnv(`${task.projectRoot}/.${task.target.target}.env`),
...parseEnv(`${task.projectRoot}/.env.${task.target.target}`),
...(task.target.configuration
? {
...parseEnv(
`${task.projectRoot}/.${task.target.configuration}.env`
),
...parseEnv(
`${task.projectRoot}/.${task.target.target}.${task.target.configuration}.env`
),
...parseEnv(
`${task.projectRoot}/.env.${task.target.configuration}`
),
...parseEnv(
`${task.projectRoot}/.env.${task.target.target}.${task.target.configuration}`
),
}
: {}),
};
} else {
return {};
} }
return environmentVariables;
} }
// endregion Environment Variables // endregion Environment Variables
@ -507,13 +528,6 @@ export class ForkedProcessTaskRunner {
} }
} }
function parseEnv(path: string) {
try {
const envContents = readFileSync(path);
return dotenv.parse(envContents);
} catch (e) {}
}
const colors = [ const colors = [
chalk.green, chalk.green,
chalk.greenBright, chalk.greenBright,

10
pnpm-lock.yaml generated
View File

@ -541,8 +541,8 @@ devDependencies:
specifier: ^1.5.1 specifier: ^1.5.1
version: 1.5.1 version: 1.5.1
dotenv: dotenv:
specifier: ~10.0.0 specifier: ~16.3.1
version: 10.0.0 version: 16.3.1
ejs: ejs:
specifier: ^3.1.7 specifier: ^3.1.7
version: 3.1.8 version: 3.1.8
@ -13732,8 +13732,8 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/dotenv@16.0.3: /dotenv@16.3.1:
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true dev: true
@ -17989,7 +17989,7 @@ packages:
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
dependencies: dependencies:
app-root-dir: 1.0.2 app-root-dir: 1.0.2
dotenv: 16.0.3 dotenv: 16.3.1
dotenv-expand: 10.0.0 dotenv-expand: 10.0.0
dev: true dev: true