GitHub CLI - سير عمل متقدمة وتقنيات الأتمتة الاحترافية [الجزء 5]
⏱️ وقت القراءة المقدر: 25 دقائق
هذا هو الجزء الأخير من سلسلة GitHub CLI. سنغطي تقنيات الأتمتة المتقدمة التي ترفع إنتاجية الفرق إلى المستوى الاحترافي.
نظام التحكم الرئيسي
بنية نظام التحكم الرئيسي
#!/bin/bash
# gh-master-controller.sh - نظام التحكم الرئيسي في GitHub CLI
set -euo pipefail
# تكوين المتغيرات العالمية
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly CONFIG_DIR="${HOME}/.config/gh-automation"
readonly LOG_DIR="${HOME}/.local/share/gh-automation/logs"
readonly CACHE_DIR="${HOME}/.cache/gh-automation"
# الألوان للإخراج
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m'
# تهيئة الدليل
init_directories() {
mkdir -p "${CONFIG_DIR}" "${LOG_DIR}" "${CACHE_DIR}"
}
# تسجيل الأحداث
log() {
local level="$1"
local message="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[${timestamp}] [${level}] ${message}" >> "${LOG_DIR}/automation.log"
case "${level}" in
"ERROR")
echo -e "${RED}[${level}] ${message}${NC}" >&2
;;
"SUCCESS")
echo -e "${GREEN}[${level}] ${message}${NC}"
;;
"WARNING")
echo -e "${YELLOW}[${level}] ${message}${NC}"
;;
*)
echo "[${level}] ${message}"
;;
esac
}
# التحقق من المتطلبات المسبقة
check_prerequisites() {
local required_tools=("gh" "jq" "git" "curl")
for tool in "${required_tools[@]}"; do
if ! command -v "${tool}" &> /dev/null; then
log "ERROR" "أداة مطلوبة غير موجودة: ${tool}"
return 1
fi
done
# التحقق من مصادقة GitHub
if ! gh auth status &> /dev/null; then
log "ERROR" "مصادقة GitHub CLI غير مكتملة. شغّل: gh auth login"
return 1
fi
log "SUCCESS" "جميع المتطلبات المسبقة مكتملة"
return 0
}
# الوظيفة الرئيسية لنظام التحكم
main() {
init_directories
check_prerequisites || exit 1
local command="${1:-help}"
case "${command}" in
"setup-team")
setup_team_automation "${@:2}"
;;
"deploy")
run_deployment_pipeline "${@:2}"
;;
"monitor")
monitor_repository "${@:2}"
;;
"report")
generate_team_report "${@:2}"
;;
*)
show_help
;;
esac
}
main "$@"
التكوين الديناميكي
# إدارة ملف التكوين
load_config() {
local repo="${1}"
local config_file="${CONFIG_DIR}/${repo//\//_}.json"
if [[ ! -f "${config_file}" ]]; then
# إنشاء تكوين افتراضي
create_default_config "${repo}" > "${config_file}"
log "INFO" "تم إنشاء ملف تكوين افتراضي: ${config_file}"
fi
cat "${config_file}"
}
create_default_config() {
local repo="${1}"
cat << EOF
{
"repo": "${repo}",
"automation": {
"auto_merge": false,
"require_reviews": 2,
"protected_branches": ["main", "develop"],
"label_automation": true
},
"notifications": {
"slack_webhook": "",
"email_recipients": []
},
"deployment": {
"environments": ["staging", "production"],
"auto_deploy_staging": true,
"require_approval_production": true
}
}
EOF
}
دمج CI/CD المتقدم
إدارة سير العمل
# مراقبة سير عمل GitHub Actions
monitor_workflows() {
local repo="${1:-$(get_current_repo)}"
local status_filter="${2:-in_progress}"
log "INFO" "مراقبة سير العمل لـ ${repo}"
gh run list \
--repo "${repo}" \
--status "${status_filter}" \
--json "name,status,conclusion,url,startedAt" \
--jq '.[] | {
name: .name,
status: .status,
conclusion: (.conclusion // "running"),
url: .url,
started: .startedAt
}'
}
# الانتظار حتى اكتمال CI
wait_for_ci() {
local pr_number="${1}"
local timeout="${2:-600}"
local check_interval="${3:-30}"
local elapsed=0
log "INFO" "انتظار اكتمال CI لـ PR #${pr_number}"
while [[ ${elapsed} -lt ${timeout} ]]; do
local checks_status
checks_status=$(gh pr checks "${pr_number}" --json "state,name,startedAt" 2>/dev/null)
local pending_count
pending_count=$(echo "${checks_status}" | jq '[.[] | select(.state == "pending")] | length')
local failed_count
failed_count=$(echo "${checks_status}" | jq '[.[] | select(.state == "failure")] | length')
if [[ ${failed_count} -gt 0 ]]; then
log "ERROR" "فشل فحص CI. عدد الفشل: ${failed_count}"
return 1
fi
if [[ ${pending_count} -eq 0 ]]; then
log "SUCCESS" "اكتملت جميع فحوصات CI بنجاح"
return 0
fi
log "INFO" "الانتظار... عمليات فحص معلقة: ${pending_count} (${elapsed}/${timeout}s)"
sleep "${check_interval}"
elapsed=$((elapsed + check_interval))
done
log "ERROR" "انتهاء مهلة انتظار CI"
return 1
}
خط أنابيب النشر التلقائي
# خط أنابيب نشر كامل
run_deployment_pipeline() {
local environment="${1:-staging}"
local branch="${2:-$(git rev-parse --abbrev-ref HEAD)}"
local repo="${3:-$(get_current_repo)}"
log "INFO" "بدء خط أنابيب النشر: ${environment} <= ${branch}"
# الخطوة 1: التحقق من اجتياز جميع فحوصات CI
local latest_run
latest_run=$(gh run list \
--repo "${repo}" \
--branch "${branch}" \
--limit 1 \
--json "databaseId,status,conclusion" \
--jq '.[0]')
local run_status
run_status=$(echo "${latest_run}" | jq -r '.status')
local run_conclusion
run_conclusion=$(echo "${latest_run}" | jq -r '.conclusion')
if [[ "${run_status}" != "completed" || "${run_conclusion}" != "success" ]]; then
log "ERROR" "CI لم يكتمل بنجاح: status=${run_status}, conclusion=${run_conclusion}"
return 1
fi
# الخطوة 2: إنشاء نشر
local deployment_id
deployment_id=$(gh api \
--method POST \
"/repos/${repo}/deployments" \
--field "ref=${branch}" \
--field "environment=${environment}" \
--field "auto_merge=false" \
--field "required_contexts=[]" \
--jq '.id')
log "INFO" "تم إنشاء النشر: ${deployment_id}"
# الخطوة 3: تشغيل خطوات النشر الفعلية
deploy_to_environment "${environment}" "${branch}"
local deploy_result=$?
# الخطوة 4: تحديث حالة النشر
if [[ ${deploy_result} -eq 0 ]]; then
gh api \
--method POST \
"/repos/${repo}/deployments/${deployment_id}/statuses" \
--field "state=success" \
--field "description=نجح النشر"
log "SUCCESS" "اكتمل النشر لـ ${environment} بنجاح"
else
gh api \
--method POST \
"/repos/${repo}/deployments/${deployment_id}/statuses" \
--field "state=failure" \
--field "description=فشل النشر"
log "ERROR" "فشل النشر لـ ${environment}"
return 1
fi
}
أتمتة إعداد الفريق
نص إعداد الأعضاء الجدد
#!/bin/bash
# setup-new-member.sh - أتمتة إعداد أعضاء الفريق الجدد
setup_new_team_member() {
local username="${1}"
local team_name="${2}"
local org="${3:-$(gh api /user/orgs --jq '.[0].login')}"
log "INFO" "إعداد عضو فريق جديد: ${username} في ${org}/${team_name}"
# الخطوة 1: إضافة المستخدم إلى المنظمة
gh api \
--method PUT \
"/orgs/${org}/memberships/${username}" \
--field "role=member" &>/dev/null
# الخطوة 2: إضافة إلى الفريق
local team_slug
team_slug=$(gh api "/orgs/${org}/teams" \
--jq ".[] | select(.name == \"${team_name}\") | .slug")
if [[ -z "${team_slug}" ]]; then
log "WARNING" "الفريق غير موجود: ${team_name}. إنشاء..."
team_slug=$(create_team "${team_name}" "${org}")
fi
gh api \
--method PUT \
"/orgs/${org}/teams/${team_slug}/memberships/${username}" \
--field "role=member"
# الخطوة 3: منح الأذونات اللازمة للمستودعات
setup_repository_access "${username}" "${team_slug}" "${org}"
# الخطوة 4: إرسال بريد ترحيبي تلقائي
send_welcome_notification "${username}" "${org}" "${team_name}"
log "SUCCESS" "اكتمل إعداد ${username}"
}
# إعداد الوصول للمستودعات
setup_repository_access() {
local username="${1}"
local team_slug="${2}"
local org="${3}"
# الحصول على قائمة المستودعات المعيّنة للفريق
local repos
repos=$(gh api \
"/orgs/${org}/teams/${team_slug}/repos" \
--jq '.[].full_name')
while IFS= read -r repo; do
# إضافة الإشارات وتكوين إعدادات المستودع
setup_repo_notifications "${username}" "${repo}"
log "INFO" "تم تكوين الوصول لـ ${repo}"
done <<< "${repos}"
}
# إرسال إشعار ترحيبي
send_welcome_notification() {
local username="${1}"
local org="${2}"
local team_name="${3}"
local welcome_issue_body
welcome_issue_body=$(cat << EOF
مرحبًا @${username}!
أهلًا بك في فريق ${team_name} التقني في ${org}.
## خطوات البدء
- [ ] راجع وثائق الفريق
- [ ] أعدّ بيئة التطوير المحلية
- [ ] اقرأ إرشادات المساهمة
- [ ] تعرّف على زملاء الفريق
## أدوات مفيدة
- نظام إدارة المشاريع: GitHub Projects
- قناة التواصل: Slack #engineering
- وثائق الفريق: wiki
نحن سعداء بانضمامك!
EOF
)
# إنشاء issue ترحيبية في مستودع المنظمة
gh issue create \
--repo "${org}/.github" \
--title "ترحيب بعضو جديد: @${username}" \
--body "${welcome_issue_body}" \
--label "onboarding" \
--assignee "${username}" 2>/dev/null || true
}
دمج zshrc وتحسين سير العمل
إعداد بيئة GitHub CLI الكاملة
# ~/.zshrc - إضافات GitHub CLI
# ===== إعداد GitHub CLI =====
export GH_PAGER="less -FRX"
export GH_NO_UPDATE_NOTIFIER=1
# ===== اختصارات GitHub CLI =====
alias ghpr='gh pr create --fill'
alias ghprl='gh pr list --assignee @me'
alias ghprv='gh pr view --web'
alias ghis='gh issue create'
alias ghisl='gh issue list --assignee @me'
alias ghrun='gh run list --limit 5'
alias ghrw='gh run watch'
# ===== وظائف GitHub CLI المتقدمة =====
# بحث سريع وإنشاء PR
ghprc() {
local title="${1:-}"
local base="${2:-main}"
if [[ -z "${title}" ]]; then
gh pr create --fill --web
else
gh pr create \
--title "${title}" \
--base "${base}" \
--fill
fi
}
# دمج PR بعد الفحص
ghrm() {
local pr_number="${1:-}"
if [[ -z "${pr_number}" ]]; then
# استخدام PR الحالي
pr_number=$(gh pr view --json number --jq '.number' 2>/dev/null)
fi
if [[ -z "${pr_number}" ]]; then
echo "لا يوجد PR مرتبط بالفرع الحالي"
return 1
fi
# فحص حالة CI
echo "جاري التحقق من حالة CI للـ PR #${pr_number}..."
gh pr checks "${pr_number}" --watch
# دمج بعد اجتياز الفحوصات
gh pr merge "${pr_number}" --merge --delete-branch
}
# عرض لوحة تحكم سريعة
ghd() {
echo "=== لوحة تحكم GitHub ==="
echo ""
echo "--- طلبات السحب الخاصة بي ---"
gh pr list --assignee @me --limit 5 --json "number,title,state" \
--jq '.[] | "#\(.number) \(.state) - \(.title)"'
echo ""
echo "--- المشكلات الخاصة بي ---"
gh issue list --assignee @me --limit 5 --json "number,title,state" \
--jq '.[] | "#\(.number) \(.state) - \(.title)"'
echo ""
echo "--- آخر سير العمل ---"
gh run list --limit 3 --json "name,status,conclusion" \
--jq '.[] | "\(.name): \(.status)/\(.conclusion)"'
}
قائمة التحقق من النشر
سير عمل مراجعة ما قبل النشر
#!/bin/bash
# pre-deploy-checklist.sh - التحقق الآلي قبل النشر
run_pre_deploy_checklist() {
local repo="${1:-$(get_current_repo)}"
local environment="${2:-production}"
local branch="${3:-main}"
log "INFO" "تشغيل قائمة التحقق قبل نشر ${environment}"
local failed_checks=0
local total_checks=0
# قائمة الفحوصات
declare -A checks=(
["اجتياز فحوصات CI"]="check_ci_status ${branch} ${repo}"
["اكتمال مراجعات الكود"]="check_pr_reviews ${repo}"
["بيئة النشر جاهزة"]="check_environment_health ${environment}"
["قاعدة البيانات محدّثة"]="check_database_migrations ${environment}"
["الخدمات التابعة تعمل"]="check_dependencies ${environment}"
)
for check_name in "${!checks[@]}"; do
total_checks=$((total_checks + 1))
echo -n "فحص: ${check_name}... "
if eval "${checks[${check_name}]}" &>/dev/null; then
echo -e "${GREEN}PASS${NC}"
else
echo -e "${RED}FAIL${NC}"
failed_checks=$((failed_checks + 1))
fi
done
echo ""
echo "نتائج الفحص: $((total_checks - failed_checks))/${total_checks} نجاح"
if [[ ${failed_checks} -gt 0 ]]; then
log "ERROR" "فشل في ${failed_checks} فحص. إلغاء النشر."
return 1
fi
log "SUCCESS" "نجاحت جميع فحوصات ما قبل النشر"
return 0
}
تحسين أداء GitHub CLI
التخزين المؤقت وتجميع الطلبات
# آلية التخزين المؤقت للطلبات المتكررة
cached_gh_api() {
local endpoint="${1}"
local cache_ttl="${2:-300}" # 5 دقائق افتراضيًا
local cache_file="${CACHE_DIR}/$(echo "${endpoint}" | md5sum | cut -d' ' -f1).json"
# فحص صلاحية الكاش
if [[ -f "${cache_file}" ]]; then
local cache_age=$(( $(date +%s) - $(stat -c %Y "${cache_file}") ))
if [[ ${cache_age} -lt ${cache_ttl} ]]; then
cat "${cache_file}"
return 0
fi
fi
# جلب البيانات الحديثة
local data
data=$(gh api "${endpoint}")
echo "${data}" > "${cache_file}"
echo "${data}"
}
# تجميع الطلبات المتوازية
batch_api_requests() {
local requests=("$@")
local tmpdir=$(mktemp -d)
local pids=()
# تشغيل الطلبات بالتوازي
for i in "${!requests[@]}"; do
gh api "${requests[${i}]}" > "${tmpdir}/result_${i}.json" &
pids+=($!)
done
# الانتظار لإتمام جميع الطلبات
for pid in "${pids[@]}"; do
wait "${pid}"
done
# دمج النتائج
for i in "${!requests[@]}"; do
if [[ -f "${tmpdir}/result_${i}.json" ]]; then
cat "${tmpdir}/result_${i}.json"
fi
done
rm -rf "${tmpdir}"
}
نظام النسخ الاحتياطي والتعافي
نسخ احتياطي لتكوينات المستودع
#!/bin/bash
# backup-repo-config.sh - نسخ احتياطي لتكوينات المستودع
backup_repository_config() {
local repo="${1}"
local backup_dir="${2:-${HOME}/.gh-backups/$(date '+%Y%m%d')}"
mkdir -p "${backup_dir}/${repo//\//_}"
log "INFO" "بدء نسخ احتياطي لـ ${repo}"
# نسخ احتياطي للإعدادات
backup_settings() {
gh api "/repos/${repo}" \
--jq '{
name, description, private,
has_issues, has_projects, has_wiki,
allow_merge_commit, allow_squash_merge, allow_rebase_merge,
delete_branch_on_merge
}' > "${backup_dir}/${repo//\//_}/settings.json"
}
# نسخ احتياطي للتسميات
backup_labels() {
gh label list \
--repo "${repo}" \
--json "name,color,description" \
> "${backup_dir}/${repo//\//_}/labels.json"
}
# نسخ احتياطي لقواعد حماية الفروع
backup_branch_protection() {
local default_branch
default_branch=$(gh api "/repos/${repo}" --jq '.default_branch')
gh api "/repos/${repo}/branches/${default_branch}/protection" \
2>/dev/null \
> "${backup_dir}/${repo//\//_}/branch_protection.json" || true
}
backup_settings
backup_labels
backup_branch_protection
log "SUCCESS" "اكتمل النسخ الاحتياطي في ${backup_dir}"
}
# استعادة التكوين من نسخة احتياطية
restore_repository_config() {
local repo="${1}"
local backup_dir="${2}"
if [[ ! -d "${backup_dir}" ]]; then
log "ERROR" "دليل النسخ الاحتياطي غير موجود: ${backup_dir}"
return 1
fi
log "INFO" "استعادة تكوين ${repo} من ${backup_dir}"
# استعادة التسميات
if [[ -f "${backup_dir}/labels.json" ]]; then
local labels
labels=$(cat "${backup_dir}/labels.json")
echo "${labels}" | jq -c '.[]' | while IFS= read -r label; do
local name color description
name=$(echo "${label}" | jq -r '.name')
color=$(echo "${label}" | jq -r '.color')
description=$(echo "${label}" | jq -r '.description // ""')
gh label create \
--repo "${repo}" \
"${name}" \
--color "${color}" \
--description "${description}" \
--force 2>/dev/null || true
done
fi
log "SUCCESS" "اكتملت الاستعادة"
}
الخلاصة
في هذه السلسلة المكونة من خمسة أجزاء، تعلمنا:
ما تعلمناه
- الأساسيات: استخدام gh CLI الأساسي والعمليات اليومية
- أتمتة PR: أتمتة دورة حياة طلبات السحب بالكامل
- إدارة المشاريع: إدارة المشاريع القائمة على السياق وإعداد التقارير
- أتمتة Wiki: أتمتة الوثائق المتعددة اللغات
- سير العمل المتقدمة: نظام التحكم الرئيسي وتكامل CI/CD
التوصيات النهائية
# قائمة المراجعة النهائية
# 1. ابدأ بالتدريج
start_simple() {
echo "ابدأ بالاختصارات الأساسية وأضف الأتمتة تدريجيًا"
}
# 2. وثّق سير العمل
document_workflows() {
echo "احتفظ بتوثيق محدّث لعمليات الأتمتة الخاصة بك"
}
# 3. اختبر قبل الأتمتة
test_before_automating() {
echo "تحقق يدويًا أولًا، ثم أتمت عندما تكون العملية ثابتة"
}
الخطوات التالية: