diff --git a/CHANGELOG.md b/CHANGELOG.md index b1e2311..ac82ef4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,27 @@ Pico Composer Installer Changelog refer to both the UPGRADE and NEWS sections of the docs for more details. +### Version 2.0.2 +Released: - + +``` +* [Fixed] Update README.md for v2.0 +``` + +### Version 2.0.1 +Released: 2025-12-23 + +``` +* [Fixed] Fix implicit nullable deprecation +``` + +### Version 2.0.0 +Released: 2025-12-22 + +``` +* [Changed] Update to a Composer 2.x plugin (no functional changes) +``` + ### Version 1.0.1 Released: 2019-11-24 diff --git a/README.md b/README.md index 580a8ae..93fdfe0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Pico Composer Installer This is the repository of Pico's official [Composer][] installer. -Pico is a stupidly simple, blazing fast, flat file CMS. See http://picocms.org/ for more info. +Pico is a stupidly simple, blazing fast, flat file CMS. See https://picocms.org/ for more info. This Composer plugin is responsible for installing Pico plugins and themes using the Composer package manager (i.e. by running `composer install` on the command line). It assumes responsibility for packages that identify as `{ "type": "pico-plugin" }` and `{ "type": "pico-theme" }` in their `composer.json` and instructs Composer to install these packages to Pico's `plugins/` and `themes/` folder respectively. @@ -19,14 +19,14 @@ Install If you've used Pico's official composer starter project ([`picocms/pico-composer`][pico-composer]), your website's `composer.json` (the "root package") already depends on `picocms/composer-installer`. If this isn't true, run the following to load it from [Packagist.org][]: ```shell -$ composer require picocms/composer-installer:^1.0 +$ composer require picocms/composer-installer:^2.0 ``` The Composer plugin tries to automatically register itself for the `post-autoload-dump` event. This is a prerequisite for the installer to create a `vendor/pico-plugin.php`. If the installer was successful in doing so, you'll see the following three lines when running `composer install` or `composer update`: ``` Generating autoload files -> Pico\Composer\Installer\PluginInstaller::postAutoloadDump +> picocms\\ComposerInstaller\\Installer::postAutoloadDump Creating Pico plugins file ``` @@ -36,7 +36,7 @@ If you see just the first two lines and not the third one, please make sure that { "scripts": { "post-autoload-dump": [ - "Pico\\Composer\\Installer\\PluginInstaller::postAutoloadDump" + "picocms\\ComposerInstaller\\Installer::postAutoloadDump" ] } } diff --git a/composer.json b/composer.json index 869aa10..6b5df51 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,9 @@ { "name": "picocms/composer-installer", - "type": "composer-installer", + "type": "composer-plugin", "description": "A composer plugin responsible for installing plugins and themes for Pico, a stupidly simple, blazing fast, flat file CMS.", "keywords": [ "pico", "picocms", "pico-cms", "picocms-plugin", "picocms-theme", "picocms-installer", "composer", "composer-plugin", "composer-installer" ], - "homepage": "http://picocms.org/", + "homepage": "https://picocms.org/", "license": "MIT", "authors": [ { @@ -13,7 +13,7 @@ }, { "name": "The Pico Community", - "homepage": "http://picocms.org/" + "homepage": "https://picocms.org/" }, { "name": "Contributors", @@ -24,12 +24,20 @@ "issues": "https://github.com/picocms/composer-installer/issues", "source": "https://github.com/picocms/composer-installer" }, + "require": { + "php": ">=7.2", + "composer-plugin-api": "^2.1" + }, "autoload": { "psr-4": { - "Pico\\Composer\\": "src" + "picocms\\ComposerInstaller\\": "src" } }, "extra": { - "class": "Pico\\Composer\\Installer\\PluginInstaller" + "class": "picocms\\ComposerInstaller\\Plugin", + "branch-alias": { + "dev-master": "1.0.x-dev", + "dev-pico-3.0": "2.0.x-dev" + } } } diff --git a/src/Installer/PluginInstaller.php b/src/Installer.php similarity index 81% rename from src/Installer/PluginInstaller.php rename to src/Installer.php index 7bc9aad..57f7504 100644 --- a/src/Installer/PluginInstaller.php +++ b/src/Installer.php @@ -4,13 +4,13 @@ * in the version control history of the file, available from the following * original location: * - * + * * * SPDX-License-Identifier: MIT * License-Filename: LICENSE */ -namespace Pico\Composer\Installer; +namespace picocms\ComposerInstaller; use Composer\Composer; use Composer\Installer\BinaryInstaller; @@ -18,6 +18,7 @@ use Composer\IO\IOInterface; use Composer\Package\AliasPackage; use Composer\Package\PackageInterface; +use Composer\Package\RootPackageInterface; use Composer\Script\Event; use Composer\Util\Filesystem; @@ -28,40 +29,40 @@ * and themes for Pico using Composer. Pico is a stupidly simple, blazing fast, * flat file CMS. * - * See for more info. + * See for more info. * * @author Daniel Rudolf - * @link http://picocms.org - * @license http://opensource.org/licenses/MIT The MIT License - * @version 1.0 + * @link https://picocms.org + * @license https://opensource.org/licenses/MIT The MIT License + * @version 2.0 */ -class PluginInstaller extends LibraryInstaller +class Installer extends LibraryInstaller { /** * Package name of this composer installer * * @var string */ - const PACKAGE_NAME = 'picocms/composer-installer'; + public const PACKAGE_NAME = 'picocms/composer-installer'; /** * Package type of Pico plugins * * @var string */ - const PACKAGE_TYPE_PLUGIN = 'pico-plugin'; + public const PACKAGE_TYPE_PLUGIN = 'pico-plugin'; /** * Package type of Pico themes * * @var string */ - const PACKAGE_TYPE_THEME = 'pico-theme'; + public const PACKAGE_TYPE_THEME = 'pico-theme'; /** * Composer root package * - * @var PackageInterface|null + * @var RootPackageInterface|null */ protected $rootPackage; @@ -70,10 +71,10 @@ class PluginInstaller extends LibraryInstaller * * @var string[] */ - protected $installDirs = array( + protected $installDirs = [ self::PACKAGE_TYPE_PLUGIN => 'plugins', - self::PACKAGE_TYPE_THEME => 'themes' - ); + self::PACKAGE_TYPE_THEME => 'themes', + ]; /** * A flag to check usage of the postAutoloadDump event @@ -86,38 +87,37 @@ class PluginInstaller extends LibraryInstaller * Initializes Pico plugin and theme installer * * This method tries to register the `post-autoload-dump` script - * ({@see PluginInstaller::postAutoloadDump()}), if it wasn't explicitly + * ({@see Installer::postAutoloadDump()}), if it wasn't explicitly * set already. If this isn't possible, the autoload dump event can't be - * used ({@see PluginInstaller::checkAutoloadDump()}). + * used ({@see Installer::checkAutoloadDump()}). * - * @param IOInterface $io - * @param Composer $composer - * @param string $type - * @param Filesystem $filesystem - * @param BinaryInstaller $binaryInstaller + * @param IOInterface $io + * @param Composer $composer + * @param string|null $type + * @param Filesystem|null $filesystem + * @param BinaryInstaller|null $binaryInstaller */ public function __construct( IOInterface $io, Composer $composer, - $type = 'library', - Filesystem $filesystem = null, - BinaryInstaller $binaryInstaller = null + ?string $type = 'library', + ?Filesystem $filesystem = null, + ?BinaryInstaller $binaryInstaller = null ) { parent::__construct($io, $composer, $type, $filesystem, $binaryInstaller); + $this->rootPackage = static::getRootPackage($this->composer); // try to register the `post-autoload-dump` script $scripts = $this->rootPackage->getScripts(); - $callback = get_called_class() . '::postAutoloadDump'; + $callback = static::class . '::postAutoloadDump'; if (isset($scripts['post-autoload-dump']) && in_array($callback, $scripts['post-autoload-dump'])) { // the user explicitly added the `post-autoload-dump` script, // force the autoload dump event to be used static::$useAutoloadDump = true; } else { - if (is_callable(array($this->rootPackage, 'setScripts'))) { - $scripts['post-autoload-dump'][] = $callback; - $this->rootPackage->setScripts($scripts); - } + $scripts['post-autoload-dump'][] = $callback; + $this->rootPackage->setScripts($scripts); // check whether the autoload dump event is used static::checkAutoloadDump($this->composer); @@ -129,7 +129,7 @@ public function __construct( * * Using the autoload dump event will always create `pico-plugin.php` in * Composer's vendor dir. Plugins are nevertheless installed to Pico's - * `plugins/` dir ({@see PluginInstaller::getInstallPath()}). + * `plugins/` dir ({@see Installer::getInstallPath()}). * * The autoload dump event is used when the root package is a project and * explicitly requires this composer installer. @@ -138,23 +138,23 @@ public function __construct( * * @return bool */ - public static function checkAutoloadDump(Composer $composer) + public static function checkAutoloadDump(Composer $composer): bool { if (static::$useAutoloadDump === null) { static::$useAutoloadDump = false; $rootPackage = static::getRootPackage($composer); - if (!$rootPackage || ($rootPackage->getType() !== 'project')) { + if ($rootPackage->getType() !== 'project') { return false; } $rootPackageRequires = $rootPackage->getRequires(); - if (!isset($rootPackageRequires[static::PACKAGE_NAME])) { + if (!isset($rootPackageRequires[self::PACKAGE_NAME])) { return false; } $scripts = $rootPackage->getScripts(); - $callback = get_called_class() . '::postAutoloadDump'; + $callback = static::class . '::postAutoloadDump'; if (!isset($scripts['post-autoload-dump']) || !in_array($callback, $scripts['post-autoload-dump'])) { return false; } @@ -173,7 +173,7 @@ public static function checkAutoloadDump(Composer $composer) * * @param Event $event */ - public static function postAutoloadDump(Event $event) + public static function postAutoloadDump(Event $event): void { $io = $event->getIO(); $composer = $event->getComposer(); @@ -201,9 +201,10 @@ public static function postAutoloadDump(Event $event) $rootPackage = static::getRootPackage($composer); $packages = $composer->getRepositoryManager()->getLocalRepository()->getPackages(); - $plugins = $pluginClassNames = array(); + $plugins = []; + $pluginClassNames = []; foreach ($packages as $package) { - if ($package->getType() !== static::PACKAGE_TYPE_PLUGIN) { + if ($package->getType() !== self::PACKAGE_TYPE_PLUGIN) { continue; } @@ -222,7 +223,7 @@ public static function postAutoloadDump(Event $event) * package's or the plugin package's `composer.json`, or are derived * implicitly from the plugin's installer name. The installer name is, for * its part, either specified explicitly, or derived implicitly from the - * plugin package's name ({@see PluginInstaller::getInstallName()}). + * plugin package's name ({@see Installer::getInstallName()}). * * 1. Using the "pico-plugin" extra in the root package's `composer.json`: * ```yaml @@ -236,7 +237,7 @@ public static function postAutoloadDump(Event $event) * ``` * * Besides matching exact package names, you can also use the prefixes - * `vendor:` or `name:` ({@see PluginInstaller::mapRootExtra()}). + * `vendor:` or `name:` ({@see Installer::mapRootExtra()}). * * 2. Using the "pico-plugin" extra in the package's `composer.json`: * ```yaml @@ -247,19 +248,21 @@ public static function postAutoloadDump(Event $event) * } * ``` * - * 3. Using the installer name ({@see PluginInstaller::getInstallName()}). + * 3. Using the installer name ({@see Installer::getInstallName()}). * * @param PackageInterface $package * @param PackageInterface|null $rootPackage * * @return string[] */ - public static function getPluginClassNames(PackageInterface $package, PackageInterface $rootPackage = null) - { + public static function getPluginClassNames( + PackageInterface $package, + ?PackageInterface $rootPackage = null + ): array { $packageType = $package->getType(); $packagePrettyName = $package->getPrettyName(); - $classNames = array(); + $classNames = []; // 1. root package $rootPackageExtra = $rootPackage ? $rootPackage->getExtra() : null; @@ -278,7 +281,7 @@ public static function getPluginClassNames(PackageInterface $package, PackageInt // 3. guess by installer name if (!$classNames) { $installName = static::getInstallName($package, $rootPackage); - $classNames = array($installName); + $classNames = [ $installName ]; } return $classNames; @@ -293,14 +296,14 @@ public static function getPluginClassNames(PackageInterface $package, PackageInt * name. * * Install names are determined the same way as plugin class names. See - * {@see PluginInstaller::getPluginClassNames()} for details. + * {@see Installer::getPluginClassNames()} for details. * * @param PackageInterface $package * @param PackageInterface|null $rootPackage * * @return string */ - public static function getInstallName(PackageInterface $package, PackageInterface $rootPackage = null) + public static function getInstallName(PackageInterface $package, ?PackageInterface $rootPackage = null): string { $packagePrettyName = $package->getPrettyName(); $packageName = $package->getName(); @@ -332,11 +335,11 @@ public static function getInstallName(PackageInterface $package, PackageInterfac * * @return string */ - protected static function guessInstallName($packageName) + protected static function guessInstallName(string $packageName): string { $name = $packageName; if (strpos($packageName, '/') !== false) { - list(, $name) = explode('/', $packageName); + [, $name] = explode('/', $packageName); } $name = preg_replace('/[\.\-_]+(?>plugin|theme)$/u', '', $name); @@ -363,24 +366,28 @@ function ($matches) { * * @return mixed */ - protected static function mapRootExtra(array $packageExtra, $packagePrettyName) + protected static function mapRootExtra(array $packageExtra, string $packagePrettyName) { if (isset($packageExtra[$packagePrettyName])) { return $packageExtra[$packagePrettyName]; } if (strpos($packagePrettyName, '/') !== false) { - list($vendor, $name) = explode('/', $packagePrettyName); + [$vendor, $name] = explode('/', $packagePrettyName); } else { $vendor = ''; $name = $packagePrettyName; } foreach ($packageExtra as $key => $value) { - if ((substr($key, 0, 5) === 'name:') && (substr($key, 5) === $name)) { - return $value; - } elseif ((substr($key, 0, 7) === 'vendor:') && (substr($key, 7) === $vendor)) { - return $value; + if (substr_compare($key, 'name:', 0, 5) === 0) { + if (substr_compare($key, $name, 5) === 0) { + return $value; + } + } elseif (substr_compare($key, 'vendor:', 0, 7) === 0) { + if (substr_compare($key, $vendor, 7) === 0) { + return $value; + } } } @@ -394,9 +401,9 @@ protected static function mapRootExtra(array $packageExtra, $packagePrettyName) * * @return string */ - protected static function getPluginConfig($vendorDir) + protected static function getPluginConfig(string $vendorDir): string { - return $vendorDir . '/' . static::PACKAGE_TYPE_PLUGIN . '.php'; + return $vendorDir . '/pico-plugin.php'; } /** @@ -406,9 +413,9 @@ protected static function getPluginConfig($vendorDir) * @param array $plugins * @param array $pluginClassNames */ - public static function writePluginConfig($pluginConfig, array $plugins, array $pluginClassNames) + public static function writePluginConfig(string $pluginConfig, array $plugins, array $pluginClassNames): void { - $data = array(); + $data = []; foreach ($plugins as $pluginName => $installerName) { // see https://github.com/composer/composer/blob/1.0.0/src/Composer/Command/InitCommand.php#L206-L210 if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}', $pluginName)) { @@ -417,6 +424,7 @@ public static function writePluginConfig($pluginConfig, array $plugins, array $p . "a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+" ); } + $data[] = sprintf(" '%s' => array(", $pluginName); if (!preg_match('{^[a-zA-Z0-9_.-]+$}', $installerName)) { @@ -425,6 +433,7 @@ public static function writePluginConfig($pluginConfig, array $plugins, array $p . "it must be alphanumeric, matching: [a-zA-Z0-9_.-]+" ); } + $data[] = sprintf(" 'installerName' => '%s',", $installerName); if (isset($pluginClassNames[$pluginName])) { @@ -437,6 +446,7 @@ public static function writePluginConfig($pluginConfig, array $plugins, array $p "The plugin class name '" . $className . "' is no valid PHP class name" ); } + $data[] = sprintf(" '%s',", $className); } @@ -460,7 +470,7 @@ public static function writePluginConfig($pluginConfig, array $plugins, array $p $contents = sprintf( $contents, basename($pluginConfig), - static::PACKAGE_NAME, + self::PACKAGE_NAME, implode("\n", $data) ); @@ -472,9 +482,9 @@ public static function writePluginConfig($pluginConfig, array $plugins, array $p * * @param Composer $composer * - * @return PackageInterface + * @return RootPackageInterface */ - protected static function getRootPackage(Composer $composer) + protected static function getRootPackage(Composer $composer): RootPackageInterface { $rootPackage = $composer->getPackage(); if ($rootPackage) { @@ -496,8 +506,8 @@ protected static function getRootPackage(Composer $composer) public function supports($packageType) { return ( - ($packageType === static::PACKAGE_TYPE_PLUGIN) - || ($packageType === static::PACKAGE_TYPE_THEME) + ($packageType === self::PACKAGE_TYPE_PLUGIN) + || ($packageType === self::PACKAGE_TYPE_THEME) ); } @@ -529,7 +539,7 @@ public function getInstallPath(PackageInterface $package) * * @return string */ - protected function initializeInstallDir($packageType) + protected function initializeInstallDir(string $packageType): string { $installDir = ''; diff --git a/src/Plugin.php b/src/Plugin.php new file mode 100644 index 0000000..d7394b6 --- /dev/null +++ b/src/Plugin.php @@ -0,0 +1,79 @@ + + * + * SPDX-License-Identifier: MIT + * License-Filename: LICENSE + */ + +namespace picocms\ComposerInstaller; + +use Composer\Composer; +use Composer\IO\IOInterface; +use Composer\Plugin\PluginInterface; + +/** + * Composer plugin for the Pico plugin and theme installer + * + * This Composer plugin registers Pico's plugin and theme installer. Pico is a + * stupidly simple, blazing fast, flat file CMS. + * + * See for more info. + * + * @author Daniel Rudolf + * @link https://picocms.org + * @license https://opensource.org/licenses/MIT The MIT License + * @version 2.0 + */ +class Plugin implements PluginInterface +{ + /** + * Instance of Pico's plugin and theme installer + * + * @var Installer|null + */ + private $installer; + + /** + * Adds Pico's plugin and theme installer to Composer + * + * @param Composer $composer + * @param IOInterface $io + * + * @return void + */ + public function activate(Composer $composer, IOInterface $io): void + { + $this->installer = new Installer($io, $composer); + $composer->getInstallationManager()->addInstaller($this->installer); + } + + /** + * Removes Pico's plugin and theme installer from Composer + * + * @param Composer $composer + * @param IOInterface $io + * + * @return void + */ + public function deactivate(Composer $composer, IOInterface $io): void + { + $composer->getInstallationManager()->removeInstaller($this->installer); + } + + /** + * Uninstalls the Composer plugin (no-op) + * + * @param Composer $composer + * @param IOInterface $io + * + * @return void + */ + public function uninstall(Composer $composer, IOInterface $io): void + { + } +}