chore: merge dev � ci-platform.yml and gitignore updates #16
@@ -0,0 +1,431 @@
|
||||
# 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/moko-platform
|
||||
# PATH: /.gitea/workflows/ci-platform.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: MokoStandards Platform CI — the standards engine validates itself
|
||||
#
|
||||
# +========================================================================+
|
||||
# | MOKOSTANDARDS PLATFORM CI |
|
||||
# +========================================================================+
|
||||
# | |
|
||||
# | This is NOT a generic CI workflow. This is the self-validation |
|
||||
# | pipeline for the central MokoStandards enterprise platform. |
|
||||
# | |
|
||||
# | It dogfoods every tool the platform ships to governed repos: |
|
||||
# | |
|
||||
# | Gate 1 — Code Quality phpcs (PSR-12), phpstan (L5), psalm |
|
||||
# | Gate 2 — Unit Tests phpunit with coverage threshold |
|
||||
# | Gate 3 — Self-Health bin/moko health against its own repo |
|
||||
# | Gate 4 — Governance Checks headers, secrets, structure, versions |
|
||||
# | Gate 5 — Template Lint validate workflow templates parse clean |
|
||||
# | |
|
||||
# | If it doesn't pass its own checks, it can't enforce them. |
|
||||
# | |
|
||||
# +========================================================================+
|
||||
|
||||
name: "Platform: MokoStandards CI"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- dev/**
|
||||
- rc/**
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'wiki/**'
|
||||
- '.gitea/ISSUE_TEMPLATE/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- dev/**
|
||||
- rc/**
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
full_suite:
|
||||
description: 'Run full validation suite (including slow checks)'
|
||||
required: false
|
||||
default: 'true'
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: ci-platform-${{ github.repository }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
PHP_VERSION: '8.2'
|
||||
|
||||
jobs:
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Gate 1 — Code Quality
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
code-quality:
|
||||
name: "Gate 1: Code Quality"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP ${{ env.PHP_VERSION }}
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php${{ env.PHP_VERSION }}-cli php${{ env.PHP_VERSION }}-mbstring \
|
||||
php${{ env.PHP_VERSION }}-xml php${{ env.PHP_VERSION }}-curl php${{ env.PHP_VERSION }}-zip \
|
||||
php${{ env.PHP_VERSION }}-intl >/dev/null 2>&1
|
||||
php -v
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: |
|
||||
composer install --no-interaction --prefer-dist
|
||||
echo "Dependencies installed: $(composer show | wc -l) packages"
|
||||
|
||||
- name: "PHP Syntax Check"
|
||||
run: |
|
||||
ERRORS=0
|
||||
CHECKED=0
|
||||
while IFS= read -r -d '' file; do
|
||||
CHECKED=$((CHECKED + 1))
|
||||
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
||||
echo "::error file=${file}::PHP syntax error"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done < <(find lib/ validate/ automation/ cli/ src/ deploy/ -name "*.php" -print0 2>/dev/null)
|
||||
|
||||
{
|
||||
echo "### PHP Syntax"
|
||||
echo "Checked ${CHECKED} files — ${ERRORS} error(s)"
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
[ "$ERRORS" -eq 0 ] || exit 1
|
||||
|
||||
- name: "PHPCS (PSR-12)"
|
||||
run: |
|
||||
vendor/bin/phpcs --standard=phpcs.xml --report=summary lib/ validate/ automation/ 2>&1 || {
|
||||
echo "::error::PHPCS found coding standard violations"
|
||||
echo "### PHPCS" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Coding standard violations detected. Run \`composer phpcs\` locally." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
}
|
||||
echo "### PHPCS" >> $GITHUB_STEP_SUMMARY
|
||||
echo "PSR-12 compliance: passed" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: "PHPStan (Level 5)"
|
||||
run: |
|
||||
vendor/bin/phpstan analyse -c phpstan.neon --no-progress --error-format=github 2>&1 || {
|
||||
echo "::error::PHPStan found type errors"
|
||||
echo "### PHPStan" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Static analysis errors detected. Run \`composer phpstan\` locally." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
}
|
||||
echo "### PHPStan" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Static analysis (level 5): passed" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: "Psalm"
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if [ -f "psalm.xml" ]; then
|
||||
vendor/bin/psalm --config=psalm.xml --no-progress --output-format=github 2>&1 || {
|
||||
echo "### Psalm" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Psalm found issues (advisory — not blocking)." >> $GITHUB_STEP_SUMMARY
|
||||
}
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Gate 2 — Unit Tests
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
tests:
|
||||
name: "Gate 2: Unit Tests"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: code-quality
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['8.1', '8.2', '8.3']
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP ${{ matrix.php }}
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php${{ matrix.php }}-cli php${{ matrix.php }}-mbstring \
|
||||
php${{ matrix.php }}-xml php${{ matrix.php }}-curl php${{ matrix.php }}-zip \
|
||||
php${{ matrix.php }}-intl >/dev/null 2>&1
|
||||
php -v
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
|
||||
- name: "PHPUnit (PHP ${{ matrix.php }})"
|
||||
run: |
|
||||
vendor/bin/phpunit --testdox 2>&1
|
||||
{
|
||||
echo "### PHPUnit (PHP ${{ matrix.php }})"
|
||||
echo "All tests passed."
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Gate 3 — Self-Health (Dogfood)
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
self-health:
|
||||
name: "Gate 3: Self-Health Check"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
needs: code-quality
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup PHP
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php${{ env.PHP_VERSION }}-cli php${{ env.PHP_VERSION }}-mbstring \
|
||||
php${{ env.PHP_VERSION }}-xml php${{ env.PHP_VERSION }}-curl php${{ env.PHP_VERSION }}-zip >/dev/null 2>&1
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
|
||||
- name: "Run bin/moko health against self"
|
||||
run: |
|
||||
php bin/moko health -- --path . --json > /tmp/health-report.json 2>&1 || true
|
||||
SCORE=$(cat /tmp/health-report.json | python3 -c "import sys,json; print(json.load(sys.stdin).get('percentage', 0))" 2>/dev/null || echo "0")
|
||||
LEVEL=$(cat /tmp/health-report.json | python3 -c "import sys,json; print(json.load(sys.stdin).get('level', 'unknown'))" 2>/dev/null || echo "unknown")
|
||||
|
||||
{
|
||||
echo "### Self-Health Report"
|
||||
echo ""
|
||||
echo "| Metric | Value |"
|
||||
echo "|---|---|"
|
||||
echo "| Score | ${SCORE}% |"
|
||||
echo "| Level | ${LEVEL} |"
|
||||
echo ""
|
||||
echo "The platform must pass its own health check to enforce it on others."
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Platform must score at least 80%
|
||||
python3 -c "exit(0 if float('${SCORE}') >= 80.0 else 1)" || {
|
||||
echo "::error::Self-health score ${SCORE}% is below 80% threshold"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Gate 4 — Governance Checks
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
governance:
|
||||
name: "Gate 4: Governance"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
needs: code-quality
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup PHP
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php${{ env.PHP_VERSION }}-cli php${{ env.PHP_VERSION }}-mbstring \
|
||||
php${{ env.PHP_VERSION }}-xml php${{ env.PHP_VERSION }}-curl >/dev/null 2>&1
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction --prefer-dist
|
||||
|
||||
- name: "License headers (SPDX)"
|
||||
run: |
|
||||
MISSING=0
|
||||
CHECKED=0
|
||||
while IFS= read -r -d '' file; do
|
||||
CHECKED=$((CHECKED + 1))
|
||||
if ! head -n 20 "$file" | grep -q "SPDX-License-Identifier:"; then
|
||||
echo "::warning file=${file}::Missing SPDX header"
|
||||
MISSING=$((MISSING + 1))
|
||||
fi
|
||||
done < <(find lib/ validate/ cli/ src/ automation/ deploy/ -name "*.php" -print0 2>/dev/null)
|
||||
|
||||
{
|
||||
echo "### License Headers"
|
||||
echo "Checked ${CHECKED} files — ${MISSING} missing SPDX headers"
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Advisory — warn but don't fail (yet)
|
||||
[ "$MISSING" -eq 0 ] || echo "::warning::${MISSING} files missing SPDX license headers"
|
||||
|
||||
- name: "Secret detection"
|
||||
run: |
|
||||
FOUND=0
|
||||
# Check for common secret patterns in source files
|
||||
while IFS= read -r -d '' file; do
|
||||
if grep -qEi '(password|secret|token|apikey|api_key)\s*[:=]\s*["\x27][^\s]{8,}' "$file" 2>/dev/null; then
|
||||
echo "::error file=${file}::Potential hardcoded secret detected"
|
||||
FOUND=$((FOUND + 1))
|
||||
fi
|
||||
done < <(find lib/ validate/ cli/ src/ automation/ deploy/ -name "*.php" -print0 2>/dev/null)
|
||||
|
||||
{
|
||||
echo "### Secret Detection"
|
||||
if [ "$FOUND" -eq 0 ]; then
|
||||
echo "No hardcoded secrets detected."
|
||||
else
|
||||
echo "${FOUND} potential secrets found."
|
||||
fi
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
[ "$FOUND" -eq 0 ] || exit 1
|
||||
|
||||
- name: "Version consistency"
|
||||
run: |
|
||||
# Extract version from composer.json
|
||||
COMPOSER_VER=$(python3 -c "import json; print(json.load(open('composer.json'))['version'])")
|
||||
# Extract version from README.md
|
||||
README_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1)
|
||||
|
||||
{
|
||||
echo "### Version Consistency"
|
||||
echo "| Source | Version |"
|
||||
echo "|---|---|"
|
||||
echo "| composer.json | ${COMPOSER_VER} |"
|
||||
echo "| README.md | ${README_VER:-not found} |"
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ -n "$README_VER" ] && [ "$COMPOSER_VER" != "$README_VER" ]; then
|
||||
echo "::warning::Version mismatch: composer.json=${COMPOSER_VER} vs README.md=${README_VER}"
|
||||
fi
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Gate 5 — Template Integrity
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
templates:
|
||||
name: "Gate 5: Template Integrity"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
needs: code-quality
|
||||
if: github.event_name != 'push' || github.event.inputs.full_suite != 'false'
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "Validate workflow templates"
|
||||
run: |
|
||||
ERRORS=0
|
||||
CHECKED=0
|
||||
|
||||
# Check all YAML workflow templates parse cleanly
|
||||
while IFS= read -r -d '' file; do
|
||||
CHECKED=$((CHECKED + 1))
|
||||
if ! python3 -c "import yaml; yaml.safe_load(open('${file}'))" 2>/dev/null; then
|
||||
echo "::error file=${file}::Invalid YAML"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done < <(find templates/workflows/ -name "*.yml" -o -name "*.yaml" 2>/dev/null | tr '\n' '\0')
|
||||
|
||||
# Also check the live workflows
|
||||
while IFS= read -r -d '' file; do
|
||||
CHECKED=$((CHECKED + 1))
|
||||
if ! python3 -c "import yaml; yaml.safe_load(open('${file}'))" 2>/dev/null; then
|
||||
echo "::error file=${file}::Invalid YAML"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done < <(find .gitea/workflows/ -name "*.yml" -o -name "*.yaml" 2>/dev/null | tr '\n' '\0')
|
||||
|
||||
{
|
||||
echo "### Template Integrity"
|
||||
echo "Validated ${CHECKED} YAML files — ${ERRORS} parse errors"
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
[ "$ERRORS" -eq 0 ] || exit 1
|
||||
|
||||
- name: "Validate gitignore templates"
|
||||
run: |
|
||||
TEMPLATES=0
|
||||
for GI in templates/configs/gitignore templates/configs/gitignore.dolibarr templates/configs/.gitignore.joomla; do
|
||||
if [ -f "$GI" ]; then
|
||||
TEMPLATES=$((TEMPLATES + 1))
|
||||
# Verify required entries
|
||||
for REQUIRED in ".claude/" "TODO.md" "*.min.css" "*.min.js" "wiki/"; do
|
||||
if ! grep -q "$REQUIRED" "$GI"; then
|
||||
echo "::error file=${GI}::Missing required entry: ${REQUIRED}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
echo "### Gitignore Templates" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Validated ${TEMPLATES} gitignore templates." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: "Validate PHP validation scripts"
|
||||
run: |
|
||||
ERRORS=0
|
||||
CHECKED=0
|
||||
while IFS= read -r -d '' file; do
|
||||
CHECKED=$((CHECKED + 1))
|
||||
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
||||
echo "::error file=${file}::Validation script has syntax error"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done < <(find validate/ -name "*.php" -print0 2>/dev/null)
|
||||
|
||||
{
|
||||
echo "### Validation Scripts"
|
||||
echo "Checked ${CHECKED} scripts — ${ERRORS} syntax errors"
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
[ "$ERRORS" -eq 0 ] || { echo "::error::Validation scripts must be error-free"; exit 1; }
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
# Summary
|
||||
# ═══════════════════════════════════════════════════════════════════════
|
||||
summary:
|
||||
name: "CI Summary"
|
||||
runs-on: ubuntu-latest
|
||||
needs: [code-quality, tests, self-health, governance, templates]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Check gate results
|
||||
run: |
|
||||
{
|
||||
echo "# MokoStandards Platform CI"
|
||||
echo ""
|
||||
echo "| Gate | Job | Status |"
|
||||
echo "|---|---|---|"
|
||||
echo "| 1 | Code Quality | ${{ needs.code-quality.result }} |"
|
||||
echo "| 2 | Unit Tests | ${{ needs.tests.result }} |"
|
||||
echo "| 3 | Self-Health | ${{ needs.self-health.result }} |"
|
||||
echo "| 4 | Governance | ${{ needs.governance.result }} |"
|
||||
echo "| 5 | Templates | ${{ needs.templates.result }} |"
|
||||
echo ""
|
||||
echo "> *The standards engine must pass its own standards.*"
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Fail if any required gate failed
|
||||
if [ "${{ needs.code-quality.result }}" = "failure" ] || \
|
||||
[ "${{ needs.tests.result }}" = "failure" ] || \
|
||||
[ "${{ needs.self-health.result }}" = "failure" ] || \
|
||||
[ "${{ needs.governance.result }}" = "failure" ] || \
|
||||
[ "${{ needs.templates.result }}" = "failure" ]; then
|
||||
echo "::error::One or more CI gates failed"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user