@dotenvx/dotenvx 1.75.1 → 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.
- package/CHANGELOG.md +24 -1
- package/README.md +4 -114
- package/package.json +8 -7
- package/src/cli/actions/decrypt.js +35 -17
- package/src/cli/actions/doctor.js +2 -2
- package/src/cli/actions/encrypt.js +64 -85
- package/src/cli/actions/ext/genexample.js +2 -2
- package/src/cli/actions/get.js +16 -7
- package/src/cli/actions/keypair.js +55 -42
- package/src/cli/actions/login.js +4 -3
- package/src/cli/actions/normalizeArmorAliases.js +9 -0
- package/src/cli/actions/run.js +65 -22
- package/src/cli/actions/set.js +51 -37
- package/src/cli/dotenvx.js +6 -21
- package/src/lib/api/{postKeypair.js → postArmorKeyring.js} +4 -14
- package/src/lib/{helpers/envResolution → conventions}/environment.js +1 -1
- package/src/lib/conventions/filepaths.js +9 -0
- package/src/lib/conventions/keynames.js +38 -0
- package/src/lib/helpers/catchAndLog.js +4 -1
- package/src/lib/helpers/cryptography/index.js +0 -8
- package/src/lib/helpers/cryptography/mutateKeysSrc.js +5 -21
- package/src/lib/helpers/envResolution/index.js +1 -1
- package/src/lib/helpers/errors.js +13 -0
- package/src/lib/helpers/keypairMetadata.js +1 -1
- package/src/lib/main.d.ts +2 -39
- package/src/lib/main.js +82 -45
- package/src/lib/providers/armor/index.js +29 -0
- package/src/lib/providers/index.js +46 -0
- package/src/lib/providers/worker.js +6 -0
- package/src/lib/resolvers/envs.js +302 -0
- package/src/lib/resolvers/get.js +69 -0
- package/src/lib/resolvers/keypair.js +81 -0
- package/src/lib/resolvers/ls.js +41 -0
- package/src/lib/services/armorDown.js +2 -2
- package/src/lib/services/{armorKeypair.js → armorKeyring.js} +14 -51
- package/src/lib/services/armorMove.js +2 -2
- package/src/lib/services/armorPull.js +2 -2
- package/src/lib/services/armorPush.js +2 -2
- package/src/lib/services/armorUp.js +2 -2
- package/src/lib/services/genexample.js +5 -6
- package/src/lib/services/login.js +25 -0
- package/src/lib/services/prebuild.js +9 -3
- package/src/lib/services/precommit.js +9 -3
- package/src/lib/transforms/decrypt.js +83 -0
- package/src/lib/transforms/encrypt.js +179 -0
- package/src/lib/transforms/set.js +179 -0
- package/src/shared/logger.js +5 -0
- package/src/cli/actions/normalizeArmorOptions.js +0 -10
- package/src/cli/actions/rotate.js +0 -87
- package/src/lib/helpers/append.js +0 -62
- package/src/lib/helpers/cryptography/armorKeypair.js +0 -42
- package/src/lib/helpers/cryptography/armorKeypairSync.js +0 -55
- package/src/lib/helpers/cryptography/encryptValue.js +0 -7
- package/src/lib/helpers/cryptography/localKeypair.js +0 -14
- package/src/lib/helpers/cryptography/mutateKeysSrcSync.js +0 -38
- package/src/lib/helpers/cryptography/provision.js +0 -56
- package/src/lib/helpers/cryptography/provisionSync.js +0 -51
- package/src/lib/helpers/cryptography/provisionWithPrivateKey.js +0 -26
- package/src/lib/helpers/escapeDollarSigns.js +0 -5
- package/src/lib/helpers/escapeForRegex.js +0 -5
- package/src/lib/helpers/evalKeyValue.js +0 -23
- package/src/lib/helpers/isIgnoringDotenvKeys.js +0 -19
- package/src/lib/helpers/keyResolution/index.js +0 -16
- package/src/lib/helpers/keyResolution/keyNamesForEnvFile.js +0 -24
- package/src/lib/helpers/keyResolution/keyValues.js +0 -99
- package/src/lib/helpers/keyResolution/keyValuesFromEnvSrc.js +0 -85
- package/src/lib/helpers/keyResolution/keyValuesSync.js +0 -103
- package/src/lib/helpers/keyResolution/readFileKey.js +0 -19
- package/src/lib/helpers/keyResolution/readFileKeySync.js +0 -17
- package/src/lib/helpers/keyResolution/readProcessKey.js +0 -7
- package/src/lib/helpers/localDisplayPath.js +0 -11
- package/src/lib/helpers/parse.js +0 -214
- package/src/lib/helpers/pluralize.js +0 -10
- package/src/lib/helpers/quotes.js +0 -36
- package/src/lib/helpers/replace.js +0 -82
- package/src/lib/helpers/resolveEscapeSequences.js +0 -5
- package/src/lib/services/decrypt.js +0 -156
- package/src/lib/services/encrypt.js +0 -213
- package/src/lib/services/get.js +0 -76
- package/src/lib/services/keypair.js +0 -60
- package/src/lib/services/loginPoll.js +0 -36
- package/src/lib/services/ls.js +0 -57
- package/src/lib/services/rotate.js +0 -197
- package/src/lib/services/run.js +0 -255
- package/src/lib/services/sets.js +0 -328
|
@@ -3,8 +3,7 @@ const path = require('path')
|
|
|
3
3
|
|
|
4
4
|
const Errors = require('../helpers/errors')
|
|
5
5
|
const findEnvFiles = require('../helpers/findEnvFiles')
|
|
6
|
-
const
|
|
7
|
-
const { scan } = require('@dotenvx/primitives')
|
|
6
|
+
const { scan, upsert } = require('@dotenvx/primitives')
|
|
8
7
|
|
|
9
8
|
function finalValues (src) {
|
|
10
9
|
const { parsed } = scan(src)
|
|
@@ -38,7 +37,7 @@ class Genexample {
|
|
|
38
37
|
/** @type {Record<string, string>} */
|
|
39
38
|
const injected = {}
|
|
40
39
|
/** @type {Record<string, string>} */
|
|
41
|
-
const
|
|
40
|
+
const existed = {}
|
|
42
41
|
|
|
43
42
|
let exampleSrc = `# ${this.exampleFilename} - generated with dotenvx\n`
|
|
44
43
|
|
|
@@ -58,7 +57,7 @@ class Genexample {
|
|
|
58
57
|
keys.add(key)
|
|
59
58
|
|
|
60
59
|
// once newSrc is built write it out
|
|
61
|
-
src =
|
|
60
|
+
src = upsert(src, key, '') // empty value
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
exampleSrc += `\n${src}`
|
|
@@ -80,7 +79,7 @@ class Genexample {
|
|
|
80
79
|
const parsed = finalValues(exampleSrc)
|
|
81
80
|
for (const key of [...keys]) {
|
|
82
81
|
if (key in parsed) {
|
|
83
|
-
|
|
82
|
+
existed[key] = parsed[key]
|
|
84
83
|
} else {
|
|
85
84
|
exampleSrc += `${key}=''\n`
|
|
86
85
|
|
|
@@ -97,7 +96,7 @@ class Genexample {
|
|
|
97
96
|
exampleFilepath: this.exampleFilepath,
|
|
98
97
|
addedKeys: [...addedKeys],
|
|
99
98
|
injected,
|
|
100
|
-
|
|
99
|
+
existed
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
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,7 +3,7 @@ const fsx = require('./../helpers/fsx')
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const ignore = require('ignore')
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const ls = require('../resolvers/ls')
|
|
7
7
|
const Errors = require('../helpers/errors')
|
|
8
8
|
const { sealed } = require('@dotenvx/primitives')
|
|
9
9
|
|
|
@@ -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
|
|
39
|
-
const dotenvFiles = lsService.run()
|
|
38
|
+
const dotenvFiles = this._filepaths()
|
|
40
39
|
dotenvFiles.forEach(_file => {
|
|
41
40
|
count += 1
|
|
42
41
|
|
|
@@ -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,7 +3,7 @@ const fsx = require('./../helpers/fsx')
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const ignore = require('ignore')
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const ls = require('../resolvers/ls')
|
|
7
7
|
const { sealed } = require('@dotenvx/primitives')
|
|
8
8
|
|
|
9
9
|
const InstallPrecommitHook = require('./../helpers/installPrecommitHook')
|
|
@@ -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
|
|
53
|
-
const dotenvFiles = lsService.run()
|
|
52
|
+
const dotenvFiles = this._filepaths()
|
|
54
53
|
dotenvFiles.forEach(_file => {
|
|
55
54
|
count += 1
|
|
56
55
|
|
|
@@ -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
|
package/src/shared/logger.js
CHANGED
|
@@ -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
|