Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for loading env values #23

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.vscode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Including that file was intentional; revert this


# Logs
logs
*.log
Expand Down
21 changes: 0 additions & 21 deletions .vscode/launch.json

This file was deleted.

51 changes: 50 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ Autocomplete and Strongly Typed Keys are your new best friend! Using `simple-env
| Autocomplete | ✅ | ❌ | ❌ |
| Strongly Typed Keys | ✅ | ❌ | ❌ |
| Single Location Refactor | ✅ | ❌ | ❌ |
| Loads .env | ✅ | ✅ | ❌ |
| Return Type Helpers | 🔜 | ❌ | ✅ |
| Loads .env | 🔜 | ✅ | ❌ |

Let's see how some of the features above look in code:

Expand Down Expand Up @@ -161,6 +161,55 @@ export const env = setEnv({

> **NOTE**: if you choose to assign `optional` and `required` env vars individually, `setEnv` should only be done _once_ for each or you will overwrite your previously defined values.

### Loading DotEnv Files

You can give `simple-env` a path to a `.env` file, and it will parse the file and import the contents into the environment!

If you don't specify a path, `simple-env` WILL NOT import anything!

```typescript
// src/env.ts
import setEnv from '@americanairlines/simple-env';

export const env = setEnv({
required: {
nodeEnv: 'NODE_ENV',
someRequiredSecret: 'SOME_REQUIRED_SECRET',
},
options: {
envPath: "./.env"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should work as well. Do you think the original is better?

Suggested change
envPath: "./.env"
envPath: ".env"

}
});
```
Each variable needs to be declared on a separate line.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add all of this to a new sub-section called Correct .env Syntax (or something like it). To keep the README simple, can we add all of that to a <details> tag?


Comments must be on their own line, inline comments will be read in as part of the value to the variable!

If `simple-env` finds something that it doesn't know how to parse, it just skips it and moves on to the next thing!

Here's a sample `.env` file that will make us all happy :^)
```
# Comments like this will be ignored
// These will be ignored too :^)

