Skip to content

Commit 6b6efde

Browse files
committed
Support PHPUnit prepare recipe steps
1 parent 55863c2 commit 6b6efde

5 files changed

Lines changed: 53 additions & 4 deletions

File tree

packages/cli/src/commands/recipe-build.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { readFile, writeFile } from "node:fs/promises"
2-
import { buildWordPressBenchRecipe, buildWordPressPhpunitRecipe, type WorkspaceRecipe, type WorkspaceRecipeExtraPlugin, type WorkspaceRecipeMount } from "@automattic/wp-codebox-core"
2+
import { buildWordPressBenchRecipe, buildWordPressPhpunitRecipe, type WorkspaceRecipe, type WorkspaceRecipeExtraPlugin, type WorkspaceRecipeMount, type WorkspaceRecipeStep } from "@automattic/wp-codebox-core"
33

44
interface RecipeBuildOptions {
55
recipeType: "phpunit" | "bench"
@@ -26,6 +26,7 @@ interface WordPressPhpunitBuilderOptions {
2626
bootstrapMode?: string
2727
projectBootstrap?: string
2828
multisite?: boolean
29+
prepareSteps?: WorkspaceRecipeStep[]
2930
}
3031

3132
interface WordPressBenchBuilderOptions {
@@ -81,6 +82,7 @@ function buildRecipe(recipeType: RecipeBuildOptions["recipeType"], options: Word
8182
bootstrapMode: stringOrUndefined((options as WordPressPhpunitBuilderOptions).bootstrapMode),
8283
projectBootstrap: stringOrUndefined((options as WordPressPhpunitBuilderOptions).projectBootstrap),
8384
multisite: Boolean((options as WordPressPhpunitBuilderOptions).multisite),
85+
prepareSteps: Array.isArray((options as WordPressPhpunitBuilderOptions).prepareSteps) ? (options as WordPressPhpunitBuilderOptions).prepareSteps : [],
8486
})
8587
case "bench":
8688
return buildWordPressBenchRecipe({

packages/cli/src/recipe-validation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const defaultPolicy: RuntimePolicy = {
2020
}
2121

2222
const supportedRecipeCommands = new Set(recipeCommandDefinitions().map((command) => command.id))
23+
const hostRecipeCommandPattern = /^host\/[A-Za-z0-9._/-]+$/
2324

2425
export async function loadWorkspaceRecipe(recipePath: string): Promise<WorkspaceRecipe> {
2526
return parseWorkspaceRecipe(await readFile(recipePath, "utf8"), recipePath)
@@ -413,7 +414,7 @@ export async function validateWorkspaceRecipeSemantics(recipe: WorkspaceRecipe,
413414

414415
for (const { phase, index, step } of recipeWorkflowSteps(recipe)) {
415416
const path = `$.workflow.${phase}[${index}]`
416-
if (!supportedRecipeCommands.has(step.command)) {
417+
if (!supportedRecipeCommands.has(step.command) && !hostRecipeCommandPattern.test(step.command)) {
417418
addIssue("unsupported-command", `${path}.command`, `Unsupported recipe command: ${step.command}`)
418419
continue
419420
}

packages/runtime-core/src/recipe-builders.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { WorkspaceRecipe, WorkspaceRecipeExtraPlugin, WorkspaceRecipeMount } from "./runtime-contracts.js"
1+
import type { WorkspaceRecipe, WorkspaceRecipeExtraPlugin, WorkspaceRecipeMount, WorkspaceRecipeStep } from "./runtime-contracts.js"
22
import { DEFAULT_WORDPRESS_VERSION } from "./runtime-defaults.js"
33

44
type JsonObject = Record<string, unknown>
@@ -26,6 +26,7 @@ export interface WordPressPhpunitRecipeOptions {
2626
bootstrapMode?: "managed" | "project" | (string & {})
2727
projectBootstrap?: string
2828
multisite?: boolean
29+
prepareSteps?: WorkspaceRecipeStep[]
2930
}
3031

3132
export interface WordPressBenchRecipeOptions {
@@ -89,6 +90,7 @@ export function buildWordPressPhpunitRecipe(options: WordPressPhpunitRecipeOptio
8990
]),
9091
},
9192
workflow: {
93+
...(options.prepareSteps && options.prepareSteps.length > 0 ? { before: normalizeRecipeSteps(options.prepareSteps, "prepareSteps") } : {}),
9294
steps: [{
9395
command: "wordpress.phpunit",
9496
args: [
@@ -112,6 +114,21 @@ export function buildWordPressPhpunitRecipe(options: WordPressPhpunitRecipeOptio
112114
}
113115
}
114116

117+
function normalizeRecipeSteps(steps: readonly WorkspaceRecipeStep[], label: string): WorkspaceRecipeStep[] {
118+
return steps.map((step, index) => {
119+
if (!step.command || typeof step.command !== "string") {
120+
throw new Error(`${label}[${index}] requires command`)
121+
}
122+
123+
return {
124+
command: step.command,
125+
...(step.args !== undefined ? { args: step.args } : {}),
126+
...(step.allowFailure !== undefined ? { allowFailure: step.allowFailure } : {}),
127+
...(step.advisory !== undefined ? { advisory: step.advisory } : {}),
128+
}
129+
})
130+
}
131+
115132
export function buildWordPressBenchRecipe(options: WordPressBenchRecipeOptions): WorkspaceRecipe {
116133
const pluginSlug = requiredPluginSlug(options.pluginSlug, "buildWordPressBenchRecipe")
117134
const componentId = options.componentId?.trim() || pluginSlug

packages/runtime-core/src/recipe-schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface WorkspaceRecipeJsonSchemaOptions {
66

77
export function createWorkspaceRecipeJsonSchema(options: WorkspaceRecipeJsonSchemaOptions = {}): WorkspaceRecipeJsonSchema {
88
const commandSchema = options.recipeCommandIds && options.recipeCommandIds.length > 0
9-
? { enum: [...options.recipeCommandIds] }
9+
? { anyOf: [{ enum: [...options.recipeCommandIds] }, { type: "string", pattern: "^host/[A-Za-z0-9._/-]+$" }] }
1010
: { type: "string" }
1111

1212
return {

scripts/wordpress-recipe-builders-smoke.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import assert from "node:assert/strict"
2+
import { spawnSync } from "node:child_process"
3+
import { mkdtempSync, writeFileSync } from "node:fs"
4+
import { tmpdir } from "node:os"
5+
import { dirname, join, resolve } from "node:path"
6+
import { fileURLToPath } from "node:url"
27
import { DEFAULT_WORDPRESS_VERSION, buildWordPressBenchRecipe, buildWordPressPhpunitRecipe, createBenchmarkDefinitionJsonSchema, createBenchResultsJsonSchema, createWorkspaceRecipeJsonSchema, recipeCommandDefinitions } from "@automattic/wp-codebox-core"
38
import Ajv2020 from "ajv/dist/2020.js"
49

10+
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..")
11+
const cli = resolve(root, "packages/cli/dist/index.js")
12+
513
const recipeCommandIds = recipeCommandDefinitions().filter((command) => command.recipe).map((command) => command.id)
614
const ajv = new Ajv2020({ strict: false })
715
const validate = ajv.compile(createWorkspaceRecipeJsonSchema({ recipeCommandIds }))
@@ -23,6 +31,10 @@ const phpunitRecipe = buildWordPressPhpunitRecipe({
2331
bootstrapMode: "project",
2432
projectBootstrap: "tests/bootstrap.php",
2533
multisite: true,
34+
prepareSteps: [{
35+
command: "host/prepare-php",
36+
args: ['input-json={"args":["bin/generate-feature-config.php"],"cwd":"/repo/demo-plugin-source"}'],
37+
}],
2638
mounts: [
2739
{ source: "/repo/vendor", target: "/wp-codebox-vendor", mode: "readonly" },
2840
],
@@ -34,6 +46,10 @@ assert.equal(buildWordPressBenchRecipe({ pluginSlug: "demo-plugin" }).runtime?.w
3446
assert.equal(phpunitRecipe.inputs?.mounts?.[0]?.mode, "readwrite")
3547
assert.deepEqual(phpunitRecipe.inputs?.mounts?.[0], { source: "/repo/demo-plugin-source", target: "/wordpress/wp-content/plugins/demo-plugin", mode: "readwrite" })
3648
assert.deepEqual(phpunitRecipe.inputs?.mounts?.[1], { source: "/repo/vendor", target: "/wp-codebox-vendor", mode: "readonly" })
49+
assert.deepEqual(phpunitRecipe.workflow.before, [{
50+
command: "host/prepare-php",
51+
args: ['input-json={"args":["bin/generate-feature-config.php"],"cwd":"/repo/demo-plugin-source"}'],
52+
}])
3753
assert.deepEqual(phpunitRecipe.workflow.steps[0]?.args, [
3854
"plugin-slug=demo-plugin",
3955
"cwd=tests/phpunit",
@@ -52,6 +68,19 @@ assert.deepEqual(phpunitRecipe.workflow.steps[0]?.args, [
5268
])
5369
assert.ok(validate(phpunitRecipe), ajv.errorsText(validate.errors))
5470

71+
const workspace = mkdtempSync(join(tmpdir(), "wp-codebox-phpunit-prepare-builder-"))
72+
const recipePath = join(workspace, "recipe.json")
73+
writeFileSync(recipePath, `${JSON.stringify({
74+
schema: "wp-codebox/workspace-recipe/v1",
75+
workflow: {
76+
before: phpunitRecipe.workflow.before,
77+
steps: [{ command: "wordpress.run-php", args: ["code=echo 'ok';"] }],
78+
},
79+
}, null, 2)}\n`)
80+
const validateRecipe = spawnSync(process.execPath, [cli, "recipe", "validate", "--recipe", recipePath, "--json"], { cwd: root, encoding: "utf8" })
81+
assert.equal(validateRecipe.status, 0, validateRecipe.stderr || validateRecipe.stdout)
82+
assert.equal(JSON.parse(validateRecipe.stdout).valid, true)
83+
5584
const benchRecipe = buildWordPressBenchRecipe({
5685
wordpressVersion: "7.0",
5786
componentId: "demo-component",

0 commit comments

Comments
 (0)