├── .dev.vars.template ├── .github └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── pnpm-lock.yaml ├── public ├── favicon.ico ├── index.css ├── index.html ├── index.js ├── logo.svg └── og.svg ├── scripts ├── backup.go ├── banner.go ├── build.bat ├── build.sh ├── config.go ├── cron.go ├── go.mod ├── install.go ├── log.go ├── main.go ├── menu.go ├── network.go ├── types.go ├── uninstall.go └── utils.go ├── src ├── constants.ts ├── env.d.ts ├── index.ts ├── scheduled.ts ├── services │ ├── __tests__ │ │ └── hosts.test.ts │ └── hosts.ts └── types.ts ├── tsconfig.json ├── vitest.config.ts └── wrangler.toml /.dev.vars.template: -------------------------------------------------------------------------------- 1 | [vars] 2 | API_KEY = "" 3 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Go Binaries 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write # 添加写入权限 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: "1.22" # Updated to latest stable version 23 | 24 | - name: Build binaries 25 | working-directory: ./scripts 26 | run: | 27 | # Windows 版本 28 | GOOS=windows GOARCH=amd64 go build -ldflags="-s -w -X main.Version=${GITHUB_REF_NAME}" -o ../github-hosts.windows-amd64.exe . 29 | GOOS=windows GOARCH=386 go build -ldflags="-s -w -X main.Version=${GITHUB_REF_NAME}" -o ../github-hosts.windows-386.exe . 30 | 31 | # macOS 版本 32 | GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w -X main.Version=${GITHUB_REF_NAME}" -o ../github-hosts.darwin-amd64 . 33 | GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w -X main.Version=${GITHUB_REF_NAME}" -o ../github-hosts.darwin-arm64 . 34 | 35 | # Linux 版本 36 | GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X main.Version=${GITHUB_REF_NAME}" -o ../github-hosts.linux-amd64 . 37 | GOOS=linux GOARCH=386 go build -ldflags="-s -w -X main.Version=${GITHUB_REF_NAME}" -o ../github-hosts.linux-386 . 38 | GOOS=linux GOARCH=arm64 go build -ldflags="-s -w -X main.Version=${GITHUB_REF_NAME}" -o ../github-hosts.linux-arm64 . 39 | 40 | cd .. # Return to root directory for zip operations 41 | 42 | # 创建 ZIP 包(包含二进制文件) 43 | zip -j github-hosts.windows-amd64.zip github-hosts.windows-amd64.exe 44 | zip -j github-hosts.windows-386.zip github-hosts.windows-386.exe 45 | zip -j github-hosts.darwin-amd64.zip github-hosts.darwin-amd64 46 | zip -j github-hosts.darwin-arm64.zip github-hosts.darwin-arm64 47 | zip -j github-hosts.linux-amd64.zip github-hosts.linux-amd64 48 | zip -j github-hosts.linux-386.zip github-hosts.linux-386 49 | zip -j github-hosts.linux-arm64.zip github-hosts.linux-arm64 50 | 51 | - name: Create Release 52 | uses: softprops/action-gh-release@v2 53 | with: 54 | files: | 55 | github-hosts.windows-amd64.exe 56 | github-hosts.windows-386.exe 57 | github-hosts.darwin-amd64 58 | github-hosts.darwin-arm64 59 | github-hosts.linux-amd64 60 | github-hosts.linux-386 61 | github-hosts.linux-arm64 62 | github-hosts.windows-amd64.zip 63 | github-hosts.windows-386.zip 64 | github-hosts.darwin-amd64.zip 65 | github-hosts.darwin-arm64.zip 66 | github-hosts.linux-amd64.zip 67 | github-hosts.linux-386.zip 68 | github-hosts.linux-arm64.zip 69 | draft: true 70 | prerelease: false 71 | generate_release_notes: true 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # prod 2 | dist/ 3 | 4 | # dev 5 | .yarn/ 6 | !.yarn/releases 7 | .vscode/* 8 | !.vscode/launch.json 9 | !.vscode/*.code-snippets 10 | .idea/workspace.xml 11 | .idea/usage.statistics.xml 12 | .idea/shelf 13 | 14 | # deps 15 | node_modules/ 16 | .wrangler 17 | 18 | # env 19 | .env 20 | .env.production 21 | .dev.vars 22 | 23 | 24 | # logs 25 | logs/ 26 | *.log 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | pnpm-debug.log* 31 | lerna-debug.log* 32 | 33 | # misc 34 | .DS_Store 35 | .idea 36 | 37 | build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 TinsFox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | github-hosts logo 3 |

github-hosts

4 |

GitHub 访问加速,解决 GitHub 访问慢的问题。使用 Cloudflare Workers 和公共 DNS API 来获取 IP 地址。

