diff --git a/composer.json b/composer.json index 3b1db49..0438dd9 100644 --- a/composer.json +++ b/composer.json @@ -4,12 +4,16 @@ "minimum-stability": "dev", "require": { "dxw/rubbishthorclone": "dev-master", - "aura/autoload": "2.x-dev" + "aura/autoload": "2.x-dev", + "nikita2206/result": "dev-master", + "kevinlebrun/colors.php": "dev-master" }, "bin": ["bin/whippet"], "require-dev": { "phpunit/phpunit": "4.8.*", - "dxw/phar-install": "dev-master" + "dxw/phar-install": "dev-master", + "mikey179/vfsStream": "~1.6@dev", + "sebastian/environment": "1.3.6" }, "scripts": { "post-update-cmd": "vendor/bin/phar-install" diff --git a/composer.lock b/composer.lock index 7841058..6c83e8e 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,11 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "e884b64daa19954db689b91a9da02198", + "hash": "08d2959a62bdd156536e25704f357654", + "content-hash": "58c3bfeaa6e4ff86347ef40bf762687f", "packages": [ { "name": "aura/autoload", @@ -61,12 +62,12 @@ "source": { "type": "git", "url": "https://github.com/dxw/RubbishThorClone.git", - "reference": "a0b3cf9b5a2de8812e07d20bf69dbff17896d3c3" + "reference": "d82bff3b873100f5701908d6956472303fdbd095" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dxw/RubbishThorClone/zipball/a0b3cf9b5a2de8812e07d20bf69dbff17896d3c3", - "reference": "a0b3cf9b5a2de8812e07d20bf69dbff17896d3c3", + "url": "https://api.github.com/repos/dxw/RubbishThorClone/zipball/d82bff3b873100f5701908d6956472303fdbd095", + "reference": "d82bff3b873100f5701908d6956472303fdbd095", "shasum": "" }, "require": { @@ -80,7 +81,58 @@ ] }, "notification-url": "https://packagist.org/downloads/", - "time": "2016-03-30 20:17:49" + "time": "2016-06-15 12:36:21" + }, + { + "name": "kevinlebrun/colors.php", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/kevinlebrun/colors.php.git", + "reference": "6d7140aeedef46c97c2324f09b752c599ef17dac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kevinlebrun/colors.php/zipball/6d7140aeedef46c97c2324f09b752c599ef17dac", + "reference": "6d7140aeedef46c97c2324f09b752c599ef17dac", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "satooshi/php-coveralls": "1.0.*", + "squizlabs/php_codesniffer": "1.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "Colors": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kevin Le Brun", + "email": "lebrun.k@gmail.com", + "homepage": "http://kevinlebrun.fr", + "role": "developer" + } + ], + "description": "Colors for PHP CLI scripts", + "homepage": "https://github.com/kevinlebrun/colors.php", + "keywords": [ + "cli", + "color", + "colors", + "console", + "shell" + ], + "time": "2016-04-12 20:58:34" }, { "name": "mjackson/optionparser", @@ -105,6 +157,45 @@ "notification-url": "https://packagist.org/downloads/", "time": "2015-03-17 16:57:12" }, + { + "name": "nikita2206/result", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/nikita2206/result.git", + "reference": "7b08cb5a2ecd79843d34aec3902b0ea973a92473" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikita2206/result/zipball/7b08cb5a2ecd79843d34aec3902b0ea973a92473", + "reference": "7b08cb5a2ecd79843d34aec3902b0ea973a92473", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "~4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Result\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nikita Nefedov", + "email": "inefedor@gmail.com" + } + ], + "description": "Result data type (just like in Rust)", + "time": "2016-03-25 16:02:11" + }, { "name": "pear/console_table", "version": "dev-master", @@ -248,39 +339,182 @@ "homepage": "https://github.com/dxw/phar-install", "time": "2016-02-17 13:55:06" }, + { + "name": "mikey179/vfsStream", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/mikey179/vfsStream.git", + "reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mikey179/vfsStream/zipball/0247f57b2245e8ad2e689d7cee754b45fbabd592", + "reference": "0247f57b2245e8ad2e689d7cee754b45fbabd592", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "org\\bovigo\\vfs\\": "src/main/php" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Frank Kleine", + "homepage": "http://frankkleine.de/", + "role": "Developer" + } + ], + "description": "Virtual file system to mock the real file system in unit tests.", + "homepage": "http://vfs.bovigo.org/", + "time": "2016-07-18 14:02:57" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27 11:43:31" + }, { "name": "phpdocumentor/reflection-docblock", - "version": "2.0.4", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", - "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" }, - "suggest": { - "dflydev/markdown": "~1.0", - "erusev/parsedown": "~1.0" + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-06-10 09:48:41" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { - "psr-0": { - "phpDocumentor": [ + "psr-4": { + "phpDocumentor\\Reflection\\": [ "src/" ] } @@ -292,10 +526,10 @@ "authors": [ { "name": "Mike van Riel", - "email": "mike.vanriel@naenius.com" + "email": "me@mikevanriel.com" } ], - "time": "2015-02-03 12:10:50" + "time": "2016-06-10 07:14:17" }, { "name": "phpspec/prophecy", @@ -303,23 +537,23 @@ "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "b02221e42163be673f9b44a0bc92a8b4907a7c6d" + "reference": "d883566b83ae601a50b38b249e92450dc57cf875" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b02221e42163be673f9b44a0bc92a8b4907a7c6d", - "reference": "b02221e42163be673f9b44a0bc92a8b4907a7c6d", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d883566b83ae601a50b38b249e92450dc57cf875", + "reference": "d883566b83ae601a50b38b249e92450dc57cf875", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "~2.0", - "sebastian/comparator": "~1.1", - "sebastian/recursion-context": "~1.0" + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0" }, "require-dev": { - "phpspec/phpspec": "~2.0" + "phpspec/phpspec": "^2.0" }, "type": "library", "extra": { @@ -357,7 +591,7 @@ "spy", "stub" ], - "time": "2016-02-21 17:41:21" + "time": "2016-07-19 16:08:43" }, { "name": "phpunit/php-code-coverage", @@ -511,21 +745,24 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.7", + "version": "1.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", - "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, "type": "library", "autoload": { "classmap": [ @@ -548,7 +785,7 @@ "keywords": [ "timer" ], - "time": "2015-06-21 08:01:12" + "time": "2016-05-12 18:03:57" }, { "name": "phpunit/php-token-stream", @@ -605,12 +842,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "78e05b2c945073d0cf59139b7e654aeec7b1ac1c" + "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/78e05b2c945073d0cf59139b7e654aeec7b1ac1c", - "reference": "78e05b2c945073d0cf59139b7e654aeec7b1ac1c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c062dddcb68e44b563f66ee319ddae2b5a322a90", + "reference": "c062dddcb68e44b563f66ee319ddae2b5a322a90", "shasum": "" }, "require": { @@ -669,7 +906,7 @@ "testing", "xunit" ], - "time": "2016-04-01 17:54:37" + "time": "2016-07-21 06:48:14" }, { "name": "phpunit/phpunit-mock-objects", @@ -845,16 +1082,16 @@ }, { "name": "sebastian/environment", - "version": "dev-master", + "version": "1.3.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf" + "reference": "2292b116f43c272ff4328083096114f84ea46a56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", - "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/2292b116f43c272ff4328083096114f84ea46a56", + "reference": "2292b116f43c272ff4328083096114f84ea46a56", "shasum": "" }, "require": { @@ -891,7 +1128,7 @@ "environment", "hhvm" ], - "time": "2016-02-26 18:40:46" + "time": "2016-05-04 07:59:13" }, { "name": "sebastian/exporter", @@ -899,12 +1136,12 @@ "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "f88f8936517d54ae6d589166810877fb2015d0a2" + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f88f8936517d54ae6d589166810877fb2015d0a2", - "reference": "f88f8936517d54ae6d589166810877fb2015d0a2", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", "shasum": "" }, "require": { @@ -958,7 +1195,7 @@ "export", "exporter" ], - "time": "2015-08-09 04:23:41" + "time": "2016-06-17 09:04:28" }, { "name": "sebastian/global-state", @@ -1105,21 +1342,27 @@ "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "407e31ad9742ace5c3d01642f02a3b2e6062bae5" + "reference": "cd3288cd749886aa1ff43038f17db6494e883262" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/407e31ad9742ace5c3d01642f02a3b2e6062bae5", - "reference": "407e31ad9742ace5c3d01642f02a3b2e6062bae5", + "url": "https://api.github.com/repos/symfony/yaml/zipball/cd3288cd749886aa1ff43038f17db6494e883262", + "reference": "cd3288cd749886aa1ff43038f17db6494e883262", "shasum": "" }, "require": { "php": ">=5.5.9" }, + "require-dev": { + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -1146,7 +1389,57 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-03-30 14:44:34" + "time": "2016-07-17 14:08:33" + }, + { + "name": "webmozart/assert", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "3a8e045064f294992a13966b6c892fb9d64853a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/3a8e045064f294992a13966b6c892fb9d64853a3", + "reference": "3a8e045064f294992a13966b6c892fb9d64853a3", + "shasum": "" + }, + "require": { + "php": "^5.3.3|^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2016-03-04 13:27:44" } ], "aliases": [], @@ -1154,7 +1447,10 @@ "stability-flags": { "dxw/rubbishthorclone": 20, "aura/autoload": 20, - "dxw/phar-install": 20 + "nikita2206/result": 20, + "kevinlebrun/colors.php": 20, + "dxw/phar-install": 20, + "mikey179/vfsstream": 20 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/phpunit.xml b/phpunit.xml index 0b19677..dd3da50 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,4 +1,4 @@ - + tests diff --git a/readme.md b/readme.md index d52c81b..c32ca52 100644 --- a/readme.md +++ b/readme.md @@ -7,13 +7,13 @@ This project is a framework for building WordPress applications that eases deplo Whippet has a few basic goals: 1. Allowing proper build steps to take place, that automate build tasks both during development and deployment -2. Properly managing plugins, allowing them to be version controlled and easily updated +2. Properly managing plugins and themes, allowing them to be version controlled and easily updated 3. Managing the creation of releases, including rollbacks 4. Automating the generation of commonly required objects like new applications and new themes 5. Facilitating automated testing 6. Allowing structured test data to be distributed as part of the codebase -At the moment, Whippet can manages plugins and releases and compile stylesheets in Whippet-enabled themes. +At the moment, Whippet can manages plugins and themes and releases and compile stylesheets in Whippet-enabled themes. During development, whippet is designed to be used in conjunction with [Whippet Server](https://github.com/dxw/whippet-server). These projects will be combined at some point in the future. @@ -83,9 +83,9 @@ To change the version, replace "master" with the version you'd like: "revision": "4.1.1" ``` -### Add plugins +### Add plugins and themes -If you're using any plugins from the codex, you should add them to your ```plugins``` file. For more information, see the [Plugins section](#plugins). +If you're using any plugins or themes from the codex, you should add them to your `whippet.json` file. For more information, see the [Plugins section](#plugins). ### Give yourself some credit! @@ -162,76 +162,91 @@ An application that uses Whippet must have the following directory structure, an Note: At the moment, Whippet assumes it is running within dxw's infrastructure, and makes some assumptions accordingly. If you run into a problem where this may be the cause, please open an issue. -To manage plugins using Whippet, you make entries in the Plugins file in the root of the application. +To manage plugins and themes using Whippet, you make entries in the `whippet.json` file in the root of the application. -The first line of the file should specify a source for plugins. The source should be a base url for a git repo. +The file should specify a source for plugins and themes. The source should be a base url for a git repo. -If you are a dxw customer, the source will be ```git@git.dxw.net:wordpress-plugins/```. If not, we suggest using ```https://github.com/wp-plugins```. +If you are a dxw customer, the sources will be `git@git.dxw.net:wordpress-plugins/` and `git@git.dxw.net:wordpress-themes/`. If not, we suggest using `https://github.com/wp-plugins` for plugins. -Subsequent lines should specify plugins that you want to install. They consist of the name of the plugin followed by an equals sign, followed optionally by a tag or branch name. Example: +The rest of the file should specify plugins and themes that you want to install. Example: ``` -source = "git@git.dxw.net:wordpress-plugins/" - -akismet= +{ + "src": { + "plugins": "git@git.dxw.net:wordpress-plugins/", + "themes": "git@git.dxw.net:wordpress-themes/" + }, + "plugins": [ + {"name": "akismet"} + ], + "themes": [ + {"name": "twentyfourteen"}, + {"name": "twentysixteen"}, + {"name": "twentyten"} + ] +} ``` -The ```akismet=``` instructs Whippet (on request) to install the most recent version of Akismet available in the repo. Whippet will determine a valid repo URL for the akismet plugin by appending it to the source. In this example: +The `{"name": "akismet"}` instructs Whippet (on request) to install the most recent version of Akismet available in the repo. Whippet will determine a valid repo URL for the akismet plugin by appending the name to the source. In this example: ``` git@git.dxw.net:wordpress-plugins/akismet ``` -You can also specify a particular label or branch that you want to use. Generally, this will either be master (the default) or a tag (for a specific version). So you can do: +You can also specify a particular label or branch that you want to use. Generally, this will either be master (the default) or a tag (for a specific version), but you can use any git reference. So you can do: ``` -akismet = v3.0.0 +{ + "name": "akismet", + "ref": "v1.1" +} ``` -Which will cause Whippet to install the plugin at the commit with that tag. If you use a branch: +Which will cause Whippet to install the plugin at the commit with that tag or branch. -``` -akismet = master -``` - -Then whippet's behaviour will vary depending on what command you run (see below). +Finally, you can also specify a repo for an individual plugin or theme explicitly: -Finally, you can also specify a repo for an individual plugin explicitly: +- Pull version 3.0.0 from your own special repo: ``` -; Pull version 3.0.0 from your own special repo -akismet = 3.0.0, git@my-git-server.com:akismet +{ + "name": "akismet", + "ref": "v3.0.0", + "src": "git@my-git-server.com:akismet" +} +``` -; Or, pull master: -akismet = master, git@my-git-server.com:akismet +- Or, pull master: -; This works too: -akismet = , git@my-git-server.com:akismet +``` +{ + "name": "akismet", + "ref": "master", + "src": "git@my-git-server.com:akismet" +} ``` -### whippet plugins install - -When run for the first time, this command will install all the plugins in your Plugins file, at the most -recent commits that exist in the remote for branch or tag you specify (or master, if not specified.) The -hashes for these commits will be saved in plugins.lock, which you should commit into git. +- This works too: -When run on subsequent occasions, this command will: +``` +{ + "name": "akismet", + "src": "git@my-git-server.com:akismet" +} +``` -1. Check for plugins that have been removed from your Plugins file, and delete them from the application -2. Check for changes to the Plugins file, and update, add or remove plugins as specified -3. Check for plugins that have been added to your Plugins file, and clone them +### whippet deps update -Critically, if no changes have been made to the Plugins file, whippet plugin install will always install -the commits specified in plugins.lock; ie, the most recent versions that were available at the time the -plugins were last installed. +This command will: -### whippet plugins upgrade +1. Check the commit hash for each ref of each repo specified in `whippet.json` +2. Update `whippet.lock` +3. Update `.gitignore` with the plugins/themes installed, and remove plugins/themes that are removed from `whippet.json` +4. Run `whippet deps install` -This command checks to see if the branch or tag in the Plugins file has a newer commit on the remote repo than -is installed locally, and if so, updates the installed plugin to the newest one available on the remote. +### whippet plugins install -It is used where the Plugins file refers to a branch (either explicitly, or by leaving it blank and -defaulting to master) and you wish to update the locally installed version to the newest one available. +This command will run through the items in `whippet.lock` and clone any missing plugins/themes, or fetch and checkout. ## Deploys @@ -250,7 +265,7 @@ mkdir /var/local/myapp/releases cp /path/to/your/wp-config.php /var/local/myapp/shared/ ``` -When you deploy, Whippet will make sure your app is up to date (per your plugins.lock), create a new release in releases, and create a symlink that points to it (in this example, at /var/local/myapp/current). +When you deploy, Whippet will make sure your app is up to date (per your `whippet.lock`), create a new release in releases, and create a symlink that points to it (in this example, at /var/local/myapp/current). You can then configure your webserver to use the current symlink as your document root, and your application should be available. diff --git a/src/Dependencies/Installer.php b/src/Dependencies/Installer.php new file mode 100644 index 0000000..b0fd85c --- /dev/null +++ b/src/Dependencies/Installer.php @@ -0,0 +1,86 @@ +factory = $factory; + $this->dir = $dir; + } + + public function install() + { + $result = $this->loadWhippetFiles(); + if ($result->isErr()) { + return $result; + } + + $count = 0; + + foreach (['themes', 'plugins'] as $type) { + foreach ($this->lockFile->getDependencies($type) as $dep) { + $result = $this->installDependency($type, $dep); + if ($result->isErr()) { + return $result; + } + + ++$count; + } + } + + if ($count === 0) { + echo "whippet.lock contains nothing to install\n"; + } + + return \Result\Result::ok(); + } + + private function loadWhippetFiles() + { + if (!is_file($this->dir.'/whippet.json')) { + return \Result\Result::err('whippet.json not found'); + } + + $result = $this->factory->callStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $this->dir.'/whippet.lock'); + if ($result->isErr()) { + return \Result\Result::err(sprintf('whippet.lock: %s', $result->getErr())); + } + $this->lockFile = $result->unwrap(); + + $hash = sha1(file_get_contents($this->dir.'/whippet.json')); + if ($this->lockFile->getHash() !== $hash) { + return \Result\Result::err('mismatched hash - run `whippet dependencies update` first'); + } + + return \Result\Result::ok(); + } + + private function installDependency($type, $dep) + { + $path = $this->dir.'/wp-content/'.$type.'/'.$dep['name']; + + $git = $this->factory->newInstance('\\Dxw\\Whippet\\Git\\Git', $path); + + if (!$git->is_repo()) { + echo sprintf("[Adding %s/%s]\n", $type, $dep['name']); + $result = $git->clone_repo($dep['src']); + + if ($result === false) { + return \Result\Result::err('could not clone repository'); + } + } else { + echo sprintf("[Checking %s/%s]\n", $type, $dep['name']); + } + + $result = $git->checkout($dep['revision']); + if ($result === false) { + return \Result\Result::err('could not checkout revision'); + } + + return \Result\Result::ok(); + } +} diff --git a/src/Dependencies/Migration.php b/src/Dependencies/Migration.php new file mode 100644 index 0000000..ce28d75 --- /dev/null +++ b/src/Dependencies/Migration.php @@ -0,0 +1,153 @@ +factory = $factory; + $this->dir = $dir; + } + + public function migrate() + { + $result = $this->loadFiles(); + if ($result->isErr()) { + return $result; + } + + $whippetData = [ + 'src' => [ + 'plugins' => $this->pluginsFile['source'], + ], + 'plugins' => [ + ], + ]; + + foreach ($this->pluginsFile['plugins'] as $name => $data) { + $whippetData['plugins'][] = $this->getPlugin($name, $data); + } + + $whippetJson = $this->factory->newInstance('\\Dxw\\Whippet\\Files\\WhippetJson', $whippetData); + $whippetJson->saveToPath($this->dir.'/whippet.json'); + + return \Result\Result::ok(); + } + + private function loadFiles() + { + if (is_file($this->dir.'/whippet.json')) { + return \Result\Result::err('will not overwrite existing whippet.json'); + } + + if (!is_file($this->dir.'/plugins')) { + return \Result\Result::err('plugins file not found in current working directory'); + } + + $result = $this->parsePluginsFile(file_get_contents($this->dir.'/plugins')); + if ($result->isErr()) { + return $result; + } + $this->pluginsFile = $result->unwrap(); + + return \Result\Result::ok(); + } + + private function getPlugin($name, $data) + { + $newPlugin = [ + 'name' => $name, + ]; + + if ($data->revision !== 'master') { + $newPlugin['ref'] = $data->revision; + } + + if ($data->repository !== '') { + $newPlugin['src'] = $data->repository; + } + + return $newPlugin; + } + + // Copied/pasted from ManifestIo because that class doesn't expose the source line + // + // Beware of untested code + private function parsePluginsFile($raw_file) + { + // Check for #-comments + $lines = explode("\n", $raw_file); + foreach ($lines as $line) { + if (preg_match('/^\s*#/', $line)) { + return \Result\Result::err('Comments beginning with # are not permitted'); + } + } + + $plugins = parse_ini_string($raw_file); + + if (!is_array($plugins)) { + return \Result\Result::err('Unable to parse Plugins file'); + } + + // Got plugins - turn names to sources + $source = $append = ''; + $plugins_manifest = new \stdClass(); + + foreach ($plugins as $plugin => $data) { + // + // Special lines + // + + if ($plugin == 'source') { + if (empty($data)) { + return \Result\Result::err("Source is empty. It should just specify a repo root:\n\n source = 'git@git.dxw.net:wordpress-plugins/'\n\nWhippet will attempt to find a source for your plugins by appending the plugin name to this URL."); + } + + $source = $data; + continue; + } + + if ($plugin == 'append') { + $append = $data; + continue; + } + + $repository = $revision = ''; + + // + // Everything else should be a plugin + // + + // First see if there is data. + if (!empty($data)) { + // Format: LABEL[, REPO] + if (strpos($data, ',') !== false) { + list($revision, $repository) = explode(',', $data); + } else { + $revision = $data; + } + } + + // if (empty($repository)) { + // $repository = "{$source}{$plugin}{$append}"; + // } + + if (empty($revision)) { + $revision = 'master'; + } + + // We should now have repo and revision + $plugins_manifest->$plugin = new \stdClass(); + $plugins_manifest->$plugin->repository = $repository; + $plugins_manifest->$plugin->revision = $revision; + } + + return \Result\Result::ok([ + 'source' => $source, + 'plugins' => $plugins_manifest, + ]); + } +} diff --git a/src/Dependencies/Updater.php b/src/Dependencies/Updater.php new file mode 100644 index 0000000..40eacc0 --- /dev/null +++ b/src/Dependencies/Updater.php @@ -0,0 +1,134 @@ +factory = $factory; + $this->dir = $dir; + } + + public function update() + { + $result = $this->loadWhippetFiles(); + if ($result->isErr()) { + return $result; + } + + $this->updateHash(); + $this->loadGitignore(); + + $count = 0; + + foreach (['themes', 'plugins'] as $type) { + foreach ($this->jsonFile->getDependencies($type) as $dep) { + echo sprintf("[Updating %s/%s]\n", $type, $dep['name']); + + $result = $this->addDependencyToLockfile($type, $dep); + if ($result->isErr()) { + return $result; + } + + $this->addDependencyToGitignore($type, $dep['name']); + + ++$count; + } + } + + $this->lockFile->saveToPath($this->dir.'/whippet.lock'); + $this->gitignore->save_ignores(array_unique($this->ignores)); + + if ($count === 0) { + echo "whippet.json contains no dependencies\n"; + } + + return \Result\Result::ok(); + } + + private function loadWhippetFiles() + { + $result = $this->factory->callStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $this->dir.'/whippet.json'); + if ($result->isErr()) { + return \Result\Result::err(sprintf('whippet.json: %s', $result->getErr())); + } + $this->jsonFile = $result->unwrap(); + + $result = $this->factory->callStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $this->dir.'/whippet.lock'); + if ($result->isErr()) { + $this->lockFile = $this->factory->newInstance('\\Dxw\\Whippet\\Files\\WhippetLock', []); + } else { + $this->lockFile = $result->unwrap(); + } + + return \Result\Result::ok(); + } + + private function updateHash() + { + $jsonHash = sha1(file_get_contents($this->dir.'/whippet.json')); + $this->lockFile->setHash($jsonHash); + } + + private function loadGitignore() + { + $this->gitignore = $this->factory->newInstance('\\Dxw\\Whippet\\Git\\Gitignore', (string) $this->dir); + + $this->ignores = []; + if (is_file($this->dir.'/.gitignore')) { + $this->ignores = $this->gitignore->get_ignores(); + } + + // Iterate through locked dependencies and remove from gitignore + foreach (['themes', 'plugins'] as $type) { + foreach ($this->lockFile->getDependencies($type) as $dep) { + $line = $this->getGitignoreDependencyLine($type, $dep['name']); + $index = array_search($line, $this->ignores); + if ($index !== false) { + unset($this->ignores[$index]); + } + } + } + } + + private function addDependencyToGitignore($type, $name) + { + $this->ignores[] = $this->getGitignoreDependencyLine($type, $name); + } + + private function getGitignoreDependencyLine($type, $name) + { + return '/wp-content/'.$type.'/'.$name."\n"; + } + + private function addDependencyToLockfile($type, array $dep) + { + if (isset($dep['src'])) { + $src = $dep['src']; + } else { + $sources = $this->jsonFile->getSources(); + if (!isset($sources[$type])) { + return \Result\Result::err('missing sources'); + } + $src = $sources[$type].$dep['name']; + } + + $ref = 'master'; + if (isset($dep['ref'])) { + $ref = $dep['ref']; + } + + $commitResult = $this->factory->callStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', $src, $ref); + + if ($commitResult->isErr()) { + return \Result\Result::err(sprintf('git command failed: %s', $commitResult->getErr())); + } + + $this->lockFile->addDependency($type, $dep['name'], $src, $commitResult->unwrap()); + + return \Result\Result::ok(); + } +} diff --git a/src/Factory.php b/src/Factory.php new file mode 100644 index 0000000..fc55b83 --- /dev/null +++ b/src/Factory.php @@ -0,0 +1,25 @@ +newInstanceArgs($args); + } + + public function callStatic() + { + $args = func_get_args(); + $className = array_shift($args); + $methodName = array_shift($args); + + return call_user_func_array([$className, $methodName], $args); + } +} diff --git a/src/Files/Base.php b/src/Files/Base.php new file mode 100644 index 0000000..ef3f8c0 --- /dev/null +++ b/src/Files/Base.php @@ -0,0 +1,35 @@ +data = $data; + } + + public function saveToPath(/* string */ $path) + { + file_put_contents($path, json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n"); + } +} diff --git a/src/Files/WhippetJson.php b/src/Files/WhippetJson.php new file mode 100644 index 0000000..96e7359 --- /dev/null +++ b/src/Files/WhippetJson.php @@ -0,0 +1,20 @@ +data[$type])) { + return $this->data[$type]; + } else { + return []; + } + } + + public function getSources() + { + return $this->data['src']; + } +} diff --git a/src/Files/WhippetLock.php b/src/Files/WhippetLock.php new file mode 100644 index 0000000..8df50ec --- /dev/null +++ b/src/Files/WhippetLock.php @@ -0,0 +1,34 @@ +data[$type])) { + return []; + } + + return $this->data[$type]; + } + + public function getHash() + { + return $this->data['hash']; + } + + public function setHash(/* string */ $hash) + { + $this->data['hash'] = $hash; + } + + public function addDependency(/* string */ $type, /* string */ $name, /* string */ $src, /* string */ $revision) + { + $this->data[$type][] = [ + 'name' => $name, + 'src' => $src, + 'revision' => $revision, + ]; + } +} diff --git a/src/Git/Git.php b/src/Git/Git.php index da91fc0..c524138 100644 --- a/src/Git/Git.php +++ b/src/Git/Git.php @@ -326,4 +326,19 @@ public function get_tmpdir($in_dir = false) return $tmp_dir; } + + public static function ls_remote($repo, $ref) + { + exec(sprintf('git ls-remote %s %s', escapeshellarg($repo), escapeshellarg($ref)), $output, $return); + + if ($return !== 0) { + return \Result\Result::err('git error'); + } + + if (count($output) === 0) { + return \Result\Result::err('ref not found'); + } + + return \Result\Result::ok(explode("\t", $output[0])[0]); + } }; diff --git a/src/Modules/Dependencies.php b/src/Modules/Dependencies.php new file mode 100644 index 0000000..1a9e109 --- /dev/null +++ b/src/Modules/Dependencies.php @@ -0,0 +1,61 @@ +factory = new \Dxw\Whippet\Factory(); + $this->projectDirectory = \Dxw\Whippet\ProjectDirectory::find(getcwd()); + } + + public function commands() + { + $this->command('install', 'Installs dependencies'); + $this->command('update', 'Updates dependencies to their latest versions'); + $this->command('migrate', 'Converts legacy plugins file to whippet.json'); + } + + private function exitIfError(\Result\Result $result) + { + if ($result->isErr()) { + echo sprintf("ERROR: %s\n", $result->getErr()); + exit(1); + } + } + + private function getDirectory() + { + $this->exitIfError($this->projectDirectory); + + return $this->projectDirectory->unwrap(); + } + + public function install() + { + $dir = $this->getDirectory(); + $installer = new \Dxw\Whippet\Dependencies\Installer($this->factory, $dir); + + $this->exitIfError($installer->install()); + } + + public function update() + { + $dir = $this->getDirectory(); + $updater = new \Dxw\Whippet\Dependencies\Updater($this->factory, $dir); + $installer = new \Dxw\Whippet\Dependencies\Installer($this->factory, $dir); + + $this->exitIfError($updater->update()); + $this->exitIfError($installer->install()); + } + + public function migrate() + { + $dir = new \Dxw\Whippet\ProjectDirectory(getcwd()); + $migration = new \Dxw\Whippet\Dependencies\Migration($this->factory, $dir); + $this->exitIfError($migration->migrate()); + } +} diff --git a/src/Modules/Helpers/WhippetHelpers.php b/src/Modules/Helpers/WhippetHelpers.php index dcfb3a0..885e751 100644 --- a/src/Modules/Helpers/WhippetHelpers.php +++ b/src/Modules/Helpers/WhippetHelpers.php @@ -21,8 +21,10 @@ public function whippet_init() { if (!$this->plugins_manifest_file = $this->find_file('plugins')) { if (!$this->plugins_manifest_file = $this->find_file('Plugins')) { - echo "Unable to find plugins manifest file\n"; - exit(1); + if (!$this->plugins_manifest_file = $this->find_file('whippet.json')) { + echo "Unable to find whippet.json or plugins manifest file\n"; + exit(1); + } } } $this->project_dir = dirname($this->plugins_manifest_file); diff --git a/src/Modules/Plugin.php b/src/Modules/Plugin.php index 5520faa..12e6ce3 100644 --- a/src/Modules/Plugin.php +++ b/src/Modules/Plugin.php @@ -13,6 +13,36 @@ public function commands() $this->command('upgrade [PLUGIN]', 'Upgrades PLUGIN to the most recent available version, or to the version specified in your Plugin file.'); } + private function deprecationNotice($internal) + { + if ($internal) { + $this->warningText([ + 'Notice: Using a deprecated `plugins` file.', + ]); + } else { + $this->warningText([ + 'The plugins subcommand is deprecated and will be removed in a future release.', + '', + 'To migrate a `plugins` file to a `whippet.json` file, run the following:', + ' $ whippet deps migrate', + '', + 'Once you have a `whippet.json` file, you can run the following instead of `whippet plugins upgrade`:', + ' $ whippet deps update', + '', + 'And the following instead of `whippet plugins install`:', + ' $ whippet deps install', + '', + '', + ]); + } + } + + private function warningText($lines) + { + $c = new \Colors\Color(); + echo $c(implode("\n", $lines))->bg('red')->fg('white')."\n"; + } + /* * Commands */ @@ -24,8 +54,9 @@ public function commands() * This command will not change an installed commit unless the revision has changed. It * just makes sure that what's in the project is what's in the file. */ - public function install() + public function install($internal = false) { + $this->deprecationNotice($internal); $this->whippet_init(); $this->load_plugins_manifest(); $this->load_plugins_lock(); @@ -218,6 +249,8 @@ public function install() $gitignore->save_ignores($ignores); echo "Completed successfully\n"; + + return \Result\Result::ok(); } /* @@ -226,6 +259,7 @@ public function install() */ public function upgrade($upgrade_plugin = '') { + $this->deprecationNotice(true); $this->whippet_init(); $this->load_plugins_manifest(); $this->load_plugins_lock(); diff --git a/src/Modules/Release.php b/src/Modules/Release.php index fd521ba..fcd41b0 100644 --- a/src/Modules/Release.php +++ b/src/Modules/Release.php @@ -38,8 +38,16 @@ public function create(&$force) $force = false; } - // Got plugins.lock? - if (!$this->plugins_lock_file || !file_exists($this->plugins_lock_file)) { + // Got whippet.{json,lock} or plugins.lock? + if (is_file($this->project_dir.'/whippet.json') && is_file($this->project_dir.'/whippet.lock')) { + $factory = new \Dxw\Whippet\Factory(); + $installer = new \Dxw\Whippet\Dependencies\Installer( + $factory, + new \Dxw\Whippet\ProjectDirectory($this->project_dir) + ); + } elseif ($this->plugins_lock_file && file_exists($this->plugins_lock_file)) { + $installer = new Plugin(); + } else { echo "Couldn't find plugins.lock in the project directory. (Did you run whippet plugins install?)\n"; die(1); } @@ -76,8 +84,11 @@ public function create(&$force) } // Make sure wp-content is up to date - $plugin = new Plugin(); - $plugin->install(); + $result = $installer->install(true); + if ($result->isErr()) { + echo sprintf("ERROR: %s\n", $result->getErr()); + exit(1); + } // Copy over wp-content $this->recurse_copy("{$this->project_dir}/wp-content", "{$this->release_dir}/wp-content"); diff --git a/src/ProjectDirectory.php b/src/ProjectDirectory.php new file mode 100644 index 0000000..72f1482 --- /dev/null +++ b/src/ProjectDirectory.php @@ -0,0 +1,30 @@ +path = $path; + } + + public function __toString() + { + return $this->path; + } +} diff --git a/src/Whippet.php b/src/Whippet.php index 4f188fd..9abd4c7 100644 --- a/src/Whippet.php +++ b/src/Whippet.php @@ -23,6 +23,8 @@ public function commands() $this->command('migrate OLDPATH NEWPATH', 'Examines an existing wp-content directory and attempts to create an identical Whippet application.'); $this->command('server SERVER_COMMAND', ''); + $this->command('dependencies SUBCOMMAND', 'Manage dependencies (themes, plugins)'); + $this->command('deps SUBCOMMAND', 'Alias for dependencies'); } public function plugins($plugin_command) @@ -75,4 +77,14 @@ public function server($plugin_command) { (new Modules\Server())->start(array_slice($this->argv, 1)); } + + public function dependencies() + { + (new Modules\Dependencies())->start(array_slice($this->argv, 1)); + } + + public function deps() + { + $this->dependencies(); + } }; diff --git a/tests/Helpers.php b/tests/Helpers.php new file mode 100644 index 0000000..275b81a --- /dev/null +++ b/tests/Helpers.php @@ -0,0 +1,120 @@ +factoryNewInstance = []; + $this->factoryCallStatic = []; + } + + private function getWhippetLock(/* string */ $hash, array $dependencyMap) + { + $whippetLock = $this->getMockBuilder('\\Dxw\\Whippet\\Files\\WhippetLock') + ->disableOriginalConstructor() + ->getMock(); + + $whippetLock->method('getHash') + ->willReturn($hash); + + $map = []; + foreach ($dependencyMap as $dependencyType => $return) { + $map[] = [$dependencyType, $return]; + } + + $whippetLock->method('getDependencies') + ->will($this->returnValueMap($map)); + + return $whippetLock; + } + + private function getGit($isRepo, $cloneRepo, $checkout) + { + $git = $this->getMockBuilder('\\Dxw\\Whippet\\Git\\Git') + ->disableOriginalConstructor() + ->getMock(); + + $git->method('is_repo') + ->willReturn($isRepo); + + if ($cloneRepo !== null) { + $return = true; + + if (is_array($cloneRepo)) { + $return = $cloneRepo['return']; + $cloneRepo = $cloneRepo['with']; + } + + $git->expects($this->exactly(1)) + ->method('clone_repo') + ->with($cloneRepo) + ->will($this->returnCallback(function () use ($return) { + echo "git clone output\n"; + + return $return; + })); + } + + if ($checkout !== null) { + $return = true; + + if (is_array($checkout)) { + $return = $checkout['return']; + $checkout = $checkout['with']; + } + + $git->expects($this->exactly(1)) + ->method('checkout') + ->with($checkout) + ->will($this->returnCallback(function () use ($return) { + echo "git checkout output\n"; + + return $return; + })); + } + + return $git; + } + + private function getWhippetJson(array $data) + { + return new \Dxw\Whippet\Files\WhippetJson($data); + } + + private function getFactory() + { + $factory = $this->getMockBuilder('\\Dxw\\Whippet\\Factory') + ->disableOriginalConstructor() + ->getMock(); + + $factory->method('newInstance') + ->will($this->returnValueMap($this->factoryNewInstance)); + + $factory->method('callStatic') + ->will($this->returnValueMap($this->factoryCallStatic)); + + return $factory; + } + + private function addFactoryNewInstance() + { + $this->factoryNewInstance[] = func_get_args(); + } + + private function addFactoryCallStatic() + { + $this->factoryCallStatic[] = func_get_args(); + } + + private function getProjectDirectory($dir) + { + return new \Dxw\Whippet\ProjectDirectory($dir); + } + + private function getDir() + { + $root = \org\bovigo\vfs\vfsStream::setup(); + + return $root->url(); + } +} diff --git a/tests/dependencies/installer_test.php b/tests/dependencies/installer_test.php new file mode 100644 index 0000000..222e30d --- /dev/null +++ b/tests/dependencies/installer_test.php @@ -0,0 +1,248 @@ +getDir(); + file_put_contents($dir.'/whippet.json', 'foobar'); + file_put_contents($dir.'/whippet.lock', 'foobar'); + + $whippetLock = $this->getWhippetLock(sha1('foobar'), [ + 'themes' => [ + [ + 'name' => 'my-theme', + 'src' => 'git@git.dxw.net:wordpress-themes/my-theme', + 'revision' => '27ba906', + ], + ], + 'plugins' => [ + [ + 'name' => 'my-plugin', + 'src' => 'git@git.dxw.net:wordpress-plugins/my-plugin', + 'revision' => '123456', + ], + [ + 'name' => 'another-plugin', + 'src' => 'git@git.dxw.net:wordpress-plugins/another-plugin', + 'revision' => '789abc', + ], + ], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $gitMyTheme = $this->getGit(false, 'git@git.dxw.net:wordpress-themes/my-theme', '27ba906'); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Git', $dir.'/wp-content/themes/my-theme', $gitMyTheme); + $gitMyPlugin = $this->getGit(false, 'git@git.dxw.net:wordpress-plugins/my-plugin', '123456'); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Git', $dir.'/wp-content/plugins/my-plugin', $gitMyPlugin); + $gitAnotherPlugin = $this->getGit(false, 'git@git.dxw.net:wordpress-plugins/another-plugin', '789abc'); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Git', $dir.'/wp-content/plugins/another-plugin', $gitAnotherPlugin); + + $dependencies = new \Dxw\Whippet\Dependencies\Installer( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->install(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals("[Adding themes/my-theme]\ngit clone output\ngit checkout output\n[Adding plugins/my-plugin]\ngit clone output\ngit checkout output\n[Adding plugins/another-plugin]\ngit clone output\ngit checkout output\n", $output); + } + + public function testInstallThemeAlreadyCloned() + { + $dir = $this->getDir(); + file_put_contents($dir.'/whippet.json', 'foobar'); + file_put_contents($dir.'/whippet.lock', 'foobar'); + + mkdir($dir.'/wp-content/themes/my-theme'); + + $whippetLock = $this->getWhippetLock(sha1('foobar'), [ + 'themes' => [ + [ + 'name' => 'my-theme', + 'src' => 'git@git.dxw.net:wordpress-themes/my-theme', + 'revision' => '27ba906', + ], + ], + 'plugins' => [], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $git = $this->getGit(true, null, '27ba906'); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Git', $dir.'/wp-content/themes/my-theme', $git); + + $dependencies = new \Dxw\Whippet\Dependencies\Installer( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->install(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals("[Checking themes/my-theme]\ngit checkout output\n", $output); + } + + public function testInstallMissingWhippetJson() + { + $dir = $this->getDir(); + + $dependencies = new \Dxw\Whippet\Dependencies\Installer( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->install(); + $output = ob_get_clean(); + + $this->assertEquals(true, $result->isErr()); + $this->assertEquals('whippet.json not found', $result->getErr()); + $this->assertEquals('', $output); + } + + public function testInstallMissingWhippetLock() + { + $dir = $this->getDir(); + file_put_contents($dir.'/whippet.json', 'foobar'); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::err('file not found')); + + $dependencies = new \Dxw\Whippet\Dependencies\Installer( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->install(); + $output = ob_get_clean(); + + $this->assertEquals(true, $result->isErr()); + $this->assertEquals('whippet.lock: file not found', $result->getErr()); + $this->assertEquals('', $output); + } + + public function testInstallWrongHash() + { + $dir = $this->getDir(); + file_put_contents($dir.'/whippet.json', 'foobar'); + file_put_contents($dir.'/whippet.lock', 'foobar'); + + $whippetLock = $this->getWhippetLock('123123', []); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $dependencies = new \Dxw\Whippet\Dependencies\Installer( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->install(); + $output = ob_get_clean(); + + $this->assertEquals(true, $result->isErr()); + $this->assertEquals('mismatched hash - run `whippet dependencies update` first', $result->getErr()); + $this->assertEquals('', $output); + } + + public function testInstallCloneFails() + { + $dir = $this->getDir(); + file_put_contents($dir.'/whippet.json', 'foobar'); + file_put_contents($dir.'/whippet.lock', 'foobar'); + + $whippetLock = $this->getWhippetLock(sha1('foobar'), [ + 'themes' => [ + [ + 'name' => 'my-theme', + 'src' => 'git@git.dxw.net:wordpress-themes/my-theme', + 'revision' => '27ba906', + ], + ], + 'plugins' => [], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $gitMyTheme = $this->getGit(false, ['with' => 'git@git.dxw.net:wordpress-themes/my-theme', 'return' => false], null); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Git', $dir.'/wp-content/themes/my-theme', $gitMyTheme); + + $dependencies = new \Dxw\Whippet\Dependencies\Installer( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->install(); + $output = ob_get_clean(); + + $this->assertTrue($result->isErr()); + $this->assertEquals('could not clone repository', $result->getErr()); + $this->assertEquals("[Adding themes/my-theme]\ngit clone output\n", $output); + } + + public function testInstallCheckoutFails() + { + $dir = $this->getDir(); + file_put_contents($dir.'/whippet.json', 'foobar'); + file_put_contents($dir.'/whippet.lock', 'foobar'); + + $whippetLock = $this->getWhippetLock(sha1('foobar'), [ + 'themes' => [ + [ + 'name' => 'my-theme', + 'src' => 'git@git.dxw.net:wordpress-themes/my-theme', + 'revision' => '27ba906', + ], + ], + 'plugins' => [], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $gitMyTheme = $this->getGit(false, 'git@git.dxw.net:wordpress-themes/my-theme', ['with' => '27ba906', 'return' => false]); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Git', $dir.'/wp-content/themes/my-theme', $gitMyTheme); + + $dependencies = new \Dxw\Whippet\Dependencies\Installer( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->install(); + $output = ob_get_clean(); + + $this->assertTrue($result->isErr()); + $this->assertEquals('could not checkout revision', $result->getErr()); + $this->assertEquals("[Adding themes/my-theme]\ngit clone output\ngit checkout output\n", $output); + } + + public function testInstallBlankLockfile() + { + $dir = $this->getDir(); + file_put_contents($dir.'/whippet.json', 'foobar'); + file_put_contents($dir.'/whippet.lock', 'foobar'); + + $whippetLock = $this->getWhippetLock(sha1('foobar'), [ + 'themes' => [], + 'plugins' => [], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $dependencies = new \Dxw\Whippet\Dependencies\Installer( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->install(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals("whippet.lock contains nothing to install\n", $output); + } +} diff --git a/tests/dependencies/migration_test.php b/tests/dependencies/migration_test.php new file mode 100644 index 0000000..bf73e2d --- /dev/null +++ b/tests/dependencies/migration_test.php @@ -0,0 +1,192 @@ +getMockBuilder('\\Dxw\\Whippet\\Files\\WhippetJson') + ->disableOriginalConstructor() + ->getMock(); + + $whippetJson->expects($this->exactly(1)) + ->method('saveToPath') + ->with($path); + + return $whippetJson; + } + + public function testMigrate() + { + $dir = $this->getDir(); + + file_put_contents($dir.'/plugins', implode("\n", [ + 'source = "git@git.dxw.net:wordpress-plugins/"', + 'twitget=', + 'acf-options-page=', + 'new-members-only=', + 'wordpress-importer=', + 'page-links-to=', + 'akismet=', + 'acf-repeater=', + 'advanced-custom-fields=', + 'theme-my-login=', + 'breadcrumb-navxt=', + 'contact-form-7=', + 'wp-realtime-sitemap=', + 'tinymce-advanced=', + 'relevanssi-premium=', + 'jw-player-plugin-for-wordpress=', + 'gravityforms=', + 'unconfirmed=', + 'oauth2-server = ,git@git.dxw.net:dxw-wp-plugins/oauth2-server', + 'network-approve-users = v1.1.1,git@git.dxw.net:dxw-wp-plugins/network-approve-users', + ])); + + $whippetJson = $this->getWhippetJsonExpectSavePath($dir.'/whippet.json'); + + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Files\\WhippetJson', [ + 'src' => [ + 'plugins' => 'git@git.dxw.net:wordpress-plugins/', + ], + 'plugins' => [ + ['name' => 'twitget'], + ['name' => 'acf-options-page'], + ['name' => 'new-members-only'], + ['name' => 'wordpress-importer'], + ['name' => 'page-links-to'], + ['name' => 'akismet'], + ['name' => 'acf-repeater'], + ['name' => 'advanced-custom-fields'], + ['name' => 'theme-my-login'], + ['name' => 'breadcrumb-navxt'], + ['name' => 'contact-form-7'], + ['name' => 'wp-realtime-sitemap'], + ['name' => 'tinymce-advanced'], + ['name' => 'relevanssi-premium'], + ['name' => 'jw-player-plugin-for-wordpress'], + ['name' => 'gravityforms'], + ['name' => 'unconfirmed'], + [ + 'name' => 'oauth2-server', + 'src' => 'git@git.dxw.net:dxw-wp-plugins/oauth2-server', + ], + [ + 'name' => 'network-approve-users', + 'ref' => 'v1.1.1', + 'src' => 'git@git.dxw.net:dxw-wp-plugins/network-approve-users', + ], + ], + ], $whippetJson); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-themes/my-theme', 'v1.4', \Result\Result::ok('27ba906')); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-plugins/my-plugin', 'v1.6', \Result\Result::ok('d961c3d')); + + $migration = new \Dxw\Whippet\Dependencies\Migration( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $migration->migrate(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals('', $output); + } + + public function testMigrateDeprecatedComment() + { + $dir = $this->getDir(); + + file_put_contents($dir.'/plugins', implode("\n", [ + '#bad comment', + 'source = "git@git.dxw.net:wordpress-plugins/"', + 'twitget=', + ])); + + $migration = new \Dxw\Whippet\Dependencies\Migration( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $migration->migrate(); + $output = ob_get_clean(); + + $this->assertTrue($result->isErr()); + $this->assertEquals('Comments beginning with # are not permitted', $result->getErr()); + $this->assertEquals('', $output); + + $this->assertFalse(file_exists($dir.'/whippet.json')); + } + + public function testMigrateMissingSource() + { + $dir = $this->getDir(); + + file_put_contents($dir.'/plugins', implode("\n", [ + 'source=', + ])); + + $migration = new \Dxw\Whippet\Dependencies\Migration( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $migration->migrate(); + $output = ob_get_clean(); + + $this->assertTrue($result->isErr()); + $this->assertEquals("Source is empty. It should just specify a repo root:\n\n source = 'git@git.dxw.net:wordpress-plugins/'\n\nWhippet will attempt to find a source for your plugins by appending the plugin name to this URL.", $result->getErr()); + $this->assertEquals('', $output); + + $this->assertFalse(file_exists($dir.'/whippet.json')); + } + + public function testMigrateMissingPluginsFile() + { + $dir = $this->getDir(); + + $migration = new \Dxw\Whippet\Dependencies\Migration( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $migration->migrate(); + $output = ob_get_clean(); + + $this->assertTrue($result->isErr()); + $this->assertEquals('plugins file not found in current working directory', $result->getErr()); + $this->assertEquals('', $output); + + $this->assertFalse(file_exists($dir.'/whippet.json')); + } + + public function testMigratePreExistingWhippetJson() + { + $dir = $this->getDir(); + touch($dir.'/whippet.json'); + + file_put_contents($dir.'/plugins', implode("\n", [ + 'source = "git@git.dxw.net:wordpress-plugins/"', + 'twitget=', + ])); + + $migration = new \Dxw\Whippet\Dependencies\Migration( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $migration->migrate(); + $output = ob_get_clean(); + + $this->assertTrue($result->isErr()); + $this->assertEquals('will not overwrite existing whippet.json', $result->getErr()); + $this->assertEquals('', $output); + } +} diff --git a/tests/dependencies/updater_test.php b/tests/dependencies/updater_test.php new file mode 100644 index 0000000..d1803c1 --- /dev/null +++ b/tests/dependencies/updater_test.php @@ -0,0 +1,616 @@ +getMockBuilder('\\Dxw\\Whippet\\Git\\Gitignore') + ->disableOriginalConstructor() + ->getMock(); + + $getIgnores = $gitignore->method('get_ignores'); + if ($warnOnGet) { + $getIgnores->will($this->returnCallback(function () { trigger_error('$warOnGet set but not prevented', E_USER_WARNING); })); + } else { + $getIgnores->willReturn($get); + } + + $gitignore->expects($this->exactly($saveIgnores ? 1 : 0)) + ->method('save_ignores') + ->with($save); + + return $gitignore; + } + + private function getWhippetLockWritable(array $addDependency, /* string */ $hash, /* string */ $path, array $getDependencies) + { + $whippetLock = $this->getMockBuilder('\\Dxw\\Whippet\\Files\\WhippetLock') + ->disableOriginalConstructor() + ->getMock(); + + call_user_func_array( + [ + $whippetLock->expects($this->exactly(count($addDependency))) + ->method('addDependency'), + 'withConsecutive', + ], + $addDependency + ); + + $whippetLock->expects($this->exactly(1)) + ->method('setHash') + ->with($hash); + + $whippetLock->expects($this->exactly($path === null ? 0 : 1)) + ->method('saveToPath') + ->with($path); + + if ($getDependencies === []) { + $getDependencies = [ + ['themes', []], + ['plugins', []], + ]; + } + + $whippetLock->method('getDependencies') + ->will($this->returnValueMap($getDependencies)); + + return $whippetLock; + } + + public function testUpdate() + { + $dir = $this->getDir(); + + $whippetJson = $this->getWhippetJson([ + 'src' => [ + 'themes' => 'git@git.dxw.net:wordpress-themes/', + 'plugins' => 'git@git.dxw.net:wordpress-plugins/', + ], + 'themes' => [ + [ + 'name' => 'my-theme', + 'ref' => 'v1.4', + ], + ], + 'plugins' => [ + [ + 'name' => 'my-plugin', + 'ref' => 'v1.6', + ], + ], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $dir.'/whippet.json', \Result\Result::ok($whippetJson)); + + file_put_contents($dir.'/whippet.json', 'foobar'); + + $gitignore = $this->getGitignore([], [ + "/wp-content/themes/my-theme\n", + "/wp-content/plugins/my-plugin\n", + ], true, false); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Gitignore', $dir, $gitignore); + + $whippetLock = $this->getWhippetLockWritable([ + ['themes', 'my-theme', 'git@git.dxw.net:wordpress-themes/my-theme', '27ba906'], + ['plugins', 'my-plugin', 'git@git.dxw.net:wordpress-plugins/my-plugin', 'd961c3d'], + ], sha1('foobar'), $dir.'/whippet.lock', []); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-themes/my-theme', 'v1.4', \Result\Result::ok('27ba906')); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-plugins/my-plugin', 'v1.6', \Result\Result::ok('d961c3d')); + + $dependencies = new \Dxw\Whippet\Dependencies\Updater( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->update(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals("[Updating themes/my-theme]\n[Updating plugins/my-plugin]\n", $output); + } + + public function testUpdateWithExistingGitignore() + { + $dir = $this->getDir(); + touch($dir.'/.gitignore'); + + $whippetJson = $this->getWhippetJson([ + 'src' => [ + 'themes' => 'git@git.dxw.net:wordpress-themes/', + ], + 'themes' => [ + [ + 'name' => 'my-theme', + 'ref' => 'v1.4', + ], + ], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $dir.'/whippet.json', \Result\Result::ok($whippetJson)); + + file_put_contents($dir.'/whippet.json', 'foobar'); + + $gitignore = $this->getGitignore([ + "/wp-content/languages\n", + "/node_modules\n", + "/vendor\n", + ], [ + "/wp-content/languages\n", + "/node_modules\n", + "/vendor\n", + "/wp-content/themes/my-theme\n", + ], true, false); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Gitignore', $dir, $gitignore); + + $whippetLock = $this->getWhippetLockWritable([ + ['themes', 'my-theme', 'git@git.dxw.net:wordpress-themes/my-theme', '27ba906'], + ], sha1('foobar'), $dir.'/whippet.lock', []); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-themes/my-theme', 'v1.4', \Result\Result::ok('27ba906')); + + $dependencies = new \Dxw\Whippet\Dependencies\Updater( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->update(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals("[Updating themes/my-theme]\n", $output); + } + + public function testUpdateWithExistingGitignoreNoDuplication() + { + $dir = $this->getDir(); + touch($dir.'/.gitignore'); + + $whippetJson = $this->getWhippetJson([ + 'src' => [ + 'themes' => 'git@git.dxw.net:wordpress-themes/', + ], + 'themes' => [ + [ + 'name' => 'my-theme', + 'ref' => 'v1.4', + ], + ], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $dir.'/whippet.json', \Result\Result::ok($whippetJson)); + + file_put_contents($dir.'/whippet.json', 'foobar'); + + $gitignore = $this->getGitignore([ + "/wp-content/languages\n", + "/node_modules\n", + "/vendor\n", + "/wp-content/themes/my-theme\n", + ], [ + "/wp-content/languages\n", + "/node_modules\n", + "/vendor\n", + "/wp-content/themes/my-theme\n", + ], true, false); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Gitignore', $dir, $gitignore); + + $whippetLock = $this->getWhippetLockWritable([ + ['themes', 'my-theme', 'git@git.dxw.net:wordpress-themes/my-theme', '27ba906'], + ], sha1('foobar'), $dir.'/whippet.lock', []); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-themes/my-theme', 'v1.4', \Result\Result::ok('27ba906')); + + $dependencies = new \Dxw\Whippet\Dependencies\Updater( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->update(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals("[Updating themes/my-theme]\n", $output); + } + + public function testUpdateFailedGitCommand() + { + $dir = $this->getDir(); + + $whippetJson = $this->getWhippetJson([ + 'src' => [ + 'themes' => 'git@git.dxw.net:wordpress-themes/', + ], + 'themes' => [ + [ + 'name' => 'my-theme', + 'ref' => 'v1.4', + ], + ], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $dir.'/whippet.json', \Result\Result::ok($whippetJson)); + + file_put_contents($dir.'/whippet.json', 'foobar'); + + $gitignore = $this->getGitignore([], [ + "/wp-content/themes/my-theme\n", + ], false, false); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Gitignore', $dir, $gitignore); + + $whippetLock = $this->getWhippetLockWritable([], sha1('foobar'), null, []); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-themes/my-theme', 'v1.4', \Result\Result::err('oh no')); + + $dependencies = new \Dxw\Whippet\Dependencies\Updater( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->update(); + $output = ob_get_clean(); + + $this->assertTrue($result->isErr()); + $this->assertEquals('git command failed: oh no', $result->getErr()); + $this->assertEquals("[Updating themes/my-theme]\n", $output); + } + + public function testUpdateWithExplicitSrc() + { + $dir = $this->getDir(); + + $whippetJson = $this->getWhippetJson([ + 'src' => [ + 'themes' => 'git@git.dxw.net:wordpress-themes/', + ], + 'themes' => [ + [ + 'name' => 'my-theme', + 'ref' => 'v1.4', + 'src' => 'foobar', + ], + ], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $dir.'/whippet.json', \Result\Result::ok($whippetJson)); + + file_put_contents($dir.'/whippet.json', 'foobar'); + + $gitignore = $this->getGitignore([], [ + "/wp-content/themes/my-theme\n", + ], true, false); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Gitignore', $dir, $gitignore); + + $whippetLock = $this->getWhippetLockWritable([ + ['themes', 'my-theme', 'foobar', '27ba906'], + ], sha1('foobar'), $dir.'/whippet.lock', []); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'foobar', 'v1.4', \Result\Result::ok('27ba906')); + + $dependencies = new \Dxw\Whippet\Dependencies\Updater( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->update(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals("[Updating themes/my-theme]\n", $output); + } + + public function testUpdateWithoutRef() + { + $dir = $this->getDir(); + + $whippetJson = $this->getWhippetJson([ + 'src' => [ + 'themes' => 'git@git.dxw.net:wordpress-themes/', + ], + 'themes' => [ + [ + 'name' => 'my-theme', + ], + ], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $dir.'/whippet.json', \Result\Result::ok($whippetJson)); + + file_put_contents($dir.'/whippet.json', 'foobar'); + + $gitignore = $this->getGitignore([], [ + "/wp-content/themes/my-theme\n", + ], true, false); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Gitignore', $dir, $gitignore); + + $whippetLock = $this->getWhippetLockWritable([ + ['themes', 'my-theme', 'git@git.dxw.net:wordpress-themes/my-theme', '27ba906'], + ], sha1('foobar'), $dir.'/whippet.lock', []); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-themes/my-theme', 'master', \Result\Result::ok('27ba906')); + + $dependencies = new \Dxw\Whippet\Dependencies\Updater( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->update(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals("[Updating themes/my-theme]\n", $output); + } + + public function testUpdateBlankJsonfile() + { + $dir = $this->getDir(); + + $whippetJson = $this->getWhippetJson([]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $dir.'/whippet.json', \Result\Result::ok($whippetJson)); + + file_put_contents($dir.'/whippet.json', 'foobar'); + + $gitignore = $this->getGitignore([], [], true, false); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Gitignore', $dir, $gitignore); + + $whippetLock = $this->getWhippetLockWritable([], sha1('foobar'), $dir.'/whippet.lock', []); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $dependencies = new \Dxw\Whippet\Dependencies\Updater( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->update(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals("whippet.json contains no dependencies\n", $output); + } + + public function testUpdateNoGitignore() + { + $dir = $this->getDir(); + + $whippetJson = $this->getWhippetJson([ + 'src' => [ + 'themes' => 'git@git.dxw.net:wordpress-themes/', + 'plugins' => 'git@git.dxw.net:wordpress-plugins/', + ], + 'themes' => [ + [ + 'name' => 'my-theme', + 'ref' => 'v1.4', + ], + ], + 'plugins' => [ + [ + 'name' => 'my-plugin', + 'ref' => 'v1.6', + ], + ], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $dir.'/whippet.json', \Result\Result::ok($whippetJson)); + + file_put_contents($dir.'/whippet.json', 'foobar'); + + $gitignore = $this->getGitignore([], [ + "/wp-content/themes/my-theme\n", + "/wp-content/plugins/my-plugin\n", + ], true, true); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Gitignore', $dir, $gitignore); + + $whippetLock = $this->getWhippetLockWritable([ + ['themes', 'my-theme', 'git@git.dxw.net:wordpress-themes/my-theme', '27ba906'], + ['plugins', 'my-plugin', 'git@git.dxw.net:wordpress-plugins/my-plugin', 'd961c3d'], + ], sha1('foobar'), $dir.'/whippet.lock', []); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-themes/my-theme', 'v1.4', \Result\Result::ok('27ba906')); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-plugins/my-plugin', 'v1.6', \Result\Result::ok('d961c3d')); + + $dependencies = new \Dxw\Whippet\Dependencies\Updater( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->update(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals("[Updating themes/my-theme]\n[Updating plugins/my-plugin]\n", $output); + } + + public function testUpdateRemoveFromGitignore() + { + $dir = $this->getDir(); + touch($dir.'/.gitignore'); + + $whippetJson = $this->getWhippetJson([ + 'src' => [ + 'themes' => 'git@git.dxw.net:wordpress-themes/', + ], + 'themes' => [ + [ + 'name' => 'my-theme', + 'ref' => 'v1.4', + ], + ], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $dir.'/whippet.json', \Result\Result::ok($whippetJson)); + + file_put_contents($dir.'/whippet.json', 'foobar'); + + $gitignore = $this->getGitignore([ + "/wp-content/themes/my-theme\n", + "/wp-content/plugins/unmanaged-plugin\n", + "/wp-content/plugins/removed-plugin\n", + ], [ + "/wp-content/themes/my-theme\n", + "/wp-content/plugins/unmanaged-plugin\n", + ], true, false); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Gitignore', $dir, $gitignore); + + $whippetLock = $this->getWhippetLockWritable([ + ['themes', 'my-theme', 'git@git.dxw.net:wordpress-themes/my-theme', '27ba906'], + ], sha1('foobar'), $dir.'/whippet.lock', [ + ['themes', []], + ['plugins', [ + ['name' => 'removed-plugin'], + ]], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-themes/my-theme', 'v1.4', \Result\Result::ok('27ba906')); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-plugins/my-plugin', 'v1.6', \Result\Result::ok('d961c3d')); + + $dependencies = new \Dxw\Whippet\Dependencies\Updater( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->update(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals("[Updating themes/my-theme]\n", $output); + } + + public function testUpdateBubbleErrors() + { + $dir = $this->getDir(); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $dir.'/whippet.json', \Result\Result::err('a WhippetJson error')); + + $dependencies = new \Dxw\Whippet\Dependencies\Updater( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->update(); + $output = ob_get_clean(); + + $this->assertTrue($result->isErr()); + $this->assertEquals('whippet.json: a WhippetJson error', $result->getErr()); + $this->assertEquals('', $output); + } + + public function testUpdateNoExistingWhippetLock() + { + $dir = $this->getDir(); + + $whippetJson = $this->getWhippetJson([ + 'src' => [ + 'themes' => 'git@git.dxw.net:wordpress-themes/', + 'plugins' => 'git@git.dxw.net:wordpress-plugins/', + ], + 'themes' => [ + [ + 'name' => 'my-theme', + 'ref' => 'v1.4', + ], + ], + 'plugins' => [ + [ + 'name' => 'my-plugin', + 'ref' => 'v1.6', + ], + ], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $dir.'/whippet.json', \Result\Result::ok($whippetJson)); + + file_put_contents($dir.'/whippet.json', 'foobar'); + + $gitignore = $this->getGitignore([], [ + "/wp-content/themes/my-theme\n", + "/wp-content/plugins/my-plugin\n", + ], true, false); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Gitignore', $dir, $gitignore); + + $whippetLock = $this->getWhippetLockWritable([ + ['themes', 'my-theme', 'git@git.dxw.net:wordpress-themes/my-theme', '27ba906'], + ['plugins', 'my-plugin', 'git@git.dxw.net:wordpress-plugins/my-plugin', 'd961c3d'], + ], sha1('foobar'), $dir.'/whippet.lock', []); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Files\\WhippetLock', [], $whippetLock); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-themes/my-theme', 'v1.4', \Result\Result::ok('27ba906')); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-plugins/my-plugin', 'v1.6', \Result\Result::ok('d961c3d')); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::err('file not found')); + + $dependencies = new \Dxw\Whippet\Dependencies\Updater( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->update(); + $output = ob_get_clean(); + + $this->assertFalse($result->isErr()); + $this->assertEquals("[Updating themes/my-theme]\n[Updating plugins/my-plugin]\n", $output); + } + + public function testUpdateWithBrokenJson() + { + $dir = $this->getDir(); + + $whippetJson = $this->getWhippetJson([ + 'src' => [ + 'plugins' => 'git@git.dxw.net:wordpress-plugins/', + ], + 'themes' => [ + [ + 'name' => 'my-theme', + 'ref' => 'v1.4', + ], + ], + 'plugins' => [ + [ + 'name' => 'my-plugin', + 'ref' => 'v1.6', + ], + ], + ]); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetJson', 'fromFile', $dir.'/whippet.json', \Result\Result::ok($whippetJson)); + + file_put_contents($dir.'/whippet.json', 'foobar'); + + $gitignore = $this->getGitignore([], [ + "/wp-content/themes/my-theme\n", + "/wp-content/plugins/my-plugin\n", + ], false, false); + $this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Gitignore', $dir, $gitignore); + + $whippetLock = $this->getWhippetLockWritable([], sha1('foobar'), null, []); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock)); + + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-themes/my-theme', 'v1.4', \Result\Result::ok('27ba906')); + $this->addFactoryCallStatic('\\Dxw\\Whippet\\Git\\Git', 'ls_remote', 'git@git.dxw.net:wordpress-plugins/my-plugin', 'v1.6', \Result\Result::ok('d961c3d')); + + $dependencies = new \Dxw\Whippet\Dependencies\Updater( + $this->getFactory(), + $this->getProjectDirectory($dir) + ); + + ob_start(); + $result = $dependencies->update(); + $output = ob_get_clean(); + + $this->assertTrue($result->isErr()); + $this->assertEquals('missing sources', $result->getErr()); + $this->assertEquals("[Updating themes/my-theme]\n", $output); + } +} diff --git a/tests/files/whippet_json_test.php b/tests/files/whippet_json_test.php new file mode 100644 index 0000000..715ba65 --- /dev/null +++ b/tests/files/whippet_json_test.php @@ -0,0 +1,39 @@ + [ + ['name' => 'advanced-custom-fields'], + ], + ]); + + $this->assertEquals([ + ['name' => 'advanced-custom-fields'], + ], $whippetJson->getDependencies('plugins')); + } + + public function testGetDependenciesBlank() + { + $whippetJson = new \Dxw\Whippet\Files\WhippetJson([]); + + $this->assertEquals([], $whippetJson->getDependencies('plugins')); + } + + public function testGetSources() + { + $whippetJson = new \Dxw\Whippet\Files\WhippetJson([ + 'src' => [ + 'plugins' => 'git@git.dxw.net:wordpress-plugins/', + ], + ]); + + $this->assertEquals([ + 'plugins' => 'git@git.dxw.net:wordpress-plugins/', + ], $whippetJson->getSources()); + } +} diff --git a/tests/files/whippet_lock_test.php b/tests/files/whippet_lock_test.php new file mode 100644 index 0000000..62180f9 --- /dev/null +++ b/tests/files/whippet_lock_test.php @@ -0,0 +1,171 @@ + [ + [ + 'name' => 'my-theme', + 'src' => 'git@git.dxw.net:wordpress-themes/my-theme', + 'revision' => '27ba906', + ], + ], + ]); + + $this->assertEquals([ + [ + 'name' => 'my-theme', + 'src' => 'git@git.dxw.net:wordpress-themes/my-theme', + 'revision' => '27ba906', + ], + ], $whippetLock->getDependencies('themes')); + } + + public function testFromStringGetDependencies() + { + $whippetLock = \Dxw\Whippet\Files\WhippetLock::fromString(json_encode([ + 'themes' => [ + [ + 'name' => 'my-theme', + 'src' => 'git@git.dxw.net:wordpress-themes/my-theme', + 'revision' => '27ba906', + ], + ], + ])); + + $this->assertFalse($whippetLock->isErr()); + $this->assertEquals([ + [ + 'name' => 'my-theme', + 'src' => 'git@git.dxw.net:wordpress-themes/my-theme', + 'revision' => '27ba906', + ], + ], $whippetLock->unwrap()->getDependencies('themes')); + } + + public function testFromFileGetDependencies() + { + $dir = $this->getDir(); + + file_put_contents($dir.'/whippet.lock', json_encode([ + 'themes' => [ + [ + 'name' => 'my-theme', + 'src' => 'git@git.dxw.net:wordpress-themes/my-theme', + 'revision' => '27ba906', + ], + ], + ])); + + $whippetLock = \Dxw\Whippet\Files\WhippetLock::fromFile($dir.'/whippet.lock'); + + $this->assertFalse($whippetLock->isErr()); + $this->assertEquals([ + [ + 'name' => 'my-theme', + 'src' => 'git@git.dxw.net:wordpress-themes/my-theme', + 'revision' => '27ba906', + ], + ], $whippetLock->unwrap()->getDependencies('themes')); + } + + public function testGetHash() + { + $whippetLock = new \Dxw\Whippet\Files\WhippetLock([ + 'hash' => '123', + ]); + + $this->assertEquals('123', $whippetLock->getHash()); + } + + public function testGetDependenciesNotSet() + { + $whippetLock = new \Dxw\Whippet\Files\WhippetLock([ + 'themes' => [], + ]); + + $this->assertEquals([], $whippetLock->getDependencies('plugins')); + } + + public function testSetHash() + { + $whippetLock = new \Dxw\Whippet\Files\WhippetLock([]); + + $whippetLock->setHash('123'); + + $this->assertEquals('123', $whippetLock->getHash()); + } + + public function testAddDependency() + { + $whippetLock = new \Dxw\Whippet\Files\WhippetLock([]); + + $whippetLock->addDependency('plugins', 'my-plugin', 'git@git.dxw.net:foobar/baz', '123abc'); + $this->assertEquals([ + [ + 'name' => 'my-plugin', + 'src' => 'git@git.dxw.net:foobar/baz', + 'revision' => '123abc', + ], + ], $whippetLock->getDependencies('plugins')); + } + + public function testSaveToPath() + { + $dir = $this->getDir(); + + $data = [ + 'foo' => 'bar', + ]; + + $whippetLock = new \Dxw\Whippet\Files\WhippetLock($data); + + $whippetLock->saveToPath($dir.'/my-whippet.lock'); + + $this->assertTrue(file_exists($dir.'/my-whippet.lock')); + $this->assertEquals($data, json_decode(file_get_contents($dir.'/my-whippet.lock'), true)); + } + + public function testSaveToPathPrettyPrinting() + { + $dir = $this->getDir(); + + $data = [ + 'foo' => '/', + ]; + + $whippetLock = new \Dxw\Whippet\Files\WhippetLock($data); + + $whippetLock->saveToPath($dir.'/my-whippet.lock'); + + $this->assertTrue(file_exists($dir.'/my-whippet.lock')); + $this->assertEquals(implode("\n", [ + '{', + ' "foo": "/"', + '}', + '', // Trailing newline + ]), file_get_contents($dir.'/my-whippet.lock'), true); + } + + public function testFromStringInvalid() + { + $output = \Dxw\Whippet\Files\WhippetLock::fromString('this is not json'); + + $this->assertTrue($output->isErr()); + $this->assertEquals('invalid JSON', $output->getErr()); + } + + public function testFromFileNotFound() + { + $dir = $this->getDir(); + + $output = \Dxw\Whippet\Files\WhippetLock::fromFile($dir.'/file-not-found.json'); + + $this->assertTrue($output->isErr()); + $this->assertEquals('file not found', $output->getErr()); + } +} diff --git a/tests/project_directory_test.php b/tests/project_directory_test.php new file mode 100644 index 0000000..6a52e14 --- /dev/null +++ b/tests/project_directory_test.php @@ -0,0 +1,110 @@ +getDir(); + + mkdir($dir.'/wp-content/themes/my-theme'); + touch($dir.'/whippet.json'); + + foreach ([ + $dir.'/wp-content/themes/my-theme', + $dir.'/wp-content/themes', + $dir.'/wp-content', + $dir, + ] as $path) { + $result = \Dxw\Whippet\ProjectDirectory::find($path); + $this->assertFalse($result->isErr()); + $this->assertInstanceOf('\\Dxw\\Whippet\\ProjectDirectory', $result->unwrap()); + $this->assertEquals($dir, $result->unwrap()->__toString()); + } + } + + public function testGetDirectorySuccess2() + { + $dir = $this->getDir(); + + mkdir($dir.'/projects'); + mkdir($dir.'/projects/project1'); + mkdir($dir.'/projects/project1/wp-content/themes/my-theme'); + mkdir($dir.'/projects/project1/wp-content'); + mkdir($dir.'/projects/project1/wp-content/themes'); + mkdir($dir.'/projects/project1/wp-content/themes/my-theme'); + touch($dir.'/projects/project1/whippet.json'); + + foreach ([ + $dir.'/projects/project1/wp-content/themes/my-theme', + $dir.'/projects/project1/wp-content/themes', + $dir.'/projects/project1/wp-content', + $dir.'/projects/project1', + ] as $path) { + $result = \Dxw\Whippet\ProjectDirectory::find($path); + $this->assertFalse($result->isErr()); + $this->assertInstanceOf('\\Dxw\\Whippet\\ProjectDirectory', $result->unwrap()); + $this->assertEquals($dir.'/projects/project1', $result->unwrap()->__toString()); + } + } + + public function testGetDirectoryFailure() + { + $dir = $this->getDir(); + + mkdir($dir.'/projects'); + mkdir($dir.'/projects/project1'); + mkdir($dir.'/projects/project1/wp-content/themes/my-theme'); + mkdir($dir.'/projects/project1/wp-content'); + mkdir($dir.'/projects/project1/wp-content/themes'); + mkdir($dir.'/projects/project1/wp-content/themes/my-theme'); + touch($dir.'/plugins'); + + foreach ([ + $dir.'/projects/project1/wp-content/themes/my-theme', + $dir.'/projects/project1/wp-content/themes', + $dir.'/projects/project1/wp-content', + $dir.'/projects/project1', + ] as $path) { + $result = \Dxw\Whippet\ProjectDirectory::find($path); + $this->assertTrue($result->isErr()); + $this->assertEquals('whippet.json not found', $result->getErr()); + } + } + + public function testGetDirectoryWhippetJson() + { + $dir = $this->getDir(); + + mkdir($dir.'/wp-content/themes/my-theme'); + touch($dir.'/whippet.json'); + + foreach ([ + $dir.'/wp-content/themes/my-theme', + $dir.'/wp-content/themes', + $dir.'/wp-content', + $dir, + ] as $path) { + $result = \Dxw\Whippet\ProjectDirectory::find($path); + $this->assertFalse($result->isErr()); + $this->assertInstanceOf('\\Dxw\\Whippet\\ProjectDirectory', $result->unwrap()); + $this->assertEquals($dir, $result->unwrap()->__toString()); + } + } + + public function testGetDirectoryAvoidPluginsDirectory() + { + $dir = $this->getDir(); + + mkdir($dir.'/wp-content'); + mkdir($dir.'/wp-content/plugins'); + mkdir($dir.'/wp-content/plugins/my-plugin'); + touch($dir.'/whippet.json'); + + $result = \Dxw\Whippet\ProjectDirectory::find($dir.'/wp-content/plugins/my-plugin'); + $this->assertFalse($result->isErr()); + $this->assertInstanceOf('\\Dxw\\Whippet\\ProjectDirectory', $result->unwrap()); + $this->assertEquals($dir, $result->unwrap()->__toString()); + } +} diff --git a/vendor.phar b/vendor.phar index e9bbd39..40c5deb 100644 Binary files a/vendor.phar and b/vendor.phar differ