@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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.10.0",
2
+ "version": "2.11.1",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -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 decryptedValues = require('../../lib/helpers/decryptedValues')
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 = decryptedValues(processedEnvs)
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
@@ -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
- child = execute.execa(commandArgs[0], commandArgs.slice(1), {
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="Dotenvx"
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
@@ -173,7 +173,6 @@ export interface DotenvConfigOptions {
173
173
  | 'error'
174
174
  | 'warn'
175
175
  | 'success'
176
- | 'successv'
177
176
  | 'info'
178
177
  | 'help'
179
178
  | 'verbose'
@@ -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
 
@@ -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 successv = (m) => getColor('amber')(`⟐ ${m}`)
23
- const info = getColor('gray')
24
- const help = getColor('dodgerblue')
25
- const verbose = (m) => getColor('plum')(`┆ ${m}`)
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
- const formattedMessage = formatMessage(level, message)
34
- console.error(formattedMessage)
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) => stdout('warn', msg),
87
+ warn: (msg) => stderr('warn', msg),
89
88
  // success
90
- success: (msg) => stdout('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) => stdout('help', msg),
93
+ help: (msg) => stderr('help', msg),
96
94
  // verbose
97
- verbose: (msg) => stdout('verbose', msg),
95
+ verbose: (msg) => stderr('verbose', msg),
98
96
  // debug
99
- debug: (msg) => stdout('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