197 lines
7.6 KiB
YAML
197 lines
7.6 KiB
YAML
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
# FILE INFORMATION
|
|
# DEFGROUP: Gitea.Workflow
|
|
# INGROUP: MokoStandards.CI
|
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
|
# PATH: /templates/workflows/universal/pr-check.yml.template
|
|
# VERSION: 05.00.00
|
|
# BRIEF: PR gate — branch policy + code validation before merge
|
|
|
|
name: "Universal: PR Check"
|
|
|
|
on:
|
|
pull_request:
|
|
types: [opened, synchronize, reopened, edited]
|
|
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
|
|
jobs:
|
|
# ── Branch Policy ──────────────────────────────────────────────────────
|
|
branch-policy:
|
|
name: Branch Policy
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check branch merge target
|
|
run: |
|
|
HEAD="${{ github.head_ref }}"
|
|
BASE="${{ github.base_ref }}"
|
|
|
|
echo "PR: ${HEAD} → ${BASE}"
|
|
|
|
ALLOWED=true
|
|
REASON=""
|
|
|
|
case "$HEAD" in
|
|
feature/*|feat/*)
|
|
if [ "$BASE" != "dev" ]; then
|
|
ALLOWED=false
|
|
REASON="Feature branches must target 'dev', not '${BASE}'"
|
|
fi
|
|
;;
|
|
fix/*|bugfix/*)
|
|
if [ "$BASE" != "dev" ]; then
|
|
ALLOWED=false
|
|
REASON="Fix branches must target 'dev', not '${BASE}'"
|
|
fi
|
|
;;
|
|
hotfix/*)
|
|
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
|
|
ALLOWED=false
|
|
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
|
|
fi
|
|
;;
|
|
alpha/*|beta/*)
|
|
if [ "$BASE" != "dev" ]; then
|
|
ALLOWED=false
|
|
REASON="Pre-release branches must target 'dev', not '${BASE}'"
|
|
fi
|
|
;;
|
|
rc/*)
|
|
if [ "$BASE" != "main" ]; then
|
|
ALLOWED=false
|
|
REASON="Release candidate branches must target 'main', not '${BASE}'"
|
|
fi
|
|
;;
|
|
dev)
|
|
if [ "$BASE" != "main" ]; then
|
|
ALLOWED=false
|
|
REASON="Dev branch can only merge into 'main', not '${BASE}'"
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
if [ "$ALLOWED" = false ]; then
|
|
echo "::error::${REASON}"
|
|
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
|
|
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
|
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
|
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
|
exit 1
|
|
fi
|
|
|
|
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
|
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
|
|
|
# ── Code Validation ────────────────────────────────────────────────────
|
|
validate:
|
|
name: Validate PR
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Detect platform
|
|
id: platform
|
|
run: |
|
|
# Read platform from XML manifest (<platform> tag) or plain text fallback
|
|
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1)
|
|
[ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]')
|
|
[ -z "$PLATFORM" ] && PLATFORM="generic"
|
|
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Setup PHP
|
|
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
|
run: |
|
|
if ! command -v php &> /dev/null; then
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
|
fi
|
|
|
|
- name: PHP syntax check
|
|
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
|
run: |
|
|
ERRORS=0
|
|
while IFS= read -r -d '' file; do
|
|
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
|
ERRORS=$((ERRORS + 1))
|
|
fi
|
|
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0)
|
|
echo "PHP lint: ${ERRORS} error(s)"
|
|
[ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; }
|
|
|
|
- name: Validate platform manifest
|
|
run: |
|
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
|
case "$PLATFORM" in
|
|
joomla)
|
|
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
|
if [ -z "$MANIFEST" ]; then
|
|
echo "::warning::No Joomla manifest found (WaaS site)"
|
|
exit 0
|
|
fi
|
|
echo "Manifest: ${MANIFEST}"
|
|
if command -v php &> /dev/null; then
|
|
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; }
|
|
fi
|
|
for ELEMENT in name version description; do
|
|
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
|
|
done
|
|
echo "Joomla manifest valid"
|
|
;;
|
|
dolibarr)
|
|
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
|
|
if [ -z "$MOD_FILE" ]; then
|
|
echo "::error::No mod*.class.php found"
|
|
exit 1
|
|
fi
|
|
echo "Dolibarr module: ${MOD_FILE}"
|
|
;;
|
|
*)
|
|
echo "Generic platform — no manifest validation"
|
|
;;
|
|
esac
|
|
|
|
- name: Check update stream format
|
|
run: |
|
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
|
case "$PLATFORM" in
|
|
joomla)
|
|
if [ -f "updates.xml" ]; then
|
|
if command -v php &> /dev/null; then
|
|
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; }
|
|
fi
|
|
echo "updates.xml valid"
|
|
fi
|
|
;;
|
|
dolibarr)
|
|
[ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt"
|
|
;;
|
|
esac
|
|
|
|
- name: Verify package source
|
|
run: |
|
|
SOURCE_DIR="src"
|
|
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
|
if [ ! -d "$SOURCE_DIR" ]; then
|
|
echo "::warning::No src/ or htdocs/ directory"
|
|
exit 0
|
|
fi
|
|
FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l)
|
|
echo "Source: ${FILE_COUNT} files"
|
|
[ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; }
|