b529012 ✅ test(tests): avoid hardcoded missing path 07583c8 🐛 fix(tests): make doc link check awk-portable 6244aa3 🔧 chore(ci): relax gitattributes check 31dd0c5 🐛 fix(scripts): require existing project root 15d4d63 🔧 chore(ci): install python3-pip 90c6313 🔧 chore(ci): run tests in a single job d84eff0 🔧 chore(ci): make actions base url configurable 395598d 🔧 chore(ci): fix yaml step names 4ae2733 ✨ feat(sync_standards): auto-detect existing languages e97fb00 ✨ feat(sync_standards): default gitattributes to append da0ef2b ✅ test: add automated tests and ci workflow 99bef30 🎨 style(markdown): format all markdown files with prettier 7e96bc8 ♻️ refactor(tsl): split function.md into 44 modular files 0d6b2a0 📝 docs(readme): add quick decision table and TL;DR for distribution methods 3b2188f 🎨 style(markdown): format markdown files with prettier 41fc43b 📝 docs(tsl): document {Unit.}Type source annotation 3dceaf7 📝 docs(tsl): clarify tsf-only top-level rules and type annotations 3a63829 📝 docs(skills): add create-plan skill f02a707 🐛 fix(scripts): escape markdown backticks in vendor_playbook 064aa92 🐛 fix(scripts): correct bat scripts 4881feb 🔧 chore(gitattributes): enforce crlf for bat files 1fa3e2a 🐛 fix(scripts): escape parentheses in sync_standards output 3958cad 📝 docs(vendor_playbook): mention ci templates 9d059cf ✨ feat(templates): add gitea ci example a52bb24 📝 docs(codex_skills): normalize markdown formatting cc8ad4c 📝 docs(skills): slim commit-message 2a98e15 🐛 fix(skills): quote YAML descriptions 8f78d22 🐛 fix(sync_standards): generate minimal AGENTS.md 5547665 ✨ feat(skills): add commit-message suggestion skill 3fe8bd7 🔧 chore(sync_standards): create AGENTS.md on sync 283d311 ✨ feat(playbook): add syntax book and codex skills tooling 5b97ed5 📝 docs(tsl): clarify syntax references 27e0700 ✨ feat(skills): add pdf/docx/pptx/xlsx wrapper workflows 5551363 ✨ feat(skills): add debugging and bulk refactor workflows 1d4f548 ✨ feat(skills): add built-in workflow skills c0895dd 📝 docs(skills): add anthropics document-skills integration cf9f80d 🔧 chore(playbook): add Claude Code skills guide and align python templates git-subtree-dir: docs/standards/playbook git-subtree-split: b529012c59c5d67c3073884d7b617763647417fd
333 lines
9.8 KiB
Bash
333 lines
9.8 KiB
Bash
#!/usr/bin/env sh
|
||
# CI 模板验证脚本
|
||
|
||
set -eu
|
||
|
||
echo "========================================"
|
||
echo "🔧 CI 模板验证"
|
||
echo "========================================"
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
PLAYBOOK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||
TEMPLATES_DIR="$PLAYBOOK_ROOT/templates/ci"
|
||
|
||
VALIDATION_PASSED=0
|
||
VALIDATION_FAILED=0
|
||
ERRORS_FILE="/tmp/ci_template_validation_errors.txt"
|
||
REPORT_FILE="$SCRIPT_DIR/ci_validation_report.txt"
|
||
|
||
> "$ERRORS_FILE"
|
||
> "$REPORT_FILE"
|
||
|
||
echo "📁 模板目录: $TEMPLATES_DIR"
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 辅助函数
|
||
# ============================================
|
||
|
||
validate_file_exists() {
|
||
local file="$1"
|
||
local description="$2"
|
||
|
||
if [ -f "$file" ]; then
|
||
echo " ✅ $description: $(basename "$file")"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
return 0
|
||
else
|
||
echo " ❌ $description: $(basename "$file") - 文件不存在"
|
||
echo "文件不存在: $file" >> "$ERRORS_FILE"
|
||
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
validate_yaml_syntax() {
|
||
local file="$1"
|
||
local description="$2"
|
||
|
||
if ! command -v yamllint >/dev/null 2>&1; then
|
||
echo " ⚠️ $description: 跳过(yamllint 未安装)"
|
||
return 0
|
||
fi
|
||
|
||
if yamllint -d relaxed "$file" >/dev/null 2>&1; then
|
||
echo " ✅ $description: YAML 语法正确"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
return 0
|
||
else
|
||
echo " ❌ $description: YAML 语法错误"
|
||
echo "YAML 语法错误: $file" >> "$ERRORS_FILE"
|
||
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
validate_workflow_structure() {
|
||
local file="$1"
|
||
|
||
echo " 📋 检查 workflow 结构:"
|
||
|
||
# 检查必要字段
|
||
if grep -q "^name:" "$file"; then
|
||
echo " ✓ 包含 name 字段"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
else
|
||
echo " ✗ 缺少 name 字段"
|
||
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||
fi
|
||
|
||
if grep -q "^on:" "$file"; then
|
||
echo " ✓ 包含 on 触发器"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
else
|
||
echo " ✗ 缺少 on 触发器"
|
||
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||
fi
|
||
|
||
if grep -q "^jobs:" "$file"; then
|
||
echo " ✓ 包含 jobs 定义"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
else
|
||
echo " ✗ 缺少 jobs 定义"
|
||
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||
fi
|
||
|
||
# 检查 runs-on
|
||
if grep -q "runs-on:" "$file"; then
|
||
runner=$(grep "runs-on:" "$file" | head -1 | awk '{print $2}')
|
||
echo " ✓ 配置了 runner: $runner"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
else
|
||
echo " ✗ 缺少 runs-on 配置"
|
||
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||
fi
|
||
|
||
# 检查 steps
|
||
if grep -q "steps:" "$file"; then
|
||
step_count=$(grep -c "^ - name:" "$file" || echo 0)
|
||
echo " ✓ 包含 $step_count 个步骤"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
else
|
||
echo " ✗ 缺少 steps 定义"
|
||
VALIDATION_FAILED=$((VALIDATION_FAILED + 1))
|
||
fi
|
||
}
|
||
|
||
# ============================================
|
||
# 查找并验证 Gitea workflow 文件
|
||
# ============================================
|
||
|
||
echo "🔍 查找 Gitea workflow 文件"
|
||
GITEA_WORKFLOWS_DIR="$TEMPLATES_DIR/gitea/.gitea/workflows"
|
||
|
||
if [ ! -d "$GITEA_WORKFLOWS_DIR" ]; then
|
||
echo "⚠️ Gitea workflows 目录不存在: $GITEA_WORKFLOWS_DIR"
|
||
echo "跳过 Gitea workflow 验证"
|
||
else
|
||
WORKFLOW_FILES=$(find "$GITEA_WORKFLOWS_DIR" -name "*.yml" -o -name "*.yaml" 2>/dev/null || true)
|
||
|
||
if [ -z "$WORKFLOW_FILES" ]; then
|
||
echo "⚠️ 未找到 Gitea workflow 文件"
|
||
else
|
||
for workflow in $WORKFLOW_FILES; do
|
||
echo ""
|
||
echo "🔍 验证 $(basename "$workflow")"
|
||
|
||
if validate_file_exists "$workflow" "$(basename "$workflow")"; then
|
||
# 验证 YAML 语法
|
||
validate_yaml_syntax "$workflow" "$(basename "$workflow")"
|
||
|
||
# 验证 workflow 结构
|
||
validate_workflow_structure "$workflow"
|
||
|
||
# 检查是否包含中文注释(符合项目风格)
|
||
if grep -q "# .*[\u4e00-\u9fa5]" "$workflow" 2>/dev/null || grep -qP "[\x{4e00}-\x{9fa5}]" "$workflow" 2>/dev/null; then
|
||
echo " ✓ 包含中文注释(符合项目风格)"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
fi
|
||
|
||
# 检查配置区域标记
|
||
if grep -q "# ====.*配置.*====" "$workflow"; then
|
||
echo " ✓ 包含配置区域标记"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
fi
|
||
|
||
# 检查环境变量配置
|
||
if grep -q "^env:" "$workflow"; then
|
||
echo " ✓ 包含环境变量配置"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
fi
|
||
fi
|
||
done
|
||
fi
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 查找并验证 GitHub Actions workflow 文件
|
||
# ============================================
|
||
|
||
echo "🔍 查找 GitHub Actions workflow 文件"
|
||
GITHUB_WORKFLOWS_DIR="$TEMPLATES_DIR/github/.github/workflows"
|
||
|
||
if [ -d "$GITHUB_WORKFLOWS_DIR" ]; then
|
||
WORKFLOW_FILES=$(find "$GITHUB_WORKFLOWS_DIR" -name "*.yml" -o -name "*.yaml" 2>/dev/null || true)
|
||
|
||
if [ -n "$WORKFLOW_FILES" ]; then
|
||
for workflow in $WORKFLOW_FILES; do
|
||
echo ""
|
||
echo "🔍 验证 $(basename "$workflow")"
|
||
|
||
if validate_file_exists "$workflow" "$(basename "$workflow")"; then
|
||
# 验证 YAML 语法
|
||
validate_yaml_syntax "$workflow" "$(basename "$workflow")"
|
||
|
||
# 验证 workflow 结构
|
||
validate_workflow_structure "$workflow"
|
||
fi
|
||
done
|
||
fi
|
||
else
|
||
echo "ℹ️ GitHub Actions workflows 目录不存在(可选)"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 验证特定 workflow 模板
|
||
# ============================================
|
||
|
||
echo "🔍 验证特定 workflow 模板"
|
||
|
||
# 检查 standards-check workflow
|
||
STANDARDS_CHECK="$GITEA_WORKFLOWS_DIR/standards-check.yml"
|
||
if [ -f "$STANDARDS_CHECK" ]; then
|
||
echo ""
|
||
echo "📋 验证 standards-check.yml:"
|
||
|
||
# 检查是否包含格式化检查
|
||
if grep -q "格式化\|format" "$STANDARDS_CHECK"; then
|
||
echo " ✓ 包含格式化检查"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
fi
|
||
|
||
# 检查是否包含 lint 检查
|
||
if grep -q "lint\|检查" "$STANDARDS_CHECK"; then
|
||
echo " ✓ 包含 lint 检查"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
fi
|
||
else
|
||
echo "ℹ️ 未找到 standards-check.yml(可选)"
|
||
fi
|
||
|
||
# 检查 test workflow
|
||
TEST_WORKFLOW="$GITEA_WORKFLOWS_DIR/test.yml"
|
||
if [ -f "$TEST_WORKFLOW" ]; then
|
||
echo ""
|
||
echo "📋 验证 test.yml:"
|
||
|
||
# 检查是否包含测试步骤
|
||
if grep -q "测试\|test" "$TEST_WORKFLOW"; then
|
||
echo " ✓ 包含测试步骤"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
fi
|
||
|
||
# 检查是否配置了测试矩阵
|
||
if grep -q "strategy:" "$TEST_WORKFLOW" && grep -q "matrix:" "$TEST_WORKFLOW"; then
|
||
echo " ✓ 配置了测试矩阵"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
fi
|
||
else
|
||
echo "ℹ️ 未找到 test.yml(可选)"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 验证 README 文档
|
||
# ============================================
|
||
|
||
echo "🔍 验证 CI 模板文档"
|
||
|
||
if [ -d "$TEMPLATES_DIR" ]; then
|
||
# 查找 README
|
||
README_FILE=""
|
||
for name in README.md readme.md README README.txt; do
|
||
if [ -f "$TEMPLATES_DIR/$name" ]; then
|
||
README_FILE="$TEMPLATES_DIR/$name"
|
||
break
|
||
fi
|
||
done
|
||
|
||
if [ -n "$README_FILE" ]; then
|
||
echo " ✅ 找到说明文档: $(basename "$README_FILE")"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
|
||
# 检查文档内容
|
||
if grep -qi "使用\|usage\|how to" "$README_FILE"; then
|
||
echo " ✓ 包含使用说明"
|
||
VALIDATION_PASSED=$((VALIDATION_PASSED + 1))
|
||
fi
|
||
else
|
||
echo " ⚠️ 未找到 README 文档(建议添加)"
|
||
fi
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# ============================================
|
||
# 生成验证报告
|
||
# ============================================
|
||
|
||
echo "========================================"
|
||
echo "📊 验证结果统计"
|
||
echo "========================================"
|
||
echo "✅ 通过: $VALIDATION_PASSED"
|
||
echo "❌ 失败: $VALIDATION_FAILED"
|
||
|
||
if [ $((VALIDATION_PASSED + VALIDATION_FAILED)) -gt 0 ]; then
|
||
echo "📈 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
|
||
else
|
||
echo "📈 通过率: N/A (无测试项)"
|
||
fi
|
||
|
||
echo ""
|
||
|
||
# 写入报告文件
|
||
{
|
||
echo "CI 模板验证报告"
|
||
echo "===================="
|
||
echo ""
|
||
echo "验证时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||
echo "模板目录: $TEMPLATES_DIR"
|
||
echo ""
|
||
echo "统计结果:"
|
||
echo " 通过: $VALIDATION_PASSED"
|
||
echo " 失败: $VALIDATION_FAILED"
|
||
if [ $((VALIDATION_PASSED + VALIDATION_FAILED)) -gt 0 ]; then
|
||
echo " 通过率: $(awk "BEGIN {printf \"%.1f\", ($VALIDATION_PASSED * 100.0) / ($VALIDATION_PASSED + $VALIDATION_FAILED)}")%"
|
||
fi
|
||
echo ""
|
||
if [ -s "$ERRORS_FILE" ]; then
|
||
echo "错误详情:"
|
||
cat "$ERRORS_FILE"
|
||
fi
|
||
} > "$REPORT_FILE"
|
||
|
||
echo "📄 详细报告: $REPORT_FILE"
|
||
echo "========================================"
|
||
|
||
# 清理临时文件
|
||
rm -f "$ERRORS_FILE"
|
||
|
||
# 返回结果
|
||
if [ "$VALIDATION_FAILED" -eq 0 ]; then
|
||
echo "✅ 所有 CI 模板验证通过"
|
||
exit 0
|
||
else
|
||
echo "❌ CI 模板验证失败 ($VALIDATION_FAILED 个错误)"
|
||
exit 1
|
||
fi
|