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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@
"test:php-patch-approval-filter": "php scripts/php-patch-approval-filter-smoke.php",
"test:schema-parity": "tsx tests/schema-parity.test.ts",
"test:recipe-validation-descriptors": "tsx tests/recipe-validation-descriptors.test.ts",
"test:runtime-workers": "tsx tests/runtime-workers.test.ts",
"test:recipe-runtime-backend-normalization": "tsx tests/recipe-runtime-backend-normalization.test.ts",
"test:recipe-runtime-setup-staged-materialization": "tsx tests/recipe-runtime-setup-staged-materialization.test.ts",
"test:recipe-run-provenance": "tsx tests/recipe-run-provenance.test.ts",
Expand Down
10 changes: 9 additions & 1 deletion packages/cli/src/commands/recipe-build.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { readFile, writeFile } from "node:fs/promises"
import { buildGenericAbilityRuntimeRunRecipe, buildRuntimePackageRunRecipe, buildWordPressBenchRecipe, buildWordPressPhpunitRecipe, compileRecipeTemplate, type GenericAbilityRuntimeRunOptions, type RecipeTemplateInput, type RuntimePackageRunRecipeOptions, type RuntimeWordPressInstallMode, type WorkspaceRecipe, type WorkspaceRecipeExtraPlugin, type WorkspaceRecipeMount, type WorkspaceRecipePHPWasmExtensionManifest, type WorkspaceRecipeRuntimeBackendPackage, type WorkspaceRecipeRuntimeService, type WorkspaceRecipeStep } from "@automattic/wp-codebox-core"
import { buildGenericAbilityRuntimeRunRecipe, buildRuntimePackageRunRecipe, buildWordPressBenchRecipe, buildWordPressPhpunitRecipe, compileRecipeTemplate, type GenericAbilityRuntimeRunOptions, type RecipeTemplateInput, type RuntimePackageRunRecipeOptions, type RuntimeWorkerCount, type RuntimeWordPressInstallMode, type WorkspaceRecipe, type WorkspaceRecipeExtraPlugin, type WorkspaceRecipeMount, type WorkspaceRecipePHPWasmExtensionManifest, type WorkspaceRecipeRuntimeBackendPackage, type WorkspaceRecipeRuntimeService, type WorkspaceRecipeStep } from "@automattic/wp-codebox-core"

