@dotenvx/dotenvx 2.10.0 → 2.11.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/CHANGELOG.md +19 -1
- package/README.md +206 -132
- package/package.json +1 -1
- package/src/cli/actions/run.js +2 -2
- package/src/cli/actions/update.js +15 -0
- package/src/cli/dotenvx.js +8 -1
- package/src/lib/helpers/colorDepth.js +7 -1
- package/src/lib/helpers/executeCommand.js +10 -2
- package/src/lib/helpers/kits/sample.js +1 -19
- package/src/lib/helpers/ptyCommand.js +27 -0
- package/src/lib/helpers/redactedValues.js +23 -0
- package/src/lib/main.d.ts +0 -1
- package/src/shared/colors.js +4 -4
- package/src/shared/logger.js +31 -35
- package/src/lib/helpers/decryptedValues.js +0 -32
package/package.json
CHANGED
package/src/cli/actions/run.js
CHANGED
|
@@ -13,7 +13,7 @@ const resolveEnvKeysFile = require('../../lib/helpers/resolveEnvKeysFile')
|
|
|
13
13
|
const mask = require('../../lib/helpers/mask')
|
|
14
14
|
const maskEnvSrc = require('../../lib/helpers/maskEnvSrc')
|
|
15
15
|
const maskProcessedEnvs = require('../../lib/helpers/maskProcessedEnvs')
|
|
16
|
-
const
|
|
16
|
+
const redactedValues = require('../../lib/helpers/redactedValues')
|
|
17
17
|
const { redactOutput } = require('../../lib/helpers/redactOutput')
|
|
18
18
|
|
|
19
19
|
const { determine } = require('./../../lib/helpers/envResolution')
|
|
@@ -127,7 +127,7 @@ async function run () {
|
|
|
127
127
|
})
|
|
128
128
|
|
|
129
129
|
if (redactEnabled) {
|
|
130
|
-
sensitiveValues =
|
|
130
|
+
sensitiveValues = redactedValues(processedEnvs)
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
if (maskEnabled) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const childProcess = require('child_process')
|
|
2
|
+
|
|
3
|
+
const INSTALL_COMMAND = 'curl -sfS https://dotenvx.sh | sh'
|
|
4
|
+
|
|
5
|
+
function update () {
|
|
6
|
+
if (process.pkg) {
|
|
7
|
+
childProcess.execSync(INSTALL_COMMAND, { stdio: 'inherit' })
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
console.log('global: npm install -g @dotenvx/dotenvx@latest')
|
|
12
|
+
console.log('local: npm install @dotenvx/dotenvx@latest --save')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = update
|
package/src/cli/dotenvx.js
CHANGED
|
@@ -64,13 +64,13 @@ program.command('run')
|
|
|
64
64
|
.option('-e, --env <strings...>', 'environment variable(s) set as string (example: "HELLO=World")', collectEnvs('env'), [])
|
|
65
65
|
.option('-f, --env-file <path>', 'path(s) to your env file(s)', collectEnvs('envFile'), [])
|
|
66
66
|
.option('-fk, --env-keys-file <path>', 'path(s) to your .env.keys file(s) (default: same path as your env file)', collectEnvKeys)
|
|
67
|
+
.option('--redact', 'redact injected values except keys ending in _PLAIN', false)
|
|
67
68
|
.option('-o, --overload', 'override existing env variables (by default, existing env vars take precedence over .env files)')
|
|
68
69
|
.option('--strict', 'process.exit(1) on any errors', false)
|
|
69
70
|
.option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
|
|
70
71
|
.option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
|
|
71
72
|
.option('--token <token>', 'set Armor ⛨ token')
|
|
72
73
|
.option('--mask [characters]', 'inject masked values, optionally setting visible characters')
|
|
73
|
-
.option('--redact', 'redact decrypted values from command output', false)
|
|
74
74
|
.option('--no-armor', 'disable Dotenvx Armor features')
|
|
75
75
|
.option('--no-native', 'disable OS secret store features')
|
|
76
76
|
.action(function (...args) {
|
|
@@ -227,6 +227,13 @@ program.command('doctor', { hidden: true })
|
|
|
227
227
|
return require('./actions/doctor').apply(this, args)
|
|
228
228
|
})
|
|
229
229
|
|
|
230
|
+
// dotenvx update
|
|
231
|
+
program.command('update')
|
|
232
|
+
.description('update dotenvx')
|
|
233
|
+
.action(function (...args) {
|
|
234
|
+
return require('./actions/update').apply(this, args)
|
|
235
|
+
})
|
|
236
|
+
|
|
230
237
|
// dotenvx login (compatibility alias for dotenvx armor login)
|
|
231
238
|
program.command('login', { hidden: true })
|
|
232
239
|
.description('log in to move keys off-device, share with your team, and audit access')
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
const { WriteStream } = require('tty')
|
|
2
2
|
|
|
3
|
-
const getColorDepth = () => {
|
|
3
|
+
const getColorDepth = (stream) => {
|
|
4
|
+
if (stream && !stream.isTTY) return 1
|
|
5
|
+
|
|
4
6
|
try {
|
|
7
|
+
if (stream && typeof stream.getColorDepth === 'function') {
|
|
8
|
+
return stream.getColorDepth()
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
return WriteStream.prototype.getColorDepth()
|
|
6
12
|
} catch (error) {
|
|
7
13
|
const term = process.env.TERM
|
|
@@ -4,6 +4,7 @@ const execute = require('./../../lib/helpers/execute')
|
|
|
4
4
|
const { logger } = require('./../../shared/logger')
|
|
5
5
|
const Errors = require('./errors')
|
|
6
6
|
const { createRedactedStreamWriter, redactOutput } = require('./redactOutput')
|
|
7
|
+
const ptyCommand = require('./ptyCommand')
|
|
7
8
|
|
|
8
9
|
async function executeCommand (commandArgs, env, sensitiveValues = []) {
|
|
9
10
|
const FORWARD_SIGNAL_GRACE_MS = 1000
|
|
@@ -133,8 +134,15 @@ async function executeCommand (commandArgs, env, sensitiveValues = []) {
|
|
|
133
134
|
|
|
134
135
|
const redactStdout = sensitiveValues.length > 0
|
|
135
136
|
const redactStderr = sensitiveValues.length > 0
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
const usePty = redactStdout && Boolean(
|
|
138
|
+
process.stdin && process.stdin.isTTY &&
|
|
139
|
+
process.stdout && process.stdout.isTTY &&
|
|
140
|
+
process.stderr && process.stderr.isTTY
|
|
141
|
+
)
|
|
142
|
+
const ptyArgs = usePty ? ptyCommand(commandArgs) : null
|
|
143
|
+
const executedArgs = ptyArgs || commandArgs
|
|
144
|
+
|
|
145
|
+
child = execute.execa(executedArgs[0], executedArgs.slice(1), {
|
|
138
146
|
stdio: ['inherit', redactStdout ? 'pipe' : 'inherit', redactStderr ? 'pipe' : 'inherit'],
|
|
139
147
|
buffer: false,
|
|
140
148
|
env: { ...process.env, ...env }
|
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
const SAMPLE_ENV_KIT = `
|
|
2
|
-
HELLO="
|
|
3
|
-
|
|
4
|
-
# ── Hosting ──────────────────────────────────────
|
|
5
|
-
AWS_ACCESS_KEY_ID="xxxx"
|
|
6
|
-
AWS_SECRET_ACCESS_KEY="xxxx"
|
|
7
|
-
DATABASE_URL="postgresql://postgres:pass@db.ref.supabase.co:5432/postgres"
|
|
8
|
-
VERCEL_TOKEN="vcp_xxxx"
|
|
9
|
-
|
|
10
|
-
# ── AI ───────────────────────────────────────────
|
|
11
|
-
OPENAI_API_KEY="sk-xxxx"
|
|
12
|
-
ANTHROPIC_API_KEY="sk-ant-xxxx"
|
|
13
|
-
|
|
14
|
-
# ── Infrastructure ───────────────────────────────
|
|
15
|
-
AUTH0_CLIENT_ID="xxxx"
|
|
16
|
-
AUTH0_CLIENT_SECRET="xxxx"
|
|
17
|
-
RESEND_API_KEY="re_xxxx"
|
|
18
|
-
STRIPE_API_KEY="sk_test_xxxx"
|
|
19
|
-
FLAGSMITH_ENV_ID="xxxx"
|
|
20
|
-
SENTRY_DSN="https://hex@o1234.ingest.us.sentry.io/1234567"
|
|
2
|
+
HELLO="World"
|
|
21
3
|
`.trimStart()
|
|
22
4
|
|
|
23
5
|
module.exports = SAMPLE_ENV_KIT
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const { which } = require('@dotenvx/tooling')
|
|
3
|
+
|
|
4
|
+
function shellQuote (value) {
|
|
5
|
+
const escapedQuote = ['\'', '"', '\'', '"', '\''].join('')
|
|
6
|
+
return `'${`${value}`.replace(/'/g, escapedQuote)}'`
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function ptyCommand (commandArgs, platform = process.platform) {
|
|
10
|
+
if (!['darwin', 'linux'].includes(platform)) return null
|
|
11
|
+
|
|
12
|
+
let script
|
|
13
|
+
try {
|
|
14
|
+
script = path.resolve(which.sync('script'))
|
|
15
|
+
} catch (e) {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (platform === 'darwin') {
|
|
20
|
+
return [script, '-q', '/dev/null', ...commandArgs]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// util-linux script only accepts the child command as a shell string.
|
|
24
|
+
return [script, '-qefc', commandArgs.map(shellQuote).join(' '), '/dev/null']
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = ptyCommand
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const isPlainKey = require('./cryptography/isPlainKey')
|
|
2
|
+
|
|
3
|
+
function redactedValues (processedEnvs) {
|
|
4
|
+
const result = new Set()
|
|
5
|
+
|
|
6
|
+
for (const processedEnv of processedEnvs || []) {
|
|
7
|
+
const values = {
|
|
8
|
+
...(processedEnv.injected || {}),
|
|
9
|
+
...(processedEnv.existed || {})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
for (const [key, value] of Object.entries(values)) {
|
|
13
|
+
if (isPlainKey(key)) continue
|
|
14
|
+
if (value === undefined || value === null || value === '') continue
|
|
15
|
+
|
|
16
|
+
result.add(`${value}`)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return [...result]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = redactedValues
|
package/src/lib/main.d.ts
CHANGED
package/src/shared/colors.js
CHANGED
|
@@ -33,8 +33,8 @@ const colorsTrueColor = new Map([
|
|
|
33
33
|
['red', [140, 35, 50]] // #8C2332 brighter garnet
|
|
34
34
|
])
|
|
35
35
|
|
|
36
|
-
function getColor (color) {
|
|
37
|
-
const colorDepth = depth.getColorDepth()
|
|
36
|
+
function getColor (color, stream) {
|
|
37
|
+
const colorDepth = depth.getColorDepth(stream)
|
|
38
38
|
if (!colors256.has(color)) {
|
|
39
39
|
throw new Errors({ color }).invalidColor()
|
|
40
40
|
}
|
|
@@ -53,8 +53,8 @@ function getColor (color) {
|
|
|
53
53
|
return (message) => message
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function bold (message) {
|
|
57
|
-
if (depth.getColorDepth() >= 4) {
|
|
56
|
+
function bold (message, stream) {
|
|
57
|
+
if (depth.getColorDepth(stream) >= 4) {
|
|
58
58
|
return `\x1b[1m${message}\x1b[22m`
|
|
59
59
|
}
|
|
60
60
|
|
package/src/shared/logger.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const packageJson = require('../lib/helpers/packageJson')
|
|
2
1
|
const Errors = require('../lib/helpers/errors')
|
|
3
2
|
const { getColor, bold } = require('./colors')
|
|
4
3
|
|
|
@@ -7,7 +6,6 @@ const levels = {
|
|
|
7
6
|
infoerror: 0,
|
|
8
7
|
warn: 1,
|
|
9
8
|
success: 2,
|
|
10
|
-
successv: 2,
|
|
11
9
|
info: 2,
|
|
12
10
|
help: 2,
|
|
13
11
|
verbose: 4,
|
|
@@ -15,23 +13,26 @@ const levels = {
|
|
|
15
13
|
silly: 6
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
const error = (m) => bold(getColor('red')(`☠ ${m}`))
|
|
19
|
-
const infoerror = getColor('gray') // actually an error
|
|
20
|
-
const warn = (m) => getColor('orangered')(`⚠ ${m}`)
|
|
21
|
-
const success = getColor('amber')
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const debug = (m) => getColor('plum')(`┆ ${m}`)
|
|
16
|
+
const error = (m, stream) => bold(getColor('red', stream)(`☠ ${m}`), stream)
|
|
17
|
+
const infoerror = (m, stream) => getColor('gray', stream)(m) // actually an error
|
|
18
|
+
const warn = (m, stream) => getColor('orangered', stream)(`⚠ ${m}`)
|
|
19
|
+
const success = (m, stream) => getColor('amber', stream)(m)
|
|
20
|
+
const info = (m, stream) => getColor('gray', stream)(m)
|
|
21
|
+
const help = (m, stream) => getColor('dodgerblue', stream)(m)
|
|
22
|
+
const verbose = (m, stream) => getColor('plum', stream)(`┆ ${m}`)
|
|
23
|
+
const debug = (m, stream) => getColor('plum', stream)(`┆ ${m}`)
|
|
27
24
|
|
|
28
25
|
let currentLevel = levels.info // default log level
|
|
29
|
-
let currentName = 'dotenvx' // default logger name
|
|
30
|
-
let currentVersion = packageJson.version // default logger version
|
|
31
26
|
|
|
32
27
|
function stderr (level, message) {
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
if (levels[level] === undefined) {
|
|
29
|
+
throw new Errors({ level }).missingLogLevel()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (levels[level] <= currentLevel) {
|
|
33
|
+
const formattedMessage = formatMessage(level, message, process.stderr)
|
|
34
|
+
console.error(formattedMessage)
|
|
35
|
+
}
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
function stdout (level, message) {
|
|
@@ -40,40 +41,38 @@ function stdout (level, message) {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
if (levels[level] <= currentLevel) {
|
|
43
|
-
const formattedMessage = formatMessage(level, message)
|
|
44
|
+
const formattedMessage = formatMessage(level, message, process.stdout)
|
|
44
45
|
console.log(formattedMessage)
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
function formatMessage (level, message) {
|
|
49
|
+
function formatMessage (level, message, stream) {
|
|
49
50
|
const formattedMessage = typeof message === 'object' ? JSON.stringify(message) : message
|
|
50
51
|
|
|
51
52
|
switch (level.toLowerCase()) {
|
|
52
53
|
// errors
|
|
53
54
|
case 'error':
|
|
54
|
-
return error(formattedMessage)
|
|
55
|
+
return error(formattedMessage, stream)
|
|
55
56
|
case 'infoerror':
|
|
56
|
-
return infoerror(formattedMessage)
|
|
57
|
+
return infoerror(formattedMessage, stream)
|
|
57
58
|
// warns
|
|
58
59
|
case 'warn':
|
|
59
|
-
return warn(formattedMessage)
|
|
60
|
+
return warn(formattedMessage, stream)
|
|
60
61
|
// successes
|
|
61
62
|
case 'success':
|
|
62
|
-
return success(formattedMessage)
|
|
63
|
-
case 'successv': // success with 'version'
|
|
64
|
-
return successv(`${formattedMessage} · ${currentName}@${currentVersion}`)
|
|
63
|
+
return success(formattedMessage, stream)
|
|
65
64
|
// info
|
|
66
65
|
case 'info':
|
|
67
|
-
return info(formattedMessage)
|
|
66
|
+
return info(formattedMessage, stream)
|
|
68
67
|
// help
|
|
69
68
|
case 'help':
|
|
70
|
-
return help(formattedMessage)
|
|
69
|
+
return help(formattedMessage, stream)
|
|
71
70
|
// verbose
|
|
72
71
|
case 'verbose':
|
|
73
|
-
return verbose(formattedMessage)
|
|
72
|
+
return verbose(formattedMessage, stream)
|
|
74
73
|
// debug
|
|
75
74
|
case 'debug':
|
|
76
|
-
return debug(formattedMessage)
|
|
75
|
+
return debug(formattedMessage, stream)
|
|
77
76
|
}
|
|
78
77
|
}
|
|
79
78
|
|
|
@@ -85,18 +84,17 @@ const logger = {
|
|
|
85
84
|
error: (msg) => stderr('error', msg),
|
|
86
85
|
infoerror: (msg) => stderr('infoerror', msg),
|
|
87
86
|
// warns
|
|
88
|
-
warn: (msg) =>
|
|
87
|
+
warn: (msg) => stderr('warn', msg),
|
|
89
88
|
// success
|
|
90
|
-
success: (msg) =>
|
|
91
|
-
successv: (msg) => stdout('successv', msg),
|
|
89
|
+
success: (msg) => stderr('success', msg),
|
|
92
90
|
// info
|
|
93
91
|
info: (msg) => stdout('info', msg),
|
|
94
92
|
// help
|
|
95
|
-
help: (msg) =>
|
|
93
|
+
help: (msg) => stderr('help', msg),
|
|
96
94
|
// verbose
|
|
97
|
-
verbose: (msg) =>
|
|
95
|
+
verbose: (msg) => stderr('verbose', msg),
|
|
98
96
|
// debug
|
|
99
|
-
debug: (msg) =>
|
|
97
|
+
debug: (msg) => stderr('debug', msg),
|
|
100
98
|
setLevel: (level) => {
|
|
101
99
|
if (levels[level] !== undefined) {
|
|
102
100
|
currentLevel = levels[level]
|
|
@@ -104,11 +102,9 @@ const logger = {
|
|
|
104
102
|
}
|
|
105
103
|
},
|
|
106
104
|
setName: (name) => {
|
|
107
|
-
currentName = name
|
|
108
105
|
logger.name = name
|
|
109
106
|
},
|
|
110
107
|
setVersion: (version) => {
|
|
111
|
-
currentVersion = version
|
|
112
108
|
logger.version = version
|
|
113
109
|
}
|
|
114
110
|
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const { encrypted, scan } = require('@dotenvx/primitives')
|
|
2
|
-
|
|
3
|
-
function decryptedValues (processedEnvs) {
|
|
4
|
-
const result = new Set()
|
|
5
|
-
|
|
6
|
-
for (const processedEnv of processedEnvs || []) {
|
|
7
|
-
const src = processedEnv.src || processedEnv.string
|
|
8
|
-
if (!src) continue
|
|
9
|
-
|
|
10
|
-
let rawParsed
|
|
11
|
-
try {
|
|
12
|
-
rawParsed = scan(src).parsed
|
|
13
|
-
} catch (error) {
|
|
14
|
-
continue
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
for (const [key, rawValues] of Object.entries(rawParsed || {})) {
|
|
18
|
-
const rawValue = rawValues[rawValues.length - 1]
|
|
19
|
-
const injectedValue = (processedEnv.injected || {})[key]
|
|
20
|
-
|
|
21
|
-
if (!encrypted(rawValue)) continue
|
|
22
|
-
if (injectedValue === undefined || injectedValue === null || injectedValue === '') continue
|
|
23
|
-
if (encrypted(injectedValue)) continue
|
|
24
|
-
|
|
25
|
-
result.add(`${injectedValue}`)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return [...result]
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
module.exports = decryptedValues
|