b4400a68e5
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
344 lines
11 KiB
PHP
344 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* WP-CLI commands for the HivePress family integration.
|
|
*
|
|
* Registered as a namespace under `wp wpdo hivepress`:
|
|
*
|
|
* wp wpdo hivepress detect — list installed HivePress family addons
|
|
* wp wpdo hivepress score — 8-dimension suitability report
|
|
* wp wpdo hivepress doctor — health probe each active adapter
|
|
* wp wpdo hivepress migrate — flip an addon's modules to cutover state
|
|
* wp wpdo hivepress rollback — reset an addon's modules to idle
|
|
* wp wpdo hivepress benchmark — run sample query timings (no-op when adapters inactive)
|
|
*
|
|
* Per Karpathy guideline: the CLI is a thin wrapper over the introspection
|
|
* helpers built in Sprints 1-3 (detector / suitability-scorer / benchmark).
|
|
* No new business logic lives here.
|
|
*
|
|
* @package WP_Data_Optimizer
|
|
* @since 3.0.0
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
if ( ! class_exists( 'TMDO_CLI_HivePress' ) ) {
|
|
|
|
/**
|
|
* `wp wpdo hivepress <subcommand>` namespace handler.
|
|
*/
|
|
class TMDO_CLI_HivePress {
|
|
|
|
/**
|
|
* List installed HivePress family addons + adapter binding state.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* [--format=<format>]
|
|
* : Output format (table|json|csv|yaml). Default: table.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* wp wpdo hivepress detect
|
|
* wp wpdo hivepress detect --format=json
|
|
*
|
|
* @param array $args Positional args (unused).
|
|
* @param array $assoc_args Flag args.
|
|
*
|
|
* @subcommand detect
|
|
*/
|
|
public function detect( $args, $assoc_args ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
|
unset( $args );
|
|
$format = (string) ( $assoc_args['format'] ?? 'table' );
|
|
$detected = class_exists( 'TMDO_HivePress_Detector' )
|
|
? TMDO_HivePress_Detector::detect()
|
|
: array();
|
|
$catalog = class_exists( 'TMDO_HivePress_Detector' )
|
|
? TMDO_HivePress_Detector::catalog()
|
|
: array();
|
|
$bound = class_exists( 'TMDO_HivePress_Bootstrap' )
|
|
? array_keys( TMDO_HivePress_Bootstrap::adapters() )
|
|
: array();
|
|
|
|
$rows = array();
|
|
foreach ( $catalog as $slug => $name ) {
|
|
$rows[] = array(
|
|
'slug' => $slug,
|
|
'name' => $name,
|
|
'detected' => isset( $detected[ $slug ] ) ? 'yes' : 'no',
|
|
'version' => $detected[ $slug ] ?? '-',
|
|
'bound' => in_array( $slug, $bound, true ) ? 'yes' : 'no',
|
|
);
|
|
}
|
|
|
|
if ( ! class_exists( 'WP_CLI\\Utils' ) || 'table' === $format ) {
|
|
WP_CLI::log( '=== HivePress family detection ===' );
|
|
WP_CLI::log( '' );
|
|
WP_CLI::log( str_pad( 'slug', 26 ) . str_pad( 'detected', 12 ) . str_pad( 'version', 10 ) . 'bound' );
|
|
WP_CLI::log( str_repeat( '-', 60 ) );
|
|
foreach ( $rows as $row ) {
|
|
WP_CLI::log( str_pad( $row['slug'], 26 ) . str_pad( $row['detected'], 12 ) . str_pad( $row['version'], 10 ) . $row['bound'] );
|
|
}
|
|
WP_CLI::log( '' );
|
|
WP_CLI::success( sprintf( 'Detected %d / %d addons', count( $detected ), count( $catalog ) ) );
|
|
return;
|
|
}
|
|
|
|
\WP_CLI\Utils\format_items( $format, $rows, array( 'slug', 'name', 'detected', 'version', 'bound' ) );
|
|
}
|
|
|
|
/**
|
|
* Show 8-dimension suitability report (per-adapter + aggregate).
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* [--addon=<slug>]
|
|
* : Filter to a single addon slug.
|
|
*
|
|
* [--format=<format>]
|
|
* : Output format (table|json|yaml). Default: table.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* wp wpdo hivepress score
|
|
* wp wpdo hivepress score --addon=hivepress-reviews
|
|
* wp wpdo hivepress score --format=json
|
|
*
|
|
* @param array $args Positional args (unused).
|
|
* @param array $assoc_args Flag args.
|
|
*
|
|
* @subcommand score
|
|
*/
|
|
public function score( $args, $assoc_args ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
|
unset( $args );
|
|
if ( ! class_exists( 'TMDO_HivePress_Suitability_Scorer' ) ) {
|
|
WP_CLI::error( 'TMDO_HivePress_Suitability_Scorer not loaded.' );
|
|
}
|
|
$report = TMDO_HivePress_Suitability_Scorer::report();
|
|
$addon = isset( $assoc_args['addon'] ) ? (string) $assoc_args['addon'] : '';
|
|
$format = (string) ( $assoc_args['format'] ?? 'table' );
|
|
|
|
if ( '' !== $addon ) {
|
|
if ( ! isset( $report['adapters'][ $addon ] ) ) {
|
|
WP_CLI::error( sprintf( 'Addon "%s" not active.', $addon ) );
|
|
}
|
|
$score = $report['adapters'][ $addon ];
|
|
if ( 'json' === $format || 'yaml' === $format ) {
|
|
\WP_CLI\Utils\format_items( $format, array( $score ), array_keys( $score ) );
|
|
return;
|
|
}
|
|
WP_CLI::log( sprintf( '=== Score: %s ===', $addon ) );
|
|
foreach ( $score as $k => $v ) {
|
|
WP_CLI::log( sprintf( ' %s: %s', str_pad( $k, 22 ), $v ) );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( 'json' === $format || 'yaml' === $format ) {
|
|
\WP_CLI\Utils\format_items( $format, array( $report ), array( 'aggregate', 'adapter_count', 'per_dimension', 'adapters' ) );
|
|
return;
|
|
}
|
|
|
|
WP_CLI::log( '=== HivePress 8-D Suitability Report ===' );
|
|
WP_CLI::log( '' );
|
|
WP_CLI::log( sprintf( 'Active adapters: %d', $report['adapter_count'] ) );
|
|
WP_CLI::log( sprintf( 'Aggregate score: %s / 10', $report['aggregate'] ) );
|
|
WP_CLI::log( '' );
|
|
WP_CLI::log( 'Per-dimension averages:' );
|
|
foreach ( $report['per_dimension'] as $dim => $avg ) {
|
|
WP_CLI::log( sprintf( ' %s: %s', str_pad( $dim, 22 ), $avg ) );
|
|
}
|
|
WP_CLI::log( '' );
|
|
WP_CLI::log( 'Per-adapter aggregate:' );
|
|
foreach ( $report['adapters'] as $slug => $score ) {
|
|
WP_CLI::log( sprintf( ' %s → %s', str_pad( $slug, 26 ), $score['aggregate'] ?? '-' ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run doctor probe for each active adapter.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* [--addon=<slug>]
|
|
* : Limit probe to a single addon.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* wp wpdo hivepress doctor
|
|
* wp wpdo hivepress doctor --addon=hivepress-favorites
|
|
*
|
|
* @param array $args Positional args (unused).
|
|
* @param array $assoc_args Flag args.
|
|
*
|
|
* @subcommand doctor
|
|
*/
|
|
public function doctor( $args, $assoc_args ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
|
unset( $args );
|
|
$adapters = class_exists( 'TMDO_HivePress_Bootstrap' )
|
|
? TMDO_HivePress_Bootstrap::adapters()
|
|
: array();
|
|
$only = isset( $assoc_args['addon'] ) ? (string) $assoc_args['addon'] : '';
|
|
|
|
$failed = 0;
|
|
$total = 0;
|
|
foreach ( $adapters as $slug => $adapter ) {
|
|
if ( '' !== $only && $slug !== $only ) {
|
|
continue;
|
|
}
|
|
if ( ! $adapter instanceof TMDO_HivePress_Adapter ) {
|
|
continue;
|
|
}
|
|
++$total;
|
|
$probe = $adapter->doctor_check();
|
|
if ( ! empty( $probe['ok'] ) ) {
|
|
WP_CLI::log( sprintf( '✓ %s: %s', $slug, $probe['message'] ?? 'OK' ) );
|
|
} else {
|
|
WP_CLI::log( sprintf( '✗ %s: %s', $slug, $probe['message'] ?? 'FAIL' ) );
|
|
++$failed;
|
|
}
|
|
}
|
|
|
|
if ( 0 === $total ) {
|
|
WP_CLI::warning( 'No adapters active. Install HivePress first.' );
|
|
return;
|
|
}
|
|
if ( $failed > 0 ) {
|
|
WP_CLI::error( sprintf( 'Doctor: %d / %d adapters failed.', $failed, $total ) );
|
|
}
|
|
WP_CLI::success( sprintf( 'Doctor: %d / %d adapters healthy.', $total, $total ) );
|
|
}
|
|
|
|
/**
|
|
* Flip an addon's modules to a target FSM state (default: cutover).
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* <slug>
|
|
* : Addon slug (e.g. hivepress-reviews).
|
|
*
|
|
* [--state=<state>]
|
|
* : Target state. One of: dual_write, backfill, verify, cutover, cleanup, complete. Default: cutover.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* wp wpdo hivepress migrate hivepress-reviews
|
|
* wp wpdo hivepress migrate hivepress-favorites --state=dual_write
|
|
*
|
|
* @param array $args Positional args (slug).
|
|
* @param array $assoc_args Flag args.
|
|
*
|
|
* @subcommand migrate
|
|
*/
|
|
public function migrate( $args, $assoc_args ): void {
|
|
$slug = (string) ( $args[0] ?? '' );
|
|
$state = (string) ( $assoc_args['state'] ?? 'cutover' );
|
|
$this->set_state( $slug, $state );
|
|
}
|
|
|
|
/**
|
|
* Roll an addon's modules back to idle.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* <slug>
|
|
* : Addon slug.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* wp wpdo hivepress rollback hivepress-reviews
|
|
*
|
|
* @param array $args Positional args (slug).
|
|
* @param array $assoc_args Flag args (unused).
|
|
*
|
|
* @subcommand rollback
|
|
*/
|
|
public function rollback( $args, $assoc_args ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
|
unset( $assoc_args );
|
|
$slug = (string) ( $args[0] ?? '' );
|
|
$this->set_state( $slug, 'idle' );
|
|
}
|
|
|
|
/**
|
|
* Run benchmark suite for active adapters.
|
|
*
|
|
* Currently a placeholder that reports availability — real query-pair
|
|
* benchmarks are wired by individual addons during their migration
|
|
* tooling. This command lists which adapters can be benchmarked.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* wp wpdo hivepress benchmark
|
|
*
|
|
* @param array $args Positional args (unused).
|
|
* @param array $assoc_args Flag args (unused).
|
|
*
|
|
* @subcommand benchmark
|
|
*/
|
|
public function benchmark( $args, $assoc_args ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
|
|
unset( $args, $assoc_args );
|
|
if ( ! class_exists( 'TMDO_HivePress_Benchmark' ) ) {
|
|
WP_CLI::error( 'TMDO_HivePress_Benchmark not loaded.' );
|
|
}
|
|
$adapters = class_exists( 'TMDO_HivePress_Bootstrap' )
|
|
? TMDO_HivePress_Bootstrap::adapters()
|
|
: array();
|
|
|
|
if ( empty( $adapters ) ) {
|
|
WP_CLI::warning( 'No adapters active — no benchmark to run.' );
|
|
return;
|
|
}
|
|
|
|
WP_CLI::log( '=== HivePress benchmark availability ===' );
|
|
foreach ( $adapters as $slug => $adapter ) {
|
|
$mods = $adapter instanceof TMDO_HivePress_Adapter ? $adapter->migrations() : array();
|
|
if ( empty( $mods ) ) {
|
|
WP_CLI::log( sprintf( ' %s: no FSM modules', $slug ) );
|
|
continue;
|
|
}
|
|
WP_CLI::log( sprintf( ' %s: %s', $slug, implode( ', ', $mods ) ) );
|
|
}
|
|
WP_CLI::log( '' );
|
|
WP_CLI::log( 'Run `wp wpdo benchmark <module>` for per-module timings.' );
|
|
}
|
|
|
|
// ── Internal helpers ────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Apply a target FSM state to all modules owned by an addon's adapter.
|
|
*
|
|
* @param string $slug Addon slug.
|
|
* @param string $state Target state.
|
|
*/
|
|
private function set_state( string $slug, string $state ): void {
|
|
if ( '' === $slug ) {
|
|
WP_CLI::error( 'Missing addon slug.' );
|
|
}
|
|
if ( ! in_array( $state, TMDO_Feature_Flags::VALID_STATES, true ) ) {
|
|
WP_CLI::error( sprintf( 'Invalid state "%s". Valid: %s', $state, implode( ', ', TMDO_Feature_Flags::VALID_STATES ) ) );
|
|
}
|
|
|
|
$adapter = class_exists( 'TMDO_HivePress_Bootstrap' )
|
|
? TMDO_HivePress_Bootstrap::adapter_for( $slug )
|
|
: null;
|
|
if ( ! $adapter instanceof TMDO_HivePress_Adapter ) {
|
|
WP_CLI::error( sprintf( 'Adapter "%s" not active.', $slug ) );
|
|
}
|
|
|
|
$modules = $adapter->migrations();
|
|
if ( empty( $modules ) ) {
|
|
WP_CLI::warning( sprintf( 'Adapter "%s" has no FSM modules.', $slug ) );
|
|
return;
|
|
}
|
|
|
|
foreach ( $modules as $module ) {
|
|
TMDO_Feature_Flags::set( (string) $module, $state );
|
|
WP_CLI::log( sprintf( ' %s → %s', $module, $state ) );
|
|
}
|
|
WP_CLI::success( sprintf( 'Adapter "%s" modules set to %s.', $slug, $state ) );
|
|
}
|
|
}
|
|
|
|
} // end if ( ! class_exists )
|