11import { cp , mkdtemp , rm , stat } from "node:fs/promises"
22import { tmpdir } from "node:os"
33import { join , posix , resolve } from "node:path"
4- import { phpRuntimeRecipePluginPreloadFunction , type ExecutionResult , type MountSpec , type Runtime , type WorkspaceRecipe , type WorkspaceRecipeMount , type WorkspaceRecipePluginRuntimeHealthProbe } from "@automattic/wp-codebox-core"
4+ import { phpRuntimeRecipePluginPreloadFunction , type ExecutionResult , type MountSpec , type Runtime , type RuntimeCreateSpec , type WorkspaceRecipe , type WorkspaceRecipeMount , type WorkspaceRecipePluginRuntimeHealthProbe } from "@automattic/wp-codebox-core"
5+ import { requiresManagedMysqlMultisitePreinstall } from "@automattic/wp-codebox-playground"
56import { installMuPluginsCode , installPluginComposerAutoloadersCode , prepareRecipeDependencyOverlays , prepareRecipeExtraPlugins , prepareRecipeRuntimeOverlays , prepareRecipeStagedFiles , prepareRecipeWorkspacePreloads , prepareRecipeWorkspaces , recipeMountType , type PreparedDependencyOverlay , type PreparedExtraPlugin , type PreparedRuntimeOverlay , type PreparedStagedFile , type PreparedWorkspaceMount } from "../recipe-sources.js"
67import { pluginRuntimeHealthProbeStep , type RecipeWorkflowPhase } from "../recipe-validation.js"
78import { pluginRuntimeHealthProbeStepIndex , pluginRuntimeSetupStepIndex } from "../recipe-dry-run.js"
@@ -52,9 +53,10 @@ export async function applyRecipeRuntimeSetup(args: {
5253 runtime : Runtime
5354 prepared : PreparedRecipeRuntimeSetup
5455 phaseExecutor : RecipeRunPhaseExecutor
56+ runtimeSpec : Pick < RuntimeCreateSpec , "environment" | "runtimeEnv" >
5557 interruption ?: RecipeInterruptionController
5658} ) : Promise < RecipeRuntimeSetupResult > {
57- const { recipe, recipeDirectory, runtime, prepared, phaseExecutor, interruption } = args
59+ const { recipe, recipeDirectory, runtime, runtimeSpec , prepared, phaseExecutor, interruption } = args
5860 const { workspaceMounts, extraPlugins, dependencyOverlays, stagedFiles, overlays, inputMountBaselinePaths, inputMountPathMap } = prepared
5961 const executions : RecipeExecutionResult [ ] = [ ]
6062 const phaseTracker = phaseExecutor . tracker
@@ -127,19 +129,20 @@ export async function applyRecipeRuntimeSetup(args: {
127129 interruption ?. throwIfInterrupted ( )
128130 }
129131
132+ const extraPluginMounts : MountSpec [ ] = extraPlugins . map ( ( plugin ) => ( {
133+ type : "directory" ,
134+ source : plugin . source ,
135+ target : plugin . target ,
136+ mode : "readonly" ,
137+ metadata : {
138+ kind : "extra-plugin" ,
139+ slug : plugin . slug ,
140+ source : plugin . provenance ,
141+ } ,
142+ } ) )
130143 await phaseTracker . run ( "mount_plugins" , phasePluginMountData ( extraPlugins ) , async ( ) => {
131- for ( const plugin of extraPlugins ) {
132- await awaitRecipe ( `extra-plugin.mount:${ plugin . slug } ` , runtime . mount ( {
133- type : "directory" ,
134- source : plugin . source ,
135- target : plugin . target ,
136- mode : "readonly" ,
137- metadata : {
138- kind : "extra-plugin" ,
139- slug : plugin . slug ,
140- source : plugin . provenance ,
141- } ,
142- } ) )
144+ for ( const [ index , plugin ] of extraPlugins . entries ( ) ) {
145+ await awaitRecipe ( `extra-plugin.mount:${ plugin . slug } ` , runtime . mount ( extraPluginMounts [ index ] ) )
143146 interruption ?. throwIfInterrupted ( )
144147 }
145148 } )
@@ -190,6 +193,7 @@ export async function applyRecipeRuntimeSetup(args: {
190193 }
191194
192195 const materializableMounts : MountSpec [ ] = [
196+ ...extraPluginMounts ,
193197 ...inputMounts ,
194198 ...stagedFiles . map ( ( stagedFile ) => ( {
195199 type : stagedFile . type ,
@@ -205,17 +209,19 @@ export async function applyRecipeRuntimeSetup(args: {
205209 interruption ?. throwIfInterrupted ( )
206210 }
207211
208- const muPluginInstallCode = installMuPluginsCode ( extraPlugins )
212+ const isolateManagedMultisitePreinstall = recipeHasManagedMysqlMultisitePhpunit ( recipe , runtimeSpec )
213+ const muPluginInstallCode = isolateManagedMultisitePreinstall ? null : installMuPluginsCode ( extraPlugins )
209214 if ( muPluginInstallCode ) {
210215 executions . push ( withRecipeExecutionPhase ( await runtime . execute ( { command : "wordpress.run-php" , args : setupPhpArgs ( muPluginInstallCode ) } ) , "setup" , - 2 , "extra-plugin.install-mu-loader" ) )
211216 }
212217
213- const composerAutoloaderInstallCode = installPluginComposerAutoloadersCode ( extraPlugins )
218+ const composerAutoloaderInstallCode = isolateManagedMultisitePreinstall ? null : installPluginComposerAutoloadersCode ( extraPlugins )
214219 if ( composerAutoloaderInstallCode ) {
215220 executions . push ( withRecipeExecutionPhase ( await runtime . execute ( { command : "wordpress.run-php" , args : setupPhpArgs ( composerAutoloaderInstallCode ) } ) , "setup" , - 2 , "extra-plugin.install-composer-autoloaders" ) )
216221 }
217222
218- const activatedPlugins = extraPlugins . filter ( ( plugin ) => plugin . loadAs === "plugin" && plugin . activate !== false )
223+ const deferredPluginFiles = managedPhpunitDeferredPluginFiles ( recipe , runtimeSpec )
224+ const activatedPlugins = extraPlugins . filter ( ( plugin ) => plugin . loadAs === "plugin" && plugin . activate !== false && ! deferredPluginFiles . has ( plugin . pluginFile ) )
219225 if ( activatedPlugins . length > 0 ) {
220226 const activePluginsAfterActivation = await phaseTracker . run ( "activate_plugins" , phasePluginActivationData ( activatedPlugins ) , async ( ) => {
221227 for ( const plugin of activatedPlugins ) {
@@ -243,6 +249,28 @@ export async function applyRecipeRuntimeSetup(args: {
243249 return { executions }
244250}
245251
252+ function managedPhpunitDeferredPluginFiles ( recipe : WorkspaceRecipe , runtimeSpec : Pick < RuntimeCreateSpec , "environment" | "runtimeEnv" > ) : Set < string > {
253+ const deferred = new Set < string > ( )
254+ for ( const step of [ ...( recipe . workflow . before ?? [ ] ) , ...recipe . workflow . steps , ...( recipe . workflow . after ?? [ ] ) ] ) {
255+ if ( step . command !== "wordpress.phpunit" || ! requiresManagedMysqlMultisitePreinstall ( step . args ?? [ ] , runtimeSpec ) ) continue
256+ const raw = ( step . args ?? [ ] ) . find ( ( arg ) => arg . startsWith ( "dependency-plugins-json=" ) ) ?. slice ( "dependency-plugins-json=" . length )
257+ if ( ! raw ) continue
258+ const plugins = JSON . parse ( raw ) as unknown
259+ if ( ! Array . isArray ( plugins ) ) throw new Error ( "dependency-plugins-json must be a JSON array" )
260+ for ( const plugin of plugins ) {
261+ if ( plugin && typeof plugin === "object" && ! Array . isArray ( plugin ) && typeof ( plugin as { pluginFile ?: unknown } ) . pluginFile === "string" ) {
262+ deferred . add ( ( plugin as { pluginFile : string } ) . pluginFile )
263+ }
264+ }
265+ }
266+ return deferred
267+ }
268+
269+ function recipeHasManagedMysqlMultisitePhpunit ( recipe : WorkspaceRecipe , runtimeSpec : Pick < RuntimeCreateSpec , "environment" | "runtimeEnv" > ) : boolean {
270+ return [ ...( recipe . workflow . before ?? [ ] ) , ...recipe . workflow . steps , ...( recipe . workflow . after ?? [ ] ) ]
271+ . some ( ( step ) => step . command === "wordpress.phpunit" && requiresManagedMysqlMultisitePreinstall ( step . args ?? [ ] , runtimeSpec ) )
272+ }
273+
246274export async function cleanupInputMountBaselines ( paths : string [ ] ) : Promise < void > {
247275 await Promise . all ( paths . map ( ( path ) => rm ( path , { recursive : true , force : true } ) ) )
248276 paths . length = 0
0 commit comments