Skip to content

Commit

Permalink
Add the ability to rename files/folders and support vendor namespaces.
Browse files Browse the repository at this point in the history
Also adds a convenience "install-drupal-libraries" command.
  • Loading branch information
codebymikey committed Oct 30, 2020
1 parent 723eca1 commit e395487
Show file tree
Hide file tree
Showing 22 changed files with 498 additions and 25 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
1.3.0 / 2020-07-27
========================
* Add a `rename` property to the library definition, providing the ability to
rename a library asset to match a particular folder pattern.
* Add support for libraries with vendor namespaces like [ckeditor][ckeditor-downloads].
* Add a convenience `install-drupal-libraries` composer command. It typically
requires your composer dependencies to have already been resolved.

1.2.0 / 2020-07-20
========================
* Address a `LogicException` being thrown when the package is uninstalled.
Expand Down Expand Up @@ -25,3 +33,5 @@ definition for supporting:
1.0.0 / 2018-01-25
========================
* Initial MVP plugin.

[ckeditor-downloads]: https://github.com/balbuf/drupal-libraries-installer/issues/6
50 changes: 45 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,15 @@ If the file is not a ZIP file, then the URL must end with the file extension:
"*.{md}",
"Gruntfile.js",
"{docs,src,tests}"
]
],
"rename": {
"dist": "build"
}
},
"custom-tar-asset": {
"url": "https://assets.custom-url.com/unconventional/url/path",
"type": "tar",
"ignore": [".*", "*.{txt,md}"]
"url": "https://assets.custom-url.com/unconventional/url/path",
"type": "tar",
"ignore": [".*", "*.{txt,md}"]
}
}
}
Expand All @@ -74,6 +77,7 @@ If the file is not a ZIP file, then the URL must end with the file extension:
- `version`: The version of the library (defaults to `1.0.0`).
- `type`: The type of library archive, one of (zip, tar, rar, gzip), support depends on your composer version (defaults to `zip`).
- `ignore`: Array of folders/file globs to remove from the library (defaults to `[]`). See [PSA-2011-002](https://www.drupal.org/node/1189632).
- `rename`: Object mapping of folders/files to rename to fit a certain folder structure (optional).
- `shasum`: The SHA1 hash of the asset (optional).
_See below for how to find the ZIP URL for a GitHub repo._
Expand Down Expand Up @@ -170,12 +174,48 @@ desired link to use within your `composer.json` file.
If the library does not provide any releases, you can still reference it in ZIP file form.
The downside is that any time you download this ZIP, the contents may change based on the
state of the repo. There is no guarantee that separate users of the project will have the
exact same version of the library.
exact same version of the library. To mitigate against this issue, you should
always download a specific commit version rather than from a branch like `master`.

1. Click the green `Clone or download` button on the repo's home page.
1. Copy the URL for the `Download ZIP` link to use within your `composer.json` file.
### Library definitions with namespaces
If a library includes a vendor namespace, then its internal package name
will be prefixed with a `drupal-library_` e.g. `vendor/library` becomes
`drupal-library_vendor/library`, which in turn allows you to add a
custom installer option like the following to manage where it's downloaded:
```json5
{
// composer.json
"extra": {
"installer-paths": {
// Custom installer path entry to store them all under the same folder.
"web/libraries/myvendor/{$name}": [
"vendor:drupal-library_ckeditor"
],
"web/libraries/{$name}/": [
"type:drupal-library"
]
},
"drupal-libraries": {
"myvendor/package1": "https://download.myvendor.com/package1-1.0.0.zip",
"myvendor/package2": "https://download.myvendor.com/package2-1.4.0.zip"
}
},
}
```

Otherwise the files will be stored in `web/libraries/myvendor` by default and
will overwrite each other.

**NB**: The order of the `installer-paths` matters.

The justification behind the prefix is to avoid any potential collision with
normal composer packages.

## Notes

- This plugin is essentially a shortcut for explicitly declaring the composer package
Expand Down
2 changes: 2 additions & 0 deletions example/project/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Demo root project

Demo root project integrating with Drupal libraries installer.

Update locally after committing by running `composer update zodiacmedia/drupal-libraries-installer`.
16 changes: 13 additions & 3 deletions example/project/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
},
"extra": {
"installer-paths": {
"web/libraries/ckeditor/{$name}": [
"vendor:drupal-library_ckeditor"
],
"web/libraries/{$name}/": [
"type:drupal-library"
]
Expand All @@ -42,6 +45,8 @@
"zodiacmedia/drupal-libraries-installer-demo-dependency"
],
"drupal-libraries": {
"ckeditor/codesnippet": "https://download.ckeditor.com/codesnippet/releases/codesnippet_4.9.2.zip",
"ckeditor/contents": "https://download.ckeditor.com/contents/releases/contents_0.11.zip",
"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",
Expand All @@ -65,10 +70,15 @@
"url": "https://github.com/select2/select2/archive/4.0.13.zip",
"ignore": [
".*",
"*.{md}",
"Gruntfile.js",
"{docs,src,tests}"
]
],
"rename": {
"dist": "build",
"empty_directory": "../../../../../../../../moved-outside-library-directory",
"LICENSE.md": "README.md",
"select2.js": "index.js",
"non-existent": "ignored"
}
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/InstallLibrariesEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Zodiacmedia\DrupalLibrariesInstaller;

use Composer\Script\Event;

/**
* The Install Drupal libraries event.
*/
class InstallLibrariesEvent extends Event {

/**
* The event is triggered when the 'install-drupal-libraries' command is ran.
*
* The event listener method receives a
* \Zodiacmedia\DrupalLibrariesInstaller\DownloadLibraryEvent instance.
*
* @var string
*/
const INSTALL_LIBRARIES = 'install-drupal-libraries';

}
67 changes: 60 additions & 7 deletions src/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,26 @@
use Composer\Package\CompletePackage;
use Composer\Package\Package;
use Composer\Package\PackageInterface;
use Composer\Plugin\Capable;
use Composer\Plugin\PluginInterface;
use Composer\Script\Event;
use Composer\Script\ScriptEvents;
use Composer\Util\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\Glob;
use Composer\Plugin\Capability\CommandProvider;

/**
* The Drupal libraries installer plugin.
*/
class Plugin implements PluginInterface, EventSubscriberInterface {
class Plugin implements PluginInterface, Capable, EventSubscriberInterface {

/**
* The installed-libraries.json lock file schema version.
*
* @var string
*/
const SCHEMA_VERSION = '1.0';
const SCHEMA_VERSION = '1.1';

/**
* The composer package name.
Expand Down Expand Up @@ -94,14 +96,24 @@ public static function getSubscribedEvents() {
return [
ScriptEvents::POST_INSTALL_CMD => 'install',
ScriptEvents::POST_UPDATE_CMD => 'install',
InstallLibrariesEvent::INSTALL_LIBRARIES => 'install',
];
}

/**
* {@inheritDoc}
*/
public function getCapabilities() {
return [
CommandProvider::class => PluginCommandProvider::class,
];
}

/**
* Upon running composer install or update, install the drupal libraries.
*
* @param \Composer\Script\Event $event
* The composer install/update event.
* @param \Composer\Script\Event|\Zodiacmedia\DrupalLibrariesInstaller\InstallLibrariesEvent $event
* The composer install/update/install-drupal-libraries event.
*
* @throws \Exception
*/
Expand Down Expand Up @@ -209,6 +221,7 @@ protected function processPackage(array $processed_drupal_libraries, array $drup
// Install each library.
foreach ($extra['drupal-libraries'] as $library => $library_definition) {
$ignore_patterns = [];
$rename = [];
$sha1checksum = NULL;
if (is_string($library_definition)) {
// Simple format.
Expand All @@ -224,6 +237,7 @@ protected function processPackage(array $processed_drupal_libraries, array $drup
$version = $library_definition['version'] ?? $version;
$distribution_type = $library_definition['type'] ?? $distribution_type;
$ignore_patterns = $library_definition['ignore'] ?? $ignore_patterns;
$rename = $library_definition['rename'] ?? $rename;
$sha1checksum = $library_definition['shasum'] ?? $sha1checksum;
}

Expand Down Expand Up @@ -251,8 +265,12 @@ protected function processPackage(array $processed_drupal_libraries, array $drup
'url' => $url,
'type' => $distribution_type,
'ignore' => $ignore_patterns,
'rename' => $rename,
'package' => $package->getName(),
];
if (empty($rename)) {
unset($applied_library['rename']);
}
if (isset($sha1checksum)) {
$applied_library['shasum'] = $sha1checksum;
}
Expand Down Expand Up @@ -294,6 +312,7 @@ protected function downloadLibraries(array $processed_libraries, array $applied_
$library_package = $this->getLibraryPackage($library_name, $processed_library);

$ignore_patterns = $processed_library['ignore'];
$rename = $processed_library['rename'] ?? NULL;
$install_path = $this->installationManager->getInstallPath($library_package);
if (
(
Expand All @@ -306,7 +325,7 @@ protected function downloadLibraries(array $processed_libraries, array $applied_
// - wasn't in the lock file.
// - doesn't match what is in the lock file.
// - doesn't exist on disk.
$this->downloadPackage($library_package, $install_path, $ignore_patterns);
$this->downloadPackage($library_package, $install_path, $ignore_patterns, $rename);
}
}
}
Expand All @@ -320,8 +339,10 @@ protected function downloadLibraries(array $processed_libraries, array $applied_
* The package install path.
* @param array $ignore_patterns
* File patterns to ignore.
* @param array|null $rename
* Array mapping of files/folders to rename.
*/
protected function downloadPackage(Package $library_package, $install_path, array $ignore_patterns) {
protected function downloadPackage(Package $library_package, $install_path, array $ignore_patterns, array $rename = NULL) {
// Let composer download and unpack the library for us!
$this->downloadManager->download($library_package, $install_path);

Expand Down Expand Up @@ -364,6 +385,30 @@ function ($file) use ($patterns) {
$this->fileSystem->remove($file_pathname);
}
}

if ($rename) {
foreach ($rename as $original_file => $destination_file) {
$original_file = $this->fileSystem->normalizePath("$install_path/$original_file");
$destination_file = $this->fileSystem->normalizePath("$install_path/$destination_file");
if (strpos($original_file, $install_path) !== 0) {
$this->io->writeError(" - Could not rename <info>$original_file</info> as it is outside the library directory.");
}
elseif (strpos($destination_file, $install_path) !== 0) {
$this->io->writeError(" - Could not rename <info>$destination_file</info> as it is outside the library directory.");
}
elseif (!file_exists($original_file)) {
$this->io->writeError(" - Could not rename <info>$original_file</info> as it does not exist");
}
elseif (file_exists($destination_file)) {
$this->io->writeError(" - Could not rename <info>$original_file</info> as the destination file <info>$destination_file</info> already exists");
}
else {
$this->io->writeError(" - Renaming <info>$original_file</info> to <info>$destination_file</info>");
// Attempt to move the file over.
$this->fileSystem->rename($original_file, $destination_file);
}
}
}
}

/**
Expand All @@ -378,7 +423,15 @@ function ($file) use ($patterns) {
* The pseudo-package for the library.
*/
protected function getLibraryPackage($library_name, array $library_definition) {
$library_package_name = 'drupal-library/' . $library_name;
if (strpos($library_name, '/')) {
// The library name already contains a '/', add the "drupal-library_"
// prefix to it so that it can be configured to a custom path through its
// vendor name.
$library_package_name = "drupal-library_$library_name";
}
else {
$library_package_name = 'drupal-library/' . $library_name;
}
$library_package = new Package(
$library_package_name, $library_definition['version'], $library_definition['version']
);
Expand Down
42 changes: 42 additions & 0 deletions src/PluginCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Zodiacmedia\DrupalLibrariesInstaller;

use Composer\Command\BaseCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
* The composer plugin command provider.
*/
class PluginCommand extends BaseCommand {

/**
* {@inheritDoc}
*/
protected function configure() {
$this->setName('install-drupal-libraries');
$this->setDescription(
'Download and install the drupal libraries.'
);
}

/**
* {@inheritDoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$composer = $this->getComposer();

$install_libraries_event = new InstallLibrariesEvent(
InstallLibrariesEvent::INSTALL_LIBRARIES,
$composer,
$this->getIO(),
FALSE
);
return $composer->getEventDispatcher()->dispatch(
$install_libraries_event->getName(),
$install_libraries_event
);
}

}
21 changes: 21 additions & 0 deletions src/PluginCommandProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Zodiacmedia\DrupalLibrariesInstaller;

use Composer\Plugin\Capability\CommandProvider;

/**
* The composer plugin command provider.
*/
class PluginCommandProvider implements CommandProvider {

/**
* {@inheritDoc}
*/
public function getCommands() {
return [
new PluginCommand(),
];
}

}
Loading

0 comments on commit e395487

Please sign in to comment.