/ cmd / cmd.js
cmd.js
  1  const program = require('commander');
  2  const EmbarkController = require('./cmd_controller.js');
  3  const i18n = require('../lib/core/i18n/i18n.js');
  4  const utils = require('../lib/utils/utils.js');
  5  
  6  let embark = new EmbarkController();
  7  
  8  // set PWD to process.cwd() since Windows doesn't have a value for PWD
  9  if (!process.env.PWD) {
 10    process.env.PWD = process.cwd();
 11  }
 12  
 13  // set the anchor for embark's fs.dappPath()
 14  if (!process.env.DAPP_PATH) {
 15    process.env.DAPP_PATH = process.env.PWD;
 16  }
 17  
 18  // set the anchor for embark's fs.embarkPath()
 19  if (!process.env.EMBARK_PATH) {
 20    process.env.EMBARK_PATH = utils.joinPath(__dirname, '..');
 21  }
 22  
 23  // set the anchor for embark's fs.pkgPath()
 24  if (!process.env.PKG_PATH) {
 25    process.env.PKG_PATH = process.env.PWD;
 26  }
 27  
 28  process.env.DEFAULT_DIAGRAM_PATH = utils.joinPath(process.env.DAPP_PATH, 'diagram.svg');
 29  process.env.DEFAULT_CMD_HISTORY_PATH = utils.joinPath(process.env.DAPP_PATH, '.embark', 'cmd_history');
 30  process.env.DEFAULT_CMD_HISTORY_SIZE = 20;
 31  
 32  class Cmd {
 33    constructor() {
 34      program.version(embark.version);
 35    }
 36  
 37    process(args) {
 38      this.newApp();
 39      this.demo();
 40      this.build();
 41      this.run();
 42      this.console();
 43      this.blockchain();
 44      this.simulator();
 45      this.test();
 46      this.reset();
 47      this.ejectWebpack();
 48      this.graph();
 49      this.scaffold();
 50      this.upload();
 51      this.versionCmd();
 52      this.helpCmd();
 53      this.otherCommands();
 54  
 55      //If no arguments are passed display help by default
 56      if (!process.argv.slice(2).length) {
 57        program.help();
 58      }
 59  
 60      program.parse(args);
 61    }
 62  
 63    newApp() {
 64  
 65      let validateName = function(value) {
 66        try {
 67          if (value.match(/^[a-zA-Z\s-]+$/)) return value;
 68        } catch (e) {
 69          throw new Error(__('Name must be only letters, spaces, or dashes'));
 70        }
 71      };
 72  
 73      program
 74        .command('new [name]')
 75        .description(__('New Application'))
 76        .option('--simple', __('create a barebones project meant only for contract development'))
 77        .option('--locale [locale]', __('language to use (default: en)'))
 78        .option('--template <name/url>', __('download a template using a known name or a git host URL'))
 79        .action(function(name, options) {
 80          i18n.setOrDetectLocale(options.locale);
 81          if (name === undefined) {
 82            const promptly = require('promptly');
 83            return promptly.prompt(__("Name your app (default is %s):", 'embarkDapp'), {
 84              default: "embarkDApp",
 85              validator: validateName
 86            }, function(err, inputvalue) {
 87              if (err) {
 88                console.error(__('Invalid name') + ':', err.message);
 89                // Manually call retry
 90                // The passed error has a retry method to easily prompt again.
 91                err.retry();
 92              } else {
 93                //slightly different assignment of name since it comes from child prompt
 94                if (options.simple) {
 95                  embark.generateTemplate('simple', './', inputvalue);
 96                } else {
 97                  embark.generateTemplate('boilerplate', './', inputvalue, options.template);
 98                }
 99              }
100            });
101          }
102          if (options.simple) {
103            embark.generateTemplate('simple', './', name);
104          } else {
105            embark.generateTemplate('boilerplate', './', name, options.template);
106          }
107        });
108    }
109  
110    demo() {
111      program
112        .command('demo')
113        .option('--locale [locale]', __('language to use (default: en)'))
114        .description(__('create a working dapp with a SimpleStorage contract'))
115        .action(function(options) {
116          i18n.setOrDetectLocale(options.locale);
117          embark.generateTemplate('demo', './', 'embark_demo');
118        });
119    }
120  
121    build() {
122      program
123        .command('build [environment]')
124        .option('--contracts', 'only compile contracts into Embark wrappers')
125        .option('--logfile [logfile]', __('filename to output logs (default: none)'))
126        .option('-c, --client [client]', __('Use a specific ethereum client [%s] (default: %s)', 'geth, parity', 'geth'))
127        .option('--loglevel [loglevel]', __('level of logging to display') + ' ["error", "warn", "info", "debug", "trace"]', /^(error|warn|info|debug|trace)$/i, 'debug')
128        .option('--locale [locale]', __('language to use (default: en)'))
129        .option('--pipeline [pipeline]', __('webpack config to use (default: production)'))
130        .description(__('deploy and build dapp at ') + 'dist/ (default: development)')
131        .action(function(env, _options) {
132          i18n.setOrDetectLocale(_options.locale);
133          _options.env = env || 'development';
134          _options.logFile = _options.logfile; // fix casing
135          _options.logLevel = _options.loglevel; // fix casing
136          _options.onlyCompile = _options.contracts;
137          _options.client = _options.client;
138          _options.webpackConfigName = _options.pipeline || 'production';
139          embark.build(_options);
140        });
141    }
142  
143    run() {
144      program
145        .command('run [environment]')
146        .option('-p, --port [port]', __('port to run the dev webserver (default: %s)', '8000'))
147        .option('-c, --client [client]', __('Use a specific ethereum client [%s] (default: %s)', 'geth, parity', 'geth'))
148        .option('-b, --host [host]', __('host to run the dev webserver (default: %s)', 'localhost'))
149        .option('--noserver', __('disable the development webserver'))
150        .option('--nodashboard', __('simple mode, disables the dashboard'))
151        .option('--nobrowser', __('prevent the development webserver from automatically opening a web browser'))
152        .option('--no-color', __('no colors in case it\'s needed for compatbility purposes'))
153        .option('--logfile [logfile]', __('filename to output logs (default: %s)', 'none'))
154        .option('--loglevel [loglevel]', __('level of logging to display') + ' ["error", "warn", "info", "debug", "trace"]', /^(error|warn|info|debug|trace)$/i, 'debug')
155        .option('--locale [locale]', __('language to use (default: en)'))
156        .option('--pipeline [pipeline]', __('webpack config to use (default: development)'))
157        .description(__('run dapp (default: %s)', 'development'))
158        .action(function(env, options) {
159          i18n.setOrDetectLocale(options.locale);
160          const nullify = (v) => {
161            return (!v || typeof v !== 'string') ? null : v;
162          };
163          embark.run({
164            env: env || 'development',
165            serverPort: options.port,
166            serverHost: options.host,
167            client: options.client,
168            locale: options.locale,
169            runWebserver: !options.noserver ? null : false,
170            useDashboard: !options.nodashboard,
171            logFile: options.logfile,
172            logLevel: options.loglevel,
173            webpackConfigName: options.pipeline || 'development',
174            openBrowser: !options.nobrowser ? null : false
175          });
176        });
177    }
178  
179    console() {
180      program
181        .command('console [environment]')
182        .option('-c, --client [client]', __('Use a specific ethereum client [%s] (default: %s)', 'geth, parity', 'geth'))
183        .option('--logfile [logfile]', __('filename to output logs (default: %s)', 'none'))
184        .option('--loglevel [loglevel]', __('level of logging to display') + ' ["error", "warn", "info", "debug", "trace"]', /^(error|warn|info|debug|trace)$/i, 'debug')
185        .option('--locale [locale]', __('language to use (default: en)'))
186        .option('--pipeline [pipeline]', __('webpack config to use (default: development)'))
187        .description(__('Start the Embark console'))
188        .action(function(env, options) {
189          i18n.setOrDetectLocale(options.locale);
190          embark.console({
191            env: env || 'development',
192            client: options.client,
193            locale: options.locale,
194            logFile: options.logfile,
195            logLevel: options.loglevel,
196            webpackConfigName: options.pipeline || 'development'
197          });
198        });
199    }
200  
201    blockchain() {
202      program
203        .command('blockchain [environment]')
204        .option('-c, --client [client]', __('Use a specific ethereum client [%s] (default: %s)', 'geth, parity', 'geth'))
205        .option('--locale [locale]', __('language to use (default: en)'))
206        .description(__('run blockchain server (default: %s)', 'development'))
207        .action(function(env, options) {
208          i18n.setOrDetectLocale(options.locale);
209          embark.initConfig(env || 'development', {
210            embarkConfig: 'embark.json',
211            interceptLogs: false
212          });
213          embark.blockchain(env || 'development', options.client);
214        });
215    }
216  
217    simulator() {
218      program
219        .command('simulator [environment]')
220        .description(__('run a fast ethereum rpc simulator'))
221        .option('--testrpc', __('use ganache-cli (former "testrpc") as the rpc simulator [%s]', 'default'))
222        .option('-p, --port [port]', __('port to run the rpc simulator (default: %s)', '8545'))
223        .option('-h, --host [host]', __('host to run the rpc simulator (default: %s)', 'localhost'))
224        .option('-a, --accounts [numAccounts]', __('number of accounts (default: %s)', '10'))
225        .option('-e, --defaultBalanceEther [balance]', __('Amount of ether to assign each test account (default: %s)', '100'))
226        .option('-l, --gasLimit [gasLimit]', __('custom gas limit (default: %s)', '8000000'))
227        .option('--locale [locale]', __('language to use (default: en)'))
228  
229        .action(function(env, options) {
230          i18n.setOrDetectLocale(options.locale);
231          embark.initConfig(env || 'development', {
232            embarkConfig: 'embark.json',
233            interceptLogs: false
234          });
235          embark.simulator({
236            port: options.port,
237            host: options.host,
238            numAccounts: options.numAccounts,
239            defaultBalance: options.balance,
240            gasLimit: options.gasLimit
241          });
242        });
243    }
244  
245    test() {
246      program
247        .command('test [file]')
248        .option('-n , --node <node>', __('node for running the tests ["vm", "embark", <endpoint>] (default: vm)\n') +
249                '                       vm - ' + __('start and use an Ethereum simulator (ganache)') + '\n' +
250                '                       embark - ' + __('use the node of a running embark process') + '\n' +
251                '                       <endpoint> - ' + __('connect to and use the specified node'))
252        .option('-d , --gasDetails', __('print the gas cost for each contract deployment when running the tests'))
253        .option('-c , --coverage', __('generate a coverage report after running the tests (vm only)'))
254        .option('--nobrowser', __('do not start browser after coverage report is generated'))
255        .option('--locale [locale]', __('language to use (default: en)'))
256        .option('--loglevel [loglevel]', __('level of logging to display') + ' ["error", "warn", "info", "debug", "trace"]', /^(error|warn|info|debug|trace)$/i, 'warn')
257        .option('--solc', __('run only solidity tests'))
258        .description(__('run tests'))
259        .action(function(file, options) {
260          const node = options.node || 'vm';
261          const urlRegexExp = /^(vm|embark|((ws|https?):\/\/([a-zA-Z0-9_.-]*):?([0-9]*)?))$/i;
262          if (!urlRegexExp.test(node)) {
263            console.error(`invalid --node option: must be "vm", "embark" or a valid URL\n`.red);
264            options.outputHelp();
265            process.exit(1);
266          }
267          options.node = node;
268          if (options.coverage && options.node !== 'vm') {
269            console.error(`invalid --node option: coverage supports "vm" only\n`.red);
270            options.outputHelp();
271            process.exit(1);
272          }
273          i18n.setOrDetectLocale(options.locale);
274          embark.runTests({file, solc:options.solc, logLevel: options.loglevel, gasDetails: options.gasDetails,
275            node: options.node, coverage: options.coverage, noBrowser: options.nobrowser});
276        });
277    }
278  
279    upload() {
280      program
281        .command('upload [environment]')
282        //.option('--ens [ensDomain]', __('ENS domain to associate to'))
283        .option('--logfile [logfile]', __('filename to output logs (default: %s)', 'none'))
284        .option('--loglevel [loglevel]', __('level of logging to display') + ' ["error", "warn", "info", "debug", "trace"]', /^(error|warn|info|debug|trace)$/i, 'debug')
285        .option('--locale [locale]', __('language to use (default: en)'))
286        .option('-c, --client [client]', __('Use a specific ethereum client [%s] (default: %s)', 'geth, parity', 'geth'))
287        .option('--pipeline [pipeline]', __('webpack config to use (default: production)'))
288        .description(__('Upload your dapp to a decentralized storage') + '.')
289        .action(function(env, _options) {
290          i18n.setOrDetectLocale(_options.locale);
291          if (env === "ipfs" || env === "swarm") {
292            console.warn(("did you mean " + "embark upload".bold + " ?").underline);
293            console.warn("In embark 3.1 forwards, the correct command is embark upload <environment> and the provider is configured in config/storage.js");
294          }
295          _options.env = env || 'development';
296          _options.ensDomain = _options.ens;
297          _options.logFile = _options.logfile; // fix casing
298          _options.logLevel = _options.loglevel; // fix casing
299          _options.client = _options.client;
300          _options.webpackConfigName = _options.pipeline || 'production';
301          embark.upload(_options);
302        });
303    }
304  
305    graph() {
306      program
307        .command('graph [environment]')
308        .option('--skip-undeployed', __('Graph will not include undeployed contracts'))
309        .option('--skip-functions', __('Graph will not include functions'))
310        .option('--skip-events', __('Graph will not include events'))
311        .option('--locale [locale]', __('language to use (default: en)'))
312        .option('--output [svgfile]', __('filepath to output SVG graph to (default: %s)', process.env['DEFAULT_DIAGRAM_PATH']))
313        .description(__('generates documentation based on the smart contracts configured'))
314        .action(function(env, options) {
315          i18n.setOrDetectLocale(options.locale);
316          embark.graph({
317            env: env || 'development',
318            logFile: options.logfile,
319            skipUndeployed: options.skipUndeployed,
320            skipFunctions: options.skipFunctions,
321            skipEvents: options.skipEvents,
322            output: options.output || process.env['DEFAULT_DIAGRAM_PATH']
323          });
324        });
325    }
326  
327    scaffold() {
328      program
329        .command('scaffold [contract] [fields...]')
330        .option('--framework <framework>', 'UI framework to use. (default: react)')
331        .option('--contract-language <language>', 'Language used for the smart contract generation (default: solidity)')
332        .option('--overwrite', 'Overwrite existing files. (default: false)')
333        .description(__('Generates a contract and a function tester for you\nExample: ContractName field1:uint field2:address --contract-language solidity --framework react'))
334        .action(function(contract, fields, options) {
335          if (contract === undefined) {
336            console.log("contract name is required");
337            process.exit(0);
338          }
339  
340          let fieldMapping = {};
341          if (fields.length > 0) {
342            const typeRegex = /^(u?int[0-9]{0,3}(\[\])?|string|bool|address|bytes[0-9]{0,3})(\[\])?$/;
343            const varRegex = /^[a-zA-Z][a-zA-Z0-9_]*$/;
344            fieldMapping = fields.reduce((acc, curr) => {
345              const c = curr.split(':');
346  
347              if (!varRegex.test(c[0])) {
348                console.log("Invalid variable name: " + c[0]);
349                process.exit(1);
350              }
351  
352              if (!typeRegex.test(c[1])) {
353                console.log("Invalid datatype: " + c[1] + " - The dApp generator might not support this type at the moment");
354                process.exit(1);
355              }
356  
357              acc[c[0]] = c[1];
358              return acc;
359            }, {});
360          }
361  
362          i18n.setOrDetectLocale(options.locale);
363          options.env = 'development';
364          options.logFile = options.logfile; // fix casing
365          options.logLevel = options.loglevel; // fix casing
366          options.onlyCompile = options.contracts;
367          options.client = options.client || 'geth';
368          options.webpackConfigName = options.pipeline || 'development';
369          options.contract = contract;
370          options.framework = options.framework || 'react';
371          options.contractLanguage = options.contractLanguage || 'solidity';
372          options.overwrite = options.overwrite || false;
373          options.fields = fieldMapping;
374  
375          embark.scaffold(options);
376        });
377    }
378  
379    reset() {
380      program
381        .command('reset')
382        .option('--locale [locale]', __('language to use (default: en)'))
383        .description(__('resets embarks state on this dapp including clearing cache'))
384        .action(function(options) {
385          i18n.setOrDetectLocale(options.locale);
386          embark.initConfig('development', {
387            embarkConfig: 'embark.json', interceptLogs: false
388          });
389          embark.reset();
390        });
391    }
392  
393    ejectWebpack() {
394      program
395        .command('eject-webpack')
396        .description(__('copy the default webpack config into your dapp for customization'))
397        .action(function() {
398          embark.initConfig('development', {
399            embarkConfig: 'embark.json',
400            interceptLogs: false
401          });
402          embark.ejectWebpack();
403        });
404    }
405  
406    versionCmd() {
407      program
408        .command('version')
409        .description(__('output the version number'))
410        .action(function() {
411          console.log(embark.version);
412          process.exit(0);
413        });
414    }
415  
416    helpCmd() {
417      program
418        .command('help')
419        .description(__('output usage information and help information'))
420        .action(function() {
421          console.log("Documentation can be found at: ".green + "https://embark.status.im/docs/".underline.green);
422          console.log("");
423          console.log("Have an issue? submit it here: ".green + "https://github.com/embark-framework/embark/issues/new".underline.green);
424          console.log("or chat with us directly at: ".green + "https://gitter.im/embark-framework/Lobby".underline.green);
425          program.help();
426          process.exit(0);
427        });
428    }
429  
430    otherCommands() {
431      program
432        .action(function(cmd) {
433          console.log((__('unknown command') + ' "%s"').red, cmd);
434          let utils = require('../lib/utils/utils.js');
435          let dictionary = ['new', 'demo', 'build', 'run', 'blockchain', 'simulator', 'test', 'upload', 'version', 'console', 'eject-webpack', 'graph', 'help', 'reset'];
436          let suggestion = utils.proposeAlternative(cmd, dictionary);
437          if (suggestion) {
438            console.log((__('did you mean') + ' "%s"?').green, suggestion);
439          }
440          console.log("type embark --help to see the available commands");
441          process.exit(0);
442        });
443    }
444  
445  }
446  
447  module.exports = Cmd;