@dotenvx/dotenvx 1.59.1 → 1.60.1

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 +14 -4
  3. package/package.json +7 -6
  4. package/src/cli/actions/decrypt.js +15 -8
  5. package/src/cli/actions/encrypt.js +22 -17
  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 +26 -14
  11. package/src/cli/actions/run.js +13 -6
  12. package/src/cli/actions/set.js +31 -17
  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 +15 -11
  25. package/src/lib/helpers/cryptography/provisionSync.js +51 -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 +9 -21
  39. package/src/lib/main.d.ts +20 -4
  40. package/src/lib/main.js +30 -22
  41. package/src/lib/services/decrypt.js +8 -8
  42. package/src/lib/services/encrypt.js +19 -12
  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 +26 -20
  49. package/src/lib/services/run.js +82 -9
  50. package/src/lib/services/sets.js +142 -13
  51. package/src/shared/logger.js +3 -3
  52. package/src/lib/helpers/sleep.js +0 -5
@@ -29,12 +29,12 @@ const detectEncoding = require('./../helpers/detectEncoding')
29
29
  const SAMPLE_ENV_KIT = require('./../helpers/kits/sample')
30
30
 
31
31
  class Encrypt {
32
- constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, opsOn = false, noCreate = false) {
32
+ constructor (envs = [], key = [], excludeKey = [], envKeysFilepath = null, noOps = false, noCreate = 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
  this.noCreate = noCreate
39
39
 
40
40
  this.processedEnvs = []
@@ -42,7 +42,7 @@ class Encrypt {
42
42
  this.unchangedFilepaths = new Set()
43
43
  }
44
44
 
45
- run () {
45
+ async run () {
46
46
  // example
47
47
  // envs [
48
48
  // { type: 'envFile', value: '.env' }
@@ -56,7 +56,7 @@ class Encrypt {
56
56
 
57
57
  for (const env of this.envs) {
58
58
  if (env.type === TYPE_ENV_FILE) {
59
- this._encryptEnvFile(env.value)
59
+ await this._encryptEnvFile(env.value)
60
60
  }
61
61
  }
62
62
 
@@ -67,10 +67,11 @@ class Encrypt {
67
67
  }
68
68
  }
69
69
 
70
- _encryptEnvFile (envFilepath) {
70
+ async _encryptEnvFile (envFilepath) {
71
71
  const row = {}
72
72
  row.keys = []
73
73
  row.type = TYPE_ENV_FILE
74
+ let fileCreated = false
74
75
 
75
76
  const filepath = path.resolve(envFilepath)
76
77
  row.filepath = filepath
@@ -79,14 +80,16 @@ class Encrypt {
79
80
  try {
80
81
  // if noCreate is on then detectEncoding will throw and we'll halt the calls
81
82
  // but if noCreate is false then create the file if it doesn't exist
82
- if (!fsx.existsSync(filepath) && !this.noCreate) {
83
- fsx.writeFileX(filepath, SAMPLE_ENV_KIT)
83
+ if (!(await fsx.exists(filepath)) && !this.noCreate) {
84
+ await fsx.writeFileX(filepath, SAMPLE_ENV_KIT)
85
+ fileCreated = true
84
86
  }
85
- const encoding = detectEncoding(filepath)
86
- let envSrc = fsx.readFileX(filepath, { encoding })
87
+ const encoding = await detectEncoding(filepath)
88
+ let envSrc = await fsx.readFileX(filepath, { encoding })
87
89
  if (envSrc.trim().length === 0) {
88
90
  envSrc = SAMPLE_ENV_KIT
89
91
  row.kitCreated = 'sample'
92
+ row.changed = true
90
93
  }
91
94
  const envParsed = dotenvParse(envSrc)
92
95
 
@@ -94,15 +97,16 @@ class Encrypt {
94
97
  let privateKey
95
98
 
96
99
  const { publicKeyName, privateKeyName } = keyNames(envFilepath)
97
- const { publicKeyValue, privateKeyValue } = keyValues(envFilepath, { keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
100
+ const { publicKeyValue, privateKeyValue } = await keyValues(envFilepath, { keysFilepath: this.envKeysFilepath, noOps: this.noOps })
98
101
 
99
102
  // first pass - provision
100
103
  if (!privateKeyValue && !publicKeyValue) {
101
- const prov = provision({ envSrc, envFilepath, keysFilepath: this.envKeysFilepath, opsOn: this.opsOn })
104
+ const prov = await provision({ envSrc, envFilepath, keysFilepath: this.envKeysFilepath, noOps: this.noOps })
102
105
  envSrc = prov.envSrc
103
106
  publicKey = prov.publicKey
104
107
  privateKey = prov.privateKey
105
- row.privateKeyAdded = prov.privateKeyAdded
108
+ row.localPrivateKeyAdded = prov.localPrivateKeyAdded
109
+ row.remotePrivateKeyAdded = prov.remotePrivateKeyAdded
106
110
  row.envKeysFilepath = prov.envKeysFilepath
107
111
  } else if (privateKeyValue) {
108
112
  const prov = provisionWithPrivateKey({ envSrc, envFilepath, keysFilepath: this.envKeysFilepath, privateKeyValue, publicKeyValue, publicKeyName })
@@ -148,6 +152,9 @@ class Encrypt {
148
152
  }
149
153
 
150
154
  row.envSrc = envSrc
155
+ if (fileCreated) {
156
+ row.changed = true
157
+ }
151
158
  if (row.changed) {
152
159
  this.changedFilepaths.add(envFilepath)
153
160
  } else {
@@ -39,7 +39,7 @@ class Genexample {
39
39
  }
40
40
 
41
41
  // get the original src
42
- let src = fsx.readFileX(filepath)
42
+ let src = fsx.readFileXSync(filepath)
43
43
  const parsed = dotenvParse(src)
44
44
  for (const key in parsed) {
45
45
  // used later
@@ -63,7 +63,7 @@ class Genexample {
63
63
  }
64
64
  } else {
65
65
  // it already exists (which means the user might have it modified a way in which they prefer, so replace exampleSrc with their existing .env.example)
66
- exampleSrc = fsx.readFileX(this.exampleFilepath)
66
+ exampleSrc = fsx.readFileXSync(this.exampleFilepath)
67
67
 
68
68
  const parsed = dotenvParse(exampleSrc)
69
69
  for (const key of [...keys]) {
@@ -2,19 +2,28 @@ const Run = require('./run')
2
2
  const Errors = require('./../helpers/errors')
3
3
 
4
4
  class Get {
5
- constructor (key, envs = [], overload = false, all = false, envKeysFilepath = null, opsOn = true) {
5
+ constructor (key, envs = [], overload = false, all = false, envKeysFilepath = null, noOps = false) {
6
6
  this.key = key
7
7
  this.envs = envs
8
8
  this.overload = overload
9
9
  this.all = all
10
10
  this.envKeysFilepath = envKeysFilepath
11
- this.opsOn = opsOn
11
+ this.noOps = noOps
12
12
  }
13
13
 
14
- run () {
14
+ runSync () {
15
15
  const processEnv = { ...process.env }
16
- const { processedEnvs } = new Run(this.envs, this.overload, processEnv, this.envKeysFilepath, this.opsOn).run()
16
+ const { processedEnvs } = new Run(this.envs, this.overload, processEnv, this.envKeysFilepath, this.noOps).runSync()
17
+ return this._result(processedEnvs, processEnv)
18
+ }
19
+
20
+ async run () {
21
+ const processEnv = { ...process.env }
22
+ const { processedEnvs } = await new Run(this.envs, this.overload, processEnv, this.envKeysFilepath, this.noOps).run()
23
+ return this._result(processedEnvs, processEnv)
24
+ }
17
25
 
26
+ _result (processedEnvs, processEnv) {
18
27
  const errors = []
19
28
  for (const processedEnv of processedEnvs) {
20
29
  for (const error of processedEnv.errors) {
@@ -32,27 +41,27 @@ class Get {
32
41
  }
33
42
 
34
43
  return { parsed, errors }
35
- } else {
36
- // if user wants to return ALL envs (even prior set on machine)
37
- if (this.all) {
38
- return { parsed: processEnv, errors }
39
- }
44
+ }
40
45
 
41
- // typical scenario - return only envs that were identified in the .env file
42
- // iterate over all processedEnvs.parsed and grab from processEnv
43
- /** @type {Record<string, string>} */
44
- const parsed = {}
45
- for (const processedEnv of processedEnvs) {
46
- // parsed means we saw the key in a file or --env flag. this effectively filters out any preset machine envs - while still respecting complex evaluating, expansion, and overload. in other words, the value might be the machine value because the key was displayed in a .env file
47
- if (processedEnv.parsed) {
48
- for (const key of Object.keys(processedEnv.parsed)) {
49
- parsed[key] = processEnv[key]
50
- }
46
+ // if user wants to return ALL envs (even prior set on machine)
47
+ if (this.all) {
48
+ return { parsed: processEnv, errors }
49
+ }
50
+
51
+ // typical scenario - return only envs that were identified in the .env file
52
+ // iterate over all processedEnvs.parsed and grab from processEnv
53
+ /** @type {Record<string, string>} */
54
+ const parsed = {}
55
+ for (const processedEnv of processedEnvs) {
56
+ // parsed means we saw the key in a file or --env flag. this effectively filters out any preset machine envs - while still respecting complex evaluating, expansion, and overload. in other words, the value might be the machine value because the key was displayed in a .env file
57
+ if (processedEnv.parsed) {
58
+ for (const key of Object.keys(processedEnv.parsed)) {
59
+ parsed[key] = processEnv[key]
51
60
  }
52
61
  }
53
-
54
- return { parsed, errors }
55
62
  }
63
+
64
+ return { parsed, errors }
56
65
  }
57
66
  }
58
67
 
@@ -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,38 +78,38 @@ 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
- row.privateKeyAdded = false // TODO: change to localPrivateKeyAdded
98
+ row.localPrivateKeyAdded = false
99
+ row.remotePrivateKeyAdded = true
99
100
  } 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) })
101
+ row.envKeysFilepath = this.envKeysFilepath || path.join(path.dirname(envFilepath), '.env.keys')
102
+ envKeysFilepath = path.resolve(row.envKeysFilepath)
103
+ const encodingForKeys = await detectEncoding(envKeysFilepath)
104
+ this.envKeysSources[envKeysFilepath] ||= await fsx.readFileX(envKeysFilepath, { encoding: encodingForKeys })
106
105
  envKeysSrc = this.envKeysSources[envKeysFilepath]
107
106
 
108
107
  const kp = localKeypair()
109
108
  newPublicKey = kp.publicKey
110
109
  newPrivateKey = kp.privateKey
111
110
 
112
- row.privateKeyAdded = true
111
+ row.localPrivateKeyAdded = true
112
+ row.remotePrivateKeyAdded = false
113
113
  }
114
114
 
115
115
  // .env
@@ -145,7 +145,7 @@ class Rotate {
145
145
  row.privateKeyName = privateKeyName
146
146
  row.privateKey = newPrivateKey
147
147
 
148
- if (!this.opsOn) {
148
+ if (this.noOps) {
149
149
  // keys src only for ops
150
150
  envKeysSrc = append(envKeysSrc, privateKeyName, newPrivateKey) // append privateKey
151
151
  this.envKeysSources[envKeysFilepath] = envKeysSrc
@@ -155,7 +155,13 @@ class Rotate {
155
155
  this.changedFilepaths.add(envFilepath)
156
156
  } catch (e) {
157
157
  if (e.code === 'ENOENT') {
158
- row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
158
+ const missingPath = e.path ? path.resolve(e.path) : null
159
+ const expectedEnvKeysPath = row.envKeysFilepath ? path.resolve(row.envKeysFilepath) : null
160
+ if (this.noOps && expectedEnvKeysPath && missingPath === expectedEnvKeysPath) {
161
+ row.error = new Errors({ envKeysFilepath: row.envKeysFilepath }).missingEnvKeysFile()
162
+ } else {
163
+ row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
164
+ }
159
165
  } else {
160
166
  row.error = e
161
167
  }
@@ -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,