diff --git a/features/package-install.feature b/features/package-install.feature index bfa9338d..ff76ccbe 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: """ @@ -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: """ @@ -957,7 +958,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 -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: @@ -1093,7 +1094,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- @@ -1201,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: @@ -1290,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: @@ -1374,11 +1375,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` diff --git a/src/Package_Command.php b/src/Package_Command.php index 1b24df3d..3b1ed22c 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() ); @@ -526,12 +526,12 @@ 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.' ); } } - WP_CLI::line( $packages_dir ); + WP_CLI::line( Path::normalize( $packages_dir ) ); } /** @@ -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() ); @@ -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 ) ); } @@ -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 ); @@ -1346,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 ) ) { diff --git a/tests/phpunit/ComposerJsonTest.php b/tests/phpunit/ComposerJsonTest.php index c1cdf9b6..21af48fb 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 ); } @@ -80,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 ) ); @@ -105,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 ) ); @@ -115,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 ) ); @@ -126,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 ) ); @@ -170,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 ) ); @@ -182,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 ); @@ -193,6 +198,8 @@ 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 = \WP_CLI\Path::normalize( $path ); + $path = preg_replace( '#^/private/(var|tmp)/#i', '/$1/', $path ); + return $path; } } diff --git a/tests/phpunit/JsonManipulatorTest.php b/tests/phpunit/JsonManipulatorTest.php index 156ab7f9..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()); } } @@ -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( preg_replace( '/\R/', "\n", $expected ), preg_replace( '/\R/', "\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( preg_replace( '/\R/', "\n", $expected ), preg_replace( '/\R/', "\n", $manipulator->getContents() ) ); } public function testIndentDetection() @@ -2576,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 ) ); + } }