@dotenvx/dotenvx 0.19.0 → 0.20.0

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.0",
2
+ "version": "0.20.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -1,11 +1,5 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
- const ignore = require('ignore')
4
-
5
1
  const logger = require('./../../shared/logger')
6
2
 
7
- const helpers = require('./../helpers')
8
-
9
3
  const Precommit = require('./../../lib/services/precommit')
10
4
 
11
5
  function precommit () {
@@ -14,102 +8,6 @@ function precommit () {
14
8
 
15
9
  const precommit = new Precommit(options)
16
10
  precommit.run()
17
-
18
- // 0. handle the --install flag
19
- if (options.install) {
20
- installPrecommitHook()
21
- return
22
- }
23
-
24
- // 1. check for .gitignore file
25
- if (!fs.existsSync('.gitignore')) {
26
- logger.errorvp('.gitignore missing')
27
- logger.help2('? add it with [touch .gitignore]')
28
- process.exit(1)
29
- }
30
-
31
- // 2. check .env* files against .gitignore file
32
- let warningCount = 0
33
- const ig = ignore().add(fs.readFileSync('.gitignore').toString())
34
- const files = fs.readdirSync(process.cwd())
35
- const dotenvFiles = files.filter(file => file.match(/^\.env(\..+)?$/))
36
- dotenvFiles.forEach(file => {
37
- // check if that file is being ignored
38
- if (ig.ignores(file)) {
39
- switch (file) {
40
- case '.env.example':
41
- warningCount += 1
42
- logger.warnv(`${file} (currently ignored but should not be)`)
43
- logger.help2(`? add !${file} to .gitignore with [echo "!${file}" >> .gitignore]`)
44
- break
45
- case '.env.vault':
46
- warningCount += 1
47
- logger.warnv(`${file} (currently ignored but should not be)`)
48
- logger.help2(`? add !${file} to .gitignore with [echo "!${file}" >> .gitignore]`)
49
- break
50
- default:
51
- break
52
- }
53
- } else {
54
- switch (file) {
55
- case '.env.example':
56
- break
57
- case '.env.vault':
58
- break
59
- default:
60
- logger.errorvp(`${file} not properly gitignored`)
61
- logger.help2(`? add ${file} to .gitignore with [echo ".env*" >> .gitignore]`)
62
- process.exit(1) // 3.1 exit early with error code
63
- break
64
- }
65
- }
66
- })
67
-
68
- // 3. outpout success
69
- if (warningCount > 0) {
70
- logger.successvp(`success (with ${helpers.pluralize('warning', warningCount)})`)
71
- } else {
72
- logger.successvp('success')
73
- }
74
- }
75
-
76
- function installPrecommitHook () {
77
- const hookScript = `#!/bin/sh
78
-
79
- if ! command -v dotenvx &> /dev/null
80
- then
81
- echo "[dotenvx][precommit] 'dotenvx' command not found"
82
- echo "[dotenvx][precommit] ? install it with [brew install dotenvx/brew/dotenvx]"
83
- echo "[dotenvx][precommit] ? other install options [https://dotenvx.com/docs/install]"
84
- exit 1
85
- fi
86
-
87
- dotenvx precommit`
88
- const hookPath = path.join('.git', 'hooks', 'pre-commit')
89
-
90
- try {
91
- // Check if the pre-commit file already exists
92
- if (fs.existsSync(hookPath)) {
93
- // Read the existing content of the file
94
- const existingContent = fs.readFileSync(hookPath, 'utf8')
95
-
96
- // Check if 'dotenvx precommit' already exists in the file
97
- if (!existingContent.includes('dotenvx precommit')) {
98
- // Append 'dotenvx precommit' to the existing file
99
- fs.appendFileSync(hookPath, '\n' + hookScript)
100
- logger.successvp(`dotenvx precommit appended [${hookPath}]`)
101
- } else {
102
- logger.warnvp(`dotenvx precommit exists [${hookPath}]`)
103
- }
104
- } else {
105
- // If the pre-commit file doesn't exist, create a new one with the hookScript
106
- fs.writeFileSync(hookPath, hookScript)
107
- fs.chmodSync(hookPath, '755') // Make the file executable
108
- logger.successvp(`dotenvx precommit installed [${hookPath}]`)
109
- }
110
- } catch (err) {
111
- logger.errorvp(`Failed to modify pre-commit hook: ${err.message}`)
112
- }
113
11
  }
114
12
 
115
13
  module.exports = precommit
@@ -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
 
@@ -156,55 +159,48 @@ async function run () {
156
159
  }
157
160
  }
158
161
  } 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()
162
+ const { files, readableFilepaths, uniqueInjectedKeys } = new RunDefault(options.envFile, options.env, options.overload).run()
167
163
 
