├── .editorconfig ├── .github └── workflows │ ├── build.yml │ └── create_release.yml ├── .gitignore ├── .npmignore ├── .prettierrc.js ├── .tool-versions ├── LICENSE.txt ├── README.md ├── demo └── index.html ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── cli.ts ├── config.ts ├── lib │ ├── cacheRegexes.ts │ ├── dict.ts │ ├── dictionaries │ │ ├── convert.ts │ │ ├── dictionary.ts │ │ └── jisDai2.ts │ ├── kan2num.ts │ ├── normalizeHelpers.ts │ ├── patchAddr.ts │ ├── utils.ts │ └── zen2han.ts ├── main-node.ts ├── main.ts ├── normalize.ts └── types.ts ├── test ├── addresses │ ├── addresses.csv │ ├── addresses.test.ts │ ├── build-test-data.ts │ └── list.txt ├── helpers.ts ├── integration │ ├── browser.test.ts │ ├── browser │ │ ├── index-esm.html │ │ └── index-umd.html │ ├── node-cjs │ │ ├── index.js │ │ ├── package-lock.json │ │ └── package.json │ ├── node-esm │ │ ├── index.js │ │ ├── package-lock.json │ │ └── package.json │ ├── node.test.ts │ ├── webpack-ts │ │ ├── .gitignore │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── src │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── webpack.config.js │ └── webpack.test.ts ├── main │ ├── filesystem-api.test.ts │ ├── main.test.ts │ └── metadata.test.ts └── run.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | tags: ['*'] 11 | pull_request: 12 | branches: 13 | - master 14 | 15 | jobs: 16 | build: 17 | name: "ビルド・リント" 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Use Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: '22.x' 25 | cache: 'npm' 26 | - run: npm ci 27 | - run: npm run lint 28 | - run: npm run build 29 | 30 | - uses: actions/upload-artifact@v4 31 | with: 32 | name: dist 33 | path: dist 34 | 35 | - uses: actions/upload-pages-artifact@v3 36 | if: github.ref == 'refs/heads/master' 37 | with: 38 | path: demo 39 | 40 | deploy-pages: 41 | name: "GitHub Pages へのデプロイ" 42 | runs-on: ubuntu-latest 43 | needs: 44 | - build 45 | permissions: 46 | pages: write # to deploy to Pages 47 | id-token: write # to verify the deployment originates from an appropriate source 48 | environment: 49 | name: github-pages 50 | url: ${{ steps.deployment.outputs.page_url }} 51 | if: github.ref == 'refs/heads/master' 52 | steps: 53 | - uses: actions/deploy-pages@v4 54 | id: deployment 55 | 56 | test: 57 | name: "テスト" 58 | needs: 59 | - build 60 | 61 | runs-on: ${{ matrix.os }} 62 | 63 | strategy: 64 | matrix: 65 | os: [ubuntu-latest, windows-latest] 66 | node-version: [18.x, 20.x, 22.x] 67 | 68 | steps: 69 | - uses: actions/checkout@v4 70 | - name: Use Node.js ${{ matrix.node-version }} 71 | uses: actions/setup-node@v4 72 | with: 73 | node-version: ${{ matrix.node-version }} 74 | cache: 'npm' 75 | 76 | - uses: actions/download-artifact@v4 77 | with: 78 | name: dist 79 | path: dist 80 | 81 | - run: npm ci 82 | - run: npm test 83 | 84 | # 結合テストはビルドされたものが必要 85 | # また、Node.js 18.x では結合テストをスキップする 86 | - name: 結合テスト 87 | if: matrix.node-version != '18.x' 88 | run: npm run test:integration 89 | 90 | # アドレステストは時間かかるし、環境依存が無いので、matrixにいれる必要無い 91 | test-addresses: 92 | name: "詳細住所テスト" 93 | needs: 94 | - build 95 | runs-on: ubuntu-latest 96 | steps: 97 | - uses: actions/checkout@v4 98 | - name: Use Node.js 99 | uses: actions/setup-node@v4 100 | with: 101 | node-version: '22.x' 102 | cache: 'npm' 103 | - run: npm ci 104 | - run: npm run build 105 | - run: npm run test:addresses 106 | 107 | publish: 108 | name: 'npm 公開' 109 | runs-on: ubuntu-latest 110 | needs: 111 | - test 112 | if: startsWith(github.ref, 'refs/tags/v') 113 | steps: 114 | - uses: actions/checkout@v4 115 | # Setup .npmrc file to publish to npm 116 | - uses: actions/setup-node@v4 117 | with: 118 | node-version: '22.x' 119 | registry-url: 'https://registry.npmjs.org' 120 | scope: '@geolonia' 121 | cache: 'npm' 122 | - run: npm ci 123 | - run: npm run build 124 | - run: npm publish --access=public 125 | env: 126 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 127 | -------------------------------------------------------------------------------- /.github/workflows/create_release.yml: -------------------------------------------------------------------------------- 1 | name: Auto Release 2 | 3 | permissions: 4 | contents: write 5 | on: 6 | push: 7 | tags: 8 | - 'v*.*.*' 9 | 10 | jobs: 11 | release: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v3 17 | 18 | - name: Create GitHub release 19 | uses: softprops/action-gh-release@v1 20 | with: 21 | name: ${{ github.event.inputs.tag }} 22 | tag_name: ${{ github.event.inputs.tag }} 23 | generate_release_notes: true 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | **/node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | /demo/main-esm.mjs* 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | /cache 28 | /dist 29 | 30 | // Use npm 31 | yarn.lock 32 | 33 | test/japanese-addresses-master 34 | 35 | test/integration/browser/main-esm.mjs 36 | test/integration/browser/main-umd.cjs 37 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /cache 2 | /.github 3 | /src/addresses/test.* 4 | /test 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | semi: false, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | tabWidth: 2, 6 | } 7 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 22.9.0 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 Geolonia Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @geolonia/normalize-japanese-addresses 2 | 3 | [![build](https://github.com/geolonia/normalize-japanese-addresses/actions/workflows/build.yml/badge.svg)](https://github.com/geolonia/normalize-japanese-addresses/actions/workflows/build.yml) 4 | 5 | オープンソースの住所正規化ライブラリです。 6 | 7 | 経産省の [IMI コンポーネントツール](https://info.gbiz.go.jp/tools/imi_tools/)のジオコーディングの仕組みからインスピレーションをうけて開発しました。 8 | 9 | ## デモ 10 | 11 | [https://geolonia.github.io/normalize-japanese-addresses/](https://geolonia.github.io/normalize-japanese-addresses/) 12 | 13 | ## インストール 14 | 15 | ライブラリは npm レジストリで `@geolonia/normalize-japanese-addresses` として配布されています。 16 | npm コマンドなどを使ってインストールして下さい。 17 | 18 | ```shell 19 | $ npm install @geolonia/normalize-japanese-addresses -S 20 | ``` 21 | 22 | ## 使い方 23 | 24 | ### `normalize(address: string, option: Option)` 25 | 26 | 住所を正規化します。 27 | 28 | ```javascript 29 | import { normalize } from '@geolonia/normalize-japanese-addresses'; 30 | // ESMを利用しない場合は下記 31 | // const { normalize } = require('@geolonia/normalize-japanese-addresses'); 32 | 33 | normalize('北海道札幌市西区24-2-2-3-3').then(result => { 34 | console.log(result); 35 | // { 36 | // "pref": "北海道", // 都道府県名 37 | // "city": "札幌市西区", // 市区町村名 38 | // "town": "二十四軒二条二丁目", // 大字・丁目名 39 | // "addr": "3-3", // 街区符号・住居符号または地番 40 | // "level": 8, // 正規化レベル 41 | // "point": { 42 | // "lat": 43.074206115, // 緯度 43 | // "lng": 141.315540696, // 軽度 44 | // "level": 8 // 位置情報データレベル 45 | // }, 46 | // "other": "" // 正規化できなかった文字列 47 | // } 48 | }) 49 | ``` 50 | 51 | 住所の正規化結果として戻されるオブジェクトには、`level` プロパティが含まれます。`level` には、住所文字列のどこまでを判別できたかを以下の数値で格納しています。 52 | 53 | * `0` - 都道府県も判別できなかった。 54 | * `1` - 都道府県まで判別できた。 55 | * `2` - 市区町村まで判別できた。 56 | * `3` - 大字・丁目まで判別できた。 57 | * `8` - 住居表示住所の街区符号・住居符号または地番住所の地番まで判別できた。 58 | 59 | `point` に位置情報データ (EPSG:4326) が入っています。位置情報の精度を表す `level` プロパティを参照してください。住所正規化レベルと位置情報データのレベルが異なる場合は主には、住居表示または地番情報(レベル8)は存在しましたが、位置情報データが存在しなかった場合。この場合は、大字・丁目の代表点の位置情報データを返却します。 60 | 61 | 位置情報データのレベルは下記となります。 62 | 63 | * `1` - 都道府県庁所在地 64 | * `2` - 市区町村役所(役場)所在地 65 | * `3` - 大字・丁目の代表点 66 | * `8` - 住居表示住所の場合はフロンテージ位置多い。地番住所の場合は地番の中央点。 67 | 68 | レベルの上限を設定することも可能です。例えば都道府県名のみを正規化したい場合、`level` オプションで指定することで処理を速くすることができます。 69 | 70 | ```javascript 71 | const { normalize } = require('@geolonia/normalize-japanese-addresses') 72 | normalize('北海道札幌市西区24-2-2-3-3', { level: 1 }).then(result => { 73 | console.log(result); 74 | // { 75 | // "pref": "北海道", 76 | // "other": "札幌市西区24-2-2-3-3", 77 | // "level": 1, 78 | // "point": { 79 | // "lat": 43.0639406375, 80 | // "lng": 141.347906782, 81 | // "level": 1 82 | // } 83 | // } 84 | }) 85 | ``` 86 | 87 | ### グローバルオプション 88 | 89 | 以下のパラメーターを変更することでライブラリの動作全体に関わる設定が変更できます。 90 | 91 | 92 | #### `config.japaneseAddressesApi: string` 93 | 94 | 住所データを配信する Web API のエンドポイントを指定します。デフォルトは `https://japanese-addresses-v2.geoloniamaps.com/api/ja` です。この API から配信されるデータのディレクトリ構成は [Geolonia 住所データ](https://github.com/geolonia/japanese-addresses-v2/) を参考にしてください。 95 | 96 | NodeJS環境のみ、このオプションに対して `file://` 形式の URL を指定することで、ローカルファイルとして保存したファイルを参照することができます。 97 | 98 | ## 正規化の内容 99 | 100 | * `XXX郡` などの郡の名前が省略されている住所に対しては、それを補完します。 101 | * 住所に含まれるアルファベットと数字を半角に統一します。 102 | * 京都の通り名を削除します。 103 | * 新字体と旧字体のゆらぎを吸収して、国交省の位置参照情報に記載されている地名にあわせます。 104 | * `ヶケが`、`ヵカか力`、`之ノの`、`ッツっつ` などのゆらぎを吸収して、国交省の位置参照情報に記載されている地名にあわせます。 105 | * `釜`と`竈`、`埠頭`と`ふ頭`などの漢字のゆらぎを吸収します。 106 | * 町丁目レベルに記載されている数字は、国交省の位置参照情報にあわせて、すべて漢数字に変換します。 107 | * 番地や号レベルに記載されている数字はアラビア数字に変換し、`番地` などの文字列は `-` に変換します。 108 | * 住所の末尾に建物名がある場合は、なるべくなにもしないでそのまま返す仕様になっていますが、できればあらかじめ分離していただいたほうがいいかもしれません。 109 | 110 | 参考: 111 | 112 | * [ゆらぎを処理している文字列に関しては、ソースコードを御覧ください。](https://github.com/geolonia/normalize-japanese-addresses/blob/master/src/lib/dict.ts) 113 | * [変換前、変換後の住所の例はテストコードを御覧ください。](https://github.com/geolonia/normalize-japanese-addresses/blob/master/test/main/main.test.ts) 114 | 115 | 116 | ## 開発者向け情報 117 | 118 | まず、以下のコマンドで環境を用意してください。 119 | 120 | ```shell 121 | $ git clone git@github.com:geolonia/normalize-japanese-addresses.git 122 | $ cd normalize-japanese-addresses 123 | $ npm install 124 | ``` 125 | 126 | 次に、以下を実行してコンパイルをおこないます。 127 | 128 | ```shell 129 | $ npm run build 130 | ``` 131 | 132 | dist フォルダ以下に main-node.js など必要なファイルが生成されるので、 133 | 134 | ```javascript 135 | // sample.js 136 | import { normalize } from './dist/main-node-esm.mjs'; 137 | // ESMを利用しない場合は下記 138 | // const { normalize } = require('./dist/main-node-cjs.cjs'); 139 | 140 | normalize('北海道札幌市西区24-2-2-3-3', { level: 3 }).then(result => { 141 | console.log(result); 142 | // { 143 | // "pref": "北海道", 144 | // "city": "札幌市西区", 145 | // "town": "二十四軒二条二丁目", 146 | // "other": "3-3", 147 | // "level": 3, 148 | // "point": { 149 | // "lat": 43.074273, 150 | // "lng": 141.315099, 151 | // "level": 3 152 | // } 153 | // } 154 | }) 155 | ``` 156 | 157 | という内容で sample.js を用意したら、 158 | 159 | ```shell 160 | $ node sample.js 161 | ``` 162 | 163 | でサンプルファイルを実行することができます。 164 | 165 | ## NodeJS バージョン対応方針について 166 | 167 | `normalize-japanese-addresses` は現在、 NodeJS 18.x, 20.x, 22.x を対象としてテストを実施し、動作を確認しております。ビルド時は「開発環境」の NodeJS バージョンを利用ください。 168 | 169 | NodeJS 以外のブラウザの環境は、最新ブラウザを前提とした対応となります。fetchが利用可能な環境であれば動く可能性が高い。テストは最新の Chrome を使って実施しております。 170 | 171 | 開発環境は、 `.tool-versions` にかかれているバージョンを利用してください。 172 | 173 | 準拠としては [Node.js Releases](https://nodejs.org/en/about/previous-releases) を参照してください。基本的に動作環境は Maintenance, Active LTS をターゲットとし、開発環境は最新の Active LTS を利用しますが、更新のタイミング等でずれることがあります。 174 | 175 | ## 注意 176 | 177 | * この正規化エンジンは、住所の「名寄せ」を目的としており、たとえば京都の「通り名」は削除します。 178 | * 郵便や宅急便などに使用される住所としては、問題ないと考えています。 179 | * 正規化に利用するデータは、 [`japanese-addresses-v2`](https://github.com/geolonia/japanese-addresses-v2) で作成されます。元データに関してはそのレポジトリを御覧ください。 180 | * 住居表示が未整備の地域については全体的に苦手です。 181 | 182 | ### 貢献方法 183 | 184 | [プルリクエスト](https://github.com/geolonia/normalize-japanese-addresses/pulls) や [Issue](https://github.com/geolonia/normalize-japanese-addresses/issues) はいつでも歓迎します。 185 | 186 | ## ライセンス、利用規約 187 | 188 | - ソースコードのライセンスは MIT ライセンスです。 189 | - ご利用に際しましては、できればソーシャルでのシェア、[Geolonia](https://geolonia.com/) へのリンクの設置などをしていただけると、開発者たちのモチベーションが上がると思います。 190 | 191 | 住所の正規化を工夫すれば精度があがりそうなので、そのあたりのアイディアを募集しています。 192 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @geolonia/normalize-japanese-addresses 7 | 8 | 9 | 10 | 11 |
12 |

住所正規化デモ

13 |
14 | 15 |
16 | 17 |
18 |

By @geolonia/normalize-japanese-addresses

