diff --git a/Gruntfile.coffee b/Gruntfile.coffee index 00dfffa0..5909c4e1 100644 --- a/Gruntfile.coffee +++ b/Gruntfile.coffee @@ -1,7 +1,30 @@ module.exports = (grunt) -> grunt.initConfig - "embark-framework": + "embark-framework": {} + pkg: grunt.file.readJSON('package.json') + clean: + build: ["build/"] + coffee: + compile: + expand: true + src: 'src/**/*.coffee' + dest: 'build/' + ext: '.js' + uglify: + options: banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n' + build: + files: + 'build/<%= pkg.name %>.min.js': [ + "build/<%= pkg.name %>.js" + ] + mochaTest: + test: + src: ['test/**/*.js'] grunt.loadTasks "tasks" + require('matchdep').filterAll('grunt-*').forEach(grunt.loadNpmTasks) + + grunt.registerTask 'default', ['clean'] + grunt.registerTask 'build', ['clean', 'coffee'] diff --git a/boilerplate/package.json b/boilerplate/package.json index 4d677e4f..485b332f 100644 --- a/boilerplate/package.json +++ b/boilerplate/package.json @@ -10,7 +10,7 @@ "license": "ISC", "homepage": "", "devDependencies": { - "embark-framework": "^0.4.3", + "embark-framework": "/Users/iurimatias/Projects/embark-framework", "grunt-contrib-clean": "^0.6.0", "grunt-contrib-coffee": "^0.13.0", "grunt-contrib-concat": "^0.5.1", diff --git a/build/src/index.js b/build/src/index.js new file mode 100644 index 00000000..10a8c44d --- /dev/null +++ b/build/src/index.js @@ -0,0 +1,36 @@ +(function() { + var commander, compression, embark, express, hashmerge, jasmine, methodmissing, python, readYaml, shelljs, shelljs_global, syncMe, web3, wrench; + + hashmerge = require('hashmerge'); + + readYaml = require('read-yaml'); + + shelljs = require('shelljs'); + + shelljs_global = require('shelljs/global'); + + web3 = require('web3'); + + express = require('express'); + + compression = require('compression'); + + commander = require('commander'); + + wrench = require('wrench'); + + python = require('python'); + + syncMe = require('sync-me'); + + methodmissing = require('methodmissing'); + + jasmine = require('jasmine'); + + embark = {}; + + embark.Tests = require('./test.js'); + + module.exports = embark; + +}).call(this); diff --git a/build/src/test.js b/build/src/test.js new file mode 100644 index 00000000..428f092f --- /dev/null +++ b/build/src/test.js @@ -0,0 +1,96 @@ +(function() { + var Test, TestContract, TestContractWrapper, className, compiled_contracts, contract, contractDB, contractFile, contractFiles, fs, grunt, i, len, mm, py_exec, python, request, source, sync, web3; + + python = require('python').shell; + + web3 = require('web3'); + + fs = require('fs'); + + mm = require('methodmissing'); + + sync = require('sync-me'); + + grunt = require('grunt'); + + py_exec = function(cmd) { + return sync(python, cmd)[1].trim(); + }; + + py_exec('from ethertdd import EvmContract'); + + web3.setProvider(new web3.providers.HttpProvider('http://localhost:8101')); + + web3.eth.defaultAccount = web3.eth.accounts[0]; + + TestContractWrapper = (function() { + TestContractWrapper = function(contract, className, args) { + this.contract = contract; + this.className = className; + this.args = args; + this.initializeContract(); + }; + TestContractWrapper.prototype.initializeContract = function() { + var example_abi, example_binary; + example_abi = JSON.stringify(contract.info.abiDefinition); + example_binary = contract.code.slice(2); + py_exec("example_abi = '" + example_abi + "'"); + py_exec("example_abi"); + py_exec("example_binary = '" + example_binary + "'.decode('hex')"); + py_exec("example_binary"); + if (this.args === void 0) { + py_exec(this.className + '_contract = EvmContract(example_abi, example_binary, \'' + this.className + '\')'); + } else { + py_exec(this.className + '_contract = EvmContract(example_abi, example_binary, \'' + this.className + '\', [' + this.args.join(',') + '])'); + } + this.contractVariable = this.className + '_contract'; + }; + TestContractWrapper.prototype.execCmd = function(method, args) { + var arg_list, data, key, value; + arg_list = []; + for (key in args) { + value = args[key]; + value = args[key]; + arg_list.push(value); + } + data = py_exec(this.className + '_contract.' + method + '(' + arg_list.join(',') + ')'); + return data; + }; + return TestContractWrapper; + })(); + + TestContract = function(contract, className, args) { + var Obj, wrapper; + wrapper = new TestContractWrapper(contract, className, args); + Obj = mm(wrapper, function(key, args) { + return wrapper.execCmd(key, args); + }); + return Obj; + }; + + contractFiles = grunt.file.expand('./app/contracts/**/*.sol'); + + contractDB = {}; + + for (i = 0, len = contractFiles.length; i < len; i++) { + contractFile = contractFiles[i]; + source = fs.readFileSync(contractFile).toString(); + compiled_contracts = web3.eth.compile.solidity(source); + for (className in compiled_contracts) { + contract = compiled_contracts[className]; + contractDB[className] = contract; + } + } + + request = function(className, args) { + contract = contractDB[className]; + return TestContract(contract, className, args); + }; + + Test = { + request: request + }; + + module.exports = Test; + +}).call(this); diff --git a/lib/blockchain.js b/lib/blockchain.js new file mode 100644 index 00000000..79abeb68 --- /dev/null +++ b/lib/blockchain.js @@ -0,0 +1,102 @@ +var Deploy; +var readYaml = require('read-yaml'); + +startChain = function(env) { + try { + blockchainConfig = readYaml.sync("config/blockchain.yml"); + } catch (_error) { + exception = _error; + console.log("==== error reading config/blockchain.yml"); + console.log(exception); + } + + rpcHost = blockchainConfig[env].rpc_host; + + rpcPort = blockchainConfig[env].rpc_port; + + rpcWhitelist = blockchainConfig[env].rpc_whitelist; + + minerthreads = blockchainConfig[env].minerthreads; + + datadir = blockchainConfig[env].datadir; + + networkId = blockchainConfig[env].network_id || Math.floor((Math.random() * 100000) + 1000); + + port = blockchainConfig[env].port || "30303"; + + console_toggle = blockchainConfig[env].console || false; + + mine_when_needed = blockchainConfig[env].mine_when_needed || false; + + account = blockchainConfig[env].account; + + address = account.address; + + cmd = "geth "; + + if (datadir !== "default") { + cmd += "--datadir=\"" + datadir + "\" "; + cmd += "--logfile=\"" + datadir + ".log\" "; + } + + cmd += "--port " + port + " "; + + cmd += "--rpc "; + + cmd += "--rpcport " + rpcPort + " "; + + cmd += "--networkid " + networkId + " "; + + cmd += "--rpccorsdomain \"" + rpcWhitelist + "\" "; + + if (minerthreads !== void 0) { + cmd += "--minerthreads \"" + minerthreads + "\" "; + } + + cmd += "--mine "; + + if (account.password !== void 0) { + cmd += "--password " + account.password + " "; + } + + if (account.init) { + console.log("=== initializating account"); + console.log("running: " + cmd + " account list"); + result = exec(cmd + "account list"); + console.log("finished"); + console.log("=== output is " + result.output); + if (result.output.indexOf("Fatal") < 0) { + console.log("=== already initialized"); + address = result.output.match(/{(\w+)}/)[1]; + } else { + console.log("running: " + cmd + " account new"); + output = exec(cmd + " account new"); + address = output.output.match(/{(\w+)}/)[1]; + } + } + + if (address !== void 0) { + cmd += "--unlock " + address + " "; + } + + if (console_toggle) { + cmd += "console"; + } + + if (mine_when_needed) { + cmd += "js node_modules/embark-framework/js/mine.js"; + } + + console.log("running: " + cmd); + + console.log("=== running geth"); + + exec(cmd); +} + +Blockchain = { + startChain: startChain +} + +module.exports = Blockchain + diff --git a/lib/deploy.js b/lib/deploy.js new file mode 100644 index 00000000..81bbab00 --- /dev/null +++ b/lib/deploy.js @@ -0,0 +1,136 @@ +var web3 = require('web3'); +var fs = require('fs'); +var grunt = require('grunt'); +var readYaml = require('read-yaml'); + +deployContracts = function(env, contractFiles, destFile) { + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + blockchainConfig = readYaml.sync("config/blockchain.yml"); + + contractsConfig = readYaml.sync("config/contracts.yml")[env || "development"]; + + rpcHost = blockchainConfig[env || "development"].rpc_host; + + rpcPort = blockchainConfig[env || "development"].rpc_port; + + gasLimit = blockchainConfig[env || "development"].gas_limit || 500000; + + gasPrice = blockchainConfig[env || "development"].gas_price || 10000000000000; + + try { + web3.setProvider(new web3.providers.HttpProvider("http://" + rpcHost + ":" + rpcPort)); + primaryAddress = web3.eth.coinbase; + web3.eth.defaultAccount = primaryAddress; + } catch (_error) { + e = _error; + console.log("==== can't connect to " + rpcHost + ":" + rpcPort + " check if an ethereum node is running"); + exit; + } + + console.log("address is : " + primaryAddress); + + result = "web3.setProvider(new web3.providers.HttpProvider('http://" + rpcHost + ":" + rpcPort + "'));"; + + result += "web3.eth.defaultAccount = web3.eth.accounts[0];"; + + contractDependencies = {}; + + if (contractsConfig != null) { + for (className in contractsConfig) { + options = contractsConfig[className]; + if (options.args == null) { + continue; + } + ref = options.args; + for (i = 0, len = ref.length; i < len; i++) { + arg = ref[i]; + if (arg[0] === "$") { + if (contractDependencies[className] === void 0) { + contractDependencies[className] = []; + } + contractDependencies[className].push(arg.substr(1)); + } + } + } + } + + all_contracts = []; + + contractDB = {}; + + for (j = 0, len1 = contractFiles.length; j < len1; j++) { + contractFile = contractFiles[j]; + //source = grunt.file.read(contractFile); + source = fs.readFileSync(contractFile).toString() + console.log("deploying " + contractFile); + compiled_contracts = web3.eth.compile.solidity(source); + for (className in compiled_contracts) { + contract = compiled_contracts[className]; + all_contracts.push(className); + contractDB[className] = contract; + } + } + + all_contracts.sort((function(_this) { + return function(a, b) { + var contract_1, contract_2; + contract_1 = contractDependencies[a]; + contract_2 = contractDependencies[b]; + if (indexOf.call(contract_1, a) >= 0 && indexOf.call(contract_2, b) >= 0) { + console.log("looks like you have a circular dependency between " + a + " and " + b); + return exit; + } else if (indexOf.call(contract_1, b) >= 0) { + return 1; + } else if (indexOf.call(contract_2, a) >= 0) { + return -1; + } else { + return 0; + } + }; + })(this)); + + deployedContracts = {}; + + for (k = 0, len2 = all_contracts.length; k < len2; k++) { + className = all_contracts[k]; + contract = contractDB[className]; + contractGasLimit = (contractsConfig != null ? (ref1 = contractsConfig[className]) != null ? ref1.gasLimit : void 0 : void 0) || gasLimit; + contractGasPrice = (contractsConfig != null ? (ref2 = contractsConfig[className]) != null ? ref2.gasPrice : void 0 : void 0) || gasPrice; + args = (contractsConfig != null ? (ref3 = contractsConfig[className]) != null ? ref3.args : void 0 : void 0) || []; + contractObject = web3.eth.contract(contract.info.abiDefinition); + realArgs = []; + for (l = 0, len3 = args.length; l < len3; l++) { + arg = args[l]; + if (arg[0] === "$") { + realArgs.push(deployedContracts[arg.substr(1)]); + } else { + realArgs.push(arg); + } + } + contractParams = realArgs; + contractParams.push({ + from: primaryAddress, + data: contract.code, + gas: contractGasLimit, + gasPrice: contractGasPrice + }); + contractAddress = contractObject["new"].apply(contractObject, contractParams).address; + deployedContracts[className] = contractAddress; + console.log("address is " + contractAddress); + console.log("deployed " + className + " at " + contractAddress); + abi = JSON.stringify(contract.info.abiDefinition); + result += "var " + className + "Abi = " + abi + ";"; + result += "var " + className + "Contract = web3.eth.contract(" + className + "Abi);"; + result += "var " + className + " = " + className + "Contract.at('" + contractAddress + "');"; + } + + grunt.file.write(destFile, result); +} + +Deploy = { + deployContracts: deployContracts +} + +module.exports = Deploy + diff --git a/index.js b/lib/index.js similarity index 75% rename from index.js rename to lib/index.js index 29c50c09..824f1277 100644 --- a/index.js +++ b/lib/index.js @@ -13,7 +13,10 @@ var methodmissing = require('methodmissing'); var jasmine = require('jasmine'); embark = {} -embark.Tests = require('./lib/test.js'); +embark.Tests = require('./test.js'); +embark.Blockchain = require('./blockchain.js'); +embark.Deploy = require('./deploy.js'); +embark.Release = require('./ipfs.js'); module.exports = embark; diff --git a/lib/ipfs.js b/lib/ipfs.js new file mode 100644 index 00000000..725d1240 --- /dev/null +++ b/lib/ipfs.js @@ -0,0 +1,30 @@ +require('shelljs/global'); + +ipfs = function() { + ipfs_path = "~/go/bin"; + + build_dir = "dist/dapp/"; + + cmd = ipfs_path + "/ipfs add -r " + build_dir; + + grunt.log.writeln("=== adding " + cmd + " to ipfs"); + + result = exec(cmd); + + rows = result.output.split("\n"); + + dir_row = rows[rows.length - 2]; + + dir_hash = dir_row.split(" ")[1]; + + console.log("=== DApp available at http://localhost:8080/ipfs/" + dir_hash + "/"); + + console.log("=== DApp available at http://gateway.ipfs.io/ipfs/" + dir_hash + "/"); +} + +Release = { + ipfs: ipfs +} + +module.exports = Release + diff --git a/lib/test.js b/lib/test.js index 6da22a49..2f7a4409 100644 --- a/lib/test.js +++ b/lib/test.js @@ -9,11 +9,6 @@ py_exec = function(cmd) { return sync(python, cmd)[1].trim(); } -py_exec("from ethertdd import EvmContract") - -web3.setProvider(new web3.providers.HttpProvider('http://localhost:8101')); -web3.eth.defaultAccount = web3.eth.accounts[0]; - TestContractWrapper = (function() { function TestContractWrapper(contract, className, args) { this.contract = contract; @@ -64,23 +59,28 @@ TestContract = function(contract, className, args) { return Obj; } -//TODO: get the files from the config -contractFiles = grunt.file.expand("./app/contracts/**/*.sol") -contractDB = {} - -var i; -for (i = 0, len = contractFiles.length; i < len; i++) { - var contractFile = contractFiles[i]; - var source = fs.readFileSync(contractFile).toString() - - compiled_contracts = web3.eth.compile.solidity(source) - for (className in compiled_contracts) { - var contract = compiled_contracts[className]; - contractDB[className] = contract; - } -} - request = function(className, args) { + py_exec("from ethertdd import EvmContract") + + web3.setProvider(new web3.providers.HttpProvider('http://localhost:8101')); + web3.eth.defaultAccount = web3.eth.accounts[0]; + + //TODO: get the files from the config + contractFiles = grunt.file.expand("./app/contracts/**/*.sol") + contractDB = {} + + var i; + for (i = 0, len = contractFiles.length; i < len; i++) { + var contractFile = contractFiles[i]; + var source = fs.readFileSync(contractFile).toString() + + compiled_contracts = web3.eth.compile.solidity(source) + for (className in compiled_contracts) { + var contract = compiled_contracts[className]; + contractDB[className] = contract; + } + } + var contract = contractDB[className]; return TestContract(contract, className, args) } diff --git a/package.json b/package.json index be80db9b..f5c758b0 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "bin": { "embark": "./bin/embark" }, - "main": "./index.js", + "main": "./lib/index.js", "directories": { "lib": "./lib" }, @@ -38,5 +38,9 @@ }, "author": "Iuri Matias ", "contributors": [], - "license": "ISC" + "license": "ISC", + "devDependencies": { + "grunt-mocha-test": "^0.12.7", + "mocha": "^2.2.5" + } } diff --git a/tasks/blockchain.coffee b/tasks/blockchain.coffee index 89b2a953..b5bcde1f 100644 --- a/tasks/blockchain.coffee +++ b/tasks/blockchain.coffee @@ -1,71 +1,7 @@ module.exports = (grunt) -> - readYaml = require('read-yaml') - require('shelljs/global') grunt.registerTask "blockchain", "deploy ethereum node", (env_) => env = env_ || "development" - try - blockchainConfig = readYaml.sync("config/blockchain.yml") - catch exception - grunt.log.writeln("==== error reading config/blockchain.yml") - grunt.log.writeln(exception) - - rpcHost = blockchainConfig[env].rpc_host - rpcPort = blockchainConfig[env].rpc_port - rpcWhitelist = blockchainConfig[env].rpc_whitelist - - minerthreads = blockchainConfig[env].minerthreads - datadir = blockchainConfig[env].datadir - networkId = blockchainConfig[env].network_id || Math.floor(((Math.random()*100000)+1000)) - port = blockchainConfig[env].port || "30303" - console = blockchainConfig[env].console || false - mine_when_needed = blockchainConfig[env].mine_when_needed || false - - account = blockchainConfig[env].account - address = account.address - - cmd = "geth " - unless datadir is "default" - cmd += "--datadir=\"#{datadir}\" " - cmd += "--logfile=\"#{datadir}.log\" " - cmd += "--port #{port} " - cmd += "--rpc " - cmd += "--rpcport #{rpcPort} " - cmd += "--networkid #{networkId} " - cmd += "--rpccorsdomain \"#{rpcWhitelist}\" " - unless minerthreads is undefined - cmd += "--minerthreads \"#{minerthreads}\" " - cmd += "--mine " - - if account.password isnt undefined - cmd += "--password #{account.password} " - - if account.init - grunt.log.writeln("=== initializating account") - - grunt.log.writeln("running: #{cmd} account list") - result = exec(cmd + "account list") - grunt.log.writeln("finished") - grunt.log.writeln("=== output is #{result.output}") - if result.output.indexOf("Fatal") < 0 - grunt.log.writeln("=== already initialized") - address = result.output.match(/{(\w+)}/)[1] - else - grunt.log.writeln("running: #{cmd} account new") - output = exec(cmd + " account new") - address = output.output.match(/{(\w+)}/)[1] - - if address isnt undefined - cmd += "--unlock #{address} " - - if console - cmd += "console" - - if mine_when_needed - cmd += "js node_modules/embark-framework/js/mine.js" - - grunt.log.writeln("running: #{cmd}") - grunt.log.writeln("=== running geth") - #sh.run(cmd) - exec(cmd) + Embark = require('embark-framework') + Embark.Blockchain.startChain(env) diff --git a/tasks/deploy.coffee b/tasks/deploy.coffee index c4e83ef0..c926588d 100644 --- a/tasks/deploy.coffee +++ b/tasks/deploy.coffee @@ -2,99 +2,11 @@ module.exports = (grunt) -> web3 = require('web3') readYaml = require('read-yaml'); - grunt.registerTask "deploy_contracts", "deploy code", (env) => - blockchainConfig = readYaml.sync("config/blockchain.yml") - contractsConfig = readYaml.sync("config/contracts.yml")[env || "development"] - rpcHost = blockchainConfig[env || "development"].rpc_host - rpcPort = blockchainConfig[env || "development"].rpc_port - gasLimit = blockchainConfig[env || "development"].gas_limit || 500000 - gasPrice = blockchainConfig[env || "development"].gas_price || 10000000000000 + grunt.registerTask "deploy_contracts", "deploy code", (env_) => + env = env_ || "development" + contractFiles = grunt.file.expand(grunt.config.get("deploy.contracts")); + destFile = grunt.config.get("deploy.dest"); - try - web3.setProvider(new web3.providers.HttpProvider("http://#{rpcHost}:#{rpcPort}")) - primaryAddress = web3.eth.coinbase - web3.eth.defaultAccount = primaryAddress - catch e - grunt.log.writeln("==== can't connect to #{rpcHost}:#{rpcPort} check if an ethereum node is running") - exit - - grunt.log.writeln("address is : #{primaryAddress}") - - result = "web3.setProvider(new web3.providers.HttpProvider('http://#{rpcHost}:#{rpcPort}'));" - result += "web3.eth.defaultAccount = web3.eth.accounts[0];" - - contractDependencies = {} - if contractsConfig? - for className, options of contractsConfig - continue unless options.args? - for arg in options.args - if arg[0] is "$" - if contractDependencies[className] is undefined - contractDependencies[className] = [] - contractDependencies[className].push(arg.substr(1)) - - all_contracts = [] - contractDB = {} - - contractFiles = grunt.file.expand(grunt.config.get("deploy.contracts")) - for contractFile in contractFiles - source = grunt.file.read(contractFile) - - grunt.log.writeln("deploying #{contractFile}") - compiled_contracts = web3.eth.compile.solidity(source) - - for className, contract of compiled_contracts - all_contracts.push(className) - contractDB[className] = contract - - all_contracts.sort (a,b) => - contract_1 = contractDependencies[a]? or [] - contract_2 = contractDependencies[b]? or [] - - if a in contract_1 and b in contract_2 - grunt.log.writeln("looks like you have a circular dependency between #{a} and #{b}") - exit - else if b in contract_1 - 1 - else if a in contract_2 - -1 - else - 0 - - deployedContracts = {} - - for className in all_contracts - contract = contractDB[className] - contractGasLimit = contractsConfig?[className]?.gasLimit || gasLimit - contractGasPrice = contractsConfig?[className]?.gasPrice || gasPrice - - args = contractsConfig?[className]?.args || [] - - contractObject = web3.eth.contract(contract.info.abiDefinition) - - realArgs = [] - - for arg in args - if arg[0] is "$" - realArgs.push(deployedContracts[arg.substr(1)]) - else - realArgs.push(arg) - - contractParams = realArgs - contractParams.push({from: primaryAddress, data: contract.code, gas: contractGasLimit, gasPrice: contractGasPrice}) - contractAddress = contractObject.new.apply(contractObject, contractParams).address - deployedContracts[className] = contractAddress - - console.log "address is #{contractAddress}" - - grunt.log.writeln("deployed #{className} at #{contractAddress}") - - abi = JSON.stringify(contract.info.abiDefinition) - - result += "var #{className}Abi = #{abi};" - result += "var #{className}Contract = web3.eth.contract(#{className}Abi);" - result += "var #{className} = #{className}Contract.at('#{contractAddress}');"; - - destFile = grunt.config.get("deploy.dest") - grunt.file.write(destFile, result) + Embark = require('embark-framework') + Embark.Deploy.deployContracts(env, contractFiles, destFile) diff --git a/tasks/ipfs.coffee b/tasks/ipfs.coffee index 702b2352..fe11bed1 100644 --- a/tasks/ipfs.coffee +++ b/tasks/ipfs.coffee @@ -1,20 +1,6 @@ module.exports = (grunt) -> - require('shelljs/global') grunt.registerTask "ipfs", "distribute into ipfs", (env_) => - env = env_ || "development" - - ipfs_path = "~/go/bin" - build_dir = "dist/dapp/" - cmd = "#{ipfs_path}/ipfs add -r #{build_dir}" - - grunt.log.writeln("=== adding #{cmd} to ipfs") - result = exec(cmd) - - rows = result.output.split("\n") - dir_row = rows[rows.length - 2] - dir_hash = dir_row.split(" ")[1] - - grunt.log.writeln("=== DApp available at http://localhost:8080/ipfs/#{dir_hash}/") - grunt.log.writeln("=== DApp available at http://gateway.ipfs.io/ipfs/#{dir_hash}/") + Embark = require('embark-framework') + Embark.Release.ipfs() diff --git a/tasks/server.coffee b/tasks/server.coffee index ce11fa59..dfe607c8 100644 --- a/tasks/server.coffee +++ b/tasks/server.coffee @@ -1,11 +1,11 @@ module.exports = (grunt) -> express = require("express") compression = require("compression") - readYaml = require('read-yaml'); - - serverConfig = readYaml.sync("config/server.yml") + readYaml = require('read-yaml') grunt.registerTask "server", "static file development server", => + serverConfig = readYaml.sync("config/server.yml") + webPort = serverConfig.port || 8000 webHost = serverConfig.host || 'localhost' webRoot = "generated/dapp" diff --git a/test/test.js b/test/test.js new file mode 100644 index 00000000..6cb53b31 --- /dev/null +++ b/test/test.js @@ -0,0 +1,2 @@ +EmbarkSpec = require('../lib/test.js').Tests; +