@dotenvx/dotenvx 2.7.3 → 2.9.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/CHANGELOG.md CHANGED
@@ -2,7 +2,20 @@
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/v2.7.3...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.9.0...main)
6
+
7
+ ## [2.9.0](https://github.com/dotenvx/dotenvx/compare/v2.8.0...v2.9.0) (2026-07-14)
8
+
9
+ ### Changed
10
+
11
+ * BREAKING: `ls` from `@dotenvx/dotenvx` is now async/await.
12
+
13
+ ## [2.8.0](https://github.com/dotenvx/dotenvx/compare/v2.7.3...v2.8.0) (2026-07-14)
14
+
15
+ ### Added
16
+
17
+ * Add `FILE_NOT_WRITABLE` error for user convenience ([#891](https://github.com/dotenvx/dotenvx/pull/891))
18
+ * Support Enclaved Armored Keys ([#892](https://github.com/dotenvx/dotenvx/pull/892))
6
19
 
7
20
  ## [2.7.3](https://github.com/dotenvx/dotenvx/compare/v2.7.2...v2.7.3) (2026-07-13)
8
21
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.7.3",
2
+ "version": "2.9.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -17,7 +17,7 @@ function prebuild (directory) {
17
17
  } = new Prebuild(directory, options).run()
18
18
 
19
19
  for (const warning of warnings) {
20
- logger.warn(warning.messageWithHelp)
20
+ logger.warn(warning.messageWithHelp || warning.message)
21
21
  }
22
22
 
23
23
  logger.success(successMessage)
@@ -17,7 +17,7 @@ function precommit (directory) {
17
17
  } = new Precommit(directory, options).run()
18
18
 
19
19
  for (const warning of warnings) {
20
- logger.warn(warning.messageWithHelp)
20
+ logger.warn(warning.messageWithHelp || warning.message)
21
21
  }
22
22
 
23
23
  logger.success(successMessage)
@@ -53,7 +53,7 @@ async function get (key) {
53
53
  }
54
54
 
55
55
  errorCount += 1
56
- logger.error(error.messageWithHelp)
56
+ logger.error(error.messageWithHelp || error.message)
57
57
  }
58
58
 
59
59
  if (spinner) spinner.stop()
@@ -1,24 +1,76 @@
1
1
  const { objectTreeify: treeify } = require('@dotenvx/tooling')
2
+ const path = require('path')
2
3
 
3
4
  const { logger } = require('./../../shared/logger')
4
5
 
5
6
  const main = require('./../../lib/main')
6
7
  const ArrayToTree = require('./../../lib/helpers/arrayToTree')
8
+ const catchAndLog = require('../../lib/helpers/catchAndLog')
9
+ const createSpinner = require('../../lib/helpers/createSpinner')
7
10
 
8
- function ls (directory) {
11
+ async function ls (directory) {
9
12
  // debug args
10
13
  logger.debug(`directory: ${directory}`)
11
14
 
12
15
  const options = this.opts()
16
+ let spinnerOptions
17
+ if (typeof this.optsWithGlobals === 'function') {
18
+ spinnerOptions = this.optsWithGlobals()
19
+ } else {
20
+ spinnerOptions = options
21
+ }
22
+ const spinner = await createSpinner({ ...spinnerOptions, ...options, text: 'traversing' })
23
+ const startedAt = Date.now()
24
+ let directoryCount = 1
13
25
  logger.debug(`options: ${JSON.stringify(options)}`)
14
26
 
15
- const filepaths = main.ls(directory, options.envFile, options.excludeEnvFile)
16
- logger.debug(`filepaths: ${JSON.stringify(filepaths)}`)
27
+ try {
28
+ const filepaths = await main.ls(directory, options.envFile, options.excludeEnvFile, (filepath) => {
29
+ directoryCount += 1
17
30
 
18
- const tree = new ArrayToTree(filepaths).run()
19
- logger.debug(`tree: ${JSON.stringify(tree)}`)
31
+ if (spinner) {
32
+ const elapsedSeconds = Math.floor((Date.now() - startedAt) / 1000)
33
+ let directoryLabel = 'directories'
34
+ if (directoryCount === 1) {
35
+ directoryLabel = 'directory'
36
+ }
20
37
 
21
- logger.info(treeify(tree))
38
+ spinner.text = `traversing ${directoryCount.toLocaleString()} ${directoryLabel} (${elapsedSeconds}s) — ${filepath}`
39
+ }
40
+ })
41
+ logger.debug(`filepaths: ${JSON.stringify(filepaths)}`)
42
+
43
+ if (spinner) spinner.stop()
44
+
45
+ if (options.json) {
46
+ const cwd = path.resolve(directory || '.')
47
+ const absoluteFilepaths = filepaths.map(filepath => path.resolve(cwd, filepath))
48
+ console.log(JSON.stringify(absoluteFilepaths, null, 2))
49
+ } else {
50
+ const tree = new ArrayToTree(filepaths).run()
51
+ logger.debug(`tree: ${JSON.stringify(tree)}`)
52
+ logger.info(treeify(tree))
53
+ }
54
+
55
+ if (!spinnerOptions.quiet && !options.quiet) {
56
+ const elapsedSeconds = Math.floor((Date.now() - startedAt) / 1000)
57
+ const matchedDirectoryCount = new Set(filepaths.map(filepath => path.dirname(filepath))).size
58
+ let fileLabel = 'files'
59
+ if (filepaths.length === 1) {
60
+ fileLabel = 'file'
61
+ }
62
+ let directoryLabel = 'directories'
63
+ if (matchedDirectoryCount === 1) {
64
+ directoryLabel = 'directory'
65
+ }
66
+
67
+ console.error(`▣ found ${filepaths.length.toLocaleString()} .env ${fileLabel} across ${matchedDirectoryCount.toLocaleString()} ${directoryLabel} of ${directoryCount.toLocaleString()} scanned in ${elapsedSeconds}s`)
68
+ }
69
+ } catch (error) {
70
+ if (spinner) spinner.stop()
71
+ catchAndLog(error)
72
+ process.exit(1)
73
+ }
22
74
  }
23
75
 
24
76
  module.exports = ls
@@ -151,7 +151,7 @@ async function run () {
151
151
  if (error.code === 'MISSING_ENV_FILE' && options.convention) { // do not output error for conventions (too noisy)
152
152
  // intentionally quiet
153
153
  } else {
154
- logger.error(error.messageWithHelp)
154
+ logger.error(error.messageWithHelp || error.message)
155
155
  }
156
156
  }
157
157
 
@@ -24,6 +24,7 @@ ext.command('ls')
24
24
  .argument('[directory]', 'directory to list .env files from', '.')
25
25
  .option('-f, --env-file <filenames...>', 'path(s) to your env file(s)', '.env*')
26
26
  .option('-ef, --exclude-env-file <excludeFilenames...>', 'path(s) to exclude from your env file(s) (default: none)')
27
+ .option('--json', 'output a JSON array of absolute filepaths')
27
28
  .action(function (...args) {
28
29
  return require('./../actions/ls').apply(this, args)
29
30
  })
@@ -176,6 +176,7 @@ program.command('ls')
176
176
  .argument('[directory]', 'directory to list .env files from', '.')
177
177
  .option('-f, --env-file <filenames...>', 'path(s) to your env file(s)', '.env*')
178
178
  .option('-ef, --exclude-env-file <excludeFilenames...>', 'path(s) to exclude from your env file(s) (default: none)')
179
+ .option('--json', 'output a JSON array of absolute filepaths')
179
180
  .action(function (...args) {
180
181
  return require('./actions/ls').apply(this, args)
181
182
  })
@@ -0,0 +1,49 @@
1
+ const { http } = require('../helpers/http')
2
+ const buildApiError = require('../helpers/buildApiError')
3
+ const packageJson = require('../helpers/packageJson')
4
+ const normalizeToken = require('../helpers/normalizeToken')
5
+
6
+ class PostArmorDecrypt {
7
+ constructor (hostname, token, devicePublicKey, publicKey, src, grantToken) {
8
+ this.hostname = hostname || 'https://armor.dotenvx.com'
9
+ this.token = token
10
+ this.devicePublicKey = devicePublicKey
11
+ this.publicKey = publicKey
12
+ this.src = src
13
+ this.grantToken = grantToken
14
+ }
15
+
16
+ async run () {
17
+ const token = normalizeToken(this.token)
18
+ const url = `${this.hostname}/api/armor/decrypt`
19
+ const body = {
20
+ device_public_key: this.devicePublicKey,
21
+ cli_version: packageJson.version,
22
+ public_key: this.publicKey,
23
+ src: this.src
24
+ }
25
+
26
+ if (this.grantToken) {
27
+ body.grant_token = this.grantToken
28
+ }
29
+
30
+ const resp = await http(url, {
31
+ method: 'POST',
32
+ headers: {
33
+ Authorization: `Bearer ${token}`,
34
+ 'Content-Type': 'application/json'
35
+ },
36
+ body: JSON.stringify(body)
37
+ })
38
+
39
+ const json = await resp.body.json()
40
+
41
+ if (resp.statusCode >= 400) {
42
+ throw buildApiError(resp.statusCode, json)
43
+ }
44
+
45
+ return json
46
+ }
47
+ }
48
+
49
+ module.exports = PostArmorDecrypt
@@ -0,0 +1,21 @@
1
+ const Session = require('../../../db/session')
2
+ const PostArmorDecrypt = require('../../api/postArmorDecrypt')
3
+
4
+ async function index (src, options = {}) {
5
+ const sesh = new Session()
6
+
7
+ const hostname = sesh.hostname()
8
+ const token = sesh.token()
9
+ const devicePublicKey = sesh.devicePublicKey()
10
+
11
+ return await new PostArmorDecrypt(
12
+ hostname,
13
+ token,
14
+ devicePublicKey,
15
+ options.publicKey,
16
+ src,
17
+ options.grantToken
18
+ ).run()
19
+ }
20
+
21
+ module.exports = index
@@ -0,0 +1,6 @@
1
+ const { runAsWorker } = require('@dotenvx/tooling')
2
+ const decryptor = require('./armor/index')
3
+
4
+ runAsWorker(async (src, options) => {
5
+ return decryptor(src, options)
6
+ })
@@ -0,0 +1,69 @@
1
+ const Session = require('./../../db/session')
2
+
3
+ const armorDecryptor = require('./armor/index')
4
+
5
+ function syncArmorDecryptor (src, options) {
6
+ const { createSyncFn } = require('@dotenvx/tooling')
7
+ const runDecryptorSync = createSyncFn(require.resolve('./decryptor-worker.js'))
8
+ return runDecryptorSync(src, options)
9
+ }
10
+
11
+ function armorDecryptorForOptions (options) {
12
+ return (src, decryptOptions) => armorDecryptor(src, {
13
+ ...decryptOptions,
14
+ onStatus: options.onStatus,
15
+ token: options.token,
16
+ command: options.command
17
+ })
18
+ }
19
+
20
+ function syncArmorDecryptorForOptions (options) {
21
+ return (src, decryptOptions) => syncArmorDecryptor(src, {
22
+ ...decryptOptions,
23
+ onStatus: options.onStatus,
24
+ token: options.token,
25
+ command: options.command
26
+ })
27
+ }
28
+
29
+ function useArmor (options, noArmor) {
30
+ return options.noArmor !== true && options.armor !== false && !noArmor
31
+ }
32
+
33
+ async function decryptors (options = {}) {
34
+ if (Object.prototype.hasOwnProperty.call(options, 'decryptor')) {
35
+ return options.decryptor
36
+ }
37
+
38
+ if (options.noArmor === true || options.armor === false) {
39
+ return null
40
+ }
41
+
42
+ const sesh = new Session()
43
+ const noArmor = !options.token && await sesh.noArmor()
44
+ if (!useArmor(options, noArmor)) {
45
+ return null
46
+ }
47
+
48
+ return armorDecryptorForOptions(options)
49
+ }
50
+
51
+ decryptors.sync = function decryptorsSync (options = {}) {
52
+ if (Object.prototype.hasOwnProperty.call(options, 'decryptor')) {
53
+ return options.decryptor
54
+ }
55
+
56
+ if (options.noArmor === true || options.armor === false) {
57
+ return null
58
+ }
59
+
60
+ const sesh = new Session()
61
+ const noArmor = !options.token && sesh.noArmorSync()
62
+ if (!useArmor(options, noArmor)) {
63
+ return null
64
+ }
65
+
66
+ return syncArmorDecryptorForOptions(options)
67
+ }
68
+
69
+ module.exports = decryptors
@@ -1,6 +1,11 @@
1
1
  const { logger } = require('./../../shared/logger')
2
+ const Errors = require('./errors')
2
3
 
3
4
  function catchAndLog (error) {
5
+ if (error.code === 'EACCES' || error.code === 'EPERM') {
6
+ error = new Errors({ filepath: error.path }).fileNotWritable()
7
+ }
8
+
4
9
  const msg = error.messageWithHelp || error.message
5
10
  if (msg) {
6
11
  logger.error(msg)
@@ -20,6 +20,7 @@ const ISSUE_BY_CODE = {
20
20
  MISSING_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/464',
21
21
  MISSING_PUBLIC_KEY: 'https://github.com/dotenvx/dotenvx/issues/865',
22
22
  MISSING_VALUE: 'https://github.com/dotenvx/dotenvx/issues/864',
23
+ FILE_NOT_WRITABLE: 'https://github.com/dotenvx/dotenvx/issues/890',
23
24
  PRECOMMIT_HOOK_MODIFY_FAILED: 'try again or report error',
24
25
  WRONG_PRIVATE_KEY: 'https://github.com/dotenvx/dotenvx/issues/466'
25
26
  }
@@ -279,6 +280,19 @@ class Errors {
279
280
  return e
280
281
  }
281
282
 
283
+ fileNotWritable () {
284
+ const code = 'FILE_NOT_WRITABLE'
285
+ const filepath = this.filepath || 'unknown'
286
+ const message = `[${code}] cannot write to file (${filepath})`
287
+ const help = `fix: [${ISSUE_BY_CODE[code]}]`
288
+
289
+ const e = new Error(message)
290
+ e.code = code
291
+ e.help = help
292
+ e.messageWithHelp = `${message}. ${help}`
293
+ return e
294
+ }
295
+
282
296
  missingValue () {
283
297
  const code = 'MISSING_VALUE'
284
298
  const message = `[${code}] missing value (${this.key})`
@@ -0,0 +1,21 @@
1
+ const NETWORK_ERROR_CODES = new Set([
2
+ 'ECONNRESET',
3
+ 'ECONNREFUSED',
4
+ 'ENOTFOUND',
5
+ 'ENETDOWN',
6
+ 'ENETUNREACH',
7
+ 'EHOSTDOWN',
8
+ 'EHOSTUNREACH',
9
+ 'EPIPE',
10
+ 'ETIMEDOUT',
11
+ 'UND_ERR_CONNECT_TIMEOUT',
12
+ 'UND_ERR_HEADERS_TIMEOUT',
13
+ 'UND_ERR_BODY_TIMEOUT',
14
+ 'UND_ERR_SOCKET'
15
+ ])
16
+
17
+ function isNetworkError (error) {
18
+ return NETWORK_ERROR_CODES.has(error && error.code) || NETWORK_ERROR_CODES.has(error && error.cause && error.cause.code)
19
+ }
20
+
21
+ module.exports = isNetworkError
@@ -0,0 +1,66 @@
1
+ const { parse, parseSync } = require('@dotenvx/primitives')
2
+ const isNetworkError = require('./isNetworkError')
3
+
4
+ const SERVER_SIDE_DECRYPTION_REQUIRED = 'SERVER_SIDE_DECRYPTION_REQUIRED'
5
+
6
+ function decryptOptions (error) {
7
+ const meta = error.meta || {}
8
+
9
+ return {
10
+ publicKey: meta.public_key,
11
+ grantToken: meta.grant_token,
12
+ error
13
+ }
14
+ }
15
+
16
+ function parseOptionsWithoutProvider (options) {
17
+ return {
18
+ ...options,
19
+ provider: null,
20
+ decryptor: null
21
+ }
22
+ }
23
+
24
+ async function parseWithDecryptor (src, options = {}) {
25
+ try {
26
+ return await parse(src, options)
27
+ } catch (error) {
28
+ if (error.code !== SERVER_SIDE_DECRYPTION_REQUIRED || typeof options.decryptor !== 'function') {
29
+ throw error
30
+ }
31
+
32
+ try {
33
+ const result = await options.decryptor(src, decryptOptions(error))
34
+ return await parse(result.src, parseOptionsWithoutProvider(options))
35
+ } catch (decryptorError) {
36
+ if (isNetworkError(decryptorError)) {
37
+ return await parse(src, parseOptionsWithoutProvider(options))
38
+ }
39
+
40
+ throw decryptorError
41
+ }
42
+ }
43
+ }
44
+
45
+ parseWithDecryptor.sync = function parseWithDecryptorSync (src, options = {}) {
46
+ try {
47
+ return parseSync(src, options)
48
+ } catch (error) {
49
+ if (error.code !== SERVER_SIDE_DECRYPTION_REQUIRED || typeof options.decryptor !== 'function') {
50
+ throw error
51
+ }
52
+
53
+ try {
54
+ const result = options.decryptor(src, decryptOptions(error))
55
+ return parseSync(result.src, parseOptionsWithoutProvider(options))
56
+ } catch (decryptorError) {
57
+ if (isNetworkError(decryptorError)) {
58
+ return parseSync(src, parseOptionsWithoutProvider(options))
59
+ }
60
+
61
+ throw decryptorError
62
+ }
63
+ }
64
+ }
65
+
66
+ module.exports = parseWithDecryptor
package/src/lib/main.d.ts CHANGED
@@ -391,9 +391,11 @@ export function get(
391
391
  * @param directory - current working directory
392
392
  * @param envFile - glob pattern to match env files
393
393
  * @param excludeEnvFile - glob pattern to exclude env files
394
+ * @param onDirectory - called as each directory is traversed
394
395
  */
395
396
  export function ls(
396
397
  directory: string,
397
398
  envFile: string | string[],
398
- excludeEnvFile: string | string[]
399
- ): string[];
399
+ excludeEnvFile: string | string[],
400
+ onDirectory?: (directory: string) => void
401
+ ): Promise<string[]>;
package/src/lib/main.js CHANGED
@@ -115,10 +115,10 @@ const config = function (options = {}) {
115
115
 
116
116
  if (error.code === 'MISSING_ENV_FILE') {
117
117
  if (!options.convention) { // do not output error for conventions (too noisy)
118
- logger.error(error.messageWithHelp)
118
+ logger.error(error.messageWithHelp || error.message)
119
119
  }
120
120
  } else {
121
- logger.error(error.messageWithHelp)
121
+ logger.error(error.messageWithHelp || error.message)
122
122
  }
123
123
  }
124
124
 
@@ -155,7 +155,7 @@ const config = function (options = {}) {
155
155
  } catch (error) {
156
156
  if (strict) throw error // throw immediately if strict
157
157
 
158
- logger.error(error.messageWithHelp)
158
+ logger.error(error.messageWithHelp || error.message)
159
159
 
160
160
  return { parsed: {}, error }
161
161
  }
@@ -209,7 +209,7 @@ const parse = function (src, options = {}) {
209
209
  continue // ignore error
210
210
  }
211
211
 
212
- logger.error(error.messageWithHelp)
212
+ logger.error(error.messageWithHelp || error.message)
213
213
  }
214
214
 
215
215
  return parsed
@@ -347,7 +347,7 @@ const get = async function (key, options = {}) {
347
347
 
348
348
  if (options.strict) throw error // throw immediately if strict
349
349
 
350
- logger.error(error.messageWithHelp)
350
+ logger.error(error.messageWithHelp || error.message)
351
351
  }
352
352
 
353
353
  if (key) {
@@ -389,8 +389,8 @@ const get = async function (key, options = {}) {
389
389
  }
390
390
 
391
391
  /** @type {import('./main').ls} */
392
- const ls = function (directory, envFile, excludeEnvFile) {
393
- return lsResolver({ directory, envFile, excludeEnvFile })
392
+ const ls = async function (directory, envFile, excludeEnvFile, onDirectory) {
393
+ return await lsResolver({ directory, envFile, excludeEnvFile, onDirectory })
394
394
  }
395
395
 
396
396
  function resolveNoArmor (options = {}) {
@@ -1,6 +1,7 @@
1
1
  const Session = require('../../../db/session')
2
2
  const ArmorKeyring = require('../../services/armorKeyring')
3
3
  const armoredKeyDisplay = require('../../helpers/armoredKeyDisplay')
4
+ const isNetworkError = require('../../helpers/isNetworkError')
4
5
 
5
6
  async function index (publicKeyHex, options = {}) {
6
7
  const sesh = new Session()
@@ -23,7 +24,15 @@ async function index (publicKeyHex, options = {}) {
23
24
  }
24
25
  }
25
26
 
26
- return await keyring.run() // { "publicKey": "privateKey" }
27
+ try {
28
+ return await keyring.run() // { "publicKey": "privateKey" }
29
+ } catch (error) {
30
+ if (isNetworkError(error)) {
31
+ return {}
32
+ }
33
+
34
+ throw error
35
+ }
27
36
  }
28
37
 
29
38
  module.exports = index
@@ -5,8 +5,8 @@ const nativeProvider = require('./native/index')
5
5
 
6
6
  function syncArmorProvider (publicKeyHex) {
7
7
  const { createSyncFn } = require('@dotenvx/tooling')
8
- const runProviderSync = createSyncFn(require.resolve('./worker.js'))
9
- return runProviderSync(require.resolve('./armor/index'), publicKeyHex)
8
+ const runProviderSync = createSyncFn(require.resolve('./provider-worker.js'))
9
+ return runProviderSync(publicKeyHex)
10
10
  }
11
11
 
12
12
  function hasKey (keyring, publicKeyHex) {
@@ -0,0 +1,6 @@
1
+ const { runAsWorker } = require('@dotenvx/tooling')
2
+ const provider = require('./armor/index')
3
+
4
+ runAsWorker(async (publicKeyHex) => {
5
+ return provider(publicKeyHex)
6
+ })
@@ -1,6 +1,6 @@
1
1
  const fsx = require('./../helpers/fsx')
2
2
  const path = require('path')
3
- const { encrypted, parse, parseSync } = require('@dotenvx/primitives')
3
+ const { encrypted } = require('@dotenvx/primitives')
4
4
 
5
5
  const TYPE_ENV = 'env'
6
6
  const TYPE_ENV_FILE = 'envFile'
@@ -10,6 +10,8 @@ const detectEncoding = require('./../helpers/detectEncoding')
10
10
  const detectEncodingSync = require('./../helpers/detectEncodingSync')
11
11
  const keynames = require('./../conventions/keynames')
12
12
  const providers = require('./../providers')
13
+ const decryptors = require('./../decryptors')
14
+ const parseWithDecryptor = require('./../helpers/parseWithDecryptor')
13
15
 
14
16
  function unresolvedEncryptedErrors (parsed) {
15
17
  const keys = []
@@ -48,7 +50,7 @@ function inject (processEnv, parsed) {
48
50
  }
49
51
  }
50
52
 
51
- function buildParseOptions ({ processEnv, overload, envKeysFilepath, provider }) {
53
+ function buildParseOptions ({ processEnv, overload, envKeysFilepath, provider, decryptor }) {
52
54
  const options = {
53
55
  processEnv,
54
56
  overload,
@@ -61,10 +63,14 @@ function buildParseOptions ({ processEnv, overload, envKeysFilepath, provider })
61
63
  options.provider = null
62
64
  }
63
65
 
66
+ if (decryptor) {
67
+ options.decryptor = decryptor
68
+ }
69
+
64
70
  return options
65
71
  }
66
72
 
67
- async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider }) {
73
+ async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider, decryptor }) {
68
74
  const row = {}
69
75
  row.type = TYPE_ENV
70
76
  row.string = env.value
@@ -79,7 +85,8 @@ async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider
79
85
  processEnv: parseProcessEnv,
80
86
  overload,
81
87
  envKeysFilepath,
82
- provider
88
+ provider,
89
+ decryptor
83
90
  })
84
91
 
85
92
  const {
@@ -87,7 +94,7 @@ async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider
87
94
  errors,
88
95
  injected,
89
96
  existed
90
- } = await parse(env.value, parseOptions)
97
+ } = await parseWithDecryptor(env.value, parseOptions)
91
98
 
92
99
  row.parsed = parsed
93
100
  row.errors = decryptErrors(parsed, errors)
@@ -102,7 +109,7 @@ async function injectEnv ({ env, overload, processEnv, envKeysFilepath, provider
102
109
  return row
103
110
  }
104
111
 
105
- function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider }) {
112
+ function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider, decryptor }) {
106
113
  const row = {}
107
114
  row.type = TYPE_ENV
108
115
  row.string = env.value
@@ -117,7 +124,8 @@ function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider }
117
124
  processEnv: parseProcessEnv,
118
125
  overload,
119
126
  envKeysFilepath,
120
- provider
127
+ provider,
128
+ decryptor
121
129
  })
