@dotenvx/dotenvx 1.6.5 → 1.8.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,30 @@
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.6.5...main)
5
+ ## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.8.0...main)
6
+
7
+ ### Added
8
+
9
+ * warn when decryption fails on `run` or `get` ([#339](https://github.com/dotenvx/dotenvx/pull/339))
10
+ * decrypt expanded values as necessary ([#336](https://github.com/dotenvx/dotenvx/pull/336))
11
+
12
+ ### Changed
13
+
14
+ * use `ansi` colors over `rgb` - for wider terminal coverage ([#340](https://github.com/dotenvx/dotenvx/pull/340))
15
+ * replace `chalk` with `picocolors` and `color-name` - cutting down on 5 dependencies ([#335](https://github.com/dotenvx/dotenvx/pull/335))
16
+ * replace `execa` with `tinyexec` - cutting down on 15 dependencies ([#328](https://github.com/dotenvx/dotenvx/pull/328))
17
+ * optimize `Ls._filepaths` ([#317](https://github.com/dotenvx/dotenvx/pull/317/))
18
+
19
+ ### Removed
20
+
21
+ * remove `picocolors` and `color-name` - cutting down on 2 dependencies ([#340](https://github.com/dotenvx/dotenvx/pull/340))
22
+ * 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))
23
+
24
+ ## 1.7.0
25
+
26
+ ### Removed
27
+
28
+ * remove `ext settings` command (and [`conf`](https://www.npmjs.com/package/conf) along with it) ([#323](https://github.com/dotenvx/dotenvx/pull/323))
6
29
 
7
30
  ## 1.6.5
8
31
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.6.5",
2
+ "version": "1.8.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -36,17 +36,15 @@
36
36
  },
37
37
  "funding": "https://dotenvx.com",
38
38
  "dependencies": {
39
- "chalk": "^4.1.2",
40
39
  "commander": "^11.1.0",
41
- "conf": "^10.2.0",
42
40
  "diff": "^5.2.0",
43
41
  "dotenv": "^16.4.5",
44
42
  "eciesjs": "^0.4.6",
45
- "execa": "^5.1.1",
46
- "fdir": "^6.1.1",
43
+ "fdir": "^6.2.0",
47
44
  "ignore": "^5.3.0",
48
45
  "object-treeify": "1.1.33",
49
- "picomatch": "^3.0.1",
46
+ "picomatch": "^4.0.2",
47
+ "tinyexec": "^0.2.0",
50
48
  "which": "^4.0.0",
51
49
  "winston": "^3.11.0",
52
50
  "xxhashjs": "^0.2.2"
@@ -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')
@@ -57,13 +55,6 @@ ext.command('scan')
57
55
  .description('scan for leaked secrets')
58
56
  .action(require('./../actions/ext/scan'))
59
57
 
60
- // dotenvx settings
61
- ext.command('settings')
62
- .description('print current dotenvx settings')
63
- .argument('[key]', 'settings name')
64
- .option('-pp, --pretty-print', 'pretty print output')
65
- .action(require('./../actions/ext/settings'))
66
-
67
58
  ext.addCommand(require('./../commands/ext/vault'))
68
59
 
69
60
  module.exports = ext
@@ -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.d.ts CHANGED
@@ -289,21 +289,6 @@ export function genexample(
289
289
  envFile: string
290
290
  ): GenExampleOutput;
291
291
 
292
- export type Settings = {
293
- DOTENVX_SETTINGS_FILEPATH: string;
294
- };
295
-
296
- type KeyOfSettings = Extract<keyof Settings, string>;
297
-
298
- /**
299
- * Get the dotenvx settings
300
- *
301
- * @param [key] - the key to get the value of
302
- */
303
- export function settings(
304
- key: KeyOfSettings | undefined | null = null
305
- ): Settings;
306
-
307
292
  /**
308
293
  * Decrypt ciphertext
309
294
  * @param encrypted - the encrypted ciphertext string
package/src/lib/main.js CHANGED
@@ -12,7 +12,6 @@ const Status = require('./services/status')
12
12
  const Encrypt = require('./services/encrypt')
13
13
  const Decrypt = require('./services/decrypt')
14
14
  const Genexample = require('./services/genexample')
15
- const Settings = require('./services/settings')
16
15
  const VaultEncrypt = require('./services/vaultEncrypt')
17
16
 
18
17
  // helpers
@@ -204,12 +203,6 @@ const status = function (directory) {
204
203
  return new Status(directory).run()
205
204
  }
206
205
 
207
- /** @type {import('./main').settings} */
208
- const settings = function (key = null) {
209
- // @ts-ignore
210
- return new Settings(key).run()
211
- }
212
-
213
206
  // misc/cleanup
214
207
 
215
208
  /** @type {import('./main').vaultDecrypt} */
@@ -255,8 +248,6 @@ module.exports = {
255
248
  set,
256
249
  status,
257
250
  genexample,
258
- // settings
259
- settings,
260
251
  // misc/cleanup
261
252
  vaultEncrypt,
262
253
  vaultDecrypt
@@ -54,9 +54,9 @@ class Decrypt {
54
54
  if (encrypted) {
55
55
  row.keys.push(key) // track key(s)
56
56
 
57
- const plainValue = decryptValue(value, privateKey)
57
+ const decryptedValue = decryptValue(value, privateKey)
58
58
  // once newSrc is built write it out
59
- src = replace(src, key, plainValue)
59
+ src = replace(src, key, decryptedValue)
60
60
 
61
61
  row.changed = true // track change
62
62
  }
@@ -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,12 @@
1
1
  const winston = require('winston')
2
- const chalk = require('chalk')
3
2
 
4
3
  const printf = winston.format.printf
5
4
  const combine = winston.format.combine
6
5
  const createLogger = winston.createLogger
7
6
  const transports = winston.transports
8
7
 
9
- const packageJson = require('./../lib/helpers/packageJson')
8
+ const packageJson = require('../lib/helpers/packageJson')
9
+ const { getColor, bold } = require('./colors')
10
10
 
11
11
  const levels = {
12
12
  error: 0,
@@ -32,15 +32,15 @@ const levels = {
32
32
  silly: 6
33
33
  }
34
34
 
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')
35
+ const error = (m) => bold(getColor('red')(m))
36
+ const warn = getColor('orangered')
37
+ const success = getColor('green')
38
+ const successv = getColor('olive') // yellow-ish tint that 'looks' like dotenv
39
+ const help = getColor('blue')
40
+ const help2 = getColor('gray')
41
+ const http = getColor('green')
42
+ const verbose = getColor('plum')
43
+ const debug = getColor('plum')
44
44
 
45
45
  const dotenvxFormat = printf(({ level, message, label, timestamp }) => {
46
46
  const formattedMessage = typeof message === 'object' ? JSON.stringify(message) : message
@@ -119,5 +119,6 @@ const setLogLevel = options => {
119
119
 
120
120
  module.exports = {
121
121
  logger,
122
+ getColor,
122
123
  setLogLevel
123
124
  }
@@ -1,25 +0,0 @@
1
- const { logger } = require('./../../../shared/logger')
2
-
3
- const main = require('./../../../lib/main')
4
-
5
- function settings (key = null) {
6
- logger.debug(`key: ${key}`)
7
-
8
- const options = this.opts()
9
- logger.debug(`options: ${JSON.stringify(options)}`)
10
-
11
- const value = main.settings(key)
12
-
13
- if (typeof value === 'object' && value !== null) {
14
- let space = 0
15
- if (options.prettyPrint) {
16
- space = 2
17
- }
18
-
19
- process.stdout.write(JSON.stringify(value, null, space))
20
- } else {
21
- process.stdout.write(value)
22
- }
23
- }
24
-
25
- module.exports = settings
@@ -1,28 +0,0 @@
1
- const store = require('./../../shared/store')
2
-
3
- class Settings {
4
- constructor (key = null) {
5
- this.key = key
6
- }
7
-
8
- run () {
9
- const store = this._store()
10
-
11
- if (this.key) {
12
- return store[this.key]
13
- }
14
-
15
- // json of dotenvx.settings
16
- return store
17
- }
18
-
19
- _store () {
20
- const h = {
21
- DOTENVX_SETTINGS_FILEPATH: store.configPath()
22
- }
23
-
24
- return { ...h, ...store.confStore.store }
25
- }
26
- }
27
-
28
- module.exports = Settings
@@ -1,146 +0,0 @@
1
- const Conf = require('conf')
2
- const dotenv = require('dotenv')
3
- const packageJson = require('./../lib/helpers/packageJson')
4
-
5
- function jsonToEnv (json) {
6
- return Object.entries(json).map(function ([key, value]) {
7
- return key + '=' + `"${value}"`
8
- }).join('\n')
9
- }
10
-
11
- function convertFullUsernameToEnvFormat (fullUsername) {
12
- // gh/motdotla => GH_MOTDOTLA_DOTENVX_TOKEN
13
- return fullUsername
14
- .toUpperCase()
15
- .replace(/\//g, '_') // Replace all slashes with underscores
16
- .concat('_DOTENVX_TOKEN') // Append '_DOTENVX_TOKEN' at the end
17
- }
18
-
19
- function findFirstMatchingKey (data) {
20
- const dotenvxTokenValue = data.DOTENVX_TOKEN
21
-
22
- for (const [key, value] of Object.entries(data)) {
23
- if (key !== 'DOTENVX_TOKEN' && value === dotenvxTokenValue) {
24
- return key
25
- }
26
- }
27
-
28
- return null // Return null if no matching key is found
29
- }
30
-
31
- function parseUsernameFromTokenKey (key) {
32
- // Remove the leading GH_/GL_ and trailing '_DOTENVX_TOKEN'
33
- const modifiedKey = key.replace(/^(GH_|GL_)/, '').replace(/_DOTENVX_TOKEN$/, '')
34
-
35
- // Convert to lowercase
36
- return modifiedKey.toLowerCase()
37
- }
38
-
39
- const confStore = new Conf({
40
- projectName: 'dotenvx',
41
- configName: '.env',
42
- // looks better on user's machine
43
- // https://github.com/sindresorhus/conf/tree/v10.2.0#projectsuffix.
44
- projectSuffix: '',
45
- fileExtension: '',
46
- // in the spirit of dotenv and format inherently puts limits on config complexity
47
- serialize: function (json) {
48
- return jsonToEnv(json)
49
- },
50
- // Convert .env format to an object
51
- deserialize: function (env) {
52
- return dotenv.parse(env)
53
- }
54
- })
55
-
56
- const getHostname = function () {
57
- return confStore.get('DOTENVX_HOSTNAME') || 'https://hub.dotenvx.com'
58
- }
59
-
60
- const getUsername = function () {
61
- const key = findFirstMatchingKey(confStore.store)
62
-
63
- if (key) {
64
- return parseUsernameFromTokenKey(key)
65
- } else {
66
- return null
67
- }
68
- }
69
-
70
- const getToken = function () {
71
- return confStore.get('DOTENVX_TOKEN')
72
- }
73
-
74
- const getLatestVersion = function () {
75
- return confStore.get('DOTENVX_LATEST_VERSION') || packageJson.version
76
- }
77
-
78
- const getLatestVersionLastChecked = function () {
79
- return parseInt(confStore.get('DOTENVX_LATEST_VERSION_LAST_CHECKED') || 0)
80
- }
81
-
82
- const setToken = function (fullUsername, accessToken) {
83
- // current logged in user
84
- confStore.set('DOTENVX_TOKEN', accessToken)
85
-
86
- // for future use to switch between accounts locally
87
- const memory = convertFullUsernameToEnvFormat(fullUsername)
88
- confStore.set(memory, accessToken)
89
-
90
- return accessToken
91
- }
92
-
93
- const setHostname = function (hostname) {
94
- confStore.set('DOTENVX_HOSTNAME', hostname)
95
-
96
- return hostname
97
- }
98
-
99
- const setLatestVersion = function (version) {
100
- confStore.set('DOTENVX_LATEST_VERSION', version)
101
-
102
- return version
103
- }
104
-
105
- const setLatestVersionLastChecked = function (dateNow) {
106
- confStore.set('DOTENVX_LATEST_VERSION_LAST_CHECKED', dateNow)
107
-
108
- return dateNow
109
- }
110
-
111
- const deleteToken = function () {
112
- // memory user
113
- const key = findFirstMatchingKey(confStore.store) // GH_MOTDOTLA_DOTENVX_TOKEN
114
- confStore.delete(key)
115
-
116
- // current logged in user
117
- confStore.delete('DOTENVX_TOKEN')
118
-
119
- return true
120
- }
121
-
122
- const deleteHostname = function () {
123
- confStore.delete('DOTENVX_HOSTNAME')
124
-
125
- return true
126
- }
127
-
128
- const configPath = function () {
129
- return confStore.path
130
- }
131
-
132
- module.exports = {
133
- confStore,
134
- getHostname,
135
- getToken,
136
- getUsername,
137
- getLatestVersion,
138
- getLatestVersionLastChecked,
139
- setHostname,
140
- setToken,
141
- setLatestVersion,
142
- setLatestVersionLastChecked,
143
- deleteToken,
144
- deleteHostname,
145
- configPath
146
- }