⏱️ وقت القراءة المقدر: 22 دقائق

هذا الجزء الرابع من سلسلة GitHub CLI. سنتعلم كيفية بناء نظام Wiki معياري وتوليد الوثائق تلقائيًا.

بنية النظام المعياري لـ Wiki

الهيكل الأساسي

.github/wiki/
   bin/
      wiki-init.sh           # تهيئة Wiki
      wiki-update.sh         # تحديث الصفحات
      api-doc-gen.sh         # توليد وثائق API
      translate-check.sh     # فحص حالة الترجمة
   templates/
      page-template.md       # قالب الصفحة
      api-template.md        # قالب وثيقة API
   config/
      wiki-config.json       # إعدادات Wiki
   scripts/
      extract-jsdoc.js       # استخراج تعليقات JSDoc
      parse-openapi.py       # تحليل مواصفات OpenAPI

تهيئة Wiki

#!/bin/bash
# wiki-init.sh - تهيئة نظام Wiki

REPO="${1}"
WIKI_DIR="${HOME}/.wiki-automation/${REPO//\//_}"

init_wiki() {
    local repo="${1}"
    
    echo "تهيئة Wiki للمستودع: ${repo}"
    
    # استنساخ مستودع Wiki
    local wiki_url="https://github.com/${repo}.wiki.git"
    local local_wiki_path="${WIKI_DIR}/wiki"
    
    if [[ -d "${local_wiki_path}" ]]; then
        # تحديث المستودع الموجود
        git -C "${local_wiki_path}" pull origin master 2>/dev/null || \
        git -C "${local_wiki_path}" pull origin main 2>/dev/null
    else
        mkdir -p "${WIKI_DIR}"
        git clone "${wiki_url}" "${local_wiki_path}" 2>/dev/null || {
            echo "تعذّر الاستنساخ. التحقق من تفعيل Wiki..."
            enable_wiki "${repo}"
            git clone "${wiki_url}" "${local_wiki_path}"
        }
    fi
    
    echo "تمت التهيئة: ${local_wiki_path}"
}

# تفعيل Wiki في المستودع
enable_wiki() {
    local repo="${1}"
    
    gh api \
        --method PATCH \
        "/repos/${repo}" \
        --field "has_wiki=true" \
        --jq '.has_wiki'
    
    echo "تم تفعيل Wiki"
}

init_wiki "${REPO}"

توليد وثائق API تلقائيًا

من مواصفات OpenAPI

#!/bin/bash
# api-doc-gen.sh - توليد وثائق API من OpenAPI

