From 484f9d35666417dd4b89378ed0ca8dae27453fd7 Mon Sep 17 00:00:00 2001 From: csh Date: Sat, 28 Feb 2026 11:10:01 +0800 Subject: [PATCH] :wrench: chore(ci): automate thirdparty superpowers refresh and sync base --- .gitea/ci/update_thirdparty_superpowers.sh | 77 +++++++++++++++++++ .gitea/workflows/sync-superpowers.yml | 17 ++-- .../update-thirdparty-superpowers.yml | 68 ++++++++++++++++ tests/README.md | 1 + tests/test_superpowers_workflows.py | 42 ++++++++++ 5 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 .gitea/ci/update_thirdparty_superpowers.sh create mode 100644 .gitea/workflows/update-thirdparty-superpowers.yml create mode 100644 tests/test_superpowers_workflows.py diff --git a/.gitea/ci/update_thirdparty_superpowers.sh b/.gitea/ci/update_thirdparty_superpowers.sh new file mode 100644 index 0000000..a669e8e --- /dev/null +++ b/.gitea/ci/update_thirdparty_superpowers.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_DIR="${REPO_DIR:-$(pwd)}" +TARGET_BRANCH="${TARGET_BRANCH:-thirdparty/skill}" +SNAPSHOT_DIR="${SNAPSHOT_DIR:-superpowers}" +SOURCE_FILE="${SOURCE_FILE:-${SNAPSHOT_DIR}/SOURCE.md}" +UPSTREAM_REPO="${UPSTREAM_REPO:-https://github.com/obra/superpowers.git}" +UPSTREAM_REF="${UPSTREAM_REF:-main}" +COMMIT_AUTHOR_NAME="${COMMIT_AUTHOR_NAME:-playbook-bot}" +COMMIT_AUTHOR_EMAIL="${COMMIT_AUTHOR_EMAIL:-playbook-bot@local}" + +cd "$REPO_DIR" + +git config user.name "$COMMIT_AUTHOR_NAME" +git config user.email "$COMMIT_AUTHOR_EMAIL" + +git fetch origin "$TARGET_BRANCH" +git checkout -B "$TARGET_BRANCH" "origin/$TARGET_BRANCH" + +latest_sha="$(git ls-remote "$UPSTREAM_REPO" "refs/heads/$UPSTREAM_REF" | awk 'NR==1 {print $1}')" +if [ -z "$latest_sha" ]; then + echo "ERROR: failed to resolve upstream ref: $UPSTREAM_REPO $UPSTREAM_REF" >&2 + exit 1 +fi + +current_sha="" +if [ -f "$SOURCE_FILE" ]; then + current_sha="$(sed -n 's/^- Ref:[[:space:]]*//p' "$SOURCE_FILE" | head -n 1)" +fi + +if [ "$latest_sha" = "$current_sha" ]; then + echo "Third-party snapshot is up to date: $latest_sha" + exit 0 +fi + +tmp_dir="$(mktemp -d)" +cleanup() { + rm -rf "$tmp_dir" +} +trap cleanup EXIT + +upstream_dir="$tmp_dir/upstream" +git init "$upstream_dir" >/dev/null +git -C "$upstream_dir" remote add origin "$UPSTREAM_REPO" +git -C "$upstream_dir" fetch --depth 1 origin "$latest_sha" +git -C "$upstream_dir" checkout --detach FETCH_HEAD + +rm -rf "$SNAPSHOT_DIR" +mkdir -p "$SNAPSHOT_DIR" +git -C "$upstream_dir" archive --format=tar HEAD | tar -xf - -C "$SNAPSHOT_DIR" + +snapshot_date="$(date -u +%Y-%m-%d)" +cat > "$SOURCE_FILE" </dev/null; then - git checkout -f "$TARGET_SHA" - else - if [ -n "$TARGET_REF" ]; then - git fetch origin "$TARGET_REF" - git checkout -f FETCH_HEAD - else - git checkout -f "${{ github.ref_name }}" - fi - fi + # Always run sync from the latest target branch state. + # This prevents stale/manual dispatch refs from using outdated scripts. + git fetch origin "${{ env.TARGET_BRANCH }}" + git checkout -B "${{ env.TARGET_BRANCH }}" "origin/${{ env.TARGET_BRANCH }}" git config --global --add safe.directory "$REPO_DIR" echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV diff --git a/.gitea/workflows/update-thirdparty-superpowers.yml b/.gitea/workflows/update-thirdparty-superpowers.yml new file mode 100644 index 0000000..0c8dcf4 --- /dev/null +++ b/.gitea/workflows/update-thirdparty-superpowers.yml @@ -0,0 +1,68 @@ +name: Update Third-party Superpowers + +on: + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: thirdparty-superpowers-update-${{ github.repository }} + cancel-in-progress: true + +env: + WORKSPACE_DIR: "/home/workspace" + TARGET_BRANCH: "thirdparty/skill" + UPSTREAM_REPO: "https://github.com/obra/superpowers.git" + UPSTREAM_REF: "main" + +jobs: + update: + name: Update thirdparty/skill snapshot + runs-on: ubuntu-22.04 + + steps: + - name: Prepare repo + run: | + echo "========================================" + echo "Prepare repo to WORKSPACE_DIR" + echo "========================================" + + REPO_NAME="${{ github.event.repository.name }}" + REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME" + TOKEN="${{ secrets.WORKFLOW }}" + if [ -n "$TOKEN" ]; then + REPO_URL="https://oauth2:${TOKEN}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git" + else + REPO_URL="${GITHUB_SERVER_URL}/${{ github.repository }}.git" + fi + + if [ -d "$REPO_DIR" ]; then + if [ -d "$REPO_DIR/.git" ]; then + cd "$REPO_DIR" + git clean -fdx + git reset --hard + git fetch --all --tags --force --prune --prune-tags + else + rm -rf "$REPO_DIR" + fi + fi + + if [ ! -d "$REPO_DIR/.git" ]; then + mkdir -p "${{ env.WORKSPACE_DIR }}" + git clone "$REPO_URL" "$REPO_DIR" + cd "$REPO_DIR" + fi + + git fetch origin main + git checkout -B main origin/main + + git config --global --add safe.directory "$REPO_DIR" + echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV + + - name: Update thirdparty/skill snapshot + shell: bash + run: | + set -euo pipefail + cd "$REPO_DIR" + bash .gitea/ci/update_thirdparty_superpowers.sh diff --git a/tests/README.md b/tests/README.md index c3c7cb8..7fc17b9 100644 --- a/tests/README.md +++ b/tests/README.md @@ -16,6 +16,7 @@ tests/ ├── test_vendor_snapshot_templates.py # vendor 快照模板完整性测试 ├── test_plan_progress_cli.py # plan_progress CLI 测试 ├── test_superpowers_list_sync.py # superpowers 列表一致性测试 +├── test_superpowers_workflows.py # superpowers 工作流配置校验 ├── test_sync_templates_placeholders.py # 占位符替换测试(sync_rules/sync_standards) ├── test_toml_edge_cases.py # TOML 解析边界测试 ├── templates/ # 模板验证测试 diff --git a/tests/test_superpowers_workflows.py b/tests/test_superpowers_workflows.py new file mode 100644 index 0000000..3375de9 --- /dev/null +++ b/tests/test_superpowers_workflows.py @@ -0,0 +1,42 @@ +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SYNC_WORKFLOW = ROOT / ".gitea" / "workflows" / "sync-superpowers.yml" +AUTO_UPDATE_WORKFLOW = ROOT / ".gitea" / "workflows" / "update-thirdparty-superpowers.yml" +AUTO_UPDATE_SCRIPT = ROOT / ".gitea" / "ci" / "update_thirdparty_superpowers.sh" + + +class SuperpowersWorkflowTests(unittest.TestCase): + def test_sync_workflow_uses_manual_trigger(self): + text = SYNC_WORKFLOW.read_text(encoding="utf-8") + self.assertIn("workflow_dispatch:", text) + + def test_sync_workflow_runs_from_latest_main(self): + text = SYNC_WORKFLOW.read_text(encoding="utf-8") + self.assertIn('TARGET_BRANCH: "main"', text) + self.assertIn('git fetch origin "${{ env.TARGET_BRANCH }}"', text) + self.assertIn( + 'git checkout -B "${{ env.TARGET_BRANCH }}" "origin/${{ env.TARGET_BRANCH }}"', + text, + ) + + def test_auto_update_workflow_triggers_on_main_push(self): + text = AUTO_UPDATE_WORKFLOW.read_text(encoding="utf-8") + self.assertIn("push:", text) + self.assertIn("- main", text) + self.assertIn("workflow_dispatch:", text) + + def test_auto_update_workflow_runs_update_script(self): + text = AUTO_UPDATE_WORKFLOW.read_text(encoding="utf-8") + self.assertIn("bash .gitea/ci/update_thirdparty_superpowers.sh", text) + + def test_auto_update_script_targets_thirdparty_branch(self): + text = AUTO_UPDATE_SCRIPT.read_text(encoding="utf-8") + self.assertIn('TARGET_BRANCH="${TARGET_BRANCH:-thirdparty/skill}"', text) + self.assertIn("git ls-remote", text) + self.assertIn('git checkout -B "$TARGET_BRANCH" "origin/$TARGET_BRANCH"', text) + + +if __name__ == "__main__": + unittest.main()