interface RecipeBuildOptions {
recipeType: "phpunit" | "bench" | "template" | "generic-ability-runtime-run" | "runtime-package-run"
Expand All @@ -11,6 +11,7 @@ interface WordPressPhpunitBuilderOptions {
blueprint?: unknown
wordpressVersion?: string
phpVersion?: string
workers?: RuntimeWorkerCount
databaseType?: "sqlite" | "mysql"
wordpressInstallMode?: RuntimeWordPressInstallMode
extensions?: WorkspaceRecipePHPWasmExtensionManifest[]
Expand Down Expand Up @@ -82,6 +83,7 @@ function buildRecipe(recipeType: RecipeBuildOptions["recipeType"], options: Word
blueprint: phpunitOptions.blueprint,
wordpressVersion: stringOrUndefined(phpunitOptions.wordpressVersion),
phpVersion: stringOrUndefined(phpunitOptions.phpVersion),
workers: workerCountOrUndefined(phpunitOptions.workers),
databaseType: phpunitOptions.databaseType,
wordpressInstallMode: phpunitOptions.wordpressInstallMode,
extensions: Array.isArray(phpunitOptions.extensions) ? phpunitOptions.extensions : [],
Expand Down Expand Up @@ -182,6 +184,12 @@ function requiredString(value: unknown, name: string): string {
return value
}

function workerCountOrUndefined(value: unknown): RuntimeWorkerCount | undefined {
if (value === undefined || value === "auto") return value
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 64) return value
throw new Error("workers must be an integer from 1 to 64 or auto.")
}

function stringOrUndefined(value: unknown): string | undefined {
return typeof value === "string" && value !== "" ? value : undefined
}
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/commands/recipe-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ export async function runRecipe(options: RecipeRunOptions, interruption?: Recipe
name: plan.runtime.name,
version: plan.runtime.wp,
phpVersion: plan.runtime.phpVersion,
workers: plan.runtime.workers,
wordpressInstallMode: plan.runtime.wordpressInstallMode,
databaseSetup: recipe.inputs?.services?.some(({ kind }) => kind === "mysql") ? "external" as const : undefined,
blueprint: plan.runtime.blueprint,
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/recipe-dry-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface RecipePlan {
name: string
wp: string
phpVersion?: string
workers?: number | "auto"
wordpressInstallMode?: RuntimeWordPressInstallMode
assets?: RuntimeAssetSpec
blueprint: unknown
Expand Down Expand Up @@ -442,6 +443,7 @@ export async function planWorkspaceRecipe(recipe: WorkspaceRecipe, recipeDirecto
name: recipe.runtime?.name ?? "wp-codebox-recipe",
wp: recipe.runtime?.wp ?? context.defaultWordPressVersion,
...(recipe.runtime?.phpVersion ? { phpVersion: recipe.runtime.phpVersion } : {}),
...(recipe.runtime?.workers !== undefined ? { workers: recipe.runtime.workers } : {}),
...(recipe.runtime?.wordpressInstallMode ? { wordpressInstallMode: recipe.runtime.wordpressInstallMode } : {}),
...(recipe.runtime?.assets ? { assets: recipe.runtime.assets } : {}),
...(recipe.runtime?.extensions ? { extensions: recipe.runtime.extensions } : {}),
Expand Down
4 changes: 3 additions & 1 deletion packages/runtime-core/src/recipe-builders.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { RuntimePreviewSpec, RuntimeWordPressInstallMode, WorkspaceRecipe, WorkspaceRecipeExtraPlugin, WorkspaceRecipeMount, WorkspaceRecipePHPWasmExtensionManifest, WorkspaceRecipeRuntimeBackendPackage, WorkspaceRecipeRuntimeService, WorkspaceRecipeStep } from "./runtime-contracts.js"
import type { RuntimePreviewSpec, RuntimeWorkerCount, RuntimeWordPressInstallMode, WorkspaceRecipe, WorkspaceRecipeExtraPlugin, WorkspaceRecipeMount, WorkspaceRecipePHPWasmExtensionManifest, WorkspaceRecipeRuntimeBackendPackage, WorkspaceRecipeRuntimeService, WorkspaceRecipeStep } from "./runtime-contracts.js"
import { commandArg, commandJsonArg, commandStringListArg } from "./command-codecs.js"
export { buildRuntimePackageRunRecipe, CODEBOX_RUN_RUNTIME_PACKAGE_ABILITY, RUNTIME_PACKAGE_ARTIFACT_DECLARATION_SCHEMA, RUNTIME_PACKAGE_EXECUTION_INPUT_SCHEMA, RUNTIME_PACKAGE_EXECUTION_RESULT_SCHEMA, RUNTIME_PACKAGE_OUTPUT_PROJECTION_SCHEMA, runtimePackageExecutionInput, type RuntimePackageArtifactDeclaration, type RuntimePackageExecutionInput, type RuntimePackageOutputProjection, type RuntimePackageRunRecipeOptions } from "./runtime-package-execution.js"
export { RUNTIME_PACKAGE_DIAGNOSTIC_SCHEMA, RUNTIME_PACKAGE_RESULT_SCHEMA, RUNTIME_PACKAGE_TASK_SCHEMA, normalizeRuntimePackageResult, normalizeRuntimePackageTask, validateRuntimePackageTask, type RuntimePackageDiagnostic, type RuntimePackageResult, type RuntimePackageTask } from "./runtime-package-contracts.js"
Expand All @@ -14,6 +14,7 @@ export interface NormalizeRecipeMountsOptions {
export interface WordPressPhpunitRecipeOptions {
wordpressVersion?: string
phpVersion?: string
workers?: RuntimeWorkerCount
databaseType?: "sqlite" | "mysql"
wordpressInstallMode?: RuntimeWordPressInstallMode
blueprint?: unknown
Expand Down Expand Up @@ -81,6 +82,7 @@ export function buildWordPressPhpunitRecipe(options: WordPressPhpunitRecipeOptio
runtime: {
wp: options.wordpressVersion ?? DEFAULT_WORDPRESS_VERSION,
...(options.phpVersion ? { phpVersion: options.phpVersion } : {}),
...(options.workers !== undefined ? { workers: options.workers } : {}),
...(options.wordpressInstallMode ? { wordpressInstallMode: options.wordpressInstallMode } : {}),
blueprint: blueprintWithMultisite(options.blueprint ?? { steps: [] }, (options.multisite ?? false) && options.databaseType !== "mysql"),
...(options.preview || options.multisite ? { preview: multisitePreview(options.preview, options.multisite ?? false) } : {}),
Expand Down
7 changes: 7 additions & 0 deletions packages/runtime-core/src/recipe-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ export function createWorkspaceRecipeJsonSchema(options: WorkspaceRecipeJsonSche
pattern: "^[0-9]+\\.[0-9]+$",
description: "PHP runtime version passed to WordPress Playground, for example 8.3 or 8.4.",
},
workers: {
anyOf: [
{ type: "integer", minimum: 1, maximum: 64 },
{ const: "auto" },
],
description: "Runtime request worker count. Omit to use the backend default.",
},
wordpressInstallMode: {
enum: enumValues(options.runtimeWordPressInstallModes, defaultRuntimeWordPressInstallModes),
description: "Controls how Playground prepares a mounted WordPress directory. Use do-not-attempt-installing for custom distributions that own their own boot/readiness probes.",
Expand Down
4 changes: 3 additions & 1 deletion packages/runtime-core/src/runtime-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { RuntimePolicy } from "./runtime-policy.js"
import { SANDBOX_WORKSPACE_ROOT } from "./runtime-action-adapter.js"
import type { ArtifactFileDigest, ArtifactManifestFile, ArtifactSpec, ArtifactViewerMetadata } from "./artifact-manifest.js"
import type { HostToolDefinition, HostToolRegistry } from "./host-tool-registry.js"
import type { BackendNeutralRuntimeProvenance, RuntimePHPWasmExtensionManifest, RuntimeWordPressAssetSpec, RuntimeWordPressDatabaseSetupContract, RuntimeWordPressEnvironmentSpec, RuntimeWordPressInstallModeContract, RuntimeWordPressProvenance } from "./runtime-neutral-contracts.js"
import type { BackendNeutralRuntimeProvenance, RuntimePHPWasmExtensionManifest, RuntimeWorkerCount, RuntimeWordPressAssetSpec, RuntimeWordPressDatabaseSetupContract, RuntimeWordPressEnvironmentSpec, RuntimeWordPressInstallModeContract, RuntimeWordPressProvenance } from "./runtime-neutral-contracts.js"
import type {
RUNTIME_EPISODE_ACTION_SCHEMA,
RUNTIME_EPISODE_OBSERVATION_SCHEMA,
Expand All @@ -20,6 +20,7 @@ export interface EnvironmentSpec extends RuntimeWordPressEnvironmentSpec {}

export type RuntimeWordPressInstallMode = RuntimeWordPressInstallModeContract
export type RuntimeWordPressDatabaseSetup = RuntimeWordPressDatabaseSetupContract
export type { RuntimeWorkerCount }

export interface RuntimeAssetSpec extends RuntimeWordPressAssetSpec {}

Expand Down Expand Up @@ -647,6 +648,7 @@ export interface WorkspaceRecipe {
name?: string
wp?: string
phpVersion?: string
workers?: RuntimeWorkerCount
wordpressInstallMode?: RuntimeWordPressInstallMode
blueprint?: unknown
preview?: RuntimePreviewSpec
Expand Down
12 changes: 12 additions & 0 deletions packages/runtime-core/src/runtime-neutral-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface BackendNeutralEnvironmentSpec {

export type RuntimeWordPressInstallModeContract = "install-from-existing-files" | "install-from-existing-files-if-needed" | "do-not-attempt-installing"
export type RuntimeWordPressDatabaseSetupContract = "runtime-managed" | "external"
export type RuntimeWorkerCount = number | "auto"

export interface RuntimeWordPressAssetSpec extends BackendNeutralRuntimeAssetSpec {
wordpressDirectory?: string
Expand All @@ -61,6 +62,7 @@ export interface RuntimePHPWasmExtensionManifest {
export interface RuntimeWordPressEnvironmentSpec extends BackendNeutralEnvironmentSpec {
blueprint?: unknown
phpVersion?: string
workers?: RuntimeWorkerCount
assets?: RuntimeWordPressAssetSpec
wordpressInstallMode?: RuntimeWordPressInstallModeContract
databaseSetup?: RuntimeWordPressDatabaseSetupContract
Expand Down Expand Up @@ -115,13 +117,23 @@ export function normalizeRuntimeWordPressEnvironmentSpec(input: unknown): Runtim
...neutral,
blueprint: value.blueprint,
phpVersion: optionalString(value.phpVersion, "environment.phpVersion"),
workers: normalizeRuntimeWorkerCount(value.workers),
assets: normalizeRuntimeWordPressAssetSpec(value.assets),
wordpressInstallMode: optionalString(value.wordpressInstallMode, "environment.wordpressInstallMode") as RuntimeWordPressInstallModeContract | undefined,
databaseSetup: optionalString(value.databaseSetup, "environment.databaseSetup") as RuntimeWordPressDatabaseSetupContract | undefined,
extensions: normalizeRuntimePHPWasmExtensionManifests(value.extensions),
})
}

function normalizeRuntimeWorkerCount(value: unknown): RuntimeWorkerCount | undefined {
if (value === undefined) return undefined
if (value === "auto") return value
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > 64) {
throw new Error("environment.workers must be an integer from 1 to 64 or auto.")
}
return value
}

function normalizeRuntimePHPWasmExtensionManifests(input: unknown): RuntimePHPWasmExtensionManifest[] | undefined {
if (input === undefined) return undefined
if (!Array.isArray(input)) throw new Error("environment.extensions must be an array.")
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-playground/src/playground-cli-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export async function startPlaygroundCliServer(spec: RuntimeCreateSpec, mounts:
quiet: true,
verbosity: "quiet",
skipBrowser: true,
workers: 6,
workers: spec.environment.workers ?? 6,
mount: [
...postinstallMounts.map((mount) => ({
hostPath: mount.source,
Expand Down
6 changes: 5 additions & 1 deletion tests/playground-cli-runner-bootstrap-ini.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ try {
environment: {
version: "mounted-wordpress-source",
phpVersion: "8.4",
workers: 1,
wordpressInstallMode: "do-not-attempt-installing",
databaseSetup: "external",
assets: { wordpressDirectory: wordpressDevelopDirectory },
Expand Down Expand Up @@ -84,7 +85,7 @@ try {
assert.equal(calls[0]["mount-before-install"]?.[4]?.vfsPath, "/wordpress/wp-config.php")
assert.deepEqual(calls[0]["mount-before-install"]?.[5], { hostPath: wordpressDevelopDirectory, vfsPath: "/wordpress" })
assert.deepEqual(calls[0].mount, [])
assert.equal(calls[0].workers, 6)
assert.equal(calls[0].workers, 1)
assert.equal(calls[0].wordpressInstallMode, "do-not-attempt-installing")
assert.equal(calls[0].skipSqliteSetup, true)
assert.equal(calls[0].phpEnv?.DB_PASSWORD, "secret")
Expand Down Expand Up @@ -135,6 +136,7 @@ try {
await defaultRuntimeIniServer[Symbol.asyncDispose]()

assert.equal(calls.length, 1)
assert.equal(calls[0].workers, 1)
assert.deepEqual(calls[0].phpIniEntries, { memory_limit: "512M" })
assert.equal(calls[0].skipSqliteSetup, false)
assert.equal(shouldUseProgrammaticPlaygroundRunner(defaultRuntimeIniSpec), true)
Expand All @@ -146,6 +148,7 @@ try {
...defaultRuntimeIniSpec.environment,
version: "latest",
wordpressInstallMode: undefined,
workers: undefined,
assets: undefined,
},
}
Expand All @@ -158,6 +161,7 @@ try {
assert.equal(calls[0]["mount-before-install"]?.[0]?.vfsPath, "/internal/wp-codebox")
assert.match(calls[0]["mount-before-install"]?.[1]?.vfsPath ?? "", /^\/wordpress\/wp-codebox-execute-[a-f0-9]{24}\.php$/)
assert.equal(calls[0].wordpressInstallMode, undefined)
assert.equal(calls[0].workers, 6)
assert.equal(shouldUseProgrammaticPlaygroundRunner(downloadedWordPressSpec), false)

calls.length = 0
Expand Down
46 changes: 46 additions & 0 deletions tests/runtime-workers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import assert from "node:assert/strict"
import { readFile, writeFile } from "node:fs/promises"
import { join } from "node:path"

import { runRecipeBuildCommand } from "../packages/cli/src/commands/recipe-build.js"
import { planWorkspaceRecipe } from "../packages/cli/src/recipe-dry-run.js"
import { buildWordPressPhpunitRecipe } from "../packages/runtime-core/src/recipe-builders.js"
import { normalizeRuntimeWordPressEnvironmentSpec, validateWorkspaceRecipeJsonSchema, type WorkspaceRecipe } from "../packages/runtime-core/src/index.js"
import { withTempDir } from "../scripts/test-kit.js"

const recipe: WorkspaceRecipe = {
schema: "wp-codebox/workspace-recipe/v1",
runtime: { workers: 1 },
workflow: { steps: [{ command: "wordpress.run-php" }] },
}

assert.equal(validateWorkspaceRecipeJsonSchema(recipe).valid, true)
assert.equal(validateWorkspaceRecipeJsonSchema({ ...recipe, runtime: { workers: "auto" } }).valid, true)
assert.equal(validateWorkspaceRecipeJsonSchema({ ...recipe, runtime: { workers: 0 } }).valid, false)
assert.equal(validateWorkspaceRecipeJsonSchema({ ...recipe, runtime: { workers: 65 } }).valid, false)
assert.equal(validateWorkspaceRecipeJsonSchema({ ...recipe, runtime: { workers: 1.5 } }).valid, false)
assert.deepEqual(normalizeRuntimeWordPressEnvironmentSpec({ kind: "wordpress", workers: 1 }), { kind: "wordpress", workers: 1 })
assert.throws(() => normalizeRuntimeWordPressEnvironmentSpec({ kind: "wordpress", workers: 0 }), /workers must be an integer/)

assert.equal(buildWordPressPhpunitRecipe({ pluginSlug: "example", workers: 1 }).runtime?.workers, 1)
assert.equal(buildWordPressPhpunitRecipe({ pluginSlug: "example", workers: "auto" }).runtime?.workers, "auto")
assert.equal(buildWordPressPhpunitRecipe({ pluginSlug: "example" }).runtime?.workers, undefined)

const plan = await planWorkspaceRecipe(recipe, process.cwd(), { recipePath: "recipe.json" }, {
defaultWordPressVersion: "latest",
resolveExecutionSpec: async (step) => ({ command: step.command, args: step.args ?? [] }),
})
assert.equal(plan.runtime.workers, 1)

await withTempDir("wp-codebox-runtime-workers-", async (directory) => {
const optionsPath = join(directory, "options.json")
const recipePath = join(directory, "recipe.json")
await writeFile(optionsPath, JSON.stringify({ pluginSlug: "example", workers: 1 }))
assert.equal(await runRecipeBuildCommand(["phpunit", "--options", optionsPath, "--output", recipePath]), 0)
assert.equal((JSON.parse(await readFile(recipePath, "utf8")) as WorkspaceRecipe).runtime?.workers, 1)

await writeFile(optionsPath, JSON.stringify({ pluginSlug: "example", workers: 0 }))
await assert.rejects(runRecipeBuildCommand(["phpunit", "--options", optionsPath, "--output", recipePath]), /workers must be an integer/)
})

console.log("runtime workers ok")
Loading