name: JSON Data Validation on: pull_request_target: types: [opened, synchronize, reopened, edited] branches: - main paths: - 'repo/pathing/**/*.json' workflow_dispatch: inputs: path: description: '要验证的路径' required: true default: 'repo/pathing' type: string auto_fix: description: '是否自动修复问题' required: false default: true type: boolean pr_number: description: '关联的 PR 号' required: false type: string jobs: validate-json: runs-on: ubuntu-latest permissions: contents: write pull-requests: write steps: - name: Set environment variables based on trigger type id: set_env run: | # 根据触发类型设置环境变量 if [ "${{ github.event_name }}" = "pull_request_target" ]; then echo "触发类型: PR触发" echo "trigger_type=pr" >> $GITHUB_OUTPUT echo "validate_path=pr_files" >> $GITHUB_OUTPUT echo "auto_fix=true" >> $GITHUB_OUTPUT echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT echo "head_ref=${{ github.event.pull_request.head.ref }}" >> $GITHUB_OUTPUT echo "head_repo=${{ github.event.pull_request.head.repo.full_name }}" >> $GITHUB_OUTPUT echo "base_ref=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT echo "base_sha=${{ github.event.pull_request.base.sha }}" >> $GITHUB_OUTPUT else echo "触发类型: 手动触发" echo "trigger_type=manual" >> $GITHUB_OUTPUT echo "validate_path=${{ github.event.inputs.path }}" >> $GITHUB_OUTPUT echo "auto_fix=${{ github.event.inputs.auto_fix }}" >> $GITHUB_OUTPUT echo "pr_number=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT echo "head_ref=" >> $GITHUB_OUTPUT echo "head_repo=" >> $GITHUB_OUTPUT echo "base_ref=main" >> $GITHUB_OUTPUT echo "base_sha=" >> $GITHUB_OUTPUT fi - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha || github.sha }} token: ${{ secrets.GITHUB_TOKEN }} repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install dependencies run: | pip install packaging semver - name: Debug file structure run: | echo "触发类型: ${{ steps.set_env.outputs.trigger_type }}" echo "验证路径: ${{ steps.set_env.outputs.validate_path }}" echo "自动修复: ${{ steps.set_env.outputs.auto_fix }}" echo "PR号: ${{ steps.set_env.outputs.pr_number }}" echo "Current directory: $(pwd)" echo "List files in root:" ls -la echo "List files in build directory (if exists):" if [ -d "build" ]; then ls -la build/ else echo "build directory does not exist" mkdir -p build fi - name: Setup Git and repositories run: | git config --global user.name "GitHub Actions Bot" git config --global user.email "actions@github.com" # 设置Git处理中文等特殊字符 git config --global core.quotepath false # 确保远程仓库设置正确 echo "当前远程仓库配置:" git remote -v # 设置变量 MAIN_REPO="${{ github.repository }}" PR_REPO="${{ github.event.pull_request.head.repo.full_name || github.repository }}" # 设置上游仓库(upstream)指向主仓库 UPSTREAM_REPO="https://github.com/${MAIN_REPO}.git" echo "设置upstream指向主仓库: $UPSTREAM_REPO" git remote remove upstream 2>/dev/null || true git remote add upstream $UPSTREAM_REPO # 确保origin指向PR的fork仓库 if [ "$PR_REPO" != "$MAIN_REPO" ] && [ "${{ steps.set_env.outputs.trigger_type }}" = "pr" ]; then ORIGIN_REPO="https://github.com/${PR_REPO}.git" echo "PR来自fork仓库,设置origin指向: $ORIGIN_REPO" else ORIGIN_REPO=$UPSTREAM_REPO echo "PR来自同一仓库或非PR触发,origin与upstream相同: $ORIGIN_REPO" fi git remote set-url origin $ORIGIN_REPO 2>/dev/null || git remote add origin $ORIGIN_REPO # 获取最新的主仓库和分支 echo "获取远程分支信息" git fetch upstream git fetch origin # 显示远程仓库配置 echo "更新后的远程仓库配置:" git remote -v # 检查是否处于PR环境并切换到正确的分支 if [ "${{ steps.set_env.outputs.trigger_type }}" = "pr" ] && [ -n "${{ steps.set_env.outputs.head_ref }}" ]; then echo "检测到PR,切换到PR分支: ${{ steps.set_env.outputs.head_ref }}" if [ "$PR_REPO" != "$MAIN_REPO" ]; then # fork仓库的PR,需要先创建本地分支追踪fork的远程分支 git checkout -b "${{ steps.set_env.outputs.head_ref }}" --track "origin/${{ steps.set_env.outputs.head_ref }}" || \ git checkout -b "${{ steps.set_env.outputs.head_ref }}" --no-track && \ git push --set-upstream origin "${{ steps.set_env.outputs.head_ref }}" else # 同一仓库的PR git checkout "${{ steps.set_env.outputs.head_ref }}" || git checkout -b "${{ steps.set_env.outputs.head_ref }}" fi elif [ -n "${{ github.ref_name }}" ]; then echo "切换到分支: ${{ github.ref_name }}" if [[ "${{ github.ref_name }}" == "main" ]]; then # main分支需要明确指定 git checkout upstream/main -b main else git checkout "${{ github.ref_name }}" || git checkout -b "${{ github.ref_name }}" fi else echo "创建临时分支" git checkout -b temp-validation-branch fi - name: Prepare validation script run: | # 检查build目录和validate.py文件是否存在 mkdir -p build if [ ! -f "build/validate.py" ]; then echo "build/validate.py不存在,跳过获取步骤" else echo "build/validate.py已存在,检查文件头部" head -n 5 build/validate.py fi - name: Get PR information for workflow_dispatch if: ${{ steps.set_env.outputs.trigger_type == 'manual' && steps.set_env.outputs.pr_number != '' }} id: pr_info uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | try { const pr = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: parseInt(${{ steps.set_env.outputs.pr_number }}) }); core.setOutput('head_sha', pr.data.head.sha); core.setOutput('head_ref', pr.data.head.ref); core.setOutput('head_repo', pr.data.head.repo.full_name); core.setOutput('found', 'true'); console.log(`找到 PR #${{ steps.set_env.outputs.pr_number }}`); console.log(`Head SHA: ${pr.data.head.sha}`); console.log(`Head Ref: ${pr.data.head.ref}`); console.log(`Head Repo: ${pr.data.head.repo.full_name}`); // 如果找到PR,切换到PR分支 const exec = require('child_process').execSync; if (pr.data.head.ref) { console.log(`切换到PR分支: ${pr.data.head.ref}`); exec(`git checkout ${pr.data.head.ref} || git checkout -b ${pr.data.head.ref}`); } } catch (error) { console.log(`获取 PR #${{ steps.set_env.outputs.pr_number }} 信息失败: ${error.message}`); core.setOutput('found', 'false'); } - name: Get changed files for PR trigger id: changed_files if: ${{ steps.set_env.outputs.trigger_type == 'pr' }} run: | # 输出分支信息便于调试 echo "当前分支: $(git branch --show-current)" echo "HEAD指向: $(git rev-parse HEAD)" echo "PR基础分支: ${{ steps.set_env.outputs.base_ref }}" # 确保有upstream/main分支 git fetch upstream main echo "Upstream/main SHA: $(git rev-parse upstream/main)" # 创建临时变量来存储修改的文件列表 CHANGED_FILES="" # 方法1:尝试使用git diff检测变化 echo "检测方法1: 使用git diff检测" FILES_METHOD_1=$(git diff --name-only upstream/main HEAD | grep -E '^repo/pathing/.*\.json$' || true) if [ -n "$FILES_METHOD_1" ]; then echo "方法1找到的JSON文件:" echo "$FILES_METHOD_1" CHANGED_FILES="$FILES_METHOD_1" else echo "方法1未找到修改的JSON文件" fi # 方法2:如果方法1失败,尝试直接查询PR API获取修改的文件 if [ -z "$CHANGED_FILES" ] && [ -n "${{ steps.set_env.outputs.pr_number }}" ]; then echo "检测方法2: 使用GitHub API检测" PR_FILES=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ "https://api.github.com/repos/${{ github.repository }}/pulls/${{ steps.set_env.outputs.pr_number }}/files" | \ jq -r '.[] | select(.filename | test("^repo/pathing/.*\\.json$")) | .filename') if [ -n "$PR_FILES" ]; then echo "方法2找到的JSON文件:" echo "$PR_FILES" CHANGED_FILES="$PR_FILES" else echo "方法2未找到修改的JSON文件" fi fi # 方法3:如果前两种方法都失败,列出所有repo/pathing中的JSON文件,但限制在最近修改的 if [ -z "$CHANGED_FILES" ]; then echo "检测方法3: 列出最近修改的JSON文件" # 列出过去5次提交中修改的JSON文件 RECENT_FILES=$(git log -n 5 --name-only --pretty=format: | grep -E '^repo/pathing/.*\.json$' | sort -u || true) if [ -n "$RECENT_FILES" ]; then echo "最近修改的JSON文件:" echo "$RECENT_FILES" CHANGED_FILES="$RECENT_FILES" echo "⚠️ 警告: 使用最近修改的文件作为回退方案" else echo "方法3未找到最近修改的JSON文件" fi fi # 最后回退方案:直接指定验证目录 if [ -z "$CHANGED_FILES" ]; then echo "⚠️ 警告: 所有方法均未检测到修改的JSON文件,将验证整个repo/pathing目录" CHANGED_FILES="repo/pathing" fi # 输出结果 echo "最终找到的修改文件:" echo "$CHANGED_FILES" # 使用base64编码保存文件列表,避免特殊字符问题 echo "changed_files=$(echo "$CHANGED_FILES" | base64 -w 0)" >> $GITHUB_OUTPUT - name: Run validation for PR trigger if: ${{ steps.set_env.outputs.trigger_type == 'pr' }} env: GITHUB_ACTOR: ${{ github.actor }} PR_NUMBER: ${{ steps.set_env.outputs.pr_number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HEAD_REF: ${{ steps.set_env.outputs.head_ref }} PR_REPO: ${{ steps.set_env.outputs.head_repo || github.repository }} CHANGED_FILES_B64: ${{ steps.changed_files.outputs.changed_files }} run: | # 使用base64解码文件列表 CHANGED_FILES=$(echo "$CHANGED_FILES_B64" | base64 --decode) echo "PR 触发模式,验证修改的 JSON 文件" if [ -z "$CHANGED_FILES" ]; then echo "没有找到修改的 JSON 文件,跳过验证" exit 0 fi # 检查Python解释器编码设置 echo "Python编码设置:" python -c "import sys; print(sys.getdefaultencoding())" # 检查CHANGED_FILES是否包含整个目录 if [ "$CHANGED_FILES" = "repo/pathing" ]; then echo "验证整个目录: repo/pathing" python build/validate.py "repo/pathing" --fix else # 创建一个临时文件来存储文件列表 echo "$CHANGED_FILES" > temp_file_list.txt # 单独验证每个修改的文件,使用while读取避免文件名中的空格和特殊字符问题 while IFS= read -r file; do echo "验证文件: $file" if [ -f "$file" ]; then python build/validate.py "$file" --fix else echo "警告: 文件不存在 - $file" fi done < temp_file_list.txt rm temp_file_list.txt fi # 检查是否有文件被修改 if [ -n "$(git status --porcelain)" ]; then echo "发现修改,提交更改" git add . git commit -m "自动修复 JSON 格式和版本号 [ci skip]" # 确定当前分支 CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) echo "当前分支: ${CURRENT_BRANCH}" if [ "$CURRENT_BRANCH" = "HEAD" ]; then # 如果在detached HEAD状态,使用正确的方式推送 if [ -n "${HEAD_REF}" ]; then echo "在detached HEAD状态,使用HEAD_REF推送: ${HEAD_REF}" git push origin HEAD:${HEAD_REF} else echo "无法确定目标分支,跳过推送" fi else # 常规推送 echo "推送到分支: ${CURRENT_BRANCH}" git push origin ${CURRENT_BRANCH} fi else echo "没有文件被修改,无需提交" fi - name: Run validation for manual trigger if: ${{ steps.set_env.outputs.trigger_type == 'manual' }} env: GITHUB_ACTOR: ${{ github.actor }} PR_NUMBER: ${{ steps.set_env.outputs.pr_number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HEAD_REF: ${{ steps.pr_info.outputs.head_ref || '' }} PR_REPO: ${{ steps.pr_info.outputs.head_repo || github.repository }} VALIDATE_PATH: ${{ steps.set_env.outputs.validate_path }} AUTO_FIX: ${{ steps.set_env.outputs.auto_fix }} run: | echo "手动触发模式,验证路径: ${VALIDATE_PATH}" # 使用引号包裹路径,处理特殊字符 python build/validate.py "${VALIDATE_PATH}" $([[ "${AUTO_FIX}" == "true" ]] && echo "--fix") # 检查是否有文件被修改 if [ -n "$(git status --porcelain)" ]; then echo "发现修改,提交更改" git add . git commit -m "自动修复 JSON 格式和版本号 [ci skip]" # 如果关联了PR,尝试提交更改到PR if [ -n "$PR_NUMBER" ] && [ -n "$HEAD_REF" ]; then echo "提交更改到PR: #$PR_NUMBER" # 确定当前分支 CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) echo "当前分支: ${CURRENT_BRANCH}" if [ "$CURRENT_BRANCH" = "HEAD" ]; then # 如果在detached HEAD状态,使用正确的方式推送 echo "在detached HEAD状态,使用HEAD_REF推送: ${HEAD_REF}" git push origin HEAD:${HEAD_REF} else # 常规推送 echo "推送到分支: ${CURRENT_BRANCH}" git push origin ${CURRENT_BRANCH} fi else # 未关联PR或无法确定分支,直接提交到main分支 echo "未关联PR或无法确定分支,直接提交到main分支" # 保存修改的文件列表 MODIFIED_FILES=$(git status --porcelain | grep -E "^ M|^M" | awk '{print $2}') echo "已修改的文件:" echo "$MODIFIED_FILES" # 将修改的文件保存到临时目录 TEMP_DIR=$(mktemp -d) echo "创建临时目录: $TEMP_DIR" for file in $MODIFIED_FILES; do # 确保目标目录存在 mkdir -p "$TEMP_DIR/$(dirname "$file")" # 复制修改后的文件到临时目录 cp "$file" "$TEMP_DIR/$file" echo "已复制文件: $file" done # 切换到main分支 git fetch upstream main git checkout -b temp-main upstream/main # 从临时目录复制修改后的文件到main分支 for file in $MODIFIED_FILES; do # 确保目标目录存在 mkdir -p "$(dirname "$file")" # 复制文件 cp "$TEMP_DIR/$file" "$file" echo "已应用修改到main分支: $file" done # 提交并推送 git add . git commit -m "自动修复 JSON 格式和版本号 [ci skip]" git push upstream temp-main:main fi else echo "没有文件被修改,无需提交" fi - name: Add PR comment if: ${{ (steps.set_env.outputs.trigger_type == 'pr') || (steps.set_env.outputs.trigger_type == 'manual' && steps.set_env.outputs.pr_number != '' && steps.pr_info.outputs.found == 'true') }} continue-on-error: true uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const fs = require('fs'); const pr_number = ${{ steps.set_env.outputs.pr_number }}; if (fs.existsSync('validation_notes.md')) { const message = fs.readFileSync('validation_notes.md', 'utf8'); await github.rest.issues.createComment({ issue_number: pr_number, owner: context.repo.owner, repo: context.repo.repo, body: message }); } else { console.log("没有发现 validation_notes.md 文件"); // 检查是否有文件被修改并提交 const { execSync } = require('child_process'); let commitMessage = ''; try { const lastCommit = execSync('git log -1 --pretty=%B').toString().trim(); if (lastCommit.includes('自动修复')) { commitMessage = '✅ 校验完成并自动修复了一些问题。修改已提交到PR中。'; } else { commitMessage = '✅ 校验完成,没有发现需要修复的问题'; } } catch (error) { commitMessage = '✅ 校验完成,没有发现需要修复的问题'; } await github.rest.issues.createComment({ issue_number: pr_number, owner: context.repo.owner, repo: context.repo.repo, body: commitMessage }); }