Public Access
b73c1eba25
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Platform: mokoplatform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: mokoplatform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: mokoplatform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: mokoplatform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: mokoplatform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: mokoplatform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Platform: mokoplatform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Security Audit / Dependency Audit (pull_request) Successful in 8s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Failing after 11s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 21s
Generic: Project CI / Lint & Validate (pull_request) Failing after 32s
Platform: mokoplatform CI / Gate 1: Code Quality (pull_request) Failing after 1m31s
Scans source files to detect platform, name, version, element_name, package_type, language, entry_point, description, and license_spdx. Supports Joomla, Dolibarr, Go, MCP/Node, and generic platforms. Includes --diff and --update modes for comparing against and pushing to the Gitea manifest API. Warns on missing core fields. Also removes deprecated mcp/servers/mokowaas_api (consolidated to separate repo) and syncs dev branch changes.
160 lines
4.8 KiB
PHP
160 lines
4.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Checkpoint Manager - Manages checkpoints for recovery operations
|
|
*
|
|
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* FILE INFORMATION
|
|
* DEFGROUP: MokoPlatform.Enterprise.Checkpoint
|
|
* INGROUP: MokoPlatform.Enterprise
|
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
|
* PATH: /lib/Enterprise/CheckpointManager.php
|
|
* BRIEF: Checkpoint manager for resumable operations
|
|
*
|
|
* @package MokoPlatform\Enterprise
|
|
* @version 04.00.04
|
|
* @author mokoplatform Team
|
|
* @license GPL-3.0-or-later
|
|
*/
|
|
|
|
namespace MokoEnterprise;
|
|
|
|
use DateTime;
|
|
use DateTimeZone;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Manages checkpoints for recovery operations.
|
|
*
|
|
* Features:
|
|
* - Save/load checkpoint state
|
|
* - Automatic timestamp tracking
|
|
* - Checkpoint listing and cleanup
|
|
* - JSON-based state persistence
|
|
*
|
|
* Example:
|
|
* ```php
|
|
* $manager = new CheckpointManager('.checkpoints');
|
|
* $manager->saveCheckpoint('operation', ['step' => 1, 'data' => 'value']);
|
|
* $state = $manager->loadCheckpoint('operation');
|
|
* ```
|
|
*/
|
|
class CheckpointManager
|
|
{
|
|
private string $checkpointDir;
|
|
|
|
public const VERSION = '09.23.00';
|
|
|
|
/**
|
|
* Initialize checkpoint manager.
|
|
*
|
|
* @param string $checkpointDir Directory to store checkpoints
|
|
*/
|
|
public function __construct(string $checkpointDir = '.checkpoints')
|
|
{
|
|
$this->checkpointDir = $checkpointDir;
|
|
|
|
// Create checkpoint directory if it doesn't exist
|
|
if (!is_dir($this->checkpointDir)) {
|
|
if (!mkdir($this->checkpointDir, 0755, true) && !is_dir($this->checkpointDir)) {
|
|
throw new RecoveryError("Failed to create checkpoint directory: {$this->checkpointDir}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save a checkpoint.
|
|
*
|
|
* @param string $name Checkpoint name
|
|
* @param array<string, mixed> $state State to save
|
|
* @return string Path to checkpoint file
|
|
* @throws RecoveryError
|
|
*/
|
|
public function saveCheckpoint(string $name, array $state): string
|
|
{
|
|
$timestamp = (new DateTime('now', new DateTimeZone('UTC')))->format('Ymd_His');
|
|
$checkpointFile = "{$this->checkpointDir}/{$name}_{$timestamp}.json";
|
|
|
|
try {
|
|
$json = json_encode($state, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
|
|
file_put_contents($checkpointFile, $json, LOCK_EX);
|
|
error_log("Checkpoint saved: {$checkpointFile}");
|
|
return $checkpointFile;
|
|
} catch (Throwable $e) {
|
|
error_log("Failed to save checkpoint: {$e->getMessage()}");
|
|
throw new RecoveryError("Checkpoint save failed: {$e->getMessage()}");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load the most recent checkpoint for a name.
|
|
*
|
|
* @param string $name Checkpoint name
|
|
* @return array<string, mixed>|null Checkpoint state or null if not found
|
|
*/
|
|
public function loadCheckpoint(string $name): ?array
|
|
{
|
|
$checkpoints = glob("{$this->checkpointDir}/{$name}_*.json");
|
|
if ($checkpoints === false || empty($checkpoints)) {
|
|
return null;
|
|
}
|
|
|
|
// Sort by filename (which includes timestamp) to get latest
|
|
sort($checkpoints);
|
|
$latest = end($checkpoints);
|
|
|
|
try {
|
|
$json = file_get_contents($latest);
|
|
if ($json === false) {
|
|
error_log("Failed to read checkpoint: {$latest}");
|
|
return null;
|
|
}
|
|
|
|
$state = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
|
|
error_log("Checkpoint loaded: {$latest}");
|
|
return $state;
|
|
} catch (Throwable $e) {
|
|
error_log("Failed to load checkpoint: {$e->getMessage()}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List available checkpoints.
|
|
*
|
|
* @param string|null $name Filter by checkpoint name (optional)
|
|
* @return array<string> List of checkpoint file paths
|
|
*/
|
|
public function listCheckpoints(?string $name = null): array
|
|
{
|
|
$pattern = $name ? "{$this->checkpointDir}/{$name}_*.json" : "{$this->checkpointDir}/*.json";
|
|
$checkpoints = glob($pattern);
|
|
return $checkpoints !== false ? $checkpoints : [];
|
|
}
|
|
|
|
/**
|
|
* Clean up old checkpoints.
|
|
*
|
|
* @param string|null $name Filter by checkpoint name (optional)
|
|
* @param int $keepLatest Number of latest checkpoints to keep
|
|
*/
|
|
public function cleanupCheckpoints(?string $name = null, int $keepLatest = 5): void
|
|
{
|
|
$checkpoints = $this->listCheckpoints($name);
|
|
sort($checkpoints);
|
|
|
|
if (count($checkpoints) > $keepLatest) {
|
|
$toRemove = array_slice($checkpoints, 0, -$keepLatest);
|
|
foreach ($toRemove as $checkpoint) {
|
|
unlink($checkpoint);
|
|
error_log("Removed old checkpoint: {$checkpoint}");
|
|
}
|
|
}
|
|
}
|
|
}
|