src/Package_Command.php
public function install( $args, $assoc_args ) {
		list( $package_name ) = $args;

		$this->set_composer_auth_env_var();
		$git_package = false;
		$dir_package = false;
		$version     = 'dev-master';
		if ( $this->is_git_repository( $package_name ) ) {
			$git_package = $package_name;
			preg_match( '#([^:\/]+\/[^\/]+)\.git#', $package_name, $matches );
			if ( ! empty( $matches[1] ) ) {
				$package_name = $this->check_git_package_name( $matches[1] );
			} else {
				WP_CLI::error( "Couldn't parse package name from expected path '<name>/<package>'." );
			}
		} elseif ( ( false !== strpos( $package_name, '://' ) && false !== stripos( $package_name, '.zip' ) )
			|| ( pathinfo( $package_name, PATHINFO_EXTENSION ) === 'zip' && is_file( $package_name ) ) ) {
			// Download the remote ZIP file to a temp directory
			$temp = false;
			if ( false !== strpos( $package_name, '://' ) ) {
				$temp     = Utils\get_temp_dir() . uniqid( 'wp-cli-package_', true /*more_entropy*/ ) . '.zip';
				$options  = [
					'timeout'  => 600,
					'filename' => $temp,
				];
				$response = Utils\http_request( 'GET', $package_name, null, [], $options );
				if ( 20 !== (int) substr( $response->status_code, 0, 2 ) ) {
					@unlink( $temp ); // @codingStandardsIgnoreLine
					WP_CLI::error( sprintf( "Couldn't download package from '%s' (HTTP code %d).", $package_name, $response->status_code ) );
				}
				$package_name = $temp;
			}
			$dir_package = Utils\get_temp_dir() . uniqid( 'wp-cli-package_', true /*more_entropy*/ );
			try {
				// Extract the package to get the package name
				Extractor::extract( $package_name, $dir_package );
				if ( $temp ) {
					unlink( $temp );
					$temp = false;
				}
				list( $package_name, $version ) = self::get_package_name_and_version_from_dir_package( $dir_package );
				// Move to a location based on the package name
				$local_dir          = rtrim( WP_CLI::get_runner()->get_packages_dir_path(), '/' ) . '/local/';
				$actual_dir_package = $local_dir . str_replace( '/', '-', $package_name );
				Extractor::copy_overwrite_files( $dir_package, $actual_dir_package );
				Extractor::rmdir( $dir_package );
				// Behold, the extracted package
				$dir_package = $actual_dir_package;
			} catch ( Exception $e ) {
				if ( $temp ) {
					unlink( $temp );
				}
				if ( file_exists( $dir_package ) ) {
					try {
						Extractor::rmdir( $dir_package );
					} catch ( Exception $rmdir_e ) {
						WP_CLI::warning( $rmdir_e->getMessage() );
					}
				}
				WP_CLI::error( $e->getMessage() );
			}
		} elseif ( is_dir( $package_name ) && file_exists( $package_name . '/composer.json' ) ) {
			$dir_package = $package_name;
			if ( ! Utils\is_path_absolute( $dir_package ) ) {
				$dir_package = getcwd() . DIRECTORY_SEPARATOR . $dir_package;
			}
			list( $package_name, $version ) = self::get_package_name_and_version_from_dir_package( $dir_package );
		} else {
			if ( false !== strpos( $package_name, ':' ) ) {
				list( $package_name, $version ) = explode( ':', $package_name );
			}
			$package = $this->get_package_by_shortened_identifier( $package_name );
			if ( ! $package ) {
				WP_CLI::error( sprintf( "Invalid package: shortened identifier '%s' not found.", $package_name ) );
			}
			if ( is_string( $package ) ) {
				if ( $this->is_git_repository( $package ) ) {
					$git_package = $package;
					if ( '@stable' === $version ) {
						$tag     = $this->get_github_latest_release_tag( $package_name );
						$version = $this->guess_version_constraint_from_tag( $tag );
					}
					$package_name = $this->check_git_package_name( $package_name, $version );
				}
			} elseif ( $package_name !== $package->getPrettyName() ) {
				// BC support for specifying lowercase names for mixed-case package index packages - don't bother warning.
				$package_name = $package->getPrettyName();
			}
		}

		WP_CLI::log( sprintf( 'Installing package %s (%s)', $package_name, $version ) );

		// Read the WP-CLI packages composer.json and do some initial error checking.
		list( $json_path, $composer_backup, $composer_backup_decoded ) = $this->get_composer_json_path_backup_decoded();

		// Revert on shutdown if `$revert` is true (set to false on success).
		$revert = true;
		$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 ) );
		$json_manipulator = new JsonManipulator( $composer_backup );
		$json_manipulator->addMainKey( 'name', 'wp-cli/wp-cli' );
		$json_manipulator->addMainKey( 'version', self::get_wp_cli_version_composer() );
		$json_manipulator->addLink( 'require', $package_name, $version, false /*sortPackages*/, true /*caseInsensitive*/ );
		$json_manipulator->addConfigSetting( 'secure-http', true );

		$package_args = [];
		if ( $git_package ) {
			WP_CLI::log( sprintf( 'Registering %s as a VCS repository...', $git_package ) );
			$package_args = [
				'type' => 'vcs',
				'url'  => $git_package,
			];
			$json_manipulator->addSubNode(
				'repositories',
				$package_name,
				$package_args,
				true /*caseInsensitive*/
			);
		} elseif ( $dir_package ) {
			WP_CLI::log( sprintf( 'Registering %s as a path repository...', $dir_package ) );
			$package_args = [
				'type' => 'path',
				'url'  => $dir_package,
			];
			$json_manipulator->addSubNode(
				'repositories',
				$package_name,
				$package_args,
				true /*caseInsensitive*/
			);
		}
		// If the composer file does not contain the current package index repository, refresh the repository definition.
		if ( empty( $composer_backup_decoded['repositories']['wp-cli']['url'] ) || self::PACKAGE_INDEX_URL !== $composer_backup_decoded['repositories']['wp-cli']['url'] ) {
			WP_CLI::log( 'Updating package index repository url...' );
			$package_args = $this->composer_type_package;
			$json_manipulator->addRepository(
				'wp-cli',
				$package_args
			);
		}

		file_put_contents( $json_path, $json_manipulator->getContents() );
		$composer = $this->get_composer();

		// Set up the EventSubscriber
		$event_subscriber = new PackageManagerEventSubscriber();
		$composer->getEventDispatcher()->addSubscriber( $event_subscriber );
		// Set up the installer
		$install = Installer::create( new ComposerIO(), $composer );
		$install->setUpdate( true ); // Installer class will only override composer.lock with this flag
		$install->setPreferSource( true ); // Use VCS when VCS for easier contributions.

		// Try running the installer, but revert composer.json if failed
		WP_CLI::log( 'Using Composer to install the package...' );
		WP_CLI::log( '---' );
		$res = false;
		try {
			$res = $install->run();
		} catch ( Exception $e ) {
			WP_CLI::warning( $e->getMessage() );
		}
		WP_CLI::log( '---' );

		if ( 0 === $res ) {
			$revert = false;
			WP_CLI::success( 'Package installed.' );
		} else {
			$res_msg = $res ? " (Composer return code {$res})" : ''; // $res may be null apparently.
			WP_CLI::debug( "composer.json content:\n" . file_get_contents( $json_path ), 'packages' );
			WP_CLI::error( "Package installation failed{$res_msg}." );
		}
	}
