@dotenvx/dotenvx 0.20.2 → 0.21.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.20.2",
2
+ "version": "0.21.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -42,6 +42,7 @@
42
42
  "ora": "^5.4.1",
43
43
  "undici": "^5.28.3",
44
44
  "update-notifier": "^5.1.0",
45
+ "which": "^4.0.0",
45
46
  "winston": "^3.11.0",
46
47
  "xxhashjs": "^0.2.2"
47
48
  },
@@ -1,10 +1,10 @@
1
1
  const fs = require('fs')
2
+ const dotenv = require('dotenv')
2
3
 
3
- const main = require('./../../lib/main')
4
4
  const logger = require('./../../shared/logger')
5
5
  const helpers = require('./../helpers')
6
6
  const createSpinner = require('./../../shared/createSpinner')
7
- const parseEncryptionKeyFromDotenvKey = require('./../../lib/helpers/parseEncryptionKeyFromDotenvKey')
7
+ const libDecrypt = require('./../../lib/helpers/decrypt')
8
8
 
9
9
  const spinner = createSpinner('decrypting')
10
10
 
@@ -37,8 +37,8 @@ async function decrypt () {
37
37
  process.exit(1)
38
38
  }
39
39
 
40
- const dotenvKeys = (main.configDotenv({ path: keysFilepath }).parsed || {})
41
- const dotenvVault = (main.configDotenv({ path: vaultFilepath }).parsed || {})
40
+ const dotenvKeys = dotenv.configDotenv({ path: keysFilepath }).parsed
41
+ const dotenvVault = dotenv.configDotenv({ path: vaultFilepath }).parsed
42
42
 
