@dotenvx/dotenvx 0.22.0 → 0.24.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.22.0",
2
+ "version": "0.24.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -6,8 +6,28 @@ function precommit () {
6
6
  const options = this.opts()
7
7
  logger.debug(`options: ${JSON.stringify(options)}`)
8
8
 
9
- const precommit = new Precommit(options)
10
- precommit.run()
9
+ try {
10
+ const {
11
+ successMessage,
12
+ warnings
13
+ } = new Precommit(options).run()
14
+
15
+ for (const warning of warnings) {
16
+ logger.warnv(warning.message)
17
+ if (warning.help) {
18
+ logger.help(warning.help)
19
+ }
20
+ }
21
+
22
+ logger.successvp(successMessage)
23
+ } catch (error) {
24
+ logger.errorvp(error.message)
25
+ if (error.help) {
26
+ logger.help(error.help)
27
+ }
28
+
29
+ process.exit(1)
30
+ }
11
31
  }
12
32
 
13
33
  module.exports = precommit
@@ -0,0 +1,54 @@
1
+ const { execSync } = require('child_process')
2
+
3
+ function _chomp (value) {
4
+ return value.replace(/\r?\n|\r/, '')
5
+ }
6
+
7
+ function interpolate (value, processEnv, parsed) {
8
+ const matches = value.match(/\$\([^()]+\)/) || []
9
+
10
+ return matches.reduce(function (newValue, match) {
11
+ // get command
12
+ const command = match.substring(2, match.length - 1)
13
+ // execute command
14
+ const value = _chomp(execSync(command).toString())
15
+ // replace with command value
16
+ return newValue.replace(match, value)
17
+ }, value)
18
+ }
19
+
20
+ function evaluate (options) {
21
+ let processEnv = process.env
22
+ if (options && options.processEnv != null) {
23
+ processEnv = options.processEnv
24
+ }
25
+
26
+ for (const key in options.parsed) {
27
+ let value = options.parsed[key]
28
+
29
+ const inProcessEnv = Object.prototype.hasOwnProperty.call(processEnv, key)
30
+
31
+ if (inProcessEnv) {
32
+ if (processEnv[key] === options.parsed[key]) {
33
+ // assume was set to processEnv from the .env file if the values match and therefore interpolate
34
+ value = interpolate(value, processEnv, options.parsed)
35
+ } else {
36
+ // do not interpolate - assume processEnv had the intended value even if containing a $.
37
+ value = processEnv[key]
38
+ }
39
+ } else {
40
+ // not inProcessEnv so assume interpolation for this .env key
41
+ value = interpolate(value, processEnv, options.parsed)
42
+ }
43
+
44
+ options.parsed[key] = value
45
+ }
46
+
47
+ for (const processKey in options.parsed) {
48
+ processEnv[processKey] = options.parsed[processKey]
49
+ }
50
+
51
+ return options
52
+ }
53
+
54
+ module.exports.eval = evaluate
@@ -3,6 +3,7 @@ const path = require('path')
3
3
  const encrypt = require('./encrypt')
4
4
  const changed = require('./changed')
5
5
  const guessEnvironment = require('./guessEnvironment')
6
+ const removePersonal = require('./removePersonal')
6
7
 