public function install( $args, $assoc_args ) {
		list( $package_name ) = $args;

		$this->set_composer_auth_env_var();
		$git_package = false;
		$dir_package = false;
		$version     = 'dev-master';
		if ( $this->is_git_repository( $package_name ) ) {
			$git_package = $package_name;
			preg_match( '#([^:\/]+\/[^\/]+)\.git#', $package_name, $matches );
			if ( ! empty( $matches[1] ) ) {
				$package_name = $this->check_git_package_name( $matches[1] );
			} else {
				WP_CLI::error( "Couldn't parse package name from expected path '<name>/<package>'." );
			}
		} elseif ( ( false !== strpos( $package_name, '://' ) && false !== stripos( $package_name, '.zip' ) )
			|| ( pathinfo( $package_name, PATHINFO_EXTENSION ) === 'zip' && is_file( $package_name ) ) ) {
			// Download the remote ZIP file to a temp directory
			$temp = false;
			if ( false !== strpos( $package_name, '://' ) ) {
				$temp     = Utils\get_temp_dir() . uniqid( 'wp-cli-package_', true /*more_entropy*/ ) . '.zip';
				$options  = [
					'timeout'  => 600,
					'filename' => $temp,
				];
				$response = Utils\http_request( 'GET', $package_name, null, [], $options );
				if ( 20 !== (int) substr( $response->status_code, 0, 2 ) ) {
					@unlink( $temp ); // @codingStandardsIgnoreLine
					WP_CLI::error( sprintf( "Couldn't download package from '%s' (HTTP code %d).", $package_name, $response->status_code ) );
				}
				$package_name = $temp;
			}
			$dir_package = Utils\get_temp_dir() . uniqid( 'wp-cli-package_', true /*more_entropy*/ );
			try {
				// Extract the package to get the package name
				Extractor::extract( $package_name, $dir_package );
				if ( $temp ) {
					unlink( $temp );
					$temp = false;
				}
				list( $package_name, $version ) = self::get_package_name_and_version_from_dir_package( $dir_package );
				// Move to a location based on the package name
				$local_dir          = rtrim( WP_CLI::get_runner()->get_packages_dir_path(), '/' ) . '/local/';
				$actual_dir_package = $local_dir . str_replace( '/', '-', $package_name );
				Extractor::copy_overwrite_files( $dir_package, $actual_dir_package );
				Extractor::rmdir( $dir_package );
				// Behold, the extracted package
				$dir_package = $actual_dir_package;
			} catch ( Exception $e ) {
				if ( $temp ) {
					unlink( $temp );
				}
				if ( file_exists( $dir_package ) ) {
					try {
						Extractor::rmdir( $dir_package );
					} catch ( Exception $rmdir_e ) {
						WP_CLI::warning( $rmdir_e->getMessage() );
					}
				}
				WP_CLI::error( $e->getMessage() );
			}
		} elseif ( is_dir( $package_name ) && file_exists( $package_name . '/composer.json' ) ) {
			$dir_package = $package_name;
			if ( ! Utils\is_path_absolute( $dir_package ) ) {
				$dir_package = getcwd() . DIRECTORY_SEPARATOR . $dir_package;
			}
			list( $package_name, $version ) = self::get_package_name_and_version_from_dir_package( $dir_package );
		} else {
			if ( false !== strpos( $package_name, ':' ) ) {
				list( $package_name, $version ) = explode( ':', $package_name );
			}
			$package = $this->get_package_by_shortened_identifier( $package_name );
			if ( ! $package ) {
				WP_CLI::error( sprintf( "Invalid package: shortened identifier '%s' not found.", $package_name ) );
			}
			if ( is_string( $package ) ) {
				if ( $this->is_git_repository( $package ) ) {
					$git_package = $package;
					if ( '@stable' === $version ) {
						$tag     = $this->get_github_latest_release_tag( $package_name );
						$version = $this->guess_version_constraint_from_tag( $tag );
					}
					$package_name = $this->check_git_package_name( $package_name, $version );
				}
			} elseif ( $package_name !== $package->getPrettyName() ) {
				// BC support for specifying lowercase names for mixed-case package index packages - don't bother warning.
				$package_name = $package->getPrettyName();
			}
		}

		WP_CLI::log( sprintf( 'Installing package %s (%s)', $package_name, $version ) );

		// Read the WP-CLI packages composer.json and do some initial error checking.
		list( $json_path, $composer_backup, $composer_backup_decoded ) = $this->get_composer_json_path_backup_decoded();

		// Revert on shutdown if `$revert` is true (set to false on success).
		$revert = true;
		$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 ) );
		$json_manipulator = new JsonManipulator( $composer_backup );
		$json_manipulator->addMainKey( 'name', 'wp-cli/wp-cli' );
		$json_manipulator->addMainKey( 'version', self::get_wp_cli_version_composer() );
		$json_manipulator->addLink( 'require', $package_name, $version, false /*sortPackages*/, true /*caseInsensitive*/ );
		$json_manipulator->addConfigSetting( 'secure-http', true );

		$package_args = [];
		if ( $git_package ) {
			WP_CLI::log( sprintf( 'Registering %s as a VCS repository...', $git_package ) );
			$package_args = [
				'type' => 'vcs',
				'url'  => $git_package,
			];
			$json_manipulator->addSubNode(
				'repositories',
				$package_name,
				$package_args,
				true /*caseInsensitive*/
			);
		} elseif ( $dir_package ) {
			WP_CLI::log( sprintf( 'Registering %s as a path repository...', $dir_package ) );
			$package_args = [
				'type' => 'path',
				'url'  => $dir_package,
			];
			$json_manipulator->addSubNode(
				'repositories',
				$package_name,
				$package_args,
				true /*caseInsensitive*/
			);
		}
		// If the composer file does not contain the current package index repository, refresh the repository definition.
		if ( empty( $composer_backup_decoded['repositories']['wp-cli']['url'] ) || self::PACKAGE_INDEX_URL !== $composer_backup_decoded['repositories']['wp-cli']['url'] ) {
			WP_CLI::log( 'Updating package index repository url...' );
			$package_args = $this->composer_type_package;
			$json_manipulator->addRepository(
				'wp-cli',
				$package_args
			);
		}

		file_put_contents( $json_path, $json_manipulator->getContents() );
		$composer = $this->get_composer();

		// Set up the EventSubscriber
		$event_subscriber = new PackageManagerEventSubscriber();
		$composer->getEventDispatcher()->addSubscriber( $event_subscriber );
		// Set up the installer
		$install = Installer::create( new ComposerIO(), $composer );
		$install->setUpdate( true ); // Installer class will only override composer.lock with this flag
		$install->setPreferSource( true ); // Use VCS when VCS for easier contributions.

		// Try running the installer, but revert composer.json if failed
		WP_CLI::log( 'Using Composer to install the package...' );
		WP_CLI::log( '---' );
		$res = false;
		try {
			$res = $install->run();
		} catch ( Exception $e ) {
			WP_CLI::warning( $e->getMessage() );
		}
		WP_CLI::log( '---' );

		if ( 0 === $res ) {
			$revert = false;
			WP_CLI::success( 'Package installed.' );
		} else {
			$res_msg = $res ? " (Composer return code {$res})" : ''; // $res may be null apparently.
			WP_CLI::debug( "composer.json content:\n" . file_get_contents( $json_path ), 'packages' );
			WP_CLI::error( "Package installation failed{$res_msg}." );
		}
	}
