From 0d1213e09bbd87bd57ece4cdf9007685df3a8389 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 12:34:45 +0100 Subject: [PATCH 01/14] Use curl instead of wget in test --- features/package-install.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/package-install.feature b/features/package-install.feature index bfa9338d..bbdc6571 100644 --- a/features/package-install.feature +++ b/features/package-install.feature @@ -957,7 +957,7 @@ Feature: Install WP-CLI packages Scenario: Install a package from a local zip Given an empty directory - And I run `wget -q -O google-sitemap-generator-cli.zip https://github.com/wp-cli/google-sitemap-generator-cli/archive/master.zip` + And I run `curl -sS -L -o google-sitemap-generator-cli.zip https://github.com/wp-cli/google-sitemap-generator-cli/archive/master.zip` When I run `wp package install google-sitemap-generator-cli.zip` Then STDOUT should contain: From 08e15b62e6b5378a7a76374782647699f23e9f61 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 23:31:18 +0100 Subject: [PATCH 02/14] Update features/package-install.feature --- features/package-install.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/package-install.feature b/features/package-install.feature index bbdc6571..14ac5185 100644 --- a/features/package-install.feature +++ b/features/package-install.feature @@ -957,7 +957,7 @@ Feature: Install WP-CLI packages Scenario: Install a package from a local zip Given an empty directory - And I run `curl -sS -L -o google-sitemap-generator-cli.zip https://github.com/wp-cli/google-sitemap-generator-cli/archive/master.zip` + And I run `curl -sS -L -o google-sitemap-generator-cli.zip https://github.com/wp-cli/google-sitemap-generator-cli/archive/main.zip` When I run `wp package install google-sitemap-generator-cli.zip` Then STDOUT should contain: From cbefff3dcbc9ae9ef18e9596560bd562b94c4a3a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 23:45:23 +0100 Subject: [PATCH 03/14] Update some unit tests for Windows --- tests/phpunit/ComposerJsonTest.php | 13 +++++++++++-- tests/phpunit/JsonManipulatorTest.php | 10 ++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/phpunit/ComposerJsonTest.php b/tests/phpunit/ComposerJsonTest.php index c1cdf9b6..e0a433a4 100644 --- a/tests/phpunit/ComposerJsonTest.php +++ b/tests/phpunit/ComposerJsonTest.php @@ -37,7 +37,12 @@ public function set_up() { } $class_wp_cli_capture_exit->setValue( null, true ); - $this->temp_dir = Utils\get_temp_dir() . uniqid( 'wp-cli-test-package-composer-json-', true ) . '/'; + $temp_dir = Utils\get_temp_dir(); + if ( Utils\is_windows() ) { + $temp_dir = realpath( $temp_dir ) ?: $temp_dir; + $temp_dir = str_replace( '\\', '/', $temp_dir ); + } + $this->temp_dir = rtrim( $temp_dir, '/' ) . '/' . uniqid( 'wp-cli-test-package-composer-json-', true ) . '/'; mkdir( $this->temp_dir ); } @@ -193,6 +198,10 @@ public function test_get_composer_json_path_backup_decoded() { } private function mac_safe_path( $path ) { - return preg_replace( '#^/private/(var|tmp)/#i', '/$1/', $path ); + $path = preg_replace( '#^/private/(var|tmp)/#i', '/$1/', $path ); + if ( Utils\is_windows() ) { + $path = str_replace( '\\', '/', $path ); + } + return $path; } } diff --git a/tests/phpunit/JsonManipulatorTest.php b/tests/phpunit/JsonManipulatorTest.php index 156ab7f9..32ceb356 100644 --- a/tests/phpunit/JsonManipulatorTest.php +++ b/tests/phpunit/JsonManipulatorTest.php @@ -2167,10 +2167,11 @@ public function testAddMainKeyWithContentHavingDollarSignFollowedByDigit2() $manipulator = new JsonManipulator('{}'); $this->assertTrue($manipulator->addMainKey('foo', '$1bar')); - $this->assertEquals('{ + $expected = '{ "foo": "$1bar" } -', $manipulator->getContents()); +'; + $this->assertEquals( str_replace( "\r\n", "\n", $expected ), str_replace( "\r\n", "\n", $manipulator->getContents() ) ); } public function testUpdateMainKey() @@ -2301,9 +2302,10 @@ public function testRemoveMainKey() $this->assertTrue($manipulator->removeMainKey('require')); $this->assertTrue($manipulator->removeMainKey('require-dev')); - $this->assertEquals('{ + $expected = '{ } -', $manipulator->getContents()); +'; + $this->assertEquals( str_replace( "\r\n", "\n", $expected ), str_replace( "\r\n", "\n", $manipulator->getContents() ) ); } public function testIndentDetection() From bc5dbbc4b9f740fd9484f89b5d6a1babe4bf6ad5 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 23:56:42 +0100 Subject: [PATCH 04/14] Update features/package-install.feature Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- features/package-install.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/package-install.feature b/features/package-install.feature index 14ac5185..b9b5d12c 100644 --- a/features/package-install.feature +++ b/features/package-install.feature @@ -957,7 +957,7 @@ Feature: Install WP-CLI packages Scenario: Install a package from a local zip Given an empty directory - And I run `curl -sS -L -o google-sitemap-generator-cli.zip https://github.com/wp-cli/google-sitemap-generator-cli/archive/main.zip` + And I run `curl -fSsL -o google-sitemap-generator-cli.zip https://github.com/wp-cli/google-sitemap-generator-cli/archive/main.zip` When I run `wp package install google-sitemap-generator-cli.zip` Then STDOUT should contain: From fcecbd679a453d75ab710b07e397660a7de3633f Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sat, 28 Mar 2026 23:58:53 +0100 Subject: [PATCH 05/14] tweak more tests --- features/package-install.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/package-install.feature b/features/package-install.feature index b9b5d12c..a4e441c4 100644 --- a/features/package-install.feature +++ b/features/package-install.feature @@ -243,7 +243,7 @@ Feature: Install WP-CLI packages """ And the {PACKAGE_PATH}composer.json file should not contain: """ - "wp-cli/google-sitemap-generator-cli": "dev-master" + "wp-cli/google-sitemap-generator-cli": """ And the {PACKAGE_PATH}composer.json file should not contain: """ @@ -1093,7 +1093,7 @@ Feature: Install WP-CLI packages Error: Couldn't download package from 'https://github.com/wp-cli/google-sitemap-generator.zip' (HTTP code 404). """ - When I run `wp package install https://github.com/wp-cli/google-sitemap-generator-cli/archive/master.zip` + When I run `wp package install https://github.com/wp-cli/google-sitemap-generator-cli/archive/main.zip` Then STDOUT should contain: """ Installing package wp-cli/google-sitemap-generator-cli (dev- From 0288e88ea2b0c8cee7cab37c04f5cddcef8051a7 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 12:17:04 +0200 Subject: [PATCH 06/14] Normalize line endings --- tests/phpunit/JsonManipulatorTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/JsonManipulatorTest.php b/tests/phpunit/JsonManipulatorTest.php index 32ceb356..7ef6e11a 100644 --- a/tests/phpunit/JsonManipulatorTest.php +++ b/tests/phpunit/JsonManipulatorTest.php @@ -2171,7 +2171,7 @@ public function testAddMainKeyWithContentHavingDollarSignFollowedByDigit2() "foo": "$1bar" } '; - $this->assertEquals( str_replace( "\r\n", "\n", $expected ), str_replace( "\r\n", "\n", $manipulator->getContents() ) ); + $this->assertEquals( preg_replace( '/\R/', "\n", $expected ), preg_replace( '/\R/', "\n", $manipulator->getContents() ) ); } public function testUpdateMainKey() @@ -2305,7 +2305,7 @@ public function testRemoveMainKey() $expected = '{ } '; - $this->assertEquals( str_replace( "\r\n", "\n", $expected ), str_replace( "\r\n", "\n", $manipulator->getContents() ) ); + $this->assertEquals( preg_replace( '/\R/', "\n", $expected ), preg_replace( '/\R/', "\n", $manipulator->getContents() ) ); } public function testIndentDetection() From 9057bde0554890a5b9d0102856425a324e7021ab Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 29 Mar 2026 12:17:29 +0200 Subject: [PATCH 07/14] normalize slashes & mac paths --- tests/phpunit/ComposerJsonTest.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/phpunit/ComposerJsonTest.php b/tests/phpunit/ComposerJsonTest.php index e0a433a4..e3c68fc4 100644 --- a/tests/phpunit/ComposerJsonTest.php +++ b/tests/phpunit/ComposerJsonTest.php @@ -85,7 +85,7 @@ public function test_create_default_composer_json() { // Succeed. $expected = $this->temp_dir . 'packages/composer.json'; $actual = $create_default_composer_json->invoke( $package, $expected ); - $this->assertSame( $expected, $this->mac_safe_path( $actual ) ); + $this->assertSame( $this->mac_safe_path( realpath( $expected ) ?: $expected ), $this->mac_safe_path( realpath( $actual ) ?: $actual ) ); $this->assertTrue( false !== strpos( file_get_contents( $actual ), 'wp-cli/wp-cli' ) ); unlink( $actual ); rmdir( dirname( $actual ) ); @@ -110,7 +110,7 @@ public function test_get_composer_json_path() { putenv( 'WP_CLI_PACKAGES_DIR' ); $expected = $this->temp_dir . '.wp-cli/packages/composer.json'; $actual = $get_composer_json_path->invoke( $package ); - $this->assertSame( $expected, $this->mac_safe_path( $actual ) ); + $this->assertSame( $this->mac_safe_path( realpath( $expected ) ?: $expected ), $this->mac_safe_path( realpath( $actual ) ?: $actual ) ); $this->assertTrue( false !== strpos( file_get_contents( $actual ), 'wp-cli/wp-cli' ) ); unlink( $actual ); rmdir( dirname( $actual ) ); @@ -120,7 +120,7 @@ public function test_get_composer_json_path() { putenv( 'WP_CLI_PACKAGES_DIR=' . $this->temp_dir . 'packages' ); $expected = $this->temp_dir . 'packages/composer.json'; $actual = $get_composer_json_path->invoke( $package ); - $this->assertSame( $expected, $this->mac_safe_path( $actual ) ); + $this->assertSame( $this->mac_safe_path( realpath( $expected ) ?: $expected ), $this->mac_safe_path( realpath( $actual ) ?: $actual ) ); $this->assertTrue( false !== strpos( file_get_contents( $actual ), 'wp-cli/wp-cli' ) ); unlink( $actual ); rmdir( dirname( $actual ) ); @@ -131,7 +131,7 @@ public function test_get_composer_json_path() { mkdir( $this->temp_dir . 'packages' ); touch( $expected ); $actual = $get_composer_json_path->invoke( $package ); - $this->assertSame( $expected, $this->mac_safe_path( $actual ) ); + $this->assertSame( $this->mac_safe_path( realpath( $expected ) ?: $expected ), $this->mac_safe_path( realpath( $actual ) ?: $actual ) ); $this->assertSame( 0, filesize( $actual ) ); unlink( $actual ); rmdir( dirname( $actual ) ); @@ -175,7 +175,7 @@ public function test_get_composer_json_path_backup_decoded() { // Succeed with newly created. $expected = $this->temp_dir . 'packages/composer.json'; list( $actual, $content, $decoded ) = $get_composer_json_path_backup_decoded->invoke( $package ); - $this->assertSame( $expected, $this->mac_safe_path( $actual ) ); + $this->assertSame( $this->mac_safe_path( realpath( $expected ) ?: $expected ), $this->mac_safe_path( realpath( $actual ) ?: $actual ) ); $this->assertTrue( false !== strpos( file_get_contents( $actual ), 'wp-cli/wp-cli' ) ); $this->assertSame( file_get_contents( $actual ), $content ); $this->assertFalse( empty( $decoded ) ); @@ -187,7 +187,7 @@ public function test_get_composer_json_path_backup_decoded() { mkdir( $this->temp_dir . 'packages' ); file_put_contents( $expected, '{}' ); list( $actual, $content, $decoded ) = $get_composer_json_path_backup_decoded->invoke( $package ); - $this->assertSame( $expected, $this->mac_safe_path( $actual ) ); + $this->assertSame( $this->mac_safe_path( realpath( $expected ) ?: $expected ), $this->mac_safe_path( realpath( $actual ) ?: $actual ) ); $this->assertSame( '{}', $content ); $this->assertTrue( empty( $decoded ) ); unlink( $expected ); @@ -199,9 +199,7 @@ public function test_get_composer_json_path_backup_decoded() { private function mac_safe_path( $path ) { $path = preg_replace( '#^/private/(var|tmp)/#i', '/$1/', $path ); - if ( Utils\is_windows() ) { - $path = str_replace( '\\', '/', $path ); - } + $path = str_replace( '\\', '/', $path ); return $path; } } From 0e9c8695a9e47061ac16aa04f280c1935c723f40 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 31 Mar 2026 12:27:01 +0200 Subject: [PATCH 08/14] Use `Runner::get_packages_dir_path()` --- src/Package_Command.php | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/Package_Command.php b/src/Package_Command.php index 1b24df3d..128fb399 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -526,7 +526,7 @@ public function list_( $args, $assoc_args ) { public function path( $args ) { $packages_dir = WP_CLI::get_runner()->get_packages_dir_path(); if ( ! empty( $args ) ) { - $packages_dir .= 'vendor/' . $args[0]; + $packages_dir .= 'vendor' . DIRECTORY_SEPARATOR . $args[0]; if ( ! is_dir( $packages_dir ) ) { WP_CLI::error( 'Invalid package name.' ); } @@ -1192,21 +1192,12 @@ private function get_composer_json_path() { if ( null === $composer_path || getenv( 'WP_CLI_TEST_PACKAGE_GET_COMPOSER_JSON_PATH' ) ) { - if ( getenv( 'WP_CLI_PACKAGES_DIR' ) ) { - $composer_path = Path::trailingslashit( getenv( 'WP_CLI_PACKAGES_DIR' ) ) . 'composer.json'; - } else { - $composer_path = Path::trailingslashit( Path::get_home_dir() ) . '.wp-cli/packages/composer.json'; - } + $packages_dir = WP_CLI::get_runner()->get_packages_dir_path(); + $composer_path = rtrim( $packages_dir, '/\\' ) . DIRECTORY_SEPARATOR . 'composer.json'; // `composer.json` and its directory might need to be created if ( ! file_exists( $composer_path ) ) { $composer_path = $this->create_default_composer_json( $composer_path ); - } else { - $composer_path = realpath( $composer_path ); - if ( false === $composer_path ) { - $error = error_get_last(); - WP_CLI::error( sprintf( "Composer path '%s' for packages/composer.json not found: %s", $composer_path, $error['message'] ) ); - } } } @@ -1238,11 +1229,7 @@ private function create_default_composer_json( $composer_path ) { } } - $composer_dir = realpath( $composer_dir ); - if ( false === $composer_dir ) { - $error = error_get_last(); - WP_CLI::error( sprintf( "Composer directory '%s' for packages not found: %s", $composer_dir, $error['message'] ) ); - } + // No realpath() here to preserve short names on Windows if applicable. $composer_path = Path::trailingslashit( $composer_dir ) . Path::basename( $composer_path ); From aa9f5fe622541a39c7c64ad42e6f837cdb54d415 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 9 Apr 2026 08:36:19 +0200 Subject: [PATCH 09/14] Normalize some paths --- src/Package_Command.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Package_Command.php b/src/Package_Command.php index 128fb399..6b50a6e1 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -358,7 +358,7 @@ public function install( $args, $assoc_args ) { $this->register_revert_shutdown_function( $json_path, $composer_backup, $revert ); // Add the 'require' to composer.json - WP_CLI::log( sprintf( 'Updating %s to require the package...', $json_path ) ); + WP_CLI::log( sprintf( 'Updating %s to require the package...', Path::normalize( $json_path ) ) ); $json_manipulator = new JsonManipulator( $composer_backup ); $json_manipulator->addMainKey( 'name', 'wp-cli/wp-cli' ); $json_manipulator->addMainKey( 'version', self::get_wp_cli_version_composer() ); @@ -835,12 +835,12 @@ public function uninstall( $args, $assoc_args ) { $this->register_revert_shutdown_function( $json_path, $composer_backup, $revert ); // Remove the 'require' from composer.json. - WP_CLI::log( sprintf( 'Removing require statement for package \'%s\' from %s', $package_name, $json_path ) ); + WP_CLI::log( sprintf( 'Removing require statement for package \'%s\' from %s', $package_name, Path::normalize( $json_path ) ) ); $manipulator = new JsonManipulator( $composer_backup ); $manipulator->removeSubNode( 'require', $package_name, true /*caseInsensitive*/ ); // Remove the 'repository' details from composer.json. - WP_CLI::log( sprintf( 'Removing repository details from %s', $json_path ) ); + WP_CLI::log( sprintf( 'Removing repository details from %s', Path::normalize( $json_path ) ) ); $manipulator->removeSubNode( 'repositories', $package_name, true /*caseInsensitive*/ ); file_put_contents( $json_path, $manipulator->getContents() ); From 6d7b34974252ca9968d8fc2b2c61e0d935627a82 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 9 Apr 2026 08:36:34 +0200 Subject: [PATCH 10/14] Update some tests for Windows --- features/package-install.feature | 4 ++-- features/package-update.feature | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/features/package-install.feature b/features/package-install.feature index a4e441c4..5e971409 100644 --- a/features/package-install.feature +++ b/features/package-install.feature @@ -1374,11 +1374,11 @@ Feature: Install WP-CLI packages """ And STDOUT should be empty - When I try `wp package install https://example.com/non-existent-zip-asdfasdf.zip` + When I try `wp package install http://example.com/non-existent-zip-asdfasdf.zip` Then the return code should be 1 And STDERR should contain: """ - Error: Couldn't download package from 'https://example.com/non-existent-zip-asdfasdf.zip' + Error: Couldn't download package from 'http://example.com/non-existent-zip-asdfasdf.zip' """ And STDOUT should be empty diff --git a/features/package-update.feature b/features/package-update.feature index 6d36ea89..3aa12ef3 100644 --- a/features/package-update.feature +++ b/features/package-update.feature @@ -62,7 +62,7 @@ Feature: Update WP-CLI packages | name | update | | wp-cli-test/updateable-package | available | - When I run `sed -i.bak s/v1.0.0/\>=1.0.0/g {PACKAGE_PATH}/composer.json` + When I run `wp eval "file_put_contents( '{PACKAGE_PATH}composer.json', str_replace( 'v1.0.0', '>=1.0.0', file_get_contents( '{PACKAGE_PATH}composer.json' ) ) );" --skip-wordpress` Then the return code should be 0 When I run `cat {PACKAGE_PATH}/composer.json` @@ -114,7 +114,7 @@ Feature: Update WP-CLI packages Success: Package installed. """ - When I run `sed -i.bak s/v1.0.0/\>=1.0.0/g {PACKAGE_PATH}/composer.json` + When I run `wp eval "file_put_contents( '{PACKAGE_PATH}composer.json', str_replace( 'v1.0.0', '>=1.0.0', file_get_contents( '{PACKAGE_PATH}composer.json' ) ) );" --skip-wordpress` Then the return code should be 0 When I run `wp package update wp-cli-test/updateable-package` @@ -147,7 +147,7 @@ Feature: Update WP-CLI packages Success: Package installed. """ - When I run `sed -i.bak s/v1.0.0/\>=1.0.0/g {PACKAGE_PATH}/composer.json` + When I run `wp eval "file_put_contents( '{PACKAGE_PATH}composer.json', str_replace( 'v1.0.0', '>=1.0.0', file_get_contents( '{PACKAGE_PATH}composer.json' ) ) );" --skip-wordpress` Then the return code should be 0 When I run `wp package update wp-cli-test/updateable-package danielbachhuber/wp-cli-reset-post-date-command` From 6cf1f70cd693f4b635d91d777a66ec17f5ad98da Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 9 Apr 2026 10:35:15 +0200 Subject: [PATCH 11/14] More normalization --- src/Package_Command.php | 4 ++-- tests/phpunit/ComposerJsonTest.php | 2 +- tests/phpunit/JsonManipulatorTest.php | 9 ++++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Package_Command.php b/src/Package_Command.php index 6b50a6e1..385d908d 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -531,7 +531,7 @@ public function path( $args ) { WP_CLI::error( 'Invalid package name.' ); } } - WP_CLI::line( $packages_dir ); + WP_CLI::line( Path::normalize( $packages_dir ) ); } /** @@ -1158,7 +1158,7 @@ private function get_installed_package_by_name( $package_name ) { * @return array Two-element array containing package name and version. */ private static function get_package_name_and_version_from_dir_package( $dir_package ) { - $composer_file = $dir_package . '/composer.json'; + $composer_file = Path::normalize( $dir_package . '/composer.json' ); if ( ! file_exists( $composer_file ) ) { WP_CLI::error( sprintf( "Invalid package: composer.json file '%s' not found.", $composer_file ) ); } diff --git a/tests/phpunit/ComposerJsonTest.php b/tests/phpunit/ComposerJsonTest.php index e3c68fc4..21af48fb 100644 --- a/tests/phpunit/ComposerJsonTest.php +++ b/tests/phpunit/ComposerJsonTest.php @@ -198,8 +198,8 @@ public function test_get_composer_json_path_backup_decoded() { } private function mac_safe_path( $path ) { + $path = \WP_CLI\Path::normalize( $path ); $path = preg_replace( '#^/private/(var|tmp)/#i', '/$1/', $path ); - $path = str_replace( '\\', '/', $path ); return $path; } } diff --git a/tests/phpunit/JsonManipulatorTest.php b/tests/phpunit/JsonManipulatorTest.php index 7ef6e11a..579e6867 100644 --- a/tests/phpunit/JsonManipulatorTest.php +++ b/tests/phpunit/JsonManipulatorTest.php @@ -25,7 +25,7 @@ public function testAddLink($json, $type, $package, $constraint, $expected) { $manipulator = new JsonManipulator($json); $this->assertTrue($manipulator->addLink($type, $package, $constraint)); - $this->assertEquals($expected, $manipulator->getContents()); + $this->assertJsonEquals($expected, $manipulator->getContents()); } public static function linkProvider() @@ -1297,7 +1297,7 @@ public function testAddLinkAndSortPackages($json, $type, $package, $constraint, { $manipulator = new JsonManipulator($json); $this->assertTrue($manipulator->addLink($type, $package, $constraint, $sortPackages)); - $this->assertEquals($expected, $manipulator->getContents()); + $this->assertJsonEquals($expected, $manipulator->getContents()); } public static function providerAddLinkAndSortPackages() @@ -1380,7 +1380,7 @@ public function testRemoveSubNode($json, $name, $expected, $expectedContent = nu $this->assertEquals($expected, $manipulator->removeSubNode('repositories', $name)); if (null !== $expectedContent) { - $this->assertEquals($expectedContent, $manipulator->getContents()); + $this->assertJsonEquals($expectedContent, $manipulator->getContents()); } } @@ -2578,4 +2578,7 @@ public static function providerRemoveSubNodeCaseInsensitive() } // WP_CLI: end caseInsensitive. + private function assertJsonEquals( $expected, $actual ) { + $this->assertEquals( preg_replace( '/\R/', "\n", $expected ), preg_replace( '/\R/', "\n", $actual ) ); + } } From 677c34d74ea76d5a2f83d1eddf63b315c9e1df56 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 9 Apr 2026 10:35:26 +0200 Subject: [PATCH 12/14] Skip if it looks like a zip file --- src/Package_Command.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Package_Command.php b/src/Package_Command.php index 385d908d..3b1ed22c 100644 --- a/src/Package_Command.php +++ b/src/Package_Command.php @@ -1333,6 +1333,10 @@ private function maybe_add_git_suffix( $package_name ) { if ( preg_match( '#\.git(?::[^:]+)?$#i', $package_name ) ) { return $package_name; } + // Skip if it looks like a zip file. + if ( preg_match( '#\.zip$#i', $package_name ) ) { + return $package_name; + } // Append .git for GitHub/GitLab HTTPS or SSH URLs, preserving any :version suffix. // Pattern: (https?://github.com//[...subgroups...] or git@github.com:/[...subgroups...])(:version)? if ( preg_match( '#^((?:https?://(?:github|gitlab)\.com/|git@(?:github|gitlab)\.com:)[^/:]+(?:/[^/:]+)*)(:.*)?$#i', $package_name, $matches ) ) { From 3bb6883387d24ec1fdb57652270c578c13567bf8 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 9 Apr 2026 10:35:35 +0200 Subject: [PATCH 13/14] Update tests --- features/package-install.feature | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/features/package-install.feature b/features/package-install.feature index 5e971409..cf37f5e0 100644 --- a/features/package-install.feature +++ b/features/package-install.feature @@ -474,8 +474,9 @@ Feature: Install WP-CLI packages Scenario: Install a package from Git using a shortened package identifier Given an empty directory - When I run `wp package install wp-cli-test/github-test-command` - Then STDOUT should contain: + When I try `wp package install wp-cli-test/github-test-command` + Then the return code should be 0 + And STDOUT should contain: """ Installing package wp-cli-test/github-test-command (dev-master) """ @@ -792,7 +793,7 @@ Feature: Install WP-CLI packages """ Warning: Package name mismatch...Updating from git name 'GeekPress/wp-rocket-cli' to composer.json name 'wp-media/wp-rocket-cli'. """ - And STDOUT should match /Installing package wp-media\/wp-rocket-cli \(dev-/ + And STDOUT should match /Installing package wp-media\/wp-rocket-cli \([^)]+\)/ # This path is sometimes changed on Macs to prefix with /private And STDOUT should contain: """ From 6566fd22e84b14d1299c18660e6016704a9fc668 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 9 Apr 2026 10:50:36 +0200 Subject: [PATCH 14/14] Use regex assertion --- features/package-install.feature | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/package-install.feature b/features/package-install.feature index cf37f5e0..ff76ccbe 100644 --- a/features/package-install.feature +++ b/features/package-install.feature @@ -1202,9 +1202,9 @@ Feature: Install WP-CLI packages """ {PACKAGE_PATH}composer.json to require the package... """ + And STDOUT should match /Registering .*?path-command as a path repository\.\.\./ And STDOUT should contain: """ - Registering {CURRENT_PATH}/path-command as a path repository... Using Composer to install the package... """ And STDOUT should contain: @@ -1291,9 +1291,9 @@ Feature: Install WP-CLI packages """ {PACKAGE_PATH}composer.json to require the package... """ + And STDOUT should match /Registering .*?path-command as a path repository\.\.\./ And STDOUT should contain: """ - Registering {CURRENT_PATH}/path-command as a path repository... Using Composer to install the package... """ And STDOUT should contain: