#!/usr/bin/env php * * This file is part of a Moko Consulting project. * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: MokoPlatform.Scripts.Maintenance * INGROUP: MokoPlatform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /maintenance/update_sha_hashes.php * BRIEF: Update SHA-256 hashes in script registry */ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; use MokoEnterprise\CliFramework; class UpdateShaHashesCli extends CliFramework { private const REGISTRY_PATH = '.script-registry.json'; private array $changes = []; protected function configure(): void { $this->setDescription('Update SHA-256 hashes for all scripts in the registry'); } protected function run(): int { $this->logForced("SHA-256 Hash Update Tool"); $this->logForced(str_repeat("=", 50)); if ($this->dryRun) { $this->logForced("Mode: DRY RUN (no changes will be made)"); } // Load registry $registry = $this->loadRegistry(); // Update hashes $updatedRegistry = $this->updateHashes($registry); // Save if not dry run and there are changes if (!$this->dryRun && !empty($this->changes)) { $this->saveRegistry($updatedRegistry); $this->logForced("\nRegistry updated successfully"); } elseif (empty($this->changes)) { $this->logForced("\nNo changes needed - all hashes are current"); } else { $this->logForced("\nDry run complete - changes detected but not applied"); } return 0; } private function loadRegistry(): array { if (!file_exists(self::REGISTRY_PATH)) { throw new \Exception("Registry file not found: " . self::REGISTRY_PATH); } $content = file_get_contents(self::REGISTRY_PATH); $registry = json_decode($content, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new \Exception("Failed to parse registry JSON: " . json_last_error_msg()); } $this->logVerbose("Registry loaded: " . count($registry['scripts']) . " scripts tracked"); return $registry; } private function updateHashes(array $registry): array { $this->logVerbose("\nChecking scripts for changes...\n"); foreach ($registry['scripts'] as $index => &$script) { $path = $script['path']; if (!file_exists($path)) { $this->logForced("Skipping missing file: {$path}"); continue; } // Calculate current hash $currentHash = hash_file('sha256', $path); $currentSize = filesize($path); // Check if changed if ($currentHash !== $script['sha256']) { $this->changes[] = [ 'path' => $path, 'old_hash' => $script['sha256'], 'new_hash' => $currentHash, ]; $this->logForced("Hash updated: {$path}"); if ($this->verbose) { $this->logVerbose(" Old: {$script['sha256']}"); $this->logVerbose(" New: {$currentHash}"); } // Update in registry $script['sha256'] = $currentHash; $script['size_bytes'] = $currentSize; } else { $this->logVerbose("No change: {$path}"); } } // Update metadata timestamp if there are changes if (!empty($this->changes)) { $microtime = microtime(true); $dt = \DateTime::createFromFormat('U.u', sprintf('%.6f', $microtime), new \DateTimeZone('UTC')); if ($dt === false) { throw new \Exception("Failed to create DateTime from microtime"); } $registry['metadata']['generated_at'] = $dt->format('Y-m-d\TH:i:s.u\Z'); } $this->logForced("\nSummary:"); $this->logForced(" Total scripts: " . count($registry['scripts'])); $this->logForced(" Changed: " . count($this->changes)); return $registry; } private function saveRegistry(array $registry): void { $json = json_encode($registry, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); if ($json === false) { throw new \Exception("Failed to encode registry JSON: " . json_last_error_msg()); } if (file_put_contents(self::REGISTRY_PATH, $json) === false) { throw new \Exception("Failed to write registry file"); } $this->logVerbose("Registry saved: " . self::REGISTRY_PATH); } private function logForced(string $message): void { echo $message . "\n"; } private function logVerbose(string $message): void { if ($this->verbose) { echo $message . "\n"; } } } $app = new UpdateShaHashesCli(); exit($app->execute());