diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index 0745b52..614bebd 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -8,6 +8,7 @@ MokoOnyx MokoConsulting MokoOnyx - Joomla site template (successor to MokoCassiopeia) + 02.07.02 GNU General Public License v3 diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml index 10a7e51..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" @@ -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 @@ -76,9 +77,9 @@ 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): 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 9544f96..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 @@ -155,6 +161,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" @@ -167,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) @@ -261,6 +269,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' && @@ -271,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] " @@ -306,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 @@ -322,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 @@ -338,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) ----- @@ -349,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 @@ -366,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] " @@ -384,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 @@ -393,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 }}" @@ -402,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 @@ -411,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" \ @@ -434,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" @@ -442,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}" \ @@ -454,25 +467,25 @@ jobs: "${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 - - - 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 + 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.MOKOGITEA_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 @@ -483,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/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 new file mode 100644 index 0000000..f084fe1 --- /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.MOKOGITEA_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 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 f0b1d88..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 @@ -87,16 +87,24 @@ 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]" - 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]" @@ -112,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" @@ -131,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 @@ -142,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 @@ -199,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/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 diff --git a/.mokogitea/workflows/update-server.yml b/.mokogitea/workflows/update-server.yml index 8660a43..cd2eff0 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' @@ -75,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 @@ -97,28 +95,28 @@ 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) + # Configure git for bot pushes 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 + git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" - # Determine stability from branch or input + # 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") + + # Determine stability from branch or manual input if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then STABILITY="${{ inputs.stability }}" elif [[ "$BRANCH" == rc/* ]]; then @@ -127,263 +125,96 @@ 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 (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 + # 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.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.MOKOGITEA_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 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 }}" + 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 @@ -394,27 +225,22 @@ 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( '${API_BASE}/contents/updates.xml', data=payload, method='PUT', headers={ - 'Authorization': 'token ${GA_TOKEN}', + 'Authorization': 'token ${GITEA_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 + print(f'WARNING: sync to main failed: {e}', file=sys.stderr) + " fi - name: SFTP deploy to dev server @@ -428,12 +254,11 @@ 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 }}" \ + 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 @@ -463,198 +288,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 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; +} diff --git a/src/templateDetails.xml b/src/templateDetails.xml index 8f3fab3..ba69ce9 100644 --- a/src/templateDetails.xml +++ b/src/templateDetails.xml @@ -36,7 +36,7 @@ MokoOnyx - 02.07.00 + 02.07.02-dev script.php 2026-05-16 Jonathan Miller || Moko Consulting @@ -366,3 +366,4 @@ +