122
130
 
123
131
  const {
@@ -125,7 +133,7 @@ function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider }
125
133
  errors,
126
134
  injected,
127
135
  existed
128
- } = parseSync(env.value, parseOptions)
136
+ } = parseWithDecryptor.sync(env.value, parseOptions)
129
137
 
130
138
  row.parsed = parsed
131
139
  row.errors = decryptErrors(parsed, errors)
@@ -140,7 +148,7 @@ function injectEnvSync ({ env, overload, processEnv, envKeysFilepath, provider }
140
148
  return row
141
149
  }
142
150
 
143
- async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, provider, readableFilepaths }) {
151
+ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, readableFilepaths }) {
144
152
  const row = {}
145
153
  row.type = TYPE_ENV_FILE
146
154
  row.filepath = env.value
@@ -157,7 +165,8 @@ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, prov
157
165
  processEnv,
158
166
  overload,
159
167
  envKeysFilepath: fk,
160
- provider
168
+ provider,
169
+ decryptor
161
170
  })
162
171
 
163
172
  const {
@@ -165,7 +174,7 @@ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, prov
165
174
  errors,
166
175
  injected,
167
176
  existed
168
- } = await parse(src, parseOptions)
177
+ } = await parseWithDecryptor(src, parseOptions)
169
178
 
