From 02300f9b796bfe0501f5de3498efb43073de2cc3 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 27 Mar 2026 15:38:19 +0100 Subject: [PATCH 01/22] Try windows logging --- bin/run-behat-tests | 2 +- src/Context/FeatureContext.php | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/run-behat-tests b/bin/run-behat-tests index 78160971..c26ae7fe 100755 --- a/bin/run-behat-tests +++ b/bin/run-behat-tests @@ -134,7 +134,7 @@ if [[ "${WP_CLI_TEST_COVERAGE}" == "true" ]] && vendor/bin/behat --help 2>/dev/n fi # Run the functional tests. -FORMAT_ARGS=(--format progress) +FORMAT_ARGS=(--format pretty) for arg in "$@"; do if [[ "$arg" == "--format"* ]]; then FORMAT_ARGS=() diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 494d3454..df14bc18 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1299,6 +1299,9 @@ private static function run_sql( $sql_cmd, $assoc_args = [], $add_database = fal } $start_time = microtime( true ); + if ( Utils\is_windows() ) { + fwrite( STDERR, "DEBUG WINDOWS SQL: sql_cmd={$sql_cmd} assoc_args=" . json_encode($assoc_args) . "\n" ); + } $result = Utils\run_mysql_command( $sql_cmd, array_merge( $assoc_args, $default_assoc_args ), null, $send_to_shell ); if ( self::$log_run_times ) { self::log_proc_method_run_time( 'run_sql ' . $sql_cmd, $start_time ); @@ -1371,6 +1374,10 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { $env = self::get_process_env_variables(); + if ( Utils\is_windows() ) { + fwrite( STDERR, "DEBUG WINDOWS PROC: command={$command} path=" . ( $path ?: 'null' ) . "\n" ); + } + if ( isset( $this->variables['SUITE_CACHE_DIR'] ) ) { $env['WP_CLI_CACHE_DIR'] = $this->variables['SUITE_CACHE_DIR']; } @@ -1407,6 +1414,7 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { */ public function background_proc( $cmd ): void { if ( Utils\is_windows() ) { + fwrite( STDERR, "DEBUG WINDOWS BG_PROC: cmd={$cmd}\n" ); // On Windows, leaving pipes open can cause hangs. // Redirect output to files and close stdin. $stdout_file = tempnam( sys_get_temp_dir(), 'behat-stdout-' ); From 7a2a75bf03a51578116242d2ba84fa743887305c Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 27 Mar 2026 18:50:14 +0100 Subject: [PATCH 02/22] inject T(E)MP var into sub-processes --- src/Context/FeatureContext.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index df14bc18..48baa990 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -462,6 +462,11 @@ private static function get_process_env_variables(): array { 'TEST_RUN_DIR' => self::$behat_run_dir, ]; + if ( Utils\is_windows() ) { + $env['TMP'] = getenv( 'TMP' ) ?: sys_get_temp_dir(); + $env['TEMP'] = getenv( 'TEMP' ) ?: sys_get_temp_dir(); + } + $env = array_merge( $_ENV, $env ); if ( self::running_with_code_coverage() ) { From a8ca70d61de496535cc8ec2731241577709c1de5 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 27 Mar 2026 18:53:57 +0100 Subject: [PATCH 03/22] realpath run_dir --- src/Context/FeatureContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 48baa990..3a21cfc6 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1188,7 +1188,7 @@ private static function get_event_file( $scope, &$line ): ?string { */ public function create_run_dir(): void { if ( ! isset( $this->variables['RUN_DIR'] ) ) { - self::$run_dir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', true ); + self::$run_dir = realpath( sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', true ) ); $this->variables['RUN_DIR'] = self::$run_dir; mkdir( $this->variables['RUN_DIR'] ); } From 6b5c279907937693b3782ca449fca4967d8ec6e7 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 00:08:34 +0100 Subject: [PATCH 04/22] mkdir tweak --- src/Context/FeatureContext.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 3a21cfc6..d190dc68 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1188,9 +1188,10 @@ private static function get_event_file( $scope, &$line ): ?string { */ public function create_run_dir(): void { if ( ! isset( $this->variables['RUN_DIR'] ) ) { - self::$run_dir = realpath( sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', true ) ); + $temp_run_dir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', true ); + mkdir( $temp_run_dir ); + self::$run_dir = realpath( $temp_run_dir ); $this->variables['RUN_DIR'] = self::$run_dir; - mkdir( $this->variables['RUN_DIR'] ); } } From 262e616eeccfdb46c9391be51cf2afa0df8b57eb Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 12:24:42 +0100 Subject: [PATCH 05/22] escape % to %% for all commands run on Windows This prevents cmd.exe from misinterpreting %...% as undefined environment variables and stripping them. --- src/Context/FeatureContext.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index d190dc68..a857e724 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1378,6 +1378,10 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { $command .= Utils\assoc_args_to_str( $assoc_args ); } + if ( Utils\is_windows() ) { + $command = str_replace( '%', '%%', $command ); + } + $env = self::get_process_env_variables(); if ( Utils\is_windows() ) { @@ -1420,6 +1424,7 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { */ public function background_proc( $cmd ): void { if ( Utils\is_windows() ) { + $cmd = str_replace( '%', '%%', $cmd ); fwrite( STDERR, "DEBUG WINDOWS BG_PROC: cmd={$cmd}\n" ); // On Windows, leaving pipes open can cause hangs. // Redirect output to files and close stdin. From 2c0c130a64696fbf56229ae33471f3a80c014044 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 13:14:31 +0100 Subject: [PATCH 06/22] remove logging again --- src/Context/FeatureContext.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index a857e724..bbc348f1 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1305,9 +1305,6 @@ private static function run_sql( $sql_cmd, $assoc_args = [], $add_database = fal } $start_time = microtime( true ); - if ( Utils\is_windows() ) { - fwrite( STDERR, "DEBUG WINDOWS SQL: sql_cmd={$sql_cmd} assoc_args=" . json_encode($assoc_args) . "\n" ); - } $result = Utils\run_mysql_command( $sql_cmd, array_merge( $assoc_args, $default_assoc_args ), null, $send_to_shell ); if ( self::$log_run_times ) { self::log_proc_method_run_time( 'run_sql ' . $sql_cmd, $start_time ); @@ -1384,10 +1381,6 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { $env = self::get_process_env_variables(); - if ( Utils\is_windows() ) { - fwrite( STDERR, "DEBUG WINDOWS PROC: command={$command} path=" . ( $path ?: 'null' ) . "\n" ); - } - if ( isset( $this->variables['SUITE_CACHE_DIR'] ) ) { $env['WP_CLI_CACHE_DIR'] = $this->variables['SUITE_CACHE_DIR']; } @@ -1425,7 +1418,6 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { public function background_proc( $cmd ): void { if ( Utils\is_windows() ) { $cmd = str_replace( '%', '%%', $cmd ); - fwrite( STDERR, "DEBUG WINDOWS BG_PROC: cmd={$cmd}\n" ); // On Windows, leaving pipes open can cause hangs. // Redirect output to files and close stdin. $stdout_file = tempnam( sys_get_temp_dir(), 'behat-stdout-' ); From 509b57ddb22386f8058ee89ce88e9e15791d2f7d Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 16:19:52 +0100 Subject: [PATCH 07/22] real path --- src/Context/GivenStepDefinitions.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Context/GivenStepDefinitions.php b/src/Context/GivenStepDefinitions.php index 3d88e70f..a3ce0684 100644 --- a/src/Context/GivenStepDefinitions.php +++ b/src/Context/GivenStepDefinitions.php @@ -54,6 +54,9 @@ public function given_a_specific_directory( $empty_or_nonexistent, $dir ): void $dir = preg_replace( '|^/private/var/|', '/var/', $dir ); $temp_dir = sys_get_temp_dir(); + if ( Utils\is_windows() ) { + $temp_dir = realpath( $temp_dir ) ?: $temp_dir; + } // Also check for temp dir prefixed with `/private` for Mac OS X. if ( 0 !== strpos( $dir, $temp_dir ) && 0 !== strpos( $dir, "/private{$temp_dir}" ) ) { From 44a832f223900f43682e206c602b1472011353d6 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 23:44:21 +0100 Subject: [PATCH 08/22] Partial revert --- src/Context/FeatureContext.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index bbc348f1..bd877da1 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1190,7 +1190,7 @@ public function create_run_dir(): void { if ( ! isset( $this->variables['RUN_DIR'] ) ) { $temp_run_dir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', true ); mkdir( $temp_run_dir ); - self::$run_dir = realpath( $temp_run_dir ); + self::$run_dir = $temp_run_dir; $this->variables['RUN_DIR'] = self::$run_dir; } } @@ -1375,10 +1375,6 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { $command .= Utils\assoc_args_to_str( $assoc_args ); } - if ( Utils\is_windows() ) { - $command = str_replace( '%', '%%', $command ); - } - $env = self::get_process_env_variables(); if ( isset( $this->variables['SUITE_CACHE_DIR'] ) ) { @@ -1417,7 +1413,6 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { */ public function background_proc( $cmd ): void { if ( Utils\is_windows() ) { - $cmd = str_replace( '%', '%%', $cmd ); // On Windows, leaving pipes open can cause hangs. // Redirect output to files and close stdin. $stdout_file = tempnam( sys_get_temp_dir(), 'behat-stdout-' ); From 661f366f835261bcd427017359002c5714de5cb6 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 12:56:53 +0200 Subject: [PATCH 09/22] undo --- src/Context/GivenStepDefinitions.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Context/GivenStepDefinitions.php b/src/Context/GivenStepDefinitions.php index a3ce0684..3d88e70f 100644 --- a/src/Context/GivenStepDefinitions.php +++ b/src/Context/GivenStepDefinitions.php @@ -54,9 +54,6 @@ public function given_a_specific_directory( $empty_or_nonexistent, $dir ): void $dir = preg_replace( '|^/private/var/|', '/var/', $dir ); $temp_dir = sys_get_temp_dir(); - if ( Utils\is_windows() ) { - $temp_dir = realpath( $temp_dir ) ?: $temp_dir; - } // Also check for temp dir prefixed with `/private` for Mac OS X. if ( 0 !== strpos( $dir, $temp_dir ) && 0 !== strpos( $dir, "/private{$temp_dir}" ) ) { From ecb21c08984c03ec3bdee0deb5f369ca16b879f3 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 15:47:05 +0200 Subject: [PATCH 10/22] revert for testing --- src/Context/FeatureContext.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index bd877da1..d04cd714 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -462,11 +462,6 @@ private static function get_process_env_variables(): array { 'TEST_RUN_DIR' => self::$behat_run_dir, ]; - if ( Utils\is_windows() ) { - $env['TMP'] = getenv( 'TMP' ) ?: sys_get_temp_dir(); - $env['TEMP'] = getenv( 'TEMP' ) ?: sys_get_temp_dir(); - } - $env = array_merge( $_ENV, $env ); if ( self::running_with_code_coverage() ) { From be25fa49df50b68f8d519863a288036d60cbccb6 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 16:14:22 +0200 Subject: [PATCH 11/22] pass down differently --- src/Context/FeatureContext.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index d04cd714..513df540 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -464,6 +464,13 @@ private static function get_process_env_variables(): array { $env = array_merge( $_ENV, $env ); + foreach ( [ 'TEMP', 'TMP' ] as $key ) { + $value = getenv( $key ); + if ( false !== $value ) { + $env[ $key ] = $value; + } + } + if ( self::running_with_code_coverage() ) { $has_coverage_driver = ( new Runtime() )->hasXdebug() || ( new Runtime() )->hasPCOV(); From 63834f93d7a3ff27a67fab8be69ae0638da20f93 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 16:30:51 +0200 Subject: [PATCH 12/22] add test --- features/testing.feature | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/features/testing.feature b/features/testing.feature index 8705258c..5336f946 100644 --- a/features/testing.feature +++ b/features/testing.feature @@ -125,3 +125,11 @@ Feature: Test that WP-CLI loads. This should only run on MySQL or MariaDB """ + Scenario: Verify sys_get_temp_dir() in sub-process + Given a WP install + When I run `wp eval 'echo sys_get_temp_dir();'` + Then STDOUT should not be: + """ + C:\Windows + """ + From 2f15de81ed35871ca14c9e7d0c6bcba4442301d0 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 16:33:31 +0200 Subject: [PATCH 13/22] add windir and systemroot --- src/Context/FeatureContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 513df540..ad2a5990 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -464,7 +464,7 @@ private static function get_process_env_variables(): array { $env = array_merge( $_ENV, $env ); - foreach ( [ 'TEMP', 'TMP' ] as $key ) { + foreach ( [ 'TEMP', 'TMP', 'SystemRoot', 'windir' ] as $key ) { $value = getenv( $key ); if ( false !== $value ) { $env[ $key ] = $value; From 8d0e2ce47bcd1a74e168d547bbe451fdb07c723f Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 17:02:37 +0200 Subject: [PATCH 14/22] fix matcher --- features/testing.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/testing.feature b/features/testing.feature index 5336f946..ae5a078d 100644 --- a/features/testing.feature +++ b/features/testing.feature @@ -128,7 +128,7 @@ Feature: Test that WP-CLI loads. Scenario: Verify sys_get_temp_dir() in sub-process Given a WP install When I run `wp eval 'echo sys_get_temp_dir();'` - Then STDOUT should not be: + Then STDOUT should not contain: """ C:\Windows """ From 4da0cd2c9a0f3c40afc7533c756c114198fdbd97 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 17:17:58 +0200 Subject: [PATCH 15/22] more resilient downloading --- src/Context/FeatureContext.php | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index ad2a5990..8cee7714 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -690,7 +690,7 @@ private static function configure_sqlite( $dir ): void { private static function cache_wp_files( $version = '' ): void { $wp_version = $version ?: getenv( 'WP_VERSION' ); $wp_version_suffix = $wp_version ? "-$wp_version" : ''; - self::$cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-download-cache' . $wp_version_suffix; + $cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-download-cache' . $wp_version_suffix; self::$sqlite_cache_dir = sys_get_temp_dir() . '/wp-cli-test-sqlite-integration-cache'; if ( 'sqlite' === getenv( 'WP_CLI_TEST_DBTYPE' ) ) { @@ -706,15 +706,34 @@ private static function cache_wp_files( $version = '' ): void { } } - if ( is_readable( self::$cache_dir . '/wp-config-sample.php' ) ) { + if ( is_readable( $cache_dir . '/wp-includes/version.php' ) ) { + self::$cache_dir = $cache_dir; return; } - $cmd = Utils\esc_cmd( 'wp core download --force --path=%s', self::$cache_dir ); + $cmd = Utils\esc_cmd( 'wp core download --force --path=%s', $cache_dir ); if ( $wp_version ) { $cmd .= Utils\esc_cmd( ' --version=%s', $wp_version ); } - Process::create( $cmd, null, self::get_process_env_variables() )->run_check(); + + $max_retries = 3; + $retry_count = 0; + $completed = false; + + // This is in addition to the retry logic inside Utils\http_request(). + while ( $retry_count < $max_retries && ! $completed ) { + try { + Process::create( $cmd, null, self::get_process_env_variables() )->run_check(); + $completed = true; + } catch ( \Exception $e ) { + ++$retry_count; + if ( $retry_count >= $max_retries ) { + throw $e; + } + } + } + + self::$cache_dir = $cache_dir; } /** @@ -1533,7 +1552,7 @@ public static function copy_dir( $src_dir, $dest_dir ): void { * @var \SplFileInfo $item */ foreach ( $iterator as $item ) { - $dest_path = $dest_dir . '/' . $iterator->getSubPathname(); + $dest_path = rtrim( $dest_dir, '/\\' ) . DIRECTORY_SEPARATOR . $iterator->getSubPathname(); if ( $item->isDir() ) { if ( ! is_dir( $dest_path ) ) { mkdir( $dest_path, 0777, true ); @@ -1573,7 +1592,7 @@ public function download_wp( $subdir = '', $version = '' ): void { echo "WordPress {$result->stdout}\n"; } - $dest_dir = $this->variables['RUN_DIR'] . "/$subdir"; + $dest_dir = rtrim( $this->variables['RUN_DIR'], '/\\' ) . ( $subdir ? DIRECTORY_SEPARATOR . $subdir : '' ); if ( $subdir ) { mkdir( $dest_dir, 0777, true /*recursive*/ ); From 7927152eded691cc94a647ba83537cb2e7f894a6 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 20:19:25 +0200 Subject: [PATCH 16/22] undo --- bin/run-behat-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/run-behat-tests b/bin/run-behat-tests index c26ae7fe..78160971 100755 --- a/bin/run-behat-tests +++ b/bin/run-behat-tests @@ -134,7 +134,7 @@ if [[ "${WP_CLI_TEST_COVERAGE}" == "true" ]] && vendor/bin/behat --help 2>/dev/n fi # Run the functional tests. -FORMAT_ARGS=(--format pretty) +FORMAT_ARGS=(--format progress) for arg in "$@"; do if [[ "$arg" == "--format"* ]]; then FORMAT_ARGS=() From b7a36f7b3c7f003efe63b0572b6e08de7b058ee5 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 30 Mar 2026 16:57:51 +0200 Subject: [PATCH 17/22] Split by newline instead --- src/Context/ThenStepDefinitions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Context/ThenStepDefinitions.php b/src/Context/ThenStepDefinitions.php index 24976511..bc267235 100644 --- a/src/Context/ThenStepDefinitions.php +++ b/src/Context/ThenStepDefinitions.php @@ -499,7 +499,7 @@ public function then_a_specific_file_folder_should_exist( $path, $type, $strictl foreach ( $files as &$file ) { $file = str_replace( $path . '/', '', $file ); } - $contents = implode( PHP_EOL, $files ); + $contents = implode( "\n", $files ); } $this->check_string( $contents, $expected, $action, false, (bool) $strictly ); } From 3e1fe8db335b26cec7cf4c86b53f33ae8efec7a1 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 30 Mar 2026 22:11:26 +0200 Subject: [PATCH 18/22] Prepend 'php ' on Windows if the command starts with the phar path. and other fixes --- src/Context/FeatureContext.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 8cee7714..493f1c25 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1220,7 +1220,7 @@ public function create_run_dir(): void { * @param string $version */ public function build_phar( $version = 'same' ): void { - $this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . '/' . uniqid( 'wp-cli-build-', true ) . '.phar'; + $this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . DIRECTORY_SEPARATOR . uniqid( 'wp-cli-build-', true ) . '.phar'; $is_bundle = false; @@ -1276,7 +1276,7 @@ public function download_phar( $version = 'same' ): void { $version ); - $this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . '/' + $this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . DIRECTORY_SEPARATOR . uniqid( 'wp-cli-download-', true ) . '.phar'; @@ -1396,6 +1396,11 @@ public function proc( $command, $assoc_args = [], $path = '' ): Process { $command .= Utils\assoc_args_to_str( $assoc_args ); } + // Prepend 'php ' on Windows if the command starts with the phar path. + if ( Utils\is_windows() && isset( $this->variables['PHAR_PATH'] ) && 0 === strpos( $command, $this->variables['PHAR_PATH'] ) ) { + $command = 'php ' . $command; + } + $env = self::get_process_env_variables(); if ( isset( $this->variables['SUITE_CACHE_DIR'] ) ) { @@ -1860,8 +1865,12 @@ public function composer_add_wp_cli_local_repository(): void { self::remove_dir( self::$composer_local_repository . '/.git' ); self::remove_dir( self::$composer_local_repository . '/vendor' ); } - $dest = self::$composer_local_repository . '/'; - $this->composer_command( "config repositories.wp-cli '{\"type\": \"path\", \"url\": \"$dest\", \"options\": {\"symlink\": false, \"versions\": { \"wp-cli/wp-cli\": \"dev-main\"}}}'" ); + if ( Utils\is_windows() ) { + $json_config = '{\"type\": \"path\", \"url\": \"' . str_replace( '\\', '/', $dest ) . '\", \"options\": {\"symlink\": false, \"versions\": { \"wp-cli/wp-cli\": \"dev-main\"}}}'; + $this->composer_command( "config repositories.wp-cli \"$json_config\"" ); + } else { + $this->composer_command( "config repositories.wp-cli '{\"type\": \"path\", \"url\": \"$dest\", \"options\": {\"symlink\": false, \"versions\": { \"wp-cli/wp-cli\": \"dev-main\"}}}'" ); + } $this->variables['COMPOSER_LOCAL_REPOSITORY'] = self::$composer_local_repository; } From 6c2e67a9c08408917991e030001aee9afa8e4a58 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 30 Mar 2026 22:26:50 +0200 Subject: [PATCH 19/22] fix --- src/Context/FeatureContext.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 493f1c25..5be4a7b2 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1866,10 +1866,10 @@ public function composer_add_wp_cli_local_repository(): void { self::remove_dir( self::$composer_local_repository . '/vendor' ); } if ( Utils\is_windows() ) { - $json_config = '{\"type\": \"path\", \"url\": \"' . str_replace( '\\', '/', $dest ) . '\", \"options\": {\"symlink\": false, \"versions\": { \"wp-cli/wp-cli\": \"dev-main\"}}}'; + $json_config = '{\"type\": \"path\", \"url\": \"' . str_replace( '\\', '/', self::$composer_local_repository ) . '\", \"options\": {\"symlink\": false, \"versions\": { \"wp-cli/wp-cli\": \"dev-main\"}}}'; $this->composer_command( "config repositories.wp-cli \"$json_config\"" ); } else { - $this->composer_command( "config repositories.wp-cli '{\"type\": \"path\", \"url\": \"$dest\", \"options\": {\"symlink\": false, \"versions\": { \"wp-cli/wp-cli\": \"dev-main\"}}}'" ); + $this->composer_command( "config repositories.wp-cli '{\"type\": \"path\", \"url\": \"" . self::$composer_local_repository . "\", \"options\": {\"symlink\": false, \"versions\": { \"wp-cli/wp-cli\": \"dev-main\"}}}'" ); } $this->variables['COMPOSER_LOCAL_REPOSITORY'] = self::$composer_local_repository; } From 13a813d0d2dfd1565384dee88c7384d35090c2d5 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 31 Mar 2026 10:28:40 +0200 Subject: [PATCH 20/22] try hardening in remove_dir --- src/Context/FeatureContext.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Context/FeatureContext.php b/src/Context/FeatureContext.php index 5be4a7b2..f8f20e40 100644 --- a/src/Context/FeatureContext.php +++ b/src/Context/FeatureContext.php @@ -1514,6 +1514,8 @@ public function move_files( $src, $dest ): void { * @param string $dir */ public static function remove_dir( $dir ): void { + $dir = Path::normalize( $dir ); + $dir = rtrim( $dir, '/\\' ); if ( ! is_dir( $dir ) ) { return; } From 149885daf2e4d0169d6e50151786f5b72afa1c29 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 31 Mar 2026 12:29:36 +0200 Subject: [PATCH 21/22] Normalize dir names --- src/Context/GivenStepDefinitions.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Context/GivenStepDefinitions.php b/src/Context/GivenStepDefinitions.php index 3d88e70f..8c984cf0 100644 --- a/src/Context/GivenStepDefinitions.php +++ b/src/Context/GivenStepDefinitions.php @@ -53,7 +53,8 @@ public function given_a_specific_directory( $empty_or_nonexistent, $dir ): void // Mac OS X can prefix the `/var` folder to turn it into `/private/var`. $dir = preg_replace( '|^/private/var/|', '/var/', $dir ); - $temp_dir = sys_get_temp_dir(); + $temp_dir = Path::normalize( sys_get_temp_dir() ); + $dir = Path::normalize( $dir ); // Also check for temp dir prefixed with `/private` for Mac OS X. if ( 0 !== strpos( $dir, $temp_dir ) && 0 !== strpos( $dir, "/private{$temp_dir}" ) ) { From 20422506a1f89c8b6a9da95e29937dbe61089582 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 31 Mar 2026 15:18:11 +0200 Subject: [PATCH 22/22] fix indentation --- features/testing.feature | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/features/testing.feature b/features/testing.feature index ae5a078d..5c2853ba 100644 --- a/features/testing.feature +++ b/features/testing.feature @@ -129,7 +129,7 @@ Feature: Test that WP-CLI loads. Given a WP install When I run `wp eval 'echo sys_get_temp_dir();'` Then STDOUT should not contain: - """ - C:\Windows - """ + """ + C:\Windows + """