@dotenvx/dotenvx 1.59.0 → 1.60.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/README.md +130 -137
  3. package/package.json +7 -6
  4. package/src/cli/actions/decrypt.js +15 -8
  5. package/src/cli/actions/encrypt.js +15 -8
  6. package/src/cli/actions/ext/genexample.js +2 -2
  7. package/src/cli/actions/ext/gitignore.js +3 -3
  8. package/src/cli/actions/get.js +12 -7
  9. package/src/cli/actions/keypair.js +13 -5
  10. package/src/cli/actions/rotate.js +16 -9
  11. package/src/cli/actions/run.js +13 -6
  12. package/src/cli/actions/set.js +19 -12
  13. package/src/cli/dotenvx.js +19 -21
  14. package/src/cli/examples.js +1 -1
  15. package/src/db/session.js +10 -6
  16. package/src/lib/extensions/ops.js +112 -63
  17. package/src/lib/helpers/catchAndLog.js +1 -1
  18. package/src/lib/helpers/createSpinner.js +24 -0
  19. package/src/lib/helpers/cryptography/index.js +4 -0
  20. package/src/lib/helpers/cryptography/mutateKeysSrc.js +4 -4
  21. package/src/lib/helpers/cryptography/mutateKeysSrcSync.js +38 -0
  22. package/src/lib/helpers/cryptography/opsKeypair.js +2 -2
  23. package/src/lib/helpers/cryptography/opsKeypairSync.js +14 -0
  24. package/src/lib/helpers/cryptography/provision.js +7 -7
  25. package/src/lib/helpers/cryptography/provisionSync.js +47 -0
  26. package/src/lib/helpers/cryptography/provisionWithPrivateKey.js +1 -1
  27. package/src/lib/helpers/detectEncoding.js +2 -2
  28. package/src/lib/helpers/detectEncodingSync.js +22 -0
  29. package/src/lib/helpers/errors.js +15 -0
  30. package/src/lib/helpers/fsx.js +27 -3
  31. package/src/lib/helpers/installPrecommitHook.js +2 -2
  32. package/src/lib/helpers/isIgnoringDotenvKeys.js +1 -1
  33. package/src/lib/helpers/keyResolution/index.js +3 -1
  34. package/src/lib/helpers/keyResolution/keyValues.js +12 -12
  35. package/src/lib/helpers/keyResolution/keyValuesSync.js +85 -0
  36. package/src/lib/helpers/keyResolution/readFileKey.js +10 -8
  37. package/src/lib/helpers/keyResolution/readFileKeySync.js +15 -0
  38. package/src/lib/helpers/kits/sample.js +11 -21
  39. package/src/lib/main.d.ts +18 -3
  40. package/src/lib/main.js +18 -18
  41. package/src/lib/services/decrypt.js +8 -8
  42. package/src/lib/services/encrypt.js +17 -11
  43. package/src/lib/services/genexample.js +2 -2
  44. package/src/lib/services/get.js +30 -21
  45. package/src/lib/services/keypair.js +21 -5
  46. package/src/lib/services/prebuild.js +7 -12
  47. package/src/lib/services/precommit.js +7 -11
  48. package/src/lib/services/rotate.js +22 -18
  49. package/src/lib/services/run.js +82 -9
  50. package/src/lib/services/sets.js +139 -12
  51. package/src/shared/logger.js +3 -3
  52. package/src/lib/helpers/sleep.js +0 -5
@@ -1,22 +1,38 @@
1
1
  const {
2
2
  keyNames,
3
- keyValues
3
+ keyValues,
4
+ keyValuesSync
4
5
  } = require('./../helpers/keyResolution')
5
6
 
