Skip to content
This repository has been archived by the owner on Mar 1, 2023. It is now read-only.

Commit

Permalink
fixes #31 (#37)
Browse files Browse the repository at this point in the history
| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #31 
| License       | MIT
| Doc PR        |
  • Loading branch information
prisis committed Aug 11, 2018
1 parent ec03be1 commit 2998d54
Show file tree
Hide file tree
Showing 18 changed files with 961 additions and 183 deletions.
3 changes: 1 addition & 2 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ parameters:
ignoreErrors:
# QuestionInstallationManager
- '#\$package of method Composer\\Package\\Version\\VersionSelector\:\:findRecommendedRequireVersion\(\) expects Composer\\Package\\PackageInterface, Composer\\Package\\PackageInterface\|true given#'
# ScriptExecutor
- '#Call to static method cerebroBinary\(\) on an unknown class Viserio\\Component\\Console\\Application#'
# ScriptExtender
- '#Parameter \#1 \$stream of class Symfony\\Component\\Console\\Output\\StreamOutput constructor expects resource, resource\|false given#'

- '#Call to an undefined method Composer\\DependencyResolver\\Operation\\OperationInterface\:\:getPackage#'
Expand Down
2 changes: 1 addition & 1 deletion src/Automatic/AbstractConfigurator.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public function add(string $name, string $configurator): void
}

if (! \is_subclass_of($configurator, ConfiguratorContract::class)) {
throw new InvalidArgumentException(\sprintf('Configurator class [%s] must extend the class [%s].', $configurator, ConfiguratorContract::class));
throw new InvalidArgumentException(\sprintf('The class [%s] must implement the interface [\\%s].', $configurator, ConfiguratorContract::class));
}

$this->configurators[$name] = $configurator;
Expand Down
209 changes: 155 additions & 54 deletions src/Automatic/Automatic.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@
use Composer\Script\Event;
use Composer\Script\ScriptEvents;
use FilesystemIterator;
use Narrowspark\Automatic\Common\Contract\Exception\InvalidArgumentException;
use Narrowspark\Automatic\Common\Contract\Package as PackageContract;
use Narrowspark\Automatic\Common\Contract\ScriptExtender as ScriptExtenderContract;
use Narrowspark\Automatic\Common\Traits\ExpandTargetDirTrait;
use Narrowspark\Automatic\Common\Traits\GetGenericPropertyReaderTrait;
use Narrowspark\Automatic\Common\Util;
use Narrowspark\Automatic\Contract\Container as ContractContainer;
use Narrowspark\Automatic\Installer\ConfiguratorInstaller;
use Narrowspark\Automatic\Installer\SkeletonInstaller;
use Narrowspark\Automatic\Prefetcher\ParallelDownloader;
Expand Down Expand Up @@ -63,18 +66,18 @@ class Automatic implements PluginInterface, EventSubscriberInterface
public const PACKAGE_NAME = 'narrowspark/automatic';

/**
* Check if the the plugin is activated.
* A Container instance.
*
* @var bool
* @var \Narrowspark\Automatic\Contract\Container
*/
private static $activated = true;
protected $container;

/**
* A Container instance.
* Check if the the plugin is activated.
*
* @var \Narrowspark\Automatic\Container
* @var bool
*/
private $container;
private static $activated = true;