Installs a WP-CLI package.

Packages are required to be a valid Composer package, and can be
specified as:

* Package name from WP-CLI's package index.
* Git URL accessible by the current shell user.
* Path to a directory on the local machine.
* Local or remote .zip file.

Packages are installed to `~/.wp-cli/packages/` by default. Use the
`WP_CLI_PACKAGES_DIR` environment variable to provide a custom path.

When installing a local directory, WP-CLI simply registers a
reference to the directory. If you move or delete the directory, WP-CLI's
reference breaks.

When installing a .zip file, WP-CLI extracts the package to
`~/.wp-cli/packages/local/<package-name>`.

## OPTIONS

<name|git|path|zip>
: Name, git URL, directory path, or .zip file for the package to install.
Names can optionally include a version constraint
(e.g. wp-cli/server-command:@stable).

## EXAMPLES

# Install the latest development version from the package index.
$ wp package install wp-cli/server-command
Installing package wp-cli/server-command (dev-master)
Updating /home/person/.wp-cli/packages/composer.json to require the package...
Using Composer to install the package...
---
Loading composer repositories with package information
Updating dependencies
Resolving dependencies through SAT
Dependency resolution completed in 0.005 seconds
Analyzed 732 packages to resolve dependencies
Analyzed 1034 rules to resolve dependencies
- Installing package
Writing lock file
Generating autoload files
---
Success: Package installed.

# Install the latest stable version.
$ wp package install wp-cli/server-command:@stable

# Install a package hosted at a git URL.
$ wp package install git@github.com:runcommand/hook.git

# Install a package in a .zip file.
$ wp package install google-sitemap-generator-cli.zip