@dotenvx/dotenvx 0.20.2 → 0.22.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.22.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a better dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -32,7 +32,7 @@
32
32
  "clipboardy": "^2.3.0",
33
33
  "commander": "^11.1.0",
34
34
  "conf": "^10.2.0",
35
- "dotenv": "16.4.5",
35
+ "dotenv": "^16.4.5",
36
36
  "dotenv-expand": "^11.0.6",
37
37
  "execa": "^5.1.1",
38
38
  "glob": "^10.3.10",
@@ -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
@@ -6,27 +6,19 @@ const dotenvExpand = require('dotenv-expand')
6
6
  const Encrypt = require('./services/encrypt')
7
7
  const Ls = require('./services/ls')
8
8
  const Get = require('./services/get')
9
+ const Genexample = require('./services/genexample')
9
10
 
11
+ // proxies to dotenv
10
12
  const config = function (options) {
11
- return dotenv.config(options)
12
- }
13
+ const env = dotenv.config(options)
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
- }
15
+ // if processEnv passed also pass to expand
16
+ if (options && options.processEnv) {
17
+ env.processEnv = options.processEnv
29
18
  }
19
+ const expanded = dotenvExpand.expand(env)
20
+
21
+ return expanded
30
22
  }
31
23
 
32
24
  const configDotenv = function (options) {
@@ -34,79 +26,10 @@ const configDotenv = function (options) {
34
26
  }
35
27
 
36
28
  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
- }
29
+ return dotenv.parse(src)
108
30
  }
109
31
 
32
+ // actions related
110
33
  const encrypt = function (directory, envFile) {
111
34
  return new Encrypt(directory, envFile).run()
112
35
  }
@@ -115,18 +38,43 @@ const ls = function (directory, envFile) {
115
38
  return new Ls(directory, envFile).run()
116
39
  }
117
40
 
41
+ const genexample = function (directory, envFile) {
42
+ return new Genexample(directory, envFile).run()
43
+ }
44
+
118
45
  const get = function (key, envFile, overload, all) {
119
46
  return new Get(key, envFile, overload, all).run()
120
47
  }
121
48
 
49
+ // misc/cleanup
50
+ const decrypt = function (encrypted, keyStr) {
51
+ try {
52
+ return dotenv.decrypt(encrypted, keyStr)
53
+ } catch (e) {
54
+ switch (e.code) {
55
+ case 'DECRYPTION_FAILED':
56
+ // more helpful error when decryption fails
57
+ logger.error('[DECRYPTION_FAILED] Unable to decrypt .env.vault with DOTENV_KEY.')
58
+ 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.')
59
+ logger.debug(`[DECRYPTION_FAILED] DOTENV_KEY is ${process.env.DOTENV_KEY}`)
60
+ process.exit(1)
61
+ break
62
+ default:
63
+ throw e
64
+ }
65
+ }
66
+ }
67
+
122
68
  module.exports = {
69
+ // dotenv proxies
123
70
  config,
124
71
  configDotenv,
125
- decrypt,
126
72
  parse,
127
- parseExpand,
128
- inject,
73
+ // actions related
129
74
  encrypt,
130
75
  ls,
131
- get
76
+ get,
77
+ genexample,
78
+ // misc/cleanup
79
+ decrypt
132
80
  }
@@ -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