refactor: Divi-Reiter und divi-check in die Divi-Integration verschoben; CoreIndependenceTest prüft auch Cli, ChangeLog, Pods und die Wurzeldateien, erkennt Alias-Importe und kennt nur noch Plugin.php als Ausnahme
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d0d3488df8
commit
7f9a087b8b
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\Divi\Cli;
|
||||
|
||||
use Closure;
|
||||
use LIT\MCP\Integrations\Divi\Release\CheckReport;
|
||||
use LIT\MCP\Integrations\Divi\Release\CheckReportStore;
|
||||
use LIT\MCP\Integrations\Divi\Release\CheckStep;
|
||||
use LIT\MCP\Integrations\Divi\Release\CompatibilityCheck;
|
||||
use LIT\MCP\Integrations\Divi\Release\CompatibilityMatrix;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviEnvironment;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviReference;
|
||||
use LIT\MCP\Integrations\Divi\Release\LineDiff;
|
||||
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||
use LIT\MCP\Integrations\Divi\Release\ReleaseResolver;
|
||||
use LIT\MCP\Integrations\Divi\Release\SourceFingerprint;
|
||||
use RuntimeException;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* wp lit-mcp divi-check (spec §7): the admin check from the command line, for a developer checking a new Divi
|
||||
* version. Never releases anything – a green version goes into DiviReleases::TESTED by hand. After the report it
|
||||
* prints the compatibility matrix (E3).
|
||||
*
|
||||
* Exit codes: 0 = report green and every feature usable; 1 = report red (or the check could not run, or
|
||||
* --update-reference refused); 2 = report green but a feature is not usable. The 2 is for a version that is
|
||||
* checked but not yet released (not in DiviReleases::TESTED, automatic release off or a reference missing): the
|
||||
* checks passed, yet LIT MCP would still refuse the affected tools. A script that only cares about "usable" tests
|
||||
* for ≠ 0. --update-reference keeps its own code: it runs before the new reference is part of any release.
|
||||
*/
|
||||
final class DiviCheckCommand
|
||||
{
|
||||
/** @var Closure(string): void */
|
||||
private Closure $out;
|
||||
|
||||
/**
|
||||
* @param Closure(): CompatibilityCheck $check
|
||||
* @param (Closure(string): void)|null $out null = WP_CLI::line().
|
||||
* @param (Closure(): ?string)|null $themeDir null = DiviEnvironment::themeDir().
|
||||
* @param string|null $expectedFile null = DiviReference::FILE.
|
||||
* @param (Closure(): ?string)|null $sourceDir null = DiviReference::sourceDir().
|
||||
* @param (Closure(): array<string, string>)|null $signatures "sig:<symbol>" => sha256 of every Divi call in the
|
||||
* running process; null = DiviReference::currentSignatures().
|
||||
* @param (Closure(): CompatibilityMatrix)|null $matrix null = the release gate's matrix, computed again.
|
||||
*/
|
||||
public function __construct(
|
||||
private Closure $check,
|
||||
private ?CheckReportStore $reports = null,
|
||||
?Closure $out = null,
|
||||
private ?Closure $themeDir = null,
|
||||
private ?string $expectedFile = null,
|
||||
private ?Closure $sourceDir = null,
|
||||
private ?Closure $signatures = null,
|
||||
private ?Closure $matrix = null
|
||||
) {
|
||||
$this->out = $out ?? static function (string $line): void {
|
||||
\WP_CLI::line($line);
|
||||
};
|
||||
}
|
||||
|
||||
/** @param Closure(): CompatibilityCheck $check */
|
||||
public static function register(Closure $check): void
|
||||
{
|
||||
if (defined('WP_CLI') && WP_CLI) {
|
||||
\WP_CLI::add_command('lit-mcp divi-check', new self($check));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft die installierte Divi-Version wie der Reiter „Divi“: Manifeste, Fingerabdrücke, Modulkatalog, Format
|
||||
* und Schreibtest auf einer Wegwerfseite. Gibt danach den Zustand je Fähigkeit aus und gibt nichts frei.
|
||||
* Exit-Code 0: Prüfung grün und alle Fähigkeiten nutzbar; 1: Prüfung rot; 2: Prüfung grün, aber mindestens
|
||||
* eine Fähigkeit nicht nutzbar (z. B. Version noch nicht in DiviReleases::TESTED).
|
||||
*
|
||||
* ## OPTIONS
|
||||
*
|
||||
* [--diff]
|
||||
* : Zeigt für jede geänderte nachgebaute Divi-Funktion den Unterschied zur Referenz.
|
||||
*
|
||||
* [--update-reference]
|
||||
* : Schreibt die Soll-Fingerabdrücke (Nachbauten und Signaturen der Divi-Aufrufe) und die Referenzquellen neu –
|
||||
* nur wenn alle übrigen Schritte grün sind.
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* wp --user=admin lit-mcp divi-check --diff
|
||||
*
|
||||
* @param list<string> $args
|
||||
* @param array<string, mixed> $assoc
|
||||
*/
|
||||
public function __invoke(array $args, array $assoc): void
|
||||
{
|
||||
$code = $this->run($assoc);
|
||||
if ($code !== 0) {
|
||||
\WP_CLI::halt($code);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $assoc */
|
||||
public function run(array $assoc): int
|
||||
{
|
||||
if (! current_user_can('manage_options')) {
|
||||
($this->out)('Bitte mit --user=<Administrator> ausführen: der Schreibtest legt eine Seite an.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$report = ($this->check)()->run(get_current_user_id());
|
||||
($this->reports ?? new CheckReportStore())->save($report);
|
||||
($this->out)(sprintf('Divi %s mit LIT MCP %s', $report->diviVersion ?: '?', $report->pluginVersion));
|
||||
foreach ($report->steps as $step) {
|
||||
($this->out)(($step->skipped ? '–' : ($step->green() ? '✓' : '✗')) . ' ' . $step->label());
|
||||
foreach ($step->problems as $problem) {
|
||||
($this->out)(' ' . $problem);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($assoc['diff'])) {
|
||||
$this->diff();
|
||||
}
|
||||
if (isset($assoc['update-reference'])) {
|
||||
$code = $this->updateReference($report);
|
||||
$this->printMatrix();
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
$unusable = $this->printMatrix();
|
||||
if (! $report->green()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return $unusable > 0 ? 2 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints one line per feature (✓ released, ✗ incompatible, – untested) with label and state, the problems
|
||||
* indented below.
|
||||
*
|
||||
* @return int The number of features that are not usable.
|
||||
*/
|
||||
private function printMatrix(): int
|
||||
{
|
||||
$matrix = $this->currentMatrix();
|
||||
($this->out)('Fähigkeiten:');
|
||||
foreach ($matrix->toArray() as $row) {
|
||||
($this->out)(sprintf(
|
||||
'%s %s – %s',
|
||||
match ($row['state']) {
|
||||
'released' => '✓',
|
||||
'incompatible' => '✗',
|
||||
default => '–',
|
||||
},
|
||||
$row['label'],
|
||||
self::stateLabel($row['state'], $row['source'])
|
||||
));
|
||||
foreach ($row['problems'] as $problem) {
|
||||
($this->out)(' ' . $problem);
|
||||
}
|
||||
}
|
||||
|
||||
return count($matrix->unusable());
|
||||
}
|
||||
|
||||
/** The check just ran, so the state is computed again instead of read from a cache older than the report. */
|
||||
private function currentMatrix(): CompatibilityMatrix
|
||||
{
|
||||
if ($this->matrix !== null) {
|
||||
return ($this->matrix)();
|
||||
}
|
||||
|
||||
delete_transient(ReleaseResolver::CACHE);
|
||||
ReleaseGate::forget();
|
||||
|
||||
return ReleaseGate::status()->matrix;
|
||||
}
|
||||
|
||||
private static function stateLabel(string $state, ?string $source): string
|
||||
{
|
||||
return match ($state) {
|
||||
'released' => 'freigegeben (' . ($source ?? '?') . ')',
|
||||
'incompatible' => 'inkompatibel',
|
||||
default => 'ungetestet',
|
||||
};
|
||||
}
|
||||
|
||||
private function diff(): void
|
||||
{
|
||||
$themeDir = ($this->themeDir ?? DiviEnvironment::themeDir(...))();
|
||||
$sourceDir = ($this->sourceDir ?? DiviReference::sourceDir(...))();
|
||||
$expected = DiviReference::expected($this->expectedFile);
|
||||
foreach (DiviAdapters::mirrors() as $mirror) {
|
||||
try {
|
||||
$print = SourceFingerprint::of(
|
||||
(string) $themeDir . '/' . $mirror->file,
|
||||
$mirror->function,
|
||||
$mirror->class
|
||||
);
|
||||
} catch (RuntimeException $e) {
|
||||
($this->out)(sprintf('%s: nicht lesbar (%s)', $mirror->id, $e->getMessage()));
|
||||
continue;
|
||||
}
|
||||
if (($expected[$mirror->id] ?? null) === $print->hash) {
|
||||
continue;
|
||||
}
|
||||
|
||||
($this->out)(sprintf(
|
||||
'%s: %s, Zeilen %d–%d',
|
||||
$mirror->id,
|
||||
$mirror->file,
|
||||
$print->startLine,
|
||||
$print->endLine
|
||||
));
|
||||
$reference = $sourceDir === null ? null : DiviReference::readSource($mirror->id, $sourceDir);
|
||||
if ($reference === null) {
|
||||
($this->out)(' keine Referenzquelle (nur in einer Entwicklungskopie mit tests/)');
|
||||
continue;
|
||||
}
|
||||
foreach (LineDiff::lines(explode("\n", rtrim($reference, "\n")), explode("\n", $print->source)) as $line) {
|
||||
($this->out)(' ' . $line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function updateReference(CheckReport $report): int
|
||||
{
|
||||
foreach ($report->steps as $step) {
|
||||
if ($step->key !== CheckStep::MIRRORS && ! $step->green()) {
|
||||
($this->out)('Referenz nicht aktualisiert: nicht nur Fingerabdrücke sind rot (siehe oben).');
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
$sourceDir = ($this->sourceDir ?? DiviReference::sourceDir(...))();
|
||||
if ($sourceDir === null) {
|
||||
($this->out)('Referenz nicht aktualisiert: nur in einer Entwicklungskopie mit tests/ möglich.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$themeDir = ($this->themeDir ?? DiviEnvironment::themeDir(...))();
|
||||
try {
|
||||
$prints = DiviReference::current((string) $themeDir);
|
||||
} catch (RuntimeException $e) {
|
||||
($this->out)(
|
||||
'Referenz nicht aktualisiert – eine nachgebaute Divi-Funktion ist nicht lesbar: ' . $e->getMessage()
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
$signatures = ($this->signatures ?? DiviReference::currentSignatures(...))();
|
||||
} catch (RuntimeException $e) {
|
||||
($this->out)(
|
||||
'Referenz nicht aktualisiert – die Signatur eines Divi-Aufrufs ist nicht lesbar: ' . $e->getMessage()
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
// Sources first, fingerprints.json last: the fingerprints decide the release gate, so a write that
|
||||
// fails before this last one leaves the gate exactly as it was.
|
||||
foreach ($prints as $id => $print) {
|
||||
DiviReference::writeSource($id, $print->source, $sourceDir);
|
||||
}
|
||||
DiviReference::writeExpected(
|
||||
array_map(static fn (SourceFingerprint $print): string => $print->hash, $prints) + $signatures,
|
||||
$this->expectedFile
|
||||
);
|
||||
} catch (RuntimeException $e) {
|
||||
($this->out)('Referenz nicht vollständig aktualisiert: ' . $e->getMessage());
|
||||
|
||||
return 1;
|
||||
}
|
||||
delete_transient(ReleaseResolver::CACHE);
|
||||
ReleaseGate::forget();
|
||||
($this->out)(sprintf(
|
||||
'Referenz aktualisiert (%d Fingerabdrücke, %d Signaturen). Smoke-Skripte laufen lassen, dann committen.',
|
||||
count($prints),
|
||||
count($signatures)
|
||||
));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user