Source: lib/commands/install.js

  1. /*!
  2. * Copyright (C) 2010-2015 by Revolution Analytics Inc.
  3. *
  4. * This program is licensed to you under the terms of Version 2.0 of the
  5. * Apache License. This program is distributed WITHOUT
  6. * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
  7. * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
  8. * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more
  9. * details.
  10. */
  11. 'use strict';
  12. var path = require('path'),
  13. fs = require('fs'),
  14. deployr = require('deployr'),
  15. request = require('request'),
  16. opn = require('opn'),
  17. shell = require('shelljs'),
  18. Download = require('download'),
  19. progress = require('download-status'),
  20. spinner = require('char-spinner'),
  21. LangType = require('../util/lang-type'),
  22. di = require('../../di'),
  23. conf = di.config,
  24. // --- color aliases ---
  25. ul = di.chalk.underline,
  26. dim = di.chalk.dim,
  27. red = di.chalk.red,
  28. gray = di.chalk.gray,
  29. green = di.chalk.green,
  30. yellow = di.chalk.yellow,
  31. magenta = di.chalk.magenta;
  32. /**
  33. * Commands for installing and deploying starter examples.
  34. * @mixin
  35. * @alias commands/install
  36. */
  37. var install = exports;
  38. /**
  39. * Usage for the _di install *_ commands which allow you install and deploy
  40. * starter examples.
  41. *
  42. * - di install example
  43. * - di install example <example name>
  44. */
  45. install.usage = [
  46. 'The `di install` command installs pre-built DeployR examples locally',
  47. '',
  48. 'Example usages:',
  49. 'di install example',
  50. 'di install example <example name>'
  51. ];
  52. /**
  53. * Installs a starter example.
  54. *
  55. * @param {String} exName - The example to install.
  56. * @param {Function} callback - Continuation to pass control when complete.
  57. */
  58. install.example = function(exName, callback) {
  59. var exConfig = {};
  60. //
  61. // Allows arbitrary amount of arguments
  62. //
  63. if (arguments.length) {
  64. var args = Array.prototype.slice.call([arguments], 0)[0];
  65. callback = args[arguments.length - 1];
  66. exName = args[0] || null;
  67. }
  68. function listExamples() {
  69. var interval = spinner(),
  70. separator = di.prompt.separator,
  71. jsList = [],
  72. javaList = [],
  73. dotNETList = [];
  74. request.get({
  75. url: conf.get('git').repos,
  76. headers: {
  77. 'User-Agent': 'Awesome-Octocat-App'
  78. }
  79. },
  80. function(err, res, body) {
  81. var found = false, choices = [];
  82. clearInterval(interval);
  83. if (err || res.statusCode != 200) {
  84. return callback(err);
  85. }
  86. try {
  87. JSON.parse(body).forEach(function(repo, i) {
  88. var name = repo.name;
  89. if (name.indexOf('example') > -1) {
  90. found = name === exName ? true : found;
  91. switch (LangType.parse(name)) {
  92. case LangType.JS:
  93. jsList.push({ name: ' ' + name });
  94. break;
  95. case LangType.JAVA:
  96. javaList.push(' ' + name);
  97. break;
  98. case LangType.DOTNET:
  99. dotNETList.push(' ' + name);
  100. break;
  101. }
  102. }
  103. });
  104. if (found) {
  105. console.log('Installing example ' + ul(exName) + '\n');
  106. installExample(exName, di.noop);
  107. return;
  108. }
  109. if (jsList.length > 0) {
  110. choices.push(separator('JavaScript Examples '));
  111. choices.push(jsList);
  112. }
  113. if (javaList.length > 0) {
  114. choices.push(separator());
  115. choices.push(separator('Java Examples '));
  116. choices.push(javaList);
  117. }
  118. if (dotNETList.length > 0) {
  119. choices.push(separator('.NET Examples '));
  120. choices.push(dotNETList);
  121. }
  122. choices.push(separator());
  123. choices.push({
  124. name: 'Take me back home!',
  125. value: {
  126. method: 'home'
  127. }
  128. });
  129. di.prompt.inquirer([{
  130. name: 'example',
  131. type: 'list',
  132. message: 'What example would you like to install?',
  133. choices: di._.flatten(choices)
  134. }], function(answer) {
  135. if (!answer.example.method) {
  136. installExample(answer.example.trim(), di.noop);
  137. } else {
  138. di.home();
  139. }
  140. }.bind(di));
  141. } catch (err) {
  142. return callback(new Error(chalk.bold(
  143. 'A problem occurred installing the example from Github.' +
  144. '\nUnable to parse response: not valid JSON.'
  145. )));
  146. }
  147. });
  148. }
  149. function installExample(example) {
  150. var download = new Download({
  151. extract: true,
  152. strip: 1,
  153. mode: '755'
  154. })
  155. .get(conf.get('git').example.replace('{{example}}', example))
  156. .dest(example)
  157. .use(progress());
  158. download.run(function(err, files, stream) {
  159. if (err) { callback(new Error(err)); }
  160. console.log(green.bold('✓ download complete.\n'));
  161. console.log(ul(example));
  162. var installer;
  163. switch (LangType.parse(example)) {
  164. case LangType.JS:
  165. installer = function(next) {
  166. console.log(yellow('\nResolving npm dependencies, this might take a while...\n'));
  167. di.spawnCommand('npm', ['install', '--production', '--silent'])
  168. .on('error', next)
  169. .on('exit', next);
  170. };
  171. break;
  172. case LangType.JAVA:
  173. case LangType.DOTNET:
  174. installer = function(next) { next(); }
  175. break;
  176. }
  177. // be in the example dir for installation and running
  178. shell.cd(example);
  179. installer(function() {
  180. fs.exists(path.resolve('di-config.json'), function(exists) {
  181. if (exists) {
  182. agent();
  183. deployr.io('/r/user/about')
  184. .error(function() {
  185. var end = !conf.get('endpoint') ?
  186. ' First identify your DeployR server endpoint.' : '';
  187. console.log(yellow('\nAuthentication required to install.' + end + '\n'));
  188. di.commands.login(function(err) {
  189. if (err) { return callback(err); }
  190. installation();
  191. });
  192. })
  193. .end(function() { installation(); });
  194. } else {
  195. // no `di-config` file given, just the launch example
  196. console.log('no di-config.json');
  197. run();
  198. }
  199. });
  200. });
  201. });
  202. function installation() {
  203. var config = require(path.resolve('di-config.json')) || {},
  204. repos = (config['app-install'] || {}).repository || [],
  205. dirs = di._.uniq(repos, 'directory'),
  206. last = dirs.length,
  207. success = 0;
  208. exConfig = {
  209. example: example,
  210. repos: repos,
  211. auth: (config['app-run'] || {}).requireAuthentication || false,
  212. tutorial: (config['app-run'] || {}).tutorial || {}
  213. };
  214. if (repos.length > 0) {
  215. console.log(yellow('\nInstalling example analytics dependencies onto DeployR...\n'));
  216. agent();
  217. repos.forEach(function(item, index) {
  218. console.log((index + 1) + '. ' +
  219. ul(item.file.filename) + ' in directory ' +
  220. ul(item.file.directory || 'root'));
  221. });
  222. console.log('');
  223. //
  224. // create directories for the dependencies, when done start the
  225. // upload/install of the analytic dependencies into DeployR
  226. //
  227. dirs.forEach(function(item) {
  228. deployr.io('/r/repository/directory/create')
  229. .data({ directory: item.file.directory })
  230. .end(function() { success++; })
  231. .ensure(function() {
  232. if (success === last) { analytics(repos); }
  233. });
  234. });
  235. } else { // othwise no dependencies just run the example
  236. candidateToRun();
  237. }
  238. }
  239. function analytics(repos) {
  240. var last = repos.length,
  241. success = 0,
  242. retry = [];
  243. repos.forEach(function(item) {
  244. var file = item.file,
  245. perm = item.permissions;
  246. deployr.io('/r/repository/file/upload')
  247. .data({
  248. filename: file.filename,
  249. directory: file.directory || 'root',
  250. restricted: perm.restricted || null,
  251. shared: perm.shared || true,
  252. published: perm.published || true,
  253. newversion: true,
  254. newversionmsg: 'DeployR CLI (examples) upload.'
  255. })
  256. .attach(path.resolve('analytics', file.filename))
  257. .error(function(err) { callback(err); })
  258. .end(function(res) {
  259. var file = res.get('repository').file;
  260. console.log(green.bold('✓ upload complete.\n'));
  261. console.log(ul(file.filename) +
  262. ' uploaded to directory ' +
  263. ul(file.directory) +
  264. ' for ' + dim(conf.get('username') + '@' + conf.get('endpoint')) +
  265. '\n');
  266. success++;
  267. })
  268. .ensure(function() {
  269. if (success === last) {
  270. console.log(green.bold('✓ installation complete.\n'));
  271. candidateToRun();
  272. }
  273. });
  274. }); // foreach
  275. }
  276. } // end installExample
  277. function candidateToRun() {
  278. di.prompt.inquirer([{
  279. name: 'run',
  280. type: 'confirm',
  281. message: 'Would you like to run the example:'
  282. }], function(result) {
  283. if (result.run) {
  284. if (exConfig.auth) {
  285. console.log('\nThis example requires ' +
  286. magenta(conf.get('username')) +
  287. '\'s password to run.\n');
  288. verifyPassword(targets);
  289. } else {
  290. targets();
  291. }
  292. }
  293. });
  294. }
  295. function verifyPassword(cb) {
  296. di.prompt.inquirer([{
  297. name: 'password',
  298. type: 'password',
  299. message: 'Password:',
  300. validate: function(input) {
  301. return input.length > 0 || 'Please enter a valid password.';
  302. }
  303. }], function(answer) {
  304. // confirm password
  305. di.prompt.inquirer([{
  306. name: 'password',
  307. type: 'password',
  308. message: 'Verify Password:'
  309. }], function(confirm) {
  310. if (answer.password === confirm.password) {
  311. cb(confirm.password);
  312. } else {
  313. console.log(red('>>') + ' Passwords do not match.\n');
  314. verifyPassword(cb);
  315. }
  316. });
  317. });
  318. }
  319. function targets(pw) {
  320. var separator = di.prompt.separator;
  321. di.clearScreen();
  322. // tutorial type examples
  323. if (exConfig.tutorial.topics) {
  324. var choices = [separator()];
  325. choices.push({
  326. name: 'Example Documentation',
  327. value: {
  328. help: exConfig.tutorial.help,
  329. }
  330. });
  331. exConfig.tutorial.topics.forEach(function(obj) {
  332. choices.push({
  333. name: obj.topic,
  334. value: {
  335. menu: obj.menu
  336. }
  337. });
  338. });
  339. choices.push(separator());
  340. choices.push({
  341. name: 'Take me back home!',
  342. value: 'home'
  343. });
  344. di.prompt.inquirer([{
  345. name: 'topic',
  346. type: 'list',
  347. message: 'Examples',
  348. choices: choices
  349. }], function(answer) {
  350. if (answer.topic === 'home') {
  351. di.home();
  352. } else if (answer.topic.help) {
  353. opn(answer.topic.help);
  354. targets(pw);
  355. } else {
  356. tutorialChoice(answer.topic.menu, pw);
  357. }
  358. });
  359. } else { // standalone example apps
  360. run({
  361. pw: pw
  362. });
  363. }
  364. }
  365. function tutorialChoice(menu, pw) {
  366. var separator = di.prompt.separator,
  367. choices = [separator()].concat(menu.map(function(obj) {
  368. return {
  369. name: obj.item,
  370. value: {
  371. test: obj.args
  372. }
  373. };
  374. }));
  375. di.prompt.inquirer([{
  376. name: 'item',
  377. type: 'list',
  378. message: 'Examples to run?',
  379. choices: choices.concat([
  380. separator(), {
  381. name: 'Take me back',
  382. value: 'back'
  383. }
  384. ])
  385. }], function(answer) {
  386. if (answer.item === 'back') {
  387. return targets(pw);
  388. }
  389. run({
  390. pw: pw,
  391. tutorial: {
  392. menu: menu,
  393. example: answer.item.test
  394. }
  395. });
  396. });
  397. }
  398. function run(optArgs) {
  399. var args = [],
  400. options = [],
  401. sep = dim(ul(' ')),
  402. cmd;
  403. optArgs = optArgs || {};
  404. switch (LangType.parse(exConfig.example)) {
  405. case LangType.JS:
  406. cmd = 'npm';
  407. args = ['start'];
  408. process.env.endpoint = conf.get('endpoint');
  409. process.env.username = conf.get('username');
  410. process.env.password = optArgs.pw || '';
  411. process.env.testmod = (optArgs.tutorial || {}).example;
  412. options = { env: process.env };
  413. break;
  414. case LangType.JAVA:
  415. cmd = (process.platform === 'win32' ? 'gradlew.bat' : './gradlew');
  416. args = [
  417. 'run',
  418. '-Pendpoint=' + conf.get('endpoint') + '/deployr',
  419. '-Pusername=' + conf.get('username'),
  420. '-Ppassword=' + optArgs.pw || '',
  421. '-DtestClass=' + (optArgs.tutorial || {}).example
  422. ];
  423. break;
  424. case LangType.DOTNET:
  425. break;
  426. }
  427. di.clearScreen();
  428. console.log(sep);
  429. di.spawnCommand(cmd, args, options)
  430. .on('error', function(err) {
  431. callback(err);
  432. })
  433. .on('close', function(err) {
  434. console.log(sep + '\n');
  435. di.prompt.inquirer([{
  436. name: 'post',
  437. type: 'list',
  438. message: 'What do you want to do?',
  439. choices: [{
  440. name: 'Take me back',
  441. value: 'back'
  442. }, {
  443. name: 'Run more examples',
  444. value: 'more'
  445. }]
  446. }], function(answer) {
  447. di.clearScreen();
  448. if (answer.post === 'back') {
  449. tutorialChoice(optArgs.tutorial.menu, optArgs.pw);
  450. } else {
  451. targets(optArgs.pw);
  452. }
  453. });
  454. });
  455. }
  456. //
  457. // Start to example installation workflow
  458. //
  459. listExamples();
  460. };
  461. /**
  462. * Output usage information describing commands for installing and deploying
  463. * starter examples.
  464. */
  465. install.example.usage = [
  466. 'The `di install` command installs pre-built DeployR examples locally',
  467. '',
  468. 'Example usages:',
  469. 'di install example',
  470. 'di install example <example name>'
  471. ];
  472. function agent() {
  473. deployr.configure({
  474. host: conf.get('endpoint'),
  475. cookies: ['JSESSIONID=' + conf.get('cookie')],
  476. sticky: true
  477. });
  478. }