UNPKG

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