Issues (791)

src/cli/extend/plugins.js (19 issues)

1
import {spawn} from 'child_process'
2
import {Promise} from 'bluebird'
3
import path from 'path'
4
import fse from 'fs-extra'
5
import clc from 'cli-color'
6
import which from 'which'
7
const npm = which.sync('npm')
8
9
import {
10
  coreUtils,
11
  config
12
} from '../'
13
14
let singleton = Symbol()
15
let singletonEnforcer = Symbol()
16
17
class Plugins {
18
19
  constructor(enforcer) {
20
    if(enforcer != singletonEnforcer) throw 'Cannot construct Plugins singleton'
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
21
    this.pluginsDir = path.join(config.root, 'node_modules') + path.sep
22
    this.scriptsDir = path.join(config.root, 'scripts') + path.sep
23
    this._plugins = []
24
    this.fn = []
25
    
26
    // Plugins
27
    if(config.plugins && Array.isArray(config.plugins)){
28
      Array.prototype.forEach.call(config.plugins, (pluginEntry) => {
29
        const plugin = this.getPluginConfig(this.pluginsDir, pluginEntry)
30
        this._plugins.push(plugin)
31
      })
32
    }
33
34
    // Scripts
35
    try {
36
      var directoryScripts = fse.lstatSync(this.scriptsDir)
37
      if (directoryScripts.isDirectory()) {
38
        this._scripts = coreUtils.file.getFoldersSync(this.scriptsDir, false)
39
        Array.prototype.forEach.call(this._scripts, (scriptEntry) => {
40
          const name = scriptEntry.path.replace(this.scriptsDir, '')
41
          const script = this.getPluginConfig(this.scriptsDir, name)
42
          this._plugins.push(script)
43
        })
44
      }
45
    } catch (e) {
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
46
      
47
    }
48
  }
49
50
  static get instance() {
51
    if(!this[singleton]) {
52
      this[singleton] = new Plugins(singletonEnforcer)
53
    }
54
    return this[singleton]
55
  }
56
57
  getPluginConfig(dir, entry){
58
    // remove npm version if any
59
    let pluginId = entry.split('@')[0]
60
61
    // remove github version if any
62
    pluginId = pluginId.split('#')[0]
63
64
    // remove github path if any
65
    pluginId = path.basename(pluginId)
66
67
    let plugHook = path.join(dir, pluginId, config.hooks.url, 'hooks.js')
68
    let plugPartials = path.join(dir, pluginId, config.pluginsPartials)
69
    let plugTemplates = path.join(dir, pluginId, config.templates.url)
70
    let plugProcess = path.join(dir, pluginId, 'process')
71
    let plugRoutes = path.join(dir, pluginId, 'routes')
72
    let plugin = {
73
      hooks : null,
74
      partials : null,
75
      templates : null,
76
      process : null,
77
      routes : null
78
    }
79
80
    try {
81
      var fileHook = fse.lstatSync(plugHook)
82
      if (fileHook.isFile()) {
83
        try {
84
          var h = require(plugHook)
85
        } catch(e){
86
          console.log(e.stack)
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
87
          console.log(
88
            clc.green('[ Hint ]'),
89
            'It seems that you don\'t have the npm module babel-preset-es2015 installed on your project'
90
          )
91
        }
92
        plugin.hooks = h.default
93
      }
94
    }catch(e) {
95
      plugin.hooks = null
96
    }
97
    
98
    try {
99
      var directoryPartials = fse.lstatSync(plugPartials)
100
      if (directoryPartials.isDirectory()) {
101
        plugin.partials = plugPartials
102
      }
103
    }catch(e) {
104
      plugin.partials = null
105
    }
106
    
107
    try {
108
      var directoryTemplates = fse.lstatSync(plugTemplates)
109
      if (directoryTemplates.isDirectory()) {
110
        plugin.templates = plugTemplates
111
      }
112
    }catch(e) {
113
      plugin.templates = null
114
    }
115
116
    try {
117
      var directoryProcess = fse.lstatSync(plugProcess)
118
      if (directoryProcess.isDirectory()) {
119
        plugin.process = {}
120
        let processFiles = coreUtils.file.getFilesSync(plugProcess, false)
121
        Array.prototype.forEach.call(processFiles, (processFile) => {
122
          plugin.process[path.basename(processFile, '.js')] = processFile
123
        })
124
      }
125
    }catch(e) {
126
      plugin.process = null
127
    }
128
129
    try {
130
      var directoryRoute = fse.lstatSync(plugRoutes)
131
      if (directoryRoute.isDirectory()) {
132
        plugin.routes = {}
133
134
        var gets = path.join(plugRoutes, 'get')
135
        try {
136
          var directoryGets = fse.lstatSync(gets)
137
          if (directoryGets.isDirectory()) {
138
            let routesGet = []
139
            let routePaths = coreUtils.file.getFilesSync(gets, false)
140
            Array.prototype.forEach.call(routePaths, (route) => {
141
              let pathUrl = `/abe/plugin/${pluginId}/${path.basename(route, '.js')}*`
142
              let routeObject = {'path':route, 'routePath':pathUrl}
143
              routesGet.push(routeObject)
144
            })
145
            plugin.routes.get = routesGet
146
          }
147
        }catch(e) {
148
          plugin.routes.get = null
149
        }
150
        try {
151
          let posts = path.join(plugRoutes, 'post')
152
          let directoryPosts = fse.lstatSync(gets)
153
          if (directoryPosts.isDirectory()) {
154
            let routesPost = []
155
            let routePaths = coreUtils.file.getFilesSync(posts, false)
156
            Array.prototype.forEach.call(routePaths, (route) => {
157
              let pathUrl = `/abe/plugin/${pluginId}/${path.basename(route, '.js')}*`
158
              let routeObject = {'path':route, 'routePath' : pathUrl}
159
              routesPost.push(routeObject)
160
            })
161
            plugin.routes.post = routesPost
162
          }
163
        }catch(e) {
164
          plugin.routes.post = null
165
        }
166
      }
167
    }catch(e) {
168
      plugin.routes = null
169
    }
170
171
    return plugin
172
  }
173
174
  getProcess(fn) {
175
    var proc = null
176
    if(typeof this._plugins !== 'undefined' && this._plugins !== null) {
177
      Array.prototype.forEach.call(this._plugins, (plugin) => {
178
        if(typeof plugin.process !== 'undefined' && plugin.process !== null
179
          && typeof plugin.process[fn] !== 'undefined' && plugin.process[fn] !== null) {
180
          proc = plugin.process[fn]
181
        }
182
      })
183
    }
184
185
    return proc
186
  }
187
188
  hooks() {
189
    if(arguments.length > 0) {
190
      var args = [].slice.call(arguments)
191
      var fn = args.shift()
192
193
      if(this._plugins != null) {
194
        Array.prototype.forEach.call(this._plugins, (plugin) => {
195
          if(plugin.hooks != null && plugin.hooks[fn] != null) {
196
            args[0] = plugin.hooks[fn].apply(this, args)
197
          }
198
        })
199
      }
200
    } else {
201
      args = ['']
202
    }
203
204
    return args[0]
205
  }
206
207
  getPartials() {
208
    var partials = []
209
    Array.prototype.forEach.call(this._plugins, (plugin) => {
210
      if(typeof plugin.partials !== 'undefined' && plugin.partials !== null) {
211
        partials.push(plugin.partials)
212
      }
213
    })
214
215
    return partials
216
  }
217
218
  getRoutes() {
219
    var routes = []
220
    Array.prototype.forEach.call(this._plugins, (plugin) => {
221
      if(typeof plugin.routes !== 'undefined' && plugin.routes !== null) {
222
        routes = routes.concat(plugin.routes)
223
      }
224
    })
225
226
    return routes
227
  }
228
229
  removePlugin(plugin){
230
    let pluginName = plugin.split('@')[0]
231
    pluginName = pluginName.split('#')[0]
232
    if(config.localConfigExist()){
233
      let json = config.getLocalConfig()
234
      if(typeof json.plugins !== 'undefined' && json.plugins !== null) {
235
        Array.prototype.forEach.call(json.plugins, (plugged, index) => {
236
          if (pluginName === path.basename(plugged.split('@')[0].split('#')[0])) {
237
            json.plugins.splice(index, 1)
238
            config.save(json)
239
          }
240
        })
241
      }
242
    }
243
  }
244
245
  updatePlugin(plugin){
246
    let json = {}
247
    let createLocalConfig = true
248
    let pluginName = plugin.split('@')[0]
249
    pluginName = pluginName.split('#')[0]
250
251
    if(config.localConfigExist()){
252
      json = config.getLocalConfig()
253
      createLocalConfig = false
254
    }
255
256
    if(typeof json.plugins === 'undefined' || json.plugins === null) {
257
      json.plugins = [plugin]
258
    } else {
259
      var found = false
260
      Array.prototype.forEach.call(json.plugins, (plugged, index) => {
261
        if (pluginName === plugged.split('@')[0].split('#')[0]) {
262
          json.plugins[index] = plugin
263
          found = true
264
        }
265
      })
266
      if (!found) {
267
        json.plugins.push(plugin)
268
      }
269
    }
270
271
    if(createLocalConfig){
272
      console.log(
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
273
        clc.green('[ Hint ]'),
274
        'creating a local config abe.json with your plugin definition',
275
        clc.cyan.underline('https://github.com/AdFabConnect/abejs/blob/master/docs/abe-config.md')
276
      )
277
    }
278
279
    config.save(json)
280
  }
281
282
  uninstall(dir, plugin) {
283
    if(plugin !== null) {
284
      this.remove(dir, plugin)
285
      .then(function() {
286
287
        return 0
288
      })
0 ignored issues
show
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
289
    } else {
290
      console.log(clc.cyan('no plugin with this name found'))
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
291
292
      return 0
293
    }
294
  }
295
296
  install(dir, plugin = null) {
297
    if(plugin !== null) {
298
      this.add(dir, plugin)
299
      .then(function() {
300
301
        return 0
302
      })
0 ignored issues
show
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
303
    } else {
304
      if(config.plugins && Array.isArray(config.plugins)){
305
        var ps = []
306
        Array.prototype.forEach.call(config.plugins, (plugin) => {
307
          ps.push(this.add(dir, plugin))
308
        })
309
        
310
        Promise.all(ps)
311
        .then(function() {
312
313
          return 0 
314
        })
0 ignored issues
show
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
315
      } else {
316
        console.log(clc.cyan('no plugin found'))
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
317
318
        return 0
319
      }
320
    }
321
  }
322
323
  remove(dir, plugin) {
324
    var p = new Promise((resolve) => {
325
      try {
326
        console.log(clc.green('spawn'), clc.cyan('npm uninstall ' + plugin))
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
327
        // const npmUninstall = spawn('npm', ['uninstall', pluginDir]);
328
        const npmUninstall = spawn(npm, ['uninstall', '--save', plugin], { cwd: dir })
329
330
        npmUninstall.stdout.on('data', (data) => {
331
          console.log(''+data)
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
332
        })
333
334
        npmUninstall.stderr.on('data', (data) => {
335
          console.log(''+data)
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
336
        })
337
338
        npmUninstall.on('close', (code) => {
339
          console.log(clc.cyan('child process exited with code'), code)
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
340
341
          if(code == 0){
342
            this.removePlugin(plugin)
343
          }
344
          
345
          resolve()
346
        })
347
348
        npmUninstall.on('error', (err) => {
349
          console.log(clc.red('cannot uninstall npm dependencies for'), dir)
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
350
          console.log(err)
351
          resolve(err)
352
        })
353
      } catch (err) {
354
        console.log(clc.cyan('uninstall'), err)
355
        resolve(err)
356
      }
357
    })
358
359
    return p
360
  }
361
362
  add(dir, plugin) {
363
    var p = new Promise((resolve) => {
364
      try {
365
        console.log(clc.green('spawn'), clc.cyan('npm install ' + plugin))
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
366
        // const npmInstall = spawn('npm', ['install', pluginDir]);
367
        const npmInstall = spawn(npm, ['install', '--save', plugin], { cwd: dir })
368
369
        npmInstall.stdout.on('data', (data) => {
370
          console.log(''+data)
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
371
        })
372
373
        npmInstall.stderr.on('data', (data) => {
374
          console.log(''+data)
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
375
        })
376
377
        npmInstall.on('close', (code) => {
378
          console.log(clc.cyan('child process exited with code'), code)
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
379
380
          if(code == 0){
381
            this.updatePlugin(plugin)
382
          }
383
          
384
          resolve()
385
        })
386
387
        npmInstall.on('error', (err) => {
388
          console.log(clc.red('cannot install npm dependencies for'), dir)
0 ignored issues
show
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
389
          console.log(err)
390
          resolve(err)
391
        })
392
      } catch (err) {
393
        console.log(clc.cyan('install'), err)
394
        resolve(err)
395
      }
396
    })
397
398
    return p
399
  }
400
}
401
402
export default Plugins