fix jsonDataValidation.yml
This commit is contained in:
584
.github/workflows/jsonDataValidation.yml
vendored
584
.github/workflows/jsonDataValidation.yml
vendored
@@ -1,11 +1,10 @@
|
|||||||
name: JSON Data Validation
|
name: JSON Data Validation
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request_target:
|
||||||
types: [opened, synchronize, reopened, edited]
|
types: [opened, synchronize, reopened, edited]
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
# 修改路径匹配,使其更宽松
|
|
||||||
paths:
|
paths:
|
||||||
- 'repo/pathing/*.json'
|
- 'repo/pathing/*.json'
|
||||||
|
|
||||||
@@ -15,14 +14,16 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: ${{ github.head_ref }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
@@ -38,36 +39,41 @@ jobs:
|
|||||||
GITHUB_ACTOR: ${{ github.actor }}
|
GITHUB_ACTOR: ${{ github.actor }}
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
HEAD_REF: ${{ github.head_ref }}
|
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||||
|
PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
run: |
|
run: |
|
||||||
cat << 'EOF' > validate.py
|
cat << 'EOF' > validate.py
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import re
|
||||||
from packaging.version import parse
|
from packaging.version import parse
|
||||||
from semver import VersionInfo
|
from semver import VersionInfo
|
||||||
|
|
||||||
|
# ==================== 配置和常量 ====================
|
||||||
|
|
||||||
# 定义有效的 type 和 move_mode 值
|
# 定义有效的 type 和 move_mode 值
|
||||||
VALID_TYPES = ["teleport", "path", "target", "orientation"]
|
VALID_TYPES = ["teleport", "path", "target", "orientation"]
|
||||||
VALID_MOVE_MODES = ["swim", "walk", "fly", "climb", "run", "dash", "jump"]
|
VALID_MOVE_MODES = ["swim", "walk", "fly", "climb", "run", "dash", "jump"]
|
||||||
|
|
||||||
# 定义 action 和 action_params 的最低兼容版本
|
# 定义 action 和 action_params 的最低兼容版本
|
||||||
ACTION_VERSION_MAP = {
|
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",
|
"fight": "0.42.0",
|
||||||
"combat_script": "0.42.0",
|
"mining": "0.43.0",
|
||||||
"log_output": "0.42.0",
|
|
||||||
"fishing": "0.43.0",
|
"fishing": "0.43.0",
|
||||||
"mining": "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 的最低兼容版本和正则表达式验证
|
||||||
@@ -79,38 +85,93 @@ jobs:
|
|||||||
"params": {"version": "0.42.0", "regex": r"^\d+$"}
|
"params": {"version": "0.42.0", "regex": r"^\d+$"}
|
||||||
},
|
},
|
||||||
"combat_script": {
|
"combat_script": {
|
||||||
"params": {"version": "0.36.4", "regex": r"^.+$"} # 任意非空字符串
|
"params": {"version": "0.42.0", "regex": r"^.+$"} # 任意非空字符串
|
||||||
},
|
},
|
||||||
"log_output": {
|
"log_output": {
|
||||||
"params": {"version": "0.40.0", "regex": r"^.+$"} # 任意非空字符串
|
"params": {"version": "0.42.0", "regex": r"^.+$"} # 任意非空字符串
|
||||||
}
|
}
|
||||||
# 其他 action 类型没有明确的 action_params 格式要求
|
# 其他 action 类型没有明确的 action_params 格式要求
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 默认版本号
|
||||||
|
DEFAULT_BGI_VERSION = "0.42.0"
|
||||||
|
DEFAULT_VERSION = "1.0"
|
||||||
|
|
||||||
|
# ==================== 文件操作 ====================
|
||||||
|
|
||||||
def get_original_file(file_path):
|
def get_original_file(file_path):
|
||||||
|
"""从 git 仓库获取原始文件内容,如果失败则尝试从本地备份获取"""
|
||||||
|
# 返回值增加一个来源标识: "git", "backup", "current", None
|
||||||
|
|
||||||
|
# 首先尝试从 git 仓库获取
|
||||||
try:
|
try:
|
||||||
# 添加编码参数解决中文路径问题
|
|
||||||
result = subprocess.run(['git', 'show', f'origin/main:{file_path}'],
|
result = subprocess.run(['git', 'show', f'origin/main:{file_path}'],
|
||||||
capture_output=True, text=True, encoding='utf-8')
|
capture_output=True, text=True, encoding='utf-8')
|
||||||
return json.loads(result.stdout) if result.returncode == 0 else None
|
if result.returncode == 0:
|
||||||
|
print("从 git 仓库成功获取原始文件")
|
||||||
|
return json.loads(result.stdout), "git"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"获取原始文件失败: {str(e)}")
|
print(f"从 git 仓库获取原始文件失败: {str(e)}")
|
||||||
return None
|
|
||||||
|
# 如果 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):
|
def process_version(current, original, is_new):
|
||||||
|
"""处理版本号更新逻辑"""
|
||||||
if is_new:
|
if is_new:
|
||||||
return "1.0"
|
return DEFAULT_VERSION
|
||||||
try:
|
|
||||||
# 修改版本号处理逻辑,确保总是增加版本号
|
|
||||||
if not original:
|
if not original:
|
||||||
return "1.0"
|
return DEFAULT_VERSION
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cv = parse(current)
|
cv = parse(current)
|
||||||
ov = parse(original)
|
ov = parse(original)
|
||||||
# 强制更新版本号,无论当前版本是否大于原始版本
|
# 强制更新版本号,无论当前版本是否大于原始版本
|
||||||
return f"{ov.major}.{ov.minor + 1}"
|
return f"{ov.major}.{ov.minor + 1}"
|
||||||
except:
|
except Exception:
|
||||||
# 如果解析失败,尝试简单的数字处理
|
# 如果解析失败,尝试简单的数字处理
|
||||||
parts = original.split('.')
|
parts = original.split('.')
|
||||||
if len(parts) >= 2:
|
if len(parts) >= 2:
|
||||||
@@ -118,16 +179,41 @@ jobs:
|
|||||||
major = int(parts[0])
|
major = int(parts[0])
|
||||||
minor = int(parts[1])
|
minor = int(parts[1])
|
||||||
return f"{major}.{minor + 1}"
|
return f"{major}.{minor + 1}"
|
||||||
except:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return f"{original}.1"
|
return f"{original}.1"
|
||||||
except Exception as e:
|
|
||||||
print(f"处理版本号失败: {str(e)}")
|
def extract_required_version(compatibility_issues):
|
||||||
return "1.0" if not original else f"{original}.1"
|
"""从兼容性问题中提取所需的最高版本号"""
|
||||||
|
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):
|
def check_action_compatibility(action_type, action_params, bgi_version):
|
||||||
"""检查 action 和 action_params 与 BGI 版本的兼容性"""
|
"""检查 action 和 action_params 与 BGI 版本的兼容性"""
|
||||||
import re
|
|
||||||
issues = []
|
issues = []
|
||||||
validation_issues = []
|
validation_issues = []
|
||||||
|
|
||||||
@@ -135,13 +221,21 @@ jobs:
|
|||||||
if not action_type:
|
if not action_type:
|
||||||
return issues, validation_issues
|
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 兼容性
|
# 检查 action 兼容性
|
||||||
if action_type in ACTION_VERSION_MAP:
|
if action_type in ACTION_VERSION_MAP:
|
||||||
min_version = ACTION_VERSION_MAP[action_type]
|
min_version = ACTION_VERSION_MAP[action_type]
|
||||||
if VersionInfo.parse(bgi_version.lstrip('v')) < VersionInfo.parse(min_version):
|
try:
|
||||||
|
if bgi_ver < VersionInfo.parse(min_version):
|
||||||
issues.append(f"action '{action_type}' 需要 BGI 版本 >= {min_version},当前为 {bgi_version}")
|
issues.append(f"action '{action_type}' 需要 BGI 版本 >= {min_version},当前为 {bgi_version}")
|
||||||
|
except ValueError:
|
||||||
|
validation_issues.append(f"无法比较版本: {min_version} 与 {bgi_version}")
|
||||||
else:
|
else:
|
||||||
# 未知的 action 类型
|
|
||||||
validation_issues.append(f"未知的 action 类型: '{action_type}',已知类型: {', '.join(sorted(ACTION_VERSION_MAP.keys()))}")
|
validation_issues.append(f"未知的 action 类型: '{action_type}',已知类型: {', '.join(sorted(ACTION_VERSION_MAP.keys()))}")
|
||||||
|
|
||||||
# 检查 action_params 兼容性和格式
|
# 检查 action_params 兼容性和格式
|
||||||
@@ -151,8 +245,11 @@ jobs:
|
|||||||
regex_pattern = param_info["regex"]
|
regex_pattern = param_info["regex"]
|
||||||
|
|
||||||
# 版本兼容性检查
|
# 版本兼容性检查
|
||||||
if VersionInfo.parse(bgi_version.lstrip('v')) < VersionInfo.parse(min_version):
|
try:
|
||||||
|
if bgi_ver < VersionInfo.parse(min_version):
|
||||||
issues.append(f"action '{action_type}' 的参数需要 BGI 版本 >= {min_version},当前为 {bgi_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)):
|
if not re.match(regex_pattern, str(action_params)):
|
||||||
@@ -160,21 +257,22 @@ jobs:
|
|||||||
|
|
||||||
return issues, validation_issues
|
return issues, validation_issues
|
||||||
|
|
||||||
def validate_file(file_path):
|
def process_coordinates(positions):
|
||||||
try:
|
"""统一处理坐标保留两位小数逻辑"""
|
||||||
with open(file_path, encoding='utf-8') as f: # 明确指定 UTF-8 编码
|
coord_changed = False
|
||||||
data = json.load(f)
|
for pos in positions:
|
||||||
except Exception as e:
|
for axis in ['x', 'y']:
|
||||||
print(f"❌ JSON 格式错误: {str(e)}")
|
if axis in pos and isinstance(pos[axis], (int, float)):
|
||||||
sys.exit(1)
|
original = pos[axis]
|
||||||
|
pos[axis] = round(float(pos[axis]), 2)
|
||||||
|
if original != pos[axis]:
|
||||||
|
coord_changed = True
|
||||||
|
return coord_changed
|
||||||
|
|
||||||
original_data = get_original_file(file_path)
|
def ensure_required_fields(info, filename):
|
||||||
is_new = not original_data
|
"""统一处理必要字段检查逻辑"""
|
||||||
info = data["info"]
|
|
||||||
filename = os.path.splitext(os.path.basename(file_path))[0]
|
|
||||||
|
|
||||||
# 自动修正逻辑
|
|
||||||
corrections = []
|
corrections = []
|
||||||
|
|
||||||
if info["name"] != filename:
|
if info["name"] != filename:
|
||||||
info["name"] = filename
|
info["name"] = filename
|
||||||
corrections.append(f"name 自动修正为 {filename}")
|
corrections.append(f"name 自动修正为 {filename}")
|
||||||
@@ -184,47 +282,144 @@ jobs:
|
|||||||
corrections.append("type 自动修正为 collect")
|
corrections.append("type 自动修正为 collect")
|
||||||
|
|
||||||
if not info["author"]:
|
if not info["author"]:
|
||||||
info["author"] = os.getenv("GITHUB_ACTOR")
|
info["author"] = os.getenv("GITHUB_ACTOR", "未知作者")
|
||||||
corrections.append(f"author 自动设置为 {info['author']}")
|
corrections.append(f"author 自动设置为 {info['author']}")
|
||||||
|
|
||||||
# 处理坐标保留两位小数
|
return corrections
|
||||||
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)):
|
def check_position_fields(positions):
|
||||||
original_y = pos["y"]
|
"""检查位置字段的有效性
|
||||||
pos["y"] = round(float(pos["y"]), 2)
|
|
||||||
if original_y != pos["y"]:
|
|
||||||
coord_changed = True
|
|
||||||
|
|
||||||
if coord_changed:
|
自动修复功能:
|
||||||
corrections.append("坐标值自动保留两位小数")
|
1. 缺少 type 字段时,自动设置为 'path'
|
||||||
|
2. type 字段无效时,自动修正为 'path'
|
||||||
# 检查 action 和 action_params 兼容性
|
3. 当 type 为 'path' 或 'target' 且缺少 move_mode 时,自动设置为 'walk'
|
||||||
bgi_version = info["bgiVersion"]
|
4. move_mode 字段无效时,自动修正为 'walk'
|
||||||
compatibility_issues = []
|
"""
|
||||||
validation_issues = []
|
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
|
||||||
|
|
||||||
for idx, pos in enumerate(data["positions"]):
|
|
||||||
# 验证 type 字段
|
# 验证 type 字段
|
||||||
if "type" in pos:
|
if "type" in pos:
|
||||||
pos_type = pos["type"]
|
pos_type = pos["type"]
|
||||||
if pos_type not in VALID_TYPES:
|
if pos_type not in VALID_TYPES:
|
||||||
validation_issues.append(f"位置 {idx+1}: type '{pos_type}' 无效,有效值为: {', '.join(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
|
# 当 type 为 path 或 target 时,验证 move_mode
|
||||||
if pos_type in ["path", "target"]:
|
if pos_type in ["path", "target"]:
|
||||||
if "move_mode" not in pos:
|
if "move_mode" not in pos:
|
||||||
validation_issues.append(f"位置 {idx+1}: type 为 '{pos_type}' 时必须指定 move_mode")
|
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:
|
elif pos["move_mode"] not in VALID_MOVE_MODES:
|
||||||
validation_issues.append(f"位置 {idx+1}: move_mode '{pos['move_mode']}' 无效,有效值为: {', '.join(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'")
|
||||||
|
|
||||||
# 验证 action 兼容性
|
# 检查第一个位置是否为 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_type = pos.get("action", "")
|
||||||
action_params = pos.get("params", "")
|
action_params = pos.get("params", "")
|
||||||
|
|
||||||
@@ -237,128 +432,185 @@ jobs:
|
|||||||
for issue in valid_issues:
|
for issue in valid_issues:
|
||||||
validation_issues.append(f"位置 {idx+1}: {issue}")
|
validation_issues.append(f"位置 {idx+1}: {issue}")
|
||||||
|
|
||||||
# 根据兼容性问题更新 bgiVersion
|
return compatibility_issues, validation_issues
|
||||||
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:
|
def update_bgi_version_for_compatibility(info, compatibility_issues, auto_fix):
|
||||||
max_required = max(required_versions, key=lambda v: VersionInfo.parse(v))
|
"""根据兼容性问题更新 BGI 版本"""
|
||||||
current_bgi = VersionInfo.parse(bgi_version.lstrip('v'))
|
corrections = []
|
||||||
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
|
# 首先检查并删除 v 前缀
|
||||||
print(f"原始版本号: {original_version}, 当前版本号: {info['version']}, 是否新文件: {is_new}")
|
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)
|
new_version = process_version(info["version"], original_version, is_new)
|
||||||
if new_version != info["version"]:
|
if new_version != info["version"]:
|
||||||
info["version"] = new_version
|
info["version"] = new_version
|
||||||
corrections.append(f"version 自动更新为 {new_version}")
|
all_corrections.append(f"version 自动更新为 {new_version}")
|
||||||
print(f"版本号已更新: {info['version']}")
|
print(f"版本号已更新: {info['version']}")
|
||||||
else:
|
else:
|
||||||
print(f"版本号未变化: {info['version']}")
|
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:
|
else:
|
||||||
pos[m] = 0.0
|
print(f"本地文件,保持版本号不变: {info['version']}")
|
||||||
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:
|
for issue in compatibility_issues:
|
||||||
notices.append(issue)
|
notices.append(issue)
|
||||||
|
|
||||||
# 添加验证问题到通知中
|
for issue in position_issues:
|
||||||
for issue in validation_issues:
|
|
||||||
notices.append(issue)
|
notices.append(issue)
|
||||||
|
|
||||||
# 保存修正
|
# 保存修正
|
||||||
if corrections:
|
if auto_fix:
|
||||||
with open(file_path, 'w', encoding='utf-8') as f: # 保存时也使用 UTF-8 编码
|
# 无论是否有问题,都打印所有自动修正项
|
||||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
if all_corrections:
|
||||||
subprocess.run(['git', 'add', file_path])
|
print("🔧 自动修正:")
|
||||||
print("🔧 自动修正:", ", ".join(corrections))
|
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
|
return notices
|
||||||
|
|
||||||
# 主流程
|
def main():
|
||||||
changed_files = subprocess.run(['git', 'diff', '--name-only', 'origin/main...HEAD'],
|
import argparse
|
||||||
capture_output=True, text=True).stdout.splitlines()
|
|
||||||
|
|
||||||
print(f"检测到的变更文件: {changed_files}")
|
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 = []
|
all_notices = []
|
||||||
for f in [f for f in changed_files if f.endswith('.json')]:
|
for root, _, files in os.walk(path):
|
||||||
print(f"\n🔍 校验文件: {f}")
|
for file in files:
|
||||||
notices = validate_file(f)
|
if file.endswith('.json'):
|
||||||
all_notices.extend([f"{f}: {n}" for n in notices])
|
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:
|
||||||
if subprocess.run(['git', 'diff', '--staged', '--quiet']).returncode:
|
print("\n所有校验注意事项:")
|
||||||
print("检测到需要提交的修改")
|
for notice in all_notices:
|
||||||
subprocess.run(['git', 'config', 'user.name', 'GitHub Actions'])
|
print(f"- {notice}")
|
||||||
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:
|
else:
|
||||||
print(f"❌ 推送失败: {push_result.stderr}")
|
print("\n✅ 所有文件校验完成,没有发现问题")
|
||||||
else:
|
else:
|
||||||
print("没有需要提交的修改")
|
print(f"❌ 无效的路径: {path}")
|
||||||
|
|
||||||
# 生成提醒信息
|
# 生成提醒信息
|
||||||
if all_notices:
|
if all_notices:
|
||||||
|
|||||||
Reference in New Issue
Block a user