From 536c02a4b6afe7a753ad329e03699bfa1a1af6b2 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sat, 30 May 2026 22:02:58 -0400 Subject: [PATCH] fix: support wp-cli bench workloads --- packages/cli/src/index.ts | 28 ++++++++++++ packages/runtime-playground/src/commands.ts | 50 ++++++++++++++++++--- packages/runtime-playground/src/index.ts | 28 ++++++++++-- scripts/recipe-bench-smoke.ts | 10 ++++- 4 files changed, 105 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index ebfc1a3..cae76a0 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -3985,6 +3985,9 @@ function recipePolicy(recipe: WorkspaceRecipe): RuntimePolicy { if (recipeWorkflowSteps(recipe).some(({ step }) => step.command === "wp-codebox.agent-sandbox-run")) { commands.unshift("wordpress.wp-cli") } + if (recipeWorkflowSteps(recipe).some(({ step }) => step.command === "wordpress.bench" && recipeBenchStepUsesWpCli(step))) { + commands.unshift("wordpress.wp-cli") + } if (recipeExtraPlugins(recipe).some((plugin) => plugin.activate !== false)) { commands.unshift("wordpress.run-php") } @@ -4004,6 +4007,31 @@ function recipePolicy(recipe: WorkspaceRecipe): RuntimePolicy { } } +function recipeBenchStepUsesWpCli(step: WorkspaceRecipe["workflow"]["steps"][number]): boolean { + const workloadsArg = (step.args ?? []).find((arg) => arg.startsWith("workloads-json=")) + if (!workloadsArg) { + return false + } + + try { + return recipeBenchWorkloadsUseWpCli(JSON.parse(workloadsArg.slice("workloads-json=".length))) + } catch { + return false + } +} + +function recipeBenchWorkloadsUseWpCli(value: unknown): boolean { + if (Array.isArray(value)) { + return value.some(recipeBenchWorkloadsUseWpCli) + } + if (!value || typeof value !== "object") { + return false + } + + const record = value as { type?: unknown; run?: unknown } + return record.type === "wp-cli" || recipeBenchWorkloadsUseWpCli(record.run) +} + function recipeStepUsesEvaluate(step: WorkspaceRecipe["workflow"]["steps"][number]): boolean { const raw = recipeStepArgValue(step.args ?? [], "steps-json") if (!raw || raw.startsWith("@")) { diff --git a/packages/runtime-playground/src/commands.ts b/packages/runtime-playground/src/commands.ts index 95bec66..cad7e74 100644 --- a/packages/runtime-playground/src/commands.ts +++ b/packages/runtime-playground/src/commands.ts @@ -49,6 +49,7 @@ export interface BenchRunCodeOptions { dependencySlugs: string[] env: Record workloads: unknown[] + wpCliBridge?: { url: string; token: string } } export interface PhpunitRunCodeOptions { @@ -1619,6 +1620,8 @@ $warmup_iterations = max(0, (int) ${JSON.stringify(String(options.warmupIteratio $dependency_slugs = json_decode(${JSON.stringify(JSON.stringify(options.dependencySlugs))}, true); $bench_env = json_decode(${JSON.stringify(JSON.stringify(options.env))}, true); $configured_workloads = json_decode(${JSON.stringify(JSON.stringify(options.workloads))}, true); +$wp_cli_bridge_url = ${JSON.stringify(options.wpCliBridge?.url ?? null)}; +$wp_cli_bridge_token = ${JSON.stringify(options.wpCliBridge?.token ?? null)}; if (is_array($bench_env)) { foreach ($bench_env as $name => $value) { @@ -1690,6 +1693,47 @@ function wp_codebox_bench_record_payload($payload, array &$metric_samples, ?arra } } +function wp_codebox_bench_run_wp_cli_step(array $step) { + global $wp_cli_bridge_url, $wp_cli_bridge_token; + $command = isset($step['command']) && is_string($step['command']) ? trim($step['command']) : ''; + if ($command === '') { + throw new RuntimeException('wp-cli bench workload steps require a command.'); + } + if (!is_string($wp_cli_bridge_url) || $wp_cli_bridge_url === '' || !is_string($wp_cli_bridge_token) || $wp_cli_bridge_token === '') { + throw new RuntimeException('wordpress.bench wp-cli workload steps require the WP-CLI bridge.'); + } + + $parse = isset($step['parse']) && is_string($step['parse']) ? $step['parse'] : ''; + $response = wp_remote_post($wp_cli_bridge_url . '/execute', array( + 'headers' => array( + 'authorization' => 'Bearer ' . $wp_cli_bridge_token, + 'content-type' => 'application/json', + ), + 'body' => wp_json_encode(array('type' => 'wp_cli', 'command' => $command), JSON_UNESCAPED_SLASHES), + 'timeout' => 300, + )); + if (is_wp_error($response)) { + throw new RuntimeException('WP-CLI bench workload bridge request failed: ' . $response->get_error_message()); + } + $body = wp_remote_retrieve_body($response); + $result = json_decode($body, true); + if (!is_array($result)) { + throw new RuntimeException('WP-CLI bench workload bridge returned invalid JSON.'); + } + if (empty($result['success'])) { + $error = isset($result['error']) && is_string($result['error']) ? $result['error'] : 'WP-CLI command failed'; + throw new RuntimeException('WP-CLI bench workload step failed: ' . $command . ' - ' . $error); + } + $stdout = isset($result['stdout']) ? (string) $result['stdout'] : ''; + if ($parse === 'json' && $stdout !== '') { + $decoded = json_decode($stdout, true); + if (json_last_error() === JSON_ERROR_NONE) { + return $decoded; + } + } + return $stdout; +} + $plugins_to_activate = array(); foreach (is_array($dependency_slugs) ? $dependency_slugs : array() as $dependency_slug) { $dependency_slug = sanitize_key((string) $dependency_slug); @@ -1755,11 +1799,7 @@ function wp_codebox_bench_run_configured_workload(array $workload, string $plugi throw new RuntimeException($result->get_error_message()); } } elseif ($type === 'wp-cli') { - if (!class_exists('WP_CLI')) { - throw new RuntimeException('WP-CLI is not loaded inside wordpress.bench yet.'); - } - $command = isset($step['command']) ? (string) $step['command'] : ''; - $result = WP_CLI::runcommand($command, array('return' => true, 'launch' => false, 'parse' => 'json')); + $result = wp_codebox_bench_run_wp_cli_step($step); } else { throw new RuntimeException('Unsupported bench workload step type: ' . $type); } diff --git a/packages/runtime-playground/src/index.ts b/packages/runtime-playground/src/index.ts index df043f5..4b7108a 100644 --- a/packages/runtime-playground/src/index.ts +++ b/packages/runtime-playground/src/index.ts @@ -373,6 +373,18 @@ function phpLiteral(value: string | number | boolean | null): string { return String(value) } +function benchWorkloadsUseWpCli(value: unknown): boolean { + if (Array.isArray(value)) { + return value.some(benchWorkloadsUseWpCli) + } + if (!value || typeof value !== "object") { + return false + } + + const record = value as { type?: unknown; run?: unknown } + return record.type === "wp-cli" || benchWorkloadsUseWpCli(record.run) +} + interface PluginCheckArtifact { targetPlugin: string files: { @@ -1520,10 +1532,18 @@ class PlaygroundRuntime implements Runtime { const dependencySlugs = commaListArg(args, "dependency-slugs") const env = jsonObjectArg(args, "env-json") const workloads = jsonArrayArg(args, "workloads-json") - const response = await this.runPlaygroundCommand("wordpress.bench", server, { - code: this.bootstrapPhpCode(benchRunCode({ componentId, pluginSlug, iterations, warmupIterations, dependencySlugs, env, workloads }), []), - }) - assertPlaygroundResponseOk("wordpress.bench", response) + const bridge = benchWorkloadsUseWpCli(workloads) ? await this.createRuntimeWpCliBridge(server) : undefined + let response: PlaygroundRunResponse + try { + response = await this.runPlaygroundCommand("wordpress.bench", server, { + code: this.bootstrapPhpCode(benchRunCode({ componentId, pluginSlug, iterations, warmupIterations, dependencySlugs, env, workloads, wpCliBridge: bridge }), []), + }) + assertPlaygroundResponseOk("wordpress.bench", response) + } finally { + if (bridge) { + await bridge.close() + } + } return promoteBrowserMetricsToBenchResults(response.text, this.browserProbes) } diff --git a/scripts/recipe-bench-smoke.ts b/scripts/recipe-bench-smoke.ts index a361db8..df01d1c 100644 --- a/scripts/recipe-bench-smoke.ts +++ b/scripts/recipe-bench-smoke.ts @@ -45,9 +45,14 @@ writeFileSync(recipePath, `${JSON.stringify({ `workloads-json=${JSON.stringify([ { id: "configured-env", - type: "php", + run: [ + { type: "wp-cli", command: "wp option update wp_codebox_bench_wp_cli yes", parse: "json" }, + { + type: "php", + code: "return array('metrics' => array('env_value' => (int) getenv('BENCH_FIXTURE_ENV'), 'define_visible' => defined('BENCH_FIXTURE_DEFINE') && BENCH_FIXTURE_DEFINE === 'defined-value' ? 1 : 0, 'wp_cli_option_visible' => get_option('wp_codebox_bench_wp_cli') === 'yes' ? 1 : 0), 'metadata' => array('kind' => 'configured'));", + }, + ], artifacts: { report: { path: "workloads/report.json", kind: "json" } }, - code: "return array('metrics' => array('env_value' => (int) getenv('BENCH_FIXTURE_ENV'), 'define_visible' => defined('BENCH_FIXTURE_DEFINE') && BENCH_FIXTURE_DEFINE === 'defined-value' ? 1 : 0), 'metadata' => array('kind' => 'configured'));", }, ])}`, ], @@ -88,6 +93,7 @@ assert.equal(configured.id, "configured-env") assert.equal(configured.source, "config") assert.equal(configured.metrics.env_value_mean, 13) assert.equal(configured.metrics.define_visible_mean, 1) +assert.equal(configured.metrics.wp_cli_option_visible_mean, 1) assert.equal(configured.metadata.kind, "configured") assert.equal(configured.artifacts.report.path, "workloads/report.json") assert.ok(output.artifacts?.directory)