168
- for (const envFilepath of optionEnvFile) {
169
- const filepath = helpers.resolvePath(envFilepath)
164
+ for (const file of files) {
165
+ const filepath = file.filepath
170
166
 
171
- logger.verbose(`loading env from ${filepath}`)
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)
167
+ logger.verbose(`loading env from ${filepath} (${path.resolve(filepath)})`)
177
168
 
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(' ')}]`
169
+ if (file.error) {
170
+ if (file.error.code === 'MISSING_ENV_FILE') {
171
+ logger.warnv(file.error)
172
+ logger.help(`? in development: add one with [echo "HELLO=World" > ${filepath}] and re-run [dotenvx run -- ${commandArgs.join(' ')}]`)
173
+ logger.help('? for production: set [DOTENV_KEY] on your server and re-deploy')
174
+ logger.help('? for ci: set [DOTENV_KEY] on your ci and re-build')
175
+ } else {
176
+ logger.warnv(file.error)
177
+ }
178
+ } else {
179
+ // debug parsed
180
+ const parsed = file.parsed
181
+ logger.debug(parsed)
182
+
183
+ // verbose/debug injected key/value
184
+ const injected = file.injected
185
+ for (const [key, value] of Object.entries(injected)) {
186
+ logger.verbose(`${key} set`)
187
+ logger.debug(`${key} set to ${value}`)
186
188
  }
187
189
 
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
190
+ // verbose/debug preExisted key/value
191
+ const preExisted = file.preExisted
192
+ for (const [key, value] of Object.entries(preExisted)) {
193
+ logger.verbose(`${key} pre-exists (protip: use --overload to override)`)
194
+ logger.debug(`${key} pre-exists as ${value} (protip: use --overload to override)`)
201
195
  }
202
196
  }
203
197
  }
204
198
 
205
- if (readableFilepaths.size > 0) {
206
- logger.successv(`injecting env (${injected.size}) from ${[...readableFilepaths]}`)
199
+ let msg = `injecting env (${uniqueInjectedKeys.length})`
200
+ if (readableFilepaths.length > 0) {
201
+ msg += ` from ${readableFilepaths}`
207
202
  }
203
+ logger.successv(msg)
208
204
  }
209
205
 
210
206
  // 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
 
@@ -1,4 +1,8 @@
1
+ /* istanbul ignore file */
2
+ const fs = require('fs')
3
+ const ignore = require('ignore')
1
4
  const logger = require('./../../shared/logger')
5
+ const helpers = require('./../../cli/helpers')
2
6
 
3
7
  const InstallPrecommitHook = require('./../helpers/installPrecommitHook')
4
8
 
@@ -14,7 +18,56 @@ class Precommit {
14
18
  return true
15
19
  }
16
20
 
17
- logger.info('implement')
21
+ // 1. check for .gitignore file
22
+ if (!fs.existsSync('.gitignore')) {
23
+ logger.errorvp('.gitignore missing')
24
+ logger.help2('? add it with [touch .gitignore]')
25
+ process.exit(1)
26
+ }
27
+
28
+ // 2. check .env* files against .gitignore file
29
+ let warningCount = 0
30
+ const ig = ignore().add(fs.readFileSync('.gitignore').toString())
31
+ const files = fs.readdirSync(process.cwd())
32
+ const dotenvFiles = files.filter(file => file.match(/^\.env(\..+)?$/))
33
+ dotenvFiles.forEach(file => {
34
+ // check if that file is being ignored
35
+ if (ig.ignores(file)) {
36
+ switch (file) {
37
+ case '.env.example':
38
+ warningCount += 1
39
+ logger.warnv(`${file} (currently ignored but should not be)`)
40
+ logger.help2(`? add !${file} to .gitignore with [echo "!${file}" >> .gitignore]`)
41
+ break
42
+ case '.env.vault':
43
+ warningCount += 1
44
+ logger.warnv(`${file} (currently ignored but should not be)`)
45
+ logger.help2(`? add !${file} to .gitignore with [echo "!${file}" >> .gitignore]`)
46
+ break
47
+ default:
48
+ break
49
+ }
50
+ } else {
51
+ switch (file) {
52
+ case '.env.example':
53
+ break
54
+ case '.env.vault':
55
+ break
56
+ default:
57
+ logger.errorvp(`${file} not properly gitignored`)
58
+ logger.help2(`? add ${file} to .gitignore with [echo ".env*" >> .gitignore]`)
59
+ process.exit(1) // 3.1 exit early with error code
60
+ break
61
+ }
62
+ }
63
+ })
64
+
65
+ // 3. outpout success
66
+ if (warningCount > 0) {
67
+ logger.successvp(`success (with ${helpers.pluralize('warning', warningCount)})`)
68
+ } else {
69
+ logger.successvp('success')
70
+ }
18
71
  }
19
72
 
20
73
  /* istanbul ignore next */
@@ -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