feat(core): load root .env in all nx processes (#3081)
Co-authored-by: Isaac Mann <isaacplmann@users.noreply.github.com> Co-authored-by: Isaac Mann <isaacplmann@users.noreply.github.com> Co-authored-by: Isaac Mann <isaacplmann@users.noreply.github.com>
This commit is contained in:
parent
3a4a3bea13
commit
6384bde734
@ -243,6 +243,35 @@ Any flags you pass to `run-many` that aren't Nx specific will be passed down to
|
||||
nx affected --target=build --prod
|
||||
```
|
||||
|
||||
## Loading Environment Variables
|
||||
|
||||
By default, Nx will load any environment variables you place in the following files:
|
||||
|
||||
1. `workspaceRoot/apps/my-app/.local.env`
|
||||
2. `workspaceRoot/apps/my-app/.env`
|
||||
3. `workspaceRoot/.local.env`
|
||||
4. `workspaceRoot/.env`
|
||||
|
||||
Order is important. Nx will move through the above list, ignoring files it can't find, and loading environment variables into the current process for the ones it can find. If it finds a variable that has already been loaded into the process, it will ignore it. It does this for two reasons:
|
||||
|
||||
1. Developers can't accidentally overwrite important system level variables (like `NODE_ENV`)
|
||||
2. Allows developers to create `.local.env` files for their local environment and override any project defaults set in `.env`
|
||||
|
||||
For example:
|
||||
|
||||
1. `workspaceRoot/apps/my-app/.local.env` contains `AUTH_URL=http://localhost/auth`
|
||||
2. `workspaceRoot/apps/my-app/.env` contains `AUTH_URL=https://prod-url.com/auth`
|
||||
3. Nx will first load the variables from `apps/my-app/.local.env` into the process. When it tries to load the variables from `apps/my-app/.env`, it will notice that `AUTH_URL` already exists, so it will ignore it.
|
||||
|
||||
We recommend nesting your **app** specific `env` files in `apps/your-app`, and creating workspace/root level `env` files for workspace-specific settings (like the [Nx Cloud token](https://nx.dev/angular/workspace/computation-caching#nx-cloud-and-distributed-computation-memoization)).
|
||||
|
||||
### Pointing to custom env files
|
||||
|
||||
If you want to load variables from `env` files other than the ones listed above:
|
||||
|
||||
1. Use the [env-cmd](https://www.npmjs.com/package/env-cmd) package: `env-cmd -f .qa.env nx serve`
|
||||
2. Use the `envFile` option of the [run-commands](https://nx.dev/angular/plugins/workspace/builders/run-commands#envfile) builder and execute your command inside of the builder
|
||||
|
||||
## Other Commands
|
||||
|
||||
`nx print-affected` prints information about affected projects in the workspace.
|
||||
|
||||
@ -35,3 +35,32 @@ set "NODE_ENV=development" && nx build myapp
|
||||
```bash
|
||||
($env:NODE_ENV = "development") -and (nx build myapp)
|
||||
```
|
||||
|
||||
## Loading Environment Variables
|
||||
|
||||
By default, Nx will load any environment variables you place in the following files:
|
||||
|
||||
1. `workspaceRoot/apps/my-app/.local.env`
|
||||
2. `workspaceRoot/apps/my-app/.env`
|
||||
3. `workspaceRoot/.local.env`
|
||||
4. `workspaceRoot/.env`
|
||||
|
||||
Order is important. Nx will move through the above list, ignoring files it can't find, and loading environment variables into the current process for the ones it can find. If it finds a variable that has already been loaded into the process, it will ignore it. It does this for two reasons:
|
||||
|
||||
1. Developers can't accidentally overwrite important system level variables (like `NODE_ENV`)
|
||||
2. Allows developers to create `.local.env` files for their local environment and override any project defaults set in `.env`
|
||||
|
||||
For example:
|
||||
|
||||
1. `workspaceRoot/apps/my-app/.local.env` contains `AUTH_URL=http://localhost/auth`
|
||||
2. `workspaceRoot/apps/my-app/.env` contains `AUTH_URL=https://prod-url.com/auth`
|
||||
3. Nx will first load the variables from `apps/my-app/.local.env` into the process. When it tries to load the variables from `apps/my-app/.env`, it will notice that `AUTH_URL` already exists, so it will ignore it.
|
||||
|
||||
We recommend nesting your **app** specific `env` files in `apps/your-app`, and creating workspace/root level `env` files for workspace-specific settings (like the [Nx Cloud token](https://nx.dev/react/workspace/computation-caching#nx-cloud-and-distributed-computation-memoization)).
|
||||
|
||||
### Pointing to custom env files
|
||||
|
||||
If you want to load variables from `env` files other than the ones listed above:
|
||||
|
||||
1. Use the [env-cmd](https://www.npmjs.com/package/env-cmd) package: `env-cmd -f .qa.env nx serve`
|
||||
2. Use the `envFile` option of the [run-commands](https://nx.dev/react/plugins/workspace/builders/run-commands#envfile) builder and execute your command inside of the builder
|
||||
|
||||
46
e2e/run-commands.test.ts
Normal file
46
e2e/run-commands.test.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import {
|
||||
ensureProject,
|
||||
forEachCli,
|
||||
readJson,
|
||||
runCLI,
|
||||
uniq,
|
||||
updateFile,
|
||||
workspaceConfigName,
|
||||
} from './utils';
|
||||
|
||||
forEachCli('nx', () => {
|
||||
describe('Run Commands', () => {
|
||||
it('should not override environment variables already set when setting a custom env file path', async (done) => {
|
||||
ensureProject();
|
||||
const nodeapp = uniq('nodeapp');
|
||||
updateFile(
|
||||
`.env`,
|
||||
'SHARED_VAR=shared-root-value\nROOT_ONLY=root-only-value'
|
||||
);
|
||||
runCLI(`generate @nrwl/express:app ${nodeapp}`);
|
||||
updateFile(
|
||||
`apps/${nodeapp}/.custom.env`,
|
||||
'SHARED_VAR=shared-nested-value\nNESTED_ONLY=nested-only-value'
|
||||
);
|
||||
const config = readJson(workspaceConfigName());
|
||||
config.projects[nodeapp].architect.echoEnvVariables = {
|
||||
builder: '@nrwl/workspace:run-commands',
|
||||
options: {
|
||||
commands: [
|
||||
{
|
||||
command: `echo "$SHARED_VAR $ROOT_ONLY $NESTED_ONLY"`,
|
||||
},
|
||||
],
|
||||
envFile: `apps/${nodeapp}/.custom.env`,
|
||||
},
|
||||
};
|
||||
updateFile(workspaceConfigName(), JSON.stringify(config));
|
||||
const result = runCLI('echoEnvVariables');
|
||||
expect(result).toContain('shared-root-value');
|
||||
expect(result).not.toContain('shared-nested-value');
|
||||
expect(result).toContain('root-only-value');
|
||||
expect(result).toContain('nested-only-value');
|
||||
done();
|
||||
}, 120000);
|
||||
});
|
||||
});
|
||||
@ -7,7 +7,9 @@ import {
|
||||
runCLIAsync,
|
||||
uniq,
|
||||
updateFile,
|
||||
tmpProjPath,
|
||||
} from './utils';
|
||||
import { writeFileSync } from 'fs';
|
||||
|
||||
forEachCli((currentCLIName) => {
|
||||
describe('Web Components Applications', () => {
|
||||
@ -58,12 +60,29 @@ forEachCli((currentCLIName) => {
|
||||
});
|
||||
|
||||
describe('CLI - Environment Variables', () => {
|
||||
it('should support NX environment variables', () => {
|
||||
it('should automatically load workspace and per-project environment variables', () => {
|
||||
ensureProject();
|
||||
|
||||
const appName = uniq('app');
|
||||
//test if the Nx CLI loads root .env vars
|
||||
updateFile(
|
||||
`.env`,
|
||||
'NX_WS_BASE=ws-base\nNX_SHARED_ENV=shared-in-workspace-base'
|
||||
);
|
||||
updateFile(
|
||||
`.local.env`,
|
||||
'NX_WS_LOCAL=ws-local\nNX_SHARED_ENV=shared-in-workspace-local'
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appName}/.env`,
|
||||
'NX_APP_BASE=app-base\nNX_SHARED_ENV=shared-in-app-base'
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appName}/.local.env`,
|
||||
'NX_APP_LOCAL=app-local\nNX_SHARED_ENV=shared-in-app-local'
|
||||
);
|
||||
const main = `apps/${appName}/src/main.ts`;
|
||||
const newCode = `const envVars = [process.env.NODE_ENV, process.env.NX_BUILD, process.env.NX_API];`;
|
||||
const newCode = `const envVars = [process.env.NODE_ENV, process.env.NX_BUILD, process.env.NX_API, process.env.NX_WS_BASE, process.env.NX_WS_LOCAL, process.env.NX_APP_BASE, process.env.NX_APP_LOCAL, process.env.NX_SHARED_ENV];`;
|
||||
|
||||
runCLI(`generate @nrwl/web:app ${appName} --no-interactive`);
|
||||
|
||||
@ -71,11 +90,38 @@ forEachCli((currentCLIName) => {
|
||||
|
||||
updateFile(main, `${newCode}\n${content}`);
|
||||
|
||||
runCLI(`build ${appName}`, {
|
||||
env: { ...process.env, NODE_ENV: 'test', NX_BUILD: '52', NX_API: 'QA' },
|
||||
const appName2 = uniq('app');
|
||||
|
||||
updateFile(
|
||||
`apps/${appName2}/.env`,
|
||||
'NX_APP_BASE=app2-base\nNX_SHARED_ENV=shared2-in-app-base'
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appName2}/.local.env`,
|
||||
'NX_APP_LOCAL=app2-local\nNX_SHARED_ENV=shared2-in-app-local'
|
||||
);
|
||||
const main2 = `apps/${appName2}/src/main.ts`;
|
||||
const newCode2 = `const envVars = [process.env.NODE_ENV, process.env.NX_BUILD, process.env.NX_API, process.env.NX_WS_BASE, process.env.NX_WS_LOCAL, process.env.NX_APP_BASE, process.env.NX_APP_LOCAL, process.env.NX_SHARED_ENV];`;
|
||||
|
||||
runCLI(`generate @nrwl/web:app ${appName2} --no-interactive`);
|
||||
|
||||
const content2 = readFile(main2);
|
||||
|
||||
updateFile(main2, `${newCode2}\n${content2}`);
|
||||
|
||||
runCLI(`run-many --target=build --all`, {
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: 'test',
|
||||
NX_BUILD: '52',
|
||||
NX_API: 'QA',
|
||||
},
|
||||
});
|
||||
expect(readFile(`dist/apps/${appName}/main.js`)).toContain(
|
||||
'const envVars = ["test", "52", "QA"];'
|
||||
'const envVars = ["test", "52", "QA", "ws-base", "ws-local", "app-base", "app-local", "shared-in-app-local"];'
|
||||
);
|
||||
expect(readFile(`dist/apps/${appName2}/main.js`)).toContain(
|
||||
'const envVars = ["test", "52", "QA", "ws-base", "ws-local", "app2-base", "app2-local", "shared2-in-app-local"];'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -54,6 +54,7 @@
|
||||
"@angular-devkit/schematics": "~9.1.0",
|
||||
"cosmiconfig": "4.0.0",
|
||||
"fs-extra": "6.0.0",
|
||||
"dotenv": "8.2.0",
|
||||
"ignore": "5.0.4",
|
||||
"npm-run-all": "4.1.5",
|
||||
"hasha": "5.1.0",
|
||||
|
||||
@ -118,6 +118,7 @@ export function createTask({
|
||||
return {
|
||||
id: getId(qualifiedTarget),
|
||||
target: qualifiedTarget,
|
||||
projectRoot: project.data.root,
|
||||
overrides: interpolateOverrides(overrides, project.name, project.data),
|
||||
};
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import { output } from '../utils/output';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { appRootPath } from '../utils/app-root';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
export class TaskOrchestrator {
|
||||
workspaceRoot = appRootPath;
|
||||
@ -175,7 +176,14 @@ export class TaskOrchestrator {
|
||||
outputPath: string,
|
||||
forwardOutput: boolean
|
||||
) {
|
||||
const env = { ...process.env };
|
||||
const envsFromFiles = {
|
||||
...parseEnv('.env'),
|
||||
...parseEnv('.local.env'),
|
||||
...parseEnv(`${task.projectRoot}/.env`),
|
||||
...parseEnv(`${task.projectRoot}/.local.env`),
|
||||
};
|
||||
|
||||
const env = { ...envsFromFiles, ...process.env };
|
||||
if (outputPath) {
|
||||
env.NX_TERMINAL_OUTPUT_PATH = outputPath;
|
||||
if (this.options.captureStderr) {
|
||||
@ -226,3 +234,10 @@ export class TaskOrchestrator {
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function parseEnv(path: string) {
|
||||
try {
|
||||
const envContents = fs.readFileSync(path);
|
||||
return dotenv.parse(envContents);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ export interface Task {
|
||||
target: Target;
|
||||
overrides: Object;
|
||||
hash?: string;
|
||||
projectRoot?: string;
|
||||
hashDetails?: {
|
||||
command: string;
|
||||
sources: { [projectName: string]: string };
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user