@dotenvx/dotenvx 0.35.1 → 0.36.1
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 +1 -0
- package/package.json +2 -1
- package/src/cli/actions/decrypt.js +47 -71
- package/src/cli/actions/run.js +18 -2
- package/src/cli/actions/status.js +49 -0
- package/src/cli/dotenvx.js +8 -0
- package/src/lib/helpers/arrayToTree.js +4 -1
- package/src/lib/helpers/containsDirectory.js +7 -0
- package/src/lib/main.js +6 -0
- package/src/lib/services/decrypt.js +126 -0
- package/src/lib/services/status.js +105 -0
package/README.md
CHANGED
|
@@ -732,6 +732,7 @@ $ dotenvx hub push
|
|
|
732
732
|
* [`dotenvx get`](https://dotenvx.com/docs/features/get) – return a single environment variable
|
|
733
733
|
* [`dotenvx set`](https://dotenvx.com/docs/features/set) – set a single environment variable
|
|
734
734
|
* [`dotenvx ls`](https://dotenvx.com/docs/features/ls) – list all .env files in your repo
|
|
735
|
+
* [`dotenvx status`](https://dotenvx.com/docs/features/status) – compare your .env* content(s) to your .env.vault decrypted content(s)
|
|
735
736
|
* [`dotenvx settings`](https://dotenvx.com/docs/features/settings) – print current dotenvx settings
|
|
736
737
|
|
|
737
738
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.36.1",
|
|
3
3
|
"name": "@dotenvx/dotenvx",
|
|
4
4
|
"description": "a better dotenv–from the creator of `dotenv`",
|
|
5
5
|
"author": "@motdotla",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"chalk": "^4.1.2",
|
|
33
33
|
"commander": "^11.1.0",
|
|
34
34
|
"conf": "^10.2.0",
|
|
35
|
+
"diff": "^5.2.0",
|
|
35
36
|
"dotenv": "^16.4.5",
|
|
36
37
|
"dotenv-expand": "^11.0.6",
|
|
37
38
|
"execa": "^5.1.1",
|
|
@@ -1,94 +1,70 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
|
-
const dotenv = require('dotenv')
|
|
3
2
|
|
|
4
3
|
const logger = require('./../../shared/logger')
|
|
5
4
|
const createSpinner = require('./../../shared/createSpinner')
|
|
6
|
-
|
|
7
|
-
const libDecrypt = require('./../../lib/helpers/decrypt')
|
|
8
5
|
const sleep = require('./../../lib/helpers/sleep')
|
|
9
|
-
const resolvePath = require('./../../lib/helpers/resolvePath')
|
|
10
6
|
|
|
11
|
-
const
|
|
7
|
+
const Decrypt = require('./../../lib/services/decrypt')
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
const ENCODING = 'utf8'
|
|
9
|
+
const spinner = createSpinner('decrypting')
|
|
15
10
|
|
|
16
|
-
async function decrypt () {
|
|
11
|
+
async function decrypt (directory) {
|
|
17
12
|
spinner.start()
|
|
18
13
|
await sleep(500) // better dx
|
|
19
14
|
|
|
15
|
+
logger.debug(`directory: ${directory}`)
|
|
16
|
+
|
|
20
17
|
const options = this.opts()
|
|
21
18
|
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// logger.verbose(`checking for .env.keys`)
|
|
36
|
-
if (!fs.existsSync(keysFilepath)) {
|
|
37
|
-
spinner.fail(`.env.keys file missing: ${keysFilepath}`)
|
|
38
|
-
logger.help('? a .env.keys file must be present in order to decrypt your .env.vault contents to .env file(s)')
|
|
39
|
-
process.exit(1)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const dotenvKeys = dotenv.configDotenv({ path: keysFilepath }).parsed
|
|
43
|
-
const dotenvVault = dotenv.configDotenv({ path: vaultFilepath }).parsed
|
|
44
|
-
|
|
45
|
-
Object.entries(dotenvKeys).forEach(([dotenvKey, value]) => {
|
|
46
|
-
// determine environment
|
|
47
|
-
const environment = dotenvKey.replace('DOTENV_KEY_', '').toLowerCase()
|
|
48
|
-
// determine corresponding vault key
|
|
49
|
-
const vaultKey = `DOTENV_VAULT_${environment.toUpperCase()}`
|
|
50
|
-
|
|
51
|
-
// attempt to find ciphertext
|
|
52
|
-
const ciphertext = dotenvVault[vaultKey]
|
|
53
|
-
|
|
54
|
-
// give warning if not found
|
|
55
|
-
if (ciphertext && ciphertext.length >= 1) {
|
|
56
|
-
// Decrypt
|
|
57
|
-
const decrypted = libDecrypt(ciphertext, value.trim())
|
|
58
|
-
|
|
59
|
-
// envFilename
|
|
60
|
-
// replace _ with . to support filenames like .env.development.local
|
|
61
|
-
let envFilename = `.env.${environment.replace('_', '.')}`
|
|
62
|
-
if (environment === 'development') {
|
|
63
|
-
envFilename = '.env'
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// check if exists
|
|
67
|
-
if (fs.existsSync(envFilename) && (fs.readFileSync(envFilename, { encoding: ENCODING }).toString() === decrypted)) {
|
|
68
|
-
unchangedEnvFilenames.add(envFilename)
|
|
20
|
+
try {
|
|
21
|
+
const {
|
|
22
|
+
processedEnvs,
|
|
23
|
+
changedFilenames,
|
|
24
|
+
unchangedFilenames
|
|
25
|
+
} = new Decrypt(directory, options.environment).run()
|
|
26
|
+
|
|
27
|
+
for (const env of processedEnvs) {
|
|
28
|
+
if (env.warning) {
|
|
29
|
+
const warning = env.warning
|
|
30
|
+
logger.warn(warning.message)
|
|
69
31
|
} else {
|
|
70
|
-
|
|
71
|
-
|
|
32
|
+
if (env.shouldWrite) {
|
|
33
|
+
logger.debug(`writing ${env.filepath}`)
|
|
34
|
+
fs.writeFileSync(env.filepath, env.decrypted)
|
|
35
|
+
} else {
|
|
36
|
+
logger.debug(`no changes for ${env.filename}`)
|
|
37
|
+
}
|
|
72
38
|
}
|
|
73
|
-
} else {
|
|
74
|
-
logger.warn(`${vaultKey} missing in .env.vault: ${vaultFilepath}`)
|
|
75
39
|
}
|
|
76
|
-
})
|
|
77
40
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
41
|
+
let changedMsg = ''
|
|
42
|
+
if (changedFilenames.length > 0) {
|
|
43
|
+
changedMsg = `decrypted (${changedFilenames.join(', ')})`
|
|
44
|
+
}
|
|
82
45
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
46
|
+
let unchangedMsg = ''
|
|
47
|
+
if (unchangedFilenames.length > 0) {
|
|
48
|
+
unchangedMsg = `no changes (${unchangedFilenames.join(', ')})`
|
|
49
|
+
}
|
|
87
50
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
51
|
+
if (changedMsg.length > 0) {
|
|
52
|
+
spinner.succeed(`${changedMsg} ${unchangedMsg}`)
|
|
53
|
+
} else {
|
|
54
|
+
spinner.done(`${unchangedMsg}`)
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
spinner.fail(error.message)
|
|
58
|
+
if (error.help) {
|
|
59
|
+
logger.help(error.help)
|
|
60
|
+
}
|
|
61
|
+
if (error.debug) {
|
|
62
|
+
logger.debug(error.debug)
|
|
63
|
+
}
|
|
64
|
+
if (error.code) {
|
|
65
|
+
logger.debug(`ERROR_CODE: ${error.code}`)
|
|
66
|
+
}
|
|
67
|
+
process.exit(1)
|
|
92
68
|
}
|
|
93
69
|
}
|
|
94
70
|
|
package/src/cli/actions/run.js
CHANGED
|
@@ -8,7 +8,7 @@ const Run = require('./../../lib/services/run')
|
|
|
8
8
|
const executeCommand = async function (commandArgs, env) {
|
|
9
9
|
const signals = [
|
|
10
10
|
'SIGHUP', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT',
|
|
11
|
-
'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2'
|
|
11
|
+
'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2'
|
|
12
12
|
]
|
|
13
13
|
|
|
14
14
|
logger.debug(`executing process command [${commandArgs.join(' ')}]`)
|
|
@@ -27,6 +27,19 @@ const executeCommand = async function (commandArgs, env) {
|
|
|
27
27
|
logger.debug('no command process to send SIGINT to')
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
// handler for SIGTERM
|
|
31
|
+
const sigtermHandler = () => {
|
|
32
|
+
logger.debug('received SIGTERM')
|
|
33
|
+
logger.debug('checking command process')
|
|
34
|
+
logger.debug(commandProcess)
|
|
35
|
+
|
|
36
|
+
if (commandProcess) {
|
|
37
|
+
logger.debug('sending SIGTERM to command process')
|
|
38
|
+
commandProcess.kill('SIGTERM') // Send SIGTEM to the command process
|
|
39
|
+
} else {
|
|
40
|
+
logger.debug('no command process to send SIGTERM to')
|
|
41
|
+
}
|
|
42
|
+
}
|
|
30
43
|
|
|
31
44
|
const handleOtherSignal = (signal) => {
|
|
32
45
|
logger.debug(`received ${signal}`)
|
|
@@ -47,6 +60,7 @@ const executeCommand = async function (commandArgs, env) {
|
|
|
47
60
|
})
|
|
48
61
|
|
|
49
62
|
process.on('SIGINT', sigintHandler)
|
|
63
|
+
process.on('SIGTERM', sigtermHandler)
|
|
50
64
|
|
|
51
65
|
signals.forEach(signal => {
|
|
52
66
|
process.on(signal, () => handleOtherSignal(signal))
|
|
@@ -61,7 +75,7 @@ const executeCommand = async function (commandArgs, env) {
|
|
|
61
75
|
}
|
|
62
76
|
} catch (error) {
|
|
63
77
|
// no color on these errors as they can be standard errors for things like jest exiting with exitCode 1 for a single failed test.
|
|
64
|
-
if (error.signal !== 'SIGINT') {
|
|
78
|
+
if (error.signal !== 'SIGINT' && error.signal !== 'SIGTERM') {
|
|
65
79
|
if (error.code === 'ENOENT') {
|
|
66
80
|
logger.errornocolor(`Unknown command: ${error.command}`)
|
|
67
81
|
} else if (error.message.includes('Command failed with exit code 1')) {
|
|
@@ -76,6 +90,8 @@ const executeCommand = async function (commandArgs, env) {
|
|
|
76
90
|
} finally {
|
|
77
91
|
// Clean up: Remove the SIGINT handler
|
|
78
92
|
process.removeListener('SIGINT', sigintHandler)
|
|
93
|
+
// Clean up: Remove the SIGTERM handler
|
|
94
|
+
process.removeListener('SIGTERM', sigtermHandler)
|
|
79
95
|
}
|
|
80
96
|
}
|
|
81
97
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const logger = require('./../../shared/logger')
|
|
2
|
+
|
|
3
|
+
const main = require('./../../lib/main')
|
|
4
|
+
|
|
5
|
+
function status (directory) {
|
|
6
|
+
// debug args
|
|
7
|
+
logger.debug(`directory: ${directory}`)
|
|
8
|
+
|
|
9
|
+
const options = this.opts()
|
|
10
|
+
logger.debug(`options: ${JSON.stringify(options)}`)
|
|
11
|
+
|
|
12
|
+
const { changes, nochanges } = main.status(directory)
|
|
13
|
+
|
|
14
|
+
const changeFilenames = []
|
|
15
|
+
const nochangeFilenames = []
|
|
16
|
+
|
|
17
|
+
for (const row of nochanges) {
|
|
18
|
+
nochangeFilenames.push(row.filename)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (nochangeFilenames.length > 0) {
|
|
22
|
+
logger.blank(`no changes (${nochangeFilenames.join(', ')})`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const row of changes) {
|
|
26
|
+
changeFilenames.push(row.filename)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (changeFilenames.length > 0) {
|
|
30
|
+
logger.warn(`changes (${changeFilenames.join(', ')})`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const row of changes) {
|
|
34
|
+
logger.blank('')
|
|
35
|
+
const padding = ' '
|
|
36
|
+
logger.blank(`${padding}\`\`\`${row.filename}`)
|
|
37
|
+
const paddedResult = row.coloredDiff.split('\n').map(line => padding + line).join('\n')
|
|
38
|
+
console.log(paddedResult)
|
|
39
|
+
logger.blank(`${padding}\`\`\``)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (changeFilenames.length > 0) {
|
|
43
|
+
logger.blank('')
|
|
44
|
+
const optionalDirectory = directory === '.' ? '' : ` ${directory}`
|
|
45
|
+
logger.blank(`run [dotenvx encrypt${optionalDirectory}] to apply changes to .env.vault`)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = status
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -111,8 +111,16 @@ program.command('encrypt')
|
|
|
111
111
|
// dotenvx decrypt
|
|
112
112
|
program.command('decrypt')
|
|
113
113
|
.description('decrypt .env.vault to .env*')
|
|
114
|
+
.argument('[directory]', 'directory to decrypt', '.')
|
|
115
|
+
.option('-e, --environment <environments...>', 'environment(s) to decrypt')
|
|
114
116
|
.action(require('./actions/decrypt'))
|
|
115
117
|
|
|
118
|
+
// dotenvx status
|
|
119
|
+
program.command('status')
|
|
120
|
+
.description('compare your .env* content(s) to your .env.vault decrypted content(s)')
|
|
121
|
+
.argument('[directory]', 'directory to check status against', '.')
|
|
122
|
+
.action(require('./actions/status'))
|
|
123
|
+
|
|
116
124
|
// dotenvx genexample
|
|
117
125
|
program.command('genexample')
|
|
118
126
|
.description('generate .env.example')
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
|
|
1
3
|
class ArrayToTree {
|
|
2
4
|
constructor (arr) {
|
|
3
5
|
this.arr = arr
|
|
@@ -7,7 +9,8 @@ class ArrayToTree {
|
|
|
7
9
|
const tree = {}
|
|
8
10
|
|
|
9
11
|
for (let i = 0; i < this.arr.length; i++) {
|
|
10
|
-
const
|
|
12
|
+
const normalizedPath = path.normalize(this.arr[i]) // normalize any strange paths
|
|
13
|
+
const parts = normalizedPath.split(path.sep) // use the platform-specific path segment separator
|
|
11
14
|
let current = tree
|
|
12
15
|
|
|
13
16
|
for (let j = 0; j < parts.length; j++) {
|
package/src/lib/main.js
CHANGED
|
@@ -7,6 +7,7 @@ const Encrypt = require('./services/encrypt')
|
|
|
7
7
|
const Ls = require('./services/ls')
|
|
8
8
|
const Get = require('./services/get')
|
|
9
9
|
const Sets = require('./services/sets')
|
|
10
|
+
const Status = require('./services/status')
|
|
10
11
|
const Genexample = require('./services/genexample')
|
|
11
12
|
const Settings = require('./services/settings')
|
|
12
13
|
|
|
@@ -61,6 +62,10 @@ const set = function (key, value, envFile) {
|
|
|
61
62
|
return new Sets(key, value, envFile).run()
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
const status = function (directory) {
|
|
66
|
+
return new Status(directory).run()
|
|
67
|
+
}
|
|
68
|
+
|
|
64
69
|
const settings = function (key = null) {
|
|
65
70
|
return new Settings(key).run()
|
|
66
71
|
}
|
|
@@ -94,6 +99,7 @@ module.exports = {
|
|
|
94
99
|
ls,
|
|
95
100
|
get,
|
|
96
101
|
set,
|
|
102
|
+
status,
|
|
97
103
|
genexample,
|
|
98
104
|
// settings
|
|
99
105
|
settings,
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const dotenv = require('dotenv')
|
|
4
|
+
|
|
5
|
+
const ENCODING = 'utf8'
|
|
6
|
+
|
|
7
|
+
const libDecrypt = require('./../../lib/helpers/decrypt')
|
|
8
|
+
|
|
9
|
+
class Decrypt {
|
|
10
|
+
constructor (directory = '.', environment) {
|
|
11
|
+
this.directory = directory
|
|
12
|
+
this.environment = environment
|
|
13
|
+
|
|
14
|
+
this.envKeysFilepath = path.resolve(this.directory, '.env.keys')
|
|
15
|
+
this.envVaultFilepath = path.resolve(this.directory, '.env.vault')
|
|
16
|
+
|
|
17
|
+
this.processedEnvs = []
|
|
18
|
+
this.changedFilenames = new Set()
|
|
19
|
+
this.unchangedFilenames = new Set()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
run () {
|
|
23
|
+
if (!fs.existsSync(this.envVaultFilepath)) {
|
|
24
|
+
const code = 'MISSING_ENV_VAULT_FILE'
|
|
25
|
+
const message = `missing .env.vault (${this.envVaultFilepath})`
|
|
26
|
+
const help = `? generate one with [dotenvx encrypt ${this.directory}]`
|
|
27
|
+
|
|
28
|
+
const error = new Error(message)
|
|
29
|
+
error.code = code
|
|
30
|
+
error.help = help
|
|
31
|
+
throw error
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!fs.existsSync(this.envKeysFilepath)) {
|
|
35
|
+
const code = 'MISSING_ENV_KEYS_FILE'
|
|
36
|
+
const message = `missing .env.keys (${this.envKeysFilepath})`
|
|
37
|
+
const help = '? a .env.keys file must be present in order to decrypt your .env.vault contents to .env file(s)'
|
|
38
|
+
|
|
39
|
+
const error = new Error(message)
|
|
40
|
+
error.code = code
|
|
41
|
+
error.help = help
|
|
42
|
+
throw error
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const dotenvKeys = dotenv.configDotenv({ path: this.envKeysFilepath }).parsed
|
|
46
|
+
const dotenvVault = dotenv.configDotenv({ path: this.envVaultFilepath }).parsed
|
|
47
|
+
const environments = this._environments()
|
|
48
|
+
|
|
49
|
+
if (environments.length > 0) {
|
|
50
|
+
// iterate over the environments
|
|
51
|
+
for (const environment of environments) {
|
|
52
|
+
const value = dotenvKeys[`DOTENV_KEY_${environment.toUpperCase()}`]
|
|
53
|
+
const row = this._processRow(value, dotenvVault, environment)
|
|
54
|
+
|
|
55
|
+
this.processedEnvs.push(row)
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
for (const [dotenvKey, value] of Object.entries(dotenvKeys)) {
|
|
59
|
+
const environment = dotenvKey.replace('DOTENV_KEY_', '').toLowerCase()
|
|
60
|
+
const row = this._processRow(value, dotenvVault, environment)
|
|
61
|
+
|
|
62
|
+
this.processedEnvs.push(row)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
processedEnvs: this.processedEnvs,
|
|
68
|
+
changedFilenames: [...this.changedFilenames],
|
|
69
|
+
unchangedFilenames: [...this.unchangedFilenames]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_processRow (value, dotenvVault, environment) {
|
|
74
|
+
environment = environment.toLowerCase() // important so we don't later write .env.DEVELOPMENT for example
|
|
75
|
+
const vaultKey = `DOTENV_VAULT_${environment.toUpperCase()}`
|
|
76
|
+
const ciphertext = dotenvVault[vaultKey] // attempt to find ciphertext
|
|
77
|
+
|
|
78
|
+
const row = {}
|
|
79
|
+
row.environment = environment
|
|
80
|
+
row.dotenvKey = value ? value.trim() : value
|
|
81
|
+
row.ciphertext = ciphertext
|
|
82
|
+
|
|
83
|
+
if (ciphertext && ciphertext.length >= 1) {
|
|
84
|
+
// Decrypt
|
|
85
|
+
const decrypted = libDecrypt(ciphertext, value.trim())
|
|
86
|
+
row.decrypted = decrypted
|
|
87
|
+
|
|
88
|
+
// envFilename
|
|
89
|
+
// replace _ with . to support filenames like .env.development.local
|
|
90
|
+
let envFilename = `.env.${environment.replace('_', '.')}`
|
|
91
|
+
if (environment === 'development') {
|
|
92
|
+
envFilename = '.env'
|
|
93
|
+
}
|
|
94
|
+
row.filename = envFilename
|
|
95
|
+
row.filepath = path.resolve(this.directory, envFilename)
|
|
96
|
+
|
|
97
|
+
// check if exists and is changing
|
|
98
|
+
if (fs.existsSync(row.filepath) && (fs.readFileSync(row.filepath, { encoding: ENCODING }).toString() === decrypted)) {
|
|
99
|
+
this.unchangedFilenames.add(envFilename)
|
|
100
|
+
} else {
|
|
101
|
+
row.shouldWrite = true
|
|
102
|
+
this.changedFilenames.add(envFilename)
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
const message = `${vaultKey} missing in .env.vault: ${this.envVaultFilepath}`
|
|
106
|
+
const warning = new Error(message)
|
|
107
|
+
row.warning = warning
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return row
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_environments () {
|
|
114
|
+
if (this.environment === undefined) {
|
|
115
|
+
return []
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!Array.isArray(this.environment)) {
|
|
119
|
+
return [this.environment]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return this.environment
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = Decrypt
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const diff = require('diff')
|
|
4
|
+
const chalk = require('chalk')
|
|
5
|
+
|
|
6
|
+
const Ls = require('./ls')
|
|
7
|
+
const Decrypt = require('./decrypt')
|
|
8
|
+
|
|
9
|
+
const containsDirectory = require('./../helpers/containsDirectory')
|
|
10
|
+
const guessEnvironment = require('./../helpers/guessEnvironment')
|
|
11
|
+
|
|
12
|
+
const ENCODING = 'utf8'
|
|
13
|
+
|
|
14
|
+
class Status {
|
|
15
|
+
constructor (directory = '.') {
|
|
16
|
+
this.directory = directory
|
|
17
|
+
this.changes = []
|
|
18
|
+
this.nochanges = []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
run () {
|
|
22
|
+
// get list of .env files
|
|
23
|
+
const files = new Ls(this.directory).run()
|
|
24
|
+
// iterate over each one
|
|
25
|
+
for (const filename of files) {
|
|
26
|
+
// skip file if directory
|
|
27
|
+
if (containsDirectory(filename)) {
|
|
28
|
+
continue
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// skip file if .env.keys
|
|
32
|
+
if (filename.endsWith('.env.keys')) {
|
|
33
|
+
continue
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// skip file if .env.vault
|
|
37
|
+
if (filename.endsWith('.env.vault')) {
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// skip file if .env.example
|
|
42
|
+
if (filename.endsWith('.env.example')) {
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// skip file if *.previous
|
|
47
|
+
if (filename.endsWith('.previous')) {
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const filepath = path.resolve(this.directory, filename)
|
|
52
|
+
|
|
53
|
+
const row = {}
|
|
54
|
+
row.filename = filename
|
|
55
|
+
row.filepath = filepath
|
|
56
|
+
row.environment = guessEnvironment(filepath)
|
|
57
|
+
|
|
58
|
+
// grab raw
|
|
59
|
+
row.raw = fs.readFileSync(filepath, { encoding: ENCODING })
|
|
60
|
+
|
|
61
|
+
// grab decrypted
|
|
62
|
+
const { processedEnvs } = new Decrypt(this.directory, row.environment).run()
|
|
63
|
+
row.decrypted = processedEnvs[0].decrypted
|
|
64
|
+
|
|
65
|
+
// differences
|
|
66
|
+
row.differences = diff.diffWords(row.decrypted, row.raw)
|
|
67
|
+
|
|
68
|
+
// any changes?
|
|
69
|
+
const hasChanges = this._hasChanges(row.differences)
|
|
70
|
+
|
|
71
|
+
if (hasChanges) {
|
|
72
|
+
row.coloredDiff = row.differences.map(this._colorizeDiff).join('')
|
|
73
|
+
this.changes.push(row)
|
|
74
|
+
} else {
|
|
75
|
+
this.nochanges.push(row)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
changes: this.changes,
|
|
81
|
+
nochanges: this.nochanges
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_colorizeDiff (part) {
|
|
86
|
+
// If the part was added, color it green
|
|
87
|
+
if (part.added) {
|
|
88
|
+
return chalk.green(part.value)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// If the part was removed, color it red
|
|
92
|
+
if (part.removed) {
|
|
93
|
+
return chalk.red(part.value)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// No color for unchanged parts
|
|
97
|
+
return part.value
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_hasChanges (differences) {
|
|
101
|
+
return differences.some(part => part.added || part.removed)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = Status
|