generate_api_docs_from_openapi() {
    local openapi_file="${1}"
    local output_dir="${2}"
    local language="${3:-ar}"
    
    echo "توليد وثائق API من: ${openapi_file}"
    
    # التحقق من وجود الملف
    if [[ ! -f "${openapi_file}" ]]; then
        echo "خطأ: ملف OpenAPI غير موجود: ${openapi_file}"
        return 1
    fi
    
    # استخراج معلومات API
    local api_title
    api_title=$(python3 -c "
import yaml, json, sys

with open('${openapi_file}') as f:
    spec = yaml.safe_load(f) if '${openapi_file}'.endswith('.yaml') else json.load(f)

info = spec.get('info', {})
print(info.get('title', 'API Documentation'))
")
    
    local api_version
    api_version=$(python3 -c "
import yaml, json

with open('${openapi_file}') as f:
    spec = yaml.safe_load(f) if '${openapi_file}'.endswith('.yaml') else json.load(f)

info = spec.get('info', {})
print(info.get('version', '1.0.0'))
")
    
    # توليد ملفات الوثائق
    python3 << 'PYTHON_SCRIPT'
import yaml
import json
import sys
import os
from datetime import datetime

openapi_file = sys.argv[1] if len(sys.argv) > 1 else ''
output_dir = sys.argv[2] if len(sys.argv) > 2 else './docs'
language = sys.argv[3] if len(sys.argv) > 3 else 'en'

with open(openapi_file) as f:
    if openapi_file.endswith('.yaml') or openapi_file.endswith('.yml'):
        spec = yaml.safe_load(f)
    else:
        spec = json.load(f)

os.makedirs(output_dir, exist_ok=True)

paths = spec.get('paths', {})
tags = {}

for path, methods in paths.items():
    for method, details in methods.items():
        if method in ['get', 'post', 'put', 'delete', 'patch']:
            for tag in details.get('tags', ['General']):
                if tag not in tags:
                    tags[tag] = []
                tags[tag].append({
                    'method': method.upper(),
                    'path': path,
                    'summary': details.get('summary', ''),
                    'description': details.get('description', ''),
                    'parameters': details.get('parameters', []),
                    'requestBody': details.get('requestBody', {}),
                    'responses': details.get('responses', {})
                })

for tag, endpoints in tags.items():
    filename = f"{tag.lower().replace(' ', '-')}.md"
    filepath = os.path.join(output_dir, filename)
    
    with open(filepath, 'w', encoding='utf-8') as f:
        if language == 'ar':
            f.write(f"# {tag}\n\n")
            f.write(f"> تاريخ التوليد: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n\n")
        else:
            f.write(f"# {tag}\n\n")
            f.write(f"> Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n\n")
        
        for endpoint in endpoints:
            f.write(f"## {endpoint['method']} `{endpoint['path']}`\n\n")
            f.write(f"{endpoint['summary']}\n\n")
            
            if endpoint['description']:
                f.write(f"{endpoint['description']}\n\n")
            
            if endpoint['parameters']:
                if language == 'ar':
                    f.write("### المعاملات\n\n")
                    f.write("| الاسم | الموقع | النوع | مطلوب | الوصف |\n")
                    f.write("|------|--------|------|-------|-------|\n")
                else:
                    f.write("### Parameters\n\n")
                    f.write("| Name | In | Type | Required | Description |\n")
                    f.write("|------|----|----|----------|-------------|\n")
                
                for param in endpoint['parameters']:
                    name = param.get('name', '')
                    location = param.get('in', '')
                    param_type = param.get('schema', {}).get('type', 'string')
                    required = 'نعم' if language == 'ar' and param.get('required', False) else ('Yes' if param.get('required', False) else 'No' if language != 'ar' else 'لا')
                    description = param.get('description', '')
                    f.write(f"| `{name}` | {location} | {param_type} | {required} | {description} |\n")
                
                f.write("\n")

print(f"تم توليد الوثائق في: {output_dir}")
PYTHON_SCRIPT
    
    echo "اكتمل توليد الوثائق"
}

من تعليقات الكود (JSDoc)

# استخراج وثائق من تعليقات JSDoc
generate_docs_from_jsdoc() {
    local source_dir="${1}"
    local output_dir="${2}"
    
    echo "استخراج تعليقات JSDoc من: ${source_dir}"
    
    node << 'NODE_SCRIPT'
const fs = require('fs');
const path = require('path');

const sourceDir = process.argv[2] || './src';
const outputDir = process.argv[3] || './docs/api';

// البحث عن ملفات JavaScript/TypeScript
function findJSFiles(dir) {
    const files = [];
    const entries = fs.readdirSync(dir, { withFileTypes: true });
    
    for (const entry of entries) {
        const fullPath = path.join(dir, entry.name);
        if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
            files.push(...findJSFiles(fullPath));
        } else if (/\.(js|ts|jsx|tsx)$/.test(entry.name)) {
            files.push(fullPath);
        }
    }
    
    return files;
}

// استخراج تعليقات JSDoc
function extractJSDoc(content) {
    const jsdocPattern = /\/\*\*([\s\S]*?)\*\//g;
    const docs = [];
    let match;
    
    while ((match = jsdocPattern.exec(content)) !== null) {
        const comment = match[1];
        const doc = {
            description: '',
            params: [],
            returns: null,
            examples: []
        };
        
        const lines = comment.split('\n').map(l => l.replace(/^\s*\*\s?/, '').trim());
        
        for (const line of lines) {
            if (line.startsWith('@param')) {
                const paramMatch = line.match(/@param\s+\{([^}]+)\}\s+(\S+)\s*(.*)/);
                if (paramMatch) {
                    doc.params.push({
                        type: paramMatch[1],
                        name: paramMatch[2],
                        description: paramMatch[3]
                    });
                }
            } else if (line.startsWith('@returns') || line.startsWith('@return')) {
                const returnMatch = line.match(/@returns?\s+\{([^}]+)\}\s*(.*)/);
                if (returnMatch) {
                    doc.returns = { type: returnMatch[1], description: returnMatch[2] };
                }
            } else if (line.startsWith('@example')) {
                doc.examples.push(line.replace('@example', '').trim());
            } else if (!line.startsWith('@') && line) {
                doc.description += (doc.description ? ' ' : '') + line;
            }
        }
        
        docs.push(doc);
    }
    
    return docs;
}