5 |
6 | 7 | ## 特性 8 | 9 | - 🚀 使用 Cloudflare Workers 部署,无需服务器 10 | - 🌍 多 DNS 服务支持(Cloudflare DNS、Google DNS) 11 | - ⚡️ 每 60 分钟自动更新 DNS 记录 12 | - 💾 使用 Cloudflare KV 存储数据 13 | - 🔄 提供多种使用方式(脚本、手动、工具) 14 | - 📡 提供 REST API 接口 15 | 16 | ## 使用方法 17 | 18 | ### 1. 命令行工具(推荐) 19 | 20 | #### MacOS 用户 21 | ```bash 22 | sudo curl -fsSL https://github.com/TinsFox/github-hosts/releases/download/v0.0.1/github-hosts.darwin-arm64 -o github-hosts && sudo chmod +x ./github-hosts && ./github-hosts 23 | ``` 24 | 25 | > [!IMPORTANT] 26 | > Windows 与 Linux 的脚本还没有经过测试,遇到问题请提 issue 27 | > Windows 运行会报错(短期没有计划修复),详见 https://github.com/TinsFox/github-hosts/issues/9#issuecomment-2784579629 28 | 29 | #### Windows 用户 30 | 在管理员权限的 PowerShell 中执行: 31 | ```powershell 32 | irm https://github.com/TinsFox/github-hosts/releases/download/v0.0.1/github-hosts.windows-amd64.exe | iex 33 | ``` 34 | 35 | #### Linux 用户 36 | ```bash 37 | sudo curl -fsSL https://github.com/TinsFox/github-hosts/releases/download/v0.0.1/github-hosts.linux-amd64 -o github-hosts && sudo chmod +x ./github-hosts && ./github-hosts 38 | ``` 39 | 40 | > 更多版本请查看 [Release 页面](https://github.com/TinsFox/github-hosts/releases) 41 | 42 | ### 2. SwitchHosts 工具 43 | 44 | 1. 下载 [SwitchHosts](https://github.com/oldj/SwitchHosts) 45 | 2. 添加规则: 46 | - 方案名:GitHub Hosts 47 | - 类型:远程 48 | - URL:`https://github-hosts.tinsfox.com/hosts` 49 | - 自动更新:1 小时 50 | 51 | ### 3. 手动更新 52 | 53 | 1. 获取 hosts:访问 [https://github-hosts.tinsfox.com/hosts](https://github-hosts.tinsfox.com/hosts) 54 | 2. 更新本地 hosts 文件: 55 | - Windows:`C:\Windows\System32\drivers\etc\hosts` 56 | - MacOS/Linux:`/etc/hosts` 57 | 3. 刷新 DNS: 58 | - Windows:`ipconfig /flushdns` 59 | - MacOS:`sudo killall -HUP mDNSResponder` 60 | - Linux:`sudo systemd-resolve --flush-caches` 61 | 62 | ## API 文档 63 | 64 | - `GET /hosts` - 获取 hosts 文件内容 65 | - `GET /hosts.json` - 获取 JSON 格式的数据 66 | - `GET /{domain}` - 获取指定域名的实时 DNS 解析结果 67 | - `POST /reset` - 清空缓存并重新获取所有数据(需要 API 密钥) 68 | 69 | ## 常见问题 70 | 71 | ### 权限问题 72 | - Windows:需要以管理员身份运行 73 | - MacOS/Linux:需要 sudo 权限 74 | 75 | ### 定时任务未生效 76 | - Windows:检查任务计划程序中的 "GitHub Hosts Updater" 77 | - MacOS/Linux:使用 `crontab -l` 检查 78 | 79 | ### 更新失败 80 | - 检查日志:`~/.github-hosts/logs/update.log` 81 | - 确保网络连接和文件权限正常 82 | 83 | ## 部署指南 84 | 85 | 1. Fork 本项目 86 | 2. 创建 Cloudflare Workers 账号 87 | 3. 安装并部署: 88 | ```bash 89 | pnpm install 90 | pnpm run dev # 本地开发 91 | pnpm run deploy # 部署到 Cloudflare 92 | ``` 93 | 94 | [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/TinsFox/github-hosts) 95 | 96 | ## 鸣谢 97 | 98 | - [GitHub520](https://github.com/521xueweihan/GitHub520) 99 | - [![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source") 100 | 101 | ## 许可证 102 | 103 | [MIT](./LICENSE) 104 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-hosts", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "dev": "wrangler dev", 6 | "deploy": "wrangler deploy --minify", 7 | "test": "vitest", 8 | "test:coverage": "vitest run --coverage", 9 | "release": "bash scripts/build.sh" 10 | }, 11 | "dependencies": { 12 | "hono": "^4.6.7" 13 | }, 14 | "license": "MIT", 15 | "devDependencies": { 16 | "@cloudflare/vitest-pool-workers": "^0.5.22", 17 | "@cloudflare/workers-types": "^4.20241022.0", 18 | "@vitest/coverage-v8": "^2.1.3", 19 | "happy-dom": "^15.7.4", 20 | "vitest": "^2.1.3", 21 | "wrangler": "^3.83.0" 22 | } 23 | } -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | hono: 12 | specifier: ^4.6.7 13 | version: 4.6.8 14 | devDependencies: 15 | '@cloudflare/vitest-pool-workers': 16 | specifier: ^0.5.22 17 | version: 0.5.22(@cloudflare/workers-types@4.20241022.0)(@vitest/runner@2.1.4)(@vitest/snapshot@2.1.4)(vitest@2.1.4(@types/node@22.8.4)(happy-dom@15.7.4)) 18 | '@cloudflare/workers-types': 19 | specifier: ^4.20241022.0 20 | version: 4.20241022.0 21 | '@vitest/coverage-v8': 22 | specifier: ^2.1.3 23 | version: 2.1.4(vitest@2.1.4(@types/node@22.8.4)(happy-dom@15.7.4)) 24 | happy-dom: 25 | specifier: ^15.7.4 26 | version: 15.7.4 27 | vitest: 28 | specifier: ^2.1.3 29 | version: 2.1.4(@types/node@22.8.4)(happy-dom@15.7.4) 30 | wrangler: 31 | specifier: ^3.83.0 32 | version: 3.83.0(@cloudflare/workers-types@4.20241022.0) 33 | 34 | packages: 35 | 36 | '@ampproject/remapping@2.3.0': 37 | resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} 38 | engines: {node: '>=6.0.0'} 39 | 40 | '@babel/helper-string-parser@7.25.9': 41 | resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} 42 | engines: {node: '>=6.9.0'} 43 | 44 | '@babel/helper-validator-identifier@7.25.9': 45 | resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} 46 | engines: {node: '>=6.9.0'} 47 | 48 | '@babel/parser@7.26.1': 49 | resolution: {integrity: sha512-reoQYNiAJreZNsJzyrDNzFQ+IQ5JFiIzAHJg9bn94S3l+4++J7RsIhNMoB+lgP/9tpmiAQqspv+xfdxTSzREOw==} 50 | engines: {node: '>=6.0.0'} 51 | hasBin: true 52 | 53 | '@babel/types@7.26.0': 54 | resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} 55 | engines: {node: '>=6.9.0'} 56 | 57 | '@bcoe/v8-coverage@0.2.3': 58 | resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} 59 | 60 | '@cloudflare/kv-asset-handler@0.3.4': 61 | resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==} 62 | engines: {node: '>=16.13'} 63 | 64 | '@cloudflare/vitest-pool-workers@0.5.22': 65 | resolution: {integrity: sha512-Zynt47hQbITDGDVjuI11/NlLuKCByooIVKbCPG0kIu3C4Wr1QcNmzZyhGVxVWKjQDRjXDgbqVN605G+SXy8twg==} 66 | peerDependencies: 67 | '@vitest/runner': 2.0.x - 2.1.x 68 | '@vitest/snapshot': 2.0.x - 2.1.x 69 | vitest: 2.0.x - 2.1.x 70 | 71 | '@cloudflare/workerd-darwin-64@1.20241022.0': 72 | resolution: {integrity: sha512-1NNYun37myMTgCUiPQEJ0cMal4mKZVTpkD0b2tx9hV70xji+frVJcSK8YVLeUm1P+Rw1d/ct8DMgQuCpsz3Fsw==} 73 | engines: {node: '>=16'} 74 | cpu: [x64] 75 | os: [darwin] 76 | 77 | '@cloudflare/workerd-darwin-arm64@1.20241022.0': 78 | resolution: {integrity: sha512-FOO/0P0U82EsTLTdweNVgw+4VOk5nghExLPLSppdOziq6IR5HVgP44Kmq5LdsUeHUhwUmfOh9hzaTpkNzUqKvw==} 79 | engines: {node: '>=16'} 80 | cpu: [arm64] 81 | os: [darwin] 82 | 83 | '@cloudflare/workerd-linux-64@1.20241022.0': 84 | resolution: {integrity: sha512-RsNc19BQJG9yd+ngnjuDeG9ywZG+7t1L4JeglgceyY5ViMNMKVO7Zpbsu69kXslU9h6xyQG+lrmclg3cBpnhYA==} 85 | engines: {node: '>=16'} 86 | cpu: [x64] 87 | os: [linux] 88 | 89 | '@cloudflare/workerd-linux-arm64@1.20241022.0': 90 | resolution: {integrity: sha512-x5mUXpKxfsosxcFmcq5DaqLs37PejHYVRsNz1cWI59ma7aC4y4Qn6Tf3i0r9MwQTF/MccP4SjVslMU6m4W7IaA==} 91 | engines: {node: '>=16'} 92 | cpu: [arm64] 93 | os: [linux] 94 | 95 | '@cloudflare/workerd-windows-64@1.20241022.0': 96 | resolution: {integrity: sha512-eBCClx4szCOgKqOlxxbdNszMqQf3MRG1B9BRIqEM/diDfdR9IrZ8l3FaEm+l9gXgPmS6m1NBn40aWuGBl8UTSw==} 97 | engines: {node: '>=16'} 98 | cpu: [x64] 99 | os: [win32] 100 | 101 | '@cloudflare/workers-shared@0.7.0': 102 | resolution: {integrity: sha512-LLQRTqx7lKC7o2eCYMpyc5FXV8d0pUX6r3A+agzhqS9aoR5A6zCPefwQGcvbKx83ozX22ATZcemwxQXn12UofQ==} 103 | engines: {node: '>=16.7.0'} 104 | 105 | '@cloudflare/workers-types@4.20241022.0': 106 | resolution: {integrity: sha512-1zOAw5QIDKItzGatzCrEpfLOB1AuMTwVqKmbw9B9eBfCUGRFNfJYMrJxIwcse9EmKahsQt2GruqU00pY/GyXgg==} 107 | 108 | '@cspotcode/source-map-support@0.8.1': 109 | resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} 110 | engines: {node: '>=12'} 111 | 112 | '@esbuild-plugins/node-globals-polyfill@0.2.3': 113 | resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} 114 | peerDependencies: 115 | esbuild: '*' 116 | 117 | '@esbuild-plugins/node-modules-polyfill@0.2.2': 118 | resolution: {integrity: sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==} 119 | peerDependencies: 120 | esbuild: '*' 121 | 122 | '@esbuild/aix-ppc64@0.21.5': 123 | resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} 124 | engines: {node: '>=12'} 125 | cpu: [ppc64] 126 | os: [aix] 127 | 128 | '@esbuild/android-arm64@0.17.19': 129 | resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} 130 | engines: {node: '>=12'} 131 | cpu: [arm64] 132 | os: [android] 133 | 134 | '@esbuild/android-arm64@0.21.5': 135 | resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} 136 | engines: {node: '>=12'} 137 | cpu: [arm64] 138 | os: [android] 139 | 140 | '@esbuild/android-arm@0.17.19': 141 | resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} 142 | engines: {node: '>=12'} 143 | cpu: [arm] 144 | os: [android] 145 | 146 | '@esbuild/android-arm@0.21.5': 147 | resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} 148 | engines: {node: '>=12'} 149 | cpu: [arm] 150 | os: [android] 151 | 152 | '@esbuild/android-x64@0.17.19': 153 | resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} 154 | engines: {node: '>=12'} 155 | cpu: [x64] 156 | os: [android] 157 | 158 | '@esbuild/android-x64@0.21.5': 159 | resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} 160 | engines: {node: '>=12'} 161 | cpu: [x64] 162 | os: [android] 163 | 164 | '@esbuild/darwin-arm64@0.17.19': 165 | resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} 166 | engines: {node: '>=12'} 167 | cpu: [arm64] 168 | os: [darwin] 169 | 170 | '@esbuild/darwin-arm64@0.21.5': 171 | resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} 172 | engines: {node: '>=12'} 173 | cpu: [arm64] 174 | os: [darwin] 175 | 176 | '@esbuild/darwin-x64@0.17.19': 177 | resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} 178 | engines: {node: '>=12'} 179 | cpu: [x64] 180 | os: [darwin] 181 | 182 | '@esbuild/darwin-x64@0.21.5': 183 | resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} 184 | engines: {node: '>=12'} 185 | cpu: [x64] 186 | os: [darwin] 187 | 188 | '@esbuild/freebsd-arm64@0.17.19': 189 | resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} 190 | engines: {node: '>=12'} 191 | cpu: [arm64] 192 | os: [freebsd] 193 | 194 | '@esbuild/freebsd-arm64@0.21.5': 195 | resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} 196 | engines: {node: '>=12'} 197 | cpu: [arm64] 198 | os: [freebsd] 199 | 200 | '@esbuild/freebsd-x64@0.17.19': 201 | resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} 202 | engines: {node: '>=12'} 203 | cpu: [x64] 204 | os: [freebsd] 205 | 206 | '@esbuild/freebsd-x64@0.21.5': 207 | resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} 208 | engines: {node: '>=12'} 209 | cpu: [x64] 210 | os: [freebsd] 211 | 212 | '@esbuild/linux-arm64@0.17.19': 213 | resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} 214 | engines: {node: '>=12'} 215 | cpu: [arm64] 216 | os: [linux] 217 | 218 | '@esbuild/linux-arm64@0.21.5': 219 | resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} 220 | engines: {node: '>=12'} 221 | cpu: [arm64] 222 | os: [linux] 223 | 224 | '@esbuild/linux-arm@0.17.19': 225 | resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} 226 | engines: {node: '>=12'} 227 | cpu: [arm] 228 | os: [linux] 229 | 230 | '@esbuild/linux-arm@0.21.5': 231 | resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} 232 | engines: {node: '>=12'} 233 | cpu: [arm] 234 | os: [linux] 235 | 236 | '@esbuild/linux-ia32@0.17.19': 237 | resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} 238 | engines: {node: '>=12'} 239 | cpu: [ia32] 240 | os: [linux] 241 | 242 | '@esbuild/linux-ia32@0.21.5': 243 | resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} 244 | engines: {node: '>=12'} 245 | cpu: [ia32] 246 | os: [linux] 247 | 248 | '@esbuild/linux-loong64@0.17.19': 249 | resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} 250 | engines: {node: '>=12'} 251 | cpu: [loong64] 252 | os: [linux] 253 | 254 | '@esbuild/linux-loong64@0.21.5': 255 | resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} 256 | engines: {node: '>=12'} 257 | cpu: [loong64] 258 | os: [linux] 259 | 260 | '@esbuild/linux-mips64el@0.17.19': 261 | resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} 262 | engines: {node: '>=12'} 263 | cpu: [mips64el] 264 | os: [linux] 265 | 266 | '@esbuild/linux-mips64el@0.21.5': 267 | resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} 268 | engines: {node: '>=12'} 269 | cpu: [mips64el] 270 | os: [linux] 271 | 272 | '@esbuild/linux-ppc64@0.17.19': 273 | resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} 274 | engines: {node: '>=12'} 275 | cpu: [ppc64] 276 | os: [linux] 277 | 278 | '@esbuild/linux-ppc64@0.21.5': 279 | resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} 280 | engines: {node: '>=12'} 281 | cpu: [ppc64] 282 | os: [linux] 283 | 284 | '@esbuild/linux-riscv64@0.17.19': 285 | resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} 286 | engines: {node: '>=12'} 287 | cpu: [riscv64] 288 | os: [linux] 289 | 290 | '@esbuild/linux-riscv64@0.21.5': 291 | resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} 292 | engines: {node: '>=12'} 293 | cpu: [riscv64] 294 | os: [linux] 295 | 296 | '@esbuild/linux-s390x@0.17.19': 297 | resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} 298 | engines: {node: '>=12'} 299 | cpu: [s390x] 300 | os: [linux] 301 | 302 | '@esbuild/linux-s390x@0.21.5': 303 | resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} 304 | engines: {node: '>=12'} 305 | cpu: [s390x] 306 | os: [linux] 307 | 308 | '@esbuild/linux-x64@0.17.19': 309 | resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} 310 | engines: {node: '>=12'} 311 | cpu: [x64] 312 | os: [linux] 313 | 314 | '@esbuild/linux-x64@0.21.5': 315 | resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} 316 | engines: {node: '>=12'} 317 | cpu: [x64] 318 | os: [linux] 319 | 320 | '@esbuild/netbsd-x64@0.17.19': 321 | resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} 322 | engines: {node: '>=12'} 323 | cpu: [x64] 324 | os: [netbsd] 325 | 326 | '@esbuild/netbsd-x64@0.21.5': 327 | resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} 328 | engines: {node: '>=12'} 329 | cpu: [x64] 330 | os: [netbsd] 331 | 332 | '@esbuild/openbsd-x64@0.17.19': 333 | resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} 334 | engines: {node: '>=12'} 335 | cpu: [x64] 336 | os: [openbsd] 337 | 338 | '@esbuild/openbsd-x64@0.21.5': 339 | resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} 340 | engines: {node: '>=12'} 341 | cpu: [x64] 342 | os: [openbsd] 343 | 344 | '@esbuild/sunos-x64@0.17.19': 345 | resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} 346 | engines: {node: '>=12'} 347 | cpu: [x64] 348 | os: [sunos] 349 | 350 | '@esbuild/sunos-x64@0.21.5': 351 | resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} 352 | engines: {node: '>=12'} 353 | cpu: [x64] 354 | os: [sunos] 355 | 356 | '@esbuild/win32-arm64@0.17.19': 357 | resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} 358 | engines: {node: '>=12'} 359 | cpu: [arm64] 360 | os: [win32] 361 | 362 | '@esbuild/win32-arm64@0.21.5': 363 | resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} 364 | engines: {node: '>=12'} 365 | cpu: [arm64] 366 | os: [win32] 367 | 368 | '@esbuild/win32-ia32@0.17.19': 369 | resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} 370 | engines: {node: '>=12'} 371 | cpu: [ia32] 372 | os: [win32] 373 | 374 | '@esbuild/win32-ia32@0.21.5': 375 | resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} 376 | engines: {node: '>=12'} 377 | cpu: [ia32] 378 | os: [win32] 379 | 380 | '@esbuild/win32-x64@0.17.19': 381 | resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} 382 | engines: {node: '>=12'} 383 | cpu: [x64] 384 | os: [win32] 385 | 386 | '@esbuild/win32-x64@0.21.5': 387 | resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} 388 | engines: {node: '>=12'} 389 | cpu: [x64] 390 | os: [win32] 391 | 392 | '@fastify/busboy@2.1.1': 393 | resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} 394 | engines: {node: '>=14'} 395 | 396 | '@isaacs/cliui@8.0.2': 397 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 398 | engines: {node: '>=12'} 399 | 400 | '@istanbuljs/schema@0.1.3': 401 | resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} 402 | engines: {node: '>=8'} 403 | 404 | '@jridgewell/gen-mapping@0.3.5': 405 | resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} 406 | engines: {node: '>=6.0.0'} 407 | 408 | '@jridgewell/resolve-uri@3.1.2': 409 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 410 | engines: {node: '>=6.0.0'} 411 | 412 | '@jridgewell/set-array@1.2.1': 413 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 414 | engines: {node: '>=6.0.0'} 415 | 416 | '@jridgewell/sourcemap-codec@1.5.0': 417 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 418 | 419 | '@jridgewell/trace-mapping@0.3.25': 420 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 421 | 422 | '@jridgewell/trace-mapping@0.3.9': 423 | resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} 424 | 425 | '@pkgjs/parseargs@0.11.0': 426 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 427 | engines: {node: '>=14'} 428 | 429 | '@rollup/rollup-android-arm-eabi@4.24.3': 430 | resolution: {integrity: sha512-ufb2CH2KfBWPJok95frEZZ82LtDl0A6QKTa8MoM+cWwDZvVGl5/jNb79pIhRvAalUu+7LD91VYR0nwRD799HkQ==} 431 | cpu: [arm] 432 | os: [android] 433 | 434 | '@rollup/rollup-android-arm64@4.24.3': 435 | resolution: {integrity: sha512-iAHpft/eQk9vkWIV5t22V77d90CRofgR2006UiCjHcHJFVI1E0oBkQIAbz+pLtthFw3hWEmVB4ilxGyBf48i2Q==} 436 | cpu: [arm64] 437 | os: [android] 438 | 439 | '@rollup/rollup-darwin-arm64@4.24.3': 440 | resolution: {integrity: sha512-QPW2YmkWLlvqmOa2OwrfqLJqkHm7kJCIMq9kOz40Zo9Ipi40kf9ONG5Sz76zszrmIZZ4hgRIkez69YnTHgEz1w==} 441 | cpu: [arm64] 442 | os: [darwin] 443 | 444 | '@rollup/rollup-darwin-x64@4.24.3': 445 | resolution: {integrity: sha512-KO0pN5x3+uZm1ZXeIfDqwcvnQ9UEGN8JX5ufhmgH5Lz4ujjZMAnxQygZAVGemFWn+ZZC0FQopruV4lqmGMshow==} 446 | cpu: [x64] 447 | os: [darwin] 448 | 449 | '@rollup/rollup-freebsd-arm64@4.24.3': 450 | resolution: {integrity: sha512-CsC+ZdIiZCZbBI+aRlWpYJMSWvVssPuWqrDy/zi9YfnatKKSLFCe6fjna1grHuo/nVaHG+kiglpRhyBQYRTK4A==} 451 | cpu: [arm64] 452 | os: [freebsd] 453 | 454 | '@rollup/rollup-freebsd-x64@4.24.3': 455 | resolution: {integrity: sha512-F0nqiLThcfKvRQhZEzMIXOQG4EeX61im61VYL1jo4eBxv4aZRmpin6crnBJQ/nWnCsjH5F6J3W6Stdm0mBNqBg==} 456 | cpu: [x64] 457 | os: [freebsd] 458 | 459 | '@rollup/rollup-linux-arm-gnueabihf@4.24.3': 460 | resolution: {integrity: sha512-KRSFHyE/RdxQ1CSeOIBVIAxStFC/hnBgVcaiCkQaVC+EYDtTe4X7z5tBkFyRoBgUGtB6Xg6t9t2kulnX6wJc6A==} 461 | cpu: [arm] 462 | os: [linux] 463 | 464 | '@rollup/rollup-linux-arm-musleabihf@4.24.3': 465 | resolution: {integrity: sha512-h6Q8MT+e05zP5BxEKz0vi0DhthLdrNEnspdLzkoFqGwnmOzakEHSlXfVyA4HJ322QtFy7biUAVFPvIDEDQa6rw==} 466 | cpu: [arm] 467 | os: [linux] 468 | 469 | '@rollup/rollup-linux-arm64-gnu@4.24.3': 470 | resolution: {integrity: sha512-fKElSyXhXIJ9pqiYRqisfirIo2Z5pTTve5K438URf08fsypXrEkVmShkSfM8GJ1aUyvjakT+fn2W7Czlpd/0FQ==} 471 | cpu: [arm64] 472 | os: [linux] 473 | 474 | '@rollup/rollup-linux-arm64-musl@4.24.3': 475 | resolution: {integrity: sha512-YlddZSUk8G0px9/+V9PVilVDC6ydMz7WquxozToozSnfFK6wa6ne1ATUjUvjin09jp34p84milxlY5ikueoenw==} 476 | cpu: [arm64] 477 | os: [linux] 478 | 479 | '@rollup/rollup-linux-powerpc64le-gnu@4.24.3': 480 | resolution: {integrity: sha512-yNaWw+GAO8JjVx3s3cMeG5Esz1cKVzz8PkTJSfYzE5u7A+NvGmbVFEHP+BikTIyYWuz0+DX9kaA3pH9Sqxp69g==} 481 | cpu: [ppc64] 482 | os: [linux] 483 | 484 | '@rollup/rollup-linux-riscv64-gnu@4.24.3': 485 | resolution: {integrity: sha512-lWKNQfsbpv14ZCtM/HkjCTm4oWTKTfxPmr7iPfp3AHSqyoTz5AgLemYkWLwOBWc+XxBbrU9SCokZP0WlBZM9lA==} 486 | cpu: [riscv64] 487 | os: [linux] 488 | 489 | '@rollup/rollup-linux-s390x-gnu@4.24.3': 490 | resolution: {integrity: sha512-HoojGXTC2CgCcq0Woc/dn12wQUlkNyfH0I1ABK4Ni9YXyFQa86Fkt2Q0nqgLfbhkyfQ6003i3qQk9pLh/SpAYw==} 491 | cpu: [s390x] 492 | os: [linux] 493 | 494 | '@rollup/rollup-linux-x64-gnu@4.24.3': 495 | resolution: {integrity: sha512-mnEOh4iE4USSccBOtcrjF5nj+5/zm6NcNhbSEfR3Ot0pxBwvEn5QVUXcuOwwPkapDtGZ6pT02xLoPaNv06w7KQ==} 496 | cpu: [x64] 497 | os: [linux] 498 | 499 | '@rollup/rollup-linux-x64-musl@4.24.3': 500 | resolution: {integrity: sha512-rMTzawBPimBQkG9NKpNHvquIUTQPzrnPxPbCY1Xt+mFkW7pshvyIS5kYgcf74goxXOQk0CP3EoOC1zcEezKXhw==} 501 | cpu: [x64] 502 | os: [linux] 503 | 504 | '@rollup/rollup-win32-arm64-msvc@4.24.3': 505 | resolution: {integrity: sha512-2lg1CE305xNvnH3SyiKwPVsTVLCg4TmNCF1z7PSHX2uZY2VbUpdkgAllVoISD7JO7zu+YynpWNSKAtOrX3AiuA==} 506 | cpu: [arm64] 507 | os: [win32] 508 | 509 | '@rollup/rollup-win32-ia32-msvc@4.24.3': 510 | resolution: {integrity: sha512-9SjYp1sPyxJsPWuhOCX6F4jUMXGbVVd5obVpoVEi8ClZqo52ViZewA6eFz85y8ezuOA+uJMP5A5zo6Oz4S5rVQ==} 511 | cpu: [ia32] 512 | os: [win32] 513 | 514 | '@rollup/rollup-win32-x64-msvc@4.24.3': 515 | resolution: {integrity: sha512-HGZgRFFYrMrP3TJlq58nR1xy8zHKId25vhmm5S9jETEfDf6xybPxsavFTJaufe2zgOGYJBskGlj49CwtEuFhWQ==} 516 | cpu: [x64] 517 | os: [win32] 518 | 519 | '@types/estree@1.0.6': 520 | resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} 521 | 522 | '@types/node-forge@1.3.11': 523 | resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} 524 | 525 | '@types/node@22.8.4': 526 | resolution: {integrity: sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw==} 527 | 528 | '@vitest/coverage-v8@2.1.4': 529 | resolution: {integrity: sha512-FPKQuJfR6VTfcNMcGpqInmtJuVXFSCd9HQltYncfR01AzXhLucMEtQ5SinPdZxsT5x/5BK7I5qFJ5/ApGCmyTQ==} 530 | peerDependencies: 531 | '@vitest/browser': 2.1.4 532 | vitest: 2.1.4 533 | peerDependenciesMeta: 534 | '@vitest/browser': 535 | optional: true 536 | 537 | '@vitest/expect@2.1.4': 538 | resolution: {integrity: sha512-DOETT0Oh1avie/D/o2sgMHGrzYUFFo3zqESB2Hn70z6QB1HrS2IQ9z5DfyTqU8sg4Bpu13zZe9V4+UTNQlUeQA==} 539 | 540 | '@vitest/mocker@2.1.4': 541 | resolution: {integrity: sha512-Ky/O1Lc0QBbutJdW0rqLeFNbuLEyS+mIPiNdlVlp2/yhJ0SbyYqObS5IHdhferJud8MbbwMnexg4jordE5cCoQ==} 542 | peerDependencies: 543 | msw: ^2.4.9 544 | vite: ^5.0.0 545 | peerDependenciesMeta: 546 | msw: 547 | optional: true 548 | vite: 549 | optional: true 550 | 551 | '@vitest/pretty-format@2.1.4': 552 | resolution: {integrity: sha512-L95zIAkEuTDbUX1IsjRl+vyBSLh3PwLLgKpghl37aCK9Jvw0iP+wKwIFhfjdUtA2myLgjrG6VU6JCFLv8q/3Ww==} 553 | 554 | '@vitest/runner@2.1.4': 555 | resolution: {integrity: sha512-sKRautINI9XICAMl2bjxQM8VfCMTB0EbsBc/EDFA57V6UQevEKY/TOPOF5nzcvCALltiLfXWbq4MaAwWx/YxIA==} 556 | 557 | '@vitest/snapshot@2.1.4': 558 | resolution: {integrity: sha512-3Kab14fn/5QZRog5BPj6Rs8dc4B+mim27XaKWFWHWA87R56AKjHTGcBFKpvZKDzC4u5Wd0w/qKsUIio3KzWW4Q==} 559 | 560 | '@vitest/spy@2.1.4': 561 | resolution: {integrity: sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==} 562 | 563 | '@vitest/utils@2.1.4': 564 | resolution: {integrity: sha512-MXDnZn0Awl2S86PSNIim5PWXgIAx8CIkzu35mBdSApUip6RFOGXBCf3YFyeEu8n1IHk4bWD46DeYFu9mQlFIRg==} 565 | 566 | acorn-walk@8.3.4: 567 | resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} 568 | engines: {node: '>=0.4.0'} 569 | 570 | acorn@8.14.0: 571 | resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} 572 | engines: {node: '>=0.4.0'} 573 | hasBin: true 574 | 575 | ansi-regex@5.0.1: 576 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 577 | engines: {node: '>=8'} 578 | 579 | ansi-regex@6.1.0: 580 | resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} 581 | engines: {node: '>=12'} 582 | 583 | ansi-styles@4.3.0: 584 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 585 | engines: {node: '>=8'} 586 | 587 | ansi-styles@6.2.1: 588 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 589 | engines: {node: '>=12'} 590 | 591 | anymatch@3.1.3: 592 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 593 | engines: {node: '>= 8'} 594 | 595 | as-table@1.0.55: 596 | resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==} 597 | 598 | assertion-error@2.0.1: 599 | resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 600 | engines: {node: '>=12'} 601 | 602 | balanced-match@1.0.2: 603 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 604 | 605 | binary-extensions@2.3.0: 606 | resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} 607 | engines: {node: '>=8'} 608 | 609 | birpc@0.2.14: 610 | resolution: {integrity: sha512-37FHE8rqsYM5JEKCnXFyHpBCzvgHEExwVVTq+nUmloInU7l8ezD1TpOhKpS8oe1DTYFqEK27rFZVKG43oTqXRA==} 611 | 612 | blake3-wasm@2.1.5: 613 | resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} 614 | 615 | brace-expansion@2.0.1: 616 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 617 | 618 | braces@3.0.3: 619 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 620 | engines: {node: '>=8'} 621 | 622 | cac@6.7.14: 623 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 624 | engines: {node: '>=8'} 625 | 626 | capnp-ts@0.7.0: 627 | resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} 628 | 629 | chai@5.1.2: 630 | resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} 631 | engines: {node: '>=12'} 632 | 633 | check-error@2.1.1: 634 | resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} 635 | engines: {node: '>= 16'} 636 | 637 | chokidar@3.6.0: 638 | resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} 639 | engines: {node: '>= 8.10.0'} 640 | 641 | cjs-module-lexer@1.4.1: 642 | resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} 643 | 644 | color-convert@2.0.1: 645 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 646 | engines: {node: '>=7.0.0'} 647 | 648 | color-name@1.1.4: 649 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 650 | 651 | cookie@0.7.2: 652 | resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} 653 | engines: {node: '>= 0.6'} 654 | 655 | cross-spawn@7.0.3: 656 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 657 | engines: {node: '>= 8'} 658 | 659 | data-uri-to-buffer@2.0.2: 660 | resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} 661 | 662 | date-fns@4.1.0: 663 | resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} 664 | 665 | debug@4.3.7: 666 | resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} 667 | engines: {node: '>=6.0'} 668 | peerDependencies: 669 | supports-color: '*' 670 | peerDependenciesMeta: 671 | supports-color: 672 | optional: true 673 | 674 | deep-eql@5.0.2: 675 | resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} 676 | engines: {node: '>=6'} 677 | 678 | defu@6.1.4: 679 | resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 680 | 681 | devalue@4.3.3: 682 | resolution: {integrity: sha512-UH8EL6H2ifcY8TbD2QsxwCC/pr5xSwPvv85LrLXVihmHVC3T3YqTCIwnR5ak0yO1KYqlxrPVOA/JVZJYPy2ATg==} 683 | 684 | eastasianwidth@0.2.0: 685 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 686 | 687 | emoji-regex@8.0.0: 688 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 689 | 690 | emoji-regex@9.2.2: 691 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 692 | 693 | entities@4.5.0: 694 | resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} 695 | engines: {node: '>=0.12'} 696 | 697 | esbuild@0.17.19: 698 | resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} 699 | engines: {node: '>=12'} 700 | hasBin: true 701 | 702 | esbuild@0.21.5: 703 | resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} 704 | engines: {node: '>=12'} 705 | hasBin: true 706 | 707 | escape-string-regexp@4.0.0: 708 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 709 | engines: {node: '>=10'} 710 | 711 | estree-walker@0.6.1: 712 | resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} 713 | 714 | estree-walker@3.0.3: 715 | resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 716 | 717 | exit-hook@2.2.1: 718 | resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} 719 | engines: {node: '>=6'} 720 | 721 | expect-type@1.1.0: 722 | resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} 723 | engines: {node: '>=12.0.0'} 724 | 725 | fill-range@7.1.1: 726 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 727 | engines: {node: '>=8'} 728 | 729 | foreground-child@3.3.0: 730 | resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} 731 | engines: {node: '>=14'} 732 | 733 | fsevents@2.3.3: 734 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 735 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 736 | os: [darwin] 737 | 738 | function-bind@1.1.2: 739 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 740 | 741 | get-source@2.0.12: 742 | resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} 743 | 744 | glob-parent@5.1.2: 745 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 746 | engines: {node: '>= 6'} 747 | 748 | glob-to-regexp@0.4.1: 749 | resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} 750 | 751 | glob@10.4.5: 752 | resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 753 | hasBin: true 754 | 755 | happy-dom@15.7.4: 756 | resolution: {integrity: sha512-r1vadDYGMtsHAAsqhDuk4IpPvr6N8MGKy5ntBo7tSdim+pWDxus2PNqOcOt8LuDZ4t3KJHE+gCuzupcx/GKnyQ==} 757 | engines: {node: '>=18.0.0'} 758 | 759 | has-flag@4.0.0: 760 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 761 | engines: {node: '>=8'} 762 | 763 | hasown@2.0.2: 764 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 765 | engines: {node: '>= 0.4'} 766 | 767 | hono@4.6.8: 768 | resolution: {integrity: sha512-f+2Ec9JAzabT61pglDiLJcF/DjiSefZkjCn9bzm1cYLGkD5ExJ3Jnv93ax9h0bn7UPLHF81KktoyjdQfWI2n1Q==} 769 | engines: {node: '>=16.9.0'} 770 | 771 | html-escaper@2.0.2: 772 | resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} 773 | 774 | is-binary-path@2.1.0: 775 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 776 | engines: {node: '>=8'} 777 | 778 | is-core-module@2.15.1: 779 | resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} 780 | engines: {node: '>= 0.4'} 781 | 782 | is-extglob@2.1.1: 783 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 784 | engines: {node: '>=0.10.0'} 785 | 786 | is-fullwidth-code-point@3.0.0: 787 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 788 | engines: {node: '>=8'} 789 | 790 | is-glob@4.0.3: 791 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 792 | engines: {node: '>=0.10.0'} 793 | 794 | is-number@7.0.0: 795 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 796 | engines: {node: '>=0.12.0'} 797 | 798 | isexe@2.0.0: 799 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 800 | 801 | istanbul-lib-coverage@3.2.2: 802 | resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} 803 | engines: {node: '>=8'} 804 | 805 | istanbul-lib-report@3.0.1: 806 | resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} 807 | engines: {node: '>=10'} 808 | 809 | istanbul-lib-source-maps@5.0.6: 810 | resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} 811 | engines: {node: '>=10'} 812 | 813 | istanbul-reports@3.1.7: 814 | resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} 815 | engines: {node: '>=8'} 816 | 817 | itty-time@1.0.6: 818 | resolution: {integrity: sha512-+P8IZaLLBtFv8hCkIjcymZOp4UJ+xW6bSlQsXGqrkmJh7vSiMFSlNne0mCYagEE0N7HDNR5jJBRxwN0oYv61Rw==} 819 | 820 | jackspeak@3.4.3: 821 | resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 822 | 823 | loupe@3.1.2: 824 | resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} 825 | 826 | lru-cache@10.4.3: 827 | resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 828 | 829 | magic-string@0.25.9: 830 | resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} 831 | 832 | magic-string@0.30.12: 833 | resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} 834 | 835 | magicast@0.3.5: 836 | resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} 837 | 838 | make-dir@4.0.0: 839 | resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} 840 | engines: {node: '>=10'} 841 | 842 | mime@3.0.0: 843 | resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} 844 | engines: {node: '>=10.0.0'} 845 | hasBin: true 846 | 847 | miniflare@3.20241022.0: 848 | resolution: {integrity: sha512-x9Fbq1Hmz1f0osIT9Qmj78iX4UpCP2EqlZnA/tzj/3+I49vc3Kq0fNqSSKplcdf6HlCHdL3fOBicmreQF4BUUQ==} 849 | engines: {node: '>=16.13'} 850 | hasBin: true 851 | 852 | minimatch@9.0.5: 853 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 854 | engines: {node: '>=16 || 14 >=14.17'} 855 | 856 | minipass@7.1.2: 857 | resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 858 | engines: {node: '>=16 || 14 >=14.17'} 859 | 860 | ms@2.1.3: 861 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 862 | 863 | mustache@4.2.0: 864 | resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} 865 | hasBin: true 866 | 867 | nanoid@3.3.7: 868 | resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} 869 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 870 | hasBin: true 871 | 872 | node-forge@1.3.1: 873 | resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} 874 | engines: {node: '>= 6.13.0'} 875 | 876 | normalize-path@3.0.0: 877 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 878 | engines: {node: '>=0.10.0'} 879 | 880 | ohash@1.1.4: 881 | resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==} 882 | 883 | package-json-from-dist@1.0.1: 884 | resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 885 | 886 | path-key@3.1.1: 887 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 888 | engines: {node: '>=8'} 889 | 890 | path-parse@1.0.7: 891 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 892 | 893 | path-scurry@1.11.1: 894 | resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 895 | engines: {node: '>=16 || 14 >=14.18'} 896 | 897 | path-to-regexp@6.3.0: 898 | resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} 899 | 900 | pathe@1.1.2: 901 | resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} 902 | 903 | pathval@2.0.0: 904 | resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} 905 | engines: {node: '>= 14.16'} 906 | 907 | picocolors@1.1.1: 908 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 909 | 910 | picomatch@2.3.1: 911 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 912 | engines: {node: '>=8.6'} 913 | 914 | postcss@8.4.47: 915 | resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} 916 | engines: {node: ^10 || ^12 || >=14} 917 | 918 | printable-characters@1.0.42: 919 | resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} 920 | 921 | readdirp@3.6.0: 922 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 923 | engines: {node: '>=8.10.0'} 924 | 925 | resolve.exports@2.0.2: 926 | resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} 927 | engines: {node: '>=10'} 928 | 929 | resolve@1.22.8: 930 | resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} 931 | hasBin: true 932 | 933 | rollup-plugin-inject@3.0.2: 934 | resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==} 935 | deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject. 936 | 937 | rollup-plugin-node-polyfills@0.2.1: 938 | resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==} 939 | 940 | rollup-pluginutils@2.8.2: 941 | resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} 942 | 943 | rollup@4.24.3: 944 | resolution: {integrity: sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==} 945 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 946 | hasBin: true 947 | 948 | selfsigned@2.4.1: 949 | resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} 950 | engines: {node: '>=10'} 951 | 952 | semver@7.6.3: 953 | resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} 954 | engines: {node: '>=10'} 955 | hasBin: true 956 | 957 | shebang-command@2.0.0: 958 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 959 | engines: {node: '>=8'} 960 | 961 | shebang-regex@3.0.0: 962 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 963 | engines: {node: '>=8'} 964 | 965 | siginfo@2.0.0: 966 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 967 | 968 | signal-exit@4.1.0: 969 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 970 | engines: {node: '>=14'} 971 | 972 | source-map-js@1.2.1: 973 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 974 | engines: {node: '>=0.10.0'} 975 | 976 | source-map@0.6.1: 977 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 978 | engines: {node: '>=0.10.0'} 979 | 980 | sourcemap-codec@1.4.8: 981 | resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} 982 | deprecated: Please use @jridgewell/sourcemap-codec instead 983 | 984 | stackback@0.0.2: 985 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 986 | 987 | stacktracey@2.1.8: 988 | resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==} 989 | 990 | std-env@3.7.0: 991 | resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} 992 | 993 | stoppable@1.1.0: 994 | resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} 995 | engines: {node: '>=4', npm: '>=6'} 996 | 997 | string-width@4.2.3: 998 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 999 | engines: {node: '>=8'} 1000 | 1001 | string-width@5.1.2: 1002 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 1003 | engines: {node: '>=12'} 1004 | 1005 | strip-ansi@6.0.1: 1006 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1007 | engines: {node: '>=8'} 1008 | 1009 | strip-ansi@7.1.0: 1010 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 1011 | engines: {node: '>=12'} 1012 | 1013 | supports-color@7.2.0: 1014 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1015 | engines: {node: '>=8'} 1016 | 1017 | supports-preserve-symlinks-flag@1.0.0: 1018 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1019 | engines: {node: '>= 0.4'} 1020 | 1021 | test-exclude@7.0.1: 1022 | resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} 1023 | engines: {node: '>=18'} 1024 | 1025 | tinybench@2.9.0: 1026 | resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} 1027 | 1028 | tinyexec@0.3.1: 1029 | resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} 1030 | 1031 | tinypool@1.0.1: 1032 | resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} 1033 | engines: {node: ^18.0.0 || >=20.0.0} 1034 | 1035 | tinyrainbow@1.2.0: 1036 | resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} 1037 | engines: {node: '>=14.0.0'} 1038 | 1039 | tinyspy@3.0.2: 1040 | resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} 1041 | engines: {node: '>=14.0.0'} 1042 | 1043 | to-regex-range@5.0.1: 1044 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1045 | engines: {node: '>=8.0'} 1046 | 1047 | tslib@2.8.0: 1048 | resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} 1049 | 1050 | ufo@1.5.4: 1051 | resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} 1052 | 1053 | undici-types@6.19.8: 1054 | resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} 1055 | 1056 | undici@5.28.4: 1057 | resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} 1058 | engines: {node: '>=14.0'} 1059 | 1060 | unenv-nightly@2.0.0-20241018-011344-e666fcf: 1061 | resolution: {integrity: sha512-D00bYn8rzkCBOlLx+k1iHQlc69jvtJRT7Eek4yIGQ6461a2tUBjngGZdRpqsoXAJCz/qBW0NgPting7Zvg+ysg==} 1062 | 1063 | vite-node@2.1.4: 1064 | resolution: {integrity: sha512-kqa9v+oi4HwkG6g8ufRnb5AeplcRw8jUF6/7/Qz1qRQOXHImG8YnLbB+LLszENwFnoBl9xIf9nVdCFzNd7GQEg==} 1065 | engines: {node: ^18.0.0 || >=20.0.0} 1066 | hasBin: true 1067 | 1068 | vite@5.4.10: 1069 | resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==} 1070 | engines: {node: ^18.0.0 || >=20.0.0} 1071 | hasBin: true 1072 | peerDependencies: 1073 | '@types/node': ^18.0.0 || >=20.0.0 1074 | less: '*' 1075 | lightningcss: ^1.21.0 1076 | sass: '*' 1077 | sass-embedded: '*' 1078 | stylus: '*' 1079 | sugarss: '*' 1080 | terser: ^5.4.0 1081 | peerDependenciesMeta: 1082 | '@types/node': 1083 | optional: true 1084 | less: 1085 | optional: true 1086 | lightningcss: 1087 | optional: true 1088 | sass: 1089 | optional: true 1090 | sass-embedded: 1091 | optional: true 1092 | stylus: 1093 | optional: true 1094 | sugarss: 1095 | optional: true 1096 | terser: 1097 | optional: true 1098 | 1099 | vitest@2.1.4: 1100 | resolution: {integrity: sha512-eDjxbVAJw1UJJCHr5xr/xM86Zx+YxIEXGAR+bmnEID7z9qWfoxpHw0zdobz+TQAFOLT+nEXz3+gx6nUJ7RgmlQ==} 1101 | engines: {node: ^18.0.0 || >=20.0.0} 1102 | hasBin: true 1103 | peerDependencies: 1104 | '@edge-runtime/vm': '*' 1105 | '@types/node': ^18.0.0 || >=20.0.0 1106 | '@vitest/browser': 2.1.4 1107 | '@vitest/ui': 2.1.4 1108 | happy-dom: '*' 1109 | jsdom: '*' 1110 | peerDependenciesMeta: 1111 | '@edge-runtime/vm': 1112 | optional: true 1113 | '@types/node': 1114 | optional: true 1115 | '@vitest/browser': 1116 | optional: true 1117 | '@vitest/ui': 1118 | optional: true 1119 | happy-dom: 1120 | optional: true 1121 | jsdom: 1122 | optional: true 1123 | 1124 | webidl-conversions@7.0.0: 1125 | resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} 1126 | engines: {node: '>=12'} 1127 | 1128 | whatwg-mimetype@3.0.0: 1129 | resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} 1130 | engines: {node: '>=12'} 1131 | 1132 | which@2.0.2: 1133 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1134 | engines: {node: '>= 8'} 1135 | hasBin: true 1136 | 1137 | why-is-node-running@2.3.0: 1138 | resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} 1139 | engines: {node: '>=8'} 1140 | hasBin: true 1141 | 1142 | workerd@1.20241022.0: 1143 | resolution: {integrity: sha512-jyGXsgO9DRcJyx6Ovv7gUyDPc3UYC2i/E0p9GFUg6GUzpldw4Y93y9kOmdfsOnKZ3+lY53veSiUniiBPE6Q2NQ==} 1144 | engines: {node: '>=16'} 1145 | hasBin: true 1146 | 1147 | wrangler@3.83.0: 1148 | resolution: {integrity: sha512-qDzdUuTngKqmm2OJUZm7Gk4+Hv37F2nNNAHuhIgItEIhxBdOVDsgKmvpd+f41MFxyuGg3fbGWYANHI+0V2Z5yw==} 1149 | engines: {node: '>=16.17.0'} 1150 | hasBin: true 1151 | peerDependencies: 1152 | '@cloudflare/workers-types': ^4.20241022.0 1153 | peerDependenciesMeta: 1154 | '@cloudflare/workers-types': 1155 | optional: true 1156 | 1157 | wrap-ansi@7.0.0: 1158 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 1159 | engines: {node: '>=10'} 1160 | 1161 | wrap-ansi@8.1.0: 1162 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 1163 | engines: {node: '>=12'} 1164 | 1165 | ws@8.18.0: 1166 | resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} 1167 | engines: {node: '>=10.0.0'} 1168 | peerDependencies: 1169 | bufferutil: ^4.0.1 1170 | utf-8-validate: '>=5.0.2' 1171 | peerDependenciesMeta: 1172 | bufferutil: 1173 | optional: true 1174 | utf-8-validate: 1175 | optional: true 1176 | 1177 | xxhash-wasm@1.0.2: 1178 | resolution: {integrity: sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==} 1179 | 1180 | youch@3.3.4: 1181 | resolution: {integrity: sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==} 1182 | 1183 | zod@3.23.8: 1184 | resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} 1185 | 1186 | snapshots: 1187 | 1188 | '@ampproject/remapping@2.3.0': 1189 | dependencies: 1190 | '@jridgewell/gen-mapping': 0.3.5 1191 | '@jridgewell/trace-mapping': 0.3.25 1192 | 1193 | '@babel/helper-string-parser@7.25.9': {} 1194 | 1195 | '@babel/helper-validator-identifier@7.25.9': {} 1196 | 1197 | '@babel/parser@7.26.1': 1198 | dependencies: 1199 | '@babel/types': 7.26.0 1200 | 1201 | '@babel/types@7.26.0': 1202 | dependencies: 1203 | '@babel/helper-string-parser': 7.25.9 1204 | '@babel/helper-validator-identifier': 7.25.9 1205 | 1206 | '@bcoe/v8-coverage@0.2.3': {} 1207 | 1208 | '@cloudflare/kv-asset-handler@0.3.4': 1209 | dependencies: 1210 | mime: 3.0.0 1211 | 1212 | '@cloudflare/vitest-pool-workers@0.5.22(@cloudflare/workers-types@4.20241022.0)(@vitest/runner@2.1.4)(@vitest/snapshot@2.1.4)(vitest@2.1.4(@types/node@22.8.4)(happy-dom@15.7.4))': 1213 | dependencies: 1214 | '@vitest/runner': 2.1.4 1215 | '@vitest/snapshot': 2.1.4 1216 | birpc: 0.2.14 1217 | cjs-module-lexer: 1.4.1 1218 | devalue: 4.3.3 1219 | esbuild: 0.17.19 1220 | miniflare: 3.20241022.0 1221 | semver: 7.6.3 1222 | vitest: 2.1.4(@types/node@22.8.4)(happy-dom@15.7.4) 1223 | wrangler: 3.83.0(@cloudflare/workers-types@4.20241022.0) 1224 | zod: 3.23.8 1225 | transitivePeerDependencies: 1226 | - '@cloudflare/workers-types' 1227 | - bufferutil 1228 | - supports-color 1229 | - utf-8-validate 1230 | 1231 | '@cloudflare/workerd-darwin-64@1.20241022.0': 1232 | optional: true 1233 | 1234 | '@cloudflare/workerd-darwin-arm64@1.20241022.0': 1235 | optional: true 1236 | 1237 | '@cloudflare/workerd-linux-64@1.20241022.0': 1238 | optional: true 1239 | 1240 | '@cloudflare/workerd-linux-arm64@1.20241022.0': 1241 | optional: true 1242 | 1243 | '@cloudflare/workerd-windows-64@1.20241022.0': 1244 | optional: true 1245 | 1246 | '@cloudflare/workers-shared@0.7.0': 1247 | dependencies: 1248 | mime: 3.0.0 1249 | zod: 3.23.8 1250 | 1251 | '@cloudflare/workers-types@4.20241022.0': {} 1252 | 1253 | '@cspotcode/source-map-support@0.8.1': 1254 | dependencies: 1255 | '@jridgewell/trace-mapping': 0.3.9 1256 | 1257 | '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19)': 1258 | dependencies: 1259 | esbuild: 0.17.19 1260 | 1261 | '@esbuild-plugins/node-modules-polyfill@0.2.2(esbuild@0.17.19)': 1262 | dependencies: 1263 | esbuild: 0.17.19 1264 | escape-string-regexp: 4.0.0 1265 | rollup-plugin-node-polyfills: 0.2.1 1266 | 1267 | '@esbuild/aix-ppc64@0.21.5': 1268 | optional: true 1269 | 1270 | '@esbuild/android-arm64@0.17.19': 1271 | optional: true 1272 | 1273 | '@esbuild/android-arm64@0.21.5': 1274 | optional: true 1275 | 1276 | '@esbuild/android-arm@0.17.19': 1277 | optional: true 1278 | 1279 | '@esbuild/android-arm@0.21.5': 1280 | optional: true 1281 | 1282 | '@esbuild/android-x64@0.17.19': 1283 | optional: true 1284 | 1285 | '@esbuild/android-x64@0.21.5': 1286 | optional: true 1287 | 1288 | '@esbuild/darwin-arm64@0.17.19': 1289 | optional: true 1290 | 1291 | '@esbuild/darwin-arm64@0.21.5': 1292 | optional: true 1293 | 1294 | '@esbuild/darwin-x64@0.17.19': 1295 | optional: true 1296 | 1297 | '@esbuild/darwin-x64@0.21.5': 1298 | optional: true 1299 | 1300 | '@esbuild/freebsd-arm64@0.17.19': 1301 | optional: true 1302 | 1303 | '@esbuild/freebsd-arm64@0.21.5': 1304 | optional: true 1305 | 1306 | '@esbuild/freebsd-x64@0.17.19': 1307 | optional: true 1308 | 1309 | '@esbuild/freebsd-x64@0.21.5': 1310 | optional: true 1311 | 1312 | '@esbuild/linux-arm64@0.17.19': 1313 | optional: true 1314 | 1315 | '@esbuild/linux-arm64@0.21.5': 1316 | optional: true 1317 | 1318 | '@esbuild/linux-arm@0.17.19': 1319 | optional: true 1320 | 1321 | '@esbuild/linux-arm@0.21.5': 1322 | optional: true 1323 | 1324 | '@esbuild/linux-ia32@0.17.19': 1325 | optional: true 1326 | 1327 | '@esbuild/linux-ia32@0.21.5': 1328 | optional: true 1329 | 1330 | '@esbuild/linux-loong64@0.17.19': 1331 | optional: true 1332 | 1333 | '@esbuild/linux-loong64@0.21.5': 1334 | optional: true 1335 | 1336 | '@esbuild/linux-mips64el@0.17.19': 1337 | optional: true 1338 | 1339 | '@esbuild/linux-mips64el@0.21.5': 1340 | optional: true 1341 | 1342 | '@esbuild/linux-ppc64@0.17.19': 1343 | optional: true 1344 | 1345 | '@esbuild/linux-ppc64@0.21.5': 1346 | optional: true 1347 | 1348 | '@esbuild/linux-riscv64@0.17.19': 1349 | optional: true 1350 | 1351 | '@esbuild/linux-riscv64@0.21.5': 1352 | optional: true 1353 | 1354 | '@esbuild/linux-s390x@0.17.19': 1355 | optional: true 1356 | 1357 | '@esbuild/linux-s390x@0.21.5': 1358 | optional: true 1359 | 1360 | '@esbuild/linux-x64@0.17.19': 1361 | optional: true 1362 | 1363 | '@esbuild/linux-x64@0.21.5': 1364 | optional: true 1365 | 1366 | '@esbuild/netbsd-x64@0.17.19': 1367 | optional: true 1368 | 1369 | '@esbuild/netbsd-x64@0.21.5': 1370 | optional: true 1371 | 1372 | '@esbuild/openbsd-x64@0.17.19': 1373 | optional: true 1374 | 1375 | '@esbuild/openbsd-x64@0.21.5': 1376 | optional: true 1377 | 1378 | '@esbuild/sunos-x64@0.17.19': 1379 | optional: true 1380 | 1381 | '@esbuild/sunos-x64@0.21.5': 1382 | optional: true 1383 | 1384 | '@esbuild/win32-arm64@0.17.19': 1385 | optional: true 1386 | 1387 | '@esbuild/win32-arm64@0.21.5': 1388 | optional: true 1389 | 1390 | '@esbuild/win32-ia32@0.17.19': 1391 | optional: true 1392 | 1393 | '@esbuild/win32-ia32@0.21.5': 1394 | optional: true 1395 | 1396 | '@esbuild/win32-x64@0.17.19': 1397 | optional: true 1398 | 1399 | '@esbuild/win32-x64@0.21.5': 1400 | optional: true 1401 | 1402 | '@fastify/busboy@2.1.1': {} 1403 | 1404 | '@isaacs/cliui@8.0.2': 1405 | dependencies: 1406 | string-width: 5.1.2 1407 | string-width-cjs: string-width@4.2.3 1408 | strip-ansi: 7.1.0 1409 | strip-ansi-cjs: strip-ansi@6.0.1 1410 | wrap-ansi: 8.1.0 1411 | wrap-ansi-cjs: wrap-ansi@7.0.0 1412 | 1413 | '@istanbuljs/schema@0.1.3': {} 1414 | 1415 | '@jridgewell/gen-mapping@0.3.5': 1416 | dependencies: 1417 | '@jridgewell/set-array': 1.2.1 1418 | '@jridgewell/sourcemap-codec': 1.5.0 1419 | '@jridgewell/trace-mapping': 0.3.25 1420 | 1421 | '@jridgewell/resolve-uri@3.1.2': {} 1422 | 1423 | '@jridgewell/set-array@1.2.1': {} 1424 | 1425 | '@jridgewell/sourcemap-codec@1.5.0': {} 1426 | 1427 | '@jridgewell/trace-mapping@0.3.25': 1428 | dependencies: 1429 | '@jridgewell/resolve-uri': 3.1.2 1430 | '@jridgewell/sourcemap-codec': 1.5.0 1431 | 1432 | '@jridgewell/trace-mapping@0.3.9': 1433 | dependencies: 1434 | '@jridgewell/resolve-uri': 3.1.2 1435 | '@jridgewell/sourcemap-codec': 1.5.0 1436 | 1437 | '@pkgjs/parseargs@0.11.0': 1438 | optional: true 1439 | 1440 | '@rollup/rollup-android-arm-eabi@4.24.3': 1441 | optional: true 1442 | 1443 | '@rollup/rollup-android-arm64@4.24.3': 1444 | optional: true 1445 | 1446 | '@rollup/rollup-darwin-arm64@4.24.3': 1447 | optional: true 1448 | 1449 | '@rollup/rollup-darwin-x64@4.24.3': 1450 | optional: true 1451 | 1452 | '@rollup/rollup-freebsd-arm64@4.24.3': 1453 | optional: true 1454 | 1455 | '@rollup/rollup-freebsd-x64@4.24.3': 1456 | optional: true 1457 | 1458 | '@rollup/rollup-linux-arm-gnueabihf@4.24.3': 1459 | optional: true 1460 | 1461 | '@rollup/rollup-linux-arm-musleabihf@4.24.3': 1462 | optional: true 1463 | 1464 | '@rollup/rollup-linux-arm64-gnu@4.24.3': 1465 | optional: true 1466 | 1467 | '@rollup/rollup-linux-arm64-musl@4.24.3': 1468 | optional: true 1469 | 1470 | '@rollup/rollup-linux-powerpc64le-gnu@4.24.3': 1471 | optional: true 1472 | 1473 | '@rollup/rollup-linux-riscv64-gnu@4.24.3': 1474 | optional: true 1475 | 1476 | '@rollup/rollup-linux-s390x-gnu@4.24.3': 1477 | optional: true 1478 | 1479 | '@rollup/rollup-linux-x64-gnu@4.24.3': 1480 | optional: true 1481 | 1482 | '@rollup/rollup-linux-x64-musl@4.24.3': 1483 | optional: true 1484 | 1485 | '@rollup/rollup-win32-arm64-msvc@4.24.3': 1486 | optional: true 1487 | 1488 | '@rollup/rollup-win32-ia32-msvc@4.24.3': 1489 | optional: true 1490 | 1491 | '@rollup/rollup-win32-x64-msvc@4.24.3': 1492 | optional: true 1493 | 1494 | '@types/estree@1.0.6': {} 1495 | 1496 | '@types/node-forge@1.3.11': 1497 | dependencies: 1498 | '@types/node': 22.8.4 1499 | 1500 | '@types/node@22.8.4': 1501 | dependencies: 1502 | undici-types: 6.19.8 1503 | 1504 | '@vitest/coverage-v8@2.1.4(vitest@2.1.4(@types/node@22.8.4)(happy-dom@15.7.4))': 1505 | dependencies: 1506 | '@ampproject/remapping': 2.3.0 1507 | '@bcoe/v8-coverage': 0.2.3 1508 | debug: 4.3.7 1509 | istanbul-lib-coverage: 3.2.2 1510 | istanbul-lib-report: 3.0.1 1511 | istanbul-lib-source-maps: 5.0.6 1512 | istanbul-reports: 3.1.7 1513 | magic-string: 0.30.12 1514 | magicast: 0.3.5 1515 | std-env: 3.7.0 1516 | test-exclude: 7.0.1 1517 | tinyrainbow: 1.2.0 1518 | vitest: 2.1.4(@types/node@22.8.4)(happy-dom@15.7.4) 1519 | transitivePeerDependencies: 1520 | - supports-color 1521 | 1522 | '@vitest/expect@2.1.4': 1523 | dependencies: 1524 | '@vitest/spy': 2.1.4 1525 | '@vitest/utils': 2.1.4 1526 | chai: 5.1.2 1527 | tinyrainbow: 1.2.0 1528 | 1529 | '@vitest/mocker@2.1.4(vite@5.4.10(@types/node@22.8.4))': 1530 | dependencies: 1531 | '@vitest/spy': 2.1.4 1532 | estree-walker: 3.0.3 1533 | magic-string: 0.30.12 1534 | optionalDependencies: 1535 | vite: 5.4.10(@types/node@22.8.4) 1536 | 1537 | '@vitest/pretty-format@2.1.4': 1538 | dependencies: 1539 | tinyrainbow: 1.2.0 1540 | 1541 | '@vitest/runner@2.1.4': 1542 | dependencies: 1543 | '@vitest/utils': 2.1.4 1544 | pathe: 1.1.2 1545 | 1546 | '@vitest/snapshot@2.1.4': 1547 | dependencies: 1548 | '@vitest/pretty-format': 2.1.4 1549 | magic-string: 0.30.12 1550 | pathe: 1.1.2 1551 | 1552 | '@vitest/spy@2.1.4': 1553 | dependencies: 1554 | tinyspy: 3.0.2 1555 | 1556 | '@vitest/utils@2.1.4': 1557 | dependencies: 1558 | '@vitest/pretty-format': 2.1.4 1559 | loupe: 3.1.2 1560 | tinyrainbow: 1.2.0 1561 | 1562 | acorn-walk@8.3.4: 1563 | dependencies: 1564 | acorn: 8.14.0 1565 | 1566 | acorn@8.14.0: {} 1567 | 1568 | ansi-regex@5.0.1: {} 1569 | 1570 | ansi-regex@6.1.0: {} 1571 | 1572 | ansi-styles@4.3.0: 1573 | dependencies: 1574 | color-convert: 2.0.1 1575 | 1576 | ansi-styles@6.2.1: {} 1577 | 1578 | anymatch@3.1.3: 1579 | dependencies: 1580 | normalize-path: 3.0.0 1581 | picomatch: 2.3.1 1582 | 1583 | as-table@1.0.55: 1584 | dependencies: 1585 | printable-characters: 1.0.42 1586 | 1587 | assertion-error@2.0.1: {} 1588 | 1589 | balanced-match@1.0.2: {} 1590 | 1591 | binary-extensions@2.3.0: {} 1592 | 1593 | birpc@0.2.14: {} 1594 | 1595 | blake3-wasm@2.1.5: {} 1596 | 1597 | brace-expansion@2.0.1: 1598 | dependencies: 1599 | balanced-match: 1.0.2 1600 | 1601 | braces@3.0.3: 1602 | dependencies: 1603 | fill-range: 7.1.1 1604 | 1605 | cac@6.7.14: {} 1606 | 1607 | capnp-ts@0.7.0: 1608 | dependencies: 1609 | debug: 4.3.7 1610 | tslib: 2.8.0 1611 | transitivePeerDependencies: 1612 | - supports-color 1613 | 1614 | chai@5.1.2: 1615 | dependencies: 1616 | assertion-error: 2.0.1 1617 | check-error: 2.1.1 1618 | deep-eql: 5.0.2 1619 | loupe: 3.1.2 1620 | pathval: 2.0.0 1621 | 1622 | check-error@2.1.1: {} 1623 | 1624 | chokidar@3.6.0: 1625 | dependencies: 1626 | anymatch: 3.1.3 1627 | braces: 3.0.3 1628 | glob-parent: 5.1.2 1629 | is-binary-path: 2.1.0 1630 | is-glob: 4.0.3 1631 | normalize-path: 3.0.0 1632 | readdirp: 3.6.0 1633 | optionalDependencies: 1634 | fsevents: 2.3.3 1635 | 1636 | cjs-module-lexer@1.4.1: {} 1637 | 1638 | color-convert@2.0.1: 1639 | dependencies: 1640 | color-name: 1.1.4 1641 | 1642 | color-name@1.1.4: {} 1643 | 1644 | cookie@0.7.2: {} 1645 | 1646 | cross-spawn@7.0.3: 1647 | dependencies: 1648 | path-key: 3.1.1 1649 | shebang-command: 2.0.0 1650 | which: 2.0.2 1651 | 1652 | data-uri-to-buffer@2.0.2: {} 1653 | 1654 | date-fns@4.1.0: {} 1655 | 1656 | debug@4.3.7: 1657 | dependencies: 1658 | ms: 2.1.3 1659 | 1660 | deep-eql@5.0.2: {} 1661 | 1662 | defu@6.1.4: {} 1663 | 1664 | devalue@4.3.3: {} 1665 | 1666 | eastasianwidth@0.2.0: {} 1667 | 1668 | emoji-regex@8.0.0: {} 1669 | 1670 | emoji-regex@9.2.2: {} 1671 | 1672 | entities@4.5.0: {} 1673 | 1674 | esbuild@0.17.19: 1675 | optionalDependencies: 1676 | '@esbuild/android-arm': 0.17.19 1677 | '@esbuild/android-arm64': 0.17.19 1678 | '@esbuild/android-x64': 0.17.19 1679 | '@esbuild/darwin-arm64': 0.17.19 1680 | '@esbuild/darwin-x64': 0.17.19 1681 | '@esbuild/freebsd-arm64': 0.17.19 1682 | '@esbuild/freebsd-x64': 0.17.19 1683 | '@esbuild/linux-arm': 0.17.19 1684 | '@esbuild/linux-arm64': 0.17.19 1685 | '@esbuild/linux-ia32': 0.17.19 1686 | '@esbuild/linux-loong64': 0.17.19 1687 | '@esbuild/linux-mips64el': 0.17.19 1688 | '@esbuild/linux-ppc64': 0.17.19 1689 | '@esbuild/linux-riscv64': 0.17.19 1690 | '@esbuild/linux-s390x': 0.17.19 1691 | '@esbuild/linux-x64': 0.17.19 1692 | '@esbuild/netbsd-x64': 0.17.19 1693 | '@esbuild/openbsd-x64': 0.17.19 1694 | '@esbuild/sunos-x64': 0.17.19 1695 | '@esbuild/win32-arm64': 0.17.19 1696 | '@esbuild/win32-ia32': 0.17.19 1697 | '@esbuild/win32-x64': 0.17.19 1698 | 1699 | esbuild@0.21.5: 1700 | optionalDependencies: 1701 | '@esbuild/aix-ppc64': 0.21.5 1702 | '@esbuild/android-arm': 0.21.5 1703 | '@esbuild/android-arm64': 0.21.5 1704 | '@esbuild/android-x64': 0.21.5 1705 | '@esbuild/darwin-arm64': 0.21.5 1706 | '@esbuild/darwin-x64': 0.21.5 1707 | '@esbuild/freebsd-arm64': 0.21.5 1708 | '@esbuild/freebsd-x64': 0.21.5 1709 | '@esbuild/linux-arm': 0.21.5 1710 | '@esbuild/linux-arm64': 0.21.5 1711 | '@esbuild/linux-ia32': 0.21.5 1712 | '@esbuild/linux-loong64': 0.21.5 1713 | '@esbuild/linux-mips64el': 0.21.5 1714 | '@esbuild/linux-ppc64': 0.21.5 1715 | '@esbuild/linux-riscv64': 0.21.5 1716 | '@esbuild/linux-s390x': 0.21.5 1717 | '@esbuild/linux-x64': 0.21.5 1718 | '@esbuild/netbsd-x64': 0.21.5 1719 | '@esbuild/openbsd-x64': 0.21.5 1720 | '@esbuild/sunos-x64': 0.21.5 1721 | '@esbuild/win32-arm64': 0.21.5 1722 | '@esbuild/win32-ia32': 0.21.5 1723 | '@esbuild/win32-x64': 0.21.5 1724 | 1725 | escape-string-regexp@4.0.0: {} 1726 | 1727 | estree-walker@0.6.1: {} 1728 | 1729 | estree-walker@3.0.3: 1730 | dependencies: 1731 | '@types/estree': 1.0.6 1732 | 1733 | exit-hook@2.2.1: {} 1734 | 1735 | expect-type@1.1.0: {} 1736 | 1737 | fill-range@7.1.1: 1738 | dependencies: 1739 | to-regex-range: 5.0.1 1740 | 1741 | foreground-child@3.3.0: 1742 | dependencies: 1743 | cross-spawn: 7.0.3 1744 | signal-exit: 4.1.0 1745 | 1746 | fsevents@2.3.3: 1747 | optional: true 1748 | 1749 | function-bind@1.1.2: {} 1750 | 1751 | get-source@2.0.12: 1752 | dependencies: 1753 | data-uri-to-buffer: 2.0.2 1754 | source-map: 0.6.1 1755 | 1756 | glob-parent@5.1.2: 1757 | dependencies: 1758 | is-glob: 4.0.3 1759 | 1760 | glob-to-regexp@0.4.1: {} 1761 | 1762 | glob@10.4.5: 1763 | dependencies: 1764 | foreground-child: 3.3.0 1765 | jackspeak: 3.4.3 1766 | minimatch: 9.0.5 1767 | minipass: 7.1.2 1768 | package-json-from-dist: 1.0.1 1769 | path-scurry: 1.11.1 1770 | 1771 | happy-dom@15.7.4: 1772 | dependencies: 1773 | entities: 4.5.0 1774 | webidl-conversions: 7.0.0 1775 | whatwg-mimetype: 3.0.0 1776 | 1777 | has-flag@4.0.0: {} 1778 | 1779 | hasown@2.0.2: 1780 | dependencies: 1781 | function-bind: 1.1.2 1782 | 1783 | hono@4.6.8: {} 1784 | 1785 | html-escaper@2.0.2: {} 1786 | 1787 | is-binary-path@2.1.0: 1788 | dependencies: 1789 | binary-extensions: 2.3.0 1790 | 1791 | is-core-module@2.15.1: 1792 | dependencies: 1793 | hasown: 2.0.2 1794 | 1795 | is-extglob@2.1.1: {} 1796 | 1797 | is-fullwidth-code-point@3.0.0: {} 1798 | 1799 | is-glob@4.0.3: 1800 | dependencies: 1801 | is-extglob: 2.1.1 1802 | 1803 | is-number@7.0.0: {} 1804 | 1805 | isexe@2.0.0: {} 1806 | 1807 | istanbul-lib-coverage@3.2.2: {} 1808 | 1809 | istanbul-lib-report@3.0.1: 1810 | dependencies: 1811 | istanbul-lib-coverage: 3.2.2 1812 | make-dir: 4.0.0 1813 | supports-color: 7.2.0 1814 | 1815 | istanbul-lib-source-maps@5.0.6: 1816 | dependencies: 1817 | '@jridgewell/trace-mapping': 0.3.25 1818 | debug: 4.3.7 1819 | istanbul-lib-coverage: 3.2.2 1820 | transitivePeerDependencies: 1821 | - supports-color 1822 | 1823 | istanbul-reports@3.1.7: 1824 | dependencies: 1825 | html-escaper: 2.0.2 1826 | istanbul-lib-report: 3.0.1 1827 | 1828 | itty-time@1.0.6: {} 1829 | 1830 | jackspeak@3.4.3: 1831 | dependencies: 1832 | '@isaacs/cliui': 8.0.2 1833 | optionalDependencies: 1834 | '@pkgjs/parseargs': 0.11.0 1835 | 1836 | loupe@3.1.2: {} 1837 | 1838 | lru-cache@10.4.3: {} 1839 | 1840 | magic-string@0.25.9: 1841 | dependencies: 1842 | sourcemap-codec: 1.4.8 1843 | 1844 | magic-string@0.30.12: 1845 | dependencies: 1846 | '@jridgewell/sourcemap-codec': 1.5.0 1847 | 1848 | magicast@0.3.5: 1849 | dependencies: 1850 | '@babel/parser': 7.26.1 1851 | '@babel/types': 7.26.0 1852 | source-map-js: 1.2.1 1853 | 1854 | make-dir@4.0.0: 1855 | dependencies: 1856 | semver: 7.6.3 1857 | 1858 | mime@3.0.0: {} 1859 | 1860 | miniflare@3.20241022.0: 1861 | dependencies: 1862 | '@cspotcode/source-map-support': 0.8.1 1863 | acorn: 8.14.0 1864 | acorn-walk: 8.3.4 1865 | capnp-ts: 0.7.0 1866 | exit-hook: 2.2.1 1867 | glob-to-regexp: 0.4.1 1868 | stoppable: 1.1.0 1869 | undici: 5.28.4 1870 | workerd: 1.20241022.0 1871 | ws: 8.18.0 1872 | youch: 3.3.4 1873 | zod: 3.23.8 1874 | transitivePeerDependencies: 1875 | - bufferutil 1876 | - supports-color 1877 | - utf-8-validate 1878 | 1879 | minimatch@9.0.5: 1880 | dependencies: 1881 | brace-expansion: 2.0.1 1882 | 1883 | minipass@7.1.2: {} 1884 | 1885 | ms@2.1.3: {} 1886 | 1887 | mustache@4.2.0: {} 1888 | 1889 | nanoid@3.3.7: {} 1890 | 1891 | node-forge@1.3.1: {} 1892 | 1893 | normalize-path@3.0.0: {} 1894 | 1895 | ohash@1.1.4: {} 1896 | 1897 | package-json-from-dist@1.0.1: {} 1898 | 1899 | path-key@3.1.1: {} 1900 | 1901 | path-parse@1.0.7: {} 1902 | 1903 | path-scurry@1.11.1: 1904 | dependencies: 1905 | lru-cache: 10.4.3 1906 | minipass: 7.1.2 1907 | 1908 | path-to-regexp@6.3.0: {} 1909 | 1910 | pathe@1.1.2: {} 1911 | 1912 | pathval@2.0.0: {} 1913 | 1914 | picocolors@1.1.1: {} 1915 | 1916 | picomatch@2.3.1: {} 1917 | 1918 | postcss@8.4.47: 1919 | dependencies: 1920 | nanoid: 3.3.7 1921 | picocolors: 1.1.1 1922 | source-map-js: 1.2.1 1923 | 1924 | printable-characters@1.0.42: {} 1925 | 1926 | readdirp@3.6.0: 1927 | dependencies: 1928 | picomatch: 2.3.1 1929 | 1930 | resolve.exports@2.0.2: {} 1931 | 1932 | resolve@1.22.8: 1933 | dependencies: 1934 | is-core-module: 2.15.1 1935 | path-parse: 1.0.7 1936 | supports-preserve-symlinks-flag: 1.0.0 1937 | 1938 | rollup-plugin-inject@3.0.2: 1939 | dependencies: 1940 | estree-walker: 0.6.1 1941 | magic-string: 0.25.9 1942 | rollup-pluginutils: 2.8.2 1943 | 1944 | rollup-plugin-node-polyfills@0.2.1: 1945 | dependencies: 1946 | rollup-plugin-inject: 3.0.2 1947 | 1948 | rollup-pluginutils@2.8.2: 1949 | dependencies: 1950 | estree-walker: 0.6.1 1951 | 1952 | rollup@4.24.3: 1953 | dependencies: 1954 | '@types/estree': 1.0.6 1955 | optionalDependencies: 1956 | '@rollup/rollup-android-arm-eabi': 4.24.3 1957 | '@rollup/rollup-android-arm64': 4.24.3 1958 | '@rollup/rollup-darwin-arm64': 4.24.3 1959 | '@rollup/rollup-darwin-x64': 4.24.3 1960 | '@rollup/rollup-freebsd-arm64': 4.24.3 1961 | '@rollup/rollup-freebsd-x64': 4.24.3 1962 | '@rollup/rollup-linux-arm-gnueabihf': 4.24.3 1963 | '@rollup/rollup-linux-arm-musleabihf': 4.24.3 1964 | '@rollup/rollup-linux-arm64-gnu': 4.24.3 1965 | '@rollup/rollup-linux-arm64-musl': 4.24.3 1966 | '@rollup/rollup-linux-powerpc64le-gnu': 4.24.3 1967 | '@rollup/rollup-linux-riscv64-gnu': 4.24.3 1968 | '@rollup/rollup-linux-s390x-gnu': 4.24.3 1969 | '@rollup/rollup-linux-x64-gnu': 4.24.3 1970 | '@rollup/rollup-linux-x64-musl': 4.24.3 1971 | '@rollup/rollup-win32-arm64-msvc': 4.24.3 1972 | '@rollup/rollup-win32-ia32-msvc': 4.24.3 1973 | '@rollup/rollup-win32-x64-msvc': 4.24.3 1974 | fsevents: 2.3.3 1975 | 1976 | selfsigned@2.4.1: 1977 | dependencies: 1978 | '@types/node-forge': 1.3.11 1979 | node-forge: 1.3.1 1980 | 1981 | semver@7.6.3: {} 1982 | 1983 | shebang-command@2.0.0: 1984 | dependencies: 1985 | shebang-regex: 3.0.0 1986 | 1987 | shebang-regex@3.0.0: {} 1988 | 1989 | siginfo@2.0.0: {} 1990 | 1991 | signal-exit@4.1.0: {} 1992 | 1993 | source-map-js@1.2.1: {} 1994 | 1995 | source-map@0.6.1: {} 1996 | 1997 | sourcemap-codec@1.4.8: {} 1998 | 1999 | stackback@0.0.2: {} 2000 | 2001 | stacktracey@2.1.8: 2002 | dependencies: 2003 | as-table: 1.0.55 2004 | get-source: 2.0.12 2005 | 2006 | std-env@3.7.0: {} 2007 | 2008 | stoppable@1.1.0: {} 2009 | 2010 | string-width@4.2.3: 2011 | dependencies: 2012 | emoji-regex: 8.0.0 2013 | is-fullwidth-code-point: 3.0.0 2014 | strip-ansi: 6.0.1 2015 | 2016 | string-width@5.1.2: 2017 | dependencies: 2018 | eastasianwidth: 0.2.0 2019 | emoji-regex: 9.2.2 2020 | strip-ansi: 7.1.0 2021 | 2022 | strip-ansi@6.0.1: 2023 | dependencies: 2024 | ansi-regex: 5.0.1 2025 | 2026 | strip-ansi@7.1.0: 2027 | dependencies: 2028 | ansi-regex: 6.1.0 2029 | 2030 | supports-color@7.2.0: 2031 | dependencies: 2032 | has-flag: 4.0.0 2033 | 2034 | supports-preserve-symlinks-flag@1.0.0: {} 2035 | 2036 | test-exclude@7.0.1: 2037 | dependencies: 2038 | '@istanbuljs/schema': 0.1.3 2039 | glob: 10.4.5 2040 | minimatch: 9.0.5 2041 | 2042 | tinybench@2.9.0: {} 2043 | 2044 | tinyexec@0.3.1: {} 2045 | 2046 | tinypool@1.0.1: {} 2047 | 2048 | tinyrainbow@1.2.0: {} 2049 | 2050 | tinyspy@3.0.2: {} 2051 | 2052 | to-regex-range@5.0.1: 2053 | dependencies: 2054 | is-number: 7.0.0 2055 | 2056 | tslib@2.8.0: {} 2057 | 2058 | ufo@1.5.4: {} 2059 | 2060 | undici-types@6.19.8: {} 2061 | 2062 | undici@5.28.4: 2063 | dependencies: 2064 | '@fastify/busboy': 2.1.1 2065 | 2066 | unenv-nightly@2.0.0-20241018-011344-e666fcf: 2067 | dependencies: 2068 | defu: 6.1.4 2069 | ohash: 1.1.4 2070 | pathe: 1.1.2 2071 | ufo: 1.5.4 2072 | 2073 | vite-node@2.1.4(@types/node@22.8.4): 2074 | dependencies: 2075 | cac: 6.7.14 2076 | debug: 4.3.7 2077 | pathe: 1.1.2 2078 | vite: 5.4.10(@types/node@22.8.4) 2079 | transitivePeerDependencies: 2080 | - '@types/node' 2081 | - less 2082 | - lightningcss 2083 | - sass 2084 | - sass-embedded 2085 | - stylus 2086 | - sugarss 2087 | - supports-color 2088 | - terser 2089 | 2090 | vite@5.4.10(@types/node@22.8.4): 2091 | dependencies: 2092 | esbuild: 0.21.5 2093 | postcss: 8.4.47 2094 | rollup: 4.24.3 2095 | optionalDependencies: 2096 | '@types/node': 22.8.4 2097 | fsevents: 2.3.3 2098 | 2099 | vitest@2.1.4(@types/node@22.8.4)(happy-dom@15.7.4): 2100 | dependencies: 2101 | '@vitest/expect': 2.1.4 2102 | '@vitest/mocker': 2.1.4(vite@5.4.10(@types/node@22.8.4)) 2103 | '@vitest/pretty-format': 2.1.4 2104 | '@vitest/runner': 2.1.4 2105 | '@vitest/snapshot': 2.1.4 2106 | '@vitest/spy': 2.1.4 2107 | '@vitest/utils': 2.1.4 2108 | chai: 5.1.2 2109 | debug: 4.3.7 2110 | expect-type: 1.1.0 2111 | magic-string: 0.30.12 2112 | pathe: 1.1.2 2113 | std-env: 3.7.0 2114 | tinybench: 2.9.0 2115 | tinyexec: 0.3.1 2116 | tinypool: 1.0.1 2117 | tinyrainbow: 1.2.0 2118 | vite: 5.4.10(@types/node@22.8.4) 2119 | vite-node: 2.1.4(@types/node@22.8.4) 2120 | why-is-node-running: 2.3.0 2121 | optionalDependencies: 2122 | '@types/node': 22.8.4 2123 | happy-dom: 15.7.4 2124 | transitivePeerDependencies: 2125 | - less 2126 | - lightningcss 2127 | - msw 2128 | - sass 2129 | - sass-embedded 2130 | - stylus 2131 | - sugarss 2132 | - supports-color 2133 | - terser 2134 | 2135 | webidl-conversions@7.0.0: {} 2136 | 2137 | whatwg-mimetype@3.0.0: {} 2138 | 2139 | which@2.0.2: 2140 | dependencies: 2141 | isexe: 2.0.0 2142 | 2143 | why-is-node-running@2.3.0: 2144 | dependencies: 2145 | siginfo: 2.0.0 2146 | stackback: 0.0.2 2147 | 2148 | workerd@1.20241022.0: 2149 | optionalDependencies: 2150 | '@cloudflare/workerd-darwin-64': 1.20241022.0 2151 | '@cloudflare/workerd-darwin-arm64': 1.20241022.0 2152 | '@cloudflare/workerd-linux-64': 1.20241022.0 2153 | '@cloudflare/workerd-linux-arm64': 1.20241022.0 2154 | '@cloudflare/workerd-windows-64': 1.20241022.0 2155 | 2156 | wrangler@3.83.0(@cloudflare/workers-types@4.20241022.0): 2157 | dependencies: 2158 | '@cloudflare/kv-asset-handler': 0.3.4 2159 | '@cloudflare/workers-shared': 0.7.0 2160 | '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19) 2161 | '@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19) 2162 | blake3-wasm: 2.1.5 2163 | chokidar: 3.6.0 2164 | date-fns: 4.1.0 2165 | esbuild: 0.17.19 2166 | itty-time: 1.0.6 2167 | miniflare: 3.20241022.0 2168 | nanoid: 3.3.7 2169 | path-to-regexp: 6.3.0 2170 | resolve: 1.22.8 2171 | resolve.exports: 2.0.2 2172 | selfsigned: 2.4.1 2173 | source-map: 0.6.1 2174 | unenv: unenv-nightly@2.0.0-20241018-011344-e666fcf 2175 | workerd: 1.20241022.0 2176 | xxhash-wasm: 1.0.2 2177 | optionalDependencies: 2178 | '@cloudflare/workers-types': 4.20241022.0 2179 | fsevents: 2.3.3 2180 | transitivePeerDependencies: 2181 | - bufferutil 2182 | - supports-color 2183 | - utf-8-validate 2184 | 2185 | wrap-ansi@7.0.0: 2186 | dependencies: 2187 | ansi-styles: 4.3.0 2188 | string-width: 4.2.3 2189 | strip-ansi: 6.0.1 2190 | 2191 | wrap-ansi@8.1.0: 2192 | dependencies: 2193 | ansi-styles: 6.2.1 2194 | string-width: 5.1.2 2195 | strip-ansi: 7.1.0 2196 | 2197 | ws@8.18.0: {} 2198 | 2199 | xxhash-wasm@1.0.2: {} 2200 | 2201 | youch@3.3.4: 2202 | dependencies: 2203 | cookie: 0.7.2 2204 | mustache: 4.2.0 2205 | stacktracey: 2.1.8 2206 | 2207 | zod@3.23.8: {} 2208 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TinsFox/github-hosts/9fc2dce3736ff77c1418e75538664b861bcdb82d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: #3b82f6; 3 | --primary-hover: #2563eb; 4 | --success-color: #166534; 5 | --success-bg: #dcfce7; 6 | --info-color: #1e40af; 7 | --info-bg: #dbeafe; 8 | --border-color: #e5e7eb; 9 | --bg-hover: #f1f5f9; 10 | --text-primary: #374151; 11 | --text-secondary: #4b5563; 12 | --radius-sm: 6px; 13 | --radius-md: 8px; 14 | --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); 15 | --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 16 | 0 2px 4px -1px rgba(0, 0, 0, 0.06); 17 | --transition: all 0.2s ease; 18 | } 19 | 20 | body { 21 | max-width: min(1000px, 95%); 22 | margin: 0 auto; 23 | padding: 20px; 24 | font-family: system-ui, -apple-system, sans-serif; 25 | line-height: 1.5; 26 | color: var(--text-primary); 27 | } 28 | 29 | .header { 30 | text-align: center; 31 | margin-bottom: 3rem; 32 | padding: 2rem 0; 33 | } 34 | 35 | .header h1 { 36 | margin: 0; 37 | font-size: 2.5rem; 38 | color: var(--primary-color); 39 | } 40 | 41 | .api-table { 42 | width: 100%; 43 | border-collapse: separate; 44 | border-spacing: 0; 45 | margin: 1.5rem 0; 46 | border: 1px solid var(--border-color); 47 | border-radius: var(--radius-md); 48 | background-color: white; 49 | box-shadow: var(--shadow-md); 50 | } 51 | 52 | .api-table th { 53 | background-color: #f9fafb; 54 | padding: 1rem 1.5rem; 55 | text-align: left; 56 | border-bottom: 1px solid var(--border-color); 57 | font-size: 0.875rem; 58 | font-weight: 600; 59 | } 60 | 61 | .api-table td { 62 | padding: 1rem 1.5rem; 63 | border-bottom: 1px solid var(--border-color); 64 | font-size: 0.875rem; 65 | vertical-align: top; 66 | } 67 | 68 | .api-table tr:last-child td { 69 | border-bottom: none; 70 | } 71 | 72 | .method { 73 | display: inline-flex; 74 | align-items: center; 75 | justify-content: center; 76 | padding: 0.25rem 0.75rem; 77 | border-radius: var(--radius-sm); 78 | font-size: 0.75rem; 79 | font-weight: 500; 80 | text-transform: uppercase; 81 | letter-spacing: 0.05em; 82 | min-width: 60px; 83 | transition: var(--transition); 84 | } 85 | 86 | .get { 87 | background-color: var(--info-bg); 88 | color: var(--info-color); 89 | } 90 | 91 | .post { 92 | background-color: var(--success-bg); 93 | color: var(--success-color); 94 | } 95 | 96 | .try-btn { 97 | background-color: var(--primary-color); 98 | color: white; 99 | border: none; 100 | padding: 0.25rem 0.75rem; 101 | border-radius: var(--radius-sm); 102 | font-size: 0.75rem; 103 | font-weight: 500; 104 | cursor: pointer; 105 | transition: var(--transition); 106 | box-shadow: var(--shadow-sm); 107 | } 108 | 109 | .try-btn.loading { 110 | position: relative; 111 | padding-left: 2rem; 112 | } 113 | 114 | .try-btn.loading::before { 115 | content: ""; 116 | position: absolute; 117 | left: 0.5rem; 118 | top: 50%; 119 | width: 1rem; 120 | height: 1rem; 121 | border: 2px solid #fff; 122 | border-radius: 50%; 123 | border-top-color: transparent; 124 | animation: spin 1s linear infinite; 125 | } 126 | 127 | @keyframes spin { 128 | to { 129 | transform: rotate(360deg); 130 | } 131 | } 132 | 133 | td:hover .try-btn { 134 | display: inline-flex; 135 | align-items: center; 136 | } 137 | 138 | .try-btn:hover { 139 | background-color: var(--primary-hover); 140 | transform: translateY(-1px); 141 | box-shadow: var(--shadow-md); 142 | } 143 | 144 | .try-btn:disabled { 145 | opacity: 0.7; 146 | cursor: not-allowed; 147 | transform: none; 148 | } 149 | 150 | .response-area { 151 | display: none; 152 | margin-top: 1rem; 153 | padding: 1rem; 154 | background-color: #f9fafb; 155 | border: 1px solid var(--border-color); 156 | border-radius: var(--radius-sm); 157 | font-family: ui-monospace, monospace; 158 | font-size: 0.875rem; 159 | line-height: 1.6; 160 | position: relative; 161 | transition: var(--transition); 162 | } 163 | 164 | .response-area .action-buttons { 165 | position: absolute; 166 | right: 0.5rem; 167 | top: 0.5rem; 168 | display: none; 169 | gap: 0.5rem; 170 | padding: 0.25rem; 171 | background-color: rgba(249, 250, 251, 0.9); 172 | border-radius: var(--radius-sm); 173 | } 174 | 175 | .response-area:hover .action-buttons { 176 | display: flex; 177 | } 178 | 179 | .response-copy-btn, 180 | .response-collapse-btn { 181 | background-color: var(--text-secondary); 182 | border: none; 183 | padding: 0.25rem 0.75rem; 184 | border-radius: var(--radius-sm); 185 | color: white; 186 | font-size: 0.75rem; 187 | cursor: pointer; 188 | transition: var(--transition); 189 | } 190 | 191 | .response-copy-btn:hover, 192 | .response-collapse-btn:hover { 193 | background-color: var(--text-primary); 194 | transform: translateY(-1px); 195 | } 196 | 197 | .response-area.collapsed { 198 | max-height: 100px; 199 | overflow-y: hidden; 200 | cursor: pointer; 201 | } 202 | 203 | .response-area.collapsed::after { 204 | content: ""; 205 | position: absolute; 206 | bottom: 0; 207 | left: 0; 208 | right: 0; 209 | height: 40px; 210 | background: linear-gradient(transparent, #f9fafb); 211 | pointer-events: none; 212 | } 213 | 214 | .code-block { 215 | background-color: #f8f9fa; 216 | padding: 0.75rem; 217 | border-radius: var(--radius-sm); 218 | margin: 0.5rem 0; 219 | position: relative; 220 | } 221 | 222 | .copy-btn { 223 | right: 0.5rem; 224 | background-color: var(--text-secondary); 225 | color: white; 226 | border: none; 227 | padding: 0.25rem 0.75rem; 228 | border-radius: var(--radius-sm); 229 | font-size: 0.75rem; 230 | cursor: pointer; 231 | transition: var(--transition); 232 | opacity: 1; 233 | } 234 | 235 | .code-block:hover { 236 | opacity: 1; 237 | } 238 | 239 | @media (max-width: 640px) { 240 | body { 241 | padding: 1rem; 242 | } 243 | 244 | .api-table td, 245 | .api-table th { 246 | padding: 0.75rem; 247 | } 248 | 249 | .header h1 { 250 | font-size: 2rem; 251 | } 252 | } 253 | 254 | .logo-container { 255 | display: flex; 256 | align-items: center; 257 | justify-content: center; 258 | margin-bottom: 1rem; 259 | perspective: 1000px; 260 | } 261 | 262 | .logo { 263 | width: 140px; 264 | height: 140px; 265 | transition: transform 0.3s ease; 266 | transform: translateZ(0); 267 | will-change: transform; 268 | } 269 | 270 | .logo:hover { 271 | transform: scale(1.05) rotate3d(1, 1, 0, 5deg); 272 | } 273 | 274 | @media (prefers-reduced-motion: reduce) { 275 | .logo { 276 | animation: none; 277 | transition: none; 278 | } 279 | 280 | svg animate, 281 | svg animateTransform { 282 | animation: none; 283 | } 284 | } 285 | 286 | @media (max-width: 640px) { 287 | .logo { 288 | width: 100px; 289 | height: 100px; 290 | } 291 | } 292 | 293 | #hosts { 294 | height: 200px; 295 | overflow-y: auto; 296 | } 297 | 298 | .important-note { 299 | background-color: #fff3cd; 300 | border: 1px solid #ffeeba; 301 | padding: 1rem; 302 | margin: 1rem 0; 303 | border-radius: 4px; 304 | } 305 | 306 | .important-note p { 307 | margin: 0; 308 | color: #856404; 309 | } 310 | 311 | code { 312 | background-color: #f6f8fa; 313 | padding: 0.2em 0.4em; 314 | border-radius: 3px; 315 | font-family: monospace; 316 | } 317 | 318 | pre code { 319 | display: block; 320 | padding: 1em; 321 | overflow-x: auto; 322 | } 323 | 324 | .hosts-container { 325 | position: relative; 326 | margin: 1rem 0; 327 | background: #f6f8fa; 328 | border: 1px solid var(--border-color); 329 | border-radius: var(--radius-md); 330 | padding: 1rem; 331 | } 332 | 333 | .hosts-container .copy-btn { 334 | position: absolute; 335 | top: 1rem; 336 | right: 1rem; 337 | background-color: var(--text-secondary); 338 | color: white; 339 | border: none; 340 | padding: 0.25rem 0.75rem; 341 | border-radius: var(--radius-sm); 342 | font-size: 0.75rem; 343 | cursor: pointer; 344 | transition: var(--transition); 345 | } 346 | 347 | .hosts-container .copy-btn:hover { 348 | background-color: var(--text-primary); 349 | transform: translateY(-1px); 350 | } 351 | 352 | #hosts { 353 | margin: 0; 354 | padding: 0.5rem; 355 | max-height: 400px; 356 | overflow-y: auto; 357 | font-family: monospace; 358 | font-size: 0.875rem; 359 | line-height: 1.5; 360 | white-space: pre; 361 | word-wrap: normal; 362 | overflow-x: auto; 363 | } 364 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | GitHub Host - 加速访问 GitHub | 自动更新的 Hosts 工具 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | 48 |
49 |

