@dotenvx/dotenvx 1.75.0 → 2.0.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 (89) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/README.md +4 -114
  3. package/package.json +8 -7
  4. package/src/cli/actions/decrypt.js +35 -17
  5. package/src/cli/actions/doctor.js +2 -2
  6. package/src/cli/actions/encrypt.js +64 -85
  7. package/src/cli/actions/ext/genexample.js +2 -2
  8. package/src/cli/actions/get.js +16 -7
  9. package/src/cli/actions/keypair.js +55 -42
  10. package/src/cli/actions/login.js +4 -3
  11. package/src/cli/actions/normalizeArmorAliases.js +9 -0
  12. package/src/cli/actions/run.js +65 -22
  13. package/src/cli/actions/set.js +51 -37
  14. package/src/cli/dotenvx.js +6 -21
  15. package/src/lib/api/{postKeypair.js → postArmorKeyring.js} +4 -14
  16. package/src/lib/{helpers/envResolution → conventions}/environment.js +1 -1
  17. package/src/lib/conventions/filepaths.js +9 -0
  18. package/src/lib/conventions/keynames.js +38 -0
  19. package/src/lib/helpers/catchAndLog.js +4 -1
  20. package/src/lib/helpers/cryptography/index.js +1 -12
  21. package/src/lib/helpers/cryptography/mutateKeysSrc.js +5 -21
  22. package/src/lib/helpers/envResolution/index.js +1 -1
  23. package/src/lib/helpers/errors.js +13 -0
  24. package/src/lib/helpers/executeDynamic.js +1 -3
  25. package/src/lib/helpers/keypairMetadata.js +3 -3
  26. package/src/lib/helpers/readEnvKey.js +4 -3
  27. package/src/lib/main.d.ts +2 -39
  28. package/src/lib/main.js +82 -45
  29. package/src/lib/providers/armor/index.js +29 -0
  30. package/src/lib/providers/index.js +46 -0
  31. package/src/lib/providers/worker.js +6 -0
  32. package/src/lib/resolvers/envs.js +302 -0
  33. package/src/lib/resolvers/get.js +69 -0
  34. package/src/lib/resolvers/keypair.js +81 -0
  35. package/src/lib/resolvers/ls.js +41 -0
  36. package/src/lib/services/armorDown.js +2 -2
  37. package/src/lib/services/{armorKeypair.js → armorKeyring.js} +14 -51
  38. package/src/lib/services/armorMove.js +2 -2
  39. package/src/lib/services/armorPull.js +2 -2
  40. package/src/lib/services/armorPush.js +2 -2
  41. package/src/lib/services/armorUp.js +2 -2
  42. package/src/lib/services/genexample.js +19 -8
  43. package/src/lib/services/login.js +25 -0
  44. package/src/lib/services/prebuild.js +11 -5
  45. package/src/lib/services/precommit.js +11 -5
  46. package/src/lib/transforms/decrypt.js +83 -0
  47. package/src/lib/transforms/encrypt.js +179 -0
  48. package/src/lib/transforms/set.js +179 -0
  49. package/src/shared/logger.js +5 -0
  50. package/src/cli/actions/normalizeArmorOptions.js +0 -10
  51. package/src/cli/actions/rotate.js +0 -87
  52. package/src/lib/helpers/append.js +0 -61
  53. package/src/lib/helpers/cryptography/armorKeypair.js +0 -42
  54. package/src/lib/helpers/cryptography/armorKeypairSync.js +0 -55
  55. package/src/lib/helpers/cryptography/encryptValue.js +0 -7
  56. package/src/lib/helpers/cryptography/localKeypair.js +0 -14
  57. package/src/lib/helpers/cryptography/mutateKeysSrcSync.js +0 -38
  58. package/src/lib/helpers/cryptography/provision.js +0 -56
  59. package/src/lib/helpers/cryptography/provisionSync.js +0 -51
  60. package/src/lib/helpers/cryptography/provisionWithPrivateKey.js +0 -26
  61. package/src/lib/helpers/dotenvParse.js +0 -55
  62. package/src/lib/helpers/escapeDollarSigns.js +0 -5
  63. package/src/lib/helpers/escapeForRegex.js +0 -5
  64. package/src/lib/helpers/evalKeyValue.js +0 -23
  65. package/src/lib/helpers/isFullyEncrypted.js +0 -27
  66. package/src/lib/helpers/isIgnoringDotenvKeys.js +0 -19
  67. package/src/lib/helpers/keyResolution/index.js +0 -16
  68. package/src/lib/helpers/keyResolution/keyNamesForEnvFile.js +0 -24
  69. package/src/lib/helpers/keyResolution/keyValues.js +0 -99
  70. package/src/lib/helpers/keyResolution/keyValuesFromEnvSrc.js +0 -80
  71. package/src/lib/helpers/keyResolution/keyValuesSync.js +0 -103
  72. package/src/lib/helpers/keyResolution/readFileKey.js +0 -17
  73. package/src/lib/helpers/keyResolution/readFileKeySync.js +0 -15
  74. package/src/lib/helpers/keyResolution/readProcessKey.js +0 -7
  75. package/src/lib/helpers/localDisplayPath.js +0 -11
  76. package/src/lib/helpers/parse.js +0 -214
  77. package/src/lib/helpers/pluralize.js +0 -10
  78. package/src/lib/helpers/quotes.js +0 -36
  79. package/src/lib/helpers/replace.js +0 -82
  80. package/src/lib/helpers/resolveEscapeSequences.js +0 -5
  81. package/src/lib/services/decrypt.js +0 -157
  82. package/src/lib/services/encrypt.js +0 -214
  83. package/src/lib/services/get.js +0 -76
  84. package/src/lib/services/keypair.js +0 -60
  85. package/src/lib/services/loginPoll.js +0 -36
  86. package/src/lib/services/ls.js +0 -57
  87. package/src/lib/services/rotate.js +0 -198
  88. package/src/lib/services/run.js +0 -255
  89. package/src/lib/services/sets.js +0 -326
