Remove update.xml calls, fix FTP secrets, and sync workflows with MokoStandards #56

Merged
Copilot merged 3 commits from copilot/remove-update-xml-calls-fix-ftp-secrets into main 2026-01-18 01:28:13 +00:00
13 changed files with 2559 additions and 402 deletions
Showing only changes of commit 1388e6610a - Show all commits

View File

@@ -1,3 +1,31 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.CI
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/ci.yml
# VERSION: 02.00.00
# BRIEF: Continuous integration workflow using local reusable workflow
# NOTE: Delegates CI execution to local reusable-ci-validation.yml for repository validation
name: Continuous Integration name: Continuous Integration
on: on:
@@ -16,10 +44,13 @@ on:
permissions: permissions:
contents: read contents: read
pull-requests: write
checks: write
jobs: jobs:
validation: ci:
uses: mokoconsulting-tech/MokoStandards/.github/workflows/reusable-ci-validation.yml@main name: Repository Validation Pipeline
uses: ./.github/workflows/reusable-ci-validation.yml
with: with:
validation-scripts-path: 'scripts/validate' profile: full
secrets: inherit secrets: inherit

View File

@@ -19,8 +19,8 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: GitHub.Workflow # DEFGROUP: GitHub.Workflow
# INGROUP: Moko-Cassiopeia.Security # INGROUP: MokoStandards.Security
# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/dependency-review.yml # PATH: /.github/workflows/dependency-review.yml
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: Dependency review workflow for vulnerability scanning in pull requests # BRIEF: Dependency review workflow for vulnerability scanning in pull requests

View File

@@ -902,7 +902,7 @@ jobs:
SFTP_AUTH_MODE: ${{ steps.sftp.outputs.auth_mode }} SFTP_AUTH_MODE: ${{ steps.sftp.outputs.auth_mode }}
SFTP_REMOTE_PATH: ${{ steps.sftp.outputs.remote_path }} SFTP_REMOTE_PATH: ${{ steps.sftp.outputs.remote_path }}
SFTP_HOST: ${{ steps.sftp.outputs.host }} SFTP_HOST: ${{ steps.sftp.outputs.host }}
SFTP_PORT: ${{ steps.sftp.outputs.port }} SRC_PORT: ${{ steps.sftp.outputs.port }}
run: | run: |
set -euo pipefail set -euo pipefail
@@ -925,7 +925,7 @@ jobs:
echo "- auth_mode: ${SFTP_AUTH_MODE:-unknown}" echo "- auth_mode: ${SFTP_AUTH_MODE:-unknown}"
echo "- remote_path: ${SFTP_REMOTE_PATH:-unknown}" echo "- remote_path: ${SFTP_REMOTE_PATH:-unknown}"
echo "- host: ${SFTP_HOST:-unknown}" echo "- host: ${SFTP_HOST:-unknown}"
echo "- port: ${SFTP_PORT:-unknown}" echo "- port: ${SRC_PORT:-unknown}"
} >> RELEASE_NOTES.md } >> RELEASE_NOTES.md
- name: Create GitHub release and attach ZIP - name: Create GitHub release and attach ZIP

222
.github/workflows/reusable-build.yml vendored Normal file
View File

@@ -0,0 +1,222 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Reusable
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/reusable-build.yml
# VERSION: 01.00.00
# BRIEF: Reusable type-aware build workflow for Joomla, Dolibarr, and generic projects
# NOTE: Automatically detects project type and applies appropriate build steps
name: Reusable Build
on:
workflow_call:
inputs:
php-version:
description: 'PHP version to use for build'
required: false
type: string
default: '8.1'
node-version:
description: 'Node.js version to use for build'
required: false
type: string
default: '20.x'
working-directory:
description: 'Working directory for build'
required: false
type: string
default: '.'
upload-artifacts:
description: 'Upload build artifacts'
required: false
type: boolean
default: true
artifact-name:
description: 'Name for uploaded artifacts'
required: false
type: string
default: 'build-artifacts'
permissions:
contents: read
jobs:
detect:
name: Detect Project Type
uses: ./.github/workflows/reusable-project-detector.yml
with:
working-directory: ${{ inputs.working-directory }}
build:
name: Build (${{ needs.detect.outputs.project-type }})
runs-on: ubuntu-latest
needs: detect
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup PHP
if: needs.detect.outputs.has-php == 'true'
uses: shivammathur/setup-php@v2
with:
php-version: ${{ inputs.php-version }}
extensions: mbstring, xml, zip, json
tools: composer:v2
- name: Setup Node.js
if: needs.detect.outputs.has-node == 'true'
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}
- name: Get Composer cache directory
if: needs.detect.outputs.has-php == 'true'
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
if: needs.detect.outputs.has-php == 'true'
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Cache Node modules
if: needs.detect.outputs.has-node == 'true'
uses: actions/cache@v5
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-node-
- name: Install PHP dependencies
if: needs.detect.outputs.has-php == 'true'
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "composer.json" ]; then
composer install --prefer-dist --no-progress --no-interaction
echo "✅ Composer dependencies installed" >> $GITHUB_STEP_SUMMARY
fi
- name: Install Node dependencies
if: needs.detect.outputs.has-node == 'true'
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "package.json" ]; then
npm ci
echo "✅ npm dependencies installed" >> $GITHUB_STEP_SUMMARY
fi
- name: Build Joomla Extension
if: needs.detect.outputs.project-type == 'joomla'
working-directory: ${{ inputs.working-directory }}
run: |
echo "### 🏗️ Building Joomla Extension" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Extension Type:** ${{ needs.detect.outputs.extension-type }}" >> $GITHUB_STEP_SUMMARY
# Run npm build if package.json has build script
if [ -f "package.json" ] && grep -q '"build"' package.json; then
echo "Running npm build..."
npm run build
echo "- ✅ npm build completed" >> $GITHUB_STEP_SUMMARY
fi
# Run composer scripts if available
if [ -f "composer.json" ] && grep -q '"build"' composer.json; then
echo "Running composer build..."
composer run-script build
echo "- ✅ composer build completed" >> $GITHUB_STEP_SUMMARY
fi
echo "- ✅ Joomla extension build completed" >> $GITHUB_STEP_SUMMARY
- name: Build Dolibarr Module
if: needs.detect.outputs.project-type == 'dolibarr'
working-directory: ${{ inputs.working-directory }}
run: |
echo "### 🏗️ Building Dolibarr Module" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Run npm build if available
if [ -f "package.json" ] && grep -q '"build"' package.json; then
echo "Running npm build..."
npm run build
echo "- ✅ npm build completed" >> $GITHUB_STEP_SUMMARY
fi
# Install Dolibarr-specific dependencies
if [ -f "composer.json" ]; then
composer install --no-dev --optimize-autoloader
echo "- ✅ Production dependencies installed" >> $GITHUB_STEP_SUMMARY
fi
echo "- ✅ Dolibarr module build completed" >> $GITHUB_STEP_SUMMARY
- name: Build Generic Project
if: needs.detect.outputs.project-type == 'generic'
working-directory: ${{ inputs.working-directory }}
run: |
echo "### 🏗️ Building Generic Project" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Try various build methods
if [ -f "package.json" ] && grep -q '"build"' package.json; then
npm run build
echo "- ✅ npm build completed" >> $GITHUB_STEP_SUMMARY
fi
if [ -f "Makefile" ]; then
make build 2>/dev/null || echo "- Makefile build not available" >> $GITHUB_STEP_SUMMARY
fi
echo "- ✅ Generic project build completed" >> $GITHUB_STEP_SUMMARY
- name: Verify build output
working-directory: ${{ inputs.working-directory }}
run: |
echo "### 📦 Build Output" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Check for common build output directories
for dir in dist build public out; do
if [ -d "$dir" ]; then
echo "- ✅ Found build output: \`$dir/\`" >> $GITHUB_STEP_SUMMARY
du -sh "$dir" >> $GITHUB_STEP_SUMMARY
fi
done
- name: Upload build artifacts
if: inputs.upload-artifacts
uses: actions/upload-artifact@v6
with:
name: ${{ inputs.artifact-name }}-${{ needs.detect.outputs.project-type }}
path: |
${{ inputs.working-directory }}/dist/
${{ inputs.working-directory }}/build/
${{ inputs.working-directory }}/public/
${{ inputs.working-directory }}/out/
retention-days: 7
if-no-files-found: ignore

View File

