Public Access
4009d68a7a
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Universal: PR Check / Secret Scan (pull_request) Successful in 15s
Generic: Project CI / Lint & Validate (pull_request) Successful in 57s
Platform: mokocli CI / Gate 1: Code Quality (pull_request) Successful in 2m4s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 35s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 28m16s
Generic: Project CI / Tests (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: mokocli CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: mokocli CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: mokocli CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
The repo was renamed mokoplatform -> mokocli; this rewrites every stale
reference across the tree (case-sensitive):
MokoPlatform -> MokoCLI (DEFGROUP/INGROUP doc tags)
mokoplatform -> mokocli (repo URLs, /opt & /tmp paths, clone URLs,
EXCLUDE lists, XML xmlns + <root> namespace)
moko-platform -> moko-cli (marker files)
XML namespace URIs and ManifestParser::NAMESPACE_URI are changed in
lockstep so local manifest-vs-parser validation stays consistent. The
external standards.mokoconsulting.tech namespace endpoint must be updated
to match separately (tracked in #336).
Refs #336
Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
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: MokoCLI.Enterprise.Checkpoint
|
|
* INGROUP: MokoCLI.Enterprise
|
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
|
* PATH: /lib/Enterprise/CheckpointManager.php
|
|
* BRIEF: Checkpoint manager for resumable operations
|
|
*
|
|
* @package MokoCLI\Enterprise
|
|
* @version 04.00.04
|
|
* @author mokocli Team
|
|
* @license GPL-3.0-or-later
|
|
*/
|
|
|
|
namespace MokoCli;
|
|
|
|
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}");
|
|
}
|
|
}
|
|
}
|
|
}
|