diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 62eff0f2..e5717974 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -3,9 +3,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: GitHub.Workflow +# DEFGROUP: Gitea.Workflow # INGROUP: MokoStandards.Release -# REPO: https://github.com/mokoconsulting-tech/MokoStandards +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API # PATH: /templates/workflows/joomla/auto-release.yml.template # VERSION: 04.06.00 # BRIEF: Joomla build & release — ZIP package, updates.xml, SHA-256 checksum @@ -22,13 +22,15 @@ # | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files | # | 5. Write updates.xml (Joomla update server XML) | # | 6. Create git tag vXX.YY.ZZ | -# | 7a. Patch: update existing GitHub Release for this minor | +# | 7a. Patch: update existing Gitea Release for this minor | # | 8. Build ZIP, upload asset, write SHA-256 to updates.xml | # | | # | Every version change: archives main -> version/XX.YY branch | -# | Patch 00 = development (no release). First release = patch 01. | +# | All patches release (including 00). Patch 00/01 = full pipeline. | # | First release only (patch == 01): | -# | 7b. Create new GitHub Release | +# | 7b. Create new Gitea Release | +# | | +# | GitHub mirror: stable/rc releases only (continue-on-error) | # | | # +========================================================================+ @@ -46,6 +48,9 @@ on: 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 @@ -61,25 +66,26 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - token: ${{ secrets.GH_TOKEN || github.token }} + token: ${{ secrets.GA_TOKEN }} fetch-depth: 0 - name: Setup MokoStandards tools env: - GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' + MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }} + MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting + COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' run: | git clone --depth 1 --branch version/04 --quiet \ - "https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \ - /tmp/mokostandards - cd /tmp/mokostandards + "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ + /tmp/mokostandards-api + cd /tmp/mokostandards-api composer install --no-dev --no-interaction --quiet # -- STEP 1: Read version ----------------------------------------------- - name: "Step 1: Read version from README.md" id: version run: | - VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null) + VERSION=$(php /tmp/mokostandards-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" @@ -97,19 +103,15 @@ jobs: echo "minor=$MINOR" >> "$GITHUB_OUTPUT" echo "major=$MAJOR" >> "$GITHUB_OUTPUT" echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT" - if [ "$PATCH" = "00" ]; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "is_minor=false" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (patch 00 = development — skipping release)" + # Determine stability for mirror gating + 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 "skip=false" >> "$GITHUB_OUTPUT" - if [ "$PATCH" = "01" ]; then - echo "is_minor=true" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (first release — full pipeline)" - else - echo "is_minor=false" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (patch — platform version + badges only)" - fi + echo "is_minor=false" >> "$GITHUB_OUTPUT" + echo "Version: $VERSION (patch — platform version + badges only)" fi - name: Check if already released @@ -243,7 +245,7 @@ jobs: steps.check.outputs.already_released != 'true' run: | VERSION="${{ steps.version.outputs.version }}" - php /tmp/mokostandards/api/cli/version_set_platform.php \ + php /tmp/mokostandards-api/cli/version_set_platform.php \ --path . --version "$VERSION" --branch main # -- STEP 4: Update version badges ---------------------------------------- @@ -309,7 +311,7 @@ jobs: # Build targetplatform (fallback to Joomla 5 if not in manifest) if [ -z "$TARGET_PLATFORM" ]; then - TARGET_PLATFORM=$(printf '' "/") + TARGET_PLATFORM=$(printf '' "/") fi # Build php_minimum tag @@ -318,11 +320,12 @@ jobs: PHP_TAG="${PHP_MINIMUM}" fi - DOWNLOAD_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip" - INFO_URL="https://github.com/${REPO}/releases/tag/v${VERSION}" + DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip" + INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/v${VERSION}" - # -- Build stable entry to temp file ───────────────────────── - { + # -- 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" @@ -331,9 +334,7 @@ jobs: printf '%s\n' " ${VERSION}" [ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}" [ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}" - printf '%s\n' ' ' - printf '%s\n' ' stable' - printf '%s\n' ' ' + printf '%s\n' " ${TAG_NAME}" printf '%s\n' " ${INFO_URL}" printf '%s\n' ' ' printf '%s\n' " ${DOWNLOAD_URL}" @@ -343,35 +344,22 @@ jobs: printf '%s\n' ' Moko Consulting' printf '%s\n' ' https://mokoconsulting.tech' printf '%s\n' ' ' - } > /tmp/stable_entry.xml - - # -- Write updates.xml preserving dev/rc entries ────────────── - # Extract existing entries for other stability levels - # Order reflects release workflow: development → alpha → beta → rc → stable - if [ -f "updates.xml" ]; then - printf 'import re, sys\n' > /tmp/extract.py - printf 'with open("updates.xml") as f: c = f.read()\n' >> /tmp/extract.py - printf 'tag = sys.argv[1]\n' >> /tmp/extract.py - printf 'm = re.search(r"( .*?" + re.escape(tag) + r".*?)", c, re.DOTALL)\n' >> /tmp/extract.py - printf 'if m: print(m.group(1))\n' >> /tmp/extract.py - fi - DEV_ENTRY=$(python3 /tmp/extract.py development 2>/dev/null || true) - ALPHA_ENTRY=$(python3 /tmp/extract.py alpha 2>/dev/null || true) - BETA_ENTRY=$(python3 /tmp/extract.py beta 2>/dev/null || true) - RC_ENTRY=$(python3 /tmp/extract.py rc 2>/dev/null || true) + } + # -- Write updates.xml with cascading channels + # Stable release updates ALL channels (development, alpha, beta, rc, stable) { printf '%s\n' '' printf '%s\n' '' - [ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY" - [ -n "$ALPHA_ENTRY" ] && echo "$ALPHA_ENTRY" - [ -n "$BETA_ENTRY" ] && echo "$BETA_ENTRY" - [ -n "$RC_ENTRY" ] && echo "$RC_ENTRY" - cat /tmp/stable_entry.xml + build_entry "development" + build_entry "alpha" + build_entry "beta" + build_entry "rc" + build_entry "stable" printf '%s\n' '' } > updates.xml - echo "updates.xml: ${VERSION} (stable + rc/dev preserved)" >> $GITHUB_STEP_SUMMARY + echo "updates.xml: ${VERSION} (all channels updated to stable)" >> $GITHUB_STEP_SUMMARY # -- Commit all changes --------------------------------------------------- - name: Commit release changes @@ -384,11 +372,13 @@ jobs: exit 0 fi VERSION="${{ steps.version.outputs.version }}" - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" + git 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="github-actions[bot] " + --author="gitea-actions[bot] " git push # -- STEP 6: Create tag --------------------------------------------------- @@ -409,69 +399,76 @@ jobs: fi echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY - # -- STEP 7: Create or update GitHub Release ------------------------------ - - name: "Step 7: GitHub Release" + # -- STEP 7: Create or update Gitea Release -------------------------------- + - name: "Step 7: Gitea Release" if: >- steps.version.outputs.skip != 'true' && steps.check.outputs.tag_exists != 'true' - env: - GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} run: | 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}" - NOTES=$(php /tmp/mokostandards/api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null) + NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null) [ -z "$NOTES" ] && NOTES="Release ${VERSION}" - echo "$NOTES" > /tmp/release_notes.md # Check if the major release already exists - EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true) + 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 [ -z "$EXISTING" ]; then + if [ -z "$EXISTING_ID" ]; then # First release for this major - gh release create "$RELEASE_TAG" \ - --title "v${MAJOR} (latest: ${VERSION})" \ - --notes-file /tmp/release_notes.md \ - --target "$BRANCH" + 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': 'v${MAJOR} (latest: ${VERSION})', + 'body': '''${NOTES}''', + 'target_commitish': '${BRANCH}' + }))")" echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY else # Append version notes to existing major release - CURRENT_NOTES=$(gh release view "$RELEASE_TAG" --json body -q .body 2>/dev/null || true) - { - echo "$CURRENT_NOTES" - echo "" - echo "---" - echo "### ${VERSION}" - echo "" - cat /tmp/release_notes.md - } > /tmp/updated_notes.md + CURRENT_BODY=$(echo "$EXISTING" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body',''))" 2>/dev/null || true) + UPDATED_BODY="${CURRENT_BODY} - gh release edit "$RELEASE_TAG" \ - --title "v${MAJOR} (latest: ${VERSION})" \ - --notes-file /tmp/updated_notes.md + --- + ### ${VERSION} + + ${NOTES}" + + curl -sf -X PATCH -H "Authorization: token ${{ secrets.GA_TOKEN }}" \ + -H "Content-Type: application/json" \ + "${API_BASE}/releases/${EXISTING_ID}" \ + -d "$(python3 -c "import json,sys; print(json.dumps({ + 'name': 'v${MAJOR} (latest: ${VERSION})', + 'body': sys.stdin.read() + }))" <<< "$UPDATED_BODY")" echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY fi # -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------ - # Every patch builds an install-ready ZIP and uploads it to the minor release. - # Result: one Release per minor version with a ZIP for each patch. - name: "Step 8: Build Joomla package and update checksum" if: >- steps.version.outputs.skip != 'true' - env: - GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} run: | 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) - gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || { + 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) @@ -505,27 +502,108 @@ jobs: 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 ---------------------------------- - gh release upload "$RELEASE_TAG" "/tmp/${ZIP_NAME}" --clobber 2>/dev/null || true - gh release upload "$RELEASE_TAG" "/tmp/${TAR_NAME}" --clobber 2>/dev/null || true + 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="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}" - TAR_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${TAR_NAME}" + 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}" - # Replace downloads block with both formats + SHA - sed -i "s|.*|\n ${ZIP_URL}\n ${TAR_URL}\n |" updates.xml 2>/dev/null || true - if grep -q '' updates.xml; then - sed -i "s|.*|sha256:${SHA256_ZIP}|" updates.xml - else - sed -i "s||\n sha256:${SHA256_ZIP}|" updates.xml - fi + # 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" + f" {tar_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="github-actions[bot] " || true + --author="gitea-actions[bot] " || true git push || true + + # Also update updates.xml on main via Gitea API (git push blocked by branch protection) + if [ "$CURRENT_BRANCH" != "main" ]; then + 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: update stable channel to ${VERSION} on main [skip ci]" \ + --arg branch "main" \ + '{content: $content, sha: $sha, message: $msg, branch: $branch}' + )" > /dev/null && echo "updates.xml synced to main via API" || echo "WARNING: failed to sync updates.xml to main" + fi + fi fi echo "### Joomla Packages" >> $GITHUB_STEP_SUMMARY @@ -535,7 +613,50 @@ jobs: 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 | [${PACKAGE_NAME}](https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}) |" >> $GITHUB_STEP_SUMMARY + echo "| Download | [${ZIP_NAME}](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}) |" >> $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_MIRROR_TOKEN != '' + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }} + run: | + 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/mokostandards-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 # -- Summary -------------------------------------------------------------- - name: Pipeline Summary @@ -556,5 +677,5 @@ jobs: echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Release | [View](https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY + echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY fi diff --git a/.github/workflows/update-server.yml b/.github/workflows/update-server.yml index 83c8e0da..df227fd8 100644 --- a/.github/workflows/update-server.yml +++ b/.github/workflows/update-server.yml @@ -3,9 +3,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: GitHub.Workflow +# DEFGROUP: Gitea.Workflow # INGROUP: MokoStandards.Joomla -# REPO: https://github.com/mokoconsulting-tech/MokoStandards +# 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 @@ -46,6 +46,9 @@ on: 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 @@ -61,39 +64,40 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: - token: ${{ secrets.GH_TOKEN || github.token }} + token: ${{ secrets.GA_TOKEN }} fetch-depth: 0 - name: Setup MokoStandards tools env: - GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' + MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }} + MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting + COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' run: | git clone --depth 1 --branch version/04 --quiet \ - "https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \ - /tmp/mokostandards 2>/dev/null || true - if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then - cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true + "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ + /tmp/mokostandards-api 2>/dev/null || true + if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then + cd /tmp/mokostandards-api && 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 }}" - VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0") + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0") - # Auto-bump patch on alpha/beta/rc branches (not dev — dev bumps manually) - if [[ "$BRANCH" != dev/* ]]; then - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - BUMPED=$(php /tmp/mokostandards/api/cli/version_bump.php --path . 2>/dev/null || true) - if [ -n "$BUMPED" ]; then - VERSION=$(php /tmp/mokostandards/api/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="github-actions[bot] " 2>/dev/null || true - git push 2>/dev/null || true - fi + # 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/mokostandards-api/cli/version_bump.php --path . 2>/dev/null || true) + if [ -n "$BUMPED" ]; then + VERSION=$(php /tmp/mokostandards-api/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 @@ -111,6 +115,8 @@ jobs: STABILITY="stable" fi + echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT" + # Parse manifest (portable — no grep -P) MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1) if [ -z "$MANIFEST" ]; then @@ -140,7 +146,7 @@ jobs: # Use manifest version if README version is empty [ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION" - [ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '' "/") + [ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '' "/") CLIENT_TAG="" [ -n "$EXT_CLIENT" ] && CLIENT_TAG="${EXT_CLIENT}" @@ -173,10 +179,10 @@ jobs: esac PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip" - DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}" - INFO_URL="https://github.com/${REPO}" + 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) ─────────────────── + # -- Build install packages (ZIP + tar.gz) -------------------- SOURCE_DIR="src" [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" if [ -d "$SOURCE_DIR" ]; then @@ -192,20 +198,62 @@ jobs: SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1) - # Ensure release exists - gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || \ - gh release create "$RELEASE_TAG" --title "${RELEASE_TAG} (${DISPLAY_VERSION})" --notes "${STABILITY} release" --prerelease --target main 2>/dev/null || true + # 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) - # Upload both formats - gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" --clobber 2>/dev/null || true - gh release upload "$RELEASE_TAG" "/tmp/${TAR_NAME}" --clobber 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 ─────────────────────────────────────── + # -- Build the new entry ----------------------------------------- NEW_ENTRY="" NEW_ENTRY="${NEW_ENTRY} \n" NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME}\n" @@ -220,37 +268,65 @@ jobs: NEW_ENTRY="${NEW_ENTRY} \n" NEW_ENTRY="${NEW_ENTRY} ${INFO_URL}\n" NEW_ENTRY="${NEW_ENTRY} \n" - TAR_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz" + TAR_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz" NEW_ENTRY="${NEW_ENTRY} ${DOWNLOAD_URL}\n" NEW_ENTRY="${NEW_ENTRY} ${TAR_URL}\n" NEW_ENTRY="${NEW_ENTRY} \n" - [ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} sha256:${SHA256}\n" + [ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} ${SHA256}\n" NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n" [ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n" NEW_ENTRY="${NEW_ENTRY} Moko Consulting\n" NEW_ENTRY="${NEW_ENTRY} https://mokoconsulting.tech\n" NEW_ENTRY="${NEW_ENTRY} " - # ── Write new entry to temp file ─────────────────────────────── + # -- Write new entry to temp file -------------------------------- printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml - # ── Merge into updates.xml ───────────────────────────────────── + # -- Merge into updates.xml (only update this stability channel) - + # Cascading update: each stability level updates itself and all lower levels + # stable → all | rc → rc,beta,alpha,dev | beta → beta,alpha,dev | 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}" + if [ ! -f "updates.xml" ]; then printf '%s\n' '' > updates.xml printf '%s\n' '' >> updates.xml cat /tmp/new_entry.xml >> updates.xml printf '\n%s\n' '' >> updates.xml else - # Remove existing entry for this stability, insert new one - printf 'import re\nstability = "%s"\n' "${STABILITY}" > /tmp/merge_xml.py - printf 'with open("updates.xml") as f: content = f.read()\n' >> /tmp/merge_xml.py - printf 'with open("/tmp/new_entry.xml") as f: new_entry = f.read()\n' >> /tmp/merge_xml.py - printf 'pattern = r" .*?" + re.escape(stability) + r".*?\\n?"\n' >> /tmp/merge_xml.py - printf 'content = re.sub(pattern, "", content, flags=re.DOTALL)\n' >> /tmp/merge_xml.py - printf 'content = content.replace("", new_entry + "\\n")\n' >> /tmp/merge_xml.py - printf 'content = re.sub(r"\\n{3,}", "\\n\\n", content)\n' >> /tmp/merge_xml.py - printf 'with open("updates.xml", "w") as f: f.write(content)\n' >> /tmp/merge_xml.py - python3 /tmp/merge_xml.py 2>/dev/null || { + # Replace each cascading channel with the new entry (different tag) + export PY_TARGETS="$TARGETS" + python3 << PYEOF + import re, os + targets = os.environ["PY_TARGETS"].split(",") + stability = "${STABILITY}" + 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 + new_entry = re.sub(r"[^<]*", f"{tag}", new_entry_template) + # Remove existing entry for this tag + pattern = r" .*?" + re.escape(tag) + r".*?\n?" + content = re.sub(pattern, "", content, flags=re.DOTALL) + # Insert before + content = content.replace("", new_entry + "\n") + content = re.sub(r"\n{3,}", "\n\n", content) + with open("updates.xml", "w") as f: + f.write(content) + PYEOF + if [ $? -ne 0 ]; then # Fallback: rebuild keeping other stability entries { printf '%s\n' '' @@ -265,19 +341,39 @@ jobs: printf '\n%s\n' '' } > /tmp/updates_new.xml mv /tmp/updates_new.xml updates.xml - } + fi fi # Commit - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" + 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="github-actions[bot] " + --author="gitea-actions[bot] " git push } + # -- Mirror to GitHub (stable and rc only) -------------------------------- + - name: Mirror release to GitHub + if: >- + (steps.update.outputs.stability == 'stable' || steps.update.outputs.stability == 'rc') && + secrets.GH_MIRROR_TOKEN != '' + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }} + run: | + GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" + STABILITY="${{ steps.update.outputs.stability }}" + echo "GitHub mirror sync for ${STABILITY} — ${GH_REPO}" >> $GITHUB_STEP_SUMMARY + # Mirror packages if they exist + for PKG in /tmp/*.zip /tmp/*.tar.gz; 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 + - name: SFTP deploy to dev server if: contains(github.ref, 'dev/') env: @@ -288,15 +384,15 @@ jobs: DEV_PORT: ${{ vars.DEV_FTP_PORT }} DEV_KEY: ${{ secrets.DEV_FTP_KEY }} DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }} - GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} run: | - # ── Permission check: admin or maintain role required ────── + # -- Permission check: admin or maintain role required -------- ACTOR="${{ github.actor }}" REPO="${{ github.repository }}" - PERMISSION=$(gh api "repos/${REPO}/collaborators/${ACTOR}/permission" \ - --jq '.permission' 2>/dev/null || \ - gh api "repos/${REPO}/collaborators/${ACTOR}" \ - --jq '.role' 2>/dev/null || echo "read") + 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) ;; *) @@ -324,11 +420,11 @@ jobs: printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json fi - PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null || true) - if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards/api/deploy/deploy-joomla.php" ]; then - php /tmp/mokostandards/api/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json - elif [ -f "/tmp/mokostandards/api/deploy/deploy-sftp.php" ]; then - php /tmp/mokostandards/api/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json + PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true) + if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then + php /tmp/mokostandards-api/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json + elif [ -f "/tmp/mokostandards-api/deploy/deploy-sftp.php" ]; then + php /tmp/mokostandards-api/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