@dotenvx/dotenvx 1.75.0 → 1.75.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 CHANGED
@@ -2,7 +2,17 @@
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.75.0...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v1.75.1...main)
6
+
7
+ ## [1.75.1](https://github.com/dotenvx/dotenvx/compare/v1.75.0...v1.75.1) (2026-06-20)
8
+
9
+ ### Changed
10
+
11
+ * Publish as named binaries ([#851](https://github.com/dotenvx/dotenvx/pull/851))
12
+
13
+ ### Removed
14
+
15
+ * Remove references to `dotenvx-vlt` (now dotenvx-armor) ([#849](https://github.com/dotenvx/dotenvx/pull/849))
6
16
 
7
17
  ## [1.75.0](https://github.com/dotenvx/dotenvx/compare/v1.74.4...v1.75.0) (2026-06-19)
8
18
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.75.0",
2
+ "version": "1.75.1",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "funding": "https://dotenvx.com",
45
45
  "dependencies": {
46
- "@dotenvx/primitives": "^0.7.1",
46
+ "@dotenvx/primitives": "^0.8.0",
47
47
  "commander": "^11.1.0",
48
48
  "conf": "^10.2.0",
49
49
  "dotenv": "^17.2.1",
@@ -1,5 +1,5 @@
1
1
  const quotes = require('./quotes')
2
- const dotenvParse = require('./dotenvParse')
2
+ const { scan } = require('@dotenvx/primitives')
3
3
  const escapeForRegex = require('./escapeForRegex')
4
4
  const escapeDollarSigns = require('./escapeDollarSigns')
5
5
 
@@ -7,11 +7,12 @@ function append (src, key, appendValue) {
7
7
  let output
8
8
  let newPart = ''
9
9
 
10
- const parsed = dotenvParse(src, true, true) // skip expanding \n and skip converting \r\n
10
+ const { parsed } = scan(src, { expandDoubleQuotedNewlines: false, convertWindowsNewlines: false })
11
11
  const _quotes = quotes(src)
12
12
  if (Object.prototype.hasOwnProperty.call(parsed, key)) {
13
13
  const quote = _quotes[key]
14
- const originalValue = parsed[key]
14
+ const values = parsed[key]
15
+ const originalValue = values[values.length - 1]
15
16
 
16
17
  newPart += `${key}=${quote}${originalValue},${appendValue}${quote}`
17
18
 
@@ -10,8 +10,5 @@ module.exports = {
10
10
  provision: require('./provision'),
11
11
  provisionSync: require('./provisionSync'),
12
12
  provisionWithPrivateKey: require('./provisionWithPrivateKey'),
13
- mutateSrc: require('./mutateSrc'),
14
-
15
- // other
16
- isFullyEncrypted: require('./../isFullyEncrypted')
13
+ mutateSrc: require('./mutateSrc')
17
14
  }
@@ -45,7 +45,6 @@ function dynamicAttempts (command, forwardedArgs) {
45
45
  if (command === 'vlt') {
46
46
  return [
47
47
  ['dotenvx-armor', forwardedArgs],
48
- ['dotenvx-vlt', forwardedArgs],
49
48
  ['dotenvx-ops', forwardedArgs]
50
49
  ]
51
50
  }
@@ -53,8 +52,7 @@ function dynamicAttempts (command, forwardedArgs) {
53
52
  if (command === 'ops') {
54
53
  return [
55
54
  ['dotenvx-armor', forwardedArgs],
56
- ['dotenvx-ops', forwardedArgs],
57
- ['dotenvx-vlt', forwardedArgs]
55
+ ['dotenvx-ops', forwardedArgs]
58
56
  ]
59
57
  }
60
58
 
@@ -1,7 +1,7 @@
1
1
  const path = require('path')
2
+ const { scan } = require('@dotenvx/primitives')
2
3
 
3
4
  const fsx = require('./../fsx')
4
- const dotenvParse = require('./../dotenvParse')
5
5
  const keyNamesForEnvFile = require('./keyNamesForEnvFile')
6
6
  const readProcessKey = require('./readProcessKey')
7
7
  const readFileKey = require('./readFileKey')
@@ -16,7 +16,7 @@ async function invertForPrivateKeyName (filepath) {
16
16
  }
17
17
 
18
18
  const envSrc = await fsx.readFileX(filepath)
19
- const envParsed = dotenvParse(envSrc)
19
+ const { parsed: envParsed } = scan(envSrc)
20
20
 
21
21
  let publicKeyName
22
22
  for (const keyName of Object.keys(envParsed)) {
@@ -1,6 +1,6 @@
1
1
  const path = require('path')
2
+ const { scan } = require('@dotenvx/primitives')
2
3
 
3
- const dotenvParse = require('./../dotenvParse')
4
4
  const readFileKeySync = require('./readFileKeySync')
5
5
  const armorKeypairSync = require('../cryptography/armorKeypairSync')
6
6
 
@@ -23,14 +23,19 @@ function publicKeyNameFromEnvSrc (envParsed) {
23
23
  return null
24
24
  }
25
25
 
26
+ function finalValue (parsed, keyName) {
27
+ const values = parsed[keyName]
28
+ return values ? values[values.length - 1] : undefined
29
+ }
30
+
26
31
  function keyValuesFromEnvSrc (src, privateKeyName = null, opts = {}) {
27
32
  let keysFilepath = opts.keysFilepath || null
28
33
  const noArmor = opts.noArmor === true
29
34
  const processEnv = opts.processEnv || process.env
30
- const envParsed = dotenvParse(src)
35
+ const { parsed: envParsed } = scan(src)
31
36
 
32
37
  const publicKeyName = publicKeyNameFromEnvSrc(envParsed)
33
- const publicKeyValue = publicKeyName ? readProcessKey(processEnv, publicKeyName) || envParsed[publicKeyName] || null : null
38
+ const publicKeyValue = publicKeyName ? readProcessKey(processEnv, publicKeyName) || finalValue(envParsed, publicKeyName) || null : null
34
39
 
35
40
  if (!privateKeyName && publicKeyName) {
36
41
  privateKeyName = publicKeyName.replace(PUBLIC_KEY_SCHEMA, PRIVATE_KEY_SCHEMA)
@@ -1,7 +1,7 @@
1
1
  const path = require('path')
2
+ const { scan } = require('@dotenvx/primitives')
2
3
 
3
4
  const fsx = require('./../fsx')
4
- const dotenvParse = require('./../dotenvParse')
5
5
  const keyNamesForEnvFile = require('./keyNamesForEnvFile')
6
6
  const readProcessKey = require('./readProcessKey')
7
7
  const readFileKeySync = require('./readFileKeySync')
@@ -16,7 +16,7 @@ function invertForPrivateKeyName (filepath) {
16
16
  }
17
17
 
18
18
  const envSrc = fsx.readFileXSync(filepath)
19
- const envParsed = dotenvParse(envSrc)
19
+ const { parsed: envParsed } = scan(envSrc)
20
20
 
21
21
  let publicKeyName
22
22
  for (const keyName of Object.keys(envParsed)) {
@@ -1,5 +1,5 @@
1
1
  const fsx = require('./../fsx')
2
- const dotenvParse = require('./../dotenvParse')
2
+ const { scan } = require('@dotenvx/primitives')
3
3
 
4
4
  async function readFileKey (keyName, filepath) {
5
5
  if (!(await fsx.exists(filepath))) {
@@ -7,10 +7,12 @@ async function readFileKey (keyName, filepath) {
7
7
  }
8
8
 
9
9
  const src = await fsx.readFileX(filepath)
10
- const parsed = dotenvParse(src)
10
+ const { parsed } = scan(src)
11
+ const values = parsed[keyName]
11
12
 
12
- if (parsed[keyName] && parsed[keyName].length > 0) {
13
- return parsed[keyName]
13
+ if (values && values.length > 0) {
14
+ const value = values[values.length - 1]
15
+ return value || undefined
14
16
  }
15
17
  }
16
18
 
@@ -1,13 +1,15 @@
1
1
  const fsx = require('./../fsx')
2
- const dotenvParse = require('./../dotenvParse')
2
+ const { scan } = require('@dotenvx/primitives')
3
3
 
4
4
  function readFileKeySync (keyName, filepath) {
5
5
  if (fsx.existsSync(filepath)) {
6
6
  const src = fsx.readFileXSync(filepath)
7
- const parsed = dotenvParse(src)
7
+ const { parsed } = scan(src)
8
+ const values = parsed[keyName]
8
9
 
9
- if (parsed[keyName] && parsed[keyName].length > 0) {
10
- return parsed[keyName]
10
+ if (values && values.length > 0) {
11
+ const value = values[values.length - 1]
12
+ return value || undefined
11
13
  }
12
14
  }
13
15
  }
@@ -1,7 +1,7 @@
1
1
  const path = require('path')
2
2
  const fs = require('fs')
3
3
  const execa = require('execa')
4
- const dotenvParse = require('./dotenvParse')
4
+ const { scan } = require('@dotenvx/primitives')
5
5
  const canonicalEnvFilename = require('./canonicalEnvFilename')
6
6
  const environment = require('./envResolution/environment')
7
7
  const sanitizeCommandForMetadata = require('./sanitizeCommandForMetadata')
@@ -56,7 +56,7 @@ function envKeys (envFile) {
56
56
  return null
57
57
  }
58
58
 
59
- const parsed = dotenvParse(src)
59
+ const { parsed } = scan(src)
60
60
 
61
61
  if (!parsed || Object.keys(parsed).length === 0) return null
62
62
 
@@ -1,5 +1,5 @@
1
1
  const fs = require('fs')
2
- const dotenvParse = require('./dotenvParse')
2
+ const { scan } = require('@dotenvx/primitives')
3
3
  const Errors = require('./errors')
4
4
 
5
5
  function ignored (error, options) {
@@ -17,8 +17,9 @@ function readEnvKey (key, filepath, options = {}) {
17
17
  return undefined
18
18
  }
19
19
 
20
- const parsed = dotenvParse(src)
21
- const value = parsed[key]
20
+ const { parsed } = scan(src)
21
+ const values = parsed[key]
22
+ const value = values ? values[values.length - 1] : undefined
22
23
  if (value === undefined) {
23
24
  const error = new Errors({ key }).missingKey()
24
25
  if (ignored(error, options)) return undefined
@@ -1,4 +1,4 @@
1
- const dotenvParse = require('./dotenvParse')
1
+ const { scan } = require('@dotenvx/primitives')
2
2
  const escapeForRegex = require('./escapeForRegex')
3
3
 
4
4
  function replaceExistingValue (src, key, originalValue, replaceValue) {
@@ -45,7 +45,7 @@ function replace (src, key, replaceValue) {
45
45
  let output
46
46
  let newPart = ''
47
47
 
48
- const parsed = dotenvParse(src, true, true, true) // skip expanding \n and skip converting \r\n
48
+ const { parsed } = scan(src, { expandDoubleQuotedNewlines: false, convertWindowsNewlines: false })
49
49
  if (Object.prototype.hasOwnProperty.call(parsed, key)) {
50
50
  const allValues = parsed[key]
51
51
  let duplicateOutput = src
@@ -1,7 +1,7 @@
1
1
  const fsx = require('./../helpers/fsx')
2
2
  const path = require('path')
3
3
  const picomatch = require('picomatch')
4
- const { encrypted } = require('@dotenvx/primitives')
4
+ const { encrypted, scan } = require('@dotenvx/primitives')
5
5
 
6
6
  const TYPE_ENV_FILE = 'envFile'
7
7
 
@@ -21,7 +21,6 @@ const {
21
21
  } = require('./../helpers/cryptography')
22
22
 
23
23
  const replace = require('./../helpers/replace')
24
- const dotenvParse = require('./../helpers/dotenvParse')
25
24
  const detectEncoding = require('./../helpers/detectEncoding')
26
25
 
27
26
  class Decrypt {
@@ -75,7 +74,7 @@ class Decrypt {
75
74
  try {
76
75
  const encoding = await detectEncoding(filepath)
77
76
  let envSrc = await fsx.readFileX(filepath, { encoding })
78
- const envParsed = dotenvParse(envSrc, false, false, true)
77
+ const envParsed = scan(envSrc).parsed
79
78
 
80
79
  const { privateKeyName } = keyNamesForEnvFile(envFilepath)
81
80
  const { privateKeyValue, privateKeySource } = await keyValues(envFilepath, {
@@ -1,7 +1,7 @@
1
1
  const fsx = require('./../helpers/fsx')
2
2
  const path = require('path')
3
3
  const picomatch = require('picomatch')
4
- const { encrypted } = require('@dotenvx/primitives')
4
+ const { encrypted, scan } = require('@dotenvx/primitives')
5
5
 
6
6
  const TYPE_ENV_FILE = 'envFile'
7
7
 
@@ -24,7 +24,6 @@ const {
24
24
  } = require('./../helpers/cryptography')
25
25
 
26
26
  const replace = require('./../helpers/replace')
27
- const dotenvParse = require('./../helpers/dotenvParse')
28
27
  const detectEncoding = require('./../helpers/detectEncoding')
29
28
  const SAMPLE_ENV_KIT = require('./../helpers/kits/sample')
30
29
 
@@ -97,7 +96,7 @@ class Encrypt {
97
96
  row.kitCreated = 'sample'
98
97
  row.changed = true
99
98
  }
100
- const envParsed = dotenvParse(envSrc, false, false, true)
99
+ const envParsed = scan(envSrc).parsed
101
100
 
102
101
  let publicKey
103
102
  let privateKey
@@ -4,7 +4,19 @@ const path = require('path')
4
4
  const Errors = require('../helpers/errors')
5
5
  const findEnvFiles = require('../helpers/findEnvFiles')
6
6
  const replace = require('../helpers/replace')
7
- const dotenvParse = require('../helpers/dotenvParse')
7
+ const { scan } = require('@dotenvx/primitives')
8
+
9
+ function finalValues (src) {
10
+ const { parsed } = scan(src)
11
+ const result = {}
12
+
13
+ for (const key in parsed) {
14
+ const values = parsed[key]
15
+ result[key] = values[values.length - 1]
16
+ }
17
+
18
+ return result
19
+ }
8
20
 
9
21
  class Genexample {
10
22
  constructor (directory = '.', envFile) {
@@ -40,7 +52,7 @@ class Genexample {
40
52
 
41
53
  // get the original src
42
54
  let src = fsx.readFileXSync(filepath)
43
- const parsed = dotenvParse(src)
55
+ const parsed = finalValues(src)
44
56
  for (const key in parsed) {
45
57
  // used later
46
58
  keys.add(key)
@@ -65,7 +77,7 @@ class Genexample {
65
77
  // it already exists (which means the user might have it modified a way in which they prefer, so replace exampleSrc with their existing .env.example)
66
78
  exampleSrc = fsx.readFileXSync(this.exampleFilepath)
67
79
 
68
- const parsed = dotenvParse(exampleSrc)
80
+ const parsed = finalValues(exampleSrc)
69
81
  for (const key of [...keys]) {
70
82
  if (key in parsed) {
71
83
  preExisted[key] = parsed[key]
@@ -5,8 +5,8 @@ const ignore = require('ignore')
5
5
 
6
6
  const Ls = require('../services/ls')
7
7
  const Errors = require('../helpers/errors')
8
+ const { sealed } = require('@dotenvx/primitives')
8
9
 
9
- const isFullyEncrypted = require('./../helpers/isFullyEncrypted')
10
10
  const MISSING_DOCKERIGNORE = '.env.keys' // by default only ignore .env.keys. all other .env* files COULD be included - as long as they are encrypted
11
11
 
12
12
  class Prebuild {
@@ -54,7 +54,7 @@ class Prebuild {
54
54
  } else {
55
55
  if (file !== '.env.example' && file !== '.env.x') {
56
56
  const src = fsx.readFileXSync(file)
57
- const encrypted = isFullyEncrypted(src)
57
+ const encrypted = sealed(src)
58
58
 
59
59
  // if contents are encrypted don't raise an error
60
60
  if (!encrypted) {
@@ -4,8 +4,8 @@ const path = require('path')
4
4
  const ignore = require('ignore')
5
5
 
6
6
  const Ls = require('../services/ls')
7
+ const { sealed } = require('@dotenvx/primitives')
7
8
 
8
- const isFullyEncrypted = require('./../helpers/isFullyEncrypted')
9
9
  const InstallPrecommitHook = require('./../helpers/installPrecommitHook')
10
10
  const Errors = require('./../helpers/errors')
11
11
  const childProcess = require('child_process')
@@ -70,7 +70,7 @@ class Precommit {
70
70
  } else {
71
71
  if (file !== '.env.example' && file !== '.env.x') {
72
72
  const src = fsx.readFileXSync(file)
73
- const encrypted = isFullyEncrypted(src)
73
+ const encrypted = sealed(src)
74
74
 
75
75
  // if contents are encrypted don't raise an error
76
76
  if (!encrypted) {
@@ -1,7 +1,7 @@
1
1
  const fsx = require('./../helpers/fsx')
2
2
  const path = require('path')
3
3
  const picomatch = require('picomatch')
4
- const { encrypted } = require('@dotenvx/primitives')
4
+ const { encrypted, scan } = require('@dotenvx/primitives')
5
5
 
6
6
  const TYPE_ENV_FILE = 'envFile'
7
7
 
@@ -25,7 +25,6 @@ const {
25
25
 
26
26
  const append = require('./../helpers/append')
27
27
  const replace = require('./../helpers/replace')
28
- const dotenvParse = require('./../helpers/dotenvParse')
29
28
  const detectEncoding = require('./../helpers/detectEncoding')
30
29
 
31
30
  class Rotate {
@@ -81,7 +80,7 @@ class Rotate {
81
80
  try {
82
81
  const encoding = await detectEncoding(filepath)
83
82
  let envSrc = await fsx.readFileX(filepath, { encoding })
84
- const envParsed = dotenvParse(envSrc, false, false, true)
83
+ const envParsed = scan(envSrc).parsed
85
84
 
86
85
  const { publicKeyName, privateKeyName } = keyNamesForEnvFile(envFilepath)
87
86
  const { privateKeyValue } = await keyValues(envFilepath, {
@@ -1,6 +1,6 @@
1
1
  const fsx = require('./../helpers/fsx')
2
2
  const path = require('path')
3
- const { encrypted } = require('@dotenvx/primitives')
3
+ const { encrypted, scan } = require('@dotenvx/primitives')
4
4
 
5
5
  const TYPE_ENV_FILE = 'envFile'
6
6
 
@@ -25,12 +25,16 @@ const {
25
25
  } = require('./../helpers/cryptography')
26
26
 
27
27
  const replace = require('./../helpers/replace')
28
- const dotenvParse = require('./../helpers/dotenvParse')
29
28
  const detectEncoding = require('./../helpers/detectEncoding')
30
29
  const detectEncodingSync = require('./../helpers/detectEncodingSync')
31
30
 
32
31
  function allValuesForKey (envSrc, key) {
33
- return dotenvParse(envSrc, false, false, true)[key] || []
32
+ return scan(envSrc).parsed[key] || []
33
+ }
34
+
35
+ function finalValueForKey (envSrc, key) {
36
+ const values = allValuesForKey(envSrc, key)
37
+ return values.length > 0 ? values[values.length - 1] : null
34
38
  }
35
39
 
36
40
  class Sets {
@@ -119,8 +123,7 @@ class Sets {
119
123
  seededWithInitialKey = true
120
124
  }
121
125
 
122
- const envParsed = dotenvParse(envSrc)
123
- row.originalValue = envParsed[row.key] || null
126
+ row.originalValue = finalValueForKey(envSrc, row.key)
124
127
  if (seededWithInitialKey) {
125
128
  row.originalValue = null
126
129
  }
@@ -236,8 +239,7 @@ class Sets {
236
239
  seededWithInitialKey = true
237
240
  }
238
241
 
239
- const envParsed = dotenvParse(envSrc)
240
- row.originalValue = envParsed[row.key] || null
242
+ row.originalValue = finalValueForKey(envSrc, row.key)
241
243
  if (seededWithInitialKey) {
242
244
  row.originalValue = null
243
245
  }
@@ -1,55 +0,0 @@
1
- // historical dotenv.parse - https://github.com/motdotla/dotenv)
2
- const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg
3
-
4
- function dotenvParse (src, skipExpandForDoubleQuotes = false, skipConvertingWindowsNewlines = false, collectAllValues = false) {
5
- const obj = {}
6
-
7
- // Convert buffer to string
8
- let lines = src.toString()
9
-
10
- // Convert line breaks to same format
11
- if (!skipConvertingWindowsNewlines) {
12
- lines = lines.replace(/\r\n?/mg, '\n')
13
- }
14
-
15
- let match
16
- while ((match = LINE.exec(lines)) != null) {
17
- const key = match[1]
18
-
19
- // Default undefined or null to empty string
20
- let value = (match[2] || '')
21
-
22
- // Remove whitespace
23
- value = value.trim()
24
-
25
- // Check if double quoted
26
- const maybeQuote = value[0]
27
-
28
- // Remove surrounding quotes
29
- value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2')
30
-
31
- // Expand newlines if double quoted
32
- if (maybeQuote === '"' && !skipExpandForDoubleQuotes) {
33
- value = value.replace(/\\n/g, '\n') // newline
34
- value = value.replace(/\\r/g, '\r') // carriage return
35
- value = value.replace(/\\t/g, '\t') // tabs
36
- }
37
-
38
- if (collectAllValues) {
39
- // handle scenario where user mistakenly includes plaintext duplicate in .env:
40
- //
41
- // # .env
42
- // HELLO="World"
43
- // HELLO="encrypted:1234"
44
- obj[key] = obj[key] || []
45
- obj[key].push(value)
46
- } else {
47
- // Add to object
48
- obj[key] = value
49
- }
50
- }
51
-
52
- return obj
53
- }
54
-
55
- module.exports = dotenvParse
@@ -1,27 +0,0 @@
1
- const { encrypted } = require('@dotenvx/primitives')
2
- const dotenvParse = require('./dotenvParse')
3
- const isPublicKey = require('./cryptography/isPublicKey')
4
-
5
- function isFullyEncrypted (src) {
6
- const parsed = dotenvParse(src, false, false, true) // collect all values
7
-
8
- for (const [key, values] of Object.entries(parsed)) {
9
- // handle scenario where user mistakenly includes plaintext duplicate in .env:
10
- //
11
- // # .env
12
- // HELLO="World"
13
- // HELLO="encrypted:1234"
14
- //
15
- // key => [value1, ...]
16
- for (const value of values) {
17
- const result = encrypted(value) || isPublicKey(key)
18
- if (!result) {
19
- return false
20
- }
21
- }
22
- }
23
-
24
- return true
25
- }
26
-
27
- module.exports = isFullyEncrypted