Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/cli/src/commands/wordpress-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ function runtimeBackedFuzzSuitePolicy(suite: FuzzSuiteContract): RuntimePolicy {

function fuzzSuiteRequiresRecipeRuntime(input: Record<string, unknown>): boolean {
const requirements = fuzzSuiteRuntimeRequirements(input)
return arrayOption(requirements?.extra_plugins).length > 0 || arrayOption(requirements?.component_contracts).length > 0
return arrayOption(requirements?.extra_plugins).length > 0 || arrayOption(requirements?.component_contracts).length > 0 || Boolean(runtimeRequirementWordPressDirectory(requirements))
}

function runtimeBackedFuzzSuiteCommands(suite: FuzzSuiteContract): string[] {
Expand Down Expand Up @@ -310,6 +310,9 @@ function applyFuzzSuiteRuntimeRequirements(recipe: WorkspaceRecipe, requirements
}
recipe.runtime = {
...runtime,
assets: runtimeRequirementWordPressDirectory(requirements)
? { ...(runtime.assets ?? {}), wordpressDirectory: runtimeRequirementWordPressDirectory(requirements) }
: runtime.assets,
stack: arrayOption(requirements.runtime_mounts).length > 0 ? { ...(runtime.stack ?? {}), mounts: arrayOption(requirements.runtime_mounts) as WorkspaceRecipeMount[] } : runtime.stack,
}
recipe.metadata = {
Expand All @@ -318,6 +321,10 @@ function applyFuzzSuiteRuntimeRequirements(recipe: WorkspaceRecipe, requirements
}
}

function runtimeRequirementWordPressDirectory(requirements: Record<string, unknown> | undefined): string | undefined {
return requirements ? stringValue(requirements.wordpress_directory) : undefined
}

function fuzzSuiteRuntimeRequirements(suiteInput: Record<string, unknown>): Record<string, unknown> | undefined {
return objectOption(objectOption(suiteInput.metadata)?.runtime_requirements)
}
Expand Down Expand Up @@ -428,6 +435,7 @@ function workloadRecipeOptions(input: Record<string, unknown>, runtimeRequiremen
const steps = workloadRecipeSteps(input, runtimeRequirements)
return {
wordpressVersion: stringValue(input.wordpressVersion ?? input.wordpress_version ?? input.wp),
wordpressDirectory: stringValue(input.wordpress_directory),
blueprint: input.blueprint,
preview: objectOption(input.preview) as WordPressWorkloadRunRecipeOptions["preview"],
mounts: arrayOption(input.mounts) as WordPressWorkloadRunRecipeOptions["mounts"],
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/recipe-dry-run.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { basename, dirname, resolve } from "node:path"
import { fixtureImportDeterministicIdPlan, normalizeRuntimeBackendKind, validateRuntimePolicy, type FixtureImportDeterministicIdPlan, type MountSpec, type RuntimePolicy, type RuntimeWordPressInstallMode, type SandboxWorkspaceMode, type WorkspaceRecipe, type WorkspaceRecipeDeclaredArtifact, type WorkspaceRecipeDistribution, type WorkspaceRecipeDistributionStartupProbe, type WorkspaceRecipeFixtureDatabase, type WorkspaceRecipePluginRuntime, type WorkspaceRecipePluginRuntimeHealthProbe, type WorkspaceRecipeSiteSeed, type WorkspaceRecipeSiteSeedBootstrap, type WorkspaceRecipeWorkspace } from "@automattic/wp-codebox-core"
import { fixtureImportDeterministicIdPlan, normalizeRuntimeBackendKind, validateRuntimePolicy, type FixtureImportDeterministicIdPlan, type MountSpec, type RuntimeAssetSpec, type RuntimePolicy, type RuntimeWordPressInstallMode, type SandboxWorkspaceMode, type WorkspaceRecipe, type WorkspaceRecipeDeclaredArtifact, type WorkspaceRecipeDistribution, type WorkspaceRecipeDistributionStartupProbe, type WorkspaceRecipeFixtureDatabase, type WorkspaceRecipePluginRuntime, type WorkspaceRecipePluginRuntimeHealthProbe, type WorkspaceRecipeSiteSeed, type WorkspaceRecipeSiteSeedBootstrap, type WorkspaceRecipeWorkspace } from "@automattic/wp-codebox-core"
import { SANDBOX_WORKSPACE_ROOT, stripUndefined } from "@automattic/wp-codebox-core/internals"
import { serializeError } from "./output.js"
import { RecipeArtifactsMountConflictError, recipeArtifactsMountConflict } from "./commands/recipe-run-artifacts-mount-guard.js"
Expand Down Expand Up @@ -55,6 +55,7 @@ export interface RecipePlan {
wp: string
phpVersion?: string
wordpressInstallMode?: RuntimeWordPressInstallMode
assets?: RuntimeAssetSpec
blueprint: unknown
extensions?: Array<{ manifest: string }>
}
Expand Down Expand Up @@ -441,6 +442,7 @@ export async function planWorkspaceRecipe(recipe: WorkspaceRecipe, recipeDirecto
wp: recipe.runtime?.wp ?? context.defaultWordPressVersion,
...(recipe.runtime?.phpVersion ? { phpVersion: recipe.runtime.phpVersion } : {}),
...(recipe.runtime?.wordpressInstallMode ? { wordpressInstallMode: recipe.runtime.wordpressInstallMode } : {}),
...(recipe.runtime?.assets ? { assets: recipe.runtime.assets } : {}),
...(recipe.runtime?.extensions ? { extensions: recipe.runtime.extensions } : {}),
blueprint: recipeBlueprintWithBootActivePlugins(recipe.runtime?.blueprint, extraPlugins),
},
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/recipe-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ function validateRecipeRuntimeAssets(assets: RuntimeAssetSpec | undefined, recip
if (assets.wordpressZip !== undefined && typeof assets.wordpressZip !== "string") {
throw new Error(`Recipe runtime assets wordpressZip must be a string: ${recipePath}`)
}

if (assets.wordpressDirectory !== undefined && typeof assets.wordpressDirectory !== "string") {
throw new Error(`Recipe runtime assets wordpressDirectory must be a string: ${recipePath}`)
}
}

function validateRecipeRuntimeExtensions(extensions: NonNullable<WorkspaceRecipe["runtime"]>["extensions"] | undefined, recipePath: string): void {
Expand Down
3 changes: 0 additions & 3 deletions packages/runtime-core/src/agent-task-recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export interface AgentTaskRunInput {
workspaces?: NonNullable<WorkspaceRecipe["inputs"]>["workspaces"]
dependency_overlays?: NonNullable<WorkspaceRecipe["inputs"]>["dependency_overlays"]
extra_plugins?: WorkspaceRecipeExtraPlugin[]
extraPlugins?: WorkspaceRecipeExtraPlugin[]
runtime_stack_mounts?: WorkspaceRecipeMount[]
runtime_overlays?: Array<Record<string, unknown>>
agent_bundles?: Array<Record<string, unknown>>
Expand Down Expand Up @@ -677,9 +676,7 @@ function agentTaskExtraPlugins(input: AgentTaskRunInput): WorkspaceRecipeExtraPl
...normalizeAgentTaskExtraPlugins(runtimeProfileRecord(input).plugins),
...normalizeAgentTaskExtraPlugins(runtimeProfileRecord(input).mu_plugins),
...normalizeAgentTaskExtraPlugins(input.extra_plugins),
...normalizeAgentTaskExtraPlugins(input.extraPlugins),
...normalizeAgentTaskExtraPlugins(runtimeRequirements?.extra_plugins),
...normalizeAgentTaskExtraPlugins(runtimeRequirements?.extraPlugins),
]
}

Expand Down
14 changes: 0 additions & 14 deletions packages/runtime-core/src/command-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,20 +627,6 @@ export const commandRegistry = [
recipe: true,
handler: { kind: "playground", method: "runRestRouteInventory" },
},
{
id: "wordpress.inventory-rest-routes",
description: "Inventory registered WordPress REST routes for fuzzing seed discovery using rest_get_server()->get_routes().",
acceptedArgs: [],
outputShape: "wp-codebox/wordpress-rest-route-inventory/v1 JSON with route, namespace, methods, bounded endpoint permission descriptors, bounded arg/schema descriptors, status, and diagnostics.",
outputSchema: objectEnvelopeSchema(WORDPRESS_REST_ROUTE_INVENTORY_SCHEMA, {
routes: { type: "array" },
namespaces: { type: "array" },
diagnostics: { type: "array" },
}),
policyRequirement: "Runtime policy commands must include wordpress.inventory-rest-routes.",
recipe: true,
handler: { kind: "playground", method: "runRestRouteInventory" },
},
{
id: "wordpress.admin-page-inventory",
description: "Inventory already-loaded WordPress admin menu pages for fuzzing target discovery without crawling the browser UI.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export const WORDPRESS_EXECUTION_ACTION_RESULT_SCHEMA = "wp-codebox/wordpress-ex

export type WordPressRuntimeInventoryCommand =
| "wordpress.rest-route-inventory"
| "wordpress.inventory-rest-routes"
| "wordpress.admin-page-inventory"
| "wordpress.admin-action-inventory"
| "wordpress.inventory-database"
Expand Down Expand Up @@ -48,7 +47,7 @@ export interface WordPressRestRouteDiscovery {

export interface WordPressRestRouteInventory {
schema: typeof WORDPRESS_REST_ROUTE_INVENTORY_SCHEMA
command: "wordpress.rest-route-inventory" | "wordpress.inventory-rest-routes"
command: "wordpress.rest-route-inventory"
status: "ok" | "unsupported"
routes: WordPressRestRouteDescriptor[]
namespaces: string[]
Expand Down
3 changes: 3 additions & 0 deletions packages/runtime-core/src/wordpress-workload-primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export interface WordPressAbilityStepOptions {

export interface WordPressWorkloadRunRecipeOptions {
wordpressVersion?: string
/** Local WordPress core source mounted at /wordpress before Playground starts. */
wordpressDirectory?: string
blueprint?: unknown
preview?: RuntimePreviewSpec
mounts?: WorkspaceRecipeMount[]
Expand Down Expand Up @@ -80,6 +82,7 @@ export function wordpressWorkloadRunRecipe(options: WordPressWorkloadRunRecipeOp
runtime: stripUndefined({
backend: "wordpress-playground",
wp: options.wordpressVersion ?? DEFAULT_WORDPRESS_VERSION,
assets: options.wordpressDirectory ? { wordpressDirectory: options.wordpressDirectory } : undefined,
blueprint: options.blueprint ?? { steps: [] },
preview: options.preview,
stack: Array.isArray(options.runtimeStackMounts) && options.runtimeStackMounts.length > 0 ? { mounts: options.runtimeStackMounts } : undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ private static function execute_fuzz_suite_step( array $step, array $case, array
return match ( $command ) {
'wordpress.ensure-plugin-active' => self::execute_fuzz_suite_plugin_activation( $args, $observation, $case_id ),
'wordpress.ensure-external-http-guardrail' => self::execute_fuzz_suite_external_http_guardrail( $args, $observation ),
'wordpress.inventory-rest-routes', 'wordpress.rest-route-inventory' => self::execute_fuzz_suite_rest_route_inventory( $args, $observation, $case_id ),
'wordpress.rest-route-inventory' => self::execute_fuzz_suite_rest_route_inventory( $args, $observation, $case_id ),
'wordpress.inventory-database' => self::execute_fuzz_suite_database_inventory( $args, $observation, $case_id ),
'wordpress.admin-page-inventory' => self::execute_fuzz_suite_admin_page_inventory( $args, $observation ),
'wordpress.fuzz-admin-pages' => self::execute_fuzz_suite_admin_page_fuzz( $args, $observation ),
Expand Down Expand Up @@ -517,7 +517,7 @@ private static function execute_fuzz_suite_rest_route_inventory( array $args, ar
$observation['namespaces'] = array_values( array_unique( $namespaces ) );
$observation['payload'] = array(
'schema' => 'wp-codebox/wordpress-rest-route-inventory/v1',
'command' => 'wordpress.inventory-rest-routes',
'command' => 'wordpress.rest-route-inventory',
'routes' => $items,
'namespaces' => $observation['namespaces'],
);
Expand Down
2 changes: 1 addition & 1 deletion scripts/php-fuzz-suite-runner-smoke.php
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ class WP_Codebox_Fuzz_Suite_Runner_Smoke {
'id' => 'rest-route-inventory',
'phases' => array(
'action' => array(
array( 'command' => 'wordpress.inventory-rest-routes', 'args' => array( 'namespaces=sample/v1', 'artifact=route_inventory' ) ),
array( 'command' => 'wordpress.rest-route-inventory', 'args' => array( 'namespaces=sample/v1', 'artifact=route_inventory' ) ),
),
),
),
Expand Down
3 changes: 1 addition & 2 deletions tests/agent-task-contracts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,8 +630,7 @@ try {
activate: false,
pluginFile: "agent-runtime-tool-bridge/agent-runtime-tool-bridge.php",
metadata: { source: "agent-task-input" },
}],
extraPlugins: [{
}, {
source: providerSource,
slug: "test-provider",
loadAs: "plugin",
Expand Down
9 changes: 5 additions & 4 deletions tests/playground-cli-runner-bootstrap-ini.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { tmpdir } from "node:os"
import { startPlaygroundCliServer, type PlaygroundCliModule } from "../packages/runtime-playground/src/playground-cli-runner.js"
import type { RuntimeCreateSpec } from "../packages/runtime-core/src/index.js"

const wordpressDirectory = await mkdtemp(join(tmpdir(), "wp-codebox-wordpress-source-"))
const wordpressDevelopDirectory = await mkdtemp(join(tmpdir(), "wp-codebox-wordpress-develop-"))
const artifactsDirectory = await mkdtemp(join(tmpdir(), "wp-codebox-artifacts-"))
const calls: Parameters<PlaygroundCliModule["runCLI"]>[0][] = []

Expand All @@ -32,7 +32,7 @@ try {
version: "mounted-wordpress-source",
phpVersion: "8.4",
wordpressInstallMode: "do-not-attempt-installing",
assets: { wordpressDirectory },
assets: { wordpressDirectory: wordpressDevelopDirectory },
extensions: [{ manifest: "/tmp/sodium/manifest.json" }],
blueprint: {},
},
Expand Down Expand Up @@ -64,7 +64,8 @@ try {
assert.equal(calls.length, 1)
assert.equal(calls[0]["mount-before-install"]?.length, 2)
assert.equal(calls[0]["mount-before-install"]?.[0]?.vfsPath, "/internal/shared")
assert.deepEqual(calls[0]["mount-before-install"]?.[1], { hostPath: wordpressDirectory, vfsPath: "/wordpress" })
// A wordpress-develop checkout is the runtime root, not an ordinary post-startup mount.
assert.deepEqual(calls[0]["mount-before-install"]?.[1], { hostPath: wordpressDevelopDirectory, vfsPath: "/wordpress" })
assert.deepEqual(calls[0].mount, [])
assert.equal(calls[0].workers, 6)
assert.equal(calls[0].wordpressInstallMode, "do-not-attempt-installing")
Expand Down Expand Up @@ -121,7 +122,7 @@ try {
assert.match(distributionAutoPrepend, /define\("WPCOM_IS_BRANCH_PREVIEW", true\)/)
assert.match(distributionAutoPrepend, /define\("WPCOM_BRANCH_ID", 123\)/)
} finally {
await rm(wordpressDirectory, { recursive: true, force: true })
await rm(wordpressDevelopDirectory, { recursive: true, force: true })
await rm(artifactsDirectory, { recursive: true, force: true })
}

Expand Down
32 changes: 30 additions & 2 deletions tests/public-fuzz-workload-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ return static function ( array $input, array $args ): array {
);
};
`, "utf8")
const wordpressDevelopSource = join(directory, "wordpress-develop")
await mkdir(wordpressDevelopSource)

const descriptorOutput = await captureStdout(async () => {
assert.equal(await runCli(["fuzz", "descriptor", "--format=json"]), 0)
Expand Down Expand Up @@ -149,7 +151,7 @@ return static function ( array $input, array $args ): array {
id: "public-cli-runtime-command-suite",
cases: [{
id: "rest-route-inventory",
target: { kind: "runtime", id: "wordpress.inventory-rest-routes", entrypoint: "wordpress.inventory-rest-routes" },
target: { kind: "runtime", id: "wordpress.rest-route-inventory", entrypoint: "wordpress.rest-route-inventory" },
input: { args: [] },
}],
}), "utf8")
Expand All @@ -163,7 +165,33 @@ return static function ( array $input, array $args ): array {
assert.notEqual(runtimeCommandFuzzJson.cases[0].skipReason, "fuzz_suite_executor_unavailable")
assert.equal(runtimeCommandFuzzJson.cases[0].metadata.adapter.adapterKind, "runtime")
assert.equal(runtimeCommandFuzzJson.cases[0].metadata.execution.result.json.schema, "wp-codebox/recipe-run-dry-run/v1")
assert.equal(runtimeCommandFuzzJson.cases[0].metadata.execution.result.json.plan.workflow.steps[0].command, "wordpress.inventory-rest-routes")
assert.equal(runtimeCommandFuzzJson.cases[0].metadata.execution.result.json.plan.workflow.steps[0].command, "wordpress.rest-route-inventory")

const coreRuntimeFuzzInput = join(directory, "core-runtime-fuzz.json")
await writeFile(coreRuntimeFuzzInput, JSON.stringify({
schema: "wp-codebox/fuzz-suite/v1",
id: "public-cli-core-runtime-suite",
metadata: {
runtime_requirements: {
wordpress_directory: wordpressDevelopSource,
extra_plugins: [{ slug: "sample-plugin", source: samplePluginSource, loadAs: "plugin", activate: true }],
},
},
cases: [{
id: "core-phpunit",
target: { kind: "runtime", id: "wordpress.core-phpunit", entrypoint: "wordpress.core-phpunit" },
input: { args: ["test-root=/wordpress/tests/phpunit"] },
}],
}), "utf8")
const coreRuntimeFuzzOutput = await captureStdout(async () => {
assert.equal(await runCli(["run-fuzz-suite", "--input-file", coreRuntimeFuzzInput, "--format=json", "--dry-run"]), 0)
})
const coreRuntimeFuzzJson = JSON.parse(coreRuntimeFuzzOutput)
const coreRuntimePlan = coreRuntimeFuzzJson.cases[0].metadata.execution.result.json.plan
assert.equal(coreRuntimePlan.runtime.assets.wordpressDirectory, wordpressDevelopSource)
assert.equal(coreRuntimePlan.runtime.stack, undefined)
assert.deepEqual(coreRuntimePlan.extra_plugins.map((plugin: { slug: string }) => plugin.slug), ["sample-plugin"])
assert.equal(coreRuntimePlan.workflow.steps[0].command, "wordpress.core-phpunit")

const phpRuntimeFuzzInput = join(directory, "runtime-php-workload-fuzz.json")
await writeFile(phpRuntimeFuzzInput, JSON.stringify({
Expand Down
5 changes: 2 additions & 3 deletions tests/wordpress-runtime-discovery-contracts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ assert.equal(executionSurfaces.surfaces[0]?.invocation.resultSchema, WORDPRESS_E

const inventoryDefinitions = [
["wordpress.rest-route-inventory", "runRestRouteInventory", WORDPRESS_REST_ROUTE_INVENTORY_SCHEMA],
["wordpress.inventory-rest-routes", "runRestRouteInventory", WORDPRESS_REST_ROUTE_INVENTORY_SCHEMA],
["wordpress.admin-page-inventory", "runAdminPageInventory", WORDPRESS_ADMIN_PAGE_INVENTORY_SCHEMA],
["wordpress.admin-action-inventory", "runAdminActionInventory", WORDPRESS_ADMIN_ACTION_INVENTORY_SCHEMA],
["wordpress.inventory-database", "runDatabaseInventory", WORDPRESS_DATABASE_INVENTORY_SCHEMA],
Expand Down Expand Up @@ -234,7 +233,7 @@ assert.deepEqual(route?.endpoints?.[0]?.args[1]?.enum, ["view", "edit"])
assert.equal(route?.endpoints?.[0]?.args[1]?.defaultPresent, true)
assert.deepEqual(route?.schema?.properties, ["id", "name"])

const inventoryPhp = runtimeInventoryPhpCode("rest", "wordpress.inventory-rest-routes", WORDPRESS_REST_ROUTE_INVENTORY_SCHEMA).replace(/^<\?php\n/, "")
const inventoryPhp = runtimeInventoryPhpCode("rest", "wordpress.rest-route-inventory", WORDPRESS_REST_ROUTE_INVENTORY_SCHEMA).replace(/^<\?php\n/, "")
const inventoryDiscovered = await runPhpJson<WordPressRestRouteInventory>(`
function wp_strip_all_tags( $text ) { return strip_tags( $text ); }
function wp_json_encode( $data, $flags = 0 ) { return json_encode( $data, $flags ); }
Expand All @@ -256,7 +255,7 @@ function rest_get_server() {
${inventoryPhp}
`)

assert.equal(inventoryDiscovered.command, "wordpress.inventory-rest-routes")
assert.equal(inventoryDiscovered.command, "wordpress.rest-route-inventory")
assert.equal(inventoryDiscovered.schema, WORDPRESS_REST_ROUTE_INVENTORY_SCHEMA)
assert.equal(inventoryDiscovered.routes[0]?.route, "/demo/v1/items")

Expand Down
Loading