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;