From a6bba8559ce7acdd2bb18ccea20bb942d283a6f6 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Mon, 25 May 2026 00:05:14 -0500 Subject: [PATCH 01/29] feat(ci): auto-create feature branch on issue open Creates feature/- from dev when an issue is opened. Comments on the issue with the branch name and checkout command. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- .mokogitea/workflows/issue-branch.yml | 73 +++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .mokogitea/workflows/issue-branch.yml diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml new file mode 100644 index 0000000..c2b02a6 --- /dev/null +++ b/.mokogitea/workflows/issue-branch.yml @@ -0,0 +1,73 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: moko-platform.Automation +# VERSION: 01.00.00 +# BRIEF: Auto-create feature branch when an issue is opened + +name: "Universal: Issue Branch" + +on: + issues: + types: [opened] + +permissions: + contents: write + issues: write + +env: + GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + +jobs: + create-branch: + name: Create feature branch + runs-on: ubuntu-latest + steps: + - name: Create branch and comment + run: | + TOKEN="${{ secrets.GA_TOKEN }}" + API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" + ISSUE_NUM="${{ github.event.issue.number }}" + ISSUE_TITLE="${{ github.event.issue.title }}" + + # Build slug from title: lowercase, replace non-alnum with dash, trim + SLUG=$(echo "${ISSUE_TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40) + BRANCH="feature/${ISSUE_NUM}-${SLUG}" + + # Check dev branch exists + DEV_EXISTS=$(curl -sf -o /dev/null -w '%{http_code}' \ + -H "Authorization: token ${TOKEN}" \ + "${API}/branches/dev" 2>/dev/null || echo "000") + + if [ "${DEV_EXISTS}" != "200" ]; then + echo "No dev branch -- skipping" + exit 0 + fi + + # Create branch from dev + HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \ + -H "Authorization: token ${TOKEN}" \ + -H "Content-Type: application/json" \ + "${API}/branches" \ + -d "{\"new_branch_name\":\"${BRANCH}\",\"old_branch_name\":\"dev\"}" 2>/dev/null || echo "000") + + if [ "${HTTP}" = "201" ]; then + echo "Created branch: ${BRANCH}" + + # Comment on issue with branch link + REPO_URL="${GITEA_URL}/${{ github.repository }}" + BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`" + + curl -sf -X POST \ + -H "Authorization: token ${TOKEN}" \ + -H "Content-Type: application/json" \ + "${API}/issues/${ISSUE_NUM}/comments" \ + -d "{\"body\":\"${BODY}\"}" > /dev/null 2>&1 + + echo "Commented on issue #${ISSUE_NUM}" + else + echo "Failed to create branch (HTTP ${HTTP}) -- may already exist" + fi -- 2.52.0 From d5dd5f316d46c7e95080227e6694a117ddfddf80 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 03:42:58 +0000 Subject: [PATCH 02/29] fix(updates): broaden targetplatform to match Joomla 5.x and 6.x [skip ci] --- updates.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/updates.xml b/updates.xml index a31703b..cfedf4e 100644 --- a/updates.xml +++ b/updates.xml @@ -18,7 +18,7 @@ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/tpl_mokoonyx-02.07.00.zip d2046ccbec46e85f378448e266a9f33b79bebd5730919c03efabeadb871e83b8 - + Moko Consulting https://mokoconsulting.tech @@ -35,7 +35,7 @@ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/tpl_mokoonyx-02.07.00.zip d2046ccbec46e85f378448e266a9f33b79bebd5730919c03efabeadb871e83b8 - + Moko Consulting https://mokoconsulting.tech @@ -52,7 +52,7 @@ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/tpl_mokoonyx-02.07.00.zip d2046ccbec46e85f378448e266a9f33b79bebd5730919c03efabeadb871e83b8 - + Moko Consulting https://mokoconsulting.tech @@ -69,7 +69,7 @@ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/tpl_mokoonyx-02.07.00.zip d2046ccbec46e85f378448e266a9f33b79bebd5730919c03efabeadb871e83b8 - + Moko Consulting https://mokoconsulting.tech @@ -86,7 +86,7 @@ https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/tpl_mokoonyx-02.07.00.zip d2046ccbec46e85f378448e266a9f33b79bebd5730919c03efabeadb871e83b8 - + Moko Consulting https://mokoconsulting.tech -- 2.52.0 From ec812da933eec2a08585ac5324084686458c5b45 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 04:49:17 +0000 Subject: [PATCH 03/29] feat: add update-server.yml with updates.xml integrity check [skip ci] --- .mokogitea/workflows/update-server.yml | 643 +++++++++++++++++++++++++ 1 file changed, 643 insertions(+) create mode 100644 .mokogitea/workflows/update-server.yml diff --git a/.mokogitea/workflows/update-server.yml b/.mokogitea/workflows/update-server.yml new file mode 100644 index 0000000..510ad8e --- /dev/null +++ b/.mokogitea/workflows/update-server.yml @@ -0,0 +1,643 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# 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 +# +# Writes updates.xml with multiple entries: +# - stable on push to main (from auto-release) +# - rc on push to rc/** +# - development on push to dev or dev/** +# +# Joomla filters by user's "Minimum Stability" setting. + +name: "Joomla: Update Server" + +on: + push: + branches: + - 'dev' + - 'dev/**' + - 'alpha/**' + - 'beta/**' + - 'rc/**' + paths: + - 'src/**' + - 'htdocs/**' + pull_request: + types: [closed] + branches: + - 'dev' + - 'dev/**' + - 'alpha/**' + - 'beta/**' + - 'rc/**' + paths: + - 'src/**' + - 'htdocs/**' + workflow_dispatch: + inputs: + stability: + description: 'Stability tag' + required: true + default: 'development' + type: choice + options: + - development + - alpha + - beta + - rc + - stable + +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: + update-xml: + name: Update updates.xml + runs-on: release + if: >- + github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + 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: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.GA_TOKEN }}"}}}' + 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 + if [ -d "/tmp/moko-platform" ]; then + echo "moko-platform already available — skipping clone" + else + git clone --depth 1 --branch main --quiet \ + "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \ + /tmp/moko-platform 2>/dev/null || true + fi + if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then + cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true + fi + + - name: Generate updates.xml entry + id: update + run: | + BRANCH="${{ github.ref_name }}" + REPO="${{ github.repository }}" + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + VERSION=$(php /tmp/moko-platform/cli/version_read.php --path . 2>/dev/null || echo "0.0.0") + + # Auto-bump patch on all branches (dev, alpha, beta, rc) + git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" + git config --local user.name "gitea-actions[bot]" + BUMPED=$(php /tmp/moko-platform/cli/version_bump.php --path . 2>/dev/null || true) + if [ -n "$BUMPED" ]; then + VERSION=$(php /tmp/moko-platform/cli/version_read.php --path . 2>/dev/null || echo "$VERSION") + git add -A + git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \ + --author="gitea-actions[bot] " 2>/dev/null || true + git push 2>/dev/null || true + fi + + # Determine stability from branch or input + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + STABILITY="${{ inputs.stability }}" + elif [[ "$BRANCH" == rc/* ]]; then + STABILITY="rc" + elif [[ "$BRANCH" == beta/* ]]; then + STABILITY="beta" + elif [[ "$BRANCH" == alpha/* ]]; then + STABILITY="alpha" + elif [[ "$BRANCH" == dev/* ]] || [[ "$BRANCH" == "dev" ]]; then + STABILITY="development" + else + STABILITY="stable" + fi + + echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT" + + # Parse manifest (portable — no grep -P) + MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "./build/*" -exec grep -l '/dev/null | head -1) + if [ -z "$MANIFEST" ]; then + echo "No Joomla manifest found — skipping" + exit 0 + fi + + # Extract fields using sed (works on all runners) + EXT_NAME=$(sed -n 's/.*\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1) + EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) + EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1) + EXT_CLIENT=$(sed -n 's/.*]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) + EXT_FOLDER=$(sed -n 's/.*]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) + EXT_VERSION=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1) + TARGET_PLATFORM=$(sed -n 's/.*\(\).*/\1/p' "$MANIFEST" | head -1) + PHP_MINIMUM=$(sed -n 's/.*\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1) + + # Fallbacks + [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}" + [ -z "$EXT_TYPE" ] && EXT_TYPE="component" + + # Derive element if not in manifest: try XML filename, then repo name + if [ -z "$EXT_ELEMENT" ]; then + EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]') + case "$EXT_ELEMENT" in + templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; + esac + fi + + # Use manifest version if README version is empty + [ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION" + + [ -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" + + FOLDER_TAG="" + [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="${EXT_FOLDER}" + + PHP_TAG="" + [ -n "$PHP_MINIMUM" ] && PHP_TAG="${PHP_MINIMUM}" + + # Version suffix for non-stable + DISPLAY_VERSION="$VERSION" + case "$STABILITY" in + development) DISPLAY_VERSION="${VERSION}-dev" ;; + alpha) DISPLAY_VERSION="${VERSION}-alpha" ;; + beta) DISPLAY_VERSION="${VERSION}-beta" ;; + rc) DISPLAY_VERSION="${VERSION}-rc" ;; + esac + + MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') + + # Each stability level has its own release tag + case "$STABILITY" in + development) RELEASE_TAG="development" ;; + alpha) RELEASE_TAG="alpha" ;; + beta) RELEASE_TAG="beta" ;; + rc) RELEASE_TAG="release-candidate" ;; + *) RELEASE_TAG="v${MAJOR}" ;; + esac + + PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip" + DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}" + INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}" + + # -- Build install packages (ZIP + tar.gz) -------------------- + SOURCE_DIR="src" + [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" + if [ -d "$SOURCE_DIR" ]; then + EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*" + TAR_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz" + + cd "$SOURCE_DIR" + zip -r "/tmp/${PACKAGE_NAME}" . -x $EXCLUDES + cd .. + tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \ + --exclude='.ftpignore' --exclude='sftp-config*' \ + --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' . + + SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1) + + # Ensure release exists on Gitea + 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 + # Create release + RELEASE_JSON=$(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_TAG} (${DISPLAY_VERSION})', + 'body': '${STABILITY} release', + 'prerelease': True, + 'target_commitish': 'main' + }))")" 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) + fi + + if [ -n "$RELEASE_ID" ]; then + # Delete existing assets with same name before uploading + ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ + "${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]") + for ASSET_FILE in "$PACKAGE_NAME" "$TAR_NAME"; do + ASSET_ID=$(echo "$ASSETS" | python3 -c " + import sys,json + assets = json.load(sys.stdin) + for a in assets: + if a['name'] == '${ASSET_FILE}': + print(a['id']); break + " 2>/dev/null || true) + if [ -n "$ASSET_ID" ]; then + curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ + "${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true + fi + done + + # Upload both formats + curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @"/tmp/${PACKAGE_NAME}" \ + "${API_BASE}/releases/${RELEASE_ID}/assets?name=${PACKAGE_NAME}" > /dev/null 2>&1 || true + + curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @"/tmp/${TAR_NAME}" \ + "${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true + fi + + echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY + else + SHA256="" + fi + + # -- Build the new entry (canonical format matching release.yml) -- + NEW_ENTRY="" + NEW_ENTRY="${NEW_ENTRY} \n" + NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME}\n" + NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME} ${STABILITY} build.\n" + NEW_ENTRY="${NEW_ENTRY} ${EXT_ELEMENT}\n" + NEW_ENTRY="${NEW_ENTRY} ${EXT_TYPE}\n" + [ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n" + [ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n" + NEW_ENTRY="${NEW_ENTRY} ${VERSION}\n" + NEW_ENTRY="${NEW_ENTRY} $(date +%Y-%m-%d)\n" + NEW_ENTRY="${NEW_ENTRY} https://git.mokoconsulting.tech/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${RELEASE_TAG}\n" + NEW_ENTRY="${NEW_ENTRY} \n" + NEW_ENTRY="${NEW_ENTRY} ${DOWNLOAD_URL}\n" + NEW_ENTRY="${NEW_ENTRY} \n" + [ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} ${SHA256}\n" + NEW_ENTRY="${NEW_ENTRY} ${STABILITY}\n" + NEW_ENTRY="${NEW_ENTRY} Moko Consulting\n" + NEW_ENTRY="${NEW_ENTRY} https://mokoconsulting.tech\n" + NEW_ENTRY="${NEW_ENTRY} \n" + [ -n "$PHP_MINIMUM" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_MINIMUM}\n" + NEW_ENTRY="${NEW_ENTRY} " + + # -- Write new entry to temp file -------------------------------- + printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml + + # -- Merge into updates.xml ---------------------------------------- + # Cascade: stable→all | rc→rc+lower | beta→beta+lower | alpha→alpha+dev | dev→dev + CASCADE_MAP="stable:development,alpha,beta,rc,stable rc:development,alpha,beta,rc beta:development,alpha,beta alpha:development,alpha development:development" + TARGETS="" + for entry in $CASCADE_MAP; do + key="${entry%%:*}" + vals="${entry#*:}" + if [ "$key" = "${STABILITY}" ]; then + TARGETS="$vals" + break + fi + done + [ -z "$TARGETS" ] && TARGETS="${STABILITY}" + + echo "Cascade: ${STABILITY} → ${TARGETS}" + + # Create updates.xml if missing + if [ ! -f "updates.xml" ]; then + printf '%s\n' "" > updates.xml + printf '%s\n' "" >> updates.xml + printf '%s\n' "" >> updates.xml + printf '%s\n' "" >> updates.xml + fi + + # Update existing blocks or create missing ones + export PY_TARGETS="$TARGETS" PY_VERSION="$VERSION" PY_DATE="$(date +%Y-%m-%d)" + python3 << 'PYEOF' + import re, os + + targets = os.environ["PY_TARGETS"].split(",") + version = os.environ["PY_VERSION"] + date = os.environ["PY_DATE"] + + with open("updates.xml") as f: + content = f.read() + with open("/tmp/new_entry.xml") as f: + new_entry_template = f.read() + + for tag in targets: + tag = tag.strip() + # Build entry with this tag's name + new_entry = re.sub(r"[^<]*", f"{tag}", new_entry_template) + + # Try to find existing block (handles both single-line and multi-line ) + block_pattern = r"((?:(?!).)*?" + re.escape(tag) + r".*?)" + match = re.search(block_pattern, content, re.DOTALL) + + if match: + # Update in place — replace entire block + content = content.replace(match.group(1), new_entry.strip()) + print(f" UPDATED: {tag} → {version}") + else: + # Create — insert before + content = content.replace("", "\n" + new_entry.strip() + "\n\n") + print(f" CREATED: {tag} → {version}") + + # Clean up excessive blank lines + content = re.sub(r"\n{3,}", "\n\n", content) + + with open("updates.xml", "w") as f: + f.write(content) + PYEOF + + # Commit + git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" + git config --local user.name "gitea-actions[bot]" + git add updates.xml + git diff --cached --quiet || { + git commit -m "chore: update updates.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \ + --author="gitea-actions[bot] " + git push + } + + # -- Sync updates.xml to main (for non-main branches) ---------------------- + - name: Sync updates.xml to main + if: github.ref_name != 'main' + run: | + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + GA_TOKEN="${{ secrets.GA_TOKEN }}" + + FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \ + "${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 \ + && echo "updates.xml synced to main (${STABILITY})" >> $GITHUB_STEP_SUMMARY \ + || echo "WARNING: failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY + else + echo "WARNING: could not get updates.xml SHA from main" >> $GITHUB_STEP_SUMMARY + fi + + - name: SFTP deploy to dev server + if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev' + env: + DEV_HOST: ${{ vars.DEV_FTP_HOST }} + DEV_PATH: ${{ vars.DEV_FTP_PATH }} + DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }} + DEV_USER: ${{ vars.DEV_FTP_USERNAME }} + DEV_PORT: ${{ vars.DEV_FTP_PORT }} + DEV_KEY: ${{ secrets.DEV_FTP_KEY }} + DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }} + run: | + # -- Permission check: admin or maintain role required -------- + ACTOR="${{ github.actor }}" + REPO="${{ github.repository }}" + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + + PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ + "${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \ + python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read") + case "$PERMISSION" in + admin|maintain|write) ;; + *) + echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write" + exit 0 + ;; + esac + + [ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; } + + SOURCE_DIR="src" + [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" + [ ! -d "$SOURCE_DIR" ] && exit 0 + + PORT="${DEV_PORT:-22}" + REMOTE="${DEV_PATH%/}" + [ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}" + + printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \ + "$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json + if [ -n "$DEV_KEY" ]; then + echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key + printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json + else + printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json + fi + + PLATFORM=$(php /tmp/moko-platform/cli/platform_detect.php --path . 2>/dev/null || true) + if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/moko-platform/deploy/deploy-joomla.php" ]; then + php /tmp/moko-platform/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json + elif [ -f "/tmp/moko-platform/deploy/deploy-sftp.php" ]; then + php /tmp/moko-platform/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json + fi + rm -f /tmp/deploy_key /tmp/sftp-config.json + echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY + + - name: Validate updates.xml integrity + run: | + ERRORS=0 + + if [ ! -f "updates.xml" ]; then + echo "::error::updates.xml not found" + exit 1 + fi + + # Well-formed XML + if ! python3 -c "import xml.etree.ElementTree as ET; ET.parse('updates.xml')" 2>/dev/null; then + echo "::error::updates.xml is not valid XML" + ERRORS=$((ERRORS+1)) + fi + + python3 << 'PYEOF' + import xml.etree.ElementTree as ET, sys, re, os + + tree = ET.parse("updates.xml") + root = tree.getroot() + updates = root.findall("update") + errors = 0 + warnings = 0 + seen_tags = set() + + # All 5 channels MUST be present + REQUIRED_CHANNELS = {"stable", "rc", "beta", "alpha", "dev"} + VALID_TAGS = REQUIRED_CHANNELS | {"development"} # accept legacy alias + REPO = os.environ.get("GITEA_REPO", "") + ORG = os.environ.get("GITEA_ORG", "MokoConsulting") + REPO_BASE = f"https://git.mokoconsulting.tech/{ORG}/" + + # Gitea release tag names per channel (Moko standard) + RELEASE_TAG_MAP = { + "stable": "stable", + "rc": "release-candidate", + "beta": "beta", + "alpha": "alpha", + "dev": "development", + "development": "development", + } + + # Joomla update XML required fields per + # https://docs.joomla.org/Deploying_an_Update_Server + REQUIRED_FIELDS = ["name", "element", "type", "version", "infourl"] + + for i, u in enumerate(updates): + tag_el = u.find("tags/tag") + tag = tag_el.text.strip() if tag_el is not None and tag_el.text else None + label = f"Entry {i+1} ({tag or '?'})" + + # -- Required Joomla fields -- + for field in REQUIRED_FIELDS: + el = u.find(field) + if el is None or not (el.text or "").strip(): + print(f"::error::{label}: missing required <{field}>") + errors += 1 + + # -- -- + dl = u.find("downloads/downloadurl") + if dl is None or not (dl.text or "").strip(): + print(f"::error::{label}: missing ") + errors += 1 + else: + dl_url = dl.text.strip() + # Must point to org repo + if REPO_BASE not in dl_url: + print(f"::error::{label}: download URL not under {REPO_BASE}: {dl_url}") + errors += 1 + # Must end in .zip + if not dl_url.endswith(".zip"): + print(f"::error::{label}: download URL must end in .zip: {dl_url}") + errors += 1 + # Must use correct Gitea release tag in path + if tag and tag in RELEASE_TAG_MAP: + expected_tag = RELEASE_TAG_MAP[tag] + if f"/download/{expected_tag}/" not in dl_url: + print(f"::error::{label}: download URL should contain /download/{expected_tag}/ but got: {dl_url}") + errors += 1 + + # -- (required for Joomla to match update) -- + client = u.find("client") + if client is None or not (client.text or "").strip(): + print(f"::error::{label}: missing (required for Joomla update matching)") + errors += 1 + + # -- -- + tp = u.find("targetplatform") + if tp is None: + print(f"::error::{label}: missing ") + errors += 1 + else: + tp_name = tp.get("name", "") + tp_ver = tp.get("version", "") + if tp_name != "joomla": + print(f"::error::{label}: targetplatform name should be 'joomla', got '{tp_name}'") + errors += 1 + if not tp_ver: + print(f"::error::{label}: targetplatform missing version regex") + errors += 1 + elif "5" not in tp_ver or "6" not in tp_ver: + print(f"::warning::{label}: targetplatform version may not cover Joomla 5+6: {tp_ver}") + warnings += 1 + + # -- must be valid Joomla type -- + type_el = u.find("type") + if type_el is not None and type_el.text: + valid_types = {"component", "module", "plugin", "template", "library", "package", "file"} + if type_el.text.strip() not in valid_types: + print(f"::error::{label}: invalid type '{type_el.text}' (expected: {valid_types})") + errors += 1 + + # -- format (XX.YY.ZZ with optional suffix) -- + ver_el = u.find("version") + if ver_el is not None and ver_el.text: + if not re.match(r"^\d{2}\.\d{2}\.\d{2}(-\w+)?$", ver_el.text.strip()): + print(f"::warning::{label}: version '{ver_el.text}' does not match XX.YY.ZZ format") + warnings += 1 + + # -- and -- + for field in ["maintainer", "maintainerurl"]: + el = u.find(field) + if el is None or not (el.text or "").strip(): + print(f"::warning::{label}: missing <{field}>") + warnings += 1 + + # -- Valid stability tag -- + if tag is None: + print(f"::error::{label}: missing ") + errors += 1 + elif tag not in VALID_TAGS: + print(f"::error::{label}: invalid tag '{tag}' (expected: {VALID_TAGS})") + errors += 1 + + # -- Duplicate tag check -- + norm_tag = "dev" if tag == "development" else tag + if norm_tag in seen_tags: + print(f"::error::{label}: duplicate channel '{tag}'") + errors += 1 + if norm_tag: + seen_tags.add(norm_tag) + + # -- All 5 channels must exist -- + missing = REQUIRED_CHANNELS - seen_tags + if missing: + print(f"::error::Missing required update channels: {', '.join(sorted(missing))}") + errors += 1 + + # -- Version ordering: higher stability must not exceed dev version -- + channel_versions = {} + for u in updates: + tag_el = u.find("tags/tag") + ver_el = u.find("version") + if tag_el is not None and ver_el is not None and tag_el.text and ver_el.text: + norm = "dev" if tag_el.text.strip() == "development" else tag_el.text.strip() + # Strip suffix for comparison (01.00.18-dev -> 01.00.18) + base_ver = re.sub(r"-\w+$", "", ver_el.text.strip()) + channel_versions[norm] = base_ver + + # Cascade check: dev >= alpha >= beta >= rc >= stable + ORDER = ["dev", "alpha", "beta", "rc", "stable"] + for j in range(1, len(ORDER)): + current = ORDER[j] + previous = ORDER[j - 1] + if current in channel_versions and previous in channel_versions: + if channel_versions[current] > channel_versions[previous]: + print(f"::error::{current} version ({channel_versions[current]}) is ahead of {previous} ({channel_versions[previous]})") + errors += 1 + + # -- Summary -- + print(f"\nupdates.xml validation: {len(updates)} entries, {errors} error(s), {warnings} warning(s)") + if errors > 0: + sys.exit(1) + PYEOF + + - name: Summary + if: always() + run: | + echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Version | \`${DISPLAY_VERSION}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $GITHUB_STEP_SUMMARY -- 2.52.0 From 7fe73aced20a14c24a615a23550b5af1ae7a7fca Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 17:35:09 +0000 Subject: [PATCH 04/29] chore(ci): update pre-release.yml from moko-platform [skip ci] --- .mokogitea/workflows/pre-release.yml | 763 ++++++++++++++------------- 1 file changed, 388 insertions(+), 375 deletions(-) diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index 44d3de6..b90d2c8 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -1,375 +1,388 @@ -# 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 - - # 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 +# 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 + paths: + - 'src/**' + - 'htdocs/**' + 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 19ac79b3e7a451549909bb3cb7dc9fd03bacd12b Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 17:36:26 +0000 Subject: [PATCH 05/29] chore(ci): update auto-release.yml from moko-platform [skip ci] --- .mokogitea/workflows/auto-release.yml | 1777 +++++++++++-------------- 1 file changed, 760 insertions(+), 1017 deletions(-) diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index 5bb83fa..9eb98c8 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -1,1017 +1,760 @@ -# 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 and 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: | - # Read platform from manifest.xml element; fallback to generic - 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" - echo "Platform detected: ${PLATFORM}" - # For packages: prefer pkg_*.xml in src/; fallback to any manifest - 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" - - # -- STEP 1: Read version ----------------------------------------------- - - name: "Step 1: Read version from README.md" - id: version - run: | - VERSION=$(php /tmp/moko-platform-api/cli/version_read.php --path . 2>/dev/null) - if [ -z "$VERSION" ]; then - echo "No VERSION in README.md -- skipping release" - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - # Derive major.minor for branch naming (patches update existing branch) - MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}') - PATCH=$(echo "$VERSION" | awk -F. '{print $3}') - - MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') - MINOR_NUM=$(echo "$VERSION" | awk -F. '{print $2}') - - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT" - echo "minor=$MINOR" >> "$GITHUB_OUTPUT" - echo "major=$MAJOR" >> "$GITHUB_OUTPUT" - echo "release_tag=stable" >> "$GITHUB_OUTPUT" - echo "stability=stable" >> "$GITHUB_OUTPUT" - echo "skip=false" >> "$GITHUB_OUTPUT" - if [ "$PATCH" = "00" ] || [ "$PATCH" = "01" ]; then - echo "is_minor=true" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (first release for this minor -- full pipeline)" - else - echo "is_minor=false" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (patch -- platform version + badges only)" - fi - - # -- STEP 1b: Bump minor version (stable = minor bump, reset patch) ------ - - name: "Step 1b: Bump minor version for stable release" - if: steps.version.outputs.skip != 'true' - id: bump - run: | - CURRENT=$(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) - [ -z "$CURRENT" ] && { echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; } - - MAJOR=$((10#$(echo "$CURRENT" | cut -d. -f1))) - MINOR=$((10#$(echo "$CURRENT" | cut -d. -f2))) - - # Minor bump, reset patch. Rollover if minor > 99 - MINOR=$((MINOR + 1)) - if [ $MINOR -gt 99 ]; then - MINOR=0 - MAJOR=$((MAJOR + 1)) - fi - - VERSION=$(printf "%02d.%02d.00" $MAJOR $MINOR) - TODAY=$(date +%Y-%m-%d) - - echo "Stable bump: ${CURRENT} -> ${VERSION} (minor)" - - # Update README.md - sed -i "s/VERSION:[[:space:]]*${CURRENT}/VERSION: ${VERSION}/" README.md - - # Update platform-specific manifest - PLATFORM="${{ steps.platform.outputs.platform }}" - MANIFEST="${{ steps.platform.outputs.manifest }}" - MOD_FILE="${{ steps.platform.outputs.mod_file }}" - case "$PLATFORM" in - joomla) - if [ -n "$MANIFEST" ]; then - MANIFEST_VER=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1) - [ -n "$MANIFEST_VER" ] && sed -i "s|${MANIFEST_VER}|${VERSION}|" "$MANIFEST" - sed -i "s|[^<]*|${TODAY}|" "$MANIFEST" - fi - # For packages: also bump version in all sub-extension manifests - if [ -d "src/packages" ]; then - for SUB_MANIFEST in $(find src/packages -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null); do - SUB_VER=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$SUB_MANIFEST" | head -1) - if [ -n "$SUB_VER" ]; then - sed -i "s|${SUB_VER}|${VERSION}|" "$SUB_MANIFEST" - sed -i "s|[^<]*|${TODAY}|" "$SUB_MANIFEST" - echo " Bumped sub-extension: $(basename $SUB_MANIFEST) ${SUB_VER} -> ${VERSION}" - fi - done - fi - ;; - dolibarr) - if [ -n "$MOD_FILE" ]; then - sed -i "s/\$this->version = '[^']*'/\$this->version = '${VERSION}'/" "$MOD_FILE" - fi - echo "${VERSION}" > update.txt - ;; - *) ;; - esac - - # Promote [Unreleased] section in CHANGELOG.md to new version - if [ -f "CHANGELOG.md" ] && grep -qi "Unreleased" CHANGELOG.md; then - sed -i "s|## \[Unreleased\]|## [${VERSION}] --- ${TODAY}|" CHANGELOG.md - sed -i "s|## Unreleased|## [${VERSION}] --- ${TODAY}|" CHANGELOG.md - sed -i "2i ## [Unreleased]" CHANGELOG.md - sed -i "3i \\ " CHANGELOG.md - echo "CHANGELOG promoted to [${VERSION}]" - fi - - # Commit and push - git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" - git config --local user.name "gitea-actions[bot]" - git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" - git add -A - git diff --cached --quiet || { - git commit -m "chore(version): bump ${CURRENT} -> ${VERSION} [skip ci]" - git push origin HEAD:main 2>&1 - } - - # Override version output for rest of pipeline - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "major=$(printf "%02d" $MAJOR)" >> "$GITHUB_OUTPUT" - - - 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' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" - find . -name "*.md" ! -path "./.git/*" ! -path "./vendor/*" | while read -r f; do - if grep -q '\[VERSION:' "$f" 2>/dev/null; then - sed -i "s/\[VERSION:[[:space:]]*[0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{2\}\]/[VERSION: ${VERSION}]/" "$f" - fi - done - - # -- STEP 5: Write updates.xml (Joomla update server) --------------------- - - name: "Step 5: Write update stream" - id: updates - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" - REPO="${{ github.repository }}" - - # -- Parse extension metadata from XML manifest ---------------- - MANIFEST=$(find ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '/dev/null | head -1) - [ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 2 -name "*.xml" ! -path "*/packages/*" -exec grep -l '/dev/null | head -1) - if [ -z "$MANIFEST" ]; then - echo "Warning: No Joomla XML manifest found -- skipping updates.xml" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - # Extract fields using sed (portable -- no grep -P) - EXT_NAME=$(sed -n 's/.*\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1) - EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1) - EXT_CLIENT=$(sed -n 's/.*]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_FOLDER=$(sed -n 's/.*]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - TARGET_PLATFORM=$(sed -n 's/.*\(\).*/\1/p' "$MANIFEST" | head -1) - PHP_MINIMUM=$(sed -n 's/.*\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1) - - # If EXT_NAME is a language key (e.g. PLG_SYSTEM_MOKOJGDPC), resolve from .ini - if echo "$EXT_NAME" | grep -qE '^[A-Z_]+$'; then - INI_NAME=$(find . -name "*.sys.ini" -path "*/en-GB/*" -exec grep -h "^${EXT_NAME}=" {} \; 2>/dev/null | head -1 | cut -d'"' -f2) - [ -z "$INI_NAME" ] && INI_NAME=$(find . -name "*.sys.ini" -exec grep -h "^${EXT_NAME}=" {} \; 2>/dev/null | head -1 | cut -d'"' -f2) - [ -n "$INI_NAME" ] && EXT_NAME="$INI_NAME" - fi - - # Fallbacks - [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}" - [ -z "$EXT_TYPE" ] && EXT_TYPE="component" - - # Derive element if not in manifest: - # 1. plugin="xxx" attribute (plugins) - # 2. module="xxx" attribute (modules) - # 3. XML filename (components, packages) - # 4. Repo name fallback (templates, anything else) - if [ -z "$EXT_ELEMENT" ]; then - EXT_ELEMENT=$(sed -n 's/.*plugin="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - fi - if [ -z "$EXT_ELEMENT" ]; then - EXT_ELEMENT=$(sed -n 's/.*module="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - fi - if [ -z "$EXT_ELEMENT" ]; then - FNAME=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]') - # If filename is generic (templateDetails, manifest), use repo name - case "$FNAME" in - templatedetails|manifest) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; - *) EXT_ELEMENT="$FNAME" ;; - esac - fi - # Final fallback - [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') - - # Save for Steps 7, 8, 8b - echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT" - echo "ext_name=${EXT_NAME}" >> "$GITHUB_OUTPUT" - echo "ext_type=${EXT_TYPE}" >> "$GITHUB_OUTPUT" - echo "ext_folder=${EXT_FOLDER}" >> "$GITHUB_OUTPUT" - - # Build client tag: plugins and frontend modules need site - CLIENT_TAG="" - if [ -n "$EXT_CLIENT" ]; then - CLIENT_TAG="${EXT_CLIENT}" - elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then - CLIENT_TAG="site" - fi - - # Build folder tag for plugins (required for Joomla to match the update) - FOLDER_TAG="" - if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then - FOLDER_TAG="${EXT_FOLDER}" - fi - - # Build targetplatform (fallback to Joomla 5 if not in manifest) - if [ -z "$TARGET_PLATFORM" ]; then - TARGET_PLATFORM=$(printf '' "/") - fi - - # Build php_minimum tag - PHP_TAG="" - if [ -n "$PHP_MINIMUM" ]; then - PHP_TAG="${PHP_MINIMUM}" - fi - - # Build TYPE_PREFIX for download URL - TYPE_PREFIX="" - case "${EXT_TYPE}" in - plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;; - module) TYPE_PREFIX="mod_" ;; - component) TYPE_PREFIX="com_" ;; - template) TYPE_PREFIX="tpl_" ;; - library) TYPE_PREFIX="lib_" ;; - package) TYPE_PREFIX="pkg_" ;; - esac - - DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip" - INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/stable" - - # -- Build update entry for a given stability tag - build_entry() { - local TAG_NAME="$1" - printf '%s\n' ' ' - printf '%s\n' " ${EXT_NAME}" - printf '%s\n' " ${EXT_NAME} update" - printf '%s\n' " ${EXT_ELEMENT}" - printf '%s\n' " ${EXT_TYPE}" - printf '%s\n' " ${VERSION}" - [ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}" - [ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}" - printf '%s\n' " ${TAG_NAME}" - printf '%s\n' " ${INFO_URL}" - printf '%s\n' ' ' - printf '%s\n' " ${DOWNLOAD_URL}" - printf '%s\n' ' ' - printf '%s\n' " ${TARGET_PLATFORM}" - [ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}" - printf '%s\n' ' Moko Consulting' - printf '%s\n' ' https://mokoconsulting.tech' - printf '%s\n' ' ' - } - - # -- Write updates.xml with cascading channels - # Stable release updates ALL channels (development, alpha, beta, rc, stable) - { - printf '%s\n' "" - printf '%s\n' "" - printf '%s\n' "" - printf '%s\n' '' - build_entry "development" - build_entry "alpha" - build_entry "beta" - build_entry "rc" - build_entry "stable" - printf '%s\n' '' - } > updates.xml - - echo "updates.xml: ${VERSION} (all channels updated to stable)" >> $GITHUB_STEP_SUMMARY - - # -- Commit all changes --------------------------------------------------- - - 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' && - steps.check.outputs.tag_exists != 'true' && - steps.version.outputs.is_minor == '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)" - 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 ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '/dev/null | head -1) - [ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 2 -name "*.xml" ! -path "*/packages/*" -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) - 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/ -- skipping package"; exit 0; } - - EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*" - - if [ "$EXT_TYPE" = "package" ] && [ -d "${SOURCE_DIR}/packages" ]; then - echo "=== Building Joomla PACKAGE (multi-extension) ===" - PKG_STAGE=$(mktemp -d) - - # ZIP each sub-extension - for ext_dir in "${SOURCE_DIR}"/packages/*/; do - [ ! -d "$ext_dir" ] && continue - SUB_NAME=$(basename "$ext_dir") - echo " Packaging sub-extension: ${SUB_NAME}" - (cd "$ext_dir" && zip -r "${PKG_STAGE}/${SUB_NAME}.zip" . -x $EXCLUDES) - done - - # Copy package-level files (manifest, script, etc.) - for f in "${SOURCE_DIR}"/*.xml "${SOURCE_DIR}"/*.php; do - [ -f "$f" ] && cp "$f" "${PKG_STAGE}/" - done - - # Create ZIP and tar.gz from staged package - (cd "$PKG_STAGE" && zip -r "/tmp/${ZIP_NAME}" .) - tar -czf "/tmp/${TAR_NAME}" -C "$PKG_STAGE" . - - rm -rf "$PKG_STAGE" - echo "Package contents built with sub-extension ZIPs" - else - # Standard extension: flat ZIP from src/ - cd "$SOURCE_DIR" - zip -r "/tmp/${ZIP_NAME}" . -x $EXCLUDES - cd .. - - tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \ - --exclude='.ftpignore' --exclude='sftp-config*' \ - --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' . - fi - - 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) - - # -- Delete existing assets with same name before uploading ------ - ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]") - for ASSET_NAME in "$ZIP_NAME" "$TAR_NAME"; do - ASSET_ID=$(echo "$ASSETS" | python3 -c " - import sys,json - assets = json.load(sys.stdin) - for a in assets: - if a['name'] == '${ASSET_NAME}': - print(a['id']); break - " 2>/dev/null || true) - if [ -n "$ASSET_ID" ]; then - curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true - fi - done - - # -- Upload both to release tag ---------------------------------- - curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @"/tmp/${ZIP_NAME}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" > /dev/null 2>&1 || true - - curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @"/tmp/${TAR_NAME}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true - - # -- Update updates.xml with both download formats --------------- - if [ -f "updates.xml" ]; then - ZIP_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}" - TAR_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${TAR_NAME}" - - # Use Python to update only the stable entry's downloads + sha256 - export PY_ZIP_URL="$ZIP_URL" PY_TAR_URL="$TAR_URL" PY_SHA="$SHA256_ZIP" - python3 << 'PYEOF' - import re, os - - with open("updates.xml") as f: - content = f.read() - - zip_url = os.environ["PY_ZIP_URL"] - tar_url = os.environ["PY_TAR_URL"] - sha = os.environ["PY_SHA"] - - # Find the stable update block and replace its downloads + sha256 - def replace_stable(m): - block = m.group(0) - # Replace downloads block - new_downloads = ( - " \n" - f" {zip_url}\n" - " " - ) - block = re.sub(r' .*?', new_downloads, block, flags=re.DOTALL) - # Add or replace sha256 - if '' in block: - block = re.sub(r' .*?', f' {sha}', block) - else: - block = block.replace('', f'\n {sha}') - return block - - content = re.sub( - r' .*?stable.*?', - replace_stable, - content, - flags=re.DOTALL - ) - - with open("updates.xml", "w") as f: - f.write(content) - PYEOF - - CURRENT_BRANCH="${{ github.ref_name }}" - git add updates.xml - git commit -m "chore(release): ZIP + tar.gz for ${VERSION} [skip ci]" \ - --author="gitea-actions[bot] " || true - git push || true - - # Sync updates.xml to main via direct API (always runs -- may be on version/XX branch) - GA_TOKEN="${{ secrets.GA_TOKEN }}" - API="${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}" - - FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \ - "${API}/contents/updates.xml?ref=main" | jq -r '.sha // empty') - - if [ -n "$FILE_SHA" ]; then - CONTENT=$(base64 -w0 updates.xml) - curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \ - -H "Content-Type: application/json" \ - "${API}/contents/updates.xml" \ - -d "$(jq -n \ - --arg content "$CONTENT" \ - --arg sha "$FILE_SHA" \ - --arg msg "chore: sync updates.xml ${VERSION} [skip ci]" \ - --arg branch "main" \ - '{content: $content, sha: $sha, message: $msg, branch: $branch}' - )" > /dev/null 2>&1 \ - && echo "updates.xml synced to main via API" \ - || echo "WARNING: failed to sync updates.xml to main" - else - echo "WARNING: could not get updates.xml SHA from main" - fi - fi - - 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 + SHA ---------------- - - name: "Step 8b: Update release body with changelog and SHA" - if: steps.version.outputs.skip != 'true' - run: | - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" - RELEASE_TAG="${{ steps.version.outputs.release_tag }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}" - EXT_TYPE="${{ steps.updates.outputs.ext_type }}" - EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}" - - # Build TYPE_PREFIX to match Step 8's ZIP naming - TYPE_PREFIX="" - case "${EXT_TYPE}" in - plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;; - module) TYPE_PREFIX="mod_" ;; - component) TYPE_PREFIX="com_" ;; - template) TYPE_PREFIX="tpl_" ;; - library) TYPE_PREFIX="lib_" ;; - package) TYPE_PREFIX="pkg_" ;; - esac - ZIP_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip" - TAR_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.tar.gz" - - # Get SHA from the built files - SHA256_ZIP="" - [ -f "/tmp/${ZIP_NAME}" ] && SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1) - SHA256_TAR="" - [ -f "/tmp/${TAR_NAME}" ] && SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1) - - # Build args for CLI tool - ARGS="--version ${VERSION} --release-tag ${RELEASE_TAG}" - ARGS="${ARGS} --token ${{ secrets.GA_TOKEN }} --api-base ${API_BASE}" - [ -n "$SHA256_ZIP" ] && ARGS="${ARGS} --zip-name ${ZIP_NAME} --zip-sha ${SHA256_ZIP}" - [ -n "$SHA256_TAR" ] && ARGS="${ARGS} --tar-name ${TAR_NAME} --tar-sha ${SHA256_TAR}" - - php /tmp/moko-platform-api/cli/release_body_update.php ${ARGS} --output-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" - 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 }}" - - # Stable deletes all pre-release channels - TAGS_TO_DELETE="development alpha beta release-candidate" - - DELETED=0 - for TAG in $TAGS_TO_DELETE; do - RELEASE_ID=$(curl -sS -H "Authorization: token ${TOKEN}" \ - "${API_BASE}/releases/tags/${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 - curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \ - "${API_BASE}/releases/${RELEASE_ID}" 2>/dev/null || true - curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \ - "${API_BASE}/tags/${TAG}" 2>/dev/null || true - echo "Deleted: ${TAG} (id: ${RELEASE_ID})" - DELETED=$((DELETED + 1)) - fi - done - echo "Cleaned up ${DELETED} pre-release channel(s)" >> $GITHUB_STEP_SUMMARY - - # -- STEP 11: Reset dev branch from main ------------------------------------ - - name: "Step 11: Delete and recreate dev branch from main" - if: steps.version.outputs.skip != 'true' - continue-on-error: true - run: | - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - TOKEN="${{ secrets.GA_TOKEN }}" - - # Delete dev branch - curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \ - "${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch" - - # Recreate dev from main (now includes version bump + changelog promotion) - curl -sf -X POST -H "Authorization: token ${TOKEN}" \ - -H "Content-Type: application/json" \ - "${API_BASE}/branches" \ - -d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main" - - echo "Dev branch reset from main (keeps dev ahead after release)" >> $GITHUB_STEP_SUMMARY - - - # -- 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 + 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: + # ── 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 d30698b82688b88a3703b8c163db9b58be42ccc2 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 19:36:20 +0000 Subject: [PATCH 06/29] chore(ci): update auto-release.yml from moko-platform [skip ci] --- .mokogitea/workflows/auto-release.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index 9eb98c8..eca25d8 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -30,9 +30,6 @@ on: types: [opened, closed] branches: - main - paths: - - 'src/**' - - 'htdocs/**' workflow_dispatch: env: -- 2.52.0 From 4150b267125b552a44b7e8ad19ae42efca414d99 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 19:36:21 +0000 Subject: [PATCH 07/29] chore(ci): update pre-release.yml from moko-platform [skip ci] --- .mokogitea/workflows/pre-release.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index b90d2c8..698251d 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -17,9 +17,6 @@ on: types: [closed] branches: - dev - paths: - - 'src/**' - - 'htdocs/**' workflow_dispatch: inputs: stability: -- 2.52.0 From e321e0f8242a46061ce80efe450e23d9ced2a7a6 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 19:54:38 +0000 Subject: [PATCH 08/29] fix(ci): use release_package.php for Joomla package builds [skip ci] --- .mokogitea/workflows/update-server.yml | 1240 ++++++++++++------------ 1 file changed, 597 insertions(+), 643 deletions(-) diff --git a/.mokogitea/workflows/update-server.yml b/.mokogitea/workflows/update-server.yml index 510ad8e..fd6407f 100644 --- a/.mokogitea/workflows/update-server.yml +++ b/.mokogitea/workflows/update-server.yml @@ -1,643 +1,597 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# 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 -# -# Writes updates.xml with multiple entries: -# - stable on push to main (from auto-release) -# - rc on push to rc/** -# - development on push to dev or dev/** -# -# Joomla filters by user's "Minimum Stability" setting. - -name: "Joomla: Update Server" - -on: - push: - branches: - - 'dev' - - 'dev/**' - - 'alpha/**' - - 'beta/**' - - 'rc/**' - paths: - - 'src/**' - - 'htdocs/**' - pull_request: - types: [closed] - branches: - - 'dev' - - 'dev/**' - - 'alpha/**' - - 'beta/**' - - 'rc/**' - paths: - - 'src/**' - - 'htdocs/**' - workflow_dispatch: - inputs: - stability: - description: 'Stability tag' - required: true - default: 'development' - type: choice - options: - - development - - alpha - - beta - - rc - - stable - -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: - update-xml: - name: Update updates.xml - runs-on: release - if: >- - github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push' - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - 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: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.GA_TOKEN }}"}}}' - 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 - if [ -d "/tmp/moko-platform" ]; then - echo "moko-platform already available — skipping clone" - else - git clone --depth 1 --branch main --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \ - /tmp/moko-platform 2>/dev/null || true - fi - if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then - cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true - fi - - - name: Generate updates.xml entry - id: update - run: | - BRANCH="${{ github.ref_name }}" - REPO="${{ github.repository }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - VERSION=$(php /tmp/moko-platform/cli/version_read.php --path . 2>/dev/null || echo "0.0.0") - - # Auto-bump patch on all branches (dev, alpha, beta, rc) - git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" - git config --local user.name "gitea-actions[bot]" - BUMPED=$(php /tmp/moko-platform/cli/version_bump.php --path . 2>/dev/null || true) - if [ -n "$BUMPED" ]; then - VERSION=$(php /tmp/moko-platform/cli/version_read.php --path . 2>/dev/null || echo "$VERSION") - git add -A - git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \ - --author="gitea-actions[bot] " 2>/dev/null || true - git push 2>/dev/null || true - fi - - # Determine stability from branch or input - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - STABILITY="${{ inputs.stability }}" - elif [[ "$BRANCH" == rc/* ]]; then - STABILITY="rc" - elif [[ "$BRANCH" == beta/* ]]; then - STABILITY="beta" - elif [[ "$BRANCH" == alpha/* ]]; then - STABILITY="alpha" - elif [[ "$BRANCH" == dev/* ]] || [[ "$BRANCH" == "dev" ]]; then - STABILITY="development" - else - STABILITY="stable" - fi - - echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT" - - # Parse manifest (portable — no grep -P) - MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "./build/*" -exec grep -l '/dev/null | head -1) - if [ -z "$MANIFEST" ]; then - echo "No Joomla manifest found — skipping" - exit 0 - fi - - # Extract fields using sed (works on all runners) - EXT_NAME=$(sed -n 's/.*\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1) - EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1) - EXT_CLIENT=$(sed -n 's/.*]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_FOLDER=$(sed -n 's/.*]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_VERSION=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1) - TARGET_PLATFORM=$(sed -n 's/.*\(\).*/\1/p' "$MANIFEST" | head -1) - PHP_MINIMUM=$(sed -n 's/.*\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1) - - # Fallbacks - [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}" - [ -z "$EXT_TYPE" ] && EXT_TYPE="component" - - # Derive element if not in manifest: try XML filename, then repo name - if [ -z "$EXT_ELEMENT" ]; then - EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]') - case "$EXT_ELEMENT" in - templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; - esac - fi - - # Use manifest version if README version is empty - [ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION" - - [ -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" - - FOLDER_TAG="" - [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="${EXT_FOLDER}" - - PHP_TAG="" - [ -n "$PHP_MINIMUM" ] && PHP_TAG="${PHP_MINIMUM}" - - # Version suffix for non-stable - DISPLAY_VERSION="$VERSION" - case "$STABILITY" in - development) DISPLAY_VERSION="${VERSION}-dev" ;; - alpha) DISPLAY_VERSION="${VERSION}-alpha" ;; - beta) DISPLAY_VERSION="${VERSION}-beta" ;; - rc) DISPLAY_VERSION="${VERSION}-rc" ;; - esac - - MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') - - # Each stability level has its own release tag - case "$STABILITY" in - development) RELEASE_TAG="development" ;; - alpha) RELEASE_TAG="alpha" ;; - beta) RELEASE_TAG="beta" ;; - rc) RELEASE_TAG="release-candidate" ;; - *) RELEASE_TAG="v${MAJOR}" ;; - esac - - PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip" - DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}" - INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}" - - # -- Build install packages (ZIP + tar.gz) -------------------- - SOURCE_DIR="src" - [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" - if [ -d "$SOURCE_DIR" ]; then - EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*" - TAR_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz" - - cd "$SOURCE_DIR" - zip -r "/tmp/${PACKAGE_NAME}" . -x $EXCLUDES - cd .. - tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \ - --exclude='.ftpignore' --exclude='sftp-config*' \ - --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' . - - SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1) - - # Ensure release exists on Gitea - 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 - # Create release - RELEASE_JSON=$(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_TAG} (${DISPLAY_VERSION})', - 'body': '${STABILITY} release', - 'prerelease': True, - 'target_commitish': 'main' - }))")" 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) - fi - - if [ -n "$RELEASE_ID" ]; then - # Delete existing assets with same name before uploading - ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]") - for ASSET_FILE in "$PACKAGE_NAME" "$TAR_NAME"; do - ASSET_ID=$(echo "$ASSETS" | python3 -c " - import sys,json - assets = json.load(sys.stdin) - for a in assets: - if a['name'] == '${ASSET_FILE}': - print(a['id']); break - " 2>/dev/null || true) - if [ -n "$ASSET_ID" ]; then - curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true - fi - done - - # Upload both formats - curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @"/tmp/${PACKAGE_NAME}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets?name=${PACKAGE_NAME}" > /dev/null 2>&1 || true - - curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @"/tmp/${TAR_NAME}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true - fi - - echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY - else - SHA256="" - fi - - # -- Build the new entry (canonical format matching release.yml) -- - NEW_ENTRY="" - NEW_ENTRY="${NEW_ENTRY} \n" - NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME}\n" - NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME} ${STABILITY} build.\n" - NEW_ENTRY="${NEW_ENTRY} ${EXT_ELEMENT}\n" - NEW_ENTRY="${NEW_ENTRY} ${EXT_TYPE}\n" - [ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n" - [ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n" - NEW_ENTRY="${NEW_ENTRY} ${VERSION}\n" - NEW_ENTRY="${NEW_ENTRY} $(date +%Y-%m-%d)\n" - NEW_ENTRY="${NEW_ENTRY} https://git.mokoconsulting.tech/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${RELEASE_TAG}\n" - NEW_ENTRY="${NEW_ENTRY} \n" - NEW_ENTRY="${NEW_ENTRY} ${DOWNLOAD_URL}\n" - NEW_ENTRY="${NEW_ENTRY} \n" - [ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} ${SHA256}\n" - NEW_ENTRY="${NEW_ENTRY} ${STABILITY}\n" - NEW_ENTRY="${NEW_ENTRY} Moko Consulting\n" - NEW_ENTRY="${NEW_ENTRY} https://mokoconsulting.tech\n" - NEW_ENTRY="${NEW_ENTRY} \n" - [ -n "$PHP_MINIMUM" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_MINIMUM}\n" - NEW_ENTRY="${NEW_ENTRY} " - - # -- Write new entry to temp file -------------------------------- - printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml - - # -- Merge into updates.xml ---------------------------------------- - # Cascade: stable→all | rc→rc+lower | beta→beta+lower | alpha→alpha+dev | dev→dev - CASCADE_MAP="stable:development,alpha,beta,rc,stable rc:development,alpha,beta,rc beta:development,alpha,beta alpha:development,alpha development:development" - TARGETS="" - for entry in $CASCADE_MAP; do - key="${entry%%:*}" - vals="${entry#*:}" - if [ "$key" = "${STABILITY}" ]; then - TARGETS="$vals" - break - fi - done - [ -z "$TARGETS" ] && TARGETS="${STABILITY}" - - echo "Cascade: ${STABILITY} → ${TARGETS}" - - # Create updates.xml if missing - if [ ! -f "updates.xml" ]; then - printf '%s\n' "" > updates.xml - printf '%s\n' "" >> updates.xml - printf '%s\n' "" >> updates.xml - printf '%s\n' "" >> updates.xml - fi - - # Update existing blocks or create missing ones - export PY_TARGETS="$TARGETS" PY_VERSION="$VERSION" PY_DATE="$(date +%Y-%m-%d)" - python3 << 'PYEOF' - import re, os - - targets = os.environ["PY_TARGETS"].split(",") - version = os.environ["PY_VERSION"] - date = os.environ["PY_DATE"] - - with open("updates.xml") as f: - content = f.read() - with open("/tmp/new_entry.xml") as f: - new_entry_template = f.read() - - for tag in targets: - tag = tag.strip() - # Build entry with this tag's name - new_entry = re.sub(r"[^<]*", f"{tag}", new_entry_template) - - # Try to find existing block (handles both single-line and multi-line ) - block_pattern = r"((?:(?!).)*?" + re.escape(tag) + r".*?)" - match = re.search(block_pattern, content, re.DOTALL) - - if match: - # Update in place — replace entire block - content = content.replace(match.group(1), new_entry.strip()) - print(f" UPDATED: {tag} → {version}") - else: - # Create — insert before - content = content.replace("", "\n" + new_entry.strip() + "\n\n") - print(f" CREATED: {tag} → {version}") - - # Clean up excessive blank lines - content = re.sub(r"\n{3,}", "\n\n", content) - - with open("updates.xml", "w") as f: - f.write(content) - PYEOF - - # Commit - git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" - git config --local user.name "gitea-actions[bot]" - git add updates.xml - git diff --cached --quiet || { - git commit -m "chore: update updates.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \ - --author="gitea-actions[bot] " - git push - } - - # -- Sync updates.xml to main (for non-main branches) ---------------------- - - name: Sync updates.xml to main - if: github.ref_name != 'main' - run: | - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - GA_TOKEN="${{ secrets.GA_TOKEN }}" - - FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \ - "${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 \ - && echo "updates.xml synced to main (${STABILITY})" >> $GITHUB_STEP_SUMMARY \ - || echo "WARNING: failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY - else - echo "WARNING: could not get updates.xml SHA from main" >> $GITHUB_STEP_SUMMARY - fi - - - name: SFTP deploy to dev server - if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev' - env: - DEV_HOST: ${{ vars.DEV_FTP_HOST }} - DEV_PATH: ${{ vars.DEV_FTP_PATH }} - DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }} - DEV_USER: ${{ vars.DEV_FTP_USERNAME }} - DEV_PORT: ${{ vars.DEV_FTP_PORT }} - DEV_KEY: ${{ secrets.DEV_FTP_KEY }} - DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }} - run: | - # -- Permission check: admin or maintain role required -------- - ACTOR="${{ github.actor }}" - REPO="${{ github.repository }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - - PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ - "${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \ - python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read") - case "$PERMISSION" in - admin|maintain|write) ;; - *) - echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write" - exit 0 - ;; - esac - - [ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; } - - SOURCE_DIR="src" - [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" - [ ! -d "$SOURCE_DIR" ] && exit 0 - - PORT="${DEV_PORT:-22}" - REMOTE="${DEV_PATH%/}" - [ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}" - - printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \ - "$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json - if [ -n "$DEV_KEY" ]; then - echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key - printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json - else - printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json - fi - - PLATFORM=$(php /tmp/moko-platform/cli/platform_detect.php --path . 2>/dev/null || true) - if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/moko-platform/deploy/deploy-joomla.php" ]; then - php /tmp/moko-platform/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json - elif [ -f "/tmp/moko-platform/deploy/deploy-sftp.php" ]; then - php /tmp/moko-platform/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json - fi - rm -f /tmp/deploy_key /tmp/sftp-config.json - echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY - - - name: Validate updates.xml integrity - run: | - ERRORS=0 - - if [ ! -f "updates.xml" ]; then - echo "::error::updates.xml not found" - exit 1 - fi - - # Well-formed XML - if ! python3 -c "import xml.etree.ElementTree as ET; ET.parse('updates.xml')" 2>/dev/null; then - echo "::error::updates.xml is not valid XML" - ERRORS=$((ERRORS+1)) - fi - - python3 << 'PYEOF' - import xml.etree.ElementTree as ET, sys, re, os - - tree = ET.parse("updates.xml") - root = tree.getroot() - updates = root.findall("update") - errors = 0 - warnings = 0 - seen_tags = set() - - # All 5 channels MUST be present - REQUIRED_CHANNELS = {"stable", "rc", "beta", "alpha", "dev"} - VALID_TAGS = REQUIRED_CHANNELS | {"development"} # accept legacy alias - REPO = os.environ.get("GITEA_REPO", "") - ORG = os.environ.get("GITEA_ORG", "MokoConsulting") - REPO_BASE = f"https://git.mokoconsulting.tech/{ORG}/" - - # Gitea release tag names per channel (Moko standard) - RELEASE_TAG_MAP = { - "stable": "stable", - "rc": "release-candidate", - "beta": "beta", - "alpha": "alpha", - "dev": "development", - "development": "development", - } - - # Joomla update XML required fields per - # https://docs.joomla.org/Deploying_an_Update_Server - REQUIRED_FIELDS = ["name", "element", "type", "version", "infourl"] - - for i, u in enumerate(updates): - tag_el = u.find("tags/tag") - tag = tag_el.text.strip() if tag_el is not None and tag_el.text else None - label = f"Entry {i+1} ({tag or '?'})" - - # -- Required Joomla fields -- - for field in REQUIRED_FIELDS: - el = u.find(field) - if el is None or not (el.text or "").strip(): - print(f"::error::{label}: missing required <{field}>") - errors += 1 - - # -- -- - dl = u.find("downloads/downloadurl") - if dl is None or not (dl.text or "").strip(): - print(f"::error::{label}: missing ") - errors += 1 - else: - dl_url = dl.text.strip() - # Must point to org repo - if REPO_BASE not in dl_url: - print(f"::error::{label}: download URL not under {REPO_BASE}: {dl_url}") - errors += 1 - # Must end in .zip - if not dl_url.endswith(".zip"): - print(f"::error::{label}: download URL must end in .zip: {dl_url}") - errors += 1 - # Must use correct Gitea release tag in path - if tag and tag in RELEASE_TAG_MAP: - expected_tag = RELEASE_TAG_MAP[tag] - if f"/download/{expected_tag}/" not in dl_url: - print(f"::error::{label}: download URL should contain /download/{expected_tag}/ but got: {dl_url}") - errors += 1 - - # -- (required for Joomla to match update) -- - client = u.find("client") - if client is None or not (client.text or "").strip(): - print(f"::error::{label}: missing (required for Joomla update matching)") - errors += 1 - - # -- -- - tp = u.find("targetplatform") - if tp is None: - print(f"::error::{label}: missing ") - errors += 1 - else: - tp_name = tp.get("name", "") - tp_ver = tp.get("version", "") - if tp_name != "joomla": - print(f"::error::{label}: targetplatform name should be 'joomla', got '{tp_name}'") - errors += 1 - if not tp_ver: - print(f"::error::{label}: targetplatform missing version regex") - errors += 1 - elif "5" not in tp_ver or "6" not in tp_ver: - print(f"::warning::{label}: targetplatform version may not cover Joomla 5+6: {tp_ver}") - warnings += 1 - - # -- must be valid Joomla type -- - type_el = u.find("type") - if type_el is not None and type_el.text: - valid_types = {"component", "module", "plugin", "template", "library", "package", "file"} - if type_el.text.strip() not in valid_types: - print(f"::error::{label}: invalid type '{type_el.text}' (expected: {valid_types})") - errors += 1 - - # -- format (XX.YY.ZZ with optional suffix) -- - ver_el = u.find("version") - if ver_el is not None and ver_el.text: - if not re.match(r"^\d{2}\.\d{2}\.\d{2}(-\w+)?$", ver_el.text.strip()): - print(f"::warning::{label}: version '{ver_el.text}' does not match XX.YY.ZZ format") - warnings += 1 - - # -- and -- - for field in ["maintainer", "maintainerurl"]: - el = u.find(field) - if el is None or not (el.text or "").strip(): - print(f"::warning::{label}: missing <{field}>") - warnings += 1 - - # -- Valid stability tag -- - if tag is None: - print(f"::error::{label}: missing ") - errors += 1 - elif tag not in VALID_TAGS: - print(f"::error::{label}: invalid tag '{tag}' (expected: {VALID_TAGS})") - errors += 1 - - # -- Duplicate tag check -- - norm_tag = "dev" if tag == "development" else tag - if norm_tag in seen_tags: - print(f"::error::{label}: duplicate channel '{tag}'") - errors += 1 - if norm_tag: - seen_tags.add(norm_tag) - - # -- All 5 channels must exist -- - missing = REQUIRED_CHANNELS - seen_tags - if missing: - print(f"::error::Missing required update channels: {', '.join(sorted(missing))}") - errors += 1 - - # -- Version ordering: higher stability must not exceed dev version -- - channel_versions = {} - for u in updates: - tag_el = u.find("tags/tag") - ver_el = u.find("version") - if tag_el is not None and ver_el is not None and tag_el.text and ver_el.text: - norm = "dev" if tag_el.text.strip() == "development" else tag_el.text.strip() - # Strip suffix for comparison (01.00.18-dev -> 01.00.18) - base_ver = re.sub(r"-\w+$", "", ver_el.text.strip()) - channel_versions[norm] = base_ver - - # Cascade check: dev >= alpha >= beta >= rc >= stable - ORDER = ["dev", "alpha", "beta", "rc", "stable"] - for j in range(1, len(ORDER)): - current = ORDER[j] - previous = ORDER[j - 1] - if current in channel_versions and previous in channel_versions: - if channel_versions[current] > channel_versions[previous]: - print(f"::error::{current} version ({channel_versions[current]}) is ahead of {previous} ({channel_versions[previous]})") - errors += 1 - - # -- Summary -- - print(f"\nupdates.xml validation: {len(updates)} entries, {errors} error(s), {warnings} warning(s)") - if errors > 0: - sys.exit(1) - PYEOF - - - name: Summary - if: always() - run: | - echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY - echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Version | \`${DISPLAY_VERSION}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $GITHUB_STEP_SUMMARY +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# 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) +# - rc on push to rc/** +# - development on push to dev or dev/** +# +# Joomla filters by user's "Minimum Stability" setting. + +name: "Update Server" + +on: + push: + branches: + - 'dev' + - 'dev/**' + - 'alpha/**' + - 'beta/**' + - 'rc/**' + paths: + - 'src/**' + - 'htdocs/**' + pull_request: + types: [closed] + branches: + - 'dev' + - 'dev/**' + - 'alpha/**' + - 'beta/**' + - 'rc/**' + paths: + - 'src/**' + - 'htdocs/**' + workflow_dispatch: + inputs: + stability: + description: 'Stability tag' + required: true + default: 'development' + type: choice + options: + - development + - alpha + - beta + - rc + - stable + +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: + update-xml: + name: Update updates.xml + runs-on: release + if: >- + github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + 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: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.GA_TOKEN }}"}}}' + 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 + if [ -d "/tmp/moko-platform" ]; then + echo "moko-platform already available — skipping clone" + else + git clone --depth 1 --branch main --quiet \ + "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \ + /tmp/moko-platform 2>/dev/null || true + fi + if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then + cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true + fi + + - name: Generate updates.xml entry + id: update + run: | + BRANCH="${{ github.ref_name }}" + REPO="${{ github.repository }}" + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + VERSION=$(php /tmp/moko-platform/cli/version_read.php --path . 2>/dev/null || echo "0.0.0") + + # Auto-bump patch on all branches (dev, alpha, beta, rc) + git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" + git config --local user.name "gitea-actions[bot]" + BUMPED=$(php /tmp/moko-platform/cli/version_bump.php --path . 2>/dev/null || true) + if [ -n "$BUMPED" ]; then + VERSION=$(php /tmp/moko-platform/cli/version_read.php --path . 2>/dev/null || echo "$VERSION") + git add -A + git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \ + --author="gitea-actions[bot] " 2>/dev/null || true + git push 2>/dev/null || true + fi + + # Determine stability from branch or input + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + STABILITY="${{ inputs.stability }}" + elif [[ "$BRANCH" == rc/* ]]; then + STABILITY="rc" + elif [[ "$BRANCH" == beta/* ]]; then + STABILITY="beta" + elif [[ "$BRANCH" == alpha/* ]]; then + STABILITY="alpha" + elif [[ "$BRANCH" == dev/* ]] || [[ "$BRANCH" == "dev" ]]; then + STABILITY="development" + else + STABILITY="stable" + fi + + echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT" + + # Parse manifest (portable — no grep -P) + MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "./build/*" -exec grep -l '/dev/null | head -1) + if [ -z "$MANIFEST" ]; then + echo "No Joomla manifest found — skipping" + exit 0 + fi + + # Extract fields using sed (works on all runners) + EXT_NAME=$(sed -n 's/.*\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1) + EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) + EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1) + EXT_CLIENT=$(sed -n 's/.*]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) + EXT_FOLDER=$(sed -n 's/.*]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) + EXT_VERSION=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1) + TARGET_PLATFORM=$(sed -n 's/.*\(\).*/\1/p' "$MANIFEST" | head -1) + PHP_MINIMUM=$(sed -n 's/.*\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1) + + # Fallbacks + [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}" + [ -z "$EXT_TYPE" ] && EXT_TYPE="component" + + # Derive element if not in manifest: try XML filename, then repo name + if [ -z "$EXT_ELEMENT" ]; then + EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]') + case "$EXT_ELEMENT" in + templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; + esac + fi + + # Use manifest version if README version is empty + [ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION" + + [ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '' "/") + + # 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}" + + PHP_TAG="" + [ -n "$PHP_MINIMUM" ] && PHP_TAG="${PHP_MINIMUM}" + + # Version suffix for non-stable + DISPLAY_VERSION="$VERSION" + case "$STABILITY" in + development) DISPLAY_VERSION="${VERSION}-dev" ;; + alpha) DISPLAY_VERSION="${VERSION}-alpha" ;; + beta) DISPLAY_VERSION="${VERSION}-beta" ;; + rc) DISPLAY_VERSION="${VERSION}-rc" ;; + esac + + MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') + + # Each stability level has its own release tag + case "$STABILITY" in + development) RELEASE_TAG="development" ;; + alpha) RELEASE_TAG="alpha" ;; + beta) RELEASE_TAG="beta" ;; + rc) RELEASE_TAG="release-candidate" ;; + *) RELEASE_TAG="v${MAJOR}" ;; + esac + + PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip" + DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}" + INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}" + + # -- Build install packages and upload to release -------------------- + php /tmp/moko-platform/cli/release_package.php \ + --path . --version "${DISPLAY_VERSION}" --tag "${RELEASE_TAG}" \ + --token "${{ secrets.GA_TOKEN }}" --api-base "${API_BASE}" \ + --repo "${GITEA_REPO}" --output /tmp 2>&1 || true + + echo "Package built and uploaded for ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY + + # -- Build the new entry (canonical format matching release.yml) -- + NEW_ENTRY="" + NEW_ENTRY="${NEW_ENTRY} \n" + NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME}\n" + NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME} ${STABILITY} build.\n" + NEW_ENTRY="${NEW_ENTRY} ${EXT_ELEMENT}\n" + NEW_ENTRY="${NEW_ENTRY} ${EXT_TYPE}\n" + [ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n" + [ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n" + NEW_ENTRY="${NEW_ENTRY} ${VERSION}\n" + NEW_ENTRY="${NEW_ENTRY} $(date +%Y-%m-%d)\n" + NEW_ENTRY="${NEW_ENTRY} https://git.mokoconsulting.tech/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${RELEASE_TAG}\n" + NEW_ENTRY="${NEW_ENTRY} \n" + NEW_ENTRY="${NEW_ENTRY} ${DOWNLOAD_URL}\n" + NEW_ENTRY="${NEW_ENTRY} \n" + [ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} ${SHA256}\n" + NEW_ENTRY="${NEW_ENTRY} ${STABILITY}\n" + NEW_ENTRY="${NEW_ENTRY} Moko Consulting\n" + NEW_ENTRY="${NEW_ENTRY} https://mokoconsulting.tech\n" + NEW_ENTRY="${NEW_ENTRY} \n" + [ -n "$PHP_MINIMUM" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_MINIMUM}\n" + NEW_ENTRY="${NEW_ENTRY} " + + # -- Write new entry to temp file -------------------------------- + printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml + + # -- Merge into updates.xml ---------------------------------------- + # Cascade: stable→all | rc→rc+lower | beta→beta+lower | alpha→alpha+dev | dev→dev + CASCADE_MAP="stable:development,alpha,beta,rc,stable rc:development,alpha,beta,rc beta:development,alpha,beta alpha:development,alpha development:development" + TARGETS="" + for entry in $CASCADE_MAP; do + key="${entry%%:*}" + vals="${entry#*:}" + if [ "$key" = "${STABILITY}" ]; then + TARGETS="$vals" + break + fi + done + [ -z "$TARGETS" ] && TARGETS="${STABILITY}" + + echo "Cascade: ${STABILITY} → ${TARGETS}" + + # Create updates.xml if missing + if [ ! -f "updates.xml" ]; then + printf '%s\n' "" > updates.xml + printf '%s\n' "" >> updates.xml + printf '%s\n' "" >> updates.xml + printf '%s\n' "" >> updates.xml + fi + + # Update existing blocks or create missing ones + export PY_TARGETS="$TARGETS" PY_VERSION="$VERSION" PY_DATE="$(date +%Y-%m-%d)" + python3 << 'PYEOF' + import re, os + + targets = os.environ["PY_TARGETS"].split(",") + version = os.environ["PY_VERSION"] + date = os.environ["PY_DATE"] + + with open("updates.xml") as f: + content = f.read() + with open("/tmp/new_entry.xml") as f: + new_entry_template = f.read() + + for tag in targets: + tag = tag.strip() + # Build entry with this tag's name + new_entry = re.sub(r"[^<]*", f"{tag}", new_entry_template) + + # Try to find existing block (handles both single-line and multi-line ) + block_pattern = r"((?:(?!).)*?" + re.escape(tag) + r".*?)" + match = re.search(block_pattern, content, re.DOTALL) + + if match: + # Update in place — replace entire block + content = content.replace(match.group(1), new_entry.strip()) + print(f" UPDATED: {tag} → {version}") + else: + # Create — insert before + content = content.replace("", "\n" + new_entry.strip() + "\n\n") + print(f" CREATED: {tag} → {version}") + + # Clean up excessive blank lines + content = re.sub(r"\n{3,}", "\n\n", content) + + with open("updates.xml", "w") as f: + f.write(content) + PYEOF + + # Commit + git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" + git config --local user.name "gitea-actions[bot]" + git add updates.xml + git diff --cached --quiet || { + git commit -m "chore: update updates.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \ + --author="gitea-actions[bot] " + git push + } + + # -- Sync updates.xml to main (for non-main branches) ---------------------- + - name: Sync updates.xml to main + if: github.ref_name != 'main' + run: | + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + GA_TOKEN="${{ secrets.GA_TOKEN }}" + + FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \ + "${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 + 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 "::error::failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY + else + 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 + if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev' + env: + DEV_HOST: ${{ vars.DEV_FTP_HOST }} + DEV_PATH: ${{ vars.DEV_FTP_PATH }} + DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }} + DEV_USER: ${{ vars.DEV_FTP_USERNAME }} + DEV_PORT: ${{ vars.DEV_FTP_PORT }} + DEV_KEY: ${{ secrets.DEV_FTP_KEY }} + DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }} + run: | + # -- Permission check: admin or maintain role required -------- + ACTOR="${{ github.actor }}" + REPO="${{ github.repository }}" + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + + PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ + "${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \ + python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read") + case "$PERMISSION" in + admin|maintain|write) ;; + *) + echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write" + exit 0 + ;; + esac + + [ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; } + + SOURCE_DIR="src" + [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" + [ ! -d "$SOURCE_DIR" ] && exit 0 + + PORT="${DEV_PORT:-22}" + REMOTE="${DEV_PATH%/}" + [ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}" + + printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \ + "$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json + if [ -n "$DEV_KEY" ]; then + echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key + printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json + else + printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json + fi + + PLATFORM=$(php /tmp/moko-platform/cli/platform_detect.php --path . 2>/dev/null || true) + if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/moko-platform/deploy/deploy-joomla.php" ]; then + php /tmp/moko-platform/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json + elif [ -f "/tmp/moko-platform/deploy/deploy-sftp.php" ]; then + php /tmp/moko-platform/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json + fi + rm -f /tmp/deploy_key /tmp/sftp-config.json + echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY + + - name: Validate updates.xml integrity + run: | + ERRORS=0 + + if [ ! -f "updates.xml" ]; then + echo "::error::updates.xml not found" + exit 1 + fi + + # Well-formed XML + if ! python3 -c "import xml.etree.ElementTree as ET; ET.parse('updates.xml')" 2>/dev/null; then + echo "::error::updates.xml is not valid XML" + ERRORS=$((ERRORS+1)) + fi + + python3 << 'PYEOF' + import xml.etree.ElementTree as ET, sys, re, os + + tree = ET.parse("updates.xml") + root = tree.getroot() + updates = root.findall("update") + errors = 0 + warnings = 0 + seen_tags = set() + + # All 5 channels MUST be present + REQUIRED_CHANNELS = {"stable", "rc", "beta", "alpha", "dev"} + VALID_TAGS = REQUIRED_CHANNELS | {"development"} # accept legacy alias + REPO = os.environ.get("GITEA_REPO", "") + ORG = os.environ.get("GITEA_ORG", "MokoConsulting") + REPO_BASE = f"https://git.mokoconsulting.tech/{ORG}/" + + # Gitea release tag names per channel (Moko standard) + RELEASE_TAG_MAP = { + "stable": "stable", + "rc": "release-candidate", + "beta": "beta", + "alpha": "alpha", + "dev": "development", + "development": "development", + } + + # Joomla update XML required fields per + # https://docs.joomla.org/Deploying_an_Update_Server + REQUIRED_FIELDS = ["name", "element", "type", "version", "infourl"] + + for i, u in enumerate(updates): + tag_el = u.find("tags/tag") + tag = tag_el.text.strip() if tag_el is not None and tag_el.text else None + label = f"Entry {i+1} ({tag or '?'})" + + # -- Required Joomla fields -- + for field in REQUIRED_FIELDS: + el = u.find(field) + if el is None or not (el.text or "").strip(): + print(f"::error::{label}: missing required <{field}>") + errors += 1 + + # -- -- + dl = u.find("downloads/downloadurl") + if dl is None or not (dl.text or "").strip(): + print(f"::error::{label}: missing ") + errors += 1 + else: + dl_url = dl.text.strip() + # Must point to org repo + if REPO_BASE not in dl_url: + print(f"::error::{label}: download URL not under {REPO_BASE}: {dl_url}") + errors += 1 + # Must end in .zip + if not dl_url.endswith(".zip"): + print(f"::error::{label}: download URL must end in .zip: {dl_url}") + errors += 1 + # Must use correct Gitea release tag in path + if tag and tag in RELEASE_TAG_MAP: + expected_tag = RELEASE_TAG_MAP[tag] + if f"/download/{expected_tag}/" not in dl_url: + print(f"::error::{label}: download URL should contain /download/{expected_tag}/ but got: {dl_url}") + errors += 1 + + # -- (required for Joomla to match update) -- + client = u.find("client") + if client is None or not (client.text or "").strip(): + print(f"::error::{label}: missing (required for Joomla update matching)") + errors += 1 + + # -- -- + tp = u.find("targetplatform") + if tp is None: + print(f"::error::{label}: missing ") + errors += 1 + else: + tp_name = tp.get("name", "") + tp_ver = tp.get("version", "") + if tp_name != "joomla": + print(f"::error::{label}: targetplatform name should be 'joomla', got '{tp_name}'") + errors += 1 + if not tp_ver: + print(f"::error::{label}: targetplatform missing version regex") + errors += 1 + elif "5" not in tp_ver or "6" not in tp_ver: + print(f"::warning::{label}: targetplatform version may not cover Joomla 5+6: {tp_ver}") + warnings += 1 + + # -- must be valid Joomla type -- + type_el = u.find("type") + if type_el is not None and type_el.text: + valid_types = {"component", "module", "plugin", "template", "library", "package", "file"} + if type_el.text.strip() not in valid_types: + print(f"::error::{label}: invalid type '{type_el.text}' (expected: {valid_types})") + errors += 1 + + # -- format (XX.YY.ZZ with optional suffix) -- + ver_el = u.find("version") + if ver_el is not None and ver_el.text: + if not re.match(r"^\d{2}\.\d{2}\.\d{2}(-\w+)?$", ver_el.text.strip()): + print(f"::warning::{label}: version '{ver_el.text}' does not match XX.YY.ZZ format") + warnings += 1 + + # -- and -- + for field in ["maintainer", "maintainerurl"]: + el = u.find(field) + if el is None or not (el.text or "").strip(): + print(f"::warning::{label}: missing <{field}>") + warnings += 1 + + # -- Valid stability tag -- + if tag is None: + print(f"::error::{label}: missing ") + errors += 1 + elif tag not in VALID_TAGS: + print(f"::error::{label}: invalid tag '{tag}' (expected: {VALID_TAGS})") + errors += 1 + + # -- Duplicate tag check -- + norm_tag = "dev" if tag == "development" else tag + if norm_tag in seen_tags: + print(f"::error::{label}: duplicate channel '{tag}'") + errors += 1 + if norm_tag: + seen_tags.add(norm_tag) + + # -- All 5 channels must exist -- + missing = REQUIRED_CHANNELS - seen_tags + if missing: + print(f"::error::Missing required update channels: {', '.join(sorted(missing))}") + errors += 1 + + # -- Version ordering: higher stability must not exceed dev version -- + channel_versions = {} + for u in updates: + tag_el = u.find("tags/tag") + ver_el = u.find("version") + if tag_el is not None and ver_el is not None and tag_el.text and ver_el.text: + norm = "dev" if tag_el.text.strip() == "development" else tag_el.text.strip() + # Strip suffix for comparison (01.00.18-dev -> 01.00.18) + base_ver = re.sub(r"-\w+$", "", ver_el.text.strip()) + channel_versions[norm] = base_ver + + # Cascade check: dev >= alpha >= beta >= rc >= stable + ORDER = ["dev", "alpha", "beta", "rc", "stable"] + for j in range(1, len(ORDER)): + current = ORDER[j] + previous = ORDER[j - 1] + if current in channel_versions and previous in channel_versions: + if channel_versions[current] > channel_versions[previous]: + print(f"::error::{current} version ({channel_versions[current]}) is ahead of {previous} ({channel_versions[previous]})") + errors += 1 + + # -- Summary -- + print(f"\nupdates.xml validation: {len(updates)} entries, {errors} error(s), {warnings} warning(s)") + if errors > 0: + sys.exit(1) + PYEOF + + - name: Summary + if: always() + run: | + echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Version | \`${DISPLAY_VERSION}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $GITHUB_STEP_SUMMARY -- 2.52.0 From 4929cb0d0e36b7121db2377454f9acce85055dea Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 22:12:07 +0000 Subject: [PATCH 09/29] chore(ci): add auto-bump.yml from moko-platform [skip ci] --- .mokogitea/workflows/auto-bump.yml | 82 ++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 .mokogitea/workflows/auto-bump.yml diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml new file mode 100644 index 0000000..ef0aedc --- /dev/null +++ b/.mokogitea/workflows/auto-bump.yml @@ -0,0 +1,82 @@ +# 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: /.mokogitea/workflows/auto-bump.yml +# VERSION: 09.02.00 +# BRIEF: Auto patch-bump version on every push to dev + +name: "Universal: Auto Version Bump" + +on: + push: + branches: + - dev + +env: + GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + +permissions: + contents: write + +jobs: + bump: + name: Patch Bump + runs-on: release + if: >- + !contains(github.event.head_commit.message, '[skip ci]') && + !contains(github.event.head_commit.message, '[skip bump]') + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + token: ${{ secrets.GA_TOKEN }} + fetch-depth: 1 + + - name: Setup moko-platform tools + 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 + if [ -d "/opt/moko-platform/cli" ]; then + echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV" + else + 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 + cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet + echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV" + fi + + - name: Patch bump version + run: | + BUMP=$(php ${MOKO_CLI}/version_bump.php --path . 2>&1) || true + echo "$BUMP" + + VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) || true + [ -z "$VERSION" ] && { echo "No version found — skipping"; exit 0; } + + # Propagate to platform manifests + php ${MOKO_CLI}/version_set_platform.php \ + --path . --version "$VERSION" --branch dev 2>/dev/null || true + php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true + + # Commit if anything changed + if git diff --quiet && git diff --cached --quiet; then + echo "No version changes to commit" + exit 0 + fi + + 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 commit -m "chore(version): patch bump to ${VERSION} [skip ci]" \ + --author="gitea-actions[bot] " + git push origin dev + echo "Bumped to ${VERSION}" >> $GITHUB_STEP_SUMMARY -- 2.52.0 From be47d213f3274bc19511171f45e787aa9b5a1004 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 22:13:19 +0000 Subject: [PATCH 10/29] chore(ci): update pre-release.yml from moko-platform [skip ci] --- .mokogitea/workflows/pre-release.yml | 104 +++++---------------------- 1 file changed, 16 insertions(+), 88 deletions(-) diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index 698251d..83443c7 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -78,15 +78,7 @@ jobs: - 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" + php ${MOKO_CLI}/manifest_read.php --path . --github-output - name: Resolve metadata and bump version id: meta @@ -100,16 +92,9 @@ jobs: release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;; esac - # Patch bump via CLI tool - php ${MOKO_CLI}/version_bump.php --path . + # Read current version (bump already handled by push workflow) 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 @@ -127,36 +112,16 @@ jobs: 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 + # Auto-detect element via manifest_element.php + php ${MOKO_CLI}/manifest_element.php \ + --path . --version "$VERSION" --stability "$STABILITY" \ + --repo "${GITEA_REPO}" --github-output - ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip" + # Read back element outputs + EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2) + ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2) + [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') + [ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip" echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT" @@ -278,54 +243,17 @@ jobs: - 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 }}" + STABILITY="${{ steps.meta.outputs.stability }}" 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" + php ${MOKO_CLI}/updates_xml_build.php \ + --path . --version "${VERSION}" --stability "${STABILITY}" \ + --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" # Commit and push if ! git diff --quiet updates.xml 2>/dev/null; then @@ -347,7 +275,7 @@ jobs: [ "$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 "origin/${BRANCH}" -- updates.xml 2>/dev/null || continue git checkout "${CURRENT_BRANCH}" -- updates.xml if ! git diff --quiet updates.xml 2>/dev/null; then git add updates.xml -- 2.52.0 From e2871bc5c64a26e68e3a033ede6340732ef30b8e Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 22:23:55 +0000 Subject: [PATCH 11/29] chore(ci): update auto-release.yml from moko-platform [skip ci] --- .mokogitea/workflows/auto-release.yml | 380 ++++---------------------- 1 file changed, 51 insertions(+), 329 deletions(-) diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index eca25d8..d665ce7 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -31,6 +31,15 @@ on: branches: - main workflow_dispatch: + inputs: + action: + description: 'Action to perform' + required: false + type: choice + default: release + options: + - release + - promote-rc env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true @@ -47,8 +56,8 @@ jobs: name: Promote Pre-Release to RC runs-on: release if: >- - github.event.action == 'opened' && - github.event.pull_request.draft == true + (github.event.action == 'opened' && github.event.pull_request.draft == true) || + (github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc') steps: - name: Checkout repository @@ -100,7 +109,8 @@ jobs: name: Build & Release Pipeline runs-on: release if: >- - github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' + github.event.pull_request.merged == true || + (github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc') steps: - name: Checkout repository @@ -170,18 +180,7 @@ jobs: 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}" + # Version bump handled by auto-bump.yml (minor on main, patch on dev) - name: Check if already released if: steps.version.outputs.skip != 'true' @@ -208,96 +207,9 @@ jobs: 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 + VERSION="${{ steps.version.outputs.version }}" + php /tmp/moko-platform-api/cli/release_validate.php \ + --path . --version "$VERSION" --output-summary --github-output || true # -- STEP 2: Create or update version/XX.YY archive branch --------------- # Always runs — every version change on main archives to version/XX.YY @@ -306,7 +218,7 @@ jobs: run: | BRANCH="${{ steps.version.outputs.branch }}" IS_MINOR="${{ steps.version.outputs.is_minor }}" - PATCH="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + PATCH="${{ steps.version.outputs.version }}" PATCH_NUM=$(echo "$PATCH" | awk -F. '{print $3}') # Check if branch exists @@ -325,7 +237,7 @@ jobs: steps.version.outputs.skip != 'true' && steps.check.outputs.already_released != 'true' run: | - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + VERSION="${{ steps.version.outputs.version }}" php /tmp/moko-platform-api/cli/version_set_platform.php \ --path . --version "$VERSION" --branch main @@ -333,7 +245,7 @@ jobs: - name: "Step 4: Update version badges" if: steps.version.outputs.skip != 'true' run: | - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + 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 @@ -342,7 +254,7 @@ jobs: steps.version.outputs.skip != 'true' && steps.platform.outputs.platform == 'joomla' run: | - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + VERSION="${{ steps.version.outputs.version }}" # Fetch latest updates.xml from main so preserve logic has all channels GA_TOKEN="${{ secrets.GA_TOKEN }}" @@ -366,7 +278,7 @@ jobs: echo "No changes to commit" exit 0 fi - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + 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 @@ -413,187 +325,34 @@ jobs: steps.version.outputs.skip != 'true' && steps.rc.outputs.promote != 'true' run: | - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + 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}" + php /tmp/moko-platform-api/cli/release_create.php \ + --path . --version "$VERSION" --tag "$RELEASE_TAG" \ + --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --repo "${GITEA_REPO}" --branch main + echo "Release created: ${VERSION}" >> $GITHUB_STEP_SUMMARY - # 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" + # -- STEP 8: Build packages and upload to release ---------------------------- + - name: "Step 8: Build package and upload" if: >- steps.version.outputs.skip != 'true' && steps.rc.outputs.promote != 'true' run: | - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + 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 + php /tmp/moko-platform-api/cli/release_package.php \ + --path . --version "$VERSION" --tag "$RELEASE_TAG" \ + --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --repo "${GITEA_REPO}" --output /tmp || true # -- 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 }}" + VERSION="${{ steps.version.outputs.version }}" RELEASE_TAG="${{ steps.version.outputs.release_tag }}" MOKO_CLI="/tmp/moko-platform-api/cli" @@ -621,44 +380,19 @@ jobs: - 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 }}" + 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 + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + php /tmp/moko-platform-api/cli/release_mirror.php \ + --version "$VERSION" --tag "$RELEASE_TAG" \ + --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --gh-token "${{ secrets.GH_TOKEN }}" --gh-repo "$GH_REPO" \ + --branch main 2>&1 || true + echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY # -- STEP 10: Sync main branch to GitHub mirror ---------------------------- - name: "Step 10: Push main to GitHub mirror" @@ -682,7 +416,7 @@ jobs: - name: "Delete lesser pre-release channels" continue-on-error: true run: | - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + 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 \ @@ -711,32 +445,20 @@ jobs: # -- 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 != '' + - name: "Post-release: Reset dev version" + 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 }}" - 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 + php /tmp/moko-platform-api/cli/version_reset_dev.php \ + --token "${{ secrets.GA_TOKEN }}" --api-base "${API_BASE}" \ + --branch dev --path . 2>&1 || true # -- Summary -------------------------------------------------------------- - name: Pipeline Summary if: always() run: | - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + VERSION="${{ steps.version.outputs.version }}" PLATFORM="${{ steps.platform.outputs.platform }}" if [ "${{ steps.version.outputs.skip }}" = "true" ]; then echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY -- 2.52.0 From 5f1599d64cf67d04ea366d2840c3207788d7f454 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 22:25:12 +0000 Subject: [PATCH 12/29] chore(ci): update auto-bump.yml from moko-platform [skip ci] --- .mokogitea/workflows/auto-bump.yml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml index ef0aedc..d1dedf1 100644 --- a/.mokogitea/workflows/auto-bump.yml +++ b/.mokogitea/workflows/auto-bump.yml @@ -16,6 +16,7 @@ on: push: branches: - dev + - main env: GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} @@ -53,9 +54,20 @@ jobs: echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV" fi - - name: Patch bump version + - name: Bump version run: | - BUMP=$(php ${MOKO_CLI}/version_bump.php --path . 2>&1) || true + BRANCH="${{ github.ref_name }}" + + # main = minor bump, dev = patch bump + if [ "$BRANCH" = "main" ]; then + BUMP_TYPE="--minor" + BUMP_LABEL="minor" + else + BUMP_TYPE="" + BUMP_LABEL="patch" + fi + + BUMP=$(php ${MOKO_CLI}/version_bump.php --path . $BUMP_TYPE 2>&1) || true echo "$BUMP" VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) || true @@ -63,7 +75,7 @@ jobs: # Propagate to platform manifests php ${MOKO_CLI}/version_set_platform.php \ - --path . --version "$VERSION" --branch dev 2>/dev/null || true + --path . --version "$VERSION" --branch "$BRANCH" 2>/dev/null || true php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true # Commit if anything changed @@ -76,7 +88,7 @@ jobs: 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 commit -m "chore(version): patch bump to ${VERSION} [skip ci]" \ + git commit -m "chore(version): ${BUMP_LABEL} bump to ${VERSION} [skip ci]" \ --author="gitea-actions[bot] " - git push origin dev - echo "Bumped to ${VERSION}" >> $GITHUB_STEP_SUMMARY + git push origin "$BRANCH" + echo "Bumped to ${VERSION} (${BUMP_LABEL})" >> $GITHUB_STEP_SUMMARY -- 2.52.0 From e83f5631fb5f16a84a8d4ab959427c6e7b51744f Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 22:35:35 +0000 Subject: [PATCH 13/29] chore(ci): update auto-release.yml from moko-platform [skip ci] --- .mokogitea/workflows/auto-release.yml | 55 ++++++++++++++++++--------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index d665ce7..e555c14 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -249,25 +249,7 @@ jobs: 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.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 + # Step 5 (updates.xml) moved after Step 8 to include SHA-256 checksum - name: Commit release changes if: >- @@ -336,6 +318,7 @@ jobs: # -- STEP 8: Build packages and upload to release ---------------------------- - name: "Step 8: Build package and upload" + id: package if: >- steps.version.outputs.skip != 'true' && steps.rc.outputs.promote != 'true' @@ -348,6 +331,40 @@ jobs: --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ --repo "${GITEA_REPO}" --output /tmp || true + # -- STEP 5: Write update stream (after build so SHA-256 is available) ----- + - name: "Step 5: Write update stream" + if: steps.version.outputs.skip != 'true' + run: | + VERSION="${{ steps.version.outputs.version }}" + SHA256="${{ steps.package.outputs.sha256_zip }}" + + # 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 + + SHA_FLAG="" + [ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}" + + 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}" \ + ${SHA_FLAG} --github-output + + # Commit updates.xml if changed + 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 remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" + git add updates.xml + git commit -m "chore: update stable channel ${VERSION} [skip ci]" \ + --author="gitea-actions[bot] " + git push origin HEAD 2>&1 || true + fi + # -- STEP 8b: Update release description with changelog ---------------------- - name: "Step 8b: Update release body" if: steps.version.outputs.skip != 'true' -- 2.52.0 From a293ac6a69c9068d1793bca97a2f2225f70e236e Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 22:36:57 +0000 Subject: [PATCH 14/29] chore(ci): update pre-release.yml from moko-platform [skip ci] --- .mokogitea/workflows/pre-release.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index 83443c7..a84e469 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -162,9 +162,18 @@ jobs: zip -r "../../build/package/${EXT_NAME}.zip" . -x $EXCLUDES cd "$OLDPWD" done + # Copy top-level files (manifest XML, script PHP, etc.) for f in "${SOURCE_DIR}"/*.xml "${SOURCE_DIR}"/*.php; do [ -f "$f" ] && cp "$f" build/package/ done + # Copy top-level directories (language/, media/, etc.) — exclude packages/ + for d in "${SOURCE_DIR}"/*/; do + [ ! -d "$d" ] && continue + DIRNAME=$(basename "$d") + [ "$DIRNAME" = "packages" ] && continue + cp -r "$d" "build/package/${DIRNAME}" + echo " Included dir: ${DIRNAME}/" + done else echo "=== Building standard extension ===" rsync -a \ @@ -245,15 +254,20 @@ jobs: run: | VERSION="${{ steps.meta.outputs.version }}" STABILITY="${{ steps.meta.outputs.stability }}" + SHA256="${{ steps.zip.outputs.sha256 }}" if [ ! -f "updates.xml" ]; then echo "No updates.xml -- skipping" exit 0 fi + SHA_FLAG="" + [ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}" + php ${MOKO_CLI}/updates_xml_build.php \ --path . --version "${VERSION}" --stability "${STABILITY}" \ - --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" + --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \ + ${SHA_FLAG} # Commit and push if ! git diff --quiet updates.xml 2>/dev/null; then -- 2.52.0 From f383462d050dffe573d153b33c225a23de1a2725 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 22:48:28 +0000 Subject: [PATCH 15/29] chore(ci): update auto-release.yml from moko-platform [skip ci] --- .mokogitea/workflows/auto-release.yml | 60 +++++++++++++-------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index e555c14..80908e4 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -87,7 +87,7 @@ jobs: --from auto --to release-candidate \ --token "${{ secrets.GA_TOKEN }}" \ --api-base "${API_BASE}" \ - --branch "${{ github.event.pull_request.head.ref }}" + --branch "${{ github.event.pull_request.head.ref || 'dev' }}" - name: Cascade lesser channels continue-on-error: true @@ -180,7 +180,17 @@ jobs: echo "::notice::No RC release — full build pipeline" fi - # Version bump handled by auto-bump.yml (minor on main, patch on dev) + - name: "Step 1b: Minor bump version" + id: bump + if: >- + steps.version.outputs.skip != 'true' && + steps.rc.outputs.promote != 'true' + run: | + MOKO_API="/tmp/moko-platform-api/cli" + php ${MOKO_API}/version_bump.php --path . --minor 2>&1 || true + 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' @@ -207,7 +217,7 @@ jobs: steps.version.outputs.skip != 'true' && steps.check.outputs.already_released != 'true' run: | - VERSION="${{ steps.version.outputs.version }}" + VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" php /tmp/moko-platform-api/cli/release_validate.php \ --path . --version "$VERSION" --output-summary --github-output || true @@ -218,7 +228,7 @@ jobs: run: | BRANCH="${{ steps.version.outputs.branch }}" IS_MINOR="${{ steps.version.outputs.is_minor }}" - PATCH="${{ steps.version.outputs.version }}" + PATCH="${{ steps.bump.outputs.version || steps.version.outputs.version }}" PATCH_NUM=$(echo "$PATCH" | awk -F. '{print $3}') # Check if branch exists @@ -237,7 +247,7 @@ jobs: steps.version.outputs.skip != 'true' && steps.check.outputs.already_released != 'true' run: | - VERSION="${{ steps.version.outputs.version }}" + VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" php /tmp/moko-platform-api/cli/version_set_platform.php \ --path . --version "$VERSION" --branch main @@ -245,7 +255,7 @@ jobs: - name: "Step 4: Update version badges" if: steps.version.outputs.skip != 'true' run: | - VERSION="${{ steps.version.outputs.version }}" + 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 @@ -260,7 +270,7 @@ jobs: echo "No changes to commit" exit 0 fi - VERSION="${{ steps.version.outputs.version }}" + 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 @@ -292,7 +302,7 @@ jobs: steps.version.outputs.skip != 'true' && steps.rc.outputs.promote == 'true' run: | - VERSION="${{ steps.version.outputs.version }}" + 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_promote.php \ --from release-candidate --to stable \ @@ -307,7 +317,7 @@ jobs: steps.version.outputs.skip != 'true' && steps.rc.outputs.promote != 'true' run: | - VERSION="${{ steps.version.outputs.version }}" + VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" RELEASE_TAG="${{ steps.version.outputs.release_tag }}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" php /tmp/moko-platform-api/cli/release_create.php \ @@ -323,7 +333,7 @@ jobs: steps.version.outputs.skip != 'true' && steps.rc.outputs.promote != 'true' run: | - VERSION="${{ steps.version.outputs.version }}" + VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" RELEASE_TAG="${{ steps.version.outputs.release_tag }}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" php /tmp/moko-platform-api/cli/release_package.php \ @@ -335,7 +345,7 @@ jobs: - name: "Step 5: Write update stream" if: steps.version.outputs.skip != 'true' run: | - VERSION="${{ steps.version.outputs.version }}" + VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" SHA256="${{ steps.package.outputs.sha256_zip }}" # Fetch latest updates.xml from main so preserve logic has all channels @@ -368,29 +378,15 @@ jobs: # -- STEP 8b: Update release description with changelog ---------------------- - name: "Step 8b: Update release body" if: steps.version.outputs.skip != 'true' + continue-on-error: true run: | - VERSION="${{ steps.version.outputs.version }}" + 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 \ + php /tmp/moko-platform-api/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 - } + 2>&1 || true echo "Release body updated" >> $GITHUB_STEP_SUMMARY # -- STEP 9: Mirror to GitHub (stable only) -------------------------------- @@ -400,7 +396,7 @@ jobs: secrets.GH_TOKEN != '' continue-on-error: true run: | - VERSION="${{ steps.version.outputs.version }}" + VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" RELEASE_TAG="${{ steps.version.outputs.release_tag }}" GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" @@ -433,7 +429,7 @@ jobs: - name: "Delete lesser pre-release channels" continue-on-error: true run: | - VERSION="${{ steps.version.outputs.version }}" + 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 \ @@ -475,7 +471,7 @@ jobs: - name: Pipeline Summary if: always() run: | - VERSION="${{ steps.version.outputs.version }}" + 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 -- 2.52.0 From 654b27925c203a5b52a7d411205af9b619e82baa Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 22:49:41 +0000 Subject: [PATCH 16/29] chore(ci): update auto-bump.yml from moko-platform [skip ci] --- .mokogitea/workflows/auto-bump.yml | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml index d1dedf1..10a7e51 100644 --- a/.mokogitea/workflows/auto-bump.yml +++ b/.mokogitea/workflows/auto-bump.yml @@ -8,7 +8,7 @@ # REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # PATH: /.mokogitea/workflows/auto-bump.yml # VERSION: 09.02.00 -# BRIEF: Auto patch-bump version on every push to dev +# BRIEF: Auto patch-bump version on every push to dev (skips merge commits) name: "Universal: Auto Version Bump" @@ -16,9 +16,9 @@ on: push: branches: - dev - - main env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} permissions: @@ -26,11 +26,12 @@ permissions: jobs: bump: - name: Patch Bump + name: Version Bump runs-on: release if: >- !contains(github.event.head_commit.message, '[skip ci]') && - !contains(github.event.head_commit.message, '[skip bump]') + !contains(github.event.head_commit.message, '[skip bump]') && + !startsWith(github.event.head_commit.message, 'Merge pull request') steps: - name: Checkout @@ -56,18 +57,7 @@ jobs: - name: Bump version run: | - BRANCH="${{ github.ref_name }}" - - # main = minor bump, dev = patch bump - if [ "$BRANCH" = "main" ]; then - BUMP_TYPE="--minor" - BUMP_LABEL="minor" - else - BUMP_TYPE="" - BUMP_LABEL="patch" - fi - - BUMP=$(php ${MOKO_CLI}/version_bump.php --path . $BUMP_TYPE 2>&1) || true + BUMP=$(php ${MOKO_CLI}/version_bump.php --path . 2>&1) || true echo "$BUMP" VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) || true @@ -75,7 +65,7 @@ jobs: # Propagate to platform manifests php ${MOKO_CLI}/version_set_platform.php \ - --path . --version "$VERSION" --branch "$BRANCH" 2>/dev/null || true + --path . --version "$VERSION" --branch dev 2>/dev/null || true php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true # Commit if anything changed @@ -88,7 +78,7 @@ jobs: 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 commit -m "chore(version): ${BUMP_LABEL} bump to ${VERSION} [skip ci]" \ + git commit -m "chore(version): patch bump to ${VERSION} [skip ci]" \ --author="gitea-actions[bot] " - git push origin "$BRANCH" - echo "Bumped to ${VERSION} (${BUMP_LABEL})" >> $GITHUB_STEP_SUMMARY + git push origin dev + echo "Bumped to ${VERSION}" >> $GITHUB_STEP_SUMMARY -- 2.52.0 From 7d2ea3e04f489ee7a2da28943f5ac8151c7b861d Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Tue, 26 May 2026 22:50:53 +0000 Subject: [PATCH 17/29] chore(ci): update pre-release.yml from moko-platform [skip ci] --- .mokogitea/workflows/pre-release.yml | 162 +++++---------------------- 1 file changed, 29 insertions(+), 133 deletions(-) diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index a84e469..f0b1d88 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -52,28 +52,19 @@ jobs: fetch-depth: 0 token: ${{ secrets.GA_TOKEN }} - - name: Setup tools + - name: Setup moko-platform tools + env: + MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }} + MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting 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" + 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 + echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV" - name: Detect platform id: platform @@ -129,132 +120,37 @@ jobs: 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 - # Copy top-level files (manifest XML, script PHP, etc.) - for f in "${SOURCE_DIR}"/*.xml "${SOURCE_DIR}"/*.php; do - [ -f "$f" ] && cp "$f" build/package/ - done - # Copy top-level directories (language/, media/, etc.) — exclude packages/ - for d in "${SOURCE_DIR}"/*/; do - [ ! -d "$d" ] && continue - DIRNAME=$(basename "$d") - [ "$DIRNAME" = "packages" ] && continue - cp -r "$d" "build/package/${DIRNAME}" - echo " Included dir: ${DIRNAME}/" - 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 + - name: Create 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) + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + php ${MOKO_CLI}/release_create.php \ + --path . --version "$VERSION" --tag "$TAG" \ + --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --repo "${GITEA_REPO}" --branch dev --prerelease - 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: Build package and upload + id: package + run: | + VERSION="${{ steps.meta.outputs.version }}" + TAG="${{ steps.meta.outputs.tag }}" + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + php ${MOKO_CLI}/release_package.php \ + --path . --version "$VERSION" --tag "$TAG" \ + --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --repo "${GITEA_REPO}" --output /tmp || true - name: Update updates.xml if: steps.platform.outputs.platform == 'joomla' run: | VERSION="${{ steps.meta.outputs.version }}" STABILITY="${{ steps.meta.outputs.stability }}" - SHA256="${{ steps.zip.outputs.sha256 }}" + SHA256="${{ steps.package.outputs.sha256_zip }}" if [ ! -f "updates.xml" ]; then echo "No updates.xml -- skipping" @@ -316,7 +212,7 @@ jobs: VERSION="${{ steps.meta.outputs.version }}" STABILITY="${{ steps.meta.outputs.stability }}" ZIP_NAME="${{ steps.meta.outputs.zip_name }}" - SHA256="${{ steps.zip.outputs.sha256 }}" + SHA256="${{ steps.package.outputs.sha256_zip }}" echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY -- 2.52.0 From 2b370497d1f7beb04fa83657e4a066c3be213fa3 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech> Date: Wed, 27 May 2026 05:32:49 +0000 Subject: [PATCH 18/29] docs: update CHANGELOG with infrastructure changes [skip ci] --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3086158..73a9c96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Migrated all workflow and template paths from `.github/` to `.mokogitea/` +- Template source paths updated +- HCL definition files removed -- Template repos are now the canonical source + +### Added +- `branch-cleanup.yml`: auto-delete merged feature branches after PR merge + ### Removed - Removed deploy-manual.yml workflow -- switching to Joomla update server method for extension distribution - Removed deploy variables and secrets (DEV_FTP_*) -- 2.52.0 From 13f29a0df8e72a0b56aa3e8b82117607b6d8c991 Mon Sep 17 00:00:00 2001 From: Moko Consulting Date: Thu, 28 May 2026 13:42:18 -0500 Subject: [PATCH 19/29] feat(workflows): append stability suffix to manifest versions [skip bump] --- .mokogitea/workflows/pre-release.yml | 235 +++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index f0b1d88..45e19d2 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -1,3 +1,4 @@ +<<<<<<< Updated upstream # Copyright (C) 2026 Moko Consulting # # SPDX-License-Identifier: GPL-3.0-or-later @@ -221,3 +222,237 @@ jobs: 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 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 + echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV" + + - name: Detect platform + id: platform + run: | + php ${MOKO_CLI}/manifest_read.php --path . --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 + + # Read current version (bump already handled by push workflow) + VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) + [ -z "$VERSION" ] && VERSION="00.00.01" + + 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 + + # Append suffix to all manifest tags + if [ -n "$SUFFIX" ]; then + find . -maxdepth 4 -name "*.xml" ! -path "./.git/*" ! -path "./build/*" \ + -exec grep -l "${VERSION}" {} \; 2>/dev/null | while read f; do + sed -i "s|${VERSION}|${VERSION}${SUFFIX}|g" "$f" + done + VERSION="${VERSION}${SUFFIX}" + fi + + # 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 via manifest_element.php + php ${MOKO_CLI}/manifest_element.php \ + --path . --version "$VERSION" --stability "$STABILITY" \ + --repo "${GITEA_REPO}" --github-output + + # Read back element outputs + EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2) + ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2) + [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') + [ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.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 "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ===" + + - name: Create release + id: release + run: | + TAG="${{ steps.meta.outputs.tag }}" + VERSION="${{ steps.meta.outputs.version }}" + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + php ${MOKO_CLI}/release_create.php \ + --path . --version "$VERSION" --tag "$TAG" \ + --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --repo "${GITEA_REPO}" --branch dev --prerelease + + - name: Build package and upload + id: package + run: | + VERSION="${{ steps.meta.outputs.version }}" + TAG="${{ steps.meta.outputs.tag }}" + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + php ${MOKO_CLI}/release_package.php \ + --path . --version "$VERSION" --tag "$TAG" \ + --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --repo "${GITEA_REPO}" --output /tmp || true + + - name: Update updates.xml + if: steps.platform.outputs.platform == 'joomla' + run: | + VERSION="${{ steps.meta.outputs.version }}" + STABILITY="${{ steps.meta.outputs.stability }}" + SHA256="${{ steps.package.outputs.sha256_zip }}" + + if [ ! -f "updates.xml" ]; then + echo "No updates.xml -- skipping" + exit 0 + fi + + SHA_FLAG="" + [ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}" + + php ${MOKO_CLI}/updates_xml_build.php \ + --path . --version "${VERSION}" --stability "${STABILITY}" \ + --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \ + ${SHA_FLAG} + + # 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}" -- updates.xml 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.package.outputs.sha256_zip }}" + 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 +>>>>>>> Stashed changes -- 2.52.0 From 9aa01cccc577d87aa792af96be2f518071ffe04f Mon Sep 17 00:00:00 2001 From: Moko Consulting Date: Thu, 28 May 2026 14:02:48 -0500 Subject: [PATCH 20/29] chore: trigger update-server workflow for version suffix [skip bump] --- src/templateDetails.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/templateDetails.xml b/src/templateDetails.xml index 8f3fab3..af75889 100644 --- a/src/templateDetails.xml +++ b/src/templateDetails.xml @@ -366,3 +366,4 @@ + -- 2.52.0 From 6f940be6d25743d080805d24b101b0f089fe07bd Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Thu, 28 May 2026 19:03:01 +0000 Subject: [PATCH 21/29] chore(version): auto-bump patch 02.07.01 [skip ci] --- .mokogitea/manifest.xml | 1 + src/templateDetails.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index 0745b52..632ecd4 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -8,6 +8,7 @@ MokoOnyx MokoConsulting MokoOnyx - Joomla site template (successor to MokoCassiopeia) + 02.07.01 GNU General Public License v3 diff --git a/src/templateDetails.xml b/src/templateDetails.xml index af75889..8d92817 100644 --- a/src/templateDetails.xml +++ b/src/templateDetails.xml @@ -36,7 +36,7 @@ MokoOnyx - 02.07.00 + 02.07.01 script.php 2026-05-16 Jonathan Miller || Moko Consulting -- 2.52.0 From e6543251d8915c4776ca24934d9997edd0672979 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Thu, 28 May 2026 19:03:02 +0000 Subject: [PATCH 22/29] chore: update updates.xml (development: 02.07.01-dev) [skip ci] --- updates.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/updates.xml b/updates.xml index cfedf4e..3713c6b 100644 --- a/updates.xml +++ b/updates.xml @@ -7,20 +7,20 @@ MokoOnyx - MokoOnyx update + MokoOnyx development build. mokoonyx template - 02.07.00 site - development - https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/stable + 02.07.01 + 2026-05-28 + https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/development - https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/stable/tpl_mokoonyx-02.07.00.zip + https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/mokoonyx-02.07.01-dev.zip - d2046ccbec46e85f378448e266a9f33b79bebd5730919c03efabeadb871e83b8 - + development Moko Consulting https://mokoconsulting.tech + MokoOnyx -- 2.52.0 From 37317288cc38e63285b0c0e6f3c4c00391316b0f Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 28 May 2026 14:05:07 -0500 Subject: [PATCH 23/29] style: add margin-right to .fa-solid icons Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- src/media/css/template.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/media/css/template.css b/src/media/css/template.css index 45af99b..129d4c4 100644 --- a/src/media/css/template.css +++ b/src/media/css/template.css @@ -23506,3 +23506,7 @@ padding: 0.25rem; font-size: 0.8125rem; } } + +.fa-solid { + margin-right: 0.25rem; +} -- 2.52.0 From d5e2018540af3c72bba5c1ce689b0ca228c3d9d9 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Thu, 28 May 2026 19:09:32 +0000 Subject: [PATCH 24/29] chore(version): auto-bump patch 02.07.02 [skip ci] --- .mokogitea/manifest.xml | 2 +- src/templateDetails.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index 632ecd4..614bebd 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -8,7 +8,7 @@ MokoOnyx MokoConsulting MokoOnyx - Joomla site template (successor to MokoCassiopeia) - 02.07.01 + 02.07.02 GNU General Public License v3 diff --git a/src/templateDetails.xml b/src/templateDetails.xml index 8d92817..6c2fa0b 100644 --- a/src/templateDetails.xml +++ b/src/templateDetails.xml @@ -36,7 +36,7 @@ MokoOnyx - 02.07.01 + 02.07.02 script.php 2026-05-16 Jonathan Miller || Moko Consulting -- 2.52.0 From 79ce5f4a492a1a6c1b05aa661ef6a41ba93eee31 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Thu, 28 May 2026 19:09:33 +0000 Subject: [PATCH 25/29] chore: update updates.xml (development: 02.07.02-dev) [skip ci] --- updates.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/updates.xml b/updates.xml index 3713c6b..72c543e 100644 --- a/updates.xml +++ b/updates.xml @@ -11,11 +11,11 @@ mokoonyx template site - 02.07.01 + 02.07.02 2026-05-28 https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/development - https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/mokoonyx-02.07.01-dev.zip + https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/mokoonyx-02.07.02-dev.zip development Moko Consulting -- 2.52.0 From f76b24de136e625dd1f77419cc78454daf5cd9bb Mon Sep 17 00:00:00 2001 From: Moko Consulting Date: Thu, 28 May 2026 14:16:01 -0500 Subject: [PATCH 26/29] =?UTF-8?q?fix(workflows):=20proper=20suffix=20handl?= =?UTF-8?q?ing=20=E2=80=94=20use=20version=5Fset=5Fplatform=20instead=20of?= =?UTF-8?q?=20sed=20[skip=20bump]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .mokogitea/workflows/auto-bump.yml | 7 +- .mokogitea/workflows/auto-release.yml | 33 ++ .mokogitea/workflows/pre-release.yml | 247 +----------- .mokogitea/workflows/update-server.yml | 505 ++++++------------------- 4 files changed, 156 insertions(+), 636 deletions(-) diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml index 10a7e51..4d0ffc0 100644 --- a/.mokogitea/workflows/auto-bump.yml +++ b/.mokogitea/workflows/auto-bump.yml @@ -63,10 +63,11 @@ jobs: VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) || true [ -z "$VERSION" ] && { echo "No version found — skipping"; exit 0; } - # Propagate to platform manifests + # Propagate to platform manifests with -dev suffix php ${MOKO_CLI}/version_set_platform.php \ - --path . --version "$VERSION" --branch dev 2>/dev/null || true + --path . --version "$VERSION" --branch dev --stability dev 2>/dev/null || true php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true + VERSION="${VERSION}-dev" # Commit if anything changed if git diff --quiet && git diff --cached --quiet; then @@ -78,7 +79,7 @@ jobs: 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 commit -m "chore(version): patch bump to ${VERSION} [skip ci]" \ + git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \ --author="gitea-actions[bot] " git push origin dev echo "Bumped to ${VERSION}" >> $GITHUB_STEP_SUMMARY diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index 80908e4..8cdf410 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -155,6 +155,8 @@ jobs: echo "skip=true" >> "$GITHUB_OUTPUT" exit 0 fi + # Strip any pre-release suffix merged from dev (e.g. 01.02.20-dev → 01.02.20) + VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//') MAJOR=$(echo "$VERSION" | cut -d. -f1) echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "release_tag=stable" >> "$GITHUB_OUTPUT" @@ -261,6 +263,18 @@ jobs: # Step 5 (updates.xml) moved after Step 8 to include SHA-256 checksum + - name: "Step 4b: Promote and prune CHANGELOG" + if: >- + steps.version.outputs.skip != 'true' && + steps.check.outputs.already_released != 'true' + run: | + VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + MOKO_API="/tmp/moko-platform-api/cli" + if [ -f "CHANGELOG.md" ]; then + php ${MOKO_API}/changelog_promote.php --path . --version "$VERSION" 2>&1 || true + php ${MOKO_API}/changelog_prune.php --path . --keep 5 2>&1 || true + fi + - name: Commit release changes if: >- steps.version.outputs.skip != 'true' && @@ -456,6 +470,25 @@ jobs: echo "Dev branch reset from main (keeps dev ahead after release)" >> $GITHUB_STEP_SUMMARY + - name: "Step 12: Create version 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 }}" + VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + BRANCH_NAME="version/${VERSION}" + MAIN_SHA=$(git rev-parse HEAD) + + # Delete old version branch if it exists (same version re-release) + curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}" + + # Create version/XX.YY.ZZ from main + curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed" + + echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY + + # -- Dolibarr post-release: Reset dev version ----------------------------- - name: "Post-release: Reset dev version" diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index 45e19d2..edbe60e 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -1,4 +1,3 @@ -<<<<<<< Updated upstream # Copyright (C) 2026 Moko Consulting # # SPDX-License-Identifier: GPL-3.0-or-later @@ -88,12 +87,20 @@ jobs: VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) [ -z "$VERSION" ] && VERSION="00.00.01" + # Strip any existing suffix from version before applying stability + VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//') + php ${MOKO_CLI}/version_set_platform.php \ - --path . --version "$VERSION" --branch "${{ github.ref_name }}" 2>/dev/null || true + --path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true # Verify version consistency across all files php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true + # Update VERSION variable with suffix + if [ -n "$SUFFIX" ]; then + VERSION="${VERSION}${SUFFIX}" + fi + # Commit version bump git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.name "gitea-actions[bot]" @@ -113,7 +120,7 @@ jobs: EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2) ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2) [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') - [ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip" + [ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip" echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT" @@ -222,237 +229,3 @@ jobs: 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 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 - echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV" - - - name: Detect platform - id: platform - run: | - php ${MOKO_CLI}/manifest_read.php --path . --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 - - # Read current version (bump already handled by push workflow) - VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) - [ -z "$VERSION" ] && VERSION="00.00.01" - - 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 - - # Append suffix to all manifest tags - if [ -n "$SUFFIX" ]; then - find . -maxdepth 4 -name "*.xml" ! -path "./.git/*" ! -path "./build/*" \ - -exec grep -l "${VERSION}" {} \; 2>/dev/null | while read f; do - sed -i "s|${VERSION}|${VERSION}${SUFFIX}|g" "$f" - done - VERSION="${VERSION}${SUFFIX}" - fi - - # 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 via manifest_element.php - php ${MOKO_CLI}/manifest_element.php \ - --path . --version "$VERSION" --stability "$STABILITY" \ - --repo "${GITEA_REPO}" --github-output - - # Read back element outputs - EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2) - ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2) - [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') - [ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.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 "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ===" - - - name: Create release - id: release - run: | - TAG="${{ steps.meta.outputs.tag }}" - VERSION="${{ steps.meta.outputs.version }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - php ${MOKO_CLI}/release_create.php \ - --path . --version "$VERSION" --tag "$TAG" \ - --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ - --repo "${GITEA_REPO}" --branch dev --prerelease - - - name: Build package and upload - id: package - run: | - VERSION="${{ steps.meta.outputs.version }}" - TAG="${{ steps.meta.outputs.tag }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - php ${MOKO_CLI}/release_package.php \ - --path . --version "$VERSION" --tag "$TAG" \ - --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ - --repo "${GITEA_REPO}" --output /tmp || true - - - name: Update updates.xml - if: steps.platform.outputs.platform == 'joomla' - run: | - VERSION="${{ steps.meta.outputs.version }}" - STABILITY="${{ steps.meta.outputs.stability }}" - SHA256="${{ steps.package.outputs.sha256_zip }}" - - if [ ! -f "updates.xml" ]; then - echo "No updates.xml -- skipping" - exit 0 - fi - - SHA_FLAG="" - [ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}" - - php ${MOKO_CLI}/updates_xml_build.php \ - --path . --version "${VERSION}" --stability "${STABILITY}" \ - --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \ - ${SHA_FLAG} - - # 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}" -- updates.xml 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.package.outputs.sha256_zip }}" - 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 ->>>>>>> Stashed changes diff --git a/.mokogitea/workflows/update-server.yml b/.mokogitea/workflows/update-server.yml index fd6407f..b99a5f2 100644 --- a/.mokogitea/workflows/update-server.yml +++ b/.mokogitea/workflows/update-server.yml @@ -4,18 +4,16 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Universal +# INGROUP: moko-platform.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) +# VERSION: 05.00.00 +# BRIEF: Pre-release build + update server XML for dev/alpha/beta/rc branches # -# Writes updates.xml with multiple entries: -# - stable on push to main (from auto-release) -# - rc on push to rc/** -# - development on push to dev or dev/** +# Thin wrapper around moko-platform CLI tools. +# Builds packages, updates updates.xml, and optionally deploys via SFTP. # -# Joomla filters by user's "Minimum Stability" setting. +# Joomla filters update entries by the user's "Minimum Stability" setting. name: "Update Server" @@ -66,7 +64,7 @@ permissions: jobs: update-xml: - name: Update updates.xml + name: Update Server runs-on: release if: >- github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push' @@ -97,28 +95,25 @@ jobs: if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true fi + echo "MOKO_CLI=/tmp/moko-platform/cli" >> "$GITHUB_ENV" - - name: Generate updates.xml entry - id: update + - name: Detect platform + id: platform + run: php ${MOKO_CLI}/manifest_read.php --path . --github-output + + - name: Resolve stability and bump version + id: meta run: | BRANCH="${{ github.ref_name }}" - REPO="${{ github.repository }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - VERSION=$(php /tmp/moko-platform/cli/version_read.php --path . 2>/dev/null || echo "0.0.0") - # Auto-bump patch on all branches (dev, alpha, beta, rc) + # Auto-bump patch version git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.name "gitea-actions[bot]" - BUMPED=$(php /tmp/moko-platform/cli/version_bump.php --path . 2>/dev/null || true) - if [ -n "$BUMPED" ]; then - VERSION=$(php /tmp/moko-platform/cli/version_read.php --path . 2>/dev/null || echo "$VERSION") - git add -A - git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \ - --author="gitea-actions[bot] " 2>/dev/null || true - git push 2>/dev/null || true - fi + php ${MOKO_CLI}/version_bump.php --path . 2>/dev/null || true - # Determine stability from branch or input + VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "0.0.0") + + # Determine stability from branch or manual input if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then STABILITY="${{ inputs.stability }}" elif [[ "$BRANCH" == rc/* ]]; then @@ -127,195 +122,93 @@ jobs: STABILITY="beta" elif [[ "$BRANCH" == alpha/* ]]; then STABILITY="alpha" - elif [[ "$BRANCH" == dev/* ]] || [[ "$BRANCH" == "dev" ]]; then + else STABILITY="development" - else - STABILITY="stable" fi + # Version suffix per stability stream + case "$STABILITY" in + development) SUFFIX="-dev"; TAG="development" ;; + alpha) SUFFIX="-alpha"; TAG="alpha" ;; + beta) SUFFIX="-beta"; TAG="beta" ;; + rc) SUFFIX="-rc"; TAG="release-candidate" ;; + *) SUFFIX=""; TAG="stable" ;; + esac + + # Propagate version with stability suffix to all manifest files + php ${MOKO_CLI}/version_set_platform.php \ + --path . --version "$VERSION" --branch "$BRANCH" --stability "$STABILITY" 2>/dev/null || true + php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true + + # Re-read version (now includes suffix from version_set_platform) + if [ -n "$SUFFIX" ]; then + VERSION="${VERSION}${SUFFIX}" + fi + + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT" + echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "display_version=${VERSION}" >> "$GITHUB_OUTPUT" - # Parse manifest (portable — no grep -P) - MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "./build/*" -exec grep -l '/dev/null | head -1) - if [ -z "$MANIFEST" ]; then - echo "No Joomla manifest found — skipping" - exit 0 - fi - - # Extract fields using sed (works on all runners) - EXT_NAME=$(sed -n 's/.*\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1) - EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1) - EXT_CLIENT=$(sed -n 's/.*]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_FOLDER=$(sed -n 's/.*]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_VERSION=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1) - TARGET_PLATFORM=$(sed -n 's/.*\(\).*/\1/p' "$MANIFEST" | head -1) - PHP_MINIMUM=$(sed -n 's/.*\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1) - - # Fallbacks - [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}" - [ -z "$EXT_TYPE" ] && EXT_TYPE="component" - - # Derive element if not in manifest: try XML filename, then repo name - if [ -z "$EXT_ELEMENT" ]; then - EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]') - case "$EXT_ELEMENT" in - templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; - esac - fi - - # Use manifest version if README version is empty - [ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION" - - [ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '' "/") - - # 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}" - - PHP_TAG="" - [ -n "$PHP_MINIMUM" ] && PHP_TAG="${PHP_MINIMUM}" - - # Version suffix for non-stable - DISPLAY_VERSION="$VERSION" - case "$STABILITY" in - development) DISPLAY_VERSION="${VERSION}-dev" ;; - alpha) DISPLAY_VERSION="${VERSION}-alpha" ;; - beta) DISPLAY_VERSION="${VERSION}-beta" ;; - rc) DISPLAY_VERSION="${VERSION}-rc" ;; - esac - - MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') - - # Each stability level has its own release tag - case "$STABILITY" in - development) RELEASE_TAG="development" ;; - alpha) RELEASE_TAG="alpha" ;; - beta) RELEASE_TAG="beta" ;; - rc) RELEASE_TAG="release-candidate" ;; - *) RELEASE_TAG="v${MAJOR}" ;; - esac - - PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip" - DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}" - INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}" - - # -- Build install packages and upload to release -------------------- - php /tmp/moko-platform/cli/release_package.php \ - --path . --version "${DISPLAY_VERSION}" --tag "${RELEASE_TAG}" \ - --token "${{ secrets.GA_TOKEN }}" --api-base "${API_BASE}" \ - --repo "${GITEA_REPO}" --output /tmp 2>&1 || true - - echo "Package built and uploaded for ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY - - # -- Build the new entry (canonical format matching release.yml) -- - NEW_ENTRY="" - NEW_ENTRY="${NEW_ENTRY} \n" - NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME}\n" - NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME} ${STABILITY} build.\n" - NEW_ENTRY="${NEW_ENTRY} ${EXT_ELEMENT}\n" - NEW_ENTRY="${NEW_ENTRY} ${EXT_TYPE}\n" - [ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n" - [ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n" - NEW_ENTRY="${NEW_ENTRY} ${VERSION}\n" - NEW_ENTRY="${NEW_ENTRY} $(date +%Y-%m-%d)\n" - NEW_ENTRY="${NEW_ENTRY} https://git.mokoconsulting.tech/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${RELEASE_TAG}\n" - NEW_ENTRY="${NEW_ENTRY} \n" - NEW_ENTRY="${NEW_ENTRY} ${DOWNLOAD_URL}\n" - NEW_ENTRY="${NEW_ENTRY} \n" - [ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} ${SHA256}\n" - NEW_ENTRY="${NEW_ENTRY} ${STABILITY}\n" - NEW_ENTRY="${NEW_ENTRY} Moko Consulting\n" - NEW_ENTRY="${NEW_ENTRY} https://mokoconsulting.tech\n" - NEW_ENTRY="${NEW_ENTRY} \n" - [ -n "$PHP_MINIMUM" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_MINIMUM}\n" - NEW_ENTRY="${NEW_ENTRY} " - - # -- Write new entry to temp file -------------------------------- - printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml - - # -- Merge into updates.xml ---------------------------------------- - # Cascade: stable→all | rc→rc+lower | beta→beta+lower | alpha→alpha+dev | dev→dev - CASCADE_MAP="stable:development,alpha,beta,rc,stable rc:development,alpha,beta,rc beta:development,alpha,beta alpha:development,alpha development:development" - TARGETS="" - for entry in $CASCADE_MAP; do - key="${entry%%:*}" - vals="${entry#*:}" - if [ "$key" = "${STABILITY}" ]; then - TARGETS="$vals" - break - fi - done - [ -z "$TARGETS" ] && TARGETS="${STABILITY}" - - echo "Cascade: ${STABILITY} → ${TARGETS}" - - # Create updates.xml if missing - if [ ! -f "updates.xml" ]; then - printf '%s\n' "" > updates.xml - printf '%s\n' "" >> updates.xml - printf '%s\n' "" >> updates.xml - printf '%s\n' "" >> updates.xml - fi - - # Update existing blocks or create missing ones - export PY_TARGETS="$TARGETS" PY_VERSION="$VERSION" PY_DATE="$(date +%Y-%m-%d)" - python3 << 'PYEOF' - import re, os - - targets = os.environ["PY_TARGETS"].split(",") - version = os.environ["PY_VERSION"] - date = os.environ["PY_DATE"] - - with open("updates.xml") as f: - content = f.read() - with open("/tmp/new_entry.xml") as f: - new_entry_template = f.read() - - for tag in targets: - tag = tag.strip() - # Build entry with this tag's name - new_entry = re.sub(r"[^<]*", f"{tag}", new_entry_template) - - # Try to find existing block (handles both single-line and multi-line ) - block_pattern = r"((?:(?!).)*?" + re.escape(tag) + r".*?)" - match = re.search(block_pattern, content, re.DOTALL) - - if match: - # Update in place — replace entire block - content = content.replace(match.group(1), new_entry.strip()) - print(f" UPDATED: {tag} → {version}") - else: - # Create — insert before - content = content.replace("", "\n" + new_entry.strip() + "\n\n") - print(f" CREATED: {tag} → {version}") - - # Clean up excessive blank lines - content = re.sub(r"\n{3,}", "\n\n", content) - - with open("updates.xml", "w") as f: - f.write(content) - PYEOF - - # Commit - git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" - git config --local user.name "gitea-actions[bot]" - git add updates.xml + # Commit version bump if changed + git add -A git diff --cached --quiet || { - git commit -m "chore: update updates.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \ + git commit -m "chore(version): auto-bump ${VERSION} [skip ci]" \ --author="gitea-actions[bot] " git push } - # -- Sync updates.xml to main (for non-main branches) ---------------------- + - name: Create release and upload package + id: package + run: | + VERSION="${{ steps.meta.outputs.version }}" + TAG="${{ steps.meta.outputs.tag }}" + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + + # Create or update Gitea release + php ${MOKO_CLI}/release_create.php \ + --path . --version "$VERSION" --tag "$TAG" \ + --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease + + # Build package and upload + php ${MOKO_CLI}/release_package.php \ + --path . --version "$VERSION" --tag "$TAG" \ + --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --repo "${GITEA_REPO}" --output /tmp || true + + - name: Update updates.xml + if: steps.platform.outputs.platform == 'joomla' + run: | + VERSION="${{ steps.meta.outputs.version }}" + STABILITY="${{ steps.meta.outputs.stability }}" + SHA256="${{ steps.package.outputs.sha256_zip }}" + + if [ ! -f "updates.xml" ]; then + echo "No updates.xml — skipping" + exit 0 + fi + + SHA_FLAG="" + [ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}" + + php ${MOKO_CLI}/updates_xml_build.php \ + --path . --version "${VERSION}" --stability "${STABILITY}" \ + --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \ + ${SHA_FLAG} + + # Commit and push updates.xml + git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" + git config --local user.name "gitea-actions[bot]" + git add updates.xml + git diff --cached --quiet || { + git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]" + git push + } + - name: Sync updates.xml to main - if: github.ref_name != 'main' + if: github.ref_name != 'main' && steps.platform.outputs.platform == 'joomla' run: | API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" GA_TOKEN="${{ secrets.GA_TOKEN }}" @@ -331,7 +224,7 @@ jobs: payload = json.dumps({ 'content': content, 'sha': '${FILE_SHA}', - 'message': 'chore: sync updates.xml from ${STABILITY} [skip ci]', + 'message': 'chore: sync updates.xml from ${{ steps.meta.outputs.stability }} [skip ci]', 'branch': 'main' }).encode() req = urllib.request.Request( @@ -345,13 +238,8 @@ jobs: 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 "::error::failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY - else - echo "::error::could not get updates.xml SHA from main — file may not exist on main yet" >> $GITHUB_STEP_SUMMARY + print(f'WARNING: sync to main failed: {e}', file=sys.stderr) + " fi - name: SFTP deploy to dev server @@ -365,9 +253,8 @@ jobs: DEV_KEY: ${{ secrets.DEV_FTP_KEY }} DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }} run: | - # -- Permission check: admin or maintain role required -------- + # Permission check: admin or maintain role required ACTOR="${{ github.actor }}" - REPO="${{ github.repository }}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ @@ -400,198 +287,24 @@ jobs: printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json fi - PLATFORM=$(php /tmp/moko-platform/cli/platform_detect.php --path . 2>/dev/null || true) - if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/moko-platform/deploy/deploy-joomla.php" ]; then - php /tmp/moko-platform/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json - elif [ -f "/tmp/moko-platform/deploy/deploy-sftp.php" ]; then - php /tmp/moko-platform/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json + PLATFORM=$(php ${MOKO_CLI}/platform_detect.php --path . 2>/dev/null || true) + if [ "$PLATFORM" = "waas-component" ] && [ -f "${MOKO_CLI}/../deploy/deploy-joomla.php" ]; then + php ${MOKO_CLI}/../deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json + elif [ -f "${MOKO_CLI}/../deploy/deploy-sftp.php" ]; then + php ${MOKO_CLI}/../deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json fi rm -f /tmp/deploy_key /tmp/sftp-config.json echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY - - name: Validate updates.xml integrity - run: | - ERRORS=0 - - if [ ! -f "updates.xml" ]; then - echo "::error::updates.xml not found" - exit 1 - fi - - # Well-formed XML - if ! python3 -c "import xml.etree.ElementTree as ET; ET.parse('updates.xml')" 2>/dev/null; then - echo "::error::updates.xml is not valid XML" - ERRORS=$((ERRORS+1)) - fi - - python3 << 'PYEOF' - import xml.etree.ElementTree as ET, sys, re, os - - tree = ET.parse("updates.xml") - root = tree.getroot() - updates = root.findall("update") - errors = 0 - warnings = 0 - seen_tags = set() - - # All 5 channels MUST be present - REQUIRED_CHANNELS = {"stable", "rc", "beta", "alpha", "dev"} - VALID_TAGS = REQUIRED_CHANNELS | {"development"} # accept legacy alias - REPO = os.environ.get("GITEA_REPO", "") - ORG = os.environ.get("GITEA_ORG", "MokoConsulting") - REPO_BASE = f"https://git.mokoconsulting.tech/{ORG}/" - - # Gitea release tag names per channel (Moko standard) - RELEASE_TAG_MAP = { - "stable": "stable", - "rc": "release-candidate", - "beta": "beta", - "alpha": "alpha", - "dev": "development", - "development": "development", - } - - # Joomla update XML required fields per - # https://docs.joomla.org/Deploying_an_Update_Server - REQUIRED_FIELDS = ["name", "element", "type", "version", "infourl"] - - for i, u in enumerate(updates): - tag_el = u.find("tags/tag") - tag = tag_el.text.strip() if tag_el is not None and tag_el.text else None - label = f"Entry {i+1} ({tag or '?'})" - - # -- Required Joomla fields -- - for field in REQUIRED_FIELDS: - el = u.find(field) - if el is None or not (el.text or "").strip(): - print(f"::error::{label}: missing required <{field}>") - errors += 1 - - # -- -- - dl = u.find("downloads/downloadurl") - if dl is None or not (dl.text or "").strip(): - print(f"::error::{label}: missing ") - errors += 1 - else: - dl_url = dl.text.strip() - # Must point to org repo - if REPO_BASE not in dl_url: - print(f"::error::{label}: download URL not under {REPO_BASE}: {dl_url}") - errors += 1 - # Must end in .zip - if not dl_url.endswith(".zip"): - print(f"::error::{label}: download URL must end in .zip: {dl_url}") - errors += 1 - # Must use correct Gitea release tag in path - if tag and tag in RELEASE_TAG_MAP: - expected_tag = RELEASE_TAG_MAP[tag] - if f"/download/{expected_tag}/" not in dl_url: - print(f"::error::{label}: download URL should contain /download/{expected_tag}/ but got: {dl_url}") - errors += 1 - - # -- (required for Joomla to match update) -- - client = u.find("client") - if client is None or not (client.text or "").strip(): - print(f"::error::{label}: missing (required for Joomla update matching)") - errors += 1 - - # -- -- - tp = u.find("targetplatform") - if tp is None: - print(f"::error::{label}: missing ") - errors += 1 - else: - tp_name = tp.get("name", "") - tp_ver = tp.get("version", "") - if tp_name != "joomla": - print(f"::error::{label}: targetplatform name should be 'joomla', got '{tp_name}'") - errors += 1 - if not tp_ver: - print(f"::error::{label}: targetplatform missing version regex") - errors += 1 - elif "5" not in tp_ver or "6" not in tp_ver: - print(f"::warning::{label}: targetplatform version may not cover Joomla 5+6: {tp_ver}") - warnings += 1 - - # -- must be valid Joomla type -- - type_el = u.find("type") - if type_el is not None and type_el.text: - valid_types = {"component", "module", "plugin", "template", "library", "package", "file"} - if type_el.text.strip() not in valid_types: - print(f"::error::{label}: invalid type '{type_el.text}' (expected: {valid_types})") - errors += 1 - - # -- format (XX.YY.ZZ with optional suffix) -- - ver_el = u.find("version") - if ver_el is not None and ver_el.text: - if not re.match(r"^\d{2}\.\d{2}\.\d{2}(-\w+)?$", ver_el.text.strip()): - print(f"::warning::{label}: version '{ver_el.text}' does not match XX.YY.ZZ format") - warnings += 1 - - # -- and -- - for field in ["maintainer", "maintainerurl"]: - el = u.find(field) - if el is None or not (el.text or "").strip(): - print(f"::warning::{label}: missing <{field}>") - warnings += 1 - - # -- Valid stability tag -- - if tag is None: - print(f"::error::{label}: missing ") - errors += 1 - elif tag not in VALID_TAGS: - print(f"::error::{label}: invalid tag '{tag}' (expected: {VALID_TAGS})") - errors += 1 - - # -- Duplicate tag check -- - norm_tag = "dev" if tag == "development" else tag - if norm_tag in seen_tags: - print(f"::error::{label}: duplicate channel '{tag}'") - errors += 1 - if norm_tag: - seen_tags.add(norm_tag) - - # -- All 5 channels must exist -- - missing = REQUIRED_CHANNELS - seen_tags - if missing: - print(f"::error::Missing required update channels: {', '.join(sorted(missing))}") - errors += 1 - - # -- Version ordering: higher stability must not exceed dev version -- - channel_versions = {} - for u in updates: - tag_el = u.find("tags/tag") - ver_el = u.find("version") - if tag_el is not None and ver_el is not None and tag_el.text and ver_el.text: - norm = "dev" if tag_el.text.strip() == "development" else tag_el.text.strip() - # Strip suffix for comparison (01.00.18-dev -> 01.00.18) - base_ver = re.sub(r"-\w+$", "", ver_el.text.strip()) - channel_versions[norm] = base_ver - - # Cascade check: dev >= alpha >= beta >= rc >= stable - ORDER = ["dev", "alpha", "beta", "rc", "stable"] - for j in range(1, len(ORDER)): - current = ORDER[j] - previous = ORDER[j - 1] - if current in channel_versions and previous in channel_versions: - if channel_versions[current] > channel_versions[previous]: - print(f"::error::{current} version ({channel_versions[current]}) is ahead of {previous} ({channel_versions[previous]})") - errors += 1 - - # -- Summary -- - print(f"\nupdates.xml validation: {len(updates)} entries, {errors} error(s), {warnings} warning(s)") - if errors > 0: - sys.exit(1) - PYEOF - - name: Summary if: always() run: | - echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY + VERSION="${{ steps.meta.outputs.version }}" + STABILITY="${{ steps.meta.outputs.stability }}" + DISPLAY="${{ steps.meta.outputs.display_version }}" + echo "## Update Server" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Version | \`${DISPLAY_VERSION}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $GITHUB_STEP_SUMMARY + echo "| Version | \`${DISPLAY}\` |" >> $GITHUB_STEP_SUMMARY -- 2.52.0 From 1d807ca7a85e88df98be37b419b308e642e882be Mon Sep 17 00:00:00 2001 From: Moko Consulting Date: Thu, 28 May 2026 14:24:07 -0500 Subject: [PATCH 27/29] refactor(workflows): rename secrets MOKOGITEA_TOKEN/GITHUB_TOKEN, use x-access-token [skip bump] --- .mokogitea/workflows/auto-bump.yml | 6 +-- .mokogitea/workflows/auto-release.yml | 61 ++++++++++++------------ .mokogitea/workflows/cascade-dev.yml | 66 +++++++++++++------------- .mokogitea/workflows/cleanup.yml | 16 +++---- .mokogitea/workflows/pr-check.yml | 26 +++++----- .mokogitea/workflows/pre-release.yml | 12 ++--- .mokogitea/workflows/update-server.yml | 25 +++++----- 7 files changed, 106 insertions(+), 106 deletions(-) diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml index 4d0ffc0..572facd 100644 --- a/.mokogitea/workflows/auto-bump.yml +++ b/.mokogitea/workflows/auto-bump.yml @@ -37,7 +37,7 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - token: ${{ secrets.GA_TOKEN }} + token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 1 - name: Setup moko-platform tools @@ -49,7 +49,7 @@ jobs: echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV" else git clone --depth 1 --branch main --quiet \ - "https://x-access-token:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \ + "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \ /tmp/moko-platform-api cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV" @@ -77,7 +77,7 @@ jobs: 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 remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git add -A git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \ --author="gitea-actions[bot] " diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index 8cdf410..8be9b71 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -63,12 +63,12 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - token: ${{ secrets.GA_TOKEN }} + token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 1 - name: Setup moko-platform tools env: - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }} + MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting run: | if ! command -v composer &> /dev/null; then @@ -85,7 +85,7 @@ jobs: 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 }}" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" \ --api-base "${API_BASE}" \ --branch "${{ github.event.pull_request.head.ref || 'dev' }}" @@ -95,7 +95,7 @@ jobs: 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 }}" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" \ --api-base "${API_BASE}" - name: Summary @@ -116,14 +116,20 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - token: ${{ secrets.GA_TOKEN }} + token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 0 + - name: Configure git for bot pushes + run: | + git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" + git config --local user.name "gitea-actions[bot]" + git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" + - name: Setup moko-platform tools env: - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }} + MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}' + COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GITHUB_TOKEN }}"}}' run: | # Ensure PHP + Composer are available if ! command -v composer &> /dev/null; then @@ -169,7 +175,7 @@ jobs: 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 }}" \ + RC_JSON=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_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) @@ -285,10 +291,6 @@ jobs: 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] " @@ -320,7 +322,7 @@ jobs: 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 }}" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" \ --api-base "${API_BASE}" \ --path . --branch main echo "Promoted RC → stable (${VERSION})" >> $GITHUB_STEP_SUMMARY @@ -336,7 +338,7 @@ jobs: API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" php /tmp/moko-platform-api/cli/release_create.php \ --path . --version "$VERSION" --tag "$RELEASE_TAG" \ - --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --repo "${GITEA_REPO}" --branch main echo "Release created: ${VERSION}" >> $GITHUB_STEP_SUMMARY @@ -352,7 +354,7 @@ jobs: API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" php /tmp/moko-platform-api/cli/release_package.php \ --path . --version "$VERSION" --tag "$RELEASE_TAG" \ - --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --repo "${GITEA_REPO}" --output /tmp || true # -- STEP 5: Write update stream (after build so SHA-256 is available) ----- @@ -363,9 +365,9 @@ jobs: SHA256="${{ steps.package.outputs.sha256_zip }}" # Fetch latest updates.xml from main so preserve logic has all channels - GA_TOKEN="${{ secrets.GA_TOKEN }}" + GITEA_TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" - curl -sf -H "Authorization: token ${GA_TOKEN}" \ + curl -sf -H "Authorization: token ${GITEA_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 @@ -380,9 +382,6 @@ jobs: # Commit updates.xml if changed 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 remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git add updates.xml git commit -m "chore: update stable channel ${VERSION} [skip ci]" \ --author="gitea-actions[bot] " @@ -398,7 +397,7 @@ jobs: RELEASE_TAG="${{ steps.version.outputs.release_tag }}" php /tmp/moko-platform-api/cli/release_body_update.php \ --path . --version "${VERSION}" --tag "${RELEASE_TAG}" \ - --token "${{ secrets.GA_TOKEN }}" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" \ --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \ 2>&1 || true echo "Release body updated" >> $GITHUB_STEP_SUMMARY @@ -407,7 +406,7 @@ jobs: - name: "Step 9: Mirror release to GitHub" if: >- steps.version.outputs.skip != 'true' && - secrets.GH_TOKEN != '' + secrets.GITHUB_TOKEN != '' continue-on-error: true run: | VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" @@ -416,8 +415,8 @@ jobs: API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" php /tmp/moko-platform-api/cli/release_mirror.php \ --version "$VERSION" --tag "$RELEASE_TAG" \ - --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ - --gh-token "${{ secrets.GH_TOKEN }}" --gh-repo "$GH_REPO" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ + --gh-token "${{ secrets.GITHUB_TOKEN }}" --gh-repo "$GH_REPO" \ --branch main 2>&1 || true echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY @@ -425,14 +424,14 @@ jobs: - name: "Step 10: Push main to GitHub mirror" if: >- steps.version.outputs.skip != 'true' && - secrets.GH_TOKEN != '' + secrets.GITHUB_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 remote add github "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \ + git remote set-url github "https://x-access-token:${{ secrets.GITHUB_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" \ @@ -448,7 +447,7 @@ jobs: php /tmp/moko-platform-api/cli/release_cascade.php \ --stability stable \ --version "${VERSION}" \ - --token "${{ secrets.GA_TOKEN }}" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" \ --api-base "${API_BASE}" 2>/dev/null || true - name: "Step 11: Delete and recreate dev branch from main" @@ -456,7 +455,7 @@ jobs: continue-on-error: true run: | API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - TOKEN="${{ secrets.GA_TOKEN }}" + TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" # Delete dev branch curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \ @@ -475,7 +474,7 @@ jobs: continue-on-error: true run: | API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - TOKEN="${{ secrets.GA_TOKEN }}" + TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" BRANCH_NAME="version/${VERSION}" MAIN_SHA=$(git rev-parse HEAD) @@ -497,7 +496,7 @@ jobs: run: | API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" php /tmp/moko-platform-api/cli/version_reset_dev.php \ - --token "${{ secrets.GA_TOKEN }}" --api-base "${API_BASE}" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \ --branch dev --path . 2>&1 || true # -- Summary -------------------------------------------------------------- diff --git a/.mokogitea/workflows/cascade-dev.yml b/.mokogitea/workflows/cascade-dev.yml index 7f26935..f7f0b3c 100644 --- a/.mokogitea/workflows/cascade-dev.yml +++ b/.mokogitea/workflows/cascade-dev.yml @@ -8,21 +8,21 @@ # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform # PATH: /templates/workflows/cascade-dev.yml.template # VERSION: 02.00.00 -# BRIEF: Forward-merge main -> all open branches after every push to main +# BRIEF: Forward-merge main → all open branches after every push to main # # +========================================================================+ -# | CASCADE MAIN -> ALL BRANCHES | +# | CASCADE MAIN → ALL BRANCHES | # +========================================================================+ # | | # | Triggers on every push to main (PR merges, bot commits, etc.) | # | | # | 1. List all branches matching: dev, rc/*, beta/*, alpha/* | -# | 2. For each: create PR (main -> branch), auto-merge if clean | +# | 2. For each: create PR (main → branch), auto-merge if clean | # | 3. On conflict: leave PR open for manual resolution | # | | # +========================================================================+ -name: "Universal: Cascade Main -> Dev" +name: "Universal: Cascade Main → Dev" on: push: @@ -42,7 +42,7 @@ permissions: jobs: cascade: - name: Cascade main -> branches + name: Cascade main → branches runs-on: ubuntu-latest if: >- !contains(github.event.head_commit.message, '[skip ci]') && @@ -52,7 +52,7 @@ jobs: - name: Discover target branches id: branches env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} + GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} run: | API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" @@ -61,7 +61,7 @@ jobs: ALL_BRANCHES="" while true; do BATCH=$(curl -sS \ - -H "Authorization: token ${GA_TOKEN}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ "${API}/branches?page=${PAGE}&limit=50" \ | jq -r '.[].name // empty') [ -z "$BATCH" ] && break @@ -83,17 +83,17 @@ jobs: if [ -z "$TARGETS" ]; then echo "targets=" >> "$GITHUB_OUTPUT" - echo " No cascade target branches found" + echo "ℹ️ No cascade target branches found" else echo "targets=$TARGETS" >> "$GITHUB_OUTPUT" COUNT=$(echo "$TARGETS" | wc -w) - echo " Found ${COUNT} target branch(es): ${TARGETS}" + echo "📋 Found ${COUNT} target branch(es): ${TARGETS}" fi - name: Cascade to all target branches if: steps.branches.outputs.targets != '' env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} + GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} run: | API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" SHORT_SHA="${GITHUB_SHA:0:7}" @@ -106,27 +106,27 @@ jobs: for BRANCH in $TARGETS; do echo "" - echo " main -> ${BRANCH} " + echo "═══ main → ${BRANCH} ═══" # Check if branch is already up to date ENCODED_BRANCH=$(echo "$BRANCH" | sed 's|/|%2F|g') RESPONSE=$(curl -sS \ - -H "Authorization: token ${GA_TOKEN}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ "${API}/compare/${ENCODED_BRANCH}...main") AHEAD=$(echo "$RESPONSE" | jq '.total_commits // 0') if [ "$AHEAD" -eq 0 ]; then - echo " Already up to date" + echo " ✅ Already up to date" SKIPPED=$((SKIPPED + 1)) continue fi - echo " main is ${AHEAD} commit(s) ahead" + echo " ℹ️ main is ${AHEAD} commit(s) ahead" # Check for existing cascade PR EXISTING=$(curl -sS \ - -H "Authorization: token ${GA_TOKEN}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ "${API}/pulls?state=open&head=${GITEA_ORG}:main&base=${ENCODED_BRANCH}&limit=1") EXISTING_COUNT=$(echo "$EXISTING" | jq 'length') @@ -134,16 +134,16 @@ jobs: if [ "$EXISTING_COUNT" -gt 0 ]; then PR_NUMBER=$(echo "$EXISTING" | jq -r '.[0].number') - echo " Reusing existing PR #${PR_NUMBER}" + echo " ℹ️ Reusing existing PR #${PR_NUMBER}" else # Create cascade PR PR_RESPONSE=$(curl -sS -w "\n%{http_code}" \ -X POST \ - -H "Authorization: token ${GA_TOKEN}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d "{ - \"title\": \"chore: cascade main -> ${BRANCH} (${SHORT_SHA}) [skip ci]\", - \"body\": \"## Automatic cascade\\n\\nForward-merging \`main\` (${SHORT_SHA}) into \`${BRANCH}\`.\\n\\nIf conflicts exist, resolve manually and merge.\\n\\n> Auto-created by **Cascade Main -> Dev**.\", + \"title\": \"chore: cascade main → ${BRANCH} (${SHORT_SHA}) [skip ci]\", + \"body\": \"## Automatic cascade\\n\\nForward-merging \`main\` (${SHORT_SHA}) into \`${BRANCH}\`.\\n\\nIf conflicts exist, resolve manually and merge.\\n\\n> Auto-created by **Cascade Main → Dev**.\", \"head\": \"main\", \"base\": \"${BRANCH}\" }" \ @@ -155,34 +155,34 @@ jobs: if [ "$HTTP_CODE" != "201" ] || [ -z "$PR_NUMBER" ]; then MSG=$(echo "$BODY" | jq -r '.message // .' 2>/dev/null | head -1) - echo " Failed to create PR (HTTP ${HTTP_CODE}): ${MSG}" + echo " ❌ Failed to create PR (HTTP ${HTTP_CODE}): ${MSG}" FAILED=$((FAILED + 1)) continue fi - echo " Created PR #${PR_NUMBER}" + echo " ✅ Created PR #${PR_NUMBER}" fi # Try auto-merge PR_DATA=$(curl -sS \ - -H "Authorization: token ${GA_TOKEN}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ "${API}/pulls/${PR_NUMBER}") MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false') if [ "$MERGEABLE" != "true" ]; then - echo " Conflicts -- PR #${PR_NUMBER} left open" + echo " ⚠️ Conflicts — PR #${PR_NUMBER} left open" CONFLICTS=$((CONFLICTS + 1)) continue fi MERGE_RESPONSE=$(curl -sS -w "\n%{http_code}" \ -X POST \ - -H "Authorization: token ${GA_TOKEN}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d "{ \"Do\": \"merge\", - \"merge_message_field\": \"chore: cascade main -> ${BRANCH} [skip ci]\", + \"merge_message_field\": \"chore: cascade main → ${BRANCH} [skip ci]\", \"delete_branch_after_merge\": false }" \ "${API}/pulls/${PR_NUMBER}/merge") @@ -190,23 +190,23 @@ jobs: MERGE_HTTP=$(echo "$MERGE_RESPONSE" | tail -1) if [ "$MERGE_HTTP" = "200" ] || [ "$MERGE_HTTP" = "204" ]; then - echo " Merged -- ${BRANCH} is in sync" + echo " ✅ Merged — ${BRANCH} is in sync" SUCCESS=$((SUCCESS + 1)) else MERGE_BODY=$(echo "$MERGE_RESPONSE" | sed '$d') - echo " Merge failed (HTTP ${MERGE_HTTP}) -- PR #${PR_NUMBER} left open" + echo " ⚠️ Merge failed (HTTP ${MERGE_HTTP}) — PR #${PR_NUMBER} left open" CONFLICTS=$((CONFLICTS + 1)) fi done # Summary echo "" - echo "" - echo " Merged: ${SUCCESS}" - echo " Conflicts: ${CONFLICTS}" - echo " Up to date: ${SKIPPED}" - echo " Failed: ${FAILED}" - echo "" + echo "════════════════════════════════════════" + echo " ✅ Merged: ${SUCCESS}" + echo " ⚠️ Conflicts: ${CONFLICTS}" + echo " ⏭️ Up to date: ${SKIPPED}" + echo " ❌ Failed: ${FAILED}" + echo "════════════════════════════════════════" if [ "$FAILED" -gt 0 ]; then exit 1 diff --git a/.mokogitea/workflows/cleanup.yml b/.mokogitea/workflows/cleanup.yml index c8012c7..29ca4d4 100644 --- a/.mokogitea/workflows/cleanup.yml +++ b/.mokogitea/workflows/cleanup.yml @@ -8,7 +8,7 @@ # REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # PATH: /.gitea/workflows/cleanup.yml # VERSION: 01.00.00 -# BRIEF: Scheduled cleanup -- delete merged branches and old workflow runs +# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs name: "Universal: Repository Cleanup" @@ -33,17 +33,17 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.GA_TOKEN }} + token: ${{ secrets.MOKOGITEA_TOKEN }} - name: Delete merged branches env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} + GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} run: | echo "=== Merged Branch Cleanup ===" API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" # List branches via API - BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ + BRANCHES=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \ "${API}/branches?limit=50" | jq -r '.[].name') DELETED=0 @@ -56,7 +56,7 @@ jobs: # Check if branch is merged into main if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then echo " Deleting merged branch: ${BRANCH}" - curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ + curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \ "${API}/branches/${BRANCH}" 2>/dev/null || true DELETED=$((DELETED + 1)) fi @@ -66,20 +66,20 @@ jobs: - name: Clean old workflow runs env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} + GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} run: | echo "=== Workflow Run Cleanup ===" API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ) # Get old completed runs - RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ + RUNS=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \ "${API}/actions/runs?status=completed&limit=50" | \ jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null) DELETED=0 for RUN_ID in $RUNS; do - curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ + curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \ "${API}/actions/runs/${RUN_ID}" 2>/dev/null || true DELETED=$((DELETED + 1)) done diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml index 35bfca2..df06523 100644 --- a/.mokogitea/workflows/pr-check.yml +++ b/.mokogitea/workflows/pr-check.yml @@ -8,7 +8,7 @@ # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform # PATH: /templates/workflows/universal/pr-check.yml.template # VERSION: 05.00.00 -# BRIEF: PR gate -- branch policy + code validation before merge +# BRIEF: PR gate — branch policy + code validation before merge name: "Universal: PR Check" @@ -24,7 +24,7 @@ env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: - # Branch Policy + # ── Branch Policy ────────────────────────────────────────────────────── branch-policy: name: Branch Policy runs-on: ubuntu-latest @@ -34,7 +34,7 @@ jobs: HEAD="${{ github.head_ref }}" BASE="${{ github.base_ref }}" - echo "PR: ${HEAD} -> ${BASE}" + echo "PR: ${HEAD} → ${BASE}" ALLOWED=true REASON="" @@ -85,18 +85,18 @@ jobs: echo "${REASON}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY - echo "- \`feature/*\` -> \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`fix/*\` -> \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`hotfix/*\` -> \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`dev\` -> \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`rc/*\` -> \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY exit 1 fi - echo "Branch policy: OK (${HEAD} -> ${BASE})" + echo "Branch policy: OK (${HEAD} → ${BASE})" echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY - # Code Validation + # ── Code Validation ──────────────────────────────────────────────────── validate: name: Validate PR runs-on: ubuntu-latest @@ -162,7 +162,7 @@ jobs: echo "Dolibarr module: ${MOD_FILE}" ;; *) - echo "Generic platform -- no manifest validation" + echo "Generic platform — no manifest validation" ;; esac @@ -204,11 +204,11 @@ jobs: steps: - name: Trigger RC pre-release env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} + GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} REPO: ${{ github.repository }} BRANCH: ${{ github.head_ref }} GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} run: | - curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}" + curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}" echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index edbe60e..7920f53 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -50,11 +50,11 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.GA_TOKEN }} + token: ${{ secrets.MOKOGITEA_TOKEN }} - name: Setup moko-platform tools env: - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }} + MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting run: | if ! command -v composer &> /dev/null; then @@ -104,7 +104,7 @@ jobs: # 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 remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_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]" @@ -139,7 +139,7 @@ jobs: API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" php ${MOKO_CLI}/release_create.php \ --path . --version "$VERSION" --tag "$TAG" \ - --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --repo "${GITEA_REPO}" --branch dev --prerelease - name: Build package and upload @@ -150,7 +150,7 @@ jobs: API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" php ${MOKO_CLI}/release_package.php \ --path . --version "$VERSION" --tag "$TAG" \ - --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --repo "${GITEA_REPO}" --output /tmp || true - name: Update updates.xml @@ -207,7 +207,7 @@ jobs: continue-on-error: true run: | API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - TOKEN="${{ secrets.GA_TOKEN }}" + TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" php ${MOKO_CLI}/release_cascade.php \ --stability "${{ steps.meta.outputs.stability }}" \ diff --git a/.mokogitea/workflows/update-server.yml b/.mokogitea/workflows/update-server.yml index b99a5f2..cd2eff0 100644 --- a/.mokogitea/workflows/update-server.yml +++ b/.mokogitea/workflows/update-server.yml @@ -73,14 +73,14 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 with: - token: ${{ secrets.GA_TOKEN }} + token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 0 - name: Setup moko-platform tools env: - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }} + MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting - COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.GA_TOKEN }}"}}}' + COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.MOKOGITEA_TOKEN }}"}}}' 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 @@ -106,9 +106,12 @@ jobs: run: | BRANCH="${{ github.ref_name }}" - # Auto-bump patch version + # Configure git for bot pushes git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.name "gitea-actions[bot]" + git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" + + # Auto-bump patch version php ${MOKO_CLI}/version_bump.php --path . 2>/dev/null || true VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "0.0.0") @@ -169,13 +172,13 @@ jobs: # Create or update Gitea release php ${MOKO_CLI}/release_create.php \ --path . --version "$VERSION" --tag "$TAG" \ - --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease # Build package and upload php ${MOKO_CLI}/release_package.php \ --path . --version "$VERSION" --tag "$TAG" \ - --token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --repo "${GITEA_REPO}" --output /tmp || true - name: Update updates.xml @@ -199,8 +202,6 @@ jobs: ${SHA_FLAG} # Commit and push updates.xml - git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" - git config --local user.name "gitea-actions[bot]" git add updates.xml git diff --cached --quiet || { git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]" @@ -211,9 +212,9 @@ jobs: if: github.ref_name != 'main' && steps.platform.outputs.platform == 'joomla' run: | API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - GA_TOKEN="${{ secrets.GA_TOKEN }}" + GITEA_TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" - FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \ + FILE_SHA=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \ "${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 @@ -231,7 +232,7 @@ jobs: '${API_BASE}/contents/updates.xml', data=payload, method='PUT', headers={ - 'Authorization': 'token ${GA_TOKEN}', + 'Authorization': 'token ${GITEA_TOKEN}', 'Content-Type': 'application/json' }) try: @@ -257,7 +258,7 @@ jobs: ACTOR="${{ github.actor }}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ + PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \ "${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \ python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read") case "$PERMISSION" in -- 2.52.0 From 159ad30ec76b1808abb01cfbb342577440d242dc Mon Sep 17 00:00:00 2001 From: Moko Consulting Date: Thu, 28 May 2026 14:25:38 -0500 Subject: [PATCH 28/29] chore(workflows): sync all universal workflows from moko-platform [skip bump] --- .mokogitea/workflows/gitleaks.yml | 4 +- .mokogitea/workflows/issue-branch.yml | 2 +- .mokogitea/workflows/repo-health.yml | 82 ++------------------------- 3 files changed, 8 insertions(+), 80 deletions(-) diff --git a/.mokogitea/workflows/gitleaks.yml b/.mokogitea/workflows/gitleaks.yml index 6a672b8..e0fdd1d 100644 --- a/.mokogitea/workflows/gitleaks.yml +++ b/.mokogitea/workflows/gitleaks.yml @@ -8,7 +8,7 @@ # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform # PATH: /templates/workflows/gitleaks.yml.template # VERSION: 01.00.00 -# BRIEF: Secret scanning -- detect leaked credentials, API keys, and tokens +# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens # # +========================================================================+ # | SECRET SCANNING | @@ -89,7 +89,7 @@ jobs: run: | REPO="${{ github.event.repository.name }}" curl -sS \ - -H "Title: ${REPO} -- secrets detected in code" \ + -H "Title: ${REPO} — secrets detected in code" \ -H "Tags: rotating_light,key" \ -H "Priority: urgent" \ -d "Gitleaks found potential secrets. Review and rotate credentials immediately." \ diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index c2b02a6..f084fe1 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Create branch and comment run: | - TOKEN="${{ secrets.GA_TOKEN }}" + TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" ISSUE_NUM="${{ github.event.issue.number }}" ISSUE_TITLE="${{ github.event.issue.title }}" diff --git a/.mokogitea/workflows/repo-health.yml b/.mokogitea/workflows/repo-health.yml index e272757..be52e37 100644 --- a/.mokogitea/workflows/repo-health.yml +++ b/.mokogitea/workflows/repo-health.yml @@ -49,22 +49,18 @@ env: SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate # Repo health policy - REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,.gitea/workflows/ - REPO_OPTIONAL_FILES: SECURITY.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/ + REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogitea/workflows/ + REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/ REPO_DISALLOWED_DIRS: REPO_DISALLOWED_FILES: TODO.md,todo.md - # Wiki-preferred documentation -- wiki is full credit, repo file is advisory - # Format: filename:WikiPageName (Gitea wiki page slug) - WIKI_PREFERRED_DOCS: CONTRIBUTING.md:Contributing,CODE_OF_CONDUCT.md:Code-of-Conduct,GOVERNANCE.md:Governance - # Extended checks toggles EXTENDED_CHECKS: "true" # File / directory variables DOCS_INDEX: docs/docs-index.md SCRIPT_DIR: scripts - WORKFLOWS_DIR: .gitea/workflows + WORKFLOWS_DIR: .mokogitea/workflows SHELLCHECK_PATTERN: '*.sh' SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml' FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true @@ -85,7 +81,7 @@ jobs: - name: Check actor permission (admin only) id: perm env: - TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }} + TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }} REPO: ${{ github.repository }} ACTOR: ${{ github.actor }} run: | @@ -94,7 +90,7 @@ jobs: PERMISSION=unknown METHOD="" - # Hardcoded authorized users -- always allowed + # Hardcoded authorized users — always allowed case "$ACTOR" in jmiller|gitea-actions[bot]) ALLOWED=true @@ -369,10 +365,6 @@ jobs: - name: Repository health checks env: PROFILE_RAW: ${{ github.event.inputs.profile }} - GA_TOKEN: ${{ secrets.GA_TOKEN }} - GITHUB_TOKEN: ${{ github.token }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_SERVER_URL: ${{ github.server_url }} run: | set -euo pipefail @@ -530,63 +522,6 @@ jobs: } >> "${GITHUB_STEP_SUMMARY}" fi - # -- Wiki-preferred documentation checks -- - # Docs that belong in the wiki; repo-file fallback is accepted but advisory - wiki_findings=() - wiki_ok=() - wiki_file_fallback=() - wiki_missing=() - - API_BASE="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}" - WIKI_TOKEN="${WIKI_TOKEN:-${GA_TOKEN:-${GITHUB_TOKEN}}}" - REPO_FULL="${GITHUB_REPOSITORY}" - - IFS=',' read -r -a wiki_docs <<< "${WIKI_PREFERRED_DOCS}" - for entry in "${wiki_docs[@]}"; do - file="${entry%%:*}" - page="${entry##*:}" - - # Check wiki via Gitea API - wiki_exists=false - http_code=$(curl -sf -o /dev/null -w '%{http_code}' \ - -H "Authorization: token ${WIKI_TOKEN}" \ - "${API_BASE}/api/v1/repos/${REPO_FULL}/wiki/page/${page}" 2>/dev/null || echo "000") - [ "${http_code}" = "200" ] && wiki_exists=true - - # Check repo file - file_exists=false - [ -f "${file}" ] && file_exists=true - - if [ "${wiki_exists}" = true ]; then - wiki_ok+=("${file} -> wiki/${page}") - elif [ "${file_exists}" = true ]; then - wiki_file_fallback+=("${file}") - content_warnings+=("${file} found in repo root (preferred location: wiki/${page})") - else - wiki_missing+=("${file} (wiki/${page})") - missing_optional+=("${file} (not in wiki or repo)") - fi - done - - { - printf '%s\n' '### Wiki-preferred documentation' - printf '%s\n' '| Document | Location | Status |' - printf '%s\n' '|---|---|---|' - for item in "${wiki_ok[@]:-}"; do - [ -z "${item}" ] && continue - printf '%s\n' "| ${item%%->*}| Wiki | OK |" - done - for item in "${wiki_file_fallback[@]:-}"; do - [ -z "${item}" ] && continue - printf '%s\n' "| ${item} | Repo file | Advisory -- migrate to wiki |" - done - for item in "${wiki_missing[@]:-}"; do - [ -z "${item}" ] && continue - printf '%s\n' "| ${item%%(*}| Missing | Warning |" - done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - # -- Joomla-specific checks -- joomla_findings=() @@ -772,13 +707,6 @@ jobs: printf '%s\n' '| Release variables | OK | Repository variables validation |' printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |' printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |' - if [ "${#wiki_file_fallback[@]}" -gt 0 ]; then - printf '%s\n' "| Wiki-preferred docs | Advisory | ${#wiki_file_fallback[@]} doc(s) in repo, prefer wiki |" - elif [ "${#wiki_missing[@]}" -gt 0 ]; then - printf '%s\n' "| Wiki-preferred docs | Warning | ${#wiki_missing[@]} doc(s) missing from wiki and repo |" - else - printf '%s\n' '| Wiki-preferred docs | OK | All docs found in wiki |' - fi printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |' if [ "${extended_enabled}" = 'true' ]; then if [ "${#extended_findings[@]}" -gt 0 ]; then -- 2.52.0 From 5c3600b3589617decc5c00091999567ce709a000 Mon Sep 17 00:00:00 2001 From: "gitea-actions[bot]" Date: Thu, 28 May 2026 19:34:16 +0000 Subject: [PATCH 29/29] chore(version): auto-bump patch 02.07.02-dev [skip ci] --- src/templateDetails.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templateDetails.xml b/src/templateDetails.xml index 6c2fa0b..ba69ce9 100644 --- a/src/templateDetails.xml +++ b/src/templateDetails.xml @@ -36,7 +36,7 @@ MokoOnyx - 02.07.02 + 02.07.02-dev script.php 2026-05-16 Jonathan Miller || Moko Consulting -- 2.52.0