From 78b0c6befda2187285cf672f1cf514f774a1baf4 Mon Sep 17 00:00:00 2001 From: go0p Date: Tue, 18 Aug 2026 14:40:36 +0800 Subject: [PATCH] ci: rebuild release pipeline around OSS manifest protocol Replace the broken yarn/Node 18 release workflow (wrong package manager, missing build/ dir, archived actions) with a manifest-based distribution flow modeled on yaklang/browser-binaries-mirror: - scripts/package-release.mjs: package the four variants, emit release-entry.json and per-artifact sha256 checksums - scripts/build-manifest.mjs: merge into a bounded public manifest (10 versions) with invariant validation - scripts/publish-oss.mjs: immutable artifacts (forbid-overwrite, one-year cache) and mutable manifest (5-minute cache, checksum published second), idempotent via head + sha256 comparison - scripts/verify-public.mjs: post-publish verification from the public endpoint (bytes, headers, zip layout) - release.yml: pnpm + Node 22, full verify:production, OSS publish, GitHub Release, separate public verify job, publish concurrency group - ci.yml: run verify:production on push/PR - single-source the extension version in package.json (wxt.config reads) - document the distribution protocol in README --- .github/workflows/ci.yml | 37 +++ .github/workflows/release.yml | 212 ++++++++---- README.md | 25 ++ package.json | 4 + pnpm-lock.yaml | 585 ++++++++++++++++++++++++++++++++++ scripts/build-manifest.mjs | 120 +++++++ scripts/package-release.mjs | 117 +++++++ scripts/publish-oss.mjs | 148 +++++++++ scripts/verify-public.mjs | 100 ++++++ wxt.config.ts | 8 +- 10 files changed, 1288 insertions(+), 68 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 scripts/build-manifest.mjs create mode 100644 scripts/package-release.mjs create mode 100644 scripts/publish-oss.mjs create mode 100644 scripts/verify-public.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ebcc3c2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify: + name: Test and build + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Test, compile, build and audit all variants + run: pnpm verify:production diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c5103d5..0a75e5d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,9 +2,146 @@ name: Build and Release on: workflow_dispatch +permissions: + contents: write + +# Serializes publishes so two runs can never interleave the +# fetch-existing-manifest / upload-manifest sequence. +concurrency: + group: oss-extension-release + cancel-in-progress: false + +env: + PUBLIC_BASE_URL: https://aliyun-oss.yaklang.com/chrome-extension + OSS_ENDPOINT: https://oss-accelerate.aliyuncs.com + OSS_BUCKET: yaklang + MANIFEST_MAX_VERSIONS: '10' + jobs: - build-and-publish: + publish: + name: Build, package and publish to OSS runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Test, compile and build all variants + run: pnpm verify:production + + - name: Read version + id: version + run: | + echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT" + echo "build_time=$(date +'%Y-%m-%d %H:%M:%S')" >> "$GITHUB_OUTPUT" + + - name: Package release artifacts + run: node scripts/package-release.mjs --dist=dist --public-base-url=${PUBLIC_BASE_URL} + + - name: Upload release artifacts to OSS (immutable) + run: | + node scripts/publish-oss.mjs release \ + --release-entry=dist/release-entry.json \ + --dist=dist \ + --endpoint=${OSS_ENDPOINT} \ + --bucket=${OSS_BUCKET} + env: + OSS_KEY_ID: ${{ secrets.OSS_KEY_ID }} + OSS_KEY_SECRET: ${{ secrets.OSS_KEY_SECRET }} + + - name: Fetch existing manifest + run: | + set -euo pipefail + # Query parameter busts the CDN's 5-minute manifest cache. + url="${PUBLIC_BASE_URL}/manifest.json?mirror_build=${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + code=$(curl -sS -o dist/existing-manifest.json -w '%{http_code}' --retry 4 --retry-all-errors "$url") + if [ "$code" = "200" ]; then + echo "Existing manifest fetched." + elif [ "$code" = "404" ]; then + rm -f dist/existing-manifest.json + echo "No existing manifest (first release)." + else + echo "Unexpected HTTP ${code} fetching ${url}" >&2 + exit 1 + fi + + - name: Build bounded manifest + run: | + existing="" + if [ -f dist/existing-manifest.json ]; then + existing="--existing-manifest=dist/existing-manifest.json" + fi + node scripts/build-manifest.mjs \ + --release-entry=dist/release-entry.json \ + ${existing} \ + --max-versions=${MANIFEST_MAX_VERSIONS} \ + --output=dist/manifest.json \ + --checksum-output=dist/manifest.json.sha256.txt + + - name: Publish manifest to OSS + run: | + node scripts/publish-oss.mjs manifest \ + --manifest=dist/manifest.json \ + --manifest-checksum=dist/manifest.json.sha256.txt \ + --endpoint=${OSS_ENDPOINT} \ + --bucket=${OSS_BUCKET} + env: + OSS_KEY_ID: ${{ secrets.OSS_KEY_ID }} + OSS_KEY_SECRET: ${{ secrets.OSS_KEY_SECRET }} + + - name: Upload release entry for verification + uses: actions/upload-artifact@v4 + with: + name: release-entry + path: dist/release-entry.json + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ steps.version.outputs.version }} + name: Release v${{ steps.version.outputs.version }} + body: | + Branch: ${{ github.ref_name }} + Commit: ${{ github.sha }} + Build Time: ${{ steps.version.outputs.build_time }} + Manifest: ${{ env.PUBLIC_BASE_URL }}/manifest.json + files: | + dist/${{ steps.version.outputs.version }}/* + dist/manifest.json + dist/manifest.json.sha256.txt + + - name: Write job summary + run: | + { + echo "## Release v${{ steps.version.outputs.version }}" + echo + echo "- Manifest: ${PUBLIC_BASE_URL}/manifest.json" + echo "- Commit: \`${{ github.sha }}\`" + echo + echo "| Variant | Size | SHA-256 |" + echo "| --- | --- | --- |" + jq -r '.artifacts[] | "| \(.variant) | \(.size) | \`\(.sha256)\` |"' dist/release-entry.json + } >> "$GITHUB_STEP_SUMMARY" + + verify: + name: Verify public release + needs: publish + runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Checkout code uses: actions/checkout@v4 @@ -12,72 +149,13 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '18' - cache: 'yarn' + node-version: '22' - - name: Install dependencies - run: yarn install --frozen-lockfile - - - name: Build project - run: yarn build - - - name: Get version - id: version - run: | - VERSION=$(jq -r '.version' build/manifest.json) - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "build_time=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT - - - name: Zip build artifacts - run: | - cd build - zip -r ../extension.zip . - - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Download release entry + uses: actions/download-artifact@v4 with: - tag_name: v${{ steps.version.outputs.version }} - release_name: Release v${{ steps.version.outputs.version }} - body: | - Branch: ${{ github.ref_name }} - Commit: ${{ github.sha }} - Build Time: ${{ steps.version.outputs.build_time }} - draft: false - prerelease: false - - - name: Upload Release Asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./extension.zip - asset_name: yakit-chrome-extension-v${{ steps.version.outputs.version }}.zip - asset_content_type: application/zip - - - name: Upload Extension To OSS - uses: tvrcgo/upload-to-oss@master - with: - key-id: ${{ secrets.OSS_KEY_ID }} - key-secret: ${{ secrets.OSS_KEY_SECRET }} - region: oss-accelerate - bucket: yaklang - assets: | - extension.zip:/chrome-extension/yakit-chrome-extension-v${{ steps.version.outputs.version }}.zip - - - name: Update OSS latest version file - run: echo ${{ steps.version.outputs.version }} > ./extension-version.txt - - - name: Upload Version File to OSS - uses: tvrcgo/upload-to-oss@master - with: - key-id: ${{ secrets.OSS_KEY_ID }} - key-secret: ${{ secrets.OSS_KEY_SECRET }} - region: oss-accelerate - bucket: yaklang - assets: | - ./extension-version.txt:/chrome-extension/latest-version.txt + name: release-entry + path: dist + - name: Verify from public endpoint + run: node scripts/verify-public.mjs --public-base-url=${PUBLIC_BASE_URL} --release-entry=dist/release-entry.json diff --git a/README.md b/README.md index 81d803a..928943a 100644 --- a/README.md +++ b/README.md @@ -328,6 +328,31 @@ Native Host 的构建与注册方式见 [native-host/README.md](./native-host/RE Chrome Store 构建声明 Chrome 138+。用户需要在扩展详情页开启“允许用户脚本”,页面主世界能力才能正常工作;未开启时扩展会明确报告原因,不会静默降级为直接 Eval。 +### 发布与下载 + +发布由 GitHub Actions 的 **Build and Release** workflow(手动触发)完成:执行 `verify:production` 全量校验后,将四个变体打包为不可变的版本化产物上传到 OSS,再发布机器可读的 manifest,并从公网侧回读验证。CI 在每次 push / PR 时运行同一套构建与审计。 + +**下载入口**(不要硬编码版本号): + +``` +https://aliyun-oss.yaklang.com/chrome-extension/manifest.json +``` + +manifest 的 `latest` 指向最新版本,`versions[0]` 为完整记录,最多保留 10 个历史版本。每个版本按 `variant`(`chrome-store` / `chrome-enterprise` / `firefox` / `firefox-amo`)匹配 artifact,字段包括 `url`、`filename`、`sha256`、`size` 与 `checksum_url`;manifest 自身的 SHA-256 在同目录的 `manifest.json.sha256.txt`。 + +推荐的消费流程: + +1. 拉取 `manifest.json`(缓存 5 分钟),按需选择版本与变体; +2. 下载 artifact(版本化 URL 永不变更,缓存一年)到临时文件; +3. 校验 `size` 与 `sha256`(或对比 `checksum_url` 内容)后,解压并安装; +4. 变体用途见上表“构建差异”。 + +**发布契约**: + +- 版本化产物不可变:URL 形如 `…/chrome-extension//-.zip`,重复发布同版本时内容一致则跳过、不一致则流水线报错拒绝覆盖; +- `manifest.json` 可变、缓存 5 分钟,先发布 manifest 再发布其校验文件,消费方可用校验文件识别中间态; +- 发布 job 结束前有独立的 verify job 从公网下载全部产物,复核 sha256、缓存头与 zip 内 `manifest.json` 版本。 + ## 权限与数据边界 扩展声明 `tabs`、`scripting`、`cookies`、`proxy`、`webRequest`、`webNavigation`、`debugger` 等权限,是为了在用户主动选择的目标页面上提供对应安全测试能力。`nativeMessaging` 是可选权限,仅在用户选择 Native 模式时请求。 diff --git a/package.json b/package.json index ff706b1..624c910 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "version": "0.2.0", "type": "module", + "packageManager": "pnpm@10.28.2", "scripts": { "dev": "wxt", "dev:wsl": "node scripts/dev-wsl.mjs", @@ -49,10 +50,13 @@ }, "devDependencies": { "@types/jsrsasign": "10.5.15", + "@types/node": "^22.0.0", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", "@types/ws": "^8.18.1", "@wxt-dev/module-react": "^1.2.2", + "adm-zip": "^0.5.16", + "ali-oss": "^6.21.0", "jose": "6.2.3", "jsencrypt": "3.5.4", "jsrsasign": "11.1.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c39b72..031fa66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,9 @@ importers: '@types/jsrsasign': specifier: 10.5.15 version: 10.5.15 + '@types/node': + specifier: ^22.0.0 + version: 22.14.1 '@types/react': specifier: ^19.2.17 version: 19.2.17 @@ -60,6 +63,12 @@ importers: '@wxt-dev/module-react': specifier: ^1.2.2 version: 1.2.2(vite@8.1.5(@types/node@22.14.1)(esbuild@0.27.7)(jiti@2.7.0))(wxt@0.20.27(@types/node@22.14.1)(jiti@2.7.0)(rollup@4.40.0)) + adm-zip: + specifier: ^0.5.16 + version: 0.5.16 + ali-oss: + specifier: ^6.21.0 + version: 6.23.0 jose: specifier: 6.2.3 version: 6.2.3 @@ -1079,10 +1088,22 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + address@1.2.2: + resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} + engines: {node: '>= 10.0.0'} + adm-zip@0.5.16: resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} engines: {node: '>=12.0'} + agentkeepalive@3.5.3: + resolution: {integrity: sha512-yqXL+k5rr8+ZRpOAntkaaRgWgE5o8ESAj5DyRmVTCSoZxXmqemb9Dd7T4i5UzwuERdLAJUy6XzR9zFVuf0kzkw==} + engines: {node: '>= 4.0.0'} + + ali-oss@6.23.0: + resolution: {integrity: sha512-FipRmyd16Pr/tEey/YaaQ/24Pc3HEpLM9S1DRakEuXlSLXNIJnu1oJtHM53eVYpvW3dXapSjrip3xylZUTIZVQ==} + engines: {node: '>=8'} + ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -1114,6 +1135,9 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + array-differ@4.0.0: resolution: {integrity: sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1149,6 +1173,9 @@ packages: resolution: {integrity: sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==} engines: {node: '>=20.19.0'} + bowser@1.9.4: + resolution: {integrity: sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==} + boxen@8.0.1: resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} engines: {node: '>=18'} @@ -1162,6 +1189,9 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + builtin-status-codes@3.0.0: + resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} + bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -1182,6 +1212,14 @@ packages: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + camelcase@8.0.0: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} @@ -1278,9 +1316,16 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + copy-to@2.0.1: + resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -1298,6 +1343,9 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + dateformat@2.2.0: + resolution: {integrity: sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==} + debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} @@ -1330,6 +1378,10 @@ packages: resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} engines: {node: '>=18'} + default-user-agent@1.0.0: + resolution: {integrity: sha512-bDF7bg6OSNcSwFWPu4zYKpVkJZQYVrAANMYB8bc9Szem1D0yKdm4sa/rOCs2aC9+2GMqQ7KnwtZRvDhmLF0dXw==} + engines: {node: '>= 0.10.0'} + define-lazy-prop@2.0.0: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} @@ -1351,10 +1403,18 @@ packages: destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + digest-header@1.1.0: + resolution: {integrity: sha512-glXVh42vz40yZb9Cq2oMOt70FIoWiv+vxNvdKdU8CwjLad25qHM3trLxhl9bVjdr6WaslIXhWpn0NO8T/67Qjg==} + engines: {node: '>= 8.0.0'} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -1400,15 +1460,29 @@ packages: resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + end-or-error@1.0.1: + resolution: {integrity: sha512-OclLMSug+k2A0JKuf494im25ANRBVW8qsjmwbgX7lQ8P82H21PQ1PWkoYwb9y5yMBS69BPlwtzdIFClo3+7kOQ==} + engines: {node: '>= 0.11.14'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -1428,9 +1502,21 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@2.3.1: resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} @@ -1447,6 +1533,9 @@ packages: resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==} engines: {node: '>=12'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1471,6 +1560,10 @@ packages: exsolve@1.1.0: resolution: {integrity: sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==} + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + fast-redact@3.5.0: resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} engines: {node: '>=6'} @@ -1501,6 +1594,9 @@ packages: resolution: {integrity: sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==} engines: {node: '>= 18'} + formstream@1.5.2: + resolution: {integrity: sha512-NASf0lgxC1AyKNXQIrXTEYkiX99LhCEXTkiGObXAkpBui86a4u8FjH1o2bGb3PpqI3kafC+yw4zWeK6l6VHTgg==} + fs-extra@11.3.0: resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} engines: {node: '>=14.14'} @@ -1510,6 +1606,9 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + fx-runner@1.4.0: resolution: {integrity: sha512-rci1g6U0rdTg6bAaBboP7XdRu01dzTAaKXxFf+PUqGuCv6Xu7o8NZdY1D5MvKGIjb6EdS1g3VlXOgksir1uGkg==} hasBin: true @@ -1526,9 +1625,20 @@ packages: resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-port-please@3.2.0: resolution: {integrity: sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-ready@1.0.0: + resolution: {integrity: sha512-mFXCZPJIlcYcth+N8267+mghfYN9h3EhsDa6JSnbA3Wrhh/XFpuowviFcsDeYZtKspQyWyJqfs4O6P8CHeTwzw==} + giget@2.0.0: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true @@ -1544,6 +1654,10 @@ packages: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} @@ -1556,6 +1670,14 @@ packages: growly@1.3.0: resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + hookable@6.1.1: resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} @@ -1565,6 +1687,13 @@ packages: htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} @@ -1592,6 +1721,9 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-class-hotfix@0.0.6: + resolution: {integrity: sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==} + is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -1602,6 +1734,10 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -1655,6 +1791,9 @@ packages: resolution: {integrity: sha512-wBOr+rNM4gkAZqoLRJI4myw5WzzIdQosFAAbnvfXP5z1LyzgAI3ivOKehC5KfqlQJZoihVhirgtCBj378Eg8GA==} engines: {node: '>=0.10.0'} + is-type-of@1.4.0: + resolution: {integrity: sha512-EddYllaovi5ysMLMEN7yzHEKh8A850cZ7pykrY1aNRQGn/CDjRDE9qEWbIdt7xGEVJmjBXzU/fNnC4ABTm8tEQ==} + is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -1676,6 +1815,9 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} + isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -1683,6 +1825,9 @@ packages: jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + js-base64@2.6.4: + resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1714,6 +1859,9 @@ packages: jsrsasign@11.1.3: resolution: {integrity: sha512-nPnK5D/4lv0Dwr7TlzrKtAd8JlLZwFTqTUUB3NQCbtdobcRcohGFxjbPySDVh74iWUudcCsapYT6OxoyhJLhhA==} + jstoxml@2.2.9: + resolution: {integrity: sha512-OYWlK0j+roh+eyaMROlNbS5cd5R25Y+IUpdl7cNdB8HNrkgwQzIS7L9MegxOiWNBj9dQhA/yAxiMwCC5mwNoBw==} + jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} @@ -1860,6 +2008,9 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} @@ -1887,6 +2038,18 @@ packages: marky@1.3.0: resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -1897,6 +2060,10 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + mlly@1.7.4: resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} @@ -1910,6 +2077,9 @@ packages: resolution: {integrity: sha512-I7tSVxHGPlmPN/enE3mS1aOSo6bWBfls+3HmuEeCUBCE7gWnm3cBXCBkpurzFjVRwC6Kld8lLaZ1Iv5vOcjvcQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nano-spawn@2.1.0: resolution: {integrity: sha512-yTW+2okrElHiH4fsiz/+/zc0EDo9BDDoC3iKk8dpv1GeRc9nUWzUZHx6TofMWErchhUQR8hY9/Eu1Uja9x1nqA==} engines: {node: '>=20.17'} @@ -1932,6 +2102,10 @@ packages: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} + node-hex@1.0.1: + resolution: {integrity: sha512-iwpZdvW6Umz12ICmu9IYPRxg0tOLGmU3Tq2tKetejCj3oZd7b2nUXwP3a7QA5M9glWy8wlPS1G3RwM/CdsUbdQ==} + engines: {node: '>=8.0.0'} + node-notifier@10.0.1: resolution: {integrity: sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==} @@ -1948,6 +2122,14 @@ packages: engines: {node: '>=18'} hasBin: true + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + obug@2.1.3: resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} engines: {node: '>=12.20.0'} @@ -1962,6 +2144,9 @@ packages: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} @@ -1974,10 +2159,20 @@ packages: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} + os-name@1.0.3: + resolution: {integrity: sha512-f5estLO2KN8vgtTRaILIgEGBoBrMnZ3JQ7W9TMZCnOIGwHe8TRGSpcagnWDo+Dfhd/z08k9Xe75hvciJJ8Qaew==} + engines: {node: '>=0.10.0'} + hasBin: true + os-shim@0.1.3: resolution: {integrity: sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==} engines: {node: '>= 0.4.0'} + osx-release@1.1.0: + resolution: {integrity: sha512-ixCMMwnVxyHFQLQnINhmIpWqXIfS2YOXchwQrk+OFzmo6nDjQ0E4KXAyyUh0T0MZgV4bUhkRrAbVqlE4yLVq4A==} + engines: {node: '>=0.10.0'} + hasBin: true + package-json@10.0.1: resolution: {integrity: sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==} engines: {node: '>=18'} @@ -1992,6 +2187,9 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + perfect-debounce@2.1.0: resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} @@ -2025,6 +2223,9 @@ packages: pkg-types@2.3.1: resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + playwright-core@1.61.1: resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==} engines: {node: '>=18'} @@ -2060,10 +2261,17 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + pupa@3.1.0: resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==} engines: {node: '>=12.20'} + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + quansync@0.2.10: resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} @@ -2140,6 +2348,9 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} @@ -2149,6 +2360,13 @@ packages: scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + sdk-base@2.0.1: + resolution: {integrity: sha512-eeG26wRwhtwYuKGCDM3LixCaxY27Pa/5lK4rLKhQa7HBjJ3U3Y+f81MMZQRsDw/8SC2Dao/83yJTXJ8aULuN8Q==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + semver@7.7.1: resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} @@ -2167,6 +2385,22 @@ packages: shellwords@0.1.1: resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -2219,9 +2453,20 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + std-env@4.2.0: resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + stream-http@2.8.2: + resolution: {integrity: sha512-QllfrBhqF1DPcz46WxKTs6Mz1Bpc+8Qm6vbqOpVav5odAXwbyzwnEczoWqtxrsmlO+cJqtPrp/8gWKWjaKLLlA==} + + stream-wormhole@1.1.0: + resolution: {integrity: sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==} + engines: {node: '>=4.0.0'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2270,6 +2515,13 @@ packages: stubborn-utils@1.0.2: resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thread-stream@3.2.0: resolution: {integrity: sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==} @@ -2295,6 +2547,9 @@ packages: resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} + to-arraybuffer@1.0.1: + resolution: {integrity: sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2323,6 +2578,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unescape@1.0.1: + resolution: {integrity: sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ==} + engines: {node: '>=0.10.0'} + unimport@4.2.0: resolution: {integrity: sha512-mYVtA0nmzrysnYnyb3ALMbByJ+Maosee2+WyE0puXl+Xm2bUwPorPaaeZt0ETfuroPOtG8jj1g/qeFZ6buFnag==} engines: {node: '>=18.12.0'} @@ -2343,9 +2602,22 @@ packages: resolution: {integrity: sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==} engines: {node: '>=18'} + urllib@2.44.1: + resolution: {integrity: sha512-vreOVvFizoiIz5NK9IYMgUknkriHHBVccn2VFfJhgKz6O2qwm0SgjFk4OpXFRDXpdrTx8EzM1DB0/pejrqXwPA==} + engines: {node: '>= 0.10.0'} + peerDependencies: + proxy-agent: ^5.0.0 + peerDependenciesMeta: + proxy-agent: + optional: true + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utility@1.18.0: + resolution: {integrity: sha512-PYxZDA+6QtvRvm//++aGdmKG/cI07jNwbROz0Ql+VzFV1+Z0Dy55NI4zZ7RHc9KKpBePNFwoErqIuqQv/cjiTA==} + engines: {node: '>= 0.12.0'} + uuid@14.0.1: resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} hasBin: true @@ -2486,6 +2758,10 @@ packages: resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} engines: {node: '>=18'} + win-release@1.1.1: + resolution: {integrity: sha512-iCRnKVvGxOQdsKhcQId2PXV1vV3J/sDPXKA4Oe9+Eti2nb2ESEsYHRYls/UjoUW3bIc5ZDO8dTH50A/5iVN+bw==} + engines: {node: '>=0.10.0'} + winreg@0.0.12: resolution: {integrity: sha512-typ/+JRmi7RqP1NanzFULK36vczznSNN8kWVA9vIqXyv8GhghUlwhGp1Xj3Nms1FsPcNnsQrJOR10N58/nQ9hQ==} @@ -2501,6 +2777,9 @@ packages: resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.21.1: resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==} engines: {node: '>=10.0.0'} @@ -2539,6 +2818,10 @@ packages: resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} engines: {node: '>=4.0'} + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -3293,8 +3576,45 @@ snapshots: acorn@8.17.0: {} + address@1.2.2: {} + adm-zip@0.5.16: {} + agentkeepalive@3.5.3: + dependencies: + humanize-ms: 1.2.1 + + ali-oss@6.23.0: + dependencies: + address: 1.2.2 + agentkeepalive: 3.5.3 + bowser: 1.9.4 + copy-to: 2.0.1 + dateformat: 2.2.0 + debug: 4.3.7 + destroy: 1.2.0 + end-or-error: 1.0.1 + get-ready: 1.0.0 + humanize-ms: 1.2.1 + is-type-of: 1.4.0 + js-base64: 2.6.4 + jstoxml: 2.2.9 + lodash: 4.18.1 + merge-descriptors: 1.0.3 + mime: 2.6.0 + platform: 1.3.6 + pump: 3.0.4 + qs: 6.15.3 + sdk-base: 2.0.1 + stream-http: 2.8.2 + stream-wormhole: 1.1.0 + urllib: 2.44.1 + utility: 1.18.0 + xml2js: 0.6.2 + transitivePeerDependencies: + - proxy-agent + - supports-color + ansi-align@3.0.1: dependencies: string-width: 4.2.3 @@ -3317,6 +3637,8 @@ snapshots: ansi-styles@6.2.3: {} + any-promise@1.3.0: {} + array-differ@4.0.0: {} array-union@3.0.1: {} @@ -3342,6 +3664,8 @@ snapshots: boolbase@2.0.0: {} + bowser@1.9.4: {} + boxen@8.0.1: dependencies: ansi-align: 3.0.1 @@ -3362,6 +3686,8 @@ snapshots: buffer-from@1.1.2: {} + builtin-status-codes@3.0.0: {} + bundle-name@4.1.0: dependencies: run-applescript: 7.0.0 @@ -3387,6 +3713,16 @@ snapshots: cac@7.0.0: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + camelcase@8.0.0: {} chai@6.2.2: {} @@ -3478,8 +3814,12 @@ snapshots: consola@3.4.2: {} + content-type@1.0.5: {} + convert-source-map@2.0.0: {} + copy-to@2.0.1: {} + core-util-is@1.0.3: {} css-select@7.0.0: @@ -3496,6 +3836,8 @@ snapshots: csstype@3.2.3: {} + dateformat@2.2.0: {} + debounce@1.2.1: {} debug@2.6.9: @@ -3515,6 +3857,10 @@ snapshots: bundle-name: 4.1.0 default-browser-id: 5.0.0 + default-user-agent@1.0.0: + dependencies: + os-name: 1.0.3 + define-lazy-prop@2.0.0: {} define-lazy-prop@3.0.0: {} @@ -3527,8 +3873,12 @@ snapshots: destr@2.0.5: {} + destroy@1.2.0: {} + detect-libc@2.1.2: {} + digest-header@1.1.0: {} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -3577,14 +3927,28 @@ snapshots: dotenv@17.4.2: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 + ee-first@1.1.1: {} + emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + end-or-error@1.0.1: {} + entities@4.5.0: {} entities@7.0.1: {} @@ -3597,8 +3961,16 @@ snapshots: dependencies: is-arrayish: 0.2.1 + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + es-module-lexer@2.3.1: {} + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + es6-error@4.1.1: {} esbuild@0.27.7: @@ -3634,6 +4006,8 @@ snapshots: escape-goat@4.0.0: {} + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} @@ -3650,6 +4024,10 @@ snapshots: exsolve@1.1.0: {} + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + fast-redact@3.5.0: {} fdir@6.5.0(picomatch@4.0.5): @@ -3670,6 +4048,13 @@ snapshots: formdata-node@6.0.3: {} + formstream@1.5.2: + dependencies: + destroy: 1.2.0 + mime: 2.6.0 + node-hex: 1.0.1 + pause-stream: 0.0.11 + fs-extra@11.3.0: dependencies: graceful-fs: 4.2.11 @@ -3679,6 +4064,8 @@ snapshots: fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + fx-runner@1.4.0: dependencies: commander: 2.9.0 @@ -3694,8 +4081,28 @@ snapshots: get-east-asian-width@1.6.0: {} + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + get-port-please@3.2.0: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-ready@1.0.0: {} + giget@2.0.0: dependencies: citty: 0.1.6 @@ -3713,6 +4120,8 @@ snapshots: dependencies: ini: 4.1.1 + gopd@1.2.0: {} + graceful-fs@4.2.10: {} graceful-fs@4.2.11: {} @@ -3721,6 +4130,12 @@ snapshots: growly@1.3.0: {} + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + hookable@6.1.1: {} html-escaper@3.0.3: {} @@ -3732,6 +4147,14 @@ snapshots: domutils: 3.2.2 entities: 7.0.1 + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + immediate@3.0.6: {} import-meta-resolve@4.2.0: {} @@ -3750,10 +4173,14 @@ snapshots: is-arrayish@0.2.1: {} + is-class-hotfix@0.0.6: {} + is-docker@2.2.1: {} is-docker@3.0.0: {} + is-extendable@0.1.1: {} + is-fullwidth-code-point@3.0.0: {} is-fullwidth-code-point@5.0.0: @@ -3791,6 +4218,12 @@ snapshots: is-relative@0.1.3: {} + is-type-of@1.4.0: + dependencies: + core-util-is: 1.0.3 + is-class-hotfix: 0.0.6 + isstream: 0.1.2 + is-wsl@2.2.0: dependencies: is-docker: 2.2.1 @@ -3807,10 +4240,14 @@ snapshots: isobject@3.0.1: {} + isstream@0.1.2: {} + jiti@2.7.0: {} jose@6.2.3: {} + js-base64@2.6.4: {} + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -3844,6 +4281,8 @@ snapshots: jsrsasign@11.1.3: {} + jstoxml@2.2.9: {} + jszip@3.10.1: dependencies: lie: 3.3.0 @@ -3970,6 +4409,8 @@ snapshots: lodash.once@4.1.1: {} + lodash@4.18.1: {} + log-update@6.1.0: dependencies: ansi-escapes: 7.0.0 @@ -4002,6 +4443,12 @@ snapshots: marky@1.3.0: {} + math-intrinsics@1.1.0: {} + + merge-descriptors@1.0.3: {} + + mime@2.6.0: {} + mimic-function@5.0.1: {} minimatch@3.1.2: @@ -4010,6 +4457,10 @@ snapshots: minimist@1.2.8: {} + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + mlly@1.7.4: dependencies: acorn: 8.17.0 @@ -4028,6 +4479,12 @@ snapshots: array-union: 3.0.1 minimatch: 3.1.2 + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nano-spawn@2.1.0: {} nanoid@3.3.16: {} @@ -4042,6 +4499,8 @@ snapshots: node-forge@1.4.0: {} + node-hex@1.0.1: {} + node-notifier@10.0.1: dependencies: growly: 1.3.0 @@ -4063,6 +4522,10 @@ snapshots: pathe: 2.0.3 tinyexec: 1.2.4 + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + obug@2.1.3: {} ofetch@1.5.1: @@ -4075,6 +4538,10 @@ snapshots: on-exit-leak-free@2.1.2: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -4094,8 +4561,17 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 + os-name@1.0.3: + dependencies: + osx-release: 1.1.0 + win-release: 1.1.1 + os-shim@0.1.3: {} + osx-release@1.1.0: + dependencies: + minimist: 1.2.8 + package-json@10.0.1: dependencies: ky: 1.14.3 @@ -4115,6 +4591,10 @@ snapshots: pathe@2.0.3: {} + pause-stream@0.0.11: + dependencies: + through: 2.3.8 + perfect-debounce@2.1.0: {} picocolors@1.1.1: {} @@ -4161,6 +4641,8 @@ snapshots: exsolve: 1.1.0 pathe: 2.0.3 + platform@1.3.6: {} + playwright-core@1.61.1: {} postcss@8.5.19: @@ -4198,10 +4680,20 @@ snapshots: ofetch: 1.5.1 zod: 4.4.3 + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + pupa@3.1.0: dependencies: escape-goat: 4.0.0 + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + quansync@0.2.10: {} quick-format-unescaped@4.0.4: {} @@ -4312,12 +4804,20 @@ snapshots: safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} + sax@1.4.1: {} scheduler@0.27.0: {} scule@1.3.0: {} + sdk-base@2.0.1: + dependencies: + get-ready: 1.0.0 + + semver@5.7.2: {} + semver@7.7.1: {} set-value@4.1.0: @@ -4331,6 +4831,34 @@ snapshots: shellwords@0.1.1: {} + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -4379,8 +4907,20 @@ snapshots: stackback@0.0.2: {} + statuses@1.5.0: {} + std-env@4.2.0: {} + stream-http@2.8.2: + dependencies: + builtin-status-codes: 3.0.0 + inherits: 2.0.4 + readable-stream: 2.3.8 + to-arraybuffer: 1.0.1 + xtend: 4.0.2 + + stream-wormhole@1.1.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -4430,6 +4970,14 @@ snapshots: stubborn-utils@1.0.2: {} + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + thread-stream@3.2.0: dependencies: real-require: 0.2.0 @@ -4449,6 +4997,8 @@ snapshots: tmp@0.2.5: {} + to-arraybuffer@1.0.1: {} + tslib@2.8.1: {} type-fest@3.13.1: {} @@ -4486,6 +5036,10 @@ snapshots: undici-types@6.21.0: {} + unescape@1.0.1: + dependencies: + extend-shallow: 2.0.1 + unimport@4.2.0: dependencies: acorn: 8.17.0 @@ -4529,8 +5083,31 @@ snapshots: semver: 7.7.1 xdg-basedir: 5.1.0 + urllib@2.44.1: + dependencies: + any-promise: 1.3.0 + content-type: 1.0.5 + default-user-agent: 1.0.0 + digest-header: 1.1.0 + ee-first: 1.1.1 + formstream: 1.5.2 + humanize-ms: 1.2.1 + iconv-lite: 0.6.3 + pump: 3.0.4 + qs: 6.15.3 + statuses: 1.5.0 + utility: 1.18.0 + util-deprecate@1.0.2: {} + utility@1.18.0: + dependencies: + copy-to: 2.0.1 + escape-html: 1.0.3 + mkdirp: 0.5.6 + mz: 2.7.0 + unescape: 1.0.1 + uuid@14.0.1: {} uuid@8.3.2: {} @@ -4654,6 +5231,10 @@ snapshots: dependencies: string-width: 7.2.0 + win-release@1.1.1: + dependencies: + semver: 5.7.2 + winreg@0.0.12: {} wrap-ansi@10.0.0: @@ -4674,6 +5255,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.0 + wrappy@1.0.2: {} + ws@8.21.1: {} wsl-utils@0.3.1: @@ -4750,6 +5333,8 @@ snapshots: xmlbuilder@11.0.1: {} + xtend@4.0.2: {} + y18n@5.0.8: {} yargs-parser@21.1.1: {} diff --git a/scripts/build-manifest.mjs b/scripts/build-manifest.mjs new file mode 100644 index 0000000..84c7f5c --- /dev/null +++ b/scripts/build-manifest.mjs @@ -0,0 +1,120 @@ +#!/usr/bin/env node +/** + * Merges the freshly packaged release (dist/release-entry.json) into the + * public manifest and writes manifest.json + manifest.json.sha256.txt. + * + * The manifest is the single entry point consumers read: `latest` plus a + * bounded `versions[]` history. Artifact objects are immutable and their URLs + * are never rewritten; only this manifest moves. + * + * Usage: + * node scripts/build-manifest.mjs --release-entry=dist/release-entry.json \ + * [--existing-manifest=dist/existing-manifest.json] [--max-versions=10] \ + * --output=dist/manifest.json --checksum-output=dist/manifest.json.sha256.txt + */ +import { createHash } from 'node:crypto'; +import { readFile, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; + +const root = resolve(import.meta.dirname, '..'); + +function parseArgs(argv) { + const out = {}; + for (const arg of argv) { + if (!arg.startsWith('--')) throw new Error(`unexpected argument: ${arg}`); + const eq = arg.indexOf('='); + const key = eq === -1 ? arg.slice(2) : arg.slice(2, eq); + out[key] = eq === -1 ? true : arg.slice(eq + 1); + } + return out; +} + +function artifactFingerprint(artifacts) { + return artifacts.map((a) => `${a.variant}:${a.sha256}`).sort().join('|'); +} + +function toVersionEntry(entry) { + return { + version: entry.version, + published_at: entry.built_at, + commit: entry.commit ?? null, + artifacts: entry.artifacts.map((a) => ({ + variant: a.variant, + browser: a.browser, + mode: a.mode, + filename: a.filename, + url: a.url, + sha256: a.sha256, + size: a.size, + checksum_url: a.checksum_url, + })), + }; +} + +function validate(manifest) { + if (!Array.isArray(manifest.versions) || manifest.versions.length === 0) { + throw new Error('manifest must contain at least one version'); + } + if (manifest.latest !== manifest.versions[0].version) { + throw new Error(`manifest.latest (${manifest.latest}) must equal versions[0].version (${manifest.versions[0].version})`); + } + const seen = new Set(); + for (const versionEntry of manifest.versions) { + if (seen.has(versionEntry.version)) throw new Error(`duplicate version in manifest: ${versionEntry.version}`); + seen.add(versionEntry.version); + if (!Array.isArray(versionEntry.artifacts) || versionEntry.artifacts.length === 0) { + throw new Error(`version ${versionEntry.version} has no artifacts`); + } + const variants = new Set(); + for (const artifact of versionEntry.artifacts) { + if (variants.has(artifact.variant)) throw new Error(`duplicate variant ${artifact.variant} in version ${versionEntry.version}`); + variants.add(artifact.variant); + if (!/^[0-9a-f]{64}$/.test(artifact.sha256)) throw new Error(`artifact ${artifact.filename}: bad sha256`); + if (!Number.isInteger(artifact.size) || artifact.size <= 0) throw new Error(`artifact ${artifact.filename}: bad size`); + if (!/^https?:\/\//.test(artifact.url)) throw new Error(`artifact ${artifact.filename}: url must be absolute`); + } + } +} + +const args = parseArgs(process.argv.slice(2)); +if (!args['release-entry']) throw new Error('--release-entry is required'); +if (!args.output) throw new Error('--output is required'); +if (!args['checksum-output']) throw new Error('--checksum-output is required'); + +const entry = JSON.parse(await readFile(resolve(root, String(args['release-entry'])), 'utf8')); +const maxVersions = Number.parseInt(String(args['max-versions'] ?? '10'), 10); +if (!Number.isInteger(maxVersions) || maxVersions < 1) throw new Error('--max-versions must be a positive integer'); + +let versions = []; +if (args['existing-manifest']) { + try { + const existing = JSON.parse(await readFile(resolve(root, String(args['existing-manifest'])), 'utf8')); + versions = Array.isArray(existing.versions) ? existing.versions : []; + } catch (err) { + if (err?.code !== 'ENOENT') throw err; + console.log('existing manifest not found; starting a fresh history'); + } +} + +const newEntry = toVersionEntry(entry); +const idx = versions.findIndex((v) => v.version === entry.version); +if (idx >= 0 && artifactFingerprint(versions[idx].artifacts) === artifactFingerprint(entry.artifacts)) { + // Idempotent rerun: keep the original entry (published_at stays stable). + console.log(`version ${entry.version} already in manifest with identical artifacts; kept as-is`); +} else { + if (idx >= 0) { + versions.splice(idx, 1); + console.log(`version ${entry.version} re-published with different artifacts; replaced entry`); + } + versions.unshift(newEntry); +} +versions = versions.slice(0, maxVersions); + +const manifest = { latest: versions[0].version, updated_at: new Date().toISOString(), versions }; +validate(manifest); + +const bytes = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, 'utf8'); +await writeFile(resolve(root, String(args.output)), bytes); +const sha256 = createHash('sha256').update(bytes).digest('hex'); +await writeFile(resolve(root, String(args['checksum-output'])), `${sha256} manifest.json\n`); +console.log(`manifest written: ${args.output} (latest=${manifest.latest}, ${versions.length} version(s) retained)`); diff --git a/scripts/package-release.mjs b/scripts/package-release.mjs new file mode 100644 index 0000000..794f50d --- /dev/null +++ b/scripts/package-release.mjs @@ -0,0 +1,117 @@ +#!/usr/bin/env node +/** + * Packages the release variants from .output into dist// and writes + * dist/release-entry.json recording filename/size/sha256/url for every + * artifact, plus per-artifact .sha256.txt checksum files. + * + * The variant table must stay in sync with `verify:production` (package.json) + * and scripts/audit-build.mjs — those define the published surface. + * + * Usage: + * node scripts/package-release.mjs --public-base-url=https://aliyun-oss.yaklang.com/chrome-extension [--dist=dist] + */ +import { execFile } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { createReadStream } from 'node:fs'; +import { access } from 'node:fs/promises'; +import { mkdir, readFile, stat, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { pipeline } from 'node:stream/promises'; +import { promisify } from 'node:util'; +import AdmZip from 'adm-zip'; + +const execFileAsync = promisify(execFile); +const root = resolve(import.meta.dirname, '..'); + +const VARIANTS = [ + { variant: 'chrome-store', browser: 'chrome', mode: 'store', dir: '.output/chrome-mv3-store' }, + { variant: 'chrome-enterprise', browser: 'chrome', mode: 'enterprise', dir: '.output/chrome-mv3-enterprise' }, + { variant: 'firefox', browser: 'firefox', mode: 'production', dir: '.output/firefox-mv2' }, + { variant: 'firefox-amo', browser: 'firefox', mode: 'store', dir: '.output/firefox-mv3-store' }, +]; + +function parseArgs(argv) { + const out = {}; + for (const arg of argv) { + if (!arg.startsWith('--')) throw new Error(`unexpected argument: ${arg}`); + const eq = arg.indexOf('='); + const key = eq === -1 ? arg.slice(2) : arg.slice(2, eq); + out[key] = eq === -1 ? true : arg.slice(eq + 1); + } + return out; +} + +async function exists(path) { + try { + await access(path); + return true; + } catch { + return false; + } +} + +async function sha256File(path) { + const hash = createHash('sha256'); + await pipeline(createReadStream(path), hash); + return hash.digest('hex'); +} + +const args = parseArgs(process.argv.slice(2)); +if (!args['public-base-url']) { + throw new Error('--public-base-url is required (e.g. https://aliyun-oss.yaklang.com/chrome-extension)'); +} +const baseUrl = String(args['public-base-url']).replace(/\/+$/, ''); +const distDir = resolve(root, String(args.dist ?? 'dist')); + +const pkg = JSON.parse(await readFile(resolve(root, 'package.json'), 'utf8')); +const { version } = pkg; + +let commit = null; +try { + const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: root }); + commit = stdout.trim(); +} catch { + // Not fatal: local runs outside a git worktree still package fine. +} + +const versionDir = resolve(distDir, version); +await mkdir(versionDir, { recursive: true }); + +const artifacts = []; +for (const target of VARIANTS) { + const outputDir = resolve(root, target.dir); + if (!(await exists(resolve(outputDir, 'manifest.json')))) { + throw new Error(`${target.variant}: ${target.dir}/manifest.json missing — run the build first (pnpm verify:production)`); + } + const builtManifest = JSON.parse(await readFile(resolve(outputDir, 'manifest.json'), 'utf8')); + if (builtManifest.version !== version) { + throw new Error(`${target.variant}: built manifest version ${builtManifest.version} != package.json version ${version}`); + } + + const filename = `${target.variant}-${version}.zip`; + const zipPath = resolve(versionDir, filename); + // Entry paths are relative to the output dir so manifest.json sits at the + // zip root, which is what browsers expect from a sideloaded extension. + const zip = new AdmZip(); + zip.addLocalFolder(outputDir); + await zip.writeZipPromise(zipPath); + const sha256 = await sha256File(zipPath); + const size = (await stat(zipPath)).size; + await writeFile(resolve(versionDir, `${filename}.sha256.txt`), `${sha256} ${filename}\n`); + + artifacts.push({ + variant: target.variant, + browser: target.browser, + mode: target.mode, + filename, + url: `${baseUrl}/${version}/${filename}`, + sha256, + size, + checksum_url: `${baseUrl}/${version}/${filename}.sha256.txt`, + }); + console.log(`packaged ${filename} (${size} bytes, sha256 ${sha256.slice(0, 12)}…)`); +} + +const entry = { version, commit, built_at: new Date().toISOString(), artifacts }; +await writeFile(resolve(distDir, 'release-entry.json'), `${JSON.stringify(entry, null, 2)}\n`); +console.log(`release entry written: ${resolve(distDir, 'release-entry.json').slice(root.length + 1)} (version ${version})`); diff --git a/scripts/publish-oss.mjs b/scripts/publish-oss.mjs new file mode 100644 index 0000000..6330139 --- /dev/null +++ b/scripts/publish-oss.mjs @@ -0,0 +1,148 @@ +#!/usr/bin/env node +/** + * Publishes release artifacts and the manifest to Aliyun OSS. + * + * The contract mirrors yaklang/browser-binaries-mirror: + * - versioned artifacts are immutable: one-year immutable cache headers, + * sha256 user meta, x-oss-forbid-overwrite on upload; an existing object + * with a different sha256 is a hard error, an identical one is skipped + * - manifest.json is mutable: five-minute cache; it is published first and + * its checksum second, so consumers can always detect a torn publish by + * verifying the checksum file + * + * Credentials come from OSS_KEY_ID / OSS_KEY_SECRET (org-level secrets). + * + * Usage: + * OSS_KEY_ID=… OSS_KEY_SECRET=… node scripts/publish-oss.mjs release \ + * --release-entry=dist/release-entry.json [--dist=dist] + * [--endpoint=https://oss-accelerate.aliyuncs.com] [--bucket=yaklang] [--prefix=chrome-extension] + * OSS_KEY_ID=… OSS_KEY_SECRET=… node scripts/publish-oss.mjs manifest \ + * --manifest=dist/manifest.json --manifest-checksum=dist/manifest.json.sha256.txt [endpoint/bucket/prefix] + */ +import { createHash } from 'node:crypto'; +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import OSSModule from 'ali-oss'; + +const OSS = OSSModule.default ?? OSSModule; +const root = resolve(import.meta.dirname, '..'); + +const ARTIFACT_CACHE = 'public, max-age=31536000, immutable'; +const MANIFEST_CACHE = 'public, max-age=300, must-revalidate'; + +function parseArgs(argv) { + const out = {}; + for (const arg of argv) { + if (!arg.startsWith('--')) throw new Error(`unexpected argument: ${arg}`); + const eq = arg.indexOf('='); + const key = eq === -1 ? arg.slice(2) : arg.slice(2, eq); + out[key] = eq === -1 ? true : arg.slice(eq + 1); + } + return out; +} + +const [subcommand, ...rest] = process.argv.slice(2); +const args = parseArgs(rest); +const endpoint = String(args.endpoint ?? 'https://oss-accelerate.aliyuncs.com'); +const bucket = String(args.bucket ?? 'yaklang'); +const prefix = String(args.prefix ?? 'chrome-extension').replace(/^\/+|\/+$/g, ''); + +const accessKeyId = process.env.OSS_KEY_ID; +const accessKeySecret = process.env.OSS_KEY_SECRET; +if (!accessKeyId || !accessKeySecret) { + throw new Error('OSS_KEY_ID and OSS_KEY_SECRET must be set in the environment'); +} +if (subcommand !== 'release' && subcommand !== 'manifest') { + throw new Error(`unknown subcommand: ${subcommand ?? '(none)'} — expected "release" or "manifest"`); +} + +const client = new OSS({ accessKeyId, accessKeySecret, bucket, endpoint, secure: true }); + +const sha256 = (buffer) => createHash('sha256').update(buffer).digest('hex'); + +async function headObject(key) { + try { + const res = await client.head(key); + return { + size: Number(res.headers['content-length']), + sha256: res.headers['x-oss-meta-sha256'] ?? null, + }; + } catch (err) { + if (err && (err.status === 404 || err.code === 'NoSuchKey')) return null; + throw err; + } +} + +async function putObject(key, buffer, { mime, cacheControl, forbidOverwrite }) { + const digest = sha256(buffer); + await client.put(key, buffer, { + mime, + headers: { + 'Cache-Control': cacheControl, + ...(forbidOverwrite ? { 'x-oss-forbid-overwrite': 'true' } : {}), + }, + meta: { sha256: digest }, + }); + const head = await headObject(key); + if (!head) throw new Error(`upload verification failed, object missing: oss://${bucket}/${key}`); + if (head.size !== buffer.length || head.sha256 !== digest) { + throw new Error(`upload verification failed: oss://${bucket}/${key} (size ${head.size}/${buffer.length}, sha256 ${head.sha256}/${digest})`); + } +} + +async function putImmutable(key, buffer, mime) { + const digest = sha256(buffer); + const existing = await headObject(key); + if (existing) { + if (existing.size === buffer.length && existing.sha256 === digest) { + console.log(`skip (identical object already published): oss://${bucket}/${key}`); + return; + } + throw new Error( + `refusing to overwrite non-matching immutable object: oss://${bucket}/${key} ` + + `(remote size=${existing.size} sha256=${existing.sha256 ?? 'unknown'}, local size=${buffer.length} sha256=${digest})`, + ); + } + await putObject(key, buffer, { mime, cacheControl: ARTIFACT_CACHE, forbidOverwrite: true }); + console.log(`uploaded: oss://${bucket}/${key} (${buffer.length} bytes)`); +} + +async function putMutable(key, buffer, mime) { + await putObject(key, buffer, { mime, cacheControl: MANIFEST_CACHE, forbidOverwrite: false }); + console.log(`published: oss://${bucket}/${key}`); +} + +async function runRelease() { + if (!args['release-entry']) throw new Error('release subcommand requires --release-entry'); + const entry = JSON.parse(await readFile(resolve(root, String(args['release-entry'])), 'utf8')); + const versionDir = resolve(root, String(args.dist ?? 'dist'), entry.version); + for (const artifact of entry.artifacts) { + const zip = await readFile(resolve(versionDir, artifact.filename)); + const digest = sha256(zip); + if (digest !== artifact.sha256) { + throw new Error(`${artifact.filename}: on-disk sha256 ${digest} != release entry ${artifact.sha256}`); + } + await putImmutable(`${prefix}/${entry.version}/${artifact.filename}`, zip, 'application/zip'); + const checksum = await readFile(resolve(versionDir, `${artifact.filename}.sha256.txt`)); + await putImmutable(`${prefix}/${entry.version}/${artifact.filename}.sha256.txt`, checksum, 'text/plain; charset=utf-8'); + } +} + +async function runManifest() { + if (!args.manifest) throw new Error('manifest subcommand requires --manifest'); + if (!args['manifest-checksum']) throw new Error('manifest subcommand requires --manifest-checksum'); + const manifest = await readFile(resolve(root, String(args.manifest))); + const checksum = await readFile(resolve(root, String(args['manifest-checksum'])), 'utf8'); + const expected = `${sha256(manifest)} manifest.json\n`; + if (checksum !== expected) { + throw new Error('manifest checksum file does not match manifest.json content'); + } + await putMutable(`${prefix}/manifest.json`, manifest, 'application/json; charset=utf-8'); + await putMutable(`${prefix}/manifest.json.sha256.txt`, Buffer.from(checksum, 'utf8'), 'text/plain; charset=utf-8'); +} + +if (subcommand === 'release') { + await runRelease(); +} else { + await runManifest(); +} diff --git a/scripts/verify-public.mjs b/scripts/verify-public.mjs new file mode 100644 index 0000000..c1a51a5 --- /dev/null +++ b/scripts/verify-public.mjs @@ -0,0 +1,100 @@ +#!/usr/bin/env node +/** + * Verifies a freshly published release from the public endpoint: artifact + * bytes and checksum files, cache headers, manifest consistency, and zip + * layout (manifest.json at the zip root with the expected version). + * + * Usage: + * node scripts/verify-public.mjs --public-base-url=https://aliyun-oss.yaklang.com/chrome-extension \ + * --release-entry=dist/release-entry.json + */ +import { createHash } from 'node:crypto'; +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import AdmZip from 'adm-zip'; +const root = resolve(import.meta.dirname, '..'); + +function parseArgs(argv) { + const out = {}; + for (const arg of argv) { + if (!arg.startsWith('--')) throw new Error(`unexpected argument: ${arg}`); + const eq = arg.indexOf('='); + const key = eq === -1 ? arg.slice(2) : arg.slice(2, eq); + out[key] = eq === -1 ? true : arg.slice(eq + 1); + } + return out; +} + +function assert(condition, message) { + if (!condition) throw new Error(message); +} + +const sha256 = (buffer) => createHash('sha256').update(buffer).digest('hex'); + +const args = parseArgs(process.argv.slice(2)); +if (!args['public-base-url']) throw new Error('--public-base-url is required'); +if (!args['release-entry']) throw new Error('--release-entry is required'); +const baseUrl = String(args['public-base-url']).replace(/\/+$/, ''); + +// Cache-busting query parameter: the manifest may be served from a 5-minute +// CDN cache, and we must observe the state right after this publish. +const bust = `verify=${Date.now()}`; + +async function fetchOk(url) { + const res = await fetch(`${url}?${bust}`); + if (!res.ok) throw new Error(`GET ${url} -> ${res.status}`); + return res; +} + +const entry = JSON.parse(await readFile(resolve(root, String(args['release-entry'])), 'utf8')); + +for (const artifact of entry.artifacts) { + const res = await fetchOk(artifact.url); + const contentType = res.headers.get('content-type') ?? ''; + const cacheControl = res.headers.get('cache-control') ?? ''; + assert(contentType.startsWith('application/'), `${artifact.filename}: unexpected content-type "${contentType}"`); + assert(cacheControl.includes('max-age=31536000') && cacheControl.includes('immutable'), + `${artifact.filename}: unexpected cache-control "${cacheControl}" for an immutable artifact`); + const body = Buffer.from(await res.arrayBuffer()); + assert(body.length === artifact.size, `${artifact.filename}: content-length ${body.length} != expected ${artifact.size}`); + assert(sha256(body) === artifact.sha256, `${artifact.filename}: sha256 mismatch`); + + const checksumRes = await fetchOk(artifact.checksum_url); + assert((await checksumRes.text()) === `${artifact.sha256} ${artifact.filename}\n`, + `${artifact.filename}: checksum file content mismatch`); + + const zip = new AdmZip(body); + const innerEntry = zip.getEntry('manifest.json'); + assert(innerEntry, `${artifact.filename}: manifest.json missing at zip root`); + const innerManifest = JSON.parse(zip.readAsText(innerEntry)); + assert(innerManifest.version === entry.version, + `${artifact.filename}: zip manifest version ${innerManifest.version} != ${entry.version}`); + const backgroundEntry = zip.getEntry('background.js'); + assert(backgroundEntry && backgroundEntry.getData().length > 0, + `${artifact.filename}: background.js missing or empty in zip`); + + console.log(`verified ${artifact.filename} (${artifact.size} bytes)`); +} + +const manifestRes = await fetchOk(`${baseUrl}/manifest.json`); +const manifestBytes = Buffer.from(await manifestRes.arrayBuffer()); +const manifestCache = manifestRes.headers.get('cache-control') ?? ''; +assert(manifestCache.includes('max-age=300'), `manifest.json: unexpected cache-control "${manifestCache}"`); +const manifest = JSON.parse(manifestBytes.toString('utf8')); +assert(manifest.latest === entry.version, `manifest.latest ${manifest.latest} != ${entry.version}`); +const versionEntry = manifest.versions.find((v) => v.version === entry.version); +assert(versionEntry, `manifest has no entry for version ${entry.version}`); +assert(versionEntry.artifacts.length === entry.artifacts.length, + `manifest artifacts count ${versionEntry.artifacts.length} != ${entry.artifacts.length}`); +for (const artifact of entry.artifacts) { + const remote = versionEntry.artifacts.find((a) => a.variant === artifact.variant); + assert(remote, `manifest missing variant ${artifact.variant} for version ${entry.version}`); + assert(remote.sha256 === artifact.sha256, `manifest sha256 mismatch for variant ${artifact.variant}`); + assert(remote.url === artifact.url, `manifest url mismatch for variant ${artifact.variant}`); +} + +const checksumRes = await fetchOk(`${baseUrl}/manifest.json.sha256.txt`); +assert((await checksumRes.text()) === `${sha256(manifestBytes)} manifest.json\n`, + 'manifest.json.sha256.txt does not match the served manifest'); + +console.log(`manifest verified: latest=${manifest.latest}, ${manifest.versions.length} version(s) in history`); diff --git a/wxt.config.ts b/wxt.config.ts index a609acc..38eb2b2 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -1,5 +1,11 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; import { defineConfig } from 'wxt'; +// package.json is the single source of truth for the version; release +// packaging asserts the built manifest matches it. +const { version } = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf8')); + // See https://wxt.dev/api/config.html export default defineConfig({ srcDir: 'src', @@ -12,7 +18,7 @@ export default defineConfig({ manifest: ({ mode, browser }) => ({ name: 'Yakit Browser Agent', description: 'Yakit 浏览器安全测试工具与 AI 上下文桥接', - version: '0.2.0', + version, action: { default_title: 'Yakit Browser Agent', },