# All of these vars are gonna work just fine!
NODE_ENV=development
SOME_REQUIRED_SECRET='Single quotes are fine!'
ANOTHER_SECRET="Double quotes are fine too, we don't discriminate :^)"
lowercase=no problem
SECRET_2_ELECTRIC_BOOGALOO = "We don't mind whitespace between the equal signs, or before the var definition"
```

Here's a sample `.env` file that will make us all sad :^(
```
# Uh-oh, these ones are invalid, so we'll have to skip them
1BAD_VAR="Variables can't begin with numbers"
ANOTHER BAD VAR="no whitespace allowed in var names"
KEBAB-CASE="can't do it"
VAR_WITHOUT_EQUAL_IS_SKIPPED
loose text like this will also get skipped
```

# Contributing

Interested in contributing to the project? Check out our [Contributing Guidelines](./.github/CONTRIBUTING.md).
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@americanairlines/simple-env",
"version": "1.0.4",
"version": "1.1.0",
"description": "An intuitive, strongly typed, and scalable way to retrieve environment variables",
"keywords": [
"env",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function setEnv<T extends UndefinedEnvVars, V extends UndefinedEn
...options,
};

if (_options.options.loadDotEnv) {
if (_options.options.envPath) {
parseEnvFile(_options.options);
}

Expand Down
2 changes: 1 addition & 1 deletion src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function parseLine(line: string): Record<string, string> {
}

export function parseEnvFile(options: ConfigOptions = {}): void {
const fullPath = options.envFile || join(process.cwd(), '.env');
const fullPath = options.envPath || join(process.cwd(), '.env');

if (!fs.existsSync(fullPath)) {
return;
Expand Down
78 changes: 2 additions & 76 deletions src/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'jest';
import fs from 'fs';
import setEnvDefault from '..';
import parseEnvFile from '../parser';

describe('simple-env', () => {
// Fresh setEnv for each test
Expand Down Expand Up @@ -105,11 +104,11 @@ describe('simple-env', () => {
expect(Object.getOwnPropertyDescriptors(env)).toHaveProperty('somethingElse');
});

it('will invoke the parser is loadDotEnv is true', () => {
it('will invoke the parser if envPath is set', () => {
existsSyncSpy.mockReturnValue(true);
readFileSpy.mockImplementation(() => Buffer.from('TEST=test'));

setEnv({ optional: { something: 'SOMETHING' }, options: { loadDotEnv: true } });
setEnv({ optional: { something: 'SOMETHING' }, options: { envPath: './a/.env' } });

expect(process.env).toHaveProperty('TEST');
});
Expand All @@ -123,77 +122,4 @@ describe('simple-env', () => {
expect(process.env).not.toHaveProperty('TEST');
});
});

describe('parser', () => {
beforeEach(() => {
process.env = {};
});

afterEach(() => {
jest.resetModules();
});

it('will not overwrite vars that already exist', () => {
const originalValue = 'I already exist';
const newValue = 'I should not';
process.env = { TEST: originalValue };

readFileSpy.mockImplementation(() => Buffer.from(`TEST=${newValue}`));
existsSyncSpy.mockReturnValue(true);

parseEnvFile();

expect(process.env.TEST).toEqual(originalValue);
});

it('will reject malformed lines', () => {
const fakeFile = `
bad
good=this
4=bad
good2='this'
good3="this"
`;
readFileSpy.mockImplementation(() => Buffer.from(fakeFile));
existsSyncSpy.mockReturnValue(true);

parseEnvFile();

expect(process.env.good).toEqual('this');
expect(process.env.good2).toEqual('this');
expect(process.env.good3).toEqual('this');
});

it('will ignore comments in the file', () => {
const fakeFile = `
#comment\n
//comment\n
TEST=test
`;
readFileSpy.mockImplementation(() => Buffer.from(fakeFile));
existsSyncSpy.mockReturnValue(true);

parseEnvFile();

expect(process.env.TEST).toEqual('test');
});

it('will not do anything if the .env file does not exist', () => {
readFileSpy.mockImplementation(() => Buffer.from('TEST=test'));
existsSyncSpy.mockReturnValue(false);

parseEnvFile();

expect(process.env.TEST).toBeUndefined();
});

it('will read an env variable from a .env file into process.env', () => {
readFileSpy.mockImplementation(() => Buffer.from('TEST=test'));
existsSyncSpy.mockReturnValue(true);

parseEnvFile();

expect(process.env.TEST).toEqual('test');
});
});
});
79 changes: 79 additions & 0 deletions src/test/parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import fs from 'fs';
import parseEnvFile from '../parser';

describe('parser', () => {
const readFileSpy = jest.spyOn(fs, 'readFileSync');
const existsSyncSpy = jest.spyOn(fs, 'existsSync');

beforeEach(async () => {
jest.resetModules();
process.env = {};
});

afterEach(() => {
jest.resetModules();
});

it('will not overwrite vars that already exist', () => {
const originalValue = 'I already exist';
const newValue = 'I should not';
process.env = { TEST: originalValue };

readFileSpy.mockImplementation(() => Buffer.from(`TEST=${newValue}`));
existsSyncSpy.mockReturnValue(true);

parseEnvFile();

expect(process.env.TEST).toEqual(originalValue);
});

it('will reject malformed lines', () => {
const fakeFile = `
bad
good=this
4=bad
good2='this'
good3="this"
`;
readFileSpy.mockImplementation(() => Buffer.from(fakeFile));
existsSyncSpy.mockReturnValue(true);

parseEnvFile();

expect(process.env.good).toEqual('this');
expect(process.env.good2).toEqual('this');
expect(process.env.good3).toEqual('this');
});

it('will ignore comments in the file', () => {
const fakeFile = `
#comment\n
//comment\n
TEST=test
`;
readFileSpy.mockImplementation(() => Buffer.from(fakeFile));
existsSyncSpy.mockReturnValue(true);

parseEnvFile();

expect(process.env.TEST).toEqual('test');
});

it('will not do anything if the .env file does not exist', () => {
readFileSpy.mockImplementation(() => Buffer.from('TEST=test'));
existsSyncSpy.mockReturnValue(false);

parseEnvFile();

expect(process.env.TEST).toBeUndefined();
});

it('will read an env variable from a .env file into process.env', () => {
readFileSpy.mockImplementation(() => Buffer.from('TEST=test'));
existsSyncSpy.mockReturnValue(true);

parseEnvFile();

expect(process.env.TEST).toEqual('test');
});
});
3 changes: 1 addition & 2 deletions src/types/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { DefaultEnvVars, UndefinedEnvVars } from './EnvVars';
import { RemoveKeys } from './helpers';

export interface ConfigOptions {
envFile?: string;
loadDotEnv?: boolean;
envPath?: string;
}

export interface Options<Required extends UndefinedEnvVars = DefaultEnvVars, Optional extends UndefinedEnvVars = DefaultEnvVars> {
Expand Down