├── .github └── workflows │ ├── ci.yml │ ├── commit-if-modified.sh │ ├── copyright-year.sh │ ├── isaacs-makework.yml │ ├── package-json-repo.js │ └── typedoc.yml ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── scripts └── fixup.sh ├── src └── index.ts ├── test └── index.js ├── tsconfig.json └── typedoc.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | node-version: [14.x, 16.x, 18.x, 19.x] 10 | platform: 11 | - os: ubuntu-latest 12 | shell: bash 13 | - os: macos-latest 14 | shell: bash 15 | - os: windows-latest 16 | shell: bash 17 | - os: windows-latest 18 | shell: powershell 19 | fail-fast: false 20 | 21 | runs-on: ${{ matrix.platform.os }} 22 | defaults: 23 | run: 24 | shell: ${{ matrix.platform.shell }} 25 | 26 | steps: 27 | - name: Checkout Repository 28 | uses: actions/checkout@v3 29 | 30 | - name: Use Nodejs ${{ matrix.node-version }} 31 | uses: actions/setup-node@v3 32 | with: 33 | node-version: ${{ matrix.node-version }} 34 | 35 | - name: Install dependencies 36 | run: npm install 37 | 38 | - name: Run Tests 39 | run: npm test -- -c -t0 40 | -------------------------------------------------------------------------------- /.github/workflows/commit-if-modified.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | git config --global user.email "$1" 3 | shift 4 | git config --global user.name "$1" 5 | shift 6 | message="$1" 7 | shift 8 | if [ $(git status --porcelain "$@" | egrep '^ M' | wc -l) -gt 0 ]; then 9 | git add "$@" 10 | git commit -m "$message" 11 | git push || git pull --rebase 12 | git push 13 | fi 14 | -------------------------------------------------------------------------------- /.github/workflows/copyright-year.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | dir=${1:-$PWD} 3 | dates=($(git log --date=format:%Y --pretty=format:'%ad' --reverse | sort | uniq)) 4 | if [ "${#dates[@]}" -eq 1 ]; then 5 | datestr="${dates}" 6 | else 7 | datestr="${dates}-${dates[${#dates[@]}-1]}" 8 | fi 9 | 10 | stripDate='s/^((.*)Copyright\b(.*?))((?:,\s*)?(([0-9]{4}\s*-\s*[0-9]{4})|(([0-9]{4},\s*)*[0-9]{4})))(?:,)?\s*(.*)\n$/$1$9\n/g' 11 | addDate='s/^.*Copyright(?:\s*\(c\))? /Copyright \(c\) '$datestr' /g' 12 | for l in $dir/LICENSE*; do 13 | perl -pi -e "$stripDate" $l 14 | perl -pi -e "$addDate" $l 15 | done 16 | -------------------------------------------------------------------------------- /.github/workflows/isaacs-makework.yml: -------------------------------------------------------------------------------- 1 | name: "various tidying up tasks to silence nagging" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | makework: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | - name: Use Node.js 17 | uses: actions/setup-node@v2.1.4 18 | with: 19 | node-version: 16.x 20 | - name: put repo in package.json 21 | run: node .github/workflows/package-json-repo.js 22 | - name: check in package.json if modified 23 | run: | 24 | bash -x .github/workflows/commit-if-modified.sh \ 25 | "package-json-repo-bot@example.com" \ 26 | "package.json Repo Bot" \ 27 | "chore: add repo to package.json" \ 28 | package.json package-lock.json 29 | - name: put all dates in license copyright line 30 | run: bash .github/workflows/copyright-year.sh 31 | - name: check in licenses if modified 32 | run: | 33 | bash .github/workflows/commit-if-modified.sh \ 34 | "license-year-bot@example.com" \ 35 | "License Year Bot" \ 36 | "chore: add copyright year to license" \ 37 | LICENSE* 38 | -------------------------------------------------------------------------------- /.github/workflows/package-json-repo.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const pf = require.resolve(`${process.cwd()}/package.json`) 4 | const pj = require(pf) 5 | 6 | if (!pj.repository && process.env.GITHUB_REPOSITORY) { 7 | const fs = require('fs') 8 | const server = process.env.GITHUB_SERVER_URL || 'https://github.com' 9 | const repo = `${server}/${process.env.GITHUB_REPOSITORY}` 10 | pj.repository = repo 11 | const json = fs.readFileSync(pf, 'utf8') 12 | const match = json.match(/^\s*\{[\r\n]+([ \t]*)"/) 13 | const indent = match[1] 14 | const output = JSON.stringify(pj, null, indent || 2) + '\n' 15 | fs.writeFileSync(pf, output) 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/typedoc.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Single deploy job since we're just deploying 25 | deploy: 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | - name: Use Nodejs ${{ matrix.node-version }} 34 | uses: actions/setup-node@v3 35 | with: 36 | node-version: 18.x 37 | - name: Install dependencies 38 | run: npm install 39 | - name: Generate typedocs 40 | run: npm run typedoc 41 | - name: Setup Pages 42 | uses: actions/configure-pages@v3 43 | - name: Upload artifact 44 | uses: actions/upload-pages-artifact@v1 45 | with: 46 | path: './docs' 47 | - name: Deploy to GitHub Pages 48 | id: deployment 49 | uses: actions/deploy-pages@v1 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore most things, include some others 2 | /* 3 | /.* 4 | !/typedoc.json 5 | !/tsconfig*.json 6 | !/src/ 7 | !bin/ 8 | !lib/ 9 | !docs/ 10 | !package.json 11 | !package-lock.json 12 | !README.md 13 | !CONTRIBUTING.md 14 | !LICENSE 15 | !CHANGELOG.md 16 | !example/ 17 | !scripts/ 18 | !tap-snapshots/ 19 | !test/ 20 | !.github/ 21 | !.travis.yml 22 | !.gitignore 23 | !.gitattributes 24 | !coverage-map.js 25 | !map.js 26 | !index.js 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The ISC License 2 | 3 | Copyright (c) 2020-2023 Isaac Z. Schlueter 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # walk-up-path 2 | 3 | Given a path string, return a generator that walks up the path, emitting 4 | each dirname. 5 | 6 | So, to get a platform-portable walk up, instead of doing something like 7 | this: 8 | 9 | ```js 10 | for (let p = dirname(path); p;) { 11 | 12 | // ... do stuff ... 13 | 14 | const pp = dirname(p) 15 | if (p === pp) 16 | p = null 17 | else 18 | p = pp 19 | } 20 | ``` 21 | 22 | Or this: 23 | 24 | ```js 25 | for (let p = dirname(path); !isRoot(p); p = dirname(p)) { 26 | // ... do stuff ... 27 | } 28 | ``` 29 | 30 | You can do this: 31 | 32 | ```js 33 | const { walkUpPath } = require('walk-up-path') 34 | for (const p of walkUpPath(path)) { 35 | // ... do stuff .. 36 | } 37 | ``` 38 | 39 | ## API 40 | 41 | ```js 42 | const { walkUpPath } = require('walk-up-path') 43 | ``` 44 | 45 | Give the fn a string, it'll yield all the directories walking up to the 46 | root. 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "walk-up-path", 3 | "version": "4.0.0", 4 | "files": [ 5 | "dist" 6 | ], 7 | "description": "Given a path string, return a generator that walks up the path, emitting each dirname.", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/isaacs/walk-up-path" 11 | }, 12 | "author": "Isaac Z. Schlueter (https://izs.me)", 13 | "license": "ISC", 14 | "scripts": { 15 | "preversion": "npm test", 16 | "postversion": "npm publish", 17 | "prepublishOnly": "git push origin --follow-tags", 18 | "prepare": "tshy", 19 | "pretest": "npm run prepare", 20 | "presnap": "npm run prepare", 21 | "test": "tap", 22 | "snap": "tap", 23 | "format": "prettier --write . --log-level warn", 24 | "typedoc": "typedoc --tsconfig tsconfig-esm.json ./src/*.ts" 25 | }, 26 | "prettier": { 27 | "experimentalTernaries": true, 28 | "semi": false, 29 | "printWidth": 75, 30 | "tabWidth": 2, 31 | "useTabs": false, 32 | "singleQuote": true, 33 | "jsxSingleQuote": false, 34 | "bracketSameLine": true, 35 | "arrowParens": "avoid", 36 | "endOfLine": "lf" 37 | }, 38 | "devDependencies": { 39 | "@types/node": "^20.14.10", 40 | "prettier": "^3.3.2", 41 | "tap": "^20.0.3", 42 | "tshy": "^3.0.0", 43 | "typedoc": "^0.26.3" 44 | }, 45 | "type": "module", 46 | "tshy": { 47 | "exports": { 48 | "./package.json": "./package.json", 49 | ".": "./src/index.ts" 50 | } 51 | }, 52 | "exports": { 53 | "./package.json": "./package.json", 54 | ".": { 55 | "import": { 56 | "types": "./dist/esm/index.d.ts", 57 | "default": "./dist/esm/index.js" 58 | }, 59 | "require": { 60 | "types": "./dist/commonjs/index.d.ts", 61 | "default": "./dist/commonjs/index.js" 62 | } 63 | } 64 | }, 65 | "main": "./dist/commonjs/index.js", 66 | "types": "./dist/commonjs/index.d.ts", 67 | "module": "./dist/esm/index.js", 68 | "engines": { 69 | "node": "20 || >=22" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /scripts/fixup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf dist 4 | mv dist-tmp dist 5 | 6 | cat >dist/cjs/package.json <dist/mjs/package.json < path.mode.dirname(...args), 5 | resolve: (...args) => path.mode.resolve(...args), 6 | } 7 | 8 | import t from 'tap' 9 | const { walkUp } = await t.mockImport('../src/index.js', { path }) 10 | 11 | t.test('posix', async t => { 12 | path.mode = posix 13 | t.strictSame( 14 | [...walkUp('/some/kinda/path')], 15 | ['/some/kinda/path', '/some/kinda', '/some', '/'] 16 | ) 17 | t.strictSame([...walkUp('/')], ['/']) 18 | t.strictSame( 19 | [...walkUp('')], 20 | [...walkUp(process.cwd().replace(/\\/g, '/'))] 21 | ) 22 | }) 23 | 24 | t.test('win32', async t => { 25 | path.mode = win32 26 | t.strictSame( 27 | [...walkUp('c:\\some\\kinda\\path')], 28 | ['c:\\some\\kinda\\path', 'c:\\some\\kinda', 'c:\\some', 'c:\\'] 29 | ) 30 | t.strictSame( 31 | [...walkUp('c:/some/kinda/path')], 32 | ['c:\\some\\kinda\\path', 'c:\\some\\kinda', 'c:\\some', 'c:\\'] 33 | ) 34 | t.strictSame([...walkUp('/')], [path.resolve(process.cwd(), '/')]) 35 | t.strictSame([...walkUp('')], [...walkUp(process.cwd())]) 36 | }) 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "inlineSources": true, 8 | "jsx": "react", 9 | "module": "nodenext", 10 | "moduleResolution": "nodenext", 11 | "noUncheckedIndexedAccess": true, 12 | "resolveJsonModule": true, 13 | "skipLibCheck": true, 14 | "sourceMap": true, 15 | "strict": true, 16 | "target": "es2022" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationLinks": { 3 | "GitHub": "https://github.com/isaacs/walk-up-path", 4 | "isaacs projects": "https://isaacs.github.io/" 5 | } 6 | } 7 | --------------------------------------------------------------------------------