@dotenvx/dotenvx 1.7.0 → 1.9.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/CHANGELOG.md CHANGED
@@ -2,7 +2,40 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
- ## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.7.0...main)
5
+ ## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.9.0...main)
6
+
7
+ ## 1.9.0
8
+
9
+ ### Added
10
+
11
+ * add `--exclude-key` (`-ek`) option to `dotenvx encrypt` and `dotenvx decrypt` ([#344](https://github.com/dotenvx/dotenvx/pull/344))
12
+
13
+ ### Changed
14
+
15
+ * preserve comments and spacing on first-time generation of .env.example file ([#346](https://github.com/dotenvx/dotenvx/pull/346))
16
+
17
+ ### Removed
18
+
19
+ * removed `winston` - logger simplified to use `console.log` going forward ([#347](https://github.com/dotenvx/dotenvx/pull/347))
20
+
21
+ ## 1.8.0
22
+
23
+ ### Added
24
+
25
+ * warn when decryption fails on `run` ([#339](https://github.com/dotenvx/dotenvx/pull/339))
26
+ * decrypt expanded values as necessary ([#336](https://github.com/dotenvx/dotenvx/pull/336))
27
+
28
+ ### Changed
29
+
30
+ * use `ansi` colors over `rgb` - for wider terminal coverage ([#340](https://github.com/dotenvx/dotenvx/pull/340))
31
+ * replace `chalk` with `picocolors` and `color-name` - cutting down on 5 dependencies ([#335](https://github.com/dotenvx/dotenvx/pull/335))
32
+ * replace `execa` with `tinyexec` - cutting down on 15 dependencies ([#328](https://github.com/dotenvx/dotenvx/pull/328))
33
+ * optimize `Ls._filepaths` ([#317](https://github.com/dotenvx/dotenvx/pull/317/))
34
+
35
+ ### Removed
36
+
37
+ * remove `picocolors` and `color-name` - cutting down on 2 dependencies ([#340](https://github.com/dotenvx/dotenvx/pull/340))
38
+ * remove `ext hub` from extension list (you can still install it as an extension [here](https://github.com/dotenvx/dotenvx-ext-hub)) ([#337](https://github.com/dotenvx/dotenvx/pull/337))
6
39
 
7
40
  ## 1.7.0
8
41
 
package/README.md CHANGED
@@ -27,31 +27,31 @@ console.log(`Hello ${process.env.HELLO}`)
27
27
 
28
28
  or install globally - *unlocks dotenv for any language, framework, or platform!*
29
29
 
30
- <details><summary>with brew 🍺</summary><br>
30
+ <details><summary>with curl 🌐 </summary><br>
31
31
 
32
32
  ```sh
33
- brew install dotenvx/brew/dotenvx
33
+ curl -sfS https://dotenvx.sh | sh
34
34
  dotenvx help
35
35
  ```
36
36
 
37
- [![brew installs](https://img.shields.io/github/downloads/dotenvx/dotenvx/total?label=brew%20installs)](https://github.com/dotenvx/homebrew-brew/blob/main/Formula/dotenvx.rb)
38
- <sup>*homebrew installs sourced from github releases - <a href="https://github.com/dotenvx/homebrew-brew/blob/main/Formula/dotenvx.rb">formula</a></sup>
37
+ [![mac](https://img.shields.io/endpoint?url=https://dotenvx.sh/stats/curl/darwin&label=mac)](https://github.com/dotenvx/dotenvx.sh/blob/main/install.sh)
38
+ [![linux](https://img.shields.io/endpoint?url=https://dotenvx.sh/stats/curl/linux&label=linux)](https://github.com/dotenvx/dotenvx.sh/blob/main/install.sh)
39
+ [![windows](https://img.shields.io/endpoint?url=https://dotenvx.sh/stats/curl/windows&label=windows)](https://github.com/dotenvx/dotenvx.sh/blob/main/install.sh)
40
+ <sup>*curl installs sourced from npm binary packages - <a href="https://www.npmjs.com/package/@dotenvx/dotenvx-linux-x86_64">example</a></sup>
39
41
 
40
42
  &nbsp;
41
43
 
42
44
  </details>
43
45
 
44
- <details><summary>with curl 🌐 </summary><br>
46
+ <details><summary>with brew 🍺</summary><br>
45
47
 
46
48
  ```sh
47
- curl -sfS https://dotenvx.sh | sh
49
+ brew install dotenvx/brew/dotenvx
48
50
  dotenvx help
49
51
  ```
50
52
 
51
- [![mac](https://img.shields.io/endpoint?url=https://dotenvx.sh/stats/curl/darwin&label=mac)](https://github.com/dotenvx/dotenvx.sh/blob/main/install.sh)
52
- [![linux](https://img.shields.io/endpoint?url=https://dotenvx.sh/stats/curl/linux&label=linux)](https://github.com/dotenvx/dotenvx.sh/blob/main/install.sh)
53
- [![windows](https://img.shields.io/endpoint?url=https://dotenvx.sh/stats/curl/windows&label=windows)](https://github.com/dotenvx/dotenvx.sh/blob/main/install.sh)
54
- <sup>*curl installs sourced from npm binary packages - <a href="https://www.npmjs.com/package/@dotenvx/dotenvx-linux-x86_64">example</a></sup>
53
+ [![brew installs](https://img.shields.io/github/downloads/dotenvx/dotenvx/total?label=brew%20installs)](https://github.com/dotenvx/homebrew-brew/blob/main/Formula/dotenvx.rb)
54
+ <sup>*homebrew installs sourced from github releases - <a href="https://github.com/dotenvx/homebrew-brew/blob/main/Formula/dotenvx.rb">formula</a></sup>
55
55
 
56
56
  &nbsp;
57
57
 
@@ -866,7 +866,7 @@ More examples
866
866
  </details>
867
867
  * <details><summary>`run --verbose`</summary><br>
868
868
 
869
- Set log level to `verbose`. ([log levels](https://github.com/winstonjs/winston?tab=readme-ov-file#logging))
869
+ Set log level to `verbose`. ([log levels](https://docs.npmjs.com/cli/v8/using-npm/logging#setting-log-levels))
870
870
 
871
871
  ```sh
872
872
  $ echo "HELLO=production" > .env.production
@@ -882,7 +882,7 @@ More examples
882
882
  </details>
883
883
  * <details><summary>`run --debug`</summary><br>
884
884
 
885
- Set log level to `debug`. ([log levels](https://github.com/winstonjs/winston?tab=readme-ov-file#logging))
885
+ Set log level to `debug`. ([log levels](https://docs.npmjs.com/cli/v8/using-npm/logging#setting-log-levels))
886
886
 
887
887
  ```sh
888
888
  $ echo "HELLO=production" > .env.production
@@ -904,7 +904,7 @@ More examples
904
904
  </details>
905
905
  * <details><summary>`run --quiet`</summary><br>
906
906
 
907
- Use `--quiet` to suppress all output (except errors). ([log levels](https://github.com/winstonjs/winston?tab=readme-ov-file#logging))
907
+ Use `--quiet` to suppress all output (except errors). ([log levels](https://docs.npmjs.com/cli/v8/using-npm/logging#setting-log-levels))
908
908
 
909
909
  ```sh
910
910
  $ echo "HELLO=production" > .env.production
@@ -927,7 +927,7 @@ More examples
927
927
  Hello production
928
928
  ```
929
929
 
930
- Available log levels are `error, warn, info, verbose, debug, silly` ([source](https://github.com/winstonjs/winston?tab=readme-ov-file#logging))
930
+ Available log levels are `error, warn, info, verbose, debug, silly` ([source](https://docs.npmjs.com/cli/v8/using-npm/logging#setting-log-levels))
931
931
 
932
932
  </details>
933
933
  * <details><summary>`run --convention=nextjs`</summary><br>
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.7.0",
2
+ "version": "1.9.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -36,18 +36,16 @@
36
36
  },
37
37
  "funding": "https://dotenvx.com",
38
38
  "dependencies": {
39
- "chalk": "^4.1.2",
40
39
  "commander": "^11.1.0",
41
40
  "diff": "^5.2.0",
42
41
  "dotenv": "^16.4.5",
43
42
  "eciesjs": "^0.4.6",
44
- "execa": "^5.1.1",
45
- "fdir": "^6.1.1",
43
+ "fdir": "^6.2.0",
46
44
  "ignore": "^5.3.0",
47
45
  "object-treeify": "1.1.33",
48
- "picomatch": "^3.0.1",
46
+ "picomatch": "^4.0.2",
47
+ "tinyexec": "^0.2.0",
49
48
  "which": "^4.0.0",
50
- "winston": "^3.11.0",
51
49
  "xxhashjs": "^0.2.2"
52
50
  },
53
51
  "devDependencies": {
@@ -13,7 +13,7 @@ function decrypt () {
13
13
  if (options.stdout) {
14
14
  const {
15
15
  processedEnvFiles
16
- } = main.decrypt(options.envFile, options.key)
16
+ } = main.decrypt(options.envFile, options.key, options.excludeKey)
17
17
 
18
18
  for (const processedEnvFile of processedEnvFiles) {
19
19
  process.stdout.write(processedEnvFile.envSrc)
@@ -25,7 +25,7 @@ function decrypt () {
25
25
  processedEnvFiles,
26
26
  changedFilepaths,
27
27
  unchangedFilepaths
28
- } = main.decrypt(options.envFile, options.key)
28
+ } = main.decrypt(options.envFile, options.key, options.excludeKey)
29
29
 
30
30
  for (const processedEnvFile of processedEnvFiles) {
31
31
  logger.verbose(`decrypting ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
@@ -15,7 +15,7 @@ function encrypt () {
15
15
  if (options.stdout) {
16
16
  const {
17
17
  processedEnvFiles
18
- } = main.encrypt(options.envFile, options.key)
18
+ } = main.encrypt(options.envFile, options.key, options.excludeKey)
19
19
 
20
20
  for (const processedEnvFile of processedEnvFiles) {
21
21
  process.stdout.write(processedEnvFile.envSrc)
@@ -27,7 +27,7 @@ function encrypt () {
27
27
  processedEnvFiles,
28
28
  changedFilepaths,
29
29
  unchangedFilepaths
30
- } = main.encrypt(options.envFile, options.key)
30
+ } = main.encrypt(options.envFile, options.key, options.excludeKey)
31
31
 
32
32
  for (const processedEnvFile of processedEnvFiles) {
33
33
  logger.verbose(`encrypting ${processedEnvFile.envFilepath} (${processedEnvFile.filepath})`)
@@ -20,9 +20,6 @@ function genexample (directory) {
20
20
 
21
21
  logger.verbose(`loading env from ${envFile}`)
22
22
 
23
- // TODO: display pre-existing
24
- // TODO: display added/appended/injected
25
-
26
23
  fs.writeFileSync(exampleFilepath, envExampleFile, ENCODING)
27
24
 
28
25
  if (addedKeys.length > 0) {
@@ -58,7 +58,7 @@ function status (directory) {
58
58
 
59
59
  if (untrackedFilenames.length > 0) {
60
60
  logger.warn(`untracked (${untrackedFilenames.join(', ')})`)
61
- logger.help(`? track them with [dotenvx encrypt ${directory}]`)
61
+ logger.help(`? track them with [dotenvx ext vault encrypt ${directory}]`)
62
62
  }
63
63
  } catch (error) {
64
64
  logger.error(error.message)
@@ -60,6 +60,15 @@ async function run () {
60
60
  logger.warnv(processedEnv.error.message)
61
61
  }
62
62
  } else {
63
+ if (processedEnv.warnings) {
64
+ for (const warning of processedEnv.warnings) {
65
+ logger.warn(warning.message)
66
+ if (warning.help) {
67
+ logger.help(warning.help)
68
+ }
69
+ }
70
+ }
71
+
63
72
  // debug parsed
64
73
  const parsed = processedEnv.parsed
65
74
  logger.debug(parsed)
@@ -9,8 +9,6 @@ ext
9
9
  .description('🔌 extensions')
10
10
  .allowUnknownOption()
11
11
 
12
- ext.addHelpText('after', ' hub 🚫 DEPRECATED: to be replaced by [dotenvx pro]')
13
-
14
12
  ext
15
13
  .argument('[command]', 'dynamic ext command')
16
14
  .argument('[args...]', 'dynamic ext command arguments')
@@ -100,6 +100,7 @@ program.command('encrypt')
100
100
  .description('convert .env file(s) to encrypted .env file(s)')
101
101
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
102
102
  .option('-k, --key <keys...>', 'keys(s) to encrypt (default: all keys in file)')
103
+ .option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from encryption (default: none)')
103
104
  .option('--stdout', 'send to stdout')
104
105
  .action(encryptAction)
105
106
 
@@ -109,6 +110,7 @@ program.command('decrypt')
109
110
  .description('convert encrypted .env file(s) to plain .env file(s)')
110
111
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)')
111
112
  .option('-k, --key <keys...>', 'keys(s) to decrypt (default: all keys in file)')
113
+ .option('-ek, --exclude-key <excludeKeys...>', 'keys(s) to exclude from decryption (default: none)')
112
114
  .option('--stdout', 'send to stdout')
113
115
  .action(decryptAction)
114
116
 
@@ -132,40 +134,8 @@ program.command('help [command]')
132
134
  program.addCommand(require('./commands/ext'))
133
135
 
134
136
  //
135
- // DEPRECATED or MOVED
137
+ // MOVED
136
138
  //
137
- const lsAction = require('./actions/ext/ls')
138
- program.command('ls')
139
- .description('DEPRECATED: moved to [dotenvx ext ls]')
140
- .argument('[directory]', 'directory to list .env files from', '.')
141
- .option('-f, --env-file <filenames...>', 'path(s) to your env file(s)', '.env*')
142
- .action(function (...args) {
143
- logger.warn('DEPRECATION NOTICE: [ls] has moved to [dotenvx ext ls]')
144
-
145
- lsAction.apply(this, args)
146
- })
147
-
148
- const genexampleAction = require('./actions/ext/genexample')
149
- program.command('genexample')
150
- .description('DEPRECATED: moved to [dotenvx ext genexample]')
151
- .argument('[directory]', 'directory to generate from', '.')
152
- .option('-f, --env-file <paths...>', 'path(s) to your env file(s)', '.env')
153
- .action(function (...args) {
154
- logger.warn('DEPRECATION NOTICE: [genexample] has moved to [dotenvx ext genexample]')
155
-
156
- genexampleAction.apply(this, args)
157
- })
158
-
159
- const gitignoreAction = require('./actions/ext/gitignore')
160
- program.command('gitignore')
161
- .description('DEPRECATED: moved to [dotenvx ext gitignore]')
162
- .addHelpText('after', examples.gitignore)
163
- .action(function (...args) {
164
- logger.warn('DEPRECATION NOTICE: [gitignore] has moved to [dotenvx ext gitignore]')
165
-
166
- gitignoreAction.apply(this, args)
167
- })
168
-
169
139
  const prebuildAction = require('./actions/ext/prebuild')
170
140
  program.command('prebuild')
171
141
  .description('DEPRECATED: moved to [dotenvx ext prebuild]')
@@ -187,15 +157,6 @@ program.command('precommit')
187
157
  precommitAction.apply(this, args)
188
158
  })
189
159
 
190
- const scanAction = require('./actions/ext/scan')
191
- program.command('scan')
192
- .description('DEPRECATED: moved to [dotenvx ext scan]')
193
- .action(function (...args) {
194
- logger.warn('DEPRECATION NOTICE: [scan] has moved to [dotenvx ext scan]')
195
-
196
- scanAction.apply(this, args)
197
- })
198
-
199
160
  // overide helpInformation to hide DEPRECATED commands
200
161
  program.helpInformation = function () {
201
162
  const originalHelp = Command.prototype.helpInformation.call(this)
@@ -0,0 +1,4 @@
1
+ const { WriteStream } = require('tty')
2
+ const getColorDepth = () => WriteStream.prototype.getColorDepth()
3
+
4
+ module.exports = { getColorDepth }
@@ -10,6 +10,7 @@ function decryptValue (value, privateKey) {
10
10
  const privateKeys = privateKey.split(',')
11
11
 
12
12
  let decryptedValue
13
+ let decryptionError
13
14
  for (const key of privateKeys) {
14
15
  const secret = Buffer.from(key, 'hex')
15
16
  const encoded = value.substring(PREFIX.length)
@@ -17,15 +18,25 @@ function decryptValue (value, privateKey) {
17
18
 
18
19
  try {
19
20
  decryptedValue = decrypt(secret, ciphertext).toString()
21
+ decryptionError = null // reset to null error (scenario for multiple private keys)
20
22
  break
21
- } catch (_error) {
22
- // TODO: somehow surface these errors to the user's logs
23
+ } catch (e) {
24
+ if (e.message === 'Invalid private key') {
25
+ decryptionError = new Error('private key looks invalid')
26
+ } else if (e.message === 'Unsupported state or unable to authenticate data') {
27
+ decryptionError = new Error('private key looks wrong')
28
+ } else if (e.message === 'Point of length 65 was invalid. Expected 33 compressed bytes or 65 uncompressed bytes') {
29
+ decryptionError = new Error('encrypted data looks malformed')
30
+ } else {
31
+ decryptionError = new Error(`${e.message}`)
32
+ }
33
+
34
+ decryptionError.code = 'DECRYPTION_FAILED'
23
35
  }
24
36
  }
25
37
 
26
- // return 'encrypted:string' value if undefined or null
27
- if (decryptedValue === undefined || decryptedValue === null) {
28
- return value
38
+ if (decryptionError) {
39
+ throw decryptionError
29
40
  }
30
41
 
31
42
  return decryptedValue
@@ -42,30 +42,27 @@ function interpolate (value, lookups) {
42
42
  }
43
43
 
44
44
  function expand (options) {
45
- let processEnv = process.env
46
- if (options && options.processEnv != null) {
47
- processEnv = options.processEnv
48
- }
45
+ const processEnv = options.processEnv || {}
46
+ const parsed = options.parsed || {}
49
47
 
50
- const combined = { ...processEnv, ...options.parsed }
51
- const combinedReversed = { ...options.parsed, ...processEnv }
48
+ const combined = { ...processEnv, ...parsed }
49
+ const combinedReversed = { ...parsed, ...processEnv }
52
50
 
53
- for (const key in options.parsed) {
54
- const value = options.parsed[key]
51
+ for (const key in parsed) {
52
+ const value = parsed[key]
55
53
 
56
54
  // interpolate using both file and processEnv (file interpolation wins. used for --overload later)
57
55
  const fileValue = _resolveEscapeSequences(interpolate(value, combined))
58
- options.parsed[key] = fileValue
56
+ parsed[key] = fileValue
59
57
 
60
58
  if (fileValue === _resolveEscapeSequences(value)) {
61
59
  continue // no change means no expansion, move on
62
60
  }
63
61
 
64
62
  if (processEnv[key]) {
65
- continue // already has a value in process.env, move on
63
+ continue // already has a value in processEnv, move on
66
64
  }
67
65
 
68
- // interpolate with processEnv only (used for default no overload)
69
66
  const processEnvValue = interpolate(value, combinedReversed) // could be empty string ''
70
67
  if (processEnvValue) {
71
68
  processEnv[key] = _resolveEscapeSequences(processEnvValue) // set it
@@ -73,7 +70,7 @@ function expand (options) {
73
70
  }
74
71
 
75
72
  return {
76
- parsed: options.parsed,
73
+ parsed,
77
74
  processEnv
78
75
  }
79
76
  }
@@ -1,9 +1,3 @@
1
- const execa = require('execa')
1
+ const { exec } = require('tinyexec')
2
2
 
3
- const execute = {
4
- execa (command, args, options) {
5
- return execa(command, args, options)
6
- }
7
- }
8
-
9
- module.exports = execute
3
+ module.exports = { exec }
@@ -13,6 +13,8 @@ async function executeCommand (commandArgs, env) {
13
13
 
14
14
  // handler for SIGINT
15
15
  let commandProcess
16
+ // workaround until error.signal gets added https://github.com/tinylibs/tinyexec/issues/28
17
+ let signal
16
18
  const sigintHandler = () => {
17
19
  logger.debug('received SIGINT')
18
20
  logger.debug('checking command process')
@@ -20,6 +22,7 @@ async function executeCommand (commandArgs, env) {
20
22
 
21
23
  if (commandProcess) {
22
24
  logger.debug('sending SIGINT to command process')
25
+ signal = 'SIGINT'
23
26
  commandProcess.kill('SIGINT') // Send SIGINT to the command process
24
27
  /* c8 ignore start */
25
28
  } else {
@@ -37,6 +40,7 @@ async function executeCommand (commandArgs, env) {
37
40
 
38
41
  if (commandProcess) {
39
42
  logger.debug('sending SIGTERM to command process')
43
+ signal = 'SIGTERM'
40
44
  commandProcess.kill('SIGTERM') // Send SIGTEM to the command process
41
45
  } else {
42
46
  logger.debug('no command process to send SIGTERM to')
@@ -73,9 +77,11 @@ async function executeCommand (commandArgs, env) {
73
77
  }
74
78
  }
75
79
 
76
- commandProcess = execute.execa(commandArgs[0], commandArgs.slice(1), {
77
- stdio: 'inherit',
78
- env: { ...process.env, ...env }
80
+ commandProcess = execute.exec(commandArgs[0], commandArgs.slice(1), {
81
+ nodeOptions: {
82
+ stdio: 'inherit',
83
+ env: { ...process.env, ...env }
84
+ }
79
85
  })
80
86
 
81
87
  process.on('SIGINT', sigintHandler)
@@ -86,7 +92,9 @@ async function executeCommand (commandArgs, env) {
86
92
  })
87
93
 
88
94
  // Wait for the command process to finish
89
- const { exitCode } = await commandProcess
95
+ // exitCode is not in the awaited result, see https://github.com/tinylibs/tinyexec/issues/27
96
+ await commandProcess
97
+ const { exitCode } = commandProcess
90
98
 
91
99
  if (exitCode !== 0) {
92
100
  logger.debug(`received exitCode ${exitCode}`)
@@ -94,11 +102,11 @@ async function executeCommand (commandArgs, env) {
94
102
  }
95
103
  } catch (error) {
96
104
  // no color on these errors as they can be standard errors for things like jest exiting with exitCode 1 for a single failed test.
97
- if (error.signal !== 'SIGINT' && error.signal !== 'SIGTERM') {
105
+ if (signal !== 'SIGINT' && signal !== 'SIGTERM') {
98
106
  if (error.code === 'ENOENT') {
99
- logger.errornocolor(`Unknown command: ${error.command}`)
107
+ logger.errornocolor(`Unknown command: ${error.path}${error.spawnargs ? ' ' + error.spawnargs.join(' ') : ''}`)
100
108
  } else if (error.message.includes('Command failed with exit code 1')) {
101
- logger.errornocolor(`Command exited with exit code 1: ${error.command}`)
109
+ logger.errornocolor(`Command exited with exit code 1: ${error.path}${error.spawnargs ? ' ' + error.spawnargs.join(' ') : ''}`)
102
110
  } else {
103
111
  logger.errornocolor(error.message)
104
112
  }
@@ -22,9 +22,10 @@ function executeExtension (ext, command, rawArgs) {
22
22
 
23
23
  const result = childProcess.spawnSync(`dotenvx-ext-${command}`, forwardedArgs, { stdio: 'inherit', env })
24
24
  if (result.error) {
25
- if (command === 'hub') {
26
- logger.warn(`[INSTALLATION_NEEDED] install dotenvx-ext-${command} to use [dotenvx ext ${command}] commands`)
27
- logger.help('? see installation instructions [https://github.com/dotenvx/dotenvx-ext-hub]')
25
+ if (command === 'vault') {
26
+ // when ready, uncomment to deprecate ext vault
27
+ // logger.warn(`[INSTALLATION_NEEDED] install dotenvx-ext-${command} to use [dotenvx ext ${command}] commands`)
28
+ // logger.help('? see installation instructions [https://github.com/dotenvx/dotenvx-ext-vault]')
28
29
  } else {
29
30
  logger.info(`error: unknown command '${command}'`)
30
31
  }
@@ -1,16 +1,16 @@
1
- function inject (processEnv = {}, parsed = {}, overload = false) {
1
+ function inject (clonedProcessEnv = {}, parsed = {}, overload = false, processEnv = process.env) {
2
2
  const injected = {}
3
3
  const preExisted = {}
4
4
 
5
5
  // set processEnv
6
6
  for (const key of Object.keys(parsed)) {
7
- if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
7
+ if (Object.prototype.hasOwnProperty.call(clonedProcessEnv, key)) {
8
8
  if (overload === true) {
9
9
  processEnv[key] = parsed[key]
10
-
11
10
  injected[key] = parsed[key] // track injected key/value
12
11
  } else {
13
- preExisted[key] = processEnv[key] // track preExisted key/value
12
+ processEnv[key] = clonedProcessEnv[key]
13
+ preExisted[key] = clonedProcessEnv[key] // track preExisted key/value
14
14
  }
15
15
  } else {
16
16
  processEnv[key] = parsed[key]
@@ -1,8 +1,3 @@
1
- const fs = require('fs')
2
- const path = require('path')
1
+ const { name, version, description } = require('../../../package.json')
3
2
 
4
- const packageJsonPath = path.join(__dirname, '../../../package.json')
5
-
6
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
7
-
8
- module.exports = packageJson
3
+ module.exports = { name, version, description }
@@ -2,16 +2,29 @@ const dotenv = require('dotenv')
2
2
  const dotenvEval = require('./dotenvEval')
3
3
  const dotenvExpand = require('./dotenvExpand')
4
4
  const decryptValue = require('./decryptValue')
5
+ const truncate = require('./truncate')
6
+
7
+ function warning (e, key, privateKey) {
8
+ const warning = new Error(`[${e.code}] could not decrypt ${key} using private key ${truncate(privateKey)}`)
9
+ warning.code = e.code
10
+ warning.help = `[${e.code}] ? ${e.message}`
11
+
12
+ return warning
13
+ }
5
14
 
6
15
  function parseDecryptEvalExpand (src, privateKey = null, processEnv = process.env) {
16
+ const warnings = []
17
+
7
18
  // parse
8
19
  const parsed = dotenv.parse(src)
9
-
10
- // handle inline encrypted values
11
20
  if (privateKey && privateKey.length > 0) {
12
21
  for (const key in parsed) {
13
- const value = parsed[key]
14
- parsed[key] = decryptValue(value, privateKey)
22
+ try {
23
+ const decryptedValue = decryptValue(parsed[key], privateKey)
24
+ parsed[key] = decryptedValue
25
+ } catch (_e) {
26
+ // do nothing. warnings tracked further below.
27
+ }
15
28
  }
16
29
  }
17
30
 
@@ -28,6 +41,24 @@ function parseDecryptEvalExpand (src, privateKey = null, processEnv = process.en
28
41
  parsed: evaled
29
42
  }
30
43
  const expanded = dotenvExpand.expand(inputEvaled)
44
+ if (privateKey && privateKey.length > 0) {
45
+ for (const key in expanded.parsed) {
46
+ try {
47
+ const decryptedValue = decryptValue(expanded.parsed[key], privateKey)
48
+ expanded.parsed[key] = decryptedValue
49
+ } catch (e) {
50
+ warnings.push(warning(e, key, privateKey))
51
+ }
52
+ }
53
+ for (const key in processEnv) {
54
+ try {
55
+ const decryptedValue = decryptValue(processEnv[key], privateKey)
56
+ processEnv[key] = decryptedValue
57
+ } catch (e) {
58
+ warnings.push(warning(e, key, privateKey))
59
+ }
60
+ }
61
+ }
31
62
 
32
63
  // 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
33
64
  const result = {}
@@ -35,7 +66,7 @@ function parseDecryptEvalExpand (src, privateKey = null, processEnv = process.en
35
66
  result[key] = expanded.parsed[key]
36
67
  }
37
68
 
38
- return { parsed: result, processEnv }
69
+ return { parsed: result, processEnv, warnings }
39
70
  }
40
71
 
41
72
  module.exports = parseDecryptEvalExpand
@@ -0,0 +1,6 @@
1
+ function truncate (str, showChar = 7) {
2
+ const visiblePart = str.slice(0, showChar)
3
+ return visiblePart + '…'
4
+ }
5
+
6
+ module.exports = truncate
package/src/lib/main.js CHANGED
@@ -189,13 +189,13 @@ const set = function (key, value, envFile, encrypt) {
189
189
  }
190
190
 
191
191
  /** @type {import('./main').encrypt} */
192
- const encrypt = function (envFile, key) {
193
- return new Encrypt(envFile, key).run()
192
+ const encrypt = function (envFile, key, excludeKey) {
193
+ return new Encrypt(envFile, key, excludeKey).run()
194
194
  }
195
195
 
196
196
  /** @type {import('./main').encrypt} */
197
- const decrypt = function (envFile, key) {
198
- return new Decrypt(envFile, key).run()
197
+ const decrypt = function (envFile, key, excludeKey) {
198
+ return new Decrypt(envFile, key, excludeKey).run()
199
199
  }
200
200
 
201
201
  /** @type {import('./main').status} */
@@ -14,10 +14,12 @@ class Decrypt {
14
14
  /**
15
15
  * @param {string|string[]} [envFile]
16
16
  * @param {string|string[]} [key]
17
+ * @param {string|string[]} [excludeKey]
17
18
  **/
18
- constructor (envFile = '.env', key = []) {
19
+ constructor (envFile = '.env', key = [], excludeKey = []) {
19
20
  this.envFile = envFile
20
21
  this.key = key
22
+ this.excludeKey = excludeKey
21
23
  this.processedEnvFiles = []
22
24
  this.changedFilepaths = new Set()
23
25
  this.unchangedFilepaths = new Set()
@@ -26,6 +28,7 @@ class Decrypt {
26
28
  run () {
27
29
  const envFilepaths = this._envFilepaths()
28
30
  const keys = this._keys()
31
+ const excludeKeys = this._excludeKeys()
29
32
  for (const envFilepath of envFilepaths) {
30
33
  const filepath = path.resolve(envFilepath)
31
34
 
@@ -49,17 +52,25 @@ class Decrypt {
49
52
  // iterate over all non-encrypted values and encrypt them
50
53
  const parsed = dotenv.parse(src)
51
54
  for (const [key, value] of Object.entries(parsed)) {
52
- if (keys.length < 1 || keys.includes(key)) { // optionally control which key to decrypt
53
- const encrypted = isEncrypted(key, value)
54
- if (encrypted) {
55
- row.keys.push(key) // track key(s)
55
+ // key excluded - don't decrypt it
56
+ if (excludeKeys.includes(key)) {
57
+ continue
58
+ }
59
+
60
+ // key effectively excluded (by not being in the list of includes) - don't encrypt it
61
+ if (keys.length > 0 && !keys.includes(key)) {
62
+ continue
63
+ }
64
+
65
+ const encrypted = isEncrypted(key, value)
66
+ if (encrypted) {
67
+ row.keys.push(key) // track key(s)
56
68
 
57
- const plainValue = decryptValue(value, privateKey)
58
- // once newSrc is built write it out
59
- src = replace(src, key, plainValue)
69
+ const decryptedValue = decryptValue(value, privateKey)
70
+ // once newSrc is built write it out
71
+ src = replace(src, key, decryptedValue)
60
72
 
61
- row.changed = true // track change
62
- }
73
+ row.changed = true // track change
63
74
  }
64
75
  }
65
76
 
@@ -106,6 +117,14 @@ class Decrypt {
106
117
 
107
118
  return this.key
108
119
  }
120
+
121
+ _excludeKeys () {
122
+ if (!Array.isArray(this.excludeKey)) {
123
+ return [this.excludeKey]
124
+ }
125
+
126
+ return this.excludeKey
127
+ }
109
128
  }
110
129
 
111
130
  module.exports = Decrypt
@@ -15,10 +15,12 @@ class Encrypt {
15
15
  /**
16
16
  * @param {string|string[]} [envFile]
17
17
  * @param {string|string[]} [key]
18
+ * @param {string|string[]} [excludeKey]
18
19
  **/
19
- constructor (envFile = '.env', key = []) {
20
+ constructor (envFile = '.env', key = [], excludeKey = []) {
20
21
  this.envFile = envFile
21
22
  this.key = key
23
+ this.excludeKey = excludeKey
22
24
  this.processedEnvFiles = []
23
25
  this.changedFilepaths = new Set()
24
26
  this.unchangedFilepaths = new Set()
@@ -27,6 +29,7 @@ class Encrypt {
27
29
  run () {
28
30
  const envFilepaths = this._envFilepaths()
29
31
  const keys = this._keys()
32
+ const excludeKeys = this._excludeKeys()
30
33
  for (const envFilepath of envFilepaths) {
31
34
  const filepath = path.resolve(envFilepath)
32
35
 
@@ -63,19 +66,25 @@ class Encrypt {
63
66
  // iterate over all non-encrypted values and encrypt them
64
67
  const parsed = dotenv.parse(src)
65
68
  for (const [key, value] of Object.entries(parsed)) {
66
- if (keys.length < 1 || keys.includes(key)) {
67
- // optionally control which key to encrypt
68
- const encrypted =
69
- isEncrypted(key, value) || isPublicKey(key, value)
70
- if (!encrypted) {
71
- row.keys.push(key) // track key(s)
72
-
73
- const encryptedValue = encryptValue(value, publicKey)
74
- // once newSrc is built write it out
75
- src = replace(src, key, encryptedValue)
76
-
77
- row.changed = true // track change
78
- }
69
+ // key excluded - don't encrypt it
70
+ if (excludeKeys.includes(key)) {
71
+ continue
72
+ }
73
+
74
+ // key effectively excluded (by not being in the list of includes) - don't encrypt it
75
+ if (keys.length > 0 && !keys.includes(key)) {
76
+ continue
77
+ }
78
+
79
+ const encrypted = isEncrypted(key, value) || isPublicKey(key, value)
80
+ if (!encrypted) {
81
+ row.keys.push(key) // track key(s)
82
+
83
+ const encryptedValue = encryptValue(value, publicKey)
84
+ // once newSrc is built write it out
85
+ src = replace(src, key, encryptedValue)
86
+
87
+ row.changed = true // track change
79
88
  }
80
89
  }
81
90
 
@@ -122,6 +131,14 @@ class Encrypt {
122
131
 
123
132
  return this.key
124
133
  }
134
+
135
+ _excludeKeys () {
136
+ if (!Array.isArray(this.excludeKey)) {
137
+ return [this.excludeKey]
138
+ }
139
+
140
+ return this.excludeKey
141
+ }
125
142
  }
126
143
 
127
144
  module.exports = Encrypt
@@ -5,11 +5,15 @@ const dotenv = require('dotenv')
5
5
  const ENCODING = 'utf8'
6
6
 
7
7
  const findEnvFiles = require('../helpers/findEnvFiles')
8
+ const replace = require('../helpers/replace')
8
9
 
9
10
  class Genexample {
10
11
  constructor (directory = '.', envFile) {
11
12
  this.directory = directory
12
13
  this.envFile = envFile || findEnvFiles(directory)
14
+
15
+ this.exampleFilename = '.env.example'
16
+ this.exampleFilepath = path.resolve(this.directory, this.exampleFilename)
13
17
  }
14
18
 
15
19
  run () {
@@ -27,6 +31,12 @@ class Genexample {
27
31
  const keys = new Set()
28
32
  const addedKeys = new Set()
29
33
  const envFilepaths = this._envFilepaths()
34
+ /** @type {Record<string, string>} */
35
+ const injected = {}
36
+ /** @type {Record<string, string>} */
37
+ const preExisted = {}
38
+
39
+ let exampleSrc = `# ${this.exampleFilename} - generated with dotenvx\n`
30
40
 
31
41
  for (const envFilepath of envFilepaths) {
32
42
  const filepath = path.resolve(this.directory, envFilepath)
@@ -41,43 +51,51 @@ class Genexample {
41
51
  throw error
42
52
  }
43
53
 
44
- const parsed = dotenv.configDotenv({ path: filepath }).parsed
45
- for (const key of Object.keys(parsed)) {
54
+ // get the original src
55
+ let src = fs.readFileSync(filepath, { encoding: ENCODING })
56
+ const parsed = dotenv.parse(src)
57
+ for (const key in parsed) {
58
+ // used later
46
59
  keys.add(key)
60
+
61
+ // once newSrc is built write it out
62
+ src = replace(src, key, '') // empty value
47
63
  }
48
- }
49
64
 
50
- let envExampleFile = ''
51
- const exampleFilename = '.env.example'
52
- const exampleFilepath = path.resolve(this.directory, exampleFilename)
53
- if (!fs.existsSync(exampleFilepath)) {
54
- envExampleFile += `# ${exampleFilename} - generated with dotenvx\n`
55
- } else {
56
- envExampleFile = fs.readFileSync(exampleFilepath, ENCODING)
65
+ exampleSrc += `\n${src}`
57
66
  }
58
67
 
59
- const currentEnvExample = dotenv.configDotenv({ path: exampleFilepath }).parsed
60
- /** @type {Record<string, string>} */
61
- const injected = {}
62
- /** @type {Record<string, string>} */
63
- const preExisted = {}
64
-
65
- for (const key of [...keys]) {
66
- if (key in currentEnvExample) {
67
- preExisted[key] = currentEnvExample[key]
68
- } else {
69
- envExampleFile += `${key}=""\n`
70
-
68
+ if (!fs.existsSync(this.exampleFilepath)) {
69
+ // it doesn't exist so just write this first generated one
70
+ // exampleSrc - already written to from the prior loop
71
+ for (const key of [...keys]) {
72
+ // every key is added since it's the first time generating .env.example
71
73
  addedKeys.add(key)
72
74
 
73
75
  injected[key] = ''
74
76
  }
77
+ } else {
78
+ // it already exists (which means the user might have it modified a way in which they prefer, so replace exampleSrc with their existing .env.example)
79
+ exampleSrc = fs.readFileSync(this.exampleFilepath, ENCODING)
80
+
81
+ const parsed = dotenv.parse(exampleSrc)
82
+ for (const key of [...keys]) {
83
+ if (key in parsed) {
84
+ preExisted[key] = parsed[key]
85
+ } else {
86
+ exampleSrc += `${key}=""\n`
87
+
88
+ addedKeys.add(key)
89
+
90
+ injected[key] = ''
91
+ }
92
+ }
75
93
  }
76
94
 
77
95
  return {
78
- envExampleFile,
96
+ envExampleFile: exampleSrc,
79
97
  envFile: this.envFile,
80
- exampleFilepath,
98
+ exampleFilepath: this.exampleFilepath,
81
99
  addedKeys: [...addedKeys],
82
100
  injected,
83
101
  preExisted
@@ -15,15 +15,17 @@ class Ls {
15
15
  }
16
16
 
17
17
  _filepaths () {
18
- const ignoreMatchers = this.ignore.map(pattern => picomatch(pattern))
19
- const pathMatchers = this._patterns().map(pattern => picomatch(pattern))
18
+ const exclude = picomatch(this.ignore)
19
+ const include = picomatch(this._patterns(), {
20
+ ignore: this.ignore
21
+ })
20
22
 
21
- const api = new Fdir()
23
+ return new Fdir()
22
24
  .withRelativePaths()
23
- .exclude((dir, path) => ignoreMatchers.some(matcher => matcher(path)))
24
- .filter((path) => pathMatchers.some(matcher => matcher(path)))
25
-
26
- return api.crawl(this.cwd).sync()
25
+ .exclude((dir, path) => exclude(path))
26
+ .filter((path) => include(path))
27
+ .crawl(this.cwd)
28
+ .sync()
27
29
  }
28
30
 
29
31
  _patterns () {
@@ -63,12 +63,12 @@ class Run {
63
63
  row.string = env
64
64
 
65
65
  try {
66
- const { parsed } = parseDecryptEvalExpand(env, null, this.processEnv)
67
-
66
+ const { parsed, processEnv, warnings } = parseDecryptEvalExpand(env, null, this.processEnv)
68
67
  row.parsed = parsed
68
+ row.warnings = warnings
69
69
  this.readableStrings.add(env)
70
70
 
71
- const { injected, preExisted } = this._inject(this.processEnv, parsed, this.overload)
71
+ const { injected, preExisted } = this._inject(processEnv, parsed, this.overload, this.processEnv)
72
72
  row.injected = injected
73
73
  row.preExisted = preExisted
74
74
 
@@ -94,10 +94,11 @@ class Run {
94
94
 
95
95
  // if DOTENV_PRIVATE_KEY_* already set in process.env then use it
96
96
  const privateKey = smartDotenvPrivateKey(envFilepath)
97
- const { parsed } = parseDecryptEvalExpand(src, privateKey, this.processEnv)
97
+ const { parsed, processEnv, warnings } = parseDecryptEvalExpand(src, privateKey, this.processEnv)
98
98
  row.parsed = parsed
99
+ row.warnings = warnings
99
100
 
100
- const { injected, preExisted } = this._inject(this.processEnv, parsed, this.overload)
101
+ const { injected, preExisted } = this._inject(processEnv, parsed, this.overload, this.processEnv)
101
102
  row.injected = injected
102
103
  row.preExisted = preExisted
103
104
 
@@ -163,10 +164,11 @@ class Run {
163
164
 
164
165
  try {
165
166
  // parse this. it's the equivalent of the .env file
166
- const { parsed } = parseDecryptEvalExpand(decrypted, null, this.processEnv)
167
+ const { parsed, processEnv, warnings } = parseDecryptEvalExpand(decrypted, null, this.processEnv)
167
168
  row.parsed = parsed
169
+ row.warnings = warnings
168
170
 
169
- const { injected, preExisted } = this._inject(this.processEnv, parsed, this.overload)
171
+ const { injected, preExisted } = this._inject(processEnv, parsed, this.overload, this.processEnv)
170
172
  row.injected = injected
171
173
  row.preExisted = preExisted
172
174
 
@@ -180,8 +182,8 @@ class Run {
180
182
  this.processedEnvs.push(row)
181
183
  }
182
184
 
183
- _inject (processEnv, parsed, overload) {
184
- return inject(processEnv, parsed, overload)
185
+ _inject (clonedProcessEnv, parsed, overload, processEnv) {
186
+ return inject(clonedProcessEnv, parsed, overload, processEnv)
185
187
  }
186
188
 
187
189
  _determineEnvsFromDotenvPrivateKey () {
@@ -1,13 +1,13 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const diff = require('diff')
4
- const chalk = require('chalk')
5
4
 
6
5
  const Ls = require('./ls')
7
6
  const VaultDecrypt = require('./vaultDecrypt')
8
7
 
9
8
  const containsDirectory = require('./../helpers/containsDirectory')
10
9
  const guessEnvironment = require('./../helpers/guessEnvironment')
10
+ const { getColor } = require('./../../shared/colors')
11
11
 
12
12
  const ENCODING = 'utf8'
13
13
 
@@ -94,12 +94,12 @@ class Status {
94
94
  _colorizeDiff (part) {
95
95
  // If the part was added, color it green
96
96
  if (part.added) {
97
- return chalk.green(part.value)
97
+ return getColor('green')(part.value)
98
98
  }
99
99
 
100
100
  // If the part was removed, color it red
101
101
  if (part.removed) {
102
- return chalk.red(part.value)
102
+ return getColor('red')(part.value)
103
103
  }
104
104
 
105
105
  // No color for unchanged parts
@@ -23,7 +23,7 @@ class VaultDecrypt {
23
23
  if (!fs.existsSync(this.envVaultFilepath)) {
24
24
  const code = 'MISSING_ENV_VAULT_FILE'
25
25
  const message = `missing .env.vault (${this.envVaultFilepath})`
26
- const help = `? generate one with [dotenvx encrypt ${this.directory}]`
26
+ const help = `? generate one with [dotenvx ext vault encrypt ${this.directory}]`
27
27
 
28
28
  const error = new Error(message)
29
29
  error.code = code
@@ -0,0 +1,50 @@
1
+ const depth = require('../lib/helpers/colorDepth')
2
+
3
+ const colors16 = new Map([
4
+ ['blue', 34],
5
+ ['gray', 37],
6
+ ['green', 32],
7
+ ['olive', 33],
8
+ ['orangered', 31], // mapped to red
9
+ ['plum', 35], // mapped to magenta
10
+ ['red', 31]
11
+ ])
12
+
13
+ const colors256 = new Map([
14
+ ['blue', 21],
15
+ ['gray', 244],
16
+ ['green', 34],
17
+ ['olive', 142],
18
+ ['orangered', 202],
19
+ ['plum', 182],
20
+ ['red', 196]
21
+ ])
22
+
23
+ function getColor (color) {
24
+ const colorDepth = depth.getColorDepth()
25
+ if (!colors256.has(color)) {
26
+ throw new Error(`Invalid color ${color}`)
27
+ }
28
+ if (colorDepth >= 8) {
29
+ const code = colors256.get(color)
30
+ return (message) => `\x1b[38;5;${code}m${message}\x1b[39m`
31
+ }
32
+ if (colorDepth >= 4) {
33
+ const code = colors16.get(color)
34
+ return (message) => `\x1b[${code}m${message}\x1b[39m`
35
+ }
36
+ return (message) => message
37
+ }
38
+
39
+ function bold (message) {
40
+ if (depth.getColorDepth() >= 4) {
41
+ return `\x1b[1m${message}\x1b[22m`
42
+ }
43
+
44
+ return message
45
+ }
46
+
47
+ module.exports = {
48
+ getColor,
49
+ bold
50
+ }
@@ -1,12 +1,5 @@
1
- const winston = require('winston')
2
- const chalk = require('chalk')
3
-
4
- const printf = winston.format.printf
5
- const combine = winston.format.combine
6
- const createLogger = winston.createLogger
7
- const transports = winston.transports
8
-
9
- const packageJson = require('./../lib/helpers/packageJson')
1
+ const packageJson = require('../lib/helpers/packageJson')
2
+ const { getColor, bold } = require('./colors')
10
3
 
11
4
  const levels = {
12
5
  error: 0,
@@ -26,26 +19,38 @@ const levels = {
26
19
  help: 2,
27
20
  help2: 2,
28
21
  blank: 2,
29
- http: 3,
30
22
  verbose: 4,
31
23
  debug: 5,
32
24
  silly: 6
33
25
  }
34
26
 
35
- const error = chalk.bold.red
36
- const warn = chalk.keyword('orangered')
37
- const success = chalk.keyword('green')
38
- const successv = chalk.keyword('olive') // yellow-ish tint that 'looks' like dotenv
39
- const help = chalk.keyword('blue')
40
- const help2 = chalk.keyword('gray')
41
- const http = chalk.keyword('green')
42
- const verbose = chalk.keyword('plum')
43
- const debug = chalk.keyword('plum')
27
+ const error = (m) => bold(getColor('red')(m))
28
+ const warn = getColor('orangered')
29
+ const success = getColor('green')
30
+ const successv = getColor('olive') // yellow-ish tint that 'looks' like dotenv
31
+ const help = getColor('blue')
32
+ const help2 = getColor('gray')
33
+ const verbose = getColor('plum')
34
+ const debug = getColor('plum')
35
+
36
+ let currentLevel = levels.info // default log level
37
+
38
+ function log (level, message) {
39
+ if (levels[level] === undefined) {
40
+ throw new Error(`MISSING_LOG_LEVEL: '${level}'. implement in logger.`)
41
+ }
42
+
43
+ if (levels[level] <= currentLevel) {
44
+ const formattedMessage = formatMessage(level, message)
45
+ console.log(formattedMessage)
46
+ }
47
+ }
44
48
 
45
- const dotenvxFormat = printf(({ level, message, label, timestamp }) => {
49
+ function formatMessage (level, message) {
46
50
  const formattedMessage = typeof message === 'object' ? JSON.stringify(message) : message
47
51
 
48
52
  switch (level.toLowerCase()) {
53
+ // errors
49
54
  case 'error':
50
55
  return error(formattedMessage)
51
56
  case 'errorv':
@@ -56,6 +61,7 @@ const dotenvxFormat = printf(({ level, message, label, timestamp }) => {
56
61
  return error(`[dotenvx@${packageJson.version}][prebuild] ${formattedMessage}`)
57
62
  case 'errornocolor':
58
63
  return formattedMessage
64
+ // warns
59
65
  case 'warn':
60
66
  return warn(formattedMessage)
61
67
  case 'warnv':
@@ -64,6 +70,7 @@ const dotenvxFormat = printf(({ level, message, label, timestamp }) => {
64
70
  return warn(`[dotenvx@${packageJson.version}][precommit] ${formattedMessage}`)
65
71
  case 'warnvpb':
66
72
  return warn(`[dotenvx@${packageJson.version}][prebuild] ${formattedMessage}`)
73
+ // successes
67
74
  case 'success':
68
75
  return success(formattedMessage)
69
76
  case 'successv': // success with 'version'
@@ -72,35 +79,66 @@ const dotenvxFormat = printf(({ level, message, label, timestamp }) => {
72
79
  return success(`[dotenvx@${packageJson.version}][precommit] ${formattedMessage}`)
73
80
  case 'successvpb': // success with 'version' and precommit
74
81
  return success(`[dotenvx@${packageJson.version}][prebuild] ${formattedMessage}`)
82
+ // info
75
83
  case 'info':
76
84
  return formattedMessage
85
+ // help
77
86
  case 'help':
78
87
  return help(formattedMessage)
79
88
  case 'help2':
80
89
  return help2(formattedMessage)
81
- case 'http':
82
- return http(formattedMessage)
90
+ // verbose
83
91
  case 'verbose':
84
92
  return verbose(formattedMessage)
93
+ // debug
85
94
  case 'debug':
86
95
  return debug(formattedMessage)
96
+ // blank
87
97
  case 'blank': // custom
88
98
  return formattedMessage
89
99
  }
90
- })
100
+ }
91
101
 
92
- const logger = createLogger({
102
+ const logger = {
103
+ // track level
93
104
  level: 'info',
94
- levels,
95
- format: combine(
96
- dotenvxFormat
97
- ),
98
- transports: [
99
- new transports.Console()
100
- ]
101
- })
102
105
 
103
- const setLogLevel = options => {
106
+ // errors
107
+ error: (msg) => log('error', msg),
108
+ errorv: (msg) => log('errorv', msg),
109
+ errorvp: (msg) => log('errorvp', msg),
110
+ errorvpb: (msg) => log('errorvpb', msg),
111
+ errornocolor: (msg) => log('errornocolor', msg),
112
+ // warns
113
+ warn: (msg) => log('warn', msg),
114
+ warnv: (msg) => log('warnv', msg),
115
+ warnvp: (msg) => log('warnvp', msg),
116
+ warnvpb: (msg) => log('warnvpb', msg),
117
+ // success
118
+ success: (msg) => log('success', msg),
119
+ successv: (msg) => log('successv', msg),
120
+ successvp: (msg) => log('successvp', msg),
121
+ successvpb: (msg) => log('successvpb', msg),
122
+ // info
123
+ info: (msg) => log('info', msg),
124
+ // help
125
+ help: (msg) => log('help', msg),
126
+ help2: (msg) => log('help2', msg),
127
+ // verbose
128
+ verbose: (msg) => log('verbose', msg),
129
+ // debug
130
+ debug: (msg) => log('debug', msg),
131
+ // blank
132
+ blank: (msg) => log('blank', msg),
133
+ setLevel: (level) => {
134
+ if (levels[level] !== undefined) {
135
+ currentLevel = levels[level]
136
+ logger.level = level
137
+ }
138
+ }
139
+ }
140
+
141
+ function setLogLevel (options) {
104
142
  const logLevel = options.debug
105
143
  ? 'debug'
106
144
  : options.verbose
@@ -110,7 +148,7 @@ const setLogLevel = options => {
110
148
  : options.logLevel
111
149
 
112
150
  if (!logLevel) return
113
- logger.level = logLevel
151
+ logger.setLevel(logLevel)
114
152
  // Only log which level it's setting if it's not set to quiet mode
115
153
  if (!options.quiet || (options.quiet && logLevel !== 'error')) {
116
154
  logger.debug(`Setting log level to ${logLevel}`)
@@ -119,5 +157,7 @@ const setLogLevel = options => {
119
157
 
120
158
  module.exports = {
121
159
  logger,
122
- setLogLevel
160
+ getColor,
161
+ setLogLevel,
162
+ levels
123
163
  }