// توليد Markdown
function generateMarkdown(file, docs) {
    let md = `# ${path.basename(file)}\n\n`;
    
    for (const doc of docs) {
        if (doc.description) {
            md += `## الوصف\n\n${doc.description}\n\n`;
        }
        
        if (doc.params.length > 0) {
            md += `## المعاملات\n\n`;
            md += `| الاسم | النوع | الوصف |\n`;
            md += `|------|------|-------|\n`;
            for (const param of doc.params) {
                md += `| \`${param.name}\` | ${param.type} | ${param.description} |\n`;
            }
            md += '\n';
        }
        
        if (doc.returns) {
            md += `## القيمة المرجعة\n\n`;
            md += `**النوع**: \`${doc.returns.type}\`\n\n`;
            if (doc.returns.description) {
                md += `${doc.returns.description}\n\n`;
            }
        }
        
        if (doc.examples.length > 0) {
            md += `## مثال\n\n\`\`\`javascript\n${doc.examples.join('\n')}\n\`\`\`\n\n`;
        }
    }
    
    return md;
}

const files = findJSFiles(sourceDir);
fs.mkdirSync(outputDir, { recursive: true });

for (const file of files) {
    const content = fs.readFileSync(file, 'utf8');
    const docs = extractJSDoc(content);
    
    if (docs.length > 0) {
        const relativePath = path.relative(sourceDir, file);
        const outputFile = path.join(outputDir, relativePath.replace(/\.(js|ts|jsx|tsx)$/, '.md'));
        
        fs.mkdirSync(path.dirname(outputFile), { recursive: true });
        fs.writeFileSync(outputFile, generateMarkdown(file, docs));
        
        console.log(`تم توليد: ${outputFile}`);
    }
}
NODE_SCRIPT
}

إدارة Wiki متعدد اللغات

هيكل الترجمة

#!/bin/bash
# multilingual-wiki.sh - إدارة Wiki متعدد اللغات

# الهيكل المقترح لـ Wiki متعدد اللغات
# Home.md (الصفحة الرئيسية)
# ar/
#   Home.md
#   API/
#     ...
# en/
#   Home.md
#   API/
#     ...
# ko/
#   Home.md
#   ...

SUPPORTED_LANGUAGES=("ar" "en" "ko" "ja" "zh")

# إنشاء هيكل Wiki متعدد اللغات
setup_multilingual_wiki() {
    local wiki_path="${1}"
    local base_language="${2:-ko}"
    
    echo "إعداد هيكل Wiki متعدد اللغات"
    
    for lang in "${SUPPORTED_LANGUAGES[@]}"; do
        mkdir -p "${wiki_path}/${lang}"
        
        # إنشاء ملف الصفحة الرئيسية
        if [[ ! -f "${wiki_path}/${lang}/Home.md" ]]; then
            create_home_page "${wiki_path}/${lang}/Home.md" "${lang}"
        fi
    done
    
    # إنشاء صفحة التنقل الرئيسية
    create_navigation_sidebar "${wiki_path}" "${SUPPORTED_LANGUAGES[@]}"
    
    echo "اكتمل إعداد الهيكل متعدد اللغات"
}

# إنشاء الصفحة الرئيسية بلغة محددة
create_home_page() {
    local file_path="${1}"
    local language="${2}"
    
    case "${language}" in
        "ar")
            cat > "${file_path}" << 'EOF'
# مرحبًا بك في الوثائق

اختر اللغة المفضلة لديك:

- [العربية](ar/Home)
- [English](en/Home)
- [한국어](ko/Home)

## أحدث التحديثات

سيظهر هنا آخر تحديث للوثائق تلقائيًا.
EOF
            ;;
        "en")
            cat > "${file_path}" << 'EOF'
