Skip to content

Commit

Permalink
Merge pull request #55 from danilofuchs/danilofuchs/local-envs
Browse files Browse the repository at this point in the history
feat: Load `.env.*.local` envs
  • Loading branch information
colynb committed Jul 24, 2020
2 parents f49bbe1 + 9cbddcf commit b376518
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 32 deletions.
20 changes: 13 additions & 7 deletions README.md
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
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

0 comments on commit b376518

Please sign in to comment.