@@ -0,0 +1,534 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Reusable
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/reusable-ci-validation.yml
# VERSION: 01.00.00
# BRIEF: Reusable CI validation workflow for repository standards enforcement
# NOTE: Supports multiple validation profiles (basic, full, strict) with configurable checks
name: Reusable CI Validation
on:
workflow_call:
inputs:
profile:
description: 'Validation profile (basic, full, strict)'
required: false
type: string
default: 'basic'
node-version:
description: 'Node.js version for frontend validation'
required: false
type: string
default: '20.x'
php-version:
description: 'PHP version for backend validation'
required: false
type: string
default: '8.1'
working-directory:
description: 'Working directory for validation'
required: false
type: string
default: '.'
validate-manifests:
description: 'Validate XML manifests (Joomla/Dolibarr)'
required: false
type: boolean
default: true
validate-changelogs:
description: 'Validate CHANGELOG.md format and structure'
required: false
type: boolean
default: true
validate-licenses:
description: 'Validate license headers in source files'
required: false
type: boolean
default: true
validate-security:
description: 'Check for secrets and security issues'
required: false
type: boolean
default: true
fail-on-warnings:
description: 'Fail the workflow on validation warnings'
required: false
type: boolean
default: false
permissions:
contents: read
pull-requests: write
checks: write
jobs:
setup:
name: Setup Validation Environment
runs-on: ubuntu-latest
outputs:
has-php: ${{ steps.detect.outputs.has-php }}
has-node: ${{ steps.detect.outputs.has-node }}
has-scripts: ${{ steps.detect.outputs.has-scripts }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Detect project components
id: detect
working-directory: ${{ inputs.working-directory }}
run: |
# Detect PHP files
if find . -name "*.php" -type f | head -1 | grep -q .; then
echo "has-php=true" >> $GITHUB_OUTPUT
echo "✅ PHP files detected" >> $GITHUB_STEP_SUMMARY
else
echo "has-php=false" >> $GITHUB_OUTPUT
echo " No PHP files detected" >> $GITHUB_STEP_SUMMARY
fi
# Detect Node.js project
if [ -f "package.json" ]; then
echo "has-node=true" >> $GITHUB_OUTPUT
echo "✅ Node.js project detected" >> $GITHUB_STEP_SUMMARY
else
echo "has-node=false" >> $GITHUB_OUTPUT
echo " No Node.js project detected" >> $GITHUB_STEP_SUMMARY
fi
# Detect validation scripts
if [ -d "scripts/validate" ] || [ -d ".github/scripts/validate" ]; then
echo "has-scripts=true" >> $GITHUB_OUTPUT
echo "✅ Validation scripts found" >> $GITHUB_STEP_SUMMARY
else
echo "has-scripts=false" >> $GITHUB_OUTPUT
echo " No validation scripts found" >> $GITHUB_STEP_SUMMARY
fi
required-validations:
name: Required Validations
runs-on: ubuntu-latest
needs: setup
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Normalize line endings
run: git config --global core.autocrlf false
- name: Setup PHP
if: needs.setup.outputs.has-php == 'true'
uses: shivammathur/setup-php@v2
with:
php-version: ${{ inputs.php-version }}
extensions: mbstring, xml
coverage: none
- name: Setup Node.js
if: needs.setup.outputs.has-node == 'true'
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}
- name: Make scripts executable
if: needs.setup.outputs.has-scripts == 'true'
working-directory: ${{ inputs.working-directory }}
run: |
if [ -d "scripts" ]; then
find scripts -name "*.sh" -type f -exec chmod +x {} \;
fi
if [ -d ".github/scripts" ]; then
find .github/scripts -name "*.sh" -type f -exec chmod +x {} \;
fi
- name: Validate XML manifests
if: inputs.validate-manifests
working-directory: ${{ inputs.working-directory }}
run: |
echo "### 📋 Manifest Validation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f "scripts/validate/manifest.sh" ]; then
echo "Running manifest validation script..." >> $GITHUB_STEP_SUMMARY
if ./scripts/validate/manifest.sh 2>&1 | tee -a $GITHUB_STEP_SUMMARY; then
echo "✅ Manifest validation passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Manifest validation script failed" >> $GITHUB_STEP_SUMMARY
exit 1
fi
elif [ -f ".github/scripts/validate/manifest.sh" ]; then
echo "Running manifest validation script..." >> $GITHUB_STEP_SUMMARY
if ./.github/scripts/validate/manifest.sh 2>&1 | tee -a $GITHUB_STEP_SUMMARY; then
echo "✅ Manifest validation passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Manifest validation script failed" >> $GITHUB_STEP_SUMMARY
exit 1
fi
elif command -v xmllint >/dev/null 2>&1; then
# Basic XML validation using xmllint
echo "Using xmllint for basic XML validation..." >> $GITHUB_STEP_SUMMARY
XML_FOUND=false
ERROR_FOUND=false
while IFS= read -r file; do
XML_FOUND=true
if ! xmllint --noout "$file" 2>&1 | tee -a $GITHUB_STEP_SUMMARY; then
echo "❌ Invalid XML: $file" >> $GITHUB_STEP_SUMMARY
ERROR_FOUND=true
fi
done < <(find . -name "*.xml" -type f ! -path "*/node_modules/*" ! -path "*/.git/*")
if [ "$ERROR_FOUND" = true ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "❌ XML validation failed" >> $GITHUB_STEP_SUMMARY
exit 1
elif [ "$XML_FOUND" = true ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ Basic XML validation passed" >> $GITHUB_STEP_SUMMARY
else
echo " No XML files found to validate" >> $GITHUB_STEP_SUMMARY
fi
else
echo " No manifest validation script or xmllint available" >> $GITHUB_STEP_SUMMARY
echo "Skipping XML validation" >> $GITHUB_STEP_SUMMARY
fi
- name: Validate XML well-formedness
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "scripts/validate/xml_wellformed.sh" ]; then
./scripts/validate/xml_wellformed.sh
elif [ -f ".github/scripts/validate/xml_wellformed.sh" ]; then
./.github/scripts/validate/xml_wellformed.sh
else
echo " No XML well-formedness validation script found, skipping"
fi
- name: Validate PHP syntax
if: needs.setup.outputs.has-php == 'true'
working-directory: ${{ inputs.working-directory }}
run: |
echo "### 🔍 PHP Syntax Validation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f "scripts/validate/php_syntax.sh" ]; then
echo "Running PHP syntax validation script..." >> $GITHUB_STEP_SUMMARY
if ./scripts/validate/php_syntax.sh 2>&1 | tee -a $GITHUB_STEP_SUMMARY; then
echo "✅ PHP syntax validation passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ PHP syntax validation script failed" >> $GITHUB_STEP_SUMMARY
exit 1
fi
elif [ -f ".github/scripts/validate/php_syntax.sh" ]; then
echo "Running PHP syntax validation script..." >> $GITHUB_STEP_SUMMARY
if ./.github/scripts/validate/php_syntax.sh 2>&1 | tee -a $GITHUB_STEP_SUMMARY; then
echo "✅ PHP syntax validation passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ PHP syntax validation script failed" >> $GITHUB_STEP_SUMMARY
exit 1
fi
else
# Basic PHP syntax check
echo "Running basic PHP syntax check..." >> $GITHUB_STEP_SUMMARY
ERROR_FOUND=false
while IFS= read -r file; do
if ! php -l "$file" 2>&1 | tee -a $GITHUB_STEP_SUMMARY; then
echo "❌ Syntax error in: $file" >> $GITHUB_STEP_SUMMARY
ERROR_FOUND=true
fi
done < <(find . -name "*.php" -type f ! -path "*/vendor/*" ! -path "*/node_modules/*")
if [ "$ERROR_FOUND" = true ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "❌ PHP syntax errors found" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ PHP syntax validation passed" >> $GITHUB_STEP_SUMMARY
fi
fi
optional-validations:
name: Optional Validations (${{ inputs.profile }})
runs-on: ubuntu-latest
needs: setup
if: inputs.profile != 'basic'
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup PHP
if: needs.setup.outputs.has-php == 'true'
uses: shivammathur/setup-php@v2
with:
php-version: ${{ inputs.php-version }}
extensions: mbstring, xml
coverage: none
- name: Make scripts executable
if: needs.setup.outputs.has-scripts == 'true'
working-directory: ${{ inputs.working-directory }}
run: |
if [ -d "scripts" ]; then
find scripts -name "*.sh" -type f -exec chmod +x {} \;
fi
if [ -d ".github/scripts" ]; then
find .github/scripts -name "*.sh" -type f -exec chmod +x {} \;
fi
- name: Validate changelog
if: inputs.validate-changelogs
continue-on-error: ${{ !inputs.fail-on-warnings }}
working-directory: ${{ inputs.working-directory }}
run: |
echo "### 📝 Changelog Validation" >> $GITHUB_STEP_SUMMARY
if [ -f "scripts/validate/changelog.sh" ]; then
./scripts/validate/changelog.sh
echo "✅ Changelog validation passed" >> $GITHUB_STEP_SUMMARY
elif [ -f ".github/scripts/validate/changelog.sh" ]; then
./.github/scripts/validate/changelog.sh
echo "✅ Changelog validation passed" >> $GITHUB_STEP_SUMMARY
elif [ -f "CHANGELOG.md" ]; then
# Basic changelog validation
if grep -q "## \[" CHANGELOG.md; then
echo "✅ Changelog appears well-formed" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Changelog may not follow standard format" >> $GITHUB_STEP_SUMMARY
[ "${{ inputs.fail-on-warnings }}" = "true" ] && exit 1
fi
else
echo "⚠️ No CHANGELOG.md found" >> $GITHUB_STEP_SUMMARY
fi
- name: Validate license headers
if: inputs.validate-licenses
continue-on-error: ${{ !inputs.fail-on-warnings }}
working-directory: ${{ inputs.working-directory }}
run: |
echo "### ⚖️ License Header Validation" >> $GITHUB_STEP_SUMMARY
if [ -f "scripts/validate/license_headers.sh" ]; then
./scripts/validate/license_headers.sh
echo "✅ License headers validated" >> $GITHUB_STEP_SUMMARY
elif [ -f ".github/scripts/validate/license_headers.sh" ]; then
./.github/scripts/validate/license_headers.sh
echo "✅ License headers validated" >> $GITHUB_STEP_SUMMARY
else
# Basic license header check
COUNT_FILE=$(mktemp)
find . \( -name "*.php" -o -name "*.js" -o -name "*.py" \) -type f -exec sh -c 'if ! head -20 "$1" | grep -qi "license\|copyright\|spdx"; then echo "1"; fi' _ {} \; > "$COUNT_FILE"
FILES_WITHOUT_LICENSE=$(wc -l < "$COUNT_FILE")
rm -f "$COUNT_FILE"
if [ "$FILES_WITHOUT_LICENSE" -eq 0 ]; then
echo "✅ License headers appear present" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Some files may be missing license headers" >> $GITHUB_STEP_SUMMARY
[ "${{ inputs.fail-on-warnings }}" = "true" ] && exit 1
fi
fi
- name: Validate language structure
continue-on-error: true
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "scripts/validate/language_structure.sh" ]; then
./scripts/validate/language_structure.sh
elif [ -f ".github/scripts/validate/language_structure.sh" ]; then
./.github/scripts/validate/language_structure.sh
else
echo " No language structure validation script found"
fi
- name: Validate paths
continue-on-error: true
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "scripts/validate/paths.sh" ]; then
./scripts/validate/paths.sh
elif [ -f ".github/scripts/validate/paths.sh" ]; then
./.github/scripts/validate/paths.sh
else
echo " No path validation script found"
fi
- name: Validate tabs/whitespace
continue-on-error: true
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "scripts/validate/tabs.sh" ]; then
./scripts/validate/tabs.sh
elif [ -f ".github/scripts/validate/tabs.sh" ]; then
./.github/scripts/validate/tabs.sh
else
echo " No tabs validation script found"
fi
- name: Validate version alignment
continue-on-error: ${{ !inputs.fail-on-warnings }}
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "scripts/validate/version_alignment.sh" ]; then
./scripts/validate/version_alignment.sh
elif [ -f ".github/scripts/validate/version_alignment.sh" ]; then
./.github/scripts/validate/version_alignment.sh
else
echo " No version alignment validation script found"
fi
security-validations:
name: Security Validations
runs-on: ubuntu-latest
if: inputs.validate-security
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Make scripts executable
working-directory: ${{ inputs.working-directory }}
run: |
if [ -d "scripts" ]; then
find scripts -name "*.sh" -type f -exec chmod +x {} \;
fi
if [ -d ".github/scripts" ]; then
find .github/scripts -name "*.sh" -type f -exec chmod +x {} \;
fi
- name: Check for secrets
working-directory: ${{ inputs.working-directory }}
run: |
echo "### 🔒 Security Validation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f "scripts/validate/no_secrets.sh" ]; then
if ./scripts/validate/no_secrets.sh 2>&1 | tee -a $GITHUB_STEP_SUMMARY; then
echo "✅ No secrets found" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Secret validation script failed" >> $GITHUB_STEP_SUMMARY
exit 1
fi
elif [ -f ".github/scripts/validate/no_secrets.sh" ]; then
if ./.github/scripts/validate/no_secrets.sh 2>&1 | tee -a $GITHUB_STEP_SUMMARY; then
echo "✅ No secrets found" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Secret validation script failed" >> $GITHUB_STEP_SUMMARY
exit 1
fi
else
# Basic secrets check using find to properly exclude directories
PATTERNS=(
"password\s*=\s*['\"][^'\"]+['\"]"
"api[_-]?key\s*=\s*['\"][^'\"]+['\"]"
"secret\s*=\s*['\"][^'\"]+['\"]"
"token\s*=\s*['\"][^'\"]+['\"]"
"BEGIN RSA PRIVATE KEY"
"BEGIN PRIVATE KEY"
)
FOUND=0
echo "Scanning for potential secrets..." >> $GITHUB_STEP_SUMMARY
for pattern in "${PATTERNS[@]}"; do
# Use find to exclude directories and files, then grep the results
while IFS= read -r file; do
if [ -f "$file" ]; then
if grep -HnE "$pattern" "$file" 2>/dev/null; then
FOUND=1
echo "⚠️ Found pattern in: $file" >> $GITHUB_STEP_SUMMARY
fi
fi
done < <(find . -type f \
! -path "*/.git/*" \
! -path "*/node_modules/*" \
! -path "*/vendor/*" \
! -path "*/.github/*" \
! -path "*/docs/*" \
! -name "*.md" \
2>/dev/null)
done
if [ $FOUND -eq 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ Basic security check passed - no secrets detected" >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "❌ Potential secrets or credentials detected" >> $GITHUB_STEP_SUMMARY
echo "Please review the findings above and ensure they are test fixtures or documentation examples" >> $GITHUB_STEP_SUMMARY
exit 1
fi
fi
summary:
name: Validation Summary
runs-on: ubuntu-latest
needs: [required-validations, optional-validations, security-validations]
if: always()
steps:
- name: Generate validation summary
run: |
echo "### 🎯 CI Validation Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Profile:** ${{ inputs.profile }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Validation Stage | Status |" >> $GITHUB_STEP_SUMMARY
echo "|-----------------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Required Validations | ${{ needs.required-validations.result == 'success' && '✅ Passed' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Optional Validations | ${{ needs.optional-validations.result == 'success' && '✅ Passed' || needs.optional-validations.result == 'skipped' && '⏭️ Skipped' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Security Validations | ${{ needs.security-validations.result == 'success' && '✅ Passed' || needs.security-validations.result == 'skipped' && '⏭️ Skipped' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Repository:** $GITHUB_REPOSITORY" >> $GITHUB_STEP_SUMMARY
echo "**Branch:** $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
echo "**Commit:** $GITHUB_SHA" >> $GITHUB_STEP_SUMMARY
- name: Check validation results
run: |
if [ "${{ needs.required-validations.result }}" == "failure" ]; then
echo "❌ Required validations failed"
exit 1
fi
if [ "${{ needs.security-validations.result }}" == "failure" ]; then
echo "❌ Security validations failed"
exit 1
fi
if [ "${{ inputs.profile }}" == "strict" ] && [ "${{ needs.optional-validations.result }}" == "failure" ]; then
echo "❌ Optional validations failed in strict mode"
exit 1
fi
echo "✅ CI validation completed successfully"

312
.github/workflows/reusable-deploy.yml vendored Normal file
View File

@@ -0,0 +1,312 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Reusable
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/reusable-deploy.yml
# VERSION: 01.00.00
# BRIEF: Reusable type-aware deployment workflow for staging and production
# NOTE: Supports Joomla, Dolibarr, and generic deployments with health checks
name: Reusable Deploy
on:
workflow_call:
inputs:
environment:
description: 'Target environment (staging, production)'
required: true
type: string
version:
description: 'Version to deploy (optional, uses latest if not specified)'
required: false
type: string
deployment-method:
description: 'Deployment method (rsync, ftp, ssh, kubernetes, custom)'
required: false
type: string
default: 'custom'
health-check-url:
description: 'URL to check after deployment'
required: false
type: string
health-check-timeout:
description: 'Health check timeout in seconds'
required: false
type: number
default: 300
working-directory:
description: 'Working directory'
required: false
type: string
default: '.'
secrets:
DEPLOY_HOST:
description: 'Deployment host/server'
required: false
DEPLOY_USER:
description: 'Deployment user'
required: false
DEPLOY_KEY:
description: 'SSH private key or deployment credentials'
required: false
DEPLOY_PATH:
description: 'Deployment path on target server'
required: false
permissions:
contents: read
deployments: write
jobs:
detect:
name: Detect Project Type
uses: ./.github/workflows/reusable-project-detector.yml
with:
working-directory: ${{ inputs.working-directory }}
prepare:
name: Prepare Deployment
runs-on: ubuntu-latest
needs: detect
outputs:
deployment-id: ${{ steps.create-deployment.outputs.deployment_id }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Determine version
id: version
run: |
if [ -n "${{ inputs.version }}" ]; then
VERSION="${{ inputs.version }}"
else
# Use latest tag or commit SHA
VERSION=$(git describe --tags --always)
fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "Deploying version: ${VERSION}"
- name: Create deployment
id: create-deployment
uses: chrnorm/deployment-action@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
environment: ${{ inputs.environment }}
description: "Deploy ${{ needs.detect.outputs.project-type }} v${{ steps.version.outputs.version }}"
- name: Deployment info
run: |
echo "### 🚀 Deployment Preparation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Environment:** ${{ inputs.environment }}" >> $GITHUB_STEP_SUMMARY
echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "**Project Type:** ${{ needs.detect.outputs.project-type }}" >> $GITHUB_STEP_SUMMARY
echo "**Method:** ${{ inputs.deployment-method }}" >> $GITHUB_STEP_SUMMARY
build:
name: Build for Deployment
needs: [detect, prepare]
uses: ./.github/workflows/reusable-build.yml
with:
working-directory: ${{ inputs.working-directory }}
upload-artifacts: true
artifact-name: deployment-package
deploy:
name: Deploy to ${{ inputs.environment }}
runs-on: ubuntu-latest
needs: [detect, prepare, build]
environment:
name: ${{ inputs.environment }}
url: ${{ inputs.health-check-url }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Download build artifacts
uses: actions/download-artifact@v7
with:
name: deployment-package-${{ needs.detect.outputs.project-type }}
path: ./dist
- name: Setup SSH key
if: inputs.deployment-method == 'ssh' || inputs.deployment-method == 'rsync'
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts
- name: Deploy via rsync
if: inputs.deployment-method == 'rsync'
run: |
echo "Deploying via rsync to ${{ secrets.DEPLOY_HOST }}..."
rsync -avz --delete \
-e "ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no" \
./dist/ \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}"
echo "✅ rsync deployment completed" >> $GITHUB_STEP_SUMMARY
- name: Deploy via SSH
if: inputs.deployment-method == 'ssh'
run: |
echo "Deploying via SSH to ${{ secrets.DEPLOY_HOST }}..."
# Create deployment package
tar -czf deployment.tar.gz -C ./dist .
# Copy to server
scp -i ~/.ssh/deploy_key deployment.tar.gz \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/tmp/"
# Extract on server
ssh -i ~/.ssh/deploy_key "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" << 'EOF'
cd ${{ secrets.DEPLOY_PATH }}
tar -xzf /tmp/deployment.tar.gz
rm /tmp/deployment.tar.gz
EOF
echo "✅ SSH deployment completed" >> $GITHUB_STEP_SUMMARY
- name: Deploy Joomla Extension
if: needs.detect.outputs.project-type == 'joomla' && inputs.deployment-method == 'custom'
run: |
echo "### 🔧 Joomla Extension Deployment" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Custom Joomla deployment logic
echo "⚠️ Custom Joomla deployment logic required" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Typical steps:" >> $GITHUB_STEP_SUMMARY
echo "1. Upload extension package to Joomla server" >> $GITHUB_STEP_SUMMARY
echo "2. Install/update via Joomla Extension Manager API" >> $GITHUB_STEP_SUMMARY
echo "3. Clear Joomla cache" >> $GITHUB_STEP_SUMMARY
echo "4. Run database migrations if needed" >> $GITHUB_STEP_SUMMARY
# Placeholder for actual deployment commands
echo "Add your Joomla-specific deployment commands here"
- name: Deploy Dolibarr Module
if: needs.detect.outputs.project-type == 'dolibarr' && inputs.deployment-method == 'custom'
run: |
echo "### 🔧 Dolibarr Module Deployment" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Custom Dolibarr deployment logic
echo "⚠️ Custom Dolibarr deployment logic required" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Typical steps:" >> $GITHUB_STEP_SUMMARY
echo "1. Upload module to Dolibarr htdocs/custom directory" >> $GITHUB_STEP_SUMMARY
echo "2. Activate module via Dolibarr API or admin panel" >> $GITHUB_STEP_SUMMARY
echo "3. Run module setup hooks" >> $GITHUB_STEP_SUMMARY
echo "4. Clear Dolibarr cache" >> $GITHUB_STEP_SUMMARY
# Placeholder for actual deployment commands
echo "Add your Dolibarr-specific deployment commands here"
- name: Deploy Generic Application
if: needs.detect.outputs.project-type == 'generic' && inputs.deployment-method == 'custom'
run: |
echo "### 🔧 Generic Application Deployment" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "⚠️ Custom deployment logic required" >> $GITHUB_STEP_SUMMARY
echo "Add your application-specific deployment commands" >> $GITHUB_STEP_SUMMARY
- name: Health check
if: inputs.health-check-url != ''
run: |
echo "Running health check on ${{ inputs.health-check-url }}..."
TIMEOUT=${{ inputs.health-check-timeout }}
ELAPSED=0
INTERVAL=10
while [ $ELAPSED -lt $TIMEOUT ]; do
if curl -f -s -o /dev/null -w "%{http_code}" "${{ inputs.health-check-url }}" | grep -q "200"; then
echo "✅ Health check passed" >> $GITHUB_STEP_SUMMARY
exit 0
fi
echo "Health check attempt $((ELAPSED / INTERVAL + 1)) failed, retrying..."
sleep $INTERVAL
ELAPSED=$((ELAPSED + INTERVAL))
done
echo "❌ Health check failed after ${TIMEOUT}s" >> $GITHUB_STEP_SUMMARY
exit 1
- name: Update deployment status (success)
if: success()
uses: chrnorm/deployment-status@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
deployment-id: ${{ needs.prepare.outputs.deployment-id }}
state: success
environment-url: ${{ inputs.health-check-url }}
- name: Deployment summary
if: success()
run: |
echo "### ✅ Deployment Successful" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Environment:** ${{ inputs.environment }}" >> $GITHUB_STEP_SUMMARY
echo "**Version:** ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "**Project Type:** ${{ needs.detect.outputs.project-type }}" >> $GITHUB_STEP_SUMMARY
echo "**Time:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_STEP_SUMMARY
if [ -n "${{ inputs.health-check-url }}" ]; then
echo "**URL:** ${{ inputs.health-check-url }}" >> $GITHUB_STEP_SUMMARY
fi
rollback:
name: Rollback on Failure
runs-on: ubuntu-latest
needs: [prepare, deploy]
if: failure()
steps:
- name: Update deployment status (failure)
uses: chrnorm/deployment-status@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
deployment-id: ${{ needs.prepare.outputs.deployment-id }}
state: failure
- name: Rollback deployment
run: |
echo "### ❌ Deployment Failed - Initiating Rollback" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "⚠️ Rollback logic needs to be implemented" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Typical rollback steps:" >> $GITHUB_STEP_SUMMARY
echo "1. Restore previous version from backup" >> $GITHUB_STEP_SUMMARY
echo "2. Revert database migrations if applied" >> $GITHUB_STEP_SUMMARY
echo "3. Clear caches" >> $GITHUB_STEP_SUMMARY
echo "4. Verify health checks pass" >> $GITHUB_STEP_SUMMARY
# Add your rollback commands here

View File

@@ -0,0 +1,356 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Reusable
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/reusable-joomla-testing.yml
# VERSION: 01.00.00
# BRIEF: Reusable Joomla testing workflow with matrix PHP/Joomla versions
# NOTE: Supports PHPUnit, integration tests, and code coverage
name: Reusable Joomla Testing
on:
workflow_call:
inputs:
php-versions:
description: 'JSON array of PHP versions to test'
required: false
type: string
default: '["7.4", "8.0", "8.1", "8.2"]'
joomla-versions:
description: 'JSON array of Joomla versions to test'
required: false
type: string
default: '["4.4", "5.0", "5.1"]'
coverage:
description: 'Enable code coverage reporting'
required: false
type: boolean
default: false
coverage-php-version:
description: 'PHP version to use for coverage reporting'
required: false
type: string
default: '8.1'
coverage-joomla-version:
description: 'Joomla version to use for coverage reporting'
required: false
type: string
default: '5.0'
working-directory:
description: 'Working directory for tests'
required: false
type: string
default: '.'
run-integration-tests:
description: 'Run integration tests with Joomla installation'
required: false
type: boolean
default: true
secrets:
CODECOV_TOKEN:
description: 'Codecov token for coverage uploads'
required: false
permissions:
contents: read
pull-requests: write
checks: write
jobs:
unit-tests:
name: PHPUnit (PHP ${{ matrix.php-version }}, Joomla ${{ matrix.joomla-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php-version: ${{ fromJSON(inputs.php-versions) }}
joomla-version: ${{ fromJSON(inputs.joomla-versions) }}
exclude:
# PHP 7.4 not compatible with Joomla 5.x
- php-version: '7.4'
joomla-version: '5.0'
- php-version: '7.4'
joomla-version: '5.1'
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, xml, mysqli, zip, gd, intl
coverage: ${{ inputs.coverage && matrix.php-version == inputs.coverage-php-version && matrix.joomla-version == inputs.coverage-joomla-version && 'xdebug' || 'none' }}
tools: composer:v2
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-${{ matrix.php-version }}-composer-
${{ runner.os }}-php-
- name: Validate composer.json
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "composer.json" ]; then
composer validate --strict
else
echo "No composer.json found, skipping validation"
fi
- name: Install dependencies
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "composer.json" ]; then
composer install --prefer-dist --no-progress --no-interaction
else
echo "No composer.json found, skipping dependency installation"
fi
- name: Setup Joomla test environment
working-directory: ${{ inputs.working-directory }}
run: |
echo "Setting up Joomla ${{ matrix.joomla-version }} test environment"
# Add Joomla-specific environment variables
echo "JOOMLA_VERSION=${{ matrix.joomla-version }}" >> $GITHUB_ENV
- name: Run PHPUnit tests
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "vendor/bin/phpunit" ]; then
if [ "${{ inputs.coverage && matrix.php-version == inputs.coverage-php-version && matrix.joomla-version == inputs.coverage-joomla-version }}" == "true" ]; then
vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml
else
vendor/bin/phpunit
fi
elif [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
if [ "${{ inputs.coverage && matrix.php-version == inputs.coverage-php-version && matrix.joomla-version == inputs.coverage-joomla-version }}" == "true" ]; then
php vendor/phpunit/phpunit/phpunit --coverage-text --coverage-clover=coverage.xml
else
php vendor/phpunit/phpunit/phpunit
fi
else
echo "⚠️ No PHPUnit configuration found, skipping tests"
exit 0
fi
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
if: inputs.coverage && matrix.php-version == inputs.coverage-php-version && matrix.joomla-version == inputs.coverage-joomla-version
with:
file: ${{ inputs.working-directory }}/coverage.xml
flags: unittests,php-${{ matrix.php-version }},joomla-${{ matrix.joomla-version }}
name: codecov-joomla-${{ matrix.php-version }}-${{ matrix.joomla-version }}
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
integration-tests:
name: Integration (Joomla ${{ matrix.joomla-version }})
runs-on: ubuntu-latest
if: inputs.run-integration-tests
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: joomla_test
MYSQL_USER: joomla
MYSQL_PASSWORD: joomla
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping --silent"
--health-interval=10s
--health-timeout=5s
--health-retries=5
strategy:
fail-fast: false
matrix:
joomla-version: ${{ fromJSON(inputs.joomla-versions) }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: mbstring, xml, mysqli, zip, gd, intl, pdo_mysql
tools: composer:v2
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-integration-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-integration-composer-
- name: Install dependencies
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "composer.json" ]; then
composer install --prefer-dist --no-progress --no-interaction
fi
- name: Download and setup Joomla ${{ matrix.joomla-version }}
run: |
echo "📦 Setting up Joomla ${{ matrix.joomla-version }} for integration testing"
# Create Joomla directory
mkdir -p /tmp/joomla
cd /tmp/joomla
# Determine Joomla version to download
JOOMLA_VERSION="${{ matrix.joomla-version }}"
# Download latest patch version for the specified minor version
if [[ "$JOOMLA_VERSION" == "4.4" ]]; then
DOWNLOAD_VERSION="4.4-Stable"
elif [[ "$JOOMLA_VERSION" == "5.0" ]]; then
DOWNLOAD_VERSION="5.0-Stable"
elif [[ "$JOOMLA_VERSION" == "5.1" ]]; then
DOWNLOAD_VERSION="5.1-Stable"
else
DOWNLOAD_VERSION="${JOOMLA_VERSION}-Stable"
fi
echo "Downloading Joomla ${DOWNLOAD_VERSION}..."
curl -L -o joomla.zip "https://downloads.joomla.org/cms/joomla${JOOMLA_VERSION%%.*}/${DOWNLOAD_VERSION}" || \
curl -L -o joomla.zip "https://github.com/joomla/joomla-cms/releases/download/${JOOMLA_VERSION}.0/Joomla_${JOOMLA_VERSION}.0-Stable-Full_Package.zip" || \
echo "⚠️ Could not download Joomla, integration tests may be limited"
if [ -f joomla.zip ]; then
unzip -q joomla.zip
echo "✅ Joomla extracted successfully"
fi
- name: Configure Joomla
run: |
echo "⚙️ Configuring Joomla for testing"
if [ -d "/tmp/joomla" ]; then
cd /tmp/joomla
# Create basic Joomla configuration
cat > configuration.php << 'EOF'
<?php
class JConfig {
public $dbtype = 'mysqli';
public $host = '127.0.0.1';
public $user = 'joomla';
public $password = 'joomla';
public $db = 'joomla_test';
public $dbprefix = 'jos_';
public $secret = 'test-secret';
public $debug = true;
public $error_reporting = 'maximum';
}
EOF
echo "✅ Joomla configuration created"
else
echo "⚠️ Joomla directory not found, skipping configuration"
fi
- name: Install extension into Joomla
working-directory: ${{ inputs.working-directory }}
run: |
echo "📦 Installing extension into Joomla"
if [ -d "/tmp/joomla" ]; then
# Copy extension files to Joomla
# This is a placeholder - actual implementation depends on extension type
echo "Extension installation logic would go here"
echo "Extension type detection and installation steps"
else
echo "⚠️ Skipping extension installation - Joomla not available"
fi
- name: Run integration tests
working-directory: ${{ inputs.working-directory }}
run: |
if [ -d "tests/Integration" ] && [ -f "vendor/bin/phpunit" ]; then
echo "🧪 Running integration tests"
# Try test suite first, then directory-based as fallback
if vendor/bin/phpunit --testsuite Integration; then
echo "✅ Integration tests passed (test suite)"
elif vendor/bin/phpunit tests/Integration/; then
echo "✅ Integration tests passed (directory)"
else
echo "❌ Integration tests failed"
exit 1
fi
else
echo " No integration tests found or PHPUnit not available"
echo "Looked for: tests/Integration/ directory"
fi
summary:
name: Testing Summary
runs-on: ubuntu-latest
needs: [unit-tests, integration-tests]
if: always()
steps:
- name: Generate test summary
run: |
echo "### 🧪 Joomla Testing Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Test Suite | Status |" >> $GITHUB_STEP_SUMMARY
echo "|------------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Unit Tests | ${{ needs.unit-tests.result == 'success' && '✅ Passed' || needs.unit-tests.result == 'skipped' && '⏭️ Skipped' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Integration Tests | ${{ needs.integration-tests.result == 'success' && '✅ Passed' || needs.integration-tests.result == 'skipped' && '⏭️ Skipped' || '❌ Failed' }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Repository:** $GITHUB_REPOSITORY" >> $GITHUB_STEP_SUMMARY
echo "**Branch:** $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
echo "**Commit:** $GITHUB_SHA" >> $GITHUB_STEP_SUMMARY
- name: Check test results
run: |
if [ "${{ needs.unit-tests.result }}" == "failure" ]; then
echo "❌ Unit tests failed"
exit 1
fi
if [ "${{ needs.integration-tests.result }}" == "failure" ]; then
echo "❌ Integration tests failed"
exit 1
fi
echo "✅ All test suites passed or were skipped"

View File

@@ -0,0 +1,297 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Reusable
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/reusable-php-quality.yml
# VERSION: 01.00.00
# BRIEF: Reusable PHP code quality analysis workflow
# NOTE: Supports PHPCS, PHPStan, Psalm with configurable PHP versions and tools
name: Reusable PHP Quality
on:
workflow_call:
inputs:
php-versions:
description: 'JSON array of PHP versions to test'
required: false
type: string
default: '["7.4", "8.0", "8.1", "8.2"]'
tools:
description: 'JSON array of quality tools to run (phpcs, phpstan, psalm)'
required: false
type: string
default: '["phpcs", "phpstan", "psalm"]'
working-directory:
description: 'Working directory for the quality checks'
required: false
type: string
default: '.'
phpcs-standard:
description: 'PHPCS coding standard to use'
required: false
type: string
default: 'PSR12'
phpstan-level:
description: 'PHPStan analysis level (0-9)'
required: false
type: string
default: '5'
psalm-level:
description: 'Psalm error level (1-8)'
required: false
type: string
default: '4'
fail-on-error:
description: 'Fail the workflow if quality checks find issues'
required: false
type: boolean
default: true
outputs:
quality-score:
description: 'Overall quality score percentage'
value: ${{ jobs.aggregate.outputs.score }}
permissions:
contents: read
pull-requests: write
checks: write
jobs:
phpcs:
name: PHP_CodeSniffer (PHP ${{ matrix.php-version }})
runs-on: ubuntu-latest
if: contains(fromJSON(inputs.tools), 'phpcs')
strategy:
fail-fast: false
matrix:
php-version: ${{ fromJSON(inputs.php-versions) }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, xml
tools: composer:v2, phpcs
coverage: none
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "composer.json" ]; then
composer install --prefer-dist --no-progress --no-interaction
fi
- name: Run PHP_CodeSniffer
working-directory: ${{ inputs.working-directory }}
continue-on-error: ${{ !inputs.fail-on-error }}
run: |
if [ -f "phpcs.xml" ] || [ -f "phpcs.xml.dist" ]; then
phpcs --standard=phpcs.xml --report=summary --report-width=120
elif [ -f "vendor/bin/phpcs" ]; then
vendor/bin/phpcs --standard=${{ inputs.phpcs-standard }} --report=summary --report-width=120 src/
else
phpcs --standard=${{ inputs.phpcs-standard }} --report=summary --report-width=120 . || echo "No PHP files found or PHPCS configuration missing"
fi
phpstan:
name: PHPStan (PHP ${{ matrix.php-version }})
runs-on: ubuntu-latest
if: contains(fromJSON(inputs.tools), 'phpstan')
strategy:
fail-fast: false
matrix:
php-version: ${{ fromJSON(inputs.php-versions) }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, xml
tools: composer:v2, phpstan
coverage: none
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "composer.json" ]; then
composer install --prefer-dist --no-progress --no-interaction
fi
- name: Run PHPStan
working-directory: ${{ inputs.working-directory }}
continue-on-error: ${{ !inputs.fail-on-error }}
run: |
if [ -f "phpstan.neon" ] || [ -f "phpstan.neon.dist" ]; then
phpstan analyse --no-progress --error-format=github
elif [ -f "vendor/bin/phpstan" ]; then
vendor/bin/phpstan analyse src/ --level=${{ inputs.phpstan-level }} --no-progress --error-format=github
else
phpstan analyse . --level=${{ inputs.phpstan-level }} --no-progress --error-format=github || echo "No PHP files found or PHPStan configuration missing"
fi
psalm:
name: Psalm (PHP ${{ matrix.php-version }})
runs-on: ubuntu-latest
if: contains(fromJSON(inputs.tools), 'psalm')
strategy:
fail-fast: false
matrix:
php-version: ${{ fromJSON(inputs.php-versions) }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, xml
tools: composer:v2, psalm
coverage: none
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache Composer dependencies
uses: actions/cache@v5
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "composer.json" ]; then
composer install --prefer-dist --no-progress --no-interaction
fi
- name: Run Psalm
working-directory: ${{ inputs.working-directory }}
continue-on-error: ${{ !inputs.fail-on-error }}
run: |
if [ -f "psalm.xml" ] || [ -f "psalm.xml.dist" ]; then
psalm --no-progress --output-format=github --show-info=false
elif [ -f "vendor/bin/psalm" ]; then
# Initialize Psalm config if it doesn't exist
if [ ! -f "psalm.xml" ]; then
echo "Initializing Psalm configuration..."
if ! vendor/bin/psalm --init src/ ${{ inputs.psalm-level }}; then
echo "⚠️ Psalm initialization failed, proceeding with defaults"
fi
fi
vendor/bin/psalm --no-progress --output-format=github --show-info=false
else
psalm --no-progress --output-format=github --show-info=false || echo "No PHP files found or Psalm configuration missing"
fi
aggregate:
name: Quality Check Summary
runs-on: ubuntu-latest
needs: [phpcs, phpstan, psalm]
if: always()
outputs:
score: ${{ steps.calculate.outputs.score }}
steps:
- name: Calculate quality score
id: calculate
run: |
# Count successful jobs
SUCCESS=0
TOTAL=0
if [ "${{ needs.phpcs.result }}" != "skipped" ]; then
TOTAL=$((TOTAL + 1))
[ "${{ needs.phpcs.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
fi
if [ "${{ needs.phpstan.result }}" != "skipped" ]; then
TOTAL=$((TOTAL + 1))
[ "${{ needs.phpstan.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
fi
if [ "${{ needs.psalm.result }}" != "skipped" ]; then
TOTAL=$((TOTAL + 1))
[ "${{ needs.psalm.result }}" == "success" ] && SUCCESS=$((SUCCESS + 1))
fi
# Calculate percentage
if [ $TOTAL -gt 0 ]; then
SCORE=$((SUCCESS * 100 / TOTAL))
else
SCORE=100
fi
echo "score=$SCORE" >> $GITHUB_OUTPUT
echo "Quality Score: $SCORE%" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- PHPCS: ${{ needs.phpcs.result }}" >> $GITHUB_STEP_SUMMARY
echo "- PHPStan: ${{ needs.phpstan.result }}" >> $GITHUB_STEP_SUMMARY
echo "- Psalm: ${{ needs.psalm.result }}" >> $GITHUB_STEP_SUMMARY
- name: Check overall status
if: inputs.fail-on-error
run: |
if [ "${{ needs.phpcs.result }}" == "failure" ] || \
[ "${{ needs.phpstan.result }}" == "failure" ] || \
[ "${{ needs.psalm.result }}" == "failure" ]; then
echo "❌ Quality checks failed"
exit 1
fi
echo "✅ All quality checks passed"

View File

@@ -0,0 +1,138 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Reusable
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/reusable-project-detector.yml
# VERSION: 01.00.00
# BRIEF: Reusable workflow for detecting project type (Joomla, Dolibarr, Generic)
# NOTE: Provides project_type and extension_type outputs for downstream workflows
name: Reusable Project Type Detection
on:
workflow_call:
inputs:
working-directory:
description: 'Working directory for detection'
required: false
type: string
default: '.'
outputs:
project-type:
description: 'Detected project type (joomla, dolibarr, generic)'
value: ${{ jobs.detect.outputs.project_type }}
extension-type:
description: 'Detected extension type (component, module, plugin, etc.)'
value: ${{ jobs.detect.outputs.extension_type }}
has-php:
description: 'Whether project contains PHP files'
value: ${{ jobs.detect.outputs.has_php }}
has-node:
description: 'Whether project contains Node.js/package.json'
value: ${{ jobs.detect.outputs.has_node }}
permissions:
contents: read
jobs:
detect:
name: Detect Project Type
runs-on: ubuntu-latest
outputs:
project_type: ${{ steps.detect.outputs.project_type }}
extension_type: ${{ steps.detect.outputs.extension_type }}
has_php: ${{ steps.detect.outputs.has_php }}
has_node: ${{ steps.detect.outputs.has_node }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Detect project type and components
id: detect
working-directory: ${{ inputs.working-directory }}
run: |
echo "### 🔍 Project Detection" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Detection priority: Joomla > Dolibarr > Generic
# Check for Joomla indicators
if [ -f "joomla.xml" ] || \
find . -maxdepth 2 \( -name "mod_*.xml" -o -name "plg_*.xml" -o -name "com_*.xml" -o -name "pkg_*.xml" -o -name "tpl_*.xml" \) 2>/dev/null | head -1 | grep -q .; then
echo "project_type=joomla" >> $GITHUB_OUTPUT
echo "**Project Type:** Joomla" >> $GITHUB_STEP_SUMMARY
# Detect Joomla extension type
if [ -d "administrator/components" ] || [ -d "components" ]; then
echo "extension_type=component" >> $GITHUB_OUTPUT
echo "**Extension Type:** Component" >> $GITHUB_STEP_SUMMARY
elif find . -maxdepth 1 -name "mod_*.xml" 2>/dev/null | head -1 | grep -q .; then
echo "extension_type=module" >> $GITHUB_OUTPUT
echo "**Extension Type:** Module" >> $GITHUB_STEP_SUMMARY
elif find . -maxdepth 1 -name "plg_*.xml" 2>/dev/null | head -1 | grep -q .; then
echo "extension_type=plugin" >> $GITHUB_OUTPUT
echo "**Extension Type:** Plugin" >> $GITHUB_STEP_SUMMARY
elif find . -maxdepth 1 -name "pkg_*.xml" 2>/dev/null | head -1 | grep -q .; then
echo "extension_type=package" >> $GITHUB_OUTPUT
echo "**Extension Type:** Package" >> $GITHUB_STEP_SUMMARY
elif find . -maxdepth 1 -name "tpl_*.xml" 2>/dev/null | head -1 | grep -q .; then
echo "extension_type=template" >> $GITHUB_OUTPUT
echo "**Extension Type:** Template" >> $GITHUB_STEP_SUMMARY
else
echo "extension_type=component" >> $GITHUB_OUTPUT
echo "**Extension Type:** Component (default)" >> $GITHUB_STEP_SUMMARY
fi
# Check for Dolibarr indicators
elif [ -d "htdocs" ] || [ -d "core/modules" ] || \
([ -f "composer.json" ] && grep -q "dolibarr" composer.json 2>/dev/null); then
echo "project_type=dolibarr" >> $GITHUB_OUTPUT
echo "extension_type=module" >> $GITHUB_OUTPUT
echo "**Project Type:** Dolibarr" >> $GITHUB_STEP_SUMMARY
echo "**Extension Type:** Module" >> $GITHUB_STEP_SUMMARY
# Default to Generic
else
echo "project_type=generic" >> $GITHUB_OUTPUT
echo "extension_type=application" >> $GITHUB_OUTPUT
echo "**Project Type:** Generic" >> $GITHUB_STEP_SUMMARY
echo "**Extension Type:** Application" >> $GITHUB_STEP_SUMMARY
fi
# Detect PHP presence
if find . -name "*.php" -type f 2>/dev/null | head -1 | grep -q .; then
echo "has_php=true" >> $GITHUB_OUTPUT
echo "- ✅ PHP files detected" >> $GITHUB_STEP_SUMMARY
else
echo "has_php=false" >> $GITHUB_OUTPUT
echo "- No PHP files detected" >> $GITHUB_STEP_SUMMARY
fi
# Detect Node.js presence
if [ -f "package.json" ]; then
echo "has_node=true" >> $GITHUB_OUTPUT
echo "- ✅ Node.js project detected (package.json)" >> $GITHUB_STEP_SUMMARY
else
echo "has_node=false" >> $GITHUB_OUTPUT
echo "- No Node.js project detected" >> $GITHUB_STEP_SUMMARY
fi

397
.github/workflows/reusable-release.yml vendored Normal file
View File

@@ -0,0 +1,397 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Reusable
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/reusable-release.yml
# VERSION: 01.00.00
# BRIEF: Reusable type-aware release workflow for Joomla, Dolibarr, and generic projects
# NOTE: Creates releases with type-specific packaging and optional marketplace publishing
name: Reusable Release
on:
workflow_call:
inputs:
version:
description: 'Release version (semver format)'
required: true
type: string
prerelease:
description: 'Mark as pre-release'
required: false
type: boolean
default: false
draft:
description: 'Create as draft release'
required: false
type: boolean
default: false
php-version:
description: 'PHP version for build'
required: false
type: string
default: '8.1'
create-github-release:
description: 'Create GitHub release'
required: false
type: boolean
default: true
publish-to-marketplace:
description: 'Publish to marketplace (Joomla/Dolibarr)'
required: false
type: boolean
default: false
working-directory:
description: 'Working directory'
required: false
type: string
default: '.'
secrets:
MARKETPLACE_TOKEN:
description: 'Marketplace API token (JED/Dolistore)'
required: false
permissions:
contents: write
jobs:
detect:
name: Detect Project Type
uses: ./.github/workflows/reusable-project-detector.yml
with:
working-directory: ${{ inputs.working-directory }}
build-package:
name: Build Release Package
runs-on: ubuntu-latest
needs: detect
outputs:
package-name: ${{ steps.package.outputs.name }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup PHP
if: needs.detect.outputs.has-php == 'true'
uses: shivammathur/setup-php@v2
with:
php-version: ${{ inputs.php-version }}
extensions: mbstring, xml, zip
tools: composer:v2
- name: Setup Node.js
if: needs.detect.outputs.has-node == 'true'
uses: actions/setup-node@v6
with:
node-version: '20.x'
- name: Validate version format
run: |
VERSION="${{ inputs.version }}"
if ! echo "$VERSION" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$'; then
echo "❌ Invalid version format: $VERSION"
echo "Expected semver format: X.Y.Z or X.Y.Z-prerelease"
exit 1
fi
echo "✅ Version format valid: $VERSION"
- name: Install dependencies
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "composer.json" ]; then
composer install --no-dev --optimize-autoloader --no-interaction
echo "✅ Composer dependencies installed" >> $GITHUB_STEP_SUMMARY
fi
if [ -f "package.json" ]; then
npm ci
if grep -q '"build"' package.json; then
npm run build
fi
echo "✅ Node dependencies installed and built" >> $GITHUB_STEP_SUMMARY
fi
- name: Update version in files
working-directory: ${{ inputs.working-directory }}
run: |
VERSION="${{ inputs.version }}"
# Update version in XML manifests (Joomla/Dolibarr)
if [ "${{ needs.detect.outputs.project-type }}" == "joomla" ] || \
[ "${{ needs.detect.outputs.project-type }}" == "dolibarr" ]; then
find . -name "*.xml" -type f -not -path "*/node_modules/*" -not -path "*/vendor/*" \
-exec sed -i "s/<version>[^<]*<\/version>/<version>${VERSION}<\/version>/g" {} \;
echo "- ✅ Updated version in XML manifests" >> $GITHUB_STEP_SUMMARY
fi
# Update version in package.json
if [ -f "package.json" ]; then
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"${VERSION}\"/g" package.json
echo "- ✅ Updated version in package.json" >> $GITHUB_STEP_SUMMARY
fi
# Update version in composer.json
if [ -f "composer.json" ]; then
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"${VERSION}\"/g" composer.json
echo "- ✅ Updated version in composer.json" >> $GITHUB_STEP_SUMMARY
fi
- name: Create Joomla package
if: needs.detect.outputs.project-type == 'joomla'
working-directory: ${{ inputs.working-directory }}
run: |
mkdir -p build/package
# Copy files excluding development artifacts
rsync -av \
--exclude='build' \
--exclude='tests' \
--exclude='.git*' \
--exclude='composer.json' \
--exclude='composer.lock' \
--exclude='phpunit.xml*' \
--exclude='phpcs.xml*' \
--exclude='phpstan.neon*' \
--exclude='psalm.xml*' \
--exclude='node_modules' \
--exclude='.github' \
--exclude='package.json' \
--exclude='package-lock.json' \
. build/package/
# Determine extension name from manifest
MANIFEST=$(find . -maxdepth 1 -name "*.xml" -not -name "phpunit.xml*" -type f | head -1)
if [ -n "$MANIFEST" ]; then
EXT_NAME=$(basename "$MANIFEST" .xml)
else
EXT_NAME=$(basename "$GITHUB_REPOSITORY" | sed 's/^joomla-//')
fi
# Create ZIP package
cd build/package
VERSION="${{ inputs.version }}"
PACKAGE_NAME="${EXT_NAME}-${VERSION}.zip"
zip -r "../${PACKAGE_NAME}" .
cd ../..
echo "PACKAGE_NAME=${PACKAGE_NAME}" >> $GITHUB_ENV
echo "✅ Created Joomla package: ${PACKAGE_NAME}" >> $GITHUB_STEP_SUMMARY
- name: Create Dolibarr package
if: needs.detect.outputs.project-type == 'dolibarr'
working-directory: ${{ inputs.working-directory }}
run: |
mkdir -p build/package
# Copy module files
rsync -av \
--exclude='build' \
--exclude='tests' \
--exclude='.git*' \
--exclude='node_modules' \
--exclude='.github' \
. build/package/
# Determine module name
if [ -f "core/modules/modMyModule.class.php" ]; then
MODULE_NAME=$(grep -oP "class modMyModule extends DolibarrModules" core/modules/*.php | head -1 | sed 's/class mod//' | sed 's/ extends.*//')
else
MODULE_NAME=$(basename "$GITHUB_REPOSITORY" | sed 's/^dolibarr-//')
fi
# Create ZIP package
cd build/package
VERSION="${{ inputs.version }}"
PACKAGE_NAME="${MODULE_NAME}-${VERSION}.zip"
zip -r "../${PACKAGE_NAME}" .
cd ../..
echo "PACKAGE_NAME=${PACKAGE_NAME}" >> $GITHUB_ENV
echo "✅ Created Dolibarr package: ${PACKAGE_NAME}" >> $GITHUB_STEP_SUMMARY
- name: Create Generic package
if: needs.detect.outputs.project-type == 'generic'
working-directory: ${{ inputs.working-directory }}
run: |
mkdir -p build/package
# Copy relevant build artifacts
if [ -d "dist" ]; then
cp -r dist/* build/package/
elif [ -d "build" ]; then
cp -r build/* build/package/
else
# Copy all files excluding development artifacts
rsync -av \
--exclude='build' \
--exclude='tests' \
--exclude='.git*' \
--exclude='node_modules' \
. build/package/
fi
# Create package
REPO_NAME=$(basename "$GITHUB_REPOSITORY")
VERSION="${{ inputs.version }}"
PACKAGE_NAME="${REPO_NAME}-${VERSION}.tar.gz"
cd build
tar -czf "${PACKAGE_NAME}" package/
cd ..
echo "PACKAGE_NAME=${PACKAGE_NAME}" >> $GITHUB_ENV
echo "✅ Created generic package: ${PACKAGE_NAME}" >> $GITHUB_STEP_SUMMARY
- name: Generate checksums
working-directory: ${{ inputs.working-directory }}
run: |
cd build
PACKAGE="${PACKAGE_NAME}"
if [ -f "$PACKAGE" ]; then
sha256sum "$PACKAGE" > "${PACKAGE}.sha256"
md5sum "$PACKAGE" > "${PACKAGE}.md5"
echo "✅ Generated checksums" >> $GITHUB_STEP_SUMMARY
fi
- name: Output package info
id: package
run: |
echo "name=${PACKAGE_NAME}" >> $GITHUB_OUTPUT
- name: Upload release artifacts
uses: actions/upload-artifact@v6
with:
name: release-package
path: |
${{ inputs.working-directory }}/build/*.zip
${{ inputs.working-directory }}/build/*.tar.gz
${{ inputs.working-directory }}/build/*.sha256
${{ inputs.working-directory }}/build/*.md5
retention-days: 30
create-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: [detect, build-package]
if: inputs.create-github-release
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Download release artifacts
uses: actions/download-artifact@v7
with:
name: release-package
path: ./artifacts
- name: Extract changelog
id: changelog
run: |
VERSION="${{ inputs.version }}"
if [ -f "CHANGELOG.md" ]; then
# Extract changelog for this version
awk "/## \[${VERSION}\]/,/## \[/{if(/## \[${VERSION}\]/)next;else if(/## \[/)exit;else print}" CHANGELOG.md > release_notes.md
if [ ! -s release_notes.md ]; then
echo "## Release ${VERSION}" > release_notes.md
echo "" >> release_notes.md
echo "No specific changelog found for this version." >> release_notes.md
echo "Please refer to the full CHANGELOG.md for details." >> release_notes.md
fi
else
echo "## Release ${VERSION}" > release_notes.md
echo "" >> release_notes.md
echo "Release created from ${{ needs.detect.outputs.project-type }} project." >> release_notes.md
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ inputs.version }}
name: Release ${{ inputs.version }}
body_path: release_notes.md
draft: ${{ inputs.draft }}
prerelease: ${{ inputs.prerelease }}
files: |
artifacts/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Release summary
run: |
echo "### 🚀 Release Created Successfully" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version:** ${{ inputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "**Project Type:** ${{ needs.detect.outputs.project-type }}" >> $GITHUB_STEP_SUMMARY
echo "**Extension Type:** ${{ needs.detect.outputs.extension-type }}" >> $GITHUB_STEP_SUMMARY
echo "**Pre-release:** ${{ inputs.prerelease }}" >> $GITHUB_STEP_SUMMARY
echo "**Draft:** ${{ inputs.draft }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Package:** ${{ needs.build-package.outputs.package-name }}" >> $GITHUB_STEP_SUMMARY
publish-marketplace:
name: Publish to Marketplace
runs-on: ubuntu-latest
needs: [detect, build-package, create-release]
if: inputs.publish-to-marketplace && (needs.detect.outputs.project-type == 'joomla' || needs.detect.outputs.project-type == 'dolibarr')
steps:
- name: Download release artifacts
uses: actions/download-artifact@v7
with:
name: release-package
path: ./artifacts
- name: Publish to marketplace
run: |
echo "### 🌐 Marketplace Publishing" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
PROJECT_TYPE="${{ needs.detect.outputs.project-type }}"
if [ "$PROJECT_TYPE" == "joomla" ]; then
echo "⚠️ Joomla Extensions Directory (JED) publishing requires manual submission" >> $GITHUB_STEP_SUMMARY
echo "Package ready at: artifacts/${{ needs.build-package.outputs.package-name }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "To publish to JED:" >> $GITHUB_STEP_SUMMARY
echo "1. Visit https://extensions.joomla.org/" >> $GITHUB_STEP_SUMMARY
echo "2. Login and submit the extension package" >> $GITHUB_STEP_SUMMARY
elif [ "$PROJECT_TYPE" == "dolibarr" ]; then
echo "⚠️ Dolistore publishing requires manual submission" >> $GITHUB_STEP_SUMMARY
echo "Package ready at: artifacts/${{ needs.build-package.outputs.package-name }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "To publish to Dolistore:" >> $GITHUB_STEP_SUMMARY
echo "1. Visit https://www.dolistore.com/" >> $GITHUB_STEP_SUMMARY
echo "2. Login and submit the module package" >> $GITHUB_STEP_SUMMARY
fi
# Note: Automated marketplace publishing would require
# marketplace-specific API implementation here
# For now, we provide manual instructions

View File

@@ -0,0 +1,210 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflows
# INGROUP: MokoStandards.Reusable
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/reusable-script-executor.yml
# VERSION: 01.00.00
# BRIEF: Reusable workflow to execute MokoStandards scripts in any repository
# NOTE: Provides unified script execution with proper environment setup
name: Execute MokoStandards Script
on:
workflow_call:
inputs:
script_path:
description: 'Path to script relative to scripts/ directory (e.g., validate/no_secrets.py)'
required: true
type: string
script_args:
description: 'Arguments to pass to the script'
required: false
type: string
default: ''
python_version:
description: 'Python version to use'
required: false
type: string
default: '3.11'
install_dependencies:
description: 'Install Python dependencies (pyyaml, etc.)'
required: false
type: boolean
default: true
working_directory:
description: 'Working directory for script execution'
required: false
type: string
default: '.'
create_summary:
description: 'Create GitHub step summary'
required: false
type: boolean
default: true
outputs:
exit_code:
description: 'Script exit code'
value: ${{ jobs.execute-script.outputs.exit_code }}
script_output:
description: 'Script output (truncated to 1000 chars)'
value: ${{ jobs.execute-script.outputs.script_output }}
jobs:
execute-script:
name: Execute ${{ inputs.script_path }}
runs-on: ubuntu-latest
outputs:
exit_code: ${{ steps.run-script.outputs.exit_code }}
script_output: ${{ steps.run-script.outputs.script_output }}
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Python
if: endsWith(inputs.script_path, '.py')
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python_version }}
- name: Install Python dependencies
if: endsWith(inputs.script_path, '.py') && inputs.install_dependencies
run: |
python -m pip install --upgrade pip
pip install pyyaml
# Install additional dependencies if requirements file exists
if [ -f "requirements.txt" ]; then
pip install -r requirements.txt
fi
if [ "${{ inputs.create_summary }}" == "true" ]; then
echo "## 📦 Dependencies Installed" >> $GITHUB_STEP_SUMMARY
echo "- Python ${{ inputs.python_version }}" >> $GITHUB_STEP_SUMMARY
echo "- PyYAML (for configuration)" >> $GITHUB_STEP_SUMMARY
fi
- name: Setup Bash
if: endsWith(inputs.script_path, '.sh')
run: |
bash --version
- name: Verify script exists
id: verify
run: |
SCRIPT_PATH="scripts/${{ inputs.script_path }}"
if [ ! -f "$SCRIPT_PATH" ]; then
echo "❌ Script not found: $SCRIPT_PATH" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Available scripts:" >> $GITHUB_STEP_SUMMARY
find scripts -name "*.py" -o -name "*.sh" | sort >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "script_full_path=$SCRIPT_PATH" >> $GITHUB_OUTPUT
if [ "${{ inputs.create_summary }}" == "true" ]; then
echo "## ✅ Script Found" >> $GITHUB_STEP_SUMMARY
echo "**Path:** \`$SCRIPT_PATH\`" >> $GITHUB_STEP_SUMMARY
echo "**Type:** $(file -b $SCRIPT_PATH)" >> $GITHUB_STEP_SUMMARY
fi
- name: Make script executable
run: |
chmod +x ${{ steps.verify.outputs.script_full_path }}
- name: Run script
id: run-script
working-directory: ${{ inputs.working_directory }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
SCRIPT_PATH="${{ steps.verify.outputs.script_full_path }}"
SCRIPT_ARGS="${{ inputs.script_args }}"
echo "## 🚀 Executing Script" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Script:** \`$SCRIPT_PATH\`" >> $GITHUB_STEP_SUMMARY
echo "**Arguments:** \`$SCRIPT_ARGS\`" >> $GITHUB_STEP_SUMMARY
echo "**Working Directory:** \`${{ inputs.working_directory }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Output" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
# Execute script and capture output
set +e
if [[ "$SCRIPT_PATH" == *.py ]]; then
OUTPUT=$(python3 "$SCRIPT_PATH" $SCRIPT_ARGS 2>&1)
EXIT_CODE=$?
elif [[ "$SCRIPT_PATH" == *.sh ]]; then
OUTPUT=$(bash "$SCRIPT_PATH" $SCRIPT_ARGS 2>&1)
EXIT_CODE=$?
else
OUTPUT=$("$SCRIPT_PATH" $SCRIPT_ARGS 2>&1)
EXIT_CODE=$?
fi
set -e
# Save outputs
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
# Truncate output for GitHub output (max 1000 chars)
OUTPUT_TRUNCATED="${OUTPUT:0:1000}"
echo "script_output<<EOF" >> $GITHUB_OUTPUT
echo "$OUTPUT_TRUNCATED" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Show full output in summary (with line limit)
echo "$OUTPUT" | head -n 100 >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Report exit code
if [ $EXIT_CODE -eq 0 ]; then
echo "### ✅ Script Completed Successfully" >> $GITHUB_STEP_SUMMARY
echo "**Exit Code:** $EXIT_CODE" >> $GITHUB_STEP_SUMMARY
else
echo "### ❌ Script Failed" >> $GITHUB_STEP_SUMMARY
echo "**Exit Code:** $EXIT_CODE" >> $GITHUB_STEP_SUMMARY
fi
exit $EXIT_CODE
- name: Upload script output
if: always()
uses: actions/upload-artifact@v6
with:
name: script-output-${{ github.run_id }}
path: |
*.log
*.json
*.csv
retention-days: 7
if-no-files-found: ignore

View File

@@ -19,411 +19,56 @@
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: GitHub.Workflow # DEFGROUP: GitHub.Workflow
# INGROUP: Moko-Cassiopeia.Compliance # INGROUP: MokoStandards.Compliance
# REPO: https://github.com/mokoconsulting-tech/moko-cassiopeia # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/standards-compliance.yml # PATH: /.github/workflows/standards-compliance.yml
# VERSION: 01.00.00 # VERSION: 01.00.00
# BRIEF: MokoStandards compliance validation workflow # BRIEF: Standards compliance validation workflow
# NOTE: Validates repository structure, documentation, and coding standards # NOTE: Runs manually, monthly, and on release builds
name: Standards Compliance name: Standards Compliance
on: on:
workflow_dispatch: # Run monthly on the 1st at 00:00 UTC
pull_request:
branches:
- main
- dev/**
- rc/**
- version/**
schedule: schedule:
# Run monthly on the 1st at 00:00 UTC
- cron: '0 0 1 * *' - cron: '0 0 1 * *'
workflow_run:
workflows: # Run on release creation
- "Create version branch and bump versions" release:
- "Release Pipeline (dev > rc > version > main)" types: [published, created]
types:
- completed # Allow manual triggering with options
workflow_dispatch:
inputs:
profile:
description: 'Validation profile'
required: false
type: choice
options:
- 'basic'
- 'full'
- 'strict'
default: 'full'
fail-on-warnings:
description: 'Fail workflow on warnings'
required: false
type: boolean
default: false
permissions: permissions:
contents: read contents: read
pull-requests: write pull-requests: write
checks: write
jobs: jobs:
repository-structure: compliance:
name: Repository Structure Validation name: Standards Compliance Validation
runs-on: ubuntu-latest uses: ./.github/workflows/reusable-ci-validation.yml
with:
steps: profile: ${{ inputs.profile || 'full' }}
- name: Checkout Repository validate-manifests: true
uses: actions/checkout@v6 validate-changelogs: true
validate-licenses: true
- name: Check Required Directories validate-security: true
run: | fail-on-warnings: ${{ inputs.fail-on-warnings || false }}
echo "### Required Directories" >> $GITHUB_STEP_SUMMARY secrets: inherit
MISSING=0
# Check required directories
for dir in docs tests scripts .github; do
if [ -d "$dir" ]; then
echo "✅ $dir/" >> $GITHUB_STEP_SUMMARY
else
echo "❌ $dir/ (missing)" >> $GITHUB_STEP_SUMMARY
MISSING=$((MISSING + 1))
fi
done
if [ $MISSING -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "⚠️ $MISSING required directories are missing" >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Check Required Files
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Required Files" >> $GITHUB_STEP_SUMMARY
MISSING=0
# Check required files
for file in README.md LICENSE CONTRIBUTING.md SECURITY.md CHANGELOG.md .editorconfig; do
if [ -f "$file" ]; then
echo "✅ $file" >> $GITHUB_STEP_SUMMARY
else
echo "❌ $file (missing)" >> $GITHUB_STEP_SUMMARY
MISSING=$((MISSING + 1))
fi
done
if [ $MISSING -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "⚠️ $MISSING required files are missing" >> $GITHUB_STEP_SUMMARY
echo "See: https://github.com/mokoconsulting-tech/MokoStandards/tree/main/templates/docs/required" >> $GITHUB_STEP_SUMMARY
exit 1
fi
documentation-quality:
name: Documentation Quality Check
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Validate README.md
run: |
echo "### README.md Validation" >> $GITHUB_STEP_SUMMARY
if [ ! -f "README.md" ]; then
echo "❌ README.md not found" >> $GITHUB_STEP_SUMMARY
exit 1
fi
# Check minimum length
SIZE=$(wc -c < README.md)
if [ $SIZE -lt 500 ]; then
echo "⚠️ README.md is too short ($SIZE bytes, minimum 500)" >> $GITHUB_STEP_SUMMARY
else
echo "✅ README.md has adequate content ($SIZE bytes)" >> $GITHUB_STEP_SUMMARY
fi
# Check for key sections
MISSING_SECTIONS=""
grep -qi "# \|## " README.md || MISSING_SECTIONS="${MISSING_SECTIONS}- No headings found\n"
if [ -n "$MISSING_SECTIONS" ]; then
echo "⚠️ README.md may be missing important sections" >> $GITHUB_STEP_SUMMARY
else
echo "✅ README.md appears well-structured" >> $GITHUB_STEP_SUMMARY
fi
- name: Validate CHANGELOG.md
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### CHANGELOG.md Validation" >> $GITHUB_STEP_SUMMARY
if [ ! -f "CHANGELOG.md" ]; then
echo "❌ CHANGELOG.md not found" >> $GITHUB_STEP_SUMMARY
exit 1
fi
# Check for Keep a Changelog format markers
if grep -qi "## \[.*\]" CHANGELOG.md; then
echo "✅ CHANGELOG.md follows Keep a Changelog format" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ CHANGELOG.md may not follow Keep a Changelog format" >> $GITHUB_STEP_SUMMARY
echo "See: https://keepachangelog.com/" >> $GITHUB_STEP_SUMMARY
fi
- name: Check Documentation Index
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Documentation Index" >> $GITHUB_STEP_SUMMARY
if [ -f "docs/index.md" ] || [ -f "docs/README.md" ]; then
echo "✅ Documentation index found" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ No documentation index (docs/index.md or docs/README.md)" >> $GITHUB_STEP_SUMMARY
fi
coding-standards:
name: Coding Standards Check
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Check for Tab Characters
run: |
echo "### Tab Character Detection" >> $GITHUB_STEP_SUMMARY
# Find files with tabs (excluding certain file types)
TABS_FOUND=$(find . -type f \
! -path "./vendor/*" \
! -path "./node_modules/*" \
! -path "./.git/*" \
! -name "Makefile*" \
! -name "*.tsv" \
-exec grep -l $'\t' {} \; 2>/dev/null | head -10)
if [ -n "$TABS_FOUND" ]; then
echo "⚠️ Tab characters found in files:" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "$TABS_FOUND" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "MokoStandards requires spaces over tabs (except in Makefiles)" >> $GITHUB_STEP_SUMMARY
else
echo "✅ No inappropriate tab characters found" >> $GITHUB_STEP_SUMMARY
fi
- name: Check File Encoding
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### File Encoding Check" >> $GITHUB_STEP_SUMMARY
# Check for UTF-8 encoding
NON_UTF8=$(find . -type f \( -name "*.php" -o -name "*.js" -o -name "*.md" \) \
! -path "./vendor/*" \
! -path "./node_modules/*" \
! -path "./.git/*" \
-exec file {} \; | grep -v "UTF-8" | head -5)
if [ -n "$NON_UTF8" ]; then
echo "⚠️ Non-UTF-8 files detected:" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "$NON_UTF8" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
else
echo "✅ All source files appear to be UTF-8 encoded" >> $GITHUB_STEP_SUMMARY
fi
- name: Check Line Endings
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Line Ending Check" >> $GITHUB_STEP_SUMMARY
# Check for CRLF line endings
CRLF_FILES=$(find . -type f \( -name "*.php" -o -name "*.js" -o -name "*.md" \) \
! -path "./vendor/*" \
! -path "./node_modules/*" \
! -path "./.git/*" \
-exec file {} \; | grep "CRLF" | head -5)
if [ -n "$CRLF_FILES" ]; then
echo "⚠️ Files with CRLF line endings found:" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "$CRLF_FILES" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "MokoStandards requires LF line endings" >> $GITHUB_STEP_SUMMARY
else
echo "✅ Line endings are consistent (LF)" >> $GITHUB_STEP_SUMMARY
fi
license-compliance:
name: License Header Validation
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Check SPDX Headers
run: |
echo "### SPDX License Header Check" >> $GITHUB_STEP_SUMMARY
# Count source files with and without SPDX headers
TOTAL_PHP=0
WITH_SPDX_PHP=0
if find . -name "*.php" -type f ! -path "./vendor/*" | head -1 | grep -q .; then
TOTAL_PHP=$(find . -name "*.php" -type f ! -path "./vendor/*" | wc -l)
WITH_SPDX_PHP=$(find . -name "*.php" -type f ! -path "./vendor/*" -exec grep -l "SPDX-License-Identifier" {} \; | wc -l)
fi
if [ $TOTAL_PHP -gt 0 ]; then
PERCENT=$((WITH_SPDX_PHP * 100 / TOTAL_PHP))
echo "- PHP files: $WITH_SPDX_PHP/$TOTAL_PHP ($PERCENT%) with SPDX headers" >> $GITHUB_STEP_SUMMARY
if [ $PERCENT -lt 80 ]; then
echo "⚠️ Less than 80% of PHP files have SPDX headers" >> $GITHUB_STEP_SUMMARY
else
echo "✅ Good SPDX header coverage" >> $GITHUB_STEP_SUMMARY
fi
fi
- name: Validate License File
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### License File Validation" >> $GITHUB_STEP_SUMMARY
if [ ! -f "LICENSE" ]; then
echo "❌ LICENSE file not found" >> $GITHUB_STEP_SUMMARY
exit 1
fi
# Check license type
if grep -qi "GNU GENERAL PUBLIC LICENSE" LICENSE; then
VERSION=$(grep -i "Version 3" LICENSE || echo "")
if [ -n "$VERSION" ]; then
echo "✅ GPL-3.0-or-later license detected" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ GPL license detected but version unclear" >> $GITHUB_STEP_SUMMARY
fi
elif grep -qi "MIT License" LICENSE; then
echo "✅ MIT license detected" >> $GITHUB_STEP_SUMMARY
elif grep -qi "Apache License" LICENSE; then
echo "✅ Apache license detected" >> $GITHUB_STEP_SUMMARY
else
echo " License type could not be automatically detected" >> $GITHUB_STEP_SUMMARY
fi
git-hygiene:
name: Git Repository Hygiene
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Check .gitignore
run: |
echo "### .gitignore Validation" >> $GITHUB_STEP_SUMMARY
if [ ! -f ".gitignore" ]; then
echo "⚠️ .gitignore file not found" >> $GITHUB_STEP_SUMMARY
exit 0
fi
# Check for common exclusions
MISSING=""
grep -q "vendor/" .gitignore || MISSING="${MISSING}vendor/ "
grep -q "node_modules/" .gitignore || MISSING="${MISSING}node_modules/ "
if [ -n "$MISSING" ]; then
echo "⚠️ .gitignore may be missing common exclusions: $MISSING" >> $GITHUB_STEP_SUMMARY
else
echo "✅ .gitignore appears complete" >> $GITHUB_STEP_SUMMARY
fi
- name: Check for Large Files
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Large File Detection" >> $GITHUB_STEP_SUMMARY
# Find files larger than 1MB
LARGE_FILES=$(find . -type f -size +1M ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" | head -5)
if [ -n "$LARGE_FILES" ]; then
echo "⚠️ Large files detected (>1MB):" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "$LARGE_FILES" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "Consider using Git LFS for large binary files" >> $GITHUB_STEP_SUMMARY
else
echo "✅ No unusually large files detected" >> $GITHUB_STEP_SUMMARY
fi
workflow-validation:
name: Workflow Configuration Check
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Check Required Workflows
run: |
echo "### GitHub Actions Workflows" >> $GITHUB_STEP_SUMMARY
WORKFLOWS_DIR=".github/workflows"
if [ ! -d "$WORKFLOWS_DIR" ]; then
echo "❌ No workflows directory found" >> $GITHUB_STEP_SUMMARY
exit 1
fi
# Check for recommended workflows
if [ -f "$WORKFLOWS_DIR/ci.yml" ] || [ -f "$WORKFLOWS_DIR/build.yml" ]; then
echo "✅ CI workflow present" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ No CI workflow found (ci.yml or build.yml)" >> $GITHUB_STEP_SUMMARY
fi
if [ -f "$WORKFLOWS_DIR/codeql-analysis.yml" ]; then
echo "✅ CodeQL security scanning present" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ CodeQL workflow not found" >> $GITHUB_STEP_SUMMARY
fi
- name: Validate Workflow Syntax
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Workflow YAML Syntax" >> $GITHUB_STEP_SUMMARY
INVALID=0
shopt -s nullglob
for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do
if [ -f "$workflow" ]; then
if python3 -c "import yaml; yaml.safe_load(open('$workflow'))" 2>/dev/null; then
echo "✅ $(basename $workflow)" >> $GITHUB_STEP_SUMMARY
else
echo "❌ $(basename $workflow) - invalid YAML" >> $GITHUB_STEP_SUMMARY
INVALID=$((INVALID + 1))
fi
fi
done
if [ $INVALID -gt 0 ]; then
exit 1
fi
summary:
name: Compliance Summary
runs-on: ubuntu-latest
needs: [repository-structure, documentation-quality, coding-standards, license-compliance, git-hygiene, workflow-validation]
if: always()
steps:
- name: Generate Compliance Report
run: |
echo "# MokoStandards Compliance Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "All compliance checks have been executed." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Validation Areas:" >> $GITHUB_STEP_SUMMARY
echo "- Repository Structure" >> $GITHUB_STEP_SUMMARY
echo "- Documentation Quality" >> $GITHUB_STEP_SUMMARY
echo "- Coding Standards" >> $GITHUB_STEP_SUMMARY
echo "- License Compliance" >> $GITHUB_STEP_SUMMARY
echo "- Git Repository Hygiene" >> $GITHUB_STEP_SUMMARY
echo "- Workflow Configuration" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "For detailed results, review individual job outputs above." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "📚 Learn more: https://github.com/mokoconsulting-tech/MokoStandards" >> $GITHUB_STEP_SUMMARY

View File

@@ -1,6 +1,21 @@
name: Create version branch and bump versions name: Version Branch and Bump
on: on:
# Run monthly on the 1st at 03:00 UTC for automated version planning
schedule:
- cron: '0 3 1 * *'
# Run on pull requests to version branches for validation
pull_request:
branches:
- version/**
- rc/**
# Run on release creation for version tracking
release:
types: [published, created]
# Allow manual triggering with full options
workflow_dispatch: workflow_dispatch:
inputs: inputs:
new_version: new_version: