@dotenvx/dotenvx 1.6.5 → 1.8.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/package.json +4 -6
- package/src/cli/actions/ext/vault/status.js +1 -1
- package/src/cli/actions/run.js +9 -0
- package/src/cli/commands/ext.js +0 -9
- package/src/lib/helpers/colorDepth.js +4 -0
- package/src/lib/helpers/decryptValue.js +16 -5
- package/src/lib/helpers/dotenvExpand.js +9 -12
- package/src/lib/helpers/execute.js +2 -8
- package/src/lib/helpers/executeCommand.js +15 -7
- package/src/lib/helpers/executeExtension.js +4 -3
- package/src/lib/helpers/inject.js +4 -4
- package/src/lib/helpers/packageJson.js +2 -7
- package/src/lib/helpers/parseDecryptEvalExpand.js +36 -5
- package/src/lib/helpers/truncate.js +6 -0
- package/src/lib/main.d.ts +0 -15
- package/src/lib/main.js +0 -9
- package/src/lib/services/decrypt.js +2 -2
- package/src/lib/services/ls.js +9 -7
- package/src/lib/services/run.js +11 -9
- package/src/lib/services/status.js +3 -3
- package/src/lib/services/vaultDecrypt.js +1 -1
- package/src/shared/colors.js +50 -0
- package/src/shared/logger.js +12 -11
- package/src/cli/actions/ext/settings.js +0 -25
- package/src/lib/services/settings.js +0 -28
- package/src/shared/store.js +0 -146
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
-
## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.
|
|
5
|
+
## [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.8.0...main)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
* warn when decryption fails on `run` or `get` ([#339](https://github.com/dotenvx/dotenvx/pull/339))
|
|
10
|
+
* decrypt expanded values as necessary ([#336](https://github.com/dotenvx/dotenvx/pull/336))
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
* use `ansi` colors over `rgb` - for wider terminal coverage ([#340](https://github.com/dotenvx/dotenvx/pull/340))
|
|
15
|
+
* replace `chalk` with `picocolors` and `color-name` - cutting down on 5 dependencies ([#335](https://github.com/dotenvx/dotenvx/pull/335))
|
|
16
|
+
* replace `execa` with `tinyexec` - cutting down on 15 dependencies ([#328](https://github.com/dotenvx/dotenvx/pull/328))
|
|
17
|
+
* optimize `Ls._filepaths` ([#317](https://github.com/dotenvx/dotenvx/pull/317/))
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
* remove `picocolors` and `color-name` - cutting down on 2 dependencies ([#340](https://github.com/dotenvx/dotenvx/pull/340))
|
|
22
|
+
* remove `ext hub` from extension list (you can still install it as an extension [here](https://github.com/dotenvx/dotenvx-ext-hub)) ([#337](https://github.com/dotenvx/dotenvx/pull/337))
|
|
23
|
+
|
|
24
|
+
## 1.7.0
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
* remove `ext settings` command (and [`conf`](https://www.npmjs.com/package/conf) along with it) ([#323](https://github.com/dotenvx/dotenvx/pull/323))
|
|
6
29
|
|
|
7
30
|
## 1.6.5
|
|
8
31
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.8.0",
|
|
3
3
|
"name": "@dotenvx/dotenvx",
|
|
4
4
|
"description": "a better dotenv–from the creator of `dotenv`",
|
|
5
5
|
"author": "@motdotla",
|
|
@@ -36,17 +36,15 @@
|
|
|
36
36
|
},
|
|
37
37
|
"funding": "https://dotenvx.com",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"chalk": "^4.1.2",
|
|
40
39
|
"commander": "^11.1.0",
|
|
41
|
-
"conf": "^10.2.0",
|
|
42
40
|
"diff": "^5.2.0",
|
|
43
41
|
"dotenv": "^16.4.5",
|
|
44
42
|
"eciesjs": "^0.4.6",
|
|
45
|
-
"
|
|
46
|
-
"fdir": "^6.1.1",
|
|
43
|
+
"fdir": "^6.2.0",
|
|
47
44
|
"ignore": "^5.3.0",
|
|
48
45
|
"object-treeify": "1.1.33",
|
|
49
|
-
"picomatch": "^
|
|
46
|
+
"picomatch": "^4.0.2",
|
|
47
|
+
"tinyexec": "^0.2.0",
|
|
50
48
|
"which": "^4.0.0",
|
|
51
49
|
"winston": "^3.11.0",
|
|
52
50
|
"xxhashjs": "^0.2.2"
|
|
@@ -58,7 +58,7 @@ function status (directory) {
|
|
|
58
58
|
|
|
59
59
|
if (untrackedFilenames.length > 0) {
|
|
60
60
|
logger.warn(`untracked (${untrackedFilenames.join(', ')})`)
|
|
61
|
-
logger.help(`? track them with [dotenvx encrypt ${directory}]`)
|
|
61
|
+
logger.help(`? track them with [dotenvx ext vault encrypt ${directory}]`)
|
|
62
62
|
}
|
|
63
63
|
} catch (error) {
|
|
64
64
|
logger.error(error.message)
|
package/src/cli/actions/run.js
CHANGED
|
@@ -60,6 +60,15 @@ async function run () {
|
|
|
60
60
|
logger.warnv(processedEnv.error.message)
|
|
61
61
|
}
|
|
62
62
|
} else {
|
|
63
|
+
if (processedEnv.warnings) {
|
|
64
|
+
for (const warning of processedEnv.warnings) {
|
|
65
|
+
logger.warn(warning.message)
|
|
66
|
+
if (warning.help) {
|
|
67
|
+
logger.help(warning.help)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
63
72
|
// debug parsed
|
|
64
73
|
const parsed = processedEnv.parsed
|
|
65
74
|
logger.debug(parsed)
|
package/src/cli/commands/ext.js
CHANGED
|
@@ -9,8 +9,6 @@ ext
|
|
|
9
9
|
.description('🔌 extensions')
|
|
10
10
|
.allowUnknownOption()
|
|
11
11
|
|
|
12
|
-
ext.addHelpText('after', ' hub 🚫 DEPRECATED: to be replaced by [dotenvx pro]')
|
|
13
|
-
|
|
14
12
|
ext
|
|
15
13
|
.argument('[command]', 'dynamic ext command')
|
|
16
14
|
.argument('[args...]', 'dynamic ext command arguments')
|
|
@@ -57,13 +55,6 @@ ext.command('scan')
|
|
|
57
55
|
.description('scan for leaked secrets')
|
|
58
56
|
.action(require('./../actions/ext/scan'))
|
|
59
57
|
|
|
60
|
-
// dotenvx settings
|
|
61
|
-
ext.command('settings')
|
|
62
|
-
.description('print current dotenvx settings')
|
|
63
|
-
.argument('[key]', 'settings name')
|
|
64
|
-
.option('-pp, --pretty-print', 'pretty print output')
|
|
65
|
-
.action(require('./../actions/ext/settings'))
|
|
66
|
-
|
|
67
58
|
ext.addCommand(require('./../commands/ext/vault'))
|
|
68
59
|
|
|
69
60
|
module.exports = ext
|
|
@@ -10,6 +10,7 @@ function decryptValue (value, privateKey) {
|
|
|
10
10
|
const privateKeys = privateKey.split(',')
|
|
11
11
|
|
|
12
12
|
let decryptedValue
|
|
13
|
+
let decryptionError
|
|
13
14
|
for (const key of privateKeys) {
|
|
14
15
|
const secret = Buffer.from(key, 'hex')
|
|
15
16
|
const encoded = value.substring(PREFIX.length)
|
|
@@ -17,15 +18,25 @@ function decryptValue (value, privateKey) {
|
|
|
17
18
|
|
|
18
19
|
try {
|
|
19
20
|
decryptedValue = decrypt(secret, ciphertext).toString()
|
|
21
|
+
decryptionError = null // reset to null error (scenario for multiple private keys)
|
|
20
22
|
break
|
|
21
|
-
} catch (
|
|
22
|
-
|
|
23
|
+
} catch (e) {
|
|
24
|
+
if (e.message === 'Invalid private key') {
|
|
25
|
+
decryptionError = new Error('private key looks invalid')
|
|
26
|
+
} else if (e.message === 'Unsupported state or unable to authenticate data') {
|
|
27
|
+
decryptionError = new Error('private key looks wrong')
|
|
28
|
+
} else if (e.message === 'Point of length 65 was invalid. Expected 33 compressed bytes or 65 uncompressed bytes') {
|
|
29
|
+
decryptionError = new Error('encrypted data looks malformed')
|
|
30
|
+
} else {
|
|
31
|
+
decryptionError = new Error(`${e.message}`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
decryptionError.code = 'DECRYPTION_FAILED'
|
|
23
35
|
}
|
|
24
36
|
}
|
|
25
37
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return value
|
|
38
|
+
if (decryptionError) {
|
|
39
|
+
throw decryptionError
|
|
29
40
|
}
|
|
30
41
|
|
|
31
42
|
return decryptedValue
|
|
@@ -42,30 +42,27 @@ function interpolate (value, lookups) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function expand (options) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
processEnv = options.processEnv
|
|
48
|
-
}
|
|
45
|
+
const processEnv = options.processEnv || {}
|
|
46
|
+
const parsed = options.parsed || {}
|
|
49
47
|
|
|
50
|
-
const combined = { ...processEnv, ...
|
|
51
|
-
const combinedReversed = { ...
|
|
48
|
+
const combined = { ...processEnv, ...parsed }
|
|
49
|
+
const combinedReversed = { ...parsed, ...processEnv }
|
|
52
50
|
|
|
53
|
-
for (const key in
|
|
54
|
-
const value =
|
|
51
|
+
for (const key in parsed) {
|
|
52
|
+
const value = parsed[key]
|
|
55
53
|
|
|
56
54
|
// interpolate using both file and processEnv (file interpolation wins. used for --overload later)
|
|
57
55
|
const fileValue = _resolveEscapeSequences(interpolate(value, combined))
|
|
58
|
-
|
|
56
|
+
parsed[key] = fileValue
|
|
59
57
|
|
|
60
58
|
if (fileValue === _resolveEscapeSequences(value)) {
|
|
61
59
|
continue // no change means no expansion, move on
|
|
62
60
|
}
|
|
63
61
|
|
|
64
62
|
if (processEnv[key]) {
|
|
65
|
-
continue // already has a value in
|
|
63
|
+
continue // already has a value in processEnv, move on
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
// interpolate with processEnv only (used for default no overload)
|
|
69
66
|
const processEnvValue = interpolate(value, combinedReversed) // could be empty string ''
|
|
70
67
|
if (processEnvValue) {
|
|
71
68
|
processEnv[key] = _resolveEscapeSequences(processEnvValue) // set it
|
|
@@ -73,7 +70,7 @@ function expand (options) {
|
|
|
73
70
|
}
|
|
74
71
|
|
|
75
72
|
return {
|
|
76
|
-
parsed
|
|
73
|
+
parsed,
|
|
77
74
|
processEnv
|
|
78
75
|
}
|
|
79
76
|
}
|
|
@@ -13,6 +13,8 @@ async function executeCommand (commandArgs, env) {
|
|
|
13
13
|
|
|
14
14
|
// handler for SIGINT
|
|
15
15
|
let commandProcess
|
|
16
|
+
// workaround until error.signal gets added https://github.com/tinylibs/tinyexec/issues/28
|
|
17
|
+
let signal
|
|
16
18
|
const sigintHandler = () => {
|
|
17
19
|
logger.debug('received SIGINT')
|
|
18
20
|
logger.debug('checking command process')
|
|
@@ -20,6 +22,7 @@ async function executeCommand (commandArgs, env) {
|
|
|
20
22
|
|
|
21
23
|
if (commandProcess) {
|
|
22
24
|
logger.debug('sending SIGINT to command process')
|
|
25
|
+
signal = 'SIGINT'
|
|
23
26
|
commandProcess.kill('SIGINT') // Send SIGINT to the command process
|
|
24
27
|
/* c8 ignore start */
|
|
25
28
|
} else {
|
|
@@ -37,6 +40,7 @@ async function executeCommand (commandArgs, env) {
|
|
|
37
40
|
|
|
38
41
|
if (commandProcess) {
|
|
39
42
|
logger.debug('sending SIGTERM to command process')
|
|
43
|
+
signal = 'SIGTERM'
|
|
40
44
|
commandProcess.kill('SIGTERM') // Send SIGTEM to the command process
|
|
41
45
|
} else {
|
|
42
46
|
logger.debug('no command process to send SIGTERM to')
|
|
@@ -73,9 +77,11 @@ async function executeCommand (commandArgs, env) {
|
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
commandProcess = execute.
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
commandProcess = execute.exec(commandArgs[0], commandArgs.slice(1), {
|
|
81
|
+
nodeOptions: {
|
|
82
|
+
stdio: 'inherit',
|
|
83
|
+
env: { ...process.env, ...env }
|
|
84
|
+
}
|
|
79
85
|
})
|
|
80
86
|
|
|
81
87
|
process.on('SIGINT', sigintHandler)
|
|
@@ -86,7 +92,9 @@ async function executeCommand (commandArgs, env) {
|
|
|
86
92
|
})
|
|
87
93
|
|
|
88
94
|
// Wait for the command process to finish
|
|
89
|
-
|
|
95
|
+
// exitCode is not in the awaited result, see https://github.com/tinylibs/tinyexec/issues/27
|
|
96
|
+
await commandProcess
|
|
97
|
+
const { exitCode } = commandProcess
|
|
90
98
|
|
|
91
99
|
if (exitCode !== 0) {
|
|
92
100
|
logger.debug(`received exitCode ${exitCode}`)
|
|
@@ -94,11 +102,11 @@ async function executeCommand (commandArgs, env) {
|
|
|
94
102
|
}
|
|
95
103
|
} catch (error) {
|
|
96
104
|
// no color on these errors as they can be standard errors for things like jest exiting with exitCode 1 for a single failed test.
|
|
97
|
-
if (
|
|
105
|
+
if (signal !== 'SIGINT' && signal !== 'SIGTERM') {
|
|
98
106
|
if (error.code === 'ENOENT') {
|
|
99
|
-
logger.errornocolor(`Unknown command: ${error.
|
|
107
|
+
logger.errornocolor(`Unknown command: ${error.path}${error.spawnargs ? ' ' + error.spawnargs.join(' ') : ''}`)
|
|
100
108
|
} else if (error.message.includes('Command failed with exit code 1')) {
|
|
101
|
-
logger.errornocolor(`Command exited with exit code 1: ${error.
|
|
109
|
+
logger.errornocolor(`Command exited with exit code 1: ${error.path}${error.spawnargs ? ' ' + error.spawnargs.join(' ') : ''}`)
|
|
102
110
|
} else {
|
|
103
111
|
logger.errornocolor(error.message)
|
|
104
112
|
}
|
|
@@ -22,9 +22,10 @@ function executeExtension (ext, command, rawArgs) {
|
|
|
22
22
|
|
|
23
23
|
const result = childProcess.spawnSync(`dotenvx-ext-${command}`, forwardedArgs, { stdio: 'inherit', env })
|
|
24
24
|
if (result.error) {
|
|
25
|
-
if (command === '
|
|
26
|
-
|
|
27
|
-
logger.
|
|
25
|
+
if (command === 'vault') {
|
|
26
|
+
// when ready, uncomment to deprecate ext vault
|
|
27
|
+
// logger.warn(`[INSTALLATION_NEEDED] install dotenvx-ext-${command} to use [dotenvx ext ${command}] commands`)
|
|
28
|
+
// logger.help('? see installation instructions [https://github.com/dotenvx/dotenvx-ext-vault]')
|
|
28
29
|
} else {
|
|
29
30
|
logger.info(`error: unknown command '${command}'`)
|
|
30
31
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
function inject (
|
|
1
|
+
function inject (clonedProcessEnv = {}, parsed = {}, overload = false, processEnv = process.env) {
|
|
2
2
|
const injected = {}
|
|
3
3
|
const preExisted = {}
|
|
4
4
|
|
|
5
5
|
// set processEnv
|
|
6
6
|
for (const key of Object.keys(parsed)) {
|
|
7
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
7
|
+
if (Object.prototype.hasOwnProperty.call(clonedProcessEnv, key)) {
|
|
8
8
|
if (overload === true) {
|
|
9
9
|
processEnv[key] = parsed[key]
|
|
10
|
-
|
|
11
10
|
injected[key] = parsed[key] // track injected key/value
|
|
12
11
|
} else {
|
|
13
|
-
|
|
12
|
+
processEnv[key] = clonedProcessEnv[key]
|
|
13
|
+
preExisted[key] = clonedProcessEnv[key] // track preExisted key/value
|
|
14
14
|
}
|
|
15
15
|
} else {
|
|
16
16
|
processEnv[key] = parsed[key]
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
const
|
|
2
|
-
const path = require('path')
|
|
1
|
+
const { name, version, description } = require('../../../package.json')
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
7
|
-
|
|
8
|
-
module.exports = packageJson
|
|
3
|
+
module.exports = { name, version, description }
|
|
@@ -2,16 +2,29 @@ const dotenv = require('dotenv')
|
|
|
2
2
|
const dotenvEval = require('./dotenvEval')
|
|
3
3
|
const dotenvExpand = require('./dotenvExpand')
|
|
4
4
|
const decryptValue = require('./decryptValue')
|
|
5
|
+
const truncate = require('./truncate')
|
|
6
|
+
|
|
7
|
+
function warning (e, key, privateKey) {
|
|
8
|
+
const warning = new Error(`[${e.code}] could not decrypt ${key} using private key ${truncate(privateKey)}`)
|
|
9
|
+
warning.code = e.code
|
|
10
|
+
warning.help = `[${e.code}] ? ${e.message}`
|
|
11
|
+
|
|
12
|
+
return warning
|
|
13
|
+
}
|
|
5
14
|
|
|
6
15
|
function parseDecryptEvalExpand (src, privateKey = null, processEnv = process.env) {
|
|
16
|
+
const warnings = []
|
|
17
|
+
|
|
7
18
|
// parse
|
|
8
19
|
const parsed = dotenv.parse(src)
|
|
9
|
-
|
|
10
|
-
// handle inline encrypted values
|
|
11
20
|
if (privateKey && privateKey.length > 0) {
|
|
12
21
|
for (const key in parsed) {
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
try {
|
|
23
|
+
const decryptedValue = decryptValue(parsed[key], privateKey)
|
|
24
|
+
parsed[key] = decryptedValue
|
|
25
|
+
} catch (_e) {
|
|
26
|
+
// do nothing. warnings tracked further below.
|
|
27
|
+
}
|
|
15
28
|
}
|
|
16
29
|
}
|
|
17
30
|
|
|
@@ -28,6 +41,24 @@ function parseDecryptEvalExpand (src, privateKey = null, processEnv = process.en
|
|
|
28
41
|
parsed: evaled
|
|
29
42
|
}
|
|
30
43
|
const expanded = dotenvExpand.expand(inputEvaled)
|
|
44
|
+
if (privateKey && privateKey.length > 0) {
|
|
45
|
+
for (const key in expanded.parsed) {
|
|
46
|
+
try {
|
|
47
|
+
const decryptedValue = decryptValue(expanded.parsed[key], privateKey)
|
|
48
|
+
expanded.parsed[key] = decryptedValue
|
|
49
|
+
} catch (e) {
|
|
50
|
+
warnings.push(warning(e, key, privateKey))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const key in processEnv) {
|
|
54
|
+
try {
|
|
55
|
+
const decryptedValue = decryptValue(processEnv[key], privateKey)
|
|
56
|
+
processEnv[key] = decryptedValue
|
|
57
|
+
} catch (e) {
|
|
58
|
+
warnings.push(warning(e, key, privateKey))
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
31
62
|
|
|
32
63
|
// for logging only log the original keys existing in parsed. this feels unnecessarily complex - like dotenv-expand should support the ability to inject additional `process.env` or objects as it sees fit to the object it wants to expand
|
|
33
64
|
const result = {}
|
|
@@ -35,7 +66,7 @@ function parseDecryptEvalExpand (src, privateKey = null, processEnv = process.en
|
|
|
35
66
|
result[key] = expanded.parsed[key]
|
|
36
67
|
}
|
|
37
68
|
|
|
38
|
-
return { parsed: result, processEnv }
|
|
69
|
+
return { parsed: result, processEnv, warnings }
|
|
39
70
|
}
|
|
40
71
|
|
|
41
72
|
module.exports = parseDecryptEvalExpand
|
package/src/lib/main.d.ts
CHANGED
|
@@ -289,21 +289,6 @@ export function genexample(
|
|
|
289
289
|
envFile: string
|
|
290
290
|
): GenExampleOutput;
|
|
291
291
|
|
|
292
|
-
export type Settings = {
|
|
293
|
-
DOTENVX_SETTINGS_FILEPATH: string;
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
type KeyOfSettings = Extract<keyof Settings, string>;
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Get the dotenvx settings
|
|
300
|
-
*
|
|
301
|
-
* @param [key] - the key to get the value of
|
|
302
|
-
*/
|
|
303
|
-
export function settings(
|
|
304
|
-
key: KeyOfSettings | undefined | null = null
|
|
305
|
-
): Settings;
|
|
306
|
-
|
|
307
292
|
/**
|
|
308
293
|
* Decrypt ciphertext
|
|
309
294
|
* @param encrypted - the encrypted ciphertext string
|
package/src/lib/main.js
CHANGED
|
@@ -12,7 +12,6 @@ const Status = require('./services/status')
|
|
|
12
12
|
const Encrypt = require('./services/encrypt')
|
|
13
13
|
const Decrypt = require('./services/decrypt')
|
|
14
14
|
const Genexample = require('./services/genexample')
|
|
15
|
-
const Settings = require('./services/settings')
|
|
16
15
|
const VaultEncrypt = require('./services/vaultEncrypt')
|
|
17
16
|
|
|
18
17
|
// helpers
|
|
@@ -204,12 +203,6 @@ const status = function (directory) {
|
|
|
204
203
|
return new Status(directory).run()
|
|
205
204
|
}
|
|
206
205
|
|
|
207
|
-
/** @type {import('./main').settings} */
|
|
208
|
-
const settings = function (key = null) {
|
|
209
|
-
// @ts-ignore
|
|
210
|
-
return new Settings(key).run()
|
|
211
|
-
}
|
|
212
|
-
|
|
213
206
|
// misc/cleanup
|
|
214
207
|
|
|
215
208
|
/** @type {import('./main').vaultDecrypt} */
|
|
@@ -255,8 +248,6 @@ module.exports = {
|
|
|
255
248
|
set,
|
|
256
249
|
status,
|
|
257
250
|
genexample,
|
|
258
|
-
// settings
|
|
259
|
-
settings,
|
|
260
251
|
// misc/cleanup
|
|
261
252
|
vaultEncrypt,
|
|
262
253
|
vaultDecrypt
|
|
@@ -54,9 +54,9 @@ class Decrypt {
|
|
|
54
54
|
if (encrypted) {
|
|
55
55
|
row.keys.push(key) // track key(s)
|
|
56
56
|
|
|
57
|
-
const
|
|
57
|
+
const decryptedValue = decryptValue(value, privateKey)
|
|
58
58
|
// once newSrc is built write it out
|
|
59
|
-
src = replace(src, key,
|
|
59
|
+
src = replace(src, key, decryptedValue)
|
|
60
60
|
|
|
61
61
|
row.changed = true // track change
|
|
62
62
|
}
|
package/src/lib/services/ls.js
CHANGED
|
@@ -15,15 +15,17 @@ class Ls {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
_filepaths () {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
18
|
+
const exclude = picomatch(this.ignore)
|
|
19
|
+
const include = picomatch(this._patterns(), {
|
|
20
|
+
ignore: this.ignore
|
|
21
|
+
})
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
return new Fdir()
|
|
22
24
|
.withRelativePaths()
|
|
23
|
-
.exclude((dir, path) =>
|
|
24
|
-
.filter((path) =>
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
.exclude((dir, path) => exclude(path))
|
|
26
|
+
.filter((path) => include(path))
|
|
27
|
+
.crawl(this.cwd)
|
|
28
|
+
.sync()
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
_patterns () {
|
package/src/lib/services/run.js
CHANGED
|
@@ -63,12 +63,12 @@ class Run {
|
|
|
63
63
|
row.string = env
|
|
64
64
|
|
|
65
65
|
try {
|
|
66
|
-
const { parsed } = parseDecryptEvalExpand(env, null, this.processEnv)
|
|
67
|
-
|
|
66
|
+
const { parsed, processEnv, warnings } = parseDecryptEvalExpand(env, null, this.processEnv)
|
|
68
67
|
row.parsed = parsed
|
|
68
|
+
row.warnings = warnings
|
|
69
69
|
this.readableStrings.add(env)
|
|
70
70
|
|
|
71
|
-
const { injected, preExisted } = this._inject(
|
|
71
|
+
const { injected, preExisted } = this._inject(processEnv, parsed, this.overload, this.processEnv)
|
|
72
72
|
row.injected = injected
|
|
73
73
|
row.preExisted = preExisted
|
|
74
74
|
|
|
@@ -94,10 +94,11 @@ class Run {
|
|
|
94
94
|
|
|
95
95
|
// if DOTENV_PRIVATE_KEY_* already set in process.env then use it
|
|
96
96
|
const privateKey = smartDotenvPrivateKey(envFilepath)
|
|
97
|
-
const { parsed } = parseDecryptEvalExpand(src, privateKey, this.processEnv)
|
|
97
|
+
const { parsed, processEnv, warnings } = parseDecryptEvalExpand(src, privateKey, this.processEnv)
|
|
98
98
|
row.parsed = parsed
|
|
99
|
+
row.warnings = warnings
|
|
99
100
|
|
|
100
|
-
const { injected, preExisted } = this._inject(
|
|
101
|
+
const { injected, preExisted } = this._inject(processEnv, parsed, this.overload, this.processEnv)
|
|
101
102
|
row.injected = injected
|
|
102
103
|
row.preExisted = preExisted
|
|
103
104
|
|
|
@@ -163,10 +164,11 @@ class Run {
|
|
|
163
164
|
|
|
164
165
|
try {
|
|
165
166
|
// parse this. it's the equivalent of the .env file
|
|
166
|
-
const { parsed } = parseDecryptEvalExpand(decrypted, null, this.processEnv)
|
|
167
|
+
const { parsed, processEnv, warnings } = parseDecryptEvalExpand(decrypted, null, this.processEnv)
|
|
167
168
|
row.parsed = parsed
|
|
169
|
+
row.warnings = warnings
|
|
168
170
|
|
|
169
|
-
const { injected, preExisted } = this._inject(
|
|
171
|
+
const { injected, preExisted } = this._inject(processEnv, parsed, this.overload, this.processEnv)
|
|
170
172
|
row.injected = injected
|
|
171
173
|
row.preExisted = preExisted
|
|
172
174
|
|
|
@@ -180,8 +182,8 @@ class Run {
|
|
|
180
182
|
this.processedEnvs.push(row)
|
|
181
183
|
}
|
|
182
184
|
|
|
183
|
-
_inject (
|
|
184
|
-
return inject(
|
|
185
|
+
_inject (clonedProcessEnv, parsed, overload, processEnv) {
|
|
186
|
+
return inject(clonedProcessEnv, parsed, overload, processEnv)
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
_determineEnvsFromDotenvPrivateKey () {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const diff = require('diff')
|
|
4
|
-
const chalk = require('chalk')
|
|
5
4
|
|
|
6
5
|
const Ls = require('./ls')
|
|
7
6
|
const VaultDecrypt = require('./vaultDecrypt')
|
|
8
7
|
|
|
9
8
|
const containsDirectory = require('./../helpers/containsDirectory')
|
|
10
9
|
const guessEnvironment = require('./../helpers/guessEnvironment')
|
|
10
|
+
const { getColor } = require('./../../shared/colors')
|
|
11
11
|
|
|
12
12
|
const ENCODING = 'utf8'
|
|
13
13
|
|
|
@@ -94,12 +94,12 @@ class Status {
|
|
|
94
94
|
_colorizeDiff (part) {
|
|
95
95
|
// If the part was added, color it green
|
|
96
96
|
if (part.added) {
|
|
97
|
-
return
|
|
97
|
+
return getColor('green')(part.value)
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// If the part was removed, color it red
|
|
101
101
|
if (part.removed) {
|
|
102
|
-
return
|
|
102
|
+
return getColor('red')(part.value)
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// No color for unchanged parts
|
|
@@ -23,7 +23,7 @@ class VaultDecrypt {
|
|
|
23
23
|
if (!fs.existsSync(this.envVaultFilepath)) {
|
|
24
24
|
const code = 'MISSING_ENV_VAULT_FILE'
|
|
25
25
|
const message = `missing .env.vault (${this.envVaultFilepath})`
|
|
26
|
-
const help = `? generate one with [dotenvx encrypt ${this.directory}]`
|
|
26
|
+
const help = `? generate one with [dotenvx ext vault encrypt ${this.directory}]`
|
|
27
27
|
|
|
28
28
|
const error = new Error(message)
|
|
29
29
|
error.code = code
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const depth = require('../lib/helpers/colorDepth')
|
|
2
|
+
|
|
3
|
+
const colors16 = new Map([
|
|
4
|
+
['blue', 34],
|
|
5
|
+
['gray', 37],
|
|
6
|
+
['green', 32],
|
|
7
|
+
['olive', 33],
|
|
8
|
+
['orangered', 31], // mapped to red
|
|
9
|
+
['plum', 35], // mapped to magenta
|
|
10
|
+
['red', 31]
|
|
11
|
+
])
|
|
12
|
+
|
|
13
|
+
const colors256 = new Map([
|
|
14
|
+
['blue', 21],
|
|
15
|
+
['gray', 244],
|
|
16
|
+
['green', 34],
|
|
17
|
+
['olive', 142],
|
|
18
|
+
['orangered', 202],
|
|
19
|
+
['plum', 182],
|
|
20
|
+
['red', 196]
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
function getColor (color) {
|
|
24
|
+
const colorDepth = depth.getColorDepth()
|
|
25
|
+
if (!colors256.has(color)) {
|
|
26
|
+
throw new Error(`Invalid color ${color}`)
|
|
27
|
+
}
|
|
28
|
+
if (colorDepth >= 8) {
|
|
29
|
+
const code = colors256.get(color)
|
|
30
|
+
return (message) => `\x1b[38;5;${code}m${message}\x1b[39m`
|
|
31
|
+
}
|
|
32
|
+
if (colorDepth >= 4) {
|
|
33
|
+
const code = colors16.get(color)
|
|
34
|
+
return (message) => `\x1b[${code}m${message}\x1b[39m`
|
|
35
|
+
}
|
|
36
|
+
return (message) => message
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function bold (message) {
|
|
40
|
+
if (depth.getColorDepth() >= 4) {
|
|
41
|
+
return `\x1b[1m${message}\x1b[22m`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return message
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
getColor,
|
|
49
|
+
bold
|
|
50
|
+
}
|
package/src/shared/logger.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const winston = require('winston')
|
|
2
|
-
const chalk = require('chalk')
|
|
3
2
|
|
|
4
3
|
const printf = winston.format.printf
|
|
5
4
|
const combine = winston.format.combine
|
|
6
5
|
const createLogger = winston.createLogger
|
|
7
6
|
const transports = winston.transports
|
|
8
7
|
|
|
9
|
-
const packageJson = require('
|
|
8
|
+
const packageJson = require('../lib/helpers/packageJson')
|
|
9
|
+
const { getColor, bold } = require('./colors')
|
|
10
10
|
|
|
11
11
|
const levels = {
|
|
12
12
|
error: 0,
|
|
@@ -32,15 +32,15 @@ const levels = {
|
|
|
32
32
|
silly: 6
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const error =
|
|
36
|
-
const warn =
|
|
37
|
-
const success =
|
|
38
|
-
const successv =
|
|
39
|
-
const help =
|
|
40
|
-
const help2 =
|
|
41
|
-
const http =
|
|
42
|
-
const verbose =
|
|
43
|
-
const debug =
|
|
35
|
+
const error = (m) => bold(getColor('red')(m))
|
|
36
|
+
const warn = getColor('orangered')
|
|
37
|
+
const success = getColor('green')
|
|
38
|
+
const successv = getColor('olive') // yellow-ish tint that 'looks' like dotenv
|
|
39
|
+
const help = getColor('blue')
|
|
40
|
+
const help2 = getColor('gray')
|
|
41
|
+
const http = getColor('green')
|
|
42
|
+
const verbose = getColor('plum')
|
|
43
|
+
const debug = getColor('plum')
|
|
44
44
|
|
|
45
45
|
const dotenvxFormat = printf(({ level, message, label, timestamp }) => {
|
|
46
46
|
const formattedMessage = typeof message === 'object' ? JSON.stringify(message) : message
|
|
@@ -119,5 +119,6 @@ const setLogLevel = options => {
|
|
|
119
119
|
|
|
120
120
|
module.exports = {
|
|
121
121
|
logger,
|
|
122
|
+
getColor,
|
|
122
123
|
setLogLevel
|
|
123
124
|
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
const { logger } = require('./../../../shared/logger')
|
|
2
|
-
|
|
3
|
-
const main = require('./../../../lib/main')
|
|
4
|
-
|
|
5
|
-
function settings (key = null) {
|
|
6
|
-
logger.debug(`key: ${key}`)
|
|
7
|
-
|
|
8
|
-
const options = this.opts()
|
|
9
|
-
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
10
|
-
|
|
11
|
-
const value = main.settings(key)
|
|
12
|
-
|
|
13
|
-
if (typeof value === 'object' && value !== null) {
|
|
14
|
-
let space = 0
|
|
15
|
-
if (options.prettyPrint) {
|
|
16
|
-
space = 2
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
process.stdout.write(JSON.stringify(value, null, space))
|
|
20
|
-
} else {
|
|
21
|
-
process.stdout.write(value)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
module.exports = settings
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
const store = require('./../../shared/store')
|
|
2
|
-
|
|
3
|
-
class Settings {
|
|
4
|
-
constructor (key = null) {
|
|
5
|
-
this.key = key
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
run () {
|
|
9
|
-
const store = this._store()
|
|
10
|
-
|
|
11
|
-
if (this.key) {
|
|
12
|
-
return store[this.key]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// json of dotenvx.settings
|
|
16
|
-
return store
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
_store () {
|
|
20
|
-
const h = {
|
|
21
|
-
DOTENVX_SETTINGS_FILEPATH: store.configPath()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return { ...h, ...store.confStore.store }
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
module.exports = Settings
|
package/src/shared/store.js
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
const Conf = require('conf')
|
|
2
|
-
const dotenv = require('dotenv')
|
|
3
|
-
const packageJson = require('./../lib/helpers/packageJson')
|
|
4
|
-
|
|
5
|
-
function jsonToEnv (json) {
|
|
6
|
-
return Object.entries(json).map(function ([key, value]) {
|
|
7
|
-
return key + '=' + `"${value}"`
|
|
8
|
-
}).join('\n')
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function convertFullUsernameToEnvFormat (fullUsername) {
|
|
12
|
-
// gh/motdotla => GH_MOTDOTLA_DOTENVX_TOKEN
|
|
13
|
-
return fullUsername
|
|
14
|
-
.toUpperCase()
|
|
15
|
-
.replace(/\//g, '_') // Replace all slashes with underscores
|
|
16
|
-
.concat('_DOTENVX_TOKEN') // Append '_DOTENVX_TOKEN' at the end
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function findFirstMatchingKey (data) {
|
|
20
|
-
const dotenvxTokenValue = data.DOTENVX_TOKEN
|
|
21
|
-
|
|
22
|
-
for (const [key, value] of Object.entries(data)) {
|
|
23
|
-
if (key !== 'DOTENVX_TOKEN' && value === dotenvxTokenValue) {
|
|
24
|
-
return key
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return null // Return null if no matching key is found
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function parseUsernameFromTokenKey (key) {
|
|
32
|
-
// Remove the leading GH_/GL_ and trailing '_DOTENVX_TOKEN'
|
|
33
|
-
const modifiedKey = key.replace(/^(GH_|GL_)/, '').replace(/_DOTENVX_TOKEN$/, '')
|
|
34
|
-
|
|
35
|
-
// Convert to lowercase
|
|
36
|
-
return modifiedKey.toLowerCase()
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const confStore = new Conf({
|
|
40
|
-
projectName: 'dotenvx',
|
|
41
|
-
configName: '.env',
|
|
42
|
-
// looks better on user's machine
|
|
43
|
-
// https://github.com/sindresorhus/conf/tree/v10.2.0#projectsuffix.
|
|
44
|
-
projectSuffix: '',
|
|
45
|
-
fileExtension: '',
|
|
46
|
-
// in the spirit of dotenv and format inherently puts limits on config complexity
|
|
47
|
-
serialize: function (json) {
|
|
48
|
-
return jsonToEnv(json)
|
|
49
|
-
},
|
|
50
|
-
// Convert .env format to an object
|
|
51
|
-
deserialize: function (env) {
|
|
52
|
-
return dotenv.parse(env)
|
|
53
|
-
}
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
const getHostname = function () {
|
|
57
|
-
return confStore.get('DOTENVX_HOSTNAME') || 'https://hub.dotenvx.com'
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const getUsername = function () {
|
|
61
|
-
const key = findFirstMatchingKey(confStore.store)
|
|
62
|
-
|
|
63
|
-
if (key) {
|
|
64
|
-
return parseUsernameFromTokenKey(key)
|
|
65
|
-
} else {
|
|
66
|
-
return null
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const getToken = function () {
|
|
71
|
-
return confStore.get('DOTENVX_TOKEN')
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const getLatestVersion = function () {
|
|
75
|
-
return confStore.get('DOTENVX_LATEST_VERSION') || packageJson.version
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const getLatestVersionLastChecked = function () {
|
|
79
|
-
return parseInt(confStore.get('DOTENVX_LATEST_VERSION_LAST_CHECKED') || 0)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const setToken = function (fullUsername, accessToken) {
|
|
83
|
-
// current logged in user
|
|
84
|
-
confStore.set('DOTENVX_TOKEN', accessToken)
|
|
85
|
-
|
|
86
|
-
// for future use to switch between accounts locally
|
|
87
|
-
const memory = convertFullUsernameToEnvFormat(fullUsername)
|
|
88
|
-
confStore.set(memory, accessToken)
|
|
89
|
-
|
|
90
|
-
return accessToken
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const setHostname = function (hostname) {
|
|
94
|
-
confStore.set('DOTENVX_HOSTNAME', hostname)
|
|
95
|
-
|
|
96
|
-
return hostname
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const setLatestVersion = function (version) {
|
|
100
|
-
confStore.set('DOTENVX_LATEST_VERSION', version)
|
|
101
|
-
|
|
102
|
-
return version
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const setLatestVersionLastChecked = function (dateNow) {
|
|
106
|
-
confStore.set('DOTENVX_LATEST_VERSION_LAST_CHECKED', dateNow)
|
|
107
|
-
|
|
108
|
-
return dateNow
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const deleteToken = function () {
|
|
112
|
-
// memory user
|
|
113
|
-
const key = findFirstMatchingKey(confStore.store) // GH_MOTDOTLA_DOTENVX_TOKEN
|
|
114
|
-
confStore.delete(key)
|
|
115
|
-
|
|
116
|
-
// current logged in user
|
|
117
|
-
confStore.delete('DOTENVX_TOKEN')
|
|
118
|
-
|
|
119
|
-
return true
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const deleteHostname = function () {
|
|
123
|
-
confStore.delete('DOTENVX_HOSTNAME')
|
|
124
|
-
|
|
125
|
-
return true
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const configPath = function () {
|
|
129
|
-
return confStore.path
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
module.exports = {
|
|
133
|
-
confStore,
|
|
134
|
-
getHostname,
|
|
135
|
-
getToken,
|
|
136
|
-
getUsername,
|
|
137
|
-
getLatestVersion,
|
|
138
|
-
getLatestVersionLastChecked,
|
|
139
|
-
setHostname,
|
|
140
|
-
setToken,
|
|
141
|
-
setLatestVersion,
|
|
142
|
-
setLatestVersionLastChecked,
|
|
143
|
-
deleteToken,
|
|
144
|
-
deleteHostname,
|
|
145
|
-
configPath
|
|
146
|
-
}
|