chore: Sync MokoStandards 04.04.00 #106

Closed
jmiller-moko wants to merge 39 commits from chore/sync-mokostandards-v04.04.00 into main
21 changed files with 2774 additions and 1712 deletions

20
.github/.mokostandards vendored Normal file
View File

@@ -0,0 +1,20 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: MokoStandards.Templates.Config
# INGROUP: MokoStandards.Templates
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/configs/moko-standards.yml
# VERSION: 04.04.00
# BRIEF: Governance attachment template — synced to .mokostandards in every governed repository
# NOTE: Tokens replaced at sync time: mokoconsulting-tech, MokoCassiopeia, waas-component, 04.04.00
#
# This file is managed automatically by MokoStandards bulk sync.
# Do not edit manually — changes will be overwritten on the next sync.
# To update governance settings, open a PR in MokoStandards instead:
# https://github.com/mokoconsulting-tech/MokoStandards
standards_source: "https://github.com/mokoconsulting-tech/MokoStandards"
standards_version: "04.04.00"
platform: "waas-component"
governed_repo: "mokoconsulting-tech/MokoCassiopeia"

View File

@@ -3,7 +3,7 @@ name: Firewall Request
about: Request firewall rule changes or access to external resources about: Request firewall rule changes or access to external resources
title: '[FIREWALL] [Resource Name] - [Brief Description]' title: '[FIREWALL] [Resource Name] - [Brief Description]'
labels: ['firewall-request', 'infrastructure', 'security'] labels: ['firewall-request', 'infrastructure', 'security']
assignees: [] assignees: ['jmiller-moko']
--- ---

View File

@@ -3,7 +3,7 @@ name: Question
about: Ask a question about usage, features, or best practices about: Ask a question about usage, features, or best practices
title: '[QUESTION] ' title: '[QUESTION] '
labels: ['question'] labels: ['question']
assignees: [] assignees: ['jmiller-moko']
--- ---

View File

@@ -3,7 +3,7 @@ name: License Request
about: Request an organization license for Sublime Text about: Request an organization license for Sublime Text
title: '[LICENSE REQUEST] Sublime Text - [Your Name]' title: '[LICENSE REQUEST] Sublime Text - [Your Name]'
labels: ['license-request', 'admin'] labels: ['license-request', 'admin']
assignees: [] assignees: ['jmiller-moko']
--- ---

102
.github/workflows/auto-dev-issue.yml vendored Normal file
View File

