feat: manifest checks, Divi compatibility from manifests, reference fingerprints

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-30 01:37:51 +02:00
co-authored by Claude Opus 5.5
parent 1a215fd7bf
commit 6b93df94c3
15 changed files with 1295 additions and 97 deletions
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Release;
use RuntimeException;
defined('ABSPATH') || exit;
/**
* The expected fingerprints of the mirrors (fingerprints.json, shipped with the plugin) and their reference sources
* (tests/Fixtures/divi/reference/<id>.txt, development only – not in the plugin package). Written by
* wp lit-mcp divi-check --update-reference.
*/
final class DiviReference
{
public const FILE = __DIR__ . '/fingerprints.json';
/** @return array<string, string> Mirror ID => sha256; [] when the file is missing or broken. */
public static function expected(?string $file = null): array
{
$file ??= self::FILE;
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local plugin file.
$raw = is_file($file) && is_readable($file) ? file_get_contents($file) : false;
$data = is_string($raw) ? json_decode($raw, true) : null;
if (! is_array($data)) {
return [];
}
$expected = [];
foreach ($data as $id => $hash) {
if (is_string($id) && is_string($hash)) {
$expected[$id] = $hash;
}
}
ksort($expected);
return $expected;
}
/**
* @param array<string, string> $hashes Mirror ID => sha256.
* @throws RuntimeException
*/
public static function writeExpected(array $hashes, ?string $file = null): void
{
ksort($hashes);
// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- pure PHP, no WordPress.
$json = json_encode($hashes, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- dev fixture.
if (! is_string($json) || file_put_contents($file ?? self::FILE, $json . "\n") === false) {
throw new RuntimeException('fingerprints.json not written');
}
}
/** tests/Fixtures/divi/reference of a development checkout; null in an installed plugin. */
public static function sourceDir(): ?string
{
$dir = dirname(__DIR__, 4) . '/tests/Fixtures/divi/reference';
return is_dir($dir) ? $dir : null;
}
public static function readSource(string $id, string $dir): ?string
{
$file = $dir . '/' . $id . '.txt';
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local dev fixture.
$source = is_file($file) ? file_get_contents($file) : false;
return is_string($source) ? $source : null;
}
/** @throws RuntimeException */
public static function writeSource(string $id, string $source, string $dir): void
{
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- dev fixture.
if (file_put_contents($dir . '/' . $id . '.txt', $source . "\n") === false) {
throw new RuntimeException(sprintf('reference source %s not written', $id));
}
}
/**
* Fingerprints of every mirror in the installed Divi.
*
* @return array<string, SourceFingerprint>
* @throws RuntimeException When a mirrored function cannot be read.
*/
public static function current(string $themeDir): array
{
$prints = [];
foreach (DiviAdapters::mirrors() as $mirror) {
$file = $themeDir . '/' . $mirror->file;
$prints[$mirror->id] = SourceFingerprint::of($file, $mirror->function, $mirror->class);
}
return $prints;
}
}