Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25257b9e31 | |||
| a5bdc89faa | |||
| 0ecba968a0 | |||
| bed7adcf1c | |||
| df59b5f6d5 | |||
| 5786f0dfc4 | |||
| 2de87d8ff4 | |||
| b241acf650 | |||
| 173dfd0f26 | |||
| 1ad277cd73 | |||
| 4624385501 |
@@ -0,0 +1,251 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: moko-platform.Automation
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
# PATH: /.gitea/workflows/branch-protection.yml
|
||||||
|
# BRIEF: Apply standardised branch protection rules to all governed repositories
|
||||||
|
#
|
||||||
|
# +========================================================================+
|
||||||
|
# | BRANCH PROTECTION SETUP |
|
||||||
|
# +========================================================================+
|
||||||
|
# | |
|
||||||
|
# | Applies protection rules for: main, dev, rc, beta, alpha |
|
||||||
|
# | |
|
||||||
|
# | main — Require PR, block rejected reviews, no force push |
|
||||||
|
# | dev — Allow push, no force push, no delete |
|
||||||
|
# | rc — Allow push, no force push, no delete |
|
||||||
|
# | beta — Allow push, no force push, no delete |
|
||||||
|
# | alpha — Allow push, no force push, no delete |
|
||||||
|
# | |
|
||||||
|
# | jmiller has override authority on all branches. |
|
||||||
|
# | |
|
||||||
|
# +========================================================================+
|
||||||
|
|
||||||
|
name: Branch Protection Setup
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 2 * * 1' # Weekly Monday 02:00 UTC
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
dry_run:
|
||||||
|
description: 'Preview mode (no changes)'
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
repos:
|
||||||
|
description: 'Comma-separated repo names (empty = all governed repos)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITEA_URL: https://git.mokoconsulting.tech
|
||||||
|
GITEA_ORG: MokoConsulting
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
protect:
|
||||||
|
name: Apply Branch Protection Rules
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Determine target repos
|
||||||
|
id: repos
|
||||||
|
env:
|
||||||
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
API="${GITEA_URL}/api/v1"
|
||||||
|
|
||||||
|
# Platform/standards/infra repos to exclude
|
||||||
|
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private MokoStandards moko-platform MokoTesting"
|
||||||
|
EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate"
|
||||||
|
|
||||||
|
if [ -n "${{ inputs.repos }}" ]; then
|
||||||
|
# User-specified repos
|
||||||
|
REPOS=$(echo "${{ inputs.repos }}" | tr ',' ' ')
|
||||||
|
else
|
||||||
|
# Fetch all org repos
|
||||||
|
PAGE=1
|
||||||
|
REPOS=""
|
||||||
|
while true; do
|
||||||
|
BATCH=$(curl -sS \
|
||||||
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
"${API}/orgs/${GITEA_ORG}/repos?page=${PAGE}&limit=50" \
|
||||||
|
| jq -r '.[].name // empty')
|
||||||
|
[ -z "$BATCH" ] && break
|
||||||
|
REPOS="$REPOS $BATCH"
|
||||||
|
PAGE=$((PAGE + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
# Filter out excluded repos
|
||||||
|
FILTERED=""
|
||||||
|
for REPO in $REPOS; do
|
||||||
|
SKIP=false
|
||||||
|
for EX in $EXCLUDE; do
|
||||||
|
if [ "$REPO" = "$EX" ]; then
|
||||||
|
SKIP=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$SKIP" = "false" ]; then
|
||||||
|
FILTERED="$FILTERED $REPO"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
REPOS="$FILTERED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "repos=$REPOS" >> "$GITHUB_OUTPUT"
|
||||||
|
COUNT=$(echo "$REPOS" | wc -w)
|
||||||
|
echo "📋 Target repos (${COUNT}): $REPOS"
|
||||||
|
|
||||||
|
- name: Apply protection rules
|
||||||
|
env:
|
||||||
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
|
DRY_RUN: ${{ inputs.dry_run || 'false' }}
|
||||||
|
run: |
|
||||||
|
API="${GITEA_URL}/api/v1"
|
||||||
|
REPOS="${{ steps.repos.outputs.repos }}"
|
||||||
|
|
||||||
|
SUCCESS=0
|
||||||
|
FAILED=0
|
||||||
|
SKIPPED=0
|
||||||
|
|
||||||
|
# ── Rule definitions ──────────────────────────────────────
|
||||||
|
# Only the CI bot (jmiller token) can push directly.
|
||||||
|
# All human contributors must use PRs.
|
||||||
|
# Force push disabled on all branches.
|
||||||
|
|
||||||
|
RULE_MAIN='{
|
||||||
|
"rule_name": "main",
|
||||||
|
"enable_push": true,
|
||||||
|
"enable_push_whitelist": true,
|
||||||
|
"push_whitelist_usernames": ["jmiller"],
|
||||||
|
"enable_force_push": false,
|
||||||
|
"enable_force_push_allowlist": false,
|
||||||
|
"force_push_allowlist_usernames": [],
|
||||||
|
"enable_merge_whitelist": false,
|
||||||
|
"required_approvals": 0,
|
||||||
|
"dismiss_stale_approvals": true,
|
||||||
|
"block_on_rejected_reviews": true,
|
||||||
|
"block_on_outdated_branch": false,
|
||||||
|
"priority": 1
|
||||||
|
}'
|
||||||
|
|
||||||
|
RULE_DEV='{
|
||||||
|
"rule_name": "dev",
|
||||||
|
"enable_push": true,
|
||||||
|
"enable_push_whitelist": true,
|
||||||
|
"push_whitelist_usernames": ["jmiller"],
|
||||||
|
"enable_force_push": false,
|
||||||
|
"enable_force_push_allowlist": false,
|
||||||
|
"force_push_allowlist_usernames": [],
|
||||||
|
"enable_merge_whitelist": false,
|
||||||
|
"required_approvals": 0,
|
||||||
|
"block_on_rejected_reviews": false,
|
||||||
|
"priority": 2
|
||||||
|
}'
|
||||||
|
|
||||||
|
RULE_RC='{
|
||||||
|
"rule_name": "rc",
|
||||||
|
"enable_push": true,
|
||||||
|
"enable_push_whitelist": true,
|
||||||
|
"push_whitelist_usernames": ["jmiller"],
|
||||||
|
"enable_force_push": false,
|
||||||
|
"enable_force_push_allowlist": false,
|
||||||
|
"force_push_allowlist_usernames": [],
|
||||||
|
"enable_merge_whitelist": false,
|
||||||
|
"required_approvals": 0,
|
||||||
|
"block_on_rejected_reviews": false,
|
||||||
|
"priority": 3
|
||||||
|
}'
|
||||||
|
|
||||||
|
RULE_BETA='{
|
||||||
|
"rule_name": "beta",
|
||||||
|
"enable_push": true,
|
||||||
|
"enable_push_whitelist": true,
|
||||||
|
"push_whitelist_usernames": ["jmiller"],
|
||||||
|
"enable_force_push": false,
|
||||||
|
"enable_force_push_allowlist": false,
|
||||||
|
"force_push_allowlist_usernames": [],
|
||||||
|
"enable_merge_whitelist": false,
|
||||||
|
"required_approvals": 0,
|
||||||
|
"block_on_rejected_reviews": false,
|
||||||
|
"priority": 4
|
||||||
|
}'
|
||||||
|
|
||||||
|
RULE_ALPHA='{
|
||||||
|
"rule_name": "alpha",
|
||||||
|
"enable_push": true,
|
||||||
|
"enable_push_whitelist": true,
|
||||||
|
"push_whitelist_usernames": ["jmiller"],
|
||||||
|
"enable_force_push": false,
|
||||||
|
"enable_force_push_allowlist": false,
|
||||||
|
"force_push_allowlist_usernames": [],
|
||||||
|
"enable_merge_whitelist": false,
|
||||||
|
"required_approvals": 0,
|
||||||
|
"block_on_rejected_reviews": false,
|
||||||
|
"priority": 5
|
||||||
|
}'
|
||||||
|
|
||||||
|
RULES=("$RULE_MAIN" "$RULE_DEV" "$RULE_RC" "$RULE_BETA" "$RULE_ALPHA")
|
||||||
|
RULE_NAMES=("main" "dev" "rc" "beta" "alpha")
|
||||||
|
|
||||||
|
# ── Apply rules to each repo ──────────────────────────────
|
||||||
|
for REPO in $REPOS; do
|
||||||
|
echo ""
|
||||||
|
echo "═══ ${REPO} ═══"
|
||||||
|
|
||||||
|
for i in "${!RULES[@]}"; do
|
||||||
|
RULE="${RULES[$i]}"
|
||||||
|
NAME="${RULE_NAMES[$i]}"
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = "true" ]; then
|
||||||
|
echo " [DRY RUN] Would apply rule: ${NAME}"
|
||||||
|
SKIPPED=$((SKIPPED + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Delete existing rule if present (idempotent recreate)
|
||||||
|
ENCODED_NAME=$(echo "$NAME" | sed 's|/|%2F|g')
|
||||||
|
curl -sS -o /dev/null -w "" \
|
||||||
|
-X DELETE \
|
||||||
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections/${ENCODED_NAME}" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create rule
|
||||||
|
RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$RULE" \
|
||||||
|
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections")
|
||||||
|
|
||||||
|
HTTP=$(echo "$RESPONSE" | tail -1)
|
||||||
|
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||||
|
|
||||||
|
if [ "$HTTP" = "201" ]; then
|
||||||
|
echo " ✅ ${NAME}"
|
||||||
|
SUCCESS=$((SUCCESS + 1))
|
||||||
|
else
|
||||||
|
echo " ❌ ${NAME} (HTTP ${HTTP}): $(echo "$BODY" | jq -r '.message // .' 2>/dev/null | head -1)"
|
||||||
|
FAILED=$((FAILED + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Summary ───────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "════════════════════════════════════════"
|
||||||
|
echo " ✅ Success: ${SUCCESS}"
|
||||||
|
echo " ❌ Failed: ${FAILED}"
|
||||||
|
echo " ⏭️ Skipped: ${SKIPPED}"
|
||||||
|
echo "════════════════════════════════════════"
|
||||||
|
|
||||||
|
if [ "$FAILED" -gt 0 ]; then
|
||||||
|
echo "::warning::${FAILED} rule(s) failed to apply"
|
||||||
|
fi
|
||||||
@@ -16,6 +16,10 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
|
- alpha
|
||||||
|
- beta
|
||||||
|
- rc
|
||||||
|
- 'feature/**'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
@@ -37,7 +41,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
- name: Setup moko-platform tools
|
||||||
@@ -49,7 +53,7 @@ jobs:
|
|||||||
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
||||||
else
|
else
|
||||||
git clone --depth 1 --branch main --quiet \
|
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
|
/tmp/moko-platform-api
|
||||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||||
@@ -57,28 +61,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
run: |
|
run: |
|
||||||
BUMP=$(php ${MOKO_CLI}/version_bump.php --path . 2>&1) || true
|
php ${MOKO_CLI}/version_auto_bump.php \
|
||||||
echo "$BUMP"
|
--path . --branch "${GITHUB_REF_NAME}" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) || true
|
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
[ -z "$VERSION" ] && { echo "No version found — skipping"; exit 0; }
|
|
||||||
|
|
||||||
# Propagate to platform manifests
|
|
||||||
php ${MOKO_CLI}/version_set_platform.php \
|
|
||||||
--path . --version "$VERSION" --branch dev 2>/dev/null || true
|
|
||||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
|
||||||
|
|
||||||
# Commit if anything changed
|
|
||||||
if git diff --quiet && git diff --cached --quiet; then
|
|
||||||
echo "No version changes to commit"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
|
||||||
git config --local user.name "gitea-actions[bot]"
|
|
||||||
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
|
||||||
git add -A
|
|
||||||
git commit -m "chore(version): patch bump to ${VERSION} [skip ci]" \
|
|
||||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
|
||||||
git push origin dev
|
|
||||||
echo "Bumped to ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|||||||
@@ -63,31 +63,75 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
- name: Setup moko-platform tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
run: |
|
run: |
|
||||||
if ! command -v composer &> /dev/null; then
|
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
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||||
|
rm -rf /tmp/moko-platform-api
|
||||||
git clone --depth 1 --branch main --quiet \
|
git clone --depth 1 --branch main --quiet \
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||||
/tmp/moko-platform-api
|
/tmp/moko-platform-api
|
||||||
cd /tmp/moko-platform-api
|
cd /tmp/moko-platform-api
|
||||||
composer install --no-dev --no-interaction --quiet
|
composer install --no-dev --no-interaction --quiet
|
||||||
|
|
||||||
- name: Promote to release-candidate
|
- name: Rename source branch to rc
|
||||||
|
run: |
|
||||||
|
SOURCE_BRANCH="${{ github.event.pull_request.head.ref || 'dev' }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
PR_NUM="${{ github.event.pull_request.number }}"
|
||||||
|
php /tmp/moko-platform-api/cli/branch_rename.php \
|
||||||
|
--from "$SOURCE_BRANCH" --to rc \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
--api-base "${API_BASE}" \
|
||||||
|
--pr "$PR_NUM"
|
||||||
|
|
||||||
|
- name: Set RC version on renamed branch
|
||||||
|
run: |
|
||||||
|
# Checkout the new rc branch
|
||||||
|
git fetch origin rc
|
||||||
|
git checkout rc
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
MOKO_CLI="/tmp/moko-platform-api/cli"
|
||||||
|
|
||||||
|
VERSION=$(php ${MOKO_CLI}/version_read.php --path .) || true
|
||||||
|
[ -z "$VERSION" ] && { echo "No version — skipping"; exit 0; }
|
||||||
|
|
||||||
|
php ${MOKO_CLI}/version_set_platform.php \
|
||||||
|
--path . --version "$VERSION" --branch rc --stability rc 2>/dev/null || true
|
||||||
|
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||||
|
|
||||||
|
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git add -A
|
||||||
|
git commit -m "chore(version): set RC stability suffix [skip ci]" \
|
||||||
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
|
git push origin rc
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build RC release
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php /tmp/moko-platform-api/cli/release_promote.php \
|
MOKO_CLI="/tmp/moko-platform-api/cli"
|
||||||
--from auto --to release-candidate \
|
VERSION=$(php ${MOKO_CLI}/version_read.php --path .) || true
|
||||||
--token "${{ secrets.GA_TOKEN }}" \
|
|
||||||
--api-base "${API_BASE}" \
|
php ${MOKO_CLI}/release_create.php \
|
||||||
--branch "${{ github.event.pull_request.head.ref || 'dev' }}"
|
--path . --version "$VERSION" --tag "release-candidate" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--repo "${GITEA_REPO}" --branch rc 2>&1 || true
|
||||||
|
|
||||||
|
php ${MOKO_CLI}/release_package.php \
|
||||||
|
--path . --version "$VERSION" --tag "release-candidate" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--repo "${GITEA_REPO}" --output /tmp 2>&1 || true
|
||||||
|
|
||||||
- name: Cascade lesser channels
|
- name: Cascade lesser channels
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@@ -95,14 +139,14 @@ jobs:
|
|||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php /tmp/moko-platform-api/cli/release_cascade.php \
|
php /tmp/moko-platform-api/cli/release_cascade.php \
|
||||||
--stability release-candidate \
|
--stability release-candidate \
|
||||||
--token "${{ secrets.GA_TOKEN }}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
--api-base "${API_BASE}"
|
--api-base "${API_BASE}"
|
||||||
|
|
||||||
- name: Summary
|
- name: Summary
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "Draft PR opened — promoted highest pre-release to RC" >> $GITHUB_STEP_SUMMARY
|
echo "Draft PR opened — branch renamed to rc, RC release built" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
|
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
|
||||||
release:
|
release:
|
||||||
@@ -116,19 +160,27 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 0
|
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
|
- name: Setup moko-platform tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
||||||
run: |
|
run: |
|
||||||
# Ensure PHP + Composer are available
|
# Ensure PHP + Composer are available
|
||||||
if ! command -v composer &> /dev/null; then
|
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
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||||
|
rm -rf /tmp/moko-platform-api
|
||||||
git clone --depth 1 --branch main --quiet \
|
git clone --depth 1 --branch main --quiet \
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||||
/tmp/moko-platform-api
|
/tmp/moko-platform-api
|
||||||
@@ -155,7 +207,8 @@ jobs:
|
|||||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
# version_set_platform strips suffixes internally when --stability stable
|
||||||
|
MAJOR=$(echo "$VERSION" | cut -d. -f1 | sed 's/-.*//')
|
||||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
|
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||||
@@ -167,9 +220,9 @@ jobs:
|
|||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
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 "{}")
|
"${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)
|
RC_ID=$(echo "$RC_JSON" | php -r "\$d=json_decode(file_get_contents('php://stdin'),true); echo \$d['id'] ?? '';" 2>/dev/null || true)
|
||||||
|
|
||||||
if [ -n "$RC_ID" ] && [ "$RC_ID" != "None" ] && [ "$RC_ID" != "" ]; then
|
if [ -n "$RC_ID" ] && [ "$RC_ID" != "None" ] && [ "$RC_ID" != "" ]; then
|
||||||
echo "promote=true" >> "$GITHUB_OUTPUT"
|
echo "promote=true" >> "$GITHUB_OUTPUT"
|
||||||
@@ -189,6 +242,7 @@ jobs:
|
|||||||
MOKO_API="/tmp/moko-platform-api/cli"
|
MOKO_API="/tmp/moko-platform-api/cli"
|
||||||
php ${MOKO_API}/version_bump.php --path . --minor 2>&1 || true
|
php ${MOKO_API}/version_bump.php --path . --minor 2>&1 || true
|
||||||
VERSION=$(php ${MOKO_API}/version_read.php --path .)
|
VERSION=$(php ${MOKO_API}/version_read.php --path .)
|
||||||
|
# version_set_platform handles suffix stripping — just pass clean base version
|
||||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
echo "Bumped to: ${VERSION}"
|
echo "Bumped to: ${VERSION}"
|
||||||
|
|
||||||
@@ -261,6 +315,18 @@ jobs:
|
|||||||
|
|
||||||
# Step 5 (updates.xml) moved after Step 8 to include SHA-256 checksum
|
# 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
|
- name: Commit release changes
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
@@ -271,14 +337,11 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
|
||||||
git config --local user.name "gitea-actions[bot]"
|
|
||||||
# Set push URL with token for branch-protected repos
|
|
||||||
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
|
||||||
git add -A
|
git add -A
|
||||||
git commit -m "chore(release): build ${VERSION} [skip ci]" \
|
git commit -m "chore(release): build ${VERSION} [skip ci]" \
|
||||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
git push -u origin HEAD
|
# Detached HEAD on PR merge — push explicitly to main
|
||||||
|
git push origin HEAD:refs/heads/main
|
||||||
|
|
||||||
# -- STEP 6: Create tag ---------------------------------------------------
|
# -- STEP 6: Create tag ---------------------------------------------------
|
||||||
- name: "Step 6: Create git tag"
|
- name: "Step 6: Create git tag"
|
||||||
@@ -306,7 +369,7 @@ jobs:
|
|||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php /tmp/moko-platform-api/cli/release_promote.php \
|
php /tmp/moko-platform-api/cli/release_promote.php \
|
||||||
--from release-candidate --to stable \
|
--from release-candidate --to stable \
|
||||||
--token "${{ secrets.GA_TOKEN }}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
--api-base "${API_BASE}" \
|
--api-base "${API_BASE}" \
|
||||||
--path . --branch main
|
--path . --branch main
|
||||||
echo "Promoted RC → stable (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
echo "Promoted RC → stable (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -322,7 +385,7 @@ jobs:
|
|||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php /tmp/moko-platform-api/cli/release_create.php \
|
php /tmp/moko-platform-api/cli/release_create.php \
|
||||||
--path . --version "$VERSION" --tag "$RELEASE_TAG" \
|
--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
|
--repo "${GITEA_REPO}" --branch main
|
||||||
echo "Release created: ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
echo "Release created: ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
@@ -338,7 +401,7 @@ jobs:
|
|||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php /tmp/moko-platform-api/cli/release_package.php \
|
php /tmp/moko-platform-api/cli/release_package.php \
|
||||||
--path . --version "$VERSION" --tag "$RELEASE_TAG" \
|
--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
|
--repo "${GITEA_REPO}" --output /tmp || true
|
||||||
|
|
||||||
# -- STEP 5: Write update stream (after build so SHA-256 is available) -----
|
# -- STEP 5: Write update stream (after build so SHA-256 is available) -----
|
||||||
@@ -349,11 +412,11 @@ jobs:
|
|||||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||||
|
|
||||||
# Fetch latest updates.xml from main so preserve logic has all channels
|
# 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 }}"
|
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 | \
|
"${API}/contents/updates.xml?ref=main" 2>/dev/null | \
|
||||||
python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin)['content']).decode())" \
|
php -r "\$d=json_decode(file_get_contents('php://stdin'),true); echo base64_decode(\$d['content'] ?? '');" \
|
||||||
> updates.xml 2>/dev/null || true
|
> updates.xml 2>/dev/null || true
|
||||||
|
|
||||||
SHA_FLAG=""
|
SHA_FLAG=""
|
||||||
@@ -366,13 +429,10 @@ jobs:
|
|||||||
|
|
||||||
# Commit updates.xml if changed
|
# Commit updates.xml if changed
|
||||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
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 add updates.xml
|
||||||
git commit -m "chore: update stable channel ${VERSION} [skip ci]" \
|
git commit -m "chore: update stable channel ${VERSION} [skip ci]" \
|
||||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
git push origin HEAD 2>&1 || true
|
git push origin HEAD:refs/heads/main 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# -- STEP 8b: Update release description with changelog ----------------------
|
# -- STEP 8b: Update release description with changelog ----------------------
|
||||||
@@ -384,7 +444,7 @@ jobs:
|
|||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
php /tmp/moko-platform-api/cli/release_body_update.php \
|
php /tmp/moko-platform-api/cli/release_body_update.php \
|
||||||
--path . --version "${VERSION}" --tag "${RELEASE_TAG}" \
|
--path . --version "${VERSION}" --tag "${RELEASE_TAG}" \
|
||||||
--token "${{ secrets.GA_TOKEN }}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||||
2>&1 || true
|
2>&1 || true
|
||||||
echo "Release body updated" >> $GITHUB_STEP_SUMMARY
|
echo "Release body updated" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -393,7 +453,7 @@ jobs:
|
|||||||
- name: "Step 9: Mirror release to GitHub"
|
- name: "Step 9: Mirror release to GitHub"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
secrets.GH_TOKEN != ''
|
secrets.GH_MIRROR_TOKEN != ''
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
@@ -402,8 +462,8 @@ jobs:
|
|||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php /tmp/moko-platform-api/cli/release_mirror.php \
|
php /tmp/moko-platform-api/cli/release_mirror.php \
|
||||||
--version "$VERSION" --tag "$RELEASE_TAG" \
|
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||||
--token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
--gh-token "${{ secrets.GH_TOKEN }}" --gh-repo "$GH_REPO" \
|
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
|
||||||
--branch main 2>&1 || true
|
--branch main 2>&1 || true
|
||||||
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
|
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
@@ -411,14 +471,14 @@ jobs:
|
|||||||
- name: "Step 10: Push main to GitHub mirror"
|
- name: "Step 10: Push main to GitHub mirror"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
secrets.GH_TOKEN != ''
|
secrets.GH_MIRROR_TOKEN != ''
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||||
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
|
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
|
||||||
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
|
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 add github "https://x-access-token:${{ secrets.GH_MIRROR_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 set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
|
||||||
git fetch origin main --depth=1
|
git fetch origin main --depth=1
|
||||||
git push github origin/main:refs/heads/main --force 2>/dev/null \
|
git push github origin/main:refs/heads/main --force 2>/dev/null \
|
||||||
&& echo "main branch pushed to GitHub mirror" \
|
&& echo "main branch pushed to GitHub mirror" \
|
||||||
@@ -434,15 +494,23 @@ jobs:
|
|||||||
php /tmp/moko-platform-api/cli/release_cascade.php \
|
php /tmp/moko-platform-api/cli/release_cascade.php \
|
||||||
--stability stable \
|
--stability stable \
|
||||||
--version "${VERSION}" \
|
--version "${VERSION}" \
|
||||||
--token "${{ secrets.GA_TOKEN }}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
--api-base "${API_BASE}" 2>/dev/null || true
|
--api-base "${API_BASE}" 2>/dev/null || true
|
||||||
|
|
||||||
- name: "Step 11: Delete and recreate dev branch from main"
|
- name: "Step 11: Clean up pre-release branches and recreate dev from main"
|
||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
|
# Delete ephemeral pre-release branches (rc, alpha, beta)
|
||||||
|
for EPHEMERAL in rc alpha beta; do
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API_BASE}/branches/${EPHEMERAL}" 2>/dev/null \
|
||||||
|
&& echo "Deleted ${EPHEMERAL} branch" \
|
||||||
|
|| echo "${EPHEMERAL} branch not found"
|
||||||
|
done
|
||||||
|
|
||||||
# Delete dev branch
|
# Delete dev branch
|
||||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
@@ -454,7 +522,26 @@ jobs:
|
|||||||
"${API_BASE}/branches" \
|
"${API_BASE}/branches" \
|
||||||
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
|
-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
|
echo "Pre-release branches cleaned, dev reset from main" >> $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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# -- Dolibarr post-release: Reset dev version -----------------------------
|
# -- Dolibarr post-release: Reset dev version -----------------------------
|
||||||
@@ -464,7 +551,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php /tmp/moko-platform-api/cli/version_reset_dev.php \
|
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
|
--branch dev --path . 2>&1 || true
|
||||||
|
|
||||||
# -- Summary --------------------------------------------------------------
|
# -- Summary --------------------------------------------------------------
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: MokoStandards.Universal
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
|
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: Delete feature branches after PR merge
|
||||||
|
|
||||||
|
name: "Branch Cleanup"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cleanup:
|
||||||
|
name: Delete merged branch
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >-
|
||||||
|
github.event.pull_request.merged == true &&
|
||||||
|
github.event.pull_request.head.ref != 'dev' &&
|
||||||
|
github.event.pull_request.head.ref != 'main'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Delete source branch
|
||||||
|
run: |
|
||||||
|
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||||
|
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
||||||
|
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
|
||||||
|
|
||||||
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||||
|
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ "$STATUS" = "204" ]; then
|
||||||
|
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "$STATUS" = "404" ]; then
|
||||||
|
echo "Branch already deleted: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "::warning::Failed to delete branch ${BRANCH} (HTTP ${STATUS})"
|
||||||
|
fi
|
||||||
@@ -50,16 +50,18 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
- name: Setup moko-platform tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
run: |
|
run: |
|
||||||
if ! command -v composer &> /dev/null; then
|
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
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||||
|
rm -rf /tmp/moko-platform-api
|
||||||
git clone --depth 1 --branch main --quiet \
|
git clone --depth 1 --branch main --quiet \
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||||
/tmp/moko-platform-api
|
/tmp/moko-platform-api
|
||||||
@@ -87,16 +89,24 @@ jobs:
|
|||||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
|
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
|
||||||
[ -z "$VERSION" ] && VERSION="00.00.01"
|
[ -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 \
|
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
|
# Verify version consistency across all files
|
||||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
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
|
# Commit version bump
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
git config --local user.name "gitea-actions[bot]"
|
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 add -A
|
||||||
git diff --cached --quiet || {
|
git diff --cached --quiet || {
|
||||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
||||||
@@ -112,7 +122,7 @@ jobs:
|
|||||||
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||||
ZIP_NAME=$(grep '^zip_name=' "$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 "$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 "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||||
@@ -131,7 +141,7 @@ jobs:
|
|||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php ${MOKO_CLI}/release_create.php \
|
php ${MOKO_CLI}/release_create.php \
|
||||||
--path . --version "$VERSION" --tag "$TAG" \
|
--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
|
--repo "${GITEA_REPO}" --branch dev --prerelease
|
||||||
|
|
||||||
- name: Build package and upload
|
- name: Build package and upload
|
||||||
@@ -142,7 +152,7 @@ jobs:
|
|||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php ${MOKO_CLI}/release_package.php \
|
php ${MOKO_CLI}/release_package.php \
|
||||||
--path . --version "$VERSION" --tag "$TAG" \
|
--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
|
--repo "${GITEA_REPO}" --output /tmp || true
|
||||||
|
|
||||||
- name: Update updates.xml
|
- name: Update updates.xml
|
||||||
@@ -199,7 +209,7 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
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 \
|
php ${MOKO_CLI}/release_cascade.php \
|
||||||
--stability "${{ steps.meta.outputs.stability }}" \
|
--stability "${{ steps.meta.outputs.stability }}" \
|
||||||
|
|||||||
@@ -4,18 +4,16 @@
|
|||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: MokoStandards.Universal
|
# INGROUP: moko-platform.Universal
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
# PATH: /templates/workflows/update-server.yml
|
# PATH: /templates/workflows/update-server.yml
|
||||||
# VERSION: 04.07.00
|
# VERSION: 05.00.00
|
||||||
# BRIEF: Update server XML feed with stable/rc/beta/alpha/dev entries (universal)
|
# BRIEF: Pre-release build + update server XML for dev/alpha/beta/rc branches
|
||||||
#
|
#
|
||||||
# Writes updates.xml with multiple <update> entries:
|
# Thin wrapper around moko-platform CLI tools.
|
||||||
# - <tag>stable</tag> on push to main (from auto-release)
|
# Builds packages, updates updates.xml, and optionally deploys via SFTP.
|
||||||
# - <tag>rc</tag> on push to rc/**
|
|
||||||
# - <tag>development</tag> on push to dev or dev/**
|
|
||||||
#
|
#
|
||||||
# Joomla filters by user's "Minimum Stability" setting.
|
# Joomla filters update entries by the user's "Minimum Stability" setting.
|
||||||
|
|
||||||
name: "Update Server"
|
name: "Update Server"
|
||||||
|
|
||||||
@@ -66,7 +64,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-xml:
|
update-xml:
|
||||||
name: Update updates.xml
|
name: Update Server
|
||||||
runs-on: release
|
runs-on: release
|
||||||
if: >-
|
if: >-
|
||||||
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||||
@@ -75,50 +73,51 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
- name: Setup moko-platform tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
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: |
|
run: |
|
||||||
if ! command -v composer &> /dev/null; then
|
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
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
if [ -d "/tmp/moko-platform" ]; then
|
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||||
echo "moko-platform already available — skipping clone"
|
rm -rf /tmp/moko-platform
|
||||||
else
|
git clone --depth 1 --branch main --quiet \
|
||||||
git clone --depth 1 --branch main --quiet \
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
/tmp/moko-platform 2>/dev/null || true
|
||||||
/tmp/moko-platform 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then
|
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
|
cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
echo "MOKO_CLI=/tmp/moko-platform/cli" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Generate updates.xml entry
|
- name: Detect platform
|
||||||
id: update
|
id: platform
|
||||||
|
run: php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||||
|
|
||||||
|
- name: Resolve stability and bump version
|
||||||
|
id: meta
|
||||||
run: |
|
run: |
|
||||||
BRANCH="${{ github.ref_name }}"
|
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.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
git config --local user.name "gitea-actions[bot]"
|
git config --local user.name "gitea-actions[bot]"
|
||||||
BUMPED=$(php /tmp/moko-platform/cli/version_bump.php --path . 2>/dev/null || true)
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
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] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true
|
|
||||||
git push 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 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")
|
||||||
|
|
||||||
|
# Strip any existing suffix before applying stability
|
||||||
|
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||||
|
|
||||||
|
# Determine stability from branch or manual input
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
STABILITY="${{ inputs.stability }}"
|
STABILITY="${{ inputs.stability }}"
|
||||||
elif [[ "$BRANCH" == rc/* ]]; then
|
elif [[ "$BRANCH" == rc/* ]]; then
|
||||||
@@ -127,263 +126,96 @@ jobs:
|
|||||||
STABILITY="beta"
|
STABILITY="beta"
|
||||||
elif [[ "$BRANCH" == alpha/* ]]; then
|
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||||
STABILITY="alpha"
|
STABILITY="alpha"
|
||||||
elif [[ "$BRANCH" == dev/* ]] || [[ "$BRANCH" == "dev" ]]; then
|
else
|
||||||
STABILITY="development"
|
STABILITY="development"
|
||||||
else
|
|
||||||
STABILITY="stable"
|
|
||||||
fi
|
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 "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)
|
# Commit version bump if changed
|
||||||
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "./build/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
git add -A
|
||||||
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>\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1)
|
|
||||||
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
|
||||||
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1)
|
|
||||||
EXT_CLIENT=$(sed -n 's/.*<extension[^>]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
|
||||||
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
|
||||||
EXT_VERSION=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1)
|
|
||||||
TARGET_PLATFORM=$(sed -n 's/.*\(<targetplatform[^/]*\/>\).*/\1/p' "$MANIFEST" | head -1)
|
|
||||||
PHP_MINIMUM=$(sed -n 's/.*<php_minimum>\([^<]*\)<\/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 '<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" %s>' "/")
|
|
||||||
|
|
||||||
# Joomla requires <client> on ALL extension types for update matching
|
|
||||||
if [ -n "$EXT_CLIENT" ]; then
|
|
||||||
CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
|
||||||
else
|
|
||||||
CLIENT_TAG="<client>site</client>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
FOLDER_TAG=""
|
|
||||||
[ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
|
|
||||||
|
|
||||||
PHP_TAG=""
|
|
||||||
[ -n "$PHP_MINIMUM" ] && PHP_TAG="<php_minimum>${PHP_MINIMUM}</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} <update>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} <name>${EXT_NAME}</name>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} <description>${EXT_NAME} ${STABILITY} build.</description>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} <element>${EXT_ELEMENT}</element>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} <type>${EXT_TYPE}</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>${VERSION}</version>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} <creationDate>$(date +%Y-%m-%d)</creationDate>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} <infourl title='${EXT_NAME}'>https://git.mokoconsulting.tech/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${RELEASE_TAG}</infourl>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} <downloads>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} <downloadurl type='full' format='zip'>${DOWNLOAD_URL}</downloadurl>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} </downloads>\n"
|
|
||||||
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>${SHA256}</sha256>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} <tags><tag>${STABILITY}</tag></tags>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} <targetplatform name='joomla' version='(5|6).*'/>\n"
|
|
||||||
[ -n "$PHP_MINIMUM" ] && NEW_ENTRY="${NEW_ENTRY} <php_minimum>${PHP_MINIMUM}</php_minimum>\n"
|
|
||||||
NEW_ENTRY="${NEW_ENTRY} </update>"
|
|
||||||
|
|
||||||
# -- 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' "<?xml version='1.0' encoding='UTF-8'?>" > updates.xml
|
|
||||||
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting -->" >> updates.xml
|
|
||||||
printf '%s\n' "<updates>" >> updates.xml
|
|
||||||
printf '%s\n' "</updates>" >> 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"<tag>[^<]*</tag>", f"<tag>{tag}</tag>", new_entry_template)
|
|
||||||
|
|
||||||
# Try to find existing block (handles both single-line and multi-line <tags>)
|
|
||||||
block_pattern = r"(<update>(?:(?!</update>).)*?<tag>" + re.escape(tag) + r"</tag>.*?</update>)"
|
|
||||||
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>{tag}</tag> → {version}")
|
|
||||||
else:
|
|
||||||
# Create — insert before </updates>
|
|
||||||
content = content.replace("</updates>", "\n" + new_entry.strip() + "\n\n</updates>")
|
|
||||||
print(f" CREATED: <tag>{tag}</tag> → {version}")
|
|
||||||
|
|
||||||
# Clean up excessive blank lines
|
|
||||||
content = re.sub(r"\n{3,}", "\n\n", content)
|
|
||||||
|
|
||||||
with open("updates.xml", "w") as f:
|
|
||||||
f.write(content)
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
# Commit
|
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
|
||||||
git config --local user.name "gitea-actions[bot]"
|
|
||||||
git add updates.xml
|
|
||||||
git diff --cached --quiet || {
|
git 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] <gitea-actions[bot]@mokoconsulting.tech>"
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
git push
|
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
|
- name: Sync updates.xml to main
|
||||||
if: github.ref_name != 'main'
|
if: github.ref_name != 'main' && steps.platform.outputs.platform == 'joomla'
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
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)
|
"${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
|
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
|
||||||
@@ -394,27 +226,22 @@ jobs:
|
|||||||
payload = json.dumps({
|
payload = json.dumps({
|
||||||
'content': content,
|
'content': content,
|
||||||
'sha': '${FILE_SHA}',
|
'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'
|
'branch': 'main'
|
||||||
}).encode()
|
}).encode()
|
||||||
req = urllib.request.Request(
|
req = urllib.request.Request(
|
||||||
'${API_BASE}/contents/updates.xml',
|
'${API_BASE}/contents/updates.xml',
|
||||||
data=payload, method='PUT',
|
data=payload, method='PUT',
|
||||||
headers={
|
headers={
|
||||||
'Authorization': 'token ${GA_TOKEN}',
|
'Authorization': 'token ${GITEA_TOKEN}',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
})
|
})
|
||||||
try:
|
try:
|
||||||
urllib.request.urlopen(req)
|
urllib.request.urlopen(req)
|
||||||
print('updates.xml synced to main')
|
print('updates.xml synced to main')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'ERROR: failed to sync updates.xml to main: {e}', file=sys.stderr)
|
print(f'WARNING: sync to main failed: {e}', file=sys.stderr)
|
||||||
sys.exit(1)
|
"
|
||||||
" \
|
|
||||||
&& echo "updates.xml synced to main (${STABILITY})" >> $GITHUB_STEP_SUMMARY \
|
|
||||||
|| echo "::error::failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "::error::could not get updates.xml SHA from main — file may not exist on main yet" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: SFTP deploy to dev server
|
- name: SFTP deploy to dev server
|
||||||
@@ -428,12 +255,11 @@ jobs:
|
|||||||
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||||
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
# -- Permission check: admin or maintain role required --------
|
# Permission check: admin or maintain role required
|
||||||
ACTOR="${{ github.actor }}"
|
ACTOR="${{ github.actor }}"
|
||||||
REPO="${{ github.repository }}"
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
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 | \
|
"${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")
|
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
|
||||||
case "$PERMISSION" in
|
case "$PERMISSION" in
|
||||||
@@ -463,198 +289,24 @@ jobs:
|
|||||||
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PLATFORM=$(php /tmp/moko-platform/cli/platform_detect.php --path . 2>/dev/null || true)
|
PLATFORM=$(php ${MOKO_CLI}/platform_detect.php --path . 2>/dev/null || true)
|
||||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/moko-platform/deploy/deploy-joomla.php" ]; then
|
if [ "$PLATFORM" = "waas-component" ] && [ -f "${MOKO_CLI}/../deploy/deploy-joomla.php" ]; then
|
||||||
php /tmp/moko-platform/deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
php ${MOKO_CLI}/../deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||||
elif [ -f "/tmp/moko-platform/deploy/deploy-sftp.php" ]; then
|
elif [ -f "${MOKO_CLI}/../deploy/deploy-sftp.php" ]; then
|
||||||
php /tmp/moko-platform/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
php ${MOKO_CLI}/../deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||||
fi
|
fi
|
||||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||||
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
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>{tag or '?'}</tag>)"
|
|
||||||
|
|
||||||
# -- 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
|
|
||||||
|
|
||||||
# -- <downloads><downloadurl> --
|
|
||||||
dl = u.find("downloads/downloadurl")
|
|
||||||
if dl is None or not (dl.text or "").strip():
|
|
||||||
print(f"::error::{label}: missing <downloads><downloadurl>")
|
|
||||||
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
|
|
||||||
|
|
||||||
# -- <client> (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 <client> (required for Joomla update matching)")
|
|
||||||
errors += 1
|
|
||||||
|
|
||||||
# -- <targetplatform> --
|
|
||||||
tp = u.find("targetplatform")
|
|
||||||
if tp is None:
|
|
||||||
print(f"::error::{label}: missing <targetplatform>")
|
|
||||||
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
|
|
||||||
|
|
||||||
# -- <type> 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
|
|
||||||
|
|
||||||
# -- <version> 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
|
|
||||||
|
|
||||||
# -- <maintainer> and <maintainerurl> --
|
|
||||||
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 <tags><tag>")
|
|
||||||
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
|
- name: Summary
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
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 "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Version | \`${DISPLAY_VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Version | \`${DISPLAY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|||||||
+100
-252
@@ -1,293 +1,141 @@
|
|||||||
# Contribution Guidelines
|
# Contributing to Moko Consulting Projects
|
||||||
|
|
||||||
This document explains how to contribute changes to the Gitea project. Topic-specific guides live in separate files so the essentials are easier to find.
|
Thank you for your interest in contributing. All Moko Consulting repositories follow this universal workflow and version policy.
|
||||||
|
|
||||||
| Topic | Document |
|
## Branching Workflow
|
||||||
| :---- | :------- |
|
|
||||||
| Backend (Go modules, API v1) | [docs/guideline-backend.md](docs/guideline-backend.md) |
|
|
||||||
| Frontend (npm, UI guidelines) | [docs/guideline-frontend.md](docs/guideline-frontend.md) |
|
|
||||||
| Maintainers, TOC, labels, merge queue, commit format for mergers | [docs/community-governance.md](docs/community-governance.md) |
|
|
||||||
| Release cycle, backports, tagging releases | [docs/release-management.md](docs/release-management.md) |
|
|
||||||
|
|
||||||
<details><summary>Table of Contents</summary>
|
|
||||||
|
|
||||||
- [Contribution Guidelines](#contribution-guidelines)
|
|
||||||
- [Introduction](#introduction)
|
|
||||||
- [AI Contribution Policy](#ai-contribution-policy)
|
|
||||||
- [Issues](#issues)
|
|
||||||
- [How to report issues](#how-to-report-issues)
|
|
||||||
- [Types of issues](#types-of-issues)
|
|
||||||
- [Discuss your design before the implementation](#discuss-your-design-before-the-implementation)
|
|
||||||
- [Issue locking](#issue-locking)
|
|
||||||
- [Building Gitea](#building-gitea)
|
|
||||||
- [Styleguide](#styleguide)
|
|
||||||
- [Copyright](#copyright)
|
|
||||||
- [Testing](#testing)
|
|
||||||
- [Translation](#translation)
|
|
||||||
- [Code review](#code-review)
|
|
||||||
- [Pull request format](#pull-request-format)
|
|
||||||
- [PR title and summary](#pr-title-and-summary)
|
|
||||||
- [Breaking PRs](#breaking-prs)
|
|
||||||
- [What is a breaking PR?](#what-is-a-breaking-pr)
|
|
||||||
- [How to handle breaking PRs?](#how-to-handle-breaking-prs)
|
|
||||||
- [Maintaining open PRs](#maintaining-open-prs)
|
|
||||||
- [Reviewing PRs](#reviewing-prs)
|
|
||||||
- [For PR authors](#for-pr-authors)
|
|
||||||
- [Documentation](#documentation)
|
|
||||||
- [Developer Certificate of Origin (DCO)](#developer-certificate-of-origin-dco)
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
It assumes you have followed the [installation instructions](https://docs.gitea.com/category/installation). \
|
|
||||||
Sensitive security-related issues should be reported to [security@gitea.io](mailto:security@gitea.io).
|
|
||||||
|
|
||||||
For configuring IDEs for Gitea development, see the [contributed IDE configurations](contrib/ide/).
|
|
||||||
|
|
||||||
## AI Contribution Policy
|
|
||||||
|
|
||||||
Contributions made with the assistance of AI tools are welcome, but contributors must use them responsibly and disclose that use clearly.
|
|
||||||
|
|
||||||
1. Review AI-generated code closely before marking a pull request ready for review.
|
|
||||||
2. Manually test the changes and add appropriate automated tests where feasible.
|
|
||||||
3. Only use AI to assist in contributions that you understand well enough to explain, defend, and revise yourself during review.
|
|
||||||
4. Disclose AI-assisted content clearly.
|
|
||||||
5. Do not use AI to reply to questions about your issue or pull request. The questions are for you, not an AI model.
|
|
||||||
6. AI may be used to help draft issues and pull requests, but contributors remain responsible for the accuracy, completeness, and intent of what they submit.
|
|
||||||
|
|
||||||
Maintainers reserve the right to close pull requests and issues that do not disclose AI assistance, that appear to be low-quality AI-generated content, or where the contributor cannot explain or defend the proposed changes themselves.
|
|
||||||
|
|
||||||
We welcome new contributors, but cannot sustain the effort of supporting contributors who primarily defer to AI rather than engaging substantively with the review process.
|
|
||||||
|
|
||||||
## Issues
|
|
||||||
|
|
||||||
### How to report issues
|
|
||||||
|
|
||||||
Please search the issues on the issue tracker with a variety of related keywords to ensure that your issue has not already been reported.
|
|
||||||
|
|
||||||
If your issue has not been reported yet, [open an issue](https://github.com/go-gitea/gitea/issues/new)
|
|
||||||
and answer the questions so we can understand and reproduce the problematic behavior. \
|
|
||||||
Please write clear and concise instructions so that we can reproduce the behavior — even if it seems obvious. \
|
|
||||||
The more detailed and specific you are, the faster we can fix the issue. \
|
|
||||||
It is really helpful if you can reproduce your problem on a site running on the latest commits, i.e. <https://demo.gitea.com>, as perhaps your problem has already been fixed on a current version. \
|
|
||||||
Please follow the guidelines described in [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html) for your report.
|
|
||||||
|
|
||||||
Please be kind—remember that Gitea comes at no cost to you, and you're getting free help.
|
|
||||||
|
|
||||||
### Types of issues
|
|
||||||
|
|
||||||
Typically, issues fall in one of the following categories:
|
|
||||||
|
|
||||||
- `bug`: Something in the frontend or backend behaves unexpectedly
|
|
||||||
- `security issue`: bug that has serious implications such as leaking another users data. Please do not file such issues on the public tracker and send a mail to security@gitea.io instead
|
|
||||||
- `feature`: Completely new functionality. You should describe this feature in enough detail that anyone who reads the issue can understand how it is supposed to be implemented
|
|
||||||
- `enhancement`: An existing feature should get an upgrade
|
|
||||||
- `refactoring`: Parts of the code base don't conform with other parts and should be changed to improve Gitea's maintainability
|
|
||||||
|
|
||||||
### Discuss your design before the implementation
|
|
||||||
|
|
||||||
We welcome submissions. \
|
|
||||||
If you want to change or add something, please let everyone know what you're working on — [file an issue](https://github.com/go-gitea/gitea/issues/new) or comment on an existing one before starting your work!
|
|
||||||
|
|
||||||
Significant changes such as new features must go through the change proposal process before they can be accepted. \
|
|
||||||
This is mainly to save yourself the trouble of implementing it, only to find out that your proposed implementation has some potential problems. \
|
|
||||||
Furthermore, this process gives everyone a chance to validate the design, helps prevent duplication of effort, and ensures that the idea fits inside
|
|
||||||
the goals for the project and tools.
|
|
||||||
|
|
||||||
Pull requests should not be the place for architecture discussions.
|
|
||||||
|
|
||||||
### Issue locking
|
|
||||||
|
|
||||||
Commenting on closed or merged issues/PRs is strongly discouraged.
|
|
||||||
Such comments will likely be overlooked as some maintainers may not view notifications on closed issues, thinking that the item is resolved.
|
|
||||||
As such, commenting on closed/merged issues/PRs may be disabled prior to the scheduled auto-locking if a discussion starts or if unrelated comments are posted.
|
|
||||||
If further discussion is needed, we encourage you to open a new issue instead and we recommend linking to the issue/PR in question for context.
|
|
||||||
|
|
||||||
## Building Gitea
|
|
||||||
|
|
||||||
See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea).
|
|
||||||
|
|
||||||
## Styleguide
|
|
||||||
|
|
||||||
You should always run `make fmt` before committing to conform to Gitea's styleguide.
|
|
||||||
|
|
||||||
## Copyright
|
|
||||||
|
|
||||||
New code files that you contribute should use the standard copyright header:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
// Copyright <current year> The Gitea Authors. All rights reserved.
|
feature/* ──PR──> dev ──draft PR──> (renamed to rc) ──merge──> main
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Afterwards, copyright should only be modified when the copyright author changes.
|
### Step by step
|
||||||
|
|
||||||
## Testing
|
1. **Create a feature branch** from `dev`:
|
||||||
|
```bash
|
||||||
|
git checkout dev && git pull
|
||||||
|
git checkout -b feature/my-change
|
||||||
|
```
|
||||||
|
|
||||||
Before submitting a pull request, run all tests to make sure your changes don't cause a regression elsewhere.
|
2. **Work and commit** on your feature branch. Push to origin.
|
||||||
|
|
||||||
Here's how to run the test suite:
|
3. **Open a PR**: `feature/my-change` → `dev`. After review and checks, merge it.
|
||||||
|
|
||||||
- code lint
|
4. **When ready for release**, open a **draft PR**: `dev` → `main`.
|
||||||
|
- This automatically renames the source branch to `rc` (release candidate)
|
||||||
|
- An RC pre-release is built and uploaded
|
||||||
|
|
||||||
| | |
|
5. **Alpha and beta branches** are created by manually renaming the branch before the RC stage:
|
||||||
| :-------------------- | :--------------------------------------------------------------------------- |
|
- Rename `dev` to `alpha` for early testing → alpha pre-release is built
|
||||||
|``make lint`` | lint everything (not needed if you only change the front- **or** backend) |
|
- Rename `alpha` to `beta` for feature-complete testing → beta pre-release is built
|
||||||
|``make lint-frontend`` | lint frontend files |
|
- When the draft PR is created, the branch is renamed to `rc`
|
||||||
|``make lint-backend`` | lint backend files |
|
|
||||||
|
|
||||||
- run tests (we suggest running them on Linux)
|
6. **Once PR checks pass** on the `rc` branch, mark the PR as ready and merge to `main`.
|
||||||
|
|
||||||
| Command | Action | |
|
7. **Merging to main** triggers the stable release pipeline:
|
||||||
|:----------------------------------------------|:-----------------------------------------------------| ------------------------------------------- |
|
- Minor version bump (e.g., `02.09.xx` → `02.10.00`)
|
||||||
| ``make test-backend[\#SpecificTestName]`` | run unit test(s) | |
|
- Stability suffix stripped (clean version)
|
||||||
| ``make test-integration[\#SpecificTestName]`` | run [integration](tests/integration) test(s) | [More details](tests/integration/README.md) |
|
- Gitea release created with ZIP/tar.gz packages
|
||||||
| ``make test-e2e`` | run [end-to-end](tests/e2e) test(s) using Playwright | |
|
- `updates.xml` updated (Joomla extensions)
|
||||||
|
- `dev` branch recreated from `main`
|
||||||
|
|
||||||
- E2E test environment variables
|
### Branch summary
|
||||||
|
|
||||||
| Variable | Description |
|
| Branch | Purpose | Created by |
|
||||||
| :-------------------------------- | :---------------------------------------------------------- |
|
|--------|---------|-----------|
|
||||||
| ``GITEA_TEST_E2E_DEBUG`` | When set, show Gitea server output |
|
| `feature/*` | New features and fixes | Developer |
|
||||||
| ``GITEA_TEST_E2E_FLAGS`` | Additional flags passed to Playwright, for example ``--ui`` |
|
| `dev` | Integration branch | Auto-recreated after release |
|
||||||
| ``GITEA_TEST_E2E_TIMEOUT_FACTOR`` | Timeout multiplier (default: 4 on CI, 1 locally) |
|
| `alpha` | Alpha pre-release testing | Manual rename from `dev` |
|
||||||
|
| `beta` | Beta pre-release testing | Manual rename from `alpha` |
|
||||||
|
| `rc` | Release candidate | Auto-renamed on draft PR to main |
|
||||||
|
| `main` | Stable releases | Protected, merge only |
|
||||||
|
| `version/XX.YY.ZZ` | Archived release snapshots | Auto-created by CI |
|
||||||
|
|
||||||
## Translation
|
### Protected branches
|
||||||
|
|
||||||
All translation work happens on [Crowdin](https://translate.gitea.com).
|
| Branch | Direct push | Merge via |
|
||||||
The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.json).
|
|--------|------------|-----------|
|
||||||
It is synced regularly with Crowdin. \
|
| `main` | Blocked (CI bot whitelisted) | PR merge only |
|
||||||
Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \
|
| `dev` | Blocked (CI bot whitelisted) | PR merge from feature/* |
|
||||||
Once a language has reached a **satisfactory percentage** of translated keys (~25%), it will be synced back into this repo and included in the next released version.
|
| `rc` | Blocked (CI bot whitelisted) | Auto-created on draft PR |
|
||||||
|
| `alpha` | Blocked (CI bot whitelisted) | Manual rename |
|
||||||
|
| `beta` | Blocked (CI bot whitelisted) | Manual rename |
|
||||||
|
| `feature/*` | Open | N/A (source branch) |
|
||||||
|
|
||||||
The tool `go run build/backport-locale.go` can be used to backport locales from the main branch to release branches that were missed.
|
## Version Policy
|
||||||
|
|
||||||
## Code review
|
### Format
|
||||||
|
|
||||||
How labels, milestones, and the merge queue work is documented in [docs/community-governance.md](docs/community-governance.md).
|
All versions use `XX.YY.ZZ` — three two-digit segments, zero-padded:
|
||||||
|
|
||||||
### Pull request format
|
- **XX** — Major version (breaking changes)
|
||||||
|
- **YY** — Minor version (new features, bumped on release to main)
|
||||||
|
- **ZZ** — Patch version (auto-incremented on every push to dev/feature branches)
|
||||||
|
|
||||||
Please try to make your pull request easy to review for us. \
|
Rollover: patch `99` → `00` increments minor; minor `99` → `00` increments major.
|
||||||
For that, please read the [*Best Practices for Faster Reviews*](https://github.com/kubernetes/community/blob/261cb0fd089b64002c91e8eddceebf032462ccd6/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) guide. \
|
|
||||||
It has lots of useful tips for any project you may want to contribute to. \
|
|
||||||
Some of the key points:
|
|
||||||
|
|
||||||
- Make small pull requests. \
|
### Stability suffixes
|
||||||
The smaller, the faster to review and the more likely it will be merged soon.
|
|
||||||
- Don't make changes unrelated to your PR. \
|
|
||||||
Maybe there are typos on some comments, maybe refactoring would be welcome on a function... \
|
|
||||||
but if that is not related to your PR, please make *another* PR for that.
|
|
||||||
- Split big pull requests into multiple small ones. \
|
|
||||||
An incremental change will be faster to review than a huge PR.
|
|
||||||
- Allow edits by maintainers. This way, the maintainers will take care of merging the PR later on instead of you.
|
|
||||||
|
|
||||||
### PR title and summary
|
Each branch appends a suffix to indicate stability:
|
||||||
|
|
||||||
In the PR title, describe the problem you are fixing, not how you are fixing it. \
|
| Branch | Suffix | Example |
|
||||||
Use the first comment as a summary of your PR. \
|
|--------|--------|---------|
|
||||||
In the PR summary, you can describe exactly how you are fixing this problem.
|
| `main` | (none) | `02.09.00` |
|
||||||
|
| `dev` | `-dev` | `02.09.01-dev` |
|
||||||
|
| `feature/*` | `-dev` | `02.09.01-dev` |
|
||||||
|
| `alpha` | `-alpha` | `02.09.01-alpha` |
|
||||||
|
| `beta` | `-beta` | `02.09.01-beta` |
|
||||||
|
| `rc` | `-rc` | `02.09.01-rc` |
|
||||||
|
|
||||||
PR titles must follow the [Conventional Commits](https://www.conventionalcommits.org/) format, because PRs are squash-merged and the PR title becomes the resulting commit message:
|
### Auto version bump
|
||||||
|
|
||||||
```text
|
On every push to `dev`, `alpha`, `beta`, `rc`, or `feature/*`:
|
||||||
type(scope)!: subject
|
|
||||||
```
|
|
||||||
|
|
||||||
The allowed types are `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, and `test`. The generic `chore` type is intentionally not accepted; pick a more descriptive type instead.
|
1. Patch version incremented
|
||||||
|
2. Stability suffix applied based on branch name
|
||||||
|
3. All version-bearing files updated (manifests, CHANGELOG, PHP headers, etc.)
|
||||||
|
4. Commit created with `[skip ci]` to avoid loops
|
||||||
|
|
||||||
Examples:
|
### Version files
|
||||||
|
|
||||||
```text
|
The version tools update all files containing version stamps:
|
||||||
fix(web): prevent avatar upload crash on empty file
|
|
||||||
feat(api): add pagination to repo hooks list
|
|
||||||
ci(workflows): lint PR titles with commitlint
|
|
||||||
```
|
|
||||||
|
|
||||||
Keep this summary up-to-date as the PR evolves. \
|
- `.mokogitea/manifest.xml` (canonical source)
|
||||||
If your PR changes the UI, you must add **after** screenshots in the PR summary. \
|
- Joomla XML manifests (`<version>` tag)
|
||||||
If you are not implementing a new feature, you should also post **before** screenshots for comparison.
|
- `README.md`, `CHANGELOG.md` (`VERSION:` pattern)
|
||||||
|
- `package.json`, `pyproject.toml`
|
||||||
|
- Any text file with a `VERSION: XX.YY.ZZ` label
|
||||||
|
|
||||||
If you are implementing a new feature, your PR will only be merged if your screenshots are up to date.\
|
Files synced from other repos (with a `# REPO:` header) are not touched.
|
||||||
Furthermore, feature PRs will only be merged if their summary contains a clear usage description (understandable for users) and testing description (understandable for reviewers).
|
|
||||||
You should strive to combine both into a single description.
|
|
||||||
|
|
||||||
Another requirement for merging PRs is that the PR is labeled correctly.\
|
## Code Standards
|
||||||
However, this is not your job as a contributor, but the job of the person merging your PR.\
|
|
||||||
If you think that your PR was labeled incorrectly, or notice that it was merged without labels, please let us know.
|
|
||||||
|
|
||||||
If your PR closes some issues, you must note that in a way that both GitHub and Gitea understand, i.e. by appending a paragraph like
|
- **PHP**: PSR-12, tabs for indentation
|
||||||
|
- **Copyright**: all files must include the Moko Consulting copyright header
|
||||||
|
- **License**: SPDX identifier `GPL-3.0-or-later` (or as specified per repo)
|
||||||
|
- **Attribution**: use `Authored-by: Moko Consulting` in commits, not individual names
|
||||||
|
|
||||||
```text
|
## Commit Messages
|
||||||
Fixes/Closes/Resolves #<ISSUE_NR_X>.
|
|
||||||
Fixes/Closes/Resolves #<ISSUE_NR_Y>.
|
|
||||||
```
|
|
||||||
|
|
||||||
to your summary. \
|
Use conventional commit format:
|
||||||
Each issue that will be closed must stand on a separate line.
|
|
||||||
|
|
||||||
### Breaking PRs
|
|
||||||
|
|
||||||
#### What is a breaking PR?
|
|
||||||
|
|
||||||
A PR is breaking if it meets one of the following criteria:
|
|
||||||
|
|
||||||
- It changes API output in an incompatible way for existing users
|
|
||||||
- It removes a setting that an admin could previously set (i.e. via `app.ini`)
|
|
||||||
- An admin must do something manually to restore the old behavior
|
|
||||||
|
|
||||||
In particular, this means that adding new settings is not breaking.\
|
|
||||||
Changing the default value of a setting or replacing the setting with another one is breaking, however.
|
|
||||||
|
|
||||||
#### How to handle breaking PRs?
|
|
||||||
|
|
||||||
If your PR has a breaking change, you must add two things to the summary of your PR:
|
|
||||||
|
|
||||||
1. A reasoning why this breaking change is necessary
|
|
||||||
2. A `BREAKING` section explaining in simple terms (understandable for a typical user) how this PR affects users and how to mitigate these changes. This section can look for example like
|
|
||||||
|
|
||||||
```md
|
|
||||||
## :warning: BREAKING :warning:
|
|
||||||
```
|
|
||||||
|
|
||||||
Breaking PRs will not be merged as long as not both of these requirements are met.
|
|
||||||
|
|
||||||
### Maintaining open PRs
|
|
||||||
|
|
||||||
Code review starts when you open a non-draft PR or move a draft out of draft state. After that, do not rebase or squash your branch; it makes new changes harder to review.
|
|
||||||
|
|
||||||
Merge the base branch into yours only when you need to, for example because of conflicting changes elsewhere. That limits unnecessary CI runs.
|
|
||||||
|
|
||||||
Every PR is squash-merged, so merge commits on your branch do not matter for final history. The squash produces a single commit; mergers follow the [commit message format](docs/community-governance.md#commit-messages) in the governance guide.
|
|
||||||
|
|
||||||
### Reviewing PRs
|
|
||||||
|
|
||||||
Maintainers are encouraged to review pull requests in areas where they have expertise or particular interest.
|
|
||||||
|
|
||||||
#### For PR authors
|
|
||||||
|
|
||||||
- **Response**: When answering reviewer questions, use real-world cases or examples and avoid speculation.
|
|
||||||
- **Discussion**: A discussion is always welcome and should be used to clarify the changes and the intent of the PR.
|
|
||||||
- **Help**: If you need help with the PR or comments are unclear, ask for clarification.
|
|
||||||
|
|
||||||
Guidance for reviewers, the merge queue, and the squash commit message format is in [docs/community-governance.md](docs/community-governance.md).
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
If you add a new feature or change an existing aspect of Gitea, the documentation for that feature must be created or updated in another PR at [https://gitea.com/gitea/docs](https://gitea.com/gitea/docs).
|
|
||||||
**The docs directory on main repository will be removed at some time. We will have a yaml file to store configuration file's meta data. After that completed, configuration documentation should be in the main repository.**
|
|
||||||
|
|
||||||
## Developer Certificate of Origin (DCO)
|
|
||||||
|
|
||||||
We consider the act of contributing to the code by submitting a Pull Request as the "Sign off" or agreement to the certifications and terms of the [DCO](DCO) and [MIT license](LICENSE). \
|
|
||||||
No further action is required. \
|
|
||||||
You can also decide to sign off your commits by adding the following line at the end of your commit messages:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Signed-off-by: Joe Smith <joe.smith@email.com>
|
type(scope): short description
|
||||||
|
|
||||||
|
Optional body with context.
|
||||||
|
|
||||||
|
Authored-by: Moko Consulting
|
||||||
```
|
```
|
||||||
|
|
||||||
If you set the `user.name` and `user.email` Git config options, you can add the line to the end of your commits automatically with `git commit -s`.
|
Types: `feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `test`, `ci`
|
||||||
|
|
||||||
We assume in good faith that the information you provide is legally binding.
|
Special flags in commit messages:
|
||||||
|
- `[skip ci]` — skip all CI workflows
|
||||||
|
- `[skip bump]` — skip auto version bump only
|
||||||
|
|
||||||
|
## Reporting Issues
|
||||||
|
|
||||||
|
Use the repository's issue tracker with the appropriate template.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Moko Consulting <hello@mokoconsulting.tech>*
|
||||||
|
|||||||
Reference in New Issue
Block a user