Skip to content
Closed

test #110

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
281 changes: 281 additions & 0 deletions .github/workflows/sync-existing-prs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
name: Sync Existing PRs to Feishu Bitable

on:
workflow_dispatch:
inputs:
state:
description: 'PR state to sync'
required: true
default: 'all'
type: choice
options:
- all
- open
- closed
- merged
limit:
description: 'Max number of PRs to sync (0 = all)'
required: false
default: '0'
type: string

jobs:
sync-existing:
runs-on: ubuntu-latest
concurrency:
group: feishu-sync-existing
cancel-in-progress: false
env:
LARK_APP_ID: ${{ secrets.LARK_APP_ID }}
LARK_APP_SECRET: ${{ secrets.LARK_APP_SECRET }}
BITABLE_APP_TOKEN: ${{ secrets.BITABLE_APP_TOKEN }}
BITABLE_TABLE_ID: ${{ secrets.BITABLE_TABLE_ID }}
steps:
- name: Check secrets
run: |
if [ -z "$LARK_APP_SECRET" ]; then
echo "::error::LARK_APP_SECRET is not set, skipping."
exit 1
fi

- name: Checkout repo
uses: actions/checkout@v4

- name: Install lark-cli
run: npm install -g @larksuite/cli

- name: Configure lark-cli
shell: bash
run: |
echo "$LARK_APP_SECRET" | lark-cli config init --app-id "$LARK_APP_ID" --brand feishu --app-secret-stdin
lark-cli auth status

- name: Fetch and sync PRs
shell: bash
env:
GH_TOKEN: ${{ github.token }}
STATE: ${{ inputs.state }}
LIMIT: ${{ inputs.limit }}
REPO: ${{ github.repository }}
run: |
set -eo pipefail

GH_ARGS=("--json" "number,title,state,isDraft,author,headRefName,baseRefName,headRefOid,url,body,createdAt,updatedAt,closedAt,mergedAt,mergedBy,labels,milestone,assignees,reviewRequests")

if [ "$STATE" = "merged" ]; then
GH_ARGS+=("--state" "closed" "--search" "is:merged")
elif [ "$STATE" != "all" ]; then
GH_ARGS+=("--state" "$STATE")
else
GH_ARGS+=("--state" "all")
fi

if [ "$LIMIT" != "0" ] && [ -n "$LIMIT" ]; then
GH_ARGS+=("--limit" "$LIMIT")
else
GH_ARGS+=("--limit" "9999")
fi

echo "Fetching PRs with: gh pr list ${GH_ARGS[*]}"
PRS=$(gh pr list "${GH_ARGS[@]}")
TOTAL=$(echo "$PRS" | jq 'length')
echo "Found $TOTAL PRs to sync"

fmt_date() {
local raw="$1"
[ -z "$raw" ] || [ "$raw" = "null" ] && echo "" && return
date -d "$raw" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$raw"
}

FAIL_COUNT=0
SUCCESS_COUNT=0

while read -r PR; do
PR_NUMBER=$(echo "$PR" | jq -r '.number')
echo "--- Processing PR #$PR_NUMBER ---"

COMMITS=0
ADDITIONS=0
DELETIONS=0
CHANGED_FILES=0

STATS_OK=true
PR_DETAIL=""
for attempt in 1 2 3; do
if PR_DETAIL=$(gh pr view "$PR_NUMBER" --json "additions,deletions,changedFiles,commits" 2>/dev/null); then
break
fi
echo " gh pr view attempt $attempt failed, retrying..."
sleep 2
if [ "$attempt" -eq 3 ]; then
echo " ::warning::gh pr view failed after 3 attempts for PR #$PR_NUMBER"
STATS_OK=false
fi
done

if [ "$STATS_OK" = true ] && [ -n "$PR_DETAIL" ]; then
COMMITS=$(echo "$PR_DETAIL" | jq -r '.commits | length // 0' 2>/dev/null) || COMMITS=0
ADDITIONS=$(echo "$PR_DETAIL" | jq -r '.additions // 0' 2>/dev/null) || ADDITIONS=0
DELETIONS=$(echo "$PR_DETAIL" | jq -r '.deletions // 0' 2>/dev/null) || DELETIONS=0
CHANGED_FILES=$(echo "$PR_DETAIL" | jq -r '.changedFiles // 0' 2>/dev/null) || CHANGED_FILES=0
fi