170
179
  row.src = src
171
180
  row.parsed = parsed
@@ -185,7 +194,7 @@ async function injectEnvFile ({ env, overload, processEnv, envKeysFilepath, prov
185
194
  return row
186
195
  }
187
196
 
188
- function injectEnvFileSync ({ env, overload, processEnv, envKeysFilepath, provider, readableFilepaths }) {
197
+ function injectEnvFileSync ({ env, overload, processEnv, envKeysFilepath, provider, decryptor, readableFilepaths }) {
189
198
  const row = {}
190
199
  row.type = TYPE_ENV_FILE
191
200
  row.filepath = env.value
@@ -202,7 +211,8 @@ function injectEnvFileSync ({ env, overload, processEnv, envKeysFilepath, provid
202
211
  processEnv,
203
212
  overload,
204
213
  envKeysFilepath: fk,
205
- provider
214
+ provider,
215
+ decryptor
206
216
  })
207
217
 
208
218
  const {
@@ -210,7 +220,7 @@ function injectEnvFileSync ({ env, overload, processEnv, envKeysFilepath, provid
210
220
  errors,
211
221
  injected,
212
222
  existed
213
- } = parseSync(src, parseOptions)
223
+ } = parseWithDecryptor.sync(src, parseOptions)
214
224
 
215
225
  row.src = src
216
226
  row.parsed = parsed
@@ -236,6 +246,7 @@ async function envs (options = {}) {
236
246
  const processEnv = options.processEnv || process.env
237
247
  const envKeysFilepath = options.envKeysFilepath || options.envKeysFile || null
238
248
  const provider = await providers(options)
249
+ const decryptor = await decryptors(options)
239
250
 
240
251
  for (const env of options.envs || []) {
241
252
  if (env.type === TYPE_ENV_FILE) {
@@ -245,6 +256,7 @@ async function envs (options = {}) {
245
256
  processEnv,
246
257
  envKeysFilepath,
247
258
  provider,
259
+ decryptor,
248
260
  readableFilepaths
249
261
  }))
250
262
  } else if (env.type === TYPE_ENV) {
@@ -253,7 +265,8 @@ async function envs (options = {}) {
253
265
  overload: options.overload,
254
266
  processEnv,
255
267
  envKeysFilepath,
256
- provider
268
+ provider,
269
+ decryptor
257
270
  }))
258
271
  }
259
272
  }
@@ -270,6 +283,7 @@ function envsSync (options = {}) {
270
283
  const processEnv = options.processEnv || process.env
271
284
  const envKeysFilepath = options.envKeysFilepath || options.envKeysFile || null
272
285
  const provider = providers.sync(options)
286
+ const decryptor = decryptors.sync(options)
273
287
 
274
288
  for (const env of options.envs || []) {
275
289
  if (env.type === TYPE_ENV_FILE) {
@@ -279,6 +293,7 @@ function envsSync (options = {}) {
279
293
  processEnv,
280
294
  envKeysFilepath,
281
295
  provider,
296
+ decryptor,
282
297
  readableFilepaths
283
298
  }))
284
299
  } else if (env.type === TYPE_ENV) {
@@ -287,7 +302,8 @@ function envsSync (options = {}) {
287
302
  overload: options.overload,
288
303
  processEnv,
289
304
  envKeysFilepath,
290
- provider
305
+ provider,
306
+ decryptor
291
307
  }))