@@ -0,0 +1,102 @@
# 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
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Automation
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/auto-dev-issue.yml
# VERSION: 04.04.00
# BRIEF: Auto-create tracking issue when a dev/** or rc/** branch is pushed
# NOTE: Synced via bulk-repo-sync to .github/workflows/auto-dev-issue.yml in all governed repos.
name: Auto Dev Branch Issue
on:
create:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: read
issues: write
jobs:
create-issue:
name: Create version tracking issue
runs-on: ubuntu-latest
if: >-
github.event.ref_type == 'branch' &&
(startsWith(github.event.ref, 'dev/') || startsWith(github.event.ref, 'rc/'))
steps:
- name: Create tracking issue
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
BRANCH="${{ github.event.ref }}"
REPO="${{ github.repository }}"
ACTOR="${{ github.actor }}"
NOW=$(date -u '+%Y-%m-%d %H:%M UTC')
# Determine branch type and version
if [[ "$BRANCH" == rc/* ]]; then
VERSION="${BRANCH#rc/}"
BRANCH_TYPE="Release Candidate"
LABEL_TYPE="type: release"
TITLE_PREFIX="rc"
else
VERSION="${BRANCH#dev/}"
BRANCH_TYPE="Development"
LABEL_TYPE="type: feature"
TITLE_PREFIX="feat"
fi
TITLE="${TITLE_PREFIX}(${VERSION}): ${BRANCH_TYPE} tracking for ${BRANCH}"
BODY="## ${BRANCH_TYPE} Branch Created
| Field | Value |
|-------|-------|
| **Branch** | \`${BRANCH}\` |
| **Version** | \`${VERSION}\` |
| **Type** | ${BRANCH_TYPE} |
| **Created by** | @${ACTOR} |
| **Created at** | ${NOW} |
| **Repository** | \`${REPO}\` |
## Checklist
- [ ] Feature development complete
- [ ] Tests passing
- [ ] README.md version bumped to \`${VERSION}\`
- [ ] CHANGELOG.md updated
- [ ] PR created targeting \`main\`
- [ ] Code reviewed and approved
- [ ] Merged to \`main\`
---
*Auto-created by [auto-dev-issue.yml](.github/workflows/auto-dev-issue.yml) on branch creation.*"
# Dedent heredoc
BODY=$(echo "$BODY" | sed 's/^ //')
# Check for existing issue with same title prefix
EXISTING=$(gh api "repos/${REPO}/issues?state=open&per_page=5" \
--jq ".[] | select(.title | startswith(\"${TITLE_PREFIX}(${VERSION})\")) | .number" 2>/dev/null | head -1)
if [ -n "$EXISTING" ]; then
echo " Issue #${EXISTING} already exists for ${VERSION}" >> $GITHUB_STEP_SUMMARY
else
ISSUE_URL=$(gh issue create \
--repo "$REPO" \
--title "$TITLE" \
--body "$BODY" \
--label "${LABEL_TYPE},version" \
--assignee "jmiller-moko" 2>&1)
echo "✅ Created tracking issue: ${ISSUE_URL}" >> $GITHUB_STEP_SUMMARY
fi

View File

@@ -1,7 +1,5 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> # 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 # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
@@ -9,12 +7,30 @@
# INGROUP: MokoStandards.Release # INGROUP: MokoStandards.Release
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/auto-release.yml # PATH: /templates/workflows/shared/auto-release.yml
# VERSION: 04.01.00 # VERSION: 04.04.00
# BRIEF: Auto-create a GitHub Release on every push to main with version from README.md # BRIEF: Unified build & release pipeline — version branch, platform version, badges, tag, release
# NOTE: Synced via bulk-repo-sync to .github/workflows/auto-release.yml in all governed repos. #
# For Dolibarr (crm-module) repos, also updates $this->version in the module descriptor. # ╔════════════════════════════════════════════════════════════════════════╗
# ║ BUILD & RELEASE PIPELINE ║
# ╠════════════════════════════════════════════════════════════════════════╣
# ║ ║
# ║ Triggers on push to main (skips bot commits + [skip ci]): ║
# ║ ║
# ║ Every push: ║
# ║ 1. Read version from README.md ║
# ║ 3. Set platform version (Dolibarr $this->version, Joomla <version>)║
# ║ 4. Update [VERSION: XX.YY.ZZ] badges in markdown files ║
# ║ 5. Write update.txt / update.xml ║
# ║ 6. Create git tag vXX.YY.ZZ ║
# ║ 7a. Patch: update existing GitHub Release for this minor ║
# ║ ║
# ║ Minor releases only (patch == 00): ║
# ║ 2. Create version/XX.YY.ZZ branch (immutable snapshot) ║
# ║ 7b. Create new GitHub Release ║
# ║ ║
# ╚════════════════════════════════════════════════════════════════════════╝
name: Auto Release name: Build & Release
on: on:
push: push:
@@ -22,14 +38,16 @@ on:
- main - main
- master - master
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions: permissions:
contents: write contents: write
jobs: jobs:
release: release:
name: Create Release name: Build & Release Pipeline
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Skip bot commits (version sync, [skip ci]) to avoid infinite loops
if: >- if: >-
!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip ci]') &&
github.actor != 'github-actions[bot]' github.actor != 'github-actions[bot]'
@@ -41,123 +59,387 @@ jobs:
token: ${{ secrets.GH_TOKEN || github.token }} token: ${{ secrets.GH_TOKEN || github.token }}
fetch-depth: 0 fetch-depth: 0
- name: Extract version from README.md - name: Setup MokoStandards tools
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch version/04.04.00 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards
cd /tmp/mokostandards
composer install --no-dev --no-interaction --quiet
# ── STEP 1: Read version ───────────────────────────────────────────
- name: "Step 1: Read version from README.md"
id: version id: version
run: | run: |
VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1) VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null)
if [ -z "$VERSION" ]; then if [ -z "$VERSION" ]; then
echo " No VERSION found in README.md — skipping release" echo " No VERSION in README.md — skipping release"
echo "skip=true" >> "$GITHUB_OUTPUT" echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0 exit 0
fi fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT" echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
echo "branch=version/${VERSION}" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT" echo "skip=false" >> "$GITHUB_OUTPUT"
echo "✅ Version: $VERSION (tag: v${VERSION})"
- name: Check if tag already exists # Detect if this is a minor release (patch == 00)
PATCH=$(echo "$VERSION" | awk -F. '{print $3}')
if [ "$PATCH" = "00" ]; then
echo "is_minor=true" >> "$GITHUB_OUTPUT"
echo "✅ Version: $VERSION (minor release — full pipeline)"
else
echo "is_minor=false" >> "$GITHUB_OUTPUT"
echo "✅ Version: $VERSION (patch — platform version + badges only)"
fi
- name: Check if already released
if: steps.version.outputs.skip != 'true' if: steps.version.outputs.skip != 'true'
id: tag_check id: check
run: | run: |
TAG="${{ steps.version.outputs.tag }}" TAG="${{ steps.version.outputs.tag }}"
if git rev-parse "$TAG" >/dev/null 2>&1; then BRANCH="${{ steps.version.outputs.branch }}"
echo " Tag $TAG already exists — skipping release"
echo "exists=true" >> "$GITHUB_OUTPUT" TAG_EXISTS=false
BRANCH_EXISTS=false
git rev-parse "$TAG" >/dev/null 2>&1 && TAG_EXISTS=true
git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q "$BRANCH" && BRANCH_EXISTS=true
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
echo "branch_exists=$BRANCH_EXISTS" >> "$GITHUB_OUTPUT"
if [ "$TAG_EXISTS" = "true" ] && [ "$BRANCH_EXISTS" = "true" ]; then
echo "already_released=true" >> "$GITHUB_OUTPUT"
else else
echo "exists=false" >> "$GITHUB_OUTPUT" echo "already_released=false" >> "$GITHUB_OUTPUT"
fi fi
- name: Update Dolibarr module version # ── SANITY CHECKS ────────────────────────────────────────────────────
- name: "Sanity: Platform-specific validation"
if: >- if: >-
steps.version.outputs.skip != 'true' && steps.version.outputs.skip != 'true' &&
steps.tag_check.outputs.exists != 'true' steps.check.outputs.already_released != 'true'
run: | run: |
PLATFORM="" VERSION="${{ steps.version.outputs.version }}"
if [ -f ".moko-standards" ]; then PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null)
PLATFORM=$(grep -E '^platform:' .moko-standards | sed 's/.*:[[:space:]]*//' | tr -d '"') ERRORS=0
echo "## 🔍 Pre-Release Sanity Checks" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Platform: \`${PLATFORM}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Common checks
if [ ! -f "LICENSE" ]; then
echo "❌ Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "✅ LICENSE" >> $GITHUB_STEP_SUMMARY
fi fi
if [ ! -d "src" ]; then
echo "⚠️ No src/ directory" >> $GITHUB_STEP_SUMMARY
else
echo "✅ src/ directory" >> $GITHUB_STEP_SUMMARY
fi
# Dolibarr-specific checks
if [ "$PLATFORM" = "crm-module" ]; then
MOD_FILE=$(find src -path "*/core/modules/mod*.class.php" -print -quit 2>/dev/null)
if [ -z "$MOD_FILE" ]; then
echo "❌ No module descriptor (src/core/modules/mod*.class.php)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "✅ Module descriptor: \`${MOD_FILE}\`" >> $GITHUB_STEP_SUMMARY
# Check module number
NUMERO=$(grep -oP '\$this->numero\s*=\s*\K\d+' "$MOD_FILE" 2>/dev/null || echo "0")
if [ "$NUMERO" = "0" ] || [ -z "$NUMERO" ]; then
echo "❌ Module number (\$this->numero) is 0 or not set" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "✅ Module number: ${NUMERO}" >> $GITHUB_STEP_SUMMARY
fi
# Check url_last_version exists
if grep -q 'url_last_version' "$MOD_FILE" 2>/dev/null; then
echo "✅ url_last_version is set" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ url_last_version not set — update checks won't work" >> $GITHUB_STEP_SUMMARY
fi
fi
fi
# Joomla-specific checks
if [ "$PLATFORM" = "waas-component" ]; then
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -z "$MANIFEST" ]; then
echo "❌ No Joomla XML manifest found" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "✅ Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
# Check extension type
TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null)
echo "✅ Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
fi
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$ERRORS" -gt 0 ]; then
echo "**❌ ${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
else
echo "**✅ All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
fi
# ── STEP 2: Create version branch (minor releases only) ─────────────
- name: "Step 2: Create version branch"
if: >-
steps.version.outputs.skip != 'true' &&
steps.version.outputs.is_minor == 'true' &&
steps.check.outputs.branch_exists != 'true'
run: |
BRANCH="${{ steps.version.outputs.branch }}"
git checkout -b "$BRANCH"
git push origin "$BRANCH"
echo "🌿 Created branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
# ── STEP 3: Set platform version ───────────────────────────────────
- name: "Step 3: Set platform version"
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.already_released != 'true'
run: |
VERSION="${{ steps.version.outputs.version }}" VERSION="${{ steps.version.outputs.version }}"
php /tmp/mokostandards/api/cli/version_set_platform.php \
--path . --version "$VERSION" --branch main
# ── STEP 4: Update version badges ──────────────────────────────────
- name: "Step 4: Update version badges"
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.already_released != 'true'
run: |
VERSION="${{ steps.version.outputs.version }}"
find . -name "*.md" ! -path "./.git/*" ! -path "./vendor/*" | while read -r f; do
if grep -q '\[VERSION:' "$f" 2>/dev/null; then
sed -i "s/\[VERSION:[[:space:]]*[0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{2\}\]/[VERSION: ${VERSION}]/" "$f"
fi
done
# ── STEP 5: Write update files (Dolibarr: update.txt / Joomla: update.xml)
- name: "Step 5: Write update files"
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.already_released != 'true'
run: |
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null)
VERSION="${{ steps.version.outputs.version }}"
REPO="${{ github.repository }}"
if [ "$PLATFORM" = "crm-module" ]; then if [ "$PLATFORM" = "crm-module" ]; then
echo "📦 Dolibarr release — setting module version to '${VERSION}'" printf '%s' "$VERSION" > update.txt
# Update $this->version in the module descriptor (core/modules/mod*.class.php) echo "📦 update.txt: ${VERSION}" >> $GITHUB_STEP_SUMMARY
find . -path "*/core/modules/mod*.class.php" -exec \
sed -i "s/\(\$this->version\s*=\s*\)['\"][^'\"]*['\"]/\1'${VERSION}'/" {} + 2>/dev/null || true
fi fi
if [ "$PLATFORM" = "waas-component" ]; then if [ "$PLATFORM" = "waas-component" ]; then
echo "📦 Joomla release — setting manifest version to '${VERSION}'" # ── Parse extension metadata from XML manifest ──────────────
# Update <version> tag in Joomla XML manifest files MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | while read -r manifest; do if [ -z "$MANIFEST" ]; then
sed -i "s|<version>[^<]*</version>|<version>${VERSION}</version>|" "$manifest" 2>/dev/null || true echo "⚠️ No Joomla XML manifest found — skipping update.xml" >> $GITHUB_STEP_SUMMARY
done else
EXT_NAME=$(grep -oP '<name>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}")
EXT_TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component")
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
EXT_CLIENT=$(grep -oP '<extension[^>]+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
EXT_FOLDER=$(grep -oP '<extension[^>]+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
TARGET_PLATFORM=$(grep -oP '<targetplatform[^/]*/>' "$MANIFEST" 2>/dev/null | head -1 || echo "")
PHP_MINIMUM=$(grep -oP '<php_minimum>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
# Derive element from manifest filename if not in XML
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(basename "$MANIFEST" .xml)
fi
# Build client tag: plugins and frontend modules need <client>site</client>
CLIENT_TAG=""
if [ -n "$EXT_CLIENT" ]; then
CLIENT_TAG="<client>${EXT_CLIENT}</client>"
elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then
CLIENT_TAG="<client>site</client>"
fi
# Build folder tag for plugins (required for Joomla to match the update)
FOLDER_TAG=""
if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then
FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
fi
# Build targetplatform (fallback to Joomla 4+5 if not in manifest)
if [ -z "$TARGET_PLATFORM" ]; then
TARGET_PLATFORM='<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />'
fi
# Build php_minimum tag
PHP_TAG=""
if [ -n "$PHP_MINIMUM" ]; then
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
fi
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip"
INFO_URL="https://github.com/${REPO}/releases/tag/v${VERSION}"
# ── Write update.xml (stable release) ───────────────────────
cat > update.xml << 'XMLEOF'
<?xml version="1.0" encoding="utf-8"?>
<updates>
<update>
<name>EXT_NAME_PH</name>
<description>EXT_NAME_PH update</description>
<element>EXT_ELEMENT_PH</element>
<type>EXT_TYPE_PH</type>
<version>VERSION_PH</version>
CLIENT_TAG_PH
FOLDER_TAG_PH
<tags>
<tag>stable</tag>
</tags>
<infourl title="EXT_NAME_PH">INFO_URL_PH</infourl>
<downloads>
<downloadurl type="full" format="zip">DOWNLOAD_URL_PH</downloadurl>
</downloads>
TARGET_PLATFORM_PH
PHP_TAG_PH
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
</updates>
XMLEOF
# Replace placeholders (avoids heredoc variable expansion issues with XML)
sed -i "s|EXT_NAME_PH|${EXT_NAME}|g" update.xml
sed -i "s|EXT_ELEMENT_PH|${EXT_ELEMENT}|g" update.xml
sed -i "s|EXT_TYPE_PH|${EXT_TYPE}|g" update.xml
sed -i "s|VERSION_PH|${VERSION}|g" update.xml
sed -i "s|CLIENT_TAG_PH|${CLIENT_TAG}|g" update.xml
sed -i "s|FOLDER_TAG_PH|${FOLDER_TAG}|g" update.xml
sed -i "s|INFO_URL_PH|${INFO_URL}|g" update.xml
sed -i "s|DOWNLOAD_URL_PH|${DOWNLOAD_URL}|g" update.xml
sed -i "s|TARGET_PLATFORM_PH|${TARGET_PLATFORM}|g" update.xml
sed -i "s|PHP_TAG_PH|${PHP_TAG}|g" update.xml
# Remove empty placeholder lines
sed -i '/^[[:space:]]*$/d' update.xml
echo "📦 update.xml: ${VERSION} (stable) — ${EXT_TYPE}/${EXT_ELEMENT}" >> $GITHUB_STEP_SUMMARY
fi
fi fi
# Commit the version update if anything changed # ── Commit all changes ─────────────────────────────────────────────
if ! git diff --quiet; then - name: Commit release changes
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add -A
git commit -m "chore(release): set version to ${VERSION} [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push
fi
- name: Extract changelog entry
if: >- if: >-
steps.version.outputs.skip != 'true' && steps.version.outputs.skip != 'true' &&
steps.tag_check.outputs.exists != 'true' steps.check.outputs.already_released != 'true'
id: changelog
run: | run: |
if git diff --quiet && git diff --cached --quiet; then
echo " No changes to commit"
exit 0
fi
VERSION="${{ steps.version.outputs.version }}" VERSION="${{ steps.version.outputs.version }}"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add -A
git commit -m "chore(release): build ${VERSION} [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push
# Try to extract the section for this version from CHANGELOG.md # ── STEP 6: Create tag ─────────────────────────────────────────────
NOTES="" - name: "Step 6: Create git tag"
if [ -f "CHANGELOG.md" ]; then
# Extract text between this version's heading and the next heading
NOTES=$(awk "/^##.*${VERSION}/,/^## /" CHANGELOG.md | head -50 | sed '1d;$d')
fi
if [ -z "$NOTES" ]; then
NOTES="Release ${VERSION}"
fi
# Write to file to avoid shell escaping issues
echo "$NOTES" > /tmp/release_notes.md
echo "✅ Release notes prepared"
- name: Create tag and release
if: >- if: >-
steps.version.outputs.skip != 'true' && steps.version.outputs.skip != 'true' &&
steps.tag_check.outputs.exists != 'true' steps.check.outputs.tag_exists != 'true'
run: |
TAG="${{ steps.version.outputs.tag }}"
git tag "$TAG"
git push origin "$TAG"
echo "🏷️ Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
# ── STEP 7: Create or update GitHub Release ──────────────────────────
- name: "Step 7: GitHub Release"
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.tag_exists != 'true'
env: env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: | run: |
TAG="${{ steps.version.outputs.tag }}"
VERSION="${{ steps.version.outputs.version }}" VERSION="${{ steps.version.outputs.version }}"
TAG="${{ steps.version.outputs.tag }}"
BRANCH="${{ steps.version.outputs.branch }}"
IS_MINOR="${{ steps.version.outputs.is_minor }}"
# Create the tag # Derive the minor version base (XX.YY.00)
git tag "$TAG" MINOR_BASE=$(echo "$VERSION" | sed 's/\.[0-9]*$/.00/')
git push origin "$TAG" MINOR_TAG="v${MINOR_BASE}"
# Create the release NOTES=$(php /tmp/mokostandards/api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
gh release create "$TAG" \ [ -z "$NOTES" ] && NOTES="Release ${VERSION}"
--title "${VERSION}" \ echo "$NOTES" > /tmp/release_notes.md
--notes-file /tmp/release_notes.md \
--target main
echo "🚀 Release ${VERSION} created: $TAG" if [ "$IS_MINOR" = "true" ]; then
# Minor release: create new GitHub Release
gh release create "$TAG" \
--title "${VERSION}" \
--notes-file /tmp/release_notes.md \
--target "$BRANCH"
echo "🚀 Release created: ${VERSION}" >> $GITHUB_STEP_SUMMARY
else
# Patch release: update the existing minor release with new tag
# Find the latest release for this minor version
EXISTING=$(gh release view "$MINOR_TAG" --json tagName -q .tagName 2>/dev/null || true)
if [ -n "$EXISTING" ]; then
# Update existing release body with patch info
CURRENT_NOTES=$(gh release view "$MINOR_TAG" --json body -q .body 2>/dev/null || true)
{
echo "$CURRENT_NOTES"
echo ""
echo "---"
echo "### Patch ${VERSION}"
echo ""
cat /tmp/release_notes.md
} > /tmp/updated_notes.md
- name: Summary gh release edit "$MINOR_TAG" \
if: steps.version.outputs.skip != 'true' --title "${MINOR_BASE} (latest: ${VERSION})" \
--notes-file /tmp/updated_notes.md
echo "📝 Release updated: ${MINOR_BASE} → patch ${VERSION}" >> $GITHUB_STEP_SUMMARY
else
# No existing minor release found — create one for this patch
gh release create "$TAG" \
--title "${VERSION}" \
--notes-file /tmp/release_notes.md
echo "🚀 Release created: ${VERSION} (no minor release found)" >> $GITHUB_STEP_SUMMARY
fi
fi
# ── Summary ────────────────────────────────────────────────────────
- name: Pipeline Summary
if: always()
run: | run: |
VERSION="${{ steps.version.outputs.version }}" VERSION="${{ steps.version.outputs.version }}"
TAG="${{ steps.version.outputs.tag }}" if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
if [ "${{ steps.tag_check.outputs.exists }}" = "true" ]; then echo "## ⏭️ Release Skipped" >> $GITHUB_STEP_SUMMARY
echo "## Release — ${VERSION}" >> $GITHUB_STEP_SUMMARY echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
echo "Tag \`${TAG}\` already exists — no new release created." >> $GITHUB_STEP_SUMMARY elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
else else
echo "## 🚀 Release — ${VERSION}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "Created tag \`${TAG}\` and GitHub Release." >> $GITHUB_STEP_SUMMARY echo "## ✅ Build & Release Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi fi

View File

@@ -5,15 +5,17 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: GitHub.Workflow # DEFGROUP: GitHub.Workflow.Template
# INGROUP: MokoStandards.Security # INGROUP: MokoStandards.Security
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/codeql-analysis.yml # PATH: /templates/workflows/generic/codeql-analysis.yml
# VERSION: 04.01.00 # VERSION: 04.04.00
# BRIEF: CodeQL security scanning workflow for PHP codebase # BRIEF: CodeQL security scanning workflow (generic — all repo types)
# NOTE: Repository is PHP-only (v04.00.04). Python was removed Feb 12, 2026. # NOTE: Deployed to .github/workflows/codeql-analysis.yml in governed repos.
# CodeQL does not support PHP directly; JavaScript scans JSON/YAML/shell.
# For PHP-specific security scanning see standards-compliance.yml.
name: "CodeQL Security Scanning" name: CodeQL Security Scanning
on: on:
push: push:
@@ -28,7 +30,7 @@ on:
- dev/** - dev/**
- rc/** - rc/**
schedule: schedule:
# Run weekly on Monday at 6:00 AM UTC # Weekly on Monday at 06:00 UTC
- cron: '0 6 * * 1' - cron: '0 6 * * 1'
workflow_dispatch: workflow_dispatch:
@@ -40,65 +42,60 @@ permissions:
jobs: jobs:
analyze: analyze:
name: Configuration Security Scan name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 360 timeout-minutes: 360
# No language matrix - PHP-only repository strategy:
# CodeQL scans workflow files, configs, and scripts for security issues fail-fast: false
# PHP security handled by SecurityValidator enterprise library matrix:
# CodeQL does not support PHP. Use 'javascript' to scan JSON, YAML,
# and shell scripts. Add 'actions' to scan GitHub Actions workflows.
language: ['javascript', 'actions']
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 uses: github/codeql-action/init@v3
with: with:
# No languages specified - scan configurations only languages: ${{ matrix.language }}
# Reference explicit config to scan YAML, JSON, shell scripts
config-file: ./.github/codeql/codeql-config.yml
# Use security-extended query suite for comprehensive coverage
queries: security-extended,security-and-quality queries: security-extended,security-and-quality
# Skip autobuild - no code compilation needed for config scanning - name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4 uses: github/codeql-action/analyze@v3
with: with:
category: "/language:config" category: "/language:${{ matrix.language }}"
upload: true upload: true
output: sarif-results output: sarif-results
wait-for-processing: true wait-for-processing: true
- name: Upload SARIF results (optional) - name: Upload SARIF results
if: always() if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v4.5.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.5.0
with: with:
name: codeql-results-config name: codeql-results-${{ matrix.language }}
path: sarif-results path: sarif-results
retention-days: 30 retention-days: 30
- name: Check for Critical/High Findings - name: Step summary
if: always() if: always()
run: | run: |
echo "### 🔍 CodeQL Security Analysis Complete" >> $GITHUB_STEP_SUMMARY echo "### 🔍 CodeQL — ${{ matrix.language }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Scan Type**: Configuration Security" >> $GITHUB_STEP_SUMMARY
echo "**Query Suite**: security-extended, security-and-quality" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Note**: MokoStandards is PHP-only (v04.00.04)." >> $GITHUB_STEP_SUMMARY
echo "This scan analyzes workflow files, JSON configs, YAML, and shell scripts." >> $GITHUB_STEP_SUMMARY
echo "For PHP-specific security: Use PHP SecurityValidator enterprise library." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
URL="https://github.com/${{ github.repository }}/security/code-scanning" URL="https://github.com/${{ github.repository }}/security/code-scanning"
echo "Check the [Security tab]($URL) for detailed findings." >> $GITHUB_STEP_SUMMARY echo "See the [Security tab]($URL) for findings." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "**Response Requirements**:" >> $GITHUB_STEP_SUMMARY echo "| Severity | SLA |" >> $GITHUB_STEP_SUMMARY
echo "- Critical: Fix within 7 days" >> $GITHUB_STEP_SUMMARY echo "|----------|-----|" >> $GITHUB_STEP_SUMMARY
echo "- High: Fix within 14 days" >> $GITHUB_STEP_SUMMARY echo "| Critical | 7 days |" >> $GITHUB_STEP_SUMMARY
echo "- Medium: Fix within 30 days" >> $GITHUB_STEP_SUMMARY echo "| High | 14 days |" >> $GITHUB_STEP_SUMMARY
echo "- Low: Fix within 60 days or next release" >> $GITHUB_STEP_SUMMARY echo "| Medium | 30 days |" >> $GITHUB_STEP_SUMMARY
echo "| Low | 60 days / next release |" >> $GITHUB_STEP_SUMMARY
summary: summary:
name: Security Scan Summary name: Security Scan Summary
@@ -107,17 +104,12 @@ jobs:
if: always() if: always()
steps: steps:
- name: Generate Summary - name: Summary
run: | run: |
echo "### 🛡️ Security Scanning Complete" >> $GITHUB_STEP_SUMMARY echo "### 🛡️ CodeQL Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "All CodeQL security scans have completed." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Trigger**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
echo "**Branch**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "**Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
SECURITY_URL="https://github.com/${{ github.repository }}/security" SECURITY_URL="https://github.com/${{ github.repository }}/security"
echo "" >> $GITHUB_STEP_SUMMARY
echo "📊 [View all security alerts]($SECURITY_URL)" >> $GITHUB_STEP_SUMMARY echo "📊 [View all security alerts]($SECURITY_URL)" >> $GITHUB_STEP_SUMMARY
POLICY_URL="https://github.com/${{ github.repository }}"
POLICY_URL="${POLICY_URL}/blob/main/docs/policy/security-scanning.md"
echo "📋 [Security scanning policy]($POLICY_URL)" >> $GITHUB_STEP_SUMMARY

View File

@@ -22,7 +22,7 @@
# INGROUP: MokoStandards.Deploy # INGROUP: MokoStandards.Deploy
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/deploy-demo.yml # PATH: /templates/workflows/shared/deploy-demo.yml
# VERSION: 04.01.00 # VERSION: 04.04.00
# BRIEF: SFTP deployment workflow for demo server — synced to all governed repos # BRIEF: SFTP deployment workflow for demo server — synced to all governed repos
# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-demo.yml in all governed repos. # NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-demo.yml in all governed repos.
# Port is resolved in order: DEMO_FTP_PORT variable → :port suffix in DEMO_FTP_HOST → 22. # Port is resolved in order: DEMO_FTP_PORT variable → :port suffix in DEMO_FTP_HOST → 22.
@@ -70,6 +70,9 @@ permissions:
contents: read contents: read
pull-requests: write pull-requests: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs: jobs:
check-permission: check-permission:
name: Verify Deployment Permission name: Verify Deployment Permission
@@ -241,7 +244,6 @@ jobs:
fi fi
done done
$SKIP && continue $SKIP && continue
if [ -f ".gitignore" ]; then
if [ -f ".gitignore" ]; then if [ -f ".gitignore" ]; then
git check-ignore -q "$rel" 2>/dev/null && { git check-ignore -q "$rel" 2>/dev/null && {
IGNORED_FILES+=("$rel | .gitignore") IGNORED_FILES+=("$rel | .gitignore")
@@ -345,8 +347,8 @@ jobs:
# ── Platform-specific path safety guards ────────────────────────────── # ── Platform-specific path safety guards ──────────────────────────────
PLATFORM="" PLATFORM=""
if [ -f ".moko-standards" ]; then MOKO_FILE=".github/.mokostandards"; [ ! -f "$MOKO_FILE" ] && MOKO_FILE=".mokostandards"; if [ -f "$MOKO_FILE" ]; then
PLATFORM=$(grep -E '^platform:' .moko-standards | sed 's/.*:[[:space:]]*//' | tr -d '"') PLATFORM=$(grep -E '^platform:' "$MOKO_FILE" | sed 's/.*:[[:space:]]*//' | tr -d '"')
fi fi
if [ "$PLATFORM" = "crm-module" ]; then if [ "$PLATFORM" = "crm-module" ]; then
@@ -407,7 +409,7 @@ jobs:
- name: Setup PHP - name: Setup PHP
if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true'
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2.31.0 uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0
with: with:
php-version: '8.1' php-version: '8.1'
tools: composer tools: composer
@@ -418,14 +420,17 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: | run: |
git clone --depth 1 --quiet \ git clone --depth 1 --branch version/04.04.00 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \ "https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards /tmp/mokostandards
cd /tmp/mokostandards cd /tmp/mokostandards
composer install --no-dev --no-interaction --quiet composer install --no-dev --no-interaction --quiet
- name: Clear remote destination folder - name: Clear remote destination folder (manual only)
if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' if: >-
steps.source.outputs.skip == 'false' &&
steps.remote.outputs.skip != 'true' &&
inputs.clear_remote == true
env: env:
SFTP_HOST: ${{ steps.conn.outputs.host }} SFTP_HOST: ${{ steps.conn.outputs.host }}
SFTP_PORT: ${{ steps.conn.outputs.port }} SFTP_PORT: ${{ steps.conn.outputs.port }}
@@ -566,6 +571,59 @@ jobs:
> /tmp/sftp-config.json > /tmp/sftp-config.json
fi fi
# ── Write update files (demo = stable) ─────────────────────────────
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null || true)
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "unknown")
REPO="${{ github.repository }}"
if [ "$PLATFORM" = "crm-module" ]; then
printf '%s' "$VERSION" > update.txt
fi
if [ "$PLATFORM" = "waas-component" ]; then
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
if [ -n "$MANIFEST" ]; then
EXT_NAME=$(grep -oP '<name>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}")
EXT_TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component")
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml)
EXT_CLIENT=$(grep -oP '<extension[^>]+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
EXT_FOLDER=$(grep -oP '<extension[^>]+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
TARGET_PLATFORM=$(grep -oP '<targetplatform[^/]*/>' "$MANIFEST" 2>/dev/null | head -1 || echo '<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />')
CLIENT_TAG=""
if [ -n "$EXT_CLIENT" ]; then CLIENT_TAG="<client>${EXT_CLIENT}</client>"; elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then CLIENT_TAG="<client>site</client>"; fi
FOLDER_TAG=""
if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"; fi
cat > update.xml << 'UPDATEXML'
<?xml version="1.0" encoding="utf-8"?>
<updates>
<update>
<name>EXT_NAME_PH</name>
<description>EXT_NAME_PH update</description>
<element>EXT_ELEMENT_PH</element>
<type>EXT_TYPE_PH</type>
<version>VERSION_PH</version>
CLIENT_TAG_PH
FOLDER_TAG_PH
<tags>
<tag>stable</tag>
</tags>
<infourl title="EXT_NAME_PH">https://github.com/REPO_PH</infourl>
<downloads>
<downloadurl type="full" format="zip">https://github.com/REPO_PH/releases/download/vVERSION_PH/EXT_ELEMENT_PH-VERSION_PH.zip</downloadurl>
</downloads>
TARGET_PLATFORM_PH
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
</updates>
UPDATEXML
sed -i "s|EXT_NAME_PH|${EXT_NAME}|g;s|EXT_ELEMENT_PH|${EXT_ELEMENT}|g;s|EXT_TYPE_PH|${EXT_TYPE}|g;s|VERSION_PH|${VERSION}|g;s|CLIENT_TAG_PH|${CLIENT_TAG}|g;s|FOLDER_TAG_PH|${FOLDER_TAG}|g;s|REPO_PH|${REPO}|g;s|TARGET_PLATFORM_PH|${TARGET_PLATFORM}|g" update.xml
sed -i '/^[[:space:]]*$/d' update.xml
fi
fi
# ── Run deploy-sftp.php from MokoStandards ──────────────────────────── # ── Run deploy-sftp.php from MokoStandards ────────────────────────────
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json) DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
if [ "$USE_PASSPHRASE" = "true" ]; then if [ "$USE_PASSPHRASE" = "true" ]; then
@@ -578,7 +636,7 @@ jobs:
rm -f /tmp/deploy_key /tmp/sftp-config.json rm -f /tmp/deploy_key /tmp/sftp-config.json
- name: Create or update failure issue - name: Create or update failure issue
if: failure() if: failure() && steps.remote.outputs.skip != 'true'
env: env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: | run: |
@@ -620,22 +678,22 @@ jobs:
--force 2>/dev/null || true --force 2>/dev/null || true
# Look for an existing open deploy-failure issue # Look for an existing open deploy-failure issue
EXISTING=$(gh api "repos/${REPO}/issues?labels=${LABEL}&state=open&per_page=1" \ EXISTING=$(gh api "repos/${REPO}/issues?labels=${LABEL}&state=all&per_page=1&sort=created&direction=desc" \
--jq '.[0].number' 2>/dev/null) --jq '.[0].number' 2>/dev/null)
if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then
gh api "repos/${REPO}/issues/${EXISTING}" \ gh api "repos/${REPO}/issues/${EXISTING}" \
-X PATCH \ -X PATCH \
-f title="$TITLE" \ -f title="$TITLE" \
-f body="$BODY" \ -f body="$BODY" \n -f state="open" \
--silent --silent
echo "📋 Failure issue #${EXISTING} updated: ${REPO}" >> "$GITHUB_STEP_SUMMARY" echo "📋 Failure issue #${EXISTING} updated/reopened: ${REPO}" >> "$GITHUB_STEP_SUMMARY"
else else
gh issue create \ gh issue create \
--repo "$REPO" \ --repo "$REPO" \
--title "$TITLE" \ --title "$TITLE" \
--body "$BODY" \ --body "$BODY" \
--label "$LABEL" \ --label "$LABEL" \n --assignee "jmiller-moko" \
| tee -a "$GITHUB_STEP_SUMMARY" | tee -a "$GITHUB_STEP_SUMMARY"
fi fi

View File

@@ -22,7 +22,7 @@
# INGROUP: MokoStandards.Deploy # INGROUP: MokoStandards.Deploy
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/deploy-dev.yml # PATH: /templates/workflows/shared/deploy-dev.yml
# VERSION: 04.01.00 # VERSION: 04.04.00
# BRIEF: SFTP deployment workflow for development server — synced to all governed repos # BRIEF: SFTP deployment workflow for development server — synced to all governed repos
# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-dev.yml in all governed repos. # NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-dev.yml in all governed repos.
# Port is resolved in order: DEV_FTP_PORT variable → :port suffix in DEV_FTP_HOST → 22. # Port is resolved in order: DEV_FTP_PORT variable → :port suffix in DEV_FTP_HOST → 22.
@@ -49,6 +49,7 @@ on:
push: push:
branches: branches:
- 'dev/**' - 'dev/**'
- 'rc/**'
- develop - develop
- development - development
paths: paths:
@@ -57,6 +58,7 @@ on:
types: [opened, synchronize, reopened, closed] types: [opened, synchronize, reopened, closed]
branches: branches:
- 'dev/**' - 'dev/**'
- 'rc/**'
- develop - develop
- development - development
paths: paths:
@@ -73,6 +75,9 @@ permissions:
contents: read contents: read
pull-requests: write pull-requests: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs: jobs:
check-permission: check-permission:
name: Verify Deployment Permission name: Verify Deployment Permission
@@ -347,8 +352,8 @@ jobs:
# ── Platform-specific path safety guards ────────────────────────────── # ── Platform-specific path safety guards ──────────────────────────────
PLATFORM="" PLATFORM=""
if [ -f ".moko-standards" ]; then MOKO_FILE=".github/.mokostandards"; [ ! -f "$MOKO_FILE" ] && MOKO_FILE=".mokostandards"; if [ -f "$MOKO_FILE" ]; then
PLATFORM=$(grep -E '^platform:' .moko-standards | sed 's/.*:[[:space:]]*//' | tr -d '"') PLATFORM=$(grep -oP '^platform:.*' "$MOKO_FILE" 2>/dev/null || true)
fi fi
if [ "$PLATFORM" = "crm-module" ]; then if [ "$PLATFORM" = "crm-module" ]; then
@@ -409,7 +414,7 @@ jobs:
- name: Setup PHP - name: Setup PHP
if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true'
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2.31.0 uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0
with: with:
php-version: '8.1' php-version: '8.1'
tools: composer tools: composer
@@ -420,14 +425,17 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: | run: |
git clone --depth 1 --quiet \ git clone --depth 1 --branch version/04.04.00 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \ "https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards /tmp/mokostandards
cd /tmp/mokostandards cd /tmp/mokostandards
composer install --no-dev --no-interaction --quiet composer install --no-dev --no-interaction --quiet
- name: Clear remote destination folder - name: Clear remote destination folder (manual only)
if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' if: >-
steps.source.outputs.skip == 'false' &&
steps.remote.outputs.skip != 'true' &&
inputs.clear_remote == true
env: env:
SFTP_HOST: ${{ steps.conn.outputs.host }} SFTP_HOST: ${{ steps.conn.outputs.host }}
SFTP_PORT: ${{ steps.conn.outputs.port }} SFTP_PORT: ${{ steps.conn.outputs.port }}
@@ -574,23 +582,87 @@ jobs:
DEPLOY_ARGS+=(--key-passphrase "$SFTP_PASSWORD") DEPLOY_ARGS+=(--key-passphrase "$SFTP_PASSWORD")
fi fi
# ── For Dolibarr (crm-module): set version to "development" before deploy # Set platform version to "development" before deploy (Dolibarr + Joomla)
PLATFORM="" php /tmp/mokostandards/api/cli/version_set_platform.php --path . --version development
if [ -f ".moko-standards" ]; then
PLATFORM=$(grep -E '^platform:' .moko-standards | sed 's/.*:[[:space:]]*//' | tr -d '"') # Write update files — dev/** = development, rc/** = rc
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null || true)
REPO="${{ github.repository }}"
BRANCH="${{ github.ref_name }}"
# Determine stability tag from branch prefix
STABILITY="development"
VERSION_LABEL="development"
if [[ "$BRANCH" == rc/* ]]; then
STABILITY="rc"
VERSION_LABEL=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "${BRANCH#rc/}")-rc
fi fi
if [ "$PLATFORM" = "crm-module" ]; then if [ "$PLATFORM" = "crm-module" ]; then
echo "📦 Dolibarr dev deploy — setting module version to 'development'" printf '%s' "$VERSION_LABEL" > update.txt
find "$SOURCE_DIR" -path "*/core/modules/mod*.class.php" -exec \
sed -i "s/\(\$this->version\s*=\s*\)['\"][^'\"]*['\"]/\1'development'/" {} + 2>/dev/null || true
fi fi
if [ "$PLATFORM" = "waas-component" ]; then if [ "$PLATFORM" = "waas-component" ]; then
echo "📦 Joomla dev deploy — setting manifest version to 'development'" MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
find "$SOURCE_DIR" -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | while read -r manifest; do if [ -n "$MANIFEST" ]; then
sed -i "s|<version>[^<]*</version>|<version>development</version>|" "$manifest" 2>/dev/null || true EXT_NAME=$(grep -oP '<name>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}")
done EXT_TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component")
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml)
EXT_CLIENT=$(grep -oP '<extension[^>]+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
EXT_FOLDER=$(grep -oP '<extension[^>]+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
TARGET_PLATFORM=$(grep -oP '<targetplatform[^/]*/>' "$MANIFEST" 2>/dev/null | head -1 || echo '<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />')
CLIENT_TAG=""
if [ -n "$EXT_CLIENT" ]; then
CLIENT_TAG="<client>${EXT_CLIENT}</client>"
elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then
CLIENT_TAG="<client>site</client>"
fi
FOLDER_TAG=""
if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then
FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
fi
DOWNLOAD_URL="https://github.com/${REPO}/archive/refs/heads/${BRANCH}.zip"
cat > update.xml << 'DEVXML'
<?xml version="1.0" encoding="utf-8"?>
<updates>
<update>
<name>EXT_NAME_PH</name>
<description>EXT_NAME_PH STABILITY_PH build</description>
<element>EXT_ELEMENT_PH</element>
<type>EXT_TYPE_PH</type>
<version>VERSION_LABEL_PH</version>
CLIENT_TAG_PH
FOLDER_TAG_PH
<tags>
<tag>STABILITY_PH</tag>
</tags>
<infourl title="EXT_NAME_PH">https://github.com/REPO_PH/tree/BRANCH_PH</infourl>
<downloads>
<downloadurl type="full" format="zip">DOWNLOAD_URL_PH</downloadurl>
</downloads>
TARGET_PLATFORM_PH
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
</updates>
DEVXML
sed -i "s|EXT_NAME_PH|${EXT_NAME}|g" update.xml
sed -i "s|EXT_ELEMENT_PH|${EXT_ELEMENT}|g" update.xml
sed -i "s|EXT_TYPE_PH|${EXT_TYPE}|g" update.xml
sed -i "s|CLIENT_TAG_PH|${CLIENT_TAG}|g" update.xml
sed -i "s|FOLDER_TAG_PH|${FOLDER_TAG}|g" update.xml
sed -i "s|REPO_PH|${REPO}|g" update.xml
sed -i "s|BRANCH_PH|${BRANCH}|g" update.xml
sed -i "s|DOWNLOAD_URL_PH|${DOWNLOAD_URL}|g" update.xml
sed -i "s|TARGET_PLATFORM_PH|${TARGET_PLATFORM}|g" update.xml
sed -i "s|VERSION_LABEL_PH|${VERSION_LABEL}|g" update.xml
sed -i "s|STABILITY_PH|${STABILITY}|g" update.xml
sed -i '/^[[:space:]]*$/d' update.xml
fi
fi fi
php /tmp/mokostandards/api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}" php /tmp/mokostandards/api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
@@ -641,22 +713,22 @@ jobs:
--force 2>/dev/null || true --force 2>/dev/null || true
# Look for an existing open deploy-failure issue # Look for an existing open deploy-failure issue
EXISTING=$(gh api "repos/${REPO}/issues?labels=${LABEL}&state=open&per_page=1" \ EXISTING=$(gh api "repos/${REPO}/issues?labels=${LABEL}&state=all&per_page=1&sort=created&direction=desc" \
--jq '.[0].number' 2>/dev/null) --jq '.[0].number' 2>/dev/null)
if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then
gh api "repos/${REPO}/issues/${EXISTING}" \ gh api "repos/${REPO}/issues/${EXISTING}" \
-X PATCH \ -X PATCH \
-f title="$TITLE" \ -f title="$TITLE" \
-f body="$BODY" \ -f body="$BODY" \n -f state="open" \
--silent --silent
echo "📋 Failure issue #${EXISTING} updated: ${REPO}" >> "$GITHUB_STEP_SUMMARY" echo "📋 Failure issue #${EXISTING} updated/reopened: ${REPO}" >> "$GITHUB_STEP_SUMMARY"
else else
gh issue create \ gh issue create \
--repo "$REPO" \ --repo "$REPO" \
--title "$TITLE" \ --title "$TITLE" \
--body "$BODY" \ --body "$BODY" \
--label "$LABEL" \ --label "$LABEL" \n --assignee "jmiller-moko" \
| tee -a "$GITHUB_STEP_SUMMARY" | tee -a "$GITHUB_STEP_SUMMARY"
fi fi

View File

@@ -22,7 +22,7 @@
# INGROUP: MokoStandards.Deploy # INGROUP: MokoStandards.Deploy
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/deploy-rs.yml # PATH: /templates/workflows/shared/deploy-rs.yml
# VERSION: 04.01.00 # VERSION: 04.04.00
# BRIEF: SFTP deployment workflow for release staging server — synced to all governed repos # BRIEF: SFTP deployment workflow for release staging server — synced to all governed repos
# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-rs.yml in all governed repos. # NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-rs.yml in all governed repos.
# Port is resolved in order: RS_FTP_PORT variable → :port suffix in RS_FTP_HOST → 22. # Port is resolved in order: RS_FTP_PORT variable → :port suffix in RS_FTP_HOST → 22.
@@ -70,6 +70,9 @@ permissions:
contents: read contents: read
pull-requests: write pull-requests: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs: jobs:
check-permission: check-permission:
name: Verify Deployment Permission name: Verify Deployment Permission
@@ -344,8 +347,8 @@ jobs:
# ── Platform-specific path safety guards ────────────────────────────── # ── Platform-specific path safety guards ──────────────────────────────
PLATFORM="" PLATFORM=""
if [ -f ".moko-standards" ]; then MOKO_FILE=".github/.mokostandards"; [ ! -f "$MOKO_FILE" ] && MOKO_FILE=".mokostandards"; if [ -f "$MOKO_FILE" ]; then
PLATFORM=$(grep -E '^platform:' .moko-standards | sed 's/.*:[[:space:]]*//' | tr -d '"') PLATFORM=$(grep -E '^platform:' "$MOKO_FILE" | sed 's/.*:[[:space:]]*//' | tr -d '"')
fi fi
# RS deployment: no path restrictions for any platform # RS deployment: no path restrictions for any platform
@@ -387,7 +390,7 @@ jobs:
- name: Setup PHP - name: Setup PHP
if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true'
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2.31.0 uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0
with: with:
php-version: '8.1' php-version: '8.1'
tools: composer tools: composer
@@ -398,14 +401,17 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: | run: |
git clone --depth 1 --quiet \ git clone --depth 1 --branch version/04.04.00 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \ "https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards /tmp/mokostandards
cd /tmp/mokostandards cd /tmp/mokostandards
composer install --no-dev --no-interaction --quiet composer install --no-dev --no-interaction --quiet
- name: Clear remote destination folder - name: Clear remote destination folder (manual only)
if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' if: >-
steps.source.outputs.skip == 'false' &&
steps.remote.outputs.skip != 'true' &&
inputs.clear_remote == true
env: env:
SFTP_HOST: ${{ steps.conn.outputs.host }} SFTP_HOST: ${{ steps.conn.outputs.host }}
SFTP_PORT: ${{ steps.conn.outputs.port }} SFTP_PORT: ${{ steps.conn.outputs.port }}
@@ -558,7 +564,7 @@ jobs:
rm -f /tmp/deploy_key /tmp/sftp-config.json rm -f /tmp/deploy_key /tmp/sftp-config.json
- name: Create or update failure issue - name: Create or update failure issue
if: failure() if: failure() && steps.remote.outputs.skip != 'true'
env: env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: | run: |
@@ -599,8 +605,8 @@ jobs:
--description "Automated deploy failure tracking" \ --description "Automated deploy failure tracking" \
--force 2>/dev/null || true --force 2>/dev/null || true
# Look for an existing open deploy-failure issue # Look for an existing deploy-failure issue (any state — reopen if closed)
EXISTING=$(gh api "repos/${REPO}/issues?labels=${LABEL}&state=open&per_page=1" \ EXISTING=$(gh api "repos/${REPO}/issues?labels=${LABEL}&state=all&per_page=1&sort=created&direction=desc" \
--jq '.[0].number' 2>/dev/null) --jq '.[0].number' 2>/dev/null)
if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then
@@ -608,14 +614,16 @@ jobs:
-X PATCH \ -X PATCH \
-f title="$TITLE" \ -f title="$TITLE" \
-f body="$BODY" \ -f body="$BODY" \
-f state="open" \
--silent --silent
echo "📋 Failure issue #${EXISTING} updated: ${REPO}" >> "$GITHUB_STEP_SUMMARY" echo "📋 Failure issue #${EXISTING} updated/reopened: ${REPO}" >> "$GITHUB_STEP_SUMMARY"
else else
gh issue create \ gh issue create \
--repo "$REPO" \ --repo "$REPO" \
--title "$TITLE" \ --title "$TITLE" \
--body "$BODY" \ --body "$BODY" \
--label "$LABEL" \ --label "$LABEL" \
--assignee "jmiller-moko" \
| tee -a "$GITHUB_STEP_SUMMARY" | tee -a "$GITHUB_STEP_SUMMARY"
fi fi

View File

@@ -22,7 +22,7 @@
# INGROUP: MokoStandards.Firewall # INGROUP: MokoStandards.Firewall
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml # PATH: /templates/workflows/shared/enterprise-firewall-setup.yml
# VERSION: 04.01.00 # VERSION: 04.04.00
# BRIEF: Enterprise firewall configuration — generates outbound allow-rules including SFTP deployment server # BRIEF: Enterprise firewall configuration — generates outbound allow-rules including SFTP deployment server
# NOTE: Reads DEV_FTP_HOST / DEV_FTP_PORT variables to include SFTP egress rules alongside HTTPS rules. # NOTE: Reads DEV_FTP_HOST / DEV_FTP_PORT variables to include SFTP egress rules alongside HTTPS rules.

View File

@@ -10,7 +10,7 @@
# INGROUP: MokoStandards.Validation # INGROUP: MokoStandards.Validation
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/repo_health.yml # PATH: /.github/workflows/repo_health.yml
# VERSION: 04.01.00 # VERSION: 04.04.01
# BRIEF: Enforces repository guardrails by validating release configuration, scripts governance, tooling availability, and core repository health artifacts. # BRIEF: Enforces repository guardrails by validating release configuration, scripts governance, tooling availability, and core repository health artifacts.
# NOTE: Field is user-managed. # NOTE: Field is user-managed.
# ============================================================================ # ============================================================================
@@ -29,7 +29,7 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
profile: profile:
description: Which configuration profile to validate. release checks SFTP variables used by release pipeline. scripts checks baseline script prerequisites. repo runs repository health only. al[...] description: 'Validation profile: all, release, scripts, or repo'
required: true required: true
default: all default: all
type: choice type: choice
@@ -39,19 +39,7 @@ on:
- scripts - scripts
- repo - repo
pull_request: pull_request:
paths:
- .github/workflows/**
- scripts/**
- docs/**
- dev/**
push: push:
branches:
- main
paths:
- .github/workflows/**
- scripts/**
- docs/**
- dev/**
permissions: permissions:
contents: read contents: read
@@ -68,7 +56,7 @@ env:
# Repo health policy # Repo health policy
# Files are listed as-is; directories must end with a trailing slash. # Files are listed as-is; directories must end with a trailing slash.
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.github/workflows/,src/ REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.github/workflows/
REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/ REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
REPO_DISALLOWED_DIRS: REPO_DISALLOWED_DIRS:
REPO_DISALLOWED_FILES: TODO.md,todo.md REPO_DISALLOWED_FILES: TODO.md,todo.md
@@ -82,6 +70,7 @@ env:
WORKFLOWS_DIR: .github/workflows WORKFLOWS_DIR: .github/workflows
SHELLCHECK_PATTERN: '*.sh' SHELLCHECK_PATTERN: '*.sh'
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml' SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs: jobs:
access_check: access_check:
@@ -412,6 +401,15 @@ jobs:
exit 0 exit 0
fi fi
# Source directory: src/ or htdocs/ (either is valid)
if [ -d "src" ]; then
SOURCE_DIR="src"
elif [ -d "htdocs" ]; then
SOURCE_DIR="htdocs"
else
missing_required+=("src/ or htdocs/ (source directory required)")
fi
IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}" IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}"
IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}" IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}"
IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}" IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"
@@ -561,6 +559,73 @@ jobs:
} >> "${GITHUB_STEP_SUMMARY}" } >> "${GITHUB_STEP_SUMMARY}"
fi fi
# ── Joomla-specific checks ───────────────────────────────────────
joomla_findings=()
# XML manifest: find any XML file containing <extension
MANIFEST="$(find . -maxdepth 2 -name '*.xml' -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)"
if [ -z "${MANIFEST}" ]; then
joomla_findings+=("Joomla XML manifest not found (no *.xml with <extension> tag)")
else
# Check <version> tag exists
if ! grep -qP '<version>' "${MANIFEST}"; then
joomla_findings+=("XML manifest: <version> tag missing")
fi
# Check extension type attribute
if ! grep -qP 'type="(component|module|plugin|library|package|template|language)"' "${MANIFEST}"; then
joomla_findings+=("XML manifest: type attribute missing or invalid")
fi
# Check <name> tag
if ! grep -qP '<name>' "${MANIFEST}"; then
joomla_findings+=("XML manifest: <name> tag missing")
fi
# Check <author> tag
if ! grep -qP '<author>' "${MANIFEST}"; then
joomla_findings+=("XML manifest: <author> tag missing")
fi
# Check <namespace> for Joomla 4+
if ! grep -qP '<namespace' "${MANIFEST}"; then
joomla_findings+=("XML manifest: <namespace> missing (required for Joomla 4+)")
fi
fi
# Language files: check for at least one .ini file
INI_COUNT="$(find . -name '*.ini' -type f 2>/dev/null | wc -l)"
if [ "${INI_COUNT}" -eq 0 ]; then
joomla_findings+=("No .ini language files found")
fi
# update.xml must exist in root (Joomla update server)
if [ ! -f 'update.xml' ]; then
joomla_findings+=("update.xml missing in root (required for Joomla update server)")
fi
# index.html files for directory listing protection
INDEX_DIRS=("${SOURCE_DIR}" "${SOURCE_DIR}/admin" "${SOURCE_DIR}/site")
for dir in "${INDEX_DIRS[@]}"; do
if [ -d "${dir}" ] && [ ! -f "${dir}/index.html" ]; then
joomla_findings+=("${dir}/index.html missing (directory listing protection)")
fi
done
if [ "${#joomla_findings[@]}" -gt 0 ]; then
{
printf '%s\n' '### Joomla extension checks'
printf '%s\n' '| Check | Status |'
printf '%s\n' '|---|---|'
for f in "${joomla_findings[@]}"; do
printf '%s\n' "| ${f} | Warning |"
done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
else
{
printf '%s\n' '### Joomla extension checks'
printf '%s\n' 'All Joomla-specific checks passed.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
extended_enabled="${EXTENDED_CHECKS:-true}" extended_enabled="${EXTENDED_CHECKS:-true}"
extended_findings=() extended_findings=()

View File

@@ -9,36 +9,58 @@
# INGROUP: MokoStandards.Maintenance # INGROUP: MokoStandards.Maintenance
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/repository-cleanup.yml # PATH: /templates/workflows/shared/repository-cleanup.yml
# VERSION: 04.01.00 # VERSION: 04.04.00
# BRIEF: One-time repository cleanup — reset labels, strip issue template headers, delete old branches # BRIEF: Recurring repository maintenance — labels, branches, workflows, logs, doc indexes
# NOTE: Synced via bulk-repo-sync to .github/workflows/repository-cleanup.yml in all governed repos. # NOTE: Synced via bulk-repo-sync to .github/workflows/repository-cleanup.yml in all governed repos.
# Run manually via workflow_dispatch. Safe to re-run — all operations are idempotent. # Runs on the 1st and 15th of each month at 6:00 AM UTC, and on manual dispatch.
name: Repository Cleanup name: Repository Cleanup
on: on:
schedule:
- cron: '0 6 1,15 * *'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
reset_labels: reset_labels:
description: 'Delete ALL existing labels and recreate the standard 54-label set' description: 'Delete ALL existing labels and recreate the standard set'
type: boolean
default: false
clean_branches:
description: 'Delete old chore/sync-mokostandards-* branches'
type: boolean type: boolean
default: true default: true
clean_branches: clean_workflows:
description: 'Delete old chore/sync-mokostandards-* branches (keeps current versioned branch only)' description: 'Delete orphaned workflow runs (cancelled, stale)'
type: boolean
default: true
clean_logs:
description: 'Delete workflow run logs older than 30 days'
type: boolean type: boolean
default: true default: true
fix_templates: fix_templates:
description: 'Strip copyright comment blocks from issue templates' description: 'Strip copyright comment blocks from issue templates'
type: boolean type: boolean
default: true default: true
rebuild_indexes:
description: 'Rebuild docs/ index files'
type: boolean
default: true
delete_closed_issues:
description: 'Delete issues that have been closed for more than 30 days'
type: boolean
default: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions: permissions:
contents: write contents: write
issues: write issues: write
actions: write
jobs: jobs:
cleanup: cleanup:
name: Repository Cleanup name: Repository Maintenance
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -46,12 +68,18 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
token: ${{ secrets.GH_TOKEN || github.token }} token: ${{ secrets.GH_TOKEN || github.token }}
fetch-depth: 0
- name: Check actor permission - name: Check actor permission
env: env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: | run: |
ACTOR="${{ github.actor }}" ACTOR="${{ github.actor }}"
# Schedule triggers use github-actions[bot]
if [ "${{ github.event_name }}" = "schedule" ]; then
echo "✅ Scheduled run — authorized"
exit 0
fi
AUTHORIZED_USERS="jmiller-moko github-actions[bot]" AUTHORIZED_USERS="jmiller-moko github-actions[bot]"
for user in $AUTHORIZED_USERS; do for user in $AUTHORIZED_USERS; do
if [ "$ACTOR" = "$user" ]; then if [ "$ACTOR" = "$user" ]; then
@@ -66,9 +94,90 @@ jobs:
*) echo "❌ Admin or maintain required"; exit 1 ;; *) echo "❌ Admin or maintain required"; exit 1 ;;
esac esac
# ── Determine which tasks to run ─────────────────────────────────────
# On schedule: run all tasks with safe defaults (labels NOT reset)
# On dispatch: use input toggles
- name: Set task flags
id: tasks
run: |
if [ "${{ github.event_name }}" = "schedule" ]; then
echo "reset_labels=false" >> $GITHUB_OUTPUT
echo "clean_branches=true" >> $GITHUB_OUTPUT
echo "clean_workflows=true" >> $GITHUB_OUTPUT
echo "clean_logs=true" >> $GITHUB_OUTPUT
echo "fix_templates=true" >> $GITHUB_OUTPUT
echo "rebuild_indexes=true" >> $GITHUB_OUTPUT
echo "delete_closed_issues=false" >> $GITHUB_OUTPUT
else
echo "reset_labels=${{ inputs.reset_labels }}" >> $GITHUB_OUTPUT
echo "clean_branches=${{ inputs.clean_branches }}" >> $GITHUB_OUTPUT
echo "clean_workflows=${{ inputs.clean_workflows }}" >> $GITHUB_OUTPUT
echo "clean_logs=${{ inputs.clean_logs }}" >> $GITHUB_OUTPUT
echo "fix_templates=${{ inputs.fix_templates }}" >> $GITHUB_OUTPUT
echo "rebuild_indexes=${{ inputs.rebuild_indexes }}" >> $GITHUB_OUTPUT
echo "delete_closed_issues=${{ inputs.delete_closed_issues }}" >> $GITHUB_OUTPUT
fi
# ── DELETE RETIRED WORKFLOWS (always runs) ────────────────────────────
- name: Delete retired workflow files
run: |
echo "## 🗑️ Retired Workflow Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
RETIRED=(
".github/workflows/build.yml"
".github/workflows/code-quality.yml"
".github/workflows/release-cycle.yml"
".github/workflows/release-pipeline.yml"
".github/workflows/branch-cleanup.yml"
".github/workflows/auto-update-changelog.yml"
".github/workflows/enterprise-issue-manager.yml"
".github/workflows/flush-actions-cache.yml"
".github/workflows/mokostandards-script-runner.yml"
".github/workflows/unified-ci.yml"
".github/workflows/unified-platform-testing.yml"
".github/workflows/reusable-build.yml"
".github/workflows/reusable-ci-validation.yml"
".github/workflows/reusable-deploy.yml"
".github/workflows/reusable-php-quality.yml"
".github/workflows/reusable-platform-testing.yml"
".github/workflows/reusable-project-detector.yml"
".github/workflows/reusable-release.yml"
".github/workflows/reusable-script-executor.yml"
".github/workflows/rebuild-docs-indexes.yml"
".github/workflows/setup-project-v2.yml"
".github/workflows/sync-docs-to-project.yml"
".github/workflows/release.yml"
".github/workflows/sync-changelogs.yml"
".github/workflows/version_branch.yml"
"update.json"
".github/workflows/auto-version-branch.yml"
)
DELETED=0
for wf in "${RETIRED[@]}"; do
if [ -f "$wf" ]; then
git rm "$wf" 2>/dev/null || rm -f "$wf"
echo " Deleted: \`$(basename $wf)\`" >> $GITHUB_STEP_SUMMARY
DELETED=$((DELETED+1))
fi
done
if [ "$DELETED" -gt 0 ]; then
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add -A
git commit -m "chore: delete ${DELETED} retired workflow file(s) [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push
echo "✅ ${DELETED} retired workflow(s) deleted" >> $GITHUB_STEP_SUMMARY
else
echo "✅ No retired workflows found" >> $GITHUB_STEP_SUMMARY
fi
# ── LABEL RESET ────────────────────────────────────────────────────── # ── LABEL RESET ──────────────────────────────────────────────────────
- name: Reset labels to standard set - name: Reset labels to standard set
if: inputs.reset_labels == true if: steps.tasks.outputs.reset_labels == 'true'
env: env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: | run: |
@@ -76,23 +185,16 @@ jobs:
echo "## 🏷️ Label Reset" >> $GITHUB_STEP_SUMMARY echo "## 🏷️ Label Reset" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
# Delete all existing labels
echo "Deleting existing labels..."
DELETED=0
gh api "repos/${REPO}/labels?per_page=100" --paginate --jq '.[].name' | while read -r label; do gh api "repos/${REPO}/labels?per_page=100" --paginate --jq '.[].name' | while read -r label; do
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$label', safe=''))") ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$label', safe=''))")
gh api -X DELETE "repos/${REPO}/labels/${ENCODED}" --silent 2>/dev/null && DELETED=$((DELETED+1)) || true gh api -X DELETE "repos/${REPO}/labels/${ENCODED}" --silent 2>/dev/null || true
done done
echo "Deleted existing labels" >> $GITHUB_STEP_SUMMARY
# Create the standard 54-label set
echo "Creating standard labels..."
CREATED=0
while IFS='|' read -r name color description; do while IFS='|' read -r name color description; do
[ -z "$name" ] && continue [ -z "$name" ] && continue
gh api "repos/${REPO}/labels" \ gh api "repos/${REPO}/labels" \
-f name="$name" -f color="$color" -f description="$description" \ -f name="$name" -f color="$color" -f description="$description" \
--silent 2>/dev/null && CREATED=$((CREATED+1)) || true --silent 2>/dev/null || true
done << 'LABELS' done << 'LABELS'
joomla|7F52FF|Joomla extension or component joomla|7F52FF|Joomla extension or component
dolibarr|FF6B6B|Dolibarr module or extension dolibarr|FF6B6B|Dolibarr module or extension
@@ -125,6 +227,7 @@ jobs:
type: enhancement|84B6EB|Enhancement to existing feature type: enhancement|84B6EB|Enhancement to existing feature
type: refactor|F9D0C4|Code refactoring type: refactor|F9D0C4|Code refactoring
type: chore|FEF2C0|Maintenance tasks type: chore|FEF2C0|Maintenance tasks
type: version|0E8A16|Version-related change
status: pending|FBCA04|Pending action or decision status: pending|FBCA04|Pending action or decision
status: in-progress|0E8A16|Currently being worked on status: in-progress|0E8A16|Currently being worked on
status: blocked|B60205|Blocked by another issue or dependency status: blocked|B60205|Blocked by another issue or dependency
@@ -149,45 +252,85 @@ jobs:
version-drift|FFA500|Version mismatch detected version-drift|FFA500|Version mismatch detected
deploy-failure|CC0000|Automated deploy failure tracking deploy-failure|CC0000|Automated deploy failure tracking
template-validation-failure|D73A4A|Template workflow validation failure template-validation-failure|D73A4A|Template workflow validation failure
version|0E8A16|Version bump or release
LABELS LABELS
echo "✅ Standard labels created" >> $GITHUB_STEP_SUMMARY echo "✅ Standard labels created" >> $GITHUB_STEP_SUMMARY
# ── BRANCH CLEANUP ─────────────────────────────────────────────────── # ── BRANCH CLEANUP ───────────────────────────────────────────────────
- name: Delete old sync branches - name: Delete old sync branches
if: inputs.clean_branches == true if: steps.tasks.outputs.clean_branches == 'true'
env: env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: | run: |
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
CURRENT="chore/sync-mokostandards-v04.01.00" CURRENT="chore/sync-mokostandards-v04.04.00"
echo "## 🌿 Branch Cleanup" >> $GITHUB_STEP_SUMMARY echo "## 🌿 Branch Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
DELETED=0 FOUND=false
gh api "repos/${REPO}/branches?per_page=100" --jq '.[].name' | \ gh api "repos/${REPO}/branches?per_page=100" --jq '.[].name' | \
grep "^chore/sync-mokostandards" | \ grep "^chore/sync-mokostandards" | \
grep -v "^${CURRENT}$" | while read -r branch; do grep -v "^${CURRENT}$" | while read -r branch; do
# Close any open PRs on this branch
gh pr list --repo "$REPO" --head "$branch" --state open --json number --jq '.[].number' 2>/dev/null | while read -r pr; do gh pr list --repo "$REPO" --head "$branch" --state open --json number --jq '.[].number' 2>/dev/null | while read -r pr; do
gh pr close "$pr" --repo "$REPO" --comment "Superseded by \`${CURRENT}\`" 2>/dev/null || true gh pr close "$pr" --repo "$REPO" --comment "Superseded by \`${CURRENT}\`" 2>/dev/null || true
echo " Closed PR #${pr}" >> $GITHUB_STEP_SUMMARY echo " Closed PR #${pr}" >> $GITHUB_STEP_SUMMARY
done done
# Delete the branch
gh api -X DELETE "repos/${REPO}/git/refs/heads/${branch}" --silent 2>/dev/null || true gh api -X DELETE "repos/${REPO}/git/refs/heads/${branch}" --silent 2>/dev/null || true
echo " Deleted: \`${branch}\`" >> $GITHUB_STEP_SUMMARY echo " Deleted: \`${branch}\`" >> $GITHUB_STEP_SUMMARY
FOUND=true
done
if [ "$FOUND" != "true" ]; then
echo "✅ No old sync branches found" >> $GITHUB_STEP_SUMMARY
fi
# ── WORKFLOW RUN CLEANUP ─────────────────────────────────────────────
- name: Clean up workflow runs
if: steps.tasks.outputs.clean_workflows == 'true'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
REPO="${{ github.repository }}"
echo "## 🔄 Workflow Run Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
DELETED=0
# Delete cancelled and stale workflow runs
for status in cancelled stale; do
gh api "repos/${REPO}/actions/runs?status=${status}&per_page=100" \
--jq '.workflow_runs[].id' 2>/dev/null | while read -r run_id; do
gh api -X DELETE "repos/${REPO}/actions/runs/${run_id}" --silent 2>/dev/null || true
DELETED=$((DELETED+1))
done
done
echo "✅ Cleaned cancelled/stale workflow runs" >> $GITHUB_STEP_SUMMARY
# ── LOG CLEANUP ──────────────────────────────────────────────────────
- name: Delete old workflow run logs
if: steps.tasks.outputs.clean_logs == 'true'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
REPO="${{ github.repository }}"
CUTOFF=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)
echo "## 📋 Log Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Deleting logs older than: ${CUTOFF}" >> $GITHUB_STEP_SUMMARY
DELETED=0
gh api "repos/${REPO}/actions/runs?created=<${CUTOFF}&per_page=100" \
--jq '.workflow_runs[].id' 2>/dev/null | while read -r run_id; do
gh api -X DELETE "repos/${REPO}/actions/runs/${run_id}/logs" --silent 2>/dev/null || true
DELETED=$((DELETED+1)) DELETED=$((DELETED+1))
done done
if [ "$DELETED" -eq 0 ] 2>/dev/null; then echo "✅ Cleaned old workflow run logs" >> $GITHUB_STEP_SUMMARY
echo "✅ No old sync branches found" >> $GITHUB_STEP_SUMMARY
else
echo "✅ Cleanup complete" >> $GITHUB_STEP_SUMMARY
fi
# ── ISSUE TEMPLATE FIX ────────────────────────────────────────────── # ── ISSUE TEMPLATE FIX ──────────────────────────────────────────────
- name: Strip copyright headers from issue templates - name: Strip copyright headers from issue templates
if: inputs.fix_templates == true if: steps.tasks.outputs.fix_templates == 'true'
run: | run: |
echo "## 📋 Issue Template Cleanup" >> $GITHUB_STEP_SUMMARY echo "## 📋 Issue Template Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
@@ -214,26 +357,158 @@ jobs:
echo "✅ No templates need cleaning" >> $GITHUB_STEP_SUMMARY echo "✅ No templates need cleaning" >> $GITHUB_STEP_SUMMARY
fi fi
# ── SELF-DELETE ───────────────────────────────────────────────────── # ── REBUILD DOC INDEXES ─────────────────────────────────────────────
- name: Delete this workflow (one-time use) - name: Rebuild docs/ index files
if: success() if: steps.tasks.outputs.rebuild_indexes == 'true'
run: |
echo "## 📚 Documentation Index Rebuild" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ ! -d "docs" ]; then
echo "⏭️ No docs/ directory — skipping" >> $GITHUB_STEP_SUMMARY
exit 0
fi
UPDATED=0
# Generate index.md for each docs/ subdirectory
find docs -type d | while read -r dir; do
INDEX="${dir}/index.md"
FILES=$(find "$dir" -maxdepth 1 -name "*.md" ! -name "index.md" -printf "- [%f](./%f)\n" 2>/dev/null | sort)
if [ -z "$FILES" ]; then
continue
fi
cat > "$INDEX" << INDEXEOF
# $(basename "$dir")
## Documents
${FILES}
---
*Auto-generated by repository-cleanup workflow*
INDEXEOF
# Dedent
sed -i 's/^ //' "$INDEX"
UPDATED=$((UPDATED+1))
done
if [ "$UPDATED" -gt 0 ]; then
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add docs/
if ! git diff --cached --quiet; then
git commit -m "docs: rebuild documentation indexes [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push
echo "✅ ${UPDATED} index file(s) rebuilt and committed" >> $GITHUB_STEP_SUMMARY
else
echo "✅ All indexes already up to date" >> $GITHUB_STEP_SUMMARY
fi
else
echo "✅ No indexes to rebuild" >> $GITHUB_STEP_SUMMARY
fi
# ── VERSION DRIFT DETECTION ──────────────────────────────────────────
- name: Check for version drift
run: |
echo "## 📦 Version Drift Check" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ ! -f "README.md" ]; then
echo "⏭️ No README.md — skipping" >> $GITHUB_STEP_SUMMARY
exit 0
fi
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md 2>/dev/null | head -1)
if [ -z "$README_VERSION" ]; then
echo "⚠️ No VERSION found in README.md FILE INFORMATION block" >> $GITHUB_STEP_SUMMARY
exit 0
fi
echo "**README version:** \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
DRIFT=0
CHECKED=0
# Check all files with FILE INFORMATION blocks
while IFS= read -r -d '' file; do
FILE_VERSION=$(grep -oP '^\s*\*?\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' "$file" 2>/dev/null | head -1)
[ -z "$FILE_VERSION" ] && continue
CHECKED=$((CHECKED+1))
if [ "$FILE_VERSION" != "$README_VERSION" ]; then
echo " ⚠️ \`${file}\`: \`${FILE_VERSION}\` (expected \`${README_VERSION}\`)" >> $GITHUB_STEP_SUMMARY
DRIFT=$((DRIFT+1))
fi
done < <(find . -maxdepth 4 -type f \( -name "*.php" -o -name "*.md" -o -name "*.yml" \) ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" -print0 2>/dev/null)
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$DRIFT" -gt 0 ]; then
echo "⚠️ **${DRIFT}** file(s) out of ${CHECKED} have version drift" >> $GITHUB_STEP_SUMMARY
echo "Run \`sync-version-on-merge\` workflow or update manually" >> $GITHUB_STEP_SUMMARY
else
echo "✅ All ${CHECKED} file(s) match README version \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# ── PROTECT CUSTOM WORKFLOWS ────────────────────────────────────────
- name: Ensure custom workflow directory exists
run: |
echo "## 🔧 Custom Workflows" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ ! -d ".github/workflows/custom" ]; then
mkdir -p .github/workflows/custom
cat > .github/workflows/custom/README.md << 'CWEOF'
# Custom Workflows
Place repo-specific workflows here. Files in this directory are:
- **Never overwritten** by MokoStandards bulk sync
- **Never deleted** by the repository-cleanup workflow
- Safe for custom CI, notifications, or repo-specific automation
Synced workflows live in `.github/workflows/` (parent directory).
CWEOF
sed -i 's/^ //' .github/workflows/custom/README.md
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add .github/workflows/custom/
if ! git diff --cached --quiet; then
git commit -m "chore: create .github/workflows/custom/ for repo-specific workflows [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push
echo "✅ Created \`.github/workflows/custom/\` directory" >> $GITHUB_STEP_SUMMARY
fi
else
CUSTOM_COUNT=$(find .github/workflows/custom -name "*.yml" -o -name "*.yaml" 2>/dev/null | wc -l)
echo "✅ Custom workflow directory exists (${CUSTOM_COUNT} workflow(s))" >> $GITHUB_STEP_SUMMARY
fi
# ── DELETE CLOSED ISSUES ──────────────────────────────────────────────
- name: Delete old closed issues
if: steps.tasks.outputs.delete_closed_issues == 'true'
env: env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: | run: |
echo "## 🗑️ Self-Cleanup" >> $GITHUB_STEP_SUMMARY REPO="${{ github.repository }}"
CUTOFF=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)
echo "## 🗑️ Closed Issue Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "Deleting issues closed before: ${CUTOFF}" >> $GITHUB_STEP_SUMMARY
WORKFLOW_FILE=".github/workflows/repository-cleanup.yml" DELETED=0
if [ -f "$WORKFLOW_FILE" ]; then gh api "repos/${REPO}/issues?state=closed&since=1970-01-01T00:00:00Z&per_page=100&sort=updated&direction=asc" \
git config --local user.email "github-actions[bot]@users.noreply.github.com" --jq ".[] | select(.closed_at < \"${CUTOFF}\") | .number" 2>/dev/null | while read -r num; do
git config --local user.name "github-actions[bot]" # Lock and close with "not_planned" to mark as cleaned up
git rm "$WORKFLOW_FILE" gh api "repos/${REPO}/issues/${num}/lock" -X PUT -f lock_reason="resolved" --silent 2>/dev/null || true
git commit -m "chore: remove repository-cleanup.yml after successful run [skip ci]" \ echo " Locked issue #${num}" >> $GITHUB_STEP_SUMMARY
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>" DELETED=$((DELETED+1))
git push done
echo "✅ Workflow file deleted — it will not appear in future syncs" >> $GITHUB_STEP_SUMMARY
if [ "$DELETED" -eq 0 ] 2>/dev/null; then
echo "✅ No old closed issues found" >> $GITHUB_STEP_SUMMARY
else else
echo " Workflow file already removed" >> $GITHUB_STEP_SUMMARY echo "✅ Locked ${DELETED} old closed issue(s)" >> $GITHUB_STEP_SUMMARY
fi fi
- name: Summary - name: Summary
@@ -241,4 +516,4 @@ jobs:
run: | run: |
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY echo "---" >> $GITHUB_STEP_SUMMARY
echo "*Run by @${{ github.actor }} via workflow_dispatch*" >> $GITHUB_STEP_SUMMARY echo "*Run by @${{ github.actor }} — trigger: ${{ github.event_name }}*" >> $GITHUB_STEP_SUMMARY

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@
# INGROUP: MokoStandards.Automation # INGROUP: MokoStandards.Automation
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/sync-version-on-merge.yml # PATH: /templates/workflows/shared/sync-version-on-merge.yml
# VERSION: 04.01.00 # VERSION: 04.04.00
# BRIEF: Auto-bump patch version on every push to main and propagate to all file headers # BRIEF: Auto-bump patch version on every push to main and propagate to all file headers
# NOTE: Synced via bulk-repo-sync to .github/workflows/sync-version-on-merge.yml in all governed repos. # NOTE: Synced via bulk-repo-sync to .github/workflows/sync-version-on-merge.yml in all governed repos.
# README.md is the single source of truth for the repository version. # README.md is the single source of truth for the repository version.
@@ -32,6 +32,9 @@ permissions:
contents: write contents: write
issues: write issues: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs: jobs:
sync-version: sync-version:
name: Propagate README version name: Propagate README version
@@ -45,7 +48,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Set up PHP - name: Set up PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2.31.0 uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0
with: with:
php-version: '8.1' php-version: '8.1'
tools: composer tools: composer
@@ -55,7 +58,7 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: | run: |
git clone --depth 1 --quiet \ git clone --depth 1 --branch version/04.04.00 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \ "https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards /tmp/mokostandards
cd /tmp/mokostandards cd /tmp/mokostandards
@@ -64,31 +67,20 @@ jobs:
- name: Auto-bump patch version - name: Auto-bump patch version
if: ${{ github.event_name == 'push' && github.actor != 'github-actions[bot]' }} if: ${{ github.event_name == 'push' && github.actor != 'github-actions[bot]' }}
run: | run: |
# If README.md was part of this push, the author already bumped the version — skip.
if git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -q '^README\.md$'; then if git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -q '^README\.md$'; then
echo "README.md changed in this push — skipping auto-bump" echo "README.md changed in this push — skipping auto-bump"
exit 0 exit 0
fi fi
CURRENT=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1) RESULT=$(php /tmp/mokostandards/api/cli/version_bump.php --path .) || {
if [ -z "$CURRENT" ]; then echo "⚠️ Could not bump version — skipping"
echo "⚠️ No VERSION found in README.md — skipping auto-bump"
exit 0 exit 0
fi }
echo "Auto-bumping patch: $RESULT"
# Increment the patch component (zero-padded to 2 digits)
MAJOR=$(echo "$CURRENT" | cut -d. -f1)
MINOR=$(echo "$CURRENT" | cut -d. -f2)
PATCH=$(echo "$CURRENT" | cut -d. -f3)
NEW_PATCH=$(printf '%02d' $(( 10#$PATCH + 1 )))
NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}"
echo "Auto-bumping patch: $CURRENT → $NEW_VERSION"
sed -i "s/^\(\s*VERSION:\s*\)${CURRENT}/\1${NEW_VERSION}/" README.md
git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]" git config --local user.name "github-actions[bot]"
git add README.md git add README.md
git commit -m "chore(version): auto-bump patch ${CURRENT} → ${NEW_VERSION} [skip ci]" \ git commit -m "chore(version): auto-bump patch ${RESULT} [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>" --author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push git push
@@ -96,7 +88,7 @@ jobs:
id: readme_version id: readme_version
run: | run: |
git pull --ff-only 2>/dev/null || true git pull --ff-only 2>/dev/null || true
VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1) VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null)
if [ -z "$VERSION" ]; then if [ -z "$VERSION" ]; then
echo "⚠️ No VERSION in README.md — skipping propagation" echo "⚠️ No VERSION in README.md — skipping propagation"
echo "skip=true" >> $GITHUB_OUTPUT echo "skip=true" >> $GITHUB_OUTPUT
@@ -119,8 +111,9 @@ jobs:
- name: Commit updated files - name: Commit updated files
if: ${{ steps.readme_version.outputs.skip != 'true' && inputs.dry_run != true }} if: ${{ steps.readme_version.outputs.skip != 'true' && inputs.dry_run != true }}
run: | run: |
git pull --ff-only 2>/dev/null || true
if git diff --quiet; then if git diff --quiet; then
echo " No version changes needed" echo " No version changes needed — already up to date"
exit 0 exit 0
fi fi
VERSION="${{ steps.readme_version.outputs.version }}" VERSION="${{ steps.readme_version.outputs.version }}"

20
.mokostandards Normal file
View File

@@ -0,0 +1,20 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: MokoStandards.Templates.Config
# INGROUP: MokoStandards.Templates
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/configs/moko-standards.yml
# VERSION: 04.04.00
# BRIEF: Governance attachment template — synced to .mokostandards in every governed repository
# NOTE: Tokens replaced at sync time: mokoconsulting-tech, MokoCassiopeia, waas-component, 04.04.00
#
# This file is managed automatically by MokoStandards bulk sync.
# Do not edit manually — changes will be overwritten on the next sync.
# To update governance settings, open a PR in MokoStandards instead:
# https://github.com/mokoconsulting-tech/MokoStandards
standards_source: "https://github.com/mokoconsulting-tech/MokoStandards"
standards_version: "04.04.00"
platform: "waas-component"
governed_repo: "mokoconsulting-tech/MokoCassiopeia"

View File

@@ -1,98 +1,87 @@
<!-- <!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project. This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
# FILE INFORMATION 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.
DEFGROUP: Joomla.Template
INGROUP: MokoCassiopeia.Governance 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.
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
FILE: CODE_OF_CONDUCT.md You should have received a copy of the GNU General Public License (./LICENSE.md).
VERSION: 03.06.03
BRIEF: Contributor code of conduct for the MokoCassiopeia project. # FILE INFORMATION
PATH: /CODE_OF_CONDUCT.md DEFGROUP:
NOTE: This document defines behavioral expectations and enforcement processes. INGROUP: Project.Documentation
REPO:
VERSION: 04.04.00
PATH: ./CODE_OF_CONDUCT.md
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
--> -->
# Code of Conduct
## Code of Conduct ## 1. Purpose
This Code of Conduct establishes expectations for behavior within the MokoCassiopeia project community. The objective is to maintain a professional, inclusive, and respectful environment aligned with open source governance best practices. The purpose of this Code of Conduct is to ensure a safe, inclusive, and respectful environment for all contributors and participants in Moko Consulting projects. This applies to all interactions, whether in repositories, issue trackers, documentation, meetings, or community spaces.
## Scope ## 2. Our Standards
This Code of Conduct applies to all project spaces, including: Participants are expected to uphold behaviors that strengthen our community, including:
* GitHub repositories, issues, pull requests, discussions, and security advisories. Demonstrating empathy and respect toward others.
* Project documentation, workflows, and release processes. Being inclusive of diverse viewpoints and backgrounds.
* Any communication channels officially associated with the project. Gracefully accepting constructive feedback.
Prioritizing collaboration over conflict.
Showing professionalism in all interactions.
## Our Standards ### Unacceptable behavior includes:
Participants are expected to: Harassment, discrimination, or derogatory comments.
Threatening or violent language or actions.
Disruptive, aggressive, or intentionally harmful behavior.
Publishing others private information without permission.
Any behavior that violates applicable laws.
* Communicate professionally and respectfully. ## 3. Responsibilities of Maintainers
* Provide constructive feedback focused on technical merit and project objectives.
* Respect differing viewpoints, experience levels, and backgrounds.
* Follow documented contribution, security, and governance policies.
Unacceptable behavior includes: Maintainers are responsible for:
* Harassment, discrimination, or exclusionary conduct. Clarifying acceptable behavior.
* Personal attacks, insults, or inflammatory comments. Taking appropriate corrective action when unacceptable behavior occurs.
* Publishing private information without consent. Removing, editing, or rejecting contributions that violate this Code.
* Disruptive behavior that materially interferes with project operations. Temporarily or permanently banning contributors who engage in repeated or severe violations.
## Enforcement Responsibilities ## 4. Scope
Project maintainers are responsible for: This Code applies to:
* Clarifying standards when questions arise. All Moko Consulting repositories.
* Taking appropriate and proportionate corrective action when violations occur. All documentation and collaboration platforms.
* Maintaining confidentiality to the extent practical during investigations. Public and private communication related to project activities.
Any representation of Moko Consulting in online or offline spaces.
## Reporting ## 5. Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported through: Instances of misconduct may be reported to:
**[hello@mokoconsulting.tech](mailto:hello@mokoconsulting.tech)**
* Email: `hello@mokoconsulting.tech` with subject `CODE OF CONDUCT: MokoCassiopeia`. All reports will be reviewed and investigated promptly and fairly. Maintainers are obligated to maintain confidentiality where possible.
Reports should include relevant context, links, screenshots, or other supporting information. Consequences may include:
## Enforcement Guidelines A warning.
Required training or mediation.
Temporary or permanent bans.
Escalation to legal authorities when required.
Corrective actions may include, but are not limited to: ## 6. Acknowledgements
* Private warning or request for corrective action. This Code of Conduct is inspired by widely adopted community guidelines, including the Contributor Covenant and major open-source collaboration standards.
* Temporary or permanent restriction from project participation.
* Removal of content that violates this Code of Conduct.
Decisions are made based on impact, severity, and pattern of behavior. ## 7. Related Documents
## No Retaliation [Governance Guide](./docs-governance.md)
[Contributor Guide](./docs-contributing.md)
[Documentation Index](./docs-index.md)
Retaliation against individuals who report concerns in good faith is not tolerated. Any retaliatory behavior will be treated as a separate violation. This Code of Conduct is a living document and may be updated following the established Change Management process.
## Jurisdiction
This project is managed from Tennessee, USA. This statement is informational and does not constitute legal advice.
---
## Metadata
* **Document:** CODE_OF_CONDUCT.md
* **Repository:** [https://github.com/mokoconsulting-tech/MokoCassiopeia](https://github.com/mokoconsulting-tech/MokoCassiopeia)
* **Path:** /CODE_OF_CONDUCT.md
* **Owner:** Moko Consulting
* **Version:** 03.06.00
* **Status:** Active
* **Effective Date:** 2025-12-18
* **Last Reviewed:** 2025-12-18
## Revision History
| Date | Change Summary | Author |
| ---------- | ----------------------------------------------------------------------------- | --------------- |
| 2025-12-18 | Initial publication of contributor conduct standards and enforcement process. | Moko Consulting |

View File

@@ -1,145 +1,128 @@
<!-- <!--
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech> Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project. This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later 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 (./LICENSE).
# FILE INFORMATION # FILE INFORMATION
DEFGROUP: Joomla.Template DEFGROUP: {{DEFGROUP}}
INGROUP: MokoCassiopeia.Governance INGROUP: Project.Documentation
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
FILE: CONTRIBUTING.md VERSION: 04.04.00
VERSION: 03.06.03 PATH: ./CONTRIBUTING.md
BRIEF: Contribution guidelines for the MokoCassiopeia project. BRIEF: How to contribute; branch strategy, commit conventions, PR workflow, and release pipeline
PATH: /CONTRIBUTING.md -->
NOTE: This document defines contribution workflow, standards, and governance alignment.
-->
## Contributing # Contributing
This document defines how to contribute to the MokoCassiopeia project. The goal is to ensure changes are reviewable, auditable, and aligned with project governance and release processes. Thank you for your interest in contributing to **MokoCassiopeia**!
## Scope This repository is governed by **[MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards)** — the authoritative source of coding standards, workflows, and policies for all Moko Consulting repositories.
These guidelines apply to all contributions, including: ## Branch Strategy
* Source code changes | Branch | Purpose | Deploys To |
* Documentation updates |--------|---------|------------|
* Bug reports and enhancement proposals | `main` | Bleeding edge — all development merges here | CI only |
| `dev/XX.YY.ZZ` | Feature development | Dev server (version: "development") |
| `version/XX.YY.ZZ` | Stable frozen snapshot | Demo + RS servers |
## Prerequisites ### Development Workflow
Contributors are expected to: ```
1. Create branch: git checkout -b dev/XX.YY.ZZ/my-feature
* Have a working understanding of Joomla template structure. 2. Develop + test (dev server auto-deploys on push)
* Be familiar with Git and GitHub pull request workflows. 3. Open PR → main (squash merge only)
* Review repository governance documents prior to submitting changes. 4. Auto-release (version branch + tag + GitHub Release created automatically)
* Set up the development environment using the provided tools.
### Quick Setup
For first-time contributors:
```bash
# Clone the repository
git clone https://github.com/mokoconsulting-tech/MokoCassiopeia.git
cd MokoCassiopeia
``` ```
See [docs/QUICK_START.md](./docs/QUICK_START.md) for detailed setup instructions. ### Branch Naming
## Development Tools | Prefix | Use |
|--------|-----|
| `dev/XX.YY.ZZ` | Feature development (e.g., `dev/02.00.00/add-extrafields`) |
| `version/XX.YY.ZZ` | Stable release (auto-created, never manually pushed) |
| `chore/` | Automated sync branches (managed by MokoStandards) |
The repository provides several tools to streamline development: > **Never use** `feature/`, `hotfix/`, or `release/` prefixes — they are not part of the MokoStandards branch strategy.
* **Pre-commit Hooks**: Automatic local validation before commits ## Commit Conventions
## Contribution Workflow Use [conventional commits](https://www.conventionalcommits.org/):
1. Fork the repository. ```
2. Create a branch from the active development branch. feat(scope): add new extrafield for invoice tracking
3. Make focused, minimal changes that address a single concern. fix(sql): correct column type in llx_mytable
4. Submit a pull request with a clear description of intent and impact. docs(readme): update installation instructions
chore(deps): bump enterprise library to 04.02.30
```
Direct commits to protected branches are not permitted. **Valid types:** `feat` | `fix` | `docs` | `chore` | `ci` | `refactor` | `style` | `test` | `perf` | `revert` | `build`
## Branching and Versioning ## Pull Request Workflow
* Development work occurs on designated development branches. 1. **Branch** from `main` using `dev/XX.YY.ZZ/description` format
* Releases are produced from versioned branches following repository standards. 2. **Bump** the patch version in `README.md` before opening the PR
* Contributors should not bump version numbers unless explicitly requested. 3. **Title** must be a valid conventional commit subject line
4. **Target** `main` — squash merge only (merge commits are disabled)
5. **CI checks** must pass before merge
## Coding and Formatting Standards ### What Happens on Merge
All contributions must: When your PR is merged to `main`, these workflows run automatically:
* Follow Joomla coding standards where applicable. 1. **sync-version-on-merge** — auto-bumps patch version, propagates to all file headers
* Conform to Moko Consulting repository standards for headers, metadata, and file structure. 2. **auto-release** — creates `version/XX.YY.ZZ` branch, git tag, and GitHub Release
* Avoid introducing tabs, inconsistent path separators, or non portable assumptions. 3. **deploy-demo / deploy-rs** — deploys to demo and RS servers (if `src/**` changed)
Automated checks may reject changes that do not meet these requirements. ## Coding Standards
## Documentation Standards All contributions must follow [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards):
Documentation changes must: | Standard | Reference |
|----------|-----------|
| Coding Style | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) |
| File Headers | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) |
| Branching | [branch-release-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branch-release-strategy.md) |
| Merge Strategy | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) |
| Scripting | [scripting-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/scripting-standards.md) |
| Build & Release | [build-release.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/workflows/build-release.md) |
* Include required metadata and revision history sections. ## PR Checklist
* Avoid embedding version numbers in revision history tables.
* Preserve existing structure unless a structural change is explicitly proposed.
## Commit Messages - [ ] Branch named `dev/XX.YY.ZZ/description`
- [ ] Patch version bumped in `README.md`
- [ ] Conventional commit format for PR title
- [ ] All new files have FILE INFORMATION headers
- [ ] `declare(strict_types=1)` in all PHP files
- [ ] PHPDoc on all public methods
- [ ] Tests pass
- [ ] CHANGELOG.md updated
- [ ] No secrets, tokens, or credentials committed
Commit messages should: ## Custom Workflows
* Be concise and descriptive. Place repo-specific workflows in `.github/workflows/custom/` — they are **never overwritten or deleted** by MokoStandards sync:
* Focus on what changed and why.
* Avoid referencing internal issue trackers unless required.
## Reporting Issues ```
.github/workflows/
Bug reports and enhancement requests should be filed as GitHub issues and include: ├── deploy-dev.yml ← Synced from MokoStandards
├── auto-release.yml ← Synced from MokoStandards
* Clear reproduction steps or use cases. └── custom/ ← Your custom workflows (safe)
* Expected versus actual behavior. └── my-custom-ci.yml
* Relevant environment details. ```
Security related issues must follow the process defined in SECURITY.md and must not be reported publicly.
## Review Process
All pull requests are subject to review. Review criteria include:
* Technical correctness
* Alignment with project goals
* Maintainability and clarity
* Risk introduced to release and update processes
Maintainers may request changes prior to approval.
## License ## License
By contributing, you agree that your contributions will be licensed under GPL-3.0-or-later, consistent with the rest of the project. By contributing, you agree that your contributions will be licensed under the [GPL-3.0-or-later](LICENSE) license.
## Code of Conduct
Participation in this project is governed by the Code of Conduct. Unacceptable behavior may result in contribution restrictions.
--- ---
## Metadata *This file is synced from [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Do not edit directly — changes will be overwritten on the next sync.*
* **Document:** CONTRIBUTING.md
* **Repository:** [https://github.com/mokoconsulting-tech/MokoCassiopeia](https://github.com/mokoconsulting-tech/MokoCassiopeia)
* **Path:** /CONTRIBUTING.md
* **Owner:** Moko Consulting
* **Version:** 03.06.00
* **Status:** Active
* **Effective Date:** 2025-12-18
* **Last Reviewed:** 2025-12-18
## Revision History
| Date | Change Summary | Author |
| ---------- | ------------------------------------------------------------------------- | --------------- |
| 2025-12-18 | Initial publication of contribution guidelines and workflow expectations. | Moko Consulting |

View File

@@ -1,185 +1,240 @@
<!-- <!--
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech> Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project. This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION This program is free software; you can redistribute it and/or modify
DEFGROUP: Joomla.Template it under the terms of the GNU General Public License as published by
INGROUP: MokoCassiopeia.Governance the Free Software Foundation; either version 3 of the License, or
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia (at your option) any later version.
FILE: SECURITY.md
VERSION: 03.06.02 This program is distributed in the hope that it will be useful,
BRIEF: Security policy and vulnerability reporting process for MokoCassiopeia. but WITHOUT ANY WARRANTY; without even the implied warranty of
PATH: /SECURITY.md MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
NOTE: This policy is process oriented and does not replace secure engineering practices. 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: [PROJECT_NAME]
INGROUP: [PROJECT_NAME].Documentation
REPO: [REPOSITORY_URL]
PATH: /SECURITY.md
VERSION: 04.04.00
BRIEF: Security vulnerability reporting and handling policy
--> -->
## Security Policy # Security Policy
This document defines how MokoCassiopeia handles vulnerability intake, triage, remediation, and disclosure. The objective is to reduce risk, protect downstream users, and preserve operational continuity with a verifiable audit trail. ## Purpose and Scope
## Scope This document defines the security vulnerability reporting, response, and disclosure policy for [PROJECT_NAME] and all repositories governed by these standards. It establishes the authoritative process for responsible disclosure, assessment, remediation, and communication of security issues.
This policy applies to:
* Repository source code, workflows, scripts, and build artifacts.
* Release packaging (ZIP outputs) generated from the repository.
* Configuration and metadata used for distribution (for example manifests and update metadata).
Out of scope:
* Vulnerabilities in upstream Joomla core, third party extensions, or external infrastructure not controlled by this repository.
* Issues that require physical access to a host, compromised administrator credentials, or a compromised hosting provider, unless the repository materially increases impact.
## Supported Versions ## Supported Versions
Security fixes are prioritized for: Security updates are provided for the following versions:
* The latest released version. | Version | Supported |
* The current development line when it is actively used for release engineering. | ------- | ------------------ |
| [X.x.x] | :white_check_mark: |
| < [X.0] | :x: |
Backports may be provided based on impact, deployment footprint, and engineering capacity. Only the current major version receives security updates. Users should upgrade to the latest supported version to receive security patches.
## Reporting a Vulnerability ## Reporting a Vulnerability
Use one of the following channels: ### Where to Report
* GitHub Security Advisories (preferred): use the repository security tab to submit a private report. **DO NOT** create public GitHub issues for security vulnerabilities.
* Email: send details to `hello@mokoconsulting.tech` with subject `SECURITY: MokoCassiopeia vulnerability report`.
Do not file a public GitHub issue for suspected security vulnerabilities. Report security vulnerabilities privately to:
### What to include **Email**: `security@[DOMAIN]`
Provide enough detail to reproduce and triage: **Subject Line**: `[SECURITY] Brief Description`
* A clear description of the vulnerability and expected impact. ### What to Include
* A minimal proof of concept or reproduction steps.
* Affected versions, configuration assumptions, and environment details.
* Any proposed mitigation or patch.
* Your preferred contact details for follow up.
## Triage and Response Targets A complete vulnerability report should include:
The project operates with response targets aligned to practical delivery realities: 1. **Description**: Clear explanation of the vulnerability
2. **Impact**: Potential security impact and severity assessment
3. **Affected Versions**: Which versions are vulnerable
4. **Reproduction Steps**: Detailed steps to reproduce the issue
5. **Proof of Concept**: Code, configuration, or demonstration (if applicable)
6. **Suggested Fix**: Proposed remediation (if known)
7. **Disclosure Timeline**: Your expectations for public disclosure
* **Acknowledgement:** within 3 business days. ### Response Timeline
* **Initial triage:** within 10 business days.
* **Fix plan:** communicated once severity is confirmed.
These targets are not guarantees. Complex issues, supply chain considerations, and coordination with upstream vendors may extend timelines. * **Initial Response**: Within 3 business days
* **Assessment Complete**: Within 7 business days
* **Fix Timeline**: Depends on severity (see below)
* **Disclosure**: Coordinated with reporter
## Severity Assessment ## Severity Classification
Issues are triaged based on business impact and technical exploitability, including: Vulnerabilities are classified using the following severity levels:
* Remote exploitability and required privileges. ### Critical
* Data confidentiality, integrity, and availability impact. * Remote code execution
* Likelihood of exploitation in typical Joomla deployments. * Authentication bypass
* Exposure surface (public endpoints, administrator area, installation flows, and update mechanisms). * Data breach or exposure of sensitive information
* **Fix Timeline**: 7 days
When appropriate, industry standard scoring such as CVSS may be used for internal prioritization. ### High
* Privilege escalation
* SQL injection or command injection
* Cross-site scripting (XSS) with significant impact
* **Fix Timeline**: 14 days
## Coordinated Disclosure ### Medium
* Information disclosure (limited scope)
* Denial of service
* Security misconfigurations with moderate impact
* **Fix Timeline**: 30 days
The project follows coordinated vulnerability disclosure: ### Low
* Security best practice violations
* Minor information leaks
* Issues requiring user interaction or complex preconditions
* **Fix Timeline**: 60 days or next release
* Reports are treated as confidential until remediation is available. ## Remediation Process
* A public advisory may be published once a fix is released.
* A reasonable embargo period is expected to enable patch distribution.
If you believe disclosure is time sensitive due to active exploitation, include that assessment and any supporting indicators. 1. **Acknowledgment**: Security team confirms receipt and begins investigation
2. **Assessment**: Vulnerability is validated, severity assigned, and impact analyzed
3. **Development**: Security patch is developed and tested
4. **Review**: Patch undergoes security review and validation
5. **Release**: Fixed version is released with security advisory
6. **Disclosure**: Public disclosure follows coordinated timeline
## Security Updates and Advisories ## Security Advisories
Security updates are distributed through: Security advisories are published via:
* GitHub releases for the repository. * GitHub Security Advisories
* GitHub Security Advisories when applicable. * Release notes and CHANGELOG.md
* Security mailing list (when established)
Advisories may include: Advisories include:
* Affected versions and fixed versions. * CVE identifier (if applicable)
* Mitigations and workarounds when a fix is not immediately available. * Severity rating
* Upgrade guidance. * Affected versions
* Fixed versions
* Mitigation steps
* Attribution (with reporter consent)
## Dependencies and Supply Chain Controls ## Security Best Practices
The project aims to manage supply chain risk through: For repositories adopting MokoStandards:
* Pinning and review of workflow dependencies where feasible. ### Required Controls
* Minimizing privileged GitHub token permissions.
* Validating build inputs prior to packaging releases.
If you identify a supply chain issue (for example compromised action, dependency confusion, or malicious upstream artifact), report it as a vulnerability. * Enable GitHub security features (Dependabot, code scanning)
* Implement branch protection on `main`
* Require code review for all changes
* Enforce signed commits (recommended)
* Use secrets management (never commit credentials)
* Maintain security documentation
* Follow secure coding standards defined in `/docs/policy/`
## Secure Development and CI Expectations ### CI/CD Security
Security posture is reinforced through operational controls: * Validate all inputs
* Sanitize outputs
* Use least privilege access
* Pin dependencies with hash verification
* Scan for vulnerabilities in dependencies
* Audit third-party actions and tools
* CI validation for packaging inputs and manifest integrity. #### Automated Security Scanning
* Consistent path normalization and whitespace hygiene checks where required for release correctness.
* Least privilege for GitHub Actions permissions.
### Template Security Features All repositories MUST implement:
**Custom Head Content Injection** **CodeQL Analysis**:
* Enabled for all supported languages (Python, JavaScript, TypeScript, Java, C/C++, C#, Go, Ruby)
* Runs on: push to main, pull requests, weekly schedule
* Query sets: `security-extended` and `security-and-quality`
* Configuration: `.github/workflows/codeql-analysis.yml`
The template provides Custom Head Code fields (`custom_head_start` and `custom_head_end`) that allow administrators to inject custom HTML, CSS, and JavaScript code. This is an intentional feature for: **Dependabot Security Updates**:
* Weekly scans for vulnerable dependencies
* Automated pull requests for security patches
* Configuration: `.github/dependabot.yml`
* Adding analytics scripts (Google Analytics, Google Tag Manager) **Secret Scanning**:
* Custom meta tags * Enabled by default with push protection
* Third-party integrations * Prevents accidental credential commits
* Custom styling * Partner patterns enabled
**Security Considerations:** **Dependency Review**:
* Required for all pull requests
* Blocks introduction of known vulnerable dependencies
* Automatic license compliance checking
* These fields use `filter="raw"` to allow HTML/JS injection See [Security Scanning Policy](docs/policy/security-scanning.md) for detailed requirements.
* **Access is restricted to Joomla administrators only** via template configuration
* This is not an XSS vulnerability as it requires administrator privileges
* Administrators should only add trusted code from verified sources
* Regular security audits should review custom head content
This policy does not guarantee that all vulnerabilities will be prevented. It defines how risk is managed when issues are discovered. ### Dependency Management
## Safe Harbor * Keep dependencies up to date
* Monitor security advisories for dependencies
* Remove unused dependencies
* Audit new dependencies before adoption
* Document security-critical dependencies
The project supports good faith security research. When you: ## Compliance and Governance
* Avoid privacy violations, data destruction, and service disruption. This security policy is binding for all repositories governed by MokoStandards. Deviations require documented justification and approval from the Security Owner.
* Limit testing to systems you own or have explicit permission to test.
* Provide a reasonable window for coordinated disclosure.
Then the project will treat your report as a constructive security contribution. Security policies are reviewed and updated at least annually or following significant security incidents.
Jurisdiction note: this repository is managed from Tennessee, USA. This note is informational only and does not constitute legal advice. ## Attribution and Recognition
## Public Communications We acknowledge and appreciate responsible disclosure. With your permission, we will:
Only maintainers will publish security advisories or public statements for confirmed vulnerabilities. Public communication will focus on actionable remediation and operational risk reduction. * Credit you in security advisories
* List you in CHANGELOG.md for the fix release
* Recognize your contribution publicly (if desired)
## Acknowledgements ## Contact and Escalation
If you want credit, include the name or handle to list in an advisory. If you prefer anonymity, state that explicitly. * **Security Team**: security@[DOMAIN]
* **Primary Contact**: [CONTACT_EMAIL]
* **Escalation**: For urgent matters requiring immediate attention, contact the maintainer directly via GitHub
## Out of Scope
The following are explicitly out of scope:
* Issues in third-party dependencies (report directly to maintainers)
* Social engineering attacks
* Physical security issues
* Denial of service via resource exhaustion without amplification
* Issues requiring physical access to systems
* Theoretical vulnerabilities without proof of exploitability
--- ---
## Metadata ## Metadata
* **Document:** SECURITY.md | Field | Value |
* **Repository:** [https://github.com/mokoconsulting-tech/MokoCassiopeia](https://github.com/mokoconsulting-tech/MokoCassiopeia) | ------------ | ----------------------------------------------- |
* **Path:** /SECURITY.md | Document | Security Policy |
* **Owner:** Moko Consulting | Path | /SECURITY.md |
* **Version:** 03.06.00 | Repository | [REPOSITORY_URL] |
* **Status:** Active | Owner | [OWNER_NAME] |
* **Effective Date:** 2025-12-18 | Scope | Security vulnerability handling |
* **Last Reviewed:** 2025-12-18 | Applies To | All repositories governed by MokoStandards |
| Status | Active |
| Effective | [YYYY-MM-DD] |
## Revision History ## Revision History
| Date | Change Summary | Author | | Date | Change Description | Author |
| ---------- | ------------------------------------------------------------------------------------------------ | --------------- | | ---------- | ------------------------------------------------- | --------------- |
| 2026-01-30 | Added Template Security Features section documenting custom head content injection controls. | Copilot Agent | | [YYYY-MM-DD] | Initial creation | [AUTHOR_NAME] |
| 2025-12-18 | Initial publication of security policy, intake channels, triage targets, and disclosure process. | Moko Consulting |

View File

@@ -14,7 +14,7 @@
"prefer-stable": true, "prefer-stable": true,
"require": { "require": {
"php": ">=8.1", "php": ">=8.1",
"mokoconsulting-tech/enterprise": "^4.0" "mokoconsulting-tech/enterprise": "dev-version/04.02.00"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^10.5", "phpunit/phpunit": "^10.5",

119
docs/update-server.md Normal file
View File

@@ -0,0 +1,119 @@
<!--
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
# FILE INFORMATION
DEFGROUP: MokoCassiopeia.Documentation
INGROUP: MokoStandards.Templates
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
PATH: /docs/update-server.md
VERSION: 04.04.00
BRIEF: How this extension's Joomla update server file (update.xml) is managed
-->
# Joomla Update Server
[![MokoStandards](https://img.shields.io/badge/MokoStandards-04.04.00-blue)](https://github.com/mokoconsulting-tech/MokoStandards)
This document explains how `update.xml` is automatically managed for this Joomla extension following the [Joomla Update Server specification](https://docs.joomla.org/Deploying_an_Update_Server).
## How It Works
Joomla checks for extension updates by fetching an XML file from the URL defined in the `<updateservers>` tag in the extension's XML manifest. MokoStandards generates this file automatically.
### Automatic Generation
| Event | Workflow | `<tag>` | `<version>` |
|-------|----------|---------|-------------|
| Merge to `main` | `auto-release.yml` | `stable` | `XX.YY.ZZ` |
| Push to `dev/**` | `deploy-dev.yml` | `development` | `development` |
| Push to `rc/**` | `deploy-dev.yml` | `rc` | `XX.YY.ZZ-rc` |
### Generated XML Structure
```xml
<?xml version="1.0" encoding="utf-8"?>
<updates>
<update>
<name>Extension Name</name>
<description>Extension Name update</description>
<element>com_extensionname</element>
<type>component</type>
<version>01.02.03</version>
<client>site</client>
<folder>system</folder> <!-- plugins only -->
<tags>
<tag>stable</tag>
</tags>
<infourl title="Extension Name">https://github.com/.../releases/tag/v01.02.03</infourl>
<downloads>
<downloadurl type="full" format="zip">https://github.com/.../releases/download/v01.02.03/com_ext-01.02.03.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="((4\.[3-9])|(5\.[0-9]))" />
<php_minimum>8.1</php_minimum> <!-- if present in manifest -->
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
</updates>
```
### Metadata Source
All metadata is extracted from the extension's XML manifest (`src/*.xml`) at build time:
| XML Element | Source | Notes |
|-------------|--------|-------|
| `<name>` | `<name>` in manifest | Extension display name |
| `<element>` | `<element>` in manifest | Must match installed extension identifier |
| `<type>` | `type` attribute on `<extension>` | `component`, `module`, `plugin`, `library`, `package`, `template` |
| `<client>` | `client` attribute on `<extension>` | `site` or `administrator`**required for plugins and modules** |
| `<folder>` | `group` attribute on `<extension>` | Plugin group (e.g., `system`, `content`) — **required for plugins** |
| `<targetplatform>` | `<targetplatform>` in manifest | Falls back to Joomla 4.3+ / 5.x if not specified |
| `<php_minimum>` | `<php_minimum>` in manifest | Included only if present |
### Extension Manifest Setup
Your XML manifest must include an `<updateservers>` tag pointing to the `update.xml` on the `main` branch:
```xml
<extension type="component" client="site" method="upgrade">
<name>My Extension</name>
<element>com_myextension</element>
<!-- ... -->
<updateservers>
<server type="extension" name="My Extension Updates">
https://raw.githubusercontent.com/mokoconsulting-tech/MokoCassiopeia/main/update.xml
</server>
</updateservers>
</extension>
```
### Branch Lifecycle
```
dev/XX.YY.ZZ → rc/XX.YY.ZZ → main → version/XX.YY.ZZ
(development) (rc) (stable) (frozen snapshot)
```
1. **Development** (`dev/**`): `update.xml` with `<tag>development</tag>`, download points to branch archive
2. **Release Candidate** (`rc/**`): `update.xml` with `<tag>rc</tag>`, version set to `XX.YY.ZZ-rc`
3. **Stable Release** (merge to `main`): `update.xml` with `<tag>stable</tag>`, download points to GitHub Release asset
4. **Frozen Snapshot** (`version/XX.YY.ZZ`): immutable, never force-pushed
### Health Checks
The `repo_health.yml` workflow verifies on every commit:
- `update.xml` exists in the repository root
- XML manifest exists with `<extension>` tag
- `<version>`, `<name>`, `<author>`, `<namespace>` tags present
- Extension `type` attribute is valid
- Language `.ini` files exist
- `index.html` directory listing protection in `src/`, `src/admin/`, `src/site/`
---
*Managed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). See [docs/workflows/update-server.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/workflows/update-server.md) for the full specification.*