GitHub Host

50 |

使用 Cloudflare Workers 和公共 DNS API 加速访问 GitHub

51 |
52 | 53 |

📝 项目介绍

54 |

GitHub 访问加速,解决 GitHub 访问慢的问题。使用 Cloudflare Workers 和公共 DNS API 来获取 IP 地址。

55 | 感谢 GitHub520 提供的灵感。 56 | 57 |

🚀 特点

58 | 65 | 66 |

💻 使用方法

67 |

1. 命令行工具(推荐)

68 | 69 |

MacOS 用户

70 |
sudo curl -fsSL https://github.com/TinsFox/github-hosts/releases/download/v0.0.1/github-hosts.darwin-arm64 -o github-hosts && sudo chmod +x ./github-hosts && ./github-hosts
71 | 72 |
73 |

⚠️ Windows 与 Linux 的脚本还没有经过测试,遇到问题请提 issue

74 |
75 | 76 |

Windows 用户

77 |

在管理员权限的 PowerShell 中执行:

78 |
irm https://github.com/TinsFox/github-hosts/releases/download/v0.0.1/github-hosts.windows-amd64.exe | iex
79 | 80 |

Linux 用户

81 |
sudo curl -fsSL https://github.com/TinsFox/github-hosts/releases/download/v0.0.1/github-hosts.linux-amd64 -o github-hosts && sudo chmod +x ./github-hosts && ./github-hosts
82 | 83 |

