diff --git a/.gitea/.mokostandards b/.gitea/.mokostandards new file mode 100644 index 0000000..ce1ce33 --- /dev/null +++ b/.gitea/.mokostandards @@ -0,0 +1,48 @@ + + + + + MokoStandards-API + MokoConsulting + MokoStandards Enterprise API — PHP implementation (Composer package: mokoconsulting-tech/enterprise) + GNU General Public License v3 + + + standards-repository + 04.07.00 + https://git.mokoconsulting.tech/MokoConsulting/MokoStandards + 2026-05-02T23:05:55+00:00 + + + HCL + php:>=8.1 + composer + + + + + + + + diff --git a/.gitea/workflows/bulk-repo-sync.yml b/.gitea/workflows/bulk-repo-sync.yml new file mode 100644 index 0000000..420d820 --- /dev/null +++ b/.gitea/workflows/bulk-repo-sync.yml @@ -0,0 +1,136 @@ +# Copyright (C) 2026 Moko Consulting +# SPDX-License-Identifier: GPL-3.0-or-later +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: MokoStandards-API.Automation +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API +# PATH: /.gitea/workflows/bulk-repo-sync.yml +# VERSION: 04.06.12 +# BRIEF: Bulk repo sync — runs from API repo, syncs standards to all governed repos + +name: Bulk Repository Sync + +on: + schedule: + - cron: '0 0 1 * *' + workflow_dispatch: + inputs: + dry_run: + description: 'Preview mode (no changes)' + required: false + type: boolean + default: true + repos: + description: 'Comma-separated repo names (empty = all)' + required: false + type: string + default: '' + exclude: + description: 'Comma-separated repos to skip' + required: false + type: string + default: '' + force: + description: 'Force overwrite protected files' + required: false + type: boolean + default: false + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + bulk-sync: + name: Sync Standards to Repositories + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + extensions: json, mbstring, curl + tools: composer + coverage: none + + - name: Install Dependencies + run: composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader + + - name: Build CLI Arguments + id: args + run: | + ARGS="--org MokoConsulting" + if [ "${{ inputs.dry_run }}" = "true" ] || [ "${{ gitea.event_name }}" = "schedule" ]; then + ARGS="$ARGS --dry-run" + fi + if [ -n "${{ inputs.repos }}" ]; then + ARGS="$ARGS --repos ${{ inputs.repos }}" + fi + if [ -n "${{ inputs.exclude }}" ]; then + ARGS="$ARGS --exclude ${{ inputs.exclude }}" + fi + if [ "${{ inputs.force }}" = "true" ]; then + ARGS="$ARGS --force" + fi + ARGS="$ARGS --yes" + echo "args=$ARGS" >> $GITHUB_OUTPUT + + - name: Run Bulk Sync + run: | + echo "Running: php automation/bulk_sync.php ${{ steps.args.outputs.args }}" + php automation/bulk_sync.php ${{ steps.args.outputs.args }} 2>&1 | tee /tmp/bulk_sync.log + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} + GIT_PLATFORM: gitea + GITEA_URL: https://git.mokoconsulting.tech + GITEA_ORG: MokoConsulting + + - name: Commit Updated Definitions + if: success() && inputs.dry_run != 'true' + run: | + if [ -n "$(git status --porcelain definitions/sync/)" ]; then + git config user.name "gitea-actions[bot]" + git config user.email "gitea-actions[bot]@git.mokoconsulting.tech" + git add definitions/sync/*.def.tf + git commit -m "chore: update synced repository definitions" || true + git push || true + fi + + - name: Enforce Release Channel Tags + if: success() + continue-on-error: true + run: | + echo "Enforcing standard tags on all repos..." + if [ "${{ inputs.dry_run }}" = "true" ]; then + bash automation/enforce_tags.sh --dry-run || echo "Tag enforcement skipped (non-fatal)" + else + bash automation/enforce_tags.sh || echo "Tag enforcement had errors (non-fatal)" + fi + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + GITEA_URL: https://git.mokoconsulting.tech + GITEA_ORG: MokoConsulting + + - name: Upload Sync Log + if: always() && github.server_url == 'https://github.com' + uses: actions/upload-artifact@v4 + with: + name: bulk-sync-log-${{ github.run_number }} + path: /tmp/bulk_sync.log + retention-days: 30 + + - name: Log Summary (Gitea) + if: always() && github.server_url != 'https://github.com' + run: | + if [ -f /tmp/bulk_sync.log ]; then + echo "## Sync Log (last 20 lines)" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + tail -20 /tmp/bulk_sync.log >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + fi diff --git a/.gitignore b/.gitignore index f0c54d5..5d085b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1062,3 +1062,5 @@ terraform.rc # but can be ignored if you want flexibility across different platforms # !.terraform.lock.hcl logs/validation/*.md +profile.ps1 +.mcp.json diff --git a/automation/bulk_joomla_template.php b/automation/bulk_joomla_template.php index 832cb1b..60e2a7c 100644 --- a/automation/bulk_joomla_template.php +++ b/automation/bulk_joomla_template.php @@ -15,11 +15,11 @@ * BRIEF: Bulk scaffold and sync Joomla template repositories * * USAGE - * php api/automation/bulk_joomla_template.php --scaffold --name=MokoTheme - * php api/automation/bulk_joomla_template.php --scaffold --name=MokoTheme --client=administrator - * php api/automation/bulk_joomla_template.php --sync --repos=MokoTheme,MokoDarkTheme - * php api/automation/bulk_joomla_template.php --sync --all - * php api/automation/bulk_joomla_template.php --list + * 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); @@ -717,13 +717,13 @@ class BulkJoomlaTemplate extends CLIApp // ── Sync updates.xml between platforms ─────────────────────────────── /** - * Sync updates.xml (or update.xml) between Gitea and GitHub for Joomla repos. + * 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 api/automation/bulk_joomla_template.php --sync-updates --repos=MokoCassiopeia + * php automation/bulk_joomla_template.php --sync-updates --repos=MokoCassiopeia */ private function syncUpdatesBetweenPlatforms(string $org): int { @@ -788,7 +788,7 @@ class BulkJoomlaTemplate extends CLIApp $name = $repo['name']; $this->log("\n[{$name}]", 'INFO'); - // Try both update.xml and updates.xml filenames + // 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'); @@ -849,7 +849,7 @@ class BulkJoomlaTemplate extends CLIApp /** * Find the updates file on both platforms, return the one with the higher version. * - * Checks both `updates.xml` and `update.xml` filenames. + * Checks both `updates.xml` and `updates.xml` filenames. * Returns the content from the platform with the newer . * Gitea wins ties (primary platform). * @@ -861,7 +861,7 @@ class BulkJoomlaTemplate extends CLIApp string $org, string $name ): ?array { - $candidates = ['updates.xml', 'update.xml']; + $candidates = ['updates.xml', 'updates.xml']; $found = []; // platform => [name, content, version] foreach (['gitea' => $gitea, 'github' => $github] as $platform => $adapter) { diff --git a/automation/bulk_sync.php b/automation/bulk_sync.php index 0503c9c..ff525fc 100755 --- a/automation/bulk_sync.php +++ b/automation/bulk_sync.php @@ -379,6 +379,7 @@ class BulkSync extends CLIApp // (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(); } @@ -419,7 +420,7 @@ class BulkSync extends CLIApp $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: api/lib/Enterprise/RepositorySynchronizer.php", 'ERROR'); + $this->log("Location: lib/Enterprise/RepositorySynchronizer.php", 'ERROR'); $this->log("Method: processRepository()", 'ERROR'); $this->log("", 'ERROR'); $this->log("Required Implementation:", 'ERROR'); @@ -508,7 +509,7 @@ class BulkSync extends CLIApp ]); $script = basename(__FILE__); $this->log("💾 Checkpoint saved. To resume once the issue is resolved, run:", 'INFO'); - $this->log(" php api/automation/{$script} --resume [same flags as before]", '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'); } @@ -958,8 +959,51 @@ class BulkSync extends CLIApp } /** - * Create a tracking issue in the target repository after a successful sync. + * 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 @@ -1020,6 +1064,39 @@ class BulkSync extends CLIApp * MokoStandards 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'; @@ -1058,7 +1135,8 @@ class BulkSync extends CLIApp // Dedent heredoc $body = preg_replace('/^ /m', '', $body); - $labels = ['standards-update', 'mokostandards', 'type: chore', 'automation']; + $labelNames = ['standards-update', 'mokostandards', 'type: chore', 'automation']; + $labels = $this->resolveLabelIds($org, $repo, $labelNames); try { // Check for an existing tracking issue (any state so we can reopen closed ones) @@ -1072,7 +1150,7 @@ class BulkSync extends CLIApp if (!empty($existing) && isset($existing[0]['number'])) { $num = $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']]; + $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; if (($existing[0]['state'] ?? 'open') === 'closed') { $patch['state'] = 'open'; } @@ -1087,7 +1165,7 @@ class BulkSync extends CLIApp 'title' => $title, 'body' => $body, 'labels' => $labels, - 'assignees' => ['jmiller-moko'], + 'assignees' => ['jmiller'], ]); $num = $issue['number'] ?? '?'; $this->log(" 📋 Tracking issue #{$num} created in {$repo}", 'INFO'); @@ -1211,11 +1289,12 @@ class BulkSync extends CLIApp 'direction'=> 'desc', ]); - $labels = ['sync-report', 'mokostandards', 'type: chore', 'automation']; + $labelNames = ['sync-report', 'mokostandards', 'type: chore', 'automation']; + $labels = $this->resolveLabelIds($org, 'MokoStandards', $labelNames); if (!empty($existing) && isset($existing[0]['number'])) { $issueNumber = $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']]; + $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; if (($existing[0]['state'] ?? 'open') === 'closed') { $patch['state'] = 'open'; } @@ -1229,7 +1308,7 @@ class BulkSync extends CLIApp 'title' => $title, 'body' => $body, 'labels' => $labels, - 'assignees' => ['jmiller-moko'], + 'assignees' => ['jmiller'], ]); $issueNumber = $issue['number'] ?? '?'; $this->log("📋 Sync report issue created: {$org}/MokoStandards#{$issueNumber}", 'INFO'); @@ -1276,7 +1355,7 @@ class BulkSync extends CLIApp 1. Check the local audit log or re-run with `--repos=` to see the specific error. 2. Fix the underlying issue (API token, rate limit, branch protection, etc.). - 3. Re-run: `php api/automation/bulk_sync.php --org={$org} --repos= --force --yes` + 3. Re-run: `php automation/bulk_sync.php --org={$org} --repos= --force --yes` 4. Close this issue once all repos are synced successfully. --- @@ -1296,7 +1375,7 @@ class BulkSync extends CLIApp if (!empty($existing) && isset($existing[0]['number'])) { $num = $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']]; + $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; if (($existing[0]['state'] ?? 'open') === 'closed') { $patch['state'] = 'open'; } @@ -1306,8 +1385,8 @@ class BulkSync extends CLIApp $issue = $this->api->post("/repos/{$org}/MokoStandards/issues", [ 'title' => $title, 'body' => $body, - 'labels' => ['sync-failure'], - 'assignees' => ['jmiller-moko'], + 'labels' => $this->resolveLabelIds($org, 'MokoStandards', ['sync-failure']), + 'assignees' => ['jmiller'], ]); $num = $issue['number'] ?? '?'; $this->log("🚨 Failure issue created: {$org}/MokoStandards#{$num}", 'WARN'); diff --git a/automation/enforce_tags.sh b/automation/enforce_tags.sh new file mode 100755 index 0000000..9d4f329 --- /dev/null +++ b/automation/enforce_tags.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# ============================================================================= +# enforce_tags.sh — Ensure all repos have the 5 standard release channel tags +# +# Standard tags: development, alpha, beta, release-candidate, stable +# Also removes non-standard tags (keeps vXX production tags) +# +# Usage: +# GA_TOKEN=xxx ./enforce_tags.sh [--dry-run] [--repos repo1,repo2] +# +# Called by: bulk-repo-sync.yml, infrastructure-tests/mirror-check.yml +# ============================================================================= +set -euo pipefail + +GITEA_URL="${GITEA_URL:-https://git.mokoconsulting.tech}" +ORG="${GITEA_ORG:-MokoConsulting}" +TOKEN="${GA_TOKEN:?GA_TOKEN required}" +DRY_RUN=false +FILTER_REPOS="" + +STANDARD_TAGS=("development" "alpha" "beta" "release-candidate" "stable") + +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) DRY_RUN=true; shift ;; + --repos) FILTER_REPOS="$2"; shift 2 ;; + *) shift ;; + esac +done + +api() { + local method="$1" path="$2" data="${3:-}" + local args=(-sf -H "Authorization: token $TOKEN" -H "Content-Type: application/json" -X "$method") + [[ -n "$data" ]] && args+=(-d "$data") + curl "${args[@]}" "$GITEA_URL/api/v1$path" 2>/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_mokostandards_xml.php b/automation/enrich_mokostandards_xml.php new file mode 100644 index 0000000..69c830e --- /dev/null +++ b/automation/enrich_mokostandards_xml.php @@ -0,0 +1,308 @@ +#!/usr/bin/env php + + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Enrich XML .mokostandards manifests with repo-specific build, deploy, and script details. + * + * Runs AFTER push_mokostandards_xml.php. Clones each repo, inspects its contents, + * and updates the manifest with discovered build/deploy/scripts config. + * + * Usage: + * php automation/enrich_mokostandards_xml.php [--dry-run] [--repo NAME] [--skip NAME,NAME] + * + * 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'; + +use MokoEnterprise\MokoStandardsParser; + +$giteaUrl = rtrim(getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech', '/'); +$giteaOrg = getenv('GITEA_ORG') ?: 'MokoConsulting'; +$token = getenv('GA_TOKEN') ?: getenv('GH_TOKEN') ?: ''; + +$dryRun = in_array('--dry-run', $argv, true); +$repoFilter = null; +$skipRepos = []; +foreach ($argv as $i => $arg) { + if ($arg === '--repo' && isset($argv[$i + 1])) $repoFilter = $argv[$i + 1]; + if ($arg === '--skip' && isset($argv[$i + 1])) $skipRepos = array_map('trim', explode(',', $argv[$i + 1])); +} + +$parser = new MokoStandardsParser(); +$tmpBase = sys_get_temp_dir() . '/moko-enrich-' . getmypid(); + +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)]; +} + +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); +} + +function gitCmd(string $workDir, string ...$args): array { + $cmd = 'git'; + foreach ($args as $a) $cmd .= ' ' . escapeshellarg($a); + return safeExec($cmd, $workDir); +} + +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; +} + +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; +} + +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 = MokoStandardsParser::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'])) { + $build = $dom->createElementNS($ns, 'build'); + $b = $enrichment['build']; + foreach (['language', 'runtime'] as $f) { if (isset($b[$f])) $build->appendChild($dom->createElementNS($ns, $f, htmlspecialchars($b[$f], ENT_XML1))); } + if (isset($b['package_type'])) $build->appendChild($dom->createElementNS($ns, 'package-type', htmlspecialchars($b['package_type'], ENT_XML1))); + if (isset($b['entry_point'])) $build->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))); } + $build->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); + } + $build->appendChild($deps); + } + $root->appendChild($build); + } + + 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(); +} + +// ── Main ───────────────────────────────────────────────────────────────── +echo "=== MokoStandards XML Manifest Enrichment ===\n"; +echo "Mode: " . ($dryRun ? "DRY RUN" : "LIVE") . "\n"; +if (!empty($skipRepos)) echo "Skipping: " . implode(', ', $skipRepos) . "\n"; +echo "\n"; + +if (empty($token)) { fprintf(STDERR, "ERROR: GA_TOKEN required\n"); exit(1); } + +$repos = 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] = safeExec('git clone --depth 1 --branch ' . escapeshellarg($defaultBranch) . ' ' . escapeshellarg($authedUrl) . ' ' . escapeshellarg($workDir)); + if ($ret !== 0) { echo "FAIL (clone)\n"; $stats['failed']++; continue; } + + $manifestPath = "{$workDir}/.gitea/.mokostandards"; + if (!file_exists($manifestPath) || !str_contains(file_get_contents($manifestPath), 'extractPlatform($existingXml) ?? 'default-repository'; + $enrichment = inspectRepo($workDir, $platform); + + if (!isset($enrichment['build'])) $enrichment['build'] = []; + $enrichment['build']['language'] = $enrichment['build']['language'] ?? $repo['language'] ?? MokoStandardsParser::platformLanguage($platform); + $enrichment['build']['package_type'] = $enrichment['build']['package_type'] ?? MokoStandardsParser::platformPackageType($platform); + + $enrichedXml = enrichManifestXml($existingXml, $enrichment); + $dc = count($enrichment['deploy'] ?? []); + $sc = count($enrichment['scripts'] ?? []); + $details = "deploy={$dc} scripts={$sc}"; + + if ($dryRun) { echo "WOULD ENRICH [{$details}]\n"; $stats['enriched']++; rmTree($workDir); continue; } + + file_put_contents($manifestPath, $enrichedXml); + gitCmd($workDir, 'config', 'user.name', 'gitea-actions[bot]'); + gitCmd($workDir, 'config', 'user.email', 'gitea-actions[bot]@git.mokoconsulting.tech'); + gitCmd($workDir, 'add', '.gitea/.mokostandards'); + + [$cr, $co] = gitCmd($workDir, 'commit', '-m', "chore: enrich .mokostandards with build/deploy/scripts\n\nAuto-detected: {$details}"); + if ($cr !== 0) { echo "SKIP (no diff)\n"; $stats['skipped']++; rmTree($workDir); continue; } + + [$pr] = gitCmd($workDir, 'push', 'origin', $defaultBranch); + if ($pr !== 0) { echo "FAIL (push)\n"; $stats['failed']++; } + else { echo "ENRICHED [{$details}]\n"; $stats['enriched']++; } + + rmTree($workDir); +} + +@rmdir($tmpBase); +echo "\n=== Summary ===\nEnriched: {$stats['enriched']}\nSkipped: {$stats['skipped']}\nFailed: {$stats['failed']}\n"; diff --git a/automation/migrate_to_gitea.php b/automation/migrate_to_gitea.php index 6c9f24d..f0ab6b0 100644 --- a/automation/migrate_to_gitea.php +++ b/automation/migrate_to_gitea.php @@ -15,10 +15,10 @@ * BRIEF: Migrate repositories from GitHub to self-hosted Gitea instance * * USAGE - * php api/automation/migrate_to_gitea.php --dry-run - * php api/automation/migrate_to_gitea.php --repos MokoCRM MokoDoliMods - * php api/automation/migrate_to_gitea.php --exclude MokoStandards --skip-archived - * php api/automation/migrate_to_gitea.php --resume + * php automation/migrate_to_gitea.php --dry-run + * php automation/migrate_to_gitea.php --repos MokoCRM MokoDoliMods + * php automation/migrate_to_gitea.php --exclude MokoStandards --skip-archived + * php automation/migrate_to_gitea.php --resume */ declare(strict_types=1); diff --git a/automation/push_files.php b/automation/push_files.php index abba031..2c671b9 100644 --- a/automation/push_files.php +++ b/automation/push_files.php @@ -358,7 +358,7 @@ class PushFiles extends CLIApp $prBody = $this->buildPRBody($entries); $pr = $this->adapter->createPullRequest( $org, $repo, $prTitle, $branch, $defaultBranch, $prBody, - ['assignees' => ['jmiller-moko']] + ['assignees' => ['jmiller']] ); $prNumber = $pr['number'] ?? null; $this->log(" 📋 PR #{$prNumber} created", 'INFO'); @@ -512,7 +512,7 @@ class PushFiles extends CLIApp if (!empty($existing) && isset($existing[0]['number'])) { $num = $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']]; + $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; if (($existing[0]['state'] ?? 'open') === 'closed') { $patch['state'] = 'open'; } @@ -526,7 +526,7 @@ class PushFiles extends CLIApp 'title' => $title, 'body' => $body, 'labels' => $labels, - 'assignees' => ['jmiller-moko'], + 'assignees' => ['jmiller'], ]); $num = $issue['number'] ?? null; $this->log(" 📋 Tracking issue #{$num} created in {$repo}", 'INFO'); @@ -590,7 +590,7 @@ class PushFiles extends CLIApp 1. Check the output above for the specific error per repo. 2. Fix the underlying issue (API token, branch permissions, file path, etc.). - 3. Re-run: `php api/automation/push_files.php --org={$org} --repos= --files= --yes` + 3. Re-run: `php automation/push_files.php --org={$org} --repos= --files= --yes` 4. Close this issue once resolved. --- @@ -610,7 +610,7 @@ class PushFiles extends CLIApp if (!empty($existing) && isset($existing[0]['number'])) { $num = $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']]; + $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; if (($existing[0]['state'] ?? 'open') === 'closed') { $patch['state'] = 'open'; } @@ -621,7 +621,7 @@ class PushFiles extends CLIApp 'title' => $title, 'body' => $body, 'labels' => ['push-failure'], - 'assignees' => ['jmiller-moko'], + 'assignees' => ['jmiller'], ]); $num = $issue['number'] ?? '?'; $this->log("🚨 Failure issue created: {$org}/MokoStandards#{$num}", 'WARN'); diff --git a/automation/push_mokostandards_xml.php b/automation/push_mokostandards_xml.php new file mode 100644 index 0000000..23587e9 --- /dev/null +++ b/automation/push_mokostandards_xml.php @@ -0,0 +1,308 @@ +#!/usr/bin/env php + + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Push XML .mokostandards manifest to all governed repositories. + * + * Uses git SSH to bypass the Gitea reverse-proxy WAF that blocks + * API requests to paths containing ".gitea". + * + * Usage: + * php automation/push_mokostandards_xml.php [--dry-run] [--repo NAME] [--force] + */ + +declare(strict_types=1); + +require_once __DIR__ . '/../vendor/autoload.php'; + +use MokoEnterprise\MokoStandardsParser; + +// ── Configuration ──────────────────────────────────────────────────────── +$giteaUrl = rtrim(getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech', '/'); +$giteaOrg = getenv('GITEA_ORG') ?: 'MokoConsulting'; +$token = getenv('GA_TOKEN') ?: getenv('GH_TOKEN') ?: ''; +$sshBase = 'ssh://gitea@git.mokoconsulting.tech:2222'; + +// ── CLI args ───────────────────────────────────────────────────────────── +$dryRun = in_array('--dry-run', $argv, true); +$force = in_array('--force', $argv, true); +$repoFilter = null; +$skipRepos = []; +foreach ($argv as $i => $arg) { + if ($arg === '--repo' && isset($argv[$i + 1])) { + $repoFilter = $argv[$i + 1]; + } + if ($arg === '--skip' && isset($argv[$i + 1])) { + $skipRepos = array_map('trim', explode(',', $argv[$i + 1])); + } +} + +$parser = new MokoStandardsParser(); +$tmpBase = sys_get_temp_dir() . '/moko-manifest-push-' . getmypid(); + +// ── Platform detection heuristics (mirrors RepositorySynchronizer) ─────── +$CRM_PLATFORM_REPOS = ['MokoDolibarr', 'MokoDoliMods']; + +function detectPlatform(array $repo): string { + global $CRM_PLATFORM_REPOS; + $name = $repo['name'] ?? ''; + $nameLower = strtolower($name); + $description = strtolower($repo['description'] ?? ''); + $topics = $repo['topics'] ?? []; + + if (in_array($name, $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'; +} + +/** + * Safe shell execution — uses proc_open with explicit arguments to avoid injection. + * @return array{int, string} + */ +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)]; +} + +/** Recursively remove a directory (cross-platform). */ +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 { + // Clear read-only flag (git objects on Windows) + @chmod($file->getPathname(), 0777); + @unlink($file->getPathname()); + } + } + @rmdir($dir); +} + +/** + * Run a git command safely in a given working directory. + * @return array{int, string} + */ +function gitCmd(string $workDir, string ...$args): array { + $cmd = 'git'; + foreach ($args as $a) { + $cmd .= ' ' . escapeshellarg($a); + } + return safeExec($cmd, $workDir); +} + +// ── Fetch all repos via API ────────────────────────────────────────────── +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) { + fprintf(STDERR, "API error (HTTP %d) fetching repos page %d\n", $code, $page); + break; + } + + $batch = json_decode($body, true); + if (empty($batch)) break; + $repos = array_merge($repos, $batch); + $page++; + } while (count($batch) >= 50); + + return $repos; +} + +// ── Main ───────────────────────────────────────────────────────────────── +echo "=== MokoStandards XML Manifest Push ===\n"; +echo "Org: {$giteaOrg}\n"; +echo "Mode: " . ($dryRun ? "DRY RUN" : "LIVE") . "\n"; +if ($repoFilter) echo "Filter: {$repoFilter}\n"; +echo "\n"; + +if (empty($token)) { + fprintf(STDERR, "ERROR: GA_TOKEN or GH_TOKEN environment variable required\n"); + exit(1); +} + +$repos = 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 = detectPlatform($repo); + $defaultBranch = $repo['default_branch'] ?? 'main'; + // Prefer HTTPS with token (SSH port 2222 may be blocked); fall back to SSH + $httpsUrl = $repo['clone_url'] ?? "{$giteaUrl}/{$giteaOrg}/{$name}.git"; + // Embed token in HTTPS URL for push auth + $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'] ?? MokoStandardsParser::platformLanguage($platform), + 'package_type' => MokoStandardsParser::platformPackageType($platform), + 'last_synced' => date('c'), + ]); + + if ($dryRun) { + echo "WOULD WRITE ({$platform})\n"; + $stats['created']++; + continue; + } + + // Clone shallow via HTTPS (token-authed) + $workDir = "{$tmpBase}/{$name}"; + @mkdir($workDir, 0755, true); + + [$ret, $out] = 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}/.gitea/.mokostandards"; + $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']++; + 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 .mokostandards manifest' + : 'chore: update .mokostandards to XML format'; + if (!empty($legacyDeleted)) { + $commitMsg .= "\n\nRemoved legacy: " . implode(', ', $legacyDeleted); + } + + gitCmd($workDir, 'config', 'user.name', 'gitea-actions[bot]'); + gitCmd($workDir, 'config', 'user.email', 'gitea-actions[bot]@git.mokoconsulting.tech'); + gitCmd($workDir, 'add', '.gitea/.mokostandards'); + foreach ($legacyDeleted as $lf) { + gitCmd($workDir, 'add', $lf); + } + + [$commitRet, $commitOut] = gitCmd($workDir, 'commit', '-m', $commitMsg); + if ($commitRet !== 0 && str_contains($commitOut, 'nothing to commit')) { + echo "SKIP (no changes)\n"; + $stats['skipped']++; + rmTree($workDir); + continue; + } + if ($commitRet !== 0) { + echo "FAIL (commit)\n"; + fprintf(STDERR, " %s\n", $commitOut); + $stats['failed']++; + rmTree($workDir); + continue; + } + + [$pushRet, $pushOut] = 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 + 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"; diff --git a/cli/archive_repo.php b/cli/archive_repo.php index 65ed6b8..698955a 100644 --- a/cli/archive_repo.php +++ b/cli/archive_repo.php @@ -15,9 +15,9 @@ * BRIEF: Gracefully retire a governed repository — archive, close issues/PRs, remove sync def * * USAGE - * php api/cli/archive_repo.php --repo MokoOldModule - * php api/cli/archive_repo.php --repo MokoOldModule --dry-run - * php api/cli/archive_repo.php --repo MokoOldModule --skip-close # Archive only, keep issues open + * php cli/archive_repo.php --repo MokoOldModule + * php cli/archive_repo.php --repo MokoOldModule --dry-run + * php cli/archive_repo.php --repo MokoOldModule --skip-close # Archive only, keep issues open */ declare(strict_types=1); @@ -143,7 +143,7 @@ if (!$dryRun) { "## Repository Archived\n\n**Repository:** `{$org}/{$repoName}`\n**Archived:** {$now}\n**Platform:** {$platformName}\n**Sync definition removed:** yes\n\n---\n*Auto-created by `archive_repo.php`*\n", [ 'labels' => ['type: chore', 'automation', 'archived'], - 'assignees' => ['jmiller-moko'], + 'assignees' => ['jmiller'], ] ); if (isset($issue['number'])) { echo " Archival record: MokoStandards#{$issue['number']}\n"; } diff --git a/cli/create_project.php b/cli/create_project.php index 85d6e5e..ae4bb9d 100644 --- a/cli/create_project.php +++ b/cli/create_project.php @@ -15,10 +15,10 @@ * BRIEF: Create baseline GitHub Projects for repositories with standard fields and views * * USAGE - * php api/cli/create_project.php --repo MokoCRM # Auto-detect type, create project - * php api/cli/create_project.php --repo MokoCRM --type dolibarr # Force type - * php api/cli/create_project.php --org mokoconsulting-tech --all # All repos without projects - * php api/cli/create_project.php --repo MokoCRM --dry-run # Preview without changes + * php cli/create_project.php --repo MokoCRM # Auto-detect type, create project + * php cli/create_project.php --repo MokoCRM --type dolibarr # Force type + * php cli/create_project.php --org mokoconsulting-tech --all # All repos without projects + * php cli/create_project.php --repo MokoCRM --dry-run # Preview without changes */ declare(strict_types=1); @@ -385,7 +385,7 @@ function createProject( updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, - readme: "Managed by MokoStandards. Run `php api/cli/create_project.php` to regenerate." + readme: "Managed by MokoStandards. Run `php cli/create_project.php` to regenerate." }) { projectV2 { id } } diff --git a/cli/create_repo.php b/cli/create_repo.php index 5e59bda..2812d80 100644 --- a/cli/create_repo.php +++ b/cli/create_repo.php @@ -15,9 +15,9 @@ * BRIEF: Scaffold a new governed repository with full MokoStandards baseline * * USAGE - * php api/cli/create_repo.php --name MokoNewModule --type dolibarr --description "My new module" - * php api/cli/create_repo.php --name MokoNewModule --type joomla --private - * php api/cli/create_repo.php --name MokoNewModule --type generic --dry-run + * php cli/create_repo.php --name MokoNewModule --type dolibarr --description "My new module" + * php cli/create_repo.php --name MokoNewModule --type joomla --private + * php cli/create_repo.php --name MokoNewModule --type generic --dry-run */ declare(strict_types=1); @@ -229,7 +229,7 @@ if (!$dryRun) { if (file_exists($syncScript)) { passthru("php " . escapeshellarg($syncScript) . " --repos " . escapeshellarg($name) . " --force --yes"); } else { - echo " Run manually: php api/automation/bulk_sync.php --repos {$name} --force --yes\n"; + echo " Run manually: php automation/bulk_sync.php --repos {$name} --force --yes\n"; } } else { echo " (dry-run) would run initial sync\n"; @@ -242,7 +242,7 @@ if (!$dryRun) { if (file_exists($projectScript)) { passthru("php " . escapeshellarg($projectScript) . " --repo " . escapeshellarg($name) . " --type " . escapeshellarg($type)); } else { - echo " Run manually: php api/cli/create_project.php --repo {$name} --type {$type}\n"; + echo " Run manually: php cli/create_project.php --repo {$name} --type {$type}\n"; } } else { echo " (dry-run) would create Project\n"; diff --git a/cli/joomla_release.php b/cli/joomla_release.php index 3ceeb0a..487e29b 100644 --- a/cli/joomla_release.php +++ b/cli/joomla_release.php @@ -15,10 +15,10 @@ * BRIEF: Joomla release pipeline — build ZIP+tar.gz, upload to GitHub Release, update updates.xml * * USAGE - * php api/cli/joomla_release.php --repo MokoCassiopeia --stability stable - * php api/cli/joomla_release.php --repo MokoCassiopeia --stability development - * php api/cli/joomla_release.php --repo MokoCassiopeia --stability rc --dry-run - * php api/cli/joomla_release.php --path /local/repo --stability stable + * php cli/joomla_release.php --repo MokoCassiopeia --stability stable + * php cli/joomla_release.php --repo MokoCassiopeia --stability development + * php cli/joomla_release.php --repo MokoCassiopeia --stability rc --dry-run + * php cli/joomla_release.php --path /local/repo --stability stable */ declare(strict_types=1); diff --git a/cli/release.php b/cli/release.php index 05b29e1..0a0e7c2 100644 --- a/cli/release.php +++ b/cli/release.php @@ -13,10 +13,10 @@ * BRIEF: Automate the MokoStandards version branch release flow * * USAGE - * php api/cli/release.php # Release current version - * php api/cli/release.php --bump minor # Bump minor, then release - * php api/cli/release.php --bump major # Bump major, then release - * php api/cli/release.php --dry-run # Preview without changes + * php cli/release.php # Release current version + * php cli/release.php --bump minor # Bump minor, then release + * php cli/release.php --bump major # Bump major, then release + * php cli/release.php --dry-run # Preview without changes */ declare(strict_types=1); @@ -30,7 +30,7 @@ foreach ($argv as $i => $arg) { } $repoRoot = dirname(__DIR__, 2); -$syncFile = "{$repoRoot}/api/lib/Enterprise/RepositorySynchronizer.php"; +$syncFile = "{$repoRoot}/lib/Enterprise/RepositorySynchronizer.php"; // Check both workflow directories for the bulk-repo-sync workflow $bulkSyncFile = file_exists("{$repoRoot}/.gitea/workflows/bulk-repo-sync.yml") ? "{$repoRoot}/.gitea/workflows/bulk-repo-sync.yml" diff --git a/cli/sync_rulesets.php b/cli/sync_rulesets.php index 47fdef6..6d1ab67 100644 --- a/cli/sync_rulesets.php +++ b/cli/sync_rulesets.php @@ -15,10 +15,10 @@ * BRIEF: Apply branch protection rules to all repos via platform adapter * * USAGE - * php api/cli/sync_rulesets.php # Apply to all repos - * php api/cli/sync_rulesets.php --repo MokoCRM # Single repo - * php api/cli/sync_rulesets.php --dry-run # Preview only - * php api/cli/sync_rulesets.php --delete # Remove then re-apply + * php cli/sync_rulesets.php # Apply to all repos + * php cli/sync_rulesets.php --repo MokoCRM # Single repo + * php cli/sync_rulesets.php --dry-run # Preview only + * php cli/sync_rulesets.php --delete # Remove then re-apply * * NOTE: On GitHub, this creates rulesets via the rulesets API. * On Gitea, this creates branch_protections via the branch protection API. diff --git a/composer.json b/composer.json index 5fce8b0..72eef53 100644 --- a/composer.json +++ b/composer.json @@ -13,26 +13,26 @@ "minimum-stability": "stable", "prefer-stable": true, "require": { - "php": ">=8.1", - "ext-json": "*", "ext-curl": "*", + "ext-json": "*", + "ext-zip": "*", "guzzlehttp/guzzle": "^7.8", "monolog/monolog": "^3.5", + "php": ">=8.1", + "phpseclib/phpseclib": "^3.0", + "psr/cache": "^3.0", + "psr/http-client": "^1.0", + "psr/log": "^3.0", + "symfony/cache": "^6.4", "symfony/console": "^6.4", - "symfony/yaml": "^6.4", "symfony/filesystem": "^6.4", - "symfony/process": "^6.4", "symfony/finder": "^6.4", "symfony/http-foundation": "^6.4", + "symfony/process": "^6.4", "symfony/routing": "^6.4", - "symfony/cache": "^6.4", + "symfony/yaml": "^6.4", "twig/twig": "^3.8", - "vlucas/phpdotenv": "^5.6", - "psr/log": "^3.0", - "psr/http-client": "^1.0", - "psr/cache": "^3.0", - "phpseclib/phpseclib": "^3.0", - "ext-zip": "*" + "vlucas/phpdotenv": "^5.6" }, "require-dev": { "phpunit/phpunit": "^10.5", @@ -73,8 +73,8 @@ ], "scripts": { "test": "phpunit", - "phpcs": "phpcs --standard=phpcs.xml api/", - "phpstan": "phpstan analyse -c phpstan.neon api/", + "phpcs": "phpcs --standard=phpcs.xml lib/ validate/ automation/", + "phpstan": "phpstan analyse -c phpstan.neon lib/ validate/ automation/", "psalm": "psalm --config=psalm.xml", "check": [ "@phpcs", diff --git a/definitions/default/client-site.tf b/definitions/default/client-site.tf new file mode 100644 index 0000000..5f29420 --- /dev/null +++ b/definitions/default/client-site.tf @@ -0,0 +1,223 @@ +/** + * Client Joomla Site Structure Definition + * Standard repository structure for client Joomla site projects + * + * Copyright (C) 2026 Moko Consulting + * SPDX-License-Identifier: GPL-3.0-or-later + * Version: 01.00.00 + * Schema Version: 1.0 + */ + +locals { + repository_structure = { + metadata = { + name = "Client Joomla Site" + description = "Standard repository structure for client Joomla site projects (overrides, media, configuration)" + repository_type = "client-site" + platform = "mokowaas" + last_updated = "2026-05-04T00:00:00Z" + maintainer = "Moko Consulting" + version = "01.00.00" + schema_version = "1.0" + template_repo = "MokoConsulting/MokoStandards-Template-Client" + } + + root_files = [ + { + name = "README.md" + description = "Client project documentation" + required = true + always_overwrite = false + }, + { + name = "LICENSE" + description = "License file (GPL-3.0-or-later)" + required = true + }, + { + name = "CHANGELOG.md" + description = "Version history and changes" + required = true + }, + { + name = "SECURITY.md" + description = "Security policy and vulnerability reporting" + required = true + always_overwrite = true + }, + { + name = "CODE_OF_CONDUCT.md" + description = "Community code of conduct" + required = true + always_overwrite = true + }, + { + name = "CONTRIBUTING.md" + description = "Contribution guidelines" + required = true + always_overwrite = true + }, + { + name = "Makefile" + description = "Build automation" + required = true + always_overwrite = true + }, + { + name = "composer.json" + description = "PHP dependency management" + required = true + always_overwrite = false + }, + { + name = "phpstan.neon" + description = "PHPStan static analysis config" + required = true + always_overwrite = true + }, + { + name = "codeception.yml" + description = "Codeception test framework config" + required = false + always_overwrite = false + }, + { + name = ".editorconfig" + description = "Editor configuration for consistent coding style" + required = true + always_overwrite = true + }, + { + name = ".gitignore" + description = "Git ignore patterns for client site projects" + required = true + always_overwrite = false + }, + ] + + // NOTE: Client sites do NOT have updates.xml — they are not installable extensions + + subdirectories = [ + { + name = "src" + description = "Site source files — template overrides, custom code, and configuration" + required = true + files = [] + }, + { + name = "src/images" + description = "Site images — branding, headers, event photos, profiles" + required = true + files = [] + }, + { + name = "src/media" + description = "Media assets — uploaded files, documents" + required = true + files = [] + }, + { + name = "docs" + description = "Client-specific documentation — brand reference, deployment, migration plans" + required = true + files = [] + }, + { + name = "scripts" + description = "Deployment and maintenance scripts" + required = false + files = [] + }, + { + name = "tests" + description = "Acceptance and unit tests" + required = false + files = [] + }, + { + name = ".gitea/workflows" + description = "Gitea Actions CI/CD workflows (10 workflows — no update-server)" + required = true + files = [ + { + name = "auto-release.yml" + description = "Stable release on PR merge to main" + required = true + always_overwrite = true + }, + { + name = "pre-release.yml" + description = "Manual pre-release for dev/alpha/beta/rc channels" + required = true + always_overwrite = true + }, + { + name = "ci-joomla.yml" + description = "PHP lint, PHPStan, coding standards" + required = true + always_overwrite = true + }, + { + name = "pr-check.yml" + description = "PR gate — validates code quality before merge" + required = true + always_overwrite = true + }, + { + name = "deploy-manual.yml" + description = "Manual SFTP deploy to selected environment" + required = true + always_overwrite = true + }, + { + name = "repo-health.yml" + description = "Repository health checks" + required = true + always_overwrite = true + }, + { + name = "security-audit.yml" + description = "Dependency vulnerability scanning" + required = true + always_overwrite = true + }, + { + name = "notify.yml" + description = "ntfy push notifications on release success or failure" + required = true + always_overwrite = true + }, + { + name = "cleanup.yml" + description = "Weekly merged branch + old run cleanup" + required = true + always_overwrite = true + }, + { + name = "sync-media.yml" + description = "Bidirectional SFTP sync for images/, files/, media/ between dev and production" + required = true + always_overwrite = true + }, + ] + }, + ] + + // Per-repo variables required for sync-media.yml + required_variables = [ + { name = "DEV_SYNC_HOST", description = "Dev server hostname" }, + { name = "DEV_SYNC_PORT", description = "Dev SSH port (default 22)" }, + { name = "DEV_SYNC_USERNAME", description = "Dev server username" }, + { name = "DEV_SYNC_PATH", description = "Base path on dev server" }, + { name = "PROD_SYNC_HOST", description = "Production server hostname" }, + { name = "PROD_SYNC_PORT", description = "Production SSH port (default 22)" }, + { name = "PROD_SYNC_USERNAME", description = "Production server username" }, + { name = "PROD_SYNC_PATH", description = "Base path on production server" }, + ] + + required_secrets = [ + { name = "DEV_SYNC_KEY", description = "SSH private key for dev server" }, + { name = "PROD_SYNC_KEY", description = "SSH private key for production server" }, + ] + } +} diff --git a/definitions/default/crm-module.tf b/definitions/default/crm-module.tf index 637908a..e34ddd9 100644 --- a/definitions/default/crm-module.tf +++ b/definitions/default/crm-module.tf @@ -167,13 +167,14 @@ EOT audience = "developer" }, { - name = ".mokostandards" - extension = "yml" - description = "MokoStandards governance attachment — links this repo back to the standards source" + name = ".gitea/.mokostandards" + extension = "xml" + description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()" required = true - always_overwrite = true + always_overwrite = false audience = "developer" - template = "templates/configs/mokostandards.yml.template" + template = "managed-by-sync" + source_type = "programmatic" }, { name = "GOVERNANCE.md" diff --git a/definitions/default/crm-platform.tf b/definitions/default/crm-platform.tf index b68190c..3e8a962 100644 --- a/definitions/default/crm-platform.tf +++ b/definitions/default/crm-platform.tf @@ -84,12 +84,13 @@ locals { template = "templates/configs/ftp_ignore" }, { - name = ".mokostandards" - extension = "" - description = "MokoStandards platform identifier" + name = ".gitea/.mokostandards" + extension = "xml" + description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()" required = true - always_overwrite = true - template = "templates/configs/mokostandards.yml.template" + always_overwrite = false + template = "managed-by-sync" + source_type = "programmatic" } ] diff --git a/definitions/default/default-repository.json b/definitions/default/default-repository.json index d5ae941..9407d58 100644 --- a/definitions/default/default-repository.json +++ b/definitions/default/default-repository.json @@ -142,15 +142,24 @@ { "name": ".github", "path": ".github", - "description": "GitHub-specific configuration", + "description": "Gitea/GitHub Actions configuration (Gitea reads .github/workflows natively)", "requirementStatus": "required", - "purpose": "Contains GitHub Actions workflows and configuration", + "purpose": "Contains CI/CD workflows and repository configuration. Gitea is the primary platform; GitHub is backup only.", "subdirectories": [ { "name": "workflows", "path": ".github/workflows", - "description": "GitHub Actions workflows", - "requirementStatus": "suggested" + "description": "CI/CD workflows (Gitea-primary, GitHub-compatible)", + "requirementStatus": "required", + "requiredFiles": [ + "auto-assign.yml", + "auto-dev-issue.yml", + "auto-release.yml", + "branch-freeze.yml", + "changelog-validation.yml", + "repository-cleanup.yml", + "sync-version-on-merge.yml" + ] } ] }, diff --git a/definitions/default/generic-repository.tf b/definitions/default/generic-repository.tf index 38ee157..0d36b5f 100644 --- a/definitions/default/generic-repository.tf +++ b/definitions/default/generic-repository.tf @@ -119,13 +119,14 @@ locals { template = "templates/configs/composer.generic.json" }, { - name = ".mokostandards" - extension = "yml" - description = "MokoStandards governance attachment — links this repo back to the standards source" + name = ".gitea/.mokostandards" + extension = "xml" + description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()" required = true - always_overwrite = true + always_overwrite = false audience = "developer" - template = "templates/configs/mokostandards.yml.template" + template = "managed-by-sync" + source_type = "programmatic" }, { name = "GOVERNANCE.md" diff --git a/definitions/default/github-private-repository.tf b/definitions/default/github-private-repository.tf index 4757c32..9f1a624 100644 --- a/definitions/default/github-private-repository.tf +++ b/definitions/default/github-private-repository.tf @@ -116,12 +116,13 @@ locals { audience = "developer" }, { - name = ".mokostandards.yml" - extension = "yml" - description = "MokoStandards governance marker — identifies this repo as platform=github-private" + name = ".gitea/.mokostandards" + extension = "xml" + description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()" required = true - always_overwrite = true - template = "templates/configs/mokostandards.yml.template" + always_overwrite = false + template = "managed-by-sync" + source_type = "programmatic" } ] diff --git a/definitions/default/joomla-template.tf b/definitions/default/joomla-template.tf index a35083e..ca08e68 100644 --- a/definitions/default/joomla-template.tf +++ b/definitions/default/joomla-template.tf @@ -132,7 +132,7 @@ locals { https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/raw/branch/main/updates.xml - https://raw.githubusercontent.com/mokoconsulting-tech/{{REPO_NAME}}/main/updates.xml + https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/main/updates.xml @@ -179,7 +179,7 @@ locals { https://git.mokoconsulting.tech/mokoconsulting-tech/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{TEMPLATE_SHORT_NAME}}.zip - https://github.com/mokoconsulting-tech/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{TEMPLATE_SHORT_NAME}}.zip + https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{TEMPLATE_SHORT_NAME}}.zip @@ -417,6 +417,83 @@ locals { required = false files = [] }, + { + name = ".gitea/workflows" + description = "Gitea Actions CI/CD workflows" + required = true + files = [ + { + name = "auto-release.yml" + description = "Automated release — builds zip, creates Gitea release, updates SHA in updates.xml. Triggered by push to main (stable) or pre-release tags (development, alpha, beta, rc)" + required = true + always_overwrite = true + template = "workflows/auto-release.yml" + }, + { + name = "ci-joomla.yml" + description = "Continuous integration — PHP linting, PHPStan static analysis, coding standards checks" + required = true + always_overwrite = true + template = "workflows/ci-joomla.yml" + }, + { + name = "pre-release.yml" + description = "Manual pre-release — builds dev/alpha/beta/rc packages with patch version bump" + required = true + always_overwrite = true + template = "workflows/pre-release.yml" + }, + { + name = "deploy-manual.yml" + description = "Manual deployment — allows selecting target environment and branch for on-demand deploys" + required = true + always_overwrite = true + template = "workflows/deploy-manual.yml" + }, + { + name = "repo-health.yml" + description = "Repository health checks — validates required files, structure compliance, and standards alignment" + required = true + always_overwrite = true + template = "workflows/repo-health.yml" + }, + { + name = "update-server.yml" + description = "Update server maintenance — validates updates.xml format and ensures download URLs are reachable" + required = true + always_overwrite = true + template = "workflows/update-server.yml" + }, + { + name = "pr-check.yml" + description = "PR gate — validates PHP syntax, manifest XML, and package build before merge to main" + required = true + always_overwrite = true + template = "workflows/pr-check.yml" + }, + { + name = "security-audit.yml" + description = "Dependency vulnerability scanning — weekly schedule and on PR when lock files change" + required = true + always_overwrite = true + template = "workflows/security-audit.yml" + }, + { + name = "notify.yml" + description = "Push notifications via ntfy on release success or workflow failure" + required = true + always_overwrite = true + template = "workflows/notify.yml" + }, + { + name = "cleanup.yml" + description = "Scheduled cleanup — delete merged branches and old workflow runs weekly" + required = true + always_overwrite = true + template = "workflows/cleanup.yml" + }, + ] + }, ] } } diff --git a/definitions/default/standards-repository.tf b/definitions/default/standards-repository.tf index 0706998..cd4732a 100644 --- a/definitions/default/standards-repository.tf +++ b/definitions/default/standards-repository.tf @@ -199,12 +199,14 @@ locals { audience = "developer" }, { - name = ".mokostandards" - extension = "" - description = "MokoStandards sync tracking file — records last sync date, version, and compliance status" + name = ".gitea/.mokostandards" + extension = "xml" + description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()" requirement_status = "required" - always_overwrite = true + always_overwrite = false audience = "developer" + template = "managed-by-sync" + source_type = "programmatic" } ] diff --git a/definitions/default/waas-component.tf b/definitions/default/waas-component.tf index 55d6cd1..7756ec2 100644 --- a/definitions/default/waas-component.tf +++ b/definitions/default/waas-component.tf @@ -83,13 +83,13 @@ locals { audience = "contributor" }, { - name = "update.xml" + name = "updates.xml" extension = "xml" - description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" + description = "Joomla extension update server manifest — lists releases for Joomla auto-update; managed by release workflow, never overwritten by sync" required = true always_overwrite = false + protected = true audience = "developer" - template = "templates/joomla/update.xml.template" stub_content = <<-MOKO_END 01.02.04 - @@ -582,7 +583,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required, see below) + ├── updates.xml # Update server manifest (root — required, see below) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -611,22 +612,22 @@ locals { --- - ## update.xml — Required in Repo Root + ## updates.xml — Required in Repo Root - `update.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. + `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 it via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. + - 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`. - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - `` — Joomla treats the version value as a regex; `[56].*` matches Joomla 5.x and 6.x. @@ -635,8 +636,8 @@ locals { ## 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 `update.xml`. - - Must include `` block pointing to this repo's `update.xml`. + - `` 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. @@ -644,16 +645,16 @@ locals { ## GitHub Actions — Token Usage - Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token). + Every workflow must use **`secrets.GA_TOKEN`** (the Gitea API token). Use `secrets.GH_TOKEN` only for GitHub mirror operations (stable/RC releases). ```yaml # ✅ Correct - uses: actions/checkout@v4 with: - token: ${{ secrets.GH_TOKEN }} + token: ${{ secrets.GA_TOKEN }} env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} + GA_TOKEN: ${{ secrets.GA_TOKEN }} ``` ```yaml @@ -666,16 +667,16 @@ locals { ## MokoStandards Reference - This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies: + This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards). Authoritative policies: | Document | Purpose | |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | + | [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | + | [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | + | [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | + | [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | + | [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | + | [joomla-development-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | --- @@ -714,8 +715,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed manifest.xml | Update `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version | + | 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 | @@ -728,8 +729,8 @@ locals { - 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, `update.xml` version, and `README.md` version go out of sync + - Use `secrets.GA_TOKEN` for Gitea operations. Use `secrets.GH_TOKEN` only for GitHub mirror (stable/RC). Never use `secrets.GITHUB_TOKEN` directly + - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync MOKO_END }, { @@ -762,7 +763,7 @@ locals { > | 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://github.com/mokoconsulting-tech/` | + > | `{{REPO_URL}}` | Full Gitea URL, e.g. `https://git.mokoconsulting.tech/MokoConsulting/` | > | `{{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`) | @@ -780,7 +781,7 @@ locals { Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`) Repository URL: {{REPO_URL}} - This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) — the single source of truth for coding standards, file-header policies, GitHub Actions workflows, and Terraform configuration templates across all Moko Consulting repositories. + This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards) — the single source of truth for coding standards, file-header policies, GitHub Actions workflows, and Terraform configuration templates across all Moko Consulting repositories. --- @@ -789,7 +790,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required) + ├── updates.xml # Update server manifest (root — required) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -839,32 +840,32 @@ locals { |------|------------------------| | `README.md` | `FILE INFORMATION` block + badge | | `manifest.xml` | `` tag | - | `update.xml` | `` in the most recent `` block | + | `updates.xml` | `` in the most recent `` block | The `make release` command / release workflow syncs all three automatically. --- - # update.xml — Required in Repo Root + # updates.xml — Required in Repo Root - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: + `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. + - `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`. - `` must be a publicly accessible GitHub Releases asset URL. - `` — Joomla treats the version value as a regex; `[56].*` matches Joomla 5.x and 6.x. - Example `update.xml` entry for a new release: + Example `updates.xml` entry for a new release: ```xml @@ -948,16 +949,16 @@ locals { # GitHub Actions — Token Usage - Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token). + Every workflow must use **`secrets.GA_TOKEN`** (the Gitea API token). Use `secrets.GH_TOKEN` only for GitHub mirror operations (stable/RC releases). ```yaml # ✅ Correct - uses: actions/checkout@v4 with: - token: ${{ secrets.GH_TOKEN }} + token: ${{ secrets.GA_TOKEN }} env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} + GA_TOKEN: ${{ secrets.GA_TOKEN }} ``` ```yaml @@ -973,8 +974,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | - | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | + | 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 | @@ -985,7 +986,7 @@ locals { - **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`, `update.xml`, and `README.md` versions diverge.** + - **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`. @@ -999,7 +1000,7 @@ locals { 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; `update.xml` updated with new entry + - [ ] 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 @@ -1010,117 +1011,109 @@ locals { | Document | Purpose | |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | + | [file-header-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | + | [coding-style-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | + | [branching-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | + | [merge-strategy.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | + | [changelog-standards.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | + | [joomla-development-guide.md](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | MOKO_END } ] subdirectories = [ { name = "workflows" - path = ".github/workflows" - description = "GitHub Actions workflows" + path = ".gitea/workflows" + description = "Gitea Actions CI/CD workflows" requirement_status = "required" files = [ - { - name = "ci-joomla.yml" - extension = "yml" - description = "Joomla-specific CI workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/ci-joomla.yml.template" - }, - { - name = "codeql-analysis.yml" - extension = "yml" - description = "CodeQL security analysis workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/generic/codeql-analysis.yml.template" - }, - { - name = "standards-compliance.yml" - extension = "yml" - description = "MokoStandards compliance validation" - requirement_status = "required" - always_overwrite = true - template = ".github/workflows/standards-compliance.yml" - }, - { - name = "enterprise-firewall-setup.yml" - extension = "yml" - description = "Enterprise firewall configuration for trusted domain access" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/enterprise-firewall-setup.yml.template" - }, - { - name = "deploy-dev.yml" - extension = "yml" - description = "SFTP deployment of src/ to the development server" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-dev.yml.template" - }, - { - name = "deploy-demo.yml" - extension = "yml" - description = "SFTP deployment of src/ to the demo server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-demo.yml.template" - }, - { - name = "deploy-rs.yml" - extension = "yml" - description = "SFTP deployment of src/ to the release staging server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-rs.yml.template" - }, - { - name = "sync-version-on-merge.yml" - extension = "yml" - description = "Auto-bump patch version on merge and propagate to all file headers" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/sync-version-on-merge.yml.template" - }, { name = "auto-release.yml" extension = "yml" - description = "Auto-create GitHub Release on push to main with version from README.md" + description = "Automated release — builds zip, creates Gitea release, updates SHA in updates.xml. Triggered by push to main (stable) or pre-release tags" requirement_status = "required" always_overwrite = true - template = "templates/workflows/shared/auto-release.yml.template" + template = "workflows/auto-release.yml" }, { - name = "repository-cleanup.yml" + name = "ci-dolibarr.yml" extension = "yml" - description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs" + description = "Continuous integration — PHP linting, PHPStan static analysis, Dolibarr module validation" requirement_status = "required" always_overwrite = true - template = "templates/workflows/shared/repository-cleanup.yml.template" + template = "workflows/ci-dolibarr.yml" }, { - name = "auto-dev-issue.yml" + name = "publish-to-mokodolimods.yml" extension = "yml" - description = "Auto-create tracking issue when a dev/** branch is pushed" + description = "On release, copies src/ into htdocs/custom/ in mokodolimods repo and opens a PR" requirement_status = "required" always_overwrite = true - template = "templates/workflows/shared/auto-dev-issue.yml.template" + template = "workflows/publish-to-mokodolimods.yml" }, { - name = "repo_health.yml" + name = "pre-release.yml" extension = "yml" - description = "Joomla-specific repository health check workflow" + description = "Manual pre-release — builds dev/alpha/beta/rc packages with patch version bump" requirement_status = "required" always_overwrite = true - template = "templates/workflows/joomla/repo_health.yml.template" + template = "workflows/pre-release.yml" + }, + { + name = "deploy-manual.yml" + extension = "yml" + description = "Manual deployment — allows selecting target environment and branch for on-demand deploys" + requirement_status = "required" + always_overwrite = true + template = "workflows/deploy-manual.yml" + }, + { + name = "repo-health.yml" + extension = "yml" + description = "Repository health checks — validates required files, structure compliance, and standards alignment" + requirement_status = "required" + always_overwrite = true + template = "workflows/repo-health.yml" + }, + { + name = "update-server.yml" + extension = "yml" + description = "Update server maintenance — validates updates.xml format and ensures download URLs are reachable" + requirement_status = "required" + always_overwrite = true + template = "workflows/update-server.yml" + }, + { + name = "pr-check.yml" + extension = "yml" + description = "PR gate — validates PHP syntax, manifest XML, and package build before merge to main" + requirement_status = "required" + always_overwrite = true + template = "workflows/pr-check.yml" + }, + { + name = "security-audit.yml" + extension = "yml" + description = "Dependency vulnerability scanning — weekly schedule and on PR when lock files change" + requirement_status = "required" + always_overwrite = true + template = "workflows/security-audit.yml" + }, + { + name = "notify.yml" + extension = "yml" + description = "Push notifications via ntfy on release success or workflow failure" + requirement_status = "required" + always_overwrite = true + template = "workflows/notify.yml" + }, + { + name = "cleanup.yml" + extension = "yml" + description = "Scheduled cleanup — delete merged branches and old workflow runs weekly" + requirement_status = "required" + always_overwrite = true + template = "workflows/cleanup.yml" } ] }, diff --git a/definitions/sync/MokoCassiopeia.def.tf b/definitions/sync/MokoCassiopeia.def.tf index 8dc3298..59ff676 100644 --- a/definitions/sync/MokoCassiopeia.def.tf +++ b/definitions/sync/MokoCassiopeia.def.tf @@ -34,7 +34,7 @@ locals { { path = "SECURITY.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" }, - { path = "update.xml" action = "updated" }, + { path = "updates.xml" action = "updated" }, { path = "phpstan.neon" action = "updated" }, { path = "Makefile" action = "updated" }, { path = ".gitignore" action = "updated" }, @@ -165,13 +165,13 @@ locals { audience = "contributor" }, { - name = "update.xml" + name = "updates.xml" extension = "xml" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" required = true always_overwrite = false audience = "developer" - template = "templates/joomla/update.xml.template" + template = "templates/joomla/updates.xml.template" stub_content = <<-MOKO_END 01.02.04 - @@ -661,7 +661,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required, see below) + ├── updates.xml # Update server manifest (root — required, see below) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -690,22 +690,22 @@ locals { --- - ## update.xml — Required in Repo Root + ## updates.xml — Required in Repo Root - `update.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. + `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 it via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. + - 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`. - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - `` — 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. @@ -714,8 +714,8 @@ locals { ## 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 `update.xml`. - - Must include `` block pointing to this repo's `update.xml`. + - `` 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. @@ -793,8 +793,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed manifest.xml | Update `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version | + | 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 | @@ -808,7 +808,7 @@ locals { - 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, `update.xml` version, and `README.md` version go out of sync + - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync MOKO_END }, { @@ -868,7 +868,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required) + ├── updates.xml # Update server manifest (root — required) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -918,32 +918,32 @@ locals { |------|------------------------| | `README.md` | `FILE INFORMATION` block + badge | | `manifest.xml` | `` tag | - | `update.xml` | `` in the most recent `` block | + | `updates.xml` | `` in the most recent `` block | The `make release` command / release workflow syncs all three automatically. --- - # update.xml — Required in Repo Root + # updates.xml — Required in Repo Root - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: + `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. + - `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`. - `` must be a publicly accessible GitHub Releases asset URL. - `` — backslash is literal (Joomla regex syntax). - Example `update.xml` entry for a new release: + Example `updates.xml` entry for a new release: ```xml @@ -1052,8 +1052,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | - | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | + | 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 | @@ -1064,7 +1064,7 @@ locals { - **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`, `update.xml`, and `README.md` versions diverge.** + - **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`. @@ -1078,7 +1078,7 @@ locals { 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; `update.xml` updated with new entry + - [ ] 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 diff --git a/definitions/sync/MokoJoomHero.def.tf b/definitions/sync/MokoJoomHero.def.tf index e61875b..ca5e45a 100644 --- a/definitions/sync/MokoJoomHero.def.tf +++ b/definitions/sync/MokoJoomHero.def.tf @@ -34,7 +34,7 @@ locals { { path = "SECURITY.md" action = "created" }, { path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" }, - { path = "update.xml" action = "updated" }, + { path = "updates.xml" action = "updated" }, { path = "phpstan.neon" action = "updated" }, { path = "Makefile" action = "updated" }, { path = ".gitignore" action = "updated" }, @@ -164,13 +164,13 @@ locals { audience = "contributor" }, { - name = "update.xml" + name = "updates.xml" extension = "xml" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" required = true always_overwrite = false audience = "developer" - template = "templates/joomla/update.xml.template" + template = "templates/joomla/updates.xml.template" stub_content = <<-MOKO_END 01.02.04 - @@ -660,7 +660,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required, see below) + ├── updates.xml # Update server manifest (root — required, see below) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -689,22 +689,22 @@ locals { --- - ## update.xml — Required in Repo Root + ## updates.xml — Required in Repo Root - `update.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. + `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 it via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. + - 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`. - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - `` — 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. @@ -713,8 +713,8 @@ locals { ## 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 `update.xml`. - - Must include `` block pointing to this repo's `update.xml`. + - `` 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. @@ -792,8 +792,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed manifest.xml | Update `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version | + | 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 | @@ -807,7 +807,7 @@ locals { - 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, `update.xml` version, and `README.md` version go out of sync + - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync MOKO_END }, { @@ -867,7 +867,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required) + ├── updates.xml # Update server manifest (root — required) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -917,32 +917,32 @@ locals { |------|------------------------| | `README.md` | `FILE INFORMATION` block + badge | | `manifest.xml` | `` tag | - | `update.xml` | `` in the most recent `` block | + | `updates.xml` | `` in the most recent `` block | The `make release` command / release workflow syncs all three automatically. --- - # update.xml — Required in Repo Root + # updates.xml — Required in Repo Root - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: + `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. + - `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`. - `` must be a publicly accessible GitHub Releases asset URL. - `` — backslash is literal (Joomla regex syntax). - Example `update.xml` entry for a new release: + Example `updates.xml` entry for a new release: ```xml @@ -1051,8 +1051,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | - | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | + | 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 | @@ -1063,7 +1063,7 @@ locals { - **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`, `update.xml`, and `README.md` versions diverge.** + - **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`. @@ -1077,7 +1077,7 @@ locals { 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; `update.xml` updated with new entry + - [ ] 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 diff --git a/definitions/sync/MokoJoomTOS.def.tf b/definitions/sync/MokoJoomTOS.def.tf index df9fdab..23a2ea1 100644 --- a/definitions/sync/MokoJoomTOS.def.tf +++ b/definitions/sync/MokoJoomTOS.def.tf @@ -34,7 +34,7 @@ locals { { path = "SECURITY.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" }, - { path = "update.xml" action = "updated" }, + { path = "updates.xml" action = "updated" }, { path = "phpstan.neon" action = "updated" }, { path = "Makefile" action = "updated" }, { path = ".gitignore" action = "updated" }, @@ -164,13 +164,13 @@ locals { audience = "contributor" }, { - name = "update.xml" + name = "updates.xml" extension = "xml" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" required = true always_overwrite = false audience = "developer" - template = "templates/joomla/update.xml.template" + template = "templates/joomla/updates.xml.template" stub_content = <<-MOKO_END 01.02.04 - @@ -660,7 +660,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required, see below) + ├── updates.xml # Update server manifest (root — required, see below) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -689,22 +689,22 @@ locals { --- - ## update.xml — Required in Repo Root + ## updates.xml — Required in Repo Root - `update.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. + `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 it via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. + - 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`. - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - `` — 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. @@ -713,8 +713,8 @@ locals { ## 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 `update.xml`. - - Must include `` block pointing to this repo's `update.xml`. + - `` 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. @@ -792,8 +792,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed manifest.xml | Update `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version | + | 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 | @@ -807,7 +807,7 @@ locals { - 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, `update.xml` version, and `README.md` version go out of sync + - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync MOKO_END }, { @@ -867,7 +867,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required) + ├── updates.xml # Update server manifest (root — required) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -917,32 +917,32 @@ locals { |------|------------------------| | `README.md` | `FILE INFORMATION` block + badge | | `manifest.xml` | `` tag | - | `update.xml` | `` in the most recent `` block | + | `updates.xml` | `` in the most recent `` block | The `make release` command / release workflow syncs all three automatically. --- - # update.xml — Required in Repo Root + # updates.xml — Required in Repo Root - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: + `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. + - `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`. - `` must be a publicly accessible GitHub Releases asset URL. - `` — backslash is literal (Joomla regex syntax). - Example `update.xml` entry for a new release: + Example `updates.xml` entry for a new release: ```xml @@ -1051,8 +1051,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | - | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | + | 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 | @@ -1063,7 +1063,7 @@ locals { - **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`, `update.xml`, and `README.md` versions diverge.** + - **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`. @@ -1077,7 +1077,7 @@ locals { 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; `update.xml` updated with new entry + - [ ] 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 diff --git a/definitions/sync/MokoStandards-Template-Joomla-Component.def.tf b/definitions/sync/MokoStandards-Template-Joomla-Component.def.tf deleted file mode 100644 index d17abf8..0000000 --- a/definitions/sync/MokoStandards-Template-Joomla-Component.def.tf +++ /dev/null @@ -1,1335 +0,0 @@ -/** - * Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Joomla-Component - * - * Auto-generated by MokoStandards bulk sync on 2026-04-02T15:30:04+00:00 - * Platform : waas-component - * Description: A repo template for a Joomla Component coding project according to MokoStandards - * - * DO NOT EDIT MANUALLY — this file is regenerated on every successful sync. - * To change what gets synced, edit api/definitions/default/waas-component.tf - * and re-run the bulk-repo-sync workflow. - */ - -locals { - sync_record = { - metadata = { - repo = "mokoconsulting-tech/MokoStandards-Template-Joomla-Component" - default_branch = "main" - detected_platform = "waas-component" - description = "A repo template for a Joomla Component coding project according to MokoStandards" - sync_timestamp = "2026-04-02T15:30:04+00:00" - source_repo = "mokoconsulting-tech/MokoStandards" - base_definition = "api/definitions/default/waas-component.tf" - } - - sync_stats = { - total_files = 41 - created_files = 3 - updated_files = 35 - skipped_files = 3 - } - - synced_files = [ - { path = "LICENSE" action = "updated" }, - { path = "SECURITY.md" action = "updated" }, - { path = "CODE_OF_CONDUCT.md" action = "updated" }, - { path = "CONTRIBUTING.md" action = "updated" }, - { path = "update.xml" action = "updated" }, - { path = "phpstan.neon" action = "updated" }, - { path = "Makefile" action = "updated" }, - { path = ".gitignore" action = "updated" }, - { path = "composer.json" action = "updated" }, - { path = ".mokostandards" action = "created" }, - { path = "docs/update-server.md" action = "created" }, - { path = ".github/copilot.yml" action = "updated" }, - { path = ".github/copilot-instructions.md" action = "updated" }, - { path = ".github/CLAUDE.md" action = "updated" }, - { path = ".github/workflows/codeql-analysis.yml" action = "updated" }, - { path = ".github/workflows/standards-compliance.yml" action = "updated" }, - { path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" }, - { path = ".github/workflows/deploy-dev.yml" action = "updated" }, - { path = ".github/workflows/deploy-demo.yml" action = "updated" }, - { path = ".github/workflows/deploy-rs.yml" action = "updated" }, - { path = ".github/workflows/sync-version-on-merge.yml" action = "updated" }, - { path = ".github/workflows/auto-release.yml" action = "updated" }, - { path = ".github/workflows/repository-cleanup.yml" action = "updated" }, - { path = ".github/workflows/auto-dev-issue.yml" action = "updated" }, - { path = ".github/workflows/repo_health.yml" action = "created" }, - { path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/joomla_issue.md" action = "updated" }, - { path = ".github/CODEOWNERS" action = "updated" }, - { path = ".github/.mokostandards" action = "migrated from root" }, - ] - - skipped_files = [ - { path = "GOVERNANCE.md" reason = "Preserved (always_overwrite=false)" }, - { path = ".github/workflows/ci-joomla.yml" reason = "Source file not found" }, - { path = ".github/workflows/custom/README.md" reason = "README — never overwritten" }, - ] - } -} - -# ---- Base platform definition (reference copy) ---- -/** - * MokoWaaS Component Structure Definition - * Standard repository structure for MokoWaaS (Joomla) components - * - * Copyright (C) 2026 Moko Consulting - * SPDX-License-Identifier: GPL-3.0-or-later - * Version: 04.05.00 - * Schema Version: 1.0 - */ - -locals { - repository_structure = { - metadata = { - name = "MokoWaaS Component" - description = "Standard repository structure for MokoWaaS (Joomla) components" - repository_type = "waas-component" - platform = "mokowaas" - last_updated = "2026-01-15T00:00:00Z" - maintainer = "Moko Consulting" - version = "04.05.00" - schema_version = "1.0" - } - - root_files = [ - { - name = "README.md" - extension = "md" - description = "Developer-focused documentation for contributors and maintainers" - required = true - always_overwrite = false - protected = true - audience = "developer" - }, - { - name = "LICENSE" - extension = "" - description = "License file (GPL-3.0-or-later) - Default for Joomla/WaaS components" - required = true - audience = "general" - template = "templates/licenses/GPL-3.0" - license_type = "GPL-3.0-or-later" - }, - { - name = "CHANGELOG.md" - extension = "md" - description = "Version history and changes" - required = true - audience = "general" - }, - { - name = "SECURITY.md" - extension = "md" - description = "Security policy and vulnerability reporting" - required = true - always_overwrite = true - template = "templates/docs/required/template-SECURITY.md" - audience = "general" - }, - { - name = "CODE_OF_CONDUCT.md" - extension = "md" - description = "Community code of conduct" - required = true - always_overwrite = true - template = "templates/docs/extra/template-CODE_OF_CONDUCT.md" - always_overwrite = true - audience = "contributor" - }, - { - name = "ROADMAP.md" - extension = "md" - description = "Project roadmap with version goals and milestones" - required = false - audience = "general" - }, - { - name = "CONTRIBUTING.md" - extension = "md" - description = "Contribution guidelines" - required = true - always_overwrite = true - template = "templates/docs/required/template-CONTRIBUTING.md" - audience = "contributor" - }, - { - name = "update.xml" - extension = "xml" - description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" - required = true - always_overwrite = false - audience = "developer" - template = "templates/joomla/update.xml.template" - stub_content = <<-MOKO_END - - - - {{EXTENSION_NAME}} - {{REPO_NAME}} — Moko Consulting Joomla extension - {{EXTENSION_ELEMENT}} - {{EXTENSION_TYPE}} - {{VERSION}} - {{REPO_URL}}/releases/tag/{{VERSION}} - - {{DOWNLOAD_URL}} - - - 7.4 - Moko Consulting - {{MAINTAINER_URL}} - - - MOKO_END - }, - { - name = "phpstan.neon" - extension = "neon" - description = "PHPStan static analysis config with Joomla framework class stubs" - required = true - always_overwrite = true - audience = "developer" - template = "templates/configs/phpstan.joomla.neon" - }, - { - name = "Makefile" - description = "Build automation using MokoStandards templates" - required = true - always_overwrite = true - audience = "developer" - source_path = "templates/makefiles" - source_filename = "Makefile.joomla.template" - source_type = "template" - destination_path = "." - destination_filename = "Makefile" - create_path = false - template = "templates/makefiles/Makefile.joomla.template" - }, - { - name = ".gitignore" - extension = "gitignore" - description = "Git ignore patterns for Joomla development - preserved during sync operations" - required = true - always_overwrite = false - audience = "developer" - template = "templates/configs/.gitignore.joomla" - validation_rules = [ - { - type = "content-pattern" - description = "Must contain sftp-config pattern to ignore SFTP sync configuration files" - pattern = "sftp-config" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain user.css pattern to ignore custom user CSS overrides" - pattern = "user\\.css" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain user.js pattern to ignore custom user JavaScript overrides" - pattern = "user\\.js" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain modulebuilder.txt pattern to ignore Joomla Module Builder artifacts" - pattern = "modulebuilder\\.txt" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain colors_custom.css pattern to ignore custom color scheme overrides" - pattern = "colors_custom\\.css" - severity = "error" - } - ] - }, - { - name = ".gitattributes" - extension = "gitattributes" - description = "Git attributes configuration" - required = true - audience = "developer" - }, - { - name = ".editorconfig" - extension = "editorconfig" - description = "Editor configuration for consistent coding style - preserved during sync" - required = true - always_overwrite = false - audience = "developer" - }, - { - name = "composer.json" - extension = "json" - description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling" - required = true - always_overwrite = false - audience = "developer" - template = "templates/configs/composer.joomla.json" - }, - { - name = ".mokostandards" - extension = "yml" - description = "MokoStandards governance attachment — links this repo back to the standards source" - required = true - always_overwrite = true - audience = "developer" - template = "templates/configs/mokostandards.yml.template" - }, - { - name = "GOVERNANCE.md" - extension = "md" - description = "Project governance rules, roles, and decision process — auto-maintained by MokoStandards" - required = true - always_overwrite = false - protected = true - audience = "all" - template = "templates/docs/required/GOVERNANCE.md" - } - ] - - directories = [ - { - name = "site" - path = "site" - description = "Component frontend (site) code" - required = true - purpose = "Contains frontend component code deployed to site" - files = [ - { - name = "controller.php" - extension = "php" - description = "Main site controller" - required = true - audience = "developer" - }, - { - name = "manifest.xml" - extension = "xml" - description = "Component manifest for site" - required = true - audience = "developer" - } - ] - subdirectories = [ - { - name = "controllers" - path = "site/controllers" - description = "Site controllers" - requirement_status = "suggested" - }, - { - name = "models" - path = "site/models" - description = "Site models" - requirement_status = "suggested" - }, - { - name = "views" - path = "site/views" - description = "Site views" - required = true - } - ] - }, - { - name = "admin" - path = "admin" - description = "Component backend (admin) code" - required = true - purpose = "Contains backend component code for administrator" - files = [ - { - name = "controller.php" - extension = "php" - description = "Main admin controller" - required = true - audience = "developer" - } - ] - subdirectories = [ - { - name = "controllers" - path = "admin/controllers" - description = "Admin controllers" - requirement_status = "suggested" - }, - { - name = "models" - path = "admin/models" - description = "Admin models" - requirement_status = "suggested" - }, - { - name = "views" - path = "admin/views" - description = "Admin views" - required = true - }, - { - name = "sql" - path = "admin/sql" - description = "Database schema files" - requirement_status = "suggested" - } - ] - }, - { - name = "media" - path = "media" - description = "Media files (CSS, JS, images)" - requirement_status = "suggested" - purpose = "Contains static assets" - subdirectories = [ - { - name = "css" - path = "media/css" - description = "Stylesheets" - requirement_status = "suggested" - }, - { - name = "js" - path = "media/js" - description = "JavaScript files" - requirement_status = "suggested" - }, - { - name = "images" - path = "media/images" - description = "Image files" - requirement_status = "suggested" - } - ] - }, - { - name = "language" - path = "language" - description = "Language translation files" - required = true - purpose = "Contains language INI files" - }, - { - name = "docs" - path = "docs" - description = "Developer and technical documentation" - required = true - purpose = "Contains technical documentation, API docs, architecture diagrams" - files = [ - { - name = "index.md" - extension = "md" - description = "Documentation index" - required = true - }, - { - name = "update-server.md" - extension = "md" - description = "Joomla update server (update.xml) documentation" - required = true - always_overwrite = true - template = "templates/docs/required/template-update-server-joomla.md" - } - ] - }, - { - name = "scripts" - path = "scripts" - description = "Repo-specific scripts — not managed by MokoStandards sync" - required = false - purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/." - files = [ - { - name = "MokoStandards.override.xml" - extension = "xml" - description = "MokoStandards sync override configuration - preserved during sync" - requirement_status = "optional" - always_overwrite = false - audience = "developer" - } - ] - }, - { - name = "tests" - path = "tests" - description = "Test files" - required = true - purpose = "Contains unit tests, integration tests, and test fixtures" - subdirectories = [ - { - name = "unit" - path = "tests/unit" - description = "Unit tests" - required = true - }, - { - name = "integration" - path = "tests/integration" - description = "Integration tests" - requirement_status = "suggested" - } - ] - }, - { - name = ".github" - path = ".github" - description = "GitHub-specific configuration" - requirement_status = "suggested" - purpose = "Contains GitHub Actions workflows and configuration" - files = [ - { - name = "copilot.yml" - extension = "yml" - description = "GitHub Copilot allowed domains configuration" - requirement_status = "required" - always_overwrite = true - template = ".github/copilot.yml" - }, - { - name = "copilot-instructions.md" - extension = "md" - description = "GitHub Copilot custom instructions enforcing MokoStandards — Joomla/WaaS edition" - requirement_status = "required" - always_overwrite = false - destination_path = ".github" - destination_filename = "copilot-instructions.md" - template = "templates/github/copilot-instructions.joomla.md.template" - stub_content = <<-MOKO_END - > [!IMPORTANT] - > **🔧 AI Self-Update Required on First Use** - > - > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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://github.com/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 [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). 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 `update.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) - ├── update.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 - ├── .github/ - │ ├── 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 - ``` - - --- - - ## update.xml — Required in Repo Root - - `update.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 it via: - ```xml - - - {{REPO_URL}}/raw/main/update.xml - - - ``` - - **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. - - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - - `` — 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 `update.xml`. - - Must include `` block pointing to this repo's `update.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 }} - ``` - - --- - - ## MokoStandards Reference - - This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies: - - | Document | Purpose | - |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/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 `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.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, `update.xml` version, and `README.md` version go out of sync - MOKO_END - }, - { - name = "CLAUDE.md" - extension = "md" - description = "Claude AI assistant context enforcing MokoStandards — Joomla/WaaS edition" - requirement_status = "required" - always_overwrite = false - destination_path = ".github" - destination_filename = "CLAUDE.md" - template = "templates/github/CLAUDE.joomla.md.template" - stub_content = <<-MOKO_END - > [!IMPORTANT] - > **🔧 AI Self-Update Required on First Use** - > - > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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://github.com/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 [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) — 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) - ├── update.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 - ├── .github/ - │ ├── workflows/ # CI/CD workflows (synced from MokoStandards) - │ ├── 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 | - | `update.xml` | `` in the most recent `` block | - - The `make release` command / release workflow syncs all three automatically. - - --- - - # update.xml — Required in Repo Root - - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: - - ```xml - - - - {{REPO_URL}}/raw/main/update.xml - - - ``` - - **Rules:** - - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. - - `` must be a publicly accessible GitHub Releases asset URL. - - `` — backslash is literal (Joomla regex syntax). - - Example `update.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 - - - - 7.4 - 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 `update.xml` and `README.md` | - | New release | Prepend `` to `update.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`, `update.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; `update.xml` updated with new entry - - [ ] FILE INFORMATION headers updated in modified files - - [ ] CHANGELOG.md updated - - [ ] Tests pass - - --- - - # Key Policy Documents (MokoStandards) - - | Document | Purpose | - |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | - MOKO_END - } - ] - subdirectories = [ - { - name = "workflows" - path = ".github/workflows" - description = "GitHub Actions workflows" - requirement_status = "required" - files = [ - { - name = "ci-joomla.yml" - extension = "yml" - description = "Joomla-specific CI workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/ci-joomla.yml.template" - }, - { - name = "codeql-analysis.yml" - extension = "yml" - description = "CodeQL security analysis workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/generic/codeql-analysis.yml.template" - }, - { - name = "standards-compliance.yml" - extension = "yml" - description = "MokoStandards compliance validation" - requirement_status = "required" - always_overwrite = true - template = ".github/workflows/standards-compliance.yml" - }, - { - name = "enterprise-firewall-setup.yml" - extension = "yml" - description = "Enterprise firewall configuration for trusted domain access" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/enterprise-firewall-setup.yml.template" - }, - { - name = "deploy-dev.yml" - extension = "yml" - description = "SFTP deployment of src/ to the development server" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-dev.yml.template" - }, - { - name = "deploy-demo.yml" - extension = "yml" - description = "SFTP deployment of src/ to the demo server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-demo.yml.template" - }, - { - name = "deploy-rs.yml" - extension = "yml" - description = "SFTP deployment of src/ to the release staging server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-rs.yml.template" - }, - { - name = "sync-version-on-merge.yml" - extension = "yml" - description = "Auto-bump patch version on merge and propagate to all file headers" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/sync-version-on-merge.yml.template" - }, - { - name = "auto-release.yml" - extension = "yml" - description = "Auto-create GitHub Release on push to main with version from README.md" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/auto-release.yml.template" - }, - { - name = "repository-cleanup.yml" - extension = "yml" - description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/repository-cleanup.yml.template" - }, - { - name = "auto-dev-issue.yml" - extension = "yml" - description = "Auto-create tracking issue when a dev/** branch is pushed" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/auto-dev-issue.yml.template" - }, - { - name = "repo_health.yml" - extension = "yml" - description = "Joomla-specific repository health check workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/repo_health.yml.template" - } - ] - }, - { - name = "ISSUE_TEMPLATE" - path = ".github/ISSUE_TEMPLATE" - description = "GitHub issue templates synced from MokoStandards" - requirement_status = "required" - files = [ - { - name = "config.yml" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/config.yml" - }, - { - name = "adr.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/adr.md" - }, - { - name = "bug_report.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/bug_report.md" - }, - { - name = "documentation.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/documentation.md" - }, - { - name = "enterprise_support.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md" - }, - { - name = "feature_request.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/feature_request.md" - }, - { - name = "firewall-request.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/firewall-request.md" - }, - { - name = "question.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/question.md" - }, - { - name = "request-license.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/request-license.md" - }, - { - name = "rfc.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/rfc.md" - }, - { - name = "security.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/security.md" - }, - { - name = "joomla_issue.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/joomla_issue.md" - } - ] - } - ] - } - ] - - repository_requirements = { - secrets = [ - { - name = "GH_TOKEN" - description = "Org-level GitHub PAT for automation" - required = true - scope = "org" - }, - { - name = "DEV_FTP_KEY" - description = "SSH private key for SFTP dev deployment (preferred); if DEV_FTP_PASSWORD is also set it is used as the key passphrase, with password-only as fallback" - required = false - scope = "org" - }, - { - name = "DEV_FTP_PASSWORD" - description = "SFTP password for dev deployment; used as SSH key passphrase when DEV_FTP_KEY is also set, and as standalone fallback if key auth fails" - required = false - scope = "org" - note = "At least one of DEV_FTP_KEY or DEV_FTP_PASSWORD must be configured" - } - ] - - variables = [ - { - name = "DEV_FTP_HOST" - description = "Dev server hostname; may include port suffix (e.g. dev.example.com or dev.example.com:2222)" - required = true - scope = "org" - }, - { - name = "DEV_FTP_PATH" - description = "Base remote path for SFTP deployment (e.g. /var/www/html)" - required = true - scope = "org" - }, - { - name = "DEV_FTP_USERNAME" - description = "SFTP username for dev server authentication" - required = true - scope = "org" - }, - { - name = "DEV_FTP_PORT" - description = "Explicit SFTP port override; if omitted the port is parsed from DEV_FTP_HOST or defaults to 22" - required = false - scope = "org" - }, - { - name = "DEV_FTP_SUFFIX" - description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /my-extension)" - required = false - scope = "repo" - } - ] - } - } -} diff --git a/definitions/sync/MokoStandards-Template-Joomla-Library.def.tf b/definitions/sync/MokoStandards-Template-Joomla-Library.def.tf deleted file mode 100644 index 9ea587f..0000000 --- a/definitions/sync/MokoStandards-Template-Joomla-Library.def.tf +++ /dev/null @@ -1,1335 +0,0 @@ -/** - * Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Joomla-Library - * - * Auto-generated by MokoStandards bulk sync on 2026-04-02T15:31:13+00:00 - * Platform : waas-component - * Description: A repo template for a Joomla Library coding project according to MokoStandards - * - * DO NOT EDIT MANUALLY — this file is regenerated on every successful sync. - * To change what gets synced, edit api/definitions/default/waas-component.tf - * and re-run the bulk-repo-sync workflow. - */ - -locals { - sync_record = { - metadata = { - repo = "mokoconsulting-tech/MokoStandards-Template-Joomla-Library" - default_branch = "main" - detected_platform = "waas-component" - description = "A repo template for a Joomla Library coding project according to MokoStandards" - sync_timestamp = "2026-04-02T15:31:13+00:00" - source_repo = "mokoconsulting-tech/MokoStandards" - base_definition = "api/definitions/default/waas-component.tf" - } - - sync_stats = { - total_files = 41 - created_files = 3 - updated_files = 35 - skipped_files = 3 - } - - synced_files = [ - { path = "LICENSE" action = "updated" }, - { path = "SECURITY.md" action = "updated" }, - { path = "CODE_OF_CONDUCT.md" action = "updated" }, - { path = "CONTRIBUTING.md" action = "updated" }, - { path = "update.xml" action = "updated" }, - { path = "phpstan.neon" action = "updated" }, - { path = "Makefile" action = "updated" }, - { path = ".gitignore" action = "updated" }, - { path = "composer.json" action = "updated" }, - { path = ".mokostandards" action = "created" }, - { path = "docs/update-server.md" action = "created" }, - { path = ".github/copilot.yml" action = "updated" }, - { path = ".github/copilot-instructions.md" action = "updated" }, - { path = ".github/CLAUDE.md" action = "updated" }, - { path = ".github/workflows/codeql-analysis.yml" action = "updated" }, - { path = ".github/workflows/standards-compliance.yml" action = "updated" }, - { path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" }, - { path = ".github/workflows/deploy-dev.yml" action = "updated" }, - { path = ".github/workflows/deploy-demo.yml" action = "updated" }, - { path = ".github/workflows/deploy-rs.yml" action = "updated" }, - { path = ".github/workflows/sync-version-on-merge.yml" action = "updated" }, - { path = ".github/workflows/auto-release.yml" action = "updated" }, - { path = ".github/workflows/repository-cleanup.yml" action = "updated" }, - { path = ".github/workflows/auto-dev-issue.yml" action = "updated" }, - { path = ".github/workflows/repo_health.yml" action = "created" }, - { path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/joomla_issue.md" action = "updated" }, - { path = ".github/CODEOWNERS" action = "updated" }, - { path = ".github/.mokostandards" action = "migrated from root" }, - ] - - skipped_files = [ - { path = "GOVERNANCE.md" reason = "Preserved (always_overwrite=false)" }, - { path = ".github/workflows/ci-joomla.yml" reason = "Source file not found" }, - { path = ".github/workflows/custom/README.md" reason = "README — never overwritten" }, - ] - } -} - -# ---- Base platform definition (reference copy) ---- -/** - * MokoWaaS Component Structure Definition - * Standard repository structure for MokoWaaS (Joomla) components - * - * Copyright (C) 2026 Moko Consulting - * SPDX-License-Identifier: GPL-3.0-or-later - * Version: 04.05.00 - * Schema Version: 1.0 - */ - -locals { - repository_structure = { - metadata = { - name = "MokoWaaS Component" - description = "Standard repository structure for MokoWaaS (Joomla) components" - repository_type = "waas-component" - platform = "mokowaas" - last_updated = "2026-01-15T00:00:00Z" - maintainer = "Moko Consulting" - version = "04.05.00" - schema_version = "1.0" - } - - root_files = [ - { - name = "README.md" - extension = "md" - description = "Developer-focused documentation for contributors and maintainers" - required = true - always_overwrite = false - protected = true - audience = "developer" - }, - { - name = "LICENSE" - extension = "" - description = "License file (GPL-3.0-or-later) - Default for Joomla/WaaS components" - required = true - audience = "general" - template = "templates/licenses/GPL-3.0" - license_type = "GPL-3.0-or-later" - }, - { - name = "CHANGELOG.md" - extension = "md" - description = "Version history and changes" - required = true - audience = "general" - }, - { - name = "SECURITY.md" - extension = "md" - description = "Security policy and vulnerability reporting" - required = true - always_overwrite = true - template = "templates/docs/required/template-SECURITY.md" - audience = "general" - }, - { - name = "CODE_OF_CONDUCT.md" - extension = "md" - description = "Community code of conduct" - required = true - always_overwrite = true - template = "templates/docs/extra/template-CODE_OF_CONDUCT.md" - always_overwrite = true - audience = "contributor" - }, - { - name = "ROADMAP.md" - extension = "md" - description = "Project roadmap with version goals and milestones" - required = false - audience = "general" - }, - { - name = "CONTRIBUTING.md" - extension = "md" - description = "Contribution guidelines" - required = true - always_overwrite = true - template = "templates/docs/required/template-CONTRIBUTING.md" - audience = "contributor" - }, - { - name = "update.xml" - extension = "xml" - description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" - required = true - always_overwrite = false - audience = "developer" - template = "templates/joomla/update.xml.template" - stub_content = <<-MOKO_END - - - - {{EXTENSION_NAME}} - {{REPO_NAME}} — Moko Consulting Joomla extension - {{EXTENSION_ELEMENT}} - {{EXTENSION_TYPE}} - {{VERSION}} - {{REPO_URL}}/releases/tag/{{VERSION}} - - {{DOWNLOAD_URL}} - - - 7.4 - Moko Consulting - {{MAINTAINER_URL}} - - - MOKO_END - }, - { - name = "phpstan.neon" - extension = "neon" - description = "PHPStan static analysis config with Joomla framework class stubs" - required = true - always_overwrite = true - audience = "developer" - template = "templates/configs/phpstan.joomla.neon" - }, - { - name = "Makefile" - description = "Build automation using MokoStandards templates" - required = true - always_overwrite = true - audience = "developer" - source_path = "templates/makefiles" - source_filename = "Makefile.joomla.template" - source_type = "template" - destination_path = "." - destination_filename = "Makefile" - create_path = false - template = "templates/makefiles/Makefile.joomla.template" - }, - { - name = ".gitignore" - extension = "gitignore" - description = "Git ignore patterns for Joomla development - preserved during sync operations" - required = true - always_overwrite = false - audience = "developer" - template = "templates/configs/.gitignore.joomla" - validation_rules = [ - { - type = "content-pattern" - description = "Must contain sftp-config pattern to ignore SFTP sync configuration files" - pattern = "sftp-config" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain user.css pattern to ignore custom user CSS overrides" - pattern = "user\\.css" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain user.js pattern to ignore custom user JavaScript overrides" - pattern = "user\\.js" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain modulebuilder.txt pattern to ignore Joomla Module Builder artifacts" - pattern = "modulebuilder\\.txt" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain colors_custom.css pattern to ignore custom color scheme overrides" - pattern = "colors_custom\\.css" - severity = "error" - } - ] - }, - { - name = ".gitattributes" - extension = "gitattributes" - description = "Git attributes configuration" - required = true - audience = "developer" - }, - { - name = ".editorconfig" - extension = "editorconfig" - description = "Editor configuration for consistent coding style - preserved during sync" - required = true - always_overwrite = false - audience = "developer" - }, - { - name = "composer.json" - extension = "json" - description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling" - required = true - always_overwrite = false - audience = "developer" - template = "templates/configs/composer.joomla.json" - }, - { - name = ".mokostandards" - extension = "yml" - description = "MokoStandards governance attachment — links this repo back to the standards source" - required = true - always_overwrite = true - audience = "developer" - template = "templates/configs/mokostandards.yml.template" - }, - { - name = "GOVERNANCE.md" - extension = "md" - description = "Project governance rules, roles, and decision process — auto-maintained by MokoStandards" - required = true - always_overwrite = false - protected = true - audience = "all" - template = "templates/docs/required/GOVERNANCE.md" - } - ] - - directories = [ - { - name = "site" - path = "site" - description = "Component frontend (site) code" - required = true - purpose = "Contains frontend component code deployed to site" - files = [ - { - name = "controller.php" - extension = "php" - description = "Main site controller" - required = true - audience = "developer" - }, - { - name = "manifest.xml" - extension = "xml" - description = "Component manifest for site" - required = true - audience = "developer" - } - ] - subdirectories = [ - { - name = "controllers" - path = "site/controllers" - description = "Site controllers" - requirement_status = "suggested" - }, - { - name = "models" - path = "site/models" - description = "Site models" - requirement_status = "suggested" - }, - { - name = "views" - path = "site/views" - description = "Site views" - required = true - } - ] - }, - { - name = "admin" - path = "admin" - description = "Component backend (admin) code" - required = true - purpose = "Contains backend component code for administrator" - files = [ - { - name = "controller.php" - extension = "php" - description = "Main admin controller" - required = true - audience = "developer" - } - ] - subdirectories = [ - { - name = "controllers" - path = "admin/controllers" - description = "Admin controllers" - requirement_status = "suggested" - }, - { - name = "models" - path = "admin/models" - description = "Admin models" - requirement_status = "suggested" - }, - { - name = "views" - path = "admin/views" - description = "Admin views" - required = true - }, - { - name = "sql" - path = "admin/sql" - description = "Database schema files" - requirement_status = "suggested" - } - ] - }, - { - name = "media" - path = "media" - description = "Media files (CSS, JS, images)" - requirement_status = "suggested" - purpose = "Contains static assets" - subdirectories = [ - { - name = "css" - path = "media/css" - description = "Stylesheets" - requirement_status = "suggested" - }, - { - name = "js" - path = "media/js" - description = "JavaScript files" - requirement_status = "suggested" - }, - { - name = "images" - path = "media/images" - description = "Image files" - requirement_status = "suggested" - } - ] - }, - { - name = "language" - path = "language" - description = "Language translation files" - required = true - purpose = "Contains language INI files" - }, - { - name = "docs" - path = "docs" - description = "Developer and technical documentation" - required = true - purpose = "Contains technical documentation, API docs, architecture diagrams" - files = [ - { - name = "index.md" - extension = "md" - description = "Documentation index" - required = true - }, - { - name = "update-server.md" - extension = "md" - description = "Joomla update server (update.xml) documentation" - required = true - always_overwrite = true - template = "templates/docs/required/template-update-server-joomla.md" - } - ] - }, - { - name = "scripts" - path = "scripts" - description = "Repo-specific scripts — not managed by MokoStandards sync" - required = false - purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/." - files = [ - { - name = "MokoStandards.override.xml" - extension = "xml" - description = "MokoStandards sync override configuration - preserved during sync" - requirement_status = "optional" - always_overwrite = false - audience = "developer" - } - ] - }, - { - name = "tests" - path = "tests" - description = "Test files" - required = true - purpose = "Contains unit tests, integration tests, and test fixtures" - subdirectories = [ - { - name = "unit" - path = "tests/unit" - description = "Unit tests" - required = true - }, - { - name = "integration" - path = "tests/integration" - description = "Integration tests" - requirement_status = "suggested" - } - ] - }, - { - name = ".github" - path = ".github" - description = "GitHub-specific configuration" - requirement_status = "suggested" - purpose = "Contains GitHub Actions workflows and configuration" - files = [ - { - name = "copilot.yml" - extension = "yml" - description = "GitHub Copilot allowed domains configuration" - requirement_status = "required" - always_overwrite = true - template = ".github/copilot.yml" - }, - { - name = "copilot-instructions.md" - extension = "md" - description = "GitHub Copilot custom instructions enforcing MokoStandards — Joomla/WaaS edition" - requirement_status = "required" - always_overwrite = false - destination_path = ".github" - destination_filename = "copilot-instructions.md" - template = "templates/github/copilot-instructions.joomla.md.template" - stub_content = <<-MOKO_END - > [!IMPORTANT] - > **🔧 AI Self-Update Required on First Use** - > - > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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://github.com/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 [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). 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 `update.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) - ├── update.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 - ├── .github/ - │ ├── 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 - ``` - - --- - - ## update.xml — Required in Repo Root - - `update.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 it via: - ```xml - - - {{REPO_URL}}/raw/main/update.xml - - - ``` - - **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. - - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - - `` — 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 `update.xml`. - - Must include `` block pointing to this repo's `update.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 }} - ``` - - --- - - ## MokoStandards Reference - - This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies: - - | Document | Purpose | - |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/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 `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.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, `update.xml` version, and `README.md` version go out of sync - MOKO_END - }, - { - name = "CLAUDE.md" - extension = "md" - description = "Claude AI assistant context enforcing MokoStandards — Joomla/WaaS edition" - requirement_status = "required" - always_overwrite = false - destination_path = ".github" - destination_filename = "CLAUDE.md" - template = "templates/github/CLAUDE.joomla.md.template" - stub_content = <<-MOKO_END - > [!IMPORTANT] - > **🔧 AI Self-Update Required on First Use** - > - > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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://github.com/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 [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) — 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) - ├── update.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 - ├── .github/ - │ ├── workflows/ # CI/CD workflows (synced from MokoStandards) - │ ├── 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 | - | `update.xml` | `` in the most recent `` block | - - The `make release` command / release workflow syncs all three automatically. - - --- - - # update.xml — Required in Repo Root - - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: - - ```xml - - - - {{REPO_URL}}/raw/main/update.xml - - - ``` - - **Rules:** - - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. - - `` must be a publicly accessible GitHub Releases asset URL. - - `` — backslash is literal (Joomla regex syntax). - - Example `update.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 - - - - 7.4 - 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 `update.xml` and `README.md` | - | New release | Prepend `` to `update.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`, `update.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; `update.xml` updated with new entry - - [ ] FILE INFORMATION headers updated in modified files - - [ ] CHANGELOG.md updated - - [ ] Tests pass - - --- - - # Key Policy Documents (MokoStandards) - - | Document | Purpose | - |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | - MOKO_END - } - ] - subdirectories = [ - { - name = "workflows" - path = ".github/workflows" - description = "GitHub Actions workflows" - requirement_status = "required" - files = [ - { - name = "ci-joomla.yml" - extension = "yml" - description = "Joomla-specific CI workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/ci-joomla.yml.template" - }, - { - name = "codeql-analysis.yml" - extension = "yml" - description = "CodeQL security analysis workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/generic/codeql-analysis.yml.template" - }, - { - name = "standards-compliance.yml" - extension = "yml" - description = "MokoStandards compliance validation" - requirement_status = "required" - always_overwrite = true - template = ".github/workflows/standards-compliance.yml" - }, - { - name = "enterprise-firewall-setup.yml" - extension = "yml" - description = "Enterprise firewall configuration for trusted domain access" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/enterprise-firewall-setup.yml.template" - }, - { - name = "deploy-dev.yml" - extension = "yml" - description = "SFTP deployment of src/ to the development server" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-dev.yml.template" - }, - { - name = "deploy-demo.yml" - extension = "yml" - description = "SFTP deployment of src/ to the demo server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-demo.yml.template" - }, - { - name = "deploy-rs.yml" - extension = "yml" - description = "SFTP deployment of src/ to the release staging server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-rs.yml.template" - }, - { - name = "sync-version-on-merge.yml" - extension = "yml" - description = "Auto-bump patch version on merge and propagate to all file headers" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/sync-version-on-merge.yml.template" - }, - { - name = "auto-release.yml" - extension = "yml" - description = "Auto-create GitHub Release on push to main with version from README.md" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/auto-release.yml.template" - }, - { - name = "repository-cleanup.yml" - extension = "yml" - description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/repository-cleanup.yml.template" - }, - { - name = "auto-dev-issue.yml" - extension = "yml" - description = "Auto-create tracking issue when a dev/** branch is pushed" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/auto-dev-issue.yml.template" - }, - { - name = "repo_health.yml" - extension = "yml" - description = "Joomla-specific repository health check workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/repo_health.yml.template" - } - ] - }, - { - name = "ISSUE_TEMPLATE" - path = ".github/ISSUE_TEMPLATE" - description = "GitHub issue templates synced from MokoStandards" - requirement_status = "required" - files = [ - { - name = "config.yml" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/config.yml" - }, - { - name = "adr.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/adr.md" - }, - { - name = "bug_report.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/bug_report.md" - }, - { - name = "documentation.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/documentation.md" - }, - { - name = "enterprise_support.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md" - }, - { - name = "feature_request.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/feature_request.md" - }, - { - name = "firewall-request.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/firewall-request.md" - }, - { - name = "question.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/question.md" - }, - { - name = "request-license.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/request-license.md" - }, - { - name = "rfc.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/rfc.md" - }, - { - name = "security.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/security.md" - }, - { - name = "joomla_issue.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/joomla_issue.md" - } - ] - } - ] - } - ] - - repository_requirements = { - secrets = [ - { - name = "GH_TOKEN" - description = "Org-level GitHub PAT for automation" - required = true - scope = "org" - }, - { - name = "DEV_FTP_KEY" - description = "SSH private key for SFTP dev deployment (preferred); if DEV_FTP_PASSWORD is also set it is used as the key passphrase, with password-only as fallback" - required = false - scope = "org" - }, - { - name = "DEV_FTP_PASSWORD" - description = "SFTP password for dev deployment; used as SSH key passphrase when DEV_FTP_KEY is also set, and as standalone fallback if key auth fails" - required = false - scope = "org" - note = "At least one of DEV_FTP_KEY or DEV_FTP_PASSWORD must be configured" - } - ] - - variables = [ - { - name = "DEV_FTP_HOST" - description = "Dev server hostname; may include port suffix (e.g. dev.example.com or dev.example.com:2222)" - required = true - scope = "org" - }, - { - name = "DEV_FTP_PATH" - description = "Base remote path for SFTP deployment (e.g. /var/www/html)" - required = true - scope = "org" - }, - { - name = "DEV_FTP_USERNAME" - description = "SFTP username for dev server authentication" - required = true - scope = "org" - }, - { - name = "DEV_FTP_PORT" - description = "Explicit SFTP port override; if omitted the port is parsed from DEV_FTP_HOST or defaults to 22" - required = false - scope = "org" - }, - { - name = "DEV_FTP_SUFFIX" - description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /my-extension)" - required = false - scope = "repo" - } - ] - } - } -} diff --git a/definitions/sync/MokoStandards-Template-Joomla-Module.def.tf b/definitions/sync/MokoStandards-Template-Joomla-Module.def.tf deleted file mode 100644 index 317bc66..0000000 --- a/definitions/sync/MokoStandards-Template-Joomla-Module.def.tf +++ /dev/null @@ -1,1335 +0,0 @@ -/** - * Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Joomla-Module - * - * Auto-generated by MokoStandards bulk sync on 2026-04-02T15:29:31+00:00 - * Platform : waas-component - * Description: A repo template for a Joomla Module coding project according to MokoStandards - * - * DO NOT EDIT MANUALLY — this file is regenerated on every successful sync. - * To change what gets synced, edit api/definitions/default/waas-component.tf - * and re-run the bulk-repo-sync workflow. - */ - -locals { - sync_record = { - metadata = { - repo = "mokoconsulting-tech/MokoStandards-Template-Joomla-Module" - default_branch = "main" - detected_platform = "waas-component" - description = "A repo template for a Joomla Module coding project according to MokoStandards" - sync_timestamp = "2026-04-02T15:29:31+00:00" - source_repo = "mokoconsulting-tech/MokoStandards" - base_definition = "api/definitions/default/waas-component.tf" - } - - sync_stats = { - total_files = 41 - created_files = 4 - updated_files = 34 - skipped_files = 3 - } - - synced_files = [ - { path = "LICENSE" action = "updated" }, - { path = "SECURITY.md" action = "created" }, - { path = "CODE_OF_CONDUCT.md" action = "updated" }, - { path = "CONTRIBUTING.md" action = "updated" }, - { path = "update.xml" action = "updated" }, - { path = "phpstan.neon" action = "updated" }, - { path = "Makefile" action = "updated" }, - { path = ".gitignore" action = "updated" }, - { path = "composer.json" action = "updated" }, - { path = ".mokostandards" action = "created" }, - { path = "docs/update-server.md" action = "created" }, - { path = ".github/copilot.yml" action = "updated" }, - { path = ".github/copilot-instructions.md" action = "updated" }, - { path = ".github/CLAUDE.md" action = "updated" }, - { path = ".github/workflows/codeql-analysis.yml" action = "updated" }, - { path = ".github/workflows/standards-compliance.yml" action = "updated" }, - { path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" }, - { path = ".github/workflows/deploy-dev.yml" action = "updated" }, - { path = ".github/workflows/deploy-demo.yml" action = "updated" }, - { path = ".github/workflows/deploy-rs.yml" action = "updated" }, - { path = ".github/workflows/sync-version-on-merge.yml" action = "updated" }, - { path = ".github/workflows/auto-release.yml" action = "updated" }, - { path = ".github/workflows/repository-cleanup.yml" action = "updated" }, - { path = ".github/workflows/auto-dev-issue.yml" action = "updated" }, - { path = ".github/workflows/repo_health.yml" action = "created" }, - { path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/joomla_issue.md" action = "updated" }, - { path = ".github/CODEOWNERS" action = "updated" }, - { path = ".github/.mokostandards" action = "migrated from root" }, - ] - - skipped_files = [ - { path = "GOVERNANCE.md" reason = "Preserved (always_overwrite=false)" }, - { path = ".github/workflows/ci-joomla.yml" reason = "Source file not found" }, - { path = ".github/workflows/custom/README.md" reason = "README — never overwritten" }, - ] - } -} - -# ---- Base platform definition (reference copy) ---- -/** - * MokoWaaS Component Structure Definition - * Standard repository structure for MokoWaaS (Joomla) components - * - * Copyright (C) 2026 Moko Consulting - * SPDX-License-Identifier: GPL-3.0-or-later - * Version: 04.05.00 - * Schema Version: 1.0 - */ - -locals { - repository_structure = { - metadata = { - name = "MokoWaaS Component" - description = "Standard repository structure for MokoWaaS (Joomla) components" - repository_type = "waas-component" - platform = "mokowaas" - last_updated = "2026-01-15T00:00:00Z" - maintainer = "Moko Consulting" - version = "04.05.00" - schema_version = "1.0" - } - - root_files = [ - { - name = "README.md" - extension = "md" - description = "Developer-focused documentation for contributors and maintainers" - required = true - always_overwrite = false - protected = true - audience = "developer" - }, - { - name = "LICENSE" - extension = "" - description = "License file (GPL-3.0-or-later) - Default for Joomla/WaaS components" - required = true - audience = "general" - template = "templates/licenses/GPL-3.0" - license_type = "GPL-3.0-or-later" - }, - { - name = "CHANGELOG.md" - extension = "md" - description = "Version history and changes" - required = true - audience = "general" - }, - { - name = "SECURITY.md" - extension = "md" - description = "Security policy and vulnerability reporting" - required = true - always_overwrite = true - template = "templates/docs/required/template-SECURITY.md" - audience = "general" - }, - { - name = "CODE_OF_CONDUCT.md" - extension = "md" - description = "Community code of conduct" - required = true - always_overwrite = true - template = "templates/docs/extra/template-CODE_OF_CONDUCT.md" - always_overwrite = true - audience = "contributor" - }, - { - name = "ROADMAP.md" - extension = "md" - description = "Project roadmap with version goals and milestones" - required = false - audience = "general" - }, - { - name = "CONTRIBUTING.md" - extension = "md" - description = "Contribution guidelines" - required = true - always_overwrite = true - template = "templates/docs/required/template-CONTRIBUTING.md" - audience = "contributor" - }, - { - name = "update.xml" - extension = "xml" - description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" - required = true - always_overwrite = false - audience = "developer" - template = "templates/joomla/update.xml.template" - stub_content = <<-MOKO_END - - - - {{EXTENSION_NAME}} - {{REPO_NAME}} — Moko Consulting Joomla extension - {{EXTENSION_ELEMENT}} - {{EXTENSION_TYPE}} - {{VERSION}} - {{REPO_URL}}/releases/tag/{{VERSION}} - - {{DOWNLOAD_URL}} - - - 7.4 - Moko Consulting - {{MAINTAINER_URL}} - - - MOKO_END - }, - { - name = "phpstan.neon" - extension = "neon" - description = "PHPStan static analysis config with Joomla framework class stubs" - required = true - always_overwrite = true - audience = "developer" - template = "templates/configs/phpstan.joomla.neon" - }, - { - name = "Makefile" - description = "Build automation using MokoStandards templates" - required = true - always_overwrite = true - audience = "developer" - source_path = "templates/makefiles" - source_filename = "Makefile.joomla.template" - source_type = "template" - destination_path = "." - destination_filename = "Makefile" - create_path = false - template = "templates/makefiles/Makefile.joomla.template" - }, - { - name = ".gitignore" - extension = "gitignore" - description = "Git ignore patterns for Joomla development - preserved during sync operations" - required = true - always_overwrite = false - audience = "developer" - template = "templates/configs/.gitignore.joomla" - validation_rules = [ - { - type = "content-pattern" - description = "Must contain sftp-config pattern to ignore SFTP sync configuration files" - pattern = "sftp-config" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain user.css pattern to ignore custom user CSS overrides" - pattern = "user\\.css" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain user.js pattern to ignore custom user JavaScript overrides" - pattern = "user\\.js" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain modulebuilder.txt pattern to ignore Joomla Module Builder artifacts" - pattern = "modulebuilder\\.txt" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain colors_custom.css pattern to ignore custom color scheme overrides" - pattern = "colors_custom\\.css" - severity = "error" - } - ] - }, - { - name = ".gitattributes" - extension = "gitattributes" - description = "Git attributes configuration" - required = true - audience = "developer" - }, - { - name = ".editorconfig" - extension = "editorconfig" - description = "Editor configuration for consistent coding style - preserved during sync" - required = true - always_overwrite = false - audience = "developer" - }, - { - name = "composer.json" - extension = "json" - description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling" - required = true - always_overwrite = false - audience = "developer" - template = "templates/configs/composer.joomla.json" - }, - { - name = ".mokostandards" - extension = "yml" - description = "MokoStandards governance attachment — links this repo back to the standards source" - required = true - always_overwrite = true - audience = "developer" - template = "templates/configs/mokostandards.yml.template" - }, - { - name = "GOVERNANCE.md" - extension = "md" - description = "Project governance rules, roles, and decision process — auto-maintained by MokoStandards" - required = true - always_overwrite = false - protected = true - audience = "all" - template = "templates/docs/required/GOVERNANCE.md" - } - ] - - directories = [ - { - name = "site" - path = "site" - description = "Component frontend (site) code" - required = true - purpose = "Contains frontend component code deployed to site" - files = [ - { - name = "controller.php" - extension = "php" - description = "Main site controller" - required = true - audience = "developer" - }, - { - name = "manifest.xml" - extension = "xml" - description = "Component manifest for site" - required = true - audience = "developer" - } - ] - subdirectories = [ - { - name = "controllers" - path = "site/controllers" - description = "Site controllers" - requirement_status = "suggested" - }, - { - name = "models" - path = "site/models" - description = "Site models" - requirement_status = "suggested" - }, - { - name = "views" - path = "site/views" - description = "Site views" - required = true - } - ] - }, - { - name = "admin" - path = "admin" - description = "Component backend (admin) code" - required = true - purpose = "Contains backend component code for administrator" - files = [ - { - name = "controller.php" - extension = "php" - description = "Main admin controller" - required = true - audience = "developer" - } - ] - subdirectories = [ - { - name = "controllers" - path = "admin/controllers" - description = "Admin controllers" - requirement_status = "suggested" - }, - { - name = "models" - path = "admin/models" - description = "Admin models" - requirement_status = "suggested" - }, - { - name = "views" - path = "admin/views" - description = "Admin views" - required = true - }, - { - name = "sql" - path = "admin/sql" - description = "Database schema files" - requirement_status = "suggested" - } - ] - }, - { - name = "media" - path = "media" - description = "Media files (CSS, JS, images)" - requirement_status = "suggested" - purpose = "Contains static assets" - subdirectories = [ - { - name = "css" - path = "media/css" - description = "Stylesheets" - requirement_status = "suggested" - }, - { - name = "js" - path = "media/js" - description = "JavaScript files" - requirement_status = "suggested" - }, - { - name = "images" - path = "media/images" - description = "Image files" - requirement_status = "suggested" - } - ] - }, - { - name = "language" - path = "language" - description = "Language translation files" - required = true - purpose = "Contains language INI files" - }, - { - name = "docs" - path = "docs" - description = "Developer and technical documentation" - required = true - purpose = "Contains technical documentation, API docs, architecture diagrams" - files = [ - { - name = "index.md" - extension = "md" - description = "Documentation index" - required = true - }, - { - name = "update-server.md" - extension = "md" - description = "Joomla update server (update.xml) documentation" - required = true - always_overwrite = true - template = "templates/docs/required/template-update-server-joomla.md" - } - ] - }, - { - name = "scripts" - path = "scripts" - description = "Repo-specific scripts — not managed by MokoStandards sync" - required = false - purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/." - files = [ - { - name = "MokoStandards.override.xml" - extension = "xml" - description = "MokoStandards sync override configuration - preserved during sync" - requirement_status = "optional" - always_overwrite = false - audience = "developer" - } - ] - }, - { - name = "tests" - path = "tests" - description = "Test files" - required = true - purpose = "Contains unit tests, integration tests, and test fixtures" - subdirectories = [ - { - name = "unit" - path = "tests/unit" - description = "Unit tests" - required = true - }, - { - name = "integration" - path = "tests/integration" - description = "Integration tests" - requirement_status = "suggested" - } - ] - }, - { - name = ".github" - path = ".github" - description = "GitHub-specific configuration" - requirement_status = "suggested" - purpose = "Contains GitHub Actions workflows and configuration" - files = [ - { - name = "copilot.yml" - extension = "yml" - description = "GitHub Copilot allowed domains configuration" - requirement_status = "required" - always_overwrite = true - template = ".github/copilot.yml" - }, - { - name = "copilot-instructions.md" - extension = "md" - description = "GitHub Copilot custom instructions enforcing MokoStandards — Joomla/WaaS edition" - requirement_status = "required" - always_overwrite = false - destination_path = ".github" - destination_filename = "copilot-instructions.md" - template = "templates/github/copilot-instructions.joomla.md.template" - stub_content = <<-MOKO_END - > [!IMPORTANT] - > **🔧 AI Self-Update Required on First Use** - > - > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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://github.com/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 [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). 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 `update.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) - ├── update.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 - ├── .github/ - │ ├── 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 - ``` - - --- - - ## update.xml — Required in Repo Root - - `update.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 it via: - ```xml - - - {{REPO_URL}}/raw/main/update.xml - - - ``` - - **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. - - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - - `` — 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 `update.xml`. - - Must include `` block pointing to this repo's `update.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 }} - ``` - - --- - - ## MokoStandards Reference - - This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies: - - | Document | Purpose | - |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/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 `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.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, `update.xml` version, and `README.md` version go out of sync - MOKO_END - }, - { - name = "CLAUDE.md" - extension = "md" - description = "Claude AI assistant context enforcing MokoStandards — Joomla/WaaS edition" - requirement_status = "required" - always_overwrite = false - destination_path = ".github" - destination_filename = "CLAUDE.md" - template = "templates/github/CLAUDE.joomla.md.template" - stub_content = <<-MOKO_END - > [!IMPORTANT] - > **🔧 AI Self-Update Required on First Use** - > - > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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://github.com/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 [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) — 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) - ├── update.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 - ├── .github/ - │ ├── workflows/ # CI/CD workflows (synced from MokoStandards) - │ ├── 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 | - | `update.xml` | `` in the most recent `` block | - - The `make release` command / release workflow syncs all three automatically. - - --- - - # update.xml — Required in Repo Root - - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: - - ```xml - - - - {{REPO_URL}}/raw/main/update.xml - - - ``` - - **Rules:** - - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. - - `` must be a publicly accessible GitHub Releases asset URL. - - `` — backslash is literal (Joomla regex syntax). - - Example `update.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 - - - - 7.4 - 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 `update.xml` and `README.md` | - | New release | Prepend `` to `update.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`, `update.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; `update.xml` updated with new entry - - [ ] FILE INFORMATION headers updated in modified files - - [ ] CHANGELOG.md updated - - [ ] Tests pass - - --- - - # Key Policy Documents (MokoStandards) - - | Document | Purpose | - |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | - MOKO_END - } - ] - subdirectories = [ - { - name = "workflows" - path = ".github/workflows" - description = "GitHub Actions workflows" - requirement_status = "required" - files = [ - { - name = "ci-joomla.yml" - extension = "yml" - description = "Joomla-specific CI workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/ci-joomla.yml.template" - }, - { - name = "codeql-analysis.yml" - extension = "yml" - description = "CodeQL security analysis workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/generic/codeql-analysis.yml.template" - }, - { - name = "standards-compliance.yml" - extension = "yml" - description = "MokoStandards compliance validation" - requirement_status = "required" - always_overwrite = true - template = ".github/workflows/standards-compliance.yml" - }, - { - name = "enterprise-firewall-setup.yml" - extension = "yml" - description = "Enterprise firewall configuration for trusted domain access" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/enterprise-firewall-setup.yml.template" - }, - { - name = "deploy-dev.yml" - extension = "yml" - description = "SFTP deployment of src/ to the development server" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-dev.yml.template" - }, - { - name = "deploy-demo.yml" - extension = "yml" - description = "SFTP deployment of src/ to the demo server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-demo.yml.template" - }, - { - name = "deploy-rs.yml" - extension = "yml" - description = "SFTP deployment of src/ to the release staging server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-rs.yml.template" - }, - { - name = "sync-version-on-merge.yml" - extension = "yml" - description = "Auto-bump patch version on merge and propagate to all file headers" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/sync-version-on-merge.yml.template" - }, - { - name = "auto-release.yml" - extension = "yml" - description = "Auto-create GitHub Release on push to main with version from README.md" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/auto-release.yml.template" - }, - { - name = "repository-cleanup.yml" - extension = "yml" - description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/repository-cleanup.yml.template" - }, - { - name = "auto-dev-issue.yml" - extension = "yml" - description = "Auto-create tracking issue when a dev/** branch is pushed" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/auto-dev-issue.yml.template" - }, - { - name = "repo_health.yml" - extension = "yml" - description = "Joomla-specific repository health check workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/repo_health.yml.template" - } - ] - }, - { - name = "ISSUE_TEMPLATE" - path = ".github/ISSUE_TEMPLATE" - description = "GitHub issue templates synced from MokoStandards" - requirement_status = "required" - files = [ - { - name = "config.yml" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/config.yml" - }, - { - name = "adr.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/adr.md" - }, - { - name = "bug_report.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/bug_report.md" - }, - { - name = "documentation.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/documentation.md" - }, - { - name = "enterprise_support.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md" - }, - { - name = "feature_request.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/feature_request.md" - }, - { - name = "firewall-request.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/firewall-request.md" - }, - { - name = "question.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/question.md" - }, - { - name = "request-license.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/request-license.md" - }, - { - name = "rfc.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/rfc.md" - }, - { - name = "security.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/security.md" - }, - { - name = "joomla_issue.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/joomla_issue.md" - } - ] - } - ] - } - ] - - repository_requirements = { - secrets = [ - { - name = "GH_TOKEN" - description = "Org-level GitHub PAT for automation" - required = true - scope = "org" - }, - { - name = "DEV_FTP_KEY" - description = "SSH private key for SFTP dev deployment (preferred); if DEV_FTP_PASSWORD is also set it is used as the key passphrase, with password-only as fallback" - required = false - scope = "org" - }, - { - name = "DEV_FTP_PASSWORD" - description = "SFTP password for dev deployment; used as SSH key passphrase when DEV_FTP_KEY is also set, and as standalone fallback if key auth fails" - required = false - scope = "org" - note = "At least one of DEV_FTP_KEY or DEV_FTP_PASSWORD must be configured" - } - ] - - variables = [ - { - name = "DEV_FTP_HOST" - description = "Dev server hostname; may include port suffix (e.g. dev.example.com or dev.example.com:2222)" - required = true - scope = "org" - }, - { - name = "DEV_FTP_PATH" - description = "Base remote path for SFTP deployment (e.g. /var/www/html)" - required = true - scope = "org" - }, - { - name = "DEV_FTP_USERNAME" - description = "SFTP username for dev server authentication" - required = true - scope = "org" - }, - { - name = "DEV_FTP_PORT" - description = "Explicit SFTP port override; if omitted the port is parsed from DEV_FTP_HOST or defaults to 22" - required = false - scope = "org" - }, - { - name = "DEV_FTP_SUFFIX" - description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /my-extension)" - required = false - scope = "repo" - } - ] - } - } -} diff --git a/definitions/sync/MokoStandards-Template-Joomla-Package.def.tf b/definitions/sync/MokoStandards-Template-Joomla-Package.def.tf deleted file mode 100644 index 10f1819..0000000 --- a/definitions/sync/MokoStandards-Template-Joomla-Package.def.tf +++ /dev/null @@ -1,1335 +0,0 @@ -/** - * Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Joomla-Package - * - * Auto-generated by MokoStandards bulk sync on 2026-04-02T15:30:39+00:00 - * Platform : waas-component - * Description: A repo template for a Joomla Package coding project according to MokoStandards - * - * DO NOT EDIT MANUALLY — this file is regenerated on every successful sync. - * To change what gets synced, edit api/definitions/default/waas-component.tf - * and re-run the bulk-repo-sync workflow. - */ - -locals { - sync_record = { - metadata = { - repo = "mokoconsulting-tech/MokoStandards-Template-Joomla-Package" - default_branch = "main" - detected_platform = "waas-component" - description = "A repo template for a Joomla Package coding project according to MokoStandards" - sync_timestamp = "2026-04-02T15:30:39+00:00" - source_repo = "mokoconsulting-tech/MokoStandards" - base_definition = "api/definitions/default/waas-component.tf" - } - - sync_stats = { - total_files = 41 - created_files = 3 - updated_files = 35 - skipped_files = 3 - } - - synced_files = [ - { path = "LICENSE" action = "updated" }, - { path = "SECURITY.md" action = "updated" }, - { path = "CODE_OF_CONDUCT.md" action = "updated" }, - { path = "CONTRIBUTING.md" action = "updated" }, - { path = "update.xml" action = "updated" }, - { path = "phpstan.neon" action = "updated" }, - { path = "Makefile" action = "updated" }, - { path = ".gitignore" action = "updated" }, - { path = "composer.json" action = "updated" }, - { path = ".mokostandards" action = "created" }, - { path = "docs/update-server.md" action = "created" }, - { path = ".github/copilot.yml" action = "updated" }, - { path = ".github/copilot-instructions.md" action = "updated" }, - { path = ".github/CLAUDE.md" action = "updated" }, - { path = ".github/workflows/codeql-analysis.yml" action = "updated" }, - { path = ".github/workflows/standards-compliance.yml" action = "updated" }, - { path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" }, - { path = ".github/workflows/deploy-dev.yml" action = "updated" }, - { path = ".github/workflows/deploy-demo.yml" action = "updated" }, - { path = ".github/workflows/deploy-rs.yml" action = "updated" }, - { path = ".github/workflows/sync-version-on-merge.yml" action = "updated" }, - { path = ".github/workflows/auto-release.yml" action = "updated" }, - { path = ".github/workflows/repository-cleanup.yml" action = "updated" }, - { path = ".github/workflows/auto-dev-issue.yml" action = "updated" }, - { path = ".github/workflows/repo_health.yml" action = "created" }, - { path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/joomla_issue.md" action = "updated" }, - { path = ".github/CODEOWNERS" action = "updated" }, - { path = ".github/.mokostandards" action = "migrated from root" }, - ] - - skipped_files = [ - { path = "GOVERNANCE.md" reason = "Preserved (always_overwrite=false)" }, - { path = ".github/workflows/ci-joomla.yml" reason = "Source file not found" }, - { path = ".github/workflows/custom/README.md" reason = "README — never overwritten" }, - ] - } -} - -# ---- Base platform definition (reference copy) ---- -/** - * MokoWaaS Component Structure Definition - * Standard repository structure for MokoWaaS (Joomla) components - * - * Copyright (C) 2026 Moko Consulting - * SPDX-License-Identifier: GPL-3.0-or-later - * Version: 04.05.00 - * Schema Version: 1.0 - */ - -locals { - repository_structure = { - metadata = { - name = "MokoWaaS Component" - description = "Standard repository structure for MokoWaaS (Joomla) components" - repository_type = "waas-component" - platform = "mokowaas" - last_updated = "2026-01-15T00:00:00Z" - maintainer = "Moko Consulting" - version = "04.05.00" - schema_version = "1.0" - } - - root_files = [ - { - name = "README.md" - extension = "md" - description = "Developer-focused documentation for contributors and maintainers" - required = true - always_overwrite = false - protected = true - audience = "developer" - }, - { - name = "LICENSE" - extension = "" - description = "License file (GPL-3.0-or-later) - Default for Joomla/WaaS components" - required = true - audience = "general" - template = "templates/licenses/GPL-3.0" - license_type = "GPL-3.0-or-later" - }, - { - name = "CHANGELOG.md" - extension = "md" - description = "Version history and changes" - required = true - audience = "general" - }, - { - name = "SECURITY.md" - extension = "md" - description = "Security policy and vulnerability reporting" - required = true - always_overwrite = true - template = "templates/docs/required/template-SECURITY.md" - audience = "general" - }, - { - name = "CODE_OF_CONDUCT.md" - extension = "md" - description = "Community code of conduct" - required = true - always_overwrite = true - template = "templates/docs/extra/template-CODE_OF_CONDUCT.md" - always_overwrite = true - audience = "contributor" - }, - { - name = "ROADMAP.md" - extension = "md" - description = "Project roadmap with version goals and milestones" - required = false - audience = "general" - }, - { - name = "CONTRIBUTING.md" - extension = "md" - description = "Contribution guidelines" - required = true - always_overwrite = true - template = "templates/docs/required/template-CONTRIBUTING.md" - audience = "contributor" - }, - { - name = "update.xml" - extension = "xml" - description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" - required = true - always_overwrite = false - audience = "developer" - template = "templates/joomla/update.xml.template" - stub_content = <<-MOKO_END - - - - {{EXTENSION_NAME}} - {{REPO_NAME}} — Moko Consulting Joomla extension - {{EXTENSION_ELEMENT}} - {{EXTENSION_TYPE}} - {{VERSION}} - {{REPO_URL}}/releases/tag/{{VERSION}} - - {{DOWNLOAD_URL}} - - - 7.4 - Moko Consulting - {{MAINTAINER_URL}} - - - MOKO_END - }, - { - name = "phpstan.neon" - extension = "neon" - description = "PHPStan static analysis config with Joomla framework class stubs" - required = true - always_overwrite = true - audience = "developer" - template = "templates/configs/phpstan.joomla.neon" - }, - { - name = "Makefile" - description = "Build automation using MokoStandards templates" - required = true - always_overwrite = true - audience = "developer" - source_path = "templates/makefiles" - source_filename = "Makefile.joomla.template" - source_type = "template" - destination_path = "." - destination_filename = "Makefile" - create_path = false - template = "templates/makefiles/Makefile.joomla.template" - }, - { - name = ".gitignore" - extension = "gitignore" - description = "Git ignore patterns for Joomla development - preserved during sync operations" - required = true - always_overwrite = false - audience = "developer" - template = "templates/configs/.gitignore.joomla" - validation_rules = [ - { - type = "content-pattern" - description = "Must contain sftp-config pattern to ignore SFTP sync configuration files" - pattern = "sftp-config" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain user.css pattern to ignore custom user CSS overrides" - pattern = "user\\.css" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain user.js pattern to ignore custom user JavaScript overrides" - pattern = "user\\.js" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain modulebuilder.txt pattern to ignore Joomla Module Builder artifacts" - pattern = "modulebuilder\\.txt" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain colors_custom.css pattern to ignore custom color scheme overrides" - pattern = "colors_custom\\.css" - severity = "error" - } - ] - }, - { - name = ".gitattributes" - extension = "gitattributes" - description = "Git attributes configuration" - required = true - audience = "developer" - }, - { - name = ".editorconfig" - extension = "editorconfig" - description = "Editor configuration for consistent coding style - preserved during sync" - required = true - always_overwrite = false - audience = "developer" - }, - { - name = "composer.json" - extension = "json" - description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling" - required = true - always_overwrite = false - audience = "developer" - template = "templates/configs/composer.joomla.json" - }, - { - name = ".mokostandards" - extension = "yml" - description = "MokoStandards governance attachment — links this repo back to the standards source" - required = true - always_overwrite = true - audience = "developer" - template = "templates/configs/mokostandards.yml.template" - }, - { - name = "GOVERNANCE.md" - extension = "md" - description = "Project governance rules, roles, and decision process — auto-maintained by MokoStandards" - required = true - always_overwrite = false - protected = true - audience = "all" - template = "templates/docs/required/GOVERNANCE.md" - } - ] - - directories = [ - { - name = "site" - path = "site" - description = "Component frontend (site) code" - required = true - purpose = "Contains frontend component code deployed to site" - files = [ - { - name = "controller.php" - extension = "php" - description = "Main site controller" - required = true - audience = "developer" - }, - { - name = "manifest.xml" - extension = "xml" - description = "Component manifest for site" - required = true - audience = "developer" - } - ] - subdirectories = [ - { - name = "controllers" - path = "site/controllers" - description = "Site controllers" - requirement_status = "suggested" - }, - { - name = "models" - path = "site/models" - description = "Site models" - requirement_status = "suggested" - }, - { - name = "views" - path = "site/views" - description = "Site views" - required = true - } - ] - }, - { - name = "admin" - path = "admin" - description = "Component backend (admin) code" - required = true - purpose = "Contains backend component code for administrator" - files = [ - { - name = "controller.php" - extension = "php" - description = "Main admin controller" - required = true - audience = "developer" - } - ] - subdirectories = [ - { - name = "controllers" - path = "admin/controllers" - description = "Admin controllers" - requirement_status = "suggested" - }, - { - name = "models" - path = "admin/models" - description = "Admin models" - requirement_status = "suggested" - }, - { - name = "views" - path = "admin/views" - description = "Admin views" - required = true - }, - { - name = "sql" - path = "admin/sql" - description = "Database schema files" - requirement_status = "suggested" - } - ] - }, - { - name = "media" - path = "media" - description = "Media files (CSS, JS, images)" - requirement_status = "suggested" - purpose = "Contains static assets" - subdirectories = [ - { - name = "css" - path = "media/css" - description = "Stylesheets" - requirement_status = "suggested" - }, - { - name = "js" - path = "media/js" - description = "JavaScript files" - requirement_status = "suggested" - }, - { - name = "images" - path = "media/images" - description = "Image files" - requirement_status = "suggested" - } - ] - }, - { - name = "language" - path = "language" - description = "Language translation files" - required = true - purpose = "Contains language INI files" - }, - { - name = "docs" - path = "docs" - description = "Developer and technical documentation" - required = true - purpose = "Contains technical documentation, API docs, architecture diagrams" - files = [ - { - name = "index.md" - extension = "md" - description = "Documentation index" - required = true - }, - { - name = "update-server.md" - extension = "md" - description = "Joomla update server (update.xml) documentation" - required = true - always_overwrite = true - template = "templates/docs/required/template-update-server-joomla.md" - } - ] - }, - { - name = "scripts" - path = "scripts" - description = "Repo-specific scripts — not managed by MokoStandards sync" - required = false - purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/." - files = [ - { - name = "MokoStandards.override.xml" - extension = "xml" - description = "MokoStandards sync override configuration - preserved during sync" - requirement_status = "optional" - always_overwrite = false - audience = "developer" - } - ] - }, - { - name = "tests" - path = "tests" - description = "Test files" - required = true - purpose = "Contains unit tests, integration tests, and test fixtures" - subdirectories = [ - { - name = "unit" - path = "tests/unit" - description = "Unit tests" - required = true - }, - { - name = "integration" - path = "tests/integration" - description = "Integration tests" - requirement_status = "suggested" - } - ] - }, - { - name = ".github" - path = ".github" - description = "GitHub-specific configuration" - requirement_status = "suggested" - purpose = "Contains GitHub Actions workflows and configuration" - files = [ - { - name = "copilot.yml" - extension = "yml" - description = "GitHub Copilot allowed domains configuration" - requirement_status = "required" - always_overwrite = true - template = ".github/copilot.yml" - }, - { - name = "copilot-instructions.md" - extension = "md" - description = "GitHub Copilot custom instructions enforcing MokoStandards — Joomla/WaaS edition" - requirement_status = "required" - always_overwrite = false - destination_path = ".github" - destination_filename = "copilot-instructions.md" - template = "templates/github/copilot-instructions.joomla.md.template" - stub_content = <<-MOKO_END - > [!IMPORTANT] - > **🔧 AI Self-Update Required on First Use** - > - > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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://github.com/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 [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). 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 `update.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) - ├── update.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 - ├── .github/ - │ ├── 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 - ``` - - --- - - ## update.xml — Required in Repo Root - - `update.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 it via: - ```xml - - - {{REPO_URL}}/raw/main/update.xml - - - ``` - - **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. - - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - - `` — 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 `update.xml`. - - Must include `` block pointing to this repo's `update.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 }} - ``` - - --- - - ## MokoStandards Reference - - This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies: - - | Document | Purpose | - |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/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 `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.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, `update.xml` version, and `README.md` version go out of sync - MOKO_END - }, - { - name = "CLAUDE.md" - extension = "md" - description = "Claude AI assistant context enforcing MokoStandards — Joomla/WaaS edition" - requirement_status = "required" - always_overwrite = false - destination_path = ".github" - destination_filename = "CLAUDE.md" - template = "templates/github/CLAUDE.joomla.md.template" - stub_content = <<-MOKO_END - > [!IMPORTANT] - > **🔧 AI Self-Update Required on First Use** - > - > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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://github.com/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 [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) — 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) - ├── update.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 - ├── .github/ - │ ├── workflows/ # CI/CD workflows (synced from MokoStandards) - │ ├── 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 | - | `update.xml` | `` in the most recent `` block | - - The `make release` command / release workflow syncs all three automatically. - - --- - - # update.xml — Required in Repo Root - - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: - - ```xml - - - - {{REPO_URL}}/raw/main/update.xml - - - ``` - - **Rules:** - - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. - - `` must be a publicly accessible GitHub Releases asset URL. - - `` — backslash is literal (Joomla regex syntax). - - Example `update.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 - - - - 7.4 - 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 `update.xml` and `README.md` | - | New release | Prepend `` to `update.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`, `update.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; `update.xml` updated with new entry - - [ ] FILE INFORMATION headers updated in modified files - - [ ] CHANGELOG.md updated - - [ ] Tests pass - - --- - - # Key Policy Documents (MokoStandards) - - | Document | Purpose | - |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | - MOKO_END - } - ] - subdirectories = [ - { - name = "workflows" - path = ".github/workflows" - description = "GitHub Actions workflows" - requirement_status = "required" - files = [ - { - name = "ci-joomla.yml" - extension = "yml" - description = "Joomla-specific CI workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/ci-joomla.yml.template" - }, - { - name = "codeql-analysis.yml" - extension = "yml" - description = "CodeQL security analysis workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/generic/codeql-analysis.yml.template" - }, - { - name = "standards-compliance.yml" - extension = "yml" - description = "MokoStandards compliance validation" - requirement_status = "required" - always_overwrite = true - template = ".github/workflows/standards-compliance.yml" - }, - { - name = "enterprise-firewall-setup.yml" - extension = "yml" - description = "Enterprise firewall configuration for trusted domain access" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/enterprise-firewall-setup.yml.template" - }, - { - name = "deploy-dev.yml" - extension = "yml" - description = "SFTP deployment of src/ to the development server" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-dev.yml.template" - }, - { - name = "deploy-demo.yml" - extension = "yml" - description = "SFTP deployment of src/ to the demo server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-demo.yml.template" - }, - { - name = "deploy-rs.yml" - extension = "yml" - description = "SFTP deployment of src/ to the release staging server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-rs.yml.template" - }, - { - name = "sync-version-on-merge.yml" - extension = "yml" - description = "Auto-bump patch version on merge and propagate to all file headers" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/sync-version-on-merge.yml.template" - }, - { - name = "auto-release.yml" - extension = "yml" - description = "Auto-create GitHub Release on push to main with version from README.md" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/auto-release.yml.template" - }, - { - name = "repository-cleanup.yml" - extension = "yml" - description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/repository-cleanup.yml.template" - }, - { - name = "auto-dev-issue.yml" - extension = "yml" - description = "Auto-create tracking issue when a dev/** branch is pushed" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/auto-dev-issue.yml.template" - }, - { - name = "repo_health.yml" - extension = "yml" - description = "Joomla-specific repository health check workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/repo_health.yml.template" - } - ] - }, - { - name = "ISSUE_TEMPLATE" - path = ".github/ISSUE_TEMPLATE" - description = "GitHub issue templates synced from MokoStandards" - requirement_status = "required" - files = [ - { - name = "config.yml" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/config.yml" - }, - { - name = "adr.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/adr.md" - }, - { - name = "bug_report.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/bug_report.md" - }, - { - name = "documentation.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/documentation.md" - }, - { - name = "enterprise_support.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md" - }, - { - name = "feature_request.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/feature_request.md" - }, - { - name = "firewall-request.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/firewall-request.md" - }, - { - name = "question.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/question.md" - }, - { - name = "request-license.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/request-license.md" - }, - { - name = "rfc.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/rfc.md" - }, - { - name = "security.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/security.md" - }, - { - name = "joomla_issue.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/joomla_issue.md" - } - ] - } - ] - } - ] - - repository_requirements = { - secrets = [ - { - name = "GH_TOKEN" - description = "Org-level GitHub PAT for automation" - required = true - scope = "org" - }, - { - name = "DEV_FTP_KEY" - description = "SSH private key for SFTP dev deployment (preferred); if DEV_FTP_PASSWORD is also set it is used as the key passphrase, with password-only as fallback" - required = false - scope = "org" - }, - { - name = "DEV_FTP_PASSWORD" - description = "SFTP password for dev deployment; used as SSH key passphrase when DEV_FTP_KEY is also set, and as standalone fallback if key auth fails" - required = false - scope = "org" - note = "At least one of DEV_FTP_KEY or DEV_FTP_PASSWORD must be configured" - } - ] - - variables = [ - { - name = "DEV_FTP_HOST" - description = "Dev server hostname; may include port suffix (e.g. dev.example.com or dev.example.com:2222)" - required = true - scope = "org" - }, - { - name = "DEV_FTP_PATH" - description = "Base remote path for SFTP deployment (e.g. /var/www/html)" - required = true - scope = "org" - }, - { - name = "DEV_FTP_USERNAME" - description = "SFTP username for dev server authentication" - required = true - scope = "org" - }, - { - name = "DEV_FTP_PORT" - description = "Explicit SFTP port override; if omitted the port is parsed from DEV_FTP_HOST or defaults to 22" - required = false - scope = "org" - }, - { - name = "DEV_FTP_SUFFIX" - description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /my-extension)" - required = false - scope = "repo" - } - ] - } - } -} diff --git a/definitions/sync/MokoStandards-Template-Joomla-Plugin.def.tf b/definitions/sync/MokoStandards-Template-Joomla-Plugin.def.tf deleted file mode 100644 index a52cd3b..0000000 --- a/definitions/sync/MokoStandards-Template-Joomla-Plugin.def.tf +++ /dev/null @@ -1,1335 +0,0 @@ -/** - * Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Joomla-Plugin - * - * Auto-generated by MokoStandards bulk sync on 2026-04-02T15:31:55+00:00 - * Platform : waas-component - * Description: A repo template for a Joomla Plugin coding project according to MokoStandards - * - * DO NOT EDIT MANUALLY — this file is regenerated on every successful sync. - * To change what gets synced, edit api/definitions/default/waas-component.tf - * and re-run the bulk-repo-sync workflow. - */ - -locals { - sync_record = { - metadata = { - repo = "mokoconsulting-tech/MokoStandards-Template-Joomla-Plugin" - default_branch = "main" - detected_platform = "waas-component" - description = "A repo template for a Joomla Plugin coding project according to MokoStandards" - sync_timestamp = "2026-04-02T15:31:55+00:00" - source_repo = "mokoconsulting-tech/MokoStandards" - base_definition = "api/definitions/default/waas-component.tf" - } - - sync_stats = { - total_files = 41 - created_files = 3 - updated_files = 35 - skipped_files = 3 - } - - synced_files = [ - { path = "LICENSE" action = "updated" }, - { path = "SECURITY.md" action = "updated" }, - { path = "CODE_OF_CONDUCT.md" action = "updated" }, - { path = "CONTRIBUTING.md" action = "updated" }, - { path = "update.xml" action = "updated" }, - { path = "phpstan.neon" action = "updated" }, - { path = "Makefile" action = "updated" }, - { path = ".gitignore" action = "updated" }, - { path = "composer.json" action = "updated" }, - { path = ".mokostandards" action = "created" }, - { path = "docs/update-server.md" action = "created" }, - { path = ".github/copilot.yml" action = "updated" }, - { path = ".github/copilot-instructions.md" action = "updated" }, - { path = ".github/CLAUDE.md" action = "updated" }, - { path = ".github/workflows/codeql-analysis.yml" action = "updated" }, - { path = ".github/workflows/standards-compliance.yml" action = "updated" }, - { path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" }, - { path = ".github/workflows/deploy-dev.yml" action = "updated" }, - { path = ".github/workflows/deploy-demo.yml" action = "updated" }, - { path = ".github/workflows/deploy-rs.yml" action = "updated" }, - { path = ".github/workflows/sync-version-on-merge.yml" action = "updated" }, - { path = ".github/workflows/auto-release.yml" action = "updated" }, - { path = ".github/workflows/repository-cleanup.yml" action = "updated" }, - { path = ".github/workflows/auto-dev-issue.yml" action = "updated" }, - { path = ".github/workflows/repo_health.yml" action = "created" }, - { path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/joomla_issue.md" action = "updated" }, - { path = ".github/CODEOWNERS" action = "updated" }, - { path = ".github/.mokostandards" action = "migrated from root" }, - ] - - skipped_files = [ - { path = "GOVERNANCE.md" reason = "Preserved (always_overwrite=false)" }, - { path = ".github/workflows/ci-joomla.yml" reason = "Source file not found" }, - { path = ".github/workflows/custom/README.md" reason = "README — never overwritten" }, - ] - } -} - -# ---- Base platform definition (reference copy) ---- -/** - * MokoWaaS Component Structure Definition - * Standard repository structure for MokoWaaS (Joomla) components - * - * Copyright (C) 2026 Moko Consulting - * SPDX-License-Identifier: GPL-3.0-or-later - * Version: 04.05.00 - * Schema Version: 1.0 - */ - -locals { - repository_structure = { - metadata = { - name = "MokoWaaS Component" - description = "Standard repository structure for MokoWaaS (Joomla) components" - repository_type = "waas-component" - platform = "mokowaas" - last_updated = "2026-01-15T00:00:00Z" - maintainer = "Moko Consulting" - version = "04.05.00" - schema_version = "1.0" - } - - root_files = [ - { - name = "README.md" - extension = "md" - description = "Developer-focused documentation for contributors and maintainers" - required = true - always_overwrite = false - protected = true - audience = "developer" - }, - { - name = "LICENSE" - extension = "" - description = "License file (GPL-3.0-or-later) - Default for Joomla/WaaS components" - required = true - audience = "general" - template = "templates/licenses/GPL-3.0" - license_type = "GPL-3.0-or-later" - }, - { - name = "CHANGELOG.md" - extension = "md" - description = "Version history and changes" - required = true - audience = "general" - }, - { - name = "SECURITY.md" - extension = "md" - description = "Security policy and vulnerability reporting" - required = true - always_overwrite = true - template = "templates/docs/required/template-SECURITY.md" - audience = "general" - }, - { - name = "CODE_OF_CONDUCT.md" - extension = "md" - description = "Community code of conduct" - required = true - always_overwrite = true - template = "templates/docs/extra/template-CODE_OF_CONDUCT.md" - always_overwrite = true - audience = "contributor" - }, - { - name = "ROADMAP.md" - extension = "md" - description = "Project roadmap with version goals and milestones" - required = false - audience = "general" - }, - { - name = "CONTRIBUTING.md" - extension = "md" - description = "Contribution guidelines" - required = true - always_overwrite = true - template = "templates/docs/required/template-CONTRIBUTING.md" - audience = "contributor" - }, - { - name = "update.xml" - extension = "xml" - description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" - required = true - always_overwrite = false - audience = "developer" - template = "templates/joomla/update.xml.template" - stub_content = <<-MOKO_END - - - - {{EXTENSION_NAME}} - {{REPO_NAME}} — Moko Consulting Joomla extension - {{EXTENSION_ELEMENT}} - {{EXTENSION_TYPE}} - {{VERSION}} - {{REPO_URL}}/releases/tag/{{VERSION}} - - {{DOWNLOAD_URL}} - - - 7.4 - Moko Consulting - {{MAINTAINER_URL}} - - - MOKO_END - }, - { - name = "phpstan.neon" - extension = "neon" - description = "PHPStan static analysis config with Joomla framework class stubs" - required = true - always_overwrite = true - audience = "developer" - template = "templates/configs/phpstan.joomla.neon" - }, - { - name = "Makefile" - description = "Build automation using MokoStandards templates" - required = true - always_overwrite = true - audience = "developer" - source_path = "templates/makefiles" - source_filename = "Makefile.joomla.template" - source_type = "template" - destination_path = "." - destination_filename = "Makefile" - create_path = false - template = "templates/makefiles/Makefile.joomla.template" - }, - { - name = ".gitignore" - extension = "gitignore" - description = "Git ignore patterns for Joomla development - preserved during sync operations" - required = true - always_overwrite = false - audience = "developer" - template = "templates/configs/.gitignore.joomla" - validation_rules = [ - { - type = "content-pattern" - description = "Must contain sftp-config pattern to ignore SFTP sync configuration files" - pattern = "sftp-config" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain user.css pattern to ignore custom user CSS overrides" - pattern = "user\\.css" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain user.js pattern to ignore custom user JavaScript overrides" - pattern = "user\\.js" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain modulebuilder.txt pattern to ignore Joomla Module Builder artifacts" - pattern = "modulebuilder\\.txt" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain colors_custom.css pattern to ignore custom color scheme overrides" - pattern = "colors_custom\\.css" - severity = "error" - } - ] - }, - { - name = ".gitattributes" - extension = "gitattributes" - description = "Git attributes configuration" - required = true - audience = "developer" - }, - { - name = ".editorconfig" - extension = "editorconfig" - description = "Editor configuration for consistent coding style - preserved during sync" - required = true - always_overwrite = false - audience = "developer" - }, - { - name = "composer.json" - extension = "json" - description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling" - required = true - always_overwrite = false - audience = "developer" - template = "templates/configs/composer.joomla.json" - }, - { - name = ".mokostandards" - extension = "yml" - description = "MokoStandards governance attachment — links this repo back to the standards source" - required = true - always_overwrite = true - audience = "developer" - template = "templates/configs/mokostandards.yml.template" - }, - { - name = "GOVERNANCE.md" - extension = "md" - description = "Project governance rules, roles, and decision process — auto-maintained by MokoStandards" - required = true - always_overwrite = false - protected = true - audience = "all" - template = "templates/docs/required/GOVERNANCE.md" - } - ] - - directories = [ - { - name = "site" - path = "site" - description = "Component frontend (site) code" - required = true - purpose = "Contains frontend component code deployed to site" - files = [ - { - name = "controller.php" - extension = "php" - description = "Main site controller" - required = true - audience = "developer" - }, - { - name = "manifest.xml" - extension = "xml" - description = "Component manifest for site" - required = true - audience = "developer" - } - ] - subdirectories = [ - { - name = "controllers" - path = "site/controllers" - description = "Site controllers" - requirement_status = "suggested" - }, - { - name = "models" - path = "site/models" - description = "Site models" - requirement_status = "suggested" - }, - { - name = "views" - path = "site/views" - description = "Site views" - required = true - } - ] - }, - { - name = "admin" - path = "admin" - description = "Component backend (admin) code" - required = true - purpose = "Contains backend component code for administrator" - files = [ - { - name = "controller.php" - extension = "php" - description = "Main admin controller" - required = true - audience = "developer" - } - ] - subdirectories = [ - { - name = "controllers" - path = "admin/controllers" - description = "Admin controllers" - requirement_status = "suggested" - }, - { - name = "models" - path = "admin/models" - description = "Admin models" - requirement_status = "suggested" - }, - { - name = "views" - path = "admin/views" - description = "Admin views" - required = true - }, - { - name = "sql" - path = "admin/sql" - description = "Database schema files" - requirement_status = "suggested" - } - ] - }, - { - name = "media" - path = "media" - description = "Media files (CSS, JS, images)" - requirement_status = "suggested" - purpose = "Contains static assets" - subdirectories = [ - { - name = "css" - path = "media/css" - description = "Stylesheets" - requirement_status = "suggested" - }, - { - name = "js" - path = "media/js" - description = "JavaScript files" - requirement_status = "suggested" - }, - { - name = "images" - path = "media/images" - description = "Image files" - requirement_status = "suggested" - } - ] - }, - { - name = "language" - path = "language" - description = "Language translation files" - required = true - purpose = "Contains language INI files" - }, - { - name = "docs" - path = "docs" - description = "Developer and technical documentation" - required = true - purpose = "Contains technical documentation, API docs, architecture diagrams" - files = [ - { - name = "index.md" - extension = "md" - description = "Documentation index" - required = true - }, - { - name = "update-server.md" - extension = "md" - description = "Joomla update server (update.xml) documentation" - required = true - always_overwrite = true - template = "templates/docs/required/template-update-server-joomla.md" - } - ] - }, - { - name = "scripts" - path = "scripts" - description = "Repo-specific scripts — not managed by MokoStandards sync" - required = false - purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/." - files = [ - { - name = "MokoStandards.override.xml" - extension = "xml" - description = "MokoStandards sync override configuration - preserved during sync" - requirement_status = "optional" - always_overwrite = false - audience = "developer" - } - ] - }, - { - name = "tests" - path = "tests" - description = "Test files" - required = true - purpose = "Contains unit tests, integration tests, and test fixtures" - subdirectories = [ - { - name = "unit" - path = "tests/unit" - description = "Unit tests" - required = true - }, - { - name = "integration" - path = "tests/integration" - description = "Integration tests" - requirement_status = "suggested" - } - ] - }, - { - name = ".github" - path = ".github" - description = "GitHub-specific configuration" - requirement_status = "suggested" - purpose = "Contains GitHub Actions workflows and configuration" - files = [ - { - name = "copilot.yml" - extension = "yml" - description = "GitHub Copilot allowed domains configuration" - requirement_status = "required" - always_overwrite = true - template = ".github/copilot.yml" - }, - { - name = "copilot-instructions.md" - extension = "md" - description = "GitHub Copilot custom instructions enforcing MokoStandards — Joomla/WaaS edition" - requirement_status = "required" - always_overwrite = false - destination_path = ".github" - destination_filename = "copilot-instructions.md" - template = "templates/github/copilot-instructions.joomla.md.template" - stub_content = <<-MOKO_END - > [!IMPORTANT] - > **🔧 AI Self-Update Required on First Use** - > - > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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://github.com/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 [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). 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 `update.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) - ├── update.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 - ├── .github/ - │ ├── 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 - ``` - - --- - - ## update.xml — Required in Repo Root - - `update.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 it via: - ```xml - - - {{REPO_URL}}/raw/main/update.xml - - - ``` - - **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. - - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - - `` — 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 `update.xml`. - - Must include `` block pointing to this repo's `update.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 }} - ``` - - --- - - ## MokoStandards Reference - - This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies: - - | Document | Purpose | - |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/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 `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.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, `update.xml` version, and `README.md` version go out of sync - MOKO_END - }, - { - name = "CLAUDE.md" - extension = "md" - description = "Claude AI assistant context enforcing MokoStandards — Joomla/WaaS edition" - requirement_status = "required" - always_overwrite = false - destination_path = ".github" - destination_filename = "CLAUDE.md" - template = "templates/github/CLAUDE.joomla.md.template" - stub_content = <<-MOKO_END - > [!IMPORTANT] - > **🔧 AI Self-Update Required on First Use** - > - > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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://github.com/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 [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) — 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) - ├── update.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 - ├── .github/ - │ ├── workflows/ # CI/CD workflows (synced from MokoStandards) - │ ├── 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 | - | `update.xml` | `` in the most recent `` block | - - The `make release` command / release workflow syncs all three automatically. - - --- - - # update.xml — Required in Repo Root - - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: - - ```xml - - - - {{REPO_URL}}/raw/main/update.xml - - - ``` - - **Rules:** - - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. - - `` must be a publicly accessible GitHub Releases asset URL. - - `` — backslash is literal (Joomla regex syntax). - - Example `update.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 - - - - 7.4 - 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 `update.xml` and `README.md` | - | New release | Prepend `` to `update.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`, `update.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; `update.xml` updated with new entry - - [ ] FILE INFORMATION headers updated in modified files - - [ ] CHANGELOG.md updated - - [ ] Tests pass - - --- - - # Key Policy Documents (MokoStandards) - - | Document | Purpose | - |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | - MOKO_END - } - ] - subdirectories = [ - { - name = "workflows" - path = ".github/workflows" - description = "GitHub Actions workflows" - requirement_status = "required" - files = [ - { - name = "ci-joomla.yml" - extension = "yml" - description = "Joomla-specific CI workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/ci-joomla.yml.template" - }, - { - name = "codeql-analysis.yml" - extension = "yml" - description = "CodeQL security analysis workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/generic/codeql-analysis.yml.template" - }, - { - name = "standards-compliance.yml" - extension = "yml" - description = "MokoStandards compliance validation" - requirement_status = "required" - always_overwrite = true - template = ".github/workflows/standards-compliance.yml" - }, - { - name = "enterprise-firewall-setup.yml" - extension = "yml" - description = "Enterprise firewall configuration for trusted domain access" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/enterprise-firewall-setup.yml.template" - }, - { - name = "deploy-dev.yml" - extension = "yml" - description = "SFTP deployment of src/ to the development server" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-dev.yml.template" - }, - { - name = "deploy-demo.yml" - extension = "yml" - description = "SFTP deployment of src/ to the demo server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-demo.yml.template" - }, - { - name = "deploy-rs.yml" - extension = "yml" - description = "SFTP deployment of src/ to the release staging server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-rs.yml.template" - }, - { - name = "sync-version-on-merge.yml" - extension = "yml" - description = "Auto-bump patch version on merge and propagate to all file headers" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/sync-version-on-merge.yml.template" - }, - { - name = "auto-release.yml" - extension = "yml" - description = "Auto-create GitHub Release on push to main with version from README.md" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/auto-release.yml.template" - }, - { - name = "repository-cleanup.yml" - extension = "yml" - description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/repository-cleanup.yml.template" - }, - { - name = "auto-dev-issue.yml" - extension = "yml" - description = "Auto-create tracking issue when a dev/** branch is pushed" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/auto-dev-issue.yml.template" - }, - { - name = "repo_health.yml" - extension = "yml" - description = "Joomla-specific repository health check workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/repo_health.yml.template" - } - ] - }, - { - name = "ISSUE_TEMPLATE" - path = ".github/ISSUE_TEMPLATE" - description = "GitHub issue templates synced from MokoStandards" - requirement_status = "required" - files = [ - { - name = "config.yml" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/config.yml" - }, - { - name = "adr.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/adr.md" - }, - { - name = "bug_report.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/bug_report.md" - }, - { - name = "documentation.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/documentation.md" - }, - { - name = "enterprise_support.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md" - }, - { - name = "feature_request.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/feature_request.md" - }, - { - name = "firewall-request.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/firewall-request.md" - }, - { - name = "question.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/question.md" - }, - { - name = "request-license.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/request-license.md" - }, - { - name = "rfc.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/rfc.md" - }, - { - name = "security.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/security.md" - }, - { - name = "joomla_issue.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/joomla_issue.md" - } - ] - } - ] - } - ] - - repository_requirements = { - secrets = [ - { - name = "GH_TOKEN" - description = "Org-level GitHub PAT for automation" - required = true - scope = "org" - }, - { - name = "DEV_FTP_KEY" - description = "SSH private key for SFTP dev deployment (preferred); if DEV_FTP_PASSWORD is also set it is used as the key passphrase, with password-only as fallback" - required = false - scope = "org" - }, - { - name = "DEV_FTP_PASSWORD" - description = "SFTP password for dev deployment; used as SSH key passphrase when DEV_FTP_KEY is also set, and as standalone fallback if key auth fails" - required = false - scope = "org" - note = "At least one of DEV_FTP_KEY or DEV_FTP_PASSWORD must be configured" - } - ] - - variables = [ - { - name = "DEV_FTP_HOST" - description = "Dev server hostname; may include port suffix (e.g. dev.example.com or dev.example.com:2222)" - required = true - scope = "org" - }, - { - name = "DEV_FTP_PATH" - description = "Base remote path for SFTP deployment (e.g. /var/www/html)" - required = true - scope = "org" - }, - { - name = "DEV_FTP_USERNAME" - description = "SFTP username for dev server authentication" - required = true - scope = "org" - }, - { - name = "DEV_FTP_PORT" - description = "Explicit SFTP port override; if omitted the port is parsed from DEV_FTP_HOST or defaults to 22" - required = false - scope = "org" - }, - { - name = "DEV_FTP_SUFFIX" - description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /my-extension)" - required = false - scope = "repo" - } - ] - } - } -} diff --git a/definitions/sync/MokoStandards-Template-Joomla-Template.def.tf b/definitions/sync/MokoStandards-Template-Joomla-Template.def.tf deleted file mode 100644 index b45b1b1..0000000 --- a/definitions/sync/MokoStandards-Template-Joomla-Template.def.tf +++ /dev/null @@ -1,1335 +0,0 @@ -/** - * Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Joomla-Template - * - * Auto-generated by MokoStandards bulk sync on 2026-04-02T15:28:58+00:00 - * Platform : waas-component - * Description: A repo template for a Joomla Template coding project according to MokoStandards - * - * DO NOT EDIT MANUALLY — this file is regenerated on every successful sync. - * To change what gets synced, edit api/definitions/default/waas-component.tf - * and re-run the bulk-repo-sync workflow. - */ - -locals { - sync_record = { - metadata = { - repo = "mokoconsulting-tech/MokoStandards-Template-Joomla-Template" - default_branch = "main" - detected_platform = "waas-component" - description = "A repo template for a Joomla Template coding project according to MokoStandards" - sync_timestamp = "2026-04-02T15:28:58+00:00" - source_repo = "mokoconsulting-tech/MokoStandards" - base_definition = "api/definitions/default/waas-component.tf" - } - - sync_stats = { - total_files = 41 - created_files = 6 - updated_files = 32 - skipped_files = 3 - } - - synced_files = [ - { path = "LICENSE" action = "updated" }, - { path = "SECURITY.md" action = "created" }, - { path = "CODE_OF_CONDUCT.md" action = "created" }, - { path = "CONTRIBUTING.md" action = "created" }, - { path = "update.xml" action = "updated" }, - { path = "phpstan.neon" action = "updated" }, - { path = "Makefile" action = "updated" }, - { path = ".gitignore" action = "updated" }, - { path = "composer.json" action = "updated" }, - { path = ".mokostandards" action = "created" }, - { path = "docs/update-server.md" action = "created" }, - { path = ".github/copilot.yml" action = "updated" }, - { path = ".github/copilot-instructions.md" action = "updated" }, - { path = ".github/CLAUDE.md" action = "updated" }, - { path = ".github/workflows/codeql-analysis.yml" action = "updated" }, - { path = ".github/workflows/standards-compliance.yml" action = "updated" }, - { path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" }, - { path = ".github/workflows/deploy-dev.yml" action = "updated" }, - { path = ".github/workflows/deploy-demo.yml" action = "updated" }, - { path = ".github/workflows/deploy-rs.yml" action = "updated" }, - { path = ".github/workflows/sync-version-on-merge.yml" action = "updated" }, - { path = ".github/workflows/auto-release.yml" action = "updated" }, - { path = ".github/workflows/repository-cleanup.yml" action = "updated" }, - { path = ".github/workflows/auto-dev-issue.yml" action = "updated" }, - { path = ".github/workflows/repo_health.yml" action = "created" }, - { path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" }, - { path = ".github/ISSUE_TEMPLATE/joomla_issue.md" action = "updated" }, - { path = ".github/CODEOWNERS" action = "updated" }, - { path = ".github/.mokostandards" action = "migrated from root" }, - ] - - skipped_files = [ - { path = "GOVERNANCE.md" reason = "Preserved (always_overwrite=false)" }, - { path = ".github/workflows/ci-joomla.yml" reason = "Source file not found" }, - { path = ".github/workflows/custom/README.md" reason = "README — never overwritten" }, - ] - } -} - -# ---- Base platform definition (reference copy) ---- -/** - * MokoWaaS Component Structure Definition - * Standard repository structure for MokoWaaS (Joomla) components - * - * Copyright (C) 2026 Moko Consulting - * SPDX-License-Identifier: GPL-3.0-or-later - * Version: 04.05.00 - * Schema Version: 1.0 - */ - -locals { - repository_structure = { - metadata = { - name = "MokoWaaS Component" - description = "Standard repository structure for MokoWaaS (Joomla) components" - repository_type = "waas-component" - platform = "mokowaas" - last_updated = "2026-01-15T00:00:00Z" - maintainer = "Moko Consulting" - version = "04.05.00" - schema_version = "1.0" - } - - root_files = [ - { - name = "README.md" - extension = "md" - description = "Developer-focused documentation for contributors and maintainers" - required = true - always_overwrite = false - protected = true - audience = "developer" - }, - { - name = "LICENSE" - extension = "" - description = "License file (GPL-3.0-or-later) - Default for Joomla/WaaS components" - required = true - audience = "general" - template = "templates/licenses/GPL-3.0" - license_type = "GPL-3.0-or-later" - }, - { - name = "CHANGELOG.md" - extension = "md" - description = "Version history and changes" - required = true - audience = "general" - }, - { - name = "SECURITY.md" - extension = "md" - description = "Security policy and vulnerability reporting" - required = true - always_overwrite = true - template = "templates/docs/required/template-SECURITY.md" - audience = "general" - }, - { - name = "CODE_OF_CONDUCT.md" - extension = "md" - description = "Community code of conduct" - required = true - always_overwrite = true - template = "templates/docs/extra/template-CODE_OF_CONDUCT.md" - always_overwrite = true - audience = "contributor" - }, - { - name = "ROADMAP.md" - extension = "md" - description = "Project roadmap with version goals and milestones" - required = false - audience = "general" - }, - { - name = "CONTRIBUTING.md" - extension = "md" - description = "Contribution guidelines" - required = true - always_overwrite = true - template = "templates/docs/required/template-CONTRIBUTING.md" - audience = "contributor" - }, - { - name = "update.xml" - extension = "xml" - description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" - required = true - always_overwrite = false - audience = "developer" - template = "templates/joomla/update.xml.template" - stub_content = <<-MOKO_END - - - - {{EXTENSION_NAME}} - {{REPO_NAME}} — Moko Consulting Joomla extension - {{EXTENSION_ELEMENT}} - {{EXTENSION_TYPE}} - {{VERSION}} - {{REPO_URL}}/releases/tag/{{VERSION}} - - {{DOWNLOAD_URL}} - - - 7.4 - Moko Consulting - {{MAINTAINER_URL}} - - - MOKO_END - }, - { - name = "phpstan.neon" - extension = "neon" - description = "PHPStan static analysis config with Joomla framework class stubs" - required = true - always_overwrite = true - audience = "developer" - template = "templates/configs/phpstan.joomla.neon" - }, - { - name = "Makefile" - description = "Build automation using MokoStandards templates" - required = true - always_overwrite = true - audience = "developer" - source_path = "templates/makefiles" - source_filename = "Makefile.joomla.template" - source_type = "template" - destination_path = "." - destination_filename = "Makefile" - create_path = false - template = "templates/makefiles/Makefile.joomla.template" - }, - { - name = ".gitignore" - extension = "gitignore" - description = "Git ignore patterns for Joomla development - preserved during sync operations" - required = true - always_overwrite = false - audience = "developer" - template = "templates/configs/.gitignore.joomla" - validation_rules = [ - { - type = "content-pattern" - description = "Must contain sftp-config pattern to ignore SFTP sync configuration files" - pattern = "sftp-config" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain user.css pattern to ignore custom user CSS overrides" - pattern = "user\\.css" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain user.js pattern to ignore custom user JavaScript overrides" - pattern = "user\\.js" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain modulebuilder.txt pattern to ignore Joomla Module Builder artifacts" - pattern = "modulebuilder\\.txt" - severity = "error" - }, - { - type = "content-pattern" - description = "Must contain colors_custom.css pattern to ignore custom color scheme overrides" - pattern = "colors_custom\\.css" - severity = "error" - } - ] - }, - { - name = ".gitattributes" - extension = "gitattributes" - description = "Git attributes configuration" - required = true - audience = "developer" - }, - { - name = ".editorconfig" - extension = "editorconfig" - description = "Editor configuration for consistent coding style - preserved during sync" - required = true - always_overwrite = false - audience = "developer" - }, - { - name = "composer.json" - extension = "json" - description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling" - required = true - always_overwrite = false - audience = "developer" - template = "templates/configs/composer.joomla.json" - }, - { - name = ".mokostandards" - extension = "yml" - description = "MokoStandards governance attachment — links this repo back to the standards source" - required = true - always_overwrite = true - audience = "developer" - template = "templates/configs/mokostandards.yml.template" - }, - { - name = "GOVERNANCE.md" - extension = "md" - description = "Project governance rules, roles, and decision process — auto-maintained by MokoStandards" - required = true - always_overwrite = false - protected = true - audience = "all" - template = "templates/docs/required/GOVERNANCE.md" - } - ] - - directories = [ - { - name = "site" - path = "site" - description = "Component frontend (site) code" - required = true - purpose = "Contains frontend component code deployed to site" - files = [ - { - name = "controller.php" - extension = "php" - description = "Main site controller" - required = true - audience = "developer" - }, - { - name = "manifest.xml" - extension = "xml" - description = "Component manifest for site" - required = true - audience = "developer" - } - ] - subdirectories = [ - { - name = "controllers" - path = "site/controllers" - description = "Site controllers" - requirement_status = "suggested" - }, - { - name = "models" - path = "site/models" - description = "Site models" - requirement_status = "suggested" - }, - { - name = "views" - path = "site/views" - description = "Site views" - required = true - } - ] - }, - { - name = "admin" - path = "admin" - description = "Component backend (admin) code" - required = true - purpose = "Contains backend component code for administrator" - files = [ - { - name = "controller.php" - extension = "php" - description = "Main admin controller" - required = true - audience = "developer" - } - ] - subdirectories = [ - { - name = "controllers" - path = "admin/controllers" - description = "Admin controllers" - requirement_status = "suggested" - }, - { - name = "models" - path = "admin/models" - description = "Admin models" - requirement_status = "suggested" - }, - { - name = "views" - path = "admin/views" - description = "Admin views" - required = true - }, - { - name = "sql" - path = "admin/sql" - description = "Database schema files" - requirement_status = "suggested" - } - ] - }, - { - name = "media" - path = "media" - description = "Media files (CSS, JS, images)" - requirement_status = "suggested" - purpose = "Contains static assets" - subdirectories = [ - { - name = "css" - path = "media/css" - description = "Stylesheets" - requirement_status = "suggested" - }, - { - name = "js" - path = "media/js" - description = "JavaScript files" - requirement_status = "suggested" - }, - { - name = "images" - path = "media/images" - description = "Image files" - requirement_status = "suggested" - } - ] - }, - { - name = "language" - path = "language" - description = "Language translation files" - required = true - purpose = "Contains language INI files" - }, - { - name = "docs" - path = "docs" - description = "Developer and technical documentation" - required = true - purpose = "Contains technical documentation, API docs, architecture diagrams" - files = [ - { - name = "index.md" - extension = "md" - description = "Documentation index" - required = true - }, - { - name = "update-server.md" - extension = "md" - description = "Joomla update server (update.xml) documentation" - required = true - always_overwrite = true - template = "templates/docs/required/template-update-server-joomla.md" - } - ] - }, - { - name = "scripts" - path = "scripts" - description = "Repo-specific scripts — not managed by MokoStandards sync" - required = false - purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/." - files = [ - { - name = "MokoStandards.override.xml" - extension = "xml" - description = "MokoStandards sync override configuration - preserved during sync" - requirement_status = "optional" - always_overwrite = false - audience = "developer" - } - ] - }, - { - name = "tests" - path = "tests" - description = "Test files" - required = true - purpose = "Contains unit tests, integration tests, and test fixtures" - subdirectories = [ - { - name = "unit" - path = "tests/unit" - description = "Unit tests" - required = true - }, - { - name = "integration" - path = "tests/integration" - description = "Integration tests" - requirement_status = "suggested" - } - ] - }, - { - name = ".github" - path = ".github" - description = "GitHub-specific configuration" - requirement_status = "suggested" - purpose = "Contains GitHub Actions workflows and configuration" - files = [ - { - name = "copilot.yml" - extension = "yml" - description = "GitHub Copilot allowed domains configuration" - requirement_status = "required" - always_overwrite = true - template = ".github/copilot.yml" - }, - { - name = "copilot-instructions.md" - extension = "md" - description = "GitHub Copilot custom instructions enforcing MokoStandards — Joomla/WaaS edition" - requirement_status = "required" - always_overwrite = false - destination_path = ".github" - destination_filename = "copilot-instructions.md" - template = "templates/github/copilot-instructions.joomla.md.template" - stub_content = <<-MOKO_END - > [!IMPORTANT] - > **🔧 AI Self-Update Required on First Use** - > - > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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://github.com/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 [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). 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 `update.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) - ├── update.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 - ├── .github/ - │ ├── 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 - ``` - - --- - - ## update.xml — Required in Repo Root - - `update.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 it via: - ```xml - - - {{REPO_URL}}/raw/main/update.xml - - - ``` - - **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. - - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - - `` — 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 `update.xml`. - - Must include `` block pointing to this repo's `update.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 }} - ``` - - --- - - ## MokoStandards Reference - - This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies: - - | Document | Purpose | - |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/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 `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.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, `update.xml` version, and `README.md` version go out of sync - MOKO_END - }, - { - name = "CLAUDE.md" - extension = "md" - description = "Claude AI assistant context enforcing MokoStandards — Joomla/WaaS edition" - requirement_status = "required" - always_overwrite = false - destination_path = ".github" - destination_filename = "CLAUDE.md" - template = "templates/github/CLAUDE.joomla.md.template" - stub_content = <<-MOKO_END - > [!IMPORTANT] - > **🔧 AI Self-Update Required on First Use** - > - > This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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://github.com/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 [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) — 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) - ├── update.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 - ├── .github/ - │ ├── workflows/ # CI/CD workflows (synced from MokoStandards) - │ ├── 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 | - | `update.xml` | `` in the most recent `` block | - - The `make release` command / release workflow syncs all three automatically. - - --- - - # update.xml — Required in Repo Root - - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: - - ```xml - - - - {{REPO_URL}}/raw/main/update.xml - - - ``` - - **Rules:** - - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. - - `` must be a publicly accessible GitHub Releases asset URL. - - `` — backslash is literal (Joomla regex syntax). - - Example `update.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 - - - - 7.4 - 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 `update.xml` and `README.md` | - | New release | Prepend `` to `update.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`, `update.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; `update.xml` updated with new entry - - [ ] FILE INFORMATION headers updated in modified files - - [ ] CHANGELOG.md updated - - [ ] Tests pass - - --- - - # Key Policy Documents (MokoStandards) - - | Document | Purpose | - |----------|---------| - | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | - | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | - | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | - | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | - | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | - | [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | - MOKO_END - } - ] - subdirectories = [ - { - name = "workflows" - path = ".github/workflows" - description = "GitHub Actions workflows" - requirement_status = "required" - files = [ - { - name = "ci-joomla.yml" - extension = "yml" - description = "Joomla-specific CI workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/ci-joomla.yml.template" - }, - { - name = "codeql-analysis.yml" - extension = "yml" - description = "CodeQL security analysis workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/generic/codeql-analysis.yml.template" - }, - { - name = "standards-compliance.yml" - extension = "yml" - description = "MokoStandards compliance validation" - requirement_status = "required" - always_overwrite = true - template = ".github/workflows/standards-compliance.yml" - }, - { - name = "enterprise-firewall-setup.yml" - extension = "yml" - description = "Enterprise firewall configuration for trusted domain access" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/enterprise-firewall-setup.yml.template" - }, - { - name = "deploy-dev.yml" - extension = "yml" - description = "SFTP deployment of src/ to the development server" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-dev.yml.template" - }, - { - name = "deploy-demo.yml" - extension = "yml" - description = "SFTP deployment of src/ to the demo server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-demo.yml.template" - }, - { - name = "deploy-rs.yml" - extension = "yml" - description = "SFTP deployment of src/ to the release staging server on merge to main" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/deploy-rs.yml.template" - }, - { - name = "sync-version-on-merge.yml" - extension = "yml" - description = "Auto-bump patch version on merge and propagate to all file headers" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/sync-version-on-merge.yml.template" - }, - { - name = "auto-release.yml" - extension = "yml" - description = "Auto-create GitHub Release on push to main with version from README.md" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/auto-release.yml.template" - }, - { - name = "repository-cleanup.yml" - extension = "yml" - description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/repository-cleanup.yml.template" - }, - { - name = "auto-dev-issue.yml" - extension = "yml" - description = "Auto-create tracking issue when a dev/** branch is pushed" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/shared/auto-dev-issue.yml.template" - }, - { - name = "repo_health.yml" - extension = "yml" - description = "Joomla-specific repository health check workflow" - requirement_status = "required" - always_overwrite = true - template = "templates/workflows/joomla/repo_health.yml.template" - } - ] - }, - { - name = "ISSUE_TEMPLATE" - path = ".github/ISSUE_TEMPLATE" - description = "GitHub issue templates synced from MokoStandards" - requirement_status = "required" - files = [ - { - name = "config.yml" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/config.yml" - }, - { - name = "adr.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/adr.md" - }, - { - name = "bug_report.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/bug_report.md" - }, - { - name = "documentation.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/documentation.md" - }, - { - name = "enterprise_support.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md" - }, - { - name = "feature_request.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/feature_request.md" - }, - { - name = "firewall-request.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/firewall-request.md" - }, - { - name = "question.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/question.md" - }, - { - name = "request-license.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/request-license.md" - }, - { - name = "rfc.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/rfc.md" - }, - { - name = "security.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/security.md" - }, - { - name = "joomla_issue.md" - always_overwrite = true - template = "templates/github/ISSUE_TEMPLATE/joomla_issue.md" - } - ] - } - ] - } - ] - - repository_requirements = { - secrets = [ - { - name = "GH_TOKEN" - description = "Org-level GitHub PAT for automation" - required = true - scope = "org" - }, - { - name = "DEV_FTP_KEY" - description = "SSH private key for SFTP dev deployment (preferred); if DEV_FTP_PASSWORD is also set it is used as the key passphrase, with password-only as fallback" - required = false - scope = "org" - }, - { - name = "DEV_FTP_PASSWORD" - description = "SFTP password for dev deployment; used as SSH key passphrase when DEV_FTP_KEY is also set, and as standalone fallback if key auth fails" - required = false - scope = "org" - note = "At least one of DEV_FTP_KEY or DEV_FTP_PASSWORD must be configured" - } - ] - - variables = [ - { - name = "DEV_FTP_HOST" - description = "Dev server hostname; may include port suffix (e.g. dev.example.com or dev.example.com:2222)" - required = true - scope = "org" - }, - { - name = "DEV_FTP_PATH" - description = "Base remote path for SFTP deployment (e.g. /var/www/html)" - required = true - scope = "org" - }, - { - name = "DEV_FTP_USERNAME" - description = "SFTP username for dev server authentication" - required = true - scope = "org" - }, - { - name = "DEV_FTP_PORT" - description = "Explicit SFTP port override; if omitted the port is parsed from DEV_FTP_HOST or defaults to 22" - required = false - scope = "org" - }, - { - name = "DEV_FTP_SUFFIX" - description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /my-extension)" - required = false - scope = "repo" - } - ] - } - } -} diff --git a/definitions/sync/MokoWaaS.def.tf b/definitions/sync/MokoWaaS.def.tf index 39da791..f95873d 100644 --- a/definitions/sync/MokoWaaS.def.tf +++ b/definitions/sync/MokoWaaS.def.tf @@ -34,7 +34,7 @@ locals { { path = "SECURITY.md" action = "created" }, { path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" }, - { path = "update.xml" action = "updated" }, + { path = "updates.xml" action = "updated" }, { path = "phpstan.neon" action = "updated" }, { path = "Makefile" action = "updated" }, { path = ".gitignore" action = "updated" }, @@ -165,13 +165,13 @@ locals { audience = "contributor" }, { - name = "update.xml" + name = "updates.xml" extension = "xml" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" required = true always_overwrite = false audience = "developer" - template = "templates/joomla/update.xml.template" + template = "templates/joomla/updates.xml.template" stub_content = <<-MOKO_END 01.02.04 - @@ -661,7 +661,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required, see below) + ├── updates.xml # Update server manifest (root — required, see below) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -690,22 +690,22 @@ locals { --- - ## update.xml — Required in Repo Root + ## updates.xml — Required in Repo Root - `update.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. + `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 it via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. + - 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`. - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - `` — 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. @@ -714,8 +714,8 @@ locals { ## 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 `update.xml`. - - Must include `` block pointing to this repo's `update.xml`. + - `` 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. @@ -793,8 +793,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed manifest.xml | Update `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version | + | 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 | @@ -808,7 +808,7 @@ locals { - 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, `update.xml` version, and `README.md` version go out of sync + - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync MOKO_END }, { @@ -868,7 +868,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required) + ├── updates.xml # Update server manifest (root — required) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -918,32 +918,32 @@ locals { |------|------------------------| | `README.md` | `FILE INFORMATION` block + badge | | `manifest.xml` | `` tag | - | `update.xml` | `` in the most recent `` block | + | `updates.xml` | `` in the most recent `` block | The `make release` command / release workflow syncs all three automatically. --- - # update.xml — Required in Repo Root + # updates.xml — Required in Repo Root - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: + `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. + - `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`. - `` must be a publicly accessible GitHub Releases asset URL. - `` — backslash is literal (Joomla regex syntax). - Example `update.xml` entry for a new release: + Example `updates.xml` entry for a new release: ```xml @@ -1052,8 +1052,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | - | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | + | 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 | @@ -1064,7 +1064,7 @@ locals { - **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`, `update.xml`, and `README.md` versions diverge.** + - **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`. @@ -1078,7 +1078,7 @@ locals { 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; `update.xml` updated with new entry + - [ ] 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 diff --git a/definitions/sync/MokoWaaSAnnounce.def.tf b/definitions/sync/MokoWaaSAnnounce.def.tf index e5fea08..464953f 100644 --- a/definitions/sync/MokoWaaSAnnounce.def.tf +++ b/definitions/sync/MokoWaaSAnnounce.def.tf @@ -34,7 +34,7 @@ locals { { path = "SECURITY.md" action = "created" }, { path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" }, - { path = "update.xml" action = "updated" }, + { path = "updates.xml" action = "updated" }, { path = "phpstan.neon" action = "updated" }, { path = "Makefile" action = "updated" }, { path = ".gitignore" action = "updated" }, @@ -165,13 +165,13 @@ locals { audience = "contributor" }, { - name = "update.xml" + name = "updates.xml" extension = "xml" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" required = true always_overwrite = false audience = "developer" - template = "templates/joomla/update.xml.template" + template = "templates/joomla/updates.xml.template" stub_content = <<-MOKO_END 01.02.04 - @@ -661,7 +661,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required, see below) + ├── updates.xml # Update server manifest (root — required, see below) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -690,22 +690,22 @@ locals { --- - ## update.xml — Required in Repo Root + ## updates.xml — Required in Repo Root - `update.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. + `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 it via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. + - 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`. - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - `` — 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. @@ -714,8 +714,8 @@ locals { ## 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 `update.xml`. - - Must include `` block pointing to this repo's `update.xml`. + - `` 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. @@ -793,8 +793,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed manifest.xml | Update `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version | + | 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 | @@ -808,7 +808,7 @@ locals { - 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, `update.xml` version, and `README.md` version go out of sync + - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync MOKO_END }, { @@ -868,7 +868,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required) + ├── updates.xml # Update server manifest (root — required) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -918,32 +918,32 @@ locals { |------|------------------------| | `README.md` | `FILE INFORMATION` block + badge | | `manifest.xml` | `` tag | - | `update.xml` | `` in the most recent `` block | + | `updates.xml` | `` in the most recent `` block | The `make release` command / release workflow syncs all three automatically. --- - # update.xml — Required in Repo Root + # updates.xml — Required in Repo Root - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: + `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. + - `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`. - `` must be a publicly accessible GitHub Releases asset URL. - `` — backslash is literal (Joomla regex syntax). - Example `update.xml` entry for a new release: + Example `updates.xml` entry for a new release: ```xml @@ -1052,8 +1052,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | - | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | + | 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 | @@ -1064,7 +1064,7 @@ locals { - **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`, `update.xml`, and `README.md` versions diverge.** + - **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`. @@ -1078,7 +1078,7 @@ locals { 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; `update.xml` updated with new entry + - [ ] 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 diff --git a/definitions/sync/MokoWaaSBrand.def.tf b/definitions/sync/MokoWaaSBrand.def.tf index 0abad56..03128c2 100644 --- a/definitions/sync/MokoWaaSBrand.def.tf +++ b/definitions/sync/MokoWaaSBrand.def.tf @@ -31,7 +31,7 @@ locals { synced_files = [ { path = "LICENSE" action = "updated" }, - { path = "update.xml" action = "updated" }, + { path = "updates.xml" action = "updated" }, { path = "phpstan.neon" action = "updated" }, { path = "Makefile" action = "updated" }, { path = ".gitignore" action = "updated" }, @@ -149,13 +149,13 @@ locals { audience = "contributor" }, { - name = "update.xml" + name = "updates.xml" extension = "xml" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" required = true always_overwrite = false audience = "developer" - template = "templates/joomla/update.xml.template" + template = "templates/joomla/updates.xml.template" stub_content = <<-MOKO_END 01.02.04 - @@ -637,7 +637,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required, see below) + ├── updates.xml # Update server manifest (root — required, see below) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -666,22 +666,22 @@ locals { --- - ## update.xml — Required in Repo Root + ## updates.xml — Required in Repo Root - `update.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. + `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 it via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. + - 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`. - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - `` — 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. @@ -690,8 +690,8 @@ locals { ## 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 `update.xml`. - - Must include `` block pointing to this repo's `update.xml`. + - `` 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. @@ -769,8 +769,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed manifest.xml | Update `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version | + | 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 | @@ -784,7 +784,7 @@ locals { - 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, `update.xml` version, and `README.md` version go out of sync + - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync MOKO_END }, { @@ -844,7 +844,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required) + ├── updates.xml # Update server manifest (root — required) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -894,32 +894,32 @@ locals { |------|------------------------| | `README.md` | `FILE INFORMATION` block + badge | | `manifest.xml` | `` tag | - | `update.xml` | `` in the most recent `` block | + | `updates.xml` | `` in the most recent `` block | The `make release` command / release workflow syncs all three automatically. --- - # update.xml — Required in Repo Root + # updates.xml — Required in Repo Root - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: + `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. + - `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`. - `` must be a publicly accessible GitHub Releases asset URL. - `` — backslash is literal (Joomla regex syntax). - Example `update.xml` entry for a new release: + Example `updates.xml` entry for a new release: ```xml @@ -1028,8 +1028,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | - | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | + | 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 | @@ -1040,7 +1040,7 @@ locals { - **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`, `update.xml`, and `README.md` versions diverge.** + - **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`. @@ -1054,7 +1054,7 @@ locals { 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; `update.xml` updated with new entry + - [ ] 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 diff --git a/definitions/sync/client-vexcreations.def.tf b/definitions/sync/client-vexcreations.def.tf index f4eb8fc..fac8cf5 100644 --- a/definitions/sync/client-vexcreations.def.tf +++ b/definitions/sync/client-vexcreations.def.tf @@ -34,7 +34,7 @@ locals { { path = "SECURITY.md" action = "updated" }, { path = "CODE_OF_CONDUCT.md" action = "updated" }, { path = "CONTRIBUTING.md" action = "updated" }, - { path = "update.xml" action = "created" }, + { path = "updates.xml" action = "created" }, { path = "phpstan.neon" action = "updated" }, { path = "Makefile" action = "created" }, { path = ".gitignore" action = "updated" }, @@ -165,13 +165,13 @@ locals { audience = "contributor" }, { - name = "update.xml" + name = "updates.xml" extension = "xml" description = "Joomla extension update server manifest — lists releases for Joomla auto-update; must be kept in sync with manifest.xml version" required = true always_overwrite = false audience = "developer" - template = "templates/joomla/update.xml.template" + template = "templates/joomla/updates.xml.template" stub_content = <<-MOKO_END 01.02.04 - @@ -661,7 +661,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required, see below) + ├── updates.xml # Update server manifest (root — required, see below) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -690,22 +690,22 @@ locals { --- - ## update.xml — Required in Repo Root + ## updates.xml — Required in Repo Root - `update.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. + `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 it via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - - Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. - - The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. + - 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`. - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - `` — 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. @@ -714,8 +714,8 @@ locals { ## 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 `update.xml`. - - Must include `` block pointing to this repo's `update.xml`. + - `` 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. @@ -793,8 +793,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed manifest.xml | Update `update.xml` version; bump README.md version | - | New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version | + | 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 | @@ -808,7 +808,7 @@ locals { - 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, `update.xml` version, and `README.md` version go out of sync + - Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync MOKO_END }, { @@ -868,7 +868,7 @@ locals { ``` {{REPO_NAME}}/ ├── manifest.xml # Joomla installer manifest (root — required) - ├── update.xml # Update server manifest (root — required) + ├── updates.xml # Update server manifest (root — required) ├── site/ # Frontend (site) code │ ├── controller.php │ ├── controllers/ @@ -918,32 +918,32 @@ locals { |------|------------------------| | `README.md` | `FILE INFORMATION` block + badge | | `manifest.xml` | `` tag | - | `update.xml` | `` in the most recent `` block | + | `updates.xml` | `` in the most recent `` block | The `make release` command / release workflow syncs all three automatically. --- - # update.xml — Required in Repo Root + # updates.xml — Required in Repo Root - `update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: + `updates.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via: ```xml - {{REPO_URL}}/raw/main/update.xml + {{REPO_URL}}/raw/main/updates.xml ``` **Rules:** - Every release prepends a new `` block at the top — older entries are preserved. - - `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. + - `` in `updates.xml` must exactly match `` in `manifest.xml` and `README.md`. - `` must be a publicly accessible GitHub Releases asset URL. - `` — backslash is literal (Joomla regex syntax). - Example `update.xml` entry for a new release: + Example `updates.xml` entry for a new release: ```xml @@ -1052,8 +1052,8 @@ locals { | Change type | Documentation to update | |-------------|------------------------| | New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | - | New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` | - | New release | Prepend `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | + | 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 | @@ -1064,7 +1064,7 @@ locals { - **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`, `update.xml`, and `README.md` versions diverge.** + - **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`. @@ -1078,7 +1078,7 @@ locals { 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; `update.xml` updated with new entry + - [ ] 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 diff --git a/definitions/sync/joomla-api-mcp.def.tf b/definitions/sync/joomla-api-mcp.def.tf new file mode 100644 index 0000000..0c7ed5b --- /dev/null +++ b/definitions/sync/joomla-api-mcp.def.tf @@ -0,0 +1,43 @@ +/** + * Repository Sync Tracking Definition: mokoconsulting-tech/joomla-api-mcp + * + * Auto-generated by MokoStandards bulk sync on 2026-04-23T00:00:00+00:00 + * Platform : default-repository + * Description: MCP server for Joomla Web Services API operations + * + * DO NOT EDIT MANUALLY — this file is regenerated on every successful sync. + * To change what gets synced, edit api/definitions/default/default-repository.tf + * and re-run the bulk-repo-sync workflow. + */ + +locals { + sync_record = { + metadata = { + repo = "mokoconsulting-tech/joomla-api-mcp" + default_branch = "main" + detected_platform = "default-repository" + description = "MCP server for Joomla Web Services API operations" + sync_timestamp = "2026-04-23T00:00:00+00:00" + source_repo = "mokoconsulting-tech/MokoStandards" + base_definition = "api/definitions/default/default-repository.tf" + } + + sync_stats = { + total_files = 0 + created_files = 0 + updated_files = 0 + skipped_files = 0 + } + + synced_files = [] + + skipped_files = [ + { path = "README.md" reason = "README — never overwritten" }, + { path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" }, + { path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" }, + { path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" }, + { path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" }, + { path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" }, + ] + } +} diff --git a/deploy/deploy-joomla.php b/deploy/deploy-joomla.php index 88aed42..ef209b2 100644 --- a/deploy/deploy-joomla.php +++ b/deploy/deploy-joomla.php @@ -20,9 +20,9 @@ * changes — only XML manifest changes require a Joomla reinstall. * * USAGE - * php api/deploy/deploy-joomla.php --path . --config /tmp/sftp-config.json - * php api/deploy/deploy-joomla.php --path . --config /tmp/sftp-config.json --dry-run - * php api/deploy/deploy-joomla.php --path . --env dev + * php deploy/deploy-joomla.php --path . --config /tmp/sftp-config.json + * php deploy/deploy-joomla.php --path . --config /tmp/sftp-config.json --dry-run + * php deploy/deploy-joomla.php --path . --env dev */ declare(strict_types=1); diff --git a/deploy/deploy-sftp.php b/deploy/deploy-sftp.php index c4b3f3f..2afff3e 100644 --- a/deploy/deploy-sftp.php +++ b/deploy/deploy-sftp.php @@ -96,25 +96,25 @@ CONFIG FORMAT EXAMPLES # Dry-run preview of dev deployment - php api/deploy/deploy-sftp.php --env dev --dry-run --verbose + php deploy/deploy-sftp.php --env dev --dry-run --verbose # Deploy to dev server - php api/deploy/deploy-sftp.php --path /repos/mymodule --env dev + php deploy/deploy-sftp.php --path /repos/mymodule --env dev # Deploy to release/production server - php api/deploy/deploy-sftp.php --path /repos/mymodule --env rs + php deploy/deploy-sftp.php --path /repos/mymodule --env rs # Use a different source directory - php api/deploy/deploy-sftp.php --env dev --src-dir htdocs + php deploy/deploy-sftp.php --env dev --src-dir htdocs # Explicit config with encrypted key - php api/deploy/deploy-sftp.php \ + php deploy/deploy-sftp.php \ --path /repos/mymodule \ --env rs \ --key-passphrase "my passphrase" # Quiet mode (errors only) - php api/deploy/deploy-sftp.php --env dev --quiet + php deploy/deploy-sftp.php --env dev --quiet EXIT CODES 0 All files uploaded successfully diff --git a/docs/WORKFLOW_STANDARDS.md b/docs/WORKFLOW_STANDARDS.md new file mode 100644 index 0000000..3f4fec1 --- /dev/null +++ b/docs/WORKFLOW_STANDARDS.md @@ -0,0 +1,180 @@ +# Workflow Standards + +> Canonical reference for Gitea Actions CI/CD workflows across all Moko Consulting repositories. + +## Architecture + +``` +Template Repos (canonical source) → Production Repos (synced copies) +───────────────────────────────────── ────────────────────────────────── +MokoStandards-Template-Joomla → MokoOnyx, MokoCassiopeia, MokoJGDPC, etc. +MokoStandards-Template-Dolibarr → MokoCRM, MokoDoliForm, MokoDoliAuth, etc. +MokoStandards-Template-Generic → MokoISOUpdatePortable, etc. +MokoStandards-Template-Client → client-clarksvillefurs, client-kiddieland +``` + +**MokoOnyx** is the living reference implementation for Joomla workflows. Template repos are the **single source of truth** for workflow content. The MokoStandards-API repo does NOT store workflow templates — its sync engine (`RepositorySynchronizer.php`) clones template repos at runtime to get the latest workflows. + +### How Sync Works + +``` +bulk-repo-sync.yml (API repo) + → RepositorySynchronizer.php detects platform type + → Clones the matching template repo to /tmp/ + → Copies .gitea/workflows/*.yml from template → target repo +``` + +No workflow files are stored in the API repo. This prevents drift. + +## Template Repos + +| Repo | Purpose | Types | +|------|---------|-------| +| `MokoStandards-Template-Joomla` | All Joomla extension types in one repo | plugin, template, module, component, package, library | +| `MokoStandards-Template-Dolibarr` | Dolibarr module scaffold | — | +| `MokoStandards-Template-Generic` | Non-platform projects | — | +| `MokoStandards-Template-Client` | Client Joomla sites with media sync | — | + +## Standard Workflow Suite + +### Joomla Repositories (10 workflows) + +| Workflow | Trigger | Purpose | +|----------|---------|---------| +| `auto-release.yml` | PR merge to main (src/ changes) | Stable release: zip, Gitea release, version bump, updates.xml | +| `pre-release.yml` | Manual dispatch | Dev/alpha/beta/rc: patch bump, zip, pre-release | +| `ci-joomla.yml` | PRs to main | PHP lint, PHPStan, coding standards | +| `pr-check.yml` | PRs to main | Gate: manifest XML validation, build test | +| `deploy-manual.yml` | Manual dispatch | SFTP deploy to selected environment | +| `repo-health.yml` | Weekly schedule / manual | Structure compliance, required files | +| `update-server.yml` | Weekly schedule / manual | Validate updates.xml format + download URLs | +| `security-audit.yml` | Weekly + PR (lock file changes) | Dependency vulnerability scanning | +| `notify.yml` | Workflow completion | ntfy push on release success or failure | +| `cleanup.yml` | Weekly (Sunday 03:00 UTC) | Delete merged branches + old workflow runs | + +### Dolibarr Repositories (11 workflows) + +Same as Joomla except: +- `ci-dolibarr.yml` replaces `ci-joomla.yml` (Dolibarr-specific validation) +- `publish-to-mokodolimods.yml` added (copies src/ to mokodolimods on release) + +### Generic Repositories (9 workflows) + +Same as Joomla minus `ci-joomla.yml` (no platform-specific CI). + +### Client Repositories (10 workflows) + +Same as Joomla minus `update-server.yml` (no updates.xml — clients are sites, not extensions), plus: +- `sync-media.yml` — Bidirectional SFTP sync for `images/`, `files/`, `media/` between dev and production (every 6 hours + manual dispatch) + +**Per-client repo variables required for sync:** +| Variable | Purpose | +|----------|---------| +| `DEV_SYNC_HOST` | Dev server hostname | +| `DEV_SYNC_PORT` | Dev SSH port (default 22) | +| `DEV_SYNC_USERNAME` | Dev server user | +| `DEV_SYNC_PATH` | Base path on dev | +| `PROD_SYNC_HOST` | Production server hostname | +| `PROD_SYNC_PORT` | Production SSH port (default 22) | +| `PROD_SYNC_USERNAME` | Production server user | +| `PROD_SYNC_PATH` | Base path on production | + +**Per-client repo secrets:** `DEV_SYNC_KEY`, `PROD_SYNC_KEY` + +## Release Model + +``` +Feature branch → PR → merge to main → auto-release.yml (STABLE) + ↓ + pre-release.yml (manual dispatch for dev/alpha/beta/rc) +``` + +- **Stable releases** trigger automatically on PR merge to main (with `src/` changes) +- **Pre-releases** (dev, alpha, beta, rc) are manual via workflow_dispatch +- All releases overwrite the previous release for that channel (no history accumulation) +- Higher releases cascade-delete lower ones (stable deletes all pre-releases, rc deletes beta+alpha+dev, etc.) + +### Version Bump Policy + +| Trigger | Bump | Example | +|---------|------|---------| +| Stable (PR merge to main) | **Minor** — reset patch to 00 | `03.00.07` → `03.01.00` | +| Pre-release (manual) | **Patch** | `03.00.07` → `03.00.08` | +| Patch rollover (99→00) | Auto-bump minor | `03.00.99` → `03.01.00` | +| Minor rollover (99→00) | Auto-bump major | `03.99.00` → `04.00.00` | + +## Org-Level Configuration + +These secrets and variables are set at the MokoConsulting org level and available to all repos: + +### Secrets +| Name | Purpose | +|------|---------| +| `GA_TOKEN` | Gitea API token for releases, branch operations | +| `GH_TOKEN` | GitHub token for mirrors | +| `DEPLOY_SSH_KEY` | Universal SSH key for SFTP deploys | +| `DEV_SSH_KEY` | Dev server SSH key | +| `DEMO_FTP_KEY` | Demo server SFTP key | + +### Variables +| Name | Value | Purpose | +|------|-------|---------| +| `NTFY_URL` | `https://ntfy.mokoconsulting.tech` | Notification server | +| `NTFY_TOPIC` | `gitea-releases` | Default notification topic | +| `DEV_SSH_HOST` | `dev.mokoconsulting.tech` | Dev server hostname | +| `DEV_SSH_PORT` | `22` | Dev server SSH port | +| `DEV_SSH_USERNAME` | `mokoconsulting_dev` | Dev server username | +| `DEMO_FTP_HOST` | `demo.mokoconsulting.tech` | Demo server hostname | +| `DEMO_FTP_PORT` | `22` | Demo server port | +| `DEMO_FTP_USERNAME` | `mokoconsulting_demo` | Demo server username | + +## Syncing Workflows + +To update workflows across all repos from the canonical template: + +```bash +# Joomla repos — sync from unified template +for REPO in MokoOnyx MokoCassiopeia MokoJGDPC MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce MokoDPCalendarAPI; do + cd /a/$REPO + rm -f .gitea/workflows/*.yml + cp /a/MokoStandards-Template-Joomla/.gitea/workflows/*.yml .gitea/workflows/ + git add .gitea/workflows/ && git commit -m "chore: sync workflows" && git push +done + +# Dolibarr repos — sync from Dolibarr template +for REPO in MokoCRM MokoDoliForm MokoDoliAuth MokoDolibarr ...; do + cd /a/$REPO + rm -f .gitea/workflows/*.yml + cp /a/MokoStandards-Template-Dolibarr/.gitea/workflows/*.yml .gitea/workflows/ + git add .gitea/workflows/ && git commit -m "chore: sync workflows" && git push +done + +# Client repos — sync from Client template +for REPO in client-clarksvillefurs client-kiddieland; do + cd /a/$REPO + rm -f .gitea/workflows/*.yml + cp /a/MokoStandards-Template-Client/.gitea/workflows/*.yml .gitea/workflows/ + git add .gitea/workflows/ && git commit -m "chore: sync workflows" && git push +done +``` + +## Changelog + +| Date | Change | +|------|--------| +| 2026-05-02 | Initial standardization: 10-workflow Joomla suite from MokoOnyx | +| 2026-05-02 | Added pre-release.yml for manual dev/alpha/beta/rc builds | +| 2026-05-02 | Removed auto-deploy (deploy is manual only) | +| 2026-05-02 | Modernized Dolibarr/Generic/Client templates to match | +| 2026-05-02 | Added workflows to all 22 Dolibarr production repos | +| 2026-05-02 | Moved canonical source from API repo to template repos | +| 2026-05-02 | Added sync-media.yml to Client template (bidirectional SFTP) | +| 2026-05-02 | Deployed workflows to client repos (clarksvillefurs, kiddieland) | +| 2026-05-02 | Consolidated 6 Joomla template repos → `MokoStandards-Template-Joomla` | +| 2026-05-02 | Deleted individual template repos (Plugin, Template, Module, Component, Package, Library) | +| 2026-05-02 | Cascade delete: higher releases auto-delete lower pre-release channels | +| 2026-05-02 | Release naming: includes extension element name (e.g. "mokodpcalendarapi 03.00.00 (stable)") | +| 2026-05-02 | Stable releases overwrite (not append) | +| 2026-05-04 | Removed updates.xml + update-server.yml from client repos (sites, not extensions) | +| 2026-05-04 | Added client-site.tf definition in MokoStandards-API | +| 2026-05-05 | Version policy: stable=minor bump, pre-release=patch bump (was major/patch) | diff --git a/docs/automation/push-files.md b/docs/automation/push-files.md index 362950f..c13cf71 100644 --- a/docs/automation/push-files.md +++ b/docs/automation/push-files.md @@ -53,5 +53,5 @@ php api/automation/push_files.php --repos MokoCRM --files "LICENSE" --yes --no-i - Creates a PR with the pushed files (unless `--direct` is used) - Creates/updates a tracking issue in each target repo - Cross-links the tracking issue to the PR in the Development sidebar -- Assigns `jmiller-moko` to both PR and issue +- Assigns `jmiller` to both PR and issue - Applies standard labels: `standards-update`, `mokostandards`, `type: chore`, `automation` diff --git a/docs/automation/repo-cleanup.md b/docs/automation/repo-cleanup.md index f34ce79..f41933c 100644 --- a/docs/automation/repo-cleanup.md +++ b/docs/automation/repo-cleanup.md @@ -78,8 +78,8 @@ The `custom/` directory is auto-created by the cleanup workflow if it doesn't ex ### Authorization -- **Schedule:** Always authorized (runs as github-actions[bot]) -- **Manual dispatch:** `jmiller-moko` and `github-actions[bot]` always authorized; others need admin/maintain role +- **Schedule:** Always authorized (runs as gitea-actions[bot]) +- **Manual dispatch:** `jmiller` and `gitea-actions[bot]` always authorized; others need admin/maintain role --- diff --git a/docs/client-repos.md b/docs/client-repos.md new file mode 100644 index 0000000..9e5b415 --- /dev/null +++ b/docs/client-repos.md @@ -0,0 +1,139 @@ + + +[![MokoStandards](https://img.shields.io/badge/MokoStandards-04.06.00-orange)](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards) + +# Client Repository Standards + +## Overview + +Client repos (`client-*`) contain Joomla site customizations for individual clients — templates, overrides, custom CSS, module configurations, and deployment scripts. They follow the **Joomla** workflow profile but have additional rules around privacy, branding, and deployment. + +## Naming Convention + +``` +client-{clientname} +``` + +- Lowercase, hyphenated client identifier +- No version numbers in the repo name +- Examples: `client-clarksvillefurs`, `client-kiddieland`, `client-vexcreations` + +## Repository Structure + +``` +client-{name}/ +├── .github/ # Workflows, CLAUDE.md, CODEOWNERS +│ ├── workflows/ # Standard Joomla workflows (from template) +│ ├── CLAUDE.md # AI assistant context for this client +│ └── CODEOWNERS # jmiller owns all files +├── src/ # Joomla extension source +│ ├── templates/ # Template overrides +│ ├── modules/ # Custom modules +│ ├── plugins/ # Custom plugins +│ └── media/ # CSS, JS, images +├── docs/ # Client-specific documentation +│ ├── INSTALLATION.md # Deployment instructions +│ └── update-server.md # Update server configuration +├── scripts/ # Build and deployment scripts +├── updates.xml # Joomla update server manifest +├── composer.json # Dependencies +├── CHANGELOG.md # Version history +├── README.md # Client overview (no sensitive details) +├── LICENSE # GPL-3.0-or-later +└── Makefile # Build targets +``` + +## Privacy Rules + +Client repos are **private** by default. These rules are non-negotiable: + +1. **No client credentials** in the repo — passwords, API keys, FTP credentials, database passwords go in environment variables or secure secrets, never in code or config files +2. **No PII** — no customer names, emails, addresses, phone numbers in code or comments +3. **No client-specific hostnames** in committed configs — use environment variables or `sftp-config.json.template` patterns with placeholders +4. **README.md is generic** — describes the extension type, not the client's business details +5. **CLAUDE.md may reference the client** by name for AI context, but must not include credentials + +## Workflows + +Client repos use the **Joomla workflow profile** from `MokoStandards-Template-Client`: + +- `auto-assign.yml` — Auto-assign issues/PRs to jmiller +- `auto-dev-issue.yml` — Create tracking issue on dev branch push +- `auto-release.yml` — Create GitHub Release on main merge +- `changelog-validation.yml` — Validate CHANGELOG.md format +- `ci-joomla.yml` — Joomla manifest validation, XML lint +- `codeql-analysis.yml` — Security scanning +- `copilot-agent.yml` — Copilot agent configuration +- `deploy-manual.yml` — Manual FTP deploy for testing +- `enterprise-firewall-setup.yml` — Copilot firewall rules +- `project-setup.yml` — Initialize project structure +- `repo_health.yml` — Repository health checks +- `repository-cleanup.yml` — Clean up stale branches/artifacts +- `standards-compliance.yml` — MokoStandards validation +- `sync-version-on-merge.yml` — Version tracking +- `update-docs.yml` — Auto-update documentation indexes +- `update-server.yml` — Update `updates.xml` on release + +## Release Tags + +Standard 5-tag system applies: + +- `development` — Dev builds +- `alpha` — Internal testing +- `beta` — Client preview / UAT +- `release-candidate` — Final QA before production +- `stable` — Production release + +## Update Server + +Client repos use the standard dual update server with **Gitea as priority 1**: + +```xml + + + https://git.mokoconsulting.tech/MokoConsulting/client-{name}/raw/branch/main/updates.xml + + + https://raw.githubusercontent.com/mokoconsulting-tech/client-{name}/main/updates.xml + + +``` + +## Deployment + +Client sites are deployed via: + +1. **Joomla updater** — site pulls from `updates.xml` (preferred for production) +2. **Manual FTP** — via `deploy-manual.yml` workflow dispatch (for dev/staging) +3. **Direct install** — download ZIP from GitHub Release and install via Joomla admin + +## Creating a New Client Repo + +1. Create from template: `MokoStandards-Template-Client` +2. Name it `client-{clientname}` (lowercase, hyphenated) +3. Set repo to **private** +4. Set up secrets: `GA_TOKEN`, `GH_TOKEN` +5. Configure push mirror to GitHub +6. Apply branch protection on `main` +7. Update `README.md`, `composer.json`, `CLAUDE.md` with client-specific context +8. Run initial bulk sync to pull latest standards + +## Differences from Standard Joomla Repos + +- **Visibility**: Always private (standard Joomla repos can be public) +- **Template**: `MokoStandards-Template-Client` (not `Template-Joomla-*`) +- **Extra workflows**: `project-setup.yml`, `update-docs.yml`, `copilot-agent.yml` +- **SFTP config**: `sftp-config.json.template` with credential placeholders +- **Branding**: Client-specific (not Moko Consulting) +- **Deployment**: Joomla updater + manual FTP (standard repos use updater only) diff --git a/docs/standards/mokostandards-file-spec.md b/docs/standards/mokostandards-file-spec.md new file mode 100644 index 0000000..b0c27e5 --- /dev/null +++ b/docs/standards/mokostandards-file-spec.md @@ -0,0 +1,243 @@ +# `.mokostandards` File Specification + +> **Version:** 1.0 +> **Status:** Active +> **Schema:** [`mokostandards-schema.xsd`](mokostandards-schema.xsd) +> **Last Updated:** 2026-05-02 + +## Overview + +The `.mokostandards` file is the **repository manifest** for every repo governed by [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards). It lives at `.gitea/.mokostandards` (no file extension) and uses **XML format** internally. + +The file serves three purposes: + +1. **Identity** — declares the repo's name, organization, description, license, and topics. +2. **Governance binding** — ties the repo to a specific MokoStandards platform definition, enabling the bulk sync to know which files, workflows, and templates to enforce. +3. **Repo-specific configuration** — captures build/deploy targets, automation scripts, and per-repo sync overrides so that tooling (CI, `make`, `composer run`) can operate without guessing. + +## Location + +``` +.gitea/.mokostandards ← primary (Gitea-hosted repos) +``` + +Legacy locations (`.mokostandards` at repo root, `.github/.mokostandards`) are auto-migrated by the bulk sync into `.gitea/.mokostandards`. + +## Format + +The file is well-formed XML with no `.xml` extension. It uses the namespace `https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API` and is validated against `mokostandards-schema.xsd`. + +## Sections + +### `` — Required + +| Element | Required | Description | +|---------------|----------|-------------| +| `` | yes | Repository name (e.g. `MokoCRM`) | +| `` | yes | Organization slug (e.g. `MokoConsulting`) | +| `` | no | Human-readable project description | +| `` | no | License name; optional `spdx` attribute for the SPDX identifier | +| `` | no | Container for `` elements — Gitea/GitHub topics | + +### `` — Required + +| Element | Required | Description | +|----------------------|----------|-------------| +| `` | yes | Platform slug — must match a `definitions/default/*.tf` file. One of: `default-repository`, `crm-module`, `crm-platform`, `generic-repository`, `github-private-repository`, `joomla-template`, `standards-repository`, `waas-component` | +| `` | yes | MokoStandards version that last synced this repo (e.g. `04.07.00`) | +| `` | yes | URL to the MokoStandards repo | +| `` | no | ISO 8601 timestamp of last bulk sync | + +### `` — Optional + +| Element | Required | Description | +|-----------------|----------|-------------| +| `` | no | Primary language (`PHP`, `JavaScript`, `CSS`, etc.) | +| `` | no | Runtime version requirement (e.g. `php:>=8.1`) | +| ``| no | Package format (`composer`, `npm`, `joomla-extension`, `dolibarr-module`) | +| `` | no | Main entry file relative to repo root | +| `` | no | Build output: ``, ``, `` | +| `` | no | Container for `` elements | + +### `` — Optional + +Contains one or more `` elements: + +| Attribute/Element | Required | Description | +|-------------------|----------|-------------| +| `@name` | yes | Target name (`dev`, `demo`, `staging`, `production`) | +| `@enabled` | no | Boolean, default `true` | +| `` | yes | Hostname or secret reference (e.g. `${{ secrets.DEV_HOST }}`) | +| `` | yes | Remote deployment path | +| `` | no | One of: `sftp`, `rsync`, `scp`, `composer`, `webhook` | +| `` | no | Branch that triggers this deploy | +| `` | no | Local source directory to deploy (default: `src/`) | + +### `` — Optional + +Contains one or more ` + + + + + + + + composer.json + + + + + + + +``` + +## Migration from Legacy Format + +The old format was a single YAML-like line: + +``` +platform: default-repository +``` + +The bulk sync will: +1. Read the legacy value +2. Generate a new XML `.mokostandards` with the detected platform +3. Commit the replacement file to `.gitea/.mokostandards` +4. Delete the old file from legacy locations (root, `.github/`) + +## Validation + +Repos are validated against `mokostandards-schema.xsd` during: +- Bulk sync (`automation/bulk_sync.php`) +- Standards compliance workflow (`.gitea/workflows/standards-compliance.yml`) +- Local validation via `vendor/bin/moko-validate` + +A missing or invalid `.mokostandards` file is a **compliance failure**. + +## Tooling Integration + +| Tool | How it uses `.mokostandards` | +|------|------------------------------| +| **bulk_sync.php** | Reads `` to select the correct definition `.tf` file; updates `` on success | +| **enforce_tags.sh** | Reads `` for tag naming | +| **deploy-*.yml** | Reads `` for host, path, method | +| **Makefile** | Can source `` for consistent `make` targets | +| **moko-validate** | Validates the XML against the XSD and checks required fields | +| **detectPlatform()** | Falls back to name/topic heuristics only when `.mokostandards` is missing or unparseable | diff --git a/docs/standards/mokostandards-schema.xsd b/docs/standards/mokostandards-schema.xsd new file mode 100644 index 0000000..a039ea0 --- /dev/null +++ b/docs/standards/mokostandards-schema.xsd @@ -0,0 +1,273 @@ + + + + + + + + + Root element of the .mokostandards repository manifest. + Every governed repository MUST contain this file at .gitea/.mokostandards + + + + + + + + + + + + + + + + + + + + + Repository identity metadata. Provides authoritative repo-level + information consumed by sync tools, CI, and documentation generators. + + + + + + + + + + + + + + SPDX license identifier for the repository + + + + + + + + + + + + + + + + + + + Binds this repository to a MokoStandards platform definition and + tracks the governance source and version. + + + + + + + + + + + + + + Platform slug — must match a .tf file in definitions/default/. + Controls which structure definition and workflows are synced. + + + + + + + + + + + + + + + + + + + Build and packaging configuration. Describes the toolchain, + entry points, and artifact outputs for this repository. + + + + + + + + + + + + + + + Describes the build output artifact (zip, phar, etc.) + + + + + + + + + + + + + + + + + + + + + + + + + Deployment targets. Each target maps to a CI workflow and + defines the connection method and remote path. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Repo-specific scripts and automation hooks. + Each script element defines a named command that CI or + developers can invoke via `make`, `composer run`, or directly. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Per-repo overrides for the bulk sync process. + Allows a repository to skip specific synced files or + opt out of certain governance features without forking + the entire platform definition. + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/workflows/README.md b/docs/workflows/README.md index 2a94420..0c8e86a 100644 --- a/docs/workflows/README.md +++ b/docs/workflows/README.md @@ -5,6 +5,7 @@ **Status**: Active | **Version**: 04.00.04 | **Effective**: 2026-01-07 ## Overview +> **Important (v2):** All workflows MUST be in `.gitea/workflows/` only. Gitea Actions does not run workflows from `.github/workflows/`. Having files in `.github/workflows/` creates ghost queued runs that block the runner. Releases use stream-based git tags (`stable`, `release-candidate`, `beta`, `alpha`, `development`). See [Release System](./release-system.md) for cascade logic, SHA-256 rules, and auto-detection. This document provides comprehensive documentation for MokoStandards workflow templates. These templates provide standardized CI/CD configurations that ensure consistency, security, and compliance across all Moko Consulting repositories. @@ -44,20 +45,20 @@ To adopt MokoStandards workflows in your repository: ```bash # Copy universal build workflow -cp templates/workflows/build.yml.template .github/workflows/build.yml +cp templates/workflows/build.yml.template .gitea/workflows/build.yml # Copy release management workflow -cp templates/workflows/release-cycle.yml.template .github/workflows/release.yml +cp templates/workflows/release-cycle.yml.template .gitea/workflows/release.yml # Copy security scanning workflows -cp templates/workflows/generic/codeql-analysis.yml .github/workflows/ -cp templates/workflows/generic/dependency-review.yml.template .github/workflows/dependency-review.yml +cp templates/workflows/generic/codeql-analysis.yml .gitea/workflows/ +cp templates/workflows/generic/dependency-review.yml.template .gitea/workflows/dependency-review.yml # Copy standards compliance workflow -cp templates/workflows/standards-compliance.yml.template .github/workflows/standards-compliance.yml +cp templates/workflows/standards-compliance.yml.template .gitea/workflows/standards-compliance.yml # Optional: Copy cache management workflow -cp templates/workflows/flush-actions-cache.yml.template .github/workflows/flush-actions-cache.yml +cp templates/workflows/flush-actions-cache.yml.template .gitea/workflows/flush-actions-cache.yml ``` Then customize the workflows for your project as needed. @@ -87,7 +88,7 @@ on: workflow_dispatch: ``` -**Usage**: Copy to `.github/workflows/build.yml` and customize as needed. +**Usage**: Copy to `.gitea/workflows/build.yml` and customize as needed. See [Build System Documentation](../build-system/README.md) for details on the Makefile precedence system. @@ -127,7 +128,7 @@ on: required: true ``` -**Usage**: Copy to `.github/workflows/release.yml` for automated release management. +**Usage**: Copy to `.gitea/workflows/release.yml` for automated release management. See [Release Management Documentation](../release-management/README.md) for complete release procedures. @@ -151,7 +152,7 @@ on: branches: [main, dev/**, rc/**] ``` -**Usage**: Copy to `.github/workflows/dependency-review.yml` to enable dependency scanning on PRs. +**Usage**: Copy to `.gitea/workflows/dependency-review.yml` to enable dependency scanning on PRs. ### 4. Standards Compliance (`standards-compliance.yml.template`) @@ -177,7 +178,7 @@ on: workflow_dispatch: ``` -**Usage**: Copy to `.github/workflows/standards-compliance.yml` to enable compliance checks. +**Usage**: Copy to `.gitea/workflows/standards-compliance.yml` to enable compliance checks. ### 5. Flush Actions Cache (`flush-actions-cache.yml.template`) @@ -213,7 +214,7 @@ on: - Clear dependency caches after major updates (Composer, npm) - Preview cache usage with dry-run mode -**Usage**: Copy to `.github/workflows/flush-actions-cache.yml` to enable cache management. +**Usage**: Copy to `.gitea/workflows/flush-actions-cache.yml` to enable cache management. See [flush_actions_cache.py documentation](/docs/api/maintenance/flush-actions-cache-py.md) for detailed script usage. @@ -221,7 +222,7 @@ See [flush_actions_cache.py documentation](/docs/api/maintenance/flush-actions-c **Location**: `templates/workflows/generic/codeql-analysis.yml` -Security scanning with GitHub's CodeQL engine (also available in `.github/workflows/`). +Security scanning with GitHub's CodeQL engine (also available in `.gitea/workflows/`). See section below for complete details. @@ -231,7 +232,7 @@ The following templates are organized by platform in `templates/workflows/`. ### 1. CI Template (`ci.yml`) -**Location**: `.github/workflows/ci.yml` (MokoStandards root) +**Location**: `.gitea/workflows/ci.yml` (MokoStandards root) Continuous Integration workflow that enforces repository standards through automated validation. @@ -255,11 +256,11 @@ on: branches: [main, dev/**, rc/**, version/**] ``` -**Usage**: Copy from MokoStandards `.github/workflows/ci.yml` to your repository. +**Usage**: Copy from MokoStandards `.gitea/workflows/ci.yml` to your repository. ### 2. CodeQL Analysis Template (`codeql-analysis.yml`) -**Location**: `.github/workflows/codeql-analysis.yml` (MokoStandards root) +**Location**: `.gitea/workflows/codeql-analysis.yml` (MokoStandards root) Security scanning workflow using GitHub's CodeQL engine for vulnerability detection. @@ -282,7 +283,7 @@ on: workflow_dispatch: ``` -**Usage**: Copy from MokoStandards `.github/workflows/codeql-analysis.yml` to your repository. +**Usage**: Copy from MokoStandards `.gitea/workflows/codeql-analysis.yml` to your repository. ### 3. Dependency Review @@ -429,7 +430,7 @@ Workflows use automatic project type detection based on file presence. See [Proj 2. **Copy workflow files**: ```bash mkdir -p .github/workflows - cp /path/to/MokoStandards/templates/workflows/joomla/ci.yml .github/workflows/ + cp /path/to/MokoStandards/templates/workflows/joomla/ci.yml .gitea/workflows/ ``` 3. **Customize for your project**: @@ -440,7 +441,7 @@ Workflows use automatic project type detection based on file presence. See [Proj 4. **Commit and enable**: ```bash - git add .github/workflows/ + git add .gitea/workflows/ git commit -m "Add MokoStandards workflows" git push ``` diff --git a/docs/workflows/auto-release.md b/docs/workflows/auto-release.md index 6799394..b6f43ed 100644 --- a/docs/workflows/auto-release.md +++ b/docs/workflows/auto-release.md @@ -56,7 +56,7 @@ Joomla repos do **not** use FTP deploy. Distribution is via GitHub Release ZIPs. ## Triggers - Push to `main` or `master` -- Skips commits by `github-actions[bot]` and commits with `[skip ci]` +- Skips commits by `gitea-actions[bot]` and commits with `[skip ci]` ## Version Lifecycle @@ -88,6 +88,7 @@ Each stability level has its own GitHub Release tag: - No minor or patch production tags are created - Pre-release tags are updated in-place per stability level +## Stream Tags (v2)Releases use stream-based git tags, NOT version numbers:- `stable` — production release- `release-candidate` — RC testing- `beta` — feature-complete stability testing- `alpha` — early testing- `development` — unstable dev buildsTo trigger a release, push the appropriate stream tag: `git tag -f stable && git push origin stable --force`### Cascade LogicEach stability level cascades to all lower levels in updates.xml:- **stable** → updates development, alpha, beta, rc, stable- **rc** → updates development, alpha, beta, rc- **beta** → updates development, alpha, beta- **alpha** → updates development, alpha- **development** → updates development only### SHA-256 Rules- Never leave `` empty — Joomla fails checksum verification on empty tags- Omit the `` tag entirely if no hash is available- Always set SHA when building a package### creationDateAlways update `` whenever version is bumped — in the manifest AND in updates.xml.### Auto-DetectionThe release workflow (`release.yml`) is fully generic:- `GITEA_REPO` derived from `github.event.repository.name`- `EXT_ELEMENT` auto-detected from the Joomla manifest `` tag- Falls back to manifest filename, then repo name (lowercased)- No per-repo customization needed### Version History (Stable Releases)Stable releases keep up to 5 previous versions in the Gitea release body. ## Requirements - `secrets.GH_TOKEN` with `contents: write` permission diff --git a/docs/workflows/build-release.md b/docs/workflows/build-release.md index 7ccafd3..c71601b 100644 --- a/docs/workflows/build-release.md +++ b/docs/workflows/build-release.md @@ -41,7 +41,7 @@ Push to main ## Triggers - Push to `main` or `master` -- Skips commits by `github-actions[bot]` and commits with `[skip ci]` +- Skips commits by `gitea-actions[bot]` and commits with `[skip ci]` - Skips if tag + branch already exist (idempotent) ## What Each Step Does @@ -76,6 +76,7 @@ Extracts release notes from CHANGELOG.md (section matching the version heading) | Merge to main | `main` | Real version | Updated | Yes | | Version bump | `main` | Auto-incremented | Updated | Next push | +## Stream Tags (v2)Releases use stream-based git tags (`stable`, `release-candidate`, `beta`, `alpha`, `development`), NOT version numbers. To trigger a release, push the appropriate stream tag: `git tag -f stable && git push origin stable --force`### Cascade LogicEach stability level cascades to all lower levels in updates.xml:- **stable** → updates development, alpha, beta, rc, stable- **rc** → updates development, alpha, beta, rc- **beta** → updates development, alpha, beta- **alpha** → updates development, alpha- **development** → updates development only### SHA-256 Rules- Never leave `` empty — Joomla fails checksum verification on empty tags- Omit the `` tag entirely if no hash is available- Always set SHA when building a package### Auto-DetectionThe release workflow is fully generic:- `GITEA_REPO` derived from `github.event.repository.name`- `EXT_ELEMENT` auto-detected from the Joomla manifest `` tag- Falls back to manifest filename, then repo name (lowercased)- No per-repo customization needed ## Related Workflows | Workflow | Role | diff --git a/docs/workflows/bulk-repo-sync.md b/docs/workflows/bulk-repo-sync.md index 28085ed..14db9ed 100644 --- a/docs/workflows/bulk-repo-sync.md +++ b/docs/workflows/bulk-repo-sync.md @@ -84,7 +84,7 @@ The **Bulk Repository Sync** workflow is MokoStandards' automated system for dep ### Workflow Location -- **File**: `.github/workflows/bulk-repo-sync.yml` +- **File**: `.gitea/workflows/bulk-repo-sync.yml` - **Script**: `api/automation/bulk_sync.php` - **Version**: 5.0 (Rebuilt using Enterprise library) - **Status**: ✅ **ENTERPRISE READY** - Fully integrated with enterprise security, audit logging, and metrics @@ -316,7 +316,7 @@ locals { exclude_files = [ { - path = ".github/workflows/custom-ci.yml" + path = ".gitea/workflows/custom-ci.yml" reason = "Custom CI workflow with special requirements" } ] @@ -425,34 +425,34 @@ The bulk sync workflow synchronizes the following file types: #### 2. **Universal Workflows** (All Repositories) -- `.github/workflows/build.yml` - Build workflow -- `.github/workflows/ci.yml` - CI validation workflow +- `.gitea/workflows/build.yml` - Build workflow +- `.gitea/workflows/ci.yml` - CI validation workflow #### 3. **Platform-Specific Workflows** **Terraform Repositories**: -- `.github/workflows/terraform-ci.yml` - Terraform CI -- `.github/workflows/terraform-deploy.yml` - Terraform deployment -- `.github/workflows/terraform-drift.yml` - Drift detection +- `.gitea/workflows/terraform-ci.yml` - Terraform CI +- `.gitea/workflows/terraform-deploy.yml` - Terraform deployment +- `.gitea/workflows/terraform-drift.yml` - Drift detection **Dolibarr Repositories**: -- `.github/workflows/release.yml` - Dolibarr release workflow -- `.github/workflows/sync-changelogs.yml` - Changelog sync +- `.gitea/workflows/release.yml` - Dolibarr release workflow +- `.gitea/workflows/sync-changelogs.yml` - Changelog sync **Joomla Repositories**: -- `.github/workflows/release.yml` - Joomla release workflow -- `.github/workflows/repo-health.yml` - Repository health checks +- `.gitea/workflows/release.yml` - Joomla release workflow +- `.gitea/workflows/repo-health.yml` - Repository health checks **Generic Repositories**: -- `.github/workflows/code-quality.yml` - Code quality checks -- `.github/workflows/codeql-analysis.yml` - Security scanning -- `.github/workflows/repo-health.yml` - Health checks +- `.gitea/workflows/code-quality.yml` - Code quality checks +- `.gitea/workflows/codeql-analysis.yml` - Security scanning +- `.gitea/workflows/repo-health.yml` - Health checks #### 4. **Reusable Workflows** (All Repositories) -- `.github/workflows/reusable-build.yml` - Reusable build workflow -- `.github/workflows/reusable-release.yml` - Reusable release workflow -- `.github/workflows/reusable-project-detector.yml` - Project detection +- `.gitea/workflows/reusable-build.yml` - Reusable build workflow +- `.gitea/workflows/reusable-release.yml` - Reusable release workflow +- `.gitea/workflows/reusable-project-detector.yml` - Project detection - Additional reusable workflows based on platform #### 5. **Validation Scripts** (All Repositories) @@ -726,7 +726,7 @@ Sync PRs respect branch protection: **Updating the Workflow**: -1. Modify `.github/workflows/bulk-repo-sync.yml` +1. Modify `.gitea/workflows/bulk-repo-sync.yml` 2. Test changes with manual trigger on test repository 3. Commit and push to MokoStandards 4. Next scheduled run uses updated workflow diff --git a/docs/workflows/changelog-management.md b/docs/workflows/changelog-management.md index 2a76cc3..2cef524 100644 --- a/docs/workflows/changelog-management.md +++ b/docs/workflows/changelog-management.md @@ -87,7 +87,7 @@ python3 api/maintenance/release_version.py --version 05.01.00 --update-files --c ### Update Changelog Workflow -**File**: `.github/workflows/changelog_update.yml` +**File**: `.gitea/workflows/changelog_update.yml` **Trigger**: Manual workflow dispatch @@ -111,7 +111,7 @@ python3 api/maintenance/release_version.py --version 05.01.00 --update-files --c ### Version Release Workflow -**File**: `.github/workflows/version_release.yml` +**File**: `.gitea/workflows/version_release.yml` **Trigger**: Manual workflow dispatch diff --git a/docs/workflows/demo-deployment.md b/docs/workflows/demo-deployment.md index 9b1085d..dd37fa1 100644 --- a/docs/workflows/demo-deployment.md +++ b/docs/workflows/demo-deployment.md @@ -51,7 +51,7 @@ At least one of `DEMO_FTP_KEY` or `DEMO_FTP_PASSWORD` must be set. ## Behaviour -1. **Permission check** — `jmiller-moko` and `github-actions[bot]` are always authorized; other actors need `admin` or `maintain` role +1. **Permission check** — `jmiller` and `gitea-actions[bot]` are always authorized; other actors need `admin` or `maintain` role 2. **Skip on chore/ branches** — PRs from `chore/` branches do not trigger deployment 3. **Skip if DEMO_FTP_SUFFIX not set** — repos without the variable are silently skipped 4. **Clear remote folder** — always clears the remote destination before uploading diff --git a/docs/workflows/dev-branch-tracking.md b/docs/workflows/dev-branch-tracking.md index f173025..18e6076 100644 --- a/docs/workflows/dev-branch-tracking.md +++ b/docs/workflows/dev-branch-tracking.md @@ -55,7 +55,7 @@ The tracking system consists of three main components: - Documentation updates - Changelog updates - Version management -3. Assigns issues to `copilot` and `jmiller-moko` +3. Assigns issues to `copilot` and `jmiller` 4. Adds labels: `automation`, `version-management`, `dev-branch` ### 2. RC Branch Issue Creation (Automatic) @@ -73,14 +73,14 @@ The tracking system consists of three main components: - Release notes preparation - Final verification and sign-off 3. Creates a **draft GitHub Release** for the major version (`vXX`) -4. Assigns issues to `copilot` and `jmiller-moko` +4. Assigns issues to `copilot` and `jmiller` 5. Adds labels: `automation`, `version-management`, `rc-branch` The draft release is later published by `auto-release.yml` when the RC merges to main. ### 2. Enterprise Issue Manager Workflow -**File**: `.github/workflows/enterprise-issue-manager.yml` +**File**: `.gitea/workflows/enterprise-issue-manager.yml` **Purpose**: Coordinates pull requests with dev branch tracking issues throughout the PR lifecycle. @@ -207,7 +207,7 @@ No action required. When a PR is merged to main: # Workflow automatically: # 1. Creates dev/04.01 branch # 2. Creates tracking issue #456 -# 3. Assigns to copilot and jmiller-moko +# 3. Assigns to copilot and jmiller ``` ### Manual Issue Creation @@ -262,7 +262,7 @@ The launch checklist aligns with the [Copilot Pre-Merge Checklist Policy](../pol ### Standards Compliance Workflow The "Standards Compliance" checklist section integrates with: -- `.github/workflows/standards-compliance.yml` +- `.gitea/workflows/standards-compliance.yml` - File header validation - Formatting standards checks @@ -279,10 +279,10 @@ The "Security Scanning" checklist section integrates with: Default assignees for tracking issues: - `copilot` (GitHub Copilot agent) -- `jmiller-moko` (organization owner — GitHub assignees must be a user account, not an org) +- `jmiller` (organization owner — GitHub assignees must be a user account, not an org) To change assignees: -- **Auto-created issues**: Edit `.github/workflows/auto-create-dev-branch.yml` line 289 +- **Auto-created issues**: Edit `.gitea/workflows/auto-create-dev-branch.yml` line 289 - **Manual template**: Edit `.github/ISSUE_TEMPLATE/dev-branch-tracking.md` line 6 ### Labels @@ -293,7 +293,7 @@ Default labels for tracking issues: - `dev-branch` - Development branch marker To change labels: -- **Auto-created issues**: Edit `.github/workflows/auto-create-dev-branch.yml` line 288 +- **Auto-created issues**: Edit `.gitea/workflows/auto-create-dev-branch.yml` line 288 - **Manual template**: Edit `.github/ISSUE_TEMPLATE/dev-branch-tracking.md` line 5 ### Branch Patterns @@ -304,7 +304,7 @@ The system tracks branches matching: - `beta/*` - Beta testing branches (optional stage) - `rc/*` - Release candidate branches -To modify patterns, edit `.github/workflows/enterprise-issue-manager.yml` line 143. +To modify patterns, edit `.gitea/workflows/enterprise-issue-manager.yml` line 143. ## Troubleshooting @@ -376,8 +376,8 @@ To modify patterns, edit `.github/workflows/enterprise-issue-manager.yml` line 1 ## Related Documentation - [Copilot Pre-Merge Checklist Policy](../policy/copilot-pre-merge-checklist.md) -- [Auto-Create Dev Branch Workflow](../../.github/workflows/auto-create-dev-branch.yml) -- [Enterprise Issue Manager Workflow](../../.github/workflows/enterprise-issue-manager.yml) +- [Auto-Create Dev Branch Workflow](../../.gitea/workflows/auto-create-dev-branch.yml) +- [Enterprise Issue Manager Workflow](../../.gitea/workflows/enterprise-issue-manager.yml) - [Dev Branch Tracking Template](../../.github/ISSUE_TEMPLATE/dev-branch-tracking.md) - [Workflow Architecture](./workflow-architecture.md) - [Contributing Guide](../../CONTRIBUTING.md) diff --git a/docs/workflows/dev-deployment.md b/docs/workflows/dev-deployment.md index 6ed4e0e..7cabf35 100644 --- a/docs/workflows/dev-deployment.md +++ b/docs/workflows/dev-deployment.md @@ -28,7 +28,7 @@ The `deploy-dev.yml` workflow pushes the contents of `src/` to a development ser - A pull request targeting those branches is **merged** (skips `chore/` branches) - Triggered manually via workflow dispatch -**Access control:** `jmiller-moko` and `github-actions[bot]` are always authorized. Other actors need **admin** or **maintain** role. +**Access control:** `jmiller` and `gitea-actions[bot]` are always authorized. Other actors need **admin** or **maintain** role. **Skips when:** `DEV_FTP_SUFFIX` variable is not set, or the branch starts with `chore/`. diff --git a/docs/workflows/index.md b/docs/workflows/index.md index 71faf88..9bdafb7 100644 --- a/docs/workflows/index.md +++ b/docs/workflows/index.md @@ -29,6 +29,10 @@ Joomla repos use GitHub Release ZIPs via `auto-release.yml` + `updates.xml`. No - [Changelog Management](./changelog-management.md) — automated changelog generation - [Changelog Validation](./changelog-validation.md) — validates CHANGELOG.md format on PR +## Client Repositories + +- [Client Repo Standards](../client-repos.md) — naming, privacy, structure, deployment for client-* repos + ## CI & Compliance Workflows - [CI Joomla](./ci-joomla.md) — Joomla-specific CI checks (manifest validation, language files, XML lint) diff --git a/docs/workflows/release-system.md b/docs/workflows/release-system.md index 6d97c6a..2348e7c 100644 --- a/docs/workflows/release-system.md +++ b/docs/workflows/release-system.md @@ -58,6 +58,7 @@ Each stability level has its own GitHub Release tag: | `vXX` | Stable production | Stable ZIPs (major only, e.g., `v04`) | - The `vXX` production tag is **major only** — one release per major version +## Workflow Location (v2)All workflows MUST be in `.gitea/workflows/` only. Gitea Actions does not run workflows from `.github/workflows/`. Having files in `.github/workflows/` creates ghost queued runs that block the runner.## Stream TagsReleases use stream-based git tags, NOT version numbers:- `stable` — production release- `release-candidate` — RC testing- `beta` — feature-complete stability testing- `alpha` — early testing- `development` — unstable dev buildsTo trigger a release, push the appropriate stream tag: `git tag -f stable && git push origin stable --force`## Cascade LogicEach stability level cascades to all lower levels in updates.xml:- **stable** → updates development, alpha, beta, rc, stable- **rc** → updates development, alpha, beta, rc- **beta** → updates development, alpha, beta- **alpha** → updates development, alpha- **development** → updates development only## SHA-256 Rules- Never leave `` empty — Joomla fails checksum verification on empty tags- Omit the `` tag entirely if no hash is available- Always set SHA when building a package## creationDateAlways update `` whenever version is bumped — in the manifest AND in updates.xml.## Auto-DetectionThe release workflow (`release.yml`) is fully generic:- `GITEA_REPO` derived from `github.event.repository.name`- `EXT_ELEMENT` auto-detected from the Joomla manifest `` tag- Falls back to manifest filename, then repo name (lowercased)- No per-repo customization needed## Version History (Stable)Stable releases keep up to 5 previous versions in the Gitea release body. - All minor+patch versions **append** release notes and ZIP assets to the same `vXX` release - No minor or patch production tags are created - Example: versions `04.01.01`, `04.02.00`, `04.03.05` all update the same `v04` release @@ -133,7 +134,7 @@ To prevent automatic release creation, include `[skip ci]` in your commit messag git commit -m "docs: update README [skip ci]" ``` -Commits by `github-actions[bot]` are also skipped automatically (e.g., auto-bump commits). +Commits by `gitea-actions[bot]` are also skipped automatically (e.g., auto-bump commits). ## Architecture @@ -187,9 +188,9 @@ Commits by `github-actions[bot]` are also skipped automatically (e.g., auto-bump ## Configuration Files -- `.github/workflows/auto-release.yml` - Main release workflow (platform-aware) -- `.github/workflows/update-server.yml` - Joomla updates.xml generation for dev/alpha/beta/rc -- `.github/workflows/changelog-validation.yml` - CHANGELOG.md format validation +- `.gitea/workflows/auto-release.yml` - Main release workflow (platform-aware) +- `.gitea/workflows/update-server.yml` - Joomla updates.xml generation for dev/alpha/beta/rc +- `.gitea/workflows/changelog-validation.yml` - CHANGELOG.md format validation - `.mokostandards` - Platform configuration (`platform: joomla|dolibarr|generic`) - `README.md` - VERSION field (single source of truth) - `CHANGELOG.md` - Release notes source diff --git a/docs/workflows/reserve-dolibarr-module-id.md b/docs/workflows/reserve-dolibarr-module-id.md index 0ba9da2..fce2c6d 100644 --- a/docs/workflows/reserve-dolibarr-module-id.md +++ b/docs/workflows/reserve-dolibarr-module-id.md @@ -11,7 +11,7 @@ The `reserve-dolibarr-module-id.yml` workflow automates the reservation of Dolib ## Quick Links - **[Module Registry](../development/crm/module-registry.md)** - Official Dolibarr module number registry -- **[Run Workflow](../../.github/workflows/reserve-dolibarr-module-id.yml)** - Reserve a module ID now +- **[Run Workflow](../../.gitea/workflows/reserve-dolibarr-module-id.yml)** - Reserve a module ID now - **[Development Guide](../guide/crm/dolibarr-development-guide.md)** - CRM development guide - **[Development Standards](../policy/crm/development-standards.md)** - Coding standards @@ -27,7 +27,7 @@ The `reserve-dolibarr-module-id.yml` workflow automates the reservation of Dolib ### Workflow Location -**File**: `.github/workflows/reserve-dolibarr-module-id.yml` +**File**: `.gitea/workflows/reserve-dolibarr-module-id.yml` **Trigger**: Manual (workflow_dispatch) **Permissions**: `contents: write`, `pull-requests: write` **Repository**: [https://git.mokoconsulting.tech/MokoConsulting/MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards) @@ -202,7 +202,7 @@ Updates `override.config.tf` to protect the workflow file: ```hcl { - path = ".github/workflows/reserve-dolibarr-module-id.yml" + path = ".gitea/workflows/reserve-dolibarr-module-id.yml" reason = "Dolibarr module ID reservation workflow" }, ``` diff --git a/docs/workflows/reusable-workflows.md b/docs/workflows/reusable-workflows.md index 9a66fb5..d689e75 100644 --- a/docs/workflows/reusable-workflows.md +++ b/docs/workflows/reusable-workflows.md @@ -26,14 +26,14 @@ MokoStandards provides seven reusable GitHub Actions workflows that enable consi # Basic quality check jobs: quality: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-php-quality.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-php-quality.yml@main # Type-aware build and release build: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-build.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-build.yml@main release: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-release.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-release.yml@main with: version: '1.0.0' ``` @@ -68,7 +68,7 @@ Runs comprehensive PHP code quality checks using PHPCS, PHPStan, and Psalm with ```yaml jobs: quality: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-php-quality.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-php-quality.yml@main with: php-versions: '["8.1", "8.2"]' tools: '["phpcs", "phpstan", "psalm"]' @@ -93,7 +93,7 @@ Matrix testing for Joomla extensions across PHP and Joomla versions with PHPUnit ```yaml jobs: test: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-joomla-testing.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-joomla-testing.yml@main with: php-versions: '["8.1", "8.2"]' joomla-versions: '["4.4", "5.0", "5.1"]' @@ -118,7 +118,7 @@ Repository standards validation with configurable profiles (basic, full, strict) ```yaml jobs: validate: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-ci-validation.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-ci-validation.yml@main with: profile: 'full' validate-security: true @@ -149,7 +149,7 @@ Automatically cleans up stale and merged branches with configurable exclusion pa ```yaml jobs: cleanup: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-branch-cleanup.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-branch-cleanup.yml@main with: stale-days: 90 delete-merged: true @@ -178,7 +178,7 @@ jobs: ```yaml jobs: cleanup: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-branch-cleanup.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-branch-cleanup.yml@main with: stale-days: 60 delete-merged: true @@ -200,7 +200,7 @@ Automatically detects project type and provides outputs for downstream workflows ```yaml jobs: detect: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-project-detector.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-project-detector.yml@main ``` **Outputs:** @@ -217,7 +217,7 @@ Universal build workflow that adapts to project type with automatic dependency m ```yaml jobs: build: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-build.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-build.yml@main with: php-version: '8.1' node-version: '20.x' @@ -243,7 +243,7 @@ Creates releases with type-specific packaging and marketplace support. ```yaml jobs: release: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-release.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-release.yml@main with: version: '1.0.0' prerelease: false @@ -272,7 +272,7 @@ Multi-environment deployment with type-specific logic and health checks. ```yaml jobs: deploy: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-deploy.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-deploy.yml@main with: environment: staging deployment-method: rsync @@ -326,11 +326,11 @@ permissions: jobs: # Auto-detect project type detect: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-project-detector.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-project-detector.yml@main # Validate code validate: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-ci-validation.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-ci-validation.yml@main with: profile: 'full' @@ -338,7 +338,7 @@ jobs: quality: needs: detect if: needs.detect.outputs.has-php == 'true' - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-php-quality.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-php-quality.yml@main with: php-versions: '["8.1", "8.2"]' @@ -346,7 +346,7 @@ jobs: test: needs: [detect, quality] if: needs.detect.outputs.project-type == 'joomla' - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-joomla-testing.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-joomla-testing.yml@main with: php-versions: '["8.1", "8.2"]' coverage: true @@ -356,13 +356,13 @@ jobs: # Build (works for all types) build: needs: [detect, validate] - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-build.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-build.yml@main # Deploy to staging deploy-staging: needs: build if: github.ref == 'refs/heads/staging' - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-deploy.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-deploy.yml@main with: environment: staging deployment-method: rsync @@ -372,7 +372,7 @@ jobs: release: needs: [detect, build] if: startsWith(github.ref, 'refs/tags/v') - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-release.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-release.yml@main with: version: ${{ github.ref_name }} @@ -380,7 +380,7 @@ jobs: deploy-production: needs: release if: github.event_name == 'release' - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-deploy.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-deploy.yml@main with: environment: production version: ${{ github.event.release.tag_name }} @@ -398,19 +398,19 @@ on: [push, pull_request] jobs: validate: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-ci-validation.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-ci-validation.yml@main with: profile: 'full' quality: needs: validate - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-php-quality.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-php-quality.yml@main with: php-versions: '["8.1", "8.2"]' test: needs: quality - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-joomla-testing.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-joomla-testing.yml@main with: coverage: true secrets: @@ -425,17 +425,17 @@ jobs: **Recommended:** Pin to main branch for automatic updates ```yaml -uses: MokoConsulting/MokoStandards/.github/workflows/reusable-php-quality.yml@main +uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-php-quality.yml@main ``` **Stable:** Pin to specific tag ```yaml -uses: MokoConsulting/MokoStandards/.github/workflows/reusable-php-quality.yml@v1.0.0 +uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-php-quality.yml@v1.0.0 ``` **Maximum Stability:** Pin to commit SHA ```yaml -uses: MokoConsulting/MokoStandards/.github/workflows/reusable-php-quality.yml@abc1234 +uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-php-quality.yml@abc1234 ``` ### Secret Management @@ -461,14 +461,14 @@ jobs: # Dev branches: basic validation validate-dev: if: startsWith(github.ref, 'refs/heads/dev/') - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-ci-validation.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-ci-validation.yml@main with: profile: 'basic' # Main branch: strict validation validate-main: if: github.ref == 'refs/heads/main' - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-ci-validation.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-ci-validation.yml@main with: profile: 'strict' fail-on-warnings: true @@ -479,7 +479,7 @@ jobs: ## Troubleshooting ### Workflow Not Found -**Error:** `Unable to resolve action MokoConsulting/MokoStandards/.github/workflows/...` +**Error:** `Unable to resolve action MokoConsulting/MokoStandards/.gitea/workflows/...` **Solution:** Ensure calling repository has access to MokoStandards. For private repositories, configure proper access permissions. @@ -536,7 +536,7 @@ jobs: # After: Reusable workflow jobs: quality: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-php-quality.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-php-quality.yml@main with: phpcs-standard: 'PSR12' tools: '["phpcs"]' diff --git a/docs/workflows/shared-workflows.md b/docs/workflows/shared-workflows.md index b9878ad..9dd0582 100644 --- a/docs/workflows/shared-workflows.md +++ b/docs/workflows/shared-workflows.md @@ -55,7 +55,7 @@ These workflows are synced to every governed repository via `bulk_sync.php`. The **Requirements:** `secrets.GH_TOKEN` with write access -**Skips:** Commits by `github-actions[bot]`, commits with `[skip ci]` +**Skips:** Commits by `gitea-actions[bot]`, commits with `[skip ci]` --- @@ -77,7 +77,7 @@ These workflows are synced to every governed repository via `bulk_sync.php`. The **On patch releases (patch != 00):** 7. Updates the existing minor release (appends patch notes, updates title) -**Protection:** Workflow files are protected by CODEOWNERS (requires @jmiller-moko review) and a file path restriction ruleset. Only bypass actors can modify `.github/workflows/`. +**Protection:** Workflow files are protected by CODEOWNERS (requires @jmiller review) and a file path restriction ruleset. Only bypass actors can modify `.gitea/workflows/`. | Version | What happens | |---------|-------------| @@ -108,7 +108,7 @@ See dedicated docs: ### Common Features (both deploy workflows) -- **Permission check:** `jmiller-moko` and `github-actions[bot]` hardcoded as authorized; others need `admin`/`maintain` role +- **Permission check:** `jmiller` and `gitea-actions[bot]` hardcoded as authorized; others need `admin`/`maintain` role - **Chore skip:** PRs from `chore/` branches do not deploy - **Suffix required:** `{ENV}_FTP_SUFFIX` must be set or deployment is skipped - **Clear before upload:** Remote folder is always cleared before uploading @@ -143,7 +143,7 @@ See [update-server.md](update-server.md) for the full update server specificatio **Trigger:** Branch creation matching `dev/**` or `rc/**` -**What it does:** Auto-creates a tracking issue when a new `dev/**` or `rc/**` branch is pushed. Assigns `jmiller-moko`. +**What it does:** Auto-creates a tracking issue when a new `dev/**` or `rc/**` branch is pushed. Assigns `jmiller`. | Branch type | Title prefix | Label | |-------------|-------------|-------| @@ -161,7 +161,7 @@ Skips if an issue for that version already exists. **What it does (always):** - Deletes 25 retired workflow files - Checks for version drift across all files -- Creates `.github/workflows/custom/` directory if missing +- Creates `.gitea/workflows/custom/` directory if missing **What it does (toggleable):** - Reset labels to 58-label standard set (manual only, off by default) @@ -175,10 +175,10 @@ Skips if an issue for that version already exists. ## Custom Workflows -Every governed repo has a `.github/workflows/custom/` directory that is **never touched by sync or cleanup**. Place repo-specific workflows here: +Every governed repo has a `.gitea/workflows/custom/` directory that is **never touched by sync or cleanup**. Place repo-specific workflows here: ``` -.github/workflows/ +.gitea/workflows/ ├── deploy-dev.yml ← Synced (overwritten on sync) ├── auto-release.yml ← Synced (overwritten on sync) ├── repository-cleanup.yml ← Synced (overwritten on sync) diff --git a/docs/workflows/standards-compliance.md b/docs/workflows/standards-compliance.md index f6560ed..52cd394 100644 --- a/docs/workflows/standards-compliance.md +++ b/docs/workflows/standards-compliance.md @@ -126,7 +126,7 @@ cp templates/docs/required/SECURITY.md ./ **Remediation**: ```bash # Fix YAML formatting -yamllint --format auto .github/workflows/*.yml +yamllint --format auto .gitea/workflows/*.yml # Fix Python formatting black scripts/**/*.py @@ -186,7 +186,7 @@ phpcs --standard=PSR12 src/ - All version references match the canonical version in `composer.json` - Checks 39+ files including: - Documentation (README.md, CHANGELOG.md, CONTRIBUTING.md) - - Workflows (.github/workflows/*.yml) + - Workflows (.gitea/workflows/*.yml) - PHP source files (src/**/*.php) - Configuration files @@ -229,7 +229,7 @@ python3 api/maintenance/validate_script_registry.py --priority critical python3 api/maintenance/generate_script_registry.py --update # Or use auto-update workflow -# .github/workflows/auto-update-sha.yml +# .gitea/workflows/auto-update-sha.yml ``` ### 9. Enterprise Readiness Check ⭐ NEW (Informational) @@ -349,7 +349,7 @@ php api/validate/check_enterprise_readiness.php --verbose php api/validate/check_repo_health.php --verbose # Lint YAML files -yamllint .github/workflows/*.yml +yamllint .gitea/workflows/*.yml # Format Python code black --check scripts/**/*.py diff --git a/docs/workflows/sub-issue-management.md b/docs/workflows/sub-issue-management.md index 1bf08d6..278fac5 100644 --- a/docs/workflows/sub-issue-management.md +++ b/docs/workflows/sub-issue-management.md @@ -205,7 +205,7 @@ The Create Sub-Issue workflow provides automation for: - [Issue Management Configuration](.github/issue-management-config.yml) - [Sub-Task Template](.github/ISSUE_TEMPLATE/sub-task.yml) -- [Create Sub-Issue Workflow](.github/workflows/create-sub-issue.yml) +- [Create Sub-Issue Workflow](.gitea/workflows/create-sub-issue.yml) - [GitHub Issues Documentation](https://docs.github.com/en/issues) ## Support diff --git a/docs/workflows/update-server.md b/docs/workflows/update-server.md index 4260163..f5a4aef 100644 --- a/docs/workflows/update-server.md +++ b/docs/workflows/update-server.md @@ -33,10 +33,44 @@ Joomla's `updates.xml` contains **multiple `` entries simultaneously** | Release Candidate | `rc` | Sites set to RC or lower | `update-server.yml` on `rc/**` push | | Beta | `beta` | Sites set to Beta or lower | `update-server.yml` on `beta/**` push | | Alpha | `alpha` | Sites set to Alpha or lower | `update-server.yml` on `alpha/**` push | -| Development | `development` | Sites set to Development | `update-server.yml` on `dev/**` push | +| Development | `development` | Sites set to Development | `update-server.yml` on `dev` or `dev/**` push | **Note**: Alpha and beta are optional stages. Not every release cycle will have alpha/beta entries. +### `update-server.yml` Trigger Behavior + +The `update-server.yml` workflow triggers on **both direct pushes and PR merges** to the following branches: + +- `dev` (bare branch — no sub-path required) +- `dev/**` (versioned dev branches like `dev/02.01`) +- `alpha/**` +- `beta/**` +- `rc/**` + +Previously, the workflow only triggered on PR merges. The addition of push triggers ensures that direct commits to these branches (e.g., CI-generated version bumps, automated fixes) also update the `updates.xml` entries. + +### Cascade Release Channels + +Each stability level writes its own channel **and all lower channels** to `updates.xml`. This ensures Joomla sites on any "Minimum Stability" setting always see the latest available release: + +| Release Stream | Channels written to updates.xml | +|---------------|-------------------------------| +| development | `development` | +| alpha | `development`, `alpha` | +| beta | `development`, `alpha`, `beta` | +| rc | `development`, `alpha`, `beta`, `rc` | +| stable | `development`, `alpha`, `beta`, `rc`, `stable` | + +Without cascade, a site set to "Development" minimum stability would only see `development` entries and would miss stable releases entirely. The cascade ensures stable releases are visible to all sites regardless of their minimum stability setting. + +For full cascade documentation, see [Cascade Release Channels](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/src/branch/main/docs/release-management/cascade-channels.md). + +### Sync to Main + +Since Joomla sites read `updates.xml` from the `main` branch, the `update-server.yml` workflow **syncs `updates.xml` to `main` via the Gitea API** after building on non-main branches. This ensures pre-release channel entries (dev, alpha, beta, rc) are visible to sites checking for updates, without requiring a PR merge to main. + +Previously, `update-server.yml` only committed `updates.xml` to the current branch, so Joomla sites never saw dev/alpha/beta/rc releases until they were merged to main. + ### How Joomla Filters Updates Joomla's update system reads all `` entries from the XML file but only presents entries whose `` matches the site's minimum stability threshold: @@ -66,7 +100,7 @@ dev → [alpha] → [beta] → rc → version/XX → main → dev optional optional (integration) (production) (feedback) ``` -- **`dev/**`**: Active development. Update files tagged as `development`. +- **`dev` or `dev/**`**: Active development. Update files tagged as `development`. - **`alpha/**`**: *(Optional)* Early internal testing. Update files tagged as `alpha`. Can be skipped. - **`beta/**`**: *(Optional)* Broader external testing. Update files tagged as `beta`. Can be skipped. - **`rc/**`**: Release candidate. Update files tagged as `rc`. RC branches deploy to dev server for final testing. @@ -95,93 +129,101 @@ development The module descriptor's `url_last_version` should point to: ``` -https://raw.githubusercontent.com/{org}/{repo}/main/update.txt +https://git.mokoconsulting.tech/MokoConsulting/{repo}/raw/branch/main/update.txt ``` ## Joomla: `updates.xml` (Multi-Entry) The `updates.xml` file contains **up to five stability entries at once** (one per stability level). Joomla reads the entire file and filters by the site's minimum stability setting. +### Platform Distribution + +| Release Type | Gitea Release | GitHub Release | Download URLs | +|-------------|---------------|----------------|---------------| +| **Stable** | Yes | Yes (via mirror) | Dual (Gitea + GitHub) | +| **RC** | Yes | No | Single (Gitea only) | +| **Beta** | Yes | No | Single (Gitea only) | +| **Alpha** | Yes | No | Single (Gitea only) | +| **Development** | Yes | No | Single (Gitea only) | + +Pre-release builds stay on Gitea for internal testing. Only stable releases are mirrored to GitHub. + ### Complete Multi-Entry Example ```xml - + My Extension My Extension stable release com_myextension component 01.02.03 - site stable - https://github.com/org/repo/releases/tag/v01 + https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases - https://github.com/org/repo/releases/download/v01/com_myextension-01.02.03.zip + https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/v01/com_myextension-01.02.03.zip + https://github.com/mokoconsulting-tech/MyExtension/releases/download/v01/com_myextension-01.02.03.zip - abc123...full-hash-here Moko Consulting https://mokoconsulting.tech - + My Extension My Extension release candidate com_myextension component 01.03.01-rc - site rc - https://github.com/org/repo/tree/rc/01.03.01 + https://git.mokoconsulting.tech/MokoConsulting/MyExtension/src/branch/rc - https://github.com/org/repo/archive/refs/heads/rc/01.03.01.zip + https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/rc/com_myextension-01.03.01-rc.zip Moko Consulting https://mokoconsulting.tech - + My Extension My Extension beta build com_myextension component 01.03.01-beta - site beta - https://github.com/org/repo/tree/beta/01.03.01 + https://git.mokoconsulting.tech/MokoConsulting/MyExtension/src/branch/beta - https://github.com/org/repo/archive/refs/heads/beta/01.03.01.zip + https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/beta/com_myextension-01.03.01-beta.zip Moko Consulting https://mokoconsulting.tech - + My Extension My Extension alpha build com_myextension component 01.03.01-alpha - site alpha - https://github.com/org/repo/tree/alpha/01.03.01 + https://git.mokoconsulting.tech/MokoConsulting/MyExtension/src/branch/alpha - https://github.com/org/repo/archive/refs/heads/alpha/01.03.01.zip + https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/alpha/com_myextension-01.03.01-alpha.zip Moko Consulting @@ -195,13 +237,12 @@ The `updates.xml` file contains **up to five stability entries at once** (one pe com_myextension component 01.04.00-dev - site development - https://github.com/org/repo/tree/dev/01.04 + https://git.mokoconsulting.tech/MokoConsulting/MyExtension/src/branch/dev - https://github.com/org/repo/archive/refs/heads/dev/01.04.zip + https://git.mokoconsulting.tech/MokoConsulting/MyExtension/releases/download/development/com_myextension-01.04.00-dev.zip Moko Consulting @@ -215,12 +256,14 @@ The `updates.xml` file contains **up to five stability entries at once** (one pe | Workflow | Trigger | Entry written | |----------|---------|---------------| | `auto-release.yml` | Push to `main` | `stable` — writes the stable entry with SHA-256 hash of the ZIP | -| `update-server.yml` | Push to `rc/**` | `rc` — adds/updates the RC entry | -| `update-server.yml` | Push to `beta/**` | `beta` — adds/updates the beta entry | -| `update-server.yml` | Push to `alpha/**` | `alpha` — adds/updates the alpha entry | -| `update-server.yml` | Push to `dev/**` | `development` — adds/updates the development entry | +| `update-server.yml` | Push to `rc/**` | `rc` — adds/updates the RC entry (+ cascaded lower channels) | +| `update-server.yml` | Push to `beta/**` | `beta` — adds/updates the beta entry (+ cascaded lower channels) | +| `update-server.yml` | Push to `alpha/**` | `alpha` — adds/updates the alpha entry (+ cascaded lower channels) | +| `update-server.yml` | Push to `dev` or `dev/**` | `development` — adds/updates the development entry | -The `auto-release.yml` workflow writes the stable entry and preserves any existing pre-release entries. The `update-server.yml` workflow writes only its specific entry (rc, beta, alpha, or dev) and preserves the others. +The `auto-release.yml` workflow writes the stable entry and preserves any existing pre-release entries. The `update-server.yml` workflow writes its specific entry (and cascaded lower channels) and preserves the others. + +**Important**: All `update-server.yml` runs also sync the updated `updates.xml` to `main` via the Gitea API, since Joomla sites read the update server XML from the `main` branch. ### XML Elements @@ -271,32 +314,40 @@ The `` tag tells Joomla where to check for updates. Both servers - Uploads ZIP to the `vXX` major release on GitHub - Computes SHA-256 hash of the ZIP - Writes/updates the `stable` entry in `updates.xml` with version, download URL, and SHA-256 + - Cascades to all 5 stability channels - Preserves any existing rc/dev entries in the file - Commits updated `updates.xml` to main 2. **On RC push** (`update-server.yml` → rc/** branches): - Writes/updates the `rc` entry in `updates.xml` - - Download URL points to the branch archive ZIP + - Cascades to `rc`, `beta`, `alpha`, and `development` channels + - Download URL points to the Gitea release ZIP - Preserves all other stability entries - Commits updated `updates.xml` to the rc branch + - **Syncs `updates.xml` to `main` via Gitea API** 3. **On beta push** (`update-server.yml` → beta/** branches): - Writes/updates the `beta` entry in `updates.xml` - - Download URL points to the branch archive ZIP + - Cascades to `beta`, `alpha`, and `development` channels + - Download URL points to the Gitea release ZIP - Preserves all other stability entries - Commits updated `updates.xml` to the beta branch + - **Syncs `updates.xml` to `main` via Gitea API** 4. **On alpha push** (`update-server.yml` → alpha/** branches): - Writes/updates the `alpha` entry in `updates.xml` - - Download URL points to the branch archive ZIP + - Cascades to `alpha` and `development` channels + - Download URL points to the Gitea release ZIP - Preserves all other stability entries - Commits updated `updates.xml` to the alpha branch + - **Syncs `updates.xml` to `main` via Gitea API** -5. **On dev push** (`update-server.yml` → dev/** branches): +5. **On dev push** (`update-server.yml` → `dev` or `dev/**` branches): - Writes/updates the `development` entry in `updates.xml` - - Download URL points to the branch archive ZIP + - Download URL points to the Gitea release ZIP - Preserves all other stability entries - Commits updated `updates.xml` to the dev branch + - **Syncs `updates.xml` to `main` via Gitea API** ### Dolibarr (update.txt) @@ -331,3 +382,37 @@ Branch protection rulesets (applied via `sync_rulesets.php`): - **ALPHA**: prevents deletion, non-fast-forward - **BETA**: prevents deletion, non-fast-forward - **RC**: prevents deletion, non-fast-forward + +## Update Server Priority + +Joomla manifest `` entries MUST follow this priority order: + +| Priority | Server | URL pattern | +|----------|--------|-------------| +| **1 (primary)** | Gitea | `https://git.mokoconsulting.tech/MokoConsulting/{REPO}/raw/branch/main/updates.xml` | +| **2 (fallback)** | GitHub | `https://raw.githubusercontent.com/mokoconsulting-tech/{REPO}/main/updates.xml` | + +### Why Gitea first + +1. **Gitea is the source of truth** — all CI/CD runs on Gitea, releases are created here first +2. **GitHub is a push mirror** — it may lag behind by minutes or hours +3. **Self-hosted control** — Gitea is under our infrastructure, GitHub is third-party +4. **Availability** — if GitHub has an outage, Joomla sites still get updates from Gitea + +### Manifest example + +```xml + + + https://git.mokoconsulting.tech/MokoConsulting/RepoName/raw/branch/main/updates.xml + + + https://raw.githubusercontent.com/mokoconsulting-tech/RepoName/main/updates.xml + + +``` + +### Enforcement + +The `enforce_tags.sh` script and `repo_health.yml` workflow validate this ordering. +Repos with GitHub as priority 1 will be flagged as non-compliant. diff --git a/docs/workflows/workflow-architecture.md b/docs/workflows/workflow-architecture.md index 1723748..644e3d8 100644 --- a/docs/workflows/workflow-architecture.md +++ b/docs/workflows/workflow-architecture.md @@ -32,6 +32,7 @@ BRIEF: Workflow architecture, hierarchy, and design patterns # Workflow Architecture ## Overview +> **Important (v2):** All workflows MUST be in `.gitea/workflows/` only. Gitea Actions does not run workflows from `.github/workflows/`. Having files in `.github/workflows/` creates ghost queued runs that block the runner. This document explains the workflow architecture used across Moko Consulting repositories, including the hierarchy, design patterns, reusable workflow patterns, and decision-making processes for workflow selection. @@ -68,7 +69,7 @@ Moko Consulting uses a three-tier architecture for GitHub Actions workflows: ↓ (called by) ┌─────────────────────────────────────────────────────────────┐ │ Tier 3: Local Workflows │ -│ Location: Individual repository .github/workflows/ │ +│ Location: Individual repository .gitea/workflows/ │ │ Visibility: Matches repository visibility │ │ Purpose: Repository-specific automation │ │ Examples: Project builds, tests, custom deployments │ @@ -77,7 +78,7 @@ Moko Consulting uses a three-tier architecture for GitHub Actions workflows: ### Tier 1: Organization-Wide Reusable Workflows -**Location**: `mokoconsulting-tech/.github-private/.github/workflows/` +**Location**: `mokoconsulting-tech/.github-private/.gitea/workflows/` **Characteristics**: - Private and secure @@ -103,7 +104,7 @@ Moko Consulting uses a three-tier architecture for GitHub Actions workflows: ### Tier 2: Public Reusable Workflows -**Location**: `MokoConsulting/MokoStandards/.github/workflows/` +**Location**: `MokoConsulting/MokoStandards/.gitea/workflows/` **Characteristics**: - Public and community-accessible @@ -130,7 +131,7 @@ Moko Consulting uses a three-tier architecture for GitHub Actions workflows: ### Tier 3: Local Workflows -**Location**: Individual repository `.github/workflows/` +**Location**: Individual repository `.gitea/workflows/` **Characteristics**: - Repository-specific @@ -160,17 +161,17 @@ Automatically detect project type and execute appropriate build/test strategy. ```yaml jobs: detect: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-project-detector.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-project-detector.yml@main build-joomla: needs: detect if: needs.detect.outputs.project-type == 'joomla' - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-joomla-testing.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-joomla-testing.yml@main build-generic: needs: detect if: needs.detect.outputs.project-type == 'generic' - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-build.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-build.yml@main ``` **Benefits**: @@ -186,18 +187,18 @@ Build complex workflows by composing simple reusable workflows. jobs: # Step 1: Validate code validate: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-ci-validation.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-ci-validation.yml@main # Step 2: Build if validation passes build: needs: validate - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-build.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-build.yml@main # Step 3: Deploy if build passes deploy: needs: build if: github.ref == 'refs/heads/main' - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-deploy.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-deploy.yml@main secrets: inherit ``` @@ -218,7 +219,7 @@ jobs: matrix: php-version: ['7.4', '8.0', '8.1', '8.2'] os: [ubuntu-latest, windows-latest] - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-php-quality.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-php-quality.yml@main with: php-version: ${{ matrix.php-version }} ``` @@ -236,13 +237,13 @@ Deploy to different environments based on branch or tag. jobs: deploy-staging: if: github.ref == 'refs/heads/dev' - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-deploy.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-deploy.yml@main with: environment: staging deploy-production: if: startsWith(github.ref, 'refs/tags/v') - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-deploy.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-deploy.yml@main with: environment: production ``` @@ -275,7 +276,7 @@ on: jobs: deploy: - uses: MokoConsulting/MokoStandards/.github/workflows/reusable-deploy.yml@main + uses: MokoConsulting/MokoStandards/.gitea/workflows/reusable-deploy.yml@main with: environment: ${{ inputs.environment }} dry-run: ${{ inputs.dry-run }} @@ -458,9 +459,9 @@ jobs: ```yaml jobs: validate: - uses: org/repo/.github/workflows/reusable-validate.yml@main + uses: org/repo/.gitea/workflows/reusable-validate.yml@main build: - uses: org/repo/.github/workflows/reusable-build.yml@main + uses: org/repo/.gitea/workflows/reusable-build.yml@main ``` ❌ **Bad**: Duplicate logic diff --git a/docs/workflows/workflow-inventory.md b/docs/workflows/workflow-inventory.md index 4dfc4e7..ba72ac1 100644 --- a/docs/workflows/workflow-inventory.md +++ b/docs/workflows/workflow-inventory.md @@ -89,7 +89,7 @@ All secrets are configured at **organization level** with inheritance: ```yaml jobs: deploy: - uses: mokoconsulting-tech/.github-private/.github/workflows/reusable/deploy-staging.yml@main + uses: mokoconsulting-tech/.github-private/.gitea/workflows/reusable/deploy-staging.yml@main secrets: inherit # All org secrets automatically available ``` @@ -175,7 +175,7 @@ env: **Migration Type:** Convert to reusable workflow in `.github-private` -**Proposed Location:** `.github-private/.github/workflows/reusable/php-quality.yml` +**Proposed Location:** `.github-private/.gitea/workflows/reusable/php-quality.yml` **Migration Complexity:** ⭐⭐⭐ Medium - Requires PHP tool setup and configuration @@ -283,7 +283,7 @@ on: jobs: quality: - uses: mokoconsulting-tech/.github-private/.github/workflows/reusable/php-quality.yml@main + uses: mokoconsulting-tech/.github-private/.gitea/workflows/reusable/php-quality.yml@main with: php-versions: '["7.4", "8.0", "8.1", "8.2"]' tools: '["phpcs", "phpstan", "psalm"]' @@ -460,7 +460,7 @@ outputs: **Migration Type:** Convert to reusable workflow in `.github-private` -**Proposed Location:** `.github-private/.github/workflows/reusable/release-pipeline.yml` +**Proposed Location:** `.github-private/.gitea/workflows/reusable/release-pipeline.yml` **Migration Complexity:** ⭐⭐⭐⭐⭐ Very High - Complex multi-stage process @@ -577,7 +577,7 @@ on: jobs: release: - uses: mokoconsulting-tech/.github-private/.github/workflows/reusable/release-pipeline.yml@main + uses: mokoconsulting-tech/.github-private/.gitea/workflows/reusable/release-pipeline.yml@main with: version: ${{ inputs.version }} platform: 'joomla' @@ -742,7 +742,7 @@ outputs: **Migration Type:** Convert to reusable workflow in `.github-private` -**Proposed Location:** `.github-private/.github/workflows/reusable/deploy-staging.yml` +**Proposed Location:** `.github-private/.gitea/workflows/reusable/deploy-staging.yml` **Migration Complexity:** ⭐⭐⭐⭐ High - Requires secure credential handling @@ -869,7 +869,7 @@ on: jobs: deploy: - uses: mokoconsulting-tech/.github-private/.github/workflows/reusable/deploy-staging.yml@main + uses: mokoconsulting-tech/.github-private/.gitea/workflows/reusable/deploy-staging.yml@main with: environment: staging health-check-url: 'https://staging.example.com/health' @@ -1015,7 +1015,7 @@ outputs: **Migration Type:** Convert to reusable workflow in `.github-private` -**Proposed Location:** `.github-private/.github/workflows/reusable/joomla-testing.yml` +**Proposed Location:** `.github-private/.gitea/workflows/reusable/joomla-testing.yml` **Migration Complexity:** ⭐⭐⭐⭐ High - Complex test matrix (PHP × Joomla versions) @@ -1134,7 +1134,7 @@ on: jobs: test: - uses: mokoconsulting-tech/.github-private/.github/workflows/reusable/joomla-testing.yml@main + uses: mokoconsulting-tech/.github-private/.gitea/workflows/reusable/joomla-testing.yml@main with: php-versions: '["7.4", "8.0", "8.1", "8.2"]' joomla-versions: '["4.4", "5.0", "5.1"]' @@ -1408,7 +1408,7 @@ Workflows use `secrets: inherit` to access all organization secrets: ```yaml jobs: quality: - uses: mokoconsulting-tech/.github-private/.github/workflows/reusable/php-quality.yml@main + uses: mokoconsulting-tech/.github-private/.gitea/workflows/reusable/php-quality.yml@main secrets: inherit # Automatically inherits all org secrets ``` diff --git a/lib/CliBase.php b/lib/CliBase.php index f3f4120..010cde0 100644 --- a/lib/CliBase.php +++ b/lib/CliBase.php @@ -11,7 +11,7 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /lib/CliBase.php * VERSION: 04.06.00 - * BRIEF: Standalone base CLI class for api/ scripts that do not use CliFramework + * BRIEF: Standalone base CLI class for scripts that do not use CliFramework */ declare(strict_types=1); diff --git a/lib/Common.php b/lib/Common.php index d53adb7..52a803a 100644 --- a/lib/Common.php +++ b/lib/Common.php @@ -11,7 +11,7 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /lib/Common.php * VERSION: 04.06.00 - * BRIEF: Common utility functions for api/ scripts + * BRIEF: Common utility functions for scripts * NOTE: Version format used throughout is zero-padded semver: XX.YY.ZZ (e.g. 04.00.04). * All version regex patterns enforce exactly two digits per component by design. */ diff --git a/lib/Enterprise/EnterpriseReadinessValidator.php b/lib/Enterprise/EnterpriseReadinessValidator.php index 9dabf4b..3b360ae 100644 --- a/lib/Enterprise/EnterpriseReadinessValidator.php +++ b/lib/Enterprise/EnterpriseReadinessValidator.php @@ -92,7 +92,7 @@ class EnterpriseReadinessValidator ]; foreach ($required as $library) { - $phpFile = "{$path}/api/lib/Enterprise/{$library}.php"; + $phpFile = "{$path}/lib/Enterprise/{$library}.php"; $this->addResult( "Enterprise library: {$library}", file_exists($phpFile), diff --git a/lib/Enterprise/GiteaAdapter.php b/lib/Enterprise/GiteaAdapter.php index 1fc5d00..5aa9699 100644 --- a/lib/Enterprise/GiteaAdapter.php +++ b/lib/Enterprise/GiteaAdapter.php @@ -273,6 +273,22 @@ class GiteaAdapter implements GitPlatformAdapter string $body = '', array $options = [] ): array { + // Gitea expects label IDs (int64), not names. Resolve if needed. + if (!empty($options['labels']) && is_string($options['labels'][0] ?? null)) { + $labelNames = $options['labels']; + $existing = $this->listLabels($org, $repo); + $nameToId = []; + foreach ($existing as $label) { + $nameToId[$label['name']] = $label['id']; + } + $options['labels'] = []; + foreach ($labelNames as $name) { + if (isset($nameToId[$name])) { + $options['labels'][] = $nameToId[$name]; + } + } + } + $data = array_merge([ 'title' => $title, 'body' => $body, diff --git a/lib/Enterprise/MokoStandardsParser.php b/lib/Enterprise/MokoStandardsParser.php new file mode 100644 index 0000000..20d4819 --- /dev/null +++ b/lib/Enterprise/MokoStandardsParser.php @@ -0,0 +1,546 @@ + + * + * This file is part of a Moko Consulting project. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * FILE INFORMATION + * DEFGROUP: MokoStandards.Enterprise + * INGROUP: MokoStandards + * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API + * PATH: /lib/Enterprise/MokoStandardsParser.php + * VERSION: 04.07.00 + * BRIEF: Parser for the XML-based .mokostandards repository manifest + */ + +declare(strict_types=1); + +namespace MokoEnterprise; + +use DOMDocument; +use SimpleXMLElement; + +/** + * MokoStandards Parser + * + * Reads, writes, and validates the .mokostandards repository manifest. + * The file uses XML format (no file extension) and lives at .gitea/.mokostandards. + * + * @package MokoStandards\Enterprise + * @version 04.07.00 + */ +class MokoStandardsParser +{ + public const SCHEMA_VERSION = '1.0'; + public const NAMESPACE_URI = 'https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API'; + public const STANDARDS_SOURCE = 'https://git.mokoconsulting.tech/MokoConsulting/MokoStandards'; + + /** Valid platform slugs — must match definitions/default/*.tf filenames. */ + public const VALID_PLATFORMS = [ + 'default-repository', + 'crm-module', + 'crm-platform', + 'generic-repository', + 'github-private-repository', + 'joomla-template', + 'standards-repository', + 'waas-component', + ]; + + /** + * Parse a .mokostandards XML string into a structured array. + * + * @param string $xmlContent Raw XML content of .mokostandards + * @return array{ + * identity: array{name: string, org: string, description?: string, license?: string, license_spdx?: string, topics?: list}, + * governance: array{platform: string, standards_version: string, standards_source: string, last_synced?: string}, + * build?: array, + * deploy?: array, + * scripts?: array, + * overrides?: array + * } + * @throws \RuntimeException If XML is invalid or missing required elements + */ + public function parse(string $xmlContent): array + { + libxml_use_internal_errors(true); + $xml = simplexml_load_string($xmlContent); + + if ($xml === false) { + $errors = libxml_get_errors(); + libxml_clear_errors(); + $msg = !empty($errors) ? $errors[0]->message : 'Unknown XML parse error'; + throw new \RuntimeException("Invalid .mokostandards XML: " . trim($msg)); + } + + // Register namespace for XPath + $xml->registerXPathNamespace('m', self::NAMESPACE_URI); + + $result = [ + 'schema_version' => (string) ($xml['schema-version'] ?? self::SCHEMA_VERSION), + 'identity' => $this->parseIdentity($xml), + 'governance' => $this->parseGovernance($xml), + ]; + + if (isset($xml->build)) { + $result['build'] = $this->parseBuild($xml->build); + } + if (isset($xml->deploy)) { + $result['deploy'] = $this->parseDeploy($xml->deploy); + } + if (isset($xml->scripts)) { + $result['scripts'] = $this->parseScripts($xml->scripts); + } + if (isset($xml->overrides)) { + $result['overrides'] = $this->parseOverrides($xml->overrides); + } + + return $result; + } + + /** + * Try to parse content, returning null on failure instead of throwing. + * + * @param string $content Raw file content (XML or legacy YAML-like) + * @return array|null Parsed data or null if unparseable + */ + public function tryParse(string $content): ?array + { + // Try XML first + if (str_contains($content, 'parse($content); + } catch (\RuntimeException $e) { + return null; + } + } + + // Try legacy YAML-like format (e.g. "platform: default-repository") + return $this->parseLegacy($content); + } + + /** + * Parse the legacy single-line YAML-like format. + * + * @param string $content e.g. "platform: default-repository\n" + * @return array|null Minimal parsed structure or null + */ + public function parseLegacy(string $content): ?array + { + $platform = null; + $fields = []; + + foreach (explode("\n", $content) as $line) { + $line = trim($line); + if ($line === '' || str_starts_with($line, '#')) { + continue; + } + if (preg_match('/^(\w[\w_-]*)\s*:\s*"?([^"]*)"?\s*$/', $line, $m)) { + $fields[$m[1]] = $m[2]; + } + } + + $platform = $fields['platform'] ?? null; + if ($platform === null) { + return null; + } + + return [ + 'schema_version' => '0.0', // legacy marker + 'identity' => [ + 'name' => $fields['governed_repo'] ?? '', + 'org' => '', + ], + 'governance' => [ + 'platform' => $platform, + 'standards_version' => $fields['standards_version'] ?? '', + 'standards_source' => $fields['standards_source'] ?? '', + ], + ]; + } + + /** + * Extract just the platform slug from any .mokostandards content (XML or legacy). + * + * @param string $content Raw file content + * @return string|null Platform slug or null if unreadable + */ + public function extractPlatform(string $content): ?string + { + $data = $this->tryParse($content); + return $data['governance']['platform'] ?? null; + } + + /** + * Generate XML .mokostandards content for a repository. + * + * @param array $params { + * @type string $name Repository name (required) + * @type string $org Organization (required) + * @type string $platform Platform slug (required) + * @type string $standards_version MokoStandards version + * @type string $description Repo description + * @type string $license SPDX license identifier + * @type list $topics Repo topics + * @type string $language Primary language + * @type string $runtime Runtime requirement + * @type string $package_type Package format + * @type string $entry_point Main entry file + * @type string $last_synced ISO 8601 timestamp + * } + * @return string Well-formed XML content + */ + public function generate(array $params): string + { + $name = $params['name'] ?? ''; + $org = $params['org'] ?? ''; + $platform = $params['platform'] ?? 'default-repository'; + $version = $params['standards_version'] ?? ''; + + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + + // Add comment header + $dom->appendChild($dom->createComment( + "\n MokoStandards Repository Manifest\n" + . " Auto-generated by MokoStandards bulk sync.\n" + . " Manual edits to and may be overwritten.\n" + . " See: docs/standards/mokostandards-file-spec.md\n" + )); + + // Root element + $root = $dom->createElementNS(self::NAMESPACE_URI, 'mokostandards'); + $root->setAttribute('schema-version', self::SCHEMA_VERSION); + $dom->appendChild($root); + + // + $identity = $dom->createElement('identity'); + $identity->appendChild($dom->createElement('name', $this->xmlEscape($name))); + $identity->appendChild($dom->createElement('org', $this->xmlEscape($org))); + + if (!empty($params['description'])) { + $identity->appendChild($dom->createElement('description', $this->xmlEscape($params['description']))); + } + + if (!empty($params['license'])) { + $license = $dom->createElement('license', $this->xmlEscape($this->licenseLabel($params['license']))); + $license->setAttribute('spdx', $params['license']); + $identity->appendChild($license); + } + + if (!empty($params['topics'])) { + $topics = $dom->createElement('topics'); + foreach ($params['topics'] as $topic) { + $topics->appendChild($dom->createElement('topic', $this->xmlEscape($topic))); + } + $identity->appendChild($topics); + } + + $root->appendChild($identity); + + // + $governance = $dom->createElement('governance'); + $governance->appendChild($dom->createElement('platform', $this->xmlEscape($platform))); + $governance->appendChild($dom->createElement('standards-version', $this->xmlEscape($version))); + $governance->appendChild($dom->createElement('standards-source', self::STANDARDS_SOURCE)); + + if (!empty($params['last_synced'])) { + $governance->appendChild($dom->createElement('last-synced', $params['last_synced'])); + } + + $root->appendChild($governance); + + // (optional) + if (!empty($params['language']) || !empty($params['runtime']) || !empty($params['package_type']) || !empty($params['entry_point'])) { + $build = $dom->createElement('build'); + if (!empty($params['language'])) { + $build->appendChild($dom->createElement('language', $this->xmlEscape($params['language']))); + } + if (!empty($params['runtime'])) { + $build->appendChild($dom->createElement('runtime', $this->xmlEscape($params['runtime']))); + } + if (!empty($params['package_type'])) { + $build->appendChild($dom->createElement('package-type', $this->xmlEscape($params['package_type']))); + } + if (!empty($params['entry_point'])) { + $build->appendChild($dom->createElement('entry-point', $this->xmlEscape($params['entry_point']))); + } + $root->appendChild($build); + } + + return $dom->saveXML(); + } + + /** + * Validate XML content against the XSD schema. + * + * @param string $xmlContent Raw XML content + * @param string|null $xsdPath Path to the XSD file (auto-detected if null) + * @return array{valid: bool, errors: list} + */ + public function validate(string $xmlContent, ?string $xsdPath = null): array + { + if ($xsdPath === null) { + $xsdPath = dirname(dirname(__DIR__)) . '/docs/standards/mokostandards-schema.xsd'; + } + + if (!file_exists($xsdPath)) { + return ['valid' => false, 'errors' => ["XSD schema not found: {$xsdPath}"]]; + } + + libxml_use_internal_errors(true); + $dom = new DOMDocument(); + $dom->loadXML($xmlContent); + + $valid = $dom->schemaValidate($xsdPath); + $errors = []; + + if (!$valid) { + foreach (libxml_get_errors() as $error) { + $errors[] = "Line {$error->line}: " . trim($error->message); + } + } + + libxml_clear_errors(); + + return ['valid' => $valid, 'errors' => $errors]; + } + + // ────────────────────────────────────────────────────────────── + // Private parsing helpers + // ────────────────────────────────────────────────────────────── + + private function parseIdentity(SimpleXMLElement $xml): array + { + $id = $xml->identity ?? null; + if ($id === null) { + throw new \RuntimeException('.mokostandards: missing required element'); + } + + $result = [ + 'name' => (string) ($id->name ?? ''), + 'org' => (string) ($id->org ?? ''), + ]; + + if ($result['name'] === '') { + throw new \RuntimeException('.mokostandards: is required'); + } + + if (isset($id->description)) { + $result['description'] = (string) $id->description; + } + if (isset($id->license)) { + $result['license'] = (string) $id->license; + $spdx = (string) ($id->license['spdx'] ?? ''); + if ($spdx !== '') { + $result['license_spdx'] = $spdx; + } + } + if (isset($id->topics)) { + $result['topics'] = []; + foreach ($id->topics->topic as $topic) { + $result['topics'][] = (string) $topic; + } + } + + return $result; + } + + private function parseGovernance(SimpleXMLElement $xml): array + { + $gov = $xml->governance ?? null; + if ($gov === null) { + throw new \RuntimeException('.mokostandards: missing required element'); + } + + $result = [ + 'platform' => (string) ($gov->platform ?? ''), + 'standards_version' => (string) ($gov->{'standards-version'} ?? ''), + 'standards_source' => (string) ($gov->{'standards-source'} ?? ''), + ]; + + if ($result['platform'] === '') { + throw new \RuntimeException('.mokostandards: is required'); + } + + if (isset($gov->{'last-synced'})) { + $result['last_synced'] = (string) $gov->{'last-synced'}; + } + + return $result; + } + + private function parseBuild(SimpleXMLElement $build): array + { + $result = []; + + foreach (['language', 'runtime', 'entry-point'] as $field) { + if (isset($build->$field)) { + $key = str_replace('-', '_', $field); + $result[$key] = (string) $build->$field; + } + } + if (isset($build->{'package-type'})) { + $result['package_type'] = (string) $build->{'package-type'}; + } + + if (isset($build->artifact)) { + $result['artifact'] = []; + foreach (['format', 'path', 'filename'] as $f) { + if (isset($build->artifact->$f)) { + $result['artifact'][$f] = (string) $build->artifact->$f; + } + } + } + + if (isset($build->dependencies)) { + $result['dependencies'] = []; + foreach ($build->dependencies->requires as $req) { + $dep = ['name' => (string) ($req['name'] ?? '')]; + if (isset($req['version'])) { + $dep['version'] = (string) $req['version']; + } + if (isset($req['type'])) { + $dep['type'] = (string) $req['type']; + } + $result['dependencies'][] = $dep; + } + } + + return $result; + } + + private function parseDeploy(SimpleXMLElement $deploy): array + { + $targets = []; + foreach ($deploy->target as $target) { + $t = [ + 'name' => (string) ($target['name'] ?? ''), + 'enabled' => ((string) ($target['enabled'] ?? 'true')) !== 'false', + 'host' => (string) ($target->host ?? ''), + 'path' => (string) ($target->path ?? ''), + ]; + if (isset($target->method)) { + $t['method'] = (string) $target->method; + } + if (isset($target->branch)) { + $t['branch'] = (string) $target->branch; + } + if (isset($target->{'src-dir'})) { + $t['src_dir'] = (string) $target->{'src-dir'}; + } + $targets[] = $t; + } + return ['targets' => $targets]; + } + + private function parseScripts(SimpleXMLElement $scripts): array + { + $result = []; + foreach ($scripts->script as $script) { + $s = [ + 'name' => (string) ($script['name'] ?? ''), + 'command' => (string) ($script->command ?? ''), + ]; + if (isset($script['phase'])) { + $s['phase'] = (string) $script['phase']; + } + if (isset($script->description)) { + $s['description'] = (string) $script->description; + } + if (isset($script->runner)) { + $s['runner'] = (string) $script->runner; + } + $result[] = $s; + } + return ['scripts' => $result]; + } + + private function parseOverrides(SimpleXMLElement $overrides): array + { + $result = []; + + if (isset($overrides->{'skip-files'})) { + $result['skip_files'] = []; + foreach ($overrides->{'skip-files'}->file as $file) { + $result['skip_files'][] = (string) $file; + } + } + + if (isset($overrides->{'skip-workflows'})) { + $result['skip_workflows'] = []; + foreach ($overrides->{'skip-workflows'}->file as $file) { + $result['skip_workflows'][] = (string) $file; + } + } + + if (isset($overrides->{'extra-secrets'})) { + $result['extra_secrets'] = []; + foreach ($overrides->{'extra-secrets'}->secret as $secret) { + $s = ['name' => (string) ($secret['name'] ?? '')]; + if (isset($secret['required'])) { + $s['required'] = ((string) $secret['required']) !== 'false'; + } + if (isset($secret['scope'])) { + $s['scope'] = (string) $secret['scope']; + } + $result['extra_secrets'][] = $s; + } + } + + return $result; + } + + /** + * Escape a string for XML element content. + */ + private function xmlEscape(string $value): string + { + return htmlspecialchars($value, ENT_XML1 | ENT_QUOTES, 'UTF-8'); + } + + /** + * Map SPDX identifier to a human-readable license label. + */ + private function licenseLabel(string $spdx): string + { + return match ($spdx) { + 'GPL-3.0-or-later' => 'GNU General Public License v3', + 'GPL-2.0-or-later' => 'GNU General Public License v2', + 'MIT' => 'MIT License', + 'Apache-2.0' => 'Apache License 2.0', + 'BSD-3-Clause' => 'BSD 3-Clause License', + 'LGPL-3.0-or-later' => 'GNU Lesser General Public License v3', + default => $spdx, + }; + } + + /** + * Map a platform slug to its default package type. + */ + public static function platformPackageType(string $platform): string + { + return match ($platform) { + 'crm-module', 'crm-platform' => 'dolibarr-module', + 'waas-component' => 'joomla-extension', + 'joomla-template' => 'joomla-extension', + 'standards-repository' => 'composer', + default => 'composer', + }; + } + + /** + * Map a platform slug to its default primary language. + */ + public static function platformLanguage(string $platform): string + { + return match ($platform) { + 'crm-module', 'crm-platform' => 'PHP', + 'waas-component', 'joomla-template' => 'PHP', + 'standards-repository' => 'PHP', + default => 'PHP', + }; + } +} diff --git a/lib/Enterprise/RepositorySynchronizer.php b/lib/Enterprise/RepositorySynchronizer.php index 03358b2..38f7721 100644 --- a/lib/Enterprise/RepositorySynchronizer.php +++ b/lib/Enterprise/RepositorySynchronizer.php @@ -39,12 +39,13 @@ class RepositorySynchronizer private const VERSION_BRANCH = 'version/' . self::STANDARDS_MAJOR; private const SYNC_BRANCH = 'chore/sync-mokostandards-v' . self::STANDARDS_MINOR; - private ApiClient $apiClient; - private GitPlatformAdapter $adapter; - private AuditLogger $logger; - private MetricsCollector $metrics; - private CheckpointManager $checkpoints; - private DefinitionParser $definitionParser; + private ApiClient $apiClient; + private GitPlatformAdapter $adapter; + private AuditLogger $logger; + private MetricsCollector $metrics; + private CheckpointManager $checkpoints; + private DefinitionParser $definitionParser; + private MokoStandardsParser $manifestParser; /** * Constructor @@ -70,6 +71,7 @@ class RepositorySynchronizer $this->metrics = $metrics; $this->checkpoints = $checkpoints ?? new CheckpointManager('.checkpoints'); $this->definitionParser = $definitionParser ?? new DefinitionParser(); + $this->manifestParser = new MokoStandardsParser(); } /** @@ -166,7 +168,7 @@ class RepositorySynchronizer { $this->logger->logInfo("Starting synchronization for {$org}/{$repo}"); - // Resolve repo root (two levels up from this file: Enterprise/ → lib/ → api/ → root) + // Resolve repo root (three levels up from this file: Enterprise/ → lib/ → root) // API repo root (definitions, sync code) $repoRoot = dirname(dirname(__DIR__)); // MokoStandards repo root (templates, configs) @@ -195,7 +197,16 @@ class RepositorySynchronizer } } - $this->logger->logInfo("Loaded " . count($filesToSync) . " sync entries from definition for {$platform}"); + $defCount = count($filesToSync) - count($sharedFiles); + $sharedAdded = count($filesToSync) - $defCount; + $sharedTotal = count($sharedFiles); + $this->logger->logInfo("Loaded " . count($filesToSync) . " sync entries for {$platform} (def={$defCount}, shared={$sharedAdded}/{$sharedTotal} added, " . ($sharedTotal - $sharedAdded) . " deduped)"); + // Log shared workflow destinations for debugging + foreach ($sharedFiles as $sf) { + $dest = $sf['destination'] ?? '?'; + $added = !isset($seen[$dest]) ? 'ADDED' : 'DEDUPED'; + $this->logger->logInfo(" shared: {$dest} [{$added}]"); + } if (empty($filesToSync)) { $this->logger->logWarning("No syncable entries found in definition for platform '{$platform}', skipping {$repo}"); @@ -215,7 +226,8 @@ class RepositorySynchronizer } // Create PR with file updates driven by the definition - $result = $this->createSyncPR($org, $repo, $platform, $filesToSync, $standardsRoot, $force); + // Use API repo root ($repoRoot) — templates live here, not in $standardsRoot + $result = $this->createSyncPR($org, $repo, $platform, $filesToSync, $repoRoot, $force); $prNumber = $result['number'] ?? null; $summary = $result['summary'] ?? []; @@ -390,7 +402,65 @@ HCL; /** Repos that are the full Dolibarr platform, not individual modules. */ private const CRM_PLATFORM_REPOS = ['MokoDolibarr', 'MokoDoliMods']; + /** + * Detect platform from the .mokostandards manifest (authoritative), falling + * back to name/topic/description heuristics when the manifest is missing or + * unparseable. + */ private function detectPlatform(array $repoInfo): string + { + $org = $repoInfo['full_name'] ? explode('/', $repoInfo['full_name'])[0] : ''; + $name = $repoInfo['name'] ?? ''; + + // ── 1. Try reading the XML .mokostandards manifest ────────────��─ + $manifestPlatform = $this->readManifestPlatform($org, $name); + if ($manifestPlatform !== null) { + $this->logger->logInfo("Platform for {$name} from .mokostandards manifest: {$manifestPlatform}"); + return $manifestPlatform; + } + + // ── 2. Fallback: heuristic detection ──────────────────────────── + return $this->detectPlatformByHeuristics($repoInfo); + } + + /** + * Read the platform slug from the remote .mokostandards manifest. + * Checks .gitea/.mokostandards, .github/.mokostandards, and root .mokostandards. + * + * @return string|null Platform slug or null if not found/parseable + */ + private function readManifestPlatform(string $org, string $repo): ?string + { + $metaDir = $this->adapter->getMetadataDir(); + $paths = [ + "{$metaDir}/.mokostandards", + '.mokostandards', + ]; + if ($metaDir === '.gitea') { + $paths[] = '.github/.mokostandards'; + } + + foreach ($paths as $path) { + try { + $file = $this->adapter->getFileContents($org, $repo, $path); + $content = base64_decode($file['content'] ?? ''); + $platform = $this->manifestParser->extractPlatform($content); + if ($platform !== null && in_array($platform, MokoStandardsParser::VALID_PLATFORMS, true)) { + return $platform; + } + } catch (Exception $e) { + $this->adapter->getApiClient()->resetCircuitBreaker(); + } + } + + return null; + } + + /** + * Heuristic platform detection from repo name, topics, and description. + * Used as fallback when .mokostandards manifest is missing or unparseable. + */ + private function detectPlatformByHeuristics(array $repoInfo): string { $name = $repoInfo['name'] ?? ''; $nameLower = strtolower($name); @@ -438,7 +508,7 @@ HCL; if (str_contains($description, 'dolibarr') || str_contains($description, 'module')) { return 'crm-module'; } - + // Default return 'default-repository'; } @@ -493,8 +563,8 @@ HCL; // Ensure composer.json requires mokoconsulting-tech/enterprise (default branch only) $this->ensureComposerEnterprise($org, $repo, $defaultBranch, $summary); - // Migrate .mokostandards (default branch only) - $this->migrateMokoStandards($org, $repo, $defaultBranch, $summary); + // Migrate .mokostandards to XML manifest (default branch only) + $this->migrateMokoStandards($org, $repo, $defaultBranch, $platform, $repoInfo, $summary); if (count($summary['copied']) === 0) { $this->logger->logWarning("No files were created/updated for {$repo}"); @@ -509,7 +579,7 @@ HCL; try { $issueData = $this->adapter->createIssue($org, $repo, $issueTitle, $issueBody, [ 'labels' => ['mokostandards', 'type: chore', 'automation'], - 'assignees' => ['jmiller-moko'], + 'assignees' => ['jmiller'], ]); $issueNumber = $issueData['number'] ?? null; $this->logger->logInfo("Created tracking issue #{$issueNumber} — " . count($summary['copied']) . " files synced directly to {$defaultBranch}"); @@ -598,7 +668,12 @@ HCL; $isReadme = $basename === 'readme.md'; $isChangelog = in_array($basename, ['changelog.md', 'changelog'], true); $isProtected = $isReadme || $isChangelog; - $canOverwrite = !$isProtected && ($force || $entry['always_overwrite']) && !($entry['protected'] ?? false); + // Protected files are NEVER overwritten, even with --force + if ($entry['protected'] ?? false) { + $summary['skipped'][] = ['file' => $targetPath, 'reason' => 'Protected — never overwritten']; + continue; + } + $canOverwrite = !$isProtected && ($force || $entry['always_overwrite']); if ($isReadme) { $summary['skipped'][] = ['file' => $targetPath, 'reason' => 'README — never overwritten']; @@ -691,65 +766,229 @@ HCL; } /** - * Migrate .mokostandards from repo root to .github/.mokostandards. - * Deletes the root file after copying to .github/. + * Migrate .mokostandards to the platform metadata dir (.gitea/ or .github/) + * and convert legacy YAML-like format to the new XML manifest. + * + * Handles: + * 1. Location migration: root or .github/ → .gitea/.mokostandards + * 2. Format migration: legacy "platform: xxx" → XML manifest + * 3. Update existing XML: refresh timestamp */ - private function migrateMokoStandards(string $org, string $repo, string $branchName, array &$summary): void - { - $metaDir = $this->adapter->getMetadataDir(); + private function migrateMokoStandards( + string $org, + string $repo, + string $branchName, + string $platform, + array $repoInfo, + array &$summary + ): void { + $metaDir = $this->adapter->getMetadataDir(); $targetPath = "{$metaDir}/.mokostandards"; - // Check if .mokostandards exists in root - try { - $rootFile = $this->adapter->getFileContents($org, $repo, '.mokostandards', $branchName); - } catch (Exception $e) { - $this->adapter->getApiClient()->resetCircuitBreaker(); - return; // Doesn't exist in root — nothing to migrate + // ── Collect existing files from all legacy locations ───────── + $legacySources = ['.mokostandards']; + if ($metaDir === '.gitea') { + $legacySources[] = '.github/.mokostandards'; } - // Check if already exists in metadata dir - $existsInMetaDir = false; - try { - $this->adapter->getFileContents($org, $repo, $targetPath, $branchName); - $existsInMetaDir = true; - } catch (Exception $e) { - $this->adapter->getApiClient()->resetCircuitBreaker(); - } - - $content = base64_decode($rootFile['content'] ?? ''); - $rootSha = $rootFile['sha'] ?? ''; - - if (!$existsInMetaDir) { - // Copy to metadata dir + $legacyFiles = []; // path => ['content' => raw, 'sha' => sha] + foreach ($legacySources as $path) { try { - $this->adapter->createOrUpdateFile( - $org, $repo, $targetPath, $content, - "chore: migrate .mokostandards to {$metaDir}/", - null, $branchName - ); - $this->logger->logInfo("Migrated .mokostandards → {$targetPath}"); - $summary['copied'][] = ['file' => $targetPath, 'action' => 'migrated from root']; + $file = $this->adapter->getFileContents($org, $repo, $path, $branchName); + $legacyFiles[$path] = [ + 'content' => base64_decode($file['content'] ?? ''), + 'sha' => $file['sha'] ?? '', + ]; } catch (Exception $e) { $this->adapter->getApiClient()->resetCircuitBreaker(); + } + } + + // Check if target already exists in metadata dir + $existingTarget = null; + try { + $file = $this->adapter->getFileContents($org, $repo, $targetPath, $branchName); + $existingTarget = [ + 'content' => base64_decode($file['content'] ?? ''), + 'sha' => $file['sha'] ?? '', + ]; + } catch (Exception $e) { + $this->adapter->getApiClient()->resetCircuitBreaker(); + } + + // ── Determine the best existing content to work from ──────── + $currentContent = $existingTarget['content'] ?? null; + if ($currentContent === null) { + // Pick from legacy sources (first found) + foreach ($legacyFiles as $data) { + $currentContent = $data['content']; + break; + } + } + + // ── Generate the new XML manifest ─────────────────────────── + $xmlContent = $this->generateMokoStandardsXml( + $org, + $repo, + $platform, + $repoInfo, + $currentContent + ); + + // ── Write to target path ──────────────────────────────────── + $targetSha = $existingTarget['sha'] ?? null; + $isNew = $existingTarget === null; + $needsUpdate = $isNew || $existingTarget['content'] !== $xmlContent; + + if ($needsUpdate) { + $action = $isNew ? 'create' : 'update'; + $commitMsg = $isNew + ? "chore: add XML .mokostandards manifest to {$metaDir}/" + : "chore: update .mokostandards manifest (XML format)"; + + try { + $this->adapter->createOrUpdateFile( + $org, $repo, $targetPath, $xmlContent, + $commitMsg, $targetSha, $branchName + ); + $this->logger->logInfo(ucfirst($action) . "d XML .mokostandards → {$targetPath}"); + $summary['copied'][] = ['file' => $targetPath, 'action' => "{$action}d (XML manifest)"]; + } catch (Exception $e) { + $this->adapter->getApiClient()->resetCircuitBreaker(); + $this->logger->logWarning("Could not {$action} .mokostandards: " . $e->getMessage()); return; } } - // Delete from root - if (!empty($rootSha)) { + // ── Delete legacy source files ────────────────────────────── + foreach ($legacyFiles as $path => $data) { + if ($path === $targetPath || empty($data['sha'])) { + continue; + } try { $this->adapter->deleteFile( - $org, $repo, '.mokostandards', $rootSha, - "chore: remove .mokostandards from root (moved to {$metaDir}/)", + $org, $repo, $path, $data['sha'], + "chore: remove legacy {$path} (replaced by {$targetPath})", $branchName ); - $this->logger->logInfo("Deleted root .mokostandards"); + $this->logger->logInfo("Deleted legacy {$path}"); } catch (Exception $e) { $this->adapter->getApiClient()->resetCircuitBreaker(); } } } + /** + * Generate an XML .mokostandards manifest for a repository. + * + * If existing content is valid XML, preserves user-edited sections + * (build, deploy, scripts, overrides) and only refreshes governance metadata. + * + * @param string $org Organization name + * @param string $repo Repository name + * @param string $platform Detected platform slug + * @param array $repoInfo Gitea API repo object + * @param string|null $existingContent Current .mokostandards content (XML or legacy) + * @return string Well-formed XML content + */ + private function generateMokoStandardsXml( + string $org, + string $repo, + string $platform, + array $repoInfo, + ?string $existingContent + ): string { + $params = [ + 'name' => $repoInfo['name'] ?? $repo, + 'org' => $org, + 'platform' => $platform, + 'standards_version' => self::STANDARDS_VERSION, + 'description' => $repoInfo['description'] ?? '', + 'license' => 'GPL-3.0-or-later', + 'topics' => $repoInfo['topics'] ?? [], + 'language' => $repoInfo['language'] ?? MokoStandardsParser::platformLanguage($platform), + 'package_type' => MokoStandardsParser::platformPackageType($platform), + 'last_synced' => date('c'), + ]; + + // If existing content is already valid XML, try to preserve user sections + if ($existingContent !== null && str_contains($existingContent, 'manifestParser->parse($existingContent); + + // Preserve user-edited build, deploy, scripts, overrides by re-emitting + // the existing XML with only governance fields refreshed. + // For now, we use the simple generate() which creates identity + governance + build. + // User-managed sections (deploy, scripts, overrides) are preserved by doing + // a targeted replacement of governance fields in the existing XML. + return $this->refreshGovernanceInXml( + $existingContent, + $platform, + self::STANDARDS_VERSION, + date('c') + ); + } catch (\RuntimeException $e) { + // Existing XML is broken — regenerate from scratch + $this->logger->logInfo("Existing .mokostandards XML invalid, regenerating: " . $e->getMessage()); + } + } + + return $this->manifestParser->generate($params); + } + + /** + * Refresh only the fields in an existing XML .mokostandards, + * preserving all other sections (build, deploy, scripts, overrides). + */ + private function refreshGovernanceInXml( + string $xml, + string $platform, + string $standardsVersion, + string $lastSynced + ): string { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->preserveWhiteSpace = true; + $dom->formatOutput = true; + + if (!$dom->loadXML($xml)) { + // If parsing fails, return as-is + return $xml; + } + + $xpath = new \DOMXPath($dom); + $xpath->registerNamespace('m', MokoStandardsParser::NAMESPACE_URI); + + // Update + $nodes = $xpath->query('//m:governance/m:platform'); + if ($nodes->length > 0) { + $nodes->item(0)->textContent = $platform; + } + + // Update + $nodes = $xpath->query('//m:governance/m:standards-version'); + if ($nodes->length > 0) { + $nodes->item(0)->textContent = $standardsVersion; + } + + // Update or create + $nodes = $xpath->query('//m:governance/m:last-synced'); + if ($nodes->length > 0) { + $nodes->item(0)->textContent = $lastSynced; + } else { + $govNodes = $xpath->query('//m:governance'); + if ($govNodes->length > 0) { + $lastSyncedEl = $dom->createElementNS( + MokoStandardsParser::NAMESPACE_URI, + 'last-synced' + ); + $lastSyncedEl->textContent = $lastSynced; + $govNodes->item(0)->appendChild($lastSyncedEl); + } + } + + return $dom->saveXML(); + } + private function ensureComposerEnterprise(string $org, string $repo, string $branchName, array &$summary): void { try { @@ -764,6 +1003,11 @@ HCL; return; } + // Don't add self-referencing dependency — skip if this IS the enterprise package + if (($json['name'] ?? '') === 'mokoconsulting-tech/enterprise') { + return; + } + $expectedConstraint = 'dev-' . self::VERSION_BRANCH; // Check if enterprise package is already required with correct constraint @@ -798,50 +1042,51 @@ HCL; } } + /** + * Template repo mapping — canonical source for each platform's workflows. + * The sync engine clones these at runtime to get the latest workflow files. + */ + private const TEMPLATE_REPOS = [ + 'joomla' => 'MokoConsulting/MokoStandards-Template-Joomla', + 'dolibarr' => 'MokoConsulting/MokoStandards-Template-Dolibarr', + 'generic' => 'MokoConsulting/MokoStandards-Template-Generic', + 'client' => 'MokoConsulting/MokoStandards-Template-Client', + ]; + private function getSharedWorkflows(string $platform, string $repoRoot): array { - $root = rtrim($repoRoot, '/'); $wfDir = $this->adapter->getWorkflowDir(); - $shared = [ - ['templates/workflows/shared/enterprise-firewall-setup.yml.template', "{$wfDir}/enterprise-firewall-setup.yml"], - ['templates/workflows/shared/sync-version-on-merge.yml.template', "{$wfDir}/sync-version-on-merge.yml"], - ['templates/workflows/shared/repository-cleanup.yml.template', "{$wfDir}/repository-cleanup.yml"], - ['templates/workflows/shared/auto-dev-issue.yml.template', "{$wfDir}/auto-dev-issue.yml"], - ['templates/workflows/shared/branch-freeze.yml.template', "{$wfDir}/branch-freeze.yml"], - ['templates/workflows/shared/auto-assign.yml.template', "{$wfDir}/auto-assign.yml"], - ['templates/workflows/shared/changelog-validation.yml.template', "{$wfDir}/changelog-validation.yml"], - ['.github/workflows/standards-compliance.yml', "{$wfDir}/standards-compliance.yml"], - ]; + // Determine which template repo to source from + $templateType = match (true) { + in_array($platform, ['crm-module', 'crm-platform']) => 'dolibarr', + in_array($platform, ['waas-component', 'joomla-template']) => 'joomla', + str_starts_with($platform, 'client') => 'client', + default => 'generic', + }; - // CodeQL is GitHub-only; on Gitea, Trivy replaces it - if ($this->adapter->getPlatformName() === 'github') { - $shared[] = ['.github/workflows/codeql-analysis.yml', "{$wfDir}/codeql-analysis.yml"]; + // Clone template repo to tmp if not already cached + $templateRepo = self::TEMPLATE_REPOS[$templateType]; + $cacheDir = sys_get_temp_dir() . '/mokostandards-sync/' . basename($templateRepo); + + if (!is_dir($cacheDir)) { + $gitUrl = $this->adapter->getCloneUrl($templateRepo); + $this->logger->logInfo("Cloning template: {$templateRepo} → {$cacheDir}"); + $cloneResult = $this->adapter->cloneRepo($templateRepo, $cacheDir, ['depth' => 1]); + if (!$cloneResult) { + throw new \RuntimeException("Failed to clone template repo: {$templateRepo}"); + } } - // Platform-specific workflows - if ($platform === 'crm-module') { - $shared[] = ['templates/workflows/shared/deploy-dev.yml.template', "{$wfDir}/deploy-dev.yml"]; - $shared[] = ['templates/workflows/shared/deploy-demo.yml.template', "{$wfDir}/deploy-demo.yml"]; - $shared[] = ['templates/workflows/dolibarr/auto-release.yml.template', "{$wfDir}/auto-release.yml"]; - $shared[] = ['templates/workflows/dolibarr/ci-dolibarr.yml.template', "{$wfDir}/ci-dolibarr.yml"]; - $shared[] = ['templates/workflows/dolibarr/publish-to-mokodolimods.yml.template', "{$wfDir}/publish-to-mokodolimods.yml"]; - $shared[] = ['templates/workflows/dolibarr/repo_health.yml.template', "{$wfDir}/repo_health.yml"]; - } elseif ($platform === 'crm-platform') { - $shared[] = ['templates/workflows/shared/deploy-dev.yml.template', "{$wfDir}/deploy-dev.yml"]; - $shared[] = ['templates/workflows/shared/deploy-demo.yml.template', "{$wfDir}/deploy-demo.yml"]; - $shared[] = ['templates/workflows/dolibarr/auto-release.yml.template', "{$wfDir}/auto-release.yml"]; - $shared[] = ['templates/workflows/dolibarr/ci-dolibarr.yml.template', "{$wfDir}/ci-dolibarr.yml"]; - } elseif ($platform === 'waas-component' || $platform === 'joomla-template') { - $shared[] = ['templates/workflows/joomla/auto-release.yml.template', "{$wfDir}/auto-release.yml"]; - $shared[] = ['templates/workflows/joomla/update-server.yml.template', "{$wfDir}/update-server.yml"]; - $shared[] = ['templates/workflows/joomla/ci-joomla.yml.template', "{$wfDir}/ci-joomla.yml"]; - $shared[] = ['templates/workflows/joomla/repo_health.yml.template', "{$wfDir}/repo_health.yml"]; - $shared[] = ['templates/workflows/joomla/deploy-manual.yml.template', "{$wfDir}/deploy-manual.yml"]; - } else { - $shared[] = ['templates/workflows/shared/deploy-dev.yml.template', "{$wfDir}/deploy-dev.yml"]; - $shared[] = ['templates/workflows/shared/deploy-demo.yml.template', "{$wfDir}/deploy-demo.yml"]; - $shared[] = ['templates/workflows/shared/auto-release.yml.template', "{$wfDir}/auto-release.yml"]; + // Read all .yml files from the template's .gitea/workflows/ + $sourceDir = "{$cacheDir}/.gitea/workflows"; + $shared = []; + + if (is_dir($sourceDir)) { + foreach (glob("{$sourceDir}/*.yml") as $file) { + $basename = basename($file); + $shared[] = [$file, "{$wfDir}/{$basename}"]; + } } // CODEOWNERS — GitHub only; Gitea doesn't enforce it @@ -868,8 +1113,7 @@ HCL; // Always create a custom/ subdirectory under the workflow dir with a README // so repos have a safe place for custom workflows that sync won't touch. - $entries = [ - [ + $entries[] = [ 'inline_content' => "# Custom Workflows\n\nPlace repo-specific workflows here.\n\n" . "- **Never overwritten** by MokoStandards bulk sync\n" . "- **Never deleted** by the repository-cleanup workflow\n" @@ -877,7 +1121,6 @@ HCL; . "Synced workflows live in the parent `{$wfDir}/` directory.\n", 'destination' => "{$wfDir}/custom/README.md", 'always_overwrite' => false, - ], ]; foreach ($shared as [$source, $dest]) { @@ -903,6 +1146,77 @@ HCL; return $entries; } + /** + * Required .gitignore entries that MUST exist in every governed repo. + * The sync validates these exist (appending if missing) without + * overwriting custom entries. Repos can add their own patterns freely. + */ + private const REQUIRED_GITIGNORE_ENTRIES = [ + // Secrets & environment + '.env', + '.env.local', + '.env.*.local', + 'secrets/', + '*.secrets.*', + + // Sublime Text project files + '*.sublime-project', + '*.sublime-workspace', + '*.sublime-settings', + + // SFTP config (Sublime SFTP, VS Code SFTP, etc.) + 'sftp-config*.json', + 'sftp-config.json.template', + 'sftp-settings.json', + + // IDE / editor + '.idea/', + '.vscode/*', + '.claude/', + '*.code-workspace', + + // OS cruft + '.DS_Store', + 'Thumbs.db', + + // Task tracking + 'TODO.md', + + // Vendor / dependencies + '/vendor/', + 'node_modules/', + + // Logs + '*.log', + ]; + + /** + * Validate that required .gitignore entries exist in a repo. + * Returns array of missing entries, empty if all present. + * + * @param string $existingContent Current .gitignore content from repo + * @return array Missing required entries + */ + public function validateGitignoreEntries(string $existingContent): array + { + $existingLines = array_map('trim', explode("\n", $existingContent)); + $existingSet = []; + foreach ($existingLines as $line) { + if ($line !== '' && !str_starts_with($line, '#')) { + $existingSet[$line] = true; + } + } + + $missing = []; + foreach (self::REQUIRED_GITIGNORE_ENTRIES as $entry) { + if (!isset($existingSet[$entry])) { + $missing[] = $entry; + } + } + + return $missing; + } + private function mergeGitConfigFile(string $existing, string $template): string { $existingLines = array_map('rtrim', explode("\n", $existing)); diff --git a/lib/plugins/Joomla/UpdateXmlGenerator.php b/lib/plugins/Joomla/UpdateXmlGenerator.php index 7fb9e7b..98a8e31 100644 --- a/lib/plugins/Joomla/UpdateXmlGenerator.php +++ b/lib/plugins/Joomla/UpdateXmlGenerator.php @@ -11,7 +11,7 @@ * INGROUP: MokoStandards * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /lib/plugins/Joomla/UpdateXmlGenerator.php - * VERSION: 04.06.00 + * VERSION: 04.07.00 * BRIEF: Generates and updates Joomla extension updates.xml files */ @@ -124,6 +124,20 @@ class UpdateXmlGenerator return $dom->saveXML(); } + /** + * Map numeric client ID to Joomla client name + * + * @param string $clientId Numeric client ID + * @return string Client name for updates.xml + */ + private function resolveClientName(string $clientId): string + { + return match ($clientId) { + '1' => 'administrator', + default => 'site', + }; + } + /** * Add an update entry to the XML document * @@ -145,8 +159,22 @@ class UpdateXmlGenerator $this->addElement($dom, $update, 'description', $release['description'] ?? ''); $this->addElement($dom, $update, 'element', $this->element); $this->addElement($dom, $update, 'type', $this->extensionType); + + // Folder (for plugins) + if (!empty($release['folder'])) { + $this->addElement($dom, $update, 'folder', $release['folder']); + } + + // Client — always emit for correct extension matching + $this->addElement($dom, $update, 'client', $this->resolveClientName($this->clientId)); + $this->addElement($dom, $update, 'version', $release['version']); + // Creation date + if (!empty($release['creation_date'])) { + $this->addElement($dom, $update, 'creationDate', $release['creation_date']); + } + // Joomla target platform $infourl = $this->addElement($dom, $update, 'infourl', $release['infourl'] ?? ''); if (!empty($release['infourl'])) { @@ -161,6 +189,37 @@ class UpdateXmlGenerator $downloadUrl->setAttribute('type', 'full'); $downloadUrl->setAttribute('format', 'zip'); + // Checksums + if (!empty($release['sha256'])) { + $this->addElement($dom, $update, 'sha256', $release['sha256']); + } + + if (!empty($release['sha384'])) { + $this->addElement($dom, $update, 'sha384', $release['sha384']); + } + + if (!empty($release['sha512'])) { + $this->addElement($dom, $update, 'sha512', $release['sha512']); + } + + // Tags + if (!empty($release['tags'])) { + $tags = $dom->createElement('tags'); + $update->appendChild($tags); + foreach ($release['tags'] as $tag) { + $this->addElement($dom, $tags, 'tag', $tag); + } + } + + // Maintainer information + if (!empty($release['maintainer'])) { + $this->addElement($dom, $update, 'maintainer', $release['maintainer']); + } + + if (!empty($release['maintainer_url'])) { + $this->addElement($dom, $update, 'maintainerurl', $release['maintainer_url']); + } + // Target platform if (!empty($release['target_platform'])) { $targetPlatform = $dom->createElement('targetplatform'); @@ -174,42 +233,6 @@ class UpdateXmlGenerator $this->addElement($dom, $update, 'php_minimum', $release['php_minimum']); } - // Optional: Tags - if (!empty($release['tags'])) { - $tags = $dom->createElement('tags'); - $update->appendChild($tags); - foreach ($release['tags'] as $tag) { - $this->addElement($dom, $tags, 'tag', $tag); - } - } - - // Optional: Maintainer information - if (!empty($release['maintainer'])) { - $this->addElement($dom, $update, 'maintainer', $release['maintainer']); - } - - if (!empty($release['maintainer_url'])) { - $this->addElement($dom, $update, 'maintainerurl', $release['maintainer_url']); - } - - // Optional: Client (site or administrator) - if ($this->clientId !== '0') { - $this->addElement($dom, $update, 'client', $this->clientId); - } - - // Optional: Checksums - if (!empty($release['sha256'])) { - $this->addElement($dom, $update, 'sha256', $release['sha256']); - } - - if (!empty($release['sha384'])) { - $this->addElement($dom, $update, 'sha384', $release['sha384']); - } - - if (!empty($release['sha512'])) { - $this->addElement($dom, $update, 'sha512', $release['sha512']); - } - // Add to updates element if ($prepend && $updates->firstChild) { $updates->insertBefore($update, $updates->firstChild); @@ -312,6 +335,11 @@ class UpdateXmlGenerator } } + // Warn if is missing + if ($update->getElementsByTagName('client')->length === 0) { + $errors[] = "Missing tag — Joomla may not match this update to the installed extension"; + } + // Check for download URL $downloads = $update->getElementsByTagName('downloads'); if ($downloads->length > 0) { diff --git a/maintenance/pin_action_shas.php b/maintenance/pin_action_shas.php index bc3ef5d..e525d93 100644 --- a/maintenance/pin_action_shas.php +++ b/maintenance/pin_action_shas.php @@ -34,7 +34,7 @@ use MokoEnterprise\PlatformAdapterFactory; * pinned commit SHA. Already-pinned references (40-char hex SHA) are left untouched. * * Usage: - * php api/maintenance/pin_action_shas.php [--dry-run] [--verbose] [--help] + * php maintenance/pin_action_shas.php [--dry-run] [--verbose] [--help] * * Environment: * GH_TOKEN Personal access token for GitHub API calls. @@ -90,7 +90,7 @@ class ActionShaPinner private function showHelp(): void { echo <<<'HELP' -Usage: php api/maintenance/pin_action_shas.php [OPTIONS] +Usage: php maintenance/pin_action_shas.php [OPTIONS] Pins GitHub Actions to immutable commit SHAs in all .github/workflows/*.yml files. Already-pinned references (40-character commit SHA) are skipped. @@ -106,10 +106,10 @@ Environment: Examples: # Preview all changes - GH_TOKEN=ghp_xxx php api/maintenance/pin_action_shas.php --dry-run --verbose + GH_TOKEN=ghp_xxx php maintenance/pin_action_shas.php --dry-run --verbose # Apply changes - GH_TOKEN=ghp_xxx php api/maintenance/pin_action_shas.php + GH_TOKEN=ghp_xxx php maintenance/pin_action_shas.php HELP; } diff --git a/maintenance/repo_inventory.php b/maintenance/repo_inventory.php index 86ffe48..2c3d783 100644 --- a/maintenance/repo_inventory.php +++ b/maintenance/repo_inventory.php @@ -15,9 +15,9 @@ * BRIEF: Generate a live inventory dashboard of all governed repos as a GitHub issue * * USAGE - * php api/maintenance/repo_inventory.php # Generate and post dashboard - * php api/maintenance/repo_inventory.php --dry-run # Preview only - * php api/maintenance/repo_inventory.php --json # JSON output to stdout + * php maintenance/repo_inventory.php # Generate and post dashboard + * php maintenance/repo_inventory.php --dry-run # Preview only + * php maintenance/repo_inventory.php --json # JSON output to stdout */ declare(strict_types=1); @@ -212,14 +212,14 @@ if (!$dryRun) { if (!empty($existing[0]['number'])) { $num = $existing[0]['number']; ghApi('PATCH', "repos/{$org}/MokoStandards/issues/{$num}", [ - 'title' => $title, 'body' => $body, 'state' => 'open', 'assignees' => ['jmiller-moko'], + 'title' => $title, 'body' => $body, 'state' => 'open', 'assignees' => ['jmiller'], ], $token); echo "Updated inventory issue #{$num}\n"; } else { [$_, $issue] = ghApi('POST', "repos/{$org}/MokoStandards/issues", [ 'title' => $title, 'body' => $body, 'labels' => ['inventory', 'type: chore', 'automation'], - 'assignees' => ['jmiller-moko'], + 'assignees' => ['jmiller'], ], $token); echo "Created inventory issue #{$issue['number']}\n"; } diff --git a/maintenance/rotate_secrets.php b/maintenance/rotate_secrets.php index 33af096..8e3e709 100644 --- a/maintenance/rotate_secrets.php +++ b/maintenance/rotate_secrets.php @@ -15,10 +15,10 @@ * BRIEF: Audit FTP secrets and variables across all governed repos — report missing or stale * * USAGE - * php api/maintenance/rotate_secrets.php --all # Audit all repos - * php api/maintenance/rotate_secrets.php --repo MokoCRM # Single repo - * php api/maintenance/rotate_secrets.php --all --json # JSON output - * php api/maintenance/rotate_secrets.php --all --create-issue # Post results as issue + * php maintenance/rotate_secrets.php --all # Audit all repos + * php maintenance/rotate_secrets.php --repo MokoCRM # Single repo + * php maintenance/rotate_secrets.php --all --json # JSON output + * php maintenance/rotate_secrets.php --all --create-issue # Post results as issue */ declare(strict_types=1); @@ -204,12 +204,12 @@ if ($createIssue && $issueCount > 0) { [$_, $existing] = ghApi('GET', "repos/{$org}/MokoStandards/issues?labels=secret-audit&state=all&per_page=1&sort=created&direction=desc", null, $token); if (!empty($existing[0]['number'])) { $num = $existing[0]['number']; - ghApi('PATCH', "repos/{$org}/MokoStandards/issues/{$num}", ['title' => "audit: FTP secrets — {$issueCount} issues", 'body' => $body, 'state' => 'open', 'assignees' => ['jmiller-moko']], $token); + ghApi('PATCH', "repos/{$org}/MokoStandards/issues/{$num}", ['title' => "audit: FTP secrets — {$issueCount} issues", 'body' => $body, 'state' => 'open', 'assignees' => ['jmiller']], $token); if (!$jsonOut) { echo "Updated audit issue #{$num}\n"; } } else { [$_, $issue] = ghApi('POST', "repos/{$org}/MokoStandards/issues", [ 'title' => "audit: FTP secrets — {$issueCount} issues", 'body' => $body, - 'labels' => ['secret-audit', 'type: chore', 'automation'], 'assignees' => ['jmiller-moko'], + 'labels' => ['secret-audit', 'type: chore', 'automation'], 'assignees' => ['jmiller'], ], $token); if (!$jsonOut) { echo "Created audit issue #{$issue['number']}\n"; } } diff --git a/maintenance/update_sha_hashes.php b/maintenance/update_sha_hashes.php index e14c34b..9b45a73 100755 --- a/maintenance/update_sha_hashes.php +++ b/maintenance/update_sha_hashes.php @@ -25,7 +25,7 @@ declare(strict_types=1); */ class ScriptRegistryUpdater { - private const REGISTRY_PATH = 'api/.script-registry.json'; + private const REGISTRY_PATH = '.script-registry.json'; private bool $dryRun = false; private bool $verbose = false; diff --git a/maintenance/update_version_from_readme.php b/maintenance/update_version_from_readme.php index 68d0bc9..93c0499 100644 --- a/maintenance/update_version_from_readme.php +++ b/maintenance/update_version_from_readme.php @@ -435,15 +435,15 @@ class UpdateVersionFromReadme extends CliFramework "", "1. Run the sync script locally:", " ```bash", - " php api/maintenance/update_version_from_readme.php --path . --dry-run", - " php api/maintenance/update_version_from_readme.php --path .", + " php maintenance/update_version_from_readme.php --path . --dry-run", + " php maintenance/update_version_from_readme.php --path .", " ```", "2. Inspect any files still flagged — they may use a non-standard VERSION format.", "3. Update them manually to match `VERSION: {$version}`.", "4. Commit and push — this issue will be closed automatically on the next successful sync.", "", "---", - "*Automatically created by [update_version_from_readme.php](api/maintenance/update_version_from_readme.php)*", + "*Automatically created by [update_version_from_readme.php](maintenance/update_version_from_readme.php)*", ]); try { @@ -458,7 +458,7 @@ class UpdateVersionFromReadme extends CliFramework if (!empty($existing[0]['number'])) { $num = (int) $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']]; + $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; if (($existing[0]['state'] ?? 'open') === 'closed') { $patch['state'] = 'open'; } @@ -472,7 +472,7 @@ class UpdateVersionFromReadme extends CliFramework 'title' => $title, 'body' => $body, 'labels' => $labels, - 'assignees' => ['jmiller-moko'], + 'assignees' => ['jmiller'], ]); $this->log('✅ Created issue #' . ($issue['number'] ?? '?') . " in {$repo}"); } diff --git a/templates/configs/.gitignore.joomla b/templates/configs/.gitignore.joomla index 3e1a463..86166bd 100644 --- a/templates/configs/.gitignore.joomla +++ b/templates/configs/.gitignore.joomla @@ -46,6 +46,7 @@ Icon? .idea/ .settings/ .claude/ +.claude-worktree*/ .vscode/* !.vscode/tasks.json !.vscode/settings.json.example diff --git a/templates/configs/gitignore b/templates/configs/gitignore index 4429d4e..61f6892 100644 --- a/templates/configs/gitignore +++ b/templates/configs/gitignore @@ -46,6 +46,7 @@ Icon? .idea/ .settings/ .claude/ +.claude-worktree*/ .vscode/* !.vscode/tasks.json !.vscode/settings.json.example diff --git a/templates/configs/gitignore.dolibarr b/templates/configs/gitignore.dolibarr index 6c26644..597bf22 100644 --- a/templates/configs/gitignore.dolibarr +++ b/templates/configs/gitignore.dolibarr @@ -46,6 +46,7 @@ Icon? .idea/ .settings/ .claude/ +.claude-worktree*/ .vscode/* !.vscode/tasks.json !.vscode/settings.json.example diff --git a/templates/configs/mokostandards.xml.template b/templates/configs/mokostandards.xml.template new file mode 100644 index 0000000..b95e787 --- /dev/null +++ b/templates/configs/mokostandards.xml.template @@ -0,0 +1,39 @@ + + + + + + {{REPO_NAME}} + {{org}} + {{REPO_DESCRIPTION}} + GNU General Public License v3 + + + + {{platform}} + {{standards_version}} + https://git.mokoconsulting.tech/MokoConsulting/MokoStandards + + + + {{PRIMARY_LANGUAGE}} + + + diff --git a/templates/gitea/CLAUDE.joomla.md.template b/templates/gitea/CLAUDE.joomla.md.template index 72e8cae..2076912 100644 --- a/templates/gitea/CLAUDE.joomla.md.template +++ b/templates/gitea/CLAUDE.joomla.md.template @@ -294,3 +294,8 @@ Before opening a PR, verify: | [merge-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | | [changelog-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | | [joomla-development-guide.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/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/gitea/CODEOWNERS b/templates/gitea/CODEOWNERS index e7e6e80..bb35912 100644 --- a/templates/gitea/CODEOWNERS +++ b/templates/gitea/CODEOWNERS @@ -1,7 +1,7 @@ # Copyright (C) 2026 Moko Consulting # SPDX-License-Identifier: GPL-3.0-or-later # -# CODEOWNERS — require approval from jmiller-moko for protected paths +# CODEOWNERS — require approval from jmiller for protected paths # Synced from MokoStandards. Do not edit manually. # # Changes to these paths require review from the listed owners before merge. @@ -9,47 +9,47 @@ # unauthorized modifications to workflows, configs, and governance files. # ── Synced workflows (managed by MokoStandards — do not edit manually) ──── -/.github/workflows/deploy-dev.yml @jmiller-moko -/.github/workflows/deploy-demo.yml @jmiller-moko -/.github/workflows/deploy-manual.yml @jmiller-moko -/.github/workflows/auto-release.yml @jmiller-moko -/.github/workflows/auto-dev-issue.yml @jmiller-moko -/.github/workflows/auto-assign.yml @jmiller-moko -/.github/workflows/sync-version-on-merge.yml @jmiller-moko -/.github/workflows/enterprise-firewall-setup.yml @jmiller-moko -/.github/workflows/repository-cleanup.yml @jmiller-moko -/.github/workflows/standards-compliance.yml @jmiller-moko -/.github/workflows/codeql-analysis.yml @jmiller-moko -/.github/workflows/repo_health.yml @jmiller-moko -/.github/workflows/ci-joomla.yml @jmiller-moko -/.github/workflows/update-server.yml @jmiller-moko -/.github/workflows/deploy-manual.yml @jmiller-moko -/.github/workflows/ci-dolibarr.yml @jmiller-moko -/.github/workflows/publish-to-mokodolimods.yml @jmiller-moko -/.github/workflows/changelog-validation.yml @jmiller-moko -/.github/workflows/branch-freeze.yml @jmiller-moko +/.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-moko -/.github/CODEOWNERS @jmiller-moko -/.github/copilot.yml @jmiller-moko -/.github/copilot-instructions.md @jmiller-moko -/.github/CLAUDE.md @jmiller-moko -/.github/.mokostandards @jmiller-moko +/.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-moko -/phpstan.neon @jmiller-moko -/Makefile @jmiller-moko -/.ftpignore @jmiller-moko -/.gitignore @jmiller-moko -/.gitattributes @jmiller-moko -/.editorconfig @jmiller-moko +/composer.json @jmiller +/phpstan.neon @jmiller +/Makefile @jmiller +/.ftpignore @jmiller +/.gitignore @jmiller +/.gitattributes @jmiller +/.editorconfig @jmiller # ── Governance documents ───────────────────────────────────────────────── -/LICENSE @jmiller-moko -/CONTRIBUTING.md @jmiller-moko -/SECURITY.md @jmiller-moko -/GOVERNANCE.md @jmiller-moko -/CODE_OF_CONDUCT.md @jmiller-moko +/LICENSE @jmiller +/CONTRIBUTING.md @jmiller +/SECURITY.md @jmiller +/GOVERNANCE.md @jmiller +/CODE_OF_CONDUCT.md @jmiller diff --git a/templates/gitea/ISSUE_TEMPLATE/dolibarr_module_id_request.md b/templates/gitea/ISSUE_TEMPLATE/dolibarr_module_id_request.md index 45a3e47..1d625d2 100644 --- a/templates/gitea/ISSUE_TEMPLATE/dolibarr_module_id_request.md +++ b/templates/gitea/ISSUE_TEMPLATE/dolibarr_module_id_request.md @@ -3,7 +3,7 @@ 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-moko'] +assignees: ['jmiller'] --- diff --git a/templates/gitea/ISSUE_TEMPLATE/firewall-request.md b/templates/gitea/ISSUE_TEMPLATE/firewall-request.md index 38be866..0691b93 100644 --- a/templates/gitea/ISSUE_TEMPLATE/firewall-request.md +++ b/templates/gitea/ISSUE_TEMPLATE/firewall-request.md @@ -3,7 +3,7 @@ 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-moko'] +assignees: ['jmiller'] --- diff --git a/templates/gitea/ISSUE_TEMPLATE/question.md b/templates/gitea/ISSUE_TEMPLATE/question.md index 74df7a0..3175013 100644 --- a/templates/gitea/ISSUE_TEMPLATE/question.md +++ b/templates/gitea/ISSUE_TEMPLATE/question.md @@ -3,7 +3,7 @@ name: Question about: Ask a question about usage, features, or best practices title: '[QUESTION] ' labels: ['question'] -assignees: ['jmiller-moko'] +assignees: ['jmiller'] --- diff --git a/templates/gitea/ISSUE_TEMPLATE/request-license.md b/templates/gitea/ISSUE_TEMPLATE/request-license.md index a9c87a7..7327adf 100644 --- a/templates/gitea/ISSUE_TEMPLATE/request-license.md +++ b/templates/gitea/ISSUE_TEMPLATE/request-license.md @@ -3,7 +3,7 @@ name: License Request about: Request an organization license for Sublime Text title: '[LICENSE REQUEST] Sublime Text - [Your Name]' labels: ['license-request', 'admin'] -assignees: ['jmiller-moko'] +assignees: ['jmiller'] --- diff --git a/templates/gitea/ISSUE_TEMPLATE/version.md b/templates/gitea/ISSUE_TEMPLATE/version.md index 22feae8..6328421 100644 --- a/templates/gitea/ISSUE_TEMPLATE/version.md +++ b/templates/gitea/ISSUE_TEMPLATE/version.md @@ -3,7 +3,7 @@ name: Version Bump about: Request or track a version change title: '[VERSION] ' labels: 'version, type: version' -assignees: 'jmiller-moko' +assignees: 'jmiller' --- ## Version Change diff --git a/templates/gitea/copilot-instructions.joomla.md.template b/templates/gitea/copilot-instructions.joomla.md.template index 9f253c3..ffacfb2 100644 --- a/templates/gitea/copilot-instructions.joomla.md.template +++ b/templates/gitea/copilot-instructions.joomla.md.template @@ -305,3 +305,6 @@ Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `d - 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/gitea/dependabot.yml.template b/templates/gitea/dependabot.yml.template index ab5761f..950a78e 100644 --- a/templates/gitea/dependabot.yml.template +++ b/templates/gitea/dependabot.yml.template @@ -39,7 +39,7 @@ updates: reviewers: - "mokoconsulting-tech/maintainers" assignees: - - "jmiller-moko" + - "jmiller" groups: github-actions: patterns: @@ -66,7 +66,7 @@ updates: # reviewers: # - "mokoconsulting-tech/maintainers" # assignees: - # - "jmiller-moko" + # - "jmiller" # groups: # github-actions-templates: # patterns: @@ -92,7 +92,7 @@ updates: reviewers: - "mokoconsulting-tech/maintainers" assignees: - - "jmiller-moko" + - "jmiller" groups: composer-dependencies: patterns: @@ -118,7 +118,7 @@ updates: # reviewers: # - "mokoconsulting-tech/maintainers" # assignees: - # - "jmiller-moko" + # - "jmiller" # groups: # npm-dependencies: # patterns: @@ -144,7 +144,7 @@ updates: # reviewers: # - "mokoconsulting-tech/maintainers" # assignees: - # - "jmiller-moko" + # - "jmiller" # groups: # python-dependencies: # patterns: diff --git a/templates/github/CLAUDE.dolibarr.md.template b/templates/github/CLAUDE.dolibarr.md.template deleted file mode 100644 index 91f46a3..0000000 --- a/templates/github/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 MokoStandards 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** `.github/copilot-instructions.md` and `.github/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-tech/MokoStandards/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-tech/MokoStandards/blob/main/docs/development/crm/module-registry.md))* -Repository URL: {{REPO_URL}} - -This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards) — 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/ -├── .github/ -│ ├── workflows/ # CI/CD workflows (synced from MokoStandards) -│ ├── 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-tech/MokoStandards/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 (MokoStandards) - -| Document | Purpose | -|----------|---------| -| [file-header-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | -| [coding-style-guide.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | -| [branching-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | -| [merge-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | -| [changelog-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | -| [module-registry.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/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-tech/MokoStandards/blob/main/docs/policy/crm/development-standards.md) | MokoCRM Dolibarr module development standards | -| [dolibarr-development-guide.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/crm/dolibarr-development-guide.md) | MokoCRM full development guide | diff --git a/templates/github/CLAUDE.joomla.md.template b/templates/github/CLAUDE.joomla.md.template deleted file mode 100644 index d000ad2..0000000 --- a/templates/github/CLAUDE.joomla.md.template +++ /dev/null @@ -1,296 +0,0 @@ - - -> [!IMPORTANT] -> **🔧 AI Self-Update Required on First Use** -> -> This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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 [MokoStandards](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards) — 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 -├── .github/ -│ ├── workflows/ # CI/CD workflows (synced from MokoStandards) -│ ├── 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 (MokoStandards) - -| Document | Purpose | -|----------|---------| -| [file-header-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | -| [coding-style-guide.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | -| [branching-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | -| [merge-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | -| [changelog-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | -| [joomla-development-guide.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | diff --git a/templates/github/CLAUDE.md.template b/templates/github/CLAUDE.md.template deleted file mode 100644 index e7771b7..0000000 --- a/templates/github/CLAUDE.md.template +++ /dev/null @@ -1,366 +0,0 @@ - - -> [!IMPORTANT] -> **🔧 AI Self-Update Required on First Use** -> -> This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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 | -> | `{{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 | -> -> --- - -# What This Repo Is - -**{{REPO_NAME}}** is a Moko Consulting **{{PLATFORM_TYPE}}** repository. - -{{REPO_DESCRIPTION}} - -Repository URL: {{REPO_URL}} - -This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards) — 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/ # Primary source code -├── docs/ # Documentation -├── tests/ # Test suite -├── .github/ -│ ├── workflows/ # CI/CD workflows (synced from MokoStandards) -│ ├── ISSUE_TEMPLATE/ # Issue templates (synced from MokoStandards) -│ ├── copilot-instructions.md # GitHub Copilot custom instructions -│ ├── CLAUDE.md # This file — Claude AI assistant context -│ └── override.tf # Repository-specific health-check overrides -├── README.md # Project overview — version source of truth -├── CHANGELOG.md # Version history -├── CONTRIBUTING.md # Contribution guidelines -└── LICENSE # GPL-3.0-or-later -``` - ---- - -# Primary Language - -**{{PRIMARY_LANGUAGE}}** is the primary language for this repository. - -YAML uses 2-space indentation (spaces, not tabs). All other text files use tabs per `.editorconfig`. - ---- - -# Composer Package (PHP repositories) - -This repository requires the MokoStandards enterprise library. The package is installed from the private GitHub VCS source. - -`composer.json` must contain: - -```json -{ - "repositories": [ - { - "type": "vcs", - "url": "https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards" - } - ], - "require": { - "mokoconsulting/mokostandards": "^4.0" - } -} -``` - -Install or update with: - -```bash -composer install # first time -composer update mokoconsulting/mokostandards # upgrade -``` - ---- - -# PHP Script Pattern - -All PHP scripts must extend `MokoStandards\Enterprise\CliFramework` — **never** use a standalone class or the legacy `CliBase`. - -```php -#!/usr/bin/env php - - * - * This file is part of a Moko Consulting project. - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: {{REPO_NAME}}.Scripts - * INGROUP: {{REPO_NAME}} - * REPO: {{REPO_URL}} - * PATH: /api/my_script.php - * VERSION: XX.YY.ZZ - * BRIEF: One-line description of what this script does - */ - -declare(strict_types=1); - -require_once __DIR__ . '/vendor/autoload.php'; - -use MokoStandards\Enterprise\CliFramework; - -class MyScript extends CliFramework -{ - protected function configure(): void - { - $this->setDescription('One-line description of what this script does'); - $this->addArgument('--path', 'Repository root path', '.'); - $this->addArgument('--dry-run', 'Preview changes without writing', false); - } - - protected function run(): int - { - $path = $this->getArgument('--path'); - $dryRun = (bool) $this->getArgument('--dry-run'); - - // implementation … - $this->log('INFO', "Processing: {$path}"); - - return 0; - } -} - -$script = new MyScript('my_script', 'One-line description of what this script does'); -exit($script->execute()); -``` - -**CliFramework interface summary:** - -| Member | Purpose | -|--------|---------| -| `configure(): void` | Abstract — register arguments with `addArgument()` | -| `run(): int` | Abstract — main script logic; return the exit code | -| `initialize(): void` | Optional hook — runs after arg-parse, before `run()` | -| `execute(array $argv = []): int` | **Public entry point** — call this at the bottom; it calls `configure()`, parses argv, then calls `run()` | -| `addArgument(string $name, string $desc, mixed $default)` | Register a CLI argument | -| `getArgument(string $name): mixed` | Read a parsed or default argument value | -| `log(string $level, string $message)` | Structured log — levels: INFO SUCCESS WARNING ERROR DEBUG | -| `error(string $message, int $code = 1): never` | Log error and exit | -| `$this->dryRun` | `true` when `--dry-run` is passed | -| `$this->verbose` | `true` when `--verbose` / `-v` is passed | - -**Forbidden patterns in PHP:** - -```php -// ❌ Wrong — legacy base class, not namespaced -class MyScript extends CliBase { … } - -// ❌ Wrong — standalone class with no framework -class MyScript { public function run() { … } } - -// ❌ Wrong — method names and entry-point transposed -protected function execute(): int { … } // should be run() -exit($script->run()); // should be execute() - -// ✅ Correct -class MyScript extends CliFramework { - protected function configure(): void { … } - protected function run(): int { … } -} -$script = new MyScript('name', 'description'); -exit($script->execute()); -``` - ---- - -# 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 badges and `FILE INFORMATION` headers automatically on merge to `main`. -- The `VERSION: XX.YY.ZZ` field in the `README.md` `FILE INFORMATION` block governs all other version references. -- Update `README.md` only — the `sync-version-on-merge` workflow propagates it to all badges and `FILE INFORMATION` headers automatically on merge to `main`. -- 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. - ---- - -# 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. - -## Minimal header - -**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/MyClass.php - * VERSION: XX.YY.ZZ - * BRIEF: One-line description of file purpose - */ - -declare(strict_types=1); -``` - -**Markdown:** -```markdown - -``` - -**YAML / Shell:** Use `#` comments with the same fields. JSON files are exempt. - ---- - -# Coding Standards - -## Naming Conventions - -| Context | Convention | Example | -|---------|-----------|---------| -| PHP class | `PascalCase` | `MyService` | -| PHP method / function | `camelCase` | `getUserData()` | -| PHP variable | `$snake_case` | `$user_id` | -| PHP constant | `UPPER_SNAKE_CASE` | `MAX_RETRIES` | -| PHP class file | `PascalCase.php` | `UserService.php` | -| PHP script file | `snake_case.php` | `check_health.php` | -| YAML workflow | `kebab-case.yml` | `code-quality.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` - -## Branch Naming - -Format: `/[/description]` - -Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/` - ---- - -# GitHub Actions — Token Usage - -Every workflow in this repository must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token). - -```yaml -# ✅ Correct — always use GH_TOKEN -- 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 - -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. - ---- - -# 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 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 extend `CliBase` in PHP scripts** — extend `MokoStandards\Enterprise\CliFramework` instead. -- **Never use `exit($script->run())`** — the correct entry point is `exit($script->execute())`. - ---- - -# Key Policy Documents (MokoStandards) - -| Document | Purpose | -|----------|---------| -| [file-header-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | -| [coding-style-guide.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | -| [branching-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | -| [merge-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions | -| [changelog-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | -| [scripting-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/scripting-standards.md) | PHP script requirements and CliFramework usage | -| [package-installation.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/package-installation.md) | Installing `mokoconsulting/mokostandards` via Composer | diff --git a/templates/github/CODEOWNERS b/templates/github/CODEOWNERS deleted file mode 100644 index e7e6e80..0000000 --- a/templates/github/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-moko for protected paths -# Synced from MokoStandards. 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 MokoStandards — do not edit manually) ──── -/.github/workflows/deploy-dev.yml @jmiller-moko -/.github/workflows/deploy-demo.yml @jmiller-moko -/.github/workflows/deploy-manual.yml @jmiller-moko -/.github/workflows/auto-release.yml @jmiller-moko -/.github/workflows/auto-dev-issue.yml @jmiller-moko -/.github/workflows/auto-assign.yml @jmiller-moko -/.github/workflows/sync-version-on-merge.yml @jmiller-moko -/.github/workflows/enterprise-firewall-setup.yml @jmiller-moko -/.github/workflows/repository-cleanup.yml @jmiller-moko -/.github/workflows/standards-compliance.yml @jmiller-moko -/.github/workflows/codeql-analysis.yml @jmiller-moko -/.github/workflows/repo_health.yml @jmiller-moko -/.github/workflows/ci-joomla.yml @jmiller-moko -/.github/workflows/update-server.yml @jmiller-moko -/.github/workflows/deploy-manual.yml @jmiller-moko -/.github/workflows/ci-dolibarr.yml @jmiller-moko -/.github/workflows/publish-to-mokodolimods.yml @jmiller-moko -/.github/workflows/changelog-validation.yml @jmiller-moko -/.github/workflows/branch-freeze.yml @jmiller-moko -# Custom workflows in .github/workflows/ not listed above are repo-owned. - -# ── GitHub configuration ───────────────────────────────────────────────── -/.github/ISSUE_TEMPLATE/ @jmiller-moko -/.github/CODEOWNERS @jmiller-moko -/.github/copilot.yml @jmiller-moko -/.github/copilot-instructions.md @jmiller-moko -/.github/CLAUDE.md @jmiller-moko -/.github/.mokostandards @jmiller-moko - -# ── Build and config files ─────────────────────────────────────────────── -/composer.json @jmiller-moko -/phpstan.neon @jmiller-moko -/Makefile @jmiller-moko -/.ftpignore @jmiller-moko -/.gitignore @jmiller-moko -/.gitattributes @jmiller-moko -/.editorconfig @jmiller-moko - -# ── Governance documents ───────────────────────────────────────────────── -/LICENSE @jmiller-moko -/CONTRIBUTING.md @jmiller-moko -/SECURITY.md @jmiller-moko -/GOVERNANCE.md @jmiller-moko -/CODE_OF_CONDUCT.md @jmiller-moko diff --git a/templates/github/CODEOWNERS.template b/templates/github/CODEOWNERS.template deleted file mode 100644 index 9344db3..0000000 --- a/templates/github/CODEOWNERS.template +++ /dev/null @@ -1,51 +0,0 @@ -# CODEOWNERS Template -# -# This file defines code ownership for automatic review assignment. -# Copy to .github/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 -/.github/ @mokoconsulting-tech/devops-team -/.github/workflows/ @mokoconsulting-tech/devops-team - -# Security-related files -/SECURITY.md @mokoconsulting-tech/security-team -/docs/policy/security-*.md @mokoconsulting-tech/security-team -/.github/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/github/ISSUE_TEMPLATE/adr.md b/templates/github/ISSUE_TEMPLATE/adr.md deleted file mode 100644 index eb40760..0000000 --- a/templates/github/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/github/ISSUE_TEMPLATE/bug_report.md b/templates/github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 38a16a7..0000000 --- a/templates/github/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/github/ISSUE_TEMPLATE/config.yml b/templates/github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index db5c43c..0000000 --- a/templates/github/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: 📚 MokoStandards Documentation - url: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards - 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/github/ISSUE_TEMPLATE/documentation.md b/templates/github/ISSUE_TEMPLATE/documentation.md deleted file mode 100644 index ed4dabc..0000000 --- a/templates/github/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 MokoStandards 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/github/ISSUE_TEMPLATE/dolibarr_issue.md b/templates/github/ISSUE_TEMPLATE/dolibarr_issue.md deleted file mode 100644 index 8366ff2..0000000 --- a/templates/github/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/github/ISSUE_TEMPLATE/dolibarr_module_id_request.md b/templates/github/ISSUE_TEMPLATE/dolibarr_module_id_request.md deleted file mode 100644 index 45a3e47..0000000 --- a/templates/github/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-moko'] ---- - - -## 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/MokoStandards/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 MokoStandards -- [ ] 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/MokoStandards/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/github/ISSUE_TEMPLATE/enterprise_support.md b/templates/github/ISSUE_TEMPLATE/enterprise_support.md deleted file mode 100644 index 4c3f0b4..0000000 --- a/templates/github/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/github/ISSUE_TEMPLATE/feature_request.md b/templates/github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 7b76dc9..0000000 --- a/templates/github/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 [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)? -- [ ] 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/github/ISSUE_TEMPLATE/firewall-request.md b/templates/github/ISSUE_TEMPLATE/firewall-request.md deleted file mode 100644 index 38be866..0000000 --- a/templates/github/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-moko'] ---- - - -## 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/github/ISSUE_TEMPLATE/joomla_issue.md b/templates/github/ISSUE_TEMPLATE/joomla_issue.md deleted file mode 100644 index d808f79..0000000 --- a/templates/github/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/github/ISSUE_TEMPLATE/question.md b/templates/github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 74df7a0..0000000 --- a/templates/github/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-moko'] ---- - - -## 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/github/ISSUE_TEMPLATE/request-license.md b/templates/github/ISSUE_TEMPLATE/request-license.md deleted file mode 100644 index a9c87a7..0000000 --- a/templates/github/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-moko'] ---- - - -## 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/github/ISSUE_TEMPLATE/rfc.md b/templates/github/ISSUE_TEMPLATE/rfc.md deleted file mode 100644 index 6f09af7..0000000 --- a/templates/github/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/github/ISSUE_TEMPLATE/security.md b/templates/github/ISSUE_TEMPLATE/security.md deleted file mode 100644 index f57b284..0000000 --- a/templates/github/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 [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)? -- [ ] 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/github/ISSUE_TEMPLATE/version.md b/templates/github/ISSUE_TEMPLATE/version.md deleted file mode 100644 index 22feae8..0000000 --- a/templates/github/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-moko' ---- - -## 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/github/PULL_REQUEST_TEMPLATE.md b/templates/github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 55e1428..0000000 --- a/templates/github/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/github/PULL_REQUEST_TEMPLATE.md.backup b/templates/github/PULL_REQUEST_TEMPLATE.md.backup deleted file mode 100644 index 7b56431..0000000 --- a/templates/github/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/github/README.md b/templates/github/README.md deleted file mode 100644 index 45ed938..0000000 --- a/templates/github/README.md +++ /dev/null @@ -1,386 +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 `.github/` 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 `.github/PULL_REQUEST_TEMPLATE.md` in your repository. - -### CODEOWNERS Template - -**File**: `CODEOWNERS.template` - -**Purpose**: Define code ownership for automatic review assignment. - -**Usage**: -1. Copy to `.github/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 `.github/` 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/ -└── .github/ - ├── 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/MokoStandards/docs - about: "Check the documentation first" - - name: "💬 Discussions" - url: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards/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 -/.github/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: -``` -.github/ -├── ISSUE_TEMPLATE/ -│ └── bug_report.md -└── PULL_REQUEST_TEMPLATE.md -``` - -### Example: Complete Setup - -For large projects: -``` -.github/ -├── 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: -``` -.github/ -├── 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) -- [MokoStandards Repository](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards) - -## Metadata - -* **Document**: templates/github/README.md -* **Repository**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards) -* **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/github/copilot-instructions.dolibarr.md.template b/templates/github/copilot-instructions.dolibarr.md.template deleted file mode 100644 index b026ee1..0000000 --- a/templates/github/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 MokoStandards 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** `.github/copilot-instructions.md` and `.github/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-tech/MokoStandards/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 [MokoStandards](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards). 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 -├── .github/ -│ ├── 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-tech/MokoStandards/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. - ---- - -## MokoStandards Reference - -This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards). Authoritative policies: - -| Document | Purpose | -|----------|---------| -| [file-header-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | -| [coding-style-guide.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | -| [branching-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | -| [merge-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | -| [changelog-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | -| [module-registry.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/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-tech/MokoStandards/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/github/copilot-instructions.joomla.md.template b/templates/github/copilot-instructions.joomla.md.template deleted file mode 100644 index d5a1996..0000000 --- a/templates/github/copilot-instructions.joomla.md.template +++ /dev/null @@ -1,307 +0,0 @@ - - -> [!IMPORTANT] -> **🔧 AI Self-Update Required on First Use** -> -> This file may contain unfilled `{{TOKEN}}` placeholders. The MokoStandards 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** `.github/copilot-instructions.md` and `.github/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 [MokoStandards](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards). 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 -├── .github/ -│ ├── 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 }} -``` - ---- - -## MokoStandards Reference - -This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards). Authoritative policies: - -| Document | Purpose | -|----------|---------| -| [file-header-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | -| [coding-style-guide.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | -| [branching-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | -| [merge-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | -| [changelog-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | -| [joomla-development-guide.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/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 diff --git a/templates/github/copilot-instructions.md.template b/templates/github/copilot-instructions.md.template deleted file mode 100644 index d038c98..0000000 --- a/templates/github/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 MokoStandards 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** `.github/copilot-instructions.md` and `.github/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 [MokoStandards](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards). 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 [MokoStandards coding-style-guide](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/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` | -| MokoStandards | `orange` | `badge/MokoStandards-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 MokoStandards enterprise library. The `composer.json` must include: - -```json -{ - "repositories": [ - { - "type": "vcs", - "url": "https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards" - } - ], - "require": { - "mokoconsulting/mokostandards": "^4.0" - } -} -``` - -Run `composer install` after adding the dependency. See [package-installation.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/package-installation.md) for full instructions. - ---- - -## PHP Script Pattern - -All PHP scripts **must** extend `MokoStandards\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 `MokoStandards\Enterprise\CliFramework` -- Never call `$script->run()` as the entry point — call `$script->execute()` -- Policy documents and guides must not be mixed - ---- - -## MokoStandards Reference - -This repository is governed by [MokoStandards](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards). Authoritative policies: - -| Document | Purpose | -|----------|---------| -| [file-header-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type | -| [coding-style-guide.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions | -| [branching-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | -| [merge-strategy.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | -| [changelog-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | -| [scripting-standards.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/scripting-standards.md) | PHP script requirements and CliFramework usage | -| [package-installation.md](https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/package-installation.md) | Installing `mokoconsulting/mokostandards` via Composer | diff --git a/templates/github/dependabot.yml.template b/templates/github/dependabot.yml.template deleted file mode 100644 index 3b371ec..0000000 --- a/templates/github/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: MokoStandards.Security -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards -# PATH: /templates/github/dependabot.yml.template -# VERSION: XX.YY.ZZ -# BRIEF: Template Dependabot configuration for governed repositories -# NOTE: Copy to .github/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 .github/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-moko" - 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-moko" - # 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-moko" - 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-moko" - # 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-moko" - # groups: - # python-dependencies: - # patterns: - # - "*" diff --git a/templates/github/override.tf.template b/templates/github/override.tf.template deleted file mode 100644 index 87fa012..0000000 --- a/templates/github/override.tf.template +++ /dev/null @@ -1,114 +0,0 @@ -# Repository Health Check Override Configuration -# Location: .github/override.tf -# -# This file allows repository-specific customization of health checks. -# It overrides the default configuration from MokoStandards. -# -# AUTO-GENERATED: This file is automatically synced from MokoStandards -# 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: ".github/workflows/custom-workflow.yml", - # Example: ".github/ISSUE_TEMPLATE/custom-template.md", - ] - - # Protected files - # Files that should never be overwritten by sync - protected_files = [ - # Example: ".github/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/joomla/updates.xml.template b/templates/joomla/updates.xml.template index 249da6b..bee07f8 100644 --- a/templates/joomla/updates.xml.template +++ b/templates/joomla/updates.xml.template @@ -1,59 +1,119 @@ - - -This file is part of a Moko Consulting project. - -SPDX-License-Identifier: GPL-3.0-or-later - -FILE INFORMATION -DEFGROUP: MokoStandards.Templates.Joomla -INGROUP: MokoStandards.Templates -REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards -PATH: /templates/joomla/updates.xml.template -VERSION: XX.YY.ZZ -BRIEF: Joomla update server XML template — lists available extension releases for Joomla auto-updates - -NOTE: Dual-platform: Gitea (primary) and GitHub (backup mirror). - The manifest.xml must declare both servers. - Tokens replaced at sync time: {{REPO_NAME}}, {{GITEA_ORG}}, {{GITHUB_ORG}}, - {{EXTENSION_NAME}}, {{EXTENSION_TYPE}}, {{EXTENSION_ELEMENT}}, {{VERSION}}, - {{MAINTAINER_URL}} ---> - - - {{EXTENSION_NAME}} - {{REPO_NAME}} — Moko Consulting Joomla extension - {{EXTENSION_ELEMENT}} - {{EXTENSION_TYPE}} - {{VERSION}} - - https://git.mokoconsulting.tech/{{GITEA_ORG}}/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{EXTENSION_ELEMENT}}.zip - https://github.com/{{GITHUB_ORG}}/{{REPO_NAME}}/releases/download/v{{VERSION}}/{{EXTENSION_ELEMENT}}.zip - - - 8.2 - Moko Consulting - {{MAINTAINER_URL}} - + + + + {{EXTENSION_NAME}} + {{EXTENSION_NAME}} development build — unstable. + {{EXTENSION_ELEMENT}} + {{EXTENSION_TYPE}} + {{EXTENSION_FOLDER}} + {{EXTENSION_CLIENT}} + {{VERSION}} + {{DATE}} + https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/tag/development + + https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/development/{{EXTENSION_ELEMENT}}-{{VERSION}}-dev.zip + + + development + Moko Consulting + https://mokoconsulting.tech + + 8.1 + + + + + {{EXTENSION_NAME}} + {{EXTENSION_NAME}} alpha build — early testing. + {{EXTENSION_ELEMENT}} + {{EXTENSION_TYPE}} + {{EXTENSION_FOLDER}} + {{EXTENSION_CLIENT}} + {{VERSION}} + {{DATE}} + https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/tag/alpha + + https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/alpha/{{EXTENSION_ELEMENT}}-{{VERSION}}-alpha.zip + + + alpha + Moko Consulting + https://mokoconsulting.tech + + 8.1 + + + + + {{EXTENSION_NAME}} + {{EXTENSION_NAME}} beta build — feature complete, stability testing. + {{EXTENSION_ELEMENT}} + {{EXTENSION_TYPE}} + {{EXTENSION_FOLDER}} + {{EXTENSION_CLIENT}} + {{VERSION}} + {{DATE}} + https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/tag/beta + + https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/beta/{{EXTENSION_ELEMENT}}-{{VERSION}}-beta.zip + + + beta + Moko Consulting + https://mokoconsulting.tech + + 8.1 + + + + + {{EXTENSION_NAME}} + {{EXTENSION_NAME}} release candidate — testing only. + {{EXTENSION_ELEMENT}} + {{EXTENSION_TYPE}} + {{EXTENSION_FOLDER}} + {{EXTENSION_CLIENT}} + {{VERSION}} + {{DATE}} + https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/tag/release-candidate + + https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/release-candidate/{{EXTENSION_ELEMENT}}-{{VERSION}}-rc.zip + + + rc + Moko Consulting + https://mokoconsulting.tech + + 8.1 + + + + + {{EXTENSION_NAME}} + {{EXTENSION_NAME}} — Moko Consulting Joomla extension. + {{EXTENSION_ELEMENT}} + {{EXTENSION_TYPE}} + {{EXTENSION_FOLDER}} + {{EXTENSION_CLIENT}} + {{VERSION}} + {{DATE}} + https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/tag/stable + + https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/releases/download/stable/{{EXTENSION_ELEMENT}}-{{VERSION}}.zip + + + stable + Moko Consulting + https://mokoconsulting.tech + + 8.1 + + diff --git a/templates/scripts/validate/validate_structure.php b/templates/scripts/validate/validate_structure.php index c074c79..975f45b 100644 --- a/templates/scripts/validate/validate_structure.php +++ b/templates/scripts/validate/validate_structure.php @@ -32,7 +32,7 @@ use MokoEnterprise\CliFramework; * - Required root files present (README.md, CHANGELOG.md, LICENSE, CONTRIBUTING.md, * SECURITY.md, .gitignore, .editorconfig, composer.json) * - Required directories present (src/, docs/, tests/) - * - .mokostandards.yml governance attachment present + * - .gitea/.mokostandards XML governance manifest present * - SPDX-License-Identifier header present in all PHP source files * - No tab characters in YAML/JSON config files * - No Windows path separators in PHP source @@ -74,10 +74,25 @@ class ValidateStructure extends CliFramework // ── Governance attachment ───────────────────────────────────────── $this->section('MokoStandards governance'); - $mokoFile = file_exists("{$path}/.mokostandards.yml"); - $this->status($mokoFile, '.mokostandards.yml'); + $mokoFile = file_exists("{$path}/.gitea/.mokostandards") + || file_exists("{$path}/.github/.mokostandards") + || file_exists("{$path}/.mokostandards"); + $this->status($mokoFile, '.gitea/.mokostandards (XML manifest)'); $mokoFile ? $passed++ : $failed++; + // Validate XML format if file exists + if ($mokoFile) { + $manifestPath = file_exists("{$path}/.gitea/.mokostandards") + ? "{$path}/.gitea/.mokostandards" + : (file_exists("{$path}/.github/.mokostandards") + ? "{$path}/.github/.mokostandards" + : "{$path}/.mokostandards"); + $manifestContent = file_get_contents($manifestPath); + $isXml = str_contains($manifestContent, 'status($isXml, '.mokostandards uses XML format'); + $isXml ? $passed++ : $failed++; + } + // ── Required directories ────────────────────────────────────────── $this->section('Required directories'); foreach (['src', 'docs', 'tests'] as $dir) { diff --git a/templates/workflows/README.md b/templates/workflows/README.md deleted file mode 100644 index 4431525..0000000 --- a/templates/workflows/README.md +++ /dev/null @@ -1,708 +0,0 @@ - - -# GitHub Workflow Templates - -## Purpose - -This directory contains consolidated GitHub Actions workflow templates for use across MokoStandards-governed repositories. These templates provide standardized CI/CD configurations for different project types. - -All workflow templates are documented in the unified repository schema at `schemas/unified-repository-schema.json`. The schema defines: -- Platform compatibility (generic, joomla, dolibarr, shared) -- Category (ci, build, test, release, deploy, quality, security, etc.) -- Required secrets and variables -- Permissions needed -- Requirement level (required, recommended, optional) - -## Live Workflows vs Templates - -**Live Workflows** (.github/workflows/) - Always active for MokoStandards repo: -- `standards-compliance.yml` - Repository standards validation -- `confidentiality-scan.yml` - Security and confidentiality checks -- `changelog_update.yml` - CHANGELOG management -- `bulk-repo-sync.yml` - Bulk repository synchronization -- `auto-create-org-projects.yml` - Organization project automation - -**Templates** (templates/workflows/) - For use in governed repositories: -- All workflow templates organized by platform and purpose -- 43 workflow templates available - -## Structure - -The workflows are organized by platform and purpose: - -### Platform-Specific Templates - -**generic/** - Universal workflows for all project types -- `ci.yml` - Generic continuous integration -- `code-quality.yml` - Code quality checks -- `codeql-analysis.yml` - Security analysis -- `dependency-review.yml.template` - Dependency review -- `repo-health.yml` - Repository health checks -- `test.yml.template` - Generic testing workflow - -**infrastructure/terraform/** - Terraform infrastructure-as-code workflows -- `ci.yml` - Terraform validation, formatting, and planning -- `deploy.yml.template` - Infrastructure deployment workflow -- `drift-detection.yml.template` - Automated drift detection - -**joomla/** - Joomla extension workflows -- `ci-joomla.yml.template` - Joomla-specific CI -- `test.yml.template` - Joomla extension testing -- `release.yml.template` - Joomla package creation -- `repo_health.yml.template` - Joomla repo health -- `version_branch.yml.template` - Version management - -**dolibarr/** - Dolibarr module workflows -- `ci-dolibarr.yml.template` - Dolibarr CI -- `test.yml.template` - Dolibarr module testing -- `release.yml.template` - Dolibarr package creation -- `sync-changelogs.yml.template` - Changelog synchronization - -### Release & Build Templates - -- `release-cycle.yml.template` - Full release cycle (main → dev → rc → version → main) -- `release-pipeline.yml.template` - Automated release pipeline -- `build.yml.template` - Universal build workflow -- `version_branch.yml` - Version branch management -- `branch-cleanup.yml.template` - Branch cleanup automation - -### Reusable Workflows - -- `reusable-build.yml.template` - Reusable build job -- `reusable-ci-validation.yml` - Reusable CI validation -- `reusable-deploy.yml` - Reusable deployment -- `reusable-joomla-testing.yml` - Reusable Joomla tests -- `reusable-php-quality.yml` - Reusable PHP quality checks -- `reusable-platform-testing.yml` - Reusable platform testing -- `reusable-project-detector.yml` - Project type detection -- `reusable-release.yml.template` - Reusable release job -- `reusable-script-executor.yml` - Reusable script execution - -### Shared Utilities - -**shared/** - Organization-wide utility workflows -- `enterprise-firewall-setup.yml.template` - Firewall configuration -- `rebuild-docs-indexes.yml.template` - Documentation indexing -- `setup-project-v2.yml.template` - Project setup automation -- `sync-docs-to-project.yml.template` - Documentation sync - -### Root Templates - -- `ci-joomla.yml.template` - Legacy Joomla CI template -- `repo_health.yml.template` - Legacy repo health template -- `repo_health_xml.yml.template` - XML-based repo health -├── dolibarr/ # Dolibarr-specific workflow templates -│ ├── ci-dolibarr.yml.template # Continuous integration for Dolibarr modules -│ ├── test.yml.template # Testing workflow for Dolibarr modules -│ └── release.yml.template # Automated release and deployment -└── generic/ # Generic/platform-agnostic workflow templates - ├── ci.yml.template # Multi-language CI (Node.js, Python, PHP, Go, Ruby, Rust) - ├── test.yml.template # Comprehensive testing (unit, integration, e2e) - ├── deploy.yml.template # Deployment workflow for multiple environments - ├── code-quality.yml.template # Code quality, linting, and static analysis - ├── codeql-analysis.yml.template # CodeQL security analysis - └── repo_health.yml.template # Repository health checks for generic projects -``` - -**Note**: All template workflow files use the `.yml.template` extension to clearly distinguish them from actual workflow files. When copying to your repository, rename them to `.yml` (e.g., `cp ci.yml.template .github/workflows/ci.yml`). - -## Template Categories - -### Joomla Templates (`joomla/`) - -Workflow templates specifically designed for Joomla extensions (components, modules, plugins, libraries, packages, templates): - -- **ci-joomla.yml.template** - Continuous integration workflow with PHP validation, XML checking, and manifest verification -- **test.yml.template** - Comprehensive testing with PHPUnit, code quality checks, and integration tests -- **release.yml.template** - Automated release workflow for creating and publishing Joomla extension packages -- **repo_health.yml.template** - Repository health monitoring including documentation checks and standards validation -- **version_branch.yml.template** - Automated version branch management and release preparation - -### Dolibarr Templates (`dolibarr/`) - -Workflow templates specifically designed for Dolibarr ERP/CRM modules: - -- **ci-dolibarr.yml.template** - Continuous integration for Dolibarr modules with structure validation, PHP syntax checking, and security checks -- **test.yml.template** - Automated testing workflow with PHPUnit tests and Dolibarr environment integration -- **release.yml.template** - Automated release workflow for Dolibarr module packaging and deployment - -### Generic Templates (`generic/`) - -Platform-agnostic workflow templates for multi-language software development: - -- **ci.yml.template** - Multi-language continuous integration with automatic language detection (supports Node.js, Python, PHP, Go, Ruby, Rust) -- **test.yml.template** - Comprehensive testing workflow supporting unit tests, integration tests, and end-to-end tests -- **deploy.yml.template** - Deployment workflow for staging and production environments with rollback capabilities -- **code-quality.yml.template** - Code quality analysis with linting, formatting, static analysis, dependency checks, and security scanning -- **codeql-analysis.yml.template** - CodeQL security analysis for vulnerability detection -- **repo_health.yml.template** - Repository health monitoring for generic projects - -## Available Templates - -### ci-joomla.yml / joomla/ci.yml -Continuous Integration workflow for Joomla component repositories. - -**Features:** -- Validates Joomla manifests -- Checks XML well-formedness -- Runs PHP syntax validation -- Validates CHANGELOG structure -- Checks license headers -- Validates version alignment -- Tab and path separator checks -- Secret scanning - -**Usage:** -Copy to your repository as `.github/workflows/ci.yml` and customize as needed. - -### repo_health.yml / generic/repo_health.yml / joomla/repo_health.yml -Repository health and governance validation workflow. - -**Features:** -- Admin-only execution gate -- Scripts governance (directory structure validation) -- Repository artifact validation (required files and directories) -- Content heuristics (CHANGELOG, LICENSE, README validation) -- Extended checks: - - CODEOWNERS presence - - Workflow pinning advisory - - Documentation link integrity - - ShellCheck validation - - SPDX header compliance - - Git hygiene (stale branches) - -**Profiles:** -- `all` - Run all checks -- `scripts` - Scripts governance only -- `repo` - Repository health only - -**Usage:** -Copy to your repository as `.github/workflows/repo_health.yml`. Requires admin permissions to run. - -### version_branch.yml / joomla/version_branch.yml -Automated version branching and version bumping workflow. - -**Features:** -- Creates `dev/` branches from base branch -- Updates version numbers across all governed files -- Updates manifest dates -- Updates CHANGELOG with version entry -- Enterprise policy gates: - - Required governance artifacts check - - Branch namespace collision defense - - Control character guard - - Update feed enforcement - -**Inputs:** -- `new_version` (required) - Version in format NN.NN.NN (e.g., 03.01.00) -- `version_text` (optional) - Version label (e.g., LTS, RC1, hotfix) -- `report_only` (optional) - Dry run mode without branch creation -- `commit_changes` (optional) - Whether to commit and push changes - -**Usage:** -Copy to your repository as `.github/workflows/version_branch.yml`. Run manually via workflow_dispatch. - -### joomla/test.yml -Comprehensive testing workflow for Joomla extensions. - -**Features:** -- PHPUnit tests across multiple PHP and Joomla versions -- Code quality checks (PHPCS, PHPStan, Psalm) -- Integration tests with MySQL database -- Code coverage reporting with Codecov integration - -**Matrix Testing:** -- PHP versions: 7.4, 8.0, 8.1, 8.2 -- Joomla versions: 4.4, 5.0 - -**Usage:** -Copy to your repository as `.github/workflows/test.yml`. - -### joomla/release.yml -Automated release and package creation workflow for Joomla extensions. - -**Features:** -- Builds release packages from tags or manual triggers -- Updates version numbers in manifest files -- Creates ZIP packages with proper structure -- Generates checksums (SHA256 and MD5) -- Creates GitHub releases with changelog extraction -- Uploads release artifacts - -**Triggers:** -- Push to tags matching `v*.*.*` -- Manual workflow dispatch with version input - -**Usage:** -Copy to your repository as `.github/workflows/release.yml`. - -### dolibarr/ci.yml -Continuous integration workflow for Dolibarr modules. - -**Features:** -- Module structure validation -- PHP syntax checking across PHP 7.4-8.2 and Dolibarr 16.0-18.0 -- Dolibarr API usage validation -- Database schema validation -- License header compliance -- Code quality checks (PHPCS, PHPStan) -- Security scanning (hardcoded credentials, SQL injection, XSS) - -**Usage:** -Copy to your repository as `.github/workflows/ci.yml`. - -### dolibarr/test.yml -Testing workflow for Dolibarr modules with full environment setup. - -**Features:** -- PHPUnit tests with Dolibarr environment -- Automatic Dolibarr installation and configuration -- MySQL database integration -- Module linking and installation -- Integration tests support -- Code coverage reporting - -**Usage:** -Copy to your repository as `.github/workflows/test.yml`. - -### generic/ci.yml -Multi-language continuous integration workflow with automatic language detection. - -**Features:** -- Automatic project language detection (Node.js, Python, PHP, Go, Ruby, Rust) -- Parallel testing across language matrices -- Language-specific linting and code quality checks -- Security scanning with Trivy -- Comprehensive test execution - -**Supported Languages:** -- Node.js (16.x, 18.x, 20.x) -- Python (3.8, 3.9, 3.10, 3.11) -- PHP (7.4, 8.0, 8.1, 8.2) -- Go (1.20, 1.21, 1.22) -- Ruby (2.7, 3.0, 3.1, 3.2) -- Rust (stable, beta) - -**Usage:** -Copy to your repository as `.github/workflows/ci.yml`. - -### generic/test.yml -Comprehensive testing workflow supporting unit, integration, and end-to-end tests. - -**Features:** -- Automatic project type detection -- Unit tests with coverage reporting -- Integration tests with PostgreSQL and Redis -- End-to-end tests with Playwright -- Codecov integration -- Test result summaries - -**Usage:** -Copy to your repository as `.github/workflows/test.yml`. - -### generic/deploy.yml -Deployment workflow for multiple environments with rollback capabilities. - -**Features:** -- Automatic environment detection (staging, production, development) -- Multi-language build support -- Separate staging and production deployment jobs -- Smoke tests after deployment -- Automatic rollback on failure -- Deployment notifications - -**Triggers:** -- Push to main or staging branches -- Release publication -- Manual workflow dispatch - -**Usage:** -Copy to your repository as `.github/workflows/deploy.yml`. Configure deployment commands for your infrastructure. - -### generic/code-quality.yml -Comprehensive code quality analysis workflow. - -**Features:** -- Multi-language linting and formatting - - JavaScript/TypeScript: ESLint, Prettier - - Python: Flake8, Black, isort, Pylint, Bandit - - PHP: PHPCS, PHP-CS-Fixer, PHPStan, Psalm - - Go: golangci-lint, go fmt - - Rust: cargo fmt, cargo clippy -- Static analysis with CodeQL -- Dependency security checks (Snyk, npm audit, pip safety) -- Code complexity analysis with radon -- Code coverage analysis - -**Usage:** -Copy to your repository as `.github/workflows/code-quality.yml`. - -### Plugin Validation Workflows - -Project-specific validation workflows using the MokoStandards plugin system. Each workflow validates projects using the appropriate plugin for automated quality assurance. - -#### validate-joomla-project.yml -**Features:** -- Validates Joomla CMS projects and extensions -- Runs health checks specific to Joomla standards -- Collects Joomla-specific metrics -- Checks release readiness for Joomla extensions -- Comments validation results on pull requests - -**Usage:** Copy to `.github/workflows/validate.yml` in Joomla projects. - -#### validate-nodejs-project.yml -**Features:** -- Validates Node.js applications and packages -- Checks package.json structure and scripts -- Runs npm audit for security vulnerabilities -- Collects Node.js project metrics -- Creates validation summary in workflow run - -**Usage:** Copy to `.github/workflows/validate.yml` in Node.js projects. - -#### validate-python-project.yml -**Features:** -- Validates Python applications and packages -- Checks pyproject.toml, setup.py, requirements.txt -- Runs safety checks for Python dependencies -- Collects Python-specific metrics -- Validates project structure and best practices - -**Usage:** Copy to `.github/workflows/validate.yml` in Python projects. - -#### validate-terraform-project.yml -**Features:** -- Validates Terraform Infrastructure as Code projects -- Checks Terraform file formatting -- Runs terraform validate -- Validates module structure -- Collects infrastructure metrics - -**Usage:** Copy to `.github/workflows/validate.yml` in Terraform projects. - -#### validate-wordpress-project.yml -**Features:** -- Validates WordPress themes and plugins -- Checks WordPress coding standards -- Validates plugin/theme structure -- Collects WordPress-specific metrics -- Checks for common WordPress security issues - -**Usage:** Copy to `.github/workflows/validate.yml` in WordPress projects. - -#### validate-mobile-project.yml -**Features:** -- Validates mobile applications (iOS/Android) -- Checks mobile app structure -- Validates configuration files -- Collects mobile app metrics - -**Usage:** Copy to `.github/workflows/validate.yml` in mobile projects. - -#### validate-api-project.yml -**Features:** -- Validates REST API and GraphQL services -- Checks OpenAPI/Swagger specifications -- Validates API structure and documentation -- Collects API-specific metrics -- Checks API security best practices - -**Usage:** Copy to `.github/workflows/validate.yml` in API projects. - -#### validate-dolibarr-project.yml -**Features:** -- Validates Dolibarr ERP/CRM modules -- Checks Dolibarr module structure -- Validates module descriptors -- Collects Dolibarr-specific metrics - -**Usage:** Copy to `.github/workflows/validate.yml` in Dolibarr projects. - -#### validate-generic-project.yml -**Features:** -- Validates generic project types -- Checks common best practices -- Validates basic project structure -- Collects general metrics - -**Usage:** Copy to `.github/workflows/validate.yml` in projects that don't fit other categories. - -#### validate-documentation-project.yml -**Features:** -- Validates documentation projects -- Checks for broken links with markdown-link-check -- Lints Markdown files with markdownlint -- Validates documentation structure -- Collects documentation metrics - -**Usage:** Copy to `.github/workflows/validate.yml` in documentation projects. - -**All Plugin Validation Workflows Include:** -- Automated project type detection (or explicit type specification) -- Validation checks with JSON output -- Health checks with scoring -- Metrics collection -- Release readiness checks (on main branch) -- Artifact upload for validation results -- Proper exit codes (0=success, 1=failure, 2=error) - -## Usage - -### For New Projects - -1. Choose the appropriate template directory for your project type: - - **Joomla extensions** → `joomla/` - - **Dolibarr modules** → `dolibarr/` - - **Other projects** → `generic/` -2. Copy the relevant workflow files to your project's `.github/workflows/` directory -3. Customize the workflow parameters as needed for your specific project: - - Update FILE INFORMATION headers with correct paths - - Adjust branch patterns to match your branching strategy - - Configure environment-specific settings (deployment URLs, secrets, etc.) -4. Commit and push to enable the workflows - -### For Existing Projects - -1. Review your current workflows against the templates -2. Identify gaps or improvements from the standard templates -3. Update your workflows to align with current standards -4. Test changes on a feature branch before merging to main - -## Integration with MokoStandards - -These workflows are designed to work with: - -- **Script templates** in `templates/scripts/` -- **Documentation standards** in `docs/policy/` -- **Repository layout standards** defined in README.md - -## Customization Guidelines - -When adapting these templates: - -- **Preserve core validation steps** - Don't remove required compliance checks -- **Add project-specific steps** - Extend templates with additional validation as needed -- **Maintain naming conventions** - Keep workflow names consistent for cross-repo visibility -- **Document deviations** - If you must deviate from templates, document why in the workflow file - -When copying templates to your repository: - -1. **Update FILE INFORMATION headers** with correct paths -2. **Adjust branch patterns** to match your branching strategy -3. **Modify validation scripts** based on available scripts in your repository -4. **Customize required artifacts** in repo_health.yml -5. **Update allowed script directories** to match your structure - -## Workflow Dependencies - -### Joomla Workflows - -**ci.yml requires:** -- `scripts/validate/manifest.sh` -- `scripts/validate/xml_wellformed.sh` -- Optional validation scripts in `scripts/validate/` - -**test.yml requires:** -- PHPUnit configuration (`phpunit.xml` or `phpunit.xml.dist`) -- Composer for dependency management -- Optional: PHPCS, PHPStan, Psalm configurations - -**release.yml requires:** -- Git tags following semver pattern (`v*.*.*`) -- XML manifest files for version updates -- Optional: CHANGELOG.md for release notes - -### Dolibarr Workflows - -**ci.yml requires:** -- Module descriptor in `core/modules/modMyModule.class.php` -- Proper Dolibarr module directory structure -- Optional: `scripts/validate/` directory for custom validation - -**test.yml requires:** -- PHPUnit configuration -- MySQL database (provided by GitHub Actions services) -- Dolibarr installation (automated in workflow) - -### Generic Workflows - -**ci.yml requires:** -- Language-specific package managers (npm, pip, composer, go, bundler, cargo) -- Test configurations for your language - -**test.yml requires:** -- Test framework configuration (Jest, pytest, PHPUnit, etc.) -- Optional: PostgreSQL and Redis (provided by services) -- Optional: Playwright for E2E tests - -**deploy.yml requires:** -- Environment secrets configured in GitHub repository settings -- Deployment target configuration (servers, cloud platforms, etc.) - -**code-quality.yml requires:** -- Optional: Snyk token for security scanning -- Language-specific linter configurations - -### repo_health.yml requires: -- Python 3.x (for JSON processing) -- ShellCheck (installed automatically if needed) - -### version_branch.yml requires: -- Python 3.x (for version bumping logic) -- Governance artifacts: LICENSE, CONTRIBUTING.md, CODE_OF_CONDUCT.md, etc. - -## Required Workflows - -All MokoStandards-governed repositories MUST implement: - -1. **CI workflow** - For build validation and testing - - Use `joomla/ci.yml` for Joomla extensions - - Use `dolibarr/ci.yml` for Dolibarr modules - - Use `generic/ci.yml` for other projects -2. **Repository health workflow** - For ongoing compliance monitoring - -Optional but recommended: - -3. **Test workflow** - For comprehensive automated testing -4. **Release workflow** - For automated release management (Joomla projects) -5. **Deploy workflow** - For automated deployments (web applications) -6. **Code quality workflow** - For advanced code analysis -7. **Version branch workflow** - For repositories using version-based branching -8. **Security scanning** - CodeQL or equivalent (now in main .github/workflows/) -9. **Dependency updates** - Dependabot (configured in .github/dependabot.yml) - -## Standards Compliance - -All workflows follow MokoStandards requirements: - -- SPDX license headers -- GPL-3.0-or-later license -- Proper error handling and reporting -- Step summaries for GitHub Actions UI -- Audit trail generation - -## Trigger Patterns - -### CI Workflows -- Push to main, dev/**, rc/**, version/** branches -- Pull requests to same branches - -### Repo Health -- Manual workflow_dispatch with profile selection -- Push to main (workflows, scripts, docs paths) -- Pull requests (workflows, scripts, docs paths) - -### Version Branch -- Manual workflow_dispatch only (admin-level operation) - -## Template Maintenance - -These templates are maintained as part of MokoStandards and updated periodically: - -- **Breaking changes** - Will be announced via changelog and require downstream updates -- **Non-breaking improvements** - Can be adopted at downstream projects' convenience -- **Security updates** - Must be adopted immediately per security policy - -## Integration with Repository Scaffolds - -These workflow templates are designed to work with project-specific repository scaffolds maintained in individual repositories. The separation allows: -- Workflow templates to be version-controlled and updated independently -- Easy discovery and comparison of workflow configurations -- Central management of CI/CD patterns across the organization - -Consult the organization's scaffold repositories for complete repository layouts that integrate these workflows. - -## Best Practices - -1. **Pin action versions** - Use specific versions (@v4) not @main/@master -2. **Test workflows** in development branches before merging to main -3. **Review step summaries** in GitHub Actions UI after runs -4. **Use workflow concurrency** to prevent simultaneous runs -5. **Set appropriate timeouts** for long-running operations -6. **Configure secrets properly** - Use GitHub repository secrets for sensitive data -7. **Start with basic workflows** - Begin with CI and testing, then add advanced workflows -8. **Monitor workflow costs** - Be aware of GitHub Actions minutes usage -9. **Use matrix strategies** - Test across multiple versions when appropriate -10. **Document customizations** - Add comments explaining any deviations from templates - -## Support and Feedback - -For issues or questions about these workflows: - -1. Review the workflow logs in GitHub Actions UI -2. Check the step summaries for detailed error reports -3. Validate your scripts locally before CI runs -4. Refer to MokoStandards documentation in `docs/` - -For questions, issues, or suggestions regarding these workflow templates: - -- Open an issue in the MokoStandards repository -- Reference specific template files in your report -- Tag with `workflow-template` label - -## Compliance - -Use of these templates helps ensure: - -- Consistent CI/CD patterns across projects -- Automated enforcement of coding standards -- Security scanning and vulnerability detection -- Documentation and governance compliance - ---- - -## Metadata - -| Field | Value | -| ---------- | ------------------------------------------------------------------------------------------------------------ | -| Document | GitHub Workflow Templates README | -| Path | /templates/workflows/README.md | -| Repository | [https://git.mokoconsulting.tech/MokoConsulting/MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards) | -| Owner | Moko Consulting | -| Scope | Workflow template documentation | -| Status | Active | -| Effective | 2026-01-04 | - -## Version History - -| Version | Date | Changes | -| -------- | ---------- | ------------------------------------------------ | -| 01.01.00 | 2026-01-04 | Added comprehensive development workflow templates | -| 01.00.01 | 2026-01-04 | Consolidated templates to /templates/workflows/ | -| 01.00.00 | 2026-01-04 | Initial workflow templates for MokoStandards | - -## Revision History - -| Date | Change Description | Author | -| ---------- | --------------------------------------------------- | --------------- | -| 2026-01-04 | Added Joomla test.yml, release.yml; Dolibarr ci.yml, test.yml; Generic ci.yml, test.yml, deploy.yml, code-quality.yml | Moko Consulting | -| 2026-01-04 | Moved to /templates/workflows/ directory | Moko Consulting | -| 2026-01-04 | Initial creation with consolidated workflow templates | Moko Consulting | diff --git a/templates/workflows/audit-log-archival.yml b/templates/workflows/audit-log-archival.yml deleted file mode 100644 index b708d65..0000000 --- a/templates/workflows/audit-log-archival.yml +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.AuditLogArchival -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /.github/workflows/audit-log-archival.yml -# VERSION: 04.06.00 -# BRIEF: Weekly audit log archival and compliance report generation -# NOTE: Archives audit logs and generates compliance reports using PHP AuditLogger - -name: Audit Log Archival - -on: - schedule: - # Run weekly on Sunday at 00:00 UTC - - cron: '0 0 * * 0' - workflow_dispatch: - inputs: - retention_days: - description: 'Days to retain audit logs' - required: false - type: number - default: 90 - generate_report: - description: 'Generate compliance report' - required: false - type: boolean - default: true - -permissions: - contents: read - -jobs: - archive-audit-logs: - name: Archive Audit Logs - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - with: - php-version: '8.1' - extensions: mbstring, curl, json - tools: composer - - - name: Install Composer Dependencies - run: composer install --no-dev --optimize-autoloader - - - name: Create Archive Directory - run: | - mkdir -p logs/audit/archive - mkdir -p logs/reports - - - name: Archive Audit Logs - id: archive - run: | - echo "## 📦 Audit Log Archival" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - RETENTION_DAYS="${{ github.event.inputs.retention_days || 90 }}" - - php << 'EOF' - startTransaction('archive_logs'); - $archivedCount = $logger->rotateLogs($retentionDays); - $transaction->logEvent('logs_archived', [ - 'count' => $archivedCount, - 'retention_days' => $retentionDays - ]); - $transaction->commit(); - - echo "✅ Successfully archived {$archivedCount} log files\n"; - file_put_contents('/tmp/archive_output.txt', "archive_count={$archivedCount}"); - - } catch (Exception $e) { - echo "❌ Archival failed: " . $e->getMessage() . "\n"; - exit(1); - } - EOF - - ARCHIVE_COUNT=$(grep archive_count /tmp/archive_output.txt | cut -d= -f2) - echo "archive_count=$ARCHIVE_COUNT" >> $GITHUB_OUTPUT - - echo "✅ Archived **${ARCHIVE_COUNT}** log files" >> $GITHUB_STEP_SUMMARY - - - name: Generate Compliance Report - if: github.event.inputs.generate_report != 'false' - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "## 📊 Compliance Report Generation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - php << 'EOF' - modify('-7 days'); - - $report = $logger->generateComplianceReport( - $startDate, - $endDate, - 'logs/reports/compliance-report.json' - ); - - echo "✅ Compliance report generated\n"; - echo "Report period: " . $startDate->format('Y-m-d') . " to " . $endDate->format('Y-m-d') . "\n"; - echo "Total events: " . ($report['total_events'] ?? 0) . "\n"; - echo "Security events: " . ($report['security_events'] ?? 0) . "\n"; - - } catch (Exception $e) { - echo "⚠️ Report generation failed: " . $e->getMessage() . "\n"; - // Don't fail the job if report generation fails - } - EOF - - if [ -f "logs/reports/compliance-report.json" ]; then - echo "✅ Compliance report generated successfully" >> $GITHUB_STEP_SUMMARY - fi - - - name: Upload Archive Report - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4.5.0 - with: - name: audit-archive-report-${{ github.run_number }} - path: | - logs/audit/archive/ - logs/reports/compliance-report.json - retention-days: 90 - - - name: Notify on Failure - if: failure() - run: | - echo "❌ Audit log archival failed" >> $GITHUB_STEP_SUMMARY - echo "Please check the logs for details" >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/auto-update-changelog.md b/templates/workflows/auto-update-changelog.md deleted file mode 100644 index a509269..0000000 --- a/templates/workflows/auto-update-changelog.md +++ /dev/null @@ -1,296 +0,0 @@ -# Auto-Update Changelog Workflow Template - -This workflow automatically updates CHANGELOG.md when a pull request is merged to the main branch. - -## Features - -- ✅ Automatically extracts PR information (title, number, author, labels) -- ✅ Determines change type from PR labels or title keywords -- ✅ Parses PR description for detailed changelog content -- ✅ Updates CHANGELOG.md in UNRELEASED section -- ✅ Creates appropriate section (Added, Changed, Fixed, etc.) -- ✅ Commits changes with descriptive message -- ✅ Posts comment on PR confirming update -- ✅ Creates CHANGELOG.md if it doesn't exist -- ✅ Skips CI on changelog commits ([skip ci]) - -## Change Type Detection - -The workflow determines the change type based on: - -### From PR Labels -- `breaking` → Breaking Changes -- `security` → Security -- `deprecated` → Deprecated -- `bug` or `fix` → Fixed -- `removed` or `delete` → Removed -- `changed` or `update` → Changed -- Default → Added - -### From PR Title Keywords -Same keywords as labels (case-insensitive) - -## Usage - -### Installation - -1. Copy this file to `.github/workflows/auto-update-changelog.yml` -2. Ensure `CHANGELOG.md` follows Keep a Changelog format -3. Commit and push - -### Configuration - -#### Branch Names -```yaml -on: - pull_request: - types: [closed] - branches: - - main # Adjust to your default branch - - master # Or add multiple branches -``` - -#### Permissions -```yaml -permissions: - contents: write # To commit changelog - pull-requests: read # To read PR info -``` - -### CHANGELOG.md Format - -The workflow expects this structure: - -```markdown -# Changelog - -## [UNRELEASED] - -### Added -- Feature descriptions - -### Changed -- Change descriptions - -### Fixed -- Bug fix descriptions - -### Security -- Security fix descriptions - -### Deprecated -- Deprecated feature notices - -### Removed -- Removed feature notices - -### Breaking Changes -- Breaking change descriptions -``` - -## PR Description Format - -For best results, structure your PR description like this: - -```markdown -## Summary - -Brief description of changes. - -### Changes Made - -- Specific change 1 -- Specific change 2 -- Specific change 3 - -### Impact - -How this affects the system. -``` - -The workflow will extract this content and add it to the changelog. - -## Examples - -### Example 1: Feature Addition - -**PR Title**: "Add user authentication feature" -**PR Labels**: `enhancement`, `feature` -**Result**: Added to `### Added` section in CHANGELOG.md - -```markdown -### Added -- **PR #123**: Add user authentication feature (by @developer) - JWT-based authentication with refresh tokens - Login and logout endpoints - Password hashing with bcrypt -``` - -### Example 2: Bug Fix - -**PR Title**: "Fix: Resolve null pointer exception in user service" -**PR Labels**: `bug` -**Result**: Added to `### Fixed` section - -```markdown -### Fixed -- **PR #124**: Fix: Resolve null pointer exception in user service (by @developer) - Added null checks in getUserById method - Improved error handling for missing users -``` - -### Example 3: Breaking Change - -**PR Title**: "BREAKING: Change API response format" -**PR Labels**: `breaking change` -**Result**: Added to `### Breaking Changes` section - -```markdown -### Breaking Changes -- **PR #125**: BREAKING: Change API response format (by @developer) - API responses now wrapped in { data, error, meta } structure - Old format no longer supported - Migration guide in docs/migration.md -``` - -## Customization - -### Change Type Keywords - -Modify the change type detection logic in the workflow: - -```yaml -- name: Extract PR Information - run: | - # Add custom keywords or change priority - if echo "$PR_TITLE" | grep -qi "YOUR_KEYWORD"; then - echo "change_type=Your Type" >> $GITHUB_OUTPUT - fi -``` - -### Changelog Entry Format - -Modify the changelog entry format: - -```yaml -- name: Update CHANGELOG.md with PR content - run: | - # Customize entry format - ENTRY="- **PR #$PR_NUMBER**: $PR_TITLE (by @$PR_USER on $DATE)" -``` - -### Content Extraction - -Modify how content is extracted from PR description: - -```yaml -- name: Parse PR Description for Changelog Content - run: | - # Add custom parsing logic - echo "$PR_BODY" | grep -A 50 "## Your Section" > /tmp/content.txt -``` - -## Integration with Pre-Merge Checklist - -This workflow complements the pre-merge checklist: - -1. **Developer** manually updates CHANGELOG during development (preferred) -2. **If missed**, this workflow adds entry automatically on merge -3. **Post-merge**, dev can edit changelog entry for more detail - -### Recommended Practice - -Use this workflow as **backup**, not replacement for manual updates: - -- ✅ Manually update CHANGELOG.md in PR (best practice) -- ✅ Use this workflow to catch missed updates -- ✅ Review and enhance auto-generated entries post-merge - -## Troubleshooting - -### Workflow Not Triggering - -Check: -- PR was actually merged (not just closed) -- Branch is in the `branches` list -- Workflow file in `.github/workflows/` -- Workflow has proper permissions - -### Commit Fails - -Check: -- `GH_TOKEN` has write permissions -- No branch protection preventing bot commits -- No conflicts in CHANGELOG.md - -### Wrong Change Type - -- Add appropriate labels to PR -- Use keywords in PR title -- Modify change type detection logic - -### Missing Content - -- Structure PR description properly -- Include `## Summary` or similar headers -- Provide detailed description - -## Related Documentation - -- [Copilot Pre-Merge Checklist](../../docs/policy/copilot-pre-merge-checklist.md) -- [Change Management Policy](../../docs/policy/change-management.md) -- [Keep a Changelog](https://keepachangelog.com/) -- [Semantic Versioning](https://semver.org/) - -## Workflow Details - -### Triggers -- Pull request closed event on main/master branches -- Only runs if PR was merged (not just closed) - -### Steps -1. Checkout repository with full history -2. Configure Git with bot credentials -3. Extract PR information (title, number, body, labels, user) -4. Determine change type from labels/title -5. Parse PR description for changelog content -6. Check if CHANGELOG.md exists (create if missing) -7. Update CHANGELOG.md in UNRELEASED section -8. Commit changes with descriptive message -9. Push to main branch -10. Comment on PR confirming update -11. Generate workflow summary - -### Outputs -- Updated CHANGELOG.md file -- Git commit with changelog update -- Comment on merged PR -- Workflow summary - -## Security Considerations - -- Uses `GH_TOKEN` with minimal required permissions -- Bot commits skip CI to avoid loops -- Read-only access to PR information -- Write access only to repository contents - -## Performance - -- Lightweight: completes in ~30 seconds -- No external dependencies -- Uses built-in GitHub Actions features -- Minimal resource usage - -## Maintenance - -Review and update: -- Change type keywords as project evolves -- Changelog format as standards change -- Content extraction logic for better parsing -- Error handling for edge cases - ---- - -**Version**: 03.00.00 -**Last Updated**: 2026-01-28 -**Maintained By**: Development Team diff --git a/templates/workflows/dolibarr/auto-release.yml.template b/templates/workflows/dolibarr/auto-release.yml.template deleted file mode 100644 index 4fe0edf..0000000 --- a/templates/workflows/dolibarr/auto-release.yml.template +++ /dev/null @@ -1,372 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Release -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/dolibarr/auto-release.yml.template -# VERSION: 04.06.00 -# BRIEF: Dolibarr build & release — module validation, update.txt -# -# +========================================================================+ -# | BUILD & RELEASE PIPELINE (DOLIBARR) | -# +========================================================================+ -# | | -# | Triggers on push to main (skips bot commits + [skip ci]): | -# | | -# | Every push: | -# | 1. Read version from README.md | -# | 3. Set platform version (Dolibarr $this->version) | -# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files | -# | 5. Write update.txt (version string) | -# | 6. Create git tag vXX.YY.ZZ | -# | 7a. Patch: update existing GitHub Release for this minor | -# | | -# | Every version change: archives main -> version/XX.YY branch | -# | Patch 00 = development (no release). First release = patch 01. | -# | First release only (patch == 01): | -# | 7b. Create new GitHub Release | -# | | -# +========================================================================+ - -name: Build & Release - -on: - pull_request: - types: [closed] - branches: - - main - paths: - - 'src/**' - - 'htdocs/**' - workflow_dispatch: - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -permissions: - contents: write - -jobs: - release: - name: Build & Release Pipeline - runs-on: ubuntu-latest - if: >- - github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - token: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - fetch-depth: 0 - - - name: Setup MokoStandards tools - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - git clone --depth 1 --branch {{standards_branch}} --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ - /tmp/mokostandards-api - cd /tmp/mokostandards-api - composer install --no-dev --no-interaction --quiet - - # -- STEP 1: Read version ----------------------------------------------- - - name: "Step 1: Read version from README.md" - id: version - run: | - VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null) - if [ -z "$VERSION" ]; then - echo "No VERSION in README.md — skipping release" - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - # Derive major.minor for branch naming (patches update existing branch) - MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}') - PATCH=$(echo "$VERSION" | awk -F. '{print $3}') - - MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') - MINOR_NUM=$(echo "$VERSION" | awk -F. '{print $2}') - - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT" - echo "minor=$MINOR" >> "$GITHUB_OUTPUT" - echo "major=$MAJOR" >> "$GITHUB_OUTPUT" - echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT" - if [ "$PATCH" = "00" ]; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "is_minor=false" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (patch 00 = development — skipping release)" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - if [ "$PATCH" = "01" ]; then - echo "is_minor=true" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (first release — full pipeline)" - else - echo "is_minor=false" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (patch — platform version + badges only)" - fi - fi - - - name: Check if already released - if: steps.version.outputs.skip != 'true' - id: check - run: | - TAG="${{ steps.version.outputs.release_tag }}" - BRANCH="${{ steps.version.outputs.branch }}" - - TAG_EXISTS=false - BRANCH_EXISTS=false - - git rev-parse "$TAG" >/dev/null 2>&1 && TAG_EXISTS=true - git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q "$BRANCH" && BRANCH_EXISTS=true - - echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT" - echo "branch_exists=$BRANCH_EXISTS" >> "$GITHUB_OUTPUT" - - if [ "$TAG_EXISTS" = "true" ] && [ "$BRANCH_EXISTS" = "true" ]; then - echo "already_released=true" >> "$GITHUB_OUTPUT" - else - echo "already_released=false" >> "$GITHUB_OUTPUT" - fi - - # -- SANITY CHECKS ------------------------------------------------------- - - name: "Sanity: Pre-release validation" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - ERRORS=0 - - echo "## Pre-Release Sanity Checks (Dolibarr)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # -- Version drift check (must pass before release) -------- - README_VER=$(grep -oP 'VERSION:\s*\K[\d.]+' README.md 2>/dev/null | head -1) - if [ "$README_VER" != "$VERSION" ]; then - echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - else - echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - fi - - # Check CHANGELOG version matches - CL_VER=$(grep -oP 'VERSION:\s*\K[\d.]+' CHANGELOG.md 2>/dev/null | head -1) - if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then - echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - fi - - # Check composer.json version if present - if [ -f "composer.json" ]; then - COMP_VER=$(grep -oP '"version"\s*:\s*"\K[^"]+' composer.json 2>/dev/null | head -1) - if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then - echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - fi - fi - - # Common checks - if [ ! -f "LICENSE" ]; then - echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - else - echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY - fi - - if [ ! -d "src" ] && [ ! -d "htdocs" ]; then - echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY - else - echo "- Source directory present" >> $GITHUB_STEP_SUMMARY - fi - - # -- Dolibarr: module descriptor check -------- - MOD_FILE=$(find src htdocs -path "*/core/modules/mod*.class.php" -print -quit 2>/dev/null) - if [ -z "$MOD_FILE" ]; then - echo "- No module descriptor (src/core/modules/mod*.class.php)" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - else - echo "- Module descriptor: \`${MOD_FILE}\`" >> $GITHUB_STEP_SUMMARY - - # -- Dolibarr: $this->numero check -------- - NUMERO=$(grep -oP '\$this->numero\s*=\s*\K\d+' "$MOD_FILE" 2>/dev/null || echo "0") - if [ "$NUMERO" = "0" ] || [ -z "$NUMERO" ]; then - echo "- Module number (\$this->numero) is 0 or not set" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - else - echo "- Module number: ${NUMERO}" >> $GITHUB_STEP_SUMMARY - fi - - # -- Dolibarr: url_last_version check -------- - if grep -q 'url_last_version' "$MOD_FILE" 2>/dev/null; then - echo "- url_last_version is set" >> $GITHUB_STEP_SUMMARY - else - echo "- Warning: url_last_version not set — update checks won't work" >> $GITHUB_STEP_SUMMARY - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "$ERRORS" -gt 0 ]; then - echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY - else - echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY - fi - - # -- STEP 2: Create or update version/XX.YY archive branch --------------- - # Always runs — every version change on main archives to version/XX.YY - - name: "Step 2: Version archive branch" - if: steps.check.outputs.already_released != 'true' - run: | - BRANCH="${{ steps.version.outputs.branch }}" - IS_MINOR="${{ steps.version.outputs.is_minor }}" - PATCH="${{ steps.version.outputs.version }}" - PATCH_NUM=$(echo "$PATCH" | awk -F. '{print $3}') - - # Check if branch exists - if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then - git push origin HEAD:"$BRANCH" --force - echo "Updated archive branch: ${BRANCH} (patch ${PATCH_NUM})" >> $GITHUB_STEP_SUMMARY - else - git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH" - git push origin "$BRANCH" --force - echo "Created archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY - fi - - # -- STEP 3: Set platform version ---------------------------------------- - - name: "Step 3: Set platform version" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - php /tmp/mokostandards-api/cli/version_set_platform.php \ - --path . --version "$VERSION" --branch main - - # -- STEP 4: Update version badges ---------------------------------------- - - name: "Step 4: Update version badges" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - find . -name "*.md" ! -path "./.git/*" ! -path "./vendor/*" | while read -r f; do - if grep -q '\[VERSION:' "$f" 2>/dev/null; then - sed -i "s/\[VERSION:[[:space:]]*[0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{2\}\]/[VERSION: ${VERSION}]/" "$f" - fi - done - - # -- STEP 5: Write update.txt -------------------------------------------- - - name: "Step 5: Write update.txt" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - printf '%s' "$VERSION" > update.txt - echo "update.txt: ${VERSION}" >> $GITHUB_STEP_SUMMARY - - # -- Commit all changes --------------------------------------------------- - - name: Commit release changes - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - if git diff --quiet && git diff --cached --quiet; then - echo "No changes to commit" - exit 0 - fi - VERSION="${{ steps.version.outputs.version }}" - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add -A - git commit -m "chore(release): build ${VERSION} [skip ci]" \ - --author="github-actions[bot] " - git push - - # -- STEP 6: Create tag --------------------------------------------------- - - name: "Step 6: Create git tag" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.tag_exists != 'true' && - steps.version.outputs.is_minor == 'true' - run: | - RELEASE_TAG="${{ steps.version.outputs.release_tag }}" - # Only create the major release tag if it doesn't exist yet - if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then - git tag "$RELEASE_TAG" - git push origin "$RELEASE_TAG" - echo "Tag created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY - else - echo "Tag ${RELEASE_TAG} already exists" >> $GITHUB_STEP_SUMMARY - fi - echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY - - # -- STEP 7: Create or update GitHub Release ------------------------------ - - name: "Step 7: GitHub Release" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.tag_exists != 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - VERSION="${{ steps.version.outputs.version }}" - RELEASE_TAG="${{ steps.version.outputs.release_tag }}" - BRANCH="${{ steps.version.outputs.branch }}" - MAJOR="${{ steps.version.outputs.major }}" - - NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null) - [ -z "$NOTES" ] && NOTES="Release ${VERSION}" - echo "$NOTES" > /tmp/release_notes.md - - EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true) - - if [ -z "$EXISTING" ]; then - gh release create "$RELEASE_TAG" \ - --title "v${MAJOR} (latest: ${VERSION})" \ - --notes-file /tmp/release_notes.md \ - --target "$BRANCH" - echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY - else - CURRENT_NOTES=$(gh release view "$RELEASE_TAG" --json body -q .body 2>/dev/null || true) - { - echo "$CURRENT_NOTES" - echo "" - echo "---" - echo "### ${VERSION}" - echo "" - cat /tmp/release_notes.md - } > /tmp/updated_notes.md - - gh release edit "$RELEASE_TAG" \ - --title "v${MAJOR} (latest: ${VERSION})" \ - --notes-file /tmp/updated_notes.md - echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY - fi - - # -- Summary -------------------------------------------------------------- - - name: Pipeline Summary - if: always() - run: | - VERSION="${{ steps.version.outputs.version }}" - if [ "${{ steps.version.outputs.skip }}" = "true" ]; then - echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY - echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY - elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then - echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY - else - echo "" >> $GITHUB_STEP_SUMMARY - echo "## Build & Release Complete (Dolibarr)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY - echo "|------|--------|" >> $GITHUB_STEP_SUMMARY - 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](https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY - fi diff --git a/templates/workflows/dolibarr/ci-dolibarr.yml.template b/templates/workflows/dolibarr/ci-dolibarr.yml.template deleted file mode 100644 index 926e6df..0000000 --- a/templates/workflows/dolibarr/ci-dolibarr.yml.template +++ /dev/null @@ -1,307 +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: Gitea.Workflow.Template -# INGROUP: MokoStandards.CI -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/dolibarr/ci-dolibarr.yml.template -# VERSION: 04.06.00 -# BRIEF: CI workflow for Dolibarr modules — lint, validate, test -# NOTE: Deployed to .github/workflows/ci-dolibarr.yml in governed Dolibarr module repos. - -name: Dolibarr Module CI - -on: - pull_request: - branches: - - main - - 'dev/**' - workflow_dispatch: - -permissions: - contents: read - pull-requests: write - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - lint-and-validate: - name: Lint & Validate - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0 - with: - php-version: '8.2' - extensions: mbstring, xml, zip, gd, curl, json - tools: composer:v2 - coverage: none - - - name: Clone MokoStandards - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} - run: | - git clone --depth 1 --branch {{standards_branch}} --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ - /tmp/mokostandards-api - - - name: Install dependencies - env: - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - if [ -f "composer.json" ]; then - composer install \ - --no-interaction \ - --prefer-dist \ - --optimize-autoloader - else - echo "No composer.json found — skipping dependency install" - fi - - - name: PHP syntax check - run: | - ERRORS=0 - for DIR in src/ htdocs/; do - if [ -d "$DIR" ]; then - while IFS= read -r -d '' FILE; do - OUTPUT=$(php -l "$FILE" 2>&1) - if echo "$OUTPUT" | grep -q "Parse error"; then - echo "::error file=${FILE}::${OUTPUT}" - ERRORS=$((ERRORS + 1)) - fi - done < <(find "$DIR" -name "*.php" -print0) - fi - done - echo "### PHP Syntax Check" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} syntax error(s) found.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY - fi - - - name: MokoStandards module validation - run: | - if [ -x "vendor/bin/validate-module" ]; then - vendor/bin/validate-module --path . 2>&1 | tee /tmp/validate.log - EXIT=${PIPESTATUS[0]} - else - echo "validate-module not in vendor/bin — running from MokoStandards" - php /tmp/mokostandards-api/bin/validate-module --path . 2>&1 | tee /tmp/validate.log - EXIT=${PIPESTATUS[0]} - fi - echo "### Module Validation" >> $GITHUB_STEP_SUMMARY - if [ $EXIT -eq 0 ]; then - echo "All validation checks passed." >> $GITHUB_STEP_SUMMARY - else - echo "Validation failures — see log below." >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - cat /tmp/validate.log >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - fi - exit $EXIT - - - name: PHP CodeSniffer - run: | - if [ -f "phpcs.xml" ]; then - if [ -x "vendor/bin/phpcs" ]; then - vendor/bin/phpcs --standard=phpcs.xml src/ htdocs/ || true - elif [ -x "/tmp/mokostandards-api/vendor/bin/phpcs" ]; then - /tmp/mokostandards-api/vendor/bin/phpcs --standard=phpcs.xml src/ htdocs/ || true - fi - else - STANDARD="" - if [ -f "vendor/mokoconsulting-tech/enterprise/phpcs.xml" ]; then - STANDARD="vendor/mokoconsulting-tech/enterprise/phpcs.xml" - elif [ -f "/tmp/mokostandards-api/phpcs.xml" ]; then - STANDARD="/tmp/mokostandards-api/phpcs.xml" - fi - if [ -n "$STANDARD" ]; then - DIRS="" - for DIR in src/ htdocs/; do - [ -d "$DIR" ] && DIRS="$DIRS $DIR" - done - if [ -n "$DIRS" ]; then - if [ -x "vendor/bin/phpcs" ]; then - vendor/bin/phpcs --standard="$STANDARD" $DIRS || true - elif [ -x "/tmp/mokostandards-api/vendor/bin/phpcs" ]; then - /tmp/mokostandards-api/vendor/bin/phpcs --standard="$STANDARD" $DIRS || true - fi - fi - fi - fi - - - name: PHPStan static analysis - run: | - DIRS="" - for DIR in src/ htdocs/; do - [ -d "$DIR" ] && DIRS="$DIRS $DIR" - done - if [ -z "$DIRS" ]; then - echo "No src/ or htdocs/ directories found — skipping PHPStan" - exit 0 - fi - if [ -f "phpstan.neon" ]; then - if [ -x "vendor/bin/phpstan" ]; then - vendor/bin/phpstan analyse -c phpstan.neon $DIRS || true - elif [ -x "/tmp/mokostandards-api/vendor/bin/phpstan" ]; then - /tmp/mokostandards-api/vendor/bin/phpstan analyse -c phpstan.neon $DIRS || true - fi - else - CONFIG="" - if [ -f "vendor/mokoconsulting-tech/enterprise/phpstan.neon" ]; then - CONFIG="vendor/mokoconsulting-tech/enterprise/phpstan.neon" - elif [ -f "/tmp/mokostandards-api/phpstan.neon" ]; then - CONFIG="/tmp/mokostandards-api/phpstan.neon" - fi - if [ -n "$CONFIG" ]; then - if [ -x "vendor/bin/phpstan" ]; then - vendor/bin/phpstan analyse -c "$CONFIG" $DIRS || true - elif [ -x "/tmp/mokostandards-api/vendor/bin/phpstan" ]; then - /tmp/mokostandards-api/vendor/bin/phpstan analyse -c "$CONFIG" $DIRS || true - fi - fi - fi - - release-readiness: - name: Release Readiness Check - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' && github.base_ref == 'main' - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Validate module descriptor version - run: | - echo "## Release Readiness" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - # Extract version from README.md - README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1) - if [ -z "$README_VERSION" ]; then - echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "README version: \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY - fi - - # Check module descriptor exists and has a version - MOD_FILE="" - for DIR in src htdocs; do - FOUND=$(find "$DIR" -path "*/core/modules/mod*.class.php" -print -quit 2>/dev/null) - if [ -n "$FOUND" ]; then - MOD_FILE="$FOUND" - break - fi - done - - if [ -z "$MOD_FILE" ]; then - echo "No module descriptor found (src|htdocs/core/modules/mod*.class.php)." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - MOD_VERSION=$(grep -oP "\\\$this->version\s*=\s*['\"]\\K[^'\"]*" "$MOD_FILE" | head -1) - echo "Module descriptor: \`${MOD_FILE}\`" >> $GITHUB_STEP_SUMMARY - - # On dev branches, version should be 'development' — on PRs to main it should match README - if [ "$MOD_VERSION" = "development" ]; then - echo "Module version is \`development\` — auto-release will update to \`${README_VERSION}\` on merge." >> $GITHUB_STEP_SUMMARY - elif [ "$MOD_VERSION" != "$README_VERSION" ] && [ -n "$README_VERSION" ]; then - echo "Module version \`${MOD_VERSION}\` differs from README \`${README_VERSION}\` — auto-release will fix this on merge." >> $GITHUB_STEP_SUMMARY - else - echo "Module version: \`${MOD_VERSION}\`" >> $GITHUB_STEP_SUMMARY - fi - fi - - # Check CHANGELOG.md exists and has content - if [ -f "CHANGELOG.md" ] || [ -f "src/ChangeLog.md" ]; then - echo "Changelog present." >> $GITHUB_STEP_SUMMARY - else - echo "No CHANGELOG.md found — consider adding one." >> $GITHUB_STEP_SUMMARY - fi - - # Check module_number is set (Dolibarr module ID) - if [ -n "$MOD_FILE" ]; then - MOD_NUMBER=$(grep -oP "module_number\s*=\s*\\K[0-9]+" "$MOD_FILE" 2>/dev/null || \ - grep -oP "\\\$this->numero\s*=\s*\\K[0-9]+" "$MOD_FILE" 2>/dev/null) - if [ -n "$MOD_NUMBER" ] && [ "$MOD_NUMBER" -gt 0 ] 2>/dev/null; then - echo "Module number: \`${MOD_NUMBER}\`" >> $GITHUB_STEP_SUMMARY - else - echo "Module number not set or is 0 — request one via issue template." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ $ERRORS -gt 0 ]; then - echo "**${ERRORS} issue(s) must be resolved before release.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**Module is ready for release.**" >> $GITHUB_STEP_SUMMARY - fi - - test: - name: Tests (PHP ${{ matrix.php }}) - runs-on: ubuntu-latest - needs: lint-and-validate - - strategy: - fail-fast: false - matrix: - php: ['8.1', '8.2', '8.3'] - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup PHP ${{ matrix.php }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0 - with: - php-version: ${{ matrix.php }} - extensions: mbstring, xml, zip, gd, curl, json - tools: composer:v2 - coverage: none - - - name: Install dependencies - env: - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - if [ -f "composer.json" ]; then - composer install \ - --no-interaction \ - --prefer-dist \ - --optimize-autoloader - else - echo "No composer.json found — skipping dependency install" - fi - - - name: Run tests - run: | - echo "### Test Results (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY - if [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then - vendor/bin/phpunit --testdox 2>&1 | tee /tmp/test-output.log - EXIT=${PIPESTATUS[0]} - if [ $EXIT -eq 0 ]; then - echo "All tests passed." >> $GITHUB_STEP_SUMMARY - else - echo "Test failures detected — see log." >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - cat /tmp/test-output.log >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - fi - exit $EXIT - else - echo "No phpunit.xml found — skipping tests." >> $GITHUB_STEP_SUMMARY - fi diff --git a/templates/workflows/dolibarr/index.md b/templates/workflows/dolibarr/index.md deleted file mode 100644 index f52d3fc..0000000 --- a/templates/workflows/dolibarr/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# Docs Index: /templates/workflows/dolibarr - -## Purpose - -This directory contains GitHub Actions workflow templates specifically designed for Dolibarr module development. - -## Available Templates - -- **ci.yml** - Continuous integration workflow for Dolibarr modules with structure validation, PHP syntax checking, and security checks -- **test.yml** - Automated testing workflow with PHPUnit tests and Dolibarr environment integration - -## Metadata - -- **Document Type:** index -- **Auto-generated:** This file is manually created - -## Revision History - -| Change | Notes | Author | -| --- | --- | --- | -| Initial creation | Created with Dolibarr workflow templates | Moko Consulting | diff --git a/templates/workflows/dolibarr/publish-to-mokodolimods.yml.template b/templates/workflows/dolibarr/publish-to-mokodolimods.yml.template deleted file mode 100644 index 8ba13e7..0000000 --- a/templates/workflows/dolibarr/publish-to-mokodolimods.yml.template +++ /dev/null @@ -1,259 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow.Dolibarr -# INGROUP: MokoStandards.Deploy -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/dolibarr/publish-to-mokodolimods.yml.template -# VERSION: 04.06.00 -# BRIEF: On release, copies src/ into htdocs/custom/$DEV_FTP_SUFFIX in mokodolimods and opens a PR -# -# Required repo variable: DEV_FTP_SUFFIX — subdirectory name under htdocs/custom/ (e.g. moko-mymodule) -# Required org secret: GH_TOKEN — must have write access to mokoconsulting-tech/mokodolimods -# Target repository: mokoconsulting-tech/mokodolimods - -name: Publish to MokoDoliMods - -on: - release: - types: [published] - -permissions: - contents: read - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - publish: - name: "Publish → mokodolimods/htdocs/custom/${{ vars.DEV_FTP_SUFFIX }}" - runs-on: ubuntu-latest - - steps: - - name: Validate DEV_FTP_SUFFIX - env: - DEV_FTP_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }} - run: | - if [ -z "$DEV_FTP_SUFFIX" ]; then - echo "❌ DEV_FTP_SUFFIX repository variable is not set." - echo " Set it to the subdirectory name under htdocs/custom/ in mokodolimods." - echo " Example: moko-mymodule" - exit 1 - fi - echo "✅ DEV_FTP_SUFFIX: ${DEV_FTP_SUFFIX}" - - - name: Checkout module repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - path: module - - - name: Validate src/ directory - id: source - run: | - if [ ! -d "module/src" ]; then - echo "❌ No src/ directory found in this repository — nothing to publish." - exit 1 - fi - COUNT=$(find module/src -type f | wc -l) - echo "✅ src/ contains ${COUNT} file(s)" - echo "count=${COUNT}" >> "$GITHUB_OUTPUT" - - - name: Dry-run — preview files that will be published - id: dryrun - env: - DEV_FTP_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }} - FTP_IGNORE: ${{ vars.FTP_IGNORE }} - run: | - DEST="htdocs/custom/${DEV_FTP_SUFFIX}" - - # ── Parse FTP_IGNORE patterns ───────────────────────────────────────── - IGNORE_PATTERNS=() - if [ -n "$FTP_IGNORE" ]; then - while IFS= read -r -d ',' token; do - pattern=$(echo "$token" | sed 's/^[[:space:]]*"//;s/"[[:space:]]*$//') - [ -n "$pattern" ] && IGNORE_PATTERNS+=("$pattern") - done <<< "${FTP_IGNORE}," - fi - - # ── Collect files, applying FTP_IGNORE and .gitignore ──────────────── - WOULD_PUBLISH=() - IGNORED_FILES=() - while IFS= read -r -d '' file; do - rel="${file#module/src/}" - - # Check FTP_IGNORE - SKIP=false - for pat in "${IGNORE_PATTERNS[@]}"; do - if echo "$rel" | grep -qE "$pat" 2>/dev/null; then - IGNORED_FILES+=("$rel (FTP_IGNORE: $pat)") - SKIP=true - break - fi - done - $SKIP && continue - - # Check .gitignore - if [ -f "module/.gitignore" ] && git -C module check-ignore -q "$rel" 2>/dev/null; then - IGNORED_FILES+=("$rel (.gitignore)") - continue - fi - - WOULD_PUBLISH+=("$rel") - done < <(find module/src -type f -print0 | sort -z) - - PUBLISH_COUNT="${#WOULD_PUBLISH[@]}" - IGNORE_COUNT="${#IGNORED_FILES[@]}" - - echo "publish_count=${PUBLISH_COUNT}" >> "$GITHUB_OUTPUT" - echo "ignore_count=${IGNORE_COUNT}" >> "$GITHUB_OUTPUT" - - # ── Write step summary ──────────────────────────────────────────────── - { - echo "## 🔍 Publish Dry-Run Summary" - echo "" - echo "| Field | Value |" - echo "|---|---|" - echo "| Destination | \`${DEST}/\` |" - echo "| Would publish | **${PUBLISH_COUNT}** file(s) |" - echo "| Ignored | **${IGNORE_COUNT}** file(s) |" - echo "" - - if [ "${PUBLISH_COUNT}" -gt 0 ]; then - echo "### 📂 Files that would be published" - echo "" - echo '```' - printf '%s\n' "${WOULD_PUBLISH[@]}" - echo '```' - echo "" - fi - - if [ "${IGNORE_COUNT}" -gt 0 ]; then - echo "### ⏭️ Files excluded" - echo "" - echo "| File | Reason |" - echo "|---|---|" - for entry in "${IGNORED_FILES[@]}"; do - file="${entry% (*}" - reason="${entry##* (}"; reason="${reason%)}" - echo "| \`${file}\` | ${reason} |" - done - echo "" - fi - } >> "$GITHUB_STEP_SUMMARY" - - echo "✅ Dry-run complete: ${PUBLISH_COUNT} would publish, ${IGNORE_COUNT} ignored" - - - name: Checkout mokodolimods repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - repository: mokoconsulting-tech/mokodolimods - token: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - path: mokodolimods - - - name: Create release branch - id: branch - run: | - MODULE="${{ github.event.repository.name }}" - TAG="${{ github.event.release.tag_name }}" - BRANCH="release/${MODULE}-${TAG}" - - cd mokodolimods - git config user.email "automation@mokoconsulting.tech" - git config user.name "Moko Standards Bot" - git checkout -b "$BRANCH" - - echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT" - echo "module=${MODULE}" >> "$GITHUB_OUTPUT" - echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - echo "ℹ️ Release branch: ${BRANCH}" - - - name: Copy src/ to htdocs/custom/${{ vars.DEV_FTP_SUFFIX }} - id: copy - env: - DEV_FTP_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }} - run: | - DEST="mokodolimods/htdocs/custom/${DEV_FTP_SUFFIX}" - mkdir -p "$DEST" - - # --delete removes files in dest that no longer exist in src/ - rsync -av --delete module/src/ "$DEST/" - - FILE_COUNT=$(find "$DEST" -type f | wc -l) - echo "dest=${DEST}" >> "$GITHUB_OUTPUT" - echo "✅ Synced to ${DEST} (${FILE_COUNT} file(s))" - - - name: Commit changes - id: commit - run: | - cd mokodolimods - git add -A - - if git diff --staged --quiet; then - echo "ℹ️ No changes to commit — src/ is already up to date in mokodolimods." - echo "changed=false" >> "$GITHUB_OUTPUT" - else - git commit -m "feat(${{ steps.branch.outputs.module }}): update to ${{ steps.branch.outputs.tag }}" - echo "changed=true" >> "$GITHUB_OUTPUT" - fi - - - name: Push release branch - if: steps.commit.outputs.changed == 'true' - run: | - cd mokodolimods - git push origin "${{ steps.branch.outputs.branch }}" - - - name: Create pull request on mokodolimods - if: steps.commit.outputs.changed == 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - MODULE="${{ steps.branch.outputs.module }}" - TAG="${{ steps.branch.outputs.tag }}" - DEV_FTP_SUFFIX="${{ vars.DEV_FTP_SUFFIX }}" - RELEASE_URL="${{ github.event.release.html_url }}" - RELEASE_BODY="${{ github.event.release.body }}" - - gh pr create \ - --repo mokoconsulting-tech/mokodolimods \ - --head "${{ steps.branch.outputs.branch }}" \ - --base main \ - --title "feat(${DEV_FTP_SUFFIX}): ${TAG}" \ - --body "## 📦 Module Release - - **Module:** \`${MODULE}\` - **Version:** \`${TAG}\` - **Destination:** \`htdocs/custom/${DEV_FTP_SUFFIX}/\` - **Source release:** ${RELEASE_URL} - - ### Release Notes - - ${RELEASE_BODY} - - --- - *This PR was created automatically by the \`publish-to-mokodolimods\` workflow.*" - - - name: Summary - if: always() - run: | - MODULE="${{ steps.branch.outputs.module }}" - TAG="${{ steps.branch.outputs.tag }}" - CHANGED="${{ steps.commit.outputs.changed }}" - - echo "### 📦 Publish to MokoDoliMods" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "| Field | Value |" >> "$GITHUB_STEP_SUMMARY" - echo "|-------|-------|" >> "$GITHUB_STEP_SUMMARY" - echo "| Module | \`${MODULE}\` |" >> "$GITHUB_STEP_SUMMARY" - echo "| Tag | \`${TAG}\` |" >> "$GITHUB_STEP_SUMMARY" - echo "| Destination | \`htdocs/custom/${{ vars.DEV_FTP_SUFFIX }}/\` |" >> "$GITHUB_STEP_SUMMARY" - echo "| Files synced | ${{ steps.source.outputs.count }} |" >> "$GITHUB_STEP_SUMMARY" - - if [ "$CHANGED" = "true" ]; then - echo "| PR | ✅ Created |" >> "$GITHUB_STEP_SUMMARY" - elif [ "$CHANGED" = "false" ]; then - echo "| PR | ℹ️ Skipped (no changes) |" >> "$GITHUB_STEP_SUMMARY" - else - echo "| PR | ❌ Not reached |" >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/templates/workflows/dolibarr/release-guide.md b/templates/workflows/dolibarr/release-guide.md deleted file mode 100644 index af24460..0000000 --- a/templates/workflows/dolibarr/release-guide.md +++ /dev/null @@ -1,379 +0,0 @@ -# Dolibarr Release Workflow - -## Overview - -The Dolibarr release workflow (`templates/workflows/dolibarr/release.yml`) automates the release process for Dolibarr modules following the standardized release cycle: **main > dev > rc > version > main**. - -## Features - -- **Automated Build**: Creates ZIP packages with proper module structure -- **Version Management**: Updates version in module descriptors -- **Development Version Support**: Automatically skips release builds when VERSION is set to "development" on main branch -- **Checksum Generation**: Creates SHA256 and MD5 checksums -- **GitHub Releases**: Automatically creates releases with artifacts -- **FTP/SFTP Upload**: Uploads RC and stable releases to FTP/SFTP servers (optional) -- **Pre-release Support**: RC releases are marked as pre-releases -- **Changelog Integration**: Extracts version-specific changelog entries - -## Development Version - -### Overview - -For Dolibarr modules on the **main** branch, the VERSION field in README.md can be set to `development` instead of a specific version number. This indicates that the code is in active development and should not trigger automatic release builds. - -### Benefits - -- **Clear Intent**: Developers immediately know the code is in development -- **No Accidental Releases**: Prevents automatic release creation from main branch pushes -- **Explicit Releases**: Forces use of workflow_dispatch or version tags for releases - -### Usage - -1. **Set Development Version** in README.md: - ```markdown - VERSION: development - ``` - -2. **Push to Main**: The workflow will detect "development" and skip building: - ```bash - git push origin main - # Workflow will show: "Skipping release build for development version" - ``` - -3. **Create Release**: Use one of these methods: - - **Workflow Dispatch**: Manually trigger with specific version - - **Version Tag**: Push a version tag (e.g., `v1.0.0`) - -### Example Workflow - -```markdown -# In README.md during development -VERSION: development - -# When ready to release: -# 1. Update README.md -VERSION: 04.06.00 - -# 2. Commit and tag -git add README.md -git commit -m "chore: prepare v1.0.0 release" -git tag v1.0.0 -git push origin main --tags - -# 3. After release, set back to development -VERSION: development -git add README.md -git commit -m "chore: set version back to development" -git push origin main -``` - -## Release Cycle - -The workflow supports the following release cycle: - -1. **main**: Stable production releases (or development if VERSION: development) -2. **dev/\***: Development branches (no automatic releases) -3. **rc/\***: Release candidate branches (marked as pre-releases) -4. **version/\***: Version branches for maintenance -5. **Tags (v\*._.\_)**: Explicit version tags - -### Pre-release Marking - -- RC branches (`rc/**`) automatically marked as pre-release -- RC tags (e.g., `v1.0.0-rc1`) automatically marked as pre-release -- Manual workflow dispatch allows explicit pre-release flag - -## Workflow Triggers - -### Automatic Triggers - -```yaml -on: - push: - branches: - - 'main' # Production releases (skipped if VERSION: development) - - 'rc/**' # Release candidates (pre-release) - tags: - - 'v*.*.*' # Version tags -``` - -### Manual Trigger - -```yaml -workflow_dispatch: - inputs: - version: 'Release version (e.g., 1.0.0)' - prerelease: 'Mark as pre-release (use for RC releases)' -``` - -## Usage - -### Automatic Release on Tag Push - -```bash -# Create and push a version tag -git tag v1.0.0 -git push origin v1.0.0 - -# For RC releases -git tag v1.0.0-rc1 -git push origin v1.0.0-rc1 # Automatically marked as pre-release -``` - -### Manual Release via Workflow Dispatch - -1. Go to Actions → Create Release -2. Click "Run workflow" -3. Enter version (e.g., `1.0.0`) -4. Check "prerelease" for RC releases -5. Click "Run workflow" - -### RC Branch Workflow - -```bash -# Create RC branch -git checkout -b rc/1.0.0 -git push origin rc/1.0.0 - -# Pushes to rc/* branches trigger pre-release builds -``` - -## Package Structure - -The workflow creates the following artifacts: - -``` -build/ -├── ModuleName-1.0.0.zip # Release package -├── ModuleName-1.0.0.zip.sha256 # SHA256 checksum -└── ModuleName-1.0.0.zip.md5 # MD5 checksum -``` - -### Excluded Files - -The package excludes development and build artifacts: - -- `build/`, `tests/`, `.git*`, `.github/` -- `composer.json`, `composer.lock` -- `phpunit.xml*`, `phpcs.xml*`, `phpstan.neon*`, `psalm.xml*` -- `node_modules/`, `package*.json` - -## Module Name Detection - -The workflow automatically detects the module name from: - -1. Module descriptor file (`core/modules/mod*.class.php`) -2. Repository name (removes `MokoDoli` or `dolibarr-` prefix) - -## Version Update - -The workflow updates the version in: - -- Module descriptor: `$this->version = 'X.Y.Z'` in `core/modules/mod*.class.php` - -## Changelog Integration - -If `CHANGELOG.md` exists, the workflow: - -1. Extracts changelog section for the release version -2. Includes it in the GitHub release notes -3. Falls back to default message if section not found - -Example `CHANGELOG.md` format: - -```markdown -## [1.0.0] - 2026-01-09 - -### Added - -- New QR code generation feature -- Enhanced PDF export - -### Fixed - -- Fixed encoding issue in QR codes -``` - -## GitHub Release - -The workflow creates a GitHub release with: - -- **Tag**: `v{version}` (e.g., `v1.0.0`) -- **Name**: `Release {version}` -- **Body**: Extracted from CHANGELOG.md -- **Assets**: ZIP package, SHA256, MD5 -- **Pre-release**: Automatically set for RC releases -- **Draft**: Always false (published immediately) - -## FTP/SFTP Upload - -### Overview - -RC and stable releases are automatically uploaded to Release System (RS) FTP/SFTP servers for distribution. This feature is optional and only activates when RS_FTP credentials are configured. - -### Configuration - -The workflow supports both password and SSH key authentication. Configure the following secrets and variables in your repository or organization settings: - -**Required Secrets:** -- `RS_FTP_HOST` - SFTP server hostname (e.g., `sftp.example.com`) -- `RS_FTP_USER` - SFTP username -- `RS_FTP_PATH` - Base path on server (variable, e.g., `/var/www/releases`) - -**Authentication (choose one):** -- `RS_FTP_PASSWORD` - Password authentication (simple) -- `RS_FTP_KEY` - SSH private key authentication (recommended) - -**Optional:** -- `RS_FTP_PORT` - SFTP port (default: 22) -- `RS_FTP_PATH_SUFFIX` - Additional path suffix (variable, e.g., `/dolibarr`) - -### Upload Behavior - -The workflow automatically determines the upload channel: -- **RC releases** (`prerelease: true`) → uploaded to `{RS_FTP_PATH}/{RS_FTP_PATH_SUFFIX}/rc/` -- **Stable releases** (`prerelease: false`) → uploaded to `{RS_FTP_PATH}/{RS_FTP_PATH_SUFFIX}/stable/` - -### Examples - -**Password Authentication:** -``` -RS_FTP_HOST: sftp.example.com -RS_FTP_USER: deploy-user -RS_FTP_PASSWORD: secure-password -RS_FTP_PATH: /var/www/releases (variable) -RS_FTP_PATH_SUFFIX: /dolibarr (variable) - -# RC release uploads to: /var/www/releases/dolibarr/rc/ -# Stable release uploads to: /var/www/releases/dolibarr/stable/ -``` - -**SSH Key Authentication:** -``` -RS_FTP_HOST: sftp.example.com -RS_FTP_USER: deploy-user -RS_FTP_KEY: -----BEGIN OPENSSH PRIVATE KEY-----... -RS_FTP_PATH: /var/www/releases (variable) - -# Uploads to: /var/www/releases/rc/ or /var/www/releases/stable/ -``` - -### Skipping FTP Upload - -FTP upload is automatically skipped if: -- RS_FTP credentials are not configured -- RS_FTP_HOST secret is empty -- Required authentication credentials are missing - -The workflow will continue and create the GitHub release even if FTP upload fails or is skipped. - -## Command-Line Script - -A companion Python script (`scripts/release/dolibarr_release.py`) allows local package creation: - -### Installation - -```bash -# Script is ready to use, no installation needed -chmod +x scripts/release/dolibarr_release.py -``` - -### Usage - -```bash -# Create release for current directory -python scripts/release/dolibarr_release.py --version 1.0.0 - -# Create release for specific module -python scripts/release/dolibarr_release.py \ - --module-dir /path/to/module \ - --version 1.0.0 - -# Create release without updating version -python scripts/release/dolibarr_release.py \ - --version 1.0.0 \ - --no-update-version - -# Specify custom output directory -python scripts/release/dolibarr_release.py \ - --version 1.0.0 \ - --output-dir /tmp/releases -``` - -### Script Features - -- Detects module name from descriptor -- Updates version in module files -- Creates properly structured ZIP package -- Generates checksums (SHA256, MD5) -- Excludes development files -- Validates version format - -## Configuration - -### Required Secrets - -None required for basic GitHub Releases. - -### Optional Secrets - -- `MARKETPLACE_TOKEN`: For Dolistore publishing (future enhancement) - -## Best Practices - -1. **Always use semantic versioning**: `X.Y.Z` format -2. **Update CHANGELOG.md**: Before creating releases -3. **Use RC branches**: For testing before production release -4. **Tag from main**: Ensure main branch is stable before tagging -5. **Test locally**: Use the script to test package creation - -## Troubleshooting - -### Module Name Not Detected - -- Ensure `core/modules/mod*.class.php` exists -- Check repository name follows `MokoDoli*` or `dolibarr-*` convention - -### Version Not Updated - -- Verify module descriptor syntax: `$this->version = 'X.Y.Z'` -- Check file permissions and encoding - -### Package Missing Files - -- Review exclusion list in workflow -- Verify file paths are correct - -## Example: Full Release Process - -```bash -# 1. Update version and changelog -vim CHANGELOG.md # Add [1.0.0] section -git add CHANGELOG.md -git commit -m "docs: prepare v1.0.0 release" - -# 2. Create RC for testing -git checkout -b rc/1.0.0 -git push origin rc/1.0.0 # Triggers pre-release build - -# 3. Test RC release -# ... test the RC package ... - -# 4. Merge to main and tag -git checkout main -git merge rc/1.0.0 -git tag v1.0.0 -git push origin main -git push origin v1.0.0 # Triggers production release - -# 5. Verify release -# Check GitHub Releases page for artifacts -``` - -## Support - -For issues or questions: - -- Open issue in MokoStandards repository -- Tag with `workflow-template` label -- Include module name and error details diff --git a/templates/workflows/dolibarr/repo_health.yml.template b/templates/workflows/dolibarr/repo_health.yml.template deleted file mode 100644 index 2d118e1..0000000 --- a/templates/workflows/dolibarr/repo_health.yml.template +++ /dev/null @@ -1,777 +0,0 @@ -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Validation -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /.github/workflows/repo_health.yml -# VERSION: 04.06.00 -# BRIEF: Dolibarr module health checks — validates release config, module descriptor, repo artifacts, and scripts governance. -# NOTE: Field is user-managed. -# ============================================================================ - -name: Repo Health - -concurrency: - group: repo-health-${{ github.repository }}-${{ github.ref }} - cancel-in-progress: true - -defaults: - run: - shell: bash - -on: - workflow_dispatch: - inputs: - profile: - description: 'Validation profile: all, release, scripts, or repo' - required: true - default: all - type: choice - options: - - all - - release - - scripts - - repo - pull_request: - push: - -permissions: - contents: read - -env: - # Release policy - Repository Variables Only - RELEASE_REQUIRED_REPO_VARS: RS_FTP_PATH_SUFFIX - RELEASE_OPTIONAL_REPO_VARS: DEV_FTP_SUFFIX - - # Scripts governance policy - # Note: directories listed without a trailing slash. - SCRIPTS_REQUIRED_DIRS: - SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate - - # Repo health policy - # Files are listed as-is; directories must end with a trailing slash. - REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.github/workflows/ - REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,docs/,update.txt - REPO_DISALLOWED_DIRS: - REPO_DISALLOWED_FILES: TODO.md,todo.md,update.json - - # Extended checks toggles - EXTENDED_CHECKS: "true" - - # File / directory variables (moved to top-level env) - DOCS_INDEX: docs/docs-index.md - SCRIPT_DIR: scripts - WORKFLOWS_DIR: .github/workflows - SHELLCHECK_PATTERN: '*.sh' - SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml' - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - access_check: - name: Access control - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: read - - outputs: - allowed: ${{ steps.perm.outputs.allowed }} - permission: ${{ steps.perm.outputs.permission }} - - steps: - - name: Check actor permission (admin only) - id: perm - env: - TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - REPO: ${{ github.repository }} - ACTOR: ${{ github.actor }} - run: | - set -euo pipefail - ALLOWED=false - PERMISSION=unknown - METHOD="" - - # Hardcoded authorized users — always allowed - case "$ACTOR" in - jmiller-moko|github-actions\[bot\]) - ALLOWED=true - PERMISSION=admin - METHOD="hardcoded allowlist" - ;; - *) - # Detect platform and check permissions via API - API_BASE="${GITHUB_API_URL:-${GITEA_API_URL:-https://api.github.com}}" - RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \ - "${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}') - PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown") - if [ "$PERMISSION" = "admin" ] || [ "$PERMISSION" = "maintain" ] || [ "$PERMISSION" = "owner" ]; then - ALLOWED=true - fi - METHOD="collaborator API" - ;; - esac - - echo "permission=${PERMISSION}" >> "$GITHUB_OUTPUT" - echo "allowed=${ALLOWED}" >> "$GITHUB_OUTPUT" - - { - echo "## 🔐 Access Authorization" - echo "" - echo "| Field | Value |" - echo "|-------|-------|" - echo "| **Actor** | \`${ACTOR}\` |" - echo "| **Repository** | \`${REPO}\` |" - echo "| **Permission** | \`${PERMISSION}\` |" - echo "| **Method** | ${METHOD} |" - echo "| **Authorized** | ${ALLOWED} |" - echo "" - if [ "$ALLOWED" = "true" ]; then - echo "✅ ${ACTOR} authorized (${METHOD})" - else - echo "❌ ${ACTOR} is NOT authorized. Requires admin or maintain role." - fi - } >> "${GITHUB_STEP_SUMMARY}" - - - name: Deny execution when not permitted - if: ${{ steps.perm.outputs.allowed != 'true' }} - run: | - set -euo pipefail - printf '%s\n' 'ERROR: Access denied. Admin permission required.' >> "${GITHUB_STEP_SUMMARY}" - exit 1 - - release_config: - name: Release configuration - needs: access_check - if: ${{ needs.access_check.outputs.allowed == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Guardrails release vars - env: - PROFILE_RAW: ${{ github.event.inputs.profile }} - RS_FTP_PATH_SUFFIX: ${{ vars.RS_FTP_PATH_SUFFIX }} - DEV_FTP_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }} - run: | - set -euo pipefail - - profile="${PROFILE_RAW:-all}" - case "${profile}" in - all|release|scripts|repo) ;; - *) - printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" - exit 1 - ;; - esac - - if [ "${profile}" = 'scripts' ] || [ "${profile}" = 'repo' ]; then - { - printf '%s\n' '### Release configuration (Repository Variables)' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' 'Status: SKIPPED' - printf '%s\n' 'Reason: profile excludes release validation' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - IFS=',' read -r -a required <<< "${RELEASE_REQUIRED_REPO_VARS}" - IFS=',' read -r -a optional <<< "${RELEASE_OPTIONAL_REPO_VARS}" - - missing=() - missing_optional=() - - for k in "${required[@]}"; do - v="${!k:-}" - [ -z "${v}" ] && missing+=("${k}") - done - - for k in "${optional[@]}"; do - v="${!k:-}" - [ -z "${v}" ] && missing_optional+=("${k}") - done - - { - printf '%s\n' '### Release configuration (Repository Variables)' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' '| Variable | Status |' - printf '%s\n' '|---|---|' - printf '%s\n' "| RS_FTP_PATH_SUFFIX | ${RS_FTP_PATH_SUFFIX:-NOT SET} |" - printf '%s\n' "| DEV_FTP_SUFFIX | ${DEV_FTP_SUFFIX:-NOT SET} |" - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - if [ "${#missing_optional[@]}" -gt 0 ]; then - { - printf '%s\n' '### Missing optional repository variables' - for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - if [ "${#missing[@]}" -gt 0 ]; then - { - printf '%s\n' '### Missing required repository variables' - for m in "${missing[@]}"; do printf '%s\n' "- ${m}"; done - printf '%s\n' 'ERROR: Guardrails failed. Missing required repository variables.' - } >> "${GITHUB_STEP_SUMMARY}" - exit 1 - fi - - { - printf '%s\n' '### Repository variables validation result' - printf '%s\n' 'Status: OK' - printf '%s\n' 'All required repository variables present.' - printf '%s\n' '' - printf '%s\n' '**Note**: Organization secrets (RS_FTP_HOST, RS_FTP_USER, etc.) are validated at deployment time, not in repository health checks.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - scripts_governance: - name: Scripts governance - needs: access_check - if: ${{ needs.access_check.outputs.allowed == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Scripts folder checks - env: - PROFILE_RAW: ${{ github.event.inputs.profile }} - run: | - set -euo pipefail - - profile="${PROFILE_RAW:-all}" - case "${profile}" in - all|release|scripts|repo) ;; - *) - printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" - exit 1 - ;; - esac - - if [ "${profile}" = 'release' ] || [ "${profile}" = 'repo' ]; then - { - printf '%s\n' '### Scripts governance' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' 'Status: SKIPPED' - printf '%s\n' 'Reason: profile excludes scripts governance' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - if [ ! -d "${SCRIPT_DIR}" ]; then - { - printf '%s\n' '### Scripts governance' - printf '%s\n' 'Status: OK (advisory)' - printf '%s\n' 'scripts/ directory not present. No scripts governance enforced.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}" - IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}" - - missing_dirs=() - unapproved_dirs=() - - for d in "${required_dirs[@]}"; do - req="${d%/}" - [ ! -d "${req}" ] && missing_dirs+=("${req}/") - done - - while IFS= read -r d; do - allowed=false - for a in "${allowed_dirs[@]}"; do - a_norm="${a%/}" - [ "${d%/}" = "${a_norm}" ] && allowed=true - done - [ "${allowed}" = false ] && unapproved_dirs+=("${d%/}/") - done < <(find "${SCRIPT_DIR}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sed 's#^\./##') - - { - printf '%s\n' '### Scripts governance' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' '| Area | Status | Notes |' - printf '%s\n' '|---|---|---|' - - if [ "${#missing_dirs[@]}" -gt 0 ]; then - printf '%s\n' '| Required directories | Warning | Missing required subfolders |' - else - printf '%s\n' '| Required directories | OK | All required subfolders present |' - fi - - if [ "${#unapproved_dirs[@]}" -gt 0 ]; then - printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |' - else - printf '%s\n' '| Directory policy | OK | No unapproved directories |' - fi - - printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |' - printf '\n' - - if [ "${#missing_dirs[@]}" -gt 0 ]; then - printf '%s\n' 'Missing required script directories:' - for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - else - printf '%s\n' 'Missing required script directories: none.' - printf '\n' - fi - - if [ "${#unapproved_dirs[@]}" -gt 0 ]; then - printf '%s\n' 'Unapproved script directories detected:' - for m in "${unapproved_dirs[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - else - printf '%s\n' 'Unapproved script directories detected: none.' - printf '\n' - fi - - printf '%s\n' 'Scripts governance completed in advisory mode.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - repo_health: - name: Repository health - needs: access_check - if: ${{ needs.access_check.outputs.allowed == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Repository health checks - env: - PROFILE_RAW: ${{ github.event.inputs.profile }} - run: | - set -euo pipefail - - profile="${PROFILE_RAW:-all}" - case "${profile}" in - all|release|scripts|repo) ;; - *) - printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" - exit 1 - ;; - esac - - if [ "${profile}" = 'release' ] || [ "${profile}" = 'scripts' ]; then - { - printf '%s\n' '### Repository health' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' 'Status: SKIPPED' - printf '%s\n' 'Reason: profile excludes repository health' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - # Source directory: src/ or htdocs/ (either is valid) - if [ -d "src" ]; then - SOURCE_DIR="src" - elif [ -d "htdocs" ]; then - SOURCE_DIR="htdocs" - else - missing_required+=("src/ or htdocs/ (source directory required)") - fi - - IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}" - IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}" - IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}" - IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES}" - - missing_required=() - missing_optional=() - - for item in "${required_artifacts[@]}"; do - if printf '%s' "${item}" | grep -q '/$'; then - d="${item%/}" - [ ! -d "${d}" ] && missing_required+=("${item}") - else - [ ! -f "${item}" ] && missing_required+=("${item}") - fi - done - - # Optional entries: handle files and directories (trailing slash indicates dir) - for f in "${optional_files[@]}"; do - if printf '%s' "${f}" | grep -q '/$'; then - d="${f%/}" - [ ! -d "${d}" ] && missing_optional+=("${f}") - else - [ ! -f "${f}" ] && missing_optional+=("${f}") - fi - done - - for d in "${disallowed_dirs[@]}"; do - d_norm="${d%/}" - [ -d "${d_norm}" ] && missing_required+=("${d_norm}/ (disallowed)") - done - - for f in "${disallowed_files[@]}"; do - [ -f "${f}" ] && missing_required+=("${f} (disallowed)") - done - - git fetch origin --prune - - dev_paths=() - dev_branches=() - - # Look for remote branches matching origin/dev*. - # A plain origin/dev is considered invalid; we require dev/ branches. - while IFS= read -r b; do - name="${b#origin/}" - if [ "${name}" = 'dev' ]; then - dev_branches+=("${name}") - else - dev_paths+=("${name}") - fi - done < <(git branch -r --list 'origin/dev*' | sed 's/^ *//') - - # If there are no dev/* branches, fail the guardrail. - if [ "${#dev_paths[@]}" -eq 0 ]; then - missing_required+=("dev/* branch (e.g. dev/01.00.00)") - fi - - # If a plain dev branch exists (origin/dev), flag it as invalid. - if [ "${#dev_branches[@]}" -gt 0 ]; then - missing_required+=("invalid branch dev (must be dev/)") - fi - - content_warnings=() - - if [ -f 'CHANGELOG.md' ] && ! grep -Eq '^# Changelog' CHANGELOG.md; then - content_warnings+=("CHANGELOG.md missing '# Changelog' header") - fi - - if [ -f 'CHANGELOG.md' ] && grep -Eq '^[# ]*Unreleased' CHANGELOG.md; then - content_warnings+=("CHANGELOG.md contains Unreleased section (review release readiness)") - fi - - if [ -f 'LICENSE' ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then - content_warnings+=("LICENSE does not look like a GPL text") - fi - - if [ -f 'README.md' ] && ! grep -qiE 'moko|Moko' README.md; then - content_warnings+=("README.md missing expected brand keyword") - fi - - export PROFILE_RAW="${profile}" - export MISSING_REQUIRED="$(printf '%s\n' "${missing_required[@]:-}")" - export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")" - export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")" - - report_json="$(python3 - <<'PY' - import json - import os - - profile = os.environ.get('PROFILE_RAW') or 'all' - - missing_required = os.environ.get('MISSING_REQUIRED', '').splitlines() if os.environ.get('MISSING_REQUIRED') else [] - missing_optional = os.environ.get('MISSING_OPTIONAL', '').splitlines() if os.environ.get('MISSING_OPTIONAL') else [] - content_warnings = os.environ.get('CONTENT_WARNINGS', '').splitlines() if os.environ.get('CONTENT_WARNINGS') else [] - - out = { - 'profile': profile, - 'missing_required': [x for x in missing_required if x], - 'missing_optional': [x for x in missing_optional if x], - 'content_warnings': [x for x in content_warnings if x], - } - - print(json.dumps(out, indent=2)) - PY - )" - - { - printf '%s\n' '### Repository health' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' '| Metric | Value |' - printf '%s\n' '|---|---|' - printf '%s\n' "| Missing required | ${#missing_required[@]} |" - printf '%s\n' "| Missing optional | ${#missing_optional[@]} |" - printf '%s\n' "| Content warnings | ${#content_warnings[@]} |" - printf '\n' - - printf '%s\n' '### Guardrails report (JSON)' - printf '%s\n' '```json' - printf '%s\n' "${report_json}" - printf '%s\n' '```' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - if [ "${#missing_required[@]}" -gt 0 ]; then - { - printf '%s\n' '### Missing required repo artifacts' - for m in "${missing_required[@]}"; do printf '%s\n' "- ${m}"; done - printf '%s\n' 'ERROR: Guardrails failed. Missing required repository artifacts.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 1 - fi - - if [ "${#missing_optional[@]}" -gt 0 ]; then - { - printf '%s\n' '### Missing optional repo artifacts' - for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - if [ "${#content_warnings[@]}" -gt 0 ]; then - { - printf '%s\n' '### Repo content warnings' - for m in "${content_warnings[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - # ── Dolibarr-specific checks ────────────────────────────────────── - dolibarr_findings=() - - # Module descriptor: src/core/modules/mod*.class.php - MOD_FILE="$(find src htdocs -path '*/core/modules/mod*.class.php' -print -quit 2>/dev/null || true)" - if [ -z "${MOD_FILE}" ]; then - dolibarr_findings+=("Module descriptor not found (src/core/modules/mod*.class.php)") - else - # Check $this->numero is set and non-zero - if ! grep -qP '\$this->numero\s*=\s*[1-9]' "${MOD_FILE}"; then - dolibarr_findings+=("Module descriptor: \$this->numero not set or is zero") - fi - # Check $this->version is not hardcoded (should be set by workflow) - if grep -qP "\\\$this->version\s*=\s*'[0-9]" "${MOD_FILE}"; then - dolibarr_findings+=("Module descriptor: \$this->version appears hardcoded (should be set by deploy/release workflow)") - fi - # Check url_last_version points to update.txt - if grep -qP 'url_last_version.*update\.json' "${MOD_FILE}"; then - dolibarr_findings+=("Module descriptor: url_last_version points to update.json (must be update.txt)") - fi - # Check url_last_version contains /main/ for main branch - CURRENT_BRANCH="${GITHUB_REF_NAME:-main}" - if [ "${CURRENT_BRANCH}" = "main" ] && ! grep -qP 'url_last_version.*\/main\/' "${MOD_FILE}"; then - dolibarr_findings+=("Module descriptor: url_last_version does not reference /main/ branch") - fi - fi - - # Source README should exist (Dolibarr module store requirement) - if [ -n "${SOURCE_DIR:-}" ] && [ ! -f "${SOURCE_DIR}/README.md" ]; then - dolibarr_findings+=("${SOURCE_DIR}/README.md missing (required for Dolibarr module store)") - fi - - # update.txt should exist in root (created by auto-release) - if [ ! -f 'update.txt' ]; then - dolibarr_findings+=("update.txt missing in root (created by auto-release workflow)") - fi - - if [ "${#dolibarr_findings[@]}" -gt 0 ]; then - { - printf '%s\n' '### Dolibarr module checks' - printf '%s\n' '| Check | Status |' - printf '%s\n' '|---|---|' - for f in "${dolibarr_findings[@]}"; do - printf '%s\n' "| ${f} | Warning |" - done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - else - { - printf '%s\n' '### Dolibarr module checks' - printf '%s\n' 'All Dolibarr-specific checks passed.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - extended_enabled="${EXTENDED_CHECKS:-true}" - extended_findings=() - - if [ "${extended_enabled}" = 'true' ]; then - # CODEOWNERS presence - if [ -f '.github/CODEOWNERS' ] || [ -f 'CODEOWNERS' ] || [ -f 'docs/CODEOWNERS' ]; then - : - else - extended_findings+=("CODEOWNERS not found (.github/CODEOWNERS preferred)") - fi - - # Workflow pinning advisory: flag uses @main/@master - if ls "${WORKFLOWS_DIR}"/*.yml >/dev/null 2>&1 || ls "${WORKFLOWS_DIR}"/*.yaml >/dev/null 2>&1; then - bad_refs="$(grep -RIn --include='*.yml' --include='*.yaml' -E '^[[:space:]]*uses:[[:space:]]*[^#]+@(main|master)\b' "${WORKFLOWS_DIR}" 2>/dev/null || true)" - if [ -n "${bad_refs}" ]; then - extended_findings+=("Workflows reference actions @main/@master (pin versions): see log excerpt") - { - printf '%s\n' '### Workflow pinning advisory' - printf '%s\n' 'Found uses: entries pinned to main/master:' - printf '%s\n' '```' - printf '%s\n' "${bad_refs}" - printf '%s\n' '```' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - # Docs index link integrity (docs/docs-index.md) - if [ -f "${DOCS_INDEX}" ]; then - missing_links="$(python3 - <<'PY' - import os - import re - - idx = os.environ.get('DOCS_INDEX', 'docs/docs-index.md') - base = os.getcwd() - - bad = [] - pat = re.compile(r'\[[^\]]+\]\(([^)]+)\)') - - with open(idx, 'r', encoding='utf-8') as f: - for line in f: - for m in pat.findall(line): - link = m.strip() - if link.startswith('http://') or link.startswith('https://') or link.startswith('#') or link.startswith('mailto:'): - continue - if link.startswith('/'): - rel = link.lstrip('/') - else: - rel = os.path.normpath(os.path.join(os.path.dirname(idx), link)) - rel = rel.split('#', 1)[0] - rel = rel.split('?', 1)[0] - if not rel: - continue - p = os.path.join(base, rel) - if not os.path.exists(p): - bad.append(rel) - - print('\n'.join(sorted(set(bad)))) - PY - )" - if [ -n "${missing_links}" ]; then - extended_findings+=("docs/docs-index.md contains broken relative links") - { - printf '%s\n' '### Docs index link integrity' - printf '%s\n' 'Broken relative links:' - while IFS= read -r l; do [ -n "${l}" ] && printf '%s\n' "- ${l}"; done <<< "${missing_links}" - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - # ShellCheck advisory - if [ -d "${SCRIPT_DIR}" ]; then - if ! command -v shellcheck >/dev/null 2>&1; then - sudo apt-get update -qq - sudo apt-get install -y shellcheck >/dev/null - fi - - sc_out='' - while IFS= read -r shf; do - [ -z "${shf}" ] && continue - out_one="$(shellcheck -S warning -x "${shf}" 2>/dev/null || true)" - if [ -n "${out_one}" ]; then - sc_out="${sc_out}${out_one}\n" - fi - done < <(find "${SCRIPT_DIR}" -type f -name "${SHELLCHECK_PATTERN}" 2>/dev/null | sort) - - if [ -n "${sc_out}" ]; then - extended_findings+=("ShellCheck warnings detected (advisory)") - sc_head="$(printf '%s' "${sc_out}" | head -n 200)" - { - printf '%s\n' '### ShellCheck (advisory)' - printf '%s\n' '```' - printf '%s\n' "${sc_head}" - printf '%s\n' '```' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - # SPDX header advisory for common source types - spdx_missing=() - IFS=',' read -r -a spdx_globs <<< "${SPDX_FILE_GLOBS}" - spdx_args=() - for g in "${spdx_globs[@]}"; do spdx_args+=("${g}"); done - - while IFS= read -r f; do - [ -z "${f}" ] && continue - if ! head -n 40 "${f}" | grep -q 'SPDX-License-Identifier:'; then - spdx_missing+=("${f}") - fi - done < <(git ls-files "${spdx_args[@]}" 2>/dev/null || true) - - if [ "${#spdx_missing[@]}" -gt 0 ]; then - extended_findings+=("SPDX header missing in some tracked files (advisory)") - { - printf '%s\n' '### SPDX header advisory' - printf '%s\n' 'Files missing SPDX-License-Identifier (first 40 lines scan):' - for f in "${spdx_missing[@]}"; do printf '%s\n' "- ${f}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - # Git hygiene advisory: branches older than 180 days (remote) - stale_cutoff_days=180 - stale_branches="$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/remotes/origin 2>/dev/null | awk -v now="$(date +%s)" -v days="${stale_cutoff_days}" '{if (now-$2 [...] - if [ -n "${stale_branches}" ]; then - extended_findings+=("Stale remote branches detected (advisory)") - { - printf '%s\n' '### Git hygiene advisory' - printf '%s\n' "Branches with last commit older than ${stale_cutoff_days} days (sample up to 50):" - while IFS= read -r b; do [ -n "${b}" ] && printf '%s\n' "- ${b}"; done <<< "${stale_branches}" - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - { - printf '%s\n' '### Guardrails coverage matrix' - printf '%s\n' '| Domain | Status | Notes |' - printf '%s\n' '|---|---|---|' - printf '%s\n' '| Access control | OK | Admin-only execution gate |' - printf '%s\n' '| Release variables | OK | Repository variables validation |' - printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |' - printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |' - printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |' - if [ "${extended_enabled}" = 'true' ]; then - if [ "${#extended_findings[@]}" -gt 0 ]; then - printf '%s\n' '| Extended checks | Warning | See extended findings below |' - else - printf '%s\n' '| Extended checks | OK | No findings |' - fi - else - printf '%s\n' '| Extended checks | SKIPPED | EXTENDED_CHECKS disabled |' - fi - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - if [ "${extended_enabled}" = 'true' ] && [ "${#extended_findings[@]}" -gt 0 ]; then - { - printf '%s\n' '### Extended findings (advisory)' - for f in "${extended_findings[@]}"; do printf '%s\n' "- ${f}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}" diff --git a/templates/workflows/generic/ci.yml.template b/templates/workflows/generic/ci.yml.template deleted file mode 100644 index e463fbb..0000000 --- a/templates/workflows/generic/ci.yml.template +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.CI -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /.github/workflows/ci.yml -# VERSION: 04.06.00 -# BRIEF: Continuous integration workflow using local reusable workflow -# NOTE: Delegates CI execution to local reusable-ci-validation.yml for repository validation - -name: Continuous Integration - -on: - push: - branches: - - main - - dev/** - - rc/** - - version/** - pull_request: - branches: - - main - - dev/** - - rc/** - - version/** - -permissions: - contents: read - pull-requests: write - checks: write - -jobs: - ci: - name: Repository Validation Pipeline - uses: ./.github/workflows/reusable-ci-validation.yml - with: - profile: full - secrets: inherit diff --git a/templates/workflows/generic/code-quality.yml.template b/templates/workflows/generic/code-quality.yml.template deleted file mode 100644 index 179d6a6..0000000 --- a/templates/workflows/generic/code-quality.yml.template +++ /dev/null @@ -1,325 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# 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 . -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Quality -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/generic/code-quality.yml -# VERSION: 04.06.00 -# BRIEF: Comprehensive code quality analysis workflow -# NOTE: Supports multiple linters, formatters, and static analysis tools - -name: Code Quality - -on: - push: - branches: - - main - - dev/** - pull_request: - branches: - - main - - dev/** - workflow_dispatch: - -permissions: - contents: read - pull-requests: write - security-events: write - -jobs: - lint: - name: Linting & Formatting - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Detect project type - id: detect - run: | - if [ -f "package.json" ]; then - echo "type=nodejs" >> $GITHUB_OUTPUT - elif [ -f "requirements.txt" ] || [ -f "setup.py" ]; then - echo "type=python" >> $GITHUB_OUTPUT - elif [ -f "composer.json" ]; then - echo "type=php" >> $GITHUB_OUTPUT - elif [ -f "go.mod" ]; then - echo "type=go" >> $GITHUB_OUTPUT - elif [ -f "Cargo.toml" ]; then - echo "type=rust" >> $GITHUB_OUTPUT - else - echo "type=unknown" >> $GITHUB_OUTPUT - fi - - # Node.js linting - - name: Setup Node.js - if: steps.detect.outputs.type == 'nodejs' - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'npm' - - - name: Install Node dependencies - if: steps.detect.outputs.type == 'nodejs' - run: npm ci - - - name: Run ESLint - if: steps.detect.outputs.type == 'nodejs' - run: | - npm run lint || npx eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 0 || true - continue-on-error: true - - - name: Run Prettier - if: steps.detect.outputs.type == 'nodejs' - run: | - npx prettier --check . || true - continue-on-error: true - - # Python linting - - name: Setup Python - if: steps.detect.outputs.type == 'python' - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install Python linters - if: steps.detect.outputs.type == 'python' - run: | - pip install flake8 black isort mypy pylint bandit - - - name: Run Flake8 - if: steps.detect.outputs.type == 'python' - run: | - flake8 . --count --statistics --show-source - continue-on-error: true - - - name: Run Black - if: steps.detect.outputs.type == 'python' - run: | - black --check . - continue-on-error: true - - - name: Run isort - if: steps.detect.outputs.type == 'python' - run: | - isort --check-only . - continue-on-error: true - - - name: Run Pylint - if: steps.detect.outputs.type == 'python' - run: | - pylint **/*.py || true - continue-on-error: true - - # PHP linting - - name: Setup PHP - if: steps.detect.outputs.type == 'php' - uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0 - with: - php-version: '8.2' - tools: composer, phpcs, php-cs-fixer, phpstan, psalm - - - name: Install PHP dependencies - if: steps.detect.outputs.type == 'php' - run: composer install --prefer-dist - - - name: Run PHP_CodeSniffer - if: steps.detect.outputs.type == 'php' - run: | - phpcs --standard=PSR12 --report=summary . || true - continue-on-error: true - - - name: Run PHP-CS-Fixer - if: steps.detect.outputs.type == 'php' - run: | - php-cs-fixer fix --dry-run --diff . || true - continue-on-error: true - - # Go linting - - name: Setup Go - if: steps.detect.outputs.type == 'go' - uses: actions/setup-go@v5 - with: - go-version: '1.22' - - - name: Run golangci-lint - if: steps.detect.outputs.type == 'go' - uses: golangci/golangci-lint-action@v3 - with: - version: latest - - - name: Run go fmt - if: steps.detect.outputs.type == 'go' - run: | - if [ -n "$(gofmt -l .)" ]; then - echo "Go files are not formatted:" - gofmt -l . - exit 1 - fi - - # Rust linting - - name: Setup Rust - if: steps.detect.outputs.type == 'rust' - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - components: clippy, rustfmt - - - name: Run cargo fmt - if: steps.detect.outputs.type == 'rust' - run: cargo fmt --all -- --check - - - name: Run cargo clippy - if: steps.detect.outputs.type == 'rust' - run: cargo clippy --all-targets --all-features -- -D warnings - - static-analysis: - name: Static Analysis - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Run CodeQL Analysis - uses: github/codeql-action/init@v4 - with: - languages: javascript, python, go - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 - - dependency-check: - name: Dependency Security Check - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Run Snyk Security Check - uses: snyk/actions/node@master - continue-on-error: true - env: - SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - - - name: Run npm audit - if: hashFiles('package-lock.json') != '' - run: npm audit --audit-level=moderate || true - - - name: Run pip safety check - if: hashFiles('requirements.txt') != '' - run: | - pip install safety - safety check -r requirements.txt || true - - complexity: - name: Code Complexity Analysis - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install radon - run: pip install radon - - - name: Calculate cyclomatic complexity - run: | - if [ -d "src" ] || [ -d "lib" ]; then - radon cc . -a -nb || true - fi - - - name: Calculate maintainability index - run: | - if [ -d "src" ] || [ -d "lib" ]; then - radon mi . -nb || true - fi - - coverage: - name: Code Coverage Analysis - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Detect project type - id: detect - run: | - if [ -f "package.json" ]; then - echo "type=nodejs" >> $GITHUB_OUTPUT - elif [ -f "requirements.txt" ]; then - echo "type=python" >> $GITHUB_OUTPUT - elif [ -f "go.mod" ]; then - echo "type=go" >> $GITHUB_OUTPUT - fi - - - name: Setup and run coverage - run: | - TYPE="${{ steps.detect.outputs.type }}" - - case $TYPE in - nodejs) - npm ci - npm test -- --coverage || true - ;; - python) - pip install pytest pytest-cov - pytest --cov=. --cov-report=xml || true - ;; - go) - go test -coverprofile=coverage.out ./... || true - ;; - esac - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - files: ./coverage.xml,./coverage.out - flags: quality-check - - summary: - name: Quality Summary - runs-on: ubuntu-latest - needs: [lint, static-analysis, dependency-check, complexity, coverage] - if: always() - - steps: - - name: Generate quality report - run: | - echo "### Code Quality Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- Repository: $GITHUB_REPOSITORY" >> $GITHUB_STEP_SUMMARY - echo "- Branch: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY - echo "- Commit: $GITHUB_SHA" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Quality checks completed:" >> $GITHUB_STEP_SUMMARY - echo "- Linting: ${{ needs.lint.result }}" >> $GITHUB_STEP_SUMMARY - echo "- Static Analysis: ${{ needs.static-analysis.result }}" >> $GITHUB_STEP_SUMMARY - echo "- Dependencies: ${{ needs.dependency-check.result }}" >> $GITHUB_STEP_SUMMARY - echo "- Complexity: ${{ needs.complexity.result }}" >> $GITHUB_STEP_SUMMARY - echo "- Coverage: ${{ needs.coverage.result }}" >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/generic/codeql-analysis.yml.template b/templates/workflows/generic/codeql-analysis.yml.template deleted file mode 100644 index c5a1714..0000000 --- a/templates/workflows/generic/codeql-analysis.yml.template +++ /dev/null @@ -1,115 +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: Gitea.Workflow.Template -# INGROUP: MokoStandards.Security -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/generic/codeql-analysis.yml.template -# VERSION: 04.06.00 -# BRIEF: CodeQL security scanning workflow (generic — all repo types) -# NOTE: Deployed to .github/workflows/codeql-analysis.yml in governed repos. -# CodeQL does not support PHP directly; JavaScript scans JSON/YAML/shell. -# For PHP-specific security scanning see standards-compliance.yml. - -name: CodeQL Security Scanning - -on: - push: - branches: - - main - - dev/** - - rc/** - - version/** - pull_request: - branches: - - main - - dev/** - - rc/** - schedule: - # Weekly on Monday at 06:00 UTC - - cron: '0 6 * * 1' - workflow_dispatch: - -permissions: - actions: read - contents: read - security-events: write - pull-requests: read - -jobs: - analyze: - name: Analyze (${{ matrix.language }}) - runs-on: ubuntu-latest - timeout-minutes: 360 - - strategy: - fail-fast: false - matrix: - # CodeQL does not support PHP. Use 'javascript' to scan JSON, YAML, - # and shell scripts. Add 'actions' to scan GitHub Actions workflows. - language: ['javascript', 'actions'] - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - queries: security-extended,security-and-quality - - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{ matrix.language }}" - upload: true - output: sarif-results - wait-for-processing: true - - - name: Upload SARIF results - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.5.0 - with: - name: codeql-results-${{ matrix.language }} - path: sarif-results - retention-days: 30 - - - name: Step summary - if: always() - run: | - echo "### 🔍 CodeQL — ${{ matrix.language }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - URL="https://github.com/${{ github.repository }}/security/code-scanning" - echo "See the [Security tab]($URL) for findings." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Severity | SLA |" >> $GITHUB_STEP_SUMMARY - echo "|----------|-----|" >> $GITHUB_STEP_SUMMARY - echo "| Critical | 7 days |" >> $GITHUB_STEP_SUMMARY - echo "| High | 14 days |" >> $GITHUB_STEP_SUMMARY - echo "| Medium | 30 days |" >> $GITHUB_STEP_SUMMARY - echo "| Low | 60 days / next release |" >> $GITHUB_STEP_SUMMARY - - summary: - name: Security Scan Summary - runs-on: ubuntu-latest - needs: analyze - if: always() - - steps: - - name: Summary - run: | - echo "### 🛡️ CodeQL Complete" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY - echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY - SECURITY_URL="https://github.com/${{ github.repository }}/security" - echo "" >> $GITHUB_STEP_SUMMARY - echo "📊 [View all security alerts]($SECURITY_URL)" >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/generic/dependency-review.yml.template b/templates/workflows/generic/dependency-review.yml.template deleted file mode 100644 index 5df6ab3..0000000 --- a/templates/workflows/generic/dependency-review.yml.template +++ /dev/null @@ -1,275 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# 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 . -# -# FILE INFORMATION -# DEFGROUP: Gitea.WorkflowTemplate -# INGROUP: MokoStandards.Security -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /.github/workflows/dependency-review.yml -# VERSION: 04.06.00 -# BRIEF: Dependency review workflow for vulnerability scanning in pull requests -# NOTE: Scans dependencies for security vulnerabilities and license compliance - -name: Dependency Review - -on: - pull_request: - branches: - - main - - dev/** - - rc/** - -permissions: - contents: read - pull-requests: write - -jobs: - dependency-review: - name: Dependency Security Review - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Dependency Review - uses: actions/dependency-review-action@v4 - with: - # Fail on critical or high severity vulnerabilities - fail-on-severity: moderate - - # Allow specific licenses (customize for your project) - # Common open-source licenses - allow-licenses: GPL-3.0, GPL-3.0-or-later, MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, LGPL-3.0 - - # Deny specific licenses (customize as needed) - # deny-licenses: AGPL-3.0, GPL-2.0 - - # Comment on PR with results - comment-summary-in-pr: always - - - name: Generate Dependency Report - if: always() - run: | - echo "# Dependency Review Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "✅ Dependency review completed" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "This workflow checks:" >> $GITHUB_STEP_SUMMARY - echo "- Security vulnerabilities in new dependencies" >> $GITHUB_STEP_SUMMARY - echo "- License compatibility" >> $GITHUB_STEP_SUMMARY - echo "- Dependency changes between base and head" >> $GITHUB_STEP_SUMMARY - - npm-audit: - name: npm Audit - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup Node.js - if: ${{ hashFiles('package.json') != '' }} - uses: actions/setup-node@v4 - with: - node-version: '18' - - - name: Run npm Audit - if: ${{ hashFiles('package.json') != '' }} - run: | - echo "### npm Audit Results" >> $GITHUB_STEP_SUMMARY - - # Run audit and capture results - if npm audit --audit-level=moderate; then - echo "✅ No moderate or higher severity vulnerabilities found" >> $GITHUB_STEP_SUMMARY - else - echo "⚠️ Moderate or higher severity vulnerabilities detected - please review" >> $GITHUB_STEP_SUMMARY - npm audit --audit-level=moderate || true - fi - - - name: Check for Outdated Packages - if: ${{ hashFiles('package.json') != '' }} - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Outdated Packages" >> $GITHUB_STEP_SUMMARY - npm outdated || echo "All packages are up to date" >> $GITHUB_STEP_SUMMARY - - composer-audit: - name: Composer Audit - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup PHP - if: ${{ hashFiles('composer.json') != '' }} - uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0 - with: - php-version: '8.1' - tools: composer:v2 - - - name: Install Dependencies - if: ${{ hashFiles('composer.json') != '' }} - run: composer install --no-interaction --prefer-dist - - - name: Run Composer Audit - if: ${{ hashFiles('composer.json') != '' }} - run: | - echo "### Composer Audit Results" >> $GITHUB_STEP_SUMMARY - - # Run audit and capture results - if composer audit; then - echo "✅ No vulnerabilities found in Composer dependencies" >> $GITHUB_STEP_SUMMARY - else - echo "⚠️ Vulnerabilities detected - please review" >> $GITHUB_STEP_SUMMARY - composer audit || true - fi - - - name: Check for Outdated Packages - if: ${{ hashFiles('composer.json') != '' }} - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Outdated Composer Packages" >> $GITHUB_STEP_SUMMARY - composer outdated --direct || echo "All packages are up to date" >> $GITHUB_STEP_SUMMARY - - python-safety: - name: Python Safety Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup Python - if: ${{ hashFiles('requirements.txt', 'pyproject.toml', 'Pipfile') != '' }} - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install Safety - if: ${{ hashFiles('requirements.txt', 'pyproject.toml', 'Pipfile') != '' }} - run: pip install safety - - - name: Run Safety Check - if: ${{ hashFiles('requirements.txt', 'pyproject.toml', 'Pipfile') != '' }} - run: | - echo "### Python Safety Check Results" >> $GITHUB_STEP_SUMMARY - - # Check requirements.txt if exists - if [ -f "requirements.txt" ]; then - if safety check -r requirements.txt; then - echo "✅ No known vulnerabilities in Python dependencies" >> $GITHUB_STEP_SUMMARY - else - echo "⚠️ Vulnerabilities detected in Python dependencies" >> $GITHUB_STEP_SUMMARY - safety check -r requirements.txt || true - fi - else - echo "ℹ️ No requirements.txt found" >> $GITHUB_STEP_SUMMARY - fi - - license-check: - name: License Compliance Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check License File - run: | - echo "### License Compliance" >> $GITHUB_STEP_SUMMARY - - if [ -f "LICENSE" ] || [ -f "LICENSE.md" ] || [ -f "LICENSE.txt" ]; then - echo "✅ LICENSE file present" >> $GITHUB_STEP_SUMMARY - - # Check for GPL-3.0 (MokoStandards default) - if grep -qi "GNU GENERAL PUBLIC LICENSE" LICENSE* 2>/dev/null; then - echo "✅ GPL-3.0 or compatible license detected" >> $GITHUB_STEP_SUMMARY - else - echo "ℹ️ Non-GPL license detected - verify compatibility" >> $GITHUB_STEP_SUMMARY - fi - else - echo "❌ LICENSE file missing" >> $GITHUB_STEP_SUMMARY - echo "Please add a LICENSE file to the repository root" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - - name: Check SPDX Headers (Optional) - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "### SPDX Header Compliance" >> $GITHUB_STEP_SUMMARY - - # Check for SPDX identifiers in source files - MISSING_HEADERS=0 - - # Check PHP files - if find . -name "*.php" -type f ! -path "./vendor/*" | head -1 | grep -q .; then - TOTAL_PHP=$(find . -name "*.php" -type f ! -path "./vendor/*" | wc -l) - WITH_SPDX=$(find . -name "*.php" -type f ! -path "./vendor/*" -exec grep -l "SPDX-License-Identifier" {} \; | wc -l) - echo "- PHP files: $WITH_SPDX/$TOTAL_PHP with SPDX headers" >> $GITHUB_STEP_SUMMARY - fi - - # Check JavaScript files - if find . -name "*.js" -type f ! -path "./node_modules/*" ! -path "./vendor/*" | head -1 | grep -q .; then - TOTAL_JS=$(find . -name "*.js" -type f ! -path "./node_modules/*" ! -path "./vendor/*" | wc -l) - WITH_SPDX_JS=$(find . -name "*.js" -type f ! -path "./node_modules/*" ! -path "./vendor/*" -exec grep -l "SPDX-License-Identifier" {} \; | wc -l) - echo "- JavaScript files: $WITH_SPDX_JS/$TOTAL_JS with SPDX headers" >> $GITHUB_STEP_SUMMARY - fi - - echo "ℹ️ SPDX headers are recommended but not required for this check" >> $GITHUB_STEP_SUMMARY - - summary: - name: Review Summary - runs-on: ubuntu-latest - needs: [dependency-review, npm-audit, composer-audit, python-safety, license-check] - if: always() - - steps: - - name: Generate Final Summary - run: | - echo "# Dependency Review Complete" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "All dependency security and license checks have been executed." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "## Checks Performed:" >> $GITHUB_STEP_SUMMARY - echo "- ✅ GitHub Dependency Review" >> $GITHUB_STEP_SUMMARY - echo "- ✅ Package Manager Audits (npm, composer, pip)" >> $GITHUB_STEP_SUMMARY - echo "- ✅ License Compliance" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Review the job results above for any issues that need attention." >> $GITHUB_STEP_SUMMARY - -# CUSTOMIZATION NOTES: -# -# 1. Adjust severity thresholds: -# Change fail-on-severity to: low, moderate, high, critical -# -# 2. Modify allowed licenses: -# Update allow-licenses list based on your project requirements -# -# 3. Add custom dependency checks: -# - Snyk integration -# - WhiteSource/Mend scanning -# - Custom license scanners -# -# 4. Configure notification: -# Add Slack/email notifications for critical findings -# -# 5. Integrate with existing tools: -# Add steps for your organization's security tools diff --git a/templates/workflows/generic/deploy.yml.template b/templates/workflows/generic/deploy.yml.template deleted file mode 100644 index d396dee..0000000 --- a/templates/workflows/generic/deploy.yml.template +++ /dev/null @@ -1,281 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# 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 . -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Deploy -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/generic/deploy.yml -# VERSION: 04.06.00 -# BRIEF: Deployment workflow for various environments -# NOTE: Supports staging, production, and other custom environments - -name: Deploy - -on: - push: - branches: - - main - - staging - release: - types: [published] - workflow_dispatch: - inputs: - environment: - description: 'Deployment environment' - required: true - type: choice - options: - - staging - - production - version: - description: 'Version to deploy (optional)' - required: false - type: string - -permissions: - contents: read - deployments: write - -jobs: - prepare: - name: Prepare Deployment - runs-on: ubuntu-latest - outputs: - environment: ${{ steps.determine-env.outputs.environment }} - version: ${{ steps.determine-version.outputs.version }} - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Determine environment - id: determine-env - run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - ENV="${{ inputs.environment }}" - elif [ "${{ github.event_name }}" == "release" ]; then - ENV="production" - elif [ "${{ github.ref }}" == "refs/heads/main" ]; then - ENV="production" - elif [ "${{ github.ref }}" == "refs/heads/staging" ]; then - ENV="staging" - else - ENV="development" - fi - echo "environment=${ENV}" >> $GITHUB_OUTPUT - echo "Deploying to: ${ENV}" - - - name: Determine version - id: determine-version - run: | - if [ "${{ inputs.version }}" != "" ]; then - VERSION="${{ inputs.version }}" - elif [ "${{ github.event_name }}" == "release" ]; then - VERSION="${{ github.event.release.tag_name }}" - else - VERSION="$(git describe --tags --always)" - fi - echo "version=${VERSION}" >> $GITHUB_OUTPUT - echo "Version: ${VERSION}" - - build: - name: Build Application - runs-on: ubuntu-latest - needs: prepare - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup build environment - run: | - if [ -f "package.json" ]; then - echo "BUILD_TYPE=nodejs" >> $GITHUB_ENV - elif [ -f "requirements.txt" ]; then - echo "BUILD_TYPE=python" >> $GITHUB_ENV - elif [ -f "go.mod" ]; then - echo "BUILD_TYPE=go" >> $GITHUB_ENV - elif [ -f "Cargo.toml" ]; then - echo "BUILD_TYPE=rust" >> $GITHUB_ENV - else - echo "BUILD_TYPE=generic" >> $GITHUB_ENV - fi - - - name: Setup Node.js - if: env.BUILD_TYPE == 'nodejs' - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'npm' - - - name: Setup Python - if: env.BUILD_TYPE == 'python' - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Setup Go - if: env.BUILD_TYPE == 'go' - uses: actions/setup-go@v5 - with: - go-version: '1.22' - - - name: Build application - run: | - case $BUILD_TYPE in - nodejs) - npm ci - npm run build - ;; - python) - pip install -r requirements.txt - python setup.py build 2>/dev/null || echo "No setup.py found" - ;; - go) - go build -o app ./... - ;; - rust) - cargo build --release - ;; - generic) - echo "Generic build - no specific build steps" - ;; - esac - - - name: Create deployment package - run: | - mkdir -p dist - - case $BUILD_TYPE in - nodejs) - if [ -d "build" ]; then cp -r build/* dist/; fi - if [ -d "dist" ]; then cp -r dist/* dist/; fi - ;; - python) - if [ -d "build" ]; then cp -r build/* dist/; fi - ;; - go) - if [ -f "app" ]; then cp app dist/; fi - ;; - rust) - if [ -f "target/release/app" ]; then cp target/release/app dist/; fi - ;; - esac - - # Add version file - echo "${{ needs.prepare.outputs.version }}" > dist/VERSION - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: deployment-package - path: dist/ - retention-days: 7 - - deploy-staging: - name: Deploy to Staging - runs-on: ubuntu-latest - needs: [prepare, build] - if: needs.prepare.outputs.environment == 'staging' || needs.prepare.outputs.environment == 'development' - environment: - name: staging - url: https://staging.example.com - - steps: - - name: Download build artifacts - uses: actions/download-artifact@v4.1.3 - with: - name: deployment-package - path: ./dist - - - name: Deploy to staging - run: | - echo "Deploying version ${{ needs.prepare.outputs.version }} to staging" - # Add your staging deployment commands here - # Examples: - # - rsync to staging server - # - kubectl apply for Kubernetes - # - aws s3 sync for S3 - # - heroku git:remote for Heroku - echo "Deployment completed" - - - name: Run smoke tests - run: | - echo "Running smoke tests on staging..." - # Add smoke test commands here - # curl https://staging.example.com/health || exit 1 - - deploy-production: - name: Deploy to Production - runs-on: ubuntu-latest - needs: [prepare, build] - if: needs.prepare.outputs.environment == 'production' - environment: - name: production - url: https://example.com - - steps: - - name: Download build artifacts - uses: actions/download-artifact@v4.1.3 - with: - name: deployment-package - path: ./dist - - - name: Deploy to production - run: | - echo "Deploying version ${{ needs.prepare.outputs.version }} to production" - # Add your production deployment commands here - echo "Deployment completed" - - - name: Run smoke tests - run: | - echo "Running smoke tests on production..." - # Add smoke test commands here - - - name: Notify deployment - run: | - echo "### Deployment Successful! 🚀" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- Environment: production" >> $GITHUB_STEP_SUMMARY - echo "- Version: ${{ needs.prepare.outputs.version }}" >> $GITHUB_STEP_SUMMARY - echo "- URL: https://example.com" >> $GITHUB_STEP_SUMMARY - echo "- Deployed by: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY - - rollback: - name: Rollback on Failure - runs-on: ubuntu-latest - needs: [deploy-staging, deploy-production] - if: failure() - - steps: - - name: Rollback deployment - run: | - echo "Deployment failed. Initiating rollback..." - # Add rollback commands here - echo "Rollback completed" - - - name: Notify failure - run: | - echo "### Deployment Failed ❌" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Rollback has been initiated." >> $GITHUB_STEP_SUMMARY - echo "Please check the logs for more details." >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/generic/index.md b/templates/workflows/generic/index.md deleted file mode 100644 index f6caf2b..0000000 --- a/templates/workflows/generic/index.md +++ /dev/null @@ -1,24 +0,0 @@ -# Docs Index: /templates/workflows/generic - -## Purpose - -This directory contains GitHub Actions workflow templates for generic software development projects supporting multiple programming languages. - -## Available Templates - -- **ci.yml** - Multi-language continuous integration workflow with automatic language detection (Node.js, Python, PHP, Go, Ruby, Rust) -- **test.yml** - Comprehensive testing workflow supporting unit tests, integration tests, and end-to-end tests -- **deploy.yml** - Deployment workflow for staging and production environments with rollback capabilities -- **code-quality.yml** - Code quality analysis with linting, formatting, static analysis, and security checks -- **repo_health.yml** - Repository health monitoring for generic projects - -## 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/workflows/generic/test.yml.template b/templates/workflows/generic/test.yml.template deleted file mode 100644 index dd902f9..0000000 --- a/templates/workflows/generic/test.yml.template +++ /dev/null @@ -1,292 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# 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 . -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Testing -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/generic/test.yml -# VERSION: 04.06.00 -# BRIEF: Comprehensive testing workflow for generic projects -# NOTE: Supports unit, integration, and end-to-end testing - -name: Test Suite - -on: - push: - branches: - - main - - dev/** - - rc/** - pull_request: - branches: - - main - - dev/** - - rc/** - workflow_dispatch: - -permissions: - contents: read - checks: write - -jobs: - unit-tests: - name: Unit Tests - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup environment - run: | - # Detect project type and setup accordingly - if [ -f "package.json" ]; then - echo "PROJECT_TYPE=nodejs" >> $GITHUB_ENV - elif [ -f "requirements.txt" ] || [ -f "setup.py" ]; then - echo "PROJECT_TYPE=python" >> $GITHUB_ENV - elif [ -f "composer.json" ]; then - echo "PROJECT_TYPE=php" >> $GITHUB_ENV - elif [ -f "go.mod" ]; then - echo "PROJECT_TYPE=go" >> $GITHUB_ENV - elif [ -f "Gemfile" ]; then - echo "PROJECT_TYPE=ruby" >> $GITHUB_ENV - elif [ -f "Cargo.toml" ]; then - echo "PROJECT_TYPE=rust" >> $GITHUB_ENV - else - echo "PROJECT_TYPE=unknown" >> $GITHUB_ENV - fi - - - name: Setup Node.js - if: env.PROJECT_TYPE == 'nodejs' - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'npm' - - - name: Setup Python - if: env.PROJECT_TYPE == 'python' - uses: actions/setup-python@v5 - with: - python-version: '3.11' - cache: 'pip' - - - name: Setup PHP - if: env.PROJECT_TYPE == 'php' - uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0 - with: - php-version: '8.2' - coverage: xdebug - tools: composer:v2 - - - name: Setup Go - if: env.PROJECT_TYPE == 'go' - uses: actions/setup-go@v5 - with: - go-version: '1.22' - cache: true - - - name: Setup Ruby - if: env.PROJECT_TYPE == 'ruby' - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.2' - bundler-cache: true - - - name: Install dependencies - run: | - case $PROJECT_TYPE in - nodejs) npm ci ;; - python) pip install -r requirements.txt -r requirements-dev.txt 2>/dev/null || pip install pytest pytest-cov ;; - php) composer install --prefer-dist ;; - go) go mod download ;; - ruby) bundle install ;; - rust) cargo fetch ;; - esac - - - name: Run unit tests - run: | - case $PROJECT_TYPE in - nodejs) npm test ;; - python) pytest tests/ --cov=. --cov-report=xml --cov-report=term ;; - php) vendor/bin/phpunit --coverage-clover coverage.xml ;; - go) go test -v -coverprofile=coverage.out ./... ;; - ruby) bundle exec rspec || bundle exec rake test ;; - rust) cargo test --all-features ;; - *) echo "No tests configured for this project type" ;; - esac - - - name: Upload coverage reports - uses: codecov/codecov-action@v3 - with: - files: ./coverage.xml,./coverage.out - flags: unittests - name: codecov-unit - - integration-tests: - name: Integration Tests - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:15 - env: - POSTGRES_PASSWORD: postgres - POSTGRES_DB: test_db - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - redis: - image: redis:7 - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 6379:6379 - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup environment - run: | - if [ -f "package.json" ]; then - echo "PROJECT_TYPE=nodejs" >> $GITHUB_ENV - elif [ -f "requirements.txt" ]; then - echo "PROJECT_TYPE=python" >> $GITHUB_ENV - elif [ -f "composer.json" ]; then - echo "PROJECT_TYPE=php" >> $GITHUB_ENV - elif [ -f "go.mod" ]; then - echo "PROJECT_TYPE=go" >> $GITHUB_ENV - else - echo "PROJECT_TYPE=unknown" >> $GITHUB_ENV - fi - - - name: Setup runtime - run: | - case $PROJECT_TYPE in - nodejs) - curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - - sudo apt-get install -y nodejs - ;; - python) - sudo apt-get update - sudo apt-get install -y python3 python3-pip - ;; - php) - sudo apt-get update - sudo apt-get install -y php php-cli php-mbstring php-xml - ;; - go) - wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz - sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz - echo "/usr/local/go/bin" >> $GITHUB_PATH - ;; - esac - - - name: Install dependencies - run: | - case $PROJECT_TYPE in - nodejs) npm ci ;; - python) pip install -r requirements.txt ;; - php) composer install ;; - go) go mod download ;; - esac - - - name: Run integration tests - env: - DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db - REDIS_URL: redis://localhost:6379 - run: | - if [ -d "tests/integration" ] || [ -d "tests/Integration" ]; then - case $PROJECT_TYPE in - nodejs) npm run test:integration || npx jest tests/integration ;; - python) pytest tests/integration/ -v ;; - php) vendor/bin/phpunit --testsuite Integration ;; - go) go test -v ./tests/integration/... ;; - *) echo "No integration tests found" ;; - esac - else - echo "No integration tests directory found" - fi - - e2e-tests: - name: End-to-End Tests - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - - - name: Install Playwright - run: | - if [ -f "package.json" ] && grep -q "playwright" package.json; then - npm ci - npx playwright install --with-deps - else - echo "Playwright not configured, skipping" - fi - - - name: Run E2E tests - run: | - if [ -d "tests/e2e" ] || [ -d "e2e" ]; then - npm run test:e2e || npx playwright test || echo "No E2E tests configured" - else - echo "No E2E tests directory found" - fi - - - name: Upload Playwright report - uses: actions/upload-artifact@v4 - if: always() - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 - - test-summary: - name: Test Summary - runs-on: ubuntu-latest - needs: [unit-tests, integration-tests, e2e-tests] - if: always() - - steps: - - name: Generate summary - run: | - echo "### Test Execution Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- Repository: $GITHUB_REPOSITORY" >> $GITHUB_STEP_SUMMARY - echo "- Branch: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY - echo "- Commit: $GITHUB_SHA" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Test suites completed:" >> $GITHUB_STEP_SUMMARY - echo "- Unit tests: ${{ needs.unit-tests.result }}" >> $GITHUB_STEP_SUMMARY - echo "- Integration tests: ${{ needs.integration-tests.result }}" >> $GITHUB_STEP_SUMMARY - echo "- E2E tests: ${{ needs.e2e-tests.result }}" >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/health-check.yml b/templates/workflows/health-check.yml deleted file mode 100644 index e07b770..0000000 --- a/templates/workflows/health-check.yml +++ /dev/null @@ -1,320 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.HealthCheck -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /.github/workflows/health-check.yml -# VERSION: 04.06.00 -# BRIEF: Hourly library health check and circuit breaker testing -# NOTE: Monitors library health and tests circuit breakers using PHP ApiClient - -name: Health Check - -on: - schedule: - # Run hourly - - cron: '0 * * * *' - workflow_dispatch: - inputs: - test_circuit_breaker: - description: 'Test circuit breaker functionality' - required: false - type: boolean - default: true - check_all_libraries: - description: 'Check all enterprise libraries' - required: false - type: boolean - default: true - # Allows external systems (e.g. MokoStandards bulk_sync) to trigger via: - # gh api repos/{org}/{repo}/dispatches -f event_type=health-check - repository_dispatch: - types: [health-check] - -permissions: - contents: read - issues: write - -jobs: - health-check: - name: Library Health Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - with: - php-version: '8.1' - extensions: mbstring, curl, json - tools: composer - - - name: Install Composer Dependencies - run: composer install --no-dev --optimize-autoloader - - - name: Create Health Check Directory - run: | - mkdir -p logs/health - mkdir -p logs/reports - - - name: Check Library Health - id: health - run: | - echo "## 🏥 Library Health Check" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - php << 'EOF' - getApiClient(); - $healthStatus['ApiClient'] = [ - 'status' => 'healthy', - 'circuit_state' => $client->getCircuitState(), - 'version' => '04.00.04' - ]; - echo "✅ ApiClient: healthy\n"; - } catch (Exception $e) { - $healthStatus['ApiClient'] = ['status' => 'unhealthy', 'error' => $e->getMessage()]; - $failedLibraries[] = 'ApiClient'; - echo "❌ ApiClient: " . $e->getMessage() . "\n"; - } - - // Test AuditLogger - try { - $logger = new AuditLogger('health_check'); - $healthStatus['AuditLogger'] = [ - 'status' => 'healthy', - 'version' => '04.00.04' - ]; - echo "✅ AuditLogger: healthy\n"; - } catch (Exception $e) { - $healthStatus['AuditLogger'] = ['status' => 'unhealthy', 'error' => $e->getMessage()]; - $failedLibraries[] = 'AuditLogger'; - echo "❌ AuditLogger: " . $e->getMessage() . "\n"; - } - - // Test MetricsCollector - try { - $metrics = new MetricsCollector('health_check'); - $metrics->increment('health_check_test'); - $healthStatus['MetricsCollector'] = [ - 'status' => 'healthy', - 'version' => '04.00.04' - ]; - echo "✅ MetricsCollector: healthy\n"; - } catch (Exception $e) { - $healthStatus['MetricsCollector'] = ['status' => 'unhealthy', 'error' => $e->getMessage()]; - $failedLibraries[] = 'MetricsCollector'; - echo "❌ MetricsCollector: " . $e->getMessage() . "\n"; - } - - // Test SecurityValidator - try { - $validator = new SecurityValidator(); - $healthStatus['SecurityValidator'] = [ - 'status' => 'healthy', - 'version' => '04.00.04' - ]; - echo "✅ SecurityValidator: healthy\n"; - } catch (Exception $e) { - $healthStatus['SecurityValidator'] = ['status' => 'unhealthy', 'error' => $e->getMessage()]; - $failedLibraries[] = 'SecurityValidator'; - echo "❌ SecurityValidator: " . $e->getMessage() . "\n"; - } - - // Save health report - file_put_contents('logs/health/health-report.json', json_encode($healthStatus, JSON_PRETTY_PRINT)); - - $summary = [ - 'total' => count($healthStatus), - 'healthy' => count(array_filter($healthStatus, fn($s) => $s['status'] === 'healthy')), - 'unhealthy' => count($failedLibraries), - 'failed' => $failedLibraries - ]; - file_put_contents('/tmp/health_summary.json', json_encode($summary)); - - if (count($failedLibraries) > 0) { - exit(1); - } - EOF - - # Read summary and output - if [ -f "/tmp/health_summary.json" ]; then - SUMMARY=$(cat /tmp/health_summary.json) - TOTAL=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["total"];') - HEALTHY=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["healthy"];') - UNHEALTHY=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["unhealthy"];') - - echo "total_libraries=$TOTAL" >> $GITHUB_OUTPUT - echo "healthy_count=$HEALTHY" >> $GITHUB_OUTPUT - echo "unhealthy_count=$UNHEALTHY" >> $GITHUB_OUTPUT - - echo "### Health Status" >> $GITHUB_STEP_SUMMARY - echo "- Total libraries checked: **${TOTAL}**" >> $GITHUB_STEP_SUMMARY - echo "- Healthy: **${HEALTHY}** ✅" >> $GITHUB_STEP_SUMMARY - echo "- Unhealthy: **${UNHEALTHY}** ❌" >> $GITHUB_STEP_SUMMARY - fi - - - name: Test Circuit Breaker - if: github.event.inputs.test_circuit_breaker != 'false' - env: - APP_ENV: test - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "## 🔌 Circuit Breaker Test" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - php << 'EOF' - getCircuitState() . "\n"; - - // Simulate failures to trip circuit breaker - for ($i = 0; $i < 4; $i++) { - try { - $client->simulateFailure(); - } catch (Exception $e) { - // Expected - } - } - - echo "After simulated failures: " . $client->getCircuitState() . "\n"; - - // Verify circuit is open - if ($client->getCircuitState() === 'OPEN') { - echo "✅ Circuit breaker correctly opened after threshold\n"; - } else { - echo "⚠️ Circuit breaker did not open as expected\n"; - } - - // Wait for timeout to allow circuit to move to half-open - echo "Waiting for circuit timeout (6 seconds)...\n"; - sleep(6); - - // Get the current state after timeout - echo "After timeout: " . $client->getCircuitState() . "\n"; - - echo "✅ Circuit breaker test completed successfully\n"; - - } catch (Exception $e) { - echo "❌ Circuit breaker test failed: " . $e->getMessage() . "\n"; - exit(1); - } - EOF - - echo "✅ Circuit breaker test passed" >> $GITHUB_STEP_SUMMARY - - - name: Upload Health Report - if: always() - uses: actions/upload-artifact@v6.0.0 - with: - name: health-report-${{ github.run_number }} - path: | - logs/health/ - retention-days: 7 - - - name: Create or update health issue - if: always() - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - REPO="${{ github.repository }}" - RUN_URL="${{ github.server_url }}/${REPO}/actions/runs/${{ github.run_id }}" - NOW=$(date -u '+%Y-%m-%d %H:%M:%S UTC') - TRIGGER="${{ github.event_name }}" - - TOTAL="${{ steps.health.outputs.total_libraries }}" - HEALTHY="${{ steps.health.outputs.healthy_count }}" - UNHEALTHY="${{ steps.health.outputs.unhealthy_count }}" - JOB_STATUS="${{ job.status }}" - - # Choose label and title based on outcome - if [ "$JOB_STATUS" = "success" ]; then - LABEL="health-check" - TITLE="✅ Repository health check passed — ${REPO}" - else - LABEL="health-check" - TITLE="❌ Repository health check failed — ${REPO}" - fi - - BODY="## Repository Health Check - - | Field | Value | - |-------|-------| - | **Repository** | \`${REPO}\` | - | **Status** | ${JOB_STATUS} | - | **Libraries checked** | ${TOTAL:-n/a} | - | **Healthy** | ${HEALTHY:-n/a} | - | **Unhealthy** | ${UNHEALTHY:-0} | - | **Trigger** | ${TRIGGER} | - | **Run at** | ${NOW} | - | **Run** | [View workflow run](${RUN_URL}) | - - ### Next steps - $([ "$JOB_STATUS" != "success" ] && echo "1. Review the [workflow run log](${RUN_URL}) for the specific failures. - 2. Check that all enterprise library dependencies are installed and up to date. - 3. Re-run the health check via **Actions → Health Check → Run workflow** once fixed." || echo "_No action required — all checks passed._") - - --- - *Auto-managed by health-check.yml — close this issue once all checks are passing.*" - - # Search for an existing health-check issue (any state) - EXISTING=$(gh api "repos/${REPO}/issues?labels=${LABEL}&state=all&per_page=1&sort=updated&direction=desc" \ - --jq '.[0].number' 2>/dev/null) - - if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then - # Check if it's closed — reopen if so - CURRENT_STATE=$(gh api "repos/${REPO}/issues/${EXISTING}" --jq '.state' 2>/dev/null) - if [ "$CURRENT_STATE" = "closed" ]; then - gh api "repos/${REPO}/issues/${EXISTING}" -X PATCH -f state=open --silent - fi - gh api "repos/${REPO}/issues/${EXISTING}" \ - -X PATCH \ - -f title="$TITLE" \ - -f body="$BODY" \ - --silent - echo "📋 Health issue #${EXISTING} updated" >> "$GITHUB_STEP_SUMMARY" - else - gh issue create \ - --repo "$REPO" \ - --title "$TITLE" \ - --body "$BODY" \ - --label "$LABEL" \ - | tee -a "$GITHUB_STEP_SUMMARY" - fi diff --git a/templates/workflows/index.md b/templates/workflows/index.md deleted file mode 100644 index 2b3d9bc..0000000 --- a/templates/workflows/index.md +++ /dev/null @@ -1,26 +0,0 @@ -# Docs Index: /templates/workflows - -## Purpose - -This index provides navigation to GitHub Actions workflow templates organized by project type. - -## Subfolders - -- [dolibarr/](./dolibarr/index.md) - Workflow templates for Dolibarr ERP/CRM modules -- [generic/](./generic/index.md) - Multi-language workflow templates for generic projects -- [joomla/](./joomla/index.md) - Workflow templates for Joomla extensions - -## Documents - -- [README](./README.md) - Comprehensive documentation for all workflow templates - -## 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/workflows/integration-tests.yml b/templates/workflows/integration-tests.yml deleted file mode 100644 index 87f69b6..0000000 --- a/templates/workflows/integration-tests.yml +++ /dev/null @@ -1,376 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.IntegrationTests -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /.github/workflows/integration-tests.yml -# VERSION: 04.06.00 -# BRIEF: Integration tests for all enterprise libraries with performance benchmarks -# NOTE: Tests all 13 PHP enterprise libraries and measures performance - -name: Integration Tests - -on: - schedule: - # Run daily at 04:00 UTC - - cron: '0 4 * * *' - pull_request: - branches: - - main - paths: - - 'api/lib/Enterprise/**' - - 'src/**/*.php' - workflow_dispatch: - inputs: - library_filter: - description: 'Filter specific library to test' - required: false - type: string - default: '' - run_benchmarks: - description: 'Run performance benchmarks' - required: false - type: boolean - default: true - -permissions: - contents: read - -jobs: - integration-tests: - name: Integration Tests - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - with: - php-version: '8.1' - extensions: mbstring, curl, json - tools: composer - - - name: Install Composer Dependencies - run: composer install --no-dev --optimize-autoloader - - - name: Create Test Directories - run: | - mkdir -p logs/tests - mkdir -p logs/benchmarks - mkdir -p logs/reports - - - name: Test Enterprise Libraries - id: test - run: | - echo "## 🧪 Enterprise Library Integration Tests" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - php << 'EOF' - ApiClient::class, - 'AuditLogger' => AuditLogger::class, - 'CliFramework' => CliFramework::class, - 'Config' => Config::class, - 'EnterpriseReadinessValidator' => EnterpriseReadinessValidator::class, - 'RecoveryManager' => RecoveryManager::class, - 'InputValidator' => InputValidator::class, - 'MetricsCollector' => MetricsCollector::class, - 'RepositoryHealthChecker' => RepositoryHealthChecker::class, - 'RepositorySynchronizer' => RepositorySynchronizer::class, - 'SecurityValidator' => SecurityValidator::class, - 'TransactionManager' => TransactionManager::class, - 'UnifiedValidation' => UnifiedValidation::class - ]; - - $libraryFilter = '${{ github.event.inputs.library_filter }}'; - if (!empty($libraryFilter)) { - $libraries = array_filter($libraries, function($name) use ($libraryFilter) { - return stripos($name, $libraryFilter) !== false; - }, ARRAY_FILTER_USE_KEY); - } - - echo "Testing " . count($libraries) . " enterprise libraries...\n\n"; - - foreach ($libraries as $libName => $libClass) { - try { - $startTime = microtime(true); - - // Library-specific tests - $testPassed = true; - $testDetails = ['version' => '04.00.04']; - - switch ($libName) { - case 'ApiClient': - $cfg = \MokoEnterprise\Config::load(); - $adp = \MokoEnterprise\PlatformAdapterFactory::create($cfg); - $client = $adp->getApiClient(); - $testDetails['circuit_state'] = $client->getCircuitState(); - break; - - case 'AuditLogger': - $logger = new AuditLogger('test'); - $testDetails['service'] = 'test'; - break; - - case 'MetricsCollector': - $metrics = new MetricsCollector('test'); - $metrics->increment('test_metric'); - $testDetails['test_metric'] = 1; - break; - - case 'SecurityValidator': - $validator = new SecurityValidator(); - $testDetails['version'] = $validator->getVersion(); - break; - - case 'Config': - $config = Config::load('test'); - $testDetails['config_loaded'] = true; - $testDetails['environment'] = $config->get('environment'); - break; - - case 'RecoveryManager': - $recovery = new RecoveryManager(); - $testDetails['initialized'] = true; - break; - - default: - // Basic instantiation test for other libraries - if (class_exists($libClass)) { - $testDetails['class_exists'] = true; - } - break; - } - - $elapsed = microtime(true) - $startTime; - $testDetails['load_time_ms'] = round($elapsed * 1000, 2); - - $testResults[$libName] = [ - 'status' => 'passed', - 'details' => $testDetails - ]; - echo "✅ {$libName}: PASSED ({$testDetails['load_time_ms']}ms)\n"; - - } catch (Exception $e) { - $testResults[$libName] = [ - 'status' => 'failed', - 'error' => $e->getMessage() - ]; - $failedTests[] = $libName; - echo "❌ {$libName}: FAILED - {$e->getMessage()}\n"; - } - } - - // Save test results - file_put_contents('logs/tests/integration-results.json', json_encode($testResults, JSON_PRETTY_PRINT)); - - // Summary - $passed = count(array_filter($testResults, fn($r) => $r['status'] === 'passed')); - $total = count($testResults); - - $summary = [ - 'total' => $total, - 'passed' => $passed, - 'failed' => count($failedTests), - 'failed_libs' => $failedTests - ]; - file_put_contents('/tmp/test_summary.json', json_encode($summary)); - - echo "\n" . str_repeat('=', 50) . "\n"; - echo "Total: {$total} | Passed: {$passed} | Failed: " . count($failedTests) . "\n"; - echo str_repeat('=', 50) . "\n"; - - if (count($failedTests) > 0) { - exit(1); - } - EOF - - # Read summary and output - if [ -f "/tmp/test_summary.json" ]; then - SUMMARY=$(cat /tmp/test_summary.json) - TOTAL=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["total"];') - PASSED=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["passed"];') - FAILED=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["failed"];') - - echo "total_tests=$TOTAL" >> $GITHUB_OUTPUT - echo "passed_tests=$PASSED" >> $GITHUB_OUTPUT - echo "failed_tests=$FAILED" >> $GITHUB_OUTPUT - - echo "### Test Results" >> $GITHUB_STEP_SUMMARY - echo "- Total libraries: **${TOTAL}**" >> $GITHUB_STEP_SUMMARY - echo "- Passed: **${PASSED}** ✅" >> $GITHUB_STEP_SUMMARY - echo "- Failed: **${FAILED}** ❌" >> $GITHUB_STEP_SUMMARY - fi - - - name: Performance Benchmarks - if: github.event.inputs.run_benchmarks != 'false' - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "## ⚡ Performance Benchmarks" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - php << 'EOF' - getApiClient(); - $start = microtime(true); - for ($i = 0; $i < 100; $i++) { - // Get metrics to test circuit breaker monitoring - $metrics = $client->getMetrics(); - } - $elapsed = microtime(true) - $start; - $benchmarks['api_client_metrics'] = [ - 'iterations' => 100, - 'total_time_ms' => round($elapsed * 1000, 2), - 'avg_time_ms' => round($elapsed * 1000 / 100, 4) - ]; - echo "✅ API Client metrics: {$benchmarks['api_client_metrics']['avg_time_ms']}ms/op\n"; - - // Benchmark 2: Audit logging - $logger = new AuditLogger('benchmark'); - $start = microtime(true); - for ($i = 0; $i < 100; $i++) { - $logger->logEvent('test_event', ['iteration' => $i]); - } - $elapsed = microtime(true) - $start; - $benchmarks['audit_logging'] = [ - 'iterations' => 100, - 'total_time_ms' => round($elapsed * 1000, 2), - 'avg_time_ms' => round($elapsed * 1000 / 100, 4) - ]; - echo "✅ Audit logging: {$benchmarks['audit_logging']['avg_time_ms']}ms/op\n"; - - // Benchmark 3: Metrics collection - $metrics = new MetricsCollector('benchmark'); - $start = microtime(true); - for ($i = 0; $i < 1000; $i++) { - $metrics->increment('test_counter'); - $metrics->setGauge('test_gauge', $i); - } - $elapsed = microtime(true) - $start; - $benchmarks['metrics_collection'] = [ - 'iterations' => 1000, - 'total_time_ms' => round($elapsed * 1000, 2), - 'avg_time_ms' => round($elapsed * 1000 / 1000, 4) - ]; - echo "✅ Metrics collection: {$benchmarks['metrics_collection']['avg_time_ms']}ms/op\n"; - - // Benchmark 4: Security scanning - $validator = new SecurityValidator(); - $testContent = str_repeat("password = 'test123'\napi_key = 'secret'\n", 10); - $start = microtime(true); - for ($i = 0; $i < 10; $i++) { - $validator->scanContent($testContent, 'test.php'); - } - $elapsed = microtime(true) - $start; - $benchmarks['security_scanning'] = [ - 'iterations' => 10, - 'total_time_ms' => round($elapsed * 1000, 2), - 'avg_time_ms' => round($elapsed * 1000 / 10, 4) - ]; - echo "✅ Security scanning: {$benchmarks['security_scanning']['avg_time_ms']}ms/op\n"; - - // Save benchmarks - file_put_contents('logs/benchmarks/performance.json', json_encode($benchmarks, JSON_PRETTY_PRINT)); - - echo "\n✅ All benchmarks completed\n"; - - } catch (Exception $e) { - echo "⚠️ Benchmark failed: {$e->getMessage()}\n"; - } - EOF - - if [ -f "logs/benchmarks/performance.json" ]; then - echo "✅ Performance benchmarks completed" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "See artifacts for detailed results" >> $GITHUB_STEP_SUMMARY - fi - - - name: Generate Integration Report - if: always() - run: | - php << 'EOF' - date('c'), - 'repository' => 'MokoStandards', - 'trigger' => '${{ github.event_name }}' - ]; - - // Load test results - $testFile = 'logs/tests/integration-results.json'; - if (file_exists($testFile)) { - $report['tests'] = json_decode(file_get_contents($testFile), true); - } - - // Load benchmarks - $benchFile = 'logs/benchmarks/performance.json'; - if (file_exists($benchFile)) { - $report['benchmarks'] = json_decode(file_get_contents($benchFile), true); - } - - // Save report - file_put_contents('logs/reports/integration-report.json', json_encode($report, JSON_PRETTY_PRINT)); - - echo "✅ Integration report generated\n"; - - } catch (Exception $e) { - echo "⚠️ Report generation failed: {$e->getMessage()}\n"; - } - EOF - - - name: Upload Test Results - if: always() - uses: actions/upload-artifact@v6.0.0 - with: - name: integration-test-results-${{ github.run_number }} - path: | - logs/tests/ - logs/benchmarks/ - logs/reports/integration-report.json - retention-days: 30 - - - name: Notify on Failure - if: failure() - run: | - echo "❌ Integration tests failed" >> $GITHUB_STEP_SUMMARY - echo "One or more library integrations are broken" >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/joomla/auto-release.yml.template b/templates/workflows/joomla/auto-release.yml.template deleted file mode 100644 index 252ad8b..0000000 --- a/templates/workflows/joomla/auto-release.yml.template +++ /dev/null @@ -1,670 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Release -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/joomla/auto-release.yml.template -# VERSION: 04.06.00 -# BRIEF: Joomla build & release — ZIP package, updates.xml, SHA-256 checksum -# -# +========================================================================+ -# | BUILD & RELEASE PIPELINE (JOOMLA) | -# +========================================================================+ -# | | -# | Triggers on push to main (skips bot commits + [skip ci]): | -# | | -# | Every push: | -# | 1. Read version from README.md | -# | 3. Set platform version (Joomla ) | -# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files | -# | 5. Write updates.xml (Joomla update server XML) | -# | 6. Create git tag vXX.YY.ZZ | -# | 7a. Patch: update existing Gitea Release for this minor | -# | 8. Build ZIP, upload asset, write SHA-256 to updates.xml | -# | | -# | Every version change: archives main -> version/XX.YY branch | -# | Patch 00 = development (no release). First release = patch 01. | -# | First release only (patch == 01): | -# | 7b. Create new Gitea Release | -# | | -# | GitHub mirror: stable/rc releases only (continue-on-error) | -# | | -# +========================================================================+ - -name: Build & Release - -on: - pull_request: - types: [closed] - branches: - - main - paths: - - 'src/**' - - 'htdocs/**' - workflow_dispatch: - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - GITEA_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 }} - -permissions: - contents: write - -jobs: - release: - name: Build & Release Pipeline - runs-on: ubuntu-latest - if: >- - github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - token: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 - - - name: Setup MokoStandards tools - env: - MOKO_CLONE_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' - run: | - git clone --depth 1 --branch {{standards_branch}} --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ - /tmp/mokostandards-api - cd /tmp/mokostandards-api - composer install --no-dev --no-interaction --quiet - - # -- STEP 1: Read version ----------------------------------------------- - - name: "Step 1: Read version from README.md" - id: version - run: | - VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null) - if [ -z "$VERSION" ]; then - echo "No VERSION in README.md — skipping release" - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - # Derive major.minor for branch naming (patches update existing branch) - MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}') - PATCH=$(echo "$VERSION" | awk -F. '{print $3}') - - MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') - MINOR_NUM=$(echo "$VERSION" | awk -F. '{print $2}') - - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT" - echo "minor=$MINOR" >> "$GITHUB_OUTPUT" - echo "major=$MAJOR" >> "$GITHUB_OUTPUT" - echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT" - # Determine stability for mirror gating - echo "stability=stable" >> "$GITHUB_OUTPUT" - if [ "$PATCH" = "00" ]; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "is_minor=false" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (patch 00 = development — skipping release)" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - if [ "$PATCH" = "01" ]; then - echo "is_minor=true" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (first release — full pipeline)" - else - echo "is_minor=false" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (patch — platform version + badges only)" - fi - fi - - - name: Check if already released - if: steps.version.outputs.skip != 'true' - id: check - run: | - TAG="${{ steps.version.outputs.release_tag }}" - BRANCH="${{ steps.version.outputs.branch }}" - - TAG_EXISTS=false - BRANCH_EXISTS=false - - git rev-parse "$TAG" >/dev/null 2>&1 && TAG_EXISTS=true - git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q "$BRANCH" && BRANCH_EXISTS=true - - echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT" - echo "branch_exists=$BRANCH_EXISTS" >> "$GITHUB_OUTPUT" - - if [ "$TAG_EXISTS" = "true" ] && [ "$BRANCH_EXISTS" = "true" ]; then - echo "already_released=true" >> "$GITHUB_OUTPUT" - else - echo "already_released=false" >> "$GITHUB_OUTPUT" - fi - - # -- SANITY CHECKS ------------------------------------------------------- - - name: "Sanity: Pre-release validation" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - ERRORS=0 - - echo "## Pre-Release Sanity Checks (Joomla)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # -- Version drift check (must pass before release) -------- - README_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1) - if [ "$README_VER" != "$VERSION" ]; then - echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - else - echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - fi - - # Check CHANGELOG version matches - CL_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' CHANGELOG.md 2>/dev/null | head -1) - if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then - echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - fi - - # Check composer.json version if present - if [ -f "composer.json" ]; then - COMP_VER=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' composer.json 2>/dev/null | head -1) - if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then - echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - fi - fi - - # Common checks - if [ ! -f "LICENSE" ]; then - echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - else - echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY - fi - - if [ ! -d "src" ] && [ ! -d "htdocs" ]; then - echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY - else - echo "- Source directory present" >> $GITHUB_STEP_SUMMARY - fi - - # -- Joomla: manifest version drift -------- - MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1) - if [ -n "$MANIFEST" ]; then - XML_VER=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1) - if [ -n "$XML_VER" ] && [ "$XML_VER" != "$VERSION" ]; then - echo "- Manifest drift: \`${XML_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - else - echo "- Manifest version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - fi - fi - - # -- Joomla: XML manifest existence -------- - if [ -z "$MANIFEST" ]; then - echo "- No Joomla XML manifest found" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - else - echo "- Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY - - # -- Joomla: extension type check -------- - TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null) - echo "- Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "$ERRORS" -gt 0 ]; then - echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY - else - echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY - fi - - # -- STEP 2: Create or update version/XX.YY archive branch --------------- - # Always runs — every version change on main archives to version/XX.YY - - name: "Step 2: Version archive branch" - if: steps.check.outputs.already_released != 'true' - run: | - BRANCH="${{ steps.version.outputs.branch }}" - IS_MINOR="${{ steps.version.outputs.is_minor }}" - PATCH="${{ steps.version.outputs.version }}" - PATCH_NUM=$(echo "$PATCH" | awk -F. '{print $3}') - - # Check if branch exists - if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then - git push origin HEAD:"$BRANCH" --force - echo "Updated archive branch: ${BRANCH} (patch ${PATCH_NUM})" >> $GITHUB_STEP_SUMMARY - else - git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH" - git push origin "$BRANCH" --force - echo "Created archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY - fi - - # -- STEP 3: Set platform version ---------------------------------------- - - name: "Step 3: Set platform version" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - php /tmp/mokostandards-api/cli/version_set_platform.php \ - --path . --version "$VERSION" --branch main - - # -- STEP 4: Update version badges ---------------------------------------- - - name: "Step 4: Update version badges" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - find . -name "*.md" ! -path "./.git/*" ! -path "./vendor/*" | while read -r f; do - if grep -q '\[VERSION:' "$f" 2>/dev/null; then - sed -i "s/\[VERSION:[[:space:]]*[0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{2\}\]/[VERSION: ${VERSION}]/" "$f" - fi - done - - # -- STEP 5: Write updates.xml (Joomla update server) --------------------- - - name: "Step 5: Write updates.xml" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - REPO="${{ github.repository }}" - - # -- Parse extension metadata from XML manifest ---------------- - MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1) - if [ -z "$MANIFEST" ]; then - echo "Warning: No Joomla XML manifest found — skipping updates.xml" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - # Extract fields using sed (portable — no grep -P) - EXT_NAME=$(sed -n 's/.*\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1) - EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1) - EXT_CLIENT=$(sed -n 's/.*]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_FOLDER=$(sed -n 's/.*]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - TARGET_PLATFORM=$(sed -n 's/.*\(\).*/\1/p' "$MANIFEST" | head -1) - PHP_MINIMUM=$(sed -n 's/.*\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1) - - # Fallbacks - [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}" - [ -z "$EXT_TYPE" ] && EXT_TYPE="component" - - # Templates/modules don't have — derive from (lowercased) - if [ -z "$EXT_ELEMENT" ]; then - EXT_ELEMENT=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ') - fi - - # Build client tag: plugins and frontend modules need site - CLIENT_TAG="" - if [ -n "$EXT_CLIENT" ]; then - CLIENT_TAG="${EXT_CLIENT}" - elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then - CLIENT_TAG="site" - fi - - # Build folder tag for plugins (required for Joomla to match the update) - FOLDER_TAG="" - if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then - FOLDER_TAG="${EXT_FOLDER}" - fi - - # Build targetplatform (fallback to Joomla 5 if not in manifest) - if [ -z "$TARGET_PLATFORM" ]; then - TARGET_PLATFORM=$(printf '' "/") - fi - - # Build php_minimum tag - PHP_TAG="" - if [ -n "$PHP_MINIMUM" ]; then - PHP_TAG="${PHP_MINIMUM}" - fi - - DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip" - INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/v${VERSION}" - - # -- Build stable entry to temp file - { - printf '%s\n' ' ' - printf '%s\n' " ${EXT_NAME}" - printf '%s\n' " ${EXT_NAME} update" - printf '%s\n' " ${EXT_ELEMENT}" - printf '%s\n' " ${EXT_TYPE}" - printf '%s\n' " ${VERSION}" - [ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}" - [ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}" - printf '%s\n' ' ' - printf '%s\n' ' stable' - printf '%s\n' ' ' - printf '%s\n' " ${INFO_URL}" - printf '%s\n' ' ' - printf '%s\n' " ${DOWNLOAD_URL}" - printf '%s\n' ' ' - printf '%s\n' " ${TARGET_PLATFORM}" - [ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}" - printf '%s\n' ' Moko Consulting' - printf '%s\n' ' https://mokoconsulting.tech' - printf '%s\n' ' ' - } > /tmp/stable_entry.xml - - # -- Write updates.xml preserving dev/rc entries - # Extract existing entries for other stability levels - if [ -f "updates.xml" ]; then - printf 'import re, sys\n' > /tmp/extract.py - printf 'with open("updates.xml") as f: c = f.read()\n' >> /tmp/extract.py - printf 'tag = sys.argv[1]\n' >> /tmp/extract.py - printf 'm = re.search(r"( .*?" + re.escape(tag) + r".*?)", c, re.DOTALL)\n' >> /tmp/extract.py - printf 'if m: print(m.group(1))\n' >> /tmp/extract.py - fi - DEV_ENTRY=$(python3 /tmp/extract.py development 2>/dev/null || true) - ALPHA_ENTRY=$(python3 /tmp/extract.py alpha 2>/dev/null || true) - BETA_ENTRY=$(python3 /tmp/extract.py beta 2>/dev/null || true) - RC_ENTRY=$(python3 /tmp/extract.py rc 2>/dev/null || true) - - { - printf '%s\n' '' - printf '%s\n' '' - [ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY" - [ -n "$ALPHA_ENTRY" ] && echo "$ALPHA_ENTRY" - [ -n "$BETA_ENTRY" ] && echo "$BETA_ENTRY" - [ -n "$RC_ENTRY" ] && echo "$RC_ENTRY" - cat /tmp/stable_entry.xml - printf '%s\n' '' - } > updates.xml - - echo "updates.xml: ${VERSION} (stable + rc/dev preserved)" >> $GITHUB_STEP_SUMMARY - - # -- Commit all changes --------------------------------------------------- - - name: Commit release changes - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - if git diff --quiet && git diff --cached --quiet; then - echo "No changes to commit" - exit 0 - fi - VERSION="${{ steps.version.outputs.version }}" - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add -A - git commit -m "chore(release): build ${VERSION} [skip ci]" \ - --author="github-actions[bot] " - git push - - # -- STEP 6: Create tag --------------------------------------------------- - - name: "Step 6: Create git tag" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.tag_exists != 'true' && - steps.version.outputs.is_minor == 'true' - run: | - RELEASE_TAG="${{ steps.version.outputs.release_tag }}" - # Only create the major release tag if it doesn't exist yet - if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then - git tag "$RELEASE_TAG" - git push origin "$RELEASE_TAG" - echo "Tag created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY - else - echo "Tag ${RELEASE_TAG} already exists" >> $GITHUB_STEP_SUMMARY - fi - echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY - - # -- STEP 7: Create or update Gitea Release -------------------------------- - - name: "Step 7: Gitea Release" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.tag_exists != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - RELEASE_TAG="${{ steps.version.outputs.release_tag }}" - BRANCH="${{ steps.version.outputs.branch }}" - MAJOR="${{ steps.version.outputs.major }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - - NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null) - [ -z "$NOTES" ] && NOTES="Release ${VERSION}" - - # Check if the major release already exists - EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true) - EXISTING_ID=$(echo "$EXISTING" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true) - - if [ -z "$EXISTING_ID" ]; then - # First release for this major - curl -sf -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Content-Type: application/json" \ - "${API_BASE}/releases" \ - -d "$(python3 -c "import json; print(json.dumps({ - 'tag_name': '${RELEASE_TAG}', - 'name': 'v${MAJOR} (latest: ${VERSION})', - 'body': '''${NOTES}''', - 'target_commitish': '${BRANCH}' - }))")" - echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY - else - # Append version notes to existing major release - CURRENT_BODY=$(echo "$EXISTING" | python3 -c "import sys,json; print(json.load(sys.stdin).get('body',''))" 2>/dev/null || true) - UPDATED_BODY="${CURRENT_BODY} - - --- - ### ${VERSION} - - ${NOTES}" - - curl -sf -X PATCH -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Content-Type: application/json" \ - "${API_BASE}/releases/${EXISTING_ID}" \ - -d "$(python3 -c "import json,sys; print(json.dumps({ - 'name': 'v${MAJOR} (latest: ${VERSION})', - 'body': sys.stdin.read() - }))" <<< "$UPDATED_BODY")" - echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY - fi - - # -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------ - - name: "Step 8: Build Joomla package and update checksum" - if: >- - steps.version.outputs.skip != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - RELEASE_TAG="${{ steps.version.outputs.release_tag }}" - REPO="${{ github.repository }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - - # All ZIPs upload to the major release tag (vXX) - RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true) - RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) - if [ -z "$RELEASE_ID" ]; then - echo "No release ${RELEASE_TAG} found — skipping ZIP upload" - exit 0 - fi - - # Find extension element name from manifest - MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1 || true) - [ -z "$MANIFEST" ] && exit 0 - - EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml) - ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip" - TAR_NAME="${EXT_ELEMENT}-${VERSION}.tar.gz" - - # -- Build install packages from src/ ---------------------------- - SOURCE_DIR="src" - [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" - [ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ — skipping package"; exit 0; } - - EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*" - - # ZIP package - cd "$SOURCE_DIR" - zip -r "/tmp/${ZIP_NAME}" . -x $EXCLUDES - cd .. - - # tar.gz package - tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \ - --exclude='.ftpignore' --exclude='sftp-config*' \ - --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' . - - ZIP_SIZE=$(stat -c%s "/tmp/${ZIP_NAME}" 2>/dev/null || stat -f%z "/tmp/${ZIP_NAME}" 2>/dev/null || echo "unknown") - TAR_SIZE=$(stat -c%s "/tmp/${TAR_NAME}" 2>/dev/null || stat -f%z "/tmp/${TAR_NAME}" 2>/dev/null || echo "unknown") - - # -- Calculate SHA-256 for both ---------------------------------- - SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1) - SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1) - - # -- Delete existing assets with same name before uploading ------ - ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]") - for ASSET_NAME in "$ZIP_NAME" "$TAR_NAME"; do - ASSET_ID=$(echo "$ASSETS" | python3 -c " - import sys,json - assets = json.load(sys.stdin) - for a in assets: - if a['name'] == '${ASSET_NAME}': - print(a['id']); break - " 2>/dev/null || true) - if [ -n "$ASSET_ID" ]; then - curl -sf -X DELETE -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true - fi - done - - # -- Upload both to release tag ---------------------------------- - curl -sf -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @"/tmp/${ZIP_NAME}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" > /dev/null 2>&1 || true - - curl -sf -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @"/tmp/${TAR_NAME}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true - - # -- Update updates.xml with both download formats --------------- - if [ -f "updates.xml" ]; then - ZIP_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}" - TAR_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${TAR_NAME}" - - # Use Python to update only the stable entry's downloads + sha256 - python3 << 'PYEOF' - import re - - with open("updates.xml") as f: - content = f.read() - - zip_url = "${ZIP_URL}" - tar_url = "${TAR_URL}" - sha = "${SHA256_ZIP}" - - # Find the stable update block and replace its downloads + sha256 - def replace_stable(m): - block = m.group(0) - # Replace downloads block - new_downloads = ( - " \n" - f" {zip_url}\n" - f" {tar_url}\n" - " " - ) - block = re.sub(r' .*?', new_downloads, block, flags=re.DOTALL) - # Add or replace sha256 - if '' in block: - block = re.sub(r' .*?', f' sha256:{sha}', block) - else: - block = block.replace('', f'\n sha256:{sha}') - return block - - content = re.sub( - r' .*?stable.*?', - replace_stable, - content, - flags=re.DOTALL - ) - - with open("updates.xml", "w") as f: - f.write(content) - PYEOF - - git add updates.xml - git commit -m "chore(release): ZIP + tar.gz for ${VERSION} [skip ci]" \ - --author="github-actions[bot] " || true - git push || true - fi - - echo "### Joomla Packages" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Package | Size | SHA-256 |" >> $GITHUB_STEP_SUMMARY - echo "|---------|------|---------|" >> $GITHUB_STEP_SUMMARY - echo "| \`${ZIP_NAME}\` | ${ZIP_SIZE} | \`${SHA256_ZIP}\` |" >> $GITHUB_STEP_SUMMARY - echo "| \`${TAR_NAME}\` | ${TAR_SIZE} | \`${SHA256_TAR}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY - echo "| Download | [${ZIP_NAME}](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}) |" >> $GITHUB_STEP_SUMMARY - - # -- STEP 9: Mirror to GitHub (stable only) -------------------------------- - - name: "Step 9: Mirror release to GitHub" - if: >- - steps.version.outputs.skip != 'true' && - steps.version.outputs.stability == 'stable' && - secrets.GH_MIRROR_TOKEN != '' - continue-on-error: true - env: - GH_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }} - run: | - VERSION="${{ steps.version.outputs.version }}" - RELEASE_TAG="${{ steps.version.outputs.release_tag }}" - MAJOR="${{ steps.version.outputs.major }}" - BRANCH="${{ steps.version.outputs.branch }}" - GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" - - NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null || true) - [ -z "$NOTES" ] && NOTES="Release ${VERSION}" - echo "$NOTES" > /tmp/release_notes.md - - EXISTING=$(gh release view "$RELEASE_TAG" --repo "$GH_REPO" --json tagName -q .tagName 2>/dev/null || true) - - if [ -z "$EXISTING" ]; then - gh release create "$RELEASE_TAG" \ - --repo "$GH_REPO" \ - --title "v${MAJOR} (latest: ${VERSION})" \ - --notes-file /tmp/release_notes.md \ - --target "$BRANCH" || true - else - gh release edit "$RELEASE_TAG" \ - --repo "$GH_REPO" \ - --title "v${MAJOR} (latest: ${VERSION})" || true - fi - - # Upload assets to GitHub mirror - for PKG in /tmp/${EXT_ELEMENT:-pkg}-${VERSION}.*; do - [ -f "$PKG" ] && gh release upload "$RELEASE_TAG" "$PKG" --repo "$GH_REPO" --clobber 2>/dev/null || true - done - echo "GitHub mirror updated: ${GH_REPO} ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY - - # -- Summary -------------------------------------------------------------- - - name: Pipeline Summary - if: always() - run: | - VERSION="${{ steps.version.outputs.version }}" - if [ "${{ steps.version.outputs.skip }}" = "true" ]; then - echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY - echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY - elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then - echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY - else - echo "" >> $GITHUB_STEP_SUMMARY - echo "## Build & Release Complete (Joomla)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY - echo "|------|--------|" >> $GITHUB_STEP_SUMMARY - 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 - fi diff --git a/templates/workflows/joomla/ci-joomla.yml.template b/templates/workflows/joomla/ci-joomla.yml.template deleted file mode 100644 index 348cdde..0000000 --- a/templates/workflows/joomla/ci-joomla.yml.template +++ /dev/null @@ -1,386 +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: Gitea.Workflow.Template -# INGROUP: MokoStandards.CI -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/joomla/ci-joomla.yml.template -# VERSION: 04.06.00 -# BRIEF: CI workflow for Joomla extensions — lint, validate, test -# NOTE: Deployed to .github/workflows/ci-joomla.yml in governed Joomla extension repos. - -name: Joomla Extension CI - -on: - pull_request: - branches: - - main - - 'dev/**' - workflow_dispatch: - -permissions: - contents: read - pull-requests: write - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - lint-and-validate: - name: Lint & Validate - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0 - with: - php-version: '8.2' - extensions: mbstring, xml, zip, gd, curl, json, simplexml - tools: composer:v2 - coverage: none - - - name: Clone MokoStandards - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} - run: | - git clone --depth 1 --branch {{standards_branch}} --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ - /tmp/mokostandards-api - - - name: Install dependencies - env: - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - if [ -f "composer.json" ]; then - composer install \ - --no-interaction \ - --prefer-dist \ - --optimize-autoloader - else - echo "No composer.json found — skipping dependency install" - fi - - - name: PHP syntax check - run: | - ERRORS=0 - for DIR in src/ htdocs/; do - if [ -d "$DIR" ]; then - FOUND=1 - while IFS= read -r -d '' FILE; do - OUTPUT=$(php -l "$FILE" 2>&1) - if echo "$OUTPUT" | grep -q "Parse error"; then - echo "::error file=${FILE}::${OUTPUT}" - ERRORS=$((ERRORS + 1)) - fi - done < <(find "$DIR" -name "*.php" -print0) - fi - done - echo "### PHP Syntax Check" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} syntax error(s) found.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY - fi - - - name: XML manifest validation - run: | - echo "### XML Manifest Validation" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - # Find the extension manifest (XML with /dev/null; then - MANIFEST="$XML_FILE" - break - fi - done - - if [ -z "$MANIFEST" ]; then - echo "No Joomla extension manifest found (XML file with \`> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Manifest found: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY - - # Validate well-formed XML - php -r " - \$xml = @simplexml_load_file('$MANIFEST'); - if (\$xml === false) { - echo 'INVALID'; - exit(1); - } - echo 'VALID'; - " > /tmp/xml_result 2>&1 - XML_RESULT=$(cat /tmp/xml_result) - if [ "$XML_RESULT" != "VALID" ]; then - echo "Manifest is not well-formed XML." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Manifest is well-formed XML." >> $GITHUB_STEP_SUMMARY - fi - - # Check required tags: name, version, author, namespace (Joomla 5+) - for TAG in name version author namespace; do - if ! grep -q "<${TAG}>" "$MANIFEST" 2>/dev/null; then - echo "Missing required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Found required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY - fi - done - fi - - if [ "${ERRORS}" -gt 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "**${ERRORS} manifest issue(s) found.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Manifest validation passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Check language files referenced in manifest - run: | - echo "### Language File Check" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - MANIFEST="" - for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do - if grep -q "/dev/null; then - MANIFEST="$XML_FILE" - break - fi - done - - if [ -n "$MANIFEST" ]; then - # Extract language file references from manifest - LANG_FILES=$(grep -oP 'language\s+tag="[^"]*"[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true) - if [ -z "$LANG_FILES" ]; then - echo "No language file references found in manifest — skipping." >> $GITHUB_STEP_SUMMARY - else - while IFS= read -r LANG_FILE; do - LANG_FILE=$(echo "$LANG_FILE" | xargs) - if [ -z "$LANG_FILE" ]; then - continue - fi - # Check in common locations - FOUND=0 - for BASE in "." "src" "htdocs"; do - if [ -f "${BASE}/${LANG_FILE}" ]; then - FOUND=1 - break - fi - done - if [ "$FOUND" -eq 0 ]; then - echo "Missing language file: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Language file present: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY - fi - done <<< "$LANG_FILES" - fi - else - echo "No manifest found — skipping language check." >> $GITHUB_STEP_SUMMARY - fi - - if [ "${ERRORS}" -gt 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "**${ERRORS} missing language file(s).**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Language file check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Check index.html files in directories - run: | - echo "### Index.html Check" >> $GITHUB_STEP_SUMMARY - MISSING=0 - CHECKED=0 - - for DIR in src/ htdocs/; do - if [ -d "$DIR" ]; then - while IFS= read -r -d '' SUBDIR; do - CHECKED=$((CHECKED + 1)) - if [ ! -f "${SUBDIR}/index.html" ]; then - echo "Missing index.html in: \`${SUBDIR}\`" >> $GITHUB_STEP_SUMMARY - MISSING=$((MISSING + 1)) - fi - done < <(find "$DIR" -type d -print0) - fi - done - - if [ "${CHECKED}" -eq 0 ]; then - echo "No src/ or htdocs/ directories found — skipping." >> $GITHUB_STEP_SUMMARY - elif [ "${MISSING}" -gt 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "**${MISSING} director(ies) missing index.html out of ${CHECKED} checked.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "All ${CHECKED} directories contain index.html." >> $GITHUB_STEP_SUMMARY - fi - - release-readiness: - name: Release Readiness Check - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' && github.base_ref == 'main' - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Validate release readiness - run: | - echo "## Release Readiness" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - # Extract version from README.md - README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1) - if [ -z "$README_VERSION" ]; then - echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "README version: \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY - fi - - # Find the extension manifest - MANIFEST="" - for XML_FILE in $(find . -maxdepth 2 -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 "No Joomla extension manifest found." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY - - # Check matches README VERSION - MANIFEST_VERSION=$(grep -oP '\K[^<]+' "$MANIFEST" | head -1) - if [ -z "$MANIFEST_VERSION" ]; then - echo "No \`\` tag in manifest." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - elif [ -n "$README_VERSION" ] && [ "$MANIFEST_VERSION" != "$README_VERSION" ]; then - echo "Manifest version \`${MANIFEST_VERSION}\` does not match README \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Manifest version: \`${MANIFEST_VERSION}\`" >> $GITHUB_STEP_SUMMARY - fi - - # Check extension type, element, client attributes - EXT_TYPE=$(grep -oP ']*\btype="\K[^"]+' "$MANIFEST" | head -1) - if [ -z "$EXT_TYPE" ]; then - echo "Missing \`type\` attribute on \`\` tag." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Extension type: \`${EXT_TYPE}\`" >> $GITHUB_STEP_SUMMARY - fi - - # Element check (component/module/plugin name) - HAS_ELEMENT=$(grep -cP '<(element|name)>' "$MANIFEST" 2>/dev/null || echo "0") - if [ "$HAS_ELEMENT" -eq 0 ]; then - echo "Missing \`\` or \`\` in manifest." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - - # Client attribute for site/admin modules and plugins - if echo "$EXT_TYPE" | grep -qP "^(module|plugin)$"; then - HAS_CLIENT=$(grep -cP ']*\bclient=' "$MANIFEST" 2>/dev/null || echo "0") - if [ "$HAS_CLIENT" -eq 0 ]; then - echo "Missing \`client\` attribute for ${EXT_TYPE} extension." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - fi - fi - - # Check updates.xml exists - if [ -f "updates.xml" ] || [ -f "updates.xml" ]; then - echo "Update XML present." >> $GITHUB_STEP_SUMMARY - else - echo "No updates.xml found." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - - # Check CHANGELOG.md exists - if [ -f "CHANGELOG.md" ]; then - echo "CHANGELOG.md present." >> $GITHUB_STEP_SUMMARY - else - echo "No CHANGELOG.md found." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ $ERRORS -gt 0 ]; then - echo "**${ERRORS} issue(s) must be resolved before release.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**Extension is ready for release.**" >> $GITHUB_STEP_SUMMARY - fi - - test: - name: Tests (PHP ${{ matrix.php }}) - runs-on: ubuntu-latest - needs: lint-and-validate - - strategy: - fail-fast: false - matrix: - php: ['8.2', '8.3'] - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup PHP ${{ matrix.php }} - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0 - with: - php-version: ${{ matrix.php }} - extensions: mbstring, xml, zip, gd, curl, json, simplexml - tools: composer:v2 - coverage: none - - - name: Install dependencies - env: - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - if [ -f "composer.json" ]; then - composer install \ - --no-interaction \ - --prefer-dist \ - --optimize-autoloader - else - echo "No composer.json found — skipping dependency install" - fi - - - name: Run tests - run: | - echo "### Test Results (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY - if [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then - vendor/bin/phpunit --testdox 2>&1 | tee /tmp/test-output.log - EXIT=${PIPESTATUS[0]} - if [ $EXIT -eq 0 ]; then - echo "All tests passed." >> $GITHUB_STEP_SUMMARY - else - echo "Test failures detected — see log." >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - cat /tmp/test-output.log >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - fi - exit $EXIT - else - echo "No phpunit.xml found — skipping tests." >> $GITHUB_STEP_SUMMARY - fi diff --git a/templates/workflows/joomla/deploy-manual.yml.template b/templates/workflows/joomla/deploy-manual.yml.template deleted file mode 100644 index a57be26..0000000 --- a/templates/workflows/joomla/deploy-manual.yml.template +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Deploy -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/joomla/deploy-manual.yml.template -# VERSION: 04.06.00 -# BRIEF: Manual SFTP deploy to dev server for Joomla repos -# NOTE: Joomla repos use update.xml for distribution. This is for manual -# dev server testing only — triggered via workflow_dispatch. - -name: 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 - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0 - with: - php-version: '8.2' - extensions: json, ssh2 - tools: composer - coverage: none - - - name: Setup MokoStandards tools - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - git clone --depth 1 --branch {{standards_branch}} --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ - /tmp/mokostandards-api 2>/dev/null || true - if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then - cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true - fi - - - name: Check FTP configuration - id: check - env: - HOST: ${{ vars.DEV_FTP_HOST }} - PATH_VAR: ${{ vars.DEV_FTP_PATH }} - SUFFIX: ${{ vars.DEV_FTP_SUFFIX }} - 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%/}" - [ -n "$SUFFIX" ] && REMOTE="${REMOTE}/${SUFFIX#/}" - 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/templates/workflows/joomla/index.md b/templates/workflows/joomla/index.md deleted file mode 100644 index 545291a..0000000 --- a/templates/workflows/joomla/index.md +++ /dev/null @@ -1,24 +0,0 @@ -# Docs Index: /templates/workflows/joomla - -## Purpose - -This directory contains GitHub Actions workflow templates specifically designed for Joomla extension development. - -## Available Templates - -- **ci.yml** - Continuous integration workflow with PHP validation, XML checking, and manifest verification -- **test.yml** - Comprehensive testing workflow with PHPUnit, code quality checks, and integration tests -- **release.yml** - Automated release workflow for creating and publishing Joomla extension packages -- **repo_health.yml** - Repository health monitoring including documentation checks and standards validation -- **version_branch.yml** - Automated version branch management and release preparation - -## 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/workflows/joomla/repo_health.yml.template b/templates/workflows/joomla/repo_health.yml.template deleted file mode 100644 index 13e46ad..0000000 --- a/templates/workflows/joomla/repo_health.yml.template +++ /dev/null @@ -1,789 +0,0 @@ -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Validation -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /.github/workflows/repo_health.yml -# VERSION: 04.06.00 -# BRIEF: Enforces repository guardrails by validating release configuration, scripts governance, tooling availability, and core repository health artifacts. -# NOTE: Field is user-managed. -# ============================================================================ - -name: Repo Health - -concurrency: - group: repo-health-${{ github.repository }}-${{ github.ref }} - cancel-in-progress: true - -defaults: - run: - shell: bash - -on: - workflow_dispatch: - inputs: - profile: - description: 'Validation profile: all, release, scripts, or repo' - required: true - default: all - type: choice - options: - - all - - release - - scripts - - repo - pull_request: - push: - -permissions: - contents: read - -env: - # Release policy - Repository Variables Only - RELEASE_REQUIRED_REPO_VARS: RS_FTP_PATH_SUFFIX - RELEASE_OPTIONAL_REPO_VARS: DEV_FTP_SUFFIX - - # Scripts governance policy - # Note: directories listed without a trailing slash. - SCRIPTS_REQUIRED_DIRS: - SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate - - # Repo health policy - # Files are listed as-is; directories must end with a trailing slash. - REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.github/workflows/ - REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/ - REPO_DISALLOWED_DIRS: - REPO_DISALLOWED_FILES: TODO.md,todo.md - - # Extended checks toggles - EXTENDED_CHECKS: "true" - - # File / directory variables (moved to top-level env) - DOCS_INDEX: docs/docs-index.md - SCRIPT_DIR: scripts - WORKFLOWS_DIR: .github/workflows - SHELLCHECK_PATTERN: '*.sh' - SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml' - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - access_check: - name: Access control - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: read - - outputs: - allowed: ${{ steps.perm.outputs.allowed }} - permission: ${{ steps.perm.outputs.permission }} - - steps: - - name: Check actor permission (admin only) - id: perm - env: - TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - REPO: ${{ github.repository }} - ACTOR: ${{ github.actor }} - run: | - set -euo pipefail - ALLOWED=false - PERMISSION=unknown - METHOD="" - - # Hardcoded authorized users — always allowed - case "$ACTOR" in - jmiller-moko|github-actions\[bot\]) - ALLOWED=true - PERMISSION=admin - METHOD="hardcoded allowlist" - ;; - *) - # Detect platform and check permissions via API - API_BASE="${GITHUB_API_URL:-${GITEA_API_URL:-https://api.github.com}}" - RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \ - "${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}') - PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown") - if [ "$PERMISSION" = "admin" ] || [ "$PERMISSION" = "maintain" ] || [ "$PERMISSION" = "owner" ]; then - ALLOWED=true - fi - METHOD="collaborator API" - ;; - esac - - echo "permission=${PERMISSION}" >> "$GITHUB_OUTPUT" - echo "allowed=${ALLOWED}" >> "$GITHUB_OUTPUT" - - { - echo "## 🔐 Access Authorization" - echo "" - echo "| Field | Value |" - echo "|-------|-------|" - echo "| **Actor** | \`${ACTOR}\` |" - echo "| **Repository** | \`${REPO}\` |" - echo "| **Permission** | \`${PERMISSION}\` |" - echo "| **Method** | ${METHOD} |" - echo "| **Authorized** | ${ALLOWED} |" - echo "" - if [ "$ALLOWED" = "true" ]; then - echo "✅ ${ACTOR} authorized (${METHOD})" - else - echo "❌ ${ACTOR} is NOT authorized. Requires admin or maintain role." - fi - } >> "${GITHUB_STEP_SUMMARY}" - - - name: Deny execution when not permitted - if: ${{ steps.perm.outputs.allowed != 'true' }} - run: | - set -euo pipefail - printf '%s\n' 'ERROR: Access denied. Admin permission required.' >> "${GITHUB_STEP_SUMMARY}" - exit 1 - - release_config: - name: Release configuration - needs: access_check - if: ${{ needs.access_check.outputs.allowed == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Guardrails release vars - env: - PROFILE_RAW: ${{ github.event.inputs.profile }} - RS_FTP_PATH_SUFFIX: ${{ vars.RS_FTP_PATH_SUFFIX }} - DEV_FTP_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }} - run: | - set -euo pipefail - - profile="${PROFILE_RAW:-all}" - case "${profile}" in - all|release|scripts|repo) ;; - *) - printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" - exit 1 - ;; - esac - - if [ "${profile}" = 'scripts' ] || [ "${profile}" = 'repo' ]; then - { - printf '%s\n' '### Release configuration (Repository Variables)' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' 'Status: SKIPPED' - printf '%s\n' 'Reason: profile excludes release validation' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - IFS=',' read -r -a required <<< "${RELEASE_REQUIRED_REPO_VARS}" - IFS=',' read -r -a optional <<< "${RELEASE_OPTIONAL_REPO_VARS}" - - missing=() - missing_optional=() - - for k in "${required[@]}"; do - v="${!k:-}" - [ -z "${v}" ] && missing+=("${k}") - done - - for k in "${optional[@]}"; do - v="${!k:-}" - [ -z "${v}" ] && missing_optional+=("${k}") - done - - { - printf '%s\n' '### Release configuration (Repository Variables)' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' '| Variable | Status |' - printf '%s\n' '|---|---|' - printf '%s\n' "| RS_FTP_PATH_SUFFIX | ${RS_FTP_PATH_SUFFIX:-NOT SET} |" - printf '%s\n' "| DEV_FTP_SUFFIX | ${DEV_FTP_SUFFIX:-NOT SET} |" - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - if [ "${#missing_optional[@]}" -gt 0 ]; then - { - printf '%s\n' '### Missing optional repository variables' - for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - if [ "${#missing[@]}" -gt 0 ]; then - { - printf '%s\n' '### Missing required repository variables' - for m in "${missing[@]}"; do printf '%s\n' "- ${m}"; done - printf '%s\n' 'ERROR: Guardrails failed. Missing required repository variables.' - } >> "${GITHUB_STEP_SUMMARY}" - exit 1 - fi - - { - printf '%s\n' '### Repository variables validation result' - printf '%s\n' 'Status: OK' - printf '%s\n' 'All required repository variables present.' - printf '%s\n' '' - printf '%s\n' '**Note**: Organization secrets (RS_FTP_HOST, RS_FTP_USER, etc.) are validated at deployment time, not in repository health checks.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - scripts_governance: - name: Scripts governance - needs: access_check - if: ${{ needs.access_check.outputs.allowed == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Scripts folder checks - env: - PROFILE_RAW: ${{ github.event.inputs.profile }} - run: | - set -euo pipefail - - profile="${PROFILE_RAW:-all}" - case "${profile}" in - all|release|scripts|repo) ;; - *) - printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" - exit 1 - ;; - esac - - if [ "${profile}" = 'release' ] || [ "${profile}" = 'repo' ]; then - { - printf '%s\n' '### Scripts governance' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' 'Status: SKIPPED' - printf '%s\n' 'Reason: profile excludes scripts governance' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - if [ ! -d "${SCRIPT_DIR}" ]; then - { - printf '%s\n' '### Scripts governance' - printf '%s\n' 'Status: OK (advisory)' - printf '%s\n' 'scripts/ directory not present. No scripts governance enforced.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}" - IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}" - - missing_dirs=() - unapproved_dirs=() - - for d in "${required_dirs[@]}"; do - req="${d%/}" - [ ! -d "${req}" ] && missing_dirs+=("${req}/") - done - - while IFS= read -r d; do - allowed=false - for a in "${allowed_dirs[@]}"; do - a_norm="${a%/}" - [ "${d%/}" = "${a_norm}" ] && allowed=true - done - [ "${allowed}" = false ] && unapproved_dirs+=("${d%/}/") - done < <(find "${SCRIPT_DIR}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sed 's#^\./##') - - { - printf '%s\n' '### Scripts governance' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' '| Area | Status | Notes |' - printf '%s\n' '|---|---|---|' - - if [ "${#missing_dirs[@]}" -gt 0 ]; then - printf '%s\n' '| Required directories | Warning | Missing required subfolders |' - else - printf '%s\n' '| Required directories | OK | All required subfolders present |' - fi - - if [ "${#unapproved_dirs[@]}" -gt 0 ]; then - printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |' - else - printf '%s\n' '| Directory policy | OK | No unapproved directories |' - fi - - printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |' - printf '\n' - - if [ "${#missing_dirs[@]}" -gt 0 ]; then - printf '%s\n' 'Missing required script directories:' - for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - else - printf '%s\n' 'Missing required script directories: none.' - printf '\n' - fi - - if [ "${#unapproved_dirs[@]}" -gt 0 ]; then - printf '%s\n' 'Unapproved script directories detected:' - for m in "${unapproved_dirs[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - else - printf '%s\n' 'Unapproved script directories detected: none.' - printf '\n' - fi - - printf '%s\n' 'Scripts governance completed in advisory mode.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - repo_health: - name: Repository health - needs: access_check - if: ${{ needs.access_check.outputs.allowed == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Repository health checks - env: - PROFILE_RAW: ${{ github.event.inputs.profile }} - run: | - set -euo pipefail - - profile="${PROFILE_RAW:-all}" - case "${profile}" in - all|release|scripts|repo) ;; - *) - printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" - exit 1 - ;; - esac - - if [ "${profile}" = 'release' ] || [ "${profile}" = 'scripts' ]; then - { - printf '%s\n' '### Repository health' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' 'Status: SKIPPED' - printf '%s\n' 'Reason: profile excludes repository health' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - # Source directory: src/ or htdocs/ (either is valid) - if [ -d "src" ]; then - SOURCE_DIR="src" - elif [ -d "htdocs" ]; then - SOURCE_DIR="htdocs" - else - missing_required+=("src/ or htdocs/ (source directory required)") - fi - - IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}" - IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}" - IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}" - IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES}" - - missing_required=() - missing_optional=() - - for item in "${required_artifacts[@]}"; do - if printf '%s' "${item}" | grep -q '/$'; then - d="${item%/}" - [ ! -d "${d}" ] && missing_required+=("${item}") - else - [ ! -f "${item}" ] && missing_required+=("${item}") - fi - done - - # Optional entries: handle files and directories (trailing slash indicates dir) - for f in "${optional_files[@]}"; do - if printf '%s' "${f}" | grep -q '/$'; then - d="${f%/}" - [ ! -d "${d}" ] && missing_optional+=("${f}") - else - [ ! -f "${f}" ] && missing_optional+=("${f}") - fi - done - - for d in "${disallowed_dirs[@]}"; do - d_norm="${d%/}" - [ -d "${d_norm}" ] && missing_required+=("${d_norm}/ (disallowed)") - done - - for f in "${disallowed_files[@]}"; do - [ -f "${f}" ] && missing_required+=("${f} (disallowed)") - done - - git fetch origin --prune - - dev_paths=() - dev_branches=() - - # Look for remote branches matching origin/dev*. - # A plain origin/dev is considered invalid; we require dev/ branches. - while IFS= read -r b; do - name="${b#origin/}" - if [ "${name}" = 'dev' ]; then - dev_branches+=("${name}") - else - dev_paths+=("${name}") - fi - done < <(git branch -r --list 'origin/dev*' | sed 's/^ *//') - - # If there are no dev/* branches, fail the guardrail. - if [ "${#dev_paths[@]}" -eq 0 ]; then - missing_required+=("dev/* branch (e.g. dev/01.00.00)") - fi - - # If a plain dev branch exists (origin/dev), flag it as invalid. - if [ "${#dev_branches[@]}" -gt 0 ]; then - missing_required+=("invalid branch dev (must be dev/)") - fi - - content_warnings=() - - if [ -f 'CHANGELOG.md' ] && ! grep -Eq '^# Changelog' CHANGELOG.md; then - content_warnings+=("CHANGELOG.md missing '# Changelog' header") - fi - - if [ -f 'CHANGELOG.md' ] && grep -Eq '^[# ]*Unreleased' CHANGELOG.md; then - content_warnings+=("CHANGELOG.md contains Unreleased section (review release readiness)") - fi - - if [ -f 'LICENSE' ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then - content_warnings+=("LICENSE does not look like a GPL text") - fi - - if [ -f 'README.md' ] && ! grep -qiE 'moko|Moko' README.md; then - content_warnings+=("README.md missing expected brand keyword") - fi - - export PROFILE_RAW="${profile}" - export MISSING_REQUIRED="$(printf '%s\n' "${missing_required[@]:-}")" - export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")" - export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")" - - report_json="$(python3 - <<'PY' - import json - import os - - profile = os.environ.get('PROFILE_RAW') or 'all' - - missing_required = os.environ.get('MISSING_REQUIRED', '').splitlines() if os.environ.get('MISSING_REQUIRED') else [] - missing_optional = os.environ.get('MISSING_OPTIONAL', '').splitlines() if os.environ.get('MISSING_OPTIONAL') else [] - content_warnings = os.environ.get('CONTENT_WARNINGS', '').splitlines() if os.environ.get('CONTENT_WARNINGS') else [] - - out = { - 'profile': profile, - 'missing_required': [x for x in missing_required if x], - 'missing_optional': [x for x in missing_optional if x], - 'content_warnings': [x for x in content_warnings if x], - } - - print(json.dumps(out, indent=2)) - PY - )" - - { - printf '%s\n' '### Repository health' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' '| Metric | Value |' - printf '%s\n' '|---|---|' - printf '%s\n' "| Missing required | ${#missing_required[@]} |" - printf '%s\n' "| Missing optional | ${#missing_optional[@]} |" - printf '%s\n' "| Content warnings | ${#content_warnings[@]} |" - printf '\n' - - printf '%s\n' '### Guardrails report (JSON)' - printf '%s\n' '```json' - printf '%s\n' "${report_json}" - printf '%s\n' '```' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - if [ "${#missing_required[@]}" -gt 0 ]; then - { - printf '%s\n' '### Missing required repo artifacts' - for m in "${missing_required[@]}"; do printf '%s\n' "- ${m}"; done - printf '%s\n' 'ERROR: Guardrails failed. Missing required repository artifacts.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 1 - fi - - if [ "${#missing_optional[@]}" -gt 0 ]; then - { - printf '%s\n' '### Missing optional repo artifacts' - for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - if [ "${#content_warnings[@]}" -gt 0 ]; then - { - printf '%s\n' '### Repo content warnings' - for m in "${content_warnings[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - # ── Joomla-specific checks ─────────────────────────────────────── - joomla_findings=() - - # XML manifest: find any XML file containing tag)") - else - # Check tag exists - if ! grep -qP '' "${MANIFEST}"; then - joomla_findings+=("XML manifest: tag missing") - fi - # Check extension type attribute - if ! grep -qP 'type="(component|module|plugin|library|package|template|language)"' "${MANIFEST}"; then - joomla_findings+=("XML manifest: type attribute missing or invalid") - fi - # Check tag - if ! grep -qP '' "${MANIFEST}"; then - joomla_findings+=("XML manifest: tag missing") - fi - # Check tag - if ! grep -qP '' "${MANIFEST}"; then - joomla_findings+=("XML manifest: tag missing") - fi - # Check for Joomla 5+ - if ! grep -qP ' missing (required for Joomla 5+)") - fi - fi - - # Language files: check for at least one .ini file - INI_COUNT="$(find . -name '*.ini' -type f 2>/dev/null | wc -l)" - if [ "${INI_COUNT}" -eq 0 ]; then - joomla_findings+=("No .ini language files found") - fi - - # updates.xml must exist in root (Joomla update server) - if [ ! -f 'updates.xml' ]; then - joomla_findings+=("updates.xml missing in root (required for Joomla update server)") - fi - - # index.html files for directory listing protection - INDEX_DIRS=("${SOURCE_DIR}" "${SOURCE_DIR}/admin" "${SOURCE_DIR}/site") - for dir in "${INDEX_DIRS[@]}"; do - if [ -d "${dir}" ] && [ ! -f "${dir}/index.html" ]; then - joomla_findings+=("${dir}/index.html missing (directory listing protection)") - fi - done - - if [ "${#joomla_findings[@]}" -gt 0 ]; then - { - printf '%s\n' '### Joomla extension checks' - printf '%s\n' '| Check | Status |' - printf '%s\n' '|---|---|' - for f in "${joomla_findings[@]}"; do - printf '%s\n' "| ${f} | Warning |" - done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - else - { - printf '%s\n' '### Joomla extension checks' - printf '%s\n' 'All Joomla-specific checks passed.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - extended_enabled="${EXTENDED_CHECKS:-true}" - extended_findings=() - - if [ "${extended_enabled}" = 'true' ]; then - # CODEOWNERS presence - if [ -f '.github/CODEOWNERS' ] || [ -f 'CODEOWNERS' ] || [ -f 'docs/CODEOWNERS' ]; then - : - else - extended_findings+=("CODEOWNERS not found (.github/CODEOWNERS preferred)") - fi - - # Workflow pinning advisory: flag uses @main/@master - if ls "${WORKFLOWS_DIR}"/*.yml >/dev/null 2>&1 || ls "${WORKFLOWS_DIR}"/*.yaml >/dev/null 2>&1; then - bad_refs="$(grep -RIn --include='*.yml' --include='*.yaml' -E '^[[:space:]]*uses:[[:space:]]*[^#]+@(main|master)\b' "${WORKFLOWS_DIR}" 2>/dev/null || true)" - if [ -n "${bad_refs}" ]; then - extended_findings+=("Workflows reference actions @main/@master (pin versions): see log excerpt") - { - printf '%s\n' '### Workflow pinning advisory' - printf '%s\n' 'Found uses: entries pinned to main/master:' - printf '%s\n' '```' - printf '%s\n' "${bad_refs}" - printf '%s\n' '```' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - # Docs index link integrity (docs/docs-index.md) - if [ -f "${DOCS_INDEX}" ]; then - missing_links="$(python3 - <<'PY' - import os - import re - - idx = os.environ.get('DOCS_INDEX', 'docs/docs-index.md') - base = os.getcwd() - - bad = [] - pat = re.compile(r'\[[^\]]+\]\(([^)]+)\)') - - with open(idx, 'r', encoding='utf-8') as f: - for line in f: - for m in pat.findall(line): - link = m.strip() - if link.startswith('http://') or link.startswith('https://') or link.startswith('#') or link.startswith('mailto:'): - continue - if link.startswith('/'): - rel = link.lstrip('/') - else: - rel = os.path.normpath(os.path.join(os.path.dirname(idx), link)) - rel = rel.split('#', 1)[0] - rel = rel.split('?', 1)[0] - if not rel: - continue - p = os.path.join(base, rel) - if not os.path.exists(p): - bad.append(rel) - - print('\n'.join(sorted(set(bad)))) - PY - )" - if [ -n "${missing_links}" ]; then - extended_findings+=("docs/docs-index.md contains broken relative links") - { - printf '%s\n' '### Docs index link integrity' - printf '%s\n' 'Broken relative links:' - while IFS= read -r l; do [ -n "${l}" ] && printf '%s\n' "- ${l}"; done <<< "${missing_links}" - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - # ShellCheck advisory - if [ -d "${SCRIPT_DIR}" ]; then - if ! command -v shellcheck >/dev/null 2>&1; then - sudo apt-get update -qq - sudo apt-get install -y shellcheck >/dev/null - fi - - sc_out='' - while IFS= read -r shf; do - [ -z "${shf}" ] && continue - out_one="$(shellcheck -S warning -x "${shf}" 2>/dev/null || true)" - if [ -n "${out_one}" ]; then - sc_out="${sc_out}${out_one}\n" - fi - done < <(find "${SCRIPT_DIR}" -type f -name "${SHELLCHECK_PATTERN}" 2>/dev/null | sort) - - if [ -n "${sc_out}" ]; then - extended_findings+=("ShellCheck warnings detected (advisory)") - sc_head="$(printf '%s' "${sc_out}" | head -n 200)" - { - printf '%s\n' '### ShellCheck (advisory)' - printf '%s\n' '```' - printf '%s\n' "${sc_head}" - printf '%s\n' '```' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - # SPDX header advisory for common source types - spdx_missing=() - IFS=',' read -r -a spdx_globs <<< "${SPDX_FILE_GLOBS}" - spdx_args=() - for g in "${spdx_globs[@]}"; do spdx_args+=("${g}"); done - - while IFS= read -r f; do - [ -z "${f}" ] && continue - if ! head -n 40 "${f}" | grep -q 'SPDX-License-Identifier:'; then - spdx_missing+=("${f}") - fi - done < <(git ls-files "${spdx_args[@]}" 2>/dev/null || true) - - if [ "${#spdx_missing[@]}" -gt 0 ]; then - extended_findings+=("SPDX header missing in some tracked files (advisory)") - { - printf '%s\n' '### SPDX header advisory' - printf '%s\n' 'Files missing SPDX-License-Identifier (first 40 lines scan):' - for f in "${spdx_missing[@]}"; do printf '%s\n' "- ${f}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - # Git hygiene advisory: branches older than 180 days (remote) - stale_cutoff_days=180 - stale_branches="$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/remotes/origin 2>/dev/null | awk -v now="$(date +%s)" -v days="${stale_cutoff_days}" '{if (now-$2 [...] - if [ -n "${stale_branches}" ]; then - extended_findings+=("Stale remote branches detected (advisory)") - { - printf '%s\n' '### Git hygiene advisory' - printf '%s\n' "Branches with last commit older than ${stale_cutoff_days} days (sample up to 50):" - while IFS= read -r b; do [ -n "${b}" ] && printf '%s\n' "- ${b}"; done <<< "${stale_branches}" - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - { - printf '%s\n' '### Guardrails coverage matrix' - printf '%s\n' '| Domain | Status | Notes |' - printf '%s\n' '|---|---|---|' - printf '%s\n' '| Access control | OK | Admin-only execution gate |' - printf '%s\n' '| Release variables | OK | Repository variables validation |' - printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |' - printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |' - printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |' - if [ "${extended_enabled}" = 'true' ]; then - if [ "${#extended_findings[@]}" -gt 0 ]; then - printf '%s\n' '| Extended checks | Warning | See extended findings below |' - else - printf '%s\n' '| Extended checks | OK | No findings |' - fi - else - printf '%s\n' '| Extended checks | SKIPPED | EXTENDED_CHECKS disabled |' - fi - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - if [ "${extended_enabled}" = 'true' ] && [ "${#extended_findings[@]}" -gt 0 ]; then - { - printf '%s\n' '### Extended findings (advisory)' - for f in "${extended_findings[@]}"; do printf '%s\n' "- ${f}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}" diff --git a/templates/workflows/joomla/update-server.yml.template b/templates/workflows/joomla/update-server.yml.template deleted file mode 100644 index 4a9d62c..0000000 --- a/templates/workflows/joomla/update-server.yml.template +++ /dev/null @@ -1,419 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Joomla -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/joomla/update-server.yml.template -# VERSION: 04.06.00 -# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries -# -# Writes updates.xml with multiple entries: -# - stable on push to main (from auto-release) -# - rc on push to rc/** -# - development on push to dev/** -# -# Joomla filters by user's "Minimum Stability" setting. - -name: Update Joomla Update Server XML Feed - -on: - pull_request: - types: [closed] - branches: - - 'dev/**' - - 'alpha/**' - - 'beta/**' - - 'rc/**' - paths: - - 'src/**' - - 'htdocs/**' - workflow_dispatch: - inputs: - stability: - description: 'Stability tag' - required: true - default: 'development' - type: choice - options: - - development - - alpha - - beta - - rc - - stable - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - GITEA_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 }} - -permissions: - contents: write - -jobs: - update-xml: - name: Update updates.xml - runs-on: ubuntu-latest - if: >- - github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - token: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 - - - name: Setup MokoStandards tools - env: - MOKO_CLONE_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' - run: | - git clone --depth 1 --branch {{standards_branch}} --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ - /tmp/mokostandards-api 2>/dev/null || true - if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then - cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true - fi - - - name: Generate updates.xml entry - id: update - run: | - BRANCH="${{ github.ref_name }}" - REPO="${{ github.repository }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0") - - # Auto-bump patch on alpha/beta/rc branches (not dev — dev bumps manually) - if [[ "$BRANCH" != dev/* ]]; then - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - BUMPED=$(php /tmp/mokostandards-api/cli/version_bump.php --path . 2>/dev/null || true) - if [ -n "$BUMPED" ]; then - VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION") - git add -A - git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \ - --author="github-actions[bot] " 2>/dev/null || true - git push 2>/dev/null || true - fi - fi - - # Determine stability from branch or input - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - STABILITY="${{ inputs.stability }}" - elif [[ "$BRANCH" == rc/* ]]; then - STABILITY="rc" - elif [[ "$BRANCH" == beta/* ]]; then - STABILITY="beta" - elif [[ "$BRANCH" == alpha/* ]]; then - STABILITY="alpha" - elif [[ "$BRANCH" == dev/* ]]; then - STABILITY="development" - else - STABILITY="stable" - fi - - echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT" - - # Parse manifest (portable — no grep -P) - MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1) - if [ -z "$MANIFEST" ]; then - echo "No Joomla manifest found — skipping" - exit 0 - fi - - # Extract fields using sed (works on all runners) - EXT_NAME=$(sed -n 's/.*\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1) - EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1) - EXT_CLIENT=$(sed -n 's/.*]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_FOLDER=$(sed -n 's/.*]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1) - EXT_VERSION=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1) - TARGET_PLATFORM=$(sed -n 's/.*\(\).*/\1/p' "$MANIFEST" | head -1) - PHP_MINIMUM=$(sed -n 's/.*\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1) - - # Fallbacks - [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}" - [ -z "$EXT_TYPE" ] && EXT_TYPE="component" - - # Templates and modules don't have — derive from - if [ -z "$EXT_ELEMENT" ]; then - EXT_ELEMENT=$(echo "$EXT_NAME" | tr '[:upper:]' '[:lower:]' | tr -d ' ') - fi - - # Use manifest version if README version is empty - [ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION" - - [ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '' "/") - - CLIENT_TAG="" - [ -n "$EXT_CLIENT" ] && CLIENT_TAG="${EXT_CLIENT}" - [ -z "$CLIENT_TAG" ] && ([ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]) && CLIENT_TAG="site" - - FOLDER_TAG="" - [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="${EXT_FOLDER}" - - PHP_TAG="" - [ -n "$PHP_MINIMUM" ] && PHP_TAG="${PHP_MINIMUM}" - - # Version suffix for non-stable - DISPLAY_VERSION="$VERSION" - case "$STABILITY" in - development) DISPLAY_VERSION="${VERSION}-dev" ;; - alpha) DISPLAY_VERSION="${VERSION}-alpha" ;; - beta) DISPLAY_VERSION="${VERSION}-beta" ;; - rc) DISPLAY_VERSION="${VERSION}-rc" ;; - esac - - MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') - - # Each stability level has its own release tag - case "$STABILITY" in - development) RELEASE_TAG="development" ;; - alpha) RELEASE_TAG="alpha" ;; - beta) RELEASE_TAG="beta" ;; - rc) RELEASE_TAG="release-candidate" ;; - *) RELEASE_TAG="v${MAJOR}" ;; - esac - - PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip" - DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}" - INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}" - - # -- Build install packages (ZIP + tar.gz) -------------------- - SOURCE_DIR="src" - [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" - if [ -d "$SOURCE_DIR" ]; then - EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*" - TAR_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz" - - cd "$SOURCE_DIR" - zip -r "/tmp/${PACKAGE_NAME}" . -x $EXCLUDES - cd .. - tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \ - --exclude='.ftpignore' --exclude='sftp-config*' \ - --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' . - - SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1) - - # Ensure release exists on Gitea - RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true) - RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) - - if [ -z "$RELEASE_ID" ]; then - # Create release - RELEASE_JSON=$(curl -sf -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Content-Type: application/json" \ - "${API_BASE}/releases" \ - -d "$(python3 -c "import json; print(json.dumps({ - 'tag_name': '${RELEASE_TAG}', - 'name': '${RELEASE_TAG} (${DISPLAY_VERSION})', - 'body': '${STABILITY} release', - 'prerelease': True, - 'target_commitish': 'main' - }))")" 2>/dev/null || true) - RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) - fi - - if [ -n "$RELEASE_ID" ]; then - # Delete existing assets with same name before uploading - ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]") - for ASSET_FILE in "$PACKAGE_NAME" "$TAR_NAME"; do - ASSET_ID=$(echo "$ASSETS" | python3 -c " - import sys,json - assets = json.load(sys.stdin) - for a in assets: - if a['name'] == '${ASSET_FILE}': - print(a['id']); break - " 2>/dev/null || true) - if [ -n "$ASSET_ID" ]; then - curl -sf -X DELETE -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true - fi - done - - # Upload both formats - curl -sf -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @"/tmp/${PACKAGE_NAME}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets?name=${PACKAGE_NAME}" > /dev/null 2>&1 || true - - curl -sf -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @"/tmp/${TAR_NAME}" \ - "${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true - fi - - echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY - else - SHA256="" - fi - - # -- Build the new entry ----------------------------------------- - NEW_ENTRY="" - NEW_ENTRY="${NEW_ENTRY} \n" - NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME}\n" - NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME} (${STABILITY})\n" - NEW_ENTRY="${NEW_ENTRY} ${EXT_ELEMENT}\n" - NEW_ENTRY="${NEW_ENTRY} ${EXT_TYPE}\n" - NEW_ENTRY="${NEW_ENTRY} ${DISPLAY_VERSION}\n" - [ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n" - [ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n" - NEW_ENTRY="${NEW_ENTRY} \n" - NEW_ENTRY="${NEW_ENTRY} ${STABILITY}\n" - NEW_ENTRY="${NEW_ENTRY} \n" - NEW_ENTRY="${NEW_ENTRY} ${INFO_URL}\n" - NEW_ENTRY="${NEW_ENTRY} \n" - TAR_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz" - NEW_ENTRY="${NEW_ENTRY} ${DOWNLOAD_URL}\n" - NEW_ENTRY="${NEW_ENTRY} ${TAR_URL}\n" - NEW_ENTRY="${NEW_ENTRY} \n" - [ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} sha256:${SHA256}\n" - NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n" - [ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n" - NEW_ENTRY="${NEW_ENTRY} Moko Consulting\n" - NEW_ENTRY="${NEW_ENTRY} https://mokoconsulting.tech\n" - NEW_ENTRY="${NEW_ENTRY} " - - # -- Write new entry to temp file -------------------------------- - printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml - - # -- Merge into updates.xml (only update this stability channel) - - if [ ! -f "updates.xml" ]; then - printf '%s\n' '' > updates.xml - printf '%s\n' '' >> updates.xml - cat /tmp/new_entry.xml >> updates.xml - printf '\n%s\n' '' >> updates.xml - else - # Remove existing entry for this stability, insert new one - python3 << PYEOF - import re - stability = "${STABILITY}" - with open("updates.xml") as f: - content = f.read() - with open("/tmp/new_entry.xml") as f: - new_entry = f.read() - pattern = r" .*?" + re.escape(stability) + r".*?\n?" - content = re.sub(pattern, "", content, flags=re.DOTALL) - content = content.replace("", new_entry + "\n") - content = re.sub(r"\n{3,}", "\n\n", content) - with open("updates.xml", "w") as f: - f.write(content) - PYEOF - if [ $? -ne 0 ]; then - # Fallback: rebuild keeping other stability entries - { - printf '%s\n' '' - printf '%s\n' '' - for TAG in stable rc development; do - [ "$TAG" = "${STABILITY}" ] && continue - if grep -q "${TAG}" updates.xml 2>/dev/null; then - sed -n "//,/<\/update>/{ /${TAG}<\/tag>/p; }" updates.xml - fi - done - cat /tmp/new_entry.xml - printf '\n%s\n' '' - } > /tmp/updates_new.xml - mv /tmp/updates_new.xml updates.xml - fi - fi - - # Commit - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add updates.xml - git diff --cached --quiet || { - git commit -m "chore: update updates.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \ - --author="github-actions[bot] " - git push - } - - # -- Mirror to GitHub (stable and rc only) -------------------------------- - - name: Mirror release to GitHub - if: >- - (steps.update.outputs.stability == 'stable' || steps.update.outputs.stability == 'rc') && - secrets.GH_MIRROR_TOKEN != '' - continue-on-error: true - env: - GH_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }} - run: | - GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" - STABILITY="${{ steps.update.outputs.stability }}" - echo "GitHub mirror sync for ${STABILITY} — ${GH_REPO}" >> $GITHUB_STEP_SUMMARY - # Mirror packages if they exist - for PKG in /tmp/*.zip /tmp/*.tar.gz; do - [ -f "$PKG" ] && gh release upload "${RELEASE_TAG}" "$PKG" --repo "$GH_REPO" --clobber 2>/dev/null || true - done - - - name: SFTP deploy to dev server - if: contains(github.ref, 'dev/') - env: - DEV_HOST: ${{ vars.DEV_FTP_HOST }} - DEV_PATH: ${{ vars.DEV_FTP_PATH }} - DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }} - DEV_USER: ${{ vars.DEV_FTP_USERNAME }} - DEV_PORT: ${{ vars.DEV_FTP_PORT }} - DEV_KEY: ${{ secrets.DEV_FTP_KEY }} - DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }} - run: | - # -- Permission check: admin or maintain role required -------- - ACTOR="${{ github.actor }}" - REPO="${{ github.repository }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - - PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \ - python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read") - case "$PERMISSION" in - admin|maintain|write) ;; - *) - echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write" - exit 0 - ;; - esac - - [ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; } - - SOURCE_DIR="src" - [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" - [ ! -d "$SOURCE_DIR" ] && exit 0 - - PORT="${DEV_PORT:-22}" - REMOTE="${DEV_PATH%/}" - [ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}" - - printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \ - "$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json - if [ -n "$DEV_KEY" ]; then - echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key - printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json - else - printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json - fi - - 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 --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json - elif [ -f "/tmp/mokostandards-api/deploy/deploy-sftp.php" ]; then - php /tmp/mokostandards-api/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json - fi - rm -f /tmp/deploy_key /tmp/sftp-config.json - echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY - - - name: Summary - if: always() - run: | - echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY - echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Version | \`${DISPLAY_VERSION}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/metrics-collection.yml b/templates/workflows/metrics-collection.yml deleted file mode 100644 index 9a16d50..0000000 --- a/templates/workflows/metrics-collection.yml +++ /dev/null @@ -1,245 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.MetricsCollection -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /.github/workflows/metrics-collection.yml -# VERSION: 04.06.00 -# BRIEF: Daily metrics collection and trend report generation -# NOTE: Collects and exports metrics using PHP MetricsCollector - -name: Metrics Collection - -on: - schedule: - # Run daily at 06:00 UTC - - cron: '0 6 * * *' - workflow_dispatch: - inputs: - export_format: - description: 'Export format for metrics' - required: false - type: choice - options: - - prometheus - - json - - both - default: 'both' - generate_trends: - description: 'Generate trend reports' - required: false - type: boolean - default: true - -permissions: - contents: read - -jobs: - collect-metrics: - name: Collect Metrics - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - with: - php-version: '8.1' - extensions: mbstring, curl, json - tools: composer - - - name: Install Composer Dependencies - run: composer install --no-dev --optimize-autoloader - - - name: Create Metrics Directory - run: | - mkdir -p logs/metrics - mkdir -p logs/reports - - - name: Collect System Metrics - id: collect - run: | - echo "## 📊 Metrics Collection" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - php << 'EOF' - increment('workflow_runs_total'); - $metrics->increment('metrics_collection_runs'); - - // Collect file counts - $phpFiles = count(iterator_to_array( - new RecursiveIteratorIterator( - new RecursiveCallbackFilterIterator( - new RecursiveDirectoryIterator('.', RecursiveDirectoryIterator::SKIP_DOTS), - function ($file, $key, $iterator) { - return $file->isDir() || $file->getExtension() === 'php'; - } - ) - ) - )); - - $ymlFiles = count(glob('.github/workflows/*.yml')); - - $metrics->setGauge('php_files_total', $phpFiles); - $metrics->setGauge('workflow_files_total', $ymlFiles); - - // Collect disk usage - $totalSize = 0; - $iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator('.', RecursiveDirectoryIterator::SKIP_DOTS) - ); - foreach ($iterator as $file) { - if ($file->isFile()) { - $totalSize += $file->getSize(); - } - } - $metrics->setGauge('repository_size_bytes', $totalSize); - - echo "✅ Collected metrics:\n"; - echo " - PHP files: {$phpFiles}\n"; - echo " - Workflow files: {$ymlFiles}\n"; - echo sprintf(" - Repository size: %.2f MB\n", $totalSize / (1024 * 1024)); - - // Export metrics - $exportFormat = '${{ github.event.inputs.export_format }}' ?: 'both'; - - if (in_array($exportFormat, ['json', 'both'])) { - $metricsData = $metrics->exportJson(); - file_put_contents('logs/metrics/metrics.json', json_encode($metricsData, JSON_PRETTY_PRINT)); - echo "✅ Exported metrics to JSON\n"; - } - - if (in_array($exportFormat, ['prometheus', 'both'])) { - $promData = $metrics->exportPrometheus(); - file_put_contents('logs/metrics/metrics.prom', $promData); - echo "✅ Exported metrics to Prometheus format\n"; - } - - // Write summary data - $summary = [ - 'php_files' => $phpFiles, - 'workflow_files' => $ymlFiles, - 'repo_size_mb' => round($totalSize / (1024 * 1024), 2) - ]; - file_put_contents('/tmp/metrics_summary.json', json_encode($summary)); - - } catch (Exception $e) { - echo "❌ Metrics collection failed: {$e->getMessage()}\n"; - exit(1); - } - EOF - - # Read summary and output - SUMMARY=$(cat /tmp/metrics_summary.json) - PHP_FILES=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["php_files"];') - WF_FILES=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["workflow_files"];') - REPO_SIZE=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["repo_size_mb"];') - - echo "php_files=$PHP_FILES" >> $GITHUB_OUTPUT - echo "workflow_files=$WF_FILES" >> $GITHUB_OUTPUT - echo "repo_size_mb=$REPO_SIZE" >> $GITHUB_OUTPUT - - echo "### Collected Metrics" >> $GITHUB_STEP_SUMMARY - echo "- PHP files: **${PHP_FILES}**" >> $GITHUB_STEP_SUMMARY - echo "- Workflow files: **${WF_FILES}**" >> $GITHUB_STEP_SUMMARY - echo "- Repository size: **${REPO_SIZE} MB**" >> $GITHUB_STEP_SUMMARY - - - name: Generate Trend Report - if: github.event.inputs.generate_trends != 'false' - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "## 📈 Trend Analysis" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - php << 'EOF' - $cutoff; - }); - - // Save updated history - file_put_contents('logs/metrics/history.json', json_encode(array_values($history), JSON_PRETTY_PRINT)); - - // Generate trend report - if (count($history) > 1) { - $first = $history[0]; - $last = end($history); - - $trendReport = [ - 'period_days' => count($history), - 'first_date' => $first['timestamp'], - 'last_date' => $last['timestamp'], - 'trends' => [ - 'php_files' => [ - 'start' => $first['gauges']['php_files_total'] ?? 0, - 'end' => $last['gauges']['php_files_total'] ?? 0 - ] - ] - ]; - - file_put_contents('logs/reports/trends-report.json', json_encode($trendReport, JSON_PRETTY_PRINT)); - - echo "✅ Trend report generated for " . count($history) . " data points\n"; - } else { - echo "⚠️ Insufficient data for trend analysis (need > 1 day)\n"; - } - - } catch (Exception $e) { - echo "⚠️ Trend generation failed: {$e->getMessage()}\n"; - } - EOF - - if [ -f "logs/reports/trends-report.json" ]; then - echo "✅ Trend report generated" >> $GITHUB_STEP_SUMMARY - fi - - - name: Upload Metrics Report - if: always() - uses: actions/upload-artifact@v6.0.0 - with: - name: metrics-report-${{ github.run_number }} - path: | - logs/metrics/ - logs/reports/trends-report.json - retention-days: 30 - - - name: Notify on Failure - if: failure() - run: | - echo "❌ Metrics collection failed" >> $GITHUB_STEP_SUMMARY - echo "Please check the logs for details" >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/security-scan.yml b/templates/workflows/security-scan.yml deleted file mode 100644 index 9c9d72e..0000000 --- a/templates/workflows/security-scan.yml +++ /dev/null @@ -1,310 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.SecurityScan -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /.github/workflows/security-scan.yml -# VERSION: 04.06.00 -# BRIEF: Daily security scanning and report generation -# NOTE: Enhanced security scanning using PHP SecurityValidator - -name: Security Scan - -on: - schedule: - # Run daily at 02:00 UTC - - cron: '0 2 * * *' - pull_request: - branches: - - main - paths: - - 'scripts/**' - - '.github/workflows/**' - workflow_dispatch: - inputs: - scan_type: - description: 'Type of security scan' - required: false - type: choice - options: - - all - - credentials - - vulnerabilities - - best-practices - default: 'all' - strict_mode: - description: 'Fail on any security issues' - required: false - type: boolean - default: false - -permissions: - contents: read - security-events: write - -jobs: - security-scan: - name: Security Scan - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 - with: - php-version: '8.1' - extensions: mbstring, curl, json - tools: composer - - - name: Install Composer Dependencies - run: composer install --no-dev --optimize-autoloader - - - name: Create Reports Directory - run: | - mkdir -p logs/security - mkdir -p logs/reports - - - name: Scan for Credentials - id: credentials - if: github.event.inputs.scan_type == 'all' || github.event.inputs.scan_type == 'credentials' || github.event.inputs.scan_type == '' - run: | - echo "## 🔐 Credential Scan" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - php << 'EOF' - isDir() || $file->getExtension() === 'php'; - } - ) - ); - - $phpFileCount = 0; - foreach ($phpFiles as $file) { - if ($file->isFile()) { - $phpFileCount++; - $findings = $validator->scanFile($file->getPathname(), true, false); - if (!empty($findings)) { - $allFindings = array_merge($allFindings, $findings); - } - } - } - - // Scan workflow files - $ymlFiles = glob('.github/workflows/*.yml'); - foreach ($ymlFiles as $filePath) { - $findings = $validator->scanFile($filePath, true, false); - if (!empty($findings)) { - $allFindings = array_merge($allFindings, $findings); - } - } - - $totalFiles = $phpFileCount + count($ymlFiles); - echo "Scanned {$phpFileCount} PHP files and " . count($ymlFiles) . " workflow files\n"; - echo "Found " . count($allFindings) . " potential credential issues\n"; - - if (!empty($allFindings)) { - echo "\n⚠️ Potential credential issues found:\n"; - foreach (array_slice($allFindings, 0, 10) as $finding) { - echo " - {$finding['file']}: {$finding['issue']}\n"; - } - } else { - echo "✅ No credential issues found\n"; - } - - // Save findings - file_put_contents('logs/security/credentials-scan.json', json_encode($allFindings, JSON_PRETTY_PRINT)); - - $summary = [ - 'files_scanned' => $totalFiles, - 'issues_found' => count($allFindings) - ]; - file_put_contents('/tmp/credential_summary.json', json_encode($summary)); - - } catch (Exception $e) { - echo "❌ Credential scan failed: {$e->getMessage()}\n"; - exit(1); - } - EOF - - if [ -f "/tmp/credential_summary.json" ]; then - SUMMARY=$(cat /tmp/credential_summary.json) - FILES=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["files_scanned"];') - ISSUES=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["issues_found"];') - - echo "files_scanned=$FILES" >> $GITHUB_OUTPUT - echo "issues_found=$ISSUES" >> $GITHUB_OUTPUT - - echo "- Files scanned: **${FILES}**" >> $GITHUB_STEP_SUMMARY - echo "- Issues found: **${ISSUES}**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Vulnerability Scan - id: vulnerabilities - if: github.event.inputs.scan_type == 'all' || github.event.inputs.scan_type == 'vulnerabilities' || github.event.inputs.scan_type == '' - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "## 🛡️ Vulnerability Scan" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - php << 'EOF' - isDir() || $file->getExtension() === 'php'; - } - ) - ); - - $phpFileCount = 0; - foreach ($phpFiles as $file) { - if ($file->isFile()) { - $phpFileCount++; - $findings = $validator->scanFile($file->getPathname(), false, true); - if (!empty($findings)) { - $vulnerabilities = array_merge($vulnerabilities, $findings); - } - } - } - - echo "Scanned {$phpFileCount} PHP files\n"; - echo "Found " . count($vulnerabilities) . " potential vulnerabilities\n"; - - if (!empty($vulnerabilities)) { - echo "\n⚠️ Potential vulnerabilities found:\n"; - foreach (array_slice($vulnerabilities, 0, 10) as $vuln) { - echo " - {$vuln['file']}: {$vuln['issue']}\n"; - } - } else { - echo "✅ No vulnerabilities found\n"; - } - - // Save findings - file_put_contents('logs/security/vulnerabilities-scan.json', json_encode($vulnerabilities, JSON_PRETTY_PRINT)); - - $summary = ['vulnerabilities_found' => count($vulnerabilities)]; - file_put_contents('/tmp/vuln_summary.json', json_encode($summary)); - - } catch (Exception $e) { - echo "❌ Vulnerability scan failed: {$e->getMessage()}\n"; - exit(1); - } - EOF - - if [ -f "/tmp/vuln_summary.json" ]; then - SUMMARY=$(cat /tmp/vuln_summary.json) - VULNS=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["vulnerabilities_found"];') - - echo "vulnerabilities_found=$VULNS" >> $GITHUB_OUTPUT - echo "- Vulnerabilities found: **${VULNS}**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Generate Security Report - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "## 📋 Security Report" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - php << 'EOF' - date('c'), - 'scan_type' => '${{ github.event.inputs.scan_type }}' ?: 'all', - 'repository' => 'MokoStandards', - 'results' => [] - ]; - - // Load credential scan results - $credFile = 'logs/security/credentials-scan.json'; - if (file_exists($credFile)) { - $report['results']['credentials'] = json_decode(file_get_contents($credFile), true); - } - - // Load vulnerability scan results - $vulnFile = 'logs/security/vulnerabilities-scan.json'; - if (file_exists($vulnFile)) { - $report['results']['vulnerabilities'] = json_decode(file_get_contents($vulnFile), true); - } - - // Calculate summary - $totalIssues = 0; - foreach ($report['results'] as $issues) { - if (is_array($issues)) { - $totalIssues += count($issues); - } - } - - $report['summary'] = [ - 'total_issues' => $totalIssues, - 'credential_issues' => count($report['results']['credentials'] ?? []), - 'vulnerabilities' => count($report['results']['vulnerabilities'] ?? []) - ]; - - // Save report - file_put_contents('logs/reports/security-report.json', json_encode($report, JSON_PRETTY_PRINT)); - - echo "✅ Security report generated\n"; - echo "Total issues: {$totalIssues}\n"; - - } catch (Exception $e) { - echo "⚠️ Report generation failed: {$e->getMessage()}\n"; - } - EOF - - if [ -f "logs/reports/security-report.json" ]; then - echo "✅ Security report generated" >> $GITHUB_STEP_SUMMARY - fi - - - name: Upload Security Report - if: always() - uses: actions/upload-artifact@v6.0.0 - with: - name: security-report-${{ github.run_number }} - path: | - logs/security/ - logs/reports/security-report.json - retention-days: 90 - - - name: Check Strict Mode - if: github.event.inputs.strict_mode == 'true' - run: | - ISSUES=$(cat logs/reports/security-report.json | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["summary"]["total_issues"];') - if [ "$ISSUES" -gt "0" ]; then - echo "❌ Security issues found in strict mode" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - - name: Notify on Failure - if: failure() - run: | - echo "❌ Security scan failed or found critical issues" >> $GITHUB_STEP_SUMMARY - echo "Please review the security report" >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/shared/auto-assign.yml.template b/templates/workflows/shared/auto-assign.yml.template deleted file mode 100644 index 64ac3df..0000000 --- a/templates/workflows/shared/auto-assign.yml.template +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Workflows.Shared -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /.github/workflows/auto-assign.yml -# VERSION: 04.06.00 -# BRIEF: Auto-assign jmiller-moko to unassigned issues and PRs every 15 minutes - -name: Auto-Assign Issues & PRs - -on: - issues: - types: [opened] - pull_request_target: - types: [opened] - schedule: - - cron: '0 */12 * * *' - workflow_dispatch: - -permissions: - issues: write - pull-requests: write - -jobs: - auto-assign: - name: Assign unassigned issues and PRs - runs-on: ubuntu-latest - - steps: - - name: Assign unassigned issues - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - REPO="${{ github.repository }}" - ASSIGNEE="jmiller-moko" - - echo "## 🏷️ Auto-Assign Report" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - ASSIGNED_ISSUES=0 - ASSIGNED_PRS=0 - - # Assign unassigned open issues - ISSUES=$(gh api "repos/$REPO/issues?state=open&per_page=100&assignee=none" --jq '.[].number' 2>/dev/null || true) - for NUM in $ISSUES; do - # Skip PRs (the issues endpoint returns PRs too) - IS_PR=$(gh api "repos/$REPO/issues/$NUM" --jq '.pull_request // empty' 2>/dev/null || true) - if [ -z "$IS_PR" ]; then - gh api "repos/$REPO/issues/$NUM/assignees" -X POST -f "assignees[]=$ASSIGNEE" --silent 2>/dev/null && { - ASSIGNED_ISSUES=$((ASSIGNED_ISSUES + 1)) - echo " Assigned issue #$NUM" - } || true - fi - done - - # Assign unassigned open PRs - PRS=$(gh api "repos/$REPO/pulls?state=open&per_page=100" --jq '.[] | select(.assignees | length == 0) | .number' 2>/dev/null || true) - for NUM in $PRS; do - gh api "repos/$REPO/issues/$NUM/assignees" -X POST -f "assignees[]=$ASSIGNEE" --silent 2>/dev/null && { - ASSIGNED_PRS=$((ASSIGNED_PRS + 1)) - echo " Assigned PR #$NUM" - } || true - done - - echo "| Type | Assigned |" >> $GITHUB_STEP_SUMMARY - echo "|------|----------|" >> $GITHUB_STEP_SUMMARY - echo "| Issues | $ASSIGNED_ISSUES |" >> $GITHUB_STEP_SUMMARY - echo "| Pull Requests | $ASSIGNED_PRS |" >> $GITHUB_STEP_SUMMARY - - if [ "$ASSIGNED_ISSUES" -eq 0 ] && [ "$ASSIGNED_PRS" -eq 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "✅ All issues and PRs already have assignees" >> $GITHUB_STEP_SUMMARY - fi diff --git a/templates/workflows/shared/auto-dev-issue.yml.template b/templates/workflows/shared/auto-dev-issue.yml.template deleted file mode 100644 index 70bb4d8..0000000 --- a/templates/workflows/shared/auto-dev-issue.yml.template +++ /dev/null @@ -1,207 +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: Gitea.Workflow -# INGROUP: MokoStandards.Automation -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/shared/auto-dev-issue.yml.template -# VERSION: 04.06.00 -# BRIEF: Auto-create tracking issue with sub-issues for dev/rc branch workflow -# NOTE: Synced via bulk-repo-sync to .github/workflows/auto-dev-issue.yml in all governed repos. - -name: Dev/RC Branch Issue - -on: - # Auto-create on RC branch creation - create: - # Manual trigger for dev branches - workflow_dispatch: - inputs: - branch: - description: 'Branch name (e.g., dev/my-feature or dev/04.06)' - required: true - type: string - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -permissions: - contents: read - issues: write - -jobs: - create-issue: - name: Create version tracking issue - runs-on: ubuntu-latest - if: >- - (github.event_name == 'workflow_dispatch') || - (github.event.ref_type == 'branch' && - (startsWith(github.event.ref, 'rc/') || - startsWith(github.event.ref, 'alpha/') || - startsWith(github.event.ref, 'beta/'))) - - steps: - - name: Create tracking issue and sub-issues - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - # For manual dispatch, use input; for auto, use event ref - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - BRANCH="${{ inputs.branch }}" - else - BRANCH="${{ github.event.ref }}" - fi - REPO="${{ github.repository }}" - ACTOR="${{ github.actor }}" - NOW=$(date -u '+%Y-%m-%d %H:%M UTC') - - # Determine branch type and version - if [[ "$BRANCH" == rc/* ]]; then - VERSION="${BRANCH#rc/}" - BRANCH_TYPE="Release Candidate" - LABEL_TYPE="type: release" - TITLE_PREFIX="rc" - elif [[ "$BRANCH" == beta/* ]]; then - VERSION="${BRANCH#beta/}" - BRANCH_TYPE="Beta" - LABEL_TYPE="type: release" - TITLE_PREFIX="beta" - elif [[ "$BRANCH" == alpha/* ]]; then - VERSION="${BRANCH#alpha/}" - BRANCH_TYPE="Alpha" - LABEL_TYPE="type: release" - TITLE_PREFIX="alpha" - else - VERSION="${BRANCH#dev/}" - BRANCH_TYPE="Development" - LABEL_TYPE="type: feature" - TITLE_PREFIX="feat" - fi - - TITLE="${TITLE_PREFIX}(${VERSION}): ${BRANCH_TYPE} tracking for ${BRANCH}" - - # Check for existing issue with same title prefix - EXISTING=$(gh api "repos/${REPO}/issues?state=open&per_page=10" \ - --jq ".[] | select(.title | startswith(\"${TITLE_PREFIX}(${VERSION})\")) | .number" 2>/dev/null | head -1) - - if [ -n "$EXISTING" ]; then - echo "ℹ️ Issue #${EXISTING} already exists for ${VERSION}" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - # ── Define sub-issues for the workflow ───────────────────────── - if [[ "$BRANCH" == rc/* ]]; then - SUB_ISSUES=( - "RC Testing|Verify all features work on rc branch|type: test,release-candidate" - "Regression Testing|Run full regression suite before merge|type: test,release-candidate" - "Version Bump|Bump version in README.md and all headers|type: version,release-candidate" - "Changelog Update|Update CHANGELOG.md with release notes|documentation,release-candidate" - "Merge to Version Branch|Create PR to version/XX|type: release,needs-review" - ) - elif [[ "$BRANCH" == alpha/* ]] || [[ "$BRANCH" == beta/* ]]; then - SUB_ISSUES=( - "Testing|Verify features on ${BRANCH_TYPE} branch|type: test,status: in-progress" - "Bug Fixes|Fix issues found during ${BRANCH_TYPE} testing|type: bug,status: pending" - "Promote to Next Stage|Create PR to promote to next release stage|type: release,needs-review" - ) - else - SUB_ISSUES=( - "Development|Implement feature/fix on dev branch|type: feature,status: in-progress" - "Unit Testing|Write and pass unit tests|type: test,status: pending" - "Code Review|Request and complete code review|needs-review,status: pending" - "Version Bump|Bump version in README.md and all headers|type: version,status: pending" - "Changelog Update|Update CHANGELOG.md with release notes|documentation,status: pending" - "Create RC Branch|Promote dev to rc branch for final testing|type: release,status: pending" - "Merge to Main|Create PR from rc/dev to main|type: release,needs-review,status: pending" - ) - fi - - # ── Create sub-issues first ─────────────────────────────────────── - SUB_LIST="" - SUB_NUMBERS="" - for SUB in "${SUB_ISSUES[@]}"; do - IFS='|' read -r SUB_TITLE SUB_DESC SUB_LABELS <<< "$SUB" - SUB_FULL_TITLE="${TITLE_PREFIX}(${VERSION}): ${SUB_TITLE}" - - SUB_BODY=$(printf '### %s\n\n%s\n\n| Field | Value |\n|-------|-------|\n| **Parent Branch** | `%s` |\n| **Version** | `%s` |\n\n---\n*Sub-issue of the %s tracking issue for `%s`.*' \ - "$SUB_TITLE" "$SUB_DESC" "$BRANCH" "$VERSION" "$BRANCH_TYPE" "$BRANCH") - - SUB_URL=$(gh issue create \ - --repo "$REPO" \ - --title "$SUB_FULL_TITLE" \ - --body "$SUB_BODY" \ - --label "${SUB_LABELS}" \ - --assignee "jmiller-moko" 2>&1) - - SUB_NUM=$(echo "$SUB_URL" | grep -oE '[0-9]+$') - if [ -n "$SUB_NUM" ]; then - SUB_LIST="${SUB_LIST}\n- [ ] ${SUB_TITLE} (#${SUB_NUM})" - SUB_NUMBERS="${SUB_NUMBERS} #${SUB_NUM}" - fi - sleep 0.3 - done - - # ── Create parent tracking issue ────────────────────────────────── - PARENT_BODY=$(printf '## %s Branch Created\n\n| Field | Value |\n|-------|-------|\n| **Branch** | `%s` |\n| **Version** | `%s` |\n| **Type** | %s |\n| **Created by** | @%s |\n| **Created at** | %s |\n| **Repository** | `%s` |\n\n## Workflow Sub-Issues\n\n%b\n\n---\n*Auto-created by [auto-dev-issue.yml](.github/workflows/auto-dev-issue.yml) on branch creation.*' \ - "$BRANCH_TYPE" "$BRANCH" "$VERSION" "$BRANCH_TYPE" "$ACTOR" "$NOW" "$REPO" "$SUB_LIST") - - PARENT_URL=$(gh issue create \ - --repo "$REPO" \ - --title "$TITLE" \ - --body "$PARENT_BODY" \ - --label "${LABEL_TYPE},version" \ - --assignee "jmiller-moko" 2>&1) - - PARENT_NUM=$(echo "$PARENT_URL" | grep -oE '[0-9]+$') - - # ── Link sub-issues back to parent ──────────────────────────────── - if [ -n "$PARENT_NUM" ]; then - for SUB in "${SUB_ISSUES[@]}"; do - IFS='|' read -r SUB_TITLE _ _ <<< "$SUB" - SUB_FULL_TITLE="${TITLE_PREFIX}(${VERSION}): ${SUB_TITLE}" - SUB_NUM=$(gh api "repos/${REPO}/issues?state=open&per_page=20" \ - --jq ".[] | select(.title == \"${SUB_FULL_TITLE}\") | .number" 2>/dev/null | head -1) - if [ -n "$SUB_NUM" ]; then - gh api "repos/${REPO}/issues/${SUB_NUM}" -X PATCH \ - -f body="$(gh api "repos/${REPO}/issues/${SUB_NUM}" --jq '.body' 2>/dev/null) - - > **Parent Issue:** #${PARENT_NUM}" --silent 2>/dev/null || true - fi - sleep 0.2 - done - fi - - # ── Create or update prerelease for alpha/beta/rc ──────────────── - if [[ "$BRANCH" == rc/* ]] || [[ "$BRANCH" == alpha/* ]] || [[ "$BRANCH" == beta/* ]]; then - case "$BRANCH_TYPE" in - Alpha) RELEASE_TAG="alpha" ;; - Beta) RELEASE_TAG="beta" ;; - "Release Candidate") RELEASE_TAG="release-candidate" ;; - esac - - EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true) - if [ -z "$EXISTING" ]; then - gh release create "$RELEASE_TAG" \ - --title "${RELEASE_TAG} (${VERSION})" \ - --notes "## ${BRANCH_TYPE} ${VERSION}\n\nBranch: \`${BRANCH}\`\nTracking issue: ${PARENT_URL}" \ - --prerelease \ - --target main 2>/dev/null || true - echo "${BRANCH_TYPE} release created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY - else - gh release edit "$RELEASE_TAG" \ - --title "${RELEASE_TAG} (${VERSION})" --prerelease 2>/dev/null || true - echo "${BRANCH_TYPE} release updated: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY - fi - fi - - # ── Summary ─────────────────────────────────────────────────────── - echo "## Dev Workflow Issues Created" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Item | Issue |" >> $GITHUB_STEP_SUMMARY - echo "|------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| **Parent** | ${PARENT_URL} |" >> $GITHUB_STEP_SUMMARY - echo "| **Sub-issues** |${SUB_NUMBERS} |" >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/shared/auto-release.yml.template b/templates/workflows/shared/auto-release.yml.template deleted file mode 100644 index 80357bb..0000000 --- a/templates/workflows/shared/auto-release.yml.template +++ /dev/null @@ -1,339 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Release -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/shared/auto-release.yml.template -# VERSION: 04.06.00 -# BRIEF: Generic build & release pipeline — version branch, platform version, badges, tag, release -# -# +========================================================================+ -# | BUILD & RELEASE PIPELINE | -# +========================================================================+ -# | | -# | Triggers on push to main (skips bot commits + [skip ci]): | -# | | -# | Every push: | -# | 1. Read version from README.md | -# | 3. Set platform version | -# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files | -# | 6. Create git tag vXX.YY.ZZ | -# | 7a. Patch: update existing GitHub Release for this minor | -# | | -# | Every version change: archives main -> version/XX.YY branch | -# | Patch 00 = development (no release). First release = patch 01. | -# | First release only (patch == 01): | -# | 7b. Create new GitHub Release | -# | | -# +========================================================================+ - -name: Build & Release - -on: - pull_request: - types: [closed] - branches: - - main - paths: - - 'src/**' - - 'htdocs/**' - workflow_dispatch: - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -permissions: - contents: write - -jobs: - release: - name: Build & Release Pipeline - runs-on: ubuntu-latest - if: >- - github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - token: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - fetch-depth: 0 - - - name: Setup MokoStandards tools - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - git clone --depth 1 --branch {{standards_branch}} --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ - /tmp/mokostandards-api - cd /tmp/mokostandards-api - composer install --no-dev --no-interaction --quiet - - # -- STEP 1: Read version ----------------------------------------------- - - name: "Step 1: Read version from README.md" - id: version - run: | - VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null) - if [ -z "$VERSION" ]; then - echo "No VERSION in README.md — skipping release" - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - # Derive major.minor for branch naming (patches update existing branch) - MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}') - PATCH=$(echo "$VERSION" | awk -F. '{print $3}') - - MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') - MINOR_NUM=$(echo "$VERSION" | awk -F. '{print $2}') - - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT" - echo "minor=$MINOR" >> "$GITHUB_OUTPUT" - echo "major=$MAJOR" >> "$GITHUB_OUTPUT" - echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT" - if [ "$PATCH" = "00" ]; then - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "is_minor=false" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (patch 00 = development — skipping release)" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - if [ "$PATCH" = "01" ]; then - echo "is_minor=true" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (first release — full pipeline)" - else - echo "is_minor=false" >> "$GITHUB_OUTPUT" - echo "Version: $VERSION (patch — platform version + badges only)" - fi - fi - - - name: Check if already released - if: steps.version.outputs.skip != 'true' - id: check - run: | - TAG="${{ steps.version.outputs.release_tag }}" - BRANCH="${{ steps.version.outputs.branch }}" - - TAG_EXISTS=false - BRANCH_EXISTS=false - - git rev-parse "$TAG" >/dev/null 2>&1 && TAG_EXISTS=true - git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q "$BRANCH" && BRANCH_EXISTS=true - - echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT" - echo "branch_exists=$BRANCH_EXISTS" >> "$GITHUB_OUTPUT" - - if [ "$TAG_EXISTS" = "true" ] && [ "$BRANCH_EXISTS" = "true" ]; then - echo "already_released=true" >> "$GITHUB_OUTPUT" - else - echo "already_released=false" >> "$GITHUB_OUTPUT" - fi - - # -- SANITY CHECKS ------------------------------------------------------- - - name: "Sanity: Pre-release validation" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - ERRORS=0 - - echo "## Pre-Release Sanity Checks" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # -- Version drift check (must pass before release) -------- - README_VER=$(grep -oP 'VERSION:\s*\K[\d.]+' README.md 2>/dev/null | head -1) - if [ "$README_VER" != "$VERSION" ]; then - echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - else - echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - fi - - # Check CHANGELOG version matches - CL_VER=$(grep -oP 'VERSION:\s*\K[\d.]+' CHANGELOG.md 2>/dev/null | head -1) - if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then - echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - fi - - # Check composer.json version if present - if [ -f "composer.json" ]; then - COMP_VER=$(grep -oP '"version"\s*:\s*"\K[^"]+' composer.json 2>/dev/null | head -1) - if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then - echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - fi - fi - - # Common checks - if [ ! -f "LICENSE" ]; then - echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS+1)) - else - echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY - fi - - if [ ! -d "src" ] && [ ! -d "htdocs" ]; then - echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY - else - echo "- Source directory present" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "$ERRORS" -gt 0 ]; then - echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY - else - echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY - fi - - # -- STEP 2: Create or update version/XX.YY archive branch --------------- - # Always runs — every version change on main archives to version/XX.YY - - name: "Step 2: Version archive branch" - if: steps.check.outputs.already_released != 'true' - run: | - BRANCH="${{ steps.version.outputs.branch }}" - IS_MINOR="${{ steps.version.outputs.is_minor }}" - PATCH="${{ steps.version.outputs.version }}" - PATCH_NUM=$(echo "$PATCH" | awk -F. '{print $3}') - - # Check if branch exists - if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then - git push origin HEAD:"$BRANCH" --force - echo "Updated archive branch: ${BRANCH} (patch ${PATCH_NUM})" >> $GITHUB_STEP_SUMMARY - else - git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH" - git push origin "$BRANCH" --force - echo "Created archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY - fi - - # -- STEP 3: Set platform version ---------------------------------------- - - name: "Step 3: Set platform version" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - php /tmp/mokostandards-api/cli/version_set_platform.php \ - --path . --version "$VERSION" --branch main - - # -- STEP 4: Update version badges ---------------------------------------- - - name: "Step 4: Update version badges" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - find . -name "*.md" ! -path "./.git/*" ! -path "./vendor/*" | while read -r f; do - if grep -q '\[VERSION:' "$f" 2>/dev/null; then - sed -i "s/\[VERSION:[[:space:]]*[0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{2\}\]/[VERSION: ${VERSION}]/" "$f" - fi - done - - # -- Commit all changes --------------------------------------------------- - - name: Commit release changes - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.already_released != 'true' - run: | - if git diff --quiet && git diff --cached --quiet; then - echo "No changes to commit" - exit 0 - fi - VERSION="${{ steps.version.outputs.version }}" - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add -A - git commit -m "chore(release): build ${VERSION} [skip ci]" \ - --author="github-actions[bot] " - git push - - # -- STEP 6: Create tag --------------------------------------------------- - - name: "Step 6: Create git tag" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.tag_exists != 'true' && - steps.version.outputs.is_minor == 'true' - run: | - RELEASE_TAG="${{ steps.version.outputs.release_tag }}" - # Only create the major release tag if it doesn't exist yet - if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then - git tag "$RELEASE_TAG" - git push origin "$RELEASE_TAG" - echo "Tag created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY - else - echo "Tag ${RELEASE_TAG} already exists" >> $GITHUB_STEP_SUMMARY - fi - echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY - - # -- STEP 7: Create or update GitHub Release ------------------------------ - - name: "Step 7: GitHub Release" - if: >- - steps.version.outputs.skip != 'true' && - steps.check.outputs.tag_exists != 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - VERSION="${{ steps.version.outputs.version }}" - RELEASE_TAG="${{ steps.version.outputs.release_tag }}" - BRANCH="${{ steps.version.outputs.branch }}" - MAJOR="${{ steps.version.outputs.major }}" - - NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null) - [ -z "$NOTES" ] && NOTES="Release ${VERSION}" - echo "$NOTES" > /tmp/release_notes.md - - # Check if the major release already exists - EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true) - - if [ -z "$EXISTING" ]; then - # First release for this major: create GitHub Release - gh release create "$RELEASE_TAG" \ - --title "v${MAJOR} (latest: ${VERSION})" \ - --notes-file /tmp/release_notes.md \ - --target "$BRANCH" - echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY - else - # Update existing major release with new version info - CURRENT_NOTES=$(gh release view "$RELEASE_TAG" --json body -q .body 2>/dev/null || true) - { - echo "$CURRENT_NOTES" - echo "" - echo "---" - echo "### ${VERSION}" - echo "" - cat /tmp/release_notes.md - } > /tmp/updated_notes.md - - gh release edit "$RELEASE_TAG" \ - --title "v${MAJOR} (latest: ${VERSION})" \ - --notes-file /tmp/updated_notes.md - echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY - fi - - # -- Summary -------------------------------------------------------------- - - name: Pipeline Summary - if: always() - run: | - VERSION="${{ steps.version.outputs.version }}" - if [ "${{ steps.version.outputs.skip }}" = "true" ]; then - echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY - echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY - elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then - echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY - else - echo "" >> $GITHUB_STEP_SUMMARY - echo "## Build & Release Complete" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY - echo "|------|--------|" >> $GITHUB_STEP_SUMMARY - 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](https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY - fi diff --git a/templates/workflows/shared/branch-freeze.yml.template b/templates/workflows/shared/branch-freeze.yml.template deleted file mode 100644 index c1f2558..0000000 --- a/templates/workflows/shared/branch-freeze.yml.template +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Automation -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/shared/branch-freeze.yml.template -# VERSION: 04.06.00 -# BRIEF: Freeze or unfreeze any branch via ruleset — manual workflow_dispatch - -name: Branch Freeze - -on: - workflow_dispatch: - inputs: - branch: - description: 'Branch to freeze/unfreeze (e.g., version/04, dev/feature)' - required: true - type: string - action: - description: 'Action to perform' - required: true - type: choice - options: - - freeze - - unfreeze - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -permissions: - contents: read - -jobs: - manage-freeze: - name: "${{ inputs.action }} branch: ${{ inputs.branch }}" - runs-on: ubuntu-latest - - steps: - - name: Check permissions - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - ACTOR="${{ github.actor }}" - REPO="${{ github.repository }}" - PERMISSION=$(gh api "repos/${REPO}/collaborators/${ACTOR}/permission" \ - --jq '.permission' 2>/dev/null || echo "read") - if [ "$PERMISSION" != "admin" ]; then - echo "Denied: only admins can freeze/unfreeze branches (${ACTOR} has ${PERMISSION})" - exit 1 - fi - - - name: "${{ inputs.action }} branch" - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - BRANCH="${{ inputs.branch }}" - ACTION="${{ inputs.action }}" - REPO="${{ github.repository }}" - RULESET_NAME="FROZEN: ${BRANCH}" - - echo "## Branch Freeze" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "$ACTION" = "freeze" ]; then - # Check if ruleset already exists - EXISTING=$(gh api "repos/${REPO}/rulesets" \ - --jq ".[] | select(.name == \"${RULESET_NAME}\") | .id" 2>/dev/null || true) - - if [ -n "$EXISTING" ]; then - echo "Branch \`${BRANCH}\` is already frozen (ruleset #${EXISTING})" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - # Create freeze ruleset — blocks all updates except admin bypass - printf '{"name":"%s","target":"branch","enforcement":"active",' "${RULESET_NAME}" > /tmp/ruleset.json - printf '"bypass_actors":[{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"always"}],' >> /tmp/ruleset.json - printf '"conditions":{"ref_name":{"include":["refs/heads/%s"],"exclude":[]}},' "${BRANCH}" >> /tmp/ruleset.json - printf '"rules":[{"type":"update"},{"type":"deletion"},{"type":"non_fast_forward"}]}' >> /tmp/ruleset.json - - RESULT=$(gh api "repos/${REPO}/rulesets" -X POST --input /tmp/ruleset.json --jq '.id' 2>&1) || true - - if echo "$RESULT" | grep -qE '^[0-9]+$'; then - echo "Frozen \`${BRANCH}\` — ruleset #${RESULT}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY - echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Branch | \`${BRANCH}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Ruleset | #${RESULT} |" >> $GITHUB_STEP_SUMMARY - echo "| Rules | No updates, no deletion, no force push |" >> $GITHUB_STEP_SUMMARY - echo "| Bypass | Repository admins only |" >> $GITHUB_STEP_SUMMARY - else - echo "Failed to freeze: ${RESULT}" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - elif [ "$ACTION" = "unfreeze" ]; then - # Find and delete the freeze ruleset - RULESET_ID=$(gh api "repos/${REPO}/rulesets" \ - --jq ".[] | select(.name == \"${RULESET_NAME}\") | .id" 2>/dev/null || true) - - if [ -z "$RULESET_ID" ]; then - echo "Branch \`${BRANCH}\` is not frozen (no ruleset found)" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - gh api "repos/${REPO}/rulesets/${RULESET_ID}" -X DELETE --silent 2>/dev/null - - echo "Unfrozen \`${BRANCH}\` — ruleset #${RULESET_ID} deleted" >> $GITHUB_STEP_SUMMARY - fi - - rm -f /tmp/ruleset.json diff --git a/templates/workflows/shared/changelog-validation.yml.template b/templates/workflows/shared/changelog-validation.yml.template deleted file mode 100644 index 0820c7c..0000000 --- a/templates/workflows/shared/changelog-validation.yml.template +++ /dev/null @@ -1,99 +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: Gitea.Workflow.Template -# INGROUP: MokoStandards.CI -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/shared/changelog-validation.yml.template -# VERSION: 04.06.00 -# BRIEF: Validates CHANGELOG.md format and version consistency -# NOTE: Deployed to .github/workflows/changelog-validation.yml in governed repos. - -name: Changelog Validation - -on: - pull_request: - branches: - - main - - 'dev/**' - workflow_dispatch: - -permissions: - contents: read - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - validate-changelog: - name: Validate CHANGELOG.md - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check CHANGELOG.md exists - run: | - echo "### Changelog Validation" >> $GITHUB_STEP_SUMMARY - if [ ! -f "CHANGELOG.md" ]; then - echo "CHANGELOG.md not found in repository root." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - echo "CHANGELOG.md exists." >> $GITHUB_STEP_SUMMARY - - - name: Check VERSION header matches README.md - run: | - # Extract version from README.md FILE INFORMATION block - README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1) - if [ -z "$README_VERSION" ]; then - echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - # Check that CHANGELOG.md has a matching version header - CHANGELOG_VERSION=$(grep -oP '^\#\#\s*\[\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' CHANGELOG.md | head -1) - if [ -z "$CHANGELOG_VERSION" ]; then - echo "No version header found in CHANGELOG.md (expected \`## [XX.YY.ZZ] - YYYY-MM-DD\`)." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - if [ "$CHANGELOG_VERSION" != "$README_VERSION" ]; then - echo "CHANGELOG latest version \`${CHANGELOG_VERSION}\` does not match README VERSION \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - echo "CHANGELOG version \`${CHANGELOG_VERSION}\` matches README VERSION." >> $GITHUB_STEP_SUMMARY - - - name: Validate conventional changelog format - run: | - ERRORS=0 - - # Check that version entries follow ## [XX.YY.ZZ] - YYYY-MM-DD format - while IFS= read -r LINE; do - if ! echo "$LINE" | grep -qP '^\#\#\s*\[[0-9]{2}\.[0-9]{2}\.[0-9]{2}\]\s*-\s*[0-9]{4}-[0-9]{2}-[0-9]{2}'; then - echo "Malformed version header: \`${LINE}\`" >> $GITHUB_STEP_SUMMARY - echo " Expected format: \`## [XX.YY.ZZ] - YYYY-MM-DD\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - done < <(grep -P '^\#\#\s*\[' CHANGELOG.md) - - ENTRY_COUNT=$(grep -cP '^\#\#\s*\[' CHANGELOG.md || echo "0") - if [ "$ENTRY_COUNT" -eq 0 ]; then - echo "No version entries found in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Found ${ENTRY_COUNT} version entr(ies) in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} format issue(s) found.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**Changelog format validation passed.**" >> $GITHUB_STEP_SUMMARY - fi diff --git a/templates/workflows/shared/deploy-demo.yml.template b/templates/workflows/shared/deploy-demo.yml.template deleted file mode 100644 index aa4dca7..0000000 --- a/templates/workflows/shared/deploy-demo.yml.template +++ /dev/null @@ -1,721 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# 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 . -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Deploy -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/shared/deploy-demo.yml.template -# VERSION: 04.06.00 -# BRIEF: SFTP deployment workflow for demo server — synced to all governed repos -# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-demo.yml in all governed repos. -# Port is resolved in order: DEMO_FTP_PORT variable → :port suffix in DEMO_FTP_HOST → 22. - -name: Deploy to Demo Server (SFTP) - -# Deploys the contents of the src/ directory to the demo server via SFTP. -# Triggers on push/merge to main — deploys the production-ready build to the demo server. -# -# Required org-level variables: DEMO_FTP_HOST, DEMO_FTP_PATH, DEMO_FTP_USERNAME -# Optional org-level variable: DEMO_FTP_PORT (auto-detected from host or defaults to 22) -# Optional org/repo variable: DEMO_FTP_SUFFIX — when set, appended to DEMO_FTP_PATH to form the -# full remote destination: DEMO_FTP_PATH/DEMO_FTP_SUFFIX -# Ignore rules: Place a .ftpignore file in the src/ directory. Each non-empty, -# non-comment line is a glob pattern tested against the relative path -# of each file (e.g. "subdir/file.txt"). The .gitignore is NOT used. -# Required org-level secret: DEMO_FTP_KEY (preferred) or DEMO_FTP_PASSWORD -# -# Access control: only users with admin or maintain role on the repository may deploy. - -on: - pull_request: - types: [closed] - branches: - - main - paths: - - 'src/**' - - 'htdocs/**' - workflow_dispatch: - inputs: - clear_remote: - description: 'Delete all files inside the remote destination folder before uploading' - required: false - default: false - type: boolean - -permissions: - contents: read - pull-requests: write - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - check-permission: - name: Verify Deployment Permission - runs-on: ubuntu-latest - steps: - - name: Check actor permission - env: - # Prefer the org-scoped GH_TOKEN secret (needed for the org membership - # fallback). Falls back to the built-in github.token so the collaborator - # endpoint still works even if GH_TOKEN is not configured. - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - ACTOR="${{ github.actor }}" - REPO="${{ github.repository }}" - ORG="${{ github.repository_owner }}" - - METHOD="" - AUTHORIZED="false" - - # Hardcoded authorized users — always allowed to deploy - AUTHORIZED_USERS="jmiller-moko github-actions[bot]" - for user in $AUTHORIZED_USERS; do - if [ "$ACTOR" = "$user" ]; then - AUTHORIZED="true" - METHOD="hardcoded allowlist" - PERMISSION="admin" - break - fi - done - - # For other actors, check repo/org permissions via API - if [ "$AUTHORIZED" != "true" ]; then - PERMISSION=$(gh api "repos/${REPO}/collaborators/${ACTOR}/permission" \ - --jq '.permission' 2>/dev/null) - METHOD="repo collaborator API" - - if [ -z "$PERMISSION" ]; then - ORG_ROLE=$(gh api "orgs/${ORG}/memberships/${ACTOR}" \ - --jq '.role' 2>/dev/null) - METHOD="org membership API" - if [ "$ORG_ROLE" = "owner" ]; then - PERMISSION="admin" - else - PERMISSION="none" - fi - fi - - case "$PERMISSION" in - admin|maintain) AUTHORIZED="true" ;; - esac - fi - - # Write detailed summary - { - echo "## 🔐 Deploy Authorization" - echo "" - echo "| Field | Value |" - echo "|-------|-------|" - echo "| **Actor** | \`${ACTOR}\` |" - echo "| **Repository** | \`${REPO}\` |" - echo "| **Permission** | \`${PERMISSION}\` |" - echo "| **Method** | ${METHOD} |" - echo "| **Authorized** | ${AUTHORIZED} |" - echo "| **Trigger** | \`${{ github.event_name }}\` |" - echo "| **Branch** | \`${{ github.ref_name }}\` |" - echo "" - } >> "$GITHUB_STEP_SUMMARY" - - if [ "$AUTHORIZED" = "true" ]; then - echo "✅ ${ACTOR} authorized to deploy (${METHOD})" >> "$GITHUB_STEP_SUMMARY" - else - echo "❌ ${ACTOR} is NOT authorized to deploy." >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Deployment requires one of:" >> "$GITHUB_STEP_SUMMARY" - echo "- Being in the hardcoded allowlist" >> "$GITHUB_STEP_SUMMARY" - echo "- Having \`admin\` or \`maintain\` role on the repository" >> "$GITHUB_STEP_SUMMARY" - exit 1 - fi - - deploy: - name: SFTP Deploy → Demo - runs-on: ubuntu-latest - needs: [check-permission] - if: >- - github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Resolve source directory - id: source - run: | - # Resolve source directory: src/ preferred, htdocs/ as fallback - if [ -d "src" ]; then - SRC="src" - elif [ -d "htdocs" ]; then - SRC="htdocs" - else - echo "⚠️ No src/ or htdocs/ directory found — skipping deployment" - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - COUNT=$(find "$SRC" -type f | wc -l) - echo "✅ Source: ${SRC}/ (${COUNT} file(s))" - echo "skip=false" >> "$GITHUB_OUTPUT" - echo "dir=${SRC}" >> "$GITHUB_OUTPUT" - - - name: Preview files to deploy - if: steps.source.outputs.skip == 'false' - env: - SOURCE_DIR: ${{ steps.source.outputs.dir }} - run: | - # ── Convert a ftpignore-style glob line to an ERE pattern ────────────── - ftpignore_to_regex() { - local line="$1" - local anchored=false - # Strip inline comments and whitespace - line=$(printf '%s' "$line" | sed 's/[[:space:]]*#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - [ -z "$line" ] && return - # Skip negation patterns (not supported) - [[ "$line" == !* ]] && return - # Trailing slash = directory marker; strip it - line="${line%/}" - # Leading slash = anchored to root; strip it - if [[ "$line" == /* ]]; then - anchored=true - line="${line#/}" - fi - # Escape ERE special chars, then restore glob semantics - local regex - regex=$(printf '%s' "$line" \ - | sed 's/[.+^${}()|[\\]/\\&/g' \ - | sed 's/\\\*\\\*/\x01/g' \ - | sed 's/\\\*/[^\/]*/g' \ - | sed 's/\x01/.*/g' \ - | sed 's/\\\?/[^\/]/g') - if $anchored; then - printf '^%s(/|$)' "$regex" - else - printf '(^|/)%s(/|$)' "$regex" - fi - } - - # ── Read .ftpignore (ftpignore-style globs) ───────────────────────── - IGNORE_PATTERNS=() - IGNORE_SOURCES=() - if [ -f "${SOURCE_DIR}/.ftpignore" ]; then - while IFS= read -r line; do - [[ "$line" =~ ^[[:space:]]*$ || "$line" =~ ^[[:space:]]*# ]] && continue - regex=$(ftpignore_to_regex "$line") - [ -n "$regex" ] && IGNORE_PATTERNS+=("$regex") && IGNORE_SOURCES+=("$line") - done < "${SOURCE_DIR}/.ftpignore" - fi - - # ── Walk src/ and classify every file ──────────────────────────────── - WILL_UPLOAD=() - IGNORED_FILES=() - while IFS= read -r -d '' file; do - rel="${file#${SOURCE_DIR}/}" - SKIP=false - for i in "${!IGNORE_PATTERNS[@]}"; do - if echo "$rel" | grep -qE "${IGNORE_PATTERNS[$i]}" 2>/dev/null; then - IGNORED_FILES+=("$rel | .ftpignore \`${IGNORE_SOURCES[$i]}\`") - SKIP=true; break - fi - done - $SKIP && continue - WILL_UPLOAD+=("$rel") - done < <(find "$SOURCE_DIR" -type f -print0 | sort -z) - - UPLOAD_COUNT="${#WILL_UPLOAD[@]}" - IGNORE_COUNT="${#IGNORED_FILES[@]}" - - echo "ℹ️ ${UPLOAD_COUNT} file(s) will be uploaded, ${IGNORE_COUNT} ignored" - - # ── Write deployment preview to step summary ────────────────────────── - { - echo "## 📋 Deployment Preview" - echo "" - echo "| Field | Value |" - echo "|---|---|" - echo "| Source | \`${SOURCE_DIR}/\` |" - echo "| Files to upload | **${UPLOAD_COUNT}** |" - echo "| Files ignored | **${IGNORE_COUNT}** |" - echo "" - if [ "${UPLOAD_COUNT}" -gt 0 ]; then - echo "### 📂 Files that will be uploaded" - echo '```' - printf '%s\n' "${WILL_UPLOAD[@]}" - echo '```' - echo "" - fi - if [ "${IGNORE_COUNT}" -gt 0 ]; then - echo "### ⏭️ Files excluded" - echo "| File | Reason |" - echo "|---|---|" - for entry in "${IGNORED_FILES[@]}"; do - f="${entry% | *}"; r="${entry##* | }" - echo "| \`${f}\` | ${r} |" - done - echo "" - fi - } >> "$GITHUB_STEP_SUMMARY" - - - name: Resolve SFTP host and port - if: steps.source.outputs.skip == 'false' - id: conn - env: - HOST_RAW: ${{ vars.DEMO_FTP_HOST }} - PORT_VAR: ${{ vars.DEMO_FTP_PORT }} - run: | - HOST="$HOST_RAW" - PORT="$PORT_VAR" - - if [ -z "$HOST" ]; then - echo "⏭️ DEMO_FTP_HOST not configured — skipping demo deployment." - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Priority 1 — explicit DEMO_FTP_PORT variable - if [ -n "$PORT" ]; then - echo "ℹ️ Using explicit DEMO_FTP_PORT=${PORT}" - - # Priority 2 — port embedded in DEMO_FTP_HOST (host:port) - elif [[ "$HOST" == *:* ]]; then - PORT="${HOST##*:}" - HOST="${HOST%:*}" - echo "ℹ️ Extracted port ${PORT} from DEMO_FTP_HOST" - - # Priority 3 — SFTP default - else - PORT="22" - echo "ℹ️ No port specified — defaulting to SFTP port 22" - fi - - echo "host=${HOST}" >> "$GITHUB_OUTPUT" - echo "port=${PORT}" >> "$GITHUB_OUTPUT" - echo "SFTP target: ${HOST}:${PORT}" - - - name: Build remote path - if: steps.source.outputs.skip == 'false' && steps.conn.outputs.skip != 'true' - id: remote - env: - DEMO_FTP_PATH: ${{ vars.DEMO_FTP_PATH }} - DEMO_FTP_SUFFIX: ${{ vars.DEMO_FTP_SUFFIX }} - run: | - BASE="$DEMO_FTP_PATH" - - if [ -z "$BASE" ]; then - echo "⏭️ DEMO_FTP_PATH not configured — skipping demo deployment." - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # DEMO_FTP_SUFFIX is required — it identifies the remote subdirectory for this repo. - # Without it we cannot safely determine the deployment target. - if [ -z "$DEMO_FTP_SUFFIX" ]; then - echo "⏭️ DEMO_FTP_SUFFIX variable is not set — skipping deployment." - echo " Set DEMO_FTP_SUFFIX as a repo or org variable to enable deploy-demo." - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "path=" >> "$GITHUB_OUTPUT" - exit 0 - fi - - REMOTE="${BASE%/}/${DEMO_FTP_SUFFIX#/}" - - # ── Platform-specific path safety guards ────────────────────────────── - PLATFORM="" - MOKO_FILE=".github/.mokostandards"; [ ! -f "$MOKO_FILE" ] && MOKO_FILE=".mokostandards"; if [ -f "$MOKO_FILE" ]; then - PLATFORM=$(grep -E '^platform:' "$MOKO_FILE" | sed 's/.*:[[:space:]]*//' | tr -d '"') - fi - - if [ "$PLATFORM" = "crm-module" ]; then - # Dolibarr modules must deploy under htdocs/custom/ — guard against - # accidentally overwriting server root or unrelated directories. - if [[ "$REMOTE" != *custom* ]]; then - echo "❌ Safety check failed: Dolibarr (crm-module) remote path must contain 'custom'." - echo " Current path: ${REMOTE}" - echo " Set DEMO_FTP_SUFFIX to the module's htdocs/custom/ subdirectory." - exit 1 - fi - fi - - if [ "$PLATFORM" = "waas-component" ]; then - # Joomla extensions may only deploy to the server's tmp/ directory. - if [[ "$REMOTE" != *tmp* ]]; then - echo "❌ Safety check failed: Joomla (waas-component) remote path must contain 'tmp'." - echo " Current path: ${REMOTE}" - echo " Set DEMO_FTP_SUFFIX to a path under the server tmp/ directory." - exit 1 - fi - fi - - echo "ℹ️ Remote path: ${REMOTE}" - echo "path=${REMOTE}" >> "$GITHUB_OUTPUT" - - - name: Detect SFTP authentication method - if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' - id: auth - env: - HAS_KEY: ${{ secrets.DEMO_FTP_KEY }} - HAS_PASSWORD: ${{ secrets.DEMO_FTP_PASSWORD }} - run: | - if [ -n "$HAS_KEY" ] && [ -n "$HAS_PASSWORD" ]; then - # Both set: key auth with password as passphrase; falls back to password-only if key fails - echo "method=key" >> "$GITHUB_OUTPUT" - echo "use_passphrase=true" >> "$GITHUB_OUTPUT" - echo "has_password=true" >> "$GITHUB_OUTPUT" - echo "ℹ️ Primary: SSH key + passphrase (DEMO_FTP_KEY / DEMO_FTP_PASSWORD)" - echo "ℹ️ Fallback: password-only auth if key authentication fails" - elif [ -n "$HAS_KEY" ]; then - # Key only: no passphrase, no password fallback - echo "method=key" >> "$GITHUB_OUTPUT" - echo "use_passphrase=false" >> "$GITHUB_OUTPUT" - echo "has_password=false" >> "$GITHUB_OUTPUT" - echo "ℹ️ Using SSH key authentication (DEMO_FTP_KEY, no passphrase, no fallback)" - elif [ -n "$HAS_PASSWORD" ]; then - # Password only: direct SFTP password auth - echo "method=password" >> "$GITHUB_OUTPUT" - echo "use_passphrase=false" >> "$GITHUB_OUTPUT" - echo "has_password=true" >> "$GITHUB_OUTPUT" - echo "ℹ️ Using password authentication (DEMO_FTP_PASSWORD)" - else - echo "❌ No SFTP credentials configured." - echo " Set DEMO_FTP_KEY (preferred) or DEMO_FTP_PASSWORD as an org-level secret." - exit 1 - fi - - - name: Setup PHP - if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' - uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0 - with: - php-version: '8.1' - tools: composer - - - name: Setup MokoStandards deploy tools - if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - git clone --depth 1 --branch {{standards_branch}} --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ - /tmp/mokostandards-api - cd /tmp/mokostandards-api - composer install --no-dev --no-interaction --quiet - - - name: Clear remote destination folder (manual only) - if: >- - steps.source.outputs.skip == 'false' && - steps.remote.outputs.skip != 'true' && - inputs.clear_remote == true - env: - SFTP_HOST: ${{ steps.conn.outputs.host }} - SFTP_PORT: ${{ steps.conn.outputs.port }} - SFTP_USER: ${{ vars.DEMO_FTP_USERNAME }} - SFTP_KEY: ${{ secrets.DEMO_FTP_KEY }} - SFTP_PASSWORD: ${{ secrets.DEMO_FTP_PASSWORD }} - AUTH_METHOD: ${{ steps.auth.outputs.method }} - USE_PASSPHRASE: ${{ steps.auth.outputs.use_passphrase }} - HAS_PASSWORD: ${{ steps.auth.outputs.has_password }} - REMOTE_PATH: ${{ steps.remote.outputs.path }} - run: | - cat > /tmp/moko_clear.php << 'PHPEOF' - login($username, $key)) { - if ($password !== '') { - echo "⚠️ Key auth failed — falling back to password\n"; - if (!$sftp->login($username, $password)) { - fwrite(STDERR, "❌ Both key and password authentication failed\n"); - exit(1); - } - echo "✅ Connected via password authentication (key fallback)\n"; - } else { - fwrite(STDERR, "❌ Key authentication failed and no password fallback is available\n"); - exit(1); - } - } else { - echo "✅ Connected via SSH key authentication\n"; - } - } else { - if (!$sftp->login($username, (string) getenv('SFTP_PASSWORD'))) { - fwrite(STDERR, "❌ Password authentication failed\n"); - exit(1); - } - echo "✅ Connected via password authentication\n"; - } - - // ── Recursive delete ──────────────────────────────────────────── - function rmrf(SFTP $sftp, string $path): void - { - $entries = $sftp->nlist($path); - if ($entries === false) { - return; // path does not exist — nothing to clear - } - foreach ($entries as $name) { - if ($name === '.' || $name === '..') { - continue; - } - $entry = "{$path}/{$name}"; - if ($sftp->is_dir($entry)) { - rmrf($sftp, $entry); - $sftp->rmdir($entry); - echo " 🗑️ Removed dir: {$entry}\n"; - } else { - $sftp->delete($entry); - echo " 🗑️ Removed file: {$entry}\n"; - } - } - } - - // ── Create remote directory tree ──────────────────────────────── - function sftpMakedirs(SFTP $sftp, string $path): void - { - $parts = array_values(array_filter(explode('/', $path), fn(string $p) => $p !== '')); - $current = str_starts_with($path, '/') ? '' : ''; - foreach ($parts as $part) { - $current .= '/' . $part; - $sftp->mkdir($current); // silently returns false if already exists - } - } - - rmrf($sftp, $remotePath); - sftpMakedirs($sftp, $remotePath); - echo "✅ Remote folder ready: {$remotePath}\n"; - PHPEOF - php /tmp/moko_clear.php - - - name: Deploy via SFTP - if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' - env: - SFTP_HOST: ${{ steps.conn.outputs.host }} - SFTP_PORT: ${{ steps.conn.outputs.port }} - SFTP_USER: ${{ vars.DEMO_FTP_USERNAME }} - SFTP_KEY: ${{ secrets.DEMO_FTP_KEY }} - SFTP_PASSWORD: ${{ secrets.DEMO_FTP_PASSWORD }} - AUTH_METHOD: ${{ steps.auth.outputs.method }} - USE_PASSPHRASE: ${{ steps.auth.outputs.use_passphrase }} - REMOTE_PATH: ${{ steps.remote.outputs.path }} - SOURCE_DIR: ${{ steps.source.outputs.dir }} - run: | - # ── Write SSH key to temp file (key auth only) ──────────────────────── - if [ "$AUTH_METHOD" = "key" ]; then - printf '%s' "$SFTP_KEY" > /tmp/deploy_key - chmod 600 /tmp/deploy_key - fi - - # ── Generate sftp-config.json safely via jq ─────────────────────────── - if [ "$AUTH_METHOD" = "key" ]; then - jq -n \ - --arg host "$SFTP_HOST" \ - --argjson port "${SFTP_PORT:-22}" \ - --arg user "$SFTP_USER" \ - --arg path "$REMOTE_PATH" \ - --arg key "/tmp/deploy_key" \ - '{host:$host, port:$port, user:$user, remote_path:$path, ssh_key_file:$key}' \ - > /tmp/sftp-config.json - else - jq -n \ - --arg host "$SFTP_HOST" \ - --argjson port "${SFTP_PORT:-22}" \ - --arg user "$SFTP_USER" \ - --arg path "$REMOTE_PATH" \ - --arg pass "$SFTP_PASSWORD" \ - '{host:$host, port:$port, user:$user, remote_path:$path, password:$pass}' \ - > /tmp/sftp-config.json - fi - - # ── Write update files (demo = stable) ───────────────────────────── - PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true) - VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "unknown") - REPO="${{ github.repository }}" - - if [ "$PLATFORM" = "crm-module" ]; then - printf '%s' "$VERSION" > update.txt - fi - - if [ "$PLATFORM" = "waas-component" ]; then - MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1 || true) - if [ -n "$MANIFEST" ]; then - EXT_NAME=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}") - EXT_TYPE=$(grep -oP ']+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component") - EXT_ELEMENT=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml) - EXT_CLIENT=$(grep -oP ']+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "") - EXT_FOLDER=$(grep -oP ']+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "") - TARGET_PLATFORM=$(grep -oP '/dev/null | head -1 || true) - [ -n "$TARGET_PLATFORM" ] && TARGET_PLATFORM="${TARGET_PLATFORM}>" - [ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '' "/") - - CLIENT_TAG="" - if [ -n "$EXT_CLIENT" ]; then CLIENT_TAG="${EXT_CLIENT}"; elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then CLIENT_TAG="site"; fi - FOLDER_TAG="" - if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then FOLDER_TAG="${EXT_FOLDER}"; fi - - DOWNLOAD_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip" - { - printf '%s\n' '' - printf '%s\n' '' - printf '%s\n' ' ' - printf '%s\n' " ${EXT_NAME}" - printf '%s\n' " ${EXT_NAME} update" - printf '%s\n' " ${EXT_ELEMENT}" - printf '%s\n' " ${EXT_TYPE}" - printf '%s\n' " ${VERSION}" - [ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}" - [ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}" - printf '%s\n' ' ' - printf '%s\n' ' stable' - printf '%s\n' ' ' - printf '%s\n' " https://github.com/${REPO}" - printf '%s\n' ' ' - printf '%s\n' " ${DOWNLOAD_URL}" - printf '%s\n' ' ' - printf '%s\n' " ${TARGET_PLATFORM}" - printf '%s\n' ' Moko Consulting' - printf '%s\n' ' https://mokoconsulting.tech' - printf '%s\n' ' ' - printf '%s\n' '' - } > updates.xml - fi - fi - - # ── Run deploy-sftp.php from MokoStandards ──────────────────────────── - DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json) - if [ "$USE_PASSPHRASE" = "true" ]; then - DEPLOY_ARGS+=(--key-passphrase "$SFTP_PASSWORD") - fi - - 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 - # Remove temp files that should never be left behind - rm -f /tmp/deploy_key /tmp/sftp-config.json - - - name: Create or update failure issue - if: failure() && steps.remote.outputs.skip != 'true' && steps.conn.outputs.skip != 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - REPO="${{ github.repository }}" - RUN_URL="${{ github.server_url }}/${REPO}/actions/runs/${{ github.run_id }}" - ACTOR="${{ github.actor }}" - BRANCH="${{ github.ref_name }}" - EVENT="${{ github.event_name }}" - NOW=$(date -u '+%Y-%m-%d %H:%M:%S UTC') - LABEL="deploy-failure" - - TITLE="fix: Demo deployment failed — ${REPO}" - BODY="## Demo Deployment Failed - - A deployment to the demo server failed and requires attention. - - | Field | Value | - |-------|-------| - | **Repository** | \`${REPO}\` | - | **Branch** | \`${BRANCH}\` | - | **Trigger** | ${EVENT} | - | **Actor** | @${ACTOR} | - | **Failed at** | ${NOW} | - | **Run** | [View workflow run](${RUN_URL}) | - - ### Next steps - 1. Review the [workflow run log](${RUN_URL}) for the specific error. - 2. Fix the underlying issue (credentials, SFTP connectivity, permissions). - 3. Re-trigger the deployment via **Actions → Deploy to Demo Server → Run workflow**. - - --- - *Auto-created by deploy-demo.yml — close this issue once the deployment is resolved.*" - - # Ensure the label exists (idempotent — no-op if already present) - gh label create "$LABEL" \ - --repo "$REPO" \ - --color "CC0000" \ - --description "Automated deploy failure tracking" \ - --force 2>/dev/null || true - - # Look for an existing open deploy-failure issue - EXISTING=$(gh api "repos/${REPO}/issues?labels=${LABEL}&state=all&per_page=1&sort=created&direction=desc" \ - --jq '.[0].number' 2>/dev/null) - - if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then - gh api "repos/${REPO}/issues/${EXISTING}" \ - -X PATCH \ - -f title="$TITLE" \ - -f body="$BODY" \ - -f state="open" \ - --silent - echo "📋 Failure issue #${EXISTING} updated/reopened: ${REPO}" >> "$GITHUB_STEP_SUMMARY" - else - gh issue create \ - --repo "$REPO" \ - --title "$TITLE" \ - --body "$BODY" \ - --label "$LABEL" \ - --assignee "jmiller-moko" \ - | tee -a "$GITHUB_STEP_SUMMARY" - fi - - - name: Deployment summary - if: always() - run: | - if [ "${{ steps.source.outputs.skip }}" == "true" ]; then - echo "### ⏭️ Deployment Skipped" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "No \`src/\` directory found in this repository." >> "$GITHUB_STEP_SUMMARY" - elif [ "${{ job.status }}" == "success" ]; then - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "### ✅ Demo Deployment Successful" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "| Field | Value |" >> "$GITHUB_STEP_SUMMARY" - echo "|-------|-------|" >> "$GITHUB_STEP_SUMMARY" - echo "| Host | \`${{ steps.conn.outputs.host }}:${{ steps.conn.outputs.port }}\` |" >> "$GITHUB_STEP_SUMMARY" - echo "| Remote path | \`${{ steps.remote.outputs.path }}\` |" >> "$GITHUB_STEP_SUMMARY" - echo "| Source | \`src/\` |" >> "$GITHUB_STEP_SUMMARY" - echo "| Trigger | ${{ github.event_name }} |" >> "$GITHUB_STEP_SUMMARY" - echo "| Auth | ${{ steps.auth.outputs.method }} |" >> "$GITHUB_STEP_SUMMARY" - echo "| Clear remote | ${{ inputs.clear_remote || 'false' }} |" >> "$GITHUB_STEP_SUMMARY" - else - echo "### ❌ Demo Deployment Failed" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Check the job log above for error details." >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/templates/workflows/shared/deploy-dev.yml.template b/templates/workflows/shared/deploy-dev.yml.template deleted file mode 100644 index 880e30c..0000000 --- a/templates/workflows/shared/deploy-dev.yml.template +++ /dev/null @@ -1,686 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# 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 . -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Deploy -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/shared/deploy-dev.yml.template -# VERSION: 04.06.00 -# BRIEF: SFTP deployment workflow for development server — synced to all governed repos -# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-dev.yml in all governed repos. -# Port is resolved in order: DEV_FTP_PORT variable → :port suffix in DEV_FTP_HOST → 22. - -name: Deploy to Dev Server (SFTP) - -# Deploys the contents of the src/ directory to the development server via SFTP. -# Triggers on every pull_request to development branches (so the dev server always -# reflects the latest PR state) and on push/merge to main branches. -# -# Required org-level variables: DEV_FTP_HOST, DEV_FTP_PATH, DEV_FTP_USERNAME -# Optional org-level variable: DEV_FTP_PORT (auto-detected from host or defaults to 22) -# Optional org/repo variable: DEV_FTP_SUFFIX — when set, appended to DEV_FTP_PATH to form the -# full remote destination: DEV_FTP_PATH/DEV_FTP_SUFFIX -# Ignore rules: Place a .ftpignore file in the src/ directory. Each non-empty, -# non-comment line is a glob pattern tested against the relative path -# of each file (e.g. "subdir/file.txt"). The .gitignore is NOT used. -# Required org-level secret: DEV_FTP_KEY (preferred) or DEV_FTP_PASSWORD -# -# Access control: only users with admin or maintain role on the repository may deploy. - -on: - pull_request: - types: [closed] - branches: - - 'dev/**' - - 'rc/**' - - develop - - development - paths: - - 'src/**' - - 'htdocs/**' - workflow_dispatch: - inputs: - clear_remote: - description: 'Delete all files inside the remote destination folder before uploading' - required: false - default: false - type: boolean - -permissions: - contents: read - pull-requests: write - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - check-permission: - name: Verify Deployment Permission - runs-on: ubuntu-latest - steps: - - name: Check actor permission - env: - # Prefer the org-scoped GH_TOKEN secret (needed for the org membership - # fallback). Falls back to the built-in github.token so the collaborator - # endpoint still works even if GH_TOKEN is not configured. - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - ACTOR="${{ github.actor }}" - REPO="${{ github.repository }}" - ORG="${{ github.repository_owner }}" - - METHOD="" - AUTHORIZED="false" - - # Hardcoded authorized users — always allowed to deploy - AUTHORIZED_USERS="jmiller-moko github-actions[bot]" - for user in $AUTHORIZED_USERS; do - if [ "$ACTOR" = "$user" ]; then - AUTHORIZED="true" - METHOD="hardcoded allowlist" - PERMISSION="admin" - break - fi - done - - # For other actors, check repo/org permissions via API - if [ "$AUTHORIZED" != "true" ]; then - PERMISSION=$(gh api "repos/${REPO}/collaborators/${ACTOR}/permission" \ - --jq '.permission' 2>/dev/null) - METHOD="repo collaborator API" - - if [ -z "$PERMISSION" ]; then - ORG_ROLE=$(gh api "orgs/${ORG}/memberships/${ACTOR}" \ - --jq '.role' 2>/dev/null) - METHOD="org membership API" - if [ "$ORG_ROLE" = "owner" ]; then - PERMISSION="admin" - else - PERMISSION="none" - fi - fi - - case "$PERMISSION" in - admin|maintain) AUTHORIZED="true" ;; - esac - fi - - # Write detailed summary - { - echo "## 🔐 Deploy Authorization" - echo "" - echo "| Field | Value |" - echo "|-------|-------|" - echo "| **Actor** | \`${ACTOR}\` |" - echo "| **Repository** | \`${REPO}\` |" - echo "| **Permission** | \`${PERMISSION}\` |" - echo "| **Method** | ${METHOD} |" - echo "| **Authorized** | ${AUTHORIZED} |" - echo "| **Trigger** | \`${{ github.event_name }}\` |" - echo "| **Branch** | \`${{ github.ref_name }}\` |" - echo "" - } >> "$GITHUB_STEP_SUMMARY" - - if [ "$AUTHORIZED" = "true" ]; then - echo "✅ ${ACTOR} authorized to deploy (${METHOD})" >> "$GITHUB_STEP_SUMMARY" - else - echo "❌ ${ACTOR} is NOT authorized to deploy." >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Deployment requires one of:" >> "$GITHUB_STEP_SUMMARY" - echo "- Being in the hardcoded allowlist" >> "$GITHUB_STEP_SUMMARY" - echo "- Having \`admin\` or \`maintain\` role on the repository" >> "$GITHUB_STEP_SUMMARY" - exit 1 - fi - - deploy: - name: SFTP Deploy → Dev - runs-on: ubuntu-latest - needs: [check-permission] - if: >- - github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Resolve source directory - id: source - run: | - # Resolve source directory: src/ preferred, htdocs/ as fallback - if [ -d "src" ]; then - SRC="src" - elif [ -d "htdocs" ]; then - SRC="htdocs" - else - echo "⚠️ No src/ or htdocs/ directory found — skipping deployment" - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - COUNT=$(find "$SRC" -type f | wc -l) - echo "✅ Source: ${SRC}/ (${COUNT} file(s))" - echo "skip=false" >> "$GITHUB_OUTPUT" - echo "dir=${SRC}" >> "$GITHUB_OUTPUT" - - - name: Preview files to deploy - if: steps.source.outputs.skip == 'false' - env: - SOURCE_DIR: ${{ steps.source.outputs.dir }} - run: | - # ── Convert a ftpignore-style glob line to an ERE pattern ────────────── - ftpignore_to_regex() { - local line="$1" - local anchored=false - # Strip inline comments and whitespace - line=$(printf '%s' "$line" | sed 's/[[:space:]]*#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - [ -z "$line" ] && return - # Skip negation patterns (not supported) - [[ "$line" == !* ]] && return - # Trailing slash = directory marker; strip it - line="${line%/}" - # Leading slash = anchored to root; strip it - if [[ "$line" == /* ]]; then - anchored=true - line="${line#/}" - fi - # Escape ERE special chars, then restore glob semantics - local regex - regex=$(printf '%s' "$line" \ - | sed 's/[.+^${}()|[\\]/\\&/g' \ - | sed 's/\\\*\\\*/\x01/g' \ - | sed 's/\\\*/[^\/]*/g' \ - | sed 's/\x01/.*/g' \ - | sed 's/\\\?/[^\/]/g') - if $anchored; then - printf '^%s(/|$)' "$regex" - else - printf '(^|/)%s(/|$)' "$regex" - fi - } - - # ── Read .ftpignore (ftpignore-style globs) ───────────────────────── - IGNORE_PATTERNS=() - IGNORE_SOURCES=() - if [ -f "${SOURCE_DIR}/.ftpignore" ]; then - while IFS= read -r line; do - [[ "$line" =~ ^[[:space:]]*$ || "$line" =~ ^[[:space:]]*# ]] && continue - regex=$(ftpignore_to_regex "$line") - [ -n "$regex" ] && IGNORE_PATTERNS+=("$regex") && IGNORE_SOURCES+=("$line") - done < "${SOURCE_DIR}/.ftpignore" - fi - - # ── Walk src/ and classify every file ──────────────────────────────── - WILL_UPLOAD=() - IGNORED_FILES=() - while IFS= read -r -d '' file; do - rel="${file#${SOURCE_DIR}/}" - SKIP=false - for i in "${!IGNORE_PATTERNS[@]}"; do - if echo "$rel" | grep -qE "${IGNORE_PATTERNS[$i]}" 2>/dev/null; then - IGNORED_FILES+=("$rel | .ftpignore \`${IGNORE_SOURCES[$i]}\`") - SKIP=true; break - fi - done - $SKIP && continue - WILL_UPLOAD+=("$rel") - done < <(find "$SOURCE_DIR" -type f -print0 | sort -z) - - UPLOAD_COUNT="${#WILL_UPLOAD[@]}" - IGNORE_COUNT="${#IGNORED_FILES[@]}" - - echo "ℹ️ ${UPLOAD_COUNT} file(s) will be uploaded, ${IGNORE_COUNT} ignored" - - # ── Write deployment preview to step summary ────────────────────────── - { - echo "## 📋 Deployment Preview" - echo "" - echo "| Field | Value |" - echo "|---|---|" - echo "| Source | \`${SOURCE_DIR}/\` |" - echo "| Files to upload | **${UPLOAD_COUNT}** |" - echo "| Files ignored | **${IGNORE_COUNT}** |" - echo "" - if [ "${UPLOAD_COUNT}" -gt 0 ]; then - echo "### 📂 Files that will be uploaded" - echo '```' - printf '%s\n' "${WILL_UPLOAD[@]}" - echo '```' - echo "" - fi - if [ "${IGNORE_COUNT}" -gt 0 ]; then - echo "### ⏭️ Files excluded" - echo "| File | Reason |" - echo "|---|---|" - for entry in "${IGNORED_FILES[@]}"; do - f="${entry% | *}"; r="${entry##* | }" - echo "| \`${f}\` | ${r} |" - done - echo "" - fi - } >> "$GITHUB_STEP_SUMMARY" - - - name: Resolve SFTP host and port - if: steps.source.outputs.skip == 'false' - id: conn - env: - HOST_RAW: ${{ vars.DEV_FTP_HOST }} - PORT_VAR: ${{ vars.DEV_FTP_PORT }} - run: | - HOST="$HOST_RAW" - PORT="$PORT_VAR" - - # Priority 1 — explicit DEV_FTP_PORT variable - if [ -n "$PORT" ]; then - echo "ℹ️ Using explicit DEV_FTP_PORT=${PORT}" - - # Priority 2 — port embedded in DEV_FTP_HOST (host:port) - elif [[ "$HOST" == *:* ]]; then - PORT="${HOST##*:}" - HOST="${HOST%:*}" - echo "ℹ️ Extracted port ${PORT} from DEV_FTP_HOST" - - # Priority 3 — SFTP default - else - PORT="22" - echo "ℹ️ No port specified — defaulting to SFTP port 22" - fi - - echo "host=${HOST}" >> "$GITHUB_OUTPUT" - echo "port=${PORT}" >> "$GITHUB_OUTPUT" - echo "SFTP target: ${HOST}:${PORT}" - - - name: Build remote path - if: steps.source.outputs.skip == 'false' - id: remote - env: - DEV_FTP_PATH: ${{ vars.DEV_FTP_PATH }} - DEV_FTP_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }} - run: | - BASE="$DEV_FTP_PATH" - - if [ -z "$BASE" ]; then - echo "❌ DEV_FTP_PATH is not set." - echo " Configure it as an org-level variable (Settings → Variables) and" - echo " ensure this repository has been granted access to it." - exit 1 - fi - - # DEV_FTP_SUFFIX is required — it identifies the remote subdirectory for this repo. - # Without it we cannot safely determine the deployment target. - if [ -z "$DEV_FTP_SUFFIX" ]; then - echo "⏭️ DEV_FTP_SUFFIX variable is not set — skipping deployment." - echo " Set DEV_FTP_SUFFIX as a repo or org variable to enable deploy-dev." - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "path=" >> "$GITHUB_OUTPUT" - exit 0 - fi - - REMOTE="${BASE%/}/${DEV_FTP_SUFFIX#/}" - - # ── Platform-specific path safety guards ────────────────────────────── - PLATFORM="" - MOKO_FILE=".github/.mokostandards"; [ ! -f "$MOKO_FILE" ] && MOKO_FILE=".mokostandards"; if [ -f "$MOKO_FILE" ]; then - PLATFORM=$(grep -oP '^platform:.*' "$MOKO_FILE" 2>/dev/null || true) - fi - - if [ "$PLATFORM" = "crm-module" ]; then - # Dolibarr modules must deploy under htdocs/custom/ — guard against - # accidentally overwriting server root or unrelated directories. - if [[ "$REMOTE" != *custom* ]]; then - echo "❌ Safety check failed: Dolibarr (crm-module) remote path must contain 'custom'." - echo " Current path: ${REMOTE}" - echo " Set DEV_FTP_SUFFIX to the module's htdocs/custom/ subdirectory." - exit 1 - fi - fi - - if [ "$PLATFORM" = "waas-component" ]; then - # Joomla extensions may only deploy to the server's tmp/ directory. - if [[ "$REMOTE" != *tmp* ]]; then - echo "❌ Safety check failed: Joomla (waas-component) remote path must contain 'tmp'." - echo " Current path: ${REMOTE}" - echo " Set DEV_FTP_SUFFIX to a path under the server tmp/ directory." - exit 1 - fi - fi - - echo "ℹ️ Remote path: ${REMOTE}" - echo "path=${REMOTE}" >> "$GITHUB_OUTPUT" - - - name: Detect SFTP authentication method - if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' - id: auth - env: - HAS_KEY: ${{ secrets.DEV_FTP_KEY }} - HAS_PASSWORD: ${{ secrets.DEV_FTP_PASSWORD }} - run: | - if [ -n "$HAS_KEY" ] && [ -n "$HAS_PASSWORD" ]; then - # Both set: key auth with password as passphrase; falls back to password-only if key fails - echo "method=key" >> "$GITHUB_OUTPUT" - echo "use_passphrase=true" >> "$GITHUB_OUTPUT" - echo "has_password=true" >> "$GITHUB_OUTPUT" - echo "ℹ️ Primary: SSH key + passphrase (DEV_FTP_KEY / DEV_FTP_PASSWORD)" - echo "ℹ️ Fallback: password-only auth if key authentication fails" - elif [ -n "$HAS_KEY" ]; then - # Key only: no passphrase, no password fallback - echo "method=key" >> "$GITHUB_OUTPUT" - echo "use_passphrase=false" >> "$GITHUB_OUTPUT" - echo "has_password=false" >> "$GITHUB_OUTPUT" - echo "ℹ️ Using SSH key authentication (DEV_FTP_KEY, no passphrase, no fallback)" - elif [ -n "$HAS_PASSWORD" ]; then - # Password only: direct SFTP password auth - echo "method=password" >> "$GITHUB_OUTPUT" - echo "use_passphrase=false" >> "$GITHUB_OUTPUT" - echo "has_password=true" >> "$GITHUB_OUTPUT" - echo "ℹ️ Using password authentication (DEV_FTP_PASSWORD)" - else - echo "❌ No SFTP credentials configured." - echo " Set DEV_FTP_KEY (preferred) or DEV_FTP_PASSWORD as an org-level secret." - exit 1 - fi - - - name: Setup PHP - if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' - uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0 - with: - php-version: '8.1' - tools: composer - - - name: Setup MokoStandards deploy tools - if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - git clone --depth 1 --branch {{standards_branch}} --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ - /tmp/mokostandards-api - cd /tmp/mokostandards-api - composer install --no-dev --no-interaction --quiet - - - name: Clear remote destination folder (manual only) - if: >- - steps.source.outputs.skip == 'false' && - steps.remote.outputs.skip != 'true' && - inputs.clear_remote == true - env: - SFTP_HOST: ${{ steps.conn.outputs.host }} - SFTP_PORT: ${{ steps.conn.outputs.port }} - SFTP_USER: ${{ vars.DEV_FTP_USERNAME }} - SFTP_KEY: ${{ secrets.DEV_FTP_KEY }} - SFTP_PASSWORD: ${{ secrets.DEV_FTP_PASSWORD }} - AUTH_METHOD: ${{ steps.auth.outputs.method }} - USE_PASSPHRASE: ${{ steps.auth.outputs.use_passphrase }} - HAS_PASSWORD: ${{ steps.auth.outputs.has_password }} - REMOTE_PATH: ${{ steps.remote.outputs.path }} - run: | - cat > /tmp/moko_clear.php << 'PHPEOF' - login($username, $key)) { - if ($password !== '') { - echo "⚠️ Key auth failed — falling back to password\n"; - if (!$sftp->login($username, $password)) { - fwrite(STDERR, "❌ Both key and password authentication failed\n"); - exit(1); - } - echo "✅ Connected via password authentication (key fallback)\n"; - } else { - fwrite(STDERR, "❌ Key authentication failed and no password fallback is available\n"); - exit(1); - } - } else { - echo "✅ Connected via SSH key authentication\n"; - } - } else { - if (!$sftp->login($username, (string) getenv('SFTP_PASSWORD'))) { - fwrite(STDERR, "❌ Password authentication failed\n"); - exit(1); - } - echo "✅ Connected via password authentication\n"; - } - - // ── Recursive delete ──────────────────────────────────────────── - function rmrf(SFTP $sftp, string $path): void - { - $entries = $sftp->nlist($path); - if ($entries === false) { - return; // path does not exist — nothing to clear - } - foreach ($entries as $name) { - if ($name === '.' || $name === '..') { - continue; - } - $entry = "{$path}/{$name}"; - if ($sftp->is_dir($entry)) { - rmrf($sftp, $entry); - $sftp->rmdir($entry); - echo " 🗑️ Removed dir: {$entry}\n"; - } else { - $sftp->delete($entry); - echo " 🗑️ Removed file: {$entry}\n"; - } - } - } - - // ── Create remote directory tree ──────────────────────────────── - function sftpMakedirs(SFTP $sftp, string $path): void - { - $parts = array_values(array_filter(explode('/', $path), fn(string $p) => $p !== '')); - $current = str_starts_with($path, '/') ? '' : ''; - foreach ($parts as $part) { - $current .= '/' . $part; - $sftp->mkdir($current); // silently returns false if already exists - } - } - - rmrf($sftp, $remotePath); - sftpMakedirs($sftp, $remotePath); - echo "✅ Remote folder ready: {$remotePath}\n"; - PHPEOF - php /tmp/moko_clear.php - - - name: Deploy via SFTP - if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' - env: - SFTP_HOST: ${{ steps.conn.outputs.host }} - SFTP_PORT: ${{ steps.conn.outputs.port }} - SFTP_USER: ${{ vars.DEV_FTP_USERNAME }} - SFTP_KEY: ${{ secrets.DEV_FTP_KEY }} - SFTP_PASSWORD: ${{ secrets.DEV_FTP_PASSWORD }} - AUTH_METHOD: ${{ steps.auth.outputs.method }} - USE_PASSPHRASE: ${{ steps.auth.outputs.use_passphrase }} - REMOTE_PATH: ${{ steps.remote.outputs.path }} - SOURCE_DIR: ${{ steps.source.outputs.dir }} - run: | - # ── Write SSH key to temp file (key auth only) ──────────────────────── - if [ "$AUTH_METHOD" = "key" ]; then - printf '%s' "$SFTP_KEY" > /tmp/deploy_key - chmod 600 /tmp/deploy_key - fi - - # ── Generate sftp-config.json safely via jq ─────────────────────────── - if [ "$AUTH_METHOD" = "key" ]; then - jq -n \ - --arg host "$SFTP_HOST" \ - --argjson port "${SFTP_PORT:-22}" \ - --arg user "$SFTP_USER" \ - --arg path "$REMOTE_PATH" \ - --arg key "/tmp/deploy_key" \ - '{host:$host, port:$port, user:$user, remote_path:$path, ssh_key_file:$key}' \ - > /tmp/sftp-config.json - else - jq -n \ - --arg host "$SFTP_HOST" \ - --argjson port "${SFTP_PORT:-22}" \ - --arg user "$SFTP_USER" \ - --arg path "$REMOTE_PATH" \ - --arg pass "$SFTP_PASSWORD" \ - '{host:$host, port:$port, user:$user, remote_path:$path, password:$pass}' \ - > /tmp/sftp-config.json - fi - - # Dev deploys skip minified files — use unminified sources for debugging - echo "*.min.js" >> "${SOURCE_DIR}/.ftpignore" - echo "*.min.css" >> "${SOURCE_DIR}/.ftpignore" - - # ── Run deploy-sftp.php from MokoStandards ──────────────────────────── - DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json) - if [ "$USE_PASSPHRASE" = "true" ]; then - DEPLOY_ARGS+=(--key-passphrase "$SFTP_PASSWORD") - fi - - # Set platform version to "development" before deploy (Dolibarr + Joomla) - php /tmp/mokostandards-api/cli/version_set_platform.php --path . --version development - - # Write update files — dev/** = development, rc/** = rc - PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true) - REPO="${{ github.repository }}" - BRANCH="${{ github.ref_name }}" - - # Determine stability tag from branch prefix - STABILITY="development" - VERSION_LABEL="development" - if [[ "$BRANCH" == rc/* ]]; then - STABILITY="rc" - VERSION_LABEL=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "${BRANCH#rc/}")-rc - fi - - if [ "$PLATFORM" = "crm-module" ]; then - printf '%s' "$VERSION_LABEL" > update.txt - fi - - if [ "$PLATFORM" = "waas-component" ]; then - MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1 || true) - if [ -n "$MANIFEST" ]; then - EXT_NAME=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}") - EXT_TYPE=$(grep -oP ']+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component") - EXT_ELEMENT=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml) - EXT_CLIENT=$(grep -oP ']+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "") - EXT_FOLDER=$(grep -oP ']+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "") - TARGET_PLATFORM=$(grep -oP '/dev/null | head -1 || true) - [ -n "$TARGET_PLATFORM" ] && TARGET_PLATFORM="${TARGET_PLATFORM}>" - [ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '' "/") - - CLIENT_TAG="" - if [ -n "$EXT_CLIENT" ]; then - CLIENT_TAG="${EXT_CLIENT}" - elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then - CLIENT_TAG="site" - fi - - FOLDER_TAG="" - if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then - FOLDER_TAG="${EXT_FOLDER}" - fi - - DOWNLOAD_URL="https://github.com/${REPO}/archive/refs/heads/${BRANCH}.zip" - - { - printf '%s\n' '' - printf '%s\n' '' - printf '%s\n' ' ' - printf '%s\n' " ${EXT_NAME}" - printf '%s\n' " ${EXT_NAME} ${STABILITY} build" - printf '%s\n' " ${EXT_ELEMENT}" - printf '%s\n' " ${EXT_TYPE}" - printf '%s\n' " ${VERSION_LABEL}" - [ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}" - [ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}" - printf '%s\n' ' ' - printf '%s\n' " ${STABILITY}" - printf '%s\n' ' ' - printf '%s\n' " https://github.com/${REPO}/tree/${BRANCH}" - printf '%s\n' ' ' - printf '%s\n' " ${DOWNLOAD_URL}" - printf '%s\n' ' ' - printf '%s\n' " ${TARGET_PLATFORM}" - printf '%s\n' ' Moko Consulting' - printf '%s\n' ' https://mokoconsulting.tech' - printf '%s\n' ' ' - printf '%s\n' '' - } > updates.xml - sed -i '/^[[:space:]]*$/d' updates.xml - fi - fi - - # Use Joomla-aware deploy for waas-component (routes files to correct Joomla dirs) - # Use standard SFTP deploy for everything else - 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 - # (both scripts handle dotfile skipping and .ftpignore natively) - # Remove temp files that should never be left behind - rm -f /tmp/deploy_key /tmp/sftp-config.json - - # Dev deploys fail silently — no issue creation. - # Demo and RS deploys create failure issues (production-facing). - - - name: Deployment summary - if: always() - run: | - if [ "${{ steps.source.outputs.skip }}" == "true" ]; then - echo "### ⏭️ Deployment Skipped" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "No \`src/\` directory found in this repository." >> "$GITHUB_STEP_SUMMARY" - elif [ "${{ job.status }}" == "success" ]; then - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "### ✅ Dev Deployment Successful" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "| Field | Value |" >> "$GITHUB_STEP_SUMMARY" - echo "|-------|-------|" >> "$GITHUB_STEP_SUMMARY" - echo "| Host | \`${{ steps.conn.outputs.host }}:${{ steps.conn.outputs.port }}\` |" >> "$GITHUB_STEP_SUMMARY" - echo "| Remote path | \`${{ steps.remote.outputs.path }}\` |" >> "$GITHUB_STEP_SUMMARY" - echo "| Source | \`src/\` |" >> "$GITHUB_STEP_SUMMARY" - echo "| Trigger | ${{ github.event_name }} |" >> "$GITHUB_STEP_SUMMARY" - echo "| Auth | ${{ steps.auth.outputs.method }} |" >> "$GITHUB_STEP_SUMMARY" - echo "| Clear remote | ${{ inputs.clear_remote || 'false' }} |" >> "$GITHUB_STEP_SUMMARY" - else - echo "### ❌ Dev Deployment Failed" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Check the job log above for error details." >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/templates/workflows/shared/deploy-rs.yml.template b/templates/workflows/shared/deploy-rs.yml.template deleted file mode 100644 index 2bcbb74..0000000 --- a/templates/workflows/shared/deploy-rs.yml.template +++ /dev/null @@ -1,663 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# 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 . -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Deploy -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/shared/deploy-rs.yml.template -# VERSION: 04.06.00 -# BRIEF: SFTP deployment workflow for release staging server — synced to all governed repos -# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-rs.yml in all governed repos. -# Port is resolved in order: RS_FTP_PORT variable → :port suffix in RS_FTP_HOST → 22. - -name: Deploy to RS Server (SFTP) - -# Deploys the contents of the src/ directory to the release staging server via SFTP. -# Triggers on push/merge to main — deploys the production-ready build to the release staging server. -# -# Required org-level variables: RS_FTP_HOST, RS_FTP_PATH, RS_FTP_USERNAME -# Optional org-level variable: RS_FTP_PORT (auto-detected from host or defaults to 22) -# Optional org/repo variable: RS_FTP_SUFFIX — when set, appended to RS_FTP_PATH to form the -# full remote destination: RS_FTP_PATH/RS_FTP_SUFFIX -# Ignore rules: Place a .ftpignore file in the src/ directory. Each non-empty, -# non-comment line is a glob pattern tested against the relative path -# of each file (e.g. "subdir/file.txt"). The .gitignore is NOT used. -# Required org-level secret: RS_FTP_KEY (preferred) or RS_FTP_PASSWORD -# -# Access control: only users with admin or maintain role on the repository may deploy. - -on: - push: - branches: - - main - - master - paths: - - 'src/**' - - 'htdocs/**' - pull_request: - types: [opened, synchronize, reopened, closed] - branches: - - main - - master - paths: - - 'src/**' - - 'htdocs/**' - workflow_dispatch: - inputs: - clear_remote: - description: 'Delete all files inside the remote destination folder before uploading' - required: false - default: false - type: boolean - -permissions: - contents: read - pull-requests: write - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - check-permission: - name: Verify Deployment Permission - runs-on: ubuntu-latest - steps: - - name: Check actor permission - env: - # Prefer the org-scoped GH_TOKEN secret (needed for the org membership - # fallback). Falls back to the built-in github.token so the collaborator - # endpoint still works even if GH_TOKEN is not configured. - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - ACTOR="${{ github.actor }}" - REPO="${{ github.repository }}" - ORG="${{ github.repository_owner }}" - - METHOD="" - AUTHORIZED="false" - - # Hardcoded authorized users — always allowed to deploy - AUTHORIZED_USERS="jmiller-moko github-actions[bot]" - for user in $AUTHORIZED_USERS; do - if [ "$ACTOR" = "$user" ]; then - AUTHORIZED="true" - METHOD="hardcoded allowlist" - PERMISSION="admin" - break - fi - done - - # For other actors, check repo/org permissions via API - if [ "$AUTHORIZED" != "true" ]; then - PERMISSION=$(gh api "repos/${REPO}/collaborators/${ACTOR}/permission" \ - --jq '.permission' 2>/dev/null) - METHOD="repo collaborator API" - - if [ -z "$PERMISSION" ]; then - ORG_ROLE=$(gh api "orgs/${ORG}/memberships/${ACTOR}" \ - --jq '.role' 2>/dev/null) - METHOD="org membership API" - if [ "$ORG_ROLE" = "owner" ]; then - PERMISSION="admin" - else - PERMISSION="none" - fi - fi - - case "$PERMISSION" in - admin|maintain) AUTHORIZED="true" ;; - esac - fi - - # Write detailed summary - { - echo "## 🔐 Deploy Authorization" - echo "" - echo "| Field | Value |" - echo "|-------|-------|" - echo "| **Actor** | \`${ACTOR}\` |" - echo "| **Repository** | \`${REPO}\` |" - echo "| **Permission** | \`${PERMISSION}\` |" - echo "| **Method** | ${METHOD} |" - echo "| **Authorized** | ${AUTHORIZED} |" - echo "| **Trigger** | \`${{ github.event_name }}\` |" - echo "| **Branch** | \`${{ github.ref_name }}\` |" - echo "" - } >> "$GITHUB_STEP_SUMMARY" - - if [ "$AUTHORIZED" = "true" ]; then - echo "✅ ${ACTOR} authorized to deploy (${METHOD})" >> "$GITHUB_STEP_SUMMARY" - else - echo "❌ ${ACTOR} is NOT authorized to deploy." >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Deployment requires one of:" >> "$GITHUB_STEP_SUMMARY" - echo "- Being in the hardcoded allowlist" >> "$GITHUB_STEP_SUMMARY" - echo "- Having \`admin\` or \`maintain\` role on the repository" >> "$GITHUB_STEP_SUMMARY" - exit 1 - fi - - deploy: - name: SFTP Deploy → RS - runs-on: ubuntu-latest - needs: [check-permission] - if: >- - !startsWith(github.head_ref || github.ref_name, 'chore/') && - (github.event_name == 'workflow_dispatch' || - github.event_name == 'push' || - (github.event_name == 'pull_request' && - (github.event.action == 'opened' || - github.event.action == 'synchronize' || - github.event.action == 'reopened' || - github.event.pull_request.merged == true))) - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Resolve source directory - id: source - run: | - # Resolve source directory: src/ preferred, htdocs/ as fallback - if [ -d "src" ]; then - SRC="src" - elif [ -d "htdocs" ]; then - SRC="htdocs" - else - echo "⚠️ No src/ or htdocs/ directory found — skipping deployment" - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - COUNT=$(find "$SRC" -type f | wc -l) - echo "✅ Source: ${SRC}/ (${COUNT} file(s))" - echo "skip=false" >> "$GITHUB_OUTPUT" - echo "dir=${SRC}" >> "$GITHUB_OUTPUT" - - - name: Preview files to deploy - if: steps.source.outputs.skip == 'false' - env: - SOURCE_DIR: ${{ steps.source.outputs.dir }} - run: | - # ── Convert a ftpignore-style glob line to an ERE pattern ────────────── - ftpignore_to_regex() { - local line="$1" - local anchored=false - # Strip inline comments and whitespace - line=$(printf '%s' "$line" | sed 's/[[:space:]]*#.*$//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - [ -z "$line" ] && return - # Skip negation patterns (not supported) - [[ "$line" == !* ]] && return - # Trailing slash = directory marker; strip it - line="${line%/}" - # Leading slash = anchored to root; strip it - if [[ "$line" == /* ]]; then - anchored=true - line="${line#/}" - fi - # Escape ERE special chars, then restore glob semantics - local regex - regex=$(printf '%s' "$line" \ - | sed 's/[.+^${}()|[\\]/\\&/g' \ - | sed 's/\\\*\\\*/\x01/g' \ - | sed 's/\\\*/[^\/]*/g' \ - | sed 's/\x01/.*/g' \ - | sed 's/\\\?/[^\/]/g') - if $anchored; then - printf '^%s(/|$)' "$regex" - else - printf '(^|/)%s(/|$)' "$regex" - fi - } - - # ── Read .ftpignore (ftpignore-style globs) ───────────────────────── - IGNORE_PATTERNS=() - IGNORE_SOURCES=() - if [ -f "${SOURCE_DIR}/.ftpignore" ]; then - while IFS= read -r line; do - [[ "$line" =~ ^[[:space:]]*$ || "$line" =~ ^[[:space:]]*# ]] && continue - regex=$(ftpignore_to_regex "$line") - [ -n "$regex" ] && IGNORE_PATTERNS+=("$regex") && IGNORE_SOURCES+=("$line") - done < "${SOURCE_DIR}/.ftpignore" - fi - - # ── Walk src/ and classify every file ──────────────────────────────── - WILL_UPLOAD=() - IGNORED_FILES=() - while IFS= read -r -d '' file; do - rel="${file#${SOURCE_DIR}/}" - SKIP=false - for i in "${!IGNORE_PATTERNS[@]}"; do - if echo "$rel" | grep -qE "${IGNORE_PATTERNS[$i]}" 2>/dev/null; then - IGNORED_FILES+=("$rel | .ftpignore \`${IGNORE_SOURCES[$i]}\`") - SKIP=true; break - fi - done - $SKIP && continue - WILL_UPLOAD+=("$rel") - done < <(find "$SOURCE_DIR" -type f -print0 | sort -z) - - UPLOAD_COUNT="${#WILL_UPLOAD[@]}" - IGNORE_COUNT="${#IGNORED_FILES[@]}" - - echo "ℹ️ ${UPLOAD_COUNT} file(s) will be uploaded, ${IGNORE_COUNT} ignored" - - # ── Write deployment preview to step summary ────────────────────────── - { - echo "## 📋 Deployment Preview" - echo "" - echo "| Field | Value |" - echo "|---|---|" - echo "| Source | \`${SOURCE_DIR}/\` |" - echo "| Files to upload | **${UPLOAD_COUNT}** |" - echo "| Files ignored | **${IGNORE_COUNT}** |" - echo "" - if [ "${UPLOAD_COUNT}" -gt 0 ]; then - echo "### 📂 Files that will be uploaded" - echo '```' - printf '%s\n' "${WILL_UPLOAD[@]}" - echo '```' - echo "" - fi - if [ "${IGNORE_COUNT}" -gt 0 ]; then - echo "### ⏭️ Files excluded" - echo "| File | Reason |" - echo "|---|---|" - for entry in "${IGNORED_FILES[@]}"; do - f="${entry% | *}"; r="${entry##* | }" - echo "| \`${f}\` | ${r} |" - done - echo "" - fi - } >> "$GITHUB_STEP_SUMMARY" - - - name: Resolve SFTP host and port - if: steps.source.outputs.skip == 'false' - id: conn - env: - HOST_RAW: ${{ vars.RS_FTP_HOST }} - PORT_VAR: ${{ vars.RS_FTP_PORT }} - run: | - HOST="$HOST_RAW" - PORT="$PORT_VAR" - - if [ -z "$HOST" ]; then - echo "⏭️ RS_FTP_HOST not configured — skipping RS deployment." - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Priority 1 — explicit RS_FTP_PORT variable - if [ -n "$PORT" ]; then - echo "ℹ️ Using explicit RS_FTP_PORT=${PORT}" - - # Priority 2 — port embedded in RS_FTP_HOST (host:port) - elif [[ "$HOST" == *:* ]]; then - PORT="${HOST##*:}" - HOST="${HOST%:*}" - echo "ℹ️ Extracted port ${PORT} from RS_FTP_HOST" - - # Priority 3 — SFTP default - else - PORT="22" - echo "ℹ️ No port specified — defaulting to SFTP port 22" - fi - - echo "host=${HOST}" >> "$GITHUB_OUTPUT" - echo "port=${PORT}" >> "$GITHUB_OUTPUT" - echo "SFTP target: ${HOST}:${PORT}" - - - name: Build remote path - if: steps.source.outputs.skip == 'false' && steps.conn.outputs.skip != 'true' - id: remote - env: - RS_FTP_PATH: ${{ vars.RS_FTP_PATH }} - RS_FTP_SUFFIX: ${{ vars.RS_FTP_SUFFIX }} - run: | - BASE="$RS_FTP_PATH" - - if [ -z "$BASE" ]; then - echo "⏭️ RS_FTP_PATH not configured — skipping RS deployment." - echo "skip=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # RS_FTP_SUFFIX is required — it identifies the remote subdirectory for this repo. - # Without it we cannot safely determine the deployment target. - if [ -z "$RS_FTP_SUFFIX" ]; then - echo "⏭️ RS_FTP_SUFFIX variable is not set — skipping deployment." - echo " Set RS_FTP_SUFFIX as a repo or org variable to enable deploy-rs." - echo "skip=true" >> "$GITHUB_OUTPUT" - echo "path=" >> "$GITHUB_OUTPUT" - exit 0 - fi - - REMOTE="${BASE%/}/${RS_FTP_SUFFIX#/}" - - # ── Platform-specific path safety guards ────────────────────────────── - PLATFORM="" - MOKO_FILE=".github/.mokostandards"; [ ! -f "$MOKO_FILE" ] && MOKO_FILE=".mokostandards"; if [ -f "$MOKO_FILE" ]; then - PLATFORM=$(grep -E '^platform:' "$MOKO_FILE" | sed 's/.*:[[:space:]]*//' | tr -d '"') - fi - - # RS deployment: no path restrictions for any platform - - echo "ℹ️ Remote path: ${REMOTE}" - echo "path=${REMOTE}" >> "$GITHUB_OUTPUT" - - - name: Detect SFTP authentication method - if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' - id: auth - env: - HAS_KEY: ${{ secrets.RS_FTP_KEY }} - HAS_PASSWORD: ${{ secrets.RS_FTP_PASSWORD }} - run: | - if [ -n "$HAS_KEY" ] && [ -n "$HAS_PASSWORD" ]; then - # Both set: key auth with password as passphrase; falls back to password-only if key fails - echo "method=key" >> "$GITHUB_OUTPUT" - echo "use_passphrase=true" >> "$GITHUB_OUTPUT" - echo "has_password=true" >> "$GITHUB_OUTPUT" - echo "ℹ️ Primary: SSH key + passphrase (RS_FTP_KEY / RS_FTP_PASSWORD)" - echo "ℹ️ Fallback: password-only auth if key authentication fails" - elif [ -n "$HAS_KEY" ]; then - # Key only: no passphrase, no password fallback - echo "method=key" >> "$GITHUB_OUTPUT" - echo "use_passphrase=false" >> "$GITHUB_OUTPUT" - echo "has_password=false" >> "$GITHUB_OUTPUT" - echo "ℹ️ Using SSH key authentication (RS_FTP_KEY, no passphrase, no fallback)" - elif [ -n "$HAS_PASSWORD" ]; then - # Password only: direct SFTP password auth - echo "method=password" >> "$GITHUB_OUTPUT" - echo "use_passphrase=false" >> "$GITHUB_OUTPUT" - echo "has_password=true" >> "$GITHUB_OUTPUT" - echo "ℹ️ Using password authentication (RS_FTP_PASSWORD)" - else - echo "❌ No SFTP credentials configured." - echo " Set RS_FTP_KEY (preferred) or RS_FTP_PASSWORD as an org-level secret." - exit 1 - fi - - - name: Setup PHP - if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' - uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0 - with: - php-version: '8.1' - tools: composer - - - name: Setup MokoStandards deploy tools - if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - git clone --depth 1 --branch {{standards_branch}} --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ - /tmp/mokostandards-api - cd /tmp/mokostandards-api - composer install --no-dev --no-interaction --quiet - - - name: Clear remote destination folder (manual only) - if: >- - steps.source.outputs.skip == 'false' && - steps.remote.outputs.skip != 'true' && - inputs.clear_remote == true - env: - SFTP_HOST: ${{ steps.conn.outputs.host }} - SFTP_PORT: ${{ steps.conn.outputs.port }} - SFTP_USER: ${{ vars.RS_FTP_USERNAME }} - SFTP_KEY: ${{ secrets.RS_FTP_KEY }} - SFTP_PASSWORD: ${{ secrets.RS_FTP_PASSWORD }} - AUTH_METHOD: ${{ steps.auth.outputs.method }} - USE_PASSPHRASE: ${{ steps.auth.outputs.use_passphrase }} - HAS_PASSWORD: ${{ steps.auth.outputs.has_password }} - REMOTE_PATH: ${{ steps.remote.outputs.path }} - run: | - cat > /tmp/moko_clear.php << 'PHPEOF' - login($username, $key)) { - if ($password !== '') { - echo "⚠️ Key auth failed — falling back to password\n"; - if (!$sftp->login($username, $password)) { - fwrite(STDERR, "❌ Both key and password authentication failed\n"); - exit(1); - } - echo "✅ Connected via password authentication (key fallback)\n"; - } else { - fwrite(STDERR, "❌ Key authentication failed and no password fallback is available\n"); - exit(1); - } - } else { - echo "✅ Connected via SSH key authentication\n"; - } - } else { - if (!$sftp->login($username, (string) getenv('SFTP_PASSWORD'))) { - fwrite(STDERR, "❌ Password authentication failed\n"); - exit(1); - } - echo "✅ Connected via password authentication\n"; - } - - // ── Recursive delete ──────────────────────────────────────────── - function rmrf(SFTP $sftp, string $path): void - { - $entries = $sftp->nlist($path); - if ($entries === false) { - return; // path does not exist — nothing to clear - } - foreach ($entries as $name) { - if ($name === '.' || $name === '..') { - continue; - } - $entry = "{$path}/{$name}"; - if ($sftp->is_dir($entry)) { - rmrf($sftp, $entry); - $sftp->rmdir($entry); - echo " 🗑️ Removed dir: {$entry}\n"; - } else { - $sftp->delete($entry); - echo " 🗑️ Removed file: {$entry}\n"; - } - } - } - - // ── Create remote directory tree ──────────────────────────────── - function sftpMakedirs(SFTP $sftp, string $path): void - { - $parts = array_values(array_filter(explode('/', $path), fn(string $p) => $p !== '')); - $current = str_starts_with($path, '/') ? '' : ''; - foreach ($parts as $part) { - $current .= '/' . $part; - $sftp->mkdir($current); // silently returns false if already exists - } - } - - rmrf($sftp, $remotePath); - sftpMakedirs($sftp, $remotePath); - echo "✅ Remote folder ready: {$remotePath}\n"; - PHPEOF - php /tmp/moko_clear.php - - - name: Deploy via SFTP - if: steps.source.outputs.skip == 'false' && steps.remote.outputs.skip != 'true' - env: - SFTP_HOST: ${{ steps.conn.outputs.host }} - SFTP_PORT: ${{ steps.conn.outputs.port }} - SFTP_USER: ${{ vars.RS_FTP_USERNAME }} - SFTP_KEY: ${{ secrets.RS_FTP_KEY }} - SFTP_PASSWORD: ${{ secrets.RS_FTP_PASSWORD }} - AUTH_METHOD: ${{ steps.auth.outputs.method }} - USE_PASSPHRASE: ${{ steps.auth.outputs.use_passphrase }} - REMOTE_PATH: ${{ steps.remote.outputs.path }} - SOURCE_DIR: ${{ steps.source.outputs.dir }} - run: | - # ── Write SSH key to temp file (key auth only) ──────────────────────── - if [ "$AUTH_METHOD" = "key" ]; then - printf '%s' "$SFTP_KEY" > /tmp/deploy_key - chmod 600 /tmp/deploy_key - fi - - # ── Generate sftp-config.json safely via jq ─────────────────────────── - if [ "$AUTH_METHOD" = "key" ]; then - jq -n \ - --arg host "$SFTP_HOST" \ - --argjson port "${SFTP_PORT:-22}" \ - --arg user "$SFTP_USER" \ - --arg path "$REMOTE_PATH" \ - --arg key "/tmp/deploy_key" \ - '{host:$host, port:$port, user:$user, remote_path:$path, ssh_key_file:$key}' \ - > /tmp/sftp-config.json - else - jq -n \ - --arg host "$SFTP_HOST" \ - --argjson port "${SFTP_PORT:-22}" \ - --arg user "$SFTP_USER" \ - --arg path "$REMOTE_PATH" \ - --arg pass "$SFTP_PASSWORD" \ - '{host:$host, port:$port, user:$user, remote_path:$path, password:$pass}' \ - > /tmp/sftp-config.json - fi - - # ── Run deploy-sftp.php from MokoStandards ──────────────────────────── - DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json) - if [ "$USE_PASSPHRASE" = "true" ]; then - DEPLOY_ARGS+=(--key-passphrase "$SFTP_PASSWORD") - fi - - 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 - # Remove temp files that should never be left behind - rm -f /tmp/deploy_key /tmp/sftp-config.json - - - name: Create or update failure issue - if: failure() && steps.remote.outputs.skip != 'true' && steps.conn.outputs.skip != 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - REPO="${{ github.repository }}" - RUN_URL="${{ github.server_url }}/${REPO}/actions/runs/${{ github.run_id }}" - ACTOR="${{ github.actor }}" - BRANCH="${{ github.ref_name }}" - EVENT="${{ github.event_name }}" - NOW=$(date -u '+%Y-%m-%d %H:%M:%S UTC') - LABEL="deploy-failure" - - TITLE="fix: RS deployment failed — ${REPO}" - BODY="## RS Deployment Failed - - A deployment to the RS server failed and requires attention. - - | Field | Value | - |-------|-------| - | **Repository** | \`${REPO}\` | - | **Branch** | \`${BRANCH}\` | - | **Trigger** | ${EVENT} | - | **Actor** | @${ACTOR} | - | **Failed at** | ${NOW} | - | **Run** | [View workflow run](${RUN_URL}) | - - ### Next steps - 1. Review the [workflow run log](${RUN_URL}) for the specific error. - 2. Fix the underlying issue (credentials, SFTP connectivity, permissions). - 3. Re-trigger the deployment via **Actions → Deploy to RS Server → Run workflow**. - - --- - *Auto-created by deploy-rs.yml — close this issue once the deployment is resolved.*" - - # Ensure the label exists (idempotent — no-op if already present) - gh label create "$LABEL" \ - --repo "$REPO" \ - --color "CC0000" \ - --description "Automated deploy failure tracking" \ - --force 2>/dev/null || true - - # Look for an existing deploy-failure issue (any state — reopen if closed) - EXISTING=$(gh api "repos/${REPO}/issues?labels=${LABEL}&state=all&per_page=1&sort=created&direction=desc" \ - --jq '.[0].number' 2>/dev/null) - - if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then - gh api "repos/${REPO}/issues/${EXISTING}" \ - -X PATCH \ - -f title="$TITLE" \ - -f body="$BODY" \ - -f state="open" \ - --silent - echo "📋 Failure issue #${EXISTING} updated/reopened: ${REPO}" >> "$GITHUB_STEP_SUMMARY" - else - gh issue create \ - --repo "$REPO" \ - --title "$TITLE" \ - --body "$BODY" \ - --label "$LABEL" \ - --assignee "jmiller-moko" \ - | tee -a "$GITHUB_STEP_SUMMARY" - fi - - - name: Deployment summary - if: always() - run: | - if [ "${{ steps.source.outputs.skip }}" == "true" ]; then - echo "### ⏭️ Deployment Skipped" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "No \`src/\` directory found in this repository." >> "$GITHUB_STEP_SUMMARY" - elif [ "${{ job.status }}" == "success" ]; then - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "### ✅ RS Deployment Successful" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "| Field | Value |" >> "$GITHUB_STEP_SUMMARY" - echo "|-------|-------|" >> "$GITHUB_STEP_SUMMARY" - echo "| Host | \`${{ steps.conn.outputs.host }}:${{ steps.conn.outputs.port }}\` |" >> "$GITHUB_STEP_SUMMARY" - echo "| Remote path | \`${{ steps.remote.outputs.path }}\` |" >> "$GITHUB_STEP_SUMMARY" - echo "| Source | \`src/\` |" >> "$GITHUB_STEP_SUMMARY" - echo "| Trigger | ${{ github.event_name }} |" >> "$GITHUB_STEP_SUMMARY" - echo "| Auth | ${{ steps.auth.outputs.method }} |" >> "$GITHUB_STEP_SUMMARY" - echo "| Clear remote | ${{ inputs.clear_remote || 'false' }} |" >> "$GITHUB_STEP_SUMMARY" - else - echo "### ❌ RS Deployment Failed" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "Check the job log above for error details." >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/templates/workflows/shared/enterprise-firewall-setup.yml.template b/templates/workflows/shared/enterprise-firewall-setup.yml.template deleted file mode 100644 index 8fe1932..0000000 --- a/templates/workflows/shared/enterprise-firewall-setup.yml.template +++ /dev/null @@ -1,758 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# 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 . - -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Firewall -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml.template -# VERSION: 04.06.00 -# BRIEF: Enterprise firewall configuration — generates outbound allow-rules including SFTP deployment server -# NOTE: Reads DEV_FTP_HOST / DEV_FTP_PORT variables to include SFTP egress rules alongside HTTPS rules. - -name: Enterprise Firewall Configuration - -# This workflow provides firewall configuration guidance for enterprise-ready sites -# It generates firewall rules for allowing outbound access to trusted domains -# including license providers, documentation sources, package registries, -# and the SFTP deployment server (DEV_FTP_HOST / DEV_FTP_PORT). -# -# Runs automatically when: -# - Coding agent workflows are triggered (pull requests with copilot/ prefix) -# - Manual workflow dispatch for custom configurations - -on: - workflow_dispatch: - inputs: - firewall_type: - description: 'Target firewall type' - required: true - type: choice - options: - - 'iptables' - - 'ufw' - - 'firewalld' - - 'aws-security-group' - - 'azure-nsg' - - 'gcp-firewall' - - 'cloudflare' - - 'all' - default: 'all' - output_format: - description: 'Output format' - required: true - type: choice - options: - - 'shell-script' - - 'json' - - 'yaml' - - 'markdown' - - 'all' - default: 'markdown' - - # Auto-run when coding agent creates or updates PRs - pull_request: - branches: - - 'copilot/**' - - 'agent/**' - types: [opened, synchronize, reopened] - - # Auto-run on push to coding agent branches - push: - branches: - - 'copilot/**' - - 'agent/**' - -permissions: - contents: read - actions: read - -jobs: - generate-firewall-rules: - name: Generate Firewall Rules - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.11' - - - name: Apply Firewall Rules to Runner (Auto-run only) - if: github.event_name != 'workflow_dispatch' - env: - DEV_FTP_HOST: ${{ vars.DEV_FTP_HOST }} - DEV_FTP_PORT: ${{ vars.DEV_FTP_PORT }} - run: | - echo "🔥 Applying firewall rules for coding agent environment..." - echo "" - echo "This step ensures the GitHub Actions runner can access trusted domains" - echo "including license providers, package registries, and documentation sources." - echo "" - - # Note: GitHub Actions runners are ephemeral and run in controlled environments - # This step documents what domains are being accessed during the workflow - # Actual firewall configuration is managed by GitHub - - cat > /tmp/trusted-domains.txt << 'EOF' - # Trusted domains for coding agent environment - # License Providers - www.gnu.org - opensource.org - choosealicense.com - spdx.org - creativecommons.org - apache.org - fsf.org - - # Documentation & Standards - semver.org - keepachangelog.com - conventionalcommits.org - - # GitHub & Related - github.com - api.github.com - docs.github.com - raw.githubusercontent.com - ghcr.io - - # Package Registries - npmjs.com - registry.npmjs.org - pypi.org - files.pythonhosted.org - packagist.org - repo.packagist.org - rubygems.org - - # Platform-Specific - joomla.org - downloads.joomla.org - docs.joomla.org - php.net - getcomposer.org - dolibarr.org - wiki.dolibarr.org - docs.dolibarr.org - - # Moko Consulting - mokoconsulting.tech - - # SFTP Deployment Server (DEV_FTP_HOST) - ${DEV_FTP_HOST:-} - - # Google Services - drive.google.com - docs.google.com - sheets.google.com - accounts.google.com - storage.googleapis.com - fonts.googleapis.com - fonts.gstatic.com - - # GitHub Extended - upload.github.com - objects.githubusercontent.com - user-images.githubusercontent.com - codeload.github.com - pkg.github.com - - # Developer Reference - developer.mozilla.org - stackoverflow.com - git-scm.com - - # CDN & Infrastructure - cdn.jsdelivr.net - unpkg.com - cdnjs.cloudflare.com - img.shields.io - - # Container Registries - hub.docker.com - registry-1.docker.io - - # CI & Code Quality - codecov.io - sonarcloud.io - - # Terraform & Infrastructure - registry.terraform.io - releases.hashicorp.com - checkpoint-api.hashicorp.com - EOF - - echo "✓ Trusted domains documented for this runner" - echo "✓ GitHub Actions runners have network access to these domains" - echo "" - - # Test connectivity to key domains - echo "Testing connectivity to key domains..." - for domain in "github.com" "www.gnu.org" "npmjs.com" "pypi.org"; do - if curl -s --max-time 3 -o /dev/null -w "%{http_code}" "https://$domain" | grep -q "200\|301\|302"; then - echo " ✓ $domain is accessible" - else - echo " ⚠️ $domain connectivity check failed (may be expected)" - fi - done - - # Test SFTP server connectivity (TCP port check) - SFTP_HOST="${DEV_FTP_HOST:-}" - SFTP_PORT="${DEV_FTP_PORT:-22}" - if [ -n "$SFTP_HOST" ]; then - # Strip any embedded :port suffix - SFTP_HOST="${SFTP_HOST%%:*}" - echo "" - echo "Testing SFTP deployment server connectivity..." - if timeout 5 bash -c "echo >/dev/tcp/${SFTP_HOST}/${SFTP_PORT}" 2>/dev/null; then - echo " ✓ SFTP server ${SFTP_HOST}:${SFTP_PORT} is reachable" - else - echo " ⚠️ SFTP server ${SFTP_HOST}:${SFTP_PORT} is not reachable from runner (firewall rule needed)" - fi - else - echo "" - echo " ℹ️ DEV_FTP_HOST not configured — skipping SFTP connectivity check" - fi - - - name: Generate Firewall Configuration - id: generate - env: - DEV_FTP_HOST: ${{ vars.DEV_FTP_HOST }} - DEV_FTP_PORT: ${{ vars.DEV_FTP_PORT }} - run: | - cat > generate_firewall_config.py << 'PYTHON_EOF' - #!/usr/bin/env python3 - """ - Enterprise Firewall Configuration Generator - - Generates firewall rules for enterprise-ready deployments allowing - access to trusted domains including license providers, documentation - sources, package registries, and platform-specific sites. - """ - - import json - import os - import yaml - import sys - from typing import List, Dict - - # SFTP deployment server from org variables - _sftp_host_raw = os.environ.get("DEV_FTP_HOST", "").strip() - _sftp_port = os.environ.get("DEV_FTP_PORT", "").strip() or "22" - # Strip embedded :port suffix if present - _sftp_host = _sftp_host_raw.split(":")[0] if _sftp_host_raw else "" - if ":" in _sftp_host_raw and not _sftp_port: - _sftp_port = _sftp_host_raw.split(":")[1] - - SFTP_HOST = _sftp_host - SFTP_PORT = int(_sftp_port) if _sftp_port.isdigit() else 22 - - # Trusted domains from .github/copilot.yml - TRUSTED_DOMAINS = { - "license_providers": [ - "www.gnu.org", - "opensource.org", - "choosealicense.com", - "spdx.org", - "creativecommons.org", - "apache.org", - "fsf.org", - ], - "documentation_standards": [ - "semver.org", - "keepachangelog.com", - "conventionalcommits.org", - ], - "github_related": [ - "github.com", - "api.github.com", - "docs.github.com", - "raw.githubusercontent.com", - "ghcr.io", - ], - "package_registries": [ - "npmjs.com", - "registry.npmjs.org", - "pypi.org", - "files.pythonhosted.org", - "packagist.org", - "repo.packagist.org", - "rubygems.org", - ], - "standards_organizations": [ - "json-schema.org", - "w3.org", - "ietf.org", - ], - "platform_specific": [ - "joomla.org", - "downloads.joomla.org", - "docs.joomla.org", - "php.net", - "getcomposer.org", - "dolibarr.org", - "wiki.dolibarr.org", - "docs.dolibarr.org", - ], - "moko_consulting": [ - "mokoconsulting.tech", - ], - "google_services": [ - "drive.google.com", - "docs.google.com", - "sheets.google.com", - "accounts.google.com", - "storage.googleapis.com", - "fonts.googleapis.com", - "fonts.gstatic.com", - ], - "github_extended": [ - "upload.github.com", - "objects.githubusercontent.com", - "user-images.githubusercontent.com", - "codeload.github.com", - "pkg.github.com", - ], - "developer_reference": [ - "developer.mozilla.org", - "stackoverflow.com", - "git-scm.com", - ], - "cdn_and_infrastructure": [ - "cdn.jsdelivr.net", - "unpkg.com", - "cdnjs.cloudflare.com", - "img.shields.io", - ], - "container_registries": [ - "hub.docker.com", - "registry-1.docker.io", - ], - "ci_code_quality": [ - "codecov.io", - "sonarcloud.io", - ], - "terraform_infrastructure": [ - "registry.terraform.io", - "releases.hashicorp.com", - "checkpoint-api.hashicorp.com", - ], - } - - # Inject SFTP deployment server as a separate category (port 22, not 443) - if SFTP_HOST: - TRUSTED_DOMAINS["sftp_deployment_server"] = [SFTP_HOST] - print(f"ℹ️ SFTP deployment server: {SFTP_HOST}:{SFTP_PORT}") - - def generate_sftp_iptables_rules(host: str, port: int) -> str: - """Generate iptables rules specifically for SFTP egress""" - return ( - f"# Allow SFTP to deployment server {host}:{port}\n" - f"iptables -A OUTPUT -p tcp -d $(dig +short {host} | head -1)" - f" --dport {port} -j ACCEPT # SFTP deploy\n" - ) - - def generate_sftp_ufw_rules(host: str, port: int) -> str: - """Generate UFW rules for SFTP egress""" - return ( - f"# Allow SFTP to deployment server\n" - f"ufw allow out to $(dig +short {host} | head -1)" - f" port {port} proto tcp comment 'SFTP deploy to {host}'\n" - ) - - def generate_sftp_firewalld_rules(host: str, port: int) -> str: - """Generate firewalld rules for SFTP egress""" - return ( - f"# Allow SFTP to deployment server\n" - f"firewall-cmd --permanent --add-rich-rule='" - f"rule family=ipv4 destination address=$(dig +short {host} | head -1)" - f" port port={port} protocol=tcp accept' # SFTP deploy\n" - ) - - def generate_iptables_rules(domains: List[str]) -> str: - """Generate iptables firewall rules""" - rules = ["#!/bin/bash", "", "# Enterprise Firewall Rules - iptables", ""] - rules.append("# Allow outbound HTTPS to trusted domains") - rules.append("") - - for domain in domains: - rules.append(f"# Allow {domain}") - rules.append(f"iptables -A OUTPUT -p tcp -d $(dig +short {domain} | head -1) --dport 443 -j ACCEPT") - - rules.append("") - rules.append("# Allow DNS lookups") - rules.append("iptables -A OUTPUT -p udp --dport 53 -j ACCEPT") - rules.append("iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT") - - return "\n".join(rules) - - def generate_ufw_rules(domains: List[str]) -> str: - """Generate UFW firewall rules""" - rules = ["#!/bin/bash", "", "# Enterprise Firewall Rules - UFW", ""] - rules.append("# Allow outbound HTTPS to trusted domains") - rules.append("") - - for domain in domains: - rules.append(f"# Allow {domain}") - rules.append(f"ufw allow out to $(dig +short {domain} | head -1) port 443 proto tcp comment 'Allow {domain}'") - - rules.append("") - rules.append("# Allow DNS") - rules.append("ufw allow out 53/udp comment 'Allow DNS UDP'") - rules.append("ufw allow out 53/tcp comment 'Allow DNS TCP'") - - return "\n".join(rules) - - def generate_firewalld_rules(domains: List[str]) -> str: - """Generate firewalld rules""" - rules = ["#!/bin/bash", "", "# Enterprise Firewall Rules - firewalld", ""] - rules.append("# Add trusted domains to firewall") - rules.append("") - - for domain in domains: - rules.append(f"# Allow {domain}") - rules.append(f"firewall-cmd --permanent --add-rich-rule='rule family=ipv4 destination address=$(dig +short {domain} | head -1) port port=443 protocol=tcp accept'") - - rules.append("") - rules.append("# Reload firewall") - rules.append("firewall-cmd --reload") - - return "\n".join(rules) - - def generate_aws_security_group(domains: List[str]) -> Dict: - """Generate AWS Security Group rules (JSON format)""" - rules = { - "SecurityGroupRules": { - "Egress": [] - } - } - - for domain in domains: - rules["SecurityGroupRules"]["Egress"].append({ - "Description": f"Allow HTTPS to {domain}", - "IpProtocol": "tcp", - "FromPort": 443, - "ToPort": 443, - "CidrIp": "0.0.0.0/0", # In practice, resolve to specific IPs - "Tags": [{ - "Key": "Domain", - "Value": domain - }] - }) - - # Add DNS - rules["SecurityGroupRules"]["Egress"].append({ - "Description": "Allow DNS", - "IpProtocol": "udp", - "FromPort": 53, - "ToPort": 53, - "CidrIp": "0.0.0.0/0" - }) - - return rules - - def generate_markdown_documentation(domains_by_category: Dict[str, List[str]]) -> str: - """Generate markdown documentation""" - md = ["# Enterprise Firewall Configuration Guide", ""] - md.append("## Overview") - md.append("") - md.append("This document provides firewall configuration guidance for enterprise-ready deployments.") - md.append("It lists trusted domains that should be whitelisted for outbound access to ensure") - md.append("proper functionality of license validation, package management, and documentation access.") - md.append("") - - md.append("## Trusted Domains by Category") - md.append("") - - all_domains = [] - for category, domains in domains_by_category.items(): - category_name = category.replace("_", " ").title() - md.append(f"### {category_name}") - md.append("") - md.append("| Domain | Purpose |") - md.append("|--------|---------|") - - for domain in domains: - all_domains.append(domain) - purpose = get_domain_purpose(domain) - md.append(f"| `{domain}` | {purpose} |") - - md.append("") - - md.append("## Implementation Examples") - md.append("") - - md.append("### iptables Example") - md.append("") - md.append("```bash") - md.append("# Allow HTTPS to trusted domain") - md.append(f"iptables -A OUTPUT -p tcp -d $(dig +short {all_domains[0]}) --dport 443 -j ACCEPT") - md.append("```") - md.append("") - - md.append("### UFW Example") - md.append("") - md.append("```bash") - md.append("# Allow HTTPS to trusted domain") - md.append(f"ufw allow out to {all_domains[0]} port 443 proto tcp") - md.append("```") - md.append("") - - md.append("### AWS Security Group Example") - md.append("") - md.append("```json") - md.append("{") - md.append(' "IpPermissions": [{') - md.append(' "IpProtocol": "tcp",') - md.append(' "FromPort": 443,') - md.append(' "ToPort": 443,') - md.append(' "IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "HTTPS to trusted domains"}]') - md.append(" }]") - md.append("}") - md.append("```") - md.append("") - - md.append("## Ports Required") - md.append("") - md.append("| Port | Protocol | Purpose |") - md.append("|------|----------|---------|") - md.append("| 443 | TCP | HTTPS (secure web access) |") - md.append("| 80 | TCP | HTTP (redirects to HTTPS) |") - md.append("| 53 | UDP/TCP | DNS resolution |") - md.append("") - - md.append("## Security Considerations") - md.append("") - md.append("1. **DNS Resolution**: Ensure DNS queries are allowed (port 53 UDP/TCP)") - md.append("2. **Certificate Validation**: HTTPS requires ability to reach certificate authorities") - md.append("3. **Dynamic IPs**: Some domains use CDNs with dynamic IPs - consider using FQDNs in rules") - md.append("4. **Regular Updates**: Review and update whitelist as services change") - md.append("5. **Logging**: Enable logging for blocked connections to identify missing rules") - md.append("") - - md.append("## Compliance Notes") - md.append("") - md.append("- All listed domains provide read-only access to public information") - md.append("- License providers enable GPL compliance verification") - md.append("- Package registries support dependency security scanning") - md.append("- No authentication credentials are transmitted to these domains") - md.append("") - - return "\n".join(md) - - def get_domain_purpose(domain: str) -> str: - """Get human-readable purpose for a domain""" - purposes = { - "www.gnu.org": "GNU licenses and documentation", - "opensource.org": "Open Source Initiative resources", - "choosealicense.com": "GitHub license selection tool", - "spdx.org": "Software Package Data Exchange identifiers", - "creativecommons.org": "Creative Commons licenses", - "apache.org": "Apache Software Foundation licenses", - "fsf.org": "Free Software Foundation resources", - "semver.org": "Semantic versioning specification", - "keepachangelog.com": "Changelog format standards", - "conventionalcommits.org": "Commit message conventions", - "github.com": "GitHub platform access", - "api.github.com": "GitHub API access", - "docs.github.com": "GitHub documentation", - "raw.githubusercontent.com": "GitHub raw content access", - "npmjs.com": "npm package registry", - "pypi.org": "Python Package Index", - "packagist.org": "PHP Composer package registry", - "rubygems.org": "Ruby gems registry", - "joomla.org": "Joomla CMS platform", - "php.net": "PHP documentation and downloads", - "dolibarr.org": "Dolibarr ERP/CRM platform", - } - return purposes.get(domain, "Trusted resource") - - def main(): - # Use inputs if provided (manual dispatch), otherwise use defaults (auto-run) - firewall_type = "${{ github.event.inputs.firewall_type }}" or "all" - output_format = "${{ github.event.inputs.output_format }}" or "markdown" - - print(f"Running in {'manual' if '${{ github.event.inputs.firewall_type }}' else 'automatic'} mode") - print(f"Firewall type: {firewall_type}") - print(f"Output format: {output_format}") - print("") - - # Collect all domains - all_domains = [] - for domains in TRUSTED_DOMAINS.values(): - all_domains.extend(domains) - - # Remove duplicates and sort - all_domains = sorted(set(all_domains)) - - print(f"Generating firewall rules for {len(all_domains)} trusted domains...") - print("") - - # Exclude SFTP server from HTTPS rule generation (different port) - https_domains = [d for d in all_domains if d != SFTP_HOST] - - # Generate based on firewall type - if firewall_type in ["iptables", "all"]: - rules = generate_iptables_rules(https_domains) - if SFTP_HOST: - rules += "\n# ── SFTP Deployment Server ──────────────────────────────\n" - rules += generate_sftp_iptables_rules(SFTP_HOST, SFTP_PORT) - with open("firewall-rules-iptables.sh", "w") as f: - f.write(rules) - print("✓ Generated iptables rules: firewall-rules-iptables.sh") - - if firewall_type in ["ufw", "all"]: - rules = generate_ufw_rules(https_domains) - if SFTP_HOST: - rules += "\n# ── SFTP Deployment Server ──────────────────────────────\n" - rules += generate_sftp_ufw_rules(SFTP_HOST, SFTP_PORT) - with open("firewall-rules-ufw.sh", "w") as f: - f.write(rules) - print("✓ Generated UFW rules: firewall-rules-ufw.sh") - - if firewall_type in ["firewalld", "all"]: - rules = generate_firewalld_rules(https_domains) - if SFTP_HOST: - rules += "\n# ── SFTP Deployment Server ──────────────────────────────\n" - rules += generate_sftp_firewalld_rules(SFTP_HOST, SFTP_PORT) - with open("firewall-rules-firewalld.sh", "w") as f: - f.write(rules) - print("✓ Generated firewalld rules: firewall-rules-firewalld.sh") - - if firewall_type in ["aws-security-group", "all"]: - rules = generate_aws_security_group(all_domains) - with open("firewall-rules-aws-sg.json", "w") as f: - json.dump(rules, f, indent=2) - print("✓ Generated AWS Security Group rules: firewall-rules-aws-sg.json") - - if output_format in ["yaml", "all"]: - with open("trusted-domains.yml", "w") as f: - yaml.dump(TRUSTED_DOMAINS, f, default_flow_style=False) - print("✓ Generated YAML domain list: trusted-domains.yml") - - if output_format in ["json", "all"]: - with open("trusted-domains.json", "w") as f: - json.dump(TRUSTED_DOMAINS, f, indent=2) - print("✓ Generated JSON domain list: trusted-domains.json") - - if output_format in ["markdown", "all"]: - md = generate_markdown_documentation(TRUSTED_DOMAINS) - with open("FIREWALL_CONFIGURATION.md", "w") as f: - f.write(md) - print("✓ Generated documentation: FIREWALL_CONFIGURATION.md") - - print("") - print("Domain Categories:") - for category, domains in TRUSTED_DOMAINS.items(): - print(f" - {category}: {len(domains)} domains") - - print("") - print("Total unique domains: ", len(all_domains)) - - if __name__ == "__main__": - main() - PYTHON_EOF - - chmod +x generate_firewall_config.py - pip install PyYAML - python3 generate_firewall_config.py - - - name: Upload Firewall Configuration Artifacts - uses: actions/upload-artifact@v6 - with: - name: firewall-configurations - path: | - firewall-rules-*.sh - firewall-rules-*.json - trusted-domains.* - FIREWALL_CONFIGURATION.md - retention-days: 90 - - - name: Display Summary - run: | - echo "## Firewall Configuration" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "**Mode**: Manual Execution" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Firewall rules have been generated for enterprise-ready deployments." >> $GITHUB_STEP_SUMMARY - else - echo "**Mode**: Automatic Execution (Coding Agent Active)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "This workflow ran automatically because a coding agent (GitHub Copilot) is active." >> $GITHUB_STEP_SUMMARY - echo "Firewall configuration has been validated for the coding agent environment." >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Files Generated" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - if ls firewall-rules-* trusted-domains.* FIREWALL_CONFIGURATION.md 2>/dev/null; then - ls -lh firewall-rules-* trusted-domains.* FIREWALL_CONFIGURATION.md 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' >> $GITHUB_STEP_SUMMARY - else - echo "- Documentation generated" >> $GITHUB_STEP_SUMMARY - fi - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "### Download Artifacts" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Download the generated firewall configurations from the workflow artifacts." >> $GITHUB_STEP_SUMMARY - else - echo "### Trusted Domains Active" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "The coding agent has access to:" >> $GITHUB_STEP_SUMMARY - echo "- License providers (GPL, OSI, SPDX, Apache, etc.)" >> $GITHUB_STEP_SUMMARY - echo "- Package registries (npm, PyPI, Packagist, RubyGems)" >> $GITHUB_STEP_SUMMARY - echo "- Documentation sources (GitHub, Joomla, Dolibarr, PHP)" >> $GITHUB_STEP_SUMMARY - echo "- Standards organizations (W3C, IETF, JSON Schema)" >> $GITHUB_STEP_SUMMARY - fi - -# Usage Instructions: -# -# This workflow runs in two modes: -# -# 1. AUTOMATIC MODE (Coding Agent): -# - Triggers when coding agent branches (copilot/**, agent/**) are pushed or PR'd -# - Validates firewall configuration for the coding agent environment -# - Documents accessible domains for compliance -# - Ensures license sources and package registries are available -# -# 2. MANUAL MODE (Enterprise Configuration): -# - Manually trigger from the Actions tab -# - Select desired firewall type and output format -# - Download generated artifacts -# - Apply firewall rules to your enterprise environment -# -# Configuration: -# - Trusted domains are sourced from .github/copilot.yml -# - Modify copilot.yml to add/remove trusted domains -# - Changes automatically propagate to firewall rules -# -# Important Notes: -# - Review generated rules before applying to production -# - Some domains may use CDNs with dynamic IPs -# - Consider using FQDN-based rules where supported -# - Test thoroughly in staging environment first -# - Monitor logs for blocked connections -# - Update rules as domains/services change diff --git a/templates/workflows/shared/repository-cleanup.yml.template b/templates/workflows/shared/repository-cleanup.yml.template deleted file mode 100644 index 5241cd5..0000000 --- a/templates/workflows/shared/repository-cleanup.yml.template +++ /dev/null @@ -1,525 +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: Gitea.Workflow -# INGROUP: MokoStandards.Maintenance -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/shared/repository-cleanup.yml.template -# VERSION: 04.06.00 -# BRIEF: Recurring repository maintenance — labels, branches, workflows, logs, doc indexes -# NOTE: Synced via bulk-repo-sync to .github/workflows/repository-cleanup.yml in all governed repos. -# Runs on the 1st and 15th of each month at 6:00 AM UTC, and on manual dispatch. - -name: Repository Cleanup - -on: - schedule: - - cron: '0 6 1,15 * *' - workflow_dispatch: - inputs: - reset_labels: - description: 'Delete ALL existing labels and recreate the standard set' - type: boolean - default: false - clean_branches: - description: 'Delete old chore/sync-mokostandards-* branches' - type: boolean - default: true - clean_workflows: - description: 'Delete orphaned workflow runs (cancelled, stale)' - type: boolean - default: true - clean_logs: - description: 'Delete workflow run logs older than 30 days' - type: boolean - default: true - fix_templates: - description: 'Strip copyright comment blocks from issue templates' - type: boolean - default: true - rebuild_indexes: - description: 'Rebuild docs/ index files' - type: boolean - default: true - delete_closed_issues: - description: 'Delete issues that have been closed for more than 30 days' - type: boolean - default: false - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -permissions: - contents: write - issues: write - actions: write - -jobs: - cleanup: - name: Repository Maintenance - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - token: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - fetch-depth: 0 - - - name: Check actor permission - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - ACTOR="${{ github.actor }}" - # Schedule triggers use github-actions[bot] - if [ "${{ github.event_name }}" = "schedule" ]; then - echo "✅ Scheduled run — authorized" - exit 0 - fi - AUTHORIZED_USERS="jmiller-moko github-actions[bot]" - for user in $AUTHORIZED_USERS; do - if [ "$ACTOR" = "$user" ]; then - echo "✅ ${ACTOR} authorized" - exit 0 - fi - done - PERMISSION=$(gh api "repos/${{ github.repository }}/collaborators/${ACTOR}/permission" \ - --jq '.permission' 2>/dev/null) - case "$PERMISSION" in - admin|maintain) echo "✅ ${ACTOR} has ${PERMISSION}" ;; - *) echo "❌ Admin or maintain required"; exit 1 ;; - esac - - # ── Determine which tasks to run ───────────────────────────────────── - # On schedule: run all tasks with safe defaults (labels NOT reset) - # On dispatch: use input toggles - - name: Set task flags - id: tasks - run: | - if [ "${{ github.event_name }}" = "schedule" ]; then - echo "reset_labels=false" >> $GITHUB_OUTPUT - echo "clean_branches=true" >> $GITHUB_OUTPUT - echo "clean_workflows=true" >> $GITHUB_OUTPUT - echo "clean_logs=true" >> $GITHUB_OUTPUT - echo "fix_templates=true" >> $GITHUB_OUTPUT - echo "rebuild_indexes=true" >> $GITHUB_OUTPUT - echo "delete_closed_issues=false" >> $GITHUB_OUTPUT - else - echo "reset_labels=${{ inputs.reset_labels }}" >> $GITHUB_OUTPUT - echo "clean_branches=${{ inputs.clean_branches }}" >> $GITHUB_OUTPUT - echo "clean_workflows=${{ inputs.clean_workflows }}" >> $GITHUB_OUTPUT - echo "clean_logs=${{ inputs.clean_logs }}" >> $GITHUB_OUTPUT - echo "fix_templates=${{ inputs.fix_templates }}" >> $GITHUB_OUTPUT - echo "rebuild_indexes=${{ inputs.rebuild_indexes }}" >> $GITHUB_OUTPUT - echo "delete_closed_issues=${{ inputs.delete_closed_issues }}" >> $GITHUB_OUTPUT - fi - - # ── DELETE RETIRED WORKFLOWS (always runs) ──────────────────────────── - - name: Delete retired workflow files - run: | - echo "## 🗑️ Retired Workflow Cleanup" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - RETIRED=( - ".github/workflows/build.yml" - ".github/workflows/code-quality.yml" - ".github/workflows/release-cycle.yml" - ".github/workflows/release-pipeline.yml" - ".github/workflows/branch-cleanup.yml" - ".github/workflows/auto-update-changelog.yml" - ".github/workflows/enterprise-issue-manager.yml" - ".github/workflows/flush-actions-cache.yml" - ".github/workflows/mokostandards-script-runner.yml" - ".github/workflows/unified-ci.yml" - ".github/workflows/unified-platform-testing.yml" - ".github/workflows/reusable-build.yml" - ".github/workflows/reusable-ci-validation.yml" - ".github/workflows/reusable-deploy.yml" - ".github/workflows/reusable-php-quality.yml" - ".github/workflows/reusable-platform-testing.yml" - ".github/workflows/reusable-project-detector.yml" - ".github/workflows/reusable-release.yml" - ".github/workflows/reusable-script-executor.yml" - ".github/workflows/rebuild-docs-indexes.yml" - ".github/workflows/setup-project-v2.yml" - ".github/workflows/sync-docs-to-project.yml" - ".github/workflows/release.yml" - ".github/workflows/sync-changelogs.yml" - ".github/workflows/version_branch.yml" - "update.json" - ".github/workflows/auto-version-branch.yml" - ".github/workflows/publish-to-mokodolibarr.yml" - ".github/workflows/ci.yml" - ".github/workflows/deploy-rs.yml" - "sftp-config.json" - "sftp-config.json.template" - "scripts/sftp-config" - ) - - DELETED=0 - for wf in "${RETIRED[@]}"; do - if [ -f "$wf" ]; then - git rm "$wf" 2>/dev/null || rm -f "$wf" - echo " Deleted: \`$(basename $wf)\`" >> $GITHUB_STEP_SUMMARY - DELETED=$((DELETED+1)) - fi - done - - if [ "$DELETED" -gt 0 ]; then - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add -A - git commit -m "chore: delete ${DELETED} retired workflow file(s) [skip ci]" \ - --author="github-actions[bot] " - git push - echo "✅ ${DELETED} retired workflow(s) deleted" >> $GITHUB_STEP_SUMMARY - else - echo "✅ No retired workflows found" >> $GITHUB_STEP_SUMMARY - fi - - # ── LABEL RESET ────────────────────────────────────────────────────── - - name: Reset labels to standard set - if: steps.tasks.outputs.reset_labels == 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - REPO="${{ github.repository }}" - echo "## 🏷️ Label Reset" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - gh api "repos/${REPO}/labels?per_page=100" --paginate --jq '.[].name' | while read -r label; do - ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$label', safe=''))") - gh api -X DELETE "repos/${REPO}/labels/${ENCODED}" --silent 2>/dev/null || true - done - - while IFS='|' read -r name color description; do - [ -z "$name" ] && continue - gh api "repos/${REPO}/labels" \ - -f name="$name" -f color="$color" -f description="$description" \ - --silent 2>/dev/null || true - done << 'LABELS' - joomla|7F52FF|Joomla extension or component - dolibarr|FF6B6B|Dolibarr module or extension - generic|808080|Generic project or library - 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 - 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 - automation|8B4513|Automated processes or scripts - mokostandards|B60205|MokoStandards 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: critical|B60205|Critical priority, must be addressed immediately - priority: high|D93F0B|High priority - priority: medium|FBCA04|Medium priority - priority: low|0E8A16|Low priority - 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 - type: version|0E8A16|Version-related change - 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/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: 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 - standards-update|B60205|MokoStandards sync update - standards-drift|FBCA04|Repository drifted from MokoStandards - 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 - LABELS - - echo "✅ Standard labels created" >> $GITHUB_STEP_SUMMARY - - # ── BRANCH CLEANUP ─────────────────────────────────────────────────── - - name: Delete old sync branches - if: steps.tasks.outputs.clean_branches == 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - REPO="${{ github.repository }}" - CURRENT="chore/sync-mokostandards-v04.05" - echo "## 🌿 Branch Cleanup" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - FOUND=false - gh api "repos/${REPO}/branches?per_page=100" --jq '.[].name' | \ - grep "^chore/sync-mokostandards" | \ - grep -v "^${CURRENT}$" | while read -r branch; do - gh pr list --repo "$REPO" --head "$branch" --state open --json number --jq '.[].number' 2>/dev/null | while read -r pr; do - gh pr close "$pr" --repo "$REPO" --comment "Superseded by \`${CURRENT}\`" 2>/dev/null || true - echo " Closed PR #${pr}" >> $GITHUB_STEP_SUMMARY - done - gh api -X DELETE "repos/${REPO}/git/refs/heads/${branch}" --silent 2>/dev/null || true - echo " Deleted: \`${branch}\`" >> $GITHUB_STEP_SUMMARY - FOUND=true - done - - if [ "$FOUND" != "true" ]; then - echo "✅ No old sync branches found" >> $GITHUB_STEP_SUMMARY - fi - - # ── WORKFLOW RUN CLEANUP ───────────────────────────────────────────── - - name: Clean up workflow runs - if: steps.tasks.outputs.clean_workflows == 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - REPO="${{ github.repository }}" - echo "## 🔄 Workflow Run Cleanup" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - DELETED=0 - # Delete cancelled and stale workflow runs - for status in cancelled stale; do - gh api "repos/${REPO}/actions/runs?status=${status}&per_page=100" \ - --jq '.workflow_runs[].id' 2>/dev/null | while read -r run_id; do - gh api -X DELETE "repos/${REPO}/actions/runs/${run_id}" --silent 2>/dev/null || true - DELETED=$((DELETED+1)) - done - done - - echo "✅ Cleaned cancelled/stale workflow runs" >> $GITHUB_STEP_SUMMARY - - # ── LOG CLEANUP ────────────────────────────────────────────────────── - - name: Delete old workflow run logs - if: steps.tasks.outputs.clean_logs == 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - REPO="${{ github.repository }}" - CUTOFF=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ) - echo "## 📋 Log Cleanup" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Deleting logs older than: ${CUTOFF}" >> $GITHUB_STEP_SUMMARY - - DELETED=0 - gh api "repos/${REPO}/actions/runs?created=<${CUTOFF}&per_page=100" \ - --jq '.workflow_runs[].id' 2>/dev/null | while read -r run_id; do - gh api -X DELETE "repos/${REPO}/actions/runs/${run_id}/logs" --silent 2>/dev/null || true - DELETED=$((DELETED+1)) - done - - echo "✅ Cleaned old workflow run logs" >> $GITHUB_STEP_SUMMARY - - # ── ISSUE TEMPLATE FIX ────────────────────────────────────────────── - - name: Strip copyright headers from issue templates - if: steps.tasks.outputs.fix_templates == 'true' - run: | - echo "## 📋 Issue Template Cleanup" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - FIXED=0 - for f in .github/ISSUE_TEMPLATE/*.md; do - [ -f "$f" ] || continue - if grep -q '^$/d' "$f" - echo " Cleaned: \`$(basename $f)\`" >> $GITHUB_STEP_SUMMARY - FIXED=$((FIXED+1)) - fi - done - - if [ "$FIXED" -gt 0 ]; then - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add .github/ISSUE_TEMPLATE/ - git commit -m "fix: strip copyright comment blocks from issue templates [skip ci]" \ - --author="github-actions[bot] " - git push - echo "✅ ${FIXED} template(s) cleaned and committed" >> $GITHUB_STEP_SUMMARY - else - echo "✅ No templates need cleaning" >> $GITHUB_STEP_SUMMARY - fi - - # ── REBUILD DOC INDEXES ───────────────────────────────────────────── - - name: Rebuild docs/ index files - if: steps.tasks.outputs.rebuild_indexes == 'true' - run: | - echo "## 📚 Documentation Index Rebuild" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ ! -d "docs" ]; then - echo "⏭️ No docs/ directory — skipping" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - UPDATED=0 - # Generate index.md for each docs/ subdirectory - find docs -type d | while read -r dir; do - INDEX="${dir}/index.md" - FILES=$(find "$dir" -maxdepth 1 -name "*.md" ! -name "index.md" -printf "- [%f](./%f)\n" 2>/dev/null | sort) - if [ -z "$FILES" ]; then - continue - fi - - cat > "$INDEX" << INDEXEOF - # $(basename "$dir") - - ## Documents - - ${FILES} - - --- - *Auto-generated by repository-cleanup workflow* - INDEXEOF - # Dedent - sed -i 's/^ //' "$INDEX" - UPDATED=$((UPDATED+1)) - done - - if [ "$UPDATED" -gt 0 ]; then - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add docs/ - if ! git diff --cached --quiet; then - git commit -m "docs: rebuild documentation indexes [skip ci]" \ - --author="github-actions[bot] " - git push - echo "✅ ${UPDATED} index file(s) rebuilt and committed" >> $GITHUB_STEP_SUMMARY - else - echo "✅ All indexes already up to date" >> $GITHUB_STEP_SUMMARY - fi - else - echo "✅ No indexes to rebuild" >> $GITHUB_STEP_SUMMARY - fi - - # ── VERSION DRIFT DETECTION ────────────────────────────────────────── - - name: Check for version drift - run: | - echo "## 📦 Version Drift Check" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ ! -f "README.md" ]; then - echo "⏭️ No README.md — skipping" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md 2>/dev/null | head -1) - if [ -z "$README_VERSION" ]; then - echo "⚠️ No VERSION found in README.md FILE INFORMATION block" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - echo "**README version:** \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - DRIFT=0 - CHECKED=0 - - # Check all files with FILE INFORMATION blocks - while IFS= read -r -d '' file; do - FILE_VERSION=$(grep -oP '^\s*\*?\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' "$file" 2>/dev/null | head -1) - [ -z "$FILE_VERSION" ] && continue - CHECKED=$((CHECKED+1)) - if [ "$FILE_VERSION" != "$README_VERSION" ]; then - echo " ⚠️ \`${file}\`: \`${FILE_VERSION}\` (expected \`${README_VERSION}\`)" >> $GITHUB_STEP_SUMMARY - DRIFT=$((DRIFT+1)) - fi - done < <(find . -maxdepth 4 -type f \( -name "*.php" -o -name "*.md" -o -name "*.yml" \) ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" -print0 2>/dev/null) - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "$DRIFT" -gt 0 ]; then - echo "⚠️ **${DRIFT}** file(s) out of ${CHECKED} have version drift" >> $GITHUB_STEP_SUMMARY - echo "Run \`sync-version-on-merge\` workflow or update manually" >> $GITHUB_STEP_SUMMARY - else - echo "✅ All ${CHECKED} file(s) match README version \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY - fi - - # ── PROTECT CUSTOM WORKFLOWS ──────────────────────────────────────── - - name: Ensure custom workflow directory exists - run: | - echo "## 🔧 Custom Workflows" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ ! -d ".github/workflows/custom" ]; then - mkdir -p .github/workflows/custom - cat > .github/workflows/custom/README.md << 'CWEOF' - # Custom Workflows - - Place repo-specific workflows here. Files in this directory are: - - **Never overwritten** by MokoStandards bulk sync - - **Never deleted** by the repository-cleanup workflow - - Safe for custom CI, notifications, or repo-specific automation - - Synced workflows live in `.github/workflows/` (parent directory). - CWEOF - sed -i 's/^ //' .github/workflows/custom/README.md - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add .github/workflows/custom/ - if ! git diff --cached --quiet; then - git commit -m "chore: create .github/workflows/custom/ for repo-specific workflows [skip ci]" \ - --author="github-actions[bot] " - git push - echo "✅ Created \`.github/workflows/custom/\` directory" >> $GITHUB_STEP_SUMMARY - fi - else - CUSTOM_COUNT=$(find .github/workflows/custom -name "*.yml" -o -name "*.yaml" 2>/dev/null | wc -l) - echo "✅ Custom workflow directory exists (${CUSTOM_COUNT} workflow(s))" >> $GITHUB_STEP_SUMMARY - fi - - # ── DELETE CLOSED ISSUES ────────────────────────────────────────────── - - name: Delete old closed issues - if: steps.tasks.outputs.delete_closed_issues == 'true' - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - run: | - REPO="${{ github.repository }}" - CUTOFF=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-30d +%Y-%m-%dT%H:%M:%SZ) - echo "## 🗑️ Closed Issue Cleanup" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Deleting issues closed before: ${CUTOFF}" >> $GITHUB_STEP_SUMMARY - - DELETED=0 - gh api "repos/${REPO}/issues?state=closed&since=1970-01-01T00:00:00Z&per_page=100&sort=updated&direction=asc" \ - --jq ".[] | select(.closed_at < \"${CUTOFF}\") | .number" 2>/dev/null | while read -r num; do - # Lock and close with "not_planned" to mark as cleaned up - gh api "repos/${REPO}/issues/${num}/lock" -X PUT -f lock_reason="resolved" --silent 2>/dev/null || true - echo " Locked issue #${num}" >> $GITHUB_STEP_SUMMARY - DELETED=$((DELETED+1)) - done - - if [ "$DELETED" -eq 0 ] 2>/dev/null; then - echo "✅ No old closed issues found" >> $GITHUB_STEP_SUMMARY - else - echo "✅ Locked ${DELETED} old closed issue(s)" >> $GITHUB_STEP_SUMMARY - fi - - - name: Summary - if: always() - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "---" >> $GITHUB_STEP_SUMMARY - echo "*Run by @${{ github.actor }} — trigger: ${{ github.event_name }}*" >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/shared/sync-version-on-merge.yml.template b/templates/workflows/shared/sync-version-on-merge.yml.template deleted file mode 100644 index 8eda567..0000000 --- a/templates/workflows/shared/sync-version-on-merge.yml.template +++ /dev/null @@ -1,137 +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: Gitea.Workflow -# INGROUP: MokoStandards.Automation -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/shared/sync-version-on-merge.yml.template -# VERSION: 04.06.00 -# BRIEF: Auto-bump patch version on every push to main and propagate to all file headers -# NOTE: Synced via bulk-repo-sync to .github/workflows/sync-version-on-merge.yml in all governed repos. -# README.md is the single source of truth for the repository version. - -name: Sync Version from README - -on: - pull_request: - types: [closed] - branches: - - main - workflow_dispatch: - inputs: - dry_run: - description: 'Dry run (preview only, no commit)' - type: boolean - default: false - -permissions: - contents: write - issues: write - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - sync-version: - name: Propagate README version - runs-on: ubuntu-latest - if: >- - github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - token: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - fetch-depth: 0 - - - name: Set up PHP - uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0 - with: - php-version: '8.1' - tools: composer - - - name: Setup MokoStandards tools - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - git clone --depth 1 --branch {{standards_branch}} --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ - /tmp/mokostandards-api - cd /tmp/mokostandards-api - composer install --no-dev --no-interaction --quiet - - - name: Auto-bump patch version - if: ${{ github.event_name != 'workflow_dispatch' && github.actor != 'github-actions[bot]' }} - run: | - if git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -q '^README\.md$'; then - echo "README.md changed in this push — skipping auto-bump" - exit 0 - fi - - RESULT=$(php /tmp/mokostandards-api/cli/version_bump.php --path .) || { - echo "⚠️ Could not bump version — skipping" - exit 0 - } - echo "Auto-bumping patch: $RESULT" - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add README.md - git commit -m "chore(version): auto-bump patch ${RESULT} [skip ci]" \ - --author="github-actions[bot] " - git push - - - name: Extract version from README.md - id: readme_version - run: | - git pull --ff-only 2>/dev/null || true - VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null) - if [ -z "$VERSION" ]; then - echo "⚠️ No VERSION in README.md — skipping propagation" - echo "skip=true" >> $GITHUB_OUTPUT - exit 0 - fi - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "skip=false" >> $GITHUB_OUTPUT - echo "✅ README.md version: $VERSION" - - - name: Run version sync - if: ${{ steps.readme_version.outputs.skip != 'true' && inputs.dry_run != true }} - run: | - php /tmp/mokostandards-api/maintenance/update_version_from_readme.php \ - --path . \ - --create-issue \ - --repo "${{ github.repository }}" - env: - GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - - - name: Commit updated files - if: ${{ steps.readme_version.outputs.skip != 'true' && inputs.dry_run != true }} - run: | - git pull --ff-only 2>/dev/null || true - if git diff --quiet; then - echo "ℹ️ No version changes needed — already up to date" - exit 0 - fi - VERSION="${{ steps.readme_version.outputs.version }}" - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add -A - git commit -m "chore(version): sync badges and headers to ${VERSION} [skip ci]" \ - --author="github-actions[bot] " - git push - - - name: Summary - run: | - VERSION="${{ steps.readme_version.outputs.version }}" - echo "## 📦 Version Sync — ${VERSION}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Source:** \`README.md\` FILE INFORMATION block" >> $GITHUB_STEP_SUMMARY - echo "**Version:** \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/terraform/ci.yml.template b/templates/workflows/terraform/ci.yml.template deleted file mode 100644 index 1c0cadc..0000000 --- a/templates/workflows/terraform/ci.yml.template +++ /dev/null @@ -1,207 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Terraform -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/terraform/ci.yml -# VERSION: 04.06.00 -# BRIEF: Terraform continuous integration workflow with validation, formatting, and planning -# NOTE: Validates Terraform configuration, checks formatting, and generates plans - -name: Terraform CI - -on: - push: - branches: - - main - - dev/** - - feature/** - paths: - - '**.tf' - - '**.tfvars' - - '.github/workflows/terraform-ci.yml' - pull_request: - branches: - - main - - dev/** - paths: - - '**.tf' - - '**.tfvars' - -permissions: - contents: read - pull-requests: write - id-token: write # Required for OIDC authentication - security-events: write # Required for CodeQL SARIF upload - -env: - TF_VERSION: '1.7.0' # Update to your preferred Terraform version - TF_WORKING_DIR: '.' # Update to your terraform directory - -jobs: - terraform-validate: - name: Terraform Validation - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: ${{ env.TF_VERSION }} - - - name: Terraform Format Check - id: fmt - run: terraform fmt -check -recursive - working-directory: ${{ env.TF_WORKING_DIR }} - continue-on-error: true - - - name: Terraform Init - id: init - run: terraform init -backend=false - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Terraform Validate - id: validate - run: terraform validate -no-color - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Comment PR - Validation Results - if: github.event_name == 'pull_request' - uses: actions/github-script@v7 # TODO: Replace with curl for Gitea compatibility - with: - script: | - const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\` - #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\` - #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\` - -
Validation Output - - \`\`\` - ${{ steps.validate.outputs.stdout }} - \`\`\` - -
- - *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Workflow: \`${{ github.workflow }}\`*`; - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: output - }) - - - name: Fail if validation failed - if: steps.validate.outcome == 'failure' - run: exit 1 - - terraform-plan: - name: Terraform Plan - runs-on: ubuntu-latest - needs: terraform-validate - if: github.event_name == 'pull_request' - - strategy: - matrix: - environment: [staging, prod] # Customize based on your environments - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: ${{ env.TF_VERSION }} - terraform_wrapper: false - - # Configure cloud credentials here based on your provider - # Example for AWS: - # - name: Configure AWS Credentials - # uses: aws-actions/configure-aws-credentials@v4 - # with: - # role-to-assume: ${{ secrets.AWS_ROLE_ARN }} - # aws-region: us-east-1 - - - name: Terraform Init - run: | - terraform init \ - -backend-config="key=terraform-${{ matrix.environment }}.tfstate" - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Terraform Plan - id: plan - run: | - terraform plan \ - -var-file="environments/${{ matrix.environment }}.tfvars" \ - -no-color \ - -out=tfplan-${{ matrix.environment }} - working-directory: ${{ env.TF_WORKING_DIR }} - continue-on-error: true - - - name: Comment PR - Plan Results - uses: actions/github-script@v7 # TODO: Replace with curl for Gitea compatibility - with: - script: | - const fs = require('fs'); - const plan = fs.readFileSync('${{ env.TF_WORKING_DIR }}/tfplan-${{ matrix.environment }}', 'utf8'); - - const output = `#### Terraform Plan for \`${{ matrix.environment }}\` 📖\`${{ steps.plan.outcome }}\` - -
Show Plan - - \`\`\`terraform - ${plan} - \`\`\` - -
- - *Environment: \`${{ matrix.environment }}\`, Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`; - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: output - }) - - - name: Upload Plan Artifact - uses: actions/upload-artifact@v4 - with: - name: tfplan-${{ matrix.environment }} - path: ${{ env.TF_WORKING_DIR }}/tfplan-${{ matrix.environment }} - retention-days: 5 - - terraform-security: - name: Terraform Security Scan - runs-on: ubuntu-latest - needs: terraform-validate - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Run tfsec - uses: aquasecurity/tfsec-action@v1.0.3 - with: - working_directory: ${{ env.TF_WORKING_DIR }} - soft_fail: true - - - name: Run Checkov - uses: bridgecrewio/checkov-action@v12 - with: - directory: ${{ env.TF_WORKING_DIR }} - framework: terraform - soft_fail: true - output_format: sarif - output_file_path: reports/checkov.sarif - - - name: Upload Checkov Results - uses: github/codeql-action/upload-sarif@v4 - if: always() - with: - sarif_file: reports/checkov.sarif diff --git a/templates/workflows/terraform/deploy.yml.template b/templates/workflows/terraform/deploy.yml.template deleted file mode 100644 index 124e9eb..0000000 --- a/templates/workflows/terraform/deploy.yml.template +++ /dev/null @@ -1,210 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# 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 . -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Terraform -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/terraform/deploy.yml.template -# VERSION: 04.06.00 -# BRIEF: Terraform infrastructure deployment workflow for multiple environments -# NOTE: Applies Terraform configurations to provision and manage infrastructure - -name: Terraform Deploy - -on: - workflow_dispatch: - inputs: - environment: - description: 'Environment to deploy' - required: true - type: choice - options: - - staging - - prod - action: - description: 'Terraform action' - required: true - type: choice - options: - - plan - - apply - - destroy - auto_approve: - description: 'Auto-approve apply/destroy (use with caution)' - required: false - type: boolean - default: false - -permissions: - contents: read - pull-requests: write - id-token: write # Required for OIDC authentication - issues: write - -env: - TF_VERSION: '1.7.0' - TF_WORKING_DIR: '.' - -jobs: - terraform-deploy: - name: Terraform ${{ inputs.action }} - ${{ inputs.environment }} - runs-on: ubuntu-latest - environment: ${{ inputs.environment }} - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: ${{ env.TF_VERSION }} - - # Configure cloud provider credentials - # Example for AWS: - - name: Configure AWS Credentials - if: ${{ vars.CLOUD_PROVIDER == 'aws' }} - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ secrets.AWS_ROLE_ARN }} - aws-region: ${{ vars.AWS_REGION || 'us-east-1' }} - - # Example for Azure: - # - name: Azure Login - # if: ${{ vars.CLOUD_PROVIDER == 'azure' }} - # uses: azure/login@v1 - # with: - # creds: ${{ secrets.AZURE_CREDENTIALS }} - - # Example for GCP: - # - name: Authenticate to Google Cloud - # if: ${{ vars.CLOUD_PROVIDER == 'gcp' }} - # uses: google-github-actions/auth@v2 - # with: - # workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - # service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - - - name: Terraform Init - run: | - terraform init \ - -backend-config="key=${{ inputs.environment }}/terraform.tfstate" \ - -backend-config="bucket=${{ vars.TF_STATE_BUCKET }}" \ - -backend-config="region=${{ vars.AWS_REGION || 'us-east-1' }}" - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Terraform Workspace - run: | - terraform workspace select ${{ inputs.environment }} || terraform workspace new ${{ inputs.environment }} - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Terraform Plan - id: plan - run: | - terraform plan \ - -var-file="environments/${{ inputs.environment }}.tfvars" \ - -out=tfplan \ - -no-color - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Upload Plan - uses: actions/upload-artifact@v6.0.0 - with: - name: tfplan-${{ inputs.environment }}-${{ github.run_number }} - path: ${{ env.TF_WORKING_DIR }}/tfplan - retention-days: 30 - - - name: Terraform Apply - if: inputs.action == 'apply' && (inputs.auto_approve || inputs.environment != 'prod') - id: apply - run: | - terraform apply -auto-approve tfplan - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Terraform Apply (Production - Manual Approval Required) - if: inputs.action == 'apply' && inputs.environment == 'prod' && !inputs.auto_approve - id: apply_prod - run: | - echo "Production deployment requires manual approval" - echo "Please review the plan and approve in GitHub environment settings" - terraform apply tfplan - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Terraform Destroy - if: inputs.action == 'destroy' && inputs.auto_approve - id: destroy - run: | - terraform destroy \ - -var-file="environments/${{ inputs.environment }}.tfvars" \ - -auto-approve - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Terraform Output - if: inputs.action == 'apply' && (steps.apply.outcome == 'success' || steps.apply_prod.outcome == 'success') - id: output - run: terraform output -json - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Comment on Issue/PR - if: always() - uses: actions/github-script@v6 # TODO: Replace with curl for Gitea compatibility - with: - script: | - const fs = require('fs'); - const action = '${{ inputs.action }}'; - const env = '${{ inputs.environment }}'; - const outcome = '${{ steps.apply.outcome || steps.destroy.outcome || steps.plan.outcome }}'; - - let body = `## Terraform ${action.toUpperCase()} - ${env}\n\n`; - body += `**Status:** ${outcome === 'success' ? '✅ Success' : '❌ Failed'}\n`; - body += `**Environment:** \`${env}\`\n`; - body += `**Action:** \`${action}\`\n`; - body += `**Triggered by:** @${{ github.actor }}\n\n`; - - if (action === 'apply' && outcome === 'success') { - body += '### Infrastructure Updated Successfully\n\n'; - try { - const outputs = '${{ steps.output.outputs.stdout }}'; - body += '
Terraform Outputs\n\n'; - body += '```json\n' + outputs + '\n```\n\n'; - body += '
\n'; - } catch (e) { - body += '_Outputs not available_\n'; - } - } - - // Post to issue if triggered from issue - if (context.issue.number) { - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: body - }); - } - - - name: Summary - if: always() - run: | - echo "### Terraform Deployment Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Environment:** ${{ inputs.environment }}" >> $GITHUB_STEP_SUMMARY - echo "- **Action:** ${{ inputs.action }}" >> $GITHUB_STEP_SUMMARY - echo "- **Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY - echo "- **Run Number:** ${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/terraform/drift-detection.yml.template b/templates/workflows/terraform/drift-detection.yml.template deleted file mode 100644 index ef89785..0000000 --- a/templates/workflows/terraform/drift-detection.yml.template +++ /dev/null @@ -1,231 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# 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 . -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Terraform -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/terraform/drift-detection.yml.template -# VERSION: 04.06.00 -# BRIEF: Terraform drift detection workflow to identify infrastructure changes -# NOTE: Runs on schedule to detect configuration drift from desired state - -name: Terraform Drift Detection - -on: - schedule: - # Run daily at 2 AM UTC - - cron: '0 2 * * *' - workflow_dispatch: - inputs: - environment: - description: 'Environment to check' - required: false - type: choice - options: - - all - - staging - - prod - -permissions: - contents: read - issues: write - id-token: write - -env: - TF_VERSION: '1.7.0' - TF_WORKING_DIR: '.' - -jobs: - drift-detection: - name: Detect Drift - ${{ matrix.environment }} - runs-on: ubuntu-latest - - strategy: - matrix: - environment: ${{ inputs.environment == 'all' && fromJSON('["staging", "prod"]') || fromJSON(format('["{0}"]', inputs.environment || 'prod')) }} - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: ${{ env.TF_VERSION }} - - # Configure cloud credentials - - name: Configure AWS Credentials - if: ${{ vars.CLOUD_PROVIDER == 'aws' }} - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ secrets.AWS_ROLE_ARN }} - aws-region: ${{ vars.AWS_REGION || 'us-east-1' }} - - - name: Terraform Init - run: | - terraform init \ - -backend-config="key=${{ matrix.environment }}/terraform.tfstate" - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Terraform Workspace - run: | - terraform workspace select ${{ matrix.environment }} || terraform workspace new ${{ matrix.environment }} - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Terraform Plan (Drift Check) - id: plan - run: | - terraform plan \ - -var-file="environments/${{ matrix.environment }}.tfvars" \ - -detailed-exitcode \ - -no-color - working-directory: ${{ env.TF_WORKING_DIR }} - continue-on-error: true - - - name: Analyze Drift - id: analyze - run: | - if [ ${{ steps.plan.outputs.exitcode }} -eq 0 ]; then - echo "drift=false" >> $GITHUB_OUTPUT - echo "No drift detected" - elif [ ${{ steps.plan.outputs.exitcode }} -eq 2 ]; then - echo "drift=true" >> $GITHUB_OUTPUT - echo "Drift detected!" - else - echo "drift=error" >> $GITHUB_OUTPUT - echo "Error during plan" - exit 1 - fi - - - name: Create Drift Issue - if: steps.analyze.outputs.drift == 'true' - uses: actions/github-script@v7 # TODO: Replace with curl for Gitea compatibility - with: - script: | - const env = '${{ matrix.environment }}'; - const plan = `${{ steps.plan.outputs.stdout }}`; - - // Check if drift issue already exists - const issues = await github.rest.issues.listForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['terraform-drift', `environment:${env}`], - state: 'open' - }); - - const body = `## 🚨 Terraform Drift Detected - - **Environment:** \`${env}\` - **Detected:** ${new Date().toISOString()} - - ### Drift Details - - Infrastructure has drifted from the desired state defined in Terraform configuration. - -
Show Terraform Plan - - \`\`\`terraform - ${plan} - \`\`\` - -
- - ### Recommended Actions - - 1. Review the changes shown in the plan - 2. Determine if changes are: - - Unauthorized modifications (security concern) - - Manual changes that should be codified - - Expected changes from other automation - 3. Either: - - Apply Terraform to restore desired state - - Update Terraform config to match current state - - Investigate unauthorized changes - - ### Auto-generated - This issue was automatically created by the drift detection workflow. - `; - - if (issues.data.length === 0) { - // Create new issue - await github.rest.issues.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: `[Terraform Drift] ${env} environment has drifted`, - body: body, - labels: ['terraform-drift', `environment:${env}`, 'infrastructure'], - assignees: ['copilot', 'jmiller-moko'] - }); - } else { - // Update existing issue - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issues.data[0].number, - body: `### New Drift Detected\n\n${body}` - }); - } - - - name: Close Drift Issue if Resolved - if: steps.analyze.outputs.drift == 'false' - uses: actions/github-script@v7 # TODO: Replace with curl for Gitea compatibility - with: - script: | - const env = '${{ matrix.environment }}'; - - // Find open drift issues for this environment - const issues = await github.rest.issues.listForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['terraform-drift', `environment:${env}`], - state: 'open' - }); - - for (const issue of issues.data) { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: `✅ Drift resolved. Infrastructure now matches Terraform configuration.\n\nDetected: ${new Date().toISOString()}` - }); - - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - state: 'closed' - }); - } - - summary: - name: Drift Detection Summary - runs-on: ubuntu-latest - needs: drift-detection - if: always() - - steps: - - name: Generate Summary - run: | - echo "### Terraform Drift Detection Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Date:** $(date -u)" >> $GITHUB_STEP_SUMMARY - echo "**Status:** ${{ needs.drift-detection.result }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Check individual jobs for drift details per environment." >> $GITHUB_STEP_SUMMARY diff --git a/templates/workflows/terraform/index.md b/templates/workflows/terraform/index.md deleted file mode 100644 index 46188d4..0000000 --- a/templates/workflows/terraform/index.md +++ /dev/null @@ -1,290 +0,0 @@ - - -# Terraform Workflow Templates - -## Purpose - -This directory contains GitHub Actions workflow templates specifically designed for Terraform infrastructure-as-code projects. These workflows provide automated validation, deployment, security scanning, and drift detection for Terraform configurations. - -## Available Templates - -### ci.yml -**Terraform Continuous Integration** -- Validates Terraform configuration syntax and formatting -- Runs `terraform fmt -check` to ensure code style compliance -- Executes `terraform validate` to verify configuration correctness -- Generates Terraform plans for multiple environments -- Posts plan results as PR comments -- Includes security scanning with tfsec and Checkov -- Runs on push to main branches and pull requests - -**Features:** -- Multi-environment plan generation (dev, staging, prod) -- Automated PR comments with validation results -- Security scanning integration -- SARIF report upload for security findings - -### deploy.yml.template -**Terraform Infrastructure Deployment** -- Manual workflow dispatch for controlled deployments -- Supports multiple cloud providers (AWS, Azure, GCP) -- Environment-specific deployments with variable files -- Workspace management for environment isolation -- Plan artifact uploads for audit trail -- Production deployment protection with manual approval -- Infrastructure state output capture - -**Features:** -- Choice of actions: plan, apply, destroy -- Auto-approve option (use cautiously, not for production) -- OIDC authentication support -- Environment protection rules -- Deployment summaries and issue comments - -### drift-detection.yml.template -**Terraform Drift Detection** -- Scheduled drift detection (daily by default) -- Compares actual infrastructure state to Terraform configuration -- Automatically creates issues when drift is detected -- Updates existing drift issues with new findings -- Auto-closes issues when drift is resolved -- Supports manual triggering for specific environments - -**Features:** -- Scheduled runs via cron -- Per-environment drift monitoring -- Automatic issue management -- Detailed drift reports in issues -- Resolution tracking - -## Infrastructure Definition - -### Supported Cloud Providers - -**AWS (Amazon Web Services)** -- OIDC authentication with `aws-actions/configure-aws-credentials@v4` -- S3 backend for state storage -- IAM role-based access - -**Azure (Microsoft Azure)** -- Service principal authentication -- Azure Storage backend for state - -**GCP (Google Cloud Platform)** -- Workload Identity Federation -- GCS backend for state storage - -### Environment Structure - -Terraform workflows expect the following directory structure: - -``` -├── infrastructure/terraform/ -│ ├── main.tf -│ ├── variables.tf -│ ├── outputs.tf -│ ├── environments/ -│ │ ├── dev.tfvars -│ │ ├── staging.tfvars -│ │ └── prod.tfvars -│ └── modules/ -│ └── ... -``` - -### Required Secrets - -Configure these secrets in your repository: - -**For AWS:** -- `AWS_ROLE_ARN` - IAM role ARN for OIDC authentication -- `TF_STATE_BUCKET` - S3 bucket for Terraform state (can be variable) - -**For Azure:** -- `AZURE_CREDENTIALS` - Service principal credentials JSON - -**For GCP:** -- `GCP_WORKLOAD_IDENTITY_PROVIDER` - Workload identity provider -- `GCP_SERVICE_ACCOUNT` - Service account email - -### Required Variables - -- `CLOUD_PROVIDER` - Cloud provider name (aws, azure, gcp) -- `AWS_REGION` - AWS region for deployments (default: us-east-1) -- `TF_STATE_BUCKET` - Terraform state storage bucket name - -## Usage - -### Setting Up CI - -1. Copy `ci.yml` to `.github/workflows/terraform-ci.yml` -2. Update `TF_VERSION` to your Terraform version -3. Update `TF_WORKING_DIR` to your Terraform directory -4. Configure cloud provider authentication -5. Commit and push - CI will run automatically - -### Deploying Infrastructure - -1. Copy `deploy.yml.template` to `.github/workflows/terraform-deploy.yml` -2. Configure required secrets and variables -3. Set up GitHub environments (dev, staging, prod) -4. Add protection rules for production environment -5. Trigger via GitHub Actions UI: - - Select environment - - Choose action (plan/apply/destroy) - - Optionally auto-approve (not recommended for prod) - -### Monitoring Drift - -1. Copy `drift-detection.yml.template` to `.github/workflows/terraform-drift.yml` -2. Configure authentication and environments -3. Adjust schedule as needed (default: daily at 2 AM UTC) -4. Issues will be created automatically when drift is detected - -## Security Scanning - -### tfsec -- Static analysis of Terraform code -- Identifies security misconfigurations -- Checks against AWS, Azure, GCP best practices -- Soft fail option allows warnings without blocking - -### Checkov -- Policy-as-code security scanning -- Over 1000 built-in checks -- SARIF output for GitHub Security tab -- Covers multiple cloud providers - -## Best Practices - -### State Management -- Always use remote state (S3, Azure Storage, GCS) -- Enable state locking to prevent concurrent modifications -- Use separate state files per environment -- Regular state backups - -### Environment Management -- Use Terraform workspaces for environment isolation -- Separate variable files per environment (`.tfvars`) -- Environment-specific state file keys -- GitHub environment protection rules for production - -### Security -- Use OIDC for authentication (avoid long-lived credentials) -- Store sensitive values in GitHub Secrets -- Regular security scanning with tfsec and Checkov -- Review plans before applying -- Enable drift detection - -### Workflow Configuration -- Pin Terraform version for consistency -- Use `terraform_wrapper: false` for plan output capture -- Enable PR comments for visibility -- Upload plan artifacts for audit trail -- Implement manual approval for production - -## Customization - -### Adjusting Environments -Modify the environment matrix in workflows: - -```yaml -strategy: - matrix: - environment: [dev, staging, prod, qa] # Add/remove as needed -``` - -### Changing Terraform Version -Update the `TF_VERSION` environment variable: - -```yaml -env: - TF_VERSION: '1.7.0' # Update to desired version -``` - -### Custom Backend Configuration -Modify the `terraform init` step: - -```yaml -- name: Terraform Init - run: | - terraform init \ - -backend-config="key=${{ matrix.environment }}/terraform.tfstate" \ - -backend-config="bucket=my-state-bucket" \ - -backend-config="dynamodb_table=my-lock-table" -``` - -## Troubleshooting - -### Plan Fails with Authentication Error -- Verify cloud provider credentials are configured -- Check OIDC trust relationship (AWS) -- Verify service principal permissions (Azure) -- Confirm workload identity setup (GCP) - -### Drift Detection False Positives -- Review excluded resource types -- Check for timestamp fields causing drift -- Consider using `lifecycle { ignore_changes = [...] }` -- Verify state is up-to-date - -### State Lock Conflicts -- Check for running workflows -- Verify state locking is enabled -- Manually unlock if needed (use cautiously) -- Review DynamoDB table for locks (AWS) - -## Related Documentation - -- [Terraform Documentation](https://www.terraform.io/docs) -- [GitHub Actions Documentation](https://docs.github.com/en/actions) -- [tfsec Documentation](https://aquasecurity.github.io/tfsec/) -- [Checkov Documentation](https://www.checkov.io/) - -## Metadata - -| Field | Value | -| -------------- | --------------------------------------------------------------------- | -| Document Type | Index | -| Domain | Infrastructure | -| Applies To | All Repositories | -| Jurisdiction | Tennessee, USA | -| Owner | Moko Consulting | -| Repo | https://git.mokoconsulting.tech/MokoConsulting/MokoStandards | -| Path | /templates/workflows/infrastructure/terraform/index.md | -| Version | 01.00.00 | -| Status | Active | -| Last Reviewed | 2026-01-28 | -| Reviewed By | MokoStandards Team | - -## Revision History - -| Date | Author | Change | Notes | -| ---------- | ------------------- | ------------------------------------------- | ---------------------------------------------- | -| 2026-01-28 | MokoStandards Team | Created Terraform workflow templates | Initial CI, deploy, and drift detection | diff --git a/templates/workflows/terraform/manage-repo-templates.yml.template b/templates/workflows/terraform/manage-repo-templates.yml.template deleted file mode 100644 index 6b20422..0000000 --- a/templates/workflows/terraform/manage-repo-templates.yml.template +++ /dev/null @@ -1,368 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# 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 . -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: MokoStandards.Terraform -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API -# PATH: /templates/workflows/terraform/manage-repo-templates.yml.template -# VERSION: 04.06.00 -# BRIEF: Terraform workflow to manage and update repository templates using GitHub provider -# NOTE: Uses Terraform to declaratively manage organization repositories and templates - -name: Terraform Manage Repository Templates - -on: - workflow_dispatch: - inputs: - action: - description: 'Action to perform' - required: true - type: choice - options: - - plan - - apply - - update-templates - target_repos: - description: 'Target repositories (comma-separated, or "all")' - required: false - default: 'all' - dry_run: - description: 'Dry run mode (plan only)' - required: false - type: boolean - default: true - - schedule: - # Run weekly on Sundays at 3 AM UTC - - cron: '0 3 * * 0' - -permissions: - contents: read - issues: write - id-token: write - -env: - TF_VERSION: '1.7.0' - TF_WORKING_DIR: './terraform/repository-management' - -jobs: - terraform-repo-management: - name: Terraform Repository Management - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: ${{ env.TF_VERSION }} - terraform_wrapper: false - - - name: Configure GitHub Token - run: | - echo "GH_TOKEN=${{ secrets.ORG_ADMIN_TOKEN || secrets.GH_TOKEN }}" >> $GITHUB_ENV - - - name: Create Terraform Configuration - run: | - mkdir -p ${{ env.TF_WORKING_DIR }} - - cat > ${{ env.TF_WORKING_DIR }}/main.tf << 'EOF' - terraform { - required_version = ">= 1.7.0" - - required_providers { - github = { - source = "integrations/github" - version = "~> 6.0" - } - } - - backend "local" { - path = "terraform.tfstate" - } - } - - provider "github" { - token = var.github_token - owner = var.github_org - } - - variable "github_token" { - description = "GitHub personal access token" - type = string - sensitive = true - } - - variable "github_org" { - description = "GitHub organization name" - type = string - default = "mokoconsulting-tech" - } - - variable "target_repos" { - description = "List of repositories to update" - type = list(string) - default = [] - } - EOF - - - name: Generate Repository Configurations - run: | - cat > ${{ env.TF_WORKING_DIR }}/repositories.tf << 'EOF' - # Data source to get repository information - data "github_repository" "repos" { - for_each = toset(var.target_repos) - full_name = "${var.github_org}/${each.value}" - } - - # Manage repository files from templates - resource "github_repository_file" "template_files" { - for_each = local.template_file_mappings - - repository = each.value.repo - branch = each.value.branch - file = each.value.file_path - content = file("${path.module}/../../${each.value.template_path}") - commit_message = "chore: Update ${each.value.file_path} from MokoStandards template" - commit_author = "MokoStandards Bot" - commit_email = "automation@mokoconsulting.tech" - overwrite_on_create = true - } - - # Local values for template mappings - locals { - # Define which templates go to which files - template_mappings = { - ".github/workflows/ci.yml" = { - generic = "templates/workflows/generic/ci.yml" - terraform = "templates/workflows/terraform/ci.yml" - joomla = "templates/workflows/joomla/ci-joomla.yml.template" - dolibarr = "templates/workflows/dolibarr/ci-dolibarr.yml.template" - } - ".editorconfig" = { - all = "templates/configs/.editorconfig" - } - ".gitignore" = { - generic = "templates/configs/.gitignore" - } - } - - # Flatten template mappings for all target repos - template_file_mappings = merge([ - for repo in var.target_repos : { - for file, templates in local.template_mappings : - "${repo}/${file}" => { - repo = repo - branch = "main" - file_path = file - template_path = try(templates[data.github_repository.repos[repo].topics[0]], templates["generic"], templates["all"], "") - } - if try(templates[data.github_repository.repos[repo].topics[0]], templates["generic"], templates["all"], "") != "" - } - ]...) - } - - # Output summary - output "updated_files" { - description = "Files updated in repositories" - value = { - for k, v in github_repository_file.template_files : - k => "${v.repository}/${v.file}" - } - } - - output "repository_info" { - description = "Information about target repositories" - value = { - for k, v in data.github_repository.repos : - k => { - full_name = v.full_name - topics = v.topics - default_branch = v.default_branch - } - } - } - EOF - - - name: Get Target Repositories - id: get_repos - run: | - if [ "${{ inputs.target_repos }}" = "all" ]; then - # Get all org repositories - gh api --paginate "/orgs/${{ github.repository_owner }}/repos" \ - --jq '.[].name' > repos.txt - else - # Use specified repositories - echo "${{ inputs.target_repos }}" | tr ',' '\n' > repos.txt - fi - - # Convert to JSON array for Terraform - REPOS_JSON=$(cat repos.txt | jq -R -s -c 'split("\n") | map(select(length > 0))') - echo "repos_json=${REPOS_JSON}" >> $GITHUB_OUTPUT - env: - GH_TOKEN: ${{ secrets.ORG_ADMIN_TOKEN || secrets.GH_TOKEN }} - - - name: Create Terraform Variables - run: | - cat > ${{ env.TF_WORKING_DIR }}/terraform.tfvars << EOF - github_org = "${{ github.repository_owner }}" - target_repos = ${{ steps.get_repos.outputs.repos_json }} - EOF - - - name: Terraform Init - run: terraform init - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Terraform Validate - run: terraform validate - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Terraform Plan - id: plan - run: | - terraform plan \ - -var="github_token=${{ env.GH_TOKEN }}" \ - -out=tfplan \ - -no-color - working-directory: ${{ env.TF_WORKING_DIR }} - env: - TF_LOG: INFO - - - name: Upload Plan - uses: actions/upload-artifact@v4 - with: - name: tfplan-repo-templates-${{ github.run_number }} - path: ${{ env.TF_WORKING_DIR }}/tfplan - retention-days: 30 - - - name: Comment Plan Results - if: github.event_name == 'workflow_dispatch' - uses: actions/github-script@v7 # TODO: Replace with curl for Gitea compatibility - with: - script: | - const fs = require('fs'); - - const output = `### Terraform Repository Template Management Plan - - **Action:** \`${{ inputs.action }}\` - **Target Repositories:** \`${{ inputs.target_repos }}\` - **Dry Run:** \`${{ inputs.dry_run }}\` - - #### Plan Summary - - The plan has been generated and uploaded as an artifact. - Review the changes before applying. - -
Repository Count - - Targeting **${{ steps.get_repos.outputs.repos_json }}** repositories - -
- - *Triggered by: @${{ github.actor }}* - `; - - // Create an issue for tracking - await github.rest.issues.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: 'Terraform Repository Template Update Plan', - body: output, - labels: ['terraform', 'repository-management', 'automation'], - assignees: ['copilot', 'jmiller-moko'] - }); - - - name: Terraform Apply - if: | - (inputs.action == 'apply' || inputs.action == 'update-templates') && - (inputs.dry_run == false || github.event_name == 'schedule') - id: apply - run: | - terraform apply \ - -var="github_token=${{ env.GH_TOKEN }}" \ - -auto-approve - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Get Terraform Outputs - if: steps.apply.outcome == 'success' - id: outputs - run: | - echo "updated_files=$(terraform output -json updated_files)" >> $GITHUB_OUTPUT - echo "repo_info=$(terraform output -json repository_info)" >> $GITHUB_OUTPUT - working-directory: ${{ env.TF_WORKING_DIR }} - - - name: Create Summary Report - if: steps.apply.outcome == 'success' - uses: actions/github-script@v7 # TODO: Replace with curl for Gitea compatibility - with: - script: | - const updatedFiles = JSON.parse('${{ steps.outputs.outputs.updated_files }}'); - const repoInfo = JSON.parse('${{ steps.outputs.outputs.repo_info }}'); - - let body = '## 🚀 Repository Templates Updated\n\n'; - body += `**Date:** ${new Date().toISOString()}\n`; - body += `**Repositories Updated:** ${Object.keys(repoInfo).length}\n\n`; - - body += '### Updated Files\n\n'; - body += '| Repository | File |\n'; - body += '|------------|------|\n'; - - for (const [key, value] of Object.entries(updatedFiles)) { - body += `| ${value.split('/')[0]} | \`${value.split('/').slice(1).join('/')}\` |\n`; - } - - body += '\n### Repository Information\n\n'; - for (const [repo, info] of Object.entries(repoInfo)) { - body += `- **${repo}**: Topics: ${info.topics.join(', ')}, Branch: ${info.default_branch}\n`; - } - - // Create issue with summary - await github.rest.issues.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: `Repository Templates Updated - ${new Date().toISOString().split('T')[0]}`, - body: body, - labels: ['terraform', 'repository-management', 'completed'], - assignees: ['copilot', 'jmiller-moko'] - }); - - - name: Summary - if: always() - run: | - echo "### Terraform Repository Template Management" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **Action:** ${{ inputs.action || 'scheduled' }}" >> $GITHUB_STEP_SUMMARY - echo "- **Status:** ${{ job.status }}" >> $GITHUB_STEP_SUMMARY - echo "- **Repositories:** ${{ inputs.target_repos || 'all' }}" >> $GITHUB_STEP_SUMMARY - echo "- **Run Number:** ${{ github.run_number }}" >> $GITHUB_STEP_SUMMARY - - cleanup: - name: Cleanup Terraform State - runs-on: ubuntu-latest - needs: terraform-repo-management - if: always() - - steps: - - name: Cleanup temporary files - run: | - echo "Terraform state is stored locally and will be cleaned up automatically" - echo "For production use, configure remote state backend (S3, Azure Storage, etc.)" diff --git a/templates/workflows/validate-api-project.yml b/templates/workflows/validate-api-project.yml deleted file mode 100644 index 8c980bd..0000000 --- a/templates/workflows/validate-api-project.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: Validate API Project - -on: - pull_request: - branches: [main, develop] - push: - branches: [main, develop] - workflow_dispatch: - -jobs: - validate: - name: Validate API Project - runs-on: ubuntu-latest - - steps: - - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Checkout MokoStandards - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - repository: mokoshalb/MokoStandards - path: .mokostandards - ref: main - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - extensions: mbstring, json, fileinfo - - - name: Install MokoStandards dependencies - working-directory: .mokostandards - run: composer install --prefer-dist --no-progress - - - name: Run API validation - id: validate - run: | - php .mokostandards/api/plugin_validate.php \ - --project-path . \ - --project-type api \ - --json > validation-results.json - - cat validation-results.json - - if jq -e '.valid == false' validation-results.json > /dev/null; then - echo "::error::Project validation failed" - exit 1 - fi - - - name: Run health check - id: health - run: | - php .mokostandards/api/plugin_health_check.php \ - --project-path . \ - --project-type api \ - --json > health-results.json - - cat health-results.json - - SCORE=$(jq -r '.score' health-results.json) - echo "Health Score: $SCORE/100" - - - name: Collect metrics - id: metrics - run: | - php .mokostandards/api/plugin_metrics.php \ - --project-path . \ - --project-type api \ - --json > metrics-results.json - - cat metrics-results.json - - - name: Check release readiness - id: readiness - run: | - php .mokostandards/api/plugin_readiness.php \ - --project-path . \ - --project-type api \ - --json > readiness-results.json - - cat readiness-results.json - - - name: Check OpenAPI/Swagger spec - continue-on-error: true - run: | - if [ -f openapi.yaml ] || [ -f swagger.yaml ]; then - echo "Found API specification" - # Install validator - npm install -g swagger-cli - swagger-cli validate openapi.yaml || swagger-cli validate swagger.yaml || true - fi - - - name: Upload validation artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: api-validation-results - path: | - validation-results.json - health-results.json - metrics-results.json - readiness-results.json - retention-days: 30 diff --git a/templates/workflows/validate-documentation-project.yml b/templates/workflows/validate-documentation-project.yml deleted file mode 100644 index 26af4cd..0000000 --- a/templates/workflows/validate-documentation-project.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: Validate Documentation Project - -on: - pull_request: - branches: [main, develop] - push: - branches: [main, develop] - workflow_dispatch: - -jobs: - validate: - name: Validate Documentation Project - runs-on: ubuntu-latest - - steps: - - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Checkout MokoStandards - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - repository: mokoshalb/MokoStandards - path: .mokostandards - ref: main - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - extensions: mbstring, json, fileinfo - - - name: Install MokoStandards dependencies - working-directory: .mokostandards - run: composer install --prefer-dist --no-progress - - - name: Run Documentation validation - id: validate - run: | - php .mokostandards/api/plugin_validate.php \ - --project-path . \ - --project-type documentation \ - --json > validation-results.json - - cat validation-results.json - - if jq -e '.valid == false' validation-results.json > /dev/null; then - echo "::error::Project validation failed" - exit 1 - fi - - - name: Run health check - id: health - run: | - php .mokostandards/api/plugin_health_check.php \ - --project-path . \ - --project-type documentation \ - --json > health-results.json - - cat health-results.json - - - name: Collect metrics - id: metrics - run: | - php .mokostandards/api/plugin_metrics.php \ - --project-path . \ - --project-type documentation \ - --json > metrics-results.json - - cat metrics-results.json - - - name: Check release readiness - id: readiness - run: | - php .mokostandards/api/plugin_readiness.php \ - --project-path . \ - --project-type documentation \ - --json > readiness-results.json - - cat readiness-results.json - - - name: Check for broken links - continue-on-error: true - run: | - npm install -g markdown-link-check - find . -name "*.md" -not -path "./node_modules/*" -not -path "./.mokostandards/*" \ - -exec markdown-link-check {} \; || true - - - name: Lint Markdown files - continue-on-error: true - run: | - npm install -g markdownlint-cli - markdownlint '**/*.md' --ignore node_modules --ignore .mokostandards || true - - - name: Upload validation artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: documentation-validation-results - path: | - validation-results.json - health-results.json - metrics-results.json - readiness-results.json - retention-days: 30 diff --git a/templates/workflows/validate-dolibarr-project.yml b/templates/workflows/validate-dolibarr-project.yml deleted file mode 100644 index e09454a..0000000 --- a/templates/workflows/validate-dolibarr-project.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Validate Dolibarr Project - -on: - pull_request: - branches: [main, develop] - push: - branches: [main, develop] - workflow_dispatch: - -jobs: - validate: - name: Validate Dolibarr Project - runs-on: ubuntu-latest - - steps: - - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Checkout MokoStandards - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - repository: mokoshalb/MokoStandards - path: .mokostandards - ref: main - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - extensions: mbstring, xml, json, fileinfo, zip, mysqli - - - name: Install MokoStandards dependencies - working-directory: .mokostandards - run: composer install --prefer-dist --no-progress - - - name: Run Dolibarr validation - id: validate - run: | - php .mokostandards/api/plugin_validate.php \ - --project-path . \ - --project-type dolibarr \ - --json > validation-results.json - - cat validation-results.json - - if jq -e '.valid == false' validation-results.json > /dev/null; then - echo "::error::Project validation failed" - exit 1 - fi - - - name: Run health check - id: health - run: | - php .mokostandards/api/plugin_health_check.php \ - --project-path . \ - --project-type dolibarr \ - --json > health-results.json - - cat health-results.json - - - name: Collect metrics - id: metrics - run: | - php .mokostandards/api/plugin_metrics.php \ - --project-path . \ - --project-type dolibarr \ - --json > metrics-results.json - - cat metrics-results.json - - - name: Check release readiness - id: readiness - run: | - php .mokostandards/api/plugin_readiness.php \ - --project-path . \ - --project-type dolibarr \ - --json > readiness-results.json - - cat readiness-results.json - - - name: Upload validation artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: dolibarr-validation-results - path: | - validation-results.json - health-results.json - metrics-results.json - readiness-results.json - retention-days: 30 diff --git a/templates/workflows/validate-generic-project.yml b/templates/workflows/validate-generic-project.yml deleted file mode 100644 index 1b77d0b..0000000 --- a/templates/workflows/validate-generic-project.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Validate Generic Project - -on: - pull_request: - branches: [main, develop] - push: - branches: [main, develop] - workflow_dispatch: - -jobs: - validate: - name: Validate Generic Project - runs-on: ubuntu-latest - - steps: - - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Checkout MokoStandards - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - repository: mokoshalb/MokoStandards - path: .mokostandards - ref: main - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - extensions: mbstring, json, fileinfo - - - name: Install MokoStandards dependencies - working-directory: .mokostandards - run: composer install --prefer-dist --no-progress - - - name: Run Generic validation - id: validate - run: | - php .mokostandards/api/plugin_validate.php \ - --project-path . \ - --project-type generic \ - --json > validation-results.json - - cat validation-results.json - - if jq -e '.valid == false' validation-results.json > /dev/null; then - echo "::error::Project validation failed" - exit 1 - fi - - - name: Run health check - id: health - run: | - php .mokostandards/api/plugin_health_check.php \ - --project-path . \ - --project-type generic \ - --json > health-results.json - - cat health-results.json - - - name: Collect metrics - id: metrics - run: | - php .mokostandards/api/plugin_metrics.php \ - --project-path . \ - --project-type generic \ - --json > metrics-results.json - - cat metrics-results.json - - - name: Check release readiness - id: readiness - run: | - php .mokostandards/api/plugin_readiness.php \ - --project-path . \ - --project-type generic \ - --json > readiness-results.json - - cat readiness-results.json - - - name: Upload validation artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: generic-validation-results - path: | - validation-results.json - health-results.json - metrics-results.json - readiness-results.json - retention-days: 30 diff --git a/templates/workflows/validate-joomla-project.yml b/templates/workflows/validate-joomla-project.yml deleted file mode 100644 index 7ee2f3e..0000000 --- a/templates/workflows/validate-joomla-project.yml +++ /dev/null @@ -1,146 +0,0 @@ -name: Validate Joomla Project - -on: - pull_request: - branches: [main, develop] - push: - branches: [main, develop] - workflow_dispatch: - schedule: - # Run daily at 6 AM UTC - - cron: '0 6 * * *' - -jobs: - validate: - name: Validate Joomla Project - runs-on: ubuntu-latest - - steps: - - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Checkout MokoStandards - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - repository: mokoshalb/MokoStandards - path: .mokostandards - ref: main - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - extensions: mbstring, xml, json, fileinfo, zip - - - name: Install MokoStandards dependencies - working-directory: .mokostandards - run: composer install --prefer-dist --no-progress --no-interaction - - - name: Run Joomla validation - id: validate - run: | - php .mokostandards/api/plugin_validate.php \ - --project-path . \ - --project-type joomla \ - --json > validation-results.json - - # Display results - cat validation-results.json - - # Check if valid - if jq -e '.valid == false' validation-results.json > /dev/null; then - echo "::error::Project validation failed" - exit 1 - fi - - - name: Run health check - id: health - run: | - php .mokostandards/api/plugin_health_check.php \ - --project-path . \ - --project-type joomla \ - --json > health-results.json - - cat health-results.json - - # Check health score - SCORE=$(jq -r '.score' health-results.json) - echo "Health Score: $SCORE/100" - - if [ "$SCORE" -lt 70 ]; then - echo "::warning::Health score is below 70" - fi - - - name: Collect metrics - id: metrics - run: | - php .mokostandards/api/plugin_metrics.php \ - --project-path . \ - --project-type joomla \ - --json > metrics-results.json - - cat metrics-results.json - - - name: Check release readiness - id: readiness - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') - run: | - php .mokostandards/api/plugin_readiness.php \ - --project-path . \ - --project-type joomla \ - --json > readiness-results.json - - cat readiness-results.json - - if jq -e '.ready == false' readiness-results.json > /dev/null; then - echo "::warning::Project is not ready for release" - jq -r '.blockers[]' readiness-results.json | while read blocker; do - echo "::warning::Blocker: $blocker" - done - fi - - - name: Upload validation artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: joomla-validation-results - path: | - validation-results.json - health-results.json - metrics-results.json - readiness-results.json - retention-days: 30 - - - name: Comment PR with results - if: github.event_name == 'pull_request' - env: - TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }} - PR_NUMBER: ${{ github.event.pull_request.number }} - REPO: ${{ github.repository }} - run: | - set -euo pipefail - COMMENT="## 📊 Joomla Project Validation Results\n\n" - - if [ -f "validation-results.json" ]; then - VALID=$(php -r 'echo json_decode(file_get_contents("validation-results.json"),true)["valid"]?"VALID":"INVALID";') - SCORE=$(php -r 'echo json_decode(file_get_contents("validation-results.json"),true)["score"]??0;') - if [ "$VALID" = "VALID" ]; then - COMMENT+="### Validation: ✅ VALID\n" - else - COMMENT+="### Validation: ❌ INVALID\n" - fi - COMMENT+="**Score:** ${SCORE}/100\n\n" - fi - - if [ -f "health-results.json" ]; then - HEALTHY=$(php -r 'echo json_decode(file_get_contents("health-results.json"),true)["healthy"]?"HEALTHY":"UNHEALTHY";') - HSCORE=$(php -r 'echo json_decode(file_get_contents("health-results.json"),true)["score"]??0;') - COMMENT+="### Health Check: ${HEALTHY}\n**Score:** ${HSCORE}/100\n\n" - fi - - API_BASE="${GITHUB_API_URL:-${GITEA_API_URL:-https://api.github.com}}" - curl -sf -X POST \ - -H "Authorization: token ${TOKEN}" \ - -H "Content-Type: application/json" \ - "${API_BASE}/repos/${REPO}/issues/${PR_NUMBER}/comments" \ - -d "$(printf '{"body":"%s"}' "$(echo -e "$COMMENT")")" > /dev/null || true diff --git a/templates/workflows/validate-mobile-project.yml b/templates/workflows/validate-mobile-project.yml deleted file mode 100644 index d157628..0000000 --- a/templates/workflows/validate-mobile-project.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Validate Mobile Project - -on: - pull_request: - branches: [main, develop] - push: - branches: [main, develop] - workflow_dispatch: - -jobs: - validate: - name: Validate Mobile Project - runs-on: ubuntu-latest - - steps: - - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Checkout MokoStandards - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - repository: mokoshalb/MokoStandards - path: .mokostandards - ref: main - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - extensions: mbstring, json, fileinfo - - - name: Install MokoStandards dependencies - working-directory: .mokostandards - run: composer install --prefer-dist --no-progress - - - name: Run Mobile validation - id: validate - run: | - php .mokostandards/api/plugin_validate.php \ - --project-path . \ - --project-type mobile \ - --json > validation-results.json - - cat validation-results.json - - if jq -e '.valid == false' validation-results.json > /dev/null; then - echo "::error::Project validation failed" - exit 1 - fi - - - name: Run health check - id: health - run: | - php .mokostandards/api/plugin_health_check.php \ - --project-path . \ - --project-type mobile \ - --json > health-results.json - - cat health-results.json - - - name: Collect metrics - id: metrics - run: | - php .mokostandards/api/plugin_metrics.php \ - --project-path . \ - --project-type mobile \ - --json > metrics-results.json - - cat metrics-results.json - - - name: Check release readiness - id: readiness - run: | - php .mokostandards/api/plugin_readiness.php \ - --project-path . \ - --project-type mobile \ - --json > readiness-results.json - - cat readiness-results.json - - - name: Upload validation artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: mobile-validation-results - path: | - validation-results.json - health-results.json - metrics-results.json - readiness-results.json - retention-days: 30 diff --git a/templates/workflows/validate-nodejs-project.yml b/templates/workflows/validate-nodejs-project.yml deleted file mode 100644 index 1d178dd..0000000 --- a/templates/workflows/validate-nodejs-project.yml +++ /dev/null @@ -1,147 +0,0 @@ -name: Validate Node.js Project - -on: - pull_request: - branches: [main, develop] - push: - branches: [main, develop] - workflow_dispatch: - schedule: - - cron: '0 6 * * *' - -jobs: - validate: - name: Validate Node.js Project - runs-on: ubuntu-latest - - steps: - - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Checkout MokoStandards - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - repository: mokoshalb/MokoStandards - path: .mokostandards - ref: main - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - cache: 'npm' - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - extensions: mbstring, json, fileinfo - - - name: Install MokoStandards dependencies - working-directory: .mokostandards - run: composer install --prefer-dist --no-progress - - - name: Run Node.js validation - id: validate - run: | - php .mokostandards/api/plugin_validate.php \ - --project-path . \ - --project-type nodejs \ - --json > validation-results.json - - cat validation-results.json - - if jq -e '.valid == false' validation-results.json > /dev/null; then - echo "::error::Project validation failed" - exit 1 - fi - - - name: Run health check - id: health - run: | - php .mokostandards/api/plugin_health_check.php \ - --project-path . \ - --project-type nodejs \ - --json > health-results.json - - cat health-results.json - - SCORE=$(jq -r '.score' health-results.json) - echo "Health Score: $SCORE/100" - - if [ "$SCORE" -lt 70 ]; then - echo "::warning::Health score is below 70" - fi - - - name: Collect metrics - id: metrics - run: | - php .mokostandards/api/plugin_metrics.php \ - --project-path . \ - --project-type nodejs \ - --json > metrics-results.json - - cat metrics-results.json - - # Extract and display key metrics - echo "### Key Metrics" - jq -r '.metrics | to_entries[] | "\(.key): \(.value)"' metrics-results.json || true - - - name: Check release readiness - id: readiness - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') - run: | - php .mokostandards/api/plugin_readiness.php \ - --project-path . \ - --project-type nodejs \ - --json > readiness-results.json - - cat readiness-results.json - - if jq -e '.ready == false' readiness-results.json > /dev/null; then - echo "::warning::Project is not ready for release" - jq -r '.blockers[]' readiness-results.json | while read blocker; do - echo "::warning::Blocker: $blocker" - done - fi - - - name: Check for security vulnerabilities - continue-on-error: true - run: | - if [ -f package-lock.json ]; then - npm audit --json > npm-audit.json || true - cat npm-audit.json - fi - - - name: Upload validation artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: nodejs-validation-results - path: | - validation-results.json - health-results.json - metrics-results.json - readiness-results.json - npm-audit.json - retention-days: 30 - - - name: Create validation summary - if: always() - run: | - echo "## Node.js Project Validation Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ -f validation-results.json ]; then - VALID=$(jq -r '.valid' validation-results.json) - SCORE=$(jq -r '.score' validation-results.json) - echo "**Validation:** $([[ $VALID == true ]] && echo '✅ VALID' || echo '❌ INVALID')" >> $GITHUB_STEP_SUMMARY - echo "**Score:** $SCORE/100" >> $GITHUB_STEP_SUMMARY - fi - - if [ -f health-results.json ]; then - HEALTHY=$(jq -r '.healthy' health-results.json) - HEALTH_SCORE=$(jq -r '.score' health-results.json) - echo "**Health:** $([[ $HEALTHY == true ]] && echo '✅ HEALTHY' || echo '⚠️ UNHEALTHY')" >> $GITHUB_STEP_SUMMARY - echo "**Health Score:** $HEALTH_SCORE/100" >> $GITHUB_STEP_SUMMARY - fi diff --git a/templates/workflows/validate-python-project.yml b/templates/workflows/validate-python-project.yml deleted file mode 100644 index 6374423..0000000 --- a/templates/workflows/validate-python-project.yml +++ /dev/null @@ -1,111 +0,0 @@ -name: Validate Python Project - -on: - pull_request: - branches: [main, develop] - push: - branches: [main, develop] - workflow_dispatch: - schedule: - - cron: '0 6 * * *' - -jobs: - validate: - name: Validate Python Project - runs-on: ubuntu-latest - - steps: - - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Checkout MokoStandards - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - repository: mokoshalb/MokoStandards - path: .mokostandards - ref: main - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - cache: 'pip' - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - extensions: mbstring, json, fileinfo - - - name: Install MokoStandards dependencies - working-directory: .mokostandards - run: composer install --prefer-dist --no-progress - - - name: Run Python validation - id: validate - run: | - php .mokostandards/api/plugin_validate.php \ - --project-path . \ - --project-type python \ - --json > validation-results.json - - cat validation-results.json - - if jq -e '.valid == false' validation-results.json > /dev/null; then - echo "::error::Project validation failed" - exit 1 - fi - - - name: Run health check - id: health - run: | - php .mokostandards/api/plugin_health_check.php \ - --project-path . \ - --project-type python \ - --json > health-results.json - - cat health-results.json - - SCORE=$(jq -r '.score' health-results.json) - echo "Health Score: $SCORE/100" - - - name: Collect metrics - id: metrics - run: | - php .mokostandards/api/plugin_metrics.php \ - --project-path . \ - --project-type python \ - --json > metrics-results.json - - cat metrics-results.json - - - name: Check release readiness - id: readiness - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') - run: | - php .mokostandards/api/plugin_readiness.php \ - --project-path . \ - --project-type python \ - --json > readiness-results.json - - cat readiness-results.json - - - name: Check for security vulnerabilities - continue-on-error: true - run: | - pip install safety - safety check --json > safety-report.json || true - cat safety-report.json - - - name: Upload validation artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: python-validation-results - path: | - validation-results.json - health-results.json - metrics-results.json - readiness-results.json - safety-report.json - retention-days: 30 diff --git a/templates/workflows/validate-terraform-project.yml b/templates/workflows/validate-terraform-project.yml deleted file mode 100644 index 1258e9e..0000000 --- a/templates/workflows/validate-terraform-project.yml +++ /dev/null @@ -1,106 +0,0 @@ -name: Validate Terraform Project - -on: - pull_request: - branches: [main, develop] - push: - branches: [main, develop] - workflow_dispatch: - -jobs: - validate: - name: Validate Terraform Project - runs-on: ubuntu-latest - - steps: - - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Checkout MokoStandards - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - repository: mokoshalb/MokoStandards - path: .mokostandards - ref: main - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: '1.6.0' - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - extensions: mbstring, json, fileinfo - - - name: Install MokoStandards dependencies - working-directory: .mokostandards - run: composer install --prefer-dist --no-progress - - - name: Run Terraform validation - id: validate - run: | - php .mokostandards/api/plugin_validate.php \ - --project-path . \ - --project-type terraform \ - --json > validation-results.json - - cat validation-results.json - - if jq -e '.valid == false' validation-results.json > /dev/null; then - echo "::error::Project validation failed" - exit 1 - fi - - - name: Run health check - id: health - run: | - php .mokostandards/api/plugin_health_check.php \ - --project-path . \ - --project-type terraform \ - --json > health-results.json - - cat health-results.json - - - name: Collect metrics - id: metrics - run: | - php .mokostandards/api/plugin_metrics.php \ - --project-path . \ - --project-type terraform \ - --json > metrics-results.json - - cat metrics-results.json - - - name: Check release readiness - id: readiness - run: | - php .mokostandards/api/plugin_readiness.php \ - --project-path . \ - --project-type terraform \ - --json > readiness-results.json - - cat readiness-results.json - - - name: Terraform fmt check - continue-on-error: true - run: terraform fmt -check -recursive - - - name: Terraform validate - continue-on-error: true - run: | - terraform init -backend=false - terraform validate - - - name: Upload validation artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: terraform-validation-results - path: | - validation-results.json - health-results.json - metrics-results.json - readiness-results.json - retention-days: 30 diff --git a/templates/workflows/validate-wordpress-project.yml b/templates/workflows/validate-wordpress-project.yml deleted file mode 100644 index 3c131f3..0000000 --- a/templates/workflows/validate-wordpress-project.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Validate WordPress Project - -on: - pull_request: - branches: [main, develop] - push: - branches: [main, develop] - workflow_dispatch: - schedule: - - cron: '0 6 * * *' - -jobs: - validate: - name: Validate WordPress Project - runs-on: ubuntu-latest - - steps: - - name: Checkout project - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Checkout MokoStandards - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - repository: mokoshalb/MokoStandards - path: .mokostandards - ref: main - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - extensions: mbstring, xml, json, fileinfo, zip, mysqli - - - name: Install MokoStandards dependencies - working-directory: .mokostandards - run: composer install --prefer-dist --no-progress - - - name: Run WordPress validation - id: validate - run: | - php .mokostandards/api/plugin_validate.php \ - --project-path . \ - --project-type wordpress \ - --json > validation-results.json - - cat validation-results.json - - if jq -e '.valid == false' validation-results.json > /dev/null; then - echo "::error::Project validation failed" - exit 1 - fi - - - name: Run health check - id: health - run: | - php .mokostandards/api/plugin_health_check.php \ - --project-path . \ - --project-type wordpress \ - --json > health-results.json - - cat health-results.json - - SCORE=$(jq -r '.score' health-results.json) - echo "Health Score: $SCORE/100" - - - name: Collect metrics - id: metrics - run: | - php .mokostandards/api/plugin_metrics.php \ - --project-path . \ - --project-type wordpress \ - --json > metrics-results.json - - cat metrics-results.json - - - name: Check release readiness - id: readiness - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master') - run: | - php .mokostandards/api/plugin_readiness.php \ - --project-path . \ - --project-type wordpress \ - --json > readiness-results.json - - cat readiness-results.json - - - name: WordPress Coding Standards - continue-on-error: true - run: | - composer global require wp-coding-standards/wpcs - phpcs --standard=WordPress --extensions=php ./ || true - - - name: Upload validation artifacts - if: always() - uses: actions/upload-artifact@v4 - with: - name: wordpress-validation-results - path: | - validation-results.json - health-results.json - metrics-results.json - readiness-results.json - retention-days: 30 diff --git a/validate/auto_detect_platform.php b/validate/auto_detect_platform.php index 59d975b..49d04af 100755 --- a/validate/auto_detect_platform.php +++ b/validate/auto_detect_platform.php @@ -64,7 +64,7 @@ class AutoDetectPlatform extends CLIApp { return [ 'repo-path:' => 'Path to repository to analyze (default: current directory)', - 'schema-dir:' => 'Path to schema definitions directory (default: api/definitions/default)', + 'schema-dir:' => 'Path to schema definitions directory (default: definitions/default)', 'output-dir:' => 'Directory for output reports (default: var/logs/validation)', ]; } diff --git a/validate/check_composer_deps.php b/validate/check_composer_deps.php index ac46ff0..16d3058 100644 --- a/validate/check_composer_deps.php +++ b/validate/check_composer_deps.php @@ -15,9 +15,9 @@ * BRIEF: Validate composer.json enterprise dependency across all governed repos * * USAGE - * php api/validate/check_composer_deps.php --repo MokoCRM # Single repo - * php api/validate/check_composer_deps.php --all # All repos - * php api/validate/check_composer_deps.php --all --json # JSON output + * php validate/check_composer_deps.php --repo MokoCRM # Single repo + * php validate/check_composer_deps.php --all # All repos + * php validate/check_composer_deps.php --all --json # JSON output */ declare(strict_types=1); diff --git a/validate/check_enterprise_readiness.php b/validate/check_enterprise_readiness.php index db04f1a..37e85db 100755 --- a/validate/check_enterprise_readiness.php +++ b/validate/check_enterprise_readiness.php @@ -139,18 +139,18 @@ class EnterpriseReadinessChecker extends CliFramework { $required = ['ApiClient', 'AuditLogger', 'Config', 'ErrorRecovery', 'MetricsCollector']; - // Enterprise libs may live in vendor/ (Composer install) or api/lib/Enterprise/ (MokoStandards itself). + // Enterprise libs may live in vendor/ (Composer install) or lib/Enterprise/ (MokoStandards itself). // A single vendor/ directory confirms the whole package is present — no need to check per-file. $vendorPkg = "{$path}/vendor/mokoconsulting-tech/enterprise"; $inVendor = is_dir($vendorPkg); foreach ($required as $library) { - $localFile = "{$path}/api/lib/Enterprise/{$library}.php"; + $localFile = "{$path}/lib/Enterprise/{$library}.php"; $found = $inVendor || file_exists($localFile); $this->addResult( "Enterprise library: {$library}", $found, - "Missing enterprise library (not in vendor/mokoconsulting-tech/enterprise or api/lib/Enterprise/)" + "Missing enterprise library (not in vendor/mokoconsulting-tech/enterprise or lib/Enterprise/)" ); } } diff --git a/validate/check_repo_health.php b/validate/check_repo_health.php index a8922f0..134e2e1 100755 --- a/validate/check_repo_health.php +++ b/validate/check_repo_health.php @@ -735,7 +735,7 @@ class RepoHealthChecker extends CliFramework if (!empty($existing[0]['number'])) { $num = (int) $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']]; + $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; if (($existing[0]['state'] ?? 'open') === 'closed') { $patch['state'] = 'open'; } @@ -749,7 +749,7 @@ class RepoHealthChecker extends CliFramework 'title' => $title, 'body' => $body, 'labels' => $labels, - 'assignees' => ['jmiller-moko'], + 'assignees' => ['jmiller'], ]); $issueNumber = $issue['number'] ?? 'unknown'; $this->log("✅ Created health issue #{$issueNumber} in {$repo}"); diff --git a/validate/check_version_consistency.php b/validate/check_version_consistency.php index a1fc587..ec84389 100755 --- a/validate/check_version_consistency.php +++ b/validate/check_version_consistency.php @@ -144,7 +144,7 @@ class CheckVersionConsistency extends CliFramework // ── Check PHP Enterprise library files ──────────────────────────────── $this->section('Checking PHP source files'); - $phpFiles = $this->findPhpFiles($path . '/api/lib/Enterprise'); + $phpFiles = $this->findPhpFiles($path . '/lib/Enterprise'); $phpTotal = count($phpFiles); foreach ($phpFiles as $i => $file) { diff --git a/validate/scan_drift.php b/validate/scan_drift.php index eb3f1d5..201aa4b 100755 --- a/validate/scan_drift.php +++ b/validate/scan_drift.php @@ -529,7 +529,7 @@ class DriftScanner extends CliFramework $body .= "1. **Option 1:** Run bulk sync to update all files automatically\n"; $body .= " ```bash\n"; $body .= " # From MokoStandards repository\n"; - $body .= " php api/automation/bulk_sync.php --repos=\"{$repo}\"\n"; + $body .= " php automation/bulk_sync.php --repos=\"{$repo}\"\n"; $body .= " ```\n\n"; $body .= "2. **Option 2:** If changes are intentional, update `.github/override.tf` to exclude files\n\n"; $body .= "3. **Option 3:** Manually update files to match templates\n\n"; @@ -550,7 +550,7 @@ class DriftScanner extends CliFramework if (!empty($existing) && isset($existing[0]['number'])) { $num = $existing[0]['number']; - $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller-moko']]; + $patch = ['title' => $title, 'body' => $body, 'assignees' => ['jmiller']]; if (($existing[0]['state'] ?? 'open') === 'closed') { $patch['state'] = 'open'; } @@ -564,7 +564,7 @@ class DriftScanner extends CliFramework 'title' => $title, 'body' => $body, 'labels' => $labels, - 'assignees' => ['jmiller-moko'], + 'assignees' => ['jmiller'], ]); $num = $issue['number'] ?? '?'; $this->log(" Created drift issue #{$num} in {$repo}"); diff --git a/wrappers/auto_detect_platform.php b/wrappers/auto_detect_platform.php index 9e94a59..b16dbfd 100644 --- a/wrappers/auto_detect_platform.php +++ b/wrappers/auto_detect_platform.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/auto_detect_platform.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/auto_detect_platform.php + * BRIEF: PHP wrapper for validate/auto_detect_platform.php */ declare(strict_types=1); const SCRIPT_NAME = 'auto_detect_platform'; -const SCRIPT_PATH = 'api/validate/auto_detect_platform.php'; +const SCRIPT_PATH = 'validate/auto_detect_platform.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/bulk_sync.php b/wrappers/bulk_sync.php index c15903a..e62d9e6 100644 --- a/wrappers/bulk_sync.php +++ b/wrappers/bulk_sync.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/bulk_sync.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/automation/bulk_sync.php + * BRIEF: PHP wrapper for automation/bulk_sync.php */ declare(strict_types=1); const SCRIPT_NAME = 'bulk_sync'; -const SCRIPT_PATH = 'api/automation/bulk_sync.php'; +const SCRIPT_PATH = 'automation/bulk_sync.php'; const SCRIPT_CATEGORY = 'automation'; /** diff --git a/wrappers/check_changelog.php b/wrappers/check_changelog.php index 3291fe1..efd221a 100644 --- a/wrappers/check_changelog.php +++ b/wrappers/check_changelog.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_changelog.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_changelog.php + * BRIEF: PHP wrapper for validate/check_changelog.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_changelog'; -const SCRIPT_PATH = 'api/validate/check_changelog.php'; +const SCRIPT_PATH = 'validate/check_changelog.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_dolibarr_module.php b/wrappers/check_dolibarr_module.php index ba74cb2..9431f3d 100644 --- a/wrappers/check_dolibarr_module.php +++ b/wrappers/check_dolibarr_module.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_dolibarr_module.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_dolibarr_module.php + * BRIEF: PHP wrapper for validate/check_dolibarr_module.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_dolibarr_module'; -const SCRIPT_PATH = 'api/validate/check_dolibarr_module.php'; +const SCRIPT_PATH = 'validate/check_dolibarr_module.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_enterprise_readiness.php b/wrappers/check_enterprise_readiness.php index 46176b5..b206383 100644 --- a/wrappers/check_enterprise_readiness.php +++ b/wrappers/check_enterprise_readiness.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_enterprise_readiness.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_enterprise_readiness.php + * BRIEF: PHP wrapper for validate/check_enterprise_readiness.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_enterprise_readiness'; -const SCRIPT_PATH = 'api/validate/check_enterprise_readiness.php'; +const SCRIPT_PATH = 'validate/check_enterprise_readiness.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_joomla_manifest.php b/wrappers/check_joomla_manifest.php index fa37d97..bc9e601 100644 --- a/wrappers/check_joomla_manifest.php +++ b/wrappers/check_joomla_manifest.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_joomla_manifest.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_joomla_manifest.php + * BRIEF: PHP wrapper for validate/check_joomla_manifest.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_joomla_manifest'; -const SCRIPT_PATH = 'api/validate/check_joomla_manifest.php'; +const SCRIPT_PATH = 'validate/check_joomla_manifest.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_language_structure.php b/wrappers/check_language_structure.php index 535c30c..dfdf329 100644 --- a/wrappers/check_language_structure.php +++ b/wrappers/check_language_structure.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_language_structure.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_language_structure.php + * BRIEF: PHP wrapper for validate/check_language_structure.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_language_structure'; -const SCRIPT_PATH = 'api/validate/check_language_structure.php'; +const SCRIPT_PATH = 'validate/check_language_structure.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_license_headers.php b/wrappers/check_license_headers.php index 0994081..54fd842 100644 --- a/wrappers/check_license_headers.php +++ b/wrappers/check_license_headers.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_license_headers.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_license_headers.php + * BRIEF: PHP wrapper for validate/check_license_headers.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_license_headers'; -const SCRIPT_PATH = 'api/validate/check_license_headers.php'; +const SCRIPT_PATH = 'validate/check_license_headers.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_no_secrets.php b/wrappers/check_no_secrets.php index 333fe24..edac90d 100644 --- a/wrappers/check_no_secrets.php +++ b/wrappers/check_no_secrets.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_no_secrets.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_no_secrets.php + * BRIEF: PHP wrapper for validate/check_no_secrets.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_no_secrets'; -const SCRIPT_PATH = 'api/validate/check_no_secrets.php'; +const SCRIPT_PATH = 'validate/check_no_secrets.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_paths.php b/wrappers/check_paths.php index 45496d0..7bad72e 100644 --- a/wrappers/check_paths.php +++ b/wrappers/check_paths.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_paths.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_paths.php + * BRIEF: PHP wrapper for validate/check_paths.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_paths'; -const SCRIPT_PATH = 'api/validate/check_paths.php'; +const SCRIPT_PATH = 'validate/check_paths.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_php_syntax.php b/wrappers/check_php_syntax.php index e6caf0d..3025501 100644 --- a/wrappers/check_php_syntax.php +++ b/wrappers/check_php_syntax.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_php_syntax.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_php_syntax.php + * BRIEF: PHP wrapper for validate/check_php_syntax.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_php_syntax'; -const SCRIPT_PATH = 'api/validate/check_php_syntax.php'; +const SCRIPT_PATH = 'validate/check_php_syntax.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_repo_health.php b/wrappers/check_repo_health.php index adef046..82a47d5 100644 --- a/wrappers/check_repo_health.php +++ b/wrappers/check_repo_health.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_repo_health.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_repo_health.php + * BRIEF: PHP wrapper for validate/check_repo_health.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_repo_health'; -const SCRIPT_PATH = 'api/validate/check_repo_health.php'; +const SCRIPT_PATH = 'validate/check_repo_health.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_structure.php b/wrappers/check_structure.php index 0fd7271..40552e6 100644 --- a/wrappers/check_structure.php +++ b/wrappers/check_structure.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_structure.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_structure.php + * BRIEF: PHP wrapper for validate/check_structure.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_structure'; -const SCRIPT_PATH = 'api/validate/check_structure.php'; +const SCRIPT_PATH = 'validate/check_structure.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_tabs.php b/wrappers/check_tabs.php index b6d5577..95a62ab 100644 --- a/wrappers/check_tabs.php +++ b/wrappers/check_tabs.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_tabs.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_tabs.php + * BRIEF: PHP wrapper for validate/check_tabs.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_tabs'; -const SCRIPT_PATH = 'api/validate/check_tabs.php'; +const SCRIPT_PATH = 'validate/check_tabs.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_version_consistency.php b/wrappers/check_version_consistency.php index ff0e327..f67f067 100644 --- a/wrappers/check_version_consistency.php +++ b/wrappers/check_version_consistency.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_version_consistency.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_version_consistency.php + * BRIEF: PHP wrapper for validate/check_version_consistency.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_version_consistency'; -const SCRIPT_PATH = 'api/validate/check_version_consistency.php'; +const SCRIPT_PATH = 'validate/check_version_consistency.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/check_xml_wellformed.php b/wrappers/check_xml_wellformed.php index ee6c4e5..53306b0 100644 --- a/wrappers/check_xml_wellformed.php +++ b/wrappers/check_xml_wellformed.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/check_xml_wellformed.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/check_xml_wellformed.php + * BRIEF: PHP wrapper for validate/check_xml_wellformed.php */ declare(strict_types=1); const SCRIPT_NAME = 'check_xml_wellformed'; -const SCRIPT_PATH = 'api/validate/check_xml_wellformed.php'; +const SCRIPT_PATH = 'validate/check_xml_wellformed.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/deploy_sftp.php b/wrappers/deploy_sftp.php index 44352de..b947bda 100644 --- a/wrappers/deploy_sftp.php +++ b/wrappers/deploy_sftp.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/deploy_sftp.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/deploy/deploy-sftp.php + * BRIEF: PHP wrapper for deploy/deploy-sftp.php */ declare(strict_types=1); const SCRIPT_NAME = 'deploy_sftp'; -const SCRIPT_PATH = 'api/deploy/deploy-sftp.php'; +const SCRIPT_PATH = 'deploy/deploy-sftp.php'; const SCRIPT_CATEGORY = 'deploy'; /** diff --git a/wrappers/fix_line_endings.php b/wrappers/fix_line_endings.php index ec8876b..5eac189 100644 --- a/wrappers/fix_line_endings.php +++ b/wrappers/fix_line_endings.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/fix_line_endings.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/fix/fix_line_endings.php + * BRIEF: PHP wrapper for fix/fix_line_endings.php */ declare(strict_types=1); const SCRIPT_NAME = 'fix_line_endings'; -const SCRIPT_PATH = 'api/fix/fix_line_endings.php'; +const SCRIPT_PATH = 'fix/fix_line_endings.php'; const SCRIPT_CATEGORY = 'fix'; /** diff --git a/wrappers/fix_permissions.php b/wrappers/fix_permissions.php index ec8109a..dc77868 100644 --- a/wrappers/fix_permissions.php +++ b/wrappers/fix_permissions.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/fix_permissions.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/fix/fix_permissions.php + * BRIEF: PHP wrapper for fix/fix_permissions.php */ declare(strict_types=1); const SCRIPT_NAME = 'fix_permissions'; -const SCRIPT_PATH = 'api/fix/fix_permissions.php'; +const SCRIPT_PATH = 'fix/fix_permissions.php'; const SCRIPT_CATEGORY = 'fix'; /** diff --git a/wrappers/fix_tabs.php b/wrappers/fix_tabs.php index 847ab5a..0864532 100644 --- a/wrappers/fix_tabs.php +++ b/wrappers/fix_tabs.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/fix_tabs.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/fix/fix_tabs.php + * BRIEF: PHP wrapper for fix/fix_tabs.php */ declare(strict_types=1); const SCRIPT_NAME = 'fix_tabs'; -const SCRIPT_PATH = 'api/fix/fix_tabs.php'; +const SCRIPT_PATH = 'fix/fix_tabs.php'; const SCRIPT_CATEGORY = 'fix'; /** diff --git a/wrappers/fix_trailing_spaces.php b/wrappers/fix_trailing_spaces.php index b3247af..aba8cb3 100644 --- a/wrappers/fix_trailing_spaces.php +++ b/wrappers/fix_trailing_spaces.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/fix_trailing_spaces.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/fix/fix_trailing_spaces.php + * BRIEF: PHP wrapper for fix/fix_trailing_spaces.php */ declare(strict_types=1); const SCRIPT_NAME = 'fix_trailing_spaces'; -const SCRIPT_PATH = 'api/fix/fix_trailing_spaces.php'; +const SCRIPT_PATH = 'fix/fix_trailing_spaces.php'; const SCRIPT_CATEGORY = 'fix'; /** diff --git a/wrappers/gen_wrappers.php b/wrappers/gen_wrappers.php index fb5458c..48a8645 100644 --- a/wrappers/gen_wrappers.php +++ b/wrappers/gen_wrappers.php @@ -12,7 +12,7 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/gen_wrappers.php * VERSION: 04.06.00 - * BRIEF: Generate PHP CLI wrapper scripts for every PHP script in api/ + * BRIEF: Generate PHP CLI wrapper scripts for every PHP script in the repo */ declare(strict_types=1); @@ -24,43 +24,43 @@ declare(strict_types=1); */ const SCRIPTS = [ // validate - 'auto_detect_platform' => ['api/validate/auto_detect_platform.php', 'validate'], - 'check_changelog' => ['api/validate/check_changelog.php', 'validate'], - 'check_dolibarr_module' => ['api/validate/check_dolibarr_module.php', 'validate'], - 'check_enterprise_readiness' => ['api/validate/check_enterprise_readiness.php', 'validate'], - 'check_joomla_manifest' => ['api/validate/check_joomla_manifest.php', 'validate'], - 'check_language_structure' => ['api/validate/check_language_structure.php', 'validate'], - 'check_license_headers' => ['api/validate/check_license_headers.php', 'validate'], - 'check_no_secrets' => ['api/validate/check_no_secrets.php', 'validate'], - 'check_paths' => ['api/validate/check_paths.php', 'validate'], - 'check_php_syntax' => ['api/validate/check_php_syntax.php', 'validate'], - 'check_repo_health' => ['api/validate/check_repo_health.php', 'validate'], - 'check_structure' => ['api/validate/check_structure.php', 'validate'], - 'check_tabs' => ['api/validate/check_tabs.php', 'validate'], - 'check_version_consistency' => ['api/validate/check_version_consistency.php', 'validate'], - 'check_xml_wellformed' => ['api/validate/check_xml_wellformed.php', 'validate'], - 'scan_drift' => ['api/validate/scan_drift.php', 'validate'], + 'auto_detect_platform' => ['validate/auto_detect_platform.php', 'validate'], + 'check_changelog' => ['validate/check_changelog.php', 'validate'], + 'check_dolibarr_module' => ['validate/check_dolibarr_module.php', 'validate'], + 'check_enterprise_readiness' => ['validate/check_enterprise_readiness.php', 'validate'], + 'check_joomla_manifest' => ['validate/check_joomla_manifest.php', 'validate'], + 'check_language_structure' => ['validate/check_language_structure.php', 'validate'], + 'check_license_headers' => ['validate/check_license_headers.php', 'validate'], + 'check_no_secrets' => ['validate/check_no_secrets.php', 'validate'], + 'check_paths' => ['validate/check_paths.php', 'validate'], + 'check_php_syntax' => ['validate/check_php_syntax.php', 'validate'], + 'check_repo_health' => ['validate/check_repo_health.php', 'validate'], + 'check_structure' => ['validate/check_structure.php', 'validate'], + 'check_tabs' => ['validate/check_tabs.php', 'validate'], + 'check_version_consistency' => ['validate/check_version_consistency.php', 'validate'], + 'check_xml_wellformed' => ['validate/check_xml_wellformed.php', 'validate'], + 'scan_drift' => ['validate/scan_drift.php', 'validate'], // automation - 'bulk_sync' => ['api/automation/bulk_sync.php', 'automation'], + 'bulk_sync' => ['automation/bulk_sync.php', 'automation'], // deploy - 'deploy_sftp' => ['api/deploy/deploy-sftp.php', 'deploy'], + 'deploy_sftp' => ['deploy/deploy-sftp.php', 'deploy'], // fix - 'fix_line_endings' => ['api/fix/fix_line_endings.php', 'fix'], - 'fix_permissions' => ['api/fix/fix_permissions.php', 'fix'], - 'fix_tabs' => ['api/fix/fix_tabs.php', 'fix'], - 'fix_trailing_spaces' => ['api/fix/fix_trailing_spaces.php', 'fix'], + 'fix_line_endings' => ['fix/fix_line_endings.php', 'fix'], + 'fix_permissions' => ['fix/fix_permissions.php', 'fix'], + 'fix_tabs' => ['fix/fix_tabs.php', 'fix'], + 'fix_trailing_spaces' => ['fix/fix_trailing_spaces.php', 'fix'], // maintenance - 'pin_action_shas' => ['api/maintenance/pin_action_shas.php', 'maintenance'], - 'setup_labels' => ['api/maintenance/setup_labels.php', 'maintenance'], - 'sync_dolibarr_readmes' => ['api/maintenance/sync_dolibarr_readmes.php', 'maintenance'], - 'update_sha_hashes' => ['api/maintenance/update_sha_hashes.php', 'maintenance'], - 'update_version_from_readme' => ['api/maintenance/update_version_from_readme.php', 'maintenance'], + 'pin_action_shas' => ['maintenance/pin_action_shas.php', 'maintenance'], + 'setup_labels' => ['maintenance/setup_labels.php', 'maintenance'], + 'sync_dolibarr_readmes' => ['maintenance/sync_dolibarr_readmes.php', 'maintenance'], + 'update_sha_hashes' => ['maintenance/update_sha_hashes.php', 'maintenance'], + 'update_version_from_readme' => ['maintenance/update_version_from_readme.php', 'maintenance'], // plugin - 'plugin_health_check' => ['api/plugin_health_check.php', 'plugin'], - 'plugin_list' => ['api/plugin_list.php', 'plugin'], - 'plugin_metrics' => ['api/plugin_metrics.php', 'plugin'], - 'plugin_readiness' => ['api/plugin_readiness.php', 'plugin'], - 'plugin_validate' => ['api/plugin_validate.php', 'plugin'], + 'plugin_health_check' => ['plugin_health_check.php', 'plugin'], + 'plugin_list' => ['plugin_list.php', 'plugin'], + 'plugin_metrics' => ['plugin_metrics.php', 'plugin'], + 'plugin_readiness' => ['plugin_readiness.php', 'plugin'], + 'plugin_validate' => ['plugin_validate.php', 'plugin'], ]; /** diff --git a/wrappers/pin_action_shas.php b/wrappers/pin_action_shas.php index ac524a3..59e85a7 100644 --- a/wrappers/pin_action_shas.php +++ b/wrappers/pin_action_shas.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/pin_action_shas.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/maintenance/pin_action_shas.php + * BRIEF: PHP wrapper for maintenance/pin_action_shas.php */ declare(strict_types=1); const SCRIPT_NAME = 'pin_action_shas'; -const SCRIPT_PATH = 'api/maintenance/pin_action_shas.php'; +const SCRIPT_PATH = 'maintenance/pin_action_shas.php'; const SCRIPT_CATEGORY = 'maintenance'; /** diff --git a/wrappers/plugin_health_check.php b/wrappers/plugin_health_check.php index 35b3006..c5f9b40 100644 --- a/wrappers/plugin_health_check.php +++ b/wrappers/plugin_health_check.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/plugin_health_check.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/plugin_health_check.php + * BRIEF: PHP wrapper for plugin_health_check.php */ declare(strict_types=1); const SCRIPT_NAME = 'plugin_health_check'; -const SCRIPT_PATH = 'api/plugin_health_check.php'; +const SCRIPT_PATH = 'plugin_health_check.php'; const SCRIPT_CATEGORY = 'plugin'; /** diff --git a/wrappers/plugin_list.php b/wrappers/plugin_list.php index c7b9224..cc33ed7 100644 --- a/wrappers/plugin_list.php +++ b/wrappers/plugin_list.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/plugin_list.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/plugin_list.php + * BRIEF: PHP wrapper for plugin_list.php */ declare(strict_types=1); const SCRIPT_NAME = 'plugin_list'; -const SCRIPT_PATH = 'api/plugin_list.php'; +const SCRIPT_PATH = 'plugin_list.php'; const SCRIPT_CATEGORY = 'plugin'; /** diff --git a/wrappers/plugin_metrics.php b/wrappers/plugin_metrics.php index c3e9f00..ddade56 100644 --- a/wrappers/plugin_metrics.php +++ b/wrappers/plugin_metrics.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/plugin_metrics.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/plugin_metrics.php + * BRIEF: PHP wrapper for plugin_metrics.php */ declare(strict_types=1); const SCRIPT_NAME = 'plugin_metrics'; -const SCRIPT_PATH = 'api/plugin_metrics.php'; +const SCRIPT_PATH = 'plugin_metrics.php'; const SCRIPT_CATEGORY = 'plugin'; /** diff --git a/wrappers/plugin_readiness.php b/wrappers/plugin_readiness.php index ec86d94..a1e8860 100644 --- a/wrappers/plugin_readiness.php +++ b/wrappers/plugin_readiness.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/plugin_readiness.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/plugin_readiness.php + * BRIEF: PHP wrapper for plugin_readiness.php */ declare(strict_types=1); const SCRIPT_NAME = 'plugin_readiness'; -const SCRIPT_PATH = 'api/plugin_readiness.php'; +const SCRIPT_PATH = 'plugin_readiness.php'; const SCRIPT_CATEGORY = 'plugin'; /** diff --git a/wrappers/plugin_validate.php b/wrappers/plugin_validate.php index 78f8b1c..4c2ed6d 100644 --- a/wrappers/plugin_validate.php +++ b/wrappers/plugin_validate.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/plugin_validate.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/plugin_validate.php + * BRIEF: PHP wrapper for plugin_validate.php */ declare(strict_types=1); const SCRIPT_NAME = 'plugin_validate'; -const SCRIPT_PATH = 'api/plugin_validate.php'; +const SCRIPT_PATH = 'plugin_validate.php'; const SCRIPT_CATEGORY = 'plugin'; /** diff --git a/wrappers/scan_drift.php b/wrappers/scan_drift.php index 22d01ec..8659a02 100644 --- a/wrappers/scan_drift.php +++ b/wrappers/scan_drift.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/scan_drift.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/validate/scan_drift.php + * BRIEF: PHP wrapper for validate/scan_drift.php */ declare(strict_types=1); const SCRIPT_NAME = 'scan_drift'; -const SCRIPT_PATH = 'api/validate/scan_drift.php'; +const SCRIPT_PATH = 'validate/scan_drift.php'; const SCRIPT_CATEGORY = 'validate'; /** diff --git a/wrappers/setup_labels.php b/wrappers/setup_labels.php index 29d165d..9a7c615 100644 --- a/wrappers/setup_labels.php +++ b/wrappers/setup_labels.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/setup_labels.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/maintenance/setup_labels.php + * BRIEF: PHP wrapper for maintenance/setup_labels.php */ declare(strict_types=1); const SCRIPT_NAME = 'setup_labels'; -const SCRIPT_PATH = 'api/maintenance/setup_labels.php'; +const SCRIPT_PATH = 'maintenance/setup_labels.php'; const SCRIPT_CATEGORY = 'maintenance'; /** diff --git a/wrappers/sync_dolibarr_readmes.php b/wrappers/sync_dolibarr_readmes.php index 7034d8e..6c14888 100644 --- a/wrappers/sync_dolibarr_readmes.php +++ b/wrappers/sync_dolibarr_readmes.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/sync_dolibarr_readmes.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/maintenance/sync_dolibarr_readmes.php + * BRIEF: PHP wrapper for maintenance/sync_dolibarr_readmes.php */ declare(strict_types=1); const SCRIPT_NAME = 'sync_dolibarr_readmes'; -const SCRIPT_PATH = 'api/maintenance/sync_dolibarr_readmes.php'; +const SCRIPT_PATH = 'maintenance/sync_dolibarr_readmes.php'; const SCRIPT_CATEGORY = 'maintenance'; /** diff --git a/wrappers/update_sha_hashes.php b/wrappers/update_sha_hashes.php index 3ccf813..09ccdfd 100644 --- a/wrappers/update_sha_hashes.php +++ b/wrappers/update_sha_hashes.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/update_sha_hashes.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/maintenance/update_sha_hashes.php + * BRIEF: PHP wrapper for maintenance/update_sha_hashes.php */ declare(strict_types=1); const SCRIPT_NAME = 'update_sha_hashes'; -const SCRIPT_PATH = 'api/maintenance/update_sha_hashes.php'; +const SCRIPT_PATH = 'maintenance/update_sha_hashes.php'; const SCRIPT_CATEGORY = 'maintenance'; /** diff --git a/wrappers/update_version_from_readme.php b/wrappers/update_version_from_readme.php index d9f28cf..639c45a 100644 --- a/wrappers/update_version_from_readme.php +++ b/wrappers/update_version_from_readme.php @@ -12,13 +12,13 @@ * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API * PATH: /wrappers/update_version_from_readme.php * VERSION: 04.06.00 - * BRIEF: PHP wrapper for api/maintenance/update_version_from_readme.php + * BRIEF: PHP wrapper for maintenance/update_version_from_readme.php */ declare(strict_types=1); const SCRIPT_NAME = 'update_version_from_readme'; -const SCRIPT_PATH = 'api/maintenance/update_version_from_readme.php'; +const SCRIPT_PATH = 'maintenance/update_version_from_readme.php'; const SCRIPT_CATEGORY = 'maintenance'; /**