# Welcome to the Documentation

## Getting Started

This wiki contains comprehensive documentation for the project.

## Latest Updates

Documentation updates will appear here automatically.
EOF
            ;;
    esac
    
    echo "تم إنشاء الصفحة الرئيسية: ${file_path}"
}

# نشر التحديثات في Wiki
publish_wiki_updates() {
    local wiki_path="${1}"
    local commit_message="${2:-تحديث الوثائق تلقائيًا}"
    
    cd "${wiki_path}" || return 1
    
    git add .
    
    if git diff --cached --quiet; then
        echo "لا توجد تغييرات للنشر"
        return 0
    fi
    
    git commit -m "${commit_message}"
    git push origin HEAD
    
    echo "تم نشر تحديثات Wiki"
}

تتبع حالة الترجمة

نظام تتبع الترجمة

#!/bin/bash
# translate-check.sh - تتبع حالة ترجمة الوثائق

# فحص مدى اكتمال الترجمة
check_translation_status() {
    local wiki_path="${1}"
    local source_lang="${2:-en}"
    
    echo "=== حالة الترجمة ==="
    echo ""
    
    # الحصول على جميع الملفات في اللغة المصدر
    local source_files
    source_files=$(find "${wiki_path}/${source_lang}" -name "*.md" -type f | sort)
    
    for lang in "${SUPPORTED_LANGUAGES[@]}"; do
        if [[ "${lang}" == "${source_lang}" ]]; then
            continue
        fi
        
        local total=0
        local translated=0
        local outdated=0
        
        while IFS= read -r source_file; do
            total=$((total + 1))
            
            # مقارنة المسار في اللغة الأخرى
            local relative_path="${source_file#${wiki_path}/${source_lang}/}"
            local target_file="${wiki_path}/${lang}/${relative_path}"
            
            if [[ -f "${target_file}" ]]; then
                # مقارنة تاريخ التعديل
                local source_time
                source_time=$(stat -c %Y "${source_file}" 2>/dev/null || stat -f %m "${source_file}")
                local target_time
                target_time=$(stat -c %Y "${target_file}" 2>/dev/null || stat -f %m "${target_file}")
                
                if [[ ${source_time} -gt ${target_time} ]]; then
                    outdated=$((outdated + 1))
                else
                    translated=$((translated + 1))
                fi
            fi
        done <<< "${source_files}"
        
        local missing=$((total - translated - outdated))
        local completion_rate=0
        if [[ ${total} -gt 0 ]]; then
            completion_rate=$(( translated * 100 / total ))
        fi
        
        printf "%-10s: %d/%d (%d%%) - ناقصة: %d, قديمة: %d\n" \
            "${lang}" "${translated}" "${total}" "${completion_rate}" "${missing}" "${outdated}"
    done
}

# إنشاء تقرير مفصل بالملفات الناقصة
generate_missing_translations_report() {
    local wiki_path="${1}"
    local source_lang="${2:-en}"
    local output_file="${3:-translation-status.md}"
    
    {
        echo "# حالة الترجمة"
        echo ""
        echo "تاريخ التقرير: $(date '+%Y-%m-%d')"
        echo ""
        
        for lang in "${SUPPORTED_LANGUAGES[@]}"; do
            if [[ "${lang}" == "${source_lang}" ]]; then
                continue
            fi
            
            echo "## ${lang}"
            echo ""
            echo "| الملف | الحالة |"
            echo "|-------|--------|"
            
            find "${wiki_path}/${source_lang}" -name "*.md" -type f | sort | while read -r source_file; do
                local relative_path="${source_file#${wiki_path}/${source_lang}/}"
                local target_file="${wiki_path}/${lang}/${relative_path}"
                
                local status
                if [[ -f "${target_file}" ]]; then
                    local source_time
                    source_time=$(stat -c %Y "${source_file}" 2>/dev/null || stat -f %m "${source_file}")
                    local target_time
                    target_time=$(stat -c %Y "${target_file}" 2>/dev/null || stat -f %m "${target_file}")
                    
                    if [[ ${source_time} -gt ${target_time} ]]; then
                        status="قديمة"
                    else
                        status="مكتملة"
                    fi
                else
                    status="ناقصة"
                fi
                
                echo "| \`${relative_path}\` | ${status} |"
            done
            
            echo ""
        done
    } > "${output_file}"
    
    echo "تم إنشاء تقرير الترجمة: ${output_file}"
}

