├── .eslintrc ├── .gitignore ├── tsconfig.json ├── .github └── workflows │ ├── release.yml │ └── nodejs.yml ├── LICENSE ├── CHANGELOG.md ├── package.json ├── src └── index.ts ├── README.md └── test └── index.test.ts /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint-config-egg/typescript", 4 | "eslint-config-egg/lib/rules/enforce-node-prefix" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.seed 2 | *.log 3 | *.csv 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | 9 | coverage.html 10 | coverage/ 11 | cov/ 12 | 13 | node_modules 14 | 15 | dump.rdb 16 | .DS_Store 17 | 18 | test.js 19 | .tshy* 20 | dist 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@eggjs/tsconfig", 3 | "compilerOptions": { 4 | "strict": true, 5 | "noImplicitAny": true, 6 | "target": "ES2022", 7 | "module": "NodeNext", 8 | "moduleResolution": "NodeNext" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: [ master ] 5 | 6 | jobs: 7 | release: 8 | name: Node.js 9 | uses: eggjs/github-actions/.github/workflows/node-release.yml@master 10 | secrets: 11 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 12 | GIT_TOKEN: ${{ secrets.GIT_TOKEN }} 13 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | Job: 11 | name: Node.js 12 | uses: node-modules/github-actions/.github/workflows/node-test.yml@master 13 | with: 14 | os: 'ubuntu-latest' 15 | version: '18.19.0, 18, 20, 22' 16 | secrets: 17 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-present Alibaba Group Holding Limited and other contributors. 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.1.0](https://github.com/eggjs/egg-path-matching/compare/v2.0.0...v2.1.0) (2024-09-18) 4 | 5 | 6 | ### Features 7 | 8 | * use path-to-regexp@6.3.0 ([#10](https://github.com/eggjs/egg-path-matching/issues/10)) ([b059f04](https://github.com/eggjs/egg-path-matching/commit/b059f04da680010b6cd506a4950bbd120cc78e95)) 9 | 10 | ## [2.0.0](https://github.com/eggjs/egg-path-matching/compare/v1.1.0...v2.0.0) (2024-06-15) 11 | 12 | 13 | ### ⚠ BREAKING CHANGES 14 | 15 | * drop Node.js < 18.19.0 support 16 | 17 | https://github.com/eggjs/egg/issues/5257 18 | 19 | ### Features 20 | 21 | * support cjs and esm both ([#8](https://github.com/eggjs/egg-path-matching/issues/8)) ([a092108](https://github.com/eggjs/egg-path-matching/commit/a092108f1552296ea7ba060300bf580f3a6a5d2e)) 22 | 23 | ## [1.1.0](https://github.com/eggjs/egg-path-matching/compare/v1.0.1...v1.1.0) (2023-12-14) 24 | 25 | 26 | ### Features 27 | 28 | * use github action and auto release ([#6](https://github.com/eggjs/egg-path-matching/issues/6)) ([1eb34da](https://github.com/eggjs/egg-path-matching/commit/1eb34da16f9676737bbc1fd95fc73ae26e8e5b39)) 29 | 30 | 1.0.1 / 2017-11-15 31 | ================== 32 | 33 | **fixes** 34 | * [[`6e94f78`](http://github.com/eggjs/egg-path-matching/commit/6e94f78d1229e85a2420274cf3e17c0c7c41c15b)] - fix: reset lastIndex when pattern used the "g" flag (#1) (eric.zhang <<910261782@qq.com>>) 35 | 36 | **others** 37 | * [[`ca6187d`](http://github.com/eggjs/egg-path-matching/commit/ca6187dd5499f681d5d820541e82210e82019055)] - docs: fix badges (dead-horse <>) 38 | * [[`f7d936b`](http://github.com/eggjs/egg-path-matching/commit/f7d936b16d26fb16ea4e5af56f8ced729331f8ae)] - build: add more scripts (dead-horse <>) 39 | 40 | 1.0.0 / 2016-11-15 41 | ================== 42 | 43 | * feat: add autod config 44 | * test: build test on node@4,5,6 45 | * feat: first implement 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egg-path-matching", 3 | "version": "2.1.0", 4 | "engine": { 5 | "node": ">= 18.19.0" 6 | }, 7 | "description": "match or ignore url path", 8 | "scripts": { 9 | "lint": "eslint src test", 10 | "test": "npm run lint -- --fix && npm run test-local", 11 | "test-local": "egg-bin test", 12 | "ci": "npm run lint && egg-bin cov && npm run prepublishOnly", 13 | "prepublishOnly": "tshy && tshy-after" 14 | }, 15 | "keywords": [ 16 | "url", 17 | "match", 18 | "ignore" 19 | ], 20 | "author": { 21 | "name": "dead-horse", 22 | "email": "dead_horse@qq.com", 23 | "url": "http://deadhorse.me" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git@github.com:eggjs/egg-path-matching" 28 | }, 29 | "license": "MIT", 30 | "dependencies": { 31 | "path-to-regexp": "^6.3.0" 32 | }, 33 | "devDependencies": { 34 | "@eggjs/tsconfig": "1", 35 | "@types/mocha": "10", 36 | "@types/node": "20", 37 | "egg-bin": "6", 38 | "eslint": "8", 39 | "eslint-config-egg": "13", 40 | "path-to-regexp-v8": "npm:path-to-regexp@^8.3.0", 41 | "tshy": "1", 42 | "tshy-after": "1", 43 | "typescript": "5" 44 | }, 45 | "files": [ 46 | "dist", 47 | "src" 48 | ], 49 | "type": "module", 50 | "tshy": { 51 | "exports": { 52 | "./package.json": "./package.json", 53 | ".": "./src/index.ts" 54 | } 55 | }, 56 | "exports": { 57 | "./package.json": "./package.json", 58 | ".": { 59 | "import": { 60 | "source": "./src/index.ts", 61 | "types": "./dist/esm/index.d.ts", 62 | "default": "./dist/esm/index.js" 63 | }, 64 | "require": { 65 | "source": "./src/index.ts", 66 | "types": "./dist/commonjs/index.d.ts", 67 | "default": "./dist/commonjs/index.js" 68 | } 69 | } 70 | }, 71 | "main": "./dist/commonjs/index.js", 72 | "types": "./dist/commonjs/index.d.ts" 73 | } 74 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { pathToRegexp } from 'path-to-regexp'; 2 | 3 | export type PathMatchingFun = (ctx: any) => boolean; 4 | 5 | export type PathMatchingPattern = string | RegExp | PathMatchingFun | (string | RegExp | PathMatchingFun)[]; 6 | 7 | export interface PathMatchingOptions { 8 | ignore?: PathMatchingPattern; 9 | match?: PathMatchingPattern; 10 | pathToRegexpModule?: any; 11 | } 12 | 13 | export function pathMatching(options: PathMatchingOptions): PathMatchingFun { 14 | options = options || {}; 15 | if (options.match && options.ignore) { 16 | throw new Error('options.match and options.ignore can not both present'); 17 | } 18 | if (!options.match && !options.ignore) { 19 | return () => true; 20 | } 21 | 22 | const pathToRegexpModule = options.pathToRegexpModule || { pathToRegexp }; 23 | const pathToRegexpFn = pathToRegexpModule.pathToRegexp || pathToRegexpModule; 24 | const matchFn = options.match ? toPathMatch(options.match, pathToRegexpFn) : toPathMatch(options.ignore!, pathToRegexpFn); 25 | 26 | return function pathMatch(ctx: any) { 27 | const matched = matchFn(ctx); 28 | return options.match ? matched : !matched; 29 | }; 30 | } 31 | 32 | function toPathMatch(pattern: PathMatchingPattern, pathToRegexpFn: any): PathMatchingFun { 33 | if (typeof pattern === 'string') { 34 | let reg = pathToRegexpFn(pattern, [], { end: false }); 35 | if (reg.regexp) { 36 | // support path-to-regexp@8 37 | // => const { regexp, keys } = pathToRegexp("/foo/:bar"); 38 | reg = reg.regexp; 39 | } 40 | if (reg.global) reg.lastIndex = 0; 41 | return ctx => reg.test(ctx.path); 42 | } 43 | if (pattern instanceof RegExp) { 44 | return ctx => { 45 | if (pattern.global) { 46 | pattern.lastIndex = 0; 47 | } 48 | return pattern.test(ctx.path); 49 | }; 50 | } 51 | if (typeof pattern === 'function') return pattern; 52 | if (Array.isArray(pattern)) { 53 | const matchFns = pattern.map(item => toPathMatch(item, pathToRegexpFn)); 54 | return ctx => matchFns.some(matchFn => matchFn(ctx)); 55 | } 56 | throw new Error(`match/ignore pattern must be RegExp, Array or String, but got ${pattern}`); 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egg-path-matching 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![CI](https://github.com/eggjs/egg-path-matching/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/egg-path-matching/actions/workflows/nodejs.yml) 5 | [![Test coverage](https://img.shields.io/codecov/c/github/eggjs/egg-path-matching.svg?style=flat-square)](https://codecov.io/gh/eggjs/egg-path-matching) 6 | [![Known Vulnerabilities][snyk-image]][snyk-url] 7 | [![npm download][download-image]][download-url] 8 | 9 | [npm-image]: https://img.shields.io/npm/v/egg-path-matching.svg?style=flat-square 10 | [npm-url]: https://npmjs.org/package/egg-path-matching 11 | [snyk-image]: https://snyk.io/test/npm/egg-path-matching/badge.svg?style=flat-square 12 | [snyk-url]: https://snyk.io/test/npm/egg-path-matching 13 | [download-image]: https://img.shields.io/npm/dm/egg-path-matching.svg?style=flat-square 14 | [download-url]: https://npmjs.org/package/egg-path-matching 15 | 16 | ## Installation 17 | 18 | ```bash 19 | npm install egg-path-matching 20 | ``` 21 | 22 | ## Usage 23 | 24 | ```ts 25 | import { pathMatching } from 'egg-path-matching'; 26 | 27 | const options = { 28 | ignore: '/api', // string will use parsed by path-to-regexp 29 | // support regexp 30 | ignore: /^\/api/, 31 | // support function 32 | ignore: ctx => ctx.path.startsWith('/api'), 33 | // support Array 34 | ignore: [ ctx => ctx.path.startsWith('/api'), /^\/foo$/, '/bar'], 35 | // support match or ignore 36 | match: '/api', 37 | // custom path-to-regexp module, default is `path-to-regexp@6` 38 | // pathToRegexpModule: customPathToRegexp, 39 | }; 40 | 41 | const match = pathMatching(options); 42 | assert.equal(match({ path: '/api' }), true); 43 | assert.equal(match({ path: '/api/hello' }), true); 44 | assert.equal(match({ path: '/api' }), true); 45 | ``` 46 | 47 | ### options 48 | 49 | - `match` {String | RegExp | Function | Array} - if request path hit `options.match`, will return `true`, otherwise will return `false`. 50 | - `ignore` {String | RegExp | Function | Array} - if request path hit `options.ignore`, will return `false`, otherwise will return `true`. 51 | - `pathToRegexpModule` {Object | Function} - custom path-to-regexp module. Default is `path-to-regexp@6`. Supports both `path-to-regexp@6` and `path-to-regexp@8+` formats. 52 | 53 | `ignore` and `match` can not both be presented. 54 | and if neither `ignore` nor `match` presented, the new function will always return `true`. 55 | 56 | ### License 57 | 58 | [MIT](LICENSE) 59 | 60 | ## Contributors 61 | 62 | [![Contributors](https://contrib.rocks/image?repo=eggjs/egg-path-matching)](https://github.com/eggjs/egg-path-matching/graphs/contributors) 63 | 64 | Made with [contributors-img](https://contrib.rocks). 65 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { strict as assert } from 'node:assert'; 2 | import { pathMatching as match } from '../src/index.js'; 3 | 4 | describe('egg-path-matching', () => { 5 | it('options.match and options.ignore both present should throw', () => { 6 | try { 7 | match({ ignore: '/api', match: '/dashboard' }); 8 | throw new Error('should not exec'); 9 | } catch (e: any) { 10 | assert.equal(e.message, 'options.match and options.ignore can not both present'); 11 | } 12 | }); 13 | 14 | it('options.match and options.ignore both not present should always return true', () => { 15 | const fn = match({}); 16 | assert(fn({}) === true); 17 | }); 18 | 19 | describe('match', () => { 20 | it('support string', () => { 21 | const fn = match({ match: '/api' }); 22 | assert(fn({ path: '/api/hello' }) === true); 23 | assert(fn({ path: '/api/' }) === true); 24 | assert(fn({ path: '/api' }) === true); 25 | assert(fn({ path: '/api1/hello' }) === false); 26 | assert(fn({ path: '/api1' }) === false); 27 | }); 28 | 29 | it('support custom pathToRegexpModule', async () => { 30 | const pathToRegexpV8 = await import('path-to-regexp-v8'); 31 | const fn = match({ match: '/api{/*path}', pathToRegexpModule: pathToRegexpV8 }); 32 | assert(fn({ path: '/api/hello' }) === true); 33 | assert(fn({ path: '/api/' }) === true); 34 | assert(fn({ path: '/api' }) === true); 35 | assert(fn({ path: '/api1/hello' }) === false); 36 | assert(fn({ path: '/api1' }) === false); 37 | }); 38 | 39 | it('support regexp', () => { 40 | const fn = match({ match: /^\/api/ }); 41 | assert(fn({ path: '/api/hello' }) === true); 42 | assert(fn({ path: '/api/' }) === true); 43 | assert(fn({ path: '/api' }) === true); 44 | assert(fn({ path: '/api1/hello' }) === true); 45 | assert(fn({ path: '/api1' }) === true); 46 | assert(fn({ path: '/v1/api1' }) === false); 47 | }); 48 | 49 | it('support global regexp', () => { 50 | const fn = match({ match: /^\/api/g }); 51 | assert(fn({ path: '/api/hello' }) === true); 52 | assert(fn({ path: '/api/' }) === true); 53 | assert(fn({ path: '/api' }) === true); 54 | assert(fn({ path: '/api1/hello' }) === true); 55 | assert(fn({ path: '/api1' }) === true); 56 | assert(fn({ path: '/v1/api1' }) === false); 57 | }); 58 | 59 | it('support function', () => { 60 | const fn = match({ 61 | match: ctx => ctx.path.startsWith('/api'), 62 | }); 63 | assert(fn({ path: '/api/hello' }) === true); 64 | assert(fn({ path: '/api/' }) === true); 65 | assert(fn({ path: '/api' }) === true); 66 | assert(fn({ path: '/api1/hello' }) === true); 67 | assert(fn({ path: '/api1' }) === true); 68 | assert(fn({ path: '/v1/api1' }) === false); 69 | }); 70 | 71 | it('support array', () => { 72 | const fn = match({ 73 | match: [ ctx => ctx.path.startsWith('/api'), '/ajax', /^\/foo$/ ], 74 | }); 75 | assert(fn({ path: '/api/hello' }) === true); 76 | assert(fn({ path: '/api/' }) === true); 77 | assert(fn({ path: '/api' }) === true); 78 | assert(fn({ path: '/api1/hello' }) === true); 79 | assert(fn({ path: '/api1' }) === true); 80 | assert(fn({ path: '/v1/api1' }) === false); 81 | assert(fn({ path: '/ajax/hello' }) === true); 82 | assert(fn({ path: '/foo' }) === true); 83 | }); 84 | }); 85 | 86 | describe('ignore', () => { 87 | it('support string', () => { 88 | const fn = match({ ignore: '/api' }); 89 | assert(fn({ path: '/api/hello' }) === false); 90 | assert(fn({ path: '/api/' }) === false); 91 | assert(fn({ path: '/api' }) === false); 92 | assert(fn({ path: '/api1/hello' }) === true); 93 | assert(fn({ path: '/api1' }) === true); 94 | }); 95 | 96 | it('support regexp', () => { 97 | const fn = match({ ignore: /^\/api/ }); 98 | assert(fn({ path: '/api/hello' }) === false); 99 | assert(fn({ path: '/api/' }) === false); 100 | assert(fn({ path: '/api' }) === false); 101 | assert(fn({ path: '/api1/hello' }) === false); 102 | assert(fn({ path: '/api1' }) === false); 103 | assert(fn({ path: '/v1/api1' }) === true); 104 | }); 105 | 106 | it('support global regexp', () => { 107 | const fn = match({ ignore: /^\/api/g }); 108 | assert(fn({ path: '/api/hello' }) === false); 109 | assert(fn({ path: '/api/' }) === false); 110 | assert(fn({ path: '/api' }) === false); 111 | assert(fn({ path: '/api1/hello' }) === false); 112 | assert(fn({ path: '/api1' }) === false); 113 | assert(fn({ path: '/v1/api1' }) === true); 114 | }); 115 | 116 | it('support function', () => { 117 | const fn = match({ 118 | ignore: ctx => ctx.path.startsWith('/api'), 119 | }); 120 | assert(fn({ path: '/api/hello' }) === false); 121 | assert(fn({ path: '/api/' }) === false); 122 | assert(fn({ path: '/api' }) === false); 123 | assert(fn({ path: '/api1/hello' }) === false); 124 | assert(fn({ path: '/api1' }) === false); 125 | assert(fn({ path: '/v1/api1' }) === true); 126 | }); 127 | 128 | it('support array', () => { 129 | const fn = match({ 130 | ignore: [ ctx => ctx.path.startsWith('/api'), '/ajax', /^\/foo$/ ], 131 | }); 132 | assert(fn({ path: '/api/hello' }) === false); 133 | assert(fn({ path: '/api/' }) === false); 134 | assert(fn({ path: '/api' }) === false); 135 | assert(fn({ path: '/api1/hello' }) === false); 136 | assert(fn({ path: '/api1' }) === false); 137 | assert(fn({ path: '/v1/api1' }) === true); 138 | assert(fn({ path: '/ajax/hello' }) === false); 139 | assert(fn({ path: '/foo' }) === false); 140 | }); 141 | }); 142 | }); 143 | --------------------------------------------------------------------------------