From 5dcbddb9e2603ddbf6344f6a6a380207bece494f Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 11 May 2026 21:14:56 +0000 Subject: [PATCH 1/5] chore: sync auto-release.yml from MokoGalleryCalendar [skip ci] --- .gitea/workflows/auto-release.yml | 245 +++++++++++++++++++++++------- 1 file changed, 191 insertions(+), 54 deletions(-) diff --git a/.gitea/workflows/auto-release.yml b/.gitea/workflows/auto-release.yml index aba9b12..279bc5e 100644 --- a/.gitea/workflows/auto-release.yml +++ b/.gitea/workflows/auto-release.yml @@ -151,22 +151,22 @@ jobs: sed -i "s|[^<]*|${TODAY}|" "$MANIFEST" fi - # Promote [Unreleased] section in CHANGELOG.md to new version if [ -f "CHANGELOG.md" ] && grep -qi "Unreleased" CHANGELOG.md; then sed -i "s|## \[Unreleased\]|## [${VERSION}] --- ${TODAY}|" CHANGELOG.md sed -i "s|## Unreleased|## [${VERSION}] --- ${TODAY}|" CHANGELOG.md sed -i "2i ## [Unreleased]" CHANGELOG.md - sed -i "3i \ " CHANGELOG.md + sed -i "3i \\ " CHANGELOG.md echo "CHANGELOG promoted to [${VERSION}]" fi + # Commit and push git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.name "gitea-actions[bot]" git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git add -A git diff --cached --quiet || { - git commit -m "chore(version): bump ${CURRENT} → ${VERSION} (minor) [skip ci]" + git commit -m "chore(version): bump ${CURRENT} → ${VERSION} [skip ci]" git push origin HEAD:main 2>&1 } @@ -320,6 +320,7 @@ jobs: # -- STEP 5: Write updates.xml (Joomla update server) --------------------- - name: "Step 5: Write updates.xml" + id: updates if: >- steps.version.outputs.skip != 'true' && steps.check.outputs.already_released != 'true' @@ -343,20 +344,44 @@ jobs: TARGET_PLATFORM=$(sed -n 's/.*\(\).*/\1/p' "$MANIFEST" | head -1) PHP_MINIMUM=$(sed -n 's/.*\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1) + # If EXT_NAME is a language key (e.g. PLG_SYSTEM_MOKOJGDPC), resolve from .ini + if echo "$EXT_NAME" | grep -qE '^[A-Z_]+$'; then + INI_NAME=$(find . -name "*.sys.ini" -path "*/en-GB/*" -exec grep -h "^${EXT_NAME}=" {} \; 2>/dev/null | head -1 | cut -d'"' -f2) + [ -z "$INI_NAME" ] && INI_NAME=$(find . -name "*.sys.ini" -exec grep -h "^${EXT_NAME}=" {} \; 2>/dev/null | head -1 | cut -d'"' -f2) + [ -n "$INI_NAME" ] && EXT_NAME="$INI_NAME" + fi + # Fallbacks [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}" [ -z "$EXT_TYPE" ] && EXT_TYPE="component" # Derive element if not in manifest: - # 1. Try XML filename (e.g. mokowaas.xml → mokowaas) - # 2. Fall back to repo name (lowercased) + # 1. plugin="xxx" attribute (plugins) + # 2. module="xxx" attribute (modules) + # 3. XML filename (components, packages) + # 4. Repo name fallback (templates, anything else) if [ -z "$EXT_ELEMENT" ]; then - EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]') + EXT_ELEMENT=$(sed -n 's/.*plugin="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) + fi + if [ -z "$EXT_ELEMENT" ]; then + EXT_ELEMENT=$(sed -n 's/.*module="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) + fi + if [ -z "$EXT_ELEMENT" ]; then + FNAME=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]') # If filename is generic (templateDetails, manifest), use repo name - case "$EXT_ELEMENT" in - templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; + case "$FNAME" in + templatedetails|manifest) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; + *) EXT_ELEMENT="$FNAME" ;; esac fi + # Final fallback + [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') + + # Save for Steps 7, 8, 8b + echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT" + echo "ext_name=${EXT_NAME}" >> "$GITHUB_OUTPUT" + echo "ext_type=${EXT_TYPE}" >> "$GITHUB_OUTPUT" + echo "ext_folder=${EXT_FOLDER}" >> "$GITHUB_OUTPUT" # Build client tag: plugins and frontend modules need site CLIENT_TAG="" @@ -383,7 +408,18 @@ jobs: PHP_TAG="${PHP_MINIMUM}" fi - DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${EXT_ELEMENT}-${VERSION}.zip" + # Build TYPE_PREFIX for download URL + TYPE_PREFIX="" + case "${EXT_TYPE}" in + plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;; + module) TYPE_PREFIX="mod_" ;; + component) TYPE_PREFIX="com_" ;; + template) TYPE_PREFIX="tpl_" ;; + library) TYPE_PREFIX="lib_" ;; + package) TYPE_PREFIX="pkg_" ;; + esac + + DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip" INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/stable" # -- Build update entry for a given stability tag @@ -478,21 +514,32 @@ jobs: MAJOR="${{ steps.version.outputs.major }}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - # Auto-detect extension element for release naming - MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1) - EXT_ELEMENT="" - if [ -n "$MANIFEST" ]; then - EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1) - [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]') - case "$EXT_ELEMENT" in templatedetails|manifest) EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; esac - else + # Reuse metadata from Step 5 (single source of truth) + EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}" + EXT_NAME="${{ steps.updates.outputs.ext_name }}" + EXT_TYPE="${{ steps.updates.outputs.ext_type }}" + EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}" + + # Fallbacks if Step 5 was skipped + if [ -z "$EXT_ELEMENT" ]; then EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') fi + [ -z "$EXT_NAME" ] && EXT_NAME="${GITEA_REPO}" NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null) [ -z "$NOTES" ] && NOTES="Release ${VERSION}" - RELEASE_NAME="${EXT_ELEMENT} ${VERSION} (stable)" + # Build release name: "Pretty Name VERSION (type_element-VERSION)" + TYPE_PREFIX="" + case "${EXT_TYPE}" in + plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;; + module) TYPE_PREFIX="mod_" ;; + component) TYPE_PREFIX="com_" ;; + template) TYPE_PREFIX="tpl_" ;; + library) TYPE_PREFIX="lib_" ;; + package) TYPE_PREFIX="pkg_" ;; + esac + RELEASE_NAME="${EXT_NAME} ${VERSION} (${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION})" # Delete existing release if present (overwrite, not append) EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ @@ -519,21 +566,6 @@ jobs: }))")" echo "Release created: ${RELEASE_NAME}" >> $GITHUB_STEP_SUMMARY - - name: "Step 7.5: Minify assets" - if: >- - steps.version.outputs.skip != 'true' - run: | - npm install --no-save terser clean-css 2>/dev/null || true - MINIFY="" - for p in "../moko-platform/build/minify.js" "scripts/minify.js"; do - [ -f "$p" ] && MINIFY="$p" && break - done - if [ -n "$MINIFY" ]; then - node "$MINIFY" src - else - echo "No minify script found — skipping" - fi - # -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------ - name: "Step 8: Build Joomla package and update checksum" if: >- @@ -557,9 +589,28 @@ jobs: MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1 || true) [ -z "$MANIFEST" ] && exit 0 - EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml) - ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip" - TAR_NAME="${EXT_ELEMENT}-${VERSION}.tar.gz" + # Reuse element from Step 5, with same fallback chain + EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}" + if [ -z "$EXT_ELEMENT" ]; then + EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1) + [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(sed -n 's/.*plugin="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null | head -1) + [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]') + [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') + fi + # ZIP name: type_folder_element-VERSION (e.g. plg_system_mokojgdpc-01.01.00.zip) + EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) + EXT_FOLDER=$(sed -n 's/.*]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) + TYPE_PREFIX="" + case "${EXT_TYPE}" in + plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;; + module) TYPE_PREFIX="mod_" ;; + component) TYPE_PREFIX="com_" ;; + template) TYPE_PREFIX="tpl_" ;; + library) TYPE_PREFIX="lib_" ;; + package) TYPE_PREFIX="pkg_" ;; + esac + ZIP_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip" + TAR_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.tar.gz" # -- Build install packages from src/ ---------------------------- SOURCE_DIR="src" @@ -668,24 +719,23 @@ jobs: GA_TOKEN="${{ secrets.GA_TOKEN }}" API="${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}" - CONTENT=$(base64 -w0 updates.xml) - for BRANCH in main dev; do - BRANCH_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \ - "${API}/contents/updates.xml?ref=${BRANCH}" | jq -r '.sha // empty' 2>/dev/null) - [ -z "$BRANCH_SHA" ] && echo "SKIP: no updates.xml on ${BRANCH}" && continue - curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \ - -H "Content-Type: application/json" \ - "${API}/contents/updates.xml" \ - -d "$(jq -n \ - --arg content "$CONTENT" \ - --arg sha "$BRANCH_SHA" \ - --arg msg "chore: sync updates.xml ${VERSION} [skip ci]" \ - --arg branch "$BRANCH" \ - '{content: $content, sha: $sha, message: $msg, branch: $branch}' - )" > /dev/null 2>&1 \ - && echo "updates.xml synced to ${BRANCH}" \ - || echo "WARNING: failed to sync updates.xml to ${BRANCH}" - done + FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \ + "${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty') + + if [ -n "$FILE_SHA" ]; then + CONTENT=$(base64 -w0 updates.xml) + curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \ + -H "Content-Type: application/json" \ + "${API}/contents/updates.xml" \ + -d "$(jq -n \ + --arg content "$CONTENT" \ + --arg sha "$FILE_SHA" \ + --arg msg "chore: sync updates.xml ${VERSION} [skip ci]" \ + --arg branch "main" \ + '{content: $content, sha: $sha, message: $msg, branch: $branch}' + )" > /dev/null 2>&1 \ + && echo "updates.xml synced to main via API" \ + || echo "WARNING: failed to sync updates.xml to main" else echo "WARNING: could not get updates.xml SHA from main" fi @@ -700,6 +750,73 @@ jobs: echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY echo "| Download | [${ZIP_NAME}](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}) |" >> $GITHUB_STEP_SUMMARY + # -- STEP 8b: Update release description with changelog + SHA ---------------- + - name: "Step 8b: Update release body with changelog and SHA" + if: steps.version.outputs.skip != 'true' + run: | + VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + RELEASE_TAG="${{ steps.version.outputs.release_tag }}" + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}" + EXT_TYPE="${{ steps.updates.outputs.ext_type }}" + EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}" + + # Build TYPE_PREFIX to match Step 8's ZIP naming + TYPE_PREFIX="" + case "${EXT_TYPE}" in + plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;; + module) TYPE_PREFIX="mod_" ;; + component) TYPE_PREFIX="com_" ;; + template) TYPE_PREFIX="tpl_" ;; + library) TYPE_PREFIX="lib_" ;; + package) TYPE_PREFIX="pkg_" ;; + esac + ZIP_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip" + TAR_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.tar.gz" + + # Get SHA from the built files + SHA256_ZIP="" + [ -f "/tmp/${ZIP_NAME}" ] && SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1) + SHA256_TAR="" + [ -f "/tmp/${TAR_NAME}" ] && SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1) + + # Extract latest changelog entry (strip the ## header to avoid duplicate) + CHANGELOG="" + if [ -f "CHANGELOG.md" ]; then + CHANGELOG=$(sed -n "/^## \[*${VERSION}/,/^## \[*[0-9]/p" CHANGELOG.md | sed '$d' | sed '1d') + [ -z "$CHANGELOG" ] && CHANGELOG=$(sed -n '/^## /,/^## /p' CHANGELOG.md | sed '$d' | sed '1d' | head -30) + fi + + # Build release body (single header, no duplicate from changelog) + BODY="## ${VERSION} ($(date +%Y-%m-%d))\n\n" + if [ -n "$CHANGELOG" ]; then + BODY="${BODY}${CHANGELOG}\n\n" + fi + BODY="${BODY}---\n\n### Checksums\n\n" + BODY="${BODY}| File | SHA-256 |\n|------|--------|\n" + [ -n "$SHA256_ZIP" ] && BODY="${BODY}| \`${ZIP_NAME}\` | \`${SHA256_ZIP}\` |\n" + [ -n "$SHA256_TAR" ] && BODY="${BODY}| \`${TAR_NAME}\` | \`${SHA256_TAR}\` |\n" + + # Get release ID and update body + RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ + "${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null | \ + python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) + + if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "None" ]; then + python3 -c " + import json, urllib.request + body = '''$(printf '%b' "$BODY")''' + data = json.dumps({'body': body}).encode() + req = urllib.request.Request( + '${API_BASE}/releases/${RELEASE_ID}', + data=data, + headers={'Authorization': 'token ${{ secrets.GA_TOKEN }}', 'Content-Type': 'application/json'}, + method='PATCH' + ) + urllib.request.urlopen(req) + " 2>/dev/null && echo "Release body updated with changelog + SHA" >> $GITHUB_STEP_SUMMARY + fi + # -- STEP 9: Mirror to GitHub (stable only) -------------------------------- - name: "Step 9: Mirror release to GitHub" if: >- @@ -789,6 +906,26 @@ jobs: done echo "Cleaned up ${DELETED} pre-release channel(s)" >> $GITHUB_STEP_SUMMARY + # -- STEP 11: Reset dev branch from main ------------------------------------ + - name: "Step 11: Delete and recreate dev branch from main" + if: steps.version.outputs.skip != 'true' + continue-on-error: true + run: | + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + TOKEN="${{ secrets.GA_TOKEN }}" + + # Delete dev branch + curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \ + "${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch" + + # Recreate dev from main (now includes version bump + changelog promotion) + curl -sf -X POST -H "Authorization: token ${TOKEN}" \ + -H "Content-Type: application/json" \ + "${API_BASE}/branches" \ + -d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main" + + echo "Dev branch reset from main (keeps dev ahead after release)" >> $GITHUB_STEP_SUMMARY + # -- Summary -------------------------------------------------------------- - name: Pipeline Summary if: always() -- 2.52.0 From b5003ebd489864624a75607dded7fe58e8ea1eb1 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 11 May 2026 22:42:25 -0500 Subject: [PATCH 2/5] chore: move workflows to .mokogitea/workflows/ [skip ci] --- {.gitea => .mokogitea}/workflows/auto-release.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {.gitea => .mokogitea}/workflows/auto-release.yml (100%) diff --git a/.gitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml similarity index 100% rename from .gitea/workflows/auto-release.yml rename to .mokogitea/workflows/auto-release.yml -- 2.52.0 From e0b96594b73d0278849d544ff91fdbea20850517 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 11 May 2026 22:44:58 -0500 Subject: [PATCH 3/5] chore: move all workflows to .mokogitea/workflows/ [skip ci] --- {.gitea => .mokogitea}/workflows/cascade-dev.yml | 0 {.gitea => .mokogitea}/workflows/ci-joomla.yml | 0 {.gitea => .mokogitea}/workflows/cleanup.yml | 0 {.gitea => .mokogitea}/workflows/deploy-manual.yml | 0 {.gitea => .mokogitea}/workflows/gitleaks.yml | 0 {.gitea => .mokogitea}/workflows/notify.yml | 0 {.gitea => .mokogitea}/workflows/pr-branch-check.yml | 0 {.gitea => .mokogitea}/workflows/pr-check.yml | 0 {.gitea => .mokogitea}/workflows/pre-release.yml | 0 {.gitea => .mokogitea}/workflows/repo-health.yml | 0 {.gitea => .mokogitea}/workflows/security-audit.yml | 0 {.gitea => .mokogitea}/workflows/update-server.yml | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename {.gitea => .mokogitea}/workflows/cascade-dev.yml (100%) rename {.gitea => .mokogitea}/workflows/ci-joomla.yml (100%) rename {.gitea => .mokogitea}/workflows/cleanup.yml (100%) rename {.gitea => .mokogitea}/workflows/deploy-manual.yml (100%) rename {.gitea => .mokogitea}/workflows/gitleaks.yml (100%) rename {.gitea => .mokogitea}/workflows/notify.yml (100%) rename {.gitea => .mokogitea}/workflows/pr-branch-check.yml (100%) rename {.gitea => .mokogitea}/workflows/pr-check.yml (100%) rename {.gitea => .mokogitea}/workflows/pre-release.yml (100%) rename {.gitea => .mokogitea}/workflows/repo-health.yml (100%) rename {.gitea => .mokogitea}/workflows/security-audit.yml (100%) rename {.gitea => .mokogitea}/workflows/update-server.yml (100%) diff --git a/.gitea/workflows/cascade-dev.yml b/.mokogitea/workflows/cascade-dev.yml similarity index 100% rename from .gitea/workflows/cascade-dev.yml rename to .mokogitea/workflows/cascade-dev.yml diff --git a/.gitea/workflows/ci-joomla.yml b/.mokogitea/workflows/ci-joomla.yml similarity index 100% rename from .gitea/workflows/ci-joomla.yml rename to .mokogitea/workflows/ci-joomla.yml diff --git a/.gitea/workflows/cleanup.yml b/.mokogitea/workflows/cleanup.yml similarity index 100% rename from .gitea/workflows/cleanup.yml rename to .mokogitea/workflows/cleanup.yml diff --git a/.gitea/workflows/deploy-manual.yml b/.mokogitea/workflows/deploy-manual.yml similarity index 100% rename from .gitea/workflows/deploy-manual.yml rename to .mokogitea/workflows/deploy-manual.yml diff --git a/.gitea/workflows/gitleaks.yml b/.mokogitea/workflows/gitleaks.yml similarity index 100% rename from .gitea/workflows/gitleaks.yml rename to .mokogitea/workflows/gitleaks.yml diff --git a/.gitea/workflows/notify.yml b/.mokogitea/workflows/notify.yml similarity index 100% rename from .gitea/workflows/notify.yml rename to .mokogitea/workflows/notify.yml diff --git a/.gitea/workflows/pr-branch-check.yml b/.mokogitea/workflows/pr-branch-check.yml similarity index 100% rename from .gitea/workflows/pr-branch-check.yml rename to .mokogitea/workflows/pr-branch-check.yml diff --git a/.gitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml similarity index 100% rename from .gitea/workflows/pr-check.yml rename to .mokogitea/workflows/pr-check.yml diff --git a/.gitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml similarity index 100% rename from .gitea/workflows/pre-release.yml rename to .mokogitea/workflows/pre-release.yml diff --git a/.gitea/workflows/repo-health.yml b/.mokogitea/workflows/repo-health.yml similarity index 100% rename from .gitea/workflows/repo-health.yml rename to .mokogitea/workflows/repo-health.yml diff --git a/.gitea/workflows/security-audit.yml b/.mokogitea/workflows/security-audit.yml similarity index 100% rename from .gitea/workflows/security-audit.yml rename to .mokogitea/workflows/security-audit.yml diff --git a/.gitea/workflows/update-server.yml b/.mokogitea/workflows/update-server.yml similarity index 100% rename from .gitea/workflows/update-server.yml rename to .mokogitea/workflows/update-server.yml -- 2.52.0 From fc8f039741d819b6a15fb41665a8c6f845aac10b Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 11 May 2026 22:46:17 -0500 Subject: [PATCH 4/5] chore: remove .gitea/ and .github/ directories [skip ci] --- .gitea/.moko-platform | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 .gitea/.moko-platform diff --git a/.gitea/.moko-platform b/.gitea/.moko-platform deleted file mode 100644 index 388e9f8..0000000 --- a/.gitea/.moko-platform +++ /dev/null @@ -1,25 +0,0 @@ - - - - - Template-Joomla - MokoConsulting - Unified Joomla extension scaffolding templates — plugin, template, module, component, package, library - GNU General Public License v3 - - - template - 04.07.00 - https://git.mokoconsulting.tech/MokoConsulting/moko-platform - 2026-05-10T19:51:10+00:00 - - - Markdown - template - src/ - - -- 2.52.0 From 6544b6dae7695beb36239cc621a2d9408a673111 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 11 May 2026 23:01:35 -0500 Subject: [PATCH 5/5] feat(workflows): merge branch policy into pr-check.yml - Combine pr-branch-check.yml into pr-check.yml as a separate job - Add platform-aware validation (joomla/dolibarr/generic) - Delete standalone pr-branch-check.yml Co-Authored-By: Claude Opus 4.6 (1M context) --- .mokogitea/workflows/pr-branch-check.yml | 90 ----------- .mokogitea/workflows/pr-check.yml | 182 +++++++++++++++++------ 2 files changed, 135 insertions(+), 137 deletions(-) delete mode 100644 .mokogitea/workflows/pr-branch-check.yml diff --git a/.mokogitea/workflows/pr-branch-check.yml b/.mokogitea/workflows/pr-branch-check.yml deleted file mode 100644 index 183a291..0000000 --- a/.mokogitea/workflows/pr-branch-check.yml +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# Enforces branch merge policy: -# feature/* → dev only -# fix/* → dev only -# hotfix/* → dev or main (emergency) -# dev → main only -# alpha/* → dev only -# beta/* → dev only -# rc/* → main only - -name: "Universal: Branch Policy Check" - -on: - pull_request: - types: [opened, synchronize, reopened, edited] - -jobs: - check-target: - name: Verify merge target - runs-on: ubuntu-latest - steps: - - name: Check branch policy - run: | - HEAD="${{ github.head_ref }}" - BASE="${{ github.base_ref }}" - - echo "PR: ${HEAD} → ${BASE}" - - ALLOWED=true - REASON="" - - case "$HEAD" in - feature/*|feat/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Feature branches must target 'dev', not '${BASE}'" - fi - ;; - fix/*|bugfix/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Fix branches must target 'dev', not '${BASE}'" - fi - ;; - hotfix/*) - if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'" - fi - ;; - alpha/*|beta/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Pre-release branches must target 'dev', not '${BASE}'" - fi - ;; - rc/*) - if [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Release candidate branches must target 'main', not '${BASE}'" - fi - ;; - dev) - if [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Dev branch can only merge into 'main', not '${BASE}'" - fi - ;; - esac - - if [ "$ALLOWED" = false ]; then - echo "::error::${REASON}" - echo "" - echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "${REASON}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY - echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - echo "Branch policy: OK (${HEAD} → ${BASE})" - echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml index dc22a56..88e1884 100644 --- a/.mokogitea/workflows/pr-check.yml +++ b/.mokogitea/workflows/pr-check.yml @@ -5,18 +5,16 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: MokoStandards.CI -# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards -# PATH: /.gitea/workflows/pr-check.yml -# VERSION: 01.00.00 -# BRIEF: PR gate — validates code quality and manifest before merge to main +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API +# PATH: /templates/workflows/universal/pr-check.yml.template +# VERSION: 05.00.00 +# BRIEF: PR gate — branch policy + code validation before merge name: "Universal: PR Check" on: pull_request: - branches: - - main - types: [opened, synchronize, reopened] + types: [opened, synchronize, reopened, edited] permissions: contents: read @@ -26,6 +24,79 @@ env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: + # ── Branch Policy ────────────────────────────────────────────────────── + branch-policy: + name: Branch Policy + runs-on: ubuntu-latest + steps: + - name: Check branch merge target + run: | + HEAD="${{ github.head_ref }}" + BASE="${{ github.base_ref }}" + + echo "PR: ${HEAD} → ${BASE}" + + ALLOWED=true + REASON="" + + case "$HEAD" in + feature/*|feat/*) + if [ "$BASE" != "dev" ]; then + ALLOWED=false + REASON="Feature branches must target 'dev', not '${BASE}'" + fi + ;; + fix/*|bugfix/*) + if [ "$BASE" != "dev" ]; then + ALLOWED=false + REASON="Fix branches must target 'dev', not '${BASE}'" + fi + ;; + hotfix/*) + if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then + ALLOWED=false + REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'" + fi + ;; + alpha/*|beta/*) + if [ "$BASE" != "dev" ]; then + ALLOWED=false + REASON="Pre-release branches must target 'dev', not '${BASE}'" + fi + ;; + rc/*) + if [ "$BASE" != "main" ]; then + ALLOWED=false + REASON="Release candidate branches must target 'main', not '${BASE}'" + fi + ;; + dev) + if [ "$BASE" != "main" ]; then + ALLOWED=false + REASON="Dev branch can only merge into 'main', not '${BASE}'" + fi + ;; + esac + + if [ "$ALLOWED" = false ]; then + echo "::error::${REASON}" + echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "${REASON}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY + echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY + exit 1 + fi + + echo "Branch policy: OK (${HEAD} → ${BASE})" + echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY + + # ── Code Validation ──────────────────────────────────────────────────── validate: name: Validate PR runs-on: ubuntu-latest @@ -34,7 +105,15 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Detect platform + id: platform + run: | + PLATFORM=$(cat .moko-platform 2>/dev/null | tr -d '[:space:]') + [ -z "$PLATFORM" ] && PLATFORM="generic" + echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT" + - name: Setup PHP + if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' run: | if ! command -v php &> /dev/null; then sudo apt-get update -qq @@ -42,65 +121,74 @@ jobs: fi - name: PHP syntax check + if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' run: | - echo "=== PHP Lint ===" ERRORS=0 while IFS= read -r -d '' file; do if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then ERRORS=$((ERRORS + 1)) fi done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0) - echo "Checked files, errors: ${ERRORS}" + echo "PHP lint: ${ERRORS} error(s)" [ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; } - - name: Validate Joomla manifest + - name: Validate platform manifest run: | - echo "=== Manifest Validation ===" - MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1) - if [ -z "$MANIFEST" ]; then - echo "::warning::No Joomla manifest found" - exit 0 - fi - echo "Manifest: ${MANIFEST}" + PLATFORM="${{ steps.platform.outputs.platform }}" + case "$PLATFORM" in + joomla) + MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1) + if [ -z "$MANIFEST" ]; then + echo "::warning::No Joomla manifest found (WaaS site)" + exit 0 + fi + echo "Manifest: ${MANIFEST}" + if command -v php &> /dev/null; then + php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; } + fi + for ELEMENT in name version description; do + grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; } + done + echo "Joomla manifest valid" + ;; + dolibarr) + MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1) + if [ -z "$MOD_FILE" ]; then + echo "::error::No mod*.class.php found" + exit 1 + fi + echo "Dolibarr module: ${MOD_FILE}" + ;; + *) + echo "Generic platform — no manifest validation" + ;; + esac - # Check well-formed XML - if ! php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}"; then - echo "::error::Manifest XML is malformed" - exit 1 - fi - - # Check required elements - for ELEMENT in name version description; do - if ! grep -q "<${ELEMENT}>" "$MANIFEST"; then - echo "::error::Missing <${ELEMENT}> in manifest" - exit 1 - fi - done - echo "Manifest valid" - - - name: Check updates.xml format + - name: Check update stream format run: | - if [ ! -f "updates.xml" ]; then - echo "No updates.xml — skipping" - exit 0 - fi - echo "=== updates.xml Validation ===" - if ! php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}"; then - echo "::error::updates.xml is malformed" - exit 1 - fi - echo "updates.xml valid" + PLATFORM="${{ steps.platform.outputs.platform }}" + case "$PLATFORM" in + joomla) + if [ -f "updates.xml" ]; then + if command -v php &> /dev/null; then + php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; } + fi + echo "updates.xml valid" + fi + ;; + dolibarr) + [ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt" + ;; + esac - - name: Verify package builds + - name: Verify package source run: | - echo "=== Package Build Test ===" SOURCE_DIR="src" [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" if [ ! -d "$SOURCE_DIR" ]; then echo "::warning::No src/ or htdocs/ directory" exit 0 fi - # Dry-run: ensure zip would succeed FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l) - echo "Source contains ${FILE_COUNT} files — package will build" + echo "Source: ${FILE_COUNT} files" [ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; } -- 2.52.0