@dotenvx/dotenvx 0.19.0 → 0.20.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/package.json +1 -1
- package/src/cli/actions/precommit.js +0 -102
- package/src/cli/actions/run.js +35 -39
- package/src/cli/dotenvx.js +1 -0
- package/src/lib/services/precommit.js +54 -1
- package/src/lib/services/runDefault.js +155 -0
package/package.json
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const ignore = require('ignore')
|
|
4
|
-
|
|
5
1
|
const logger = require('./../../shared/logger')
|
|
6
2
|
|
|
7
|
-
const helpers = require('./../helpers')
|
|
8
|
-
|
|
9
3
|
const Precommit = require('./../../lib/services/precommit')
|
|
10
4
|
|
|
11
5
|
function precommit () {
|
|
@@ -14,102 +8,6 @@ function precommit () {
|
|
|
14
8
|
|
|
15
9
|
const precommit = new Precommit(options)
|
|
16
10
|
precommit.run()
|
|
17
|
-
|
|
18
|
-
// 0. handle the --install flag
|
|
19
|
-
if (options.install) {
|
|
20
|
-
installPrecommitHook()
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// 1. check for .gitignore file
|
|
25
|
-
if (!fs.existsSync('.gitignore')) {
|
|
26
|
-
logger.errorvp('.gitignore missing')
|
|
27
|
-
logger.help2('? add it with [touch .gitignore]')
|
|
28
|
-
process.exit(1)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// 2. check .env* files against .gitignore file
|
|
32
|
-
let warningCount = 0
|
|
33
|
-
const ig = ignore().add(fs.readFileSync('.gitignore').toString())
|
|
34
|
-
const files = fs.readdirSync(process.cwd())
|
|
35
|
-
const dotenvFiles = files.filter(file => file.match(/^\.env(\..+)?$/))
|
|
36
|
-
dotenvFiles.forEach(file => {
|
|
37
|
-
// check if that file is being ignored
|
|
38
|
-
if (ig.ignores(file)) {
|
|
39
|
-
switch (file) {
|
|
40
|
-
case '.env.example':
|
|
41
|
-
warningCount += 1
|
|
42
|
-
logger.warnv(`${file} (currently ignored but should not be)`)
|
|
43
|
-
logger.help2(`? add !${file} to .gitignore with [echo "!${file}" >> .gitignore]`)
|
|
44
|
-
break
|
|
45
|
-
case '.env.vault':
|
|
46
|
-
warningCount += 1
|
|
47
|
-
logger.warnv(`${file} (currently ignored but should not be)`)
|
|
48
|
-
logger.help2(`? add !${file} to .gitignore with [echo "!${file}" >> .gitignore]`)
|
|
49
|
-
break
|
|
50
|
-
default:
|
|
51
|
-
break
|
|
52
|
-
}
|
|
53
|
-
} else {
|
|
54
|
-
switch (file) {
|
|
55
|
-
case '.env.example':
|
|
56
|
-
break
|
|
57
|
-
case '.env.vault':
|
|
58
|
-
break
|
|
59
|
-
default:
|
|
60
|
-
logger.errorvp(`${file} not properly gitignored`)
|
|
61
|
-
logger.help2(`? add ${file} to .gitignore with [echo ".env*" >> .gitignore]`)
|
|
62
|
-
process.exit(1) // 3.1 exit early with error code
|
|
63
|
-
break
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
// 3. outpout success
|
|
69
|
-
if (warningCount > 0) {
|
|
70
|
-
logger.successvp(`success (with ${helpers.pluralize('warning', warningCount)})`)
|
|
71
|
-
} else {
|
|
72
|
-
logger.successvp('success')
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function installPrecommitHook () {
|
|
77
|
-
const hookScript = `#!/bin/sh
|
|
78
|
-
|
|
79
|
-
if ! command -v dotenvx &> /dev/null
|
|
80
|
-
then
|
|
81
|
-
echo "[dotenvx][precommit] 'dotenvx' command not found"
|
|
82
|
-
echo "[dotenvx][precommit] ? install it with [brew install dotenvx/brew/dotenvx]"
|
|
83
|
-
echo "[dotenvx][precommit] ? other install options [https://dotenvx.com/docs/install]"
|
|
84
|
-
exit 1
|
|
85
|
-
fi
|
|
86
|
-
|
|
87
|
-
dotenvx precommit`
|
|
88
|
-
const hookPath = path.join('.git', 'hooks', 'pre-commit')
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
// Check if the pre-commit file already exists
|
|
92
|
-
if (fs.existsSync(hookPath)) {
|
|
93
|
-
// Read the existing content of the file
|
|
94
|
-
const existingContent = fs.readFileSync(hookPath, 'utf8')
|
|
95
|
-
|
|
96
|
-
// Check if 'dotenvx precommit' already exists in the file
|
|
97
|
-
if (!existingContent.includes('dotenvx precommit')) {
|
|
98
|
-
// Append 'dotenvx precommit' to the existing file
|
|
99
|
-
fs.appendFileSync(hookPath, '\n' + hookScript)
|
|
100
|
-
logger.successvp(`dotenvx precommit appended [${hookPath}]`)
|
|
101
|
-
} else {
|
|
102
|
-
logger.warnvp(`dotenvx precommit exists [${hookPath}]`)
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
// If the pre-commit file doesn't exist, create a new one with the hookScript
|
|
106
|
-
fs.writeFileSync(hookPath, hookScript)
|
|
107
|
-
fs.chmodSync(hookPath, '755') // Make the file executable
|
|
108
|
-
logger.successvp(`dotenvx precommit installed [${hookPath}]`)
|
|
109
|
-
}
|
|
110
|
-
} catch (err) {
|
|
111
|
-
logger.errorvp(`Failed to modify pre-commit hook: ${err.message}`)
|
|
112
|
-
}
|
|
113
11
|
}
|
|
114
12
|
|
|
115
13
|
module.exports = precommit
|
package/src/cli/actions/run.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
2
3
|
const execa = require('execa')
|
|
3
4
|
const logger = require('./../../shared/logger')
|
|
4
5
|
const helpers = require('./../helpers')
|
|
5
6
|
const main = require('./../../lib/main')
|
|
6
7
|
const parseEncryptionKeyFromDotenvKey = require('./../../lib/helpers/parseEncryptionKeyFromDotenvKey')
|
|
7
8
|
|
|
9
|
+
const RunDefault = require('./../../lib/services/runDefault')
|
|
10
|
+
|
|
8
11
|
const ENCODING = 'utf8'
|
|
9
12
|
const REPORT_ISSUE_LINK = 'https://github.com/dotenvx/dotenvx/issues/new'
|
|
10
13
|
|
|
@@ -156,55 +159,48 @@ async function run () {
|
|
|
156
159
|
}
|
|
157
160
|
}
|
|
158
161
|
} else {
|
|
159
|
-
|
|
160
|
-
let optionEnvFile = options.envFile
|
|
161
|
-
if (!Array.isArray(optionEnvFile)) {
|
|
162
|
-
optionEnvFile = [optionEnvFile]
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const readableFilepaths = new Set()
|
|
166
|
-
const injected = new Set()
|
|
162
|
+
const { files, readableFilepaths, uniqueInjectedKeys } = new RunDefault(options.envFile, options.env, options.overload).run()
|
|
167
163
|
|
|
168
|
-
for (const
|
|
169
|
-
const filepath =
|
|
164
|
+
for (const file of files) {
|
|
165
|
+
const filepath = file.filepath
|
|
170
166
|
|
|
171
|
-
logger.verbose(`loading env from ${filepath}`)
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
const src = fs.readFileSync(filepath, { encoding: ENCODING })
|
|
175
|
-
const parsed = main.parseExpand(src, options.overload)
|
|
176
|
-
const result = main.inject(process.env, parsed, options.overload)
|
|
167
|
+
logger.verbose(`loading env from ${filepath} (${path.resolve(filepath)})`)
|
|
177
168
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
169
|
+
if (file.error) {
|
|
170
|
+
if (file.error.code === 'MISSING_ENV_FILE') {
|
|
171
|
+
logger.warnv(file.error)
|
|
172
|
+
logger.help(`? in development: add one with [echo "HELLO=World" > ${filepath}] and re-run [dotenvx run -- ${commandArgs.join(' ')}]`)
|
|
173
|
+
logger.help('? for production: set [DOTENV_KEY] on your server and re-deploy')
|
|
174
|
+
logger.help('? for ci: set [DOTENV_KEY] on your ci and re-build')
|
|
175
|
+
} else {
|
|
176
|
+
logger.warnv(file.error)
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
// debug parsed
|
|
180
|
+
const parsed = file.parsed
|
|
181
|
+
logger.debug(parsed)
|
|
182
|
+
|
|
183
|
+
// verbose/debug injected key/value
|
|
184
|
+
const injected = file.injected
|
|
185
|
+
for (const [key, value] of Object.entries(injected)) {
|
|
186
|
+
logger.verbose(`${key} set`)
|
|
187
|
+
logger.debug(`${key} set to ${value}`)
|
|
186
188
|
}
|
|
187
189
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
logger.help('? for production: set [DOTENV_KEY] on your server and re-deploy')
|
|
194
|
-
logger.help('? for ci: set [DOTENV_KEY] on your ci and re-build')
|
|
195
|
-
break
|
|
196
|
-
|
|
197
|
-
// unhandled error
|
|
198
|
-
default:
|
|
199
|
-
logger.warn(e)
|
|
200
|
-
break
|
|
190
|
+
// verbose/debug preExisted key/value
|
|
191
|
+
const preExisted = file.preExisted
|
|
192
|
+
for (const [key, value] of Object.entries(preExisted)) {
|
|
193
|
+
logger.verbose(`${key} pre-exists (protip: use --overload to override)`)
|
|
194
|
+
logger.debug(`${key} pre-exists as ${value} (protip: use --overload to override)`)
|
|
201
195
|
}
|
|
202
196
|
}
|
|
203
197
|
}
|
|
204
198
|
|
|
205
|
-
|
|
206
|
-
|
|
199
|
+
let msg = `injecting env (${uniqueInjectedKeys.length})`
|
|
200
|
+
if (readableFilepaths.length > 0) {
|
|
201
|
+
msg += ` from ${readableFilepaths}`
|
|
207
202
|
}
|
|
203
|
+
logger.successv(msg)
|
|
208
204
|
}
|
|
209
205
|
|
|
210
206
|
// Extract command and arguments after '--'
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -56,6 +56,7 @@ program.command('run')
|
|
|
56
56
|
.addHelpText('after', examples.run)
|
|
57
57
|
.option('-f, --env-file <paths...>', 'path(s) to your env file(s)', '.env')
|
|
58
58
|
.option('-fv, --env-vault-file <path>', 'path to your .env.vault file', '.env.vault')
|
|
59
|
+
.option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")')
|
|
59
60
|
.option('-o, --overload', 'override existing env variables')
|
|
60
61
|
.action(require('./actions/run'))
|
|
61
62
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
/* istanbul ignore file */
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const ignore = require('ignore')
|
|
1
4
|
const logger = require('./../../shared/logger')
|
|
5
|
+
const helpers = require('./../../cli/helpers')
|
|
2
6
|
|
|
3
7
|
const InstallPrecommitHook = require('./../helpers/installPrecommitHook')
|
|
4
8
|
|
|
@@ -14,7 +18,56 @@ class Precommit {
|
|
|
14
18
|
return true
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
// 1. check for .gitignore file
|
|
22
|
+
if (!fs.existsSync('.gitignore')) {
|
|
23
|
+
logger.errorvp('.gitignore missing')
|
|
24
|
+
logger.help2('? add it with [touch .gitignore]')
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 2. check .env* files against .gitignore file
|
|
29
|
+
let warningCount = 0
|
|
30
|
+
const ig = ignore().add(fs.readFileSync('.gitignore').toString())
|
|
31
|
+
const files = fs.readdirSync(process.cwd())
|
|
32
|
+
const dotenvFiles = files.filter(file => file.match(/^\.env(\..+)?$/))
|
|
33
|
+
dotenvFiles.forEach(file => {
|
|
34
|
+
// check if that file is being ignored
|
|
35
|
+
if (ig.ignores(file)) {
|
|
36
|
+
switch (file) {
|
|
37
|
+
case '.env.example':
|
|
38
|
+
warningCount += 1
|
|
39
|
+
logger.warnv(`${file} (currently ignored but should not be)`)
|
|
40
|
+
logger.help2(`? add !${file} to .gitignore with [echo "!${file}" >> .gitignore]`)
|
|
41
|
+
break
|
|
42
|
+
case '.env.vault':
|
|
43
|
+
warningCount += 1
|
|
44
|
+
logger.warnv(`${file} (currently ignored but should not be)`)
|
|
45
|
+
logger.help2(`? add !${file} to .gitignore with [echo "!${file}" >> .gitignore]`)
|
|
46
|
+
break
|
|
47
|
+
default:
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
switch (file) {
|
|
52
|
+
case '.env.example':
|
|
53
|
+
break
|
|
54
|
+
case '.env.vault':
|
|
55
|
+
break
|
|
56
|
+
default:
|
|
57
|
+
logger.errorvp(`${file} not properly gitignored`)
|
|
58
|
+
logger.help2(`? add ${file} to .gitignore with [echo ".env*" >> .gitignore]`)
|
|
59
|
+
process.exit(1) // 3.1 exit early with error code
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// 3. outpout success
|
|
66
|
+
if (warningCount > 0) {
|
|
67
|
+
logger.successvp(`success (with ${helpers.pluralize('warning', warningCount)})`)
|
|
68
|
+
} else {
|
|
69
|
+
logger.successvp('success')
|
|
70
|
+
}
|
|
18
71
|
}
|
|
19
72
|
|
|
20
73
|
/* istanbul ignore next */
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const dotenv = require('dotenv')
|
|
4
|
+
const dotenvExpand = require('dotenv-expand')
|
|
5
|
+
|
|
6
|
+
const ENCODING = 'utf8'
|
|
7
|
+
|
|
8
|
+
class RunDefault {
|
|
9
|
+
constructor (envFile = '.env', env = [], overload = false) {
|
|
10
|
+
this.envFile = envFile
|
|
11
|
+
this.env = env
|
|
12
|
+
this.overload = overload
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
run () {
|
|
16
|
+
const strings = []
|
|
17
|
+
const files = []
|
|
18
|
+
const readableFilepaths = new Set()
|
|
19
|
+
const uniqueInjectedKeys = new Set()
|
|
20
|
+
|
|
21
|
+
const envs = this._envs()
|
|
22
|
+
for (const env of envs) {
|
|
23
|
+
const row = {}
|
|
24
|
+
row.string = env
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const parsed = this._parseExpand(env)
|
|
28
|
+
row.parsed = parsed
|
|
29
|
+
|
|
30
|
+
const { injected, preExisted } = this._inject(process.env, parsed)
|
|
31
|
+
row.injected = injected
|
|
32
|
+
row.preExisted = preExisted
|
|
33
|
+
|
|
34
|
+
for (const key of Object.keys(injected)) {
|
|
35
|
+
uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
|
|
36
|
+
}
|
|
37
|
+
} catch (e) {
|
|
38
|
+
row.error = e
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
strings.push(row)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const envFilepaths = this._envFilepaths()
|
|
45
|
+
for (const envFilepath of envFilepaths) {
|
|
46
|
+
const row = {}
|
|
47
|
+
row.filepath = envFilepath
|
|
48
|
+
|
|
49
|
+
const filepath = path.resolve(envFilepath)
|
|
50
|
+
try {
|
|
51
|
+
const src = fs.readFileSync(filepath, { encoding: ENCODING })
|
|
52
|
+
readableFilepaths.add(envFilepath)
|
|
53
|
+
|
|
54
|
+
const parsed = this._parseExpand(src)
|
|
55
|
+
row.parsed = parsed
|
|
56
|
+
|
|
57
|
+
const { injected, preExisted } = this._inject(process.env, parsed)
|
|
58
|
+
row.injected = injected
|
|
59
|
+
row.preExisted = preExisted
|
|
60
|
+
|
|
61
|
+
for (const key of Object.keys(injected)) {
|
|
62
|
+
uniqueInjectedKeys.add(key) // track uniqueInjectedKeys across multiple files
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
if (e.code === 'ENOENT') {
|
|
66
|
+
const error = new Error(`missing ${envFilepath} file (${filepath})`)
|
|
67
|
+
error.code = 'MISSING_ENV_FILE'
|
|
68
|
+
|
|
69
|
+
row.error = error
|
|
70
|
+
} else {
|
|
71
|
+
row.error = e
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
files.push(row)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
files,
|
|
80
|
+
strings,
|
|
81
|
+
readableFilepaths: [...readableFilepaths], // array
|
|
82
|
+
uniqueInjectedKeys: [...uniqueInjectedKeys]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_envFilepaths () {
|
|
87
|
+
if (!Array.isArray(this.envFile)) {
|
|
88
|
+
return [this.envFile]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return this.envFile
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_envs () {
|
|
95
|
+
if (!Array.isArray(this.env)) {
|
|
96
|
+
return [this.env]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return this.env
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_parseExpand (src) {
|
|
103
|
+
const parsed = dotenv.parse(src)
|
|
104
|
+
|
|
105
|
+
// consider moving this logic straight into dotenv-expand
|
|
106
|
+
let inputParsed = {}
|
|
107
|
+
if (this.overload) {
|
|
108
|
+
inputParsed = { ...process.env, ...parsed }
|
|
109
|
+
} else {
|
|
110
|
+
inputParsed = { ...parsed, ...process.env }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const expandPlease = {
|
|
114
|
+
processEnv: {},
|
|
115
|
+
parsed: inputParsed
|
|
116
|
+
}
|
|
117
|
+
const expanded = dotenvExpand.expand(expandPlease).parsed
|
|
118
|
+
|
|
119
|
+
// but then 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
|
|
120
|
+
const result = {}
|
|
121
|
+
for (const key in parsed) {
|
|
122
|
+
result[key] = expanded[key]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return result
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
_inject (processEnv = {}, parsed = {}) {
|
|
129
|
+
const injected = {}
|
|
130
|
+
const preExisted = {}
|
|
131
|
+
|
|
132
|
+
// set processEnv
|
|
133
|
+
for (const key of Object.keys(parsed)) {
|
|
134
|
+
if (processEnv[key]) {
|
|
135
|
+
if (this.overload === true) {
|
|
136
|
+
processEnv[key] = parsed[key]
|
|
137
|
+
|
|
138
|
+
injected[key] = parsed[key] // track injected key/value
|
|
139
|
+
} else {
|
|
140
|
+
preExisted[key] = processEnv[key] // track preExisted key/value
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
processEnv[key] = parsed[key]
|
|
144
|
+
injected[key] = parsed[key] // track injected key/value
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
injected,
|
|
150
|
+
preExisted
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = RunDefault
|