@@ -3,8 +3,19 @@ const path = require('path')
3
3
 
4
4
  const Errors = require('../helpers/errors')
5
5
  const findEnvFiles = require('../helpers/findEnvFiles')
6
- const replace = require('../helpers/replace')
7
- const dotenvParse = require('../helpers/dotenvParse')
6
+ const { scan, upsert } = require('@dotenvx/primitives')
7
+
8
+ function finalValues (src) {
9
+ const { parsed } = scan(src)
10
+ const result = {}
11
+
12
+ for (const key in parsed) {
13
+ const values = parsed[key]
14
+ result[key] = values[values.length - 1]
15
+ }
16
+
17
+ return result
18
+ }
8
19
 
9
20
  class Genexample {
10
21
  constructor (directory = '.', envFile) {
@@ -26,7 +37,7 @@ class Genexample {
26
37
  /** @type {Record<string, string>} */
27
38
  const injected = {}
28
39
  /** @type {Record<string, string>} */
29
- const preExisted = {}
40
+ const existed = {}
30
41
 
31
42
  let exampleSrc = `# ${this.exampleFilename} - generated with dotenvx\n`
32
43
 
@@ -40,13 +51,13 @@ class Genexample {
40
51
 
41
52
  // get the original src
42
53
  let src = fsx.readFileXSync(filepath)
43
- const parsed = dotenvParse(src)
54
+ const parsed = finalValues(src)
44
55
  for (const key in parsed) {
45
56
  // used later
46
57
  keys.add(key)
47
58
 
48
59
  // once newSrc is built write it out
49
- src = replace(src, key, '') // empty value
60
+ src = upsert(src, key, '') // empty value
50
61
  }
51
62
 
52
63
  exampleSrc += `\n${src}`
@@ -65,10 +76,10 @@ class Genexample {
65
76
  // 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
77
  exampleSrc = fsx.readFileXSync(this.exampleFilepath)
67
78
 
68
- const parsed = dotenvParse(exampleSrc)
79
+ const parsed = finalValues(exampleSrc)
69
80
  for (const key of [...keys]) {
70
81
  if (key in parsed) {
71
- preExisted[key] = parsed[key]
82
+ existed[key] = parsed[key]
72
83
  } else {
73
84
  exampleSrc += `${key}=''\n`
74
85
 
@@ -85,7 +96,7 @@ class Genexample {
85
96
  exampleFilepath: this.exampleFilepath,
86
97
  addedKeys: [...addedKeys],
87
98
  injected,
88
- preExisted
99
+ existed
89
100
  }
90
101
  }
91
102
 
@@ -1,5 +1,6 @@
1
1
  const Session = require('../../db/session')
2
2
  const PostOauthDeviceCode = require('../api/postOauthDeviceCode')
3
+ const PostOauthToken = require('../api/postOauthToken')
3
4
 
4
5
  class Login {
5
6
  constructor (hostname) {
@@ -21,6 +22,30 @@ class Login {
21
22
  interval: data.interval
22
23
  }
23
24
  }
25
+
26
+ async poll (deviceCode, interval) {
27
+ const sesh = new Session()
28
+
29
+ while (true) {
30
+ try {
31
+ const data = await new PostOauthToken(this.hostname, deviceCode).run()
32
+
33
+ if (data.access_token) {
34
+ sesh.login(this.hostname, data.id, data.username, data.access_token)
35
+ await sesh.notifyUpdate()
36
+ return data
37
+ }
38
+
39
+ await new Promise(resolve => setTimeout(resolve, interval * 1000))
40
+ } catch (error) {
41
+ if (error.code === 'authorization_pending') {
42
+ await new Promise(resolve => setTimeout(resolve, (interval + 1) * 1000))
43
+ } else {
44
+ throw error
45
+ }
46
+ }
47
+ }
48
+ }
24
49
  }
25
50
 
26
51
  module.exports = Login
@@ -3,10 +3,10 @@ const fsx = require('./../helpers/fsx')
3
3
  const path = require('path')
4
4
  const ignore = require('ignore')
5
5
 
6
- const Ls = require('../services/ls')
6
+ const ls = require('../resolvers/ls')
7
7
  const Errors = require('../helpers/errors')
8
+ const { sealed } = require('@dotenvx/primitives')
8
9
 
9
- const isFullyEncrypted = require('./../helpers/isFullyEncrypted')
10
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
11
11
 
12
12
  class Prebuild {
@@ -35,8 +35,7 @@ class Prebuild {
35
35
 
36
36
  // 2. check .env* files against .dockerignore file
37
37
  const ig = ignore().add(dockerignore)
38
- const lsService = new Ls(this.directory, undefined, this.excludeEnvFile)
39
- const dotenvFiles = lsService.run()
38
+ const dotenvFiles = this._filepaths()
40
39
  dotenvFiles.forEach(_file => {
41
40
  count += 1
42
41
 
@@ -54,7 +53,7 @@ class Prebuild {
54
53
  } else {
55
54
  if (file !== '.env.example' && file !== '.env.x') {
56
55
  const src = fsx.readFileXSync(file)
57
- const encrypted = isFullyEncrypted(src)
56
+ const encrypted = sealed(src)
58
57
 
59
58
  // if contents are encrypted don't raise an error
60
59
  if (!encrypted) {
@@ -81,6 +80,13 @@ class Prebuild {
81
80
  warnings
82
81
  }
83
82
  }
83
+
84
+ _filepaths () {
85
+ return ls({
86
+ directory: this.directory,
87
+ excludeEnvFile: this.excludeEnvFile
88
+ })
89
+ }
84
90
  }
85
91
 
86
92
  module.exports = Prebuild
@@ -3,9 +3,9 @@ const fsx = require('./../helpers/fsx')
3
3
  const path = require('path')
4
4
  const ignore = require('ignore')
5
5
 
6
- const Ls = require('../services/ls')
6
+ const ls = require('../resolvers/ls')
7
+ const { sealed } = require('@dotenvx/primitives')
7
8
 
8
- const isFullyEncrypted = require('./../helpers/isFullyEncrypted')
9
9
  const InstallPrecommitHook = require('./../helpers/installPrecommitHook')
10
10
  const Errors = require('./../helpers/errors')
11
11
  const childProcess = require('child_process')
@@ -49,8 +49,7 @@ class Precommit {
49
49
  // 2. check .env* files against .gitignore file
50
50
  const ig = ignore().add(gitignore)
51
51
 
52
- const lsService = new Ls(this.directory, undefined, this.excludeEnvFile)
53
- const dotenvFiles = lsService.run()
52
+ const dotenvFiles = this._filepaths()
54
53
  dotenvFiles.forEach(_file => {
55
54
  count += 1
56
55
 
@@ -70,7 +69,7 @@ class Precommit {
70
69
  } else {
71
70
  if (file !== '.env.example' && file !== '.env.x') {
72
71
  const src = fsx.readFileXSync(file)
73
- const encrypted = isFullyEncrypted(src)
72
+ const encrypted = sealed(src)
74
73
 
75
74
  // if contents are encrypted don't raise an error
76
75
  if (!encrypted) {
@@ -100,6 +99,13 @@ class Precommit {
100
99
  }
101
100
  }
102
101
 
102
+ _filepaths () {
103
+ return ls({
104
+ directory: this.directory,
105
+ excludeEnvFile: this.excludeEnvFile
106
+ })
107
+ }
108
+
103
109
  _isInGitRepo () {
104
110
  try {
105
111
  childProcess.execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' })
@@ -0,0 +1,83 @@
1
+ const fsx = require('./../helpers/fsx')
2
+ const path = require('path')
3
+ const { parse, upsert } = require('@dotenvx/primitives')
4
+
5
+ const TYPE_ENV_FILE = 'envFile'
6
+
7
+ const Errors = require('./../helpers/errors')
8
+ const { determine } = require('./../helpers/envResolution')
9
+ const detectEncoding = require('./../helpers/detectEncoding')
10
+ const providers = require('./../providers')
11
+
12
+ function parseError (error) {
13
+ return new Errors({
14
+ code: error.code,
15
+ message: error.message,
16
+ help: error.help
17
+ }).custom()
18
+ }
19
+
20
+ async function decrypt (options = {}) {
21
+ const envs = options.envs || []
22
+ const ik = options.ik
23
+ const ek = options.ek
24
+ const fk = options.fk
25
+ const provider = options.noArmor ? null : await providers(options)
26
+
27
+ const processedEnvs = []
28
+ const changedFilepaths = []
29
+ const unchangedFilepaths = []
30
+
31
+ for (const env of determine(envs, process.env)) {
32
+ if (env.type !== TYPE_ENV_FILE) {
33
+ continue
34
+ }
35
+
36
+ const envFilepath = env.envFilepath || env.value
37
+ const filepath = env.filepath || path.resolve(envFilepath)
38
+ const row = { keys: [], type: TYPE_ENV_FILE, filepath, envFilepath, changed: false }
39
+
40
+ try {
41
+ const encoding = await detectEncoding(filepath)
42
+ row.envSrc = await fsx.readFileX(filepath, { encoding })
43
+
44
+ const { parsed, errors } = await parse(row.envSrc, { fk, ik, ek, array: true, provider })
45
+
46
+ if (errors.length > 0) {
47
+ row.error = parseError(errors[0])
48
+ }
49
+
50
+ for (const [key, values] of Object.entries(parsed)) {
51
+ const before = row.envSrc
52
+ const after = upsert(before, key, values)
53
+ if (after !== before) {
54
+ row.envSrc = after
55
+ row.changed = true
56
+ row.keys.push(key)
57
+ }
58
+ }
59
+
60
+ if (row.changed) {
61
+ changedFilepaths.push(envFilepath)
62
+ } else {
63
+ unchangedFilepaths.push(envFilepath)
64
+ }
65
+ } catch (error) {
66
+ if (error.code === 'ENOENT') {
67
+ row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
68
+ } else {
69
+ row.error = error
70
+ }
71
+ }
72
+
73
+ processedEnvs.push(row)
74
+ }
75
+
76
+ return {
77
+ processedEnvs,
78
+ changedFilepaths,
79
+ unchangedFilepaths
80
+ }
81
+ }
82
+
83
+ module.exports = decrypt
@@ -0,0 +1,179 @@
1
+ const fsx = require('./../helpers/fsx')
2
+ const path = require('path')
3
+ const { encrypted, encrypt, scan, upsert, publickeys, keypair } = require('@dotenvx/primitives')
4
+
5
+ const TYPE_ENV_FILE = 'envFile'
6
+ const SAMPLE_ENV_KIT = require('../helpers/kits/sample')
7
+
8
+ const Errors = require('../helpers/errors')
9
+ const { determine } = require('./../helpers/envResolution')
10
+ const detectEncoding = require('./../helpers/detectEncoding')
11
+ const { isPublicKey, mutateSrc, mutateKeysSrc } = require('../helpers/cryptography')
12
+ const keynames = require('../conventions/keynames')
13
+ const PostArmorUp = require('../api/postArmorUp')
14
+ const prompts = require('../helpers/prompts')
15
+ const teamChoicesFromMeta = require('../helpers/teamChoicesFromMeta')
16
+ const Session = require('../../db/session')
17
+
18
+ async function selectKeyStorage () {
19
+ const selected = await prompts.select({
20
+ message: 'Choose private key storage',
21
+ choices: [
22
+ { name: '◫ File (.env.keys)', value: 'file' },
23
+ { name: '⛨ Armor (armor.dotenvx.com)', value: 'armored' }
24
+ ]
25
+ }, {
26
+ input: process.stdin,
27
+ output: process.stderr
28
+ })
29
+
30
+ return selected
31
+ }
32
+
33
+ async function encryptTransform (options = {}) {
34
+ const envs = options.envs || []
35
+ const ik = options.ik
36
+ const ek = options.ek
37
+ const fk = options.fk || '.env.keys'
38
+ let noArmor = options.noArmor // key storage selector below
39
+ const noCreate = options.noCreate
40
+
41
+ const processedEnvs = []
42
+ const changedFilepaths = []
43
+ const unchangedFilepaths = []
44
+
45
+ // set up keysSrc
46
+ let keysSrc
47
+ if (await fsx.exists(fk)) {
48
+ const encoding = await detectEncoding(fk)
49
+ keysSrc = await fsx.readFileX(fk, { encoding })
50
+ }
51
+
52
+ for (const env of determine(envs, process.env)) {
53
+ if (env.type !== TYPE_ENV_FILE) {
54
+ continue
55
+ }
56
+
57
+ const envFilepath = env.envFilepath || env.value
58
+ const filepath = env.filepath || path.resolve(envFilepath)
59
+ const row = { keys: [], type: TYPE_ENV_FILE, filepath, envFilepath, changed: false }
60
+
61
+ try {
62
+ const fileExists = await fsx.exists(filepath)
63
+ if (!fileExists && !noCreate) {
64
+ row.envSrc = SAMPLE_ENV_KIT
65
+ row.changed = true
66
+ } else {
67
+ const encoding = await detectEncoding(filepath)
68
+ row.envSrc = await fsx.readFileX(filepath, { encoding })
69
+ }
70
+
71
+ if (row.envSrc.trim().length === 0) {
72
+ row.envSrc = SAMPLE_ENV_KIT
73
+ row.changed = true
74
+ }
75
+
76
+ let publicKey = publickeys(row.envSrc)[0]
77
+
78
+ if (!publicKey) {
79
+ if (!noCreate && !noArmor && selectKeyStorage) {
80
+ noArmor = await selectKeyStorage() !== 'armored'
81
+ }
82
+
83
+ // upsert public key to .env file
84
+ const kp = keypair() // local
85
+ publicKey = kp.publicKey
86
+ const privateKey = kp.privateKey
87
+ const { publicKeyName, privateKeyName } = keynames(envFilepath)
88
+ const { envSrc } = mutateSrc({ envSrc: row.envSrc, envFilepath, keysFilepath: fk, publicKeyName, publicKeyValue: publicKey })
89
+ row.envSrc = envSrc
90
+
91
+ const comment = path.basename(envFilepath)
92
+
93
+ if (noArmor) {
94
+ const mutated = mutateKeysSrc({ keysSrc, privateKeyName, privateKeyValue: privateKey, comment })
95
+ keysSrc = mutated.keysSrc
96
+ } else {
97
+ const sesh = new Session()
98
+ const hostname = sesh.hostname()
99
+ const token = sesh.token()
100
+ const devicePublicKey = sesh.devicePublicKey()
101
+
102
+ try {
103
+ await new PostArmorUp(hostname, token, devicePublicKey, publicKey, privateKey, undefined).run()
104
+ } catch (error) {
105
+ if (error.code !== 'DOTENVX_TEAM_REQUIRED') {
106
+ throw error
107
+ }
108
+
109
+ const choices = teamChoicesFromMeta(error.meta)
110
+
111
+ let team = choices[0].value
112
+ if (choices.length > 1) {
113
+ team = await prompts.select({
114
+ message: 'Select team',
115
+ choices
116
+ }, {
117
+ input: process.stdin,
118
+ output: process.stderr
119
+ })
120
+ }
121
+
122
+ await new PostArmorUp(hostname, token, devicePublicKey, publicKey, privateKey, team).run()
123
+ }
124
+
125
+ // don't set keysSrc (in armor)
126
+ }
127
+ }
128
+
129
+ const { parsed } = scan(row.envSrc, { ik, ek })
130
+
131
+ for (const [key, values] of Object.entries(parsed)) {
132
+ // skip if public key
133
+ if (isPublicKey(key)) {
134
+ continue
135
+ }
136
+
137
+ const transformedValues = []
138
+ for (const value of values) {
139
+ if (encrypted(value)) {
140
+ transformedValues.push(value) // unchanged
141
+ } else {
142
+ const encryptedValue = encrypt(publicKey, value)
143
+ transformedValues.push(encryptedValue)
144
+ }
145
+ }
146
+
147
+ const before = row.envSrc
148
+ row.envSrc = upsert(row.envSrc, key, transformedValues)
149
+ if (row.envSrc !== before) {
150
+ row.changed = true
151
+ row.keys.push(key)
152
+ }
153
+ }
154
+
155
+ if (row.changed) {
156
+ changedFilepaths.push(envFilepath)
157
+ } else {
158
+ unchangedFilepaths.push(envFilepath)
159
+ }
160
+ } catch (error) {
161
+ if (error.code === 'ENOENT') {
162
+ row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
163
+ } else {
164
+ row.error = error
165
+ }
166
+ }
167
+
168
+ processedEnvs.push(row)
169
+ }
170
+
171
+ return {
172
+ keysSrc,
173
+ processedEnvs,
174
+ changedFilepaths,
175
+ unchangedFilepaths
176
+ }
177
+ }
178
+
179
+ module.exports = encryptTransform
@@ -0,0 +1,179 @@
1
+ const fsx = require('./../helpers/fsx')
2
+ const path = require('path')
3
+ const { encrypt, upsert, publickeys, keypair } = require('@dotenvx/primitives')
4
+
5
+ const TYPE_ENV_FILE = 'envFile'
6
+
7
+ const getResolver = require('./../resolvers/get')
8
+ const { determine } = require('./../helpers/envResolution')
9
+ const detectEncoding = require('./../helpers/detectEncoding')
10
+ const { mutateSrc, mutateKeysSrc } = require('../helpers/cryptography')
11
+ const keynames = require('../conventions/keynames')
12
+ const Errors = require('../helpers/errors')
13
+ const PostArmorUp = require('../api/postArmorUp')
14
+ const prompts = require('../helpers/prompts')
15
+ const teamChoicesFromMeta = require('../helpers/teamChoicesFromMeta')
16
+ const Session = require('../../db/session')
17
+
18
+ async function selectKeyStorage () {
19
+ const selected = await prompts.select({
20
+ message: 'Choose private key storage',
21
+ choices: [
22
+ { name: '◫ File (.env.keys)', value: 'file' },
23
+ { name: '⛨ Armor (armor.dotenvx.com)', value: 'armored' }
24
+ ]
25
+ }, {
26
+ input: process.stdin,
27
+ output: process.stderr
28
+ })
29
+
30
+ return selected
31
+ }
32
+
33
+ async function setTransform (options = {}) {
34
+ const envs = options.envs || []
35
+ const key = options.key
36
+ const value = options.value
37
+ const fk = options.fk || '.env.keys'
38
+ let noArmor = options.noArmor // key storage selector below
39
+ const noCreate = options.noCreate
40
+ const noEncrypt = !options.encrypt
41
+
42
+ const processedEnvs = []
43
+ const changedFilepaths = []
44
+ const unchangedFilepaths = []
45
+
46
+ // set up keysSrc
47
+ let keysSrc
48
+ if (await fsx.exists(fk)) {
49
+ const encoding = await detectEncoding(fk)
50
+ keysSrc = await fsx.readFileX(fk, { encoding })
51
+ }
52
+
53
+ for (const env of determine(envs, process.env)) {
54
+ if (env.type !== TYPE_ENV_FILE) {
55
+ continue
56
+ }
57
+
58
+ const envFilepath = env.envFilepath || env.value
59
+ const filepath = env.filepath || path.resolve(envFilepath)
60
+ const row = { key, value, type: TYPE_ENV_FILE, filepath, envFilepath, changed: false }
61
+
62
+ try {
63
+ const fileExists = await fsx.exists(filepath)
64
+ if (!fileExists && !noCreate) {
65
+ row.envSrc = ''
66
+ row.changed = true
67
+ } else {
68
+ const encoding = await detectEncoding(filepath)
69
+ row.envSrc = await fsx.readFileX(filepath, { encoding })
70
+ }
71
+
72
+ if (row.envSrc.trim().length === 0) {
73
+ row.envSrc = ''
74
+ row.changed = true
75
+ }
76
+
77
+ let publicKey = publickeys(row.envSrc)[0]
78
+
79
+ // only create if missing public key and encryption needed
80
+ if (!publicKey && !noEncrypt) {
81
+ if (!noCreate && !noArmor && selectKeyStorage) {
82
+ noArmor = await selectKeyStorage() !== 'armored'
83
+ }
84
+
85
+ // upsert public key to .env file
86
+ const kp = keypair() // local
87
+ publicKey = kp.publicKey
88
+ const privateKey = kp.privateKey
89
+ const { publicKeyName, privateKeyName } = keynames(envFilepath)
90
+ const { envSrc } = mutateSrc({ envSrc: row.envSrc, envFilepath, keysFilepath: fk, publicKeyName, publicKeyValue: publicKey })
91
+ row.envSrc = envSrc
92
+
93
+ const comment = path.basename(envFilepath)
94
+
95
+ if (noArmor) {
96
+ const mutated = mutateKeysSrc({ keysSrc, privateKeyName, privateKeyValue: privateKey, comment })
97
+ keysSrc = mutated.keysSrc
98
+ } else {
99
+ const sesh = new Session()
100
+ const hostname = sesh.hostname()
101
+ const token = sesh.token()
102
+ const devicePublicKey = sesh.devicePublicKey()
103
+
104
+ try {
105
+ await new PostArmorUp(hostname, token, devicePublicKey, publicKey, privateKey, undefined).run()
106
+ } catch (error) {
107
+ if (error.code !== 'DOTENVX_TEAM_REQUIRED') {
108
+ throw error
109
+ }
110
+
111
+ const choices = teamChoicesFromMeta(error.meta)
112
+
113
+ let team = choices[0].value
114
+ if (choices.length > 1) {
115
+ team = await prompts.select({
116
+ message: 'Select team',
117
+ choices
118
+ }, {
119
+ input: process.stdin,
120
+ output: process.stderr
121
+ })
122
+ }
123
+
124
+ await new PostArmorUp(hostname, token, devicePublicKey, publicKey, privateKey, team).run()
125
+ }
126
+
127
+ // don't set keysSrc (in armor)
128
+ }
129
+ }
130
+
131
+ if (noEncrypt) {
132
+ const before = row.envSrc
133
+ row.envSrc = upsert(row.envSrc, key, value)
134
+ if (row.envSrc !== before) {
135
+ row.changed = true
136
+ }
137
+ } else {
138
+ // expensive additional loop
139
+ const { parsed } = await getResolver({
140
+ key,
141
+ envs: [env],
142
+ all: true,
143
+ envKeysFile: fk,
144
+ noArmor
145
+ })
146
+
147
+ const before = parsed[key]
148
+ if (value !== before) {
149
+ const encryptedValue = encrypt(publicKey, value)
150
+ row.envSrc = upsert(row.envSrc, key, encryptedValue)
151
+ row.changed = true
152
+ }
153
+ }
154
+
155
+ if (row.changed) {
156
+ changedFilepaths.push(envFilepath)
157
+ } else {
158
+ unchangedFilepaths.push(envFilepath)
159
+ }
160
+ } catch (error) {
161
+ if (error.code === 'ENOENT') {
162
+ row.error = new Errors({ envFilepath, filepath }).missingEnvFile()
163
+ } else {
164
+ row.error = error
165
+ }
166
+ }
167
+
168
+ processedEnvs.push(row)
169
+ }
170
+
171
+ return {
172
+ keysSrc,
173
+ processedEnvs,
174
+ changedFilepaths,
175
+ unchangedFilepaths
176
+ }
177
+ }
178
+
179
+ module.exports = setTransform
@@ -4,6 +4,7 @@ const { getColor, bold } = require('./colors')
4
4
 
5
5
  const levels = {
6
6
  error: 0,
7
+ infoerror: 0,
7
8
  warn: 1,
8
9
  success: 2,
9
10
  successv: 2,
@@ -15,6 +16,7 @@ const levels = {
15
16
  }
16
17
 
17
18
  const error = (m) => bold(getColor('red')(`☠ ${m}`))
19
+ const infoerror = getColor('gray') // actually an error
18
20
  const warn = (m) => getColor('orangered')(`⚠ ${m}`)
19
21
  const success = getColor('amber')
20
22
  const successv = (m) => getColor('amber')(`⟐ ${m}`)
@@ -50,6 +52,8 @@ function formatMessage (level, message) {
50
52
  // errors
51
53
  case 'error':
52
54
  return error(formattedMessage)
55
+ case 'infoerror':
56
+ return infoerror(formattedMessage)
53
57
  // warns
54
58
  case 'warn':
55
59
  return warn(formattedMessage)
@@ -79,6 +83,7 @@ const logger = {
79
83
 
80
84
  // errors
81
85
  error: (msg) => stderr('error', msg),
86
+ infoerror: (msg) => stderr('infoerror', msg),
82
87
  // warns
83
88
  warn: (msg) => stdout('warn', msg),
84
89
  // success
@@ -1,10 +0,0 @@
1
- function normalizeArmorOptions (options) {
2
- if (options.armor === false || options.ops === false) {
3
- options.armor = false
4
- options.ops = false
5
- }
6
-
7
- return options
8
- }
9
-
10
- module.exports = normalizeArmorOptions