Issues (4925)

formio/install.js (3 issues)

Severity
1
'use strict';
2
3
var process = require('process');
4
var prompt = require('prompt');
5
var async = require('async');
6
var fs = require('fs-extra');
7
var _ = require('lodash');
8
var nunjucks = require('nunjucks');
9
nunjucks.configure([], {watch: false});
10
var util = require('./src/util/util');
11
var debug = require('debug')('formio:error');
12
var path = require('path');
13
14
module.exports = function(formio, items, done) {
15
  // The project that was created.
16
  var project = {};
17
18
  // The directory for the client application.
19
  var directories = {
20
    client: path.join(__dirname, 'client'),
21
    app: path.join(__dirname, 'app')
22
  };
23
24
  // The application they wish to install.
25
  var application = '';
26
  var templateFile = '';
27
28
  /**
29
   * Download a zip file.
30
   *
31
   * @param url
32
   * @param zipFile
33
   * @param dir
34
   * @param done
35
   * @returns {*}
36
   */
37
  var download = function(url, zipFile, dir, done) {
38
    // Check to see if the client already exists.
39
    if (fs.existsSync(zipFile)) {
40
      util.log(`${directories[dir]  } file already exists, skipping download.`);
41
      return done();
42
    }
43
44
    var request = require('request');
45
    var ProgressBar = require('progress');
46
    util.log(`Downloading ${  dir  }${'...'.green}`);
47
48
    // Download the project.
49
    var downloadError = null;
50
    var tries = 0;
51
    var bar = null;
52
    (function downloadProject() {
53
      request.get(url)
54
        .on('response', function(res) {
55
          if (
56
            !res.headers.hasOwnProperty('content-disposition') ||
57
            !parseInt(res.headers['content-length'], 10)
58
          ) {
59
            if (tries++ > 3) {
60
              return done('Unable to download project. Please try again.');
61
            }
62
63
            setTimeout(downloadProject, 200);
64
            return;
65
          }
66
67
          // Setup the progress bar.
68
          bar = new ProgressBar('  downloading [:bar] :percent :etas', {
69
            complete: '=',
70
            incomplete: ' ',
71
            width: 50,
72
            total: parseInt(res.headers['content-length'], 10)
73
          });
74
75
          res.pipe(fs.createWriteStream(zipFile, {
76
            flags: 'w'
77
          }));
78
          res.on('data', function(chunk) {
79
            if (bar) {
80
              bar.tick(chunk.length);
81
            }
82
          });
83
          res.on('error', function(err) {
84
            downloadError = err;
85
          });
86
          res.on('end', function() {
87
            setTimeout(function() {
88
              done(downloadError);
89
            }, 100);
90
          });
91
        });
92
    })();
93
  };
94
95
  /**
96
   * Extract a download to a folder.
97
   *
98
   * @param zipFile
99
   * @param fromDir
100
   * @param dir
101
   * @param done
102
   * @returns {*}
103
   */
104
  var extract = function(zipFile, fromDir, dir, done) {
105
    // See if we need to extract.
106
    if (fs.existsSync(directories[dir])) {
107
      util.log(`${directories[dir]  } already exists, skipping extraction.`);
108
      return done();
109
    }
110
111
    // Unzip the contents.
112
    var AdmZip = require('adm-zip');
113
    util.log('Extracting contents...'.green);
114
    var zip = new AdmZip(zipFile);
115
    zip.extractAllTo('', true);
116
    fs.move(fromDir, directories[dir], function(err) {
117
      if (err) {
118
        return done(err);
119
      }
120
121
      // Delete the zip file.
122
      fs.remove(zipFile);
123
124
      // Get the package json file.
125
      var info = {};
0 ignored issues
show
The assignment to variable info seems to be never used. Consider removing it.
Loading history...
126
      try {
127
        info = JSON.parse(fs.readFileSync(path.join(directories[dir], 'package.json')));
128
      }
129
      catch (err) {
130
        debug(err);
131
        return done(err);
132
      }
133
134
      // Set local variable to directory path.
135
      var directoryPath = directories[dir];
136
137
      // Change the document root if we need to.
138
      if (info.formio && info.formio.docRoot) {
139
        directoryPath = path.join(directories[dir], info.formio.docRoot);
140
      }
141
142
      if (!fs.existsSync(path.join(directoryPath, 'config.template.js'))) {
143
        return done('Missing config.template.js file');
144
      }
145
146
      // Change the project configuration.
147
      var config = fs.readFileSync(path.join(directoryPath, 'config.template.js'));
148
      var newConfig = nunjucks.renderString(config.toString(), {
149
        domain: formio.config.domain ? formio.config.domain : 'https://form.io'
150
      });
151
      fs.writeFileSync(path.join(directoryPath, 'config.js'), newConfig);
152
      done();
153
    });
154
  };
155
156
  // All the steps in the installation.
157
  var steps = {
158
    /**
159
     * Step to perform the are you sure step.
160
     *
161
     * @param done
162
     */
163
    areYouSure: function(done) {
164
      prompt.get([
165
        {
166
          name: 'install',
167
          description: 'Are you sure you wish to install? (y/N)',
168
          required: true
169
        }
170
      ], function(err, results) {
171
        if (err) {
172
          return done(err);
173
        }
174
        if (results.install.toLowerCase() !== 'y') {
175
          return done('Installation canceled.');
176
        }
177
178
        done();
179
      });
180
    },
181
182
    // Allow them to select the application.
183
    whatApp: function(done) {
184
      var repos = [
185
        'None',
186
        'https://github.com/formio/formio-app-humanresources',
187
        'https://github.com/formio/formio-app-servicetracker',
188
        'https://github.com/formio/formio-app-todo',
189
        'https://github.com/formio/formio-app-salesquote',
190
        'https://github.com/formio/formio-app-basic'
191
      ];
192
      var message = '\nWhich Github application would you like to install?\n'.green;
193
      _.each(repos, function(repo, index) {
194
        message += `  ${  index + 1  }.) ${  repo  }\n`;
195
      });
196
      message += '\nOr, you can provide a custom Github repository...\n'.green;
197
      util.log(message);
198
      prompt.get([
199
        {
200
          name: 'app',
201
          description: 'GitHub repository or selection?',
202
          default: '1',
203
          required: true
204
        }
205
      ], function(err, results) {
206
        if (err) {
207
          return done(err);
208
        }
209
210
        if (results.app.indexOf('https://github.com/') !== -1) {
211
          application = results.app;
212
        }
213
        else {
214
          var selection = parseInt(results.app, 10);
215
          if (_.isNumber(selection)) {
216
            if ((selection > 1) && (selection <= repos.length)) {
217
              application = repos[selection - 1];
218
            }
219
          }
220
        }
221
222
        // Replace github.com url.
223
        application = application.replace('https://github.com/', '');
224
        done();
225
      });
226
    },
227
228
    /**
229
     * Download the application.
230
     *
231
     * @param done
232
     * @returns {*}
233
     */
234
    downloadApp: function(done) {
235
      if (!application) {
236
        return done();
237
      }
238
239
      // Download the app.
240
      download(
241
        `https://nodeload.github.com/${  application  }/zip/master`,
242
        'app.zip',
243
        'app',
244
        done
245
      );
246
    },
247
248
    /**
249
     * Extract the application to the app folder.
250
     *
251
     * @param done
252
     * @returns {*}
253
     */
254
    extractApp: function(done) {
255
      if (!application) {
256
        return done();
257
      }
258
259
      var parts = application.split('/');
260
      var appDir = `${parts[1]  }-master`;
261
      extract('app.zip', appDir, 'app', done);
262
    },
263
264
    /**
265
     * Download the Form.io admin client.
266
     *
267
     * @param done
268
     * @returns {*}
269
     */
270
    downloadClient: function(done) {
271
      if (!items.download) {
272
        return done();
273
      }
274
275
      // Download the client.
276
      download(
277
        'https://nodeload.github.com/formio/formio-app-formio/zip/master',
278
        'client.zip',
279
        'client',
280
        done
281
      );
282
    },
283
284
    /**
285
     * Extract the client.
286
     *
287
     * @param done
288
     * @returns {*}
289
     */
290
    extractClient: function(done) {
291
      if (!items.extract) {
292
        return done();
293
      }
294
295
      extract('client.zip', 'formio-app-formio-master', 'client', done);
296
    },
297
298
    /**
299
     * Select the template to use.
300
     *
301
     * @param done
302
     * @return {*}
303
     */
304
    whatTemplate: function(done) {
305
      if (application) {
306
        templateFile = 'app';
307
        return done();
308
      }
309
310
      var message = '\nWhich project template would you like to install?\n'.green;
311
      message += '\n   Please provide the local file path of the project.json file.'.yellow;
312
      message += '\n   Or, just press '.yellow + 'ENTER'.green + ' to use the default template.\n'.yellow;
313
      util.log(message);
314
      prompt.get([
315
        {
316
          name: 'templateFile',
317
          description: 'Local file path or just press Enter for default.',
318
          default: 'client',
319
          required: true
320
        }
321
      ], function(err, results) {
322
        if (err) {
323
          return done(err);
324
        }
325
326
        templateFile = results.templateFile ? results.templateFile : 'client';
327
        done();
328
      });
329
    },
330
331
    /**
332
     * Import the template.
333
     * @param done
334
     */
335
    importTemplate: function(done) {
336
      if (!items.import) {
337
        return done();
338
      }
339
340
      // Which directory to use for importing.
341
      var dir = application ? 'app' : 'client';
342
343
      if (!items.extract) {
344
        // Get the package json file.
345
        var info = {};
0 ignored issues
show
The assignment to variable info seems to be never used. Consider removing it.
Loading history...
346
        try {
347
          info = JSON.parse(fs.readFileSync(directories[dir] + '/package.json'));
348
        }
349
        catch (err) {
350
          debug(err);
351
          return done(err);
352
        }
353
354
        // Change the document root if we need to.
355
        if (info.formio && info.formio.docRoot) {
356
          directories[dir] = path.join(directories[dir], info.formio.docRoot);
357
        }
358
      }
359
360
      if (!fs.existsSync(directories[dir] + '/project.json')) {
361
        return done('Missing project.json file');
362
      }
363
364
      var template = {};
0 ignored issues
show
The assignment to variable template seems to be never used. Consider removing it.
Loading history...
365
      try {
366
        template = JSON.parse(fs.readFileSync(path.join(directories[dir], 'project.json')));
367
      }
368
      catch (err) {
369
        debug(err);
370
        return done(err);
371
      }
372
373
      // Get the form.io service.
374
      util.log('Importing template...'.green);
375
      var importer = require('./src/templates/import')({formio: formio});
376
      importer.template(template, function(err, template) {
377
        if (err) {
378
          return done(err);
379
        }
380
381
        project = template;
382
        done(null, template);
383
      });
384
    },
385
386
    /**
387
     * Create the root user object.
388
     *
389
     * @param done
390
     */
391
    createRootUser: function(done) {
392
      if (!items.user) {
393
        return done();
394
      }
395
      util.log('Creating pre-configured root user account...'.green);
396
      util.log('Encrypting password');
397
398
      // @todo Make this a fixture that is aware of environments
399
      var system = {
400
          'email': '[email protected]',
401
          'password': 'system'
402
      };
403
404
      formio.encrypt(system.password, function(err, hash) {
405
          if (err) {
406
              return done(err);
407
          }
408
409
          // Create the system user submission.
410
          util.log('Creating system user account');
411
          formio.resources.submission.model.create({
412
              form: project.resources.admin._id,
413
              data: {
414
                  email: system.email,
415
                  password: hash
416
              },
417
              roles: [
418
                  project.roles.administrator._id
419
              ]
420
          }, function(err, item) {
421
              if (err) {
422
                  return done(err);
423
              }
424
          });
425
      });
426
427
      var result = {
428
          'email': process.env.USERNAME || '[email protected]',
429
          'password': process.env.PASSWORD || 'changeme'
430
      };
431
432
      formio.encrypt(result.password, function(err, hash) {
433
        if (err) {
434
          return done(err);
435
        }
436
437
        // Create the root user submission.
438
        util.log('Creating root user account');
439
        formio.resources.submission.model.create({
440
          form: project.resources.admin._id,
441
          data: {
442
            email: result.email,
443
            password: hash
444
          },
445
          roles: [
446
            project.roles.administrator._id
447
          ]
448
        }, function(err, item) {
449
          if (err) {
450
            return done(err);
451
          }
452
453
          done();
454
        });
455
      });
456
    }
457
  };
458
459
  util.log('Installing...');
460
  prompt.start();
461
  async.series([
462
    // steps.areYouSure,
463
    // steps.whatApp,
464
    // steps.downloadApp,
465
    // steps.extractApp,
466
    // steps.downloadClient,
467
    // steps.extractClient,
468
    // steps.whatTemplate,
469
    steps.importTemplate,
470
    steps.createRootUser
471
  ], function(err, result) {
472
    if (err) {
473
      util.log(err);
474
      return done(err);
475
    }
476
477
    util.log('Install successful!'.green);
478
    done();
479
  });
480
};
481