Skip to content

Commit b266c0f

Browse files
committed
Add support for .env files in rushx and rush-pnpm.
1 parent 119c386 commit b266c0f

5 files changed

Lines changed: 65 additions & 32 deletions

File tree

libraries/rush-lib/src/cli/RushCommandLineParser.ts

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
Colorize,
1717
type ITerminal
1818
} from '@rushstack/terminal';
19-
import dotenv from 'dotenv';
2019

2120
import { RushConfiguration } from '../api/RushConfiguration';
2221
import { RushConstants } from '../logic/RushConstants';
@@ -65,8 +64,7 @@ import { RushSession } from '../pluginFramework/RushSession';
6564
import type { IBuiltInPluginConfiguration } from '../pluginFramework/PluginLoader/BuiltInPluginLoader';
6665
import { InitSubspaceAction } from './actions/InitSubspaceAction';
6766
import { RushAlerts } from '../utilities/RushAlerts';
68-
import { RushUserConfiguration } from '../api/RushUserConfiguration';
69-
import { EnvironmentConfiguration } from '../api/EnvironmentConfiguration';
67+
import { initializeDotEnv } from '../logic/dotenv';
7068

7169
import { measureAsyncFn } from '../utilities/performance';
7270

@@ -135,22 +133,7 @@ export class RushCommandLineParser extends CommandLineParser {
135133
showVerbose: !this._restrictConsoleOutput
136134
});
137135

138-
if (EnvironmentConfiguration.hasBeenValidated) {
139-
throw new Error(
140-
`The ${EnvironmentConfiguration.name} was initialized before .env files were loaded. Rush environment ` +
141-
'variables may have unexpected values.'
142-
);
143-
}
144-
145-
if (rushJsonFilePath) {
146-
const rushJsonFolder: string = path.dirname(rushJsonFilePath);
147-
dotenv.config({ path: `${rushJsonFolder}/.env` });
148-
}
149-
150-
const rushUserFolder: string = RushUserConfiguration.getRushUserFolderPath();
151-
dotenv.config({ path: `${rushUserFolder}/.env` });
152-
153-
// TODO: Consider adding support for repo-specific `.rush-user` `.env` files.
136+
initializeDotEnv(terminal, rushJsonFilePath);
154137

