diff --git a/.gitignore b/.gitignore
index 9bbe0e7..6a2abd2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,11 @@
# libraries lock file
/installed-libraries.json
+# ide folders
+/.idea
# composer artifacts
-/vendor/
+/.phpunit.result.cache
+/composer.lock
+/composer.phar
+/phpunit.xml
+/reports
+/vendor
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..4587401
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,19 @@
+1.2.0 / 2020-07-14
+========================
+
+* Quality of life improvements such as an alternative configuration
+definition for supporting:
+ * Different file extensions.
+ * SHA1 checksum verification.
+ * Removing example/demo/test files from the libraries (see [PSA-2011-002](https://www.drupal.org/node/1189632)).
+* Ability to pull in library dependencies declared in subpackages through the
+`drupal-libraries-dependencies` extra option.
+* Basic unit tests.
+
+1.0.1 / 2018-01-25
+========================
+* Relax composer-installers version requirement.
+
+1.0.0 / 2018-01-25
+========================
+* Initial MVP plugin.
diff --git a/README.md b/README.md
index 2c8ee61..ab88e13 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ external dependencies for a Drupal site in a single place: the `composer.json` f
1. Add _Drupal Libraries Installer_ to your Drupal site project:
```sh
- composer require balbuf/drupal-libraries-installer
+ composer require zodiacmedia/drupal-libraries-installer
```
1. Add libraries to your `composer.json` file via the `drupal-libraries` property
diff --git a/composer.json b/composer.json
index 9fe3cb9..5df181a 100644
--- a/composer.json
+++ b/composer.json
@@ -1,5 +1,5 @@
{
- "name": "balbuf/drupal-libraries-installer",
+ "name": "zodiacmedia/drupal-libraries-installer",
"description": "Install Drupal libraries via a simple listing in your composer.json file",
"type": "composer-plugin",
"license": "MIT",
@@ -7,19 +7,42 @@
{
"name": "Stephen Beemsterboer",
"homepage": "https://github.com/balbuf"
+ },
+ {
+ "name": "codebymikey",
+ "homepage": "https://www.drupal.org/u/codebymikey"
}
],
"support": {
- "issues": "https://github.com/balbuf/drupal-libraries-installer/issues"
+ "issues": "https://github.com/zodiacmedia/drupal-libraries-installer/issues"
},
"autoload": {
- "psr-4": {"BalBuf\\DrupalLibrariesInstaller\\": "src/"}
+ "psr-4": {
+ "Zodiacmedia\\DrupalLibrariesInstaller\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Zodiacmedia\\DrupalLibrariesInstaller\\": "tests"
+ }
},
"extra": {
- "class": "BalBuf\\DrupalLibrariesInstaller\\Plugin"
+ "class": "Zodiacmedia\\DrupalLibrariesInstaller\\Plugin"
+ },
+ "scripts": {
+ "phpunit": "phpunit",
+ "coverage": "phpunit --coverage-text",
+ "coverage:report": "phpunit --log-junit=reports/unitreport.xml --coverage-text --coverage-html=reports/coverage --coverage-clover=reports/coverage.xml"
},
"require": {
"composer-plugin-api": "^1.0",
- "composer/installers": "^1.1"
+ "composer/installers": "^1.1",
+ "php": ">=7.2.0"
+ },
+ "require-dev": {
+ "composer/composer": "^1.10",
+ "mikey179/vfsstream": "^1.6",
+ "phpunit/phpunit": "^8.5",
+ "ext-json": "*"
}
}
diff --git a/composer.lock b/composer.lock
deleted file mode 100644
index 50cf0c1..0000000
--- a/composer.lock
+++ /dev/null
@@ -1,138 +0,0 @@
-{
- "_readme": [
- "This file locks the dependencies of your project to a known state",
- "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
- "This file is @generated automatically"
- ],
- "content-hash": "0d5407f7f7233a1bd639fd22d5542230",
- "packages": [
- {
- "name": "composer/installers",
- "version": "v1.5.0",
- "source": {
- "type": "git",
- "url": "https://github.com/composer/installers.git",
- "reference": "049797d727261bf27f2690430d935067710049c2"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/composer/installers/zipball/049797d727261bf27f2690430d935067710049c2",
- "reference": "049797d727261bf27f2690430d935067710049c2",
- "shasum": ""
- },
- "require": {
- "composer-plugin-api": "^1.0"
- },
- "replace": {
- "roundcube/plugin-installer": "*",
- "shama/baton": "*"
- },
- "require-dev": {
- "composer/composer": "1.0.*@dev",
- "phpunit/phpunit": "^4.8.36"
- },
- "type": "composer-plugin",
- "extra": {
- "class": "Composer\\Installers\\Plugin",
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Composer\\Installers\\": "src/Composer/Installers"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Kyle Robinson Young",
- "email": "kyle@dontkry.com",
- "homepage": "https://github.com/shama"
- }
- ],
- "description": "A multi-framework Composer library installer",
- "homepage": "https://composer.github.io/installers/",
- "keywords": [
- "Craft",
- "Dolibarr",
- "Eliasis",
- "Hurad",
- "ImageCMS",
- "Kanboard",
- "Lan Management System",
- "MODX Evo",
- "Mautic",
- "Maya",
- "OXID",
- "Plentymarkets",
- "Porto",
- "RadPHP",
- "SMF",
- "Thelia",
- "WolfCMS",
- "agl",
- "aimeos",
- "annotatecms",
- "attogram",
- "bitrix",
- "cakephp",
- "chef",
- "cockpit",
- "codeigniter",
- "concrete5",
- "croogo",
- "dokuwiki",
- "drupal",
- "eZ Platform",
- "elgg",
- "expressionengine",
- "fuelphp",
- "grav",
- "installer",
- "itop",
- "joomla",
- "kohana",
- "laravel",
- "lavalite",
- "lithium",
- "magento",
- "majima",
- "mako",
- "mediawiki",
- "modulework",
- "modx",
- "moodle",
- "osclass",
- "phpbb",
- "piwik",
- "ppi",
- "puppet",
- "pxcms",
- "reindex",
- "roundcube",
- "shopware",
- "silverstripe",
- "sydes",
- "symfony",
- "typo3",
- "wordpress",
- "yawik",
- "zend",
- "zikula"
- ],
- "time": "2017-12-29T09:13:20+00:00"
- }
- ],
- "packages-dev": [],
- "aliases": [],
- "minimum-stability": "stable",
- "stability-flags": [],
- "prefer-stable": false,
- "prefer-lowest": false,
- "platform": [],
- "platform-dev": []
-}
diff --git a/example/dependency/README.md b/example/dependency/README.md
new file mode 100644
index 0000000..2928ac0
--- /dev/null
+++ b/example/dependency/README.md
@@ -0,0 +1,5 @@
+# Demo dependency package.
+
+Demo dependency package includes a `moment` library version 2.0.0 which will
+not be installed because it was already declared in the root project's
+composer.json.
diff --git a/example/dependency/composer.json b/example/dependency/composer.json
new file mode 100644
index 0000000..3c41957
--- /dev/null
+++ b/example/dependency/composer.json
@@ -0,0 +1,17 @@
+{
+ "name": "zodiacmedia/drupal-libraries-installer-demo-dependency",
+ "type": "project",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "codebymikey",
+ "homepage": "https://www.drupal.org/u/codebymikey"
+ }
+ ],
+ "extra": {
+ "drupal-libraries": {
+ "dependency@flexslider": "https://github.com/woocommerce/FlexSlider/archive/2.6.4.zip",
+ "moment": "https://registry.npmjs.org/moment/-/moment-2.0.0.tgz"
+ }
+ }
+}
diff --git a/example/project/.gitignore b/example/project/.gitignore
new file mode 100644
index 0000000..25f3ea5
--- /dev/null
+++ b/example/project/.gitignore
@@ -0,0 +1,3 @@
+/composer.lock
+/vendor
+/web
diff --git a/example/project/README.md b/example/project/README.md
new file mode 100644
index 0000000..df92d3c
--- /dev/null
+++ b/example/project/README.md
@@ -0,0 +1,3 @@
+# Demo root project
+
+Demo root project integrating with Drupal libraries installer.
diff --git a/example/project/composer.json b/example/project/composer.json
new file mode 100644
index 0000000..66a9831
--- /dev/null
+++ b/example/project/composer.json
@@ -0,0 +1,75 @@
+{
+ "name": "zodiacmedia/drupal-libraries-installer-demo-project",
+ "description": "Demo project using the installer.",
+ "type": "project",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "codebymikey",
+ "homepage": "https://www.drupal.org/u/codebymikey"
+ }
+ ],
+ "repositories": {
+ "drupal-libraries-installer": {
+ "type": "vcs",
+ "url": "../.."
+ },
+ "drupal-libraries-installer-demo-dependency": {
+ "type": "path",
+ "url": "../dependency/",
+ "options": {
+ "symlink": true
+ }
+ }
+ },
+ "require": {
+ "zodiacmedia/drupal-libraries-installer": "*@dev",
+ "zodiacmedia/drupal-libraries-installer-demo-dependency": "*@dev"
+ },
+ "config": {
+ "preferred-install": "source",
+ "classmap-authoritative": true,
+ "prepend-autoloader": false,
+ "optimize-autoloader": true
+ },
+ "extra": {
+ "installer-paths": {
+ "web/libraries/{$name}/": [
+ "type:drupal-library"
+ ]
+ },
+ "drupal-libraries-dependencies": [
+ "zodiacmedia/drupal-libraries-installer-demo-dependency"
+ ],
+ "drupal-libraries": {
+ "simple-color": "https://github.com/recurser/jquery-simple-color/archive/v1.2.2.zip",
+ "chosen": {
+ "url": "https://github.com/harvesthq/chosen/releases/download/v1.8.2/chosen_v1.8.2.zip",
+ "version": "1.8.2"
+ },
+ "flexslider": {
+ "url": "https://github.com/woocommerce/FlexSlider/archive/2.6.4.zip",
+ "version": "2.6.4",
+ "type": "zip",
+ "ignore": [
+ "bower_components",
+ "demo",
+ "node_modules"
+ ]
+ },
+ "moment": {
+ "url": "https://registry.npmjs.org/moment/-/moment-2.25.0.tgz",
+ "shasum": "e961ab9a5848a1cf2c52b1af4e6c82a8401e7fe9"
+ },
+ "select2": {
+ "url": "https://github.com/select2/select2/archive/4.0.13.zip",
+ "ignore": [
+ ".*",
+ "*.{md}",
+ "Gruntfile.js",
+ "{docs,src,tests}"
+ ]
+ }
+ }
+ }
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..74cbacf
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,15 @@
+
+
+
+ ./tests
+
+
+
+
+ ./src
+
+
+
diff --git a/src/Plugin.php b/src/Plugin.php
index bf827e3..110f009 100644
--- a/src/Plugin.php
+++ b/src/Plugin.php
@@ -1,20 +1,24 @@
composer = $composer;
$this->io = $io;
- $this->fileSystem = new Filesystem();
+ $this->fileSystem = $filesystem ?? new Filesystem();
$this->downloadManager = $composer->getDownloadManager();
$this->installationManager = $composer->getInstallationManager();
}
@@ -79,7 +100,8 @@ public static function getSubscribedEvents() {
/**
* Upon running composer install or update, install the drupal libraries.
*
- * @param Event $event install/update event
+ * @param \Composer\Script\Event $event
+ * The composer install/update event.
*
* @throws \Exception
*/
@@ -128,8 +150,8 @@ public function install(Event $event) {
}
// Remove unused libraries from disk before attempting to download new ones.
- // Avoids the edge-case where the removed folder happens to be the same as the one where the new one is being
- // installed to.
+ // Avoids the edge-case where the removed folder happens to be the same as
+ // the one where a new package dependency is being installed to.
$removed_libraries = array_diff_key($applied_drupal_libraries, $processed_drupal_libraries);
if ($removed_libraries) {
$this->removeUnusedLibraries($removed_libraries);
@@ -153,38 +175,40 @@ public function install(Event $event) {
/**
* Drupal library processor.
*
- * Inspired by https://github.com/civicrm/composer-downloads-plugin
+ * Inspired by https://github.com/civicrm/composer-downloads-plugin.
*
* @param array $processed_drupal_libraries
+ * The currently processed drupal libraries.
* @param array $drupal_libraries
- * Applied drupal libraries.
+ * The currently installed drupal libraries.
* @param \Composer\Package\PackageInterface $package
+ * The package instance.
*
* @return array
* The processed packages.
*/
- protected function processPackage($processed_drupal_libraries, $drupal_libraries, $package) {
+ protected function processPackage(array $processed_drupal_libraries, array $drupal_libraries, PackageInterface $package) {
$extra = $package->getExtra();
if (empty($extra['drupal-libraries']) || !is_array($extra['drupal-libraries'])) {
return $processed_drupal_libraries;
}
- // Install each library
+ // Install each library.
foreach ($extra['drupal-libraries'] as $library => $library_definition) {
$ignore_patterns = [];
$sha1checksum = NULL;
if (is_string($library_definition)) {
// Simple format.
$url = $library_definition;
- list($version, $distribution_type) = $this->guessDefaultsFromUrl($url);
+ [$version, $distribution_type] = $this->guessDefaultsFromUrl($url);
}
else {
if (empty($library_definition['url'])) {
throw new \LogicException("The drupal-library '$library' does not contain a valid URL.");
}
$url = $library_definition['url'];
- list($version, $distribution_type) = $this->guessDefaultsFromUrl($url);
+ [$version, $distribution_type] = $this->guessDefaultsFromUrl($url);
$version = $library_definition['version'] ?? $version;
$distribution_type = $library_definition['type'] ?? $distribution_type;
$ignore_patterns = $library_definition['ignore'] ?? $ignore_patterns;
@@ -192,8 +216,9 @@ protected function processPackage($processed_drupal_libraries, $drupal_libraries
}
if (isset($processed_drupal_libraries[$library])) {
- // Only the first declaration of the library is ever used. This ensures that the root package always
- // acts as the source of truth over what version of a library is installed.
+ // Only the first declaration of the library is ever used. This ensures
+ // that the root package always acts as the source of truth over what
+ // version of a library is installed.
$old_definition = $processed_drupal_libraries[$library];
if ($this->io->isDebug()) {
$this->io->write(
@@ -207,7 +232,8 @@ protected function processPackage($processed_drupal_libraries, $drupal_libraries
}
}
else {
- // Track installed libraries in the package info in installed-libraries.json
+ // Track installed libraries in the package info in
+ // installed-libraries.json.
$applied_library = [
'version' => $version,
'url' => $url,
@@ -230,8 +256,9 @@ protected function processPackage($processed_drupal_libraries, $drupal_libraries
* Remove old unused libraries from disk.
*
* @param array $old_libraries
+ * The old libraries to remove from disk.
*/
- protected function removeUnusedLibraries($old_libraries) {
+ protected function removeUnusedLibraries(array $old_libraries) {
foreach ($old_libraries as $library_name => $library_definition) {
$library_package = $this->getLibraryPackage($library_name, $library_definition);
@@ -247,8 +274,10 @@ protected function removeUnusedLibraries($old_libraries) {
*
* @param array $processed_libraries
* The processed libraries.
+ * @param array $applied_drupal_libraries
+ * The currently installed libraries.
*/
- protected function downloadLibraries($processed_libraries, $applied_drupal_libraries) {
+ protected function downloadLibraries(array $processed_libraries, array $applied_drupal_libraries) {
foreach ($processed_libraries as $library_name => $processed_library) {
$library_package = $this->getLibraryPackage($library_name, $processed_library);
@@ -280,7 +309,7 @@ protected function downloadLibraries($processed_libraries, $applied_drupal_libra
* @param array $ignore_patterns
* File patterns to ignore.
*/
- protected function downloadPackage(Package $library_package, $install_path, $ignore_patterns) {
+ protected function downloadPackage(Package $library_package, $install_path, array $ignore_patterns) {
// Let composer download and unpack the library for us!
$this->downloadManager->download($library_package, $install_path);
@@ -328,12 +357,15 @@ function ($file) use ($patterns) {
/**
* Get a drupal-library package object from its definition.
*
- * @param $library_name
- * @param $library_definition
+ * @param string $library_name
+ * The library name.
+ * @param array $library_definition
+ * The library definition.
*
* @return \Composer\Package\Package
+ * The pseudo-package for the library.
*/
- protected function getLibraryPackage($library_name, $library_definition) {
+ protected function getLibraryPackage($library_name, array $library_definition) {
$library_package_name = 'drupal-library/' . $library_name;
$library_package = new Package(
$library_package_name, $library_definition['version'], $library_definition['version']
@@ -380,10 +412,11 @@ protected function guessDefaultsFromUrl($url) {
* Get the current installed-libraries json file path.
*
* @return string
+ * The installed libraries json lock file path.
*/
- protected function getInstalledJsonPath() {
+ public function getInstalledJsonPath() {
// Alternative approach.
- // $installed_json_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'installed-libraries.json';
+ /*$installed_json_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'installed-libraries.json';*/
/** @var \Composer\Package\CompletePackage $installer_library_package */
$installer_library_package = $this->composer->getRepositoryManager()->getLocalRepository()->findPackage(
diff --git a/tests/PluginTest.php b/tests/PluginTest.php
new file mode 100644
index 0000000..a85c224
--- /dev/null
+++ b/tests/PluginTest.php
@@ -0,0 +1,600 @@
+downloadedFiles = [];
+ $this->removedFiles = [];
+
+ $this->setupVirtualFilesystem();
+
+ $this->installationManager = $this->createMock(InstallationManager::class);
+ $this->installationManager
+ ->method('getInstallPath')
+ ->willReturnCallback([$this, 'getInstallPathCallback']);
+
+ $this->downloadManager = $this->createMock(DownloadManager::class);
+ $this->downloadManager->method('download')->willReturnCallback(
+ [$this, 'downloadManagerDownloadCallback']
+ );
+
+ // Create a partial mock, keeping the normalize functionality.
+ $this->fileSystem = $this->createPartialMock(Filesystem::class, ['remove']);
+ $this->fileSystem->method('remove')->willReturnCallback(
+ [$this, 'fileSystemRemoveCallback']
+ );
+
+ $this->composer = $this
+ ->createPartialMock(
+ Composer::class,
+ [
+ 'getPackage',
+ 'getInstallationManager',
+ 'getDownloadManager',
+ 'getRepositoryManager',
+ ]
+ );
+ $this->composer
+ ->method('getInstallationManager')
+ ->willReturn($this->installationManager);
+
+ $this->composer
+ ->method('getDownloadManager')
+ ->willReturn($this->downloadManager);
+
+ $repository_manager = $this->createMock(RepositoryManager::class);
+ $local_repository = $this->createMock(WritableRepositoryInterface::class);
+ $local_repository
+ ->method('getCanonicalPackages')
+ ->willReturnCallback([$this, 'getCanonicalPackagesCallback']);
+ $repository_manager
+ ->method('getLocalRepository')
+ ->willReturn($local_repository);
+
+ $this->composer
+ ->method('getRepositoryManager')
+ ->willReturn($repository_manager);
+
+ $this->io = $this->createMock(IOInterface::class);
+
+ $this->fixture = $this->getPluginMockInstance();
+ }
+
+ /**
+ * Callback for the FileSystem's remove method.
+ *
+ * @param string $file
+ * The file/folder path.
+ */
+ public function fileSystemRemoveCallback($file) {
+ $this->removedFiles[] = $file;
+ }
+
+ /**
+ * Callback for the DownloadManager's download function.
+ */
+ public function downloadManagerDownloadCallback(Package $package, $file) {
+ $this->downloadedFiles[] = $file;
+ }
+
+ /**
+ * Normalize a libraries directory vfs file path for comparison.
+ *
+ * @param string $file
+ * The file path.
+ *
+ * @return string
+ * The normalized path.
+ */
+ protected function normalizeVfsFilePath(string $file) {
+ $path_prefix = $this->rootDirectory->getChild(static::LIBRARIES_DIRECTORY)->url() . '/';
+ $path_prefix_length = strlen($path_prefix);
+ if (substr($file, 0, $path_prefix_length) === $path_prefix) {
+ $file = substr($file, $path_prefix_length);
+ }
+
+ return $file;
+ }
+
+ /**
+ * Prepares the virtual file system.
+ */
+ protected function setupVirtualFilesystem() {
+ // Not all tests require this, but we want to ensure we never accidentally
+ // touch our real composer.json.
+ $project_dependencies = [
+ 'vendor' => [
+ 'zodiacmedia' => [
+ 'drupal-libraries-installer' => [
+ // 'installed-libraries.json' => '{}',
+ ],
+ 'drupal-libraries-test-dependency' => [
+ 'composer.json' => /* @lang JSON */
+ <<rootDirectory = vfsStream::setup('root', NULL, $project_dependencies);
+
+ $this->installedLibrariesJsonFile = vfsStream::url(
+ $this->rootDirectory->path() . '/vendor/zodiacmedia/drupal-libraries-installer/installed-libraries.json'
+ );
+ }
+
+ /**
+ * Test to ensure the correct events are acted upon.
+ *
+ * @covers \Zodiacmedia\DrupalLibrariesInstaller\Plugin::getSubscribedEvents()
+ */
+ public function testGetSubscribedEvents() {
+ $this->assertEquals(
+ [
+ ScriptEvents::POST_INSTALL_CMD => 'install',
+ ScriptEvents::POST_UPDATE_CMD => 'install',
+ ],
+ Plugin::getSubscribedEvents()
+ );
+ }
+
+ /**
+ * Test various drupal libraries scenarios.
+ *
+ * @param string $fixture_name
+ * The fixture directory name.
+ * @param array $expected_removed_files
+ * The list of removed files.
+ * @param array $expected_downloaded_dirs
+ * The list of downloaded package directories.
+ * @param bool $installed_libraries
+ * Whether to read an initial installed libraries.
+ *
+ * @dataProvider drupalLibrariesDownloadProvider
+ *
+ * @throws \Exception
+ */
+ public function testDrupalLibrariesDownload(
+ $fixture_name,
+ array $expected_removed_files,
+ array $expected_downloaded_dirs,
+ $installed_libraries = FALSE
+ ) {
+ $this->fixtureDirectory = $this->fixtureDirectory($fixture_name);
+ $root_project = $this->rootFromJson("{$this->fixtureDirectory}/composer.json");
+
+ if ($installed_libraries) {
+ // Set the initial installed libraries content if any.
+ copy("{$this->fixtureDirectory}/installed-libraries.initial.json", $this->installedLibrariesJsonFile);
+ }
+
+ // Create the libraries structure.
+ $this->createLibrariesPackageStructures("{$this->fixtureDirectory}/libraries-structure.json");
+
+ $this->triggerPlugin($root_project, $this->fixtureDirectory);
+
+ $removed_files = array_map([$this, 'normalizeVfsFilePath'], $this->removedFiles);
+ $downloaded_files = array_map([$this, 'normalizeVfsFilePath'], $this->downloadedFiles);
+ $this->assertSame($expected_removed_files, $removed_files, 'Removed files');
+ $this->assertSame($expected_downloaded_dirs, $downloaded_files, 'Downloaded files');
+
+ // Compare the output JSON files.
+ $expected_installed_libraries_file = "{$this->fixtureDirectory}/installed-libraries.expected.json";
+ $this->assertFileExists($expected_installed_libraries_file);
+ $this->assertFileExists($this->installedLibrariesJsonFile);
+ $this->assertSame(
+ json_decode(file_get_contents($expected_installed_libraries_file), TRUE),
+ json_decode(file_get_contents($this->installedLibrariesJsonFile), TRUE),
+ 'installed-libraries.json output'
+ );
+ }
+
+ /**
+ * Fixtures data provider.
+ */
+ public function drupalLibrariesDownloadProvider() {
+ return [
+ 'download-and-ignore-library-files' => [
+ /*
+ * Given a root package with
+ * a set of different drupal library definitions.
+ * When the plugin is run
+ * Then the libraries are installed and certain files matching specific
+ * ignore patterns are removed.
+ */
+ 'download-and-ignore-library-files',
+ [
+ 'flexslider/bower_components',
+ 'flexslider/demo',
+ 'flexslider/node_modules',
+ 'select2/.git',
+ 'select2/.hidden_directory',
+ 'select2/docs',
+ 'select2/src',
+ 'select2/tests',
+ 'select2/.hidden.txt',
+ 'select2/Gruntfile.js',
+ 'select2/README.md',
+ ],
+ [
+ 'flexslider',
+ 'moment',
+ 'select2',
+ 'simple-color',
+ ],
+ ],
+ 'specific-dependency-libraries-with-override' => [
+ /*
+ * Given a root package with
+ * a drupal library override and a package dependency with a library
+ * definition.
+ * When the plugin is run
+ * Then the libraries are installed and the override kept.
+ */
+ 'specific-dependency-libraries-with-override',
+ [],
+ [
+ 'test-moment',
+ 'test-library-dependency',
+ ],
+ ],
+ 'specific-dependency-libraries' => [
+ /*
+ * Given a root package with
+ * a package dependency with a library definition.
+ * When the plugin is run
+ * Then the libraries are installed and ignore patterns applied.
+ */
+ 'specific-dependency-libraries',
+ [
+ 'test-moment/README.md',
+ ],
+ [
+ 'test-library-dependency',
+ 'test-moment',
+ ],
+ ],
+ 'all-dependency-libraries' => [
+ /*
+ * Given a root package with
+ * a wildcard package dependency library definition.
+ * When the plugin is run
+ * Then the dependency libraries are installed and their ignore
+ * patterns applied.
+ */
+ 'all-dependency-libraries',
+ [
+ 'test-moment/README.md',
+ ],
+ [
+ 'test-library-dependency',
+ 'test-moment',
+ ],
+ ],
+ 'existing-lock-file' => [
+ /*
+ * Given a project with an existing lock file with the library.
+ * and the project already exists on disk.
+ * When the plugin is run
+ * Then nothing is installed/removed.
+ */
+ 'existing-lock-file',
+ [],
+ [],
+ TRUE,
+ ],
+ ];
+ }
+
+ /**
+ * Returns the mock plugin fixture instance.
+ */
+ protected function getPluginMockInstance() {
+ /** @var \PHPUnit\Framework\MockObject\MockObject|\Zodiacmedia\DrupalLibrariesInstaller\Plugin $plugin */
+ $plugin = $this->createPartialMock(Plugin::class, ['getInstalledJsonPath']);
+ $plugin->method('getInstalledJsonPath')->willReturn(
+ $this->installedLibrariesJsonFile
+ );
+
+ return $plugin;
+ }
+
+ /**
+ * Trigger an installation of the specified plugin.
+ *
+ * @param \Composer\Package\RootPackage $package
+ * The package instance.
+ * @param string $directory
+ * Working directory for composer run.
+ *
+ * @throws \Exception
+ */
+ protected function triggerPlugin(RootPackage $package, $directory) {
+ chdir($directory);
+ // Return the current package.
+ $this->composer->method('getPackage')->willReturn($package);
+
+ // Activate the plugin.
+ $this->fixture->activate(
+ $this->composer,
+ $this->io,
+ $this->fileSystem
+ );
+
+ $event = new Event(
+ ScriptEvents::POST_INSTALL_CMD,
+ $this->composer,
+ $this->io,
+ // Dev mode.
+ FALSE,
+ [],
+ []
+ );
+ $this->fixture->install($event);
+ }
+
+ /**
+ * Returns a fixture directory.
+ *
+ * @param string $sub_directory
+ * The subdirectory.
+ *
+ * @return string
+ * The fixture directory.
+ */
+ protected function fixtureDirectory($sub_directory) {
+ return __DIR__ . "/fixtures/{$sub_directory}";
+ }
+
+ /**
+ * Returns the root package definition.
+ *
+ * @param string $file
+ * The fixture composer.json.
+ *
+ * @return \PHPUnit\Framework\MockObject\MockObject|\Composer\Package\RootPackage
+ * The root package prophecy instance.
+ */
+ protected function rootFromJson($file) {
+ $json = json_decode(file_get_contents($file), TRUE);
+ $data = array_merge(
+ [
+ 'name' => 'drupal-libraries-installer-test/root-package',
+ 'repositories' => [],
+ 'require' => [],
+ 'require-dev' => [],
+ 'suggest' => [],
+ 'extra' => [],
+ ],
+ $json
+ );
+
+ $root_name = $data['name'];
+
+ foreach (['require', 'require-dev'] as $config) {
+ foreach ($data[$config] as $dependency => $dependency_version) {
+ $link = $this->createMock(Link::class);
+ $link->method('getSource')->willReturn($root_name);
+ $link->method('getTarget')->willReturn($dependency);
+ $link->method('getPrettyConstraint')->willReturn($dependency_version);
+ $data[$config][$dependency] = $link;
+ }
+ }
+
+ $root_package = $this->createMock(RootPackage::class);
+ $root_package->method('getRequires')->willReturn($data['require']);
+ $root_package->method('getDevRequires')->willReturn($data['require-dev']);
+ $root_package->method('getRepositories')->willReturn($data['repositories']);
+ $root_package->method('getSuggests')->willReturn($data['suggest']);
+ $root_package->method('getName')->willReturn($root_name);
+ $root_package->expects($this->atLeastOnce())->method('getExtra')->willReturn($data['extra']);
+
+ return $root_package;
+ }
+
+ /**
+ * Returns the composer package directory path.
+ *
+ * It also declares a bunch of files for the globs to match.
+ *
+ * @param \Composer\Package\Package $package
+ * The composer package.
+ *
+ * @return string
+ * The install path.
+ */
+ public function getInstallPathCallback(Package $package): string {
+ $name = explode('/', $package->getName())[1];
+
+ return vfsStream::url(
+ $this->rootDirectory->path() . '/' . static::LIBRARIES_DIRECTORY . '/' . $name
+ );
+ }
+
+ /**
+ * Callback for the LocalRepository getCanonicalPackages function.
+ */
+ public function getCanonicalPackagesCallback() {
+ $root_package = $this->composer->getPackage();
+ /**
+ * @var $dependencies Link[]
+ */
+ $dependencies = array_merge($root_package->getRequires(), $root_package->getDevRequires());
+ $return = [];
+
+ foreach ($dependencies as $dependency) {
+ /* @see setupVirtualFilesystem
+ * Create pseudo dependency packages, reading from the virtual filesystem.
+ */
+ $composer_path = 'vendor/' . $dependency->getTarget() . '/composer.json';
+ $this->assertTrue($this->rootDirectory->hasChild($composer_path));
+ $json = json_decode(
+ file_get_contents($this->rootDirectory->getChild($composer_path)->url()),
+ TRUE
+ );
+ $data = array_merge(
+ [
+ 'name' => 'undefined',
+ 'extra' => [],
+ ],
+ $json
+ );
+
+ $package = $this->createMock(Package::class);
+ $package->expects($this->atLeastOnce())->method('getExtra')->willReturn($data['extra']);
+ $package->method('getName')->willReturn($data['name']);
+ $return[] = $package;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Create the libraries package structure.
+ *
+ * @param string $json_file
+ * The library structure JSON file.
+ */
+ protected function createLibrariesPackageStructures(string $json_file) {
+ // Create the directory structure for the libraries so that they exist on
+ // disk for Finder to work with.
+ vfsStream::create(
+ [
+ static::LIBRARIES_DIRECTORY => json_decode(
+ file_get_contents($json_file),
+ TRUE
+ ),
+ ]
+ );
+ }
+
+}
diff --git a/tests/fixtures/all-dependency-libraries/composer.json b/tests/fixtures/all-dependency-libraries/composer.json
new file mode 100644
index 0000000..c7341bb
--- /dev/null
+++ b/tests/fixtures/all-dependency-libraries/composer.json
@@ -0,0 +1,8 @@
+{
+ "require": {
+ "zodiacmedia/drupal-libraries-test-dependency": "*@dev"
+ },
+ "extra": {
+ "drupal-libraries-dependencies": true
+ }
+}
diff --git a/tests/fixtures/all-dependency-libraries/installed-libraries.expected.json b/tests/fixtures/all-dependency-libraries/installed-libraries.expected.json
new file mode 100644
index 0000000..3191e2e
--- /dev/null
+++ b/tests/fixtures/all-dependency-libraries/installed-libraries.expected.json
@@ -0,0 +1,22 @@
+{
+ "schema-version": "1.0",
+ "installed": {
+ "test-library-dependency": {
+ "version": "1.0.0",
+ "url": "https://example.com/test-library-dependency.zip",
+ "type": "zip",
+ "ignore": [],
+ "package": "zodiacmedia/drupal-libraries-test-dependency"
+ },
+ "test-moment": {
+ "version": "2.25.0",
+ "url": "https://registry.npmjs.org/moment/-/moment-2.25.0.tgz",
+ "type": "tar",
+ "ignore": [
+ "*.md"
+ ],
+ "package": "zodiacmedia/drupal-libraries-test-dependency",
+ "shasum": "e961ab9a5848a1cf2c52b1af4e6c82a8401e7fe9"
+ }
+ }
+}
diff --git a/tests/fixtures/all-dependency-libraries/libraries-structure.json b/tests/fixtures/all-dependency-libraries/libraries-structure.json
new file mode 100644
index 0000000..4e9c0dd
--- /dev/null
+++ b/tests/fixtures/all-dependency-libraries/libraries-structure.json
@@ -0,0 +1,10 @@
+{
+ "test-library-dependency": {
+ "index.js": "",
+ "README.md": ""
+ },
+ "test-moment": {
+ "moment.js": "",
+ "README.md": ""
+ }
+}
diff --git a/tests/fixtures/download-and-ignore-library-files/composer.json b/tests/fixtures/download-and-ignore-library-files/composer.json
new file mode 100644
index 0000000..a97a421
--- /dev/null
+++ b/tests/fixtures/download-and-ignore-library-files/composer.json
@@ -0,0 +1,30 @@
+{
+ "extra": {
+ "drupal-libraries": {
+ "flexslider": {
+ "url": "https://github.com/woocommerce/FlexSlider/archive/2.6.4.zip",
+ "version": "2.6.4",
+ "type": "zip",
+ "ignore": [
+ "bower_components",
+ "demo",
+ "node_modules"
+ ]
+ },
+ "moment": {
+ "url": "https://registry.npmjs.org/moment/-/moment-2.25.0.tgz",
+ "shasum": "e961ab9a5848a1cf2c52b1af4e6c82a8401e7fe9"
+ },
+ "select2": {
+ "url": "https://github.com/select2/select2/archive/4.0.13.zip",
+ "ignore": [
+ ".*",
+ "*.{md}",
+ "Gruntfile.js",
+ "{docs,src,tests}"
+ ]
+ },
+ "simple-color": "https://github.com/recurser/jquery-simple-color/archive/v1.2.2.zip"
+ }
+ }
+}
diff --git a/tests/fixtures/download-and-ignore-library-files/installed-libraries.expected.json b/tests/fixtures/download-and-ignore-library-files/installed-libraries.expected.json
new file mode 100644
index 0000000..582531d
--- /dev/null
+++ b/tests/fixtures/download-and-ignore-library-files/installed-libraries.expected.json
@@ -0,0 +1,43 @@
+{
+ "schema-version": "1.0",
+ "installed": {
+ "flexslider": {
+ "version": "2.6.4",
+ "url": "https://github.com/woocommerce/FlexSlider/archive/2.6.4.zip",
+ "type": "zip",
+ "ignore": [
+ "bower_components",
+ "demo",
+ "node_modules"
+ ],
+ "package": "drupal-libraries-installer-test/root-package"
+ },
+ "moment": {
+ "version": "2.25.0",
+ "url": "https://registry.npmjs.org/moment/-/moment-2.25.0.tgz",
+ "type": "tar",
+ "ignore": [],
+ "package": "drupal-libraries-installer-test/root-package",
+ "shasum": "e961ab9a5848a1cf2c52b1af4e6c82a8401e7fe9"
+ },
+ "select2": {
+ "version": "4.0.13",
+ "url": "https://github.com/select2/select2/archive/4.0.13.zip",
+ "type": "zip",
+ "ignore": [
+ ".*",
+ "*.{md}",
+ "Gruntfile.js",
+ "{docs,src,tests}"
+ ],
+ "package": "drupal-libraries-installer-test/root-package"
+ },
+ "simple-color": {
+ "version": "v1.2.2",
+ "url": "https://github.com/recurser/jquery-simple-color/archive/v1.2.2.zip",
+ "type": "zip",
+ "ignore": [],
+ "package": "drupal-libraries-installer-test/root-package"
+ }
+ }
+}
diff --git a/tests/fixtures/download-and-ignore-library-files/libraries-structure.json b/tests/fixtures/download-and-ignore-library-files/libraries-structure.json
new file mode 100644
index 0000000..54b058e
--- /dev/null
+++ b/tests/fixtures/download-and-ignore-library-files/libraries-structure.json
@@ -0,0 +1,55 @@
+{
+ "flexslider": {
+ "bower_components": {
+ ".text.txt": "",
+ "test.php": "",
+ "text.txt": ""
+ },
+ "demo": {
+ "index.html": "",
+ "demo.js": "",
+ "demo.css": "",
+ "Gruntfile.js": ""
+ },
+ "node_modules": {
+ "sub_module1": [],
+ "sub_module2": [],
+ "sub_module3": []
+ },
+ ".hidden.txt": "",
+ "Gruntfile.js": "",
+ "README.md": ""
+ },
+ "moment": {
+ "moment.js": ""
+ },
+ "select2": {
+ ".git": [],
+ ".hidden_directory": [],
+ "directory-with-hidden-file": {
+ ".should-not-be-removed.txt": ""
+ },
+ "docs": {
+ "index.html": "",
+ "readme.md": "",
+ "changes.rst": ""
+ },
+ "empty_directory": [],
+ "src": {
+ "index.js": "",
+ "style.css": "",
+ "index.html": ""
+ },
+ "tests": {
+ "Test.php": "",
+ "test.js": "",
+ "Gruntfile.js": ""
+ },
+ ".hidden.txt": "",
+ "Gruntfile.js": "",
+ "README.md": ""
+ },
+ "simple-color": {
+ "simple-color.js": ""
+ }
+}
diff --git a/tests/fixtures/existing-lock-file/composer.json b/tests/fixtures/existing-lock-file/composer.json
new file mode 100644
index 0000000..3d9cd46
--- /dev/null
+++ b/tests/fixtures/existing-lock-file/composer.json
@@ -0,0 +1,7 @@
+{
+ "extra": {
+ "drupal-libraries": {
+ "simple-color": "https://github.com/recurser/jquery-simple-color/archive/v1.2.2.zip"
+ }
+ }
+}
diff --git a/tests/fixtures/existing-lock-file/installed-libraries.expected.json b/tests/fixtures/existing-lock-file/installed-libraries.expected.json
new file mode 100644
index 0000000..0ac0bca
--- /dev/null
+++ b/tests/fixtures/existing-lock-file/installed-libraries.expected.json
@@ -0,0 +1,12 @@
+{
+ "schema-version": "1.0",
+ "installed": {
+ "simple-color": {
+ "version": "v1.2.2",
+ "url": "https://github.com/recurser/jquery-simple-color/archive/v1.2.2.zip",
+ "type": "zip",
+ "ignore": [],
+ "package": "drupal-libraries-installer-test/root-package"
+ }
+ }
+}
diff --git a/tests/fixtures/existing-lock-file/installed-libraries.initial.json b/tests/fixtures/existing-lock-file/installed-libraries.initial.json
new file mode 100644
index 0000000..0ac0bca
--- /dev/null
+++ b/tests/fixtures/existing-lock-file/installed-libraries.initial.json
@@ -0,0 +1,12 @@
+{
+ "schema-version": "1.0",
+ "installed": {
+ "simple-color": {
+ "version": "v1.2.2",
+ "url": "https://github.com/recurser/jquery-simple-color/archive/v1.2.2.zip",
+ "type": "zip",
+ "ignore": [],
+ "package": "drupal-libraries-installer-test/root-package"
+ }
+ }
+}
diff --git a/tests/fixtures/existing-lock-file/libraries-structure.json b/tests/fixtures/existing-lock-file/libraries-structure.json
new file mode 100644
index 0000000..b58e30f
--- /dev/null
+++ b/tests/fixtures/existing-lock-file/libraries-structure.json
@@ -0,0 +1,5 @@
+{
+ "simple-color": {
+ "simple-color.js": ""
+ }
+}
diff --git a/tests/fixtures/specific-dependency-libraries-with-override/composer.json b/tests/fixtures/specific-dependency-libraries-with-override/composer.json
new file mode 100644
index 0000000..fd09852
--- /dev/null
+++ b/tests/fixtures/specific-dependency-libraries-with-override/composer.json
@@ -0,0 +1,16 @@
+{
+ "require": {
+ "zodiacmedia/drupal-libraries-test-dependency": "*@dev"
+ },
+ "extra": {
+ "drupal-libraries-dependencies": [
+ "zodiacmedia/drupal-libraries-test-dependency"
+ ],
+ "drupal-libraries": {
+ "test-moment": {
+ "url": "https://assets.example.com/moment-override.zip",
+ "ignore": []
+ }
+ }
+ }
+}
diff --git a/tests/fixtures/specific-dependency-libraries-with-override/installed-libraries.expected.json b/tests/fixtures/specific-dependency-libraries-with-override/installed-libraries.expected.json
new file mode 100644
index 0000000..55f02f1
--- /dev/null
+++ b/tests/fixtures/specific-dependency-libraries-with-override/installed-libraries.expected.json
@@ -0,0 +1,19 @@
+{
+ "schema-version": "1.0",
+ "installed": {
+ "test-moment": {
+ "version": "1.0.0",
+ "url": "https://assets.example.com/moment-override.zip",
+ "type": "zip",
+ "ignore": [],
+ "package": "drupal-libraries-installer-test/root-package"
+ },
+ "test-library-dependency": {
+ "version": "1.0.0",
+ "url": "https://example.com/test-library-dependency.zip",
+ "type": "zip",
+ "ignore": [],
+ "package": "zodiacmedia/drupal-libraries-test-dependency"
+ }
+ }
+}
diff --git a/tests/fixtures/specific-dependency-libraries-with-override/libraries-structure.json b/tests/fixtures/specific-dependency-libraries-with-override/libraries-structure.json
new file mode 100644
index 0000000..4e9c0dd
--- /dev/null
+++ b/tests/fixtures/specific-dependency-libraries-with-override/libraries-structure.json
@@ -0,0 +1,10 @@
+{
+ "test-library-dependency": {
+ "index.js": "",
+ "README.md": ""
+ },
+ "test-moment": {
+ "moment.js": "",
+ "README.md": ""
+ }
+}
diff --git a/tests/fixtures/specific-dependency-libraries/composer.json b/tests/fixtures/specific-dependency-libraries/composer.json
new file mode 100644
index 0000000..3142496
--- /dev/null
+++ b/tests/fixtures/specific-dependency-libraries/composer.json
@@ -0,0 +1,10 @@
+{
+ "require": {
+ "zodiacmedia/drupal-libraries-test-dependency": "*@dev"
+ },
+ "extra": {
+ "drupal-libraries-dependencies": [
+ "zodiacmedia/drupal-libraries-test-dependency"
+ ]
+ }
+}
diff --git a/tests/fixtures/specific-dependency-libraries/installed-libraries.expected.json b/tests/fixtures/specific-dependency-libraries/installed-libraries.expected.json
new file mode 100644
index 0000000..3191e2e
--- /dev/null
+++ b/tests/fixtures/specific-dependency-libraries/installed-libraries.expected.json
@@ -0,0 +1,22 @@
+{
+ "schema-version": "1.0",
+ "installed": {
+ "test-library-dependency": {
+ "version": "1.0.0",
+ "url": "https://example.com/test-library-dependency.zip",
+ "type": "zip",
+ "ignore": [],
+ "package": "zodiacmedia/drupal-libraries-test-dependency"
+ },
+ "test-moment": {
+ "version": "2.25.0",
+ "url": "https://registry.npmjs.org/moment/-/moment-2.25.0.tgz",
+ "type": "tar",
+ "ignore": [
+ "*.md"
+ ],
+ "package": "zodiacmedia/drupal-libraries-test-dependency",
+ "shasum": "e961ab9a5848a1cf2c52b1af4e6c82a8401e7fe9"
+ }
+ }
+}
diff --git a/tests/fixtures/specific-dependency-libraries/libraries-structure.json b/tests/fixtures/specific-dependency-libraries/libraries-structure.json
new file mode 100644
index 0000000..4e9c0dd
--- /dev/null
+++ b/tests/fixtures/specific-dependency-libraries/libraries-structure.json
@@ -0,0 +1,10 @@
+{
+ "test-library-dependency": {
+ "index.js": "",
+ "README.md": ""
+ },
+ "test-moment": {
+ "moment.js": "",
+ "README.md": ""
+ }
+}