更多版本请查看 Release 页面

84 | 85 |

2. 使用 SwitchHosts 工具

86 |
    87 |
  1. 下载并安装 SwitchHosts
  2. 88 |
  3. 添加规则: 89 | 95 |
  4. 96 |
97 | 98 |

当前 hosts 内容

99 |
100 | 101 |
正在加载 hosts 内容...
102 |
103 | 104 |

3. 手动更新

105 |
    106 |
  1. 获取 hosts:访问 https://github-hosts.tinsfox.com/hosts
  2. 107 |
  3. 更新本地 hosts 文件: 108 | 112 |
  4. 113 |
  5. 刷新 DNS: 114 | 119 |
  6. 120 |
121 | 122 |

❓ 常见问题

123 |

权限问题

124 | 128 | 129 |

定时任务未生效

130 | 134 | 135 |

更新失败

136 | 140 | 141 |

🔧 API 接口文档

142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 |
接口方法描述
/hostsGET获取 hosts 文件内容
/hosts.jsonGET获取 JSON 格式的数据
/{domain}GET获取指定域名的实时 DNS 解析结果
/resetPOST清空缓存并重新获取所有数据(需要 API Key)
169 | 170 |

📦 源码

171 |

本项目完全开源,欢迎访问 GitHub 仓库

