Sync Official NestJS Docs #37
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Sync Official NestJS Docs | |
on: | |
schedule: | |
# 每天 UTC 时间 02:00 运行(北京时间 10:00) | |
- cron: '0 2 * * *' | |
workflow_dispatch: | |
# 允许手动触发 | |
push: | |
branches: | |
- main | |
paths: | |
- '.github/workflows/sync-official-docs.yml' | |
jobs: | |
sync-docs: | |
runs-on: ubuntu-latest | |
env: | |
# Cloudflare Workers AI 配置 | |
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
steps: | |
- name: Checkout current repository | |
uses: actions/checkout@v4 | |
with: | |
token: ${{ secrets.GITHUB_TOKEN }} | |
fetch-depth: 0 | |
- name: Setup Git | |
run: | | |
git config --global user.name 'github-actions[bot]' | |
git config --global user.email 'github-actions[bot]@users.noreply.github.com' | |
- name: Setup Bun | |
uses: oven-sh/setup-bun@v1 | |
with: | |
bun-version: latest | |
- name: Install dependencies | |
run: bun install --frozen-lockfile | |
- name: Update sync time badge JSON (Gist) | |
if: steps.sync-content.outputs.content_changed == 'true' || steps.translate-content.outputs.translation_changed == 'true' || steps.sync-assets.outputs.assets_changed == 'true' | |
env: | |
GIST_TOKEN: ${{ secrets.GIST_TOKEN }} | |
run: | | |
SYNC_TIME=$(date -u '+%Y-%m-%d') | |
cat > sync-summary.json <<EOF | |
{ | |
"schemaVersion": 1, | |
"label": "sync-docs", | |
"message": "$SYNC_TIME", | |
"color": "brightgreen" | |
} | |
EOF | |
gist_id="b3a004684a48178741a9ab9e1a90ab3e" | |
file_name="nestjscn-sync-time.json" | |
curl -X PATCH "https://api.github.com/gists/$gist_id" \ | |
-H "Authorization: token $GIST_TOKEN" \ | |
-d "{\"files\": {\"$file_name\": {\"content\": $(jq -Rs . < sync-summary.json)}}}" | |
- name: Clone official NestJS docs repository | |
run: | | |
git clone --depth 1 https://github.com/nestjs/docs.nestjs.com.git official-docs | |
- name: Compare and sync content folder | |
id: sync-content | |
run: | | |
if [ ! -d "official-docs/content" ]; then | |
echo "❌ 官方仓库中未找到 content 文件夹" | |
exit 1 | |
fi | |
echo "🔍 正在比较 content 文件夹..." | |
CONTENT_CHANGED=false | |
# 检查 content 文件夹是否存在差异 | |
if [ -d "content" ]; then | |
# 使用 rsync 的 dry-run 模式检查差异 | |
if ! rsync -avu --dry-run --delete official-docs/content/ content/ > /tmp/content-diff.log 2>&1; then | |
CONTENT_CHANGED=true | |
elif [ -s /tmp/content-diff.log ]; then | |
# 检查是否有实际的文件差异(过滤掉仅有的时间戳差异) | |
if grep -E "^(deleting|>|<|\*)" /tmp/content-diff.log > /dev/null; then | |
CONTENT_CHANGED=true | |
fi | |
fi | |
else | |
echo "📁 本地不存在 content 文件夹,将创建" | |
CONTENT_CHANGED=true | |
fi | |
if [ "$CONTENT_CHANGED" = true ]; then | |
echo "📋 检测到内容变更:" | |
cat /tmp/content-diff.log || echo "初始同步 - 无差异日志可用" | |
# 备份现有内容(如果存在) | |
if [ -d "content" ]; then | |
echo "💾 正在备份当前内容..." | |
mv content content-backup-$(date +%Y%m%d-%H%M%S) | |
fi | |
# 同步新内容 | |
echo "🔄 正在同步 content 文件夹..." | |
cp -r official-docs/content ./ | |
echo "✅ content 文件夹同步成功" | |
echo "content_changed=true" >> $GITHUB_OUTPUT | |
else | |
echo "✅ content 文件夹已是最新版本,无需更改" | |
echo "content_changed=false" >> $GITHUB_OUTPUT | |
fi | |
- name: Compare and sync assets folder | |
id: sync-assets | |
run: | | |
ASSETS_CHANGED=false | |
if [ -d "official-docs/src/assets" ]; then | |
echo "🔍 正在比较 assets 文件夹..." | |
# 创建 public 目录(如果不存在) | |
mkdir -p public | |
# 检查 assets 文件夹是否存在差异 | |
if [ -d "public/assets" ]; then | |
# 使用 rsync 的 dry-run 模式检查差异 | |
if ! rsync -avu --dry-run --delete official-docs/src/assets/ public/assets/ > /tmp/assets-diff.log 2>&1; then | |
ASSETS_CHANGED=true | |
elif [ -s /tmp/assets-diff.log ]; then | |
# 检查是否有实际的文件差异 | |
if grep -E "^(deleting|>|<|\*)" /tmp/assets-diff.log > /dev/null; then | |
ASSETS_CHANGED=true | |
fi | |
fi | |
else | |
echo "📁 本地不存在 assets 文件夹,将创建" | |
ASSETS_CHANGED=true | |
fi | |
if [ "$ASSETS_CHANGED" = true ]; then | |
echo "📋 检测到资源变更:" | |
cat /tmp/assets-diff.log || echo "初始同步 - 无差异日志可用" | |
# 同步新资源 | |
echo "🔄 正在同步 assets 文件夹..." | |
rsync -av --delete official-docs/src/assets/ public/assets/ | |
echo "✅ assets 文件夹同步成功" | |
echo "assets_changed=true" >> $GITHUB_OUTPUT | |
else | |
echo "✅ assets 文件夹已是最新版本,无需更改" | |
echo "assets_changed=false" >> $GITHUB_OUTPUT | |
fi | |
else | |
echo "⚠️ 官方仓库中未找到 assets 文件夹,跳过..." | |
echo "assets_changed=false" >> $GITHUB_OUTPUT | |
fi | |
- name: Clean up | |
run: | | |
rm -rf official-docs | |
- name: Process and translate content changes | |
id: translate-content | |
if: steps.sync-content.outputs.content_changed == 'true' | |
run: | | |
echo "🔄 正在处理内容变更并进行翻译..." | |
# 检查是否有可用的 Cloudflare 配置 | |
if [ -n "$CLOUDFLARE_API_TOKEN" ] && [ -n "$CLOUDFLARE_ACCOUNT_ID" ]; then | |
echo "✅ Cloudflare Workers AI 已配置,使用 AI 翻译" | |
if bun run translate-docs:verbose; then | |
echo "✅ 翻译完成,有内容更新" | |
echo "translation_changed=true" >> $GITHUB_OUTPUT | |
else | |
TRANSLATION_EXIT_CODE=$? | |
if [ $TRANSLATION_EXIT_CODE -eq 1 ]; then | |
echo "✅ 翻译完成,但无需更新" | |
echo "translation_changed=false" >> $GITHUB_OUTPUT | |
else | |
echo "❌ 翻译失败,退出代码 $TRANSLATION_EXIT_CODE" | |
echo "translation_changed=error" >> $GITHUB_OUTPUT | |
exit 1 | |
fi | |
fi | |
else | |
echo "⚠️ 未配置 Cloudflare API 凭据,使用仅格式化模式" | |
if bun run translate-docs:no-ai; then | |
echo "✅ 格式处理完成,有内容更新" | |
echo "translation_changed=true" >> $GITHUB_OUTPUT | |
else | |
TRANSLATION_EXIT_CODE=$? | |
if [ $TRANSLATION_EXIT_CODE -eq 1 ]; then | |
echo "✅ 格式处理完成,但无需更新" | |
echo "translation_changed=false" >> $GITHUB_OUTPUT | |
else | |
echo "❌ 格式处理失败,退出代码 $TRANSLATION_EXIT_CODE" | |
echo "translation_changed=error" >> $GITHUB_OUTPUT | |
exit 1 | |
fi | |
fi | |
fi | |
- name: Fix code blocks and template syntax | |
if: steps.translate-content.outputs.translation_changed == 'true' | |
run: | | |
echo "🔧 正在运行代码块和模板语法修复..." | |
bun run fix-all | |
echo "✅ 代码格式化完成" | |
- name: Update README sync time and check for changes | |
id: changes | |
run: | | |
# 如果有内容同步或翻译更新,更新 README 中的同步时间 | |
if [ "${{ steps.sync-content.outputs.content_changed }}" == "true" ] || [ "${{ steps.translate-content.outputs.translation_changed }}" == "true" ] || [ "${{ steps.sync-assets.outputs.assets_changed }}" == "true" ]; then | |
echo "📅 正在更新 README.md 中的同步时间..." | |
# 获取当前时间(北京时间)- 格式: 2025年07月01日 17:48 | |
SYNC_TIME=$(TZ='Asia/Shanghai' date '+%Y年%m月%d日 %H:%M') | |
# 更新 README.md 中的同步时间标记 | |
sed -i "s|<!-- LAST_SYNC_TIME -->.*<!-- /LAST_SYNC_TIME -->|<!-- LAST_SYNC_TIME --> $SYNC_TIME <!-- /LAST_SYNC_TIME -->|g" README.md | |
echo "✅ 同步时间已更新为: $SYNC_TIME" | |
fi | |
# 检查是否有变更需要提交 | |
git add . | |
if git diff --cached --quiet; then | |
echo "未检测到变更" | |
echo "has_changes=false" >> $GITHUB_OUTPUT | |
else | |
echo "检测到变更" | |
echo "has_changes=true" >> $GITHUB_OUTPUT | |
# 输出变更的文件列表 | |
echo "📋 变更的文件:" | |
git diff --cached --name-status | |
fi | |
- name: Commit and push changes | |
if: steps.changes.outputs.has_changes == 'true' | |
run: | | |
# 生成详细的提交信息 | |
COMMIT_MSG="🔄 Sync official NestJS docs content and assets" | |
if [ "${{ steps.sync-content.outputs.content_changed }}" == "true" ]; then | |
COMMIT_MSG="${COMMIT_MSG} | |
📝 Content changes detected and synced" | |
fi | |
if [ "${{ steps.translate-content.outputs.translation_changed }}" == "true" ]; then | |
COMMIT_MSG="${COMMIT_MSG} | |
🌐 Translations updated with Cloudflare Workers AI" | |
fi | |
if [ "${{ steps.sync-assets.outputs.assets_changed }}" == "true" ]; then | |
COMMIT_MSG="${COMMIT_MSG} | |
🎨 Assets changes detected and synced" | |
fi | |
# 添加同步信息 | |
COMMIT_MSG="${COMMIT_MSG} | |
🕒 Sync date: $(date -u '+%Y-%m-%d %H:%M:%S UTC') | |
📦 Source: https://github.com/nestjs/docs.nestjs.com" | |
git commit -m "$COMMIT_MSG" | |
git push | |
- name: Create summary | |
run: | | |
echo "## 📋 同步摘要" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "- **同步日期**: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY | |
echo "- **源仓库**: https://github.com/nestjs/docs.nestjs.com" >> $GITHUB_STEP_SUMMARY | |
# Content 同步状态 | |
if [ "${{ steps.sync-content.outputs.content_changed }}" == "true" ]; then | |
echo "- **内容同步**: ✅ 是(检测到变更并应用)" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "- **内容同步**: ✅ 已是最新(无需更改)" >> $GITHUB_STEP_SUMMARY | |
fi | |
# 翻译状态 | |
if [ "${{ steps.translate-content.outputs.translation_changed }}" == "true" ]; then | |
echo "- **翻译更新**: ✅ 是(使用 Cloudflare Workers AI 更新了 docs 文件夹)" >> $GITHUB_STEP_SUMMARY | |
elif [ "${{ steps.translate-content.outputs.translation_changed }}" == "false" ]; then | |
echo "- **翻译更新**: ✅ 已是最新(无需翻译更改)" >> $GITHUB_STEP_SUMMARY | |
elif [ "${{ steps.sync-content.outputs.content_changed }}" == "true" ]; then | |
echo "- **翻译更新**: ⚠️ 内容已变更但跳过了翻译" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "- **翻译更新**: ➖ 无内容变更需要翻译" >> $GITHUB_STEP_SUMMARY | |
fi | |
# Assets 同步状态 | |
if [ "${{ steps.sync-assets.outputs.assets_changed }}" == "true" ]; then | |
echo "- **资源同步**: ✅ 是(检测到变更并应用)" >> $GITHUB_STEP_SUMMARY | |
elif [ -d "public/assets" ]; then | |
echo "- **资源同步**: ✅ 已是最新(无需更改)" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "- **资源同步**: ⚠️ 源中未找到" >> $GITHUB_STEP_SUMMARY | |
fi | |
# Git 提交状态 | |
if [ "${{ steps.changes.outputs.has_changes }}" == "true" ]; then | |
echo "- **变更提交**: ✅ 是,变更已提交并推送" >> $GITHUB_STEP_SUMMARY | |
else | |
echo "- **变更提交**: ❌ 无变更需要提交" >> $GITHUB_STEP_SUMMARY | |
fi | |
- name: Notify on failure | |
if: failure() | |
run: | | |
echo "## ❌ 同步失败" >> $GITHUB_STEP_SUMMARY | |
echo "" >> $GITHUB_STEP_SUMMARY | |
echo "同步操作失败。请检查日志了解详细信息。" >> $GITHUB_STEP_SUMMARY | |
echo "您可以手动重新触发此工作流或调查问题。" >> $GITHUB_STEP_SUMMARY |