diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml index b58ea7d24e..0a4caaacef 100644 --- a/.mokogitea/workflows/auto-bump.yml +++ b/.mokogitea/workflows/auto-bump.yml @@ -4,8 +4,8 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Release -# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic +# INGROUP: mokocli.Release +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # PATH: /.mokogitea/workflows/auto-bump.yml # VERSION: 09.02.00 # BRIEF: Auto patch-bump version on every push to dev (skips merge commits) @@ -34,8 +34,7 @@ jobs: if: >- !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip bump]') && - !startsWith(github.event.head_commit.message, 'Merge pull request') && - !startsWith(github.event.repository.name, 'Template-') + !startsWith(github.event.head_commit.message, 'Merge pull request') steps: - name: Checkout @@ -44,7 +43,7 @@ jobs: token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 1 - - name: Setup MokoCLI tools + - name: Setup mokocli tools run: | if ! command -v composer &> /dev/null; then sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index ee87c5636f..11c043ee39 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -4,17 +4,17 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Release -# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic -# PATH: /.mokogitea/workflows/auto-release.yml -# VERSION: 05.01.00 -# BRIEF: Universal build & release � detects platform from metadata API +# INGROUP: mokocli.Release +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli +# PATH: /templates/workflows/universal/auto-release.yml.template +# VERSION: 05.00.00 +# BRIEF: Universal build & release � detects platform from manifest.xml # # +=======================================================================+ # | UNIVERSAL BUILD & RELEASE PIPELINE | # +=======================================================================+ # | | -# | Reads metadata API (joomla|dolibarr|generic) to branch logic. | +# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. | # | | # | Platform-specific: | # | joomla: XML manifest, type-prefixed packages | @@ -64,14 +64,10 @@ jobs: promote-rc: name: Promote to RC runs-on: release - # Skip on template repos (Template-*) — they scaffold other repos and do not release. if: >- - !startsWith(github.event.repository.name, 'Template-') && - ( - (github.event.action == 'opened' && github.event.pull_request.merged != true) || - (github.event.action == 'synchronize' && github.event.pull_request.merged != true) || - (github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc') - ) + (github.event.action == 'opened' && github.event.pull_request.merged != true) || + (github.event.action == 'synchronize' && github.event.pull_request.merged != true) || + (github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc') steps: - name: Checkout repository @@ -79,9 +75,8 @@ jobs: with: token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 1 - submodules: recursive - - name: Setup MokoCLI tools + - name: Setup mokocli tools env: MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting @@ -193,13 +188,9 @@ jobs: release: name: Build & Release Pipeline runs-on: release - # Skip on template repos (Template-*) — they scaffold other repos and do not release. if: >- - !startsWith(github.event.repository.name, 'Template-') && - ( - github.event.pull_request.merged == true || - (github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc') - ) + github.event.pull_request.merged == true || + (github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc') steps: - name: Checkout repository @@ -207,7 +198,6 @@ jobs: with: token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 0 - submodules: recursive - name: Configure git for bot pushes run: | @@ -228,7 +218,7 @@ jobs: fi echo "No conflict markers found" - - name: Setup MokoCLI tools + - name: Setup mokocli tools env: MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting @@ -374,47 +364,13 @@ jobs: if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then DATE=$(date +%Y-%m-%d) python3 -c " - import sys, re + import sys version, date = sys.argv[1], sys.argv[2] - lines = open('CHANGELOG.md').read().split('\n') - h2 = re.compile(r'^##\s+\[([^\]]+)\]') - header, sections, cur = [], [], None - for ln in lines: - m = h2.match(ln) - if m: - if cur: sections.append(cur) - cur = {'label': m.group(1).strip(), 'head': ln, 'body': []} - elif cur is None: - header.append(ln) - else: - cur['body'].append(ln) - if cur: sections.append(cur) - def nonempty(b): return any(x.strip() for x in b) - def trim(b): - b = b[:] - while b and not b[0].strip(): b.pop(0) - while b and not b[-1].strip(): b.pop() - return b - unreleased, order, bykey = [], [], {} - for s in sections: - key = s['label'].lower() - if key == 'unreleased': - if nonempty(s['body']): unreleased += s['body'] - continue - if not nonempty(s['body']): continue # drop blank release sections - if key in bykey: bykey[key]['body'] += [''] + s['body'] # merge duplicate heading (never drop content) - else: bykey[key] = s; order.append(key) - rest, seen = [bykey[k] for k in order], set(order) - out = [] - htxt = '\n'.join(header).rstrip() - if htxt: out += [htxt, ''] - out += ['## [Unreleased]', ''] - promote = bool(unreleased) and version.lower() not in seen - if unreleased and not promote: out += trim(unreleased) + [''] - if promote: out += ['## [%s] --- %s' % (version, date), ''] + trim(unreleased) + [''] - for s in rest: out += [s['head'], ''] + trim(s['body']) + [''] - res = re.sub(r'\n{3,}', '\n\n', '\n'.join(out)).rstrip('\n') + '\n' - open('CHANGELOG.md', 'w').write(res) + content = open('CHANGELOG.md').read() + old = '## [Unreleased]' + new = f'## [Unreleased]\n\n## [{version}] --- {date}' + content = content.replace(old, new, 1) + open('CHANGELOG.md', 'w').write(content) " "$VERSION" "$DATE" git add CHANGELOG.md git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true @@ -457,7 +413,7 @@ jobs: && echo "main branch pushed to GitHub mirror" \ || echo "WARNING: GitHub mirror push failed" - - name: "Step 11: Delete rc branch (dev reset moved to cascade-dev.yml)" + - name: "Step 11: Delete rc branch and recreate dev from main" if: steps.version.outputs.skip != 'true' continue-on-error: true run: | @@ -469,9 +425,17 @@ jobs: "${API_BASE}/branches/rc" 2>/dev/null \ && echo "Deleted rc branch" || echo "rc branch not found" - # dev is reset from main by the dedicated "Cascade Main -> Dev" workflow - # (cascade-dev.yml), which runs after this release completes. - echo "rc cleaned; dev reset handled by cascade-dev.yml" >> $GITHUB_STEP_SUMMARY + # Delete dev branch + curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \ + "${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch" + + # Recreate dev from main (now includes version bump + changelog promotion) + curl -sf -X POST -H "Authorization: token ${TOKEN}" \ + -H "Content-Type: application/json" \ + "${API_BASE}/branches" \ + -d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main" + + echo "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' diff --git a/.mokogitea/workflows/branch-cleanup.yml b/.mokogitea/workflows/branch-cleanup.yml index 909e706839..857af3cfa2 100644 --- a/.mokogitea/workflows/branch-cleanup.yml +++ b/.mokogitea/workflows/branch-cleanup.yml @@ -4,8 +4,8 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Universal -# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic +# INGROUP: MokoStandards.Universal +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # PATH: /.mokogitea/workflows/branch-cleanup.yml # VERSION: 01.00.00 # BRIEF: Delete feature branches after PR merge @@ -33,8 +33,7 @@ jobs: run: | BRANCH="${{ github.event.pull_request.head.ref }}" API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches" - # URL-encode the branch name's slashes (no PHP dependency on the runner) - ENCODED=$(printf '%s' "${BRANCH}" | sed 's|/|%2F|g') + ENCODED=$(php -r "echo rawurlencode('${BRANCH}');") STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \ -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \ diff --git a/.mokogitea/workflows/cascade-dev.yml b/.mokogitea/workflows/cascade-dev.yml index 8ae17c44ff..5f7c1d7273 100644 --- a/.mokogitea/workflows/cascade-dev.yml +++ b/.mokogitea/workflows/cascade-dev.yml @@ -1,190 +1,10 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Cascade -# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic -# PATH: /.mokogitea/workflows/cascade-dev.yml -# VERSION: 02.01.00 -# BRIEF: Cascade main -> dev; auto-merge clean, auto-resolve VERSION-stamp-only conflicts, else notify - -name: "Cascade Main -> Dev" - -on: - push: - branches: - - main - # Daily safety net: catches drift even when main only received [skip ci] pushes - # (which never fire the push trigger above). Off-round minute to avoid a fleet-wide spike. - schedule: - - cron: '23 7 * * *' - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -env: - MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - # ntfy destination is configured via repo or org variables (org vars are inherited). - NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }} - NTFY_TOPIC: ${{ vars.CASCADE_NTFY_TOPIC || vars.NTFY_TOPIC || 'gitea-releases' }} - +# DISABLED — auto-release Step 11 recreates dev from main after every release. +# Cascade-dev is redundant and causes version conflicts when both main and dev +# have different version numbers in templateDetails.xml / manifest.xml. +name: "Cascade Main → Dev (DISABLED)" +on: workflow_dispatch jobs: - cascade: - name: Cascade main -> dev + noop: runs-on: ubuntu-latest steps: - - name: Checkout (full history for merge/resolve) - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.MOKOGITEA_TOKEN }} - - - name: Cascade main -> dev (auto-resolve version stamps, else notify) - env: - TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - REPO: ${{ github.repository }} - run: | - set -uo pipefail - API="${MOKOGITEA_URL}/api/v1/repos/${REPO}" - AUTH="Authorization: token ${TOKEN}" - jqget() { python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; } - - # 0. dev must exist - if ! curl -sf -H "$AUTH" "${API}/branches/dev" >/dev/null 2>&1; then - echo "No dev branch - nothing to cascade."; exit 0 - fi - - # 1. is main ahead of dev? - AHEAD=$(curl -sf -H "$AUTH" "${API}/compare/dev...main" \ - | python3 -c "import sys,json; print(json.load(sys.stdin).get('total_commits',0))" 2>/dev/null || echo 0) - if [ "${AHEAD:-0}" -eq 0 ]; then - echo "dev already up to date with main."; exit 0 - fi - echo "main is ${AHEAD} commit(s) ahead of dev." - - # 2. reuse an open main->dev PR, else create one - PR=$(curl -sf -H "$AUTH" "${API}/pulls?state=open&base=dev" \ - | python3 -c "import sys,json; d=json.load(sys.stdin); print(next((str(p['number']) for p in d if p.get('head',{}).get('ref')=='main'), ''))" 2>/dev/null || echo "") - if [ -z "$PR" ]; then - RESP=$(curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/pulls" \ - -d '{"head":"main","base":"dev","title":"chore(sync): cascade main -> dev","body":"Automated cascade of main into dev. Auto-merges when conflict-free, auto-resolves VERSION-stamp-only conflicts, otherwise left open for manual resolution."}') - PR=$(printf '%s' "$RESP" | jqget number) - if [ -z "$PR" ]; then - echo "::warning::Could not open cascade PR: $RESP"; exit 0 - fi - echo "Opened cascade PR #${PR}" - else - echo "Reusing open cascade PR #${PR}" - fi - - notify() { - curl -sS \ - -H "Title: ${REPO}: dev cascade needs manual merge" \ - -H "Tags: warning,twisted_rightwards_arrows" \ - -H "Priority: high" \ - -H "Click: ${MOKOGITEA_URL}/${REPO}/pulls/${PR}" \ - -d "main -> dev cascade PR #${PR} $1 It was NOT auto-merged; resolve it manually." \ - "${NTFY_URL}/${NTFY_TOPIC}" || true - } - - # 3. wait for MokoGitea to compute mergeability (conflict detection) - MERGEABLE="" - for _ in 1 2 3 4 5 6; do - MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqget mergeable) - case "$MERGEABLE" in True|False) break ;; esac - sleep 3 - done - echo "mergeable=${MERGEABLE}" - - # 4a. conflict-free -> merge via API (existing behaviour) - if [ "$MERGEABLE" = "True" ]; then - CODE=$(curl -s -o /tmp/merge.json -w "%{http_code}" -H "$AUTH" -H "Content-Type: application/json" \ - -X POST "${API}/pulls/${PR}/merge" -d '{"Do":"merge","merge_when_checks_succeed":true}') - if [ "$CODE" -ge 200 ] && [ "$CODE" -lt 300 ]; then - echo "Cascade PR #${PR} merged (or scheduled to merge when checks pass)." - exit 0 - fi - echo "::warning::Auto-merge returned HTTP ${CODE}: $(cat /tmp/merge.json)" - notify "could not be auto-merged (HTTP ${CODE})." - exit 0 - fi - - # 4b. conflicts -> try to auto-resolve if they are ONLY VERSION-stamp lines. - echo "PR not cleanly mergeable; checking whether conflicts are VERSION-stamp-only..." - git config user.name "MokoGitea Cascade" - git config user.email "actions@mokoconsulting.tech" - git fetch --quiet origin main dev - git checkout -B dev origin/dev - - if git merge --no-ff --no-commit origin/main >/dev/null 2>&1; then - # Became clean at git level (e.g. mergeability was still computing) -> commit + push. - git commit -m "chore(sync): cascade main -> dev [skip ci]" >/dev/null - git push origin dev - echo "Cascade merged cleanly at git level and pushed to dev." - exit 0 - fi - - CONFLICTS=$(git diff --name-only --diff-filter=U) - echo "Conflicted files:"; echo "${CONFLICTS}" - - # A conflict is "stamp-only" when every line inside every conflict block matches - # a version-stamp pattern (VERSION: header, element, or CHANGELOG title). - is_stamp_only() { - awk ' - /^<<<<<<< / { inc=1; next } - inc && /^=======$/ { next } - /^>>>>>>> / { inc=0; next } - inc { if ($0 !~ /(VERSION:||# Changelog)/) { bad=1 } } - END { exit(bad ? 1 : 0) } - ' "$1" - } - # Resolve a stamp-only file by keeping dev (ours) for the conflicting lines, - # preserving all auto-merged content around them. - keep_ours() { - awk ' - /^<<<<<<< / { inc=1; side="ours"; next } - inc && /^=======$/ { side="theirs"; next } - /^>>>>>>> / { inc=0; next } - { if (!inc) { print; next } if (side=="ours") print } - ' "$1" > "$1.resolved" && mv "$1.resolved" "$1" - } - - ALL_STAMP=1 - for f in ${CONFLICTS}; do - if ! is_stamp_only "$f"; then - echo "::notice::$f has non-stamp conflicts -> manual resolution required." - ALL_STAMP=0; break - fi - done - - if [ "$ALL_STAMP" != "1" ]; then - git merge --abort || true - notify "has non-version-stamp conflicts and cannot be auto-resolved." - exit 0 - fi - - echo "All conflicts are VERSION-stamp-only; resolving in favour of dev." - for f in ${CONFLICTS}; do - keep_ours "$f" - git add "$f" - done - - # Best-effort: normalise stamps to dev's version if mokocli is available. - if [ -f /opt/mokocli/cli/version_check.php ]; then - php /opt/mokocli/cli/version_check.php --fix || true - git add -A - fi - - git commit -m "chore(sync): cascade main -> dev (auto-resolved version stamps) [skip ci]" >/dev/null - git push origin dev - echo "Cascade auto-resolved and pushed to dev." - - # Close the now-redundant PR (its changes are in dev) with an explanatory comment. - curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/issues/${PR}/comments" \ - -d '{"body":"Auto-resolved VERSION-stamp-only conflicts and pushed the merge to dev. Closing."}' >/dev/null || true - curl -s -H "$AUTH" -H "Content-Type: application/json" -X PATCH "${API}/pulls/${PR}" \ - -d '{"state":"closed"}' >/dev/null || true + - run: echo "Cascade disabled — auto-release handles dev recreation" diff --git a/.mokogitea/workflows/ci-generic.yml b/.mokogitea/workflows/ci-generic.yml index 869087e4af..1fb0113f63 100644 --- a/.mokogitea/workflows/ci-generic.yml +++ b/.mokogitea/workflows/ci-generic.yml @@ -4,9 +4,9 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.CI +# INGROUP: MokoStandards.CI # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic -# PATH: /.mokogitea/workflows/ci-generic.yml +# PATH: /.gitea/workflows/ci-generic.yml # VERSION: 01.00.00 # BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js) @@ -32,8 +32,6 @@ jobs: lint: name: Lint & Validate runs-on: ubuntu-latest - # Skip on template repos (Template-*) — they hold placeholder scaffolding, not buildable source. - if: ${{ !startsWith(github.event.repository.name, 'Template-') }} steps: - name: Checkout @@ -131,11 +129,7 @@ jobs: test: name: Tests runs-on: ubuntu-latest - # Independent job (no `needs: lint`): the MokoGitea Actions scheduler does not - # offer the dependent 2nd job of a needs-chain to runners, so it stalls in - # "waiting" and is reaped by ABANDONED_JOB_TIMEOUT. Guard template repos - # directly (same condition lint uses) instead of gating on lint's result. - if: ${{ !startsWith(github.event.repository.name, 'Template-') }} + needs: lint steps: - name: Checkout diff --git a/.mokogitea/workflows/ci-issue-reporter.yml b/.mokogitea/workflows/ci-issue-reporter.yml index 600d8c1df8..2911508c8e 100644 --- a/.mokogitea/workflows/ci-issue-reporter.yml +++ b/.mokogitea/workflows/ci-issue-reporter.yml @@ -4,8 +4,8 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Universal -# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic +# INGROUP: mokocli.Universal +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # PATH: /.mokogitea/workflows/ci-issue-reporter.yml # VERSION: 01.00.00 # BRIEF: Reusable workflow — creates/updates a MokoGitea issue when a CI gate fails. @@ -52,7 +52,7 @@ jobs: MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} run: | MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}" - git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli + git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh - name: Report CI failure diff --git a/.mokogitea/workflows/cleanup.yml b/.mokogitea/workflows/cleanup.yml index d08e29e554..942e98285c 100644 --- a/.mokogitea/workflows/cleanup.yml +++ b/.mokogitea/workflows/cleanup.yml @@ -4,9 +4,9 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Maintenance -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /.mokogitea/workflows/cleanup.yml +# INGROUP: MokoStandards.Maintenance +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards +# PATH: /.gitea/workflows/cleanup.yml # VERSION: 01.00.00 # BRIEF: Scheduled cleanup — delete merged branches and old workflow runs @@ -50,7 +50,7 @@ jobs: for BRANCH in $BRANCHES; do # Skip protected branches case "$BRANCH" in - main|master|dev|develop|rc|beta|alpha|release|release/*|production|stable|staging|hotfix/*|version/*) continue ;; + main|master|develop|release/*|hotfix/*) continue ;; esac # Check if branch is merged into main diff --git a/.mokogitea/workflows/deploy-dev.yml b/.mokogitea/workflows/deploy-dev.yml index 6e4361c608..f99e1b1069 100644 --- a/.mokogitea/workflows/deploy-dev.yml +++ b/.mokogitea/workflows/deploy-dev.yml @@ -1,25 +1,40 @@ # Copyright (C) 2026 Moko Consulting # SPDX-License-Identifier: GPL-3.0-or-later -# BRIEF: Build and deploy MokoGitea to dev environment on push to dev branch. -# Production deploy (deploy-mokogitea.yml) only succeeds if dev is healthy. +# BRIEF: Build and deploy to the Dev environment on push to the dev branch. +# Portable across Go server repos: ALL deployment config comes from repo +# Actions variables (vars.*) and secrets (secrets.*), nothing hardcoded. +# The dev branch is the ongoing integration branch. +# OWNER: Template-Go (canonical source; syncs to the root workflows dir). See Template-Go#3. +# +# Required repo VARIABLES (all tier-scoped so each environment is independent — +# a repo may host its rc/dev/prod tiers on different machines): +# DEV_SSH_HOST, DEV_SSH_PORT, DEV_SSH_USERNAME - SSH deploy target for the dev tier +# DEV_REGISTRY, DEV_REGISTRY_USER, DEV_IMAGE - container registry + login user + image +# DEV_CONTAINER - compose service/container to recreate +# DEV_COMPOSE_PROJECT - docker compose -p project name +# DEV_COMPOSE_DIR - dir containing docker-compose.yml on host +# DEV_SOURCE_DIR - build source checkout on host +# DEV_TAG_ENV - compose env-var name that pins the image tag +# DEV_HEALTH_URL - external URL to verify after deploy +# Required SECRETS (already configured org-wide; reused, not re-set): +# DEPLOY_SSH_KEY - deploy private key (repo/org secret) +# MOKOGITEA_TOKEN - registry/API token (org secret) -name: Deploy MokoGitea (Dev) +name: Deploy (Dev) on: push: branches: - dev + # Manual trigger for isolated end-to-end tests. + # Runs on the ref it is dispatched from. + workflow_dispatch: concurrency: - group: deploy-mokogitea-dev + group: deploy-dev cancel-in-progress: true env: - REGISTRY: git.mokoconsulting.tech - IMAGE: mokoconsulting/mokogitea - DEPLOY_HOST: git.mokoconsulting.tech - DEPLOY_PORT: 2918 - DEPLOY_USER: mokoconsulting FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: @@ -47,22 +62,21 @@ jobs: echo "$DEPLOY_KEY" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key - - name: Build and deploy to dev via SSH + - name: Build and deploy to RC via SSH env: REGISTRY_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} TAG: ${{ steps.config.outputs.tag }} run: | - # Inject runner-side values (TAG, REGISTRY_TOKEN) into the remote shell's - # environment via a command prefix, then use a *quoted* heredoc so every - # $var below expands in exactly one place: the remote dev host. This avoids - # the local-vs-remote expansion trap that previously left TAG empty. - ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} \ + # Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell + # via an env prefix; a *quoted* heredoc keeps every $var expanding once, on the + # remote. Repo variables (vars.*) are substituted inline by Actions before ssh. + ssh -i ~/.ssh/deploy_key -p ${{ vars.DEV_SSH_PORT }} \ -o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ -o ServerAliveInterval=30 -o ServerAliveCountMax=10 \ - ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} \ + ${{ vars.DEV_SSH_USERNAME }}@${{ vars.DEV_SSH_HOST }} \ "TAG='$TAG' REGISTRY_TOKEN='$REGISTRY_TOKEN' bash -s" <<'DEPLOY_EOF' set -e - echo 'SSH connected to dev environment' + echo 'SSH connected to Dev environment' if [ -z "$TAG" ]; then echo 'ERROR: TAG is empty; refusing to build an untagged image' >&2 @@ -76,52 +90,46 @@ jobs: docker image prune -af 2>/dev/null || true echo 'Pulling source...' - SOURCE_DIR=/opt/gitea-dev/source + SOURCE_DIR='${{ vars.DEV_SOURCE_DIR }}' if [ ! -d "$SOURCE_DIR/.git" ]; then - git clone -b dev https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git "$SOURCE_DIR" + git clone -b dev ${{ github.server_url }}/${{ github.repository }}.git "$SOURCE_DIR" fi cd "$SOURCE_DIR" - git remote set-url origin https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git 2>/dev/null || true + git remote set-url origin ${{ github.server_url }}/${{ github.repository }}.git 2>/dev/null || true git fetch origin dev git reset --hard origin/dev - echo "Building Docker image: ${{ env.REGISTRY }}/${{ env.IMAGE }}:$TAG" + echo "Building image: ${{ vars.DEV_REGISTRY }}/${{ vars.DEV_IMAGE }}:$TAG" docker build --no-cache --build-arg GOFLAGS='-p 1' \ - --tag "${{ env.REGISTRY }}/${{ env.IMAGE }}:$TAG" \ + --tag "${{ vars.DEV_REGISTRY }}/${{ vars.DEV_IMAGE }}:$TAG" \ -f Dockerfile . echo 'Pushing to registry...' - echo "$REGISTRY_TOKEN" | docker login ${{ env.REGISTRY }} -u ${{ env.DEPLOY_USER }} --password-stdin - docker push "${{ env.REGISTRY }}/${{ env.IMAGE }}:$TAG" + echo "$REGISTRY_TOKEN" | docker login ${{ vars.DEV_REGISTRY }} -u ${{ vars.DEV_REGISTRY_USER }} --password-stdin + docker push "${{ vars.DEV_REGISTRY }}/${{ vars.DEV_IMAGE }}:$TAG" - echo 'Restarting dev container...' - cd /opt/gitea-dev - # The dev service in the SHARED compose file reads its image tag from - # ${MOKOGITEA_DEV_TAG}. Drive it from the freshly built tag instead of - # rewriting the file with sed: the old sed pattern matched the *prod* - # service line (container_name: mokogitea) and left the dev service pinned - # to a stale image, so every dev deploy recreated old code while silently - # corrupting the prod image pin. The env-var only affects the dev service. - # Remove any lingering fixed-name container first so the recreate can't hit - # a name conflict, pin the project name for determinism, then force-recreate. - docker rm -f mokogitea-dev 2>/dev/null || true - MOKOGITEA_DEV_TAG="$TAG" docker compose -p gitea-dev up -d --force-recreate mokogitea-dev + echo 'Restarting Dev container...' + cd '${{ vars.DEV_COMPOSE_DIR }}' + # Drive the rc service image tag via its compose env-var (no sed on the shared + # file); remove any lingering fixed-name container first, then force-recreate. + docker rm -f '${{ vars.DEV_CONTAINER }}' 2>/dev/null || true + ${{ vars.DEV_TAG_ENV }}="$TAG" docker compose -p '${{ vars.DEV_COMPOSE_PROJECT }}' up -d --force-recreate '${{ vars.DEV_CONTAINER }}' echo 'Health check...' for i in 1 2 3 4 5 6 7 8; do sleep 15 - if docker inspect --format="$HEALTH_FMT" mokogitea-dev 2>/dev/null | grep -q healthy; then + if docker inspect --format="$HEALTH_FMT" '${{ vars.DEV_CONTAINER }}' 2>/dev/null | grep -q healthy; then echo 'Dev container healthy!' exit 0 fi echo "Waiting... (attempt $i/8)" done echo 'Health check failed' - docker logs mokogitea-dev --tail 20 + docker logs '${{ vars.DEV_CONTAINER }}' --tail 20 exit 1 DEPLOY_EOF - - name: Verify dev instance + - name: Verify Dev instance run: | sleep 5 - curl -sf https://git.dev.mokoconsulting.tech/api/healthz && echo " Dev API healthy" + curl -sf "${{ vars.DEV_HEALTH_URL }}" && echo " Dev API healthy" diff --git a/.mokogitea/workflows/deploy-mokogitea.yml b/.mokogitea/workflows/deploy-mokogitea.yml deleted file mode 100644 index 0af8f78ca3..0000000000 --- a/.mokogitea/workflows/deploy-mokogitea.yml +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# BRIEF: Build MokoGitea Docker image, push to registry, and deploy - -name: Deploy MokoGitea - -on: - push: - branches: - - main - workflow_dispatch: - inputs: - version: - description: 'Version tag' - required: true - default: 'latest' - environment: - description: 'Target environment' - required: true - default: 'dev' - type: choice - options: - - dev - - production - -concurrency: - group: deploy-mokogitea - cancel-in-progress: false - -env: - REGISTRY: git.mokoconsulting.tech - IMAGE: mokoconsulting/mokogitea - DEPLOY_HOST: git.mokoconsulting.tech - DEPLOY_PORT: 2918 - DEPLOY_USER: mokoconsulting - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - # NOTE: removed a "Verify dev environment is healthy" gate that curled - # git.dev and `exit 1` on failure — gating a PROD deploy on a live DEV - # health check is a cross-tier false-negative (a transient dev blip blocked - # prod). Pre-prod validation is the dev -> rc -> main pipeline + RC env. - - name: Checkout source (for version detection) - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Determine settings - id: config - run: | - if [ "${{ github.event_name }}" = "push" ]; then - VERSION=$(git describe --tags --always 2>/dev/null || echo "dev-$(git rev-parse --short HEAD)") - ENV="production" - else - VERSION="${{ github.event.inputs.version }}" - ENV="${{ github.event.inputs.environment }}" - fi - if [ "$ENV" = "production" ]; then - echo "compose_dir=/opt/gitea-dev" >> $GITHUB_OUTPUT - echo "container=mokogitea" >> $GITHUB_OUTPUT - echo "source_dir=/opt/gitea/source" >> $GITHUB_OUTPUT - echo "branch=main" >> $GITHUB_OUTPUT - echo "tag=$VERSION" >> $GITHUB_OUTPUT - echo "tag_env=MOKOGITEA_TAG" >> $GITHUB_OUTPUT - else - echo "compose_dir=/opt/gitea-dev" >> $GITHUB_OUTPUT - echo "container=mokogitea-dev" >> $GITHUB_OUTPUT - echo "source_dir=/opt/gitea-dev/source" >> $GITHUB_OUTPUT - echo "branch=dev" >> $GITHUB_OUTPUT - echo "tag=$VERSION-dev" >> $GITHUB_OUTPUT - echo "tag_env=MOKOGITEA_DEV_TAG" >> $GITHUB_OUTPUT - fi - - - name: Write deploy key - env: - DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }} - run: | - mkdir -p ~/.ssh - echo "$DEPLOY_KEY" > ~/.ssh/deploy_key - chmod 600 ~/.ssh/deploy_key - - - name: Build and deploy via SSH - env: - REGISTRY_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - TAG: ${{ steps.config.outputs.tag }} - BRANCH: ${{ steps.config.outputs.branch }} - SOURCE_DIR: ${{ steps.config.outputs.source_dir }} - COMPOSE_DIR: ${{ steps.config.outputs.compose_dir }} - CONTAINER: ${{ steps.config.outputs.container }} - TAG_ENV: ${{ steps.config.outputs.tag_env }} - run: | - HEALTH_FMT='${{ '{{' }}.State.Health.Status${{ '}}' }}' - IMAGE_FMT='Image: ${{ '{{' }}.Config.Image${{ '}}' }}' - - ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} \ - -o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ - -o ServerAliveInterval=30 -o ServerAliveCountMax=10 \ - ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} bash -s </dev/null || true - docker image prune -af 2>/dev/null || true - free -m | head -3 - - echo 'Pulling source...' - if [ ! -d $SOURCE_DIR/.git ]; then - git clone -b $BRANCH https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git $SOURCE_DIR - fi - cd $SOURCE_DIR - # Ensure remote points to MokoGitea-Fork (not the upstream fork) - git remote set-url origin https://git.mokoconsulting.tech/MokoConsulting/MokoGitea-Fork.git 2>/dev/null || true - git fetch origin $BRANCH - git reset --hard origin/$BRANCH - - echo 'Building Docker image...' - docker build --no-cache --build-arg GOFLAGS='-p 1' \ - --tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:$TAG \ - --tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest \ - -f Dockerfile . - - echo 'Pushing to registry...' - echo '$REGISTRY_TOKEN' | docker login ${{ env.REGISTRY }} -u ${{ env.DEPLOY_USER }} --password-stdin - docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:$TAG - docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest - - echo 'Restarting container...' - cd $COMPOSE_DIR - # Drive ONLY the target service's tag via its compose env-var (no sed — - # a blanket 'sed s|mokogitea:...|' clobbered the dev + rc service tags too). - # Remove any lingering fixed-name container first (avoids the "name already - # in use" conflict), then force-recreate under the shared compose project. - docker rm -f $CONTAINER 2>/dev/null || true - env $TAG_ENV="$TAG" docker compose -p gitea-dev up -d --force-recreate $CONTAINER - - echo 'Health check...' - for i in 1 2 3 4 5 6 7 8; do - sleep 15 - if docker inspect --format='$HEALTH_FMT' $CONTAINER 2>/dev/null | grep -q healthy; then - echo 'Container healthy!' - docker inspect --format='$IMAGE_FMT' $CONTAINER - exit 0 - fi - echo "Waiting... (attempt \$i/8)" - done - echo 'Health check failed' - docker logs $CONTAINER --tail 20 - exit 1 - DEPLOY_EOF - - - name: Verify - run: | - sleep 5 - curl -sf https://${{ env.DEPLOY_HOST }}/api/healthz && echo " API healthy" - - - name: Notify on failure - if: failure() - run: echo "::error::Deploy failed for ${{ steps.config.outputs.tag }}" diff --git a/.mokogitea/workflows/deploy-prod.yml b/.mokogitea/workflows/deploy-prod.yml new file mode 100644 index 0000000000..60aea36faa --- /dev/null +++ b/.mokogitea/workflows/deploy-prod.yml @@ -0,0 +1,135 @@ +# Copyright (C) 2026 Moko Consulting +# SPDX-License-Identifier: GPL-3.0-or-later +# BRIEF: Build and deploy to the Prod environment on push to the main branch. +# Portable across Go server repos: ALL deployment config comes from repo +# Actions variables (vars.*) and secrets (secrets.*), nothing hardcoded. +# Prod deploys on merge to main (dev -> rc -> main pipeline). +# OWNER: Template-Go (canonical source; syncs to the root workflows dir). See Template-Go#3. +# +# Required repo VARIABLES (all tier-scoped so each environment is independent — +# a repo may host its rc/dev/prod tiers on different machines): +# PROD_SSH_HOST, PROD_SSH_PORT, PROD_SSH_USERNAME - SSH deploy target for the prod tier +# PROD_REGISTRY, PROD_REGISTRY_USER, PROD_IMAGE - container registry + login user + image +# PROD_CONTAINER - compose service/container to recreate +# PROD_COMPOSE_PROJECT - docker compose -p project name +# PROD_COMPOSE_DIR - dir containing docker-compose.yml on host +# PROD_SOURCE_DIR - build source checkout on host +# PROD_TAG_ENV - compose env-var name that pins the image tag +# PROD_HEALTH_URL - external URL to verify after deploy +# Required SECRETS (already configured org-wide; reused, not re-set): +# DEPLOY_SSH_KEY - deploy private key (repo/org secret) +# MOKOGITEA_TOKEN - registry/API token (org secret) + +name: Deploy (Prod) + +on: + push: + branches: + - main + # Manual trigger for a prod re-deploy. + # Runs on the ref it is dispatched from (use main). + workflow_dispatch: + +concurrency: + group: deploy-prod + cancel-in-progress: false + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + deploy-prod: + name: "Build & Deploy to Prod" + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine version + id: config + run: | + VERSION=$(git describe --tags --always 2>/dev/null || echo "$(git rev-parse --short HEAD)") + echo "tag=${VERSION}" >> $GITHUB_OUTPUT + echo "Version: ${VERSION}" + + - name: Write deploy key + env: + DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + run: | + mkdir -p ~/.ssh + echo "$DEPLOY_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + + - name: Build and deploy to RC via SSH + env: + REGISTRY_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + TAG: ${{ steps.config.outputs.tag }} + run: | + # Runner-side values (TAG, REGISTRY_TOKEN) are injected into the remote shell + # via an env prefix; a *quoted* heredoc keeps every $var expanding once, on the + # remote. Repo variables (vars.*) are substituted inline by Actions before ssh. + ssh -i ~/.ssh/deploy_key -p ${{ vars.PROD_SSH_PORT }} \ + -o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + -o ServerAliveInterval=30 -o ServerAliveCountMax=10 \ + ${{ vars.PROD_SSH_USERNAME }}@${{ vars.PROD_SSH_HOST }} \ + "TAG='$TAG' REGISTRY_TOKEN='$REGISTRY_TOKEN' bash -s" <<'DEPLOY_EOF' + set -e + echo 'SSH connected to Prod environment' + + if [ -z "$TAG" ]; then + echo 'ERROR: TAG is empty; refusing to build an untagged image' >&2 + exit 1 + fi + + HEALTH_FMT='{{.State.Health.Status}}' + + echo 'Cleaning Docker build cache...' + docker builder prune -af 2>/dev/null || true + docker image prune -af 2>/dev/null || true + + echo 'Pulling source...' + SOURCE_DIR='${{ vars.PROD_SOURCE_DIR }}' + if [ ! -d "$SOURCE_DIR/.git" ]; then + git clone -b main ${{ github.server_url }}/${{ github.repository }}.git "$SOURCE_DIR" + fi + cd "$SOURCE_DIR" + git remote set-url origin ${{ github.server_url }}/${{ github.repository }}.git 2>/dev/null || true + git fetch origin main + git reset --hard origin/main + + echo "Building image: ${{ vars.PROD_REGISTRY }}/${{ vars.PROD_IMAGE }}:$TAG" + docker build --no-cache --build-arg GOFLAGS='-p 1' \ + --tag "${{ vars.PROD_REGISTRY }}/${{ vars.PROD_IMAGE }}:$TAG" \ + -f Dockerfile . + + echo 'Pushing to registry...' + echo "$REGISTRY_TOKEN" | docker login ${{ vars.PROD_REGISTRY }} -u ${{ vars.PROD_REGISTRY_USER }} --password-stdin + docker push "${{ vars.PROD_REGISTRY }}/${{ vars.PROD_IMAGE }}:$TAG" + + echo 'Restarting Prod container...' + cd '${{ vars.PROD_COMPOSE_DIR }}' + # Drive the rc service image tag via its compose env-var (no sed on the shared + # file); remove any lingering fixed-name container first, then force-recreate. + docker rm -f '${{ vars.PROD_CONTAINER }}' 2>/dev/null || true + ${{ vars.PROD_TAG_ENV }}="$TAG" docker compose -p '${{ vars.PROD_COMPOSE_PROJECT }}' up -d --force-recreate '${{ vars.PROD_CONTAINER }}' + + echo 'Health check...' + for i in 1 2 3 4 5 6 7 8; do + sleep 15 + if docker inspect --format="$HEALTH_FMT" '${{ vars.PROD_CONTAINER }}' 2>/dev/null | grep -q healthy; then + echo 'Prod container healthy!' + exit 0 + fi + echo "Waiting... (attempt $i/8)" + done + echo 'Health check failed' + docker logs '${{ vars.PROD_CONTAINER }}' --tail 20 + exit 1 + DEPLOY_EOF + + - name: Verify Prod instance + run: | + sleep 5 + curl -sf "${{ vars.PROD_HEALTH_URL }}" && echo " Prod API healthy" diff --git a/.mokogitea/workflows/gitleaks.yml b/.mokogitea/workflows/gitleaks.yml index 579f0cb374..4baefb84d8 100644 --- a/.mokogitea/workflows/gitleaks.yml +++ b/.mokogitea/workflows/gitleaks.yml @@ -4,9 +4,9 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Security -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /.mokogitea/workflows/gitleaks.yml +# INGROUP: MokoStandards.Security +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API +# PATH: /templates/workflows/gitleaks.yml.template # VERSION: 01.00.00 # BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens # diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 12a4b11ff6..eb67f8f7f8 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -4,7 +4,7 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Automation +# INGROUP: mokocli.Automation # VERSION: 01.00.00 # BRIEF: Auto-create feature branch when an issue is opened diff --git a/.mokogitea/workflows/notify.yml b/.mokogitea/workflows/notify.yml index 7a03898512..0c450f3774 100644 --- a/.mokogitea/workflows/notify.yml +++ b/.mokogitea/workflows/notify.yml @@ -4,9 +4,9 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Notifications -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /.mokogitea/workflows/notify.yml +# INGROUP: MokoStandards.Notifications +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards +# PATH: /.gitea/workflows/notify.yml # VERSION: 01.00.00 # BRIEF: Push notifications via ntfy on release success or workflow failure @@ -15,9 +15,9 @@ name: "Universal: Notifications" on: workflow_run: workflows: - - "Universal: Build & Release" - - "Joomla: Extension CI" - - "Generic: Project CI" + - "Joomla Build & Release" + - "Joomla Extension CI" + - "Deploy" types: - completed @@ -46,13 +46,13 @@ jobs: WORKFLOW="${{ github.event.workflow_run.name }}" URL="${{ github.event.workflow_run.html_url }}" - curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \ + curl -sS \ -H "Title: ${REPO} released" \ -H "Tags: white_check_mark,package" \ -H "Priority: default" \ -H "Click: ${URL}" \ -d "${WORKFLOW} completed successfully." \ - "${NTFY_URL}/${NTFY_TOPIC}" || echo "::warning::ntfy notification could not be delivered (non-fatal)" + "${NTFY_URL}/${NTFY_TOPIC}" - name: Notify on failure if: github.event.workflow_run.conclusion == 'failure' @@ -61,10 +61,10 @@ jobs: WORKFLOW="${{ github.event.workflow_run.name }}" URL="${{ github.event.workflow_run.html_url }}" - curl -sS --retry 3 --retry-connrefused --retry-delay 2 --max-time 20 \ + curl -sS \ -H "Title: ${REPO} workflow failed" \ -H "Tags: x,warning" \ -H "Priority: high" \ -H "Click: ${URL}" \ -d "${WORKFLOW} failed. Check the run for details." \ - "${NTFY_URL}/${NTFY_TOPIC}" || echo "::warning::ntfy notification could not be delivered (non-fatal)" + "${NTFY_URL}/${NTFY_TOPIC}" diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml index 6da762a8c0..9520565183 100644 --- a/.mokogitea/workflows/pr-check.yml +++ b/.mokogitea/workflows/pr-check.yml @@ -4,9 +4,9 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.CI -# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic -# PATH: /.mokogitea/workflows/pr-check.yml +# INGROUP: mokocli.CI +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli +# PATH: /templates/workflows/universal/pr-check.yml.template # VERSION: 09.23.00 # BRIEF: PR gate — branch policy + code validation before merge @@ -47,15 +47,15 @@ jobs: fi ;; fix/*|bugfix/*) - if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then + if [ "$BASE" != "dev" ]; then ALLOWED=false - REASON="Fix branches must target 'dev' or 'main', not '${BASE}'" + REASON="Fix branches must target 'dev', not '${BASE}'" fi ;; patch/*) - if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ] && [ "$BASE" != "main" ]; then + if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then ALLOWED=false - REASON="Patch branches must target 'dev', 'rc', or 'main', not '${BASE}'" + REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'" fi ;; hotfix/*) @@ -86,8 +86,7 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`fix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`patch/*\` → \`dev\`, \`rc\`, or \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY @@ -97,80 +96,6 @@ jobs: echo "Branch policy: OK (${HEAD} → ${BASE})" echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY - # ── Docs Update Gate (main PRs) ───────────────────────────────────────── - require-docs: - name: Require Docs Update - runs-on: ubuntu-latest - # Enforce only on PRs merging into main: README.md + CHANGELOG.md must both be updated. - if: ${{ github.base_ref == 'main' }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Require README.md and CHANGELOG.md in the PR diff - run: | - BASE="${{ github.event.pull_request.base.sha }}" - HEAD="${{ github.event.pull_request.head.sha }}" - CHANGED="$(git diff --name-only "$BASE" "$HEAD" 2>/dev/null || true)" - if [ -z "$CHANGED" ]; then - git fetch -q origin "${{ github.base_ref }}" 2>/dev/null || true - CHANGED="$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" 2>/dev/null || true)" - fi - echo "Changed files in PR:" - echo "$CHANGED" - MISSING="" - echo "$CHANGED" | grep -qxE 'README\.md' || MISSING="README.md" - echo "$CHANGED" | grep -qxE 'CHANGELOG\.md' || MISSING="${MISSING:+$MISSING, }CHANGELOG.md" - if [ -n "$MISSING" ]; then - echo "::error::PRs into main must update: ${MISSING}" - { - echo "## Docs Update Required" - echo "" - echo "PRs merging into \`main\` must update both **README.md** and **CHANGELOG.md**." - echo "" - echo "Not updated in this PR: **${MISSING}**" - } >> "$GITHUB_STEP_SUMMARY" - exit 1 - fi - echo "Docs update present (README.md + CHANGELOG.md)" - echo "## Docs Update: Passed" >> "$GITHUB_STEP_SUMMARY" - - # ── Wiki Update Reminder (main PRs, non-blocking) ─────────────────────── - wiki-reminder: - name: Wiki Update Reminder - runs-on: ubuntu-latest - if: ${{ github.base_ref == 'main' }} - steps: - - name: Remind to update the wiki - env: - TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - SERVER: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - REPO: ${{ github.repository }} - PR: ${{ github.event.pull_request.number }} - run: | - set -uo pipefail - { - echo "## Wiki Update Reminder" - echo "" - echo "Docs are **wiki-first** at MokoConsulting. If this change affects behavior, usage, configuration, or standards, update the repo wiki:" - echo "" - echo "- ${SERVER}/${REPO}/wiki" - echo "" - echo "_Non-blocking reminder._" - } >> "$GITHUB_STEP_SUMMARY" - # Post a single PR comment (idempotent via hidden marker); best-effort, never fails. - API="${SERVER}/api/v1/repos/${REPO}/issues/${PR}/comments" - if [ -n "${TOKEN:-}" ] && [ -n "${PR:-}" ]; then - existing="$(curl -sf -H "Authorization: token ${TOKEN}" "$API" 2>/dev/null | grep -c 'wiki-reminder' || true)" - if [ "${existing:-0}" -eq 0 ]; then - curl -sf -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" -X POST "$API" \ - -d '{"body":"\n\n**Wiki reminder:** docs are wiki-first -- if this PR changes behavior, usage, config, or standards, please update the repo wiki before/after merge. _(non-blocking)_"}' >/dev/null 2>&1 || true - fi - fi - echo "Wiki reminder emitted (non-blocking)." - # ── Secret Scanning ────────────────────────────────────────────────── gitleaks: name: Secret Scan @@ -201,8 +126,6 @@ jobs: validate: name: Validate PR runs-on: ubuntu-latest - # Skip on template repos (Template-*) — no real manifest/source/changelog to validate. - if: ${{ !startsWith(github.event.repository.name, 'Template-') }} steps: - name: Checkout @@ -210,7 +133,7 @@ jobs: - name: Check for merge conflict markers run: | - CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --exclude-dir='.git' --exclude-dir='.mokogitea' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true) + CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true) if [ -n "$CONFLICTS" ]; then echo "::error::Merge conflict markers found in source files" echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY @@ -258,9 +181,8 @@ jobs: while IFS= read -r -d '' file; do # Skip vendor, node_modules, and index.html stub files case "$file" in ./vendor/*|./node_modules/*) continue ;; esac - # Scan the whole file for the JEXEC/JPATH guard: it is placed after - # the SPDX/file-header docblock, which commonly runs past 20 lines. - if ! grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]" "$file"; then + # Check first 10 lines for JEXEC or JPATH guard + if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then echo "::error file=${file}::Missing JEXEC guard: ${file}" ERRORS=$((ERRORS + 1)) fi @@ -351,7 +273,7 @@ jobs: joomla) MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1) if [ -z "$MANIFEST" ]; then - echo "::warning::No Joomla manifest found (MokoSuite site)" + echo "::warning::No Joomla manifest found (WaaS site)" exit 0 fi echo "Manifest: ${MANIFEST}" @@ -571,9 +493,6 @@ jobs: name: Build RC Package runs-on: ubuntu-latest needs: [branch-policy, validate] - # Run only when both gates succeeded; always() forces evaluation so a skipped - # validate (e.g. template repos) skips this job cleanly instead of hanging. - if: ${{ always() && needs.branch-policy.result == 'success' && needs.validate.result == 'success' }} steps: - name: Trigger RC pre-release diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index 4ea0367727..f93f814ec6 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -4,10 +4,10 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Release -# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic -# PATH: /.mokogitea/workflows/pre-release.yml -# VERSION: 05.02.01 +# INGROUP: mokocli.Release +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +# PATH: /templates/workflows/universal/pre-release.yml.template +# VERSION: 05.02.00 # BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches name: "Universal: Pre-Release" @@ -48,13 +48,9 @@ jobs: build: name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})" runs-on: release - # Skip on template repos (Template-*) — they scaffold other repos and do not release. if: >- - !startsWith(github.event.repository.name, 'Template-') && - ( - github.event_name == 'workflow_dispatch' || - github.event_name == 'push' - ) + github.event_name == 'workflow_dispatch' || + github.event_name == 'push' steps: - name: Checkout @@ -69,7 +65,7 @@ jobs: run: | git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true - - name: Setup MokoCLI tools + - name: Setup mokocli tools env: MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting @@ -162,13 +158,7 @@ jobs: git add -A git diff --cached --quiet || { git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]" - # Push the bump commit, but do NOT fail the release if the target branch - # is protected and the release identity is not on the push allowlist. - # The build proceeds from the in-tree bumped version regardless; if the - # push is rejected, the next run simply re-bumps from the same base. - if ! git push origin HEAD 2>&1; then - echo "::warning::Version-bump commit could not be pushed (protected branch?). Building from in-tree version ${VERSION} anyway." - fi + git push origin HEAD 2>&1 } # Auto-detect element via manifest_element.php @@ -280,4 +270,4 @@ jobs: echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY - echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY \ No newline at end of file + echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY diff --git a/.mokogitea/workflows/rc-revert.yml b/.mokogitea/workflows/rc-revert.yml index f58d1fa02b..d20f288aa7 100644 --- a/.mokogitea/workflows/rc-revert.yml +++ b/.mokogitea/workflows/rc-revert.yml @@ -4,8 +4,8 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Universal -# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic +# INGROUP: mokocli.Universal +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # PATH: /.mokogitea/workflows/rc-revert.yml # VERSION: 09.23.00 # BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge @@ -25,8 +25,7 @@ jobs: runs-on: ubuntu-latest if: >- github.event.pull_request.merged == false && - startsWith(github.event.pull_request.head.ref, 'rc/') && - !startsWith(github.event.repository.name, 'Template-') + startsWith(github.event.pull_request.head.ref, 'rc/') steps: - name: Rename branch diff --git a/.mokogitea/workflows/repo-health.yml b/.mokogitea/workflows/repo-health.yml index 3011fb777d..17bff6c31c 100644 --- a/.mokogitea/workflows/repo-health.yml +++ b/.mokogitea/workflows/repo-health.yml @@ -7,9 +7,9 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Validation -# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic -# PATH: /.mokogitea/workflows/repo-health.yml +# INGROUP: mokocli.Validation +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli +# PATH: /templates/workflows/joomla/repo_health.yml.template # VERSION: 09.23.00 # BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts. # ============================================================================ diff --git a/.mokogitea/workflows/version-set.yml b/.mokogitea/workflows/version-set.yml index d7339bdce8..36b7a04977 100644 --- a/.mokogitea/workflows/version-set.yml +++ b/.mokogitea/workflows/version-set.yml @@ -4,7 +4,7 @@ # # FILE INFORMATION # DEFGROUP: MokoGitea.Workflow.Template -# INGROUP: MokoCLI.CI +# INGROUP: MokoStandards.CI # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla # PATH: /.mokogitea/workflows/version-set.yml # VERSION: 01.00.00 @@ -34,7 +34,6 @@ jobs: set-version: name: Set Version to ${{ inputs.version }} runs-on: ubuntu-latest - if: ${{ !startsWith(github.event.repository.name, 'Template-') }} steps: - name: Validate version format