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

feat: Load .env.*.local envs #55

Merged
merged 2 commits into from
Jul 24, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,28 @@ By default, the plugin looks for the file: `.env`. In most use cases this is all
.env.production
```

When you deploy with `NODE_ENV` set: `NODE_ENV=production sls deploy` the plugin will look for a file named `.env.production`. If it doesn't exist it will default to `.env`. If for some reason you can't set NODE_ENV, you could always just pass it in as an option: `sls deploy --env production` or `sls deploy --stage production`. If `NODE_ENV`, `--env` or `--stage` is not set, it will default to `development`.
When you deploy with `NODE_ENV` set: `NODE_ENV=production sls deploy` the plugin will look for files named `.env`, `.env.production`, `.env.production.local`. If for some reason you can't set NODE_ENV, you could always just pass it in as an option: `sls deploy --env production` or `sls deploy --stage production`. If `NODE_ENV`, `--env` or `--stage` is not set, it will default to `development`.

The precedence between the options is the following:
`NODE_ENV` **>** `--env` **>** `--stage`

| Valid .env file names | Description |
| --------------------- | -------------------------------------------------------------------------------------------------------------- |
| .env | Default file name when no other files are specified or found. |
| .env.development | If NODE_ENV or --env or --stage **is not set**, will try to load `.env.development`. If not found, load `.env` |
| .env.{ENV} | If NODE_ENV or --env or --stage **is set**, will try to load `.env.{env}`. If not found, load `.env` |
The env resolution pattern follows the one used by [Rail's dotenv](https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use) and [create-react-app](https://create-react-app.dev/docs/adding-custom-environment-variables/#what-other-env-files-can-be-used) (Added Apr 19, 2020 by @danilofuchs):

| Valid .env file names | Description |
| --------------------- | ------------------------------------------------------------------------------------ |
| .env | Default file, always included |
| .env.local | Included in all environments except test |
| .env.development | If NODE_ENV or --env or --stage **is not set**, will try to load `.env.development`. |
| .env.{ENV} | If NODE_ENV or --env or --stage **is set**, will try to load `.env.{env}`. |
| .env.{ENV}.local | Every env set up in `.env.{ENV}.local` **will override** other envs |

`.env` files **should be** checked into source control (with the exclusion of `.env.*.local`). Add `.env.*.local` to your .gitignore

### Plugin options

> path: path/to/my/.env

The plugin will look for your .env file in the same folder where you run the command using the file resolution rules as described above, but these rules can be overridden by setting the `path` option.
The plugin will look for your .env file in the same folder where you run the command using the file resolution rules as described above, but these rules can be overridden by setting the `path` option. This will **disable** automatic env file resolution

> basePath: path/to/my/

Expand Down
70 changes: 45 additions & 25 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,77 @@ const fs = require('fs')
class ServerlessPlugin {
constructor(serverless, options) {
this.serverless = serverless
this.serverless.service.provider.environment =
this.serverless.service.provider.environment || {}
this.config =
this.serverless.service.custom && this.serverless.service.custom['dotenv']
this.logging = this.config && typeof this.config.logging !== 'undefined' ? this.config.logging : true;

this.serverless.service.provider.environment = this.serverless.service.provider.environment || {}
this.config = this.serverless.service.custom && this.serverless.service.custom['dotenv']
this.logging = this.config && typeof this.config.logging !== 'undefined' ? this.config.logging : true

this.loadEnv(this.getEnvironment(options))
}

/**
* @param {Object} options
* @returns {string}
*/
getEnvironment(options) {
return process.env.NODE_ENV || options.env || options.stage || 'development'
}

resolveEnvFileName(env) {
/**
* @param {string} env
* @returns {string[]}
*/
resolveEnvFileNames(env) {
if (this.config && this.config.path) {
return this.config.path
if (Array.isArray(this.config.path)) {
return this.config.path
}
return [this.config.path]
}

let basePath =
this.config && this.config.basePath ? this.config.basePath : ''
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
const dotenvFiles = [
`.env.${env}.local`,
`.env.${env}`,
// Don't include `.env.local` for `test` environment
// since normally you expect tests to produce the same
// results for everyone
env !== 'test' && `.env.local`,
`.env`,
]

const basePath = this.config && this.config.basePath ? this.config.basePath : ''

let defaultPath = basePath + '.env'
let path = basePath + '.env.' + env
const filesNames = dotenvFiles.map(file => basePath + file)

return fs.existsSync(path) ? path : defaultPath
return filesNames.filter(fileName => fs.existsSync(fileName))
}

/**
* @param {string} env
*/
loadEnv(env) {
var envFileName = this.resolveEnvFileName(env)
const envFileNames = this.resolveEnvFileNames(env)
try {
let envVars = dotenvExpand(dotenv.config({ path: envFileName })).parsed
const envVarsArray = envFileNames.map(fileName => dotenvExpand(dotenv.config({ path: fileName })).parsed)

var include = false
var exclude = false
const envVars = envVarsArray.reduce((acc, curr) => ({ ...acc, ...curr }), {})

let include = false
let exclude = false

if (this.config && this.config.include) {
include = this.config.include
}

if (this.config && this.config.exclude && !include) { // Don't allow both include and exclude to be specified
if (this.config && this.config.exclude && !include) {
// Don't allow both include and exclude to be specified
exclude = this.config.exclude
}

if (envVars) {
if (this.logging) {
this.serverless.cli.log(
'DOTENV: Loading environment variables from ' + envFileName + ':'
'DOTENV: Loading environment variables from ' + envFileNames.reverse().join(', ') + ':'
)
}
if (include) {
Expand All @@ -70,7 +94,7 @@ class ServerlessPlugin {
.forEach(key => {
delete envVars[key]
})
}
}
Object.keys(envVars).forEach(key => {
if (this.logging) {
this.serverless.cli.log('\t - ' + key)
Expand All @@ -83,11 +107,7 @@ class ServerlessPlugin {
}
}
} catch (e) {
console.error(
chalk.red(
'\n Serverless Plugin Error --------------------------------------\n'
)
)
console.error(chalk.red('\n Serverless Plugin Error --------------------------------------\n'))
console.error(chalk.red(' ' + e.message))
}
}
Expand Down