Skip to content
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
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
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 Jul 18, 2013
a275bcb
Allow each subcommand to be run before WP is loaded.
danielbachhuber Jul 18, 2013
46224ec
Abstraction of `wp_list_pluck()` so we can run this without WordPress.
danielbachhuber Jul 18, 2013
1a8ab31
Package management requires the Composer PHP library.
danielbachhuber Jul 18, 2013
cc20ce8
WP-CLI community commands need to require WP-CLI as a dependency
danielbachhuber Jul 18, 2013
e58cedf
a275bcb75146547964e6d9347652434e3668da53 in less words
danielbachhuber Jul 18, 2013
351effc
Use the WP-CLI package index as our directory.
danielbachhuber Jul 18, 2013
280f156
A subcommand for browsing existing community packages
danielbachhuber Jul 18, 2013
9ec034d
If there aren't any local WP-CLI community commands, don't show an em…
danielbachhuber Jul 20, 2013
dad6b52
Move the logic to get community packages to its own method. Need to u…
danielbachhuber Jul 22, 2013
59ea6d5
Remove extra code made moot by dad6b52
danielbachhuber Jul 22, 2013
25e9e09
Support for installing a WP-CLI community package.
danielbachhuber Jul 22, 2013
16b86ea
Static variable caching goodness
danielbachhuber Jul 22, 2013
363f6e6
Rather than checking if the package requires `wp-cli`, let's just see…
danielbachhuber Jul 22, 2013
8a1bd4d
WP-CLI supports community packages, in addition to commands
danielbachhuber Jul 27, 2013
a3f8266
Write and reload the composer.json when installing
danielbachhuber Jul 27, 2013
65cc02c
Have Composer ignore composer.lock when installing
danielbachhuber Jul 27, 2013
e9e0811
the package index repo belongs in the installer
scribu Aug 8, 2013
9701bdc
merge master
scribu Aug 8, 2013
4e21f7d
use array_column()
scribu Aug 8, 2013
2d944f3
whitespace fixes
scribu Aug 8, 2013
04488a6
s/packagist repo/package index/
scribu Aug 8, 2013
62dbec8
more whitespace fixes
scribu Aug 8, 2013
c71e2d9
extract find_composer_autoloader()
scribu Aug 8, 2013
3afb8b3
work only with the installer composer.json file
scribu Aug 8, 2013
9f5dd60
look for package index under the 'wp-cli' key
scribu Aug 8, 2013
95449cb
construct ComposerRepository instance manually
scribu Aug 8, 2013
414ec8f
extract _get_repo_instance() method
scribu Aug 8, 2013
d3601c1
add smoke test for 'wp package browse'
scribu Aug 8, 2013
5353166
package list: use RepositoryInterface->hasPackage()
scribu Aug 9, 2013
958853c
introduce _show_packages() helper
scribu Aug 9, 2013
16421e0
Merge branch 'master' into package-manager-v2
danielbachhuber Aug 12, 2013
3839e7c
Fix trailing comma
danielbachhuber Aug 12, 2013
f4d9656
Merge branch 'master' into package-manager-v2
scribu Aug 12, 2013
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Expand Up @@ -12,6 +12,7 @@
"wp-cli/php-cli-tools": "0.9.3",
"mustache/mustache": "~2.4",
"rhumsaa/array_column": "~1.1",
"composer/composer": "dev-master"
Copy link
Member

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.

"rmccue/requests": "dev-master"
},
"suggest": {
Expand Down
10 changes: 10 additions & 0 deletions features/package.feature
@@ -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
"""
299 changes: 299 additions & 0 deletions php/commands/package.php
@@ -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' );

19 changes: 11 additions & 8 deletions php/utils.php
Expand Up @@ -6,28 +6,31 @@

use \WP_CLI\Dispatcher;

function load_dependencies() {
function find_composer_autoloader() {
$vendor_paths = array(
WP_CLI_ROOT . '/vendor', // top-level project
WP_CLI_ROOT . '/../../../vendor', // part of a larger project
);

$has_autoload = false;

foreach ( $vendor_paths as $vendor_path ) {
if ( file_exists( $vendor_path . '/autoload.php' ) ) {
require $vendor_path . '/autoload.php';
$has_autoload = true;
break;
return $vendor_path . '/autoload.php';
}
}

if ( !$has_autoload ) {
return false;
}

function load_dependencies() {
$autoloader_path = find_composer_autoloader();

if ( !$autoloader_path ) {
fputs( STDERR, "Internal error: Can't find Composer autoloader.\n" );
exit(3);
}

include WP_CLI_ROOT . '/php/Spyc.php';
require $autoloader_path;
require WP_CLI_ROOT . '/php/Spyc.php';
}

function load_command( $name ) {
Expand Down