6
7
  class Keypair {
7
- constructor (envFile = '.env', envKeysFilepath = null, opsOn = false) {
8
+ constructor (envFile = '.env', envKeysFilepath = null, noOps = false) {
8
9
  this.envFile = envFile
9
10
  this.envKeysFilepath = envKeysFilepath
10
- this.opsOn = opsOn
11
+ this.noOps = noOps
11
12
  }
12
13
 
13
- run () {
14
+ runSync () {
14
15
  const out = {}
15
16
 
16
17
  const filepaths = this._filepaths()
17
18
  for (const filepath of filepaths) {
18
19
  const { publicKeyName, privateKeyName } = keyNames(filepath)
19
- const { publicKeyValue, privateKeyValue } = keyValues(filepath, { keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
20
+ const { publicKeyValue, privateKeyValue } = keyValuesSync(filepath, { keysFilepath: this.envKeysFilepath, noOps: this.noOps })
21
+
22
+ out[publicKeyName] = publicKeyValue
23
+ out[privateKeyName] = privateKeyValue
24
+ }
25
+
26
+ return out
27
+ }
28
+
29
+ async run () {
30
+ const out = {}
31
+
32
+ const filepaths = this._filepaths()
33
+ for (const filepath of filepaths) {
34
+ const { publicKeyName, privateKeyName } = keyNames(filepath)
35
+ const { publicKeyValue, privateKeyValue } = await keyValues(filepath, { keysFilepath: this.envKeysFilepath, noOps: this.noOps })
20
36
 
21
37
  out[publicKeyName] = publicKeyValue
22
38
  out[privateKeyName] = privateKeyValue
@@ -7,7 +7,6 @@ const Ls = require('../services/ls')
7
7
  const Errors = require('../helpers/errors')
8
8
 
9
9
  const isFullyEncrypted = require('./../helpers/isFullyEncrypted')
10
- const packageJson = require('./../helpers/packageJson')
11
10
  const MISSING_DOCKERIGNORE = '.env.keys' // by default only ignore .env.keys. all other .env* files COULD be included - as long as they are encrypted
12
11
 
13
12
  class Prebuild {
@@ -26,12 +25,12 @@ class Prebuild {
26
25
  // 1. check for .dockerignore file
27
26
  if (!fsx.existsSync('.dockerignore')) {
28
27
  const warning = new Errors({
29
- message: `[dotenvx@${packageJson.version}][prebuild] .dockerignore missing`,
28
+ message: '.dockerignore missing',
30
29
  help: 'fix: [touch .dockerignore]'
31
30
  }).custom()
32
31
  warnings.push(warning)
33
32
  } else {
34
- dockerignore = fsx.readFileX('.dockerignore')
33
+ dockerignore = fsx.readFileXSync('.dockerignore')
35
34
  }
36
35
 
37
36
  // 2. check .env* files against .dockerignore file
@@ -47,22 +46,22 @@ class Prebuild {
47
46
  if (ig.ignores(file)) {
48
47
  if (file === '.env.example' || file === '.env.x') {
49
48
  const warning = new Errors({
50
- message: `[dotenvx@${packageJson.version}][prebuild] ${file} (currently ignored but should not be)`,
49
+ message: `${file} ignored (should not be)`,
51
50
  help: `fix: [dotenvx ext gitignore --pattern !${file}]`
52
51
  }).custom()
53
52
  warnings.push(warning)
54
53
  }
55
54
  } else {
56
55
  if (file !== '.env.example' && file !== '.env.x') {
57
- const src = fsx.readFileX(file)
56
+ const src = fsx.readFileXSync(file)
58
57
  const encrypted = isFullyEncrypted(src)
59
58
 
60
59
  // if contents are encrypted don't raise an error
61
60
  if (!encrypted) {
62
- let errorMsg = `[dotenvx@${packageJson.version}][prebuild] ${file} not protected (encrypted or dockerignored)`
61
+ let errorMsg = `${file} not encrypted/dockerignored`
63
62
  let errorHelp = `fix: [dotenvx encrypt -f ${file}] or [dotenvx ext gitignore --pattern ${file}]`
64
63
  if (file.includes('.env.keys')) {
65
- errorMsg = `[dotenvx@${packageJson.version}][prebuild] ${file} not protected (dockerignored)`
64
+ errorMsg = `${file} not dockerignored`
66
65
  errorHelp = `fix: [dotenvx ext gitignore --pattern ${file}]`
67
66
  }
68
67
 
@@ -72,11 +71,7 @@ class Prebuild {
72
71
  }
73
72
  })
74
73
 
75
- let successMessage = `[dotenvx@${packageJson.version}][prebuild] .env files (${count}) protected (encrypted or dockerignored)`
76
-
77
- if (count === 0) {
78
- successMessage = `[dotenvx@${packageJson.version}][prebuild] zero .env files`
79
- }
74
+ let successMessage = count === 0 ? '▣ no .env files' : `▣ encrypted/dockerignored (${count})`
80
75
  if (warnings.length > 0) {
81
76
  successMessage += ` with warnings (${warnings.length})`
82
77
  }
@@ -6,7 +6,6 @@ const ignore = require('ignore')
6
6
  const Ls = require('../services/ls')
7
7
 
8
8
  const isFullyEncrypted = require('./../helpers/isFullyEncrypted')
9
- const packageJson = require('./../helpers/packageJson')
10
9
  const InstallPrecommitHook = require('./../helpers/installPrecommitHook')
11
10
  const Errors = require('./../helpers/errors')
12
11
  const childProcess = require('child_process')
@@ -39,12 +38,12 @@ class Precommit {
39
38
  // 1. check for .gitignore file
40
39
  if (!fsx.existsSync('.gitignore')) {
41
40
  const warning = new Errors({
42
- message: `[dotenvx@${packageJson.version}][precommit] .gitignore missing`,
41
+ message: '.gitignore missing',
43
42
  help: 'fix: [touch .gitignore]'
44
43
  }).custom()
45
44
  warnings.push(warning)
46
45
  } else {
47
- gitignore = fsx.readFileX('.gitignore')
46
+ gitignore = fsx.readFileXSync('.gitignore')
48
47
  }
49
48
 
50
49
  // 2. check .env* files against .gitignore file
@@ -63,22 +62,22 @@ class Precommit {
63
62
  if (ig.ignores(file)) {
64
63
  if (file === '.env.example' || file === '.env.x') {
65
64
  const warning = new Errors({
66
- message: `[dotenvx@${packageJson.version}][precommit] ${file} (currently ignored but should not be)`,
65
+ message: `${file} ignored (should not be)`,
67
66
  help: `fix: [dotenvx ext gitignore --pattern !${file}]`
68
67
  }).custom()
69
68
  warnings.push(warning)
70
69
  }
71
70
  } else {
72
71
  if (file !== '.env.example' && file !== '.env.x') {
73
- const src = fsx.readFileX(file)
72
+ const src = fsx.readFileXSync(file)
74
73
  const encrypted = isFullyEncrypted(src)
75
74
 
76
75
  // if contents are encrypted don't raise an error
77
76
  if (!encrypted) {
78
- let errorMsg = `[dotenvx@${packageJson.version}][precommit] ${file} not protected (encrypted or gitignored)`
77
+ let errorMsg = `${file} not encrypted/gitignored`
79
78
  let errorHelp = `fix: [dotenvx encrypt -f ${file}] or [dotenvx ext gitignore --pattern ${file}]`
80
79
  if (file.includes('.env.keys')) {
81
- errorMsg = `[dotenvx@${packageJson.version}][precommit] ${file} not protected (gitignored)`
80
+ errorMsg = `${file} not gitignored`
82
81
  errorHelp = `fix: [dotenvx ext gitignore --pattern ${file}]`
83
82
  }
84
83
 
@@ -89,10 +88,7 @@ class Precommit {
89
88
  }
90
89
  })
91
90
 
92
- let successMessage = `[dotenvx@${packageJson.version}][precommit] .env files (${count}) protected (encrypted or gitignored)`
93
- if (count === 0) {
94
- successMessage = `[dotenvx@${packageJson.version}][precommit] zero .env files`
95
- }
91
+ let successMessage = count === 0 ? '▣ no .env files' : `▣ encrypted/gitignored (${count})`
96
92
  if (warnings.length > 0) {
97
93
  successMessage += ` with warnings (${warnings.length})`
98
94
  }
@@ -29,12 +29,12 @@ const dotenvParse = require('./../helpers/dotenvParse')
29
29
  const detectEncoding = require('./../helpers/detectEncoding')
30
30
 
31
31
  class Rotate {
32
- constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, opsOn = false) {
32
+ constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, noOps = false) {
33
33
  this.envs = determine(envs, process.env)
34
34
  this.key = key
35
35
  this.excludeKey = excludeKey
36
36
  this.envKeysFilepath = envKeysFilepath
37
- this.opsOn = opsOn
37
+ this.noOps = noOps
38
38
 
39
39
  this.processedEnvs = []
40
40
  this.changedFilepaths = new Set()
@@ -43,7 +43,7 @@ class Rotate {
43
43
  this.envKeysSources = {}
44
44
  }
45
45
 
46
- run () {
46
+ async run () {
47
47
  // example
48
48
  // envs [
49
49
  // { type: 'envFile', value: '.env' }
@@ -57,7 +57,7 @@ class Rotate {
57
57
 
58
58
  for (const env of this.envs) {
59
59
  if (env.type === TYPE_ENV_FILE) {
60
- this._rotateEnvFile(env.value)
60
+ await this._rotateEnvFile(env.value)
61
61
  }
62
62
  }
63
63
 
@@ -68,7 +68,7 @@ class Rotate {
68
68
  }
69
69
  }
70
70
 
71
- _rotateEnvFile (envFilepath) {
71
+ async _rotateEnvFile (envFilepath) {
72
72
  const row = {}
73
73
  row.keys = []
74
74
  row.type = TYPE_ENV_FILE
@@ -78,31 +78,29 @@ class Rotate {
78
78
  row.envFilepath = envFilepath
79
79
 
80
80
  try {
81
- const encoding = detectEncoding(filepath)
82
- let envSrc = fsx.readFileX(filepath, { encoding })
81
+ const encoding = await detectEncoding(filepath)
82
+ let envSrc = await fsx.readFileX(filepath, { encoding })
83
83
  const envParsed = dotenvParse(envSrc)
84
84
 
85
85
  const { publicKeyName, privateKeyName } = keyNames(envFilepath)
86
- const { privateKeyValue } = keyValues(envFilepath, { keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
86
+ const { privateKeyValue } = await keyValues(envFilepath, { keysFilepath: this.envKeysFilepath, noOps: this.noOps })
87
87
 
88
88
  let newPublicKey
89
89
  let newPrivateKey
90
90
  let envKeysFilepath
91
91
  let envKeysSrc
92
92
 
93
- if (this.opsOn) {
94
- const kp = opsKeypair()
93
+ if (!this.noOps) {
94
+ const kp = await opsKeypair()
95
95
  newPublicKey = kp.publicKey
96
96
  newPrivateKey = kp.privateKey
97
97
 
98
98
  row.privateKeyAdded = false // TODO: change to localPrivateKeyAdded
99
99
  } else {
100
- envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
101
- if (this.envKeysFilepath) {
102
- envKeysFilepath = path.resolve(this.envKeysFilepath)
103
- }
104
- row.envKeysFilepath = envKeysFilepath
105
- this.envKeysSources[envKeysFilepath] ||= fsx.readFileX(envKeysFilepath, { encoding: detectEncoding(envKeysFilepath) })
100
+ row.envKeysFilepath = this.envKeysFilepath || path.join(path.dirname(envFilepath), '.env.keys')
101
+ envKeysFilepath = path.resolve(row.envKeysFilepath)
102
+ const encodingForKeys = await detectEncoding(envKeysFilepath)
103
+ this.envKeysSources[envKeysFilepath] ||= await fsx.readFileX(envKeysFilepath, { encoding: encodingForKeys })
106
104
  envKeysSrc = this.envKeysSources[envKeysFilepath]
107
105
 
108
106
  const kp = localKeypair()
@@ -145,7 +143,7 @@ class Rotate {
145
143
  row.privateKeyName = privateKeyName
146
144
  row.privateKey = newPrivateKey
147
145
 
148
- if (!this.opsOn) {
146
+ if (this.noOps) {
149
147
  // keys src only for ops
150
148
  envKeysSrc = append(envKeysSrc, privateKeyName, newPrivateKey) // append privateKey
151
149
  this.envKeysSources[envKeysFilepath] = envKeysSrc
@@ -155,7 +153,13 @@ class Rotate {
155
153
  this.changedFilepaths.add(envFilepath)
156
154
  } catch (e) {
157
155
  if (e.code === 'ENOENT') {
158
- row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
156
+ const missingPath = e.path ? path.resolve(e.path) : null
157
+ const expectedEnvKeysPath = row.envKeysFilepath ? path.resolve(row.envKeysFilepath) : null
158
+ if (this.noOps && expectedEnvKeysPath && missingPath === expectedEnvKeysPath) {
159
+ row.error = new Errors({ envKeysFilepath: row.envKeysFilepath }).missingEnvKeysFile()
160
+ } else {
161
+ row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
162
+ }
159
163
  } else {
160
164
  row.error = e
161
165
  }
@@ -7,10 +7,12 @@ const TYPE_ENV_FILE = 'envFile'
7
7
  const Parse = require('./../helpers/parse')
8
8
  const Errors = require('./../helpers/errors')
9
9
  const detectEncoding = require('./../helpers/detectEncoding')
10
+ const detectEncodingSync = require('./../helpers/detectEncodingSync')
10
11
 
11
12
  const {
12
13
  keyNames,
13
- keyValues
14
+ keyValues,
15
+ keyValuesSync
14
16
  } = require('./../helpers/keyResolution')
15
17
 
16
18
  const {
@@ -18,12 +20,12 @@ const {
18
20
  } = require('./../helpers/envResolution')
19
21
 
20
22
  class Run {
21
- constructor (envs = [], overload = false, processEnv = process.env, envKeysFilepath = null, opsOn = false) {
23
+ constructor (envs = [], overload = false, processEnv = process.env, envKeysFilepath = null, noOps = false) {
22
24
  this.envs = determine(envs, processEnv)
23
25
  this.overload = overload
24
26
  this.processEnv = processEnv
25
27
  this.envKeysFilepath = envKeysFilepath
26
- this.opsOn = opsOn
28
+ this.noOps = noOps
27
29
 
28
30
  this.processedEnvs = []
29
31
  this.readableFilepaths = new Set()
@@ -32,7 +34,7 @@ class Run {
32
34
  this.beforeEnv = { ...this.processEnv }
33
35
  }
34
36
 
35
- run () {
37
+ runSync () {
36
38
  // example
37
39
  // envs [
38
40
  // { type: 'env', value: 'HELLO=one' },
@@ -42,7 +44,33 @@ class Run {
42
44
 
43
45
  for (const env of this.envs) {
44
46
  if (env.type === TYPE_ENV_FILE) {
45
- this._injectEnvFile(env.value)
47
+ this._injectEnvFileSync(env.value)
48
+ } else if (env.type === TYPE_ENV) {
49
+ this._injectEnv(env.value)
50
+ }
51
+ }
52
+
53
+ return {
54
+ processedEnvs: this.processedEnvs,
55
+ readableStrings: [...this.readableStrings],
56
+ readableFilepaths: [...this.readableFilepaths],
57
+ uniqueInjectedKeys: [...this.uniqueInjectedKeys],
58
+ beforeEnv: this.beforeEnv,
59
+ afterEnv: { ...this.processEnv }
60
+ }
61
+ }
62
+
63
+ async run () {
64
+ // example
65
+ // envs [
66
+ // { type: 'env', value: 'HELLO=one' },
67
+ // { type: 'envFile', value: '.env' },
68
+ // { type: 'env', value: 'HELLO=three' }
69
+ // ]
70
+
71
+ for (const env of this.envs) {
72
+ if (env.type === TYPE_ENV_FILE) {
73
+ await this._injectEnvFile(env.value)
46
74
  } else if (env.type === TYPE_ENV) {
47
75
  this._injectEnv(env.value)
48
76
  }
@@ -90,19 +118,64 @@ class Run {
90
118
  this.processedEnvs.push(row)
91
119
  }
92
120
 
93
- _injectEnvFile (envFilepath) {
121
+ _injectEnvFileSync (envFilepath) {
122
+ const row = {}
123
+ row.type = TYPE_ENV_FILE
124
+ row.filepath = envFilepath
125
+
126
+ const filepath = path.resolve(envFilepath)
127
+ try {
128
+ const encoding = detectEncodingSync(filepath)
129
+ const src = fsx.readFileXSync(filepath, { encoding })
130
+ this.readableFilepaths.add(envFilepath)
131
+
132
+ const { privateKeyName } = keyNames(filepath)
133
+ const { privateKeyValue } = keyValuesSync(filepath, { keysFilepath: this.envKeysFilepath, noOps: this.noOps })
134
+
135
+ const {
136
+ parsed,
137
+ errors,
138
+ injected,
139
+ preExisted
140
+ } = new Parse(src, privateKeyValue, this.processEnv, this.overload, privateKeyName).run()
141
+
142
+ row.privateKeyName = privateKeyName
143
+ row.privateKey = privateKeyValue
144
+ row.src = src
145
+ row.parsed = parsed
146
+ row.errors = errors
147
+ row.injected = injected
148
+ row.preExisted = preExisted
149
+
150
+ this.inject(row.parsed) // inject
151
+
152
+ for (const key of Object.keys(injected)) {
153
+ this.uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
154
+ }
155
+ } catch (e) {
156
+ if (e.code === 'ENOENT' || e.code === 'EISDIR') {
157
+ row.errors = [new Errors({ envFilepath, filepath }).missingEnvFile()]
158
+ } else {
159
+ row.errors = [e]
160
+ }
161
+ }
162
+
163
+ this.processedEnvs.push(row)
164
+ }
165
+
166
+ async _injectEnvFile (envFilepath) {
94
167
  const row = {}
95
168
  row.type = TYPE_ENV_FILE
96
169
  row.filepath = envFilepath
97
170
 
98
171
  const filepath = path.resolve(envFilepath)
99
172
  try {
100
- const encoding = detectEncoding(filepath)
101
- const src = fsx.readFileX(filepath, { encoding })
173
+ const encoding = await detectEncoding(filepath)
174
+ const src = await fsx.readFileX(filepath, { encoding })
102
175
  this.readableFilepaths.add(envFilepath)
103
176
 
104
177
  const { privateKeyName } = keyNames(filepath)
105
- const { privateKeyValue } = keyValues(filepath, { keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
178
+ const { privateKeyValue } = await keyValues(filepath, { keysFilepath: this.envKeysFilepath, noOps: this.noOps })
106
179
 
107
180
  const {
108
181
  parsed,
@@ -11,7 +11,8 @@ const {
11
11
 
12
12
  const {
13
13
  keyNames,
14
- keyValues
14
+ keyValues,
15
+ keyValuesSync
15
16
  } = require('./../helpers/keyResolution')
16
17
 
17
18
  const {
@@ -19,21 +20,23 @@ const {
19
20
  decryptKeyValue,
20
21
  isEncrypted,
21
22
  provision,
23
+ provisionSync,
22
24
  provisionWithPrivateKey
23
25
  } = require('./../helpers/cryptography')
24
26
 
25
27
  const replace = require('./../helpers/replace')
26
28
  const dotenvParse = require('./../helpers/dotenvParse')
27
29
  const detectEncoding = require('./../helpers/detectEncoding')
30
+ const detectEncodingSync = require('./../helpers/detectEncodingSync')
28
31
 
29
32
  class Sets {
30
- constructor (key, value, envs = [], encrypt = true, envKeysFilepath = null, opsOn = false, noCreate = false) {
33
+ constructor (key, value, envs = [], encrypt = true, envKeysFilepath = null, noOps = false, noCreate = false) {
31
34
  this.envs = determine(envs, process.env)
32
35
  this.key = key
33
36
  this.value = value
34
37
  this.encrypt = encrypt
35
38
  this.envKeysFilepath = envKeysFilepath
36
- this.opsOn = opsOn
39
+ this.noOps = noOps
37
40
  this.noCreate = noCreate
38
41
 
39
42
  this.processedEnvs = []
@@ -42,7 +45,7 @@ class Sets {
42
45
  this.readableFilepaths = new Set()
43
46
  }
44
47
 
45
- run () {
48
+ runSync () {
46
49
  // example
47
50
  // envs [
48
51
  // { type: 'envFile', value: '.env' }
@@ -50,7 +53,7 @@ class Sets {
50
53
 
51
54
  for (const env of this.envs) {
52
55
  if (env.type === TYPE_ENV_FILE) {
53
- this._setEnvFile(env.value)
56
+ this._setEnvFileSync(env.value)
54
57
  }
55
58
  }
56
59
 
@@ -61,7 +64,26 @@ class Sets {
61
64
  }
62
65
  }
63
66
 
64
- _setEnvFile (envFilepath) {
67
+ async run () {
68
+ // example
69
+ // envs [
70
+ // { type: 'envFile', value: '.env' }
71
+ // ]
72
+
73
+ for (const env of this.envs) {
74
+ if (env.type === TYPE_ENV_FILE) {
75
+ await this._setEnvFile(env.value)
76
+ }
77
+ }
78
+
79
+ return {
80
+ processedEnvs: this.processedEnvs,
81
+ changedFilepaths: [...this.changedFilepaths],
82
+ unchangedFilepaths: [...this.unchangedFilepaths]
83
+ }
84
+ }
85
+
86
+ _setEnvFileSync (envFilepath) {
65
87
  const row = {}
66
88
  row.key = this.key || null
67
89
  row.value = this.value || null
@@ -77,14 +99,119 @@ class Sets {
77
99
 
78
100
  if (!fsx.existsSync(filepath)) {
79
101
  if (this.noCreate) {
80
- detectEncoding(filepath) // throws ENOENT
102
+ detectEncodingSync(filepath) // throws ENOENT
103
+ } else {
104
+ fsx.writeFileXSync(filepath, '')
105
+ }
106
+ }
107
+
108
+ const encoding = detectEncodingSync(filepath)
109
+ let envSrc = fsx.readFileXSync(filepath, { encoding })
110
+
111
+ // blank files seeded by `set` should contain only the key being set
112
+ if (row.key && envSrc.trim().length === 0) {
113
+ envSrc = `${row.key}="${this.value}"\n`
114
+ seededWithInitialKey = true
115
+ }
116
+
117
+ const envParsed = dotenvParse(envSrc)
118
+ row.originalValue = envParsed[row.key] || null
119
+ if (seededWithInitialKey) {
120
+ row.originalValue = null
121
+ }
122
+ const wasPlainText = !isEncrypted(row.originalValue)
123
+ this.readableFilepaths.add(envFilepath)
124
+
125
+ if (this.encrypt) {
126
+ let publicKey
127
+ let privateKey
128
+
129
+ const { publicKeyName, privateKeyName } = keyNames(filepath)
130
+ const { publicKeyValue, privateKeyValue } = keyValuesSync(filepath, { keysFilepath: this.envKeysFilepath, noOps: this.noOps })
131
+
132
+ // first pass - provisionSync
133
+ if (!privateKeyValue && !publicKeyValue) {
134
+ const prov = provisionSync({ envSrc, envFilepath, keysFilepath: this.envKeysFilepath, noOps: this.noOps })
135
+ envSrc = prov.envSrc
136
+ publicKey = prov.publicKey
137
+ privateKey = prov.privateKey
138
+ row.privateKeyAdded = prov.privateKeyAdded
139
+ row.envKeysFilepath = prov.envKeysFilepath
140
+ } else if (privateKeyValue) {
141
+ const prov = provisionWithPrivateKey({ envSrc, envFilepath, keysFilepath: this.envKeysFilepath, privateKeyValue, publicKeyValue, publicKeyName })
142
+ publicKey = prov.publicKey
143
+ privateKey = prov.privateKey
144
+ envSrc = prov.envSrc
145
+
146
+ if (row.originalValue) {
147
+ row.originalValue = decryptKeyValue(row.key, row.originalValue, privateKeyName, privateKey)
148
+ }
149
+ } else if (publicKeyValue) {
150
+ publicKey = publicKeyValue
151
+ }
152
+
153
+ row.publicKey = publicKey
154
+ row.privateKey = privateKey
155
+ try {
156
+ row.encryptedValue = encryptValue(this.value, publicKey)
157
+ } catch {
158
+ throw new Errors({ publicKeyName, publicKey }).invalidPublicKey()
159
+ }
160
+ row.privateKeyName = privateKeyName
161
+ }
162
+
163
+ const goingFromPlainTextToEncrypted = wasPlainText && this.encrypt
164
+ const valueChanged = this.value !== row.originalValue
165
+ const shouldPersistSeededPlainValue = seededWithInitialKey && !this.encrypt
166
+
167
+ if (shouldPersistSeededPlainValue) {
168
+ row.envSrc = envSrc
169
+ this.changedFilepaths.add(envFilepath)
170
+ row.changed = true
171
+ } else if (goingFromPlainTextToEncrypted || valueChanged) {
172
+ row.envSrc = replace(envSrc, this.key, row.encryptedValue || this.value)
173
+ this.changedFilepaths.add(envFilepath)
174
+ row.changed = true
175
+ } else {
176
+ row.envSrc = envSrc
177
+ this.unchangedFilepaths.add(envFilepath)
178
+ row.changed = false
179
+ }
180
+ } catch (e) {
181
+ if (e.code === 'ENOENT') {
182
+ row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
183
+ } else {
184
+ row.error = e
185
+ }
186
+ }
187
+
188
+ this.processedEnvs.push(row)
189
+ }
190
+
191
+ async _setEnvFile (envFilepath) {
192
+ const row = {}
193
+ row.key = this.key || null
194
+ row.value = this.value || null
195
+ row.type = TYPE_ENV_FILE
196
+
197
+ const filepath = path.resolve(envFilepath)
198
+ row.filepath = filepath
199
+ row.envFilepath = envFilepath
200
+ row.changed = false
201
+
202
+ try {
203
+ let seededWithInitialKey = false
204
+
205
+ if (!(await fsx.exists(filepath))) {
206
+ if (this.noCreate) {
207
+ await detectEncoding(filepath) // throws ENOENT
81
208
  } else {
82
- fsx.writeFileX(filepath, '')
209
+ await fsx.writeFileX(filepath, '')
83
210
  }
84
211
  }
85
212
 
86
- const encoding = detectEncoding(filepath)
87
- let envSrc = fsx.readFileX(filepath, { encoding })
213
+ const encoding = await detectEncoding(filepath)
214
+ let envSrc = await fsx.readFileX(filepath, { encoding })
88
215
 
89
216
  // blank files seeded by `set` should contain only the key being set
90
217
  if (row.key && envSrc.trim().length === 0) {
@@ -105,11 +232,11 @@ class Sets {
105
232
  let privateKey
106
233
 
107
234
  const { publicKeyName, privateKeyName } = keyNames(filepath)
108
- const { publicKeyValue, privateKeyValue } = keyValues(filepath, { keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
235
+ const { publicKeyValue, privateKeyValue } = await keyValues(filepath, { keysFilepath: this.envKeysFilepath, noOps: this.noOps })
109
236
 
110
237
  // first pass - provision
111
238
  if (!privateKeyValue && !publicKeyValue) {
112
- const prov = provision({ envSrc, envFilepath, keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
239
+ const prov = await provision({ envSrc, envFilepath, keysFilepath: this.envKeysFilepath, noOps: this.noOps })
113
240
  envSrc = prov.envSrc
114
241
  publicKey = prov.publicKey
115
242
  privateKey = prov.privateKey
@@ -20,8 +20,8 @@ const success = getColor('amber')
20
20
  const successv = (m) => getColor('amber')(`⟐ ${m}`)
21
21
  const info = getColor('gray')
22
22
  const help = getColor('dodgerblue')
23
- const verbose = getColor('plum')
24
- const debug = getColor('plum')
23
+ const verbose = (m) => getColor('plum')(`┆ ${m}`)
24
+ const debug = (m) => getColor('plum')(`┆ ${m}`)
25
25
 
26
26
  let currentLevel = levels.info // default log level
27
27
  let currentName = 'dotenvx' // default logger name
@@ -121,7 +121,7 @@ function setLogLevel (options) {
121
121
  logger.setLevel(logLevel)
122
122
  // Only log which level it's setting if it's not set to quiet mode
123
123
  if (!options.quiet || (options.quiet && logLevel !== 'error')) {
124
- logger.debug(`Setting log level to ${logLevel}`)
124
+ logger.debug(`setting log level to: ${logLevel}`)
125
125
  }
126
126
  }
127
127
 
@@ -1,5 +0,0 @@
1
- function sleep (ms) {
2
- return new Promise(resolve => setTimeout(resolve, ms))
3
- }
4
-
5
- module.exports = sleep