@dotenvx/dotenvx 2.8.0 → 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,13 @@
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.8.0...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.
6
12
 
7
13
  ## [2.8.0](https://github.com/dotenvx/dotenvx/compare/v2.7.3...v2.8.0) (2026-07-14)
8
14
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.8.0",
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",
@@ -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
@@ -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
  })
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
@@ -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 = {}) {
@@ -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
  })