@dotenvx/dotenvx 0.19.1 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.19.1",
2
+ "version": "0.20.1",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -1,10 +1,13 @@
1
1
  const fs = require('fs')
2
+ const path = require('path')
2
3
  const execa = require('execa')
3
4
  const logger = require('./../../shared/logger')
4
5
  const helpers = require('./../helpers')
5
6
  const main = require('./../../lib/main')
6
7
  const parseEncryptionKeyFromDotenvKey = require('./../../lib/helpers/parseEncryptionKeyFromDotenvKey')
7
8
 
9
+ const RunDefault = require('./../../lib/services/runDefault')
10
+
8
11
  const ENCODING = 'utf8'
9
12
  const REPORT_ISSUE_LINK = 'https://github.com/dotenvx/dotenvx/issues/new'
10
13
 
@@ -36,7 +39,11 @@ const executeCommand = async function (commandArgs, env) {
36
39
  }
37
40
 
38
41
  try {
39
- commandProcess = execa(commandArgs[0], commandArgs.slice(1), {
42
+ const systemCommandPath = execa.sync('which', [commandArgs[0]]).stdout
43
+ logger.debug(`system command path [${systemCommandPath}]`)
44
+
45
+ // commandProcess = execa(commandArgs[0], commandArgs.slice(1), {
46
+ commandProcess = execa(systemCommandPath, commandArgs.slice(1), {
40
47
  stdio: 'inherit',
41
48
  env: { ...process.env, ...env }
42
49
  })
@@ -156,55 +163,48 @@ async function run () {
156
163
  }
157
164
  }
158
165
  } else {
159
- // convert to array if needed
160
- let optionEnvFile = options.envFile
161
- if (!Array.isArray(optionEnvFile)) {
162
- optionEnvFile = [optionEnvFile]
163
- }
164
-
165
- const readableFilepaths = new Set()
166
- const injected = new Set()
166
+ const { files, readableFilepaths, uniqueInjectedKeys } = new RunDefault(options.envFile, options.env, options.overload).run()
167
167
 
168
- for (const envFilepath of optionEnvFile) {
169
- const filepath = helpers.resolvePath(envFilepath)
168
+ for (const file of files) {
169
+ const filepath = file.filepath
170
170
 
171
- logger.verbose(`loading env from ${filepath}`)
171
+ logger.verbose(`loading env from ${filepath} (${path.resolve(filepath)})`)
172
172
 
173
- try {
174
- const src = fs.readFileSync(filepath, { encoding: ENCODING })
175
- const parsed = main.parseExpand(src, options.overload)
176
- const result = main.inject(process.env, parsed, options.overload)
177
-
178
- readableFilepaths.add(envFilepath)
179
- result.injected.forEach(key => injected.add(key))
180
- } catch (e) {
181
- // calculate development help message depending on state of repo
182
- const vaultFilepath = helpers.resolvePath('.env.vault')
183
- let developmentHelp = `? in development: add one with [echo "HELLO=World" > .env] and re-run [dotenvx run -- ${commandArgs.join(' ')}]`
184
- if (fs.existsSync(vaultFilepath)) {
185
- developmentHelp = `? in development: use [dotenvx decrypt] to decrypt .env.vault to .env and then re-run [dotenvx run -- ${commandArgs.join(' ')}]`
173
+ if (file.error) {
174
+ if (file.error.code === 'MISSING_ENV_FILE') {
175
+ logger.warnv(file.error)
176
+ logger.help(`? in development: add one with [echo "HELLO=World" > ${filepath}] and re-run [dotenvx run -- ${commandArgs.join(' ')}]`)
177
+ logger.help('? for production: set [DOTENV_KEY] on your server and re-deploy')
178
+ logger.help('? for ci: set [DOTENV_KEY] on your ci and re-build')
179
+ } else {
180
+ logger.warnv(file.error)
181
+ }
182
+ } else {
183
+ // debug parsed
184
+ const parsed = file.parsed
185
+ logger.debug(parsed)
186
+
187
+ // verbose/debug injected key/value
188
+ const injected = file.injected
189
+ for (const [key, value] of Object.entries(injected)) {
190
+ logger.verbose(`${key} set`)
191
+ logger.debug(`${key} set to ${value}`)
186
192
  }
187
193
 
188
- switch (e.code) {
189
- // missing .env
190
- case 'ENOENT':
191
- logger.warnv(`missing ${envFilepath} file (${filepath})`)
192
- logger.help(developmentHelp)
193
- logger.help('? for production: set [DOTENV_KEY] on your server and re-deploy')
194
- logger.help('? for ci: set [DOTENV_KEY] on your ci and re-build')
195
- break
196
-
197
- // unhandled error
198
- default:
199
- logger.warn(e)
200
- break
194
+ // verbose/debug preExisted key/value
195
+ const preExisted = file.preExisted
196
+ for (const [key, value] of Object.entries(preExisted)) {
197
+ logger.verbose(`${key} pre-exists (protip: use --overload to override)`)
198
+ logger.debug(`${key} pre-exists as ${value} (protip: use --overload to override)`)
201
199
  }
202
200
  }
203
201
  }
204
202
 
205
- if (readableFilepaths.size > 0) {
206
- logger.successv(`injecting env (${injected.size}) from ${[...readableFilepaths]}`)
203
+ let msg = `injecting env (${uniqueInjectedKeys.length})`
204
+ if (readableFilepaths.length > 0) {
205
+ msg += ` from ${readableFilepaths}`
207
206
  }
207
+ logger.successv(msg)
208
208
  }
209
209
 
210
210
  // Extract command and arguments after '--'
@@ -56,6 +56,7 @@ program.command('run')
56
56
  .addHelpText('after', examples.run)
57
57
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)', '.env')
58
58
  .option('-fv, --env-vault-file <path>', 'path to your .env.vault file', '.env.vault')
59
+ .option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")')
59
60
  .option('-o, --overload', 'override existing env variables')
60
61
  .action(require('./actions/run'))
61
62
 
@@ -0,0 +1,155 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const dotenv = require('dotenv')
4
+ const dotenvExpand = require('dotenv-expand')
5
+
6
+ const ENCODING = 'utf8'
7
+
8
+ class RunDefault {
9
+ constructor (envFile = '.env', env = [], overload = false) {
10
+ this.envFile = envFile
11
+ this.env = env
12
+ this.overload = overload
13
+ }
14
+
15
+ run () {
16
+ const strings = []
17
+ const files = []
18
+ const readableFilepaths = new Set()
19
+ const uniqueInjectedKeys = new Set()
20
+
21
+ const envs = this._envs()
22
+ for (const env of envs) {
23
+ const row = {}
24
+ row.string = env
25
+
26
+ try {
27
+ const parsed = this._parseExpand(env)
28
+ row.parsed = parsed
29
+
30
+ const { injected, preExisted } = this._inject(process.env, parsed)
31
+ row.injected = injected
32
+ row.preExisted = preExisted
33
+
34
+ for (const key of Object.keys(injected)) {
35
+ uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
36
+ }
37
+ } catch (e) {
38
+ row.error = e
39
+ }
40
+
41
+ strings.push(row)
42
+ }
43
+
44
+ const envFilepaths = this._envFilepaths()
45
+ for (const envFilepath of envFilepaths) {
46
+ const row = {}
47
+ row.filepath = envFilepath
48
+
49
+ const filepath = path.resolve(envFilepath)
50
+ try {
51
+ const src = fs.readFileSync(filepath, { encoding: ENCODING })
52
+ readableFilepaths.add(envFilepath)
53
+
54
+ const parsed = this._parseExpand(src)
55
+ row.parsed = parsed
56
+
57
+ const { injected, preExisted } = this._inject(process.env, parsed)
58
+ row.injected = injected
59
+ row.preExisted = preExisted
60
+
61
+ for (const key of Object.keys(injected)) {
62
+ uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
63
+ }
64
+ } catch (e) {
65
+ if (e.code === 'ENOENT') {
66
+ const error = new Error(`missing ${envFilepath} file (${filepath})`)
67
+ error.code = 'MISSING_ENV_FILE'
68
+
69
+ row.error = error
70
+ } else {
71
+ row.error = e
72
+ }
73
+ }
74
+
75
+ files.push(row)
76
+ }
77
+
78
+ return {
79
+ files,
80
+ strings,
81
+ readableFilepaths: [...readableFilepaths], // array
82
+ uniqueInjectedKeys: [...uniqueInjectedKeys]
83
+ }
84
+ }
85
+
86
+ _envFilepaths () {
87
+ if (!Array.isArray(this.envFile)) {
88
+ return [this.envFile]
89
+ }
90
+
91
+ return this.envFile
92
+ }
93
+
94
+ _envs () {
95
+ if (!Array.isArray(this.env)) {
96
+ return [this.env]
97
+ }
98
+
99
+ return this.env
100
+ }
101
+
102
+ _parseExpand (src) {
103
+ const parsed = dotenv.parse(src)
104
+
105
+ // consider moving this logic straight into dotenv-expand
106
+ let inputParsed = {}
107
+ if (this.overload) {
108
+ inputParsed = { ...process.env, ...parsed }
109
+ } else {
110
+ inputParsed = { ...parsed, ...process.env }
111
+ }
112
+
113
+ const expandPlease = {
114
+ processEnv: {},
115
+ parsed: inputParsed
116
+ }
117
+ const expanded = dotenvExpand.expand(expandPlease).parsed
118
+
119
+ // but then for logging only log the original keys existing in parsed. this feels unnecessarily complex - like dotenv-expand should support the ability to inject additional `process.env` or objects as it sees fit to the object it wants to expand
120
+ const result = {}
121
+ for (const key in parsed) {
122
+ result[key] = expanded[key]
123
+ }
124
+
125
+ return result
126
+ }
127
+
128
+ _inject (processEnv = {}, parsed = {}) {
129
+ const injected = {}
130
+ const preExisted = {}
131
+
132
+ // set processEnv
133
+ for (const key of Object.keys(parsed)) {
134
+ if (processEnv[key]) {
135
+ if (this.overload === true) {
136
+ processEnv[key] = parsed[key]
137
+
138
+ injected[key] = parsed[key] // track injected key/value
139
+ } else {
140
+ preExisted[key] = processEnv[key] // track preExisted key/value
141
+ }
142
+ } else {
143
+ processEnv[key] = parsed[key]
144
+ injected[key] = parsed[key] // track injected key/value
145
+ }
146
+ }
147
+
148
+ return {
149
+ injected,
150
+ preExisted
151
+ }
152
+ }
153
+ }
154
+
155
+ module.exports = RunDefault