diff --git a/.mokogitea/CLAUDE.md b/.mokogitea/CLAUDE.md index 4694270..e719206 100644 --- a/.mokogitea/CLAUDE.md +++ b/.mokogitea/CLAUDE.md @@ -38,7 +38,7 @@ composer check # Run all checks | `lib/Enterprise/Plugins/` | 11 platform plugins (Joomla, Dolibarr, Node.js, Python, etc.) | | `deploy/` | SFTP deployment scripts (Joomla, Dolibarr, health checks) | | `templates/` | Universal templates, configs, governance schema | -| `.mokogitea/workflows/` | CI/CD workflows (Gitea Actions) | +| `.mokogitea/workflows/` | CI/CD workflows (MokoGitea Actions) | | `bin/moko` | Unified CLI dispatcher — `php bin/moko ` | | `monitoring/sites.json` | Sites list for mcp_mokomonitor | @@ -71,6 +71,6 @@ PHPStan runs with `--memory-limit=512M`. CI enforces PHPCS errors; PHPStan is `c - **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 +- **Wiki**: documentation lives in the MokoGitea wiki, not `docs/` files - **New CLI tools**: extend `CliFramework`, not `CLIApp` (legacy) - **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home) diff --git a/.mokogitea/branch-protection.yml b/.mokogitea/branch-protection.yml index 82f5650..25ab04d 100644 --- a/.mokogitea/branch-protection.yml +++ b/.mokogitea/branch-protection.yml @@ -124,7 +124,8 @@ jobs: "rule_name": "main", "enable_push": true, "enable_push_whitelist": true, - "push_whitelist_usernames": ["jmiller"], + "push_whitelist_usernames": ["jmiller", "moko-deploy"], + "push_whitelist_actions_user": true, "enable_force_push": false, "enable_force_push_allowlist": false, "force_push_allowlist_usernames": [], @@ -140,10 +141,12 @@ jobs: "rule_name": "dev", "enable_push": true, "enable_push_whitelist": true, - "push_whitelist_usernames": ["jmiller"], - "enable_force_push": false, - "enable_force_push_allowlist": false, - "force_push_allowlist_usernames": [], + "push_whitelist_usernames": ["jmiller", "moko-deploy"], + "push_whitelist_actions_user": true, + "enable_force_push": true, + "enable_force_push_allowlist": true, + "force_push_allowlist_usernames": ["jmiller", "moko-deploy"], + "force_push_allowlist_actions_user": true, "enable_merge_whitelist": false, "required_approvals": 0, "block_on_rejected_reviews": false, @@ -154,10 +157,12 @@ jobs: "rule_name": "rc", "enable_push": true, "enable_push_whitelist": true, - "push_whitelist_usernames": ["jmiller"], - "enable_force_push": false, - "enable_force_push_allowlist": false, - "force_push_allowlist_usernames": [], + "push_whitelist_usernames": ["jmiller", "moko-deploy"], + "push_whitelist_actions_user": true, + "enable_force_push": true, + "enable_force_push_allowlist": true, + "force_push_allowlist_usernames": ["jmiller", "moko-deploy"], + "force_push_allowlist_actions_user": true, "enable_merge_whitelist": false, "required_approvals": 0, "block_on_rejected_reviews": false, @@ -168,7 +173,8 @@ jobs: "rule_name": "beta", "enable_push": true, "enable_push_whitelist": true, - "push_whitelist_usernames": ["jmiller"], + "push_whitelist_usernames": ["jmiller", "moko-deploy"], + "push_whitelist_actions_user": true, "enable_force_push": false, "enable_force_push_allowlist": false, "force_push_allowlist_usernames": [], @@ -182,7 +188,8 @@ jobs: "rule_name": "alpha", "enable_push": true, "enable_push_whitelist": true, - "push_whitelist_usernames": ["jmiller"], + "push_whitelist_usernames": ["jmiller", "moko-deploy"], + "push_whitelist_actions_user": true, "enable_force_push": false, "enable_force_push_allowlist": false, "force_push_allowlist_usernames": [], diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml index 6c13103..91e16f7 100644 --- a/.mokogitea/workflows/auto-bump.yml +++ b/.mokogitea/workflows/auto-bump.yml @@ -3,9 +3,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow +# DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Release -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # PATH: /.mokogitea/workflows/auto-bump.yml # VERSION: 09.02.00 # BRIEF: Auto patch-bump version on every push to dev (skips merge commits) @@ -22,7 +22,7 @@ on: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} permissions: contents: write @@ -34,7 +34,8 @@ jobs: if: >- !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip bump]') && - !startsWith(github.event.head_commit.message, 'Merge pull request') + !startsWith(github.event.head_commit.message, 'Merge pull request') && + !startsWith(github.event.repository.name, 'Template-') steps: - name: Checkout @@ -52,7 +53,7 @@ jobs: echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV" else git clone --depth 1 --branch main --quiet \ - "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \ + "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/MokoCLI.git" \ /tmp/mokocli cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV" diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index 6c65f3b..a7563e0 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -3,16 +3,16 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow +# DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Release -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli -# PATH: /templates/workflows/universal/auto-release.yml.template -# VERSION: 05.00.00 +# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic +# PATH: /.mokogitea/workflows/auto-release.yml +# VERSION: 05.01.00 # BRIEF: Universal build & release � detects platform from manifest.xml # -# +========================================================================+ +# +=======================================================================+ # | UNIVERSAL BUILD & RELEASE PIPELINE | -# +========================================================================+ +# +=======================================================================+ # | | # | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. | # | | @@ -21,15 +21,24 @@ # | dolibarr: mod*.class.php, update.txt, dev version reset | # | generic: README-only, no update stream | # | | -# +========================================================================+ +# +=======================================================================+ name: "Universal: Build & Release" on: pull_request: - types: [opened, closed] + types: [opened, synchronize, closed] branches: - main + paths-ignore: + - '.mokogitea/workflows/**' + - '*.md' + - 'wiki/**' + - '.editorconfig' + - '.gitignore' + - '.gitattributes' + - '.gitmessage' + - 'LICENSE' workflow_dispatch: inputs: action: @@ -43,7 +52,7 @@ on: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} @@ -51,13 +60,18 @@ permissions: contents: write jobs: - # ── PR Opened → Rename branch to RC and build RC release ───────────────────── + # ── PR Opened → Rename branch to RC and build RC release ───────────────────────── promote-rc: name: Promote to RC runs-on: release + # Skip on template repos (Template-*) — they scaffold other repos and do not release. if: >- - (github.event.action == 'opened' && github.event.pull_request.merged != true) || - (github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc') + !startsWith(github.event.repository.name, 'Template-') && + ( + (github.event.action == 'opened' && github.event.pull_request.merged != true) || + (github.event.action == 'synchronize' && github.event.pull_request.merged != true) || + (github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc') + ) steps: - name: Checkout repository @@ -65,6 +79,7 @@ jobs: with: token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 1 + submodules: recursive - name: Setup mokocli tools env: @@ -80,7 +95,7 @@ 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 - CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git + CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli cd /tmp/mokocli composer install --no-dev --no-interaction --quiet @@ -89,18 +104,46 @@ jobs: - name: Rename branch to rc run: | - php ${MOKO_CLI}/branch_rename.php \ - --from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" \ - --api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \ - --pr "${{ github.event.pull_request.number }}" + API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + AUTH="Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" + FROM="${{ github.event.pull_request.head.ref || 'dev' }}" + PR="${{ github.event.pull_request.number }}" + + # Resolve the source branch HEAD commit. + SRC_JSON=$(curl -sf -H "$AUTH" "${API_BASE}/branches/${FROM}") \ + || { echo "::error::Source branch ${FROM} not found"; exit 1; } + SRC_SHA=$(printf '%s' "$SRC_JSON" | python3 -c "import sys, json; print(json.load(sys.stdin)['commit']['id'])" 2>/dev/null || true) + [ -n "$SRC_SHA" ] || { echo "::error::Could not resolve HEAD of ${FROM}"; exit 1; } + + # Point rc at the source commit. If rc already exists (a protected branch that + # cannot be deleted), force-update its ref in place instead of delete+recreate: + # deleting a protected branch fails, which then makes the recreate return HTTP 409. + if curl -sf -o /dev/null -H "$AUTH" "${API_BASE}/branches/rc"; then + echo "rc exists - force-updating to ${FROM} (${SRC_SHA})" + curl -sf -X PATCH -H "$AUTH" -H "Content-Type: application/json" \ + "${API_BASE}/git/refs/heads/rc" -d "{\"sha\":\"${SRC_SHA}\",\"force\":true}" \ + || { echo "::error::Failed to force-update rc (CI token needs force-push on the protected rc branch)"; exit 1; } + else + echo "Creating rc from ${FROM}" + curl -sf -X POST -H "$AUTH" -H "Content-Type: application/json" \ + "${API_BASE}/branches" -d "{\"new_branch_name\":\"rc\",\"old_branch_name\":\"${FROM}\"}" \ + || { echo "::error::Failed to create rc from ${FROM}"; exit 1; } + fi + + # Repoint the PR at rc, then delete the old source branch (non-fatal). + if [ -n "$PR" ]; then + curl -s -X PATCH -H "$AUTH" -H "Content-Type: application/json" \ + "${API_BASE}/pulls/${PR}" -d '{"head":"rc"}' >/dev/null || true + fi + curl -s -X DELETE -H "$AUTH" "${API_BASE}/branches/${FROM}" >/dev/null || true + echo "Renamed ${FROM} -> rc" - name: Checkout rc and configure git run: | git fetch origin rc git checkout rc - git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" - git config --local user.name "gitea-actions[bot]" + git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech" + git config --local user.name "mokogitea-actions[bot]" git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" - name: Publish RC release @@ -111,7 +154,7 @@ jobs: - name: Update RC release notes from CHANGELOG.md run: | - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" # Extract [Unreleased] section from changelog @@ -149,13 +192,17 @@ jobs: echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY - # ── Merged PR → Build & Release (or promote RC to stable) ──────────────────── + # ── Merged PR → Build & Release (or promote RC to stable) ───────────────────────── release: name: Build & Release Pipeline runs-on: release + # Skip on template repos (Template-*) — they scaffold other repos and do not release. if: >- - github.event.pull_request.merged == true || - (github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc') + !startsWith(github.event.repository.name, 'Template-') && + ( + github.event.pull_request.merged == true || + (github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc') + ) steps: - name: Checkout repository @@ -163,11 +210,12 @@ jobs: with: token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 0 + submodules: recursive - name: Configure git for bot pushes run: | - git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" - git config --local user.name "gitea-actions[bot]" + git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech" + git config --local user.name "mokogitea-actions[bot]" git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" - name: Check for merge conflict markers @@ -198,7 +246,7 @@ 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 - CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git + CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli cd /tmp/mokocli composer install --no-dev --no-interaction --quiet @@ -241,14 +289,50 @@ jobs: 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" + PLATFORM="${{ steps.platform.outputs.platform }}" + if [[ "$PLATFORM" == joomla* ]]; then + echo "tag=stable" >> "$GITHUB_OUTPUT" + echo "release_tag=stable" >> "$GITHUB_OUTPUT" + else + echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT" + echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT" + fi echo "branch=main" >> "$GITHUB_OUTPUT" echo "Published version: ${VERSION}" + - name: "Create semver tag for non-Joomla repos" + id: semver + if: | + steps.version.outputs.skip != 'true' && + !startsWith(steps.platform.outputs.platform, 'joomla') + run: | + VERSION="${{ steps.version.outputs.version }}" + API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" + SEMVER_TAG="v${VERSION}" + + echo "Creating semver tag: ${SEMVER_TAG}" + + # Create the git tag via API + HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \ + -X POST -H "Authorization: token ${TOKEN}" \ + -H "Content-Type: application/json" \ + "${API_BASE}/tags" \ + -d "{\"tag_name\":\"${SEMVER_TAG}\",\"target\":\"main\",\"message\":\"Release ${VERSION}\"}" 2>/dev/null || echo "000") + + if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then + echo "Created semver tag: ${SEMVER_TAG}" + elif [ "$HTTP_CODE" = "409" ]; then + echo "Semver tag ${SEMVER_TAG} already exists (skipped)" + else + echo "::warning::Failed to create semver tag ${SEMVER_TAG} (HTTP ${HTTP_CODE})" + fi + + echo "semver_tag=${SEMVER_TAG}" >> "$GITHUB_OUTPUT" + - name: Update release notes and promote changelog run: | - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" # Get the stable release info (version and ID) @@ -298,7 +382,7 @@ jobs: content = open('CHANGELOG.md').read() old = '## [Unreleased]' new = f'## [Unreleased]\n\n## [{version}] --- {date}' - content = content.replace(old, new, 1) + content = content if ('## [' + version + ']') in content else content.replace(old, new, 1) open('CHANGELOG.md', 'w').write(content) " "$VERSION" "$DATE" git add CHANGELOG.md @@ -317,7 +401,7 @@ jobs: VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" RELEASE_TAG="${{ steps.version.outputs.release_tag }}" GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" php ${MOKO_CLI}/release_mirror.php \ --version "$VERSION" --tag "$RELEASE_TAG" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ @@ -342,11 +426,11 @@ jobs: && echo "main branch pushed to GitHub mirror" \ || echo "WARNING: GitHub mirror push failed" - - name: "Step 11: Delete rc branch and recreate dev from main" + - name: "Step 11: Delete rc branch (dev reset moved to cascade-dev.yml)" if: steps.version.outputs.skip != 'true' continue-on-error: true run: | - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" # Delete rc branch (ephemeral — created by promote-rc) @@ -354,23 +438,15 @@ jobs: "${API_BASE}/branches/rc" 2>/dev/null \ && echo "Deleted rc branch" || echo "rc branch not found" - # Delete dev branch - curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \ - "${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch" - - # Recreate dev from main (now includes version bump + changelog promotion) - curl -sf -X POST -H "Authorization: token ${TOKEN}" \ - -H "Content-Type: application/json" \ - "${API_BASE}/branches" \ - -d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main" - - echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY + # dev is reset from main by the dedicated "Cascade Main -> Dev" workflow + # (cascade-dev.yml), which runs after this release completes. + echo "rc cleaned; dev reset handled by cascade-dev.yml" >> $GITHUB_STEP_SUMMARY - name: "Step 12: Create version branch from main" if: steps.version.outputs.skip != 'true' continue-on-error: true run: | - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" BRANCH_NAME="version/${VERSION}" @@ -391,7 +467,7 @@ jobs: if: steps.version.outputs.skip != 'true' continue-on-error: true run: | - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" + API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" php ${MOKO_CLI}/version_reset_dev.php \ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \ --branch dev --path . 2>&1 || true @@ -417,5 +493,5 @@ jobs: echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY + echo "| Release | [View](${MOKOGITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY fi diff --git a/.mokogitea/workflows/branch-cleanup.yml b/.mokogitea/workflows/branch-cleanup.yml index 91a1f9b..25ebae1 100644 --- a/.mokogitea/workflows/branch-cleanup.yml +++ b/.mokogitea/workflows/branch-cleanup.yml @@ -3,9 +3,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoCli.Universal -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +# DEFGROUP: MokoGitea.Workflow +# INGROUP: MokoStandards.Universal +# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # PATH: /.mokogitea/workflows/branch-cleanup.yml # VERSION: 01.00.00 # BRIEF: Delete feature branches after PR merge @@ -33,7 +33,8 @@ jobs: run: | BRANCH="${{ github.event.pull_request.head.ref }}" API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches" - ENCODED=$(php -r "echo rawurlencode('${BRANCH}');") + # URL-encode the branch name's slashes (no PHP dependency on the runner) + ENCODED=$(printf '%s' "${BRANCH}" | sed 's|/|%2F|g') STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \ -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \ diff --git a/.mokogitea/workflows/branch-protection-enforce.yml b/.mokogitea/workflows/branch-protection-enforce.yml new file mode 100644 index 0000000..6a6b217 --- /dev/null +++ b/.mokogitea/workflows/branch-protection-enforce.yml @@ -0,0 +1,54 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Enforce branch protection rules across all org repos. +# Runs weekly and on manual dispatch. + +name: "Org: Enforce Branch Protections" + +on: + schedule: + - cron: '0 6 * * 1' # Every Monday at 6am UTC + workflow_dispatch: + inputs: + dry_run: + description: 'Dry run (show changes without applying)' + required: false + type: boolean + default: false + +jobs: + enforce: + name: Enforce Branch Protections + runs-on: release + + steps: + - name: Checkout MokoCLI + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + token: ${{ secrets.MOKOGITEA_TOKEN }} + + - name: Setup PHP + run: | + if ! command -v php > /dev/null 2>&1; then + sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-curl > /dev/null 2>&1 + fi + + - name: Run branch protection enforcement + run: | + DRY_RUN="" + if [ "${{ inputs.dry_run }}" = "true" ]; then + DRY_RUN="--dry-run" + fi + php cli/branch_protect_org.php \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" \ + --org "MokoConsulting" \ + $DRY_RUN + + - name: Summary + if: always() + run: | + echo "## Branch Protection Enforcement" >> $GITHUB_STEP_SUMMARY + echo "All repos checked for main, dev, rc, beta, alpha protections" >> $GITHUB_STEP_SUMMARY + echo "Push whitelist: jmiller only" >> $GITHUB_STEP_SUMMARY diff --git a/.mokogitea/workflows/cascade-dev.yml b/.mokogitea/workflows/cascade-dev.yml index 5f7c1d7..b2eabe1 100644 --- a/.mokogitea/workflows/cascade-dev.yml +++ b/.mokogitea/workflows/cascade-dev.yml @@ -1,10 +1,106 @@ -# DISABLED — auto-release Step 11 recreates dev from main after every release. -# Cascade-dev is redundant and causes version conflicts when both main and dev -# have different version numbers in templateDetails.xml / manifest.xml. -name: "Cascade Main → Dev (DISABLED)" -on: workflow_dispatch +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: MokoGitea.Workflow +# INGROUP: MokoStandards.Cascade +# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic +# PATH: /.mokogitea/workflows/cascade-dev.yml +# VERSION: 02.00.00 +# BRIEF: Cascade main -> dev via PR; auto-merge only if conflict-free, else notify + +name: "Cascade Main -> Dev" + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +env: + MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + # ntfy destination is configured via repo or org variables (org vars are inherited). + NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }} + NTFY_TOPIC: ${{ vars.CASCADE_NTFY_TOPIC || vars.NTFY_TOPIC || 'gitea-releases' }} + jobs: - noop: + cascade: + name: Cascade main -> dev runs-on: ubuntu-latest steps: - - run: echo "Cascade disabled — auto-release handles dev recreation" + - name: Open main -> dev PR (auto-merge if clean, else notify) + env: + TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + REPO: ${{ github.repository }} + run: | + set -uo pipefail + API="${MOKOGITEA_URL}/api/v1/repos/${REPO}" + AUTH="Authorization: token ${TOKEN}" + jqnum() { python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null; } + + # 0. dev must exist + if ! curl -sf -H "$AUTH" "${API}/branches/dev" >/dev/null 2>&1; then + echo "No dev branch - nothing to cascade."; exit 0 + fi + + # 1. is main ahead of dev? + AHEAD=$(curl -sf -H "$AUTH" "${API}/compare/dev...main" \ + | python3 -c "import sys,json; print(json.load(sys.stdin).get('total_commits',0))" 2>/dev/null || echo 0) + if [ "${AHEAD:-0}" -eq 0 ]; then + echo "dev already up to date with main."; exit 0 + fi + echo "main is ${AHEAD} commit(s) ahead of dev." + + # 2. reuse an open main->dev PR, else create one + PR=$(curl -sf -H "$AUTH" "${API}/pulls?state=open&base=dev" \ + | python3 -c "import sys,json; d=json.load(sys.stdin); print(next((str(p['number']) for p in d if p.get('head',{}).get('ref')=='main'), ''))" 2>/dev/null || echo "") + if [ -z "$PR" ]; then + RESP=$(curl -s -H "$AUTH" -H "Content-Type: application/json" -X POST "${API}/pulls" \ + -d '{"head":"main","base":"dev","title":"chore(sync): cascade main -> dev","body":"Automated cascade of main into dev. Auto-merges only if conflict-free; otherwise left open for manual resolution."}') + PR=$(printf '%s' "$RESP" | jqnum number) + if [ -z "$PR" ]; then + echo "::warning::Could not open cascade PR: $RESP"; exit 0 + fi + echo "Opened cascade PR #${PR}" + else + echo "Reusing open cascade PR #${PR}" + fi + + # 3. wait for MokoGitea to compute mergeability (conflict detection) + MERGEABLE="" + for _ in 1 2 3 4 5 6; do + MERGEABLE=$(curl -sf -H "$AUTH" "${API}/pulls/${PR}" | jqnum mergeable) + case "$MERGEABLE" in True|False) break ;; esac + sleep 3 + done + echo "mergeable=${MERGEABLE}" + + notify() { + curl -sS \ + -H "Title: ${REPO}: dev cascade needs manual merge" \ + -H "Tags: warning,twisted_rightwards_arrows" \ + -H "Priority: high" \ + -H "Click: ${MOKOGITEA_URL}/${REPO}/pulls/${PR}" \ + -d "main -> dev cascade PR #${PR} $1 It was NOT auto-merged; resolve it manually." \ + "${NTFY_URL}/${NTFY_TOPIC}" || true + } + + # 4. auto-merge only if conflict-free; otherwise notify + if [ "$MERGEABLE" = "True" ]; then + CODE=$(curl -s -o /tmp/merge.json -w "%{http_code}" -H "$AUTH" -H "Content-Type: application/json" \ + -X POST "${API}/pulls/${PR}/merge" -d '{"Do":"merge","merge_when_checks_succeed":true}') + if [ "$CODE" -ge 200 ] && [ "$CODE" -lt 300 ]; then + echo "Cascade PR #${PR} merged (or scheduled to merge when checks pass)." + else + echo "::warning::Auto-merge returned HTTP ${CODE}: $(cat /tmp/merge.json)" + notify "could not be auto-merged (HTTP ${CODE})." + fi + else + echo "::warning::Cascade PR #${PR} has conflicts (mergeable=${MERGEABLE}); sending notification." + notify "has conflicts and cannot be merged automatically." + fi diff --git a/.mokogitea/workflows/ci-generic.yml b/.mokogitea/workflows/ci-generic.yml index 2f7116e..14f81ba 100644 --- a/.mokogitea/workflows/ci-generic.yml +++ b/.mokogitea/workflows/ci-generic.yml @@ -3,16 +3,22 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoCli.CI +# DEFGROUP: MokoGitea.Workflow +# INGROUP: MokoStandards.CI # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic -# PATH: /.gitea/workflows/ci-generic.yml +# PATH: /.mokogitea/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: + pull_request: + branches: + - main + - dev + - dev/** + - rc/** workflow_dispatch: permissions: @@ -26,6 +32,8 @@ jobs: lint: name: Lint & Validate runs-on: ubuntu-latest + # Skip on template repos (Template-*) — they hold placeholder scaffolding, not buildable source. + if: ${{ !startsWith(github.event.repository.name, 'Template-') }} steps: - name: Checkout @@ -123,7 +131,11 @@ jobs: test: name: Tests runs-on: ubuntu-latest - needs: lint + # Independent job (no `needs: lint`): the Gitea Actions scheduler does not + # offer the dependent 2nd job of a needs-chain to runners, so it stalls in + # "waiting" and is reaped by ABANDONED_JOB_TIMEOUT. Guard template repos + # directly (same condition lint uses) instead of gating on lint's result. + if: ${{ !startsWith(github.event.repository.name, 'Template-') }} steps: - name: Checkout diff --git a/.mokogitea/workflows/ci-issue-reporter.yml b/.mokogitea/workflows/ci-issue-reporter.yml new file mode 100644 index 0000000..0520237 --- /dev/null +++ b/.mokogitea/workflows/ci-issue-reporter.yml @@ -0,0 +1,68 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: MokoGitea.Workflow +# INGROUP: mokocli.Universal +# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic +# PATH: /.mokogitea/workflows/ci-issue-reporter.yml +# VERSION: 01.00.00 +# BRIEF: Reusable workflow — creates/updates a MokoGitea issue when a CI gate fails. +# Clones MokoCLI and runs cli/ci_issue_reporter.sh. + +name: "Universal: CI Issue Reporter" + +on: + workflow_call: + inputs: + gate: + description: "CI gate name (e.g. PR Validation, Repository Health)" + required: true + type: string + details: + description: "Human-readable failure description" + required: true + type: string + severity: + description: "error or warning" + required: false + type: string + default: "error" + workflow: + description: "Workflow name for the issue title" + required: false + type: string + default: "" + secrets: + MOKOGITEA_TOKEN: + required: true + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + report: + name: "Report: ${{ inputs.gate }}" + runs-on: ubuntu-latest + + steps: + - name: Clone MokoCLI + env: + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + run: | + MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}" + git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli + cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh + + - name: Report CI failure + env: + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + run: | + chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh + /tmp/mokocli/cli/ci_issue_reporter.sh \ + --gate "${{ inputs.gate }}" \ + --details "${{ inputs.details }}" \ + --severity "${{ inputs.severity }}" \ + --workflow "${{ inputs.workflow }}" diff --git a/.mokogitea/workflows/ci-platform.yml b/.mokogitea/workflows/ci-platform.yml index b2bd759..0ac3fe8 100644 --- a/.mokogitea/workflows/ci-platform.yml +++ b/.mokogitea/workflows/ci-platform.yml @@ -115,7 +115,7 @@ jobs: - name: "PHPCS (PSR-12)" run: | - vendor/bin/phpcs --standard=phpcs.xml --report=summary --warning-severity=0 lib/ validate/ automation/ 2>&1 || { + vendor/bin/phpcs --standard=phpcs.xml --report=summary --warning-severity=0 lib/ validate/ 2>&1 || { echo "::error::PHPCS found coding standard violations" echo "### PHPCS" >> $GITHUB_STEP_SUMMARY echo "Coding standard violations detected. Run \`composer phpcs\` locally." >> $GITHUB_STEP_SUMMARY @@ -453,8 +453,8 @@ jobs: needs.governance.result == 'failure' || needs.templates.result == 'failure') env: - GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} run: | chmod +x automation/ci-issue-reporter.sh REPORTER="./automation/ci-issue-reporter.sh" diff --git a/.mokogitea/workflows/cleanup.yml b/.mokogitea/workflows/cleanup.yml index a62968a..b4db32b 100644 --- a/.mokogitea/workflows/cleanup.yml +++ b/.mokogitea/workflows/cleanup.yml @@ -3,10 +3,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoCli.Maintenance -# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCli -# PATH: /.gitea/workflows/cleanup.yml +# DEFGROUP: MokoGitea.Workflow +# INGROUP: MokoStandards.Maintenance +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards +# PATH: /.mokogitea/workflows/cleanup.yml # VERSION: 01.00.00 # BRIEF: Scheduled cleanup — delete merged branches and old workflow runs @@ -21,7 +21,7 @@ permissions: contents: write env: - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} jobs: cleanup: @@ -33,30 +33,30 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.GA_TOKEN }} + token: ${{ secrets.MOKOGITEA_TOKEN }} - name: Delete merged branches env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} run: | echo "=== Merged Branch Cleanup ===" - API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" + API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}" # List branches via API - BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ + BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \ "${API}/branches?limit=50" | jq -r '.[].name') DELETED=0 for BRANCH in $BRANCHES; do # Skip protected branches case "$BRANCH" in - main|master|develop|release/*|hotfix/*) continue ;; + main|master|dev|develop|rc|beta|alpha|release|release/*|production|stable|staging|hotfix/*|version/*) continue ;; esac # Check if branch is merged into main if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then echo " Deleting merged branch: ${BRANCH}" - curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ + curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \ "${API}/branches/${BRANCH}" 2>/dev/null || true DELETED=$((DELETED + 1)) fi @@ -66,20 +66,20 @@ jobs: - name: Clean old workflow runs env: - GA_TOKEN: ${{ secrets.GA_TOKEN }} + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} run: | echo "=== Workflow Run Cleanup ===" - API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" + API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}" CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ) # Get old completed runs - RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ + RUNS=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \ "${API}/actions/runs?status=completed&limit=50" | \ jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null) DELETED=0 for RUN_ID in $RUNS; do - curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ + curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \ "${API}/actions/runs/${RUN_ID}" 2>/dev/null || true DELETED=$((DELETED + 1)) done diff --git a/.mokogitea/workflows/composer-publish.yml b/.mokogitea/workflows/composer-publish.yml index 03735c9..0d104aa 100644 --- a/.mokogitea/workflows/composer-publish.yml +++ b/.mokogitea/workflows/composer-publish.yml @@ -13,7 +13,7 @@ on: workflow_dispatch: env: - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} jobs: publish: @@ -49,18 +49,18 @@ jobs: - name: Verify Gitea registry run: | echo "Gitea Composer registry auto-publishes from tags." - echo "Package available at: ${GITEA_URL}/api/packages/MokoConsulting/composer" + echo "Package available at: ${MOKOGITEA_URL}/api/packages/MokoConsulting/composer" echo "Install: composer require mokoconsulting/mokocli" - # Packagist — notify of new version + # Packagist — notify of new version (points to GitHub mirror which Packagist can access) - name: Notify Packagist - if: secrets.PACKAGIST_TOKEN != '' + if: ${{ secrets.PACKAGIST_TOKEN != '' }} run: | VERSION="${{ steps.version.outputs.version }}" echo "Notifying Packagist of version ${VERSION}..." curl -sf -X POST \ -H "Content-Type: application/json" \ - -d '{"repository":{"url":"https://git.mokoconsulting.tech/MokoConsulting/mokocli"}}' \ + -d '{"repository":{"url":"https://github.com/mokoconsulting-tech/mokocli"}}' \ "https://packagist.org/api/update-package?username=mokoconsulting&apiToken=${{ secrets.PACKAGIST_TOKEN }}" \ && echo "Packagist notified" \ || echo "::warning::Packagist notification failed (package may not be registered yet)" diff --git a/.mokogitea/workflows/deploy-manual.yml b/.mokogitea/workflows/deploy-manual.yml deleted file mode 100644 index 07f4538..0000000 --- a/.mokogitea/workflows/deploy-manual.yml +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoCli.Deploy -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# 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 MokoCli 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}/mokocli.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 9d2d3da..7312838 100644 --- a/.mokogitea/workflows/gitleaks.yml +++ b/.mokogitea/workflows/gitleaks.yml @@ -3,10 +3,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoCli.Security -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli -# PATH: /templates/workflows/gitleaks.yml.template +# DEFGROUP: MokoGitea.Workflow +# INGROUP: MokoStandards.Security +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API +# PATH: /.mokogitea/workflows/gitleaks.yml # VERSION: 01.00.00 # BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens # diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 279d65d..eb67f8f 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -3,9 +3,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow +# DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 09.37.07 +# VERSION: 01.00.00 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" @@ -19,7 +19,7 @@ permissions: issues: write env: - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} jobs: create-branch: @@ -28,8 +28,8 @@ jobs: steps: - name: Create branch and comment run: | - TOKEN="${{ secrets.GA_TOKEN }}" - API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" + TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" + API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}" ISSUE_NUM="${{ github.event.issue.number }}" ISSUE_TITLE="${{ github.event.issue.title }}" @@ -58,7 +58,7 @@ jobs: echo "Created branch: ${BRANCH}" # Comment on issue with branch link - REPO_URL="${GITEA_URL}/${{ github.repository }}" + REPO_URL="${MOKOGITEA_URL}/${{ github.repository }}" BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`" curl -sf -X POST \ diff --git a/.mokogitea/workflows/notify.yml b/.mokogitea/workflows/notify.yml index 979f76c..5fead53 100644 --- a/.mokogitea/workflows/notify.yml +++ b/.mokogitea/workflows/notify.yml @@ -3,10 +3,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoCli.Notifications -# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCli -# PATH: /.gitea/workflows/notify.yml +# DEFGROUP: MokoGitea.Workflow +# INGROUP: MokoStandards.Notifications +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards +# PATH: /.mokogitea/workflows/notify.yml # VERSION: 01.00.00 # BRIEF: Push notifications via ntfy on release success or workflow failure @@ -15,9 +15,9 @@ name: "Universal: Notifications" on: workflow_run: workflows: - - "Joomla Build & Release" - - "Joomla Extension CI" - - "Deploy" + - "Universal: Build & Release" + - "Joomla: Extension CI" + - "Generic: Project CI" types: - completed diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml index 3ac389c..c7c2e8f 100644 --- a/.mokogitea/workflows/pr-check.yml +++ b/.mokogitea/workflows/pr-check.yml @@ -3,10 +3,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow +# DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.CI -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli -# PATH: /templates/workflows/universal/pr-check.yml.template +# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic +# PATH: /.mokogitea/workflows/pr-check.yml # VERSION: 09.23.00 # BRIEF: PR gate — branch policy + code validation before merge @@ -47,15 +47,15 @@ jobs: fi ;; fix/*|bugfix/*) - if [ "$BASE" != "dev" ]; then + if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then ALLOWED=false - REASON="Fix branches must target 'dev', not '${BASE}'" + REASON="Fix branches must target 'dev' or 'main', not '${BASE}'" fi ;; patch/*) - if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then + if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ] && [ "$BASE" != "main" ]; then ALLOWED=false - REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'" + REASON="Patch branches must target 'dev', 'rc', or 'main', not '${BASE}'" fi ;; hotfix/*) @@ -86,7 +86,8 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`fix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`patch/*\` → \`dev\`, \`rc\`, or \`main\`" >> $GITHUB_STEP_SUMMARY echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY @@ -96,6 +97,80 @@ jobs: echo "Branch policy: OK (${HEAD} → ${BASE})" echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY + # ── Docs Update Gate (main PRs) ───────────────────────────────────────── + require-docs: + name: Require Docs Update + runs-on: ubuntu-latest + # Enforce only on PRs merging into main: README.md + CHANGELOG.md must both be updated. + if: ${{ github.base_ref == 'main' }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Require README.md and CHANGELOG.md in the PR diff + run: | + BASE="${{ github.event.pull_request.base.sha }}" + HEAD="${{ github.event.pull_request.head.sha }}" + CHANGED="$(git diff --name-only "$BASE" "$HEAD" 2>/dev/null || true)" + if [ -z "$CHANGED" ]; then + git fetch -q origin "${{ github.base_ref }}" 2>/dev/null || true + CHANGED="$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" 2>/dev/null || true)" + fi + echo "Changed files in PR:" + echo "$CHANGED" + MISSING="" + echo "$CHANGED" | grep -qxE 'README\.md' || MISSING="README.md" + echo "$CHANGED" | grep -qxE 'CHANGELOG\.md' || MISSING="${MISSING:+$MISSING, }CHANGELOG.md" + if [ -n "$MISSING" ]; then + echo "::error::PRs into main must update: ${MISSING}" + { + echo "## Docs Update Required" + echo "" + echo "PRs merging into \`main\` must update both **README.md** and **CHANGELOG.md**." + echo "" + echo "Not updated in this PR: **${MISSING}**" + } >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + echo "Docs update present (README.md + CHANGELOG.md)" + echo "## Docs Update: Passed" >> "$GITHUB_STEP_SUMMARY" + + # ── Wiki Update Reminder (main PRs, non-blocking) ─────────────────────── + wiki-reminder: + name: Wiki Update Reminder + runs-on: ubuntu-latest + if: ${{ github.base_ref == 'main' }} + steps: + - name: Remind to update the wiki + env: + TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + SERVER: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + REPO: ${{ github.repository }} + PR: ${{ github.event.pull_request.number }} + run: | + set -uo pipefail + { + echo "## Wiki Update Reminder" + echo "" + echo "Docs are **wiki-first** at MokoConsulting. If this change affects behavior, usage, configuration, or standards, update the repo wiki:" + echo "" + echo "- ${SERVER}/${REPO}/wiki" + echo "" + echo "_Non-blocking reminder._" + } >> "$GITHUB_STEP_SUMMARY" + # Post a single PR comment (idempotent via hidden marker); best-effort, never fails. + API="${SERVER}/api/v1/repos/${REPO}/issues/${PR}/comments" + if [ -n "${TOKEN:-}" ] && [ -n "${PR:-}" ]; then + existing="$(curl -sf -H "Authorization: token ${TOKEN}" "$API" 2>/dev/null | grep -c 'wiki-reminder' || true)" + if [ "${existing:-0}" -eq 0 ]; then + curl -sf -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" -X POST "$API" \ + -d '{"body":"\n\n**Wiki reminder:** docs are wiki-first -- if this PR changes behavior, usage, config, or standards, please update the repo wiki before/after merge. _(non-blocking)_"}' >/dev/null 2>&1 || true + fi + fi + echo "Wiki reminder emitted (non-blocking)." + # ── Secret Scanning ────────────────────────────────────────────────── gitleaks: name: Secret Scan @@ -126,6 +201,8 @@ jobs: validate: name: Validate PR runs-on: ubuntu-latest + # Skip on template repos (Template-*) — no real manifest/source/changelog to validate. + if: ${{ !startsWith(github.event.repository.name, 'Template-') }} steps: - name: Checkout @@ -133,7 +210,7 @@ jobs: - name: Check for merge conflict markers run: | - CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true) + CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --exclude-dir='.git' --exclude-dir='.mokogitea' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true) if [ -n "$CONFLICTS" ]; then echo "::error::Merge conflict markers found in source files" echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY @@ -147,11 +224,12 @@ jobs: - 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:]') + # Platform comes from the MokoGitea metadata API (public GET); manifest.xml is no longer used. + API="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${GITHUB_REPOSITORY}/metadata" + PLATFORM="$(curl -sf "$API" 2>/dev/null | python3 -c "import sys, json; print(json.load(sys.stdin).get('platform') or '')" 2>/dev/null || true)" [ -z "$PLATFORM" ] && PLATFORM="generic" echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT" + echo "Detected platform: $PLATFORM" - name: Setup PHP if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' @@ -272,7 +350,7 @@ jobs: joomla) MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1) if [ -z "$MANIFEST" ]; then - echo "::warning::No Joomla manifest found (WaaS site)" + echo "::warning::No Joomla manifest found (MokoSuite site)" exit 0 fi echo "Manifest: ${MANIFEST}" @@ -285,7 +363,7 @@ jobs: # Block legacy raw/branch update server URLs on MokoGitea RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true) if [ -n "$RAW_URLS" ]; then - echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)" + echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the MokoGitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)" echo "$RAW_URLS" exit 1 fi @@ -492,43 +570,33 @@ jobs: name: Build RC Package runs-on: ubuntu-latest needs: [branch-policy, validate] + # Run only when both gates succeeded; always() forces evaluation so a skipped + # validate (e.g. template repos) skips this job cleanly instead of hanging. + if: ${{ always() && needs.branch-policy.result == 'success' && needs.validate.result == 'success' }} steps: - name: Trigger RC pre-release env: - GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} REPO: ${{ github.repository }} BRANCH: ${{ github.head_ref }} - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + MOKOGITEA_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\"}}" + curl -s -X POST "${MOKOGITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${MOKOGITEA_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 # ── Issue Reporter ────────────────────────────────────────────────────── report-issues: name: Report Issues - runs-on: ubuntu-latest needs: [branch-policy, validate] if: >- always() && needs.validate.result == 'failure' - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - sparse-checkout: automation/ci-issue-reporter.sh - sparse-checkout-cone-mode: false - - - name: "File issue for PR validation failure" - env: - GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - run: | - chmod +x automation/ci-issue-reporter.sh - ./automation/ci-issue-reporter.sh \ - --gate "PR Validation" \ - --workflow "PR Check" \ - --severity error \ - --details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed." + uses: ./.mokogitea/workflows/ci-issue-reporter.yml + with: + gate: "PR Validation" + workflow: "PR Check" + severity: error + details: "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed." + secrets: inherit diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index b34a311..b212772 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -3,11 +3,11 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow +# DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Release -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /templates/workflows/universal/pre-release.yml.template -# VERSION: 05.01.00 +# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic +# PATH: /.mokogitea/workflows/pre-release.yml +# VERSION: 05.02.01 # BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches name: "Universal: Pre-Release" @@ -48,9 +48,13 @@ jobs: build: name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})" runs-on: release + # Skip on template repos (Template-*) — they scaffold other repos and do not release. if: >- - github.event_name == 'workflow_dispatch' || - github.event_name == 'push' + !startsWith(github.event.repository.name, 'Template-') && + ( + github.event_name == 'workflow_dispatch' || + github.event_name == 'push' + ) steps: - name: Checkout @@ -59,6 +63,11 @@ jobs: fetch-depth: 0 token: ${{ secrets.MOKOGITEA_TOKEN }} ref: ${{ github.ref_name }} + submodules: recursive + + - name: Update submodules to main + run: | + git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true - name: Setup mokocli tools env: @@ -75,7 +84,7 @@ 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 - CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git + CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV @@ -88,8 +97,20 @@ jobs: php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true php ${MOKO_CLI}/manifest_read.php --path . --github-output + - name: Check platform eligibility (Joomla only) + id: eligibility + run: | + PLATFORM="${{ steps.platform.outputs.platform }}" + if [[ "$PLATFORM" == joomla* ]] || [[ "$PLATFORM" == "joomla" ]]; then + echo "proceed=true" >> "$GITHUB_OUTPUT" + else + echo "proceed=false" >> "$GITHUB_OUTPUT" + echo "::notice::Platform '$PLATFORM' — non-Joomla, skipping pre-release auto-bump" + fi + - name: Resolve metadata and bump version id: meta + if: steps.eligibility.outputs.proceed == 'true' run: | # Auto-detect stability from branch name on push, or use input on dispatch if [ "${{ github.event_name }}" = "push" ]; then @@ -135,13 +156,19 @@ jobs: fi # Commit version bump - git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" - git config --local user.name "gitea-actions[bot]" + git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech" + git config --local user.name "mokogitea-actions[bot]" git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git add -A git diff --cached --quiet || { git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]" - git push origin HEAD 2>&1 + # Push the bump commit, but do NOT fail the release if the target branch + # is protected and the release identity is not on the push allowlist. + # The build proceeds from the in-tree bumped version regardless; if the + # push is rejected, the next run simply re-bumps from the same base. + if ! git push origin HEAD 2>&1; then + echo "::warning::Version-bump commit could not be pushed (protected branch?). Building from in-tree version ${VERSION} anyway." + fi } # Auto-detect element via manifest_element.php @@ -166,6 +193,7 @@ jobs: - name: Create release id: release + if: steps.eligibility.outputs.proceed == 'true' run: | TAG="${{ steps.meta.outputs.tag }}" VERSION="${{ steps.meta.outputs.version }}" @@ -176,6 +204,7 @@ jobs: --repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease - name: Update release notes from CHANGELOG.md + if: steps.eligibility.outputs.proceed == 'true' run: | TAG="${{ steps.meta.outputs.tag }}" VERSION="${{ steps.meta.outputs.version }}" @@ -212,6 +241,7 @@ jobs: - name: Build package and upload id: package + if: steps.eligibility.outputs.proceed == 'true' run: | VERSION="${{ steps.meta.outputs.version }}" TAG="${{ steps.meta.outputs.tag }}" @@ -225,6 +255,7 @@ jobs: # No need to build, commit, or sync updates.xml from workflows - name: "Delete lesser pre-release channels (cascade)" + if: steps.eligibility.outputs.proceed == 'true' continue-on-error: true run: | API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" @@ -249,4 +280,4 @@ jobs: echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY - echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY + echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.mokogitea/workflows/rc-revert.yml b/.mokogitea/workflows/rc-revert.yml index 5e61de8..57934ea 100644 --- a/.mokogitea/workflows/rc-revert.yml +++ b/.mokogitea/workflows/rc-revert.yml @@ -3,9 +3,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow +# DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Universal -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # PATH: /.mokogitea/workflows/rc-revert.yml # VERSION: 09.23.00 # BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge @@ -25,16 +25,25 @@ jobs: runs-on: ubuntu-latest if: >- github.event.pull_request.merged == false && - startsWith(github.event.pull_request.head.ref, 'rc/') + startsWith(github.event.pull_request.head.ref, 'rc/') && + !startsWith(github.event.repository.name, 'Template-') steps: - name: Rename branch + env: + BRANCH: ${{ github.event.pull_request.head.ref }} + REPO: ${{ github.repository }} + GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} run: | - BRANCH="${{ github.event.pull_request.head.ref }}" + set -euo pipefail + # BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use. + if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then + echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1 + fi 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 }}" + API="${GITEA_URL}/api/v1/repos/${REPO}/branches" # Create dev/ branch from rc/ branch STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \ @@ -42,25 +51,22 @@ jobs: -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 + echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY" else - echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})" - exit 1 + echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1 fi - # Delete rc/ branch - ENCODED=$(php -r "echo rawurlencode('${BRANCH}');") + # Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection) + ENCODED=$(php -r 'echo rawurlencode(getenv("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 + 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 + 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 6a25f5b..54d7cf9 100644 --- a/.mokogitea/workflows/repo-health.yml +++ b/.mokogitea/workflows/repo-health.yml @@ -6,10 +6,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow +# DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Validation -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli -# PATH: /templates/workflows/joomla/repo_health.yml.template +# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic +# PATH: /.mokogitea/workflows/repo-health.yml # VERSION: 09.23.00 # BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts. # ============================================================================ @@ -77,7 +77,7 @@ jobs: - name: Check actor permission (admin only) id: perm env: - TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }} + TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }} REPO: ${{ github.repository }} ACTOR: ${{ github.actor }} run: | @@ -88,7 +88,7 @@ jobs: # Hardcoded authorized users — always allowed case "$ACTOR" in - jmiller|gitea-actions[bot]) + jmiller|mokogitea-actions[bot]) ALLOWED=true PERMISSION=admin METHOD="hardcoded allowlist" @@ -671,42 +671,30 @@ jobs: # ═══════════════════════════════════════════════════════════════════════ # Issue Reporter — file issues for failed gates # ═══════════════════════════════════════════════════════════════════════ - report-issues: - name: "Report Issues" - runs-on: ubuntu-latest - needs: [access_check, scripts_governance, repo_health] + report-scripts: + name: "Report: Scripts Governance" + needs: [access_check, scripts_governance] if: >- always() && - (needs.scripts_governance.result == 'failure' || - needs.repo_health.result == 'failure') + needs.scripts_governance.result == 'failure' + uses: ./.mokogitea/workflows/ci-issue-reporter.yml + with: + gate: "Scripts Governance" + workflow: "Repo Health" + severity: error + details: "Scripts directory policy violations detected. Review required and allowed directories." + secrets: inherit - 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." + report-health: + name: "Report: Repository Health" + needs: [access_check, repo_health] + if: >- + always() && + needs.repo_health.result == 'failure' + uses: ./.mokogitea/workflows/ci-issue-reporter.yml + with: + gate: "Repository Health" + workflow: "Repo Health" + severity: error + details: "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary." + secrets: inherit diff --git a/.mokogitea/workflows/security-audit.yml b/.mokogitea/workflows/security-audit.yml deleted file mode 100644 index 5b3cd92..0000000 --- a/.mokogitea/workflows/security-audit.yml +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoCli.Security -# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCli -# PATH: /.gitea/workflows/security-audit.yml -# VERSION: 01.00.00 -# BRIEF: Dependency vulnerability scanning for composer and npm packages - -name: "Universal: Security Audit" - -on: - schedule: - - cron: '0 6 * * 1' # Weekly on Monday at 06:00 UTC - pull_request: - branches: - - main - paths: - - 'composer.json' - - 'composer.lock' - - 'package.json' - - 'package-lock.json' - workflow_dispatch: - -permissions: - contents: read - -env: - NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }} - NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }} - -jobs: - audit: - name: Dependency Audit - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Composer audit - if: hashFiles('composer.lock') != '' - run: | - echo "=== Composer Security Audit ===" - if ! command -v composer &> /dev/null; then - sudo apt-get update -qq - sudo apt-get install -y -qq php-cli composer >/dev/null 2>&1 - fi - composer audit --format=plain 2>&1 | tee /tmp/composer-audit.txt - RESULT=$? - if [ $RESULT -ne 0 ]; then - echo "::warning::Composer vulnerabilities found" - echo "composer_vulnerable=true" >> "$GITHUB_ENV" - else - echo "No known vulnerabilities in composer dependencies" - fi - - - name: NPM audit - if: hashFiles('package-lock.json') != '' - run: | - echo "=== NPM Security Audit ===" - npm audit --production 2>&1 | tee /tmp/npm-audit.txt || true - if npm audit --production 2>&1 | grep -q "found 0 vulnerabilities"; then - echo "No known vulnerabilities in npm dependencies" - else - echo "::warning::NPM vulnerabilities found" - echo "npm_vulnerable=true" >> "$GITHUB_ENV" - fi - - - name: Notify on vulnerabilities - if: env.composer_vulnerable == 'true' || env.npm_vulnerable == 'true' - run: | - REPO="${{ github.event.repository.name }}" - curl -sS \ - -H "Title: ${REPO} has vulnerable dependencies" \ - -H "Tags: lock,warning" \ - -H "Priority: high" \ - -d "Security audit found vulnerabilities. Review dependency updates." \ - "${NTFY_URL}/${NTFY_TOPIC}" || true diff --git a/.mokogitea/workflows/sync-feature-versions.yml b/.mokogitea/workflows/sync-feature-versions.yml index 7682008..56b4600 100644 --- a/.mokogitea/workflows/sync-feature-versions.yml +++ b/.mokogitea/workflows/sync-feature-versions.yml @@ -22,7 +22,7 @@ permissions: contents: write env: - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} jobs: sync: diff --git a/.mokogitea/workflows/sync-on-merge.yml b/.mokogitea/workflows/sync-on-merge.yml new file mode 100644 index 0000000..e362224 --- /dev/null +++ b/.mokogitea/workflows/sync-on-merge.yml @@ -0,0 +1,32 @@ +name: Sync Workflows to Repos + +on: + push: + branches: + - main + paths: + - '.mokogitea/workflows/**' + +jobs: + sync: + runs-on: ubuntu-latest + if: ${{ startsWith(github.event.repository.name, 'Template-') }} + steps: + - name: Checkout mokocli + uses: actions/checkout@v4 + with: + repository: MokoConsulting/MokoCLI + token: ${{ secrets.MOKOGITEA_TOKEN }} + + - name: Setup PHP + uses: https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/raw/branch/main/actions/setup-php@v1 + with: + php-version: '8.1' + + - name: Install dependencies + run: composer install --no-dev --no-interaction + + - name: Sync workflows to generic repos + run: php automation/bulk_sync.php --platform generic --org MokoConsulting --workflows-only --auto-merge --token "${{ secrets.MOKOGITEA_TOKEN }}" + env: + MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} diff --git a/.mokogitea/workflows/version-set.yml b/.mokogitea/workflows/version-set.yml new file mode 100644 index 0000000..eed4192 --- /dev/null +++ b/.mokogitea/workflows/version-set.yml @@ -0,0 +1,131 @@ +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: MokoGitea.Workflow.Template +# INGROUP: MokoStandards.CI +# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla +# PATH: /.mokogitea/workflows/version-set.yml +# VERSION: 01.00.00 +# BRIEF: Set or reset the extension version across all version-bearing files + +name: "Joomla: Set Version" + +on: + workflow_dispatch: + inputs: + version: + description: "Version number (e.g. 01.00.00)" + required: true + type: string + branch: + description: "Branch to update (default: current)" + required: false + type: string + +permissions: + contents: write + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + set-version: + name: Set Version to ${{ inputs.version }} + runs-on: ubuntu-latest + if: ${{ !startsWith(github.event.repository.name, 'Template-') }} + + steps: + - name: Validate version format + run: | + VERSION="${{ inputs.version }}" + if ! echo "$VERSION" | grep -qP '^\d{2}\.\d{2}\.\d{2}$'; then + echo "::error::Invalid version format '${VERSION}' — expected XX.YY.ZZ (e.g. 01.00.00)" + exit 1 + fi + echo "VERSION=${VERSION}" >> "$GITHUB_ENV" + + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.MOKOGITEA_TOKEN || github.token }} + ref: ${{ inputs.branch || github.ref }} + fetch-depth: 1 + + - name: Update manifest version + run: | + MANIFEST="" + for XML_FILE in $(find . -maxdepth 3 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do + if grep -q "/dev/null; then + MANIFEST="$XML_FILE" + break + fi + done + + if [ -z "$MANIFEST" ]; then + echo "::warning::No Joomla extension manifest found — skipping manifest update" + else + OLD_VER=$(grep -oP '\K[^<]+' "$MANIFEST" | head -1) + sed -i "s|${OLD_VER}|${VERSION}|" "$MANIFEST" + echo "Manifest: ${OLD_VER} → ${VERSION} (${MANIFEST})" + fi + + - name: Update README.md version + run: | + if [ -f "README.md" ]; then + if grep -qP '^\s*VERSION:\s*\d' README.md; then + sed -i -E "s/(VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" README.md + echo "README.md version updated to ${VERSION}" + else + echo "::warning::No VERSION line found in README.md — skipping" + fi + fi + + - name: Update CHANGELOG.md + run: | + if [ -f "CHANGELOG.md" ]; then + DATE=$(date +%Y-%m-%d) + # Check if this version already has an entry + if grep -q "^\#\# \[${VERSION}\]" CHANGELOG.md; then + echo "CHANGELOG.md already has entry for ${VERSION} — skipping" + else + # Insert new version entry after [Unreleased] or at the top after header + if grep -q '^\#\# \[Unreleased\]' CHANGELOG.md; then + sed -i "/^\#\# \[Unreleased\]/a\\\\n## [${VERSION}] --- ${DATE}" CHANGELOG.md + else + sed -i "/^\# Changelog/a\\\\n## [Unreleased]\n\n## [${VERSION}] --- ${DATE}" CHANGELOG.md + fi + echo "CHANGELOG.md: added entry for ${VERSION}" + fi + else + echo "::warning::No CHANGELOG.md found — skipping" + fi + + - name: Update FILE INFORMATION blocks + run: | + # Update VERSION in file header blocks (# VERSION: XX.YY.ZZ) + find . -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.php" -o -name "*.md" \) \ + -not -path "./.git/*" -not -path "./vendor/*" -print0 2>/dev/null | \ + while IFS= read -r -d '' FILE; do + if head -20 "$FILE" | grep -qP '^\s*#?\s*VERSION:\s*\d{2}\.\d{2}\.\d{2}'; then + sed -i -E "s/(#?\s*VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" "$FILE" + echo "Updated FILE INFORMATION VERSION in ${FILE}" + fi + done + + - name: Commit and push + run: | + git config user.name "Moko Consulting [bot]" + git config user.email "hello@mokoconsulting.tech" + git add -A + if git diff --cached --quiet; then + echo "No version changes detected — nothing to commit" + else + git commit -m "chore: set version to ${VERSION} [skip bump] + +Authored-by: Moko Consulting" + git push + echo "### Version Set" >> $GITHUB_STEP_SUMMARY + echo "Version updated to \`${VERSION}\` on branch \`${GITHUB_REF_NAME}\`" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.mokogitea/workflows/workflow-sync-trigger.yml b/.mokogitea/workflows/workflow-sync-trigger.yml index 371910c..69b00c4 100644 --- a/.mokogitea/workflows/workflow-sync-trigger.yml +++ b/.mokogitea/workflows/workflow-sync-trigger.yml @@ -3,9 +3,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later # # FILE INFORMATION -# DEFGROUP: Gitea.Workflow +# DEFGROUP: MokoGitea.Workflow # INGROUP: mokocli.Universal -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli +# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic # 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 @@ -13,6 +13,7 @@ name: "Universal: Workflow Sync Trigger" on: + workflow_dispatch: pull_request: types: [closed] branches: @@ -26,8 +27,10 @@ jobs: 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]') + startsWith(github.event.repository.name, 'Template-') && + (github.event_name == 'workflow_dispatch' || + (github.event.pull_request.merged == true && + !contains(github.event.pull_request.title, '[skip sync]'))) steps: - name: Determine platform from repo name @@ -49,8 +52,14 @@ jobs: 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 + MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}" + git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli + + - name: Install PHP + run: | + if ! command -v php &> /dev/null; then + apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1 + fi - name: Install dependencies run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f16f59..66fb384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION DEFGROUP: MokoCli.Root INGROUP: MokoCli -REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli PATH: /CHANGELOG.md BRIEF: Release changelog --> @@ -12,14 +12,17 @@ BRIEF: Release changelog # Changelog ## [Unreleased] -## [09.37.00] --- 2026-06-21 +## [09.43.00] --- 2026-07-05 -## [09.37.00] --- 2026-06-21 +## [09.43.00] --- 2026-07-05 -## [09.36.00] --- 2026-06-21 +## [09.42.00] --- 2026-07-04 -## [09.36.00] --- 2026-06-21 +## [09.42.00] --- 2026-07-04 -## [09.35.00] --- 2026-06-21 +### Fixed +- branch_rename.php: force-update the target branch ref in place instead of delete+recreate, so `moko repo:rename-branch` works when the target (e.g. `rc`) is a protected branch that cannot be deleted — previously the delete silently failed and the recreate returned HTTP 409 -## [09.35.00] --- 2026-06-21 +## [09.42.00] --- 2026-07-04 + +## [09.42.00] --- 2026-07-04 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c0b4858..d7df945 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ feature/* ──PR──> dev ──draft PR──> (renamed to rc) ──merge 7. **Merging to main** triggers the stable release pipeline: - Minor version bump (e.g., `02.09.xx` → `02.10.00`) - Stability suffix stripped (clean version) - - Gitea release created with ZIP/tar.gz packages + - MokoGitea release created with ZIP/tar.gz packages - `updates.xml` updated (Joomla extensions) - `dev` branch recreated from `main` diff --git a/PLUGIN_SCRIPTS.md b/PLUGIN_SCRIPTS.md index 73c23aa..ce38757 100644 --- a/PLUGIN_SCRIPTS.md +++ b/PLUGIN_SCRIPTS.md @@ -2,8 +2,8 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION -DEFGROUP: MokoPlatform.Root -INGROUP: MokoPlatform +DEFGROUP: MokoCLI.Root +INGROUP: MokoCLI REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli PATH: /PLUGIN_SCRIPTS.md BRIEF: Plugin system CLI documentation diff --git a/README.md b/README.md index 6bbc202..26ff4ce 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION -DEFGROUP: MokoPlatform.Root -INGROUP: MokoPlatform +DEFGROUP: MokoCLI.Root +INGROUP: MokoCLI REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli PATH: /README.md -VERSION: 09.37.07 +VERSION: 09.43.00 BRIEF: Project overview and documentation --> @@ -16,7 +16,7 @@ BRIEF: Project overview and documentation PHP implementation of mokocli — enterprise standards, automation framework, workflow templates, and bulk sync tooling. -> **Primary platform**: [Gitea — git.mokoconsulting.tech](https://git.mokoconsulting.tech/MokoConsulting/MokoCli-API) +> **Primary platform**: [MokoGitea — 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 @@ -25,7 +25,7 @@ PHP implementation of mokocli — enterprise standards, automation framework, wo |-----------|---------| | `lib/Enterprise/` | 38 PHP enterprise library classes (platform adapters, sync, validation, plugins) | | `cli/` | CLI scripts (archive, create, release, sync rulesets, version management) | -| `automation/` | Bulk sync, push files, repo cleanup, Gitea migration | +| `automation/` | Bulk sync, push files, repo cleanup, MokoGitea migration | | `validate/` | 18 validation scripts (health, structure, secrets, syntax, drift) | | `templates/` | **Workflow templates** and config templates synced to governed repos | | `definitions/` | Repository structure definitions (`.tf` format) | @@ -42,7 +42,7 @@ composer require mokoconsulting-tech/enterprise ### Composer Registry -This package is served from Gitea package registry. Add this to your `composer.json`: +This package is served from MokoGitea package registry. Add this to your `composer.json`: ```json { @@ -73,10 +73,10 @@ vendor/bin/moko inventory -- --path . | Variable | Purpose | |----------|---------| | `GIT_PLATFORM` | `gitea` (default) or `github` | -| `GA_TOKEN` | Gitea API / Gitea Actions token | +| `GA_TOKEN` | MokoGitea API / MokoGitea Actions token | | `GH_TOKEN` | GitHub API token (for mirror sync) | -| `GITEA_URL` | Gitea instance URL (default: `https://git.mokoconsulting.tech`) | -| `GITEA_ORG` | Gitea organization (default: `MokoConsulting`) | +| `GITEA_URL` | MokoGitea instance URL (default: `https://git.mokoconsulting.tech`) | +| `GITEA_ORG` | MokoGitea organization (default: `MokoConsulting`) | ## License diff --git a/analysis/index.md b/analysis/index.md index c786b1a..4f1b0d9 100644 --- a/analysis/index.md +++ b/analysis/index.md @@ -2,8 +2,8 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION -DEFGROUP: MokoPlatform.Index -INGROUP: MokoPlatform.Analysis +DEFGROUP: MokoCLI.Index +INGROUP: MokoCLI.Analysis REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli PATH: /analysis/index.md BRIEF: Analysis directory index diff --git a/automation/bulk_joomla_template.php b/automation/bulk_joomla_template.php deleted file mode 100644 index 496eabd..0000000 --- a/automation/bulk_joomla_template.php +++ /dev/null @@ -1,937 +0,0 @@ -#!/usr/bin/env php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoPlatform.Automation - * INGROUP: MokoPlatform.Scripts - * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli - * PATH: /automation/bulk_joomla_template.php - * BRIEF: Bulk scaffold and sync Joomla template repositories - * - * USAGE - * php automation/bulk_joomla_template.php --scaffold --name=MokoTheme - * php automation/bulk_joomla_template.php --scaffold --name=MokoTheme --client=administrator - * php automation/bulk_joomla_template.php --sync --repos=MokoTheme,MokoDarkTheme - * php automation/bulk_joomla_template.php --sync --all - * php automation/bulk_joomla_template.php --list - */ - -declare(strict_types=1); - -require_once __DIR__ . '/../vendor/autoload.php'; -require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; - -use MokoCli\{ - AuditLogger, - CliFramework, - Config, - GitPlatformAdapter, - MetricsCollector, - PlatformAdapterFactory -}; - -/** - * Bulk Joomla Template Manager - * - * Provides three operations for Joomla template projects: - * --scaffold: Create a new template repository with the full directory structure - * --sync: Push mokocli files to existing template repositories - * --list: List all repositories tagged as joomla-template - * - * Works with both GitHub and Gitea via the PlatformAdapterFactory. - */ -class BulkJoomlaTemplate extends CliFramework -{ - public const DEFAULT_ORG = 'MokoConsulting'; - public const VERSION = '09.23.00'; - - private GitPlatformAdapter $adapter; - private Config $config; - - protected function configure(): void - { - $this->setDescription('Bulk Joomla template management'); - $this->addArgument('--org', 'Organization', self::DEFAULT_ORG); - $this->addArgument('--scaffold', 'Create new template repo', false); - $this->addArgument('--sync', 'Sync files to template repos', false); - $this->addArgument('--list', 'List template repos', false); - $this->addArgument('--name', 'Template name for scaffold', ''); - $this->addArgument('--client', 'Joomla client: site or admin', 'site'); - $this->addArgument('--repos', 'Target repos (comma-separated)', ''); - $this->addArgument('--all', 'Sync all tagged repos', false); - $this->addArgument('--sync-updates', 'Sync updates.xml', false); - $this->addArgument('--private', 'Create as private repo', false); - $this->addArgument('--yes', 'Auto-confirm', false); - } - - protected function run(): int - { - $this->log("🎨 Joomla Template Manager v" . self::VERSION, 'INFO'); - - $this->config = Config::load(); - - try { - $this->adapter = PlatformAdapterFactory::create($this->config); - } catch (\Exception $e) { - $this->log("❌ Failed to initialize: " . $e->getMessage(), 'ERROR'); - return 1; - } - - $org = $this->getArgument('--org', self::DEFAULT_ORG); - $platform = $this->adapter->getPlatformName(); - $this->log("Platform: {$platform} | Organization: {$org}", 'INFO'); - - if ($this->getArgument('--list', false)) { - return $this->listTemplateRepos($org); - } - - if ($this->getArgument('--scaffold', false)) { - return $this->scaffoldTemplate($org); - } - - if ($this->getArgument('--sync', false)) { - return $this->syncTemplates($org); - } - - if ($this->getArgument('--sync-updates', false)) { - return $this->syncUpdatesBetweenPlatforms($org); - } - - $this->log("❌ Specify --scaffold, --sync, --sync-updates, or --list", 'ERROR'); - return 1; - } - - // ── List ───────────────────────────────────────────────────────────── - - private function listTemplateRepos(string $org): int - { - $repos = $this->findTemplateRepos($org); - - if (empty($repos)) { - $this->log("No joomla-template repositories found in {$org}", 'INFO'); - return 0; - } - - $this->log("\nJoomla template repositories in {$org}:", 'INFO'); - foreach ($repos as $repo) { - $vis = ($repo['private'] ?? false) ? 'private' : 'public'; - $url = $this->adapter->getRepoWebUrl($org, $repo['name']); - $this->log(" - {$repo['name']} ({$vis}) {$url}", 'INFO'); - } - $this->log("\nTotal: " . count($repos), 'INFO'); - - return 0; - } - - // ── Scaffold ───────────────────────────────────────────────────────── - - private function scaffoldTemplate(string $org): int - { - $name = $this->getArgument('--name', ''); - $client = $this->getArgument('--client', 'site'); - $dryRun = $this->dryRun; - - if (empty($name)) { - $this->log("❌ --name is required for --scaffold", 'ERROR'); - $this->log(" Example: --name=MokoTheme", 'ERROR'); - return 1; - } - - if (!in_array($client, ['site', 'administrator'], true)) { - $this->log("❌ --client must be 'site' or 'administrator'", 'ERROR'); - return 1; - } - - $shortName = $this->deriveShortName($name); - $this->log("\nScaffolding Joomla template:", 'INFO'); - $this->log(" Name: {$name}", 'INFO'); - $this->log(" Short name: {$shortName}", 'INFO'); - $this->log(" Client: {$client}", 'INFO'); - $this->log(" Element: tpl_{$shortName}", 'INFO'); - - if ($dryRun) { - $this->log("\n[DRY RUN] Would create repository and scaffold files", 'INFO'); - $this->printScaffoldPlan($shortName); - return 0; - } - - // Check if repo already exists - try { - $this->adapter->getRepo($org, $name); - $this->log("❌ Repository {$org}/{$name} already exists", 'ERROR'); - return 1; - } catch (\Exception $e) { - $this->adapter->getApiClient()->resetCircuitBreaker(); - } - - // Confirm - if (!$this->getArgument('--yes', false)) { - echo "\nCreate repository {$org}/{$name}? [y/N]: "; - $handle = fopen('php://stdin', 'r'); - $line = fgets($handle); - if ($handle) { - fclose($handle); - } - if (!is_string($line) || strtolower(trim($line)) !== 'y') { - $this->log("Cancelled.", 'INFO'); - return 0; - } - } - - // Create repository - $this->log("\nCreating repository...", 'INFO'); - try { - $isPrivate = $this->getArgument('--private', false); - $this->adapter->createOrgRepo($org, $name, [ - 'description' => "Joomla {$client} template — {$name}", - 'private' => $isPrivate, - 'auto_init' => true, - ]); - $this->log(" ✓ Repository created: {$org}/{$name}", 'INFO'); - } catch (\Exception $e) { - $this->log("❌ Failed to create repository: " . $e->getMessage(), 'ERROR'); - return 1; - } - - // Set topics - try { - $this->adapter->setRepoTopics($org, $name, [ - 'joomla', 'joomla-template', 'template', "joomla-{$client}", - ]); - $this->log(" ✓ Topics set", 'INFO'); - } catch (\Exception $e) { - $this->log(" ⚠️ Could not set topics: " . $e->getMessage(), 'WARN'); - $this->adapter->getApiClient()->resetCircuitBreaker(); - } - - // Scaffold files - $this->log("\nScaffolding template files...", 'INFO'); - $files = $this->getScaffoldFiles($name, $shortName, $client, $org); - - $created = 0; - foreach ($files as $path => $content) { - try { - $this->adapter->createOrUpdateFile( - $org, - $name, - $path, - $content, - "chore: scaffold {$path}" - ); - $this->log(" ✓ {$path}", 'INFO'); - $created++; - } catch (\Exception $e) { - $this->log(" ✗ {$path}: " . $e->getMessage(), 'ERROR'); - $this->adapter->getApiClient()->resetCircuitBreaker(); - } - } - - // Apply branch protection - try { - $this->adapter->setBranchProtection($org, $name, 'main', [ - 'required_reviews' => 1, - 'dismiss_stale' => true, - 'block_on_rejected' => true, - ]); - $this->log(" ✓ Branch protection applied", 'INFO'); - } catch (\Exception $e) { - $this->log(" ⚠️ Branch protection: " . $e->getMessage(), 'WARN'); - $this->adapter->getApiClient()->resetCircuitBreaker(); - } - - $url = $this->adapter->getRepoWebUrl($org, $name); - $this->log("\n✅ Template scaffolded: {$url}", 'INFO'); - $this->log(" {$created} files created", 'INFO'); - - return 0; - } - - // ── Sync ───────────────────────────────────────────────────────────── - - private function syncTemplates(string $org): int - { - $repos = []; - - if ($this->getArgument('--all', false)) { - $repos = $this->findTemplateRepos($org); - } else { - $reposArg = $this->getArgument('--repos', ''); - if (empty($reposArg)) { - $this->log("❌ --repos or --all required for --sync", 'ERROR'); - return 1; - } - $names = array_filter(array_map('trim', explode(',', $reposArg))); - foreach ($names as $name) { - $repos[] = ['name' => $name]; - } - } - - if (empty($repos)) { - $this->log("No template repositories to sync", 'INFO'); - return 0; - } - - $this->log("\nSyncing " . count($repos) . " template repo(s)...", 'INFO'); - - $dryRun = $this->dryRun; - $success = 0; - $failed = 0; - - foreach ($repos as $repo) { - $name = $repo['name']; - $this->log("\n[{$name}]", 'INFO'); - - try { - $repoData = $this->adapter->getRepo($org, $name); - $shortName = $this->deriveShortName($name); - $branch = $repoData['default_branch'] ?? 'main'; - - $syncFiles = $this->getSyncFiles($name, $shortName); - - $updated = 0; - foreach ($syncFiles as $path => $content) { - if ($dryRun) { - $this->log(" (dry-run) {$path}", 'INFO'); - $updated++; - continue; - } - - // Check if file exists - $existingSha = null; - try { - $existing = $this->adapter->getFileContents($org, $name, $path, $branch); - $existingSha = $existing['sha'] ?? null; - } catch (\Exception $e) { - $this->adapter->getApiClient()->resetCircuitBreaker(); - } - - try { - $this->adapter->createOrUpdateFile( - $org, - $name, - $path, - $content, - "chore: update {$path} from mokocli", - $existingSha, - $branch - ); - $this->log(" ✓ {$path}", 'INFO'); - $updated++; - } catch (\Exception $e) { - $this->log(" ✗ {$path}: " . $e->getMessage(), 'ERROR'); - $this->adapter->getApiClient()->resetCircuitBreaker(); - } - } - - $this->log(" {$updated} file(s) synced", 'INFO'); - $success++; - } catch (\Exception $e) { - $this->log(" ✗ {$name}: " . $e->getMessage(), 'ERROR'); - $failed++; - $this->adapter->getApiClient()->resetCircuitBreaker(); - } - } - - $this->log("\n" . str_repeat('=', 50), 'INFO'); - $this->log("Sync complete: {$success} succeeded, {$failed} failed", 'INFO'); - - return $failed > 0 ? 1 : 0; - } - - // ── Helpers ────────────────────────────────────────────────────────── - - private function findTemplateRepos(string $org): array - { - $allRepos = $this->adapter->listOrgRepos($org, true); - $templates = []; - - foreach ($allRepos as $repo) { - try { - $topics = $this->adapter->getRepoTopics($org, $repo['name']); - if (in_array('joomla-template', $topics, true)) { - $templates[] = $repo; - } - } catch (\Exception $e) { - $this->adapter->getApiClient()->resetCircuitBreaker(); - } - } - - return $templates; - } - - private function deriveShortName(string $name): string - { - // MokoTheme → mokotheme, Moko-Dark-Theme → mokodarktheme - return strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $name)); - } - - private function printScaffoldPlan(string $shortName): void - { - $files = [ - 'templateDetails.xml', - 'updates.xml', - 'src/index.php', - 'src/error.php', - 'src/offline.php', - 'src/component.php', - 'src/html/index.html', - 'src/css/.gitkeep', - 'src/js/.gitkeep', - 'src/images/.gitkeep', - "src/language/en-GB/tpl_{$shortName}.ini", - "src/language/en-GB/tpl_{$shortName}.sys.ini", - 'media/css/.gitkeep', - 'media/js/.gitkeep', - 'media/images/.gitkeep', - 'media/scss/.gitkeep', - '.editorconfig', - ]; - - $this->log("\nFiles that would be created:", 'INFO'); - foreach ($files as $f) { - $this->log(" + {$f}", 'INFO'); - } - } - - /** - * Generate the full set of scaffold files for a new template. - * - * @return array path => content - */ - private function getScaffoldFiles(string $name, string $shortName, string $client, string $org): array - { - $element = "tpl_{$shortName}"; - $now = date('Y-m-d'); - - $files = []; - - // templateDetails.xml - $files['templateDetails.xml'] = << - - {$name} - {$now} - Moko Consulting - hello@mokoconsulting.tech - https://mokoconsulting.tech - Copyright (C) 2026 Moko Consulting. All rights reserved. - GPL-3.0-or-later - 1.0.0 - {$name} — Joomla {$client} template by Moko Consulting - - - index.php - component.php - error.php - offline.php - templateDetails.xml - html - css - js - images - language - - - - css - js - images - scss - - - - topbar - navbar - hero - breadcrumbs - sidebar-left - sidebar-right - main-top - main-bottom - footer - debug - - - - - https://git.mokoconsulting.tech/{$org}/{$name}/raw/branch/main/updates.xml - - - https://raw.githubusercontent.com/{$org}/{$name}/main/updates.xml - - - - - -
- - - - - - - - -
-
-
-
- XML; - $files['templateDetails.xml'] = preg_replace('/^\t\t/m', '', $files['templateDetails.xml']); - - // updates.xml — dual-platform download URLs (Gitea primary, GitHub secondary) - $files['updates.xml'] = << - - {$name} - {$name} — Moko Consulting Joomla template - tpl_{$shortName} - template - 1.0.0 - - - https://git.mokoconsulting.tech/{$org}/{$name}/releases/download/v1.0.0/{$shortName}.zip - - - https://github.com/{$org}/{$name}/releases/download/v1.0.0/{$shortName}.zip - - - - 8.1 - - - XML; - $files['updates.xml'] = preg_replace('/^\t\t/m', '', $files['updates.xml']); - - // src/index.php - $files['src/index.php'] = <<<'PHP' - getWebAssetManager(); - - ?> - - - - - - - - -
- - -
- - - - -
- - - - -
- -
- -
- - - - - PHP; - $files['src/index.php'] = str_replace('TEMPLATE_SHORT_NAME', $shortName, $files['src/index.php']); - $files['src/index.php'] = preg_replace('/^\t\t/m', '', $files['src/index.php']); - - // src/error.php - $files['src/error.php'] = <<<'PHP' - error->getCode(); - $message = htmlspecialchars($this->error->getMessage(), ENT_QUOTES, 'UTF-8'); - - ?> - - - - - - <?php echo $code; ?> — <?php echo $message; ?> - - - - - - PHP; - $files['src/error.php'] = preg_replace('/^\t\t/m', '', $files['src/error.php']); - - // src/offline.php - $files['src/offline.php'] = <<<'PHP' - - - - - - - <?php echo htmlspecialchars($app->get('sitename')); ?> — Maintenance - - -
-

get('sitename')); ?>

-

get('offline_message', 'This site is currently undergoing maintenance. Please check back soon.'); ?>

- -
- - - - - - -
-
- - - PHP; - $files['src/offline.php'] = preg_replace('/^\t\t/m', '', $files['src/offline.php']); - - // src/component.php - $files['src/component.php'] = <<<'PHP' - - - - - - - - - - - - PHP; - $files['src/component.php'] = preg_replace('/^\t\t/m', '', $files['src/component.php']); - - // Directory keepfiles - $files['src/html/index.html'] = ''; - $files['src/css/.gitkeep'] = ''; - $files['src/js/.gitkeep'] = ''; - $files['src/images/.gitkeep'] = ''; - $files['media/css/.gitkeep'] = ''; - $files['media/js/.gitkeep'] = ''; - $files['media/images/.gitkeep'] = ''; - $files['media/scss/.gitkeep'] = ''; - - // Language files - $files["src/language/en-GB/{$element}.ini"] = "; {$name} language strings\n"; - $files["src/language/en-GB/{$element}.sys.ini"] = - "; {$name} system language strings\n" - . "{$element}=\"{$name}\"\n" - . "{$element}_XML_DESCRIPTION=\"{$name} Joomla template by Moko Consulting\"\n"; - - // .editorconfig - $repoRoot = dirname(__DIR__, 2); - $editorConfig = "{$repoRoot}/templates/configs/.editorconfig"; - if (file_exists($editorConfig)) { - $files['.editorconfig'] = file_get_contents($editorConfig) ?: ''; - } - - return $files; - } - - /** - * Get files to sync to existing template repos (standards-only, no template code). - * - * @return array path => content - */ - private function getSyncFiles(string $name, string $shortName): array - { - $repoRoot = dirname(__DIR__, 2); - $files = []; - - // Sync standards files from templates/ - $standardsFiles = [ - 'SECURITY.md' => 'templates/docs/required/template-SECURITY.md', - 'CODE_OF_CONDUCT.md' => 'templates/docs/extra/template-CODE_OF_CONDUCT.md', - 'CONTRIBUTING.md' => 'templates/docs/required/template-CONTRIBUTING.md', - '.editorconfig' => 'templates/configs/.editorconfig', - ]; - - foreach ($standardsFiles as $dest => $source) { - $fullPath = "{$repoRoot}/{$source}"; - if (file_exists($fullPath)) { - $files[$dest] = file_get_contents($fullPath) ?: ''; - } - } - - return $files; - } - - // ── Sync updates.xml between platforms ─────────────────────────────── - - /** - * Sync updates.xml (or updates.xml) between Gitea and GitHub for Joomla repos. - * - * Reads the file from both platforms, compares by latest tag, - * and pushes the newer one to the stale platform. - * - * Designed to be called from a CI workflow via: - * php automation/bulk_joomla_template.php --sync-updates --repos=MokoCassiopeia - */ - private function syncUpdatesBetweenPlatforms(string $org): int - { - $repos = []; - - if ($this->getArgument('--all', false)) { - $repos = $this->findTemplateRepos($org); - // Also include waas-component repos - $allRepos = $this->adapter->listOrgRepos($org, true); - foreach ($allRepos as $repo) { - try { - $topics = $this->adapter->getRepoTopics($org, $repo['name']); - if (in_array('joomla', $topics, true) || in_array('joomla-extension', $topics, true)) { - $repos[] = $repo; - } - } catch (\Exception $e) { - $this->adapter->getApiClient()->resetCircuitBreaker(); - } - } - // Deduplicate - $seen = []; - $repos = array_filter($repos, function ($r) use (&$seen) { - if (isset($seen[$r['name']])) { - return false; - } - $seen[$r['name']] = true; - return true; - }); - } else { - $reposArg = $this->getArgument('--repos', ''); - if (empty($reposArg)) { - $this->log("❌ --repos or --all required for --sync-updates", 'ERROR'); - return 1; - } - $names = array_filter(array_map('trim', explode(',', $reposArg))); - foreach ($names as $name) { - $repos[] = ['name' => $name]; - } - } - - if (empty($repos)) { - $this->log("No Joomla repositories to sync updates for", 'INFO'); - return 0; - } - - // Create both platform adapters - try { - $adapters = PlatformAdapterFactory::createBoth($this->config); - } catch (\Exception $e) { - $this->log("❌ Both platform tokens required for --sync-updates: " . $e->getMessage(), 'ERROR'); - return 1; - } - - $gitea = $adapters['gitea']; - $github = $adapters['github']; - $dryRun = $this->dryRun; - - $this->log("\nSyncing updates.xml across Gitea <-> GitHub for " . count($repos) . " repo(s)...", 'INFO'); - - $synced = 0; - $failed = 0; - - foreach ($repos as $repo) { - $name = $repo['name']; - $this->log("\n[{$name}]", 'INFO'); - - // Try both updates.xml and updates.xml filenames - $updateFile = $this->resolveUpdateFile($gitea, $github, $org, $name); - if ($updateFile === null) { - $this->log(" ⊘ No update(s).xml found on either platform", 'INFO'); - continue; - } - - $fileName = $updateFile['name']; - $source = $updateFile['source']; // 'gitea' or 'github' - $content = $updateFile['content']; - $target = $source === 'gitea' ? 'github' : 'gitea'; - $targetAdapter = $source === 'gitea' ? $github : $gitea; - - $this->log(" Source: {$source} ({$fileName})", 'INFO'); - - if ($dryRun) { - $this->log(" (dry-run) Would push {$fileName} to {$target}", 'INFO'); - $synced++; - continue; - } - - // Push to the other platform - try { - $existingSha = null; - try { - $existing = $targetAdapter->getFileContents($org, $name, $fileName); - $existingSha = $existing['sha'] ?? null; - - // Compare content — skip if identical - $existingContent = base64_decode($existing['content'] ?? ''); - if (trim($existingContent) === trim($content)) { - $this->log(" ✓ Already in sync", 'INFO'); - $synced++; - continue; - } - } catch (\Exception $e) { - $targetAdapter->getApiClient()->resetCircuitBreaker(); - } - - $targetAdapter->createOrUpdateFile( - $org, - $name, - $fileName, - $content, - "chore: sync {$fileName} from {$source}", - $existingSha - ); - $this->log(" ✓ Pushed to {$target}", 'INFO'); - $synced++; - } catch (\Exception $e) { - $this->log(" ✗ Failed to push to {$target}: " . $e->getMessage(), 'ERROR'); - $targetAdapter->getApiClient()->resetCircuitBreaker(); - $failed++; - } - } - - $this->log("\n" . str_repeat('=', 50), 'INFO'); - $this->log("Updates sync complete: {$synced} synced, {$failed} failed", 'INFO'); - - return $failed > 0 ? 1 : 0; - } - - /** - * Find the updates file on both platforms, return the one with the higher version. - * - * Checks both `updates.xml` and `updates.xml` filenames. - * Returns the content from the platform with the newer . - * Gitea wins ties (primary platform). - * - * @return array{name: string, source: string, content: string}|null - */ - private function resolveUpdateFile( - GitPlatformAdapter $gitea, - GitPlatformAdapter $github, - string $org, - string $name - ): ?array { - $candidates = ['updates.xml', 'updates.xml']; - $found = []; // platform => [name, content, version] - - foreach (['gitea' => $gitea, 'github' => $github] as $platform => $adapter) { - foreach ($candidates as $fileName) { - try { - $file = $adapter->getFileContents($org, $name, $fileName); - $content = base64_decode($file['content'] ?? ''); - - // Extract latest version from the XML - $version = '0.0.0'; - if (preg_match('/([^<]+)<\/version>/', $content, $m)) { - $version = trim($m[1]); - } - - $found[$platform] = [ - 'name' => $fileName, - 'content' => $content, - 'version' => $version, - ]; - break; // Found one — stop checking other filenames for this platform - } catch (\Exception $e) { - $adapter->getApiClient()->resetCircuitBreaker(); - } - } - } - - if (empty($found)) { - return null; - } - - // If only one platform has it, that's the source - if (count($found) === 1) { - $platform = array_key_first($found); - return [ - 'name' => $found[$platform]['name'], - 'source' => $platform, - 'content' => $found[$platform]['content'], - ]; - } - - // Both have it — pick the one with the higher version (Gitea wins ties) - $giteaVer = $found['gitea']['version']; - $githubVer = $found['github']['version']; - - $source = version_compare($githubVer, $giteaVer, '>') ? 'github' : 'gitea'; - - return [ - 'name' => $found[$source]['name'], - 'source' => $source, - 'content' => $found[$source]['content'], - ]; - } -} - -// Execute if run directly -if (php_sapi_name() === 'cli' && isset($argv[0]) && realpath($argv[0]) === __FILE__) { - $app = new BulkJoomlaTemplate(); - exit($app->execute()); -} diff --git a/automation/bulk_sync.php b/automation/bulk_sync.php deleted file mode 100755 index efcedd3..0000000 --- a/automation/bulk_sync.php +++ /dev/null @@ -1,1428 +0,0 @@ -#!/usr/bin/env php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoPlatform.Automation - * INGROUP: MokoPlatform.Scripts - * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli - * PATH: /automation/bulk_sync.php - * BRIEF: Enterprise-grade bulk repository synchronization - */ - -declare(strict_types=1); - -require_once __DIR__ . '/../vendor/autoload.php'; -require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; - -use MokoCli\{ - ApiClient, - AuditLogger, - CheckpointManager, - CircuitBreakerOpen, - CliFramework, - Config, - GitPlatformAdapter, - MetricsCollector, - PlatformAdapterFactory, - PluginFactory, - ProjectTypeDetector, - RateLimitExceeded, - RepositorySynchronizer, - SecurityValidator, - SynchronizationNotImplementedException -}; - -/** - * Bulk Repository Synchronization Tool - * - * Synchronizes mokocli files across multiple repositories using - * the Enterprise library for robust, audited operations. - */ -class BulkSync extends CliFramework -{ - /** - * Default organization for bulk sync operations - * Public to allow script instantiation with class constants - */ - public const DEFAULT_ORG = 'MokoConsulting'; - - /** - * Script version number - * Public to allow script instantiation with class constants - */ - public const VERSION = '09.23.00'; - public const VERSION_MINOR = '04.05'; - - private ApiClient $api; - private GitPlatformAdapter $adapter; - private RepositorySynchronizer $synchronizer; - private AuditLogger $logger; - private CheckpointManager $checkpoints; - private MetricsCollector $metrics; - private Config $config; - - /** Set to true by signal handler or rate-limit detection to abort the sync loop gracefully. */ - private bool $interrupted = false; - - /** - * Setup command-line arguments - */ - protected function configure(): void - { - $this->setDescription('Bulk repository synchronization'); - $this->addArgument('--org', 'Organization', self::DEFAULT_ORG); - $this->addArgument('--repos', 'Specific repos', ''); - $this->addArgument('--exclude', 'Repos to exclude', ''); - $this->addArgument('--skip-archived', 'Skip archived repos', false); - $this->addArgument('--yes', 'Auto-confirm', false); - $this->addArgument('--resume', 'Resume from checkpoint', false); - $this->addArgument('--force', 'Force overwrite', false); - $this->addArgument('--protect', 'Apply branch protection', false); - $this->addArgument('--no-issue', 'Skip tracking issue', false); - $this->addArgument('--update-branches', 'Merge main into branches', false); - $this->addArgument('--health', 'Run health checks', false); - } - - /** - * Main execution - */ - protected function run(): int - { - $this->log("🚀 mokocli Bulk Synchronization v" . self::VERSION, 'INFO'); - - // Initialize enterprise components - if (!$this->initializeComponents()) { - return 1; - } - - // Get configuration - $org = $this->getArgument('--org', self::DEFAULT_ORG); - $skipArchived = $this->getArgument('--skip-archived', false); - $autoConfirm = $this->getArgument('--yes', false); - - // Get repository filters - $specificRepos = $this->parseRepositoryList($this->getArgument('--repos', '')); - $excludeRepos = $this->parseRepositoryList($this->getArgument('--exclude', '')); - - $this->log("Organization: {$org}", 'INFO'); - if (!empty($specificRepos)) { - $this->log("Repositories: " . implode(', ', $specificRepos), 'INFO'); - } - if (!empty($excludeRepos)) { - $this->log("Excluding: " . implode(', ', $excludeRepos), 'INFO'); - } - - // Get repositories - $this->log("📋 Fetching repositories...", 'INFO'); - $repositories = $this->synchronizer->getRepositories($org, $skipArchived); - - // Apply filters - $repositories = $this->filterRepositories($repositories, $specificRepos, $excludeRepos); - - $count = count($repositories); - $this->log("Found {$count} repositories to sync", 'INFO'); - - if ($count === 0) { - $this->log("No repositories to process", 'WARN'); - return 0; - } - - // Load resume checkpoint if --resume is set - $alreadyProcessed = []; - if ($this->getArgument('--resume', false)) { - $checkpoint = $this->checkpoints->loadCheckpoint('bulk_sync'); - if ($checkpoint !== null) { - $alreadyProcessed = array_keys($checkpoint['results']['repositories'] ?? []); - $skipCount = count($alreadyProcessed); - $stoppedAt = $checkpoint['stopped_at'] ?? 'unknown'; - $reason = $checkpoint['stopped_reason'] ?? 'unknown'; - $this->log("▶ Resuming from checkpoint ({$reason} at '{$stoppedAt}') — skipping {$skipCount} already-processed repositories", 'INFO'); - } else { - $this->log("⚠️ No checkpoint found, starting from scratch", 'WARN'); - } - } - - // Confirm before proceeding - $remaining = $count - count($alreadyProcessed); - if (!$autoConfirm && !$this->confirmSync($remaining > 0 ? $remaining : $count)) { - $this->log("❌ Sync cancelled by user", 'INFO'); - return 0; - } - - // Sync universal workflows from Template-Generic → other templates first - $this->log("📋 Syncing universal workflows to template repos...", 'INFO'); - $templateUpdates = $this->synchronizer->syncUniversalWorkflowsToTemplates($org); - $this->log("Template sync: {$templateUpdates} file(s) updated", 'INFO'); - - // Execute synchronization - $this->log("🔄 Starting synchronization...", 'INFO'); - $results = $this->executeSynchronization($org, $repositories, $alreadyProcessed); - - // Display results - $this->displayResults($results); - - // Apply branch protection if --protect flag is set - if (isset($this->options['protect'])) { - $this->log("🔒 Applying branch protection rules...", 'INFO'); - $results['protection'] = $this->applyBranchProtectionAll($org, $repositories); - } - - // Run repo health checks if --health flag is set - if (isset($this->options['health'])) { - $this->log("🩺 Running repository health checks...", 'INFO'); - $results['health'] = $this->runHealthChecksAll($org, $repositories); - } - - // Create/update tracking issue in mokocli - $this->createSyncIssue($org, $results); - - // Create/update a failure issue when any repos failed - if ($results['failed'] > 0) { - $this->createFailureIssue($org, $results); - } - - return $results['failed'] > 0 ? 1 : 0; - } - - /** - * Initialize enterprise components - */ - private function initializeComponents(): bool - { - $this->config = Config::load(); - $platform = $this->config->getString('platform', 'github'); - - try { - $this->adapter = PlatformAdapterFactory::create($this->config); - $this->api = $this->adapter->getApiClient(); - - $this->logger = new AuditLogger('bulk_sync'); - $this->metrics = new MetricsCollector(); - $this->checkpoints = new CheckpointManager('.checkpoints'); - $this->synchronizer = new RepositorySynchronizer( - $this->api, - $this->logger, - $this->metrics, - $this->checkpoints, - null, - $this->adapter - ); - - // Initialize plugin system - - $this->log("✓ Enterprise components initialized for platform: {$platform}", 'INFO'); - return true; - } catch (\Exception $e) { - $this->log("❌ Failed to initialize: " . $e->getMessage(), 'ERROR'); - return false; - } - } - - /** - * Parse repository list from string - */ - private function parseRepositoryList(string $input): array - { - if (empty($input)) { - return []; - } - - return array_filter( - array_map('trim', preg_split('/[\s,]+/', $input)), - fn($r) => !empty($r) - ); - } - - /** - * Filter repositories based on include/exclude lists - */ - /** Repositories that are permanently excluded from bulk sync. */ - private const ALWAYS_EXCLUDE = ['mokocli', '.github-private']; - - private function filterRepositories(array $repositories, array $include, array $exclude): array - { - // Apply include filter if specified (but never override permanent exclusions) - if (!empty($include)) { - $repositories = array_filter( - $repositories, - fn($repo) => in_array($repo['name'], $include) - ); - } - - // Merge user excludes with permanent excludes - $allExclude = array_unique(array_merge($exclude, self::ALWAYS_EXCLUDE)); - - $repositories = array_filter( - $repositories, - fn($repo) => !in_array($repo['name'], $allExclude) - ); - - return array_values($repositories); - } - - /** - * Sort repositories so that .github-private is always processed first. - * All other repositories retain their original relative order. - * - * @param array $repositories - * @return array - */ - private function prioritizeRepositories(array $repositories): array - { - $priority = []; - $rest = []; - - foreach ($repositories as $repo) { - if ($repo['name'] === '.github-private') { - $priority[] = $repo; - } else { - $rest[] = $repo; - } - } - - return array_merge($priority, $rest); - } - - /** - * Confirm synchronization with user - */ - private function confirmSync(int $count): bool - { - if ($this->quiet) { - return true; - } - - echo "\n⚠️ About to synchronize {$count} repositories.\n"; - echo "This will update files across all repositories.\n"; - echo "\nContinue? [y/N]: "; - - $handle = fopen("php://stdin", "r"); - $line = fgets($handle); - if ($handle) { - fclose($handle); - } - - // fgets() returns false when stdin is not a TTY (e.g. CI, piped input); - // treat that as a non-confirmation rather than crashing. - return is_string($line) && strtolower(trim($line)) === 'y'; - } - - /** - * Execute synchronization across repositories - * - * @param array $alreadyProcessed Repo names to skip (from a resumed checkpoint) - */ - private function executeSynchronization(string $org, array $repositories, array $alreadyProcessed = []): array - { - $results = [ - 'total' => count($repositories), - 'success' => 0, - 'skipped' => 0, - 'failed' => 0, - 'repositories' => [], - 'prs' => [], - 'issues' => [], - ]; - - // Seed results with repos that were already processed so the final - // summary and issue reflect the full run, not just the resumed portion. - foreach ($alreadyProcessed as $name) { - $results['repositories'][$name] = 'skipped (resumed)'; - $results['skipped']++; - } - - // Register signal handlers so Ctrl-C / SIGTERM saves a resume checkpoint - // instead of leaving the run in an unknown state. - if (function_exists('pcntl_async_signals')) { - pcntl_async_signals(true); - pcntl_signal(SIGINT, function () { - $this->interrupted = true; - }); - pcntl_signal(SIGTERM, function () { - $this->interrupted = true; - }); - } - - $startTime = microtime(true); - - foreach ($repositories as $index => $repo) { - $repoName = $repo['name']; - $progress = $index + 1; - $total = $results['total']; - - // Skip repos already covered by a previous partial run - if (in_array($repoName, $alreadyProcessed, true)) { - $this->log("[{$progress}/{$total}] ⊘ {$repoName} (already processed — skipping)", 'INFO'); - continue; - } - - // Check for Ctrl-C / SIGTERM before starting each repo - if ($this->interrupted) { - $this->log("⚡ Interrupted before {$repoName} — saving checkpoint", 'WARN'); - $this->saveInterruptCheckpoint($results, $repoName, 'interrupted'); - break; - } - - $this->log("[{$progress}/{$total}] Processing {$repoName}...", 'INFO'); - - // Reset circuit breaker before processing each repository - // This prevents failures on one repo from blocking subsequent repos - $this->api->resetCircuitBreaker(); - - // Ensure standard labels exist on the target repo before syncing. - // Label provisioning is non-critical — if the circuit breaker trips - // (e.g. 54 new labels on a fresh repo), reset it so file sync proceeds. - if (!$this->dryRun) { - $this->ensureRepoLabels($org, $repoName); - $this->ensureReleaseTags($org, $repoName); - $this->api->resetCircuitBreaker(); - } - - try { - $updated = $this->synchronizer->processRepository( - $org, - $repoName, - $this->dryRun, - isset($this->options['force']) - ); - - if ($updated !== false && $updated > 0) { - $results['success']++; - $results['repositories'][$repoName] = 'success'; - $results['prs'][$repoName] = $updated; - $this->log(" ✓ {$repoName} updated", 'INFO'); - if (!isset($this->options['no-issue']) && !$this->dryRun) { - $issueNum = $this->createTargetRepoIssue($org, $repoName, $updated); - if ($issueNum !== null) { - $results['issues'][$repoName] = $issueNum; - } - } - if (isset($this->options['update-branches']) && !$this->dryRun) { - $this->updateOpenBranches($org, $repoName); - } - } else { - $results['skipped']++; - $results['repositories'][$repoName] = 'skipped'; - $this->log(" ⊘ {$repoName} skipped", 'INFO'); - } - } catch (SynchronizationNotImplementedException $e) { - $this->log("", 'ERROR'); - $this->log("╔══════════════════════════════════════════════════════════════════════════╗", 'ERROR'); - $this->log("║ CRITICAL ERROR: Repository Synchronization Not Implemented ║", 'ERROR'); - $this->log("╚══════════════════════════════════════════════════════════════════════════╝", 'ERROR'); - $this->log("", 'ERROR'); - $this->log("The bulk repository sync is failing silently because the core", 'ERROR'); - $this->log("synchronization logic has not been implemented yet.", 'ERROR'); - $this->log("", 'ERROR'); - $this->log("Location: lib/Enterprise/RepositorySynchronizer.php", 'ERROR'); - $this->log("Method: processRepository()", 'ERROR'); - $this->log("", 'ERROR'); - $this->log("Required Implementation:", 'ERROR'); - $this->log(" 1. Clone/fetch target repository", 'ERROR'); - $this->log(" 2. Apply file updates based on mokocli configuration", 'ERROR'); - $this->log(" 3. Create pull request with changes", 'ERROR'); - $this->log(" 4. Handle merge conflicts and validation", 'ERROR'); - $this->log("", 'ERROR'); - $this->log("Until this is implemented, bulk sync will not function.", 'ERROR'); - $this->log("", 'ERROR'); - throw $e; - } catch (CircuitBreakerOpen $e) { - $results['failed']++; - $results['repositories'][$repoName] = 'failed'; - $this->log(" ✗ {$repoName} failed: Circuit breaker open - " . $e->getMessage(), 'ERROR'); - } catch (RateLimitExceeded $e) { - // Rate limit hit — abort immediately so we don't burn retries on 403s - $results['failed']++; - $results['repositories'][$repoName] = 'failed'; - $this->log(" ✗ {$repoName} rate-limited: " . $e->getMessage(), 'ERROR'); - $this->saveInterruptCheckpoint($results, $repoName, 'rate_limited'); - break; - } catch (\Exception $e) { - // Also catch rate limits surfaced as generic exceptions by ApiClient retries - if ($this->isRateLimitError($e)) { - $results['failed']++; - $results['repositories'][$repoName] = 'failed'; - $this->log(" ✗ {$repoName} rate-limited — stopping sync", 'ERROR'); - $this->saveInterruptCheckpoint($results, $repoName, 'rate_limited'); - break; - } - $results['failed']++; - $results['repositories'][$repoName] = 'failed'; - $this->log(" ✗ {$repoName} failed: " . $e->getMessage(), 'ERROR'); - } - - // Save rolling checkpoint after each repo (skipped in dry-run) - if (!$this->dryRun) { - $this->checkpoints->saveCheckpoint('bulk_sync', [ - 'processed' => $progress, - 'total' => $total, - 'results' => $results, - 'stopped_at' => $repoName, - 'stopped_reason' => 'checkpoint', - ]); - } - } - - $duration = microtime(true) - $startTime; - $results['duration'] = $duration; - - return $results; - } - - /** - * Return true when an exception message indicates a GitHub rate-limit response. - * Catches 403 rate-limit errors that ApiClient wraps as generic exceptions. - */ - private function isRateLimitError(\Exception $e): bool - { - $msg = strtolower($e->getMessage()); - return str_contains($msg, 'rate limit') || str_contains($msg, '429') - || (str_contains($msg, '403') && str_contains($msg, 'rate')); - } - - /** - * Save a checkpoint that records where the sync was interrupted, then print - * a hint showing the exact command needed to resume. - */ - private function saveInterruptCheckpoint(array $results, string $stoppedAt, string $reason): void - { - if ($this->dryRun) { - return; - } - - try { - $this->checkpoints->saveCheckpoint('bulk_sync', [ - 'processed' => count($results['repositories']), - 'total' => $results['total'], - 'results' => $results, - 'stopped_at' => $stoppedAt, - 'stopped_reason' => $reason, - ]); - $script = basename(__FILE__); - $this->log("💾 Checkpoint saved. To resume once the issue is resolved, run:", 'INFO'); - $this->log(" php automation/{$script} --resume [same flags as before]", 'INFO'); - } catch (\Exception $e) { - $this->log("⚠️ Failed to save interrupt checkpoint: " . $e->getMessage(), 'WARN'); - } - } - - /** - * Display synchronization results - */ - private function displayResults(array $results): void - { - $this->log("\n" . str_repeat('=', 60), 'INFO'); - $this->log("📊 Synchronization Complete", 'INFO'); - $this->log(str_repeat('=', 60), 'INFO'); - - $total = $results['total']; - $success = $results['success']; - $skipped = $results['skipped']; - $failed = $results['failed']; - $duration = $results['duration']; - - $successRate = $total > 0 ? round(($success / $total) * 100, 1) : 0; - - $this->log(sprintf("Total: %d repositories", $total), 'INFO'); - $this->log(sprintf("Success: %d (✓)", $success), 'INFO'); - $this->log(sprintf("Skipped: %d (⊘)", $skipped), 'INFO'); - $this->log(sprintf("Failed: %d (✗)", $failed), 'INFO'); - $this->log(sprintf("Success Rate: %.1f%%", $successRate), 'INFO'); - $this->log(sprintf("Duration: %.2f seconds", $duration), 'INFO'); - - if ($failed > 0) { - $this->log("\n⚠️ Failed Repositories:", 'WARN'); - foreach ($results['repositories'] as $repo => $status) { - if ($status === 'failed') { - $this->log(" - {$repo}", 'WARN'); - } - } - } - - if ($this->verbose) { - $this->log("\n📋 Repository Details:", 'INFO'); - foreach ($results['repositories'] as $repo => $status) { - $icon = match ($status) { - 'success' => '✓', - 'skipped' => '⊘', - 'failed' => '✗', - default => '?' - }; - $this->log(sprintf(" %s %s: %s", $icon, $repo, $status), 'INFO'); - } - } - - $this->log(str_repeat('=', 60), 'INFO'); - - $this->writeStepSummary($results); - } - - /** - * Write synchronization results to the GitHub Actions step summary. - * - * Appends a Markdown summary table listing every repository that was - * processed — together with its outcome (updated, skipped, or failed) — - * to the file referenced by the GITHUB_STEP_SUMMARY environment variable. - * When that variable is not set (e.g. local runs) the method is a no-op. - */ - private function writeStepSummary(array $results): void - { - // Check both GitHub and Gitea step summary env vars - $summaryFile = getenv($this->adapter->getStepSummaryEnvVar()); - if (empty($summaryFile)) { - // Fallback: also check the other platform's env var - $fallback = $this->adapter->getPlatformName() === 'github' - ? getenv('GITEA_STEP_SUMMARY') - : getenv('GITHUB_STEP_SUMMARY'); - $summaryFile = $fallback ?: ''; - } - if (empty($summaryFile)) { - return; - } - - // Validate that the path is an absolute filesystem path and not a - // special device file, to guard against environment variable injection. - $realDir = realpath(dirname($summaryFile)); - if ($realDir === false || !str_starts_with($summaryFile, '/') || strpos($summaryFile, '..') !== false) { - $this->log('⚠️ GITHUB_STEP_SUMMARY path is not safe, skipping step summary write.', 'WARN'); - return; - } - - $total = $results['total']; - $success = $results['success']; - $skipped = $results['skipped']; - $failed = $results['failed']; - $duration = $results['duration']; - $successRate = $total > 0 ? round(($success / $total) * 100, 1) : 0; - - $lines = []; - $lines[] = ''; - $lines[] = '### 📊 Synchronization Summary'; - $lines[] = ''; - $lines[] = '| Total | ✅ Updated | ⊘ Skipped | ❌ Failed | Success Rate | Duration |'; - $lines[] = '|------:|----------:|----------:|----------:|-------------:|---------:|'; - $lines[] = sprintf( - '| %d | %d | %d | %d | %.1f%% | %.2fs |', - $total, - $success, - $skipped, - $failed, - $successRate, - $duration - ); - $lines[] = ''; - - if (!empty($results['repositories'])) { - $lines[] = '### 📋 Repositories Processed'; - $lines[] = ''; - $lines[] = '| Repository | Status |'; - $lines[] = '|:-----------|:-------|'; - foreach ($results['repositories'] as $repo => $status) { - $label = match ($status) { - 'success' => '✅ Updated', - 'skipped' => '⊘ Skipped', - 'failed' => '❌ Failed', - default => $status, - }; - $lines[] = sprintf('| `%s` | %s |', $repo, $label); - } - $lines[] = ''; - } - - $written = file_put_contents($summaryFile, implode("\n", $lines) . "\n", FILE_APPEND); - if ($written === false) { - $this->log('⚠️ Failed to write to GITHUB_STEP_SUMMARY.', 'WARN'); - } - } - - /** - * Apply main branch protection to all repositories. - * - * Tries classic branch protection first; records the outcome per repo. - * Private repos on the free GitHub plan will receive a 403 — those are - * noted but do not count as failures. - * - * @param array $repositories - * @return array repo name => 'protected'|'skipped'|'no_main'|'plan_limit'|'error' - */ - private function applyBranchProtectionAll(string $org, array $repositories): array - { - $protection = []; - - foreach ($repositories as $repo) { - $name = $repo['name']; - - if ($this->dryRun) { - $this->log(" (dry-run) would protect {$name}/main", 'INFO'); - $protection[$name] = 'skipped'; - continue; - } - - try { - $this->adapter->setBranchProtection($org, $name, 'main', [ - 'required_reviews' => 1, - 'dismiss_stale' => false, - 'enforce_admins' => false, - 'require_code_owner' => false, - ]); - $protection[$name] = 'protected'; - $this->log(" 🔒 {$name}: main branch protected", 'INFO'); - } catch (\Exception $e) { - $msg = $e->getMessage(); - if (str_contains($msg, '403')) { - $protection[$name] = 'plan_limit'; - $this->log(" ⚠️ {$name}: branch protection requires upgraded plan (private repo)", 'WARN'); - } elseif (str_contains($msg, '404')) { - $protection[$name] = 'no_main'; - $this->log(" ⊘ {$name}: no main branch found", 'INFO'); - } else { - $protection[$name] = 'error'; - $this->log(" ✗ {$name}: {$msg}", 'ERROR'); - } - $this->api->resetCircuitBreaker(); - } - } - - $protectedCount = count(array_filter($protection, fn($v) => $v === 'protected')); - $planLimitCount = count(array_filter($protection, fn($v) => $v === 'plan_limit')); - $this->log(sprintf( - "🔒 Branch protection: %d protected, %d require upgraded plan", - $protectedCount, - $planLimitCount - ), 'INFO'); - - return $protection; - } - - /** - * Run lightweight health checks on all repositories after sync. - * - * Checks rulesets (MAIN, VERSION, DEV) and branch protection via the GitHub API. - * Returns a map of repo name => ['score' => int, 'max' => int, 'level' => string]. - * - * @param array $repositories - * @return array - */ - private function runHealthChecksAll(string $org, array $repositories): array - { - $health = []; - - foreach ($repositories as $repo) { - $name = $repo['name']; - $score = 0; - $max = 0; - - // 1. Check branch protection rules (rulesets on GitHub, branch_protections on Gitea) - $max += 20; - try { - $protections = $this->adapter->listBranchProtections($org, $name); - $hasMain = $hasVersion = $hasDev = $hasRc = false; - - foreach ($protections as $prot) { - $protName = strtolower($prot['name'] ?? $prot['branch_name'] ?? ''); - $refs = $prot['conditions']['ref_name']['include'] ?? []; - - if (str_contains($protName, 'main') || in_array('refs/heads/main', $refs, true)) { - $hasMain = true; - } - if (str_contains($protName, 'version') || $this->refsContain($refs, 'version')) { - $hasVersion = true; - } - if ( - (str_contains($protName, 'dev') && !str_contains($protName, 'develop')) - || $this->refsContain($refs, 'dev') - ) { - $hasDev = true; - } - if (str_contains($protName, 'rc') || $this->refsContain($refs, 'rc/')) { - $hasRc = true; - } - } - - if ($hasMain) { - $score += 5; - } - if ($hasVersion) { - $score += 5; - } - if ($hasDev) { - $score += 5; - } - if ($hasRc) { - $score += 5; - } - } catch (\Exception $e) { - $this->api->resetCircuitBreaker(); - } - - // 2. Check branch protection on main (10 pts) - $max += 10; - $hasMainProtection = $this->checkBranchProtected($org, $name); - if ($hasMainProtection) { - $score += 10; - } - - // Calculate level - $pct = $max > 0 ? ($score / $max * 100) : 0; - $level = match (true) { - $pct >= 90 => 'excellent', - $pct >= 70 => 'good', - $pct >= 50 => 'fair', - default => 'poor', - }; - - $health[$name] = ['score' => $score, 'max' => $max, 'level' => $level]; - - if ($pct < 70) { - $this->log(" ⚠️ {$name}: health {$score}/{$max} ({$level})", 'WARN'); - } else { - $this->log(" ✓ {$name}: health {$score}/{$max} ({$level})", 'INFO'); - } - } - - $excellent = count(array_filter($health, fn($h) => $h['level'] === 'excellent')); - $good = count(array_filter($health, fn($h) => $h['level'] === 'good')); - $fair = count(array_filter($health, fn($h) => $h['level'] === 'fair')); - $poor = count(array_filter($health, fn($h) => $h['level'] === 'poor')); - $this->log(sprintf( - "🩺 Health: %d excellent, %d good, %d fair, %d poor", - $excellent, - $good, - $fair, - $poor - ), 'INFO'); - - return $health; - } - - /** - * Check if any ref patterns in the array contain a given keyword. - */ - private function refsContain(array $refs, string $keyword): bool - { - foreach ($refs as $ref) { - if (str_contains($ref, $keyword)) { - return true; - } - } - return false; - } - - /** - * Check if a repo's main branch has protection enabled. - * - * @return bool True if main branch is protected - */ - private function checkBranchProtected(string $org, string $repo): bool - { - try { - $protections = $this->adapter->listBranchProtections($org, $repo); - foreach ($protections as $prot) { - $name = strtolower($prot['name'] ?? $prot['branch_name'] ?? ''); - if (str_contains($name, 'main')) { - return true; - } - } - } catch (\Exception $e) { - $this->api->resetCircuitBreaker(); - } - return false; - } - - /** - * Ensure all standard mokocli labels exist on a target repository. - * - * Fetches existing labels first (GET) and only POSTs the ones that are - * missing. This avoids the 422 "already exists" responses that would - * otherwise accumulate and trip the circuit breaker on subsequent runs. - */ - private function ensureRepoLabels(string $org, string $repo): void - { - /** @var list name, hex colour (no #), description */ - $labels = [ - // Project Type - ['joomla', '7F52FF', 'Joomla extension or component'], - ['dolibarr', 'FF6B6B', 'Dolibarr module or extension'], - ['generic', '808080', 'Generic project or library'], - - // Language - ['php', '4F5D95', 'PHP code changes'], - ['javascript', 'F7DF1E', 'JavaScript code changes'], - ['typescript', '3178C6', 'TypeScript code changes'], - ['python', '3776AB', 'Python code changes'], - ['css', '1572B6', 'CSS/styling changes'], - ['html', 'E34F26', 'HTML template changes'], - - // Component - ['documentation', '0075CA', 'Documentation changes'], - ['ci-cd', '000000', 'CI/CD pipeline changes'], - ['docker', '2496ED', 'Docker configuration changes'], - ['tests', '00FF00', 'Test suite changes'], - ['security', 'FF0000', 'Security-related changes'], - ['dependencies', '0366D6', 'Dependency updates'], - ['config', 'F9D0C4', 'Configuration file changes'], - ['build', 'FFA500', 'Build system changes'], - - // Workflow / Process - ['automation', '8B4513', 'Automated processes or scripts'], - ['mokocli', 'B60205', 'mokocli compliance'], - ['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'], - - // Priority - ['priority: critical', 'B60205', 'Critical priority, must be addressed immediately'], - ['priority: high', 'D93F0B', 'High priority'], - ['priority: medium', 'FBCA04', 'Medium priority'], - ['priority: low', '0E8A16', 'Low priority'], - - // Type - ['type: bug', 'D73A4A', "Something isn't working"], - ['type: feature', 'A2EEEF', 'New feature or request'], - ['type: enhancement', '84B6EB', 'Enhancement to existing feature'], - ['type: refactor', 'F9D0C4', 'Code refactoring'], - ['type: chore', 'FEF2C0', 'Maintenance tasks'], - - // Status - ['status: pending', 'FBCA04', 'Pending action or decision'], - ['status: in-progress', '0E8A16', 'Currently being worked on'], - ['status: blocked', 'B60205', 'Blocked by another issue or dependency'], - ['status: on-hold', 'D4C5F9', 'Temporarily on hold'], - ['status: wontfix', 'FFFFFF', 'This will not be worked on'], - - // Size - ['size/xs', 'C5DEF5', 'Extra small change (1-10 lines)'], - ['size/s', '6FD1E2', 'Small change (11-30 lines)'], - ['size/m', 'F9DD72', 'Medium change (31-100 lines)'], - ['size/l', 'FFA07A', 'Large change (101-300 lines)'], - ['size/xl', 'FF6B6B', 'Extra large change (301-1000 lines)'], - ['size/xxl', 'B60205', 'Extremely large change (1000+ lines)'], - - // Health - ['health: excellent', '0E8A16', 'Health score 90-100'], - ['health: good', 'FBCA04', 'Health score 70-89'], - ['health: fair', 'FFA500', 'Health score 50-69'], - ['health: poor', 'FF6B6B', 'Health score below 50'], - - // Sync / Automation (used by bulk_sync, scan_drift, check_repo_health) - ['standards-update', 'B60205', 'mokocli sync update'], - ['standards-drift', 'FBCA04', 'Repository drifted from mokocli'], - ['sync-report', '0075CA', 'Bulk sync run report'], - ['sync-failure', 'D73A4A', 'Bulk sync failure requiring attention'], - ['push-failure', 'D73A4A', 'File push failure requiring attention'], - ['health-check', '0E8A16', 'Repository health check results'], - ['version-drift', 'FFA500', 'Version mismatch detected'], - ['deploy-failure', 'CC0000', 'Automated deploy failure tracking'], - ['template-validation-failure', 'D73A4A', 'Template workflow validation failure'], - ['version', '0E8A16', 'Version bump or release'], - ['type: version', '0E8A16', 'Version-related change'], - ]; - - // 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"); - if (!empty($probe['name'])) { - return; // already provisioned - } - } catch (\Exception $e) { - // Label doesn't exist — proceed with full provisioning - } - - // Fetch existing labels to determine which ones need creating. - $existing = []; - try { - $page = 1; - do { - $page_labels = $this->api->get("/repos/{$org}/{$repo}/labels?per_page=100&page={$page}"); - foreach ($page_labels as $label) { - $existing[strtolower($label['name'])] = true; - } - $page++; - } while (count($page_labels) === 100); - } catch (\Exception $e) { - // Cannot read labels (e.g. no access) — skip provisioning entirely - return; - } - - foreach ($labels as [$name, $color, $description]) { - if (isset($existing[strtolower($name)])) { - continue; // already exists — no POST needed - } - // Reset before each attempt — the circuit breaker checks state at the - // START of each API call, so resetting after a failure is too late. - $this->api->resetCircuitBreaker(); - try { - $this->api->post("/repos/{$org}/{$repo}/labels", [ - 'name' => $name, - 'color' => $color, - 'description' => $description, - ]); - } catch (\Exception $e) { - // Ignore — label already exists or transient failure - } - } - } - - /** - * Ensure standard release tags exist on the repository. - * - * Creates 'development', 'beta', and 'release-candidate' tags pointing - * to the default branch HEAD if they don't already exist. These tags - * are used by the release workflow to track stability channels. - */ - private function ensureReleaseTags(string $org, string $repo): void - { - $requiredTags = ['development', 'beta', 'release-candidate']; - - try { - $existingTags = $this->api->get("/repos/{$org}/{$repo}/tags", ['limit' => 50]); - } catch (\Exception $e) { - return; // Non-critical - } - - $existingNames = array_column($existingTags, 'name'); - - // Get default branch to point new tags at - try { - $repoInfo = $this->api->get("/repos/{$org}/{$repo}"); - $defaultBranch = $repoInfo['default_branch'] ?? 'main'; - } catch (\Exception $e) { - $defaultBranch = 'main'; - } - - foreach ($requiredTags as $tagName) { - if (in_array($tagName, $existingNames, true)) { - continue; - } - - try { - $this->api->post("/repos/{$org}/{$repo}/tags", [ - 'tag_name' => $tagName, - 'target' => $defaultBranch, - 'message' => "Release channel: {$tagName}", - ]); - $this->log(" 🏷️ Created tag '{$tagName}' on {$repo}", 'INFO'); - } catch (\Exception $e) { - // Non-critical — tag may already exist as a release tag - } - } - } - - /** - * Merge main into all open PR branches (except the sync branch itself). - * - * This ensures feature/development branches stay up to date with the - * latest synced standards after a bulk sync run. - */ - private function updateOpenBranches(string $org, string $repo): void - { - $syncBranchPrefix = 'chore/sync-mokocli-'; - - try { - $defaultBranch = 'main'; - try { - $repoInfo = $this->api->get("/repos/{$org}/{$repo}"); - $defaultBranch = $repoInfo['default_branch'] ?? 'main'; - } catch (\Exception $e) { -/* fallback to main */ - } - - $prs = $this->api->get("/repos/{$org}/{$repo}/pulls", [ - 'state' => 'open', - 'per_page' => 30, - 'sort' => 'updated', - 'direction' => 'desc', - ]); - - foreach ($prs as $pr) { - $branch = $pr['head']['ref'] ?? ''; - $prNum = $pr['number'] ?? 0; - - // Skip sync branches — they were just reset from main - if (str_starts_with($branch, $syncBranchPrefix)) { - continue; - } - - try { - $this->api->post("/repos/{$org}/{$repo}/merges", [ - 'base' => $branch, - 'head' => $defaultBranch, - 'commit_message' => "chore: merge {$defaultBranch} into {$branch} (mokocli sync)", - ]); - $this->log(" 🔀 Merged {$defaultBranch} → {$branch} (PR #{$prNum})", 'INFO'); - } catch (\Exception $e) { - $msg = $e->getMessage(); - if (str_contains($msg, '409') || str_contains($msg, 'Merge conflict')) { - $this->log(" ⚠️ Merge conflict: {$defaultBranch} → {$branch} (PR #{$prNum})", 'WARN'); - } elseif (str_contains($msg, '204') || str_contains($msg, 'nothing to merge')) { - $this->log(" ✓ Already up to date: {$branch}", 'DEBUG'); - } else { - $this->log(" ⚠️ Could not merge into {$branch}: " . $msg, 'WARN'); - } - } - } - } catch (\Exception $e) { - $this->log(" ⚠️ Could not update branches in {$repo}: " . $e->getMessage(), 'WARN'); - } - } - - /** - * Records which sync run touched the repo, the PR number, and the - * mokocli version that was applied — giving each repo a clear audit - * trail of what was changed and why. - */ - /** - * Resolve label names to their integer IDs for the Gitea API. - * Creates missing labels automatically. - * - * @param string $org Organization name - * @param string $repo Repository name - * @param string[] $labelNames Label names to resolve - * @return int[] Array of label IDs - */ - private function resolveLabelIds(string $org, string $repo, array $labelNames): array - { - try { - $existing = $this->api->get("/repos/{$org}/{$repo}/labels", ['limit' => 50]); - } catch (\Exception $e) { - return []; - } - - $nameToId = []; - foreach ($existing as $label) { - $nameToId[$label['name']] = (int) $label['id']; - } - - $ids = []; - foreach ($labelNames as $name) { - if (isset($nameToId[$name])) { - $ids[] = $nameToId[$name]; - } - // Skip labels that don't exist (ensureRepoLabels creates them separately) - } - - return $ids; - } - - private function createTargetRepoIssue(string $org, string $repo, int $prNumber): ?int - { - $now = gmdate('Y-m-d H:i:s') . ' UTC'; - $version = self::VERSION; - $minor = self::VERSION_MINOR; - $force = isset($this->options['force']) ? ' *(--force)*' : ''; - $prLink = $this->adapter->getPullRequestWebUrl($org, $repo, $prNumber); - $source = $this->adapter->getRepoWebUrl($org, 'mokocli'); - $branchName = 'chore/sync-mokocli-v' . $minor; - $branchLink = $this->adapter->getBranchWebUrl($org, $repo, $branchName); - - $title = "chore: mokocli v{$minor} sync tracking"; - - $body = <<resolveLabelIds($org, $repo, $labelNames); - - try { - // Check for an existing tracking issue (any state so we can reopen closed ones) - $existing = $this->api->get("/repos/{$org}/{$repo}/issues", [ - 'labels' => 'standards-update', - 'state' => 'all', - 'per_page' => 1, - 'sort' => 'created', - 'direction' => 'desc', - ]); - $existing = array_values($existing); - - if (!empty($existing) && isset($existing[0]['number'])) { - $num = $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; - if (($existing[0]['state'] ?? 'open') === 'closed') { - $patch['state'] = 'open'; - } - $this->api->patch("/repos/{$org}/{$repo}/issues/{$num}", $patch); - // Re-apply labels in case any were removed - try { - $this->api->post("/repos/{$org}/{$repo}/issues/{$num}/labels", ['labels' => $labels]); - } catch (\Exception $le) { -/* non-fatal */ - } - $this->log(" 📋 Tracking issue #{$num} updated in {$repo}", 'INFO'); - } else { - $issue = $this->api->post("/repos/{$org}/{$repo}/issues", [ - 'title' => $title, - 'body' => $body, - 'labels' => $labels, - 'assignees' => ['jmiller'], - ]); - $num = $issue['number'] ?? '?'; - $this->log(" 📋 Tracking issue #{$num} created in {$repo}", 'INFO'); - } - - // Link the tracking issue to the sync PR so it appears in the PR's Development sidebar - if (is_int($num)) { - try { - $pr = $this->api->get("/repos/{$org}/{$repo}/pulls/{$prNumber}"); - $currentBody = $pr['body'] ?? ''; - $closeRef = "Linked to #{$num}"; - if (!str_contains($currentBody, $closeRef)) { - $this->api->patch("/repos/{$org}/{$repo}/pulls/{$prNumber}", [ - 'body' => $closeRef . "\n\n" . $currentBody, - ]); - } - } catch (\Exception $le) { -/* non-fatal */ - } - } - - return is_int($num) ? $num : null; - } catch (\Exception $e) { - $this->log(" ⚠️ Could not create/update tracking issue in {$repo}: " . $e->getMessage(), 'WARN'); - return null; - } - } - - /** - * Create a tracking issue in mokocli for this sync run. - */ - private function createSyncIssue(string $org, array $results): void - { - if ($this->dryRun) { - return; - } - - $now = gmdate('Y-m-d H:i:s') . ' UTC'; - $total = $results['total']; - $success = $results['success']; - $skipped = $results['skipped']; - $failed = $results['failed']; - $duration = round($results['duration'] ?? 0, 1); - $force = isset($this->options['force']) ? ' *(--force)*' : ''; - $prs = $results['prs'] ?? []; - $issues = $results['issues'] ?? []; - - // Stable title — no timestamp so repeated runs update a single issue - $title = "sync: mokocli v" . self::VERSION_MINOR . " bulk sync report"; - - $protection = $results['protection'] ?? []; - $hasProtect = !empty($protection); - $healthData = $results['health'] ?? []; - $hasHealth = !empty($healthData); - - // Build repo table - $rows = []; - foreach ($results['repositories'] as $repo => $status) { - $icon = match (true) { - $status === 'success' => '✅', - str_starts_with($status, 'skipped') => '⊘', - str_starts_with($status, 'failed') => '❌', - default => '⚠️', - }; - $prLink = isset($prs[$repo]) - ? "[#{$prs[$repo]}](" . $this->adapter->getPullRequestWebUrl($org, $repo, $prs[$repo]) . ")" - : '—'; - $issueLink = isset($issues[$repo]) - ? "[#{$issues[$repo]}](" . $this->adapter->getIssueWebUrl($org, $repo, $issues[$repo]) . ")" - : '—'; - $row = "| `{$repo}` | {$icon} {$status} | {$prLink} | {$issueLink} |"; - if ($hasHealth) { - $h = $healthData[$repo] ?? null; - if ($h) { - $healthIcon = match ($h['level']) { - 'excellent' => '🟢', - 'good' => '🟡', - 'fair' => '🟠', - default => '🔴', - }; - $row .= " {$healthIcon} {$h['score']}/{$h['max']} |"; - } else { - $row .= ' — |'; - } - } - $rows[] = $row; - } - $table = implode("\n", $rows); - - $header = $hasHealth - ? "| Repository | Status | PR | Issue | Health |" - : "| Repository | Status | PR | Issue |"; - $separator = $hasHealth - ? "|---|---|---|---|---|" - : "|---|---|---|---|"; - - $body = <<api->get("/repos/{$org}/mokocli/issues", [ - 'labels' => 'sync-report', - 'state' => 'all', - 'per_page' => 1, - 'sort' => 'created', - 'direction' => 'desc', - ]); - - $labelNames = ['sync-report', 'mokocli', 'type: chore', 'automation']; - $labels = $this->resolveLabelIds($org, 'mokocli', $labelNames); - $existing = array_values($existing); - - if (!empty($existing) && isset($existing[0]['number'])) { - $issueNumber = $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; - if (($existing[0]['state'] ?? 'open') === 'closed') { - $patch['state'] = 'open'; - } - $this->api->patch("/repos/{$org}/mokocli/issues/{$issueNumber}", $patch); - try { - $this->api->post("/repos/{$org}/mokocli/issues/{$issueNumber}/labels", ['labels' => $labels]); - } catch (\Exception $le) { -/* non-fatal */ - } - $this->log("📋 Sync report issue updated: {$org}/mokocli#{$issueNumber}", 'INFO'); - } else { - $issue = $this->api->post("/repos/{$org}/mokocli/issues", [ - 'title' => $title, - 'body' => $body, - 'labels' => $labels, - 'assignees' => ['jmiller'], - ]); - $issueNumber = $issue['number'] ?? '?'; - $this->log("📋 Sync report issue created: {$org}/mokocli#{$issueNumber}", 'INFO'); - } - } catch (\Exception $e) { - $this->log("⚠️ Failed to create/update sync report issue: " . $e->getMessage(), 'WARN'); - } - } - - /** - * Create or update a failure issue in mokocli when repos fail to sync. - * Uses the 'sync-failure' label so it is distinct from the run-report issue. - * Reopens a closed issue rather than creating a duplicate. - */ - private function createFailureIssue(string $org, array $results): void - { - if ($this->dryRun) { - return; - } - - $now = gmdate('Y-m-d H:i:s') . ' UTC'; - $failed = $results['failed']; - $version = self::VERSION; - - $failedRepos = array_keys(array_filter( - $results['repositories'] ?? [], - fn($s) => $s === 'failed' - )); - - $repoList = implode("\n", array_map(fn($r) => "- `{$r}`", $failedRepos)); - - $title = "fix: bulk_sync failed for {$failed} repo(s) — action required"; - - $body = <<` to see the specific error. - 2. Fix the underlying issue (API token, rate limit, branch protection, etc.). - 3. Re-run: `php automation/bulk_sync.php --org={$org} --repos= --force --yes` - 4. Close this issue once all repos are synced successfully. - - --- - *Auto-created by `bulk_sync.php` — close once resolved.* - MD; - - $body = preg_replace('/^ /m', '', $body); - - try { - $existing = $this->api->get("/repos/{$org}/mokocli/issues", [ - 'labels' => 'sync-failure', - 'state' => 'all', - 'per_page' => 1, - 'sort' => 'created', - 'direction' => 'desc', - ]); - $existing = array_values($existing); - - if (!empty($existing) && isset($existing[0]['number'])) { - $num = $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; - if (($existing[0]['state'] ?? 'open') === 'closed') { - $patch['state'] = 'open'; - } - $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", [ - 'title' => $title, - 'body' => $body, - 'labels' => $this->resolveLabelIds($org, 'mokocli', ['sync-failure']), - 'assignees' => ['jmiller'], - ]); - $num = $issue['number'] ?? '?'; - $this->log("🚨 Failure issue created: {$org}/mokocli#{$num}", 'WARN'); - } - } catch (\Exception $e) { - $this->log("⚠️ Could not create/update failure issue: " . $e->getMessage(), 'WARN'); - } - } -} - -// Execute if run directly -if (php_sapi_name() === 'cli' && isset($argv[0]) && realpath($argv[0]) === __FILE__) { - $app = new BulkSync(); - exit($app->execute()); -} diff --git a/automation/bulk_workflow_trigger.sh b/automation/bulk_workflow_trigger.sh deleted file mode 100644 index c63032d..0000000 --- a/automation/bulk_workflow_trigger.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env bash -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# BRIEF: Trigger a workflow across all client-waas repos in a Gitea org - -set -euo pipefail - -# --------------------------------------------------------------------------- -# Usage -# --------------------------------------------------------------------------- -usage() { - cat </dev/null -} - -# Get repos -REPOS="" -for page in 1 2 3; do - BATCH=$(api GET "/orgs/$ORG/repos?limit=50&page=$page" | python3 -c " -import sys,json -for r in json.load(sys.stdin): - if not r.get(empty) and not r.get(archived): - print(r[name]) -" 2>/dev/null) - [[ -z "$BATCH" ]] && break - REPOS="$REPOS $BATCH" -done - -# Filter if specified -if [[ -n "$FILTER_REPOS" ]]; then - FILTERED="" - IFS=, read -ra FILTER_ARR <<< "$FILTER_REPOS" - for repo in $REPOS; do - for f in "${FILTER_ARR[@]}"; do - [[ "$repo" == "$f" ]] && FILTERED="$FILTERED $repo" - done - done - REPOS="$FILTERED" -fi - -TOTAL=$(echo $REPOS | wc -w) -ADDED=0 -DELETED=0 -ERRORS=0 - -echo "Enforcing tags on $TOTAL repos (dry_run=$DRY_RUN)" - -for repo in $REPOS; do - TAGS=$(api GET "/repos/$ORG/$repo/tags?limit=50" | python3 -c "import sys,json; print( .join(t[name] for t in json.load(sys.stdin)))" 2>/dev/null) - MAIN_SHA=$(api GET "/repos/$ORG/$repo/branches/main" | python3 -c "import sys,json; print(json.load(sys.stdin)[commit][id])" 2>/dev/null) - [[ -z "$MAIN_SHA" ]] && continue - - # Add missing standard tags - for st in "${STANDARD_TAGS[@]}"; do - if ! echo " $TAGS " | grep -q " $st "; then - if [[ "$DRY_RUN" == "true" ]]; then - echo " [DRY] ADD $repo: $st" - else - STATUS=$(api POST "/repos/$ORG/$repo/tags" "{\"tag_name\":\"$st\",\"target\":\"$MAIN_SHA\"}" | python3 -c "import sys,json; print(ok)" 2>/dev/null || echo "err") - [[ "$STATUS" == "ok" ]] && ADDED=$((ADDED + 1)) || ERRORS=$((ERRORS + 1)) - fi - fi - done - - # Remove non-standard tags - for t in $TAGS; do - IS_STD=false - for st in "${STANDARD_TAGS[@]}"; do [[ "$t" == "$st" ]] && IS_STD=true; done - # Keep vXX production tags - if [[ "$t" =~ ^v[0-9]{1,3}$ ]]; then IS_STD=true; fi - - if [[ "$IS_STD" == "false" ]]; then - if [[ "$DRY_RUN" == "true" ]]; then - echo " [DRY] DEL $repo: $t" - else - # Delete release first if exists - api DELETE "/repos/$ORG/$repo/releases/tags/$t" > /dev/null 2>&1 || true - api DELETE "/repos/$ORG/$repo/tags/$t" > /dev/null 2>&1 - DELETED=$((DELETED + 1)) - echo " DEL $repo: $t" - fi - fi - done -done - -echo "Done: $ADDED added, $DELETED deleted, $ERRORS errors (dry_run=$DRY_RUN)" diff --git a/automation/enrich_manifest_xml.php b/automation/enrich_manifest_xml.php deleted file mode 100644 index 0f82a7b..0000000 --- a/automation/enrich_manifest_xml.php +++ /dev/null @@ -1,481 +0,0 @@ -#!/usr/bin/env php - - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoPlatform.Automation - * INGROUP: MokoPlatform - * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli - * PATH: /automation/enrich_manifest_xml.php - * BRIEF: Enrich XML manifests with repo-specific build and deploy details - * - * Note: This script uses proc_open for shell commands. All arguments are escaped - * via escapeshellarg(). No user-supplied input reaches the shell unescaped. - */ - -declare(strict_types=1); - -require_once __DIR__ . '/../vendor/autoload.php'; -require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; - -use MokoCli\CliFramework; -use MokoCli\ManifestParser; - -class EnrichManifestXmlCli extends CliFramework -{ - protected function configure(): void - { - $this->setDescription('Enrich XML manifests with repo-specific build and deploy details'); - $this->addArgument('--repo', 'Filter to a single repo name', ''); - $this->addArgument('--skip', 'Comma-separated list of repos to skip', ''); - } - - protected function run(): int - { - $giteaUrl = rtrim(getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech', '/'); - $giteaOrg = getenv('GITEA_ORG') ?: 'MokoConsulting'; - $token = getenv('GA_TOKEN') ?: getenv('GH_TOKEN') ?: ''; - - $repoFilter = $this->getArgument('--repo') ?: null; - $skipStr = $this->getArgument('--skip'); - $skipRepos = $skipStr !== '' ? array_map('trim', explode(',', $skipStr)) : []; - - $parser = new ManifestParser(); - $tmpBase = sys_get_temp_dir() . '/moko-enrich-' . getmypid(); - - echo "=== mokocli XML Manifest Enrichment ===\n"; - echo "Mode: " . ($this->dryRun ? "DRY RUN" : "LIVE") . "\n"; - if (!empty($skipRepos)) { - echo "Skipping: " . implode(', ', $skipRepos) . "\n"; - } - echo "\n"; - - if (empty($token)) { - $this->log('ERROR', 'GA_TOKEN required'); - return 1; - } - - $repos = $this->fetchRepos($giteaUrl, $giteaOrg, $token); - echo "Found " . count($repos) . " repositories\n\n"; - - $stats = ['enriched' => 0, 'skipped' => 0, 'failed' => 0]; - - foreach ($repos as $repo) { - $name = $repo['name']; - if ($repoFilter && $name !== $repoFilter) { - continue; - } - if (in_array($name, $skipRepos, true)) { - echo " {$name} ... SKIP (excluded)\n"; - $stats['skipped']++; - continue; - } - if ($repo['archived'] ?? false) { - $stats['skipped']++; - continue; - } - - $defaultBranch = $repo['default_branch'] ?? 'main'; - $httpsUrl = $repo['clone_url'] ?? "{$giteaUrl}/{$giteaOrg}/{$name}.git"; - $authedUrl = preg_replace('#^https://#', "https://gitea-actions:{$token}@", $httpsUrl); - - echo " {$name} ... "; - - $workDir = "{$tmpBase}/{$name}"; - @mkdir($workDir, 0755, true); - [$ret] = $this->safeExec( - 'git clone --depth 1 --branch ' . escapeshellarg($defaultBranch) - . ' ' . escapeshellarg($authedUrl) . ' ' . escapeshellarg($workDir) - ); - if ($ret !== 0) { - echo "FAIL (clone)\n"; - $stats['failed']++; - continue; - } - - $manifestPath = "{$workDir}/.mokogitea/manifest.xml"; - if (!file_exists($manifestPath) || !str_contains(file_get_contents($manifestPath), 'rmTree($workDir); - continue; - } - - $existingXml = file_get_contents($manifestPath); - $platform = $parser->extractPlatform($existingXml) ?? 'default-repository'; - $enrichment = $this->inspectRepo($workDir, $platform); - - if (!isset($enrichment['build'])) { - $enrichment['build'] = []; - } - $enrichment['build']['language'] = $enrichment['build']['language'] - ?? $repo['language'] - ?? ManifestParser::platformLanguage($platform); - $enrichment['build']['package_type'] = $enrichment['build']['package_type'] ?? ManifestParser::platformPackageType($platform); - - $enrichedXml = $this->enrichManifestXml($existingXml, $enrichment); - $dc = count($enrichment['deploy'] ?? []); - $sc = count($enrichment['scripts'] ?? []); - $details = "deploy={$dc} scripts={$sc}"; - - if ($this->dryRun) { - echo "WOULD ENRICH [{$details}]\n"; - $stats['enriched']++; - $this->rmTree($workDir); - continue; - } - - file_put_contents($manifestPath, $enrichedXml); - $this->gitCmd($workDir, 'config', 'user.name', 'gitea-actions[bot]'); - $this->gitCmd($workDir, 'config', 'user.email', 'gitea-actions[bot]@git.mokoconsulting.tech'); - $this->gitCmd($workDir, 'add', '.mokogitea/manifest.xml'); - - [$cr, $co] = $this->gitCmd($workDir, 'commit', '-m', "chore: enrich manifest.xml with build/deploy/scripts\n\nAuto-detected: {$details}"); - if ($cr !== 0) { - echo "SKIP (no diff)\n"; - $stats['skipped']++; - $this->rmTree($workDir); - continue; - } - - [$pr] = $this->gitCmd($workDir, 'push', 'origin', $defaultBranch); - if ($pr !== 0) { - echo "FAIL (push)\n"; - $stats['failed']++; - } else { - echo "ENRICHED [{$details}]\n"; - $stats['enriched']++; - } - - $this->rmTree($workDir); - } - - @rmdir($tmpBase); - echo "\n=== Summary ===\nEnriched: {$stats['enriched']}\nSkipped: {$stats['skipped']}\nFailed: {$stats['failed']}\n"; - - return 0; - } - - private function inspectRepo(string $workDir, string $platform): array - { - $enrichment = []; - $build = []; - - // Detect entry point - if (is_dir("{$workDir}/src")) { - foreach (glob("{$workDir}/src/*.xml") ?: [] as $xf) { - $c = file_get_contents($xf); - if (str_contains($c, ' $pd, 'version' => $composer['require'][$pd], 'type' => 'platform']; - } - } - if (isset($composer['require']['mokoconsulting-tech/enterprise'])) { - $deps[] = [ - 'name' => 'mokoconsulting-tech/enterprise', - 'version' => $composer['require']['mokoconsulting-tech/enterprise'], - 'type' => 'composer', - ]; - } - if (!empty($deps)) { - $build['dependencies'] = $deps; - } - } - - // Artifact from Makefile - if (file_exists("{$workDir}/Makefile")) { - $mk = file_get_contents("{$workDir}/Makefile"); - if (preg_match('/\bdist\/(\S+\.zip)\b/', $mk, $m)) { - $build['artifact'] = ['format' => 'zip', 'path' => 'dist/', 'filename' => $m[1]]; - } - } - - if (!empty($build)) { - $enrichment['build'] = $build; - } - - // Deploy targets from workflows - $targets = []; - $wfDir = is_dir("{$workDir}/.gitea/workflows") ? "{$workDir}/.gitea/workflows" : "{$workDir}/.github/workflows"; - if (is_dir($wfDir)) { - foreach (['deploy-dev', 'deploy-demo', 'deploy-rs'] as $dn) { - $wf = "{$wfDir}/{$dn}.yml"; - if (!file_exists($wf)) { - continue; - } - $wc = file_get_contents($wf); - $t = ['name' => str_replace('deploy-', '', $dn)]; - if (str_contains($wc, 'sftp') || str_contains($wc, 'SFTP')) { - $t['method'] = 'sftp'; - } elseif (str_contains($wc, 'rsync')) { - $t['method'] = 'rsync'; - } - if (str_contains($wc, 'src/')) { - $t['src_dir'] = 'src/'; - } - if (preg_match('/branches:\s*\n\s*-\s*["\']?([^"\'}\s]+)/', $wc, $m)) { - $t['branch'] = $m[1]; - } - $targets[] = $t; - } - } - if (!empty($targets)) { - $enrichment['deploy'] = $targets; - } - - // Scripts from Makefile + composer - $scripts = []; - if (file_exists("{$workDir}/Makefile")) { - $mk = file_get_contents("{$workDir}/Makefile"); - $known = [ - 'build' => 'build', 'test' => 'test', 'lint' => 'lint', - 'clean' => 'build', 'package' => 'build', - 'validate' => 'validate', 'release' => 'release', - ]; - if (preg_match_all('/^([a-zA-Z_-]+)\s*:/m', $mk, $matches)) { - foreach ($matches[1] as $tgt) { - $tl = strtolower($tgt); - if (isset($known[$tl])) { - $scripts[] = [ - 'name' => $tl, 'phase' => $known[$tl], - 'command' => "make {$tgt}", - 'desc' => ucfirst($tl) . ' via make', - 'runner' => 'make', - ]; - } - } - } - } - if (file_exists("{$workDir}/composer.json")) { - $composer = json_decode(file_get_contents("{$workDir}/composer.json"), true) ?: []; - $km = ['test' => 'test','lint' => 'lint','cs' => 'lint','phpcs' => 'lint','phpstan' => 'lint','validate' => 'validate']; - foreach ($composer['scripts'] ?? [] as $sn => $cmd) { - $sl = strtolower($sn); - foreach ($km as $match => $phase) { - if (str_contains($sl, $match)) { - $exists = false; - foreach ($scripts as $s) { - if ($s['name'] === $sl) { - $exists = true; - break; - } - } - if (!$exists) { - $scripts[] = [ - 'name' => $sn, 'phase' => $phase, - 'command' => "composer run {$sn}", - 'desc' => is_string($cmd) ? $cmd : "Run {$sn}", - 'runner' => 'composer', - ]; - } - break; - } - } - } - } - if (!empty($scripts)) { - $enrichment['scripts'] = $scripts; - } - - return $enrichment; - } - - private function enrichManifestXml(string $xml, array $enrichment): string - { - $dom = new \DOMDocument('1.0', 'UTF-8'); - $dom->preserveWhiteSpace = false; - $dom->formatOutput = true; - if (!$dom->loadXML($xml)) { - return $xml; - } - - $ns = ManifestParser::NAMESPACE_URI; - $root = $dom->documentElement; - - foreach (['build', 'deploy', 'scripts'] as $tag) { - $toRemove = []; - $existing = $root->getElementsByTagNameNS($ns, $tag); - for ($i = 0; $i < $existing->length; $i++) { - $toRemove[] = $existing->item($i); - } - foreach ($toRemove as $node) { - $root->removeChild($node); - } - } - - if (!empty($enrichment['build'])) { - $buildEl = $dom->createElementNS($ns, 'build'); - $b = $enrichment['build']; - foreach (['language', 'runtime'] as $f) { - if (isset($b[$f])) { - $buildEl->appendChild($dom->createElementNS($ns, $f, htmlspecialchars($b[$f], ENT_XML1))); - } - } - if (isset($b['package_type'])) { - $buildEl->appendChild($dom->createElementNS($ns, 'package-type', htmlspecialchars($b['package_type'], ENT_XML1))); - } - if (isset($b['entry_point'])) { - $buildEl->appendChild($dom->createElementNS($ns, 'entry-point', htmlspecialchars($b['entry_point'], ENT_XML1))); - } - if (isset($b['artifact'])) { - $art = $dom->createElementNS($ns, 'artifact'); - foreach (['format','path','filename'] as $af) { - if (isset($b['artifact'][$af])) { - $art->appendChild($dom->createElementNS($ns, $af, htmlspecialchars($b['artifact'][$af], ENT_XML1))); - } - } - $buildEl->appendChild($art); - } - if (isset($b['dependencies'])) { - $deps = $dom->createElementNS($ns, 'dependencies'); - foreach ($b['dependencies'] as $d) { - $req = $dom->createElementNS($ns, 'requires', ''); - $req->setAttribute('name', $d['name']); - if (isset($d['version'])) { - $req->setAttribute('version', $d['version']); - } - if (isset($d['type'])) { - $req->setAttribute('type', $d['type']); - } - $deps->appendChild($req); - } - $buildEl->appendChild($deps); - } - $root->appendChild($buildEl); - } - - if (!empty($enrichment['deploy'])) { - $deploy = $dom->createElementNS($ns, 'deploy'); - foreach ($enrichment['deploy'] as $t) { - $target = $dom->createElementNS($ns, 'target'); - $target->setAttribute('name', $t['name']); - $target->appendChild($dom->createElementNS($ns, 'host', '${{ secrets.' . strtoupper($t['name']) . '_HOST }}')); - $target->appendChild($dom->createElementNS($ns, 'path', '${{ secrets.' . strtoupper($t['name']) . '_PATH }}')); - if (isset($t['method'])) { - $target->appendChild($dom->createElementNS($ns, 'method', $t['method'])); - } - if (isset($t['branch'])) { - $target->appendChild($dom->createElementNS($ns, 'branch', htmlspecialchars($t['branch'], ENT_XML1))); - } - if (isset($t['src_dir'])) { - $target->appendChild($dom->createElementNS($ns, 'src-dir', htmlspecialchars($t['src_dir'], ENT_XML1))); - } - $deploy->appendChild($target); - } - $root->appendChild($deploy); - } - - if (!empty($enrichment['scripts'])) { - $scriptsEl = $dom->createElementNS($ns, 'scripts'); - foreach ($enrichment['scripts'] as $s) { - $script = $dom->createElementNS($ns, 'script'); - $script->setAttribute('name', $s['name']); - if (isset($s['phase'])) { - $script->setAttribute('phase', $s['phase']); - } - $script->appendChild($dom->createElementNS($ns, 'command', htmlspecialchars($s['command'], ENT_XML1))); - if (isset($s['desc'])) { - $script->appendChild($dom->createElementNS($ns, 'description', htmlspecialchars($s['desc'], ENT_XML1))); - } - if (isset($s['runner'])) { - $script->appendChild($dom->createElementNS($ns, 'runner', htmlspecialchars($s['runner'], ENT_XML1))); - } - $scriptsEl->appendChild($script); - } - $root->appendChild($scriptsEl); - } - - return $dom->saveXML(); - } - - /** @return array{int, string} */ - private function safeExec(string $command, string $cwd = '.'): array - { - $proc = proc_open($command, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, $cwd); - if (!is_resource($proc)) { - return [1, "proc_open failed"]; - } - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); - fclose($pipes[1]); - fclose($pipes[2]); - return [proc_close($proc), trim($stdout . "\n" . $stderr)]; - } - - private function rmTree(string $dir): void - { - if (!is_dir($dir)) { - return; - } - $it = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS); - $files = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST); - foreach ($files as $file) { - if ($file->isDir()) { - @rmdir($file->getPathname()); - } else { - @chmod($file->getPathname(), 0777); - @unlink($file->getPathname()); - } - } - @rmdir($dir); - } - - /** @return array{int, string} */ - private function gitCmd(string $workDir, string ...$args): array - { - $cmd = 'git'; - foreach ($args as $a) { - $cmd .= ' ' . escapeshellarg($a); - } - return $this->safeExec($cmd, $workDir); - } - - private function fetchRepos(string $url, string $org, string $token): array - { - $repos = []; - $page = 1; - do { - $ch = curl_init("{$url}/api/v1/orgs/{$org}/repos?page={$page}&limit=50"); - curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: token {$token}"], CURLOPT_TIMEOUT => 30]); - $body = curl_exec($ch); - $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - if ($code !== 200) { - break; - } - $batch = json_decode($body, true); - if (empty($batch)) { - break; - } - $repos = array_merge($repos, $batch); - $page++; - } while (count($batch) >= 50); - return $repos; - } -} - -$app = new EnrichManifestXmlCli(); -exit($app->execute()); diff --git a/automation/enrich_mokostandards_xml.php b/automation/enrich_mokostandards_xml.php deleted file mode 100644 index 686e6b3..0000000 --- a/automation/enrich_mokostandards_xml.php +++ /dev/null @@ -1,484 +0,0 @@ -#!/usr/bin/env php - - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoPlatform.Automation - * INGROUP: MokoPlatform - * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli - * PATH: /automation/enrich_mokostandards_xml.php - * BRIEF: Enrich XML manifests with repo-specific build and deploy details - * - * Note: This script uses proc_open for shell commands. All arguments are escaped - * via escapeshellarg(). No user-supplied input reaches the shell unescaped. - */ - -declare(strict_types=1); - -require_once __DIR__ . '/../vendor/autoload.php'; -require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; - -use MokoCli\CliFramework; -use MokoCli\ManifestParser; - -class EnrichMokostandardsXmlCli extends CliFramework -{ - protected function configure(): void - { - $this->setDescription('Enrich XML manifests with repo-specific build and deploy details'); - $this->addArgument('--repo', 'Filter to a single repo name', ''); - $this->addArgument('--skip', 'Comma-separated list of repos to skip', ''); - } - - protected function run(): int - { - $giteaUrl = rtrim(getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech', '/'); - $giteaOrg = getenv('GITEA_ORG') ?: 'MokoConsulting'; - $token = getenv('GA_TOKEN') ?: getenv('GH_TOKEN') ?: ''; - - $repoFilter = $this->getArgument('--repo') ?: null; - $skipStr = $this->getArgument('--skip'); - $skipRepos = $skipStr !== '' ? array_map('trim', explode(',', $skipStr)) : []; - - $parser = new ManifestParser(); - $tmpBase = sys_get_temp_dir() . '/moko-enrich-' . getmypid(); - - echo "=== mokocli XML Manifest Enrichment ===\n"; - echo "Mode: " . ($this->dryRun ? "DRY RUN" : "LIVE") . "\n"; - if (!empty($skipRepos)) { - echo "Skipping: " . implode(', ', $skipRepos) . "\n"; - } - echo "\n"; - - if (empty($token)) { - $this->log('ERROR', 'GA_TOKEN required'); - return 1; - } - - $repos = $this->fetchRepos($giteaUrl, $giteaOrg, $token); - echo "Found " . count($repos) . " repositories\n\n"; - - $stats = ['enriched' => 0, 'skipped' => 0, 'failed' => 0]; - - foreach ($repos as $repo) { - $name = $repo['name']; - if ($repoFilter && $name !== $repoFilter) { - continue; - } - if (in_array($name, $skipRepos, true)) { - echo " {$name} ... SKIP (excluded)\n"; - $stats['skipped']++; - continue; - } - if ($repo['archived'] ?? false) { - $stats['skipped']++; - continue; - } - - $defaultBranch = $repo['default_branch'] ?? 'main'; - $httpsUrl = $repo['clone_url'] ?? "{$giteaUrl}/{$giteaOrg}/{$name}.git"; - $authedUrl = preg_replace('#^https://#', "https://gitea-actions:{$token}@", $httpsUrl); - - echo " {$name} ... "; - - $workDir = "{$tmpBase}/{$name}"; - @mkdir($workDir, 0755, true); - [$ret] = $this->safeExec( - 'git clone --depth 1 --branch ' . escapeshellarg($defaultBranch) - . ' ' . escapeshellarg($authedUrl) . ' ' . escapeshellarg($workDir) - ); - if ($ret !== 0) { - echo "FAIL (clone)\n"; - $stats['failed']++; - continue; - } - - $manifestPath = "{$workDir}/.mokogitea/manifest.xml"; - if (!file_exists($manifestPath) || !str_contains(file_get_contents($manifestPath), 'rmTree($workDir); - continue; - } - - $existingXml = file_get_contents($manifestPath); - $platform = $parser->extractPlatform($existingXml) ?? 'default-repository'; - $enrichment = $this->inspectRepo($workDir, $platform); - - if (!isset($enrichment['build'])) { - $enrichment['build'] = []; - } - $enrichment['build']['language'] = $enrichment['build']['language'] - ?? $repo['language'] - ?? ManifestParser::platformLanguage($platform); - $enrichment['build']['package_type'] = $enrichment['build']['package_type'] ?? ManifestParser::platformPackageType($platform); - - $enrichedXml = $this->enrichManifestXml($existingXml, $enrichment); - $dc = count($enrichment['deploy'] ?? []); - $sc = count($enrichment['scripts'] ?? []); - $details = "deploy={$dc} scripts={$sc}"; - - if ($this->dryRun) { - echo "WOULD ENRICH [{$details}]\n"; - $stats['enriched']++; - $this->rmTree($workDir); - continue; - } - - file_put_contents($manifestPath, $enrichedXml); - $this->gitCmd($workDir, 'config', 'user.name', 'gitea-actions[bot]'); - $this->gitCmd($workDir, 'config', 'user.email', 'gitea-actions[bot]@git.mokoconsulting.tech'); - $this->gitCmd($workDir, 'add', '.mokogitea/manifest.xml'); - - $commitMsg = "chore: enrich .mokostandards" - . " with build/deploy/scripts\n\n" - . "Auto-detected: {$details}"; - [$cr, $co] = $this->gitCmd( - $workDir, - 'commit', - '-m', - $commitMsg - ); - if ($cr !== 0) { - echo "SKIP (no diff)\n"; - $stats['skipped']++; - $this->rmTree($workDir); - continue; - } - - [$pr] = $this->gitCmd($workDir, 'push', 'origin', $defaultBranch); - if ($pr !== 0) { - echo "FAIL (push)\n"; - $stats['failed']++; - } else { - echo "ENRICHED [{$details}]\n"; - $stats['enriched']++; - } - - $this->rmTree($workDir); - } - - @rmdir($tmpBase); - echo "\n=== Summary ===\nEnriched: {$stats['enriched']}\nSkipped: {$stats['skipped']}\nFailed: {$stats['failed']}\n"; - - return 0; - } - - private function inspectRepo(string $workDir, string $platform): array - { - $enrichment = []; - $build = []; - - if (is_dir("{$workDir}/src")) { - foreach (glob("{$workDir}/src/*.xml") ?: [] as $xf) { - $c = file_get_contents($xf); - if (str_contains($c, ' $pd, 'version' => $composer['require'][$pd], 'type' => 'platform']; - } - } - if (isset($composer['require']['mokoconsulting-tech/enterprise'])) { - $deps[] = [ - 'name' => 'mokoconsulting-tech/enterprise', - 'version' => $composer['require']['mokoconsulting-tech/enterprise'], - 'type' => 'composer', - ]; - } - if (!empty($deps)) { - $build['dependencies'] = $deps; - } - } - - if (file_exists("{$workDir}/Makefile")) { - $mk = file_get_contents("{$workDir}/Makefile"); - if (preg_match('/\bdist\/(\S+\.zip)\b/', $mk, $m)) { - $build['artifact'] = ['format' => 'zip', 'path' => 'dist/', 'filename' => $m[1]]; - } - } - - if (!empty($build)) { - $enrichment['build'] = $build; - } - - $targets = []; - $wfDir = is_dir("{$workDir}/.gitea/workflows") ? "{$workDir}/.gitea/workflows" : "{$workDir}/.github/workflows"; - if (is_dir($wfDir)) { - foreach (['deploy-dev', 'deploy-demo', 'deploy-rs'] as $dn) { - $wf = "{$wfDir}/{$dn}.yml"; - if (!file_exists($wf)) { - continue; - } - $wc = file_get_contents($wf); - $t = ['name' => str_replace('deploy-', '', $dn)]; - if (str_contains($wc, 'sftp') || str_contains($wc, 'SFTP')) { - $t['method'] = 'sftp'; - } elseif (str_contains($wc, 'rsync')) { - $t['method'] = 'rsync'; - } - if (str_contains($wc, 'src/')) { - $t['src_dir'] = 'src/'; - } - if (preg_match('/branches:\s*\n\s*-\s*["\']?([^"\'}\s]+)/', $wc, $m)) { - $t['branch'] = $m[1]; - } - $targets[] = $t; - } - } - if (!empty($targets)) { - $enrichment['deploy'] = $targets; - } - - $scripts = []; - if (file_exists("{$workDir}/Makefile")) { - $mk = file_get_contents("{$workDir}/Makefile"); - $known = [ - 'build' => 'build', 'test' => 'test', 'lint' => 'lint', - 'clean' => 'build', 'package' => 'build', - 'validate' => 'validate', 'release' => 'release', - ]; - if (preg_match_all('/^([a-zA-Z_-]+)\s*:/m', $mk, $matches)) { - foreach ($matches[1] as $tgt) { - $tl = strtolower($tgt); - if (isset($known[$tl])) { - $scripts[] = [ - 'name' => $tl, 'phase' => $known[$tl], - 'command' => "make {$tgt}", - 'desc' => ucfirst($tl) . ' via make', - 'runner' => 'make', - ]; - } - } - } - } - if (file_exists("{$workDir}/composer.json")) { - $composer = json_decode(file_get_contents("{$workDir}/composer.json"), true) ?: []; - $km = ['test' => 'test','lint' => 'lint','cs' => 'lint','phpcs' => 'lint','phpstan' => 'lint','validate' => 'validate']; - foreach ($composer['scripts'] ?? [] as $sn => $cmd) { - $sl = strtolower($sn); - foreach ($km as $match => $phase) { - if (str_contains($sl, $match)) { - $exists = false; - foreach ($scripts as $s) { - if ($s['name'] === $sl) { - $exists = true; - break; - } - } - if (!$exists) { - $scripts[] = [ - 'name' => $sn, 'phase' => $phase, - 'command' => "composer run {$sn}", - 'desc' => is_string($cmd) ? $cmd : "Run {$sn}", - 'runner' => 'composer', - ]; - } - break; - } - } - } - } - if (!empty($scripts)) { - $enrichment['scripts'] = $scripts; - } - - return $enrichment; - } - - private function enrichManifestXml(string $xml, array $enrichment): string - { - $dom = new \DOMDocument('1.0', 'UTF-8'); - $dom->preserveWhiteSpace = false; - $dom->formatOutput = true; - if (!$dom->loadXML($xml)) { - return $xml; - } - - $ns = ManifestParser::NAMESPACE_URI; - $root = $dom->documentElement; - - foreach (['build', 'deploy', 'scripts'] as $tag) { - $toRemove = []; - $existing = $root->getElementsByTagNameNS($ns, $tag); - for ($i = 0; $i < $existing->length; $i++) { - $toRemove[] = $existing->item($i); - } - foreach ($toRemove as $node) { - $root->removeChild($node); - } - } - - if (!empty($enrichment['build'])) { - $buildEl = $dom->createElementNS($ns, 'build'); - $b = $enrichment['build']; - foreach (['language', 'runtime'] as $f) { - if (isset($b[$f])) { - $buildEl->appendChild($dom->createElementNS($ns, $f, htmlspecialchars($b[$f], ENT_XML1))); - } - } - if (isset($b['package_type'])) { - $buildEl->appendChild($dom->createElementNS($ns, 'package-type', htmlspecialchars($b['package_type'], ENT_XML1))); - } - if (isset($b['entry_point'])) { - $buildEl->appendChild($dom->createElementNS($ns, 'entry-point', htmlspecialchars($b['entry_point'], ENT_XML1))); - } - if (isset($b['artifact'])) { - $art = $dom->createElementNS($ns, 'artifact'); - foreach (['format','path','filename'] as $af) { - if (isset($b['artifact'][$af])) { - $art->appendChild($dom->createElementNS($ns, $af, htmlspecialchars($b['artifact'][$af], ENT_XML1))); - } - } - $buildEl->appendChild($art); - } - if (isset($b['dependencies'])) { - $deps = $dom->createElementNS($ns, 'dependencies'); - foreach ($b['dependencies'] as $d) { - $req = $dom->createElementNS($ns, 'requires', ''); - $req->setAttribute('name', $d['name']); - if (isset($d['version'])) { - $req->setAttribute('version', $d['version']); - } - if (isset($d['type'])) { - $req->setAttribute('type', $d['type']); - } - $deps->appendChild($req); - } - $buildEl->appendChild($deps); - } - $root->appendChild($buildEl); - } - - if (!empty($enrichment['deploy'])) { - $deploy = $dom->createElementNS($ns, 'deploy'); - foreach ($enrichment['deploy'] as $t) { - $target = $dom->createElementNS($ns, 'target'); - $target->setAttribute('name', $t['name']); - $target->appendChild($dom->createElementNS($ns, 'host', '${{ secrets.' . strtoupper($t['name']) . '_HOST }}')); - $target->appendChild($dom->createElementNS($ns, 'path', '${{ secrets.' . strtoupper($t['name']) . '_PATH }}')); - if (isset($t['method'])) { - $target->appendChild($dom->createElementNS($ns, 'method', $t['method'])); - } - if (isset($t['branch'])) { - $target->appendChild($dom->createElementNS($ns, 'branch', htmlspecialchars($t['branch'], ENT_XML1))); - } - if (isset($t['src_dir'])) { - $target->appendChild($dom->createElementNS($ns, 'src-dir', htmlspecialchars($t['src_dir'], ENT_XML1))); - } - $deploy->appendChild($target); - } - $root->appendChild($deploy); - } - - if (!empty($enrichment['scripts'])) { - $scriptsEl = $dom->createElementNS($ns, 'scripts'); - foreach ($enrichment['scripts'] as $s) { - $script = $dom->createElementNS($ns, 'script'); - $script->setAttribute('name', $s['name']); - if (isset($s['phase'])) { - $script->setAttribute('phase', $s['phase']); - } - $script->appendChild($dom->createElementNS($ns, 'command', htmlspecialchars($s['command'], ENT_XML1))); - if (isset($s['desc'])) { - $script->appendChild($dom->createElementNS($ns, 'description', htmlspecialchars($s['desc'], ENT_XML1))); - } - if (isset($s['runner'])) { - $script->appendChild($dom->createElementNS($ns, 'runner', htmlspecialchars($s['runner'], ENT_XML1))); - } - $scriptsEl->appendChild($script); - } - $root->appendChild($scriptsEl); - } - - return $dom->saveXML(); - } - - /** @return array{int, string} */ - private function safeExec(string $command, string $cwd = '.'): array - { - $proc = proc_open($command, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, $cwd); - if (!is_resource($proc)) { - return [1, "proc_open failed"]; - } - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); - fclose($pipes[1]); - fclose($pipes[2]); - return [proc_close($proc), trim($stdout . "\n" . $stderr)]; - } - - private function rmTree(string $dir): void - { - if (!is_dir($dir)) { - return; - } - $it = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS); - $files = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST); - foreach ($files as $file) { - if ($file->isDir()) { - @rmdir($file->getPathname()); - } else { - @chmod($file->getPathname(), 0777); - @unlink($file->getPathname()); - } - } - @rmdir($dir); - } - - /** @return array{int, string} */ - private function gitCmd(string $workDir, string ...$args): array - { - $cmd = 'git'; - foreach ($args as $a) { - $cmd .= ' ' . escapeshellarg($a); - } - return $this->safeExec($cmd, $workDir); - } - - private function fetchRepos(string $url, string $org, string $token): array - { - $repos = []; - $page = 1; - do { - $ch = curl_init("{$url}/api/v1/orgs/{$org}/repos?page={$page}&limit=50"); - curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: token {$token}"], CURLOPT_TIMEOUT => 30]); - $body = curl_exec($ch); - $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - if ($code !== 200) { - break; - } - $batch = json_decode($body, true); - if (empty($batch)) { - break; - } - $repos = array_merge($repos, $batch); - $page++; - } while (count($batch) >= 50); - return $repos; - } -} - -$app = new EnrichMokostandardsXmlCli(); -exit($app->execute()); diff --git a/automation/file-distributor-config-example.json b/automation/file-distributor-config-example.json deleted file mode 100644 index 3df259a..0000000 --- a/automation/file-distributor-config-example.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "description": "Example configuration file for file-distributor.ps1 v02.00.00", - "SourceFile": "C:\\path\\to\\your\\source\\file.txt", - "RootDirectory": "C:\\path\\to\\root\\directory", - "Depth": 1, - "DryRun": true, - "Overwrite": false, - "ConfirmEach": false, - "IncludeHidden": true, - "LogDirectory": "C:\\path\\to\\logs" -} diff --git a/automation/index.md b/automation/index.md deleted file mode 100644 index f064f33..0000000 --- a/automation/index.md +++ /dev/null @@ -1,32 +0,0 @@ - - -# Docs Index: /api/automation - -## Purpose - -This index provides navigation to documentation within this folder. - -## Documents - -- [README-file-distributor](./README-file-distributor.md) -- [README](./README.md) - -## Metadata - -- **Document Type:** index -- **Auto-generated:** This file is automatically generated by rebuild_indexes.py - -## Revision History - -| Date | Author | Change | Notes | -| ---------- | ------------------ | ----------------- | ------------------------------------------ | -| Auto | rebuild_indexes.py | Automated update | Generated by documentation index automation | diff --git a/automation/migrate_to_gitea.php b/automation/migrate_to_gitea.php deleted file mode 100644 index 323b75b..0000000 --- a/automation/migrate_to_gitea.php +++ /dev/null @@ -1,300 +0,0 @@ -#!/usr/bin/env php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoPlatform.Automation - * INGROUP: MokoPlatform - * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli - * 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 - * php automation/migrate_to_gitea.php --exclude mokocli --skip-archived - * php automation/migrate_to_gitea.php --resume - */ - -declare(strict_types=1); - -require_once __DIR__ . '/../vendor/autoload.php'; - -use MokoCli\CheckpointManager; -use MokoCli\CliFramework; -use MokoCli\Config; -use MokoCli\PlatformAdapterFactory; -use MokoCli\GitHubAdapter; -use MokoCli\MokoGiteaAdapter; - -/** - * Gitea Migration Script - * - * Migrates repositories from GitHub to a self-hosted Gitea instance. - * Uses Gitea's built-in migration endpoint for git history, tags, releases, - * issues, and labels. Post-migration applies branch protection, topics, - * and workflow conversion. - */ -class MigrateToGitea extends CliFramework -{ - private ?GitHubAdapter $github = null; - private ?MokoGiteaAdapter $gitea = null; - private ?CheckpointManager $checkpoints = null; - - protected function configure(): void - { - $this->setDescription('Migrate repositories from GitHub to Gitea'); - $this->addArgument('--dry-run', 'Show what would be migrated without making changes', false); - $this->addArgument('--repos', 'Specific repositories to migrate (space-separated)', ''); - $this->addArgument('--exclude', 'Repositories to exclude (space-separated)', ''); - $this->addArgument('--skip-archived', 'Skip archived repositories', false); - $this->addArgument('--resume', 'Resume from last checkpoint', false); - $this->addArgument('--github-token', 'GitHub token override', ''); - $this->addArgument('--gitea-token', 'Gitea token override', ''); - } - - protected function run(): int - { - $dryRun = (bool) $this->getArgument('--dry-run'); - $specificRepos = array_filter(explode(' ', (string) $this->getArgument('--repos'))); - $excludeRepos = array_filter(explode(' ', (string) $this->getArgument('--exclude'))); - $skipArchived = (bool) $this->getArgument('--skip-archived'); - $resume = (bool) $this->getArgument('--resume'); - - $config = Config::load(); - - // Override tokens if provided - $ghToken = (string) $this->getArgument('--github-token'); - $giteaToken = (string) $this->getArgument('--gitea-token'); - if ($ghToken !== '') { - $config->set('github.token', $ghToken); - } - if ($giteaToken !== '') { - $config->set('gitea.token', $giteaToken); - } - - // Create both adapters - try { - $adapters = PlatformAdapterFactory::createBoth($config); - $this->github = $adapters['github']; - $this->gitea = $adapters['gitea']; - } catch (\RuntimeException $e) { - $this->log('ERROR', $e->getMessage()); - return 1; - } - - $this->checkpoints = new CheckpointManager('.checkpoints/migration'); - $org = $config->getString('github.organization', 'MokoConsulting'); - $giteaOrg = $config->getString('gitea.organization', 'MokoConsulting'); - - echo "=== Gitea Migration Tool ===\n"; - echo "Source: GitHub ({$org})\n"; - echo "Destination: Gitea ({$giteaOrg}) at " . $config->getString('gitea.url') . "\n"; - echo "Mode: " . ($dryRun ? 'DRY RUN' : 'LIVE') . "\n\n"; - - // ── Phase 1: Discovery ────────────────────────────────────────── - $this->section('Phase 1: Discovery'); - - $ghRepos = $this->github->listOrgRepos($org, $skipArchived); - echo "Found " . count($ghRepos) . " repositories on GitHub\n"; - - // Filter repos - if (!empty($specificRepos)) { - $ghRepos = array_filter($ghRepos, fn($r) => in_array($r['name'], $specificRepos, true)); - } - if (!empty($excludeRepos)) { - $ghRepos = array_filter($ghRepos, fn($r) => !in_array($r['name'], $excludeRepos, true)); - } - - // Check which already exist on Gitea - $giteaRepos = []; - try { - $existing = $this->gitea->listOrgRepos($giteaOrg); - foreach ($existing as $r) { - $giteaRepos[$r['name']] = true; - } - } catch (\Exception $e) { - echo "Note: Could not list Gitea repos (org may not exist yet): {$e->getMessage()}\n"; - } - - $toMigrate = []; - $toSkip = []; - foreach ($ghRepos as $repo) { - $name = $repo['name']; - if (isset($giteaRepos[$name])) { - $toSkip[] = $name; - } else { - $toMigrate[] = $repo; - } - } - - echo "\nMigration plan:\n"; - echo " Migrate: " . count($toMigrate) . " repositories\n"; - echo " Skip: " . count($toSkip) . " (already on Gitea)\n"; - if (!empty($toSkip)) { - echo " Skipped: " . implode(', ', $toSkip) . "\n"; - } - echo "\n"; - - if (empty($toMigrate)) { - echo "Nothing to migrate.\n"; - return 0; - } - - if ($dryRun) { - echo "Repositories to migrate:\n"; - foreach ($toMigrate as $repo) { - $vis = $repo['private'] ? 'private' : 'public'; - echo " - {$repo['name']} ({$vis})\n"; - } - echo "\nDry run complete. Use without --dry-run to execute.\n"; - return 0; - } - - // ── Phase 2: Migrate ──────────────────────────────────────────── - $this->section('Phase 2: Migration'); - - $ghToken = $config->getString('github.token'); - $results = ['migrated' => [], 'failed' => [], 'skipped' => $toSkip]; - - // Resume support - $checkpoint = $resume ? $this->checkpoints->loadCheckpoint('gitea_migration') : null; - $startFrom = $checkpoint['last_completed'] ?? ''; - $skipUntil = !empty($startFrom); - - foreach ($toMigrate as $index => $repo) { - $name = $repo['name']; - - if ($skipUntil) { - if ($name === $startFrom) { - $skipUntil = false; - } - echo " Skipping {$name} (already migrated)\n"; - continue; - } - - echo "\n [{$index}/{" . count($toMigrate) . "}] Migrating {$name}...\n"; - - try { - // Shallow migration — copy current branch state only, no past - // commit history. This gives every repo a clean start on Gitea. - $this->gitea->migrateRepository([ - 'clone_addr' => "https://github.com/{$org}/{$name}.git", - 'repo_name' => $name, - 'repo_owner' => $giteaOrg, - 'service' => 'github', - 'auth_token' => $ghToken, - 'mirror' => false, - 'private' => $repo['private'], - 'issues' => false, - 'labels' => true, - 'milestones' => false, - 'releases' => false, - 'pull_requests' => false, - 'wiki' => false, - ]); - - echo " Migrated successfully\n"; - $results['migrated'][] = $name; - - // Save checkpoint after each successful migration - $this->checkpoints->saveCheckpoint('gitea_migration', [ - 'last_completed' => $name, - 'migrated' => $results['migrated'], - 'failed' => $results['failed'], - ]); - } catch (\Exception $e) { - echo " FAILED: " . $e->getMessage() . "\n"; - $results['failed'][] = ['name' => $name, 'error' => $e->getMessage()]; - $this->gitea->getApiClient()->resetCircuitBreaker(); - } - } - - // ── Phase 3: Post-migration ───────────────────────────────────── - $this->section('Phase 3: Post-migration'); - - foreach ($results['migrated'] as $name) { - echo " Post-processing {$name}...\n"; - - try { - // Apply topics from GitHub - $ghTopics = $this->github->getRepoTopics($org, $name); - if (!empty($ghTopics)) { - $this->gitea->setRepoTopics($giteaOrg, $name, $ghTopics); - echo " Topics applied\n"; - } - - // Apply branch protection - $this->gitea->setBranchProtection($giteaOrg, $name, 'main', [ - 'required_reviews' => 1, - 'dismiss_stale' => true, - 'block_on_rejected' => true, - ]); - echo " Branch protection applied\n"; - } catch (\Exception $e) { - echo " Warning: post-processing issue: " . $e->getMessage() . "\n"; - $this->gitea->getApiClient()->resetCircuitBreaker(); - } - } - - // ── Phase 4: Verification ─────────────────────────────────────── - $this->section('Phase 4: Verification'); - - $report = "## Migration Report\n\n"; - $report .= "**Date:** " . gmdate('Y-m-d H:i:s') . " UTC\n"; - $report .= "**Source:** GitHub ({$org})\n"; - $report .= "**Destination:** Gitea ({$giteaOrg})\n\n"; - - $report .= "### Results\n\n"; - $report .= "| Status | Count |\n|--------|-------|\n"; - $report .= "| Migrated | " . count($results['migrated']) . " |\n"; - $report .= "| Failed | " . count($results['failed']) . " |\n"; - $report .= "| Skipped (existing) | " . count($results['skipped']) . " |\n\n"; - - if (!empty($results['migrated'])) { - $report .= "### Migrated Repositories\n\n"; - foreach ($results['migrated'] as $name) { - $report .= "- {$name}\n"; - } - $report .= "\n"; - } - - if (!empty($results['failed'])) { - $report .= "### Failed Repositories\n\n"; - foreach ($results['failed'] as $fail) { - $report .= "- **{$fail['name']}**: {$fail['error']}\n"; - } - $report .= "\n"; - } - - echo $report; - - // Create summary issue on Gitea - try { - $this->gitea->createIssue( - $giteaOrg, - 'mokocli', - 'chore: GitHub → Gitea migration report — ' . count($results['migrated']) . ' repos migrated', - $report, - ['labels' => ['automation', 'type: chore']] - ); - echo "Migration report issue created on Gitea.\n"; - } catch (\Exception $e) { - echo "Could not create report issue: " . $e->getMessage() . "\n"; - } - - echo "\nMigration complete: " . count($results['migrated']) . " migrated, " - . count($results['failed']) . " failed, " - . count($results['skipped']) . " skipped\n"; - - return count($results['failed']) > 0 ? 1 : 0; - } -} - -$script = new MigrateToGitea('migrate_to_gitea', 'Migrate repositories from GitHub to Gitea'); -exit($script->execute()); diff --git a/automation/push_files.php b/automation/push_files.php deleted file mode 100644 index a62bfc4..0000000 --- a/automation/push_files.php +++ /dev/null @@ -1,683 +0,0 @@ -#!/usr/bin/env php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoPlatform.Automation - * INGROUP: MokoPlatform.Scripts - * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli - * PATH: /automation/push_files.php - * BRIEF: Push one or more specific files to one or more remote repositories - */ - -declare(strict_types=1); - -require_once __DIR__ . '/../vendor/autoload.php'; -require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; - -use MokoCli\{ - ApiClient, - AuditLogger, - CliFramework, - Config, - GitPlatformAdapter, - MetricsCollector, - PlatformAdapterFactory, - ProjectTypeDetector -}; - -/** - * Targeted File Push Tool - * - * Pushes one or more specific files from mokocli templates to one or - * more remote repositories — without running a full sync. - * - * Files are specified by their destination path as they appear in the target - * repository (e.g., ".github/ISSUE_TEMPLATE/config.yml"). The tool looks up - * the matching source template from the appropriate platform definition. - * - * Files may also be given as "source:destination" pairs to bypass definition - * lookup and push any arbitrary local file. - * - * Usage: - * php push_files.php --files=.github/ISSUE_TEMPLATE/config.yml --repos=MokoCRM - * php push_files.php --files=".github/workflows/ci.yml,.github/workflows/codeql-analysis.yml" --repos=MokoCRM,WaasComponent - * php push_files.php --files=templates/foo.txt:docs/foo.txt --repos=MyRepo --direct - */ -class PushFiles extends CliFramework -{ - public const DEFAULT_ORG = 'MokoConsulting'; - public const VERSION = '09.23.00'; - - private ApiClient $api; - private GitPlatformAdapter $adapter; - private AuditLogger $logger; - private ProjectTypeDetector $typeDetector; - - /** - * Setup command-line arguments - */ - protected function configure(): void - { - $this->setDescription('Push files to remote repositories'); - $this->addArgument('--org', 'GitHub organization', self::DEFAULT_ORG); - $this->addArgument('--repos', 'Target repos (comma-separated)', ''); - $this->addArgument('--files', 'Files to push (comma-separated)', ''); - $this->addArgument('--message', 'Custom commit message', ''); - $this->addArgument('--branch', 'Target branch for direct pushes', ''); - $this->addArgument('--direct', 'Push directly instead of PR', false); - $this->addArgument('--yes', 'Auto-confirm without prompting', false); - $this->addArgument('--no-issue', 'Skip creating tracking issue', false); - } - - /** - * Main execution - */ - protected function run(): int - { - $this->log('📦 mokocli File Push v' . self::VERSION, 'INFO'); - - if (!$this->initializeComponents()) { - return 1; - } - - $org = $this->getArgument('--org', self::DEFAULT_ORG); - $reposArg = $this->getArgument('--repos', ''); - $filesArg = $this->getArgument('--files', ''); - $direct = $this->getArgument('--direct', false); - $autoYes = $this->getArgument('--yes', false); - - // Validate required arguments - if (empty($reposArg)) { - $this->log('❌ --repos is required. Specify one or more repository names.', 'ERROR'); - $this->log(' Example: --repos=MokoCRM,WaasComponent', 'ERROR'); - return 1; - } - - if (empty($filesArg)) { - $this->log('❌ --files is required. Specify destination paths or source:destination pairs.', 'ERROR'); - $this->log(' Example: --files=.github/ISSUE_TEMPLATE/config.yml', 'ERROR'); - return 1; - } - - $repos = $this->parseList($reposArg); - $files = $this->parseList($filesArg); - - $this->log("Organisation: {$org}", 'INFO'); - $this->log('Repositories: ' . implode(', ', $repos), 'INFO'); - $this->log('Files: ' . implode(', ', $files), 'INFO'); - $this->log('Mode: ' . ($direct ? 'direct commit' : 'pull request'), 'INFO'); - - // Resolve file mappings for each repo - $this->log("\n🔍 Resolving file mappings...", 'INFO'); - $repoFileMaps = $this->buildRepoFileMaps($org, $repos, $files); - - if (empty($repoFileMaps)) { - $this->log('❌ No files could be resolved. Check file paths and platform definitions.', 'ERROR'); - return 1; - } - - // Confirm before proceeding - if (!$autoYes && !$this->confirmPush($repoFileMaps, $direct)) { - $this->log('❌ Cancelled.', 'INFO'); - return 0; - } - - // Execute pushes - $results = $this->executePushes($org, $repoFileMaps, $direct); - - $this->displayResults($results); - - if ($results['failed'] > 0 && !isset($this->options['no-issue']) && !$this->dryRun) { - $this->createFailureIssue($org, $results); - } - - return $results['failed'] > 0 ? 1 : 0; - } - - /** - * Initialize enterprise components - */ - private function initializeComponents(): bool - { - $config = Config::load(); - - try { - $this->adapter = PlatformAdapterFactory::create($config); - $this->api = $this->adapter->getApiClient(); - $this->logger = new AuditLogger('push_files'); - $this->typeDetector = new ProjectTypeDetector($this->logger); - - $platform = $this->adapter->getPlatformName(); - $this->log("✓ Components initialized for platform: {$platform}", 'INFO'); - return true; - } catch (\Exception $e) { - $this->log('❌ Failed to initialize: ' . $e->getMessage(), 'ERROR'); - return false; - } - } - - /** - * Parse a comma- or space-separated list into a clean array - */ - private function parseList(string $input): array - { - return array_values(array_filter( - array_map('trim', preg_split('/[\s,]+/', $input)), - fn($v) => $v !== '' - )); - } - - /** - * Build per-repo file maps: repo → [ [source, destination], … ] - * - * Each entry in $files is either: - * - "destination/path" → looked up in the platform definition - * - "source/path:destination/path" → used as-is (raw mode) - * - * @param string[] $repos - * @param string[] $files - * @return array> - */ - private function buildRepoFileMaps(string $org, array $repos, array $files): array - { - $repoRoot = dirname(__DIR__, 2); - $maps = []; - - foreach ($repos as $repo) { - // Detect the repo's platform so we load the right definition - $platform = $this->detectRepoPlatform($org, $repo); - $this->log(" {$repo}: platform = {$platform}", 'INFO'); - - $resolved = []; - foreach ($files as $fileSpec) { - if (str_contains($fileSpec, ':')) { - // Raw source:destination pair - [$src, $dest] = explode(':', $fileSpec, 2); - } else { - // Same path as source and destination - $src = $fileSpec; - $dest = $fileSpec; - } - $dest = ltrim($dest, '/'); - $srcAbs = rtrim($repoRoot, '/') . '/' . ltrim($src, '/'); - if (!file_exists($srcAbs)) { - $this->log(" ⚠️ Source not found for {$repo}: {$src}", 'WARN'); - continue; - } - $resolved[] = ['source' => $srcAbs, 'destination' => $dest]; - $this->log(" ✓ {$dest}", 'INFO'); - } - - if (!empty($resolved)) { - $maps[$repo] = $resolved; - } - } - - return $maps; - } - - /** - * Detect platform for a repo via manifest or live detection. - */ - private function detectRepoPlatform(string $org, string $repo): string - { - // Read platform from repo's .mokogitea/manifest.xml via API - try { - $fileInfo = $this->adapter->getFileContents($org, $repo, '.mokogitea/manifest.xml', 'main'); - $manifestData = isset($fileInfo['content']) ? base64_decode($fileInfo['content']) : ''; - if (!empty($manifestData)) { - $xml = @simplexml_load_string($manifestData); - if ($xml !== false) { - $platform = (string)($xml->governance->platform ?? ''); - if (!empty($platform)) { - return $platform; - } - } - } - } catch (\Exception $e) { - // Fall through to local detection - } - - // Fall back to live detection - try { - $result = $this->typeDetector->detect('.'); - return $result['type'] ?? 'default'; - } catch (\Exception $e) { - $this->log(" ⚠️ Could not detect platform for {$repo}, using 'default'", 'WARN'); - return 'default'; - } - } - - /** - * Prompt for confirmation before pushing - * - * @param array> $repoFileMaps - */ - private function confirmPush(array $repoFileMaps, bool $direct): bool - { - if ($this->quiet) { - return true; - } - - $totalFiles = array_sum(array_map('count', $repoFileMaps)); - $totalRepos = count($repoFileMaps); - $mode = $direct ? 'direct commit' : 'PR'; - - echo "\n"; - foreach ($repoFileMaps as $repo => $entries) { - echo " {$repo}:\n"; - foreach ($entries as $entry) { - echo " → {$entry['destination']}\n"; - } - } - echo "\n"; - echo "⚠️ About to push {$totalFiles} file(s) to {$totalRepos} repo(s) via {$mode}.\n"; - echo "Continue? [y/N]: "; - - $handle = fopen('php://stdin', 'r'); - $line = fgets($handle); - if ($handle) { - fclose($handle); - } - - return is_string($line) && strtolower(trim($line)) === 'y'; - } - - /** - * Execute all file pushes - * - * @param array> $repoFileMaps - * @return array{total: int, success: int, failed: int, repos: array} - */ - private function executePushes(string $org, array $repoFileMaps, bool $direct): array - { - $results = [ - 'total' => count($repoFileMaps), - 'success' => 0, - 'failed' => 0, - 'repos' => [], - ]; - - $customMessage = $this->getArgument('--message', ''); - $targetBranch = $this->getArgument('--branch', ''); - - foreach ($repoFileMaps as $repo => $entries) { - $this->log("\n[{$repo}] Pushing " . count($entries) . ' file(s)...', 'INFO'); - - try { - // Resolve the default branch - $repoData = $this->adapter->getRepo($org, $repo); - $defaultBranch = $repoData['default_branch'] ?? 'main'; - $branch = $direct - ? ($targetBranch ?: $defaultBranch) - : $this->createSyncBranch($org, $repo, $defaultBranch); - - $pushed = 0; - foreach ($entries as $entry) { - if ($this->pushSingleFile($org, $repo, $entry['source'], $entry['destination'], $branch, $customMessage)) { - $pushed++; - $this->log(" ✓ {$entry['destination']}", 'INFO'); - } else { - $this->log(" ✗ {$entry['destination']}", 'ERROR'); - } - } - - if ($pushed === 0) { - $results['failed']++; - $results['repos'][$repo] = 'failed'; - continue; - } - - $prNumber = null; - if (!$direct) { - $prTitle = "chore: push " . count($entries) . " file(s) from mokocli"; - $prBody = $this->buildPRBody($entries); - $pr = $this->adapter->createPullRequest( - $org, - $repo, - $prTitle, - $branch, - $defaultBranch, - $prBody, - ['assignees' => ['jmiller']] - ); - $prNumber = $pr['number'] ?? null; - $this->log(" 📋 PR #{$prNumber} created", 'INFO'); - $results['repos'][$repo] = "pr#{$prNumber}"; - } else { - $results['repos'][$repo] = 'pushed'; - } - - if (!isset($this->options['no-issue']) && !$this->dryRun) { - $this->createTargetRepoIssue($org, $repo, $entries, $prNumber, $direct ? $branch : null); - } - - $results['success']++; - } catch (\Exception $e) { - $this->log(" ✗ {$repo}: " . $e->getMessage(), 'ERROR'); - $results['failed']++; - $results['repos'][$repo] = 'failed'; - } - } - - return $results; - } - - /** - * Create a uniquely-named sync branch off the default branch - */ - private function createSyncBranch(string $org, string $repo, string $base): string - { - $branchName = 'moko/push-files-' . date('Ymd-His'); - - // Resolve the base branch to a commit SHA using the adapter - $sha = $this->adapter->resolveRef($org, $repo, $base); - - if (empty($sha)) { - throw new \RuntimeException("Cannot resolve SHA for branch {$base} in {$repo}"); - } - - $this->api->post("/repos/{$org}/{$repo}/git/refs", [ - 'ref' => "refs/heads/{$branchName}", - 'sha' => $sha, - ]); - - $this->log(" 🌿 Branch created: {$branchName}", 'INFO'); - return $branchName; - } - - /** - * Push a single file to a repository branch via the Contents API - * - * @return bool True on success - */ - private function pushSingleFile( - string $org, - string $repo, - string $sourcePath, - string $destPath, - string $branch, - string $customMessage - ): bool { - $content = file_get_contents($sourcePath); - if ($content === false) { - $this->log(" ⚠️ Cannot read source: {$sourcePath}", 'WARN'); - return false; - } - - $message = !empty($customMessage) - ? $customMessage - : "chore: update {$destPath} from mokocli"; - - // Fetch existing file SHA (needed for updates) - $existingSha = null; - try { - $existing = $this->adapter->getFileContents($org, $repo, $destPath, $branch); - $existingSha = $existing['sha'] ?? null; - } catch (\Exception $e) { - // File does not exist — create it (no sha needed) - $this->adapter->getApiClient()->resetCircuitBreaker(); - } - - try { - $this->adapter->createOrUpdateFile( - $org, - $repo, - $destPath, - $content, - $message, - $existingSha, - $branch - ); - return true; - } catch (\Exception $e) { - $this->log(" ✗ API error pushing {$destPath}: " . $e->getMessage(), 'ERROR'); - return false; - } - } - - /** - * Create a tracking issue in the target repository after a successful push. - * - * @param list $entries - */ - private function createTargetRepoIssue( - string $org, - string $repo, - array $entries, - ?int $prNumber, - ?string $directBranch - ): void { - $now = gmdate('Y-m-d H:i:s') . ' UTC'; - $version = self::VERSION; - $source = $this->adapter->getRepoWebUrl($org, 'mokocli'); - - $title = "chore: mokocli file push tracking"; - - $deliveryLine = $prNumber !== null - ? "| **Pull request** | [#{$prNumber}](" . $this->adapter->getPullRequestWebUrl($org, $repo, $prNumber) . ") |" - : "| **Delivery** | Direct commit to `{$directBranch}` |"; - - $fileRows = implode("\n", array_map( - fn($e) => "- `{$e['destination']}`", - $entries - )); - - $body = <<api->get("/repos/{$org}/{$repo}/issues", [ - 'labels' => 'standards-update', - 'state' => 'all', - 'per_page' => 1, - 'sort' => 'created', - 'direction' => 'desc', - ]); - - $existing = array_values($existing); - if (!empty($existing) && isset($existing[0]['number'])) { - $num = $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; - if (($existing[0]['state'] ?? 'open') === 'closed') { - $patch['state'] = 'open'; - } - $this->api->patch("/repos/{$org}/{$repo}/issues/{$num}", $patch); - try { - $this->api->post("/repos/{$org}/{$repo}/issues/{$num}/labels", ['labels' => $labels]); - } catch (\Exception $le) { -/* non-fatal */ - } - $this->log(" 📋 Tracking issue #{$num} updated in {$repo}", 'INFO'); - } else { - $issue = $this->api->post("/repos/{$org}/{$repo}/issues", [ - 'title' => $title, - 'body' => $body, - 'labels' => $labels, - 'assignees' => ['jmiller'], - ]); - $num = $issue['number'] ?? null; - $this->log(" 📋 Tracking issue #{$num} created in {$repo}", 'INFO'); - } - - // Cross-link: patch the sync PR body to reference the tracking issue - // so GitHub shows it in the PR's Development sidebar. - if ($prNumber !== null && is_int($num)) { - try { - $pr = $this->api->get("/repos/{$org}/{$repo}/pulls/{$prNumber}"); - $currentBody = $pr['body'] ?? ''; - $ref = "Linked to #{$num}"; - if (!str_contains($currentBody, $ref)) { - $this->api->patch("/repos/{$org}/{$repo}/pulls/{$prNumber}", [ - 'body' => $ref . "\n\n" . $currentBody, - ]); - } - } catch (\Exception $le) { -/* non-fatal */ - } - } - } catch (\Exception $e) { - $this->log(" ⚠️ Could not create/update tracking issue in {$repo}: " . $e->getMessage(), 'WARN'); - } - } - - /** - * Create or update a failure issue in mokocli when repos fail to receive files. - * Uses the 'push-failure' label. Reopens a closed issue rather than creating a duplicate. - */ - private function createFailureIssue(string $org, array $results): void - { - $now = gmdate('Y-m-d H:i:s') . ' UTC'; - $failed = $results['failed']; - $version = self::VERSION; - - $failedRepos = array_keys(array_filter( - $results['repos'] ?? [], - fn($s) => $s === 'failed' - )); - - $repoList = implode("\n", array_map(fn($r) => "- `{$r}`", $failedRepos)); - $fileArgs = $this->getArgument('--files', ''); - - $title = "fix: push_files failed for {$failed} repo(s) — action required"; - - $body = << --files= --yes` - 4. Close this issue once resolved. - - --- - *Auto-created by `push_files.php` — close once resolved.* - MD; - - $body = preg_replace('/^ /m', '', $body); - - try { - $existing = $this->api->get("/repos/{$org}/mokocli/issues", [ - 'labels' => 'push-failure', - 'state' => 'all', - 'per_page' => 1, - 'sort' => 'created', - 'direction' => 'desc', - ]); - - $existing = array_values($existing); - if (!empty($existing) && isset($existing[0]['number'])) { - $num = $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; - if (($existing[0]['state'] ?? 'open') === 'closed') { - $patch['state'] = 'open'; - } - $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", [ - 'title' => $title, - 'body' => $body, - 'labels' => ['push-failure'], - 'assignees' => ['jmiller'], - ]); - $num = $issue['number'] ?? '?'; - $this->log("🚨 Failure issue created: {$org}/mokocli#{$num}", 'WARN'); - } - } catch (\Exception $e) { - $this->log("⚠️ Could not create/update failure issue: " . $e->getMessage(), 'WARN'); - } - } - - /** - * Build a markdown PR body listing every pushed file - * - * @param list $entries - */ - private function buildPRBody(array $entries): string - { - $now = gmdate('Y-m-d H:i:s') . ' UTC'; - $lines = ["## mokocli File Push\n", "**Pushed:** {$now}\n", '### Files\n']; - - foreach ($entries as $entry) { - $lines[] = "- `{$entry['destination']}`"; - } - - $sourceUrl = $this->adapter->getRepoWebUrl(self::DEFAULT_ORG, 'mokocli'); - $lines[] = "\n---\n*Generated by [mokocli]({$sourceUrl}) `push_files.php`*"; - - return implode("\n", $lines); - } - - /** - * Display final results - * - * @param array{total: int, success: int, failed: int, repos: array} $results - */ - private function displayResults(array $results): void - { - $this->log("\n" . str_repeat('=', 60), 'INFO'); - $this->log('📊 Push Complete', 'INFO'); - $this->log(str_repeat('=', 60), 'INFO'); - $this->log(sprintf('Total: %d repos', $results['total']), 'INFO'); - $this->log(sprintf('Success: %d', $results['success']), 'INFO'); - $this->log(sprintf('Failed: %d', $results['failed']), 'INFO'); - - if ($this->verbose) { - $this->log("\n📋 Details:", 'INFO'); - foreach ($results['repos'] as $repo => $outcome) { - $icon = str_starts_with($outcome, 'pr#') || $outcome === 'pushed' ? '✓' : '✗'; - $this->log(" {$icon} {$repo}: {$outcome}", 'INFO'); - } - } - - $this->log(str_repeat('=', 60), 'INFO'); - } -} - -// Execute if run directly -if (php_sapi_name() === 'cli' && isset($argv[0]) && realpath($argv[0]) === __FILE__) { - $app = new PushFiles(); - exit($app->execute()); -} diff --git a/automation/push_manifest_xml.php b/automation/push_manifest_xml.php deleted file mode 100644 index 00c7a59..0000000 --- a/automation/push_manifest_xml.php +++ /dev/null @@ -1,345 +0,0 @@ -#!/usr/bin/env php - - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoPlatform.Automation - * INGROUP: MokoPlatform - * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli - * PATH: /automation/push_manifest_xml.php - * BRIEF: Push XML manifests to all governed repositories - */ - -declare(strict_types=1); - -require_once __DIR__ . '/../vendor/autoload.php'; -require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; - -use MokoCli\CliFramework; -use MokoCli\ManifestParser; - -class PushManifestXmlCli extends CliFramework -{ - private const CRM_PLATFORM_REPOS = ['MokoDolibarr', 'MokoDoliMods']; - - protected function configure(): void - { - $this->setDescription('Push XML manifest.xml to all governed repositories'); - $this->addArgument('--repo', 'Filter to a single repo name', ''); - $this->addArgument('--skip', 'Comma-separated list of repos to skip', ''); - $this->addArgument('--force', 'Force overwrite even if already XML', false); - } - - protected function run(): int - { - $giteaUrl = rtrim(getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech', '/'); - $giteaOrg = getenv('GITEA_ORG') ?: 'MokoConsulting'; - $token = getenv('GA_TOKEN') ?: getenv('GH_TOKEN') ?: ''; - - $force = $this->getArgument('--force'); - $repoFilter = $this->getArgument('--repo') ?: null; - $skipStr = $this->getArgument('--skip'); - $skipRepos = $skipStr !== '' ? array_map('trim', explode(',', $skipStr)) : []; - - $parser = new ManifestParser(); - $tmpBase = sys_get_temp_dir() . '/moko-manifest-push-' . getmypid(); - - echo "=== mokocli XML Manifest Push ===\n"; - echo "Org: {$giteaOrg}\n"; - echo "Mode: " . ($this->dryRun ? "DRY RUN" : "LIVE") . "\n"; - if ($repoFilter) { - echo "Filter: {$repoFilter}\n"; - } - echo "\n"; - - if (empty($token)) { - $this->log('ERROR', 'GA_TOKEN or GH_TOKEN environment variable required'); - return 1; - } - - $repos = $this->fetchRepos($giteaUrl, $giteaOrg, $token); - echo "Found " . count($repos) . " repositories\n\n"; - - $stats = ['created' => 0, 'updated' => 0, 'skipped' => 0, 'failed' => 0]; - - foreach ($repos as $repo) { - $name = $repo['name']; - if ($repoFilter && $name !== $repoFilter) { - continue; - } - if (in_array($name, $skipRepos, true)) { - echo " SKIP {$name} (excluded)\n"; - $stats['skipped']++; - continue; - } - if ($repo['archived'] ?? false) { - echo " SKIP {$name} (archived)\n"; - $stats['skipped']++; - continue; - } - - $platform = $this->detectPlatform($repo); - $defaultBranch = $repo['default_branch'] ?? 'main'; - $httpsUrl = $repo['clone_url'] ?? "{$giteaUrl}/{$giteaOrg}/{$name}.git"; - $authedUrl = preg_replace('#^https://#', "https://gitea-actions:{$token}@", $httpsUrl); - - echo " {$name} [{$platform}] ... "; - - // Generate XML manifest - $xmlContent = $parser->generate([ - 'name' => $name, - 'org' => $giteaOrg, - 'platform' => $platform, - 'standards_version' => '04.07.00', - 'description' => $repo['description'] ?? '', - 'license' => 'GPL-3.0-or-later', - 'topics' => $repo['topics'] ?? [], - 'language' => $repo['language'] ?? ManifestParser::platformLanguage($platform), - 'package_type' => ManifestParser::platformPackageType($platform), - 'last_synced' => date('c'), - ]); - - if ($this->dryRun) { - echo "WOULD WRITE ({$platform})\n"; - $stats['created']++; - continue; - } - - // Clone shallow via HTTPS (token-authed) - $workDir = "{$tmpBase}/{$name}"; - @mkdir($workDir, 0755, true); - - [$ret, $out] = $this->safeExec( - 'git clone --depth 1 --branch ' . escapeshellarg($defaultBranch) . ' ' - . escapeshellarg($authedUrl) . ' ' . escapeshellarg($workDir) - ); - if ($ret !== 0) { - echo "FAIL (clone)\n"; - fprintf(STDERR, " %s\n", $out); - $stats['failed']++; - continue; - } - - // Check if already XML and up-to-date - $manifestPath = "{$workDir}/.mokogitea/manifest.xml"; - $existingIsXml = file_exists($manifestPath) && str_contains(file_get_contents($manifestPath), 'extractPlatform(file_get_contents($manifestPath)); - if ($existingPlatform === $platform) { - echo "SKIP (already XML)\n"; - $stats['skipped']++; - $this->rmTree($workDir); - continue; - } - } - - // Write manifest - @mkdir("{$workDir}/.gitea", 0755, true); - file_put_contents($manifestPath, $xmlContent); - - // Delete legacy files if present - $legacyDeleted = []; - foreach (['.mokostandards', '.github/.mokostandards', '.gitea/.mokostandards', '.mokogitea/.mokostandards'] as $legacy) { - $legacyPath = "{$workDir}/{$legacy}"; - if (file_exists($legacyPath)) { - unlink($legacyPath); - $legacyDeleted[] = $legacy; - } - } - - // Commit - $isNew = !$existingIsXml; - $commitMsg = $isNew - ? 'chore: add XML manifest.xml' - : 'chore: update manifest.xml'; - if (!empty($legacyDeleted)) { - $commitMsg .= "\n\nRemoved legacy: " . implode(', ', $legacyDeleted); - } - - $this->gitCmd($workDir, 'config', 'user.name', 'gitea-actions[bot]'); - $this->gitCmd($workDir, 'config', 'user.email', 'gitea-actions[bot]@git.mokoconsulting.tech'); - $this->gitCmd($workDir, 'add', '.mokogitea/manifest.xml'); - foreach ($legacyDeleted as $lf) { - $this->gitCmd($workDir, 'add', $lf); - } - - [$commitRet, $commitOut] = $this->gitCmd($workDir, 'commit', '-m', $commitMsg); - if ($commitRet !== 0 && str_contains($commitOut, 'nothing to commit')) { - echo "SKIP (no changes)\n"; - $stats['skipped']++; - $this->rmTree($workDir); - continue; - } - if ($commitRet !== 0) { - echo "FAIL (commit)\n"; - fprintf(STDERR, " %s\n", $commitOut); - $stats['failed']++; - $this->rmTree($workDir); - continue; - } - - [$pushRet, $pushOut] = $this->gitCmd($workDir, 'push', 'origin', $defaultBranch); - if ($pushRet !== 0) { - echo "FAIL (push)\n"; - fprintf(STDERR, " %s\n", $pushOut); - $stats['failed']++; - } else { - $action = $isNew ? 'CREATED' : 'UPDATED'; - echo "{$action}\n"; - $stats[$isNew ? 'created' : 'updated']++; - } - - // Cleanup - $this->rmTree($workDir); - } - - // Cleanup tmp base - @rmdir($tmpBase); - - echo "\n=== Summary ===\n"; - echo "Created: {$stats['created']}\n"; - echo "Updated: {$stats['updated']}\n"; - echo "Skipped: {$stats['skipped']}\n"; - echo "Failed: {$stats['failed']}\n"; - - return 0; - } - - private function detectPlatform(array $repo): string - { - $name = $repo['name'] ?? ''; - $nameLower = strtolower($name); - $description = strtolower($repo['description'] ?? ''); - $topics = $repo['topics'] ?? []; - - if (in_array($name, self::CRM_PLATFORM_REPOS, true)) { - return 'crm-platform'; - } - if (in_array('dolibarr-platform', $topics)) { - return 'crm-platform'; - } - if (in_array('joomla-template', $topics)) { - return 'joomla-template'; - } - if (in_array('joomla', $topics) || in_array('joomla-extension', $topics)) { - return 'waas-component'; - } - if (in_array('dolibarr', $topics) || in_array('dolibarr-module', $topics)) { - return 'crm-module'; - } - - if (str_contains($nameLower, 'template') && (str_contains($nameLower, 'joomla') || str_contains($nameLower, 'tpl'))) { - return 'joomla-template'; - } - if (str_contains($nameLower, 'joomla') || str_contains($nameLower, 'waas')) { - return 'waas-component'; - } - if (str_contains($nameLower, 'doli') || str_contains($nameLower, 'crm')) { - return 'crm-module'; - } - - if (str_contains($description, 'joomla template')) { - return 'joomla-template'; - } - if (str_contains($description, 'joomla') || str_contains($description, 'component')) { - return 'waas-component'; - } - if (str_contains($description, 'dolibarr') || str_contains($description, 'module')) { - return 'crm-module'; - } - - if (str_contains($nameLower, 'standard')) { - return 'standards-repository'; - } - return 'default-repository'; - } - - /** - * @return array{int, string} - */ - private function safeExec(string $command, string $cwd = '.'): array - { - $proc = proc_open( - $command, - [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], - $pipes, - $cwd - ); - if (!is_resource($proc)) { - return [1, "proc_open failed for: {$command}"]; - } - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); - fclose($pipes[1]); - fclose($pipes[2]); - $code = proc_close($proc); - return [$code, trim($stdout . "\n" . $stderr)]; - } - - private function rmTree(string $dir): void - { - if (!is_dir($dir)) { - return; - } - $it = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS); - $files = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST); - foreach ($files as $file) { - if ($file->isDir()) { - @rmdir($file->getPathname()); - } else { - @chmod($file->getPathname(), 0777); - @unlink($file->getPathname()); - } - } - @rmdir($dir); - } - - /** - * @return array{int, string} - */ - private function gitCmd(string $workDir, string ...$args): array - { - $cmd = 'git'; - foreach ($args as $a) { - $cmd .= ' ' . escapeshellarg($a); - } - return $this->safeExec($cmd, $workDir); - } - - private function fetchRepos(string $url, string $org, string $token): array - { - $repos = []; - $page = 1; - do { - $ch = curl_init("{$url}/api/v1/orgs/{$org}/repos?page={$page}&limit=50"); - curl_setopt_array($ch, [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_HTTPHEADER => ["Authorization: token {$token}"], - CURLOPT_TIMEOUT => 30, - ]); - $body = curl_exec($ch); - $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - if ($code !== 200) { - $this->log('ERROR', "API error (HTTP {$code}) fetching repos page {$page}"); - break; - } - - $batch = json_decode($body, true); - if (empty($batch)) { - break; - } - $repos = array_merge($repos, $batch); - $page++; - } while (count($batch) >= 50); - - return $repos; - } -} - -$app = new PushManifestXmlCli(); -exit($app->execute()); diff --git a/automation/push_mokostandards_xml.php b/automation/push_mokostandards_xml.php deleted file mode 100644 index 4ba48f9..0000000 --- a/automation/push_mokostandards_xml.php +++ /dev/null @@ -1,345 +0,0 @@ -#!/usr/bin/env php - - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoPlatform.Automation - * INGROUP: MokoPlatform - * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli - * PATH: /automation/push_mokostandards_xml.php - * BRIEF: Push XML manifests to all governed repositories - */ - -declare(strict_types=1); - -require_once __DIR__ . '/../vendor/autoload.php'; -require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; - -use MokoCli\CliFramework; -use MokoCli\ManifestParser; - -class PushMokostandardsXmlCli extends CliFramework -{ - private const CRM_PLATFORM_REPOS = ['MokoDolibarr', 'MokoDoliMods']; - - protected function configure(): void - { - $this->setDescription('Push XML manifests to all governed repositories'); - $this->addArgument('--repo', 'Filter to a single repo name', ''); - $this->addArgument('--skip', 'Comma-separated list of repos to skip', ''); - $this->addArgument('--force', 'Force overwrite even if already XML', false); - } - - protected function run(): int - { - $giteaUrl = rtrim(getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech', '/'); - $giteaOrg = getenv('GITEA_ORG') ?: 'MokoConsulting'; - $token = getenv('GA_TOKEN') ?: getenv('GH_TOKEN') ?: ''; - - $force = $this->getArgument('--force'); - $repoFilter = $this->getArgument('--repo') ?: null; - $skipStr = $this->getArgument('--skip'); - $skipRepos = $skipStr !== '' ? array_map('trim', explode(',', $skipStr)) : []; - - $parser = new ManifestParser(); - $tmpBase = sys_get_temp_dir() . '/moko-manifest-push-' . getmypid(); - - echo "=== mokocli XML Manifest Push ===\n"; - echo "Org: {$giteaOrg}\n"; - echo "Mode: " . ($this->dryRun ? "DRY RUN" : "LIVE") . "\n"; - if ($repoFilter) { - echo "Filter: {$repoFilter}\n"; - } - echo "\n"; - - if (empty($token)) { - $this->log('ERROR', 'GA_TOKEN or GH_TOKEN environment variable required'); - return 1; - } - - $repos = $this->fetchRepos($giteaUrl, $giteaOrg, $token); - echo "Found " . count($repos) . " repositories\n\n"; - - $stats = ['created' => 0, 'updated' => 0, 'skipped' => 0, 'failed' => 0]; - - foreach ($repos as $repo) { - $name = $repo['name']; - if ($repoFilter && $name !== $repoFilter) { - continue; - } - if (in_array($name, $skipRepos, true)) { - echo " SKIP {$name} (excluded)\n"; - $stats['skipped']++; - continue; - } - if ($repo['archived'] ?? false) { - echo " SKIP {$name} (archived)\n"; - $stats['skipped']++; - continue; - } - - $platform = $this->detectPlatform($repo); - $defaultBranch = $repo['default_branch'] ?? 'main'; - $httpsUrl = $repo['clone_url'] ?? "{$giteaUrl}/{$giteaOrg}/{$name}.git"; - $authedUrl = preg_replace('#^https://#', "https://gitea-actions:{$token}@", $httpsUrl); - - echo " {$name} [{$platform}] ... "; - - // Generate XML manifest - $xmlContent = $parser->generate([ - 'name' => $name, - 'org' => $giteaOrg, - 'platform' => $platform, - 'standards_version' => '04.07.00', - 'description' => $repo['description'] ?? '', - 'license' => 'GPL-3.0-or-later', - 'topics' => $repo['topics'] ?? [], - 'language' => $repo['language'] ?? ManifestParser::platformLanguage($platform), - 'package_type' => ManifestParser::platformPackageType($platform), - 'last_synced' => date('c'), - ]); - - if ($this->dryRun) { - echo "WOULD WRITE ({$platform})\n"; - $stats['created']++; - continue; - } - - // Clone shallow via HTTPS (token-authed) - $workDir = "{$tmpBase}/{$name}"; - @mkdir($workDir, 0755, true); - - [$ret, $out] = $this->safeExec( - 'git clone --depth 1 --branch ' . escapeshellarg($defaultBranch) . ' ' - . escapeshellarg($authedUrl) . ' ' . escapeshellarg($workDir) - ); - if ($ret !== 0) { - echo "FAIL (clone)\n"; - fprintf(STDERR, " %s\n", $out); - $stats['failed']++; - continue; - } - - // Check if already XML and up-to-date - $manifestPath = "{$workDir}/.mokogitea/manifest.xml"; - $existingIsXml = file_exists($manifestPath) && str_contains(file_get_contents($manifestPath), 'extractPlatform(file_get_contents($manifestPath)); - if ($existingPlatform === $platform) { - echo "SKIP (already XML)\n"; - $stats['skipped']++; - $this->rmTree($workDir); - continue; - } - } - - // Write manifest - @mkdir("{$workDir}/.gitea", 0755, true); - file_put_contents($manifestPath, $xmlContent); - - // Delete legacy files if present - $legacyDeleted = []; - foreach (['.mokostandards', '.github/.mokostandards'] as $legacy) { - $legacyPath = "{$workDir}/{$legacy}"; - if (file_exists($legacyPath)) { - unlink($legacyPath); - $legacyDeleted[] = $legacy; - } - } - - // Commit - $isNew = !$existingIsXml; - $commitMsg = $isNew - ? 'chore: add XML manifest.xml' - : 'chore: update .mokostandards to XML format'; - if (!empty($legacyDeleted)) { - $commitMsg .= "\n\nRemoved legacy: " . implode(', ', $legacyDeleted); - } - - $this->gitCmd($workDir, 'config', 'user.name', 'gitea-actions[bot]'); - $this->gitCmd($workDir, 'config', 'user.email', 'gitea-actions[bot]@git.mokoconsulting.tech'); - $this->gitCmd($workDir, 'add', '.mokogitea/manifest.xml'); - foreach ($legacyDeleted as $lf) { - $this->gitCmd($workDir, 'add', $lf); - } - - [$commitRet, $commitOut] = $this->gitCmd($workDir, 'commit', '-m', $commitMsg); - if ($commitRet !== 0 && str_contains($commitOut, 'nothing to commit')) { - echo "SKIP (no changes)\n"; - $stats['skipped']++; - $this->rmTree($workDir); - continue; - } - if ($commitRet !== 0) { - echo "FAIL (commit)\n"; - fprintf(STDERR, " %s\n", $commitOut); - $stats['failed']++; - $this->rmTree($workDir); - continue; - } - - [$pushRet, $pushOut] = $this->gitCmd($workDir, 'push', 'origin', $defaultBranch); - if ($pushRet !== 0) { - echo "FAIL (push)\n"; - fprintf(STDERR, " %s\n", $pushOut); - $stats['failed']++; - } else { - $action = $isNew ? 'CREATED' : 'UPDATED'; - echo "{$action}\n"; - $stats[$isNew ? 'created' : 'updated']++; - } - - // Cleanup - $this->rmTree($workDir); - } - - // Cleanup tmp base - @rmdir($tmpBase); - - echo "\n=== Summary ===\n"; - echo "Created: {$stats['created']}\n"; - echo "Updated: {$stats['updated']}\n"; - echo "Skipped: {$stats['skipped']}\n"; - echo "Failed: {$stats['failed']}\n"; - - return 0; - } - - private function detectPlatform(array $repo): string - { - $name = $repo['name'] ?? ''; - $nameLower = strtolower($name); - $description = strtolower($repo['description'] ?? ''); - $topics = $repo['topics'] ?? []; - - if (in_array($name, self::CRM_PLATFORM_REPOS, true)) { - return 'crm-platform'; - } - if (in_array('dolibarr-platform', $topics)) { - return 'crm-platform'; - } - if (in_array('joomla-template', $topics)) { - return 'joomla-template'; - } - if (in_array('joomla', $topics) || in_array('joomla-extension', $topics)) { - return 'waas-component'; - } - if (in_array('dolibarr', $topics) || in_array('dolibarr-module', $topics)) { - return 'crm-module'; - } - - if (str_contains($nameLower, 'template') && (str_contains($nameLower, 'joomla') || str_contains($nameLower, 'tpl'))) { - return 'joomla-template'; - } - if (str_contains($nameLower, 'joomla') || str_contains($nameLower, 'waas')) { - return 'waas-component'; - } - if (str_contains($nameLower, 'doli') || str_contains($nameLower, 'crm')) { - return 'crm-module'; - } - - if (str_contains($description, 'joomla template')) { - return 'joomla-template'; - } - if (str_contains($description, 'joomla') || str_contains($description, 'component')) { - return 'waas-component'; - } - if (str_contains($description, 'dolibarr') || str_contains($description, 'module')) { - return 'crm-module'; - } - - if (str_contains($nameLower, 'standard')) { - return 'standards-repository'; - } - return 'default-repository'; - } - - /** - * @return array{int, string} - */ - private function safeExec(string $command, string $cwd = '.'): array - { - $proc = proc_open( - $command, - [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], - $pipes, - $cwd - ); - if (!is_resource($proc)) { - return [1, "proc_open failed for: {$command}"]; - } - $stdout = stream_get_contents($pipes[1]); - $stderr = stream_get_contents($pipes[2]); - fclose($pipes[1]); - fclose($pipes[2]); - $code = proc_close($proc); - return [$code, trim($stdout . "\n" . $stderr)]; - } - - private function rmTree(string $dir): void - { - if (!is_dir($dir)) { - return; - } - $it = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS); - $files = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST); - foreach ($files as $file) { - if ($file->isDir()) { - @rmdir($file->getPathname()); - } else { - @chmod($file->getPathname(), 0777); - @unlink($file->getPathname()); - } - } - @rmdir($dir); - } - - /** - * @return array{int, string} - */ - private function gitCmd(string $workDir, string ...$args): array - { - $cmd = 'git'; - foreach ($args as $a) { - $cmd .= ' ' . escapeshellarg($a); - } - return $this->safeExec($cmd, $workDir); - } - - private function fetchRepos(string $url, string $org, string $token): array - { - $repos = []; - $page = 1; - do { - $ch = curl_init("{$url}/api/v1/orgs/{$org}/repos?page={$page}&limit=50"); - curl_setopt_array($ch, [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_HTTPHEADER => ["Authorization: token {$token}"], - CURLOPT_TIMEOUT => 30, - ]); - $body = curl_exec($ch); - $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - if ($code !== 200) { - $this->log('ERROR', "API error (HTTP {$code}) fetching repos page {$page}"); - break; - } - - $batch = json_decode($body, true); - if (empty($batch)) { - break; - } - $repos = array_merge($repos, $batch); - $page++; - } while (count($batch) >= 50); - - return $repos; - } -} - -$app = new PushMokostandardsXmlCli(); -exit($app->execute()); diff --git a/automation/repo_cleanup.php b/automation/repo_cleanup.php deleted file mode 100644 index d6d90ab..0000000 --- a/automation/repo_cleanup.php +++ /dev/null @@ -1,517 +0,0 @@ -#!/usr/bin/env php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoPlatform.Automation - * INGROUP: MokoPlatform.Scripts - * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli - * PATH: /automation/repo_cleanup.php - * BRIEF: Enterprise repository cleanup — branches, PRs, issues, workflows, labels, logs - */ - -declare(strict_types=1); - -require_once __DIR__ . '/../vendor/autoload.php'; -require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; - -use MokoCli\{ApiClient, AuditLogger, CliFramework, Config, GitPlatformAdapter, MetricsCollector, PlatformAdapterFactory}; - -/** - * Enterprise Repository Cleanup - * - * Comprehensive maintenance tool for governed repositories: - * 1. Delete stale sync branches (keeps current versioned branch) - * 2. Close superseded PRs on deleted branches - * 3. Close/lock resolved tracking issues where linked PR is merged - * 4. Delete retired workflow files from repos - * 5. Clean cancelled/stale workflow runs - * 6. Delete workflow run logs older than N days - * 7. Verify and provision standard labels - * 8. Version drift detection - */ -class RepoCleanup extends CliFramework -{ - private const VERSION = '09.23.00'; - private const SYNC_PREFIX = 'chore/sync-mokocli-'; - private const CURRENT_BRANCH = 'chore/sync-mokocli-v04.02.00'; - - /** 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', - 'flush-actions-cache.yml', 'mokocli-script-runner.yml', 'unified-ci.yml', - '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', - 'rebuild-docs-indexes.yml', 'setup-project-v2.yml', 'sync-docs-to-project.yml', - 'release.yml', 'sync-changelogs.yml', 'version_branch.yml', - 'publish-to-mokodolibarr.yml', 'ci.yml', - 'deploy-rs.yml', - ]; - - private ApiClient $api; - private GitPlatformAdapter $adapter; - protected bool $dryRun = false; - private float $startTime; - - protected function configure(): void - { - $this->setDescription('Enterprise repository cleanup'); - $this->addArgument('--org', 'GitHub organization', 'MokoConsulting'); - $this->addArgument('--repos', 'Specific repos (space-separated)', ''); - $this->addArgument('--skip-archived', 'Skip archived repos', false); - $this->addArgument('--close-issues', 'Close resolved tracking issues', false); - $this->addArgument('--lock-old-issues', 'Lock issues closed >30 days', false); - $this->addArgument('--clean-workflows', 'Delete stale workflow runs', false); - $this->addArgument('--clean-logs', 'Delete old workflow logs', false); - $this->addArgument('--log-days', 'Days to keep logs', '30'); - $this->addArgument('--delete-retired', 'Delete retired workflows', false); - $this->addArgument('--check-labels', 'Verify labels exist', false); - $this->addArgument('--check-drift', 'Check version drift', false); - $this->addArgument('--all', 'Run all operations', false); - $this->addArgument('--yes', 'Auto-confirm', false); - $this->addArgument('--json', 'Output as JSON', false); - } - - protected function run(): int - { - $this->startTime = microtime(true); - $org = $this->getArgument('--org', 'MokoConsulting'); - $this->dryRun = (bool) $this->getArgument('--dry-run', false); - $runAll = (bool) $this->getArgument('--all', false); - - $config = Config::load(); - - try { - $this->adapter = PlatformAdapterFactory::create($config); - $this->api = $this->adapter->getApiClient(); - } catch (\Exception $e) { - $this->errorMsg('Failed to initialize platform adapter: ' . $e->getMessage()); - return 1; - } - - - $this->logMsg("🧹 mokocli Repository Cleanup v" . self::VERSION); - $this->logMsg("Organization: {$org}"); - $this->logMsg("Current sync branch: " . self::CURRENT_BRANCH); - if ($this->dryRun) { - $this->logMsg("⚠️ DRY RUN — no changes will be made"); - } - $this->logMsg(''); - - $repos = $this->fetchRepositories($org); - $this->logMsg("Found " . count($repos) . " repositories"); - $this->logMsg(''); - - $results = [ - 'repos_processed' => 0, - 'repos_cleaned' => 0, - 'branches_deleted' => 0, - 'prs_closed' => 0, - 'issues_closed' => 0, - 'issues_locked' => 0, - 'workflows_deleted' => 0, - 'runs_deleted' => 0, - 'logs_deleted' => 0, - 'labels_missing' => 0, - 'version_drift' => 0, - 'retired_files' => 0, - 'errors' => 0, - ]; - - foreach ($repos as $i => $repo) { - $name = $repo['name']; - $num = $i + 1; - $total = count($repos); - $this->logMsg("[{$num}/{$total}] {$name}"); - $results['repos_processed']++; - - try { - $this->api->resetCircuitBreaker(); - $cleaned = false; - - // Always: delete old sync branches + close their PRs - $cleaned = $this->cleanBranches($org, $name, $results) || $cleaned; - - // Optional: close resolved issues - if ($runAll || $this->getArgument('--close-issues', false)) { - $cleaned = $this->closeResolvedIssues($org, $name, $results) || $cleaned; - } - - // Optional: lock old closed issues - if ($runAll || $this->getArgument('--lock-old-issues', false)) { - $cleaned = $this->lockOldIssues($org, $name, $results) || $cleaned; - } - - // Optional: delete retired workflow files - if ($runAll || $this->getArgument('--delete-retired', false)) { - $cleaned = $this->deleteRetiredWorkflows($org, $name, $results) || $cleaned; - } - - // Optional: clean workflow runs - if ($runAll || $this->getArgument('--clean-workflows', false)) { - $cleaned = $this->cleanWorkflowRuns($org, $name, $results) || $cleaned; - } - - // Optional: clean old logs - if ($runAll || $this->getArgument('--clean-logs', false)) { - $cleaned = $this->cleanOldLogs($org, $name, $results) || $cleaned; - } - - // Optional: check labels - if ($runAll || $this->getArgument('--check-labels', false)) { - $this->checkLabels($org, $name, $results); - } - - // Optional: check version drift - if ($runAll || $this->getArgument('--check-drift', false)) { - $this->checkVersionDrift($org, $name, $results); - } - - if ($cleaned) { - $results['repos_cleaned']++; - } - } catch (\Exception $e) { - $this->errorMsg(" ✗ {$name}: " . $e->getMessage()); - $results['errors']++; - } - } - - $duration = round(microtime(true) - $this->startTime, 1); - - $this->logMsg(''); - $this->logMsg('============================================================'); - $this->logMsg("🧹 Cleanup Complete ({$duration}s)"); - $this->logMsg('============================================================'); - $this->logMsg("Repos processed: {$results['repos_processed']}"); - $this->logMsg("Repos with changes: {$results['repos_cleaned']}"); - $this->logMsg("Branches deleted: {$results['branches_deleted']}"); - $this->logMsg("PRs closed: {$results['prs_closed']}"); - $this->logMsg("Issues closed: {$results['issues_closed']}"); - $this->logMsg("Issues locked: {$results['issues_locked']}"); - $this->logMsg("Retired files: {$results['retired_files']}"); - $this->logMsg("Workflow runs: {$results['runs_deleted']}"); - $this->logMsg("Logs cleaned: {$results['logs_deleted']}"); - $this->logMsg("Labels missing: {$results['labels_missing']}"); - $this->logMsg("Version drift: {$results['version_drift']}"); - $this->logMsg("Errors: {$results['errors']}"); - $this->logMsg('============================================================'); - - if ($this->getArgument('--json', false)) { - $results['duration_seconds'] = $duration; - echo json_encode($results, JSON_PRETTY_PRINT) . "\n"; - } - - return $results['errors'] > 0 ? 1 : 0; - } - - // ─── Repository fetching ───────────────────────────────────────────── - - private function fetchRepositories(string $org): array - { - $specificRepos = trim((string) $this->getArgument('--repos', '')); - $skipArchived = (bool) $this->getArgument('--skip-archived', false); - - if (!empty($specificRepos)) { - $names = preg_split('/[\s,]+/', $specificRepos); - return array_map(fn($n) => ['name' => trim($n), 'archived' => false], $names); - } - - $allRepos = $this->adapter->listOrgRepos($org, $skipArchived); - return array_filter($allRepos, fn($r) => !in_array($r['name'], ['mokocli', '.github-private'], true)); - } - - // ─── Cleanup operations ────────────────────────────────────────────── - - private function cleanBranches(string $org, string $repo, array &$results): bool - { - $changed = false; - try { - $branches = $this->api->get("/repos/{$org}/{$repo}/branches", ['per_page' => 100]); - } catch (\Exception $e) { - return false; - } - - foreach ($branches as $branch) { - $name = $branch['name'] ?? ''; - if (!str_starts_with($name, self::SYNC_PREFIX) || $name === self::CURRENT_BRANCH) { - continue; - } - - // Close open PRs on this branch - try { - $prs = $this->api->get("/repos/{$org}/{$repo}/pulls", [ - 'state' => 'open', 'head' => "{$org}:{$name}", 'per_page' => 10, - ]); - foreach ($prs as $pr) { - if (($pr['number'] ?? 0) > 0 && !$this->dryRun) { - $this->api->patch("/repos/{$org}/{$repo}/pulls/{$pr['number']}", ['state' => 'closed']); - } - $this->logMsg(" 🔒 Closed PR #{$pr['number']} ({$name})"); - $results['prs_closed']++; - $changed = true; - } - } catch (\Exception $e) { -/* non-fatal */ - } - - if (!$this->dryRun) { - try { - $this->api->delete("/repos/{$org}/{$repo}/git/refs/heads/{$name}"); - } catch (\Exception $e) { - continue; - } - } - $this->logMsg(" 🗑️ Deleted branch: {$name}"); - $results['branches_deleted']++; - $changed = true; - } - - return $changed; - } - - private function closeResolvedIssues(string $org, string $repo, array &$results): bool - { - $changed = false; - foreach (['standards-update', 'standards-drift'] as $label) { - try { - $issues = $this->api->get("/repos/{$org}/{$repo}/issues", [ - 'labels' => $label, 'state' => 'open', 'per_page' => 10, - ]); - } catch (\Exception $e) { - continue; - } - - foreach ($issues as $issue) { - $num = $issue['number'] ?? 0; - $body = $issue['body'] ?? ''; - if (preg_match('/\[#(\d+)\]/', $body, $m)) { - $prNum = (int) $m[1]; - try { - $pr = $this->api->get("/repos/{$org}/{$repo}/pulls/{$prNum}"); - if (!empty($pr['merged_at'])) { - if (!$this->dryRun) { - $this->api->patch("/repos/{$org}/{$repo}/issues/{$num}", [ - 'state' => 'closed', 'state_reason' => 'completed', - ]); - } - $this->logMsg(" ✅ Closed issue #{$num} (PR #{$prNum} merged)"); - $results['issues_closed']++; - $changed = true; - } - } catch (\Exception $e) { -/* non-fatal */ - } - } - } - } - return $changed; - } - - private function lockOldIssues(string $org, string $repo, array &$results): bool - { - $changed = false; - $cutoff = date('Y-m-d\TH:i:s\Z', strtotime('-30 days')); - - try { - $issues = $this->api->get("/repos/{$org}/{$repo}/issues", [ - 'state' => 'closed', 'per_page' => 50, 'sort' => 'updated', 'direction' => 'asc', - ]); - } catch (\Exception $e) { - return false; - } - - foreach ($issues as $issue) { - $closedAt = $issue['closed_at'] ?? ''; - $locked = $issue['locked'] ?? false; - $num = $issue['number'] ?? 0; - - if ($locked || $closedAt > $cutoff || $num === 0) { - continue; - } - - if (!$this->dryRun) { - try { - $this->api->put("/repos/{$org}/{$repo}/issues/{$num}/lock", [ - 'lock_reason' => 'resolved', - ]); - } catch (\Exception $e) { - continue; - } - } - $results['issues_locked']++; - $changed = true; - } - - if ($results['issues_locked'] > 0) { - $this->logMsg(" 🔒 Locked {$results['issues_locked']} old closed issue(s)"); - } - return $changed; - } - - private function deleteRetiredWorkflows(string $org, string $repo, array &$results): bool - { - $changed = false; - $defaultBranch = 'main'; - try { - $repoInfo = $this->api->get("/repos/{$org}/{$repo}"); - $defaultBranch = $repoInfo['default_branch'] ?? 'main'; - } catch (\Exception $e) { -/* fallback to main */ - } - - // Check both workflow directories for retired workflows (supports dual-platform repos) - $wfDirs = array_unique(['.github/workflows', '.mokogitea/workflows', $this->adapter->getWorkflowDir()]); - foreach (self::RETIRED_WORKFLOWS as $wf) { - foreach ($wfDirs as $wfDir) { - $path = "{$wfDir}/{$wf}"; - try { - $file = $this->api->get("/repos/{$org}/{$repo}/contents/{$path}"); - $sha = $file['sha'] ?? ''; - if (empty($sha)) { - continue; - } - - if (!$this->dryRun) { - $this->api->delete("/repos/{$org}/{$repo}/contents/{$path}", [ - 'message' => "chore: delete retired workflow {$wf}", - 'sha' => $sha, - 'branch' => $defaultBranch, - ]); - } - $this->logMsg(" Deleted retired: {$wf} (from {$wfDir})"); - $results['retired_files']++; - $changed = true; - } catch (\Exception $e) { - // File doesn't exist in this dir — skip - $this->api->resetCircuitBreaker(); - } - } - } - return $changed; - } - - private function cleanWorkflowRuns(string $org, string $repo, array &$results): bool - { - $changed = false; - foreach (['cancelled', 'stale'] as $status) { - try { - $runs = $this->api->get("/repos/{$org}/{$repo}/actions/runs", [ - 'status' => $status, 'per_page' => 100, - ]); - foreach (($runs['workflow_runs'] ?? []) as $run) { - $id = $run['id'] ?? 0; - if ($id > 0 && !$this->dryRun) { - try { - $this->api->delete("/repos/{$org}/{$repo}/actions/runs/{$id}"); - $results['runs_deleted']++; - $changed = true; - } catch (\Exception $e) { - $this->api->resetCircuitBreaker(); - } - } - } - } catch (\Exception $e) { -/* non-fatal */ - } - } - if ($results['runs_deleted'] > 0) { - $this->logMsg(" 🔄 Cleaned {$results['runs_deleted']} workflow run(s)"); - } - return $changed; - } - - private function cleanOldLogs(string $org, string $repo, array &$results): bool - { - $changed = false; - $days = (int) $this->getArgument('--log-days', '30'); - $cutoff = date('Y-m-d\TH:i:s\Z', strtotime("-{$days} days")); - - try { - $runs = $this->api->get("/repos/{$org}/{$repo}/actions/runs", [ - 'created' => "<{$cutoff}", 'per_page' => 100, - ]); - foreach (($runs['workflow_runs'] ?? []) as $run) { - $id = $run['id'] ?? 0; - if ($id > 0 && !$this->dryRun) { - try { - $this->api->delete("/repos/{$org}/{$repo}/actions/runs/{$id}/logs"); - $results['logs_deleted']++; - $changed = true; - } catch (\Exception $e) { - $this->api->resetCircuitBreaker(); - } - } - } - } catch (\Exception $e) { -/* non-fatal */ - } - - if ($results['logs_deleted'] > 0) { - $this->logMsg(" 📋 Cleaned {$results['logs_deleted']} old log(s)"); - } - return $changed; - } - - private function checkLabels(string $org, string $repo, array &$results): void - { - try { - $this->api->get("/repos/{$org}/{$repo}/labels/mokocli"); - } catch (\Exception $e) { - $this->logMsg(" ⚠️ Missing 'mokocli' label"); - $results['labels_missing']++; - $this->api->resetCircuitBreaker(); - } - } - - private function checkVersionDrift(string $org, string $repo, array &$results): void - { - try { - $file = $this->api->get("/repos/{$org}/{$repo}/contents/README.md"); - $content = base64_decode($file['content'] ?? ''); - if (preg_match('/^\s*VERSION:\s*(\d{2}\.\d{2}\.\d{2})/m', $content, $m)) { - $version = $m[1]; - - // Check manifest.xml for the tracked mokocli version - try { - $mokoFile = $this->api->get("/repos/{$org}/{$repo}/contents/.mokogitea/manifest.xml"); - $mokoContent = base64_decode($mokoFile['content'] ?? ''); - if (preg_match('/standards_version:\s*(\d{2}\.\d{2}\.\d{2})/m', $mokoContent, $vm)) { - if ($vm[1] !== self::VERSION) { - $this->logMsg(" ⚠️ Standards drift: {$vm[1]} (expected " . self::VERSION . ")"); - $results['version_drift']++; - } - } - } catch (\Exception $e) { - $this->api->resetCircuitBreaker(); - } - } - } catch (\Exception $e) { - $this->api->resetCircuitBreaker(); - } - } - - // ─── Helpers ───────────────────────────────────────────────────────── - - private function logMsg(string $message): void - { - if (!$this->quiet) { - echo $message . "\n"; - } - } - - private function errorMsg(string $message): void - { - fwrite(STDERR, $message . "\n"); - } -} - -$app = new RepoCleanup(); -exit($app->execute()); diff --git a/automation/server-autoheal.sh b/automation/server-autoheal.sh deleted file mode 100644 index 11a0739..0000000 --- a/automation/server-autoheal.sh +++ /dev/null @@ -1,678 +0,0 @@ -#!/usr/bin/env bash -# server-autoheal.sh - Auto-heal on restart + split backup management -# -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# DEFGROUP: MokoPlatform.Automation.ServerAutoheal -# INGROUP: MokoPlatform.Automation -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /automation/server-autoheal.sh -# BRIEF: Server auto-heal on unclean restart + split system/content backups -# -# Usage: -# server-autoheal.sh [options] -# -# Commands: -# boot-check Run at boot — auto-heals if no safe point exists -# set-safepoint Mark current state as safe (call before planned shutdown) -# backup-system Run a system backup (configs, packages, services) -# backup-content Run a content backup (site files, databases, uploads) -# cleanup Prune expired backups per retention policy -# status Show safe point and backup status -# -# Scheduling (cron): -# @reboot server-autoheal.sh boot-check -# 0 3 * * * server-autoheal.sh backup-system (daily at 3am) -# 0 */2 * * * server-autoheal.sh backup-content (every 2 hours) -# 30 */2 * * * server-autoheal.sh cleanup (30 min after content backup) - -set -euo pipefail - -# ────────────────────────────────────────────── -# Configuration — override via /etc/moko/autoheal.conf -# ────────────────────────────────────────────── -CONF_FILE="/etc/moko/autoheal.conf" -[[ -f "$CONF_FILE" ]] && source "$CONF_FILE" - -BACKUP_ROOT="${BACKUP_ROOT:-/var/backups/moko}" -SAFEPOINT_FILE="${SAFEPOINT_FILE:-/var/run/moko/safepoint}" -LOG_FILE="${LOG_FILE:-/var/log/moko/autoheal.log}" -LOCK_DIR="${LOCK_DIR:-/var/run/moko}" - -# System backup: configs, package lists, service state, cron -SYSTEM_BACKUP_DIR="${BACKUP_ROOT}/system" -SYSTEM_BACKUP_RETAIN="${SYSTEM_BACKUP_RETAIN:-7}" # keep 7 daily system backups - -# Content backup: web roots, databases, uploads -CONTENT_BACKUP_DIR="${BACKUP_ROOT}/content" -CONTENT_BACKUP_RETAIN_HOURS="${CONTENT_BACKUP_RETAIN_HOURS:-24}" # 1 day of content backups - -# Paths to back up — override these in /etc/moko/autoheal.conf -SYSTEM_PATHS="${SYSTEM_PATHS:-/etc/nginx /etc/php /etc/mysql /etc/cron.d /etc/systemd/system}" -CONTENT_PATHS="${CONTENT_PATHS:-/var/www}" -DB_NAMES="${DB_NAMES:-}" # space-separated list, empty = auto-detect all - -# ────────────────────────────────────────────── -# Helpers -# ────────────────────────────────────────────── -log() { - local level="$1"; shift - local ts - ts=$(date -u '+%Y-%m-%dT%H:%M:%SZ') - local msg="[$ts] [$level] $*" - echo "$msg" | tee -a "$LOG_FILE" >&2 -} - -ensure_dirs() { - mkdir -p "$SYSTEM_BACKUP_DIR" "$CONTENT_BACKUP_DIR" \ - "$LOCK_DIR" "$(dirname "$LOG_FILE")" -} - -acquire_lock() { - local lockfile="${LOCK_DIR}/autoheal-${1}.lock" - if [[ -f "$lockfile" ]]; then - local pid - pid=$(<"$lockfile") - if kill -0 "$pid" 2>/dev/null; then - log WARN "Another $1 operation is running (PID $pid), skipping" - exit 0 - fi - rm -f "$lockfile" - fi - echo $$ > "$lockfile" - trap "rm -f '$lockfile'" EXIT -} - -timestamp() { - date -u '+%Y%m%d_%H%M%S' -} - -# ────────────────────────────────────────────── -# Safe-point management -# ────────────────────────────────────────────── -cmd_set_safepoint() { - ensure_dirs - local ts - ts=$(timestamp) - cat > "$SAFEPOINT_FILE" </dev/null || echo "unknown") -set_by=${SUDO_USER:-$(whoami)} -EOF - log INFO "Safe point set at $ts by ${SUDO_USER:-$(whoami)}" -} - -cmd_clear_safepoint() { - rm -f "$SAFEPOINT_FILE" - log INFO "Safe point cleared" -} - -has_safepoint() { - [[ -f "$SAFEPOINT_FILE" ]] -} - -# ────────────────────────────────────────────── -# System backup (daily) -# ────────────────────────────────────────────── -cmd_backup_system() { - ensure_dirs - acquire_lock "system-backup" - - local ts - ts=$(timestamp) - local archive="${SYSTEM_BACKUP_DIR}/system_${ts}.tar.gz" - local manifest="${SYSTEM_BACKUP_DIR}/system_${ts}.manifest" - - log INFO "Starting system backup → $archive" - - # Collect existing paths only - local existing_paths=() - for p in $SYSTEM_PATHS; do - [[ -e "$p" ]] && existing_paths+=("$p") - done - - if [[ ${#existing_paths[@]} -eq 0 ]]; then - log WARN "No system paths found to back up" - return 1 - fi - - # Archive configs and system files - tar -czf "$archive" "${existing_paths[@]}" 2>/dev/null || true - - # Capture package list and service state as manifest - { - echo "=== PACKAGES ===" - if command -v dpkg &>/dev/null; then - dpkg --get-selections - elif command -v rpm &>/dev/null; then - rpm -qa --qf '%{NAME}\t%{VERSION}\n' - fi - echo "" - echo "=== ENABLED SERVICES ===" - if command -v systemctl &>/dev/null; then - systemctl list-unit-files --state=enabled --no-pager 2>/dev/null || true - fi - echo "" - echo "=== CRONTABS ===" - for user_home in /var/spool/cron/crontabs/*; do - [[ -f "$user_home" ]] && echo "--- $(basename "$user_home") ---" && cat "$user_home" - done 2>/dev/null || true - } > "$manifest" - - local size - size=$(du -sh "$archive" 2>/dev/null | cut -f1) - log INFO "System backup complete: $archive ($size)" - - # Prune old system backups (keep $SYSTEM_BACKUP_RETAIN) - local count - count=$(find "$SYSTEM_BACKUP_DIR" -name 'system_*.tar.gz' | wc -l) - if [[ "$count" -gt "$SYSTEM_BACKUP_RETAIN" ]]; then - local to_remove=$((count - SYSTEM_BACKUP_RETAIN)) - find "$SYSTEM_BACKUP_DIR" -name 'system_*.tar.gz' -printf '%T+ %p\n' \ - | sort | head -n "$to_remove" | awk '{print $2}' \ - | while read -r f; do - rm -f "$f" "${f%.tar.gz}.manifest" - log INFO "Pruned old system backup: $f" - done - fi -} - -# ────────────────────────────────────────────── -# Content backup (every 2 hours) -# ────────────────────────────────────────────── -cmd_backup_content() { - ensure_dirs - acquire_lock "content-backup" - - local ts - ts=$(timestamp) - local archive="${CONTENT_BACKUP_DIR}/content_${ts}.tar.gz" - local db_dump="${CONTENT_BACKUP_DIR}/content_${ts}.sql.gz" - - log INFO "Starting content backup → $archive" - - # Back up web content / uploads - local existing_paths=() - for p in $CONTENT_PATHS; do - [[ -e "$p" ]] && existing_paths+=("$p") - done - - if [[ ${#existing_paths[@]} -gt 0 ]]; then - tar -czf "$archive" "${existing_paths[@]}" 2>/dev/null || true - local size - size=$(du -sh "$archive" 2>/dev/null | cut -f1) - log INFO "Content files archived: $archive ($size)" - else - log WARN "No content paths found to back up" - fi - - # Database dump - if command -v mysqldump &>/dev/null || command -v mariadb-dump &>/dev/null; then - local dump_cmd="mysqldump" - command -v mariadb-dump &>/dev/null && dump_cmd="mariadb-dump" - - local databases=() - if [[ -n "$DB_NAMES" ]]; then - read -ra databases <<< "$DB_NAMES" - else - # Auto-detect: dump all databases except system ones - databases=($(${dump_cmd%dump} -N -e \ - "SELECT schema_name FROM information_schema.schemata - WHERE schema_name NOT IN ('information_schema','performance_schema','mysql','sys')" \ - 2>/dev/null | tr '\n' ' ')) || true - fi - - if [[ ${#databases[@]} -gt 0 ]]; then - $dump_cmd --single-transaction --routines --triggers \ - --databases "${databases[@]}" 2>/dev/null \ - | gzip > "$db_dump" - local db_size - db_size=$(du -sh "$db_dump" 2>/dev/null | cut -f1) - log INFO "Database dump complete: $db_dump ($db_size)" - else - log WARN "No databases found to dump" - fi - fi -} - -# ────────────────────────────────────────────── -# Cleanup — prune content backups older than retention -# ────────────────────────────────────────────── -cmd_cleanup() { - ensure_dirs - local before_count after_count - - # Content: keep only last 24 hours (1 day) - before_count=$(find "$CONTENT_BACKUP_DIR" -name 'content_*' -type f | wc -l) - find "$CONTENT_BACKUP_DIR" -name 'content_*' -type f \ - -mmin +$((CONTENT_BACKUP_RETAIN_HOURS * 60)) -delete 2>/dev/null || true - after_count=$(find "$CONTENT_BACKUP_DIR" -name 'content_*' -type f | wc -l) - local removed=$((before_count - after_count)) - [[ "$removed" -gt 0 ]] && log INFO "Pruned $removed content backup(s) older than ${CONTENT_BACKUP_RETAIN_HOURS}h" - - # System: keep N most recent (handled in backup-system, but double-check here) - before_count=$(find "$SYSTEM_BACKUP_DIR" -name 'system_*' -type f | wc -l) - local max_system_files=$((SYSTEM_BACKUP_RETAIN * 2)) # .tar.gz + .manifest - if [[ "$before_count" -gt "$max_system_files" ]]; then - local excess=$((before_count - max_system_files)) - find "$SYSTEM_BACKUP_DIR" -name 'system_*' -type f -printf '%T+ %p\n' \ - | sort | head -n "$excess" | awk '{print $2}' \ - | xargs -r rm -f - log INFO "Pruned excess system backups" - fi - - log INFO "Cleanup complete" -} - -# ────────────────────────────────────────────── -# Boot check — the auto-heal entry point -# ────────────────────────────────────────────── -cmd_boot_check() { - ensure_dirs - acquire_lock "boot-check" - - log INFO "=== Boot check started ===" - log INFO "Hostname: $(hostname), Kernel: $(uname -r)" - - if has_safepoint; then - log INFO "Safe point found — server was shut down cleanly" - log INFO "Clearing safe point for next cycle" - cmd_clear_safepoint - log INFO "=== Boot check passed (clean restart) ===" - return 0 - fi - - log WARN "NO safe point found — server restarted without clean shutdown" - log WARN "Initiating auto-heal sequence..." - - auto_heal - local rc=$? - - # Set safe point after successful heal - if [[ $rc -eq 0 ]]; then - cmd_set_safepoint - log INFO "=== Boot check complete (healed successfully) ===" - else - log ERROR "=== Boot check FAILED — manual intervention required ===" - fi - - return $rc -} - -# ────────────────────────────────────────────── -# Auto-heal strategy -# -# TODO: This is the core decision point. Implement the recovery -# steps that match your server's architecture. See guidance below. -# -# Trade-offs to consider: -# - Restore-from-backup: safest, but content may be up to 2h stale -# - Service-restart-only: faster, keeps current data, but won't fix -# corrupted configs or broken filesystem state -# - Hybrid: restart services first, verify health, only restore if -# health checks fail — best of both worlds but more complex -# -# The function receives no arguments. Use the latest system + content -# backups to restore if needed. Return 0 on success, 1 on failure. -# ────────────────────────────────────────────── -auto_heal() { - log INFO "Phase 1: Verify and repair filesystem" - # Check for common post-crash issues - repair_filesystem - - log INFO "Phase 2: Restore system configuration if corrupted" - restore_system_if_needed - - log INFO "Phase 3: Restart core services" - restart_services - - log INFO "Phase 4: Verify health" - if ! verify_health; then - log WARN "Health check failed after service restart — restoring from backup" - restore_from_backup - restart_services - - if ! verify_health; then - log ERROR "Health check still failing after restore — giving up" - return 1 - fi - fi - - log INFO "Auto-heal completed successfully" - return 0 -} - -# ────────────────────────────────────────────── -# Heal sub-steps -# ────────────────────────────────────────────── -repair_filesystem() { - # Fix common post-crash filesystem issues - # Clear stale PID/lock/socket files that prevent services from starting - local stale_files=( - /var/run/nginx.pid - /var/run/mysqld/mysqld.pid - /var/run/php-fpm.pid - /var/lib/mysql/*.pid - ) - for f in "${stale_files[@]}"; do - for expanded in $f; do - if [[ -f "$expanded" ]]; then - local pid - pid=$(<"$expanded") 2>/dev/null || true - if [[ -n "$pid" ]] && ! kill -0 "$pid" 2>/dev/null; then - rm -f "$expanded" - log INFO "Removed stale PID file: $expanded" - fi - fi - done - done - - # Fix permissions on critical dirs that may get mangled - [[ -d /var/run/mysqld ]] && chown mysql:mysql /var/run/mysqld 2>/dev/null || true - [[ -d /var/lib/php/sessions ]] && chmod 1733 /var/lib/php/sessions 2>/dev/null || true - - # Repair tmp/cache dirs - for d in /tmp /var/tmp; do - [[ -d "$d" ]] && chmod 1777 "$d" 2>/dev/null || true - done -} - -restore_system_if_needed() { - # Find latest system backup - local latest_system - latest_system=$(find "$SYSTEM_BACKUP_DIR" -name 'system_*.tar.gz' -printf '%T+ %p\n' \ - 2>/dev/null | sort -r | head -1 | awk '{print $2}') - - if [[ -z "$latest_system" ]]; then - log WARN "No system backup available to verify against" - return 0 - fi - - # Check if critical configs exist and are non-empty - local needs_restore=false - local critical_configs=("/etc/nginx/nginx.conf" "/etc/php" "/etc/mysql") - - for cfg in "${critical_configs[@]}"; do - if [[ -e "$cfg" ]]; then - # Config exists — check if it's a file and non-empty, or a directory - if [[ -f "$cfg" && ! -s "$cfg" ]]; then - log WARN "Critical config is empty: $cfg" - needs_restore=true - break - fi - fi - done - - if $needs_restore; then - log WARN "Restoring system config from $latest_system" - tar -xzf "$latest_system" -C / 2>/dev/null || { - log ERROR "System restore failed from $latest_system" - return 1 - } - log INFO "System config restored" - else - log INFO "System configs look intact — skipping restore" - fi -} - -restart_services() { - if ! command -v systemctl &>/dev/null; then - log WARN "systemctl not available — skipping service restart" - return 0 - fi - - local services=("mysql" "mariadb" "nginx" "apache2" "php-fpm" "php8.1-fpm" "php8.2-fpm" "php8.3-fpm") - - for svc in "${services[@]}"; do - if systemctl is-enabled "$svc" &>/dev/null; then - log INFO "Restarting $svc..." - systemctl restart "$svc" 2>/dev/null && \ - log INFO "$svc restarted OK" || \ - log WARN "$svc restart failed" - fi - done -} - -verify_health() { - local failures=0 - - # Check critical services are running - local services=("mysql" "mariadb" "nginx" "apache2") - for svc in "${services[@]}"; do - if systemctl is-enabled "$svc" &>/dev/null; then - if ! systemctl is-active "$svc" &>/dev/null; then - log WARN "Service not running: $svc" - ((failures++)) - fi - fi - done - - # Check if web server responds - if command -v curl &>/dev/null; then - if ! curl -sf -o /dev/null --max-time 10 "http://localhost/" 2>/dev/null; then - log WARN "Local web server not responding" - ((failures++)) - fi - fi - - # Check if database accepts connections - if command -v mysqladmin &>/dev/null; then - if ! mysqladmin ping --silent 2>/dev/null; then - log WARN "Database not responding to ping" - ((failures++)) - fi - fi - - [[ $failures -eq 0 ]] -} - -restore_from_backup() { - log WARN "=== Full restore from backup ===" - - # Restore system config - local latest_system - latest_system=$(find "$SYSTEM_BACKUP_DIR" -name 'system_*.tar.gz' -printf '%T+ %p\n' \ - 2>/dev/null | sort -r | head -1 | awk '{print $2}') - - if [[ -n "$latest_system" ]]; then - log INFO "Restoring system from $latest_system" - tar -xzf "$latest_system" -C / 2>/dev/null || \ - log ERROR "System restore failed" - fi - - # Restore content - local latest_content - latest_content=$(find "$CONTENT_BACKUP_DIR" -name 'content_*.tar.gz' -printf '%T+ %p\n' \ - 2>/dev/null | sort -r | head -1 | awk '{print $2}') - - if [[ -n "$latest_content" ]]; then - log INFO "Restoring content from $latest_content" - tar -xzf "$latest_content" -C / 2>/dev/null || \ - log ERROR "Content restore failed" - fi - - # Restore database - local latest_db - latest_db=$(find "$CONTENT_BACKUP_DIR" -name 'content_*.sql.gz' -printf '%T+ %p\n' \ - 2>/dev/null | sort -r | head -1 | awk '{print $2}') - - if [[ -n "$latest_db" ]]; then - log INFO "Restoring database from $latest_db" - local mysql_cmd="mysql" - command -v mariadb &>/dev/null && mysql_cmd="mariadb" - zcat "$latest_db" | $mysql_cmd 2>/dev/null || \ - log ERROR "Database restore failed" - fi -} - -# ────────────────────────────────────────────── -# Status -# ────────────────────────────────────────────── -cmd_status() { - echo "=== Moko Server Auto-Heal Status ===" - echo "" - - # Safe point - if has_safepoint; then - echo "Safe point: SET" - cat "$SAFEPOINT_FILE" | sed 's/^/ /' - else - echo "Safe point: NOT SET (will auto-heal on next boot)" - fi - echo "" - - # System backups - echo "System backups (${SYSTEM_BACKUP_DIR}):" - local sys_count - sys_count=$(find "$SYSTEM_BACKUP_DIR" -name 'system_*.tar.gz' 2>/dev/null | wc -l) - echo " Count: $sys_count (retain $SYSTEM_BACKUP_RETAIN)" - local latest_sys - latest_sys=$(find "$SYSTEM_BACKUP_DIR" -name 'system_*.tar.gz' -printf '%T+ %p\n' \ - 2>/dev/null | sort -r | head -1) - if [[ -n "$latest_sys" ]]; then - echo " Latest: $(echo "$latest_sys" | awk '{print $2}')" - echo " Timestamp: $(echo "$latest_sys" | awk '{print $1}')" - else - echo " Latest: (none)" - fi - echo "" - - # Content backups - echo "Content backups (${CONTENT_BACKUP_DIR}):" - local cnt_count - cnt_count=$(find "$CONTENT_BACKUP_DIR" -name 'content_*.tar.gz' 2>/dev/null | wc -l) - echo " Count: $cnt_count (retain ${CONTENT_BACKUP_RETAIN_HOURS}h)" - local latest_cnt - latest_cnt=$(find "$CONTENT_BACKUP_DIR" -name 'content_*.tar.gz' -printf '%T+ %p\n' \ - 2>/dev/null | sort -r | head -1) - if [[ -n "$latest_cnt" ]]; then - echo " Latest: $(echo "$latest_cnt" | awk '{print $2}')" - echo " Timestamp: $(echo "$latest_cnt" | awk '{print $1}')" - else - echo " Latest: (none)" - fi - echo "" - - # Disk usage - echo "Backup disk usage:" - du -sh "$SYSTEM_BACKUP_DIR" "$CONTENT_BACKUP_DIR" 2>/dev/null | sed 's/^/ /' -} - -# ────────────────────────────────────────────── -# Install helper — sets up cron + systemd -# ────────────────────────────────────────────── -cmd_install() { - local script_path - script_path=$(readlink -f "$0") - - echo "Installing Moko Auto-Heal..." - - # Create config directory - mkdir -p /etc/moko "$(dirname "$LOG_FILE")" "$LOCK_DIR" - - # Write example config if none exists - if [[ ! -f "$CONF_FILE" ]]; then - cat > "$CONF_FILE" <<'CONF' -# /etc/moko/autoheal.conf — Server auto-heal configuration -# Uncomment and modify as needed - -# BACKUP_ROOT="/var/backups/moko" -# SAFEPOINT_FILE="/var/run/moko/safepoint" -# LOG_FILE="/var/log/moko/autoheal.log" - -# System backup paths (space-separated) -# SYSTEM_PATHS="/etc/nginx /etc/php /etc/mysql /etc/cron.d /etc/systemd/system" - -# Content backup paths (space-separated) -# CONTENT_PATHS="/var/www" - -# Database names (space-separated, empty = auto-detect all) -# DB_NAMES="" - -# Retention -# SYSTEM_BACKUP_RETAIN=7 # daily backups to keep -# CONTENT_BACKUP_RETAIN_HOURS=24 # hours of content backups to keep -CONF - echo " Created config: $CONF_FILE" - fi - - # Install cron jobs - local cron_file="/etc/cron.d/moko-autoheal" - cat > "$cron_file" < "$shutdown_hook" <&2 - echo "Run '$0 help' for usage" >&2 - exit 1 - ;; - esac -} - -main "$@" diff --git a/automation/update_dependencies.php b/automation/update_dependencies.php deleted file mode 100644 index 0f1f77e..0000000 --- a/automation/update_dependencies.php +++ /dev/null @@ -1,633 +0,0 @@ -#!/usr/bin/env php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: MokoPlatform.Automation - * INGROUP: MokoPlatform.Scripts - * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli - * PATH: /automation/update_dependencies.php - * VERSION: 09.37.07 - * BRIEF: Cross-repo dependency update automation — scan, update, PR, auto-merge - */ - -declare(strict_types=1); - -require_once __DIR__ . '/../vendor/autoload.php'; - -use MokoCli\{ - ApiClient, - AuditLogger, - CheckpointManager, - CircuitBreakerOpen, - CliFramework, - Config, - GitPlatformAdapter, - PlatformAdapterFactory, - RateLimitExceeded -}; - -/** - * Cross-Repo Dependency Update Automation - * - * Scans org repos for outdated Composer/npm dependencies, creates PRs with - * changelogs, and optionally auto-merges safe patch updates. - * - * @see https://git.mokoconsulting.tech/MokoConsulting/mokocli/issues/149 - */ -class UpdateDependencies extends CliFramework -{ - public const VERSION = '01.00.00'; - - private const BRANCH_PREFIX = 'chore/deps-update'; - - private ApiClient $api; - private GitPlatformAdapter $adapter; - private AuditLogger $logger; - private CheckpointManager $checkpoints; - - /** Summary counters. */ - private int $reposScanned = 0; - private int $reposUpdated = 0; - private int $prsCreated = 0; - private int $autoMerged = 0; - private int $reposFailed = 0; - - protected function configure(): void - { - $this->setDescription('Cross-repo dependency update automation'); - $this->addArgument('--org', 'Organization to scan', 'MokoConsulting'); - $this->addArgument('--repos', 'Comma-separated list of specific repos', ''); - $this->addArgument('--exclude', 'Comma-separated list of repos to exclude', ''); - $this->addArgument('--skip-archived', 'Skip archived repositories', true); - $this->addArgument('--type', 'Dependency type: composer, npm, or all', 'all'); - $this->addArgument('--patch-only', 'Only update patch versions (safe updates)', false); - $this->addArgument('--auto-merge', 'Auto-merge PRs with only patch updates', false); - $this->addArgument('--resume', 'Resume from checkpoint', false); - } - - protected function run(): int - { - $this->log("Dependency Update Automation v" . self::VERSION, 'INFO'); - - if (!$this->initComponents()) { - return self::EXIT_FAILURE; - } - - $org = $this->getArgument('--org', 'MokoConsulting'); - $depType = strtolower($this->getArgument('--type', 'all')); - $patchOnly = $this->getArgument('--patch-only', false); - $autoMerge = $this->getArgument('--auto-merge', false); - - // ── Gather repos ───────────────────────────────────────────────── - $repos = $this->gatherRepos($org); - if ($repos === null) { - return self::EXIT_FAILURE; - } - - $total = count($repos); - $this->log("Found {$total} repositories to scan", 'INFO'); - - // ── Resume support ─────────────────────────────────────────────── - $completed = []; - if ($this->getArgument('--resume', false)) { - $checkpoint = $this->checkpoints->load('deps_update'); - if ($checkpoint) { - $completed = $checkpoint['completed'] ?? []; - $this->log("Resuming — skipping " . count($completed) . " already-processed repos", 'INFO'); - } - } - - // ── Process each repo ──────────────────────────────────────────── - $this->section('Scanning repositories for outdated dependencies'); - - foreach ($repos as $i => $repo) { - $repoName = $repo['name']; - $this->progress($i + 1, $total, $repoName); - - if (in_array($repoName, $completed, true)) { - continue; - } - - try { - $this->processRepo($org, $repoName, $depType, $patchOnly, $autoMerge); - $completed[] = $repoName; - - $this->checkpoints->save('deps_update', ['completed' => $completed]); - } catch (RateLimitExceeded $e) { - $this->log("Rate limit hit — checkpoint saved", 'WARNING'); - break; - } catch (CircuitBreakerOpen $e) { - $this->log("Circuit breaker open — checkpoint saved", 'WARNING'); - break; - } catch (\Exception $e) { - $this->log("Failed {$repoName}: {$e->getMessage()}", 'ERROR'); - $this->reposFailed++; - } - } - - $this->progress($total, $total, '', true); - - // ── Summary ────────────────────────────────────────────────────── - $this->section('Summary'); - $this->printSummary( - $this->reposScanned - $this->reposFailed, - $this->reposFailed, - $this->elapsed() - ); - - $this->log("Repos scanned: {$this->reposScanned}", 'INFO'); - $this->log("Repos updated: {$this->reposUpdated}", 'INFO'); - $this->log("PRs created: {$this->prsCreated}", 'INFO'); - if ($autoMerge) { - $this->log("Auto-merged: {$this->autoMerged}", 'INFO'); - } - - if (count($completed) === $total) { - $this->checkpoints->clear('deps_update'); - } - - return $this->reposFailed > 0 ? self::EXIT_FAILURE : self::EXIT_SUCCESS; - } - - // ── Component init ─────────────────────────────────────────────────── - - private function initComponents(): bool - { - try { - $config = new Config(); - $this->api = new ApiClient($config); - $this->adapter = PlatformAdapterFactory::create($this->api, $config); - $this->logger = new AuditLogger(); - $this->checkpoints = new CheckpointManager(); - return true; - } catch (\Exception $e) { - $this->log("Failed to initialise: {$e->getMessage()}", 'ERROR'); - return false; - } - } - - // ── Repo gathering ─────────────────────────────────────────────────── - - private function gatherRepos(string $org): ?array - { - $specificRepos = array_filter(explode(',', $this->getArgument('--repos', ''))); - $excludeRepos = array_filter(explode(',', $this->getArgument('--exclude', ''))); - $skipArchived = $this->getArgument('--skip-archived', true); - - // Default exclusions - $excludeRepos = array_merge($excludeRepos, [ - 'mokocli', '.mokogitea-private', 'org-profile', - ]); - - try { - $repos = $this->adapter->listOrgRepos($org, $skipArchived); - } catch (\Exception $e) { - $this->log("Failed to list repos: {$e->getMessage()}", 'ERROR'); - return null; - } - - if (!empty($specificRepos)) { - $repos = array_filter($repos, fn($r) => in_array($r['name'], $specificRepos, true)); - } - if (!empty($excludeRepos)) { - $repos = array_filter($repos, fn($r) => !in_array($r['name'], $excludeRepos, true)); - } - - return array_values($repos); - } - - // ── Per-repo processing ────────────────────────────────────────────── - - private function processRepo( - string $org, - string $repoName, - string $depType, - bool $patchOnly, - bool $autoMerge - ): void { - $this->reposScanned++; - - $hasComposer = ($depType === 'all' || $depType === 'composer'); - $hasNpm = ($depType === 'all' || $depType === 'npm'); - - $outdated = []; - - // ── Composer ───────────────────────────────────────────────── - if ($hasComposer) { - $composerOutdated = $this->scanComposer($org, $repoName, $patchOnly); - if ($composerOutdated !== null) { - $outdated['composer'] = $composerOutdated; - } - } - - // ── npm ────────────────────────────────────────────────────── - if ($hasNpm) { - $npmOutdated = $this->scanNpm($org, $repoName, $patchOnly); - if ($npmOutdated !== null) { - $outdated['npm'] = $npmOutdated; - } - } - - if (empty($outdated)) { - return; - } - - // Check if there's already an open deps PR - if ($this->hasExistingDepsPR($org, $repoName)) { - $this->log(" {$repoName}: existing deps PR found — skipping", 'INFO'); - return; - } - - $this->reposUpdated++; - - // ── Create PR ──────────────────────────────────────────────── - $totalUpdates = 0; - $allPatchOnly = true; - - foreach ($outdated as $type => $packages) { - $totalUpdates += count($packages); - foreach ($packages as $pkg) { - if (!$this->isPatchUpdate($pkg['current'] ?? '', $pkg['latest'] ?? '')) { - $allPatchOnly = false; - } - } - } - - $title = "chore(deps): update {$totalUpdates} " . ($totalUpdates === 1 ? 'dependency' : 'dependencies'); - $body = $this->buildPrBody($repoName, $outdated); - $branch = self::BRANCH_PREFIX . '-' . date('Y-m-d'); - - if ($this->dryRun) { - $this->log("[dry-run] Would create PR in {$repoName}: {$title}", 'INFO'); - foreach ($outdated as $type => $packages) { - foreach ($packages as $pkg) { - $this->log(" [{$type}] {$pkg['name']}: {$pkg['current']} → {$pkg['latest']}", 'INFO'); - } - } - return; - } - - try { - // Clone repo, run updates, push branch - $prNumber = $this->cloneUpdateAndPR($org, $repoName, $branch, $title, $body, $outdated); - - if ($prNumber > 0) { - $this->prsCreated++; - $this->log(" {$repoName}: PR #{$prNumber} created", 'INFO'); - - // Auto-merge if all updates are patch-level - if ($autoMerge && $allPatchOnly && $prNumber > 0) { - $this->tryAutoMerge($org, $repoName, $prNumber); - } - } - } catch (\Exception $e) { - $this->log(" {$repoName}: PR creation failed — {$e->getMessage()}", 'ERROR'); - } - } - - // ── Composer scanning ──────────────────────────────────────────────── - - private function scanComposer(string $org, string $repoName, bool $patchOnly): ?array - { - // Check if repo has composer.json - try { - $this->adapter->getFileContents($org, $repoName, 'composer.json'); - } catch (\Exception $e) { - return null; - } - - // Check if repo has composer.lock - try { - $this->adapter->getFileContents($org, $repoName, 'composer.lock'); - } catch (\Exception $e) { - return null; - } - - // Clone to temp dir and run composer outdated - $tmpDir = sys_get_temp_dir() . '/moko_deps_' . $repoName . '_' . getmypid(); - @mkdir($tmpDir, 0700, true); - - try { - $cloneUrl = $this->adapter->getCloneUrl($org, $repoName); - $cmd = sprintf( - 'git clone --depth 1 --quiet %s %s 2>/dev/null', - escapeshellarg($cloneUrl), - escapeshellarg($tmpDir) - ); - exec($cmd, $output, $exitCode); - if ($exitCode !== 0) { - return null; - } - - // Run composer outdated - $flags = $patchOnly ? '--minor-only' : ''; - $cmd = sprintf( - 'composer outdated --format=json --no-interaction %s --working-dir=%s 2>/dev/null', - $flags, - escapeshellarg($tmpDir) - ); - $json = shell_exec($cmd); - if ($json === null || $json === '') { - return null; - } - - $data = json_decode($json, true); - $installed = $data['installed'] ?? []; - - if (empty($installed)) { - return null; - } - - $outdated = []; - foreach ($installed as $pkg) { - // Skip abandoned/dev packages - if (($pkg['abandoned'] ?? false) || str_starts_with($pkg['version'] ?? '', 'dev-')) { - continue; - } - - $outdated[] = [ - 'name' => $pkg['name'] ?? '', - 'current' => $pkg['version'] ?? '', - 'latest' => $pkg['latest'] ?? '', - 'status' => $pkg['latest-status'] ?? 'unknown', - ]; - } - - return empty($outdated) ? null : $outdated; - } finally { - // Cleanup - if (is_dir($tmpDir)) { - exec(sprintf('rm -rf %s', escapeshellarg($tmpDir))); - } - } - } - - // ── npm scanning ───────────────────────────────────────────────────── - - private function scanNpm(string $org, string $repoName, bool $patchOnly): ?array - { - // Check if repo has package.json - try { - $this->adapter->getFileContents($org, $repoName, 'package.json'); - } catch (\Exception $e) { - return null; - } - - // Check for lock file - $hasLock = false; - foreach (['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml'] as $lockFile) { - try { - $this->adapter->getFileContents($org, $repoName, $lockFile); - $hasLock = true; - break; - } catch (\Exception $e) { - // continue - } - } - - if (!$hasLock) { - return null; - } - - $tmpDir = sys_get_temp_dir() . '/moko_deps_npm_' . $repoName . '_' . getmypid(); - @mkdir($tmpDir, 0700, true); - - try { - $cloneUrl = $this->adapter->getCloneUrl($org, $repoName); - exec(sprintf('git clone --depth 1 --quiet %s %s 2>/dev/null', - escapeshellarg($cloneUrl), escapeshellarg($tmpDir))); - - if (!file_exists("{$tmpDir}/package.json")) { - return null; - } - - // Install deps first (needed for npm outdated) - exec(sprintf('cd %s && npm install --silent 2>/dev/null', escapeshellarg($tmpDir))); - - $json = shell_exec(sprintf('cd %s && npm outdated --json 2>/dev/null', escapeshellarg($tmpDir))); - if ($json === null || $json === '' || $json === '{}') { - return null; - } - - $data = json_decode($json, true); - if (!is_array($data) || empty($data)) { - return null; - } - - $outdated = []; - foreach ($data as $name => $info) { - $current = $info['current'] ?? ''; - $wanted = $info['wanted'] ?? ''; - $latest = $info['latest'] ?? ''; - $target = $patchOnly ? $wanted : $latest; - - if ($current === $target || $target === '') { - continue; - } - - $outdated[] = [ - 'name' => $name, - 'current' => $current, - 'latest' => $target, - 'status' => ($current === $wanted) ? 'up-to-date' : 'outdated', - ]; - } - - return empty($outdated) ? null : $outdated; - } finally { - if (is_dir($tmpDir)) { - exec(sprintf('rm -rf %s', escapeshellarg($tmpDir))); - } - } - } - - // ── PR creation ────────────────────────────────────────────────────── - - private function cloneUpdateAndPR( - string $org, - string $repoName, - string $branch, - string $title, - string $body, - array $outdated - ): int { - $tmpDir = sys_get_temp_dir() . '/moko_deps_pr_' . $repoName . '_' . getmypid(); - @mkdir($tmpDir, 0700, true); - - try { - $cloneUrl = $this->adapter->getCloneUrl($org, $repoName); - exec(sprintf('git clone --quiet %s %s 2>/dev/null', - escapeshellarg($cloneUrl), escapeshellarg($tmpDir))); - - // Create branch - exec(sprintf('git -C %s checkout -b %s 2>/dev/null', - escapeshellarg($tmpDir), escapeshellarg($branch))); - - $updated = false; - - // Run composer update if needed - if (isset($outdated['composer'])) { - $packages = array_column($outdated['composer'], 'name'); - $cmd = sprintf( - 'cd %s && composer update %s --no-interaction --quiet 2>/dev/null', - escapeshellarg($tmpDir), - implode(' ', array_map('escapeshellarg', $packages)) - ); - exec($cmd, $output, $exitCode); - if ($exitCode === 0) { - $updated = true; - } - } - - // Run npm update if needed - if (isset($outdated['npm'])) { - $packages = array_column($outdated['npm'], 'name'); - $cmd = sprintf( - 'cd %s && npm update %s --save 2>/dev/null', - escapeshellarg($tmpDir), - implode(' ', array_map('escapeshellarg', $packages)) - ); - exec($cmd, $output, $exitCode); - if ($exitCode === 0) { - $updated = true; - } - } - - if (!$updated) { - return 0; - } - - // Commit and push - exec(sprintf('git -C %s config user.email "gitea-actions[bot]@mokoconsulting.tech"', escapeshellarg($tmpDir))); - exec(sprintf('git -C %s config user.name "gitea-actions[bot]"', escapeshellarg($tmpDir))); - exec(sprintf('git -C %s add -A', escapeshellarg($tmpDir))); - - // Check if there are actual changes - exec(sprintf('git -C %s diff --cached --quiet', escapeshellarg($tmpDir)), $output, $diffExit); - if ($diffExit === 0) { - return 0; // No changes - } - - exec(sprintf('git -C %s commit -m %s', - escapeshellarg($tmpDir), - escapeshellarg($title . " [skip ci]"))); - exec(sprintf('git -C %s push origin %s 2>/dev/null', - escapeshellarg($tmpDir), escapeshellarg($branch)), $output, $pushExit); - - if ($pushExit !== 0) { - $this->log(" {$repoName}: push failed", 'ERROR'); - return 0; - } - - // Create PR via API - $defaultBranch = $this->getDefaultBranch($org, $repoName); - $pr = $this->adapter->createPullRequest( - $org, $repoName, $title, $branch, $defaultBranch, $body, [ - 'labels' => ['dependencies'], - ] - ); - - return (int) ($pr['number'] ?? 0); - } finally { - if (is_dir($tmpDir)) { - exec(sprintf('rm -rf %s', escapeshellarg($tmpDir))); - } - } - } - - // ── Auto-merge ─────────────────────────────────────────────────────── - - private function tryAutoMerge(string $org, string $repoName, int $prNumber): void - { - try { - $this->api->put( - "/repos/{$org}/{$repoName}/pulls/{$prNumber}/merge", - ['Do' => 'squash', 'merge_message_field' => 'chore(deps): auto-merge patch updates'] - ); - $this->autoMerged++; - $this->log(" {$repoName}: PR #{$prNumber} auto-merged", 'INFO'); - } catch (\Exception $e) { - $this->log(" {$repoName}: auto-merge failed — {$e->getMessage()}", 'WARNING'); - } - } - - // ── Helpers ─────────────────────────────────────────────────────────── - - private function hasExistingDepsPR(string $org, string $repoName): bool - { - try { - $prs = $this->adapter->listPullRequests($org, $repoName, ['state' => 'open']); - foreach ($prs as $pr) { - if (str_starts_with($pr['head']['ref'] ?? '', self::BRANCH_PREFIX)) { - return true; - } - } - } catch (\Exception $e) { - // Ignore — proceed with creating PR - } - return false; - } - - private function getDefaultBranch(string $org, string $repoName): string - { - try { - $repo = $this->api->get("/repos/{$org}/{$repoName}"); - return $repo['default_branch'] ?? 'main'; - } catch (\Exception $e) { - return 'main'; - } - } - - private function isPatchUpdate(string $current, string $latest): bool - { - $cur = explode('.', ltrim($current, 'v')); - $lat = explode('.', ltrim($latest, 'v')); - - if (count($cur) < 3 || count($lat) < 3) { - return false; - } - - // Same major and minor, only patch differs - return $cur[0] === $lat[0] && $cur[1] === $lat[1] && $cur[2] !== $lat[2]; - } - - private function buildPrBody(string $repoName, array $outdated): string - { - $lines = [ - "## Dependency Updates", - "", - "**Repository**: `{$repoName}`", - "**Scanned**: " . date('Y-m-d H:i:s'), - "", - ]; - - foreach ($outdated as $type => $packages) { - $lines[] = "### " . ucfirst($type); - $lines[] = ""; - $lines[] = "| Package | Current | Latest | Type |"; - $lines[] = "|---------|---------|--------|------|"; - - foreach ($packages as $pkg) { - $updateType = $this->isPatchUpdate($pkg['current'], $pkg['latest']) ? 'patch' : 'minor/major'; - $lines[] = "| `{$pkg['name']}` | {$pkg['current']} | {$pkg['latest']} | {$updateType} |"; - } - - $lines[] = ""; - } - - $lines[] = "---"; - $lines[] = "*Auto-generated by `moko deps:update`*"; - - return implode("\n", $lines); - } -} - -$script = new UpdateDependencies('update_dependencies', 'Cross-repo dependency update automation'); -exit($script->execute()); diff --git a/cli/audit_query.php b/cli/audit_query.php index da0c527..43a5558 100644 --- a/cli/audit_query.php +++ b/cli/audit_query.php @@ -14,8 +14,8 @@ * (at your option) any later version. * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.CLI - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.CLI + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/audit_query.php * BRIEF: Search, filter, and export audit logs diff --git a/cli/branch_protect_org.php b/cli/branch_protect_org.php new file mode 100644 index 0000000..46fd057 --- /dev/null +++ b/cli/branch_protect_org.php @@ -0,0 +1,158 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Enforce branch protection rules across all repos in the org. + * + * Usage: + * php cli/branch_protect_org.php --token TOKEN [--org MokoConsulting] [--dry-run] + * + * Branch flow: feature/* -> dev -> rc -> main + * main, dev, rc: push whitelist only (no direct push) + * alpha, beta: push whitelist only (pre-release) + */ + +declare(strict_types=1); + +$options = getopt('', ['token:', 'org:', 'api-base:', 'dry-run', 'help']); + +if (isset($options['help']) || empty($options['token'])) { + echo "Usage: php cli/branch_protect_org.php --token TOKEN [--org ORG] [--api-base URL] [--dry-run]\n"; + echo "\n"; + echo "Options:\n"; + echo " --token Gitea API token (required)\n"; + echo " --org Organization name (default: MokoConsulting)\n"; + echo " --api-base API base URL (default: https://git.mokoconsulting.tech/api/v1)\n"; + echo " --dry-run Show what would be changed without making changes\n"; + exit(0); +} + +$token = $options['token']; +$org = $options['org'] ?? 'MokoConsulting'; +$apiBase = rtrim($options['api-base'] ?? 'https://git.mokoconsulting.tech/api/v1', '/'); +$dryRun = isset($options['dry-run']); + +// Protected branches and their rules +$branchRules = [ + // Primary branches (flow: feature/* -> dev -> rc -> main) + 'main' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']], + 'dev' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']], + 'rc' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']], + 'beta' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']], + 'alpha' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']], + + // Synonyms (prevent bypass via alternate names) + 'master' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']], + 'develop' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']], + 'release' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']], + 'production' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']], + 'stable' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']], + 'staging' => ['enable_push' => true, 'enable_push_whitelist' => true, 'push_whitelist_usernames' => ['jmiller']], +]; + +function apiRequest(string $method, string $url, string $token, ?array $body = null): array +{ + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_CUSTOMREQUEST => $method, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + 'Authorization: token ' . $token, + 'Content-Type: application/json', + 'Accept: application/json', + ], + CURLOPT_TIMEOUT => 30, + ]); + + if ($body !== null) { + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body)); + } + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + return [ + 'status' => $httpCode, + 'data' => json_decode($response, true) ?: [], + ]; +} + +// 1. List all org repos +echo "Fetching repos for {$org}...\n"; +$page = 1; +$repos = []; + +do { + $result = apiRequest('GET', "{$apiBase}/orgs/{$org}/repos?limit=50&page={$page}", $token); + $batch = $result['data']; + $repos = array_merge($repos, $batch); + $page++; +} while (count($batch) === 50); + +echo sprintf("Found %d repos\n\n", count($repos)); + +$summary = ['protected' => 0, 'added' => 0, 'skipped' => 0, 'errors' => 0]; + +foreach ($repos as $repo) { + $repoName = $repo['name']; + + if ($repo['archived'] ?? false) { + continue; + } + + // Get existing protections + $existing = apiRequest('GET', "{$apiBase}/repos/{$org}/{$repoName}/branch_protections", $token); + $existingNames = array_map(fn($p) => $p['branch_name'] ?? '', $existing['data'] ?: []); + + $added = []; + $skipped = []; + + foreach ($branchRules as $branch => $rules) { + if (in_array($branch, $existingNames, true)) { + $skipped[] = $branch; + $summary['skipped']++; + continue; + } + + if ($dryRun) { + $added[] = $branch; + $summary['added']++; + continue; + } + + $body = array_merge($rules, ['branch_name' => $branch]); + $result = apiRequest('POST', "{$apiBase}/repos/{$org}/{$repoName}/branch_protections", $token, $body); + + if ($result['status'] >= 200 && $result['status'] < 300) { + $added[] = $branch; + $summary['added']++; + } elseif ($result['status'] === 422) { + $skipped[] = $branch; + $summary['skipped']++; + } else { + $added[] = "{$branch}(ERR:{$result['status']})"; + $summary['errors']++; + } + } + + $summary['protected']++; + + if (!empty($added)) { + $prefix = $dryRun ? '[DRY-RUN] ' : ''; + echo sprintf(" %s%-35s added: %s\n", $prefix, $repoName, implode(', ', $added)); + } +} + +echo "\n"; +echo sprintf("Summary: %d repos, %d rules added, %d already existed, %d errors\n", + $summary['protected'], $summary['added'], $summary['skipped'], $summary['errors']); + +if ($dryRun) { + echo "\n(Dry run - no changes made)\n"; +} diff --git a/cli/branch_rename.php b/cli/branch_rename.php index 3cd0018..2441091 100644 --- a/cli/branch_rename.php +++ b/cli/branch_rename.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/branch_rename.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Rename a git branch via Gitea API (create new, update PR, delete old) */ @@ -64,26 +64,46 @@ class BranchRenameCli extends CliFramework return 1; } - // Step 2: Delete target branch if it already exists + // Step 2: Point the target branch at the source commit. + // If the target already exists (e.g. a protected `rc` branch that cannot be + // deleted), force-update its ref in place instead of delete+recreate: deleting a + // protected branch fails, which then makes the recreate return HTTP 409. + $sourceSha = ''; + if (isset($check['body']['commit']['id']) && is_string($check['body']['commit']['id'])) { + $sourceSha = $check['body']['commit']['id']; + } $targetCheck = $this->apiRequest('GET', "{$apiBase}/branches/{$to}", $headers); if ($targetCheck['code'] === 200) { - echo "Target branch '{$to}' already exists — deleting\n"; + echo "Target branch '{$to}' already exists - force-updating to {$from} +"; if (!$this->dryRun) { - $this->apiRequest('DELETE', "{$apiBase}/branches/{$to}", $headers); + if ($sourceSha === '') { + $this->log('ERROR', "Cannot resolve HEAD commit of source '{$from}'"); + return 1; + } + $ref = $this->apiRequest('PATCH', "{$apiBase}/git/refs/heads/{$to}", $headers, [ + 'sha' => $sourceSha, + 'force' => true, + ]); + if ($ref['code'] < 200 || $ref['code'] >= 300) { + $this->log('ERROR', "Failed to force-update '{$to}': HTTP {$ref['code']} (needs force-push perm)"); + $this->log('ERROR', json_encode($ref['body'])); + return 1; + } } - } - - // Step 3: Create new branch from source - echo "Creating branch: {$to} (from {$from})\n"; - if (!$this->dryRun) { - $create = $this->apiRequest('POST', "{$apiBase}/branches", $headers, [ - 'new_branch_name' => $to, - 'old_branch_name' => $from, - ]); - if ($create['code'] < 200 || $create['code'] >= 300) { - $this->log('ERROR', "Failed to create branch '{$to}': HTTP {$create['code']}"); - $this->log('ERROR', json_encode($create['body'])); - return 1; + } else { + echo "Creating branch: {$to} (from {$from}) +"; + if (!$this->dryRun) { + $create = $this->apiRequest('POST', "{$apiBase}/branches", $headers, [ + 'new_branch_name' => $to, + 'old_branch_name' => $from, + ]); + if ($create['code'] < 200 || $create['code'] >= 300) { + $this->log('ERROR', "Failed to create branch '{$to}': HTTP {$create['code']}"); + $this->log('ERROR', json_encode($create['body'])); + return 1; + } } } diff --git a/cli/bulk_workflow_push.php b/cli/bulk_workflow_push.php index f79f8d0..9fba428 100644 --- a/cli/bulk_workflow_push.php +++ b/cli/bulk_workflow_push.php @@ -12,7 +12,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/bulk_workflow_push.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Push a workflow file to all governed repos via the Gitea Contents API */ diff --git a/cli/bulk_workflow_trigger.php b/cli/bulk_workflow_trigger.php index 43bbcf9..f84c58a 100644 --- a/cli/bulk_workflow_trigger.php +++ b/cli/bulk_workflow_trigger.php @@ -12,7 +12,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/bulk_workflow_trigger.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Trigger a workflow across multiple repos at once */ diff --git a/automation/ci-issue-reporter.sh b/cli/ci_issue_reporter.sh similarity index 79% rename from automation/ci-issue-reporter.sh rename to cli/ci_issue_reporter.sh index c13d00f..63bfcad 100644 --- a/automation/ci-issue-reporter.sh +++ b/cli/ci_issue_reporter.sh @@ -6,10 +6,10 @@ # # FILE INFORMATION # DEFGROUP: Automation.CI -# INGROUP: mokocli.Automation -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /automation/ci-issue-reporter.sh -# VERSION: 09.23.00 +# INGROUP: mokocli.CLI +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI +# PATH: /cli/ci_issue_reporter.sh +# VERSION: 10.00.00 # BRIEF: Creates or updates a Gitea issue when a CI gate fails. # Deduplicates by searching open issues with the "ci-auto" label # whose title matches the gate. If a matching issue exists, a comment @@ -19,10 +19,10 @@ set -euo pipefail # ── Defaults ──────────────────────────────────────────────────────────────── -GITEA_URL="${GITEA_URL:-https://git.mokoconsulting.tech}" -GITEA_TOKEN="${GITEA_TOKEN:-}" +MOKOGITEA_URL="${MOKOGITEA_URL:-https://git.mokoconsulting.tech}" +MOKOGITEA_TOKEN="${MOKOGITEA_TOKEN:-}" REPO="${GITHUB_REPOSITORY:-}" -RUN_URL="${GITHUB_SERVER_URL:-${GITEA_URL}}/${REPO}/actions/runs/${GITHUB_RUN_ID:-0}" +RUN_URL="${GITHUB_SERVER_URL:-${MOKOGITEA_URL}}/${REPO}/actions/runs/${GITHUB_RUN_ID:-0}" LABEL_NAME="ci-auto" LABEL_COLOR="#e11d48" @@ -34,7 +34,7 @@ WORKFLOW="" # ── Parse arguments ───────────────────────────────────────────────────────── usage() { cat </dev/null || echo "000") if [[ "$exists" == "200" ]]; then - # Check if label already exists local found found=$(curl -sf \ - -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Authorization: token ${MOKOGITEA_TOKEN}" \ "${API}/labels" 2>/dev/null \ | grep -o "\"name\":\"${LABEL_NAME}\"" || true) if [[ -z "$found" ]]; then curl -sf -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Authorization: token ${MOKOGITEA_TOKEN}" \ -H "Content-Type: application/json" \ "${API}/labels" \ -d "{\"name\":\"${LABEL_NAME}\",\"color\":\"${LABEL_COLOR}\",\"description\":\"Auto-created by CI issue reporter\"}" \ @@ -108,17 +107,15 @@ ensure_label() { # ── Search for existing open issue ────────────────────────────────────────── find_existing_issue() { - # URL-encode the gate name for the query local query query=$(printf '%s' "[CI] ${GATE}" | sed 's/ /%20/g; s/\[/%5B/g; s/\]/%5D/g') local response response=$(curl -sf \ - -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Authorization: token ${MOKOGITEA_TOKEN}" \ "${API}/issues?type=issues&state=open&labels=${LABEL_NAME}&q=${query}&limit=5" \ 2>/dev/null || echo "[]") - # Extract the first matching issue number echo "$response" \ | grep -oP '"number":\s*\K[0-9]+' \ | head -1 @@ -151,7 +148,7 @@ ${DETAILS} Fix the issue described above and push a new commit. This issue will be closed automatically when the gate passes, or can be closed manually. --- -*Auto-created by [ci-issue-reporter](${GITEA_URL}/${REPO}/src/branch/main/automation/ci-issue-reporter.sh)* +*Auto-created by [ci-issue-reporter](${MOKOGITEA_URL}/MokoConsulting/MokoCLI/src/branch/main/cli/ci_issue_reporter.sh)* BODY } @@ -174,14 +171,13 @@ ensure_label EXISTING=$(find_existing_issue) if [[ -n "$EXISTING" ]]; then - # Append comment to existing issue COMMENT_BODY=$(build_comment) COMMENT_JSON=$(printf '%s' "$COMMENT_BODY" | python3 -c " import sys, json print(json.dumps({'body': sys.stdin.read()}))" 2>/dev/null) HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Authorization: token ${MOKOGITEA_TOKEN}" \ -H "Content-Type: application/json" \ "${API}/issues/${EXISTING}/comments" \ -d "${COMMENT_JSON}" 2>/dev/null || echo "000") @@ -192,7 +188,6 @@ print(json.dumps({'body': sys.stdin.read()}))" 2>/dev/null) echo "WARNING: Failed to comment on issue #${EXISTING} (HTTP ${HTTP})" fi else - # Create new issue ISSUE_BODY=$(build_body) ISSUE_JSON=$(python3 -c " import sys, json @@ -203,9 +198,8 @@ print(json.dumps({ 'labels': [] }))" "$TITLE" <<< "$ISSUE_BODY" 2>/dev/null) - # Create the issue RESPONSE=$(curl -sf -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Authorization: token ${MOKOGITEA_TOKEN}" \ -H "Content-Type: application/json" \ "${API}/issues" \ -d "${ISSUE_JSON}" 2>/dev/null || echo "{}") @@ -213,16 +207,15 @@ print(json.dumps({ ISSUE_NUM=$(echo "$RESPONSE" | grep -oP '"number":\s*\K[0-9]+' | head -1) if [[ -n "$ISSUE_NUM" ]]; then - # Apply label (separate call — more reliable across Gitea versions) LABEL_ID=$(curl -sf \ - -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Authorization: token ${MOKOGITEA_TOKEN}" \ "${API}/labels" 2>/dev/null \ | grep -oP "\"id\":\s*\K[0-9]+(?=[^}]*\"name\":\s*\"${LABEL_NAME}\")" \ | head -1 || true) if [[ -n "$LABEL_ID" ]]; then curl -sf -X POST \ - -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Authorization: token ${MOKOGITEA_TOKEN}" \ -H "Content-Type: application/json" \ "${API}/issues/${ISSUE_NUM}/labels" \ -d "{\"labels\":[${LABEL_ID}]}" \ diff --git a/cli/client_dashboard.php b/cli/client_dashboard.php index 2a273b7..02e4054 100644 --- a/cli/client_dashboard.php +++ b/cli/client_dashboard.php @@ -12,7 +12,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/client_dashboard.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Generate unified client dashboard HTML */ @@ -127,7 +127,7 @@ class ClientDashboardCli extends CliFramework $name = $repo['name'] ?? ''; if ( - !str_starts_with($name, 'client-waas-') + !str_starts_with($name, 'client-waas-') && !str_starts_with($name, 'client-mokosuite-') || !empty($repo['archived']) ) { continue; @@ -135,7 +135,7 @@ class ClientDashboardCli extends CliFramework $clients[] = [ 'repo' => $repo['full_name'] ?? '', - 'name' => str_replace('client-waas-', '', $name), + 'name' => str_replace(['client-waas-', 'client-mokosuite-'], '', $name), 'description' => $repo['description'] ?? '', 'updated' => $repo['updated_at'] ?? '', 'url' => $repo['html_url'] ?? '', diff --git a/cli/client_inventory.php b/cli/client_inventory.php index 8d45b34..e6de097 100644 --- a/cli/client_inventory.php +++ b/cli/client_inventory.php @@ -12,7 +12,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/client_inventory.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Discover and list all client-waas repos with their server configuration status */ @@ -79,7 +79,7 @@ class ClientInventoryCli extends CliFramework foreach ($repos as $repo) { $repoName = $repo['name'] ?? ''; - if (strpos($repoName, 'client-waas') === false) { + if (strpos($repoName, 'client-waas') === false && strpos($repoName, 'client-mokosuite') === false) { continue; } diff --git a/cli/client_provision.php b/cli/client_provision.php index c505a0d..f56a5cd 100644 --- a/cli/client_provision.php +++ b/cli/client_provision.php @@ -12,7 +12,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/client_provision.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Provision a new client environment end-to-end */ @@ -87,7 +87,7 @@ class ClientProvisionCli extends CliFramework return 1; } - $this->repoName = 'client-waas-' . $clientName; + $this->repoName = 'client-mokosuite-' . $clientName; $this->log('INFO', "=== Client Provisioning: {$clientName} ==="); $this->log('INFO', " Org: {$this->org}"); @@ -104,7 +104,7 @@ class ClientProvisionCli extends CliFramework 'variables' => 'setVariables', 'secrets' => 'setSecrets', 'monitoring' => 'setupMonitoring', - 'summary' => 'printSummary', + 'summary' => 'printProvisioningSummary', ]; $exitCode = 0; @@ -156,7 +156,7 @@ class ClientProvisionCli extends CliFramework $resp = $this->giteaApi( 'POST', '/api/v1/repos/MokoConsulting/' - . 'Template-Client-WaaS/generate', + . 'Template-Client/generate', $payload ); @@ -383,7 +383,7 @@ class ClientProvisionCli extends CliFramework return 0; } - private function printSummary(): int + private function printProvisioningSummary(): int { $vars = $this->config['variables'] ?? []; $secrets = $this->config['secrets'] ?? []; diff --git a/cli/create_project.php b/cli/create_project.php index f7926ef..a80a733 100644 --- a/cli/create_project.php +++ b/cli/create_project.php @@ -95,6 +95,10 @@ class CreateProjectCli extends CliFramework $repoRoot = dirname(__DIR__, 2); $templatesDir = "{$repoRoot}/templates/projects"; + if (!is_dir($templatesDir)) { + $this->log('ERROR', "Project templates directory not found: {$templatesDir}"); + return 1; + } $repos = []; diff --git a/cli/deploy_joomla.php b/cli/deploy_joomla.php index 77856f5..ecd7d68 100644 --- a/cli/deploy_joomla.php +++ b/cli/deploy_joomla.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.CLI - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.CLI + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/deploy_joomla.php * BRIEF: Smart Joomla deploy — routes files to correct server directories by extension type diff --git a/cli/grafana_dashboard.php b/cli/grafana_dashboard.php index 7171875..b33ed85 100644 --- a/cli/grafana_dashboard.php +++ b/cli/grafana_dashboard.php @@ -12,7 +12,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/grafana_dashboard.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Manage Grafana dashboards via API */ diff --git a/cli/joomla_build.php b/cli/joomla_build.php index ac96730..e48dc22 100644 --- a/cli/joomla_build.php +++ b/cli/joomla_build.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/joomla_build.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Build a Joomla extension ZIP from manifest — all types supported * NOTE: Called by pre-release and auto-release workflows. */ diff --git a/cli/joomla_metadata_validate.php b/cli/joomla_metadata_validate.php index 5f1a1a4..d47f734 100644 --- a/cli/joomla_metadata_validate.php +++ b/cli/joomla_metadata_validate.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/joomla_metadata_validate.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Validate MokoGitea repo metadata against Joomla extension manifest XML */ diff --git a/cli/manifest_detect.php b/cli/manifest_detect.php index 55def38..01da38c 100644 --- a/cli/manifest_detect.php +++ b/cli/manifest_detect.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/manifest_detect.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Auto-detect manifest fields from source files and optionally push to API */ diff --git a/cli/manifest_integrity.php b/cli/manifest_integrity.php index c0c799a..fc0b043 100644 --- a/cli/manifest_integrity.php +++ b/cli/manifest_integrity.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/manifest_integrity.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Cross-check manifest API fields against repo contents across the org */ diff --git a/cli/manifest_licensing.php b/cli/manifest_licensing.php index 150b7d3..6067ee8 100644 --- a/cli/manifest_licensing.php +++ b/cli/manifest_licensing.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/manifest_licensing.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Ensure licensing tags (updateservers, dlid) in Joomla extension manifests */ diff --git a/cli/manifest_read.php b/cli/manifest_read.php index 720bd16..b3105fa 100644 --- a/cli/manifest_read.php +++ b/cli/manifest_read.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/manifest_read.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Read repo metadata from Gitea manifest API, auto-detect the rest */ diff --git a/cli/platform_detect.php b/cli/platform_detect.php index e025815..5451ca2 100644 --- a/cli/platform_detect.php +++ b/cli/platform_detect.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/platform_detect.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Auto-detect repository platform type and optionally update manifest */ diff --git a/cli/release.php b/cli/release.php index ef059d4..994ec07 100644 --- a/cli/release.php +++ b/cli/release.php @@ -40,7 +40,6 @@ class ReleaseCli extends CliFramework $bulkSyncFile = file_exists("{$repoRoot}/.mokogitea/workflows/bulk-repo-sync.yml") ? "{$repoRoot}/.mokogitea/workflows/bulk-repo-sync.yml" : "{$repoRoot}/.github/workflows/bulk-repo-sync.yml"; - $cleanupFile = "{$repoRoot}/templates/workflows/shared/repository-cleanup.yml.template"; // -- Step 1: Read current version -- $readme = "{$repoRoot}/README.md"; @@ -131,19 +130,7 @@ class ReleaseCli extends CliFramework file_put_contents($bulkSyncFile, $bulkContent); } - // -- Step 5: Update repository-cleanup.yml current branch -- - echo "Updating repository-cleanup.yml -> chore/sync-mokostandards-v{$minorVersion}\n"; - if (!$this->dryRun) { - $cleanupContent = file_get_contents($cleanupFile); - $cleanupContent = preg_replace( - '/CURRENT="chore\/sync-mokostandards-v[^"]*"/', - "CURRENT=\"chore/sync-mokostandards-v{$minorVersion}\"", - $cleanupContent - ); - file_put_contents($cleanupFile, $cleanupContent); - } - - // -- Step 6: Commit changes -- + // -- Step 5: Commit changes -- if (!$this->dryRun) { echo "Committing...\n"; passthru("cd {$repoRoot} && git add -A && git commit -m \"chore(release): prepare {$currentVersion} release [skip ci]\""); diff --git a/cli/release_cascade.php b/cli/release_cascade.php index 49dc0d5..626afb2 100644 --- a/cli/release_cascade.php +++ b/cli/release_cascade.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/release_cascade.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Cascade release zip to all lower stability channels */ diff --git a/cli/release_package.php b/cli/release_package.php index c5f83f4..665aaaf 100644 --- a/cli/release_package.php +++ b/cli/release_package.php @@ -270,12 +270,90 @@ class ReleasePackageCli extends CliFramework } } + // Check if sub-source is itself a Joomla package (nested package) + $nestedPkgManifests = glob("{$subSourceDir}/pkg_*.xml") ?: []; + $isNestedPackage = !empty($nestedPkgManifests) && is_dir("{$subSourceDir}/packages"); + $subZip = new \ZipArchive(); if ($subZip->open($subZipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { $this->log('ERROR', "Failed to create sub-package ZIP: {$subZipPath}"); continue; } - $this->addDirToZip($subZip, $subSourceDir, '', $this->excludePatterns); + + if ($isNestedPackage) { + // Build nested package: zip each sub-extension, then assemble + echo " Building nested package: {$subName}\n"; + $nestedPkgDirs = glob("{$subSourceDir}/packages/*", GLOB_ONLYDIR) ?: []; + + $nestedManifested = []; + foreach ($nestedPkgManifests as $npmf) { + $npmXml = @simplexml_load_file($npmf); + if ($npmXml && isset($npmXml->files)) { + foreach ($npmXml->files->file as $fn) { + $nzn = pathinfo((string) $fn, PATHINFO_FILENAME); + if (!empty($nzn)) { + $nestedManifested[$nzn] = true; + } + } + } + } + + // Use a subdirectory to avoid filename collisions with outer ZIPs + $nestedOutputDir = "{$outputDir}/nested_{$subName}"; + @mkdir($nestedOutputDir, 0755, true); + + foreach ($nestedPkgDirs as $npd) { + $nestedSubName = basename($npd); + if (!empty($nestedManifested) && !isset($nestedManifested[$nestedSubName])) { + continue; + } + $nestedSubZipPath = "{$nestedOutputDir}/{$nestedSubName}.zip"; + $nsZip = new \ZipArchive(); + if ($nsZip->open($nestedSubZipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { + continue; + } + $this->addDirToZip($nsZip, $npd, '', $this->excludePatterns); + $nsZip->close(); + $subZip->addFile($nestedSubZipPath, "packages/{$nestedSubName}.zip"); + echo " Nested sub: {$nestedSubName}.zip\n"; + } + + // Patch nested manifest: ensure folder="packages" so Joomla finds sub-ZIPs + foreach ($nestedPkgManifests as $npmf) { + $npmContent = file_get_contents($npmf); + if ($npmContent !== false + && strpos($npmContent, '') !== false + && strpos($npmContent, 'folder="packages"') === false + ) { + $npmContent = str_replace('', '', $npmContent); + file_put_contents($npmf, $npmContent); + echo " Fixed: added folder=\"packages\" to " . basename($npmf) . "\n"; + } + } + + // Add top-level files (manifest, script, etc.) with exclude filter + $nestedTopFiles = array_merge( + glob("{$subSourceDir}/*.xml") ?: [], + glob("{$subSourceDir}/*.php") ?: [] + ); + foreach ($nestedTopFiles as $ntf) { + if (!$this->isExcluded(basename($ntf), $this->excludePatterns)) { + $subZip->addFile($ntf, basename($ntf)); + } + } + + // Add all top-level directories except packages/ + $nestedTopDirs = glob("{$subSourceDir}/*", GLOB_ONLYDIR) ?: []; + foreach ($nestedTopDirs as $ntd) { + $ndName = basename($ntd); + if ($ndName === 'packages') { + continue; + } + $this->addDirToZip($subZip, $ntd, $ndName, $this->excludePatterns); + } + } else { + $this->addDirToZip($subZip, $subSourceDir, '', $this->excludePatterns); + } $subZip->close(); $zip->addFile($subZipPath, "packages/{$subName}.zip"); diff --git a/cli/release_publish.php b/cli/release_publish.php index 6e26693..db3ff02 100644 --- a/cli/release_publish.php +++ b/cli/release_publish.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/release_publish.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Publish a release and create copies for all lesser stability streams. */ diff --git a/cli/scaffold_client.php b/cli/scaffold_client.php index f9f833f..d49b681 100644 --- a/cli/scaffold_client.php +++ b/cli/scaffold_client.php @@ -12,8 +12,8 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/scaffold_client.php - * VERSION: 09.37.07 - * BRIEF: Scaffold a new client-waas repo from Template-Client-WaaS with pre-configured settings + * VERSION: 09.43.00 + * BRIEF: Scaffold a new client-waas repo from Template-Client with pre-configured settings */ declare(strict_types=1); @@ -26,7 +26,7 @@ class ScaffoldClientCli extends CliFramework { protected function configure(): void { - $this->setDescription('Scaffold a new client-waas repo from Template-Client-WaaS'); + $this->setDescription('Scaffold a new client-waas repo from Template-Client'); $this->addArgument('--name', 'Client name', ''); $this->addArgument('--org', 'Gitea organization', ''); $this->addArgument('--gitea-url', 'Gitea URL', 'https://git.mokoconsulting.tech'); @@ -47,7 +47,7 @@ class ScaffoldClientCli extends CliFramework $this->log('INFO', "Scaffolding client repo: {$org}/{$repoName}"); $this->log('INFO', "Gitea URL: {$giteaUrl}"); if ($this->dryRun) { - $this->log('INFO', '[DRY RUN] Would create repo from template MokoConsulting/Template-Client-WaaS'); + $this->log('INFO', '[DRY RUN] Would create repo from template MokoConsulting/Template-Client'); $this->log('INFO', "[DRY RUN] Repo: {$org}/{$repoName}"); $this->printPostSetupInstructions($repoName, $giteaUrl, $org); return 0; @@ -64,7 +64,7 @@ class ScaffoldClientCli extends CliFramework ]); $response = $this->apiRequest( 'POST', - "/api/v1/repos/MokoConsulting/Template-Client-WaaS/generate", + "/api/v1/repos/MokoConsulting/Template-Client/generate", $giteaUrl, $token, $createPayload diff --git a/cli/theme_vars_check.php b/cli/theme_vars_check.php new file mode 100644 index 0000000..433157d --- /dev/null +++ b/cli/theme_vars_check.php @@ -0,0 +1,294 @@ +#!/usr/bin/env php + + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * FILE INFORMATION + * DEFGROUP: mokocli.CLI + * INGROUP: mokocli + * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli + * PATH: /cli/theme_vars_check.php + * BRIEF: Validate a client MokoOnyx theme package — required CSS variables + * (derived dynamically from the MokoOnyx standard theme) are defined in + * the client's light/dark custom CSS, required files exist, the manifest + * is sane, and (optionally) the repo's Gitea metadata is set. + * + * Standalone (no CliFramework dependency) so it runs even when the shared + * framework autoloader is unavailable. + * + * Usage: + * php theme_vars_check.php --path . --reference /tmp/mokoonyx [--github-output] + * [--api-base --repo --token ] + */ + +declare(strict_types=1); + +$opts = getopt('', ['path:', 'reference:', 'github-output', 'api-base:', 'repo:', 'token:']); +$path = isset($opts['path']) && is_string($opts['path']) && $opts['path'] !== '' ? $opts['path'] : '.'; +$ref = isset($opts['reference']) && is_string($opts['reference']) ? $opts['reference'] : ''; +$gh = array_key_exists('github-output', $opts); + +$root = realpath($path); +if ($root === false) { $root = $path; } + +$src = is_dir("$root/src") ? "$root/src" : (is_dir("$root/source") ? "$root/source" : null); +if ($src === null) { + fwrite(STDERR, "ERROR: no src/ or source/ directory under $root\n"); + exit(1); +} + +$errors = 0; +$summary = []; +$fail = function (string $msg) use (&$errors, &$summary): void { + echo " [FAIL] $msg\n"; + $summary[] = "FAIL: $msg"; + $errors++; +}; +$ok = function (string $msg): void { echo " [ok] $msg\n"; }; +$note = function (string $msg): void { echo " [note] $msg\n"; }; + +/** Extract the set of CSS custom-property names DEFINED in a CSS string. */ +$definedVars = static function (string $css): array { + // Matches "--name:" at a declaration position (not var(--name) uses). + preg_match_all('/(?:^|[\s;{])(--[a-z0-9_-]+)\s*:/i', $css, $m); + return array_values(array_unique(array_map('strtolower', $m[1] ?? []))); +}; + +/** Find the MokoOnyx standard theme file for a mode inside a reference checkout. */ +$findStandard = static function (string $ref, string $mode): ?string { + if ($ref === '') { return null; } + foreach ([ + "$ref/source/media/css/theme/$mode.standard.css", + "$ref/media/templates/site/mokoonyx/css/theme/$mode.standard.css", + "$ref/$mode.standard.css", + ] as $cand) { + if (is_file($cand)) { return $cand; } + } + return null; +}; + +$cssDir = "$src/media/templates/site/mokoonyx/css"; +$themeDir = "$cssDir/theme"; +$manifest = "$src/templateDetails.xml"; +$customs = ['light' => "$themeDir/light.custom.css", 'dark' => "$themeDir/dark.custom.css"]; + +echo "MokoOnyx theme validation: $src\n\n"; + +// 1) Required files ------------------------------------------------------- +echo "=== Required files ===\n"; +$requiredFiles = [ + 'templateDetails.xml' => $manifest, + 'theme/light.custom.css' => $customs['light'], + 'theme/dark.custom.css' => $customs['dark'], + 'user.css' => "$cssDir/user.css", +]; +foreach ($requiredFiles as $label => $file) { + is_file($file) ? $ok("$label present") : $fail("missing required file: $label"); +} + +// 1b) Forbidden committed files ------------------------------------------ +echo "\n=== Forbidden files ===\n"; +$scan = static function (string $dir, array $skip) use (&$scan): array { + $out = []; + foreach (scandir($dir) ?: [] as $e) { + if ($e === '.' || $e === '..') { continue; } + $p = "$dir/$e"; + if (is_dir($p)) { + if (in_array($e, $skip, true)) { continue; } + $out = array_merge($out, $scan($p, $skip)); + } else { + $out[] = $p; + } + } + return $out; +}; +$forbidden = []; +if (is_dir("$root/.claude")) { $forbidden[] = '.claude/'; } +foreach ($scan($root, ['.git', 'vendor', 'node_modules']) as $f) { + $b = basename($f); + $rel = ltrim(str_replace('\\', '/', substr($f, strlen($root))), '/'); + if ($b === '.mcp.json' || $b === 'TODO.md' + || fnmatch('.mcp_*.json', $b) || fnmatch('sftp-config*.json', $b) + || fnmatch('*.min.css', $b) || fnmatch('*.min.js', $b)) { + $forbidden[] = $rel; + } +} +$forbidden = array_values(array_unique($forbidden)); +if ($forbidden) { + $shown = array_slice($forbidden, 0, 10); + $more = count($forbidden) - count($shown); + $fail('forbidden file(s) committed: ' . implode(', ', $shown) . ($more > 0 ? " (+$more more)" : '')); +} else { + $ok('no forbidden files committed'); +} + +// 2) CSS variables — required set derived from the MokoOnyx standard theme +echo "\n=== CSS variables vs MokoOnyx standard theme ===\n"; +if ($ref === '') { + $note('no --reference given; skipping variable parity check'); +} else { + foreach ($customs as $mode => $customFile) { + $std = $findStandard($ref, $mode); + if ($std === null) { + $fail("$mode: standard theme not found under reference '$ref'"); + continue; + } + if (!is_file($customFile)) { + continue; // already reported missing + } + $required = $definedVars((string) file_get_contents($std)); + $defined = $definedVars((string) file_get_contents($customFile)); + $missing = array_values(array_diff($required, $defined)); + if ($missing) { + $shown = array_slice($missing, 0, 15); + $more = count($missing) - count($shown); + $fail("$mode mode missing " . count($missing) . '/' . count($required) + . ' standard variable(s): ' . implode(', ', $shown) . ($more > 0 ? " (+$more more)" : '')); + } else { + $ok("$mode mode defines all " . count($required) . ' standard variables'); + } + } +} + +// 3) Manifest sanity ------------------------------------------------------ +echo "\n=== Manifest (templateDetails.xml) ===\n"; +if (is_file($manifest)) { + $xml = @simplexml_load_file($manifest); + if ($xml === false) { + $fail('templateDetails.xml is not well-formed XML'); + } else { + $version = isset($xml->version) ? trim((string) $xml->version) : ''; + $version !== '' ? $ok("version $version") : $fail(' is missing or empty'); + + $server = isset($xml->updateservers->server) ? trim((string) $xml->updateservers->server) : ''; + if ($server === '') { + $fail(' is missing'); + } elseif (strpos($server, '/raw/branch/') !== false) { + $fail('update server uses a legacy raw/branch URL; use the dynamic MokoGitea feed'); + } else { + $ok("update server: $server"); + } + + isset($xml->dlid) ? $ok(' license-key field present') + : $fail(' is missing'); + + // Element naming convention (MokoOnyx client theme file package) + $type = trim((string) ($xml['type'] ?? '')); + $element = isset($xml->element) ? trim((string) $xml->element) : ''; + if ($type === 'file' && $element !== '') { + strpos($element, 'file_mokoonyx_') === 0 + ? $ok("element naming ok ($element)") + : $fail("element '$element' should start with 'file_mokoonyx_' for a MokoOnyx client theme package"); + } + + // Required manifest fields + foreach (['name', 'element', 'author', 'creationDate'] as $field) { + (isset($xml->$field) && trim((string) $xml->$field) !== '') + ? $ok("<$field> present") + : $fail("<$field> is missing or empty"); + } + + // scriptfile (if declared) must exist + if (isset($xml->scriptfile)) { + $sf = trim((string) $xml->scriptfile); + is_file("$src/$sf") ? $ok("scriptfile '$sf' present") + : $fail(" references '$sf' which is not in src/"); + } + + // Fileset integrity — every referenced file/folder must exist in src/ + if (isset($xml->fileset)) { + $missingFs = []; + foreach ($xml->fileset->files as $files) { + $folder = trim((string) ($files['folder'] ?? '')); + $baseDir = $folder !== '' ? "$src/$folder" : $src; + foreach ($files->filename as $fn) { + $rel = ($folder !== '' ? "$folder/" : '') . trim((string) $fn); + if (!is_file("$baseDir/" . trim((string) $fn))) { $missingFs[] = $rel; } + } + foreach ($files->folder as $fd) { + $rel = ($folder !== '' ? "$folder/" : '') . trim((string) $fd) . '/'; + if (!is_dir("$baseDir/" . trim((string) $fd))) { $missingFs[] = $rel; } + } + } + if ($missingFs) { + $shown = array_slice($missingFs, 0, 10); + $more = count($missingFs) - count($shown); + $fail('fileset references ' . count($missingFs) . ' missing path(s): ' + . implode(', ', $shown) . ($more > 0 ? " (+$more more)" : '')); + } else { + $ok('all fileset paths exist in src/'); + } + } + } +} + +// 4) Repository metadata via Gitea API (optional) ------------------------- +$apiBase = isset($opts['api-base']) && is_string($opts['api-base']) ? rtrim($opts['api-base'], '/') : ''; +$repoSlug = isset($opts['repo']) && is_string($opts['repo']) ? trim($opts['repo']) : ''; +$token = isset($opts['token']) && is_string($opts['token']) ? trim($opts['token']) : ''; +if ($apiBase !== '' && $repoSlug !== '' && $token !== '') { + echo "\n=== Repository metadata (Gitea API) ===\n"; + $url = "$apiBase/repos/$repoSlug"; + $ctx = stream_context_create(['http' => [ + 'method' => 'GET', + 'header' => "Authorization: token $token\r\nAccept: application/json\r\n", + 'ignore_errors' => true, + 'timeout' => 15, + ]]); + $resp = @file_get_contents($url, false, $ctx); + $data = $resp !== false ? json_decode($resp, true) : null; + if (!is_array($data) || !isset($data['name'])) { + $fail("could not read repo metadata from Gitea API ($url)"); + } else { + trim((string) ($data['description'] ?? '')) !== '' ? $ok('description set') + : $fail('repo description is empty'); + trim((string) ($data['website'] ?? '')) !== '' ? $ok('website set: ' . $data['website']) + : $fail('repo website is empty'); + $topics = $data['topics'] ?? []; + (is_array($topics) && count($topics) > 0) ? $ok(count($topics) . ' topic(s) set') + : $fail('repo has no topics'); + + // Required topics + $requiredTopics = ['joomla', 'client-waas', 'mokoonyx']; + $haveTopics = array_map('strtolower', is_array($topics) ? $topics : []); + $missingTopics = array_values(array_diff($requiredTopics, $haveTopics)); + $missingTopics + ? $fail('missing required topic(s): ' . implode(', ', $missingTopics)) + : $ok('required topics present (' . implode(', ', $requiredTopics) . ')'); + + ((string) ($data['default_branch'] ?? '')) === 'main' ? $ok('default branch is main') + : $fail("default branch is '" . ($data['default_branch'] ?? '') . "' (expected main)"); + + // Branch protection on main + $bresp = @file_get_contents("$apiBase/repos/$repoSlug/branches/main", false, $ctx); + $bdata = $bresp !== false ? json_decode($bresp, true) : null; + if (is_array($bdata) && array_key_exists('protected', $bdata)) { + $bdata['protected'] ? $ok('main branch is protected') + : $fail('main branch is not protected'); + } else { + $note('could not read branch protection state for main'); + } + } +} else { + echo "\n=== Repository metadata (Gitea API) ===\n"; + $note('API args not provided; skipping repo metadata check'); +} + +// Output / exit ----------------------------------------------------------- +echo "\n"; +if ($gh && ($f = getenv('GITHUB_STEP_SUMMARY'))) { + $md = "## MokoOnyx Theme Validation\n\n"; + $md .= $errors === 0 + ? "All checks passed.\n" + : implode("\n", array_map(static fn ($l) => "- $l", $summary)) . "\n"; + @file_put_contents($f, $md, FILE_APPEND); +} + +if ($errors > 0) { + echo "FAILED: $errors issue(s) found.\n"; + exit(1); +} +echo "All MokoOnyx theme validation checks passed.\n"; +exit(0); diff --git a/cli/updates_xml_sync.php b/cli/updates_xml_sync.php index 8f4003c..acbf5dc 100644 --- a/cli/updates_xml_sync.php +++ b/cli/updates_xml_sync.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/updates_xml_sync.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Sync updates.xml to target branches via Gitea API * NOTE: Called by pre-release and auto-release workflows after updates.xml * is modified on the current branch. Pushes the file to other branches diff --git a/cli/version_auto_bump.php b/cli/version_auto_bump.php index e03c711..b302737 100644 --- a/cli/version_auto_bump.php +++ b/cli/version_auto_bump.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/version_auto_bump.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Auto patch-bump, set stability suffix, and commit — single CLI replacing inline workflow bash */ diff --git a/cli/version_bump.php b/cli/version_bump.php index 9a5a5f3..88cf517 100644 --- a/cli/version_bump.php +++ b/cli/version_bump.php @@ -214,7 +214,8 @@ class VersionBumpCli extends CliFramework $newContent = preg_replace( $xmlPattern, '${1}' . $newBase . '', - $content + $content, + 1 ); } else { $xmlPattern = '#\d{2}\.\d{2}\.\d{2}' @@ -222,9 +223,13 @@ class VersionBumpCli extends CliFramework $newContent = preg_replace( $xmlPattern, "{$newFull}", - $content + $content, + 1 ); } + if ($newContent !== null) { + $newContent = preg_replace('#(\s*[^<]*)\s*[^<]*#', '$1', $newContent); + } if ($newContent !== null && $newContent !== $content) { file_put_contents($xmlFile, $newContent); $updatedFiles[] = substr($xmlFile, strlen($root) + 1); @@ -234,6 +239,20 @@ class VersionBumpCli extends CliFramework if (!empty($updatedFiles)) { fwrite(STDERR, "Updated " . count($updatedFiles) . " Joomla manifest(s): " . implode(', ', $updatedFiles) . "\n"); } + // Joomla schema version: create empty SQL update file if sql/updates/mysql/ exists + $sqlUpdateDirs = array_merge( + SourceResolver::globSource($root, 'packages/*/sql/updates/mysql'), + SourceResolver::globSource($root, 'sql/updates/mysql'), + glob("{$root}/sql/updates/mysql") ?: [] + ); + $sqlUpdateDirs = array_unique(array_filter($sqlUpdateDirs, 'is_dir')); + foreach ($sqlUpdateDirs as $sqlDir) { + $sqlFile = "{$sqlDir}/{$newBase}.sql"; + if (!file_exists($sqlFile)) { + file_put_contents($sqlFile, "/* {$newBase} — no schema changes */\n"); + fwrite(STDERR, "Created SQL update file: " . substr($sqlFile, strlen($root) + 1) . "\n"); + } + } $packageJsonFile = "{$root}/package.json"; if (file_exists($packageJsonFile)) { $pkgContent = file_get_contents($packageJsonFile); @@ -370,7 +389,7 @@ class VersionBumpCli extends CliFramework /** * Scan git release tags for the highest version across all channels. * - * Checks release names like "MokoSuiteClient (VERSION: 09.37.07)" in + * Checks release names like "MokoSuiteClient (VERSION: 09.43.00)" in * git tags (stable, release-candidate, development, etc.) to find the * highest version that has been released on any channel. */ diff --git a/cli/version_check.php b/cli/version_check.php index 28d776e..7e400b2 100644 --- a/cli/version_check.php +++ b/cli/version_check.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/version_check.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Validate version consistency across README, manifests, and sub-packages */ @@ -131,9 +131,11 @@ class VersionCheckCli extends CliFramework $updated = preg_replace( $vPat, "{$highestVersion}", - $content + $content, + 1 ); if ($updated !== null) { + $updated = preg_replace('#(\s*[^<]*)\s*[^<]*#', '$1', $updated); file_put_contents($mokoManifest, $updated); } echo " Fixed: .mokogitea/manifest.xml -> {$highestVersion}\n"; } @@ -185,8 +187,9 @@ class VersionCheckCli extends CliFramework if (!file_exists($file)) { continue; } $content = file_get_contents($file); - $updated = preg_replace('#[^<]*#', "{$highestVersion}", $content); + $updated = preg_replace('#[^<]*#', "{$highestVersion}", $content, 1); if ($updated !== null) { + $updated = preg_replace('#(\s*[^<]*)\s*[^<]*#', '$1', $updated); file_put_contents($file, $updated); } echo " Fixed: {$source} -> {$highestVersion}\n"; } diff --git a/cli/version_set_platform.php b/cli/version_set_platform.php index 79e1c15..526c0e6 100644 --- a/cli/version_set_platform.php +++ b/cli/version_set_platform.php @@ -164,8 +164,12 @@ class VersionSetPlatformCli extends CliFramework $updated = preg_replace( '|[^<]*|', "{$version}", - $content + $content, + 1 ); + if ($updated !== null) { + $updated = preg_replace('#(\s*[^<]*)\s*[^<]*#', '$1', $updated); + } if ($updated !== null && $updated !== $content) { file_put_contents($file, $updated); $relPath = str_replace($root . '/', '', $file); diff --git a/cli/wiki_sync.php b/cli/wiki_sync.php index 1a10097..817f780 100644 --- a/cli/wiki_sync.php +++ b/cli/wiki_sync.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/wiki_sync.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Sync select wiki pages from mokocli to all template repos */ diff --git a/cli/workflow_sync.php b/cli/workflow_sync.php index 10a4ec7..f2dc323 100644 --- a/cli/workflow_sync.php +++ b/cli/workflow_sync.php @@ -10,7 +10,7 @@ * INGROUP: mokocli * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /cli/workflow_sync.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Sync workflows from Generic → platform templates → live repos based on manifest.platform */ @@ -42,9 +42,13 @@ class WorkflowSyncCli extends CliFramework 'joomla' => ['deploy-manual.yml'], ]; + /** Prefix for custom workflows preserved during orphan cleanup. */ + private const CUSTOM_PREFIX = 'custom-'; + private int $updated = 0; private int $created = 0; private int $skipped = 0; + private int $deleted = 0; private int $errors = 0; protected function configure(): void @@ -56,6 +60,7 @@ class WorkflowSyncCli extends CliFramework $this->addArgument('--branch', 'Target branch (default: main)', 'main'); $this->addArgument('--phase', 'Phase to run: all, templates, repos (default: all)', 'all'); $this->addArgument('--platform-filter', 'Only sync repos matching this platform', ''); + $this->addArgument('--delete-orphans', 'Delete workflows not in template (preserves custom-* and custom/)', false); } protected function run(): int @@ -114,7 +119,7 @@ class WorkflowSyncCli extends CliFramework echo "\n"; $this->log('INFO', "Done: {$this->created} created, {$this->updated} updated, " - . "{$this->skipped} skipped, {$this->errors} error(s)."); + . "{$this->deleted} deleted, {$this->skipped} skipped, {$this->errors} error(s)."); return $this->errors > 0 ? 1 : 0; } @@ -275,14 +280,15 @@ class WorkflowSyncCli extends CliFramework foreach ($workflows as $workflow) { $filename = $workflow['name']; + $destPath = '.mokogitea/workflows/' . $filename; + $label = "{$repoFullName}/{$filename}"; + // Skip platform-excluded workflows if (in_array($filename, self::PLATFORM_EXCLUDES[$platform] ?? [], true)) { fprintf(STDERR, "%-45s | %s\n", $label, 'EXCLUDED (platform)'); $this->skipped++; continue; } - $destPath = '.mokogitea/workflows/' . $filename; - $label = "{$repoFullName}/{$filename}"; // Get source content from template $sourceContent = $this->getFileContent( @@ -303,6 +309,14 @@ class WorkflowSyncCli extends CliFramework $destPath, $sourceContent, $branch, $commitMsg, $label ); } + + // Delete orphan workflows if enabled + if ($this->getArgument('--delete-orphans', false)) { + $templateNames = array_map(fn($w) => $w['name'], $workflows); + $this->deleteOrphanWorkflows( + $giteaUrl, $token, $org, $repoName, $branch, $templateNames, $platform + ); + } } echo "\n"; @@ -406,6 +420,116 @@ class WorkflowSyncCli extends CliFramework } } + /** + * Delete workflows in a repo that are NOT in the template and NOT custom. + * + * Protected from deletion: + * - Files matching template workflow names + * - Files with `custom-` prefix (convention for repo-specific workflows) + * - Directories named `custom` (future: subfolder discovery) + * - Platform-excluded workflows + */ + private function deleteOrphanWorkflows( + string $giteaUrl, + string $token, + string $org, + string $repoName, + string $branch, + array $templateNames, + string $platform + ): void { + $repoWorkflows = $this->listWorkflows($giteaUrl, $token, $org, $repoName, $branch); + if ($repoWorkflows === null) { + return; + } + + $platformExcludes = self::PLATFORM_EXCLUDES[$platform] ?? []; + + foreach ($repoWorkflows as $workflow) { + $name = $workflow['name']; + + // Keep if it's in the template + if (in_array($name, $templateNames, true)) { + continue; + } + + // Keep if it has the custom- prefix + if (str_starts_with($name, self::CUSTOM_PREFIX)) { + $label = "{$org}/{$repoName}/{$name}"; + fprintf(STDERR, "%-45s | %s\n", $label, 'KEPT (custom)'); + continue; + } + + // Keep if it's platform-excluded (legitimately skipped during sync) + if (in_array($name, $platformExcludes, true)) { + $label = "{$org}/{$repoName}/{$name}"; + fprintf(STDERR, "%-45s | %s\n", $label, 'KEPT (platform-excluded)'); + continue; + } + + // Delete orphan + $filePath = '.mokogitea/workflows/' . $name; + $label = "{$org}/{$repoName}/{$name}"; + + if ($this->dryRun) { + fprintf(STDERR, "%-45s | %s\n", $label, 'WOULD DELETE'); + $this->deleted++; + continue; + } + + $deleted = $this->deleteFile($giteaUrl, $token, $org, $repoName, $filePath, $branch); + if ($deleted) { + fprintf(STDERR, "%-45s | %s\n", $label, 'DELETED'); + $this->deleted++; + } else { + fprintf(STDERR, "%-45s | %s\n", $label, 'ERROR (delete)'); + $this->errors++; + } + } + } + + /** + * Delete a file from a repo via the Gitea Contents API. + */ + private function deleteFile( + string $giteaUrl, + string $token, + string $org, + string $repoName, + string $filePath, + string $branch + ): bool { + // Get SHA first + $existing = $this->apiRequest( + $giteaUrl, $token, 'GET', + "/api/v1/repos/{$org}/{$repoName}/contents/{$filePath}?ref={$branch}" + ); + + if ($existing['code'] !== 200) { + return false; + } + + $data = json_decode($existing['body'], true); + $sha = $data['sha'] ?? ''; + if ($sha === '') { + return false; + } + + $payload = json_encode([ + 'sha' => $sha, + 'message' => "chore: delete orphan workflow {$filePath} [skip ci]", + 'branch' => $branch, + ]); + + $response = $this->apiRequest( + $giteaUrl, $token, 'DELETE', + "/api/v1/repos/{$org}/{$repoName}/contents/{$filePath}", + $payload + ); + + return $response['code'] === 200; + } + /** * List workflow files in a repo's .mokogitea/workflows/ directory. */ diff --git a/composer.json b/composer.json index e612f76..3c1a264 100644 --- a/composer.json +++ b/composer.json @@ -71,8 +71,8 @@ ], "scripts": { "test": "phpunit", - "phpcs": "phpcs --standard=phpcs.xml lib/ validate/ automation/", - "phpstan": "phpstan analyse -c phpstan.neon lib/ validate/ automation/", + "phpcs": "phpcs --standard=phpcs.xml lib/ validate/", + "phpstan": "phpstan analyse -c phpstan.neon lib/ validate/", "psalm": "psalm --config=psalm.xml", "check": [ "@phpcs", diff --git a/deploy/backup-before-deploy.php b/deploy/backup-before-deploy.php index 7145b1b..f5add44 100644 --- a/deploy/backup-before-deploy.php +++ b/deploy/backup-before-deploy.php @@ -8,11 +8,11 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Deploy - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Deploy + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /deploy/backup-before-deploy.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Snapshot Joomla directories before deployment for rollback capability */ diff --git a/deploy/deploy-and-verify.php b/deploy/deploy-and-verify.php index 2315404..f5fca20 100644 --- a/deploy/deploy-and-verify.php +++ b/deploy/deploy-and-verify.php @@ -4,8 +4,8 @@ /* Copyright (C) 2026 Moko Consulting * SPDX-License-Identifier: GPL-3.0-or-later * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Deploy - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Deploy + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /deploy/deploy-and-verify.php * BRIEF: Deploy with automatic health check and rollback on failure diff --git a/deploy/deploy-dolibarr.php b/deploy/deploy-dolibarr.php index 42c0528..9a2f999 100644 --- a/deploy/deploy-dolibarr.php +++ b/deploy/deploy-dolibarr.php @@ -8,11 +8,11 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Deploy - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Deploy + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /deploy/deploy-dolibarr.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Deploy Dolibarr module files to a remote server via SFTP/rsync */ diff --git a/deploy/deploy-joomla.php b/deploy/deploy-joomla.php index f3ffc05..f6218f5 100644 --- a/deploy/deploy-joomla.php +++ b/deploy/deploy-joomla.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Deploy - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Deploy + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /deploy/deploy-joomla.php * BRIEF: Smart Joomla deploy — routes files to correct Joomla directories based on XML manifest diff --git a/deploy/deploy-sftp.php b/deploy/deploy-sftp.php index b53a6f8..315e1e6 100644 --- a/deploy/deploy-sftp.php +++ b/deploy/deploy-sftp.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Deploy - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Deploy + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /deploy/deploy-sftp.php * BRIEF: Deploy a repository src/ directory to a remote web server via SFTP diff --git a/deploy/health-check.php b/deploy/health-check.php index 1a2a8fe..92e5af0 100644 --- a/deploy/health-check.php +++ b/deploy/health-check.php @@ -8,11 +8,11 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Deploy - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Deploy + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /deploy/health-check.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Post-deploy health check — verify a Joomla site is responding correctly */ diff --git a/deploy/rollback-joomla.php b/deploy/rollback-joomla.php index 7fcae43..0534293 100644 --- a/deploy/rollback-joomla.php +++ b/deploy/rollback-joomla.php @@ -8,11 +8,11 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Deploy - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Deploy + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /deploy/rollback-joomla.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Rollback a Joomla deployment by restoring from a pre-deploy snapshot */ diff --git a/deploy/sync-joomla.php b/deploy/sync-joomla.php index 09d2131..eab3f82 100644 --- a/deploy/sync-joomla.php +++ b/deploy/sync-joomla.php @@ -8,11 +8,11 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Deploy - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Deploy + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /deploy/sync-joomla.php - * VERSION: 09.37.07 + * VERSION: 09.43.00 * BRIEF: Sync Joomla site directories between two servers via rsync over SSH */ diff --git a/fix/fix_line_endings.php b/fix/fix_line_endings.php index 4b1215a..a64be47 100644 --- a/fix/fix_line_endings.php +++ b/fix/fix_line_endings.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Fix - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Fix + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /fix/fix_line_endings.php * BRIEF: CLI script to normalise CRLF/CR to LF in tracked source files diff --git a/fix/fix_permissions.php b/fix/fix_permissions.php index 0600e11..41612a5 100644 --- a/fix/fix_permissions.php +++ b/fix/fix_permissions.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Fix - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Fix + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /fix/fix_permissions.php * BRIEF: CLI script to normalise file permissions (dirs 755, files 644, scripts 755) diff --git a/fix/fix_tabs.php b/fix/fix_tabs.php index cee465c..14a6e26 100644 --- a/fix/fix_tabs.php +++ b/fix/fix_tabs.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Fix - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Fix + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /fix/fix_tabs.php * BRIEF: CLI script to convert tabs to spaces in tracked source files diff --git a/fix/fix_trailing_spaces.php b/fix/fix_trailing_spaces.php index 0c79b4d..de3d123 100644 --- a/fix/fix_trailing_spaces.php +++ b/fix/fix_trailing_spaces.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Fix - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Fix + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /fix/fix_trailing_spaces.php * BRIEF: CLI script to remove trailing whitespace from tracked source files diff --git a/fix/index.md b/fix/index.md index db654c7..3e46806 100644 --- a/fix/index.md +++ b/fix/index.md @@ -2,8 +2,8 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION -DEFGROUP: MokoPlatform.Index -INGROUP: MokoPlatform.Fix +DEFGROUP: MokoCLI.Index +INGROUP: MokoCLI.Fix REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli PATH: /fix/index.md BRIEF: Fix directory index diff --git a/index.md b/index.md index 02692f2..abba0bb 100644 --- a/index.md +++ b/index.md @@ -2,8 +2,8 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION -DEFGROUP: MokoPlatform.Root -INGROUP: MokoPlatform +DEFGROUP: MokoCLI.Root +INGROUP: MokoCLI REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli PATH: /index.md BRIEF: Scripts directory index @@ -61,7 +61,7 @@ Shared library code - GitHub client ### [Wiki](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki) -All documentation lives in the Gitea wiki. +All documentation lives in the MokoGitea wiki. ### [Tests](tests/) Test scripts diff --git a/lib/CliBase.php b/lib/CliBase.php index b87842c..667872d 100644 --- a/lib/CliBase.php +++ b/lib/CliBase.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Lib - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Lib + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * 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 84b240d..f35a557 100644 --- a/lib/Common.php +++ b/lib/Common.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Lib - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Lib + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Common.php * BRIEF: Common utility functions for scripts diff --git a/lib/Enterprise/AbstractProjectPlugin.php b/lib/Enterprise/AbstractProjectPlugin.php index de8e952..371758f 100644 --- a/lib/Enterprise/AbstractProjectPlugin.php +++ b/lib/Enterprise/AbstractProjectPlugin.php @@ -9,8 +9,8 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/AbstractProjectPlugin.php * BRIEF: Abstract base class for project plugins @@ -23,7 +23,7 @@ namespace MokoCli; * * Provides common functionality for all project type plugins * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 1.0.0 */ abstract class AbstractProjectPlugin implements ProjectPluginInterface diff --git a/lib/Enterprise/ApiClient.php b/lib/Enterprise/ApiClient.php index c766734..41aa876 100644 --- a/lib/Enterprise/ApiClient.php +++ b/lib/Enterprise/ApiClient.php @@ -9,8 +9,8 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.API - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.API + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/ApiClient.php * BRIEF: HTTP API client library @@ -31,7 +31,7 @@ declare(strict_types=1); * * SPDX-License-Identifier: GPL-3.0-or-later * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/Enterprise/AuditLogger.php b/lib/Enterprise/AuditLogger.php index 3f7fd51..1f52492 100644 --- a/lib/Enterprise/AuditLogger.php +++ b/lib/Enterprise/AuditLogger.php @@ -22,13 +22,13 @@ declare(strict_types=1); * (at your option) any later version. * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Audit - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Audit + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/AuditLogger.php * BRIEF: Enterprise audit logging * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/Enterprise/CheckpointManager.php b/lib/Enterprise/CheckpointManager.php index d7efd6d..6de08e5 100644 --- a/lib/Enterprise/CheckpointManager.php +++ b/lib/Enterprise/CheckpointManager.php @@ -10,13 +10,13 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Checkpoint - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Checkpoint + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/CheckpointManager.php * BRIEF: Checkpoint manager for resumable operations * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/Enterprise/CliFramework.php b/lib/Enterprise/CliFramework.php index 1285ce5..f999325 100644 --- a/lib/Enterprise/CliFramework.php +++ b/lib/Enterprise/CliFramework.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.CLI - * INGROUP: MokoPlatform.Enterprise + * 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 diff --git a/lib/Enterprise/Config.php b/lib/Enterprise/Config.php index b2bd66b..b696a70 100644 --- a/lib/Enterprise/Config.php +++ b/lib/Enterprise/Config.php @@ -9,8 +9,8 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Config - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Config + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/Config.php * BRIEF: Configuration manager @@ -32,7 +32,7 @@ declare(strict_types=1); * * SPDX-License-Identifier: GPL-3.0-or-later * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/Enterprise/ConfigValidator.php b/lib/Enterprise/ConfigValidator.php index ed07fb0..d2e2332 100644 --- a/lib/Enterprise/ConfigValidator.php +++ b/lib/Enterprise/ConfigValidator.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/ConfigValidator.php * BRIEF: Validate project config against plugin JSON schema diff --git a/lib/Enterprise/EnterpriseReadinessValidator.php b/lib/Enterprise/EnterpriseReadinessValidator.php index ab791f6..4ff829b 100644 --- a/lib/Enterprise/EnterpriseReadinessValidator.php +++ b/lib/Enterprise/EnterpriseReadinessValidator.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/EnterpriseReadinessValidator.php * BRIEF: Enterprise readiness validation library diff --git a/lib/Enterprise/ErrorRecovery.php b/lib/Enterprise/ErrorRecovery.php index 07db26f..491f153 100644 --- a/lib/Enterprise/ErrorRecovery.php +++ b/lib/Enterprise/ErrorRecovery.php @@ -17,13 +17,13 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Recovery - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Recovery + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/ErrorRecovery.php * BRIEF: Error recovery framework * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/Enterprise/FileFixUtility.php b/lib/Enterprise/FileFixUtility.php index 788fe43..7d6a6b4 100644 --- a/lib/Enterprise/FileFixUtility.php +++ b/lib/Enterprise/FileFixUtility.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise - * INGROUP: MokoPlatform.Lib + * DEFGROUP: MokoCLI.Enterprise + * INGROUP: MokoCLI.Lib * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/FileFixUtility.php * BRIEF: Utility class for fixing file formatting issues (line endings, permissions, tabs, trailing spaces) diff --git a/lib/Enterprise/GitHubAdapter.php b/lib/Enterprise/GitHubAdapter.php index 5fa23b9..0fec957 100644 --- a/lib/Enterprise/GitHubAdapter.php +++ b/lib/Enterprise/GitHubAdapter.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Platform - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Platform + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/GitHubAdapter.php * BRIEF: GitHub implementation of GitPlatformAdapter @@ -31,7 +31,7 @@ use RuntimeException; * - Topics: PUT with {"names": [...]} * - Workflow dir: .github/workflows * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @since 04.06.10 * @see GitPlatformAdapter */ diff --git a/lib/Enterprise/GitPlatformAdapter.php b/lib/Enterprise/GitPlatformAdapter.php index 3fa2a94..afda126 100644 --- a/lib/Enterprise/GitPlatformAdapter.php +++ b/lib/Enterprise/GitPlatformAdapter.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Platform - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Platform + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/GitPlatformAdapter.php * BRIEF: Interface defining all git platform operations for GitHub/Gitea abstraction @@ -25,7 +25,7 @@ namespace MokoCli; * Implementations exist for GitHub (GitHubAdapter) and Gitea (MokoGiteaAdapter), * allowing scripts to work against either platform transparently. * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.06.10 */ interface GitPlatformAdapter diff --git a/lib/Enterprise/InputValidator.php b/lib/Enterprise/InputValidator.php index 944d86e..5beca64 100644 --- a/lib/Enterprise/InputValidator.php +++ b/lib/Enterprise/InputValidator.php @@ -9,8 +9,8 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Validation - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Validation + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/InputValidator.php * BRIEF: Input validation library @@ -31,7 +31,7 @@ declare(strict_types=1); * * SPDX-License-Identifier: GPL-3.0-or-later * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/Enterprise/ManifestParser.php b/lib/Enterprise/ManifestParser.php index 77653cd..baa49c4 100644 --- a/lib/Enterprise/ManifestParser.php +++ b/lib/Enterprise/ManifestParser.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/ManifestParser.php * BRIEF: Parser for the XML-based manifest.xml repository manifest @@ -28,7 +28,7 @@ use SimpleXMLElement; * Reads, writes, and validates the manifest.xml repository manifest. * The file uses XML format (no file extension) and lives at .mokogitea/manifest.xml. * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.07.00 */ class ManifestParser diff --git a/lib/Enterprise/MetricsCollector.php b/lib/Enterprise/MetricsCollector.php index bb06ee2..43d0d75 100644 --- a/lib/Enterprise/MetricsCollector.php +++ b/lib/Enterprise/MetricsCollector.php @@ -9,8 +9,8 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Metrics - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Metrics + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/MetricsCollector.php * BRIEF: Metrics collection framework @@ -46,7 +46,7 @@ declare(strict_types=1); * * SPDX-License-Identifier: GPL-3.0-or-later * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/Enterprise/MokoGiteaAdapter.php b/lib/Enterprise/MokoGiteaAdapter.php index 451731f..48737d1 100644 --- a/lib/Enterprise/MokoGiteaAdapter.php +++ b/lib/Enterprise/MokoGiteaAdapter.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Platform - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Platform + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/MokoGiteaAdapter.php * BRIEF: Gitea implementation of GitPlatformAdapter @@ -33,7 +33,7 @@ use RuntimeException; * - Branch protection: flat API (not rulesets) * - Workflow dir: .mokogitea/workflows * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @since 04.06.10 * @see GitPlatformAdapter */ diff --git a/lib/Enterprise/PackageBuilder.php b/lib/Enterprise/PackageBuilder.php index d0609de..01b0d33 100644 --- a/lib/Enterprise/PackageBuilder.php +++ b/lib/Enterprise/PackageBuilder.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise - * INGROUP: MokoPlatform.Lib + * DEFGROUP: MokoCLI.Enterprise + * INGROUP: MokoCLI.Lib * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/PackageBuilder.php * BRIEF: Builds release packages for generic, Dolibarr module, and Joomla component projects diff --git a/lib/Enterprise/PlatformAdapterFactory.php b/lib/Enterprise/PlatformAdapterFactory.php index 287c8a2..9d97724 100644 --- a/lib/Enterprise/PlatformAdapterFactory.php +++ b/lib/Enterprise/PlatformAdapterFactory.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Platform - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Platform + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/PlatformAdapterFactory.php * BRIEF: Factory for creating platform-specific GitPlatformAdapter instances @@ -33,7 +33,7 @@ use RuntimeException; * $repos = $adapter->listOrgRepos('mokoconsulting-tech'); * ``` * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.06.10 * * @since 04.00.00 diff --git a/lib/Enterprise/PluginFactory.php b/lib/Enterprise/PluginFactory.php index 526b567..fe09f42 100644 --- a/lib/Enterprise/PluginFactory.php +++ b/lib/Enterprise/PluginFactory.php @@ -9,8 +9,8 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/PluginFactory.php * BRIEF: Plugin factory for project type detection @@ -23,7 +23,7 @@ namespace MokoCli; * * Provides convenient methods for plugin instantiation with dependency injection * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 1.0.0 */ class PluginFactory diff --git a/lib/Enterprise/PluginRegistry.php b/lib/Enterprise/PluginRegistry.php index f3dcced..e24232a 100644 --- a/lib/Enterprise/PluginRegistry.php +++ b/lib/Enterprise/PluginRegistry.php @@ -9,8 +9,8 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/PluginRegistry.php * BRIEF: Plugin registry for available project plugins @@ -35,7 +35,7 @@ use MokoCli\Plugins\McpServerPlugin; * * Manages plugin discovery, registration, and lifecycle * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 1.0.0 */ class PluginRegistry diff --git a/lib/Enterprise/Plugins/ApiPlugin.php b/lib/Enterprise/Plugins/ApiPlugin.php index 9df5af0..9fbacb2 100644 --- a/lib/Enterprise/Plugins/ApiPlugin.php +++ b/lib/Enterprise/Plugins/ApiPlugin.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/Plugins/ApiPlugin.php * BRIEF: Enterprise plugin for API/Microservices projects diff --git a/lib/Enterprise/Plugins/DocumentationPlugin.php b/lib/Enterprise/Plugins/DocumentationPlugin.php index a8209f4..aa405c9 100644 --- a/lib/Enterprise/Plugins/DocumentationPlugin.php +++ b/lib/Enterprise/Plugins/DocumentationPlugin.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/Plugins/DocumentationPlugin.php * BRIEF: Enterprise plugin for documentation projects diff --git a/lib/Enterprise/Plugins/DolibarrPlugin.php b/lib/Enterprise/Plugins/DolibarrPlugin.php index c4f8e08..2b3d56a 100644 --- a/lib/Enterprise/Plugins/DolibarrPlugin.php +++ b/lib/Enterprise/Plugins/DolibarrPlugin.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/Plugins/DolibarrPlugin.php * BRIEF: Enterprise plugin for Dolibarr modules diff --git a/lib/Enterprise/Plugins/GenericPlugin.php b/lib/Enterprise/Plugins/GenericPlugin.php index d9ccf5f..faa098e 100644 --- a/lib/Enterprise/Plugins/GenericPlugin.php +++ b/lib/Enterprise/Plugins/GenericPlugin.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/Plugins/GenericPlugin.php * BRIEF: Enterprise plugin for generic projects diff --git a/lib/Enterprise/Plugins/JoomlaPlugin.php b/lib/Enterprise/Plugins/JoomlaPlugin.php index f763137..838706f 100644 --- a/lib/Enterprise/Plugins/JoomlaPlugin.php +++ b/lib/Enterprise/Plugins/JoomlaPlugin.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/Plugins/JoomlaPlugin.php * BRIEF: Enterprise plugin for Joomla projects diff --git a/lib/Enterprise/Plugins/McpServerPlugin.php b/lib/Enterprise/Plugins/McpServerPlugin.php index 63e8e16..0e75982 100644 --- a/lib/Enterprise/Plugins/McpServerPlugin.php +++ b/lib/Enterprise/Plugins/McpServerPlugin.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/Plugins/McpServerPlugin.php * BRIEF: Enterprise plugin for MCP (Model Context Protocol) server projects diff --git a/lib/Enterprise/Plugins/MobilePlugin.php b/lib/Enterprise/Plugins/MobilePlugin.php index b12318d..eaef4f3 100644 --- a/lib/Enterprise/Plugins/MobilePlugin.php +++ b/lib/Enterprise/Plugins/MobilePlugin.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/Plugins/MobilePlugin.php * BRIEF: Enterprise plugin for mobile app projects diff --git a/lib/Enterprise/Plugins/NodeJsPlugin.php b/lib/Enterprise/Plugins/NodeJsPlugin.php index 957a645..11182df 100644 --- a/lib/Enterprise/Plugins/NodeJsPlugin.php +++ b/lib/Enterprise/Plugins/NodeJsPlugin.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/Plugins/NodeJsPlugin.php * BRIEF: Enterprise plugin for Node.js/TypeScript projects diff --git a/lib/Enterprise/Plugins/PythonPlugin.php b/lib/Enterprise/Plugins/PythonPlugin.php index 016279c..a2e67dd 100644 --- a/lib/Enterprise/Plugins/PythonPlugin.php +++ b/lib/Enterprise/Plugins/PythonPlugin.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/Plugins/PythonPlugin.php * BRIEF: Enterprise plugin for Python projects diff --git a/lib/Enterprise/Plugins/TerraformPlugin.php b/lib/Enterprise/Plugins/TerraformPlugin.php index 8bb3622..31a4465 100644 --- a/lib/Enterprise/Plugins/TerraformPlugin.php +++ b/lib/Enterprise/Plugins/TerraformPlugin.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/Plugins/TerraformPlugin.php * BRIEF: Enterprise plugin for Terraform projects diff --git a/lib/Enterprise/Plugins/WordPressPlugin.php b/lib/Enterprise/Plugins/WordPressPlugin.php index ce53678..b3ac042 100644 --- a/lib/Enterprise/Plugins/WordPressPlugin.php +++ b/lib/Enterprise/Plugins/WordPressPlugin.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/Plugins/WordPressPlugin.php * BRIEF: Enterprise plugin for WordPress projects diff --git a/lib/Enterprise/ProjectConfigValidator.php b/lib/Enterprise/ProjectConfigValidator.php index d0ae799..56c8cb6 100644 --- a/lib/Enterprise/ProjectConfigValidator.php +++ b/lib/Enterprise/ProjectConfigValidator.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.ProjectTypes - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.ProjectTypes + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/ProjectConfigValidator.php * BRIEF: Enterprise library for validating project configurations diff --git a/lib/Enterprise/ProjectMetricsCollector.php b/lib/Enterprise/ProjectMetricsCollector.php index 531a396..5b355db 100644 --- a/lib/Enterprise/ProjectMetricsCollector.php +++ b/lib/Enterprise/ProjectMetricsCollector.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.ProjectTypes - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.ProjectTypes + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/ProjectMetricsCollector.php * BRIEF: Enterprise library for collecting project-specific metrics diff --git a/lib/Enterprise/ProjectPluginInterface.php b/lib/Enterprise/ProjectPluginInterface.php index b2f5b4b..1fc7574 100644 --- a/lib/Enterprise/ProjectPluginInterface.php +++ b/lib/Enterprise/ProjectPluginInterface.php @@ -9,8 +9,8 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Plugins - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Plugins + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/ProjectPluginInterface.php * BRIEF: Interface for project type plugins @@ -24,7 +24,7 @@ namespace MokoCli; * Each project type (Joomla, Node.js, Python, etc.) implements this interface * to provide type-specific validation, metrics, and management capabilities. * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 1.0.0 */ interface ProjectPluginInterface diff --git a/lib/Enterprise/ProjectTypeDetector.php b/lib/Enterprise/ProjectTypeDetector.php index bf57128..927e0a7 100644 --- a/lib/Enterprise/ProjectTypeDetector.php +++ b/lib/Enterprise/ProjectTypeDetector.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.ProjectTypes - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise.ProjectTypes + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/ProjectTypeDetector.php * BRIEF: Enterprise library for detecting project types diff --git a/lib/Enterprise/RecoveryError.php b/lib/Enterprise/RecoveryError.php index 3780c14..51855a0 100644 --- a/lib/Enterprise/RecoveryError.php +++ b/lib/Enterprise/RecoveryError.php @@ -10,13 +10,13 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Recovery - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Recovery + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/RecoveryError.php * BRIEF: Recovery error exception class * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/Enterprise/RecoveryManager.php b/lib/Enterprise/RecoveryManager.php index 013bb70..76d443f 100644 --- a/lib/Enterprise/RecoveryManager.php +++ b/lib/Enterprise/RecoveryManager.php @@ -10,13 +10,13 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Recovery - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Recovery + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/RecoveryManager.php * BRIEF: Recovery manager for failed operations * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/Enterprise/RecoverySuggestion.php b/lib/Enterprise/RecoverySuggestion.php index 87373fa..7d050b3 100644 --- a/lib/Enterprise/RecoverySuggestion.php +++ b/lib/Enterprise/RecoverySuggestion.php @@ -1,11 +1,10 @@ -#!/usr/bin/env php * SPDX-License-Identifier: GPL-3.0-or-later * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise - * INGROUP: MokoPlatform.Lib + * DEFGROUP: MokoCLI.Enterprise + * INGROUP: MokoCLI.Lib * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/RecoverySuggestion.php * BRIEF: Smart error recovery suggestions for validators @@ -76,7 +75,9 @@ class RecoverySuggestion */ public static function forMissingHeader(string $file): string { - return "Add SPDX license header to {$file}:\n /* Copyright (C) 2026 Moko Consulting \n * SPDX-License-Identifier: GPL-3.0-or-later */"; + return "Add SPDX license header to {$file}:\n" + . " /* Copyright (C) 2026 Moko Consulting \n" + . " * SPDX-License-Identifier: GPL-3.0-or-later */"; } /** diff --git a/lib/Enterprise/RepositoryHealthChecker.php b/lib/Enterprise/RepositoryHealthChecker.php index 38e666c..c345e38 100644 --- a/lib/Enterprise/RepositoryHealthChecker.php +++ b/lib/Enterprise/RepositoryHealthChecker.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/RepositoryHealthChecker.php * BRIEF: Repository health checking enterprise library diff --git a/lib/Enterprise/RepositorySynchronizer.php b/lib/Enterprise/RepositorySynchronizer.php index 51e0e57..219a24f 100644 --- a/lib/Enterprise/RepositorySynchronizer.php +++ b/lib/Enterprise/RepositorySynchronizer.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/RepositorySynchronizer.php * BRIEF: Repository synchronization enterprise library @@ -930,11 +930,11 @@ class RepositorySynchronizer private const TEMPLATE_REPOS = [ 'joomla' => 'MokoConsulting/Template-Joomla', 'waas-component' => 'MokoConsulting/Template-Joomla', - 'dolibarr' => 'MokoConsulting/Template-Dolibarr', - 'crm-module' => 'MokoConsulting/Template-Dolibarr', 'generic' => 'MokoConsulting/Template-Generic', 'mcp' => 'MokoConsulting/Template-Generic', - 'client' => 'MokoConsulting/Template-Client-WaaS', + 'dolibarr' => 'MokoConsulting/Template-Generic', + 'crm-module' => 'MokoConsulting/Template-Generic', + 'client' => 'MokoConsulting/Template-Client', ]; /** @@ -951,8 +951,7 @@ class RepositorySynchronizer */ private const TEMPLATE_SYNC_TARGETS = [ 'MokoConsulting/Template-Joomla', - 'MokoConsulting/Template-Dolibarr', - 'MokoConsulting/Template-Client-WaaS', + 'MokoConsulting/Template-Client', ]; /** @@ -1083,20 +1082,6 @@ class RepositorySynchronizer } } - // CODEOWNERS — GitHub only; Gitea doesn't enforce it - if ($this->adapter->getPlatformName() === 'github') { - $shared[] = ['templates/mokogitea/CODEOWNERS', '.github/CODEOWNERS']; - } - - // Platform-specific gitignore (merged, not replaced) - $gitignoreMap = [ - 'dolibarr' => 'templates/configs/gitignore.dolibarr', - 'platform' => 'templates/configs/gitignore.dolibarr', - 'joomla' => 'templates/configs/.gitignore.joomla', - ]; - $gitignoreTemplate = $gitignoreMap[$platform] ?? 'templates/configs/gitignore'; - $shared[] = [$gitignoreTemplate, '.gitignore']; - // Create TODO.md stub if it doesn't exist (gitignored after first commit) $entries[] = [ 'inline_content' => "# TODO\n\n> **Note:** This file is not tracked in " diff --git a/lib/Enterprise/RetryHelper.php b/lib/Enterprise/RetryHelper.php index 9898704..1de2328 100644 --- a/lib/Enterprise/RetryHelper.php +++ b/lib/Enterprise/RetryHelper.php @@ -10,13 +10,13 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Recovery - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Recovery + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/RetryHelper.php * BRIEF: Retry helper with exponential backoff * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/Enterprise/SecurityValidator.php b/lib/Enterprise/SecurityValidator.php index be3c0ac..c02996e 100644 --- a/lib/Enterprise/SecurityValidator.php +++ b/lib/Enterprise/SecurityValidator.php @@ -9,8 +9,8 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Security - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Security + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/SecurityValidator.php * BRIEF: Security validation library @@ -45,7 +45,7 @@ declare(strict_types=1); * * SPDX-License-Identifier: GPL-3.0-or-later * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/Enterprise/SourceResolver.php b/lib/Enterprise/SourceResolver.php index 8b582b0..7002a07 100644 --- a/lib/Enterprise/SourceResolver.php +++ b/lib/Enterprise/SourceResolver.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise - * INGROUP: MokoPlatform.Lib + * DEFGROUP: MokoCLI.Enterprise + * INGROUP: MokoCLI.Lib * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/SourceResolver.php * BRIEF: Resolve the root-level source directory across repos (source/, src/, htdocs/) diff --git a/lib/Enterprise/SynchronizationException.php b/lib/Enterprise/SynchronizationException.php index 86b6ca1..effad0f 100644 --- a/lib/Enterprise/SynchronizationException.php +++ b/lib/Enterprise/SynchronizationException.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Enterprise + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/SynchronizationException.php * BRIEF: Custom exception for repository synchronization errors diff --git a/lib/Enterprise/TransactionManager.php b/lib/Enterprise/TransactionManager.php index cf91c4a..c80659a 100644 --- a/lib/Enterprise/TransactionManager.php +++ b/lib/Enterprise/TransactionManager.php @@ -9,8 +9,8 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Transaction - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Transaction + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/TransactionManager.php * BRIEF: Transaction manager for atomic operations @@ -52,7 +52,7 @@ declare(strict_types=1); * * SPDX-License-Identifier: GPL-3.0-or-later * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/Enterprise/UnifiedValidation.php b/lib/Enterprise/UnifiedValidation.php index 8db0afe..a1cfa91 100644 --- a/lib/Enterprise/UnifiedValidation.php +++ b/lib/Enterprise/UnifiedValidation.php @@ -9,8 +9,8 @@ declare(strict_types=1); * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Enterprise.Validation - * INGROUP: MokoPlatform.Enterprise + * DEFGROUP: MokoCLI.Enterprise.Validation + * INGROUP: MokoCLI.Enterprise * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/Enterprise/UnifiedValidation.php * BRIEF: Unified validation framework @@ -51,7 +51,7 @@ declare(strict_types=1); * * SPDX-License-Identifier: GPL-3.0-or-later * - * @package MokoPlatform\Enterprise + * @package MokoCLI\Enterprise * @version 04.00.04 * @author mokocli Team * @license GPL-3.0-or-later diff --git a/lib/index.md b/lib/index.md index a76eac1..53019e6 100644 --- a/lib/index.md +++ b/lib/index.md @@ -2,8 +2,8 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION -DEFGROUP: MokoPlatform.Index -INGROUP: MokoPlatform.Lib +DEFGROUP: MokoCLI.Index +INGROUP: MokoCLI.Lib REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli PATH: /lib/index.md BRIEF: Library directory index diff --git a/lib/plugins/Joomla/UpdateXmlGenerator.php b/lib/plugins/Joomla/UpdateXmlGenerator.php index 505eafb..af0c623 100644 --- a/lib/plugins/Joomla/UpdateXmlGenerator.php +++ b/lib/plugins/Joomla/UpdateXmlGenerator.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Joomla - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Joomla + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /lib/plugins/Joomla/UpdateXmlGenerator.php * BRIEF: Generates and updates Joomla extension updates.xml files diff --git a/maintenance/index.md b/maintenance/index.md index e291914..89cd2b0 100644 --- a/maintenance/index.md +++ b/maintenance/index.md @@ -2,8 +2,8 @@ Copyright (C) 2026 Moko Consulting SPDX-License-Identifier: GPL-3.0-or-later FILE INFORMATION -DEFGROUP: MokoPlatform.Index -INGROUP: MokoPlatform.Maintenance +DEFGROUP: MokoCLI.Index +INGROUP: MokoCLI.Maintenance REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli PATH: /maintenance/index.md BRIEF: Maintenance directory index diff --git a/maintenance/pin_action_shas.php b/maintenance/pin_action_shas.php index 4d3c5f0..27ebdec 100644 --- a/maintenance/pin_action_shas.php +++ b/maintenance/pin_action_shas.php @@ -9,8 +9,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Maintenance - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Maintenance + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /maintenance/pin_action_shas.php * BRIEF: Pin GitHub Actions to immutable commit SHAs in workflow files diff --git a/maintenance/repo_inventory.php b/maintenance/repo_inventory.php index d019fd8..ef4f632 100644 --- a/maintenance/repo_inventory.php +++ b/maintenance/repo_inventory.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Maintenance - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Maintenance + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /maintenance/repo_inventory.php * BRIEF: Generate a live inventory dashboard of all governed repos as a GitHub issue diff --git a/maintenance/rotate_secrets.php b/maintenance/rotate_secrets.php index 882abf9..52abeba 100644 --- a/maintenance/rotate_secrets.php +++ b/maintenance/rotate_secrets.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Maintenance - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Maintenance + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /maintenance/rotate_secrets.php * BRIEF: Audit FTP secrets and variables across all governed repos -- report missing or stale diff --git a/maintenance/setup_labels.php b/maintenance/setup_labels.php index cebfd6e..34aea7d 100644 --- a/maintenance/setup_labels.php +++ b/maintenance/setup_labels.php @@ -10,8 +10,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Maintenance - * INGROUP: MokoPlatform + * 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 diff --git a/maintenance/sync_dolibarr_readmes.php b/maintenance/sync_dolibarr_readmes.php index 4658ff6..fc18b28 100644 --- a/maintenance/sync_dolibarr_readmes.php +++ b/maintenance/sync_dolibarr_readmes.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Maintenance - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Maintenance + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /maintenance/sync_dolibarr_readmes.php * BRIEF: Keeps root README.md and src/README.md in sync for Dolibarr module repositories @@ -77,7 +77,7 @@ class SyncDolibarrReadmes extends CliFramework $moduleName = $this->extractModuleName($rootContent, $repoRoot); $repoUrl = $this->extractField($rootContent, 'REPO', 'https://git.mokoconsulting.tech/MokoConsulting'); - $defgroup = $this->extractField($rootContent, 'DEFGROUP', 'MokoPlatform.Module'); + $defgroup = $this->extractField($rootContent, 'DEFGROUP', 'MokoCLI.Module'); $ingroup = $this->extractField($rootContent, 'INGROUP', 'mokocli'); $brief = $this->extractField($rootContent, 'BRIEF', "{$moduleName} end-user documentation"); diff --git a/maintenance/update_repo_inventory.php b/maintenance/update_repo_inventory.php index 1f4e175..ad8135b 100644 --- a/maintenance/update_repo_inventory.php +++ b/maintenance/update_repo_inventory.php @@ -9,8 +9,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Maintenance - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Maintenance + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /maintenance/update_repo_inventory.php * BRIEF: Queries GitHub org repos and rewrites the auto-generated section of REPOSITORY_INVENTORY.md diff --git a/maintenance/update_sha_hashes.php b/maintenance/update_sha_hashes.php index 7dbd1be..889d066 100755 --- a/maintenance/update_sha_hashes.php +++ b/maintenance/update_sha_hashes.php @@ -9,8 +9,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Maintenance - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Maintenance + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /maintenance/update_sha_hashes.php * BRIEF: Update SHA-256 hashes in script registry diff --git a/maintenance/update_version_from_readme.php b/maintenance/update_version_from_readme.php index 49b2173..3383a2a 100644 --- a/maintenance/update_version_from_readme.php +++ b/maintenance/update_version_from_readme.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Maintenance - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Maintenance + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * 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 diff --git a/mcp/servers/mokobackup/.mokogitea/CLAUDE.md b/mcp/servers/mokobackup/.mokogitea/CLAUDE.md index 7b51297..6184835 100644 --- a/mcp/servers/mokobackup/.mokogitea/CLAUDE.md +++ b/mcp/servers/mokobackup/.mokogitea/CLAUDE.md @@ -1,6 +1,6 @@ # mcp_mokobackup -MCP server for database and file backups across Dolibarr, Joomla/Akeeba, Gitea, and file-based environments. +MCP server for database and file backups across Dolibarr, Joomla/Akeeba, MokoGitea, and file-based environments. ## Quick Reference @@ -45,5 +45,5 @@ Default config at `~/.mcp_mokobackup.json`. Client repos override via `BACKUP_MC - **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 +- **Wiki**: documentation lives in the MokoGitea wiki, not `docs/` files - **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home) diff --git a/mcp/servers/mokobackup/README.md b/mcp/servers/mokobackup/README.md index 73c6de4..992d8a2 100644 --- a/mcp/servers/mokobackup/README.md +++ b/mcp/servers/mokobackup/README.md @@ -15,7 +15,7 @@ Model Context Protocol server for database dumps, file backups, and Akeeba Backu | **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) | +| **Platform** | [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/backup-mcp) (primary) | --- diff --git a/mcp/servers/mokocrm_api/.mokogitea/CLAUDE.md b/mcp/servers/mokocrm_api/.mokogitea/CLAUDE.md index 56a3168..a951bc9 100644 --- a/mcp/servers/mokocrm_api/.mokogitea/CLAUDE.md +++ b/mcp/servers/mokocrm_api/.mokogitea/CLAUDE.md @@ -40,5 +40,5 @@ src/ - **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 +- **Wiki**: documentation lives in the MokoGitea wiki, not `docs/` files - **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home) diff --git a/mcp/servers/mokocrm_api/CHANGELOG.md b/mcp/servers/mokocrm_api/CHANGELOG.md index 16f075c..a122524 100644 --- a/mcp/servers/mokocrm_api/CHANGELOG.md +++ b/mcp/servers/mokocrm_api/CHANGELOG.md @@ -53,7 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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 +- 12 MokoGitea Actions CI/CD workflows ## Revision History diff --git a/mcp/servers/mokocrm_api/CONTRIBUTING.md b/mcp/servers/mokocrm_api/CONTRIBUTING.md index d8bf7d2..78d7b88 100644 --- a/mcp/servers/mokocrm_api/CONTRIBUTING.md +++ b/mcp/servers/mokocrm_api/CONTRIBUTING.md @@ -14,7 +14,7 @@ DEFGROUP: dolibarr-api-mcp.Documentation INGROUP: dolibarr-api-mcp REPO: https://git.mokoconsulting.tech/MokoConsulting/dolibarr-api-mcp - VERSION: 09.37.07 + VERSION: 09.43.00 PATH: ./CONTRIBUTING.md BRIEF: Contribution guidelines for the project --> @@ -48,7 +48,7 @@ This project adheres to the Contributor Covenant Code of Conduct. By participati ### Reporting Bugs -- Use the Gitea issue tracker +- Use the MokoGitea issue tracker - Describe the bug clearly with steps to reproduce - Include the Dolibarr version you're connecting to - Include relevant logs or error messages @@ -143,7 +143,7 @@ Every repo maintains 5 standard release channel tags: ### CI/CD -- Gitea Actions runs all CI workflows +- MokoGitea Actions runs all CI workflows - Workflows live in `.gitea/workflows/` ### Secrets diff --git a/mcp/servers/mokocrm_api/README.md b/mcp/servers/mokocrm_api/README.md index 2eda4be..236e7fc 100644 --- a/mcp/servers/mokocrm_api/README.md +++ b/mcp/servers/mokocrm_api/README.md @@ -8,7 +8,7 @@ MCP server for Dolibarr ERP/CRM REST API operations |---|---| | **Language** | TypeScript | | **License** | GPL-3.0-or-later | -| **Platform** | [Gitea](https://git.mokoconsulting.tech/MokoConsulting/dolibarr-api-mcp) | +| **Platform** | [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/dolibarr-api-mcp) | | **Node.js** | >= 20.0.0 | | **MCP SDK** | @modelcontextprotocol/sdk ^1.12.1 | diff --git a/mcp/servers/mokocrm_api/SECURITY.md b/mcp/servers/mokocrm_api/SECURITY.md index c101202..9155c64 100644 --- a/mcp/servers/mokocrm_api/SECURITY.md +++ b/mcp/servers/mokocrm_api/SECURITY.md @@ -10,7 +10,7 @@ DEFGROUP: dolibarr-api-mcp.Documentation INGROUP: dolibarr-api-mcp REPO: https://git.mokoconsulting.tech/MokoConsulting/dolibarr-api-mcp PATH: /SECURITY.md -VERSION: 09.37.07 +VERSION: 09.43.00 BRIEF: Security vulnerability reporting and handling policy --> @@ -31,7 +31,7 @@ Only the current major version receives security updates. ## Reporting a Vulnerability -Report security vulnerabilities via Gitea issue (preferred): +Report security vulnerabilities via MokoGitea issue (preferred): https://git.mokoconsulting.tech/MokoConsulting/dolibarr-api-mcp/issues/new?template=security.yaml Or email: hello@mokoconsulting.tech diff --git a/mcp/servers/mokodreamhost/.mokogitea/CLAUDE.md b/mcp/servers/mokodreamhost/.mokogitea/CLAUDE.md index 5383398..2a8013a 100644 --- a/mcp/servers/mokodreamhost/.mokogitea/CLAUDE.md +++ b/mcp/servers/mokodreamhost/.mokogitea/CLAUDE.md @@ -38,5 +38,5 @@ src/ - **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 +- **Wiki**: documentation lives in the MokoGitea wiki, not `docs/` files - **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home) diff --git a/mcp/servers/mokogitea_api b/mcp/servers/mokogitea_api deleted file mode 160000 index 44e1259..0000000 --- a/mcp/servers/mokogitea_api +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 44e1259c3e9ecd1caeffd7717359fee9c29106d7 diff --git a/mcp/servers/mokogitea_skill/commands/deploy.md b/mcp/servers/mokogitea_skill/commands/deploy.md index c080ca6..35c0f4e 100644 --- a/mcp/servers/mokogitea_skill/commands/deploy.md +++ b/mcp/servers/mokogitea_skill/commands/deploy.md @@ -18,10 +18,10 @@ Build the current project and deploy to a target server. | Name | Host | Joomla Path | Dolibarr Path | |---|---|---|---| -| WAAS_DEV | waas.dev.mokoconsulting.tech | /home/mokoconsulting_dev/waas.dev.mokoconsulting.tech/ | — | -| WAAS_DEMO | waas.demo.mokoconsulting.tech | /home/mokoconsulting_demo/waas.demo.mokoconsulting.tech/ | — | +| WAAS_DEV | mokosuite.dev.mokoconsulting.tech | /home/mokoconsulting_dev/mokosuite.dev.mokoconsulting.tech/ | — | +| WAAS_DEMO | mokosuite.demo.mokoconsulting.tech | /home/mokoconsulting_demo/mokosuite.demo.mokoconsulting.tech/ | — | | WAAS_LIVE | mokoconsulting.tech | /home/mokoconsulting/mokoconsulting.tech/ | — | -| CRM_DEV | waas.dev.mokoconsulting.tech | — | /home/mokoconsulting_dev/crm.dev.mokoconsulting.tech/htdocs/custom/ | +| CRM_DEV | mokosuite.dev.mokoconsulting.tech | — | /home/mokoconsulting_dev/crm.dev.mokoconsulting.tech/htdocs/custom/ | | CRM_LIVE | crm.mokoconsulting.tech | — | /home/mokoconsulting_crm/crm.mokoconsulting.tech/htdocs/custom/ | ## Instructions @@ -39,7 +39,7 @@ Build the current project and deploy to a target server. - Verify build output exists 4. **Deploy:** - - **Joomla**: Use `mcp_mokowaas` tool to install ZIP via Joomla API, OR use `mcp_mokossh` to SFTP + run CLI installer + - **Joomla**: Use `mcp_mokosuite` tool to install ZIP via Joomla API, OR use `mcp_mokossh` to SFTP + run CLI installer - **Dolibarr**: Use `mcp_mokossh` to rsync/scp module directory to `htdocs/custom/` 5. **Post-deploy:** diff --git a/mcp/servers/mokogitea_skill/commands/mcp-rebuild.md b/mcp/servers/mokogitea_skill/commands/mcp-rebuild.md index 32e31bf..1c9e716 100644 --- a/mcp/servers/mokogitea_skill/commands/mcp-rebuild.md +++ b/mcp/servers/mokogitea_skill/commands/mcp-rebuild.md @@ -23,7 +23,7 @@ Rebuild MCP server(s) by running `npm install` and `npm run build`. | mcp_mokogitea_api | A:/MCP/mcp_mokogitea_api | Yes (TypeScript) | | mcp_mokomonitor | A:/MCP/mcp_mokomonitor | Yes (TypeScript) | | mcp_mokossh | A:/MCP/mcp_mokossh | No (plain JS) | -| mcp_mokowaas | A:/MCP/mcp_mokowaas | Yes (TypeScript) | +| mcp_mokosuite | A:/MCP/mcp_mokosuite | Yes (TypeScript) | | mcp_windows | A:/MCP/mcp_windows | Yes (TypeScript) | ## Instructions diff --git a/mcp/servers/mokogitea_skill/commands/new-client.md b/mcp/servers/mokogitea_skill/commands/new-client.md index 1580686..46e72d2 100644 --- a/mcp/servers/mokogitea_skill/commands/new-client.md +++ b/mcp/servers/mokogitea_skill/commands/new-client.md @@ -1,17 +1,17 @@ --- name: new-client -description: Scaffold a new WaaS client — Gitea org, theme repo, MCP config, wiki, SSH key +description: Scaffold a new MokoSuite client — MokoGitea org, theme repo, MCP config, wiki, SSH key allowed-tools: Bash, Read, Write, Edit, Glob, Grep --- -# New WaaS Client Setup +# New MokoSuite Client Setup -Scaffold everything needed for a new WaaS client deployment. +Scaffold everything needed for a new MokoSuite client deployment. ## Context - Current directory: !`pwd` -- Template repo: A:/Templates/Template-Client-WaaS/ +- Template repo: A:/Templates/Template-Client-MokoSuite/ ## Instructions @@ -22,19 +22,19 @@ Ask the user for: Then execute these steps: -### 1. Create Gitea Organization +### 1. Create MokoGitea Organization - Use `mcp_mokogitea_api` tool `gitea_org_create` - Org name: PascalCase of slug (e.g. "AcmeCorp") -- Description: "WaaS client: {Client name}" +- Description: "MokoSuite client: {Client name}" ### 2. Create Client Repository - Use `mcp_mokogitea_api` tool `gitea_repo_create` in the new org -- Repo name: `client-waas-{slug}` -- Initialize from Template-Client-WaaS if possible, otherwise create empty and copy files +- Repo name: `client-mokosuite-{slug}` +- Initialize from Template-Client-MokoSuite if possible, otherwise create empty and copy files ### 3. Clone and Scaffold Locally - Clone to `A:/client-{slug}/` -- Copy template files from `A:/Templates/Template-Client-WaaS/` +- Copy template files from `A:/Templates/Template-Client-MokoSuite/` - Update `src/*.xml` manifest: - `MokoOnyx Theme — {Client Name}` - `file_mokoonyx_{slug}` @@ -44,7 +44,7 @@ Then execute these steps: ### 4. Create SSH Key - Generate key pair at `C:/Users/jmill/OneDrive/Documents/Keys/repos/client-{slug}` -- Add public key as deploy key on the Gitea repo +- Add public key as deploy key on the MokoGitea repo ### 5. Setup GitHub Mirror - Use `gitea_repo_mirror_setup_github_backup_full` to create GitHub backup mirror diff --git a/mcp/servers/mokogitea_skill/commands/release.md b/mcp/servers/mokogitea_skill/commands/release.md index 9a8812d..adc240b 100644 --- a/mcp/servers/mokogitea_skill/commands/release.md +++ b/mcp/servers/mokogitea_skill/commands/release.md @@ -1,6 +1,6 @@ --- name: release -description: Create a release — build ZIP, tag, update updates.xml, create Gitea release +description: Create a release — build ZIP, tag, update updates.xml, create MokoGitea release allowed-tools: Bash, Read, Write, Edit, Glob, Grep --- @@ -42,12 +42,12 @@ Create a release for the current repository. - Create annotated tag: `git tag -a vX.Y.Z -m "Release X.Y.Z"` - Push commits and tags: `git push origin && git push origin --tags` -6. **Create Gitea release:** +6. **Create MokoGitea release:** - Use `mcp_mokogitea_api` tool `gitea_release_create` with the tag - Upload the built ZIP as a release asset via `gitea_release_upload_asset` 7. **Joomla-specific: update updates.xml:** - - Prepend new `` block with version, download URL pointing to Gitea release asset + - Prepend new `` block with version, download URL pointing to MokoGitea release asset - ZIP filename follows Joomla convention from feedback_joomla_release_naming memory - Commit and push the updates.xml change diff --git a/mcp/servers/mokogitea_skill/commands/server-health.md b/mcp/servers/mokogitea_skill/commands/server-health.md index 1f8d042..5812734 100644 --- a/mcp/servers/mokogitea_skill/commands/server-health.md +++ b/mcp/servers/mokogitea_skill/commands/server-health.md @@ -17,23 +17,23 @@ Run health checks across all infrastructure servers using SSH. Use `mcp_mokossh` MCP tools to run health checks on each server: ### 1. GIT Server (git.mokoconsulting.tech) -- `systemctl status gitea` — is Gitea running? +- `systemctl status gitea` — is MokoGitea running? - `systemctl status act_runner` — is the Actions runner running? - `df -h /` — disk space -- `du -sh /var/lib/gitea/repositories/ /var/lib/gitea/data/ /var/lib/gitea/log/` — Gitea storage breakdown +- `du -sh /var/lib/gitea/repositories/ /var/lib/gitea/data/ /var/lib/gitea/log/` — MokoGitea storage breakdown - `uptime` — load average - `free -h` — memory usage - `certbot certificates 2>/dev/null | grep -E "Expiry|Domains"` — SSL cert expiry - `fail2ban-client status sshd 2>/dev/null | grep "Total banned"` — banned IPs -### 2. WAAS_DEV (waas.dev.mokoconsulting.tech) +### 2. WAAS_DEV (mokosuite.dev.mokoconsulting.tech) - `df -h /` — disk space - `uptime` — load average - `free -h` — memory - `php -v | head -1` — PHP version - Check Joomla health endpoint if available -### 3. WAAS_DEMO (waas.demo.mokoconsulting.tech) +### 3. WAAS_DEMO (mokosuite.demo.mokoconsulting.tech) - Same checks as WAAS_DEV ### 4. WAAS_LIVE (mokoconsulting.tech) diff --git a/mcp/servers/mokogitea_skill/skills/mokogitea/SKILL.md b/mcp/servers/mokogitea_skill/skills/mokogitea/SKILL.md index f4af8ee..ee28db1 100644 --- a/mcp/servers/mokogitea_skill/skills/mokogitea/SKILL.md +++ b/mcp/servers/mokogitea_skill/skills/mokogitea/SKILL.md @@ -1,29 +1,29 @@ --- name: mokogitea -description: "MokoGitea server infrastructure, SSH commands, deployment, backup, and administration. Use when the user mentions: gitea, mokogitea, git server, deploy, deployment, restart gitea, ssh into, server status, backup gitea, restore, mirror, github backup, update server, gitea actions, CI/CD, runner, waas server, crm server, server health, disk space, systemctl, nginx, certbot, fail2ban, firewall, server logs, gitea logs, or any remote server operation." -when_to_use: "Auto-trigger when discussing: server management, SSH operations, Gitea administration, deployments, backups, mirrors, CI runners, or any infrastructure task involving git.mokoconsulting.tech or the MokoGitea instance." +description: "MokoGitea server infrastructure, SSH commands, deployment, backup, and administration. Use when the user mentions: gitea, mokogitea, git server, deploy, deployment, restart gitea, ssh into, server status, backup gitea, restore, mirror, github backup, update server, gitea actions, CI/CD, runner, mokosuite server, crm server, server health, disk space, systemctl, nginx, certbot, fail2ban, firewall, server logs, gitea logs, or any remote server operation." +when_to_use: "Auto-trigger when discussing: server management, SSH operations, MokoGitea administration, deployments, backups, mirrors, CI runners, or any infrastructure task involving git.mokoconsulting.tech or the MokoGitea instance." --- # MokoGitea Infrastructure Reference -You are helping with MokoGitea server infrastructure. Use the `mcp_mokossh` MCP tools for SSH commands and `mcp_mokogitea_api` tools for Gitea API operations. +You are helping with MokoGitea server infrastructure. Use the `mcp_mokossh` MCP tools for SSH commands and `mcp_mokogitea_api` tools for MokoGitea API operations. ## Server Map | Name | Host | User | Port | Purpose | |---|---|---|---|---| -| GIT | git.mokoconsulting.tech | mokoconsulting | 2918 | MokoGitea instance (Gitea fork) | -| WAAS_DEV | waas.dev.mokoconsulting.tech | mokoconsulting_dev | 22 | WaaS dev (Joomla + Dolibarr) | -| WAAS_DEMO | waas.demo.mokoconsulting.tech | mokoconsulting_demo | 22 | WaaS demo | -| WAAS_LIVE | mokoconsulting.tech | mokoconsulting | 22 | WaaS production | -| CRM_DEV | waas.dev.mokoconsulting.tech | mokoconsulting_dev | 22 | CRM dev (shared host with WAAS_DEV) | +| GIT | git.mokoconsulting.tech | mokoconsulting | 2918 | MokoGitea instance (MokoGitea fork) | +| WAAS_DEV | mokosuite.dev.mokoconsulting.tech | mokoconsulting_dev | 22 | MokoSuite dev (Joomla + Dolibarr) | +| WAAS_DEMO | mokosuite.demo.mokoconsulting.tech | mokoconsulting_demo | 22 | MokoSuite demo | +| WAAS_LIVE | mokoconsulting.tech | mokoconsulting | 22 | MokoSuite production | +| CRM_DEV | mokosuite.dev.mokoconsulting.tech | mokoconsulting_dev | 22 | CRM dev (shared host with WAAS_DEV) | | CRM_LIVE | crm.mokoconsulting.tech | mokoconsulting_crm | 22 | CRM production | SSH key: `jmiller_private.openssh` (all MCP connections) ## Common SSH Commands -### Gitea Server (GIT) +### MokoGitea Server (GIT) ```bash # Service management @@ -35,12 +35,12 @@ sudo systemctl stop gitea sudo journalctl -u gitea -f # Follow live logs sudo journalctl -u gitea --since "1 hour ago" # Recent logs -# Gitea CLI (run as git user) +# MokoGitea CLI (run as git user) sudo -u git /usr/local/bin/gitea admin user list sudo -u git /usr/local/bin/gitea admin auth list sudo -u git /usr/local/bin/gitea doctor check -# Gitea Actions runner +# MokoGitea Actions runner sudo systemctl status act_runner sudo systemctl restart act_runner sudo journalctl -u act_runner -f @@ -73,12 +73,12 @@ sudo fail2ban-client status sudo fail2ban-client status sshd ``` -### WaaS Servers (WAAS_DEV / WAAS_DEMO / WAAS_LIVE) +### MokoSuite Servers (WAAS_DEV / WAAS_DEMO / WAAS_LIVE) ```bash # Joomla paths -# Dev: /home/mokoconsulting_dev/waas.dev.mokoconsulting.tech/ -# Demo: /home/mokoconsulting_demo/waas.demo.mokoconsulting.tech/ +# Dev: /home/mokoconsulting_dev/mokosuite.dev.mokoconsulting.tech/ +# Demo: /home/mokoconsulting_demo/mokosuite.demo.mokoconsulting.tech/ # Live: /home/mokoconsulting/mokoconsulting.tech/ # Check Joomla version @@ -125,7 +125,7 @@ ls htdocs/custom/ mysql -u root dolibarr -e "SELECT name, value FROM llx_const WHERE name LIKE '%VERSION%';" ``` -## Gitea API Operations +## MokoGitea API Operations Use `mcp_mokogitea_api` MCP tools for API operations: @@ -144,17 +144,17 @@ Use `mcp_mokobackup` MCP tools: | Target | Type | What it backs up | |---|---|---| -| gitea-db | mysql | Gitea MySQL database | +| gitea-db | mysql | MokoGitea MySQL database | | gitea-files | files | `/var/lib/gitea/` (repos, data, avatars) | -| waas-dev | akeeba | Joomla dev site via Akeeba API | -| waas-demo | akeeba | Joomla demo site | -| waas-live | akeeba | Joomla production site | +| mokosuite-dev | akeeba | Joomla dev site via Akeeba API | +| mokosuite-demo | akeeba | Joomla demo site | +| mokosuite-live | akeeba | Joomla production site | | crm-live | dolibarr | Dolibarr production (DB + documents + custom) | | crm-dev | dolibarr | Dolibarr dev | ## Deployment -Deployments are handled by **Gitea Actions workflows** (not manual SSH): +Deployments are handled by **MokoGitea Actions workflows** (not manual SSH): - Push to `dev` → CI runs (lint, build, validate) - Merge PR to `main` → release workflow builds ZIP, creates release, deploys via SFTP - Client sites: `client-release.yml` workflow handles theme package deployment @@ -171,12 +171,12 @@ All repos mirror to GitHub (mokoconsulting-tech org) as backup: | Path | Contents | |---|---| -| `/usr/local/bin/gitea` | Gitea binary | +| `/usr/local/bin/gitea` | MokoGitea binary | | `/etc/gitea/app.ini` | Main configuration | -| `/var/lib/gitea/` | All Gitea data | +| `/var/lib/gitea/` | All MokoGitea data | | `/var/lib/gitea/repositories/` | Git bare repos | | `/var/lib/gitea/data/` | Attachments, avatars, LFS | -| `/var/lib/gitea/log/` | Gitea logs | +| `/var/lib/gitea/log/` | MokoGitea logs | | `/var/lib/gitea/custom/` | Custom templates, public files | | `/etc/nginx/sites-enabled/gitea.conf` | Nginx reverse proxy config | | `/home/git/.act_runner/` | Actions runner config | @@ -184,7 +184,7 @@ All repos mirror to GitHub (mokoconsulting-tech org) as backup: ## Important Notes - Port 2918 on GIT server is **shell SSH** (not just git protocol) — full command execution -- Gitea repo names on server use **hyphens** (e.g. `mcp-mokobackup`), local dirs use **underscores** +- MokoGitea repo names on server use **hyphens** (e.g. `mcp-mokobackup`), local dirs use **underscores** - `mokocli` CLI tools handle CI checks — don't inline bash in workflows - All infra docs live in **mokogitea-private wiki**, not public repos - Two master SSH keys (jmiller + moko) on all servers diff --git a/mcp/servers/mokomonitor/.mokogitea/.moko-platform b/mcp/servers/mokomonitor/.mokogitea/.moko-cli similarity index 100% rename from mcp/servers/mokomonitor/.mokogitea/.moko-platform rename to mcp/servers/mokomonitor/.mokogitea/.moko-cli diff --git a/mcp/servers/mokomonitor/.mokogitea/CLAUDE.md b/mcp/servers/mokomonitor/.mokogitea/CLAUDE.md index de7166d..cf6f36a 100644 --- a/mcp/servers/mokomonitor/.mokogitea/CLAUDE.md +++ b/mcp/servers/mokomonitor/.mokogitea/CLAUDE.md @@ -33,12 +33,12 @@ src/ - Config defines **connections** for health checks + optional **Grafana** config - Sites list at `A:/mokocli/monitoring/sites.json` for bulk monitoring -- Grafana at bench.mokoconsulting.tech — WaaS dashboard for uptime/performance +- Grafana at bench.mokoconsulting.tech — MokoSuite dashboard for uptime/performance ## 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 +- **Wiki**: documentation lives in the MokoGitea wiki, not `docs/` files - **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home) diff --git a/mcp/servers/mokomonitor/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md b/mcp/servers/mokomonitor/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md index e7031a8..7f77801 100644 --- a/mcp/servers/mokomonitor/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md +++ b/mcp/servers/mokomonitor/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md @@ -18,7 +18,7 @@ assignees: '' - [ ] API response error (4xx / 5xx) ### MCP Server -- **Server Name**: [e.g., mcp_mokowaas] +- **Server Name**: [e.g., mcp_mokosuite] - **Server Version**: [e.g., 1.0.0] - **Node.js Version**: [e.g., 20.x] diff --git a/mcp/servers/mokomonitor/README.md b/mcp/servers/mokomonitor/README.md index 44eba64..2e19890 100644 --- a/mcp/servers/mokomonitor/README.md +++ b/mcp/servers/mokomonitor/README.md @@ -15,7 +15,7 @@ MCP server for infrastructure monitoring -- server health, Grafana dashboards, a | **Language** | Node.js | | **License** | GPL-3.0-or-later | | **Config** | `~/.monitor-mcp.json` | -| **Platform** | [Gitea](https://git.mokoconsulting.tech/MokoConsulting/monitor-mcp) (primary) | +| **Platform** | [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/monitor-mcp) (primary) | --- diff --git a/mcp/servers/mokomonitor/mcp_mokomonitor/.mokogitea/.moko-platform b/mcp/servers/mokomonitor/mcp_mokomonitor/.mokogitea/.moko-cli similarity index 100% rename from mcp/servers/mokomonitor/mcp_mokomonitor/.mokogitea/.moko-platform rename to mcp/servers/mokomonitor/mcp_mokomonitor/.mokogitea/.moko-cli diff --git a/mcp/servers/mokomonitor/mcp_mokomonitor/.mokogitea/CLAUDE.md b/mcp/servers/mokomonitor/mcp_mokomonitor/.mokogitea/CLAUDE.md index de7166d..cf6f36a 100644 --- a/mcp/servers/mokomonitor/mcp_mokomonitor/.mokogitea/CLAUDE.md +++ b/mcp/servers/mokomonitor/mcp_mokomonitor/.mokogitea/CLAUDE.md @@ -33,12 +33,12 @@ src/ - Config defines **connections** for health checks + optional **Grafana** config - Sites list at `A:/mokocli/monitoring/sites.json` for bulk monitoring -- Grafana at bench.mokoconsulting.tech — WaaS dashboard for uptime/performance +- Grafana at bench.mokoconsulting.tech — MokoSuite dashboard for uptime/performance ## 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 +- **Wiki**: documentation lives in the MokoGitea wiki, not `docs/` files - **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home) diff --git a/mcp/servers/mokomonitor/mcp_mokomonitor/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md b/mcp/servers/mokomonitor/mcp_mokomonitor/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md index e7031a8..7f77801 100644 --- a/mcp/servers/mokomonitor/mcp_mokomonitor/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md +++ b/mcp/servers/mokomonitor/mcp_mokomonitor/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md @@ -18,7 +18,7 @@ assignees: '' - [ ] API response error (4xx / 5xx) ### MCP Server -- **Server Name**: [e.g., mcp_mokowaas] +- **Server Name**: [e.g., mcp_mokosuite] - **Server Version**: [e.g., 1.0.0] - **Node.js Version**: [e.g., 20.x] diff --git a/mcp/servers/mokossh/.mokogitea/CLAUDE.md b/mcp/servers/mokossh/.mokogitea/CLAUDE.md index b53d270..3660b71 100644 --- a/mcp/servers/mokossh/.mokogitea/CLAUDE.md +++ b/mcp/servers/mokossh/.mokogitea/CLAUDE.md @@ -52,10 +52,10 @@ src/ | Name | Host | User | Port | |---|---|---|---| | GIT | git.mokoconsulting.tech | mokoconsulting | 2918 | -| WAAS_DEV | waas.dev.mokoconsulting.tech | mokoconsulting_dev | 22 | -| WAAS_DEMO | waas.demo.mokoconsulting.tech | mokoconsulting_demo | 22 | +| WAAS_DEV | mokosuite.dev.mokoconsulting.tech | mokoconsulting_dev | 22 | +| WAAS_DEMO | mokosuite.demo.mokoconsulting.tech | mokoconsulting_demo | 22 | | WAAS_LIVE | mokoconsulting.tech | mokoconsulting | 22 | -| CRM_DEV | waas.dev.mokoconsulting.tech | mokoconsulting_dev | 22 | +| CRM_DEV | mokosuite.dev.mokoconsulting.tech | mokoconsulting_dev | 22 | | CRM_LIVE | crm.mokoconsulting.tech | mokoconsulting_crm | 22 | SSH key: `jmiller_private.openssh` diff --git a/mcp/servers/mokossh/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md b/mcp/servers/mokossh/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md index e7031a8..7f77801 100644 --- a/mcp/servers/mokossh/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md +++ b/mcp/servers/mokossh/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md @@ -18,7 +18,7 @@ assignees: '' - [ ] API response error (4xx / 5xx) ### MCP Server -- **Server Name**: [e.g., mcp_mokowaas] +- **Server Name**: [e.g., mcp_mokosuite] - **Server Version**: [e.g., 1.0.0] - **Node.js Version**: [e.g., 20.x] diff --git a/mcp/servers/mokossh/README.md b/mcp/servers/mokossh/README.md index d51cfb9..5bdd34f 100644 --- a/mcp/servers/mokossh/README.md +++ b/mcp/servers/mokossh/README.md @@ -16,7 +16,7 @@ Model Context Protocol server for SSH remote server management -- execute comman | **Tools** | 37 tools across 6 groups | | **License** | MIT | | **Upstream** | [bvisible/mcp-ssh-manager](https://github.com/bvisible/mcp-ssh-manager) v3.4.0 | -| **Platform** | [Gitea](https://git.mokoconsulting.tech/MokoConsulting/ssh-mcp) (primary) | +| **Platform** | [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/ssh-mcp) (primary) | --- diff --git a/mcp/servers/mokosuite_api/.mokogitea/CLAUDE.md b/mcp/servers/mokosuite_api/.mokogitea/CLAUDE.md index 2548604..c59f11f 100644 --- a/mcp/servers/mokosuite_api/.mokogitea/CLAUDE.md +++ b/mcp/servers/mokosuite_api/.mokogitea/CLAUDE.md @@ -31,7 +31,7 @@ src/ ``` - Config defines **connections** — each is a Joomla site with URL + API token -- Default connections: moko-live (mokoconsulting.tech), moko-dev (waas.dev), moko-demo (waas.demo) +- Default connections: moko-live (mokoconsulting.tech), moko-dev (mokosuite.dev), moko-demo (mokosuite.demo) - Client sites added via client repo config overrides ## Rules @@ -39,5 +39,5 @@ src/ - **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 +- **Wiki**: documentation lives in the MokoGitea wiki, not `docs/` files - **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home) diff --git a/mcp/servers/mokosuite_api/CONTRIBUTING.md b/mcp/servers/mokosuite_api/CONTRIBUTING.md index a6b0b98..18f45db 100644 --- a/mcp/servers/mokosuite_api/CONTRIBUTING.md +++ b/mcp/servers/mokosuite_api/CONTRIBUTING.md @@ -14,7 +14,7 @@ DEFGROUP: INGROUP: Project.Documentation REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCli-Template-Generic - VERSION: 09.37.07 + VERSION: 09.43.00 PATH: ./CONTRIBUTING.md BRIEF: Contribution guidelines for the project --> @@ -148,13 +148,13 @@ Every repo maintains 5 standard release channel tags: ### CI/CD -- Gitea Actions runs all CI workflows +- MokoGitea Actions runs all CI workflows - GitHub Actions are disabled on mirrored repos - Workflows live in both `.github/workflows/` and `.gitea/workflows/` ### Update Servers (Joomla) -In manifest ``, Gitea must be priority 1, GitHub priority 2. +In manifest ``, MokoGitea must be priority 1, GitHub priority 2. ### Secrets diff --git a/mcp/servers/mokosuite_api/README.md b/mcp/servers/mokosuite_api/README.md index 4e6041c..a36f143 100644 --- a/mcp/servers/mokosuite_api/README.md +++ b/mcp/servers/mokosuite_api/README.md @@ -15,7 +15,7 @@ Model Context Protocol server for the Joomla Web Services API -- full CRUD for a | **Language** | Node.js | | **Tools** | 67 tools across 15 resource groups | | **License** | GPL-3.0-or-later | -| **Platform** | [Gitea](https://git.mokoconsulting.tech/MokoConsulting/joomla-api-mcp) (primary) | +| **Platform** | [MokoGitea](https://git.mokoconsulting.tech/MokoConsulting/joomla-api-mcp) (primary) | --- diff --git a/mcp/servers/mokosuite_api/SECURITY.md b/mcp/servers/mokosuite_api/SECURITY.md index b6d483b..0b677e6 100644 --- a/mcp/servers/mokosuite_api/SECURITY.md +++ b/mcp/servers/mokosuite_api/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME] INGROUP: [PROJECT_NAME].Documentation REPO: [REPOSITORY_URL] PATH: /SECURITY.md -VERSION: 09.37.07 +VERSION: 09.43.00 BRIEF: Security vulnerability reporting and handling policy --> @@ -46,7 +46,7 @@ Only the current major version receives security updates. Users should upgrade t ## Reporting a Vulnerability -Report security vulnerabilities via Gitea issue (preferred): +Report security vulnerabilities via MokoGitea issue (preferred): https://git.mokoconsulting.tech/MokoConsulting/MokoCli-Template-Generic/issues/new?template=security.yaml Or email: hello@mokoconsulting.tech diff --git a/mcp/servers/windows/.mokogitea/CLAUDE.md b/mcp/servers/windows/.mokogitea/CLAUDE.md index 8e557f0..fe7480d 100644 --- a/mcp/servers/windows/.mokogitea/CLAUDE.md +++ b/mcp/servers/windows/.mokogitea/CLAUDE.md @@ -40,5 +40,5 @@ src/ - **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 +- **Wiki**: documentation lives in the MokoGitea wiki, not `docs/` files - **Standards**: [MokoCli](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home) diff --git a/mcp/servers/windows/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md b/mcp/servers/windows/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md index e7031a8..7f77801 100644 --- a/mcp/servers/windows/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md +++ b/mcp/servers/windows/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md @@ -18,7 +18,7 @@ assignees: '' - [ ] API response error (4xx / 5xx) ### MCP Server -- **Server Name**: [e.g., mcp_mokowaas] +- **Server Name**: [e.g., mcp_mokosuite] - **Server Version**: [e.g., 1.0.0] - **Node.js Version**: [e.g., 20.x] diff --git a/mcp/servers/windows/mcp_windows b/mcp/servers/windows/mcp_windows deleted file mode 160000 index 4ae12d4..0000000 --- a/mcp/servers/windows/mcp_windows +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4ae12d459bd5d8402fac3a282464b73c37aa85f9 diff --git a/phpcs.xml b/phpcs.xml index 67b03e7..1850ac1 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -12,7 +12,6 @@ SPDX-License-Identifier: GPL-3.0-or-later lib validate - automation cli diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9bf9bce..532e2e5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2363,3 +2363,711 @@ parameters: identifier: deadCode.unreachable count: 2 path: validate/scan_drift.php + - + message: '#^Function apiRequest\(\) has parameter \$body with no value type specified in iterable type array\.$#' + count: 1 + path: cli/branch_protect_org.php + - + message: '#^Function apiRequest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/branch_protect_org.php + - + message: '#^Method BranchRenameCli\:\:apiRequest\(\) has parameter \$body with no value type specified in iterable type array\.$#' + count: 1 + path: cli/branch_rename.php + - + message: '#^Method BranchRenameCli\:\:apiRequest\(\) has parameter \$headers with no value type specified in iterable type array\.$#' + count: 1 + path: cli/branch_rename.php + - + message: '#^Method BranchRenameCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/branch_rename.php + - + message: '#^Method BulkWorkflowPushCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/bulk_workflow_push.php + - + message: '#^Method BulkWorkflowPushCli\:\:fetchOrgRepos\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/bulk_workflow_push.php + - + message: '#^Method BulkWorkflowTriggerCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/bulk_workflow_trigger.php + - + message: '#^Method BulkWorkflowTriggerCli\:\:buildRepoList\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/bulk_workflow_trigger.php + - + message: '#^Method ClientDashboardCli\:\:discoverClients\(\) never returns null so it can be removed from the return type\.$#' + count: 1 + path: cli/client_dashboard.php + - + message: '#^Method ClientInventoryCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/client_inventory.php + - + message: '#^Method ClientInventoryCli\:\:checkVariables\(\) has parameter \$requiredVars with no value type specified in iterable type array\.$#' + count: 1 + path: cli/client_inventory.php + - + message: '#^Method ClientInventoryCli\:\:fetchOrgRepos\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/client_inventory.php + - + message: '#^Method ClientInventoryCli\:\:fetchOrgs\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/client_inventory.php + - + message: '#^Method ClientProvisionCli\:\:giteaApi\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/client_provision.php + - + message: '#^Method ClientProvisionCli\:\:grafanaApi\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/client_provision.php + - + message: '#^Method ClientProvisionCli\:\:httpRequest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/client_provision.php + - + message: '#^Method ClientProvisionCli\:\:printSummary\(\) overrides method MokoCli\\CliFramework\:\:printSummary\(\) but misses parameter \#1 \$passed\.$#' + count: 1 + path: cli/client_provision.php + - + message: '#^Method ClientProvisionCli\:\:printSummary\(\) overrides method MokoCli\\CliFramework\:\:printSummary\(\) but misses parameter \#2 \$failed\.$#' + count: 1 + path: cli/client_provision.php + - + message: '#^Method ClientProvisionCli\:\:printSummary\(\) overrides method MokoCli\\CliFramework\:\:printSummary\(\) but misses parameter \#3 \$elapsed\.$#' + count: 1 + path: cli/client_provision.php + - + message: '#^Private method ClientProvisionCli\:\:printSummary\(\) overriding protected method MokoCli\\CliFramework\:\:printSummary\(\) should be protected or public\.$#' + count: 1 + path: cli/client_provision.php + - + message: '#^Return type int of method ClientProvisionCli\:\:printSummary\(\) is not covariant with return type void of method MokoCli\\CliFramework\:\:printSummary\(\)\.$#' + count: 1 + path: cli/client_provision.php + - + message: '#^Method CompletionCli\:\:generateBash\(\) has parameter \$commands with no value type specified in iterable type array\.$#' + count: 1 + path: cli/completion.php + - + message: '#^Method CompletionCli\:\:generateBash\(\) has parameter \$flags with no value type specified in iterable type array\.$#' + count: 1 + path: cli/completion.php + - + message: '#^Method CompletionCli\:\:generateZsh\(\) has parameter \$commands with no value type specified in iterable type array\.$#' + count: 1 + path: cli/completion.php + - + message: '#^Method CompletionCli\:\:generateZsh\(\) has parameter \$flags with no value type specified in iterable type array\.$#' + count: 1 + path: cli/completion.php + - + message: '#^Method CreateProjectCli\:\:createProject\(\) has parameter \$template with no value type specified in iterable type array\.$#' + count: 1 + path: cli/create_project.php + - + message: '#^Method CreateProjectCli\:\:graphql\(\) has parameter \$variables with no value type specified in iterable type array\.$#' + count: 1 + path: cli/create_project.php + - + message: '#^Method CreateProjectCli\:\:graphql\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/create_project.php + - + message: '#^Method CreateProjectCli\:\:parseTemplate\(\) return type has no value type specified in iterable type array\.$#' + count: 2 + path: cli/create_project.php + - + message: '#^Method CreateProjectCli\:\:restGet\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/create_project.php + - + message: '#^Method DeployJoomla\:\:buildDeployMap\(\) has parameter \$ext with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:dryRunWalk\(\) has parameter \$deployMap with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:firstExistingDir\(\) has parameter \$candidates with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:getManifestDestination\(\) has parameter \$ext with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:getManifestFilename\(\) has parameter \$ext with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapComponent\(\) has parameter \$ext with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapComponent\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapFile\(\) has parameter \$ext with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapFile\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapLanguage\(\) has parameter \$ext with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapLanguage\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapLibrary\(\) has parameter \$ext with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapLibrary\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapModule\(\) has parameter \$ext with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapModule\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapPackage\(\) has parameter \$ext with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapPackage\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapPlugin\(\) has parameter \$ext with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapPlugin\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapTemplate\(\) has parameter \$ext with no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method DeployJoomla\:\:mapTemplate\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Parameter \#2 \.\.\.\$args of method phpseclib3\\Net\\SSH2\:\:login\(\) expects array\\|phpseclib3\\Crypt\\Common\\PrivateKey\|phpseclib3\\System\\SSH\\Agent\|string\|null, phpseclib3\\Crypt\\Common\\AsymmetricKey given\.$#' + count: 1 + path: cli/deploy_joomla.php + - + message: '#^Method GrafanaDashboardCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/grafana_dashboard.php + - + message: '#^Method JoomlaBuildCli\:\:parseManifest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/joomla_build.php + - + message: '#^Method JoomlaBuildCli\:\:typePrefix\(\) has parameter \$meta with no value type specified in iterable type array\.$#' + count: 1 + path: cli/joomla_build.php + - + message: '#^Method JoomlaMetadataValidateCli\:\:compare\(\) has parameter \$joomlaXml with no value type specified in iterable type array\.$#' + count: 1 + path: cli/joomla_metadata_validate.php + - + message: '#^Method JoomlaMetadataValidateCli\:\:compare\(\) has parameter \$metadata with no value type specified in iterable type array\.$#' + count: 1 + path: cli/joomla_metadata_validate.php + - + message: '#^Method JoomlaMetadataValidateCli\:\:compare\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/joomla_metadata_validate.php + - + message: '#^Method JoomlaMetadataValidateCli\:\:findJoomlaManifest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/joomla_metadata_validate.php + - + message: '#^Method JoomlaMetadataValidateCli\:\:loadMetadata\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/joomla_metadata_validate.php + - + message: '#^Method JoomlaMetadataValidateCli\:\:printResults\(\) has parameter \$results with no value type specified in iterable type array\.$#' + count: 1 + path: cli/joomla_metadata_validate.php + - + message: '#^Call to an undefined method LicenseManage\:\:isDryRun\(\)\.$#' + count: 7 + path: cli/license_manage.php + - + message: '#^Method LicenseManage\:\:apiDelete\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/license_manage.php + - + message: '#^Method LicenseManage\:\:apiGet\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/license_manage.php + - + message: '#^Method LicenseManage\:\:apiPatch\(\) has parameter \$data with no value type specified in iterable type array\.$#' + count: 1 + path: cli/license_manage.php + - + message: '#^Method LicenseManage\:\:apiPatch\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/license_manage.php + - + message: '#^Method LicenseManage\:\:apiPost\(\) has parameter \$data with no value type specified in iterable type array\.$#' + count: 1 + path: cli/license_manage.php + - + message: '#^Method LicenseManage\:\:apiPost\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/license_manage.php + - + message: '#^Method LicenseManage\:\:apiRequest\(\) has parameter \$data with no value type specified in iterable type array\.$#' + count: 1 + path: cli/license_manage.php + - + message: '#^Method LicenseManage\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/license_manage.php + - + message: '#^Parameter \#1 \$passed of method MokoCli\\CliFramework\:\:status\(\) expects bool, string given\.$#' + count: 4 + path: cli/license_manage.php + - + message: '#^Parameter \#2 \$label of method MokoCli\\CliFramework\:\:status\(\) expects string, false given\.$#' + count: 1 + path: cli/license_manage.php + - + message: '#^Parameter \#2 \$label of method MokoCli\\CliFramework\:\:status\(\) expects string, true given\.$#' + count: 2 + path: cli/license_manage.php + - + message: '#^Call to an undefined method ManifestDetectCli\:\:sectionHeader\(\)\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestDetectCli\:\:computeDiff\(\) has parameter \$current with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestDetectCli\:\:computeDiff\(\) has parameter \$detected with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestDetectCli\:\:computeDiff\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestDetectCli\:\:detectAll\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestDetectCli\:\:detectDolibarr\(\) has parameter \$fields with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestDetectCli\:\:detectGeneric\(\) has parameter \$fields with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestDetectCli\:\:detectGo\(\) has parameter \$fields with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestDetectCli\:\:detectJoomla\(\) has parameter \$fields with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestDetectCli\:\:detectNode\(\) has parameter \$fields with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestDetectCli\:\:fetchManifest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestDetectCli\:\:pushManifest\(\) has parameter \$current with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestDetectCli\:\:pushManifest\(\) has parameter \$update with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_detect.php + - + message: '#^Method ManifestIntegrityCli\:\:applyFixes\(\) has parameter \$current with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Method ManifestIntegrityCli\:\:applyFixes\(\) has parameter \$issues with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Method ManifestIntegrityCli\:\:fetchManifest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Method ManifestIntegrityCli\:\:fetchOrgRepos\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Method ManifestIntegrityCli\:\:printIssues\(\) has parameter \$issues with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Method ManifestIntegrityCli\:\:runDetect\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Method ManifestIntegrityCli\:\:validate\(\) has parameter \$current with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Method ManifestIntegrityCli\:\:validate\(\) has parameter \$detected with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Method ManifestIntegrityCli\:\:validate\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Method ManifestIntegrityCli\:\:validateManifestOnly\(\) has parameter \$manifest with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Method ManifestIntegrityCli\:\:validateManifestOnly\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Method ManifestIntegrityCli\:\:validatePlatformStructure\(\) has parameter \$manifest with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Method ManifestIntegrityCli\:\:validatePlatformStructure\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_integrity.php + - + message: '#^Constant ManifestReadCli\:\:JOOMLA_XML_ROOTS is unused\.$#' + count: 1 + path: cli/manifest_read.php + - + message: '#^Method ManifestReadCli\:\:addAliases\(\) has parameter \$fields with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_read.php + - + message: '#^Method ManifestReadCli\:\:addAliases\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_read.php + - + message: '#^Method ManifestReadCli\:\:autoDetect\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_read.php + - + message: '#^Method ManifestReadCli\:\:detectDolibarr\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_read.php + - + message: '#^Method ManifestReadCli\:\:detectJoomla\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_read.php + - + message: '#^Method ManifestReadCli\:\:fetchFromApi\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_read.php + - + message: '#^Method ManifestReadCli\:\:outputFields\(\) has parameter \$fields with no value type specified in iterable type array\.$#' + count: 1 + path: cli/manifest_read.php + - + message: '#^Method PackageBuildCli\:\:addDirectoryToZip\(\) has parameter \$excludes with no value type specified in iterable type array\.$#' + count: 1 + path: cli/package_build.php + - + message: '#^Method PlatformDetectCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/platform_detect.php + - + message: '#^Method ReleaseCascadeCli\:\:cascadeToChannel\(\) has parameter \$downloads with no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_cascade.php + - + message: '#^Method ReleaseCascadeCli\:\:getLowerChannels\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_cascade.php + - + message: '#^Method ReleaseCascadeCli\:\:giteaApi\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_cascade.php + - + message: '#^Method ReleaseCreateCli\:\:giteaApi\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_create.php + - + message: '#^Method ReleaseManageCli\:\:getReleaseByTag\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_manage.php + - + message: '#^Method ReleaseManageCli\:\:releaseGiteaApi\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_manage.php + - + message: '#^Method ReleaseMirrorCli\:\:giteaApi\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_mirror.php + - + message: '#^Method ReleaseMirrorCli\:\:githubApi\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_mirror.php + - + message: '#^Strict comparison using \=\=\= between mixed~\(0\|0\.0\|''''\|''0''\|array\{\}\|false\|null\) and '''' will always evaluate to false\.$#' + count: 1 + path: cli/release_notes.php + - + message: '#^Method ReleasePackageCli\:\:addDirToZip\(\) has parameter \$excludes with no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_package.php + - + message: '#^Method ReleasePackageCli\:\:isExcluded\(\) has parameter \$patterns with no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_package.php + - + message: '#^Method ReleasePromoteCli\:\:giteaApi\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_promote.php + - + message: '#^Variable \$token in empty\(\) always exists and is not falsy\.$#' + count: 1 + path: cli/release_publish.php + - + message: '#^Property ReleaseValidateCli\:\:\$results type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_validate.php + - + message: '#^Property ReleaseVerifyCli\:\:\$results type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/release_verify.php + - + message: '#^Call to an undefined method MokoCli\\GitPlatformAdapter\:\:createRepository\(\)\.$#' + count: 1 + path: cli/repo_wizard.php + - + message: '#^Method RepoWizard\:\:choose\(\) has parameter \$options with no value type specified in iterable type array\.$#' + count: 1 + path: cli/repo_wizard.php + - + message: '#^Method RepoWizard\:\:generateFiles\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/repo_wizard.php + - + message: '#^Offset 0 on non\-empty\-list\ on left side of \?\? always exists and is not nullable\.$#' + count: 1 + path: cli/repo_wizard.php + - + message: '#^Property RepoWizard\:\:\$answers type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/repo_wizard.php + - + message: '#^Method ScaffoldClientCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/scaffold_client.php + - + message: '#^Method ThemeLintCli\:\:findFiles\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/theme_lint.php + - + message: '#^Offset 1 on array\{list\, list\\} on left side of \?\? always exists and is not nullable\.$#' + count: 1 + path: cli/theme_vars_check.php + - + message: '#^Method UpdatesXmlSyncCli\:\:apiCall\(\) has parameter \$data with no value type specified in iterable type array\.$#' + count: 1 + path: cli/updates_xml_sync.php + - + message: '#^Method UpdatesXmlSyncCli\:\:apiCall\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/updates_xml_sync.php + - + message: '#^Offset ''alpha''\|''beta''\|''dev''\|''rc'' on array\{dev\: ''\-dev'', alpha\: ''\-alpha'', beta\: ''\-beta'', rc\: ''\-rc''\} on left side of \?\? always exists and is not nullable\.$#' + count: 1 + path: cli/version_auto_bump.php + - + message: '#^Ternary operator condition is always true\.$#' + count: 1 + path: cli/version_auto_bump.php + - + message: '#^Variable \$updatedFiles on left side of \?\? always exists and is not nullable\.$#' + count: 1 + path: cli/version_bump.php + - + message: '#^Method VersionBumpRemoteCli\:\:giteaApi\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/version_bump_remote.php + - + message: '#^Method VersionResetDevCli\:\:giteaApiCall\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/version_reset_dev.php + - + message: '#^Method WikiSyncCli\:\:apiGet\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/wiki_sync.php + - + message: '#^Method WikiSyncCli\:\:apiPatch\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/wiki_sync.php + - + message: '#^Method WikiSyncCli\:\:apiPost\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/wiki_sync.php + - + message: '#^Method WikiSyncCli\:\:apiWrite\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/wiki_sync.php + - + message: '#^Method WikiSyncCli\:\:discoverTemplateRepos\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/wiki_sync.php + - + message: '#^Method WikiSyncCli\:\:getStandardsPages\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/wiki_sync.php + - + message: '#^Property WikiSyncCli\:\:\$pages type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/wiki_sync.php + - + message: '#^Property WikiSyncCli\:\:\$targetRepos type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/wiki_sync.php + - + message: '#^Method WorkflowSyncCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/workflow_sync.php + - + message: '#^Method WorkflowSyncCli\:\:deleteOrphanWorkflows\(\) has parameter \$templateNames with no value type specified in iterable type array\.$#' + count: 1 + path: cli/workflow_sync.php + - + message: '#^Method WorkflowSyncCli\:\:fetchOrgRepos\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/workflow_sync.php + - + message: '#^Method WorkflowSyncCli\:\:listWorkflows\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: cli/workflow_sync.php + - + message: '#^Strict comparison using \=\=\= between string and false will always evaluate to false\.$#' + count: 1 + path: cli/workflow_sync.php + - + message: '#^Constructor of class MokoCli\\CLIApp has an unused parameter \$description\.$#' + count: 1 + path: lib/Enterprise/CliFramework.php + - + message: '#^Method MokoCli\\ManifestParser\:\:generate\(\) has parameter \$params with no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/ManifestParser.php + - + message: '#^Method MokoCli\\ManifestParser\:\:parse\(\) return type has no value type specified in iterable type array\.$#' + count: 4 + path: lib/Enterprise/ManifestParser.php + - + message: '#^Method MokoCli\\ManifestParser\:\:parseBuild\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/ManifestParser.php + - + message: '#^Method MokoCli\\ManifestParser\:\:parseDeploy\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/ManifestParser.php + - + message: '#^Method MokoCli\\ManifestParser\:\:parseGovernance\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/ManifestParser.php + - + message: '#^Method MokoCli\\ManifestParser\:\:parseIdentity\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/ManifestParser.php + - + message: '#^Method MokoCli\\ManifestParser\:\:parseLegacy\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/ManifestParser.php + - + message: '#^Method MokoCli\\ManifestParser\:\:parseOverrides\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/ManifestParser.php + - + message: '#^Method MokoCli\\ManifestParser\:\:parseScripts\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/ManifestParser.php + - + message: '#^Method MokoCli\\ManifestParser\:\:tryParse\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/ManifestParser.php + - + message: '#^Method MokoCli\\Plugins\\DolibarrPlugin\:\:getCommands\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/Plugins/DolibarrPlugin.php + - + message: '#^Method MokoCli\\Plugins\\JoomlaPlugin\:\:getCommands\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/Plugins/JoomlaPlugin.php + - + message: '#^Variable \$content on left side of \?\? always exists and is not nullable\.$#' + count: 2 + path: lib/Enterprise/Plugins/McpServerPlugin.php + - + message: '#^Method MokoCli\\Plugins\\NodeJsPlugin\:\:getCommands\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/Plugins/NodeJsPlugin.php + - + message: '#^Method MokoCli\\Plugins\\PythonPlugin\:\:getCommands\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/Plugins/PythonPlugin.php + - + message: '#^Method MokoCli\\Plugins\\WordPressPlugin\:\:getCommands\(\) return type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/Plugins/WordPressPlugin.php + - + message: '#^Call to static method platformPackageType\(\) on an unknown class MokoCli\\mokocliParser\.$#' + count: 1 + path: lib/Enterprise/RepositorySynchronizer.php + - + message: '#^Method MokoCli\\RepositorySynchronizer\:\:generateManifestXml\(\) has parameter \$repoInfo with no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/RepositorySynchronizer.php + - + message: '#^Method MokoCli\\RepositorySynchronizer\:\:migrateManifest\(\) has parameter \$repoInfo with no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/RepositorySynchronizer.php + - + message: '#^Method MokoCli\\RepositorySynchronizer\:\:migrateManifest\(\) has parameter \$summary with no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/RepositorySynchronizer.php + - + message: '#^Offset 1 on array\{non\-empty\-list\\}\>, non\-empty\-list\\}\>\} in empty\(\) always exists and is not falsy\.$#' + count: 1 + path: lib/Enterprise/SecurityValidator.php + - + message: '#^Property MokoCli\\SourceResolver\:\:\$apiCache type has no value type specified in iterable type array\.$#' + count: 1 + path: lib/Enterprise/SourceResolver.php + - + message: '#^Undefined variable\: \$defTotal$#' + count: 3 + path: validate/check_version_consistency.php diff --git a/phpstan.neon b/phpstan.neon index 02d68f9..05c8746 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,7 +10,6 @@ parameters: paths: - lib - validate - - automation - cli excludePaths: analyseAndScan: diff --git a/plugin_health_check.php b/plugin_health_check.php index c7b8d5b..774a291 100755 --- a/plugin_health_check.php +++ b/plugin_health_check.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Plugin - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Plugin + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /plugin_health_check.php * BRIEF: Run health checks on a project using the auto-detected or specified plugin diff --git a/plugin_list.php b/plugin_list.php index 3bac075..2053730 100755 --- a/plugin_list.php +++ b/plugin_list.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Plugin - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Plugin + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /plugin_list.php * BRIEF: List all available project-type plugins and their capabilities diff --git a/plugin_metrics.php b/plugin_metrics.php index 883d8c6..aaf1352 100755 --- a/plugin_metrics.php +++ b/plugin_metrics.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Plugin - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Plugin + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /plugin_metrics.php * BRIEF: Collect project metrics using the auto-detected or specified plugin diff --git a/plugin_readiness.php b/plugin_readiness.php index 501c53d..0bd46cc 100755 --- a/plugin_readiness.php +++ b/plugin_readiness.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Plugin - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Plugin + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /plugin_readiness.php * BRIEF: Check release readiness of a project using the auto-detected or specified plugin diff --git a/plugin_validate.php b/plugin_validate.php index bc4b0ef..e28ccb9 100755 --- a/plugin_validate.php +++ b/plugin_validate.php @@ -7,8 +7,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Scripts.Plugin - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Scripts.Plugin + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /plugin_validate.php * BRIEF: Validate a project's structure and standards using the auto-detected or specified plugin diff --git a/release/generate_dolibarr_version_txt.php b/release/generate_dolibarr_version_txt.php index 6754bdf..559cae3 100644 --- a/release/generate_dolibarr_version_txt.php +++ b/release/generate_dolibarr_version_txt.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Release - * INGROUP: MokoPlatform.Scripts + * DEFGROUP: MokoCLI.Release + * INGROUP: MokoCLI.Scripts * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /release/generate_dolibarr_version_txt.php * BRIEF: Create or update version.txt on Dolibarr module release diff --git a/release/generate_joomla_update_xml.php b/release/generate_joomla_update_xml.php index 754ffc1..ad27440 100644 --- a/release/generate_joomla_update_xml.php +++ b/release/generate_joomla_update_xml.php @@ -8,8 +8,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Release - * INGROUP: MokoPlatform.Scripts + * DEFGROUP: MokoCLI.Release + * INGROUP: MokoCLI.Scripts * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /release/generate_joomla_update_xml.php * BRIEF: Create or update the in updates.xml on release diff --git a/scripts/sync-wikis-to-github.sh b/scripts/sync-wikis-to-github.sh index e1f7f8f..af5812c 100644 --- a/scripts/sync-wikis-to-github.sh +++ b/scripts/sync-wikis-to-github.sh @@ -78,7 +78,7 @@ else ["MokoConsulting/ssh-mcp"]="ssh-mcp" ["MokoConsulting/backup-mcp"]="backup-mcp" ["MokoConsulting/joomla-api-mcp"]="joomla-api-mcp" - ["MokoConsulting/Template-Client-WaaS"]="Template-Client-WaaS" + ["MokoConsulting/Template-Client"]="Template-Client" ["ClarksvilleFurs/client-waas-clarksvillefurs"]="client-clarksvillefurs" ) fi diff --git a/security/advisory_scan.php b/security/advisory_scan.php index 83299b7..f384855 100644 --- a/security/advisory_scan.php +++ b/security/advisory_scan.php @@ -9,8 +9,8 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Security - * INGROUP: MokoPlatform.Scripts + * DEFGROUP: MokoCLI.Security + * INGROUP: MokoCLI.Scripts * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /security/advisory_scan.php * BRIEF: Cross-repo security advisory aggregator — scans org repos for known CVEs diff --git a/source/functions.php b/source/functions.php index c6383fd..396f784 100644 --- a/source/functions.php +++ b/source/functions.php @@ -7,15 +7,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoPlatform.Common - * INGROUP: MokoPlatform + * DEFGROUP: MokoCLI.Common + * INGROUP: MokoCLI * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli * PATH: /src/functions.php * BRIEF: Common utility functions * * This file provides global helper functions for mokocli. * - * @package MokoPlatform + * @package MokoCLI * @version 04.00.04 */ diff --git a/templates/client-provision-example.json b/templates/client-provision-example.json deleted file mode 100644 index 39bbfa9..0000000 --- a/templates/client-provision-example.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "exampleclient", - "org": "ExampleClient", - "gitea_url": "https://git.mokoconsulting.tech", - - "variables": { - "DEV_SYNC_HOST": "dev.exampleclient.com", - "DEV_SYNC_PORT": "22", - "DEV_SYNC_USER": "exampleclient", - "DEV_SYNC_PATH": "/home/exampleclient/dev.exampleclient.com", - "DEV_SITE_URL": "https://dev.exampleclient.com", - "LIVE_SSH_HOST": "iad1-shared-b7-01.dreamhost.com", - "LIVE_SSH_PORT": "22", - "LIVE_SSH_USER": "exampleclient", - "LIVE_SYNC_PATH": "/home/exampleclient/exampleclient.com", - "RS_FTP_PATH_SUFFIX": "exampleclient.com" - }, - - "secrets": { - "DEV_SYNC_KEY": "@keys/exampleclient-dev.pem", - "LIVE_SSH_KEY": "@keys/exampleclient-live.pem" - }, - - "monitoring": { - "urls": [ - "https://exampleclient.com", - "https://dev.exampleclient.com" - ], - "domains": [ - "exampleclient.com" - ], - "grafana_dashboard": "monitoring/grafana/client-joomla-dashboard.json", - "grafana_folder": "Clients" - } -} diff --git a/templates/config/issue-management-config.yml.template b/templates/config/issue-management-config.yml.template deleted file mode 100644 index 308bcd2..0000000 --- a/templates/config/issue-management-config.yml.template +++ /dev/null @@ -1,63 +0,0 @@ -# Enterprise Issue Management Configuration Template -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# Deploy to: .github/issue-management-config.yml - -version: "1.0.0" - -enterprise: - organization: - name: "ORGANIZATION_NAME" - default_assignees: - - "copilot" - - projects: - enabled: true - default_project_number: null - auto_add_issues: true - - milestones: - enabled: true - auto_assign: true - create_if_missing: true - - labels: - priority: - critical: - name: "priority/critical" - color: "d73a4a" - sla_hours: 4 - high: - name: "priority/high" - color: "ff9800" - sla_hours: 24 - medium: - name: "priority/medium" - color: "fbca04" - sla_hours: 72 - -automation: - create_on_branch: true - branch_patterns: - - "dev/**" - - "rc/**" - close_on_merge: true - close_on_branch_delete: true - link_prs_to_issues: true - create_sub_tasks_for_prs: true - -sla: - enabled: true - timezone: "UTC" - -audit: - enabled: true - retention_days: 365 - -metrics: - enabled: true - -error_handling: - retry_on_failure: true - max_retries: 3 diff --git a/templates/configs/.eslintrc.json b/templates/configs/.eslintrc.json deleted file mode 100644 index 351fe80..0000000 --- a/templates/configs/.eslintrc.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true, - "node": true - }, - "extends": [ - "eslint:recommended" - ], - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "rules": { - "indent": ["error", 2], - "linebreak-style": ["error", "unix"], - "quotes": ["error", "single"], - "semi": ["error", "always"], - "no-unused-vars": ["warn"], - "no-console": ["warn", { "allow": ["warn", "error"] }], - "eqeqeq": ["error", "always"], - "curly": ["error", "all"], - "brace-style": ["error", "1tbs"], - "no-trailing-spaces": "error", - "no-multiple-empty-lines": ["error", { "max": 1 }], - "eol-last": ["error", "always"] - } -} diff --git a/templates/configs/.gitignore.joomla b/templates/configs/.gitignore.joomla deleted file mode 100644 index 625c77a..0000000 --- a/templates/configs/.gitignore.joomla +++ /dev/null @@ -1,220 +0,0 @@ -# ============================================================ -# Local task tracking (not version controlled) -# ============================================================ -TODO.md - -# ============================================================ -# Environment and secrets -# ============================================================ -.env -.env.local -.env.*.local -*.local.php -*.secret.php -configuration.php -configuration.*.php -configuration.local.php -conf/conf.php -conf/conf*.php -secrets/ -*.secrets.* - -# ============================================================ -# Logs, dumps and databases -# ============================================================ -*.db -*.db-journal -*.dump -*.log -*.pid -*.seed - - -# ============================================================ -# OS / Editor / IDE cruft -# ============================================================ -.DS_Store -Thumbs.db -desktop.ini -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db -$RECYCLE.BIN/ -System Volume Information/ -*.lnk -Icon? -.idea/ -.settings/ -.claude/ -.claude-worktree*/ -.vscode/* -!.vscode/tasks.json -!.vscode/settings.json.example -!.vscode/extensions.json -*.code-workspace -*.sublime* -.project -.buildpath -.classpath -*.bak -*.swp -*.swo -*.tmp -*.old -*.orig - -# ============================================================ -# Dev scripts and scratch -# ============================================================ -TODO.md -todo* -*ffs* - -# ============================================================ -# SFTP / sync tools -# ============================================================ -sftp-config*.json -sftp-config.json.template -sftp-settings.json - -# ============================================================ -# Sublime SFTP / FTP sync -# ============================================================ -*.sublime-project -*.sublime-workspace -*.sublime-settings -.libsass.json -*.ffs* - -# ============================================================ -# Replit / cloud IDE -# ============================================================ -.replit -replit.md - -# ============================================================ -# Archives / release artifacts -# ============================================================ -*.7z -*.rar -*.tar -*.tar.gz -*.tgz -*.zip -artifacts/ -release/ -releases/ - -# ============================================================ -# Build outputs and site generators -# ============================================================ -.mkdocs-build/ -.cache/ -.parcel-cache/ -build/ -dist/ -out/ -/site/ -*.map -*.css.map -*.js.map -*.tsbuildinfo - -# ============================================================ -# CI / test artifacts -# ============================================================ -.coverage -.coverage.* -coverage/ -coverage.xml -htmlcov/ -junit.xml -reports/ -test-results/ -tests/_output/ -.github/local/ -.github/workflows/*.log - -# ============================================================ -# Node / JavaScript -# ============================================================ -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -.pnpm-store/ -.yarn/ -.npmrc -.eslintcache -package-lock.json - -# ============================================================ -# PHP / Composer tooling -# ============================================================ -/vendor/ -!src/media/vendor/ -composer.lock -*.phar -codeception.phar -.phpunit.result.cache -.php_cs.cache -.php-cs-fixer.cache -.phpstan.cache -.phplint-cache -phpmd-cache/ -.psalm/ -.rector/ - -# ============================================================ -# Python -# ============================================================ -__pycache__/ -*.py[cod] -*.pyc -*$py.class -*.so -.Python -.eggs/ -*.egg -*.egg-info/ -.installed.cfg -MANIFEST -develop-eggs/ -downloads/ -eggs/ -parts/ -sdist/ -var/ -wheels/ -ENV/ -env/ -.venv/ -venv/ -.pytest_cache/ -.mypy_cache/ -.ruff_cache/ -.pyright/ -.tox/ -.nox/ -*.cover -*.coverage -hypothesis/ - -# ============================================================ -# Local wiki clone (not version controlled) -# ============================================================ -wiki/ - -# ============================================================ -# Joomla runtime / development -# ============================================================ -cache/ -tmp/ -logs/ -configuration.php -user.css -user.js -colors_custom.css -modulebuilder.txt -administrator/components/com_akeebabackup/backup/ diff --git a/templates/configs/.htmlhintrc b/templates/configs/.htmlhintrc deleted file mode 100644 index 866999f..0000000 --- a/templates/configs/.htmlhintrc +++ /dev/null @@ -1,22 +0,0 @@ -{ - "extends": "htmlhint:recommended", - "tagname-lowercase": true, - "attr-lowercase": true, - "attr-value-double-quotes": true, - "doctype-first": true, - "tag-pair": true, - "spec-char-escape": true, - "id-unique": true, - "src-not-empty": true, - "attr-no-duplication": true, - "title-require": true, - "alt-require": true, - "doctype-html5": true, - "style-disabled": false, - "inline-style-disabled": false, - "inline-script-disabled": false, - "space-tab-mixed-disabled": "space", - "id-class-ad-disabled": false, - "href-abs-or-rel": false, - "attr-unsafe-chars": true -} diff --git a/templates/configs/.prettierrc.json b/templates/configs/.prettierrc.json deleted file mode 100644 index ec18ce1..0000000 --- a/templates/configs/.prettierrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "semi": true, - "trailingComma": "es5", - "singleQuote": true, - "printWidth": 100, - "tabWidth": 2, - "useTabs": false, - "endOfLine": "lf", - "arrowParens": "always", - "bracketSpacing": true -} diff --git a/templates/configs/.pylintrc b/templates/configs/.pylintrc deleted file mode 100644 index 8479652..0000000 --- a/templates/configs/.pylintrc +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# SPDX-License-Identifier: GPL-3.0-or-later - -# Pylint configuration for MokoCli projects - -[MASTER] -# Python code to execute, usually for sys.path manipulation -init-hook='import sys; sys.path.append(".")' - -# Use multiple processes to speed up Pylint -jobs=0 - -# Pickle collected data for later comparisons -persistent=yes - -# List of plugins (as comma separated values of python module names) -load-plugins= - -# Minimum Python version to use for version dependent checks -py-version=3.8 - -[MESSAGES CONTROL] -# Disable specific messages -disable= - missing-module-docstring, - missing-function-docstring, - too-few-public-methods, - too-many-arguments, - too-many-locals, - too-many-branches, - too-many-statements, - duplicate-code, - fixme - -[REPORTS] -# Set the output format -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score -score=yes - -[BASIC] -# Good variable names -good-names=i,j,k,ex,Run,_,id,pk - -# Regular expressions for acceptable names -variable-rgx=[a-z_][a-z0-9_]{0,30}$ -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ -class-rgx=[A-Z_][a-zA-Z0-9]+$ -function-rgx=[a-z_][a-z0-9_]{1,50}$ -method-rgx=[a-z_][a-z0-9_]{1,50}$ - -[FORMAT] -# Maximum number of characters on a single line -max-line-length=100 - -# Maximum number of lines in a module -max-module-lines=1000 - -# String used for indentation -indent-string=' ' - -[DESIGN] -# Maximum number of arguments for function / method -max-args=7 - -# Maximum number of attributes for a class -max-attributes=10 - -[IMPORTS] -# Deprecated modules which should not be used -deprecated-modules=optparse,imp - -[CLASSES] -# List of method names used to declare instance attributes -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method -valid-metaclass-classmethod-first-arg=mcs - -[EXCEPTIONS] -# Exceptions that will emit a warning when caught -overgeneral-exceptions=builtins.Exception diff --git a/templates/configs/README.md b/templates/configs/README.md deleted file mode 100644 index 00d121d..0000000 --- a/templates/configs/README.md +++ /dev/null @@ -1,306 +0,0 @@ - - -# Code Quality Configuration Templates - -This directory contains standardized configuration files for code quality, linting, and security tools used across mokocli projects. - -## Available Configurations - -### PHP Tools - -#### `phpcs.xml` - PHP_CodeSniffer -**Purpose**: Enforce PHP coding standards (PSR-12 based) - -**Usage**: -```bash -# Copy to your project root -cp phpcs.xml /path/to/your/project/ - -# Run PHPCS -phpcs --standard=phpcs.xml src/ - -# Auto-fix issues -phpcbf --standard=phpcs.xml src/ -``` - -**Features**: -- PSR-12 compliance -- Line length limits (120 chars) -- Forbidden functions detection (eval, var_dump, etc.) -- Commented-out code detection - -#### `phpstan.neon` - PHPStan -**Purpose**: Static analysis for PHP code - -**Usage**: -```bash -# Copy to your project root -cp phpstan.neon /path/to/your/project/ - -# Install PHPStan -composer require --dev phpstan/phpstan - -# Run analysis -phpstan analyse -``` - -**Configuration**: -- Level 5 analysis (adjust as needed) -- Checks for type errors, dead code, and more -- Configurable ignore patterns - -#### `psalm.xml` - Psalm -**Purpose**: Advanced static analysis for PHP - -**Usage**: -```bash -# Copy to your project root -cp psalm.xml /path/to/your/project/ - -# Install Psalm -composer require --dev vimeo/psalm - -# Initialize and run -psalm --init -psalm -``` - -**Configuration**: -- Error level 4 (balanced strictness) -- Finds unused code (optional) -- Customizable issue handlers - -### JavaScript/TypeScript Tools - -#### `.eslintrc.json` - ESLint -**Purpose**: Identify and fix JavaScript code issues - -**Usage**: -```bash -# Copy to your project root -cp .eslintrc.json /path/to/your/project/ - -# Install ESLint -npm install --save-dev eslint - -# Run linting -npx eslint . - -# Auto-fix issues -npx eslint . --fix -``` - -**Features**: -- ES2021 support -- Tab indentation (2-space visual width) -- Unix line endings -- Single quotes for strings -- Semicolon enforcement - -#### `.prettierrc.json` - Prettier -**Purpose**: Opinionated code formatter for JavaScript/TypeScript - -**Usage**: -```bash -# Copy to your project root -cp .prettierrc.json /path/to/your/project/ - -# Install Prettier -npm install --save-dev prettier - -# Check formatting -npx prettier --check . - -# Auto-format -npx prettier --write . -``` - -**Configuration**: -- 100 character line width -- Single quotes -- Trailing commas (ES5) -- Tab indentation (2-space visual width) - -### Python Tools - -#### `.pylintrc` - Pylint -**Purpose**: Python code analysis and style checking - -**Usage**: -```bash -# Copy to your project root -cp .pylintrc /path/to/your/project/ - -# Install Pylint -pip install pylint - -# Run analysis -pylint **/*.py -``` - -**Features**: -- PEP 8 compliance -- 100 character line limit -- Configurable message disabling -- Custom naming conventions - -#### `pyproject.toml` - Python Project Configuration -**Purpose**: Unified configuration for Black, isort, mypy, and pytest - -**Usage**: -```bash -# Copy to your project root -cp pyproject.toml /path/to/your/project/ - -# Install tools -pip install black isort mypy pytest pytest-cov - -# Run Black formatter -black . - -# Sort imports with isort -isort . - -# Type check with mypy -mypy src/ - -# Run tests with coverage -pytest --cov=src -``` - -**Tools Configured**: -- **Black**: Opinionated Python formatter -- **isort**: Import statement sorter -- **mypy**: Static type checker -- **pytest**: Test framework -- **coverage**: Code coverage measurement - -### HTML Tools - -#### `.htmlhintrc` - HTMLHint -**Purpose**: HTML5 validation and best practices - -**Usage**: -```bash -# Copy to your project root -cp .htmlhintrc /path/to/your/project/ - -# Install HTMLHint -npm install -g htmlhint - -# Run validation -htmlhint **/*.html -``` - -**Features**: -- HTML5 doctype validation -- Tag and attribute validation -- Accessibility checks (alt, title requirements) -- Style and script validation - -## Integration with GitHub Actions - -All these tools are integrated into the `code-quality.yml` workflow template. To use: - -1. **Copy the workflow**: - ```bash - cp templates/workflows/code-quality.yml.template .github/workflows/code-quality.yml - ``` - -2. **Copy relevant config files**: - ```bash - # For PHP projects - cp templates/configs/phpcs.xml . - cp templates/configs/phpstan.neon . - - # For JavaScript projects - cp templates/configs/.eslintrc.json . - cp templates/configs/.prettierrc.json . - - # For Python projects - cp templates/configs/.pylintrc . - cp templates/configs/pyproject.toml . - - # For HTML projects - cp templates/configs/.htmlhintrc . - ``` - -3. **Customize for your project**: Adjust tool configurations based on your specific requirements - -## Tool Installation - -### PHP -```bash -# Via Composer -composer require --dev squizlabs/php_codesniffer phpstan/phpstan vimeo/psalm -``` - -### JavaScript/TypeScript -```bash -# Via npm -npm install --save-dev eslint prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin -``` - -### Python -```bash -# Via pip -pip install pylint black mypy isort pytest pytest-cov -``` - -### HTML -```bash -# Via npm (global) -npm install -g htmlhint -``` - -## Configuration Customization - -Each configuration file can be customized for your project: - -1. **Adjust severity levels**: Change error levels to match your team's standards -2. **Add ignore patterns**: Exclude specific files or directories -3. **Enable/disable rules**: Fine-tune which checks are active -4. **Set code style preferences**: Modify indentation, line length, etc. - -## Security Best Practices - -These configurations include security-focused rules: - -- **PHP**: Forbidden functions (eval, create_function) -- **JavaScript**: No console.log in production -- **Python**: Import security patterns -- **HTML**: XSS prevention patterns - -## CI/CD Integration - -These tools work seamlessly with: - -- GitHub Actions (see workflow templates) -- GitLab CI -- Jenkins -- CircleCI -- Travis CI - -## Support and Updates - -Configuration templates are maintained in the mokocli repository: -- **Repository**: https://git.mokoconsulting.tech/MokoConsulting/mokocli -- **Documentation**: https://git.mokoconsulting.tech/MokoConsulting/mokocli/tree/main/docs -- **Issues**: Report problems or suggest improvements via GitHub Issues - -## Version History - -- **v1.0.0** (2026-01): Initial release with PHP, JavaScript, Python, and HTML configurations diff --git a/templates/configs/composer.dolibarr.json b/templates/configs/composer.dolibarr.json deleted file mode 100644 index b4b0aba..0000000 --- a/templates/configs/composer.dolibarr.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "mokoconsulting-tech/{{repo_name_lower}}", - "description": "{{repo_name}} Dolibarr module by Moko Consulting", - "type": "dolibarr-module", - "version": "01.00.00", - "license": "GPL-3.0-or-later", - "authors": [ - { - "name": "Moko Consulting", - "email": "hello@mokoconsulting.tech" - } - ], - "minimum-stability": "dev", - "prefer-stable": true, - "require": { - "php": ">=8.1", - "mokoconsulting-tech/enterprise": "dev-version/04.02.00" - }, - "require-dev": { - "phpunit/phpunit": "^10.5", - "phpstan/phpstan": "^2.0", - "squizlabs/php_codesniffer": "^4.0" - }, - "autoload": { - "psr-4": { - "MokoConsulting\\{{repo_name}}\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "MokoConsulting\\{{repo_name}}\\Tests\\": "tests/" - } - }, - "repositories": [ - { - "type": "vcs", - "url": "https://git.mokoconsulting.tech/mokoconsulting-tech/MokoCli-API" - } - ], - "config": { - "sort-packages": true, - "optimize-autoloader": true, - "preferred-install": "dist", - "allow-plugins": { - "composer/installers": true - } - }, - "scripts": { - "validate-module": "vendor/bin/validate-module --path .", - "build": "vendor/bin/build-package --path .", - "test": "phpunit", - "phpcs": "phpcs --standard=vendor/mokoconsulting-tech/enterprise/phpcs.xml src/", - "phpstan": "phpstan analyse -c phpstan.neon src/" - } -} diff --git a/templates/configs/composer.generic.json b/templates/configs/composer.generic.json deleted file mode 100644 index f5be9d8..0000000 --- a/templates/configs/composer.generic.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "mokoconsulting-tech/{{repo_name_lower}}", - "description": "{{repo_name}} library by Moko Consulting", - "type": "library", - "version": "01.00.00", - "license": "GPL-3.0-or-later", - "authors": [ - { - "name": "Moko Consulting", - "email": "hello@mokoconsulting.tech" - } - ], - "minimum-stability": "stable", - "prefer-stable": true, - "require": { - "php": ">=8.1", - "mokoconsulting-tech/enterprise": "dev-version/04.02.00" - }, - "require-dev": { - "phpunit/phpunit": "^10.5", - "phpstan/phpstan": "^2.0", - "squizlabs/php_codesniffer": "^4.0" - }, - "autoload": { - "psr-4": { - "MokoConsulting\\{{repo_name}}\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "MokoConsulting\\{{repo_name}}\\Tests\\": "tests/" - } - }, - "repositories": [ - { - "type": "vcs", - "url": "https://git.mokoconsulting.tech/mokoconsulting-tech/MokoCli-API" - } - ], - "config": { - "sort-packages": true, - "optimize-autoloader": true, - "preferred-install": "dist" - }, - "scripts": { - "validate": "vendor/bin/validate-structure --path .", - "test": "phpunit", - "phpcs": "phpcs --standard=vendor/mokoconsulting-tech/enterprise/phpcs.xml src/", - "phpstan": "phpstan analyse -c vendor/mokoconsulting-tech/enterprise/phpstan.neon src/" - } -} diff --git a/templates/configs/composer.joomla.json b/templates/configs/composer.joomla.json deleted file mode 100644 index 514d6cd..0000000 --- a/templates/configs/composer.joomla.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "name": "mokoconsulting-tech/{{repo_name_lower}}", - "description": "{{repo_name}} Joomla component by Moko Consulting", - "type": "joomla-component", - "version": "01.00.00", - "license": "GPL-3.0-or-later", - "authors": [ - { - "name": "Moko Consulting", - "email": "hello@mokoconsulting.tech" - } - ], - "minimum-stability": "stable", - "prefer-stable": true, - "require": { - "php": ">=8.1", - "mokoconsulting-tech/enterprise": "dev-version/04.02.00" - }, - "require-dev": { - "phpunit/phpunit": "^10.5", - "phpstan/phpstan": "^2.0", - "squizlabs/php_codesniffer": "^4.0" - }, - "autoload": { - "psr-4": { - "MokoConsulting\\{{repo_name}}\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "MokoConsulting\\{{repo_name}}\\Tests\\": "tests/" - } - }, - "repositories": [ - { - "type": "vcs", - "url": "https://git.mokoconsulting.tech/mokoconsulting-tech/MokoCli-API" - } - ], - "config": { - "sort-packages": true, - "optimize-autoloader": true, - "preferred-install": "dist", - "allow-plugins": { - "composer/installers": true - } - }, - "scripts": { - "validate-manifest": "vendor/bin/validate-manifest --path .", - "build": "vendor/bin/build-package --path .", - "test": "phpunit", - "phpcs": "phpcs --standard=vendor/mokoconsulting-tech/enterprise/phpcs.xml src/", - "phpstan": "phpstan analyse -c phpstan.neon src/" - } -} diff --git a/templates/configs/ftpignore b/templates/configs/ftpignore deleted file mode 100644 index 63e7979..0000000 --- a/templates/configs/ftpignore +++ /dev/null @@ -1,47 +0,0 @@ -# .ftpignore — FTP/SFTP upload exclusion rules -# Syntax mirrors .gitignore: blank lines and # comments are ignored, -# * matches within a path segment, ** matches across segments, ? matches one char. -# A leading / anchors to the upload root; a trailing / matches directories only. -# Negation (!) is not supported. -# -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later - -# ── Version-control metadata ────────────────────────────────────────────────── -.git/ -.gitignore -.gitkeep -.gitattributes -.gitmodules - -# ── CI / editor / tooling artefacts ────────────────────────────────────────── -.github/ -.editorconfig -.yamllint -.phpcs.xml -.phpstan.neon -.psalm.xml -.mokostandards - -# ── Dependency directories ──────────────────────────────────────────────────── -vendor/ -node_modules/ - -# ── Build / cache / temp ────────────────────────────────────────────────────── -build/ -dist/ -*.log -*.tmp -*.cache - -# ── Secrets & local config (HARDCODED DENY — never deploy these) ───────────── -.env -.env.* -sftp-config*.json -sftp-config.json.template -scripts/sftp-config/ -scripts/keys/ -*.ppk -*.pem -*.key -.ftpignore diff --git a/templates/configs/gitignore b/templates/configs/gitignore deleted file mode 100644 index 2529535..0000000 --- a/templates/configs/gitignore +++ /dev/null @@ -1,208 +0,0 @@ -# ============================================================ -# Local task tracking (not version controlled) -# ============================================================ -TODO.md - -# ============================================================ -# Environment and secrets -# ============================================================ -.env -.env.local -.env.*.local -*.local.php -*.secret.php -configuration.php -configuration.*.php -configuration.local.php -conf/conf.php -conf/conf*.php -secrets/ -*.secrets.* - -# ============================================================ -# Logs, dumps and databases -# ============================================================ -*.db -*.db-journal -*.dump -*.log -*.pid -*.seed - - -# ============================================================ -# OS / Editor / IDE cruft -# ============================================================ -.DS_Store -Thumbs.db -desktop.ini -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db -$RECYCLE.BIN/ -System Volume Information/ -*.lnk -Icon? -.idea/ -.settings/ -.claude/ -.claude-worktree*/ -.vscode/* -!.vscode/tasks.json -!.vscode/settings.json.example -!.vscode/extensions.json -*.code-workspace -*.sublime* -.project -.buildpath -.classpath -*.bak -*.swp -*.swo -*.tmp -*.old -*.orig - -# ============================================================ -# Dev scripts and scratch -# ============================================================ -TODO.md -todo* -*ffs* - -# ============================================================ -# SFTP / sync tools -# ============================================================ -sftp-config*.json -sftp-config.json.template -sftp-settings.json - -# ============================================================ -# Sublime SFTP / FTP sync -# ============================================================ -*.sublime-project -*.sublime-workspace -*.sublime-settings -.libsass.json -*.ffs* - -# ============================================================ -# Replit / cloud IDE -# ============================================================ -.replit -replit.md - -# ============================================================ -# Archives / release artifacts -# ============================================================ -*.7z -*.rar -*.tar -*.tar.gz -*.tgz -*.zip -artifacts/ -release/ -releases/ - -# ============================================================ -# Build outputs and site generators -# ============================================================ -.mkdocs-build/ -.cache/ -.parcel-cache/ -build/ -dist/ -out/ -/site/ -*.map -*.css.map -*.js.map -*.tsbuildinfo - -# ============================================================ -# CI / test artifacts -# ============================================================ -.coverage -.coverage.* -coverage/ -coverage.xml -htmlcov/ -junit.xml -reports/ -test-results/ -tests/_output/ -.github/local/ -.github/workflows/*.log - -# ============================================================ -# Node / JavaScript -# ============================================================ -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -.pnpm-store/ -.yarn/ -.npmrc -.eslintcache -package-lock.json - -# ============================================================ -# PHP / Composer tooling -# ============================================================ -/vendor/ -!src/media/vendor/ -composer.lock -*.phar -codeception.phar -.phpunit.result.cache -.php_cs.cache -.php-cs-fixer.cache -.phpstan.cache -.phplint-cache -phpmd-cache/ -.psalm/ -.rector/ - -# ============================================================ -# Python -# ============================================================ -__pycache__/ -*.py[cod] -*.pyc -*$py.class -*.so -.Python -.eggs/ -*.egg -*.egg-info/ -.installed.cfg -MANIFEST -develop-eggs/ -downloads/ -eggs/ -parts/ -sdist/ -var/ -wheels/ -ENV/ -env/ -.venv/ -venv/ -.pytest_cache/ -.mypy_cache/ -.ruff_cache/ -.pyright/ -.tox/ -.nox/ -*.cover -*.coverage -hypothesis/ - -# ============================================================ -# Local wiki clone (not version controlled) -# ============================================================ -wiki/ - diff --git a/templates/configs/gitignore.dolibarr b/templates/configs/gitignore.dolibarr deleted file mode 100644 index 8e9cd4a..0000000 --- a/templates/configs/gitignore.dolibarr +++ /dev/null @@ -1,225 +0,0 @@ -# ============================================================ -# Local task tracking (not version controlled) -# ============================================================ -TODO.md - -# ============================================================ -# Environment and secrets -# ============================================================ -.env -.env.local -.env.*.local -*.local.php -*.secret.php -configuration.php -configuration.*.php -configuration.local.php -conf/conf.php -conf/conf*.php -secrets/ -*.secrets.* - -# ============================================================ -# Logs, dumps and databases -# ============================================================ -*.db -*.db-journal -*.dump -*.log -*.pid -*.seed - - -# ============================================================ -# OS / Editor / IDE cruft -# ============================================================ -.DS_Store -Thumbs.db -desktop.ini -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db -$RECYCLE.BIN/ -System Volume Information/ -*.lnk -Icon? -.idea/ -.settings/ -.claude/ -.claude-worktree*/ -.vscode/* -!.vscode/tasks.json -!.vscode/settings.json.example -!.vscode/extensions.json -*.code-workspace -*.sublime* -.project -.buildpath -.classpath -*.bak -*.swp -*.swo -*.tmp -*.old -*.orig - -# ============================================================ -# Dev scripts and scratch -# ============================================================ -TODO.md -todo* -*ffs* - -# ============================================================ -# SFTP / sync tools -# ============================================================ -sftp-config*.json -sftp-config.json.template -sftp-settings.json - -# ============================================================ -# Sublime SFTP / FTP sync -# ============================================================ -*.sublime-project -*.sublime-workspace -*.sublime-settings -.libsass.json -*.ffs* - -# ============================================================ -# Replit / cloud IDE -# ============================================================ -.replit -replit.md - -# ============================================================ -# Archives / release artifacts -# ============================================================ -*.7z -*.rar -*.tar -*.tar.gz -*.tgz -*.zip -artifacts/ -release/ -releases/ - -# ============================================================ -# Build outputs and site generators -# ============================================================ -.mkdocs-build/ -.cache/ -.parcel-cache/ -build/ -dist/ -out/ -site/ -*.map -*.css.map -*.js.map -*.tsbuildinfo - -# ============================================================ -# CI / test artifacts -# ============================================================ -.coverage -.coverage.* -coverage/ -coverage.xml -htmlcov/ -junit.xml -reports/ -test-results/ -tests/_output/ -.github/local/ -.github/workflows/*.log - -# ============================================================ -# Node / JavaScript -# ============================================================ -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -.pnpm-store/ -.yarn/ -.npmrc -.eslintcache -package-lock.json - -# ============================================================ -# PHP / Composer tooling -# ============================================================ -/vendor/ -!src/media/vendor/ -composer.lock -*.phar -codeception.phar -.phpunit.result.cache -.php_cs.cache -.php-cs-fixer.cache -.phpstan.cache -.phplint-cache -phpmd-cache/ -.psalm/ -.rector/ - -# ============================================================ -# Python -# ============================================================ -__pycache__/ -*.py[cod] -*.pyc -*$py.class -*.so -.Python -.eggs/ -*.egg -*.egg-info/ -.installed.cfg -MANIFEST -develop-eggs/ -downloads/ -eggs/ -parts/ -sdist/ -var/ -wheels/ -ENV/ -env/ -.venv/ -venv/ -.pytest_cache/ -.mypy_cache/ -.ruff_cache/ -.pyright/ -.tox/ -.nox/ -*.cover -*.coverage -hypothesis/ - -# ============================================================ -# Local wiki clone (not version controlled) -# ============================================================ -wiki/ - -# ============================================================ -# Dolibarr runtime / data -# ============================================================ -documents/ -dolibarr_documents/ -uploads/ -thumbs/ -data/ -cache/ -temp/ -tmp/ -logs/ -htdocs/documents/ -htdocs/cache/ -htdocs/tmp/ -htdocs/logs/ -conf/conf.php diff --git a/templates/configs/index.md b/templates/configs/index.md deleted file mode 100644 index 94bd1b6..0000000 --- a/templates/configs/index.md +++ /dev/null @@ -1,43 +0,0 @@ - - -# Code Quality Configuration Templates - -Standardized configuration files for code quality and security tools. - -## Available Configurations - -### PHP -- `phpcs.xml` - PHP_CodeSniffer (PSR-12) -- `phpstan.neon` - PHPStan static analysis -- `psalm.xml` - Psalm advanced analysis - -### JavaScript/TypeScript -- `.eslintrc.json` - ESLint linting -- `.prettierrc.json` - Prettier formatting - -### Python -- `.pylintrc` - Pylint analysis -- `pyproject.toml` - Black, isort, mypy, pytest - -### HTML -- `.htmlhintrc` - HTMLHint validation - -## Quick Start - -```bash -# Copy configuration for your language -cp templates/configs/.eslintrc.json . -cp templates/configs/phpcs.xml . -cp templates/configs/.pylintrc . -``` - -See [README.md](README.md) for detailed documentation. diff --git a/templates/configs/manifest.xml.template b/templates/configs/manifest.xml.template deleted file mode 100644 index 58326ca..0000000 --- a/templates/configs/manifest.xml.template +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - {{REPO_NAME}} - {{org}} - {{REPO_DESCRIPTION}} - GNU General Public License v3 - - - - {{platform}} - {{standards_version}} - https://git.mokoconsulting.tech/MokoConsulting/mokocli - - - - {{PRIMARY_LANGUAGE}} - - - diff --git a/templates/configs/manifest.yml.template b/templates/configs/manifest.yml.template deleted file mode 100644 index e712a25..0000000 --- a/templates/configs/manifest.yml.template +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: MokoPlatform.Templates.Config -# INGROUP: MokoPlatform.Templates -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /templates/configs/manifest.yml.template -# BRIEF: Governance attachment template — synced to .mokogitea/manifest.xml in every governed repository -# NOTE: Tokens replaced at sync time: {{org}}, {{repo_name}}, {{platform}}, {{standards_version}} -# -# This file is managed automatically by mokocli bulk sync. -# Do not edit manually — changes will be overwritten on the next sync. -# To update governance settings, open a PR in mokocli instead: -# https://git.mokoconsulting.tech/MokoConsulting/mokocli - -standards_source: "https://git.mokoconsulting.tech/MokoConsulting/mokocli" -standards_version: "{{standards_version}}" -platform: "{{platform}}" -governed_repo: "{{org}}/{{repo_name}}" diff --git a/templates/configs/mokostandards.xml.template b/templates/configs/mokostandards.xml.template deleted file mode 100644 index ed72e27..0000000 --- a/templates/configs/mokostandards.xml.template +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - {{REPO_NAME}} - {{org}} - {{REPO_DESCRIPTION}} - GNU General Public License v3 - - - - {{platform}} - {{standards_version}} - https://git.mokoconsulting.tech/MokoConsulting/mokocli - - - - {{PRIMARY_LANGUAGE}} - - - diff --git a/templates/configs/mokostandards.yml.template b/templates/configs/mokostandards.yml.template deleted file mode 100644 index 12bbd0e..0000000 --- a/templates/configs/mokostandards.yml.template +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: MokoPlatform.Templates.Config -# INGROUP: MokoPlatform.Templates -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /templates/configs/moko-standards.yml.template -# BRIEF: Governance attachment template — synced to .mokostandards in every governed repository -# NOTE: Tokens replaced at sync time: {{org}}, {{repo_name}}, {{platform}}, {{standards_version}} -# -# This file is managed automatically by mokocli bulk sync. -# Do not edit manually — changes will be overwritten on the next sync. -# To update governance settings, open a PR in mokocli instead: -# https://git.mokoconsulting.tech/MokoConsulting/mokocli - -standards_source: "https://git.mokoconsulting.tech/MokoConsulting/mokocli" -standards_version: "{{standards_version}}" -platform: "{{platform}}" -governed_repo: "{{org}}/{{repo_name}}" diff --git a/templates/configs/pa11yci.json b/templates/configs/pa11yci.json deleted file mode 100644 index c08219a..0000000 --- a/templates/configs/pa11yci.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "defaults": { - "standard": "WCAG2AA", - "timeout": 30000, - "wait": 1000, - "ignore": [], - "chromeLaunchConfig": { - "args": [ - "--no-sandbox", - "--disable-setuid-sandbox" - ] - } - }, - "urls": [ - { - "url": "http://localhost:8080/", - "actions": [] - } - ], - "concurrency": 2, - "useIncognitoBrowserContext": true -} \ No newline at end of file diff --git a/templates/configs/phpcs.xml b/templates/configs/phpcs.xml deleted file mode 100644 index babfcca..0000000 --- a/templates/configs/phpcs.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - PHP_CodeSniffer configuration for mokocli projects - - - src - tests - - - */vendor/* - */node_modules/* - */.git/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/templates/configs/phpstan.dolibarr.neon b/templates/configs/phpstan.dolibarr.neon deleted file mode 100644 index 00ec81c..0000000 --- a/templates/configs/phpstan.dolibarr.neon +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# PHPStan configuration for Dolibarr module repositories. -# Extends the base MokoCli config and adds Dolibarr class stubs -# so PHPStan can resolve CommonObject, DoliDB, Conf, User, etc. -# without requiring a full Dolibarr installation. - -parameters: - level: 5 - - paths: - - src - - excludePaths: - - vendor - - node_modules - - # Dolibarr class stubs — resolved via the enterprise package from vendor/ - stubFiles: - - vendor/mokoconsulting-tech/enterprise/templates/stubs/dolibarr.php - - # Suppress errors that are structural in Dolibarr's dynamic architecture - ignoreErrors: - # Dolibarr uses dynamic properties heavily (pre-PHP 8.2 pattern) - - '#Access to an undefined property [A-Za-z]+::\$#' - # Module descriptors use magic property assignment in __construct - - '#Variable \$[a-z]+ might not be defined\.#' - - # Common Dolibarr globals declared at script entry point - dynamicConstantNames: - - DOL_DOCUMENT_ROOT - - DOL_URL_ROOT - - DOL_VERSION - - MAIN_DB_PREFIX - - reportUnmatchedIgnoredErrors: false - checkMissingIterableValueType: false - checkGenericClassInNonGenericObjectType: false diff --git a/templates/configs/phpstan.joomla.neon b/templates/configs/phpstan.joomla.neon deleted file mode 100644 index 9bb35ef..0000000 --- a/templates/configs/phpstan.joomla.neon +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# PHPStan configuration for Joomla extension repositories. -# Extends the base MokoCli config and adds Joomla framework class stubs -# so PHPStan can resolve Factory, CMSApplication, User, Table, etc. -# without requiring a full Joomla installation. - -parameters: - level: 5 - - paths: - - src - - excludePaths: - - vendor - - node_modules - - # Joomla framework stubs — resolved via the enterprise package from vendor/ - stubFiles: - - vendor/mokoconsulting-tech/enterprise/templates/stubs/joomla.php - - # Suppress errors that are structural in Joomla's service-container architecture - ignoreErrors: - # Joomla's service-based dependency injection returns mixed from getApplication() - - '#Cannot call method .+ on Joomla\\CMS\\Application\\CMSApplication\|null#' - # Factory::getX() patterns are safe at runtime even when nullable in stubs - - '#Call to static method [a-zA-Z]+\(\) on an interface#' - - reportUnmatchedIgnoredErrors: false - checkMissingIterableValueType: false - checkGenericClassInNonGenericObjectType: false diff --git a/templates/configs/phpstan.neon b/templates/configs/phpstan.neon deleted file mode 100644 index 1182786..0000000 --- a/templates/configs/phpstan.neon +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# SPDX-License-Identifier: GPL-3.0-or-later - -# PHPStan configuration for MokoCli projects -parameters: - level: 5 - paths: - - src - - tests - excludePaths: - - vendor - - node_modules - - # Report unknown classes and functions - reportUnmatchedIgnoredErrors: false - - # Check for dead code - checkMissingIterableValueType: false - checkGenericClassInNonGenericObjectType: false - - # Additional checks - checkAlwaysTrueCheckTypeFunctionCall: true - checkAlwaysTrueInstanceof: true - checkAlwaysTrueStrictComparison: true - checkExplicitMixedMissingReturn: true - checkFunctionNameCase: true - checkInternalClassCaseSensitivity: true - - # Ignore common patterns - ignoreErrors: - # Add project-specific ignores here - # - '#Call to an undefined method#' diff --git a/templates/configs/psalm.xml b/templates/configs/psalm.xml deleted file mode 100644 index a1b21ba..0000000 --- a/templates/configs/psalm.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/templates/configs/pyproject.toml b/templates/configs/pyproject.toml deleted file mode 100644 index 9747912..0000000 --- a/templates/configs/pyproject.toml +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# SPDX-License-Identifier: GPL-3.0-or-later - -# Python project configuration for MokoCli projects - -[tool.black] -line-length = 100 -target-version = ['py38', 'py39', 'py310', 'py311'] -include = '\.pyi?$' -exclude = ''' -/( - \.git - | \.venv - | venv - | \.eggs - | \.tox - | build - | dist - | __pycache__ - | node_modules -)/ -''' - -[tool.isort] -profile = "black" -line_length = 100 -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true -ensure_newline_before_comments = true - -[tool.mypy] -python_version = "3.8" -warn_return_any = true -warn_unused_configs = true -disallow_untyped_defs = false -disallow_incomplete_defs = false -check_untyped_defs = true -disallow_untyped_calls = false -disallow_untyped_decorators = false -no_implicit_optional = true -warn_redundant_casts = true -warn_unused_ignores = true -warn_no_return = true -warn_unreachable = true -strict_equality = true -ignore_missing_imports = true - -[[tool.mypy.overrides]] -module = "tests.*" -disallow_untyped_defs = false - -[tool.pytest.ini_options] -minversion = "6.0" -addopts = "-ra -q --strict-markers" -testpaths = [ - "tests", -] -python_files = "test_*.py" -python_classes = "Test*" -python_functions = "test_*" - -[tool.coverage.run] -source = ["src"] -omit = [ - "*/tests/*", - "*/__pycache__/*", - "*/venv/*", - "*/.venv/*", -] - -[tool.coverage.report] -exclude_lines = [ - "pragma: no cover", - "def __repr__", - "raise AssertionError", - "raise NotImplementedError", - "if __name__ == .__main__.:", - "if TYPE_CHECKING:", - "@abstractmethod", -] diff --git a/templates/configs/renovate.json b/templates/configs/renovate.json deleted file mode 100644 index 15c6a10..0000000 --- a/templates/configs/renovate.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended", - "schedule:weekly", - ":disableDependencyDashboard" - ], - "labels": ["dependencies"], - "automerge": false, - "platformAutomerge": false, - "rangeStrategy": "bump", - "packageRules": [ - { - "matchUpdateTypes": ["patch"], - "automerge": true - }, - { - "matchManagers": ["composer"], - "enabled": true - }, - { - "matchManagers": ["npm"], - "enabled": true - } - ] -} diff --git a/templates/docs/README.md b/templates/docs/README.md deleted file mode 100644 index c71b0df..0000000 --- a/templates/docs/README.md +++ /dev/null @@ -1,117 +0,0 @@ - - -# Documentation Templates - -## Purpose - -This directory contains governed documentation templates for the mokocli organization. These templates ensure consistency, completeness, and compliance across all documentation artifacts. - -## Intended Use - -Use these templates when: - -- Creating new documentation files -- Establishing documentation in new repositories -- Ensuring compliance with documentation standards -- Maintaining consistency across projects - -## Instructions - -### Template Categories - -Templates are organized into two categories: - -1. **Required Templates** - `/templates/docs/required/` - - Mandatory documentation files for all repositories - - Must be present and maintained - - Subject to compliance review - -2. **Extra Templates** - `/templates/docs/extra/` - - Optional documentation files - - Recommended for specific use cases - - Enhance documentation quality - -### Using Templates - -To use a template: - -1. Navigate to appropriate template category (required or extra) -2. Copy the template file to your target location -3. Rename the file removing the `template-` prefix -4. Replace all placeholder content with actual information -5. Complete all required fields -6. Remove example sections or mark them explicitly as examples -7. Follow the template instructions section -8. Validate against Document Formatting Policy - -### Template Maintenance - -Templates are governed assets and must: - -- Follow Document Formatting Policy requirements -- Include all required sections for templates -- Contain no production data -- Use placeholder values only -- Be reviewed per governance schedule -- Have Project task entries - -## Required Fields - -When using templates, ensure these fields are completed: - -- All section headers and content -- Metadata fields specific to the document -- Revision history -- Purpose and scope statements -- Responsibilities and governance rules (where applicable) - -## Example Usage - -### Creating a New Repository README - -```bash -# Copy template to target location -cp /templates/docs/required/template-README.md /path/to/repo/README.md - -# Edit the file -# - Replace "[Repository Name]" with actual repository name -# - Complete all sections -# - Update metadata -# - Customize content for your repository -``` - -### Creating a New Policy Document - -```bash -# Use policy template structure -# Follow /docs/policy/ examples -# Ensure all mandatory policy sections included -# Obtain required approvals per policy -``` - -## Metadata - -- **Document Type:** overview -- **Document Subtype:** catalog -- **Owner Role:** Documentation Owner -- **Approval Required:** No -- **Evidence Required:** Yes -- **Review Cycle:** Annual -- **Retention:** Indefinite -- **Compliance Tags:** Governance -- **Status:** Published - -## Revision History - -- Initial template catalog established -- Template categories and usage instructions defined -- Template maintenance requirements documented diff --git a/templates/docs/extra/README.md b/templates/docs/extra/README.md deleted file mode 100644 index ea89df0..0000000 --- a/templates/docs/extra/README.md +++ /dev/null @@ -1,200 +0,0 @@ - - -# Extra Documentation Templates - -## Purpose - -This directory contains optional documentation templates that enhance repository documentation quality and completeness. These templates are recommended but not mandatory, providing additional documentation capabilities beyond baseline requirements. - -## Intended Use - -Use these templates when: - -- Enhanced documentation is beneficial -- Specific use cases require additional documentation -- Improving documentation beyond minimum requirements -- Addressing stakeholder needs for additional information -- Meeting specific project or compliance needs - -## Instructions - -### Optional Templates - -Extra templates provide documentation for: - -1. **Code of Conduct** - Community behavior guidelines -2. **Security Policy** - Security reporting and disclosure -3. **Support Documentation** - Support channels and procedures -4. **Governance Documents** - Decision-making and authority -5. **Additional Technical Documentation** - Architecture, design, API docs - -### Template Selection - -Choose extra templates based on: - -- **Project Size** - Larger projects benefit from comprehensive documentation -- **Community Size** - Projects with external contributors need governance docs -- **Security Requirements** - Security-sensitive projects need security policy -- **Stakeholder Needs** - Specific stakeholder requirements -- **Compliance Requirements** - Regulatory or audit requirements - -### Template Usage - -For each extra template: - -1. Determine if template is needed for your project -2. Copy the template file from this directory -3. Rename removing the `template-` prefix -4. Place in appropriate repository location -5. Complete all sections -6. Replace all placeholder values -7. Customize for your project context -8. Create Project task if document is critical -9. Maintain per appropriate review cycle - -### When to Use Extra Templates - -#### Code of Conduct - -Use when: - -- Repository accepts external contributions -- Community interactions expected -- Need to establish behavioral expectations -- Creating inclusive environment - -#### Security Policy - -Use when: - -- Repository contains security-sensitive code -- Security vulnerabilities may be discovered -- Need coordinated disclosure process -- Compliance requires security documentation - -#### Support Documentation - -Use when: - -- Users need support information -- Multiple support channels exist -- Support expectations must be clear -- SLA or support tiers defined - -#### Governance Documents - -Use when: - -- Decision-making authority must be clear -- Multiple maintainers or teams involved -- Escalation procedures needed -- Organizational governance required - -## Required Fields - -When using extra templates, ensure these fields are completed: - -- All section headers with appropriate content -- Purpose and scope clearly defined -- Contact information where applicable -- Procedures and processes clearly documented -- Metadata (for governed documents) -- Revision history - -## Example Usage - -### Adding Code of Conduct - -```bash -# Copy template -cp /templates/docs/extra/template-CODE_OF_CONDUCT.md ./CODE_OF_CONDUCT.md - -# Edit the file -# - Complete all sections -# - Add contact information for enforcement -# - Customize behavioral expectations -# - Add reporting procedures - -# Commit to repository -git add CODE_OF_CONDUCT.md -git commit -m "Add Code of Conduct" -``` - -### Adding Security Policy - -```bash -# Copy template -cp /templates/docs/extra/template-SECURITY.md ./SECURITY.md - -# Edit the file -# - Define supported versions -# - Add vulnerability reporting procedures -# - Define disclosure timelines -# - Add security contact information - -# Commit to repository -git add SECURITY.md -git commit -m "Add security policy" -``` - -### Adding Support Documentation - -```bash -# Copy template -cp /templates/docs/extra/template-SUPPORT.md ./SUPPORT.md - -# Edit the file -# - List support channels -# - Define response expectations -# - Add support tiers if applicable -# - Include escalation procedures - -# Commit to repository -git add SUPPORT.md -git commit -m "Add support documentation" -``` - -## Template List - -- **template-CODE_OF_CONDUCT.md** - Community behavior guidelines template -- Additional templates as needed for specific use cases - -Note: Extra templates are created on-demand based on organizational needs. Check this directory for available templates or request new templates through governance channels. - -## Best Practices - -When using extra templates: - -- Only use templates that add value to your project -- Keep documentation current and accurate -- Follow Document Formatting Policy -- Create Project tasks for critical documents -- Review and update per appropriate schedule -- Remove unused templates to reduce maintenance burden - -## Metadata - -- **Document Type:** overview -- **Document Subtype:** catalog -- **Owner Role:** Documentation Owner -- **Approval Required:** No -- **Evidence Required:** Yes -- **Review Cycle:** Annual -- **Retention:** Indefinite -- **Compliance Tags:** Governance -- **Status:** Published - -## Revision History - -- Initial extra templates catalog established -- Template selection guidance defined -- Usage instructions and best practices documented diff --git a/templates/docs/extra/index.md b/templates/docs/extra/index.md deleted file mode 100644 index 554d598..0000000 --- a/templates/docs/extra/index.md +++ /dev/null @@ -1,32 +0,0 @@ - - -# Docs Index: /templates/docs/extra - -## Purpose - -This index provides navigation to documentation within this folder. - -## Documents - -- [README](./README.md) -- [template-CODE_OF_CONDUCT](./template-CODE_OF_CONDUCT.md) - -## Metadata - -- **Document Type:** index -- **Auto-generated:** This file is automatically generated by rebuild_indexes.py - -## Revision History - -| Change | Notes | Author | -| --- | --- | --- | -| Automated update | Generated by documentation index automation | rebuild_indexes.py | diff --git a/templates/docs/extra/template-CODE_OF_CONDUCT.md b/templates/docs/extra/template-CODE_OF_CONDUCT.md deleted file mode 100644 index e1ae3bf..0000000 --- a/templates/docs/extra/template-CODE_OF_CONDUCT.md +++ /dev/null @@ -1,86 +0,0 @@ - -# Code of Conduct - -## 1. Purpose - -The purpose of this Code of Conduct is to ensure a safe, inclusive, and respectful environment for all contributors and participants in Moko Consulting projects. This applies to all interactions, whether in repositories, issue trackers, documentation, meetings, or community spaces. - -## 2. Our Standards - -Participants are expected to uphold behaviors that strengthen our community, including: - - Demonstrating empathy and respect toward others. - Being inclusive of diverse viewpoints and backgrounds. - Gracefully accepting constructive feedback. - Prioritizing collaboration over conflict. - Showing professionalism in all interactions. - -### Unacceptable behavior includes: - - Harassment, discrimination, or derogatory comments. - Threatening or violent language or actions. - Disruptive, aggressive, or intentionally harmful behavior. - Publishing others’ private information without permission. - Any behavior that violates applicable laws. - -## 3. Responsibilities of Maintainers - -Maintainers are responsible for: - - Clarifying acceptable behavior. - Taking appropriate corrective action when unacceptable behavior occurs. - Removing, editing, or rejecting contributions that violate this Code. - Temporarily or permanently banning contributors who engage in repeated or severe violations. - -## 4. Scope - -This Code applies to: - - All Moko Consulting repositories. - All documentation and collaboration platforms. - Public and private communication related to project activities. - Any representation of Moko Consulting in online or offline spaces. - -## 5. Enforcement - -Instances of misconduct may be reported to: -**[hello@mokoconsulting.tech](mailto:hello@mokoconsulting.tech)** - -All reports will be reviewed and investigated promptly and fairly. Maintainers are obligated to maintain confidentiality where possible. - -Consequences may include: - - A warning. - Required training or mediation. - Temporary or permanent bans. - Escalation to legal authorities when required. - -## 6. Acknowledgements - -This Code of Conduct is inspired by widely adopted community guidelines, including the Contributor Covenant and major open-source collaboration standards. - -## 7. Related Documents - - [Governance Guide](./docs-governance.md) - [Contributor Guide](./docs-contributing.md) - [Documentation Index](./docs-index.md) - -This Code of Conduct is a living document and may be updated following the established Change Management process. diff --git a/templates/docs/extra/template-ROADMAP.md b/templates/docs/extra/template-ROADMAP.md deleted file mode 100644 index dc01995..0000000 --- a/templates/docs/extra/template-ROADMAP.md +++ /dev/null @@ -1,142 +0,0 @@ - - -# [Project Name] Roadmap - -## Scope and Intent - -This document defines the roadmap for [Project Name] starting with version **[X.Y.Z]**. It establishes the sequencing, intent, and maturity expectations for deliverables governed by this project. - -This roadmap is forward-looking by design. Completed work prior to the current baseline version is considered foundational and is intentionally excluded. This file tracks **current and future-state development only**. - -## Version [X.Y.Z] — [Phase Name] ✅ COMPLETED / 🔄 IN PROGRESS / 📋 PLANNED - -Focus: [Brief description of this version's primary focus] - -### Completed Deliverables - -* ✅ **[Category Name]** - * ✅ [Specific deliverable or feature] - * ✅ [Specific deliverable or feature] -* ✅ **[Category Name]** - * ✅ [Specific deliverable or feature] - -### In Progress - -* 🔄 [Item currently being worked on] -* 🔄 [Item currently being worked on] - -### Planned Deliverables - -* [Future planned item] -* [Future planned item] - -### Achieved Outcomes - -* ✅ [Measurable outcome or benefit] -* ✅ [Measurable outcome or benefit] -* 🔄 [Partially achieved outcome] - -## Version [X.Y+1.Z] — [Next Phase Name] - -Focus: [Brief description of next version's primary focus] - -### In-Scope Deliverables - -* [Deliverable for next version] -* [Deliverable for next version] - -### Outcomes - -* [Expected outcome or benefit] -* [Expected outcome or benefit] - -## Version [X.Y+2.Z] — [Future Phase Name] - -Focus: [Brief description of future version's primary focus] - -### Planned Deliverables - -* [Future deliverable] -* [Future deliverable] - -### Outcomes - -* [Expected outcome] -* [Expected outcome] - -## Version [X.Y+3.Z] and Beyond — [Long-term Vision] - -Focus: [Long-term strategic direction] - -### Forward-Looking Initiatives - -* [Strategic initiative] -* [Strategic initiative] - -### Outcomes - -* [Long-term goal] -* [Long-term goal] - ---- - -This roadmap is intentionally conservative and additive. New versions extend prior guarantees without breaking existing contracts. - -## Guidelines for Version Planning - -### Version Numbering - -Use semantic versioning for clarity: -- **Major versions** (X.0.0): Breaking changes, major architectural shifts -- **Minor versions** (X.Y.0): New features, significant enhancements -- **Patch versions** (X.Y.Z): Bug fixes, minor improvements - -### Planning Horizon - -- **Current Version**: Actively being delivered -- **Next 2-3 Versions**: Clearly defined with specific deliverables -- **Future Versions**: High-level themes and strategic direction - -### Status Indicators - -- ✅ **COMPLETED**: Delivered and validated -- 🔄 **IN PROGRESS**: Active development underway -- 📋 **PLANNED**: Scheduled but not yet started -- ⏸️ **PAUSED**: Temporarily on hold -- ❌ **CANCELLED**: No longer pursuing - -## Metadata - -``` -Owner: [Owner Name/Role] -Reviewers: [Reviewer Names/Roles] -Status: Active -Last Updated: [YYYY-MM-DD] -Next Review: [YYYY-MM-DD] -``` - -## Revision History - -| Date | Version | Author | Notes | -| ---------- | -------- | -------------- | ------------------------------- | -| YYYY-MM-DD | X.Y.Z | [Author Name] | [Description of changes] | -| YYYY-MM-DD | X.Y.Z | [Author Name] | [Description of changes] | diff --git a/templates/docs/index.md b/templates/docs/index.md deleted file mode 100644 index bac85a8..0000000 --- a/templates/docs/index.md +++ /dev/null @@ -1,36 +0,0 @@ - - -# Docs Index: /templates/docs - -## Purpose - -This index provides navigation to documentation within this folder. - -## Subfolders - -- [extra/](./extra/index.md) -- [required/](./required/index.md) - -## Documents - -- [README](./README.md) - -## Metadata - -- **Document Type:** index -- **Auto-generated:** This file is automatically generated by rebuild_indexes.py - -## Revision History - -| Change | Notes | Author | -| --- | --- | --- | -| Automated update | Generated by documentation index automation | rebuild_indexes.py | diff --git a/templates/docs/required/CODEOWNERS b/templates/docs/required/CODEOWNERS deleted file mode 100644 index 990bc8a..0000000 --- a/templates/docs/required/CODEOWNERS +++ /dev/null @@ -1,37 +0,0 @@ -# CODEOWNERS - -## Repository Ownership Matrix - -``` -# GLOBAL DEFAULT -* @mokoconsulting-tech - -# DOCUMENTATION SUITE -/docs/ @mokoconsulting-tech -/docs/templates/ @mokoconsulting-tech -/docs/adr/ @mokoconsulting-tech -/docs/diagrams/ @mokoconsulting-tech -/docs/security/ @mokoconsulting-tech - -# ENGINEERING + CODE ASSETS -/htdocs/ @mokoconsulting-tech -/src/ @mokoconsulting-tech -/api/ @mokoconsulting-tech - -# CI/CD WORKFLOWS -/.github/workflows/ @mokoconsulting-tech - -# ISSUE TEMPLATES -/.github/ISSUE_TEMPLATE/ @mokoconsulting-tech - -# GOVERNANCE + OPERATIONAL FILES -/CONTRIBUTING.md @mokoconsulting-tech -/GOVERNANCE.md @mokoconsulting-tech -/CODE_OF_CONDUCT.md @mokoconsulting-tech - -# SECURITY -/SECURITY.md @mokoconsulting-tech - -# TEMPLATE LIBRARY -/docs/templates/ @mokoconsulting-tech -``` diff --git a/templates/docs/required/GOVERNANCE.md b/templates/docs/required/GOVERNANCE.md deleted file mode 100644 index 0a1cb09..0000000 --- a/templates/docs/required/GOVERNANCE.md +++ /dev/null @@ -1,119 +0,0 @@ - - -[![mokocli](https://img.shields.io/badge/moko--platform-{{standards_version}}-blue)](https://git.mokoconsulting.tech/MokoConsulting/mokocli) - -# Project Governance - -## Overview - -This document defines the governance model for the `{{repo_name}}` repository within the -`{{org}}` organization. It is automatically maintained by -[mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) v{{standards_version}}. - -Full governance policy is defined in the mokocli source repository: -[docs/policy/GOVERNANCE.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/GOVERNANCE.md) - ---- - -## Roles and Responsibilities - -### Maintainer - -**GitHub**: @mokoconsulting-tech - -**Authority**: Final decision-making authority on all matters for this repository. - -**Responsibilities**: -- Review and merge pull requests -- Maintain code quality and standards compliance -- Manage releases and versioning -- Respond to issues and security reports - -### Contributors - -**Authority**: Submit changes via pull requests. - -**Requirements**: -- Read and accept `CODE_OF_CONDUCT.md` -- Follow `CONTRIBUTING.md` guidelines - ---- - -## Decision-Making - -All changes must be submitted as pull requests. The maintainer (@mokoconsulting-tech) -reviews and approves all changes before they are merged. - -### Sole Operator Policy - -This organization operates under a **sole operator** model. The maintainer (@mokoconsulting-tech) -is the sole employee and owner and may self-approve pull requests when no second reviewer is -available. The following requirements remain mandatory regardless: - -1. **Pull Requests Required** — all changes to protected branches go through a PR. -2. **Automated Checks** — all CI checks must pass before merging. -3. **Audit Trail** — issues, pull requests, and commit history are preserved. -4. **Documentation** — changes are documented in `CHANGELOG.md`. - -See the full policy: -[Sole Operator Policy](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/GOVERNANCE.md#sole-operator-policy) - ---- - -## Change Management - -| Change Type | Approval | Process | -|-------------|----------|---------| -| Routine (docs, bug fixes) | Maintainer | PR → CI pass → merge | -| Significant (new features) | Maintainer | PR with description → CI pass → merge | -| Major (breaking, architecture) | Maintainer | Issue discussion → PR → CI pass → merge | -| Emergency (security) | Maintainer | Labelled `EMERGENCY` → immediate merge → post-mortem | - ---- - -## Reporting Issues - -- **Bugs / Features**: Open a [GitHub Issue](https://github.com/{{org}}/{{repo_name}}/issues) -- **Security vulnerabilities**: See [SECURITY.md](./SECURITY.md) -- **Code of Conduct**: See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) -- **Contact**: dev@mokoconsulting.tech - ---- - -## Metadata - -| Field | Value | -| ------------- | ----------------------------------------------- | -| Document Type | Policy | -| Domain | Governance | -| Applies To | {{org}}/{{repo_name}} | -| Jurisdiction | Tennessee, USA | -| Maintainer | @mokoconsulting-tech | -| Standards | mokocli v{{standards_version}} | -| Repo | https://github.com/{{org}}/{{repo_name}} | -| Path | /GOVERNANCE.md | -| Status | Active — auto-maintained by mokocli | diff --git a/templates/docs/required/README.md b/templates/docs/required/README.md deleted file mode 100644 index 8b5d4dd..0000000 --- a/templates/docs/required/README.md +++ /dev/null @@ -1,176 +0,0 @@ - - -# Required Documentation Templates - -## Purpose - -This directory contains mandatory documentation templates that MUST be present in all repositories governed by mokocli documentation policies. These templates ensure baseline documentation compliance and organizational consistency. - -## Intended Use - -Use these templates when: - -- Creating a new repository -- Establishing baseline repository documentation -- Ensuring compliance with documentation governance -- Meeting mandatory documentation requirements - -## Instructions - -### Mandatory Templates - -All repositories MUST include these documentation files: - -1. **README.md** - Repository overview and entry point -2. **CHANGELOG.md** - Change tracking and release history -3. **CONTRIBUTING.md** - Contribution guidelines and workflow -4. **LICENSE** - License terms and copyright (see templates/licenses/ for authoritative versions) -5. **SECURITY.md** - Security vulnerability reporting and handling policy - -### Template Usage - -For each required template: - -1. Copy the template file from this directory -2. Rename removing the `template-` prefix -3. Place in repository root directory -4. Complete all required sections -5. Replace all placeholder values -6. Customize for your repository context -7. Validate compliance with Document Formatting Policy -8. Create Project task for the document - -### Compliance Requirements - -Required documentation MUST: - -- Exist at repository root level -- Follow template structure -- Include all mandatory sections -- Contain accurate, current information -- Be maintained per review cycle -- Have corresponding Project task entries - -### Non-Compliance Consequences - -Repositories without required documentation: - -- Are considered non-compliant -- May not satisfy audit requirements -- Cannot be used for production purposes -- Must remediate before deployment approval - -## Required Fields - -All required templates must have these completed: - -### README.md - -- Repository name and purpose -- Installation and usage instructions -- License information -- Contribution guidelines reference -- Contact information - -### CHANGELOG.md - -- Structured change history -- Release information -- Breaking changes documentation -- Migration guidance where applicable - -### CONTRIBUTING.md - -- Contribution workflow -- Code of conduct reference -- Development guidelines -- Pull request requirements -- Testing requirements - -### LICENSE.md - -- License text -- Copyright information -- License terms -- Redistribution terms - -### SECURITY.md - -- Supported versions -- Vulnerability reporting process -- Security best practices -- Response timeline and severity classification - -## Example Usage - -### New Repository Setup - -```bash -# Create repository directory -mkdir my-new-repo -cd my-new-repo - -# Initialize git -git init - -# Copy required templates -cp /templates/docs/required/template-README.md ./README.md -cp /templates/docs/required/template-CHANGELOG.md ./CHANGELOG.md -cp /templates/docs/required/template-CONTRIBUTING.md ./CONTRIBUTING.md -cp /templates/licenses/GPL-3.0 ./LICENSE -cp /templates/docs/required/template-SECURITY.md ./SECURITY.md - -# Edit each file to complete required fields -# Validate compliance -# Commit to repository -git add README.md CHANGELOG.md CONTRIBUTING.md LICENSE SECURITY.md -git commit -m "Add required documentation" -``` - -### Existing Repository Compliance - -```bash -# Check for missing required files -ls -1 README.md CHANGELOG.md CONTRIBUTING.md LICENSE.md SECURITY.md - -# Copy missing templates -# Complete required fields -# Commit to repository -``` - -## Template List - -- **template-README.md** - Repository overview template -- **template-CHANGELOG.md** - Change log template -- **template-CONTRIBUTING.md** - Contribution guidelines template -- **template-SECURITY.md** - Security policy template -- **template-CODE_OF_CONDUCT.md** - Community code of conduct template - -**Note**: LICENSE templates are in `/templates/licenses/` directory (see [templates/licenses/README.md](../../licenses/README.md)) - -## Metadata - -- **Document Type:** overview -- **Document Subtype:** catalog -- **Owner Role:** Documentation Owner -- **Approval Required:** No -- **Evidence Required:** Yes -- **Review Cycle:** Annual -- **Retention:** Indefinite -- **Compliance Tags:** Governance, Compliance -- **Status:** Published - -## Revision History - -- Initial required templates catalog established -- Mandatory template requirements defined -- Compliance and usage instructions documented diff --git a/templates/docs/required/index.md b/templates/docs/required/index.md deleted file mode 100644 index 7b8a9f8..0000000 --- a/templates/docs/required/index.md +++ /dev/null @@ -1,36 +0,0 @@ - - -# Docs Index: /templates/docs/required - -## Purpose - -This index provides navigation to documentation within this folder. - -## Documents - -- [README](./README.md) -- [template-CHANGELOG](./template-CHANGELOG.md) -- [template-CONTRIBUTING](./template-CONTRIBUTING.md) -- [template-LICENSE](./template-LICENSE.md) -- [template-README](./template-README.md) -- [template-SECURITY](./template-SECURITY.md) - -## Metadata - -- **Document Type:** index -- **Auto-generated:** This file is automatically generated by rebuild_indexes.py - -## Revision History - -| Change | Notes | Author | -| --- | --- | --- | -| Automated update | Generated by documentation index automation | rebuild_indexes.py | diff --git a/templates/docs/required/template-CHANGELOG.md b/templates/docs/required/template-CHANGELOG.md deleted file mode 100644 index 190fcd4..0000000 --- a/templates/docs/required/template-CHANGELOG.md +++ /dev/null @@ -1,27 +0,0 @@ - -# Changelog - -## [Unreleased] -### Added -- Initial defaults and docs - -## [1.0] - YYYY-MM-DD -### Added -- First published draft diff --git a/templates/docs/required/template-CONTRIBUTING.md b/templates/docs/required/template-CONTRIBUTING.md deleted file mode 100644 index b1dc0a8..0000000 --- a/templates/docs/required/template-CONTRIBUTING.md +++ /dev/null @@ -1,128 +0,0 @@ - - -# Contributing - -Thank you for your interest in contributing to **{{REPO_NAME}}**! - -This repository is governed by **[mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli)** — the authoritative source of coding standards, workflows, and policies for all Moko Consulting repositories. - -## Branch Strategy - -| Branch | Purpose | Deploys To | -|--------|---------|------------| -| `main` | Bleeding edge — all development merges here | CI only | -| `dev/XX.YY.ZZ` | Feature development | Dev server (version: "development") | -| `version/XX` | Stable frozen snapshot | Demo + RS servers | - -### Development Workflow - -``` -1. Create branch: git checkout -b dev/XX.YY.ZZ/my-feature -2. Develop + test (dev server auto-deploys on push) -3. Open PR → main (squash merge only) -4. Auto-release (version branch + tag + GitHub Release created automatically) -``` - -### Branch Naming - -| Prefix | Use | -|--------|-----| -| `dev/XX.YY.ZZ` | Feature development (e.g., `dev/02.00.00/add-extrafields`) | -| `version/XX` | Stable release (auto-created, never manually pushed) | -| `chore/` | Automated sync branches (managed by mokocli) | - -> **Never use** `feature/`, `hotfix/`, or `release/` prefixes — they are not part of the mokocli branch strategy. - -## Commit Conventions - -Use [conventional commits](https://www.conventionalcommits.org/): - -``` -feat(scope): add new extrafield for invoice tracking -fix(sql): correct column type in llx_mytable -docs(readme): update installation instructions -chore(deps): bump enterprise library to 04.02.30 -``` - -**Valid types:** `feat` | `fix` | `docs` | `chore` | `ci` | `refactor` | `style` | `test` | `perf` | `revert` | `build` - -## Pull Request Workflow - -1. **Branch** from `main` using `dev/XX.YY.ZZ/description` format -2. **Bump** the patch version in `README.md` before opening the PR -3. **Title** must be a valid conventional commit subject line -4. **Target** `main` — squash merge only (merge commits are disabled) -5. **CI checks** must pass before merge - -### What Happens on Merge - -When your PR is merged to `main`, these workflows run automatically: - -1. **sync-version-on-merge** — auto-bumps patch version, propagates to all file headers -2. **auto-release** — creates `version/XX` branch, git tag, and GitHub Release -3. **deploy-demo / deploy-rs** — deploys to demo and RS servers (if `src/**` changed) - -## Coding Standards - -All contributions must follow [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli): - -| Standard | Reference | -|----------|-----------| -| Coding Style | [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/coding-style-guide.md) | -| File Headers | [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/file-header-standards.md) | -| Branching | [branch-release-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/branch-release-strategy.md) | -| Merge Strategy | [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/merge-strategy.md) | -| Scripting | [scripting-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/scripting-standards.md) | -| Build & Release | [build-release.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/workflows/build-release.md) | - -## PR Checklist - -- [ ] Branch named `dev/XX.YY.ZZ/description` -- [ ] Patch version bumped in `README.md` -- [ ] Conventional commit format for PR title -- [ ] All new files have FILE INFORMATION headers -- [ ] `declare(strict_types=1)` in all PHP files -- [ ] PHPDoc on all public methods -- [ ] Tests pass -- [ ] CHANGELOG.md updated -- [ ] No secrets, tokens, or credentials committed - -## Custom Workflows - -Place repo-specific workflows in `.mokogitea/workflows/custom/` — they are **never overwritten or deleted** by mokocli sync: - -``` -.mokogitea/workflows/ -├── deploy-dev.yml ← Synced from mokocli -├── auto-release.yml ← Synced from mokocli -└── custom/ ← Your custom workflows (safe) - └── my-custom-ci.yml -``` - -## License - -By contributing, you agree that your contributions will be licensed under the [GPL-3.0-or-later](LICENSE) license. - ---- - -*This file is synced from [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli). Do not edit directly — changes will be overwritten on the next sync.* diff --git a/templates/docs/required/template-INSTALLATION.md b/templates/docs/required/template-INSTALLATION.md deleted file mode 100644 index 6e680ae..0000000 --- a/templates/docs/required/template-INSTALLATION.md +++ /dev/null @@ -1,440 +0,0 @@ - - -# Installation - -## Overview - -This document provides comprehensive installation and setup instructions for **[PROJECT_NAME]**. - -## Table of Contents - -- [Prerequisites](#prerequisites) -- [Installation Methods](#installation-methods) -- [Quick Start](#quick-start) -- [Detailed Installation](#detailed-installation) -- [Configuration](#configuration) -- [Verification](#verification) -- [Troubleshooting](#troubleshooting) -- [Next Steps](#next-steps) - -## Prerequisites - -### System Requirements - -- **Operating System**: [Specify supported OS versions] -- **Runtime**: [e.g., PHP 8.1+, Node.js 20+, Python 3.9+] -- **Memory**: [Minimum RAM required] -- **Disk Space**: [Minimum disk space required] - -### Software Dependencies - -**Required:** -- [List required dependencies with versions] -- Example: Git 2.30+ -- Example: Composer 2.0+ - -**Optional:** -- [List optional dependencies] - -### Access Requirements - -- [Any required access permissions, credentials, or accounts] -- Example: GitHub account for cloning private repositories -- Example: Database access credentials - -## Installation Methods - -### Method 1: Using Package Manager (Recommended) - -**For [Platform/Package Manager]:** - -```bash -# Installation command -[package-manager] install [package-name] - -# Verify installation -[package-name] --version -``` - -### Method 2: From Source - -**Clone the repository:** - -```bash -# Clone from GitHub -git clone https://github.com/[organization]/[repository].git -cd [repository] - -# Checkout stable version (recommended) -git checkout tags/v[VERSION] -``` - -### Method 3: Using Pre-built Binary/Package - -**Download and install:** - -```bash -# Download release -wget https://github.com/[organization]/[repository]/releases/download/v[VERSION]/[package-name] - -# Make executable (if applicable) -chmod +x [package-name] - -# Move to system path (optional) -sudo mv [package-name] /usr/local/bin/ -``` - -## Quick Start - -For users who want to get started quickly: - -```bash -# 1. Install -[installation-command] - -# 2. Configure -[configuration-command] - -# 3. Run -[run-command] - -# 4. Verify -[verification-command] -``` - -## Detailed Installation - -### Step 1: Prepare Environment - -**1.1 Install System Dependencies** - -For Ubuntu/Debian: -```bash -sudo apt update -sudo apt install [dependencies] -``` - -For macOS: -```bash -brew install [dependencies] -``` - -For Windows: -```powershell -# PowerShell commands or link to Windows-specific guide -``` - -**1.2 Set Up Environment Variables** - -```bash -# Add to ~/.bashrc or ~/.zshrc -export [VAR_NAME]=[value] - -# Reload shell configuration -source ~/.bashrc -``` - -### Step 2: Install Application - -**2.1 Install via [Method]** - -```bash -[Detailed installation commands with explanations] -``` - -**2.2 Install Dependencies** - -```bash -# For PHP projects -composer install --no-dev - -# For Node.js projects -npm install --production - -# For Python projects -pip install -r requirements.txt -``` - -### Step 3: Initial Configuration - -**3.1 Create Configuration File** - -```bash -# Copy example configuration -cp config/config.example.php config/config.php - -# Or use configuration wizard -php bin/configure.php -``` - -**3.2 Configure Database (if applicable)** - -```bash -# Create database -mysql -u root -p -e "CREATE DATABASE [db_name];" - -# Import schema -mysql -u root -p [db_name] < database/schema.sql - -# Update configuration -nano config/database.php -``` - -**3.3 Set Permissions** - -```bash -# Set appropriate ownership -sudo chown -R www-data:www-data /var/www/[project] - -# Set directory permissions (755) -find /var/www/[project] -type d -exec chmod 755 {} \; - -# Set file permissions (644 for most files) -find /var/www/[project] -type f -exec chmod 644 {} \; - -# Make executable files executable (if needed) -chmod +x /var/www/[project]/bin/* - -# Restrict sensitive directories (storage, cache, logs) -chmod 750 /var/www/[project]/storage -chmod 750 /var/www/[project]/cache -``` - -### Step 4: Initialize Application - -**4.1 Run Setup Script** - -```bash -# Run initialization -php bin/setup.php - -# Or for other platforms -./scripts/setup.sh -``` - -**4.2 Create Admin User (if applicable)** - -```bash -# Create first admin user -php bin/create-admin.php --email=admin@example.com --name="Admin User" -``` - -## Configuration - -### Configuration Files - -| File | Purpose | Required | -|------|---------|----------| -| `config/config.php` | Main configuration | Yes | -| `config/database.php` | Database settings | Yes | -| `config/cache.php` | Cache configuration | No | -| `.env` | Environment variables | Yes | - -### Essential Configuration Options - -**config/config.php:** - -```php -return [ - 'app_name' => '[APPLICATION_NAME]', - 'app_url' => 'https://example.com', - 'debug' => false, // Set to true for development - 'timezone' => 'UTC', -]; -``` - -**Database Configuration:** - -```php -return [ - 'host' => 'localhost', - 'port' => 3306, - 'database' => '[db_name]', - 'username' => '[db_user]', - 'password' => '[db_password]', -]; -``` - -### Environment Variables - -Create `.env` file: - -```bash -APP_ENV=production -APP_DEBUG=false -APP_URL=https://example.com - -DB_HOST=localhost -DB_PORT=3306 -DB_DATABASE=[db_name] -DB_USERNAME=[db_user] -DB_PASSWORD=[db_password] -``` - -## Verification - -### Verify Installation - -**Check version:** - -```bash -[command] --version -# Expected output: v[VERSION] -``` - -**Run health check:** - -```bash -[command] health-check -# or -php bin/health-check.php -``` - -**Test basic functionality:** - -```bash -# Run test command -[command] test - -# Access web interface -curl http://localhost:[port]/health -``` - -### Expected Output - -``` -✓ Application installed successfully -✓ Database connection established -✓ All dependencies available -✓ Configuration valid -✓ System ready for use -``` - -## Troubleshooting - -### Common Issues - -#### Issue: Installation fails with dependency error - -**Symptom:** -``` -Error: Package [package-name] not found -``` - -**Solution:** -```bash -# Update package manager -[package-manager] update - -# Retry installation -[package-manager] install [package-name] -``` - -#### Issue: Database connection fails - -**Symptom:** -``` -Error: SQLSTATE[HY000] [2002] Connection refused -``` - -**Solution:** -1. Verify database service is running: - ```bash - sudo systemctl status mysql - ``` - -2. Check database credentials in configuration - -3. Verify database host and port are correct - -#### Issue: Permission denied errors - -**Symptom:** -``` -Error: Permission denied: /var/www/[project]/storage -``` - -**Solution:** -```bash -# Fix ownership -sudo chown -R www-data:www-data /var/www/[project] - -# Fix permissions -sudo chmod -R 755 /var/www/[project]/storage -``` - -### Getting Help - -If you encounter issues not covered here: - -1. **Check Logs:** - ```bash - tail -f logs/application.log - tail -f /var/log/apache2/error.log - ``` - -2. **Enable Debug Mode:** - ```bash - # In config/config.php - 'debug' => true - ``` - -3. **Consult Documentation:** - - [Troubleshooting Guide](guide/troubleshooting.md) - - [FAQ](guide/faq.md) - -4. **Community Support:** - - GitHub Issues: [link] - - Discussion Forum: [link] - - Email: support@example.com - -## Next Steps - -After successful installation: - -1. **Review Configuration:** - - [Configuration Guide](guide/configuration.md) - - [Security Hardening](guide/security.md) - -2. **Read Getting Started:** - - [Quick Start Guide](guide/quickstart.md) - - [User Guide](guide/user-guide.md) - -3. **For Developers:** - - [Development Setup](development/setup.md) - - [Contributing Guidelines](../CONTRIBUTING.md) - -4. **For Operators:** - - [Deployment Guide](deployment/procedures.md) - - [Monitoring Setup](operations/monitoring.md) - -## Additional Resources - -- [Project Documentation](README.md) -- [API Reference](reference/api/) -- [Change Log](../CHANGELOG.md) -- [Security Policy](../SECURITY.md) - ---- - -## Support - -For installation support: -- **Documentation**: Review all guides in [docs/guide/](guide/) -- **Issues**: Report problems at [GitHub Issues](https://github.com/[organization]/[repository]/issues) -- **Email**: support@mokoconsulting.tech - ---- - -*Last Updated: [DATE]* -*Version: [VERSION]* diff --git a/templates/docs/required/template-LICENSE.md b/templates/docs/required/template-LICENSE.md deleted file mode 100644 index b3a7105..0000000 --- a/templates/docs/required/template-LICENSE.md +++ /dev/null @@ -1,694 +0,0 @@ - - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. - diff --git a/templates/docs/required/template-README.md b/templates/docs/required/template-README.md deleted file mode 100644 index d2ae455..0000000 --- a/templates/docs/required/template-README.md +++ /dev/null @@ -1,53 +0,0 @@ - - -# {{REPO_NAME}} - -[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) -[![mokocli](https://img.shields.io/badge/moko--platform-{{standards_version}}-orange)](https://git.mokoconsulting.tech/MokoConsulting/mokocli) - -> {{REPO_DESCRIPTION}} - -## Overview - - - -## Installation - - - -## Usage - - - -## Contributing - -See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. This repository follows [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli). - -## License - -This project is licensed under the GNU General Public License v3.0 or later — see the [LICENSE](LICENSE) file for details. - -Copyright (C) 2026 Moko Consulting - -This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. diff --git a/templates/docs/required/template-SECURITY.md b/templates/docs/required/template-SECURITY.md deleted file mode 100644 index a9631c5..0000000 --- a/templates/docs/required/template-SECURITY.md +++ /dev/null @@ -1,239 +0,0 @@ - - -# Security Policy - -## Purpose and Scope - -This document defines the security vulnerability reporting, response, and disclosure policy for [PROJECT_NAME] and all repositories governed by these standards. It establishes the authoritative process for responsible disclosure, assessment, remediation, and communication of security issues. - -## Supported Versions - -Security updates are provided for the following versions: - -| Version | Supported | -| ------- | ------------------ | -| [X.x.x] | :white_check_mark: | -| < [X.0] | :x: | - -Only the current major version receives security updates. Users should upgrade to the latest supported version to receive security patches. - -## Reporting a Vulnerability - -### Where to Report - -**DO NOT** create public GitHub issues for security vulnerabilities. - -Report security vulnerabilities privately to: - -**Email**: `security@[DOMAIN]` - -**Subject Line**: `[SECURITY] Brief Description` - -### What to Include - -A complete vulnerability report should include: - -1. **Description**: Clear explanation of the vulnerability -2. **Impact**: Potential security impact and severity assessment -3. **Affected Versions**: Which versions are vulnerable -4. **Reproduction Steps**: Detailed steps to reproduce the issue -5. **Proof of Concept**: Code, configuration, or demonstration (if applicable) -6. **Suggested Fix**: Proposed remediation (if known) -7. **Disclosure Timeline**: Your expectations for public disclosure - -### Response Timeline - -* **Initial Response**: Within 3 business days -* **Assessment Complete**: Within 7 business days -* **Fix Timeline**: Depends on severity (see below) -* **Disclosure**: Coordinated with reporter - -## Severity Classification - -Vulnerabilities are classified using the following severity levels: - -### Critical -* Remote code execution -* Authentication bypass -* Data breach or exposure of sensitive information -* **Fix Timeline**: 7 days - -### High -* Privilege escalation -* SQL injection or command injection -* Cross-site scripting (XSS) with significant impact -* **Fix Timeline**: 14 days - -### Medium -* Information disclosure (limited scope) -* Denial of service -* Security misconfigurations with moderate impact -* **Fix Timeline**: 30 days - -### Low -* Security best practice violations -* Minor information leaks -* Issues requiring user interaction or complex preconditions -* **Fix Timeline**: 60 days or next release - -## Remediation Process - -1. **Acknowledgment**: Security team confirms receipt and begins investigation -2. **Assessment**: Vulnerability is validated, severity assigned, and impact analyzed -3. **Development**: Security patch is developed and tested -4. **Review**: Patch undergoes security review and validation -5. **Release**: Fixed version is released with security advisory -6. **Disclosure**: Public disclosure follows coordinated timeline - -## Security Advisories - -Security advisories are published via: - -* GitHub Security Advisories -* Release notes and CHANGELOG.md -* Security mailing list (when established) - -Advisories include: - -* CVE identifier (if applicable) -* Severity rating -* Affected versions -* Fixed versions -* Mitigation steps -* Attribution (with reporter consent) - -## Security Best Practices - -For repositories adopting mokocli: - -### Required Controls - -* Enable GitHub security features (Dependabot, code scanning) -* Implement branch protection on `main` -* Require code review for all changes -* Enforce signed commits (recommended) -* Use secrets management (never commit credentials) -* Maintain security documentation -* Follow secure coding standards defined in `/docs/policy/` - -### CI/CD Security - -* Validate all inputs -* Sanitize outputs -* Use least privilege access -* Pin dependencies with hash verification -* Scan for vulnerabilities in dependencies -* Audit third-party actions and tools - -#### Automated Security Scanning - -All repositories MUST implement: - -**CodeQL Analysis**: -* Enabled for all supported languages (Python, JavaScript, TypeScript, Java, C/C++, C#, Go, Ruby) -* Runs on: push to main, pull requests, weekly schedule -* Query sets: `security-extended` and `security-and-quality` -* Configuration: `.github/workflows/codeql-analysis.yml` - -**Dependabot Security Updates**: -* Weekly scans for vulnerable dependencies -* Automated pull requests for security patches -* Configuration: `.github/dependabot.yml` - -**Secret Scanning**: -* Enabled by default with push protection -* Prevents accidental credential commits -* Partner patterns enabled - -**Dependency Review**: -* Required for all pull requests -* Blocks introduction of known vulnerable dependencies -* Automatic license compliance checking - -See [Security Scanning Policy](docs/policy/security-scanning.md) for detailed requirements. - -### Dependency Management - -* Keep dependencies up to date -* Monitor security advisories for dependencies -* Remove unused dependencies -* Audit new dependencies before adoption -* Document security-critical dependencies - -## Compliance and Governance - -This security policy is binding for all repositories governed by mokocli. Deviations require documented justification and approval from the Security Owner. - -Security policies are reviewed and updated at least annually or following significant security incidents. - -## Attribution and Recognition - -We acknowledge and appreciate responsible disclosure. With your permission, we will: - -* Credit you in security advisories -* List you in CHANGELOG.md for the fix release -* Recognize your contribution publicly (if desired) - -## Contact and Escalation - -* **Security Team**: security@[DOMAIN] -* **Primary Contact**: [CONTACT_EMAIL] -* **Escalation**: For urgent matters requiring immediate attention, contact the maintainer directly via GitHub - -## Out of Scope - -The following are explicitly out of scope: - -* Issues in third-party dependencies (report directly to maintainers) -* Social engineering attacks -* Physical security issues -* Denial of service via resource exhaustion without amplification -* Issues requiring physical access to systems -* Theoretical vulnerabilities without proof of exploitability - ---- - -## Metadata - -| Field | Value | -| ------------ | ----------------------------------------------- | -| Document | Security Policy | -| Path | /SECURITY.md | -| Repository | [REPOSITORY_URL] | -| Owner | [OWNER_NAME] | -| Scope | Security vulnerability handling | -| Applies To | All repositories governed by mokocli | -| Status | Active | -| Effective | [YYYY-MM-DD] | - -## Revision History - -| Date | Change Description | Author | -| ---------- | ------------------------------------------------- | --------------- | -| [YYYY-MM-DD] | Initial creation | [AUTHOR_NAME] | diff --git a/templates/docs/required/template-TODO.md b/templates/docs/required/template-TODO.md deleted file mode 100644 index c9b35e9..0000000 --- a/templates/docs/required/template-TODO.md +++ /dev/null @@ -1,10 +0,0 @@ -# TODO - -## Critical - - - -## Normal - - - -## Low - - diff --git a/templates/fonts/Google/Open Sans-300.ttf b/templates/fonts/Google/Open Sans-300.ttf deleted file mode 100644 index 3556a92..0000000 Binary files a/templates/fonts/Google/Open Sans-300.ttf and /dev/null differ diff --git a/templates/fonts/Google/Open Sans-300italic.ttf b/templates/fonts/Google/Open Sans-300italic.ttf deleted file mode 100644 index 02a66f1..0000000 Binary files a/templates/fonts/Google/Open Sans-300italic.ttf and /dev/null differ diff --git a/templates/fonts/Google/Open Sans-500.ttf b/templates/fonts/Google/Open Sans-500.ttf deleted file mode 100644 index 5fe31ed..0000000 Binary files a/templates/fonts/Google/Open Sans-500.ttf and /dev/null differ diff --git a/templates/fonts/Google/Open Sans-500italic.ttf b/templates/fonts/Google/Open Sans-500italic.ttf deleted file mode 100644 index f9dfd7e..0000000 Binary files a/templates/fonts/Google/Open Sans-500italic.ttf and /dev/null differ diff --git a/templates/fonts/Google/Open Sans-600.ttf b/templates/fonts/Google/Open Sans-600.ttf deleted file mode 100644 index be167af..0000000 Binary files a/templates/fonts/Google/Open Sans-600.ttf and /dev/null differ diff --git a/templates/fonts/Google/Open Sans-600italic.ttf b/templates/fonts/Google/Open Sans-600italic.ttf deleted file mode 100644 index 5196de8..0000000 Binary files a/templates/fonts/Google/Open Sans-600italic.ttf and /dev/null differ diff --git a/templates/fonts/Google/Open Sans-700.ttf b/templates/fonts/Google/Open Sans-700.ttf deleted file mode 100644 index 1658163..0000000 Binary files a/templates/fonts/Google/Open Sans-700.ttf and /dev/null differ diff --git a/templates/fonts/Google/Open Sans-700italic.ttf b/templates/fonts/Google/Open Sans-700italic.ttf deleted file mode 100644 index 23e6b86..0000000 Binary files a/templates/fonts/Google/Open Sans-700italic.ttf and /dev/null differ diff --git a/templates/fonts/Google/Open Sans-800.ttf b/templates/fonts/Google/Open Sans-800.ttf deleted file mode 100644 index c1799c5..0000000 Binary files a/templates/fonts/Google/Open Sans-800.ttf and /dev/null differ diff --git a/templates/fonts/Google/Open Sans-800italic.ttf b/templates/fonts/Google/Open Sans-800italic.ttf deleted file mode 100644 index 683066a..0000000 Binary files a/templates/fonts/Google/Open Sans-800italic.ttf and /dev/null differ diff --git a/templates/fonts/Google/Open Sans-Italic.ttf b/templates/fonts/Google/Open Sans-Italic.ttf deleted file mode 100644 index 222edf6..0000000 Binary files a/templates/fonts/Google/Open Sans-Italic.ttf and /dev/null differ diff --git a/templates/fonts/Google/Open Sans-Regular.ttf b/templates/fonts/Google/Open Sans-Regular.ttf deleted file mode 100644 index 4d3ff73..0000000 Binary files a/templates/fonts/Google/Open Sans-Regular.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-Bold.ttf b/templates/fonts/Google/OpenSans-Bold.ttf deleted file mode 100644 index a803d91..0000000 Binary files a/templates/fonts/Google/OpenSans-Bold.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-BoldItalic.ttf b/templates/fonts/Google/OpenSans-BoldItalic.ttf deleted file mode 100644 index 741b0dd..0000000 Binary files a/templates/fonts/Google/OpenSans-BoldItalic.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-ExtraBold.ttf b/templates/fonts/Google/OpenSans-ExtraBold.ttf deleted file mode 100644 index a98b73c..0000000 Binary files a/templates/fonts/Google/OpenSans-ExtraBold.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-ExtraBoldItalic.ttf b/templates/fonts/Google/OpenSans-ExtraBoldItalic.ttf deleted file mode 100644 index 29ea988..0000000 Binary files a/templates/fonts/Google/OpenSans-ExtraBoldItalic.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-Italic.ttf b/templates/fonts/Google/OpenSans-Italic.ttf deleted file mode 100644 index 6533045..0000000 Binary files a/templates/fonts/Google/OpenSans-Italic.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-ItalicVariable.ttf b/templates/fonts/Google/OpenSans-ItalicVariable.ttf deleted file mode 100644 index 6533045..0000000 Binary files a/templates/fonts/Google/OpenSans-ItalicVariable.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-Light.ttf b/templates/fonts/Google/OpenSans-Light.ttf deleted file mode 100644 index 141eca7..0000000 Binary files a/templates/fonts/Google/OpenSans-Light.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-LightItalic.ttf b/templates/fonts/Google/OpenSans-LightItalic.ttf deleted file mode 100644 index 940a773..0000000 Binary files a/templates/fonts/Google/OpenSans-LightItalic.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-Medium.ttf b/templates/fonts/Google/OpenSans-Medium.ttf deleted file mode 100644 index bba26bb..0000000 Binary files a/templates/fonts/Google/OpenSans-Medium.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-MediumItalic.ttf b/templates/fonts/Google/OpenSans-MediumItalic.ttf deleted file mode 100644 index 7485365..0000000 Binary files a/templates/fonts/Google/OpenSans-MediumItalic.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-Regular.ttf b/templates/fonts/Google/OpenSans-Regular.ttf deleted file mode 100644 index c8a301e..0000000 Binary files a/templates/fonts/Google/OpenSans-Regular.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-RegularVariable.ttf b/templates/fonts/Google/OpenSans-RegularVariable.ttf deleted file mode 100644 index c8a301e..0000000 Binary files a/templates/fonts/Google/OpenSans-RegularVariable.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-SemiBold.ttf b/templates/fonts/Google/OpenSans-SemiBold.ttf deleted file mode 100644 index 7f6e04a..0000000 Binary files a/templates/fonts/Google/OpenSans-SemiBold.ttf and /dev/null differ diff --git a/templates/fonts/Google/OpenSans-SemiBoldItalic.ttf b/templates/fonts/Google/OpenSans-SemiBoldItalic.ttf deleted file mode 100644 index 4d2828e..0000000 Binary files a/templates/fonts/Google/OpenSans-SemiBoldItalic.ttf and /dev/null differ diff --git a/templates/fonts/osaka-re.ttf b/templates/fonts/osaka-re.ttf deleted file mode 100644 index db295e2..0000000 Binary files a/templates/fonts/osaka-re.ttf and /dev/null differ diff --git a/templates/governance.yml b/templates/governance.yml deleted file mode 100644 index 5a9925e..0000000 --- a/templates/governance.yml +++ /dev/null @@ -1,86 +0,0 @@ -# governance.yml — Repository governance configuration -# Place this file in each Template-* repo to define branch protections, -# labels, secrets, variables, and repository settings for governed repos. -# -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# Schema Version: 1.0 - -schema_version: "1.0" - -branch_protections: - - pattern: main - require_pull_request: true - required_approvals: 0 - dismiss_stale_reviews: true - block_on_rejected_reviews: true - restrict_pushes: true - push_whitelist: [jmiller] - enable_force_push: true - force_push_whitelist: [jmiller] - enforce_admins: false - - pattern: dev - require_pull_request: false - restrict_pushes: false - enable_force_push: true - force_push_whitelist: [jmiller] - - pattern: "rc/*" - require_pull_request: false - restrict_pushes: false - enable_force_push: true - force_push_whitelist: [jmiller] - - pattern: "beta/*" - require_pull_request: false - restrict_pushes: false - enable_force_push: true - force_push_whitelist: [jmiller] - - pattern: "alpha/*" - require_pull_request: false - restrict_pushes: false - enable_force_push: true - force_push_whitelist: [jmiller] - -labels: - - name: bug - color: d73a4a - description: "Something isn't working" - - name: enhancement - color: a2eeef - description: "New feature or request" - - name: documentation - color: "0075ca" - description: "Improvements or additions to documentation" - - name: security - color: ee0701 - description: "Security vulnerability or concern" - -secrets: - - name: GA_TOKEN - description: "Gitea API token for automation" - required: true - scope: org - - name: GH_TOKEN - description: "GitHub PAT for mirror operations" - required: false - scope: org - -variables: - - name: GITEA_URL - description: "Gitea instance URL" - default: "https://git.mokoconsulting.tech" - scope: org - - name: GITEA_ORG - description: "Gitea organization name" - default: "MokoConsulting" - scope: org - -repo_settings: - has_issues: true - has_projects: true - has_wiki: false - has_discussions: false - allow_merge_commit: true - allow_squash_merge: true - allow_rebase_merge: false - delete_branch_on_merge: true - allow_auto_merge: false diff --git a/templates/images/primary/apple-touch-icon.png b/templates/images/primary/apple-touch-icon.png deleted file mode 100644 index 6ee8e51..0000000 Binary files a/templates/images/primary/apple-touch-icon.png and /dev/null differ diff --git a/templates/images/primary/background.png b/templates/images/primary/background.png deleted file mode 100644 index 9dd154a..0000000 Binary files a/templates/images/primary/background.png and /dev/null differ diff --git a/templates/images/primary/background.svg b/templates/images/primary/background.svg deleted file mode 100644 index 593bf3d..0000000 --- a/templates/images/primary/background.svg +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/templates/images/primary/favicon-96x96.png b/templates/images/primary/favicon-96x96.png deleted file mode 100644 index db33cbe..0000000 Binary files a/templates/images/primary/favicon-96x96.png and /dev/null differ diff --git a/templates/images/primary/favicon.gif b/templates/images/primary/favicon.gif deleted file mode 100644 index fb11e2f..0000000 Binary files a/templates/images/primary/favicon.gif and /dev/null differ diff --git a/templates/images/primary/favicon.ico b/templates/images/primary/favicon.ico deleted file mode 100644 index bb4ce54..0000000 Binary files a/templates/images/primary/favicon.ico and /dev/null differ diff --git a/templates/images/primary/favicon.svg b/templates/images/primary/favicon.svg deleted file mode 100644 index 0b89898..0000000 --- a/templates/images/primary/favicon.svg +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/templates/images/primary/favicon_120.png b/templates/images/primary/favicon_120.png deleted file mode 100644 index 036b90d..0000000 Binary files a/templates/images/primary/favicon_120.png and /dev/null differ diff --git a/templates/images/primary/favicon_256.png b/templates/images/primary/favicon_256.png deleted file mode 100644 index a44ce8e..0000000 Binary files a/templates/images/primary/favicon_256.png and /dev/null differ diff --git a/templates/images/primary/logo.png b/templates/images/primary/logo.png deleted file mode 100644 index 91683ca..0000000 Binary files a/templates/images/primary/logo.png and /dev/null differ diff --git a/templates/images/primary/logo.svg b/templates/images/primary/logo.svg deleted file mode 100644 index 444e24c..0000000 --- a/templates/images/primary/logo.svg +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/templates/images/primary/logo_tiger.png b/templates/images/primary/logo_tiger.png deleted file mode 100644 index ff43fd2..0000000 Binary files a/templates/images/primary/logo_tiger.png and /dev/null differ diff --git a/templates/images/primary/tigerhead_3.svg b/templates/images/primary/tigerhead_3.svg deleted file mode 100644 index 79f5a34..0000000 --- a/templates/images/primary/tigerhead_3.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/templates/images/primary/web-app-manifest-192x192.png b/templates/images/primary/web-app-manifest-192x192.png deleted file mode 100644 index 1c01176..0000000 Binary files a/templates/images/primary/web-app-manifest-192x192.png and /dev/null differ diff --git a/templates/images/primary/web-app-manifest-512x512.png b/templates/images/primary/web-app-manifest-512x512.png deleted file mode 100644 index 4e42389..0000000 Binary files a/templates/images/primary/web-app-manifest-512x512.png and /dev/null differ diff --git a/templates/index.md b/templates/index.md deleted file mode 100644 index 0b85c2f..0000000 --- a/templates/index.md +++ /dev/null @@ -1,433 +0,0 @@ - - -# Templates Catalog - -## Overview - -This directory contains all templates and reference implementations provided by mokocli. Templates are non-authoritative examples that demonstrate how to implement the standards defined in `/docs/policy/`. - -## Purpose - -Templates serve to: - -- **Accelerate Development**: Provide ready-to-use starting points -- **Demonstrate Standards**: Show concrete implementations of policies -- **Ensure Consistency**: Standardize common patterns across repositories -- **Reduce Errors**: Pre-validated templates minimize mistakes -- **Guide Implementation**: Provide examples for complex requirements - -## Template Categories - -### Images (`images/`) - -Brand and favicon image assets for all Moko Consulting governed repositories. - -**Contents (`images/primary/`):** -- `logo.png` / `logo.svg` — Primary Moko Consulting logo -- `logo_tiger.png` / `tigerhead_3.svg` — Tiger-head brand variant -- `background.png` / `background.svg` — Full-width hero backgrounds -- `favicon_256.png` — ★ **Sync template** — deployed to Dolibarr module `img/` directories as `object_favicon_256.png` -- `favicon_120.png`, `favicon-96x96.png`, `favicon.svg`, `favicon.ico`, `favicon.gif` — favicon set -- `apple-touch-icon.png` — iOS home-screen icon -- `web-app-manifest-192x192.png`, `web-app-manifest-512x512.png` — PWA manifest icons - -**Documentation**: [docs/templates/images/index.md](../docs/templates/images/index.md) - -### Fonts (`fonts/`) - -Locally bundled typefaces for offline, print, and native use cases. - -**Contents:** -- `osaka-re.ttf` — Osaka Re, the **official stylized logo font** (TrueType, for wordmark and brand lockups only) - -Body text uses **Open Sans** via Google Fonts. -See [Google Fonts Policy](../docs/policy/google-fonts.md) for requirements. - -**Documentation**: [docs/templates/fonts/index.md](../docs/templates/fonts/index.md) - -### Workflows (`workflows/`) - -GitHub Actions workflow templates for CI/CD automation. - -**Categories:** -- **Joomla**: Joomla-specific workflows (CI, testing, deployment) -- **Dolibarr**: Dolibarr-specific workflows (CI, testing, deployment) -- **Generic**: Platform-agnostic workflows (CI, health checks, standards) - -**Key Templates:** -- Build and test automation -- Repository health monitoring -- Version branch management -- Standards compliance validation - -**Documentation**: [workflows/README.md](./workflows/README.md) - -### Configurations (`configs/`) - -Configuration file templates for common tools. - -**Templates:** -- `.editorconfig` - Editor configuration for consistent code style -- `.gitignore` - Git ignore patterns by project type -- `.yamllint` - YAML linting configuration -- Language-specific configuration files - -**Documentation**: [configs/README.md](./configs/README.md) - -### Documentation (`docs/`) - -Documentation file templates aligned with mokocli policies. - -**Templates:** -- `README.md.template` - Repository README template -- `CONTRIBUTING.md.template` - Contribution guidelines template -- `SECURITY.md.template` - Security policy template -- `CHANGELOG.md.template` - Changelog template -- Policy, guide, and checklist templates - -**Documentation**: [docs/index.md](./docs/index.md) - -### GitHub (`github/`) - -GitHub-specific templates for issues, PRs, and repository configuration. - -**Templates:** -- Issue templates (bug reports, feature requests, custom) -- Pull request template -- CODEOWNERS template -- GitHub Actions workflow examples - -**Documentation**: [github/README.md](./github/README.md) - -### Scripts (`scripts/`) - -Script templates and utilities for automation. - -**Templates:** -- Build automation scripts -- Validation and testing scripts -- Deployment scripts -- Documentation generation scripts - -**Requirements**: All scripts follow [Scripting Standards](../docs/policy/scripting-standards.md) - -**Documentation**: [scripts/index.md](./scripts/index.md) - -### Schemas (`schemas/`) - -Repository structure schema templates for defining custom repository types. - -**Templates:** -- `template-repository-structure.xml` - Base template for custom schemas -- Schema documentation and usage examples - -**Purpose**: -- Define required files and directories for repository types -- Enable automated structure validation -- Support platform-specific requirements - -**Documentation**: [docs/reference/schemas.md](./docs/reference/schemas.md) - -### Build (`build/`) - -Build system templates and configurations. - -**Templates:** -- Makefile templates by project type -- Build script templates -- Package configuration templates -- CI/CD build integration - -**Documentation**: [build/README.md](./build/README.md) - -### Security (`security/`) - -Security templates for directory listing prevention and web server protection. - -**Templates:** -- `index.html` - Static HTML redirect template (all projects) -- `index.php` - PHP server-side redirect template (PHP projects) - -**Purpose**: -- Prevent directory listing exposure on web servers -- Redirect unauthorized access to repository root -- Provide dual-layer protection for PHP-based projects - -**Usage:** -```bash -# For PHP projects (e.g., Dolibarr/MokoCRM) -find src -type d -exec sh -c 'cp templates/security/index.html "$1" && cp templates/security/index.php "$1"' _ {} \; - -# For non-PHP projects -find src -type d -exec cp templates/security/index.html {} \; -``` - -**Policy**: [SEC-DIR-001 Directory Listing Prevention](../docs/policy/security/directory-listing-prevention.md) - -**Documentation**: [security/README.md](./security/README.md) - -### Projects (`projects/`) - -Project management and planning templates. - -**Templates:** -- Project structure templates -- Task management templates -- Planning and roadmap templates - -**Documentation**: [projects/README.md](./projects/README.md) - -## Using Templates - -### Basic Usage - -1. **Identify Need**: Determine what template you need -2. **Locate Template**: Find appropriate template in this catalog -3. **Copy Template**: Copy template to your repository -4. **Customize**: Adapt template to your project needs -5. **Validate**: Ensure template meets standards -6. **Maintain**: Keep template updated with standards - -### Template vs Policy - -**Important Distinction:** - -- **Templates** (this directory): Non-binding examples and starting points -- **Policies** (`/docs/policy/`): Binding requirements that must be followed - -Templates demonstrate one way to implement policies, but other valid implementations may exist. Always refer to policies for requirements. - -### Customization Guidelines - -When customizing templates: - -1. **Preserve Structure**: Maintain overall organization -2. **Follow Standards**: Adhere to policies even when customizing -3. **Add Context**: Include project-specific information -4. **Update Headers**: Update file headers with your project info -5. **Document Changes**: Note significant deviations from template -6. **Keep Organized**: Maintain clear directory structure - -### Template Selection Guide - -**For New Repositories:** -- Review [Repository Organization Guide](../docs/guide/repository-organization.md) for structure -- Add documentation templates from `docs/` -- Copy workflow templates from `workflows/` -- Add configuration templates from `configs/` -- Apply security templates from `security/` to src directories - -**For Existing Repositories:** -- Identify gaps in current structure -- Add missing documentation from `docs/` -- Integrate workflows from `workflows/` -- Adopt configuration standards from `configs/` -- Apply security templates from `security/` to src directories - -**For Standards Compliance:** -- Use templates to meet minimum requirements -- Validate against policies in `/docs/policy/` -- Run compliance checks after implementation - -**For Security Hardening:** -- Apply `security/index.html` to all src directories (required) -- Apply `security/index.php` to all src directories in PHP projects (required) -- Refer to [SEC-DIR-001](../docs/policy/security/directory-listing-prevention.md) for requirements - -**Note:** Repository structure templates have been moved to individual scaffold repositories. See the [Repository Organization Guide](../docs/guide/repository-organization.md) for details. - -## Template Organization - -### Directory Structure - -``` -templates/ -├── index.md # This file - catalog of all templates -├── images/ # Brand and favicon image assets -│ └── primary/ # Current production brand set (logo, favicon_256, etc.) -├── fonts/ # Locally bundled typeface files (offline / print / native use) -│ └── osaka-re.ttf # Osaka Re — stylized logo font (TrueType) -├── workflows/ # GitHub Actions workflow templates -│ ├── joomla/ # Joomla-specific workflows -│ ├── dolibarr/ # Dolibarr-specific workflows -│ ├── generic/ # Platform-agnostic workflows -│ └── README.md -├── configs/ # Configuration file templates -│ ├── .editorconfig -│ ├── .gitignore -│ ├── .yamllint -│ └── README.md -├── docs/ # Documentation templates -│ ├── required/ # Required documentation files -│ ├── extra/ # Optional documentation -│ ├── README.md -│ └── index.md -├── github/ # GitHub-specific templates -│ ├── ISSUE_TEMPLATE/ # Issue templates -│ ├── PULL_REQUEST_TEMPLATE.md -│ ├── CODEOWNERS.template -│ └── README.md -├── security/ # Security templates -│ ├── index.html # Static redirect template -│ ├── index.php # PHP redirect template -│ └── README.md -├── scripts/ # Script templates -│ └── index.md -├── build/ # Build system templates -│ └── README.md -└── projects/ # Project management templates - └── README.md -``` - -**Note:** Repository structure templates (formerly in `repos/`) have been moved to individual scaffold repositories for better maintainability. - -### Naming Conventions - -**Template Files:** -- Use `.template` suffix for files meant to be copied and renamed -- Use descriptive names: `README.md.template`, `CODEOWNERS.template` -- Use lowercase with hyphens for multi-word names - -**Directories:** -- Use lowercase names -- Use singular nouns when possible -- Be descriptive but concise - -## Template Maintenance - -### Version Control - -Templates are versioned with file headers: - -```markdown -# FILE INFORMATION -VERSION: XX.YY.ZZ -``` - -**Version Increments:** -- **Major (XX)**: Breaking changes to template structure -- **Minor (YY)**: New sections or significant additions -- **Patch (ZZ)**: Minor fixes, typos, clarifications - -### Update Process - -1. **Identify Need**: Determine what needs to change -2. **Update Template**: Make changes following standards -3. **Update Version**: Increment version number appropriately -4. **Document Change**: Add to template's revision history -5. **Test Template**: Validate template works as expected -6. **Submit PR**: Create pull request for review -7. **Communicate**: Notify users of significant changes - -### Deprecation - -To deprecate a template: - -1. Add deprecation notice at top of template -2. Provide alternative template or approach -3. Set removal date (minimum 90 days) -4. Update catalog and documentation -5. After removal date, archive or remove - -## Best Practices - -### Creating New Templates - -1. **Start with Standards**: Review relevant policies first -2. **Use Existing Templates**: Base on similar templates when possible -3. **Include Headers**: Add proper file headers with metadata -4. **Document Usage**: Include inline comments and usage notes -5. **Provide Examples**: Show concrete usage examples -6. **Test Thoroughly**: Validate template works as intended -7. **Get Review**: Have template reviewed before publishing - -### Using Templates - -1. **Understand Purpose**: Read template documentation first -2. **Review Policies**: Understand requirements being implemented -3. **Customize Appropriately**: Adapt to project needs -4. **Validate Result**: Ensure result meets standards -5. **Keep Updated**: Periodically review for updates -6. **Report Issues**: Feedback improves templates - -### Common Pitfalls - -- **Don't treat templates as requirements**: Templates are examples, policies are requirements -- **Don't blindly copy**: Understand what you're copying and why -- **Don't skip customization**: Templates need project-specific adaptation -- **Don't ignore updates**: Old templates may not reflect current standards -- **Don't forget headers**: Update file headers when using templates - -## Support and Resources - -### Documentation - -- [Repository Organization Guide](../docs/guide/repository-organization.md) -- [File Header Standards](../docs/policy/file-header-standards.md) -- [Scripting Standards](../docs/policy/scripting-standards.md) -- [Workflow Standards](../docs/policy/workflow-standards.md) - -### Getting Help - -- Browse existing templates for examples -- Review mokocli repository as reference implementation -- Consult with repository maintainers -- Submit issues for template problems or requests - -### Contributing - -To contribute new templates or improvements: - -1. Review [CONTRIBUTING.md](../CONTRIBUTING.md) -2. Follow template creation best practices -3. Submit pull request with: - - New/updated template - - Documentation updates - - Test results or validation -4. Address review feedback -5. Template merged after approval - -## Metadata - -* **Document**: templates/index.md -* **Repository**: [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) -* **Owner**: Moko Consulting Engineering Team -* **Scope**: Template catalog and usage guide -* **Lifecycle**: Active -* **Audience**: All engineers and developers - -## Revision History - -| Version | Date | Author | Notes | -| -------- | ---------- | ------------------------------- | ----------------------------------------------- | -| 03.00.00 | 2026-01-28 | GitHub Copilot | Version bump to 03.00.00 across repository | -| 02.02.00 | 2026-01-16 | GitHub Copilot | Removed repos/ - moved to individual scaffold repositories | -| 02.01.00 | 2026-01-16 | GitHub Copilot | Added security templates section and reorganized for usability | -| 02.00.00 | 2026-01-13 | GitHub Copilot | Comprehensive templates catalog creation | -| 01.00.00 | 2025-01-XX | rebuild_indexes.py | Auto-generated initial index | diff --git a/templates/joomla/index.html b/templates/joomla/index.html deleted file mode 100644 index 7d3e719..0000000 --- a/templates/joomla/index.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/templates/licenses/GPL-3.0 b/templates/licenses/GPL-3.0 deleted file mode 100644 index 1120bdf..0000000 --- a/templates/licenses/GPL-3.0 +++ /dev/null @@ -1,696 +0,0 @@ - - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. - diff --git a/templates/licenses/README.md b/templates/licenses/README.md deleted file mode 100644 index eb97d3a..0000000 --- a/templates/licenses/README.md +++ /dev/null @@ -1,216 +0,0 @@ - - -# License Templates - -## Purpose - -This directory contains authoritative license files that serve as the official templates for all repositories in the `mokoconsulting-tech` organization. These files are the single source of truth for license text used across all projects. - -## Authoritative Licenses - -The following license files are maintained as authoritative sources: - -### GPL-3.0 (GNU General Public License v3.0 or later) - -**File**: `GPL-3.0` - -**SPDX Identifier**: `GPL-3.0-or-later` - -**Usage**: This is the **primary and default license** for all Moko Consulting projects unless explicitly specified otherwise. - -**Source**: Official text from Free Software Foundation (https://www.gnu.org/licenses/gpl-3.0.txt) - -**Default License For**: -- **Generic repositories** (default) -- **Joomla/WaaS component repositories** (default) -- **Dolibarr/CRM module repositories** (default) -- All software projects, libraries, frameworks, tools and utilities - -**Repository Requirements**: -- Copy this file to repository root as `LICENSE` (no extension) -- Include copyright notice in project files -- Update copyright year and owner in project header comments - -### MIT License - -**File**: `MIT` - -**SPDX Identifier**: `MIT` - -**Usage**: Alternative permissive license for specific use cases. - -**When to Use**: -- When GPL compatibility is not required -- Maximum permissiveness desired -- Integration with MIT-licensed dependencies - -### Apache License 2.0 - -**File**: `Apache-2.0` - -**SPDX Identifier**: `Apache-2.0` - -**Usage**: Alternative license providing patent protection. - -**When to Use**: -- Projects requiring explicit patent grant -- Apache ecosystem integration -- When patent protection is critical - -## Using These Licenses - -### For New Repositories - -1. **Copy the license file** to your repository root: - ```bash - # From your repository root - cp /path/to/mokocli/templates/licenses/GPL-3.0 ./LICENSE - ``` - -2. **No file extension**: The LICENSE file must not have an extension (use `LICENSE`, not `LICENSE.txt` or `LICENSE.md`) - -3. **Add copyright notice**: Update your project's README and source files with appropriate copyright notices - -4. **Reference in documentation**: Mention the license in your README.md - -### For Existing Repositories - -1. **Verify license text**: Compare your existing LICENSE file with the authoritative version -2. **Update if needed**: If text differs, update to match the authoritative version -3. **Document changes**: Note license updates in CHANGELOG.md - -## Updating License Templates - -When updating these authoritative license files: - -1. **Verify source**: Ensure updates come from official sources (FSF, OSI, Apache Foundation) -2. **Document changes**: Update this README with change details -3. **Notify stakeholders**: Announce updates to development team -4. **Version control**: Commit changes with detailed commit message -5. **Propagate**: Update existing repositories as needed - -## License Selection Guide - -### Decision Tree - -``` -Is this a new Moko Consulting project? - └─ Yes → Use GPL-3.0-or-later (default) - └─ No → See exceptions below - -Project Type Default Licenses: - - Generic Repository → GPL-3.0-or-later - - Joomla/WaaS Component → GPL-3.0-or-later - - Dolibarr/CRM Module → GPL-3.0-or-later - -Exceptions requiring different licenses: - - Third-party integration requiring MIT → Use MIT - - Patent-sensitive project → Use Apache-2.0 - - Client-specific requirements → Consult legal team -``` - -### Compatibility Matrix - -| Your License | Can Use GPL-3.0 | Can Use MIT | Can Use Apache-2.0 | -|--------------|-----------------|-------------|-------------------| -| GPL-3.0-or-later | ✅ Yes | ✅ Yes | ✅ Yes | -| MIT | ❌ No | ✅ Yes | ✅ Yes | -| Apache-2.0 | ⚠️ Compatible but becomes GPL | ✅ Yes | ✅ Yes | - -## Compliance Requirements - -### GPL-3.0-or-later Projects - -**Must**: -- Include complete license text in LICENSE file -- Include copyright notice in source files -- Provide source code to recipients -- Document modifications to GPL code -- License derivative works under GPL-3.0-or-later - -**Must Not**: -- Remove or modify license text -- Claim proprietary ownership of GPL code -- Impose additional restrictions - -### File Headers - -Include this header in all source files: - -``` -Copyright (C) [YEAR] Moko Consulting - -This file is part of [PROJECT NAME]. - -SPDX-License-Identifier: GPL-3.0-or-later - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -``` - -## Verification - -### Verify License Text Integrity - -```bash -# Check if your LICENSE matches the authoritative version -diff LICENSE /path/to/mokocli/templates/licenses/GPL-3.0 - -# Calculate checksum for verification -sha256sum LICENSE -``` - -### Automated Verification - -The repository health workflow automatically verifies: -- LICENSE file exists -- LICENSE file has no extension -- License text matches authoritative version (for GPL-3.0) -- SPDX identifier is present in source files - -## References - -- [GNU GPL v3.0 Official](https://www.gnu.org/licenses/gpl-3.0.html) -- [SPDX License List](https://spdx.org/licenses/) -- [Choose a License](https://choosealicense.com/) -- [GPL Compliance Guide](https://www.gnu.org/licenses/gpl-compliance.html) -- [mokocli License Policy](../../docs/policy/license-compliance.md) - -## Maintenance - -**Owner**: Legal/Compliance Team -**Review Cycle**: Annual or when FSF updates licenses -**Last Updated**: 2026-01-16 -**Next Review**: 2027-01-16 - -## Change History - -| Date | Change | Reason | Author | -|------|--------|--------|--------| -| 2026-01-16 | Initial creation with GPL-3.0 | Establish authoritative license source | GitHub Copilot | - -## Contact - -For questions about license selection or compliance: -- **Email**: legal@mokoconsulting.tech -- **Documentation**: See [License Compliance Policy](../../docs/policy/license-compliance.md) -- **Issues**: Open an issue in mokocli repository diff --git a/templates/licenses/index.md b/templates/licenses/index.md deleted file mode 100644 index 0139a89..0000000 --- a/templates/licenses/index.md +++ /dev/null @@ -1,43 +0,0 @@ - - -# Docs Index: /templates/licenses - -## Purpose - -This directory contains authoritative license files that serve as the official templates for all repositories in the organization. - -## License Files - -- [GPL-3.0](./GPL-3.0) - GNU General Public License v3.0 or later (Primary license) -- [README](./README.md) - License selection guide and compliance requirements - -## Usage - -Copy the appropriate license file to your repository root as `LICENSE` (no extension). - -See README.md for: -- License selection guidance -- Compliance requirements -- File header templates -- Verification procedures - -## Metadata - -- **Document Type:** index -- **Owner:** Legal/Compliance Team -- **Review Cycle:** Annual - -## Revision History - -| Change | Notes | Author | -| --- | --- | --- | -| Initial creation | Added authoritative license templates | GitHub Copilot | diff --git a/templates/makefiles/Makefile.dolibarr.template b/templates/makefiles/Makefile.dolibarr.template deleted file mode 100644 index 253fa30..0000000 --- a/templates/makefiles/Makefile.dolibarr.template +++ /dev/null @@ -1,402 +0,0 @@ -# Makefile for Dolibarr Modules -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This is a reference Makefile for building Dolibarr modules. -# Copy this to your repository root as "Makefile" and customize as needed. - -# ============================================================================== -# CONFIGURATION - Customize these for your module -# ============================================================================== - -# Module Configuration -MODULE_NAME := mokoexample -MODULE_VERSION := 1.0.0 -MODULE_NUMBER := 500000 -# Module number should be unique (500000+ for custom modules) - -# Directories -SRC_DIR := . -BUILD_DIR := build -DIST_DIR := dist -DOCS_DIR := docs - -# Dolibarr Installation (for local testing - customize path) -DOLIBARR_ROOT := /var/www/html/dolibarr -DOLIBARR_CUSTOM := $(DOLIBARR_ROOT)/htdocs/custom/$(MODULE_NAME) - -# Tools -PHP := php -COMPOSER := composer -PHPCS := vendor/bin/phpcs -PHPCBF := vendor/bin/phpcbf -PHPSTAN := vendor/bin/phpstan -PHPUNIT := vendor/bin/phpunit -MSGFMT := msgfmt - -# Coding Standards -PHPCS_STANDARD := PSR12 -PHPSTAN_LEVEL := 5 - -# Files to include in package (customize as needed) -PACKAGE_FILES := admin class core img langs lib sql *.md *.php - -# Colors for output -COLOR_RESET := \033[0m -COLOR_GREEN := \033[32m -COLOR_YELLOW := \033[33m -COLOR_BLUE := \033[34m -COLOR_RED := \033[31m - -# ============================================================================== -# TARGETS -# ============================================================================== - -.PHONY: help -help: ## Show this help message - @echo "$(COLOR_BLUE)╔════════════════════════════════════════════════════════════╗$(COLOR_RESET)" - @echo "$(COLOR_BLUE)║ Dolibarr Module Makefile ║$(COLOR_RESET)" - @echo "$(COLOR_BLUE)╚════════════════════════════════════════════════════════════╝$(COLOR_RESET)" - @echo "" - @echo "Module: $(MODULE_NAME) v$(MODULE_VERSION) (#$(MODULE_NUMBER))" - @echo "" - @echo "$(COLOR_GREEN)Available targets:$(COLOR_RESET)" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_BLUE)%-20s$(COLOR_RESET) %s\n", $$1, $$2}' - @echo "" - @echo "$(COLOR_YELLOW)Quick Start:$(COLOR_RESET)" - @echo " 1. make install-deps # Install dependencies" - @echo " 2. make build # Build module package" - @echo " 3. make test # Run tests" - @echo "" - -.PHONY: install-deps -install-deps: ## Install development dependencies - @echo "$(COLOR_BLUE)Installing dependencies...$(COLOR_RESET)" - @if [ -f "composer.json" ]; then \ - $(COMPOSER) install; \ - echo "$(COLOR_GREEN)✓ Composer dependencies installed$(COLOR_RESET)"; \ - else \ - echo "$(COLOR_YELLOW)⚠ No composer.json found$(COLOR_RESET)"; \ - fi - -.PHONY: update-deps -update-deps: ## Update dependencies - @echo "$(COLOR_BLUE)Updating dependencies...$(COLOR_RESET)" - @if [ -f "composer.json" ]; then \ - $(COMPOSER) update; \ - echo "$(COLOR_GREEN)✓ Composer dependencies updated$(COLOR_RESET)"; \ - fi - -.PHONY: lint -lint: ## Run PHP linter (syntax check) - @echo "$(COLOR_BLUE)Running PHP linter...$(COLOR_RESET)" - @find . -name "*.php" ! -path "./vendor/*" ! -path "./$(BUILD_DIR)/*" \ - -exec $(PHP) -l {} \; | grep -v "No syntax errors" || true - @echo "$(COLOR_GREEN)✓ PHP linting complete$(COLOR_RESET)" - -.PHONY: phpcs -phpcs: ## Run PHP CodeSniffer - @echo "$(COLOR_BLUE)Running PHP CodeSniffer...$(COLOR_RESET)" - @if [ -f "$(PHPCS)" ]; then \ - $(PHPCS) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,$(BUILD_DIR) .; \ - else \ - echo "$(COLOR_YELLOW)⚠ PHP CodeSniffer not installed. Run: make install-deps$(COLOR_RESET)"; \ - fi - -.PHONY: phpcbf -phpcbf: ## Fix coding standards automatically - @echo "$(COLOR_BLUE)Running PHP Code Beautifier...$(COLOR_RESET)" - @if [ -f "$(PHPCBF)" ]; then \ - $(PHPCBF) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,$(BUILD_DIR) .; \ - echo "$(COLOR_GREEN)✓ Code formatting applied$(COLOR_RESET)"; \ - else \ - echo "$(COLOR_YELLOW)⚠ PHP Code Beautifier not installed. Run: make install-deps$(COLOR_RESET)"; \ - fi - -.PHONY: phpstan -phpstan: ## Run PHPStan static analysis - @echo "$(COLOR_BLUE)Running PHPStan...$(COLOR_RESET)" - @if [ -f "$(PHPSTAN)" ]; then \ - $(PHPSTAN) analyse --level=$(PHPSTAN_LEVEL) --no-progress class admin core || true; \ - else \ - echo "$(COLOR_YELLOW)⚠ PHPStan not installed. Run: make install-deps$(COLOR_RESET)"; \ - fi - -.PHONY: validate -validate: lint phpcs ## Run all validation checks - @echo "$(COLOR_GREEN)✓ All validation checks passed$(COLOR_RESET)" - -.PHONY: test -test: ## Run PHPUnit tests - @echo "$(COLOR_BLUE)Running tests...$(COLOR_RESET)" - @if [ -f "$(PHPUNIT)" ] && [ -f "phpunit.xml" ]; then \ - $(PHPUNIT); \ - else \ - echo "$(COLOR_YELLOW)⚠ PHPUnit not configured$(COLOR_RESET)"; \ - fi - -.PHONY: test-coverage -test-coverage: ## Run tests with coverage report - @echo "$(COLOR_BLUE)Running tests with coverage...$(COLOR_RESET)" - @if [ -f "$(PHPUNIT)" ] && [ -f "phpunit.xml" ]; then \ - $(PHPUNIT) --coverage-html $(BUILD_DIR)/coverage; \ - echo "$(COLOR_GREEN)✓ Coverage report: $(BUILD_DIR)/coverage/index.html$(COLOR_RESET)"; \ - else \ - echo "$(COLOR_YELLOW)⚠ PHPUnit not configured$(COLOR_RESET)"; \ - fi - -.PHONY: compile-translations -compile-translations: ## Compile translation files (.po to .mo) - @echo "$(COLOR_BLUE)Compiling translation files...$(COLOR_RESET)" - @if command -v $(MSGFMT) >/dev/null 2>&1; then \ - for po in langs/*/*.po 2>/dev/null; do \ - if [ -f "$$po" ]; then \ - mo=$${po%.po}.mo; \ - $(MSGFMT) -o "$$mo" "$$po"; \ - echo " Compiled: $$po -> $$mo"; \ - fi; \ - done; \ - echo "$(COLOR_GREEN)✓ Translation files compiled$(COLOR_RESET)"; \ - else \ - echo "$(COLOR_YELLOW)⚠ msgfmt not installed. Install: apt-get install gettext$(COLOR_RESET)"; \ - fi - -.PHONY: validate-structure -validate-structure: ## Validate Dolibarr module structure - @echo "$(COLOR_BLUE)Validating module structure...$(COLOR_RESET)" - @ERRORS=0; \ - \ - if [ ! -d "core/modules" ]; then \ - echo "$(COLOR_RED)✗ Missing core/modules/ directory$(COLOR_RESET)"; \ - ERRORS=$$((ERRORS + 1)); \ - fi; \ - \ - if [ ! -f "core/modules/mod$(MODULE_NAME).class.php" ]; then \ - echo "$(COLOR_YELLOW)⚠ Module descriptor not found: core/modules/mod$(MODULE_NAME).class.php$(COLOR_RESET)"; \ - fi; \ - \ - if [ ! -d "admin" ]; then \ - echo "$(COLOR_YELLOW)⚠ No admin/ directory (may be optional)$(COLOR_RESET)"; \ - fi; \ - \ - if [ ! -d "class" ]; then \ - echo "$(COLOR_YELLOW)⚠ No class/ directory (may be optional)$(COLOR_RESET)"; \ - fi; \ - \ - if [ $$ERRORS -eq 0 ]; then \ - echo "$(COLOR_GREEN)✓ Module structure appears valid$(COLOR_RESET)"; \ - else \ - echo "$(COLOR_RED)✗ Module structure validation failed$(COLOR_RESET)"; \ - exit 1; \ - fi - -.PHONY: check-migrations -check-migrations: ## Check SQL migration files - @echo "$(COLOR_BLUE)Checking database migration files...$(COLOR_RESET)" - @if [ -d "sql" ]; then \ - SQL_FILES=$$(find sql -name "*.sql" 2>/dev/null | wc -l); \ - if [ $$SQL_FILES -gt 0 ]; then \ - echo "$(COLOR_GREEN)✓ Found $$SQL_FILES SQL migration files$(COLOR_RESET)"; \ - find sql -name "*.sql" -exec echo " {}" \;; \ - else \ - echo "$(COLOR_YELLOW)⚠ No SQL migration files found$(COLOR_RESET)"; \ - fi; \ - else \ - echo "$(COLOR_YELLOW)⚠ No sql/ directory$(COLOR_RESET)"; \ - fi - -.PHONY: clean -clean: ## Clean build artifacts - @echo "$(COLOR_BLUE)Cleaning build artifacts...$(COLOR_RESET)" - @rm -rf $(BUILD_DIR) $(DIST_DIR) vendor - @find . -name "*.log" -delete - @find . -name ".DS_Store" -delete - @echo "$(COLOR_GREEN)✓ Build artifacts cleaned$(COLOR_RESET)" - -.PHONY: build -build: clean validate validate-structure compile-translations ## Build module package - @echo "$(COLOR_BLUE)Building Dolibarr module package...$(COLOR_RESET)" - @mkdir -p $(DIST_DIR) $(BUILD_DIR)/$(MODULE_NAME) - - # Copy files to build directory - @rsync -av --progress \ - --exclude='$(BUILD_DIR)' \ - --exclude='$(DIST_DIR)' \ - --exclude='vendor' \ - --exclude='.git*' \ - --exclude='Makefile' \ - --exclude='composer.json' \ - --exclude='composer.lock' \ - --exclude='phpunit.xml' \ - --exclude='phpstan.neon' \ - --exclude='tests/' \ - $(PACKAGE_FILES) $(BUILD_DIR)/$(MODULE_NAME)/ 2>/dev/null || true - - # Create ZIP package - @cd $(BUILD_DIR) && zip -r ../$(DIST_DIR)/$(MODULE_NAME)-$(MODULE_VERSION).zip $(MODULE_NAME) - - @echo "$(COLOR_GREEN)✓ Package created: $(DIST_DIR)/$(MODULE_NAME)-$(MODULE_VERSION).zip$(COLOR_RESET)" - -.PHONY: package -package: build ## Alias for build - @echo "$(COLOR_GREEN)✓ Package ready for distribution$(COLOR_RESET)" - -.PHONY: install-local -install-local: build ## Install module to local Dolibarr - @echo "$(COLOR_BLUE)Installing module to local Dolibarr...$(COLOR_RESET)" - @if [ ! -d "$(DOLIBARR_ROOT)" ]; then \ - echo "$(COLOR_RED)✗ Dolibarr root not found at $(DOLIBARR_ROOT)$(COLOR_RESET)"; \ - echo "Update DOLIBARR_ROOT in Makefile"; \ - exit 1; \ - fi - - # Remove existing installation - @rm -rf $(DOLIBARR_CUSTOM) - - # Extract package - @mkdir -p $(DOLIBARR_ROOT)/htdocs/custom - @unzip -o $(DIST_DIR)/$(MODULE_NAME)-$(MODULE_VERSION).zip -d $(DOLIBARR_ROOT)/htdocs/custom/ - - # Set permissions (if running as root/sudo) - @if [ "$$EUID" -eq 0 ]; then \ - chown -R www-data:www-data $(DOLIBARR_CUSTOM); \ - echo "$(COLOR_GREEN)✓ Permissions set$(COLOR_RESET)"; \ - fi - - @echo "$(COLOR_GREEN)✓ Module installed at $(DOLIBARR_CUSTOM)$(COLOR_RESET)" - @echo "$(COLOR_YELLOW)Enable module at: Home → Setup → Modules/Applications$(COLOR_RESET)" - -.PHONY: uninstall-local -uninstall-local: ## Uninstall module from local Dolibarr - @echo "$(COLOR_BLUE)Uninstalling module...$(COLOR_RESET)" - @if [ -d "$(DOLIBARR_CUSTOM)" ]; then \ - rm -rf $(DOLIBARR_CUSTOM); \ - echo "$(COLOR_GREEN)✓ Module uninstalled$(COLOR_RESET)"; \ - else \ - echo "$(COLOR_YELLOW)⚠ Module not installed at $(DOLIBARR_CUSTOM)$(COLOR_RESET)"; \ - fi - -.PHONY: dev-install -dev-install: ## Create symlink for development - @echo "$(COLOR_BLUE)Creating development symlink...$(COLOR_RESET)" - @if [ ! -d "$(DOLIBARR_ROOT)" ]; then \ - echo "$(COLOR_RED)✗ Dolibarr root not found at $(DOLIBARR_ROOT)$(COLOR_RESET)"; \ - echo "Update DOLIBARR_ROOT in Makefile"; \ - exit 1; \ - fi - - @rm -rf $(DOLIBARR_CUSTOM) - @mkdir -p $(DOLIBARR_ROOT)/htdocs/custom - @ln -s "$(PWD)" $(DOLIBARR_CUSTOM) - - @echo "$(COLOR_GREEN)✓ Development symlink created at $(DOLIBARR_CUSTOM)$(COLOR_RESET)" - @echo "$(COLOR_YELLOW)Enable module at: Home → Setup → Modules/Applications$(COLOR_RESET)" - -.PHONY: watch -watch: ## Watch for changes and rebuild - @echo "$(COLOR_BLUE)Watching for changes...$(COLOR_RESET)" - @echo "$(COLOR_YELLOW)Press Ctrl+C to stop$(COLOR_RESET)" - @while true; do \ - inotifywait -r -e modify,create,delete --exclude '($(BUILD_DIR)|$(DIST_DIR)|vendor)' . 2>/dev/null || \ - (echo "$(COLOR_YELLOW)⚠ inotifywait not installed. Install: apt-get install inotify-tools$(COLOR_RESET)" && sleep 5); \ - make validate; \ - done - -.PHONY: docs -docs: ## Generate documentation - @echo "$(COLOR_BLUE)Generating documentation...$(COLOR_RESET)" - @mkdir -p $(DOCS_DIR) - @if command -v phpdoc >/dev/null 2>&1; then \ - phpdoc -d class,admin,core -t $(DOCS_DIR); \ - echo "$(COLOR_GREEN)✓ Documentation generated in $(DOCS_DIR)$(COLOR_RESET)"; \ - else \ - echo "$(COLOR_YELLOW)⚠ phpDocumentor not installed$(COLOR_RESET)"; \ - fi - -.PHONY: db-install -db-install: ## Install database tables (requires DB credentials) - @echo "$(COLOR_BLUE)Installing database tables...$(COLOR_RESET)" - @if [ -z "$(DB_NAME)" ]; then \ - echo "$(COLOR_RED)✗ DB_NAME not set$(COLOR_RESET)"; \ - echo "Usage: make db-install DB_NAME=dolibarr DB_USER=root MYSQL_PWD=password"; \ - exit 1; \ - fi - @if [ ! -d "sql" ]; then \ - echo "$(COLOR_YELLOW)⚠ No sql/ directory found$(COLOR_RESET)"; \ - exit 0; \ - fi - @for sql in sql/llx_$(MODULE_NAME)_*.sql 2>/dev/null; do \ - if [ -f "$$sql" ]; then \ - echo "Executing $$sql..."; \ - mysql -u $(DB_USER) $(DB_NAME) < "$$sql"; \ - fi; \ - done - @echo "$(COLOR_GREEN)✓ Database tables installed$(COLOR_RESET)" - -.PHONY: db-uninstall -db-uninstall: ## Remove database tables (requires DB credentials) - @echo "$(COLOR_BLUE)Removing database tables...$(COLOR_RESET)" - @if [ -z "$(DB_NAME)" ]; then \ - echo "$(COLOR_RED)✗ DB_NAME not set$(COLOR_RESET)"; \ - echo "Usage: make db-uninstall DB_NAME=dolibarr DB_USER=root MYSQL_PWD=password"; \ - exit 1; \ - fi - @mysql -u $(DB_USER) $(DB_NAME) -e "SHOW TABLES LIKE 'llx_$(MODULE_NAME)_%';" | tail -n +2 | while read table; do \ - echo "Dropping $$table..."; \ - mysql -u $(DB_USER) $(DB_NAME) -e "DROP TABLE IF EXISTS $$table;"; \ - done - @echo "$(COLOR_GREEN)✓ Database tables removed$(COLOR_RESET)" - -.PHONY: tail-logs -tail-logs: ## Tail Dolibarr error logs - @if [ -f "$(DOLIBARR_ROOT)/documents/dolibarr.log" ]; then \ - tail -f $(DOLIBARR_ROOT)/documents/dolibarr.log; \ - else \ - echo "$(COLOR_YELLOW)⚠ Log file not found at $(DOLIBARR_ROOT)/documents/dolibarr.log$(COLOR_RESET)"; \ - fi - -.PHONY: clear-cache -clear-cache: ## Clear Dolibarr cache - @echo "$(COLOR_BLUE)Clearing cache...$(COLOR_RESET)" - @if [ -d "$(DOLIBARR_ROOT)/documents/temp" ]; then \ - rm -rf $(DOLIBARR_ROOT)/documents/temp/*; \ - echo "$(COLOR_GREEN)✓ Cache cleared$(COLOR_RESET)"; \ - else \ - echo "$(COLOR_YELLOW)⚠ Cache directory not found$(COLOR_RESET)"; \ - fi - -.PHONY: version -version: ## Display version information - @echo "$(COLOR_BLUE)Module Information:$(COLOR_RESET)" - @echo " Name: $(MODULE_NAME)" - @echo " Version: $(MODULE_VERSION)" - @echo " Number: $(MODULE_NUMBER)" - -.PHONY: security-check -security-check: ## Run security checks on dependencies - @echo "$(COLOR_BLUE)Running security checks...$(COLOR_RESET)" - @if [ -f "composer.json" ]; then \ - $(COMPOSER) audit || echo "$(COLOR_YELLOW)⚠ Vulnerabilities found$(COLOR_RESET)"; \ - fi - -.PHONY: release -release: validate test build ## Create a release (validate + test + build) - @echo "$(COLOR_GREEN)✓ Release package ready$(COLOR_RESET)" - @echo "" - @echo "$(COLOR_BLUE)Release Checklist:$(COLOR_RESET)" - @echo " [ ] Update CHANGELOG.md" - @echo " [ ] Update version in module descriptor" - @echo " [ ] Test installation in clean Dolibarr" - @echo " [ ] Test database migrations" - @echo " [ ] Tag release in git: git tag v$(MODULE_VERSION)" - @echo " [ ] Push tags: git push --tags" - @echo " [ ] Create GitHub release" - @echo "" - @echo "$(COLOR_GREEN)Package: $(DIST_DIR)/$(MODULE_NAME)-$(MODULE_VERSION).zip$(COLOR_RESET)" - -.PHONY: all -all: install-deps validate test build ## Run complete build pipeline - @echo "$(COLOR_GREEN)✓ Complete build pipeline finished$(COLOR_RESET)" - -# Default target -.DEFAULT_GOAL := help diff --git a/templates/makefiles/Makefile.generic.template b/templates/makefiles/Makefile.generic.template deleted file mode 100644 index abccdf9..0000000 --- a/templates/makefiles/Makefile.generic.template +++ /dev/null @@ -1,402 +0,0 @@ -# Generic Project Makefile -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This is a reference Makefile for generic PHP/Node.js projects. -# Copy this to your repository root as "Makefile" and customize as needed. - -# ============================================================================== -# CONFIGURATION - Customize these for your project -# ============================================================================== - -# Project Configuration -PROJECT_NAME := myproject -PROJECT_VERSION := 1.0.0 - -# Directories -SRC_DIR := src -BUILD_DIR := build -DIST_DIR := dist -TESTS_DIR := tests -DOCS_DIR := docs - -# Tools -PHP := php -COMPOSER := composer -NPM := npm -NODE := node -PHPCS := vendor/bin/phpcs -PHPCBF := vendor/bin/phpcbf -PHPUNIT := vendor/bin/phpunit -PHPSTAN := vendor/bin/phpstan - -# Coding Standards -PHPCS_STANDARD := PSR12 -PHPSTAN_LEVEL := 5 - -# Build types (development, production, staging) -BUILD_TYPE ?= production - -# Colors for output -COLOR_RESET := \033[0m -COLOR_GREEN := \033[32m -COLOR_YELLOW := \033[33m -COLOR_BLUE := \033[34m -COLOR_RED := \033[31m - -# ============================================================================== -# TARGETS -# ============================================================================== - -.PHONY: help -help: ## Show this help message - @echo "$(COLOR_BLUE)╔════════════════════════════════════════════════════════════╗$(COLOR_RESET)" - @echo "$(COLOR_BLUE)║ Generic Project Makefile ║$(COLOR_RESET)" - @echo "$(COLOR_BLUE)╚════════════════════════════════════════════════════════════╝$(COLOR_RESET)" - @echo "" - @echo "Project: $(PROJECT_NAME) v$(PROJECT_VERSION)" - @echo "Build Type: $(BUILD_TYPE)" - @echo "" - @echo "$(COLOR_GREEN)Available targets:$(COLOR_RESET)" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_BLUE)%-20s$(COLOR_RESET) %s\n", $$1, $$2}' - @echo "" - @echo "$(COLOR_YELLOW)Quick Start:$(COLOR_RESET)" - @echo " 1. make install-deps # Install dependencies" - @echo " 2. make build # Build project" - @echo " 3. make test # Run tests" - @echo "" - @echo "$(COLOR_YELLOW)Build Types:$(COLOR_RESET)" - @echo " make build BUILD_TYPE=development" - @echo " make build BUILD_TYPE=production" - @echo " make build BUILD_TYPE=staging" - @echo "" - -.PHONY: install-deps -install-deps: ## Install all dependencies (Composer + npm) - @echo "$(COLOR_BLUE)Installing dependencies...$(COLOR_RESET)" - @if [ -f "composer.json" ]; then \ - echo "Installing Composer dependencies..."; \ - $(COMPOSER) install; \ - echo "$(COLOR_GREEN)✓ Composer dependencies installed$(COLOR_RESET)"; \ - fi - @if [ -f "package.json" ]; then \ - echo "Installing npm dependencies..."; \ - $(NPM) install; \ - echo "$(COLOR_GREEN)✓ npm dependencies installed$(COLOR_RESET)"; \ - fi - -.PHONY: update-deps -update-deps: ## Update all dependencies - @echo "$(COLOR_BLUE)Updating dependencies...$(COLOR_RESET)" - @if [ -f "composer.json" ]; then \ - $(COMPOSER) update; \ - echo "$(COLOR_GREEN)✓ Composer dependencies updated$(COLOR_RESET)"; \ - fi - @if [ -f "package.json" ]; then \ - $(NPM) update; \ - echo "$(COLOR_GREEN)✓ npm dependencies updated$(COLOR_RESET)"; \ - fi - -.PHONY: lint -lint: ## Run linters (PHP + JavaScript) - @echo "$(COLOR_BLUE)Running linters...$(COLOR_RESET)" - - # PHP linting - @if find . -name "*.php" ! -path "./vendor/*" ! -path "./node_modules/*" ! -path "./$(BUILD_DIR)/*" | head -1 | grep -q .; then \ - echo "Linting PHP files..."; \ - find . -name "*.php" ! -path "./vendor/*" ! -path "./node_modules/*" ! -path "./$(BUILD_DIR)/*" \ - -exec $(PHP) -l {} \; | grep -v "No syntax errors" || true; \ - echo "$(COLOR_GREEN)✓ PHP linting complete$(COLOR_RESET)"; \ - fi - - # JavaScript linting - @if [ -f "package.json" ] && grep -q "eslint" package.json 2>/dev/null; then \ - echo "Linting JavaScript files..."; \ - $(NPM) run lint 2>/dev/null || echo "$(COLOR_YELLOW)⚠ ESLint not configured$(COLOR_RESET)"; \ - fi - -.PHONY: phpcs -phpcs: ## Run PHP CodeSniffer - @echo "$(COLOR_BLUE)Running PHP CodeSniffer...$(COLOR_RESET)" - @if [ -f "$(PHPCS)" ]; then \ - $(PHPCS) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,node_modules,$(BUILD_DIR) $(SRC_DIR); \ - else \ - echo "$(COLOR_YELLOW)⚠ PHP CodeSniffer not installed. Run: make install-deps$(COLOR_RESET)"; \ - fi - -.PHONY: phpcbf -phpcbf: ## Fix PHP coding standards automatically - @echo "$(COLOR_BLUE)Running PHP Code Beautifier...$(COLOR_RESET)" - @if [ -f "$(PHPCBF)" ]; then \ - $(PHPCBF) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,node_modules,$(BUILD_DIR) $(SRC_DIR); \ - echo "$(COLOR_GREEN)✓ Code formatting applied$(COLOR_RESET)"; \ - else \ - echo "$(COLOR_YELLOW)⚠ PHP Code Beautifier not installed. Run: make install-deps$(COLOR_RESET)"; \ - fi - -.PHONY: phpstan -phpstan: ## Run PHPStan static analysis - @echo "$(COLOR_BLUE)Running PHPStan...$(COLOR_RESET)" - @if [ -f "$(PHPSTAN)" ]; then \ - $(PHPSTAN) analyse --level=$(PHPSTAN_LEVEL) --no-progress $(SRC_DIR) || true; \ - else \ - echo "$(COLOR_YELLOW)⚠ PHPStan not installed. Run: make install-deps$(COLOR_RESET)"; \ - fi - -.PHONY: format -format: ## Format code (PHP + JavaScript) - @echo "$(COLOR_BLUE)Formatting code...$(COLOR_RESET)" - @$(MAKE) phpcbf - @if [ -f "package.json" ] && grep -q "prettier" package.json 2>/dev/null; then \ - $(NPM) run format 2>/dev/null || echo "$(COLOR_YELLOW)⚠ Prettier not configured$(COLOR_RESET)"; \ - fi - -.PHONY: validate -validate: lint phpcs ## Run all validation checks - @echo "$(COLOR_GREEN)✓ All validation checks passed$(COLOR_RESET)" - -.PHONY: test -test: ## Run all tests - @echo "$(COLOR_BLUE)Running tests...$(COLOR_RESET)" - - # PHP tests - @if [ -f "$(PHPUNIT)" ] && [ -f "phpunit.xml" ]; then \ - echo "Running PHPUnit tests..."; \ - $(PHPUNIT); \ - fi - - # JavaScript tests - @if [ -f "package.json" ] && grep -q "\"test\":" package.json; then \ - echo "Running JavaScript tests..."; \ - $(NPM) test; \ - fi - - @echo "$(COLOR_GREEN)✓ All tests complete$(COLOR_RESET)" - -.PHONY: test-coverage -test-coverage: ## Run tests with coverage report - @echo "$(COLOR_BLUE)Running tests with coverage...$(COLOR_RESET)" - @mkdir -p $(BUILD_DIR)/coverage - - @if [ -f "$(PHPUNIT)" ] && [ -f "phpunit.xml" ]; then \ - $(PHPUNIT) --coverage-html $(BUILD_DIR)/coverage/php; \ - echo "$(COLOR_GREEN)✓ PHP coverage: $(BUILD_DIR)/coverage/php/index.html$(COLOR_RESET)"; \ - fi - - @if [ -f "package.json" ] && grep -q "test:coverage" package.json; then \ - $(NPM) run test:coverage; \ - echo "$(COLOR_GREEN)✓ JavaScript coverage generated$(COLOR_RESET)"; \ - fi - -.PHONY: clean -clean: ## Clean build artifacts - @echo "$(COLOR_BLUE)Cleaning build artifacts...$(COLOR_RESET)" - @rm -rf $(BUILD_DIR) $(DIST_DIR) - @find . -name "*.log" -delete - @find . -name ".DS_Store" -delete - @echo "$(COLOR_GREEN)✓ Build artifacts cleaned$(COLOR_RESET)" - -.PHONY: build -build: clean validate ## Build project - @echo "$(COLOR_BLUE)Building project ($(BUILD_TYPE))...$(COLOR_RESET)" - @mkdir -p $(BUILD_DIR) $(DIST_DIR) - - # Install production dependencies - @if [ -f "composer.json" ]; then \ - if [ "$(BUILD_TYPE)" = "production" ]; then \ - $(COMPOSER) install --no-dev --optimize-autoloader --no-interaction; \ - else \ - $(COMPOSER) install --no-interaction; \ - fi; \ - fi - - @if [ -f "package.json" ]; then \ - if [ "$(BUILD_TYPE)" = "production" ]; then \ - $(NPM) ci --production; \ - else \ - $(NPM) ci; \ - fi; \ - fi - - # Build assets if build script exists - @if [ -f "package.json" ] && grep -q "\"build\":" package.json; then \ - echo "Building assets..."; \ - $(NPM) run build; \ - echo "$(COLOR_GREEN)✓ Assets built$(COLOR_RESET)"; \ - fi - - # Copy source files to build directory - @rsync -av --progress \ - --exclude='$(BUILD_DIR)' \ - --exclude='$(DIST_DIR)' \ - --exclude='.git*' \ - --exclude='node_modules/' \ - --exclude='tests/' \ - --exclude='Makefile' \ - --exclude='phpunit.xml' \ - --exclude='*.md' \ - $(SRC_DIR)/ $(BUILD_DIR)/ - - @echo "$(COLOR_GREEN)✓ Build complete: $(BUILD_DIR)/$(COLOR_RESET)" - -.PHONY: package -package: build ## Create distribution package - @echo "$(COLOR_BLUE)Creating distribution package...$(COLOR_RESET)" - @mkdir -p $(DIST_DIR) - - # Create tarball - @tar -czf $(DIST_DIR)/$(PROJECT_NAME)-$(PROJECT_VERSION).tar.gz -C $(BUILD_DIR) . - - # Create zip - @cd $(BUILD_DIR) && zip -r ../$(DIST_DIR)/$(PROJECT_NAME)-$(PROJECT_VERSION).zip . - - @echo "$(COLOR_GREEN)✓ Packages created:$(COLOR_RESET)" - @echo " - $(DIST_DIR)/$(PROJECT_NAME)-$(PROJECT_VERSION).tar.gz" - @echo " - $(DIST_DIR)/$(PROJECT_NAME)-$(PROJECT_VERSION).zip" - -.PHONY: dev -dev: ## Start development server - @echo "$(COLOR_BLUE)Starting development server...$(COLOR_RESET)" - @if [ -f "package.json" ] && grep -q "\"dev\":" package.json; then \ - $(NPM) run dev; \ - elif [ -f "composer.json" ] && command -v php >/dev/null 2>&1; then \ - echo "Starting PHP built-in server..."; \ - $(PHP) -S localhost:8000 -t $(SRC_DIR); \ - else \ - echo "$(COLOR_RED)✗ No development server configuration found$(COLOR_RESET)"; \ - exit 1; \ - fi - -.PHONY: watch -watch: ## Watch for changes and rebuild - @echo "$(COLOR_BLUE)Watching for changes...$(COLOR_RESET)" - @if [ -f "package.json" ] && grep -q "\"watch\":" package.json; then \ - $(NPM) run watch; \ - else \ - echo "$(COLOR_YELLOW)⚠ No watch script configured$(COLOR_RESET)"; \ - echo "Add to package.json: \"watch\": \"nodemon --exec make build\""; \ - fi - -.PHONY: docs -docs: ## Generate documentation - @echo "$(COLOR_BLUE)Generating documentation...$(COLOR_RESET)" - @mkdir -p $(DOCS_DIR) - - # PHP documentation - @if command -v phpdoc >/dev/null 2>&1; then \ - phpdoc -d $(SRC_DIR) -t $(DOCS_DIR)/php; \ - echo "$(COLOR_GREEN)✓ PHP documentation: $(DOCS_DIR)/php$(COLOR_RESET)"; \ - fi - - # JavaScript documentation - @if [ -f "package.json" ] && grep -q "jsdoc" package.json; then \ - $(NPM) run docs 2>/dev/null || echo "$(COLOR_YELLOW)⚠ JSDoc not configured$(COLOR_RESET)"; \ - fi - -.PHONY: version -version: ## Display version information - @echo "$(COLOR_BLUE)Project Information:$(COLOR_RESET)" - @echo " Name: $(PROJECT_NAME)" - @echo " Version: $(PROJECT_VERSION)" - @echo "" - @if [ -f "composer.json" ]; then \ - echo "PHP Dependencies:"; \ - $(COMPOSER) show -i | head -5; \ - echo ""; \ - fi - @if [ -f "package.json" ]; then \ - echo "Node.js Dependencies:"; \ - $(NPM) list --depth=0 | head -10; \ - fi - -.PHONY: security-check -security-check: ## Run security checks on dependencies - @echo "$(COLOR_BLUE)Running security checks...$(COLOR_RESET)" - - @if [ -f "composer.json" ]; then \ - echo "Checking Composer dependencies..."; \ - $(COMPOSER) audit || echo "$(COLOR_YELLOW)⚠ Composer vulnerabilities found$(COLOR_RESET)"; \ - fi - - @if [ -f "package.json" ]; then \ - echo "Checking npm dependencies..."; \ - $(NPM) audit || echo "$(COLOR_YELLOW)⚠ npm vulnerabilities found$(COLOR_RESET)"; \ - fi - - @echo "$(COLOR_GREEN)✓ Security checks complete$(COLOR_RESET)" - -.PHONY: outdated -outdated: ## Check for outdated dependencies - @echo "$(COLOR_BLUE)Checking for outdated dependencies...$(COLOR_RESET)" - - @if [ -f "composer.json" ]; then \ - echo "Composer outdated packages:"; \ - $(COMPOSER) outdated --direct || true; \ - echo ""; \ - fi - - @if [ -f "package.json" ]; then \ - echo "npm outdated packages:"; \ - $(NPM) outdated || true; \ - fi - -.PHONY: fix -fix: ## Fix code issues automatically - @$(MAKE) phpcbf - @$(MAKE) format - -.PHONY: ci -ci: install-deps validate test ## Run CI pipeline (install, validate, test) - @echo "$(COLOR_GREEN)✓ CI pipeline complete$(COLOR_RESET)" - -.PHONY: release -release: validate test build package ## Create a release - @echo "$(COLOR_GREEN)✓ Release ready$(COLOR_RESET)" - @echo "" - @echo "$(COLOR_BLUE)Release Checklist:$(COLOR_RESET)" - @echo " [ ] Update CHANGELOG.md" - @echo " [ ] Update version in package.json/composer.json" - @echo " [ ] Test package installation" - @echo " [ ] Tag release: git tag v$(PROJECT_VERSION)" - @echo " [ ] Push tags: git push --tags" - @echo " [ ] Create GitHub release" - @echo "" - @echo "$(COLOR_GREEN)Packages:$(COLOR_RESET)" - @ls -lh $(DIST_DIR)/ - -.PHONY: benchmark -benchmark: ## Run performance benchmarks - @echo "$(COLOR_BLUE)Running benchmarks...$(COLOR_RESET)" - @if [ -f "package.json" ] && grep -q "benchmark" package.json; then \ - $(NPM) run benchmark; \ - else \ - echo "$(COLOR_YELLOW)⚠ No benchmark script configured$(COLOR_RESET)"; \ - fi - -.PHONY: profile -profile: ## Profile application performance - @echo "$(COLOR_BLUE)Profiling application...$(COLOR_RESET)" - @if command -v xdebug >/dev/null 2>&1; then \ - echo "Xdebug profiling enabled"; \ - $(PHP) -d xdebug.mode=profile $(SRC_DIR)/index.php; \ - else \ - echo "$(COLOR_YELLOW)⚠ Xdebug not installed$(COLOR_RESET)"; \ - fi - -.PHONY: deploy -deploy: ## Deploy application (customize for your infrastructure) - @echo "$(COLOR_BLUE)Deploying application...$(COLOR_RESET)" - @echo "$(COLOR_YELLOW)⚠ Deploy target not configured$(COLOR_RESET)" - @echo "Customize this target for your deployment needs" - @echo "Examples:" - @echo " - rsync to remote server" - @echo " - Docker container build/push" - @echo " - Cloud provider deployment (AWS, GCP, Azure)" - @echo " - FTP/SFTP upload" - -.PHONY: all -all: install-deps validate test build package ## Run complete pipeline - @echo "$(COLOR_GREEN)✓ Complete pipeline finished$(COLOR_RESET)" - -# Default target -.DEFAULT_GOAL := help diff --git a/templates/makefiles/Makefile.joomla.template b/templates/makefiles/Makefile.joomla.template deleted file mode 100644 index ec94cc9..0000000 --- a/templates/makefiles/Makefile.joomla.template +++ /dev/null @@ -1,333 +0,0 @@ -# Makefile for Joomla Extensions -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# This is a reference Makefile for building Joomla extensions. -# Copy this to your repository root as "Makefile" and customize as needed. -# -# Supports: Modules, Plugins, Components, Packages, Templates - -# ============================================================================== -# CONFIGURATION - Customize these for your extension -# ============================================================================== - -# Extension Configuration -EXTENSION_NAME := mokoexample -EXTENSION_TYPE := module -# Options: module, plugin, component, package, template -EXTENSION_VERSION := 1.0.0 - -# Module Configuration (for modules only) -MODULE_TYPE := site -# Options: site, admin - -# Plugin Configuration (for plugins only) -PLUGIN_GROUP := system -# Options: system, content, user, authentication, etc. - -# Directories -SRC_DIR := . -BUILD_DIR := build -DIST_DIR := dist -DOCS_DIR := docs - -# Joomla Installation (for local testing - customize paths) -JOOMLA_ROOT := /var/www/html/joomla -JOOMLA_VERSION := 4 - -# Tools -PHP := php -COMPOSER := composer -NPM := npm -PHPCS := vendor/bin/phpcs -PHPCBF := vendor/bin/phpcbf -PHPUNIT := vendor/bin/phpunit -ZIP := zip - -# Coding Standards -PHPCS_STANDARD := Joomla - -# Colors for output -COLOR_RESET := \033[0m -COLOR_GREEN := \033[32m -COLOR_YELLOW := \033[33m -COLOR_BLUE := \033[34m -COLOR_RED := \033[31m - -# ============================================================================== -# TARGETS -# ============================================================================== - -.PHONY: help -help: ## Show this help message - @echo "$(COLOR_BLUE)╔════════════════════════════════════════════════════════════╗$(COLOR_RESET)" - @echo "$(COLOR_BLUE)║ Joomla Extension Makefile ║$(COLOR_RESET)" - @echo "$(COLOR_BLUE)╚════════════════════════════════════════════════════════════╝$(COLOR_RESET)" - @echo "" - @echo "Extension: $(EXTENSION_NAME) ($(EXTENSION_TYPE)) v$(EXTENSION_VERSION)" - @echo "" - @echo "$(COLOR_GREEN)Available targets:$(COLOR_RESET)" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_BLUE)%-20s$(COLOR_RESET) %s\n", $$1, $$2}' - @echo "" - @echo "$(COLOR_YELLOW)Quick Start:$(COLOR_RESET)" - @echo " 1. make install-deps # Install dependencies" - @echo " 2. make build # Build extension package" - @echo " 3. make test # Run tests" - @echo "" - -.PHONY: install-deps -install-deps: ## Install all dependencies (Composer + npm) - @echo "$(COLOR_BLUE)Installing dependencies...$(COLOR_RESET)" - @if [ -f "composer.json" ]; then \ - $(COMPOSER) install; \ - echo "$(COLOR_GREEN)✓ Composer dependencies installed$(COLOR_RESET)"; \ - fi - @if [ -f "package.json" ]; then \ - $(NPM) install; \ - echo "$(COLOR_GREEN)✓ npm dependencies installed$(COLOR_RESET)"; \ - fi - -.PHONY: update-deps -update-deps: ## Update all dependencies - @echo "$(COLOR_BLUE)Updating dependencies...$(COLOR_RESET)" - @if [ -f "composer.json" ]; then \ - $(COMPOSER) update; \ - echo "$(COLOR_GREEN)✓ Composer dependencies updated$(COLOR_RESET)"; \ - fi - @if [ -f "package.json" ]; then \ - $(NPM) update; \ - echo "$(COLOR_GREEN)✓ npm dependencies updated$(COLOR_RESET)"; \ - fi - -.PHONY: lint -lint: ## Run PHP linter (syntax check) - @echo "$(COLOR_BLUE)Running PHP linter...$(COLOR_RESET)" - @find . -name "*.php" ! -path "./vendor/*" ! -path "./node_modules/*" ! -path "./$(BUILD_DIR)/*" \ - -exec $(PHP) -l {} \; | grep -v "No syntax errors" || true - @echo "$(COLOR_GREEN)✓ PHP linting complete$(COLOR_RESET)" - -.PHONY: phpcs -phpcs: ## Run PHP CodeSniffer (Joomla standards) - @echo "$(COLOR_BLUE)Running PHP CodeSniffer...$(COLOR_RESET)" - @if [ -f "$(PHPCS)" ]; then \ - $(PHPCS) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,node_modules,$(BUILD_DIR) .; \ - else \ - echo "$(COLOR_YELLOW)⚠ PHP CodeSniffer not installed. Run: make install-deps$(COLOR_RESET)"; \ - fi - -.PHONY: phpcbf -phpcbf: ## Fix coding standards automatically - @echo "$(COLOR_BLUE)Running PHP Code Beautifier...$(COLOR_RESET)" - @if [ -f "$(PHPCBF)" ]; then \ - $(PHPCBF) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,node_modules,$(BUILD_DIR) .; \ - echo "$(COLOR_GREEN)✓ Code formatting applied$(COLOR_RESET)"; \ - else \ - echo "$(COLOR_YELLOW)⚠ PHP Code Beautifier not installed. Run: make install-deps$(COLOR_RESET)"; \ - fi - -.PHONY: validate -validate: lint phpcs ## Run all validation checks - @echo "$(COLOR_GREEN)✓ All validation checks passed$(COLOR_RESET)" - -.PHONY: test -test: ## Run PHPUnit tests - @echo "$(COLOR_BLUE)Running tests...$(COLOR_RESET)" - @if [ -f "$(PHPUNIT)" ] && [ -f "phpunit.xml" ]; then \ - $(PHPUNIT); \ - else \ - echo "$(COLOR_YELLOW)⚠ PHPUnit not configured$(COLOR_RESET)"; \ - fi - -.PHONY: test-coverage -test-coverage: ## Run tests with coverage report - @echo "$(COLOR_BLUE)Running tests with coverage...$(COLOR_RESET)" - @if [ -f "$(PHPUNIT)" ] && [ -f "phpunit.xml" ]; then \ - $(PHPUNIT) --coverage-html $(BUILD_DIR)/coverage; \ - echo "$(COLOR_GREEN)✓ Coverage report: $(BUILD_DIR)/coverage/index.html$(COLOR_RESET)"; \ - else \ - echo "$(COLOR_YELLOW)⚠ PHPUnit not configured$(COLOR_RESET)"; \ - fi - -.PHONY: clean -clean: ## Clean build artifacts - @echo "$(COLOR_BLUE)Cleaning build artifacts...$(COLOR_RESET)" - @rm -rf $(BUILD_DIR) $(DIST_DIR) - @echo "$(COLOR_GREEN)✓ Build artifacts cleaned$(COLOR_RESET)" - -.PHONY: build -build: clean validate ## Build extension package - @echo "$(COLOR_BLUE)Building Joomla extension package...$(COLOR_RESET)" - @mkdir -p $(DIST_DIR) $(BUILD_DIR) - - # Determine package prefix based on extension type - @case "$(EXTENSION_TYPE)" in \ - module) \ - PACKAGE_PREFIX="mod_$(EXTENSION_NAME)"; \ - BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \ - ;; \ - plugin) \ - PACKAGE_PREFIX="plg_$(PLUGIN_GROUP)_$(EXTENSION_NAME)"; \ - BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \ - ;; \ - component) \ - PACKAGE_PREFIX="com_$(EXTENSION_NAME)"; \ - BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \ - ;; \ - package) \ - PACKAGE_PREFIX="pkg_$(EXTENSION_NAME)"; \ - BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \ - ;; \ - template) \ - PACKAGE_PREFIX="tpl_$(EXTENSION_NAME)"; \ - BUILD_TARGET="$(BUILD_DIR)/$$PACKAGE_PREFIX"; \ - ;; \ - *) \ - echo "$(COLOR_RED)✗ Unknown extension type: $(EXTENSION_TYPE)$(COLOR_RESET)"; \ - exit 1; \ - ;; \ - esac; \ - \ - mkdir -p "$$BUILD_TARGET"; \ - \ - echo "Building $$PACKAGE_PREFIX..."; \ - \ - rsync -av --progress \ - --exclude='$(BUILD_DIR)' \ - --exclude='$(DIST_DIR)' \ - --exclude='.git*' \ - --exclude='vendor/' \ - --exclude='node_modules/' \ - --exclude='tests/' \ - --exclude='Makefile' \ - --exclude='composer.json' \ - --exclude='composer.lock' \ - --exclude='package.json' \ - --exclude='package-lock.json' \ - --exclude='phpunit.xml' \ - --exclude='*.md' \ - --exclude='.editorconfig' \ - . "$$BUILD_TARGET/"; \ - \ - cd $(BUILD_DIR) && $(ZIP) -r "../$(DIST_DIR)/$${PACKAGE_PREFIX}-$(EXTENSION_VERSION).zip" "$${PACKAGE_PREFIX}"; \ - \ - echo "$(COLOR_GREEN)✓ Package created: $(DIST_DIR)/$${PACKAGE_PREFIX}-$(EXTENSION_VERSION).zip$(COLOR_RESET)" - -.PHONY: package -package: build ## Alias for build - @echo "$(COLOR_GREEN)✓ Package ready for distribution$(COLOR_RESET)" - -.PHONY: install-local -install-local: build ## Install to local Joomla (upload via admin) - @echo "$(COLOR_BLUE)Package ready for installation$(COLOR_RESET)" - @case "$(EXTENSION_TYPE)" in \ - module) PACKAGE="mod_$(EXTENSION_NAME)";; \ - plugin) PACKAGE="plg_$(PLUGIN_GROUP)_$(EXTENSION_NAME)";; \ - component) PACKAGE="com_$(EXTENSION_NAME)";; \ - package) PACKAGE="pkg_$(EXTENSION_NAME)";; \ - template) PACKAGE="tpl_$(EXTENSION_NAME)";; \ - esac; \ - echo "$(COLOR_YELLOW)Upload $(DIST_DIR)/$${PACKAGE}-$(EXTENSION_VERSION).zip via Joomla Administrator$(COLOR_RESET)"; \ - echo "Admin URL: $(JOOMLA_ROOT) → Extensions → Install" - -.PHONY: dev-install -dev-install: ## Create symlink for development (Joomla 4+) - @echo "$(COLOR_BLUE)Creating development symlink...$(COLOR_RESET)" - @if [ ! -d "$(JOOMLA_ROOT)" ]; then \ - echo "$(COLOR_RED)✗ Joomla root not found at $(JOOMLA_ROOT)$(COLOR_RESET)"; \ - echo "Update JOOMLA_ROOT in Makefile"; \ - exit 1; \ - fi - - @case "$(EXTENSION_TYPE)" in \ - module) \ - if [ "$(MODULE_TYPE)" = "admin" ]; then \ - TARGET="$(JOOMLA_ROOT)/administrator/modules/mod_$(EXTENSION_NAME)"; \ - else \ - TARGET="$(JOOMLA_ROOT)/modules/mod_$(EXTENSION_NAME)"; \ - fi; \ - ;; \ - plugin) \ - TARGET="$(JOOMLA_ROOT)/plugins/$(PLUGIN_GROUP)/$(EXTENSION_NAME)"; \ - ;; \ - component) \ - echo "$(COLOR_YELLOW)⚠ Components require complex symlink setup$(COLOR_RESET)"; \ - echo "Manual setup recommended for component development"; \ - exit 1; \ - ;; \ - *) \ - echo "$(COLOR_RED)✗ dev-install not supported for $(EXTENSION_TYPE)$(COLOR_RESET)"; \ - exit 1; \ - ;; \ - esac; \ - \ - rm -rf "$$TARGET"; \ - ln -s "$(PWD)" "$$TARGET"; \ - echo "$(COLOR_GREEN)✓ Development symlink created at $$TARGET$(COLOR_RESET)" - -.PHONY: watch -watch: ## Watch for changes and rebuild - @echo "$(COLOR_BLUE)Watching for changes...$(COLOR_RESET)" - @echo "$(COLOR_YELLOW)Press Ctrl+C to stop$(COLOR_RESET)" - @while true; do \ - inotifywait -r -e modify,create,delete --exclude '($(BUILD_DIR)|$(DIST_DIR)|vendor|node_modules)' . 2>/dev/null || \ - (echo "$(COLOR_YELLOW)⚠ inotifywait not installed. Install: apt-get install inotify-tools$(COLOR_RESET)" && sleep 5); \ - make build; \ - done - -.PHONY: version -version: ## Display version information - @echo "$(COLOR_BLUE)Extension Information:$(COLOR_RESET)" - @echo " Name: $(EXTENSION_NAME)" - @echo " Type: $(EXTENSION_TYPE)" - @echo " Version: $(EXTENSION_VERSION)" - @if [ "$(EXTENSION_TYPE)" = "module" ]; then \ - echo " Module: $(MODULE_TYPE)"; \ - fi - @if [ "$(EXTENSION_TYPE)" = "plugin" ]; then \ - echo " Group: $(PLUGIN_GROUP)"; \ - fi - -.PHONY: docs -docs: ## Generate documentation - @echo "$(COLOR_BLUE)Generating documentation...$(COLOR_RESET)" - @mkdir -p $(DOCS_DIR) - @echo "$(COLOR_YELLOW)⚠ Documentation generation not configured$(COLOR_RESET)" - @echo "Consider adding phpDocumentor or similar" - -.PHONY: release -release: validate test build ## Create a release (validate + test + build) - @echo "$(COLOR_GREEN)✓ Release package ready$(COLOR_RESET)" - @echo "" - @echo "$(COLOR_BLUE)Release Checklist:$(COLOR_RESET)" - @echo " [ ] Update CHANGELOG.md" - @echo " [ ] Update version in XML manifest" - @echo " [ ] Test installation in clean Joomla" - @echo " [ ] Tag release in git: git tag v$(EXTENSION_VERSION)" - @echo " [ ] Push tags: git push --tags" - @echo " [ ] Create GitHub release" - @echo "" - @case "$(EXTENSION_TYPE)" in \ - module) PACKAGE="mod_$(EXTENSION_NAME)";; \ - plugin) PACKAGE="plg_$(PLUGIN_GROUP)_$(EXTENSION_NAME)";; \ - component) PACKAGE="com_$(EXTENSION_NAME)";; \ - package) PACKAGE="pkg_$(EXTENSION_NAME)";; \ - template) PACKAGE="tpl_$(EXTENSION_NAME)";; \ - esac; \ - echo "$(COLOR_GREEN)Package: $(DIST_DIR)/$${PACKAGE}-$(EXTENSION_VERSION).zip$(COLOR_RESET)" - -.PHONY: security-check -security-check: ## Run security checks on dependencies - @echo "$(COLOR_BLUE)Running security checks...$(COLOR_RESET)" - @if [ -f "composer.json" ]; then \ - $(COMPOSER) audit || echo "$(COLOR_YELLOW)⚠ Vulnerabilities found$(COLOR_RESET)"; \ - fi - @if [ -f "package.json" ]; then \ - $(NPM) audit || echo "$(COLOR_YELLOW)⚠ Vulnerabilities found$(COLOR_RESET)"; \ - fi - -.PHONY: all -all: install-deps validate test build ## Run complete build pipeline - @echo "$(COLOR_GREEN)✓ Complete build pipeline finished$(COLOR_RESET)" - -# Default target -.DEFAULT_GOAL := help diff --git a/templates/makefiles/README.md b/templates/makefiles/README.md deleted file mode 100644 index 819d8f0..0000000 --- a/templates/makefiles/README.md +++ /dev/null @@ -1,103 +0,0 @@ - - -# Makefile Templates - -This directory contains Makefile templates for different repository types. - -## Template Files - -### Makefile.generic.template -Generic Makefile for standard repositories. Includes common targets for: -- Building and compiling -- Testing and linting -- Cleaning and maintenance -- Development workflow automation - -**Usage:** -```bash -cp templates/makefiles/Makefile.generic.template ./Makefile -``` - -### Makefile.joomla.template -Joomla-specific Makefile for extensions (components, modules, plugins, etc.). Includes targets for: -- Joomla manifest validation -- Extension packaging -- Installation and deployment -- Testing with Joomla framework -- Build artifact creation - -**Usage:** -```bash -cp templates/makefiles/Makefile.joomla.template ./Makefile -``` - -### Makefile.dolibarr.template -Dolibarr-specific Makefile for modules. Includes targets for: -- Module descriptor validation -- SQL migration management -- Module packaging -- Installation and deployment -- Testing with Dolibarr framework -- Build artifact creation - -**Usage:** -```bash -cp templates/makefiles/Makefile.dolibarr.template ./Makefile -``` - -## Template Naming Convention - -All Makefile templates use the `.template` extension to clearly distinguish them from active Makefile files. When copying to your repository: - -1. **Remove the .template extension**: `Makefile.generic.template` → `Makefile` -2. **Customize for your project**: Update variables and targets as needed -3. **Test the targets**: Run `make help` to see available targets - -## Customization - -After copying a template: - -1. Update project-specific variables at the top of the Makefile -2. Modify paths to match your repository structure -3. Add or remove targets based on your needs -4. Update dependencies between targets if necessary - -## Schema Integration - -These Makefile templates are referenced in the repository structure schemas: - -- **default-repository.xml**: Uses `Makefile.generic.template` (suggested) -- **waas-component.xml**: Uses `Makefile.joomla.template` (required) -- **crm-module.xml**: Uses `Makefile.dolibarr.template` (required) - -The schemas define source/destination mappings that automatically convert `.template` extensions to the final `Makefile` name. - -## Common Targets - -Most templates include these standard targets: - -- `make help` - Display available targets -- `make build` - Build the project -- `make test` - Run tests -- `make lint` - Run linters -- `make clean` - Clean build artifacts -- `make install` - Install dependencies -- `make package` - Create distribution package - -Platform-specific templates may include additional targets relevant to their ecosystem. - -## Support - -For questions or issues with Makefile templates: -- See main mokocli documentation -- Check the platform-specific guides (Joomla, Dolibarr) -- Review the schema documentation for source/destination details diff --git a/templates/mokogitea/CLAUDE.dolibarr.md.template b/templates/mokogitea/CLAUDE.dolibarr.md.template deleted file mode 100644 index 708901d..0000000 --- a/templates/mokogitea/CLAUDE.dolibarr.md.template +++ /dev/null @@ -1,296 +0,0 @@ - - -> [!IMPORTANT] -> **🔧 AI Self-Update Required on First Use** -> -> This file may contain unfilled `{{TOKEN}}` placeholders. The mokocli bulk-sync system -> replaces them automatically at sync time; if you can still see them, sync has not yet run for -> this repository — or this is a brand-new repo. -> -> **On your very first interaction in this repository, before answering any other question:** -> -> 1. Check whether any `{{TOKEN}}` placeholders remain in this file. -> 2. If they do, locate the values as described in the table below and replace every occurrence -> in **both** `.gitea/copilot-instructions.md` and `.gitea/CLAUDE.md`. -> 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]` -> through the closing `> ---` line) in both files. -> 4. Tell the developer: *"I've self-updated the AI context files with this repository's -> values — all set."* -> -> | Placeholder | Where to find the value | -> |---|---| -> | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) | -> | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://git.mokoconsulting.tech/mokoconsulting-tech/` | -> | `{{REPO_DESCRIPTION}}` | First paragraph of `README.md` body, or the GitHub repo description | -> | `{{MODULE_NAME}}` | The module name as used in Dolibarr (lowercase, e.g. `mymodule`) — from the `langs/en_US/*.lang` filename or `$this->rights_class` in the module descriptor | -> | `{{MODULE_CLASS}}` | PascalCase module class name (e.g. `MyModule`) — from the `src/core/modules/mod*.class.php` filename | -> | `{{MODULE_ID}}` | The `$this->numero` value in `src/core/modules/mod*.class.php`; check [module-registry.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/development/crm/module-registry.md) if creating a new module | -> -> --- - -# What This Repo Is - -**{{REPO_NAME}}** is a Moko Consulting **MokoCRM** (Dolibarr) module repository. - -{{REPO_DESCRIPTION}} - -Module name: **{{MODULE_NAME}}** -Module class: **{{MODULE_CLASS}}** -Module ID: **{{MODULE_ID}}** *(unique, immutable — registered in [module-registry.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/development/crm/module-registry.md))* -Repository URL: {{REPO_URL}} - -This repository is governed by [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) — the single source of truth for coding standards, file-header policies, GitHub Actions workflows, and Terraform configuration templates across all Moko Consulting repositories. - ---- - -# Repo Structure - -``` -{{REPO_NAME}}/ -├── src/ # Module source (deployed to Dolibarr) -│ ├── index.php # REQUIRED — accessforbidden() guard -│ ├── README.md # End-user documentation -│ ├── core/ -│ │ ├── index.php # REQUIRED — accessforbidden() guard -│ │ └── modules/ -│ │ ├── index.php # REQUIRED — accessforbidden() guard -│ │ └── mod{{MODULE_CLASS}}.class.php # Main module descriptor -│ ├── langs/ -│ │ ├── index.php # REQUIRED — accessforbidden() guard -│ │ └── en_US/ -│ │ ├── index.php # REQUIRED — accessforbidden() guard -│ │ └── {{MODULE_NAME}}.lang -│ ├── sql/ # Database schema -│ │ └── index.php # REQUIRED — accessforbidden() guard -│ ├── class/ # PHP class files -│ │ └── index.php # REQUIRED — accessforbidden() guard -│ └── lib/ # Library files -│ └── index.php # REQUIRED — accessforbidden() guard -├── docs/ # Technical documentation -├── scripts/ # Build and maintenance scripts -├── tests/ # Test suite -│ ├── unit/ -│ └── integration/ -├── .gitea/ -│ ├── workflows/ # CI/CD workflows (synced from mokocli) -│ ├── copilot-instructions.md -│ └── CLAUDE.md # This file -├── README.md # Version source of truth -├── CHANGELOG.md -├── CONTRIBUTING.md -├── LICENSE # GPL-3.0-or-later -└── Makefile # Build automation -``` - -**Every directory inside `src/` MUST have an `index.php`** that either contains live code or calls `accessforbidden()`. Standard guard template (adjust the relative fallback path to match directory depth from `htdocs/`): - -```php - - * SPDX-License-Identifier: GPL-3.0-or-later - * FILE INFORMATION / BRIEF: Directory access guard - */ -// Adjust relative path below per depth: mymodule/subdir/ uses ../../../main.inc.php -$res = 0; -if (!$res && !empty($_SERVER["DOCUMENT_ROOT"])) { - $res = @include $_SERVER["DOCUMENT_ROOT"]."/main.inc.php"; -} -if (!$res && file_exists("../../../main.inc.php")) { - $res = @include "../../../main.inc.php"; -} -if (!$res) { die("Include of main fails"); } -accessforbidden(); -``` - ---- - -# Primary Language - -**PHP** (≥ 8.1) is the primary language for this Dolibarr module. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`. - ---- - -# Version Management - -**`README.md` is the single source of truth for the repository version.** - -- **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it to all `FILE INFORMATION` headers automatically on merge. -- Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`). -- Never hardcode a version number in body text — use the badge or FILE INFORMATION header only. - -### Dolibarr Version Alignment - -Two artefacts must always carry the same version: - -| Artefact | Location | -|----------|----------| -| `README.md` | `FILE INFORMATION VERSION` field + badge | -| Module descriptor | `$this->version` in `src/core/modules/mod{{MODULE_CLASS}}.class.php` | - ---- - -# Module Descriptor Class - -The file `src/core/modules/mod{{MODULE_CLASS}}.class.php` is the Dolibarr module descriptor. The key properties: - -```php -public $numero = {{MODULE_ID}}; // IMMUTABLE — never change; registered globally -public $version = 'XX.YY.ZZ'; // Must match README.md version exactly -``` - -**`$numero` is permanent.** It was registered in [module-registry.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/development/crm/module-registry.md) when this module was created. Changing it would break all Dolibarr installations that have this module activated. - -Before creating a new module, always check the registry for the next available ID. - ---- - -# File Header Requirements - -Every new file **must** have a copyright header as its first content. JSON files, binary files, generated files, and third-party files are exempt. - -**PHP:** -```php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: {{REPO_NAME}}.Module - * INGROUP: {{REPO_NAME}} - * REPO: {{REPO_URL}} - * PATH: /src/class/MyClass.php - * VERSION: XX.YY.ZZ - * BRIEF: One-line description of file purpose - */ -``` - -**Markdown / YAML / Shell:** Use the appropriate comment syntax with the same fields. - ---- - -# Coding Standards - -## Naming Conventions - -| Context | Convention | Example | -|---------|-----------|---------| -| PHP class | `PascalCase` | `MyService` | -| PHP method / function | `camelCase` | `getUserData()` | -| PHP variable | `$snake_case` | `$module_name` | -| PHP constant | `UPPER_SNAKE_CASE` | `MAX_RETRIES` | -| PHP class file | `PascalCase.php` | `ApiClient.php` | -| PHP script file | `snake_case.php` | `check_health.php` | -| YAML workflow | `kebab-case.yml` | `ci-dolibarr.yml` | -| Markdown doc | `kebab-case.md` | `installation-guide.md` | - -## Commit Messages - -Format: `(): ` — imperative, lower-case subject, no trailing period. - -Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build` - -## Branch Naming - -Format: `/[/description]` - -Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/` - ---- - -# GitHub Actions — Token Usage - -Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token). - -```yaml -# ✅ Correct -- uses: actions/checkout@v4 - with: - token: ${{ secrets.GH_TOKEN }} - -env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} -``` - -```yaml -# ❌ Wrong — never use these -token: ${{ github.token }} -token: ${{ secrets.GITHUB_TOKEN }} -``` - -PHP scripts read the token with: `getenv('GH_TOKEN') ?: getenv('GITHUB_TOKEN')` — `GH_TOKEN` is always preferred; `GITHUB_TOKEN` is a local-dev fallback only. - ---- - -# Keeping Documentation Current - -| Change type | Documentation to update | -|-------------|------------------------| -| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | -| New or changed module version | Update `$this->version` in module descriptor; bump `README.md` | -| New library class or major feature | `CHANGELOG.md` entry under `Added` | -| Bug fix | `CHANGELOG.md` entry under `Fixed` | -| Breaking change | `CHANGELOG.md` entry under `Changed` | -| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | -| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | - ---- - -# What NOT to Do - -- **Never commit directly to `main`** — all changes go through a PR. -- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. -- **Never change `$this->numero`** — the module ID is permanent and globally registered. -- **Never skip the FILE INFORMATION block** on a new source file. -- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. -- **Never mix tabs and spaces** within a file — follow `.editorconfig`. -- **Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows** — always use `secrets.GH_TOKEN`. -- **Never register a new module ID** without first consulting module-registry.md. -- **Never let `$this->version` and `README.md` version diverge.** -- **Never create a directory inside `src/` without adding an `index.php`** — every directory must have an `index.php` with live code or `accessforbidden()`. - ---- - -# PR Checklist - -Before opening a PR, verify: - -- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) -- [ ] `$this->version` in module descriptor updated to match -- [ ] FILE INFORMATION headers updated in modified files -- [ ] CHANGELOG.md updated -- [ ] Every new directory inside `src/` has an `index.php` (live code or `accessforbidden()` guard) - ---- - -# Key Policy Documents (mokocli) - -| Document | Purpose | -|----------|---------| -| [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | -| [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | -| [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | -| [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | -| [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | -| [module-registry.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/development/crm/module-registry.md) | Dolibarr module ID registry — check before reserving a new ID | -| [crm/development-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/crm/development-standards.md) | MokoCRM Dolibarr module development standards | -| [dolibarr-development-guide.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/guide/crm/dolibarr-development-guide.md) | MokoCRM full development guide | diff --git a/templates/mokogitea/CLAUDE.joomla.md.template b/templates/mokogitea/CLAUDE.joomla.md.template deleted file mode 100644 index ac67dac..0000000 --- a/templates/mokogitea/CLAUDE.joomla.md.template +++ /dev/null @@ -1,301 +0,0 @@ - - -> [!IMPORTANT] -> **🔧 AI Self-Update Required on First Use** -> -> This file may contain unfilled `{{TOKEN}}` placeholders. The mokocli bulk-sync system -> replaces them automatically at sync time; if you can still see them, sync has not yet run for -> this repository — or this is a brand-new repo. -> -> **On your very first interaction in this repository, before answering any other question:** -> -> 1. Check whether any `{{TOKEN}}` placeholders remain in this file. -> 2. If they do, locate the values as described in the table below and replace every occurrence -> in **both** `.gitea/copilot-instructions.md` and `.gitea/CLAUDE.md`. -> 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]` -> through the closing `> ---` line) in both files. -> 4. Tell the developer: *"I've self-updated the AI context files with this repository's -> values — all set."* -> -> | Placeholder | Where to find the value | -> |---|---| -> | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) | -> | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://git.mokoconsulting.tech/mokoconsulting-tech/` | -> | `{{REPO_DESCRIPTION}}` | First paragraph of `README.md` body, or the GitHub repo description | -> | `{{EXTENSION_NAME}}` | The `` element in `manifest.xml` at the repository root | -> | `{{EXTENSION_TYPE}}` | The `type` attribute of the `` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) | -> | `{{EXTENSION_ELEMENT}}` | The `` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) | -> -> --- - -# What This Repo Is - -**{{REPO_NAME}}** is a Moko Consulting **MokoWaaS** (Joomla) extension repository. - -{{REPO_DESCRIPTION}} - -Extension name: **{{EXTENSION_NAME}}** -Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`) -Repository URL: {{REPO_URL}} - -This repository is governed by [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) — the single source of truth for coding standards, file-header policies, GitHub Actions workflows, and Terraform configuration templates across all Moko Consulting repositories. - ---- - -# Repo Structure - -``` -{{REPO_NAME}}/ -├── manifest.xml # Joomla installer manifest (root — required) -├── updates.xml # Update server manifest (root — required) -├── site/ # Frontend (site) code -│ ├── controller.php -│ ├── controllers/ -│ ├── models/ -│ └── views/ -├── admin/ # Backend (admin) code -│ ├── controller.php -│ ├── controllers/ -│ ├── models/ -│ ├── views/ -│ └── sql/ -├── language/ # Language INI files -├── media/ # CSS, JS, images -├── docs/ # Technical documentation -├── tests/ # Test suite -├── .gitea/ -│ ├── workflows/ # CI/CD workflows (synced from mokocli) -│ ├── copilot-instructions.md -│ └── CLAUDE.md # This file -├── README.md # Version source of truth -├── CHANGELOG.md -├── CONTRIBUTING.md -└── LICENSE # GPL-3.0-or-later -``` - ---- - -# Primary Language - -**PHP** (≥ 7.4) is the primary language for this Joomla extension. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`. - ---- - -# Version Management - -**`README.md` is the single source of truth for the repository version.** - -- **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it to all `FILE INFORMATION` headers automatically on merge. -- Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`). -- Never hardcode a version number in body text — use the badge or FILE INFORMATION header only. - -### Joomla Version Alignment - -Three files must **always have the same version**: - -| File | Where the version lives | -|------|------------------------| -| `README.md` | `FILE INFORMATION` block + badge | -| `manifest.xml` | `` tag | -| `updates.xml` | `` in the most recent `` block | - -The `make release` command / release workflow syncs all three automatically. - ---- - -# updates.xml — Required in Repo Root - -`updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: - -```xml - - - - https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}/raw/branch/main/updates.xml - - - https://raw.githubusercontent.com/mokoconsulting-tech/{{REPO_NAME}}/main/updates.xml - - -``` - -**Rules:** -- Every release prepends a new `` block at the top — older entries are preserved. -- `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`. -- `` must include two `` entries: Gitea release asset (primary) and GitHub release asset (mirror). -- `` — backslash is literal (Joomla regex syntax). - -Example `updates.xml` entry for a new release: -```xml - - - {{EXTENSION_NAME}} - {{REPO_NAME}} - {{EXTENSION_ELEMENT}} - {{EXTENSION_TYPE}} - 01.02.04 - {{REPO_URL}}/releases/tag/01.02.04 - - - {{REPO_URL}}/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip - - - - 8.2 - Moko Consulting - https://mokoconsulting.tech - - -``` - ---- - -# File Header Requirements - -Every new file **must** have a copyright header as its first content. JSON files, binary files, generated files, and third-party files are exempt. - -**PHP:** -```php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: {{REPO_NAME}}.{{EXTENSION_TYPE}} - * INGROUP: {{REPO_NAME}} - * REPO: {{REPO_URL}} - * PATH: /site/controllers/item.php - * VERSION: XX.YY.ZZ - * BRIEF: One-line description of file purpose - */ - -defined('_JEXEC') or die; -``` - -**Markdown / YAML / Shell / XML:** Use the appropriate comment syntax with the same fields. - ---- - -# Coding Standards - -## Naming Conventions - -| Context | Convention | Example | -|---------|-----------|---------| -| PHP class | `PascalCase` | `ItemModel` | -| PHP method / function | `camelCase` | `getItems()` | -| PHP variable | `$snake_case` | `$item_id` | -| PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` | -| PHP class file | `PascalCase.php` | `ItemModel.php` | -| YAML workflow | `kebab-case.yml` | `ci-joomla.yml` | -| Markdown doc | `kebab-case.md` | `installation-guide.md` | - -## Commit Messages - -Format: `(): ` — imperative, lower-case subject, no trailing period. - -Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build` - -## Branch Naming - -Format: `/[/description]` - -Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/` - ---- - -# GitHub Actions — Token Usage - -Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token). - -```yaml -# ✅ Correct -- uses: actions/checkout@v4 - with: - token: ${{ secrets.GH_TOKEN }} - -env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} -``` - -```yaml -# ❌ Wrong — never use these -token: ${{ github.token }} -token: ${{ secrets.GITHUB_TOKEN }} -``` - ---- - -# Keeping Documentation Current - -| Change type | Documentation to update | -|-------------|------------------------| -| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | -| New or changed `manifest.xml` | Sync version to `updates.xml` and `README.md` | -| New release | Prepend `` to `updates.xml`; update `CHANGELOG.md`; bump `README.md` | -| New or changed workflow | `docs/workflows/.md` | -| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | -| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | - ---- - -# What NOT to Do - -- **Never commit directly to `main`** — all changes go through a PR. -- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate. -- **Never let `manifest.xml`, `updates.xml`, and `README.md` versions diverge.** -- **Never skip the FILE INFORMATION block** on a new source file. -- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw. -- **Never mix tabs and spaces** within a file — follow `.editorconfig`. -- **Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows** — always use `secrets.GH_TOKEN`. -- **Never remove `defined('_JEXEC') or die;`** from web-accessible PHP files. - ---- - -# PR Checklist - -Before opening a PR, verify: - -- [ ] Patch version bumped in `README.md` (e.g. `01.02.03` → `01.02.04`) -- [ ] If this is a release: `manifest.xml` version updated; `updates.xml` updated with new entry -- [ ] FILE INFORMATION headers updated in modified files -- [ ] CHANGELOG.md updated -- [ ] Tests pass - ---- - -# Key Policy Documents (mokocli) - -| Document | Purpose | -|----------|---------| -| [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | -| [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | -| [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | -| [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | -| [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | -| [joomla-development-guide.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | - -### Update Server Priority - -In the manifest XML `` block, Gitea MUST be priority 1 and GitHub priority 2. -Never set GitHub as the primary update server — Gitea is the source of truth. diff --git a/templates/mokogitea/CLAUDE.md.template b/templates/mokogitea/CLAUDE.md.template deleted file mode 100644 index 94187df..0000000 --- a/templates/mokogitea/CLAUDE.md.template +++ /dev/null @@ -1,34 +0,0 @@ -# {{REPO_NAME}} - -{{REPO_DESCRIPTION}} - -## Quick Reference - -| Field | Value | -|---|---| -| **Platform** | {{PLATFORM_TYPE}} | -| **Language** | {{PRIMARY_LANGUAGE}} | -| **Branch** | develop on `dev`, merge to `main` (protected) | -| **Wiki** | [{{REPO_NAME}} Wiki](https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/wiki) | - -## Commands - -```bash -make build # Build the project -make lint # Run linters -make validate # Validate structure -make release # Full release pipeline -make clean # Clean build artifacts -``` - -## Architecture - - - -## 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/mokocli/wiki/Home) diff --git a/templates/mokogitea/CODEOWNERS b/templates/mokogitea/CODEOWNERS deleted file mode 100644 index f818f4a..0000000 --- a/templates/mokogitea/CODEOWNERS +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# CODEOWNERS — require approval from jmiller for protected paths -# Synced from MokoCli. Do not edit manually. -# -# Changes to these paths require review from the listed owners before merge. -# Combined with branch protection (require PR reviews), this prevents -# unauthorized modifications to workflows, configs, and governance files. - -# ── Synced workflows (managed by MokoCli — do not edit manually) ──── -/.github/workflows/deploy-dev.yml @jmiller -/.github/workflows/deploy-demo.yml @jmiller -/.github/workflows/deploy-manual.yml @jmiller -/.github/workflows/auto-release.yml @jmiller -/.github/workflows/auto-dev-issue.yml @jmiller -/.github/workflows/auto-assign.yml @jmiller -/.github/workflows/sync-version-on-merge.yml @jmiller -/.github/workflows/enterprise-firewall-setup.yml @jmiller -/.github/workflows/repository-cleanup.yml @jmiller -/.github/workflows/standards-compliance.yml @jmiller -/.github/workflows/codeql-analysis.yml @jmiller -/.github/workflows/repo_health.yml @jmiller -/.github/workflows/ci-joomla.yml @jmiller -/.github/workflows/update-server.yml @jmiller -/.github/workflows/deploy-manual.yml @jmiller -/.github/workflows/ci-dolibarr.yml @jmiller -/.github/workflows/publish-to-mokodolimods.yml @jmiller -/.github/workflows/changelog-validation.yml @jmiller -/.github/workflows/branch-freeze.yml @jmiller -# Custom workflows in .github/workflows/ not listed above are repo-owned. - -# ── GitHub configuration ───────────────────────────────────────────────── -/.github/ISSUE_TEMPLATE/ @jmiller -/.github/CODEOWNERS @jmiller -/.github/copilot.yml @jmiller -/.github/copilot-instructions.md @jmiller -/.github/CLAUDE.md @jmiller -/.github/.mokostandards @jmiller - -# ── Build and config files ─────────────────────────────────────────────── -/composer.json @jmiller -/phpstan.neon @jmiller -/Makefile @jmiller -/.ftpignore @jmiller -/.gitignore @jmiller -/.gitattributes @jmiller -/.editorconfig @jmiller - -# ── Governance documents ───────────────────────────────────────────────── -/LICENSE @jmiller -/CONTRIBUTING.md @jmiller -/SECURITY.md @jmiller -/GOVERNANCE.md @jmiller -/CODE_OF_CONDUCT.md @jmiller diff --git a/templates/mokogitea/CODEOWNERS.template b/templates/mokogitea/CODEOWNERS.template deleted file mode 100644 index 50d270a..0000000 --- a/templates/mokogitea/CODEOWNERS.template +++ /dev/null @@ -1,51 +0,0 @@ -# CODEOWNERS Template -# -# This file defines code ownership for automatic review assignment. -# Copy to .gitea/CODEOWNERS (remove .template suffix) and customize. -# -# Syntax: -# pattern owner(s) -# -# Examples: -# * @org/default-team -# /docs/ @org/docs-team -# *.js @org/frontend-team -# /src/security/ @org/security-team -# -# Last matching pattern takes precedence. -# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners - -# Default owners for everything in the repo -# These owners will be requested for review when someone opens a PR -# Unless a later match takes precedence -* @mokoconsulting-tech/maintainers - -# Documentation -/docs/ @mokoconsulting-tech/docs-team -/README.md @mokoconsulting-tech/docs-team -/CHANGELOG.md @mokoconsulting-tech/docs-team -/CONTRIBUTING.md @mokoconsulting-tech/docs-team - -# GitHub configuration -/.gitea/ @mokoconsulting-tech/devops-team -/.gitea/workflows/ @mokoconsulting-tech/devops-team - -# Security-related files -/SECURITY.md @mokoconsulting-tech/security-team -/docs/policy/security-*.md @mokoconsulting-tech/security-team -/.gitea/workflows/security-*.yml @mokoconsulting-tech/security-team - -# API and automation -/api/ @mokoconsulting-tech/automation-team - -# Build system -/Makefiles/ @mokoconsulting-tech/build-team - -# Policies (require policy owner approval) -/docs/policy/ @mokoconsulting-tech/policy-owners - -# Templates -/templates/ @mokoconsulting-tech/template-maintainers - -# Schemas -/schemas/ @mokoconsulting-tech/architecture-team diff --git a/templates/mokogitea/ISSUE_TEMPLATE/adr.md b/templates/mokogitea/ISSUE_TEMPLATE/adr.md deleted file mode 100644 index eb40760..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/adr.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -name: Architecture Decision Record (ADR) -about: Propose or document an architectural decision -title: '[ADR] ' -labels: 'architecture, decision' -assignees: '' - ---- - - -## ADR Number -ADR-XXXX - -## Status -- [ ] Proposed -- [ ] Accepted -- [ ] Deprecated -- [ ] Superseded by ADR-XXXX - -## Context -Describe the issue or problem that motivates this decision. - -## Decision -State the architecture decision and provide rationale. - -## Consequences -### Positive -- List positive consequences - -### Negative -- List negative consequences or trade-offs - -### Neutral -- List neutral aspects - -## Alternatives Considered -### Alternative 1 -- Description -- Pros -- Cons -- Why not chosen - -### Alternative 2 -- Description -- Pros -- Cons -- Why not chosen - -## Implementation Plan -1. Step 1 -2. Step 2 -3. Step 3 - -## Stakeholders -- **Decision Makers**: @user1, @user2 -- **Consulted**: @user3, @user4 -- **Informed**: team-name - -## Technical Details -### Architecture Diagram -``` -[Add diagram or link] -``` - -### Dependencies -- Dependency 1 -- Dependency 2 - -### Impact Analysis -- **Performance**: [Impact description] -- **Security**: [Impact description] -- **Scalability**: [Impact description] -- **Maintainability**: [Impact description] - -## Testing Strategy -- [ ] Unit tests -- [ ] Integration tests -- [ ] Performance tests -- [ ] Security tests - -## Documentation -- [ ] Architecture documentation updated -- [ ] API documentation updated -- [ ] Developer guide updated -- [ ] Runbook created - -## Migration Path -Describe how to migrate from current state to new architecture. - -## Rollback Plan -Describe how to rollback if issues occur. - -## Timeline -- **Proposal Date**: -- **Decision Date**: -- **Implementation Start**: -- **Expected Completion**: - -## References -- Related ADRs: -- External resources: -- RFCs: - -## Review Checklist -- [ ] Aligns with enterprise architecture principles -- [ ] Security implications reviewed -- [ ] Performance implications reviewed -- [ ] Cost implications reviewed -- [ ] Compliance requirements met -- [ ] Team consensus achieved diff --git a/templates/mokogitea/ISSUE_TEMPLATE/bug_report.md b/templates/mokogitea/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 38a16a7..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: Bug Report -about: Report a bug or issue with the project -title: '[BUG] ' -labels: 'bug' -assignees: '' - ---- - - -## Bug Description -A clear and concise description of what the bug is. - -## Steps to Reproduce -1. Go to '...' -2. Click on '...' -3. Scroll down to '...' -4. See error - -## Expected Behavior -A clear and concise description of what you expected to happen. - -## Actual Behavior -A clear and concise description of what actually happened. - -## Screenshots -If applicable, add screenshots to help explain your problem. - -## Environment -- **Project**: [e.g., MokoDoliTools, moko-cassiopeia] -- **Version**: [e.g., 1.2.3] -- **Platform**: [e.g., Dolibarr 18.0, Joomla 5.0] -- **PHP Version**: [e.g., 8.1] -- **Database**: [e.g., MySQL 8.0, PostgreSQL 14] -- **Browser** (if applicable): [e.g., Chrome 120, Firefox 121] -- **OS**: [e.g., Ubuntu 22.04, Windows 11] - -## Additional Context -Add any other context about the problem here. - -## Possible Solution -If you have suggestions on how to fix the issue, please describe them here. - -## Checklist -- [ ] I have searched for similar issues before creating this one -- [ ] I have provided all the requested information -- [ ] I have tested this on the latest stable version -- [ ] I have checked the documentation and couldn't find a solution diff --git a/templates/mokogitea/ISSUE_TEMPLATE/config.yml b/templates/mokogitea/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index f392686..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -blank_issues_enabled: true -contact_links: - - name: 💼 Enterprise Support - url: https://mokoconsulting.tech/enterprise - about: Enterprise-level support and consultation services - - name: 💬 Ask a Question - url: https://mokoconsulting.tech/ - about: Get help or ask questions through our website - - name: 📚 mokocli Documentation - url: https://git.mokoconsulting.tech/MokoConsulting/mokocli - 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 - about: Report security vulnerabilities privately (for critical issues) - - name: 💡 Community Discussions - url: https://github.com/orgs/mokoconsulting-tech/discussions - about: Join community discussions and Q&A diff --git a/templates/mokogitea/ISSUE_TEMPLATE/documentation.md b/templates/mokogitea/ISSUE_TEMPLATE/documentation.md deleted file mode 100644 index 43ad195..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/documentation.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: Documentation Issue -about: Report an issue with documentation -title: '[DOCS] ' -labels: 'documentation' -assignees: '' - ---- - - -## Documentation Issue - -**Location**: - - -## Issue Type - -- [ ] Typo or grammar error -- [ ] Outdated information -- [ ] Missing documentation -- [ ] Unclear explanation -- [ ] Broken links -- [ ] Missing examples -- [ ] Other (specify below) - -## Description - - -## Current Content - -``` -Current text here -``` - -## Suggested Improvement - -``` -Suggested text here -``` - -## Additional Context - - -## Standards Alignment -- [ ] Follows mokocli documentation guidelines -- [ ] Uses en_US/en_GB localization -- [ ] Includes proper SPDX headers where applicable - -## Checklist -- [ ] I have searched for similar documentation issues -- [ ] I have provided a clear description -- [ ] I have suggested an improvement (if applicable) diff --git a/templates/mokogitea/ISSUE_TEMPLATE/dolibarr_issue.md b/templates/mokogitea/ISSUE_TEMPLATE/dolibarr_issue.md deleted file mode 100644 index 8366ff2..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/dolibarr_issue.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -name: Dolibarr Module Issue -about: Report an issue with a Dolibarr module -title: '[DOLIBARR] ' -labels: 'dolibarr' -assignees: '' - ---- - - -## Module Details -- **Module Name**: [e.g., MokoDoliTools] -- **Module Version**: [e.g., 1.2.3] -- **Module Type**: [Custom Module / Third-party Module] - -## Dolibarr Environment -- **Dolibarr Version**: [e.g., 18.0.0] -- **PHP Version**: [e.g., 8.1.0] -- **Database**: [MySQL / PostgreSQL / MariaDB] -- **Database Version**: [e.g., 8.0] -- **Server**: [Apache / Nginx / IIS] -- **Hosting**: [Shared / VPS / Dedicated / Cloud] - -## Issue Description -Provide a clear and detailed description of the issue. - -## Steps to Reproduce -1. Log into Dolibarr -2. Navigate to '...' -3. Click on '...' -4. See error - -## Expected Behavior -What you expected to happen. - -## Actual Behavior -What actually happened. - -## Error Messages -``` -# Paste any error messages from Dolibarr logs -# Location: documents/dolibarr.log -``` - -## PHP Error Logs -```php -// Paste any PHP errors from error_log -``` - -## Screenshots -Add screenshots to help explain the issue. - -## Module Configuration -```php -// Paste relevant module configuration (sanitize sensitive data) -``` - -## Installed Modules -List other installed modules that might conflict: -- Module 1 (version) -- Module 2 (version) - -## User Permissions -- **User Type**: [Admin / User] -- **Permissions**: List relevant permissions enabled - -## Database Tables -- [ ] Module tables created correctly -- [ ] Data migration completed -- [ ] Foreign keys intact - -## Additional Context -- **Multi-Company**: [Yes / No] -- **Custom Hooks**: [Yes / No] -- **Third-party Integrations**: [List any] -- **Cron Jobs**: [Enabled / Disabled] - -## Performance Impact -- **Page Load Time**: [seconds] -- **Database Query Count**: [if known] -- **Memory Usage**: [if known] - -## Checklist -- [ ] I have cleared Dolibarr cache -- [ ] I have disabled other modules to test for conflicts -- [ ] I have checked Dolibarr logs -- [ ] I have verified database tables are correct -- [ ] I have checked PHP error logs -- [ ] I have tested with default Dolibarr theme -- [ ] I have searched for similar issues -- [ ] I am using a supported Dolibarr version -- [ ] I have proper user permissions diff --git a/templates/mokogitea/ISSUE_TEMPLATE/dolibarr_module_id_request.md b/templates/mokogitea/ISSUE_TEMPLATE/dolibarr_module_id_request.md deleted file mode 100644 index 39dfe59..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/dolibarr_module_id_request.md +++ /dev/null @@ -1,189 +0,0 @@ ---- -name: Dolibarr Module ID Request -about: Request a unique module ID for a Dolibarr module -title: '[MODULE ID] ' -labels: ['dolibarr', 'module-id-request', 'admin'] -assignees: ['jmiller'] ---- - - -## Module ID Request - -### Module Information - -**Module Name**: -**Module Technical Name** (for descriptor class): mod_______ -**Module Version**: -**Short Description**: - -### Developer Information - -**Developer Name**: -**GitHub Username**: @ -**Email**: @mokoconsulting.tech -**Team/Department**: -**Manager**: @ - -### Distribution Intent - -**Where will this module be used?** -- [ ] Internal use only (Moko Consulting Tech organization) -- [ ] Client-specific custom module -- [ ] Public distribution (DoliStore / GitHub public) -- [ ] Open source community contribution - -**Expected Distribution Timeline**: -- [ ] Development phase (not ready for distribution) -- [ ] Beta testing (internal) -- [ ] Ready for public release -- [ ] Already in use (needs ID for existing module) - -### Module Purpose - -**What problem does this module solve?** - -**Key Features**: -- -- -- - -**Target Dolibarr Version(s)**: -- [ ] Dolibarr 18.x -- [ ] Dolibarr 19.x -- [ ] Dolibarr 20.x -- [ ] Other (specify): _______ - -### Technical Details - -**Module Type**: -- [ ] New standalone module -- [ ] Extension of existing module -- [ ] Fork of existing module (specify original): _______ -- [ ] Migration from another platform - -**Dependencies**: -List any Dolibarr modules or external libraries this module depends on: -- -- - -**Database Changes**: -- [ ] Creates new tables -- [ ] Modifies existing tables -- [ ] No database changes - -**Hooks Used**: -List Dolibarr hooks this module will use: -- -- - -### Repository Information - -**Repository Location**: -- [ ] Repository already exists: https://git.mokoconsulting.tech/mokoconsulting-tech/_______ -- [ ] Repository will be created after ID assignment -- [ ] Private repository (internal use) -- [ ] Public repository - -**Documentation**: -- [ ] Module design document exists -- [ ] README prepared -- [ ] User manual planned -- [ ] Developer documentation available - -### ID Range Preference - -Based on the [Dolibarr Module ID Policy](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/DOLIBARR_MODULE_ID_REQUEST.md): - -**Preferred Range** (will be assigned by coordinator): -- [ ] Internal module (100000-109999) -- [ ] Client-specific (110000-119999) -- [ ] Public module (120000+, requires external registration) - -**Justification for preference**: - -### Security and Compliance - -**For Public Modules** (required before external registration): -- [ ] Code follows mokocli -- [ ] Security review completed -- [ ] No sensitive data or credentials in code -- [ ] License properly defined (GPL-3.0-or-later) -- [ ] Copyright headers included -- [ ] SPDX identifiers present - -**For Internal/Client Modules**: -- [ ] Follows internal coding standards -- [ ] Approved for intended use case -- [ ] Manager approval received - -### Conflict Check - -**Have you checked for conflicts?** -- [ ] Verified no internal module uses this name -- [ ] Searched Dolibarr Wiki for similar modules -- [ ] Checked DoliStore for existing modules -- [ ] Reviewed internal registry (docs/reference/dolibarr-module-ids.md) - -**Similar Modules Found**: -- [ ] None found -- [ ] Found similar modules (list below) - -If similar modules exist, explain why a new module is needed: - -### Additional Context - -**Urgency**: -- [ ] Urgent (needed within 48 hours - please justify below) -- [ ] Normal (2 business days for internal, 1-2 weeks for public) -- [ ] Low priority (when available) - -**If urgent, explain why**: - -**Additional Information**: - -### Acknowledgments - -- [ ] I have read the [Dolibarr Module ID Policy](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/DOLIBARR_MODULE_ID_REQUEST.md) -- [ ] I understand internal modules use range 100000-119999 -- [ ] I understand public modules require external registration with Dolibarr Foundation -- [ ] I understand module IDs are never reused once allocated -- [ ] I will implement the assigned ID in the module descriptor correctly -- [ ] I will follow Dolibarr module development best practices - ---- - -## For Coordinator Use Only - -**Do not edit below this line** - -### Review Checklist - -- [ ] Request is complete and clear -- [ ] No conflicts found in internal registry -- [ ] No conflicts found in DoliStore (for public modules) -- [ ] Manager approval verified (if required) -- [ ] Security review completed (for public modules) -- [ ] Distribution intent is appropriate - -### ID Assignment - -**Assigned Module ID**: _______ -**ID Range**: [Internal 100k / Client 110k / Public 120k+] -**Date Assigned**: _______ -**Assigned By**: @_______ - -**Registry Updated**: -- [ ] Added to docs/reference/dolibarr-module-ids.md -- [ ] Repository created/updated -- [ ] Developer notified with implementation guidelines - -### External Registration (Public Modules Only) - -- [ ] Request submitted to Dolibarr Foundation -- [ ] Submission date: _______ -- [ ] Dolibarr Wiki updated: _______ -- [ ] DoliStore registration: _______ -- [ ] Official ID confirmed: _______ -- [ ] Confirmation date: _______ - -**Notes**: diff --git a/templates/mokogitea/ISSUE_TEMPLATE/enterprise_support.md b/templates/mokogitea/ISSUE_TEMPLATE/enterprise_support.md deleted file mode 100644 index 4c3f0b4..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/enterprise_support.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -name: Enterprise Support Request -about: Request enterprise-level support or consultation -title: '[ENTERPRISE] ' -labels: 'enterprise, support' -assignees: '' - ---- - - -## Support Request Type -- [ ] Critical Production Issue -- [ ] Performance Optimization -- [ ] Security Audit -- [ ] Architecture Review -- [ ] Custom Development -- [ ] Migration Support -- [ ] Training & Onboarding -- [ ] Other (please specify) - -## Priority Level -- [ ] P0 - Critical (Production Down) -- [ ] P1 - High (Major Feature Broken) -- [ ] P2 - Medium (Non-Critical Issue) -- [ ] P3 - Low (Enhancement/Question) - -## Organization Details -- **Company Name**: -- **Contact Person**: -- **Email**: -- **Phone** (for P0/P1 issues): -- **Timezone**: - -## Issue Description -Provide a clear and detailed description of your request or issue. - -## Business Impact -Describe the impact on your business operations: -- Number of users affected: -- Revenue impact (if applicable): -- Deadline/SLA requirements: - -## Environment Details -- **Deployment Type**: [On-Premise / Cloud / Hybrid] -- **Platform**: [Joomla / Dolibarr / Custom] -- **Version**: -- **Infrastructure**: [AWS / Azure / GCP / Other] -- **Scale**: [Users / Transactions / Data Volume] - -## Current Configuration -```yaml -# Paste relevant configuration (sanitize sensitive data) -``` - -## Logs and Diagnostics -``` -# Paste relevant logs (sanitize sensitive data) -``` - -## Attempted Solutions -Describe any troubleshooting steps already taken. - -## Expected Resolution -Describe your expected outcome or resolution. - -## Additional Resources -- **Documentation Links**: -- **Related Issues**: -- **Screenshots/Videos**: - -## Enterprise SLA -- [ ] Standard Support (initial response within 1–3 weeks) -- [ ] Premium Support (initial response within 5 business days) -- [ ] Critical Support (initial response within 72 hours) -- [ ] Custom SLA (specify): - -## Compliance Requirements -- [ ] GDPR -- [ ] HIPAA -- [ ] SOC 2 -- [ ] ISO 27001 -- [ ] Other (specify): - ---- -**Note**: Enterprise support requests require an active support contract. If you don't have one, please contact us at enterprise@mokoconsulting.tech diff --git a/templates/mokogitea/ISSUE_TEMPLATE/feature_request.md b/templates/mokogitea/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 2f7d663..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -name: Feature Request -about: Suggest a new feature or enhancement -title: '[FEATURE] ' -labels: 'enhancement' -assignees: '' - ---- - - -## Feature Description -A clear and concise description of the feature you'd like to see. - -## Problem or Use Case -Describe the problem this feature would solve or the use case it addresses. -Ex. I'm always frustrated when [...] - -## Proposed Solution -A clear and concise description of what you want to happen. - -## Alternative Solutions -A clear and concise description of any alternative solutions or features you've considered. - -## Benefits -Describe how this feature would benefit users: -- Who would use this feature? -- What problems does it solve? -- What value does it add? - -## Implementation Details (Optional) -If you have ideas about how this could be implemented, share them here: -- Technical approach -- Files/components that might need changes -- Any concerns or challenges you foresee - -## Additional Context -Add any other context, mockups, or screenshots about the feature request here. - -## Relevant Standards -Does this relate to any standards in [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli)? -- [ ] Accessibility (WCAG 2.1 AA) -- [ ] Localization (en_US/en_GB) -- [ ] Security best practices -- [ ] Code quality standards -- [ ] Other: [specify] - -## Checklist -- [ ] I have searched for similar feature requests before creating this one -- [ ] I have clearly described the use case and benefits -- [ ] I have considered alternative solutions -- [ ] This feature aligns with the project's goals and scope diff --git a/templates/mokogitea/ISSUE_TEMPLATE/firewall-request.md b/templates/mokogitea/ISSUE_TEMPLATE/firewall-request.md deleted file mode 100644 index 0691b93..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/firewall-request.md +++ /dev/null @@ -1,190 +0,0 @@ ---- -name: Firewall Request -about: Request firewall rule changes or access to external resources -title: '[FIREWALL] [Resource Name] - [Brief Description]' -labels: ['firewall-request', 'infrastructure', 'security'] -assignees: ['jmiller'] ---- - - -## Firewall Request - -### Request Type -- [ ] Allow outbound access to external service/API -- [ ] Allow inbound access from external source -- [ ] Modify existing firewall rule -- [ ] Remove/revoke firewall rule -- [ ] Other (specify): - -### Resource Information -**Service/Domain Name**: -**IP Address(es)**: -**Port(s)**: -**Protocol**: -- [ ] HTTP (80) -- [ ] HTTPS (443) -- [ ] SSH (22) -- [ ] FTP (21) -- [ ] SFTP (22) -- [ ] Custom (specify): _______________ - -### Requestor Information -**Name**: -**GitHub Username**: @ -**Email**: @mokoconsulting.tech -**Team/Department**: -**Manager**: @ - -### Business Justification -**Why is this access needed?** - -**Which project(s) require this access?** - -**What functionality will break without this access?** - -**Is there an alternative solution?** -- [ ] Yes (explain): -- [ ] No - -### Security Considerations -**Data Classification**: -- [ ] Public -- [ ] Internal -- [ ] Confidential -- [ ] Restricted - -**Sensitive Data Transmission**: -- [ ] No sensitive data will be transmitted -- [ ] Sensitive data will be transmitted (encryption required) -- [ ] Authentication credentials will be transmitted (secure storage required) - -**Third-Party Service**: -- [ ] This is a trusted/verified third-party service -- [ ] This is a new/unverified service (security review required) - -**Service Documentation**: -(Provide link to service documentation or API specs) - -### Access Scope -**Affected Systems**: -- [ ] Development environment only -- [ ] Staging environment only -- [ ] Production environment -- [ ] All environments - -**Access Duration**: -- [ ] Permanent (ongoing business need) -- [ ] Temporary (specify end date): _______________ -- [ ] Testing only (specify duration): _______________ - -### Technical Details -**Source System(s)**: -(Which internal systems need access?) - -**Destination System(s)**: -(Which external systems need to be accessed?) - -**Expected Traffic Volume**: -(e.g., requests per hour/day) - -**Traffic Pattern**: -- [ ] Continuous -- [ ] Periodic (specify frequency): _______________ -- [ ] On-demand/manual -- [ ] Scheduled (specify schedule): _______________ - -### Testing Requirements -**Pre-Production Testing**: -- [ ] Request includes dev/staging access for testing -- [ ] Testing can be done with production access only -- [ ] No testing required (modify existing rule) - -**Testing Plan**: - -**Rollback Plan**: -(What happens if access needs to be revoked?) - -### Compliance & Audit -**Compliance Requirements**: -- [ ] GDPR considerations -- [ ] SOC 2 compliance required -- [ ] PCI DSS considerations -- [ ] Other regulatory requirements: _______________ -- [ ] No specific compliance requirements - -**Audit/Logging Requirements**: -- [ ] Standard logging sufficient -- [ ] Enhanced logging/monitoring required -- [ ] Real-time alerting required - -### Urgency -- [ ] Critical (production down, immediate access needed) -- [ ] High (needed within 24 hours) -- [ ] Normal (needed within 1 week) -- [ ] Low priority (needed within 1 month) - -**If critical/high urgency, explain why:** - -### Approvals -**Manager Approval**: -- [ ] Manager has been notified and approves this request - -**Security Team Review Required**: -- [ ] Yes (new external service, sensitive data) -- [ ] No (minor change, established service) - -### Additional Information - -**Related Documentation**: -(Links to relevant docs, RFCs, tickets, etc.) - -**Dependencies**: -(Other systems or changes this depends on) - -**Comments/Questions**: - ---- - -## For Infrastructure/Security Team Use Only - -**Do not edit below this line** - -### Security Review -- [ ] Security team review completed -- [ ] Risk assessment: Low / Medium / High -- [ ] Encryption required: Yes / No -- [ ] VPN required: Yes / No -- [ ] Additional security controls: _______________ - -**Reviewed By**: @_______________ -**Review Date**: _______________ -**Review Notes**: - -### Implementation -- [ ] Firewall rule created/modified -- [ ] Rule tested in dev/staging -- [ ] Rule deployed to production -- [ ] Monitoring/alerting configured -- [ ] Documentation updated - -**Firewall Rule ID**: _______________ -**Implementation Date**: _______________ -**Implemented By**: @_______________ - -**Configuration Details**: -``` -Source: -Destination: -Port/Protocol: -Action: Allow/Deny -``` - -### Verification -- [ ] Requestor confirmed access working -- [ ] Logs reviewed (no anomalies) -- [ ] Security scan completed (if applicable) - -**Verification Date**: _______________ -**Verified By**: @_______________ - -### Notes diff --git a/templates/mokogitea/ISSUE_TEMPLATE/joomla_issue.md b/templates/mokogitea/ISSUE_TEMPLATE/joomla_issue.md deleted file mode 100644 index d808f79..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/joomla_issue.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -name: Joomla Extension Issue -about: Report an issue with a Joomla extension -title: '[JOOMLA] ' -labels: 'joomla' -assignees: '' - ---- - - -## Issue Type -- [ ] Component Issue -- [ ] Module Issue -- [ ] Plugin Issue -- [ ] Template Issue - -## Extension Details -- **Extension Name**: [e.g., moko-cassiopeia] -- **Extension Version**: [e.g., 1.2.3] -- **Extension Type**: [Component / Module / Plugin / Template] - -## Joomla Environment -- **Joomla Version**: [e.g., 4.4.0, 5.0.0] -- **PHP Version**: [e.g., 8.1.0] -- **Database**: [MySQL / PostgreSQL / MariaDB] -- **Database Version**: [e.g., 8.0] -- **Server**: [Apache / Nginx / IIS] -- **Hosting**: [Shared / VPS / Dedicated / Cloud] - -## Issue Description -Provide a clear and detailed description of the issue. - -## Steps to Reproduce -1. Go to '...' -2. Click on '...' -3. Configure '...' -4. See error - -## Expected Behavior -What you expected to happen. - -## Actual Behavior -What actually happened. - -## Error Messages -``` -# Paste any error messages from Joomla error logs -# Location: administrator/logs/error.php -``` - -## Browser Console Errors -```javascript -// Paste any JavaScript console errors (F12 in browser) -``` - -## Screenshots -Add screenshots to help explain the issue. - -## Configuration -```ini -# Paste extension configuration (sanitize sensitive data) -``` - -## Installed Extensions -List other installed extensions that might conflict: -- Extension 1 (version) -- Extension 2 (version) - -## Template Overrides -- [ ] Using template overrides -- [ ] Custom CSS -- [ ] Custom JavaScript - -## Additional Context -- **Multilingual Site**: [Yes / No] -- **Cache Enabled**: [Yes / No] -- **Debug Mode**: [Yes / No] -- **SEF URLs**: [Yes / No] - -## Checklist -- [ ] I have cleared Joomla cache -- [ ] I have disabled other extensions to test for conflicts -- [ ] I have checked Joomla error logs -- [ ] I have tested with a default Joomla template -- [ ] I have checked browser console for JavaScript errors -- [ ] I have searched for similar issues -- [ ] I am using a supported Joomla version diff --git a/templates/mokogitea/ISSUE_TEMPLATE/question.md b/templates/mokogitea/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 3175013..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -name: Question -about: Ask a question about usage, features, or best practices -title: '[QUESTION] ' -labels: ['question'] -assignees: ['jmiller'] ---- - - -## Question - -**Your question:** - - -## Context - -**What are you trying to accomplish?** - - -**What have you already tried?** - - -**Category**: -- [ ] Script usage -- [ ] Configuration -- [ ] Workflow setup -- [ ] Documentation interpretation -- [ ] Best practices -- [ ] Integration -- [ ] Other: __________ - -## Environment (if relevant) - -**Your setup**: -- Operating System: -- Version: - -## What You've Researched - -**Documentation reviewed**: -- [ ] README.md -- [ ] Project documentation -- [ ] Other (specify): __________ - -**Similar issues/questions found**: -- # -- # - -## Expected Outcome - -**What result are you hoping for?** - - -## Code/Configuration Samples - -**Relevant code or configuration** (if applicable): - -```bash -# Your code here -``` - -## Additional Context - -**Any other relevant information:** - - -**Screenshots** (if helpful): - - -## Urgency - -- [ ] Urgent (blocking work) -- [ ] Normal (can work on other things meanwhile) -- [ ] Low priority (just curious) - -## Checklist - -- [ ] I have searched existing issues and discussions -- [ ] I have reviewed relevant documentation -- [ ] I have provided sufficient context -- [ ] I have included code/configuration samples if relevant -- [ ] This is a genuine question (not a bug report or feature request) diff --git a/templates/mokogitea/ISSUE_TEMPLATE/request-license.md b/templates/mokogitea/ISSUE_TEMPLATE/request-license.md deleted file mode 100644 index 7327adf..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/request-license.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -name: License Request -about: Request an organization license for Sublime Text -title: '[LICENSE REQUEST] Sublime Text - [Your Name]' -labels: ['license-request', 'admin'] -assignees: ['jmiller'] ---- - - -## License Request - -### Tool Information -**Tool Name**: Sublime Text - -**License Type Requested**: Organization Pool - -**Personal Purchase**: -- [ ] I prefer to purchase my own license ($99 USD - recommended, immediate access) -- [ ] I prefer an organization license (1-2 business days, organization use only) -- [ ] I have already purchased my own license (registration only for support) - -### Requestor Information -**Name**: -**GitHub Username**: @ -**Email**: @mokoconsulting.tech -**Team/Department**: -**Manager**: @ - -### Justification -**Why do you need this license?** - -**Primary use case**: -- [ ] Remote development (SFTP to servers) -- [ ] Local development -- [ ] Code review -- [ ] Documentation editing -- [ ] Other (specify): - -**Which projects/repositories will you work on?** - -**Have you evaluated the free trial?** -- [ ] Yes, I've used the trial and Sublime Text meets my needs -- [ ] No, requesting license before trial - -**Alternative tools considered**: -- [ ] VS Code (free alternative) -- [ ] Vim/Neovim (free, terminal-based) -- [ ] Other: _______________ - -### Platform -- [ ] Windows -- [ ] macOS -- [ ] Linux (distribution: ________) - -### Urgency -- [ ] Urgent (needed within 24 hours - please justify) -- [ ] Normal (1-2 business days) -- [ ] Low priority (when available) - -**If urgent, please explain why:** - -### SFTP Plugin -**Note**: Sublime SFTP plugin ($16 USD) is a **separate personal purchase** and is NOT provided by the organization. - -- [ ] I understand SFTP plugin requires separate personal purchase -- [ ] I have already purchased SFTP plugin -- [ ] I will purchase SFTP plugin if needed for my work -- [ ] I don't need SFTP plugin (local development only) - -### Acknowledgments -- [ ] I have read the License Management Policy (/docs/github-private/LICENSE_MANAGEMENT.md) -- [ ] I understand organization licenses are for work use only -- [ ] I understand organization licenses must be returned upon leaving -- [ ] I understand personal purchases ($99) are an alternative with lifetime access -- [ ] I understand SFTP plugin ($16) requires separate personal purchase -- [ ] I agree to the terms of use - -### Additional Information - -**Expected daily usage hours**: _____ hours/day - -**Duration of need**: -- [ ] Permanent (ongoing role) -- [ ] Temporary project (_____ months) -- [ ] Trial/Evaluation (_____ weeks) - -**Comments/Questions**: - ---- - -## For Admin Use Only - -**Do not edit below this line** - -- [ ] Manager approval received (@manager-username) -- [ ] License available in pool (current: __/20) -- [ ] License type confirmed (Organization / Personal registration) -- [ ] License key sent via encrypted email -- [ ] Activation confirmed by user -- [ ] Added to license tracking sheet -- [ ] User notified of SFTP plugin requirement - -**License Key ID**: _____________ -**Date Issued**: _____________ -**Issued By**: @_____________ - -**Notes**: diff --git a/templates/mokogitea/ISSUE_TEMPLATE/rfc.md b/templates/mokogitea/ISSUE_TEMPLATE/rfc.md deleted file mode 100644 index 6f09af7..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/rfc.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -name: Request for Comments (RFC) -about: Propose a significant change for community discussion -title: '[RFC] ' -labels: 'rfc, discussion' -assignees: '' - ---- - - -## RFC Summary -One-paragraph summary of the proposal. - -## Motivation -Why are we doing this? What use cases does it support? What is the expected outcome? - -## Detailed Design -### Overview -Provide a detailed explanation of the proposed change. - -### API Changes (if applicable) -```php -// Before -function oldApi($param1) { } - -// After -function newApi($param1, $param2) { } -``` - -### User Experience Changes -Describe how users will interact with this change. - -### Implementation Approach -High-level implementation strategy. - -## Drawbacks -Why should we *not* do this? - -## Alternatives -What other designs have been considered? What is the impact of not doing this? - -### Alternative 1 -- Description -- Trade-offs - -### Alternative 2 -- Description -- Trade-offs - -## Adoption Strategy -How will existing users adopt this? Is this a breaking change? - -### Migration Guide -```bash -# Steps to migrate -``` - -### Deprecation Timeline -- **Announcement**: -- **Deprecation**: -- **Removal**: - -## Unresolved Questions -- Question 1 -- Question 2 - -## Future Possibilities -What future work does this enable? - -## Impact Assessment -### Performance -Expected performance impact. - -### Security -Security considerations and implications. - -### Compatibility -- **Backward Compatible**: [Yes / No] -- **Breaking Changes**: [List] - -### Maintenance -Long-term maintenance considerations. - -## Community Input -### Stakeholders -- [ ] Core team -- [ ] Module developers -- [ ] End users -- [ ] Enterprise customers - -### Feedback Period -**Duration**: [e.g., 2 weeks] -**Deadline**: [date] - -## Implementation Timeline -### Phase 1: Design -- [ ] RFC discussion -- [ ] Design finalization -- [ ] Approval - -### Phase 2: Implementation -- [ ] Core implementation -- [ ] Tests -- [ ] Documentation - -### Phase 3: Release -- [ ] Beta release -- [ ] Feedback collection -- [ ] Stable release - -## Success Metrics -How will we measure success? -- Metric 1 -- Metric 2 - -## References -- Related RFCs: -- External documentation: -- Prior art: - -## Open Questions for Community -1. Question 1? -2. Question 2? - ---- -**Note**: This RFC is open for community discussion. Please provide feedback in the comments below. diff --git a/templates/mokogitea/ISSUE_TEMPLATE/security.md b/templates/mokogitea/ISSUE_TEMPLATE/security.md deleted file mode 100644 index ea85959..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/security.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -name: Security Vulnerability Report -about: Report a security vulnerability (use only for non-critical issues) -title: '[SECURITY] ' -labels: 'security' -assignees: '' - ---- - - -## ⚠️ IMPORTANT: Private Disclosure Required - -**For critical security vulnerabilities, DO NOT use this template.** -Follow the process in [SECURITY.md](../SECURITY.md) for responsible disclosure. - -Use this template only for: -- Security improvements -- Non-critical security suggestions -- Security documentation updates - ---- - -## Security Issue - -**Severity**: - - -## Description - - -## Affected Components - - -## Suggested Mitigation - - -## Standards Reference -Does this relate to security standards in [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli)? -- [ ] SPDX license identifiers -- [ ] Secret management -- [ ] Dependency security -- [ ] Access control -- [ ] Other: [specify] - -## Additional Context - - -## Checklist -- [ ] This is NOT a critical vulnerability requiring private disclosure -- [ ] I have reviewed the SECURITY.md policy -- [ ] I have provided sufficient detail for evaluation diff --git a/templates/mokogitea/ISSUE_TEMPLATE/version.md b/templates/mokogitea/ISSUE_TEMPLATE/version.md deleted file mode 100644 index 6328421..0000000 --- a/templates/mokogitea/ISSUE_TEMPLATE/version.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Version Bump -about: Request or track a version change -title: '[VERSION] ' -labels: 'version, type: version' -assignees: 'jmiller' ---- - -## Version Change - -**Current version**: -**Requested version**: -**Change type**: - -## Reason - - - -## Checklist - -- [ ] README.md `VERSION:` field updated -- [ ] CHANGELOG.md entry added -- [ ] Module descriptor version updated (Dolibarr: `$this->version`, Joomla: ``) -- [ ] All file headers will be auto-propagated by `sync-version-on-merge` workflow diff --git a/templates/mokogitea/PULL_REQUEST_TEMPLATE.md b/templates/mokogitea/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 55e1428..0000000 --- a/templates/mokogitea/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,86 +0,0 @@ - - -# Pull Request - -## Description - -Please include a summary of the changes and the related issue. Include relevant motivation and context. - -Fixes # (issue) - -## Type of Change - -Please delete options that are not relevant. - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] Documentation update -- [ ] Performance improvement -- [ ] Code refactoring -- [ ] CI/CD improvement -- [ ] Dependency update - -## How Has This Been Tested? - -Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. - -- [ ] Test A -- [ ] Test B - -**Test Configuration**: -- OS: -- Version: -- Other relevant configuration: - -## Checklist - -- [ ] My code follows the style guidelines of this project -- [ ] I have performed a self-review of my own code -- [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings -- [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] New and existing unit tests pass locally with my changes -- [ ] Any dependent changes have been merged and published in downstream modules -- [ ] I have checked my code and corrected any misspellings -- [ ] I have updated the CHANGELOG.md file -- [ ] **Patch version bumped** — incremented `XX.YY.ZZ` in `README.md` (e.g. `01.02.03` → `01.02.04`); `sync-version-on-merge` propagates it to all file headers and badges on merge -- [ ] All file headers are present and correct - -## Breaking Changes - -Does this PR introduce any breaking changes? If yes, please describe the impact and migration path: - -## Screenshots (if applicable) - -Add screenshots to help explain your changes. - -## Additional Context - -Add any other context about the pull request here. - -## Related Issues/PRs - -- Related to # -- Depends on # -- Blocks # diff --git a/templates/mokogitea/PULL_REQUEST_TEMPLATE.md.backup b/templates/mokogitea/PULL_REQUEST_TEMPLATE.md.backup deleted file mode 100644 index 7b56431..0000000 --- a/templates/mokogitea/PULL_REQUEST_TEMPLATE.md.backup +++ /dev/null @@ -1,85 +0,0 @@ - - -# Pull Request - -## Description - -Please include a summary of the changes and the related issue. Include relevant motivation and context. - -Fixes # (issue) - -## Type of Change - -Please delete options that are not relevant. - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] Documentation update -- [ ] Performance improvement -- [ ] Code refactoring -- [ ] CI/CD improvement -- [ ] Dependency update - -## How Has This Been Tested? - -Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. - -- [ ] Test A -- [ ] Test B - -**Test Configuration**: -- OS: -- Version: -- Other relevant configuration: - -## Checklist - -- [ ] My code follows the style guidelines of this project -- [ ] I have performed a self-review of my own code -- [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation -- [ ] My changes generate no new warnings -- [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] New and existing unit tests pass locally with my changes -- [ ] Any dependent changes have been merged and published in downstream modules -- [ ] I have checked my code and corrected any misspellings -- [ ] I have updated the CHANGELOG.md file -- [ ] All file headers are present and correct - -## Breaking Changes - -Does this PR introduce any breaking changes? If yes, please describe the impact and migration path: - -## Screenshots (if applicable) - -Add screenshots to help explain your changes. - -## Additional Context - -Add any other context about the pull request here. - -## Related Issues/PRs - -- Related to # -- Depends on # -- Blocks # diff --git a/templates/mokogitea/README.md b/templates/mokogitea/README.md deleted file mode 100644 index 2566e09..0000000 --- a/templates/mokogitea/README.md +++ /dev/null @@ -1,385 +0,0 @@ - - -# GitHub Templates - -## Overview - -This directory contains templates for GitHub-specific features including issue templates, pull request templates, and CODEOWNERS files. These templates help standardize collaboration and contribution processes across repositories. - -## Purpose - -GitHub templates provide: - -- **Consistent Issue Reporting**: Standardized bug reports and feature requests -- **Structured Pull Requests**: Clear PR descriptions and checklists -- **Code Ownership**: Defined code ownership for reviews -- **Better Collaboration**: Clear expectations for contributors -- **Quality Control**: Ensure all necessary information is captured - -## Template Categories - -### Issue Templates - -Located in `ISSUE_TEMPLATE/` directory: - -- **Bug Report** (`bug_report.md`): Template for reporting bugs -- **Feature Request** (`feature_request.md`): Template for requesting features -- **Custom Templates**: Project-specific issue types -- **Configuration** (`config.yml`): Issue template configuration - -**Usage**: Copy entire `ISSUE_TEMPLATE/` directory to your repository's `.mokogitea/` directory. - -### Pull Request Template - -**File**: `PULL_REQUEST_TEMPLATE.md` - -**Purpose**: Standardize pull request descriptions and ensure all necessary information is provided before review. - -**Usage**: Copy to `.mokogitea/PULL_REQUEST_TEMPLATE.md` in your repository. - -### CODEOWNERS Template - -**File**: `CODEOWNERS.template` - -**Purpose**: Define code ownership for automatic review assignment. - -**Usage**: -1. Copy to `.mokogitea/CODEOWNERS` (remove `.template` suffix) -2. Customize with your team and file patterns -3. Commit to repository - -## Using These Templates - -### Setup Process - -1. **Choose Templates**: Identify which templates your repository needs -2. **Copy to Repository**: Copy templates to your repository's `.mokogitea/` directory -3. **Customize**: Adapt templates to your project's needs -4. **Test**: Create test issues/PRs to validate templates -5. **Document**: Update README with any project-specific requirements - -### Directory Structure in Your Repository - -``` -your-repository/ -└── .mokogitea/ - ├── ISSUE_TEMPLATE/ - │ ├── bug_report.md - │ ├── feature_request.md - │ └── config.yml - ├── PULL_REQUEST_TEMPLATE.md - └── CODEOWNERS -``` - -## Issue Templates - -### Bug Report Template - -**Purpose**: Capture all information needed to reproduce and fix bugs. - -**Required Information:** -- Bug description -- Steps to reproduce -- Expected behavior -- Actual behavior -- Environment details -- Screenshots (if applicable) - -**Customization:** -- Add project-specific environment fields -- Add relevant labels automatically -- Customize sections for your workflow - -### Feature Request Template - -**Purpose**: Clearly describe desired functionality and use cases. - -**Required Information:** -- Feature description -- Use case and motivation -- Proposed solution -- Alternatives considered -- Additional context - -**Customization:** -- Add project-specific fields -- Include acceptance criteria template -- Add design review section if needed - -### Configuration File - -**File**: `config.yml` - -**Purpose**: Configure issue template behavior. - -**Options:** -- Disable blank issues -- Add external links -- Set contact links -- Configure template chooser - -**Example:** -```yaml -blank_issues_enabled: false -contact_links: - - name: "📚 Documentation" - url: https://git.mokoconsulting.tech/MokoConsulting/mokocli/docs - about: "Check the documentation first" - - name: "💬 Discussions" - url: https://git.mokoconsulting.tech/MokoConsulting/mokocli/discussions - about: "Ask questions and discuss ideas" -``` - -## Pull Request Template - -### Standard Sections - -The PR template includes: - -1. **Description**: What changes are being made and why -2. **Type of Change**: Classification of the change -3. **Checklist**: Pre-merge verification items -4. **Testing**: How changes were tested -5. **Related Issues**: Links to related issues -6. **Breaking Changes**: Any breaking changes -7. **Documentation**: Documentation updates needed - -### Customization - -Add project-specific sections: -- **Performance Impact**: For performance-critical projects -- **Security Considerations**: For security-focused projects -- **UI Changes**: For projects with user interfaces -- **Database Migrations**: For projects with databases -- **Rollback Plan**: For production deployments - -### Best Practices - -- **Keep It Concise**: Don't make template too long -- **Use Checkboxes**: Make requirements clear and verifiable -- **Provide Examples**: Show good PR descriptions -- **Link to Guidelines**: Reference CONTRIBUTING.md -- **Make It Helpful**: Template should aid contributors - -## CODEOWNERS Template - -### Purpose - -The CODEOWNERS file: -- Defines ownership of code areas -- Automatically requests reviews from owners -- Protects critical code paths -- Documents team structure -- Ensures expertise is consulted - -### Syntax - -``` -# Pattern # Owner(s) -* @org/default-team -/docs/ @org/docs-team -/src/ @org/dev-team -/.mokogitea/workflows/ @org/devops-team -/scripts/ @org/automation-team -/docs/policy/security-*.md @org/security-team -``` - -### Customization - -1. **Replace Organization**: Change `@org/` to your organization -2. **Define Teams**: Match GitHub team structure -3. **Set Patterns**: Use glob patterns for files -4. **Order Matters**: Last matching pattern wins -5. **Test Ownership**: Verify assignments work correctly - -### Best Practices - -- **Start Broad**: Default owner for all files -- **Get Specific**: Specific patterns for critical areas -- **Use Teams**: Prefer teams over individuals -- **Document Intent**: Add comments explaining ownership -- **Keep Updated**: Review quarterly - -### Protection Rules - -Combine CODEOWNERS with branch protection: -- Require code owner review -- Prevent bypassing by admins -- Ensure critical code is reviewed - -## Template Maintenance - -### Version Control - -Track template changes: -- Use semantic versioning in headers -- Document changes in revision history -- Communicate template updates -- Provide migration guidance - -### Review Cadence - -**Quarterly Review:** -- Evaluate template effectiveness -- Gather user feedback -- Update for new requirements -- Remove obsolete sections -- Add missing sections - -**Annual Review:** -- Major template overhaul -- Align with updated standards -- Benchmark against industry practices -- Solicit team feedback - -### Testing Templates - -Before publishing templates: -1. Create test issue using template -2. Create test PR using template -3. Verify CODEOWNERS assignments -4. Check for broken links -5. Validate formatting -6. Get team review - -## Examples - -### Example: Minimal Setup - -For small projects: -``` -.mokogitea/ -├── ISSUE_TEMPLATE/ -│ └── bug_report.md -└── PULL_REQUEST_TEMPLATE.md -``` - -### Example: Complete Setup - -For large projects: -``` -.mokogitea/ -├── ISSUE_TEMPLATE/ -│ ├── bug_report.md -│ ├── feature_request.md -│ ├── security_report.md -│ ├── documentation.md -│ └── config.yml -├── PULL_REQUEST_TEMPLATE.md -└── CODEOWNERS -``` - -### Example: Multi-Project Setup - -For repositories with multiple components: -``` -.mokogitea/ -├── ISSUE_TEMPLATE/ -│ ├── bug_report.md -│ ├── feature_request.md -│ ├── performance_issue.md -│ ├── security_report.md -│ └── config.yml -├── PULL_REQUEST_TEMPLATE/ -│ ├── pull_request_template.md # Default -│ ├── hotfix.md # Hotfix template -│ └── release.md # Release template -└── CODEOWNERS -``` - -## Common Pitfalls - -### Anti-Patterns to Avoid - -1. **Overly Complex Templates**: Templates that are too long discourage use -2. **Too Many Required Fields**: Makes template tedious -3. **Vague Instructions**: Unclear what's expected -4. **Outdated Information**: References to old processes -5. **No Customization**: Generic templates don't fit all projects -6. **Ignored Templates**: Templates that aren't enforced -7. **Missing Documentation**: No guidance on using templates - -### How to Avoid Them - -- Keep templates concise and focused -- Make fields optional when possible -- Provide clear examples -- Review and update regularly -- Customize for your project -- Enforce template usage in reviews -- Document template purpose and usage - -## Integration with Workflows - -### Automated Validation - -Use GitHub Actions to validate: -- Required sections are present -- Links are valid -- Labels are applied correctly -- Assignees are set -- CODEOWNERS are requested - -### Auto-Labeling - -Automatically label issues/PRs based on: -- Template used -- Files changed -- Keywords in description -- Size of change - -### Status Checks - -Require status checks that verify: -- PR template checklist completed -- All required reviewers approved -- Documentation updated -- Tests passing - -## References - -- [GitHub Issue Templates Documentation](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository) -- [GitHub PR Templates Documentation](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository) -- [GitHub CODEOWNERS Documentation](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners) -- [mokocli Repository](https://git.mokoconsulting.tech/MokoConsulting/mokocli) - -## Metadata - -* **Document**: templates/mokogitea/README.md -* **Repository**: [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli) -* **Owner**: Moko Consulting Engineering Team -* **Scope**: GitHub templates and configuration -* **Lifecycle**: Active -* **Audience**: All repository maintainers and contributors - -## Revision History - -| Version | Date | Author | Notes | -| -------- | ---------- | ------------------------------- | ----------------------------------------------- | -| 01.00.00 | 2026-01-13 | GitHub Copilot | Initial GitHub templates documentation | diff --git a/templates/mokogitea/copilot-instructions.dolibarr.md.template b/templates/mokogitea/copilot-instructions.dolibarr.md.template deleted file mode 100644 index 75611e7..0000000 --- a/templates/mokogitea/copilot-instructions.dolibarr.md.template +++ /dev/null @@ -1,335 +0,0 @@ - - -> [!IMPORTANT] -> **🔧 AI Self-Update Required on First Use** -> -> This file may contain unfilled `{{TOKEN}}` placeholders. The mokocli bulk-sync system -> replaces them automatically at sync time; if you can still see them, sync has not yet run for -> this repository — or this is a brand-new repo. -> -> **On your very first interaction in this repository, before answering any other question:** -> -> 1. Check whether any `{{TOKEN}}` placeholders remain in this file. -> 2. If they do, locate the values as described in the table below and replace every occurrence -> in **both** `.gitea/copilot-instructions.md` and `.gitea/CLAUDE.md`. -> 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]` -> through the closing `> ---` line) in both files. -> 4. Tell the developer: *"I've self-updated the AI context files with this repository's -> values — all set."* -> -> | Placeholder | Where to find the value | -> |---|---| -> | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) | -> | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://git.mokoconsulting.tech/mokoconsulting-tech/` | -> | `{{MODULE_NAME}}` | The module name as used in Dolibarr (lowercase, e.g. `mymodule`) — from the `langs/en_US/*.lang` filename or `$this->rights_class` in the module descriptor | -> | `{{MODULE_CLASS}}` | PascalCase module class name (e.g. `MyModule`) — from the `src/core/modules/mod*.class.php` filename | -> | `{{MODULE_ID}}` | The `$this->numero` value in `src/core/modules/mod*.class.php`; check [module-registry.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/development/crm/module-registry.md) if creating a new module | -> | `{{PRIMARY_LANGUAGE}}` | Primary programming language (usually `PHP`) | -> -> --- - -# {{REPO_NAME}} — GitHub Copilot Custom Instructions - -## What This Repo Is - -This is a **Moko Consulting MokoCRM** (Dolibarr) module repository governed by [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli). All coding standards, workflows, and policies are defined there and enforced here via bulk sync. - -Repository URL: {{REPO_URL}} -Module name: **{{MODULE_NAME}}** -Module class: **{{MODULE_CLASS}}** -Module ID: **{{MODULE_ID}}** -Platform: **Dolibarr / MokoCRM** - ---- - -## Primary Language - -**PHP** (≥ 8.1) is the primary language for this Dolibarr module. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`. - ---- - -## File Header — Always Required on New Files - -Every new file needs a copyright header as its first content. - -**PHP:** -```php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: {{REPO_NAME}}.Module - * INGROUP: {{REPO_NAME}} - * REPO: {{REPO_URL}} - * PATH: /src/path/to/file.php - * VERSION: XX.YY.ZZ - * BRIEF: One-line description of purpose - */ -``` - -**Markdown:** -```markdown - -``` - -**YAML / Shell:** Use `#` comments with the same fields. JSON files are exempt. - ---- - -## Version Management - -**`README.md` is the single source of truth for the repository version.** - -- **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it automatically to all badges and `FILE INFORMATION` headers on merge to `main`. -- The `VERSION: XX.YY.ZZ` field in `README.md` governs all other version references. -- Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`). -- Never hardcode a specific version in document body text — use the badge or FILE INFORMATION header only. - -### Dolibarr Module Version Alignment - -The version in `README.md` **must always match** the `$this->version` property in the main module descriptor class (`src/core/modules/mod{{MODULE_CLASS}}.class.php`). - -```php -// In src/core/modules/mod{{MODULE_CLASS}}.class.php -public $version = '01.02.04'; // Must match README.md version -``` - ---- - -## Dolibarr Module Structure - -``` -{{REPO_NAME}}/ -├── src/ # Module source code (deployed to Dolibarr) -│ ├── index.php # REQUIRED — accessforbidden() guard -│ ├── README.md # End-user documentation -│ ├── core/ -│ │ ├── index.php # REQUIRED — accessforbidden() guard -│ │ └── modules/ -│ │ ├── index.php # REQUIRED — accessforbidden() guard -│ │ └── mod{{MODULE_CLASS}}.class.php # Main module descriptor -│ ├── langs/ -│ │ ├── index.php # REQUIRED — accessforbidden() guard -│ │ └── en_US/ -│ │ ├── index.php # REQUIRED — accessforbidden() guard -│ │ └── {{MODULE_NAME}}.lang -│ ├── sql/ # Database schema -│ │ ├── index.php # REQUIRED — accessforbidden() guard -│ │ ├── llx_{{MODULE_NAME}}.sql -│ │ └── llx_{{MODULE_NAME}}.key.sql -│ ├── class/ # PHP class files -│ │ └── index.php # REQUIRED — accessforbidden() guard -│ └── lib/ # Library files -│ └── index.php # REQUIRED — accessforbidden() guard -├── docs/ # Technical documentation -├── scripts/ # Build and maintenance scripts -├── tests/ # Test suite -├── .gitea/ -│ ├── workflows/ -│ ├── copilot-instructions.md # This file -│ └── CLAUDE.md -├── README.md # Version source of truth -├── CHANGELOG.md -├── CONTRIBUTING.md -├── LICENSE # GPL-3.0-or-later -└── Makefile # Build automation -``` - -**Every directory inside `src/` MUST have an `index.php`** that either contains live code or calls Dolibarr's built-in `accessforbidden()` to block direct web access. Use this guard template for non-public directories (adjust the relative fallback path to match the directory depth from `htdocs/`): - -```php - - * SPDX-License-Identifier: GPL-3.0-or-later - * FILE INFORMATION / BRIEF: Directory access guard - */ -// Adjust relative path below per depth: mymodule/subdir/ uses ../../../main.inc.php -$res = 0; -if (!$res && !empty($_SERVER["DOCUMENT_ROOT"])) { - $res = @include $_SERVER["DOCUMENT_ROOT"]."/main.inc.php"; -} -if (!$res && file_exists("../../../main.inc.php")) { - $res = @include "../../../main.inc.php"; -} -if (!$res) { die("Include of main fails"); } -accessforbidden(); -``` - ---- - -## Module Descriptor Class Pattern - -The main module descriptor (`src/core/modules/mod{{MODULE_CLASS}}.class.php`) must follow this pattern: - -```php -db = $db; - $this->numero = {{MODULE_ID}}; // Unique module ID — do not change - $this->rights_class = '{{MODULE_NAME}}'; - $this->family = 'crm'; - $this->module_position = '50'; - $this->name = preg_replace('/^mod/i', '', get_class($this)); - $this->description = 'Description of {{MODULE_NAME}} module'; - $this->version = 'XX.YY.ZZ'; // Must match README.md version - $this->const_name = 'MAIN_MODULE_' . strtoupper($this->name); - $this->picto = 'object_favicon_256.png@mokocrm'; - $this->editor_name = 'Moko Consulting'; - $this->editor_url = 'https://mokoconsulting.tech'; // Must be an external online web site - $this->editor_squarred_logo = 'object_favicon_256.png@mokocrm'; - } -} -``` - -**Key rules for the module descriptor:** -- `$this->numero` is a globally unique ID registered in [module-registry.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/development/crm/module-registry.md) — **never change it**. -- `$this->version` must exactly match the version in `README.md`. -- Register new modules in the module registry before using any ID. - ---- - -## GitHub Actions — Token Usage - -Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token). - -```yaml -# ✅ Correct -- uses: actions/checkout@v4 - with: - token: ${{ secrets.GH_TOKEN }} - -env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} -``` - -```yaml -# ❌ Wrong — never use these in workflows -token: ${{ github.token }} -token: ${{ secrets.GITHUB_TOKEN }} -``` - -PHP scripts read the token with: `getenv('GH_TOKEN') ?: getenv('GITHUB_TOKEN')` — `GH_TOKEN` is always preferred; `GITHUB_TOKEN` is a local-dev fallback only. - ---- - -## mokocli Reference - -This repository is governed by [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli). Authoritative policies: - -| Document | Purpose | -|----------|---------| -| [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | -| [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | -| [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | -| [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | -| [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | -| [module-registry.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/development/crm/module-registry.md) | Dolibarr module ID registry — check before reserving a new ID | -| [crm-development-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/crm/development-standards.md) | MokoCRM Dolibarr module development standards | - ---- - -## Naming Conventions - -| Context | Convention | Example | -|---------|-----------|---------| -| PHP class | `PascalCase` | `MyService` | -| PHP method / function | `camelCase` | `getUserData()` | -| PHP variable | `$snake_case` | `$module_name` | -| PHP constant | `UPPER_SNAKE_CASE` | `MAX_RETRIES` | -| PHP class file | `PascalCase.php` | `ApiClient.php` | -| PHP script file | `snake_case.php` | `check_health.php` | -| YAML workflow | `kebab-case.yml` | `ci-dolibarr.yml` | -| Markdown doc | `kebab-case.md` | `installation-guide.md` | - ---- - -## Commit Messages - -Format: `(): ` — imperative, lower-case subject, no trailing period. - -Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build` - ---- - -## Branch Naming - -Format: `/[/description]` - -Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/` - ---- - -## Keeping Documentation Current - -| Change type | Documentation to update | -|-------------|------------------------| -| New or renamed public PHP method | PHPDoc block; `docs/api/` index for that class | -| New or changed module version | Update `$this->version` in module descriptor; bump `README.md` | -| New library class or major feature | `CHANGELOG.md` entry under `Added` | -| Bug fix | `CHANGELOG.md` entry under `Fixed` | -| Breaking change | `CHANGELOG.md` entry under `Changed` | -| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | -| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | - ---- - -## Key Constraints - -- Never commit directly to `main` — all changes go via PR, squash-merged -- Never skip the FILE INFORMATION block on a new file -- Never change `$this->numero` (module ID) — it is permanently registered in the module registry -- Never hardcode version numbers in body text — update `README.md` and let automation propagate -- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` -- Never let the module descriptor `$this->version` and `README.md` version go out of sync -- Never register a new module ID without first checking module-registry.md for the next available ID -- Never create a new directory inside `src/` without adding an `index.php` that calls `accessforbidden()` or contains live code diff --git a/templates/mokogitea/copilot-instructions.joomla.md.template b/templates/mokogitea/copilot-instructions.joomla.md.template deleted file mode 100644 index a9f213e..0000000 --- a/templates/mokogitea/copilot-instructions.joomla.md.template +++ /dev/null @@ -1,310 +0,0 @@ - - -> [!IMPORTANT] -> **🔧 AI Self-Update Required on First Use** -> -> This file may contain unfilled `{{TOKEN}}` placeholders. The mokocli bulk-sync system -> replaces them automatically at sync time; if you can still see them, sync has not yet run for -> this repository — or this is a brand-new repo. -> -> **On your very first interaction in this repository, before answering any other question:** -> -> 1. Check whether any `{{TOKEN}}` placeholders remain in this file. -> 2. If they do, locate the values as described in the table below and replace every occurrence -> in **both** `.gitea/copilot-instructions.md` and `.gitea/CLAUDE.md`. -> 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]` -> through the closing `> ---` line) in both files. -> 4. Tell the developer: *"I've self-updated the AI context files with this repository's -> values — all set."* -> -> | Placeholder | Where to find the value | -> |---|---| -> | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) | -> | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://git.mokoconsulting.tech/mokoconsulting-tech/` | -> | `{{EXTENSION_NAME}}` | The `` element in `manifest.xml` at the repository root | -> | `{{EXTENSION_TYPE}}` | The `type` attribute of the `` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) | -> | `{{EXTENSION_ELEMENT}}` | The `` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) | -> -> --- - -# {{REPO_NAME}} — GitHub Copilot Custom Instructions - -## What This Repo Is - -This is a **Moko Consulting MokoWaaS** (Joomla) repository governed by [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli). All coding standards, workflows, and policies are defined there and enforced here via bulk sync. - -Repository URL: {{REPO_URL}} -Extension name: **{{EXTENSION_NAME}}** -Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`) -Platform: **Joomla 4.x / MokoWaaS** - ---- - -## Primary Language - -**PHP** (≥ 7.4) is the primary language for this Joomla extension. JavaScript may be used for frontend enhancements. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`. - ---- - -## File Header — Always Required on New Files - -Every new file needs a copyright header as its first content. - -**PHP:** -```php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: {{REPO_NAME}}.{{EXTENSION_TYPE}} - * INGROUP: {{REPO_NAME}} - * REPO: {{REPO_URL}} - * PATH: /path/to/file.php - * VERSION: XX.YY.ZZ - * BRIEF: One-line description of purpose - */ - -defined('_JEXEC') or die; -``` - -**Markdown:** -```markdown - -``` - -**YAML / Shell / XML:** Use the appropriate comment syntax with the same fields. JSON files are exempt. - ---- - -## Version Management - -**`README.md` is the single source of truth for the repository version.** - -- **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it automatically to all badges and `FILE INFORMATION` headers on merge to `main`. -- The `VERSION: XX.YY.ZZ` field in `README.md` governs all other version references. -- Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`). -- Never hardcode a specific version in document body text — use the badge or FILE INFORMATION header only. - -### Joomla Version Alignment - -The version in `README.md` **must always match** the `` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically. - -```xml - -01.02.04 - - - - - {{EXTENSION_NAME}} - 01.02.04 - - - {{REPO_URL}}/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip - - - - - - -``` - ---- - -## Joomla Extension Structure - -``` -{{REPO_NAME}}/ -├── manifest.xml # Joomla installer manifest (root — required) -├── updates.xml # Update server manifest (root — required, see below) -├── site/ # Frontend (site) code -│ ├── controller.php -│ ├── controllers/ -│ ├── models/ -│ └── views/ -├── admin/ # Backend (admin) code -│ ├── controller.php -│ ├── controllers/ -│ ├── models/ -│ ├── views/ -│ └── sql/ -├── language/ # Language INI files -├── media/ # CSS, JS, images (deployed to /media/{{EXTENSION_ELEMENT}}/) -├── docs/ # Technical documentation -├── tests/ # Test suite -├── .gitea/ -│ ├── workflows/ -│ ├── copilot-instructions.md # This file -│ └── CLAUDE.md -├── README.md # Version source of truth -├── CHANGELOG.md -├── CONTRIBUTING.md -├── LICENSE # GPL-3.0-or-later -└── Makefile # Build automation -``` - ---- - -## updates.xml — Required in Repo Root - -`updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension. - -The `manifest.xml` must reference BOTH update servers: -```xml - - - https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}/raw/branch/main/updates.xml - - - https://raw.githubusercontent.com/mokoconsulting-tech/{{REPO_NAME}}/main/updates.xml - - -``` - -**Rules:** -- Every release must prepend a new `` block at the top of `updates.xml` — old entries must be preserved below. -- The `` in `updates.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. -- `` must include two `` entries: Gitea release asset (primary) and GitHub release asset (mirror). -- `` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it. - ---- - -## manifest.xml Rules - -- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). -- `` tag must be kept in sync with `README.md` version and `updates.xml`. -- Must include `` block pointing to this repo's `updates.xml`. -- Must include `` and `` sections. -- Joomla 4.x requires `Moko\{{EXTENSION_NAME}}` for namespaced extensions. - ---- - -## GitHub Actions — Token Usage - -Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token). - -```yaml -# ✅ Correct -- uses: actions/checkout@v4 - with: - token: ${{ secrets.GH_TOKEN }} - -env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} -``` - -```yaml -# ❌ Wrong — never use these in workflows -token: ${{ github.token }} -token: ${{ secrets.GITHUB_TOKEN }} -``` - ---- - -## mokocli Reference - -This repository is governed by [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli). Authoritative policies: - -| Document | Purpose | -|----------|---------| -| [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | -| [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | -| [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | -| [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | -| [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | -| [joomla-development-guide.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | - ---- - -## Naming Conventions - -| Context | Convention | Example | -|---------|-----------|---------| -| PHP class | `PascalCase` | `MyController` | -| PHP method / function | `camelCase` | `getItems()` | -| PHP variable | `$snake_case` | `$item_id` | -| PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` | -| PHP class file | `PascalCase.php` | `ItemModel.php` | -| YAML workflow | `kebab-case.yml` | `ci-joomla.yml` | -| Markdown doc | `kebab-case.md` | `installation-guide.md` | - ---- - -## Commit Messages - -Format: `(): ` — imperative, lower-case subject, no trailing period. - -Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build` - ---- - -## Branch Naming - -Format: `/[/description]` - -Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/` - ---- - -## Keeping Documentation Current - -| Change type | Documentation to update | -|-------------|------------------------| -| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | -| New or changed manifest.xml | Update `updates.xml` version; bump README.md version | -| New release | Prepend `` block to `updates.xml`; update CHANGELOG.md; bump README.md version | -| New or changed workflow | `docs/workflows/.md` | -| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | -| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it | - ---- - -## Key Constraints - -- Never commit directly to `main` — all changes go via PR, squash-merged -- Never skip the FILE INFORMATION block on a new file -- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files -- Never hardcode version numbers in body text — update `README.md` and let automation propagate -- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` -- Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync - -## Update Server Priority -In , Gitea must be priority 1 and GitHub priority 2. Never reverse this. diff --git a/templates/mokogitea/copilot-instructions.md.template b/templates/mokogitea/copilot-instructions.md.template deleted file mode 100644 index eaa2eac..0000000 --- a/templates/mokogitea/copilot-instructions.md.template +++ /dev/null @@ -1,328 +0,0 @@ - - -> [!IMPORTANT] -> **🔧 AI Self-Update Required on First Use** -> -> This file may contain unfilled `{{TOKEN}}` placeholders. The mokocli bulk-sync system -> replaces them automatically at sync time; if you can still see them, sync has not yet run for -> this repository — or this is a brand-new repo. -> -> **On your very first interaction in this repository, before answering any other question:** -> -> 1. Check whether any `{{TOKEN}}` placeholders remain in this file. -> 2. If they do, locate the values as described in the table below and replace every occurrence -> in **both** `.gitea/copilot-instructions.md` and `.gitea/CLAUDE.md`. -> 3. After replacing all tokens, **delete this entire notice block** (from `> [!IMPORTANT]` -> through the closing `> ---` line) in both files. -> 4. Tell the developer: *"I've self-updated the AI context files with this repository's -> values — all set."* -> -> | Placeholder | Where to find the value | -> |---|---| -> | `{{REPO_NAME}}` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) | -> | `{{REPO_URL}}` | Full GitHub URL, e.g. `https://git.mokoconsulting.tech/mokoconsulting-tech/` | -> | `{{PRIMARY_LANGUAGE}}` | The dominant programming language (check file extensions in the repository) | -> | `{{PLATFORM_TYPE}}` | The project type: `PHP library`, `Joomla extension`, `Dolibarr module`, `WaaS site`, etc. — infer from repo structure | -> -> --- - -# {{REPO_NAME}} — GitHub Copilot Custom Instructions - -## What This Repo Is - -This is a **Moko Consulting** repository governed by [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli). All coding standards, workflows, and policies are defined there and enforced here via bulk sync. - -Repository URL: {{REPO_URL}} -Primary language: **{{PRIMARY_LANGUAGE}}** -Platform type: **{{PLATFORM_TYPE}}** - ---- - -## Primary Language - -**{{PRIMARY_LANGUAGE}} is the primary language for this repository.** Follow the conventions documented in [mokocli coding-style-guide](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/coding-style-guide.md). - -YAML uses 2-space indentation (spaces, not tabs). All other text files use tabs per `.editorconfig`. - ---- - -## File Header — Always Required on New Files - -Every new file needs a copyright header as its first content. Use the minimal form unless the file is a policy doc, README, or public API. - -**PHP:** -```php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: {{REPO_NAME}}.Module - * INGROUP: {{REPO_NAME}} - * REPO: {{REPO_URL}} - * PATH: /path/to/file.php - * VERSION: XX.YY.ZZ - * BRIEF: One-line description of purpose - */ - -declare(strict_types=1); -``` - -**Markdown:** -```markdown - -``` - -**YAML / Shell:** Use `#` comments with the same fields. JSON files are exempt. - ---- - -## Version Management - -**`README.md` is the single source of truth for the repository version.** - -- **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03` → `01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it automatically to all badges and `FILE INFORMATION` headers on merge to `main`. -- The `VERSION: XX.YY.ZZ` field in the README.md `FILE INFORMATION` block governs all other version references. -- Update the version in `README.md` only — the `sync-version-on-merge` workflow propagates it automatically to all badges and `FILE INFORMATION` headers on merge to `main`. -- Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `04.00.04`). -- Never hardcode a specific version in document body text — use the badge or FILE INFORMATION header only. - -### Badge Colors - -Each badge type has a designated color — no two types share the same color: - -| Badge | Color | Example | -|-------|-------|---------| -| Version | `blue` | `badge/version-XX.YY.ZZ-blue?logo=v` | -| License | `green` | `badge/license-GPL--3.0-green` | -| PHP | `777BB4` | `badge/PHP-8.2%2B-777BB4?logo=php` | -| Joomla | `red` | `badge/Joomla-5.x-red?logo=joomla` | -| Dolibarr | `red` | `badge/Dolibarr-20.x-red` | -| mokocli | `orange` | `badge/moko--platform-04.06.00-orange` | - ---- - -## GitHub Actions — Token Usage - -Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token). This applies to all `actions/checkout`, `gh` CLI calls, and any step that talks to the GitHub API. - -```yaml -# ✅ Correct -- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - token: ${{ secrets.GH_TOKEN }} - -env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} -``` - -```yaml -# ❌ Wrong — never use these in workflows -token: ${{ github.token }} -token: ${{ secrets.GITHUB_TOKEN }} -``` - -PHP scripts read the token with: `getenv('GH_TOKEN') ?: getenv('GITHUB_TOKEN')` — `GH_TOKEN` is always preferred; `GITHUB_TOKEN` is accepted only as a local-dev fallback. - ---- - -## Composer Package (PHP repositories) - -This repository requires the mokocli enterprise library. The `composer.json` must include: - -```json -{ - "repositories": [ - { - "type": "vcs", - "url": "https://git.mokoconsulting.tech/MokoConsulting/mokocli" - } - ], - "require": { - "mokoconsulting/mokostandards": "^4.0" - } -} -``` - -Run `composer install` after adding the dependency. See [package-installation.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/guide/package-installation.md) for full instructions. - ---- - -## PHP Script Pattern - -All PHP scripts **must** extend `MokoCli\Enterprise\CliFramework`. Never write standalone classes or extend the legacy `CliBase`. - -```php -#!/usr/bin/env php -setDescription('One-line description'); - $this->addArgument('--path', 'Repository root', '.'); - $this->addArgument('--dry-run', 'Preview without writing', false); - } - - protected function run(): int - { - $path = $this->getArgument('--path'); - $dryRun = (bool) $this->getArgument('--dry-run'); - - $this->log('INFO', "Processing: {$path}"); - return 0; - } -} - -$script = new MyScript('my_script', 'One-line description'); -exit($script->execute()); -``` - -**Key rules:** -- Abstract methods to implement: `configure()` and `run()` — **not** `execute()` -- `execute()` is the **public entry point** that orchestrates setup (arg parsing, `initialize()`) and then calls your `run()` implementation; call it at the bottom with `exit($script->execute())` -- Entry point at the bottom: `$script->execute()` — **not** `$script->run()` -- Constructor always takes `(string $name, string $description = '')`; pass the description here — `setDescription()` inside `configure()` is only needed to override it -- `log(string $level, string $message)` — level is the **first** argument (INFO / SUCCESS / WARNING / ERROR) -- `$this->dryRun` and `$this->verbose` are set automatically from `--dry-run` / `--verbose` - ---- - -## Naming Conventions - -| Context | Convention | Example | -|---------|-----------|---------| -| PHP class | `PascalCase` | `MyService` | -| PHP method / function | `camelCase` | `getUserData()` | -| PHP variable | `$snake_case` | `$repo_path` | -| PHP constant | `UPPER_SNAKE_CASE` | `DEFAULT_THRESHOLD` | -| PHP class file | `PascalCase.php` | `ApiClient.php` | -| PHP script file | `snake_case.php` | `check_health.php` | -| YAML workflow | `kebab-case.yml` | `bulk-repo-sync.yml` | -| Markdown doc | `kebab-case.md` | `coding-style-guide.md` | - ---- - -## Commit Messages - -Format: `(): ` — imperative, lower-case subject, no trailing period. - -Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build` - -Examples: -- `feat(module): add user preference caching` -- `fix(api): handle null response from external service` -- `docs(readme): update installation instructions` -- `chore(deps): bump phpunit to 11.x` - ---- - -## Branch Naming - -Approved prefixes: `dev/` · `alpha/` · `beta/` · `rc/` · `version/` · `copilot/` · `dependabot/` - -Pipeline: `dev → [alpha] → [beta] → rc → version/XX → main → dev` -- Alpha and beta are optional — dev can go straight to rc - -- `dev/XX.YY` or `dev/feature-name` — development (version optional) -- `alpha/XX.YY.ZZ` — early internal testing (optional, three-part required) -- `beta/XX.YY.ZZ` — broader external testing (optional, three-part required) -- `rc/XX.YY.ZZ` — release candidate (three-part required) -- `version/XX` — major version integration branch (major only, e.g., `version/04`) -- Release tags: `vXX` (major only — one release per major version) -- Pre-release tags: `development` · `alpha` · `beta` · `release-candidate` -- Patch `00` = development (no release), first release = `01` - -Examples: -- ✅ `dev/04.06` · `dev/new-dashboard` · `alpha/04.06.01` · `beta/04.06.01` · `rc/04.06.01` -- ❌ `feature/my-thing` — rejected by branch protection - ---- - -## Keeping Documentation Current - -Whenever you make code changes, update the corresponding documentation in the same commit or PR. Do not leave docs stale. - -| Change type | Documentation to update | -|-------------|------------------------| -| New or renamed public PHP method | PHPDoc block on the method; `docs/api/` index for that class | -| New or changed CLI script argument | Script's own `--help` text; `docs/api/` or equivalent | -| New or changed GitHub Actions workflow | `docs/workflows/.md` | -| New or changed policy | Corresponding file under `docs/policy/` | -| New library class or major feature | `CHANGELOG.md` entry under `Added` | -| Bug fix | `CHANGELOG.md` entry under `Fixed` | -| Breaking change | `CHANGELOG.md` entry under `Changed`; update `CONTRIBUTING.md` if contributor steps change | -| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | -| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it to all headers and badges on merge | - -If your code change makes any existing doc sentence false or incomplete, fix the doc before closing the PR. - ---- - -## Key Constraints - -- Never commit directly to `main` — all changes go via PR, squash-merged -- Never skip the FILE INFORMATION block on a new file -- Never use bare `catch (\Throwable $e) {}` without logging or re-throwing -- Never hardcode version numbers in body text — update `README.md` and let automation propagate -- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN` -- Never extend `CliBase` in PHP scripts — extend `MokoCli\Enterprise\CliFramework` -- Never call `$script->run()` as the entry point — call `$script->execute()` -- Policy documents and guides must not be mixed - ---- - -## mokocli Reference - -This repository is governed by [mokocli](https://git.mokoconsulting.tech/MokoConsulting/mokocli). Authoritative policies: - -| Document | Purpose | -|----------|---------| -| [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | -| [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | -| [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | -| [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | -| [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | -| [scripting-standards.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/policy/scripting-standards.md) | PHP script requirements and CliFramework usage | -| [package-installation.md](https://git.mokoconsulting.tech/MokoConsulting/mokocli/blob/main/docs/guide/package-installation.md) | Installing `mokoconsulting/mokostandards` via Composer | diff --git a/templates/mokogitea/dependabot.yml.template b/templates/mokogitea/dependabot.yml.template deleted file mode 100644 index 6b7e0b2..0000000 --- a/templates/mokogitea/dependabot.yml.template +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: GitHub.Dependabot -# INGROUP: MokoPlatform.Security -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /templates/mokogitea/dependabot.yml.template -# VERSION: XX.YY.ZZ -# BRIEF: Template Dependabot configuration for governed repositories -# NOTE: Copy to .gitea/dependabot.yml and remove ecosystems that don't apply. -# Keep the github-actions entry — it is required for all governed repos. -# The templates/workflows entry only applies if your repo ships template -# workflow files (.yml) under templates/workflows/. -# .yml.template files are NOT scanned by Dependabot; update them manually. - -version: 2 -updates: - - # ------------------------------------------------------------------------- - # GitHub Actions — REQUIRED for all governed repositories - # Monitors uses: pins in .gitea/workflows/*.yml - # ------------------------------------------------------------------------- - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "monthly" - open-pull-requests-limit: 5 - labels: - - "dependencies" - - "security" - - "automated" - commit-message: - prefix: "chore(deps)" - include: "scope" - reviewers: - - "mokoconsulting-tech/maintainers" - assignees: - - "jmiller" - groups: - github-actions: - patterns: - - "*" - - # ------------------------------------------------------------------------- - # GitHub Actions — template workflows - # Include only if this repo ships template .yml files under templates/workflows/ - # Remove this block if templates/workflows/ does not exist in your repo. - # ------------------------------------------------------------------------- - # - package-ecosystem: "github-actions" - # directory: "/templates/workflows" - # schedule: - # interval: "monthly" - # open-pull-requests-limit: 5 - # labels: - # - "dependencies" - # - "security" - # - "automated" - # - "templates" - # commit-message: - # prefix: "chore(deps)" - # include: "scope" - # reviewers: - # - "mokoconsulting-tech/maintainers" - # assignees: - # - "jmiller" - # groups: - # github-actions-templates: - # patterns: - # - "*" - - # ------------------------------------------------------------------------- - # Composer — PHP repositories - # Remove this block if the repo has no composer.json - # ------------------------------------------------------------------------- - - package-ecosystem: "composer" - directory: "/" - schedule: - interval: "monthly" - open-pull-requests-limit: 5 - labels: - - "dependencies" - - "security" - - "automated" - - "php" - commit-message: - prefix: "chore(deps)" - include: "scope" - reviewers: - - "mokoconsulting-tech/maintainers" - assignees: - - "jmiller" - groups: - composer-dependencies: - patterns: - - "*" - - # ------------------------------------------------------------------------- - # npm — Node.js / JavaScript repositories - # Remove this block if the repo has no package.json - # ------------------------------------------------------------------------- - # - package-ecosystem: "npm" - # directory: "/" - # schedule: - # interval: "monthly" - # open-pull-requests-limit: 5 - # labels: - # - "dependencies" - # - "security" - # - "automated" - # - "javascript" - # commit-message: - # prefix: "chore(deps)" - # include: "scope" - # reviewers: - # - "mokoconsulting-tech/maintainers" - # assignees: - # - "jmiller" - # groups: - # npm-dependencies: - # patterns: - # - "*" - - # ------------------------------------------------------------------------- - # pip — Python repositories - # Remove this block if the repo has no requirements.txt / pyproject.toml - # ------------------------------------------------------------------------- - # - package-ecosystem: "pip" - # directory: "/" - # schedule: - # interval: "monthly" - # open-pull-requests-limit: 5 - # labels: - # - "dependencies" - # - "security" - # - "automated" - # - "python" - # commit-message: - # prefix: "chore(deps)" - # include: "scope" - # reviewers: - # - "mokoconsulting-tech/maintainers" - # assignees: - # - "jmiller" - # groups: - # python-dependencies: - # patterns: - # - "*" diff --git a/templates/mokogitea/override.tf.template b/templates/mokogitea/override.tf.template deleted file mode 100644 index 3f50b2b..0000000 --- a/templates/mokogitea/override.tf.template +++ /dev/null @@ -1,114 +0,0 @@ -# Repository Health Check Override Configuration -# Location: .gitea/override.tf -# -# This file allows repository-specific customization of health checks. -# It overrides the default configuration from mokocli. -# -# AUTO-GENERATED: This file is automatically synced from mokocli -# To customize: Edit this file and it will be preserved on future syncs - -locals { - # Repository-specific metadata - override_metadata = { - repository_name = "REPOSITORY_NAME_PLACEHOLDER" - repository_type = "REPOSITORY_TYPE_PLACEHOLDER" # Options: generic, nodejs, terraform, joomla, dolibarr, standards - override_reason = "Repository-specific health check customization" - last_updated = "AUTO_UPDATED" - auto_synced = true - } - - # Disable specific checks (by check ID) - # Uncomment and add check IDs to disable them - disabled_checks = [ - # Example: "npm-publish-workflow", - # Example: "deployment-secrets-documented", - # Example: "terraform-docs-generation", - ] - - # Adjust point values for specific checks - # Uncomment and modify to change point values - custom_point_values = { - # Example: "ci-workflow-present" = 10 # Increase from default - # Example: "security-scan" = 15 - # Example: "branch-protection-enabled" = 8 - } - - # Custom category point adjustments - # Uncomment to override entire category point totals - custom_category_points = { - # Example: ci_cd_status = 20 - # Example: security = 25 - # Example: workflows = 15 - } - - # Custom threshold percentages - # Uncomment to adjust pass/fail thresholds - custom_thresholds = { - # excellent = 95 # Default: 90 - # good = 80 # Default: 70 - # fair = 60 # Default: 50 - # poor = 0 # Default: 0 - } - - # Additional repository-specific checks - # Add custom checks unique to this repository - additional_checks = { - # Example custom check: - # custom_database_migration = { - # id = "custom-database-migration" - # name = "Database Migration Scripts" - # description = "Check for database migration scripts" - # points = 5 - # check_type = "directory-exists" - # category = "required-folders" - # required = false - # remediation = "Add database migration scripts" - # parameters = { - # directory_path = "db/migrations" - # } - # } - } - - # File sync exclusions - # Files to exclude from automatic sync - sync_exclusions = [ - # Example: ".gitea/workflows/custom-workflow.yml", - # Example: ".gitea/ISSUE_TEMPLATE/custom-template.md", - ] - - # Protected files - # Files that should never be overwritten by sync - protected_files = [ - # Example: ".gitea/workflows/deployment.yml", - # Example: "scripts/release/custom-release.sh", - ] -} - -# Export overrides for consumption by health check validation -output "health_check_overrides" { - description = "Repository-specific health check overrides" - value = { - metadata = local.override_metadata - disabled_checks = local.disabled_checks - custom_points = local.custom_point_values - custom_categories = local.custom_category_points - custom_thresholds = local.custom_thresholds - additional_checks = local.additional_checks - sync_exclusions = local.sync_exclusions - protected_files = local.protected_files - } -} - -# Override configuration summary -output "override_summary" { - description = "Summary of active overrides" - value = { - total_disabled_checks = length(local.disabled_checks) - total_custom_points = length(local.custom_point_values) - total_custom_categories = length(local.custom_category_points) - total_additional_checks = length(local.additional_checks) - total_sync_exclusions = length(local.sync_exclusions) - total_protected_files = length(local.protected_files) - has_custom_thresholds = length(local.custom_thresholds) > 0 - } -} diff --git a/templates/mokogitea/renovate.json b/templates/mokogitea/renovate.json deleted file mode 100644 index f1f37eb..0000000 --- a/templates/mokogitea/renovate.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended", - ":automergeMinor", - ":automergeDigest", - ":semanticCommits", - ":label(dependencies)" - ], - "schedule": ["before 6am on Monday"], - "timezone": "America/Chicago", - "packageRules": [ - { - "matchPackagePatterns": ["*"], - "groupName": "all dependencies", - "groupSlug": "all" - }, - { - "matchDepTypes": ["devDependencies"], - "automerge": true - }, - { - "matchUpdateTypes": ["major"], - "automerge": false, - "labels": ["breaking-change", "dependencies"] - } - ], - "php": { - "enabled": true - }, - "composer": { - "enabled": true - }, - "vulnerabilityAlerts": { - "enabled": true, - "labels": ["security"] - } -} diff --git a/templates/repos/Template-Client b/templates/repos/Template-Client deleted file mode 160000 index ea812e0..0000000 --- a/templates/repos/Template-Client +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ea812e081762b3a7db00f13178c522d894954f0f diff --git a/templates/repos/Template-Generic b/templates/repos/Template-Generic deleted file mode 160000 index 1964c36..0000000 --- a/templates/repos/Template-Generic +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1964c36ffeae26e102b541124f25970f17d6b5f2 diff --git a/templates/repos/Template-Joomla b/templates/repos/Template-Joomla deleted file mode 160000 index 0f52a81..0000000 --- a/templates/repos/Template-Joomla +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0f52a816924156f689a4ffc95317af0f0778b158 diff --git a/templates/repos/Template-MCP b/templates/repos/Template-MCP deleted file mode 160000 index a5d777c..0000000 --- a/templates/repos/Template-MCP +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a5d777c62fce3d6f75eb4fc9713b70f9e597740f diff --git a/templates/required/README.md b/templates/required/README.md deleted file mode 100644 index 18e1faa..0000000 --- a/templates/required/README.md +++ /dev/null @@ -1,170 +0,0 @@ - - -# Required Templates - -This directory contains **REQUIRED** files that must be present in all mokocli-compliant repositories. - -## Overview - -Required templates are essential files that provide core functionality and ensure consistency across all repositories in the organization. These files must be copied to target repositories and kept synchronized with mokocli updates. - -## Required Files - -### 1. setup-labels.sh - -**Status**: ✅ REQUIRED -**Target Location**: `scripts/maintenance/setup-labels.sh` -**Purpose**: Deploy standardized GitHub labels to repository - -**Installation**: -```bash -# Quick install -curl -fsSL https://raw.githubusercontent.com/MokoConsulting/mokocli/main/templates/required/setup-labels.sh > scripts/maintenance/setup-labels.sh -chmod +x scripts/maintenance/setup-labels.sh - -# Or copy from mokocli -cp /path/to/mokocli/templates/required/setup-labels.sh scripts/maintenance/setup-labels.sh -chmod +x scripts/maintenance/setup-labels.sh -``` - -**Usage**: -```bash -# Preview labels (dry-run) -./scripts/maintenance/setup-labels.sh --dry-run - -# Deploy labels -./scripts/maintenance/setup-labels.sh -``` - -**Features**: -- 46 standard labels across 8 categories -- Project types (joomla, dolibarr, generic) -- Languages (php, javascript, typescript, python, css, html) -- Components (documentation, ci-cd, docker, tests, security, dependencies, config, build) -- Workflow (automation, mokocli, needs-review, work-in-progress, breaking-change) -- Priority (critical, high, medium, low) -- Type (bug, feature, enhancement, refactor, chore) -- Status (pending, in-progress, blocked, on-hold, wontfix) -- Size (xs, s, m, l, xl, xxl) -- Health (excellent, good, fair, poor) - -**Requirements**: -- GitHub CLI (gh) installed -- Authenticated with GitHub CLI -- Admin access to repository - -**Validation**: -```bash -# Check if present -[ -f scripts/maintenance/setup-labels.sh ] && echo "✅ Present" || echo "❌ Missing" - -# Check if executable -[ -x scripts/maintenance/setup-labels.sh ] && echo "✅ Executable" || echo "❌ Not executable" - -# Verify labels deployed -gh label list | wc -l # Should show 46+ labels -``` - -## Compliance Checking - -### Check Repository Compliance - -```bash -# Check if all required files are present -required_files=( - "scripts/maintenance/setup-labels.sh" -) - -for file in "${required_files[@]}"; do - if [ -f "$file" ]; then - echo "✅ $file" - else - echo "❌ $file - MISSING" - fi -done -``` - -### Automated Compliance - -Use the mokocli validation scripts: - -```bash -# From mokocli repository -python3 scripts/validate/validate_repo_health.py --check-required-files - -# Or use bulk validation -php scripts/automation/bulk_update_repos.php --validate-only -``` - -## Syncing Updates - -Required files should be kept in sync with mokocli: - -```bash -# Update single file -curl -fsSL https://raw.githubusercontent.com/MokoConsulting/mokocli/main/templates/required/setup-labels.sh > scripts/maintenance/setup-labels.sh - -# Or use bulk sync -cd /path/to/mokocli -./scripts/automation/bulk_update_repos.sh --org MokoConsulting --sync-required -``` - -## Using with GitHub Copilot - -To deploy required files to a repository using GitHub Copilot: - -```markdown -Deploy required mokocli files to this repository. - -Required files to deploy: -1. setup-labels.sh - Label deployment script - -Process: -1. Create scripts/maintenance/ directory if not exists -2. Download setup-labels.sh from MokoConsulting/mokocli -3. Copy to scripts/maintenance/setup-labels.sh -4. Make executable: chmod +x scripts/maintenance/setup-labels.sh -5. Test with dry-run: ./scripts/maintenance/setup-labels.sh --dry-run -6. Deploy labels: ./scripts/maintenance/setup-labels.sh -7. Verify labels in repository settings - -Source: https://git.mokoconsulting.tech/MokoConsulting/mokocli/tree/main/templates/required -``` - -## Future Required Files - -As mokocli evolves, additional required files may be added: - -- **PLANNED**: `.github/labeler.yml` - Auto-labeling configuration -- **PLANNED**: `.editorconfig` - Editor configuration -- **PLANNED**: `scripts/validate/check_compliance.sh` - Standards compliance checker -- **PLANNED**: `.github/workflows/standards-check.yml` - Automated standards validation - -## Support - -- **Documentation**: [Copilot Sync Standards Guide](../../docs/guide/copilot-sync-standards.md) -- **Label Guide**: [Label Deployment Guide](../../docs/guides/label-deployment.md) -- **Issues**: https://git.mokoconsulting.tech/MokoConsulting/mokocli/issues -- **Contact**: hello@mokoconsulting.tech - -## Related Documentation - -- [Copilot Sync Standards Guide](../../docs/guide/copilot-sync-standards.md) -- [Label Deployment Guide](../../docs/guides/label-deployment.md) -- [Bulk Repository Updates](../../docs/guide/bulk-repository-updates.md) -- [Template Catalog](../README.md) - ---- - -**Last Updated**: 2026-01-28 -**Version**: 03.01.00 -**Maintained By**: mokocli Team diff --git a/templates/schemas/README.md b/templates/schemas/README.md deleted file mode 100644 index cabd2f5..0000000 --- a/templates/schemas/README.md +++ /dev/null @@ -1,83 +0,0 @@ - - -# Repository Structure Schemas - -This directory contains template schema files for defining custom repository structures. - -## Overview - -Schema files define the expected structure, files, and directories for different types of repositories in the Moko Consulting ecosystem. - -## Available Schemas - -### template-repository-structure.xml - -A base template schema that can be customized for new repository types. This template includes: - -- Required root-level files (README, LICENSE, CHANGELOG, etc.) -- Core directory structure (docs, scripts, .github) -- Basic GitHub Actions workflow requirements -- Documentation standards - -## Usage - -To create a custom schema: - -1. Copy `template-repository-structure.xml` to your desired location -2. Update the `` section with your repository type details -3. Customize the `` and `` sections -4. Reference appropriate templates from the `/templates/` directory -5. Save with a descriptive name (e.g., `my-project-type.xml`) - -## Schema Structure - -Each schema file contains: - -- **Metadata**: Repository type, platform, maintainer information -- **Root Files**: Required files in the repository root -- **Directories**: Required and optional directory structure -- **Files within Directories**: Specific files expected in each directory - -## Requirement Status Values - -- `required`: Must be present -- `suggested`: Recommended but optional -- `optional`: Nice to have -- `not-allowed`: Should not be present - -## Template References - -Use the `