Pull workflows from MokoStandards and add reusable workflows
Co-authored-by: jmiller-moko <230051081+jmiller-moko@users.noreply.github.com>
This commit is contained in:
37
.github/workflows/ci.yml
vendored
37
.github/workflows/ci.yml
vendored
@@ -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
|
||||||
|
|||||||
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@@ -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
|
||||||
|
|||||||
4
.github/workflows/release_pipeline.yml
vendored
4
.github/workflows/release_pipeline.yml
vendored
@@ -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
222
.github/workflows/reusable-build.yml
vendored
Normal 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
|
||||||
534
.github/workflows/reusable-ci-validation.yml
vendored
Normal file
534
.github/workflows/reusable-ci-validation.yml
vendored
Normal 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
312
.github/workflows/reusable-deploy.yml
vendored
Normal 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
|
||||||
356
.github/workflows/reusable-joomla-testing.yml
vendored
Normal file
356
.github/workflows/reusable-joomla-testing.yml
vendored
Normal 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"
|
||||||
297
.github/workflows/reusable-php-quality.yml
vendored
Normal file
297
.github/workflows/reusable-php-quality.yml
vendored
Normal 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"
|
||||||
138
.github/workflows/reusable-project-detector.yml
vendored
Normal file
138
.github/workflows/reusable-project-detector.yml
vendored
Normal 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
397
.github/workflows/reusable-release.yml
vendored
Normal 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
|
||||||
210
.github/workflows/reusable-script-executor.yml
vendored
Normal file
210
.github/workflows/reusable-script-executor.yml
vendored
Normal 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
|
||||||
431
.github/workflows/standards-compliance.yml
vendored
431
.github/workflows/standards-compliance.yml
vendored
@@ -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:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- dev/**
|
|
||||||
- rc/**
|
|
||||||
- version/**
|
|
||||||
schedule:
|
|
||||||
# Run monthly on the 1st at 00:00 UTC
|
# Run monthly on the 1st at 00:00 UTC
|
||||||
|
schedule:
|
||||||
- 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
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Check Required Directories
|
|
||||||
run: |
|
|
||||||
echo "### Required Directories" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
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:
|
with:
|
||||||
fetch-depth: 0
|
profile: ${{ inputs.profile || 'full' }}
|
||||||
|
validate-manifests: true
|
||||||
- name: Check .gitignore
|
validate-changelogs: true
|
||||||
run: |
|
validate-licenses: true
|
||||||
echo "### .gitignore Validation" >> $GITHUB_STEP_SUMMARY
|
validate-security: true
|
||||||
|
fail-on-warnings: ${{ inputs.fail-on-warnings || false }}
|
||||||
if [ ! -f ".gitignore" ]; then
|
secrets: inherit
|
||||||
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
|
|
||||||
|
|||||||
17
.github/workflows/version_branch.yml
vendored
17
.github/workflows/version_branch.yml
vendored
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user