#!/usr/bin/env python3 """ GitHub Actions 工作流程驗證腳本 此腳本驗證 GitHub Actions 工作流程文件的語法和配置正確性。 """ import sys from pathlib import Path import yaml def validate_yaml_syntax(file_path: Path) -> bool: """驗證 YAML 文件語法""" try: with open(file_path, encoding="utf-8") as f: yaml.safe_load(f) print(f"✅ {file_path.name}: YAML 語法正確") return True except yaml.YAMLError as e: print(f"❌ {file_path.name}: YAML 語法錯誤 - {e}") return False except Exception as e: print(f"❌ {file_path.name}: 讀取文件失敗 - {e}") return False def validate_workflow_structure(file_path: Path) -> bool: """驗證工作流程結構""" try: with open(file_path, encoding="utf-8") as f: workflow = yaml.safe_load(f) # 檢查是否成功解析 if workflow is None: print(f"❌ {file_path.name}: 文件為空或解析失敗") return False # 檢查必需的頂級字段 # 注意:YAML 會將 'on' 解析為 True,所以我們需要特殊處理 required_fields = ["name", "jobs"] for field in required_fields: if field not in workflow: print(f"❌ {file_path.name}: 缺少必需字段 '{field}'") print(f" 實際字段: {list(workflow.keys())}") return False # 檢查 'on' 字段(可能被解析為 True) if "on" not in workflow and True not in workflow: print(f"❌ {file_path.name}: 缺少觸發條件 'on'") print(f" 實際字段: {list(workflow.keys())}") return False # 檢查 jobs 結構 if not isinstance(workflow["jobs"], dict): print(f"❌ {file_path.name}: 'jobs' 必須是字典") return False if not workflow["jobs"]: print(f"❌ {file_path.name}: 'jobs' 不能為空") return False print(f"✅ {file_path.name}: 工作流程結構正確") return True except Exception as e: print(f"❌ {file_path.name}: 結構驗證失敗 - {e}") return False def validate_build_desktop_workflow(file_path: Path) -> bool: """驗證桌面構建工作流程的特定配置""" try: with open(file_path, encoding="utf-8") as f: workflow = yaml.safe_load(f) # 檢查 matrix 配置 build_job = workflow["jobs"].get("build-desktop", {}) strategy = build_job.get("strategy", {}) matrix = strategy.get("matrix", {}) if "include" not in matrix: print(f"❌ {file_path.name}: 缺少 matrix.include 配置") return False # 檢查平台配置 platforms = matrix["include"] expected_platforms = {"windows", "macos-intel", "macos-arm64", "linux"} actual_platforms = {item.get("name") for item in platforms} if not expected_platforms.issubset(actual_platforms): missing = expected_platforms - actual_platforms print(f"❌ {file_path.name}: 缺少平台配置: {missing}") return False print(f"✅ {file_path.name}: 桌面構建配置正確") return True except Exception as e: print(f"❌ {file_path.name}: 桌面構建驗證失敗 - {e}") return False def validate_publish_workflow(file_path: Path) -> bool: """驗證發佈工作流程的特定配置""" try: with open(file_path, encoding="utf-8") as f: workflow = yaml.safe_load(f) # 檢查輸入參數 - 注意 'on' 可能被解析為 True on_section = workflow.get("on") or workflow.get(True) if not on_section: print(f"❌ {file_path.name}: 找不到觸發條件") return False workflow_dispatch = on_section.get("workflow_dispatch", {}) inputs = workflow_dispatch.get("inputs", {}) required_inputs = {"version_type", "include_desktop"} actual_inputs = set(inputs.keys()) if not required_inputs.issubset(actual_inputs): missing = required_inputs - actual_inputs print(f"❌ {file_path.name}: 缺少輸入參數: {missing}") print(f" 實際輸入參數: {actual_inputs}") return False # 檢查是否有桌面應用處理步驟 release_job = workflow["jobs"].get("release", {}) steps = release_job.get("steps", []) has_desktop_steps = any( "desktop" in step.get("name", "").lower() for step in steps ) if not has_desktop_steps: print(f"❌ {file_path.name}: 缺少桌面應用處理步驟") return False print(f"✅ {file_path.name}: 發佈工作流程配置正確") return True except Exception as e: print(f"❌ {file_path.name}: 發佈工作流程驗證失敗 - {e}") return False def main(): """主函數""" print("🔍 驗證 GitHub Actions 工作流程...") print() # 獲取工作流程目錄 workflows_dir = Path(__file__).parent.parent / ".github" / "workflows" if not workflows_dir.exists(): print(f"❌ 工作流程目錄不存在: {workflows_dir}") sys.exit(1) # 查找所有工作流程文件 workflow_files = list(workflows_dir.glob("*.yml")) + list( workflows_dir.glob("*.yaml") ) if not workflow_files: print(f"❌ 在 {workflows_dir} 中沒有找到工作流程文件") sys.exit(1) print(f"📁 找到 {len(workflow_files)} 個工作流程文件") print() # 驗證每個文件 all_valid = True for workflow_file in sorted(workflow_files): print(f"🔍 驗證 {workflow_file.name}...") # 基本語法驗證 if not validate_yaml_syntax(workflow_file): all_valid = False continue # 結構驗證 if not validate_workflow_structure(workflow_file): all_valid = False continue # 特定工作流程驗證 if workflow_file.name == "build-desktop.yml": if not validate_build_desktop_workflow(workflow_file): all_valid = False elif workflow_file.name == "publish.yml": if not validate_publish_workflow(workflow_file): all_valid = False print() # 總結 if all_valid: print("🎉 所有工作流程文件驗證通過!") print() print("📋 下一步:") print(" 1. 提交並推送更改到 GitHub") print(" 2. 測試 'Build Desktop Applications' 工作流程") print(" 3. 測試 'Build Desktop & Release' 工作流程") print(" 4. 驗證桌面應用是否正確包含在發佈中") else: print("❌ 部分工作流程文件驗證失敗") print("請修復上述問題後重新運行驗證") sys.exit(1) if __name__ == "__main__": main()