├── .github ├── dependabot.yml ├── funding.yml └── workflows │ ├── release.yml │ └── tests.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── declaration.tsconfig.json ├── fixtures ├── another-folder │ └── test.json ├── ignore-folder │ ├── sub-folder-file.json │ └── sub-sub-folder │ │ └── sub-sub-folder-file.json ├── sub-folder │ ├── sub-folder-file.json │ └── sub-sub-folder │ │ └── sub-sub-folder-file.json └── test.json ├── index.js ├── package.json ├── test.js └── tsconfig.json /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Basic dependabot.yml file with 2 | # minimum configuration for two package managers 3 | 4 | version: 2 5 | updates: 6 | # Enable version updates for npm 7 | - package-ecosystem: "npm" 8 | # Look for `package.json` and `lock` files in the `root` directory 9 | directory: "/" 10 | # Check the npm registry for updates every day (weekdays) 11 | schedule: 12 | interval: "daily" 13 | # Enable updates to github actions 14 | - package-ecosystem: "github-actions" 15 | directory: "/" 16 | schedule: 17 | interval: "daily" 18 | 19 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ['bcomnes'] 4 | custom: ['https://bret.io'] 5 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: npm bump 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | newversion: 7 | description: 'npm version {major,minor,patch}' 8 | required: true 9 | 10 | env: 11 | node_version: 'lts/*' 12 | FORCE_COLOR: 2 13 | 14 | jobs: 15 | version_and_release: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | # fetch full history so things like auto-changelog work properly 21 | fetch-depth: 0 22 | - name: Use Node.js ${{ env.node_version }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ env.node_version }} 26 | # setting a registry enables the NODE_AUTH_TOKEN env variable where we can set an npm token. REQUIRED 27 | registry-url: 'https://registry.npmjs.org' 28 | - run: npm i 29 | - run: npm test 30 | - name: npm version && npm publish 31 | uses: bcomnes/npm-bump@v2 32 | with: 33 | git_email: bcomnes@gmail.com 34 | git_username: ${{ github.actor }} 35 | newversion: ${{ github.event.inputs.newversion }} 36 | github_token: ${{ secrets.GITHUB_TOKEN }} # built in actions token. Passed tp gh-release if in use. 37 | npm_token: ${{ secrets.NPM_TOKEN }} # user set secret token generated at npm 38 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [pull_request, push] 4 | 5 | env: 6 | FORCE_COLOR: 2 7 | 8 | jobs: 9 | test: 10 | runs-on: ${{ matrix.os }} 11 | 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest] 15 | node: ['lts/*'] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - run: npm i 24 | - run: npm test 25 | 26 | automerge: 27 | needs: test 28 | runs-on: ubuntu-latest 29 | permissions: 30 | pull-requests: write 31 | contents: write 32 | steps: 33 | - uses: fastify/github-action-merge-dependabot@v3 34 | if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'pull_request' && contains(github.head_ref, 'dependabot/github_actions') }} 35 | with: 36 | github-token: ${{secrets.github_token}} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | sandbox.js 3 | .nyc_output 4 | .tap 5 | .nova 6 | 7 | # Generated types 8 | *.d.ts 9 | *.d.ts.map 10 | 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | sandbox.js 3 | .nyc_output 4 | .tap 5 | .nova 6 | 7 | 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). 9 | 10 | ## [v3.0.5](https://github.com/bcomnes/async-folder-walker/compare/v3.0.4...v3.0.5) 11 | 12 | ### Commits 13 | 14 | - Adjust npmignore [`c3711c1`](https://github.com/bcomnes/async-folder-walker/commit/c3711c11f8e9f24310ffcb72885682ec108825ce) 15 | 16 | ## [v3.0.4](https://github.com/bcomnes/async-folder-walker/compare/v3.0.3...v3.0.4) - 2023-11-23 17 | 18 | ### Commits 19 | 20 | - Fix publish lifecycle [`d9b570f`](https://github.com/bcomnes/async-folder-walker/commit/d9b570f4f9ebae87615759d2a173ef3916816337) 21 | 22 | ## [v3.0.3](https://github.com/bcomnes/async-folder-walker/compare/v3.0.2...v3.0.3) - 2023-11-23 23 | 24 | ### Commits 25 | 26 | - Republish without additional assets [`0aaa28d`](https://github.com/bcomnes/async-folder-walker/commit/0aaa28d1e72bee0bfd79d244a3f7e7f737eba954) 27 | 28 | ## [v3.0.2](https://github.com/bcomnes/async-folder-walker/compare/v3.0.1...v3.0.2) - 2023-11-23 29 | 30 | ### Merged 31 | 32 | - Bump actions/setup-node from 3 to 4 [`#61`](https://github.com/bcomnes/async-folder-walker/pull/61) 33 | 34 | ### Commits 35 | 36 | - Add a return type helper [`f6a9651`](https://github.com/bcomnes/async-folder-walker/commit/f6a965142b5037e9c5f5351704371093cf671c92) 37 | - Tooling tweaks [`83e620a`](https://github.com/bcomnes/async-folder-walker/commit/83e620aa6897c937cc9a7342f65172736cc1b398) 38 | - Include full repo in published module [`5c9fd20`](https://github.com/bcomnes/async-folder-walker/commit/5c9fd208ea3e22b63d81ddd5777f72142aab8f1a) 39 | 40 | ## [v3.0.1](https://github.com/bcomnes/async-folder-walker/compare/v3.0.0...v3.0.1) - 2023-10-19 41 | 42 | ### Commits 43 | 44 | - Fix type building lifecycle [`6a92f89`](https://github.com/bcomnes/async-folder-walker/commit/6a92f8928799ee125ecba02e75cd628d550338fd) 45 | 46 | ## [v3.0.0](https://github.com/bcomnes/async-folder-walker/compare/v2.2.1...v3.0.0) - 2023-10-19 47 | 48 | ### Merged 49 | 50 | - Bump tap from 16.3.9 to 18.4.0 [`#60`](https://github.com/bcomnes/async-folder-walker/pull/60) 51 | - Bump actions/checkout from 3 to 4 [`#54`](https://github.com/bcomnes/async-folder-walker/pull/54) 52 | - Bump p-temporary-directory from 1.1.1 to 2.0.1 [`#52`](https://github.com/bcomnes/async-folder-walker/pull/52) 53 | - Bump bcomnes/npm-bump from 2.0.2 to 2.1.0 [`#42`](https://github.com/bcomnes/async-folder-walker/pull/42) 54 | - Bump npm-run-all2 from 5.0.2 to 6.0.0 [`#41`](https://github.com/bcomnes/async-folder-walker/pull/41) 55 | - Bump standard from 16.0.4 to 17.0.0 [`#39`](https://github.com/bcomnes/async-folder-walker/pull/39) 56 | - Bump @voxpelli/tsconfig from 2.1.0 to 4.0.0 [`#40`](https://github.com/bcomnes/async-folder-walker/pull/40) 57 | - Bump actions/setup-node from 3.1.0 to 3.1.1 [`#38`](https://github.com/bcomnes/async-folder-walker/pull/38) 58 | - Bump fastify/github-action-merge-dependabot from 3.0.2 to 3.1 [`#37`](https://github.com/bcomnes/async-folder-walker/pull/37) 59 | - Bump actions/setup-node from 3.0.0 to 3.1.0 [`#36`](https://github.com/bcomnes/async-folder-walker/pull/36) 60 | 61 | ### Commits 62 | 63 | - **Breaking change:** Fix types [`9400369`](https://github.com/bcomnes/async-folder-walker/commit/9400369fcec19fd0585e69eab68e75dea90363df) 64 | - Merge pull request #45 from bcomnes/dependabot/npm_and_yarn/gh-release-7.0.0 [`740f315`](https://github.com/bcomnes/async-folder-walker/commit/740f31596753de649c9219ffc3e89dc81a619778) 65 | - Bump gh-release from 6.0.4 to 7.0.0 [`142b738`](https://github.com/bcomnes/async-folder-walker/commit/142b738c6c524b243e28c71705e2da2a7cc2600c) 66 | 67 | ## [v2.2.1](https://github.com/bcomnes/async-folder-walker/compare/v2.2.0...v2.2.1) - 2022-03-09 68 | 69 | ### Commits 70 | 71 | - Add ignore type [`5f737bc`](https://github.com/bcomnes/async-folder-walker/commit/5f737bcdf2e3960cb5f7f12c666a13f3d2cae0d0) 72 | 73 | ## [v2.2.0](https://github.com/bcomnes/async-folder-walker/compare/v2.1.1...v2.2.0) - 2022-03-09 74 | 75 | ### Merged 76 | 77 | - Bump tap from 15.2.3 to 16.0.0 [`#33`](https://github.com/bcomnes/async-folder-walker/pull/33) 78 | - Bump actions/checkout from 2.4.0 to 3 [`#32`](https://github.com/bcomnes/async-folder-walker/pull/32) 79 | - Bump actions/setup-node from 2.5.1 to 3.0.0 [`#31`](https://github.com/bcomnes/async-folder-walker/pull/31) 80 | - Bump fastify/github-action-merge-dependabot from 2.7.1 to 3.0.2 [`#29`](https://github.com/bcomnes/async-folder-walker/pull/29) 81 | - Bump actions/setup-node from 2.5.0 to 2.5.1 [`#30`](https://github.com/bcomnes/async-folder-walker/pull/30) 82 | - Bump fastify/github-action-merge-dependabot from 2.7.0 to 2.7.1 [`#28`](https://github.com/bcomnes/async-folder-walker/pull/28) 83 | - Bump fastify/github-action-merge-dependabot from 2.6.0 to 2.7.0 [`#27`](https://github.com/bcomnes/async-folder-walker/pull/27) 84 | - Bump actions/setup-node from 2.4.1 to 2.5.0 [`#26`](https://github.com/bcomnes/async-folder-walker/pull/26) 85 | - Bump fastify/github-action-merge-dependabot from 2.5.0 to 2.6.0 [`#25`](https://github.com/bcomnes/async-folder-walker/pull/25) 86 | - Bump actions/checkout from 2.3.5 to 2.4.0 [`#24`](https://github.com/bcomnes/async-folder-walker/pull/24) 87 | - Bump actions/checkout from 2.3.4 to 2.3.5 [`#23`](https://github.com/bcomnes/async-folder-walker/pull/23) 88 | - Bump actions/setup-node from 2.4.0 to 2.4.1 [`#22`](https://github.com/bcomnes/async-folder-walker/pull/22) 89 | - Bump fastify/github-action-merge-dependabot from 2.4.0 to 2.5.0 [`#21`](https://github.com/bcomnes/async-folder-walker/pull/21) 90 | 91 | ### Commits 92 | 93 | - Try publishing the types [`b80e4c9`](https://github.com/bcomnes/async-folder-walker/commit/b80e4c9c5378fd4cfb97fa4e4abdbe5eeabbe575) 94 | - Update tests.yml [`f827f0b`](https://github.com/bcomnes/async-folder-walker/commit/f827f0ba0a6d6eba395a32238d6f14f62f9f4117) 95 | 96 | ## [v2.1.1](https://github.com/bcomnes/async-folder-walker/compare/v2.0.1...v2.1.1) - 2021-09-14 97 | 98 | ### Merged 99 | 100 | - Add gitignore style ignore strings [`#20`](https://github.com/bcomnes/async-folder-walker/pull/20) 101 | - Bump fastify/github-action-merge-dependabot from 2.3.0 to 2.4.0 [`#19`](https://github.com/bcomnes/async-folder-walker/pull/19) 102 | - Bump fastify/github-action-merge-dependabot from 2.2.0 to 2.3.0 [`#18`](https://github.com/bcomnes/async-folder-walker/pull/18) 103 | - Bump actions/setup-node from 2.3.2 to 2.4.0 [`#17`](https://github.com/bcomnes/async-folder-walker/pull/17) 104 | - Bump actions/setup-node from 2.3.1 to 2.3.2 [`#16`](https://github.com/bcomnes/async-folder-walker/pull/16) 105 | - Bump actions/setup-node from 2.3.0 to 2.3.1 [`#15`](https://github.com/bcomnes/async-folder-walker/pull/15) 106 | - Bump actions/setup-node from 2.2.0 to 2.3.0 [`#14`](https://github.com/bcomnes/async-folder-walker/pull/14) 107 | - Bump fastify/github-action-merge-dependabot from 2.1.1 to 2.2.0 [`#13`](https://github.com/bcomnes/async-folder-walker/pull/13) 108 | - Bump actions/setup-node from 2.1.5 to 2.2.0 [`#12`](https://github.com/bcomnes/async-folder-walker/pull/12) 109 | - Bump fastify/github-action-merge-dependabot from 2.1.0 to 2.1.1 [`#11`](https://github.com/bcomnes/async-folder-walker/pull/11) 110 | - Bump fastify/github-action-merge-dependabot from 2.0.0 to 2.1.0 [`#10`](https://github.com/bcomnes/async-folder-walker/pull/10) 111 | - Bump tap from 14.11.0 to 15.0.1 [`#9`](https://github.com/bcomnes/async-folder-walker/pull/9) 112 | - Bump fastify/github-action-merge-dependabot from v1.2.1 to v2.0.0 [`#7`](https://github.com/bcomnes/async-folder-walker/pull/7) 113 | - Bump fastify/github-action-merge-dependabot from v1.2.0 to v1.2.1 [`#6`](https://github.com/bcomnes/async-folder-walker/pull/6) 114 | - Bump fastify/github-action-merge-dependabot from v1.1.1 to v1.2.0 [`#5`](https://github.com/bcomnes/async-folder-walker/pull/5) 115 | - Bump actions/setup-node from v2.1.4 to v2.1.5 [`#4`](https://github.com/bcomnes/async-folder-walker/pull/4) 116 | - Bump standard from 14.3.4 to 16.0.3 [`#3`](https://github.com/bcomnes/async-folder-walker/pull/3) 117 | 118 | ### Commits 119 | 120 | - Enable automation [`d4bc195`](https://github.com/bcomnes/async-folder-walker/commit/d4bc1951d38becf2671ccc6b921b1ed61259936e) 121 | - Add support for gitignore style ignore strings and arrays [`079426f`](https://github.com/bcomnes/async-folder-walker/commit/079426fc5024b310af9aa03b17d2069445cc6fa0) 122 | - Fix publish flow [`8a10a9c`](https://github.com/bcomnes/async-folder-walker/commit/8a10a9c90512f913d134af3d6984467a59f1896f) 123 | 124 | ## [v2.0.1](https://github.com/bcomnes/async-folder-walker/compare/v2.0.0...v2.0.1) - 2019-12-27 125 | 126 | ### Merged 127 | 128 | - Switch to tap and standard and update tess [`#2`](https://github.com/bcomnes/async-folder-walker/pull/2) 129 | 130 | ### Commits 131 | 132 | - Switch to tap and standard [`fcc3ab1`](https://github.com/bcomnes/async-folder-walker/commit/fcc3ab1d0fad3b3fd3baf5ba31fa242727e86afe) 133 | - A few docs and test tweaks [`85d434a`](https://github.com/bcomnes/async-folder-walker/commit/85d434aa99f5e84b4336781427bfc5f98314170f) 134 | - Update with windows example [`4d341a0`](https://github.com/bcomnes/async-folder-walker/commit/4d341a01e60a995aba16aae3dcc3ff3c149c67da) 135 | 136 | ## [v2.0.0](https://github.com/bcomnes/async-folder-walker/compare/v1.0.0...v2.0.0) - 2019-12-26 137 | 138 | ### Commits 139 | 140 | - Don't use ESM. Not worth the trouble in node. [`7b47d9c`](https://github.com/bcomnes/async-folder-walker/commit/7b47d9c07f90640e6df5dc886090b0ac604d181c) 141 | - changelog [`ab0bd32`](https://github.com/bcomnes/async-folder-walker/commit/ab0bd325dd0bf76454971bf4a6736b0c1b774c1b) 142 | 143 | ## [v1.0.0](https://github.com/bcomnes/async-folder-walker/compare/v0.0.1...v1.0.0) - 2019-11-22 144 | 145 | ### Commits 146 | 147 | - Implement readme, tsdoc and rest of tests [`c619629`](https://github.com/bcomnes/async-folder-walker/commit/c619629489fa5a11eb3fa0974ec2034d88a875f9) 148 | - Changelog [`af238d5`](https://github.com/bcomnes/async-folder-walker/commit/af238d5ef8b6c750915920be88dbe0799d0e7ae0) 149 | 150 | ## v0.0.1 - 2019-11-21 151 | 152 | ### Commits 153 | 154 | - Initial commit [`a9e2114`](https://github.com/bcomnes/async-folder-walker/commit/a9e21146d39faa1f3ae989bf3fdc3a903c81191e) 155 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | - Contributors reserve the right to walk away from this project at any moment with or without notice. 4 | - Questions are welcome, however unless there is a official support contract established between the maintainers and the requester, support is not guaranteed. 5 | - Patches, ideas and changes welcome. 6 | - Fixes almost always welcome. 7 | - Features sometimes welcome. Please open an issue to discuss the issue prior to spending lots of time on the problem. It may be rejected. If you don't want to wait around for the discussion to commence, and you really want to jump into the implementation work, be prepared for fork if the idea is respectfully declined. 8 | - Try to stay within the style of the existing code. 9 | - All tests must pass. 10 | - Additional features or code paths must be tested. 11 | - Aim for 100% coverage. 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Bret Comnes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-folder-walker 2 | [![Actions Status](https://github.com/bcomnes/async-folder-walker/workflows/tests/badge.svg)](https://github.com/bcomnes/async-folder-walker/actions) 3 | 4 | A recursive async iterator of the files and directories in a given folder. Can take multiple folders, limit walk depth and filter based on path names and stat results. 5 | 6 | ![](https://repository-images.githubusercontent.com/223294839/43cf9600-0d3f-11ea-858e-81b08a14509f) 7 | 8 | ## Installation 9 | 10 | ``` 11 | npm install async-folder-walker 12 | ``` 13 | 14 | ## Usage 15 | 16 | ``` js 17 | const { asyncFolderWalker, allFiles } = require('async-folder-walker') 18 | 19 | async function iterateFiles () { 20 | const walker = asyncFolderWalker(['.git', 'node_modules']) 21 | for await (const file of walker) { 22 | console.log(file) // logs the file path! 23 | } 24 | } 25 | 26 | async function getAllFiles () { 27 | const allFilepaths = await allFiles(['.git', 'node_modules']) 28 | console.log(allFilepaths) 29 | } 30 | 31 | iterateFiles().then(() => getAllFiles()) 32 | ``` 33 | 34 | ## API 35 | 36 | ### `const { asyncFolderWalker, allFiles } = require('async-folder-walker')` 37 | 38 | Import `asyncFolderWalker` or `allFiles`. 39 | 40 | ### `async-gen = asyncFolderWalker(paths, [opts])` 41 | 42 | Return an async generator that will iterate over all of files inside of a directory. `paths` can be a string path or an Array of string paths. 43 | 44 | You can iterate over each file and directory individually using a `for-await...of` loop. Note, you must be inside an [async function statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). 45 | 46 | ```js 47 | const { asyncFolderWalker } = require('async-folder-walker') 48 | async function iterateFiles () { 49 | const walker = asyncFolderWalker(['.git', 'node_modules']) 50 | for await (const file of walker) { 51 | console.log(file) // logs the file path! 52 | } 53 | } 54 | 55 | iterateFiles() 56 | ``` 57 | 58 | Opts include: 59 | 60 | ```js 61 | { 62 | fs: require('fs'), 63 | pathFilter: filepath => true, 64 | statFilter st => true, 65 | ignore: [], 66 | maxDepth: Infinity, 67 | shaper: ({ root, filepath, stat, relname, basename }) => filepath 68 | } 69 | ``` 70 | 71 | The `pathFilter` function allows you to filter files from additional async stat operations. Return false to filter the file. 72 | 73 | ```js 74 | { // exclude node_modules 75 | pathFilter: filepath => !filepath.includes(node_modules) 76 | } 77 | ``` 78 | 79 | The `statFilter` function allows you to filter files based on the internal stat operation. Return false to filter the file. 80 | 81 | ```js 82 | { // exclude all directories: 83 | statFilter: st => !st.isDirectory() 84 | } 85 | ``` 86 | 87 | The `ignore` option can be a string or or array of strings that should follow gitignore style ignore strings. Files and directories that match are ignored. 88 | 89 | ```js 90 | { // Ignore node_modules at any depth and any file starting with a . 91 | ['node_modules', '.*'] 92 | } 93 | ``` 94 | 95 | The `shaper` function lets you change the shape of the returned value based on data accumulaed during the iteration. To return the same shape as [okdistribute/folder-walker](https://github.com/okdistribute/folder-walker) use the following function: 96 | 97 | ```js 98 | { // Return the same shape as folder-walker 99 | shaper: fwData => fwData 100 | } 101 | ```` 102 | 103 | Example of a fwData object for a directory: 104 | 105 | ```js 106 | { 107 | root: '/Users/bret/repos/async-folder-walker/fixtures', 108 | filepath: '/Users/bret/repos/async-folder-walker/fixtures/sub-folder/sub-sub-folder', 109 | stat: Stats { 110 | dev: 16777220, 111 | mode: 16877, 112 | nlink: 3, 113 | uid: 501, 114 | gid: 20, 115 | rdev: 0, 116 | blksize: 4096, 117 | ino: 30244023, 118 | size: 96, 119 | blocks: 0, 120 | atimeMs: 1574381262779.8396, 121 | mtimeMs: 1574380914743.5474, 122 | ctimeMs: 1574380914743.5474, 123 | birthtimeMs: 1574380905232.5996, 124 | atime: 2019-11-22T00:07:42.780Z, 125 | mtime: 2019-11-22T00:01:54.744Z, 126 | ctime: 2019-11-22T00:01:54.744Z, 127 | birthtime: 2019-11-22T00:01:45.233Z 128 | }, 129 | relname: 'sub-folder/sub-sub-folder', 130 | basename: 'sub-sub-folder' 131 | } 132 | ``` 133 | 134 | and another example for a file on windows: 135 | 136 | ```js 137 | { 138 | root: 'D:\\a\\async-folder-walker\\async-folder-walker\\fixtures', 139 | filepath: 'D:\\a\\async-folder-walker\\async-folder-walker\\fixtures\\sub-folder\\sub-sub-folder\\sub-sub-folder-file.json', 140 | stat: Stats { 141 | dev: 1321874112, 142 | mode: 33206, 143 | nlink: 1, 144 | uid: 0, 145 | gid: 0, 146 | rdev: 0, 147 | blksize: 4096, 148 | ino: 562949953421580, 149 | size: 37, 150 | blocks: 0, 151 | atimeMs: 1577476819530.035, 152 | mtimeMs: 1577476819530.035, 153 | ctimeMs: 1577476819530.035, 154 | birthtimeMs: 1577476819530.035, 155 | atime: 2019-12-27T20:00:19.530Z, 156 | mtime: 2019-12-27T20:00:19.530Z, 157 | ctime: 2019-12-27T20:00:19.530Z, 158 | birthtime: 2019-12-27T20:00:19.530Z 159 | }, 160 | relname: 'sub-folder\\sub-sub-folder\\sub-sub-folder-file.json', 161 | basename: 'sub-sub-folder-file.json' 162 | } 163 | ``` 164 | 165 | The `stat` property is an instance of [fs.Stats](https://nodejs.org/api/fs.html#fs_class_fs_stats) so it has extra methods not listed here. 166 | 167 | ### `files = await allFiles(paths, [opts])` 168 | 169 | Get an Array of all files inside of a directory. `paths` can be a single string path or an array of string paths. 170 | 171 | `opts` Is the same as [`asyncFolderWalker`](#async-gen--asyncfolderwalkerpaths-opts). 172 | 173 | ## See also 174 | 175 | This module is effectivly a rewrite of [okdistribute/folder-walker](https://github.com/okdistribute/folder-walker) using async generators instead of Node streams, and a few tweaks to the underlying options to make the results a bit more flexible. 176 | 177 | - [for-await...of](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) 178 | 179 | ## License 180 | 181 | MIT 182 | -------------------------------------------------------------------------------- /declaration.tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "declarationMap": true, 6 | "noEmit": false, 7 | "emitDeclarationOnly": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /fixtures/another-folder/test.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /fixtures/ignore-folder/sub-folder-file.json: -------------------------------------------------------------------------------- 1 | { 2 | "sub-folder": "content" 3 | } 4 | -------------------------------------------------------------------------------- /fixtures/ignore-folder/sub-sub-folder/sub-sub-folder-file.json: -------------------------------------------------------------------------------- 1 | { 2 | "sub-sub-folder": "content" 3 | } 4 | -------------------------------------------------------------------------------- /fixtures/sub-folder/sub-folder-file.json: -------------------------------------------------------------------------------- 1 | { 2 | "sub-folder": "content" 3 | } 4 | -------------------------------------------------------------------------------- /fixtures/sub-folder/sub-sub-folder/sub-sub-folder-file.json: -------------------------------------------------------------------------------- 1 | { 2 | "sub-sub-folder": "content" 3 | } 4 | -------------------------------------------------------------------------------- /fixtures/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "world" 3 | } 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** 4 | * @typedef {import('fs').Stats} Stats 5 | */ 6 | 7 | 'use strict' 8 | 9 | const fs = require('fs') 10 | const path = require('path') 11 | const ignore = require('ignore') 12 | 13 | const { readdir, lstat } = fs.promises 14 | 15 | /** 16 | * PathFilter lets you filter files based on a resolved `filepath`. 17 | * @callback PathFilter 18 | * @param {string} filepath - The resolved `filepath` of the file to test for filtering. 19 | * 20 | * @return {boolean} Return false to filter the given `filepath` and true to include it. 21 | */ 22 | 23 | /** 24 | * @type PathFilter 25 | */ 26 | const pathFilter = (/* filepath */) => true 27 | 28 | /** 29 | * statFilter lets you filter files based on a lstat object. 30 | * @callback StatFilter 31 | * @param {Stats} st - A fs.Stats instance. 32 | * 33 | * @return {boolean} Return false to filter the given `filepath` and true to include it. 34 | */ 35 | 36 | /** 37 | * @type StatFilter 38 | */ 39 | const statFilter = (/* st */) => true 40 | 41 | /** 42 | * FWStats is the object that the okdistribute/folder-walker module returns by default. 43 | * 44 | * @typedef FWStats 45 | * @property {string} root - The filepath of the directory where the walk started. 46 | * @property {string} filepath - The resolved assolute path. 47 | * @property {Stats} stat - A fs.Stats instance. 48 | * @property {string} relname - The relative path to `root`. 49 | * @property {string} basename - The resolved filepath of the files containing directory. 50 | */ 51 | 52 | /** 53 | * Shaper lets you change the shape of the returned file data from walk-time stats. 54 | * @template T 55 | * @callback Shaper 56 | * @param {FWStats} fwStats - The same status object returned from folder-walker. 57 | * 58 | * @return {T} - Whatever you want returned from the directory walk. 59 | */ 60 | 61 | /** 62 | * @type {Shaper} 63 | */ 64 | const shaper = ({ filepath/*, root, stat, relname, basename */ }) => filepath 65 | 66 | /** 67 | * AFWReturnType will return the return type of AFW for your given shaper. 68 | * 69 | * @template T 70 | * @typedef {T extends AsyncGenerator ? U : never} AFWReturnType 71 | */ 72 | 73 | /** 74 | * Options object. 75 | * 76 | * @template T 77 | * @typedef {object} AFWOpts 78 | * @property {PathFilter} pathFilter=pathFilter - A pathFilter callback. 79 | * @property {StatFilter} statFilter=statFilter - A statFilter callback. 80 | * @property {string[]} ignore=[] - An array of .gitignore style strings of files to ignore. 81 | * @property {number} maxDepth=Infinity - The maximum number of folders to walk down into. 82 | * @property {Shaper} shaper=shaper - A shaper callback. 83 | */ 84 | 85 | /** 86 | * Create an async generator that iterates over all folders and directories inside of `dirs`. 87 | * 88 | * @template T 89 | * @public 90 | * @param {string|string[]} dirs - The path or paths of the directory to walk. 91 | * @param {?Partial>} [opts] - Options used for the directory walk. 92 | * @yields {T} - An iterator that returns a value of type T. 93 | */ 94 | async function * asyncFolderWalker (dirs, opts) { 95 | /** @type {AFWOpts} */ 96 | const resolvedOpts = Object.assign({ 97 | fs, 98 | pathFilter, 99 | statFilter, 100 | ignore: [], 101 | maxDepth: Infinity, 102 | shaper 103 | }, opts) 104 | 105 | // @ts-ignore 106 | const ig = ignore().add(resolvedOpts.ignore) 107 | 108 | const roots = [dirs].flat().filter(resolvedOpts.pathFilter) 109 | const pending = [] 110 | 111 | while (roots.length) { 112 | const root = roots.shift() 113 | if (!root) continue // Handle potential undefined value 114 | pending.push(root) 115 | 116 | while (pending.length) { 117 | const current = pending.shift() 118 | if (!current) continue // Handle potential undefined value 119 | 120 | const st = await lstat(current) 121 | const rel = relname(root, current) 122 | if (ig.ignores(st.isDirectory() ? rel + '/' : rel)) continue 123 | if ((!st.isDirectory() || depthLimiter(current, root, resolvedOpts.maxDepth)) && resolvedOpts.statFilter(st)) { 124 | yield resolvedOpts.shaper(fwShape(root, current, st)) 125 | continue 126 | } 127 | 128 | const files = await readdir(current) 129 | files.sort() 130 | 131 | for (const file of files) { 132 | const next = path.join(current, file) 133 | if (resolvedOpts.pathFilter(next)) pending.unshift(next) 134 | } 135 | if (current === root || !resolvedOpts.statFilter(st)) continue 136 | else yield resolvedOpts.shaper(fwShape(root, current, st)) 137 | } 138 | } 139 | } 140 | 141 | /** 142 | * @param {string} root 143 | * @param {string} name 144 | * @return {string} The basename or relative name if root === name 145 | */ 146 | function relname (root, name) { 147 | return root === name ? path.basename(name) : path.relative(root, name) 148 | } 149 | 150 | /** 151 | * Generates the same shape as the folder-walker module. 152 | * 153 | * @param {string} root - Root filepath. 154 | * @param {string} name - Target filepath. 155 | * @param {Stats} st - fs.Stat object. 156 | * @returns {FWStats} Folder walker object. 157 | */ 158 | function fwShape (root, name, st) { 159 | return { 160 | root, 161 | filepath: name, 162 | stat: st, 163 | relname: relname(root, name), 164 | basename: path.basename(name) 165 | } 166 | } 167 | 168 | /** 169 | * Test if we are at maximum directory depth. 170 | * 171 | * @param {string} filePath - The resolved path of the target file. 172 | * @param {string} relativeTo - The root directory of the current walk. 173 | * @param {number} maxDepth - The maximum number of folders to descend into. 174 | * @returns {boolean} Return true to signal stop descending. 175 | */ 176 | function depthLimiter (filePath, relativeTo, maxDepth) { 177 | if (maxDepth === Infinity) return false 178 | const rootDepth = relativeTo.split(path.sep).length 179 | const fileDepth = filePath.split(path.sep).length 180 | return fileDepth - rootDepth > maxDepth 181 | } 182 | 183 | /** 184 | * Async iterable collector. 185 | * 186 | * @template T 187 | * @public 188 | * @param {AsyncIterableIterator} iterator - The iterator to collect into an array. 189 | * @returns {Promise} Array of items collected from the iterator. 190 | */ 191 | async function all (iterator) { 192 | const collect = [] 193 | 194 | for await (const result of iterator) { 195 | collect.push(result) 196 | } 197 | 198 | return collect 199 | } 200 | 201 | /** 202 | * Gives you all files from the directory walk as an array. 203 | * 204 | * @template T 205 | * @public 206 | * @param {string|string[]} dirs - The path of the directory to walk, or an array of directory paths. 207 | * @param {Partial>} [opts] - Options used for the directory walk. 208 | * @returns {Promise} Array of files or any other result from the directory walk. 209 | */ 210 | async function allFiles (dirs, opts) { 211 | return all(asyncFolderWalker(dirs, opts)) 212 | } 213 | 214 | module.exports = { 215 | asyncFolderWalker, 216 | allFiles, 217 | all 218 | } 219 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-folder-walker", 3 | "description": "An async iterator for walking directories, with helpful options", 4 | "version": "3.0.5", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "author": "Bret Comnes (https://bret.io)", 8 | "bugs": { 9 | "url": "https://github.com/bcomnes/async-folder-walker/issues" 10 | }, 11 | "dependencies": { 12 | "ignore": "^5.1.8" 13 | }, 14 | "devDependencies": { 15 | "@tsconfig/recommended": "^1.0.3", 16 | "@types/node": "^20.8.4", 17 | "auto-changelog": "^2.2.0", 18 | "gh-release": "^7.0.0", 19 | "npm-run-all2": "^6.0.0", 20 | "p-temporary-directory": "^2.0.1", 21 | "standard": "^17.0.0", 22 | "tap": "^18.4.0", 23 | "typescript": "~5.2.2" 24 | }, 25 | "peerDependencies": { 26 | "@types/node": "*" 27 | }, 28 | "homepage": "https://github.com/bcomnes/async-folder-walker", 29 | "keywords": [], 30 | "license": "MIT", 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/bcomnes/async-folder-walker.git" 34 | }, 35 | "scripts": { 36 | "prepublishOnly": "npm run build && git push --follow-tags && gh-release -y", 37 | "postpublish": "npm run clean", 38 | "test": "npm run clean && run-s test:*", 39 | "test:standard": "standard", 40 | "test:tap": "tap", 41 | "test:tsc": "tsc", 42 | "debug": "node --nolazy --inspect-brk=9229 -r esm test.js", 43 | "version": "run-s version:*", 44 | "version:changelog": "auto-changelog -p --template keepachangelog auto-changelog --breaking-pattern 'BREAKING CHANGE:'", 45 | "version:git": "git add CHANGELOG.md", 46 | "clean": "run-p clean:*", 47 | "clean:declarations": "rm -rf $(find . -maxdepth 1 -type f -name '*.d.ts*')", 48 | "build": "npm run clean && run-p build:*", 49 | "build:declaration": "tsc -p declaration.tsconfig.json" 50 | }, 51 | "standard": { 52 | "ignore": [ 53 | "dist" 54 | ] 55 | }, 56 | "tap": { 57 | "serial": [], 58 | "typecheck": true, 59 | "allow-incomplete-coverage": true, 60 | "coverage-report": [ 61 | "text", 62 | "lcovonly" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const tap = require('tap') 2 | const { asyncFolderWalker, allFiles } = require('.') 3 | const path = require('path') 4 | // @ts-ignore 5 | const tmp = require('p-temporary-directory') 6 | 7 | const fixtures = path.join(__dirname, 'fixtures') 8 | 9 | tap.test('for of multiple folders', async t => { 10 | for await (const file of asyncFolderWalker([ 11 | path.join(fixtures, 'sub-folder'), 12 | path.join(fixtures, 'another-folder') 13 | ])) { 14 | t.ok(file, file) 15 | } 16 | }) 17 | 18 | tap.test('Array from async iterator', async t => { 19 | const files = await allFiles([ 20 | path.join(fixtures, 'sub-folder'), 21 | path.join(fixtures, 'another-folder') 22 | ]) 23 | t.equal(files.length, 4, 'expected number of files are found') 24 | }) 25 | 26 | tap.test('No args', async t => { 27 | // @ts-ignore 28 | for await (const file of asyncFolderWalker()) { 29 | t.fail('no files should be found!', file) 30 | } 31 | t.pass('for of executed') 32 | }) 33 | 34 | tap.test('No folders', async t => { 35 | const [dir, cleanup] = await tmp() 36 | try { 37 | for await (const file of asyncFolderWalker(dir)) { 38 | t.fail('no files should be found!', file) 39 | } 40 | t.pass('for of executed') 41 | } finally { 42 | await cleanup() 43 | } 44 | }) 45 | 46 | tap.test('When you just pass a file', async t => { 47 | const [dir, cleanup] = await tmp() 48 | try { 49 | const theFile = path.join(fixtures, 'test.json') 50 | const files = await allFiles([theFile, dir]) 51 | t.equal(files.length, 1, 'only one file is found') 52 | t.equal(theFile, files[0], 'only one file is found') 53 | } finally { 54 | await cleanup() 55 | } 56 | }) 57 | 58 | tap.test('pathFilter works', async t => { 59 | const filterStrig = 'sub-folder' 60 | const files = await allFiles(fixtures, { 61 | pathFilter: p => !p.includes(filterStrig) 62 | }) 63 | 64 | t.notOk(files.some(f => f.includes(filterStrig)), 'No paths include the excluded string') 65 | }) 66 | 67 | tap.test('statFilter works', async t => { 68 | const stats = await allFiles(fixtures, { 69 | statFilter: st => !st.isDirectory(), // Exclude files 70 | shaper: ({ stat /*, root, filepath, relname, basename */ }) => stat // Lets get the stats instead of paths 71 | }) 72 | 73 | for (const st of stats) { 74 | t.notOk(st.isDirectory(), 'none of the files are directories') 75 | } 76 | }) 77 | 78 | tap.test('dont include root directory in response', async (t) => { 79 | const root = fixtures 80 | for await (const file of asyncFolderWalker(root)) { 81 | if (file === root) t.fail('root directory should not be in results') 82 | } 83 | t.pass('The root was not included in results.') 84 | }) 85 | 86 | tap.test('dont walk past the maxDepth', async t => { 87 | const maxDepth = 3 88 | const walker = asyncFolderWalker(['.git', 'node_modules'], { maxDepth }) 89 | for await (const file of walker) { 90 | const correctLength = file.split(path.sep).length - process.cwd().split(path.sep).length <= maxDepth 91 | if (!correctLength) t.fail('walker walked past the depth it was supposed to') 92 | } 93 | t.pass('Walker was depth limited') 94 | }) 95 | 96 | tap.test('ignore-folder should be ignored', async t => { 97 | for await (const file of asyncFolderWalker([fixtures], { 98 | ignore: ['ignore-folder', '.*'] 99 | })) { 100 | t.notOk(file.includes('ignore-folder', 'does not include ignored files and folders')) 101 | } 102 | }) 103 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "noEmit": true, 7 | "resolveJsonModule": true, 8 | "skipLibCheck": false, 9 | "removeComments": false, 10 | "stripInternal": true, 11 | "exactOptionalPropertyTypes": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noImplicitOverride": true, 14 | "noPropertyAccessFromIndexSignature": true, 15 | "noUncheckedIndexedAccess": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true 19 | }, 20 | "files": [ 21 | "index.js" 22 | ], 23 | "exclude": [ 24 | "node_modules" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------