From c58350892cb2f7436635d2261bf5df37adb460a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=8B=E4=BA=91?= Date: Fri, 4 Apr 2025 03:51:39 +0800 Subject: [PATCH] fix jsonDataValidation.yml --- .github/workflows/jsonDataValidation.yml | 998 ++++++++++++++--------- 1 file changed, 625 insertions(+), 373 deletions(-) diff --git a/.github/workflows/jsonDataValidation.yml b/.github/workflows/jsonDataValidation.yml index ce083348..63ed429e 100644 --- a/.github/workflows/jsonDataValidation.yml +++ b/.github/workflows/jsonDataValidation.yml @@ -1,11 +1,10 @@ name: JSON Data Validation on: - pull_request: + pull_request_target: types: [opened, synchronize, reopened, edited] branches: - main - # 修改路径匹配,使其更宽松 paths: - 'repo/pathing/*.json' @@ -15,380 +14,633 @@ jobs: permissions: contents: write pull-requests: write + id-token: write steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.head_ref }} - token: ${{ secrets.GITHUB_TOKEN }} + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.event.pull_request.head.repo.full_name }} - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.10' + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' - - name: Install dependencies - run: | - pip install packaging semver + - name: Install dependencies + run: | + pip install packaging semver - - name: Run validation and correction - env: - GITHUB_ACTOR: ${{ github.actor }} - PR_NUMBER: ${{ github.event.pull_request.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - HEAD_REF: ${{ github.head_ref }} - run: | - cat << 'EOF' > validate.py - import json - import os - import sys - import subprocess - from packaging.version import parse - from semver import VersionInfo - - # 定义有效的 type 和 move_mode 值 - VALID_TYPES = ["teleport", "path", "target", "orientation"] - VALID_MOVE_MODES = ["swim", "walk", "fly", "climb", "run", "dash", "jump"] - - # 定义 action 和 action_params 的最低兼容版本 - ACTION_VERSION_MAP = { - "stop_flying": "0.42.0", - "force_tp": "0.42.0", - "nahida_collect": "0.42.0", - "pick_around": "0.42.0", - "hydro_collect": "0.42.0", - "electro_collect": "0.42.0", - "anemo_collect": "0.42.0", - "pyro_collect": "0.43.0", - "up_down_grab_leaf": "0.42.0", - "fight": "0.42.0", - "combat_script": "0.42.0", - "log_output": "0.42.0", - "fishing": "0.43.0", - "mining": "0.43.0" - } - - # 定义 action_params 的最低兼容版本和正则表达式验证 - ACTION_PARAMS_VERSION_MAP = { - "stop_flying": { - "params": {"version": "0.44.0", "regex": r"^\d+(\.\d+)?$"} - }, - "pick_around": { - "params": {"version": "0.42.0", "regex": r"^\d+$"} - }, - "combat_script": { - "params": {"version": "0.36.4", "regex": r"^.+$"} # 任意非空字符串 - }, - "log_output": { - "params": {"version": "0.40.0", "regex": r"^.+$"} # 任意非空字符串 - } - # 其他 action 类型没有明确的 action_params 格式要求 - } - - def get_original_file(file_path): - try: - # 添加编码参数解决中文路径问题 - result = subprocess.run(['git', 'show', f'origin/main:{file_path}'], - capture_output=True, text=True, encoding='utf-8') - return json.loads(result.stdout) if result.returncode == 0 else None - except Exception as e: - print(f"获取原始文件失败: {str(e)}") - return None - - def process_version(current, original, is_new): - if is_new: - return "1.0" - try: - # 修改版本号处理逻辑,确保总是增加版本号 - if not original: - return "1.0" - - try: - cv = parse(current) - ov = parse(original) - # 强制更新版本号,无论当前版本是否大于原始版本 - return f"{ov.major}.{ov.minor + 1}" - except: - # 如果解析失败,尝试简单的数字处理 - parts = original.split('.') - if len(parts) >= 2: - try: - major = int(parts[0]) - minor = int(parts[1]) - return f"{major}.{minor + 1}" - except: - pass - return f"{original}.1" - except Exception as e: - print(f"处理版本号失败: {str(e)}") - return "1.0" if not original else f"{original}.1" - - def check_action_compatibility(action_type, action_params, bgi_version): - """检查 action 和 action_params 与 BGI 版本的兼容性""" - import re - issues = [] - validation_issues = [] - - # 如果 action_type 为空,则跳过检查 - if not action_type: - return issues, validation_issues - - # 检查 action 兼容性 - if action_type in ACTION_VERSION_MAP: - min_version = ACTION_VERSION_MAP[action_type] - if VersionInfo.parse(bgi_version.lstrip('v')) < VersionInfo.parse(min_version): - issues.append(f"action '{action_type}' 需要 BGI 版本 >= {min_version},当前为 {bgi_version}") - else: - # 未知的 action 类型 - validation_issues.append(f"未知的 action 类型: '{action_type}',已知类型: {', '.join(sorted(ACTION_VERSION_MAP.keys()))}") - - # 检查 action_params 兼容性和格式 - if action_type in ACTION_PARAMS_VERSION_MAP and action_params: - param_info = ACTION_PARAMS_VERSION_MAP[action_type]["params"] - min_version = param_info["version"] - regex_pattern = param_info["regex"] - - # 版本兼容性检查 - if VersionInfo.parse(bgi_version.lstrip('v')) < VersionInfo.parse(min_version): - issues.append(f"action '{action_type}' 的参数需要 BGI 版本 >= {min_version},当前为 {bgi_version}") - - # 参数格式验证 - if not re.match(regex_pattern, str(action_params)): - validation_issues.append(f"action '{action_type}' 的参数格式不正确: '{action_params}',应匹配模式: {regex_pattern}") - - return issues, validation_issues - - def validate_file(file_path): - try: - with open(file_path, encoding='utf-8') as f: # 明确指定 UTF-8 编码 - data = json.load(f) - except Exception as e: - print(f"❌ JSON 格式错误: {str(e)}") - sys.exit(1) - - original_data = get_original_file(file_path) - is_new = not original_data - info = data["info"] - filename = os.path.splitext(os.path.basename(file_path))[0] - - # 自动修正逻辑 - corrections = [] - if info["name"] != filename: - info["name"] = filename - corrections.append(f"name 自动修正为 {filename}") - - if info["type"] not in ["collect", "fight"]: - info["type"] = "collect" - corrections.append("type 自动修正为 collect") - - if not info["author"]: - info["author"] = os.getenv("GITHUB_ACTOR") - corrections.append(f"author 自动设置为 {info['author']}") - - # 处理坐标保留两位小数 - coord_changed = False - for pos in data["positions"]: - if "x" in pos and isinstance(pos["x"], (int, float)): - original_x = pos["x"] - pos["x"] = round(float(pos["x"]), 2) - if original_x != pos["x"]: - coord_changed = True - - if "y" in pos and isinstance(pos["y"], (int, float)): - original_y = pos["y"] - pos["y"] = round(float(pos["y"]), 2) - if original_y != pos["y"]: - coord_changed = True - - if coord_changed: - corrections.append("坐标值自动保留两位小数") - - # 检查 action 和 action_params 兼容性 - bgi_version = info["bgiVersion"] - compatibility_issues = [] - validation_issues = [] - - for idx, pos in enumerate(data["positions"]): - # 验证 type 字段 - if "type" in pos: - pos_type = pos["type"] - if pos_type not in VALID_TYPES: - validation_issues.append(f"位置 {idx+1}: type '{pos_type}' 无效,有效值为: {', '.join(VALID_TYPES)}") - - # 当 type 为 path 或 target 时,验证 move_mode - if pos_type in ["path", "target"]: - if "move_mode" not in pos: - validation_issues.append(f"位置 {idx+1}: type 为 '{pos_type}' 时必须指定 move_mode") - elif pos["move_mode"] not in VALID_MOVE_MODES: - validation_issues.append(f"位置 {idx+1}: move_mode '{pos['move_mode']}' 无效,有效值为: {', '.join(VALID_MOVE_MODES)}") - - # 验证 action 兼容性 - action_type = pos.get("action", "") - action_params = pos.get("params", "") - - if action_type: - compat_issues, valid_issues = check_action_compatibility(action_type, action_params, bgi_version) - - for issue in compat_issues: - compatibility_issues.append(f"位置 {idx+1}: {issue}") - - for issue in valid_issues: - validation_issues.append(f"位置 {idx+1}: {issue}") - - # 根据兼容性问题更新 bgiVersion - if compatibility_issues: - required_versions = [] - for issue in compatibility_issues: - # 从错误信息中提取版本号 - parts = issue.split(">=") - if len(parts) > 1: - version_part = parts[1].split(",")[0].strip() - required_versions.append(version_part) - - if required_versions: - max_required = max(required_versions, key=lambda v: VersionInfo.parse(v)) - current_bgi = VersionInfo.parse(bgi_version.lstrip('v')) - if current_bgi < VersionInfo.parse(max_required): - info["bgiVersion"] = f"v{max_required}" - corrections.append(f"bgiVersion 自动更新为 v{max_required} 以兼容所有功能") - compatibility_issues = [] # 清空兼容性问题,因为已经更新了版本 - - original_version = original_data["info"]["version"] if original_data and "info" in original_data else None - print(f"原始版本号: {original_version}, 当前版本号: {info['version']}, 是否新文件: {is_new}") - - new_version = process_version(info["version"], original_version, is_new) - if new_version != info["version"]: - info["version"] = new_version - corrections.append(f"version 自动更新为 {new_version}") - print(f"版本号已更新: {info['version']}") - else: - print(f"版本号未变化: {info['version']}") - - try: - bgi_ver = VersionInfo.parse(info["bgiVersion"].lstrip('v')) - if not (bgi_ver > VersionInfo.parse("0.42.0")): - print(f"❌ bgiVersion {info['bgiVersion']} 必须大于 0.42") - sys.exit(1) - except ValueError: - print(f"❌ 无效的 bgiVersion 格式: {info['bgiVersion']}") - sys.exit(1) - - # 检查 bgiVersion 并自动修正 - bgi_version = info["bgiVersion"] - try: - bgi_ver = VersionInfo.parse(bgi_version.lstrip('v')) - if bgi_ver < VersionInfo.parse("0.42.0"): - # 自动修正为 v0.42.0 - info["bgiVersion"] = "v0.42.0" - corrections.append(f"bgiVersion {bgi_version} 自动更新为 v0.42.0 (原版本低于要求)") - bgi_version = "v0.42.0" - except ValueError: - # 格式无效时自动修正 - info["bgiVersion"] = "v0.42.0" - corrections.append(f"bgiVersion {bgi_version} 格式无效,自动更新为 v0.42.0") - bgi_version = "v0.42.0" - - # 校验 positions - notices = [] - for idx, pos in enumerate(data["positions"]): - if not all(key in pos for key in ["x", "y", "type"]): - # 改为自动添加缺失字段而不是退出 - missing = [k for k in ["x", "y", "type"] if k not in pos] - for m in missing: - if m == "type": - pos[m] = "teleport" - else: - pos[m] = 0.0 - corrections.append(f"position {idx+1} 自动补全缺失字段: {', '.join(missing)}") - if idx == 0 and pos["type"] != "teleport": - notices.append("⚠️ 第一个 position 的 type 不是 teleport") - - # 添加兼容性问题和验证问题到通知中 - for issue in compatibility_issues: - notices.append(issue) - - # 添加验证问题到通知中 - for issue in validation_issues: - notices.append(issue) - - # 保存修正 - if corrections: - with open(file_path, 'w', encoding='utf-8') as f: # 保存时也使用 UTF-8 编码 - json.dump(data, f, indent=2, ensure_ascii=False) - subprocess.run(['git', 'add', file_path]) - print("🔧 自动修正:", ", ".join(corrections)) - - return notices - - # 主流程 - changed_files = subprocess.run(['git', 'diff', '--name-only', 'origin/main...HEAD'], - capture_output=True, text=True).stdout.splitlines() - - print(f"检测到的变更文件: {changed_files}") - - all_notices = [] - for f in [f for f in changed_files if f.endswith('.json')]: - print(f"\n🔍 校验文件: {f}") - notices = validate_file(f) - all_notices.extend([f"{f}: {n}" for n in notices]) - - # 提交自动修正 - if subprocess.run(['git', 'diff', '--staged', '--quiet']).returncode: - print("检测到需要提交的修改") - subprocess.run(['git', 'config', 'user.name', 'GitHub Actions']) - subprocess.run(['git', 'config', 'user.email', 'actions@github.com']) - commit_result = subprocess.run(['git', 'commit', '-m', '🛠 自动校验修正'], capture_output=True, text=True) - print(f"提交结果: {commit_result.stdout} {commit_result.stderr}") - - # 修改 push 命令,指定远程分支名称 - head_ref = os.getenv('HEAD_REF') - if not head_ref: - print("⚠️ 无法获取 HEAD_REF 环境变量") - head_ref = "HEAD:refs/heads/" + subprocess.run(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], - capture_output=True, text=True).stdout.strip() - print(f"使用当前分支: {head_ref}") - - push_result = subprocess.run(['git', 'push', 'origin', f'HEAD:{head_ref}'], capture_output=True, text=True) - print(f"推送结果: {push_result.stdout} {push_result.stderr}") - - if push_result.returncode == 0: - print("✅ 自动修正已提交") - else: - print(f"❌ 推送失败: {push_result.stderr}") - else: - print("没有需要提交的修改") - - # 生成提醒信息 - if all_notices: - with open('validation_notes.md', 'w') as f: - f.write("## 校验注意事项\n\n" + "\n".join(f"- {n}" for n in all_notices)) - EOF - - python validate.py - - - name: Add PR comment - if: always() - uses: actions/github-script@v6 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const fs = require('fs'); - if (fs.existsSync('validation_notes.md')) { - const message = fs.readFileSync('validation_notes.md', 'utf8'); - await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: message - }); - } else { - console.log("没有发现 validation_notes.md 文件"); - await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: "✅ 校验完成,没有发现问题" - }); + - name: Run validation and correction + env: + GITHUB_ACTOR: ${{ github.actor }} + PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_REF: ${{ github.event.pull_request.head.ref }} + PR_REPO: ${{ github.event.pull_request.head.repo.full_name }} + run: | + cat << 'EOF' > validate.py + import json + import os + import sys + import subprocess + import re + from packaging.version import parse + from semver import VersionInfo + + # ==================== 配置和常量 ==================== + + # 定义有效的 type 和 move_mode 值 + VALID_TYPES = ["teleport", "path", "target", "orientation"] + VALID_MOVE_MODES = ["swim", "walk", "fly", "climb", "run", "dash", "jump"] + + # 定义 action 和 action_params 的最低兼容版本 + ACTION_VERSION_MAP = { + "fight": "0.42.0", + "mining": "0.43.0", + "fishing": "0.43.0", + "force_tp": "0.42.0", + "log_output": "0.42.0", + "anemo_collect": "0.42.0", + "combat_script": "0.42.0", + "hydro_collect": "0.42.0", + "pick_around": "0.42.0", + "pyro_collect": "0.43.0", + "stop_flying": "0.42.0", + "normal_attack": "0.42.0", + "electro_collect": "0.42.0", + "nahida_collect": "0.42.0", + "up_down_grab_leaf": "0.42.0" } + + # 定义 action_params 的最低兼容版本和正则表达式验证 + ACTION_PARAMS_VERSION_MAP = { + "stop_flying": { + "params": {"version": "0.44.0", "regex": r"^\d+(\.\d+)?$"} + }, + "pick_around": { + "params": {"version": "0.42.0", "regex": r"^\d+$"} + }, + "combat_script": { + "params": {"version": "0.42.0", "regex": r"^.+$"} # 任意非空字符串 + }, + "log_output": { + "params": {"version": "0.42.0", "regex": r"^.+$"} # 任意非空字符串 + } + # 其他 action 类型没有明确的 action_params 格式要求 + } + + # 默认版本号 + DEFAULT_BGI_VERSION = "0.42.0" + DEFAULT_VERSION = "1.0" + + # ==================== 文件操作 ==================== + + def get_original_file(file_path): + """从 git 仓库获取原始文件内容,如果失败则尝试从本地备份获取""" + # 返回值增加一个来源标识: "git", "backup", "current", None + + # 首先尝试从 git 仓库获取 + try: + result = subprocess.run(['git', 'show', f'origin/main:{file_path}'], + capture_output=True, text=True, encoding='utf-8') + if result.returncode == 0: + print("从 git 仓库成功获取原始文件") + return json.loads(result.stdout), "git" + except Exception as e: + print(f"从 git 仓库获取原始文件失败: {str(e)}") + + # 如果 git 获取失败,尝试从本地备份目录获取 + try: + # 假设有一个备份目录,存放原始文件 + backup_dir = os.path.join(os.path.dirname(os.path.dirname(file_path)), "backups") + backup_file = os.path.join(backup_dir, os.path.basename(file_path)) + + if os.path.exists(backup_file): + print(f"从本地备份获取原始文件: {backup_file}") + with open(backup_file, 'r', encoding='utf-8') as f: + return json.load(f), "backup" + except Exception as e: + print(f"从本地备份获取原始文件失败: {str(e)}") + + # 如果都失败了,尝试使用当前文件的副本作为原始文件 + try: + print("尝试使用当前文件作为原始文件") + with open(file_path, 'r', encoding='utf-8') as f: + current_data = json.load(f) + # 创建一个副本,避免引用相同的对象 + return json.loads(json.dumps(current_data)), "current" + except Exception as e: + print(f"使用当前文件作为原始文件失败: {str(e)}") + + print("无法获取任何形式的原始文件") + return None, None + def load_json_file(file_path): + """加载 JSON 文件""" + try: + with open(file_path, encoding='utf-8') as f: + return json.load(f), None + except Exception as e: + return None, f"❌ JSON 格式错误: {str(e)}" + + def save_json_file(file_path, data): + """保存 JSON 文件""" + try: + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + return True + except Exception as e: + print(f"保存文件失败: {str(e)}") + return False + + # ==================== 版本处理 ==================== + + def process_version(current, original, is_new): + """处理版本号更新逻辑""" + if is_new: + return DEFAULT_VERSION + + if not original: + return DEFAULT_VERSION + + try: + cv = parse(current) + ov = parse(original) + # 强制更新版本号,无论当前版本是否大于原始版本 + return f"{ov.major}.{ov.minor + 1}" + except Exception: + # 如果解析失败,尝试简单的数字处理 + parts = original.split('.') + if len(parts) >= 2: + try: + major = int(parts[0]) + minor = int(parts[1]) + return f"{major}.{minor + 1}" + except ValueError: + pass + return f"{original}.1" + + def extract_required_version(compatibility_issues): + """从兼容性问题中提取所需的最高版本号""" + required_versions = [] + for issue in compatibility_issues: + parts = issue.split(">=") + if len(parts) > 1: + version_part = parts[1].split(",")[0].strip() + version_match = re.search(r'(\d+\.\d+\.\d+)', version_part) + if version_match: + required_versions.append(version_match.group(1)) + + if not required_versions: + return None + + try: + return max(required_versions, key=lambda v: VersionInfo.parse(v)) + except ValueError: + return None + + def parse_bgi_version(version_str): + """解析 BGI 版本号""" + try: + # 确保删除 v 前缀 + return VersionInfo.parse(version_str.lstrip('v')) + except ValueError: + return None + + # ==================== 字段验证 ==================== + + def check_action_compatibility(action_type, action_params, bgi_version): + """检查 action 和 action_params 与 BGI 版本的兼容性""" + issues = [] + validation_issues = [] + + # 如果 action_type 为空,则跳过检查 + if not action_type: + return issues, validation_issues + + # 确保 bgi_version 是有效的格式 + bgi_ver = parse_bgi_version(bgi_version) + if not bgi_ver: + validation_issues.append(f"无效的 bgiVersion 格式: {bgi_version}") + return issues, validation_issues + + # 检查 action 兼容性 + if action_type in ACTION_VERSION_MAP: + min_version = ACTION_VERSION_MAP[action_type] + try: + if bgi_ver < VersionInfo.parse(min_version): + issues.append(f"action '{action_type}' 需要 BGI 版本 >= {min_version},当前为 {bgi_version}") + except ValueError: + validation_issues.append(f"无法比较版本: {min_version} 与 {bgi_version}") + else: + validation_issues.append(f"未知的 action 类型: '{action_type}',已知类型: {', '.join(sorted(ACTION_VERSION_MAP.keys()))}") + + # 检查 action_params 兼容性和格式 + if action_type in ACTION_PARAMS_VERSION_MAP and action_params: + param_info = ACTION_PARAMS_VERSION_MAP[action_type]["params"] + min_version = param_info["version"] + regex_pattern = param_info["regex"] + + # 版本兼容性检查 + try: + if bgi_ver < VersionInfo.parse(min_version): + issues.append(f"action '{action_type}' 的参数需要 BGI 版本 >= {min_version},当前为 {bgi_version}") + except ValueError: + validation_issues.append(f"无法比较版本: {min_version} 与 {bgi_version}") + + # 参数格式验证 + if not re.match(regex_pattern, str(action_params)): + validation_issues.append(f"action '{action_type}' 的参数格式不正确: '{action_params}',应匹配模式: {regex_pattern}") + + return issues, validation_issues + + def process_coordinates(positions): + """统一处理坐标保留两位小数逻辑""" + coord_changed = False + for pos in positions: + for axis in ['x', 'y']: + if axis in pos and isinstance(pos[axis], (int, float)): + original = pos[axis] + pos[axis] = round(float(pos[axis]), 2) + if original != pos[axis]: + coord_changed = True + return coord_changed + + def ensure_required_fields(info, filename): + """统一处理必要字段检查逻辑""" + corrections = [] + + if info["name"] != filename: + info["name"] = filename + corrections.append(f"name 自动修正为 {filename}") + + if info["type"] not in ["collect", "fight"]: + info["type"] = "collect" + corrections.append("type 自动修正为 collect") + + if not info["author"]: + info["author"] = os.getenv("GITHUB_ACTOR", "未知作者") + corrections.append(f"author 自动设置为 {info['author']}") + + return corrections + + def check_position_fields(positions): + """检查位置字段的有效性 + + 自动修复功能: + 1. 缺少 type 字段时,自动设置为 'path' + 2. type 字段无效时,自动修正为 'path' + 3. 当 type 为 'path' 或 'target' 且缺少 move_mode 时,自动设置为 'walk' + 4. move_mode 字段无效时,自动修正为 'walk' + """ + validation_issues = [] + notices = [] + corrections = [] # 添加修正列表 + + for idx, pos in enumerate(positions): + # 检查必需字段 + required_fields = ["x", "y", "type"] + missing_fields = [field for field in required_fields if field not in pos] + + if missing_fields: + validation_issues.append(f"位置 {idx+1} 缺少必需字段: {', '.join(missing_fields)}") + # 自动添加缺失的 type 字段 + if "type" in missing_fields: + pos["type"] = "path" # 自动修复:缺少 type 字段时设置为 path + corrections.append(f"位置 {idx+1} 缺少 type 字段,已设置为默认值 'path'") + # 如果添加了 path 类型,也需要添加 move_mode + if "move_mode" not in pos: + pos["move_mode"] = "walk" # 自动修复:为 path 类型添加默认 move_mode + corrections.append(f"位置 {idx+1} 缺少 move_mode 字段,已设置为默认值 'walk'") + # 移除 continue,确保后续检查能够执行 + # continue + + # 验证 type 字段 + if "type" in pos: + pos_type = pos["type"] + if pos_type not in VALID_TYPES: + validation_issues.append(f"位置 {idx+1}: type '{pos_type}' 无效,有效值为: {', '.join(VALID_TYPES)}") + # 自动修正无效的 type 字段 + pos["type"] = "path" # 自动修复:无效 type 修正为 path + corrections.append(f"位置 {idx+1} 的 type '{pos_type}' 无效,已修正为 'path'") + pos_type = "path" # 更新 pos_type 以便后续检查 + + # 当 type 为 path 或 target 时,验证 move_mode + if pos_type in ["path", "target"]: + if "move_mode" not in pos: + validation_issues.append(f"位置 {idx+1}: type 为 '{pos_type}' 时必须指定 move_mode") + # 自动添加缺失的 move_mode + pos["move_mode"] = "walk" # 自动修复:缺少 move_mode 时设置为 walk + corrections.append(f"位置 {idx+1} 缺少 move_mode 字段,已设置为默认值 'walk'") + elif pos["move_mode"] not in VALID_MOVE_MODES: + validation_issues.append(f"位置 {idx+1}: move_mode '{pos['move_mode']}' 无效,有效值为: {', '.join(VALID_MOVE_MODES)}") + # 自动修正无效的 move_mode + pos["move_mode"] = "walk" # 自动修复:无效 move_mode 修正为 walk + corrections.append(f"位置 {idx+1} 的 move_mode '{pos['move_mode']}' 无效,已修正为 'walk'") + + # 检查第一个位置是否为 teleport + if idx == 0 and pos.get("type") != "teleport": + notices.append("⚠️ 第一个 position 的 type 不是 teleport") + + return validation_issues, notices, corrections + + def check_bgi_version_compatibility(bgi_version, auto_fix=False): + """检查 BGI 版本兼容性""" + corrections = [] + + # 删除可能存在的 v 前缀 + if bgi_version.startswith('v'): + bgi_version = bgi_version.lstrip('v') + corrections.append(f"bgiVersion 前缀 'v' 已删除") + + bgi_ver = parse_bgi_version(bgi_version) + + if not bgi_ver: + if auto_fix: + corrections.append(f"bgiVersion {bgi_version} 格式无效,自动更新为 {DEFAULT_BGI_VERSION}") + return DEFAULT_BGI_VERSION, corrections + return bgi_version, [] + + if bgi_ver < VersionInfo.parse(DEFAULT_BGI_VERSION): + if auto_fix: + corrections.append(f"bgiVersion {bgi_version} 自动更新为 {DEFAULT_BGI_VERSION} (原版本低于要求)") + return DEFAULT_BGI_VERSION, corrections + + return bgi_version, corrections + + # ==================== 主验证逻辑 ==================== + + def initialize_data(data, file_path): + """初始化数据结构,确保必要字段存在""" + messages = [] + + if "info" not in data: + data["info"] = {} + messages.append(f"⚠️ 文件缺少 info 字段,已添加默认值") + + info = data["info"] + filename = os.path.splitext(os.path.basename(file_path))[0] + + # 检查并添加必要的字段 + if "name" not in info: + info["name"] = filename + messages.append(f"⚠️ 文件缺少 name 字段,已设置为文件名: {info['name']}") + + if "type" not in info: + info["type"] = "collect" + messages.append(f"⚠️ 文件缺少 type 字段,已设置为默认值: collect") + + if "author" not in info: + info["author"] = os.getenv("GITHUB_ACTOR", "未知作者") + messages.append(f"⚠️ 文件缺少 author 字段,已设置为: {info['author']}") + + if "version" not in info: + info["version"] = DEFAULT_VERSION + messages.append(f"⚠️ 文件缺少 version 字段,已设置为默认值: {DEFAULT_VERSION}") + + if "bgiVersion" not in info: + info["bgiVersion"] = DEFAULT_BGI_VERSION + messages.append(f"⚠️ 文件缺少 bgiVersion 字段,已设置为默认值: {DEFAULT_BGI_VERSION}") + + if "positions" not in data: + data["positions"] = [] + messages.append(f"⚠️ 文件缺少 positions 字段,已添加空数组") + + for message in messages: + print(message) + + return data + + def check_actions_compatibility(positions, bgi_version): + """检查所有位置的 action 兼容性""" + compatibility_issues = [] + validation_issues = [] + + for idx, pos in enumerate(positions): + action_type = pos.get("action", "") + action_params = pos.get("params", "") + + if action_type: + compat_issues, valid_issues = check_action_compatibility(action_type, action_params, bgi_version) + + for issue in compat_issues: + compatibility_issues.append(f"位置 {idx+1}: {issue}") + + for issue in valid_issues: + validation_issues.append(f"位置 {idx+1}: {issue}") + + return compatibility_issues, validation_issues + + def update_bgi_version_for_compatibility(info, compatibility_issues, auto_fix): + """根据兼容性问题更新 BGI 版本""" + corrections = [] + + # 首先检查并删除 v 前缀 + if info["bgiVersion"].startswith('v'): + info["bgiVersion"] = info["bgiVersion"].lstrip('v') + corrections.append(f"bgiVersion 前缀 'v' 已删除") + + if auto_fix and compatibility_issues: + max_required = extract_required_version(compatibility_issues) + + if max_required: + # 确保 max_required 没有 v 前缀 + max_required = max_required.lstrip('v') + + try: + current_bgi = parse_bgi_version(info["bgiVersion"]) + if current_bgi and current_bgi < VersionInfo.parse(max_required): + info["bgiVersion"] = max_required + corrections.append(f"bgiVersion {info['bgiVersion']} 自动更新为 {max_required} 以兼容所有功能") + return [], corrections + except ValueError as e: + print(f"警告: 版本号解析失败 - {e}") + info["bgiVersion"] = DEFAULT_BGI_VERSION + corrections.append(f"bgiVersion 自动更新为 {DEFAULT_BGI_VERSION} (版本解析失败)") + return [], corrections + + return compatibility_issues, corrections + + def validate_file(file_path, auto_fix=False): + """验证并修复 JSON 文件""" + # 加载文件 + data, error = load_json_file(file_path) + if error: + print(error) + return [] + + # 获取原始文件 + original_data, source = get_original_file(file_path) if auto_fix else (None, None) + is_new = not original_data if auto_fix else True + + # 初始化数据结构 + data = initialize_data(data, file_path) + info = data["info"] + filename = os.path.splitext(os.path.basename(file_path))[0] + + # 收集所有修正 - 修复:添加了这一行来定义 all_corrections 变量 + all_corrections = [] + + # 检查必要字段 + corrections = ensure_required_fields(info, filename) + all_corrections.extend(corrections) + + # 检查并删除 bgiVersion 的 v 前缀 + if "bgiVersion" in info and info["bgiVersion"].startswith('v'): + info["bgiVersion"] = info["bgiVersion"].lstrip('v') + all_corrections.append("bgiVersion 前缀 'v' 已删除") + + # 处理坐标 + coord_changed = process_coordinates(data["positions"]) + if coord_changed: + all_corrections.append("坐标值自动保留两位小数") + + # 检查 BGI 版本兼容性 + bgi_version, corrections = check_bgi_version_compatibility(info["bgiVersion"], auto_fix) + if corrections: + info["bgiVersion"] = bgi_version + all_corrections.extend(corrections) + + # 检查位置字段 - 修改为接收三个返回值 + position_issues, notices, pos_corrections = check_position_fields(data["positions"]) + if auto_fix and pos_corrections: + all_corrections.extend(pos_corrections) + + # 检查 action 兼容性 + compatibility_issues, action_validation_issues = check_actions_compatibility(data["positions"], info["bgiVersion"]) + position_issues.extend(action_validation_issues) + + # 根据兼容性问题更新 BGI 版本 + compatibility_issues, corrections = update_bgi_version_for_compatibility(info, compatibility_issues, auto_fix) + all_corrections.extend(corrections) + + # 更新版本号 - 只有从 git 获取的文件才更新版本号 + if auto_fix: + has_original_version = False + original_version = None + + if original_data and "info" in original_data and "version" in original_data["info"]: + original_version = original_data["info"]["version"] + has_original_version = True + print(f"成功获取原始版本号: {original_version}") + else: + print("未找到原始版本号,将视为新文件处理") + + # 只有在没有原始版本号时才视为新文件 + is_new = not has_original_version + + print(f"原始版本号: {original_version}, 当前版本号: {info['version']}, 是否新文件: {is_new}, 来源: {source}") + + # 只有当文件来源是 git 时才更新版本号 + if source == "git": + new_version = process_version(info["version"], original_version, is_new) + if new_version != info["version"]: + info["version"] = new_version + all_corrections.append(f"version 自动更新为 {new_version}") + print(f"版本号已更新: {info['version']}") + else: + print(f"版本号未变化: {info['version']}") + else: + print(f"本地文件,保持版本号不变: {info['version']}") + + # 合并所有通知 + for issue in compatibility_issues: + notices.append(issue) + + for issue in position_issues: + notices.append(issue) + + # 保存修正 + if auto_fix: + # 无论是否有问题,都打印所有自动修正项 + if all_corrections: + print("🔧 自动修正:") + for correction in all_corrections: + print(f" - {correction}") + else: + print("✅ 没有需要自动修正的项目") + + # 只有在有修正或问题时才保存文件 + if all_corrections or position_issues: + if save_json_file(file_path, data): + print("✅ 文件已保存") + else: + notices.append("❌ 保存文件失败") + + return notices + + def main(): + import argparse + + parser = argparse.ArgumentParser(description='校验 BetterGI 脚本文件') + parser.add_argument('path', help='要校验的文件或目录路径') + parser.add_argument('--fix', action='store_true', help='自动修复问题') + args = parser.parse_args() + + path = args.path + auto_fix = args.fix + + if os.path.isfile(path) and path.endswith('.json'): + print(f"\n🔍 校验文件: {path}") + notices = validate_file(path, auto_fix) + if notices: + print("\n校验注意事项:") + for notice in notices: + print(f"- {notice}") + else: + print("✅ 校验完成,没有发现问题") + elif os.path.isdir(path): + all_notices = [] + for root, _, files in os.walk(path): + for file in files: + if file.endswith('.json'): + file_path = os.path.join(root, file) + print(f"\n🔍 校验文件: {file_path}") + notices = validate_file(file_path, auto_fix) + if notices: + all_notices.extend([f"{file_path}: {n}" for n in notices]) + + if all_notices: + print("\n所有校验注意事项:") + for notice in all_notices: + print(f"- {notice}") + else: + print("\n✅ 所有文件校验完成,没有发现问题") + else: + print(f"❌ 无效的路径: {path}") + + # 生成提醒信息 + if all_notices: + with open('validation_notes.md', 'w') as f: + f.write("## 校验注意事项\n\n" + "\n".join(f"- {n}" for n in all_notices)) + EOF + + python validate.py + + - name: Add PR comment + if: always() + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('fs'); + if (fs.existsSync('validation_notes.md')) { + const message = fs.readFileSync('validation_notes.md', 'utf8'); + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: message + }); + } else { + console.log("没有发现 validation_notes.md 文件"); + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "✅ 校验完成,没有发现问题" + }); + }