@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.
- package/CHANGELOG.md +34 -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 +1 -12
- 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/executeDynamic.js +1 -3
- package/src/lib/helpers/keypairMetadata.js +3 -3
- package/src/lib/helpers/readEnvKey.js +4 -3
- 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 +19 -8
- package/src/lib/services/login.js +25 -0
- package/src/lib/services/prebuild.js +11 -5
- package/src/lib/services/precommit.js +11 -5
- 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 -61
- 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/dotenvParse.js +0 -55
- 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/isFullyEncrypted.js +0 -27
- 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 -80
- package/src/lib/helpers/keyResolution/keyValuesSync.js +0 -103
- package/src/lib/helpers/keyResolution/readFileKey.js +0 -17
- package/src/lib/helpers/keyResolution/readFileKeySync.js +0 -15
- 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 -157
- package/src/lib/services/encrypt.js +0 -214
- 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 -198
- package/src/lib/services/run.js +0 -255
- 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
|
|
7
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
79
|
+
const parsed = finalValues(exampleSrc)
|
|
69
80
|
for (const key of [...keys]) {
|
|
70
81
|
if (key in parsed) {
|
|
71
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
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
|