├── .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 |

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 | [](https://deploy.workers.cloudflare.com/?url=https://github.com/TinsFox/github-hosts)
95 |
96 | ## 鸣谢
97 |
98 | - [GitHub520](https://github.com/521xueweihan/GitHub520)
99 | - [](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 |
52 |
53 | 📝 项目介绍
54 | GitHub 访问加速,解决 GitHub 访问慢的问题。使用 Cloudflare Workers 和公共 DNS API 来获取 IP 地址。
55 | 感谢 GitHub520 提供的灵感。
56 |
57 | 🚀 特点
58 |
59 | - 使用 Cloudflare Workers 部署,无需服务器
60 | - 使用 Cloudflare DNS (1.1.1.1) 和 Google DNS 进行域名解析
61 | - 使用 Cloudflare KV 存储数据,确保高可用性
62 | - 提供 REST API 接口
63 | - 每 60 分钟自动更新一次 DNS 记录
64 |
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 | - 下载并安装 SwitchHosts
88 | - 添加规则:
89 |
90 | - 方案名:GitHub Hosts
91 | - 类型:远程
92 | - URL:https://github-hosts.tinsfox.com/hosts
93 | - 自动更新:1 小时
94 |
95 |
96 |
97 |
98 | 当前 hosts 内容
99 |
100 |
101 |
正在加载 hosts 内容...
102 |
103 |
104 | 3. 手动更新
105 |
106 | - 获取 hosts:访问 https://github-hosts.tinsfox.com/hosts
107 | - 更新本地 hosts 文件:
108 |
109 | - Windows:
C:\Windows\System32\drivers\etc\hosts
110 | - MacOS/Linux:
/etc/hosts
111 |
112 |
113 | - 刷新 DNS:
114 |
115 | - Windows:
ipconfig /flushdns
116 | - MacOS:
sudo killall -HUP mDNSResponder
117 | - Linux:
sudo systemd-resolve --flush-caches
118 |
119 |
120 |
121 |
122 | ❓ 常见问题
123 | 权限问题
124 |
125 | - Windows:需要以管理员身份运行
126 | - MacOS/Linux:需要 sudo 权限
127 |
128 |
129 | 定时任务未生效
130 |
131 | - Windows:检查任务计划程序中的 "GitHub Hosts Updater"
132 | - MacOS/Linux:使用
crontab -l
检查
133 |
134 |
135 | 更新失败
136 |
137 | - 检查日志:
~/.github-hosts/logs/update.log
138 | - 确保网络连接和文件权限正常
139 |
140 |
141 | 🔧 API 接口文档
142 |
143 |
144 | 接口 |
145 | 方法 |
146 | 描述 |
147 |
148 |
149 | /hosts |
150 | GET |
151 | 获取 hosts 文件内容 |
152 |
153 |
154 | /hosts.json |
155 | GET |
156 | 获取 JSON 格式的数据 |
157 |
158 |
159 | /{domain} |
160 | GET |
161 | 获取指定域名的实时 DNS 解析结果 |
162 |
163 |
164 | /reset |
165 | POST |
166 | 清空缓存并重新获取所有数据(需要 API Key) |
167 |
168 |
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 |
--------------------------------------------------------------------------------
/public/og.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 | 140.82.114.25 |
21 |
22 |
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 | 140.82.114.4 |
87 | 140.82.114.5 |
88 |
89 |
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 |
--------------------------------------------------------------------------------