155138
if (rushJsonFilePath) {
156139
this.rushConfiguration = RushConfiguration.loadFromConfigurationFile(rushJsonFilePath);

libraries/rush-lib/src/cli/RushPnpmCommandLineParser.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { Utilities } from '../utilities/Utilities';
3333
import type { Subspace } from '../api/Subspace';
3434
import type { PnpmOptionsConfiguration } from '../logic/pnpm/PnpmOptionsConfiguration';
3535
import { EnvironmentVariableNames } from '../api/EnvironmentConfiguration';
36+
import { initializeDotEnv } from '../logic/dotenv';
3637

3738
const RUSH_SKIP_CHECKS_PARAMETER: string = '--rush-skip-checks';
3839

@@ -78,10 +79,17 @@ export class RushPnpmCommandLineParser {
7879
this._terminal = terminal;
7980

8081
// Are we in a Rush repo?
81-
const rushConfiguration: RushConfiguration | undefined = RushConfiguration.tryLoadFromDefaultLocation({
82+
const rushJsonFilePath: string | undefined = RushConfiguration.tryFindRushJsonLocation({
8283
// showVerbose is false because the logging message may break JSON output
8384
showVerbose: false
8485
});
86+
87+
initializeDotEnv(terminal, rushJsonFilePath);
88+
89+
const rushConfiguration: RushConfiguration | undefined = rushJsonFilePath
90+
? RushConfiguration.loadFromConfigurationFile(rushJsonFilePath)
91+
: undefined;
92+
8593
NodeJsCompatibility.warnAboutCompatibilityIssues({
8694
isRushLib: true,
8795
alreadyReportedNodeTooNewError: !!options.alreadyReportedNodeTooNewError,

libraries/rush-lib/src/cli/RushXCommandLine.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { Event } from '../api/EventHooks';
2525
import { EnvironmentVariableNames } from '../api/EnvironmentConfiguration';
2626
import { RushConstants } from '../logic/RushConstants';
2727
import { PnpmSyncUtilities } from '../utilities/PnpmSyncUtilities';
28+
import { initializeDotEnv } from '../logic/dotenv';
2829

2930
interface IRushXCommandLineArguments {
3031
/**
@@ -77,18 +78,31 @@ export class RushXCommandLine {
7778
public static async launchRushXAsync(launcherVersion: string, options: ILaunchOptions): Promise<void> {
7879
try {
7980
const rushxArguments: IRushXCommandLineArguments = RushXCommandLine._parseCommandLineArguments();
80-
const rushConfiguration: RushConfiguration | undefined = RushConfiguration.tryLoadFromDefaultLocation({
81+
const rushJsonFilePath: string | undefined = RushConfiguration.tryFindRushJsonLocation({
8182
showVerbose: false
8283
});
84+
const { isDebug, help, ignoreHooks } = rushxArguments;
85+
86+
const terminalProvider: ITerminalProvider = new ConsoleTerminalProvider({
87+
debugEnabled: isDebug,
88+
verboseEnabled: isDebug
89+
});
90+
const terminal: ITerminal = new Terminal(terminalProvider);
91+
92+
initializeDotEnv(terminal, rushJsonFilePath);
93+
94+
const rushConfiguration: RushConfiguration | undefined = rushJsonFilePath
95+
? RushConfiguration.loadFromConfigurationFile(rushJsonFilePath)
96+
: undefined;
8397
const eventHooksManager: EventHooksManager | undefined = rushConfiguration
8498
? new EventHooksManager(rushConfiguration)
8599
: undefined;
86100

87101
const suppressHooks: boolean = process.env[EnvironmentVariableNames._RUSH_RECURSIVE_RUSHX_CALL] === '1';
88-
const attemptHooks: boolean = !suppressHooks && !rushxArguments.help;
102+
const attemptHooks: boolean = !suppressHooks && !help;
89103
if (attemptHooks) {
90104
try {
91-
eventHooksManager?.handle(Event.preRushx, rushxArguments.isDebug, rushxArguments.ignoreHooks);
105+
eventHooksManager?.handle(Event.preRushx, isDebug, ignoreHooks);
92106
} catch (error) {
93107
// eslint-disable-next-line no-console
94108
console.error(Colorize.red('PreRushx hook error: ' + (error as Error).message));
@@ -98,10 +112,10 @@ export class RushXCommandLine {
98112
// promise exception), so we start with the assumption that the exit code is 1
99113
// and set it to 0 only on success.
100114
process.exitCode = 1;
101-
await RushXCommandLine._launchRushXInternalAsync(rushxArguments, rushConfiguration, options);
115+
await RushXCommandLine._launchRushXInternalAsync(terminal, rushxArguments, rushConfiguration, options);
102116
if (attemptHooks) {
103117
try {
104-
eventHooksManager?.handle(Event.postRushx, rushxArguments.isDebug, rushxArguments.ignoreHooks);
118+
eventHooksManager?.handle(Event.postRushx, isDebug, ignoreHooks);
105119
} catch (error) {
106120
// eslint-disable-next-line no-console
107121
console.error(Colorize.red('PostRushx hook error: ' + (error as Error).message));
@@ -122,6 +136,7 @@ export class RushXCommandLine {
122136
}
123137

124138
private static async _launchRushXInternalAsync(
139+
terminal: ITerminal,
125140
rushxArguments: IRushXCommandLineArguments,
126141
rushConfiguration: RushConfiguration | undefined,
127142
options: ILaunchOptions
@@ -218,12 +233,6 @@ export class RushXCommandLine {
218233
}
219234
});
220235

221-
const terminalProvider: ITerminalProvider = new ConsoleTerminalProvider({
222-
debugEnabled: rushxArguments.isDebug,
223-
verboseEnabled: rushxArguments.isDebug
224-
});
225-
const terminal: ITerminal = new Terminal(terminalProvider);
226-
227236
if (rushConfiguration?.isPnpm && rushConfiguration?.experimentsConfiguration) {
228237
const { configuration: experiments } = rushConfiguration?.experimentsConfiguration;
229238

libraries/rush-lib/src/cli/test/RushXCommandLine.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4+
jest.mock('../../logic/dotenv', () => ({
5+
initializeDotEnv: () => {}
6+
}));
7+
48
import { PackageJsonLookup } from '@rushstack/node-core-library';
59

610
import { Utilities } from '../../utilities/Utilities';
@@ -61,7 +65,8 @@ describe(RushXCommandLine.name, () => {
6165
return projects.find((project) => project.projectFolder === path);
6266
}
6367
} as RushConfiguration;
64-
jest.spyOn(RushConfiguration, 'tryLoadFromDefaultLocation').mockReturnValue(rushConfiguration);
68+
jest.spyOn(RushConfiguration, 'tryFindRushJsonLocation').mockReturnValue('/Users/jdoe/bigrepo');
69+
jest.spyOn(RushConfiguration, 'loadFromConfigurationFile').mockReturnValue(rushConfiguration);
6570

6671
// Mock command execution
6772
executeLifecycleCommandMock = jest.spyOn(Utilities, 'executeLifecycleCommand');
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import path from 'node:path';
5+
import dotenv from 'dotenv';
6+
import type { ITerminal } from '@rushstack/terminal';
7+
8+
import { RushUserConfiguration } from '../api/RushUserConfiguration';
9+
import { EnvironmentConfiguration } from '../api/EnvironmentConfiguration';
10+
11+
export function initializeDotEnv(terminal: ITerminal, rushJsonFilePath: string | undefined): void {
12+
if (EnvironmentConfiguration.hasBeenValidated) {
13+
throw terminal.writeWarningLine(
14+
`The ${EnvironmentConfiguration.name} was initialized before .env files were loaded. Rush environment ` +
15+
'variables may have unexpected values.'
16+
);
17+
}
18+
19+
if (rushJsonFilePath) {
20+
const rushJsonFolder: string = path.dirname(rushJsonFilePath);
21+
dotenv.config({ path: `${rushJsonFolder}/.env` });
22+
}
23+
24+
const rushUserFolder: string = RushUserConfiguration.getRushUserFolderPath();
25+
dotenv.config({ path: `${rushUserFolder}/.env` });
26+
27+
// TODO: Consider adding support for repo-specific `.rush-user` `.env` files.
28+
}

0 commit comments

Comments
 (0)