New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A command for browsing, installing, updating and deleting community commands #602
Closed
Closed
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
e10a330
Initial migration of the in-progress Package Command at https://githu…
danielbachhuber a275bcb
Allow each subcommand to be run before WP is loaded.
danielbachhuber 46224ec
Abstraction of `wp_list_pluck()` so we can run this without WordPress.
danielbachhuber 1a8ab31
Package management requires the Composer PHP library.
danielbachhuber cc20ce8
WP-CLI community commands need to require WP-CLI as a dependency
danielbachhuber e58cedf
a275bcb75146547964e6d9347652434e3668da53 in less words
danielbachhuber 351effc
Use the WP-CLI package index as our directory.
danielbachhuber 280f156
A subcommand for browsing existing community packages
danielbachhuber 9ec034d
If there aren't any local WP-CLI community commands, don't show an em…
danielbachhuber dad6b52
Move the logic to get community packages to its own method. Need to u…
danielbachhuber 59ea6d5
Remove extra code made moot by dad6b52
danielbachhuber 25e9e09
Support for installing a WP-CLI community package.
danielbachhuber 16b86ea
Static variable caching goodness
danielbachhuber 363f6e6
Rather than checking if the package requires `wp-cli`, let's just see…
danielbachhuber 8a1bd4d
WP-CLI supports community packages, in addition to commands
danielbachhuber a3f8266
Write and reload the composer.json when installing
danielbachhuber 65cc02c
Have Composer ignore composer.lock when installing
danielbachhuber e9e0811
the package index repo belongs in the installer
scribu 9701bdc
merge master
scribu 4e21f7d
use array_column()
scribu 2d944f3
whitespace fixes
scribu 04488a6
s/packagist repo/package index/
scribu 62dbec8
more whitespace fixes
scribu c71e2d9
extract find_composer_autoloader()
scribu 3afb8b3
work only with the installer composer.json file
scribu 9f5dd60
look for package index under the 'wp-cli' key
scribu 95449cb
construct ComposerRepository instance manually
scribu 414ec8f
extract _get_repo_instance() method
scribu d3601c1
add smoke test for 'wp package browse'
scribu 5353166
package list: use RepositoryInterface->hasPackage()
scribu 958853c
introduce _show_packages() helper
scribu 16421e0
Merge branch 'master' into package-manager-v2
danielbachhuber 3839e7c
Fix trailing comma
danielbachhuber f4d9656
Merge branch 'master' into package-manager-v2
scribu File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
Feature: Manage WP-CLI packages | ||
|
||
Scenario: Empty dir | ||
Given an empty directory | ||
|
||
When I run `wp package browse` | ||
Then STDOUT should contain: | ||
""" | ||
wp-cli/server-command | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,299 @@ | ||
<?php | ||
use \Composer\Config; | ||
use \Composer\Config\JsonConfigSource; | ||
use \Composer\Factory; | ||
use \Composer\IO\NullIO; | ||
use \Composer\Installer; | ||
use \Composer\Json\JsonFile; | ||
use \Composer\Json\JsonManipulator; | ||
use \Composer\Package; | ||
use \Composer\Package\Version\VersionParser; | ||
use \Composer\Repository; | ||
use \Composer\Repository\CompositeRepository; | ||
use \Composer\Repository\ComposerRepository; | ||
use \Composer\Repository\RepositoryManager; | ||
use \Composer\Util\Filesystem; | ||
|
||
/** | ||
* Manage WP-CLI community packages. | ||
* | ||
* @package WP-CLI | ||
* | ||
* @when before_wp_load | ||
*/ | ||
class Package_Command extends WP_CLI_Command { | ||
|
||
// TODO: read from composer.json | ||
const PACKAGE_INDEX_URL = 'http://wp-cli.org/package-index/'; | ||
|
||
private $fields = array( | ||
'name', | ||
'description', | ||
'authors', | ||
); | ||
|
||
private function _show_packages( $packages, $assoc_args ) { | ||
$defaults = array( | ||
'fields' => implode( ',', $this->fields ), | ||
'format' => 'table' | ||
); | ||
$assoc_args = array_merge( $defaults, $assoc_args ); | ||
|
||
$list = array(); | ||
foreach ( $packages as $package ) { | ||
$package_output = new stdClass; | ||
$package_output->name = $package->getName(); | ||
$package_output->description = $package->getDescription(); | ||
$package_output->authors = implode( ',', array_column( (array) $package->getAuthors(), 'name' ) ); | ||
$list[$package_output->name] = $package_output; | ||
} | ||
|
||
WP_CLI\Utils\format_items( $assoc_args['format'], $list, $assoc_args['fields'] ); | ||
} | ||
|
||
/** | ||
* Browse available WP-CLI community packages. | ||
* | ||
* @subcommand browse | ||
* @synopsis [--format=<format>] | ||
*/ | ||
public function browse( $_, $assoc_args ) { | ||
$this->_show_packages( $this->get_community_packages(), $assoc_args ); | ||
} | ||
|
||
/** | ||
* Install a WP-CLI community package. | ||
* | ||
* @subcommand install | ||
* @synopsis <package-name> [--version=<version>] | ||
*/ | ||
public function install( $args, $assoc_args ) { | ||
list( $package_name ) = $args; | ||
|
||
$defaults = array( | ||
'version' => 'dev-master', | ||
); | ||
$assoc_args = array_merge( $defaults, $assoc_args ); | ||
|
||
$package = $this->get_community_package_by_name( $package_name ); | ||
if ( ! $package ) | ||
WP_CLI::error( "Invalid package." ); | ||
|
||
$composer = $this->get_composer(); | ||
$composer_json_obj = $this->get_composer_json(); | ||
|
||
// Add the 'require' to composer.json | ||
$composer_backup = file_get_contents( $composer_json_obj->getPath() ); | ||
$json_manipulator = new JsonManipulator( $composer_backup ); | ||
$json_manipulator->addLink( 'require', $package_name, $assoc_args['version'] ); | ||
file_put_contents( $composer_json_obj->getPath(), $json_manipulator->getContents() ); | ||
$composer = $this->get_composer(); | ||
|
||
// Set up the installer | ||
$install = Installer::create( new NullIO, $composer ); | ||
$install->setUpdate( true ); // Installer class will only override composer.lock with this flag | ||
|
||
// Try running the installer, but revert composer.json if failed | ||
if ( $install->run() ) { | ||
WP_CLI::success( "Package installed." ); | ||
} else { | ||
file_put_contents( $composer_json_obj->getPath(), $composer_backup ); | ||
WP_CLI::error( "Package installation failed." ); | ||
} | ||
} | ||
|
||
/** | ||
* List installed WP-CLI community packages. | ||
* | ||
* @subcommand list | ||
* @synopsis [--format=<format>] | ||
*/ | ||
public function _list( $args, $assoc_args ) { | ||
$this->_show_packages( $this->get_installed_packages(), $assoc_args ); | ||
} | ||
|
||
/** | ||
* Uninstall a WP-CLI community package. | ||
* | ||
* @subcommand uninstall | ||
* @synopsis <package-name> | ||
*/ | ||
public function uninstall( $args ) { | ||
list( $package_name ) = $args; | ||
|
||
$composer = $this->get_composer(); | ||
if ( false === ( $package = $this->get_installed_package_by_name( $package_name ) ) ) | ||
WP_CLI::error( "Package not installed." ); | ||
|
||
$composer_json_obj = $this->get_composer_json(); | ||
|
||
// Remove the 'require' from composer.json | ||
$contents = file_get_contents( $composer_json_obj->getPath() ); | ||
$manipulator = new JsonManipulator( $contents ); | ||
$manipulator->removeSubNode( 'require', $package_name ); | ||
file_put_contents( $composer_json_obj->getPath(), $manipulator->getContents() ); | ||
|
||
// Delete the directory | ||
$filesystem = new Filesystem; | ||
$package_path = getcwd() . '/' . $composer->getConfig()->get('vendor-dir') . '/' . $package->getName(); | ||
$filesystem->removeDirectory( $package_path ); | ||
|
||
// Reset Composer and regenerate the auto-loader | ||
$composer = $this->get_composer(); | ||
$this->regenerate_autoloader(); | ||
|
||
WP_CLI::success( "Uninstalled package." ); | ||
} | ||
|
||
/** | ||
* Check whether a package is a WP-CLI community package based | ||
* on membership in our package index. | ||
* | ||
* @param object $package A package object | ||
* @return bool | ||
*/ | ||
private function is_community_package( $package ) { | ||
return $this->package_index()->hasPackage( $package ); | ||
} | ||
|
||
/** | ||
* Get a Composer instance. | ||
*/ | ||
private function get_composer() { | ||
$composer_path = $this->get_composer_json_path(); | ||
|
||
// Composer's auto-load generating code makes some assumptions about where | ||
// the 'vendor-dir' is, and where Composer is running from. | ||
// Best to just pretend we're installing a package from ~/.wp-cli or similar | ||
chdir( pathinfo( $composer_path, PATHINFO_DIRNAME ) ); | ||
|
||
try { | ||
$composer = Factory::create( new NullIO, $composer_path ); | ||
} catch( Exception $e ) { | ||
WP_CLI::error( $e->getMessage() ); | ||
} | ||
|
||
$this->composer = $composer; | ||
return $this->composer; | ||
} | ||
|
||
/** | ||
* Get all of the community packages. | ||
*/ | ||
private function get_community_packages() { | ||
static $community_packages; | ||
|
||
if ( null === $community_packages ) { | ||
$community_packages = $this->package_index()->getPackages(); | ||
} | ||
|
||
return $community_packages; | ||
} | ||
|
||
// We need to construct the instance manually, because there's no way to select | ||
// a particular instance using $composer->getRepositoryManager() | ||
private function package_index() { | ||
static $package_index; | ||
|
||
if ( !$package_index ) { | ||
$config = new Config(); | ||
$config->merge(array('config' => array( | ||
'home' => dirname( $this->get_composer_json_path() ), | ||
/* 'cache-dir' => $cacheDir */ | ||
))); | ||
$config->setConfigSource( new JsonConfigSource( $this->get_composer_json() ) ); | ||
|
||
$package_index = new ComposerRepository( array( 'url' => self::PACKAGE_INDEX_URL ), new NullIO, $config ); | ||
} | ||
|
||
return $package_index; | ||
} | ||
|
||
/** | ||
* Get a community package by its name. | ||
*/ | ||
private function get_community_package_by_name( $package_name ) { | ||
foreach( $this->get_community_packages() as $package ) { | ||
if ( $package_name == $package->getName() ) | ||
return $package; | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Get the installed community packages. | ||
*/ | ||
private function get_installed_packages() { | ||
$composer = $this->get_composer(); | ||
$repo = $composer->getRepositoryManager()->getLocalRepository(); | ||
|
||
$installed_packages = array(); | ||
foreach( $repo->getPackages() as $package ) { | ||
if ( ! $this->is_community_package( $package ) ) | ||
continue; | ||
$installed_packages[] = $package; | ||
} | ||
|
||
return $installed_packages; | ||
} | ||
|
||
/** | ||
* Get an installed package by its name. | ||
*/ | ||
private function get_installed_package_by_name( $package_name ) { | ||
foreach( $this->get_installed_packages() as $package ) { | ||
if ( $package_name == $package->getName() ) | ||
return $package; | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Check if the package name provided is already installed. | ||
*/ | ||
private function is_package_installed( $package_name ) { | ||
if ( $this->get_installed_package_by_name( $package_name ) ) | ||
return true; | ||
else | ||
return false; | ||
} | ||
|
||
/** | ||
* Get the composer.json object | ||
*/ | ||
private function get_composer_json() { | ||
return new JsonFile( $this->get_composer_json_path() ); | ||
} | ||
|
||
/** | ||
* Get the path to composer.json | ||
*/ | ||
private function get_composer_json_path() { | ||
static $composer_path; | ||
|
||
if ( null === $composer_path ) { | ||
$composer_path = WP_CLI\Utils\find_file_upward( 'composer.json', dirname( WP_CLI_ROOT ) ); | ||
if ( ! $composer_path ) { | ||
WP_CLI::error( "Can't find composer.json file outside of the WP-CLI directory." ); | ||
} | ||
} | ||
|
||
return $composer_path; | ||
} | ||
|
||
/** | ||
* Regenerate the Composer autoloader | ||
*/ | ||
private function regenerate_autoloader() { | ||
$this->composer->getAutoloadGenerator()->dump( | ||
$this->composer->getConfig(), | ||
$this->composer->getRepositoryManager()->getLocalRepository(), | ||
$this->composer->getPackage(), | ||
$this->composer->getInstallationManager(), | ||
'composer' | ||
); | ||
} | ||
} | ||
|
||
WP_CLI::add_command( 'package', 'Package_Command' ); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing a trailing comma here.