Completed
Push — master ( 0f22ff...eb2b2e )
by Bernhard
08:50
created

PuliPlugin::verifyPuliVersion()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 32
Code Lines 21

Duplication

Lines 19
Ratio 59.38 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 19
loc 32
rs 8.5806
cc 4
eloc 21
nc 4
nop 0
1
<?php
2
3
/*
4
 * This file is part of the puli/composer-plugin package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\ComposerPlugin;
13
14
use Composer\Composer;
15
use Composer\EventDispatcher\EventSubscriberInterface;
16
use Composer\IO\IOInterface;
17
use Composer\Package\AliasPackage;
18
use Composer\Package\PackageInterface;
19
use Composer\Plugin\PluginInterface;
20
use Composer\Script\CommandEvent;
21
use Composer\Script\Event;
22
use Composer\Script\ScriptEvents;
23
use Exception;
24
use RuntimeException;
25
use Webmozart\PathUtil\Path;
26
27
/**
28
 * A Puli plugin for Composer.
29
 *
30
 * The plugin updates the Puli package repository based on the Composer
31
 * packages whenever `composer install` or `composer update` is executed.
32
 *
33
 * @since  1.0
34
 *
35
 * @author Bernhard Schussek <[email protected]>
36
 */
37
class PuliPlugin implements PluginInterface, EventSubscriberInterface
38
{
39
    /**
40
     * The minimum version of the Puli CLI.
41
     */
42
    const MIN_CLI_VERSION = '1.0.0-beta9';
43
44
    /**
45
     * The maximum version of the Puli CLI.
46
     */
47
    const MAX_CLI_VERSION = '1.999.99999';
48
49
    /**
50
     * The name of the installer.
51
     */
52
    const INSTALLER_NAME = 'composer';
53
54
    /**
55
     * @var bool
56
     */
57
    private $initialized = false;
58
59
    /**
60
     * @var PuliRunner
61
     */
62
    private $puliRunner;
63
64
    /**
65
     * @var string
66
     */
67
    private $rootDir;
68
69
    /**
70
     * @var bool
71
     */
72
    private $runPostInstall = true;
73
74
    /**
75
     * @var bool
76
     */
77
    private $runPostAutoloadDump = true;
78
79
    /**
80
     * {@inheritdoc}
81
     */
82
    public static function getSubscribedEvents()
83
    {
84
        return array(
85
            ScriptEvents::POST_INSTALL_CMD => 'postInstall',
86
            ScriptEvents::POST_UPDATE_CMD => 'postInstall',
87
            ScriptEvents::POST_AUTOLOAD_DUMP => 'postAutoloadDump',
88
        );
89
    }
90
91
    public function __construct(PuliRunner $puliRunner = null)
92
    {
93
        $this->puliRunner = $puliRunner;
94
        $this->rootDir = Path::normalize(getcwd());
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100
    public function activate(Composer $composer, IOInterface $io)
101
    {
102
        $composer->getEventDispatcher()->addSubscriber($this);
103
    }
104
105
    public function postAutoloadDump(Event $event)
106
    {
107
        // Plugin has been uninstalled
108
        if (!file_exists(__FILE__)) {
109
            return;
110
        }
111
112
        if (!$this->initialized) {
113
            $this->initialize($event->getComposer(), $event->getIO());
114
        }
115
116
        // This method is called twice. Run it only once.
117
        if (!$this->runPostAutoloadDump) {
118
            return;
119
        }
120
121
        $this->runPostAutoloadDump = false;
122
123
        $io = $event->getIO();
124
125
        $compConfig = $event->getComposer()->getConfig();
126
        $vendorDir = $compConfig->get('vendor-dir');
127
128
        // On TravisCI, $vendorDir is a relative path. Probably an old Composer
129
        // build or something. Usually, $vendorDir should be absolute already.
130
        $vendorDir = Path::makeAbsolute($vendorDir, $this->rootDir);
131
132
        $autoloadFile = $vendorDir.'/autoload.php';
133
        $classMapFile = $vendorDir.'/composer/autoload_classmap.php';
134
135
        try {
136
            $factoryClass = $this->getConfigKey('factory.in.class');
137
            $factoryFile = $this->getConfigKey('factory.in.file');
138
        } catch (PuliRunnerException $e) {
139
            $this->printWarning($io, 'Could not load Puli configuration', $e);
140
141
            return;
142
        }
143
144
        $factoryFile = Path::makeAbsolute($factoryFile, $this->rootDir);
0 ignored issues
show
Bug introduced by
It seems like $factoryFile can also be of type boolean or null; however, Webmozart\PathUtil\Path::makeAbsolute() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
145
146
        $this->insertFactoryClassConstant($io, $autoloadFile, $factoryClass);
147
        $this->insertFactoryClassMap($io, $classMapFile, $vendorDir, $factoryClass, $factoryFile);
148
        $this->setBootstrapFile($io, $autoloadFile);
149
    }
150
151
    /**
152
     * Updates the Puli repository after Composer installations/updates.
153
     *
154
     * @param CommandEvent $event The Composer event.
155
     */
156
    public function postInstall(CommandEvent $event)
157
    {
158
        // Plugin has been uninstalled
159
        if (!file_exists(__FILE__)) {
160
            return;
161
        }
162
163
        if (!$this->initialized) {
164
            $this->initialize($event->getComposer(), $event->getIO());
165
        }
166
167
        // This method is called twice. Run it only once.
168
        if (!$this->runPostInstall) {
169
            return;
170
        }
171
172
        $this->runPostInstall = false;
173
174
        $io = $event->getIO();
175
176
        $io->write('<info>Looking for updated Puli packages</info>');
177
178
        $rootPackage = $event->getComposer()->getPackage();
179
        $composerPackages = $this->loadComposerPackages($event->getComposer());
180
        $prodPackageNames = $this->filterProdPackageNames($composerPackages, $rootPackage);
181
        $env = $event->isDevMode() ? PuliPackage::ENV_DEV : PuliPackage::ENV_PROD;
182
183
        try {
184
            $puliPackages = $this->loadPuliPackages();
185
        } catch (PuliRunnerException $e) {
186
            $this->printWarning($io, 'Could not load Puli packages', $e);
187
188
            return;
189
        }
190
191
        // Don't remove non-existing packages in production environment
192
        // Removed packages could be dev dependencies (i.e. "require-dev"
193
        // of the root package or "require" of another dev dependency), and
194
        // we can't find out whether they are since Composer doesn't load them
195
        if (PuliPackage::ENV_PROD !== $env) {
196
            $this->removeRemovedPackages($composerPackages, $puliPackages, $io);
197
        }
198
199
        $this->installNewPackages($composerPackages, $prodPackageNames, $puliPackages, $io, $event->getComposer());
200
201
        // Don't print warnings for non-existing packages in production
202
        if (PuliPackage::ENV_PROD !== $env) {
203
            $this->checkForNotFoundErrors($puliPackages, $io);
204
        }
205
206
        $this->checkForNotLoadableErrors($puliPackages, $io);
207
        $this->adoptComposerName($puliPackages, $io, $event->getComposer());
208
        $this->buildPuli($io);
209
    }
210
211
    /**
212
     * @param Composer    $composer
213
     * @param IOInterface $io
214
     */
215
    private function initialize(Composer $composer, IOInterface $io)
216
    {
217
        // This method must be run after all packages are installed, otherwise
218
        // it could be that the puli/cli is not yet installed and hence the
219
        // CLI is not available
220
221
        // Previously, this was called in activate(), which is called
222
        // immediately after installing the plugin, but potentially before
223
        // installing the CLI
224
225
        $this->initialized = true;
226
227
        // Keep the manually set runner
228
        if (!$this->puliRunner) {
229
            try {
230
                // Add Composer's bin directory in case the "puli" executable is
231
                // installed with Composer
232
                $this->puliRunner = new PuliRunner($composer->getConfig()->get('bin-dir'));
233
            } catch (RuntimeException $e) {
234
                $this->printWarning($io, 'Plugin initialization failed', $e);
235
                $this->runPostAutoloadDump = false;
236
                $this->runPostInstall = false;
237
            }
238
        }
239
240
        // Use the runner to verify if Puli has the right version
241
        try {
242
            $this->verifyPuliVersion();
243
        } catch (RuntimeException $e) {
244
            $this->printWarning($io, 'Version check failed', $e);
245
            $this->runPostAutoloadDump = false;
246
            $this->runPostInstall = false;
247
        }
248
    }
249
250
    /**
251
     * @param PackageInterface[] $composerPackages
252
     * @param bool[]             $prodPackageNames
253
     * @param PuliPackage[]      $puliPackages
254
     * @param IOInterface        $io
255
     * @param Composer           $composer
256
     */
257
    private function installNewPackages(array $composerPackages, array $prodPackageNames, array &$puliPackages, IOInterface $io, Composer $composer)
258
    {
259
        $installationManager = $composer->getInstallationManager();
260
261
        foreach ($composerPackages as $packageName => $package) {
262
            if ($package instanceof AliasPackage) {
263
                $package = $package->getAliasOf();
264
            }
265
266
            // We need to normalize the system-dependent paths returned by Composer
267
            $installPath = Path::normalize($installationManager->getInstallPath($package));
268
            $env = isset($prodPackageNames[$packageName]) ? PuliPackage::ENV_PROD : PuliPackage::ENV_DEV;
269
270
            // Skip meta packages
271
            if ('' === $installPath) {
272
                continue;
273
            }
274
275
            if (isset($puliPackages[$packageName])) {
276
                $puliPackage = $puliPackages[$packageName];
277
278
                // Only proceed if the install path or environment has changed
279
                if ($installPath === $puliPackage->getInstallPath() && $env === $puliPackage->getEnvironment()) {
280
                    continue;
281
                }
282
283
                // Only remove packages installed by Composer
284
                if (self::INSTALLER_NAME === $puliPackage->getInstallerName()) {
285
                    $io->write(sprintf(
286
                        'Reinstalling <info>%s</info> (<comment>%s</comment>) in <comment>%s</comment>',
287
                        $packageName,
288
                        Path::makeRelative($installPath, $this->rootDir),
289
                        $env
290
                    ));
291
292
                    try {
293
                        $this->removePackage($packageName);
294
                    } catch (PuliRunnerException $e) {
295
                        $this->printPackageWarning($io, 'Could not remove package "%s" (at ./%s)', $packageName, $installPath, $e);
296
297
                        continue;
298
                    }
299
                }
300
            } else {
301
                $io->write(sprintf(
302
                    'Installing <info>%s</info> (<comment>%s</comment>) in <comment>%s</comment>',
303
                    $packageName,
304
                    Path::makeRelative($installPath, $this->rootDir),
305
                    $env
306
                ));
307
            }
308
309
            try {
310
                $this->installPackage($installPath, $packageName, $env);
311
            } catch (PuliRunnerException $e) {
312
                $this->printPackageWarning($io, 'Could not install package "%s" (at ./%s)', $packageName, $installPath, $e);
313
314
                continue;
315
            }
316
317
            $puliPackages[$packageName] = new PuliPackage(
318
                $packageName,
319
                self::INSTALLER_NAME,
320
                $installPath,
321
                PuliPackage::STATE_ENABLED,
322
                $env
323
            );
324
        }
325
    }
326
327
    /**
328
     * @param PackageInterface[] $composerPackages
329
     * @param PuliPackage[]      $puliPackages
330
     * @param IOInterface        $io
331
     */
332
    private function removeRemovedPackages(array $composerPackages, array &$puliPackages, IOInterface $io)
333
    {
334
        /** @var PuliPackage[] $notFoundPackages */
335
        $notFoundPackages = array_filter($puliPackages, function (PuliPackage $package) {
336
            return PuliPackage::STATE_NOT_FOUND === $package->getState()
337
                && PuliPlugin::INSTALLER_NAME === $package->getInstallerName();
338
        });
339
340
        foreach ($notFoundPackages as $packageName => $package) {
341
            // Check whether package was only moved
342
            if (isset($composerPackages[$packageName])) {
343
                continue;
344
            }
345
346
            $io->write(sprintf(
347
                'Removing <info>%s</info> (<comment>%s</comment>)',
348
                $packageName,
349
                Path::makeRelative($package->getInstallPath(), $this->rootDir)
350
            ));
351
352
            try {
353
                $this->removePackage($packageName);
354
            } catch (PuliRunnerException $e) {
355
                $this->printPackageWarning($io, 'Could not remove package "%s" (at ./%s)', $packageName, $package->getInstallPath(), $e);
356
357
                continue;
358
            }
359
360
            unset($puliPackages[$packageName]);
361
        }
362
    }
363
364 View Code Duplication
    private function checkForNotFoundErrors(array $puliPackages, IOInterface $io)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
365
    {
366
        /** @var PuliPackage[] $notFoundPackages */
367
        $notFoundPackages = array_filter($puliPackages,
368
            function (PuliPackage $package) {
369
                return PuliPackage::STATE_NOT_FOUND === $package->getState()
370
                && PuliPlugin::INSTALLER_NAME === $package->getInstallerName();
371
            });
372
373
        foreach ($notFoundPackages as $package) {
374
            $this->printPackageWarning(
375
                $io,
376
                'The package "%s" (at ./%s) could not be found',
377
                $package->getName(),
378
                $package->getInstallPath()
379
            );
380
        }
381
    }
382
383 View Code Duplication
    private function checkForNotLoadableErrors(array $puliPackages, IOInterface $io)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
384
    {
385
        /** @var PuliPackage[] $notLoadablePackages */
386
        $notLoadablePackages = array_filter($puliPackages, function (PuliPackage $package) {
387
            return PuliPackage::STATE_NOT_LOADABLE === $package->getState()
388
                && PuliPlugin::INSTALLER_NAME === $package->getInstallerName();
389
        });
390
391
        foreach ($notLoadablePackages as $package) {
392
            $this->printPackageWarning(
393
                $io,
394
                'The package "%s" (at ./%s) could not be loaded',
395
                $package->getName(),
396
                $package->getInstallPath()
397
            );
398
        }
399
    }
400
401
    private function adoptComposerName(array $puliPackages, IOInterface $io, Composer $composer)
402
    {
403
        $rootDir = $this->rootDir;
404
405
        /** @var PuliPackage[] $rootPackages */
406
        $rootPackages = array_filter($puliPackages, function (PuliPackage $package) use ($rootDir) {
407
            return !$package->getInstallerName() && $rootDir === $package->getInstallPath();
408
        });
409
410
        if (0 === count($rootPackages)) {
411
            // This should never happen
412
            $this->printWarning($io, 'No root package could be found');
413
414
            return;
415
        }
416
417
        if (count($rootPackages) > 1) {
418
            // This should never happen
419
            $this->printWarning($io, 'More than one root package was found');
420
421
            return;
422
        }
423
424
        /** @var PuliPackage $rootPackage */
425
        $rootPackage = reset($rootPackages);
426
        $name = $rootPackage->getName();
427
        $newName = $composer->getPackage()->getName();
428
429
        // Rename the root package after changing the name in composer.json
430
        if ($name !== $newName) {
431
            try {
432
                $this->renamePackage($name, $newName);
433
            } catch (PuliRunnerException $e) {
434
                $this->printWarning($io, sprintf(
435
                    'Could not rename root package to "%s"',
436
                    $newName
437
                ), $e);
438
            }
439
        }
440
    }
441
442
    private function insertFactoryClassConstant(IOInterface $io, $autoloadFile, $factoryClass)
443
    {
444
        if (!file_exists($autoloadFile)) {
445
            throw new PuliPluginException(sprintf(
446
                'Could not adjust autoloader: The file %s was not found.',
447
                $autoloadFile
448
            ));
449
        }
450
451
        $io->write('<info>Generating PULI_FACTORY_CLASS constant</info>');
452
453
        $contents = file_get_contents($autoloadFile);
454
        $escFactoryClass = var_export($factoryClass, true);
455
        $constant = "if (!defined('PULI_FACTORY_CLASS')) {\n";
456
        $constant .= sprintf("    define('PULI_FACTORY_CLASS', %s);\n", $escFactoryClass);
457
        $constant .= "}\n\n";
458
459
        // Regex modifiers:
460
        // "m": \s matches newlines
461
        // "D": $ matches at EOF only
462
        // Translation: insert before the last "return" in the file
463
        $contents = preg_replace('/\n(?=return [^;]+;\s*$)/mD', "\n".$constant,
464
            $contents);
465
466
        file_put_contents($autoloadFile, $contents);
467
    }
468
469
    private function insertFactoryClassMap(IOInterface $io, $classMapFile, $vendorDir, $factoryClass, $factoryFile)
470
    {
471
        if (!file_exists($classMapFile)) {
472
            throw new PuliPluginException(sprintf(
473
                'Could not adjust autoloader: The file %s was not found.',
474
                $classMapFile
475
            ));
476
        }
477
478
        $io->write(sprintf('<info>Registering %s with the class-map autoloader</info>', $factoryClass));
479
480
        $relFactoryFile = Path::makeRelative($factoryFile, $vendorDir);
481
        $escFactoryClass = var_export($factoryClass, true);
482
        $escFactoryFile = var_export('/'.$relFactoryFile, true);
483
        $classMap = sprintf("\n    %s => \$vendorDir . %s,", $escFactoryClass, $escFactoryFile);
484
485
        $contents = file_get_contents($classMapFile);
486
487
        // Regex modifiers:
488
        // "m": \s matches newlines
489
        // "D": $ matches at EOF only
490
        // Translation: insert before the last ");" in the file
491
        $contents = preg_replace('/\n(?=\);\s*$)/mD', "\n".$classMap, $contents);
492
493
        file_put_contents($classMapFile, $contents);
494
    }
495
496
    private function setBootstrapFile(IOInterface $io, $autoloadFile)
497
    {
498
        $bootstrapFile = $this->getConfigKey('bootstrap-file');
499
500
        // Don't change user-defined bootstrap files
501
        if (!empty($bootstrapFile)) {
502
            return;
503
        }
504
505
        $relAutoloadFile = Path::makeRelative($autoloadFile, $this->rootDir);
506
507
        $io->write(sprintf('<info>Setting "bootstrap-file" to "%s"</info>', $relAutoloadFile));
508
509
        $this->setConfigKey('bootstrap-file', $relAutoloadFile);
510
    }
511
512
    /**
513
     * Loads Composer's currently installed packages.
514
     *
515
     * @param Composer $composer The Composer instance.
516
     *
517
     * @return PackageInterface[] The installed packages indexed by their names.
518
     */
519
    private function loadComposerPackages(Composer $composer)
520
    {
521
        $repository = $composer->getRepositoryManager()->getLocalRepository();
522
        $packages = array();
523
524
        foreach ($repository->getPackages() as $package) {
525
            /* @var PackageInterface $package */
526
            $packages[$package->getName()] = $package;
527
        }
528
529
        return $packages;
530
    }
531
532
    private function getConfigKey($key)
533
    {
534
        $value = trim($this->puliRunner->run('config %key% --parsed', array(
535
            'key' => $key,
536
        )));
537
538
        switch ($value) {
539
            case 'null':
540
                return null;
541
            case 'true':
542
                return true;
543
            case 'false':
544
                return false;
545
            default:
546
                return $value;
547
        }
548
    }
549
550
    private function setConfigKey($key, $value)
551
    {
552
        $this->puliRunner->run('config %key% %value%', array(
553
            'key' => $key,
554
            'value' => $value,
555
        ));
556
    }
557
558
    /**
559
     * @return PuliPackage[]
560
     */
561
    private function loadPuliPackages()
562
    {
563
        $packages = array();
564
565
        $output = $this->puliRunner->run('package --list --format %format%', array(
566
            'format' => '%name%;%installer%;%install_path%;%state%;%env%',
567
        ));
568
569
        // PuliRunner replaces \r\n by \n for those Windows boxes
570
        foreach (explode("\n", $output) as $packageLine) {
571
            if (!$packageLine) {
572
                continue;
573
            }
574
575
            $packageParts = explode(';', $packageLine);
576
577
            $packages[$packageParts[0]] = new PuliPackage(
578
                $packageParts[0],
579
                $packageParts[1],
580
                $packageParts[2],
581
                $packageParts[3],
582
                $packageParts[4]
583
            );
584
        }
585
586
        return $packages;
587
    }
588
589
    private function installPackage($installPath, $packageName, $env)
590
    {
591
        $env = PuliPackage::ENV_DEV === $env ? ' --dev' : '';
592
593
        $this->puliRunner->run('package --install %path% %package_name% --installer %installer%'.$env, array(
594
            'path' => $installPath,
595
            'package_name' => $packageName,
596
            'installer' => self::INSTALLER_NAME,
597
        ));
598
    }
599
600
    private function removePackage($packageName)
601
    {
602
        $this->puliRunner->run('package --delete %package_name%', array(
603
            'package_name' => $packageName,
604
        ));
605
    }
606
607
    private function buildPuli(IOInterface $io)
608
    {
609
        $io->write('<info>Running "puli build"</info>');
610
611
        $this->puliRunner->run('build');
612
    }
613
614
    private function renamePackage($name, $newName)
615
    {
616
        $this->puliRunner->run('package --rename %old_name% %new_name%', array(
617
            'old_name' => $name,
618
            'new_name' => $newName,
619
        ));
620
    }
621
622
    /**
623
     * @param IOInterface    $io
624
     * @param                $message
625
     * @param Exception|null $exception
626
     */
627
    private function printWarning(IOInterface $io, $message, Exception $exception = null)
628
    {
629
        if (!$exception) {
630
            $reasonPhrase = '';
631
        } elseif ($io->isVerbose()) {
632
            $reasonPhrase = $exception instanceof PuliRunnerException
633
                ? $exception->getFullError()
634
                : $exception->getMessage()."\n\n".$exception->getTraceAsString();
635
        } else {
636
            $reasonPhrase = $exception instanceof PuliRunnerException
637
                ? $exception->getShortError()
638
                : $exception->getMessage();
639
        }
640
641
        $io->writeError(sprintf(
642
            '<warning>Warning: %s%s</warning>',
643
            $message,
644
            $reasonPhrase ? ': '.$reasonPhrase : '.'
645
        ));
646
    }
647
648
    private function printPackageWarning(IOInterface $io, $message, $packageName, $installPath, PuliRunnerException $exception = null)
649
    {
650
        $this->printWarning($io, sprintf(
651
            $message,
652
            $packageName,
653
            Path::makeRelative($installPath, $this->rootDir)
654
        ), $exception);
655
    }
656
657
    private function filterProdPackageNames(array $composerPackages, PackageInterface $package, array &$result = array())
658
    {
659
        // Resolve aliases
660
        if ($package instanceof AliasPackage) {
661
            $package = $package->getAliasOf();
662
        }
663
664
        // Package was processed already
665
        if (isset($result[$package->getName()])) {
666
            return $result;
667
        }
668
669
        $result[$package->getName()] = true;
670
671
        // Recursively filter package names
672
        foreach ($package->getRequires() as $packageName => $link) {
673
            if (isset($composerPackages[$packageName])) {
674
                $this->filterProdPackageNames($composerPackages, $composerPackages[$packageName], $result);
675
            }
676
        }
677
678
        return $result;
679
    }
680
681
    private function verifyPuliVersion()
682
    {
683
        $versionString = $this->puliRunner->run('-V');
684
685
        if (!preg_match('~\d+\.\d+\.\d+(-\w+)?~', $versionString, $matches)) {
686
            throw new RuntimeException(sprintf(
687
                'Could not determine Puli version. "puli -V" returned: %s',
688
                $versionString
689
            ));
690
        }
691
692 View Code Duplication
        if (version_compare($matches[0], self::MIN_CLI_VERSION, '<')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
693
            throw new RuntimeException(sprintf(
694
                'Found an unsupported version of the Puli CLI: %s. Please '.
695
                'upgrade to version %s or higher. You can also install the '.
696
                'puli/cli dependency at version %s in your project.',
697
                $matches[0],
698
                self::MIN_CLI_VERSION,
699
                self::MIN_CLI_VERSION
700
            ));
701
        }
702
703 View Code Duplication
        if (version_compare($matches[0], self::MAX_CLI_VERSION, '>')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
704
            throw new RuntimeException(sprintf(
705
                'Found an unsupported version of the Puli CLI: %s. Please '.
706
                'downgrade to a lower version than %s. You can also install '.
707
                'the puli/cli dependency in your project.',
708
                $matches[0],
709
                self::MAX_CLI_VERSION
710
            ));
711
        }
712
    }
713
}
714