6060use function WordPress \Zip \is_zip_file_stream ;
6161
6262class Runner {
63+ const EXECUTION_MODE_CREATE_NEW_SITE = 'create-new-site ' ;
64+ const EXECUTION_MODE_APPLY_TO_EXISTING_SITE = 'apply-to-existing-site ' ;
65+ const EXECUTION_MODES = [
66+ self ::EXECUTION_MODE_CREATE_NEW_SITE ,
67+ self ::EXECUTION_MODE_APPLY_TO_EXISTING_SITE ,
68+ ];
69+
6370 /**
6471 * @var RunnerConfiguration
6572 */
@@ -114,7 +121,7 @@ public function __construct( RunnerConfiguration $configuration ) {
114121 $ this ->configuration = $ configuration ;
115122 $ this ->validateConfiguration ( $ configuration );
116123
117- $ this ->client = new Client ();
124+ $ this ->client = apply_filters ( ' blueprint.http_client ' , new Client () );
118125 $ this ->mainTracker = new Tracker ();
119126
120127 // Set up progress logging
@@ -135,17 +142,17 @@ private function validateConfiguration( RunnerConfiguration $config ): void {
135142
136143 // Validate execution mode
137144 $ mode = $ config ->getExecutionMode ();
138- if ( ! in_array ( $ mode , [ ' create-new-site ' , ' apply-to-existing-site ' ] , true ) ) {
139- throw new BlueprintExecutionException ( "Execution mode must be either 'create-new-site' or 'apply-to-existing-site'. " );
145+ if ( ! in_array ( $ mode , self :: EXECUTION_MODES , true ) ) {
146+ throw new BlueprintExecutionException ( "Execution mode must be one of: " . implode ( ' , ' , self :: EXECUTION_MODES ) );
140147 }
141148
142149 // Validate site URL
143150 // Note: $options is not defined in this context, so we skip this block.
144151 // If you want to validate the site URL, you should use $config->getTargetSiteUrl().
145152 $ siteUrl = $ config ->getTargetSiteUrl ();
146- if ( $ mode === ' create-new-site ' ) {
153+ if ( $ mode === self :: EXECUTION_MODE_CREATE_NEW_SITE ) {
147154 if ( empty ( $ siteUrl ) ) {
148- throw new BlueprintExecutionException ( "Site URL is required when the execution mode is 'create-new-site '. " );
155+ throw new BlueprintExecutionException ( sprintf ( "Site URL is required when the execution mode is '%s '. " , self :: EXECUTION_MODE_CREATE_NEW_SITE ) );
149156 }
150157 }
151158 if ( ! empty ( $ siteUrl ) && ! filter_var ( $ siteUrl , FILTER_VALIDATE_URL ) ) {
@@ -195,6 +202,7 @@ private function validateConfiguration( RunnerConfiguration $config ): void {
195202
196203 public function run (): void {
197204 $ tempRoot = wp_unix_sys_get_temp_dir () . '/wp-blueprints-runtime- ' . uniqid ();
205+
198206 // TODO: Are there cases where we should not have these permissions?
199207 mkdir ( $ tempRoot , 0777 , true );
200208
@@ -227,8 +235,13 @@ public function run(): void {
227235 $ targetSiteFs = LocalFilesystem::create ( $ this ->configuration ->getTargetSiteRoot () );
228236 $ wpCliReference = DataReference::create ( 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar ' );
229237
230- $ execution_context_root = $ this ->blueprintExecutionContext ->get_meta ()['root ' ];
231- assert (is_string ($ execution_context_root ) && strlen ($ execution_context_root ) > 0 , 'Assertion failed: Execution context root was empty. ' );
238+ $ execution_context = $ this ->blueprintExecutionContext ->get_meta ();
239+ if (
240+ isset ($ execution_context ['root ' ]) &&
241+ ( !is_string ($ execution_context ['root ' ]) || strlen ($ execution_context ['root ' ]) === 0 )
242+ ) {
243+ throw new BlueprintExecutionException ('Execution context was a local directory, but the Runner could not determine the root directory. This should never happen. Please report this as a bug. ' );
244+ }
232245
233246 $ this ->runtime = new Runtime (
234247 $ targetSiteFs ,
@@ -238,7 +251,7 @@ public function run(): void {
238251 $ this ->blueprintArray ,
239252 $ tempRoot ,
240253 $ wpCliReference ,
241- $ execution_context_root
254+ isset ( $ execution_context [ ' root ' ]) ? $ execution_context [ ' root ' ] : null
242255 );
243256 $ this ->progressObserver ->setRuntime ( $ this ->runtime );
244257 $ progress ['wpCli ' ]->setCaption ( 'Downloading WP-CLI ' );
@@ -247,16 +260,21 @@ public function run(): void {
247260 ], $ progress ['wpCli ' ] );
248261
249262 $ progress ['targetResolution ' ]->setCaption ( 'Resolving target site ' );
250- if ( $ this ->configuration ->getExecutionMode () === ' apply-to-existing-site ' ) {
263+ if ( $ this ->configuration ->getExecutionMode () === self :: EXECUTION_MODE_APPLY_TO_EXISTING_SITE ) {
251264 ExistingSiteResolver::resolve ( $ this ->runtime , $ progress ['targetResolution ' ], $ this ->wpVersionConstraint );
252265 } else {
253266 NewSiteResolver::resolve ( $ this ->runtime , $ progress ['targetResolution ' ], $ this ->wpVersionConstraint , $ this ->recommendedWpVersion );
254267 }
255268 $ progress ['targetResolution ' ]->finish ();
256269
270+ do_action ('blueprint.target_resolved ' );
271+
257272 $ progress ['data ' ]->setCaption ( 'Resolving data references ' );
258273 $ this ->assets ->startEagerResolution ( $ this ->dataReferencesToAutoResolve , $ progress ['data ' ] );
259274 $ this ->executePlan ( $ progress ['execution ' ], $ plan , $ this ->runtime );
275+
276+ // @TODO: Assert WordPress is still correctly installed
277+
260278 $ progress ->finish ();
261279 } finally {
262280 // TODO: Optionally preserve workspace in case of error? Support resuming after error?
@@ -289,7 +307,14 @@ private function loadBlueprint() {
289307 $ blueprintString = $ resolved ->getStream ()->consume_all ();
290308 $ this ->blueprintExecutionContext = LocalFilesystem::create ( dirname ( $ reference ->get_path () ) );
291309 } else {
310+ // For the purposes of Blueprint resolution, the execution context is the
311+ // current working directory. This way, a path such as ./blueprint.json
312+ // will mean "a blueprint.json file in the current working directory" and not
313+ // "a ./blueprint.json path without a point of reference".
314+ $ this ->assets ->setExecutionContext ( LocalFilesystem::create ( getcwd () ) );
292315 $ resolved = $ this ->assets ->resolve ( $ reference );
316+ $ this ->assets ->setExecutionContext ( null );
317+
293318 if ( $ resolved instanceof File ) {
294319 $ stream = $ resolved ->getStream ();
295320
@@ -377,6 +402,8 @@ private function validateBlueprint(): void {
377402
378403 $ this ->configuration ->getLogger ()->debug ( 'Final resolved Blueprint: ' . json_encode ( $ this ->blueprintArray , JSON_PRETTY_PRINT ) );
379404
405+ $ this ->blueprintArray = apply_filters ( 'blueprint.resolved ' , $ this ->blueprintArray );
406+
380407 // Assert the Blueprint conforms to the latest JSON schema.
381408 $ v = new HumanFriendlySchemaValidator (
382409 json_decode ( file_get_contents ( __DIR__ . '/Versions/Version2/json-schema/schema-v2.json ' ), true )
@@ -439,7 +466,7 @@ private function validateBlueprint(): void {
439466 // WordPress Version Constraint
440467 if ( isset ( $ this ->blueprintArray ['wordpressVersion ' ] ) ) {
441468 $ wp_version = $ this ->blueprintArray ['wordpressVersion ' ];
442- $ recommended = null ;
469+ $ min = $ max = $ recommended = null ;
443470 if ( is_string ( $ wp_version ) ) {
444471 $ this ->recommendedWpVersion = $ wp_version ;
445472 $ recommended = WordPressVersion::fromString ( $ wp_version );
@@ -466,6 +493,8 @@ private function validateBlueprint(): void {
466493 $ this ->recommendedWpVersion = $ wp_version ['max ' ];
467494 $ max = WordPressVersion::fromString ( $ wp_version ['max ' ] );
468495 if ( ! $ max ) {
496+ // @TODO: Reuse this error message
497+ // 'Unrecognized WordPress version. Please use "latest", a URL, or a numeric version such as "6.2", "6.0.1", "6.2-beta1", or "6.2-RC1"'
469498 throw new BlueprintExecutionException ( 'Invalid WordPress version string in wordpressVersion.max: ' . $ wp_version ['max ' ] );
470499 }
471500 }
@@ -487,6 +516,14 @@ private function validateBlueprint(): void {
487516 // correctly. The actual version check for WordPress is done in
488517 // NewSiteResolver and ExistingSiteResolver.
489518 }
519+
520+ // Validate the override constraint if it was set
521+ if ( $ this ->wpVersionConstraint ) {
522+ $ wpConstraintErrors = $ this ->wpVersionConstraint ->validate ();
523+ if ( ! empty ( $ wpConstraintErrors ) ) {
524+ throw new BlueprintExecutionException ( 'Invalid WordPress version constraint from CLI override: ' . implode ( '; ' , $ wpConstraintErrors ) );
525+ }
526+ }
490527 }
491528
492529 private function createExecutionPlan (): array {
@@ -627,9 +664,9 @@ private function createExecutionPlan(): array {
627664 // @TODO: Make sure this doesn't get included twice in the execution plan,
628665 // e.g. if the Blueprint specified this step manually.
629666 if ( $ step instanceof ImportContentStep ) {
630- if ($ this ->configuration ->isRunningAsPhar ()) {
631- throw new InvalidArgumentException ( '@TODO: Importing content is not supported when running as phar. ' );
632- } else {
667+ // if($this->configuration->isRunningAsPhar()) {
668+ // throw new InvalidArgumentException( '@TODO: Importing content is not supported when running as phar.' );
669+ // } else {
633670 $ libraries_phar_path = __DIR__ . '/../../dist/php-toolkit.phar ' ;
634671 if (!file_exists ($ libraries_phar_path )) {
635672 throw new InvalidArgumentException (
@@ -642,7 +679,7 @@ private function createExecutionPlan(): array {
642679 'filename ' => 'php-toolkit.phar ' ,
643680 'content ' => file_get_contents ( $ libraries_phar_path )
644681 ] ) );
645- }
682+ // }
646683 array_unshift ( $ plan , $ this ->createStepObject ( 'writeFiles ' , [
647684 'files ' => [
648685 'php-toolkit.phar ' => $ source ,
@@ -923,14 +960,6 @@ static function () {
923960
924961 return new WriteFilesStep ( $ files );
925962
926- case 'runPHP ' :
927- return new RunPHPStep (
928- $ this ->createDataReference ( [
929- 'filename ' => 'run-php.php ' ,
930- 'content ' => $ data ['code ' ],
931- ] ),
932- $ data ['env ' ] ?? []
933- );
934963 case 'unzip ' :
935964 $ zipFile = $ this ->createDataReference ( $ data ['zipFile ' ], [ ExecutionContextPath::class ] );
936965
0 commit comments