43
43
  Object.entries(dotenvKeys).forEach(([dotenvKey, value]) => {
44
44
  // determine environment
@@ -51,10 +51,8 @@ async function decrypt () {
51
51
 
52
52
  // give warning if not found
53
53
  if (ciphertext && ciphertext.length >= 1) {
54
- const key = parseEncryptionKeyFromDotenvKey(value.trim())
55
-
56
54
  // Decrypt
57
- const decrypted = main.decrypt(ciphertext, key)
55
+ const decrypted = libDecrypt(ciphertext, value.trim())
58
56
 
59
57
  // envFilename
60
58
  let envFilename = `.env.${environment}`
@@ -1,6 +1,6 @@
1
1
  const fs = require('fs')
2
- const helpers = require('./../helpers')
3
2
  const main = require('./../../lib/main')
3
+ const helpers = require('./../helpers')
4
4
  const logger = require('./../../shared/logger')
5
5
  const createSpinner = require('./../../shared/createSpinner')
6
6
 
@@ -8,84 +8,44 @@ const spinner = createSpinner('generating')
8
8
 
9
9
  const ENCODING = 'utf8'
10
10
 
11
- async function genexample () {
11
+ async function genexample (directory) {
12
12
  spinner.start()
13
13
  await helpers.sleep(500) // better dx
14
14
 
15
+ logger.debug(`directory: ${directory}`)
16
+
15
17
  const options = this.opts()
16
18
  logger.debug(`options: ${JSON.stringify(options)}`)
17
19
 
18
- let optionEnvFile = options.envFile
19
- if (!Array.isArray(optionEnvFile)) {
20
- optionEnvFile = [optionEnvFile]
21
- }
22
-
23
- // must be at least one .env* file
24
- if (optionEnvFile.length < 1) {
25
- spinner.fail('no .env* files found')
26
- logger.help('? add one with [echo "HELLO=World" > .env] and then run [dotenvx genexample]')
27
- process.exit(1)
28
- }
29
-
30
- const keys = new Set()
31
- const addedKeys = new Set()
20
+ try {
21
+ const {
22
+ envExampleFile,
23
+ envFile,
24
+ exampleFilepath,
25
+ addedKeys
26
+ } = main.genexample(directory, options.envFile)
32
27
 
33
- for (const envFilepath of optionEnvFile) {
34
- const filepath = helpers.resolvePath(envFilepath)
28
+ logger.verbose(`loading env from ${envFile}`)
35
29
 
36
- logger.verbose(`loading env from ${filepath}`)
30
+ // TODO: display pre-existing
31
+ // TODO: display added/appended/injected
37
32
 
38
- try {
39
- const src = fs.readFileSync(filepath, { encoding: ENCODING })
40
- const parsed = main.parse(src)
41
- Object.keys(parsed).forEach(key => { keys.add(key) })
42
- } catch (e) {
43
- // calculate development help message depending on state of repo
44
- const vaultFilepath = helpers.resolvePath('.env.vault')
45
- let developmentHelp = '? in development: add one with [echo "HELLO=World" > .env] and re-run [dotenvx genexample]'
46
- if (fs.existsSync(vaultFilepath)) {
47
- developmentHelp = '? in development: use [dotenvx decrypt] to decrypt .env.vault to .env and then re-run [dotenvx genexample]'
48
- }
33
+ fs.writeFileSync(exampleFilepath, envExampleFile, ENCODING)
49
34
 
50
- switch (e.code) {
51
- // missing .env
52
- case 'ENOENT':
53
- logger.warn(`missing ${envFilepath} file (${filepath})`)
54
- logger.help(developmentHelp)
55
- break
56
-
57
- // unhandled error
58
- default:
59
- logger.warn(e)
60
- break
61
- }
62
- }
63
- }
64
-
65
- const exampleFilename = '.env.example'
66
- const exampleFilepath = helpers.resolvePath(exampleFilename)
67
- if (!fs.existsSync(exampleFilepath)) {
68
- logger.verbose(`creating ${exampleFilename}`)
69
- fs.writeFileSync(exampleFilename, `# ${exampleFilename}\n`)
70
- }
71
-
72
- const currentEnvExample = (main.configDotenv({ path: exampleFilepath }).parsed || {})
73
- const keysArray = Array.from(keys)
74
-
75
- keysArray.forEach(key => {
76
- if (key in currentEnvExample) {
77
- logger.verbose(`pre-existing ${key} in ${exampleFilename}`)
35
+ if (addedKeys.length > 0) {
36
+ spinner.succeed(`updated .env.example (${addedKeys.length})`)
78
37
  } else {
79
- addedKeys.add(key)
80
- logger.verbose(`appending ${key} to ${exampleFilename}`)
81
- fs.appendFileSync('.env.example', `${key}=""\n`, ENCODING)
38
+ spinner.done('no changes (.env.example)')
82
39
  }
83
- })
84
-
85
- if (addedKeys.size > 0) {
86
- spinner.succeed(`updated ${exampleFilename} (${addedKeys.size})`)
87
- } else {
88
- spinner.done(`no changes (${exampleFilename})`)
40
+ } catch (error) {
41
+ spinner.fail(error.message)
42
+ if (error.help) {
43
+ logger.help(error.help)
44
+ }
45
+ if (error.code) {
46
+ logger.debug(`ERROR_CODE: ${error.code}`)
47
+ }
48
+ process.exit(1)
89
49
  }
90
50
  }
91
51
 
@@ -1,5 +1,6 @@
1
1
  const path = require('path')
2
2
  const execa = require('execa')
3
+ const which = require('which')
3
4
  const logger = require('./../../shared/logger')
4
5
 
5
6
  const RunDefault = require('./../../lib/services/runDefault')
@@ -35,8 +36,13 @@ const executeCommand = async function (commandArgs, env) {
35
36
  }
36
37
 
37
38
  try {
38
- const systemCommandPath = execa.sync('which', [commandArgs[0]]).stdout
39
- logger.debug(`system command path [${systemCommandPath}]`)
39
+ let systemCommandPath = commandArgs[0]
40
+ try {
41
+ systemCommandPath = which.sync(`${commandArgs[0]}`)
42
+ logger.debug(`expanding process command to [${systemCommandPath} ${commandArgs.slice(1).join(' ')}]`)
43
+ } catch (e) {
44
+ logger.debug(`could not expand process command. using [${systemCommandPath} ${commandArgs.slice(1).join(' ')}]`)
45
+ }
40
46
 
41
47
  // commandProcess = execa(commandArgs[0], commandArgs.slice(1), {
42
48
  commandProcess = execa(systemCommandPath, commandArgs.slice(1), {
@@ -95,6 +95,7 @@ program.command('prebuild')
95
95
  // dotenvx genexample
96
96
  program.command('genexample')
97
97
  .description('generate .env.example')
98
+ .argument('[directory]', 'directory to generate from', '.')
98
99
  .option('-f, --env-file <paths...>', 'path(s) to your env file(s)', '.env')
99
100
  .action(require('./actions/genexample'))
100
101
 
@@ -0,0 +1,27 @@
1
+ const fs = require('fs')
2
+
3
+ const RESERVED_ENV_FILES = ['.env.vault', '.env.project', '.env.keys', '.env.me', '.env.x', '.env.example']
4
+
5
+ function findEnvFiles (directory) {
6
+ try {
7
+ const files = fs.readdirSync(directory)
8
+ const envFiles = files.filter(file =>
9
+ file.startsWith('.env') &&
10
+ !file.endsWith('.previous') &&
11
+ !RESERVED_ENV_FILES.includes(file)
12
+ )
13
+
14
+ return envFiles
15
+ } catch (e) {
16
+ if (e.code === 'ENOENT') {
17
+ const error = new Error(`missing directory (${directory})`)
18
+ error.code = 'MISSING_DIRECTORY'
19
+
20
+ throw error
21
+ } else {
22
+ throw e
23
+ }
24
+ }
25
+ }
26
+
27
+ module.exports = findEnvFiles
package/src/lib/main.js CHANGED
@@ -1,112 +1,26 @@
1
1
  const logger = require('./../shared/logger')
2
2
  const dotenv = require('dotenv')
3
- const dotenvExpand = require('dotenv-expand')
4
3
 
5
4
  // services
6
5
  const Encrypt = require('./services/encrypt')
7
6
  const Ls = require('./services/ls')
8
7
  const Get = require('./services/get')
8
+ const Genexample = require('./services/genexample')
9
9
 
10
+ // proxies to dotenv
10
11
  const config = function (options) {
11
12
  return dotenv.config(options)
12
13
  }
13
14
 
14
- const decrypt = function (encrypted, keyStr) {
15
- try {
16
- return dotenv.decrypt(encrypted, keyStr)
17
- } catch (e) {
18
- switch (e.code) {
19
- case 'DECRYPTION_FAILED':
20
- // more helpful error when decryption fails
21
- logger.error('[DECRYPTION_FAILED] Unable to decrypt .env.vault with DOTENV_KEY.')
22
- logger.help('[DECRYPTION_FAILED] Run with debug flag [dotenvx run --debug -- yourcommand] or manually run [echo $DOTENV_KEY] to compare it to the one in .env.keys.')
23
- logger.debug(`[DECRYPTION_FAILED] DOTENV_KEY is ${process.env.DOTENV_KEY}`)
24
- process.exit(1)
25
- break
26
- default:
27
- throw e
28
- }
29
- }
30
- }
31
-
32
15
  const configDotenv = function (options) {
33
16
  return dotenv.configDotenv(options)
34
17
  }
35
18
 
36
19
  const parse = function (src) {
37
- const result = dotenv.parse(src)
38
-
39
- logger.debug(result)
40
-
41
- return result
42
- }
43
-
44
- const parseExpand = function (src, overload) {
45
- const parsed = dotenv.parse(src)
46
-
47
- // consider moving this logic straight into dotenv-expand
48
- let inputParsed = {}
49
- if (overload) {
50
- inputParsed = { ...process.env, ...parsed }
51
- } else {
52
- inputParsed = { ...parsed, ...process.env }
53
- }
54
-
55
- const expandPlease = {
56
- processEnv: {},
57
- parsed: inputParsed
58
- }
59
- const expanded = dotenvExpand.expand(expandPlease).parsed
60
-
61
- // 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
62
- const result = {}
63
- for (const key in parsed) {
64
- result[key] = expanded[key]
65
- }
66
-
67
- logger.debug(result)
68
-
69
- return result
70
- }
71
-
72
- const inject = function (processEnv = {}, parsed = {}, overload = false) {
73
- if (typeof parsed !== 'object') {
74
- throw new Error('OBJECT_REQUIRED: Please check the parsed argument being passed to inject')
75
- }
76
-
77
- const injected = new Set()
78
- const preExisting = new Set()
79
-
80
- // set processEnv
81
- for (const key of Object.keys(parsed)) {
82
- if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
83
- if (overload === true) {
84
- processEnv[key] = parsed[key]
85
- injected.add(key)
86
-
87
- logger.verbose(`${key} set`)
88
- logger.debug(`${key} set to ${parsed[key]}`)
89
- } else {
90
- preExisting.add(key)
91
-
92
- logger.verbose(`${key} pre-exists (protip: use --overload to override)`)
93
- logger.debug(`${key} pre-exists as ${processEnv[key]} (protip: use --overload to override)`)
94
- }
95
- } else {
96
- processEnv[key] = parsed[key]
97
- injected.add(key)
98
-
99
- logger.verbose(`${key} set`)
100
- logger.debug(`${key} set to ${parsed[key]}`)
101
- }
102
- }
103
-
104
- return {
105
- injected,
106
- preExisting
107
- }
20
+ return dotenv.parse(src)
108
21
  }
109
22
 
23
+ // actions related
110
24
  const encrypt = function (directory, envFile) {
111
25
  return new Encrypt(directory, envFile).run()
112
26
  }
@@ -115,18 +29,43 @@ const ls = function (directory, envFile) {
115
29
  return new Ls(directory, envFile).run()
116
30
  }
117
31
 
32
+ const genexample = function (directory, envFile) {
33
+ return new Genexample(directory, envFile).run()
34
+ }
35
+
118
36
  const get = function (key, envFile, overload, all) {
119
37
  return new Get(key, envFile, overload, all).run()
120
38
  }
121
39
 
40
+ // misc/cleanup
41
+ const decrypt = function (encrypted, keyStr) {
42
+ try {
43
+ return dotenv.decrypt(encrypted, keyStr)
44
+ } catch (e) {
45
+ switch (e.code) {
46
+ case 'DECRYPTION_FAILED':
47
+ // more helpful error when decryption fails
48
+ logger.error('[DECRYPTION_FAILED] Unable to decrypt .env.vault with DOTENV_KEY.')
49
+ logger.help('[DECRYPTION_FAILED] Run with debug flag [dotenvx run --debug -- yourcommand] or manually run [echo $DOTENV_KEY] to compare it to the one in .env.keys.')
50
+ logger.debug(`[DECRYPTION_FAILED] DOTENV_KEY is ${process.env.DOTENV_KEY}`)
51
+ process.exit(1)
52
+ break
53
+ default:
54
+ throw e
55
+ }
56
+ }
57
+ }
58
+
122
59
  module.exports = {
60
+ // dotenv proxies
123
61
  config,
124
62
  configDotenv,
125
- decrypt,
126
63
  parse,
127
- parseExpand,
128
- inject,
64
+ // actions related
129
65
  encrypt,
130
66
  ls,
131
- get
67
+ get,
68
+ genexample,
69
+ // misc/cleanup
70
+ decrypt
132
71
  }
@@ -6,12 +6,13 @@ const DotenvKeys = require('./../helpers/dotenvKeys')
6
6
  const DotenvVault = require('./../helpers/dotenvVault')
7
7
 
8
8
  const ENCODING = 'utf8'
9
- const RESERVED_ENV_FILES = ['.env.vault', '.env.project', '.env.keys', '.env.me', '.env.x']
9
+
10
+ const findEnvFiles = require('../helpers/findEnvFiles')
10
11
 
11
12
  class Encrypt {
12
13
  constructor (directory = '.', envFile) {
13
14
  this.directory = directory
14
- this.envFile = envFile || this._findEnvFiles()
15
+ this.envFile = envFile || findEnvFiles(directory)
15
16
  // calculated
16
17
  this.envKeysFilepath = path.resolve(this.directory, '.env.keys')
17
18
  this.envVaultFilepath = path.resolve(this.directory, '.env.vault')
@@ -112,17 +113,6 @@ class Encrypt {
112
113
 
113
114
  return dotenv.configDotenv(options).parsed
114
115
  }
115
-
116
- _findEnvFiles () {
117
- const files = fs.readdirSync(this.directory)
118
- const envFiles = files.filter(file =>
119
- file.startsWith('.env') &&
120
- !file.endsWith('.previous') &&
121
- !RESERVED_ENV_FILES.includes(file)
122
- )
123
-
124
- return envFiles
125
- }
126
116
  }
127
117
 
128
118
  module.exports = Encrypt
@@ -0,0 +1,94 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const dotenv = require('dotenv')
4
+
5
+ const ENCODING = 'utf8'
6
+
7
+ const findEnvFiles = require('../helpers/findEnvFiles')
8
+
9
+ class Genexample {
10
+ constructor (directory = '.', envFile) {
11
+ this.directory = directory
12
+ this.envFile = envFile || findEnvFiles(directory)
13
+ }
14
+
15
+ run () {
16
+ if (this.envFile.length < 1) {
17
+ const code = 'MISSING_ENV_FILES'
18
+ const message = 'no .env* files found'
19
+ const help = '? add one with [echo "HELLO=World" > .env] and then run [dotenvx genexample]'
20
+
21
+ const error = new Error(message)
22
+ error.code = code
23
+ error.help = help
24
+ throw error
25
+ }
26
+
27
+ const keys = new Set()
28
+ const addedKeys = new Set()
29
+ const envFilepaths = this._envFilepaths()
30
+
31
+ for (const envFilepath of envFilepaths) {
32
+ const filepath = path.resolve(this.directory, envFilepath)
33
+ if (!fs.existsSync(filepath)) {
34
+ const code = 'MISSING_ENV_FILE'
35
+ const message = `file does not exist at [${filepath}]`
36
+ const help = `? add it with [echo "HELLO=World" > ${envFilepath}] and then run [dotenvx genexample]`
37
+
38
+ const error = new Error(message)
39
+ error.code = code
40
+ error.help = help
41
+ throw error
42
+ }
43
+
44
+ const parsed = dotenv.configDotenv({ path: filepath }).parsed
45
+ for (const key of Object.keys(parsed)) {
46
+ keys.add(key)
47
+ }
48
+ }
49
+
50
+ let envExampleFile = ''
51
+ const exampleFilename = '.env.example'
52
+ const exampleFilepath = path.resolve(this.directory, exampleFilename)
53
+ if (!fs.existsSync(exampleFilepath)) {
54
+ envExampleFile += `# ${exampleFilename}\n`
55
+ } else {
56
+ envExampleFile = fs.readFileSync(exampleFilepath, ENCODING)
57
+ }
58
+
59
+ const currentEnvExample = dotenv.configDotenv({ path: exampleFilepath }).parsed
60
+ const injected = {}
61
+ const preExisted = {}
62
+
63
+ for (const key of [...keys]) {
64
+ if (key in currentEnvExample) {
65
+ preExisted[key] = currentEnvExample[key]
66
+ } else {
67
+ envExampleFile += `${key}=""\n`
68
+
69
+ addedKeys.add(key)
70
+
71
+ injected[key] = ''
72
+ }
73
+ }
74
+
75
+ return {
76
+ envExampleFile,
77
+ envFile: this.envFile,
78
+ exampleFilepath,
79
+ addedKeys: [...addedKeys],
80
+ injected,
81
+ preExisted
82
+ }
83
+ }
84
+
85
+ _envFilepaths () {
86
+ if (!Array.isArray(this.envFile)) {
87
+ return [this.envFile]
88
+ }
89
+
90
+ return this.envFile
91
+ }
92
+ }
93
+
94
+ module.exports = Genexample