diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..47d2d28 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,53 @@ +name: Deploy + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + deployments: write + +concurrency: + group: pages-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'production' }} + cancel-in-progress: true + +jobs: + deploy: + name: "Deploy ${{ github.event_name == 'pull_request' && format('PR #{0}', github.event.pull_request.number) || 'production' }}" + runs-on: ubuntu-latest + + steps: + - name: Setup JS + uses: sxzz/workflows/setup-js@main + + - name: Restore build data + id: build-data-cache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: | + data/raw + public/fonts/*.woff2 + public/fonts/fonts-*.css + key: build-data-${{ runner.os }}-${{ hashFiles('data/sources.lock.json', 'package.json', 'pnpm-lock.yaml', 'scripts/*.ts', 'shared/*.ts', 'app/locales/*.ts', 'app/generated/*.ts', 'public/data/*.json') }} + restore-keys: build-data-${{ runner.os }}- + + - name: Build data + if: steps.build-data-cache.outputs.cache-hit != 'true' + run: pnpm build:data + + - name: Generate site + run: pnpm generate + + - name: Deploy to Cloudflare Pages + uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + gitHubToken: ${{ secrets.GITHUB_TOKEN }} + command: >- + pages deploy .output/public + --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }} + --branch=${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'main' }} diff --git a/.gitignore b/.gitignore index 5e29507..d4671a1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ dist .DS_Store .eslintcache -# Cached downloads of the external sources; pnpm build:data refetches them +# Cached downloads of external sources, verified against data/sources.lock.json data/raw/ # Generated by pnpm build:data: 12MB of font binaries and the @font-face rules diff --git a/README.md b/README.md index 500ddf2..b301e9f 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Source Han Sans 与 Source Han Serif 各自的五个地区共享同一个字形 ```bash pnpm install pnpm build:data # 生成字表与字体子集,首次会下载约 211MB 原始数据,之后走缓存 +pnpm update:sources # 检查并锁定新版第三方数据;有变化时下载并重新生成 pnpm dev pnpm test pnpm generate # 静态站点 @@ -70,21 +71,28 @@ pnpm generate # 静态站点 ## 部署 -静态站点,`.output/public` 直接丢给任意静态托管即可。Cloudflare Pages 的设置: +静态站点,`.output/public` 直接丢给任意静态托管即可。线上部署由 [GitHub Actions](.github/workflows/deploy.yml) 构建后直传 Cloudflare Pages: -| | | -| --------- | ---------------------------------- | -| 构建命令 | `pnpm build:data && pnpm generate` | -| 输出目录 | `.output/public` | -| Node 版本 | LTS(`.node-version`) | +- `main` 的 push 部署到 production; +- 指向 `main` 的 PR 部署到 `pr-<编号>` preview,后续提交沿用同一个预览地址。 + +仓库需要配置三个 Actions secrets: + +- `CLOUDFLARE_API_TOKEN`:具有 `Account / Cloudflare Pages / Edit` 权限的 API token; +- `CLOUDFLARE_ACCOUNT_ID`:Pages 项目所在的 Cloudflare account ID; +- `CLOUDFLARE_PROJECT_NAME`:Cloudflare Pages 项目名。 + +Cloudflare Pages 的 production branch 设为 `main`。在项目的 **Settings → Builds → Branch control** 中关闭 **Enable automatic production branch deployments**,并将 **Preview branch** 设为 **None**;Cloudflare 只托管 Actions 上传的成品,不再自行构建。 全部 8,449 个字组详情页都会生成独立 HTML;页面数据在本地 bundle 中,因此关闭了每路由额外生成 `_payload.json` 的 payload extraction。地区异体别名也不另外生成跳转页,而是通过 `public/_redirects` 回到应用后由客户端跳转。同一条回退规则也让应用显示未知字符的 404;`public/_headers` 给带内容哈希的 `_nuxt/*` 设了长缓存。 -构建时 `pnpm build:data` 会下载约 **211 MB** 原始数据(其中 195 MB 是十份 Noto CJK 字体),CI 上每次构建都要重下。若嫌慢,可改为本地构建后直传: +第三方资产的具体 commit、Unicode 版本与 SHA-256 记录在 `data/sources.lock.json`;需要升级时运行 `pnpm update:sources`。它会解析上游版本,版本有变化时下载变化的固定版本、更新 lockfile,并直接重新生成数据;版本未变则跳过下载与生成。构建时 `pnpm build:data` 会按 lockfile 下载并校验约 **211 MB** 原始数据(其中 195 MB 是十份 Noto CJK 字体)。Actions 以 lockfile、生成脚本和依赖的内容作为缓存键:输入完全不变时跳过下载与数据生成,生成逻辑变化时也能复用已校验的下载。 + +本地也可构建后直传: ```bash pnpm build:data && pnpm generate -pnpm dlx wrangler pages deploy .output/public +pnpm wrangler pages deploy .output/public --project-name="$CLOUDFLARE_PROJECT_NAME" ``` ## 数据来源 diff --git a/data/sources.lock.json b/data/sources.lock.json new file mode 100644 index 0000000..bc8b4c6 --- /dev/null +++ b/data/sources.lock.json @@ -0,0 +1,210 @@ +{ + "version": 1, + "revisions": { + "github:zispace/hanzi-chars@main": "105544909cab18db1fedfd41a42c87408fbaac59", + "github:BYVoid/OpenCC@master": "3acca9851846cee58b0be32f2618d8f675935a19", + "github:adobe-fonts/source-han-sans@master": "0b993716f6910f0c8e00f957c767ab3cf5cb7602", + "github:adobe-fonts/source-han-serif@master": "4356704ff7c68e9a84bf2bd489c9ebe186a9e2ef", + "github:ruddfawcett/hanziDB.csv@master": "3e9f908d25f70674862343b201f6b641a36cd226", + "github:notofonts/noto-cjk@main": "f8d157532fbfaeda587e826d4cd5b21a49186f7c", + "github:notofonts/notofonts.github.io@main": "bbb09a4d552a4180570754be89cb4eafc9cef34b", + "unicode:ucd": "17.0.0" + }, + "assets": { + "charlist/cn-1.txt": { + "url": "https://raw.githubusercontent.com/zispace/hanzi-chars/105544909cab18db1fedfd41a42c87408fbaac59/data-charlist/%E3%80%8A%E9%80%9A%E7%94%A8%E8%A7%84%E8%8C%83%E6%B1%89%E5%AD%97%E8%A1%A8%E3%80%8B%EF%BC%882013%E5%B9%B4%EF%BC%89%E4%B8%80%E7%BA%A7%E5%AD%97.txt", + "sha256": "3b46d0e204271a86d2d7f990f408d8e01dbd20fc5ea9f0cea3ef4e6c46b19e44", + "size": 14133 + }, + "charlist/cn-2.txt": { + "url": "https://raw.githubusercontent.com/zispace/hanzi-chars/105544909cab18db1fedfd41a42c87408fbaac59/data-charlist/%E3%80%8A%E9%80%9A%E7%94%A8%E8%A7%84%E8%8C%83%E6%B1%89%E5%AD%97%E8%A1%A8%E3%80%8B%EF%BC%882013%E5%B9%B4%EF%BC%89%E4%BA%8C%E7%BA%A7%E5%AD%97.txt", + "sha256": "cd584c8e2ae39fb0cabf6d793f47c0383ee7c1dd3210b5a5e1f114cb4832e954", + "size": 12064 + }, + "charlist/cn-3.txt": { + "url": "https://raw.githubusercontent.com/zispace/hanzi-chars/105544909cab18db1fedfd41a42c87408fbaac59/data-charlist/%E3%80%8A%E9%80%9A%E7%94%A8%E8%A7%84%E8%8C%83%E6%B1%89%E5%AD%97%E8%A1%A8%E3%80%8B%EF%BC%882013%E5%B9%B4%EF%BC%89%E4%B8%89%E7%BA%A7%E5%AD%97.txt", + "sha256": "0ed75010dd3fbcf4e0362ac80463af240d4a08fa902f5f27cd6c6543b96ca573", + "size": 6674 + }, + "charlist/tw-common.txt": { + "url": "https://raw.githubusercontent.com/zispace/hanzi-chars/105544909cab18db1fedfd41a42c87408fbaac59/data-charlist/%E8%87%BA%E7%81%A3%E3%80%8A%E5%B8%B8%E7%94%A8%E5%9C%8B%E5%AD%97%E8%A1%A8%E3%80%8B%EF%BC%881982%E5%B9%B4%EF%BC%89.txt", + "sha256": "d5247938cdb37a89ce2dc6f940342a8d1414dcd70e6fa65d51ebf5151cbc8058", + "size": 19675 + }, + "charlist/tw-sub.txt": { + "url": "https://raw.githubusercontent.com/zispace/hanzi-chars/105544909cab18db1fedfd41a42c87408fbaac59/data-charlist/%E8%87%BA%E7%81%A3%E3%80%8A%E6%AC%A1%E5%B8%B8%E7%94%A8%E5%9C%8B%E5%AD%97%E8%A1%A8%E3%80%8B%EF%BC%881982%E5%B9%B4%EF%BC%89.txt", + "sha256": "23491aa6b46b8a2a150c9e1a55773897415e69bb57a5dc6e4b8af7d512093d37", + "size": 26690 + }, + "charlist/hk-common.txt": { + "url": "https://raw.githubusercontent.com/zispace/hanzi-chars/105544909cab18db1fedfd41a42c87408fbaac59/data-charlist/%E9%A6%99%E6%B8%AF%E3%80%8A%E5%B8%B8%E7%94%A8%E5%AD%97%E8%A1%A8%E3%80%8B.txt", + "sha256": "18f42fb2c8e57fae5fa79d4f185a23abbc0f64d3d9b8e5cb3fc083640e14f3e4", + "size": 21938 + }, + "charlist/jp-joyo.txt": { + "url": "https://raw.githubusercontent.com/zispace/hanzi-chars/105544909cab18db1fedfd41a42c87408fbaac59/data-charlist/%E6%97%A5%E6%9C%AC%E3%80%8A%E5%B8%B8%E7%94%A8%E6%BC%A2%E5%AD%97%E8%A1%A8%E3%80%8B%EF%BC%882010%E5%B9%B4%EF%BC%89.txt", + "sha256": "9075456c2901972a1094bb0ef8b898894f7d1dd5cab928459c3855cce36c41fc", + "size": 10625 + }, + "charlist/jp-grade.txt": { + "url": "https://raw.githubusercontent.com/zispace/hanzi-chars/105544909cab18db1fedfd41a42c87408fbaac59/data-charlist/%E6%97%A5%E6%9C%AC%E3%80%8A%E5%AD%A6%E5%B9%B4%E5%88%A5%E6%BC%A2%E5%AD%97%E9%85%8D%E5%BD%93%E8%A1%A8%E3%80%8B%EF%BC%882017%E5%B9%B4%EF%BC%89.txt", + "sha256": "633eb2960f222b1beca821723c37605fb4af09571cdc5dc728787242a6300fdb", + "size": 4432 + }, + "charlist/kr-basic.txt": { + "url": "https://raw.githubusercontent.com/zispace/hanzi-chars/105544909cab18db1fedfd41a42c87408fbaac59/data-charlist/%E9%9F%A9%E5%9B%BD%E3%80%8A%E6%BC%A2%E6%96%87%E6%95%99%E8%82%B2%E7%94%A8%E5%9F%BA%E7%A4%8E%E6%BC%A2%E5%AD%97%E3%80%8B%EF%BC%882000%E5%B9%B4%E7%89%88%EF%BC%89.txt", + "sha256": "2f719e3c8ed6fc20629c4925009a5aeccff9e518f539b8cbcb49306a877653f2", + "size": 7466 + }, + "opencc/STCharacters.txt": { + "url": "https://raw.githubusercontent.com/BYVoid/OpenCC/3acca9851846cee58b0be32f2618d8f675935a19/data/dictionary/STCharacters.txt", + "sha256": "a0ca1601c70648cf48b33c3c6210ccbecc5c7eead4b4c3daf76587ba2c03582b", + "size": 36026 + }, + "opencc/TSCharacters.txt": { + "url": "https://raw.githubusercontent.com/BYVoid/OpenCC/3acca9851846cee58b0be32f2618d8f675935a19/data/dictionary/TSCharacters.txt", + "sha256": "737c21c66f55a419dd6956cb3089476cdefc5a36877452631617696df1e5d925", + "size": 104516 + }, + "opencc/TWVariants.txt": { + "url": "https://raw.githubusercontent.com/BYVoid/OpenCC/3acca9851846cee58b0be32f2618d8f675935a19/data/dictionary/TWVariants.txt", + "sha256": "e187278e119c427ca561180ac5da5b20e9f8681190458f35c327ce499e95a6a5", + "size": 986 + }, + "opencc/HKVariants.txt": { + "url": "https://raw.githubusercontent.com/BYVoid/OpenCC/3acca9851846cee58b0be32f2618d8f675935a19/data/dictionary/HKVariants.txt", + "sha256": "e5cd4345303224587102f2c9e4d2b67d2b7e349c6ce9152e4a118f4656cf7302", + "size": 1001 + }, + "opencc/JPShinjitaiCharacters.txt": { + "url": "https://raw.githubusercontent.com/BYVoid/OpenCC/3acca9851846cee58b0be32f2618d8f675935a19/data/dictionary/JPShinjitaiCharacters.txt", + "sha256": "12cec7250b873ef52b36d8f92218d4f92c0aaf5d8cd7c58fe42d9785bdcdc43a", + "size": 12442 + }, + "cmap/sans-CN.txt": { + "url": "https://raw.githubusercontent.com/adobe-fonts/source-han-sans/0b993716f6910f0c8e00f957c767ab3cf5cb7602/UniSourceHanSansCN-UTF32-H", + "sha256": "96d45400d35c0ee9da163975c8a31ce55f3724a9ddc20e292c35c94a7e37a514", + "size": 327260 + }, + "cmap/sans-HK.txt": { + "url": "https://raw.githubusercontent.com/adobe-fonts/source-han-sans/0b993716f6910f0c8e00f957c767ab3cf5cb7602/UniSourceHanSansHK-UTF32-H", + "sha256": "5064a4e861ce5a85d1dccfcc1db6ec8624e2e29ab21bbfe8aa042fce965c2dd3", + "size": 318808 + }, + "cmap/sans-TW.txt": { + "url": "https://raw.githubusercontent.com/adobe-fonts/source-han-sans/0b993716f6910f0c8e00f957c767ab3cf5cb7602/UniSourceHanSansTW-UTF32-H", + "sha256": "4f7606801d590ac8582b6488a08e8b9a6e0805bfbba9b5a94ad9ae8ebe4387a4", + "size": 322394 + }, + "cmap/sans-JP.txt": { + "url": "https://raw.githubusercontent.com/adobe-fonts/source-han-sans/0b993716f6910f0c8e00f957c767ab3cf5cb7602/UniSourceHanSansJP-UTF32-H", + "sha256": "aaa897077c75efc9c7bd1a7e3902663e80e01601dabd4d6b8b775bc107657e23", + "size": 320984 + }, + "cmap/sans-KR.txt": { + "url": "https://raw.githubusercontent.com/adobe-fonts/source-han-sans/0b993716f6910f0c8e00f957c767ab3cf5cb7602/UniSourceHanSansKR-UTF32-H", + "sha256": "8e237aa665a02d3e65b3461bb942f96e2892270c480dcb984d842f6e65d68f6c", + "size": 323439 + }, + "cmap/serif-CN.txt": { + "url": "https://raw.githubusercontent.com/adobe-fonts/source-han-serif/4356704ff7c68e9a84bf2bd489c9ebe186a9e2ef/UniSourceHanSerifCN-UTF32-H", + "sha256": "5d306e03a777064f7ad3f90308ac3a5d7aa89a9e2f3ae5b4496fbd919dd22243", + "size": 323390 + }, + "cmap/serif-HK.txt": { + "url": "https://raw.githubusercontent.com/adobe-fonts/source-han-serif/4356704ff7c68e9a84bf2bd489c9ebe186a9e2ef/UniSourceHanSerifHK-UTF32-H", + "sha256": "8a57057ac492bac4c942d0d611685048433ecf1c3b22972499e109fd69b3cbfd", + "size": 316016 + }, + "cmap/serif-TW.txt": { + "url": "https://raw.githubusercontent.com/adobe-fonts/source-han-serif/4356704ff7c68e9a84bf2bd489c9ebe186a9e2ef/UniSourceHanSerifTW-UTF32-H", + "sha256": "0d590f72cac5c8e50f9c2a54232e708c647502050dc7481d1cebaba79a5a5826", + "size": 319086 + }, + "cmap/serif-JP.txt": { + "url": "https://raw.githubusercontent.com/adobe-fonts/source-han-serif/4356704ff7c68e9a84bf2bd489c9ebe186a9e2ef/UniSourceHanSerifJP-UTF32-H", + "sha256": "435f637871f3cba49983d479ee00b1015cc61aa20677e47551205c49c915c268", + "size": 317668 + }, + "cmap/serif-KR.txt": { + "url": "https://raw.githubusercontent.com/adobe-fonts/source-han-serif/4356704ff7c68e9a84bf2bd489c9ebe186a9e2ef/UniSourceHanSerifKR-UTF32-H", + "sha256": "3bd68ceaeb92b8bb3e6703040d3ef9894b004f40270d1ead5899f3c4deebbac7", + "size": 320710 + }, + "frequency/hanziDB.csv": { + "url": "https://raw.githubusercontent.com/ruddfawcett/hanziDB.csv/3e9f908d25f70674862343b201f6b641a36cd226/data/hanziDB.csv", + "sha256": "03f809543a2bd0d32c8f65f98567cbaa01527f2eefbdb0ea214a1abddc0b03a0", + "size": 552982 + }, + "unihan/Unihan.zip": { + "url": "https://www.unicode.org/Public/17.0.0/ucd/Unihan.zip", + "sha256": "f7a48b2b545acfaa77b2d607ae28747404ce02baefee16396c5d2d7a8ef34b5e", + "size": 8518517 + }, + "font/NotoSansCJKsc-Regular.otf": { + "url": "https://raw.githubusercontent.com/notofonts/noto-cjk/f8d157532fbfaeda587e826d4cd5b21a49186f7c/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf", + "sha256": "2c76254f6fc379fddfce0a7e84fb5385bb135d3e399294f6eeb6680d0365b74b", + "size": 16437364 + }, + "font/NotoSansCJKhk-Regular.otf": { + "url": "https://raw.githubusercontent.com/notofonts/noto-cjk/f8d157532fbfaeda587e826d4cd5b21a49186f7c/Sans/OTF/TraditionalChineseHK/NotoSansCJKhk-Regular.otf", + "sha256": "97c937514d645eae90415d30ba025e08a94d5bdffdc627404864f90aa0c7d83b", + "size": 16427604 + }, + "font/NotoSansCJKtc-Regular.otf": { + "url": "https://raw.githubusercontent.com/notofonts/noto-cjk/f8d157532fbfaeda587e826d4cd5b21a49186f7c/Sans/OTF/TraditionalChinese/NotoSansCJKtc-Regular.otf", + "sha256": "dce08bd4fd91aa8aa76ed8fea4b694c2dfb8550f67871e326843212ddbeb88b4", + "size": 16435884 + }, + "font/NotoSansCJKjp-Regular.otf": { + "url": "https://raw.githubusercontent.com/notofonts/noto-cjk/f8d157532fbfaeda587e826d4cd5b21a49186f7c/Sans/OTF/Japanese/NotoSansCJKjp-Regular.otf", + "sha256": "68a3fc98800b2a27b371f2fb79991daf3633bd89309d4ffaa6946fd587f375b5", + "size": 16467736 + }, + "font/NotoSansCJKkr-Regular.otf": { + "url": "https://raw.githubusercontent.com/notofonts/noto-cjk/f8d157532fbfaeda587e826d4cd5b21a49186f7c/Sans/OTF/Korean/NotoSansCJKkr-Regular.otf", + "sha256": "6bcb2a0703aa137e874fc2dffa85f6c21ba9a67fa329e81b8c801663af7e992a", + "size": 16433112 + }, + "font/NotoSerifCJKsc-Regular.otf": { + "url": "https://raw.githubusercontent.com/notofonts/noto-cjk/f8d157532fbfaeda587e826d4cd5b21a49186f7c/Serif/OTF/SimplifiedChinese/NotoSerifCJKsc-Regular.otf", + "sha256": "2a2eae2628df83556c54018c41e20fa532c1b862c5256ae8b3f23feb918d12ca", + "size": 24543080 + }, + "font/NotoSerifCJKhk-Regular.otf": { + "url": "https://raw.githubusercontent.com/notofonts/noto-cjk/f8d157532fbfaeda587e826d4cd5b21a49186f7c/Serif/OTF/TraditionalChineseHK/NotoSerifCJKhk-Regular.otf", + "sha256": "e687d5674d83acf62d1264da5d002c5401363418abb98dca0836d38d88953708", + "size": 24533712 + }, + "font/NotoSerifCJKtc-Regular.otf": { + "url": "https://raw.githubusercontent.com/notofonts/noto-cjk/f8d157532fbfaeda587e826d4cd5b21a49186f7c/Serif/OTF/TraditionalChinese/NotoSerifCJKtc-Regular.otf", + "sha256": "234301038e76e7c35c43113785024700c4e4fe7bdce1d1fbbc42fca7e6683798", + "size": 24541904 + }, + "font/NotoSerifCJKjp-Regular.otf": { + "url": "https://raw.githubusercontent.com/notofonts/noto-cjk/f8d157532fbfaeda587e826d4cd5b21a49186f7c/Serif/OTF/Japanese/NotoSerifCJKjp-Regular.otf", + "sha256": "d9854c7a8ef170b5a7932558856fd64eb8de0b007cd823fed6f9f514ad2803d3", + "size": 24573864 + }, + "font/NotoSerifCJKkr-Regular.otf": { + "url": "https://raw.githubusercontent.com/notofonts/noto-cjk/f8d157532fbfaeda587e826d4cd5b21a49186f7c/Serif/OTF/Korean/NotoSerifCJKkr-Regular.otf", + "sha256": "77b4b741f864d27f15e90f275b17106dde90b2ad28f82bab72dc95805db5fb42", + "size": 24539640 + }, + "font/NotoSans-VF.ttf": { + "url": "https://raw.githubusercontent.com/notofonts/notofonts.github.io/bbb09a4d552a4180570754be89cb4eafc9cef34b/fonts/NotoSans/unhinted/variable-ttf/NotoSans%5Bwdth,wght%5D.ttf", + "sha256": "511cc04b442ffcc3b24d65c2cc780bdffaf482b20ba0aebdc8f27b37017ff0f5", + "size": 1581884 + }, + "font/NotoSerif-VF.ttf": { + "url": "https://raw.githubusercontent.com/notofonts/notofonts.github.io/bbb09a4d552a4180570754be89cb4eafc9cef34b/fonts/NotoSerif/unhinted/variable-ttf/NotoSerif%5Bwdth,wght%5D.ttf", + "sha256": "8918809882ffe61eb32baf3f4b3420fda6c022f2219b1b0272755d70ecabc7de", + "size": 1887176 + }, + "font/OFL.txt": { + "url": "https://raw.githubusercontent.com/notofonts/noto-cjk/f8d157532fbfaeda587e826d4cd5b21a49186f7c/Sans/LICENSE", + "sha256": "6a73f9541c2de74158c0e7cf6b0a58ef774f5a780bf191f2d7ec9cc53efe2bf2", + "size": 4301 + } + } +} diff --git a/package.json b/package.json index af17c2c..acfa133 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "format": "prettier --write .", "test": "vitest run", "build:data": "node scripts/build-data.ts && node scripts/build-fonts.ts", + "update:sources": "node scripts/update-sources.ts", "postinstall": "nuxt prepare" }, "dependencies": { @@ -48,7 +49,8 @@ "subset-font": "catalog:", "typescript": "catalog:", "vitest": "catalog:", - "vue-tsc": "catalog:" + "vue-tsc": "catalog:", + "wrangler": "4.124.0" }, "prettier": "@sxzz/prettier-config" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08f3cd6..f31df12 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -156,6 +156,9 @@ importers: vue-tsc: specifier: 'catalog:' version: 3.3.10(typescript@6.0.3) + wrangler: + specifier: 4.124.0 + version: 4.124.0 packages: @@ -327,9 +330,56 @@ packages: resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} + '@cloudflare/kv-asset-handler@0.5.0': + resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} + engines: {node: '>=22.0.0'} + + '@cloudflare/unenv-preset@2.16.1': + resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: '>1.20260305.0 <2.0.0-0' + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/workerd-darwin-64@1.20260815.1': + resolution: {integrity: sha512-7PsLdcz6pT9EMd1EJGZEgMyYRfs0CHxGs62PS2L1w3s6+xGmQcRXKm/zoMftmqZF45JBa4MzFeownRKbRt/x5g==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20260815.1': + resolution: {integrity: sha512-60wtg8ng7FVWeOg/UMbZ9Ye0sslpRRAKoftPbdtuH2volq676quxVr6Zm2EjVULH/JFZeCn72dbLlrnbh0Mpcw==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20260815.1': + resolution: {integrity: sha512-MuqKIHPo0Qyo8MZMmy0lP2B5PeAL7f4T9Fu4Usk3QdbV4JIrKG/OoybN3Ign7m/Dff+L1Oo/ZHydB+hEg1ueFw==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20260815.1': + resolution: {integrity: sha512-XNFtJ5rIqJxnY6ISjkfbhT/ODiWJ6LcBvNbntuPD6I/F2k7aZeKgPaXrvWvKde66LXyzFKzc8Hn+Ydx4shevQg==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20260815.1': + resolution: {integrity: sha512-PiIUWrhbMg3quolwjgMvPOd75vKESjT4aDm7nL6mSjL5IOgmpO/zKstXnYfnEH3pq7sC0UCvKlF8ZPcfsh8NMw==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + '@colordx/core@5.5.0': resolution: {integrity: sha512-3PxTH8itZzltK0U9jTwVVnjLXvnDYuq3m+QXsHkENxWiPRh4WaoLcs1SQjqgZ55kS+QyirpH5BVwzP2gMVG6EQ==} + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + '@dxup/nuxt@0.5.8': resolution: {integrity: sha512-IuKNesqRKXNYFqLtDHZlV2GBNY+iyb6lc64zVxLXssXgb+O1bfirRbeuzscVNgDrRtAPs/htb3dNF4aCwRctIw==} @@ -366,156 +416,312 @@ packages: resolution: {integrity: sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==} engines: {node: '>=10'} + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.28.2': resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.28.2': resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.28.2': resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.28.2': resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.28.2': resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.28.2': resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.28.2': resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.28.2': resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.28.2': resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.28.2': resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.28.2': resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.28.2': resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.28.2': resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.28.2': resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.28.2': resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.28.2': resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.28.2': resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.28.2': resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.28.2': resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.28.2': resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.28.2': resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.28.2': resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.28.2': resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.28.2': resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.28.2': resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.28.2': resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} engines: {node: '>=18'} @@ -623,6 +829,168 @@ packages: '@iconify/utils@3.1.4': resolution: {integrity: sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.35.2': + resolution: {integrity: sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.35.2': + resolution: {integrity: sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [darwin] + + '@img/sharp-freebsd-wasm32@0.35.2': + resolution: {integrity: sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.1': + resolution: {integrity: sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.3.1': + resolution: {integrity: sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.3.1': + resolution: {integrity: sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.3.1': + resolution: {integrity: sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.3.1': + resolution: {integrity: sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.3.1': + resolution: {integrity: sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.3.1': + resolution: {integrity: sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.3.1': + resolution: {integrity: sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.3.1': + resolution: {integrity: sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.3.1': + resolution: {integrity: sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.35.2': + resolution: {integrity: sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.35.2': + resolution: {integrity: sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==} + engines: {node: '>=20.9.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.35.2': + resolution: {integrity: sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==} + engines: {node: '>=20.9.0'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.35.2': + resolution: {integrity: sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==} + engines: {node: '>=20.9.0'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.35.2': + resolution: {integrity: sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==} + engines: {node: '>=20.9.0'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.35.2': + resolution: {integrity: sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.35.2': + resolution: {integrity: sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.35.2': + resolution: {integrity: sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.35.2': + resolution: {integrity: sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.2': + resolution: {integrity: sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==} + engines: {node: '>=20.9.0'} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.35.2': + resolution: {integrity: sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.35.2': + resolution: {integrity: sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==} + engines: {node: ^20.9.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.35.2': + resolution: {integrity: sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [win32] + '@ioredis/commands@1.10.0': resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==} @@ -653,6 +1021,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@kwsites/file-exists@1.1.1': resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} @@ -1060,6 +1431,9 @@ packages: '@poppinss/colors@4.1.6': resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + '@poppinss/dumper@0.7.0': resolution: {integrity: sha512-0UTYalzk2t6S4rA2uHOz5bSSW2CHdv4vggJI6Alg90yvl0UgXs6XSXpH96OH+bRkX4J/06djv29pqXJ0lq5Kag==} @@ -2052,6 +2426,9 @@ packages: birpc@4.2.0: resolution: {integrity: sha512-KxgKcZPfrtzJDDALHPguGpGJUrzdgpymyiQQgzFjWreHMOpWrnFNVREr5J48x2DBh8ZVioscrV1SBkDipGiX+Q==} + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2222,6 +2599,10 @@ packages: cookie-es@3.1.1: resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + core-js-compat@3.50.0: resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} engines: {node: '>=6.4.0'} @@ -2477,6 +2858,11 @@ packages: es-module-lexer@2.3.2: resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.28.2: resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} engines: {node: '>=18'} @@ -3507,6 +3893,10 @@ packages: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + miniflare@5.20260815.0-alpha: + resolution: {integrity: sha512-YAaGj4Sh5f4fqHKiMQ8zRHDOOM5IGUVtMhnLIeyjuQfU+9P6hcOTrHUVtbfj/ZPay9Kzik4pWELB39pGgefjiQ==} + engines: {node: '>=22.0.0'} + minimatch@10.2.6: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} @@ -3813,6 +4203,9 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -4241,6 +4634,10 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.35.2: + resolution: {integrity: sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==} + engines: {node: '>=20.9.0'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -4556,6 +4953,10 @@ packages: undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + undici@7.29.0: + resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} + engines: {node: '>=20.18.1'} + undici@8.10.0: resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==} engines: {node: '>=22.19.0'} @@ -5014,6 +5415,21 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + workerd@1.20260815.1: + resolution: {integrity: sha512-8bArFkHmlp7qFEKVPyNzDzHzS35gc2fg0PYBcDtaNLF7UCDryCX2BQnpkUkTHYIy824IRrHOTwOEoTj0sUO2Fg==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.124.0: + resolution: {integrity: sha512-75euoZKjVTJYFy+Xhctt/5JlZL4M6A4xmovZsUlep+6GHcCm14n9VtdGzNIybOW2t8wuNxRj5iMUwjT5E7Ctog==} + engines: {node: '>=22.0.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^5.20260815.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -5026,6 +5442,18 @@ packages: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.21.3: resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} engines: {node: '>=10.0.0'} @@ -5081,6 +5509,9 @@ packages: youch-core@0.3.3: resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + youch@4.1.1: resolution: {integrity: sha512-mxW3qiSnl+GRxXsaUMzv2Mbada1Y8CDltET9UxejDQe6DBYlSekghl5U5K0ReAikcHDi0G1vKZEmmo/NWAGKLA==} @@ -5305,8 +5736,35 @@ snapshots: '@cloudflare/kv-asset-handler@0.4.2': {} + '@cloudflare/kv-asset-handler@0.5.0': {} + + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260815.1)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260815.1 + + '@cloudflare/workerd-darwin-64@1.20260815.1': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20260815.1': + optional: true + + '@cloudflare/workerd-linux-64@1.20260815.1': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20260815.1': + optional: true + + '@cloudflare/workerd-windows-64@1.20260815.1': + optional: true + '@colordx/core@5.5.0': {} + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + '@dxup/nuxt@0.5.8(esbuild@0.28.2)(magicast@0.5.4)(oxc-parser@0.131.0)(rolldown@1.2.5)(rollup@4.62.4)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.50.0)(yaml@2.9.0))(webpack@5.109.2(cssnano@8.0.6(postcss@8.5.26))(csso@5.0.5)(esbuild@0.28.2)(lightningcss@1.33.0)(postcss@8.5.26))': dependencies: '@dxup/unimport': 0.1.2 @@ -5383,81 +5841,159 @@ snapshots: '@es-joy/resolve.exports@1.2.0': {} + '@esbuild/aix-ppc64@0.28.1': + optional: true + '@esbuild/aix-ppc64@0.28.2': optional: true + '@esbuild/android-arm64@0.28.1': + optional: true + '@esbuild/android-arm64@0.28.2': optional: true + '@esbuild/android-arm@0.28.1': + optional: true + '@esbuild/android-arm@0.28.2': optional: true + '@esbuild/android-x64@0.28.1': + optional: true + '@esbuild/android-x64@0.28.2': optional: true + '@esbuild/darwin-arm64@0.28.1': + optional: true + '@esbuild/darwin-arm64@0.28.2': optional: true + '@esbuild/darwin-x64@0.28.1': + optional: true + '@esbuild/darwin-x64@0.28.2': optional: true + '@esbuild/freebsd-arm64@0.28.1': + optional: true + '@esbuild/freebsd-arm64@0.28.2': optional: true + '@esbuild/freebsd-x64@0.28.1': + optional: true + '@esbuild/freebsd-x64@0.28.2': optional: true + '@esbuild/linux-arm64@0.28.1': + optional: true + '@esbuild/linux-arm64@0.28.2': optional: true + '@esbuild/linux-arm@0.28.1': + optional: true + '@esbuild/linux-arm@0.28.2': optional: true + '@esbuild/linux-ia32@0.28.1': + optional: true + '@esbuild/linux-ia32@0.28.2': optional: true + '@esbuild/linux-loong64@0.28.1': + optional: true + '@esbuild/linux-loong64@0.28.2': optional: true + '@esbuild/linux-mips64el@0.28.1': + optional: true + '@esbuild/linux-mips64el@0.28.2': optional: true + '@esbuild/linux-ppc64@0.28.1': + optional: true + '@esbuild/linux-ppc64@0.28.2': optional: true + '@esbuild/linux-riscv64@0.28.1': + optional: true + '@esbuild/linux-riscv64@0.28.2': optional: true + '@esbuild/linux-s390x@0.28.1': + optional: true + '@esbuild/linux-s390x@0.28.2': optional: true + '@esbuild/linux-x64@0.28.1': + optional: true + '@esbuild/linux-x64@0.28.2': optional: true + '@esbuild/netbsd-arm64@0.28.1': + optional: true + '@esbuild/netbsd-arm64@0.28.2': optional: true + '@esbuild/netbsd-x64@0.28.1': + optional: true + '@esbuild/netbsd-x64@0.28.2': optional: true + '@esbuild/openbsd-arm64@0.28.1': + optional: true + '@esbuild/openbsd-arm64@0.28.2': optional: true + '@esbuild/openbsd-x64@0.28.1': + optional: true + '@esbuild/openbsd-x64@0.28.2': optional: true + '@esbuild/openharmony-arm64@0.28.1': + optional: true + '@esbuild/openharmony-arm64@0.28.2': optional: true + '@esbuild/sunos-x64@0.28.1': + optional: true + '@esbuild/sunos-x64@0.28.2': optional: true + '@esbuild/win32-arm64@0.28.1': + optional: true + '@esbuild/win32-arm64@0.28.2': optional: true + '@esbuild/win32-ia32@0.28.1': + optional: true + '@esbuild/win32-ia32@0.28.2': optional: true + '@esbuild/win32-x64@0.28.1': + optional: true + '@esbuild/win32-x64@0.28.2': optional: true @@ -5568,6 +6104,112 @@ snapshots: '@iconify/types': 2.0.0 import-meta-resolve: 4.2.0 + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.3.1 + optional: true + + '@img/sharp-darwin-x64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.3.1 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.2': + dependencies: + '@img/sharp-wasm32': 0.35.2 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.3.1': + optional: true + + '@img/sharp-libvips-darwin-x64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-arm64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-arm@1.3.1': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-s390x@1.3.1': + optional: true + + '@img/sharp-libvips-linux-x64@1.3.1': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.3.1': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.3.1': + optional: true + + '@img/sharp-linux-arm64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.3.1 + optional: true + + '@img/sharp-linux-arm@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.3.1 + optional: true + + '@img/sharp-linux-ppc64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.3.1 + optional: true + + '@img/sharp-linux-riscv64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.3.1 + optional: true + + '@img/sharp-linux-s390x@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.3.1 + optional: true + + '@img/sharp-linux-x64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.3.1 + optional: true + + '@img/sharp-linuxmusl-arm64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.3.1 + optional: true + + '@img/sharp-linuxmusl-x64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.3.1 + optional: true + + '@img/sharp-wasm32@0.35.2': + dependencies: + '@emnapi/runtime': 1.11.1 + optional: true + + '@img/sharp-webcontainers-wasm32@0.35.2': + dependencies: + '@img/sharp-wasm32': 0.35.2 + optional: true + + '@img/sharp-win32-arm64@0.35.2': + optional: true + + '@img/sharp-win32-ia32@0.35.2': + optional: true + + '@img/sharp-win32-x64@0.35.2': + optional: true + '@ioredis/commands@1.10.0': {} '@isaacs/cliui@8.0.2': @@ -5607,6 +6249,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@kwsites/file-exists@1.1.1(supports-color@10.2.2)': dependencies: debug: 4.4.3(supports-color@10.2.2) @@ -6141,6 +6788,12 @@ snapshots: dependencies: kleur: 4.1.5 + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 + '@poppinss/dumper@0.7.0': dependencies: '@poppinss/colors': 4.1.6 @@ -7287,6 +7940,8 @@ snapshots: birpc@4.2.0: {} + blake3-wasm@2.1.5: {} + boolbase@1.0.0: {} brace-expansion@2.1.4: @@ -7434,6 +8089,8 @@ snapshots: cookie-es@3.1.1: {} + cookie@1.1.1: {} + core-js-compat@3.50.0: dependencies: browserslist: 4.28.8 @@ -7646,6 +8303,35 @@ snapshots: es-module-lexer@2.3.2: {} + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + esbuild@0.28.2: optionalDependencies: '@esbuild/aix-ppc64': 0.28.2 @@ -8917,6 +9603,18 @@ snapshots: mimic-fn@4.0.0: {} + miniflare@5.20260815.0-alpha: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.35.2 + undici: 7.29.0 + workerd: 1.20260815.1 + ws: 8.21.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + minimatch@10.2.6: dependencies: brace-expansion: 5.0.9 @@ -9423,6 +10121,8 @@ snapshots: lru-cache: 11.5.2 minipass: 7.1.3 + path-to-regexp@6.3.0: {} + pathe@2.0.3: {} perfect-debounce@2.1.0: {} @@ -9861,6 +10561,38 @@ snapshots: setprototypeof@1.2.0: {} + sharp@0.35.2: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.35.2 + '@img/sharp-darwin-x64': 0.35.2 + '@img/sharp-freebsd-wasm32': 0.35.2 + '@img/sharp-libvips-darwin-arm64': 1.3.1 + '@img/sharp-libvips-darwin-x64': 1.3.1 + '@img/sharp-libvips-linux-arm': 1.3.1 + '@img/sharp-libvips-linux-arm64': 1.3.1 + '@img/sharp-libvips-linux-ppc64': 1.3.1 + '@img/sharp-libvips-linux-riscv64': 1.3.1 + '@img/sharp-libvips-linux-s390x': 1.3.1 + '@img/sharp-libvips-linux-x64': 1.3.1 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.1 + '@img/sharp-libvips-linuxmusl-x64': 1.3.1 + '@img/sharp-linux-arm': 0.35.2 + '@img/sharp-linux-arm64': 0.35.2 + '@img/sharp-linux-ppc64': 0.35.2 + '@img/sharp-linux-riscv64': 0.35.2 + '@img/sharp-linux-s390x': 0.35.2 + '@img/sharp-linux-x64': 0.35.2 + '@img/sharp-linuxmusl-arm64': 0.35.2 + '@img/sharp-linuxmusl-x64': 0.35.2 + '@img/sharp-webcontainers-wasm32': 0.35.2 + '@img/sharp-win32-arm64': 0.35.2 + '@img/sharp-win32-ia32': 0.35.2 + '@img/sharp-win32-x64': 0.35.2 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -10173,6 +10905,8 @@ snapshots: undici-types@8.3.0: {} + undici@7.29.0: {} + undici@8.10.0: {} unenv@2.0.0-rc.24: @@ -10626,6 +11360,30 @@ snapshots: word-wrap@1.2.5: {} + workerd@1.20260815.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260815.1 + '@cloudflare/workerd-darwin-arm64': 1.20260815.1 + '@cloudflare/workerd-linux-64': 1.20260815.1 + '@cloudflare/workerd-linux-arm64': 1.20260815.1 + '@cloudflare/workerd-windows-64': 1.20260815.1 + + wrangler@4.124.0: + dependencies: + '@cloudflare/kv-asset-handler': 0.5.0 + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260815.1) + blake3-wasm: 2.1.5 + esbuild: 0.28.1 + miniflare: 5.20260815.0-alpha + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260815.1 + optionalDependencies: + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -10644,6 +11402,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.2.0 + ws@8.21.0: {} + ws@8.21.3: {} wsl-utils@1.0.0: @@ -10684,6 +11444,14 @@ snapshots: '@poppinss/exception': 1.2.3 error-stack-parser-es: 1.0.5 + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.24 + cookie: 1.1.1 + youch-core: 0.3.3 + youch@4.1.1: dependencies: '@poppinss/colors': 4.1.6 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0770515..be33d9b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -25,5 +25,6 @@ catalog: vue-tsc: ^3.3.10 allowBuilds: esbuild: true + workerd: true enableGlobalVirtualStore: true minimumReleaseAge: 0 diff --git a/scripts/sources.ts b/scripts/sources.ts index c04f23b..e3359a8 100644 --- a/scripts/sources.ts +++ b/scripts/sources.ts @@ -6,7 +6,8 @@ * keep up to date. */ import { Buffer } from 'node:buffer' -import { existsSync } from 'node:fs' +import { createHash } from 'node:crypto' +import { existsSync, readFileSync } from 'node:fs' import { mkdir, readFile, writeFile } from 'node:fs/promises' import { dirname, join } from 'node:path' import process from 'node:process' @@ -18,6 +19,7 @@ export const ROOT = dirname(dirname(fileURLToPath(import.meta.url))) export const RAW_DIR = join(ROOT, 'data/raw') export const DATA_DIR = join(ROOT, 'public/data') export const FONT_DIR = join(ROOT, 'public/fonts') +export const SOURCE_LOCK_PATH = join(ROOT, 'data/sources.lock.json') /** * Copy in every interface language. Written out here rather than in the @@ -250,10 +252,11 @@ const noto = (style: 'Sans' | 'Serif', region: string) => ) /** - * Cache path -> download URL. Paths are grouped by what the file is, so - * data/raw stays legible once it holds a hundred megabytes of downloads. + * Cache path -> moving upstream URL. `pnpm update:sources` resolves these refs + * to immutable versions and writes their checksums to SOURCE_LOCK_PATH. Builds + * only download the pinned URLs from that lockfile. */ -export const ASSETS: Record = { +export const ASSET_URLS: Record = { 'charlist/cn-1.txt': charList('《通用规范汉字表》(2013年)一级字.txt'), 'charlist/cn-2.txt': charList('《通用规范汉字表》(2013年)二级字.txt'), 'charlist/cn-3.txt': charList('《通用规范汉字表》(2013年)三级字.txt'), @@ -318,19 +321,72 @@ export const ASSETS: Record = { 'font/OFL.txt': gh('notofonts/noto-cjk', 'main', 'Sans/LICENSE'), } -/** Read from the cache, downloading first if needed. */ +export interface LockedAsset { + url: string + sha256: string + size: number +} + +export interface SourceLock { + version: 1 + revisions: Record + assets: Record +} + +let loadedSourceLock: SourceLock | undefined + +export function sourceLock(): SourceLock { + if (loadedSourceLock) return loadedSourceLock + const lock = JSON.parse(readFileSync(SOURCE_LOCK_PATH, 'utf8')) as SourceLock + if (lock.version !== 1 || !lock.assets || !lock.revisions) + throw new Error(`invalid source lock: ${SOURCE_LOCK_PATH}`) + loadedSourceLock = lock + return lock +} + +export const sha256 = (data: Uint8Array): string => + createHash('sha256').update(data).digest('hex') + +function lockedAsset(name: string): LockedAsset { + if (!ASSET_URLS[name]) throw new Error(`unregistered source: ${name}`) + const asset = sourceLock().assets[name] + if (!asset) throw new Error(`source missing from lockfile: ${name}`) + if (!/^https:\/\//.test(asset.url) || !/^[a-f\d]{64}$/.test(asset.sha256)) + throw new Error(`invalid locked source: ${name}`) + return asset +} + +function matchesLock(data: Buffer, asset: LockedAsset): boolean { + return data.byteLength === asset.size && sha256(data) === asset.sha256 +} + +async function downloadLocked( + name: string, + path: string, + asset: LockedAsset, +): Promise { + const res = await fetch(asset.url) + if (!res.ok) throw new Error(`download failed: ${res.status} ${asset.url}`) + const data = Buffer.from(await res.arrayBuffer()) + if (!matchesLock(data, asset)) + throw new Error(`checksum mismatch for locked source: ${name}`) + await mkdir(dirname(path), { recursive: true }) + await writeFile(path, data) + return data +} + +/** Read a verified pinned source from the cache, downloading when necessary. */ export async function raw(name: string): Promise { - const url = ASSETS[name] - if (!url) throw new Error(`unregistered source: ${name}`) + const asset = lockedAsset(name) const path = join(RAW_DIR, name) - if (!existsSync(path)) { - await mkdir(dirname(path), { recursive: true }) + if (existsSync(path)) { + const cached = await readFile(path) + if (matchesLock(cached, asset)) return cached + process.stderr.write(` ↻ ${name}\n`) + } else { process.stderr.write(` ↓ ${name}\n`) - const res = await fetch(url) - if (!res.ok) throw new Error(`download failed: ${res.status} ${url}`) - await writeFile(path, Buffer.from(await res.arrayBuffer())) } - return readFile(path) + return downloadLocked(name, path, asset) } export async function rawText(name: string): Promise { diff --git a/scripts/update-sources.ts b/scripts/update-sources.ts new file mode 100644 index 0000000..a29f0a5 --- /dev/null +++ b/scripts/update-sources.ts @@ -0,0 +1,202 @@ +import { Buffer } from 'node:buffer' +import { createHash } from 'node:crypto' +import { existsSync } from 'node:fs' +import { mkdir, readFile, writeFile } from 'node:fs/promises' +import { dirname, join } from 'node:path' +import process from 'node:process' +import { + ASSET_URLS, + RAW_DIR, + SOURCE_LOCK_PATH, + type LockedAsset, + type SourceLock, +} from './sources.ts' + +const GITHUB_RAW = + /^https:\/\/raw\.githubusercontent\.com\/([^/]+)\/([^/]+)\/([^/]+)\/(.+)$/ +const UNICODE_LATEST = 'https://www.unicode.org/Public/UCD/latest/ucd/' +const UNICODE_README = 'https://www.unicode.org/Public/UCD/latest/ReadMe.txt' + +interface GitHubRef { + repo: string + ref: string +} + +const githubRef = (url: string): GitHubRef | undefined => { + const match = GITHUB_RAW.exec(url) + return match ? { repo: `${match[1]}/${match[2]}`, ref: match[3]! } : undefined +} + +async function githubRevision(source: GitHubRef): Promise { + const headers: Record = { + Accept: 'application/vnd.github+json', + 'User-Agent': 'hanji-source-lock', + } + if (process.env.GITHUB_TOKEN) + headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}` + const res = await fetch( + `https://api.github.com/repos/${source.repo}/commits/${encodeURIComponent(source.ref)}`, + { headers }, + ) + if (!res.ok) + throw new Error( + `cannot resolve ${source.repo}@${source.ref}: ${res.status}`, + ) + const json = (await res.json()) as { sha?: string } + if (!json.sha || !/^[a-f\d]{40}$/.test(json.sha)) + throw new Error(`invalid revision for ${source.repo}@${source.ref}`) + return json.sha +} + +async function unicodeVersion(): Promise { + const res = await fetch(UNICODE_README) + if (!res.ok) throw new Error(`cannot resolve latest Unicode: ${res.status}`) + const match = /for Version (\d+\.\d+\.\d+) of the Unicode Standard/.exec( + await res.text(), + ) + if (!match) throw new Error('cannot parse latest Unicode version') + return match[1]! +} + +function pinnedUrl( + url: string, + revisions: ReadonlyMap, + unicode: string, +): string { + const match = GITHUB_RAW.exec(url) + if (match) { + const key = `${match[1]}/${match[2]}@${match[3]}` + const revision = revisions.get(key) + if (!revision) throw new Error(`unresolved GitHub source: ${key}`) + return `https://raw.githubusercontent.com/${match[1]}/${match[2]}/${revision}/${match[4]}` + } + if (url.startsWith(UNICODE_LATEST)) + return url.replace( + UNICODE_LATEST, + `https://www.unicode.org/Public/${unicode}/ucd/`, + ) + throw new Error(`source URL cannot be pinned: ${url}`) +} + +function isLockedAsset(asset: LockedAsset | undefined): asset is LockedAsset { + return Boolean( + asset && + /^https:\/\//.test(asset.url) && + /^[a-f\d]{64}$/.test(asset.sha256) && + Number.isSafeInteger(asset.size) && + asset.size >= 0, + ) +} + +async function existingLock(): Promise { + if (!existsSync(SOURCE_LOCK_PATH)) return undefined + const lock = JSON.parse( + await readFile(SOURCE_LOCK_PATH, 'utf8'), + ) as SourceLock + if (lock.version !== 1 || !lock.assets || !lock.revisions) + throw new Error(`invalid source lock: ${SOURCE_LOCK_PATH}`) + return lock +} + +function sameRecord( + left: Readonly>, + right: Readonly>, +): boolean { + const keys = Object.keys(left) + return ( + keys.length === Object.keys(right).length && + keys.every((key) => left[key] === right[key]) + ) +} + +async function download(name: string, url: string): Promise { + process.stderr.write(` ↓ ${name}\n`) + const res = await fetch(url) + if (!res.ok) throw new Error(`download failed: ${res.status} ${url}`) + const data = Buffer.from(await res.arrayBuffer()) + await mkdir(dirname(join(RAW_DIR, name)), { recursive: true }) + await writeFile(join(RAW_DIR, name), data) + return { + url, + sha256: createHash('sha256').update(data).digest('hex'), + size: data.byteLength, + } +} + +const refs = new Map() +for (const url of Object.values(ASSET_URLS)) { + const source = githubRef(url) + if (source) refs.set(`${source.repo}@${source.ref}`, source) +} + +process.stderr.write('Resolving source versions...\n') +const [resolvedRefs, unicode, previous] = await Promise.all([ + Promise.all( + [...refs].map( + async ([key, source]) => [key, await githubRevision(source)] as const, + ), + ), + unicodeVersion(), + existingLock(), +]) +const revisions = new Map(resolvedRefs) +const revisionRecord = { + ...Object.fromEntries( + [...revisions].map(([key, revision]) => [`github:${key}`, revision]), + ), + 'unicode:ucd': unicode, +} +const entries = Object.entries(ASSET_URLS).map( + ([name, url]) => [name, pinnedUrl(url, revisions, unicode)] as const, +) +const current = Boolean( + previous && + sameRecord(previous.revisions, revisionRecord) && + Object.keys(previous.assets).length === entries.length && + entries.every( + ([name, url]) => + previous.assets[name]?.url === url && + isLockedAsset(previous.assets[name]), + ), +) + +if (current) { + process.stderr.write( + 'Source versions unchanged; skipping downloads and data build.\n', + ) +} else { + const assets = new Array(entries.length) + let next = 0 + + process.stderr.write('Downloading changed pinned sources...\n') + const worker = async () => { + while (next < entries.length) { + const index = next++ + const [name, url] = entries[index]! + const locked = previous?.assets[name] + assets[index] = [ + name, + locked?.url === url && isLockedAsset(locked) + ? locked + : await download(name, url), + ] + } + } + await Promise.all(Array.from({ length: 4 }, worker)) + + const lock: SourceLock = { + version: 1, + revisions: revisionRecord, + assets: Object.fromEntries(assets), + } + await mkdir(dirname(SOURCE_LOCK_PATH), { recursive: true }) + await writeFile(SOURCE_LOCK_PATH, `${JSON.stringify(lock, null, 2)}\n`) + + const total = assets.reduce((sum, [, asset]) => sum + asset.size, 0) + process.stderr.write( + `Locked ${assets.length} assets (${(total / 1024 / 1024).toFixed(1)} MB).\n`, + ) + process.stderr.write('Building data from locked sources...\n') + await import('./build-data.ts') + await import('./build-fonts.ts') +}