@dotenvx/dotenvx 0.38.0 → 0.40.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/README.md +653 -17
- package/package.json +1 -1
- package/src/cli/actions/encryptme.js +73 -0
- package/src/cli/actions/get.js +11 -1
- package/src/cli/actions/run.js +5 -4
- package/src/cli/actions/set.js +43 -9
- package/src/cli/actions/vault/encrypt.js +0 -1
- package/src/cli/commands/hub.js +8 -8
- package/src/cli/dotenvx.js +19 -11
- package/src/lib/helpers/findOrCreatePublicKey.js +4 -2
- package/src/lib/helpers/isEncrypted.js +8 -0
- package/src/lib/helpers/isFullyEncrypted.js +18 -0
- package/src/lib/helpers/isIgnoringDotenvKeys.js +19 -0
- package/src/lib/main.js +14 -3
- package/src/lib/services/encrypt.js +73 -87
- package/src/lib/services/precommit.js +19 -8
- package/src/lib/services/sets.js +32 -10
- package/src/lib/services/vaultEncrypt.js +118 -0
|
@@ -3,7 +3,9 @@ const fs = require('fs')
|
|
|
3
3
|
const ignore = require('ignore')
|
|
4
4
|
|
|
5
5
|
const pluralize = require('./../helpers/pluralize')
|
|
6
|
+
const isFullyEncrypted = require('./../helpers/isFullyEncrypted')
|
|
6
7
|
const InstallPrecommitHook = require('./../helpers/installPrecommitHook')
|
|
8
|
+
const MISSING_GITIGNORE = '.env.keys' // by default only ignore .env.keys. all other .env* files COULD be included - as long as they are encrypted
|
|
7
9
|
|
|
8
10
|
class Precommit {
|
|
9
11
|
constructor (options = {}) {
|
|
@@ -22,16 +24,20 @@ class Precommit {
|
|
|
22
24
|
}
|
|
23
25
|
} else {
|
|
24
26
|
const warnings = []
|
|
27
|
+
let successMessage = 'success'
|
|
28
|
+
let gitignore = MISSING_GITIGNORE
|
|
25
29
|
|
|
26
30
|
// 1. check for .gitignore file
|
|
27
31
|
if (!fs.existsSync('.gitignore')) {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
const warning = new Error('.gitignore missing')
|
|
33
|
+
warning.help = '? add it with [touch .gitignore]'
|
|
34
|
+
warnings.push(warning)
|
|
35
|
+
} else {
|
|
36
|
+
gitignore = fs.readFileSync('.gitignore').toString()
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
// 2. check .env* files against .gitignore file
|
|
34
|
-
const ig = ignore().add(
|
|
40
|
+
const ig = ignore().add(gitignore)
|
|
35
41
|
const files = fs.readdirSync(process.cwd())
|
|
36
42
|
const dotenvFiles = files.filter(file => file.match(/^\.env(\..+)?$/))
|
|
37
43
|
dotenvFiles.forEach(file => {
|
|
@@ -44,14 +50,19 @@ class Precommit {
|
|
|
44
50
|
}
|
|
45
51
|
} else {
|
|
46
52
|
if (file !== '.env.example' && file !== '.env.vault') {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
const src = fs.readFileSync(file).toString()
|
|
54
|
+
const encrypted = isFullyEncrypted(src)
|
|
55
|
+
|
|
56
|
+
// if contents are encrypted don't raise an error
|
|
57
|
+
if (!encrypted) {
|
|
58
|
+
const error = new Error(`${file} not encrypted (or not gitignored)`)
|
|
59
|
+
error.help = `? encrypt it with [dotenvx protect -f ${file}] or add ${file} to .gitignore with [echo ".env*" >> .gitignore]`
|
|
60
|
+
throw error
|
|
61
|
+
}
|
|
50
62
|
}
|
|
51
63
|
}
|
|
52
64
|
})
|
|
53
65
|
|
|
54
|
-
let successMessage = 'success'
|
|
55
66
|
if (warnings.length > 0) {
|
|
56
67
|
successMessage = `success (with ${pluralize('warning', warnings.length)})`
|
|
57
68
|
}
|
package/src/lib/services/sets.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
|
|
4
|
+
const dotenv = require('dotenv')
|
|
5
|
+
|
|
4
6
|
const findOrCreatePublicKey = require('./../helpers/findOrCreatePublicKey')
|
|
7
|
+
const guessPrivateKeyName = require('./../helpers/guessPrivateKeyName')
|
|
5
8
|
const encryptValue = require('./../helpers/encryptValue')
|
|
6
9
|
const replace = require('./../helpers/replace')
|
|
7
10
|
|
|
@@ -14,39 +17,57 @@ class Sets {
|
|
|
14
17
|
this.envFile = envFile
|
|
15
18
|
this.encrypt = encrypt
|
|
16
19
|
|
|
17
|
-
this.publicKey = null
|
|
18
20
|
this.processedEnvFiles = []
|
|
19
|
-
this.
|
|
21
|
+
this.changedFilepaths = new Set()
|
|
22
|
+
this.unchangedFilepaths = new Set()
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
run () {
|
|
23
26
|
const envFilepaths = this._envFilepaths()
|
|
24
27
|
for (const envFilepath of envFilepaths) {
|
|
28
|
+
const filepath = path.resolve(envFilepath)
|
|
29
|
+
|
|
25
30
|
const row = {}
|
|
26
31
|
row.key = this.key
|
|
27
|
-
row.filepath = envFilepath
|
|
28
32
|
row.value = this.value
|
|
33
|
+
row.filepath = filepath
|
|
34
|
+
row.envFilepath = envFilepath
|
|
35
|
+
row.changed = false
|
|
29
36
|
|
|
30
|
-
const filepath = path.resolve(envFilepath)
|
|
31
37
|
try {
|
|
32
38
|
let value = this.value
|
|
33
39
|
let src = fs.readFileSync(filepath, { encoding: ENCODING })
|
|
40
|
+
row.originalValue = dotenv.parse(src)[row.key] || null
|
|
41
|
+
|
|
34
42
|
if (this.encrypt) {
|
|
35
43
|
const envKeysFilepath = path.join(path.dirname(filepath), '.env.keys')
|
|
36
44
|
const {
|
|
45
|
+
envSrc,
|
|
37
46
|
publicKey,
|
|
38
|
-
|
|
47
|
+
privateKey,
|
|
48
|
+
privateKeyAdded
|
|
39
49
|
} = findOrCreatePublicKey(filepath, envKeysFilepath)
|
|
40
50
|
src = envSrc // overwrite the original read (because findOrCreatePublicKey) rewrite to it
|
|
41
51
|
value = encryptValue(value, publicKey)
|
|
42
|
-
|
|
52
|
+
|
|
53
|
+
row.changed = true // track change
|
|
54
|
+
row.encryptedValue = value
|
|
43
55
|
row.publicKey = publicKey
|
|
56
|
+
row.privateKey = privateKey
|
|
57
|
+
row.privateKeyAdded = privateKeyAdded
|
|
58
|
+
row.privateKeyName = guessPrivateKeyName(filepath)
|
|
44
59
|
}
|
|
45
60
|
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
if (value !== row.originalValue) {
|
|
62
|
+
row.envSrc = replace(src, this.key, value)
|
|
63
|
+
|
|
64
|
+
this.changedFilepaths.add(envFilepath)
|
|
65
|
+
row.changed = true
|
|
66
|
+
} else {
|
|
67
|
+
row.envSrc = src
|
|
48
68
|
|
|
49
|
-
|
|
69
|
+
this.unchangedFilepaths.add(envFilepath)
|
|
70
|
+
}
|
|
50
71
|
} catch (e) {
|
|
51
72
|
if (e.code === 'ENOENT') {
|
|
52
73
|
const error = new Error(`missing ${envFilepath} file (${filepath})`)
|
|
@@ -63,7 +84,8 @@ class Sets {
|
|
|
63
84
|
|
|
64
85
|
return {
|
|
65
86
|
processedEnvFiles: this.processedEnvFiles,
|
|
66
|
-
|
|
87
|
+
changedFilepaths: [...this.changedFilepaths],
|
|
88
|
+
unchangedFilepaths: [...this.unchangedFilepaths]
|
|
67
89
|
}
|
|
68
90
|
}
|
|
69
91
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const dotenv = require('dotenv')
|
|
4
|
+
|
|
5
|
+
const DotenvKeys = require('./../helpers/dotenvKeys')
|
|
6
|
+
const DotenvVault = require('./../helpers/dotenvVault')
|
|
7
|
+
|
|
8
|
+
const ENCODING = 'utf8'
|
|
9
|
+
|
|
10
|
+
const findEnvFiles = require('../helpers/findEnvFiles')
|
|
11
|
+
|
|
12
|
+
class VaultEncrypt {
|
|
13
|
+
constructor (directory = '.', envFile) {
|
|
14
|
+
this.directory = directory
|
|
15
|
+
this.envFile = envFile || findEnvFiles(directory)
|
|
16
|
+
// calculated
|
|
17
|
+
this.envKeysFilepath = path.resolve(this.directory, '.env.keys')
|
|
18
|
+
this.envVaultFilepath = path.resolve(this.directory, '.env.vault')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
run () {
|
|
22
|
+
if (this.envFile.length < 1) {
|
|
23
|
+
const code = 'MISSING_ENV_FILES'
|
|
24
|
+
const message = 'no .env* files found'
|
|
25
|
+
const help = '? add one with [echo "HELLO=World" > .env] and then run [dotenvx vault encrypt]'
|
|
26
|
+
|
|
27
|
+
const error = new Error(message)
|
|
28
|
+
error.code = code
|
|
29
|
+
error.help = help
|
|
30
|
+
throw error
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const parsedDotenvKeys = this._parsedDotenvKeys()
|
|
34
|
+
const parsedDotenvVaults = this._parsedDotenvVault()
|
|
35
|
+
const envFilepaths = this._envFilepaths()
|
|
36
|
+
|
|
37
|
+
// build filepaths to be passed to DotenvKeys
|
|
38
|
+
const uniqueEnvFilepaths = new Set()
|
|
39
|
+
for (const envFilepath of envFilepaths) {
|
|
40
|
+
const filepath = path.resolve(this.directory, envFilepath)
|
|
41
|
+
if (!fs.existsSync(filepath)) {
|
|
42
|
+
const code = 'MISSING_ENV_FILE'
|
|
43
|
+
const message = `file does not exist at [${filepath}]`
|
|
44
|
+
const help = `? add it with [echo "HELLO=World" > ${envFilepath}] and then run [dotenvx vault encrypt]`
|
|
45
|
+
|
|
46
|
+
const error = new Error(message)
|
|
47
|
+
error.code = code
|
|
48
|
+
error.help = help
|
|
49
|
+
throw error
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
uniqueEnvFilepaths.add(filepath)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// generate .env.keys string
|
|
56
|
+
const {
|
|
57
|
+
dotenvKeys,
|
|
58
|
+
dotenvKeysFile,
|
|
59
|
+
addedKeys,
|
|
60
|
+
existingKeys
|
|
61
|
+
} = new DotenvKeys([...uniqueEnvFilepaths], parsedDotenvKeys).run()
|
|
62
|
+
|
|
63
|
+
// build look up of .env filepaths and their raw content
|
|
64
|
+
const dotenvFiles = {}
|
|
65
|
+
for (const filepath of [...uniqueEnvFilepaths]) {
|
|
66
|
+
const raw = fs.readFileSync(filepath, ENCODING)
|
|
67
|
+
dotenvFiles[filepath] = raw
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// generate .env.vault string
|
|
71
|
+
const {
|
|
72
|
+
dotenvVaultFile,
|
|
73
|
+
addedVaults,
|
|
74
|
+
existingVaults,
|
|
75
|
+
addedDotenvFilenames
|
|
76
|
+
} = new DotenvVault(dotenvFiles, dotenvKeys, parsedDotenvVaults).run()
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
// from DotenvKeys
|
|
80
|
+
dotenvKeys,
|
|
81
|
+
dotenvKeysFile,
|
|
82
|
+
addedKeys,
|
|
83
|
+
existingKeys,
|
|
84
|
+
// from DotenvVault
|
|
85
|
+
dotenvVaultFile,
|
|
86
|
+
addedVaults,
|
|
87
|
+
existingVaults,
|
|
88
|
+
addedDotenvFilenames,
|
|
89
|
+
envFile: this.envFile
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_envFilepaths () {
|
|
94
|
+
if (!Array.isArray(this.envFile)) {
|
|
95
|
+
return [this.envFile]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return this.envFile
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_parsedDotenvKeys () {
|
|
102
|
+
const options = {
|
|
103
|
+
path: this.envKeysFilepath
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return dotenv.configDotenv(options).parsed
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_parsedDotenvVault () {
|
|
110
|
+
const options = {
|
|
111
|
+
path: this.envVaultFilepath
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return dotenv.configDotenv(options).parsed
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = VaultEncrypt
|