From 8c6c169262fe1f4af598ec73aa8a2c4e08beebd1 Mon Sep 17 00:00:00 2001 From: wumingzhinu Date: Fri, 25 Sep 2026 09:10:39 +0800 Subject: [PATCH] fix(ci): make edgeone artifact guard read-only --- .github/workflows/edgeone-artifact-guard.yml | 47 +++++++------------- tests/artifact-guard.test.ts | 27 +++++++++++ 2 files changed, 42 insertions(+), 32 deletions(-) create mode 100644 tests/artifact-guard.test.ts diff --git a/.github/workflows/edgeone-artifact-guard.yml b/.github/workflows/edgeone-artifact-guard.yml index 4fd224a3..22c6669d 100644 --- a/.github/workflows/edgeone-artifact-guard.yml +++ b/.github/workflows/edgeone-artifact-guard.yml @@ -5,9 +5,8 @@ name: EdgeOne Artifact Guard # 详见 docs/edgeone.md),因此必须提交进仓库并与后端源码、前端产物保持同步。 # # 本工作流按与部署一致的流程重新构建该产物并与已提交版本比对: -# - push 到 main / 手动触发:产物过期时自动重建并提交(bot 提交不会再次触发 -# workflow,无循环风险) -# - pull request:仅检查并失败,由 PR 作者本地重建后提交(便于审查) +# - push 到 main / 手动触发:只校验并失败,不直接修改受保护分支 +# - pull request:只校验并失败,由 PR 作者本地重建后提交(便于审查) on: push: @@ -35,7 +34,7 @@ on: workflow_dispatch: permissions: - contents: write + contents: read concurrency: group: edgeone-artifact-guard-${{ github.ref }} @@ -44,7 +43,7 @@ concurrency: jobs: guard: runs-on: ubuntu-latest - name: Rebuild and sync cloud-functions artifact + name: Rebuild and verify cloud-functions artifact steps: - name: Checkout uses: actions/checkout@v7 @@ -59,7 +58,7 @@ jobs: cache: pnpm - name: Install dependencies - run: pnpm install --no-frozen-lockfile + run: pnpm install --frozen-lockfile - name: Fetch official frontend dist run: node scripts/fetch-frontend.mjs @@ -70,42 +69,26 @@ jobs: - name: Check artifact freshness id: check run: | - if [ -n "$(git status --porcelain -- cloud-functions)" ]; then + if [ -z "$(git status --porcelain -- cloud-functions)" ]; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else echo "changed=true" >> "$GITHUB_OUTPUT" + git status --short -- cloud-functions git diff --stat -- cloud-functions - else - echo "changed=false" >> "$GITHUB_OUTPUT" + git diff --exit-code -- cloud-functions || true + echo "::error::cloud-functions/[[default]].js 与源码不同步。请在本地运行 pnpm run build,并提交更新后的 cloud-functions/[[default]].js。" + exit 1 fi - - name: Auto-commit refreshed artifact - id: autocommit - if: steps.check.outputs.changed == 'true' && github.event_name != 'pull_request' - run: | - TRIGGERED=$(git rev-parse --short HEAD) - git config user.name "edgeone-deploy[bot]" - git config user.email "edgeone-deploy-bot@users.noreply.github.com" - git add cloud-functions - git commit -m "chore(edgeone): refresh cloud-functions artifact (after $TRIGGERED)" - git pull --rebase origin "$GITHUB_REF_NAME" - git push origin "HEAD:$GITHUB_REF_NAME" - echo "committed=$TRIGGERED" >> "$GITHUB_OUTPUT" - - name: Job summary if: always() run: | if [ "${{ steps.check.outputs.changed }}" = "false" ]; then - echo "✅ cloud-functions/[[default]].js 与源码同步,无需更新" >> "$GITHUB_STEP_SUMMARY" - elif [ -n "${{ steps.autocommit.outputs.committed }}" ]; then - echo "🤖 产物已过期,已自动重建并提交(触发提交:${{ steps.autocommit.outputs.committed }})" >> "$GITHUB_STEP_SUMMARY" + echo "cloud-functions/[[default]].js 与源码同步,无需更新" >> "$GITHUB_STEP_SUMMARY" + else + echo "cloud-functions/[[default]].js 已过期;请使用任务上传的刷新产物更新提交。" >> "$GITHUB_STEP_SUMMARY" fi - - name: Fail on out-of-sync artifact (pull request) - if: steps.check.outputs.changed == 'true' && github.event_name == 'pull_request' - run: | - echo "::error::cloud-functions/[[default]].js 与源码不同步(后端代码或官方前端产物已更新)。" - echo "::error::请在本地运行 pnpm run build 后提交更新后的 cloud-functions/[[default]].js;也可下载本任务上传的 cloud-functions-refreshed 产物覆盖提交。" - exit 1 - - name: Upload refreshed artifact on failure if: failure() uses: actions/upload-artifact@v4 diff --git a/tests/artifact-guard.test.ts b/tests/artifact-guard.test.ts new file mode 100644 index 00000000..20d5e973 --- /dev/null +++ b/tests/artifact-guard.test.ts @@ -0,0 +1,27 @@ +import assert from "node:assert/strict" +import fs from "node:fs" +import path from "node:path" +import { fileURLToPath } from "node:url" +import { describe, test } from "node:test" + +const workflowPath = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + "../.github/workflows/edgeone-artifact-guard.yml", +) +const workflow = fs.readFileSync(workflowPath, "utf8") + +describe("EdgeOne Artifact Guard workflow", () => { + test("uses the lockfile and never pushes generated artifacts", () => { + assert.match(workflow, /pnpm install --frozen-lockfile/) + assert.match(workflow, /contents: read/) + assert.doesNotMatch(workflow, /contents: write/) + assert.doesNotMatch(workflow, /git push origin/) + assert.doesNotMatch(workflow, /Auto-commit refreshed artifact/) + }) + + test("fails and uploads the refreshed artifact when it is stale", () => { + assert.match(workflow, /git status --porcelain -- cloud-functions/) + assert.match(workflow, /git diff --exit-code -- cloud-functions/) + assert.match(workflow, /if: failure\(\)/) + }) +})