/**
* Check if composer.lock should be updated.
Expand Down Expand Up @@ -106,6 +109,7 @@ public static function getSubscribedEvents(): array

return [
'auto-scripts' => 'executeAutoScripts',
'post-install-out' => 'postInstallOut',
InstallerEvents::PRE_DEPENDENCIES_SOLVING => [['onPreDependenciesSolving', \PHP_INT_MAX]],
InstallerEvents::POST_DEPENDENCIES_SOLVING => [['populateFilesCacheDir', \PHP_INT_MAX]],
PackageEvents::PRE_PACKAGE_INSTALL => [['populateFilesCacheDir', ~\PHP_INT_MAX]],
Expand All @@ -126,7 +130,7 @@ public static function getSubscribedEvents(): array
*/
public function activate(Composer $composer, IOInterface $io): void
{
if (($errorMessage = $this->getErrorMessage()) !== null) {
if (($errorMessage = $this->getErrorMessage($io)) !== null) {
self::$activated = false;

$io->writeError('<warning>Narrowspark Automatic has been disabled. ' . $errorMessage . '</warning>');
Expand Down Expand Up @@ -183,13 +187,27 @@ public function activate(Composer $composer, IOInterface $io): void
/**
* Get the Container instance.
*
* @return \Narrowspark\Automatic\Container
* @return \Narrowspark\Automatic\Contract\Container
*/
public function getContainer(): Container
public function getContainer(): ContractContainer
{
return $this->container;
}

/**
* Execute on composer post-install-out event.
*
* @param \Composer\Script\Event $event
*
* @return void
*/
public function postInstallOut(Event $event): void
{
$event->stopPropagation();

$this->container->get(IOInterface::class)->write($this->postInstallOutput);
}

/**
* Records composer operations.
*
Expand Down Expand Up @@ -239,22 +257,6 @@ public function onCommand(CommandEvent $event): void
*/
public function onPostCreateProject(Event $event): void
{
/** @var \Narrowspark\Automatic\Lock $lock */
$lock = $this->container->get(Lock::class);
/** @var \Composer\IO\IOInterface $io */
$io = $this->container->get(IOInterface::class);

$lock->read();

if ($lock->has(SkeletonInstaller::LOCK_KEY) && $io->isInteractive()) {
/** @var \Narrowspark\Automatic\SkeletonGenerator $skeletonGenerator */
$skeletonGenerator = $this->container->get(SkeletonGenerator::class);

$skeletonGenerator->run();

$skeletonGenerator->remove();
}

/** @var \Composer\Json\JsonFile $json */
/** @var \Composer\Json\JsonManipulator $manipulator */
[$json, $manipulator] = Util::getComposerJsonFileAndManipulator();
Expand All @@ -272,9 +274,22 @@ public function onPostCreateProject(Event $event): void
}
}

$manipulator->addSubNode('scripts', 'post-install-out', 'Added by automatic');

$scripts = [
'@auto-scripts',
'@post-install-out',
];

$manipulator->addSubNode('scripts', 'post-install-cmd', $scripts);
$manipulator->addSubNode('scripts', 'post-update-cmd', $scripts);
$manipulator->addSubNode('scripts', 'auto-scripts', new \stdClass());

\file_put_contents($json->getPath(), $manipulator->getContents());

$this->updateComposerLock();

$this->runSkeletonGenerator();
}

/**
Expand Down Expand Up @@ -404,11 +419,20 @@ public function executeAutoScripts(Event $event): void
$json = new JsonFile(Factory::getComposerFile());
$jsonContents = $json->read();

foreach ($jsonContents['scripts']['auto-scripts'] as $cmd => $type) {
$this->container->get(ScriptExecutor::class)->execute($type, $cmd);
}
if (isset($jsonContents['scripts']['auto-scripts'])) {
/** @var \Narrowspark\Automatic\ScriptExecutor $scriptExecutor */
$scriptExecutor = $this->container->get(ScriptExecutor::class);

$this->container->get(IOInterface::class)->write($this->postInstallOutput);
foreach ((array) $this->container->get(Lock::class)->get(ScriptExecutor::TYPE) as $extender) {
$scriptExecutor->addExtender($extender);
}

foreach ($jsonContents['scripts']['auto-scripts'] as $cmd => $type) {
$scriptExecutor->execute($type, $cmd);
}
} else {
$this->container->get(IOInterface::class)->write('No auto-scripts section was found under scripts.', true, IOInterface::VERBOSE);
}
}

