name: Plugin Registry # 插件市场:plugins/registry/.toml 指向作者仓库的固定 commit。 # - check:PR 上运行作者的代码(测试、pnpm 构建),只读权限、不读 secret。 # 检查脚本与 ab_sdk 取自 base 分支,PR 不能改掉检查规则。 # - plan / build:合并后。build 每个插件一个 job(会运行作者的前端构建,没有密钥), # 一个插件的构建篡改不到别的插件。 # - sign:唯一读 PLUGIN_SIGNING_KEY 的 job,不运行作者代码。它自己下载复用的 zip # 并校验 sha256,核对每个新 zip 的 id 与来源,再签名发布。 on: pull_request: branches: ["main"] paths: ["plugins/**"] push: branches: ["main"] paths: ["plugins/**"] workflow_dispatch: inputs: rebuild: description: "忽略已发布的 zip,全部重新打包(打包逻辑变更后使用)" type: boolean default: false permissions: contents: read concurrency: group: plugin-registry-${{ github.event_name == 'pull_request' && github.ref || 'publish' }} cancel-in-progress: false env: REBUILD: ${{ inputs.rebuild && 'true' || '' }} jobs: check: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.base.sha }} fetch-depth: 0 persist-credentials: false - name: Fetch PR head run: | git fetch -q origin "refs/pull/${{ github.event.number }}/head" # 固定为 SHA:作者代码在检查过程中可以改写 .git/FETCH_HEAD echo "HEAD_SHA=$(git rev-parse FETCH_HEAD)" >> "$GITHUB_ENV" # 引入本脚本的那个 PR 时 base 上还没有它,只能用 PR 自己的版本 if [ ! -f scripts/plugin_registry.py ]; then echo "::warning::base 分支没有 scripts/plugin_registry.py,使用 PR 中的版本" git checkout "$(git rev-parse FETCH_HEAD)" -- scripts/plugin_registry.py fi - &python uses: actions/setup-python@v5 with: python-version: "3.14" - &uv uses: astral-sh/setup-uv@v10.3.0 with: version: "0.13.x" - &pnpm uses: pnpm/action-setup@v6 with: version: 12.14.0 run_install: false - &node uses: actions/setup-node@v4 with: node-version: 24 - &deps name: Install backend run: mkdir -p backend/config && cd backend && uv sync --group dev - &catalog name: Download published catalog env: GH_TOKEN: ${{ github.token }} run: gh release download plugins -p catalog.json -p catalog.json.sig -D old || echo "release plugins 还不存在" - name: Check changed entries run: | set -o pipefail uv run --project backend python scripts/plugin_registry.py check \ --base "${{ github.event.pull_request.base.sha }}" --head "$HEAD_SHA" \ --catalog old/catalog.json --out dist | tee -a "$GITHUB_STEP_SUMMARY" plan: # 带着签名密钥的发布只从 main 运行 if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest outputs: build: ${{ steps.plan.outputs.build }} steps: - uses: actions/checkout@v4 with: persist-credentials: false - *python - *uv - *deps - *catalog - id: plan run: | uv run --project backend python scripts/plugin_registry.py plan --catalog old/catalog.json ${REBUILD:+--rebuild} > plan.json cat plan.json echo "build=$(jq -c .build plan.json)" >> "$GITHUB_OUTPUT" build: needs: plan if: needs.plan.outputs.build != '[]' runs-on: ubuntu-latest strategy: fail-fast: true matrix: id: ${{ fromJSON(needs.plan.outputs.build) }} steps: - uses: actions/checkout@v4 with: persist-credentials: false - *python - *uv - *pnpm - *node - *deps - run: uv run --project backend python scripts/plugin_registry.py build-one "${{ matrix.id }}" --out dist - uses: actions/upload-artifact@v4 with: name: plugin-${{ matrix.id }} path: dist/ retention-days: 7 sign: needs: [plan, build] if: always() && needs.plan.result == 'success' && (needs.build.result == 'success' || needs.build.result == 'skipped') runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 with: persist-credentials: false - *python - *uv - *deps - *catalog - uses: actions/download-artifact@v4 with: pattern: plugin-* path: dist - name: Collect run: | mkdir -p dist uv run --project backend python scripts/plugin_registry.py collect \ --catalog old/catalog.json ${REBUILD:+--rebuild} --dist dist --out stage - name: Sign catalog env: PLUGIN_SIGNING_KEY: ${{ secrets.PLUGIN_SIGNING_KEY }} run: | if [ -z "$PLUGIN_SIGNING_KEY" ]; then echo "::error::PLUGIN_SIGNING_KEY secret is not set" >&2 exit 1 fi umask 077 printf '%s\n' "$PLUGIN_SIGNING_KEY" > "$RUNNER_TEMP/key.pem" shopt -s nullglob # 全部下架时没有 zip,签一个空目录 zips=(stage/*.zip) uv run --no-project --with cryptography python scripts/build_plugin_catalog.py \ --key "$RUNNER_TEMP/key.pem" --sources stage/sources.json --out release-assets "${zips[@]}" rm -f "$RUNNER_TEMP/key.pem" - name: Publish env: GH_TOKEN: ${{ github.token }} run: | # tag 名不含 beta / alpha,不会触发 build.yml 的镜像构建 gh release view plugins >/dev/null 2>&1 || gh release create plugins \ --target "${{ github.sha }}" --latest=false --title "Plugin catalog" \ --notes "AutoBangumi 插件市场的签名目录,由 plugin-registry 工作流维护。" gh release upload plugins release-assets/* --clobber # 下架:删除不在新目录中的旧 zip 与签名 for asset in $(gh release view plugins --json assets -q '.assets[].name'); do [ -e "release-assets/$asset" ] || gh release delete-asset plugins "$asset" -y done