دمج zshrc لـ Wiki

وظائف Wiki في zshrc

# ~/.zshrc - وظائف Wiki

# ===== إعدادات Wiki =====
export WIKI_BASE_DIR="${HOME}/.wiki-automation"
export WIKI_DEFAULT_LANG="ar"

# ===== اختصارات Wiki =====
alias wikis='wiki-status'
alias wikiu='wiki-update'
alias wikie='wiki-edit'

# عرض حالة Wiki
wiki-status() {
    local repo="${1:-$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null)}"
    
    if [[ -z "${repo}" ]]; then
        echo "خطأ: يجب تحديد المستودع"
        return 1
    fi
    
    echo "=== حالة Wiki: ${repo} ==="
    
    local wiki_path="${WIKI_BASE_DIR}/${repo//\//_}/wiki"
    
    if [[ ! -d "${wiki_path}" ]]; then
        echo "Wiki غير مهيأ. تشغيل: wiki-init ${repo}"
        return 1
    fi
    
    # عرض عدد الملفات
    local file_count
    file_count=$(find "${wiki_path}" -name "*.md" | wc -l)
    echo "عدد الصفحات: ${file_count}"
    
    # عرض آخر تحديث
    local last_update
    last_update=$(git -C "${wiki_path}" log -1 --format="%ar" 2>/dev/null || echo "غير معروف")
    echo "آخر تحديث: ${last_update}"
    
    # فحص حالة الترجمة
    if [[ -d "${wiki_path}/en" || -d "${wiki_path}/ar" ]]; then
        echo ""
        echo "حالة الترجمة:"
        check_translation_status "${wiki_path}" "en"
    fi
}

# تحديث Wiki
wiki-update() {
    local repo="${1:-$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null)}"
    local message="${2:-تحديث تلقائي للوثائق}"
    
    local wiki_path="${WIKI_BASE_DIR}/${repo//\//_}/wiki"
    
    if [[ ! -d "${wiki_path}" ]]; then
        echo "Wiki غير مهيأ. تشغيل: wiki-init ${repo}"
        return 1
    fi
    
    # سحب آخر التغييرات
    git -C "${wiki_path}" pull --rebase
    
    # توليد وثائق API إذا وجدت
    if [[ -f "openapi.yaml" || -f "openapi.json" ]]; then
        local openapi_file
        openapi_file=$(ls openapi.yaml openapi.json 2>/dev/null | head -1)
        
        generate_api_docs_from_openapi "${openapi_file}" "${wiki_path}/api/${WIKI_DEFAULT_LANG}"
    fi
    
    # نشر التغييرات
    publish_wiki_updates "${wiki_path}" "${message}"
}

# تحرير صفحة في Wiki
wiki-edit() {
    local page="${1}"
    local lang="${2:-${WIKI_DEFAULT_LANG}}"
    local repo="${3:-$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null)}"
    
    local wiki_path="${WIKI_BASE_DIR}/${repo//\//_}/wiki"
    local page_file="${wiki_path}/${lang}/${page}.md"
    
    mkdir -p "$(dirname "${page_file}")"
    
    "${EDITOR:-vim}" "${page_file}"
    
    # النشر بعد التحرير
    read -p "نشر التغييرات؟ [y/N] " -n 1 -r
    echo
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        publish_wiki_updates "${wiki_path}" "تحديث: ${page}"
    fi
}

الخلاصة

في هذا الجزء الرابع، تعلمنا:

  • نظام Wiki المعياري: بناء نظام مرن وقابل للتوسع
  • توليد وثائق API تلقائيًا: من مواصفات OpenAPI وتعليقات الكود
  • إدارة متعددة اللغات: دعم المستخدمين في مناطق مختلفة
  • تتبع الترجمة: مراقبة حالة الترجمة ومنع الوثائق القديمة
  • دمج zshrc: تحسين سير العمل بأوامر سريعة

المقالة التالية: الجزء 5 - سير العمل المتقدمة وتكامل CI/CD (الجزء الأخير في السلسلة)