Public Access
feat(workflow-sync): add --phase health drift detection + --repo-filter [#350] #358
@@ -5,7 +5,7 @@
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: MokoGIT.Workflow
|
||||
# INGROUP: MokoCLI.Automation
|
||||
# VERSION: 01.00.00
|
||||
# VERSION: 09.45.01
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
|
||||
@@ -13,6 +13,10 @@ BRIEF: Release changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- `workflow_sync.php`: `--phase health` — read-only drift detection that flags repos missing their synced `.mokogit/workflows/`, with a non-zero exit plus optional critical ntfy push (`--alert-ntfy`) and auto-filed Git issue (`--alert-issue`).
|
||||
- `workflow_sync.php`: `--repo-filter` to scope sync/health to specific repos.
|
||||
|
||||
## [09.42.00] --- 2026-07-04
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -6,7 +6,7 @@ DEFGROUP: MokoCLI.Root
|
||||
INGROUP: MokoCLI
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
PATH: /README.md
|
||||
VERSION: 09.45.00
|
||||
VERSION: 09.45.01
|
||||
BRIEF: Project overview and documentation
|
||||
-->
|
||||
|
||||
|
||||
@@ -185,6 +185,7 @@ const COMMAND_MAP = [
|
||||
// Bulk operations
|
||||
'bulk:push-workflow' => 'cli/bulk_workflow_push.php',
|
||||
'bulk:trigger' => 'cli/bulk_workflow_trigger.php',
|
||||
'workflow:sync' => 'cli/workflow_sync.php',
|
||||
'bulk:sync-rulesets' => 'cli/sync_rulesets.php',
|
||||
'bulk:push-files' => 'automation/push_files.php',
|
||||
'bulk:push-manifest' => 'automation/push_manifest_xml.php',
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/branch_rename.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Rename a git branch via Git API (create new, update PR, delete old)
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/bulk_workflow_push.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Push a workflow file to all governed repos via the Git Contents API
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/bulk_workflow_trigger.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Trigger a workflow across multiple repos at once
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/client_dashboard.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Generate unified client dashboard HTML
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/client_inventory.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Discover and list all client-waas repos with their server configuration status
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/client_provision.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Provision a new client environment end-to-end
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/grafana_dashboard.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Manage Grafana dashboards via API
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/joomla_build.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Build a Joomla extension ZIP from manifest — all types supported
|
||||
* NOTE: Called by pre-release and auto-release workflows.
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/joomla_metadata_validate.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Validate MokoGIT repo metadata against Joomla extension manifest XML
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/manifest_detect.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Auto-detect manifest fields from source files and optionally push to API
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/manifest_integrity.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Cross-check manifest API fields against repo contents across the org
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/manifest_licensing.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Ensure licensing tags (updateservers, dlid) in Joomla extension manifests
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/manifest_read.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Read repo metadata from Git manifest API, auto-detect the rest
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/platform_detect.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Auto-detect repository platform type and optionally update manifest
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/release_cascade.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Cascade release zip to all lower stability channels
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/release_publish.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Publish a release and create copies for all lesser stability streams.
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/scaffold_client.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Scaffold a new client-waas repo from Template-Client with pre-configured settings
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/updates_xml_sync.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Sync updates.xml to target branches via Git API
|
||||
* NOTE: Called by pre-release and auto-release workflows after updates.xml
|
||||
* is modified on the current branch. Pushes the file to other branches
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/version_auto_bump.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Auto patch-bump, set stability suffix, and commit — single CLI replacing inline workflow bash
|
||||
*/
|
||||
|
||||
|
||||
@@ -389,7 +389,7 @@ class VersionBumpCli extends CliFramework
|
||||
/**
|
||||
* Scan git release tags for the highest version across all channels.
|
||||
*
|
||||
* Checks release names like "MokoSuiteClient (VERSION: 09.45.00)" in
|
||||
* Checks release names like "MokoSuiteClient (VERSION: 09.45.01)" in
|
||||
* git tags (stable, release-candidate, development, etc.) to find the
|
||||
* highest version that has been released on any channel.
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/version_check.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Validate version consistency across README, manifests, and sub-packages
|
||||
*/
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/wiki_sync.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Sync select wiki pages from mokocli to all template repos
|
||||
*/
|
||||
|
||||
|
||||
+314
-4
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/workflow_sync.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Sync workflows from Generic → platform templates → live repos based on manifest.platform
|
||||
*/
|
||||
|
||||
@@ -59,6 +59,13 @@ class WorkflowSyncCli extends CliFramework
|
||||
/** Prefix for custom workflows preserved during orphan cleanup. */
|
||||
private const CUSTOM_PREFIX = 'custom-';
|
||||
|
||||
/** Default ntfy endpoint for critical drift alerts (matches notify.yml / gitleaks.yml). */
|
||||
private const DEFAULT_NTFY_URL = 'https://ntfy.mokoconsulting.tech';
|
||||
private const DEFAULT_NTFY_TOPIC = 'mokogit-ops';
|
||||
|
||||
/** Marker in auto-filed health issues, used for idempotency. */
|
||||
private const HEALTH_ISSUE_MARKER = '[workflow-health]';
|
||||
|
||||
private int $updated = 0;
|
||||
private int $created = 0;
|
||||
private int $skipped = 0;
|
||||
@@ -72,9 +79,16 @@ class WorkflowSyncCli extends CliFramework
|
||||
$this->addArgument('--token', 'Git API token', '');
|
||||
$this->addArgument('--org', 'Target organization', '');
|
||||
$this->addArgument('--branch', 'Target branch (default: main)', 'main');
|
||||
$this->addArgument('--phase', 'Phase to run: all, templates, repos (default: all)', 'all');
|
||||
$this->addArgument('--phase', 'Phase to run: all, templates, repos, health (default: all)', 'all');
|
||||
$this->addArgument('--platform-filter', 'Only sync repos matching this platform', '');
|
||||
$this->addArgument('--repo-filter', 'Only sync/check these repos (comma-separated names)', '');
|
||||
$this->addArgument('--delete-orphans', 'Delete workflows not in template (preserves custom-* and custom/)', false);
|
||||
|
||||
// Health-check (--phase health) options — read-only unless an alert flag is set.
|
||||
$this->addArgument('--alert-ntfy', 'On drift, send a critical ntfy push notification', false);
|
||||
$this->addArgument('--ntfy-url', 'ntfy server URL', self::DEFAULT_NTFY_URL);
|
||||
$this->addArgument('--ntfy-topic', 'ntfy topic', self::DEFAULT_NTFY_TOPIC);
|
||||
$this->addArgument('--alert-issue', 'On drift, open/update a Git issue on each affected repo', false);
|
||||
}
|
||||
|
||||
protected function run(): int
|
||||
@@ -96,11 +110,16 @@ class WorkflowSyncCli extends CliFramework
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!in_array($phase, ['all', 'templates', 'repos'], true)) {
|
||||
$this->log('ERROR', "--phase must be one of: all, templates, repos (got: {$phase})");
|
||||
if (!in_array($phase, ['all', 'templates', 'repos', 'health'], true)) {
|
||||
$this->log('ERROR', "--phase must be one of: all, templates, repos, health (got: {$phase})");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Health-check is a standalone, read-only phase — it never syncs.
|
||||
if ($phase === 'health') {
|
||||
return $this->runHealthCheck($gitUrl, $token, $org, $branch, $platformFilter);
|
||||
}
|
||||
|
||||
$this->log('INFO', "Workflow Sync — org: {$org}, branch: {$branch}, phase: {$phase}");
|
||||
|
||||
if ($platformFilter !== '') {
|
||||
@@ -240,6 +259,12 @@ class WorkflowSyncCli extends CliFramework
|
||||
$this->log('INFO', '=== Phase 2: Sync Platform Templates → Live Repos ===');
|
||||
echo "\n";
|
||||
|
||||
$repoFilter = $this->parseRepoFilter();
|
||||
|
||||
if ($repoFilter !== []) {
|
||||
$this->log('INFO', 'Repo filter: ' . implode(', ', $repoFilter));
|
||||
}
|
||||
|
||||
$repos = $this->fetchOrgRepos($gitUrl, $token, $org);
|
||||
|
||||
if ($repos === null) {
|
||||
@@ -263,6 +288,11 @@ class WorkflowSyncCli extends CliFramework
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply repo filter
|
||||
if ($repoFilter !== [] && !in_array($repoName, $repoFilter, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read manifest.platform
|
||||
$platform = $this->getRepoPlatform($gitUrl, $token, $org, $repoName, $branch);
|
||||
|
||||
@@ -692,6 +722,286 @@ class WorkflowSyncCli extends CliFramework
|
||||
return $repos;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Phase: health — read-only drift detection + alerting
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Parse --repo-filter into a list of repo names (empty ⇒ no filter).
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private function parseRepoFilter(): array
|
||||
{
|
||||
$raw = (string) $this->getArgument('--repo-filter');
|
||||
|
||||
return array_values(array_filter(array_map('trim', explode(',', $raw)), fn($n) => $n !== ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Health-check phase: verify every live repo still has the synced workflows its
|
||||
* platform template provides, and (optionally) alert on drift. Fails loudly
|
||||
* (non-zero exit) when a repo is missing its canonical set, so a scheduled job,
|
||||
* an ntfy push, and/or a Git issue surface silent loss instead of it rotting.
|
||||
*/
|
||||
private function runHealthCheck(
|
||||
string $gitUrl,
|
||||
string $token,
|
||||
string $org,
|
||||
string $branch,
|
||||
string $platformFilter
|
||||
): int {
|
||||
$alertNtfy = (bool) $this->getArgument('--alert-ntfy');
|
||||
$alertIssue = (bool) $this->getArgument('--alert-issue');
|
||||
$ntfyUrl = rtrim((string) $this->getArgument('--ntfy-url'), '/');
|
||||
$ntfyTopic = (string) $this->getArgument('--ntfy-topic');
|
||||
$repoFilter = $this->parseRepoFilter();
|
||||
|
||||
$this->log('INFO', "Workflow Health-Check — org: {$org}, branch: {$branch}");
|
||||
|
||||
if ($platformFilter !== '') {
|
||||
$this->log('INFO', "Platform filter: {$platformFilter}");
|
||||
}
|
||||
if ($repoFilter !== []) {
|
||||
$this->log('INFO', 'Repo filter: ' . implode(', ', $repoFilter));
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
$repos = $this->fetchOrgRepos($gitUrl, $token, $org);
|
||||
|
||||
if ($repos === null) {
|
||||
return self::EXIT_FAILURE;
|
||||
}
|
||||
|
||||
$this->log('INFO', 'Scanning ' . count($repos) . " repo(s) in \"{$org}\".");
|
||||
echo "\n";
|
||||
|
||||
$templateCache = [];
|
||||
$unhealthy = [];
|
||||
$checked = 0;
|
||||
|
||||
foreach ($repos as $repoFullName) {
|
||||
[, $repoName] = explode('/', $repoFullName, 2);
|
||||
|
||||
if (str_starts_with($repoName, 'Template-')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$platform = $this->getRepoPlatform($gitUrl, $token, $org, $repoName, $branch);
|
||||
|
||||
if ($platformFilter !== '' && $platform !== $platformFilter) {
|
||||
continue;
|
||||
}
|
||||
if ($repoFilter !== [] && !in_array($repoName, $repoFilter, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// A repo that has no such branch is out of scope for this run, not unhealthy.
|
||||
if (!$this->branchExists($gitUrl, $token, $org, $repoName, $branch)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$templateRepo = self::PLATFORM_TEMPLATES[$platform] ?? self::DEFAULT_TEMPLATE;
|
||||
|
||||
// Expected set = the template's workflows (cached), minus platform excludes.
|
||||
// Do NOT fall back to Generic on an unreachable template — that assesses against
|
||||
// the wrong baseline. Record null (missing) distinctly.
|
||||
if (!array_key_exists($templateRepo, $templateCache)) {
|
||||
$tpl = $this->listWorkflows($gitUrl, $token, $org, $templateRepo, $branch);
|
||||
$templateCache[$templateRepo] = $tpl === null
|
||||
? null
|
||||
: array_map(fn($w) => $w['name'], $tpl);
|
||||
}
|
||||
|
||||
if ($templateCache[$templateRepo] === null) {
|
||||
$this->log('WARN', "Template '{$templateRepo}' for platform '{$platform}' is unreachable"
|
||||
. " — cannot assess {$repoName}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
$expected = array_values(array_diff(
|
||||
$templateCache[$templateRepo],
|
||||
self::PLATFORM_EXCLUDES[$platform] ?? []
|
||||
));
|
||||
|
||||
if (count($expected) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$repoList = $this->listWorkflows($gitUrl, $token, $org, $repoName, $branch);
|
||||
$actual = array_map(fn($w) => $w['name'], $repoList ?? []);
|
||||
|
||||
$missing = $this->evaluateRepoHealth($expected, $actual);
|
||||
$checked++;
|
||||
|
||||
if (count($missing) > 0) {
|
||||
$unhealthy[$repoName] = [
|
||||
'full' => $repoFullName,
|
||||
'platform' => $platform,
|
||||
'expected' => count($expected),
|
||||
'present' => count(array_intersect($expected, $actual)),
|
||||
'missing' => $missing,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
if (count($unhealthy) === 0) {
|
||||
$this->success("All {$checked} in-scope repo(s) have their synced workflows on '{$branch}'.");
|
||||
return self::EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
|
||||
foreach ($unhealthy as $repo => $info) {
|
||||
$rows[] = [$repo, $info['platform'], "{$info['present']}/{$info['expected']}", (string) count($info['missing'])];
|
||||
}
|
||||
|
||||
$this->log('ERROR', count($unhealthy) . " of {$checked} repo(s) are MISSING synced workflows on '{$branch}':");
|
||||
$this->table(['Repo', 'Platform', 'Present', 'Missing'], $rows);
|
||||
|
||||
if ($alertNtfy) {
|
||||
$this->sendNtfyAlert($ntfyUrl, $ntfyTopic, $org, $branch, $unhealthy);
|
||||
}
|
||||
if ($alertIssue) {
|
||||
foreach ($unhealthy as $repo => $info) {
|
||||
$this->openHealthIssue($gitUrl, $token, $org, $repo, $branch, $info);
|
||||
}
|
||||
}
|
||||
|
||||
return self::EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Health verdict policy — given the workflows a repo SHOULD have (its platform
|
||||
* template's set, minus excludes) and what it ACTUALLY has, return the missing
|
||||
* filenames. Non-empty ⇒ unhealthy. Extra/custom files are ignored.
|
||||
*
|
||||
* @param string[] $expected
|
||||
* @param string[] $actual
|
||||
* @return string[]
|
||||
*/
|
||||
private function evaluateRepoHealth(array $expected, array $actual): array
|
||||
{
|
||||
return array_values(array_diff($expected, $actual));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when the given branch exists in the repo.
|
||||
*/
|
||||
private function branchExists(
|
||||
string $gitUrl,
|
||||
string $token,
|
||||
string $org,
|
||||
string $repoName,
|
||||
string $branch
|
||||
): bool {
|
||||
$response = $this->apiRequest($gitUrl, $token, 'GET', "/api/v1/repos/{$org}/{$repoName}/branches/{$branch}");
|
||||
|
||||
return $response['code'] === 200;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a critical ntfy push notification about drift (matches notify.yml headers).
|
||||
*/
|
||||
private function sendNtfyAlert(
|
||||
string $ntfyUrl,
|
||||
string $topic,
|
||||
string $org,
|
||||
string $branch,
|
||||
array $unhealthy
|
||||
): void {
|
||||
if ($ntfyUrl === '' || $topic === '') {
|
||||
$this->warn('ntfy alert requested but --ntfy-url/--ntfy-topic is empty; skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
$count = count($unhealthy);
|
||||
$names = implode(', ', array_keys($unhealthy));
|
||||
$title = "Workflow drift: {$count} repo(s) missing workflows";
|
||||
$body = "Org {$org} ({$branch}): {$count} repo(s) missing synced .mokogit/workflows/.\n{$names}";
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->log('INFO', "[DRY RUN] Would send critical ntfy to {$ntfyUrl}/{$topic}: {$title}");
|
||||
return;
|
||||
}
|
||||
|
||||
$ch = curl_init("{$ntfyUrl}/{$topic}");
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
"Title: {$title}",
|
||||
'Priority: urgent',
|
||||
'Tags: rotating_light,warning',
|
||||
]);
|
||||
curl_exec($ch);
|
||||
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($code >= 200 && $code < 300) {
|
||||
$this->success("Sent critical ntfy alert to {$ntfyUrl}/{$topic}.");
|
||||
} else {
|
||||
$this->warn("ntfy alert failed (HTTP {$code}).");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a Git issue on an affected repo describing the missing workflows.
|
||||
* Idempotent: skips if an open issue carrying the marker already exists.
|
||||
*/
|
||||
private function openHealthIssue(
|
||||
string $gitUrl,
|
||||
string $token,
|
||||
string $org,
|
||||
string $repoName,
|
||||
string $branch,
|
||||
array $info
|
||||
): void {
|
||||
$existing = $this->apiRequest(
|
||||
$gitUrl, $token, 'GET',
|
||||
"/api/v1/repos/{$org}/{$repoName}/issues?state=open&type=issues&limit=50"
|
||||
);
|
||||
|
||||
if ($existing['code'] === 200) {
|
||||
$issues = json_decode($existing['body'], true) ?: [];
|
||||
|
||||
foreach ($issues as $iss) {
|
||||
if (str_contains((string) ($iss['title'] ?? ''), self::HEALTH_ISSUE_MARKER)) {
|
||||
$this->log('INFO', "{$repoName}: health issue already open (#{$iss['number']}), skipping.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$missingList = implode("\n", array_map(fn($f) => "- `{$f}`", $info['missing']));
|
||||
$title = self::HEALTH_ISSUE_MARKER . " Missing synced workflows on {$branch}";
|
||||
$body = 'Automated health-check found this repository is missing '
|
||||
. count($info['missing']) . " workflow(s) that its platform template "
|
||||
. "(`{$info['platform']}`) provides in `.mokogit/workflows/` on `{$branch}`:\n\n"
|
||||
. $missingList
|
||||
. "\n\nRestore with:\n\n```\nphp bin/moko workflow:sync --org {$org} --branch {$branch} --token \$TOKEN\n```"
|
||||
. "\n\n<sub>Filed by workflow_sync.php --phase health.</sub>";
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->log('INFO', "[DRY RUN] Would open health issue on {$org}/{$repoName}.");
|
||||
return;
|
||||
}
|
||||
|
||||
$payload = json_encode(['title' => $title, 'body' => $body]);
|
||||
|
||||
$resp = $this->apiRequest($gitUrl, $token, 'POST', "/api/v1/repos/{$org}/{$repoName}/issues", $payload);
|
||||
|
||||
if ($resp['code'] === 201) {
|
||||
$data = json_decode($resp['body'], true);
|
||||
$this->success("Opened health issue {$org}/{$repoName}#" . ($data['number'] ?? '?') . '.');
|
||||
} else {
|
||||
$this->warn("Failed to open issue on {$org}/{$repoName} (HTTP {$resp['code']}).");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an HTTP request to the Git API.
|
||||
*/
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoCLI
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/backup-before-deploy.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Snapshot Joomla directories before deployment for rollback capability
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoCLI
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/deploy-dolibarr.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Deploy Dolibarr module files to a remote server via SFTP/rsync
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoCLI
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/health-check.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Post-deploy health check — verify a Joomla site is responding correctly
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoCLI
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/rollback-joomla.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Rollback a Joomla deployment by restoring from a pre-deploy snapshot
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoCLI
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/sync-joomla.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Sync Joomla site directories between two servers via rsync over SSH
|
||||
*/
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
DEFGROUP: dolibarr-api-mcp.Documentation
|
||||
INGROUP: dolibarr-api-mcp
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/dolibarr-api-mcp
|
||||
VERSION: 09.45.00
|
||||
VERSION: 09.45.01
|
||||
PATH: ./CONTRIBUTING.md
|
||||
BRIEF: Contribution guidelines for the project
|
||||
-->
|
||||
|
||||
@@ -10,7 +10,7 @@ DEFGROUP: dolibarr-api-mcp.Documentation
|
||||
INGROUP: dolibarr-api-mcp
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/dolibarr-api-mcp
|
||||
PATH: /SECURITY.md
|
||||
VERSION: 09.45.00
|
||||
VERSION: 09.45.01
|
||||
BRIEF: Security vulnerability reporting and handling policy
|
||||
-->
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
DEFGROUP:
|
||||
INGROUP: Project.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCli-Template-Generic
|
||||
VERSION: 09.45.00
|
||||
VERSION: 09.45.01
|
||||
PATH: ./CONTRIBUTING.md
|
||||
BRIEF: Contribution guidelines for the project
|
||||
-->
|
||||
|
||||
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
|
||||
INGROUP: [PROJECT_NAME].Documentation
|
||||
REPO: [REPOSITORY_URL]
|
||||
PATH: /SECURITY.md
|
||||
VERSION: 09.45.00
|
||||
VERSION: 09.45.01
|
||||
BRIEF: Security vulnerability reporting and handling policy
|
||||
-->
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class VersionBumpTest extends TestCase
|
||||
{
|
||||
file_put_contents(
|
||||
"{$this->tmpDir}/README.md",
|
||||
"<!-- VERSION: 09.45.00 -->\nSome content\n"
|
||||
"<!-- VERSION: 09.45.01 -->\nSome content\n"
|
||||
);
|
||||
|
||||
$this->execute();
|
||||
|
||||
@@ -34,7 +34,7 @@ class VersionReadTest extends TestCase
|
||||
{
|
||||
file_put_contents(
|
||||
"{$this->tmpDir}/README.md",
|
||||
"# Test\n<!-- VERSION: 09.45.00 -->\n"
|
||||
"# Test\n<!-- VERSION: 09.45.01 -->\n"
|
||||
);
|
||||
|
||||
$this->assertSame('02.03.04', trim($this->runScript()));
|
||||
@@ -68,7 +68,7 @@ class VersionReadTest extends TestCase
|
||||
{
|
||||
file_put_contents(
|
||||
"{$this->tmpDir}/README.md",
|
||||
"<!-- VERSION: 09.45.00 -->\n"
|
||||
"<!-- VERSION: 09.45.01 -->\n"
|
||||
);
|
||||
mkdir("{$this->tmpDir}/src", 0755, true);
|
||||
file_put_contents(
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoCLI
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /validate/check_file_integrity.php
|
||||
* VERSION: 09.45.00
|
||||
* VERSION: 09.45.01
|
||||
* BRIEF: Compare deployed files on a remote server against the local repository to detect drift
|
||||
*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user