Completed
Push — 1.0 ( 499dc3...8b26f6 )
by David
13s queued 11s
created

NodeJsPlugin::onDeactivate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
namespace Mouf\NodeJsInstaller;
3
4
use Composer\Composer;
5
use Composer\Package\AliasPackage;
6
use Composer\Package\CompletePackage;
7
use Composer\Script\Event;
8
use Composer\EventDispatcher\EventSubscriberInterface;
9
use Composer\IO\IOInterface;
10
use Composer\Plugin\PluginInterface;
11
use Composer\Script\ScriptEvents;
12
use Composer\Util\Filesystem;
13
14
/**
15
 * This class is the entry point for the NodeJs plugin.
16
 *
17
 *
18
 * @author David Négrier
19
 */
20
class NodeJsPlugin implements PluginInterface, EventSubscriberInterface
21
{
22
    const NODEJS_TARGET_DIR = 'vendor/nodejs/nodejs';
23
    const DOWNLOAD_NODEJS_EVENT = 'download-nodejs';
24
25
    /**
26
     * @var Composer
27
     */
28
    protected $composer;
29
30
    /**
31
     * @var IOInterface
32
     */
33
    protected $io;
34
35
    public function activate(Composer $composer, IOInterface $io)
36
    {
37
        $this->composer = $composer;
38
        $this->io = $io;
39
    }
40
41
    public function deactivate(Composer $composer, IOInterface $io)
0 ignored issues
show
Unused Code introduced by
The parameter $io is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
42
    {
43
        $binDir = $composer->getConfig()->get('bin-dir');
44
        $this->onDeactivate($binDir);
45
    }
46
47
    public function uninstall(Composer $composer, IOInterface $io)
0 ignored issues
show
Unused Code introduced by
The parameter $io is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
48
    {
49
        $binDir = $composer->getConfig()->get('bin-dir');
50
        $targetDir = self::NODEJS_TARGET_DIR;
51
        $this->onUninstall($binDir, $targetDir);
52
    }
53
54
    /**
55
     * Let's register the harmony dependencies update events.
56
     *
57
     * @return array
58
     */
59
    public static function getSubscribedEvents()
60
    {
61
        return array(
62
            ScriptEvents::POST_INSTALL_CMD => array(
63
                array('onPostUpdateInstall', 1),
64
            ),
65
            ScriptEvents::POST_UPDATE_CMD => array(
66
                array('onPostUpdateInstall', 1),
67
            ),
68
            self::DOWNLOAD_NODEJS_EVENT => array(
69
                array('onPostUpdateInstall', 1)
70
            )
71
        );
72
    }
73
74
    /**
75
     * Script callback; Acted on after install or update.
76
     */
77
    public function onPostUpdateInstall(Event $event)
78
    {
79
        $settings = array(
80
            'targetDir' => self::NODEJS_TARGET_DIR,
81
            'forceLocal' => false,
82
            'includeBinInPath' => false,
83
        );
84
85
        $extra = $event->getComposer()->getPackage()->getExtra();
86
87
        if (isset($extra['mouf']['nodejs'])) {
88
            $rootSettings = $extra['mouf']['nodejs'];
89
            $settings = array_merge($settings, $rootSettings);
90
            $settings['targetDir'] = trim($settings['targetDir'], '/\\');
91
        }
92
93
        $binDir = $event->getComposer()->getConfig()->get('bin-dir');
94
95
        if (!class_exists(__NAMESPACE__.'\\NodeJsVersionMatcher')) {
96
            //The package is being uninstalled
97
            $this->onUninstall($binDir, $settings['targetDir']);
98
99
            return;
100
        }
101
102
        $nodeJsVersionMatcher = new NodeJsVersionMatcher();
103
104
        $versionConstraint = $this->getMergedVersionConstraint();
105
106
        $this->verboseLog("<info>NodeJS installer:</info>");
107
        $this->verboseLog(" - Requested version: ".$versionConstraint);
108
109
        $nodeJsInstaller = new NodeJsInstaller($this->io, $this->composer);
110
111
        $isLocal = false;
112
113
        if ($settings['forceLocal']) {
114
            $this->verboseLog(" - Forcing local NodeJS install.");
115
            $this->installLocalVersion($binDir, $nodeJsInstaller, $versionConstraint, $settings['targetDir']);
116
            $isLocal = true;
117
        } else {
118
            $globalVersion = $nodeJsInstaller->getNodeJsGlobalInstallVersion();
119
120
            if ($globalVersion !== null) {
121
                $this->verboseLog(" - Global NodeJS install found: v".$globalVersion);
122
                $npmPath = $nodeJsInstaller->getGlobalInstallPath('npm');
123
124
                if (!$npmPath) {
125
                    $this->verboseLog(" - No NPM install found");
126
                    $this->installLocalVersion($binDir, $nodeJsInstaller, $versionConstraint, $settings['targetDir']);
127
                    $isLocal = true;
128
                } elseif (!$nodeJsVersionMatcher->isVersionMatching($globalVersion, $versionConstraint)) {
129
                    $this->installLocalVersion($binDir, $nodeJsInstaller, $versionConstraint, $settings['targetDir']);
130
                    $isLocal = true;
131
                } else {
132
                    $this->verboseLog(" - Global NodeJS install matches constraint ".$versionConstraint);
133
                }
134
            } else {
135
                $this->verboseLog(" - No global NodeJS install found");
136
                $this->installLocalVersion($binDir, $nodeJsInstaller, $versionConstraint, $settings['targetDir']);
137
                $isLocal = true;
138
            }
139
        }
140
141
        // Now, let's create the bin scripts that start node and NPM
142
        $nodeJsInstaller->createBinScripts($binDir, $settings['targetDir'], $isLocal);
143
144
        // Finally, let's register vendor/bin in the PATH.
145
        if ($settings['includeBinInPath']) {
146
            $nodeJsInstaller->registerPath($binDir);
147
        }
148
    }
149
150
    /**
151
     * Writes message only in verbose mode.
152
     * @param string $message
153
     */
154
    private function verboseLog($message)
155
    {
156
        if ($this->io->isVerbose()) {
157
            $this->io->write($message);
158
        }
159
    }
160
161
    /**
162
     * Checks local NodeJS version, performs install if needed.
163
     *
164
     * @param  string                   $binDir
165
     * @param  NodeJsInstaller          $nodeJsInstaller
166
     * @param  string                   $versionConstraint
167
     * @param  string                   $targetDir
168
     * @throws NodeJsInstallerException
169
     */
170
    private function installLocalVersion($binDir, NodeJsInstaller $nodeJsInstaller, $versionConstraint, $targetDir)
171
    {
172
        $nodeJsVersionMatcher = new NodeJsVersionMatcher();
173
174
        $localVersion = $nodeJsInstaller->getNodeJsLocalInstallVersion($binDir);
175
        if ($localVersion !== null) {
176
            $this->verboseLog(" - Local NodeJS install found: v".$localVersion);
177
178
            if (!$nodeJsVersionMatcher->isVersionMatching($localVersion, $versionConstraint)) {
179
                $this->installBestPossibleLocalVersion($nodeJsInstaller, $versionConstraint, $targetDir);
180
            } else {
181
                // Question: should we update to the latest version? Should we have a nodejs.lock file???
182
                $this->verboseLog(" - Local NodeJS install matches constraint ".$versionConstraint);
183
            }
184
        } else {
185
            $this->verboseLog(" - No local NodeJS install found");
186
            $this->installBestPossibleLocalVersion($nodeJsInstaller, $versionConstraint, $targetDir);
187
        }
188
    }
189
190
    /**
191
     * Installs locally the best possible NodeJS version matching $versionConstraint
192
     *
193
     * @param  NodeJsInstaller          $nodeJsInstaller
194
     * @param  string                   $versionConstraint
195
     * @param  string                   $targetDir
196
     * @throws NodeJsInstallerException
197
     */
198
    private function installBestPossibleLocalVersion(NodeJsInstaller $nodeJsInstaller, $versionConstraint, $targetDir)
199
    {
200
        $nodeJsVersionsLister = new NodeJsVersionsLister($this->io, $this->composer);
201
        $allNodeJsVersions = $nodeJsVersionsLister->getList();
202
203
        $nodeJsVersionMatcher = new NodeJsVersionMatcher();
204
        $bestPossibleVersion = $nodeJsVersionMatcher->findBestMatchingVersion($allNodeJsVersions, $versionConstraint);
205
206
        if ($bestPossibleVersion === null) {
207
            throw new NodeJsInstallerNodeVersionException("No NodeJS version could be found for constraint '".$versionConstraint."'");
208
        }
209
210
        $nodeJsInstaller->install($bestPossibleVersion, $targetDir);
211
    }
212
213
    /**
214
     * Gets the version constraint from all included packages and merges it into one constraint.
215
     */
216
    private function getMergedVersionConstraint()
217
    {
218
        $packagesList = $this->composer->getRepositoryManager()->getLocalRepository()
219
            ->getCanonicalPackages();
220
        $packagesList[] = $this->composer->getPackage();
221
222
        $versions = array();
223
224
        foreach ($packagesList as $package) {
225
            if ($package instanceof AliasPackage) {
226
                $package = $package->getAliasOf();
227
            }
228
            if ($package instanceof CompletePackage) {
229
                $extra = $package->getExtra();
230
                if (isset($extra['mouf']['nodejs']['version'])) {
231
                    $versions[] = $extra['mouf']['nodejs']['version'];
232
                }
233
            }
234
        }
235
236
        if (!empty($versions)) {
237
            return implode(", ", $versions);
238
        } else {
239
            return "*";
240
        }
241
    }
242
243
    /**
244
     * Uninstalls NodeJS.
245
     * Note: other classes cannot be loaded here since the package has already been removed.
246
     */
247
    private function onUninstall($binDir, $targetDir)
248
    {
249
        $fileSystem = new Filesystem();
250
251
        if (file_exists($targetDir)) {
252
            $this->verboseLog("Removing NodeJS local install");
253
254
            // Let's remove target directory
255
            $fileSystem->remove($targetDir);
256
257
            $vendorNodeDir = dirname($targetDir);
258
259
            if ($fileSystem->isDirEmpty($vendorNodeDir)) {
260
                $fileSystem->remove($vendorNodeDir);
261
            }
262
        }
263
264
        $this->onDeactivate($binDir);
265
    }
266
267
    /**
268
     * Deactivates NodeJS links.
269
     */
270
    private function onDeactivate($binDir)
271
    {
272
        $fileSystem = new Filesystem();
273
274
        // Remove the links.
275
        $this->verboseLog("Removing NodeJS and NPM links from Composer bin directory");
276
        foreach (array("node", "npm", "node.bat", "npm.bat") as $file) {
277
            $realFile = $binDir.DIRECTORY_SEPARATOR.$file;
278
            if (file_exists($realFile)) {
279
                $fileSystem->remove($realFile);
280
            }
281
        }
282
    }
283
284
}
285