19 |
20 | 21 | 22 | 23 | 218 | 219 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import tsdoc from 'eslint-plugin-tsdoc' 2 | import tsParser from '@typescript-eslint/parser' 3 | import path from 'node:path' 4 | import { fileURLToPath } from 'node:url' 5 | import js from '@eslint/js' 6 | import { FlatCompat } from '@eslint/eslintrc' 7 | 8 | const __filename = fileURLToPath(import.meta.url) 9 | const __dirname = path.dirname(__filename) 10 | const compat = new FlatCompat({ 11 | baseDirectory: __dirname, 12 | recommendedConfig: js.configs.recommended, 13 | allConfig: js.configs.all, 14 | }) 15 | 16 | export default [ 17 | { 18 | ignores: ['node_modules/', 'dist'], 19 | }, 20 | ...compat.extends( 21 | 'plugin:@typescript-eslint/recommended', 22 | 'plugin:prettier/recommended', 23 | ), 24 | { 25 | plugins: { 26 | tsdoc, 27 | }, 28 | 29 | languageOptions: { 30 | parser: tsParser, 31 | ecmaVersion: 2020, 32 | sourceType: 'module', 33 | }, 34 | 35 | rules: { 36 | '@typescript-eslint/explicit-module-boundary-types': 0, 37 | '@typescript-eslint/ban-ts-comment': 0, 38 | '@typescript-eslint/no-var-requires': 0, 39 | 'tsdoc/syntax': 'error', 40 | }, 41 | }, 42 | ] 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@geolonia/normalize-japanese-addresses", 3 | "version": "3.1.3", 4 | "description": "日本の住所を正規化するライブラリ", 5 | "type": "module", 6 | "main": "./dist/main-node-cjs.cjs", 7 | "types": "./dist/main-node.d.ts", 8 | "exports": { 9 | "node": { 10 | "import": "./dist/main-node-esm.mjs", 11 | "types": "./dist/main-node.d.ts", 12 | "require": "./dist/main-node-cjs.cjs" 13 | }, 14 | "import": "./dist/main-esm.mjs", 15 | "types": "./dist/main.d.ts", 16 | "default": "./dist/main-umd.cjs" 17 | }, 18 | "scripts": { 19 | "cli": "tsx ./src/cli.ts", 20 | "test": "npm run test:main", 21 | "test:main": "tsx ./test/run.ts main", 22 | "test:addresses": "tsx ./test/run.ts addresses", 23 | "test:generate-test-data": "tsx test/addresses/build-test-data.ts > test/addresses/addresses.csv", 24 | "test:integration": "tsx ./test/run.ts integration", 25 | "lint": "eslint \"src/**/*.ts\" \"test/**/*.test.ts\" --fix", 26 | "build": "npm run clean && rollup -c rollup.config.js && shx cp ./dist/main-esm.mjs* ./demo/", 27 | "clean": "shx rm -rf dist" 28 | }, 29 | "engines": { 30 | "node": ">=18" 31 | }, 32 | "author": "Geolonia, Inc.", 33 | "license": "MIT", 34 | "devDependencies": { 35 | "@rollup/plugin-commonjs": "^28.0.1", 36 | "@rollup/plugin-node-resolve": "^15.3.0", 37 | "@rollup/plugin-replace": "^6.0.1", 38 | "@rollup/plugin-terser": "^0.4.4", 39 | "@rollup/plugin-typescript": "^12.1.1", 40 | "@types/node": "^22", 41 | "@types/papaparse": "^5.3.14", 42 | "@typescript-eslint/eslint-plugin": "^8.7.0", 43 | "@typescript-eslint/parser": "^8.7.0", 44 | "eslint": "^9.11.1", 45 | "eslint-config-prettier": "^9.1.0", 46 | "eslint-plugin-prettier": "^5.2.1", 47 | "eslint-plugin-tsdoc": "^0.3.0", 48 | "glob": "^11.0.0", 49 | "jest-matcher-deep-close-to": "^3.0.2", 50 | "prettier": "^3.3.3", 51 | "puppeteer": "^23.6.0", 52 | "rollup": "^4.24.0", 53 | "rollup-plugin-delete": "^2.1.0", 54 | "rollup-plugin-dts": "^6.1.1", 55 | "shx": "^0.3.4", 56 | "tsx": "^4.19.1", 57 | "typescript": "^5.6.2" 58 | }, 59 | "dependencies": { 60 | "@geolonia/japanese-addresses-v2": "0.0.5", 61 | "@geolonia/japanese-numeral": "^1.0.2", 62 | "lru-cache": "^11.0.1", 63 | "papaparse": "^5.4.1", 64 | "undici": "^6.19.8" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | 4 | import typescript from '@rollup/plugin-typescript' 5 | import { dts } from 'rollup-plugin-dts' 6 | import del from 'rollup-plugin-delete' 7 | import resolve from '@rollup/plugin-node-resolve' 8 | import commonjs from '@rollup/plugin-commonjs' 9 | import terser from '@rollup/plugin-terser' 10 | import replace from '@rollup/plugin-replace' 11 | 12 | const packageJson = JSON.parse( 13 | fs.readFileSync(path.join(import.meta.dirname, 'package.json'), 'utf-8'), 14 | ) 15 | 16 | const replacePlugin = replace({ 17 | preventAssignment: true, 18 | values: { 19 | __VERSION__: `'${packageJson.version}'`, 20 | }, 21 | }) 22 | 23 | export default [ 24 | { 25 | input: 'src/main.ts', 26 | output: { 27 | file: './dist/main-umd.cjs', 28 | name: 'normalize', 29 | format: 'umd', 30 | sourcemap: true, 31 | }, 32 | plugins: [ 33 | typescript(), 34 | replacePlugin, 35 | resolve({ browser: true }), 36 | commonjs(), 37 | terser(), 38 | ], 39 | }, 40 | { 41 | input: 'src/main.ts', 42 | output: { 43 | file: './dist/main-esm.mjs', 44 | name: 'normalize', 45 | format: 'esm', 46 | sourcemap: true, 47 | }, 48 | plugins: [ 49 | typescript(), 50 | replacePlugin, 51 | resolve({ browser: true }), 52 | commonjs(), 53 | terser(), 54 | ], 55 | }, 56 | { 57 | input: 'src/main-node.ts', 58 | external: [ 59 | '@geolonia/japanese-numeral', 60 | '@geolonia/japanese-addresses-v2', 61 | 'papaparse', 62 | 'undici', 63 | 'lru-cache', 64 | 'node:fs', 65 | ], 66 | output: { 67 | file: './dist/main-node-esm.mjs', 68 | format: 'esm', 69 | sourcemap: true, 70 | }, 71 | plugins: [typescript(), replacePlugin], 72 | }, 73 | { 74 | input: 'src/main-node.ts', 75 | external: [ 76 | '@geolonia/japanese-numeral', 77 | '@geolonia/japanese-addresses-v2', 78 | 'papaparse', 79 | 'undici', 80 | 'lru-cache', 81 | 'node:fs', 82 | ], 83 | output: { 84 | file: './dist/main-node-cjs.cjs', 85 | format: 'cjs', 86 | sourcemap: true, 87 | }, 88 | plugins: [typescript(), replacePlugin], 89 | }, 90 | { 91 | input: 'dist/main.d.ts', 92 | output: [ 93 | { 94 | file: './dist/main.d.ts', 95 | format: 'es', 96 | }, 97 | ], 98 | plugins: [dts()], 99 | }, 100 | { 101 | input: 'dist/main-node.d.ts', 102 | output: [ 103 | { 104 | file: './dist/main-node.d.ts', 105 | format: 'es', 106 | }, 107 | ], 108 | plugins: [ 109 | dts(), 110 | del({ 111 | targets: ['dist/**/*.d.ts', '!dist/main.d.ts', '!dist/main-node.d.ts'], 112 | hook: 'buildEnd', 113 | }), 114 | ], 115 | }, 116 | ] 117 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | import { normalize } from './main-node' 2 | 3 | async function main(args: string[]) { 4 | let options = {} 5 | if (args.length > 1) { 6 | options = JSON.parse(args[1]) 7 | } 8 | const result = await normalize(args[0], options) 9 | console.log(JSON.stringify(result, null, 2)) 10 | } 11 | 12 | main(process.argv.slice(2)) 13 | .then(() => { 14 | process.exit(0) 15 | }) 16 | .catch((e) => { 17 | console.error(e) 18 | process.exit(1) 19 | }) 20 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from './normalize' 2 | 3 | // export const defaultEndpoint = 'http://localhost:8080/api/ja' 4 | export const defaultEndpoint = 5 | 'https://japanese-addresses-v2.geoloniamaps.com/api/ja' 6 | 7 | export const currentConfig: Config = { 8 | japaneseAddressesApi: defaultEndpoint, 9 | cacheSize: 1_000, 10 | } 11 | 12 | export type FetchOptions = { 13 | offset?: number 14 | length?: number 15 | } 16 | 17 | export type FetchResponseLike = { 18 | json: () => Promise 19 | text: () => Promise 20 | ok: boolean 21 | } 22 | 23 | export type FetchLike = ( 24 | input: string, 25 | options?: FetchOptions, 26 | ) => Promise 27 | 28 | /** 29 | * @internal 30 | */ 31 | export const __internals: { fetch: FetchLike } = { 32 | // default fetch 33 | fetch: (input: string, options) => { 34 | const o = options || {} 35 | let url = new URL( 36 | `${currentConfig.japaneseAddressesApi}${input}`, 37 | ).toString() 38 | if (currentConfig.geoloniaApiKey) { 39 | url += `?geolonia-api-key=${currentConfig.geoloniaApiKey}` 40 | } 41 | const headers: HeadersInit = {} 42 | if (typeof o.length !== 'undefined' && typeof o.offset !== 'undefined') { 43 | headers['Range'] = `bytes=${o.offset}-${o.offset + o.length - 1}` 44 | } 45 | let globalFetch: typeof fetch 46 | if (typeof fetch !== 'undefined') { 47 | globalFetch = fetch 48 | } else if (typeof window !== 'undefined') { 49 | globalFetch = window.fetch 50 | } else { 51 | throw new Error('fetch is not available in this environment') 52 | } 53 | return globalFetch(url, { 54 | headers, 55 | }) 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /src/lib/cacheRegexes.ts: -------------------------------------------------------------------------------- 1 | import { toRegexPattern } from './dict' 2 | import { kan2num } from './kan2num' 3 | import Papaparse from 'papaparse' 4 | import { LRUCache } from 'lru-cache' 5 | import { currentConfig, __internals } from '../config' 6 | import { findKanjiNumbers, kanji2number } from '@geolonia/japanese-numeral' 7 | import { 8 | cityName, 9 | LngLat, 10 | MachiAzaApi, 11 | machiAzaName, 12 | PrefectureApi, 13 | prefectureName, 14 | SingleChiban, 15 | SingleCity, 16 | SingleMachiAza, 17 | SinglePrefecture, 18 | SingleRsdt, 19 | } from '@geolonia/japanese-addresses-v2' 20 | 21 | export type PrefectureList = PrefectureApi 22 | // interface SingleTown { 23 | // town: string 24 | // originalTown?: string 25 | // koaza: string 26 | // lat: string 27 | // lng: string 28 | // } 29 | type SingleTown = SingleMachiAza 30 | export type TownList = MachiAzaApi 31 | interface SingleAddr { 32 | addr: string 33 | lat: string | null 34 | lng: string | null 35 | } 36 | export type AddrList = SingleAddr[] 37 | 38 | const cache = new LRUCache({ 39 | max: currentConfig.cacheSize, 40 | }) 41 | 42 | // eslint-disable-next-line @typescript-eslint/no-empty-object-type 43 | async function fetchFromCache( 44 | key: string, 45 | fetcher: () => Promise, 46 | ): Promise { 47 | let data = cache.get(key) as T | undefined 48 | if (typeof data !== 'undefined') { 49 | return data 50 | } 51 | data = await fetcher() 52 | cache.set(key, data) 53 | return data 54 | } 55 | 56 | let cachedPrefecturePatterns: [SinglePrefecture, string][] | undefined = 57 | undefined 58 | const cachedCityPatterns: Map = new Map() 59 | let cachedPrefectures: PrefectureList | undefined = undefined 60 | const cachedTowns: { [key: string]: TownList } = {} 61 | let cachedSameNamedPrefectureCityRegexPatterns: [string, string][] | undefined = 62 | undefined 63 | 64 | export const getPrefectures = async () => { 65 | if (typeof cachedPrefectures !== 'undefined') { 66 | return cachedPrefectures 67 | } 68 | 69 | const prefsResp = await __internals.fetch('.json', {}) // ja.json 70 | const data = (await prefsResp.json()) as PrefectureApi 71 | return cachePrefectures(data) 72 | } 73 | 74 | export const cachePrefectures = (data: PrefectureList) => { 75 | return (cachedPrefectures = data) 76 | } 77 | 78 | export const getPrefectureRegexPatterns = (api: PrefectureApi) => { 79 | if (cachedPrefecturePatterns) { 80 | return cachedPrefecturePatterns 81 | } 82 | 83 | const data = api.data 84 | cachedPrefecturePatterns = data.map<[SinglePrefecture, string]>((pref) => { 85 | const _pref = pref.pref.replace(/(都|道|府|県)$/, '') // `東京` の様に末尾の `都府県` が抜けた住所に対応 86 | const pattern = `^${_pref}(都|道|府|県)?` 87 | return [pref, pattern] 88 | }) 89 | 90 | return cachedPrefecturePatterns 91 | } 92 | 93 | export const getCityRegexPatterns = (pref: SinglePrefecture) => { 94 | const cachedResult = cachedCityPatterns.get(pref.code) 95 | if (typeof cachedResult !== 'undefined') { 96 | return cachedResult 97 | } 98 | 99 | const cities = pref.cities 100 | // 少ない文字数の地名に対してミスマッチしないように文字の長さ順にソート 101 | cities.sort((a, b) => { 102 | return cityName(a).length - cityName(b).length 103 | }) 104 | 105 | const patterns = cities.map<[SingleCity, string]>((city) => { 106 | const name = cityName(city) 107 | let pattern = `^${toRegexPattern(name)}` 108 | if (name.match(/(町|村)$/)) { 109 | pattern = `^${toRegexPattern(name).replace(/(.+?)郡/, '($1郡)?')}` // 郡が省略されてるかも 110 | } 111 | return [city, pattern] 112 | }) 113 | 114 | cachedCityPatterns.set(pref.code, patterns) 115 | return patterns 116 | } 117 | 118 | export const getTowns = async ( 119 | prefObj: SinglePrefecture, 120 | cityObj: SingleCity, 121 | apiVersion: number, 122 | ) => { 123 | const pref = prefectureName(prefObj) 124 | const city = cityName(cityObj) 125 | 126 | const cacheKey = `${pref}-${city}` 127 | const cachedTown = cachedTowns[cacheKey] 128 | if (typeof cachedTown !== 'undefined') { 129 | return cachedTown 130 | } 131 | 132 | const townsResp = await __internals.fetch( 133 | ['', encodeURI(pref), encodeURI(city) + `.json?v=${apiVersion}`].join('/'), 134 | {}, 135 | ) 136 | const towns = (await townsResp.json()) as MachiAzaApi 137 | return (cachedTowns[cacheKey] = towns) 138 | } 139 | 140 | type MetadataRow = { start: number; length: number } 141 | 142 | async function fetchSubresource( 143 | kind: '地番' | '住居表示', 144 | pref: SinglePrefecture, 145 | city: SingleCity, 146 | row: MetadataRow, 147 | apiVersion: number, 148 | ) { 149 | const prefN = prefectureName(pref) 150 | const cityN = cityName(city) 151 | const resp = await __internals.fetch( 152 | [ 153 | '', 154 | encodeURI(prefN), 155 | encodeURI(`${cityN}-${kind}.txt?v=${apiVersion}`), 156 | ].join('/'), 157 | { 158 | offset: row.start, 159 | length: row.length, 160 | }, 161 | ) 162 | return resp.text() 163 | } 164 | 165 | type RsdtDataRow = { 166 | blk_num: string 167 | rsdt_num: string 168 | rsdt_num2: string 169 | lng: string 170 | lat: string 171 | } 172 | type ChibanDataRow = { 173 | prc_num1: string 174 | prc_num2: string 175 | prc_num3: string 176 | lng: string 177 | lat: string 178 | } 179 | function parseSubresource( 180 | data: string, 181 | ): T[] { 182 | const firstLineEnd = data.indexOf('\n') 183 | // const firstLine = data.slice(0, firstLineEnd) 184 | const rest = data.slice(firstLineEnd + 1) 185 | const lines = Papaparse.parse(rest, { 186 | header: true, 187 | }).data 188 | const out: T[] = [] 189 | for (const line of lines) { 190 | const point: LngLat | undefined = 191 | line.lng && line.lat 192 | ? [parseFloat(line.lng), parseFloat(line.lat)] 193 | : undefined 194 | if ('blk_num' in line) { 195 | out.push({ 196 | blk_num: line.blk_num, 197 | rsdt_num: line.rsdt_num, 198 | rsdt_num2: line.rsdt_num2, 199 | point: point, 200 | } as T) 201 | } else if ('prc_num1' in line) { 202 | out.push({ 203 | prc_num1: line.prc_num1, 204 | prc_num2: line.prc_num2, 205 | prc_num3: line.prc_num3, 206 | point: point, 207 | } as T) 208 | } 209 | } 210 | return out 211 | } 212 | 213 | export const getRsdt = async ( 214 | pref: SinglePrefecture, 215 | city: SingleCity, 216 | town: SingleTown, 217 | apiVersion: number, 218 | ) => { 219 | const row = town.csv_ranges?.住居表示 220 | if (!row) { 221 | return [] 222 | } 223 | 224 | const parsed = await fetchFromCache( 225 | `住居表示-${pref.code}-${city.code}-${machiAzaName(town)}`, 226 | async () => { 227 | const data = await fetchSubresource( 228 | '住居表示', 229 | pref, 230 | city, 231 | row, 232 | apiVersion, 233 | ) 234 | const parsed = parseSubresource(data) 235 | parsed.sort((a, b) => { 236 | const aStr = [a.blk_num, a.rsdt_num, a.rsdt_num2] 237 | .filter((a) => !!a) 238 | .join('-') 239 | const bStr = [b.blk_num, b.rsdt_num, b.rsdt_num2] 240 | .filter((a) => !!a) 241 | .join('-') 242 | return bStr.length - aStr.length 243 | }) 244 | return parsed 245 | }, 246 | ) 247 | return parsed 248 | } 249 | 250 | export const getChiban = async ( 251 | pref: SinglePrefecture, 252 | city: SingleCity, 253 | town: SingleTown, 254 | apiVersion: number, 255 | ) => { 256 | const row = town.csv_ranges?.地番 257 | if (!row) { 258 | return [] 259 | } 260 | 261 | const parsed = await fetchFromCache( 262 | `地番-${pref.code}-${city.code}-${machiAzaName(town)}`, 263 | async () => { 264 | const data = await fetchSubresource('地番', pref, city, row, apiVersion) 265 | const parsed = parseSubresource(data) 266 | parsed.sort((a, b) => { 267 | const aStr = [a.prc_num1, a.prc_num2, a.prc_num3] 268 | .filter((a) => !!a) 269 | .join('-') 270 | const bStr = [b.prc_num1, b.prc_num2, b.prc_num3] 271 | .filter((a) => !!a) 272 | .join('-') 273 | return bStr.length - aStr.length 274 | }) 275 | return parsed 276 | }, 277 | ) 278 | 279 | return parsed 280 | } 281 | 282 | // 十六町 のように漢数字と町が連結しているか 283 | const isKanjiNumberFollewedByCho = (targetTownName: string) => { 284 | const xCho = targetTownName.match(/.町/g) 285 | if (!xCho) return false 286 | const kanjiNumbers = findKanjiNumbers(xCho[0]) 287 | return kanjiNumbers.length > 0 288 | } 289 | 290 | export const getTownRegexPatterns = async ( 291 | pref: SinglePrefecture, 292 | city: SingleCity, 293 | apiVersion: number, 294 | ) => 295 | fetchFromCache<[SingleTown, string][]>( 296 | `${pref.code}-${city.code}`, 297 | async () => { 298 | const api = await getTowns(pref, city, apiVersion) 299 | const pre_towns = api.data 300 | const townSet = new Set(pre_towns.map((town) => machiAzaName(town))) 301 | const towns: ( 302 | | SingleMachiAza 303 | | (SingleMachiAza & { originalTown: SingleMachiAza }) 304 | )[] = [] 305 | 306 | const isKyoto = city.city === '京都市' 307 | 308 | // 町丁目に「○○町」が含まれるケースへの対応 309 | // 通常は「○○町」のうち「町」の省略を許容し同義語として扱うが、まれに自治体内に「○○町」と「○○」が共存しているケースがある。 310 | // この場合は町の省略は許容せず、入力された住所は書き分けられているものとして正規化を行う。 311 | // 更に、「愛知県名古屋市瑞穂区十六町1丁目」漢数字を含むケースだと丁目や番地・号の正規化が不可能になる。このようなケースも除外。 312 | for (const town of pre_towns) { 313 | towns.push(town) 314 | 315 | const originalTown = machiAzaName(town) 316 | if (originalTown.indexOf('町') === -1) continue 317 | const townAbbr = originalTown.replace(/(?!^町)町/g, '') // NOTE: 冒頭の「町」は明らかに省略するべきではないので、除外 318 | if ( 319 | !isKyoto && // 京都は通り名削除の処理があるため、意図しないマッチになるケースがある。これを除く 320 | !townSet.has(townAbbr) && 321 | !townSet.has(`大字${townAbbr}`) && // 大字は省略されるため、大字〇〇と〇〇町がコンフリクトする。このケースを除外 322 | !isKanjiNumberFollewedByCho(originalTown) 323 | ) { 324 | // エイリアスとして町なしのパターンを登録 325 | towns.push({ 326 | machiaza_id: town.machiaza_id, 327 | point: town.point, 328 | oaza_cho: townAbbr, 329 | originalTown: town, 330 | }) 331 | } 332 | } 333 | 334 | // 少ない文字数の地名に対してミスマッチしないように文字の長さ順にソート 335 | towns.sort((a, b) => { 336 | let aLen = machiAzaName(a).length 337 | let bLen = machiAzaName(b).length 338 | 339 | // 大字で始まる場合、優先度を低く設定する。 340 | // 大字XX と XXYY が存在するケースもあるので、 XXYY を先にマッチしたい 341 | if (machiAzaName(a).startsWith('大字')) aLen -= 2 342 | if (machiAzaName(b).startsWith('大字')) bLen -= 2 343 | 344 | return bLen - aLen 345 | }) 346 | 347 | const patterns = towns.map<[SingleMachiAza, string]>((town) => { 348 | const pattern = toRegexPattern( 349 | machiAzaName(town) 350 | // 横棒を含む場合(流通センター、など)に対応 351 | .replace(/[--﹣−‐⁃‑‒–—﹘―⎯⏤ーー─━]/g, '[--﹣−‐⁃‑‒–—﹘―⎯⏤ーー─━]') 352 | .replace(/大?字/g, '(大?字)?') 353 | // 以下住所マスターの町丁目に含まれる数字を正規表現に変換する 354 | // ABRデータには大文字の数字が含まれている(第1地割、など)ので、数字も一致するようにする 355 | .replace( 356 | /([壱一二三四五六七八九十]+|[1234567890]+)(丁目?|番(町|丁)|条|軒|線|(の|ノ)町|地割|号)/g, 357 | (match: string) => { 358 | const patterns = [] 359 | 360 | patterns.push( 361 | match 362 | .toString() 363 | .replace( 364 | /(丁目?|番(町|丁)|条|軒|線|(の|ノ)町|地割|号)/, 365 | '', 366 | ), 367 | ) // 漢数字 368 | 369 | if (match.match(/^壱/)) { 370 | patterns.push('一') 371 | patterns.push('1') 372 | patterns.push('1') 373 | } else { 374 | const num = match 375 | .replace(/([一二三四五六七八九十]+)/g, (match) => { 376 | return kan2num(match) 377 | }) 378 | .replace(/([1234567890]+)/g, (match) => { 379 | return kanji2number(match).toString() 380 | }) 381 | .replace(/(丁目?|番(町|丁)|条|軒|線|(の|ノ)町|地割|号)/, '') 382 | 383 | patterns.push(num.toString()) // 半角アラビア数字 384 | } 385 | 386 | // 以下の正規表現は、上のよく似た正規表現とは違うことに注意! 387 | const _pattern = `(${patterns.join( 388 | '|', 389 | )})((丁|町)目?|番(町|丁)|条|軒|線|の町?|地割|号|[--﹣−‐⁃‑‒–—﹘―⎯⏤ーー─━])` 390 | // if (city === '下閉伊郡普代村' && town.machiaza_id === '0022000') { 391 | // console.log(_pattern) 392 | // } 393 | return _pattern // デバッグのときにめんどくさいので変数に入れる。 394 | }, 395 | ), 396 | ) 397 | return ['originalTown' in town ? town.originalTown : town, pattern] 398 | }) 399 | 400 | // X丁目の丁目なしの数字だけ許容するため、最後に数字だけ追加していく 401 | for (const town of towns) { 402 | const chomeMatch = machiAzaName(town).match( 403 | /([^一二三四五六七八九十]+)([一二三四五六七八九十]+)(丁目?)/, 404 | ) 405 | if (!chomeMatch) { 406 | continue 407 | } 408 | const chomeNamePart = chomeMatch[1] 409 | const chomeNum = chomeMatch[2] 410 | const pattern = toRegexPattern( 411 | `^${chomeNamePart}(${chomeNum}|${kan2num(chomeNum)})`, 412 | ) 413 | patterns.push([town, pattern]) 414 | } 415 | 416 | return patterns 417 | }, 418 | ) 419 | 420 | export const getSameNamedPrefectureCityRegexPatterns = ( 421 | prefApi: PrefectureApi, 422 | ) => { 423 | if (typeof cachedSameNamedPrefectureCityRegexPatterns !== 'undefined') { 424 | return cachedSameNamedPrefectureCityRegexPatterns 425 | } 426 | 427 | const prefList = prefApi.data 428 | const _prefs = prefList.map((pref) => { 429 | return pref.pref.replace(/[都|道|府|県]$/, '') 430 | }) 431 | 432 | cachedSameNamedPrefectureCityRegexPatterns = [] 433 | for (const pref of prefList) { 434 | for (const city of pref.cities) { 435 | const cityN = cityName(city) 436 | 437 | // 「福島県石川郡石川町」のように、市の名前が別の都道府県名から始まっているケースも考慮する。 438 | for (let j = 0; j < _prefs.length; j++) { 439 | if (cityN.indexOf(_prefs[j]) === 0) { 440 | cachedSameNamedPrefectureCityRegexPatterns.push([ 441 | `${pref.pref}${cityN}`, 442 | `^${cityN}`, 443 | ]) 444 | } 445 | } 446 | } 447 | } 448 | 449 | return cachedSameNamedPrefectureCityRegexPatterns 450 | } 451 | -------------------------------------------------------------------------------- /src/lib/dict.ts: -------------------------------------------------------------------------------- 1 | import { convert } from './dictionaries/convert' 2 | 3 | export const toRegexPattern = (string: string) => { 4 | let _str = string 5 | 6 | // 以下なるべく文字数が多いものほど上にすること 7 | _str = _str 8 | .replace(/三栄町|四谷三栄町/g, '(三栄町|四谷三栄町)') 9 | .replace(/鬮野川|くじ野川|くじの川/g, '(鬮野川|くじ野川|くじの川)') 10 | .replace(/柿碕町|柿さき町/g, '(柿碕町|柿さき町)') 11 | .replace(/通り|とおり/g, '(通り|とおり)') 12 | .replace(/埠頭|ふ頭/g, '(埠頭|ふ頭)') 13 | .replace(/番町|番丁/g, '(番町|番丁)') 14 | .replace(/大冝|大宜/g, '(大冝|大宜)') 15 | .replace(/穝|さい/g, '(穝|さい)') 16 | .replace(/杁|えぶり/g, '(杁|えぶり)') 17 | .replace(/薭|稗|ひえ|ヒエ/g, '(薭|稗|ひえ|ヒエ)') 18 | .replace(/[之ノの]/g, '[之ノの]') 19 | .replace(/[ヶケが]/g, '[ヶケが]') 20 | .replace(/[ヵカか力]/g, '[ヵカか力]') 21 | .replace(/[ッツっつ]/g, '[ッツっつ]') 22 | .replace(/[ニ二]/g, '[ニ二]') 23 | .replace(/[ハ八]/g, '[ハ八]') 24 | .replace(/塚|塚/g, '(塚|塚)') 25 | .replace(/釜|竈/g, '(釜|竈)') 26 | .replace(/條|条/g, '(條|条)') 27 | .replace(/狛|拍/g, '(狛|拍)') 28 | .replace(/藪|薮/g, '(藪|薮)') 29 | .replace(/渕|淵/g, '(渕|淵)') 30 | .replace(/エ|ヱ|え/g, '(エ|ヱ|え)') 31 | .replace(/曾|曽/g, '(曾|曽)') 32 | .replace(/舟|船/g, '(舟|船)') 33 | .replace(/莵|菟/g, '(莵|菟)') 34 | .replace(/市|巿/g, '(市|巿)') 35 | .replace(/崎|﨑/g, '(崎|﨑)') 36 | 37 | _str = convert(_str) 38 | 39 | return _str 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/dictionaries/convert.ts: -------------------------------------------------------------------------------- 1 | import { dictionary } from './dictionary' 2 | 3 | type PatternMap = { [key: string]: string } 4 | const patternMap = dictionary.reduce((acc: PatternMap, dictionary) => { 5 | const pattern = `(${dictionary.src}|${dictionary.dst})` 6 | // { 亞: '(亞|亜)', 亜: '(亞|亜)', 圍: '(圍|囲)', 囲: '(圍|囲)', ...} 7 | return { ...acc, [dictionary.src]: pattern, [dictionary.dst]: pattern } 8 | }, {}) 9 | 10 | const regexp = new RegExp( 11 | Array.from(new Set(Object.values(patternMap))).join('|'), 12 | 'g', 13 | ) 14 | 15 | export const convert = (regexText: string) => 16 | regexText.replace(regexp, (match) => patternMap[match]) 17 | -------------------------------------------------------------------------------- /src/lib/dictionaries/dictionary.ts: -------------------------------------------------------------------------------- 1 | import { jisDai2Dictionary } from './jisDai2' 2 | 3 | export type Dictionary = { src: string; dst: string } 4 | 5 | export const dictionary = [ 6 | jisDai2Dictionary, 7 | // Add more dictionary here 8 | // exmapleDictionary, 9 | ].flat() 10 | -------------------------------------------------------------------------------- /src/lib/dictionaries/jisDai2.ts: -------------------------------------------------------------------------------- 1 | import { Dictionary } from './dictionary' 2 | 3 | // JIS 第2水準 => 第1水準 及び 旧字体 => 新字体 4 | export const jisDai2Dictionary: Dictionary[] = [ 5 | { src: '亞', dst: '亜' }, 6 | { src: '圍', dst: '囲' }, 7 | { src: '壹', dst: '壱' }, 8 | { src: '榮', dst: '栄' }, 9 | { src: '驛', dst: '駅' }, 10 | { src: '應', dst: '応' }, 11 | { src: '櫻', dst: '桜' }, 12 | { src: '假', dst: '仮' }, 13 | { src: '會', dst: '会' }, 14 | { src: '懷', dst: '懐' }, 15 | { src: '覺', dst: '覚' }, 16 | { src: '樂', dst: '楽' }, 17 | { src: '陷', dst: '陥' }, 18 | { src: '歡', dst: '歓' }, 19 | { src: '氣', dst: '気' }, 20 | { src: '戲', dst: '戯' }, 21 | { src: '據', dst: '拠' }, 22 | { src: '挾', dst: '挟' }, 23 | { src: '區', dst: '区' }, 24 | { src: '徑', dst: '径' }, 25 | { src: '溪', dst: '渓' }, 26 | { src: '輕', dst: '軽' }, 27 | { src: '藝', dst: '芸' }, 28 | { src: '儉', dst: '倹' }, 29 | { src: '圈', dst: '圏' }, 30 | { src: '權', dst: '権' }, 31 | { src: '嚴', dst: '厳' }, 32 | { src: '恆', dst: '恒' }, 33 | { src: '國', dst: '国' }, 34 | { src: '齋', dst: '斎' }, 35 | { src: '雜', dst: '雑' }, 36 | { src: '蠶', dst: '蚕' }, 37 | { src: '殘', dst: '残' }, 38 | { src: '兒', dst: '児' }, 39 | { src: '實', dst: '実' }, 40 | { src: '釋', dst: '釈' }, 41 | { src: '從', dst: '従' }, 42 | { src: '縱', dst: '縦' }, 43 | { src: '敍', dst: '叙' }, 44 | { src: '燒', dst: '焼' }, 45 | { src: '條', dst: '条' }, 46 | { src: '剩', dst: '剰' }, 47 | { src: '壤', dst: '壌' }, 48 | { src: '釀', dst: '醸' }, 49 | { src: '眞', dst: '真' }, 50 | { src: '盡', dst: '尽' }, 51 | { src: '醉', dst: '酔' }, 52 | { src: '髓', dst: '髄' }, 53 | { src: '聲', dst: '声' }, 54 | { src: '竊', dst: '窃' }, 55 | { src: '淺', dst: '浅' }, 56 | { src: '錢', dst: '銭' }, 57 | { src: '禪', dst: '禅' }, 58 | { src: '爭', dst: '争' }, 59 | { src: '插', dst: '挿' }, 60 | { src: '騷', dst: '騒' }, 61 | { src: '屬', dst: '属' }, 62 | { src: '對', dst: '対' }, 63 | { src: '滯', dst: '滞' }, 64 | { src: '擇', dst: '択' }, 65 | { src: '單', dst: '単' }, 66 | { src: '斷', dst: '断' }, 67 | { src: '癡', dst: '痴' }, 68 | { src: '鑄', dst: '鋳' }, 69 | { src: '敕', dst: '勅' }, 70 | { src: '鐵', dst: '鉄' }, 71 | { src: '傳', dst: '伝' }, 72 | { src: '黨', dst: '党' }, 73 | { src: '鬪', dst: '闘' }, 74 | { src: '屆', dst: '届' }, 75 | { src: '腦', dst: '脳' }, 76 | { src: '廢', dst: '廃' }, 77 | { src: '發', dst: '発' }, 78 | { src: '蠻', dst: '蛮' }, 79 | { src: '拂', dst: '払' }, 80 | { src: '邊', dst: '辺' }, 81 | { src: '瓣', dst: '弁' }, 82 | { src: '寶', dst: '宝' }, 83 | { src: '沒', dst: '没' }, 84 | { src: '滿', dst: '満' }, 85 | { src: '藥', dst: '薬' }, 86 | { src: '餘', dst: '余' }, 87 | { src: '樣', dst: '様' }, 88 | { src: '亂', dst: '乱' }, 89 | { src: '兩', dst: '両' }, 90 | { src: '禮', dst: '礼' }, 91 | { src: '靈', dst: '霊' }, 92 | { src: '爐', dst: '炉' }, 93 | { src: '灣', dst: '湾' }, 94 | { src: '惡', dst: '悪' }, 95 | { src: '醫', dst: '医' }, 96 | { src: '飮', dst: '飲' }, 97 | { src: '營', dst: '営' }, 98 | { src: '圓', dst: '円' }, 99 | { src: '歐', dst: '欧' }, 100 | { src: '奧', dst: '奥' }, 101 | { src: '價', dst: '価' }, 102 | { src: '繪', dst: '絵' }, 103 | { src: '擴', dst: '拡' }, 104 | { src: '學', dst: '学' }, 105 | { src: '罐', dst: '缶' }, 106 | { src: '勸', dst: '勧' }, 107 | { src: '觀', dst: '観' }, 108 | { src: '歸', dst: '帰' }, 109 | { src: '犧', dst: '犠' }, 110 | { src: '擧', dst: '挙' }, 111 | { src: '狹', dst: '狭' }, 112 | { src: '驅', dst: '駆' }, 113 | { src: '莖', dst: '茎' }, 114 | { src: '經', dst: '経' }, 115 | { src: '繼', dst: '継' }, 116 | { src: '缺', dst: '欠' }, 117 | { src: '劍', dst: '剣' }, 118 | { src: '檢', dst: '検' }, 119 | { src: '顯', dst: '顕' }, 120 | { src: '廣', dst: '広' }, 121 | { src: '鑛', dst: '鉱' }, 122 | { src: '碎', dst: '砕' }, 123 | { src: '劑', dst: '剤' }, 124 | { src: '參', dst: '参' }, 125 | { src: '慘', dst: '惨' }, 126 | { src: '絲', dst: '糸' }, 127 | { src: '辭', dst: '辞' }, 128 | { src: '舍', dst: '舎' }, 129 | { src: '壽', dst: '寿' }, 130 | { src: '澁', dst: '渋' }, 131 | { src: '肅', dst: '粛' }, 132 | { src: '將', dst: '将' }, 133 | { src: '證', dst: '証' }, 134 | { src: '乘', dst: '乗' }, 135 | { src: '疊', dst: '畳' }, 136 | { src: '孃', dst: '嬢' }, 137 | { src: '觸', dst: '触' }, 138 | { src: '寢', dst: '寝' }, 139 | { src: '圖', dst: '図' }, 140 | { src: '穗', dst: '穂' }, 141 | { src: '樞', dst: '枢' }, 142 | { src: '齊', dst: '斉' }, 143 | { src: '攝', dst: '摂' }, 144 | { src: '戰', dst: '戦' }, 145 | { src: '潛', dst: '潜' }, 146 | { src: '雙', dst: '双' }, 147 | { src: '莊', dst: '荘' }, 148 | { src: '裝', dst: '装' }, 149 | { src: '藏', dst: '蔵' }, 150 | { src: '續', dst: '続' }, 151 | { src: '體', dst: '体' }, 152 | { src: '臺', dst: '台' }, 153 | { src: '澤', dst: '沢' }, 154 | { src: '膽', dst: '胆' }, 155 | { src: '彈', dst: '弾' }, 156 | { src: '蟲', dst: '虫' }, 157 | { src: '廳', dst: '庁' }, 158 | { src: '鎭', dst: '鎮' }, 159 | { src: '點', dst: '点' }, 160 | { src: '燈', dst: '灯' }, 161 | { src: '盜', dst: '盗' }, 162 | { src: '獨', dst: '独' }, 163 | { src: '貳', dst: '弐' }, 164 | { src: '霸', dst: '覇' }, 165 | { src: '賣', dst: '売' }, 166 | { src: '髮', dst: '髪' }, 167 | { src: '祕', dst: '秘' }, 168 | { src: '佛', dst: '仏' }, 169 | { src: '變', dst: '変' }, 170 | { src: '辯', dst: '弁' }, 171 | { src: '豐', dst: '豊' }, 172 | { src: '飜', dst: '翻' }, 173 | { src: '默', dst: '黙' }, 174 | { src: '與', dst: '与' }, 175 | { src: '譽', dst: '誉' }, 176 | { src: '謠', dst: '謡' }, 177 | { src: '覽', dst: '覧' }, 178 | { src: '獵', dst: '猟' }, 179 | { src: '勵', dst: '励' }, 180 | { src: '齡', dst: '齢' }, 181 | { src: '勞', dst: '労' }, 182 | { src: '壓', dst: '圧' }, 183 | { src: '爲', dst: '為' }, 184 | { src: '隱', dst: '隠' }, 185 | { src: '衞', dst: '衛' }, 186 | { src: '鹽', dst: '塩' }, 187 | { src: '毆', dst: '殴' }, 188 | { src: '穩', dst: '穏' }, 189 | { src: '畫', dst: '画' }, 190 | { src: '壞', dst: '壊' }, 191 | { src: '殼', dst: '殻' }, 192 | { src: '嶽', dst: '岳' }, 193 | { src: '卷', dst: '巻' }, 194 | { src: '關', dst: '関' }, 195 | { src: '顏', dst: '顔' }, 196 | { src: '僞', dst: '偽' }, 197 | { src: '舊', dst: '旧' }, 198 | { src: '峽', dst: '峡' }, 199 | { src: '曉', dst: '暁' }, 200 | { src: '勳', dst: '勲' }, 201 | { src: '惠', dst: '恵' }, 202 | { src: '螢', dst: '蛍' }, 203 | { src: '鷄', dst: '鶏' }, 204 | { src: '縣', dst: '県' }, 205 | { src: '險', dst: '険' }, 206 | { src: '獻', dst: '献' }, 207 | { src: '驗', dst: '験' }, 208 | { src: '效', dst: '効' }, 209 | { src: '號', dst: '号' }, 210 | { src: '濟', dst: '済' }, 211 | { src: '册', dst: '冊' }, 212 | { src: '棧', dst: '桟' }, 213 | { src: '贊', dst: '賛' }, 214 | { src: '齒', dst: '歯' }, 215 | { src: '濕', dst: '湿' }, 216 | { src: '寫', dst: '写' }, 217 | { src: '收', dst: '収' }, 218 | { src: '獸', dst: '獣' }, 219 | { src: '處', dst: '処' }, 220 | { src: '稱', dst: '称' }, 221 | { src: '奬', dst: '奨' }, 222 | { src: '淨', dst: '浄' }, 223 | { src: '繩', dst: '縄' }, 224 | { src: '讓', dst: '譲' }, 225 | { src: '囑', dst: '嘱' }, 226 | { src: '愼', dst: '慎' }, 227 | { src: '粹', dst: '粋' }, 228 | { src: '隨', dst: '随' }, 229 | { src: '數', dst: '数' }, 230 | { src: '靜', dst: '静' }, 231 | { src: '專', dst: '専' }, 232 | { src: '踐', dst: '践' }, 233 | { src: '纖', dst: '繊' }, 234 | { src: '壯', dst: '壮' }, 235 | { src: '搜', dst: '捜' }, 236 | { src: '總', dst: '総' }, 237 | { src: '臟', dst: '臓' }, 238 | { src: '墮', dst: '堕' }, 239 | { src: '帶', dst: '帯' }, 240 | { src: '瀧', dst: '滝' }, 241 | { src: '擔', dst: '担' }, 242 | { src: '團', dst: '団' }, 243 | { src: '遲', dst: '遅' }, 244 | { src: '晝', dst: '昼' }, 245 | { src: '聽', dst: '聴' }, 246 | { src: '遞', dst: '逓' }, 247 | { src: '轉', dst: '転' }, 248 | { src: '當', dst: '当' }, 249 | { src: '稻', dst: '稲' }, 250 | { src: '讀', dst: '読' }, 251 | { src: '惱', dst: '悩' }, 252 | { src: '拜', dst: '拝' }, 253 | { src: '麥', dst: '麦' }, 254 | { src: '拔', dst: '抜' }, 255 | { src: '濱', dst: '浜' }, 256 | { src: '竝', dst: '並' }, 257 | { src: '辨', dst: '弁' }, 258 | { src: '舖', dst: '舗' }, 259 | { src: '襃', dst: '褒' }, 260 | { src: '萬', dst: '万' }, 261 | { src: '譯', dst: '訳' }, 262 | { src: '豫', dst: '予' }, 263 | { src: '搖', dst: '揺' }, 264 | { src: '來', dst: '来' }, 265 | { src: '龍', dst: '竜' }, 266 | { src: '壘', dst: '塁' }, 267 | { src: '隸', dst: '隷' }, 268 | { src: '戀', dst: '恋' }, 269 | { src: '樓', dst: '楼' }, 270 | { src: '鰺', dst: '鯵' }, 271 | { src: '鶯', dst: '鴬' }, 272 | { src: '蠣', dst: '蛎' }, 273 | { src: '攪', dst: '撹' }, 274 | { src: '竈', dst: '竃' }, 275 | { src: '灌', dst: '潅' }, 276 | { src: '諫', dst: '諌' }, 277 | { src: '頸', dst: '頚' }, 278 | { src: '礦', dst: '砿' }, 279 | { src: '蘂', dst: '蕊' }, 280 | { src: '靱', dst: '靭' }, 281 | { src: '賤', dst: '賎' }, 282 | { src: '壺', dst: '壷' }, 283 | { src: '礪', dst: '砺' }, 284 | { src: '檮', dst: '梼' }, 285 | { src: '濤', dst: '涛' }, 286 | { src: '邇', dst: '迩' }, 287 | { src: '蠅', dst: '蝿' }, 288 | { src: '檜', dst: '桧' }, 289 | { src: '儘', dst: '侭' }, 290 | { src: '藪', dst: '薮' }, 291 | { src: '籠', dst: '篭' }, 292 | { src: '彌', dst: '弥' }, 293 | { src: '麩', dst: '麸' }, 294 | { src: '驒', dst: '騨' }, 295 | ] 296 | -------------------------------------------------------------------------------- /src/lib/kan2num.ts: -------------------------------------------------------------------------------- 1 | import { kanji2number, findKanjiNumbers } from '@geolonia/japanese-numeral' 2 | 3 | export const kan2num = (string: string) => { 4 | const kanjiNumbers = findKanjiNumbers(string) 5 | for (let i = 0; i < kanjiNumbers.length; i++) { 6 | try { 7 | // @ts-ignore 8 | string = string.replace(kanjiNumbers[i], kanji2number(kanjiNumbers[i])) 9 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 10 | } catch (error) { 11 | // ignore 12 | } 13 | } 14 | 15 | return string 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/normalizeHelpers.ts: -------------------------------------------------------------------------------- 1 | import { zen2han } from './zen2han' 2 | 3 | /** 4 | * 入力された住所に対して以下の正規化を予め行う。 5 | * 6 | * 1. `1-2-3` や `四-五-六` のようなフォーマットのハイフンを半角に統一。 7 | * 2. 町丁目以前にあるスペースをすべて削除。 8 | * 3. 最初に出てくる `1-` や `五-` のような文字列を町丁目とみなして、それ以前のスペースをすべて削除する。 9 | */ 10 | export function prenormalize(input: string): string { 11 | return ( 12 | input 13 | .normalize('NFC') 14 | .replace(/ /g, ' ') 15 | .replace(/ +/g, ' ') 16 | .replace(/([0-9A-Za-z]+)/g, (match) => { 17 | // 全角のアラビア数字は問答無用で半角にする 18 | return zen2han(match) 19 | }) 20 | // 数字の後または数字の前にくる横棒はハイフンに統一する 21 | .replace( 22 | /([0-90-9一二三四五六七八九〇十百千][--﹣−‐⁃‑‒–—﹘―⎯⏤ーー─━])|([--﹣−‐⁃‑‒–—﹘―⎯⏤ーー─━])[0-90-9一二三四五六七八九〇十]/g, 23 | (match) => { 24 | return match.replace(/[--﹣−‐⁃‑‒–—﹘―⎯⏤ーー─━]/g, '-') 25 | }, 26 | ) 27 | .replace(/(.+)(丁目?|番(町|地|丁)|条|軒|線|(の|ノ)町|地割)/, (match) => { 28 | return match.replace(/ /g, '') // 町丁目名以前のスペースはすべて削除 29 | }) 30 | .replace(/(.+)((郡.+(町|村))|((市|巿).+(区|區)))/, (match) => { 31 | return match.replace(/ /g, '') // 区、郡以前のスペースはすべて削除 32 | }) 33 | .replace(/.+?[0-9一二三四五六七八九〇十百千]-/, (match) => { 34 | return match.replace(/ /g, '') // 1番はじめに出てくるアラビア数字以前のスペースを削除 35 | }) 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /src/lib/patchAddr.ts: -------------------------------------------------------------------------------- 1 | const addrPatches = [ 2 | { 3 | pref: '香川県', 4 | city: '仲多度郡まんのう町', 5 | town: '勝浦', 6 | pattern: '^字?家[6六]', 7 | result: '家六', 8 | }, 9 | { 10 | pref: '愛知県', 11 | city: 'あま市', 12 | town: '西今宿', 13 | pattern: '^字?梶村[1一]', 14 | result: '梶村一', 15 | }, 16 | { 17 | pref: '香川県', 18 | city: '丸亀市', 19 | town: '原田町', 20 | pattern: '^字?東三分[1一]', 21 | result: '東三分一', 22 | }, 23 | ] 24 | 25 | export const patchAddr = ( 26 | prefName: string, 27 | cityName: string, 28 | townName: string, 29 | addr: string, 30 | ): string => { 31 | let _addr = addr 32 | for (let i = 0; i < addrPatches.length; i++) { 33 | const patch = addrPatches[i] 34 | if ( 35 | patch.pref === prefName && 36 | patch.city === cityName && 37 | patch.town === townName 38 | ) { 39 | _addr = _addr.replace(new RegExp(patch.pattern), patch.result) 40 | } 41 | } 42 | 43 | return _addr 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SingleCity, 3 | SingleMachiAza, 4 | SinglePrefecture, 5 | } from '@geolonia/japanese-addresses-v2' 6 | 7 | export function removeCitiesFromPrefecture( 8 | pref: SinglePrefecture | undefined, 9 | ): Omit | undefined { 10 | if (!pref) { 11 | return undefined 12 | } 13 | 14 | const newPref: Omit & { cities?: SingleCity[] } = 15 | { 16 | ...pref, 17 | } 18 | delete newPref.cities 19 | return newPref 20 | } 21 | 22 | export function removeExtraFromMachiAza( 23 | machiAza: SingleMachiAza | undefined, 24 | ): Omit | undefined { 25 | if (!machiAza) { 26 | return undefined 27 | } 28 | 29 | const newMachiAza: SingleMachiAza = { ...machiAza } 30 | delete newMachiAza.csv_ranges 31 | return newMachiAza 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/zen2han.ts: -------------------------------------------------------------------------------- 1 | export const zen2han = (str: string) => { 2 | return str.replace(/[A-Za-z0-9]/g, (s) => { 3 | return String.fromCharCode(s.charCodeAt(0) - 0xfee0) 4 | }) 5 | } 6 | -------------------------------------------------------------------------------- /src/main-node.ts: -------------------------------------------------------------------------------- 1 | import * as Normalize from './normalize' 2 | import { __internals, FetchOptions, FetchResponseLike } from './config' 3 | import { promises as fs } from 'node:fs' 4 | import { fetch } from 'undici' 5 | 6 | export type { NormalizeResult } from './types' 7 | 8 | export const requestHandlers = { 9 | file: async (fileURL: URL, options?: FetchOptions) => { 10 | const o = options || {} 11 | const filePath = 12 | process.platform === 'win32' 13 | ? decodeURI(fileURL.pathname).substring(1) 14 | : decodeURI(fileURL.pathname) 15 | const f = await fs.open(filePath, 'r') 16 | let contents: Buffer, ok: boolean 17 | if (typeof o.length !== 'undefined' && typeof o.offset !== 'undefined') { 18 | contents = Buffer.alloc(o.length) 19 | const resp = await f.read(contents, 0, o.length, o.offset) 20 | ok = resp.bytesRead === o.length 21 | } else { 22 | contents = await f.readFile() 23 | ok = true 24 | } 25 | await f.close() 26 | return { 27 | json: async () => { 28 | return JSON.parse(contents.toString('utf-8')) 29 | }, 30 | text: async () => { 31 | return contents.toString('utf-8') 32 | }, 33 | ok, 34 | } 35 | }, 36 | http: (fileURL: URL, options?: FetchOptions) => { 37 | const o = options || {} 38 | if (Normalize.config.geoloniaApiKey) { 39 | fileURL.search = `?geolonia-api-key=${Normalize.config.geoloniaApiKey}` 40 | } 41 | const headers: HeadersInit = { 42 | 'User-Agent': 43 | 'normalize-japanese-addresses/0.1 (+https://github.com/geolonia/normalize-japanese-addresses/)', 44 | } 45 | if (typeof o.length !== 'undefined' && typeof o.offset !== 'undefined') { 46 | headers['Range'] = `bytes=${o.offset}-${o.offset + o.length - 1}` 47 | } 48 | return fetch(fileURL.toString(), { 49 | headers, 50 | }) 51 | }, 52 | } 53 | 54 | /** 55 | * 正規化のためのデータを取得する 56 | * @param input - Path part like '東京都/文京区.json' 57 | * @param requestOptions - input を構造化したデータ 58 | */ 59 | const fetchOrReadFile = async ( 60 | input: string, 61 | options?: FetchOptions, 62 | ): Promise => { 63 | const fileURL = new URL(`${Normalize.config.japaneseAddressesApi}${input}`) 64 | if (fileURL.protocol === 'http:' || fileURL.protocol === 'https:') { 65 | return requestHandlers.http(fileURL, options) 66 | } else if (fileURL.protocol === 'file:') { 67 | return requestHandlers.file(fileURL, options) 68 | } else { 69 | throw new Error(`Unknown URL schema: ${fileURL.protocol}`) 70 | } 71 | } 72 | 73 | __internals.fetch = fetchOrReadFile 74 | export const version = Normalize.version 75 | export const config = Normalize.config 76 | export const normalize = Normalize.normalize 77 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as Normalize from './normalize' 2 | 3 | export type { NormalizeResult } from './types' 4 | 5 | export const version = Normalize.version 6 | export const config = Normalize.config 7 | export const normalize = Normalize.normalize 8 | -------------------------------------------------------------------------------- /src/normalize.ts: -------------------------------------------------------------------------------- 1 | import { number2kanji } from '@geolonia/japanese-numeral' 2 | import { currentConfig } from './config' 3 | import { kan2num } from './lib/kan2num' 4 | import { zen2han } from './lib/zen2han' 5 | import { patchAddr } from './lib/patchAddr' 6 | import { 7 | getPrefectures, 8 | getPrefectureRegexPatterns, 9 | getCityRegexPatterns, 10 | getTownRegexPatterns, 11 | getSameNamedPrefectureCityRegexPatterns, 12 | getRsdt, 13 | getChiban, 14 | } from './lib/cacheRegexes' 15 | import { 16 | chibanToString, 17 | cityName, 18 | machiAzaName, 19 | prefectureName, 20 | rsdtToString, 21 | SingleChiban, 22 | SingleCity, 23 | SingleMachiAza, 24 | SinglePrefecture, 25 | SingleRsdt, 26 | } from '@geolonia/japanese-addresses-v2' 27 | import { prenormalize } from './lib/normalizeHelpers' 28 | import { 29 | cityToResultPoint, 30 | machiAzaToResultPoint, 31 | NormalizeResult, 32 | NormalizeResultPoint, 33 | prefectureToResultPoint, 34 | rsdtOrChibanToResultPoint, 35 | upgradePoint, 36 | } from './types' 37 | import { 38 | removeCitiesFromPrefecture, 39 | removeExtraFromMachiAza, 40 | } from './lib/utils' 41 | 42 | export type TransformRequestQuery = { 43 | level: number // level = -1 は旧 API。 transformRequestFunction を設定しても無視する 44 | pref?: string 45 | city?: string 46 | town?: string 47 | } 48 | 49 | const __VERSION__: string = 'dev' 50 | export const version = __VERSION__ 51 | 52 | /** 53 | * normalize {@link Normalizer} の動作オプション。 54 | */ 55 | export interface Config { 56 | /** 住所データを URL 形式で指定。 file:// 形式で指定するとローカルファイルを参照できます。 */ 57 | japaneseAddressesApi: string 58 | 59 | /** 内部キャッシュの最大サイズ。デフォルトでは 1,000 件 */ 60 | cacheSize: number 61 | 62 | geoloniaApiKey?: string 63 | } 64 | export const config: Config = currentConfig 65 | 66 | /** 67 | * 正規化関数の {@link normalize} のオプション 68 | */ 69 | export interface Option { 70 | /** 71 | * 希望最大正規化を行うレベルを指定します。{@link Option.level} 72 | * 73 | * @see https://github.com/geolonia/normalize-japanese-addresses#normalizeaddress-string 74 | */ 75 | level?: number 76 | 77 | geoloniaApiKey?: string 78 | } 79 | 80 | /** 81 | * 住所を正規化します。 82 | * 83 | * @param input - 住所文字列 84 | * @param option - 正規化のオプション {@link Option} 85 | * 86 | * @returns 正規化結果のオブジェクト {@link NormalizeResult} 87 | * 88 | * @see https://github.com/geolonia/normalize-japanese-addresses#normalizeaddress-string 89 | */ 90 | export type Normalizer = ( 91 | input: string, 92 | option?: Option, 93 | ) => Promise 94 | 95 | const defaultOption = { 96 | level: 8, 97 | } 98 | 99 | const normalizeTownName = async ( 100 | input: string, 101 | pref: SinglePrefecture, 102 | city: SingleCity, 103 | apiVersion: number, 104 | ) => { 105 | input = input.trim().replace(/^大字/, '') 106 | const townPatterns = await getTownRegexPatterns(pref, city, apiVersion) 107 | 108 | const regexPrefixes = ['^'] 109 | if (city.city === '京都市') { 110 | // 京都は通り名削除のために後方一致を使う 111 | regexPrefixes.push('.*') 112 | } 113 | 114 | for (const regexPrefix of regexPrefixes) { 115 | for (const [town, pattern] of townPatterns) { 116 | const regex = new RegExp(`${regexPrefix}${pattern}`) 117 | const match = input.match(regex) 118 | if (match) { 119 | return { 120 | town, 121 | other: input.substring(match[0].length), 122 | } 123 | } 124 | } 125 | } 126 | } 127 | 128 | type NormalizedAddrPart = { 129 | chiban?: SingleChiban 130 | rsdt?: SingleRsdt 131 | rest: string 132 | } 133 | async function normalizeAddrPart( 134 | addr: string, 135 | pref: SinglePrefecture, 136 | city: SingleCity, 137 | town: SingleMachiAza, 138 | apiVersion: number, 139 | ): Promise { 140 | const match = addr.match( 141 | /^([1-9][0-9]*)(?:-([1-9][0-9]*))?(?:-([1-9][0-9]*))?/, 142 | ) 143 | if (!match) { 144 | return { 145 | rest: addr, 146 | } 147 | } 148 | // TODO: rsdtの場合はrsdtと地番を両方取得する 149 | if (town.rsdt) { 150 | const res = await getRsdt(pref, city, town, apiVersion) 151 | for (const rsdt of res) { 152 | const addrPart = rsdtToString(rsdt) 153 | if (match[0] === addrPart) { 154 | return { 155 | rsdt, 156 | rest: addr.substring(addrPart.length), 157 | } 158 | } 159 | } 160 | } else { 161 | const res = await getChiban(pref, city, town, apiVersion) 162 | for (const chiban of res) { 163 | const addrPart = chibanToString(chiban) 164 | if (match[0] === addrPart) { 165 | return { 166 | chiban, 167 | rest: addr.substring(addrPart.length), 168 | } 169 | } 170 | } 171 | } 172 | return { 173 | rest: addr, 174 | } 175 | } 176 | 177 | export const normalize: Normalizer = async ( 178 | address, 179 | _option = defaultOption, 180 | ) => { 181 | const option = { ...defaultOption, ..._option } 182 | 183 | option.geoloniaApiKey ??= config.geoloniaApiKey 184 | 185 | // other に入っている文字列は正規化するときに 186 | let other = prenormalize(address) 187 | 188 | let pref: SinglePrefecture | undefined 189 | let city: SingleCity | undefined 190 | let town: SingleMachiAza | undefined 191 | let point: NormalizeResultPoint | undefined 192 | let addr: string | undefined 193 | let level = 0 194 | 195 | // 都道府県名の正規化 196 | 197 | const prefectures = await getPrefectures() 198 | const apiVersion = prefectures.meta.updated 199 | const prefPatterns = getPrefectureRegexPatterns(prefectures) 200 | const sameNamedPrefectureCityRegexPatterns = 201 | getSameNamedPrefectureCityRegexPatterns(prefectures) 202 | 203 | // 県名が省略されており、かつ市の名前がどこかの都道府県名と同じ場合(例.千葉県千葉市)、 204 | // あらかじめ県名を補完しておく。 205 | for (const [prefectureCity, reg] of sameNamedPrefectureCityRegexPatterns) { 206 | const match = other.match(reg) 207 | if (match) { 208 | other = other.replace(new RegExp(reg), prefectureCity) 209 | break 210 | } 211 | } 212 | 213 | for (const [_pref, pattern] of prefPatterns) { 214 | const match = other.match(pattern) 215 | if (match) { 216 | pref = _pref 217 | other = other.substring(match[0].length) // 都道府県名以降の住所 218 | point = prefectureToResultPoint(pref) 219 | break 220 | } 221 | } 222 | 223 | if (!pref) { 224 | // 都道府県名が省略されている 225 | const matched: { 226 | pref: SinglePrefecture 227 | city: SingleCity 228 | other: string 229 | }[] = [] 230 | for (const _pref of prefectures.data) { 231 | const cityPatterns = getCityRegexPatterns(_pref) 232 | 233 | other = other.trim() 234 | for (const [_city, pattern] of cityPatterns) { 235 | const match = other.match(pattern) 236 | if (match) { 237 | matched.push({ 238 | pref: _pref, 239 | city: _city, 240 | other: other.substring(match[0].length), 241 | }) 242 | } 243 | } 244 | } 245 | 246 | // マッチする都道府県が複数ある場合は町名まで正規化して都道府県名を判別する。(例: 東京都府中市と広島県府中市など) 247 | if (1 === matched.length) { 248 | pref = matched[0].pref 249 | } else { 250 | for (const m of matched) { 251 | const normalized = await normalizeTownName( 252 | m.other, 253 | m.pref, 254 | m.city, 255 | apiVersion, 256 | ) 257 | if (normalized) { 258 | pref = m.pref 259 | city = m.city 260 | town = normalized.town 261 | other = normalized.other 262 | point = upgradePoint(point, machiAzaToResultPoint(town)) 263 | } 264 | } 265 | } 266 | } 267 | 268 | if (pref && option.level >= 2) { 269 | const cityPatterns = getCityRegexPatterns(pref) 270 | 271 | other = other.trim() 272 | for (const [_city, pattern] of cityPatterns) { 273 | const match = other.match(pattern) 274 | if (match) { 275 | city = _city 276 | point = upgradePoint(point, cityToResultPoint(city)) 277 | other = other.substring(match[0].length) // 市区町村名以降の住所 278 | break 279 | } 280 | } 281 | } 282 | 283 | // 町丁目以降の正規化 284 | if (pref && city && option.level >= 3) { 285 | const normalized = await normalizeTownName(other, pref, city, apiVersion) 286 | if (normalized) { 287 | town = normalized.town 288 | other = normalized.other 289 | point = upgradePoint(point, machiAzaToResultPoint(town)) 290 | } 291 | 292 | // townが取得できた場合にのみ、addrに対する各種の変換処理を行う。 293 | if (town) { 294 | other = other 295 | .replace(/^-/, '') 296 | .replace(/([0-9]+)(丁目)/g, (match) => { 297 | return match.replace(/([0-9]+)/g, (num) => { 298 | return number2kanji(Number(num)) 299 | }) 300 | }) 301 | .replace( 302 | /(([0-9]+|[〇一二三四五六七八九十百千]+)(番地?)([0-9]+|[〇一二三四五六七八九十百千]+)号)\s*(.+)/, 303 | '$1 $5', 304 | ) 305 | .replace( 306 | /([0-9]+|[〇一二三四五六七八九十百千]+)\s*(番地?)\s*([0-9]+|[〇一二三四五六七八九十百千]+)\s*号?/, 307 | '$1-$3', 308 | ) 309 | .replace(/([0-9]+|[〇一二三四五六七八九十百千]+)番(地|$)/, '$1') 310 | .replace(/([0-9]+|[〇一二三四五六七八九十百千]+)の/g, '$1-') 311 | .replace( 312 | /([0-9]+|[〇一二三四五六七八九十百千]+)[--﹣−‐⁃‑‒–—﹘―⎯⏤ーー─━]/g, 313 | (match) => { 314 | return kan2num(match).replace(/[--﹣−‐⁃‑‒–—﹘―⎯⏤ーー─━]/g, '-') 315 | }, 316 | ) 317 | .replace( 318 | /[--﹣−‐⁃‑‒–—﹘―⎯⏤ーー─━]([0-9]+|[〇一二三四五六七八九十百千]+)/g, 319 | (match) => { 320 | return kan2num(match).replace(/[--﹣−‐⁃‑‒–—﹘―⎯⏤ーー─━]/g, '-') 321 | }, 322 | ) 323 | .replace(/([0-9]+|[〇一二三四五六七八九十百千]+)-/, (s) => { 324 | // `1-` のようなケース 325 | return kan2num(s) 326 | }) 327 | .replace(/-([0-9]+|[〇一二三四五六七八九十百千]+)/, (s) => { 328 | // `-1` のようなケース 329 | return kan2num(s) 330 | }) 331 | .replace(/-[^0-9]([0-9]+|[〇一二三四五六七八九十百千]+)/, (s) => { 332 | // `-あ1` のようなケース 333 | return kan2num(zen2han(s)) 334 | }) 335 | .replace(/([0-9]+|[〇一二三四五六七八九十百千]+)$/, (s) => { 336 | // `串本町串本1234` のようなケース 337 | return kan2num(s) 338 | }) 339 | .trim() 340 | } 341 | } 342 | 343 | other = patchAddr( 344 | pref ? prefectureName(pref) : '', 345 | city ? cityName(city) : '', 346 | town ? machiAzaName(town) : '', 347 | other, 348 | ) 349 | 350 | if (pref) level = level + 1 351 | if (city) level = level + 1 352 | if (town) level = level + 1 353 | 354 | if (option.level <= 3 || level < 3) { 355 | const result: NormalizeResult = { 356 | pref: pref ? prefectureName(pref) : undefined, 357 | city: city ? cityName(city) : undefined, 358 | town: town ? machiAzaName(town) : undefined, 359 | other: other, 360 | level, 361 | point, 362 | metadata: { 363 | input: address, 364 | prefecture: removeCitiesFromPrefecture(pref), 365 | city: city, 366 | machiAza: removeExtraFromMachiAza(town), 367 | }, 368 | } 369 | return result 370 | } 371 | 372 | const normalizedAddrPart = await normalizeAddrPart( 373 | other, 374 | pref!, 375 | city!, 376 | town!, 377 | apiVersion, 378 | ) 379 | // TODO: rsdtと地番を両方対応した時に両方返すけど、今はrsdtを優先する 380 | if (normalizedAddrPart.rsdt) { 381 | addr = rsdtToString(normalizedAddrPart.rsdt) 382 | other = normalizedAddrPart.rest 383 | point = upgradePoint( 384 | point, 385 | rsdtOrChibanToResultPoint(normalizedAddrPart.rsdt), 386 | ) 387 | level = 8 388 | } else if (normalizedAddrPart.chiban) { 389 | addr = chibanToString(normalizedAddrPart.chiban) 390 | other = normalizedAddrPart.rest 391 | point = upgradePoint( 392 | point, 393 | rsdtOrChibanToResultPoint(normalizedAddrPart.chiban), 394 | ) 395 | level = 8 396 | } 397 | const result: NormalizeResult = { 398 | pref: pref ? prefectureName(pref) : undefined, 399 | city: city ? cityName(city) : undefined, 400 | town: town ? machiAzaName(town) : undefined, 401 | addr, 402 | level, 403 | point, 404 | other, 405 | metadata: { 406 | input: address, 407 | prefecture: removeCitiesFromPrefecture(pref), 408 | city: city, 409 | machiAza: removeExtraFromMachiAza(town), 410 | rsdt: normalizedAddrPart.rsdt, 411 | chiban: normalizedAddrPart.chiban, 412 | }, 413 | } 414 | return result 415 | } 416 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SingleChiban, 3 | SingleCity, 4 | SingleMachiAza, 5 | SinglePrefecture, 6 | SingleRsdt, 7 | } from '@geolonia/japanese-addresses-v2' 8 | 9 | /** 10 | * 正規化対象の住所の位置情報 11 | * 位置情報は EPSG:4326 (WGS84) です。 12 | */ 13 | export type NormalizeResultPoint = { 14 | /// 緯度 15 | lat: number 16 | /// 経度 17 | lng: number 18 | /** 19 | * 緯度経度の正確さを表すレベル 20 | * - 1 - 都道府県の代表点(県庁所在地)の位置 21 | * - 2 - 市区町村の代表点(市役所など)の位置 22 | * - 3 - 丁目・町字の代表点の位置 23 | * - 8 - 住居表示住所または地番の位置 24 | */ 25 | level: number 26 | } 27 | 28 | export function isNormalizeResultPoint( 29 | obj: unknown, 30 | ): obj is NormalizeResultPoint { 31 | // Check that obj is an object and not null 32 | if (typeof obj !== 'object' || obj === null) { 33 | return false 34 | } 35 | 36 | // Destructure properties from the object 37 | const { lat, lng, level } = obj as { [key: string]: unknown } 38 | 39 | // Check that lat, lng, and level are numbers 40 | if ( 41 | typeof lat !== 'number' || 42 | typeof lng !== 'number' || 43 | typeof level !== 'number' 44 | ) { 45 | return false 46 | } 47 | 48 | // Optionally, validate that level is one of the expected values 49 | const validLevels = [1, 2, 3, 8] 50 | if (!validLevels.includes(level)) { 51 | return false 52 | } 53 | 54 | // All checks passed 55 | return true 56 | } 57 | 58 | export function prefectureToResultPoint( 59 | pref: SinglePrefecture, 60 | ): NormalizeResultPoint { 61 | return { 62 | lat: pref.point[1], 63 | lng: pref.point[0], 64 | level: 1, 65 | } 66 | } 67 | 68 | export function cityToResultPoint(city: SingleCity): NormalizeResultPoint { 69 | return { 70 | lat: city.point[1], 71 | lng: city.point[0], 72 | level: 2, 73 | } 74 | } 75 | 76 | export function machiAzaToResultPoint( 77 | machiAza: SingleMachiAza, 78 | ): NormalizeResultPoint | undefined { 79 | if (!machiAza.point) return undefined 80 | return { 81 | lat: machiAza.point[1], 82 | lng: machiAza.point[0], 83 | level: 3, 84 | } 85 | } 86 | 87 | export function rsdtOrChibanToResultPoint( 88 | input: SingleRsdt | SingleChiban, 89 | ): NormalizeResultPoint | undefined { 90 | if (!input.point) return undefined 91 | return { 92 | lat: input.point[1], 93 | lng: input.point[0], 94 | level: 8, 95 | } 96 | } 97 | 98 | export function upgradePoint( 99 | a: NormalizeResultPoint | undefined, 100 | b: NormalizeResultPoint | undefined, 101 | ): NormalizeResultPoint | undefined { 102 | if (!a) return b 103 | if (!b) return a 104 | if (a.level > b.level) return a 105 | return b 106 | } 107 | 108 | export type NormalizeResultMetadata = { 109 | input: string 110 | 111 | /** 都道府県 */ 112 | prefecture?: Omit 113 | /** 市区町村 */ 114 | city?: SingleCity 115 | /** 町字 */ 116 | machiAza?: SingleMachiAza 117 | /** 丁目 */ 118 | chiban?: SingleChiban 119 | /** 住居表示住所 */ 120 | rsdt?: SingleRsdt 121 | } 122 | 123 | export type NormalizeResult = { 124 | /** 都道府県 */ 125 | pref?: string 126 | /** 市区町村 */ 127 | city?: string 128 | /** 129 | * 丁目・町字 130 | * 丁目の場合は、丁目名の後に漢数字で丁目が付与される。 131 | * 例:「青葉一丁目」 132 | */ 133 | town?: string 134 | /** 住居表示または地番 */ 135 | addr?: string 136 | /** 正規化後の住所文字列。完全に正規化された場合は、空の文字列が入ります。 */ 137 | other: string 138 | 139 | /** 140 | * 住所の緯度経度 141 | * 注意: 正規化レベルが8でも、位置情報は8でもない場合もあります。 142 | */ 143 | point?: NormalizeResultPoint 144 | 145 | /** 146 | * 住所文字列をどこまで判別できたかを表す正規化レベル 147 | * - 0 - 都道府県も判別できなかった。 148 | * - 1 - 都道府県まで判別できた。 149 | * - 2 - 市区町村まで判別できた。 150 | * - 3 - 丁目・町字まで判別できた。 151 | * - 8 - 住居表示住所または地番の判別ができた。 152 | */ 153 | level: number 154 | 155 | /** 追加情報 */ 156 | metadata: NormalizeResultMetadata 157 | } 158 | -------------------------------------------------------------------------------- /test/addresses/addresses.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'node:test' 2 | import { normalize } from '../../src/main-node' 3 | import fs from 'node:fs' 4 | import path from 'node:path' 5 | import Papa from 'papaparse' 6 | import { NormalizeResult } from '../../src/types' 7 | import { assertMatchCloseTo } from '../helpers' 8 | 9 | const input = fs.readFileSync( 10 | path.join(import.meta.dirname, '/addresses.csv'), 11 | { 12 | encoding: 'utf-8', 13 | }, 14 | ) 15 | const lines = Papa.parse(input).data 16 | 17 | describe(`address tests`, { concurrency: 4 }, () => { 18 | for (const line of lines) { 19 | const addr = line[0] 20 | if (addr === '住所') { 21 | continue 22 | } 23 | 24 | let testName = addr 25 | if (line[9] !== '') { 26 | testName += ` (${line[9]})` 27 | } 28 | 29 | test(testName, async () => { 30 | const res = await normalize(addr) 31 | const point = line[7] ? line[7].split(',').map(parseFloat) : undefined 32 | const match: Partial = { 33 | other: line[5], 34 | level: parseInt(line[6]), 35 | } 36 | if (line[1] !== '') match.pref = line[1] 37 | if (line[2] !== '') match.city = line[2] 38 | if (line[3] !== '') match.town = line[3] 39 | if (line[4] !== '') match.addr = line[4] 40 | if (point) { 41 | match.point = { 42 | lng: point[0], 43 | lat: point[1], 44 | level: parseInt(line[8]), 45 | } 46 | } 47 | assertMatchCloseTo(res, match) 48 | }) 49 | } 50 | }) 51 | -------------------------------------------------------------------------------- /test/addresses/build-test-data.ts: -------------------------------------------------------------------------------- 1 | import { normalize } from '../../src/main-node' 2 | import fs from 'node:fs' 3 | import path from 'node:path' 4 | import Papa from 'papaparse' 5 | 6 | const list = fs.readFileSync(path.join(import.meta.dirname, 'list.txt'), { 7 | encoding: 'utf-8', 8 | }) 9 | const data = Papa.parse(list).data 10 | 11 | ;(async () => { 12 | // data.sort() 13 | 14 | const addedAddresses: Set = new Set() 15 | 16 | const output: string[][] = [ 17 | [ 18 | '住所', 19 | '都道府県', 20 | '市区町村', 21 | '町字', 22 | '番地号', 23 | 'その他', 24 | 'レベル', 25 | '緯度経度', 26 | '位置情報レベル', 27 | '備考', 28 | ], 29 | ] 30 | for (const line of data) { 31 | const address = line[0] 32 | if (!address) { 33 | continue 34 | } 35 | if (addedAddresses.has(address)) { 36 | throw new Error( 37 | `重複の入力住所: ${address} 重複を除き再試行してください。`, 38 | ) 39 | } 40 | addedAddresses.add(address) 41 | 42 | const result = await normalize(address) 43 | output.push([ 44 | address, 45 | result.pref || '', 46 | result.city || '', 47 | result.town || '', 48 | result.addr || '', 49 | result.other, 50 | result.level.toString(), 51 | result.point ? `${result.point.lng},${result.point.lat}` : '', 52 | result.point ? result.point.level.toString() : '', 53 | line[1] || '', 54 | ]) 55 | } 56 | 57 | const csv = Papa.unparse(output, { 58 | header: true, 59 | }) 60 | console.log(csv) 61 | })() 62 | -------------------------------------------------------------------------------- /test/helpers.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert' 2 | import { toMatchCloseTo } from 'jest-matcher-deep-close-to'; 3 | import { NormalizeResult } from '../src/types'; 4 | 5 | export function assertMatchCloseTo( 6 | received: NormalizeResult, 7 | expected: Partial, 8 | ) { 9 | const result = toMatchCloseTo(received, expected) 10 | assert.ok(result.pass, result.message()) 11 | } 12 | -------------------------------------------------------------------------------- /test/integration/browser.test.ts: -------------------------------------------------------------------------------- 1 | import { before, describe, test } from 'node:test' 2 | import assert from 'node:assert' 3 | import puppeteer from 'puppeteer' 4 | 5 | import fs from 'node:fs/promises' 6 | import path from 'node:path' 7 | 8 | const runTest = (type: 'esm' | 'umd') => { 9 | return async () => { 10 | const browser = await puppeteer.launch({ 11 | args: [ 12 | '--disable-web-security', // for loading files from file:// 13 | '--no-sandbox', // for running in CI 14 | ], 15 | }) 16 | try { 17 | const page = await browser.newPage() 18 | await page.goto( 19 | `file://${path.join(import.meta.dirname, `browser/index-${type}.html`)}`, 20 | ) 21 | 22 | await Promise.race([ 23 | page.waitForSelector('#result', { timeout: 20_000 }), 24 | page.waitForSelector('#error', { timeout: 20_000 }), 25 | ]) 26 | 27 | const errorElem = await page.$$('#error') 28 | if (errorElem.length > 0) { 29 | const errorValue = await page.$eval('#error', (el) => el.textContent) 30 | assert.strictEqual( 31 | errorElem.length, 32 | 0, 33 | `#error should not exist, instead we have: ${errorValue}`, 34 | ) 35 | } 36 | 37 | const result = await page.$eval('#result', (el) => el.textContent) 38 | assert.strictEqual(result, 'OK') 39 | } finally { 40 | await browser.close() 41 | } 42 | } 43 | } 44 | 45 | describe(`browser`, { timeout: 60_000 }, () => { 46 | before(async () => { 47 | await fs.copyFile( 48 | path.join(import.meta.dirname, '../../dist/main-esm.mjs'), 49 | path.join(import.meta.dirname, 'browser/main-esm.mjs'), 50 | ) 51 | await fs.copyFile( 52 | path.join(import.meta.dirname, '../../dist/main-umd.cjs'), 53 | path.join(import.meta.dirname, 'browser/main-umd.cjs'), 54 | ) 55 | }) 56 | 57 | test(`esm`, runTest('esm')) 58 | test(`umd`, runTest('umd')) 59 | }) 60 | -------------------------------------------------------------------------------- /test/integration/browser/index-esm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/integration/browser/index-umd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/integration/node-cjs/index.js: -------------------------------------------------------------------------------- 1 | const assert = require('node:assert'); 2 | const { normalize } = require('@geolonia/normalize-japanese-addresses'); 3 | 4 | (async () => { 5 | const res = await normalize('渋谷区道玄坂1-10-8'); 6 | assert.strictEqual(res.pref, '東京都'); 7 | })() 8 | .then(() => { 9 | console.log('OK'); 10 | process.exit(0); 11 | }) 12 | .catch((err) => { 13 | console.error(err); 14 | process.exit(1); 15 | }); 16 | -------------------------------------------------------------------------------- /test/integration/node-cjs/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nja-test-cjs", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "nja-test-cjs", 8 | "dependencies": { 9 | "@geolonia/normalize-japanese-addresses": "file:../../.." 10 | } 11 | }, 12 | "../../..": { 13 | "name": "@geolonia/normalize-japanese-addresses", 14 | "version": "3.0.1", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@geolonia/japanese-addresses-v2": "0.0.5", 18 | "@geolonia/japanese-numeral": "^1.0.2", 19 | "lru-cache": "^11.0.1", 20 | "papaparse": "^5.4.1", 21 | "undici": "^6.19.8" 22 | }, 23 | "devDependencies": { 24 | "@rollup/plugin-commonjs": "^28.0.1", 25 | "@rollup/plugin-node-resolve": "^15.3.0", 26 | "@rollup/plugin-replace": "^6.0.1", 27 | "@rollup/plugin-terser": "^0.4.4", 28 | "@rollup/plugin-typescript": "^12.1.1", 29 | "@types/node": "^22", 30 | "@types/papaparse": "^5.3.14", 31 | "@typescript-eslint/eslint-plugin": "^8.7.0", 32 | "@typescript-eslint/parser": "^8.7.0", 33 | "eslint": "^9.11.1", 34 | "eslint-config-prettier": "^9.1.0", 35 | "eslint-plugin-prettier": "^5.2.1", 36 | "eslint-plugin-tsdoc": "^0.3.0", 37 | "glob": "^11.0.0", 38 | "jest-matcher-deep-close-to": "^3.0.2", 39 | "prettier": "^3.3.3", 40 | "puppeteer": "^23.6.0", 41 | "rollup": "^4.24.0", 42 | "shx": "^0.3.4", 43 | "tsx": "^4.19.1", 44 | "typescript": "^5.6.2" 45 | }, 46 | "engines": { 47 | "node": ">=20" 48 | } 49 | }, 50 | "node_modules/@geolonia/normalize-japanese-addresses": { 51 | "resolved": "../../..", 52 | "link": true 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/integration/node-cjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nja-test-cjs", 3 | "dependencies": { 4 | "@geolonia/normalize-japanese-addresses": "file:../../.." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/integration/node-esm/index.js: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert'; 2 | import { normalize } from '@geolonia/normalize-japanese-addresses'; 3 | 4 | (async () => { 5 | const res = await normalize('渋谷区道玄坂1-10-8'); 6 | assert.strictEqual(res.pref, '東京都'); 7 | })() 8 | .then(() => { 9 | console.log('OK'); 10 | process.exit(0); 11 | }) 12 | .catch((err) => { 13 | console.error(err); 14 | process.exit(1); 15 | }); 16 | -------------------------------------------------------------------------------- /test/integration/node-esm/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nja-test-cjs", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "nja-test-cjs", 8 | "dependencies": { 9 | "@geolonia/normalize-japanese-addresses": "file:../../.." 10 | } 11 | }, 12 | "../../..": { 13 | "name": "@geolonia/normalize-japanese-addresses", 14 | "version": "3.0.1", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@geolonia/japanese-addresses-v2": "0.0.5", 18 | "@geolonia/japanese-numeral": "^1.0.2", 19 | "lru-cache": "^11.0.1", 20 | "papaparse": "^5.4.1", 21 | "undici": "^6.19.8" 22 | }, 23 | "devDependencies": { 24 | "@rollup/plugin-commonjs": "^28.0.1", 25 | "@rollup/plugin-node-resolve": "^15.3.0", 26 | "@rollup/plugin-replace": "^6.0.1", 27 | "@rollup/plugin-terser": "^0.4.4", 28 | "@rollup/plugin-typescript": "^12.1.1", 29 | "@types/node": "^22", 30 | "@types/papaparse": "^5.3.14", 31 | "@typescript-eslint/eslint-plugin": "^8.7.0", 32 | "@typescript-eslint/parser": "^8.7.0", 33 | "eslint": "^9.11.1", 34 | "eslint-config-prettier": "^9.1.0", 35 | "eslint-plugin-prettier": "^5.2.1", 36 | "eslint-plugin-tsdoc": "^0.3.0", 37 | "glob": "^11.0.0", 38 | "jest-matcher-deep-close-to": "^3.0.2", 39 | "prettier": "^3.3.3", 40 | "puppeteer": "^23.6.0", 41 | "rollup": "^4.24.0", 42 | "shx": "^0.3.4", 43 | "tsx": "^4.19.1", 44 | "typescript": "^5.6.2" 45 | }, 46 | "engines": { 47 | "node": ">=20" 48 | } 49 | }, 50 | "node_modules/@geolonia/normalize-japanese-addresses": { 51 | "resolved": "../../..", 52 | "link": true 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/integration/node-esm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nja-test-cjs", 3 | "type": "module", 4 | "dependencies": { 5 | "@geolonia/normalize-japanese-addresses": "file:../../.." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/integration/node.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'node:test' 2 | import assert from 'node:assert' 3 | 4 | import { promisify } from 'node:util' 5 | import { exec as execCb } from 'node:child_process' 6 | import path from 'node:path' 7 | 8 | const exec = promisify(execCb) 9 | 10 | const runTest = (type: 'cjs' | 'esm') => { 11 | return async () => { 12 | // the test project is in ./node-${type} 13 | const cwd = path.join(import.meta.dirname, `node-${type}`) 14 | await exec(`npm install`, { cwd }) 15 | const res = await exec(`node ./index.js`, { cwd }) 16 | 17 | assert.strictEqual(res.stdout.trim(), 'OK') 18 | assert.strictEqual(res.stderr, '') 19 | } 20 | } 21 | 22 | describe(`node`, () => { 23 | test(`cjs`, runTest('cjs')) 24 | test(`esm`, runTest('esm')) 25 | }) 26 | -------------------------------------------------------------------------------- /test/integration/webpack-ts/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /test/integration/webpack-ts/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-ts", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "webpack-ts", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@geolonia/normalize-japanese-addresses": "file:../../.." 13 | }, 14 | "devDependencies": { 15 | "ts-loader": "^9.5.1", 16 | "typescript": "^5.6.3", 17 | "webpack": "^5.95.0", 18 | "webpack-cli": "^5.1.4" 19 | } 20 | }, 21 | "../../..": { 22 | "name": "@geolonia/normalize-japanese-addresses", 23 | "version": "3.0.1", 24 | "license": "MIT", 25 | "dependencies": { 26 | "@geolonia/japanese-addresses-v2": "0.0.5", 27 | "@geolonia/japanese-numeral": "^1.0.2", 28 | "lru-cache": "^11.0.1", 29 | "papaparse": "^5.4.1", 30 | "undici": "^6.19.8" 31 | }, 32 | "devDependencies": { 33 | "@rollup/plugin-commonjs": "^28.0.1", 34 | "@rollup/plugin-node-resolve": "^15.3.0", 35 | "@rollup/plugin-replace": "^6.0.1", 36 | "@rollup/plugin-terser": "^0.4.4", 37 | "@rollup/plugin-typescript": "^12.1.1", 38 | "@types/node": "^22", 39 | "@types/papaparse": "^5.3.14", 40 | "@typescript-eslint/eslint-plugin": "^8.7.0", 41 | "@typescript-eslint/parser": "^8.7.0", 42 | "eslint": "^9.11.1", 43 | "eslint-config-prettier": "^9.1.0", 44 | "eslint-plugin-prettier": "^5.2.1", 45 | "eslint-plugin-tsdoc": "^0.3.0", 46 | "glob": "^11.0.0", 47 | "jest-matcher-deep-close-to": "^3.0.2", 48 | "prettier": "^3.3.3", 49 | "puppeteer": "^23.6.0", 50 | "rollup": "^4.24.0", 51 | "shx": "^0.3.4", 52 | "tsx": "^4.19.1", 53 | "typescript": "^5.6.2" 54 | }, 55 | "engines": { 56 | "node": ">=20" 57 | } 58 | }, 59 | "node_modules/@discoveryjs/json-ext": { 60 | "version": "0.5.7", 61 | "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", 62 | "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", 63 | "dev": true, 64 | "license": "MIT", 65 | "engines": { 66 | "node": ">=10.0.0" 67 | } 68 | }, 69 | "node_modules/@geolonia/normalize-japanese-addresses": { 70 | "resolved": "../../..", 71 | "link": true 72 | }, 73 | "node_modules/@jridgewell/gen-mapping": { 74 | "version": "0.3.5", 75 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", 76 | "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", 77 | "dev": true, 78 | "license": "MIT", 79 | "dependencies": { 80 | "@jridgewell/set-array": "^1.2.1", 81 | "@jridgewell/sourcemap-codec": "^1.4.10", 82 | "@jridgewell/trace-mapping": "^0.3.24" 83 | }, 84 | "engines": { 85 | "node": ">=6.0.0" 86 | } 87 | }, 88 | "node_modules/@jridgewell/resolve-uri": { 89 | "version": "3.1.2", 90 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 91 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 92 | "dev": true, 93 | "license": "MIT", 94 | "engines": { 95 | "node": ">=6.0.0" 96 | } 97 | }, 98 | "node_modules/@jridgewell/set-array": { 99 | "version": "1.2.1", 100 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", 101 | "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", 102 | "dev": true, 103 | "license": "MIT", 104 | "engines": { 105 | "node": ">=6.0.0" 106 | } 107 | }, 108 | "node_modules/@jridgewell/source-map": { 109 | "version": "0.3.6", 110 | "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", 111 | "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", 112 | "dev": true, 113 | "license": "MIT", 114 | "dependencies": { 115 | "@jridgewell/gen-mapping": "^0.3.5", 116 | "@jridgewell/trace-mapping": "^0.3.25" 117 | } 118 | }, 119 | "node_modules/@jridgewell/sourcemap-codec": { 120 | "version": "1.5.0", 121 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 122 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 123 | "dev": true, 124 | "license": "MIT" 125 | }, 126 | "node_modules/@jridgewell/trace-mapping": { 127 | "version": "0.3.25", 128 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 129 | "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 130 | "dev": true, 131 | "license": "MIT", 132 | "dependencies": { 133 | "@jridgewell/resolve-uri": "^3.1.0", 134 | "@jridgewell/sourcemap-codec": "^1.4.14" 135 | } 136 | }, 137 | "node_modules/@types/estree": { 138 | "version": "1.0.6", 139 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 140 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 141 | "dev": true, 142 | "license": "MIT" 143 | }, 144 | "node_modules/@types/json-schema": { 145 | "version": "7.0.15", 146 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 147 | "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 148 | "dev": true, 149 | "license": "MIT" 150 | }, 151 | "node_modules/@types/node": { 152 | "version": "22.7.8", 153 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.8.tgz", 154 | "integrity": "sha512-a922jJy31vqR5sk+kAdIENJjHblqcZ4RmERviFsER4WJcEONqxKcjNOlk0q7OUfrF5sddT+vng070cdfMlrPLg==", 155 | "dev": true, 156 | "license": "MIT", 157 | "dependencies": { 158 | "undici-types": "~6.19.2" 159 | } 160 | }, 161 | "node_modules/@webassemblyjs/ast": { 162 | "version": "1.12.1", 163 | "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", 164 | "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", 165 | "dev": true, 166 | "license": "MIT", 167 | "dependencies": { 168 | "@webassemblyjs/helper-numbers": "1.11.6", 169 | "@webassemblyjs/helper-wasm-bytecode": "1.11.6" 170 | } 171 | }, 172 | "node_modules/@webassemblyjs/floating-point-hex-parser": { 173 | "version": "1.11.6", 174 | "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", 175 | "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", 176 | "dev": true, 177 | "license": "MIT" 178 | }, 179 | "node_modules/@webassemblyjs/helper-api-error": { 180 | "version": "1.11.6", 181 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", 182 | "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", 183 | "dev": true, 184 | "license": "MIT" 185 | }, 186 | "node_modules/@webassemblyjs/helper-buffer": { 187 | "version": "1.12.1", 188 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", 189 | "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", 190 | "dev": true, 191 | "license": "MIT" 192 | }, 193 | "node_modules/@webassemblyjs/helper-numbers": { 194 | "version": "1.11.6", 195 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", 196 | "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", 197 | "dev": true, 198 | "license": "MIT", 199 | "dependencies": { 200 | "@webassemblyjs/floating-point-hex-parser": "1.11.6", 201 | "@webassemblyjs/helper-api-error": "1.11.6", 202 | "@xtuc/long": "4.2.2" 203 | } 204 | }, 205 | "node_modules/@webassemblyjs/helper-wasm-bytecode": { 206 | "version": "1.11.6", 207 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", 208 | "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", 209 | "dev": true, 210 | "license": "MIT" 211 | }, 212 | "node_modules/@webassemblyjs/helper-wasm-section": { 213 | "version": "1.12.1", 214 | "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", 215 | "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", 216 | "dev": true, 217 | "license": "MIT", 218 | "dependencies": { 219 | "@webassemblyjs/ast": "1.12.1", 220 | "@webassemblyjs/helper-buffer": "1.12.1", 221 | "@webassemblyjs/helper-wasm-bytecode": "1.11.6", 222 | "@webassemblyjs/wasm-gen": "1.12.1" 223 | } 224 | }, 225 | "node_modules/@webassemblyjs/ieee754": { 226 | "version": "1.11.6", 227 | "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", 228 | "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", 229 | "dev": true, 230 | "license": "MIT", 231 | "dependencies": { 232 | "@xtuc/ieee754": "^1.2.0" 233 | } 234 | }, 235 | "node_modules/@webassemblyjs/leb128": { 236 | "version": "1.11.6", 237 | "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", 238 | "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", 239 | "dev": true, 240 | "license": "Apache-2.0", 241 | "dependencies": { 242 | "@xtuc/long": "4.2.2" 243 | } 244 | }, 245 | "node_modules/@webassemblyjs/utf8": { 246 | "version": "1.11.6", 247 | "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", 248 | "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", 249 | "dev": true, 250 | "license": "MIT" 251 | }, 252 | "node_modules/@webassemblyjs/wasm-edit": { 253 | "version": "1.12.1", 254 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", 255 | "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", 256 | "dev": true, 257 | "license": "MIT", 258 | "dependencies": { 259 | "@webassemblyjs/ast": "1.12.1", 260 | "@webassemblyjs/helper-buffer": "1.12.1", 261 | "@webassemblyjs/helper-wasm-bytecode": "1.11.6", 262 | "@webassemblyjs/helper-wasm-section": "1.12.1", 263 | "@webassemblyjs/wasm-gen": "1.12.1", 264 | "@webassemblyjs/wasm-opt": "1.12.1", 265 | "@webassemblyjs/wasm-parser": "1.12.1", 266 | "@webassemblyjs/wast-printer": "1.12.1" 267 | } 268 | }, 269 | "node_modules/@webassemblyjs/wasm-gen": { 270 | "version": "1.12.1", 271 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", 272 | "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", 273 | "dev": true, 274 | "license": "MIT", 275 | "dependencies": { 276 | "@webassemblyjs/ast": "1.12.1", 277 | "@webassemblyjs/helper-wasm-bytecode": "1.11.6", 278 | "@webassemblyjs/ieee754": "1.11.6", 279 | "@webassemblyjs/leb128": "1.11.6", 280 | "@webassemblyjs/utf8": "1.11.6" 281 | } 282 | }, 283 | "node_modules/@webassemblyjs/wasm-opt": { 284 | "version": "1.12.1", 285 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", 286 | "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", 287 | "dev": true, 288 | "license": "MIT", 289 | "dependencies": { 290 | "@webassemblyjs/ast": "1.12.1", 291 | "@webassemblyjs/helper-buffer": "1.12.1", 292 | "@webassemblyjs/wasm-gen": "1.12.1", 293 | "@webassemblyjs/wasm-parser": "1.12.1" 294 | } 295 | }, 296 | "node_modules/@webassemblyjs/wasm-parser": { 297 | "version": "1.12.1", 298 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", 299 | "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", 300 | "dev": true, 301 | "license": "MIT", 302 | "dependencies": { 303 | "@webassemblyjs/ast": "1.12.1", 304 | "@webassemblyjs/helper-api-error": "1.11.6", 305 | "@webassemblyjs/helper-wasm-bytecode": "1.11.6", 306 | "@webassemblyjs/ieee754": "1.11.6", 307 | "@webassemblyjs/leb128": "1.11.6", 308 | "@webassemblyjs/utf8": "1.11.6" 309 | } 310 | }, 311 | "node_modules/@webassemblyjs/wast-printer": { 312 | "version": "1.12.1", 313 | "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", 314 | "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", 315 | "dev": true, 316 | "license": "MIT", 317 | "dependencies": { 318 | "@webassemblyjs/ast": "1.12.1", 319 | "@xtuc/long": "4.2.2" 320 | } 321 | }, 322 | "node_modules/@webpack-cli/configtest": { 323 | "version": "2.1.1", 324 | "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", 325 | "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", 326 | "dev": true, 327 | "license": "MIT", 328 | "engines": { 329 | "node": ">=14.15.0" 330 | }, 331 | "peerDependencies": { 332 | "webpack": "5.x.x", 333 | "webpack-cli": "5.x.x" 334 | } 335 | }, 336 | "node_modules/@webpack-cli/info": { 337 | "version": "2.0.2", 338 | "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", 339 | "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", 340 | "dev": true, 341 | "license": "MIT", 342 | "engines": { 343 | "node": ">=14.15.0" 344 | }, 345 | "peerDependencies": { 346 | "webpack": "5.x.x", 347 | "webpack-cli": "5.x.x" 348 | } 349 | }, 350 | "node_modules/@webpack-cli/serve": { 351 | "version": "2.0.5", 352 | "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", 353 | "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", 354 | "dev": true, 355 | "license": "MIT", 356 | "engines": { 357 | "node": ">=14.15.0" 358 | }, 359 | "peerDependencies": { 360 | "webpack": "5.x.x", 361 | "webpack-cli": "5.x.x" 362 | }, 363 | "peerDependenciesMeta": { 364 | "webpack-dev-server": { 365 | "optional": true 366 | } 367 | } 368 | }, 369 | "node_modules/@xtuc/ieee754": { 370 | "version": "1.2.0", 371 | "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", 372 | "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", 373 | "dev": true, 374 | "license": "BSD-3-Clause" 375 | }, 376 | "node_modules/@xtuc/long": { 377 | "version": "4.2.2", 378 | "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", 379 | "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", 380 | "dev": true, 381 | "license": "Apache-2.0" 382 | }, 383 | "node_modules/acorn": { 384 | "version": "8.13.0", 385 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", 386 | "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", 387 | "dev": true, 388 | "license": "MIT", 389 | "bin": { 390 | "acorn": "bin/acorn" 391 | }, 392 | "engines": { 393 | "node": ">=0.4.0" 394 | } 395 | }, 396 | "node_modules/acorn-import-attributes": { 397 | "version": "1.9.5", 398 | "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", 399 | "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", 400 | "dev": true, 401 | "license": "MIT", 402 | "peerDependencies": { 403 | "acorn": "^8" 404 | } 405 | }, 406 | "node_modules/ajv": { 407 | "version": "6.12.6", 408 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 409 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 410 | "dev": true, 411 | "license": "MIT", 412 | "dependencies": { 413 | "fast-deep-equal": "^3.1.1", 414 | "fast-json-stable-stringify": "^2.0.0", 415 | "json-schema-traverse": "^0.4.1", 416 | "uri-js": "^4.2.2" 417 | }, 418 | "funding": { 419 | "type": "github", 420 | "url": "https://github.com/sponsors/epoberezkin" 421 | } 422 | }, 423 | "node_modules/ajv-keywords": { 424 | "version": "3.5.2", 425 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", 426 | "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", 427 | "dev": true, 428 | "license": "MIT", 429 | "peerDependencies": { 430 | "ajv": "^6.9.1" 431 | } 432 | }, 433 | "node_modules/ansi-styles": { 434 | "version": "4.3.0", 435 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 436 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 437 | "dev": true, 438 | "license": "MIT", 439 | "dependencies": { 440 | "color-convert": "^2.0.1" 441 | }, 442 | "engines": { 443 | "node": ">=8" 444 | }, 445 | "funding": { 446 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 447 | } 448 | }, 449 | "node_modules/braces": { 450 | "version": "3.0.3", 451 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 452 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 453 | "dev": true, 454 | "license": "MIT", 455 | "dependencies": { 456 | "fill-range": "^7.1.1" 457 | }, 458 | "engines": { 459 | "node": ">=8" 460 | } 461 | }, 462 | "node_modules/browserslist": { 463 | "version": "4.24.2", 464 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", 465 | "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", 466 | "dev": true, 467 | "funding": [ 468 | { 469 | "type": "opencollective", 470 | "url": "https://opencollective.com/browserslist" 471 | }, 472 | { 473 | "type": "tidelift", 474 | "url": "https://tidelift.com/funding/github/npm/browserslist" 475 | }, 476 | { 477 | "type": "github", 478 | "url": "https://github.com/sponsors/ai" 479 | } 480 | ], 481 | "license": "MIT", 482 | "dependencies": { 483 | "caniuse-lite": "^1.0.30001669", 484 | "electron-to-chromium": "^1.5.41", 485 | "node-releases": "^2.0.18", 486 | "update-browserslist-db": "^1.1.1" 487 | }, 488 | "bin": { 489 | "browserslist": "cli.js" 490 | }, 491 | "engines": { 492 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 493 | } 494 | }, 495 | "node_modules/buffer-from": { 496 | "version": "1.1.2", 497 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 498 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 499 | "dev": true, 500 | "license": "MIT" 501 | }, 502 | "node_modules/caniuse-lite": { 503 | "version": "1.0.30001669", 504 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", 505 | "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", 506 | "dev": true, 507 | "funding": [ 508 | { 509 | "type": "opencollective", 510 | "url": "https://opencollective.com/browserslist" 511 | }, 512 | { 513 | "type": "tidelift", 514 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 515 | }, 516 | { 517 | "type": "github", 518 | "url": "https://github.com/sponsors/ai" 519 | } 520 | ], 521 | "license": "CC-BY-4.0" 522 | }, 523 | "node_modules/chalk": { 524 | "version": "4.1.2", 525 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 526 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 527 | "dev": true, 528 | "license": "MIT", 529 | "dependencies": { 530 | "ansi-styles": "^4.1.0", 531 | "supports-color": "^7.1.0" 532 | }, 533 | "engines": { 534 | "node": ">=10" 535 | }, 536 | "funding": { 537 | "url": "https://github.com/chalk/chalk?sponsor=1" 538 | } 539 | }, 540 | "node_modules/chrome-trace-event": { 541 | "version": "1.0.4", 542 | "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", 543 | "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", 544 | "dev": true, 545 | "license": "MIT", 546 | "engines": { 547 | "node": ">=6.0" 548 | } 549 | }, 550 | "node_modules/clone-deep": { 551 | "version": "4.0.1", 552 | "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", 553 | "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", 554 | "dev": true, 555 | "license": "MIT", 556 | "dependencies": { 557 | "is-plain-object": "^2.0.4", 558 | "kind-of": "^6.0.2", 559 | "shallow-clone": "^3.0.0" 560 | }, 561 | "engines": { 562 | "node": ">=6" 563 | } 564 | }, 565 | "node_modules/color-convert": { 566 | "version": "2.0.1", 567 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 568 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 569 | "dev": true, 570 | "license": "MIT", 571 | "dependencies": { 572 | "color-name": "~1.1.4" 573 | }, 574 | "engines": { 575 | "node": ">=7.0.0" 576 | } 577 | }, 578 | "node_modules/color-name": { 579 | "version": "1.1.4", 580 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 581 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 582 | "dev": true, 583 | "license": "MIT" 584 | }, 585 | "node_modules/colorette": { 586 | "version": "2.0.20", 587 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", 588 | "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", 589 | "dev": true, 590 | "license": "MIT" 591 | }, 592 | "node_modules/commander": { 593 | "version": "2.20.3", 594 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 595 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 596 | "dev": true, 597 | "license": "MIT" 598 | }, 599 | "node_modules/cross-spawn": { 600 | "version": "7.0.3", 601 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 602 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 603 | "dev": true, 604 | "license": "MIT", 605 | "dependencies": { 606 | "path-key": "^3.1.0", 607 | "shebang-command": "^2.0.0", 608 | "which": "^2.0.1" 609 | }, 610 | "engines": { 611 | "node": ">= 8" 612 | } 613 | }, 614 | "node_modules/electron-to-chromium": { 615 | "version": "1.5.42", 616 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.42.tgz", 617 | "integrity": "sha512-gIfKavKDw1mhvic9nbzA5lZw8QSHpdMwLwXc0cWidQz9B15pDoDdDH4boIatuFfeoCatb3a/NGL6CYRVFxGZ9g==", 618 | "dev": true, 619 | "license": "ISC" 620 | }, 621 | "node_modules/enhanced-resolve": { 622 | "version": "5.17.1", 623 | "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", 624 | "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", 625 | "dev": true, 626 | "license": "MIT", 627 | "dependencies": { 628 | "graceful-fs": "^4.2.4", 629 | "tapable": "^2.2.0" 630 | }, 631 | "engines": { 632 | "node": ">=10.13.0" 633 | } 634 | }, 635 | "node_modules/envinfo": { 636 | "version": "7.14.0", 637 | "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", 638 | "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", 639 | "dev": true, 640 | "license": "MIT", 641 | "bin": { 642 | "envinfo": "dist/cli.js" 643 | }, 644 | "engines": { 645 | "node": ">=4" 646 | } 647 | }, 648 | "node_modules/es-module-lexer": { 649 | "version": "1.5.4", 650 | "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", 651 | "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", 652 | "dev": true, 653 | "license": "MIT" 654 | }, 655 | "node_modules/escalade": { 656 | "version": "3.2.0", 657 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 658 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 659 | "dev": true, 660 | "license": "MIT", 661 | "engines": { 662 | "node": ">=6" 663 | } 664 | }, 665 | "node_modules/eslint-scope": { 666 | "version": "5.1.1", 667 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", 668 | "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", 669 | "dev": true, 670 | "license": "BSD-2-Clause", 671 | "dependencies": { 672 | "esrecurse": "^4.3.0", 673 | "estraverse": "^4.1.1" 674 | }, 675 | "engines": { 676 | "node": ">=8.0.0" 677 | } 678 | }, 679 | "node_modules/esrecurse": { 680 | "version": "4.3.0", 681 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 682 | "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 683 | "dev": true, 684 | "license": "BSD-2-Clause", 685 | "dependencies": { 686 | "estraverse": "^5.2.0" 687 | }, 688 | "engines": { 689 | "node": ">=4.0" 690 | } 691 | }, 692 | "node_modules/esrecurse/node_modules/estraverse": { 693 | "version": "5.3.0", 694 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 695 | "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 696 | "dev": true, 697 | "license": "BSD-2-Clause", 698 | "engines": { 699 | "node": ">=4.0" 700 | } 701 | }, 702 | "node_modules/estraverse": { 703 | "version": "4.3.0", 704 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", 705 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", 706 | "dev": true, 707 | "license": "BSD-2-Clause", 708 | "engines": { 709 | "node": ">=4.0" 710 | } 711 | }, 712 | "node_modules/events": { 713 | "version": "3.3.0", 714 | "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", 715 | "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", 716 | "dev": true, 717 | "license": "MIT", 718 | "engines": { 719 | "node": ">=0.8.x" 720 | } 721 | }, 722 | "node_modules/fast-deep-equal": { 723 | "version": "3.1.3", 724 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 725 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 726 | "dev": true, 727 | "license": "MIT" 728 | }, 729 | "node_modules/fast-json-stable-stringify": { 730 | "version": "2.1.0", 731 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 732 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 733 | "dev": true, 734 | "license": "MIT" 735 | }, 736 | "node_modules/fastest-levenshtein": { 737 | "version": "1.0.16", 738 | "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", 739 | "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", 740 | "dev": true, 741 | "license": "MIT", 742 | "engines": { 743 | "node": ">= 4.9.1" 744 | } 745 | }, 746 | "node_modules/fill-range": { 747 | "version": "7.1.1", 748 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 749 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 750 | "dev": true, 751 | "license": "MIT", 752 | "dependencies": { 753 | "to-regex-range": "^5.0.1" 754 | }, 755 | "engines": { 756 | "node": ">=8" 757 | } 758 | }, 759 | "node_modules/find-up": { 760 | "version": "4.1.0", 761 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", 762 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", 763 | "dev": true, 764 | "license": "MIT", 765 | "dependencies": { 766 | "locate-path": "^5.0.0", 767 | "path-exists": "^4.0.0" 768 | }, 769 | "engines": { 770 | "node": ">=8" 771 | } 772 | }, 773 | "node_modules/flat": { 774 | "version": "5.0.2", 775 | "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", 776 | "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", 777 | "dev": true, 778 | "license": "BSD-3-Clause", 779 | "bin": { 780 | "flat": "cli.js" 781 | } 782 | }, 783 | "node_modules/function-bind": { 784 | "version": "1.1.2", 785 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 786 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 787 | "dev": true, 788 | "license": "MIT", 789 | "funding": { 790 | "url": "https://github.com/sponsors/ljharb" 791 | } 792 | }, 793 | "node_modules/glob-to-regexp": { 794 | "version": "0.4.1", 795 | "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", 796 | "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", 797 | "dev": true, 798 | "license": "BSD-2-Clause" 799 | }, 800 | "node_modules/graceful-fs": { 801 | "version": "4.2.11", 802 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 803 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 804 | "dev": true, 805 | "license": "ISC" 806 | }, 807 | "node_modules/has-flag": { 808 | "version": "4.0.0", 809 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 810 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 811 | "dev": true, 812 | "license": "MIT", 813 | "engines": { 814 | "node": ">=8" 815 | } 816 | }, 817 | "node_modules/hasown": { 818 | "version": "2.0.2", 819 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 820 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 821 | "dev": true, 822 | "license": "MIT", 823 | "dependencies": { 824 | "function-bind": "^1.1.2" 825 | }, 826 | "engines": { 827 | "node": ">= 0.4" 828 | } 829 | }, 830 | "node_modules/import-local": { 831 | "version": "3.2.0", 832 | "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", 833 | "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", 834 | "dev": true, 835 | "license": "MIT", 836 | "dependencies": { 837 | "pkg-dir": "^4.2.0", 838 | "resolve-cwd": "^3.0.0" 839 | }, 840 | "bin": { 841 | "import-local-fixture": "fixtures/cli.js" 842 | }, 843 | "engines": { 844 | "node": ">=8" 845 | }, 846 | "funding": { 847 | "url": "https://github.com/sponsors/sindresorhus" 848 | } 849 | }, 850 | "node_modules/interpret": { 851 | "version": "3.1.1", 852 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", 853 | "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", 854 | "dev": true, 855 | "license": "MIT", 856 | "engines": { 857 | "node": ">=10.13.0" 858 | } 859 | }, 860 | "node_modules/is-core-module": { 861 | "version": "2.15.1", 862 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", 863 | "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", 864 | "dev": true, 865 | "license": "MIT", 866 | "dependencies": { 867 | "hasown": "^2.0.2" 868 | }, 869 | "engines": { 870 | "node": ">= 0.4" 871 | }, 872 | "funding": { 873 | "url": "https://github.com/sponsors/ljharb" 874 | } 875 | }, 876 | "node_modules/is-number": { 877 | "version": "7.0.0", 878 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 879 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 880 | "dev": true, 881 | "license": "MIT", 882 | "engines": { 883 | "node": ">=0.12.0" 884 | } 885 | }, 886 | "node_modules/is-plain-object": { 887 | "version": "2.0.4", 888 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", 889 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 890 | "dev": true, 891 | "license": "MIT", 892 | "dependencies": { 893 | "isobject": "^3.0.1" 894 | }, 895 | "engines": { 896 | "node": ">=0.10.0" 897 | } 898 | }, 899 | "node_modules/isexe": { 900 | "version": "2.0.0", 901 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 902 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 903 | "dev": true, 904 | "license": "ISC" 905 | }, 906 | "node_modules/isobject": { 907 | "version": "3.0.1", 908 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 909 | "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", 910 | "dev": true, 911 | "license": "MIT", 912 | "engines": { 913 | "node": ">=0.10.0" 914 | } 915 | }, 916 | "node_modules/jest-worker": { 917 | "version": "27.5.1", 918 | "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", 919 | "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", 920 | "dev": true, 921 | "license": "MIT", 922 | "dependencies": { 923 | "@types/node": "*", 924 | "merge-stream": "^2.0.0", 925 | "supports-color": "^8.0.0" 926 | }, 927 | "engines": { 928 | "node": ">= 10.13.0" 929 | } 930 | }, 931 | "node_modules/jest-worker/node_modules/supports-color": { 932 | "version": "8.1.1", 933 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 934 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 935 | "dev": true, 936 | "license": "MIT", 937 | "dependencies": { 938 | "has-flag": "^4.0.0" 939 | }, 940 | "engines": { 941 | "node": ">=10" 942 | }, 943 | "funding": { 944 | "url": "https://github.com/chalk/supports-color?sponsor=1" 945 | } 946 | }, 947 | "node_modules/json-parse-even-better-errors": { 948 | "version": "2.3.1", 949 | "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", 950 | "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", 951 | "dev": true, 952 | "license": "MIT" 953 | }, 954 | "node_modules/json-schema-traverse": { 955 | "version": "0.4.1", 956 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 957 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 958 | "dev": true, 959 | "license": "MIT" 960 | }, 961 | "node_modules/kind-of": { 962 | "version": "6.0.3", 963 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", 964 | "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", 965 | "dev": true, 966 | "license": "MIT", 967 | "engines": { 968 | "node": ">=0.10.0" 969 | } 970 | }, 971 | "node_modules/loader-runner": { 972 | "version": "4.3.0", 973 | "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", 974 | "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", 975 | "dev": true, 976 | "license": "MIT", 977 | "engines": { 978 | "node": ">=6.11.5" 979 | } 980 | }, 981 | "node_modules/locate-path": { 982 | "version": "5.0.0", 983 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", 984 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", 985 | "dev": true, 986 | "license": "MIT", 987 | "dependencies": { 988 | "p-locate": "^4.1.0" 989 | }, 990 | "engines": { 991 | "node": ">=8" 992 | } 993 | }, 994 | "node_modules/merge-stream": { 995 | "version": "2.0.0", 996 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 997 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 998 | "dev": true, 999 | "license": "MIT" 1000 | }, 1001 | "node_modules/micromatch": { 1002 | "version": "4.0.8", 1003 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 1004 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 1005 | "dev": true, 1006 | "license": "MIT", 1007 | "dependencies": { 1008 | "braces": "^3.0.3", 1009 | "picomatch": "^2.3.1" 1010 | }, 1011 | "engines": { 1012 | "node": ">=8.6" 1013 | } 1014 | }, 1015 | "node_modules/mime-db": { 1016 | "version": "1.52.0", 1017 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1018 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1019 | "dev": true, 1020 | "license": "MIT", 1021 | "engines": { 1022 | "node": ">= 0.6" 1023 | } 1024 | }, 1025 | "node_modules/mime-types": { 1026 | "version": "2.1.35", 1027 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1028 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1029 | "dev": true, 1030 | "license": "MIT", 1031 | "dependencies": { 1032 | "mime-db": "1.52.0" 1033 | }, 1034 | "engines": { 1035 | "node": ">= 0.6" 1036 | } 1037 | }, 1038 | "node_modules/neo-async": { 1039 | "version": "2.6.2", 1040 | "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", 1041 | "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", 1042 | "dev": true, 1043 | "license": "MIT" 1044 | }, 1045 | "node_modules/node-releases": { 1046 | "version": "2.0.18", 1047 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", 1048 | "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", 1049 | "dev": true, 1050 | "license": "MIT" 1051 | }, 1052 | "node_modules/p-limit": { 1053 | "version": "2.3.0", 1054 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", 1055 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", 1056 | "dev": true, 1057 | "license": "MIT", 1058 | "dependencies": { 1059 | "p-try": "^2.0.0" 1060 | }, 1061 | "engines": { 1062 | "node": ">=6" 1063 | }, 1064 | "funding": { 1065 | "url": "https://github.com/sponsors/sindresorhus" 1066 | } 1067 | }, 1068 | "node_modules/p-locate": { 1069 | "version": "4.1.0", 1070 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", 1071 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", 1072 | "dev": true, 1073 | "license": "MIT", 1074 | "dependencies": { 1075 | "p-limit": "^2.2.0" 1076 | }, 1077 | "engines": { 1078 | "node": ">=8" 1079 | } 1080 | }, 1081 | "node_modules/p-try": { 1082 | "version": "2.2.0", 1083 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 1084 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 1085 | "dev": true, 1086 | "license": "MIT", 1087 | "engines": { 1088 | "node": ">=6" 1089 | } 1090 | }, 1091 | "node_modules/path-exists": { 1092 | "version": "4.0.0", 1093 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1094 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 1095 | "dev": true, 1096 | "license": "MIT", 1097 | "engines": { 1098 | "node": ">=8" 1099 | } 1100 | }, 1101 | "node_modules/path-key": { 1102 | "version": "3.1.1", 1103 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1104 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1105 | "dev": true, 1106 | "license": "MIT", 1107 | "engines": { 1108 | "node": ">=8" 1109 | } 1110 | }, 1111 | "node_modules/path-parse": { 1112 | "version": "1.0.7", 1113 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1114 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 1115 | "dev": true, 1116 | "license": "MIT" 1117 | }, 1118 | "node_modules/picocolors": { 1119 | "version": "1.1.1", 1120 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1121 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 1122 | "dev": true, 1123 | "license": "ISC" 1124 | }, 1125 | "node_modules/picomatch": { 1126 | "version": "2.3.1", 1127 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1128 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1129 | "dev": true, 1130 | "license": "MIT", 1131 | "engines": { 1132 | "node": ">=8.6" 1133 | }, 1134 | "funding": { 1135 | "url": "https://github.com/sponsors/jonschlinkert" 1136 | } 1137 | }, 1138 | "node_modules/pkg-dir": { 1139 | "version": "4.2.0", 1140 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", 1141 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", 1142 | "dev": true, 1143 | "license": "MIT", 1144 | "dependencies": { 1145 | "find-up": "^4.0.0" 1146 | }, 1147 | "engines": { 1148 | "node": ">=8" 1149 | } 1150 | }, 1151 | "node_modules/punycode": { 1152 | "version": "2.3.1", 1153 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 1154 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 1155 | "dev": true, 1156 | "license": "MIT", 1157 | "engines": { 1158 | "node": ">=6" 1159 | } 1160 | }, 1161 | "node_modules/randombytes": { 1162 | "version": "2.1.0", 1163 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 1164 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 1165 | "dev": true, 1166 | "license": "MIT", 1167 | "dependencies": { 1168 | "safe-buffer": "^5.1.0" 1169 | } 1170 | }, 1171 | "node_modules/rechoir": { 1172 | "version": "0.8.0", 1173 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", 1174 | "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", 1175 | "dev": true, 1176 | "license": "MIT", 1177 | "dependencies": { 1178 | "resolve": "^1.20.0" 1179 | }, 1180 | "engines": { 1181 | "node": ">= 10.13.0" 1182 | } 1183 | }, 1184 | "node_modules/resolve": { 1185 | "version": "1.22.8", 1186 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", 1187 | "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", 1188 | "dev": true, 1189 | "license": "MIT", 1190 | "dependencies": { 1191 | "is-core-module": "^2.13.0", 1192 | "path-parse": "^1.0.7", 1193 | "supports-preserve-symlinks-flag": "^1.0.0" 1194 | }, 1195 | "bin": { 1196 | "resolve": "bin/resolve" 1197 | }, 1198 | "funding": { 1199 | "url": "https://github.com/sponsors/ljharb" 1200 | } 1201 | }, 1202 | "node_modules/resolve-cwd": { 1203 | "version": "3.0.0", 1204 | "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", 1205 | "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", 1206 | "dev": true, 1207 | "license": "MIT", 1208 | "dependencies": { 1209 | "resolve-from": "^5.0.0" 1210 | }, 1211 | "engines": { 1212 | "node": ">=8" 1213 | } 1214 | }, 1215 | "node_modules/resolve-from": { 1216 | "version": "5.0.0", 1217 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", 1218 | "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", 1219 | "dev": true, 1220 | "license": "MIT", 1221 | "engines": { 1222 | "node": ">=8" 1223 | } 1224 | }, 1225 | "node_modules/safe-buffer": { 1226 | "version": "5.2.1", 1227 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1228 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1229 | "dev": true, 1230 | "funding": [ 1231 | { 1232 | "type": "github", 1233 | "url": "https://github.com/sponsors/feross" 1234 | }, 1235 | { 1236 | "type": "patreon", 1237 | "url": "https://www.patreon.com/feross" 1238 | }, 1239 | { 1240 | "type": "consulting", 1241 | "url": "https://feross.org/support" 1242 | } 1243 | ], 1244 | "license": "MIT" 1245 | }, 1246 | "node_modules/schema-utils": { 1247 | "version": "3.3.0", 1248 | "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", 1249 | "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", 1250 | "dev": true, 1251 | "license": "MIT", 1252 | "dependencies": { 1253 | "@types/json-schema": "^7.0.8", 1254 | "ajv": "^6.12.5", 1255 | "ajv-keywords": "^3.5.2" 1256 | }, 1257 | "engines": { 1258 | "node": ">= 10.13.0" 1259 | }, 1260 | "funding": { 1261 | "type": "opencollective", 1262 | "url": "https://opencollective.com/webpack" 1263 | } 1264 | }, 1265 | "node_modules/semver": { 1266 | "version": "7.6.3", 1267 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", 1268 | "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", 1269 | "dev": true, 1270 | "license": "ISC", 1271 | "bin": { 1272 | "semver": "bin/semver.js" 1273 | }, 1274 | "engines": { 1275 | "node": ">=10" 1276 | } 1277 | }, 1278 | "node_modules/serialize-javascript": { 1279 | "version": "6.0.2", 1280 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", 1281 | "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", 1282 | "dev": true, 1283 | "license": "BSD-3-Clause", 1284 | "dependencies": { 1285 | "randombytes": "^2.1.0" 1286 | } 1287 | }, 1288 | "node_modules/shallow-clone": { 1289 | "version": "3.0.1", 1290 | "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", 1291 | "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", 1292 | "dev": true, 1293 | "license": "MIT", 1294 | "dependencies": { 1295 | "kind-of": "^6.0.2" 1296 | }, 1297 | "engines": { 1298 | "node": ">=8" 1299 | } 1300 | }, 1301 | "node_modules/shebang-command": { 1302 | "version": "2.0.0", 1303 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1304 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1305 | "dev": true, 1306 | "license": "MIT", 1307 | "dependencies": { 1308 | "shebang-regex": "^3.0.0" 1309 | }, 1310 | "engines": { 1311 | "node": ">=8" 1312 | } 1313 | }, 1314 | "node_modules/shebang-regex": { 1315 | "version": "3.0.0", 1316 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1317 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1318 | "dev": true, 1319 | "license": "MIT", 1320 | "engines": { 1321 | "node": ">=8" 1322 | } 1323 | }, 1324 | "node_modules/source-map": { 1325 | "version": "0.7.4", 1326 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", 1327 | "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", 1328 | "dev": true, 1329 | "license": "BSD-3-Clause", 1330 | "engines": { 1331 | "node": ">= 8" 1332 | } 1333 | }, 1334 | "node_modules/source-map-support": { 1335 | "version": "0.5.21", 1336 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", 1337 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 1338 | "dev": true, 1339 | "license": "MIT", 1340 | "dependencies": { 1341 | "buffer-from": "^1.0.0", 1342 | "source-map": "^0.6.0" 1343 | } 1344 | }, 1345 | "node_modules/source-map-support/node_modules/source-map": { 1346 | "version": "0.6.1", 1347 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1348 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1349 | "dev": true, 1350 | "license": "BSD-3-Clause", 1351 | "engines": { 1352 | "node": ">=0.10.0" 1353 | } 1354 | }, 1355 | "node_modules/supports-color": { 1356 | "version": "7.2.0", 1357 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1358 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1359 | "dev": true, 1360 | "license": "MIT", 1361 | "dependencies": { 1362 | "has-flag": "^4.0.0" 1363 | }, 1364 | "engines": { 1365 | "node": ">=8" 1366 | } 1367 | }, 1368 | "node_modules/supports-preserve-symlinks-flag": { 1369 | "version": "1.0.0", 1370 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1371 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1372 | "dev": true, 1373 | "license": "MIT", 1374 | "engines": { 1375 | "node": ">= 0.4" 1376 | }, 1377 | "funding": { 1378 | "url": "https://github.com/sponsors/ljharb" 1379 | } 1380 | }, 1381 | "node_modules/tapable": { 1382 | "version": "2.2.1", 1383 | "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", 1384 | "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", 1385 | "dev": true, 1386 | "license": "MIT", 1387 | "engines": { 1388 | "node": ">=6" 1389 | } 1390 | }, 1391 | "node_modules/terser": { 1392 | "version": "5.36.0", 1393 | "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", 1394 | "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", 1395 | "dev": true, 1396 | "license": "BSD-2-Clause", 1397 | "dependencies": { 1398 | "@jridgewell/source-map": "^0.3.3", 1399 | "acorn": "^8.8.2", 1400 | "commander": "^2.20.0", 1401 | "source-map-support": "~0.5.20" 1402 | }, 1403 | "bin": { 1404 | "terser": "bin/terser" 1405 | }, 1406 | "engines": { 1407 | "node": ">=10" 1408 | } 1409 | }, 1410 | "node_modules/terser-webpack-plugin": { 1411 | "version": "5.3.10", 1412 | "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", 1413 | "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", 1414 | "dev": true, 1415 | "license": "MIT", 1416 | "dependencies": { 1417 | "@jridgewell/trace-mapping": "^0.3.20", 1418 | "jest-worker": "^27.4.5", 1419 | "schema-utils": "^3.1.1", 1420 | "serialize-javascript": "^6.0.1", 1421 | "terser": "^5.26.0" 1422 | }, 1423 | "engines": { 1424 | "node": ">= 10.13.0" 1425 | }, 1426 | "funding": { 1427 | "type": "opencollective", 1428 | "url": "https://opencollective.com/webpack" 1429 | }, 1430 | "peerDependencies": { 1431 | "webpack": "^5.1.0" 1432 | }, 1433 | "peerDependenciesMeta": { 1434 | "@swc/core": { 1435 | "optional": true 1436 | }, 1437 | "esbuild": { 1438 | "optional": true 1439 | }, 1440 | "uglify-js": { 1441 | "optional": true 1442 | } 1443 | } 1444 | }, 1445 | "node_modules/to-regex-range": { 1446 | "version": "5.0.1", 1447 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1448 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1449 | "dev": true, 1450 | "license": "MIT", 1451 | "dependencies": { 1452 | "is-number": "^7.0.0" 1453 | }, 1454 | "engines": { 1455 | "node": ">=8.0" 1456 | } 1457 | }, 1458 | "node_modules/ts-loader": { 1459 | "version": "9.5.1", 1460 | "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", 1461 | "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", 1462 | "dev": true, 1463 | "license": "MIT", 1464 | "dependencies": { 1465 | "chalk": "^4.1.0", 1466 | "enhanced-resolve": "^5.0.0", 1467 | "micromatch": "^4.0.0", 1468 | "semver": "^7.3.4", 1469 | "source-map": "^0.7.4" 1470 | }, 1471 | "engines": { 1472 | "node": ">=12.0.0" 1473 | }, 1474 | "peerDependencies": { 1475 | "typescript": "*", 1476 | "webpack": "^5.0.0" 1477 | } 1478 | }, 1479 | "node_modules/typescript": { 1480 | "version": "5.6.3", 1481 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", 1482 | "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", 1483 | "dev": true, 1484 | "license": "Apache-2.0", 1485 | "bin": { 1486 | "tsc": "bin/tsc", 1487 | "tsserver": "bin/tsserver" 1488 | }, 1489 | "engines": { 1490 | "node": ">=14.17" 1491 | } 1492 | }, 1493 | "node_modules/undici-types": { 1494 | "version": "6.19.8", 1495 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 1496 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 1497 | "dev": true, 1498 | "license": "MIT" 1499 | }, 1500 | "node_modules/update-browserslist-db": { 1501 | "version": "1.1.1", 1502 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", 1503 | "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", 1504 | "dev": true, 1505 | "funding": [ 1506 | { 1507 | "type": "opencollective", 1508 | "url": "https://opencollective.com/browserslist" 1509 | }, 1510 | { 1511 | "type": "tidelift", 1512 | "url": "https://tidelift.com/funding/github/npm/browserslist" 1513 | }, 1514 | { 1515 | "type": "github", 1516 | "url": "https://github.com/sponsors/ai" 1517 | } 1518 | ], 1519 | "license": "MIT", 1520 | "dependencies": { 1521 | "escalade": "^3.2.0", 1522 | "picocolors": "^1.1.0" 1523 | }, 1524 | "bin": { 1525 | "update-browserslist-db": "cli.js" 1526 | }, 1527 | "peerDependencies": { 1528 | "browserslist": ">= 4.21.0" 1529 | } 1530 | }, 1531 | "node_modules/uri-js": { 1532 | "version": "4.4.1", 1533 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1534 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1535 | "dev": true, 1536 | "license": "BSD-2-Clause", 1537 | "dependencies": { 1538 | "punycode": "^2.1.0" 1539 | } 1540 | }, 1541 | "node_modules/watchpack": { 1542 | "version": "2.4.2", 1543 | "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", 1544 | "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", 1545 | "dev": true, 1546 | "license": "MIT", 1547 | "dependencies": { 1548 | "glob-to-regexp": "^0.4.1", 1549 | "graceful-fs": "^4.1.2" 1550 | }, 1551 | "engines": { 1552 | "node": ">=10.13.0" 1553 | } 1554 | }, 1555 | "node_modules/webpack": { 1556 | "version": "5.95.0", 1557 | "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", 1558 | "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", 1559 | "dev": true, 1560 | "license": "MIT", 1561 | "dependencies": { 1562 | "@types/estree": "^1.0.5", 1563 | "@webassemblyjs/ast": "^1.12.1", 1564 | "@webassemblyjs/wasm-edit": "^1.12.1", 1565 | "@webassemblyjs/wasm-parser": "^1.12.1", 1566 | "acorn": "^8.7.1", 1567 | "acorn-import-attributes": "^1.9.5", 1568 | "browserslist": "^4.21.10", 1569 | "chrome-trace-event": "^1.0.2", 1570 | "enhanced-resolve": "^5.17.1", 1571 | "es-module-lexer": "^1.2.1", 1572 | "eslint-scope": "5.1.1", 1573 | "events": "^3.2.0", 1574 | "glob-to-regexp": "^0.4.1", 1575 | "graceful-fs": "^4.2.11", 1576 | "json-parse-even-better-errors": "^2.3.1", 1577 | "loader-runner": "^4.2.0", 1578 | "mime-types": "^2.1.27", 1579 | "neo-async": "^2.6.2", 1580 | "schema-utils": "^3.2.0", 1581 | "tapable": "^2.1.1", 1582 | "terser-webpack-plugin": "^5.3.10", 1583 | "watchpack": "^2.4.1", 1584 | "webpack-sources": "^3.2.3" 1585 | }, 1586 | "bin": { 1587 | "webpack": "bin/webpack.js" 1588 | }, 1589 | "engines": { 1590 | "node": ">=10.13.0" 1591 | }, 1592 | "funding": { 1593 | "type": "opencollective", 1594 | "url": "https://opencollective.com/webpack" 1595 | }, 1596 | "peerDependenciesMeta": { 1597 | "webpack-cli": { 1598 | "optional": true 1599 | } 1600 | } 1601 | }, 1602 | "node_modules/webpack-cli": { 1603 | "version": "5.1.4", 1604 | "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", 1605 | "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", 1606 | "dev": true, 1607 | "license": "MIT", 1608 | "dependencies": { 1609 | "@discoveryjs/json-ext": "^0.5.0", 1610 | "@webpack-cli/configtest": "^2.1.1", 1611 | "@webpack-cli/info": "^2.0.2", 1612 | "@webpack-cli/serve": "^2.0.5", 1613 | "colorette": "^2.0.14", 1614 | "commander": "^10.0.1", 1615 | "cross-spawn": "^7.0.3", 1616 | "envinfo": "^7.7.3", 1617 | "fastest-levenshtein": "^1.0.12", 1618 | "import-local": "^3.0.2", 1619 | "interpret": "^3.1.1", 1620 | "rechoir": "^0.8.0", 1621 | "webpack-merge": "^5.7.3" 1622 | }, 1623 | "bin": { 1624 | "webpack-cli": "bin/cli.js" 1625 | }, 1626 | "engines": { 1627 | "node": ">=14.15.0" 1628 | }, 1629 | "funding": { 1630 | "type": "opencollective", 1631 | "url": "https://opencollective.com/webpack" 1632 | }, 1633 | "peerDependencies": { 1634 | "webpack": "5.x.x" 1635 | }, 1636 | "peerDependenciesMeta": { 1637 | "@webpack-cli/generators": { 1638 | "optional": true 1639 | }, 1640 | "webpack-bundle-analyzer": { 1641 | "optional": true 1642 | }, 1643 | "webpack-dev-server": { 1644 | "optional": true 1645 | } 1646 | } 1647 | }, 1648 | "node_modules/webpack-cli/node_modules/commander": { 1649 | "version": "10.0.1", 1650 | "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", 1651 | "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", 1652 | "dev": true, 1653 | "license": "MIT", 1654 | "engines": { 1655 | "node": ">=14" 1656 | } 1657 | }, 1658 | "node_modules/webpack-merge": { 1659 | "version": "5.10.0", 1660 | "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", 1661 | "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", 1662 | "dev": true, 1663 | "license": "MIT", 1664 | "dependencies": { 1665 | "clone-deep": "^4.0.1", 1666 | "flat": "^5.0.2", 1667 | "wildcard": "^2.0.0" 1668 | }, 1669 | "engines": { 1670 | "node": ">=10.0.0" 1671 | } 1672 | }, 1673 | "node_modules/webpack-sources": { 1674 | "version": "3.2.3", 1675 | "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", 1676 | "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", 1677 | "dev": true, 1678 | "license": "MIT", 1679 | "engines": { 1680 | "node": ">=10.13.0" 1681 | } 1682 | }, 1683 | "node_modules/which": { 1684 | "version": "2.0.2", 1685 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1686 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1687 | "dev": true, 1688 | "license": "ISC", 1689 | "dependencies": { 1690 | "isexe": "^2.0.0" 1691 | }, 1692 | "bin": { 1693 | "node-which": "bin/node-which" 1694 | }, 1695 | "engines": { 1696 | "node": ">= 8" 1697 | } 1698 | }, 1699 | "node_modules/wildcard": { 1700 | "version": "2.0.1", 1701 | "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", 1702 | "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", 1703 | "dev": true, 1704 | "license": "MIT" 1705 | } 1706 | } 1707 | } 1708 | -------------------------------------------------------------------------------- /test/integration/webpack-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-ts", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "build": "webpack" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "description": "", 12 | "devDependencies": { 13 | "ts-loader": "^9.5.1", 14 | "typescript": "^5.6.3", 15 | "webpack": "^5.95.0", 16 | "webpack-cli": "^5.1.4" 17 | }, 18 | "dependencies": { 19 | "@geolonia/normalize-japanese-addresses": "file:../../.." 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/integration/webpack-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { normalize } from '@geolonia/normalize-japanese-addresses'; 2 | 3 | (async () => { 4 | const res = await normalize('渋谷区道玄坂1-10-8'); 5 | if (res.pref !== '東京都') { 6 | throw new Error(`Expected 東京都 but got ${res.pref}`); 7 | } 8 | })() 9 | .then(() => { 10 | console.log('OK'); 11 | process.exit(0); 12 | }) 13 | .catch((err) => { 14 | console.error(err); 15 | process.exit(1); 16 | }); 17 | -------------------------------------------------------------------------------- /test/integration/webpack-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "noImplicitAny": true, 5 | "module": "es6", 6 | "target": "es5", 7 | "jsx": "react", 8 | "allowJs": true, 9 | "moduleResolution": "node" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/integration/webpack-ts/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.tsx?$/, 9 | use: 'ts-loader', 10 | exclude: /node_modules/, 11 | }, 12 | ], 13 | }, 14 | resolve: { 15 | extensions: ['.tsx', '.ts', '.js'], 16 | }, 17 | output: { 18 | filename: 'bundle.js', 19 | path: path.resolve(__dirname, 'dist'), 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /test/integration/webpack.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'node:test' 2 | import assert from 'node:assert' 3 | 4 | import { promisify } from 'node:util' 5 | import { exec as execCb } from 'node:child_process' 6 | import path from 'node:path' 7 | 8 | const exec = promisify(execCb) 9 | 10 | describe(`webpack with TS`, { timeout: 60_000 }, () => { 11 | test(`build & run`, async () => { 12 | const cwd = path.join(import.meta.dirname, `webpack-ts`) 13 | await exec(`npm install`, { cwd }) 14 | await exec(`npm run build`, { cwd }) 15 | const res = await exec(`node ./dist/bundle.js`, { cwd }) 16 | 17 | assert.strictEqual(res.stdout.trim(), 'OK') 18 | assert.strictEqual(res.stderr, '') 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /test/main/filesystem-api.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test, before, after } from 'node:test' 2 | import path from 'node:path' 3 | import os from 'node:os' 4 | import fs from 'node:fs' 5 | import assert from 'node:assert' 6 | import { pipeline } from 'node:stream/promises' 7 | import { fetch } from 'undici' 8 | import { normalize, config } from '../../src/main-node' 9 | import { assertMatchCloseTo } from '../helpers' 10 | 11 | async function downloadFile(file: string, destDir: string) { 12 | const resp = await fetch(`https://japanese-addresses-v2.geoloniamaps.com/api/${file}`) 13 | const outputFile = path.join(destDir, file) 14 | await fs.promises.mkdir(path.dirname(outputFile), { recursive: true }) 15 | const writer = fs.createWriteStream(outputFile) 16 | if (!resp.body) { throw new Error('No body') } 17 | await pipeline(resp.body, writer) 18 | } 19 | 20 | describe(`API stored in filesystem`, () => { 21 | let tmpdir: string 22 | before(async () => { 23 | tmpdir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'qtile-update-csv-')) 24 | const files = [ 25 | 'ja.json', 26 | 'ja/東京都/渋谷区.json', 27 | 'ja/東京都/渋谷区-住居表示.txt', 28 | ] 29 | for (const file of files) { 30 | await downloadFile(file, tmpdir) 31 | } 32 | 33 | config.japaneseAddressesApi = `file://${tmpdir}/ja` 34 | }) 35 | 36 | after(async () => { 37 | await fs.promises.rm(tmpdir, { recursive: true, force: true }) 38 | }) 39 | 40 | test(`基本的に動く`, async () => { 41 | const res = await normalize('渋谷区道玄坂1-10-8') 42 | assertMatchCloseTo(res, { 43 | pref: '東京都', 44 | city: '渋谷区', 45 | town: '道玄坂一丁目', 46 | level: 8, 47 | }) 48 | }) 49 | 50 | test(`用意されていないエリアはエラーになる`, async () => { 51 | try { 52 | await normalize('東京都千代田区') 53 | } catch (e) { 54 | assert.strictEqual(e.code, 'ENOENT') 55 | } 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /test/main/main.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'node:test' 2 | import assert from 'node:assert' 3 | import { normalize } from '../../src/main-node' 4 | import { assertMatchCloseTo } from '../helpers' 5 | 6 | describe(`basic tests`, () => { 7 | test('It should get the level `1` with `神奈川県横浜市港北区大豆戸町17番地11`', async () => { 8 | const res = await normalize('神奈川県横浜市港北区大豆戸町17番地11', { 9 | level: 1, 10 | }) 11 | assertMatchCloseTo(res, { 12 | pref: '神奈川県', 13 | level: 1, 14 | }) 15 | }) 16 | 17 | test('It should get the level `2` with `神奈川県横浜市港北区大豆戸町17番地11`', async () => { 18 | const res = await normalize('神奈川県横浜市港北区大豆戸町17番地11', { 19 | level: 2, 20 | }) 21 | assertMatchCloseTo(res, { 22 | pref: '神奈川県', 23 | city: '横浜市港北区', 24 | level: 2, 25 | }) 26 | }) 27 | 28 | test('It should get the level `3` with `神奈川県横浜市港北区大豆戸町17番地11`', async () => { 29 | const res = await normalize('神奈川県横浜市港北区大豆戸町17番地11', { 30 | level: 3, 31 | }) 32 | assertMatchCloseTo(res, { 33 | pref: '神奈川県', 34 | city: '横浜市港北区', 35 | town: '大豆戸町', 36 | other: '17-11', 37 | level: 3, 38 | }) 39 | }) 40 | 41 | test('It should get the level `2` with `神奈川県横浜市港北区`', async () => { 42 | const res = await normalize('神奈川県横浜市港北区', { 43 | level: 3, 44 | }) 45 | assertMatchCloseTo(res, { 46 | pref: '神奈川県', 47 | city: '横浜市港北区', 48 | level: 2, 49 | }) 50 | }) 51 | 52 | test('It should get the level `1` with `神奈川県`', async () => { 53 | const res = await normalize('神奈川県', { 54 | level: 3, 55 | }) 56 | assertMatchCloseTo(res, { 57 | pref: '神奈川県', 58 | level: 1, 59 | }) 60 | }) 61 | 62 | test('It should get the level `1` with `神奈川県あいうえお市`', async () => { 63 | const res = await normalize('神奈川県あいうえお市') 64 | assertMatchCloseTo(res, { 65 | pref: '神奈川県', 66 | level: 1, 67 | }) 68 | }) 69 | 70 | test('It should get the level `2` with `東京都港区あいうえお`', async () => { 71 | const res = await normalize('東京都港区あいうえお') 72 | assertMatchCloseTo(res, { 73 | pref: '東京都', 74 | city: '港区', 75 | level: 2, 76 | }) 77 | }) 78 | 79 | test('It should get the level `0` with `あいうえお`', async () => { 80 | const res = await normalize('あいうえお') 81 | assertMatchCloseTo(res, { 82 | other: 'あいうえお', 83 | level: 0, 84 | }) 85 | }) 86 | 87 | describe('東京都江東区豊洲一丁目2-27 のパターンテスト', () => { 88 | const addresses = [ 89 | '東京都江東区豊洲1丁目2-27', 90 | '東京都江東区豊洲 1丁目2-27', 91 | '東京都江東区豊洲 1-2-27', 92 | '東京都 江東区 豊洲 1-2-27', 93 | '東京都江東区豊洲 1ー2ー27', 94 | '東京都江東区豊洲 一丁目2-27', 95 | '江東区豊洲 一丁目2-27', 96 | ] 97 | for (const address of addresses) { 98 | test(address, async () => { 99 | const res = await normalize(address) 100 | assertMatchCloseTo(res, { 101 | pref: '東京都', 102 | city: '江東区', 103 | town: '豊洲一丁目', 104 | addr: '2-27', 105 | level: 8, 106 | point: { 107 | lat: 35.661166758, 108 | lng: 139.793685144, 109 | level: 8, 110 | }, 111 | }) 112 | }) 113 | } 114 | }) 115 | 116 | test('東京都町田市木曽東4丁目14-イ22 ジオロニアマンション', async () => { 117 | const res = await normalize( 118 | '東京都町田市木曽東四丁目14ーイ22 ジオロニアマンション', 119 | ) 120 | assertMatchCloseTo(res, { 121 | other: '14-イ22 ジオロニアマンション', 122 | }) 123 | }) 124 | 125 | test('東京都町田市木曽東4丁目14-A22 ジオロニアマンション', async () => { 126 | const res = await normalize( 127 | '東京都町田市木曽東四丁目14ーA22 ジオロニアマンション', 128 | ) 129 | assertMatchCloseTo(res, { 130 | other: '14-A22 ジオロニアマンション', 131 | }) 132 | }) 133 | 134 | test('東京都町田市木曽東4丁目一四━A二二 ジオロニアマンション', async () => { 135 | const res = await normalize( 136 | '東京都町田市木曽東四丁目一四━A二二 ジオロニアマンション', 137 | ) 138 | assertMatchCloseTo(res, { 139 | other: '14-A22 ジオロニアマンション', 140 | }) 141 | }) 142 | 143 | test('東京都江東区豊洲 四-2-27', async () => { 144 | const res = await normalize('東京都江東区豊洲 四-2-27') 145 | assertMatchCloseTo(res, { 146 | town: '豊洲四丁目', 147 | }) 148 | }) 149 | 150 | describe('石川県七尾市藤橋町亥45番地1 のパターンテスト', () => { 151 | const addresses = [ 152 | '石川県七尾市藤橋町亥45番地1', 153 | '石川県七尾市藤橋町亥四十五番地1', 154 | '石川県七尾市藤橋町 亥 四十五番地1', 155 | '石川県七尾市藤橋町 亥 45-1', 156 | '七尾市藤橋町 亥 45-1', 157 | ] 158 | for (const address of addresses) { 159 | test(address, async () => { 160 | const res = await normalize(address) 161 | assertMatchCloseTo(res, { 162 | pref: '石川県', 163 | city: '七尾市', 164 | town: '藤橋町亥', 165 | addr: '45-1', 166 | level: 8, 167 | point: { 168 | lat: 37.043108, 169 | lng: 136.967296, 170 | level: 2, 171 | }, 172 | }) 173 | }) 174 | } 175 | }) 176 | 177 | test('should handle unicode normalization', async () => { 178 | const address = `茨城県つくば市筑穂1丁目10−4`.normalize('NFKD') 179 | const resp = await normalize(address) 180 | assert.strictEqual(resp.city, 'つくば市') 181 | }) 182 | 183 | test('町丁目名が判別できなかった場合、残った住所には漢数字->数字などの変換処理を施さない', async () => { 184 | const res = await normalize('北海道滝川市一の坂町西') 185 | assert.strictEqual(res.level, 2) 186 | assert.strictEqual(res.town, undefined) 187 | assert.strictEqual(res.other, '一の坂町西') 188 | }) 189 | 190 | test('丁目の数字だけあるときは正しく「一丁目」まで補充できる', async () => { 191 | const res = await normalize('東京都文京区小石川1') 192 | assert.strictEqual(res.town, '小石川一丁目') 193 | assert.strictEqual(res.other, '') 194 | }) 195 | 196 | test('丁目の数字だけあるときは正しく「一丁目」まで補充できる(以降も対応)', async () => { 197 | const res = await normalize('東京都文京区小石川1ビル名') 198 | assert.strictEqual(res.town, '小石川一丁目') 199 | assert.strictEqual(res.other, 'ビル名') 200 | }) 201 | 202 | describe('旧漢字対応', () => { 203 | test('亞 -> 亜', async () => { 204 | const addresses = ['宮城県大崎市古川大崎東亞', '宮城県大崎市古川大崎東亜'] 205 | for (const address of addresses) { 206 | const res = await normalize(address) 207 | assert.strictEqual(res.town, '古川大崎字東亜') 208 | assert.strictEqual(res.level, 3) 209 | } 210 | }) 211 | 212 | test('澤 -> 沢', async () => { 213 | const addresses = [ 214 | '東京都西多摩郡奥多摩町海沢', 215 | '東京都西多摩郡奥多摩町海澤', 216 | ] 217 | for (const address of addresses) { 218 | const res = await normalize(address) 219 | assert.strictEqual(res.town, '海澤') 220 | assert.strictEqual(res.level, 3) 221 | } 222 | }) 223 | 224 | test('麩 -> 麸', async () => { 225 | const addresses = ['愛知県津島市池麩町', '愛知県津島市池麸町'] 226 | for (const address of addresses) { 227 | const res = await normalize(address) 228 | assert.strictEqual(res.town, '池麸町') 229 | assert.strictEqual(res.level, 3) 230 | } 231 | }) 232 | 233 | test('驒 -> 騨', async () => { 234 | const addresses = ['岐阜県飛驒市', '岐阜県飛騨市'] 235 | for (const address of addresses) { 236 | const res = await normalize(address) 237 | assert.strictEqual(res.city, '飛騨市') 238 | assert.strictEqual(res.level, 2) 239 | } 240 | }) 241 | }) 242 | 243 | test('柿碕町|柿さき町', async () => { 244 | const addresses = ['愛知県安城市柿さき町', '愛知県安城市柿碕町'] 245 | for (const address of addresses) { 246 | const res = await normalize(address) 247 | assert.strictEqual(res.town, '柿碕町') 248 | assert.strictEqual(res.level, 3) 249 | } 250 | }) 251 | 252 | describe('漢数字の小字のケース', () => { 253 | test('愛知県豊田市西丹波町三五十', async () => { 254 | const address = '愛知県豊田市西丹波町三五十' 255 | const res = await normalize(address) 256 | assertMatchCloseTo(res, { 257 | town: '西丹波町', 258 | other: '三五十', 259 | level: 3, 260 | }) 261 | }) 262 | 263 | test('広島県府中市栗柄町名字八五十2459 小字以降は現在のところ無視される', async () => { 264 | const address = '広島県府中市栗柄町名字八五十2459' 265 | const res = await normalize(address) 266 | assertMatchCloseTo(res, { 267 | town: '栗柄町', 268 | other: '名字八五十2459', 269 | level: 3, 270 | }) 271 | }) 272 | }) 273 | }) 274 | -------------------------------------------------------------------------------- /test/main/metadata.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from 'node:test' 2 | import assert from 'node:assert' 3 | import { normalize } from '../../src/main-node' 4 | 5 | describe(`metadata tests`, () => { 6 | test(`all fields are present`, async () => { 7 | const res = await normalize('渋谷区道玄坂1-10-8') 8 | assert.strictEqual(res.metadata.input, '渋谷区道玄坂1-10-8') 9 | 10 | assert.strictEqual(res.metadata.prefecture?.code, 130001) 11 | assert.strictEqual(res.metadata.prefecture?.pref, '東京都') 12 | assert.strictEqual(res.metadata.prefecture?.pref_k, 'トウキョウト') 13 | assert.strictEqual(res.metadata.prefecture?.pref_r, 'Tokyo') 14 | assert.ok(!('cities' in res.metadata.prefecture)) 15 | 16 | assert.strictEqual(res.metadata.city?.code, 131130) 17 | assert.strictEqual(res.metadata.city?.city, '渋谷区') 18 | assert.strictEqual(res.metadata.city?.city_k, 'シブヤク') 19 | assert.strictEqual(res.metadata.city?.city_r, 'Shibuya-ku') 20 | 21 | assert.strictEqual(res.metadata.machiAza?.oaza_cho, '道玄坂') 22 | assert.strictEqual(res.metadata.machiAza?.oaza_cho_k, 'ドウゲンザカ') 23 | assert.strictEqual(res.metadata.machiAza?.oaza_cho_r, 'Dogenzaka') 24 | assert.strictEqual(res.metadata.machiAza?.chome_n, 1) 25 | 26 | assert.strictEqual(res.metadata.rsdt?.blk_num, '10') 27 | assert.strictEqual(res.metadata.rsdt?.rsdt_num, '8') 28 | 29 | assert.strictEqual(res.metadata.chiban, undefined) 30 | }) 31 | 32 | test(`the appropriate fields are not set if level is set`, async () => { 33 | const res = await normalize('渋谷区道玄坂1-10-8', { level: 2 }) 34 | assert.strictEqual(res.metadata.input, '渋谷区道玄坂1-10-8') 35 | 36 | assert.ok(res.metadata.prefecture) 37 | assert.ok(res.metadata.city) 38 | 39 | assert.strictEqual(res.metadata.machiAza, undefined) 40 | assert.strictEqual(res.metadata.rsdt, undefined) 41 | assert.strictEqual(res.metadata.chiban, undefined) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /test/run.ts: -------------------------------------------------------------------------------- 1 | import { spec as specReporter } from 'node:test/reporters'; 2 | import { run } from 'node:test'; 3 | import process from 'node:process'; 4 | import { finished } from 'node:stream/promises'; 5 | import path from 'node:path'; 6 | import { glob } from 'glob'; 7 | 8 | async function main(base: string, ...testNamePatterns: string[]) { 9 | // node-glob doesn't work with Windows paths, but it does work on Windows 10 | // if you give it a POSIX path. 11 | const files = await glob(path.posix.join('test', base, '**/*.test.ts'), { 12 | absolute: true, 13 | }); 14 | const testStream = run({ 15 | files, 16 | testNamePatterns, 17 | }); 18 | testStream.on('test:fail', () => { 19 | process.exitCode = 1 20 | }); 21 | const outStream = testStream.compose(new specReporter()).pipe(process.stdout); 22 | await finished(outStream); 23 | } 24 | 25 | main(process.argv[2], ...process.argv.slice(3)) 26 | .then(() => { 27 | process.exit() 28 | }) 29 | .catch((e) => { 30 | console.error(e) 31 | process.exit(1) 32 | }) 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "target": "es6", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "declaration": true 17 | }, 18 | "include": ["src"], 19 | "exclude": ["__test__", "src/**/*.test.ts", "dist", "node_modules"] 20 | } 21 | --------------------------------------------------------------------------------