diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..69eb051 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "templates/repos/Template-Client"] + path = templates/repos/Template-Client + url = https://git.mokoconsulting.tech/MokoConsulting/Template-Client.git +[submodule "templates/repos/Template-Generic"] + path = templates/repos/Template-Generic + url = https://git.mokoconsulting.tech/MokoConsulting/Template-Generic.git +[submodule "templates/repos/Template-Joomla"] + path = templates/repos/Template-Joomla + url = https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla.git +[submodule "templates/repos/Template-MCP"] + path = templates/repos/Template-MCP + url = https://git.mokoconsulting.tech/MokoConsulting/Template-MCP.git diff --git a/.mokogitea/CLAUDE.md b/.mokogitea/CLAUDE.md index c06a709..53c5d39 100644 --- a/.mokogitea/CLAUDE.md +++ b/.mokogitea/CLAUDE.md @@ -1,4 +1,8 @@ +<<<<<<< HEAD # MokoCLI +======= +# mokoplatform +>>>>>>> main Enterprise automation, validation, sync, and governance engine for all Moko Consulting repositories. @@ -9,7 +13,11 @@ Enterprise automation, validation, sync, and governance engine for all Moko Cons | **Language** | PHP 8.1+ | | **Version** | 09.01.00 | | **Branch** | develop on `dev`, merge to `main` (protected) | +<<<<<<< HEAD | **Wiki** | [MokoCLI Wiki](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki) | +======= +| **Wiki** | [mokoplatform Wiki](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki) | +>>>>>>> main ## Commands @@ -44,7 +52,7 @@ composer check # Run all checks ### CLI Framework -All CLI tools extend `MokoEnterprise\CliFramework` (`lib/Enterprise/CliFramework.php`). +All CLI tools extend `MokoCli\CliFramework` (`lib/Enterprise/CliFramework.php`). Built-in flags: `--help`, `--verbose`, `--quiet`, `--dry-run`. After adding a CLI tool, register it in `bin/moko` COMMAND_MAP. @@ -73,4 +81,8 @@ PHPStan runs with `--memory-limit=512M`. CI enforces PHPCS errors; PHPStan is `c - **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`) - **Wiki**: documentation lives in the Gitea wiki, not `docs/` files - **New CLI tools**: extend `CliFramework`, not `CLIApp` (legacy) +<<<<<<< HEAD - **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home) +======= +- **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki/Home) +>>>>>>> main diff --git a/.mokogitea/ISSUE_TEMPLATE/config.yml b/.mokogitea/ISSUE_TEMPLATE/config.yml index bbaebdc..8679476 100644 --- a/.mokogitea/ISSUE_TEMPLATE/config.yml +++ b/.mokogitea/ISSUE_TEMPLATE/config.yml @@ -7,8 +7,13 @@ contact_links: - name: 💬 Ask a Question url: https://mokoconsulting.tech/ about: Get help or ask questions through our website +<<<<<<< HEAD - name: 📚 MokoCLI Documentation url: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + - name: 📚 mokoplatform Documentation + url: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main about: View our coding standards and best practices - name: 🔒 Report a Security Vulnerability url: https://git.mokoconsulting.tech/mokoconsulting-tech/.github-private/security/advisories/new diff --git a/.mokogitea/ISSUE_TEMPLATE/documentation.md b/.mokogitea/ISSUE_TEMPLATE/documentation.md index 8156651..972fb6c 100644 --- a/.mokogitea/ISSUE_TEMPLATE/documentation.md +++ b/.mokogitea/ISSUE_TEMPLATE/documentation.md @@ -42,7 +42,11 @@ Suggested text here ## Standards Alignment +<<<<<<< HEAD - [ ] Follows MokoCLI documentation guidelines +======= +- [ ] Follows mokoplatform documentation guidelines +>>>>>>> main - [ ] Uses en_US/en_GB localization - [ ] Includes proper SPDX headers where applicable diff --git a/.mokogitea/ISSUE_TEMPLATE/feature_request.md b/.mokogitea/ISSUE_TEMPLATE/feature_request.md index 650523c..98a8882 100644 --- a/.mokogitea/ISSUE_TEMPLATE/feature_request.md +++ b/.mokogitea/ISSUE_TEMPLATE/feature_request.md @@ -37,7 +37,11 @@ If you have ideas about how this could be implemented, share them here: Add any other context, mockups, or screenshots about the feature request here. ## Relevant Standards +<<<<<<< HEAD Does this relate to any standards in [MokoCLI](https://git.mokoconsulting.tech/MokoConsulting/mokocli)? +======= +Does this relate to any standards in [mokoplatform](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform)? +>>>>>>> main - [ ] Accessibility (WCAG 2.1 AA) - [ ] Localization (en_US/en_GB) - [ ] Security best practices diff --git a/.mokogitea/ISSUE_TEMPLATE/security.md b/.mokogitea/ISSUE_TEMPLATE/security.md index 37a266d..ebf3f89 100644 --- a/.mokogitea/ISSUE_TEMPLATE/security.md +++ b/.mokogitea/ISSUE_TEMPLATE/security.md @@ -35,7 +35,11 @@ Use this template only for: ## Standards Reference +<<<<<<< HEAD Does this relate to security standards in [MokoCLI](https://git.mokoconsulting.tech/MokoConsulting/mokocli)? +======= +Does this relate to security standards in [mokoplatform](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform)? +>>>>>>> main - [ ] SPDX license identifiers - [ ] Secret management - [ ] Dependency security diff --git a/.mokogitea/branch-protection.yml b/.mokogitea/branch-protection.yml index 1789097..16e5c8c 100644 --- a/.mokogitea/branch-protection.yml +++ b/.mokogitea/branch-protection.yml @@ -2,8 +2,13 @@ # SPDX-License-Identifier: GPL-3.0-or-later # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.Automation # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +# INGROUP: mokoplatform.Automation +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main # PATH: /.gitea/workflows/branch-protection.yml # BRIEF: Apply standardised branch protection rules to all governed repositories # @@ -57,13 +62,18 @@ jobs: - name: Determine target repos id: repos env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} + GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} run: | API="${GITEA_URL}/api/v1" # Platform/standards/infra repos to exclude +<<<<<<< HEAD EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private mokocli mokoplatform MokoTesting" EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate" +======= + EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private mokoplatform MokoTesting" + EXCLUDE="$EXCLUDE MokoCli-Template-Client MokoCli-Template-Dolibarr MokoCli-Template-Generic MokoCli-Template-Joomla MokoDoliProjTemplate" +>>>>>>> main if [ -n "${{ inputs.repos }}" ]; then # User-specified repos @@ -105,7 +115,7 @@ jobs: - name: Apply protection rules env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} + GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} DRY_RUN: ${{ inputs.dry_run || 'false' }} run: | API="${GITEA_URL}/api/v1" diff --git a/.mokogitea/bulk-repo-sync.yml b/.mokogitea/bulk-repo-sync.yml index a7c831b..81a0f43 100644 --- a/.mokogitea/bulk-repo-sync.yml +++ b/.mokogitea/bulk-repo-sync.yml @@ -2,8 +2,13 @@ # SPDX-License-Identifier: GPL-3.0-or-later # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.Automation # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +# INGROUP: mokoplatform.Automation +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main # PATH: /.gitea/workflows/bulk-repo-sync.yml # BRIEF: Bulk repo sync — runs from API repo, syncs standards to all governed repos @@ -84,8 +89,8 @@ jobs: echo "Running: php automation/bulk_sync.php ${{ steps.args.outputs.args }}" php automation/bulk_sync.php ${{ steps.args.outputs.args }} 2>&1 | tee /tmp/bulk_sync.log env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} + GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + GH_TOKEN: ${{ secrets.GH_PAT }} GIT_PLATFORM: gitea GITEA_URL: https://git.mokoconsulting.tech GITEA_ORG: MokoConsulting @@ -112,7 +117,7 @@ jobs: bash automation/enforce_tags.sh || echo "Tag enforcement had errors (non-fatal)" fi env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} + GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} GITEA_URL: https://git.mokoconsulting.tech GITEA_ORG: MokoConsulting diff --git a/.mokogitea/pr-branch-check.yml b/.mokogitea/pr-branch-check.yml index 0cb099c..e37149d 100644 --- a/.mokogitea/pr-branch-check.yml +++ b/.mokogitea/pr-branch-check.yml @@ -2,9 +2,15 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION +<<<<<<< HEAD # DEFGROUP: MokoCLI.CI # INGROUP: MokoCLI # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +# DEFGROUP: mokoplatform.CI +# INGROUP: mokoplatform +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main # PATH: /.gitea/workflows/pr-branch-check.yml # BRIEF: PR branch merge policy enforcement # diff --git a/.mokogitea/renovate.yml b/.mokogitea/renovate.yml index ce6f4e7..9c90952 100644 --- a/.mokogitea/renovate.yml +++ b/.mokogitea/renovate.yml @@ -4,8 +4,13 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.Automation # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +# INGROUP: mokoplatform.Automation +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main # PATH: /.gitea/workflows/renovate.yml # BRIEF: Run Renovate Bot across all governed repos for dependency updates # @@ -57,12 +62,17 @@ jobs: - name: Determine target repos id: repos env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} + GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} run: | API="${GITEA_URL}/api/v1" +<<<<<<< HEAD EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private mokocli mokoplatform MokoTesting" EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate" +======= + EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private mokoplatform MokoTesting" + EXCLUDE="$EXCLUDE MokoCli-Template-Client MokoCli-Template-Dolibarr MokoCli-Template-Generic MokoCli-Template-Joomla MokoDoliProjTemplate" +>>>>>>> main if [ -n "${{ inputs.repos }}" ]; then REPOS=$(echo "${{ inputs.repos }}" | tr ',' ' ') @@ -107,7 +117,7 @@ jobs: - name: Run Renovate if: steps.repos.outputs.repo_list != '' env: - RENOVATE_TOKEN: ${{ secrets.GA_TOKEN }} + RENOVATE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} RENOVATE_PLATFORM: gitea RENOVATE_ENDPOINT: ${{ env.GITEA_URL }}/api/v1 RENOVATE_GIT_AUTHOR: 'Renovate Bot ' diff --git a/.mokogitea/sync-wikis.yml b/.mokogitea/sync-wikis.yml index 28ab105..293f761 100644 --- a/.mokogitea/sync-wikis.yml +++ b/.mokogitea/sync-wikis.yml @@ -4,8 +4,13 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.Maintenance # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +# INGROUP: mokoplatform.Maintenance +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main # PATH: /.gitea/workflows/sync-wikis.yml # BRIEF: Daily sync of all Gitea wikis to consolidated GitHub wiki repo @@ -31,7 +36,7 @@ jobs: - name: Sync all wikis env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} + GH_TOKEN: ${{ secrets.GH_PAT }} GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} run: | if [ -z "$GH_TOKEN" ]; then diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml index 1c0ce49..8c78804 100644 --- a/.mokogitea/workflows/auto-bump.yml +++ b/.mokogitea/workflows/auto-bump.yml @@ -4,10 +4,17 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.Release # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # PATH: /.mokogitea/workflows/auto-bump.yml # VERSION: 09.23.00 +======= +# INGROUP: mokocli.Release +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +# PATH: /.mokogitea/workflows/auto-bump.yml +# VERSION: 09.02.00 +>>>>>>> main # BRIEF: Auto patch-bump version on every push to dev (skips merge commits) name: "Universal: Auto Version Bump" @@ -43,6 +50,7 @@ jobs: token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 1 +<<<<<<< HEAD - name: Setup MokoCLI tools run: | # Check both new (mokocli) and legacy (mokoplatform) install paths @@ -57,6 +65,16 @@ jobs: 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 rm -rf /tmp/mokocli +======= + - 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 + fi + if [ -d "/opt/mokocli/cli" ]; then + echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV" + else +>>>>>>> main git clone --depth 1 --branch main --quiet \ "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \ /tmp/mokocli diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index e86c538..514e2db 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -4,8 +4,13 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.Release # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +# INGROUP: mokocli.Release +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli +>>>>>>> main # PATH: /templates/workflows/universal/auto-release.yml.template # VERSION: 05.00.00 # BRIEF: Universal build & release � detects platform from manifest.xml @@ -66,11 +71,16 @@ jobs: token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 1 +<<<<<<< HEAD - name: Setup MokoCLI tools +======= + - name: Setup mokocli tools +>>>>>>> main env: MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting run: | +<<<<<<< HEAD # Check both new (mokocli) and legacy (mokoplatform) install paths if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then echo "Using pre-installed /opt/mokocli" @@ -80,6 +90,13 @@ jobs: echo MOKO_CLI=/opt/mokoplatform/cli >> $GITHUB_ENV else echo "Falling back to fresh clone" +======= + if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then + echo Using pre-installed /opt/mokocli + echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV + else + echo Falling back to fresh clone +>>>>>>> main if ! command -v composer > /dev/null 2>&1; then sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 fi @@ -113,6 +130,43 @@ jobs: --path . --stability rc --bump minor --branch rc \ --token "${{ secrets.MOKOGITEA_TOKEN }}" +<<<<<<< HEAD +======= + - name: Update RC release notes from CHANGELOG.md + run: | + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" + + # Extract [Unreleased] section from changelog + NOTES="" + if [ -f "CHANGELOG.md" ]; then + NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md) + fi + [ -z "$NOTES" ] && NOTES="Release candidate" + + # Find the RC release and update its body + RELEASE_ID=$(curl -sf -H "Authorization: token ${TOKEN}" \ + "${API_BASE}/releases/tags/release-candidate" \ + | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) + + if [ -n "$RELEASE_ID" ]; then + python3 -c " + import json, urllib.request + body = open('/dev/stdin').read() + payload = json.dumps({'body': body}).encode() + req = urllib.request.Request( + '${API_BASE}/releases/${RELEASE_ID}', + data=payload, method='PATCH', + headers={ + 'Authorization': 'token ${TOKEN}', + 'Content-Type': 'application/json' + }) + urllib.request.urlopen(req) + " <<< "$NOTES" + echo "RC release notes updated from CHANGELOG.md" + fi + +>>>>>>> main - name: Summary if: always() run: | @@ -153,12 +207,17 @@ jobs: fi echo "No conflict markers found" +<<<<<<< HEAD - name: Setup MokoCLI tools +======= + - name: Setup mokocli tools +>>>>>>> main env: MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' run: | +<<<<<<< HEAD # Check both new (mokocli) and legacy (mokoplatform) install paths if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then echo "Using pre-installed /opt/mokocli" @@ -168,6 +227,13 @@ jobs: echo MOKO_CLI=/opt/mokoplatform/cli >> $GITHUB_ENV else echo "Falling back to fresh clone" +======= + if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then + echo Using pre-installed /opt/mokocli + echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV + else + echo Falling back to fresh clone +>>>>>>> main if ! command -v composer > /dev/null 2>&1; then sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 fi @@ -179,6 +245,7 @@ jobs: echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV fi +<<<<<<< HEAD - name: "Publish stable release" run: | php ${MOKO_CLI}/release_publish.php \ @@ -201,6 +268,75 @@ jobs: RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \ "${API_BASE}/releases/tags/stable" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) +======= + - name: "Detect platform" + id: platform + run: | + php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true + php ${MOKO_CLI}/manifest_read.php --path . --github-output 2>/dev/null || true + + - name: "Determine version bump level" + id: bump + run: | + # Fix/patch branches: version was already bumped by pre-release, just strip suffix + # Feature/dev branches: bump minor for the new stable release + HEAD_REF="${{ github.event.pull_request.head.ref || 'dev' }}" + case "$HEAD_REF" in + fix/*|patch/*|hotfix/*|bugfix/*) BUMP="none" ;; + *) BUMP="minor" ;; + esac + echo "level=${BUMP}" >> "$GITHUB_OUTPUT" + echo "Bump level: ${BUMP} (from branch: ${HEAD_REF})" + + - name: "Publish stable release" + run: | + BUMP_FLAG="" + if [ "${{ steps.bump.outputs.level }}" != "none" ]; then + BUMP_FLAG="--bump ${{ steps.bump.outputs.level }}" + fi + php ${MOKO_CLI}/release_publish.php \ + --path . --stability stable ${BUMP_FLAG} --branch main \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" + + - name: "Read published version" + id: version + run: | + VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "") + VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//') + [ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "tag=stable" >> "$GITHUB_OUTPUT" + echo "release_tag=stable" >> "$GITHUB_OUTPUT" + echo "branch=main" >> "$GITHUB_OUTPUT" + echo "Published version: ${VERSION}" + + - name: Update release notes and promote changelog + run: | + API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" + + # Get the stable release info (version and ID) + RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \ + "${API_BASE}/releases/tags/stable" 2>/dev/null || echo '{}') + RELEASE_ID=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" <<< "$RELEASE_JSON" 2>/dev/null || true) + # Extract version from release name (e.g. "06.17.00" or "v06.17.00") + VERSION=$(python3 -c " + import json, sys, re + r = json.load(sys.stdin) + name = r.get('name', '') + m = re.search(r'(\d+\.\d+\.\d+)', name) + print(m.group(1) if m else '') + " <<< "$RELEASE_JSON" 2>/dev/null || true) + + # Extract [Unreleased] section from changelog + NOTES="" + if [ -f "CHANGELOG.md" ]; then + NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md) + fi + [ -z "$NOTES" ] && NOTES="Stable release" + + # Update release body via API +>>>>>>> main if [ -n "$RELEASE_ID" ]; then python3 -c " import json, urllib.request @@ -210,7 +346,11 @@ jobs: '${API_BASE}/releases/${RELEASE_ID}', data=payload, method='PATCH', headers={ +<<<<<<< HEAD 'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}', +======= + 'Authorization': 'token ${TOKEN}', +>>>>>>> main 'Content-Type': 'application/json' }) urllib.request.urlopen(req) @@ -218,6 +358,27 @@ jobs: echo "Release notes updated from CHANGELOG.md" fi +<<<<<<< HEAD +======= + # Promote [Unreleased] → [version] in CHANGELOG.md and reset + if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then + DATE=$(date +%Y-%m-%d) + python3 -c " + import sys + version, date = sys.argv[1], sys.argv[2] + 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 + git push origin main || true + echo "Changelog promoted: [Unreleased] → [${VERSION}]" + fi + +>>>>>>> main # -- STEP 9: Mirror to GitHub (stable only) -------------------------------- - name: "Step 9: Mirror release to GitHub" if: >- diff --git a/.mokogitea/workflows/ci-generic.yml b/.mokogitea/workflows/ci-generic.yml new file mode 100644 index 0000000..18ae768 --- /dev/null +++ b/.mokogitea/workflows/ci-generic.yml @@ -0,0 +1,191 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: MokoStandards.CI +# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic +# PATH: /.gitea/workflows/ci-generic.yml +# VERSION: 01.00.00 +# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js) + +name: "Generic: Project CI" + +on: + workflow_dispatch: + +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + # ── Lint & Validate ─────────────────────────────────────────────────── + lint: + name: Lint & Validate + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Detect toolchain + id: detect + run: | + HAS_PHP=false + HAS_NODE=false + [ -f "composer.json" ] && HAS_PHP=true + [ -f "package.json" ] && HAS_NODE=true + echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT" + echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT" + echo "Toolchain: PHP=$HAS_PHP Node=$HAS_NODE" + + - name: Setup PHP + if: steps.detect.outputs.has_php == 'true' + run: | + if ! command -v php &> /dev/null; then + sudo apt-get update -qq + sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1 + fi + php -v + + - name: Setup Node.js + if: steps.detect.outputs.has_node == 'true' + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install PHP dependencies + if: steps.detect.outputs.has_php == 'true' + run: | + if [ -f "composer.json" ]; then + composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true + fi + + - name: Install Node.js dependencies + if: steps.detect.outputs.has_node == 'true' + run: | + if [ -f "package.json" ]; then + npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true + fi + + - name: PHP syntax check + if: steps.detect.outputs.has_php == 'true' + run: | + ERRORS=0 + while IFS= read -r -d '' file; do + if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then + echo "::error file=${file}::PHP syntax error" + ERRORS=$((ERRORS + 1)) + fi + done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" -print0) + + echo "## PHP Lint" >> $GITHUB_STEP_SUMMARY + if [ "$ERRORS" -eq 0 ]; then + echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY + else + echo "${ERRORS} file(s) with syntax errors." >> $GITHUB_STEP_SUMMARY + exit 1 + fi + + - name: TypeScript/JavaScript lint + if: steps.detect.outputs.has_node == 'true' + run: | + if [ -f "node_modules/.bin/eslint" ]; then + npx eslint src/ --quiet 2>&1 || { echo "::error::ESLint errors found"; exit 1; } + echo "## ESLint" >> $GITHUB_STEP_SUMMARY + echo "All files passed ESLint." >> $GITHUB_STEP_SUMMARY + elif [ -f ".eslintrc.json" ] || [ -f ".eslintrc.js" ] || [ -f "eslint.config.js" ]; then + echo "::warning::ESLint config found but eslint not installed" + else + echo "No ESLint configured — skipping" + fi + + - name: TypeScript compile check + if: steps.detect.outputs.has_node == 'true' + run: | + if [ -f "tsconfig.json" ] && [ -f "node_modules/.bin/tsc" ]; then + npx tsc --noEmit 2>&1 || { echo "::error::TypeScript compilation errors"; exit 1; } + echo "## TypeScript" >> $GITHUB_STEP_SUMMARY + echo "TypeScript compilation passed." >> $GITHUB_STEP_SUMMARY + fi + + - name: PHPStan static analysis + if: steps.detect.outputs.has_php == 'true' + run: | + if [ -f "phpstan.neon" ] && [ -f "vendor/bin/phpstan" ]; then + vendor/bin/phpstan analyse --no-progress 2>&1 || { echo "::warning::PHPStan found issues"; } + fi + + # ── Tests ───────────────────────────────────────────────────────────── + test: + name: Tests + runs-on: ubuntu-latest + needs: lint + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Detect toolchain + id: detect + run: | + HAS_PHP=false + HAS_NODE=false + [ -f "composer.json" ] && HAS_PHP=true + [ -f "package.json" ] && HAS_NODE=true + echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT" + echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT" + + - name: Setup PHP + if: steps.detect.outputs.has_php == 'true' + run: | + if ! command -v php &> /dev/null; then + sudo apt-get update -qq + sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1 + fi + + - name: Setup Node.js + if: steps.detect.outputs.has_node == 'true' + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: | + [ -f "composer.json" ] && composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true + [ -f "package.json" ] && { npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true; } + + - name: Run PHP tests + if: steps.detect.outputs.has_php == 'true' + run: | + if [ -f "vendor/bin/phpunit" ]; then + vendor/bin/phpunit --testdox 2>&1 + echo "## PHPUnit" >> $GITHUB_STEP_SUMMARY + echo "Tests passed." >> $GITHUB_STEP_SUMMARY + elif [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then + echo "::warning::PHPUnit config found but phpunit not installed" + else + echo "No PHPUnit configured — skipping" + fi + + - name: Run Node.js tests + if: steps.detect.outputs.has_node == 'true' + run: | + if jq -e '.scripts.test' package.json > /dev/null 2>&1; then + npm test 2>&1 + echo "## Node.js Tests" >> $GITHUB_STEP_SUMMARY + echo "Tests passed." >> $GITHUB_STEP_SUMMARY + else + echo "No test script in package.json — skipping" + fi + + - name: Build check + run: | + if [ -f "Makefile" ]; then + make build 2>&1 || echo "::warning::Build failed or not configured" + elif [ -f "package.json" ] && jq -e '.scripts.build' package.json > /dev/null 2>&1; then + npm run build 2>&1 || echo "::warning::Build failed" + fi diff --git a/.mokogitea/workflows/ci-platform.yml b/.mokogitea/workflows/ci-platform.yml index 2cac577..ccb5893 100644 --- a/.mokogitea/workflows/ci-platform.yml +++ b/.mokogitea/workflows/ci-platform.yml @@ -4,18 +4,30 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.CI # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # PATH: /.mokogitea/workflows/ci-platform.yml # VERSION: 09.23.00 # BRIEF: MokoCLI CI — the standards engine validates itself +======= +# INGROUP: mokoplatform.CI +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +# PATH: /.mokogitea/workflows/ci-platform.yml +# VERSION: 09.23.00 +# BRIEF: mokoplatform CI — the standards engine validates itself +>>>>>>> main # # +========================================================================+ # | MOKOCLI CI | # +========================================================================+ # | | # | This is NOT a generic CI workflow. This is the self-validation | +<<<<<<< HEAD # | pipeline for the central MokoCLI enterprise engine. | +======= +# | pipeline for the central mokoplatform enterprise engine. | +>>>>>>> main # | | # | It dogfoods every tool the platform ships to governed repos: | # | | @@ -29,7 +41,11 @@ # | | # +========================================================================+ +<<<<<<< HEAD name: "Platform: MokoCLI CI" +======= +name: "Platform: mokoplatform CI" +>>>>>>> main on: push: @@ -421,7 +437,11 @@ jobs: - name: Check gate results run: | { +<<<<<<< HEAD echo "# MokoCLI CI" +======= + echo "# mokoplatform CI" +>>>>>>> main echo "" echo "| Gate | Job | Status |" echo "|---|---|---|" diff --git a/.mokogitea/workflows/cleanup.yml b/.mokogitea/workflows/cleanup.yml index 3a266e9..48c0b7b 100644 --- a/.mokogitea/workflows/cleanup.yml +++ b/.mokogitea/workflows/cleanup.yml @@ -4,10 +4,17 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.Maintenance # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # PATH: /.mokogitea/workflows/cleanup.yml # VERSION: 09.23.00 +======= +# INGROUP: MokoStandards.Maintenance +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards +# PATH: /.gitea/workflows/cleanup.yml +# VERSION: 01.00.00 +>>>>>>> main # BRIEF: Scheduled cleanup — delete merged branches and old workflow runs name: "Universal: Repository Cleanup" @@ -33,17 +40,17 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.MOKOGITEA_TOKEN }} + token: ${{ secrets.GA_TOKEN }} - name: Delete merged branches env: - GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + GA_TOKEN: ${{ secrets.GA_TOKEN }} run: | echo "=== Merged Branch Cleanup ===" API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" # List branches via API - BRANCHES=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \ + BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ "${API}/branches?limit=50" | jq -r '.[].name') DELETED=0 @@ -56,7 +63,7 @@ jobs: # Check if branch is merged into main if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then echo " Deleting merged branch: ${BRANCH}" - curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \ + curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ "${API}/branches/${BRANCH}" 2>/dev/null || true DELETED=$((DELETED + 1)) fi @@ -66,20 +73,20 @@ jobs: - name: Clean old workflow runs env: - GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + GA_TOKEN: ${{ secrets.GA_TOKEN }} run: | echo "=== Workflow Run Cleanup ===" API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ) # Get old completed runs - RUNS=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \ + RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ "${API}/actions/runs?status=completed&limit=50" | \ jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null) DELETED=0 for RUN_ID in $RUNS; do - curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \ + curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ "${API}/actions/runs/${RUN_ID}" 2>/dev/null || true DELETED=$((DELETED + 1)) done diff --git a/.mokogitea/workflows/deploy-manual.yml b/.mokogitea/workflows/deploy-manual.yml new file mode 100644 index 0000000..bb133ed --- /dev/null +++ b/.mokogitea/workflows/deploy-manual.yml @@ -0,0 +1,126 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: MokoStandards.Deploy +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API +# PATH: /templates/workflows/joomla/deploy-manual.yml.template +# VERSION: 04.07.00 +# BRIEF: Manual SFTP deploy to dev server for Joomla repos + +name: "Universal: Deploy to Dev (Manual)" + +on: + workflow_dispatch: + inputs: + clear_remote: + description: 'Delete all remote files before uploading' + required: false + default: 'false' + type: boolean + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +permissions: + contents: read + +jobs: + deploy: + name: SFTP Deploy to Dev + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Setup PHP + run: | + php -v && composer --version + + - name: Setup MokoStandards tools + env: + GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }} + MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }} + MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} + COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}' + run: | + git clone --depth 1 --branch main --quiet \ + "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ + /tmp/mokostandards-api 2>/dev/null || true + if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then + cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true + fi + + - name: Check FTP configuration + id: check + env: + HOST: ${{ vars.DEV_FTP_HOST }} + PATH_VAR: ${{ vars.DEV_FTP_PATH }} + PORT: ${{ vars.DEV_FTP_PORT }} + run: | + if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then + echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy" + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "skip=false" >> "$GITHUB_OUTPUT" + echo "host=$HOST" >> "$GITHUB_OUTPUT" + + REMOTE="${PATH_VAR%/}" + echo "remote=$REMOTE" >> "$GITHUB_OUTPUT" + + [ -z "$PORT" ] && PORT="22" + echo "port=$PORT" >> "$GITHUB_OUTPUT" + + - name: Deploy via SFTP + if: steps.check.outputs.skip != 'true' + env: + SFTP_KEY: ${{ secrets.DEV_FTP_KEY }} + SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }} + SFTP_USER: ${{ vars.DEV_FTP_USERNAME }} + run: | + SOURCE_DIR="src" + [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" + [ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; } + + printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \ + "${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \ + > /tmp/sftp-config.json + + if [ -n "$SFTP_KEY" ]; then + echo "$SFTP_KEY" > /tmp/deploy_key + chmod 600 /tmp/deploy_key + printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json + else + printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json + fi + + DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json) + [ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote) + + PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true) + if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then + php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}" + else + php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}" + fi + + rm -f /tmp/deploy_key /tmp/sftp-config.json + + - name: Summary + if: always() + run: | + if [ "${{ steps.check.outputs.skip }}" = "true" ]; then + echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY + else + echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.mokogitea/workflows/gitleaks.yml b/.mokogitea/workflows/gitleaks.yml index 4a7f4fb..0ca4a6c 100644 --- a/.mokogitea/workflows/gitleaks.yml +++ b/.mokogitea/workflows/gitleaks.yml @@ -4,10 +4,15 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.Security # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +# INGROUP: MokoStandards.Security +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API +>>>>>>> main # PATH: /templates/workflows/gitleaks.yml.template -# VERSION: 09.23.00 +# VERSION: 01.00.00 # BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens # # +========================================================================+ @@ -25,10 +30,6 @@ name: "Universal: Secret Scanning" on: - pull_request: - branches: - - main - - 'dev/**' schedule: - cron: '0 5 * * 1' # Weekly Monday 05:00 UTC workflow_dispatch: diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 23234c9..3f37d47 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -4,8 +4,13 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.Automation # VERSION: 09.25.05 +======= +# INGROUP: mokocli.Automation +# VERSION: 09.32.00 +>>>>>>> main # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" @@ -28,7 +33,7 @@ jobs: steps: - name: Create branch and comment run: | - TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" + TOKEN="${{ secrets.GA_TOKEN }}" API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" ISSUE_NUM="${{ github.event.issue.number }}" ISSUE_TITLE="${{ github.event.issue.title }}" diff --git a/.mokogitea/workflows/notify.yml b/.mokogitea/workflows/notify.yml index d4429fb..6cf7584 100644 --- a/.mokogitea/workflows/notify.yml +++ b/.mokogitea/workflows/notify.yml @@ -4,10 +4,17 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.Notifications # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # PATH: /.mokogitea/workflows/notify.yml # VERSION: 09.23.00 +======= +# INGROUP: MokoStandards.Notifications +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards +# PATH: /.gitea/workflows/notify.yml +# VERSION: 01.00.00 +>>>>>>> main # BRIEF: Push notifications via ntfy on release success or workflow failure name: "Universal: Notifications" diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml index 114aac7..3f1511a 100644 --- a/.mokogitea/workflows/pr-check.yml +++ b/.mokogitea/workflows/pr-check.yml @@ -96,6 +96,32 @@ jobs: echo "Branch policy: OK (${HEAD} → ${BASE})" echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY + # ── Secret Scanning ────────────────────────────────────────────────── + gitleaks: + name: Secret Scan + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Gitleaks + run: | + GITLEAKS_VERSION="8.21.2" + curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ + | tar -xz -C /usr/local/bin gitleaks + + - name: Scan PR commits for secrets + run: | + if gitleaks detect --source . --verbose \ + --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} 2>&1; then + echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY + else + echo "::error::Potential secrets detected in PR commits" + exit 1 + fi + # ── Code Validation ──────────────────────────────────────────────────── validate: name: Validate PR @@ -172,8 +198,7 @@ jobs: if: steps.platform.outputs.platform == 'joomla' run: | MISSING=0 - SOURCE_DIR="source" - [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="src" + SOURCE_DIR="src" [ ! -d "$SOURCE_DIR" ] && exit 0 while IFS= read -r dir; do if [ ! -f "${dir}/index.html" ]; then @@ -221,7 +246,7 @@ jobs: echo "joomla.asset.json: valid" fi - # Validate all XML files in source/src/ are well-formed + # Validate all XML files in src/ are well-formed XML_ERRORS=0 if command -v php &> /dev/null; then while IFS= read -r -d '' xmlfile; do @@ -452,11 +477,10 @@ jobs: - name: Verify package source run: | - SOURCE_DIR="source" - [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="src" + SOURCE_DIR="src" [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" if [ ! -d "$SOURCE_DIR" ]; then - echo "::warning::No source/, src/, or htdocs/ directory" + echo "::warning::No src/ or htdocs/ directory" exit 0 fi FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l) diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index 6e6cfe4..5df9ddf 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -4,7 +4,11 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.Release +======= +# INGROUP: mokocli.Release +>>>>>>> main # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # PATH: /templates/workflows/universal/pre-release.yml.template # VERSION: 05.01.00 @@ -60,11 +64,16 @@ jobs: token: ${{ secrets.MOKOGITEA_TOKEN }} ref: ${{ github.ref_name }} +<<<<<<< HEAD - name: Setup MokoCLI tools +======= + - name: Setup mokocli tools +>>>>>>> main env: MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting run: | +<<<<<<< HEAD # Check both new (mokocli) and legacy (mokoplatform) install paths if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/cli/manifest_element.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then echo "Using pre-installed /opt/mokocli" @@ -74,6 +83,14 @@ jobs: echo MOKO_CLI=/opt/mokoplatform/cli >> $GITHUB_ENV else echo "Falling back to fresh clone" +======= + # Use pre-installed /opt/mokocli if available (updated by cron every 6h) + if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/cli/manifest_element.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then + echo Using pre-installed /opt/mokocli + echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV + else + echo Falling back to fresh clone +>>>>>>> main if ! command -v composer > /dev/null 2>&1; then sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 fi diff --git a/.mokogitea/workflows/rc-revert.yml b/.mokogitea/workflows/rc-revert.yml new file mode 100644 index 0000000..5e61de8 --- /dev/null +++ b/.mokogitea/workflows/rc-revert.yml @@ -0,0 +1,66 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# 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 + +name: "RC Revert" + +on: + pull_request: + types: [closed] + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + revert: + name: Rename rc/ back to dev/ + runs-on: ubuntu-latest + if: >- + github.event.pull_request.merged == false && + startsWith(github.event.pull_request.head.ref, 'rc/') + + steps: + - name: Rename branch + run: | + BRANCH="${{ github.event.pull_request.head.ref }}" + SUFFIX="${BRANCH#rc/}" + DEV_BRANCH="dev/${SUFFIX}" + API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches" + TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" + + # Create dev/ branch from rc/ branch + STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: token ${TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \ + "${API}" 2>/dev/null || true) + + if [ "$STATUS" = "201" ]; then + echo "Created branch: ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY + else + echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})" + exit 1 + fi + + # Delete rc/ branch + ENCODED=$(php -r "echo rawurlencode('${BRANCH}');") + STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \ + -H "Authorization: token ${TOKEN}" \ + "${API}/${ENCODED}" 2>/dev/null || true) + + if [ "$STATUS" = "204" ]; then + echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY + else + echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})" + fi + + echo "### RC Reverted" >> $GITHUB_STEP_SUMMARY + echo "${BRANCH} → ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY diff --git a/.mokogitea/workflows/repo-health.yml b/.mokogitea/workflows/repo-health.yml index b18f2d0..cf8cba0 100644 --- a/.mokogitea/workflows/repo-health.yml +++ b/.mokogitea/workflows/repo-health.yml @@ -1,3 +1,4 @@ +<<<<<<< HEAD # ============================================================================ # Copyright (C) 2025 Moko Consulting # @@ -711,3 +712,717 @@ jobs: report_gate "Repository Health" \ "${{ needs.repo_health.result }}" \ "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary." +======= +# ============================================================================ +# Copyright (C) 2025 Moko Consulting +# +# This file is part of a Moko Consulting project. +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# 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. +# ============================================================================ + +name: "Generic: Repo Health" + +defaults: + run: + shell: bash + +on: + workflow_dispatch: + inputs: + profile: + description: 'Validation profile: all, scripts, or repo' + required: true + default: all + type: choice + options: + - all + - scripts + - repo + pull_request: + branches: + - main + +permissions: + contents: read + +env: + # Scripts governance policy + SCRIPTS_REQUIRED_DIRS: + SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate + + # Repo health policy + REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogitea/workflows/ + REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/ + REPO_DISALLOWED_DIRS: + REPO_DISALLOWED_FILES: TODO.md,todo.md + + # Extended checks toggles + EXTENDED_CHECKS: "true" + + # File / directory variables + DOCS_INDEX: docs/docs-index.md + SCRIPT_DIR: scripts + WORKFLOWS_DIR: .mokogitea/workflows + SHELLCHECK_PATTERN: '*.sh' + SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml' + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + access_check: + name: Access control + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + + outputs: + allowed: ${{ steps.perm.outputs.allowed }} + permission: ${{ steps.perm.outputs.permission }} + + steps: + - name: Check actor permission (admin only) + id: perm + env: + TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }} + REPO: ${{ github.repository }} + ACTOR: ${{ github.actor }} + run: | + set -euo pipefail + ALLOWED=false + PERMISSION=unknown + METHOD="" + + # Hardcoded authorized users — always allowed + case "$ACTOR" in + jmiller|gitea-actions[bot]) + ALLOWED=true + PERMISSION=admin + METHOD="hardcoded allowlist" + ;; + *) + # Detect platform and check permissions via API + API_BASE="${GITHUB_API_URL:-${GITEA_API_URL:-https://api.github.com}}" + RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \ + "${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}') + PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown") + if [ "$PERMISSION" = "admin" ] || [ "$PERMISSION" = "maintain" ] || [ "$PERMISSION" = "owner" ]; then + ALLOWED=true + fi + METHOD="collaborator API" + ;; + esac + + echo "permission=${PERMISSION}" >> "$GITHUB_OUTPUT" + echo "allowed=${ALLOWED}" >> "$GITHUB_OUTPUT" + + { + echo "## Access Authorization" + echo "" + echo "| Field | Value |" + echo "|-------|-------|" + echo "| **Actor** | \`${ACTOR}\` |" + echo "| **Repository** | \`${REPO}\` |" + echo "| **Permission** | \`${PERMISSION}\` |" + echo "| **Method** | ${METHOD} |" + echo "| **Authorized** | ${ALLOWED} |" + echo "" + if [ "$ALLOWED" = "true" ]; then + echo "${ACTOR} authorized (${METHOD})" + else + echo "${ACTOR} is NOT authorized. Requires admin or maintain role." + fi + } >> "${GITHUB_STEP_SUMMARY}" + + - name: Deny execution when not permitted + if: ${{ steps.perm.outputs.allowed != 'true' }} + run: | + set -euo pipefail + printf '%s\n' 'ERROR: Access denied. Admin permission required.' >> "${GITHUB_STEP_SUMMARY}" + exit 1 + + scripts_governance: + name: Scripts governance + needs: access_check + if: ${{ needs.access_check.outputs.allowed == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 + + - name: Scripts folder checks + env: + PROFILE_RAW: ${{ github.event.inputs.profile }} + run: | + set -euo pipefail + + profile="${PROFILE_RAW:-all}" + case "${profile}" in + all|scripts|repo) ;; + *) + printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" + exit 1 + ;; + esac + + if [ "${profile}" = 'repo' ]; then + { + printf '%s\n' '### Scripts governance' + printf '%s\n' "Profile: ${profile}" + printf '%s\n' 'Status: SKIPPED' + printf '%s\n' 'Reason: profile excludes scripts governance' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + exit 0 + fi + + if [ ! -d "${SCRIPT_DIR}" ]; then + { + printf '%s\n' '### Scripts governance' + printf '%s\n' 'Status: OK (advisory)' + printf '%s\n' 'scripts/ directory not present. No scripts governance enforced.' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + exit 0 + fi + + if [ -n "${SCRIPTS_REQUIRED_DIRS:-}" ]; then IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}"; else required_dirs=(); fi + IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}" + + missing_dirs=() + unapproved_dirs=() + + for d in "${required_dirs[@]}"; do + req="${d%/}" + [ ! -d "${req}" ] && missing_dirs+=("${req}/") + done + + while IFS= read -r d; do + allowed=false + for a in "${allowed_dirs[@]}"; do + a_norm="${a%/}" + [ "${d%/}" = "${a_norm}" ] && allowed=true + done + [ "${allowed}" = false ] && unapproved_dirs+=("${d%/}/") + done < <(find "${SCRIPT_DIR}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sed 's#^\./##') + + { + printf '%s\n' '### Scripts governance' + printf '%s\n' "Profile: ${profile}" + printf '%s\n' '| Area | Status | Notes |' + printf '%s\n' '|---|---|---|' + + if [ "${#missing_dirs[@]}" -gt 0 ]; then + printf '%s\n' '| Required directories | Warning | Missing required subfolders |' + else + printf '%s\n' '| Required directories | OK | All required subfolders present |' + fi + + if [ "${#unapproved_dirs[@]}" -gt 0 ]; then + printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |' + else + printf '%s\n' '| Directory policy | OK | No unapproved directories |' + fi + + printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |' + printf '\n' + + if [ "${#missing_dirs[@]}" -gt 0 ]; then + printf '%s\n' 'Missing required script directories:' + for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done + printf '\n' + else + printf '%s\n' 'Missing required script directories: none.' + printf '\n' + fi + + if [ "${#unapproved_dirs[@]}" -gt 0 ]; then + printf '%s\n' 'Unapproved script directories detected:' + for m in "${unapproved_dirs[@]}"; do printf '%s\n' "- ${m}"; done + printf '\n' + else + printf '%s\n' 'Unapproved script directories detected: none.' + printf '\n' + fi + + printf '%s\n' 'Scripts governance completed in advisory mode.' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + + repo_health: + name: Repository health + needs: access_check + if: ${{ needs.access_check.outputs.allowed == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 + + - name: Repository health checks + env: + PROFILE_RAW: ${{ github.event.inputs.profile }} + run: | + set -euo pipefail + + profile="${PROFILE_RAW:-all}" + case "${profile}" in + all|scripts|repo) ;; + *) + printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" + exit 1 + ;; + esac + + if [ "${profile}" = 'scripts' ]; then + { + printf '%s\n' '### Repository health' + printf '%s\n' "Profile: ${profile}" + printf '%s\n' 'Status: SKIPPED' + printf '%s\n' 'Reason: profile excludes repository health' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + exit 0 + fi + + IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}" + IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}" + if [ -n "${REPO_DISALLOWED_DIRS:-}" ]; then IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"; else disallowed_dirs=(); fi + IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES:-}" + + missing_required=() + missing_optional=() + + # Source directory: src/ or htdocs/ (either is valid for extension repos) + SOURCE_DIR="" + if [ -d "src" ]; then + SOURCE_DIR="src" + elif [ -d "htdocs" ]; then + SOURCE_DIR="htdocs" + elif [ -d "deploy" ] || [ -d "cli" ] || [ -d "monitoring" ]; then + # Platform/tooling repos don't need src/ + SOURCE_DIR="" + else + missing_required+=("src/ or htdocs/ (source directory required)") + fi + + for item in "${required_artifacts[@]}"; do + if printf '%s' "${item}" | grep -q '/$'; then + d="${item%/}" + [ ! -d "${d}" ] && missing_required+=("${item}") + else + [ ! -f "${item}" ] && missing_required+=("${item}") + fi + done + + for f in "${optional_files[@]}"; do + if printf '%s' "${f}" | grep -q '/$'; then + d="${f%/}" + [ ! -d "${d}" ] && missing_optional+=("${f}") + else + [ ! -f "${f}" ] && missing_optional+=("${f}") + fi + done + + for d in "${disallowed_dirs[@]}"; do + d_norm="${d%/}" + [ -d "${d_norm}" ] && missing_required+=("${d_norm}/ (disallowed)") + done + + for f in "${disallowed_files[@]}"; do + [ -f "${f}" ] && missing_required+=("${f} (disallowed)") + done + + git fetch origin --prune + + dev_paths=() + dev_branches=() + + while IFS= read -r b; do + name="${b#origin/}" + if [ "${name}" = 'dev' ]; then + dev_branches+=("${name}") + else + dev_paths+=("${name}") + fi + done < <(git branch -r --list 'origin/dev*' | sed 's/^ *//') + + if [ "${#dev_paths[@]}" -eq 0 ] && [ "${#dev_branches[@]}" -eq 0 ]; then + missing_required+=("dev or dev/* branch") + fi + + content_warnings=() + + if [ -f 'CHANGELOG.md' ] && ! grep -Eq '^# Changelog' CHANGELOG.md; then + content_warnings+=("CHANGELOG.md missing '# Changelog' header") + fi + + if [ -f 'CHANGELOG.md' ] && grep -Eq '^[# ]*Unreleased' CHANGELOG.md; then + content_warnings+=("CHANGELOG.md contains Unreleased section (review release readiness)") + fi + + if [ -f 'LICENSE' ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then + content_warnings+=("LICENSE does not look like a GPL text") + fi + + if [ -f 'README.md' ] && ! grep -qiE 'moko|Moko' README.md; then + content_warnings+=("README.md missing expected brand keyword") + fi + + export PROFILE_RAW="${profile}" + export MISSING_REQUIRED="$(printf '%s\n' "${missing_required[@]:-}")" + export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")" + export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")" + + report_json=$(printf '{"profile":"%s","missing_required":%d,"missing_optional":%d,"content_warnings":%d}' "$profile" "${#missing_required[@]}" "${#missing_optional[@]}" "${#content_warnings[@]}") + + { + printf '%s\n' '### Repository health' + printf '%s\n' "Profile: ${profile}" + printf '%s\n' '| Metric | Value |' + printf '%s\n' '|---|---|' + printf '%s\n' "| Missing required | ${#missing_required[@]} |" + printf '%s\n' "| Missing optional | ${#missing_optional[@]} |" + printf '%s\n' "| Content warnings | ${#content_warnings[@]} |" + printf '\n' + + printf '%s\n' '### Guardrails report (JSON)' + printf '%s\n' '```json' + printf '%s\n' "${report_json}" + printf '%s\n' '```' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + + if [ "${#missing_required[@]}" -gt 0 ]; then + { + printf '%s\n' '### Missing required repo artifacts' + for m in "${missing_required[@]}"; do printf '%s\n' "- ${m}"; done + printf '%s\n' 'ERROR: Guardrails failed. Missing required repository artifacts.' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + exit 1 + fi + + if [ "${#missing_optional[@]}" -gt 0 ]; then + { + printf '%s\n' '### Missing optional repo artifacts' + for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + + if [ "${#content_warnings[@]}" -gt 0 ]; then + { + printf '%s\n' '### Repo content warnings' + for m in "${content_warnings[@]}"; do printf '%s\n' "- ${m}"; done + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + + # -- Joomla-specific checks -- + joomla_findings=() + + MANIFEST="$(find . -maxdepth 2 -name '*.xml' -exec grep -l '/dev/null | head -1 || true)" + if [ -z "${MANIFEST}" ]; then + joomla_findings+=("Joomla XML manifest not found (no *.xml with tag)") + else + if ! grep -qP '' "${MANIFEST}"; then + joomla_findings+=("XML manifest: tag missing") + fi + if ! grep -qP 'type="(component|module|plugin|library|package|template|language)"' "${MANIFEST}"; then + joomla_findings+=("XML manifest: type attribute missing or invalid") + fi + if ! grep -qP '' "${MANIFEST}"; then + joomla_findings+=("XML manifest: tag missing") + fi + if ! grep -qP '' "${MANIFEST}"; then + joomla_findings+=("XML manifest: tag missing") + fi + if ! grep -qP ' missing (required for Joomla 5+)") + fi + fi + + INI_COUNT="$(find . -name '*.ini' -type f 2>/dev/null | wc -l)" + if [ "${INI_COUNT}" -eq 0 ]; then + joomla_findings+=("No .ini language files found") + fi + + if [ ! -f 'updates.xml' ]; then + joomla_findings+=("updates.xml missing in root (required for Joomla update server)") + fi + + if [ -n "${SOURCE_DIR}" ]; then + INDEX_DIRS=("${SOURCE_DIR}" "${SOURCE_DIR}/admin" "${SOURCE_DIR}/site") + for dir in "${INDEX_DIRS[@]}"; do + if [ -d "${dir}" ] && [ ! -f "${dir}/index.html" ]; then + joomla_findings+=("${dir}/index.html missing (directory listing protection)") + fi + done + fi + + if [ "${#joomla_findings[@]}" -gt 0 ]; then + { + printf '%s\n' '### Joomla extension checks' + printf '%s\n' '| Check | Status |' + printf '%s\n' '|---|---|' + for f in "${joomla_findings[@]}"; do + printf '%s\n' "| ${f} | Warning |" + done + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + else + { + printf '%s\n' '### Joomla extension checks' + printf '%s\n' 'All Joomla-specific checks passed.' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + + extended_enabled="${EXTENDED_CHECKS:-true}" + extended_findings=() + + if [ "${extended_enabled}" = 'true' ]; then + if [ -f '.github/CODEOWNERS' ] || [ -f 'CODEOWNERS' ] || [ -f 'docs/CODEOWNERS' ]; then + : + else + extended_findings+=("CODEOWNERS not found (.github/CODEOWNERS preferred)") + fi + + if ls "${WORKFLOWS_DIR}"/*.yml >/dev/null 2>&1 || ls "${WORKFLOWS_DIR}"/*.yaml >/dev/null 2>&1; then + bad_refs="$(grep -RIn --include='*.yml' --include='*.yaml' -E '^[[:space:]]*uses:[[:space:]]*[^#]+@(main|master)\b' "${WORKFLOWS_DIR}" 2>/dev/null || true)" + if [ -n "${bad_refs}" ]; then + extended_findings+=("Workflows reference actions @main/@master (pin versions): see log excerpt") + { + printf '%s\n' '### Workflow pinning advisory' + printf '%s\n' 'Found uses: entries pinned to main/master:' + printf '%s\n' '```' + printf '%s\n' "${bad_refs}" + printf '%s\n' '```' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + fi + + if [ -f "${DOCS_INDEX}" ]; then + missing_links="" + while IFS= read -r docline; do + for link in $(echo "$docline" | grep -oE '\]\([^)]+\)' | sed 's/\](//' | sed 's/)$//' || true); do + case "$link" in http://*|https://*|"#"*|mailto:*) continue ;; esac + linkpath="${link%%#*}" + linkpath="${linkpath%%\?*}" + [ -z "$linkpath" ] && continue + if [ "${linkpath:0:1}" = "/" ]; then + testpath="${linkpath#/}" + else + testpath="$(dirname "${DOCS_INDEX}")/${linkpath}" + fi + [ ! -e "$testpath" ] && missing_links="${missing_links}${testpath} " + done + done < "${DOCS_INDEX}" + if [ -n "${missing_links}" ]; then + extended_findings+=("docs/docs-index.md contains broken relative links") + { + printf '%s\n' '### Docs index link integrity' + printf '%s\n' 'Broken relative links:' + for bl in ${missing_links}; do + printf '%s\n' "- ${bl}" + done + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + fi + + if [ -d "${SCRIPT_DIR}" ]; then + if ! command -v shellcheck >/dev/null 2>&1; then + sudo apt-get update -qq + sudo apt-get install -y shellcheck >/dev/null + fi + + sc_out='' + while IFS= read -r shf; do + [ -z "${shf}" ] && continue + out_one="$(shellcheck -S warning -x "${shf}" 2>/dev/null || true)" + if [ -n "${out_one}" ]; then + sc_out="${sc_out}${out_one}\n" + fi + done < <(find "${SCRIPT_DIR}" -type f -name "${SHELLCHECK_PATTERN}" 2>/dev/null | sort) + + if [ -n "${sc_out}" ]; then + extended_findings+=("ShellCheck warnings detected (advisory)") + sc_head="$(printf '%s' "${sc_out}" | head -n 200)" + { + printf '%s\n' '### ShellCheck (advisory)' + printf '%s\n' '```' + printf '%s\n' "${sc_head}" + printf '%s\n' '```' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + fi + + spdx_missing=() + IFS=',' read -r -a spdx_globs <<< "${SPDX_FILE_GLOBS}" + spdx_args=() + for g in "${spdx_globs[@]}"; do spdx_args+=("${g}"); done + + while IFS= read -r f; do + [ -z "${f}" ] && continue + if ! head -n 40 "${f}" | grep -q 'SPDX-License-Identifier:'; then + spdx_missing+=("${f}") + fi + done < <(git ls-files "${spdx_args[@]}" 2>/dev/null || true) + + if [ "${#spdx_missing[@]}" -gt 0 ]; then + extended_findings+=("SPDX header missing in some tracked files (advisory)") + { + printf '%s\n' '### SPDX header advisory' + printf '%s\n' 'Files missing SPDX-License-Identifier (first 40 lines scan):' + for f in "${spdx_missing[@]}"; do printf '%s\n' "- ${f}"; done + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + + stale_cutoff_days=180 + stale_branches="$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/remotes/origin 2>/dev/null | awk -v now="$(date +%s)" -v days="${stale_cutoff_days}" '{if (now-$2 > days*86400) print $1}' | head -50)" + if [ -n "${stale_branches}" ]; then + extended_findings+=("Stale remote branches detected (advisory)") + { + printf '%s\n' '### Git hygiene advisory' + printf '%s\n' "Branches with last commit older than ${stale_cutoff_days} days (sample up to 50):" + while IFS= read -r b; do [ -n "${b}" ] && printf '%s\n' "- ${b}"; done <<< "${stale_branches}" + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + fi + + { + printf '%s\n' '### Guardrails coverage matrix' + printf '%s\n' '| Domain | Status | Notes |' + printf '%s\n' '|---|---|---|' + printf '%s\n' '| Access control | OK | Admin-only execution gate |' + printf '%s\n' '| Release policy | N/A | Releases handled by MokoGitea |' + printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |' + printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |' + printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |' + if [ "${extended_enabled}" = 'true' ]; then + if [ "${#extended_findings[@]}" -gt 0 ]; then + printf '%s\n' '| Extended checks | Warning | See extended findings below |' + else + printf '%s\n' '| Extended checks | OK | No findings |' + fi + else + printf '%s\n' '| Extended checks | SKIPPED | EXTENDED_CHECKS disabled |' + fi + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + + if [ "${extended_enabled}" = 'true' ] && [ "${#extended_findings[@]}" -gt 0 ]; then + { + printf '%s\n' '### Extended findings (advisory)' + for f in "${extended_findings[@]}"; do printf '%s\n' "- ${f}"; done + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + + printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}" + + + site-health: + name: Site Health + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + + - name: Uptime check + if: env.URLS != '' + run: | + echo "$URLS" > /tmp/urls.txt + php monitoring/uptime-probe.php --urls /tmp/urls.txt --timeout 15 || echo "::warning::Some sites are down" + rm -f /tmp/urls.txt + env: + URLS: ${{ vars.MONITORED_URLS }} + + - name: SSL certificate check + if: env.DOMAINS != '' + run: | + echo "$DOMAINS" > /tmp/domains.txt + php monitoring/ssl-check.php --domains /tmp/domains.txt --warn-days 30 || echo "::warning::SSL certificates expiring soon" + rm -f /tmp/domains.txt + env: + DOMAINS: ${{ vars.MONITORED_DOMAINS }} + + - name: Summary + if: always() + run: | + echo "### Site Health" >> $GITHUB_STEP_SUMMARY + echo "Uptime and SSL checks completed." >> $GITHUB_STEP_SUMMARY + + # ═══════════════════════════════════════════════════════════════════════ + # Issue Reporter — file issues for failed gates + # ═══════════════════════════════════════════════════════════════════════ + report-issues: + name: "Report Issues" + runs-on: ubuntu-latest + needs: [access_check, scripts_governance, repo_health] + if: >- + always() && + (needs.scripts_governance.result == 'failure' || + needs.repo_health.result == 'failure') + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + sparse-checkout: automation/ci-issue-reporter.sh + sparse-checkout-cone-mode: false + + - name: "File issues for failed gates" + env: + GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + run: | + chmod +x automation/ci-issue-reporter.sh + REPORTER="./automation/ci-issue-reporter.sh" + WF="Repo Health" + + report_gate() { + local gate="$1" result="$2" details="$3" + if [ "$result" = "failure" ]; then + "$REPORTER" --gate "$gate" --details "$details" --workflow "$WF" --severity error + fi + } + + report_gate "Scripts Governance" \ + "${{ needs.scripts_governance.result }}" \ + "Scripts directory policy violations detected. Review required and allowed directories." + + report_gate "Repository Health" \ + "${{ needs.repo_health.result }}" \ + "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary." +>>>>>>> main diff --git a/.mokogitea/workflows/security-audit.yml b/.mokogitea/workflows/security-audit.yml index ac92d77..b91d4cf 100644 --- a/.mokogitea/workflows/security-audit.yml +++ b/.mokogitea/workflows/security-audit.yml @@ -4,10 +4,17 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow +<<<<<<< HEAD # INGROUP: MokoCLI.Security # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # PATH: /.mokogitea/workflows/security-audit.yml # VERSION: 09.23.00 +======= +# INGROUP: MokoStandards.Security +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards +# PATH: /.gitea/workflows/security-audit.yml +# VERSION: 01.00.00 +>>>>>>> main # BRIEF: Dependency vulnerability scanning for composer and npm packages name: "Universal: Security Audit" @@ -80,19 +87,3 @@ jobs: -H "Priority: high" \ -d "Security audit found vulnerabilities. Review dependency updates." \ "${NTFY_URL}/${NTFY_TOPIC}" || true - - - - name: Joomla version audit - if: always() - run: | - if [ -f "monitoring/joomla-version-audit.php" ] && [ -n "$JOOMLA_SITES" ]; then - echo "$JOOMLA_SITES" > /tmp/sites.json - php monitoring/joomla-version-audit.php --sites /tmp/sites.json || true - echo "### Joomla Version Audit" >> $GITHUB_STEP_SUMMARY - rm -f /tmp/sites.json - else - echo "Joomla audit skipped (no script or JOOMLA_SITES_JSON not configured)" - fi - env: - JOOMLA_SITES: ${{ vars.JOOMLA_SITES_JSON }} - diff --git a/.mokogitea/workflows/sync-feature-versions.yml b/.mokogitea/workflows/sync-feature-versions.yml new file mode 100644 index 0000000..7682008 --- /dev/null +++ b/.mokogitea/workflows/sync-feature-versions.yml @@ -0,0 +1,103 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: mokocli.Automation +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +# PATH: /.mokogitea/workflows/sync-feature-versions.yml +# VERSION: 01.00.00 +# BRIEF: Merge dev into open feature branches after version bumps + +name: "Universal: Sync Feature Branch Versions" + +on: + push: + branches: + - dev + workflow_dispatch: + +permissions: + contents: write + +env: + GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + +jobs: + sync: + name: Sync feature branches with dev + runs-on: ubuntu-latest + if: >- + github.event_name == 'workflow_dispatch' || + contains(github.event.head_commit.message, 'chore(version)') + + steps: + - name: Checkout dev + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: dev + token: ${{ secrets.MOKOGITEA_TOKEN }} + + - name: Configure git + 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: Merge dev into feature branches + run: | + echo "=== Syncing feature branches with dev ===" + + # Fetch all remote branches + git fetch origin + + # Find feature branches (feature/*, fix/*, patch/*, hotfix/*, bugfix/*, chore/*) + BRANCHES=$(git branch -r --list 'origin/feature/*' 'origin/fix/*' 'origin/patch/*' 'origin/hotfix/*' 'origin/bugfix/*' 'origin/chore/*' | sed 's|origin/||; s/^[[:space:]]*//') + + if [ -z "$BRANCHES" ]; then + echo "No feature branches found — nothing to sync" + exit 0 + fi + + SYNCED=0 + SKIPPED=0 + FAILED=0 + + for BRANCH in $BRANCHES; do + echo "" + echo "--- ${BRANCH} ---" + + # Skip branches that are already up to date with dev + if git merge-base --is-ancestor dev "origin/${BRANCH}" 2>/dev/null; then + echo "Already up to date" + SKIPPED=$((SKIPPED + 1)) + continue + fi + + # Try to merge dev into the branch + git checkout "origin/${BRANCH}" -B "$BRANCH" 2>/dev/null + if git merge dev --no-edit -m "chore: merge dev into ${BRANCH} (version sync) [skip ci]" 2>/dev/null; then + git push origin "$BRANCH" 2>/dev/null + echo "Synced successfully" + SYNCED=$((SYNCED + 1)) + else + git merge --abort 2>/dev/null || true + echo "Merge conflict — skipping (manual rebase needed)" + FAILED=$((FAILED + 1)) + fi + done + + # Return to dev + git checkout dev 2>/dev/null || true + + echo "" + echo "=== Summary ===" + echo "Synced: ${SYNCED}" + echo "Already current: ${SKIPPED}" + echo "Conflicts (skipped): ${FAILED}" + + if [ "$FAILED" -gt 0 ]; then + echo "::warning::${FAILED} branch(es) had merge conflicts and need manual rebase" + fi diff --git a/.mokogitea/workflows/workflow-sync-trigger.yml b/.mokogitea/workflows/workflow-sync-trigger.yml new file mode 100644 index 0000000..371910c --- /dev/null +++ b/.mokogitea/workflows/workflow-sync-trigger.yml @@ -0,0 +1,73 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: mokocli.Universal +# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml +# VERSION: 01.01.00 +# BRIEF: Trigger workflow sync to live repos when a PR is merged to main + +name: "Universal: Workflow Sync Trigger" + +on: + pull_request: + types: [closed] + branches: + - main + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + sync: + name: Sync workflows to live repos + runs-on: ubuntu-latest + if: >- + github.event.pull_request.merged == true && + !contains(github.event.pull_request.title, '[skip sync]') + + steps: + - name: Determine platform from repo name + id: platform + run: | + REPO="${{ github.event.repository.name }}" + case "$REPO" in + Template-Joomla) PLATFORM="joomla" ;; + Template-Dolibarr) PLATFORM="dolibarr" ;; + Template-Go) PLATFORM="go" ;; + Template-MCP) PLATFORM="mcp" ;; + Template-Generic) PLATFORM="" ;; + *) PLATFORM="" ;; + esac + echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT" + echo "Platform: ${PLATFORM:-all}" + + - name: Clone mokocli + env: + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + run: | + GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}" + git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli + + - name: Install dependencies + run: | + cd /tmp/mokocli + composer install --no-dev --no-interaction --quiet 2>/dev/null || true + + - name: Run workflow sync + env: + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + run: | + ARGS="--token ${MOKOGITEA_TOKEN}" + ARGS="${ARGS} --org ${{ vars.GITEA_ORG || github.repository_owner }}" + ARGS="${ARGS} --phase repos" + + PLATFORM="${{ steps.platform.outputs.platform }}" + if [ -n "$PLATFORM" ]; then + ARGS="${ARGS} --platform-filter ${PLATFORM}" + fi + + php /tmp/mokocli/cli/workflow_sync.php ${ARGS} diff --git a/.script-registry.json b/.script-registry.json index 7dad2a8..62786e2 100644 --- a/.script-registry.json +++ b/.script-registry.json @@ -1,7 +1,11 @@ { "metadata": { "generated_at": "2026-03-10T19:51:42.238134Z", +<<<<<<< HEAD "repository": "MokoConsulting/mokocli", +======= + "repository": "MokoConsulting/mokoplatform", +>>>>>>> main "version": "1.0.0" }, "scripts": [ diff --git a/CHANGELOG.md b/CHANGELOG.md index 28b5006..0fbb057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,15 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION +<<<<<<< HEAD DEFGROUP: MokoStandards.Root INGROUP: MokoStandards REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +DEFGROUP: MokoCli.Root +INGROUP: MokoCli +REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main PATH: /CHANGELOG.md BRIEF: Release changelog --> @@ -12,34 +18,32 @@ BRIEF: Release changelog # Changelog ## [Unreleased] +## [09.32.00] --- 2026-06-21 + +## [09.32.00] --- 2026-06-21 + +## [09.31.00] --- 2026-06-21 + +## [09.31.00] --- 2026-06-21 + +## [09.30.00] --- 2026-06-21 + +## [09.30.00] --- 2026-06-21 + ### Added -- `workflow_sync.php` — cascading workflow sync from Generic → platform templates → live repos based on manifest.platform -- `platform_detect.php` — auto-detect repo platform type (joomla/dolibarr/go/mcp/platform/generic) from file structure, optionally update manifest -- Version prefix support in `version_read.php` and `version_bump.php` — repos with `` in manifest (e.g. MokoGitea: `1.26.1+moko.`) get prefix-aware version scanning and bumping -- Platform types: joomla, dolibarr, go, mcp, platform, generic -- Template-Go and Template-MCP repos created +- `security:advisories` command — cross-repo security advisory aggregator (#150) + - Scans org repos for known CVEs via `composer audit` + - Aggregates results into a single report with severity breakdown + - Auto-creates tracking issues for critical/high vulnerabilities (`--create-issues`) + - Checkpoint-based resumability with `--resume` + - Export to JSON/CSV with `--export` ### Changed -- `auto-release.yml` — patch branches (fix/*, patch/*, hotfix/*, bugfix/*) use `--bump none` (pre-release already bumped); feature/dev branches bump minor -- `pre-release.yml` — triggers on push to dev, fix/**, patch/**, hotfix/**, bugfix/**, alpha, beta, rc branches -- Version format standardized: `[prefix]XX.YY.ZZ` in source files, suffix (`-dev`, `-rc`) added by release system only - -## [09.25.00] --- 2026-06-04 - -## [09.23] --- 2026-05-31 - -## [09.22] --- 2026-05-31 - -### Changed -- **refactor(cli):** migrate 64 legacy scripts to CliFramework (#235) — all tools in cli/, automation/, maintenance/, deploy/, release/ now extend CliFramework with free --help, --verbose, --quiet, --dry-run, --json, banners, and coloured logging - -### Fixed -- fix: auto-detect org/repo in updates_xml_build from manifest and git remote -- fix: restore hyphen in version suffixes -- fix: release names use standardized format -- fix: remove lesser stream copies, each stream updates independently -- fix: sort updates.xml entries dev first, stable last - -## [09.21] --- 2026-05-30 - -## [09.20] --- 2026-05-30 +- `manifest:read` rewritten to use Gitea manifest API as primary source (#283) + - Falls back to auto-detection from source tree (Joomla, Dolibarr, generic) + - No longer requires `.mokogitea/manifest.xml` file + - Backward-compatible field aliases for existing CI consumers +- Renamed `MokoStandards` namespace → `MokoCli` across all files +- Renamed `MokoEnterprise` namespace → `MokoCli` across all files +- Renamed `MokoStandardsParser` class → `ManifestParser` +- Fixed `composer.json` autoload paths: `src/` → `source/` diff --git a/PLUGIN_SCRIPTS.md b/PLUGIN_SCRIPTS.md index 516140f..3224935 100644 --- a/PLUGIN_SCRIPTS.md +++ b/PLUGIN_SCRIPTS.md @@ -2,16 +2,26 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION +<<<<<<< HEAD DEFGROUP: MokoCLI.Root INGROUP: MokoCLI REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +DEFGROUP: MokoPlatform.Root +INGROUP: MokoPlatform +REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main PATH: /PLUGIN_SCRIPTS.md BRIEF: Plugin system CLI documentation --> # Plugin System CLI Scripts +<<<<<<< HEAD Command-line scripts for validating, health checking, and managing projects using the MokoCLI plugin system. +======= +Command-line scripts for validating, health checking, and managing projects using the mokoplatform plugin system. +>>>>>>> main ## Available Scripts diff --git a/README.md b/README.md index e2b05d4..b8646cf 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION +<<<<<<< HEAD DEFGROUP: MokoCLI.Root INGROUP: MokoCLI REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli @@ -15,9 +16,24 @@ BRIEF: Project overview and documentation ![Version](https://img.shields.io/badge/version-09.01.00-blue) ![PHP](https://img.shields.io/badge/PHP-8.1%2B-777BB4) ![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green) PHP implementation of MokoCLI — enterprise standards, automation framework, workflow templates, and bulk sync tooling. +======= +DEFGROUP: MokoPlatform.Root +INGROUP: MokoPlatform +REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +PATH: /README.md +VERSION: 09.32.00 +BRIEF: Project overview and documentation +--> -> **Primary platform**: [Gitea — git.mokoconsulting.tech](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API) -> **Backup mirror**: [GitHub](https://github.com/MokoConsulting/MokoStandards-API) *(read-only mirror)* +# mokoplatform Enterprise API + +![Version](https://img.shields.io/badge/version-09.01.00-blue) ![PHP](https://img.shields.io/badge/PHP-8.1%2B-777BB4) ![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green) + +PHP implementation of mokoplatform — enterprise standards, automation framework, workflow templates, and bulk sync tooling. +>>>>>>> main + +> **Primary platform**: [Gitea — git.mokoconsulting.tech](https://git.mokoconsulting.tech/MokoConsulting/MokoCli-API) +> **Backup mirror**: [GitHub](https://github.com/MokoConsulting/MokoCli-API) *(read-only mirror)* ## What Lives Here diff --git a/analysis/index.md b/analysis/index.md index 4f1b0d9..4c83cf1 100644 --- a/analysis/index.md +++ b/analysis/index.md @@ -2,9 +2,15 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION +<<<<<<< HEAD DEFGROUP: MokoCLI.Index INGROUP: MokoCLI.Analysis REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +DEFGROUP: MokoPlatform.Index +INGROUP: MokoPlatform.Analysis +REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main PATH: /analysis/index.md BRIEF: Analysis directory index --> diff --git a/automation/bulk_joomla_template.php b/automation/bulk_joomla_template.php index b0846ef..840fa55 100644 --- a/automation/bulk_joomla_template.php +++ b/automation/bulk_joomla_template.php @@ -9,9 +9,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Automation * INGROUP: MokoCLI.Scripts * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Automation + * INGROUP: MokoPlatform.Scripts + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /automation/bulk_joomla_template.php * BRIEF: Bulk scaffold and sync Joomla template repositories * @@ -28,7 +34,7 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\{ +use MokoCli\{ AuditLogger, CliFramework, Config, @@ -42,7 +48,11 @@ use MokoEnterprise\{ * * Provides three operations for Joomla template projects: * --scaffold: Create a new template repository with the full directory structure +<<<<<<< HEAD * --sync: Push MokoCLI files to existing template repositories +======= + * --sync: Push mokoplatform files to existing template repositories +>>>>>>> main * --list: List all repositories tagged as joomla-template * * Works with both GitHub and Gitea via the PlatformAdapterFactory. @@ -318,7 +328,11 @@ class BulkJoomlaTemplate extends CliFramework $name, $path, $content, +<<<<<<< HEAD "chore: update {$path} from MokoCLI", +======= + "chore: update {$path} from mokoplatform", +>>>>>>> main $existingSha, $branch ); diff --git a/automation/bulk_sync.php b/automation/bulk_sync.php index c251f51..260f00e 100755 --- a/automation/bulk_sync.php +++ b/automation/bulk_sync.php @@ -9,9 +9,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Automation * INGROUP: MokoCLI.Scripts * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Automation + * INGROUP: MokoPlatform.Scripts + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /automation/bulk_sync.php * BRIEF: Enterprise-grade bulk repository synchronization */ @@ -21,7 +27,7 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\{ +use MokoCli\{ ApiClient, AuditLogger, CheckpointManager, @@ -42,7 +48,11 @@ use MokoEnterprise\{ /** * Bulk Repository Synchronization Tool * +<<<<<<< HEAD * Synchronizes MokoCLI files across multiple repositories using +======= + * Synchronizes mokoplatform files across multiple repositories using +>>>>>>> main * the Enterprise library for robust, audited operations. */ class BulkSync extends CliFramework @@ -95,7 +105,11 @@ class BulkSync extends CliFramework */ protected function run(): int { +<<<<<<< HEAD $this->log("🚀 MokoCLI Bulk Synchronization v" . self::VERSION, 'INFO'); +======= + $this->log("🚀 mokoplatform Bulk Synchronization v" . self::VERSION, 'INFO'); +>>>>>>> main // Initialize enterprise components if (!$this->initializeComponents()) { @@ -180,7 +194,11 @@ class BulkSync extends CliFramework $results['health'] = $this->runHealthChecksAll($org, $repositories); } +<<<<<<< HEAD // Create/update tracking issue in MokoCLI +======= + // Create/update tracking issue in mokoplatform +>>>>>>> main $this->createSyncIssue($org, $results); // Create/update a failure issue when any repos failed @@ -244,7 +262,11 @@ class BulkSync extends CliFramework * Filter repositories based on include/exclude lists */ /** Repositories that are permanently excluded from bulk sync. */ +<<<<<<< HEAD private const ALWAYS_EXCLUDE = ['MokoCLI', '.github-private']; +======= + private const ALWAYS_EXCLUDE = ['mokoplatform', '.github-private']; +>>>>>>> main private function filterRepositories(array $repositories, array $include, array $exclude): array { @@ -426,7 +448,11 @@ class BulkSync extends CliFramework $this->log("", 'ERROR'); $this->log("Required Implementation:", 'ERROR'); $this->log(" 1. Clone/fetch target repository", 'ERROR'); +<<<<<<< HEAD $this->log(" 2. Apply file updates based on MokoCLI configuration", 'ERROR'); +======= + $this->log(" 2. Apply file updates based on mokoplatform configuration", 'ERROR'); +>>>>>>> main $this->log(" 3. Create pull request with changes", 'ERROR'); $this->log(" 4. Handle merge conflicts and validation", 'ERROR'); $this->log("", 'ERROR'); @@ -837,7 +863,11 @@ class BulkSync extends CliFramework } /** +<<<<<<< HEAD * Ensure all standard MokoCLI labels exist on a target repository. +======= + * Ensure all standard mokoplatform labels exist on a target repository. +>>>>>>> main * * Fetches existing labels first (GET) and only POSTs the ones that are * missing. This avoids the 422 "already exists" responses that would @@ -872,7 +902,11 @@ class BulkSync extends CliFramework // Workflow / Process ['automation', '8B4513', 'Automated processes or scripts'], +<<<<<<< HEAD ['MokoCLI', 'B60205', 'MokoCLI compliance'], +======= + ['mokoplatform', 'B60205', 'mokoplatform compliance'], +>>>>>>> main ['needs-review', 'FBCA04', 'Awaiting code review'], ['work-in-progress', 'D93F0B', 'Work in progress, not ready for merge'], ['breaking-change', 'D73A4A', 'Breaking API or functionality change'], @@ -912,8 +946,13 @@ class BulkSync extends CliFramework ['health: poor', 'FF6B6B', 'Health score below 50'], // Sync / Automation (used by bulk_sync, scan_drift, check_repo_health) +<<<<<<< HEAD ['standards-update', 'B60205', 'MokoCLI sync update'], ['standards-drift', 'FBCA04', 'Repository drifted from MokoCLI'], +======= + ['standards-update', 'B60205', 'mokoplatform sync update'], + ['standards-drift', 'FBCA04', 'Repository drifted from mokoplatform'], +>>>>>>> main ['sync-report', '0075CA', 'Bulk sync run report'], ['sync-failure', 'D73A4A', 'Bulk sync failure requiring attention'], ['push-failure', 'D73A4A', 'File push failure requiring attention'], @@ -925,10 +964,17 @@ class BulkSync extends CliFramework ['type: version', '0E8A16', 'Version-related change'], ]; +<<<<<<< HEAD // Quick check: if the repo already has the 'MokoCLI' label, it was // provisioned previously — skip the expensive full label provisioning. try { $probe = $this->api->get("/repos/{$org}/{$repo}/labels/MokoCLI"); +======= + // Quick check: if the repo already has the 'mokoplatform' label, it was + // provisioned previously — skip the expensive full label provisioning. + try { + $probe = $this->api->get("/repos/{$org}/{$repo}/labels/mokoplatform"); +>>>>>>> main if (!empty($probe['name'])) { return; // already provisioned } @@ -1024,7 +1070,11 @@ class BulkSync extends CliFramework */ private function updateOpenBranches(string $org, string $repo): void { +<<<<<<< HEAD $syncBranchPrefix = 'chore/sync-MokoCLI-'; +======= + $syncBranchPrefix = 'chore/sync-mokoplatform-'; +>>>>>>> main try { $defaultBranch = 'main'; @@ -1055,7 +1105,11 @@ class BulkSync extends CliFramework $this->api->post("/repos/{$org}/{$repo}/merges", [ 'base' => $branch, 'head' => $defaultBranch, +<<<<<<< HEAD 'commit_message' => "chore: merge {$defaultBranch} into {$branch} (MokoCLI sync)", +======= + 'commit_message' => "chore: merge {$defaultBranch} into {$branch} (mokoplatform sync)", +>>>>>>> main ]); $this->log(" 🔀 Merged {$defaultBranch} → {$branch} (PR #{$prNum})", 'INFO'); } catch (\Exception $e) { @@ -1076,7 +1130,11 @@ class BulkSync extends CliFramework /** * Records which sync run touched the repo, the PR number, and the +<<<<<<< HEAD * MokoCLI version that was applied — giving each repo a clear audit +======= + * mokoplatform version that was applied — giving each repo a clear audit +>>>>>>> main * trail of what was changed and why. */ /** @@ -1119,6 +1177,7 @@ class BulkSync extends CliFramework $minor = self::VERSION_MINOR; $force = isset($this->options['force']) ? ' *(--force)*' : ''; $prLink = $this->adapter->getPullRequestWebUrl($org, $repo, $prNumber); +<<<<<<< HEAD $source = $this->adapter->getRepoWebUrl($org, 'MokoCLI'); $branchName = 'chore/sync-MokoCLI-v' . $minor; $branchLink = $this->adapter->getBranchWebUrl($org, $repo, $branchName); @@ -1129,6 +1188,18 @@ class BulkSync extends CliFramework ## MokoCLI Sync Applied A MokoCLI bulk sync run has updated files in this repository. +======= + $source = $this->adapter->getRepoWebUrl($org, 'mokoplatform'); + $branchName = 'chore/sync-mokoplatform-v' . $minor; + $branchLink = $this->adapter->getBranchWebUrl($org, $repo, $branchName); + + $title = "chore: mokoplatform v{$minor} sync tracking"; + + $body = <<>>>>>> main | Field | Value | |-------|-------| @@ -1144,13 +1215,21 @@ class BulkSync extends CliFramework Protected files (README, CHANGELOG, GOVERNANCE, etc.) were not overwritten. --- +<<<<<<< HEAD *Updated automatically by [MokoCLI]({$source}) `bulk_sync.php`* +======= + *Updated automatically by [mokoplatform]({$source}) `bulk_sync.php`* +>>>>>>> main MD; // Dedent heredoc $body = preg_replace('/^ /m', '', $body); +<<<<<<< HEAD $labelNames = ['standards-update', 'MokoCLI', 'type: chore', 'automation']; +======= + $labelNames = ['standards-update', 'mokoplatform', 'type: chore', 'automation']; +>>>>>>> main $labels = $this->resolveLabelIds($org, $repo, $labelNames); try { @@ -1213,7 +1292,11 @@ class BulkSync extends CliFramework } /** +<<<<<<< HEAD * Create a tracking issue in MokoCLI for this sync run. +======= + * Create a tracking issue in mokoplatform for this sync run. +>>>>>>> main */ private function createSyncIssue(string $org, array $results): void { @@ -1232,7 +1315,11 @@ class BulkSync extends CliFramework $issues = $results['issues'] ?? []; // Stable title — no timestamp so repeated runs update a single issue +<<<<<<< HEAD $title = "sync: MokoCLI v" . self::VERSION_MINOR . " bulk sync report"; +======= + $title = "sync: mokoplatform v" . self::VERSION_MINOR . " bulk sync report"; +>>>>>>> main $protection = $results['protection'] ?? []; $hasProtect = !empty($protection); @@ -1281,7 +1368,11 @@ class BulkSync extends CliFramework : "|---|---|---|---|"; $body = <<>>>>>> main **Organisation:** `{$org}` **Triggered:** {$now}{$force} @@ -1301,7 +1392,11 @@ class BulkSync extends CliFramework try { // Search for existing issue by label — any state so we can reopen closed ones +<<<<<<< HEAD $existing = $this->api->get("/repos/{$org}/MokoCLI/issues", [ +======= + $existing = $this->api->get("/repos/{$org}/mokoplatform/issues", [ +>>>>>>> main 'labels' => 'sync-report', 'state' => 'all', 'per_page' => 1, @@ -1309,8 +1404,13 @@ class BulkSync extends CliFramework 'direction' => 'desc', ]); +<<<<<<< HEAD $labelNames = ['sync-report', 'MokoCLI', 'type: chore', 'automation']; $labels = $this->resolveLabelIds($org, 'MokoCLI', $labelNames); +======= + $labelNames = ['sync-report', 'mokoplatform', 'type: chore', 'automation']; + $labels = $this->resolveLabelIds($org, 'mokoplatform', $labelNames); +>>>>>>> main $existing = array_values($existing); if (!empty($existing) && isset($existing[0]['number'])) { @@ -1319,6 +1419,7 @@ class BulkSync extends CliFramework if (($existing[0]['state'] ?? 'open') === 'closed') { $patch['state'] = 'open'; } +<<<<<<< HEAD $this->api->patch("/repos/{$org}/MokoCLI/issues/{$issueNumber}", $patch); try { $this->api->post("/repos/{$org}/MokoCLI/issues/{$issueNumber}/labels", ['labels' => $labels]); @@ -1328,13 +1429,28 @@ class BulkSync extends CliFramework $this->log("📋 Sync report issue updated: {$org}/MokoCLI#{$issueNumber}", 'INFO'); } else { $issue = $this->api->post("/repos/{$org}/MokoCLI/issues", [ +======= + $this->api->patch("/repos/{$org}/mokoplatform/issues/{$issueNumber}", $patch); + try { + $this->api->post("/repos/{$org}/mokoplatform/issues/{$issueNumber}/labels", ['labels' => $labels]); + } catch (\Exception $le) { +/* non-fatal */ + } + $this->log("📋 Sync report issue updated: {$org}/mokoplatform#{$issueNumber}", 'INFO'); + } else { + $issue = $this->api->post("/repos/{$org}/mokoplatform/issues", [ +>>>>>>> main 'title' => $title, 'body' => $body, 'labels' => $labels, 'assignees' => ['jmiller'], ]); $issueNumber = $issue['number'] ?? '?'; +<<<<<<< HEAD $this->log("📋 Sync report issue created: {$org}/MokoCLI#{$issueNumber}", 'INFO'); +======= + $this->log("📋 Sync report issue created: {$org}/mokoplatform#{$issueNumber}", 'INFO'); +>>>>>>> main } } catch (\Exception $e) { $this->log("⚠️ Failed to create/update sync report issue: " . $e->getMessage(), 'WARN'); @@ -1342,7 +1458,11 @@ class BulkSync extends CliFramework } /** +<<<<<<< HEAD * Create or update a failure issue in MokoCLI when repos fail to sync. +======= + * Create or update a failure issue in mokoplatform when repos fail to sync. +>>>>>>> main * Uses the 'sync-failure' label so it is distinct from the run-report issue. * Reopens a closed issue rather than creating a duplicate. */ @@ -1388,7 +1508,11 @@ class BulkSync extends CliFramework $body = preg_replace('/^ /m', '', $body); try { +<<<<<<< HEAD $existing = $this->api->get("/repos/{$org}/MokoCLI/issues", [ +======= + $existing = $this->api->get("/repos/{$org}/mokoplatform/issues", [ +>>>>>>> main 'labels' => 'sync-failure', 'state' => 'all', 'per_page' => 1, @@ -1403,6 +1527,7 @@ class BulkSync extends CliFramework if (($existing[0]['state'] ?? 'open') === 'closed') { $patch['state'] = 'open'; } +<<<<<<< HEAD $this->api->patch("/repos/{$org}/MokoCLI/issues/{$num}", $patch); $this->log("🚨 Failure issue #{$num} updated: {$org}/MokoCLI#{$num}", 'WARN'); } else { @@ -1414,6 +1539,19 @@ class BulkSync extends CliFramework ]); $num = $issue['number'] ?? '?'; $this->log("🚨 Failure issue created: {$org}/MokoCLI#{$num}", 'WARN'); +======= + $this->api->patch("/repos/{$org}/mokoplatform/issues/{$num}", $patch); + $this->log("🚨 Failure issue #{$num} updated: {$org}/mokoplatform#{$num}", 'WARN'); + } else { + $issue = $this->api->post("/repos/{$org}/mokoplatform/issues", [ + 'title' => $title, + 'body' => $body, + 'labels' => $this->resolveLabelIds($org, 'mokoplatform', ['sync-failure']), + 'assignees' => ['jmiller'], + ]); + $num = $issue['number'] ?? '?'; + $this->log("🚨 Failure issue created: {$org}/mokoplatform#{$num}", 'WARN'); +>>>>>>> main } } catch (\Exception $e) { $this->log("⚠️ Could not create/update failure issue: " . $e->getMessage(), 'WARN'); diff --git a/automation/enrich_manifest_xml.php b/automation/enrich_manifest_xml.php index 3039c62..bba0c43 100644 --- a/automation/enrich_manifest_xml.php +++ b/automation/enrich_manifest_xml.php @@ -6,9 +6,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Automation * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Automation + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /automation/enrich_manifest_xml.php * BRIEF: Enrich XML manifests with repo-specific build and deploy details * @@ -21,8 +27,8 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; -use MokoEnterprise\MokoStandardsParser; +use MokoCli\CliFramework; +use MokoCli\ManifestParser; class EnrichManifestXmlCli extends CliFramework { @@ -43,10 +49,14 @@ class EnrichManifestXmlCli extends CliFramework $skipStr = $this->getArgument('--skip'); $skipRepos = $skipStr !== '' ? array_map('trim', explode(',', $skipStr)) : []; - $parser = new MokoStandardsParser(); + $parser = new ManifestParser(); $tmpBase = sys_get_temp_dir() . '/moko-enrich-' . getmypid(); +<<<<<<< HEAD echo "=== MokoCLI XML Manifest Enrichment ===\n"; +======= + echo "=== mokoplatform XML Manifest Enrichment ===\n"; +>>>>>>> main echo "Mode: " . ($this->dryRun ? "DRY RUN" : "LIVE") . "\n"; if (!empty($skipRepos)) { echo "Skipping: " . implode(', ', $skipRepos) . "\n"; @@ -97,7 +107,11 @@ class EnrichManifestXmlCli extends CliFramework } $manifestPath = "{$workDir}/.mokogitea/manifest.xml"; +<<<<<<< HEAD if (!file_exists($manifestPath) || !str_contains(file_get_contents($manifestPath), '>>>>>> main echo "SKIP (no XML manifest)\n"; $stats['skipped']++; $this->rmTree($workDir); @@ -113,8 +127,8 @@ class EnrichManifestXmlCli extends CliFramework } $enrichment['build']['language'] = $enrichment['build']['language'] ?? $repo['language'] - ?? MokoStandardsParser::platformLanguage($platform); - $enrichment['build']['package_type'] = $enrichment['build']['package_type'] ?? MokoStandardsParser::platformPackageType($platform); + ?? ManifestParser::platformLanguage($platform); + $enrichment['build']['package_type'] = $enrichment['build']['package_type'] ?? ManifestParser::platformPackageType($platform); $enrichedXml = $this->enrichManifestXml($existingXml, $enrichment); $dc = count($enrichment['deploy'] ?? []); @@ -312,7 +326,7 @@ class EnrichManifestXmlCli extends CliFramework return $xml; } - $ns = MokoStandardsParser::NAMESPACE_URI; + $ns = ManifestParser::NAMESPACE_URI; $root = $dom->documentElement; foreach (['build', 'deploy', 'scripts'] as $tag) { diff --git a/automation/enrich_mokostandards_xml.php b/automation/enrich_mokostandards_xml.php index 854bca5..08c8b83 100644 --- a/automation/enrich_mokostandards_xml.php +++ b/automation/enrich_mokostandards_xml.php @@ -6,9 +6,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Automation * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Automation + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /automation/enrich_mokostandards_xml.php * BRIEF: Enrich XML manifests with repo-specific build and deploy details * @@ -21,8 +27,8 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; -use MokoEnterprise\MokoStandardsParser; +use MokoCli\CliFramework; +use MokoCli\ManifestParser; class EnrichMokostandardsXmlCli extends CliFramework { @@ -43,10 +49,14 @@ class EnrichMokostandardsXmlCli extends CliFramework $skipStr = $this->getArgument('--skip'); $skipRepos = $skipStr !== '' ? array_map('trim', explode(',', $skipStr)) : []; - $parser = new MokoStandardsParser(); + $parser = new ManifestParser(); $tmpBase = sys_get_temp_dir() . '/moko-enrich-' . getmypid(); +<<<<<<< HEAD echo "=== MokoCLI XML Manifest Enrichment ===\n"; +======= + echo "=== mokoplatform XML Manifest Enrichment ===\n"; +>>>>>>> main echo "Mode: " . ($this->dryRun ? "DRY RUN" : "LIVE") . "\n"; if (!empty($skipRepos)) { echo "Skipping: " . implode(', ', $skipRepos) . "\n"; @@ -97,7 +107,11 @@ class EnrichMokostandardsXmlCli extends CliFramework } $manifestPath = "{$workDir}/.mokogitea/manifest.xml"; +<<<<<<< HEAD if (!file_exists($manifestPath) || !str_contains(file_get_contents($manifestPath), '>>>>>> main echo "SKIP (no XML manifest)\n"; $stats['skipped']++; $this->rmTree($workDir); @@ -113,8 +127,8 @@ class EnrichMokostandardsXmlCli extends CliFramework } $enrichment['build']['language'] = $enrichment['build']['language'] ?? $repo['language'] - ?? MokoStandardsParser::platformLanguage($platform); - $enrichment['build']['package_type'] = $enrichment['build']['package_type'] ?? MokoStandardsParser::platformPackageType($platform); + ?? ManifestParser::platformLanguage($platform); + $enrichment['build']['package_type'] = $enrichment['build']['package_type'] ?? ManifestParser::platformPackageType($platform); $enrichedXml = $this->enrichManifestXml($existingXml, $enrichment); $dc = count($enrichment['deploy'] ?? []); @@ -315,7 +329,7 @@ class EnrichMokostandardsXmlCli extends CliFramework return $xml; } - $ns = MokoStandardsParser::NAMESPACE_URI; + $ns = ManifestParser::NAMESPACE_URI; $root = $dom->documentElement; foreach (['build', 'deploy', 'scripts'] as $tag) { diff --git a/automation/index.md b/automation/index.md index 6e07aaa..6c5a30c 100644 --- a/automation/index.md +++ b/automation/index.md @@ -2,9 +2,15 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION +<<<<<<< HEAD DEFGROUP: MokoCLI.Index INGROUP: MokoCLI.Automation REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +DEFGROUP: MokoPlatform.Index +INGROUP: MokoPlatform.Automation +REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main PATH: /automation/index.md BRIEF: Automation directory index --> diff --git a/automation/migrate_to_gitea.php b/automation/migrate_to_gitea.php index c2497bf..b1a8a32 100644 --- a/automation/migrate_to_gitea.php +++ b/automation/migrate_to_gitea.php @@ -8,16 +8,26 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Automation * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Automation + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /automation/migrate_to_gitea.php * BRIEF: Migrate repositories from GitHub to self-hosted Gitea instance * * USAGE * php automation/migrate_to_gitea.php --dry-run * php automation/migrate_to_gitea.php --repos MokoCRM MokoDoliMods +<<<<<<< HEAD * php automation/migrate_to_gitea.php --exclude MokoCLI --skip-archived +======= + * php automation/migrate_to_gitea.php --exclude mokoplatform --skip-archived +>>>>>>> main * php automation/migrate_to_gitea.php --resume */ @@ -25,12 +35,12 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; -use MokoEnterprise\CheckpointManager; -use MokoEnterprise\CliFramework; -use MokoEnterprise\Config; -use MokoEnterprise\PlatformAdapterFactory; -use MokoEnterprise\GitHubAdapter; -use MokoEnterprise\MokoGiteaAdapter; +use MokoCli\CheckpointManager; +use MokoCli\CliFramework; +use MokoCli\Config; +use MokoCli\PlatformAdapterFactory; +use MokoCli\GitHubAdapter; +use MokoCli\MokoGiteaAdapter; /** * Gitea Migration Script @@ -278,7 +288,11 @@ class MigrateToGitea extends CliFramework try { $this->gitea->createIssue( $giteaOrg, +<<<<<<< HEAD 'MokoCLI', +======= + 'mokoplatform', +>>>>>>> main 'chore: GitHub → Gitea migration report — ' . count($results['migrated']) . ' repos migrated', $report, ['labels' => ['automation', 'type: chore']] diff --git a/automation/push_files.php b/automation/push_files.php index b84b9e2..0332c9e 100644 --- a/automation/push_files.php +++ b/automation/push_files.php @@ -9,9 +9,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Automation * INGROUP: MokoCLI.Scripts * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Automation + * INGROUP: MokoPlatform.Scripts + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /automation/push_files.php * BRIEF: Push one or more specific files to one or more remote repositories */ @@ -21,7 +27,7 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\{ +use MokoCli\{ ApiClient, AuditLogger, CliFramework, @@ -35,7 +41,11 @@ use MokoEnterprise\{ /** * Targeted File Push Tool * +<<<<<<< HEAD * Pushes one or more specific files from MokoCLI templates to one or +======= + * Pushes one or more specific files from mokoplatform templates to one or +>>>>>>> main * more remote repositories — without running a full sync. * * Files are specified by their destination path as they appear in the target @@ -81,7 +91,11 @@ class PushFiles extends CliFramework */ protected function run(): int { +<<<<<<< HEAD $this->log('📦 MokoCLI File Push v' . self::VERSION, 'INFO'); +======= + $this->log('📦 mokoplatform File Push v' . self::VERSION, 'INFO'); +>>>>>>> main if (!$this->initializeComponents()) { return 1; @@ -337,7 +351,11 @@ class PushFiles extends CliFramework $prNumber = null; if (!$direct) { +<<<<<<< HEAD $prTitle = "chore: push " . count($entries) . " file(s) from MokoCLI"; +======= + $prTitle = "chore: push " . count($entries) . " file(s) from mokoplatform"; +>>>>>>> main $prBody = $this->buildPRBody($entries); $pr = $this->adapter->createPullRequest( $org, @@ -414,7 +432,11 @@ class PushFiles extends CliFramework $message = !empty($customMessage) ? $customMessage +<<<<<<< HEAD : "chore: update {$destPath} from MokoCLI"; +======= + : "chore: update {$destPath} from mokoplatform"; +>>>>>>> main // Fetch existing file SHA (needed for updates) $existingSha = null; @@ -457,9 +479,15 @@ class PushFiles extends CliFramework ): void { $now = gmdate('Y-m-d H:i:s') . ' UTC'; $version = self::VERSION; +<<<<<<< HEAD $source = $this->adapter->getRepoWebUrl($org, 'MokoCLI'); $title = "chore: MokoCLI file push tracking"; +======= + $source = $this->adapter->getRepoWebUrl($org, 'mokoplatform'); + + $title = "chore: mokoplatform file push tracking"; +>>>>>>> main $deliveryLine = $prNumber !== null ? "| **Pull request** | [#{$prNumber}](" . $this->adapter->getPullRequestWebUrl($org, $repo, $prNumber) . ") |" @@ -471,9 +499,15 @@ class PushFiles extends CliFramework )); $body = <<>>>>>> main | Field | Value | |-------|-------| @@ -487,12 +521,20 @@ class PushFiles extends CliFramework {$fileRows} --- +<<<<<<< HEAD *Generated automatically by [MokoCLI]({$source}) `push_files.php`* +======= + *Generated automatically by [mokoplatform]({$source}) `push_files.php`* +>>>>>>> main MD; $body = preg_replace('/^ /m', '', $body); +<<<<<<< HEAD $labels = ['standards-update', 'MokoCLI', 'type: chore', 'automation']; +======= + $labels = ['standards-update', 'mokoplatform', 'type: chore', 'automation']; +>>>>>>> main try { $existing = $this->api->get("/repos/{$org}/{$repo}/issues", [ @@ -550,7 +592,11 @@ class PushFiles extends CliFramework } /** +<<<<<<< HEAD * Create or update a failure issue in MokoCLI when repos fail to receive files. +======= + * Create or update a failure issue in mokoplatform when repos fail to receive files. +>>>>>>> main * Uses the 'push-failure' label. Reopens a closed issue rather than creating a duplicate. */ private function createFailureIssue(string $org, array $results): void @@ -598,7 +644,11 @@ class PushFiles extends CliFramework $body = preg_replace('/^ /m', '', $body); try { +<<<<<<< HEAD $existing = $this->api->get("/repos/{$org}/MokoCLI/issues", [ +======= + $existing = $this->api->get("/repos/{$org}/mokoplatform/issues", [ +>>>>>>> main 'labels' => 'push-failure', 'state' => 'all', 'per_page' => 1, @@ -613,17 +663,28 @@ class PushFiles extends CliFramework if (($existing[0]['state'] ?? 'open') === 'closed') { $patch['state'] = 'open'; } +<<<<<<< HEAD $this->api->patch("/repos/{$org}/MokoCLI/issues/{$num}", $patch); $this->log("🚨 Failure issue #{$num} updated: {$org}/MokoCLI#{$num}", 'WARN'); } else { $issue = $this->api->post("/repos/{$org}/MokoCLI/issues", [ +======= + $this->api->patch("/repos/{$org}/mokoplatform/issues/{$num}", $patch); + $this->log("🚨 Failure issue #{$num} updated: {$org}/mokoplatform#{$num}", 'WARN'); + } else { + $issue = $this->api->post("/repos/{$org}/mokoplatform/issues", [ +>>>>>>> main 'title' => $title, 'body' => $body, 'labels' => ['push-failure'], 'assignees' => ['jmiller'], ]); $num = $issue['number'] ?? '?'; +<<<<<<< HEAD $this->log("🚨 Failure issue created: {$org}/MokoCLI#{$num}", 'WARN'); +======= + $this->log("🚨 Failure issue created: {$org}/mokoplatform#{$num}", 'WARN'); +>>>>>>> main } } catch (\Exception $e) { $this->log("⚠️ Could not create/update failure issue: " . $e->getMessage(), 'WARN'); @@ -638,14 +699,23 @@ class PushFiles extends CliFramework private function buildPRBody(array $entries): string { $now = gmdate('Y-m-d H:i:s') . ' UTC'; +<<<<<<< HEAD $lines = ["## MokoCLI File Push\n", "**Pushed:** {$now}\n", '### Files\n']; +======= + $lines = ["## mokoplatform File Push\n", "**Pushed:** {$now}\n", '### Files\n']; +>>>>>>> main foreach ($entries as $entry) { $lines[] = "- `{$entry['destination']}`"; } +<<<<<<< HEAD $sourceUrl = $this->adapter->getRepoWebUrl(self::DEFAULT_ORG, 'MokoCLI'); $lines[] = "\n---\n*Generated by [MokoCLI]({$sourceUrl}) `push_files.php`*"; +======= + $sourceUrl = $this->adapter->getRepoWebUrl(self::DEFAULT_ORG, 'mokoplatform'); + $lines[] = "\n---\n*Generated by [mokoplatform]({$sourceUrl}) `push_files.php`*"; +>>>>>>> main return implode("\n", $lines); } diff --git a/automation/push_manifest_xml.php b/automation/push_manifest_xml.php index 815395b..5fdbb81 100644 --- a/automation/push_manifest_xml.php +++ b/automation/push_manifest_xml.php @@ -6,9 +6,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Automation * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Automation + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /automation/push_manifest_xml.php * BRIEF: Push XML manifests to all governed repositories */ @@ -18,8 +24,8 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; -use MokoEnterprise\MokoStandardsParser; +use MokoCli\CliFramework; +use MokoCli\ManifestParser; class PushManifestXmlCli extends CliFramework { @@ -44,10 +50,14 @@ class PushManifestXmlCli extends CliFramework $skipStr = $this->getArgument('--skip'); $skipRepos = $skipStr !== '' ? array_map('trim', explode(',', $skipStr)) : []; - $parser = new MokoStandardsParser(); + $parser = new ManifestParser(); $tmpBase = sys_get_temp_dir() . '/moko-manifest-push-' . getmypid(); +<<<<<<< HEAD echo "=== MokoCLI XML Manifest Push ===\n"; +======= + echo "=== mokoplatform XML Manifest Push ===\n"; +>>>>>>> main echo "Org: {$giteaOrg}\n"; echo "Mode: " . ($this->dryRun ? "DRY RUN" : "LIVE") . "\n"; if ($repoFilter) { @@ -97,8 +107,8 @@ class PushManifestXmlCli extends CliFramework 'description' => $repo['description'] ?? '', 'license' => 'GPL-3.0-or-later', 'topics' => $repo['topics'] ?? [], - 'language' => $repo['language'] ?? MokoStandardsParser::platformLanguage($platform), - 'package_type' => MokoStandardsParser::platformPackageType($platform), + 'language' => $repo['language'] ?? ManifestParser::platformLanguage($platform), + 'package_type' => ManifestParser::platformPackageType($platform), 'last_synced' => date('c'), ]); @@ -125,7 +135,11 @@ class PushManifestXmlCli extends CliFramework // Check if already XML and up-to-date $manifestPath = "{$workDir}/.mokogitea/manifest.xml"; +<<<<<<< HEAD $existingIsXml = file_exists($manifestPath) && str_contains(file_get_contents($manifestPath), '>>>>>> main if ($existingIsXml && !$force) { $existingPlatform = $parser->extractPlatform(file_get_contents($manifestPath)); if ($existingPlatform === $platform) { diff --git a/automation/push_mokostandards_xml.php b/automation/push_mokostandards_xml.php index 19fa8df..28e49ec 100644 --- a/automation/push_mokostandards_xml.php +++ b/automation/push_mokostandards_xml.php @@ -6,9 +6,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Automation * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Automation + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /automation/push_mokostandards_xml.php * BRIEF: Push XML manifests to all governed repositories */ @@ -18,8 +24,8 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; -use MokoEnterprise\MokoStandardsParser; +use MokoCli\CliFramework; +use MokoCli\ManifestParser; class PushMokostandardsXmlCli extends CliFramework { @@ -44,10 +50,14 @@ class PushMokostandardsXmlCli extends CliFramework $skipStr = $this->getArgument('--skip'); $skipRepos = $skipStr !== '' ? array_map('trim', explode(',', $skipStr)) : []; - $parser = new MokoStandardsParser(); + $parser = new ManifestParser(); $tmpBase = sys_get_temp_dir() . '/moko-manifest-push-' . getmypid(); +<<<<<<< HEAD echo "=== MokoCLI XML Manifest Push ===\n"; +======= + echo "=== mokoplatform XML Manifest Push ===\n"; +>>>>>>> main echo "Org: {$giteaOrg}\n"; echo "Mode: " . ($this->dryRun ? "DRY RUN" : "LIVE") . "\n"; if ($repoFilter) { @@ -97,8 +107,8 @@ class PushMokostandardsXmlCli extends CliFramework 'description' => $repo['description'] ?? '', 'license' => 'GPL-3.0-or-later', 'topics' => $repo['topics'] ?? [], - 'language' => $repo['language'] ?? MokoStandardsParser::platformLanguage($platform), - 'package_type' => MokoStandardsParser::platformPackageType($platform), + 'language' => $repo['language'] ?? ManifestParser::platformLanguage($platform), + 'package_type' => ManifestParser::platformPackageType($platform), 'last_synced' => date('c'), ]); @@ -125,7 +135,11 @@ class PushMokostandardsXmlCli extends CliFramework // Check if already XML and up-to-date $manifestPath = "{$workDir}/.mokogitea/manifest.xml"; +<<<<<<< HEAD $existingIsXml = file_exists($manifestPath) && str_contains(file_get_contents($manifestPath), '>>>>>> main if ($existingIsXml && !$force) { $existingPlatform = $parser->extractPlatform(file_get_contents($manifestPath)); if ($existingPlatform === $platform) { diff --git a/automation/repo_cleanup.php b/automation/repo_cleanup.php index 645739e..974a4ba 100644 --- a/automation/repo_cleanup.php +++ b/automation/repo_cleanup.php @@ -9,9 +9,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Automation * INGROUP: MokoCLI.Scripts * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Automation + * INGROUP: MokoPlatform.Scripts + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /automation/repo_cleanup.php * BRIEF: Enterprise repository cleanup — branches, PRs, issues, workflows, labels, logs */ @@ -21,7 +27,7 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\{ApiClient, AuditLogger, CliFramework, Config, GitPlatformAdapter, MetricsCollector, PlatformAdapterFactory}; +use MokoCli\{ApiClient, AuditLogger, CliFramework, Config, GitPlatformAdapter, MetricsCollector, PlatformAdapterFactory}; /** * Enterprise Repository Cleanup @@ -39,14 +45,23 @@ use MokoEnterprise\{ApiClient, AuditLogger, CliFramework, Config, GitPlatformAda class RepoCleanup extends CliFramework { private const VERSION = '09.23.00'; +<<<<<<< HEAD private const SYNC_PREFIX = 'chore/sync-MokoCLI-'; private const CURRENT_BRANCH = 'chore/sync-MokoCLI-v04.02.00'; +======= + private const SYNC_PREFIX = 'chore/sync-mokoplatform-'; + private const CURRENT_BRANCH = 'chore/sync-mokoplatform-v04.02.00'; +>>>>>>> main /** Workflow files that have been retired and should be deleted from governed repos. */ private const RETIRED_WORKFLOWS = [ 'build.yml', 'code-quality.yml', 'release-cycle.yml', 'release-pipeline.yml', 'branch-cleanup.yml', 'auto-update-changelog.yml', 'enterprise-issue-manager.yml', +<<<<<<< HEAD 'flush-actions-cache.yml', 'MokoCLI-script-runner.yml', 'unified-ci.yml', +======= + 'flush-actions-cache.yml', 'mokoplatform-script-runner.yml', 'unified-ci.yml', +>>>>>>> main 'unified-platform-testing.yml', 'reusable-build.yml', 'reusable-ci-validation.yml', 'reusable-deploy.yml', 'reusable-php-quality.yml', 'reusable-platform-testing.yml', 'reusable-project-detector.yml', 'reusable-release.yml', 'reusable-script-executor.yml', @@ -98,7 +113,11 @@ class RepoCleanup extends CliFramework } +<<<<<<< HEAD $this->logMsg("🧹 MokoCLI Repository Cleanup v" . self::VERSION); +======= + $this->logMsg("🧹 mokoplatform Repository Cleanup v" . self::VERSION); +>>>>>>> main $this->logMsg("Organization: {$org}"); $this->logMsg("Current sync branch: " . self::CURRENT_BRANCH); if ($this->dryRun) { @@ -225,7 +244,11 @@ class RepoCleanup extends CliFramework } $allRepos = $this->adapter->listOrgRepos($org, $skipArchived); +<<<<<<< HEAD return array_filter($allRepos, fn($r) => !in_array($r['name'], ['MokoCLI', '.github-private'], true)); +======= + return array_filter($allRepos, fn($r) => !in_array($r['name'], ['mokoplatform', '.github-private'], true)); +>>>>>>> main } // ─── Cleanup operations ────────────────────────────────────────────── @@ -463,9 +486,15 @@ class RepoCleanup extends CliFramework private function checkLabels(string $org, string $repo, array &$results): void { try { +<<<<<<< HEAD $this->api->get("/repos/{$org}/{$repo}/labels/MokoCLI"); } catch (\Exception $e) { $this->logMsg(" ⚠️ Missing 'MokoCLI' label"); +======= + $this->api->get("/repos/{$org}/{$repo}/labels/mokoplatform"); + } catch (\Exception $e) { + $this->logMsg(" ⚠️ Missing 'mokoplatform' label"); +>>>>>>> main $results['labels_missing']++; $this->api->resetCircuitBreaker(); } @@ -479,7 +508,11 @@ class RepoCleanup extends CliFramework if (preg_match('/^\s*VERSION:\s*(\d{2}\.\d{2}\.\d{2})/m', $content, $m)) { $version = $m[1]; +<<<<<<< HEAD // Check manifest.xml for the tracked MokoCLI version +======= + // Check manifest.xml for the tracked mokoplatform version +>>>>>>> main try { $mokoFile = $this->api->get("/repos/{$org}/{$repo}/contents/.mokogitea/manifest.xml"); $mokoContent = base64_decode($mokoFile['content'] ?? ''); diff --git a/bin/moko b/bin/moko index 89e8775..e8aa87e 100644 --- a/bin/moko +++ b/bin/moko @@ -9,11 +9,19 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /bin/moko * BRIEF: Unified CLI dispatcher — run any MokoCLI script without needing GitHub Actions +======= + * DEFGROUP: MokoCli.CLI + * INGROUP: MokoCli + * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform + * PATH: /bin/moko + * BRIEF: Unified CLI dispatcher — run any MokoCli script without needing GitHub Actions +>>>>>>> main * * USAGE * php bin/moko [options] (all platforms) @@ -220,6 +228,9 @@ const COMMAND_MAP = [ // Licensing 'license' => 'cli/license_manage.php', + // Security + 'security:advisories' => 'security/advisory_scan.php', + // Shell completion 'completion' => 'cli/completion.php', @@ -292,10 +303,17 @@ function printHelp(): void { echo <<<'HELP' ╔══════════════════════════════════════════════════════════╗ +<<<<<<< HEAD ║ MokoCLI (bin/moko) ║ ╚══════════════════════════════════════════════════════════╝ Run any MokoCLI script locally without GitHub Actions. +======= +║ MokoCli CLI (bin/moko) ║ +╚══════════════════════════════════════════════════════════╝ + +Run any MokoCli script locally without GitHub Actions. +>>>>>>> main USAGE php bin/moko [options] (all platforms) @@ -397,7 +415,7 @@ function loadPluginCommands(): array $commands = []; foreach (glob("{$pluginDir}/*Plugin.php") as $file) { - $className = 'MokoEnterprise\\Plugins\\' + $className = 'MokoCli\\Plugins\\' . pathinfo($file, PATHINFO_FILENAME); if (!class_exists($className)) { diff --git a/cli/archive_repo.php b/cli/archive_repo.php index b64f9b5..4da4628 100644 --- a/cli/archive_repo.php +++ b/cli/archive_repo.php @@ -8,9 +8,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /cli/archive_repo.php * BRIEF: Gracefully retire a governed repository — archive, close issues/PRs, remove sync def */ @@ -20,9 +26,9 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; -use MokoEnterprise\Config; -use MokoEnterprise\PlatformAdapterFactory; +use MokoCli\CliFramework; +use MokoCli\Config; +use MokoCli\PlatformAdapterFactory; class ArchiveRepoCli extends CliFramework { @@ -135,7 +141,11 @@ class ArchiveRepoCli extends CliFramework try { $issue = $adapter->createIssue( $org, +<<<<<<< HEAD 'MokoCLI', +======= + 'mokoplatform', +>>>>>>> main "chore: archived repository {$repoName}", "## Repository Archived\n\n" . "**Repository:** `{$org}/{$repoName}`\n" @@ -150,7 +160,11 @@ class ArchiveRepoCli extends CliFramework ] ); if (isset($issue['number'])) { +<<<<<<< HEAD echo " Archival record: MokoCLI#{$issue['number']}\n"; +======= + echo " Archival record: mokoplatform#{$issue['number']}\n"; +>>>>>>> main } } catch (\Exception $e) { echo " Warning: could not create archival record: " . $e->getMessage() . "\n"; diff --git a/cli/audit_query.php b/cli/audit_query.php index 8bfe715..f2e17f7 100644 --- a/cli/audit_query.php +++ b/cli/audit_query.php @@ -14,9 +14,15 @@ * (at your option) any later version. * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.CLI * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.CLI + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /cli/audit_query.php * BRIEF: Search, filter, and export audit logs */ @@ -25,7 +31,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; /** * CLI tool to search, filter, and export audit logs. diff --git a/cli/badge_update.php b/cli/badge_update.php index e385c89..70a6de5 100644 --- a/cli/badge_update.php +++ b/cli/badge_update.php @@ -6,9 +6,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /cli/badge_update.php * BRIEF: Update [VERSION: XX.XX.XX] badges in all markdown files */ @@ -17,7 +23,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class BadgeUpdateCli extends CliFramework { diff --git a/cli/branch_rename.php b/cli/branch_rename.php index 8b9818d..6e81262 100644 --- a/cli/branch_rename.php +++ b/cli/branch_rename.php @@ -6,11 +6,19 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/branch_rename.php * VERSION: 09.25.05 +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform + * PATH: /cli/branch_rename.php + * VERSION: 09.32.00 +>>>>>>> main * BRIEF: Rename a git branch via Gitea API (create new, update PR, delete old) */ @@ -18,7 +26,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class BranchRenameCli extends CliFramework { diff --git a/cli/bulk_workflow_push.php b/cli/bulk_workflow_push.php index 82a80a0..8c47701 100644 --- a/cli/bulk_workflow_push.php +++ b/cli/bulk_workflow_push.php @@ -8,11 +8,19 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/bulk_workflow_push.php * VERSION: 09.25.05 +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform + * PATH: /cli/bulk_workflow_push.php + * VERSION: 09.32.00 +>>>>>>> main * BRIEF: Push a workflow file to all governed repos via the Gitea Contents API */ @@ -20,7 +28,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class BulkWorkflowPushCli extends CliFramework { @@ -154,7 +162,11 @@ class BulkWorkflowPushCli extends CliFramework 'content' => $encodedContent, 'sha' => $remoteSha, 'message' => "chore: sync {$destPath} " +<<<<<<< HEAD . "from MokoCLI [skip ci]", +======= + . "from mokoplatform [skip ci]", +>>>>>>> main 'branch' => $branch, ]); @@ -184,7 +196,11 @@ class BulkWorkflowPushCli extends CliFramework $payload = json_encode([ 'content' => $encodedContent, 'message' => "chore: add {$destPath} " +<<<<<<< HEAD . "from MokoCLI [skip ci]", +======= + . "from mokoplatform [skip ci]", +>>>>>>> main 'branch' => $branch, ]); diff --git a/cli/bulk_workflow_trigger.php b/cli/bulk_workflow_trigger.php index 57f7040..4b4c41c 100644 --- a/cli/bulk_workflow_trigger.php +++ b/cli/bulk_workflow_trigger.php @@ -8,11 +8,19 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/bulk_workflow_trigger.php * VERSION: 09.25.05 +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform + * PATH: /cli/bulk_workflow_trigger.php + * VERSION: 09.32.00 +>>>>>>> main * BRIEF: Trigger a workflow across multiple repos at once */ @@ -20,7 +28,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class BulkWorkflowTriggerCli extends CliFramework { diff --git a/cli/changelog_promote.php b/cli/changelog_promote.php index d952950..0c75069 100644 --- a/cli/changelog_promote.php +++ b/cli/changelog_promote.php @@ -6,9 +6,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /cli/changelog_promote.php * BRIEF: Promote [Unreleased] section in CHANGELOG.md to a versioned entry */ @@ -17,7 +23,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class ChangelogPromoteCli extends CliFramework { diff --git a/cli/changelog_prune.php b/cli/changelog_prune.php index 318fc3d..42a3160 100644 --- a/cli/changelog_prune.php +++ b/cli/changelog_prune.php @@ -6,9 +6,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /cli/changelog_prune.php * BRIEF: Prune old CHANGELOG.md entries — keeps [Unreleased] + last N releases */ @@ -17,7 +23,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class ChangelogPruneCli extends CliFramework { diff --git a/cli/client_dashboard.php b/cli/client_dashboard.php index 55187f8..0d296ad 100644 --- a/cli/client_dashboard.php +++ b/cli/client_dashboard.php @@ -8,11 +8,19 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/client_dashboard.php * VERSION: 09.25.05 +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform + * PATH: /cli/client_dashboard.php + * VERSION: 09.32.00 +>>>>>>> main * BRIEF: Generate unified client dashboard HTML */ @@ -20,7 +28,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class ClientDashboardCli extends CliFramework { diff --git a/cli/client_health_check.php b/cli/client_health_check.php index d8cec51..cf92f7f 100644 --- a/cli/client_health_check.php +++ b/cli/client_health_check.php @@ -6,9 +6,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /cli/client_health_check.php * BRIEF: Verify a client site's update server, installed version, and release availability */ @@ -17,7 +23,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class ClientHealthCheckCli extends CliFramework { diff --git a/cli/client_inventory.php b/cli/client_inventory.php index 46778f7..4fcb199 100644 --- a/cli/client_inventory.php +++ b/cli/client_inventory.php @@ -8,11 +8,19 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/client_inventory.php * VERSION: 09.25.05 +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform + * PATH: /cli/client_inventory.php + * VERSION: 09.32.00 +>>>>>>> main * BRIEF: Discover and list all client-waas repos with their server configuration status */ @@ -20,7 +28,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class ClientInventoryCli extends CliFramework { diff --git a/cli/client_provision.php b/cli/client_provision.php index ff88290..6dcfac8 100644 --- a/cli/client_provision.php +++ b/cli/client_provision.php @@ -8,11 +8,19 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/client_provision.php * VERSION: 09.25.05 +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform + * PATH: /cli/client_provision.php + * VERSION: 09.32.00 +>>>>>>> main * BRIEF: Provision a new client environment end-to-end */ @@ -20,7 +28,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class ClientProvisionCli extends CliFramework { diff --git a/cli/completion.php b/cli/completion.php index d8354ca..093b436 100644 --- a/cli/completion.php +++ b/cli/completion.php @@ -6,9 +6,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /cli/completion.php * BRIEF: Generate bash/zsh tab completion scripts for bin/moko */ @@ -17,7 +23,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class CompletionCli extends CliFramework { diff --git a/cli/create_project.php b/cli/create_project.php index 5440801..f6853c7 100644 --- a/cli/create_project.php +++ b/cli/create_project.php @@ -8,9 +8,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /cli/create_project.php * BRIEF: Create baseline GitHub Projects for repositories with standard fields and views */ @@ -19,12 +25,16 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class CreateProjectCli extends CliFramework { /** @var string[] */ +<<<<<<< HEAD private array $ALWAYS_EXCLUDE = ['MokoCLI', '.github-private']; +======= + private array $ALWAYS_EXCLUDE = ['mokoplatform', '.github-private']; +>>>>>>> main /** @var array */ private array $PLATFORM_TO_TYPE = [ @@ -80,10 +90,10 @@ class CreateProjectCli extends CliFramework return 2; } - $config = \MokoEnterprise\Config::load(); + $config = \MokoCli\Config::load(); $platformName = $config->getString('platform', 'gitea'); try { - $adapter = \MokoEnterprise\PlatformAdapterFactory::create($config); + $adapter = \MokoCli\PlatformAdapterFactory::create($config); $api = $adapter->getApiClient(); } catch (\Exception $e) { $this->log('ERROR', "Platform initialization failed: " . $e->getMessage()); @@ -183,7 +193,11 @@ class CreateProjectCli extends CliFramework CURLOPT_HTTPHEADER => [ 'Authorization: bearer ' . $token, 'Content-Type: application/json', +<<<<<<< HEAD 'User-Agent: MokoCLI-CreateProject', +======= + 'User-Agent: mokoplatform-CreateProject', +>>>>>>> main ], ]); $body = (string) curl_exec($ch); @@ -205,7 +219,7 @@ class CreateProjectCli extends CliFramework return $data['data'] ?? []; } - private function restGet(string $path, string $token, ?\MokoEnterprise\ApiClient $apiClient = null): array + private function restGet(string $path, string $token, ?\MokoCli\ApiClient $apiClient = null): array { if ($apiClient !== null) { try { @@ -217,7 +231,7 @@ class CreateProjectCli extends CliFramework return []; } - private function detectRepoPlatform(string $org, string $repo, string $token, ?\MokoEnterprise\ApiClient $apiClient = null): string + private function detectRepoPlatform(string $org, string $repo, string $token, ?\MokoCli\ApiClient $apiClient = null): string { foreach (['.github/.mokostandards', '.mokogitea/.mokostandards', '.mokostandards'] as $path) { $data = $this->restGet("repos/{$org}/{$repo}/contents/{$path}", $token, $apiClient); @@ -422,14 +436,22 @@ class CreateProjectCli extends CliFramework updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, +<<<<<<< HEAD readme: "Managed by MokoCLI. Run `php cli/create_project.php` to regenerate." +======= + readme: "Managed by mokoplatform. Run `php cli/create_project.php` to regenerate." +>>>>>>> main }) { projectV2 { id } } }', [ 'projectId' => $projectId, +<<<<<<< HEAD 'shortDescription' => "Standard project board for {$repo}. Auto-created by MokoCLI.", +======= + 'shortDescription' => "Standard project board for {$repo}. Auto-created by mokoplatform.", +>>>>>>> main ], $token ); diff --git a/cli/create_repo.php b/cli/create_repo.php index b24787d..2f8bec8 100644 --- a/cli/create_repo.php +++ b/cli/create_repo.php @@ -8,11 +8,19 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.CLI * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/create_repo.php * BRIEF: Scaffold a new governed repository with full MokoCLI baseline +======= + * DEFGROUP: mokoplatform.CLI + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform + * PATH: /cli/create_repo.php + * BRIEF: Scaffold a new governed repository with full mokoplatform baseline +>>>>>>> main */ declare(strict_types=1); @@ -20,15 +28,19 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; -use MokoEnterprise\Config; -use MokoEnterprise\PlatformAdapterFactory; +use MokoCli\CliFramework; +use MokoCli\Config; +use MokoCli\PlatformAdapterFactory; class CreateRepoCli extends CliFramework { protected function configure(): void { +<<<<<<< HEAD $this->setDescription('Scaffold a new governed repository with full MokoCLI baseline'); +======= + $this->setDescription('Scaffold a new governed repository with full mokoplatform baseline'); +>>>>>>> main $this->addArgument('--name', 'Repository name', null); $this->addArgument('--type', 'Project type', null); $this->addArgument('--description', 'Repository description', ''); @@ -60,6 +72,7 @@ class CreateRepoCli extends CliFramework 'generic' => 'generic', ]; $TYPE_TO_TOPICS = [ +<<<<<<< HEAD 'dolibarr' => ['dolibarr', 'erp', 'crm', 'php', 'MokoCLI'], 'joomla' => ['joomla', 'cms', 'php', 'MokoCLI'], 'nodejs' => ['nodejs', 'javascript', 'typescript', 'MokoCLI'], @@ -70,6 +83,18 @@ class CreateRepoCli extends CliFramework ]; $platform = $TYPE_TO_PLATFORM[$type] ?? 'generic'; $topics = $TYPE_TO_TOPICS[$type] ?? ['MokoCLI']; +======= + 'dolibarr' => ['dolibarr', 'erp', 'crm', 'php', 'mokoplatform'], + 'joomla' => ['joomla', 'cms', 'php', 'mokoplatform'], + 'nodejs' => ['nodejs', 'javascript', 'typescript', 'mokoplatform'], + 'terraform' => ['terraform', 'infrastructure', 'iac', 'mokoplatform'], + 'python' => ['python', 'mokoplatform'], + 'wordpress' => ['wordpress', 'php', 'cms', 'mokoplatform'], + 'generic' => ['mokoplatform'], + ]; + $platform = $TYPE_TO_PLATFORM[$type] ?? 'generic'; + $topics = $TYPE_TO_TOPICS[$type] ?? ['mokoplatform']; +>>>>>>> main $platformName = $adapter->getPlatformName(); $vis = $private ? 'private' : 'public'; echo "Scaffolding new repository: {$org}/{$name}" @@ -84,7 +109,11 @@ class CreateRepoCli extends CliFramework if (!$this->dryRun) { try { $data = $adapter->createOrgRepo($org, $name, [ +<<<<<<< HEAD 'description' => $description ?: "Managed by MokoCLI ({$type})", +======= + 'description' => $description ?: "Managed by mokoplatform ({$type})", +>>>>>>> main 'private' => $private, 'has_issues' => true, 'has_projects' => true, @@ -138,12 +167,16 @@ class CreateRepoCli extends CliFramework echo "Step 4: Creating README.md...\n"; $baseUrl = $platformName === 'gitea' ? $config->getString('gitea.url', 'https://git.mokoconsulting.tech') : 'https://github.com'; $repoUrl = "{$baseUrl}/{$org}/{$name}"; - $standardsUrl = "{$baseUrl}/{$org}/MokoStandards"; + $standardsUrl = "{$baseUrl}/{$org}/MokoCli"; $readmeContent = " diff --git a/index.md b/index.md index e809329..c042df4 100644 --- a/index.md +++ b/index.md @@ -2,16 +2,26 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION +<<<<<<< HEAD DEFGROUP: MokoCLI.Root INGROUP: MokoCLI REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +DEFGROUP: MokoPlatform.Root +INGROUP: MokoPlatform +REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main PATH: /index.md BRIEF: Scripts directory index --> # Scripts Index +<<<<<<< HEAD Quick navigation for MokoCLI scripts organized by function. +======= +Quick navigation for mokoplatform scripts organized by function. +>>>>>>> main ## Core Categories @@ -60,7 +70,11 @@ Shared library code - Extension utilities - GitHub client +<<<<<<< HEAD ### [Wiki](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki) +======= +### [Wiki](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki) +>>>>>>> main All documentation lives in the Gitea wiki. ### [Tests](tests/) @@ -93,4 +107,8 @@ All three languages may coexist in the same directory for the same functionality ## See Also - [README.md](README.md) - Comprehensive scripts documentation +<<<<<<< HEAD - [Wiki](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki) - Documentation (wiki-first) +======= +- [Wiki](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki) - Documentation (wiki-first) +>>>>>>> main diff --git a/lib/CliBase.php b/lib/CliBase.php index 667872d..b96c62c 100644 --- a/lib/CliBase.php +++ b/lib/CliBase.php @@ -7,9 +7,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Lib * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Lib + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/CliBase.php * BRIEF: Standalone base CLI class for scripts that do not use CliFramework */ diff --git a/lib/Common.php b/lib/Common.php index f6bfc70..3e33416 100644 --- a/lib/Common.php +++ b/lib/Common.php @@ -7,9 +7,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Lib * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Lib + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Common.php * BRIEF: Common utility functions for scripts * NOTE: Version format used throughout is zero-padded semver: XX.YY.ZZ (e.g. 04.00.04). @@ -32,8 +38,13 @@ class Common */ const FALLBACK_VERSION = '04.00.00'; +<<<<<<< HEAD const REPO_URL = 'https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API'; const REPO_URL_GITHUB = 'https://git.mokoconsulting.tech/MokoConsulting/mokocli'; +======= + const REPO_URL = 'https://git.mokoconsulting.tech/MokoConsulting/mokocli'; + const REPO_URL_GITHUB = 'https://git.mokoconsulting.tech/MokoConsulting/mokoplatform'; +>>>>>>> main const COPYRIGHT = 'Copyright (C) 2026 Moko Consulting '; const LICENSE = 'GPL-3.0-or-later'; diff --git a/lib/Enterprise/AbstractProjectPlugin.php b/lib/Enterprise/AbstractProjectPlugin.php index f8d8755..d01d172 100644 --- a/lib/Enterprise/AbstractProjectPlugin.php +++ b/lib/Enterprise/AbstractProjectPlugin.php @@ -9,14 +9,20 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/AbstractProjectPlugin.php * BRIEF: Abstract base class for project plugins */ -namespace MokoEnterprise; +namespace MokoCli; /** * Abstract base class for project type plugins diff --git a/lib/Enterprise/ApiClient.php b/lib/Enterprise/ApiClient.php index 9f7e5b3..84fb3ed 100644 --- a/lib/Enterprise/ApiClient.php +++ b/lib/Enterprise/ApiClient.php @@ -9,9 +9,15 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.API * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.API + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/ApiClient.php * BRIEF: HTTP API client library */ @@ -33,11 +39,15 @@ declare(strict_types=1); * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later */ -namespace MokoEnterprise; +namespace MokoCli; use DateTime; use DateTimeZone; @@ -166,7 +176,7 @@ class ApiClient int $circuitBreakerThreshold = 5, int $circuitBreakerTimeout = 60, bool $enableCaching = true, - string $userAgent = 'MokoStandards-APIClient/1.0', + string $userAgent = 'MokoCli-APIClient/1.0', ?LoggerInterface $logger = null, string $authScheme = 'Bearer' ) { diff --git a/lib/Enterprise/AuditLogger.php b/lib/Enterprise/AuditLogger.php index abbae13..61dc8f0 100644 --- a/lib/Enterprise/AuditLogger.php +++ b/lib/Enterprise/AuditLogger.php @@ -22,19 +22,29 @@ declare(strict_types=1); * (at your option) any later version. * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Audit * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Audit + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/AuditLogger.php * BRIEF: Enterprise audit logging * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later */ -namespace MokoEnterprise; +namespace MokoCli; use DateTime; use DateTimeZone; diff --git a/lib/Enterprise/CheckpointManager.php b/lib/Enterprise/CheckpointManager.php index 5817923..acafed7 100644 --- a/lib/Enterprise/CheckpointManager.php +++ b/lib/Enterprise/CheckpointManager.php @@ -10,19 +10,29 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Checkpoint * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Checkpoint + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/CheckpointManager.php * BRIEF: Checkpoint manager for resumable operations * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later */ -namespace MokoEnterprise; +namespace MokoCli; use DateTime; use DateTimeZone; diff --git a/lib/Enterprise/CliFramework.php b/lib/Enterprise/CliFramework.php index a3f90b4..921794c 100644 --- a/lib/Enterprise/CliFramework.php +++ b/lib/Enterprise/CliFramework.php @@ -7,27 +7,43 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.CLI * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/CliFramework.php * BRIEF: CliFramework — unified base class for all MokoCLI CLI scripts +======= + * DEFGROUP: MokoPlatform.Enterprise.CLI + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform + * PATH: /lib/Enterprise/CliFramework.php + * BRIEF: CliFramework — unified base class for all mokoplatform CLI scripts +>>>>>>> main */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; use DateTime; use DateTimeZone; use Exception; // ============================================================================= +<<<<<<< HEAD // CliFramework — current base class for all MokoCLI CLI scripts // ============================================================================= /** * Base class for MokoCLI CLI scripts. +======= +// CliFramework — current base class for all mokoplatform CLI scripts +// ============================================================================= + +/** + * Base class for mokoplatform CLI scripts. +>>>>>>> main * * Provides argument parsing, a structured lifecycle, and a full console * graphics system (banners, coloured log levels, progress bars, status diff --git a/lib/Enterprise/Config.php b/lib/Enterprise/Config.php index 80a420e..385433e 100644 --- a/lib/Enterprise/Config.php +++ b/lib/Enterprise/Config.php @@ -9,9 +9,15 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Config * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Config + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/Config.php * BRIEF: Configuration manager */ @@ -34,11 +40,15 @@ declare(strict_types=1); * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later */ -namespace MokoEnterprise; +namespace MokoCli; use RuntimeException; diff --git a/lib/Enterprise/ConfigValidator.php b/lib/Enterprise/ConfigValidator.php index 99a6988..c262417 100644 --- a/lib/Enterprise/ConfigValidator.php +++ b/lib/Enterprise/ConfigValidator.php @@ -7,21 +7,31 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/ConfigValidator.php * BRIEF: Validate project config against plugin JSON schema */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; /** * Configuration Validator * +<<<<<<< HEAD * Validates MokoCLI configuration files (YAML, JSON, HCL) +======= + * Validates mokoplatform configuration files (YAML, JSON, HCL) +>>>>>>> main * against expected schemas and reports errors. * * @since 04.00.00 diff --git a/lib/Enterprise/EnterpriseReadinessValidator.php b/lib/Enterprise/EnterpriseReadinessValidator.php index 019d389..7c3a7e8 100644 --- a/lib/Enterprise/EnterpriseReadinessValidator.php +++ b/lib/Enterprise/EnterpriseReadinessValidator.php @@ -8,16 +8,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/EnterpriseReadinessValidator.php * BRIEF: Enterprise readiness validation library */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; /** * Enterprise Readiness Validator diff --git a/lib/Enterprise/ErrorRecovery.php b/lib/Enterprise/ErrorRecovery.php index fe5439b..8baa139 100644 --- a/lib/Enterprise/ErrorRecovery.php +++ b/lib/Enterprise/ErrorRecovery.php @@ -17,20 +17,30 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Recovery * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Recovery + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/ErrorRecovery.php * BRIEF: Error recovery framework * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later * @deprecated Individual class files should be used instead */ -namespace MokoEnterprise; +namespace MokoCli; use Throwable; diff --git a/lib/Enterprise/FileFixUtility.php b/lib/Enterprise/FileFixUtility.php index ed94621..c72150d 100644 --- a/lib/Enterprise/FileFixUtility.php +++ b/lib/Enterprise/FileFixUtility.php @@ -7,16 +7,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise * INGROUP: MokoCLI.Lib * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise + * INGROUP: MokoPlatform.Lib + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/FileFixUtility.php * BRIEF: Utility class for fixing file formatting issues (line endings, permissions, tabs, trailing spaces) */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; diff --git a/lib/Enterprise/GitHubAdapter.php b/lib/Enterprise/GitHubAdapter.php index 9fb3fd4..be4f1d7 100644 --- a/lib/Enterprise/GitHubAdapter.php +++ b/lib/Enterprise/GitHubAdapter.php @@ -7,16 +7,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Platform * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Platform + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/GitHubAdapter.php * BRIEF: GitHub implementation of GitPlatformAdapter */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; use RuntimeException; diff --git a/lib/Enterprise/GitPlatformAdapter.php b/lib/Enterprise/GitPlatformAdapter.php index fdb28ea..ab4e14d 100644 --- a/lib/Enterprise/GitPlatformAdapter.php +++ b/lib/Enterprise/GitPlatformAdapter.php @@ -7,21 +7,31 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Platform * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Platform + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/GitPlatformAdapter.php * BRIEF: Interface defining all git platform operations for GitHub/Gitea abstraction */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; /** * Git Platform Adapter Interface * +<<<<<<< HEAD * Defines all platform operations required by MokoCLI automation. +======= + * Defines all platform operations required by mokoplatform automation. +>>>>>>> main * Implementations exist for GitHub (GitHubAdapter) and Gitea (MokoGiteaAdapter), * allowing scripts to work against either platform transparently. * diff --git a/lib/Enterprise/InputValidator.php b/lib/Enterprise/InputValidator.php index 37c9c50..4a74116 100644 --- a/lib/Enterprise/InputValidator.php +++ b/lib/Enterprise/InputValidator.php @@ -9,9 +9,15 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Validation * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Validation + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/InputValidator.php * BRIEF: Input validation library */ @@ -33,11 +39,15 @@ declare(strict_types=1); * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later */ -namespace MokoEnterprise; +namespace MokoCli; use InvalidArgumentException; use RuntimeException; @@ -65,7 +75,7 @@ class ValidationError extends RuntimeException * * Example: * ```php - * use MokoEnterprise\InputValidator; + * use MokoCli\InputValidator; * * $path = InputValidator::validatePath('/tmp/file.txt'); * $email = InputValidator::validateEmail('user@example.com'); @@ -161,11 +171,19 @@ class InputValidator break; case 'moko': +<<<<<<< HEAD // MokoCLI format: XX.YY.ZZ $pattern = '/^\d{2}\.\d{2}\.\d{2}$/'; if (!preg_match($pattern, $version)) { throw new ValidationError( "Invalid MokoCLI version format: {$version}. Expected: XX.YY.ZZ" +======= + // mokoplatform format: XX.YY.ZZ + $pattern = '/^\d{2}\.\d{2}\.\d{2}$/'; + if (!preg_match($pattern, $version)) { + throw new ValidationError( + "Invalid mokoplatform version format: {$version}. Expected: XX.YY.ZZ" +>>>>>>> main ); } break; diff --git a/lib/Enterprise/MokoStandardsParser.php b/lib/Enterprise/ManifestParser.php similarity index 94% rename from lib/Enterprise/MokoStandardsParser.php rename to lib/Enterprise/ManifestParser.php index 971e7f1..e7f6fc7 100644 --- a/lib/Enterprise/MokoStandardsParser.php +++ b/lib/Enterprise/ManifestParser.php @@ -8,22 +8,29 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD:lib/Enterprise/MokoStandardsParser.php * DEFGROUP: MokoCLI.Enterprise * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/MokoStandardsParser.php +======= + * DEFGROUP: MokoPlatform.Enterprise + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform + * PATH: /lib/Enterprise/ManifestParser.php +>>>>>>> main:lib/Enterprise/ManifestParser.php * BRIEF: Parser for the XML-based manifest.xml repository manifest */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; use DOMDocument; use SimpleXMLElement; /** - * MokoStandards Parser + * MokoCli Parser * * Reads, writes, and validates the manifest.xml repository manifest. * The file uses XML format (no file extension) and lives at .mokogitea/manifest.xml. @@ -31,11 +38,16 @@ use SimpleXMLElement; * @package MokoCLI\Enterprise * @version 04.07.00 */ -class MokoStandardsParser +class ManifestParser { public const SCHEMA_VERSION = '1.0'; +<<<<<<< HEAD:lib/Enterprise/MokoStandardsParser.php public const NAMESPACE_URI = 'https://standards.mokoconsulting.tech/MokoCLI/1.0'; public const STANDARDS_SOURCE = 'https://git.mokoconsulting.tech/MokoConsulting/mokocli'; +======= + public const NAMESPACE_URI = 'https://standards.mokoconsulting.tech/mokoplatform/1.0'; + public const STANDARDS_SOURCE = 'https://git.mokoconsulting.tech/MokoConsulting/mokoplatform'; +>>>>>>> main:lib/Enterprise/ManifestParser.php /** Valid platform slugs — must match Template-* repo names. */ public const VALID_PLATFORMS = [ @@ -180,7 +192,11 @@ class MokoStandardsParser * @type string $name Repository name (required) * @type string $org Organization (required) * @type string $platform Platform slug (required) +<<<<<<< HEAD:lib/Enterprise/MokoStandardsParser.php * @type string $standards_version MokoCLI version +======= + * @type string $standards_version mokoplatform version +>>>>>>> main:lib/Enterprise/ManifestParser.php * @type string $description Repo description * @type string $license SPDX license identifier * @type list $topics Repo topics @@ -204,8 +220,13 @@ class MokoStandardsParser // Add comment header $dom->appendChild($dom->createComment( +<<<<<<< HEAD:lib/Enterprise/MokoStandardsParser.php "\n MokoStandards Repository Manifest\n" . " Auto-generated by MokoCLI bulk sync.\n" +======= + "\n MokoCli Repository Manifest\n" + . " Auto-generated by mokoplatform bulk sync.\n" +>>>>>>> main:lib/Enterprise/ManifestParser.php . " Manual edits to and may be overwritten.\n" . " See: docs/standards/mokostandards-file-spec.md\n" )); diff --git a/lib/Enterprise/ManifestReader.php b/lib/Enterprise/ManifestReader.php index 9495548..0af910a 100644 --- a/lib/Enterprise/ManifestReader.php +++ b/lib/Enterprise/ManifestReader.php @@ -7,16 +7,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: mokoplatform.Enterprise + * INGROUP: mokoplatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/ManifestReader.php * BRIEF: Read and parse .mokogitea/manifest.xml — shared across all CLI tools */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; /** * Manifest Reader @@ -58,7 +64,11 @@ class ManifestReader $candidates = [ "{$root}/.mokogitea/manifest.xml", "{$root}/.mokogitea/.manifest.xml", +<<<<<<< HEAD "{$root}/.mokogitea/.MokoCLI", +======= + "{$root}/.mokogitea/.mokoplatform", +>>>>>>> main ]; $manifestFile = null; diff --git a/lib/Enterprise/MetricsCollector.php b/lib/Enterprise/MetricsCollector.php index 73a12c7..2b2acaa 100644 --- a/lib/Enterprise/MetricsCollector.php +++ b/lib/Enterprise/MetricsCollector.php @@ -9,15 +9,25 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Metrics * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Metrics + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/MetricsCollector.php * BRIEF: Metrics collection framework */ /** +<<<<<<< HEAD * Metrics Collector for MokoCLI +======= + * Metrics Collector for mokoplatform +>>>>>>> main * * Provides observability and monitoring capabilities: * - Execution time tracking with timers @@ -48,11 +58,15 @@ declare(strict_types=1); * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later */ -namespace MokoEnterprise; +namespace MokoCli; use DateTime; use DateTimeZone; diff --git a/lib/Enterprise/MokoGiteaAdapter.php b/lib/Enterprise/MokoGiteaAdapter.php index af80d1f..9f5688e 100644 --- a/lib/Enterprise/MokoGiteaAdapter.php +++ b/lib/Enterprise/MokoGiteaAdapter.php @@ -7,16 +7,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Platform * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Platform + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/MokoGiteaAdapter.php * BRIEF: Gitea implementation of GitPlatformAdapter */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; use Exception; use RuntimeException; diff --git a/lib/Enterprise/PackageBuilder.php b/lib/Enterprise/PackageBuilder.php index 8f3104d..32b9305 100644 --- a/lib/Enterprise/PackageBuilder.php +++ b/lib/Enterprise/PackageBuilder.php @@ -7,16 +7,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise * INGROUP: MokoCLI.Lib * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise + * INGROUP: MokoPlatform.Lib + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/PackageBuilder.php * BRIEF: Builds release packages for generic, Dolibarr module, and Joomla component projects */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; diff --git a/lib/Enterprise/PlatformAdapterFactory.php b/lib/Enterprise/PlatformAdapterFactory.php index 6f63c38..b8a4d6b 100644 --- a/lib/Enterprise/PlatformAdapterFactory.php +++ b/lib/Enterprise/PlatformAdapterFactory.php @@ -7,16 +7,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Platform * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Platform + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/PlatformAdapterFactory.php * BRIEF: Factory for creating platform-specific GitPlatformAdapter instances */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; use RuntimeException; diff --git a/lib/Enterprise/PluginFactory.php b/lib/Enterprise/PluginFactory.php index ab1d289..7c573d0 100644 --- a/lib/Enterprise/PluginFactory.php +++ b/lib/Enterprise/PluginFactory.php @@ -9,14 +9,20 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/PluginFactory.php * BRIEF: Plugin factory for project type detection */ -namespace MokoEnterprise; +namespace MokoCli; /** * Plugin Factory - Factory for creating and managing plugin instances diff --git a/lib/Enterprise/PluginRegistry.php b/lib/Enterprise/PluginRegistry.php index 9e037b3..46d1972 100644 --- a/lib/Enterprise/PluginRegistry.php +++ b/lib/Enterprise/PluginRegistry.php @@ -9,26 +9,32 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/PluginRegistry.php * BRIEF: Plugin registry for available project plugins */ -namespace MokoEnterprise; +namespace MokoCli; -use MokoEnterprise\Plugins\JoomlaPlugin; -use MokoEnterprise\Plugins\DolibarrPlugin; -use MokoEnterprise\Plugins\GenericPlugin; -use MokoEnterprise\Plugins\DocumentationPlugin; -use MokoEnterprise\Plugins\NodeJsPlugin; -use MokoEnterprise\Plugins\PythonPlugin; -use MokoEnterprise\Plugins\TerraformPlugin; -use MokoEnterprise\Plugins\WordPressPlugin; -use MokoEnterprise\Plugins\MobilePlugin; -use MokoEnterprise\Plugins\ApiPlugin; -use MokoEnterprise\Plugins\McpServerPlugin; +use MokoCli\Plugins\JoomlaPlugin; +use MokoCli\Plugins\DolibarrPlugin; +use MokoCli\Plugins\GenericPlugin; +use MokoCli\Plugins\DocumentationPlugin; +use MokoCli\Plugins\NodeJsPlugin; +use MokoCli\Plugins\PythonPlugin; +use MokoCli\Plugins\TerraformPlugin; +use MokoCli\Plugins\WordPressPlugin; +use MokoCli\Plugins\MobilePlugin; +use MokoCli\Plugins\ApiPlugin; +use MokoCli\Plugins\McpServerPlugin; /** * Plugin Registry - Central registry for all project type plugins diff --git a/lib/Enterprise/Plugins/ApiPlugin.php b/lib/Enterprise/Plugins/ApiPlugin.php index bf52a6f..c589c4a 100644 --- a/lib/Enterprise/Plugins/ApiPlugin.php +++ b/lib/Enterprise/Plugins/ApiPlugin.php @@ -8,18 +8,24 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/Plugins/ApiPlugin.php * BRIEF: Enterprise plugin for API/Microservices projects */ declare(strict_types=1); -namespace MokoEnterprise\Plugins; +namespace MokoCli\Plugins; -use MokoEnterprise\AbstractProjectPlugin; +use MokoCli\AbstractProjectPlugin; /** * API/Microservices Project Plugin diff --git a/lib/Enterprise/Plugins/DocumentationPlugin.php b/lib/Enterprise/Plugins/DocumentationPlugin.php index 6529877..e1cad4e 100644 --- a/lib/Enterprise/Plugins/DocumentationPlugin.php +++ b/lib/Enterprise/Plugins/DocumentationPlugin.php @@ -8,18 +8,24 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/Plugins/DocumentationPlugin.php * BRIEF: Enterprise plugin for documentation projects */ declare(strict_types=1); -namespace MokoEnterprise\Plugins; +namespace MokoCli\Plugins; -use MokoEnterprise\AbstractProjectPlugin; +use MokoCli\AbstractProjectPlugin; /** * Documentation Project Plugin diff --git a/lib/Enterprise/Plugins/DolibarrPlugin.php b/lib/Enterprise/Plugins/DolibarrPlugin.php index 6da6166..a98d0c7 100644 --- a/lib/Enterprise/Plugins/DolibarrPlugin.php +++ b/lib/Enterprise/Plugins/DolibarrPlugin.php @@ -8,18 +8,24 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/Plugins/DolibarrPlugin.php * BRIEF: Enterprise plugin for Dolibarr modules */ declare(strict_types=1); -namespace MokoEnterprise\Plugins; +namespace MokoCli\Plugins; -use MokoEnterprise\AbstractProjectPlugin; +use MokoCli\AbstractProjectPlugin; /** * Dolibarr Module Plugin diff --git a/lib/Enterprise/Plugins/GenericPlugin.php b/lib/Enterprise/Plugins/GenericPlugin.php index 6fa9d28..dbc0660 100644 --- a/lib/Enterprise/Plugins/GenericPlugin.php +++ b/lib/Enterprise/Plugins/GenericPlugin.php @@ -8,18 +8,24 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/Plugins/GenericPlugin.php * BRIEF: Enterprise plugin for generic projects */ declare(strict_types=1); -namespace MokoEnterprise\Plugins; +namespace MokoCli\Plugins; -use MokoEnterprise\AbstractProjectPlugin; +use MokoCli\AbstractProjectPlugin; /** * Generic Project Plugin diff --git a/lib/Enterprise/Plugins/JoomlaPlugin.php b/lib/Enterprise/Plugins/JoomlaPlugin.php index 044fb79..95725a8 100644 --- a/lib/Enterprise/Plugins/JoomlaPlugin.php +++ b/lib/Enterprise/Plugins/JoomlaPlugin.php @@ -8,18 +8,24 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/Plugins/JoomlaPlugin.php * BRIEF: Enterprise plugin for Joomla projects */ declare(strict_types=1); -namespace MokoEnterprise\Plugins; +namespace MokoCli\Plugins; -use MokoEnterprise\AbstractProjectPlugin; +use MokoCli\AbstractProjectPlugin; /** * Joomla Project Plugin diff --git a/lib/Enterprise/Plugins/McpServerPlugin.php b/lib/Enterprise/Plugins/McpServerPlugin.php index 58f42f6..c2f6f93 100644 --- a/lib/Enterprise/Plugins/McpServerPlugin.php +++ b/lib/Enterprise/Plugins/McpServerPlugin.php @@ -8,19 +8,25 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/Plugins/McpServerPlugin.php * BRIEF: Enterprise plugin for MCP (Model Context Protocol) server projects */ declare(strict_types=1); -namespace MokoEnterprise\Plugins; +namespace MokoCli\Plugins; -use MokoEnterprise\AbstractProjectPlugin; -use MokoEnterprise\SourceResolver; +use MokoCli\AbstractProjectPlugin; +use MokoCli\SourceResolver; /** * MCP Server Project Plugin diff --git a/lib/Enterprise/Plugins/MobilePlugin.php b/lib/Enterprise/Plugins/MobilePlugin.php index 8086d0e..4890a40 100644 --- a/lib/Enterprise/Plugins/MobilePlugin.php +++ b/lib/Enterprise/Plugins/MobilePlugin.php @@ -8,18 +8,24 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/Plugins/MobilePlugin.php * BRIEF: Enterprise plugin for mobile app projects */ declare(strict_types=1); -namespace MokoEnterprise\Plugins; +namespace MokoCli\Plugins; -use MokoEnterprise\AbstractProjectPlugin; +use MokoCli\AbstractProjectPlugin; /** * Mobile App Project Plugin diff --git a/lib/Enterprise/Plugins/NodeJsPlugin.php b/lib/Enterprise/Plugins/NodeJsPlugin.php index 1756110..4830f16 100644 --- a/lib/Enterprise/Plugins/NodeJsPlugin.php +++ b/lib/Enterprise/Plugins/NodeJsPlugin.php @@ -8,18 +8,24 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/Plugins/NodeJsPlugin.php * BRIEF: Enterprise plugin for Node.js/TypeScript projects */ declare(strict_types=1); -namespace MokoEnterprise\Plugins; +namespace MokoCli\Plugins; -use MokoEnterprise\AbstractProjectPlugin; +use MokoCli\AbstractProjectPlugin; /** * Node.js/TypeScript Project Plugin diff --git a/lib/Enterprise/Plugins/PythonPlugin.php b/lib/Enterprise/Plugins/PythonPlugin.php index 632a991..0f9d6be 100644 --- a/lib/Enterprise/Plugins/PythonPlugin.php +++ b/lib/Enterprise/Plugins/PythonPlugin.php @@ -8,18 +8,24 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/Plugins/PythonPlugin.php * BRIEF: Enterprise plugin for Python projects */ declare(strict_types=1); -namespace MokoEnterprise\Plugins; +namespace MokoCli\Plugins; -use MokoEnterprise\AbstractProjectPlugin; +use MokoCli\AbstractProjectPlugin; /** * Python Project Plugin diff --git a/lib/Enterprise/Plugins/TerraformPlugin.php b/lib/Enterprise/Plugins/TerraformPlugin.php index d7e5db1..a97ed83 100644 --- a/lib/Enterprise/Plugins/TerraformPlugin.php +++ b/lib/Enterprise/Plugins/TerraformPlugin.php @@ -8,18 +8,24 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/Plugins/TerraformPlugin.php * BRIEF: Enterprise plugin for Terraform projects */ declare(strict_types=1); -namespace MokoEnterprise\Plugins; +namespace MokoCli\Plugins; -use MokoEnterprise\AbstractProjectPlugin; +use MokoCli\AbstractProjectPlugin; /** * Terraform Project Plugin diff --git a/lib/Enterprise/Plugins/WordPressPlugin.php b/lib/Enterprise/Plugins/WordPressPlugin.php index ebebd0e..9664679 100644 --- a/lib/Enterprise/Plugins/WordPressPlugin.php +++ b/lib/Enterprise/Plugins/WordPressPlugin.php @@ -8,18 +8,24 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/Plugins/WordPressPlugin.php * BRIEF: Enterprise plugin for WordPress projects */ declare(strict_types=1); -namespace MokoEnterprise\Plugins; +namespace MokoCli\Plugins; -use MokoEnterprise\AbstractProjectPlugin; +use MokoCli\AbstractProjectPlugin; /** * WordPress Project Plugin diff --git a/lib/Enterprise/ProjectConfigValidator.php b/lib/Enterprise/ProjectConfigValidator.php index 0ec39c8..1a42d9d 100644 --- a/lib/Enterprise/ProjectConfigValidator.php +++ b/lib/Enterprise/ProjectConfigValidator.php @@ -8,16 +8,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.ProjectTypes * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.ProjectTypes + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/ProjectConfigValidator.php * BRIEF: Enterprise library for validating project configurations */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; /** * Project Config Validator diff --git a/lib/Enterprise/ProjectMetricsCollector.php b/lib/Enterprise/ProjectMetricsCollector.php index 865f887..2b90dcf 100644 --- a/lib/Enterprise/ProjectMetricsCollector.php +++ b/lib/Enterprise/ProjectMetricsCollector.php @@ -8,16 +8,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.ProjectTypes * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.ProjectTypes + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/ProjectMetricsCollector.php * BRIEF: Enterprise library for collecting project-specific metrics */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; /** * Project Metrics Collector diff --git a/lib/Enterprise/ProjectPluginInterface.php b/lib/Enterprise/ProjectPluginInterface.php index d5f9913..ece8014 100644 --- a/lib/Enterprise/ProjectPluginInterface.php +++ b/lib/Enterprise/ProjectPluginInterface.php @@ -9,14 +9,20 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Plugins * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Plugins + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/ProjectPluginInterface.php * BRIEF: Interface for project type plugins */ -namespace MokoEnterprise; +namespace MokoCli; /** * Interface for project type-specific enterprise plugins diff --git a/lib/Enterprise/ProjectTypeDetector.php b/lib/Enterprise/ProjectTypeDetector.php index 2f88bbb..368093f 100644 --- a/lib/Enterprise/ProjectTypeDetector.php +++ b/lib/Enterprise/ProjectTypeDetector.php @@ -8,16 +8,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.ProjectTypes * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.ProjectTypes + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/ProjectTypeDetector.php * BRIEF: Enterprise library for detecting project types */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; /** * Project Type Detector diff --git a/lib/Enterprise/RecoveryError.php b/lib/Enterprise/RecoveryError.php index 973197a..3f3f778 100644 --- a/lib/Enterprise/RecoveryError.php +++ b/lib/Enterprise/RecoveryError.php @@ -10,19 +10,29 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Recovery * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Recovery + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/RecoveryError.php * BRIEF: Recovery error exception class * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later */ -namespace MokoEnterprise; +namespace MokoCli; use RuntimeException; diff --git a/lib/Enterprise/RecoveryManager.php b/lib/Enterprise/RecoveryManager.php index 0056124..0c7eec2 100644 --- a/lib/Enterprise/RecoveryManager.php +++ b/lib/Enterprise/RecoveryManager.php @@ -10,19 +10,29 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Recovery * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Recovery + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/RecoveryManager.php * BRIEF: Recovery manager for failed operations * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later */ -namespace MokoEnterprise; +namespace MokoCli; use DateTime; use DateTimeZone; diff --git a/lib/Enterprise/RepositoryHealthChecker.php b/lib/Enterprise/RepositoryHealthChecker.php index ac7ab01..3af027f 100644 --- a/lib/Enterprise/RepositoryHealthChecker.php +++ b/lib/Enterprise/RepositoryHealthChecker.php @@ -8,16 +8,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/RepositoryHealthChecker.php * BRIEF: Repository health checking enterprise library */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; /** * Repository Health Checker diff --git a/lib/Enterprise/RepositorySynchronizer.php b/lib/Enterprise/RepositorySynchronizer.php index be56288..d7ff669 100644 --- a/lib/Enterprise/RepositorySynchronizer.php +++ b/lib/Enterprise/RepositorySynchronizer.php @@ -8,16 +8,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/RepositorySynchronizer.php * BRIEF: Repository synchronization enterprise library */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; use Exception; use RuntimeException; @@ -44,7 +50,7 @@ class RepositorySynchronizer private AuditLogger $logger; private MetricsCollector $metrics; private CheckpointManager $checkpoints; - private MokoStandardsParser $manifestParser; + private ManifestParser $manifestParser; /** * Constructor @@ -66,7 +72,7 @@ class RepositorySynchronizer $this->logger = $logger; $this->metrics = $metrics; $this->checkpoints = $checkpoints ?? new CheckpointManager('.checkpoints'); - $this->manifestParser = new MokoStandardsParser(); + $this->manifestParser = new ManifestParser(); } /** @@ -165,8 +171,13 @@ class RepositorySynchronizer // Resolve repo root (three levels up from this file: Enterprise/ → lib/ → root) // API repo root (definitions, sync code) $repoRoot = dirname(dirname(__DIR__)); +<<<<<<< HEAD // MokoCLI repo root (templates, configs) $standardsRoot = getenv('MOKOSTANDARDS_ROOT') ?: dirname($repoRoot) . '/MokoCLI'; +======= + // mokoplatform repo root (templates, configs) + $standardsRoot = getenv('MOKOSTANDARDS_ROOT') ?: dirname($repoRoot) . '/mokoplatform'; +>>>>>>> main // Detect platform from repo metadata $repoInfo = $this->adapter->getRepo($org, $repo); @@ -287,7 +298,7 @@ class RepositorySynchronizer $file = $this->adapter->getFileContents($org, $repo, $path); $content = base64_decode($file['content'] ?? ''); $platform = $this->manifestParser->extractPlatform($content); - if ($platform !== null && in_array($platform, MokoStandardsParser::VALID_PLATFORMS, true)) { + if ($platform !== null && in_array($platform, ManifestParser::VALID_PLATFORMS, true)) { return $platform; } } catch (Exception $e) { @@ -364,7 +375,11 @@ class RepositorySynchronizer * @param string $repo * @param string $platform Detected platform slug (e.g. 'dolibarr') * @param array $filesToSync +<<<<<<< HEAD * @param string $repoRoot Absolute path to the MokoCLI repository root +======= + * @param string $repoRoot Absolute path to the mokoplatform repository root +>>>>>>> main * @param bool $force When true, overwrite files even when always_overwrite = false * @return array{number: ?int, summary: array} */ @@ -412,7 +427,7 @@ class RepositorySynchronizer $this->ensureComposerEnterprise($org, $repo, $defaultBranch, $summary); // Migrate legacy .mokostandards to XML manifest (default branch only) - $this->migrateMokoStandards($org, $repo, $defaultBranch, $platform, $repoInfo, $summary); + $this->migrateManifest($org, $repo, $defaultBranch, $platform, $repoInfo, $summary); if (count($summary['copied']) === 0) { $this->logger->logWarning("No files were created/updated for {$repo}"); @@ -421,7 +436,11 @@ class RepositorySynchronizer // Create tracking issue (no PR — files pushed directly to default branch) $issueBody = $this->generatePRBody($summary); +<<<<<<< HEAD $issueTitle = 'chore: MokoCLI v' . self::STANDARDS_MINOR . ' sync — ' . count($summary['copied']) . ' files updated'; +======= + $issueTitle = 'chore: mokoplatform v' . self::STANDARDS_MINOR . ' sync — ' . count($summary['copied']) . ' files updated'; +>>>>>>> main $issueNumber = null; try { @@ -478,7 +497,11 @@ class RepositorySynchronizer * never added. * * @param string $existing Current file content from the remote repo +<<<<<<< HEAD * @param string $template Template file content from MokoCLI +======= + * @param string $template Template file content from mokoplatform +>>>>>>> main * @return string Merged content */ /** @@ -501,7 +524,11 @@ class RepositorySynchronizer * @param string $repo Repository name * @param string $platform Detected platform type * @param array $filesToSync Files to synchronize +<<<<<<< HEAD * @param string $repoRoot Path to MokoCLI root +======= + * @param string $repoRoot Path to mokoplatform root +>>>>>>> main * @param bool $force Force overwrite * @param string $branchName Target branch * @param string|null $moduleId Dolibarr module ID (pre-fetched) @@ -583,7 +610,11 @@ class RepositorySynchronizer $repo, $targetPath, $content, +<<<<<<< HEAD "chore: update {$targetPath} from MokoCLI", +======= + "chore: update {$targetPath} from mokoplatform", +>>>>>>> main $existingFile['sha'] ?? null, $branchName ); @@ -597,7 +628,11 @@ class RepositorySynchronizer $repo, $targetPath, $content, +<<<<<<< HEAD "chore: add {$targetPath} from MokoCLI", +======= + "chore: add {$targetPath} from mokoplatform", +>>>>>>> main null, $branchName ); @@ -613,7 +648,11 @@ class RepositorySynchronizer $repo, $targetPath, $content, +<<<<<<< HEAD "chore: update {$targetPath} from MokoCLI", +======= + "chore: update {$targetPath} from mokoplatform", +>>>>>>> main $existing['sha'] ?? null, $branchName ); @@ -641,7 +680,7 @@ class RepositorySynchronizer * 2. Format migration: legacy "platform: xxx" → XML manifest * 3. Update existing XML: refresh timestamp */ - private function migrateMokoStandards( + private function migrateManifest( string $org, string $repo, string $branchName, @@ -694,7 +733,7 @@ class RepositorySynchronizer } // ── Generate the new XML manifest ─────────────────────────── - $xmlContent = $this->generateMokoStandardsXml( + $xmlContent = $this->generateManifestXml( $org, $repo, $platform, @@ -766,7 +805,7 @@ class RepositorySynchronizer * @param string|null $existingContent Current .mokostandards content (XML or legacy) * @return string Well-formed XML content */ - private function generateMokoStandardsXml( + private function generateManifestXml( string $org, string $repo, string $platform, @@ -781,8 +820,13 @@ class RepositorySynchronizer 'description' => $repoInfo['description'] ?? '', 'license' => 'GPL-3.0-or-later', 'topics' => $repoInfo['topics'] ?? [], +<<<<<<< HEAD 'language' => $repoInfo['language'] ?? MokoStandardsParser::platformLanguage($platform), 'package_type' => MokoCLIParser::platformPackageType($platform), +======= + 'language' => $repoInfo['language'] ?? ManifestParser::platformLanguage($platform), + 'package_type' => mokoplatformParser::platformPackageType($platform), +>>>>>>> main 'last_synced' => date('c'), ]; @@ -831,7 +875,7 @@ class RepositorySynchronizer } $xpath = new \DOMXPath($dom); - $xpath->registerNamespace('m', MokoStandardsParser::NAMESPACE_URI); + $xpath->registerNamespace('m', ManifestParser::NAMESPACE_URI); // Update $nodes = $xpath->query('//m:governance/m:platform'); @@ -853,7 +897,7 @@ class RepositorySynchronizer $govNodes = $xpath->query('//m:governance'); if ($govNodes->length > 0) { $lastSyncedEl = $dom->createElementNS( - MokoStandardsParser::NAMESPACE_URI, + ManifestParser::NAMESPACE_URI, 'last-synced' ); $lastSyncedEl->textContent = $lastSynced; @@ -1110,7 +1154,11 @@ class RepositorySynchronizer // so repos have a safe place for custom workflows that sync won't touch. $entries[] = [ 'inline_content' => "# Custom Workflows\n\nPlace repo-specific workflows here.\n\n" +<<<<<<< HEAD . "- **Never overwritten** by MokoCLI bulk sync\n" +======= + . "- **Never overwritten** by mokoplatform bulk sync\n" +>>>>>>> main . "- **Never deleted** by the repository-cleanup workflow\n" . "- Safe for custom CI, notifications, or repo-specific automation\n\n" . "Synced workflows live in the parent `{$wfDir}/` directory.\n", @@ -1261,7 +1309,11 @@ class RepositorySynchronizer // Append missing lines with a clear separator $merged = rtrim($existing) . "\n\n" +<<<<<<< HEAD . "# ── MokoCLI sync (auto-appended) ────────────────────────────────\n" +======= + . "# ── mokoplatform sync (auto-appended) ────────────────────────────────\n" +>>>>>>> main . implode("\n", $missing) . "\n"; return $merged; @@ -1313,7 +1365,11 @@ class RepositorySynchronizer '{{standards_version}}' => self::STANDARDS_VERSION, '{{standards_minor}}' => self::STANDARDS_MINOR, '{{standards_branch}}' => self::VERSION_BRANCH, +<<<<<<< HEAD // Single-brace tokens — used by GitHub repository templates and older MokoCLI stubs +======= + // Single-brace tokens — used by GitHub repository templates and older mokoplatform stubs +>>>>>>> main '{REPO_NAME}' => $repoInfo['name'] ?? $repo, '{REPO_URL}' => "https://github.com/{$org}/{$repo}", '{REPO_DESCRIPTION}' => $repoInfo['description'] ?? '', @@ -1383,8 +1439,13 @@ class RepositorySynchronizer */ private function generatePRBody(array $summary): string { +<<<<<<< HEAD $body = "## MokoCLI Synchronization\n\n"; $body .= "This PR synchronizes workflows, configurations, and scripts from the MokoCLI repository.\n\n"; +======= + $body = "## mokoplatform Synchronization\n\n"; + $body .= "This PR synchronizes workflows, configurations, and scripts from the mokoplatform repository.\n\n"; +>>>>>>> main // Summary statistics $body .= "### Summary\n"; @@ -1419,7 +1480,11 @@ class RepositorySynchronizer $body .= "- Verify issue templates render correctly\n\n"; $body .= "---\n"; +<<<<<<< HEAD $body .= "*This PR was automatically generated by the MokoCLI bulk sync process.*\n"; +======= + $body .= "*This PR was automatically generated by the mokoplatform bulk sync process.*\n"; +>>>>>>> main return $body; } @@ -1524,7 +1589,11 @@ class RepositorySynchronizer default => 'EDEDED', }, match ($label) { +<<<<<<< HEAD 'mokostandards' => 'MokoCLI compliance', +======= + 'mokostandards' => 'mokoplatform compliance', +>>>>>>> main 'type: chore' => 'Maintenance tasks', 'automation' => 'Automated processes or scripts', default => '', diff --git a/lib/Enterprise/RetryHelper.php b/lib/Enterprise/RetryHelper.php index 46fb2e6..98f8ddb 100644 --- a/lib/Enterprise/RetryHelper.php +++ b/lib/Enterprise/RetryHelper.php @@ -10,19 +10,29 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Recovery * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Recovery + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/RetryHelper.php * BRIEF: Retry helper with exponential backoff * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later */ -namespace MokoEnterprise; +namespace MokoCli; use Exception; use Throwable; diff --git a/lib/Enterprise/SecurityValidator.php b/lib/Enterprise/SecurityValidator.php index a7a23a2..94ff415 100644 --- a/lib/Enterprise/SecurityValidator.php +++ b/lib/Enterprise/SecurityValidator.php @@ -9,15 +9,25 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Security * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Security + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/SecurityValidator.php * BRIEF: Security validation library */ /** +<<<<<<< HEAD * Security Validator for MokoCLI +======= + * Security Validator for mokoplatform +>>>>>>> main * * Provides security scanning and validation: * - Credential detection in code/config files @@ -47,11 +57,15 @@ declare(strict_types=1); * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later */ -namespace MokoEnterprise; +namespace MokoCli; use Exception; use RecursiveDirectoryIterator; diff --git a/lib/Enterprise/SourceResolver.php b/lib/Enterprise/SourceResolver.php index ac843e2..36ba9a6 100644 --- a/lib/Enterprise/SourceResolver.php +++ b/lib/Enterprise/SourceResolver.php @@ -7,22 +7,28 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise * INGROUP: MokoCLI.Lib * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise + * INGROUP: MokoPlatform.Lib + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/SourceResolver.php * BRIEF: Resolve the root-level source directory across repos (source/, src/, htdocs/) */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; /** * Source Directory Resolver * * Provides a single, consistent fallback chain for locating the root-level - * source directory in any MokoStandards repository. The preferred directory + * source directory in any MokoCli repository. The preferred directory * is `source/`, with legacy `src/` and `htdocs/` as fallbacks. * * This class exists because Joomla extensions use `src/` for namespace @@ -51,17 +57,29 @@ class SourceResolver */ private const CANDIDATES = ['source', 'src', 'htdocs']; + /** Cache of API-resolved entry points keyed by "org/repo". */ + private static array $apiCache = []; + /** * Resolve the source directory name for a repository root. * - * Returns the first candidate directory that exists, or 'source' as the - * default when no candidate is found (e.g. for new repos being scaffolded). + * Resolution order: + * 1. Gitea Manifest API `entry_point` (when GA_TOKEN/GITEA_TOKEN + GITHUB_REPOSITORY are set) + * 2. First candidate directory that exists on the filesystem + * 3. 'source' as the default (e.g. for new repos being scaffolded) * * @param string $root Absolute path to the repository root. * @return string Directory name (e.g. 'source', 'src', 'htdocs'). */ public static function resolve(string $root): string { + // Try API first (CI environments where token + repo are available) + $apiResult = self::resolveFromApi($root); + if ($apiResult !== null) { + return $apiResult; + } + + // Filesystem fallback foreach (self::CANDIDATES as $candidate) { if (is_dir("{$root}/{$candidate}")) { return $candidate; @@ -71,6 +89,92 @@ class SourceResolver return 'source'; } + /** + * Query the MokoGitea Manifest API for the entry_point field. + * + * Only attempts the call when GA_TOKEN or GITEA_TOKEN is set. Results are + * cached per org/repo for the lifetime of the process. + * + * @param string $root Repository root (used to derive org/repo from git remote). + * @return string|null Directory name from entry_point, or null if unavailable. + */ + public static function resolveFromApi(string $root): ?string + { + $token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: ''; + if ($token === '') { + return null; + } + + [$org, $repo] = self::resolveOrgRepo($root); + if ($org === '' || $repo === '') { + return null; + } + + $cacheKey = "{$org}/{$repo}"; + if (array_key_exists($cacheKey, self::$apiCache)) { + return self::$apiCache[$cacheKey]; + } + + $baseUrl = rtrim(getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech', '/'); + $url = "{$baseUrl}/api/v1/repos/{$org}/{$repo}/manifest"; + + $ctx = stream_context_create([ + 'http' => [ + 'header' => "Authorization: token {$token}\r\nAccept: application/json\r\n", + 'timeout' => 5, + 'ignore_errors' => true, + ], + ]); + + $body = @file_get_contents($url, false, $ctx); + if ($body === false) { + self::$apiCache[$cacheKey] = null; + return null; + } + + $status = 0; + if (isset($http_response_header[0])) { + preg_match('/\d{3}/', $http_response_header[0], $m); + $status = (int) ($m[0] ?? 0); + } + if ($status < 200 || $status >= 300) { + self::$apiCache[$cacheKey] = null; + return null; + } + + $data = json_decode($body, true); + $entryPoint = $data['entry_point'] ?? ''; + + // Normalize: "source/" → "source", "cli/" → "cli" + $result = ($entryPoint !== '') ? rtrim($entryPoint, '/') : null; + + self::$apiCache[$cacheKey] = $result; + return $result; + } + + /** + * Resolve org/repo from GITHUB_REPOSITORY env or git remote. + * + * @return array{0: string, 1: string} + */ + private static function resolveOrgRepo(string $root): array + { + $envRepo = getenv('GITHUB_REPOSITORY') ?: ''; + if ($envRepo !== '' && str_contains($envRepo, '/')) { + return explode('/', $envRepo, 2); + } + + $remoteUrl = trim((string) @shell_exec( + 'git -C ' . escapeshellarg($root) . ' remote get-url origin 2>/dev/null' + )); + + if ($remoteUrl !== '' && preg_match('#[/:]([^/]+)/([^/]+?)(?:\.git)?$#', $remoteUrl, $m)) { + return [$m[1], $m[2]]; + } + + return ['', '']; + } + /** * Resolve the source directory as an absolute path. * @@ -181,7 +285,7 @@ class SourceResolver public static function warnIfLegacy(string $root): void { if (self::isLegacy($root)) { - fwrite(STDERR, "⚠ WARNING: This repo uses src/ which is deprecated. Rename to source/ per MokoStandards.\n"); + fwrite(STDERR, "⚠ WARNING: This repo uses src/ which is deprecated. Rename to source/ per MokoCli conventions.\n"); } } } diff --git a/lib/Enterprise/SynchronizationException.php b/lib/Enterprise/SynchronizationException.php index 6387da7..1e2c562 100644 --- a/lib/Enterprise/SynchronizationException.php +++ b/lib/Enterprise/SynchronizationException.php @@ -8,16 +8,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/SynchronizationException.php * BRIEF: Custom exception for repository synchronization errors */ declare(strict_types=1); -namespace MokoEnterprise; +namespace MokoCli; use RuntimeException; diff --git a/lib/Enterprise/TransactionManager.php b/lib/Enterprise/TransactionManager.php index 798e676..e4af502 100644 --- a/lib/Enterprise/TransactionManager.php +++ b/lib/Enterprise/TransactionManager.php @@ -9,15 +9,25 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Transaction * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Transaction + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/TransactionManager.php * BRIEF: Transaction manager for atomic operations */ /** +<<<<<<< HEAD * Transaction Manager for MokoCLI +======= + * Transaction Manager for mokoplatform +>>>>>>> main * * Provides atomic multi-step operations with automatic rollback: * - Transaction boundaries for ACID operations @@ -54,11 +64,15 @@ declare(strict_types=1); * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later */ -namespace MokoEnterprise; +namespace MokoCli; use DateTime; use DateTimeZone; diff --git a/lib/Enterprise/UnifiedValidation.php b/lib/Enterprise/UnifiedValidation.php index af623a1..4910672 100644 --- a/lib/Enterprise/UnifiedValidation.php +++ b/lib/Enterprise/UnifiedValidation.php @@ -9,15 +9,25 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Enterprise.Validation * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Enterprise.Validation + * INGROUP: MokoPlatform.Enterprise + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/Enterprise/UnifiedValidation.php * BRIEF: Unified validation framework */ /** +<<<<<<< HEAD * Unified Validation Framework for MokoCLI +======= + * Unified Validation Framework for mokoplatform +>>>>>>> main * * Consolidates all validation logic into a single framework with plugins. * Replaces 12+ individual validator scripts with a unified approach. @@ -53,11 +63,15 @@ declare(strict_types=1); * * @package MokoCLI\Enterprise * @version 04.00.04 +<<<<<<< HEAD * @author MokoCLI Team +======= + * @author mokoplatform Team +>>>>>>> main * @license GPL-3.0-or-later */ -namespace MokoEnterprise; +namespace MokoCli; use Exception; use RecursiveDirectoryIterator; diff --git a/lib/index.md b/lib/index.md index 53019e6..b281521 100644 --- a/lib/index.md +++ b/lib/index.md @@ -2,9 +2,15 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION +<<<<<<< HEAD DEFGROUP: MokoCLI.Index INGROUP: MokoCLI.Lib REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +DEFGROUP: MokoPlatform.Index +INGROUP: MokoPlatform.Lib +REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main PATH: /lib/index.md BRIEF: Library directory index --> diff --git a/lib/plugins/Joomla/UpdateXmlGenerator.php b/lib/plugins/Joomla/UpdateXmlGenerator.php index b697bc4..cbd7d81 100644 --- a/lib/plugins/Joomla/UpdateXmlGenerator.php +++ b/lib/plugins/Joomla/UpdateXmlGenerator.php @@ -8,16 +8,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Joomla * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Joomla + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /lib/plugins/Joomla/UpdateXmlGenerator.php * BRIEF: Generates and updates Joomla extension updates.xml files */ declare(strict_types=1); -namespace MokoStandards\Plugins\Joomla; +namespace MokoCli\Plugins\Joomla; use DOMDocument; use DOMElement; diff --git a/maintenance/index.md b/maintenance/index.md index 89cd2b0..54620fe 100644 --- a/maintenance/index.md +++ b/maintenance/index.md @@ -2,9 +2,15 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION +<<<<<<< HEAD DEFGROUP: MokoCLI.Index INGROUP: MokoCLI.Maintenance REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= +DEFGROUP: MokoPlatform.Index +INGROUP: MokoPlatform.Maintenance +REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main PATH: /maintenance/index.md BRIEF: Maintenance directory index --> diff --git a/maintenance/pin_action_shas.php b/maintenance/pin_action_shas.php index 9441f4f..0830faa 100644 --- a/maintenance/pin_action_shas.php +++ b/maintenance/pin_action_shas.php @@ -9,9 +9,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Scripts.Maintenance * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Scripts.Maintenance + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /maintenance/pin_action_shas.php * BRIEF: Pin GitHub Actions to immutable commit SHAs in workflow files * NOTE: Resolves tag/branch refs to commit SHAs via the GitHub API to satisfy @@ -23,10 +29,10 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; -use MokoEnterprise\Config; -use MokoEnterprise\GitPlatformAdapter; -use MokoEnterprise\PlatformAdapterFactory; +use MokoCli\CliFramework; +use MokoCli\Config; +use MokoCli\GitPlatformAdapter; +use MokoCli\PlatformAdapterFactory; class PinActionShasCli extends CliFramework { diff --git a/maintenance/repo_inventory.php b/maintenance/repo_inventory.php index 982e27f..4f49cf5 100644 --- a/maintenance/repo_inventory.php +++ b/maintenance/repo_inventory.php @@ -8,9 +8,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Maintenance * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Maintenance + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /maintenance/repo_inventory.php * BRIEF: Generate a live inventory dashboard of all governed repos as a GitHub issue */ @@ -19,14 +25,18 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class RepoInventoryCli extends CliFramework { private $api = null; private string $token = ''; private $platformConfig = null; +<<<<<<< HEAD private const ALWAYS_EXCLUDE = ['MokoCLI', '.github-private']; +======= + private const ALWAYS_EXCLUDE = ['mokoplatform', '.github-private']; +>>>>>>> main protected function configure(): void { @@ -37,9 +47,9 @@ class RepoInventoryCli extends CliFramework protected function initialize(): void { - $this->platformConfig = \MokoEnterprise\Config::load(); + $this->platformConfig = \MokoCli\Config::load(); try { - $adapter = \MokoEnterprise\PlatformAdapterFactory::create($this->platformConfig); + $adapter = \MokoCli\PlatformAdapterFactory::create($this->platformConfig); $this->api = $adapter->getApiClient(); } catch (\Exception $e) { $this->log('ERROR', "Platform init failed: " . $e->getMessage()); @@ -161,7 +171,11 @@ class RepoInventoryCli extends CliFramework if (!$this->dryRun) { $title = "dashboard: repository inventory ({$org})"; +<<<<<<< HEAD $issueQuery = "repos/{$org}/MokoCLI/issues" +======= + $issueQuery = "repos/{$org}/mokoplatform/issues" +>>>>>>> main . "?labels=inventory&state=all&per_page=1" . "&sort=created&direction=desc"; [$_, $existing] = $this->ghApi('GET', $issueQuery, null); @@ -169,7 +183,11 @@ class RepoInventoryCli extends CliFramework $num = $existing[0]['number']; $this->ghApi( 'PATCH', +<<<<<<< HEAD "repos/{$org}/MokoCLI/issues/{$num}", +======= + "repos/{$org}/mokoplatform/issues/{$num}", +>>>>>>> main [ 'title' => $title, 'body' => $body, @@ -181,7 +199,11 @@ class RepoInventoryCli extends CliFramework } else { [$_, $issue] = $this->ghApi( 'POST', +<<<<<<< HEAD "repos/{$org}/MokoCLI/issues", +======= + "repos/{$org}/mokoplatform/issues", +>>>>>>> main [ 'title' => $title, 'body' => $body, @@ -226,7 +248,11 @@ class RepoInventoryCli extends CliFramework CURLOPT_HTTPHEADER => [ 'Authorization: bearer ' . $this->token, 'Content-Type: application/json', +<<<<<<< HEAD 'User-Agent: MokoCLI-Inventory', +======= + 'User-Agent: mokoplatform-Inventory', +>>>>>>> main ], ]); $body = (string) curl_exec($ch); diff --git a/maintenance/rotate_secrets.php b/maintenance/rotate_secrets.php index 1d3050d..0a326fe 100644 --- a/maintenance/rotate_secrets.php +++ b/maintenance/rotate_secrets.php @@ -8,9 +8,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Maintenance * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Maintenance + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /maintenance/rotate_secrets.php * BRIEF: Audit FTP secrets and variables across all governed repos -- report missing or stale */ @@ -19,13 +25,17 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class RotateSecretsCli extends CliFramework { private $api = null; private string $token = ''; +<<<<<<< HEAD private const ALWAYS_EXCLUDE = ['MokoCLI', '.github-private']; +======= + private const ALWAYS_EXCLUDE = ['mokoplatform', '.github-private']; +>>>>>>> main private const ENVS = [ 'DEV' => [ 'vars' => ['DEV_FTP_HOST', 'DEV_FTP_PATH', 'DEV_FTP_USERNAME', 'DEV_FTP_SUFFIX'], @@ -53,9 +63,9 @@ class RotateSecretsCli extends CliFramework protected function initialize(): void { - $config = \MokoEnterprise\Config::load(); + $config = \MokoCli\Config::load(); try { - $adapter = \MokoEnterprise\PlatformAdapterFactory::create($config); + $adapter = \MokoCli\PlatformAdapterFactory::create($config); $this->api = $adapter->getApiClient(); } catch (\Exception $e) { $this->log('ERROR', "Platform init failed: " . $e->getMessage()); @@ -186,7 +196,11 @@ class RotateSecretsCli extends CliFramework . "| Repository | Issue |\n|---|---|\n" . "{$table}\n\n---\n" . "*Auto-created by `rotate_secrets.php`*\n"; +<<<<<<< HEAD $auditQuery = "repos/{$org}/MokoCLI/issues" +======= + $auditQuery = "repos/{$org}/mokoplatform/issues" +>>>>>>> main . "?labels=secret-audit&state=all" . "&per_page=1&sort=created&direction=desc"; [$_, $existing] = $this->ghApi('GET', $auditQuery, null); @@ -196,7 +210,11 @@ class RotateSecretsCli extends CliFramework $num = $existing[0]['number']; $this->ghApi( 'PATCH', +<<<<<<< HEAD "repos/{$org}/MokoCLI/issues/{$num}", +======= + "repos/{$org}/mokoplatform/issues/{$num}", +>>>>>>> main [ 'title' => $auditTitle, 'body' => $body, @@ -210,7 +228,11 @@ class RotateSecretsCli extends CliFramework } else { [$_, $issue] = $this->ghApi( 'POST', +<<<<<<< HEAD "repos/{$org}/MokoCLI/issues", +======= + "repos/{$org}/mokoplatform/issues", +>>>>>>> main [ 'title' => $auditTitle, 'body' => $body, diff --git a/maintenance/setup_labels.php b/maintenance/setup_labels.php index 5a5b61d..cd191bb 100644 --- a/maintenance/setup_labels.php +++ b/maintenance/setup_labels.php @@ -3,31 +3,47 @@ /* Copyright (C) 2026 Moko Consulting * +<<<<<<< HEAD * REQUIRED FILE: This file must be present in all MokoCLI-compliant repositories +======= + * REQUIRED FILE: This file must be present in all mokoplatform-compliant repositories +>>>>>>> main * * This file is part of a Moko Consulting project. * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Scripts.Maintenance * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /maintenance/setup_labels.php * BRIEF: REQUIRED label deployment script for all MokoCLI-governed repositories +======= + * DEFGROUP: MokoPlatform.Scripts.Maintenance + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform + * PATH: /maintenance/setup_labels.php + * BRIEF: REQUIRED label deployment script for all mokoplatform-governed repositories +>>>>>>> main */ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; -use MokoEnterprise\CliFramework; -use MokoEnterprise\Config; -use MokoEnterprise\GitPlatformAdapter; -use MokoEnterprise\PlatformAdapterFactory; +use MokoCli\CliFramework; +use MokoCli\Config; +use MokoCli\GitPlatformAdapter; +use MokoCli\PlatformAdapterFactory; /** +<<<<<<< HEAD * Deploys the standard set of repository labels required by MokoCLI. +======= + * Deploys the standard set of repository labels required by mokoplatform. +>>>>>>> main * * Uses the platform adapter (GitHub or Gitea) to create or update each label. * Supports --dry-run mode to preview without making changes. @@ -66,7 +82,11 @@ class SetupLabels extends CliFramework // Workflow / Process ['automation', '8B4513', 'Automated processes or scripts'], +<<<<<<< HEAD ['MokoCLI', 'B60205', 'MokoCLI compliance'], +======= + ['mokoplatform', 'B60205', 'mokoplatform compliance'], +>>>>>>> main ['needs-review', 'FBCA04', 'Awaiting code review'], ['work-in-progress', 'D93F0B', 'Work in progress, not ready for merge'], ['breaking-change', 'D73A4A', 'Breaking API or functionality change'], @@ -106,8 +126,13 @@ class SetupLabels extends CliFramework ['health: poor', 'FF6B6B', 'Health score below 50'], // Sync / Automation +<<<<<<< HEAD ['standards-update', 'B60205', 'MokoCLI sync update'], ['standards-drift', 'FBCA04', 'Repository drifted from MokoCLI'], +======= + ['standards-update', 'B60205', 'mokoplatform sync update'], + ['standards-drift', 'FBCA04', 'Repository drifted from mokoplatform'], +>>>>>>> main ['sync-report', '0075CA', 'Bulk sync run report'], ['sync-failure', 'D73A4A', 'Bulk sync failure requiring attention'], ['push-failure', 'D73A4A', 'File push failure requiring attention'], diff --git a/maintenance/sync_dolibarr_readmes.php b/maintenance/sync_dolibarr_readmes.php index 8e97585..b1ba92a 100644 --- a/maintenance/sync_dolibarr_readmes.php +++ b/maintenance/sync_dolibarr_readmes.php @@ -8,9 +8,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Scripts.Maintenance * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Scripts.Maintenance + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /maintenance/sync_dolibarr_readmes.php * BRIEF: Keeps root README.md and src/README.md in sync for Dolibarr module repositories * NOTE: Version format is zero-padded semver: XX.YY.ZZ (e.g. 04.00.04). All version regex @@ -21,7 +27,7 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; /** * Synchronises root README.md ↔ src/README.md for a Dolibarr module repository. @@ -77,8 +83,13 @@ class SyncDolibarrReadmes extends CliFramework $moduleName = $this->extractModuleName($rootContent, $repoRoot); $repoUrl = $this->extractField($rootContent, 'REPO', 'https://git.mokoconsulting.tech/MokoConsulting'); +<<<<<<< HEAD $defgroup = $this->extractField($rootContent, 'DEFGROUP', 'MokoCLI.Module'); $ingroup = $this->extractField($rootContent, 'INGROUP', 'MokoCLI'); +======= + $defgroup = $this->extractField($rootContent, 'DEFGROUP', 'MokoPlatform.Module'); + $ingroup = $this->extractField($rootContent, 'INGROUP', 'mokoplatform'); +>>>>>>> main $brief = $this->extractField($rootContent, 'BRIEF', "{$moduleName} end-user documentation"); $installSection = $this->extractSection($rootContent, 'Installation'); @@ -199,7 +210,7 @@ class SyncDolibarrReadmes extends CliFramework private function updateRootReadme(string $path, string $content, string $version, bool $dryRun): void { $updated = preg_replace( - '/(https:\/\/img\.shields\.io\/badge\/MokoStandards-)\d{2}\.\d{2}\.\d{2}/i', + '/(https:\/\/img\.shields\.io\/badge\/MokoCli-)\d{2}\.\d{2}\.\d{2}/i', '${1}' . $version, $content ); @@ -272,7 +283,11 @@ NOTE: This file is auto-generated by sync_dolibarr_readmes.php from root README. Last synced: {$today} --> +<<<<<<< HEAD [![MokoCLI](https://img.shields.io/badge/moko--platform-{$version}-blue)]({$repoUrl}) +======= +[![mokoplatform](https://img.shields.io/badge/moko--platform-{$version}-blue)]({$repoUrl}) +>>>>>>> main # {$moduleName} diff --git a/maintenance/update_repo_inventory.php b/maintenance/update_repo_inventory.php index 5a592f2..667d8a5 100644 --- a/maintenance/update_repo_inventory.php +++ b/maintenance/update_repo_inventory.php @@ -9,9 +9,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Scripts.Maintenance * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Scripts.Maintenance + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /maintenance/update_repo_inventory.php * BRIEF: Queries GitHub org repos and rewrites the auto-generated section of REPOSITORY_INVENTORY.md */ @@ -20,10 +26,10 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; -use MokoEnterprise\CliFramework; -use MokoEnterprise\Config; -use MokoEnterprise\GitPlatformAdapter; -use MokoEnterprise\PlatformAdapterFactory; +use MokoCli\CliFramework; +use MokoCli\Config; +use MokoCli\GitPlatformAdapter; +use MokoCli\PlatformAdapterFactory; /** * Queries the git platform API for all repositories in the organisation and @@ -205,7 +211,11 @@ class UpdateRepoInventory extends CliFramework $lower = strtolower($name); +<<<<<<< HEAD if (in_array('mokostandards-core', $topics, true) || $name === 'MokoCLI' || $name === '.github-private') { +======= + if (in_array('mokostandards-core', $topics, true) || $name === 'mokoplatform' || $name === '.github-private') { +>>>>>>> main $groups['core'][] = $repo; } elseif ( in_array('dolibarr-module', $topics, true) diff --git a/maintenance/update_sha_hashes.php b/maintenance/update_sha_hashes.php index 9b6223c..fd20731 100755 --- a/maintenance/update_sha_hashes.php +++ b/maintenance/update_sha_hashes.php @@ -9,9 +9,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Scripts.Maintenance * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Scripts.Maintenance + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /maintenance/update_sha_hashes.php * BRIEF: Update SHA-256 hashes in script registry */ @@ -20,7 +26,7 @@ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; -use MokoEnterprise\CliFramework; +use MokoCli\CliFramework; class UpdateShaHashesCli extends CliFramework { diff --git a/maintenance/update_version_from_readme.php b/maintenance/update_version_from_readme.php index 9ad3fd9..1d5ad6c 100644 --- a/maintenance/update_version_from_readme.php +++ b/maintenance/update_version_from_readme.php @@ -8,9 +8,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION +<<<<<<< HEAD * DEFGROUP: MokoCLI.Scripts.Maintenance * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +======= + * DEFGROUP: MokoPlatform.Scripts.Maintenance + * INGROUP: MokoPlatform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +>>>>>>> main * PATH: /maintenance/update_version_from_readme.php * BRIEF: Reads VERSION from README.md FILE INFORMATION block and propagates it to all badges and FILE INFORMATION headers * NOTE: README.md is the single source of truth for the repository version. @@ -22,14 +28,18 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; -use MokoEnterprise\{ApiClient, AuditLogger, CliFramework}; +use MokoCli\{ApiClient, AuditLogger, CliFramework}; /** * Propagates the version from README.md FILE INFORMATION block to every * badge and FILE INFORMATION VERSION field in the repository. * * Sources updated: +<<<<<<< HEAD * - Markdown badge: [![MokoCLI](https://img.shields.io/badge/moko--platform-OLD-blue)] +======= + * - Markdown badge: [![mokoplatform](https://img.shields.io/badge/moko--platform-OLD-blue)] +>>>>>>> main * - Markdown header: VERSION: OLD (inside comment blocks) * - PHP header: * VERSION: OLD (inside block comments) * - YAML/Shell header:# VERSION: OLD @@ -220,9 +230,13 @@ class UpdateVersionFromReadme extends CliFramework $updated = $original; // ── Badge replacement (all file types) ─────────────────────────── +<<<<<<< HEAD // shields.io badge: [![MokoCLI](...badge/moko--platform-XX.YY.ZZ-color)] +======= + // shields.io badge: [![mokoplatform](...badge/moko--platform-XX.YY.ZZ-color)] +>>>>>>> main $updated = preg_replace( - '/(\[!\[MokoStandards\]\(https:\/\/img\.shields\.io\/badge\/MokoStandards-)[0-9]{2}\.[0-9]{2}\.[0-9]{2}(-[a-z]+\)\])/', + '/(\[!\[MokoCli\]\(https:\/\/img\.shields\.io\/badge\/MokoCli-)[0-9]{2}\.[0-9]{2}\.[0-9]{2}(-[a-z]+\)\])/', '${1}' . $version . '${2}', $updated ); @@ -411,9 +425,9 @@ class UpdateVersionFromReadme extends CliFramework private function createDriftIssue(string $repo, string $version, int $remaining): void { if (!isset($this->apiClient)) { - $config = \MokoEnterprise\Config::load(); + $config = \MokoCli\Config::load(); try { - $adapter = \MokoEnterprise\PlatformAdapterFactory::create($config); + $adapter = \MokoCli\PlatformAdapterFactory::create($config); $this->apiClient = $adapter->getApiClient(); } catch (\Exception $e) { $this->error('Platform initialization failed: ' . $e->getMessage()); diff --git a/mcp/config.example.json b/mcp/config.example.json index 339f5a1..7586dbf 100644 --- a/mcp/config.example.json +++ b/mcp/config.example.json @@ -1,6 +1,11 @@ { +<<<<<<< HEAD "apiPath": "A:/mokocli", "standardsPath": "A:/mokocli", +======= + "apiPath": "A:/mokoplatform", + "standardsPath": "A:/mokoplatform", +>>>>>>> main "giteaUrl": "https://git.mokoconsulting.tech", "giteaToken": "your-gitea-api-token" } diff --git a/mcp/package.json b/mcp/package.json index 9f896d1..c79db9a 100644 --- a/mcp/package.json +++ b/mcp/package.json @@ -1,4 +1,5 @@ { +<<<<<<< HEAD "name": "@mokoconsulting/mokocli-mcp", "version": "1.0.0", "description": "MCP server for MokoCLI governance — validation, compliance, platform detection, definitions browser", @@ -7,6 +8,15 @@ "bin": { "moko-platform-mcp": "dist/index.js", "mokocli-mcp": "dist/index.js" +======= + "name": "@mokoconsulting/mokoplatform-mcp", + "version": "1.0.0", + "description": "MCP server for mokoplatform governance — validation, compliance, platform detection, definitions browser", + "type": "module", + "main": "dist/index.js", + "bin": { + "mokoplatform-mcp": "dist/index.js" +>>>>>>> main }, "scripts": { "build": "tsc", diff --git a/mcp/servers/mokobackup/.mokogitea/CLAUDE.md b/mcp/servers/mokobackup/.mokogitea/CLAUDE.md new file mode 100644 index 0000000..4a39740 --- /dev/null +++ b/mcp/servers/mokobackup/.mokogitea/CLAUDE.md @@ -0,0 +1,49 @@ +# mcp_mokobackup + +MCP server for database and file backups across Dolibarr, Joomla/Akeeba, Gitea, and file-based environments. + +## Quick Reference + +| Field | Value | +|---|---| +| **Package** | `@mokoconsulting/backup-mcp` | +| **Entry** | `dist/index.js` | +| **Config** | `~/.mcp_mokobackup.json` (override: `BACKUP_MCP_CONFIG` env var) | +| **Language** | TypeScript | +| **Branch** | develop on `dev`, merge to `main` (protected) | + +## Commands + +```bash +npm install # Install dependencies +npm run build # Compile TypeScript → dist/ +npm run dev # Watch mode +``` + +## Architecture + +``` +src/ +├── index.ts # MCP server entry, tool registration +├── config.ts # Loads ~/.mcp_mokobackup.json, resolves targets +├── client.ts # Backup execution logic +├── akeeba.ts # Akeeba Backup API integration (Joomla sites) +├── mokobackup.ts # MokoSuite Backup REST API integration +└── types.ts # BackupConfig, BackupTarget types +``` + +- Config defines **targets** — each target has a type (akeeba, dolibarr, mysql, files, gitea-db, gitea-files) +- Client-specific targets go in client repo configs, not global +- Dolibarr backups read `conf.php` via SSH to get DB credentials + +## Config + +Default config at `~/.mcp_mokobackup.json`. Client repos override via `BACKUP_MCP_CONFIG` env var pointing to their own config file (e.g. `A:/client-clarksvillefurs/.mcp_mokobackup.json`). + +## Rules + +- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, `*.min.css`/`*.min.js` +- **Attribution**: `Authored-by: Moko Consulting` +- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`) +- **Wiki**: documentation lives in the Gitea wiki, not `docs/` files +- **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki/Home) diff --git a/mcp/servers/mokobackup/.mokogitea/workflows/pr-check.yml b/mcp/servers/mokobackup/.mokogitea/workflows/pr-check.yml new file mode 100644 index 0000000..a92189f --- /dev/null +++ b/mcp/servers/mokobackup/.mokogitea/workflows/pr-check.yml @@ -0,0 +1,236 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: mokoplatform.CI +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokoplatform +# PATH: /templates/workflows/universal/pr-check.yml.template +# VERSION: 05.00.00 +# BRIEF: PR gate — branch policy + code validation before merge + +name: "Universal: PR Check" + +on: + pull_request: + types: [opened, synchronize, reopened, edited] + +permissions: + contents: read + pull-requests: write + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + # ── Branch Policy ────────────────────────────────────────────────────── + branch-policy: + name: Branch Policy + runs-on: ubuntu-latest + steps: + - name: Check branch merge target + run: | + HEAD="${{ github.head_ref }}" + BASE="${{ github.base_ref }}" + + echo "PR: ${HEAD} → ${BASE}" + + ALLOWED=true + REASON="" + + case "$HEAD" in + feature/*|feat/*) + if [ "$BASE" != "dev" ]; then + ALLOWED=false + REASON="Feature branches must target 'dev', not '${BASE}'" + fi + ;; + fix/*|bugfix/*) + if [ "$BASE" != "dev" ]; then + ALLOWED=false + REASON="Fix branches must target 'dev', not '${BASE}'" + fi + ;; + patch/*) + if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then + ALLOWED=false + REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'" + fi + ;; + hotfix/*) + if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then + ALLOWED=false + REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'" + fi + ;; + rc) + if [ "$BASE" != "main" ]; then + ALLOWED=false + REASON="RC branch can only merge into 'main', not '${BASE}'" + fi + ;; + dev) + if [ "$BASE" != "main" ]; then + ALLOWED=false + REASON="Dev branch can only merge into 'main', not '${BASE}'" + fi + ;; + esac + + if [ "$ALLOWED" = false ]; then + echo "::error::${REASON}" + echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "${REASON}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY + echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY + exit 1 + fi + + echo "Branch policy: OK (${HEAD} → ${BASE})" + echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY + + # ── Code Validation ──────────────────────────────────────────────────── + validate: + name: Validate PR + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Detect platform + id: platform + run: | + # Read platform from XML manifest ( tag) or plain text fallback + PLATFORM=$(sed -n 's/.*\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1) + [ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]') + [ -z "$PLATFORM" ] && PLATFORM="generic" + echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT" + + - name: Setup PHP + if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' + run: | + if ! command -v php &> /dev/null; then + sudo apt-get update -qq + sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1 + fi + + - name: PHP syntax check + if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' + run: | + ERRORS=0 + while IFS= read -r -d '' file; do + if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then + ERRORS=$((ERRORS + 1)) + fi + done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0) + echo "PHP lint: ${ERRORS} error(s)" + [ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; } + + - name: Validate platform manifest + run: | + PLATFORM="${{ steps.platform.outputs.platform }}" + case "$PLATFORM" in + 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 (WaaS site)" + exit 0 + fi + echo "Manifest: ${MANIFEST}" + if command -v php &> /dev/null; then + php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; } + fi + for ELEMENT in name version description; do + grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; } + done + echo "Joomla manifest valid" + ;; + dolibarr) + MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1) + if [ -z "$MOD_FILE" ]; then + echo "::error::No mod*.class.php found" + exit 1 + fi + echo "Dolibarr module: ${MOD_FILE}" + ;; + *) + echo "Generic platform — no manifest validation" + ;; + esac + + - name: Check update stream format + run: | + PLATFORM="${{ steps.platform.outputs.platform }}" + case "$PLATFORM" in + joomla) + if [ -f "updates.xml" ]; then + if command -v php &> /dev/null; then + php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; } + fi + echo "updates.xml valid" + fi + ;; + dolibarr) + [ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt" + ;; + esac + + - name: Check changelog has unreleased entry + run: | + if [ ! -f "CHANGELOG.md" ]; then + echo "::warning::No CHANGELOG.md found" + exit 0 + fi + # Check for content under [Unreleased] section + if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then + echo "::error::CHANGELOG.md missing [Unreleased] section" + exit 1 + fi + # Check there's at least one entry (Added/Changed/Fixed/Removed) under Unreleased + UNRELEASED_CONTENT=$(sed -n '/## \[Unreleased\]/,/## \[/p' CHANGELOG.md | grep -cE '^\s*-\s' || true) + if [ "$UNRELEASED_CONTENT" -eq 0 ]; then + echo "::error::CHANGELOG.md [Unreleased] section has no entries. Add a changelog entry describing your changes." + echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY + echo "The \`[Unreleased]\` section in CHANGELOG.md has no entries." >> $GITHUB_STEP_SUMMARY + echo "Add a line like \`- Description of your change\` under a heading (\`### Added\`, \`### Changed\`, \`### Fixed\`, etc.)" >> $GITHUB_STEP_SUMMARY + exit 1 + fi + echo "Changelog: ${UNRELEASED_CONTENT} entry/entries in [Unreleased]" + + - name: Verify package source + run: | + SOURCE_DIR="src" + [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" + if [ ! -d "$SOURCE_DIR" ]; then + echo "::warning::No src/ or htdocs/ directory" + exit 0 + fi + FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l) + echo "Source: ${FILE_COUNT} files" + [ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; } + + # ── Pre-Release RC Build ───────────────────────────────────────────────── + pre-release: + name: Build RC Package + runs-on: ubuntu-latest + needs: [branch-policy, validate] + + steps: + - name: Trigger RC pre-release + env: + GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + REPO: ${{ github.repository }} + BRANCH: ${{ github.head_ref }} + GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + run: | + curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}" + echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY + echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY diff --git a/mcp/servers/mokobackup/CHANGELOG.md b/mcp/servers/mokobackup/CHANGELOG.md new file mode 100644 index 0000000..c01e899 --- /dev/null +++ b/mcp/servers/mokobackup/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +## [1.0] — 2026-05-08 + +### Added +- Initial release diff --git a/mcp/servers/mokobackup/CONTRIBUTING.md b/mcp/servers/mokobackup/CONTRIBUTING.md new file mode 100644 index 0000000..2b4ccb8 --- /dev/null +++ b/mcp/servers/mokobackup/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing + +See [standards](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki). diff --git a/mcp/servers/mokobackup/README.md b/mcp/servers/mokobackup/README.md new file mode 100644 index 0000000..ff422e6 --- /dev/null +++ b/mcp/servers/mokobackup/README.md @@ -0,0 +1,76 @@ +# backup-mcp + +MCP server for database and file backups across Dolibarr and Joomla environments + +![Language](https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white) ![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green?style=flat-square) ![Wiki](https://img.shields.io/badge/wiki-backup-mcp-blue?style=flat-square) + + +Model Context Protocol server for database dumps, file backups, and Akeeba Backup integration on Joomla sites. + +--- + +| | | +|---|---| +| **Type** | MCP Server | +| **Language** | Node.js | +| **Tools** | 11 tools (6 SSH-based + 5 Akeeba API) | +| **License** | GPL-3.0-or-later | +| **Platform** | [Gitea](https://git.mokoconsulting.tech/MokoConsulting/backup-mcp) (primary) | + +--- + +## Overview + +backup-mcp provides two backup strategies through a single MCP server: + +| Strategy | Method | Tools | +|----------|--------|-------| +| **SSH Backups** | MySQL/PostgreSQL dumps and tar archives via SSH | `backup_database`, `backup_files`, `backup_list`, `backup_prune`, `backup_status`, `backup_list_targets` | +| **Akeeba Backups** | Joomla Web Services API (`/api/index.php/v1/akeebabackup/*`) | `akeeba_backup`, `akeeba_list`, `akeeba_download`, `akeeba_delete`, `akeeba_profiles` | + +Each client repo has its own `.backup-mcp.json` scoped via the `BACKUP_MCP_CONFIG` env var in `.mcp.json`. + +--- + +## Wiki Pages + +### Reference + +- [Tools Reference](https://git.mokoconsulting.tech/MokoConsulting/backup-mcp/wiki/Tools-Reference) -- all 11 tools with descriptions +- [Akeeba Integration](https://git.mokoconsulting.tech/MokoConsulting/backup-mcp/wiki/Akeeba-Integration) -- Akeeba Backup Pro setup, requirements, per-client workspace config + +--- + +## Related Wikis + +| Repo | Purpose | +|------|---------| +| [ssh-mcp](https://git.mokoconsulting.tech/MokoConsulting/ssh-mcp/wiki) | SSH server management (used for SSH-based backups) | +| [joomla-api-mcp](https://git.mokoconsulting.tech/MokoConsulting/joomla-api-mcp/wiki) | Joomla Web Services API MCP | +| [deploy-mcp](https://git.mokoconsulting.tech/MokoConsulting/deploy-mcp/wiki) | Git-based deployment MCP | + +--- + +> **[MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki)** -- central standards hub for all Moko Consulting projects. + +--- + + + +--- + +## Documentation + +Full documentation is available on the [Wiki](https://git.mokoconsulting.tech/MokoConsulting/backup-mcp/wiki). + +## Contributing + +See the wiki for development guidelines and contribution instructions. + +## License + +This project is licensed under the GNU General Public License v3.0 or later -- see the [LICENSE](LICENSE) file. + +--- + +*[Moko Consulting](https://mokoconsulting.tech) -- [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki/Home)* diff --git a/mcp/servers/mokobackup/SECURITY.md b/mcp/servers/mokobackup/SECURITY.md new file mode 100644 index 0000000..15e38fc --- /dev/null +++ b/mcp/servers/mokobackup/SECURITY.md @@ -0,0 +1,3 @@ +# Security + +Report to hello@mokoconsulting.tech. diff --git a/mcp/servers/mokobackup/config.example.json b/mcp/servers/mokobackup/config.example.json new file mode 100644 index 0000000..45901b0 --- /dev/null +++ b/mcp/servers/mokobackup/config.example.json @@ -0,0 +1,28 @@ +{ + "defaultTarget": "dolibarr-db", + "targets": { + "dolibarr-db": { + "name": "dolibarr", + "type": "mysql", + "sshHost": "crm.mokoconsulting.tech", + "sshUser": "mokoconsulting", + "sshKeyPath": "~/.ssh/id_ed25519", + "database": "dolibarr", + "dbUser": "dolibarr", + "dbPassword": "your-db-password", + "localBackupDir": "~/backups/dolibarr" + }, + "joomla-db": { + "name": "joomla", + "type": "mysql", + "sshHost": "waas.mokoconsulting.tech", + "sshUser": "mokoconsulting", + "sshKeyPath": "~/.ssh/id_ed25519", + "database": "joomla", + "dbUser": "joomla", + "dbPassword": "your-db-password", + "remotePaths": ["/var/www/html/images", "/var/www/html/media"], + "localBackupDir": "~/backups/joomla" + } + } +} diff --git a/mcp/servers/mokobackup/package.json b/mcp/servers/mokobackup/package.json new file mode 100644 index 0000000..91d2a41 --- /dev/null +++ b/mcp/servers/mokobackup/package.json @@ -0,0 +1,25 @@ +{ + "name": "@mokoconsulting/backup-mcp", + "version": "1.0.0", + "description": "MCP server for database and file backups across Dolibarr and Joomla environments", + "type": "module", + "main": "dist/index.js", + "bin": { "backup-mcp": "dist/index.js" }, + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "start": "node dist/index.js", + "clean": "rm -rf dist/" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.1", + "zod": "^3.24.4" + }, + "devDependencies": { + "@types/node": "^22.15.3", + "typescript": "^5.8.3" + }, + "engines": { "node": ">=20.0.0" }, + "license": "GPL-3.0-or-later", + "author": "Moko Consulting " +} diff --git a/mcp/servers/mokobackup/src/akeeba.ts b/mcp/servers/mokobackup/src/akeeba.ts new file mode 100644 index 0000000..f30380e --- /dev/null +++ b/mcp/servers/mokobackup/src/akeeba.ts @@ -0,0 +1,129 @@ +import * as https from 'node:https'; +import * as http from 'node:http'; +import { mkdirSync } from 'node:fs'; +import { join } from 'node:path'; +import type { BackupTarget, BackupResult, AkeebaBackupRecord } from './types.js'; + +const TIMEOUT_MS = 300_000; // 5 min for backup operations + +/** + * Akeeba Backup client using Joomla Web Services API + * Endpoint: /api/index.php/v1/akeebabackup/* + * Auth: Bearer token (Joomla API token) + */ +export class AkeebaClient { + private readonly target: BackupTarget; + private readonly baseUrl: string; + private readonly headers: Record; + + constructor(target: BackupTarget) { + this.target = target; + const site = (target.siteUrl ?? '').replace(/\/+$/, ''); + this.baseUrl = `${site}/api/index.php/v1/akeebabackup`; + this.headers = { + 'Authorization': `Bearer ${target.secretWord}`, + 'Content-Type': 'application/json', + 'Accept': 'application/vnd.api+json', + }; + } + + private request(url: string, method: string, body?: unknown): Promise<{ status: number; data: unknown }> { + return new Promise((resolve, reject) => { + const parsed = new URL(url); + const mod = parsed.protocol === 'https:' ? https : http; + const payload = body ? JSON.stringify(body) : undefined; + const opts: http.RequestOptions = { + hostname: parsed.hostname, + port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80), + path: parsed.pathname + parsed.search, + method, + headers: { ...this.headers, ...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}) }, + timeout: TIMEOUT_MS, + }; + + const req = mod.request(opts, (res) => { + const chunks: Buffer[] = []; + res.on('data', (c: Buffer) => chunks.push(c)); + res.on('end', () => { + const raw = Buffer.concat(chunks).toString(); + let data: unknown; + try { data = JSON.parse(raw); } catch { data = raw; } + resolve({ status: res.statusCode ?? 0, data }); + }); + }); + req.on('error', reject); + req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); }); + if (payload) req.write(payload); + req.end(); + }); + } + + async startBackup(profileId?: number, description?: string): Promise { + try { + const profile = profileId ?? this.target.profileId ?? 1; + const desc = description ?? `MCP backup ${new Date().toISOString()}`; + const res = await this.request(`${this.baseUrl}/backup`, 'POST', { + profile: profile, + description: desc, + }); + + if (res.status >= 400) { + return { success: false, message: `Akeeba backup failed: ${JSON.stringify(res.data)}` }; + } + + return { success: true, message: `Akeeba backup started (profile ${profile}): ${desc}` }; + } catch (err) { + return { success: false, message: `Akeeba backup failed: ${err}` }; + } + } + + async listBackups(limit = 20): Promise { + try { + const res = await this.request(`${this.baseUrl}/backups?page[limit]=${limit}`, 'GET'); + if (res.status >= 400) return []; + const body = res.data as { data?: Array<{ attributes: AkeebaBackupRecord }> }; + return (body.data ?? []).map(d => d.attributes); + } catch { return []; } + } + + async deleteBackup(id: string): Promise { + try { + const res = await this.request(`${this.baseUrl}/backup/${id}`, 'DELETE'); + if (res.status >= 400) return { success: false, message: `Delete failed: ${JSON.stringify(res.data)}` }; + return { success: true, message: `Deleted Akeeba backup ${id}` }; + } catch (err) { + return { success: false, message: `Delete failed: ${err}` }; + } + } + + async downloadBackup(id: string): Promise { + try { + mkdirSync(this.target.localBackupDir, { recursive: true }); + const res = await this.request(`${this.baseUrl}/backup/${id}/download`, 'GET'); + + if (res.status >= 400) { + return { success: false, message: `Download failed: ${JSON.stringify(res.data)}` }; + } + + const filename = `${this.target.name}-akeeba-${id}-${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)}.jpa`; + const localFile = join(this.target.localBackupDir, filename); + const fs = await import('node:fs/promises'); + + if (typeof res.data === 'string') { + const buffer = Buffer.from(res.data, 'base64'); + await fs.writeFile(localFile, buffer); + return { success: true, message: `Downloaded backup ${id}`, filePath: localFile, sizeBytes: buffer.length }; + } + + await fs.writeFile(localFile, JSON.stringify(res.data)); + return { success: true, message: `Downloaded backup ${id}`, filePath: localFile }; + } catch (err) { + return { success: false, message: `Download failed: ${err}` }; + } + } + + async getProfiles(): Promise { + const res = await this.request(`${this.baseUrl}/profiles`, 'GET'); + return res.data; + } +} diff --git a/mcp/servers/mokobackup/src/client.ts b/mcp/servers/mokobackup/src/client.ts new file mode 100644 index 0000000..946507e --- /dev/null +++ b/mcp/servers/mokobackup/src/client.ts @@ -0,0 +1,141 @@ +// Uses execFile (safe, no shell interpolation) for all SSH/command execution +import { execFile as execFileCb } from 'node:child_process'; +import { promisify } from 'node:util'; +import { mkdirSync, readdirSync, statSync } from 'node:fs'; +import { join } from 'node:path'; +import type { BackupTarget, BackupResult } from './types.js'; + +const execFile = promisify(execFileCb); +const TIMEOUT = 300_000; // 5 min for large backups + +export class BackupClient { + private readonly target: BackupTarget; + + constructor(target: BackupTarget) { + this.target = target; + mkdirSync(target.localBackupDir, { recursive: true }); + } + + private sshArgs(cmd: string): string[] { + const args = ['-o', 'StrictHostKeyChecking=accept-new', '-o', 'BatchMode=yes']; + if (this.target.sshPort) args.push('-p', String(this.target.sshPort)); + if (this.target.sshKeyPath) args.push('-i', this.target.sshKeyPath); + args.push(`${this.target.sshUser}@${this.target.sshHost}`, cmd); + return args; + } + + private timestamp(): string { + return new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); + } + + async dumpDatabase(): Promise { + const ts = this.timestamp(); + const filename = `${this.target.name}-db-${ts}.sql.gz`; + const localFile = join(this.target.localBackupDir, filename); + const dumpCmd = this.target.type === 'mysql' + ? `mysqldump -u ${this.target.dbUser} -p'${this.target.dbPassword}' ${this.target.database} | gzip` + : `PGPASSWORD='${this.target.dbPassword}' pg_dump -U ${this.target.dbUser} ${this.target.database} | gzip`; + + try { + const { stdout } = await execFile('ssh', this.sshArgs(dumpCmd), { timeout: TIMEOUT, maxBuffer: 500 * 1024 * 1024 }); + const fs = await import('node:fs/promises'); + await fs.writeFile(localFile, stdout, 'binary'); + const stat = statSync(localFile); + return { success: true, message: `Database backup: ${filename}`, filePath: localFile, sizeBytes: stat.size }; + } catch (err) { + return { success: false, message: `Database backup failed: ${err}` }; + } + } + + async backupFiles(): Promise { + const ts = this.timestamp(); + const filename = `${this.target.name}-files-${ts}.tar.gz`; + const localFile = join(this.target.localBackupDir, filename); + const paths = (this.target.remotePaths ?? []).join(' '); + const tarCmd = `tar czf - ${paths}`; + + try { + const { stdout } = await execFile('ssh', this.sshArgs(tarCmd), { timeout: TIMEOUT, maxBuffer: 500 * 1024 * 1024 }); + const fs = await import('node:fs/promises'); + await fs.writeFile(localFile, stdout, 'binary'); + const stat = statSync(localFile); + return { success: true, message: `File backup: ${filename}`, filePath: localFile, sizeBytes: stat.size }; + } catch (err) { + return { success: false, message: `File backup failed: ${err}` }; + } + } + + listBackups(): { name: string; size: number; date: Date }[] { + try { + return readdirSync(this.target.localBackupDir) + .filter(f => f.startsWith(this.target.name)) + .map(f => { + const stat = statSync(join(this.target.localBackupDir, f)); + return { name: f, size: stat.size, date: stat.mtime }; + }) + .sort((a, b) => b.date.getTime() - a.date.getTime()); + } catch { return []; } + } + + async parseDolibarrConf(): Promise<{ dbHost: string; dbName: string; dbUser: string; dbPass: string; dataRoot: string }> { + const confPath = this.target.confPath ?? '/htdocs/conf/conf.php'; + const cmd = `cat ${confPath}`; + const { stdout } = await execFile('ssh', this.sshArgs(cmd), { timeout: TIMEOUT }); + const get = (key: string) => { + const m = stdout.match(new RegExp(`\\$${key}\\s*=\\s*['"]([^'"]*)`)); + if (!m) throw new Error(`Could not find $${key} in ${confPath}`); + return m[1]; + }; + return { + dbHost: get('dolibarr_main_db_host'), + dbName: get('dolibarr_main_db_name'), + dbUser: get('dolibarr_main_db_user'), + dbPass: get('dolibarr_main_db_pass'), + dataRoot: get('dolibarr_main_data_root'), + }; + } + + async dumpDolibarr(): Promise { + const ts = this.timestamp(); + const conf = await this.parseDolibarrConf(); + + // 1. Database dump + const dbFilename = `${this.target.name}-db-${ts}.sql.gz`; + const dbLocalFile = join(this.target.localBackupDir, dbFilename); + const dumpCmd = `mysqldump -h ${conf.dbHost} -u ${conf.dbUser} -p'${conf.dbPass}' ${conf.dbName} | gzip`; + try { + const { stdout: dbOut } = await execFile('ssh', this.sshArgs(dumpCmd), { timeout: TIMEOUT, maxBuffer: 500 * 1024 * 1024 }); + const fs = await import('node:fs/promises'); + await fs.writeFile(dbLocalFile, dbOut, 'binary'); + + // 2. Documents + custom directories + const filesFilename = `${this.target.name}-files-${ts}.tar.gz`; + const filesLocalFile = join(this.target.localBackupDir, filesFilename); + const customDir = conf.dataRoot.replace(/\/documents\/?$/, '/custom'); + const tarCmd = `tar czf - ${conf.dataRoot} ${customDir} 2>/dev/null`; + const { stdout: tarOut } = await execFile('ssh', this.sshArgs(tarCmd), { timeout: TIMEOUT, maxBuffer: 500 * 1024 * 1024 }); + await fs.writeFile(filesLocalFile, tarOut, 'binary'); + + const dbStat = statSync(dbLocalFile); + const filesStat = statSync(filesLocalFile); + return { + success: true, + message: `Dolibarr backup complete:\n DB: ${dbFilename} (${(dbStat.size / 1024 / 1024).toFixed(1)} MB)\n Files: ${filesFilename} (${(filesStat.size / 1024 / 1024).toFixed(1)} MB)`, + filePath: dbLocalFile, + sizeBytes: dbStat.size + filesStat.size, + }; + } catch (err) { + return { success: false, message: `Dolibarr backup failed: ${err}` }; + } + } + + async pruneBackups(olderThanDays: number): Promise { + const cutoff = Date.now() - olderThanDays * 86400000; + const fs = await import('node:fs/promises'); + const backups = this.listBackups().filter(b => b.date.getTime() < cutoff); + for (const b of backups) { + await fs.unlink(join(this.target.localBackupDir, b.name)); + } + return { success: true, message: `Pruned ${backups.length} backups older than ${olderThanDays} days` }; + } +} diff --git a/mcp/servers/mokobackup/src/config.ts b/mcp/servers/mokobackup/src/config.ts new file mode 100644 index 0000000..6244886 --- /dev/null +++ b/mcp/servers/mokobackup/src/config.ts @@ -0,0 +1,29 @@ +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { homedir } from 'node:os'; +import type { BackupConfig, BackupTarget } from './types.js'; + +const CONFIG_FILENAME = '.mcp_mokobackup.json'; + +export async function loadConfig(): Promise { + const configPath = process.env.BACKUP_MCP_CONFIG + ? resolve(process.env.BACKUP_MCP_CONFIG) + : resolve(homedir(), CONFIG_FILENAME); + + const raw = await readFile(configPath, 'utf-8'); + const parsed = JSON.parse(raw) as Partial; + if (!parsed.targets || Object.keys(parsed.targets).length === 0) { + throw new Error(`No targets in ${configPath}`); + } + return { + targets: parsed.targets, + defaultTarget: parsed.defaultTarget ?? Object.keys(parsed.targets)[0], + }; +} + +export function getTarget(config: BackupConfig, name?: string): BackupTarget { + const key = name ?? config.defaultTarget; + const target = config.targets[key]; + if (!target) throw new Error(`Target "${key}" not found. Available: ${Object.keys(config.targets).join(', ')}`); + return target; +} diff --git a/mcp/servers/mokobackup/src/index.ts b/mcp/servers/mokobackup/src/index.ts new file mode 100644 index 0000000..9f7b538 --- /dev/null +++ b/mcp/servers/mokobackup/src/index.ts @@ -0,0 +1,128 @@ +#!/usr/bin/env node +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { z } from 'zod'; +import { loadConfig, getTarget } from './config.js'; +import { BackupClient } from './client.js'; +import { AkeebaClient } from './akeeba.js'; +import { MokoBackupClient } from './mokobackup.js'; +import type { BackupConfig } from './types.js'; + +let config: BackupConfig; + +function clientFor(t?: string): BackupClient { return new BackupClient(getTarget(config, t)); } + +/** + * Return the appropriate Joomla backup client based on target type. + * MokoBackup and Akeeba have the same interface — auto-detect which to use. + */ +function joomlaBackupFor(t?: string): AkeebaClient | MokoBackupClient { + const target = getTarget(config, t); + if (target.type === 'mokobackup') return new MokoBackupClient(target); + return new AkeebaClient(target); +} + +// Keep legacy function for backwards compatibility +function akeebaFor(t?: string): AkeebaClient | MokoBackupClient { return joomlaBackupFor(t); } +function text(s: string) { return { content: [{ type: 'text' as const, text: s }] }; } + +const T = { target: z.string().optional().describe('Backup target name') }; + +const server = new McpServer({ name: 'backup-mcp', version: '1.0.0' }); + +// ── SSH-based backups ──────────────────────────────────────────────── + +server.tool('backup_database', 'Dump database (MySQL/PostgreSQL) to local backup via SSH', { ...T }, + async ({ target }) => { const r = await clientFor(target).dumpDatabase(); return text(JSON.stringify(r, null, 2)); }); + +server.tool('backup_files', 'Backup remote directories to local tar.gz via SSH', { ...T }, + async ({ target }) => { const r = await clientFor(target).backupFiles(); return text(JSON.stringify(r, null, 2)); }); + +server.tool('backup_list', 'List available local backups with sizes and dates', { ...T }, + async ({ target }) => { + const backups = clientFor(target).listBackups(); + if (backups.length === 0) return text('No backups found'); + return text(backups.map(b => `${b.name} ${(b.size / 1024 / 1024).toFixed(1)} MB ${b.date.toISOString()}`).join('\n')); + }); + +server.tool('backup_prune', 'Delete local backups older than specified days', { + ...T, days: z.number().describe('Delete backups older than this many days'), +}, async ({ target, days }) => { const r = await clientFor(target).pruneBackups(days); return text(r.message); }); + +server.tool('backup_status', 'Show backup disk usage and last backup info', { ...T }, + async ({ target }) => { + const backups = clientFor(target).listBackups(); + const totalMB = backups.reduce((s, b) => s + b.size, 0) / 1024 / 1024; + const last = backups[0]; + return text(`Backups: ${backups.length}\nTotal size: ${totalMB.toFixed(1)} MB\nLast backup: ${last ? `${last.name} (${last.date.toISOString()})` : 'none'}`); + }); + +// ── Akeeba Backup (Joomla sites) ───────────────────────────────────── + +server.tool('akeeba_backup', 'Start a backup on a Joomla site (Akeeba or MokoBackup — auto-detected by target type)', { + ...T, description: z.string().optional().describe('Backup description'), +}, async ({ target, description }) => { + const r = await akeebaFor(target).startBackup(undefined, description); + return text(JSON.stringify(r, null, 2)); +}); + +server.tool('akeeba_list', 'List backup records on a Joomla site (Akeeba or MokoBackup)', { + ...T, limit: z.number().optional().describe('Number of records (default 20)'), +}, async ({ target, limit }) => { + const records = await akeebaFor(target).listBackups(limit ?? 20); + if (records.length === 0) return text('No backups found'); + return text(records.map(r => + `#${r.id} ${r.status} ${r.description} ${r.backupstart} ${r.archivename} ${(r.total_size / 1024 / 1024).toFixed(1)} MB` + ).join('\n')); +}); + +server.tool('akeeba_download', 'Download a backup archive from a Joomla site (Akeeba or MokoBackup)', { + ...T, backup_id: z.string().describe('Backup record ID'), +}, async ({ target, backup_id }) => { + const r = await akeebaFor(target).downloadBackup(backup_id); + return text(JSON.stringify(r, null, 2)); +}); + +server.tool('akeeba_delete', 'Delete a backup record from a Joomla site (Akeeba or MokoBackup)', { + ...T, backup_id: z.string().describe('Backup record ID'), +}, async ({ target, backup_id }) => { + const r = await akeebaFor(target).deleteBackup(backup_id); + return text(r.message); +}); + +server.tool('akeeba_profiles', 'List backup profiles on a Joomla site (Akeeba or MokoBackup)', { ...T }, + async ({ target }) => { + const profiles = await akeebaFor(target).getProfiles(); + return text(JSON.stringify(profiles, null, 2)); + }); + + +// ── Dolibarr Backup ───────────────────────────────────────────────── + +server.tool('dolibarr_backup', 'Backup a Dolibarr instance (DB + documents + custom) by reading conf.php via SSH', { + ...T, +}, async ({ target }) => { + const r = await clientFor(target).dumpDolibarr(); + return text(JSON.stringify(r, null, 2)); +}); + +server.tool('dolibarr_conf', 'Read and display Dolibarr database settings from conf.php via SSH (no passwords shown)', { + ...T, +}, async ({ target }) => { + const conf = await clientFor(target).parseDolibarrConf(); + return text(`DB Host: ${conf.dbHost}\nDB Name: ${conf.dbName}\nDB User: ${conf.dbUser}\nData Root: ${conf.dataRoot}`); +}); + +// ── General ────────────────────────────────────────────────────────── + +server.tool('backup_list_targets', 'List all configured backup targets', {}, + async () => text(Object.entries(config.targets).map(([k, v]) => { + const loc = (v.type === 'akeeba' || v.type === 'mokobackup') ? v.siteUrl : `${v.sshUser}@${v.sshHost}`; + return `${k}${k === config.defaultTarget ? ' (default)' : ''}: ${v.type} @ ${loc} → ${v.localBackupDir}`; + }).join('\n'))); + +async function main() { + config = await loadConfig(); + await server.connect(new StdioServerTransport()); +} +main().catch(err => { console.error(err); process.exit(1); }); diff --git a/mcp/servers/mokobackup/src/mokobackup.ts b/mcp/servers/mokobackup/src/mokobackup.ts new file mode 100644 index 0000000..da23d74 --- /dev/null +++ b/mcp/servers/mokobackup/src/mokobackup.ts @@ -0,0 +1,144 @@ +import * as https from 'node:https'; +import * as http from 'node:http'; +import { mkdirSync } from 'node:fs'; +import { join } from 'node:path'; +import type { BackupTarget, BackupResult, AkeebaBackupRecord } from './types.js'; + +const TIMEOUT_MS = 300_000; // 5 min for backup operations + +/** + * MokoSuite Backup client using Joomla Web Services API + * Endpoint: /api/index.php/v1/mokobackup/* + * Auth: Bearer token (Joomla API token) + * + * Wire-compatible with AkeebaClient — same interface, different base URL. + * The existing akeeba_* MCP tools work with both backends. + */ +export class MokoBackupClient { + private readonly target: BackupTarget; + private readonly baseUrl: string; + private readonly headers: Record; + + constructor(target: BackupTarget) { + this.target = target; + const site = (target.siteUrl ?? '').replace(/\/+$/, ''); + this.baseUrl = `${site}/api/index.php/v1/mokobackup`; + this.headers = { + 'Authorization': `Bearer ${target.secretWord}`, + 'Content-Type': 'application/json', + 'Accept': 'application/vnd.api+json', + }; + } + + private request(url: string, method: string, body?: unknown): Promise<{ status: number; data: unknown }> { + return new Promise((resolve, reject) => { + const parsed = new URL(url); + const mod = parsed.protocol === 'https:' ? https : http; + const payload = body ? JSON.stringify(body) : undefined; + const opts: http.RequestOptions = { + hostname: parsed.hostname, + port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80), + path: parsed.pathname + parsed.search, + method, + headers: { ...this.headers, ...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}) }, + timeout: TIMEOUT_MS, + }; + + const req = mod.request(opts, (res) => { + const chunks: Buffer[] = []; + res.on('data', (c: Buffer) => chunks.push(c)); + res.on('end', () => { + const raw = Buffer.concat(chunks).toString(); + let data: unknown; + try { data = JSON.parse(raw); } catch { data = raw; } + resolve({ status: res.statusCode ?? 0, data }); + }); + }); + req.on('error', reject); + req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); }); + if (payload) req.write(payload); + req.end(); + }); + } + + async startBackup(profileId?: number, description?: string): Promise { + try { + const profile = profileId ?? this.target.profileId ?? 1; + const desc = description ?? `MCP backup ${new Date().toISOString()}`; + const res = await this.request(`${this.baseUrl}/backup`, 'POST', { + profile: profile, + description: desc, + }); + + if (res.status >= 400) { + return { success: false, message: `MokoBackup failed: ${JSON.stringify(res.data)}` }; + } + + // MokoBackup returns {data: {success, message, record_id}} + const body = res.data as { data?: { success: boolean; message: string } }; + const msg = body?.data?.message ?? `Backup started (profile ${profile}): ${desc}`; + + return { success: true, message: msg }; + } catch (err) { + return { success: false, message: `MokoBackup failed: ${err}` }; + } + } + + async listBackups(limit = 20): Promise { + try { + const res = await this.request(`${this.baseUrl}/backups?page[limit]=${limit}`, 'GET'); + if (res.status >= 400) return []; + const body = res.data as { data?: Array<{ attributes: AkeebaBackupRecord }> }; + return (body.data ?? []).map(d => d.attributes); + } catch { return []; } + } + + async deleteBackup(id: string): Promise { + try { + const res = await this.request(`${this.baseUrl}/backup/${id}`, 'DELETE'); + if (res.status >= 400) return { success: false, message: `Delete failed: ${JSON.stringify(res.data)}` }; + return { success: true, message: `Deleted MokoBackup record ${id}` }; + } catch (err) { + return { success: false, message: `Delete failed: ${err}` }; + } + } + + async downloadBackup(id: string): Promise { + try { + mkdirSync(this.target.localBackupDir, { recursive: true }); + const res = await this.request(`${this.baseUrl}/backup/${id}/download`, 'GET'); + + if (res.status >= 400) { + return { success: false, message: `Download failed: ${JSON.stringify(res.data)}` }; + } + + const filename = `${this.target.name}-mokobackup-${id}-${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)}.zip`; + const localFile = join(this.target.localBackupDir, filename); + const fs = await import('node:fs/promises'); + + if (typeof res.data === 'string') { + const buffer = Buffer.from(res.data, 'base64'); + await fs.writeFile(localFile, buffer); + return { success: true, message: `Downloaded backup ${id}`, filePath: localFile, sizeBytes: buffer.length }; + } + + // Handle JSON-wrapped response + const body = res.data as { data?: string }; + if (body?.data && typeof body.data === 'string') { + const buffer = Buffer.from(body.data, 'base64'); + await fs.writeFile(localFile, buffer); + return { success: true, message: `Downloaded backup ${id}`, filePath: localFile, sizeBytes: buffer.length }; + } + + await fs.writeFile(localFile, JSON.stringify(res.data)); + return { success: true, message: `Downloaded backup ${id}`, filePath: localFile }; + } catch (err) { + return { success: false, message: `Download failed: ${err}` }; + } + } + + async getProfiles(): Promise { + const res = await this.request(`${this.baseUrl}/profiles`, 'GET'); + return res.data; + } +} diff --git a/mcp/servers/mokobackup/src/types.ts b/mcp/servers/mokobackup/src/types.ts new file mode 100644 index 0000000..bedb48a --- /dev/null +++ b/mcp/servers/mokobackup/src/types.ts @@ -0,0 +1,58 @@ +export interface BackupTarget { + name: string; + type: 'mysql' | 'postgres' | 'files' | 'akeeba' | 'mokobackup' | 'dolibarr'; + sshHost?: string; + sshPort?: number; + sshUser?: string; + sshKeyPath?: string; + database?: string; + dbUser?: string; + dbPassword?: string; + remotePaths?: string[]; + localBackupDir: string; + /** Akeeba/MokoBackup: site base URL (e.g. https://clarksvillefurs.com) */ + siteUrl?: string; + /** Akeeba/MokoBackup: Joomla API token (Bearer auth) */ + secretWord?: string; + /** Akeeba/MokoBackup: backup profile ID (default 1) */ + profileId?: number; + /** Dolibarr-specific: path to conf/conf.php on the remote server */ + confPath?: string; +} + +export interface BackupConfig { + targets: Record; + defaultTarget: string; +} + +export interface BackupResult { + success: boolean; + message: string; + filePath?: string; + sizeBytes?: number; +} + +export interface AkeebaBackupRecord { + id: string; + description: string; + status: string; + origin: string; + type: string; + profile_id: number; + archivename: string; + absolute_path: string; + multipart: number; + tag: string; + backupstart: string; + backupend: string; + filesexist: number; + remote_filename: string; + total_size: number; +} + +export interface AkeebaApiResponse { + body: { + status: number; + data: unknown; + }; +} diff --git a/mcp/servers/mokobackup/tsconfig.json b/mcp/servers/mokobackup/tsconfig.json new file mode 100644 index 0000000..0dd168c --- /dev/null +++ b/mcp/servers/mokobackup/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/mcp/servers/mokocrm_api/.mokogitea/CLAUDE.md b/mcp/servers/mokocrm_api/.mokogitea/CLAUDE.md new file mode 100644 index 0000000..d77ecb9 --- /dev/null +++ b/mcp/servers/mokocrm_api/.mokogitea/CLAUDE.md @@ -0,0 +1,44 @@ +# mcp_mokocrm + +MCP server for Dolibarr ERP/CRM REST API operations — third parties, invoices, proposals, projects, tasks, contacts, and business management. + +## Quick Reference + +| Field | Value | +|---|---| +| **Package** | `@mokoconsulting/mcp-mokocrm-api` | +| **Entry** | `dist/index.js` | +| **Config** | `~/.mcp_mokocrm.json` (override: `DOLIBARR_API_MCP_CONFIG` env var) | +| **Language** | TypeScript | +| **Branch** | develop on `dev`, merge to `main` (protected) | + +## Commands + +```bash +npm install # Install dependencies +npm run build # Compile TypeScript → dist/ +npm run dev # Watch mode +``` + +## Architecture + +``` +src/ +├── index.ts # MCP server entry, tool registration +├── config.ts # Loads ~/.mcp_mokocrm.json, resolves connections +├── client.ts # Dolibarr REST API client wrapper +├── tools/ # Individual tool implementations +└── types.ts # DolibarrConfig, DolibarrConnection types +``` + +- Config defines **connections** — each is a Dolibarr instance with URL + API key +- Default connections: production (crm.mokoconsulting.tech), dev (crm.dev.mokoconsulting.tech) +- No demo environment for CRM + +## Rules + +- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, `*.min.css`/`*.min.js` +- **Attribution**: `Authored-by: Moko Consulting` +- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`) +- **Wiki**: documentation lives in the Gitea wiki, not `docs/` files +- **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki/Home) diff --git a/mcp/servers/mokocrm_api/CHANGELOG.md b/mcp/servers/mokocrm_api/CHANGELOG.md new file mode 100644 index 0000000..16f075c --- /dev/null +++ b/mcp/servers/mokocrm_api/CHANGELOG.md @@ -0,0 +1,62 @@ + + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.0] - 2026-05-07 + +### Added +- Initial MCP server with Dolibarr REST API tools +- Third party management (list, get, create, update, delete) +- Invoice management (list, get, create, add lines, validate, set paid) +- Commercial proposal management (list, get, create, add lines, validate, close) +- Customer order management (list, get, create, validate) +- Product and service catalog (list, get, create, update, stock levels) +- Contact/address management (list, get) +- Project management (list, get, create) +- Task management (list, get) +- User management (list, get) +- Category management (list by type) +- Bank account listing +- Supplier invoice listing +- Supplier order listing +- Warehouse listing +- Company setup and system status endpoints +- Raw API passthrough for any Dolibarr endpoint +- Multi-connection support with named connections +- Interactive setup wizard (`npm run setup`) +- SQL filter builder (`buildSqlFilter`, `searchFilter`) for safe query construction +- Full documentation: README, INSTALLATION, ARCHITECTURE, API reference +- MokoCli-compliant project structure +- 12 Gitea Actions CI/CD workflows + +## Revision History + +| Date | Version | Author | Notes | +| --- | --- | --- | --- | +| 2026-05-07 | 0.0.1 | jmiller | Initial release | diff --git a/mcp/servers/mokocrm_api/CONTRIBUTING.md b/mcp/servers/mokocrm_api/CONTRIBUTING.md new file mode 100644 index 0000000..a74f168 --- /dev/null +++ b/mcp/servers/mokocrm_api/CONTRIBUTING.md @@ -0,0 +1,161 @@ + + +# Contributing to dolibarr-api-mcp + +We appreciate your interest in contributing to this project! This document provides guidelines for contributing. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [Getting Started](#getting-started) +- [How to Contribute](#how-to-contribute) +- [Development Workflow](#development-workflow) +- [Commit Messages](#commit-messages) +- [Pull Request Process](#pull-request-process) + +## Code of Conduct + +This project adheres to the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to hello@mokoconsulting.tech. + +## Getting Started + +1. Fork the repository +2. Clone your fork locally +3. Install dependencies: `npm install` +4. Build: `npm run build` +5. Create a new branch for your work + +## How to Contribute + +### Reporting Bugs + +- Use the Gitea issue tracker +- Describe the bug clearly with steps to reproduce +- Include the Dolibarr version you're connecting to +- Include relevant logs or error messages + +### Adding New Tools + +If you want to add support for a Dolibarr API endpoint not yet covered: + +1. Check the [Dolibarr API Explorer](https://your-dolibarr.com/api/index.php/explorer) for endpoint details +2. Add the tool registration in `src/index.ts` following the existing patterns +3. Update `docs/API.md` with the new tool's parameter table +4. Update `README.md` tool listing +5. Update `CHANGELOG.md` + +### Contributing Code + +- Pick an issue or create one +- Fork the repository and create a branch +- Make your changes following the project conventions +- Submit a pull request + +## Development Workflow + +1. Ensure your fork is up to date with the main repository +2. Create a feature branch from `main` +3. Make your changes +4. Test against a Dolibarr instance (use `npm run setup` to configure a dev connection) +5. Build with `npm run build` to catch TypeScript errors +6. Commit your changes with clear messages +7. Push to your fork +8. Create a pull request + +## Commit Messages + +Follow the conventional commit format: + +``` +(): + + + +