PR_STATE=$(echo "$PR" | jq -r '.state')
IS_DRAFT=$(echo "$PR" | jq -r '.isDraft')
MERGED_AT_RAW=$(echo "$PR" | jq -r '.mergedAt // empty')

if [ -n "$MERGED_AT_RAW" ]; then
STATUS="Merged"
elif [ "$PR_STATE" = "CLOSED" ]; then
STATUS="Closed"
elif [ "$IS_DRAFT" = "true" ]; then
STATUS="Draft"
else
STATUS="Open"
fi

CREATED_AT=$(fmt_date "$(echo "$PR" | jq -r '.createdAt // empty')")
UPDATED_AT=$(fmt_date "$(echo "$PR" | jq -r '.updatedAt // empty')")
CLOSED_AT=$(fmt_date "$(echo "$PR" | jq -r '.closedAt // empty')")
MERGED_AT_FMT=$(fmt_date "$(echo "$PR" | jq -r '.mergedAt // empty')")

TITLE=$(echo "$PR" | jq -r '.title // empty')
AUTHOR=$(echo "$PR" | jq -r '.author.login // empty')
BRANCH=$(echo "$PR" | jq -r '.headRefName // empty')
BASE_BRANCH=$(echo "$PR" | jq -r '.baseRefName // empty')
HEAD_SHA=$(echo "$PR" | jq -r '.headRefOid // empty')
URL=$(echo "$PR" | jq -r '.url // empty')
BODY=$(echo "$PR" | jq -r '.body // empty' | head -c 5000)
LABELS=$(echo "$PR" | jq -r '[.labels[]?.name] | join(", ")')
MILESTONE=$(echo "$PR" | jq -r '.milestone.title // empty')
ASSIGNEES=$(echo "$PR" | jq -r '[.assignees[]?.login] | join(", ")')
REVIEWERS=$(echo "$PR" | jq -r '[.reviewRequests[]? | (.login // .name)] | join(", ")')
MERGED_BY=$(echo "$PR" | jq -r '.mergedBy.login // empty')

