From 395921282eaacf9d2e161ccd46e41bddce24e17f Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 9 May 2026 17:40:20 -0500 Subject: [PATCH] feat: add client platform type with detection and structure definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Client repos (WaaS) are managed Joomla sites — not extensions. They have src/ with site structure PLUS deployment configs (sftp-config, monitoring, sync scripts). Detection prioritizes client over joomla when deployment markers are present. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- definitions/default/client.tf | 350 ++++++++++++++---------------- validate/auto_detect_platform.php | 67 +++++- 2 files changed, 223 insertions(+), 194 deletions(-) diff --git a/definitions/default/client.tf b/definitions/default/client.tf index 8af0c94..76b8c68 100644 --- a/definitions/default/client.tf +++ b/definitions/default/client.tf @@ -1,6 +1,10 @@ /** - * Client Joomla Site Structure Definition - * Standard repository structure for client Joomla site projects + * Client Repository Structure Definition + * Standard repository structure for managed Joomla client sites (WaaS) + * + * This is NOT a Joomla extension — it's a full managed client site with + * deployment configs, monitoring, SFTP settings, and sync workflows. + * The src/ directory mirrors the Joomla site's public_html. * * Copyright (C) 2026 Moko Consulting * SPDX-License-Identifier: GPL-3.0-or-later @@ -11,231 +15,193 @@ locals { repository_structure = { metadata = { - name = "Client Joomla Site" - description = "Standard repository structure for client Joomla site projects (overrides, media, configuration)" + name = "Client Site" + description = "Managed Joomla client site — full site structure, not an extension" repository_type = "client" - platform = "mokowaas" - last_updated = "2026-05-04T00:00:00Z" + platform = "client" + last_updated = "2026-05-09T00:00:00Z" maintainer = "Moko Consulting" version = "01.00.00" schema_version = "1.0" - template_repo = "MokoConsulting/MokoStandards-Template-Client" } + detection_hints = [ + "scripts/sftp-config/", + "scripts/sync-dev-to-live.sh", + "monitoring/grafana/", + "src/administrator/", + "src/components/", + "src/plugins/", + "src/templates/", + "src/media/templates/site/mokoonyx/" + ] + root_files = [ { name = "README.md" - description = "Client project documentation" + extension = "md" + description = "Client site overview and deployment info" + required = true + always_overwrite = false + protected = true + }, + { + name = "CHANGELOG.md" + extension = "md" + description = "Release history" 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" + extension = "" + description = "GPL-3.0-or-later license file" required = true always_overwrite = true + template = "templates/docs/required/LICENSE" }, { name = "Makefile" - description = "Build automation" + extension = "" + description = "Build and deployment targets (includes minify)" required = true - always_overwrite = true + always_overwrite = false }, { name = "composer.json" - description = "PHP dependency management" + extension = "json" + description = "PHP dependencies" 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" + extension = "" + description = "Git ignore rules (must include *.min.css, *.min.js, TODO.md)" required = true always_overwrite = false - }, - { - name = "renovate.json" - description = "Renovate dependency management configuration" - required = true - always_overwrite = false - }, + } ] - // NOTE: Client sites do NOT have updates.xml — they are not installable extensions - - subdirectories = [ + directories = [ { - 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 - }, - { - name = "cascade-dev.yml" - description = "Forward-merge main to all open branches (dev, rc/*, beta/*, alpha/*) on push to main" - required = true - always_overwrite = true - }, - { - name = "gitleaks.yml" - description = "Secret scanning — detect leaked credentials, API keys, and tokens using Gitleaks" - required = true - always_overwrite = true - }, + name = "src" + path = "src" + description = "Joomla site public_html mirror — deployed via SFTP" + required = true + purpose = "Contains the full Joomla site directory structure" + subdirectories = [ + { name = "administrator", path = "src/administrator", description = "Joomla admin", required = true }, + { name = "components", path = "src/components", description = "Frontend components", required = true }, + { name = "plugins", path = "src/plugins", description = "Plugins", required = true }, + { name = "modules", path = "src/modules", description = "Modules", required = true }, + { name = "templates", path = "src/templates", description = "Templates", required = true }, + { name = "media", path = "src/media", description = "Media assets", required = true }, + { name = "images", path = "src/images", description = "Site images", required = false }, + { name = "language", path = "src/language", description = "Language files", required = false }, + { name = "libraries", path = "src/libraries", description = "Libraries", required = false }, + { name = "layouts", path = "src/layouts", description = "Layouts", required = false } ] }, - ] - - // 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" }, + { + name = "scripts" + path = "scripts" + description = "Deployment, sync, and monitoring scripts" + required = true + purpose = "Contains SFTP configs, sync scripts, and monitoring" + subdirectories = [ + { + name = "sftp-config" + path = "scripts/sftp-config" + description = "SFTP connection configs (dev + live)" + required = true + files = [ + { + name = "sftp-config.dev.json" + extension = "json" + description = "Dev server SFTP connection" + required = true + always_overwrite = false + }, + { + name = "sftp-config.rs.json" + extension = "json" + description = "Live/release server SFTP connection" + required = true + always_overwrite = false + } + ] + } + ] + }, + { + name = "monitoring" + path = "monitoring" + description = "Grafana dashboard templates" + required = true + purpose = "Contains Panopticon-style Grafana dashboard JSON" + subdirectories = [ + { + name = "grafana" + path = "monitoring/grafana" + description = "Grafana dashboard JSON templates" + required = true + files = [ + { + name = "client-joomla-dashboard.json" + extension = "json" + description = "Panopticon-style Grafana dashboard template" + required = true + always_overwrite = true + template = "templates/monitoring/client-joomla-dashboard.json" + } + ] + } + ] + }, + { + name = ".gitea" + path = ".gitea" + description = "Gitea configuration" + required = true + purpose = "Contains Gitea Actions workflows" + subdirectories = [ + { + name = "workflows" + path = ".gitea/workflows" + description = "Gitea Actions CI/CD workflows" + required = true + files = [ + { + name = "auto-release.yml" + extension = "yml" + description = "Auto-release on merge to main" + required = true + always_overwrite = true + }, + { + name = "deploy.yml" + extension = "yml" + description = "Deploy src/ to servers via SFTP" + required = true + always_overwrite = true + }, + { + name = "add-endpoint.yml" + extension = "yml" + description = "Add monitoring endpoint to sites.json" + required = true + always_overwrite = true + } + ] + } + ] + } ] } } + +output "client_structure" { + description = "Client site repository structure definition" + value = local.repository_structure +} diff --git a/validate/auto_detect_platform.php b/validate/auto_detect_platform.php index 256627d..bf1fa19 100755 --- a/validate/auto_detect_platform.php +++ b/validate/auto_detect_platform.php @@ -44,6 +44,7 @@ class AutoDetectPlatform extends CLIApp private PluginFactory $pluginFactory; private array $detectionResults = [ + 'client' => ['score' => 0, 'indicators' => []], 'joomla' => ['score' => 0, 'indicators' => []], 'dolibarr' => ['score' => 0, 'indicators' => []], 'nodejs' => ['score' => 0, 'indicators' => []], @@ -124,6 +125,9 @@ class AutoDetectPlatform extends CLIApp $this->log("Plugin system did not detect type, using legacy detection", 'WARNING'); // Run platform detection using legacy methods + // Client must run BEFORE Joomla — client repos contain Joomla dirs + // but are NOT Joomla extensions + $this->detectClient($repoPath); $this->detectJoomla($repoPath); $this->detectDolibarr($repoPath); $this->detectNodeJS($repoPath); @@ -166,6 +170,65 @@ class AutoDetectPlatform extends CLIApp return 0; } + /** + * Detect client site repository. + * Client repos have src/ with Joomla site structure PLUS deployment + * configs (sftp-config/, monitoring/). They are NOT Joomla extensions. + */ + private function detectClient(string $repoPath): void + { + $score = 0; + $indicators = []; + + // Strong indicators: deployment/monitoring configs + $clientMarkers = [ + 'scripts/sftp-config' => 30, + 'scripts/sftp-config/sftp-config.dev.json' => 10, + 'scripts/sftp-config/sftp-config.rs.json' => 10, + 'monitoring/grafana' => 20, + 'scripts/sync-dev-to-live.sh' => 15, + 'scripts/joomla-monitor.sh' => 10, + 'scripts/joomla-monitor.sites.conf' => 10, + ]; + + foreach ($clientMarkers as $path => $weight) { + $full = $repoPath . '/' . $path; + if (is_dir($full) || is_file($full)) { + $score += $weight; + $indicators[] = "Found: {$path} (+{$weight})"; + } + } + + // Site structure inside src/ (not at root — that would be a Joomla extension) + $siteDirs = ['src/administrator', 'src/components', 'src/plugins', 'src/templates', 'src/media']; + $siteDirCount = 0; + foreach ($siteDirs as $dir) { + if (is_dir($repoPath . '/' . $dir)) { + $siteDirCount++; + } + } + if ($siteDirCount >= 3) { + $score += 20; + $indicators[] = "Joomla site structure in src/ ({$siteDirCount}/5 dirs)"; + } + + // Negative: if there's a Joomla manifest XML in src/, it's an extension not a client + $manifests = glob($repoPath . '/src/*.xml'); + foreach ($manifests ?: [] as $xml) { + $content = @file_get_contents($xml); + if ($content && preg_match('/detectionResults['client'] = [ + 'score' => max(0, $score), + 'indicators' => $indicators, + ]; + } + private function detectJoomla(string $repoPath): void { $score = 0; @@ -639,8 +702,8 @@ class AutoDetectPlatform extends CLIApp $indicators[] = "Found interactive setup wizard"; } - // Check for .mokostandards platform declaration - $mokoFiles = ["{$repoPath}/.gitea/.mokostandards", "{$repoPath}/.github/.mokostandards"]; + // Check for .moko-platform platform declaration + $mokoFiles = ["{$repoPath}/.gitea/.moko-platform", "{$repoPath}/.github/.moko-platform"]; foreach ($mokoFiles as $mokoFile) { if (file_exists($mokoFile)) { $content = @file_get_contents($mokoFile);