/**
Expand Down Expand Up @@ -512,6 +536,7 @@ public function populateFilesCacheDir(InstallerEvent $event): void
*/
public function onFileDownload(PreFileDownloadEvent $event): void
{
/** @var \Narrowspark\Automatic\Prefetcher\ParallelDownloader $rfs */
$rfs = $this->container->get(ParallelDownloader::class);

if ($event->getRemoteFilesystem() !== $rfs) {
Expand All @@ -537,15 +562,17 @@ private function shouldRecordOperation(PackageEvent $event): bool
}

// when Composer runs with --no-dev, ignore uninstall operations on packages from require-dev
if (! $event->isDevMode() && $operation instanceof UninstallOperation) {
if ($operation instanceof UninstallOperation && ! $event->isDevMode()) {
foreach ($event->getComposer()->getLocker()->getLockData()['packages-dev'] as $devPackage) {
if ($package->getName() === $devPackage['name']) {
return false;
}
}
}

return ($operation instanceof InstallOperation && ! $this->container->get(Lock::class)->has($package->getName())) || $operation instanceof UninstallOperation;
$isInstallOperation = $operation instanceof InstallOperation && ! $this->container->get(Lock::class)->has($package->getName());

return $isInstallOperation || $operation instanceof UninstallOperation;
}

/**
Expand Down Expand Up @@ -641,7 +668,13 @@ private function doActionOnPackageOperation(PackageContract $package): void
*/
private function doInstall(PackageContract $package, PackageConfigurator $packageConfigurator): void
{
/** @var \Narrowspark\Automatic\Lock $lock */
$lock = $this->container->get(Lock::class);

$this->writeScriptExtenderToLock($package, $lock);

$this->container->get(Configurator::class)->configure($package);

$packageConfigurator->configure($package);

if ($package->hasConfig('post-install-output')) {
Expand All @@ -652,7 +685,13 @@ private function doInstall(PackageContract $package, PackageConfigurator $packag
$this->postInstallOutput[] = '';
}

$this->writePackageOperationToLock($package);
$lock->add(
self::LOCK_PACKAGES,
\array_merge(
(array) $lock->get(self::LOCK_PACKAGES),
[$package->getName() => $package->toArray()]
)
);
}

/**
Expand All @@ -668,54 +707,116 @@ private function doInstall(PackageContract $package, PackageConfigurator $packag
private function doUninstall(PackageContract $package, PackageConfigurator $packageConfigurator): void
{
$this->container->get(Configurator::class)->unconfigure($package);

$packageConfigurator->unconfigure($package);

/** @var \Narrowspark\Automatic\Lock $lock */
$lock = $this->container->get(Lock::class);

if ($package->hasConfig(ScriptExecutor::TYPE)) {
$extenders = (array) $lock->get(ScriptExecutor::TYPE);

/** @var \Narrowspark\Automatic\Common\Contract\ScriptExtender $extender */
foreach ((array) $package->getConfig(ScriptExecutor::TYPE) as $extender) {
$type = $extender::getType();

if (isset($extenders[$type])) {
unset($extenders[$type]);
}
}

$lock->add(ScriptExecutor::TYPE, $extenders);
}

$lock->remove($package->getName());
}

/**
* @codeCoverageIgnore
* Looks if the package has a extra section for script extender.
*
* Check if automatic can be activated.
* @param \Narrowspark\Automatic\Common\Contract\Package $package
* @param \Narrowspark\Automatic\Lock $lock
*
* @return null|string
* @throws \Narrowspark\Automatic\Common\Contract\Exception\InvalidArgumentException
*
* @return void
*/
private function getErrorMessage(): ?string
private function writeScriptExtenderToLock(PackageContract $package, Lock $lock): void
{
if (! \extension_loaded('openssl')) {
return 'You must enable the openssl extension in your "php.ini" file.';
}
if ($package->hasConfig(ScriptExecutor::TYPE)) {
$extenders = (array) $lock->get(ScriptExecutor::TYPE);

\preg_match('/\d+.\d+.\d+/m', Composer::VERSION, $matches);
foreach ((array) $package->getConfig(ScriptExecutor::TYPE) as $extender) {
/** @var \Narrowspark\Automatic\Common\Contract\ScriptExtender $extender */
if (isset($extenders[$extender::getType()])) {
throw new InvalidArgumentException(\sprintf('Script executor extender with the name [%s] already exists.', $extender::getType()));
}

if ($matches !== null && \version_compare($matches[0], '1.6.0') === -1) {
return \sprintf('Your version "%s" of Composer is too old; Please upgrade.', Composer::VERSION);
}
if (! \is_subclass_of($extender, ScriptExtenderContract::class)) {
throw new InvalidArgumentException(\sprintf('The class [%s] must implement the interface [%s].', $extender, ScriptExtenderContract::class));
}

return null;
/** @var \Narrowspark\Automatic\Common\Contract\ScriptExtender $extender */
$extenders[$extender::getType()] = $extender;
}

$lock->add(ScriptExecutor::TYPE, $extenders);
}
}

/**
* Write the package operation to the lock file.
* Run found skeleton generators.
*
* @param \Narrowspark\Automatic\Common\Contract\Package $package
* @throws \Exception
*
* @return void
*/
private function writePackageOperationToLock(PackageContract $package): void
private function runSkeletonGenerator(): void
{
/** @var \Narrowspark\Automatic\Lock $lock */
$lock = $this->container->get(Lock::class);

$lock->add(
self::LOCK_PACKAGES,
\array_merge(
(array) $lock->get(self::LOCK_PACKAGES),
[$package->getName() => $package->toArray()]
)
);
$lock->read();

if ($lock->has(SkeletonInstaller::LOCK_KEY) && $this->container->get(IOInterface::class)->isInteractive()) {
/** @var \Narrowspark\Automatic\SkeletonGenerator $skeletonGenerator */
$skeletonGenerator = $this->container->get(SkeletonGenerator::class);

$skeletonGenerator->run();

$skeletonGenerator->remove();
}

$lock->clear();
}

/**
* Check if automatic can be activated.
*
* @param \Composer\IO\IOInterface $io
*
* @return null|string
*/
private function getErrorMessage(IOInterface $io): ?string
{
// @codeCoverageIgnoreStart
if (! \extension_loaded('openssl')) {
return 'You must enable the openssl extension in your "php.ini" file.';
}

\preg_match('/\d+.\d+.\d+/m', Composer::VERSION, $matches);

if ($matches !== null && \version_compare($matches[0], '1.6.0') === -1) {
return \sprintf('Your version "%s" of Composer is too old; Please upgrade.', Composer::VERSION);
}
// @codeCoverageIgnoreEnd

// skip on no interactive mode
if (! $io->isInteractive()) {
return 'Composer running in a no interaction mode.';
}

return null;
}
}

Expand Down
Loading

0 comments on commit 2998d54

Please sign in to comment.