Skip to content

Commit

Permalink
Merge pull request #1883 from nestjs/feat/skip-predefined-change-order
Browse files Browse the repository at this point in the history
feat: order of reading variables, add skip predefined
  • Loading branch information
kamilmysliwiec authored Jan 17, 2025
2 parents 28c4d6b + c53c63c commit e9c8727
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 16 deletions.
14 changes: 10 additions & 4 deletions lib/config.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ export class ConfigModule {
* Also, registers custom configurations globally.
* @param options
*/
static async forRoot(
options: ConfigModuleOptions = {},
static async forRoot<ValidationOptions extends Record<string, any>>(
options: ConfigModuleOptions<ValidationOptions> = {},
): Promise<DynamicModule> {
const envFilePaths = Array.isArray(options.envFilePath)
? options.envFilePath
Expand All @@ -65,12 +65,13 @@ export class ConfigModule {
? {}
: this.loadEnvFile(envFilePaths, options);

if (!options.ignoreEnvVars) {
if (!options.ignoreEnvVars || options.validatePredefined) {
config = {
...config,
...process.env,
};
}

if (options.validate) {
const validatedConfig = options.validate(config);
validatedEnvConfig = validatedConfig;
Expand Down Expand Up @@ -101,9 +102,14 @@ export class ConfigModule {
const configServiceProvider = {
provide: ConfigService,
useFactory: (configService: ConfigService) => {
const untypedConfigService = configService as any;
if (options.cache) {
(configService as any).isCacheEnabled = true;
untypedConfigService.isCacheEnabled = true;
}
if (options.skipPredefined) {
untypedConfigService.skipPredefined = true;
}

configService.setEnvFilePaths(envFilePaths);
return configService;
},
Expand Down
30 changes: 22 additions & 8 deletions lib/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,17 @@ export class ConfigService<
return this._isCacheEnabled;
}

private set skipPredefined(value: boolean) {
this._skipPredefined = value;
}

private get skipPredefined(): boolean {
return this._skipPredefined;
}

private readonly cache: Partial<K> = {} as any;
private readonly _changes$ = new Subject<ConfigChangeEvent>();
private _skipPredefined = false;
private _isCacheEnabled = false;
private envFilePaths: string[] = [];

Expand Down Expand Up @@ -122,6 +131,11 @@ export class ConfigService<
defaultValueOrOptions?: T | ConfigGetOptions,
options?: ConfigGetOptions,
): T | undefined {
const internalValue = this.getFromInternalConfig(propertyPath);
if (!isUndefined(internalValue)) {
return internalValue;
}

const validatedEnvValue = this.getFromValidatedEnv(propertyPath);
if (!isUndefined(validatedEnvValue)) {
return validatedEnvValue;
Expand All @@ -132,14 +146,14 @@ export class ConfigService<
? undefined
: defaultValueOrOptions;

const processEnvValue = this.getFromProcessEnv(propertyPath, defaultValue);
if (!isUndefined(processEnvValue)) {
return processEnvValue;
}

const internalValue = this.getFromInternalConfig(propertyPath);
if (!isUndefined(internalValue)) {
return internalValue;
if (!this.skipPredefined) {
const processEnvValue = this.getFromProcessEnv(
propertyPath,
defaultValue,
);
if (!isUndefined(processEnvValue)) {
return processEnvValue;
}
}

return defaultValue as T;
Expand Down
20 changes: 18 additions & 2 deletions lib/interfaces/config-module-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { ConfigFactory } from './config-factory.interface';
/**
* @publicApi
*/
export interface ConfigModuleOptions {
export interface ConfigModuleOptions<
ValidationOptions extends Record<string, any> = Record<string, any>,
> {
/**
* If "true", values from the process.env object will be cached in the memory.
* This improves the overall application performance.
Expand All @@ -25,6 +27,7 @@ export interface ConfigModuleOptions {

/**
* If "true", predefined environment variables will not be validated.
* @deprecated Use `validatePredefined` instead.
*/
ignoreEnvVars?: boolean;

Expand All @@ -42,6 +45,19 @@ export interface ConfigModuleOptions {
*/
validate?: (config: Record<string, any>) => Record<string, any>;

/**
* If "true", predefined environment variables will be validated.
* Predefined environment variables are process variables that were set before the module was imported.
* @default true
*/
validatePredefined?: boolean;

/**
* If "true", predefined environment variables will be ignored and not picked up by the `ConfigService#get` method.
* @default false
*/
skipPredefined?: boolean;

/**
* Environment variables validation schema (Joi).
*/
Expand All @@ -51,7 +67,7 @@ export interface ConfigModuleOptions {
* Schema validation options.
* See: https://joi.dev/api/?v=17.3.0#anyvalidatevalue-options
*/
validationOptions?: Record<string, any>;
validationOptions?: ValidationOptions;

/**
* Array of custom configuration files to be loaded.
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/load-priority.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ describe('Environment variables and .env files', () => {
await app.init();
});

it('should choose .env file vars over load configuration', () => {
it('should choose the load configuration over .env file vars', () => {
const configService = app.get(ConfigService);
expect(configService.get('PORT')).toEqual('4000');
expect(configService.get('PORT')).toEqual('8000');
});
});

Expand Down
29 changes: 29 additions & 0 deletions tests/e2e/skip-predefined.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { ConfigModule, ConfigService } from '../../lib';
import { AppModule } from '../src/app.module';

describe('Environment variables (skip predefined)', () => {
let app: INestApplication;

beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [AppModule.withSkipPredefined()],
}).compile();

app = module.createNestApplication();
await app.init();
});

it(`should ignore predefined environment variables`, async () => {
process.env.RANDOM_PREDEFINED = 'test';
await ConfigModule.envVariablesLoaded;

const configService = app.get(ConfigService);
expect(configService.get('RANDOM_PREDEFINED')).toBeUndefined();
});

afterEach(async () => {
await app.close();
});
});
13 changes: 13 additions & 0 deletions tests/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ export class AppModule {
};
}

static withSkipPredefined(): DynamicModule {
return {
module: AppModule,
imports: [
ConfigModule.forRoot({
envFilePath: join(__dirname, '.env'),
load: [() => ({ obj: { test: 'true', test2: undefined } })],
skipPredefined: true,
}),
],
};
}

static withEnvVars(): DynamicModule {
return {
module: AppModule,
Expand Down

0 comments on commit e9c8727

Please sign in to comment.