7
8
  class DotenvVault {
8
9
  constructor (dotenvFiles = {}, dotenvKeys = {}, dotenvVaults = {}) {
@@ -23,8 +24,10 @@ class DotenvVault {
23
24
  let ciphertext = this.dotenvVaults[vault]
24
25
  const dotenvKey = this.dotenvKeys[`DOTENV_KEY_${environment.toUpperCase()}`]
25
26
 
26
- if (!ciphertext || ciphertext.length === 0 || changed(ciphertext, raw, dotenvKey)) {
27
- ciphertext = encrypt(raw, dotenvKey)
27
+ const cleanRaw = removePersonal(raw)
28
+
29
+ if (!ciphertext || ciphertext.length === 0 || changed(ciphertext, cleanRaw, dotenvKey)) {
30
+ ciphertext = encrypt(cleanRaw, dotenvKey)
28
31
  this.dotenvVaults[vault] = ciphertext
29
32
  addedVaults.add(vault) // for info logging to user
30
33
 
@@ -1,8 +1,6 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
 
4
- const logger = require('./../../shared/logger')
5
-
6
4
  const HOOK_SCRIPT = `#!/bin/sh
7
5
 
8
6
  if ! command -v dotenvx &> /dev/null
@@ -21,20 +19,30 @@ class InstallPrecommitHook {
21
19
  }
22
20
 
23
21
  run () {
22
+ let successMessage
23
+
24
24
  try {
25
25
  // Check if the pre-commit file already exists
26
26
  if (this._exists()) {
27
27
  // Check if 'dotenvx precommit' already exists in the file
28
- if (!this._currentHook().includes('dotenvx precommit')) {
29
- this._appendHook()
28
+ if (this._currentHook().includes('dotenvx precommit')) {
29
+ // do nothing
30
+ successMessage = `dotenvx precommit exists [${this.hookPath}]`
30
31
  } else {
31
- logger.warnvp(`dotenvx precommit exists [${this.hookPath}]`)
32
+ this._appendHook()
33
+ successMessage = `dotenvx precommit appended [${this.hookPath}]`
32
34
  }
33
35
  } else {
34
36
  this._createHook()
37
+ successMessage = `dotenvx precommit installed [${this.hookPath}]`
38
+ }
39
+
40
+ return {
41
+ successMessage
35
42
  }
36
43
  } catch (err) {
37
- logger.errorvp(`failed to modify pre-commit hook: ${err.message}`)
44
+ const error = new Error(`failed to modify pre-commit hook: ${err.message}`)
45
+ throw error
38
46
  }
39
47
  }
40
48
 
@@ -50,13 +58,11 @@ class InstallPrecommitHook {
50
58
  // If the pre-commit file doesn't exist, create a new one with the hookScript
51
59
  fs.writeFileSync(this.hookPath, HOOK_SCRIPT)
52
60
  fs.chmodSync(this.hookPath, '755') // Make the file executable
53
- logger.successvp(`dotenvx precommit installed [${this.hookPath}]`)
54
61
  }
55
62
 
56
63
  _appendHook () {
57
64
  // Append 'dotenvx precommit' to the existing file
58
65
  fs.appendFileSync(this.hookPath, '\n' + HOOK_SCRIPT)
59
- logger.successvp(`dotenvx precommit appended [${this.hookPath}]`)
60
66
  }
61
67
  }
62
68
 
@@ -1,7 +1,8 @@
1
1
  const dotenv = require('dotenv')
2
2
  const dotenvExpand = require('dotenv-expand')
3
+ const dotenvEval = require('./dotenvEval')
3
4
 
4
- function parseExpand (src, overload) {
5
+ function parseExpandAndEval (src, overload) {
5
6
  const parsed = dotenv.parse(src)
6
7
 
7
8
  // consider moving this logic straight into dotenv-expand
@@ -18,13 +19,26 @@ function parseExpand (src, overload) {
18
19
  }
19
20
  const expanded = dotenvExpand.expand(expandPlease).parsed
20
21
 
22
+ let inputExpanded = {}
23
+ if (overload) {
24
+ inputExpanded = { ...process.env, ...expanded }
25
+ } else {
26
+ inputExpanded = { ...expanded, ...process.env }
27
+ }
28
+
29
+ const evaluatePlease = {
30
+ processEnv: {},
31
+ parsed: inputExpanded
32
+ }
33
+ const evaluated = dotenvEval.eval(evaluatePlease).parsed
34
+
21
35
  // 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
22
36
  const result = {}
23
37
  for (const key in parsed) {
24
- result[key] = expanded[key]
38
+ result[key] = evaluated[key]
25
39
  }
26
40
 
27
41
  return result
28
42
  }
29
43
 
30
- module.exports = parseExpand
44
+ module.exports = parseExpandAndEval
@@ -0,0 +1,10 @@
1
+ function pluralize (word, count) {
2
+ // simple pluralization: add 's' at the end
3
+ if (count === 0 || count > 1) {
4
+ return word + 's'
5
+ } else {
6
+ return word
7
+ }
8
+ }
9
+
10
+ module.exports = pluralize
@@ -0,0 +1,9 @@
1
+ const SPLIT_REGEX = /#\s*personal\.dotenv\s*/i
2
+
3
+ function removePersonal (raw) {
4
+ const parts = raw.split(SPLIT_REGEX)
5
+
6
+ return parts[0]
7
+ }
8
+
9
+ module.exports = removePersonal
package/src/lib/main.js CHANGED
@@ -8,6 +8,9 @@ const Ls = require('./services/ls')
8
8
  const Get = require('./services/get')
9
9
  const Genexample = require('./services/genexample')
10
10
 
11
+ // helpers
12
+ const dotenvEval = require('./helpers/dotenvEval')
13
+
11
14
  // proxies to dotenv
12
15
  const config = function (options) {
13
16
  const env = dotenv.config(options)
@@ -18,7 +21,13 @@ const config = function (options) {
18
21
  }
19
22
  const expanded = dotenvExpand.expand(env)
20
23
 
21
- return expanded
24
+ // if processEnv passed also pass to eval
25
+ if (options && options.processEnv) {
26
+ expanded.processEnv = options.processEnv
27
+ }
28
+ const evaluated = dotenvEval.eval(expanded)
29
+
30
+ return evaluated
22
31
  }
23
32
 
24
33
  const configDotenv = function (options) {
@@ -1,9 +1,8 @@
1
1
  /* istanbul ignore file */
2
2
  const fs = require('fs')
3
3
  const ignore = require('ignore')
4
- const logger = require('./../../shared/logger')
5
- const helpers = require('./../../cli/helpers')
6
4
 
5
+ const pluralize = require('./../helpers/pluralize')
7
6
  const InstallPrecommitHook = require('./../helpers/installPrecommitHook')
8
7
 
9
8
  class Precommit {
@@ -13,66 +12,59 @@ class Precommit {
13
12
 
14
13
  run () {
15
14
  if (this.install) {
16
- this._installPrecommitHook()
15
+ const {
16
+ successMessage
17
+ } = this._installPrecommitHook()
17
18
 
18
- return true
19
- }
19
+ return {
20
+ successMessage,
21
+ warnings: []
22
+ }
23
+ } else {
24
+ const warnings = []
20
25
 
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
- }
26
+ // 1. check for .gitignore file
27
+ if (!fs.existsSync('.gitignore')) {
28
+ const error = new Error('.gitignore missing')
29
+ error.help = '? add it with [touch .gitignore]'
30
+ throw error
31
+ }
27
32
 
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
33
+ // 2. check .env* files against .gitignore file
34
+ const ig = ignore().add(fs.readFileSync('.gitignore').toString())
35
+ const files = fs.readdirSync(process.cwd())
36
+ const dotenvFiles = files.filter(file => file.match(/^\.env(\..+)?$/))
37
+ dotenvFiles.forEach(file => {
38
+ // check if that file is being ignored
39
+ if (ig.ignores(file)) {
40
+ if (file === '.env.example' || file === '.env.vault') {
41
+ const warning = new Error(`${file} (currently ignored but should not be)`)
42
+ warning.help = `? add !${file} to .gitignore with [echo "!${file}" >> .gitignore]`
43
+ warnings.push(warning)
44
+ }
45
+ } else {
46
+ if (file !== '.env.example' && file !== '.env.vault') {
47
+ const error = new Error(`${file} not properly gitignored`)
48
+ error.help = `? add ${file} to .gitignore with [echo ".env*" >> .gitignore]`
49
+ throw error
50
+ }
61
51
  }
52
+ })
53
+
54
+ let successMessage = 'success'
55
+ if (warnings.length > 0) {
56
+ successMessage = `success (with ${pluralize('warning', warnings.length)})`
62
57
  }
63
- })
64
58
 
65
- // 3. outpout success
66
- if (warningCount > 0) {
67
- logger.successvp(`success (with ${helpers.pluralize('warning', warningCount)})`)
68
- } else {
69
- logger.successvp('success')
59
+ return {
60
+ successMessage,
61
+ warnings
62
+ }
70
63
  }
71
64
  }
72
65
 
73
- /* istanbul ignore next */
74
66
  _installPrecommitHook () {
75
- new InstallPrecommitHook().run()
67
+ return new InstallPrecommitHook().run()
76
68
  }
77
69
  }
78
70
 
@@ -4,7 +4,7 @@ const path = require('path')
4
4
  const ENCODING = 'utf8'
5
5
 
6
6
  const inject = require('./../helpers/inject')
7
- const parseExpand = require('./../helpers/parseExpand')
7
+ const parseExpandAndEval = require('./../helpers/parseExpandAndEval')
8
8
 
9
9
  class RunDefault {
10
10
  constructor (envFile = '.env', env = [], overload = false) {
@@ -25,7 +25,7 @@ class RunDefault {
25
25
  row.string = env
26
26
 
27
27
  try {
28
- const parsed = parseExpand(env, this.overload)
28
+ const parsed = parseExpandAndEval(env, this.overload)
29
29
  row.parsed = parsed
30
30
 
31
31
  const { injected, preExisted } = inject(process.env, parsed, this.overload)
@@ -52,7 +52,7 @@ class RunDefault {
52
52
  const src = fs.readFileSync(filepath, { encoding: ENCODING })
53
53
  readableFilepaths.add(envFilepath)
54
54
 
55
- const parsed = parseExpand(src, this.overload)
55
+ const parsed = parseExpandAndEval(src, this.overload)
56
56
  row.parsed = parsed
57
57
 
58
58
  const { injected, preExisted } = inject(process.env, parsed, this.overload)
@@ -4,7 +4,7 @@ const dotenv = require('dotenv')
4
4
 
5
5
  const inject = require('./../helpers/inject')
6
6
  const decrypt = require('./../helpers/decrypt')
7
- const parseExpand = require('./../helpers/parseExpand')
7
+ const parseExpandAndEval = require('./../helpers/parseExpandAndEval')
8
8
  const parseEnvironmentFromDotenvKey = require('./../helpers/parseEnvironmentFromDotenvKey')
9
9
 
10
10
  const ENCODING = 'utf8'
@@ -43,7 +43,7 @@ class RunVault {
43
43
  const row = {}
44
44
  row.string = env
45
45
 
46
- const parsed = parseExpand(env, this.overload)
46
+ const parsed = parseExpandAndEval(env, this.overload)
47
47
  row.parsed = parsed
48
48
 
49
49
  const { injected, preExisted } = inject(process.env, parsed, this.overload)
@@ -77,7 +77,7 @@ class RunVault {
77
77
  }
78
78
 
79
79
  // parse this. it's the equivalent of the .env file
80
- const parsed = parseExpand(decrypted, this.overload)
80
+ const parsed = parseExpandAndEval(decrypted, this.overload)
81
81
  const { injected, preExisted } = inject(process.env, parsed, this.overload)
82
82
 
83
83
  for (const key of Object.keys(injected)) {