172 | 173 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /public/index.js: -------------------------------------------------------------------------------- 1 | // 获取当前页面的基础 URL 2 | const baseUrl = window.location.origin 3 | 4 | function escapeHtml(str) { 5 | const div = document.createElement("div") 6 | div.textContent = str 7 | return div.innerHTML 8 | } 9 | 10 | async function copyToClipboard(btn) { 11 | try { 12 | const hostsElement = document.getElementById("hosts") 13 | await navigator.clipboard.writeText(hostsElement.textContent) 14 | 15 | const originalText = btn.textContent 16 | btn.textContent = "已复制" 17 | 18 | setTimeout(() => { 19 | btn.textContent = originalText 20 | }, 1000) 21 | } catch (err) { 22 | console.error("复制失败:", err) 23 | } 24 | } 25 | 26 | async function loadHosts() { 27 | const hostsElement = document.getElementById("hosts") 28 | // 如果元素不存在,直接返回 29 | if (!hostsElement) return 30 | 31 | try { 32 | const response = await fetch(`${baseUrl}/hosts`) 33 | if (!response.ok) throw new Error("Failed to load hosts") 34 | const hostsContent = await response.text() 35 | hostsElement.textContent = hostsContent 36 | } catch (error) { 37 | hostsElement.textContent = "加载 hosts 内容失败,请稍后重试" 38 | console.error("Error loading hosts:", error) 39 | } 40 | } 41 | 42 | function setupEventListeners() { 43 | document.querySelectorAll(".copy-btn").forEach((btn) => { 44 | btn.addEventListener("click", () => copyToClipboard(btn)) 45 | }) 46 | 47 | document.addEventListener("click", (e) => { 48 | if (e.target.classList.contains("response-collapse-btn")) { 49 | toggleCollapse(e.target) 50 | } 51 | 52 | if (e.target.closest(".response-area.collapsed")) { 53 | const collapseBtn = e.target 54 | .closest(".response-area") 55 | .querySelector(".response-collapse-btn") 56 | if (collapseBtn) { 57 | toggleCollapse(collapseBtn) 58 | } 59 | } 60 | }) 61 | } 62 | 63 | // 确保 DOM 完全加载后再执行 64 | document.addEventListener("DOMContentLoaded", () => { 65 | loadHosts() 66 | setupEventListeners() 67 | }) 68 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 80 | 85 | 86 | -------------------------------------------------------------------------------- /public/og.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | GitHub Host 26 | 27 | 28 | 29 | 30 | 使用 Cloudflare Workers 和公共 DNS API 加速访问 GitHub 31 | 32 | 33 | 34 | 35 | 36 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /scripts/backup.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "sort" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // listBackups 获取备份文件列表 13 | func (app *App) listBackups() ([]string, error) { 14 | files, err := os.ReadDir(app.backupDir) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | var backups []string 20 | for _, file := range files { 21 | if !file.IsDir() && strings.HasPrefix(file.Name(), "hosts_") { 22 | backups = append(backups, file.Name()) 23 | } 24 | } 25 | return backups, nil 26 | } 27 | 28 | // listBackupsWithDetails 显示备份列表详情 29 | func (app *App) listBackupsWithDetails() error { 30 | backups, err := app.listBackups() 31 | if err != nil { 32 | return err 33 | } 34 | 35 | if len(backups) == 0 { 36 | app.logWithLevel(INFO, "没有找到备份文件") 37 | return nil 38 | } 39 | 40 | fmt.Println("\n可用的备份文件:") 41 | fmt.Println("序号\t备份时间\t\t文件大小") 42 | fmt.Println("----------------------------------------") 43 | 44 | // 按时间排序 45 | sort.Slice(backups, func(i, j int) bool { 46 | return backups[i] > backups[j] // 降序排列 47 | }) 48 | 49 | for i, backup := range backups { 50 | path := filepath.Join(app.backupDir, backup) 51 | info, err := os.Stat(path) 52 | if err != nil { 53 | continue 54 | } 55 | timeStr := strings.TrimPrefix(backup, "hosts_") 56 | timeStr = strings.TrimSuffix(timeStr, filepath.Ext(timeStr)) 57 | fmt.Printf("%d\t%s\t%.2f KB\n", i+1, timeStr, float64(info.Size())/1024) 58 | } 59 | return nil 60 | } 61 | 62 | // createNewBackup 创建新的备份 63 | func (app *App) createNewBackup() error { 64 | app.logWithLevel(INFO, "创建新的备份...") 65 | if err := app.backupHosts(); err != nil { 66 | return err 67 | } 68 | app.logWithLevel(SUCCESS, "备份创建成功") 69 | return nil 70 | } 71 | 72 | // backupHosts 备份当前 hosts 文件 73 | func (app *App) backupHosts() error { 74 | timestamp := time.Now().Format("20060102_150405") 75 | backupPath := filepath.Join(app.backupDir, fmt.Sprintf("hosts_%s", timestamp)) 76 | 77 | input, err := os.ReadFile(hostsFile) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | return os.WriteFile(backupPath, input, 0644) 83 | } 84 | 85 | // restoreBackupMenu 显示恢复备份菜单 86 | func (app *App) restoreBackupMenu() error { 87 | backups, err := app.listBackups() 88 | if err != nil { 89 | return err 90 | } 91 | 92 | if len(backups) == 0 { 93 | app.logWithLevel(INFO, "没有可用的备份文件") 94 | return nil 95 | } 96 | 97 | if err := app.listBackupsWithDetails(); err != nil { 98 | return err 99 | } 100 | 101 | fmt.Print("\n请选择要恢复的备份序号(0 取消): ") 102 | var choice int 103 | fmt.Scanf("%d", &choice) 104 | 105 | if choice == 0 { 106 | return nil 107 | } 108 | 109 | if choice < 1 || choice > len(backups) { 110 | return fmt.Errorf("无效的选择") 111 | } 112 | 113 | // 确认恢复 114 | fmt.Print("确定要恢复这个备份吗?这将覆盖当前的 hosts 文件 [y/N]: ") 115 | var confirm string 116 | fmt.Scanf("%s", &confirm) 117 | 118 | if strings.ToLower(confirm) != "y" { 119 | app.logWithLevel(INFO, "已取消恢复操作") 120 | return nil 121 | } 122 | 123 | backupFile := filepath.Join(app.backupDir, backups[choice-1]) 124 | return app.restoreBackup(backupFile) 125 | } 126 | 127 | // deleteBackupMenu 显示删除备份菜单 128 | func (app *App) deleteBackupMenu() error { 129 | backups, err := app.listBackups() 130 | if err != nil { 131 | return err 132 | } 133 | 134 | if len(backups) == 0 { 135 | app.logWithLevel(INFO, "没有可用的备份文件") 136 | return nil 137 | } 138 | 139 | if err := app.listBackupsWithDetails(); err != nil { 140 | return err 141 | } 142 | 143 | fmt.Print("\n请选择要删除的备份序号(0 取消): ") 144 | var choice int 145 | fmt.Scanf("%d", &choice) 146 | 147 | if choice == 0 { 148 | return nil 149 | } 150 | 151 | if choice < 1 || choice > len(backups) { 152 | return fmt.Errorf("无效的选择") 153 | } 154 | 155 | // 确认删除 156 | fmt.Print("确定要删除这个备份吗?此操作不可恢复 [y/N]: ") 157 | var confirm string 158 | fmt.Scanf("%s", &confirm) 159 | 160 | if strings.ToLower(confirm) != "y" { 161 | app.logWithLevel(INFO, "已取消删除操作") 162 | return nil 163 | } 164 | 165 | backupFile := filepath.Join(app.backupDir, backups[choice-1]) 166 | if err := os.Remove(backupFile); err != nil { 167 | return fmt.Errorf("删除备份失败: %w", err) 168 | } 169 | 170 | app.logWithLevel(SUCCESS, "备份已删除") 171 | return nil 172 | } 173 | 174 | // restoreBackup 恢复指定的备份文件 175 | func (app *App) restoreBackup(backupFile string) error { 176 | // 先创建当前 hosts 文件的备份 177 | if err := app.backupHosts(); err != nil { 178 | return fmt.Errorf("创建当前 hosts 备份失败: %w", err) 179 | } 180 | 181 | // 读取备份文件 182 | content, err := os.ReadFile(backupFile) 183 | if err != nil { 184 | return fmt.Errorf("读取备份文件失败: %w", err) 185 | } 186 | 187 | // 写入到 hosts 文件 188 | if err := os.WriteFile(hostsFile, content, 0644); err != nil { 189 | return fmt.Errorf("恢复 hosts 文件失败: %w", err) 190 | } 191 | 192 | // 刷新 DNS 缓存 193 | if err := app.flushDNSCache(); err != nil { 194 | app.logWithLevel(WARNING, "DNS 缓存刷新失败: %v", err) 195 | } 196 | 197 | app.logWithLevel(SUCCESS, "hosts 文件已恢复") 198 | return nil 199 | } 200 | -------------------------------------------------------------------------------- /scripts/banner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const banner = ` 4 | _______ __ __ __ __ __ __ 5 | / ____(_) /_/ /_ __ __/ /_ / /_ ____ / /_/ /______ 6 | / / __/ / __/ __ \/ / / / __ \/ __ \/ __ \/ __/ __/ ___/ 7 | / /_/ / / /_/ / / / /_/ / / / / /_/ / /_/ / /_/ /_(__ ) 8 | \____/_/\__/_/ /_/\__,_/_/ /_/\____/\____/\__/\__/____/ 9 | GitHub Hosts Manager - https://github.com/TinsFox/github-hosts 10 | Version: 1.0.0 11 | 加速访问 GitHub 12 | ` 13 | -------------------------------------------------------------------------------- /scripts/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal enabledelayedexpansion 3 | 4 | :: 切换到脚本所在目录 5 | cd /d "%~dp0" 6 | 7 | :: 版本号 8 | set VERSION=1.0.0 9 | 10 | :: 构建目录 11 | set BUILD_DIR=..\build 12 | set BINARY_NAME=github-hosts 13 | 14 | :: 清理构建目录 15 | echo 清理构建目录... 16 | if exist %BUILD_DIR% rd /s /q %BUILD_DIR% 17 | mkdir %BUILD_DIR% 18 | 19 | :: 定义支持的平台 20 | set PLATFORMS=^ 21 | darwin/amd64^ 22 | darwin/arm64^ 23 | linux/amd64^ 24 | linux/arm64^ 25 | windows/amd64 26 | 27 | :: 遍历平台进行构建 28 | for %%p in (%PLATFORMS%) do ( 29 | for /f "tokens=1,2 delims=/" %%a in ("%%p") do ( 30 | set GOOS=%%a 31 | set GOARCH=%%b 32 | 33 | :: 构建输出文件名 34 | set OUTPUT=%BUILD_DIR%\%BINARY_NAME%_!GOOS!_!GOARCH! 35 | if "!GOOS!"=="windows" set OUTPUT=!OUTPUT!.exe 36 | 37 | echo 正在构建 !GOOS!/!GOARCH!... 38 | 39 | :: 执行构建 40 | set GOOS=!GOOS! 41 | set GOARCH=!GOARCH! 42 | go build -o "!OUTPUT!" -ldflags="-s -w -X main.Version=%VERSION%" . 43 | 44 | if !errorlevel! equ 0 ( 45 | echo √ 构建成功: !OUTPUT! 46 | ) else ( 47 | echo × 构建失败: !GOOS!/!GOARCH! 48 | ) 49 | ) 50 | ) 51 | 52 | :: 创建压缩包 53 | echo 创建压缩包... 54 | cd %BUILD_DIR% 55 | for %%f in (*) do ( 56 | if exist "%%f" ( 57 | tar -czf "%%f.tar.gz" "%%f" 58 | del "%%f" 59 | echo √ 已创建: %%f.tar.gz 60 | ) 61 | ) 62 | cd .. 63 | 64 | echo 构建完成! 65 | dir /b %BUILD_DIR% 66 | 67 | endlocal -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 定义颜色 4 | RED='\033[0;31m' 5 | GREEN='\033[0;32m' 6 | YELLOW='\033[1;33m' 7 | NC='\033[0m' # No Color 8 | 9 | # 切换到 scripts 目录 10 | cd "$(dirname "$0")" 11 | 12 | # 检查 go.mod 文件 13 | if [ ! -f "go.mod" ]; then 14 | echo -e "${YELLOW}初始化 Go 模块...${NC}" 15 | go mod init github.com/TinsFox/github-hosts/scripts 16 | go mod tidy 17 | fi 18 | 19 | # 版本号 20 | VERSION="1.0.0" 21 | 22 | # 构建目录 23 | BUILD_DIR="../build" 24 | BINARY_NAME="github-hosts" 25 | 26 | # 支持的平台 27 | PLATFORMS=( 28 | "darwin/amd64" 29 | "darwin/arm64" 30 | "linux/amd64" 31 | "linux/arm64" 32 | "windows/amd64" 33 | ) 34 | 35 | # 清理构建目录 36 | echo -e "${YELLOW}清理构建目录...${NC}" 37 | rm -rf $BUILD_DIR 38 | mkdir -p $BUILD_DIR 39 | 40 | # 遍历平台进行构建 41 | for PLATFORM in "${PLATFORMS[@]}"; do 42 | # 分割平台信息 43 | IFS='/' read -r -a array <<< "$PLATFORM" 44 | GOOS="${array[0]}" 45 | GOARCH="${array[1]}" 46 | 47 | # 构建输出文件名 48 | OUTPUT="$BUILD_DIR/${BINARY_NAME}_${GOOS}_${GOARCH}" 49 | if [ "$GOOS" = "windows" ]; then 50 | OUTPUT="${OUTPUT}.exe" 51 | fi 52 | 53 | echo -e "${YELLOW}正在构建 $GOOS/$GOARCH...${NC}" 54 | 55 | # 执行构建 56 | GOOS=$GOOS GOARCH=$GOARCH go build -o "$OUTPUT" -ldflags="-s -w -X main.Version=$VERSION" . 57 | 58 | if [ $? -eq 0 ]; then 59 | echo -e "${GREEN}✓ 构建成功: $OUTPUT${NC}" 60 | else 61 | echo -e "${RED}✗ 构建失败: $GOOS/$GOARCH${NC}" 62 | fi 63 | done 64 | 65 | # 创建压缩包 66 | echo -e "${YELLOW}创建压缩包...${NC}" 67 | cd $BUILD_DIR 68 | for FILE in *; do 69 | if [ -f "$FILE" ]; then 70 | tar -czf "${FILE}.tar.gz" "$FILE" 71 | echo -e "${GREEN}✓ 已创建: ${FILE}.tar.gz${NC}" 72 | fi 73 | done 74 | cd .. 75 | 76 | echo -e "${GREEN}构建完成!${NC}" 77 | -------------------------------------------------------------------------------- /scripts/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "runtime" 10 | "time" 11 | ) 12 | 13 | // toggleAutoUpdate 切换自动更新状态 14 | func (app *App) toggleAutoUpdate() error { 15 | config, err := app.loadConfig() 16 | if err != nil { 17 | return fmt.Errorf("读取配置失败: %w", err) 18 | } 19 | 20 | currentStatus := map[bool]string{true: "开启", false: "关闭"}[config.AutoUpdate] 21 | targetStatus := map[bool]string{true: "关闭", false: "开启"}[config.AutoUpdate] 22 | 23 | fmt.Printf("\n当前自动更新已%s,是否%s?[y/N]: ", currentStatus, targetStatus) 24 | 25 | var response string 26 | fmt.Scanf("%s", &response) 27 | 28 | if response != "y" && response != "Y" { 29 | app.logWithLevel(INFO, "保持当前状态不变") 30 | return nil 31 | } 32 | 33 | // 更新配置 34 | config.AutoUpdate = !config.AutoUpdate 35 | if err := app.updateConfig(config.UpdateInterval, config.AutoUpdate); err != nil { 36 | return fmt.Errorf("更新配置失败: %w", err) 37 | } 38 | 39 | if config.AutoUpdate { 40 | // 开启自动更新时,设置定时任务 41 | if err := app.setupCron(config.UpdateInterval); err != nil { 42 | app.logWithLevel(ERROR, "设置定时任务失败: %v", err) 43 | // 回滚配置 44 | config.AutoUpdate = false 45 | app.updateConfig(config.UpdateInterval, false) 46 | return fmt.Errorf("设置定时任务失败: %w", err) 47 | } 48 | app.logWithLevel(SUCCESS, "自动更新已开启,更新间隔为 %d 分钟", config.UpdateInterval) 49 | } else { 50 | // 关闭自动更新时,移除定时任务 51 | if runtime.GOOS == "darwin" { 52 | exec.Command("launchctl", "bootout", "system/com.github.hosts").Run() 53 | os.Remove(darwinPlistPath) 54 | } else if runtime.GOOS == "windows" { 55 | exec.Command("schtasks", "/delete", "/tn", windowsTaskName, "/f").Run() 56 | } else { 57 | os.Remove(linuxCronPath) 58 | } 59 | app.logWithLevel(SUCCESS, "自动更新已关闭") 60 | } 61 | 62 | return nil 63 | } 64 | 65 | // changeUpdateInterval 修改更新间隔 66 | func (app *App) changeUpdateInterval() error { 67 | config, err := app.loadConfig() 68 | if err != nil { 69 | return fmt.Errorf("读取配置失败: %w", err) 70 | } 71 | 72 | fmt.Println("\n请选择新的更新间隔:") 73 | fmt.Println("1. 每 30 分钟") 74 | fmt.Println("2. 每 60 分钟") 75 | fmt.Println("3. 每 120 分钟") 76 | 77 | var choice int 78 | fmt.Scanf("%d", &choice) 79 | 80 | var interval int 81 | switch choice { 82 | case 1: 83 | interval = 30 84 | case 2: 85 | interval = 60 86 | case 3: 87 | interval = 120 88 | default: 89 | return fmt.Errorf("无效的选项") 90 | } 91 | 92 | // 更新配置 93 | if err := app.updateConfig(interval, config.AutoUpdate); err != nil { 94 | return fmt.Errorf("更新配置失败: %w", err) 95 | } 96 | 97 | // 如果启用了自动更新,则更新定时任务 98 | if config.AutoUpdate { 99 | if err := app.setupCron(interval); err != nil { 100 | return fmt.Errorf("更新定时任务失败: %w", err) 101 | } 102 | } 103 | 104 | app.logWithLevel(SUCCESS, "更新间隔已修改为 %d 分钟", interval) 105 | return nil 106 | } 107 | 108 | // exportConfigToFile 导出配置到文件 109 | func (app *App) exportConfigToFile() error { 110 | exportPath := filepath.Join(app.baseDir, fmt.Sprintf("config_export_%s.json", time.Now().Format("20060102_150405"))) 111 | 112 | data, err := os.ReadFile(app.configFile) 113 | if err != nil { 114 | return fmt.Errorf("读取配置失败: %w", err) 115 | } 116 | 117 | if err := os.WriteFile(exportPath, data, 0644); err != nil { 118 | return fmt.Errorf("导出配置失败: %w", err) 119 | } 120 | 121 | app.logWithLevel(SUCCESS, "配置已导出到: %s", exportPath) 122 | return nil 123 | } 124 | 125 | // importConfigFromFile 从文件导入配置 126 | func (app *App) importConfigFromFile() error { 127 | fmt.Print("请输入配置文件路径: ") 128 | var path string 129 | fmt.Scanf("%s", &path) 130 | 131 | data, err := os.ReadFile(path) 132 | if err != nil { 133 | return fmt.Errorf("读取配置文件失败: %w", err) 134 | } 135 | 136 | var config Config 137 | if err := json.Unmarshal(data, &config); err != nil { 138 | return fmt.Errorf("配置文件格式无效: %w", err) 139 | } 140 | 141 | if err := os.WriteFile(app.configFile, data, 0644); err != nil { 142 | return fmt.Errorf("更新配置失败: %w", err) 143 | } 144 | 145 | app.logWithLevel(SUCCESS, "配置导入成功") 146 | return nil 147 | } 148 | -------------------------------------------------------------------------------- /scripts/cron.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "runtime" 9 | "text/template" 10 | ) 11 | 12 | // setupCron 根据操作系统设置定时任务 13 | func (app *App) setupCron(interval int) error { 14 | // 创建更新脚本 15 | scriptPath := filepath.Join(app.baseDir, "update.sh") 16 | if runtime.GOOS == "windows" { 17 | scriptPath = filepath.Join(app.baseDir, "update.bat") 18 | } 19 | 20 | if err := app.createUpdateScript(scriptPath); err != nil { 21 | return fmt.Errorf("创建更新脚本失败: %w", err) 22 | } 23 | 24 | // 根据操作系统选择不同的定时任务实现 25 | switch runtime.GOOS { 26 | case "darwin": 27 | return app.setupDarwinCron(interval, scriptPath) 28 | case "linux": 29 | return app.setupLinuxCron(interval, scriptPath) 30 | case "windows": 31 | return app.setupWindowsCron(interval, scriptPath) 32 | default: 33 | return fmt.Errorf("不支持的操作系统: %s", runtime.GOOS) 34 | } 35 | } 36 | 37 | // createUpdateScript 创建更新脚本 38 | func (app *App) createUpdateScript(path string) error { 39 | var content string 40 | if runtime.GOOS == "windows" { 41 | // Windows 批处理脚本 42 | content = `@echo off 43 | echo [%date% %time%] 开始更新 hosts 文件... >> "{{.LogDir}}\update.log" 44 | 45 | :: 清理已存在的 GitHub Hosts 内容 46 | powershell -Command "& {(Get-Content '{{.HostsFile}}') -notmatch '===== GitHub Hosts (Start|End) =====' | Set-Content '{{.HostsFile}}.tmp'}" 47 | move /Y "{{.HostsFile}}.tmp" "{{.HostsFile}}" 48 | 49 | :: 获取新的 hosts 内容 50 | echo # ===== GitHub Hosts Start ===== >> "{{.HostsFile}}" 51 | powershell -Command "& {(New-Object System.Net.WebClient).DownloadString('{{.HostsAPI}}')}" >> "{{.HostsFile}}" 52 | echo # ===== GitHub Hosts End ===== >> "{{.HostsFile}}" 53 | 54 | :: 刷新 DNS 缓存 55 | ipconfig /flushdns 56 | 57 | echo [%date% %time%] 更新完成 >> "{{.LogDir}}\update.log" 58 | ` 59 | } else { 60 | // Unix 系统脚本 61 | content = `#!/bin/bash 62 | LOG_FILE="{{.LogDir}}/update_$(date +%Y%m%d).log" 63 | TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') 64 | 65 | log() { 66 | echo "[$TIMESTAMP] $1" >> "$LOG_FILE" 67 | } 68 | 69 | log "开始更新 hosts 文件..." 70 | 71 | # 清理已存在的 GitHub Hosts 内容 72 | sed -i.bak '/# ===== GitHub Hosts Start =====/,/# ===== GitHub Hosts End =====/d' {{.HostsFile}} 73 | 74 | # 获取新的 hosts 内容 75 | echo "# ===== GitHub Hosts Start =====" >> {{.HostsFile}} 76 | curl -fsSL {{.HostsAPI}} >> {{.HostsFile}} 77 | echo "# ===== GitHub Hosts End =====" >> {{.HostsFile}} 78 | 79 | # 刷新 DNS 缓存 80 | if [ "$(uname)" == "Darwin" ]; then 81 | killall -HUP mDNSResponder 82 | log "已刷新 MacOS DNS 缓存" 83 | else 84 | if systemd-resolve --flush-caches > /dev/null 2>&1; then 85 | log "已刷新 Linux DNS 缓存" 86 | elif systemctl restart systemd-resolved > /dev/null 2>&1; then 87 | log "已重启 systemd-resolved 服务" 88 | fi 89 | fi 90 | 91 | log "更新完成" 92 | ` 93 | } 94 | 95 | // 准备模板数据 96 | data := struct { 97 | LogDir string 98 | HostsFile string 99 | HostsAPI string 100 | }{ 101 | LogDir: app.logDir, 102 | HostsFile: hostsFile, 103 | HostsAPI: hostsAPI, 104 | } 105 | 106 | // 解析并执行模板 107 | tmpl, err := template.New("script").Parse(content) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) 113 | if err != nil { 114 | return err 115 | } 116 | defer f.Close() 117 | 118 | return tmpl.Execute(f, data) 119 | } 120 | 121 | // setupWindowsCron 设置 Windows 计划任务 122 | func (app *App) setupWindowsCron(interval int, scriptPath string) error { 123 | // 删除已存在的任务 124 | exec.Command("schtasks", "/delete", "/tn", windowsTaskName, "/f").Run() 125 | 126 | // 创建新任务 127 | cmd := exec.Command("schtasks", "/create", "/tn", windowsTaskName, 128 | "/tr", scriptPath, 129 | "/sc", "minute", 130 | "/mo", fmt.Sprintf("%d", interval), 131 | "/ru", "SYSTEM", 132 | "/f") 133 | 134 | if output, err := cmd.CombinedOutput(); err != nil { 135 | return fmt.Errorf("创建计划任务失败: %s, %v", string(output), err) 136 | } 137 | 138 | return nil 139 | } 140 | 141 | // setupDarwinCron 设置 macOS 定时任务 142 | func (app *App) setupDarwinCron(interval int, scriptPath string) error { 143 | // 先尝试卸载已存在的服务 144 | exec.Command("launchctl", "bootout", "system/com.github.hosts").Run() 145 | // 删除旧的 plist 文件 146 | os.Remove(darwinPlistPath) 147 | 148 | content := fmt.Sprintf(` 149 | 150 | 151 | 152 | Label 153 | com.github.hosts 154 | ProgramArguments 155 | 156 | /bin/bash 157 | %s 158 | 159 | StartInterval 160 | %d 161 | RunAtLoad 162 | 163 | 164 | `, scriptPath, interval*60) 165 | 166 | // 写入新的 plist 文件 167 | if err := os.WriteFile(darwinPlistPath, []byte(content), 0644); err != nil { 168 | return fmt.Errorf("写入 plist 文件失败: %w", err) 169 | } 170 | 171 | // 加载新的服务 172 | cmd := exec.Command("launchctl", "bootstrap", "system", darwinPlistPath) 173 | if output, err := cmd.CombinedOutput(); err != nil { 174 | return fmt.Errorf("加载服务失败: %v, 输出: %s", err, string(output)) 175 | } 176 | 177 | return nil 178 | } 179 | 180 | // setupLinuxCron 设置 Linux 定时任务 181 | func (app *App) setupLinuxCron(interval int, scriptPath string) error { 182 | var schedule string 183 | switch interval { 184 | case 30: 185 | schedule = "*/30 * * * *" 186 | case 60: 187 | schedule = "0 * * * *" 188 | case 120: 189 | schedule = "0 */2 * * *" 190 | default: 191 | return fmt.Errorf("无效的时间间隔: %d", interval) 192 | } 193 | 194 | content := fmt.Sprintf("%s root %s > %s/update.log 2>&1\n", 195 | schedule, scriptPath, app.logDir) 196 | 197 | if err := os.WriteFile(linuxCronPath, []byte(content), 0644); err != nil { 198 | return fmt.Errorf("写入 cron 文件失败: %w", err) 199 | } 200 | 201 | // 重启 cron 服务 202 | if err := exec.Command("systemctl", "restart", "cron").Run(); err != nil { 203 | return fmt.Errorf("重启 cron 服务失败: %w", err) 204 | } 205 | 206 | return nil 207 | } 208 | -------------------------------------------------------------------------------- /scripts/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/TinsFox/github-hosts/scripts 2 | 3 | go 1.21 4 | -------------------------------------------------------------------------------- /scripts/install.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | func (app *App) installMenu() error { 15 | app.logWithLevel(INFO, "检查系统状态...") 16 | 17 | // 首先检查是否已存在 hosts 数据 18 | content, err := os.ReadFile(hostsFile) 19 | if err == nil && strings.Contains(string(content), "GitHub Hosts") { 20 | // 已存在 GitHub Hosts 数据,询问是否更新 21 | fmt.Print("\n检测到已存在 GitHub Hosts 数据,是否要更新?[Y/n]: ") 22 | var updateResponse string 23 | fmt.Scanf("%s", &updateResponse) 24 | 25 | if updateResponse == "n" || updateResponse == "N" { 26 | app.logWithLevel(INFO, "已取消更新操作") 27 | return nil 28 | } 29 | 30 | // 用户选择更新,直接执行更新操作 31 | if err := app.updateHosts(); err != nil { 32 | app.logWithLevel(ERROR, "更新 hosts 失败: %v", err) 33 | return fmt.Errorf("更新 hosts 失败: %w", err) 34 | } 35 | app.logWithLevel(SUCCESS, "hosts 文件更新完成") 36 | return nil 37 | } 38 | 39 | // 不存在 GitHub Hosts 数据,执行完整的安装流程 40 | app.logWithLevel(INFO, "开始安装配置向导...") 41 | 42 | // 1. 选择是否开启自动更新 43 | var autoUpdate bool = true // 默认开启 44 | fmt.Print("\n是否开启自动更新?[Y/n]: ") 45 | var response string 46 | fmt.Scanf("%s", &response) 47 | 48 | var interval int = 60 // 默认 60 分钟 49 | if response == "n" || response == "N" { 50 | autoUpdate = false 51 | app.logWithLevel(INFO, "已禁用自动更新") 52 | } else { 53 | app.logWithLevel(INFO, "已启用自动更新") 54 | 55 | fmt.Println("\n请选择更新间隔:") 56 | fmt.Println("1. 每 30 分钟") 57 | fmt.Println("2. 每 60 分钟") 58 | fmt.Println("3. 每 120 分钟") 59 | fmt.Print("请输入选项 (1-3): ") 60 | 61 | var choice int 62 | fmt.Scanf("%d", &choice) 63 | 64 | switch choice { 65 | case 1: 66 | interval = 30 67 | case 2: 68 | interval = 60 69 | case 3: 70 | interval = 120 71 | default: 72 | app.logWithLevel(ERROR, "无效的选项,将使用默认间隔(60分钟)") 73 | interval = 60 74 | } 75 | app.logWithLevel(INFO, "选择的更新间隔: %d 分钟", interval) 76 | } 77 | 78 | app.logWithLevel(INFO, "开始执行安装流程...") 79 | 80 | // 1. Setup directories 81 | app.logWithLevel(INFO, "第 1/4 步: 创建必要的目录结构") 82 | if err := app.setupDirectories(); err != nil { 83 | app.logWithLevel(ERROR, "创建目录失败: %v", err) 84 | return fmt.Errorf("创建目录失败: %w", err) 85 | } 86 | app.logWithLevel(SUCCESS, "目录创建完成") 87 | app.logWithLevel(INFO, " - 基础目录: %s", app.baseDir) 88 | app.logWithLevel(INFO, " - 配置文件: %s", app.configFile) 89 | app.logWithLevel(INFO, " - 备份目录: %s", app.backupDir) 90 | app.logWithLevel(INFO, " - 日志目录: %s", app.logDir) 91 | 92 | // 2. Update config 93 | app.logWithLevel(INFO, "第 2/4 步: 更新配置文件") 94 | if err := app.updateConfig(interval, autoUpdate); err != nil { 95 | app.logWithLevel(ERROR, "更新配置失败: %v", err) 96 | return fmt.Errorf("更新配置失败: %w", err) 97 | } 98 | app.logWithLevel(SUCCESS, "配置文件更新完成") 99 | 100 | // 3. Update hosts 101 | app.logWithLevel(INFO, "第 3/4 步: 更新 hosts 文件") 102 | if err := app.updateHosts(); err != nil { 103 | app.logWithLevel(ERROR, "更新 hosts 失败: %v", err) 104 | return fmt.Errorf("更新 hosts 失败: %w", err) 105 | } 106 | app.logWithLevel(SUCCESS, "hosts 文件更新完成") 107 | 108 | // 4. Setup cron 109 | if autoUpdate { 110 | app.logWithLevel(INFO, "第 4/4 步: 设置定时更新任务") 111 | if err := app.setupCron(interval); err != nil { 112 | app.logWithLevel(ERROR, "设置定时任务失败: %v", err) 113 | return fmt.Errorf("设置定时任务失败: %w", err) 114 | } 115 | app.logWithLevel(SUCCESS, "定时任务设置完成") 116 | } else { 117 | app.logWithLevel(INFO, "已跳过定时任务设置(自动更新已禁用)") 118 | } 119 | 120 | // 显示安装完成信息 121 | app.logWithLevel(SUCCESS, "安装完成!") 122 | app.logWithLevel(INFO, "系统配置信息:") 123 | if autoUpdate { 124 | app.logWithLevel(INFO, " • 更新间隔: 每 %d 分钟", interval) 125 | } 126 | app.logWithLevel(INFO, " • 自动更新: %s", map[bool]string{true: "已启用", false: "已禁用"}[autoUpdate]) 127 | app.logWithLevel(INFO, " • 配置文件: %s", app.configFile) 128 | app.logWithLevel(INFO, " • 日志文件: %s", filepath.Join(app.logDir, "update.log")) 129 | app.logWithLevel(INFO, " • 备份目录: %s", app.backupDir) 130 | 131 | // 显示当前 hosts 文件内容 132 | app.logWithLevel(INFO, "\n当前 hosts 文件内容:") 133 | fmt.Println("----------------------------------------") 134 | content, err = os.ReadFile(hostsFile) 135 | if err != nil { 136 | app.logWithLevel(ERROR, "读取 hosts 文件失败: %v", err) 137 | } else { 138 | fmt.Println(string(content)) 139 | } 140 | fmt.Println("----------------------------------------") 141 | 142 | // 自动执行网络连接测试 143 | app.logWithLevel(INFO, "\n开始测试网络连接...") 144 | if err := app.testConnection(); err != nil { 145 | app.logWithLevel(WARNING, "网络连接测试出现问题: %v", err) 146 | } 147 | 148 | return nil 149 | } 150 | 151 | func (app *App) setupDirectories() error { 152 | dirs := []string{app.baseDir, app.backupDir, app.logDir} 153 | for _, dir := range dirs { 154 | if err := os.MkdirAll(dir, 0755); err != nil { 155 | return err 156 | } 157 | } 158 | return nil 159 | } 160 | 161 | func (app *App) updateConfig(interval int, autoUpdate bool) error { 162 | config := Config{ 163 | UpdateInterval: interval, 164 | LastUpdate: time.Now().UTC(), 165 | Version: "1.0.0", 166 | AutoUpdate: autoUpdate, 167 | } 168 | 169 | data, err := json.MarshalIndent(config, "", " ") 170 | if err != nil { 171 | return err 172 | } 173 | 174 | return os.WriteFile(app.configFile, data, 0644) 175 | } 176 | 177 | func (app *App) updateHosts() error { 178 | app.logWithLevel(INFO, "开始备份当前 hosts 文件") 179 | if err := app.backupHosts(); err != nil { 180 | return fmt.Errorf("backup failed: %w", err) 181 | } 182 | app.logWithLevel(SUCCESS, "hosts 文件备份完成") 183 | 184 | // 先清理已存在的 GitHub Hosts 内容 185 | app.logWithLevel(INFO, "清理已存在的 GitHub Hosts 内容") 186 | if err := app.cleanHostsFile(); err != nil { 187 | return fmt.Errorf("清理已存在内容失败: %w", err) 188 | } 189 | app.logWithLevel(SUCCESS, "已清理旧的 hosts 内容") 190 | 191 | app.logWithLevel(INFO, "正在从服务器获取最新 hosts 数据") 192 | resp, err := http.Get(hostsAPI) 193 | if err != nil { 194 | return fmt.Errorf("failed to download hosts: %w", err) 195 | } 196 | defer resp.Body.Close() 197 | 198 | if resp.StatusCode != http.StatusOK { 199 | return fmt.Errorf("server returned status code: %d", resp.StatusCode) 200 | } 201 | 202 | content, err := io.ReadAll(resp.Body) 203 | if err != nil { 204 | return fmt.Errorf("failed to read response: %w", err) 205 | } 206 | app.logWithLevel(SUCCESS, "成功获取最新 hosts 数据") 207 | 208 | app.logWithLevel(INFO, "正在更新本地 hosts 文件") 209 | f, err := os.OpenFile(hostsFile, os.O_APPEND|os.O_WRONLY, 0644) 210 | if err != nil { 211 | return fmt.Errorf("failed to open hosts file: %w", err) 212 | } 213 | defer f.Close() 214 | 215 | // 添加开始标记和更新时间 216 | startMarker := fmt.Sprintf("\n# ===== GitHub Hosts Start ===== \n# (Updated: %s)\n", 217 | time.Now().Format("2006-01-02 15:04:05")) 218 | if _, err := f.WriteString(startMarker); err != nil { 219 | return fmt.Errorf("failed to write start marker: %w", err) 220 | } 221 | 222 | // 写入 hosts 内容 223 | if _, err := f.Write(content); err != nil { 224 | return fmt.Errorf("failed to write hosts content: %w", err) 225 | } 226 | 227 | // 添加结束标记 228 | endMarker := "# ===== GitHub Hosts End =====\n" 229 | if _, err := f.WriteString(endMarker); err != nil { 230 | return fmt.Errorf("failed to write end marker: %w", err) 231 | } 232 | 233 | app.logWithLevel(SUCCESS, "hosts 文件更新成功") 234 | 235 | app.logWithLevel(INFO, "正在刷新 DNS 缓存") 236 | if err := app.flushDNSCache(); err != nil { 237 | app.logWithLevel(WARNING, "DNS 缓存刷新失败: %v", err) 238 | } else { 239 | app.logWithLevel(SUCCESS, "DNS 缓存刷新完成") 240 | } 241 | 242 | return nil 243 | } 244 | -------------------------------------------------------------------------------- /scripts/log.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "time" 8 | ) 9 | 10 | // logWithLevel 输出带有级别的日志,并同时写入日志文件 11 | // writeToFile 参数控制是否写入日志文件,默认为 true 12 | func (app *App) logWithLevel(level LogLevel, format string, args ...interface{}) { 13 | app.logWithLevelOpt(level, true, format, args...) 14 | } 15 | 16 | // logWithLevelOpt 输出带有级别的日志,可选择是否写入日志文件 17 | func (app *App) logWithLevelOpt(level LogLevel, writeToFile bool, format string, args ...interface{}) { 18 | var prefix string 19 | switch level { 20 | case INFO: 21 | prefix = "ℹ️ " 22 | case SUCCESS: 23 | prefix = "✅ " 24 | case WARNING: 25 | prefix = "⚠️ " 26 | case ERROR: 27 | prefix = "❌ " 28 | } 29 | 30 | timestamp := time.Now().Format("2006-01-02 15:04:05") 31 | message := fmt.Sprintf(format, args...) 32 | logLine := fmt.Sprintf("%s[%s] %s\n", prefix, timestamp, message) 33 | 34 | // 输出到控制台 35 | fmt.Print(logLine) 36 | 37 | // 如果不需要写入文件,直接返回 38 | if !writeToFile { 39 | return 40 | } 41 | 42 | // 写入到日志文件 43 | logFile := filepath.Join(app.logDir, fmt.Sprintf("update_%s.log", time.Now().Format("20060102"))) 44 | 45 | // 确保日志目录存在 46 | if err := os.MkdirAll(app.logDir, 0755); err != nil { 47 | fmt.Printf("❌ 创建日志目录失败: %v\n", err) 48 | return 49 | } 50 | 51 | // 以追加模式打开日志文件 52 | f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 53 | if err != nil { 54 | fmt.Printf("❌ 打开日志文件失败: %v\n", err) 55 | return 56 | } 57 | defer f.Close() 58 | 59 | // 写入日志 60 | if _, err := f.WriteString(logLine); err != nil { 61 | fmt.Printf("❌ 写入日志失败: %v\n", err) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /scripts/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "runtime" 11 | "strings" 12 | ) 13 | 14 | // checkAndElevateSudo 检查权限并在需要时提权 15 | func checkAndElevateSudo() error { 16 | // Windows 系统使用不同的权限检查方式 17 | if runtime.GOOS == "windows" { 18 | // 检查是否以管理员权限运行 19 | isAdmin, err := isWindowsAdmin() 20 | if err != nil { 21 | return fmt.Errorf("检查 Windows 权限失败: %w", err) 22 | } 23 | 24 | if !isAdmin { 25 | fmt.Println("需要管理员权限来修改 hosts 文件") 26 | fmt.Println("请右键点击程序,选择'以管理员身份运行'") 27 | 28 | // 获取当前可执行文件的路径 29 | exe, err := os.Executable() 30 | if err != nil { 31 | return fmt.Errorf("获取程序路径失败: %w", err) 32 | } 33 | 34 | // 使用 runas 命令提权运行 35 | cmd := exec.Command("powershell", "Start-Process", exe, "-Verb", "RunAs") 36 | if err := cmd.Run(); err != nil { 37 | return fmt.Errorf("提权失败: %w", err) 38 | } 39 | 40 | // 退出当前的非管理员进程 41 | os.Exit(0) 42 | } 43 | return nil 44 | } 45 | 46 | // Unix 系统的权限检查 47 | if os.Geteuid() == 0 { 48 | return nil 49 | } 50 | 51 | // 检查命令是否以 sudo 运行 52 | sudoUID := os.Getenv("SUDO_UID") 53 | if sudoUID != "" { 54 | return nil 55 | } 56 | 57 | fmt.Println("需要管理员权限来修改 hosts 文件") 58 | fmt.Println("请输入 sudo 密码:") 59 | 60 | // 获取当前可执行文件的路径 61 | exe, err := os.Executable() 62 | if err != nil { 63 | return fmt.Errorf("获取程序路径失败: %w", err) 64 | } 65 | 66 | // 构建使用 sudo 运行的命令 67 | cmd := exec.Command("sudo", "-S", exe) 68 | 69 | // 将当前程序的标准输入输出连接到新进程 70 | cmd.Stdin = os.Stdin 71 | cmd.Stdout = os.Stdout 72 | cmd.Stderr = os.Stderr 73 | 74 | // 运行提权后的程序 75 | if err := cmd.Run(); err != nil { 76 | return fmt.Errorf("提权失败: %w", err) 77 | } 78 | 79 | // 退出当前的非 root 进程 80 | os.Exit(0) 81 | return nil 82 | } 83 | 84 | // isWindowsAdmin 检查当前进程是否具有管理员权限 85 | func isWindowsAdmin() (bool, error) { 86 | if runtime.GOOS != "windows" { 87 | return false, fmt.Errorf("不是 Windows 系统") 88 | } 89 | 90 | // 创建一个测试文件在系统目录 91 | testPath := filepath.Join(os.Getenv("windir"), ".test") 92 | err := os.WriteFile(testPath, []byte("test"), 0644) 93 | if err == nil { 94 | // 如果成功创建,则删除测试文件 95 | os.Remove(testPath) 96 | return true, nil 97 | } 98 | 99 | // 如果创建失败,检查是否是权限问题 100 | if os.IsPermission(err) { 101 | return false, nil 102 | } 103 | 104 | return false, err 105 | } 106 | 107 | // clearScreen 清空控制台 108 | func clearScreen() { 109 | switch runtime.GOOS { 110 | case "windows": 111 | cmd := exec.Command("cmd", "/c", "cls") 112 | cmd.Stdout = os.Stdout 113 | cmd.Run() 114 | default: // linux, darwin, etc 115 | cmd := exec.Command("clear") 116 | cmd.Stdout = os.Stdout 117 | cmd.Run() 118 | } 119 | } 120 | 121 | func main() { 122 | // 检查权限并在需要时提权 123 | if err := checkAndElevateSudo(); err != nil { 124 | fmt.Printf("错误: %v\n", err) 125 | fmt.Println("请使用管理员权限运行此程序") 126 | os.Exit(1) 127 | } 128 | 129 | clearScreen() // 启动时先清屏 130 | fmt.Println(banner) 131 | 132 | app, err := NewApp() 133 | if err != nil { 134 | log.Fatalf("初始化失败: %v", err) 135 | } 136 | 137 | for { 138 | // 显示安装状态 139 | installed, _ := app.checkInstallStatus() 140 | app.displayInstallStatus() 141 | 142 | fmt.Println("\n[基础功能]") 143 | fmt.Println("1. 安装/更新") 144 | if installed { 145 | fmt.Println("2. 卸载程序") 146 | fmt.Println("3. 查看 hosts 内容") 147 | 148 | fmt.Println("\n[自动更新]") 149 | // 动态显示自动更新选项 150 | config, err := app.loadConfig() 151 | if err == nil { 152 | if config.AutoUpdate { 153 | fmt.Println("4. 关闭自动更新") 154 | } else { 155 | fmt.Println("4. 开启自动更新") 156 | } 157 | } else { 158 | fmt.Println("4. 切换自动更新") 159 | } 160 | fmt.Println("5. 修改更新间隔") 161 | 162 | fmt.Println("\n[系统工具]") 163 | fmt.Println("6. 测试网络连接") 164 | fmt.Println("7. 检查系统状态") 165 | fmt.Println("8. 查看更新日志") 166 | fmt.Println("9. 打开配置目录") 167 | fmt.Println("10. 系统诊断") 168 | } 169 | 170 | fmt.Println("\n[系统]") 171 | fmt.Println("11. 打开 hosts 文件") 172 | 173 | fmt.Println("\n[关于]") 174 | fmt.Println("12. 🐙 访问项目主页") 175 | 176 | fmt.Println("\n0. 退出程序") 177 | fmt.Printf("\n请输入选项 (0-12 或 q 退出): ") 178 | 179 | // 读取用户输入 180 | var input string 181 | fmt.Scanln(&input) 182 | 183 | // 检查是否是退出命令 184 | if input == "q" || input == "Q" { 185 | fmt.Println("感谢使用,再见!") 186 | return 187 | } 188 | 189 | // 转换输入为数字 190 | var choice int 191 | _, err := fmt.Sscanf(input, "%d", &choice) 192 | if err != nil { 193 | fmt.Println("无效的选项,请重试") 194 | waitForEnter() 195 | continue 196 | } 197 | 198 | // 在未安装状态下限制某些选项的访问 199 | if !installed && (choice >= 2 && choice <= 10) { 200 | fmt.Println("\n❌ 请先安装程序才能使用该功能") 201 | waitForEnter() 202 | continue 203 | } 204 | 205 | switch choice { 206 | case 1: // 安装/更新 207 | if err := app.installMenu(); err != nil { 208 | log.Printf("安装失败: %v", err) 209 | } 210 | waitForEnter() 211 | case 2: // 卸载 212 | if !installed { 213 | continue 214 | } 215 | if err := app.uninstall(); err != nil { 216 | log.Printf("卸载失败: %v", err) 217 | } 218 | waitForEnter() 219 | case 3: // 查看 hosts 220 | if err := app.showHostsContent(); err != nil { 221 | log.Printf("查看 hosts 内容失败: %v", err) 222 | } 223 | waitForEnter() 224 | case 4: // 切换自动更新 225 | if err := app.toggleAutoUpdate(); err != nil { 226 | log.Printf("切换自动更新失败: %v", err) 227 | } 228 | waitForEnter() 229 | case 5: // 修改更新间隔 230 | if err := app.changeUpdateInterval(); err != nil { 231 | log.Printf("修改更新间隔失败: %v", err) 232 | } 233 | waitForEnter() 234 | case 6: // 测试网络连接 235 | if err := app.testConnection(); err != nil { 236 | log.Printf("网络测试失败: %v", err) 237 | } 238 | waitForEnter() 239 | case 7: // 检查系统状态 240 | if err := app.checkStatus(); err != nil { 241 | log.Printf("状态检查失败: %v", err) 242 | } 243 | waitForEnter() 244 | case 8: // 查看更新日志 245 | if err := app.showUpdateLogs(); err != nil { 246 | log.Printf("查看日志失败: %v", err) 247 | } 248 | waitForEnter() 249 | case 9: // 打开配置目录 250 | if err := app.openConfigDir(); err != nil { 251 | log.Printf("打开配置目录失败: %v", err) 252 | } 253 | waitForEnter() 254 | case 10: // 系统诊断 255 | if err := app.runDiagnostics(); err != nil { 256 | log.Printf("系统诊断失败: %v", err) 257 | } 258 | waitForEnter() 259 | case 11: // 打开 hosts 文件 260 | if err := app.openHostsFile(); err != nil { 261 | log.Printf("打开 hosts 文件失败: %v", err) 262 | } 263 | waitForEnter() 264 | case 12: // 访问项目主页 265 | if err := app.openGitHubRepo(); err != nil { 266 | log.Printf("打开项目主页失败: %v", err) 267 | } 268 | waitForEnter() 269 | case 0: // 退出 270 | fmt.Println("感谢使用,再见!") 271 | return 272 | default: 273 | fmt.Println("无效的选项,请重试") 274 | waitForEnter() 275 | } 276 | } 277 | } 278 | 279 | // NewApp 创建新的应用实例 280 | func NewApp() (*App, error) { 281 | homeDir, err := os.UserHomeDir() 282 | if err != nil { 283 | return nil, fmt.Errorf("failed to get home directory: %w", err) 284 | } 285 | 286 | baseDir := filepath.Join(homeDir, ".github-hosts") 287 | app := &App{ 288 | baseDir: baseDir, 289 | configFile: filepath.Join(baseDir, "config.json"), 290 | backupDir: filepath.Join(baseDir, "backups"), 291 | logDir: filepath.Join(baseDir, "logs"), 292 | logger: log.New(os.Stdout, "", log.LstdFlags), 293 | } 294 | 295 | return app, nil 296 | } 297 | 298 | // openGitHubRepo 打开项目主页 299 | func (app *App) openGitHubRepo() error { 300 | repoURL := "https://github.com/TinsFox/github-hosts" 301 | var cmd *exec.Cmd 302 | 303 | switch runtime.GOOS { 304 | case "darwin": 305 | cmd = exec.Command("open", repoURL) 306 | case "linux": 307 | cmd = exec.Command("xdg-open", repoURL) 308 | case "windows": 309 | cmd = exec.Command("cmd", "/c", "start", repoURL) 310 | default: 311 | return fmt.Errorf("不支持的操作系统: %s", runtime.GOOS) 312 | } 313 | 314 | if err := cmd.Run(); err != nil { 315 | return fmt.Errorf("打开浏览器失败: %w", err) 316 | } 317 | 318 | app.logWithLevel(SUCCESS, "已在浏览器中打开项目主页") 319 | return nil 320 | } 321 | 322 | // loadConfig 加载配置文件 323 | func (app *App) loadConfig() (*Config, error) { 324 | data, err := os.ReadFile(app.configFile) 325 | if err != nil { 326 | return nil, err 327 | } 328 | 329 | var config Config 330 | if err := json.Unmarshal(data, &config); err != nil { 331 | return nil, err 332 | } 333 | 334 | return &config, nil 335 | } 336 | 337 | // waitForEnter 等待用户按回车并重新显示界面 338 | func waitForEnter() { 339 | fmt.Print("\n按回车键继续...") 340 | fmt.Scanln() // 等待用户按下回车键 341 | clearScreen() // 清空控制台 342 | fmt.Println(banner) // 重新显示 banner 343 | } 344 | 345 | // checkInstallStatus 检查程序安装状态 346 | func (app *App) checkInstallStatus() (bool, *InstallStatus) { 347 | status := &InstallStatus{ 348 | IsInstalled: false, 349 | AutoUpdate: false, 350 | UpdateInterval: 0, 351 | LastUpdate: "", 352 | Version: "v1.0.0", // 当前程序版本 353 | } 354 | 355 | // 检查配置文件是否存在 356 | config, err := app.loadConfig() 357 | if err == nil && config != nil { 358 | status.IsInstalled = true 359 | status.AutoUpdate = config.AutoUpdate 360 | status.UpdateInterval = config.UpdateInterval 361 | 362 | // 获取最后更新时间 363 | if stat, err := os.Stat(app.configFile); err == nil { 364 | status.LastUpdate = stat.ModTime().Format("2006-01-02 15:04:05") 365 | } 366 | } 367 | 368 | return status.IsInstalled, status 369 | } 370 | 371 | // displayInstallStatus 显示安装状态 372 | func (app *App) displayInstallStatus() { 373 | installed, status := app.checkInstallStatus() 374 | 375 | fmt.Println("\n=== 系统状态 ===") 376 | if installed { 377 | fmt.Println("📦 安装状态: ✅ 已安装") 378 | fmt.Printf("🔄 自动更新: %s\n", formatBool(status.AutoUpdate)) 379 | if status.AutoUpdate { 380 | fmt.Printf("⏱️ 更新间隔: %d 小时\n", status.UpdateInterval) 381 | } 382 | fmt.Printf("🕒 上次更新: %s\n", status.LastUpdate) 383 | fmt.Printf("📌 程序版本: %s\n", status.Version) 384 | 385 | // 检查 hosts 文件中的 GitHub 记录数量 386 | count, _ := app.countGitHubHosts() 387 | fmt.Printf("📝 GitHub Hosts 记录数: %d\n", count) 388 | } else { 389 | fmt.Println("📦 安装状态: ❌ 未安装") 390 | fmt.Println("💡 提示: 请选择选项 1 进行安装") 391 | } 392 | fmt.Println(strings.Repeat("-", 30)) 393 | } 394 | 395 | // formatBool 格式化布尔值显示 396 | func formatBool(b bool) string { 397 | if b { 398 | return "✅ 已开启" 399 | } 400 | return "❌ 已关闭" 401 | } 402 | 403 | // countGitHubHosts 统计 hosts 文件中的 GitHub 相关记录数量 404 | func (app *App) countGitHubHosts() (int, error) { 405 | content, err := os.ReadFile(hostsFile) 406 | if err != nil { 407 | return 0, err 408 | } 409 | 410 | count := 0 411 | lines := strings.Split(string(content), "\n") 412 | for _, line := range lines { 413 | if strings.Contains(line, "github") || strings.Contains(line, "githubusercontent") { 414 | count++ 415 | } 416 | } 417 | return count, nil 418 | } 419 | 420 | // InstallStatus 安装状态结构体 421 | type InstallStatus struct { 422 | IsInstalled bool 423 | AutoUpdate bool 424 | UpdateInterval int 425 | LastUpdate string 426 | Version string 427 | } 428 | 429 | // openHostsFile 打开 hosts 文件 430 | func (app *App) openHostsFile() error { 431 | var cmd *exec.Cmd 432 | 433 | switch runtime.GOOS { 434 | case "darwin": 435 | // macOS 使用 open 命令 436 | cmd = exec.Command("open", hostsFile) 437 | case "linux": 438 | // Linux 使用 xdg-open 命令 439 | cmd = exec.Command("xdg-open", hostsFile) 440 | case "windows": 441 | // Windows 使用 notepad 打开 442 | cmd = exec.Command("notepad", hostsFile) 443 | default: 444 | return fmt.Errorf("不支持的操作系统: %s", runtime.GOOS) 445 | } 446 | 447 | if err := cmd.Run(); err != nil { 448 | return fmt.Errorf("打开 hosts 文件失败: %w", err) 449 | } 450 | 451 | app.logWithLevel(SUCCESS, "已打开 hosts 文件") 452 | return nil 453 | } 454 | -------------------------------------------------------------------------------- /scripts/menu.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "runtime" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | // showHostsContent 显示 hosts 文件内容 14 | func (app *App) showHostsContent() error { 15 | // 读取 hosts 文件内容 16 | content, err := os.ReadFile(hostsFile) 17 | if err != nil { 18 | return fmt.Errorf("读取 hosts 文件失败: %w", err) 19 | } 20 | 21 | // 显示完整内容 22 | fmt.Printf("\n当前 hosts 文件内容 (%s):\n", hostsFile) 23 | fmt.Println(strings.Repeat("-", 80)) 24 | fmt.Println(string(content)) 25 | fmt.Println(strings.Repeat("-", 80)) 26 | 27 | // 显示文件信息 28 | if info, err := os.Stat(hostsFile); err == nil { 29 | fmt.Printf("文件大小: %.2f KB\n", float64(info.Size())/1024) 30 | fmt.Printf("修改时间: %s\n", info.ModTime().Format("2006-01-02 15:04:05")) 31 | } 32 | 33 | return nil 34 | } 35 | 36 | // openConfigDir 打开配置目录 37 | func (app *App) openConfigDir() error { 38 | var cmd *exec.Cmd 39 | 40 | switch runtime.GOOS { 41 | case "darwin": 42 | cmd = exec.Command("open", app.baseDir) 43 | case "linux": 44 | cmd = exec.Command("xdg-open", app.baseDir) 45 | case "windows": 46 | cmd = exec.Command("explorer", app.baseDir) 47 | default: 48 | return fmt.Errorf("不支持的操作系统: %s", runtime.GOOS) 49 | } 50 | 51 | if err := cmd.Run(); err != nil { 52 | return fmt.Errorf("打开目录失败: %w", err) 53 | } 54 | 55 | return nil 56 | } 57 | 58 | // showUpdateLogs 显示更新日志 59 | func (app *App) showUpdateLogs() error { 60 | logFile := filepath.Join(app.logDir, fmt.Sprintf("update_%s.log", time.Now().Format("20060102"))) 61 | 62 | content, err := os.ReadFile(logFile) 63 | if err != nil { 64 | if os.IsNotExist(err) { 65 | app.logWithLevel(INFO, "今日暂无更新日志") 66 | return nil 67 | } 68 | return fmt.Errorf("读取日志文件失败: %w", err) 69 | } 70 | 71 | fmt.Println("\n最近的更新日志:") 72 | fmt.Println(strings.Repeat("-", 80)) 73 | fmt.Println(string(content)) 74 | fmt.Println(strings.Repeat("-", 80)) 75 | return nil 76 | } 77 | 78 | // checkStatus 检查系统状态 79 | func (app *App) checkStatus() error { 80 | app.logWithLevel(INFO, "开始检查系统状态...") 81 | 82 | // 1. 检查配置文件 83 | config, err := app.loadConfig() 84 | if err != nil { 85 | app.logWithLevel(ERROR, "配置文件检查失败: %v", err) 86 | } else { 87 | app.logWithLevel(INFO, "配置文件状态:") 88 | app.logWithLevel(INFO, " • 更新间隔: %d 分钟", config.UpdateInterval) 89 | app.logWithLevel(INFO, " • 自动更新: %s", map[bool]string{true: "已启用", false: "已禁用"}[config.AutoUpdate]) 90 | app.logWithLevel(INFO, " • 最后更新: %s", config.LastUpdate.Local().Format("2006-01-02 15:04:05")) 91 | app.logWithLevel(INFO, " • 版本: %s", config.Version) 92 | } 93 | 94 | // 2. 检查 hosts 文件 95 | if _, err := os.Stat(hostsFile); err != nil { 96 | app.logWithLevel(ERROR, "hosts 文件检查失败: %v", err) 97 | } else { 98 | content, err := os.ReadFile(hostsFile) 99 | if err != nil { 100 | app.logWithLevel(ERROR, "读取 hosts 文件失败: %v", err) 101 | } else { 102 | lines := strings.Split(string(content), "\n") 103 | githubCount := 0 104 | for _, line := range lines { 105 | if strings.Contains(strings.ToLower(line), "github") { 106 | githubCount++ 107 | } 108 | } 109 | app.logWithLevel(INFO, "hosts 文件状态:") 110 | app.logWithLevel(INFO, " • 文件大小: %.2f KB", float64(len(content))/1024) 111 | app.logWithLevel(INFO, " • GitHub 相关记录数: %d", githubCount) 112 | } 113 | } 114 | 115 | // 3. 检查定时任务状态 116 | app.logWithLevel(INFO, "定时任务状态:") 117 | switch runtime.GOOS { 118 | case "darwin": 119 | cmd := exec.Command("launchctl", "list", "com.github.hosts") 120 | if err := cmd.Run(); err == nil { 121 | app.logWithLevel(SUCCESS, " • 定时任务运行正常") 122 | } else { 123 | app.logWithLevel(WARNING, " • 定时任务未运行") 124 | } 125 | case "windows": 126 | cmd := exec.Command("schtasks", "/query", "/tn", windowsTaskName) 127 | if err := cmd.Run(); err == nil { 128 | app.logWithLevel(SUCCESS, " • 定时任务运行正常") 129 | } else { 130 | app.logWithLevel(WARNING, " • 定时任务未运行") 131 | } 132 | case "linux": 133 | if _, err := os.Stat(linuxCronPath); err == nil { 134 | app.logWithLevel(SUCCESS, " • 定时任务配置正常") 135 | } else { 136 | app.logWithLevel(WARNING, " • 定时任务配置不存在") 137 | } 138 | } 139 | 140 | // 4. 检查目录权限 141 | app.logWithLevel(INFO, "目录权限检查:") 142 | dirs := []string{app.baseDir, app.backupDir, app.logDir} 143 | for _, dir := range dirs { 144 | if err := app.checkDirPermissions(dir); err != nil { 145 | app.logWithLevel(WARNING, " • %s: %v", dir, err) 146 | } else { 147 | app.logWithLevel(SUCCESS, " • %s: 权限正常", dir) 148 | } 149 | } 150 | 151 | // 5. 检查备份状态 152 | if backups, err := app.listBackups(); err != nil { 153 | app.logWithLevel(ERROR, "备份检查失败: %v", err) 154 | } else { 155 | app.logWithLevel(INFO, "备份状态:") 156 | app.logWithLevel(INFO, " • 备份文件数量: %d", len(backups)) 157 | if len(backups) > 0 { 158 | app.logWithLevel(INFO, " • 最新备份: %s", backups[len(backups)-1]) 159 | } 160 | } 161 | 162 | return nil 163 | } 164 | -------------------------------------------------------------------------------- /scripts/network.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net" 7 | "net/http" 8 | "os" 9 | "os/exec" 10 | "runtime" 11 | "strings" 12 | "text/tabwriter" 13 | "time" 14 | ) 15 | 16 | // testConnection 测试网络连接 17 | func (app *App) testConnection() error { 18 | app.logWithLevel(INFO, "开始网络连接测试...") 19 | fmt.Println("\n=== 连接测试结果 ===") 20 | 21 | // 读取 hosts 文件 22 | content, err := os.ReadFile(hostsFile) 23 | if err != nil { 24 | fmt.Printf("\n❌ 严重错误:无法读取 hosts 文件\n") 25 | fmt.Printf("❌ 错误详情:%v\n", err) 26 | return err 27 | } 28 | 29 | // 解析 hosts 文件中的 GitHub 相关记录 30 | var tests []struct { 31 | name string 32 | ip string 33 | host string 34 | } 35 | 36 | startMarker := "# ===== GitHub Hosts Start =====" 37 | endMarker := "# ===== GitHub Hosts End =====" 38 | inGithubSection := false 39 | scanner := bufio.NewScanner(strings.NewReader(string(content))) 40 | 41 | for scanner.Scan() { 42 | line := strings.TrimSpace(scanner.Text()) 43 | 44 | if line == startMarker { 45 | inGithubSection = true 46 | continue 47 | } 48 | if line == endMarker { 49 | inGithubSection = false 50 | continue 51 | } 52 | 53 | if inGithubSection && line != "" && !strings.HasPrefix(line, "#") { 54 | fields := strings.Fields(line) 55 | if len(fields) >= 2 { 56 | tests = append(tests, struct { 57 | name string 58 | ip string 59 | host string 60 | }{ 61 | name: fields[1], 62 | ip: fields[0], 63 | host: fields[1], 64 | }) 65 | } 66 | } 67 | } 68 | 69 | if len(tests) == 0 { 70 | fmt.Printf("\n❌ 错误:在 hosts 文件中未找到 GitHub 相关记录\n") 71 | return fmt.Errorf("no github hosts found") 72 | } 73 | 74 | // 创建 HTTP 客户端 75 | client := &http.Client{ 76 | Timeout: 10 * time.Second, 77 | Transport: &http.Transport{ 78 | TLSHandshakeTimeout: 5 * time.Second, 79 | ResponseHeaderTimeout: 5 * time.Second, 80 | DisableKeepAlives: true, 81 | }, 82 | } 83 | 84 | // 使用 tabwriter 创建表格 85 | w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 86 | 87 | // 输出表头 88 | fmt.Fprintf(w, "\n%s\t%s\t%s\t%s\t%s\n", 89 | "域名", 90 | "状态", 91 | "响应时间", 92 | "当前解析IP", 93 | "期望IP") 94 | fmt.Fprintln(w, strings.Repeat("-", 100)) 95 | 96 | // 测试结果统计 97 | var ( 98 | successCount = 0 99 | failCount = 0 100 | ) 101 | 102 | // 测试每个域名 103 | for _, test := range tests { 104 | start := time.Now() 105 | 106 | // 获取实际 DNS 解析结果 107 | actualIP := "" 108 | addrs, err := net.LookupHost(test.host) 109 | if err != nil { 110 | fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", 111 | test.host, 112 | "✗ DNS失败", 113 | "-", 114 | "解析失败", 115 | test.ip) 116 | fmt.Printf("❌ DNS 解析失败: %v\n", err) 117 | failCount++ 118 | continue 119 | } 120 | if len(addrs) > 0 { 121 | actualIP = addrs[0] 122 | } 123 | 124 | // 测试连接 125 | resp, err := client.Get("https://" + test.host) 126 | elapsed := time.Since(start) 127 | 128 | if err != nil { 129 | fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", 130 | test.host, 131 | "✗ 连接失败", 132 | fmt.Sprintf("%.2fs", elapsed.Seconds()), 133 | actualIP, 134 | test.ip) 135 | fmt.Printf("❌ 连接失败: %v\n", err) 136 | failCount++ 137 | continue 138 | } 139 | defer resp.Body.Close() 140 | 141 | // 检查 IP 匹配和连接状态 142 | status := "✓ 正常" 143 | if actualIP != test.ip { 144 | status = "! IP不匹配" 145 | fmt.Printf("⚠️ %s 的 IP 不匹配!当前: %s, 期望: %s\n", test.host, actualIP, test.ip) 146 | failCount++ 147 | } else if resp.StatusCode != http.StatusOK { 148 | status = fmt.Sprintf("! 状态%d", resp.StatusCode) 149 | fmt.Printf("⚠️ %s 返回异常状态码: %d\n", test.host, resp.StatusCode) 150 | failCount++ 151 | } else { 152 | successCount++ 153 | } 154 | 155 | fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", 156 | test.host, 157 | status, 158 | fmt.Sprintf("%.2fs", elapsed.Seconds()), 159 | actualIP, 160 | test.ip) 161 | } 162 | 163 | fmt.Fprintln(w, strings.Repeat("-", 100)) 164 | w.Flush() 165 | 166 | // 输出总结 167 | fmt.Printf("\n测试总结:\n") 168 | fmt.Printf("总计测试: %d\n", len(tests)) 169 | if successCount > 0 { 170 | fmt.Printf("✅ 成功: %d\n", successCount) 171 | } 172 | if failCount > 0 { 173 | fmt.Printf("❌ 失败: %d\n", failCount) 174 | fmt.Printf("\n⚠️ 警告:检测到 %d 个问题,建议重新执行更新操作\n", failCount) 175 | } else { 176 | fmt.Printf("\n✅ 太好了!所有测试都通过了\n") 177 | } 178 | 179 | return nil 180 | } 181 | 182 | // flushDNSCache 刷新 DNS 缓存 183 | func (app *App) flushDNSCache() error { 184 | var cmd *exec.Cmd 185 | 186 | switch runtime.GOOS { 187 | case "darwin": 188 | cmd = exec.Command("killall", "-HUP", "mDNSResponder") 189 | case "linux": 190 | cmd = exec.Command("systemd-resolve", "--flush-caches") 191 | if err := cmd.Run(); err != nil { 192 | cmd = exec.Command("systemctl", "restart", "systemd-resolved") 193 | } 194 | case "windows": 195 | cmd = exec.Command("ipconfig", "/flushdns") 196 | default: 197 | return fmt.Errorf("不支持的操作系统: %s", runtime.GOOS) 198 | } 199 | 200 | return cmd.Run() 201 | } 202 | -------------------------------------------------------------------------------- /scripts/types.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | // App 应用程序结构体 10 | type App struct { 11 | baseDir string 12 | configFile string 13 | backupDir string 14 | logDir string 15 | logger *log.Logger 16 | } 17 | 18 | // Config 配置文件结构体 19 | type Config struct { 20 | UpdateInterval int `json:"updateInterval"` 21 | LastUpdate time.Time `json:"lastUpdate"` 22 | Version string `json:"version"` 23 | AutoUpdate bool `json:"autoUpdate"` 24 | } 25 | 26 | // LogLevel 定义日志级别 27 | type LogLevel int 28 | 29 | const ( 30 | INFO LogLevel = iota 31 | SUCCESS 32 | WARNING 33 | ERROR 34 | ) 35 | 36 | const ( 37 | MaxMenuOption = 12 // Maximum menu option number 38 | ) 39 | 40 | // displayOption 定义菜单选项 41 | type displayOption struct { 42 | id int 43 | name string 44 | description string 45 | handler func(*App) error 46 | } 47 | 48 | // 系统相关常量 49 | var ( 50 | // hostsAPI 定义 API 地址 51 | hostsAPI = "https://github-hosts.tinsfox.com/hosts" 52 | 53 | // hostsFile 根据操作系统定义 hosts 文件路径 54 | hostsFile = getHostsFilePath() 55 | 56 | // 定时任务相关路径 57 | windowsTaskName = "GitHubHostsUpdate" 58 | darwinPlistPath = "/Library/LaunchDaemons/com.github.hosts.plist" 59 | linuxCronPath = "/etc/cron.d/github-hosts" 60 | ) 61 | 62 | // getHostsFilePath 根据操作系统返回 hosts 文件路径 63 | func getHostsFilePath() string { 64 | if runtime.GOOS == "windows" { 65 | return "C:\\Windows\\System32\\drivers\\etc\\hosts" 66 | } 67 | return "/etc/hosts" 68 | } 69 | -------------------------------------------------------------------------------- /scripts/uninstall.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "runtime" 8 | "strings" 9 | ) 10 | 11 | func (app *App) uninstall() error { 12 | // 首先检查程序是否已安装 13 | installed, _ := app.checkInstallStatus() 14 | if !installed { 15 | app.logWithLevelOpt(ERROR, false, "程序尚未安装,无需卸载") 16 | return fmt.Errorf("程序未安装") 17 | } 18 | 19 | app.logWithLevelOpt(INFO, false, "准备卸载 GitHub Hosts 更新程序...") 20 | app.logWithLevelOpt(WARNING, false, "此操作将删除所有程序文件、配置和日志,且不可恢复") 21 | 22 | // 询问用户确认 23 | fmt.Print("确定要卸载吗?(y/N): ") 24 | var response string 25 | fmt.Scanln(&response) 26 | 27 | // 检查用户响应 28 | if response != "y" && response != "Y" { 29 | app.logWithLevelOpt(INFO, false, "已取消卸载") 30 | return nil 31 | } 32 | 33 | app.logWithLevelOpt(INFO, false, "开始卸载...") 34 | 35 | // 1. 清理 hosts 文件中的 GitHub 相关记录 36 | app.logWithLevelOpt(INFO, false, "正在清理 hosts 文件...") 37 | if err := app.cleanHostsFile(); err != nil { 38 | app.logWithLevelOpt(ERROR, false, "清理 hosts 文件失败: %v", err) 39 | return err 40 | } 41 | app.logWithLevelOpt(SUCCESS, false, "hosts 文件已清理") 42 | 43 | // 2. 移除定时任务 44 | app.logWithLevelOpt(INFO, false, "正在移除定时任务...") 45 | if runtime.GOOS == "darwin" { 46 | exec.Command("launchctl", "bootout", "system/com.github.hosts").Run() 47 | os.Remove(darwinPlistPath) 48 | } else if runtime.GOOS == "windows" { 49 | exec.Command("schtasks", "/delete", "/tn", windowsTaskName, "/f").Run() 50 | } else { 51 | os.Remove(linuxCronPath) 52 | } 53 | app.logWithLevelOpt(SUCCESS, false, "定时任务已移除") 54 | 55 | // 3. 删除程序文件和目录 56 | app.logWithLevelOpt(INFO, false, "正在删除程序文件...") 57 | 58 | // 获取用户主目录 59 | homeDir, err := os.UserHomeDir() 60 | if err != nil { 61 | app.logWithLevelOpt(ERROR, false, "获取用户主目录失败: %v", err) 62 | return err 63 | } 64 | 65 | // 需要删除的目录列表 66 | dirsToRemove := []string{ 67 | app.baseDir, // 主程序目录 68 | app.backupDir, // 备份目录 69 | app.logDir, // 日志目录 70 | homeDir + "/.github-hosts", // 配置目录 71 | homeDir + "/.github-hosts/backups", // 备份目录 72 | homeDir + "/.github-hosts/logs", // 日志目录 73 | } 74 | 75 | // 删除所有相关目录 76 | for _, dir := range dirsToRemove { 77 | if err := os.RemoveAll(dir); err != nil { 78 | app.logWithLevelOpt(WARNING, false, "删除目录失败: %s: %v", dir, err) 79 | } 80 | } 81 | 82 | // 4. 刷新 DNS 缓存 83 | app.logWithLevelOpt(INFO, false, "正在刷新 DNS 缓存...") 84 | if err := app.flushDNSCache(); err != nil { 85 | app.logWithLevelOpt(WARNING, false, "DNS 缓存刷新失败: %v", err) 86 | } 87 | 88 | app.logWithLevelOpt(SUCCESS, false, "卸载完成") 89 | app.logWithLevelOpt(INFO, false, "所有程序文件和配置已清理干净") 90 | return nil 91 | } 92 | 93 | // cleanHostsFile 清理 hosts 文件中的 GitHub 相关记录 94 | func (app *App) cleanHostsFile() error { 95 | // 读取 hosts 文件内容 96 | content, err := os.ReadFile(hostsFile) 97 | if err != nil { 98 | return fmt.Errorf("读取 hosts 文件失败: %w", err) 99 | } 100 | 101 | lines := strings.Split(string(content), "\n") 102 | var newLines []string 103 | var lastLineEmpty bool = true // 用于跟踪上一行是否为空 104 | 105 | // 逐行处理,移除 GitHub 相关记录和多余的空行 106 | for _, line := range lines { 107 | trimmedLine := strings.TrimSpace(line) 108 | 109 | // 跳过 GitHub 相关记录 110 | if strings.Contains(trimmedLine, "github") || strings.Contains(trimmedLine, "githubusercontent") { 111 | continue 112 | } 113 | 114 | // 处理空行:只有当上一行不是空行时才保留当前空行 115 | if trimmedLine == "" { 116 | if lastLineEmpty { 117 | continue // 跳过连续的空行 118 | } 119 | lastLineEmpty = true 120 | } else { 121 | lastLineEmpty = false 122 | } 123 | 124 | newLines = append(newLines, line) 125 | } 126 | 127 | // 确保文件末尾只有一个换行符 128 | for len(newLines) > 0 && strings.TrimSpace(newLines[len(newLines)-1]) == "" { 129 | newLines = newLines[:len(newLines)-1] 130 | } 131 | newLines = append(newLines, "") // 添加一个空行作为文件结尾 132 | 133 | // 写回文件 134 | newContent := strings.Join(newLines, "\n") 135 | if err := os.WriteFile(hostsFile, []byte(newContent), 0644); err != nil { 136 | return fmt.Errorf("写入 hosts 文件失败: %w", err) 137 | } 138 | 139 | return nil 140 | } 141 | -------------------------------------------------------------------------------- /scripts/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | ) 9 | 10 | // checkDirPermissions 检查目录权限 11 | func (app *App) checkDirPermissions(dir string) error { 12 | // 检查目录是否存在 13 | if _, err := os.Stat(dir); os.IsNotExist(err) { 14 | return fmt.Errorf("目录不存在: %s", dir) 15 | } 16 | 17 | // 检查是否可写 18 | testFile := filepath.Join(dir, ".test") 19 | if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil { 20 | return fmt.Errorf("目录不可写: %s", dir) 21 | } 22 | os.Remove(testFile) 23 | 24 | return nil 25 | } 26 | 27 | // runDiagnostics 运行系统诊断 28 | func (app *App) runDiagnostics() error { 29 | app.logWithLevel(INFO, "开始系统诊断...") 30 | 31 | // 1. 检查系统信息 32 | app.logWithLevel(INFO, "系统信息:") 33 | app.logWithLevel(INFO, " • 操作系统: %s", runtime.GOOS) 34 | app.logWithLevel(INFO, " • 架构: %s", runtime.GOARCH) 35 | 36 | // 2. 检查目录权限 37 | app.logWithLevel(INFO, "检查目录权限...") 38 | dirs := []string{app.baseDir, app.backupDir, app.logDir} 39 | for _, dir := range dirs { 40 | if err := app.checkDirPermissions(dir); err != nil { 41 | app.logWithLevel(WARNING, "目录权限问题: %v", err) 42 | } else { 43 | app.logWithLevel(SUCCESS, "目录权限正常: %s", dir) 44 | } 45 | } 46 | 47 | // 3. 检查网络连接 48 | app.logWithLevel(INFO, "检查网络连接...") 49 | if err := app.testConnection(); err != nil { 50 | app.logWithLevel(WARNING, "网络连接问题: %v", err) 51 | } 52 | 53 | // 4. 检查配置文件 54 | app.logWithLevel(INFO, "检查配置文件...") 55 | if config, err := app.loadConfig(); err != nil { 56 | app.logWithLevel(WARNING, "配置文件问题: %v", err) 57 | } else { 58 | app.logWithLevel(SUCCESS, "配置文件正常") 59 | app.logWithLevel(INFO, " • 版本: %s", config.Version) 60 | app.logWithLevel(INFO, " • 上次更新: %s", config.LastUpdate.Local().Format("2006-01-02 15:04:05")) 61 | } 62 | 63 | app.logWithLevel(SUCCESS, "诊断完成") 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const GITHUB_URLS = [ 2 | "alive.github.com", 3 | "api.github.com", 4 | "assets-cdn.github.com", 5 | "avatars.githubusercontent.com", 6 | "avatars0.githubusercontent.com", 7 | "avatars1.githubusercontent.com", 8 | "avatars2.githubusercontent.com", 9 | "avatars3.githubusercontent.com", 10 | "avatars4.githubusercontent.com", 11 | "avatars5.githubusercontent.com", 12 | "camo.githubusercontent.com", 13 | "central.github.com", 14 | "cloud.githubusercontent.com", 15 | "codeload.github.com", 16 | "collector.github.com", 17 | "desktop.githubusercontent.com", 18 | "favicons.githubusercontent.com", 19 | "gist.github.com", 20 | "github-cloud.s3.amazonaws.com", 21 | "github-com.s3.amazonaws.com", 22 | "github-production-release-asset-2e65be.s3.amazonaws.com", 23 | "github-production-repository-file-5c1aeb.s3.amazonaws.com", 24 | "github-production-user-asset-6210df.s3.amazonaws.com", 25 | "github.blog", 26 | "github.com", 27 | "github.community", 28 | "github.githubassets.com", 29 | "github.global.ssl.fastly.net", 30 | "github.io", 31 | "github.map.fastly.net", 32 | "githubstatus.com", 33 | "live.github.com", 34 | "media.githubusercontent.com", 35 | "objects.githubusercontent.com", 36 | "pipelines.actions.githubusercontent.com", 37 | "raw.githubusercontent.com", 38 | "user-images.githubusercontent.com", 39 | "vscode.dev", 40 | "education.github.com", 41 | "private-user-images.githubusercontent.com", 42 | ] 43 | 44 | export const HOSTS_TEMPLATE = `# github hosts 45 | # 加速 GitHub 访问 46 | 47 | {content} 48 | 49 | # 数据更新时间:{updateTime} 50 | ` 51 | 52 | export const GITHUB_API_BASE = "https://api.github.com" 53 | 54 | export const HOSTS_PATH = "hosts" 55 | 56 | export const DNS_PROVIDERS = [ 57 | { 58 | url: (domain: string) => `https://1.1.1.1/dns-query?name=${domain}&type=A`, 59 | headers: { Accept: "application/dns-json" }, 60 | name: "Cloudflare DNS", 61 | }, 62 | { 63 | url: (domain: string) => `https://dns.google/resolve?name=${domain}&type=A`, 64 | headers: { Accept: "application/dns-json" }, 65 | name: "Google DNS", 66 | }, 67 | ] 68 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare module "cloudflare:test" { 2 | interface ProvidedEnv { 3 | KV_NAMESPACE: KVNamespace 4 | } 5 | // ...or if you have an existing `Env` type... 6 | interface ProvidedEnv extends Env {} 7 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from "hono" 2 | import { 3 | formatHostsFile, 4 | getDomainData, 5 | getHostsData, 6 | resetHostsData, 7 | } from "./services/hosts" 8 | import { handleSchedule } from "./scheduled" 9 | import { Bindings } from "./types" 10 | 11 | const app = new Hono<{ Bindings: Bindings }>() 12 | 13 | app.get("/", async (c) => { 14 | const html = await c.env.ASSETS.get("index.html") 15 | if (!html) { 16 | return c.text("Template not found", 404) 17 | } 18 | 19 | return c.html(html) 20 | }) 21 | 22 | app.get("/hosts.json", async (c) => { 23 | const data = await getHostsData(c.env) 24 | return c.json(data) 25 | }) 26 | 27 | app.get("/hosts", async (c) => { 28 | const data = await getHostsData(c.env) 29 | const hostsContent = formatHostsFile(data) 30 | return c.text(hostsContent) 31 | }) 32 | 33 | app.post("/reset", async (c) => { 34 | const apiKey = c.req.query("key") 35 | 36 | // 验证 API key 37 | if (apiKey !== c.env.API_KEY) { 38 | return c.json({ error: "Unauthorized" }, 401) 39 | } 40 | 41 | const newEntries = await resetHostsData(c.env) 42 | 43 | return c.json({ 44 | message: "Reset completed", 45 | entriesCount: newEntries.length, 46 | entries: newEntries, 47 | }) 48 | }) 49 | 50 | app.get("/:domain", async (c) => { 51 | const domain = c.req.param("domain") 52 | const data = await getDomainData(c.env, domain) 53 | 54 | if (!data) { 55 | return c.json({ error: "Domain not found" }, 404) 56 | } 57 | 58 | return c.json(data) 59 | }) 60 | 61 | export default { 62 | fetch: app.fetch, 63 | async scheduled(event: ScheduledEvent, env: Bindings, ctx: ExecutionContext) { 64 | ctx.waitUntil(handleSchedule(event, env)) 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /src/scheduled.ts: -------------------------------------------------------------------------------- 1 | import { Bindings } from "./types" 2 | import { fetchLatestHostsData, storeData } from "./services/hosts" 3 | 4 | export async function handleSchedule( 5 | event: ScheduledEvent, 6 | env: Bindings 7 | ): Promise { 8 | console.log("Running scheduled task...") 9 | 10 | try { 11 | const newEntries = await fetchLatestHostsData() 12 | await storeData(env, newEntries) 13 | 14 | console.log("Scheduled task completed successfully") 15 | } catch (error) { 16 | console.error("Error in scheduled task:", error) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/services/__tests__/hosts.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach } from "vitest" 2 | import { fetchIPFromIPAddress } from "../hosts" 3 | 4 | describe("fetchIpFromIpaddress", () => { 5 | beforeEach(() => { 6 | // 清除所有模拟 7 | vi.clearAllMocks() 8 | }) 9 | 10 | it("should successfully extract IP from DNS section", async () => { 11 | // 模拟 fetch 响应 12 | global.fetch = vi.fn().mockResolvedValue({ 13 | text: () => 14 | Promise.resolve(` 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 |
140.82.114.25
23 |
24 | 25 | 26 | `), 27 | }) 28 | 29 | const result = await fetchIPFromIPAddress("github.com") 30 | expect(result).toBe("140.82.114.25") 31 | expect(fetch).toHaveBeenCalledWith( 32 | "https://sites.ipaddress.com/github.com", 33 | expect.any(Object) 34 | ) 35 | }) 36 | 37 | it("should return null when DNS section is not found", async () => { 38 | global.fetch = vi.fn().mockResolvedValue({ 39 | text: () => 40 | Promise.resolve(` 41 | 42 | 43 |
No DNS section here
44 | 45 | 46 | `), 47 | }) 48 | 49 | const result = await fetchIPFromIPAddress("invalid-domain.com") 50 | expect(result).toBeNull() 51 | }) 52 | 53 | it("should handle fetch errors gracefully", async () => { 54 | global.fetch = vi.fn().mockRejectedValue(new Error("Network error")) 55 | 56 | const result = await fetchIPFromIPAddress("github.com") 57 | expect(result).toBeNull() 58 | }) 59 | 60 | it("should use fallback IP when DNS section IP is not available", async () => { 61 | global.fetch = vi.fn().mockResolvedValue({ 62 | text: () => 63 | Promise.resolve(` 64 | 65 | 66 |
67 |
IP Address: 192.168.1.1
68 | 69 | 70 | `), 71 | }) 72 | 73 | const result = await fetchIPFromIPAddress("github.com") 74 | expect(result).toBe("192.168.1.1") 75 | }) 76 | 77 | it("should handle multiple IPs in DNS section", async () => { 78 | global.fetch = vi.fn().mockResolvedValue({ 79 | text: () => 80 | Promise.resolve(` 81 | 82 | 83 |
84 | 85 | 86 | 87 | 88 | 89 |
140.82.114.4140.82.114.5
90 |
91 | 92 | 93 | `), 94 | }) 95 | 96 | const result = await fetchIPFromIPAddress("github.com") 97 | expect(result).toBe("140.82.114.4") // 应该返回第一个找到的 IP 98 | }) 99 | }) 100 | -------------------------------------------------------------------------------- /src/services/hosts.ts: -------------------------------------------------------------------------------- 1 | import { DNS_PROVIDERS, GITHUB_URLS, HOSTS_TEMPLATE } from "../constants" 2 | import { Bindings } from "../types" 3 | 4 | export type HostEntry = [string, string] 5 | 6 | interface DomainData { 7 | ip: string 8 | lastUpdated: string 9 | lastChecked: string 10 | } 11 | 12 | interface DomainDataList { 13 | [key: string]: DomainData 14 | } 15 | interface KVData { 16 | domain_data: DomainDataList 17 | lastUpdated: string 18 | } 19 | 20 | interface DnsQuestion { 21 | name: string 22 | type: number 23 | } 24 | 25 | interface DnsAnswer { 26 | name: string 27 | type: number 28 | TTL: number 29 | data: string 30 | } 31 | 32 | interface DnsResponse { 33 | Status: number 34 | TC: boolean 35 | RD: boolean 36 | RA: boolean 37 | AD: boolean 38 | CD: boolean 39 | Question: DnsQuestion[] 40 | Answer: DnsAnswer[] 41 | } 42 | 43 | async function retry( 44 | fn: () => Promise, 45 | retries: number = 3, 46 | delay: number = 1000 47 | ): Promise { 48 | try { 49 | return await fn() 50 | } catch (error) { 51 | if (retries === 0) throw error 52 | await new Promise((resolve) => setTimeout(resolve, delay)) 53 | return retry(fn, retries - 1, delay * 2) 54 | } 55 | } 56 | 57 | export async function fetchIPFromIPAddress( 58 | domain: string, 59 | providerName?: string 60 | ): Promise { 61 | const provider = 62 | DNS_PROVIDERS.find((p) => p.name === providerName) || DNS_PROVIDERS[0] 63 | 64 | try { 65 | const response = await retry(() => 66 | fetch(provider.url(domain), { headers: provider.headers }) 67 | ) 68 | 69 | if (!response.ok) return null 70 | 71 | const data = (await response.json()) as DnsResponse 72 | 73 | // 查找类型为 1 (A记录) 的答案 74 | const aRecord = data.Answer?.find((answer) => answer.type === 1) 75 | const ip = aRecord?.data 76 | 77 | if (ip && /^\d+\.\d+\.\d+\.\d+$/.test(ip)) { 78 | return ip 79 | } 80 | } catch (error) { 81 | console.error(`Error with DNS provider:`, error) 82 | } 83 | 84 | return null 85 | } 86 | 87 | export async function fetchLatestHostsData(): Promise { 88 | const entries: HostEntry[] = [] 89 | const batchSize = 5 90 | 91 | for (let i = 0; i < GITHUB_URLS.length; i += batchSize) { 92 | console.log( 93 | `Processing batch ${i / batchSize + 1}/${Math.ceil( 94 | GITHUB_URLS.length / batchSize 95 | )}` 96 | ) 97 | 98 | const batch = GITHUB_URLS.slice(i, i + batchSize) 99 | const batchResults = await Promise.all( 100 | batch.map(async (domain) => { 101 | const ip = await fetchIPFromIPAddress(domain) 102 | console.log(`Domain: ${domain}, IP: ${ip}`) 103 | return ip ? ([ip, domain] as HostEntry) : null 104 | }) 105 | ) 106 | 107 | entries.push( 108 | ...batchResults.filter((result): result is HostEntry => result !== null) 109 | ) 110 | 111 | if (i + batchSize < GITHUB_URLS.length) { 112 | await new Promise((resolve) => setTimeout(resolve, 2000)) 113 | } 114 | } 115 | 116 | console.log(`Total entries found: ${entries.length}`) 117 | return entries 118 | } 119 | export async function storeData( 120 | env: Bindings, 121 | data: HostEntry[] 122 | ): Promise { 123 | 124 | await updateHostsData(env, data) 125 | } 126 | export async function getHostsData(env: Bindings): Promise { 127 | const kvData = (await env.github_hosts.get("domain_data", { 128 | type: "json", 129 | })) as KVData | null 130 | 131 | // 如果数据不存在,或者最后更新时间超过1小时,获取新数据 132 | if ( 133 | !kvData?.lastUpdated || 134 | new Date(kvData.lastUpdated).getTime() + 1000 * 60 * 60 < Date.now() || 135 | Object.keys(kvData.domain_data || {}).length === 0 136 | ) { 137 | const newEntries = await fetchLatestHostsData() 138 | await storeData(env, newEntries) 139 | return newEntries 140 | } 141 | 142 | try { 143 | // 从 KV 获取所有域名的数据 144 | const entries: HostEntry[] = [] 145 | for (const domain of GITHUB_URLS) { 146 | const domainData = kvData.domain_data[domain] 147 | if (domainData) { 148 | entries.push([domainData.ip, domain]) 149 | } 150 | } 151 | return entries 152 | } catch (error) { 153 | console.error("Error getting hosts data:", error) 154 | return [] 155 | } 156 | } 157 | 158 | export async function updateHostsData( 159 | env: Bindings, 160 | newEntries?: HostEntry[] 161 | ): Promise { 162 | try { 163 | const currentTime = new Date().toISOString() 164 | const kvData = (await env.github_hosts.get("domain_data", { 165 | type: "json", 166 | })) as KVData | null || { domain_data: {}, lastUpdated: currentTime } 167 | 168 | if (!newEntries) { 169 | // 只更新检查时间 170 | for (const domain in kvData.domain_data) { 171 | kvData.domain_data[domain] = { 172 | ...kvData.domain_data[domain], 173 | lastChecked: currentTime, 174 | } 175 | } 176 | } else { 177 | // 更新域名数据 178 | for (const [ip, domain] of newEntries) { 179 | const oldData = kvData.domain_data[domain] 180 | const hasChanged = !oldData || oldData.ip !== ip 181 | 182 | kvData.domain_data[domain] = { 183 | ip, 184 | lastUpdated: hasChanged ? currentTime : oldData?.lastUpdated || currentTime, 185 | lastChecked: currentTime, 186 | } 187 | } 188 | } 189 | 190 | kvData.lastUpdated = currentTime 191 | await env.github_hosts.put("domain_data", JSON.stringify(kvData)) 192 | } catch (error) { 193 | console.error("Error updating hosts data:", error) 194 | } 195 | } 196 | 197 | export function formatHostsFile(entries: HostEntry[]): string { 198 | const content = entries 199 | .map(([ip, domain]) => `${ip.padEnd(30)}${domain}`) 200 | .join("\n") 201 | 202 | const updateTime = new Date().toLocaleString("en-US", { 203 | timeZone: "Asia/Shanghai", 204 | hour12: false, 205 | }) 206 | 207 | return HOSTS_TEMPLATE.replace("{content}", content).replace( 208 | "{updateTime}", 209 | updateTime 210 | ) 211 | } 212 | 213 | // 修改:获取单个域名数据的方法,直接从爬虫获取实时数据 214 | export async function getDomainData( 215 | env: Bindings, 216 | domain: string 217 | ): Promise { 218 | try { 219 | const ip = await fetchIPFromIPAddress(domain) 220 | if (!ip) { 221 | return null 222 | } 223 | 224 | const currentTime = new Date().toISOString() 225 | const kvData = (await env.github_hosts.get("domain_data", { 226 | type: "json", 227 | })) as KVData | null || { domain_data: {}, lastUpdated: currentTime } 228 | 229 | const newData: DomainData = { 230 | ip, 231 | lastUpdated: currentTime, 232 | lastChecked: currentTime, 233 | } 234 | 235 | kvData.domain_data[domain] = newData 236 | kvData.lastUpdated = currentTime 237 | await env.github_hosts.put("domain_data", JSON.stringify(kvData)) 238 | 239 | return newData 240 | } catch (error) { 241 | console.error(`Error getting data for domain ${domain}:`, error) 242 | return null 243 | } 244 | } 245 | 246 | // 修改:清空 KV 并重新获取所有数据 247 | export async function resetHostsData(env: Bindings): Promise { 248 | try { 249 | console.log("Clearing KV data...") 250 | await env.github_hosts.delete("domain_data") 251 | console.log("KV data cleared") 252 | 253 | console.log("Fetching new data...") 254 | const newEntries = await fetchLatestHostsData() 255 | console.log("New entries fetched:", newEntries) 256 | 257 | await updateHostsData(env, newEntries) 258 | console.log("New data stored in KV") 259 | 260 | return newEntries 261 | } catch (error) { 262 | console.error("Error resetting hosts data:", error) 263 | return [] 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Bindings { 2 | HOSTS_STORE: KVNamespace 3 | API_KEY: string 4 | ASSETS: { get(key: string): Promise } 5 | github_hosts: KVNamespace 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "strict": true, 7 | "skipLibCheck": true, 8 | "lib": ["ESNext"], 9 | "types": [ 10 | "@cloudflare/workers-types/2023-07-01", 11 | "@cloudflare/vitest-pool-workers" 12 | ], 13 | "jsx": "react-jsx", 14 | "jsxImportSource": "hono/jsx" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config" 2 | 3 | export default defineWorkersConfig({ 4 | test: { 5 | environment: "happy-dom", 6 | coverage: { 7 | provider: "v8", 8 | reporter: ["text", "json", "html"], 9 | }, 10 | poolOptions: { 11 | workers: { 12 | wrangler: { configPath: "./wrangler.toml" }, 13 | }, 14 | }, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "github-hosts" 2 | main = "src/index.ts" 3 | compatibility_date = "2024-10-28" 4 | compatibility_flags = ["nodejs_compat"] 5 | assets = { directory = "public" } 6 | 7 | # [vars] 8 | # API_KEY = "" 9 | 10 | [[kv_namespaces]] 11 | binding = "github_hosts" 12 | id = "b47d7f8c9df14032b4fd6c65b2f81e63" 13 | 14 | 15 | # [[r2_buckets]] 16 | # binding = "MY_BUCKET" 17 | # bucket_name = "my-bucket" 18 | 19 | # [[d1_databases]] 20 | # binding = "DB" 21 | # database_name = "my-database" 22 | # database_id = "" 23 | 24 | # [ai] 25 | # binding = "AI" 26 | 27 | [observability] 28 | enabled = true 29 | head_sampling_rate = 1 30 | 31 | [triggers] 32 | crons = ["0 */1 * * *"] 33 | --------------------------------------------------------------------------------