292
308
  }
293
309
  }
@@ -2,6 +2,14 @@ const { Fdir } = require('@dotenvx/tooling')
2
2
  const path = require('path')
3
3
  const { match } = require('@dotenvx/primitives')
4
4
 
5
+ const DEFAULT_EXCLUDED_DIRECTORY_EXTENSIONS = new Set([
6
+ '.app',
7
+ '.key',
8
+ '.numbers',
9
+ '.pages',
10
+ '.photoslibrary'
11
+ ])
12
+
5
13
  function patternsFor (value) {
6
14
  if (!Array.isArray(value)) {
7
15
  return [`**/${value}`]
@@ -18,24 +26,44 @@ function excludePatternsFor (value) {
18
26
  return value.map(part => `**/${part}`)
19
27
  }
20
28
 
21
- function ls (options = {}) {
29
+ function crawler (options = {}) {
22
30
  const ignore = ['node_modules/**', '**/node_modules/**', '.git/**', '**/.git/**']
23
31
  const cwd = path.resolve(options.directory || './')
24
32
  const envFile = options.envFile || ['.env*']
25
33
  const excludeEnvFile = options.excludeEnvFile || []
26
34
  const excludePatterns = excludePatternsFor(excludeEnvFile)
27
- const excludes = excludePatterns.length > 0 ? ignore.concat(excludePatterns) : ignore
35
+ let excludes
36
+ if (excludePatterns.length > 0) {
37
+ excludes = ignore.concat(excludePatterns)
38
+ } else {
39
+ excludes = ignore
40
+ }
28
41
  const exclude = match(excludes, { dot: true })
29
42
  const include = match(patternsFor(envFile), {
30
43
  dot: true,
31
44
  ignore: excludes
32
45
  })
46
+ const onDirectory = options.onDirectory || (() => {})
33
47
 
34
48
  return new Fdir()
35
49
  .withRelativePaths()
50
+ .exclude((dirname, directory) => {
51
+ if (dirname === 'node_modules' || dirname === '.git') return true
52
+ if (DEFAULT_EXCLUDED_DIRECTORY_EXTENSIONS.has(path.extname(dirname).toLowerCase())) return true
53
+
54
+ onDirectory(path.relative(cwd, directory) || '.')
55
+ return false
56
+ })
36
57
  .filter((filepath) => !exclude(filepath) && include(filepath))
37
58
  .crawl(cwd)
38
- .sync()
59
+ }
60
+
61
+ async function ls (options = {}) {
62
+ return await crawler(options).withPromise()
63
+ }
64
+
65
+ ls.sync = function (options = {}) {
66
+ return crawler(options).sync()
39
67
  }
40
68
 
41
69
  module.exports = ls
@@ -82,7 +82,7 @@ class Prebuild {
82
82
  }
83
83
 
84
84
  _filepaths () {
85
- return ls({
85
+ return ls.sync({
86
86
  directory: this.directory,
87
87
  excludeEnvFile: this.excludeEnvFile
88
88
  })
@@ -100,7 +100,7 @@ class Precommit {
100
100
  }
101
101
 
102
102
  _filepaths () {
103
- return ls({
103
+ return ls.sync({
104
104
  directory: this.directory,
105
105
  excludeEnvFile: this.excludeEnvFile
106
106
  })
@@ -1,6 +1,6 @@
1
1
  const fsx = require('./../helpers/fsx')
2
2
  const path = require('path')
3
- const { parse, upsert } = require('@dotenvx/primitives')
3
+ const { upsert } = require('@dotenvx/primitives')
4
4
 
5
5
  const TYPE_ENV_FILE = 'envFile'
6
6
 
@@ -8,6 +8,8 @@ const Errors = require('./../helpers/errors')
8
8
  const { determine } = require('./../helpers/envResolution')
9
9
  const detectEncoding = require('./../helpers/detectEncoding')
10
10
  const providers = require('./../providers')
11
+ const decryptors = require('./../decryptors')
12
+ const parseWithDecryptor = require('./../helpers/parseWithDecryptor')
11
13
 
12
14
  function parseError (error) {
13
15
  return new Errors({
@@ -23,6 +25,7 @@ async function decrypt (options = {}) {
23
25
  const ek = options.ek
24
26
  const fk = options.fk
25
27
  const provider = await providers(options)
28
+ const decryptor = await decryptors(options)
26
29
 
27
30
  const processedEnvs = []
28
31
  const changedFilepaths = []
@@ -41,7 +44,7 @@ async function decrypt (options = {}) {
41
44
  const encoding = await detectEncoding(filepath)
42
45
  row.envSrc = await fsx.readFileX(filepath, { encoding })
43
46
 
44
- const { parsed, errors } = await parse(row.envSrc, { fk, ik, ek, array: true, provider })
47
+ const { parsed, errors } = await parseWithDecryptor(row.envSrc, { fk, ik, ek, array: true, provider, decryptor })
45
48
 
46
49
  if (errors.length > 0) {
47
50
  row.error = parseError(errors[0])
@@ -1,6 +0,0 @@
1
- const { runAsWorker } = require('@dotenvx/tooling')
2
-
3
- runAsWorker(async (providerPath, publicKeyHex) => {
4
- const provider = require(providerPath)
5
- return provider(publicKeyHex)
6
- })