-
Notifications
You must be signed in to change notification settings - Fork 9
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
base: main
Are you sure you want to change the base?
Changes from 18 commits
749e3eb
f534154
20dcc2f
f4d78df
39e4ad4
a07693d
4feb523
046ccb2
dc3747b
d663bb4
71b28be
1d49d3d
a1910ea
aabef0b
f10df70
7d7566e
e8711ca
b1058ce
cf06e65
e2d6dd2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
.vscode | ||
|
||
# Logs | ||
logs | ||
*.log | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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: | ||||||
|
||||||
|
@@ -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 | ||||||
nkahlor marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
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! | ||||||
nkahlor marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
```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" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||
} | ||||||
}); | ||||||
``` | ||||||
Each variable needs to be declared on a separate line. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add all of this to a new sub-section called |
||||||
|
||||||
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). | ||||||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import * as fs from 'fs'; | ||
import { join } from 'path'; | ||
import { ConfigOptions } from './types/Options'; | ||
|
||
function parseLine(line: string): Record<string, string> { | ||
const delimiter = '='; | ||
const lineRegex = /^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*="?'?.*'?"?$/g; | ||
nkahlor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let [key, val] = line.split(delimiter); | ||
|
||
// Ignore comments, or lines which don't conform to acceptable patterns | ||
if (key.startsWith('#') || key.startsWith('//') || !lineRegex.test(line)) { | ||
return {}; | ||
} | ||
|
||
key = key.trim(); | ||
val = val.trim(); | ||
// Get rid of wrapping double or single quotes | ||
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) { | ||
val = val.substr(1, val.length - 2); | ||
} | ||
|
||
return { [key]: val }; | ||
} | ||
|
||
export function parseEnvFile(options: ConfigOptions = {}): void { | ||
const fullPath = options.envPath || join(process.cwd(), '.env'); | ||
|
||
if (!fs.existsSync(fullPath)) { | ||
return; | ||
} | ||
|
||
const envFileContents = fs.readFileSync(fullPath).toString(); | ||
let envVarPairs: Record<string, string> = {}; | ||
const eol = /\r?\n/; | ||
|
||
const lines = envFileContents.split(eol); | ||
lines.forEach((line: string) => { | ||
envVarPairs = { ...envVarPairs, ...parseLine(line) }; | ||
}); | ||
|
||
// Toss everything into the environment | ||
Object.entries(envVarPairs).forEach(([key, val]) => { | ||
// Prefer env vars that have been set by the OS | ||
if (key in process.env === false) { | ||
process.env[key] = val; | ||
} | ||
}); | ||
} | ||
|
||
export default parseEnvFile; |
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'); | ||
}); | ||
}); |
There was a problem hiding this comment.
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