@dotenvx/dotenvx 2.8.0 → 2.10.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,19 @@
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.10.0...main)
6
+
7
+ ## [2.10.0](https://github.com/dotenvx/dotenvx/compare/v2.9.0...v2.10.0) (2026-07-15)
8
+
9
+ ### Added
10
+
11
+ * Add `run --redact` to keep secrets out of command output. ([#894](https://github.com/dotenvx/dotenvx/pull/894))
12
+
13
+ ## [2.9.0](https://github.com/dotenvx/dotenvx/compare/v2.8.0...v2.9.0) (2026-07-14)
14
+
15
+ ### Changed
16
+
17
+ * BREAKING: `ls` from `@dotenvx/dotenvx` is now async/await. ([#893](https://github.com/dotenvx/dotenvx/pull/893))
6
18
 
7
19
  ## [2.8.0](https://github.com/dotenvx/dotenvx/compare/v2.7.3...v2.8.0) (2026-07-14)
8
20
 
package/README.md CHANGED
@@ -1471,6 +1471,24 @@ $ dotenvx set HELLO Dotenvx -fk .env.keys -f apps/app1/.env
1471
1471
  $ dotenvx run -fk .env.keys -f apps/app1/.env -- yourcommand
1472
1472
  ```
1473
1473
 
1474
+ </details>
1475
+ <details><summary>`run --redact`</summary><br>
1476
+
1477
+ Redact successfully decrypted values from the command's stdout and stderr. The command still receives the real values; only its output is filtered.
1478
+
1479
+ ```sh
1480
+ $ touch .env
1481
+ $ dotenvx set SECRET super-secret-value
1482
+ $ echo "console.log(process.env.SECRET)" > index.js
1483
+
1484
+ $ dotenvx run --redact --quiet -- node index.js
1485
+ [REDACTED]
1486
+ ```
1487
+
1488
+ Redaction is off by default. It applies only to values that were encrypted, successfully decrypted, and injected. Plaintext values are left unchanged. Matching is exact, so transformed or derived values are not redacted.
1489
+
1490
+ Because redaction filters stdout and stderr, interactive commands that require a TTY may behave differently.
1491
+
1474
1492
  </details>
1475
1493
  <details><summary>`run --mask`</summary><br>
1476
1494
 
@@ -2335,6 +2353,21 @@ $ dotenvx ls -ef '**/.env.prod*'
2335
2353
  └─ .env
2336
2354
  ```
2337
2355
 
2356
+ </details>
2357
+ <details><summary>`ls --json`</summary><br>
2358
+
2359
+ Print all matching `.env` files as a JSON array of absolute filepaths. Progress and summary details are written to stderr, so stdout can be safely piped to another command or file.
2360
+
2361
+ ```sh
2362
+ $ dotenvx ls --json
2363
+ [
2364
+ "/path/to/project/.env",
2365
+ "/path/to/project/apps/backend/.env"
2366
+ ]
2367
+
2368
+ $ dotenvx ls --json > dotenv-files.json
2369
+ ```
2370
+
2338
2371
  </details>
2339
2372
  <details><summary>`genexample`</summary><br>
2340
2373
 
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.8.0",
2
+ "version": "2.10.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
@@ -13,6 +13,8 @@ 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')
17
+ const { redactOutput } = require('../../lib/helpers/redactOutput')
16
18
 
17
19
  const { determine } = require('./../../lib/helpers/envResolution')
18
20
 
@@ -53,11 +55,13 @@ async function run () {
53
55
  const options = normalizeDotenvConfigConvention(normalizeDotenvConfigQuiet(this.opts()))
54
56
  const spinnerOptions = typeof this.optsWithGlobals === 'function' ? this.optsWithGlobals() : options
55
57
  const maskEnabled = options.mask !== undefined
58
+ const redactEnabled = options.redact === true
56
59
  let showChar = options.mask
57
60
  if (options.mask === true) {
58
61
  showChar = 6
59
62
  }
60
63
  let commandEnv = process.env
64
+ let sensitiveValues = []
61
65
 
62
66
  let commandArgs = this.args
63
67
  if (commandArgs.length < 1) {
@@ -122,6 +126,10 @@ async function run () {
122
126
  }
123
127
  })
124
128
 
129
+ if (redactEnabled) {
130
+ sensitiveValues = decryptedValues(processedEnvs)
131
+ }
132
+
125
133
  if (maskEnabled) {
126
134
  commandEnv = { ...process.env }
127
135
  maskProcessedEnvs(processedEnvs, commandEnv, showChar)
@@ -156,18 +164,18 @@ async function run () {
156
164
  }
157
165
 
158
166
  // debug parsed
159
- logger.debug(processedEnv.parsed)
167
+ logger.debug(redactOutput(processedEnv.parsed, sensitiveValues))
160
168
 
161
169
  // verbose/debug injected key/value
162
170
  for (const [key, value] of Object.entries(processedEnv.injected || {})) {
163
171
  logger.verbose(`${key} set`)
164
- logger.debug(`${key} set to ${value}`)
172
+ logger.debug(redactOutput(`${key} set to ${value}`, sensitiveValues))
165
173
  }
166
174
 
167
175
  // verbose/debug existed key/value
168
176
  for (const [key, value] of Object.entries(processedEnv.existed || {})) {
169
177
  logger.verbose(`${key} pre-exists (protip: use --overload to override)`)
170
- logger.debug(`${key} pre-exists as ${value} (protip: use --overload to override)`)
178
+ logger.debug(redactOutput(`${key} pre-exists as ${value} (protip: use --overload to override)`, sensitiveValues))
171
179
  }
172
180
  }
173
181
 
@@ -189,7 +197,7 @@ async function run () {
189
197
  process.exit(1)
190
198
  }
191
199
 
192
- await executeCommand(commandArgs, commandEnv)
200
+ await executeCommand(commandArgs, commandEnv, sensitiveValues)
193
201
  }
194
202
 
195
203
  module.exports = run
@@ -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
  })
@@ -70,6 +70,7 @@ program.command('run')
70
70
  .option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
71
71
  .option('--token <token>', 'set Armor ⛨ token')
72
72
  .option('--mask [characters]', 'inject masked values, optionally setting visible characters')
73
+ .option('--redact', 'redact decrypted values from command output', false)
73
74
  .option('--no-armor', 'disable Dotenvx Armor features')
74
75
  .option('--no-native', 'disable OS secret store features')
75
76
  .action(function (...args) {
@@ -176,6 +177,7 @@ program.command('ls')
176
177
  .argument('[directory]', 'directory to list .env files from', '.')
177
178
  .option('-f, --env-file <filenames...>', 'path(s) to your env file(s)', '.env*')
178
179
  .option('-ef, --exclude-env-file <excludeFilenames...>', 'path(s) to exclude from your env file(s) (default: none)')
180
+ .option('--json', 'output a JSON array of absolute filepaths')
179
181
  .action(function (...args) {
180
182
  return require('./actions/ls').apply(this, args)
181
183
  })
@@ -1,11 +1,6 @@
1
1
  const { logger } = require('./../../shared/logger')
2
- const Errors = require('./errors')
3
2
 
4
3
  function catchAndLog (error) {
5
- if (error.code === 'EACCES' || error.code === 'EPERM') {
6
- error = new Errors({ filepath: error.path }).fileNotWritable()
7
- }
8
-
9
4
  const msg = error.messageWithHelp || error.message
10
5
  if (msg) {
11
6
  logger.error(msg)
@@ -0,0 +1,32 @@
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
@@ -3,8 +3,9 @@ const { which } = require('@dotenvx/tooling')
3
3
  const execute = require('./../../lib/helpers/execute')
4
4
  const { logger } = require('./../../shared/logger')
5
5
  const Errors = require('./errors')
6
+ const { createRedactedStreamWriter, redactOutput } = require('./redactOutput')
6
7
 
7
- async function executeCommand (commandArgs, env) {
8
+ async function executeCommand (commandArgs, env, sensitiveValues = []) {
8
9
  const FORWARD_SIGNAL_GRACE_MS = 1000
9
10
  const FORCE_KILL_GRACE_MS = 1000
10
11
  const signals = [
@@ -130,11 +131,27 @@ async function executeCommand (commandArgs, env) {
130
131
  }
131
132
  }
132
133
 
134
+ const redactStdout = sensitiveValues.length > 0
135
+ const redactStderr = sensitiveValues.length > 0
136
+
133
137
  child = execute.execa(commandArgs[0], commandArgs.slice(1), {
134
- stdio: 'inherit',
138
+ stdio: ['inherit', redactStdout ? 'pipe' : 'inherit', redactStderr ? 'pipe' : 'inherit'],
139
+ buffer: false,
135
140
  env: { ...process.env, ...env }
136
141
  })
137
142
 
143
+ if (redactStdout && child.stdout) {
144
+ const stdoutWriter = createRedactedStreamWriter(process.stdout, sensitiveValues, child.stdout)
145
+ child.stdout.on('data', stdoutWriter.write)
146
+ child.stdout.once('end', stdoutWriter.flush)
147
+ }
148
+
149
+ if (redactStderr && child.stderr) {
150
+ const stderrWriter = createRedactedStreamWriter(process.stderr, sensitiveValues, child.stderr)
151
+ child.stderr.on('data', stderrWriter.write)
152
+ child.stderr.once('end', stderrWriter.flush)
153
+ }
154
+
138
155
  process.on('SIGINT', sigintHandler)
139
156
  process.on('SIGTERM', sigtermHandler)
140
157
 
@@ -157,7 +174,7 @@ async function executeCommand (commandArgs, env) {
157
174
  if (error.code === 'ENOENT') {
158
175
  logger.error(`Unknown command: ${error.command}`)
159
176
  } else {
160
- logger.error(error.message)
177
+ logger.error(redactOutput(error.message, sensitiveValues))
161
178
  }
162
179
  }
163
180
 
@@ -1,4 +1,5 @@
1
1
  const fs = require('fs')
2
+ const Errors = require('./errors')
2
3
 
3
4
  const ENCODING = 'utf8'
4
5
 
@@ -19,11 +20,27 @@ function readFileXSync (filepath, encoding = null) {
19
20
  }
20
21
 
21
22
  function writeFileXSync (filepath, str) {
22
- return fs.writeFileSync(filepath, str, ENCODING) // utf8 always
23
+ try {
24
+ return fs.writeFileSync(filepath, str, ENCODING) // utf8 always
25
+ } catch (error) {
26
+ if (error.code === 'EACCES' || error.code === 'EPERM') {
27
+ throw new Errors({ filepath }).fileNotWritable()
28
+ }
29
+
30
+ throw error
31
+ }
23
32
  }
24
33
 
25
34
  async function writeFileX (filepath, str) {
26
- return fs.promises.writeFile(filepath, str, ENCODING)
35
+ try {
36
+ return await fs.promises.writeFile(filepath, str, ENCODING)
37
+ } catch (error) {
38
+ if (error.code === 'EACCES' || error.code === 'EPERM') {
39
+ throw new Errors({ filepath }).fileNotWritable()
40
+ }
41
+
42
+ throw error
43
+ }
27
44
  }
28
45
 
29
46
  async function exists (filepath) {
@@ -0,0 +1,9 @@
1
+ function redact (str) {
2
+ if (!str || str.length < 1) {
3
+ return ''
4
+ }
5
+
6
+ return '[REDACTED]'
7
+ }
8
+
9
+ module.exports = redact
@@ -0,0 +1,120 @@
1
+ const redact = require('./redact')
2
+ const { StringDecoder } = require('string_decoder')
3
+
4
+ function normalizedValues (values) {
5
+ return [...new Set((values || [])
6
+ .filter(value => value !== undefined && value !== null && `${value}`.length > 0)
7
+ .map(value => `${value}`))]
8
+ .sort((a, b) => b.length - a.length)
9
+ }
10
+
11
+ function redactOutput (value, sensitiveValues) {
12
+ const values = normalizedValues(sensitiveValues)
13
+ if (values.length < 1 || value === undefined || value === null) return value
14
+
15
+ if (Array.isArray(value)) {
16
+ return value.map(item => redactOutput(item, values))
17
+ }
18
+
19
+ if (typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
20
+ const result = {}
21
+ for (const [key, item] of Object.entries(value)) {
22
+ result[key] = redactOutput(item, values)
23
+ }
24
+ return result
25
+ }
26
+
27
+ if (typeof value !== 'string') return value
28
+
29
+ let result = value
30
+ for (const sensitiveValue of values) {
31
+ result = result.split(sensitiveValue).join(redact(sensitiveValue))
32
+ }
33
+ return result
34
+ }
35
+
36
+ function partialMatchLength (value, sensitiveValues) {
37
+ let longest = 0
38
+
39
+ for (const sensitiveValue of sensitiveValues) {
40
+ const maxLength = Math.min(value.length, sensitiveValue.length - 1)
41
+ for (let length = maxLength; length > longest; length--) {
42
+ if (sensitiveValue.startsWith(value.slice(-length))) {
43
+ longest = length
44
+ break
45
+ }
46
+ }
47
+ }
48
+
49
+ return longest
50
+ }
51
+
52
+ function safeBoundary (value, boundary, sensitiveValues) {
53
+ let result = boundary
54
+ let changed = true
55
+
56
+ while (changed) {
57
+ changed = false
58
+ for (const sensitiveValue of sensitiveValues) {
59
+ let index = value.indexOf(sensitiveValue)
60
+ while (index !== -1) {
61
+ const end = index + sensitiveValue.length
62
+ if (index < result && end > result) {
63
+ result = index
64
+ changed = true
65
+ }
66
+ index = value.indexOf(sensitiveValue, index + 1)
67
+ }
68
+ }
69
+ }
70
+
71
+ return result
72
+ }
73
+
74
+ function createRedactedStreamWriter (stream, sensitiveValues, source) {
75
+ const values = normalizedValues(sensitiveValues)
76
+ const decoder = new StringDecoder('utf8')
77
+ let pending = ''
78
+ let waitingForDrain = false
79
+
80
+ const writeToStream = (value) => {
81
+ if (!value) return
82
+
83
+ const canContinue = stream.write(redactOutput(value, values))
84
+ if (!canContinue && source && !waitingForDrain) {
85
+ waitingForDrain = true
86
+ source.pause()
87
+ stream.once('drain', () => {
88
+ waitingForDrain = false
89
+ source.resume()
90
+ })
91
+ }
92
+ }
93
+
94
+ const flush = () => {
95
+ pending += decoder.end()
96
+ if (!pending) return
97
+ writeToStream(pending)
98
+ pending = ''
99
+ }
100
+
101
+ const write = (chunk) => {
102
+ pending += Buffer.isBuffer(chunk) ? decoder.write(chunk) : `${chunk}`
103
+
104
+ const holdbackLength = partialMatchLength(pending, values)
105
+ let boundary = pending.length - holdbackLength
106
+ boundary = safeBoundary(pending, boundary, values)
107
+
108
+ const output = pending.slice(0, boundary)
109
+ pending = pending.slice(boundary)
110
+
111
+ writeToStream(output)
112
+ }
113
+
114
+ return { write, flush }
115
+ }
116
+
117
+ module.exports = {
118
+ redactOutput,
119
+ createRedactedStreamWriter
120
+ }
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
  })