@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.
- package/CHANGELOG.md +18 -1
- package/README.md +130 -137
- package/package.json +7 -6
- package/src/cli/actions/decrypt.js +15 -8
- package/src/cli/actions/encrypt.js +15 -8
- package/src/cli/actions/ext/genexample.js +2 -2
- package/src/cli/actions/ext/gitignore.js +3 -3
- package/src/cli/actions/get.js +12 -7
- package/src/cli/actions/keypair.js +13 -5
- package/src/cli/actions/rotate.js +16 -9
- package/src/cli/actions/run.js +13 -6
- package/src/cli/actions/set.js +19 -12
- package/src/cli/dotenvx.js +19 -21
- package/src/cli/examples.js +1 -1
- package/src/db/session.js +10 -6
- package/src/lib/extensions/ops.js +112 -63
- package/src/lib/helpers/catchAndLog.js +1 -1
- package/src/lib/helpers/createSpinner.js +24 -0
- package/src/lib/helpers/cryptography/index.js +4 -0
- package/src/lib/helpers/cryptography/mutateKeysSrc.js +4 -4
- package/src/lib/helpers/cryptography/mutateKeysSrcSync.js +38 -0
- package/src/lib/helpers/cryptography/opsKeypair.js +2 -2
- package/src/lib/helpers/cryptography/opsKeypairSync.js +14 -0
- package/src/lib/helpers/cryptography/provision.js +7 -7
- package/src/lib/helpers/cryptography/provisionSync.js +47 -0
- package/src/lib/helpers/cryptography/provisionWithPrivateKey.js +1 -1
- package/src/lib/helpers/detectEncoding.js +2 -2
- package/src/lib/helpers/detectEncodingSync.js +22 -0
- package/src/lib/helpers/errors.js +15 -0
- package/src/lib/helpers/fsx.js +27 -3
- package/src/lib/helpers/installPrecommitHook.js +2 -2
- package/src/lib/helpers/isIgnoringDotenvKeys.js +1 -1
- package/src/lib/helpers/keyResolution/index.js +3 -1
- package/src/lib/helpers/keyResolution/keyValues.js +12 -12
- package/src/lib/helpers/keyResolution/keyValuesSync.js +85 -0
- package/src/lib/helpers/keyResolution/readFileKey.js +10 -8
- package/src/lib/helpers/keyResolution/readFileKeySync.js +15 -0
- package/src/lib/helpers/kits/sample.js +11 -21
- package/src/lib/main.d.ts +18 -3
- package/src/lib/main.js +18 -18
- package/src/lib/services/decrypt.js +8 -8
- package/src/lib/services/encrypt.js +17 -11
- package/src/lib/services/genexample.js +2 -2
- package/src/lib/services/get.js +30 -21
- package/src/lib/services/keypair.js +21 -5
- package/src/lib/services/prebuild.js +7 -12
- package/src/lib/services/precommit.js +7 -11
- package/src/lib/services/rotate.js +22 -18
- package/src/lib/services/run.js +82 -9
- package/src/lib/services/sets.js +139 -12
- package/src/shared/logger.js +3 -3
- 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,
|
|
8
|
+
constructor (envFile = '.env', envKeysFilepath = null, noOps = false) {
|
|
8
9
|
this.envFile = envFile
|
|
9
10
|
this.envKeysFilepath = envKeysFilepath
|
|
10
|
-
this.
|
|
11
|
+
this.noOps = noOps
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
|
|
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 } =
|
|
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:
|
|
28
|
+
message: '.dockerignore missing',
|
|
30
29
|
help: 'fix: [touch .dockerignore]'
|
|
31
30
|
}).custom()
|
|
32
31
|
warnings.push(warning)
|
|
33
32
|
} else {
|
|
34
|
-
dockerignore = fsx.
|
|
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:
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
41
|
+
message: '.gitignore missing',
|
|
43
42
|
help: 'fix: [touch .gitignore]'
|
|
44
43
|
}).custom()
|
|
45
44
|
warnings.push(warning)
|
|
46
45
|
} else {
|
|
47
|
-
gitignore = fsx.
|
|
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:
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|
|
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.
|
|
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,
|
|
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.
|
|
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(
|
|
101
|
-
|
|
102
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
}
|
package/src/lib/services/run.js
CHANGED
|
@@ -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,
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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,
|
|
178
|
+
const { privateKeyValue } = await keyValues(filepath, { keysFilepath: this.envKeysFilepath, noOps: this.noOps })
|
|
106
179
|
|
|
107
180
|
const {
|
|
108
181
|
parsed,
|
package/src/lib/services/sets.js
CHANGED
|
@@ -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,
|
|
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.
|
|
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
|
-
|
|
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.
|
|
56
|
+
this._setEnvFileSync(env.value)
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -61,7 +64,26 @@ class Sets {
|
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
package/src/shared/logger.js
CHANGED
|
@@ -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(`
|
|
124
|
+
logger.debug(`setting log level to: ${logLevel}`)
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|