From ffa50f646057f3d739368c99d293071b22061bee Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Tue, 26 May 2026 17:41:23 +0000
Subject: [PATCH 1/6] fix: updates.xml all channels 02.11.00 [skip ci]
---
updates.xml | 55 ++++++++++++++++++-----------------------------------
1 file changed, 18 insertions(+), 37 deletions(-)
diff --git a/updates.xml b/updates.xml
index 293258a2..abc3bf8c 100644
--- a/updates.xml
+++ b/updates.xml
@@ -1,7 +1,7 @@
@@ -10,12 +10,12 @@
MokoWaaS update
pkg_mokowaas
package
- 02.09.00
+ 02.11.00
site
stable
https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.09.00.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.11.00.zip
Moko Consulting
@@ -26,12 +26,12 @@
MokoWaaS update
pkg_mokowaas
package
- 02.09.00
+ 02.11.00
site
rc
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.11.00.zip
Moko Consulting
@@ -42,12 +42,12 @@
MokoWaaS update
pkg_mokowaas
package
- 02.09.00
+ 02.11.00
site
beta
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.11.00.zip
Moko Consulting
@@ -58,12 +58,12 @@
MokoWaaS update
pkg_mokowaas
package
- 02.09.00
+ 02.11.00
site
alpha
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.11.00.zip
Moko Consulting
@@ -74,12 +74,12 @@
MokoWaaS update
pkg_mokowaas
package
- 02.09.00
+ 02.11.00
site
dev
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.11.00.zip
Moko Consulting
@@ -92,33 +92,14 @@
plugin
system
site
- 02.09.00
+ 02.11.00
stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.11.00.zip
Moko Consulting
https://mokoconsulting.tech
-
-
- MokoWaaS
- MokoWaaS development build.
- pkg_mokowaas
- package
- 02.10.03
- 2026-05-26
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
-
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.10.03-dev.zip
-
- ddf956ccf7f33d37b923c501bd7a58a38614b3566500c57b24eee0f5ac0201b9
- development
- Moko Consulting
- https://mokoconsulting.tech
-
-
-
--
2.52.0
From 49a741883008f614dc16f4f694b12bd0bdf68358 Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Tue, 26 May 2026 18:42:43 +0000
Subject: [PATCH 2/6] chore: sync updates.xml from [skip ci]
---
updates.xml | 55 +++++++++++++++++++++++++++++++++++------------------
1 file changed, 37 insertions(+), 18 deletions(-)
diff --git a/updates.xml b/updates.xml
index abc3bf8c..34b00b09 100644
--- a/updates.xml
+++ b/updates.xml
@@ -1,7 +1,7 @@
@@ -10,12 +10,12 @@
MokoWaaS update
pkg_mokowaas
package
- 02.11.00
+ 02.09.00
site
stable
https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.11.00.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.09.00.zip
Moko Consulting
@@ -26,12 +26,12 @@
MokoWaaS update
pkg_mokowaas
package
- 02.11.00
+ 02.09.00
site
rc
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.11.00.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
Moko Consulting
@@ -42,12 +42,12 @@
MokoWaaS update
pkg_mokowaas
package
- 02.11.00
+ 02.09.00
site
beta
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.11.00.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
Moko Consulting
@@ -58,12 +58,12 @@
MokoWaaS update
pkg_mokowaas
package
- 02.11.00
+ 02.09.00
site
alpha
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.11.00.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
Moko Consulting
@@ -74,12 +74,12 @@
MokoWaaS update
pkg_mokowaas
package
- 02.11.00
+ 02.09.00
site
dev
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.11.00.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
Moko Consulting
@@ -92,14 +92,33 @@
plugin
system
site
- 02.11.00
+ 02.09.00
stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.11.00.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
Moko Consulting
https://mokoconsulting.tech
+
+
+ MokoWaaS
+ MokoWaaS development build.
+ pkg_mokowaas
+ package
+ 02.10.04
+ 2026-05-26
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
+
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.10.04-dev.zip
+
+ 19d135c23a9ade1948547ed955c1b9a0dce857e1d6b2e0a62e20fecc268b5523
+ development
+ Moko Consulting
+ https://mokoconsulting.tech
+
+
+
--
2.52.0
From fa31455619e3f8f1ed7b72c0000824a017a5c00f Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Tue, 26 May 2026 18:52:45 +0000
Subject: [PATCH 3/6] chore(ci): update auto-release.yml from moko-platform
[skip ci]
---
.mokogitea/workflows/auto-release.yml | 1423 +++++++++++++------------
1 file changed, 757 insertions(+), 666 deletions(-)
diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml
index efb25373..eca25d89 100644
--- a/.mokogitea/workflows/auto-release.yml
+++ b/.mokogitea/workflows/auto-release.yml
@@ -1,666 +1,757 @@
-# Copyright (C) 2026 Moko Consulting
-#
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# FILE INFORMATION
-# DEFGROUP: Gitea.Workflow
-# INGROUP: moko-platform.Release
-# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
-# PATH: /templates/workflows/universal/auto-release.yml.template
-# VERSION: 05.00.00
-# BRIEF: Universal build & release � detects platform from manifest.xml
-#
-# +========================================================================+
-# | UNIVERSAL BUILD & RELEASE PIPELINE |
-# +========================================================================+
-# | |
-# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
-# | |
-# | Platform-specific: |
-# | joomla: XML manifest, updates.xml, type-prefixed packages |
-# | dolibarr: mod*.class.php, update.txt, dev version reset |
-# | generic: README-only, no update stream |
-# | |
-# +========================================================================+
-
-name: "Universal: Build & Release"
-
-on:
- pull_request:
- types: [closed]
- branches:
- - main
- paths:
- - 'src/**'
- - 'htdocs/**'
- workflow_dispatch:
-
-env:
- FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
- GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
- GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
- GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
-
-permissions:
- contents: write
-
-jobs:
- release:
- name: Build & Release Pipeline
- runs-on: release
- if: >-
- github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- with:
- token: ${{ secrets.GA_TOKEN }}
- fetch-depth: 0
-
- - name: Setup moko-platform tools
- env:
- MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
- MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
- COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
- run: |
- # Ensure PHP + Composer are available
- if ! command -v composer &> /dev/null; then
- sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
- fi
- git clone --depth 1 --branch main --quiet \
- "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
- /tmp/moko-platform-api
- cd /tmp/moko-platform-api
- composer install --no-dev --no-interaction --quiet
-
-
- # -- PLATFORM DETECTION ---------------------------------------------------
- - name: Detect platform
- id: platform
- run: |
- php /tmp/moko-platform-api/cli/manifest_read.php --path . --github-output
- MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1 || true)
- MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1 || true)
- echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
- echo "mod_file=${MOD_FILE}" >> "$GITHUB_OUTPUT"
-
- - name: "Step 1: Read version"
- id: version
- run: |
- VERSION=$(php /tmp/moko-platform-api/cli/version_read.php --path .)
- if [ -z "$VERSION" ]; then
- echo "::error::No VERSION in README.md"
- echo "skip=true" >> "$GITHUB_OUTPUT"
- exit 0
- fi
- MAJOR=$(echo "$VERSION" | cut -d. -f1)
- echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- echo "release_tag=stable" >> "$GITHUB_OUTPUT"
- echo "skip=false" >> "$GITHUB_OUTPUT"
- echo "branch=main" >> "$GITHUB_OUTPUT"
-
- - name: "Step 1b: Bump version"
- id: bump
- if: steps.version.outputs.skip != 'true'
- run: |
- MOKO_API="/tmp/moko-platform-api/cli"
- BUMP=$(php ${MOKO_API}/version_bump.php --path . --minor)
- VERSION=$(echo "$BUMP" | grep -oP '\d{2}\.\d{2}\.\d{2}$' || true)
- [ -z "$VERSION" ] && VERSION=$(php ${MOKO_API}/version_read.php --path .)
- echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- echo "Bumped to: ${VERSION}"
-
- - name: Check if already released
- if: steps.version.outputs.skip != 'true'
- id: check
- run: |
- TAG="${{ steps.version.outputs.release_tag }}"
- BRANCH="${{ steps.version.outputs.branch }}"
-
- 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"
-
- # Tag and branch may persist across patch releases — never skip
- echo "already_released=false" >> "$GITHUB_OUTPUT"
-
- # -- SANITY CHECKS -------------------------------------------------------
- - name: "Sanity: Pre-release validation"
- if: >-
- steps.version.outputs.skip != 'true' &&
- steps.check.outputs.already_released != 'true'
- run: |
- VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- ERRORS=0
-
- PLATFORM="${{ steps.platform.outputs.platform }}"
- MANIFEST="${{ steps.platform.outputs.manifest }}"
- MOD_FILE="${{ steps.platform.outputs.mod_file }}"
- echo "## Pre-Release Sanity Checks (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
- echo "" >> $GITHUB_STEP_SUMMARY
-
- # -- Version drift check (must pass before release) --------
- README_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1)
- if [ "$README_VER" != "$VERSION" ]; then
- echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
- ERRORS=$((ERRORS+1))
- else
- echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
- fi
-
- # Check CHANGELOG version matches
- CL_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' CHANGELOG.md 2>/dev/null | head -1)
- if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then
- echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
- ERRORS=$((ERRORS+1))
- fi
-
- # Check composer.json version if present
- if [ -f "composer.json" ]; then
- COMP_VER=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' composer.json 2>/dev/null | head -1)
- if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then
- echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
- ERRORS=$((ERRORS+1))
- fi
- fi
-
- # Common checks
- if [ ! -f "LICENSE" ]; then
- echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
- ERRORS=$((ERRORS+1))
- else
- echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY
- fi
-
- if [ ! -d "src" ] && [ ! -d "htdocs" ]; then
- echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY
- else
- echo "- Source directory present" >> $GITHUB_STEP_SUMMARY
- fi
-
- # -- Platform-specific checks --------
- case "$PLATFORM" in
- joomla)
- if [ -n "$MANIFEST" ]; then
- XML_VER=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
- if [ -n "$XML_VER" ] && [ "$XML_VER" != "$VERSION" ]; then
- echo "- Manifest drift: \`${XML_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
- ERRORS=$((ERRORS+1))
- else
- echo "- Manifest version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
- fi
- TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null)
- echo "- Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
- else
- echo "- No Joomla XML manifest (WaaS site)" >> $GITHUB_STEP_SUMMARY
- fi ;;
- dolibarr)
- if [ -n "$MOD_FILE" ]; then
- MOD_VER=$(sed -n "s/.*\\\$this->version = '\([^']*\)'.*/\1/p" "$MOD_FILE" 2>/dev/null | head -1)
- if [ -n "$MOD_VER" ] && [ "$MOD_VER" != "$VERSION" ]; then
- echo "- Module drift: \`${MOD_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
- ERRORS=$((ERRORS+1))
- else
- echo "- Module version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
- fi
- else
- echo "- No mod*.class.php found" >> $GITHUB_STEP_SUMMARY
- ERRORS=$((ERRORS+1))
- fi
- if [ ! -f "update.txt" ]; then
- echo "- Missing update.txt" >> $GITHUB_STEP_SUMMARY
- ERRORS=$((ERRORS+1))
- fi ;;
- *) echo "- Generic platform � no manifest checks" >> $GITHUB_STEP_SUMMARY ;;
- esac
-
- 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 or update version/XX.YY archive branch ---------------
- # Always runs — every version change on main archives to version/XX.YY
- - name: "Step 2: Version archive branch"
- if: steps.check.outputs.already_released != 'true'
- run: |
- BRANCH="${{ steps.version.outputs.branch }}"
- IS_MINOR="${{ steps.version.outputs.is_minor }}"
- PATCH="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- PATCH_NUM=$(echo "$PATCH" | awk -F. '{print $3}')
-
- # Check if branch exists
- if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
- git push origin HEAD:"$BRANCH" --force
- echo "Updated archive branch: ${BRANCH} (patch ${PATCH_NUM})" >> $GITHUB_STEP_SUMMARY
- else
- git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH"
- git push origin "$BRANCH" --force
- echo "Created archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
- fi
-
- # -- 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.bump.outputs.version || steps.version.outputs.version }}"
- php /tmp/moko-platform-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'
- run: |
- VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- php /tmp/moko-platform-api/cli/badge_update.php --path . --version "${VERSION}" 2>/dev/null || true
- php /tmp/moko-platform-api/cli/version_check.php --path . --fix 2>/dev/null || true
-
- - name: "Step 5: Write update stream"
- if: >-
- steps.version.outputs.skip != 'true' &&
- steps.platform.outputs.platform == 'joomla'
- run: |
- VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
-
- # Fetch latest updates.xml from main so preserve logic has all channels
- GA_TOKEN="${{ secrets.GA_TOKEN }}"
- API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
- curl -sf -H "Authorization: token ${GA_TOKEN}" \
- "${API}/contents/updates.xml?ref=main" 2>/dev/null | \
- python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin)['content']).decode())" \
- > updates.xml 2>/dev/null || true
-
- php /tmp/moko-platform-api/cli/updates_xml_build.php \
- --path . --version "${VERSION}" --stability stable \
- --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
- --github-output
-
- - name: Commit release changes
- if: >-
- steps.version.outputs.skip != 'true' &&
- steps.check.outputs.already_released != 'true'
- run: |
- if git diff --quiet && git diff --cached --quiet; then
- echo "No changes to commit"
- exit 0
- fi
- VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
- git config --local user.name "gitea-actions[bot]"
- # Set push URL with token for branch-protected repos
- git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- git add -A
- git commit -m "chore(release): build ${VERSION} [skip ci]" \
- --author="gitea-actions[bot] "
- git push -u origin HEAD
-
- # -- STEP 6: Create tag ---------------------------------------------------
- - name: "Step 6: Create git tag"
- if: >-
- steps.version.outputs.skip != 'true'
- run: |
- RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
- # Only create the major release tag if it doesn't exist yet
- if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
- git tag "$RELEASE_TAG"
- git push origin "$RELEASE_TAG"
- echo "Tag created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
- else
- echo "Tag ${RELEASE_TAG} already exists" >> $GITHUB_STEP_SUMMARY
- fi
- echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
-
- # -- STEP 7: Create or update Gitea Release --------------------------------
- - name: "Step 7: Gitea Release"
- if: >-
- steps.version.outputs.skip != 'true'
- run: |
- VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
- BRANCH="${{ steps.version.outputs.branch }}"
- MAJOR="${{ steps.version.outputs.major }}"
- API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
-
- # 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/moko-platform-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
- [ -z "$NOTES" ] && NOTES="Release ${VERSION}"
-
- # Build release name: "Pretty Name VERSION (type_element-VERSION)"
- # Strip existing type prefix to prevent duplication
- EXT_ELEMENT=$(echo "$EXT_ELEMENT" | sed -E 's/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)//')
- 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 }}" \
- "${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
- EXISTING_ID=$(echo "$EXISTING" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
-
- if [ -n "$EXISTING_ID" ]; then
- curl -sS -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- "${API_BASE}/releases/${EXISTING_ID}" 2>/dev/null || true
- curl -sS -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- "${API_BASE}/tags/${RELEASE_TAG}" 2>/dev/null || true
- echo "Deleted previous stable release (id: ${EXISTING_ID})"
- fi
-
- # Create fresh release
- curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- -H "Content-Type: application/json" \
- "${API_BASE}/releases" \
- -d "$(python3 -c "import json; print(json.dumps({
- 'tag_name': '${RELEASE_TAG}',
- 'name': '${RELEASE_NAME}',
- 'body': '''## ${VERSION} ($(date +%Y-%m-%d))\n${NOTES}''',
- 'target_commitish': '${BRANCH}'
- }))")"
- echo "Release created: ${RELEASE_NAME}" >> $GITHUB_STEP_SUMMARY
-
- # -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
- - name: "Step 8: Build package and update checksum"
- if: >-
- steps.version.outputs.skip != 'true'
- run: |
- VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
- REPO="${{ github.repository }}"
- API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
-
- # All ZIPs upload to the major release tag (vXX)
- RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- "${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
- RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
- if [ -z "$RELEASE_ID" ]; then
- echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
- exit 0
- fi
-
- # Find extension element name from manifest
- MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1 || true)
- [ -z "$MANIFEST" ] && exit 0
-
- # 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)
- # For packages, prefer over filename-derived element
- if [ "$EXT_TYPE" = "package" ]; then
- PKG_NAME=$(sed -n 's/.*\([^<]*\)<\/packagename>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
- [ -n "$PKG_NAME" ] && EXT_ELEMENT="$PKG_NAME"
- fi
- # Strip existing type prefix to prevent duplication (e.g. pkg_mokowaas → mokowaas)
- EXT_ELEMENT=$(echo "$EXT_ELEMENT" | sed -E 's/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)//')
- 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"
- [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
- [ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/"; exit 0; }
-
- # ZIP package (type-aware via moko-platform PHP API)
- php /tmp/moko-platform-api/cli/joomla_build.php --path . --version "${VERSION}" --output /tmp
- # Match the expected ZIP_NAME for upload
- BUILT_ZIP=$(ls /tmp/${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip 2>/dev/null | head -1 || true)
- if [ -n "$BUILT_ZIP" ] && [ "$BUILT_ZIP" != "/tmp/${ZIP_NAME}" ]; then
- mv "$BUILT_ZIP" "/tmp/${ZIP_NAME}"
- fi
-
- # tar.gz package (flat source archive)
- tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" --exclude='.ftpignore' --exclude='sftp-config*' --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
-
- ZIP_SIZE=$(stat -c%s "/tmp/${ZIP_NAME}" 2>/dev/null || stat -f%z "/tmp/${ZIP_NAME}" 2>/dev/null || echo "unknown")
- TAR_SIZE=$(stat -c%s "/tmp/${TAR_NAME}" 2>/dev/null || stat -f%z "/tmp/${TAR_NAME}" 2>/dev/null || echo "unknown")
-
- # -- Calculate SHA-256 for both ----------------------------------
- SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
- SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
-
- # -- Get existing assets for cleanup --------------------------------
- ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- "${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]")
-
- # -- Create per-file .sha256 checksum files -------------------------
- echo "${SHA256_ZIP} ${ZIP_NAME}" > "/tmp/${ZIP_NAME}.sha256"
- echo "${SHA256_TAR} ${TAR_NAME}" > "/tmp/${TAR_NAME}.sha256"
-
- # -- Upload packages + checksums to release tag --------------------
- for ASSET in "${ZIP_NAME}" "${TAR_NAME}" "${ZIP_NAME}.sha256" "${TAR_NAME}.sha256"; do
- [ ! -f "/tmp/${ASSET}" ] && continue
- # Delete existing asset with same name
- ASSET_ID=$(echo "$ASSETS" | python3 -c "
- import sys,json
- assets = json.load(sys.stdin)
- for a in assets:
- if a['name'] == '${ASSET}':
- print(a['id']); break
- " 2>/dev/null || true)
- [ -n "$ASSET_ID" ] && curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- "${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
- # Upload
- curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- -H "Content-Type: application/octet-stream" \
- --data-binary @"/tmp/${ASSET}" \
- "${API_BASE}/releases/${RELEASE_ID}/assets?name=${ASSET}" > /dev/null 2>&1 || true
- done
-
- # updates.xml already handled by Step 5 (updates_xml_build.php with preserve logic)
-
- echo "### Packages" >> $GITHUB_STEP_SUMMARY
- echo "" >> $GITHUB_STEP_SUMMARY
- echo "| Package | Size | SHA-256 |" >> $GITHUB_STEP_SUMMARY
- echo "|---------|------|---------|" >> $GITHUB_STEP_SUMMARY
- echo "| \`${ZIP_NAME}\` | ${ZIP_SIZE} | \`${SHA256_ZIP}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| \`${TAR_NAME}\` | ${TAR_SIZE} | \`${SHA256_TAR}\` |" >> $GITHUB_STEP_SUMMARY
- 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 ----------------------
- - name: "Step 8b: Update release body"
- if: steps.version.outputs.skip != 'true'
- run: |
- VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
- MOKO_CLI="/tmp/moko-platform-api/cli"
-
- php ${MOKO_CLI}/release_body_update.php \
- --path . --version "${VERSION}" --tag "${RELEASE_TAG}" \
- --token "${{ secrets.GA_TOKEN }}" \
- --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
- 2>/dev/null || {
- # Fallback: simple body update if CLI not available
- API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
- 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
- BODY="## ${VERSION} ($(date +%Y-%m-%d))\n\nChecksum files attached as \`*.sha256\` assets."
- curl -sf -X PATCH -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
- -H "Content-Type: application/json" \
- "${API_BASE}/releases/${RELEASE_ID}" \
- -d "{\"body\":\"${BODY}\"}" > /dev/null 2>&1
- fi
- }
- echo "Release body updated" >> $GITHUB_STEP_SUMMARY
-
- # -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- - name: "Step 9: Mirror release to GitHub"
- if: >-
- steps.version.outputs.skip != 'true' &&
- steps.version.outputs.stability == 'stable' &&
- secrets.GH_TOKEN != ''
- continue-on-error: true
- env:
- GH_TOKEN: ${{ secrets.GH_TOKEN }}
- run: |
- VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
- MAJOR="${{ steps.version.outputs.major }}"
- BRANCH="${{ steps.version.outputs.branch }}"
- GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
-
- NOTES=$(php /tmp/moko-platform-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null || true)
- [ -z "$NOTES" ] && NOTES="Release ${VERSION}"
- echo "$NOTES" > /tmp/release_notes.md
-
- EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".tag_name // empty" || true)
-
- if [ -z "$EXISTING" ]; then
- gh release create "$RELEASE_TAG" \
- --repo "$GH_REPO" \
- --title "v${MAJOR} (latest: ${VERSION})" \
- --notes-file /tmp/release_notes.md \
- --target "$BRANCH" || true
- else
- gh release edit "$RELEASE_TAG" \
- --repo "$GH_REPO" \
- --title "v${MAJOR} (latest: ${VERSION})" || true
- fi
-
- # Upload assets to GitHub mirror
- for PKG in /tmp/${EXT_ELEMENT:-pkg}-${VERSION}.*; do
- if [ -f "$PKG" ]; then
- _RELID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".id // empty")
- [ -n "$_RELID" ] && curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" -H "Content-Type: application/octet-stream" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/${_RELID}/assets?name=$(basename $PKG)" --data-binary "@$PKG" > /dev/null 2>&1 || true
- fi
- done
- echo "GitHub mirror updated: ${GH_REPO} ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
-
- # -- STEP 10: Sync main branch to GitHub mirror ----------------------------
- - name: "Step 10: Push main to GitHub mirror"
- if: >-
- steps.version.outputs.skip != 'true' &&
- secrets.GH_TOKEN != ''
- continue-on-error: true
- run: |
- GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
- GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
- GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
- git remote add github "https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
- git remote set-url github "https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
- git fetch origin main --depth=1
- git push github origin/main:refs/heads/main --force 2>/dev/null \
- && echo "main branch pushed to GitHub mirror" \
- || echo "WARNING: GitHub mirror push failed"
-
- # -- Clean up lesser pre-releases (cascade) ---------------------------------
- # stable → deletes all | rc → beta,alpha,dev | beta → alpha,dev | alpha → dev
- - name: "Delete lesser pre-release channels"
- continue-on-error: true
- run: |
- php /tmp/moko-platform-api/cli/release_cascade.php \
- --stability stable \
- --token "${{ secrets.GA_TOKEN }}" \
- --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
- --gitea-url "${GITEA_URL}" 2>/dev/null || true
-
- - 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
-
-
- # -- Dolibarr post-release: Reset dev version -----------------------------
- - name: "Dolibarr: Reset dev version"
- if: >-
- steps.version.outputs.skip != 'true' &&
- steps.platform.outputs.platform == 'dolibarr' &&
- steps.platform.outputs.mod_file != ''
- continue-on-error: true
- run: |
- API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
- TOKEN="${{ secrets.GA_TOKEN }}"
- MOD_FILE="${{ steps.platform.outputs.mod_file }}"
- ENCODED_PATH=$(echo "$MOD_FILE" | sed 's|^\./||' | python3 -c "import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip()))")
- FILE_RESP=$(curl -sf -H "Authorization: token ${TOKEN}" "${API_BASE}/contents/${ENCODED_PATH}?ref=dev" 2>/dev/null || true)
- FILE_SHA=$(echo "$FILE_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
- FILE_CONTENT=$(echo "$FILE_RESP" | python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin).get('content','')).decode())" 2>/dev/null || true)
- if [ -n "$FILE_SHA" ] && [ -n "$FILE_CONTENT" ]; then
- UPDATED=$(echo "$FILE_CONTENT" | sed "s/\$this->version = '[^']*'/\$this->version = 'development'/")
- ENCODED=$(echo "$UPDATED" | base64 -w0)
- curl -sf -X PUT -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/contents/${ENCODED_PATH}" \
- -d "$(jq -n --arg content \"$ENCODED\" --arg sha \"$FILE_SHA\" --arg msg \"chore(version): reset dev version [skip ci]\" --arg branch \"dev\" '{content:$content,sha:$sha,message:$msg,branch:$branch}')" > /dev/null 2>&1 || true
- fi
-
- # -- Summary --------------------------------------------------------------
- - name: Pipeline Summary
- if: always()
- run: |
- VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- PLATFORM="${{ steps.platform.outputs.platform }}"
- if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
- echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
- echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
- elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
- echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
- else
- echo "" >> $GITHUB_STEP_SUMMARY
- echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
- echo "" >> $GITHUB_STEP_SUMMARY
- echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
- echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
- echo "| Platform | \`${PLATFORM}\` |" >> $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](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
- fi
+# Copyright (C) 2026 Moko Consulting
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# FILE INFORMATION
+# DEFGROUP: Gitea.Workflow
+# INGROUP: moko-platform.Release
+# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
+# PATH: /templates/workflows/universal/auto-release.yml.template
+# VERSION: 05.00.00
+# BRIEF: Universal build & release � detects platform from manifest.xml
+#
+# +========================================================================+
+# | UNIVERSAL BUILD & RELEASE PIPELINE |
+# +========================================================================+
+# | |
+# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
+# | |
+# | Platform-specific: |
+# | joomla: XML manifest, updates.xml, type-prefixed packages |
+# | dolibarr: mod*.class.php, update.txt, dev version reset |
+# | generic: README-only, no update stream |
+# | |
+# +========================================================================+
+
+name: "Universal: Build & Release"
+
+on:
+ pull_request:
+ types: [opened, closed]
+ branches:
+ - main
+ workflow_dispatch:
+
+env:
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
+ GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
+ GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
+ GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
+
+permissions:
+ contents: write
+
+jobs:
+ # ── Draft PR → Promote highest pre-release to RC ─────────────────────────────
+ promote-rc:
+ name: Promote Pre-Release to RC
+ runs-on: release
+ if: >-
+ github.event.action == 'opened' &&
+ github.event.pull_request.draft == true
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+ with:
+ token: ${{ secrets.GA_TOKEN }}
+ fetch-depth: 1
+
+ - name: Setup moko-platform tools
+ env:
+ MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
+ MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
+ run: |
+ if ! command -v composer &> /dev/null; then
+ sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
+ fi
+ git clone --depth 1 --branch main --quiet \
+ "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
+ /tmp/moko-platform-api
+ cd /tmp/moko-platform-api
+ composer install --no-dev --no-interaction --quiet
+
+ - name: Promote to release-candidate
+ run: |
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ php /tmp/moko-platform-api/cli/release_promote.php \
+ --from auto --to release-candidate \
+ --token "${{ secrets.GA_TOKEN }}" \
+ --api-base "${API_BASE}" \
+ --branch "${{ github.event.pull_request.head.ref }}"
+
+ - name: Cascade lesser channels
+ continue-on-error: true
+ run: |
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ php /tmp/moko-platform-api/cli/release_cascade.php \
+ --stability release-candidate \
+ --token "${{ secrets.GA_TOKEN }}" \
+ --api-base "${API_BASE}"
+
+ - name: Summary
+ if: always()
+ run: |
+ echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
+ echo "Draft PR opened — promoted highest pre-release to RC" >> $GITHUB_STEP_SUMMARY
+
+ # ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
+ release:
+ name: Build & Release Pipeline
+ runs-on: release
+ if: >-
+ github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+ with:
+ token: ${{ secrets.GA_TOKEN }}
+ fetch-depth: 0
+
+ - name: Setup moko-platform tools
+ env:
+ MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
+ MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
+ COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
+ run: |
+ # Ensure PHP + Composer are available
+ if ! command -v composer &> /dev/null; then
+ sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
+ fi
+ git clone --depth 1 --branch main --quiet \
+ "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
+ /tmp/moko-platform-api
+ cd /tmp/moko-platform-api
+ composer install --no-dev --no-interaction --quiet
+
+
+ # -- PLATFORM DETECTION ---------------------------------------------------
+ - name: Detect platform
+ id: platform
+ run: |
+ php /tmp/moko-platform-api/cli/manifest_read.php --path . --github-output
+ MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1 || true)
+ MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1 || true)
+ echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
+ echo "mod_file=${MOD_FILE}" >> "$GITHUB_OUTPUT"
+
+ - name: "Step 1: Read version"
+ id: version
+ run: |
+ VERSION=$(php /tmp/moko-platform-api/cli/version_read.php --path .)
+ if [ -z "$VERSION" ]; then
+ echo "::error::No VERSION in README.md"
+ echo "skip=true" >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+ MAJOR=$(echo "$VERSION" | cut -d. -f1)
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
+ echo "release_tag=stable" >> "$GITHUB_OUTPUT"
+ echo "skip=false" >> "$GITHUB_OUTPUT"
+ echo "branch=main" >> "$GITHUB_OUTPUT"
+
+ # -- CHECK FOR RC PROMOTION ------------------------------------------------
+ - name: "Check for RC release"
+ id: rc
+ if: steps.version.outputs.skip != 'true'
+ run: |
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ RC_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
+ "${API_BASE}/releases/tags/release-candidate" 2>/dev/null || echo "{}")
+ RC_ID=$(echo "$RC_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
+
+ if [ -n "$RC_ID" ] && [ "$RC_ID" != "None" ] && [ "$RC_ID" != "" ]; then
+ echo "promote=true" >> "$GITHUB_OUTPUT"
+ echo "release_id=${RC_ID}" >> "$GITHUB_OUTPUT"
+ echo "::notice::RC release found (id: ${RC_ID}) — will promote to stable"
+ else
+ echo "promote=false" >> "$GITHUB_OUTPUT"
+ echo "::notice::No RC release — full build pipeline"
+ fi
+
+ - name: "Step 1b: Bump version"
+ id: bump
+ if: >-
+ steps.version.outputs.skip != 'true' &&
+ steps.rc.outputs.promote != 'true'
+ run: |
+ MOKO_API="/tmp/moko-platform-api/cli"
+ BUMP=$(php ${MOKO_API}/version_bump.php --path . --minor)
+ VERSION=$(echo "$BUMP" | grep -oP '\d{2}\.\d{2}\.\d{2}$' || true)
+ [ -z "$VERSION" ] && VERSION=$(php ${MOKO_API}/version_read.php --path .)
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
+ echo "Bumped to: ${VERSION}"
+
+ - name: Check if already released
+ if: steps.version.outputs.skip != 'true'
+ id: check
+ run: |
+ TAG="${{ steps.version.outputs.release_tag }}"
+ BRANCH="${{ steps.version.outputs.branch }}"
+
+ 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"
+
+ # Tag and branch may persist across patch releases — never skip
+ echo "already_released=false" >> "$GITHUB_OUTPUT"
+
+ # -- SANITY CHECKS -------------------------------------------------------
+ - name: "Sanity: Pre-release validation"
+ if: >-
+ steps.version.outputs.skip != 'true' &&
+ steps.check.outputs.already_released != 'true'
+ run: |
+ VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+ ERRORS=0
+
+ PLATFORM="${{ steps.platform.outputs.platform }}"
+ MANIFEST="${{ steps.platform.outputs.manifest }}"
+ MOD_FILE="${{ steps.platform.outputs.mod_file }}"
+ echo "## Pre-Release Sanity Checks (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+
+ # -- Version drift check (must pass before release) --------
+ README_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1)
+ if [ "$README_VER" != "$VERSION" ]; then
+ echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
+ ERRORS=$((ERRORS+1))
+ else
+ echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ # Check CHANGELOG version matches
+ CL_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' CHANGELOG.md 2>/dev/null | head -1)
+ if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then
+ echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
+ ERRORS=$((ERRORS+1))
+ fi
+
+ # Check composer.json version if present
+ if [ -f "composer.json" ]; then
+ COMP_VER=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' composer.json 2>/dev/null | head -1)
+ if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then
+ echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
+ ERRORS=$((ERRORS+1))
+ fi
+ fi
+
+ # Common checks
+ if [ ! -f "LICENSE" ]; then
+ echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
+ ERRORS=$((ERRORS+1))
+ else
+ echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ if [ ! -d "src" ] && [ ! -d "htdocs" ]; then
+ echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "- Source directory present" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ # -- Platform-specific checks --------
+ case "$PLATFORM" in
+ joomla)
+ if [ -n "$MANIFEST" ]; then
+ XML_VER=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
+ if [ -n "$XML_VER" ] && [ "$XML_VER" != "$VERSION" ]; then
+ echo "- Manifest drift: \`${XML_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
+ ERRORS=$((ERRORS+1))
+ else
+ echo "- Manifest version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
+ fi
+ TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null)
+ echo "- Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "- No Joomla XML manifest (WaaS site)" >> $GITHUB_STEP_SUMMARY
+ fi ;;
+ dolibarr)
+ if [ -n "$MOD_FILE" ]; then
+ MOD_VER=$(sed -n "s/.*\\\$this->version = '\([^']*\)'.*/\1/p" "$MOD_FILE" 2>/dev/null | head -1)
+ if [ -n "$MOD_VER" ] && [ "$MOD_VER" != "$VERSION" ]; then
+ echo "- Module drift: \`${MOD_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
+ ERRORS=$((ERRORS+1))
+ else
+ echo "- Module version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
+ fi
+ else
+ echo "- No mod*.class.php found" >> $GITHUB_STEP_SUMMARY
+ ERRORS=$((ERRORS+1))
+ fi
+ if [ ! -f "update.txt" ]; then
+ echo "- Missing update.txt" >> $GITHUB_STEP_SUMMARY
+ ERRORS=$((ERRORS+1))
+ fi ;;
+ *) echo "- Generic platform � no manifest checks" >> $GITHUB_STEP_SUMMARY ;;
+ esac
+
+ 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 or update version/XX.YY archive branch ---------------
+ # Always runs — every version change on main archives to version/XX.YY
+ - name: "Step 2: Version archive branch"
+ if: steps.check.outputs.already_released != 'true'
+ run: |
+ BRANCH="${{ steps.version.outputs.branch }}"
+ IS_MINOR="${{ steps.version.outputs.is_minor }}"
+ PATCH="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+ PATCH_NUM=$(echo "$PATCH" | awk -F. '{print $3}')
+
+ # Check if branch exists
+ if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
+ git push origin HEAD:"$BRANCH" --force
+ echo "Updated archive branch: ${BRANCH} (patch ${PATCH_NUM})" >> $GITHUB_STEP_SUMMARY
+ else
+ git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH"
+ git push origin "$BRANCH" --force
+ echo "Created archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
+ fi
+
+ # -- 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.bump.outputs.version || steps.version.outputs.version }}"
+ php /tmp/moko-platform-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'
+ run: |
+ VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+ php /tmp/moko-platform-api/cli/badge_update.php --path . --version "${VERSION}" 2>/dev/null || true
+ php /tmp/moko-platform-api/cli/version_check.php --path . --fix 2>/dev/null || true
+
+ - name: "Step 5: Write update stream"
+ if: >-
+ steps.version.outputs.skip != 'true' &&
+ steps.platform.outputs.platform == 'joomla'
+ run: |
+ VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+
+ # Fetch latest updates.xml from main so preserve logic has all channels
+ GA_TOKEN="${{ secrets.GA_TOKEN }}"
+ API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
+ curl -sf -H "Authorization: token ${GA_TOKEN}" \
+ "${API}/contents/updates.xml?ref=main" 2>/dev/null | \
+ python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin)['content']).decode())" \
+ > updates.xml 2>/dev/null || true
+
+ php /tmp/moko-platform-api/cli/updates_xml_build.php \
+ --path . --version "${VERSION}" --stability stable \
+ --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
+ --github-output
+
+ - name: Commit release changes
+ if: >-
+ steps.version.outputs.skip != 'true' &&
+ steps.check.outputs.already_released != 'true'
+ run: |
+ if git diff --quiet && git diff --cached --quiet; then
+ echo "No changes to commit"
+ exit 0
+ fi
+ VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+ git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
+ git config --local user.name "gitea-actions[bot]"
+ # Set push URL with token for branch-protected repos
+ git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
+ git add -A
+ git commit -m "chore(release): build ${VERSION} [skip ci]" \
+ --author="gitea-actions[bot] "
+ git push -u origin HEAD
+
+ # -- STEP 6: Create tag ---------------------------------------------------
+ - name: "Step 6: Create git tag"
+ if: >-
+ steps.version.outputs.skip != 'true'
+ run: |
+ RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
+ # Only create the major release tag if it doesn't exist yet
+ if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
+ git tag "$RELEASE_TAG"
+ git push origin "$RELEASE_TAG"
+ echo "Tag created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "Tag ${RELEASE_TAG} already exists" >> $GITHUB_STEP_SUMMARY
+ fi
+ echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
+
+ # -- STEP 7a: Promote RC to stable (skip build) ----------------------------
+ - name: "Step 7a: Promote RC to stable"
+ if: >-
+ steps.version.outputs.skip != 'true' &&
+ steps.rc.outputs.promote == 'true'
+ run: |
+ VERSION="${{ steps.version.outputs.version }}"
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ php /tmp/moko-platform-api/cli/release_promote.php \
+ --from release-candidate --to stable \
+ --token "${{ secrets.GA_TOKEN }}" \
+ --api-base "${API_BASE}" \
+ --path . --branch main
+ echo "Promoted RC → stable (${VERSION})" >> $GITHUB_STEP_SUMMARY
+
+ # -- STEP 7b: Create or update Gitea Release (full build path) -------------
+ - name: "Step 7b: Gitea Release"
+ if: >-
+ steps.version.outputs.skip != 'true' &&
+ steps.rc.outputs.promote != 'true'
+ run: |
+ VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+ RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
+ BRANCH="${{ steps.version.outputs.branch }}"
+ MAJOR="${{ steps.version.outputs.major }}"
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+
+ # 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/moko-platform-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
+ [ -z "$NOTES" ] && NOTES="Release ${VERSION}"
+
+ # Build release name: "Pretty Name VERSION (type_element-VERSION)"
+ # Strip existing type prefix to prevent duplication
+ EXT_ELEMENT=$(echo "$EXT_ELEMENT" | sed -E 's/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)//')
+ 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 }}" \
+ "${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
+ EXISTING_ID=$(echo "$EXISTING" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
+
+ if [ -n "$EXISTING_ID" ]; then
+ curl -sS -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
+ "${API_BASE}/releases/${EXISTING_ID}" 2>/dev/null || true
+ curl -sS -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
+ "${API_BASE}/tags/${RELEASE_TAG}" 2>/dev/null || true
+ echo "Deleted previous stable release (id: ${EXISTING_ID})"
+ fi
+
+ # Create fresh release
+ curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
+ -H "Content-Type: application/json" \
+ "${API_BASE}/releases" \
+ -d "$(python3 -c "import json; print(json.dumps({
+ 'tag_name': '${RELEASE_TAG}',
+ 'name': '${RELEASE_NAME}',
+ 'body': '''## ${VERSION} ($(date +%Y-%m-%d))\n${NOTES}''',
+ 'target_commitish': '${BRANCH}'
+ }))")"
+ echo "Release created: ${RELEASE_NAME}" >> $GITHUB_STEP_SUMMARY
+
+ # -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
+ - name: "Step 8: Build package and update checksum"
+ if: >-
+ steps.version.outputs.skip != 'true' &&
+ steps.rc.outputs.promote != 'true'
+ run: |
+ VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+ RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
+ REPO="${{ github.repository }}"
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+
+ # All ZIPs upload to the major release tag (vXX)
+ RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
+ "${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
+ RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
+ if [ -z "$RELEASE_ID" ]; then
+ echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
+ exit 0
+ fi
+
+ # Find extension element name from manifest
+ MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1 || true)
+ [ -z "$MANIFEST" ] && exit 0
+
+ # 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)
+ # For packages, prefer over filename-derived element
+ if [ "$EXT_TYPE" = "package" ]; then
+ PKG_NAME=$(sed -n 's/.*\([^<]*\)<\/packagename>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
+ [ -n "$PKG_NAME" ] && EXT_ELEMENT="$PKG_NAME"
+ fi
+ # Strip existing type prefix to prevent duplication (e.g. pkg_mokowaas → mokowaas)
+ EXT_ELEMENT=$(echo "$EXT_ELEMENT" | sed -E 's/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)//')
+ 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"
+ [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
+ [ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/"; exit 0; }
+
+ # ZIP package (type-aware via moko-platform PHP API)
+ php /tmp/moko-platform-api/cli/joomla_build.php --path . --version "${VERSION}" --output /tmp
+ # Match the expected ZIP_NAME for upload
+ BUILT_ZIP=$(ls /tmp/${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip 2>/dev/null | head -1 || true)
+ if [ -n "$BUILT_ZIP" ] && [ "$BUILT_ZIP" != "/tmp/${ZIP_NAME}" ]; then
+ mv "$BUILT_ZIP" "/tmp/${ZIP_NAME}"
+ fi
+
+ # tar.gz package (flat source archive)
+ tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" --exclude='.ftpignore' --exclude='sftp-config*' --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
+
+ ZIP_SIZE=$(stat -c%s "/tmp/${ZIP_NAME}" 2>/dev/null || stat -f%z "/tmp/${ZIP_NAME}" 2>/dev/null || echo "unknown")
+ TAR_SIZE=$(stat -c%s "/tmp/${TAR_NAME}" 2>/dev/null || stat -f%z "/tmp/${TAR_NAME}" 2>/dev/null || echo "unknown")
+
+ # -- Calculate SHA-256 for both ----------------------------------
+ SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
+ SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
+
+ # -- Get existing assets for cleanup --------------------------------
+ ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
+ "${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]")
+
+ # -- Create per-file .sha256 checksum files -------------------------
+ echo "${SHA256_ZIP} ${ZIP_NAME}" > "/tmp/${ZIP_NAME}.sha256"
+ echo "${SHA256_TAR} ${TAR_NAME}" > "/tmp/${TAR_NAME}.sha256"
+
+ # -- Upload packages + checksums to release tag --------------------
+ for ASSET in "${ZIP_NAME}" "${TAR_NAME}" "${ZIP_NAME}.sha256" "${TAR_NAME}.sha256"; do
+ [ ! -f "/tmp/${ASSET}" ] && continue
+ # Delete existing asset with same name
+ ASSET_ID=$(echo "$ASSETS" | python3 -c "
+ import sys,json
+ assets = json.load(sys.stdin)
+ for a in assets:
+ if a['name'] == '${ASSET}':
+ print(a['id']); break
+ " 2>/dev/null || true)
+ [ -n "$ASSET_ID" ] && curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
+ "${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
+ # Upload
+ curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
+ -H "Content-Type: application/octet-stream" \
+ --data-binary @"/tmp/${ASSET}" \
+ "${API_BASE}/releases/${RELEASE_ID}/assets?name=${ASSET}" > /dev/null 2>&1 || true
+ done
+
+ # updates.xml already handled by Step 5 (updates_xml_build.php with preserve logic)
+
+ echo "### Packages" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Package | Size | SHA-256 |" >> $GITHUB_STEP_SUMMARY
+ echo "|---------|------|---------|" >> $GITHUB_STEP_SUMMARY
+ echo "| \`${ZIP_NAME}\` | ${ZIP_SIZE} | \`${SHA256_ZIP}\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| \`${TAR_NAME}\` | ${TAR_SIZE} | \`${SHA256_TAR}\` |" >> $GITHUB_STEP_SUMMARY
+ 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 ----------------------
+ - name: "Step 8b: Update release body"
+ if: steps.version.outputs.skip != 'true'
+ run: |
+ VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+ RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
+ MOKO_CLI="/tmp/moko-platform-api/cli"
+
+ php ${MOKO_CLI}/release_body_update.php \
+ --path . --version "${VERSION}" --tag "${RELEASE_TAG}" \
+ --token "${{ secrets.GA_TOKEN }}" \
+ --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
+ 2>/dev/null || {
+ # Fallback: simple body update if CLI not available
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ 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
+ BODY="## ${VERSION} ($(date +%Y-%m-%d))\n\nChecksum files attached as \`*.sha256\` assets."
+ curl -sf -X PATCH -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
+ -H "Content-Type: application/json" \
+ "${API_BASE}/releases/${RELEASE_ID}" \
+ -d "{\"body\":\"${BODY}\"}" > /dev/null 2>&1
+ fi
+ }
+ echo "Release body updated" >> $GITHUB_STEP_SUMMARY
+
+ # -- STEP 9: Mirror to GitHub (stable only) --------------------------------
+ - name: "Step 9: Mirror release to GitHub"
+ if: >-
+ steps.version.outputs.skip != 'true' &&
+ steps.version.outputs.stability == 'stable' &&
+ secrets.GH_TOKEN != ''
+ continue-on-error: true
+ env:
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
+ run: |
+ VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+ RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
+ MAJOR="${{ steps.version.outputs.major }}"
+ BRANCH="${{ steps.version.outputs.branch }}"
+ GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
+
+ NOTES=$(php /tmp/moko-platform-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null || true)
+ [ -z "$NOTES" ] && NOTES="Release ${VERSION}"
+ echo "$NOTES" > /tmp/release_notes.md
+
+ EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".tag_name // empty" || true)
+
+ if [ -z "$EXISTING" ]; then
+ gh release create "$RELEASE_TAG" \
+ --repo "$GH_REPO" \
+ --title "v${MAJOR} (latest: ${VERSION})" \
+ --notes-file /tmp/release_notes.md \
+ --target "$BRANCH" || true
+ else
+ gh release edit "$RELEASE_TAG" \
+ --repo "$GH_REPO" \
+ --title "v${MAJOR} (latest: ${VERSION})" || true
+ fi
+
+ # Upload assets to GitHub mirror
+ for PKG in /tmp/${EXT_ELEMENT:-pkg}-${VERSION}.*; do
+ if [ -f "$PKG" ]; then
+ _RELID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".id // empty")
+ [ -n "$_RELID" ] && curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" -H "Content-Type: application/octet-stream" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/${_RELID}/assets?name=$(basename $PKG)" --data-binary "@$PKG" > /dev/null 2>&1 || true
+ fi
+ done
+ echo "GitHub mirror updated: ${GH_REPO} ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
+
+ # -- STEP 10: Sync main branch to GitHub mirror ----------------------------
+ - name: "Step 10: Push main to GitHub mirror"
+ if: >-
+ steps.version.outputs.skip != 'true' &&
+ secrets.GH_TOKEN != ''
+ continue-on-error: true
+ run: |
+ GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
+ GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
+ GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
+ git remote add github "https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
+ git remote set-url github "https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
+ git fetch origin main --depth=1
+ git push github origin/main:refs/heads/main --force 2>/dev/null \
+ && echo "main branch pushed to GitHub mirror" \
+ || echo "WARNING: GitHub mirror push failed"
+
+ # -- Clean up lesser pre-releases (cascade) ---------------------------------
+ # stable → deletes all | rc → beta,alpha,dev | beta → alpha,dev | alpha → dev
+ - name: "Delete lesser pre-release channels"
+ continue-on-error: true
+ run: |
+ VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ php /tmp/moko-platform-api/cli/release_cascade.php \
+ --stability stable \
+ --version "${VERSION}" \
+ --token "${{ secrets.GA_TOKEN }}" \
+ --api-base "${API_BASE}" 2>/dev/null || true
+
+ - 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
+
+
+ # -- Dolibarr post-release: Reset dev version -----------------------------
+ - name: "Dolibarr: Reset dev version"
+ if: >-
+ steps.version.outputs.skip != 'true' &&
+ steps.platform.outputs.platform == 'dolibarr' &&
+ steps.platform.outputs.mod_file != ''
+ continue-on-error: true
+ run: |
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ TOKEN="${{ secrets.GA_TOKEN }}"
+ MOD_FILE="${{ steps.platform.outputs.mod_file }}"
+ ENCODED_PATH=$(echo "$MOD_FILE" | sed 's|^\./||' | python3 -c "import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip()))")
+ FILE_RESP=$(curl -sf -H "Authorization: token ${TOKEN}" "${API_BASE}/contents/${ENCODED_PATH}?ref=dev" 2>/dev/null || true)
+ FILE_SHA=$(echo "$FILE_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
+ FILE_CONTENT=$(echo "$FILE_RESP" | python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin).get('content','')).decode())" 2>/dev/null || true)
+ if [ -n "$FILE_SHA" ] && [ -n "$FILE_CONTENT" ]; then
+ UPDATED=$(echo "$FILE_CONTENT" | sed "s/\$this->version = '[^']*'/\$this->version = 'development'/")
+ ENCODED=$(echo "$UPDATED" | base64 -w0)
+ curl -sf -X PUT -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/contents/${ENCODED_PATH}" \
+ -d "$(jq -n --arg content \"$ENCODED\" --arg sha \"$FILE_SHA\" --arg msg \"chore(version): reset dev version [skip ci]\" --arg branch \"dev\" '{content:$content,sha:$sha,message:$msg,branch:$branch}')" > /dev/null 2>&1 || true
+ fi
+
+ # -- Summary --------------------------------------------------------------
+ - name: Pipeline Summary
+ if: always()
+ run: |
+ VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
+ PLATFORM="${{ steps.platform.outputs.platform }}"
+ if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
+ echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
+ echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
+ elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
+ echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
+ else
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
+ echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
+ echo "| Platform | \`${PLATFORM}\` |" >> $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](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
+ fi
--
2.52.0
From 0dcb8a4a1dbdf3810af00193b6914ffbda23fa7e Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Tue, 26 May 2026 18:52:46 +0000
Subject: [PATCH 4/6] chore(ci): update pre-release.yml from moko-platform
[skip ci]
---
.mokogitea/workflows/pre-release.yml | 746 ++++++++++++++-------------
1 file changed, 385 insertions(+), 361 deletions(-)
diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml
index 7f096904..698251d1 100644
--- a/.mokogitea/workflows/pre-release.yml
+++ b/.mokogitea/workflows/pre-release.yml
@@ -1,361 +1,385 @@
-# Copyright (C) 2026 Moko Consulting
-#
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# FILE INFORMATION
-# DEFGROUP: Gitea.Workflow
-# INGROUP: moko-platform.Release
-# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
-# PATH: /templates/workflows/universal/pre-release.yml.template
-# VERSION: 05.01.00
-# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
-
-name: "Universal: Pre-Release"
-
-on:
- workflow_dispatch:
- inputs:
- stability:
- description: 'Pre-release channel'
- required: true
- type: choice
- options:
- - development
- - alpha
- - beta
- - release-candidate
-
-permissions:
- contents: write
-
-env:
- GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
- GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
- GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
-
-jobs:
- build:
- name: "Build Pre-Release (${{ inputs.stability }})"
- runs-on: release
-
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
- token: ${{ secrets.GA_TOKEN }}
-
- - name: Setup tools
- run: |
- # Update moko-platform CLI tools if available; install PHP if missing
- if command -v moko-platform-update &> /dev/null; then
- moko-platform-update
- elif [ -d "/opt/moko-platform" ]; then
- cd /opt/moko-platform && git pull origin main --quiet 2>/dev/null || true
- else
- if ! command -v php &> /dev/null; then
- sudo apt-get update -qq
- sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl >/dev/null 2>&1
- fi
- git clone --depth 1 --branch main --quiet \
- "https://x-access-token:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
- /tmp/moko-platform-api
- fi
- # Set MOKO_CLI to whichever path exists
- if [ -d "/opt/moko-platform/cli" ]; then
- echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
- else
- echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
- fi
-
- - name: Detect platform
- id: platform
- run: |
- PLATFORM=$(sed -n 's/.*\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1 | tr -d '[:space:]')
- [ -z "$PLATFORM" ] && PLATFORM="generic"
- echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
- MANIFEST=$(find ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '/dev/null | head -1)
- [ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "*/packages/*" -exec grep -l '/dev/null | head -1)
- [ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1)
- MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
- echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
- echo "mod_file=${MOD_FILE}" >> "$GITHUB_OUTPUT"
-
- - name: Resolve metadata and bump version
- id: meta
- run: |
- STABILITY="${{ inputs.stability }}"
-
- case "$STABILITY" in
- development) SUFFIX="-dev"; TAG="development" ;;
- alpha) SUFFIX="-alpha"; TAG="alpha" ;;
- beta) SUFFIX="-beta"; TAG="beta" ;;
- release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
- esac
-
- # Patch bump via CLI tool
- php ${MOKO_CLI}/version_bump.php --path .
- VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
- [ -z "$VERSION" ] && VERSION="00.00.01"
- TODAY=$(date +%Y-%m-%d)
-
- # Update platform-specific manifest
- PLATFORM="${{ steps.platform.outputs.platform }}"
- MANIFEST="${{ steps.platform.outputs.manifest }}"
- MOD_FILE="${{ steps.platform.outputs.mod_file }}"
-
- php ${MOKO_CLI}/version_set_platform.php \
- --path . --version "$VERSION" --branch "${{ github.ref_name }}" 2>/dev/null || true
-
- # Verify version consistency across all files
- php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
-
- # Commit version bump
- 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): pre-release bump to ${VERSION} [skip ci]"
- git push origin HEAD 2>&1
- }
-
- # Auto-detect element (platform-aware)
- EXT_ELEMENT=""
- case "$PLATFORM" in
- joomla)
- if [ -n "$MANIFEST" ]; then
- EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
- if [ -z "$EXT_ELEMENT" ]; then
- 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
- fi
- else
- EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
- fi
- ;;
- dolibarr)
- if [ -n "$MOD_FILE" ]; then
- MOD_BASENAME=$(basename "$MOD_FILE" .class.php)
- EXT_ELEMENT=$(echo "$MOD_BASENAME" | sed 's/^mod//' | tr '[:upper:]' '[:lower:]')
- else
- EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
- fi
- ;;
- *)
- EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
- ;;
- esac
-
- ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
-
- echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
- echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
- echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
- echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
- echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
- echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
-
- echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
-
- - name: Build package
- run: |
- SOURCE_DIR="src"
- [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
- if [ ! -d "$SOURCE_DIR" ]; then
- echo "::error::No src/ or htdocs/ directory"
- exit 1
- fi
-
- MANIFEST="${{ steps.meta.outputs.manifest }}"
- EXT_TYPE=""
- if [ -n "$MANIFEST" ]; then
- EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
- fi
-
- EXCLUDES="sftp-config* .ftpignore *.ppk *.pem *.key .env* *.local .build-trigger"
-
- mkdir -p build/package
-
- REPO_ROOT=$(pwd)
-
- if [ "$EXT_TYPE" = "package" ] && [ -d "${SOURCE_DIR}/packages" ]; then
- echo "=== Building Joomla PACKAGE (multi-extension) ==="
- for ext_dir in "${SOURCE_DIR}"/packages/*/; do
- [ ! -d "$ext_dir" ] && continue
- EXT_NAME=$(basename "$ext_dir")
- echo " Packaging sub-extension: ${EXT_NAME}"
- cd "$ext_dir"
- zip -r "${REPO_ROOT}/build/package/${EXT_NAME}.zip" . -x $EXCLUDES
- cd "${REPO_ROOT}"
- done
- for f in "${SOURCE_DIR}"/*.xml "${SOURCE_DIR}"/*.php; do
- [ -f "$f" ] && cp "$f" build/package/
- done
- else
- echo "=== Building standard extension ==="
- rsync -a \
- --exclude='sftp-config*' \
- --exclude='.ftpignore' \
- --exclude='*.ppk' \
- --exclude='*.pem' \
- --exclude='*.key' \
- --exclude='.env*' \
- --exclude='*.local' \
- --exclude='.build-trigger' \
- "${SOURCE_DIR}/" build/package/
- fi
-
- - name: Create ZIP
- id: zip
- run: |
- ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
- cd build/package
- zip -r "../${ZIP_NAME}" .
- cd ..
-
- SHA256=$(sha256sum "${ZIP_NAME}" | cut -d' ' -f1)
- echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
- echo "ZIP: ${ZIP_NAME} (SHA: ${SHA256:0:16}...)"
-
- - name: Create or replace Gitea release
- id: release
- run: |
- TAG="${{ steps.meta.outputs.tag }}"
- VERSION="${{ steps.meta.outputs.version }}"
- STABILITY="${{ steps.meta.outputs.stability }}"
- SHA256="${{ steps.zip.outputs.sha256 }}"
- ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
- EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}"
- TOKEN="${{ secrets.GA_TOKEN }}"
- API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
- BRANCH=$(git branch --show-current)
-
- BODY="## ${VERSION} ($(date +%Y-%m-%d))
- **Channel:** ${STABILITY}
- Checksum file attached as \`${ZIP_NAME}.sha256\`."
-
- # Delete existing release
- EXISTING_ID=$(curl -sS -H "Authorization: token ${TOKEN}" \
- "${API}/releases/tags/${TAG}" | jq -r '.id // empty' 2>/dev/null)
- if [ -n "$EXISTING_ID" ]; then
- curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
- "${API}/releases/${EXISTING_ID}" 2>/dev/null || true
- curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
- "${API}/tags/${TAG}" 2>/dev/null || true
- fi
-
- # Create release
- RELEASE_ID=$(curl -sS -X POST -H "Authorization: token ${TOKEN}" \
- -H "Content-Type: application/json" \
- "${API}/releases" \
- -d "$(jq -n \
- --arg tag "$TAG" \
- --arg target "$BRANCH" \
- --arg name "${EXT_ELEMENT} ${VERSION} (${STABILITY})" \
- --arg body "$BODY" \
- '{tag_name: $tag, target_commitish: $target, name: $name, body: $body, prerelease: true}'
- )" | jq -r '.id')
-
- echo "release_id=${RELEASE_ID}" >> "$GITHUB_OUTPUT"
-
- # Upload ZIP
- curl -sS -X POST -H "Authorization: token ${TOKEN}" \
- -H "Content-Type: application/octet-stream" \
- "${API}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
- --data-binary "@build/${ZIP_NAME}"
-
- # Upload per-file checksum
- echo "${SHA256} ${ZIP_NAME}" > "build/${ZIP_NAME}.sha256"
- curl -sS -X POST -H "Authorization: token ${TOKEN}" \
- -H "Content-Type: application/octet-stream" \
- "${API}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}.sha256" \
- --data-binary "@build/${ZIP_NAME}.sha256"
-
- echo "Released: ${EXT_ELEMENT} ${VERSION} (${STABILITY})"
-
- - name: Update updates.xml
- if: steps.platform.outputs.platform == 'joomla'
- run: |
- STABILITY="${{ steps.meta.outputs.stability }}"
- VERSION="${{ steps.meta.outputs.version }}"
- SHA256="${{ steps.zip.outputs.sha256 }}"
- ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
- TAG="${{ steps.meta.outputs.tag }}"
-
- if [ ! -f "updates.xml" ]; then
- echo "No updates.xml -- skipping"
- exit 0
- fi
-
- # Use moko-platform CLI to build/update updates.xml (preserves other channels)
- MOKO_CLI="/tmp/moko-platform-api/cli"
- if [ -f "${MOKO_CLI}/updates_xml_build.php" ]; then
- php "${MOKO_CLI}/updates_xml_build.php" \
- --path . --version "${VERSION}" --stability "${STABILITY}" \
- --sha "${SHA256}" \
- --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}"
- echo "Updated ${STABILITY} channel via CLI: version=${VERSION}"
- else
- echo "WARNING: updates_xml_build.php not found — skipping"
- fi
-
- # Commit and push
- if ! git diff --quiet updates.xml 2>/dev/null; then
- git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
- git config --local user.name "gitea-actions[bot]"
- git add updates.xml
- git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
- git push origin HEAD 2>&1 || echo "WARNING: push failed"
- fi
-
- - name: "Sync updates.xml to all branches"
- if: steps.platform.outputs.platform == 'joomla'
- run: |
- CURRENT_BRANCH="${{ github.ref_name }}"
- git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
- git config --local user.name "gitea-actions[bot]"
-
- for BRANCH in main dev; do
- [ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
- echo "Syncing updates.xml -> ${BRANCH}"
- git fetch origin "${BRANCH}" 2>/dev/null || continue
- git checkout "origin/${BRANCH}" -- . 2>/dev/null || continue
- git checkout "${CURRENT_BRANCH}" -- updates.xml
- if ! git diff --quiet updates.xml 2>/dev/null; then
- git add updates.xml
- git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
- git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
- fi
- git checkout "${CURRENT_BRANCH}" 2>/dev/null
- done
-
- - name: "Delete lesser pre-release channels (cascade)"
- continue-on-error: true
- run: |
- API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
- TOKEN="${{ secrets.GA_TOKEN }}"
-
- php ${MOKO_CLI}/release_cascade.php \
- --stability "${{ steps.meta.outputs.stability }}" \
- --token "${TOKEN}" \
- --api-base "${API_BASE}"
-
- - name: Summary
- if: always()
- run: |
- VERSION="${{ steps.meta.outputs.version }}"
- STABILITY="${{ steps.meta.outputs.stability }}"
- ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
- SHA256="${{ steps.zip.outputs.sha256 }}"
- echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
- echo "" >> $GITHUB_STEP_SUMMARY
- echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
- echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
- echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
- echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
- echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
+# Copyright (C) 2026 Moko Consulting
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# FILE INFORMATION
+# DEFGROUP: Gitea.Workflow
+# INGROUP: moko-platform.Release
+# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
+# PATH: /templates/workflows/universal/pre-release.yml.template
+# VERSION: 05.01.00
+# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
+
+name: "Universal: Pre-Release"
+
+on:
+ pull_request:
+ types: [closed]
+ branches:
+ - dev
+ workflow_dispatch:
+ inputs:
+ stability:
+ description: 'Pre-release channel'
+ required: true
+ type: choice
+ options:
+ - development
+ - alpha
+ - beta
+ - release-candidate
+
+permissions:
+ contents: write
+
+env:
+ GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
+ GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
+ GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
+
+jobs:
+ build:
+ name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
+ runs-on: release
+ if: >-
+ github.event_name == 'workflow_dispatch' ||
+ (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev')
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GA_TOKEN }}
+
+ - name: Setup tools
+ run: |
+ # Update moko-platform CLI tools if available; install PHP if missing
+ if command -v moko-platform-update &> /dev/null; then
+ moko-platform-update
+ elif [ -d "/opt/moko-platform" ]; then
+ cd /opt/moko-platform && git pull origin main --quiet 2>/dev/null || true
+ else
+ if ! command -v php &> /dev/null; then
+ sudo apt-get update -qq
+ sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl >/dev/null 2>&1
+ fi
+ git clone --depth 1 --branch main --quiet \
+ "https://x-access-token:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
+ /tmp/moko-platform-api
+ fi
+ # Set MOKO_CLI to whichever path exists
+ if [ -d "/opt/moko-platform/cli" ]; then
+ echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
+ else
+ echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
+ fi
+
+ - name: Detect platform
+ id: platform
+ run: |
+ PLATFORM=$(sed -n 's/.*\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1 | tr -d '[:space:]')
+ [ -z "$PLATFORM" ] && PLATFORM="generic"
+ echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
+ MANIFEST=$(find ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '/dev/null | head -1)
+ [ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "*/packages/*" -exec grep -l '/dev/null | head -1)
+ [ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1)
+ MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
+ echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
+ echo "mod_file=${MOD_FILE}" >> "$GITHUB_OUTPUT"
+
+ - name: Resolve metadata and bump version
+ id: meta
+ run: |
+ STABILITY="${{ inputs.stability || 'development' }}"
+
+ case "$STABILITY" in
+ development) SUFFIX="-dev"; TAG="development" ;;
+ alpha) SUFFIX="-alpha"; TAG="alpha" ;;
+ beta) SUFFIX="-beta"; TAG="beta" ;;
+ release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
+ esac
+
+ # Patch bump via CLI tool
+ php ${MOKO_CLI}/version_bump.php --path .
+ VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
+ [ -z "$VERSION" ] && VERSION="00.00.01"
+ TODAY=$(date +%Y-%m-%d)
+
+ # Update platform-specific manifest
+ PLATFORM="${{ steps.platform.outputs.platform }}"
+ MANIFEST="${{ steps.platform.outputs.manifest }}"
+ MOD_FILE="${{ steps.platform.outputs.mod_file }}"
+
+ php ${MOKO_CLI}/version_set_platform.php \
+ --path . --version "$VERSION" --branch "${{ github.ref_name }}" 2>/dev/null || true
+
+ # Verify version consistency across all files
+ php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
+
+ # Commit version bump
+ 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): pre-release bump to ${VERSION} [skip ci]"
+ git push origin HEAD 2>&1
+ }
+
+ # Auto-detect element (platform-aware)
+ EXT_ELEMENT=""
+ case "$PLATFORM" in
+ joomla)
+ if [ -n "$MANIFEST" ]; then
+ EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
+ if [ -z "$EXT_ELEMENT" ]; then
+ 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
+ fi
+ else
+ EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
+ fi
+ ;;
+ dolibarr)
+ if [ -n "$MOD_FILE" ]; then
+ MOD_BASENAME=$(basename "$MOD_FILE" .class.php)
+ EXT_ELEMENT=$(echo "$MOD_BASENAME" | sed 's/^mod//' | tr '[:upper:]' '[:lower:]')
+ else
+ EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
+ fi
+ ;;
+ *)
+ EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
+ ;;
+ esac
+
+ ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
+
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
+ echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
+ echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
+ echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
+ echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
+ echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
+ echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
+
+ echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
+
+ - name: Build package
+ run: |
+ SOURCE_DIR="src"
+ [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
+ if [ ! -d "$SOURCE_DIR" ]; then
+ echo "::error::No src/ or htdocs/ directory"
+ exit 1
+ fi
+
+ MANIFEST="${{ steps.meta.outputs.manifest }}"
+ EXT_TYPE=""
+ if [ -n "$MANIFEST" ]; then
+ EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
+ fi
+
+ EXCLUDES="sftp-config* .ftpignore *.ppk *.pem *.key .env* *.local .build-trigger"
+
+ mkdir -p build/package
+
+ if [ "$EXT_TYPE" = "package" ] && [ -d "${SOURCE_DIR}/packages" ]; then
+ echo "=== Building Joomla PACKAGE (multi-extension) ==="
+ for ext_dir in "${SOURCE_DIR}"/packages/*/; do
+ [ ! -d "$ext_dir" ] && continue
+ EXT_NAME=$(basename "$ext_dir")
+ echo " Packaging sub-extension: ${EXT_NAME}"
+ cd "$ext_dir"
+ zip -r "../../build/package/${EXT_NAME}.zip" . -x $EXCLUDES
+ cd "$OLDPWD"
+ done
+ for f in "${SOURCE_DIR}"/*.xml "${SOURCE_DIR}"/*.php; do
+ [ -f "$f" ] && cp "$f" build/package/
+ done
+ else
+ echo "=== Building standard extension ==="
+ rsync -a \
+ --exclude='sftp-config*' \
+ --exclude='.ftpignore' \
+ --exclude='*.ppk' \
+ --exclude='*.pem' \
+ --exclude='*.key' \
+ --exclude='.env*' \
+ --exclude='*.local' \
+ --exclude='.build-trigger' \
+ "${SOURCE_DIR}/" build/package/
+ fi
+
+ - name: Create ZIP
+ id: zip
+ run: |
+ ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
+ cd build/package
+ zip -r "../${ZIP_NAME}" .
+ cd ..
+
+ SHA256=$(sha256sum "${ZIP_NAME}" | cut -d' ' -f1)
+ echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
+ echo "ZIP: ${ZIP_NAME} (SHA: ${SHA256:0:16}...)"
+
+ - name: Create or replace Gitea release
+ id: release
+ run: |
+ TAG="${{ steps.meta.outputs.tag }}"
+ VERSION="${{ steps.meta.outputs.version }}"
+ STABILITY="${{ steps.meta.outputs.stability }}"
+ SHA256="${{ steps.zip.outputs.sha256 }}"
+ ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
+ EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}"
+ TOKEN="${{ secrets.GA_TOKEN }}"
+ API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ BRANCH=$(git branch --show-current)
+
+ BODY="## ${VERSION} ($(date +%Y-%m-%d))
+ **Channel:** ${STABILITY}
+ **SHA-256:** \`${SHA256}\`"
+
+ # Delete existing release
+ EXISTING_ID=$(curl -sS -H "Authorization: token ${TOKEN}" \
+ "${API}/releases/tags/${TAG}" | jq -r '.id // empty' 2>/dev/null)
+ if [ -n "$EXISTING_ID" ]; then
+ curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
+ "${API}/releases/${EXISTING_ID}" 2>/dev/null || true
+ curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
+ "${API}/tags/${TAG}" 2>/dev/null || true
+ fi
+
+ # Create release
+ RELEASE_ID=$(curl -sS -X POST -H "Authorization: token ${TOKEN}" \
+ -H "Content-Type: application/json" \
+ "${API}/releases" \
+ -d "$(jq -n \
+ --arg tag "$TAG" \
+ --arg target "$BRANCH" \
+ --arg name "${EXT_ELEMENT} ${VERSION} (${STABILITY})" \
+ --arg body "$BODY" \
+ '{tag_name: $tag, target_commitish: $target, name: $name, body: $body, prerelease: true}'
+ )" | jq -r '.id')
+
+ echo "release_id=${RELEASE_ID}" >> "$GITHUB_OUTPUT"
+
+ # Upload ZIP
+ curl -sS -X POST -H "Authorization: token ${TOKEN}" \
+ -H "Content-Type: application/octet-stream" \
+ "${API}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
+ --data-binary "@build/${ZIP_NAME}"
+
+ echo "Released: ${EXT_ELEMENT} ${VERSION} (${STABILITY})"
+
+ - name: Update updates.xml
+ if: steps.platform.outputs.platform == 'joomla'
+ run: |
+ STABILITY="${{ steps.meta.outputs.stability }}"
+ VERSION="${{ steps.meta.outputs.version }}"
+ SHA256="${{ steps.zip.outputs.sha256 }}"
+ ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
+ TAG="${{ steps.meta.outputs.tag }}"
+
+ if [ ! -f "updates.xml" ]; then
+ echo "No updates.xml -- skipping"
+ exit 0
+ fi
+
+ # Map stability to XML tag name
+ case "$STABILITY" in
+ development) XML_TAG="development" ;;
+ alpha) XML_TAG="alpha" ;;
+ beta) XML_TAG="beta" ;;
+ release-candidate) XML_TAG="rc" ;;
+ *) XML_TAG="$STABILITY" ;;
+ esac
+
+ DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${TAG}/${ZIP_NAME}"
+
+ # Use PHP to update the channel in updates.xml
+ php -r '
+ $xml_tag = $argv[1];
+ $version = $argv[2];
+ $sha256 = $argv[3];
+ $url = $argv[4];
+ $date = date("Y-m-d");
+
+ $content = file_get_contents("updates.xml");
+ $pattern = "/((?:(?!<\/update>).)*?" . preg_quote($xml_tag) . "<\/tag>.*?<\/update>)/s";
+
+ $content = preg_replace_callback($pattern, function($m) use ($version, $sha256, $url, $date) {
+ $block = $m[0];
+ $block = preg_replace("/[^<]*<\/version>/", "{$version}", $block);
+ if (strpos($block, "") !== false) {
+ $block = preg_replace("/[^<]*<\/sha256>/", "{$sha256}", $block);
+ } else {
+ $block = str_replace("", "\n {$sha256}", $block);
+ }
+ $block = preg_replace("/(]*>)[^<]*(<\/downloadurl>)/", "\${1}{$url}\${2}", $block);
+ return $block;
+ }, $content);
+
+ file_put_contents("updates.xml", $content);
+ echo "Updated {$xml_tag} channel: version={$version}\n";
+ ' "$XML_TAG" "$VERSION" "$SHA256" "$DOWNLOAD_URL"
+
+ # Commit and push
+ if ! git diff --quiet updates.xml 2>/dev/null; then
+ git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
+ git config --local user.name "gitea-actions[bot]"
+ git add updates.xml
+ git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
+ git push origin HEAD 2>&1 || echo "WARNING: push failed"
+ fi
+
+ - name: "Sync updates.xml to all branches"
+ if: steps.platform.outputs.platform == 'joomla'
+ run: |
+ CURRENT_BRANCH="${{ github.ref_name }}"
+ git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
+ git config --local user.name "gitea-actions[bot]"
+
+ for BRANCH in main dev; do
+ [ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
+ echo "Syncing updates.xml -> ${BRANCH}"
+ git fetch origin "${BRANCH}" 2>/dev/null || continue
+ git checkout "origin/${BRANCH}" -- . 2>/dev/null || continue
+ git checkout "${CURRENT_BRANCH}" -- updates.xml
+ if ! git diff --quiet updates.xml 2>/dev/null; then
+ git add updates.xml
+ git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
+ git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
+ fi
+ git checkout "${CURRENT_BRANCH}" 2>/dev/null
+ done
+
+ - name: "Delete lesser pre-release channels (cascade)"
+ continue-on-error: true
+ run: |
+ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+ TOKEN="${{ secrets.GA_TOKEN }}"
+
+ php ${MOKO_CLI}/release_cascade.php \
+ --stability "${{ steps.meta.outputs.stability }}" \
+ --token "${TOKEN}" \
+ --api-base "${API_BASE}"
+
+ - name: Summary
+ if: always()
+ run: |
+ VERSION="${{ steps.meta.outputs.version }}"
+ STABILITY="${{ steps.meta.outputs.stability }}"
+ ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
+ SHA256="${{ steps.zip.outputs.sha256 }}"
+ echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
+ echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
+ echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
+ echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
+ echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
--
2.52.0
From c8f4e38f6b530ac6dd45bf5a4c867e905079390d Mon Sep 17 00:00:00 2001
From: "gitea-actions[bot]"
Date: Tue, 26 May 2026 18:58:54 +0000
Subject: [PATCH 5/6] chore(release): build 02.10.03 [skip ci]
---
src/packages/com_mokowaas/mokowaas.xml | 2 +-
src/packages/plg_system_mokowaas/mokowaas.xml | 2 +-
.../plg_webservices_mokowaas/mokowaas.xml | 2 +-
src/pkg_mokowaas.xml | 2 +-
updates.xml | 182 ++++++++----------
5 files changed, 83 insertions(+), 107 deletions(-)
diff --git a/src/packages/com_mokowaas/mokowaas.xml b/src/packages/com_mokowaas/mokowaas.xml
index 7318bff7..9b24b1bb 100644
--- a/src/packages/com_mokowaas/mokowaas.xml
+++ b/src/packages/com_mokowaas/mokowaas.xml
@@ -7,7 +7,7 @@
GPL-3.0-or-later
hello@mokoconsulting.tech
https://mokoconsulting.tech
- 02.10.00
+ 02.10.03
Minimal API-only component for MokoWaaS. Provides REST endpoints for site health, cache, updates, and backups.
Moko\Component\MokoWaaS\Api
diff --git a/src/packages/plg_system_mokowaas/mokowaas.xml b/src/packages/plg_system_mokowaas/mokowaas.xml
index d13ff311..f887dca7 100644
--- a/src/packages/plg_system_mokowaas/mokowaas.xml
+++ b/src/packages/plg_system_mokowaas/mokowaas.xml
@@ -30,7 +30,7 @@
GNU General Public License version 3 or later; see LICENSE.md
hello@mokoconsulting.tech
https://mokoconsulting.tech
- 02.10.00
+ 02.10.03
This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform.
Moko\Plugin\System\MokoWaaS
script.php
diff --git a/src/packages/plg_webservices_mokowaas/mokowaas.xml b/src/packages/plg_webservices_mokowaas/mokowaas.xml
index aa6075e3..d74dc53a 100644
--- a/src/packages/plg_webservices_mokowaas/mokowaas.xml
+++ b/src/packages/plg_webservices_mokowaas/mokowaas.xml
@@ -7,7 +7,7 @@
GPL-3.0-or-later
hello@mokoconsulting.tech
https://mokoconsulting.tech
- 02.10.00
+ 02.10.03
Joomla Web Services API routes for MokoWaaS site management — health checks, cache, updates, backups, and site info.
Moko\Plugin\WebServices\MokoWaaS
diff --git a/src/pkg_mokowaas.xml b/src/pkg_mokowaas.xml
index 7875de20..56439755 100644
--- a/src/pkg_mokowaas.xml
+++ b/src/pkg_mokowaas.xml
@@ -2,7 +2,7 @@
MokoWaaS
mokowaas
- 02.10.00
+ 02.10.03
2026-05-23
Moko Consulting
hello@mokoconsulting.tech
diff --git a/updates.xml b/updates.xml
index 34b00b09..485e3491 100644
--- a/updates.xml
+++ b/updates.xml
@@ -1,124 +1,100 @@
- MokoWaaS
- MokoWaaS update
- pkg_mokowaas
- package
- 02.09.00
- site
- stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
-
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.09.00.zip
-
-
- Moko Consulting
- https://mokoconsulting.tech
-
-
- MokoWaaS
- MokoWaaS update
- pkg_mokowaas
- package
- 02.09.00
- site
- rc
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
-
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
-
-
- Moko Consulting
- https://mokoconsulting.tech
-
-
- MokoWaaS
- MokoWaaS update
- pkg_mokowaas
- package
- 02.09.00
- site
- beta
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
-
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
-
-
- Moko Consulting
- https://mokoconsulting.tech
-
-
- MokoWaaS
- MokoWaaS update
- pkg_mokowaas
- package
- 02.09.00
- site
- alpha
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
-
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
-
-
- Moko Consulting
- https://mokoconsulting.tech
-
-
- MokoWaaS
- MokoWaaS update
- pkg_mokowaas
- package
- 02.09.00
- site
- dev
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
-
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
-
-
- Moko Consulting
- https://mokoconsulting.tech
-
-
- System - MokoWaaS
- MokoWaaS update (legacy plugin)
- mokowaas
- plugin
- system
- site
- 02.09.00
- stable
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
-
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.09.00-dev.zip
-
-
- Moko Consulting
- https://mokoconsulting.tech
-
-
-
MokoWaaS
MokoWaaS development build.
pkg_mokowaas
package
02.10.04
2026-05-26
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development
- https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.10.04-dev.zip
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.10.04-dev.zip
19d135c23a9ade1948547ed955c1b9a0dce857e1d6b2e0a62e20fecc268b5523
development
Moko Consulting
https://mokoconsulting.tech
-
+
+
+
+ MokoWaaS
+ MokoWaaS update
+ pkg_mokowaas
+ package
+ 02.10.03
+ dev
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
+
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.10.03.zip
+
+
+ Moko Consulting
+ https://mokoconsulting.tech
+
+
+ MokoWaaS
+ MokoWaaS update
+ pkg_mokowaas
+ package
+ 02.10.03
+ alpha
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
+
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.10.03.zip
+
+
+ Moko Consulting
+ https://mokoconsulting.tech
+
+
+ MokoWaaS
+ MokoWaaS update
+ pkg_mokowaas
+ package
+ 02.10.03
+ beta
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
+
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.10.03.zip
+
+
+ Moko Consulting
+ https://mokoconsulting.tech
+
+
+ MokoWaaS
+ MokoWaaS update
+ pkg_mokowaas
+ package
+ 02.10.03
+ rc
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
+
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.10.03.zip
+
+
+ Moko Consulting
+ https://mokoconsulting.tech
+
+
+ MokoWaaS
+ MokoWaaS update
+ pkg_mokowaas
+ package
+ 02.10.03
+ stable
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/stable
+
+ https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/stable/pkg_mokowaas-02.10.03.zip
+
+
+ Moko Consulting
+ https://mokoconsulting.tech
-
--
2.52.0
From bacc0eba197383d722b37f5fd38b9d3e5590a92b Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Tue, 26 May 2026 19:03:56 +0000
Subject: [PATCH 6/6] chore: sync .mokogitea/workflows/update-server.yml from
moko-platform [skip ci]
---
.mokogitea/workflows/update-server.yml | 59 +++++++++++++++++---------
1 file changed, 38 insertions(+), 21 deletions(-)
diff --git a/.mokogitea/workflows/update-server.yml b/.mokogitea/workflows/update-server.yml
index 510ad8ef..c77cdaa9 100644
--- a/.mokogitea/workflows/update-server.yml
+++ b/.mokogitea/workflows/update-server.yml
@@ -4,11 +4,11 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
-# INGROUP: MokoStandards.Joomla
-# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
-# PATH: /templates/workflows/joomla/update-server.yml.template
-# VERSION: 04.06.00
-# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries
+# INGROUP: MokoStandards.Universal
+# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
+# PATH: /templates/workflows/update-server.yml
+# VERSION: 04.07.00
+# BRIEF: Update server XML feed with stable/rc/beta/alpha/dev entries (universal)
#
# Writes updates.xml with multiple entries:
# - stable on push to main (from auto-release)
@@ -17,7 +17,7 @@
#
# Joomla filters by user's "Minimum Stability" setting.
-name: "Joomla: Update Server"
+name: "Update Server"
on:
push:
@@ -169,9 +169,12 @@ jobs:
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '' "/")
- CLIENT_TAG=""
- [ -n "$EXT_CLIENT" ] && CLIENT_TAG="${EXT_CLIENT}"
- [ -z "$CLIENT_TAG" ] && ([ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]) && CLIENT_TAG="site"
+ # Joomla requires on ALL extension types for update matching
+ if [ -n "$EXT_CLIENT" ]; then
+ CLIENT_TAG="${EXT_CLIENT}"
+ else
+ CLIENT_TAG="site"
+ fi
FOLDER_TAG=""
[ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="${EXT_FOLDER}"
@@ -384,20 +387,34 @@ jobs:
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
- CONTENT=$(base64 -w0 updates.xml)
- curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \
- -H "Content-Type: application/json" \
- "${API_BASE}/contents/updates.xml" \
- -d "$(python3 -c "import json; print(json.dumps({
- 'content': '${CONTENT}',
- 'sha': '${FILE_SHA}',
- 'message': 'chore: sync updates.xml from ${STABILITY} [skip ci]',
- 'branch': 'main'
- }))")" > /dev/null 2>&1 \
+ python3 -c "
+ import base64, json, urllib.request, sys
+ with open('updates.xml', 'rb') as f:
+ content = base64.b64encode(f.read()).decode()
+ payload = json.dumps({
+ 'content': content,
+ 'sha': '${FILE_SHA}',
+ 'message': 'chore: sync updates.xml from ${STABILITY} [skip ci]',
+ 'branch': 'main'
+ }).encode()
+ req = urllib.request.Request(
+ '${API_BASE}/contents/updates.xml',
+ data=payload, method='PUT',
+ headers={
+ 'Authorization': 'token ${GA_TOKEN}',
+ 'Content-Type': 'application/json'
+ })
+ try:
+ urllib.request.urlopen(req)
+ print('updates.xml synced to main')
+ except Exception as e:
+ print(f'ERROR: failed to sync updates.xml to main: {e}', file=sys.stderr)
+ sys.exit(1)
+ " \
&& echo "updates.xml synced to main (${STABILITY})" >> $GITHUB_STEP_SUMMARY \
- || echo "WARNING: failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY
+ || echo "::error::failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY
else
- echo "WARNING: could not get updates.xml SHA from main" >> $GITHUB_STEP_SUMMARY
+ echo "::error::could not get updates.xml SHA from main — file may not exist on main yet" >> $GITHUB_STEP_SUMMARY
fi
- name: SFTP deploy to dev server
--
2.52.0