if [ "$STATS_OK" = true ]; then
FIELDS=$(jq -n \
--arg title "$TITLE" \
--argjson pr_number "$PR_NUMBER" \
--arg pr_index "[$PR_NUMBER]" \
--arg author "$AUTHOR" \
--arg repo "$REPO" \
--arg status "$STATUS" \
--arg url "$URL" \
--arg branch "$BRANCH" \
--arg base_branch "$BASE_BRANCH" \
--arg head_sha "$HEAD_SHA" \
--arg body "$BODY" \
--arg created_at "$CREATED_AT" \
--arg updated_at "$UPDATED_AT" \
--arg closed_at "$CLOSED_AT" \
--arg merged_at "$MERGED_AT_FMT" \
--arg merged_by "$MERGED_BY" \
--argjson commits "${COMMITS:-0}" \
--argjson additions "${ADDITIONS:-0}" \
--argjson deletions "${DELETIONS:-0}" \
--argjson changed_files "${CHANGED_FILES:-0}" \
--arg labels "$LABELS" \
--arg milestone "$MILESTONE" \
--arg assignees "$ASSIGNEES" \
--arg reviewers "$REVIEWERS" \
'{
"PR标题": $title, "PR编号": $pr_number, "PR索引": $pr_index,
"作者": $author, "仓库": $repo, "状态": $status, "链接": $url,
"分支名称": $branch, "目标分支": $base_branch, "Head SHA": $head_sha,
"PR描述": $body, "创建时间": $created_at, "更新时间": $updated_at,
"提交数": $commits, "新增行数": $additions, "删除行数": $deletions,
"变更文件数": $changed_files, "标签": $labels, "里程碑": $milestone,
"指派人": $assignees, "审查人": $reviewers, "合并人": $merged_by
}
| if $closed_at != "" then . + {"关闭时间": $closed_at} else . end
| if $merged_at != "" then . + {"合并时间": $merged_at} else . end')
else
FIELDS=$(jq -n \
--arg title "$TITLE" \
--argjson pr_number "$PR_NUMBER" \
--arg pr_index "[$PR_NUMBER]" \
--arg author "$AUTHOR" \
--arg repo "$REPO" \
--arg status "$STATUS" \
--arg url "$URL" \
--arg branch "$BRANCH" \
--arg base_branch "$BASE_BRANCH" \
--arg head_sha "$HEAD_SHA" \
--arg body "$BODY" \
--arg created_at "$CREATED_AT" \
--arg updated_at "$UPDATED_AT" \
--arg closed_at "$CLOSED_AT" \
--arg merged_at "$MERGED_AT_FMT" \
--arg merged_by "$MERGED_BY" \
--arg labels "$LABELS" \
--arg milestone "$MILESTONE" \
--arg assignees "$ASSIGNEES" \
--arg reviewers "$REVIEWERS" \
'{
"PR标题": $title, "PR编号": $pr_number, "PR索引": $pr_index,
"作者": $author, "仓库": $repo, "状态": $status, "链接": $url,
"分支名称": $branch, "目标分支": $base_branch, "Head SHA": $head_sha,
"PR描述": $body, "创建时间": $created_at, "更新时间": $updated_at,
"标签": $labels, "里程碑": $milestone,
"指派人": $assignees, "审查人": $reviewers, "合并人": $merged_by
}
| if $closed_at != "" then . + {"关闭时间": $closed_at} else . end
| if $merged_at != "" then . + {"合并时间": $merged_at} else . end')
fi

# --- Find existing record by PR索引 keyword ---
RECORD_ID=""

SEARCH_RESULT=$(lark-cli base +record-search \
--base-token "$BITABLE_APP_TOKEN" \
--table-id "$BITABLE_TABLE_ID" \
--as bot \
--format json \
--json "{\"keyword\":\"[$PR_NUMBER]\",\"search_fields\":[\"PR索引\"],\"select_fields\":[\"PR索引\"],\"limit\":200}" 2>&1) || true

if echo "$SEARCH_RESULT" | jq -e '.ok == true' > /dev/null 2>&1; then
RECORD_ID=$(echo "$SEARCH_RESULT" | jq -r --arg idx "[$PR_NUMBER]" '
.data as $d |
if ($d.record_id_list | length) == 0 then empty
elif ($d.fields | length) == 0 then $d.record_id_list[0]
else
($d.fields | to_entries | map(select(.value == "PR索引")) | .[0].key // null) as $col |
if $col == null then $d.record_id_list[0]
else
[ $d.data | to_entries[] | select(
(.value[$col] | tostring) == $idx
) | .key ] | .[0] as $row |
if $row == null then empty
else $d.record_id_list[$row | tonumber]
end
end
end // empty' 2>/dev/null) || true
fi

# --- Upsert ---
ARGS=(
--base-token "$BITABLE_APP_TOKEN"
--table-id "$BITABLE_TABLE_ID"
--as bot
--json "$FIELDS"
)
if [ -n "$RECORD_ID" ]; then
echo " Updating existing record: $RECORD_ID"
ARGS+=(--record-id "$RECORD_ID")
else
echo " Creating new record"
fi
if lark-cli base +record-upsert "${ARGS[@]}" > /dev/null 2>&1; then
SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
else
echo " ::warning::upsert failed for PR #$PR_NUMBER, skipping"
FAIL_COUNT=$((FAIL_COUNT + 1))
fi

sleep 1
done < <(echo "$PRS" | jq -c '.[]')

echo "=== Sync complete: $SUCCESS_COUNT succeeded, $FAIL_COUNT failed ==="
if [ "$FAIL_COUNT" -gt 0 ]; then
echo "::warning::$FAIL_COUNT PR(s) failed to sync"
fi
Loading