Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 15 additions & 32 deletions .github/workflows/edgeone-artifact-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -35,7 +34,7 @@ on:
workflow_dispatch:

permissions:
contents: write
contents: read

concurrency:
group: edgeone-artifact-guard-${{ github.ref }}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
27 changes: 27 additions & 0 deletions tests/artifact-guard.test.ts
Original file line number Diff line number Diff line change
@@ -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\(\)/)
})
})