Merge tag '2.4.0'

2.4.0
This commit is contained in:
Iuri Matias 2017-03-09 06:40:27 -05:00
commit e175cd6b56
145 changed files with 13429 additions and 12877 deletions

View File

@ -8,12 +8,16 @@ engines:
enabled: false
global-require:
enabled: false
guard-for-in:
enabled: false
ratings:
paths:
- "lib/**/*"
exclude_paths:
- "tests/"
- "test/"
- "old_test/"
- "boilerplate/"
- "demo/"
- "js/"
- "test_app/"
- "docs/"

7
.gitignore vendored
View File

@ -5,3 +5,10 @@ demo/dist/
demo/.embark/development/
demo/config/production/password
boilerplate/dist/
docs/_build
docs/utils/__pycache_
test_app/dist/
test_app/.embark/development/
test_app/config/production/password
test_app/node_modules/
test_app/chains.json

111
README.md
View File

@ -1,4 +1,3 @@
[![Join the chat at https://gitter.im/iurimatias/embark-framework](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iurimatias/embark-framework?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build
Status](https://travis-ci.org/iurimatias/embark-framework.svg?branch=develop)](https://travis-ci.org/iurimatias/embark-framework)
@ -7,16 +6,33 @@ Status](https://travis-ci.org/iurimatias/embark-framework.svg?branch=develop)](h
What is Embark
======
Embark is a framework that allows you to easily develop and deploy DApps.
Embark is a framework that allows you to easily develop and deploy Decentralized Applications (DApps).
A Decentralized Application is serverless html5 application that uses one or more decentralized technologies.
Embark currently integrates with EVM blockchains (Ethereum), Decentralized Storages (IPFS), and Decentralizaed communication platforms (Whisper and Orbit). Swarm is supported for deployment.
With Embark you can:
**Blockchain (Ethereum)**
* Automatically deploy contracts and make them available in your JS code. Embark watches for changes, and if you update a contract, Embark will automatically redeploy the contracts (if needed) and the dapp.
* Use any build pipeline or tool you wish, including grunt and meteor. (for 1.x, plugins coming soon for 2.x series)
* Contracts are available in JS with Promises.
* Do Test Driven Development with Contracts using Javascript.
* Easily deploy to & use decentralized systems such as IPFS.
* Keep track of deployed contracts, deploy only when truly needed.
* Manage different chains (e.g testnet, private net, livenet)
* Quickly create advanced DApps using multiple contracts that can interact with decentralized infrastructure for storage and comunication.
* Easily manage complex systems of interdependent contracts.
**Decentralized Storage (IPFS)**
* Easily Store & Retrieve Data on the DApp through EmbarkJS. Includin uploading and retrieving files.
* Deploy the full application to IPFS or Swarm.
**Decentralized Communication (Whisper, Orbit)**
* Easily send/receive messages through channels in P2P through Whisper or Orbit.
**Web Technologies**
* Integrate with any web technology including React, Foundation, etc..
* Use any build pipeline or tool you wish, including grunt, gulp and webpack.
Table of Contents
======
@ -28,18 +44,18 @@ Table of Contents
* [Using and Configuring Contracts](#dapp-structure)
* [EmbarkJS](#embarkjs)
* [EmbarkJS - Storage (IPFS)](#embarkjs---storage)
* [EmbarkJS - Communication (Whisper)](#embarkjs---communication)
* [EmbarkJS - Communication (Whisper/Orbit)](#embarkjs---communication)
* [Testing Contracts](#tests)
* [Working with different chains](#working-with-different-chains)
* [Custom Application Structure](#structuring-application)
* [Deploying to IPFS](#deploying-to-ipfs)
* [LiveReload Plugin](#livereload-plugin)
* [Extending Functionality with Plugins](#plugins)
* [Donations](#donations)
Installation
======
Requirements: geth (1.4.4 or higher), node (5.0.0) and npm
Optional: serpent (develop) if using contracts with Serpent, testrpc or ethersim if using the simulator or the test functionality.
Requirements: geth (1.5.8 or higher), node (6.9.1 or higher is recommended) and npm
Optional: testrpc (3.0 or higher) if using the simulator or the test functionality.
Further: depending on the dapp stack you choose: [IPFS](https://ipfs.io/)
```Bash
@ -58,6 +74,9 @@ Embark's npm package has changed from ```embark-framework``` to ```embark```, th
Usage - Demo
======
![Embark Demo screenshot](http://i.imgur.com/a9ddSjn.png)
You can easily create a sample working DApp with the following:
```Bash
@ -136,7 +155,7 @@ Solidity/Serpent files in the contracts directory will automatically be deployed
Libraries and languages available
======
Embark can build and deploy contracts coded in Solidity or Serpent. It will make them available on the client side using EmbarkJS and Web3.js.
Embark can build and deploy contracts coded in Solidity. It will make them available on the client side using EmbarkJS and Web3.js.
Further documentation for these can be found below:
@ -149,6 +168,7 @@ Embark will automatically take care of deployment for you and set all needed JS
```Javascript
# app/contracts/simple_storage.sol
pragma solidity ^0.4.7;
contract SimpleStorage {
uint public storedData;
@ -169,8 +189,8 @@ Will automatically be available in Javascript as:
```Javascript
# app/js/index.js
SimpleStorage.set(100);
SimpleStorage.get();
SimpleStorage.storedData();
SimpleStorage.get().then(function(value) { console.log(value.toNumber()) });
SimpleStorage.storedData().then(function(value) { console.log(value.toNumber()) });
```
You can specify for each contract and environment its gas costs and arguments:
@ -291,6 +311,12 @@ methods in EmbarkJS contracts will be converted to promises.
myContract.get().then(function(value) { console.log("value is " + value.toNumber) });
```
events:
```Javascript
myContract.eventName({from: web3.eth.accounts}, 'latest').then(function(event) { console.log(event) });
```
**deployment**
Client side deployment will be automatically available in Embark for existing contracts:
@ -346,17 +372,33 @@ The current available storage is IPFS. it can be initialized as
EmbarkJS.Storage.getUrl(hash);
```
note: if not using localhost, the cors needs to be set as ```ipfs --json API.HTTPHeaders.Access-Control-Allow-Origin '["your-host-name-port"]```
EmbarkJS - Communication
======
**initialization**
The current available communication is Whisper.
For Whisper:
```Javascript
EmbarkJS.Messages.setProvider('whisper')
```
For Orbit:
You'll need to use IPFS from master and run it as: ```ipfs daemon --enable-pubsub-experiment```
then set the provider:
```Javascript
EmbarkJS.Messages.setProvider('orbit', {server: 'localhost', port: 5001})
```
**listening to messages**
```Javascript
EmbarkJS.Messages.listenTo({topic: ["achannel", "anotherchannel"]}).then(function(message) { console.log("received: " + message); })
EmbarkJS.Messages.listenTo({topic: ["topic1", "topic2"]}).then(function(message) { console.log("received: " + message); })
```
**sending messages**
@ -364,15 +406,17 @@ The current available communication is Whisper.
you can send plain text
```Javascript
EmbarkJS.Messages.sendMessage({topic: "achannel", data: 'hello world'})
EmbarkJS.Messages.sendMessage({topic: "sometopic", data: 'hello world'})
```
or an object
```Javascript
EmbarkJS.Messages.sendMessage({topic: "achannel", data: {msg: 'hello world'}})
EmbarkJS.Messages.sendMessage({topic: "sometopic", data: {msg: 'hello world'}})
```
note: array of topics are considered an AND. In Whisper you can use another array for OR combinations of several topics e.g ```["topic1", ["topic2", "topic3"]]``` => ```topic1 AND (topic2 OR topic 3)```
Tests
======
@ -424,9 +468,9 @@ Working with different chains
You can specify which environment to deploy to:
```$ embark blockchain production```
```$ embark blockchain livenet```
```$ embark run production```
```$ embark run livenet```
The environment is a specific blockchain configuration that can be managed at config/blockchain.json
@ -439,7 +483,7 @@ The environment is a specific blockchain configuration that can be managed at co
"rpcPort": 8545,
"rpcCorsDomain": "http://localhost:8000",
"account": {
"password": "config/production/password"
"password": "config/livenet/password"
}
},
...
@ -448,7 +492,7 @@ The environment is a specific blockchain configuration that can be managed at co
Structuring Application
======
Embark is quite flexible and you can configure you're own directory structure using ```embark.json```
Embark is quite flexible and you can configure your own directory structure using ```embark.json```
```Json
# embark.json
@ -456,24 +500,39 @@ Embark is quite flexible and you can configure you're own directory structure us
"contracts": ["app/contracts/**"],
"app": {
"css/app.css": ["app/css/**"],
"images/": ["app/images/**"],
"js/app.js": ["embark.js", "app/js/**"],
"index.html": "app/index.html"
},
"buildDir": "dist/",
"config": "config/"
"config": "config/",
"plugins": {}
}
```
Deploying to IPFS
Deploying to IPFS and Swarm
======
To deploy a dapp to IPFS, all you need to do is run a local IPFS node and then run ```embark ipfs```.
If you want to deploy to the livenet then after configuring you account on ```config/blockchain.json``` on the ```production``` environment then you can deploy to that chain by specifying the environment ```embark ipfs production```.
To deploy a dapp to IPFS, all you need to do is run a local IPFS node and then run ```embark upload ipfs```.
If you want to deploy to the livenet then after configuring you account on ```config/blockchain.json``` on the ```livenet``` environment then you can deploy to that chain by specifying the environment ```embark ipfs livenet```.
LiveReload Plugin
To deploy a dapp to SWARM, all you need to do is run a local SWARM node and then run ```embark upload swarm```.
Plugins
======
Embark works quite well with the LiveReload Plugin
It's possible to extend Embarks functionality with plugins. For example the following is possible:
* plugin to add support for es6, jsx, coffescript, etc (``embark.registerPipeline``)
* plugin to add standard contracts or a contract framework (``embark.registerContractConfiguration`` and ``embark.addContractFile``)
* plugin to make some contracts available in all environments for use by other contracts or the dapp itself e.g a Token, a DAO, ENS, etc.. (``embark.registerContractConfiguration`` and ``embark.addContractFile``)
* plugin to add a libraries such as react or boostrap (``embark.addFileToPipeline``)
* plugin to specify a particular web3 initialization for special provider uses (``embark.registerClientWeb3Provider``)
* plugin to create a different contract wrapper (``embark.registerContractsGeneration``)
* plugin to add new console commands (``embark.registerConsoleCommand``)
* plugin to add support for another contract language such as viper, LLL, etc (``embark.registerCompiler``)
For more information on how to develop your own plugin please see the [plugin documentation](http://embark.readthedocs.io/en/latest/plugins.html)
Donations
======

View File

@ -2,3 +2,4 @@
node_modules/
dist/
config/production/password
config/livenet/password

View File

@ -1,10 +1,12 @@
{
"development": {
"enabled": true,
"networkType": "custom",
"genesisBlock": "config/development/genesis.json",
"datadir": ".embark/development/datadir",
"mineWhenNeeded": true,
"nodiscover": true,
"maxpeers": 0,
"rpcHost": "localhost",
"rpcPort": 8545,
"rpcCorsDomain": "http://localhost:8000",
@ -13,25 +15,34 @@
}
},
"testnet": {
"enabled": true,
"networkType": "testnet",
"rpcHost": "localhost",
"rpcPort": 8545
},
"livenet": {
"networkType": "livenet",
"light": true,
"rpcHost": "localhost",
"rpcPort": 8545,
"rpcCorsDomain": "http://localhost:8000",
"account": {
"password": "config/production/password"
"password": "config/testnet/password"
}
},
"livenet": {
"enabled": true,
"networkType": "livenet",
"light": true,
"rpcHost": "localhost",
"rpcPort": 8545,
"rpcCorsDomain": "http://localhost:8000",
"account": {
"password": "config/livenet/password"
}
},
"privatenet": {
"enabled": true,
"networkType": "custom",
"rpcHost": "localhost",
"rpcPort": 8545,
"datadir": "yourdatadir",
"networkId": "123",
"nodes": []
"bootnodes": ""
}
}

View File

@ -0,0 +1,7 @@
{
"default": {
"enabled": true,
"provider": "whisper",
"available_providers": ["whisper", "orbit"]
}
}

View File

@ -1 +0,0 @@
prod_password

View File

@ -0,0 +1,16 @@
{
"default": {
"enabled": true,
"ipfs_bin": "ipfs",
"provider": "ipfs",
"available_providers": ["ipfs"],
"host": "localhost",
"port": 5001
},
"development": {
"enabled": true,
"provider": "ipfs",
"host": "localhost",
"port": 5001
}
}

View File

@ -0,0 +1 @@
test_password

View File

@ -0,0 +1,5 @@
{
"enabled": true,
"host": "localhost",
"port": 8000
}

View File

@ -6,5 +6,6 @@
"index.html": "app/index.html"
},
"buildDir": "dist/",
"config": "config/"
"config": "config/",
"plugins": {}
}

View File

@ -1,16 +1,15 @@
{
"name": "app_name",
"name": "%APP_NAME%",
"version": "0.0.1",
"description": "",
"main": "Gruntfile.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "embark test"
},
"author": "",
"license": "ISC",
"homepage": "",
"devDependencies": {
"embark": "^2.1.4",
"embark": "^2.4.0",
"mocha": "^2.2.5"
}
}

1
demo/.gitignore vendored
View File

@ -2,3 +2,4 @@
node_modules/
dist/
config/production/password
config/livenet/password

View File

@ -1,4 +1,4 @@
pragma solidity ^0.4.2;
pragma solidity ^0.4.7;
contract SimpleStorage {
uint public storedData;

View File

@ -3,3 +3,47 @@ div {
margin: 15px;
}
.logs {
background-color: black;
font-size: 14px;
color: white;
font-weight: bold;
padding: 10px;
border-radius: 8px;
}
.tab-content {
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
border-bottom: 1px solid #ddd;
padding: 10px;
margin: 0px;
}
.nav-tabs {
margin-bottom: 0;
}
.status-offline {
vertical-align: middle;
margin-left: 5px;
margin-top: 4px;
width: 12px;
height: 12px;
background: red;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
}
.status-online {
vertical-align: middle;
margin-left: 5px;
margin-top: 4px;
width: 12px;
height: 12px;
background: mediumseagreen;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
}

0
demo/app/images/.gitkeep Normal file
View File

View File

@ -5,27 +5,101 @@
<script src="js/app.js"></script>
</head>
<body class="container">
<h3>Embark - SimpleStorage Demo</h3>
<h3>Embark - Usage Example</h3>
<h3> 1. Set the value in the blockchain</h3>
<div class="form-group form-inline">
<input type="text" class="text form-control" value="10">
<button class="set btn btn-primary">Set Value</button>
<p>Once you set the value, the transaction will need to be mined and then the value will be updated on the blockchain.</p>
</div>
<ul class="nav nav-tabs" role="tablist" id="myTabs">
<li role="presentation" class="active"><a href="#blockchain" aria-controls="blockchain" role="tab" data-toggle="tab">Blockchain</a></li>
<li role="presentation"><a href="#storage" aria-controls="storage" role="tab" data-toggle="tab">Decentralized Storage (IPFS)<span class="pull-right" id="status-storage"></a></li>
<li role="presentation"><a href="#communication" aria-controls="communication" role="tab" data-toggle="tab">P2P communication (Whisper/Orbit)<span class="pull-right" id="status-communication"></span></a></li>
</ul>
<h3> 2. Get the current value</h3>
<div class="form-group">
<div>
current value is <span class="value"></span>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="blockchain">
<h3> 1. Set the value in the blockchain</h3>
<div class="form-group form-inline">
<input type="text" class="text form-control" value="10">
<button class="set btn btn-primary">Set Value</button>
<p>Once you set the value, the transaction will need to be mined and then the value will be updated on the blockchain.</p>
</div>
<h3> 2. Get the current value</h3>
<div class="form-group">
<div>
current value is <span class="value"></span>
</div>
<button class="get btn btn-primary">Get Value</button>
<p>Click the button to get the current value. The initial value is 100.</p>
</div>
<h3> 3. Contract Calls </h3>
<p>Javascript calls being made: </p>
<div class="logs">
</div>
</div>
<button class="get btn btn-primary">Get Value</button>
<p>Click the button to get the current value. The initial value is 100.</p>
</div>
<div role="tabpanel" class="tab-pane" id="storage">
<div class="error alert alert-danger" role="alert">The node you are using does not support IPFS. Please ensure <a href="https://github.com/ipfs/js-ipfs-api#cors" target="_blank">CORS</a> is setup for the IPFS node.</div>
<div id="storage-controls">
<h3> 3. Contract Calls </h3>
<div class="logs">
<p>Javascript call being made: </p>
<h3>Save text to IPFS</h3>
<div class="form-group form-inline">
<input type="text" class="ipfsText text form-control" value="hello world!">
<button class="setIpfsText btn btn-primary">Save</button>
<p>generated Hash: <span class="textHash"></span></p>
</div>
<h3>Load text from IPFS given an hash</h3>
<div class="form-group form-inline">
<input type="text" class="textHash text form-control" size="60">
<button class="loadIpfsHash set btn btn-primary">Load</button>
<p>result: <span class="ipfsText"></span></p>
</div>
<h3>upload file to ipfs</h3>
<div class="form-group form-inline">
<input type="file" class="form-control">
<button class="uploadFile set btn btn-primary">upload</button>
<p>generated hash: <span class="fileIpfsHash"></span></p>
</div>
<h3>Get file or image from ipfs</h3>
<div class="form-group form-inline">
<input type="text" class="fileIpfsHash form-control" size="60">
<button class="loadIpfsFile set btn btn-primary">Load</button>
<p>file available at: <span class="ipfsFileUrl"></span></p>
<p><img class="ipfsImage" src=""></p>
</div>
<p>Javascript calls being made: </p>
<div class="logs">
<br> EmbarkJS.Storage.setProvider('ipfs',{server: 'localhost', port: '5001'})
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="communication">
<div class="error alert alert-danger" role="alert">The node you are using does not support Whisper</div>
<div id="communication-controls">
<h3>Listen To channel</h3>
<div class="form-group form-inline listen">
<input type="text" class="channel text form-control" placeholder="channel">
<button class="listenToChannel set btn btn-primary">Start Listening</button>
<div id="subscribeList"></div>
<p>messages received:<p>
<div id="messagesList"></div>
</div>
<h3>Send Message</h3>
<div class="form-group form-inline send">
<input type="text" class="channel text form-control" placeholder="channel">
<input type="text" class="message text form-control" placeholder="message">
<button class="sendMessage set btn btn-primary">Send Message</button>
</div>
<p>Javascript calls being made: </p>
<div class="logs">
<br> EmbarkJS.Messages.setProvider('whisper')
</div>
</div>
</div>
</div>
</body>

7
demo/app/js/_vendor/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,22 +1,119 @@
/*globals $, SimpleStorage, document*/
var addToLog = function(txt) {
$(".logs").append("<br>" + txt);
var addToLog = function(id, txt) {
$(id + " .logs").append("<br>" + txt);
};
// ===========================
// Blockchain example
// ===========================
$(document).ready(function() {
$("button.set").click(function() {
var value = parseInt($("input.text").val(), 10);
$("#blockchain button.set").click(function() {
var value = parseInt($("#blockchain input.text").val(), 10);
SimpleStorage.set(value);
addToLog("SimpleStorage.set(" + value + ")");
addToLog("#blockchain", "SimpleStorage.set(" + value + ")");
});
$("button.get").click(function() {
$("#blockchain button.get").click(function() {
SimpleStorage.get().then(function(value) {
$(".value").html(value.toNumber());
$("#blockchain .value").html(value.toNumber());
});
addToLog("SimpleStorage.get()");
addToLog("#blockchain", "SimpleStorage.get()");
});
});
// ===========================
// Storage (IPFS) example
// ===========================
$(document).ready(function() {
// automatic set if config/storage.json has "enabled": true and "provider": "ipfs"
//EmbarkJS.Storage.setProvider('ipfs',{server: 'localhost', port: '5001'});
$("#storage .error").hide();
EmbarkJS.Storage.ipfsConnection.ping()
.then(function(){
$("#status-storage").addClass('status-online');
$("#storage-controls").show();
})
.catch(function(err) {
if(err){
console.log("IPFS Connection Error => " + err.message);
$("#storage .error").show();
$("#status-storage").addClass('status-offline');
$("#storage-controls").hide();
}
});
$("#storage button.setIpfsText").click(function() {
var value = $("#storage input.ipfsText").val();
EmbarkJS.Storage.saveText(value).then(function(hash) {
$("span.textHash").html(hash);
$("input.textHash").val(hash);
});
addToLog("#storage", "EmbarkJS.Storage.saveText('" + value + "').then(function(hash) { })");
});
$("#storage button.loadIpfsHash").click(function() {
var value = $("#storage input.textHash").val();
EmbarkJS.Storage.get(value).then(function(content) {
$("span.ipfsText").html(content);
});
addToLog("#storage", "EmbarkJS.Storage.get('" + value + "').then(function(content) { })");
});
$("#storage button.uploadFile").click(function() {
var input = $("#storage input[type=file]");
EmbarkJS.Storage.uploadFile(input).then(function(hash) {
$("span.fileIpfsHash").html(hash);
$("input.fileIpfsHash").val(hash);
});
addToLog("#storage", "EmbarkJS.Storage.uploadFile($('input[type=file]')).then(function(hash) { })");
});
$("#storage button.loadIpfsFile").click(function() {
var hash = $("#storage input.fileIpfsHash").val();
var url = EmbarkJS.Storage.getUrl(hash);
var link = '<a href="' + url + '" target="_blank">' + url + '</a>';
$("span.ipfsFileUrl").html(link);
$(".ipfsImage").attr('src', url);
addToLog("#storage", "EmbarkJS.Storage.getUrl('" + hash + "')");
});
});
// ===========================
// Communication (Whisper) example
// ===========================
$(document).ready(function() {
$("#communication .error").hide();
web3.version.getWhisper(function(err, res) {
if (err) {
$("#communication .error").show();
$("#communication-controls").hide();
+ $("#status-communication").addClass('status-offline');
} else {
EmbarkJS.Messages.setProvider('whisper');
$("#status-communication").addClass('status-online');
}
});
$("#communication button.listenToChannel").click(function() {
var channel = $("#communication .listen input.channel").val();
$("#communication #subscribeList").append("<br> subscribed to " + channel + " now try sending a message");
EmbarkJS.Messages.listenTo({topic: [channel]}).then(function(message) {
$("#communication #messagesList").append("<br> channel: " + channel + " message: " + message);
});
addToLog("#communication", "EmbarkJS.Messages.listenTo({topic: ['" + channel + "']}).then(function(message) {})");
});
$("#communication button.sendMessage").click(function() {
var channel = $("#communication .send input.channel").val();
var message = $("#communication .send input.message").val();
EmbarkJS.Messages.sendMessage({topic: channel, data: message});
addToLog("#communication", "EmbarkJS.Messages.sendMessage({topic: '" + channel + "', data: '" + message + "'})");
});
});

View File

@ -1,2 +1 @@
{
}
{}

View File

@ -1,10 +1,12 @@
{
"development": {
"enabled": true,
"networkType": "custom",
"genesisBlock": "config/development/genesis.json",
"datadir": ".embark/development/datadir",
"mineWhenNeeded": true,
"nodiscover": true,
"maxpeers": 0,
"rpcHost": "localhost",
"rpcPort": 8545,
"rpcCorsDomain": "http://localhost:8000",
@ -13,25 +15,35 @@
}
},
"testnet": {
"enabled": true,
"networkType": "testnet",
"rpcHost": "localhost",
"rpcPort": 8545
},
"livenet": {
"networkType": "livenet",
"light": true,
"rpcHost": "localhost",
"rpcPort": 8545,
"rpcCorsDomain": "http://localhost:8000",
"account": {
"password": "config/production/password"
"password": "config/testnet/password"
}
},
"livenet": {
"enabled": true,
"networkType": "livenet",
"light": true,
"rpcHost": "localhost",
"rpcPort": 8545,
"rpcCorsDomain": "http://localhost:8000",
"account": {
"password": "config/livenet/password"
}
},
"privatenet": {
"enabled": true,
"networkType": "custom",
"rpcHost": "localhost",
"rpcPort": 8545,
"rpcCorsDomain": "http://localhost:8000",
"datadir": "yourdatadir",
"networkId": "123",
"nodes": []
"bootnodes": ""
}
}

View File

@ -0,0 +1,7 @@
{
"default": {
"enabled": true,
"provider": "whisper",
"available_providers": ["whisper", "orbit"]
}
}

16
demo/config/storage.json Normal file
View File

@ -0,0 +1,16 @@
{
"default": {
"enabled": true,
"ipfs_bin": "ipfs",
"provider": "ipfs",
"available_providers": ["ipfs"],
"host": "localhost",
"port": 5001
},
"development": {
"enabled": true,
"provider": "ipfs",
"host": "localhost",
"port": 5001
}
}

View File

@ -0,0 +1 @@
test_password

View File

@ -0,0 +1,5 @@
{
"enabled": true,
"host": "localhost",
"port": 8000
}

View File

@ -2,9 +2,12 @@
"contracts": ["app/contracts/**"],
"app": {
"css/app.css": ["app/css/**"],
"js/app.js": ["embark.js", "app/js/**"],
"images/": ["app/images/**"],
"js/app.js": ["embark.js", "app/js/_vendor/jquery.min.js", "app/js/_vendor/bootstrap.min.js", "app/js/**"],
"index.html": "app/index.html"
},
"buildDir": "dist/",
"config": "config/"
"config": "config/",
"plugins": {
}
}

View File

@ -10,7 +10,7 @@
"license": "ISC",
"homepage": "",
"devDependencies": {
"embark": "^2.1.4",
"embark": "^2.4.0",
"mocha": "^2.2.5"
}
}

20
docs/Makefile Normal file
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = embark
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

160
docs/conf.py Normal file
View File

@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
#
# ENS documentation build configuration file, created by
# sphinx-quickstart on Thu Dec 15 16:45:41 2016.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
from recommonmark.parser import CommonMarkParser
source_parsers = {
'.md': CommonMarkParser,
}
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.mathjax']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
source_suffix = ['.rst', '.md']
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Embark'
copyright = u'2017, Iuri Matias'
author = u'Iuri Matias'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = u'2.4'
# The full version, including alpha/beta/rc tags.
release = u'2.4.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'Embarkdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Embark.tex', u'Embark Documentation',
u'Iuri Matias \\textless{}\\textgreater{}', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'embark', u'Embark Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Embark', u'Embark Documentation',
author, 'Embark', 'One line description of project.',
'Miscellaneous'),
]

View File

@ -0,0 +1,22 @@
Configuring Communication (Whisper, Orbit)
==========================
Embark will check your prefered communication configuration in the file ``config/communication.json``. This file will contain the prefered configuration for each environment. With ``default`` being the configuration fields that applies to every environment. Each of those can be individually overriden in a per environment basis.
e.g :
.. code:: javascript
{
"default": {
"enabled": true,
"provider": "whisper",
"available_providers": ["whisper", "orbit"]
}
}
options available:
* ``enabled`` (boolean: true/false) to enable or completly disable communication support
* ``provider`` (string: "wisper" or "orbit") desired provider to automatically connect to on the dapp. e.g in the example above, seting this to ``"whisper"`` will automaticaly add ``EmbarkJS.setProvider('whisper')`` to the generated code
* ``available_providers`` (array: ["whisper", "orbit"]) list of communication platforms to be supported on the dapp. This will affect what's available with the EmbarkJS library on the dapp so if you don't need Orbit for e.g, removing it from this will considerably reduce the file size of the generated JS code.

View File

@ -0,0 +1,33 @@
Configuring Storage (IPFS)
==========================
Embark will check your prefered storage configuration in the file ``config/storage.json``. This file will contain the prefered configuration for each environment. With ``default`` being the configuration fields that applies to every environment. Each of those can be individually overriden in a per environment basis.
e.g :
.. code:: javascript
{
"default": {
"enabled": true,
"ipfs_bin": "ipfs",
"provider": "ipfs",
"available_providers": ["ipfs"],
"host": "localhost",
"port": 5001
},
"development": {
"enabled": true,
"provider": "ipfs",
"host": "localhost",
"port": 5001
}
}
options available:
* ``enabled`` (boolean: true/false) to enable or completly disable storage support
* ``ipfs_bin`` (string) name or desired path to the ipfs binary
* ``provider`` (string: "ipfs") desired provider to automatically connect to on the dapp. e.g in the example above, seting this to ``"ipfs"`` will automaticaly add ``EmbarkJS.setProvider('ipfs', {server: 'localhost', 5001})`` to the generated code
* ``available_providers`` (array: ["ipfs"]) list of storages to be supported on the dapp. This will affect what's available with the EmbarkJS library on the dapp.
* ``host`` and ``port`` of the ipfs node to connect to.

View File

@ -0,0 +1,42 @@
Creating a new DApp
===================
If you want to create a blank new app.
.. code:: bash
$ embark new AppName
$ cd AppName
To run Embark then in one console run:
.. code:: bash
$ embark blockchain
And in another console run:
.. code:: bash
$ embark run
DApp Structure
==============
.. code:: bash
app/
|___ contracts/ #solidity smart contracts
|___ html/
|___ css/
|___ js/
config/
|___ blockchain.json #rpc and blockchain configuration
|___ contracts.json #ethereum contracts configuration
|___ storage.json #ipfs configuration
|___ communication.json #whisper/orbit configuration
|___ webserver.json #dev webserver configuration
test/
|___ #contracts tests
Solidity files in the contracts directory will automatically be deployed with ``embark run``. Changes in any files will automatically be reflected in app, changes to contracts will result in a redeployment and update of their JS Bindings

24
docs/dashboard.rst Normal file
View File

@ -0,0 +1,24 @@
Dashboard
=========
Embark 2 comes with a terminal dashboard.
.. figure:: http://i.imgur.com/s4OQZpu.jpg
:alt: Dashboard
Dashboard
The dashboard will tell you the state of your contracts, the enviroment
you are using, and what embark is doing at the moment.
**available services**
Available Services will display the services available to your dapp in
green, if one of these is down then it will be displayed in red.
**logs and console**
There is a console at the bottom which can be used to interact with
contracts or with embark itself. type ``help`` to see a list of
available commands, more commands will be added with each version of
Embark.

View File

@ -0,0 +1,8 @@
Deploying to IPFS
=================
To deploy a dapp to IPFS, all you need to do is run a local IPFS node
and then run ``embark upload ipfs``. If you want to deploy to the livenet then
after configuring you account on ``config/blockchain.json`` on the
``livenet`` environment then you can deploy to that chain by
specifying the environment ``embark ipfs livenet``.

View File

@ -0,0 +1,4 @@
Deploying to SWARM
==================
To deploy a dapp to SWARM, all you need to do is run a local SWARM node and then run ``embark upload swarm``.

4
docs/donations.md Normal file
View File

@ -0,0 +1,4 @@
Donations
======
If you like Embark please consider donating to 0x8811FdF0F988f0CD1B7E9DE252ABfA5b18c1cDb1

5
docs/donations.rst Normal file
View File

@ -0,0 +1,5 @@
Donations
=========
If you like Embark please consider donating to
0x8811FdF0F988f0CD1B7E9DE252ABfA5b18c1cDb1

View File

@ -0,0 +1,46 @@
EmbarkJS - Communication (Whisper, Orbit)
=========================================
**initialization**
For Whisper:
.. code:: javascript
EmbarkJS.Messages.setProvider('whisper')
For Orbit:
You'll need to use IPFS from master and run it as:
``ipfs daemon --enable-pubsub-experiment``
then set the provider:
.. code:: javascript
EmbarkJS.Messages.setProvider('orbit', {server: 'localhost', port: 5001})
**listening to messages**
.. code:: javascript
EmbarkJS.Messages.listenTo({topic: ["topic1", "topic2"]}).then(function(message) { console.log("received: " + message); })
**sending messages**
you can send plain text
.. code:: javascript
EmbarkJS.Messages.sendMessage({topic: "sometopic", data: 'hello world'})
or an object
.. code:: javascript
EmbarkJS.Messages.sendMessage({topic: "sometopic", data: {msg: 'hello world'}})
note: array of topics are considered an AND. In Whisper you can use
another array for OR combinations of several topics e.g
``["topic1", ["topic2", "topic3"]]`` =>
``topic1 AND (topic2 OR topic 3)``

39
docs/embarkjs-storage.rst Normal file
View File

@ -0,0 +1,39 @@
EmbarkJS - Storage (IPFS)
=========================
**initialization**
The current available storage is IPFS. it can be initialized as
.. code:: javascript
EmbarkJS.Storage.setProvider('ipfs',{server: 'localhost', port: '5001'})
**Saving Text**
.. code:: javascript
EmbarkJS.Storage.saveText("hello world").then(function(hash) {});
**Retrieving Data/Text**
.. code:: javascript
EmbarkJS.Storage.get(hash).then(function(content) {});
**Uploading a file**
.. code:: html
<input type="file">
.. code:: javascript
var input = $("input[type=file"]);
EmbarkJS.Storage.uploadFile(input).then(function(hash) {});
**Generate URL to file**
.. code:: javascript
EmbarkJS.Storage.getUrl(hash);

30
docs/embarkjs.rst Normal file
View File

@ -0,0 +1,30 @@
EmbarkJS
========
EmbarkJS is a javascript library meant to abstract and facilitate the
development of DApps.
**promises**
methods in EmbarkJS contracts will be converted to promises.
.. code:: javascript
var myContract = new EmbarkJS.Contract({abi: abiObject, address: "0x123"});
myContract.get().then(function(value) { console.log("value is " + value.toNumber) });
**deployment**
Client side deployment will be automatically available in Embark for
existing contracts:
.. code:: javascript
SimpleStorage.deploy().then(function(anotherSimpleStorage) {});
or it can be manually definied as
.. code:: javascript
var myContract = new EmbarkJS.Contract({abi: abiObject, code: code});
myContract.deploy().then(function(anotherMyContractObject) {});

40
docs/index.rst Normal file
View File

@ -0,0 +1,40 @@
.. embark documentation master file, created by
sphinx-quickstart on Tue Jan 10 06:39:27 2017.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to embark's documentation!
==================================
This is a work in progress, feel free to contribute!
.. toctree::
:maxdepth: 2
installation.rst
usage.rst
dashboard.rst
creating-a-new-dapp.rst
libraries-and-languages-available.rst
using-contracts.rst
configuring-storage.rst
configuring-communication.rst
embarkjs.rst
embarkjs-storage.rst
embarkjs-communication.rst
tests.rst
working-with-different-chains.rst
structuring-application.rst
deploying-to-ipfs.rst
deploying-to-swarm.rst
plugins.rst
using-embark-with-grunt.rst
donations.rst
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

24
docs/installation.rst Normal file
View File

@ -0,0 +1,24 @@
Installation
============
Requirements: geth (1.5.8 or higher), node (6.9.1 or higher is recommended) and npm
serpent (develop) if using contracts with Serpent, testrpc (3.0 or higher)
if using the simulator or the test functionality. Further: depending on
the dapp stack you choose: `IPFS <https://ipfs.io/>`__
.. code:: bash
$ npm -g install embark
# If you plan to use the simulator instead of a real ethereum node.
$ npm -g install ethereumjs-testrpc
See `Complete Installation
Instructions <https://github.com/iurimatias/embark-framework/wiki/Installation>`__.
**updating from embark 1**
Embark's npm package has changed from ``embark-framework`` to
``embark``, this sometimes can create conflicts. To update first
uninstall embark-framework 1 to avoid any conflicts.
``npm uninstall -g embark-framework`` then ``npm install -g embark``

View File

@ -0,0 +1,14 @@
Libraries and languages available
=================================
Embark can build and deploy contracts coded in Solidity or Serpent. It
will make them available on the client side using EmbarkJS and Web3.js.
Further documentation for these can be found below:
- Smart Contracts:
`Solidity <https://solidity.readthedocs.io/en/develop/>`__ and
`Serpent <https://github.com/ethereum/wiki/wiki/Serpent>`__
- Client Side:
`Web3.js <https://github.com/ethereum/wiki/wiki/JavaScript-API>`__
and `EmbarkJS <#embarkjs>`__

36
docs/make.bat Normal file
View File

@ -0,0 +1,36 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=embark
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd

296
docs/plugins.rst Normal file
View File

@ -0,0 +1,296 @@
Extending functionality with plugins
====================================
**To add a plugin to embark:**
1. Add the npm package to package.json
e.g ``npm install embark-babel --save``
2. Then add the package to ``plugins:`` in embark.json
e.g ``"plugins": { "embark-babel": {} }``
**Creating a plugin:**
1. ``mkdir yourpluginname``
2. ``cd yourpluginname``
3. ``npm init``
4. create and edit ``index.js``
5. add the following code:
.. code:: javascript
module.exports = function(embark) {
}
The ``embark`` object then provides an api to extend different functionality of embark.
**Usecases examples**
* plugin to add support for es6, jsx, coffescript, etc (``embark.registerPipeline``)
* plugin to add standard contracts or a contract framework (``embark.registerContractConfiguration`` and ``embark.addContractFile``)
* plugin to make some contracts available in all environments for use by other contracts or the dapp itself e.g a Token, a DAO, ENS, etc.. (``embark.registerContractConfiguration`` and ``embark.addContractFile``)
* plugin to add a libraries such as react or boostrap (``embark.addFileToPipeline``)
* plugin to specify a particular web3 initialization for special provider uses (``embark.registerClientWeb3Provider``)
* plugin to create a different contract wrapper (``embark.registerContractsGeneration``)
* plugin to add new console commands (``embark.registerConsoleCommand``)
* plugin to add support for another contract language such as viper, LLL, etc (``embark.registerCompiler``)
* plugin that executes certain actions when contracts are deployed (``embark.events.on``)
**embark.pluginConfig**
Object containing the config for the plugin specified in embark.json, for e.g with:
.. code:: json
"plugins": {
"embark-babel": { "files": ["**/*.js", "!**/jquery.min.js"], "presets": ["es2015", "react"] }
}
``embark.pluginConfig`` will contain ``{ "files": ["**/*.js", "!**/jquery.min.js"], "presets": ["es2015", "react"] }``
**embark.registerPipeline(matchingFiles, callback(options))**
This call will return the content of the current asset file so the plugin can transform it in some way. Typically this is used to implement pipeline plugins such as Babel, JSX, sass to css, etc..
``matchingFiles`` is an array of matching files the plugin should be called for e.g [``**/*.js``, ``!vendor/jquery.js``] matches all javascript files except vendor/jquery.js
options available:
* targetFile - filename to be generated
* source - content of the file
expected return: ``string``
.. code:: javascript
var babel = require("babel-core");
require("babel-preset-react");
module.exports = function(embark) {
embark.registerPipeline(["**/*.js", "**/*.jsx"], function(options) {
return babel.transform(options.source, {minified: true, presets: ['react']}).code;
});
}
**embark.registerContractConfiguration(contractsConfig)**
This call is used to specify a configure of one or more contracts in one or
several environments. This is useful for specifying the different configurations
a contract might have depending on the enviroment. For instance in the code
bellow, the ``DGDToken`` contract code will redeployed with the arguments
``100`` in any environment, except for the livenet since it's already deployed
there at a particular address.
Typically this call is used in combination with ``embark.addContractFile``
``contractsConfig`` is an object in the same structure as the one found in the
contracts configuration at ``config/contracts.json``. The users own
configuration will be merged with the one specified in the plugins.
.. code:: javascript
module.exports = function(embark) {
embark.registerContractConfiguration({
"default": {
"contracts": {
"DGDToken": {
"args": [
100
]
}
}
},
"livenet": {
"contracts": {
"DGDToken": {
"address": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a"
}
}
}
});
}
**embark.addContractFile(file)**
Typically this call is used in combination with ``embark.registerContractConfiguration``. If you want to make the contract available but not automatically deployed without the user specifying so you can use ``registerContractConfiguration`` to set the contract config to ``deploy: false``, this is particularly useful for when the user is meant to extend the contract being given (e.g ``contract MyToken is StandardToken``)
``file`` is the contract file to add to embark, the path should relative to the plugin.
.. code:: javascript
module.exports = function(embark) {
embark.addContractFile("./DGDToken.sol");
}
**embark.addFileToPipeline(file, options)**
This call is used to add a file to the pipeline so it's included with the dapp on the client side.
``file`` is the file to add to the pipeline, the path should relative to the plugin.
``options`` available:
* skipPipeline - If true it will not apply transformations to the file. For
example if you have a babel plugin to transform es6 code or a minifier plugin, setting this to
true will not apply the plugin on this file.
.. code:: javascript
module.exports = function(embark) {
embark.addFileToPipeline("./jquery.js", {skipPipeline: true});
}
**embark.registerClientWeb3Provider(callback(options))**
This call can be used to override the default web3 object generation in the dapp. it's useful if you want to add a plugin to interact with services like http://infura.io or if you want to use your own web3.js library extension.
options available:
* rpcHost - configured rpc Host to connect to
* rpcPort - configured rpc Port to connect to
* blockchainConfig - object containing the full blockchain configuration for the current environment
expected return: ``string``
example:
.. code:: javascript
module.exports = function(embark) {
embark.registerClientWeb3Provider(function(options) {
return "web3 = new Web3(new Web3.providers.HttpProvider('http://" + options.rpcHost + ":" + options.rpcPort + "');";
});
}
**embark.registerContractsGeneration(callback(options))**
By default Embark will use EmbarkJS to declare contracts in the dapp. You can override and use your own client side library.
options available:
* contracts - Hash of objects containing all the deployed contracts. (key: contractName, value: contract object)
* abiDefinition
* code
* deployedAddress
* gasEstimates
* gas
* gasPrice
* runtimeByteCode
expected return: ``string``
.. code:: javascript
module.exports = function(embark) {
embark.registerContractsGeneration(function(options) {
for(var className in this.contractsManager.contracts) {
var abi = JSON.stringify(contract.abiDefinition);
return className + " = " + web3.eth.contract(" + abi + ").at('" + contract.deployedAddress + "');";
}
});
}
**embark.registerConsoleCommand(callback(options))**
This call is used to extend the console with custom commands.
expected return: ``string`` (output to print in console) or ``boolean`` (skip command if false)
.. code:: javascript
module.exports = function(embark) {
embark.registerConsoleCommand(function(cmd, options) {
if (cmd === "hello") {
return "hello there!";
}
// continue to embark or next plugin;
return false;
});
}
**embark.registerCompiler(extension, callback(contractFiles, doneCallback))**
expected doneCallback arguments: ``err`` and ``hash`` of compiled contracts
* Hash of objects containing the compiled contracts. (key: contractName, value: contract object)
* code - contract bytecode (string)
* runtimeBytecode - contract runtimeBytecode (string)
* gasEstimates - gas estimates for constructor and methods (hash)
* e.g ``{"creation":[20131,38200],"external":{"get()":269,"set(uint256)":20163,"storedData()":224},"internal":{}}``
* functionHashes - object with methods and their corresponding hash identifier (hash)
* e.g ``{"get()":"6d4ce63c","set(uint256)":"60fe47b1","storedData()":"2a1afcd9"}``
* abiDefinition - contract abi (array of objects)
* e.g ``[{"constant":true,"inputs":[],"name":"storedData","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"}, etc...``
below a possible implementation of a solcjs plugin:
.. code:: javascript
var solc = require('solc');
module.exports = function(embark) {
embark.registerCompiler(".sol", function(contractFiles, cb) {
// prepare input for solc
var input = {};
for (var i = 0; i < contractFiles.length; i++) {
var filename = contractFiles[i].filename.replace('app/contracts/','');
input[filename] = contractFiles[i].content.toString();
}
// compile files
var output = solc.compile({sources: input}, 1);
// generate the compileObject expected by embark
var json = output.contracts;
var compiled_object = {};
for (var className in json) {
var contract = json[className];
compiled_object[className] = {};
compiled_object[className].code = contract.bytecode;
compiled_object[className].runtimeBytecode = contract.runtimeBytecode;
compiled_object[className].gasEstimates = contract.gasEstimates;
compiled_object[className].functionHashes = contract.functionHashes;
compiled_object[className].abiDefinition = JSON.parse(contract.interface);
}
cb(null, compiled_object);
});
}
**embark.logger**
To print messages to the embark log is it better to use ``embark.logger``
instead of ``console``.
e.g ``embark.logger.info("hello")``
**embark.events.on(eventName, callback(*args))**
This call is used to listen and react to events that happen in Embark such as contract deployment
* eventName - name of event to listen to
* available events:
* "contractsDeployed" - triggered when contracts have been deployed
* "file-add", "file-change", "file-remove", "file-event" - triggered on
a file change, args is (filetype, path)
* "abi", "abi-vanila", "abi-contracts-vanila" - triggered when contracts
have been deployed and returns the generated JS code
* "outputDone" - triggered when dapp is (re)generated
* "firstDeploymentDone" - triggered when the dapp is deployed and generated
for the first time
.. code:: javascript
module.exports = function(embark) {
embark.events.on("contractsDeployed", function() {
embark.logger.info("plugin says: your contracts have been deployed");
});
embark.events.on("file-changed", function(filetype, path) {
if (type === 'contract') {
embark.logger.info("plugin says: you just changed the contract at " + path);
}
});
}

View File

@ -0,0 +1,22 @@
Structuring Application
=======================
Embark is quite flexible and you can configure you're own directory
structure using ``embark.json``
.. code:: json
# embark.json
{
"contracts": ["app/contracts/**"],
"app": {
"css/app.css": ["app/css/**"],
"images/": ["app/images/**"],
"js/app.js": ["embark.js", "app/js/**"],
"index.html": "app/index.html"
},
"buildDir": "dist/",
"config": "config/",
"plugins": {}
}

48
docs/tests.rst Normal file
View File

@ -0,0 +1,48 @@
Testing Ethereum Contracts
==========================
You can run specs with ``embark test``, it will run any test files under
``test/``.
Embark includes a testing lib to fastly run & test your contracts in a
EVM.
.. code:: javascript
# test/simple_storage_spec.js
var assert = require('assert');
var Embark = require('embark');
var EmbarkSpec = Embark.initTests();
var web3 = EmbarkSpec.web3;
describe("SimpleStorage", function() {
before(function(done) {
var contractsConfig = {
"SimpleStorage": {
args: [100]
}
};
EmbarkSpec.deployAll(contractsConfig, done);
});
it("should set constructor value", function(done) {
SimpleStorage.storedData(function(err, result) {
assert.equal(result.toNumber(), 100);
done();
});
});
it("set storage value", function(done) {
SimpleStorage.set(150, function() {
SimpleStorage.get(function(err, result) {
assert.equal(result.toNumber(), 150);
done();
});
});
});
});
Embark uses `Mocha <http://mochajs.org/>`__ by default, but you can use
any testing framework you want.

44
docs/usage.rst Normal file
View File

@ -0,0 +1,44 @@
Usage
=====
Usage - Demo
============
You can easily create a sample working DApp with the following:
.. code:: bash
$ embark demo
$ cd embark_demo
You can run a REAL ethereum node for development purposes:
.. code:: bash
$ embark blockchain
Alternatively, to use an ethereum rpc simulator simply run:
.. code:: bash
$ embark simulator
By default embark blockchain will mine a minimum amount of ether and
will only mine when new transactions come in. This is quite usefull to
keep a low CPU. The option can be configured at
``config/blockchain.json``. Note that running a real node requires at
least 2GB of free ram, please take this into account if running it in a
VM.
Then, in another command line:
.. code:: bash
$ embark run
This will automatically deploy the contracts, update their JS bindings
and deploy your DApp to a local server at http://localhost:8000
Note that if you update your code it will automatically be re-deployed,
contracts included. There is no need to restart embark, refreshing the
page on the browser will do.

138
docs/using-contracts.rst Normal file
View File

@ -0,0 +1,138 @@
Configuring & Using Contracts
===============
Embark will automatically take care of deployment for you and set all
needed JS bindings. For example, the contract below:
.. code:: javascript
# app/contracts/simple_storage.sol
contract SimpleStorage {
uint public storedData;
function SimpleStorage(uint initialValue) {
storedData = initialValue;
}
function set(uint x) {
storedData = x;
}
function get() constant returns (uint retVal) {
return storedData;
}
}
Will automatically be available in Javascript as:
.. code:: javascript
# app/js/index.js
SimpleStorage.set(100);
SimpleStorage.get().then(function(value) { console.log(value.toNumber()) });
SimpleStorage.storedData().then(function(value) { console.log(value.toNumber()) });
You can specify for each contract and environment its gas costs and
arguments:
.. code:: json
# config/contracts.json
{
"development": {
"gas": "auto",
"contracts": {
"SimpleStorage": {
"args": [
100
]
}
}
}
}
If you are using multiple contracts, you can pass a reference to another
contract as ``$ContractName``, Embark will automatically replace this
with the correct address for the contract.
.. code:: json
# config/contracts.json
{
...
"development": {
"contracts": {
"SimpleStorage": {
"args": [
100,
$MyStorage
]
},
"MyStorage": {
"args": [
"initial string"
]
},
"MyMainContract": {
"args": [
$SimpleStorage
]
}
}
}
...
}
You can now deploy many instances of the same contract. e.g
.. code:: json
# config/contracts.json
{
"development": {
"contracts": {
"Currency": {
"deploy": false,
"args": [
100
]
},
"Usd": {
"instanceOf": "Currency",
"args": [
200
]
},
"MyCoin": {
"instanceOf": "Currency",
"args": [
200
]
}
}
}
}
...
Contracts addresses can be defined, If an address is defined the
contract wouldn't be deployed but its defined address will be used
instead.
.. code:: json
# config/contracts.json
{
...
"development": {
"contracts": {
"UserStorage": {
"address": "0x123456"
},
"UserManagement": {
"args": [
"$UserStorage"
]
}
}
}
...
}

View File

@ -0,0 +1,34 @@
Using Embark with Grunt
====================================
**1. Edit embark.json**
Edit ``embark.json`` to have the line ``"js/app.js": ["embark.js"]``, this will make embark create the file containing the contracts initilization to ``dist/app.js``.
.. code:: json
{
"contracts": ["app/contracts/**"],
"app": {
"app.js": ["embark.js"]
},
"buildDir": "dist/",
"config": "config/",
"plugins": {
}
}
**2. add the generated file to Grunt config file so it's included with the other assets**
.. code:: coffee
module.exports = (grunt) ->
grunt.initConfig(
files:
js:
src: [
"dist/app.js"
"app/js/**/*.js"
]

View File

@ -0,0 +1,26 @@
Working with different chains
=============================
You can specify which environment to deploy to:
``$ embark blockchain livenet``
``$ embark run livenet``
The environment is a specific blockchain configuration that can be
managed at config/blockchain.json
.. code:: json
# config/blockchain.json
...
"livenet": {
"networkType": "livenet",
"rpcHost": "localhost",
"rpcPort": 8545,
"rpcCorsDomain": "http://localhost:8000",
"account": {
"password": "config/livenet/password"
}
},
...

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
var Promise = require('bluebird');
/*jshint esversion: 6 */
//var Ipfs = require('./ipfs.js');
var EmbarkJS = {
@ -10,7 +10,7 @@ EmbarkJS.Contract = function(options) {
this.abi = options.abi;
this.address = options.address;
this.code = options.code;
this.code = '0x' + options.code;
this.web3 = options.web3 || web3;
var ContractClass = this.web3.eth.contract(this.abi);
@ -59,40 +59,73 @@ EmbarkJS.Contract = function(options) {
};
return true;
} else if (typeof self._originalContractObject[p] === 'function') {
self[p] = Promise.promisify(self._originalContractObject[p]);
self[p] = function(_args) {
var args = Array.prototype.slice.call(arguments);
var fn = self._originalContractObject[p];
var props = self.abi.find((x) => x.name == p);
var promise = new Promise(function(resolve, reject) {
args.push(function(err, transaction) {
promise.tx = transaction;
if (err) {
return reject(err);
}
var getConfirmation = function() {
self.web3.eth.getTransactionReceipt(transaction, function(err, receipt) {
if (err) {
return reject(err);
}
if (receipt !== null) {
return resolve(receipt);
}
setTimeout(getConfirmation, 1000);
});
};
if (typeof(transaction) !== "string" || props.constant) {
resolve(transaction);
} else {
getConfirmation();
}
});
fn.apply(fn, args);
});
return promise;
};
return true;
}
return false;
});
};
EmbarkJS.Contract.prototype.deploy = function(args) {
EmbarkJS.Contract.prototype.deploy = function(args, _options) {
var self = this;
var contractParams;
var options = _options || {};
contractParams = args || [];
contractParams.push({
from: this.web3.eth.accounts[0],
data: this.code,
gas: 500000,
gasPrice: 10000000000000
gas: options.gas || 800000
});
var contractObject = this.web3.eth.contract(this.abi);
var promise = new Promise(function(resolve, reject) {
contractParams.push(function(err, transaction) {
console.log("callback");
if (err) {
console.log("error");
reject(err);
} else if (transaction.address !== undefined) {
console.log("address contract: " + transaction.address);
resolve(new EmbarkJS.Contract({abi: self.abi, code: self.code, address: transaction.address}));
}
});
console.log(contractParams);
// returns promise
// deploys contract
@ -109,13 +142,14 @@ EmbarkJS.IPFS = 'ipfs';
EmbarkJS.Storage = {
};
// EmbarkJS.Storage.setProvider('ipfs',{server: 'localhost', port: '5001'})<F37>
//{server: localhost, port: 5001};
EmbarkJS.Storage.setProvider = function(provider, options) {
if (provider === 'ipfs') {
this.currentStorage = EmbarkJS.Storage.IPFS;
this.ipfsConnection = IpfsApi(options.server, options.port);
if (options === undefined) {
this.ipfsConnection = IpfsApi('localhost', '5001');
} else {
this.ipfsConnection = IpfsApi(options.server, options.port);
}
} else {
throw Error('unknown provider');
}
@ -123,6 +157,9 @@ EmbarkJS.Storage.setProvider = function(provider, options) {
EmbarkJS.Storage.saveText = function(text) {
var self = this;
if (!this.ipfsConnection) {
this.setProvider('ipfs');
}
var promise = new Promise(function(resolve, reject) {
self.ipfsConnection.add((new self.ipfsConnection.Buffer(text)), function(err, result) {
if (err) {
@ -144,6 +181,10 @@ EmbarkJS.Storage.uploadFile = function(inputSelector) {
throw new Error('no file found');
}
if (!this.ipfsConnection) {
this.setProvider('ipfs');
}
var promise = new Promise(function(resolve, reject) {
var reader = new FileReader();
reader.onloadend = function() {
@ -167,6 +208,9 @@ EmbarkJS.Storage.get = function(hash) {
var self = this;
// TODO: detect type, then convert if needed
//var ipfsHash = web3.toAscii(hash);
if (!this.ipfsConnection) {
this.setProvider('ipfs');
}
var promise = new Promise(function(resolve, reject) {
self.ipfsConnection.object.get([hash]).then(function(node) {
@ -186,20 +230,45 @@ EmbarkJS.Storage.getUrl = function(hash) {
EmbarkJS.Messages = {
};
EmbarkJS.Messages.setProvider = function(provider) {
EmbarkJS.Messages.setProvider = function(provider, options) {
var self = this;
var ipfs;
if (provider === 'whisper') {
this.currentMessages = EmbarkJS.Messages.Whisper;
if (typeof variable === 'undefined') {
if (options === undefined) {
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
} else {
web3 = new Web3(new Web3.providers.HttpProvider("http://" + options.server + ':' + options.port));
}
}
web3.version.getWhisper(function(err, res) {
if (err) {
console.log("whisper not available");
} else {
self.currentMessages.identity = web3.shh.newIdentity();
}
});
} else if (provider === 'orbit') {
this.currentMessages = EmbarkJS.Messages.Orbit;
if (options === undefined) {
ipfs = HaadIpfsApi('localhost', '5001');
} else {
ipfs = HaadIpfsApi(options.server, options.port);
}
this.currentMessages.orbit = new Orbit(ipfs);
this.currentMessages.orbit.connect(web3.eth.accounts[0]);
} else {
throw Error('unknown provider');
}
};
EmbarkJS.Messages.sendMessage = function(options) {
return EmbarkJS.Messages.Whisper.sendMessage(options);
return this.currentMessages.sendMessage(options);
};
EmbarkJS.Messages.listenTo = function(options) {
return EmbarkJS.Messages.Whisper.listenTo(options);
return this.currentMessages.listenTo(options);
};
EmbarkJS.Messages.Whisper = {
@ -208,9 +277,10 @@ EmbarkJS.Messages.Whisper = {
EmbarkJS.Messages.Whisper.sendMessage = function(options) {
var topics = options.topic || options.topics;
var data = options.data || options.payload;
var identity = options.identity || web3.shh.newIdentity();
var identity = options.identity || this.identity || web3.shh.newIdentity();
var ttl = options.ttl || 100;
var priority = options.priority || 1000;
var _topics;
if (topics === undefined) {
throw new Error("missing option: topic");
@ -222,42 +292,41 @@ EmbarkJS.Messages.Whisper.sendMessage = function(options) {
// do fromAscii to each topics unless it's already a string
if (typeof topics === 'string') {
topics = topics;
_topics = [web3.fromAscii(topics)];
} else {
// TODO: replace with es6 + babel;
var _topics = [];
for (var i = 0; i < topics.length; i++) {
_topics.push(web3.fromAscii(topics[i]));
}
topics = _topics;
}
topics = _topics;
var payload = JSON.stringify(data);
var message = {
from: identity,
topics: [web3.fromAscii(topics)],
topics: topics,
payload: web3.fromAscii(payload),
ttl: ttl,
priority: priority
};
return web3.shh.post(message);
return web3.shh.post(message, function() {});
};
EmbarkJS.Messages.Whisper.listenTo = function(options) {
var topics = options.topic || options.topics;
var _topics = [];
if (typeof topics === 'string') {
topics = [topics];
_topics = [topics];
} else {
// TODO: replace with es6 + babel;
var _topics = [];
for (var i = 0; i < topics.length; i++) {
_topics.push(web3.fromAscii(topics[i]));
_topics.push(topics[i]);
}
topics = _topics;
}
topics = _topics;
var filterOptions = {
topics: topics
@ -275,17 +344,102 @@ EmbarkJS.Messages.Whisper.listenTo = function(options) {
return err;
};
messageEvents.prototype.stop = function() {
this.filter.stopWatching();
};
var promise = new messageEvents();
var filter = web3.shh.filter(filterOptions, function(err, result) {
var payload = JSON.parse(web3.toAscii(result.payload));
var data;
if (err) {
promise.error(err);
} else {
promise.cb(payload);
data = {
topic: topics,
data: payload,
from: result.from,
time: (new Date(result.sent * 1000))
};
promise.cb(payload, data, result);
}
});
promise.filter = filter;
return promise;
};
EmbarkJS.Messages.Orbit = {
};
EmbarkJS.Messages.Orbit.sendMessage = function(options) {
var topics = options.topic || options.topics;
var data = options.data || options.payload;
if (topics === undefined) {
throw new Error("missing option: topic");
}
if (data === undefined) {
throw new Error("missing option: data");
}
if (typeof topics === 'string') {
topics = topics;
} else {
// TODO: better to just send to different channels instead
topics = topics.join(',');
}
this.orbit.join(topics);
var payload = JSON.stringify(data);
this.orbit.send(topics, data);
};
EmbarkJS.Messages.Orbit.listenTo = function(options) {
var self = this;
var topics = options.topic || options.topics;
if (typeof topics === 'string') {
topics = topics;
} else {
topics = topics.join(',');
}
this.orbit.join(topics);
var messageEvents = function() {
this.cb = function() {};
};
messageEvents.prototype.then = function(cb) {
this.cb = cb;
};
messageEvents.prototype.error = function(err) {
return err;
};
var promise = new messageEvents();
this.orbit.events.on('message', (channel, message) => {
// TODO: looks like sometimes it's receving messages from all topics
if (topics !== channel) return;
self.orbit.getPost(message.payload.value, true).then((post) => {
var data = {
topic: channel,
data: post.content,
from: post.meta.from.name,
time: (new Date(post.meta.ts))
};
promise.cb(post.content, data, post);
});
});
return promise;
};

61
js/ipfs-api.min.js vendored Normal file

File diff suppressed because one or more lines are too long

60
js/orbit.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +0,0 @@
var ABIGenerator = function(blockchainConfig, contractsManager) {
this.blockchainConfig = blockchainConfig;
this.contractsManager = contractsManager;
this.rpcHost = blockchainConfig.rpcHost;
this.rpcPort = blockchainConfig.rpcPort;
};
ABIGenerator.prototype.generateProvider = function() {
var result = "";
result += "\nif (typeof web3 !== 'undefined' && typeof Web3 !== 'undefined') {";
result += '\n\tweb3 = new Web3(web3.currentProvider);';
result += "\n} else if (typeof Web3 !== 'undefined') {";
result += '\n\tweb3 = new Web3(new Web3.providers.HttpProvider("http://' + this.rpcHost + ':' + this.rpcPort + '"));';
result += '\n}';
result += "\nweb3.eth.defaultAccount = web3.eth.accounts[0];";
return result;
};
ABIGenerator.prototype.generateContracts = function(useEmbarkJS) {
var result = "\n";
for(var className in this.contractsManager.contracts) {
var contract = this.contractsManager.contracts[className];
var abi = JSON.stringify(contract.abiDefinition);
var gasEstimates = JSON.stringify(contract.gasEstimates);
if (useEmbarkJS) {
result += "\n" + className + " = new EmbarkJS.Contract({abi: " + abi + ", address: '" + contract.deployedAddress + "', code: '" + contract.code + "', gasEstimates: " + gasEstimates + "});";
} else {
result += "\n" + className + "Abi = " + abi + ";";
result += "\n" + className + "Contract = web3.eth.contract(" + className + "Abi);";
result += "\n" + className + " = " + className + "Contract.at('" + contract.deployedAddress + "');";
}
}
return result;
};
ABIGenerator.prototype.generateABI = function(options) {
var result = "";
result += this.generateProvider();
result += this.generateContracts(options.useEmbarkJS);
return result;
};
module.exports = ABIGenerator;

View File

@ -1,9 +1,10 @@
var program = require('commander');
var colors = require('colors');
var shelljs = require('shelljs');
var Cmd = function(Embark) {
this.Embark = Embark;
program.version('2.1.4');
program.version(Embark.version);
};
Cmd.prototype.process = function(args) {
@ -14,8 +15,14 @@ Cmd.prototype.process = function(args) {
this.blockchain();
this.simulator();
this.test();
this.ipfs();
this.upload();
this.otherCommands();
//If no arguments are passed display help by default
if (!process.argv.slice(2).length) {
program.help();
}
program.parse(args);
};
@ -29,7 +36,7 @@ Cmd.prototype.newApp = function() {
console.log("please specify your app Name".red);
console.log("e.g embark new MyApp".green);
console.log("e.g embark new --help for more information".green);
exit();
process.exit(code);
}
self.Embark.generateTemplate('boilerplate', './', name);
});
@ -51,10 +58,7 @@ Cmd.prototype.build = function() {
.command('build [environment]')
.description('deploy and build dapp at dist/ (default: development)')
.action(function(env, options) {
self.Embark.initConfig(env || 'development', {
embarkConfig: 'embark.json'
});
self.Embark.build(env || 'development');
self.Embark.build({env: env || 'development'});
});
};
@ -62,13 +66,20 @@ Cmd.prototype.run = function() {
var self = this;
program
.command('run [environment]')
.option('-p, --port [port]', 'port to run the dev webserver')
.option('-p, --port [port]', 'port to run the dev webserver (default: 8000)')
.option('-b, --host [host]', 'host to run the dev webserver (default: localhost)')
.option('--noserver', 'disable the development webserver')
.option('--nodashboard', 'simple mode, disables the dashboard')
.option('--no-color', 'no colors in case it\'s needed for compatbility purposes')
.description('run dapp (default: development)')
.action(function(env, options) {
self.Embark.initConfig(env || 'development', {
embarkConfig: 'embark.json'
self.Embark.run({
env: env || 'development',
serverPort: options.port,
serverHost: options.host,
runWebserver: !options.noserver,
useDashboard: !options.nodashboard
});
self.Embark.run({env: env || 'development', serverPort: options.port});
});
};
@ -80,41 +91,27 @@ Cmd.prototype.blockchain = function() {
.description('run blockchain server (default: development)')
.action(function(env ,options) {
self.Embark.initConfig(env || 'development', {
embarkConfig: 'embark.json'
embarkConfig: 'embark.json',
interceptLogs: false
});
self.Embark.blockchain(env || 'development', options.client || 'geth');
});
};
Cmd.prototype.simulator = function() {
var self = this;
program
.command('simulator')
.command('simulator [environment]')
.description('run a fast ethereum rpc simulator')
.option('--testrpc', 'use testrpc as the rpc simulator [default]')
.option('--ethersim', 'use ethersim as the rpc simulator')
.action(function(options) {
var Sim;
if (options.ethersim) {
try {
Sim = require('ethersim');
} catch(e) {
console.log('EtherSim not found; Please install it with "npm install ethersim --save"');
console.log('For more information see https://github.com/iurimatias/ethersim');
process.exit(1);
}
Sim.startServer();
}
else {
try {
Sim = require('ethereumjs-testrpc');
} catch(e) {
console.log('TestRPC not found; Please install it with "npm install -g ethereumjs-testrpc');
console.log('For more information see https://github.com/ethereumjs/testrpc');
process.exit(1);
}
exec('testrpc');
}
.option('-p, --port [port]', 'port to run the rpc simulator (default: 8000)')
.option('-h, --host [host]', 'host to run the rpc simulator (default: localhost)')
.action(function(env, options) {
self.Embark.initConfig(env || 'development', {
embarkConfig: 'embark.json',
interceptLogs: false
});
self.Embark.simulator({port: options.port, host: options.host});
});
};
@ -123,17 +120,21 @@ Cmd.prototype.test = function() {
.command('test')
.description('run tests')
.action(function() {
exec('mocha test/ --no-timeouts');
shelljs.exec('mocha test/ --no-timeouts');
});
};
Cmd.prototype.ipfs = function() {
Cmd.prototype.upload = function() {
var self = this;
program
.command('ipfs')
.description('deploy to IPFS')
.action(function() {
self.Embark.ipfs();
.command('upload [platform] [environment]')
.description('upload your dapp to a decentralized storage. possible options: ipfs, swarm (e.g embark upload swarm)')
.action(function(platform, env, options) {
// TODO: get env in cmd line as well
self.Embark.initConfig(env || 'development', {
embarkConfig: 'embark.json', interceptLogs: false
});
self.Embark.upload(platform);
});
};
@ -141,6 +142,8 @@ Cmd.prototype.otherCommands = function() {
program
.action(function(env){
console.log('unknown command "%s"'.red, env);
console.log("type embark --help to see the available commands");
process.exit(0);
});
};

View File

@ -1,12 +1,18 @@
var mkdirp = require('mkdirp');
var wrench = require('wrench');
var colors = require('colors');
var shelljs = require('shelljs');
var fs = require('../../core/fs.js');
var GethCommands = require('./geth_commands.js');
var Blockchain = function(blockchainConfig, Client) {
this.blockchainConfig = blockchainConfig;
/*eslint complexity: ["error", 22]*/
var Blockchain = function(options) {
this.blockchainConfig = options.blockchainConfig;
this.env = options.env || 'development';
this.client = options.client;
this.config = {
geth_bin: this.blockchainConfig.geth_bin || 'geth',
networkType: this.blockchainConfig.networkType || 'custom',
genesisBlock: this.blockchainConfig.genesisBlock || false,
datadir: this.blockchainConfig.datadir || false,
@ -18,43 +24,44 @@ var Blockchain = function(blockchainConfig, Client) {
port: this.blockchainConfig.port || 30303,
nodiscover: this.blockchainConfig.nodiscover || false,
mine: this.blockchainConfig.mine || false,
account: this.blockchainConfig.account || {}
account: this.blockchainConfig.account || {},
whisper: (this.blockchainConfig.whisper === undefined) || this.blockchainConfig.whisper,
maxpeers: ((this.blockchainConfig.maxpeers === 0) ? 0 : (this.blockchainConfig.maxpeers || 25)),
bootnodes: this.blockchainConfig.bootnodes || "",
rpcApi: (this.blockchainConfig.rpcApi || ['eth', 'web3', 'net']),
vmdebug: this.blockchainConfig.vmdebug || false
};
if (this.blockchainConfig.whisper === false) {
this.config.whisper = false;
} else {
this.config.whisper = (this.blockchainConfig.whisper || true);
}
this.client = new Client({config: this.config});
this.client = new options.client({config: this.config, env: this.env});
};
Blockchain.prototype.runCommand = function(cmd) {
console.log(("running: " + cmd.underline).green);
return exec(cmd);
return shelljs.exec(cmd);
};
Blockchain.prototype.run = function() {
var self = this;
console.log("===============================================================================".magenta);
console.log("===============================================================================".magenta);
console.log(("Embark Blockchain Using: " + this.client.name.underline).magenta);
console.log("===============================================================================".magenta);
console.log("===============================================================================".magenta);
var address = this.initChainAndGetAddress();
var mainCommand = this.client.mainCommand(address);
this.runCommand(mainCommand);
this.client.mainCommand(address, function(cmd) {
self.runCommand(cmd);
});
};
Blockchain.prototype.initChainAndGetAddress = function() {
var address = null, result;
// ensure datadir exists, bypassing the interactive liabilities prompt.
this.datadir = '.embark/development/datadir';
mkdirp.sync(this.datadir);
this.datadir = '.embark/' + this.env + '/datadir';
fs.mkdirpSync(this.datadir);
// copy mining script
wrench.copyDirSyncRecursive(__dirname + "/../js", ".embark/development/js", {forceDelete: true});
fs.copySync(fs.embarkPath("js"), ".embark/" + this.env + "/js", {overwrite: true});
// check if an account already exists, create one if not, return address
result = this.runCommand(this.client.listAccountsCommand());
@ -75,9 +82,9 @@ Blockchain.prototype.initChainAndGetAddress = function() {
return address;
};
var BlockchainClient = function(blockchainConfig, client) {
var BlockchainClient = function(blockchainConfig, client, env) {
if (client === 'geth') {
return new Blockchain(blockchainConfig, GethCommands);
return new Blockchain({blockchainConfig: blockchainConfig, client: GethCommands, env: env});
} else {
throw new Error('unknown client');
}

View File

@ -0,0 +1,164 @@
var async = require('async');
// TODO: make all of this async
var GethCommands = function(options) {
this.config = options.config;
this.env = options.env || 'development';
this.name = "Go-Ethereum (https://github.com/ethereum/go-ethereum)";
this.geth_bin = this.config.geth_bin || "geth";
};
GethCommands.prototype.commonOptions = function() {
var config = this.config;
var cmd = "";
cmd += this.determineNetworkType(config);
if (config.datadir) {
cmd += "--datadir=\"" + config.datadir + "\" ";
}
if (config.light) {
cmd += "--light ";
}
if (config.fast) {
cmd += "--fast ";
}
if (config.account && config.account.password) {
cmd += "--password " + config.account.password + " ";
}
return cmd;
};
GethCommands.prototype.determineNetworkType = function(config) {
var cmd = "";
if (config.networkType === 'testnet') {
cmd += "--testnet ";
} else if (config.networkType === 'olympic') {
cmd += "--olympic ";
} else if (config.networkType === 'custom') {
cmd += "--networkid " + config.networkId + " ";
}
return cmd;
};
GethCommands.prototype.initGenesisCommmand = function() {
var config = this.config;
var cmd = this.geth_bin + " " + this.commonOptions();
if (config.genesisBlock) {
cmd += "init \"" + config.genesisBlock + "\" ";
}
return cmd;
};
GethCommands.prototype.newAccountCommand = function() {
return this.geth_bin + " " + this.commonOptions() + "account new ";
};
GethCommands.prototype.listAccountsCommand = function() {
return this.geth_bin + " " + this.commonOptions() + "account list ";
};
GethCommands.prototype.determineRpcOptions = function(config) {
var cmd = "";
cmd += "--port " + config.port + " ";
cmd += "--rpc ";
cmd += "--rpcport " + config.rpcPort + " ";
cmd += "--rpcaddr " + config.rpcHost + " ";
if (config.rpcCorsDomain) {
if (config.rpcCorsDomain === '*') {
console.log('==================================');
console.log('make sure you know what you are doing');
console.log('==================================');
}
cmd += "--rpccorsdomain=\"" + config.rpcCorsDomain + "\" ";
} else {
console.log('==================================');
console.log('warning: cors is not set');
console.log('==================================');
}
return cmd;
};
GethCommands.prototype.mainCommand = function(address, done) {
var self = this;
var config = this.config;
var rpc_api = (this.config.rpcApi || ['eth', 'web3', 'net']);
async.series([
function commonOptions(callback) {
var cmd = self.commonOptions();
callback(null, cmd);
},
function rpcOptions(callback) {
var cmd = self.determineRpcOptions(self.config);
callback(null, cmd);
},
function dontGetPeers(callback) {
if (config.nodiscover) {
return callback(null, "--nodiscover");
}
callback(null, "");
},
function vmDebug(callback) {
if (config.vmdebug) {
return callback(null, "--vmdebug");
}
callback(null, "");
},
function maxPeers(callback) {
var cmd = "--maxpeers " + config.maxpeers;
callback(null, cmd);
},
function mining(callback) {
if (config.mineWhenNeeded || config.mine) {
return callback(null, "--mine ");
}
callback("");
},
function bootnodes(callback) {
if (config.bootnodes && config.bootnodes !== "" && config.bootnodes !== []) {
return callback(null, "--bootnodes " + config.bootnodes);
}
callback("");
},
function whisper(callback) {
if (config.whisper) {
rpc_api.push('shh');
return callback(null, "--shh ");
}
callback("");
},
function rpcApi(callback) {
callback(null, '--rpcapi "' + rpc_api.join(',') + '"');
},
function accountToUnlock(callback) {
var accountAddress = config.account.address || address;
if (accountAddress) {
return callback(null, "--unlock=" + accountAddress);
}
callback(null, "");
},
function mineWhenNeeded(callback) {
if (config.mineWhenNeeded) {
return callback(null, "js .embark/" + self.env + "/js/mine.js");
}
callback(null, "");
}
], function(err, results) {
if (err) {
throw new Error(err.message);
}
done(self.geth_bin + " " + results.join(" "));
});
};
module.exports = GethCommands;

18
lib/cmds/simulator.js Normal file
View File

@ -0,0 +1,18 @@
var shelljs = require('shelljs');
var Simulator = function(options) {
this.blockchainConfig = options.blockchainConfig;
};
Simulator.prototype.run = function(options) {
var cmds = [];
cmds.push("-p " + (this.blockchainConfig.rpcPort || options.port || 8545));
cmds.push("-h " + (this.blockchainConfig.rpcHost || options.host || 'localhost'));
cmds.push("-a " + (options.num || 10));
shelljs.exec('testrpc ' + cmds.join(' '));
};
module.exports = Simulator;

View File

@ -0,0 +1,32 @@
var fs = require('../core/fs.js');
var utils = require('../core/utils.js');
var TemplateGenerator = function(templateName) {
this.templateName = templateName;
};
TemplateGenerator.prototype.generate = function(destinationFolder, name) {
var templatePath = fs.embarkPath(this.templateName);
console.log('Initializing Embark Template....'.green);
fs.copySync(templatePath, destinationFolder + name);
utils.cd(destinationFolder + name);
utils.sed('package.json', '%APP_NAME%', name);
console.log('Installing packages.. this can take a few seconds'.green);
utils.runCmd('npm install');
console.log('Init complete'.green);
console.log('\nApp ready at '.green + destinationFolder + name);
if (name === 'embark_demo') {
console.log('-------------------'.yellow);
console.log('Next steps:'.green);
console.log(('-> ' + ('cd ' + destinationFolder + name).bold.cyan).green);
console.log('-> '.green + 'embark blockchain'.bold.cyan + ' or '.green + 'embark simulator'.bold.cyan);
console.log('open another console in the same directory and run'.green);
console.log('-> '.green + 'embark run'.bold.cyan);
console.log('For more info go to http://github.com/iurimatias/embark-framework'.green);
}
};
module.exports = TemplateGenerator;

View File

@ -1,112 +0,0 @@
var shelljs = require('shelljs');
var shelljs_global = require('shelljs/global');
var fs = require('fs');
var solc = require('solc');
var Compiler = function() {
};
Compiler.prototype.compile_solidity = function(contractFiles) {
var input = {};
for (var i = 0; i < contractFiles.length; i++){
// TODO: this depends on the config
var filename = contractFiles[i].filename.replace('app/contracts/','');
input[filename] = contractFiles[i].content.toString();
}
var output = solc.compile({sources: input}, 1);
if (output.errors) {
throw new Error ("Solidity errors: " + output.errors);
}
var json = output.contracts;
compiled_object = {};
for (var className in json) {
var contract = json[className];
compiled_object[className] = {};
compiled_object[className].code = contract.bytecode;
compiled_object[className].runtimeBytecode = contract.runtimeBytecode;
compiled_object[className].gasEstimates = contract.gasEstimates;
compiled_object[className].functionHashes = contract.functionHashes;
compiled_object[className].abiDefinition = JSON.parse(contract.interface);
}
return compiled_object;
};
Compiler.prototype.compile_serpent = function(contractFiles) {
var cmd, result, output, json, compiled_object;
//TODO: figure out how to compile multiple files and get the correct json
var contractFile = contractFiles[0];
cmd = "serpent compile " + contractFile;
result = exec(cmd, {silent: true});
code = result.output;
if (result.code === 1) {
throw new Error(result.output);
}
cmd = "serpent mk_full_signature " + contractFile;
result = exec(cmd, {silent: true});
if (result.code === 1) {
throw new Error(result.output);
}
json = JSON.parse(result.output.trim());
className = contractFile.split('.')[0].split("/").pop();
for (var i=0; i < json.length; i++) {
var elem = json[i];
if (elem.outputs.length > 0) {
elem.constant = true;
}
}
compiled_object = {};
compiled_object[className] = {};
compiled_object[className].code = code.trim();
compiled_object[className].info = {};
compiled_object[className].abiDefinition = json;
return compiled_object;
};
Compiler.prototype.compile = function(contractFiles) {
var solidity = [], serpent = [];
for (var i = 0; i < contractFiles.length; i++) {
var contractParts = contractFiles[i].split('.'),
extension = contractParts[contractParts.length-1];
if (extension === 'sol') {
solidity.push(contractFiles[i]);
}
else if (extension === 'se') {
serpent.push(contractFiles[i]);
}
else {
throw new Error("extension not known, got " + extension);
}
}
var contracts = [];
if (solidity.length > 0) {
contracts.concat(this.compile_solidity(solidity));
}
if (serpent.length > 0) {
contracts.concat(this.compile_serpent(serpent));
}
return contracts;
};
module.exports = Compiler;

View File

@ -1,97 +0,0 @@
var fs = require('fs');
var grunt = require('grunt');
var merge = require('merge');
// TODO: add wrapper for fs so it can also work in the browser
// can work with both read and save
var Config = function(options) {
this.env = options.env;
this.blockchainConfig = {};
this.contractsConfig = {};
this.pipelineConfig = {};
this.chainTracker = {};
this.assetFiles = {};
this.contractsFiles = [];
this.configDir = options.configDir || 'config/';
this.chainsFile = options.chainsFile || './chains.json';
};
Config.prototype.loadConfigFiles = function(options) {
this.embarkConfig = JSON.parse(fs.readFileSync(options.embarkConfig));
this.loadPipelineConfigFile();
this.loadBlockchainConfigFile();
this.loadContractsConfigFile();
this.loadChainTrackerFile();
};
Config.prototype.reloadConfig = function() {
this.loadPipelineConfigFile();
this.loadBlockchainConfigFile();
this.loadContractsConfigFile();
this.loadChainTrackerFile();
};
Config.prototype.loadBlockchainConfigFile = function() {
var defaultBlockchainConfig = JSON.parse(fs.readFileSync(this.configDir + "blockchain.json"))[this.env];
this.blockchainConfig = defaultBlockchainConfig;
};
Config.prototype.loadContractsConfigFile = function() {
var contractsConfig = JSON.parse(fs.readFileSync(this.configDir + "contracts.json"));
var defaultContractsConfig = contractsConfig['default'];
var envContractsConfig = contractsConfig[this.env];
var mergedConfig = merge.recursive(defaultContractsConfig, envContractsConfig);
this.contractsConfig = mergedConfig;
};
Config.prototype.loadPipelineConfigFile = function() {
var contracts = this.embarkConfig.contracts;
this.contractsFiles = this.loadFiles(contracts);
var assets = this.embarkConfig.app;
for(var targetFile in assets) {
this.assetFiles[targetFile] = this.loadFiles(assets[targetFile]);
}
this.buildDir = this.embarkConfig.buildDir;
this.configDir = this.embarkConfig.config;
};
Config.prototype.loadChainTrackerFile = function() {
//var self = this;
var chainTracker;
try {
chainTracker = JSON.parse(fs.readFileSync(this.chainsFile));
}
catch(err) {
//self.logger.info(this.chainsFile + ' file not found, creating it...');
chainTracker = {};
fs.writeFileSync(this.chainsFile, '{}');
}
this.chainTracker = chainTracker;
};
Config.prototype.loadFiles = function(files) {
var originalFiles = grunt.file.expand({nonull: true}, files);
var readFiles = [];
originalFiles.filter(function(file) {
return file.indexOf('.') >= 0;
}).filter(function(file) {
if (file === 'embark.js') {
//readFiles.push({filename: 'bluebird.js', content: fs.readFileSync("../js/bluebird.js").toString()});
readFiles.push({filename: 'web3.js', content: fs.readFileSync(__dirname + "/../js/web3.js").toString()});
//readFiles.push({filename: 'embark.js', content: fs.readFileSync("../js/ipfs.js").toString()+ fs.readFileSync("../js/build/embark.bundle.js").toString()});
readFiles.push({filename: 'ipfs.js', content: fs.readFileSync(__dirname + "/../js/ipfs.js").toString()});
readFiles.push({filename: 'embark.js', content: fs.readFileSync(__dirname + "/../js/build/embark.bundle.js").toString()});
} else {
readFiles.push({filename: file, content: fs.readFileSync(file).toString()});
}
});
return readFiles;
};
module.exports = Config;

View File

@ -1,35 +0,0 @@
var Web3 = require('web3');
var Console = function(options) {
};
Console.prototype.runCode = function(code) {
eval(code); // jshint ignore:line
};
Console.prototype.executeCmd = function(cmd, callback) {
if (cmd === 'help') {
var helpText = [
'Welcome to Embark 2',
'',
'possible commands are:',
'quit - to immediatly exit',
'',
'The web3 object and the interfaces for the deployed contrats and their methods are also available'
];
return callback(helpText.join('\n'));
} else if (cmd === 'quit') {
exit();
}
try {
var result = eval(cmd); // jshint ignore:line
return callback(result);
}
catch(e) {
return callback(e.message.red);
}
};
module.exports = Console;

View File

@ -1,214 +0,0 @@
var Compiler = require('./compiler.js');
var toposort = require('toposort');
// TODO: create a contract object
var ContractsManager = function(options) {
this.contractFiles = options.contractFiles;
this.contractsConfig = options.contractsConfig;
this.contracts = {};
this.logger = options.logger;
this.contractDependencies = {};
};
ContractsManager.prototype.compileContracts = function() {
var compiler = new Compiler();
return compiler.compile_solidity(this.contractFiles);
};
ContractsManager.prototype.build = function() {
this.compiledContracts = this.compileContracts();
var className;
var contract;
// go through config file first
for(className in this.contractsConfig.contracts) {
contract = this.contractsConfig.contracts[className];
contract.className = className;
contract.args = contract.args || [];
this.contracts[className] = contract;
}
// compile contracts
for(className in this.compiledContracts) {
var compiledContract = this.compiledContracts[className];
var contractConfig = this.contractsConfig.contracts[className];
contract = this.contracts[className] || {className: className, args: []};
contract.code = compiledContract.code;
contract.runtimeBytecode = compiledContract.runtimeBytecode;
contract.gasEstimates = compiledContract.gasEstimates;
contract.functionHashes = compiledContract.functionHashes;
contract.abiDefinition = compiledContract.abiDefinition;
contract.gas = (contractConfig && contractConfig.gas) || this.contractsConfig.gas;
if (contract.deploy === undefined) {
contract.deploy = true;
}
if (contract.gas === 'auto') {
var maxGas;
if (contract.deploy) {
maxGas = Math.max(contract.gasEstimates.creation[0], contract.gasEstimates.creation[1], 500000);
} else {
maxGas = 500000;
}
// TODO: put a check so it doesn't go over the block limit
var adjustedGas = Math.round(maxGas * 1.40);
contract.gas = adjustedGas;
}
contract.gasPrice = contract.gasPrice || this.contractsConfig.gasPrice;
contract.type = 'file';
contract.className = className;
this.contracts[className] = contract;
}
// deal with special configs
for(className in this.contracts) {
contract = this.contracts[className];
// if deploy intention is not specified default is true
if (contract.deploy === undefined) {
contract.deploy = true;
}
if (contract.instanceOf !== undefined) {
var parentContractName = contract.instanceOf;
var parentContract = this.contracts[parentContractName];
if (parentContract === className) {
this.logger.error(className + ": instanceOf is set to itself");
continue;
}
if (parentContract === undefined) {
this.logger.error(className + ": couldn't find instanceOf contract " + parentContractName);
continue;
}
if (parentContract.args && parentContract.args.length > 0 && contract.args === []) {
contract.args = parentContract.args;
}
if (contract.code !== undefined) {
this.logger.error(className + " has code associated to it but it's configured as an instanceOf " + parentContractName);
}
contract.code = parentContract.code;
contract.runtimeBytecode = parentContract.runtimeBytecode;
contract.gasEstimates = parentContract.gasEstimates;
contract.functionHashes = parentContract.functionHashes;
contract.abiDefinition = parentContract.abiDefinition;
contract.gas = contract.gas || parentContract.gas;
contract.gasPrice = contract.gasPrice || parentContract.gasPrice;
}
}
// remove contracts that don't have code
for(className in this.contracts) {
contract = this.contracts[className];
if (contract.code === undefined) {
this.logger.error(className + " has no code associated");
delete this.contracts[className];
}
}
this.logger.trace(this.contracts);
// determine dependencies
for(className in this.contracts) {
contract = this.contracts[className];
if (contract.args === []) continue;
var ref = contract.args;
for (var j = 0; j < ref.length; j++) {
var arg = ref[j];
if (arg[0] === "$") {
if (this.contractDependencies[className] === void 0) {
this.contractDependencies[className] = [];
}
this.contractDependencies[className].push(arg.substr(1));
}
}
}
};
ContractsManager.prototype.getContract = function(className) {
return this.contracts[className];
};
ContractsManager.prototype.sortContracts = function(contractList) {
var converted_dependencies = [], i;
for(var contract in this.contractDependencies) {
var dependencies = this.contractDependencies[contract];
for(i=0; i < dependencies.length; i++) {
converted_dependencies.push([contract, dependencies[i]]);
}
}
var orderedDependencies = toposort(converted_dependencies).reverse();
var newList = contractList.sort(function(a,b) {
var order_a = orderedDependencies.indexOf(a.className);
var order_b = orderedDependencies.indexOf(b.className);
return order_a - order_b;
});
return newList;
};
// TODO: should be built contracts
ContractsManager.prototype.listContracts = function() {
var contracts = [];
for(var className in this.contracts) {
var contract = this.contracts[className];
contracts.push(contract);
}
return this.sortContracts(contracts);
};
ContractsManager.prototype.contractsState = function() {
var data = [];
for(var className in this.contracts) {
var contract = this.contracts[className];
var contractData;
if (contract.deploy === false) {
contractData = [
className.green,
'Interface or set to not deploy'.green,
"\t\tn/a".green
];
} else if (contract.error) {
contractData = [
className.green,
(contract.error).red,
'\t\tError'.red
];
} else {
contractData = [
className.green,
(contract.deployedAddress || '...').green,
((contract.deployedAddress !== undefined) ? "\t\tDeployed".green : "\t\tPending".magenta)
];
}
data.push(contractData);
}
return data;
};
module.exports = ContractsManager;

117
lib/contracts/abi.js Normal file
View File

@ -0,0 +1,117 @@
var ABIGenerator = function(options) {
this.blockchainConfig = options.blockchainConfig || {};
this.storageConfig = options.storageConfig || {};
this.communicationConfig = options.communicationConfig || {};
this.contractsManager = options.contractsManager;
this.rpcHost = options.blockchainConfig && options.blockchainConfig.rpcHost;
this.rpcPort = options.blockchainConfig && options.blockchainConfig.rpcPort;
this.plugins = options.plugins;
};
ABIGenerator.prototype.generateProvider = function() {
var self = this;
var result = "";
var providerPlugins;
if (self.blockchainConfig === {} || self.blockchainConfig.enabled === false) {
return "";
}
if (this.plugins) {
providerPlugins = this.plugins.getPluginsFor('clientWeb3Provider');
}
if (this.plugins && providerPlugins.length > 0) {
providerPlugins.forEach(function(plugin) {
result += plugin.generateProvider(self) + "\n";
});
} else {
result += "\nif (typeof web3 !== 'undefined' && typeof Web3 !== 'undefined') {";
result += '\n\tweb3 = new Web3(web3.currentProvider);';
result += "\n} else if (typeof Web3 !== 'undefined') {";
result += '\n\tweb3 = new Web3(new Web3.providers.HttpProvider("http://' + this.rpcHost + ':' + this.rpcPort + '"));';
result += '\n}';
result += "\nweb3.eth.defaultAccount = web3.eth.accounts[0];";
}
return result;
};
ABIGenerator.prototype.generateContracts = function(useEmbarkJS) {
var self = this;
var result = "\n";
var contractsPlugins;
if (self.blockchainConfig === {} || self.blockchainConfig.enabled === false) {
return "";
}
if (this.plugins) {
contractsPlugins = this.plugins.getPluginsFor('contractGeneration');
}
if (this.plugins && contractsPlugins.length > 0) {
contractsPlugins.forEach(function(plugin) {
result += plugin.generateContracts({contracts: self.contractsManager.contracts});
});
} else {
for(var className in this.contractsManager.contracts) {
var contract = this.contractsManager.contracts[className];
var abi = JSON.stringify(contract.abiDefinition);
var gasEstimates = JSON.stringify(contract.gasEstimates);
if (useEmbarkJS) {
result += "\n" + className + " = new EmbarkJS.Contract({abi: " + abi + ", address: '" + contract.deployedAddress + "', code: '" + contract.code + "', gasEstimates: " + gasEstimates + "});";
} else {
result += "\n" + className + "Abi = " + abi + ";";
result += "\n" + className + "Contract = web3.eth.contract(" + className + "Abi);";
result += "\n" + className + " = " + className + "Contract.at('" + contract.deployedAddress + "');";
}
}
}
return result;
};
ABIGenerator.prototype.generateStorageInitialization = function(useEmbarkJS) {
var self = this;
var result = "\n";
if (!useEmbarkJS || self.storageConfig === {}) return "";
if (self.storageConfig.provider === 'ipfs' && self.storageConfig.enabled === true) {
result += "\nEmbarkJS.Storage.setProvider('" + self.storageConfig.provider + "', {server: '" + self.storageConfig.host + "', port: '" + self.storageConfig.port + "'});";
}
return result;
};
ABIGenerator.prototype.generateCommunicationInitialization = function(useEmbarkJS) {
var self = this;
var result = "\n";
if (!useEmbarkJS || self.communicationConfig === {}) return "";
if (self.communicationConfig.provider === 'whisper' && self.communicationConfig.enabled === true) {
result += "\nEmbarkJS.Messages.setProvider('" + self.communicationConfig.provider + "');";
} else if (self.communicationConfig.provider === 'orbit' && self.communicationConfig.enabled === true) {
result += "\nEmbarkJS.Messages.setProvider('" + self.communicationConfig.provider + "', {server: '" + self.communicationConfig.host + "', port: '" + self.communicationConfig.port + "'});";
}
return result;
};
ABIGenerator.prototype.generateABI = function(options) {
var result = "";
result += this.generateProvider();
result += this.generateContracts(options.useEmbarkJS);
result += this.generateStorageInitialization(options.useEmbarkJS);
result += this.generateCommunicationInitialization(options.useEmbarkJS);
return result;
};
module.exports = ABIGenerator;

118
lib/contracts/compiler.js Normal file
View File

@ -0,0 +1,118 @@
/*jshint esversion: 6, loopfunc: true */
var async = require('async');
var SolcW = require('./solcW.js');
function asyncEachObject(object, iterator, callback) {
async.each(
Object.keys(object || {}),
function(key, next){
iterator(key, object[key], next);
},
callback
);
}
async.eachObject = asyncEachObject;
var Compiler = function(options) {
this.plugins = options.plugins;
this.logger = options.logger;
};
Compiler.prototype.compile_contracts = function(contractFiles, cb) {
var self = this;
var available_compilers = {
//".se": this.compile_serpent
".sol": this.compile_solidity.bind(this)
};
if (this.plugins) {
var compilerPlugins = this.plugins.getPluginsFor('compilers');
if (compilerPlugins.length > 0) {
compilerPlugins.forEach(function(plugin) {
plugin.compilers.forEach(function(compilerObject) {
available_compilers[compilerObject.extension] = compilerObject.cb;
});
});
}
}
var compiledObject = {};
async.eachObject(available_compilers,
function(extension, compiler, callback) {
// TODO: warn about files it doesn't know how to compile
var matchingFiles = contractFiles.filter(function(file) {
return (file.filename.match(/\.[0-9a-z]+$/)[0] === extension);
});
compiler.call(compiler, matchingFiles || [], function(err, compileResult) {
Object.assign(compiledObject, compileResult);
callback(err, compileResult);
});
},
function (err) {
cb(err, compiledObject);
}
);
};
Compiler.prototype.compile_solidity = function(contractFiles, cb) {
var self = this;
var input = {};
var solcW;
async.waterfall([
function prepareInput(callback) {
for (var i = 0; i < contractFiles.length; i++){
// TODO: this depends on the config
var filename = contractFiles[i].filename.replace('app/contracts/','');
input[filename] = contractFiles[i].content.toString();
}
callback();
},
function loadCompiler(callback) {
// TODO: there ino need to load this twice
solcW = new SolcW();
if (solcW.isCompilerLoaded()) {
return callback();
}
self.logger.info("loading solc compiler..");
solcW.load_compiler(function(){
callback();
});
},
function compileContracts(callback) {
self.logger.info("compiling contracts...");
solcW.compile({sources: input}, 1, function(output) {
if (output.errors) {
return callback(new Error ("Solidity errors: " + output.errors).message);
}
callback(null, output);
});
},
function createCompiledObject(output, callback) {
var json = output.contracts;
compiled_object = {};
for (var className in json) {
var contract = json[className];
compiled_object[className] = {};
compiled_object[className].code = contract.bytecode;
compiled_object[className].runtimeBytecode = contract.runtimeBytecode;
compiled_object[className].realRuntimeBytecode = contract.runtimeBytecode.slice(0, -68);
compiled_object[className].swarmHash = contract.runtimeBytecode.slice(-68).slice(0,64);
compiled_object[className].gasEstimates = contract.gasEstimates;
compiled_object[className].functionHashes = contract.functionHashes;
compiled_object[className].abiDefinition = JSON.parse(contract.interface);
}
callback(null, compiled_object);
}
], function(err, result) {
cb(err, result);
});
};
module.exports = Compiler;

247
lib/contracts/contracts.js Normal file
View File

@ -0,0 +1,247 @@
var toposort = require('toposort');
var async = require('async');
var Compiler = require('./compiler.js');
// TODO: create a contract object
var adjustGas = function(contract) {
var maxGas, adjustedGas;
if (contract.gas === 'auto') {
if (contract.deploy || contract.deploy === undefined) {
if (contract.gasEstimates.creation !== undefined) {
// TODO: should sum it instead
maxGas = Math.max(contract.gasEstimates.creation[0], contract.gasEstimates.creation[1], 500000);
} else {
maxGas = 500000;
}
} else {
maxGas = 500000;
}
// TODO: put a check so it doesn't go over the block limit
adjustedGas = Math.round(maxGas * 1.40);
adjustedGas += 25000;
contract.gas = adjustedGas;
}
};
var ContractsManager = function(options) {
this.contractFiles = options.contractFiles;
this.contractsConfig = options.contractsConfig;
this.contracts = {};
this.logger = options.logger;
this.plugins = options.plugins;
this.contractDependencies = {};
};
ContractsManager.prototype.build = function(done) {
var self = this;
async.waterfall([
function compileContracts(callback) {
var compiler = new Compiler({plugins: self.plugins, logger: self.logger});
compiler.compile_contracts(self.contractFiles, function(err, compiledObject) {
self.compiledContracts = compiledObject;
callback(err);
});
},
function prepareContractsFromConfig(callback) {
var className, contract;
for(className in self.contractsConfig.contracts) {
contract = self.contractsConfig.contracts[className];
contract.className = className;
contract.args = contract.args || [];
self.contracts[className] = contract;
}
callback();
},
function setDeployIntention(callback) {
var className, contract;
for(className in self.contracts) {
contract = self.contracts[className];
contract.deploy = (contract.deploy === undefined) || contract.deploy;
}
callback();
},
function prepareContractsFromCompilation(callback) {
var className, compiledContract, contractConfig, contract;
for(className in self.compiledContracts) {
compiledContract = self.compiledContracts[className];
contractConfig = self.contractsConfig.contracts[className];
contract = self.contracts[className] || {className: className, args: []};
contract.code = compiledContract.code;
contract.runtimeBytecode = compiledContract.runtimeBytecode;
contract.realRuntimeBytecode = (contract.realRuntimeBytecode || contract.runtimeBytecode);
contract.swarmHash = compiledContract.swarmHash;
contract.gasEstimates = compiledContract.gasEstimates;
contract.functionHashes = compiledContract.functionHashes;
contract.abiDefinition = compiledContract.abiDefinition;
contract.gas = (contractConfig && contractConfig.gas) || self.contractsConfig.gas || 'auto';
adjustGas(contract);
contract.gasPrice = contract.gasPrice || self.contractsConfig.gasPrice;
contract.type = 'file';
contract.className = className;
self.contracts[className] = contract;
}
callback();
},
/*eslint complexity: ["error", 11]*/
function dealWithSpecialConfigs(callback) {
var className, contract, parentContractName, parentContract;
for(className in self.contracts) {
contract = self.contracts[className];
if (contract.instanceOf === undefined) { continue; }
parentContractName = contract.instanceOf;
parentContract = self.contracts[parentContractName];
if (parentContract === className) {
self.logger.error(className + ": instanceOf is set to itself");
continue;
}
if (parentContract === undefined) {
self.logger.error(className + ": couldn't find instanceOf contract " + parentContractName);
continue;
}
if (parentContract.args && parentContract.args.length > 0 && ((contract.args && contract.args.length === 0) || contract.args === undefined)) {
contract.args = parentContract.args;
}
if (contract.code !== undefined) {
self.logger.error(className + " has code associated to it but it's configured as an instanceOf " + parentContractName);
}
contract.code = parentContract.code;
contract.runtimeBytecode = parentContract.runtimeBytecode;
contract.gasEstimates = parentContract.gasEstimates;
contract.functionHashes = parentContract.functionHashes;
contract.abiDefinition = parentContract.abiDefinition;
contract.gas = contract.gas || parentContract.gas;
contract.gasPrice = contract.gasPrice || parentContract.gasPrice;
contract.type = 'instance';
}
callback();
},
function removeContractsWithNoCode(callback) {
var className, contract;
for(className in self.contracts) {
contract = self.contracts[className];
if (contract.code === undefined) {
self.logger.error(className + " has no code associated");
delete self.contracts[className];
}
}
self.logger.trace(self.contracts);
callback();
},
function determineDependencies(callback) {
var className, contract;
for(className in self.contracts) {
contract = self.contracts[className];
if (contract.args === []) continue;
var ref = contract.args;
for (var j = 0; j < ref.length; j++) {
var arg = ref[j];
if (arg[0] === "$") {
self.contractDependencies[className] = self.contractDependencies[className] || [];
self.contractDependencies[className].push(arg.substr(1));
}
}
}
callback();
}
], function(err, result) {
if (err) {
self.logger.error("Error Compiling/Building contracts: " + err);
}
self.logger.trace("finished".underline);
done(err, self);
});
};
ContractsManager.prototype.getContract = function(className) {
return this.contracts[className];
};
ContractsManager.prototype.sortContracts = function(contractList) {
var converted_dependencies = [], i;
for(var contract in this.contractDependencies) {
var dependencies = this.contractDependencies[contract];
for(i=0; i < dependencies.length; i++) {
converted_dependencies.push([contract, dependencies[i]]);
}
}
var orderedDependencies = toposort(converted_dependencies).reverse();
var newList = contractList.sort(function(a,b) {
var order_a = orderedDependencies.indexOf(a.className);
var order_b = orderedDependencies.indexOf(b.className);
return order_a - order_b;
});
return newList;
};
// TODO: should be built contracts
ContractsManager.prototype.listContracts = function() {
var contracts = [];
for(var className in this.contracts) {
var contract = this.contracts[className];
contracts.push(contract);
}
return this.sortContracts(contracts);
};
ContractsManager.prototype.contractsState = function() {
var data = [];
for(var className in this.contracts) {
var contract = this.contracts[className];
var contractData;
if (contract.deploy === false) {
contractData = [
className.green,
'Interface or set to not deploy'.green,
"\t\tn/a".green
];
} else if (contract.error) {
contractData = [
className.green,
(contract.error).red,
'\t\tError'.red
];
} else {
contractData = [
className.green,
(contract.deployedAddress || '...').green,
((contract.deployedAddress !== undefined) ? "\t\tDeployed".green : "\t\tPending".magenta)
];
}
data.push(contractData);
}
return data;
};
module.exports = ContractsManager;

View File

@ -1,8 +1,9 @@
var async = require('async');
var Compiler = require('./compiler.js');
var RunCode = require('../core/runCode.js');
var DeployTracker = require('./deploy_tracker.js');
var ABIGenerator = require('./abi.js');
var web3;
var Deploy = function(options) {
this.web3 = options.web3;
@ -15,6 +16,23 @@ var Deploy = function(options) {
});
};
Deploy.prototype.determineArguments = function(suppliedArgs) {
var realArgs = [], l, arg, contractName, referedContract;
for (l = 0; l < suppliedArgs.length; l++) {
arg = suppliedArgs[l];
if (arg[0] === "$") {
contractName = arg.substr(1);
referedContract = this.contractsManager.getContract(contractName);
realArgs.push(referedContract.deployedAddress);
} else {
realArgs.push(arg);
}
}
return realArgs;
};
Deploy.prototype.checkAndDeployContract = function(contract, params, callback) {
var self = this;
var suppliedArgs;
@ -32,68 +50,41 @@ Deploy.prototype.checkAndDeployContract = function(contract, params, callback) {
if (contract.address !== undefined) {
// determine arguments
suppliedArgs = (params || contract.args);
realArgs = [];
for (l = 0; l < suppliedArgs.length; l++) {
arg = suppliedArgs[l];
if (arg[0] === "$") {
contractName = arg.substr(1);
referedContract = this.contractsManager.getContract(contractName);
realArgs.push(referedContract.deployedAddress);
} else {
realArgs.push(arg);
}
}
realArgs = self.determineArguments(params || contract.args);
contract.deployedAddress = contract.address;
self.deployTracker.trackContract(contract.className, contract.code, realArgs, contract.address);
self.deployTracker.trackContract(contract.className, contract.realRuntimeBytecode, realArgs, contract.address);
self.deployTracker.save();
self.logger.contractsState(self.contractsManager.contractsState());
return callback();
}
var trackedContract = self.deployTracker.getContract(contract.className, contract.code, contract.args);
var trackedContract = self.deployTracker.getContract(contract.className, contract.realRuntimeBytecode, contract.args);
if (trackedContract && this.web3.eth.getCode(trackedContract.address) !== "0x") {
self.logger.info(contract.className + " already deployed " + trackedContract.address);
self.logger.info(contract.className.bold.cyan + " already deployed at ".green + trackedContract.address.bold.cyan);
contract.deployedAddress = trackedContract.address;
self.logger.contractsState(self.contractsManager.contractsState());
callback();
return callback();
} else {
// determine arguments
suppliedArgs = (params || contract.args);
realArgs = [];
for (l = 0; l < suppliedArgs.length; l++) {
arg = suppliedArgs[l];
if (arg[0] === "$") {
contractName = arg.substr(1);
referedContract = this.contractsManager.getContract(contractName);
realArgs.push(referedContract.deployedAddress);
} else {
realArgs.push(arg);
}
}
realArgs = self.determineArguments(params || contract.args);
this.deployContract(contract, realArgs, function(err, address) {
self.deployTracker.trackContract(contract.className, contract.code, realArgs, address);
if (err) {
return callback(new Error(err));
}
self.deployTracker.trackContract(contract.className, contract.realRuntimeBytecode, realArgs, address);
self.deployTracker.save();
self.logger.contractsState(self.contractsManager.contractsState());
// TODO: replace evals with separate process so it's isolated and with
// a callback
if (contract.onDeploy !== undefined) {
self.logger.info('executing onDeploy commands');
var abiGenerator = new ABIGenerator({}, self.contractsManager);
web3 = self.web3;
var abiGenerator = new ABIGenerator({contractsManager: self.contractsManager});
var abi = abiGenerator.generateContracts(false);
eval(abi); // jshint ignore:line
var cmds = contract.onDeploy.join(';\n');
eval(cmds); // jshint ignore:line
RunCode.doEval(abi + "\n" + cmds, self.web3);
}
callback();
@ -109,7 +100,9 @@ Deploy.prototype.deployContract = function(contract, params, callback) {
var contractParams = (params || contract.args).slice();
this.web3.eth.getAccounts(function(err, accounts) {
//console.log("using address" + this.web3.eth.accounts[0]);
if (err) {
return callback(new Error(err));
}
// TODO: probably needs to be defaultAccount
// TODO: it wouldn't necessary be the first address
@ -117,29 +110,29 @@ Deploy.prototype.deployContract = function(contract, params, callback) {
contractParams.push({
//from: this.web3.eth.coinbase,
from: accounts[0],
data: contract.code,
data: "0x" + contract.code,
gas: contract.gas,
gasPrice: contract.gasPrice
});
self.logger.info("deploying " + contract.className + " with " + contract.gas + " gas");
self.logger.info("deploying " + contract.className.bold.cyan + " with ".green + contract.gas + " gas".green);
contractParams.push(function(err, transaction) {
self.logger.contractsState(self.contractsManager.contractsState());
if (err) {
self.logger.error("error deploying contract: " + contract.className);
self.logger.error("error deploying contract: " + contract.className.cyan);
var errMsg = err.toString();
if (errMsg === 'Error: The contract code couldn\'t be stored, please check your gas amount.') {
errMsg = 'The contract code couldn\'t be stored, out of gas or constructor error';
}
self.logger.error(errMsg);
contract.error = errMsg;
callback(new Error(err));
return callback(new Error(err));
} else if (transaction.address !== undefined) {
self.logger.info(contract.className + " deployed at " + transaction.address);
self.logger.info(contract.className.bold.cyan + " deployed at ".green + transaction.address.bold.cyan);
contract.deployedAddress = transaction.address;
contract.transactionHash = transaction.transactionHash;
callback(null, transaction.address);
return callback(null, transaction.address);
}
});
@ -157,7 +150,12 @@ Deploy.prototype.deployAll = function(done) {
self.checkAndDeployContract(contract, null, callback);
},
function(err, results) {
self.logger.info("finished");
if (err) {
self.logger.error("error deploying contracts");
self.logger.error(err.message);
self.logger.debug(err.stack);
}
self.logger.info("finished deploying contracts");
self.logger.trace(arguments);
done();
}

View File

@ -0,0 +1,85 @@
var async = require('async');
var Web3 = require('web3');
var Deploy = require('./deploy.js');
var ContractsManager = require('./contracts.js');
var DeployManager = function(options) {
this.config = options.config;
this.logger = options.logger;
this.blockchainConfig = this.config.blockchainConfig;
this.plugins = options.plugins;
this.events = options.events;
this.web3 = options.web3;
this.chainConfig = (options.trackContracts !== false) ? this.config.chainTracker : false;
};
DeployManager.prototype.deployContracts = function(done) {
var self = this;
if (self.blockchainConfig === {} || self.blockchainConfig.enabled === false) {
self.logger.info("Blockchain component is disabled in the config".underline);
self.events.emit('blockchainDisabled', {});
return done();
}
async.waterfall([
function buildContracts(callback) {
var contractsManager = new ContractsManager({
contractFiles: self.config.contractsFiles,
contractsConfig: self.config.contractsConfig,
logger: self.logger,
plugins: self.plugins
});
contractsManager.build(callback);
},
function connectWithWeb3(contractsManager, callback) {
var web3;
if (self.web3) {
web3 = self.web3;
} else {
web3 = new Web3();
var web3Endpoint = 'http://' + self.config.blockchainConfig.rpcHost + ':' + self.config.blockchainConfig.rpcPort;
web3.setProvider(new web3.providers.HttpProvider(web3Endpoint));
if (!web3.isConnected()) {
self.logger.error(("Couldn't connect to " + web3Endpoint.underline + " are you sure it's on?").red);
self.logger.info("make sure you have an ethereum node or simulator running. e.g 'embark blockchain'".magenta);
return callback(Error("error connecting to blockchain node"));
}
}
callback(null, contractsManager, web3);
},
function setDefaultAccount(contractsManager, web3, callback) {
web3.eth.getAccounts(function(err, accounts) {
if (err) {
return callback(new Error(err));
}
var accountConfig = self.config.blockchainConfig.account;
var selectedAccount = accountConfig && accountConfig.address;
web3.eth.defaultAccount = (selectedAccount || accounts[0]);
callback(null, contractsManager, web3);
});
},
function deployAllContracts(contractsManager, web3, callback) {
var deploy = new Deploy({
web3: web3,
contractsManager: contractsManager,
logger: self.logger,
chainConfig: self.chainConfig,
env: self.config.env
});
deploy.deployAll(function() {
self.events.emit('contractsDeployed', contractsManager);
callback(null, contractsManager);
});
}
], function(err, result) {
if (err) {
done(err, null);
} else {
done(null, result);
}
});
};
module.exports = DeployManager;

View File

@ -1,5 +1,4 @@
var fs = require('fs');
var prettyJson = require("json-honey");
var fs = require('../core/fs.js');
var DeployTracker = function(options) {
this.logger = options.logger;
@ -12,6 +11,7 @@ var DeployTracker = function(options) {
return;
}
// TODO: need to make this async
var block = this.web3.eth.getBlock(0);
var chainId = block.hash;
@ -49,7 +49,7 @@ DeployTracker.prototype.getContract = function(contractName, code, args) {
// chainConfig can be an abstract PersistentObject
DeployTracker.prototype.save = function() {
if (this.chainConfig === false) { return; }
fs.writeFileSync("./chains.json", prettyJson(this.chainConfig));
fs.writeJSONSync("./chains.json", this.chainConfig);
};
module.exports = DeployTracker;

18
lib/contracts/solcP.js Normal file
View File

@ -0,0 +1,18 @@
var solc;
process.on('message', function(msg) {
if (msg.action === 'loadCompiler') {
solc = require('solc');
process.send({result: "loadedCompiler"});
}
if (msg.action === 'compile') {
var output = solc.compile(msg.obj, msg.optimize);
process.send({result: "compilation", output: output});
}
});
process.on('exit', function() {
process.exit(0);
});

36
lib/contracts/solcW.js Normal file
View File

@ -0,0 +1,36 @@
var utils = require('../core/utils.js');
var solcProcess;
var compilerLoaded = false;
var SolcW = function() {
};
SolcW.prototype.load_compiler = function(done) {
if (compilerLoaded) { done(); }
solcProcess = require('child_process').fork(utils.joinPath(__dirname, '/solcP.js'));
solcProcess.once('message', function(msg) {
if (msg.result !== 'loadedCompiler') {
return;
}
compilerLoaded = true;
done();
});
solcProcess.send({action: 'loadCompiler'});
};
SolcW.prototype.isCompilerLoaded = function() {
return (compilerLoaded === true);
};
SolcW.prototype.compile = function(obj, optimize, done) {
solcProcess.once('message', function(msg) {
if (msg.result !== 'compilation') {
return;
}
done(msg.output);
});
solcProcess.send({action: 'compile', obj: obj, optimize: optimize});
};
module.exports = SolcW;

309
lib/core/config.js Normal file
View File

@ -0,0 +1,309 @@
var fs = require('./fs.js');
var Plugins = require('./plugins.js');
var utils = require('./utils.js');
// TODO: add wrapper for fs so it can also work in the browser
// can work with both read and save
var Config = function(options) {
this.env = options.env;
this.blockchainConfig = {};
this.contractsConfig = {};
this.pipelineConfig = {};
this.webServerConfig = {};
this.chainTracker = {};
this.assetFiles = {};
this.contractsFiles = [];
this.configDir = options.configDir || 'config/';
this.chainsFile = options.chainsFile || './chains.json';
this.plugins = options.plugins;
this.logger = options.logger;
this.events = options.events;
};
Config.prototype.loadConfigFiles = function(options) {
var interceptLogs = options.interceptLogs;
if (options.interceptLogs === undefined) {
interceptLogs = true;
}
//Check if the config file exists
var embarkConfigExists = fs.existsSync(options.embarkConfig);
if(!embarkConfigExists){
this.logger.error('Cannot find file ' + options.embarkConfig + '. Please ensure you are running this command inside the Dapp folder');
process.exit(1);
}
this.embarkConfig = fs.readJSONSync(options.embarkConfig);
this.embarkConfig.plugins = this.embarkConfig.plugins || {};
this.plugins = new Plugins({plugins: this.embarkConfig.plugins, logger: this.logger, interceptLogs: interceptLogs, events: this.events, config: this});
this.plugins.loadPlugins();
this.loadEmbarkConfigFile();
this.loadBlockchainConfigFile();
this.loadStorageConfigFile();
this.loadCommunicationConfigFile();
this.loadPipelineConfigFile();
this.loadContractsConfigFile();
this.loadWebServerConfigFile();
this.loadChainTrackerFile();
this.loadPluginContractFiles();
};
Config.prototype.reloadConfig = function() {
this.loadEmbarkConfigFile();
this.loadBlockchainConfigFile();
this.loadStorageConfigFile();
this.loadCommunicationConfigFile();
this.loadPipelineConfigFile();
this.loadContractsConfigFile();
this.loadChainTrackerFile();
};
Config.prototype.loadBlockchainConfigFile = function() {
var defaultBlockchainConfig = fs.readJSONSync(this.configDir + "blockchain.json");
this.blockchainConfig = defaultBlockchainConfig[this.env] || {};
if (this.blockchainConfig.enabled === undefined) {
this.blockchainConfig.enabled = true;
}
};
Config.prototype.loadContractsConfigFile = function() {
var configObject = {};
var configPlugins = this.plugins.getPluginsFor('contractsConfig');
if (configPlugins.length > 0) {
configPlugins.forEach(function(plugin) {
plugin.contractsConfigs.forEach(function(pluginConfig) {
configObject = utils.recursiveMerge(configObject, pluginConfig);
});
});
}
var contractsConfig = fs.readJSONSync(this.configDir + "contracts.json");
configObject = utils.recursiveMerge(configObject, contractsConfig);
var defaultContractsConfig = configObject['default'];
var envContractsConfig = configObject[this.env];
var mergedConfig = utils.recursiveMerge(defaultContractsConfig, envContractsConfig);
this.contractsConfig = mergedConfig;
};
Config.prototype.loadStorageConfigFile = function() {
var configObject = {
"default": {
"enabled": true,
"available_providers": ["ipfs"],
"ipfs_bin": "ipfs",
"provider": "ipfs",
"host": "localhost",
"port": 5001
},
"development": {
}
};
//var configPlugins = this.plugins.getPluginsFor('storageConfig');
//if (configPlugins.length > 0) {
// configPlugins.forEach(function(plugin) {
// plugin.contractsConfigs.forEach(function(pluginConfig) {
// configObject = utils.recursiveMerge(configObject, pluginConfig);
// });
// });
//}
var storageConfig;
if (fs.existsSync(this.configDir + "storage.json")) {
storageConfig = fs.readJSONSync(this.configDir + "storage.json");
configObject = utils.recursiveMerge(configObject, storageConfig);
}
var defaultStorageConfig = configObject['default'];
var envStorageConfig = configObject[this.env];
var mergedConfig = utils.recursiveMerge(defaultStorageConfig, envStorageConfig);
this.storageConfig = mergedConfig || {};
if (this.storageConfig.enabled === undefined) {
this.storageConfig.enabled = true;
}
if (this.storageConfig.available_providers === undefined) {
this.storageConfig.available_providers = [];
}
};
Config.prototype.loadCommunicationConfigFile = function() {
var configObject = {
"default": {
"enabled": true,
"provider": "whisper",
"available_providers": ["whisper", "orbit"]
}
};
//var configPlugins = this.plugins.getPluginsFor('communicationConfig');
//if (configPlugins.length > 0) {
// configPlugins.forEach(function(plugin) {
// plugin.contractsConfigs.forEach(function(pluginConfig) {
// configObject = utils.recursiveMerge(configObject, pluginConfig);
// });
// });
//}
var communicationConfig;
if (fs.existsSync(this.configDir + "communication.json")) {
communicationConfig = fs.readJSONSync(this.configDir + "communication.json");
configObject = utils.recursiveMerge(configObject, communicationConfig);
}
var defaultCommunicationConfig = configObject['default'];
var envCommunicationConfig = configObject[this.env];
var mergedConfig = utils.recursiveMerge(defaultCommunicationConfig, envCommunicationConfig);
this.communicationConfig = mergedConfig || {};
// TODO: probably not necessary if the default object is done right
if (this.communicationConfig.enabled === undefined) {
this.communicationConfig.enabled = true;
}
if (this.communicationConfig.available_providers === undefined) {
this.communicationConfig.available_providers = [];
}
};
Config.prototype.loadWebServerConfigFile = function() {
var webServerConfigJSON;
if (fs.existsSync(this.configDir + "webserver.json")) {
webServerConfigJSON = fs.readJSONSync(this.configDir + "webserver.json");
} else {
webServerConfigJSON = {};
}
var defaultWebConfig = {
"enabled": true,
"host": "localhost",
"port": 8000
};
this.webServerConfig = utils.recursiveMerge(defaultWebConfig, webServerConfigJSON);
};
Config.prototype.loadEmbarkConfigFile = function() {
var contracts = this.embarkConfig.contracts;
this.contractsFiles = this.loadFiles(contracts);
this.buildDir = this.embarkConfig.buildDir;
this.configDir = this.embarkConfig.config;
};
Config.prototype.loadPipelineConfigFile = function() {
var assets = this.embarkConfig.app;
for(var targetFile in assets) {
this.assetFiles[targetFile] = this.loadFiles(assets[targetFile]);
}
};
Config.prototype.loadChainTrackerFile = function() {
//var self = this;
var chainTracker;
try {
chainTracker = fs.readJSONSync(this.chainsFile);
}
catch(err) {
//self.logger.info(this.chainsFile + ' file not found, creating it...');
chainTracker = {};
fs.writeJSONSync(this.chainsFile, {});
}
this.chainTracker = chainTracker;
};
Config.prototype.loadFiles = function(files) {
var self = this;
var originalFiles = utils.filesMatchingPattern(files);
var readFiles = [];
// get embark.js object first
originalFiles.filter(function(file) {
return file.indexOf('.') >= 0;
}).filter(function(file) {
if (file === 'embark.js') {
if (self.blockchainConfig.enabled || self.communicationConfig.provider === 'whisper' || self.communicationConfig.available_providers.indexOf('whisper') >= 0) {
readFiles.push({filename: 'web3.js', content: fs.readFileSync(fs.embarkPath("js/web3.js")).toString(), path: fs.embarkPath("js/web3.js")});
}
if (self.storageConfig.enabled && (self.storageConfig.provider === 'ipfs' || self.storageConfig.available_providers.indexOf('ipfs') >= 0)) {
readFiles.push({filename: 'ipfs.js', content: fs.readFileSync(fs.embarkPath("js/ipfs.js")).toString(), path: fs.embarkPath("js/ipfs.js")});
}
if (self.communicationConfig.enabled && (self.communicationConfig.provider === 'orbit' || self.communicationConfig.available_providers.indexOf('orbit') >= 0)) {
// TODO: remove duplicated files if functionality is the same for storage and orbit
readFiles.push({filename: 'ipfs-api.js', content: fs.readFileSync(fs.embarkPath("js/ipfs-api.min.js")).toString(), path: fs.embarkPath("js/ipfs-api.min.js")});
readFiles.push({filename: 'orbit.js', content: fs.readFileSync(fs.embarkPath("js/orbit.min.js")).toString(), path: fs.embarkPath("js/orbit.min.js")});
}
readFiles.push({filename: 'embark.js', content: fs.readFileSync(fs.embarkPath("js/build/embark.bundle.js")).toString(), path: fs.embarkPath("js/build/embark.bundle.js")});
}
});
// get plugins
var filesFromPlugins = [];
var filePlugins = self.plugins.getPluginsFor('pipelineFiles');
if (filePlugins.length > 0) {
filePlugins.forEach(function(plugin) {
try {
var fileObjects = plugin.runFilePipeline();
for (var i=0; i < fileObjects.length; i++) {
var fileObject = fileObjects[i];
filesFromPlugins.push(fileObject);
}
}
catch(err) {
self.logger.error(err.message);
}
});
}
filesFromPlugins.filter(function(file) {
if (utils.fileMatchesPattern(files, file.intendedPath)) {
readFiles.push(file);
}
});
// get user files
originalFiles.filter(function(file) {
return file.indexOf('.') >= 0;
}).filter(function(file) {
if (file === 'embark.js') {
return;
} else if (file === 'abi.js') {
readFiles.push({filename: file, content: "", path: file});
} else {
readFiles.push({filename: file, content: fs.readFileSync(file).toString(), path: file});
}
});
return readFiles;
};
Config.prototype.loadPluginContractFiles = function() {
var self = this;
var contractsPlugins = this.plugins.getPluginsFor('contractFiles');
if (contractsPlugins.length > 0) {
contractsPlugins.forEach(function(plugin) {
plugin.contractsFiles.forEach(function(file) {
var filename = file.replace('./','');
self.contractsFiles.push({filename: filename, content: plugin.loadPluginFile(file), path: plugin.pathToFile(file)});
});
});
}
};
module.exports = Config;

5
lib/core/core.js Normal file
View File

@ -0,0 +1,5 @@
var Core = function() {};
module.exports = Core;

20
lib/core/debug_util.js Normal file
View File

@ -0,0 +1,20 @@
// util to map async method names
function extend(filename, async) {
if (async._waterfall !== undefined) {
return;
}
async._waterfall = async.waterfall;
async.waterfall = function(_tasks, callback) {
var tasks = _tasks.map(function(t) {
var fn = function() {
console.log("async " + filename + ": " + t.name);
t.apply(t, arguments);
};
return fn;
});
async._waterfall(tasks, callback);
};
}
module.exports = extend;

147
lib/core/engine.js Normal file
View File

@ -0,0 +1,147 @@
var Events = require('./events.js');
var Logger = require('./logger.js');
var Config = require('./config.js');
var DeployManager = require('../contracts/deploy_manager.js');
var ABIGenerator = require('../contracts/abi.js');
var Dashboard = require('../dashboard/dashboard.js');
var ServicesMonitor = require('./services.js');
var Pipeline = require('../pipeline/pipeline.js');
var Server = require('../pipeline/server.js');
var Watch = require('../pipeline/watch.js');
var Engine = function(options) {
this.env = options.env;
this.embarkConfig = options.embarkConfig;
this.interceptLogs = options.interceptLogs;
this.version = "2.4.0";
};
Engine.prototype.init = function(_options) {
var options = _options || {};
this.events = new Events();
this.logger = options.logger || new Logger({logLevel: 'debug'});
this.config = new Config({env: this.env, logger: this.logger, events: this.events});
this.config.loadConfigFiles({embarkConfig: this.embarkConfig, interceptLogs: this.interceptLogs});
this.plugins = this.config.plugins;
};
Engine.prototype.startService = function(serviceName, _options) {
var options = _options || {};
var services = {
"monitor": this.monitorService,
"pipeline": this.pipelineService,
"abi": this.abiService,
"deployment": this.deploymentService,
"fileWatcher": this.fileWatchService,
"webServer": this.webServerService
};
var service = services[serviceName];
if (!service) {
throw new Error("unknown service: " + serviceName);
}
// need to be careful with circular references due to passing the web3 object
//this.logger.trace("calling: " + serviceName + "(" + JSON.stringify(options) + ")");
return service.apply(this, [options]);
};
Engine.prototype.monitorService = function(options) {
var servicesMonitor = new ServicesMonitor({
logger: this.logger,
config: this.config,
serverHost: options.serverHost,
serverPort: options.serverPort,
runWebserver: options.runWebserver,
version: this.version
});
servicesMonitor.startMonitor();
};
Engine.prototype.pipelineService = function(options) {
var self = this;
this.logger.setStatus("Building Assets");
var pipeline = new Pipeline({
buildDir: this.config.buildDir,
contractsFiles: this.config.contractsFiles,
assetFiles: this.config.assetFiles,
logger: this.logger,
plugins: this.plugins
});
this.events.on('abi', function(abi) {
self.currentAbi = abi;
pipeline.build(abi);
self.events.emit('outputDone');
});
this.events.on('file-event', function(fileType, path) {
if (fileType === 'asset') {
self.config.reloadConfig();
pipeline.build(self.abi, path);
self.events.emit('outputDone');
}
});
};
Engine.prototype.abiService = function(options) {
var self = this;
var generateABICode = function(contractsManager) {
var abiGenerator = new ABIGenerator({
blockchainConfig: self.config.blockchainConfig,
contractsManager: contractsManager,
plugins: self.plugins,
storageConfig: self.config.storageConfig,
communicationConfig: self.config.communicationConfig
});
var embarkJSABI = abiGenerator.generateABI({useEmbarkJS: true});
var vanillaABI = abiGenerator.generateABI({useEmbarkJS: false});
var vanillaContractsABI = abiGenerator.generateContracts(false);
self.events.emit('abi-contracts-vanila', vanillaContractsABI);
self.events.emit('abi-vanila', vanillaABI);
self.events.emit('abi', embarkJSABI);
};
this.events.on('contractsDeployed', generateABICode);
this.events.on('blockchainDisabled', generateABICode);
};
Engine.prototype.deploymentService = function(options) {
var self = this;
this.deployManager = new DeployManager({
web3: options.web3,
trackContracts: options.trackContracts,
config: this.config,
logger: this.logger,
plugins: this.plugins,
events: this.events
});
this.events.on('file-event', function(fileType, path) {
if (fileType === 'contract' || fileType === 'config') {
self.config.reloadConfig();
self.deployManager.deployContracts(function() {});
}
});
};
Engine.prototype.fileWatchService = function(options) {
this.logger.setStatus("Watching for changes");
var watch = new Watch({logger: this.logger, events: this.events});
watch.start();
};
Engine.prototype.webServerService = function(options) {
var webServerConfig = this.config.webServerConfig;
if (!webServerConfig.enabled) { return; }
this.logger.setStatus("Starting Server");
var server = new Server({
logger: this.logger,
host: options.host || webServerConfig.host,
port: options.port || webServerConfig.port
});
server.start(function(){});
};
module.exports = Engine;

3
lib/core/events.js Normal file
View File

@ -0,0 +1,3 @@
var EventEmitter = require('events');
module.exports = EventEmitter;

46
lib/core/fs.js Normal file
View File

@ -0,0 +1,46 @@
var fs = require('fs-extra');
var utils = require('./utils.js');
function mkdirpSync() {
return fs.mkdirpSync.apply(fs.mkdirpSync, arguments);
}
function copySync() {
return fs.copySync.apply(fs.copySync, arguments);
}
function writeFileSync() {
return fs.writeFileSync.apply(fs.writeFileSync, arguments);
}
function readFileSync() {
return fs.readFileSync.apply(fs.readFileSync, arguments);
}
function readJSONSync() {
return fs.readJSONSync.apply(fs.readJSONSync, arguments);
}
function writeJSONSync() {
return fs.writeJSONSync.apply(fs.writeJSONSync, arguments);
}
function existsSync(){
return fs.existsSync.apply(fs.existsSync, arguments);
}
// returns embarks root directory
function embarkPath(fileOrDir) {
return utils.joinPath(__dirname, '/../../', fileOrDir);
}
module.exports = {
mkdirpSync: mkdirpSync,
copySync: copySync,
readFileSync: readFileSync,
writeFileSync: writeFileSync,
readJSONSync: readJSONSync,
writeJSONSync: writeJSONSync,
existsSync: existsSync,
embarkPath: embarkPath
};

View File

@ -4,8 +4,9 @@ var Logger = function(options) {
this.logLevels = ['error', 'warn', 'info', 'debug', 'trace'];
this.logLevel = options.logLevel || 'info';
this.logFunction = options.logFunction || console.log;
this.contractsState = options.contractsState || console.log;
this.availableServices = options.availableServices || console.log;
this.contractsState = options.contractsState || function() {};
this.availableServices = options.availableServices || function() {};
this.setStatus = options.setStatus || console.log;
};
Logger.prototype.error = function(txt) {

159
lib/core/plugin.js Normal file
View File

@ -0,0 +1,159 @@
/*jshint esversion: 6, loopfunc: true */
var fs = require('./fs.js');
var utils = require('./utils.js');
// TODO: pass other params like blockchainConfig, contract files, etc..
var Plugin = function(options) {
this.name = options.name;
this.pluginModule = options.pluginModule;
this.pluginPath = options.pluginPath;
this.pluginConfig = options.pluginConfig;
this.shouldInterceptLogs = options.interceptLogs;
this.clientWeb3Providers = [];
this.contractsGenerators = [];
this.pipeline = [];
this.pipelineFiles = [];
this.console = [];
this.contractsConfigs = [];
this.contractsFiles = [];
this.compilers = [];
this.pluginTypes = [];
this.logger = options.logger;
this.events = options.events;
this.config = options.config;
};
Plugin.prototype.loadPlugin = function() {
if (this.shouldInterceptLogs) {
this.interceptLogs(this.pluginModule);
}
(this.pluginModule.call(this, this));
};
Plugin.prototype.loadPluginFile = function(filename) {
return fs.readFileSync(this.pathToFile(filename)).toString();
};
Plugin.prototype.pathToFile = function(filename) {
return utils.joinPath(this.pluginPath, filename);
};
Plugin.prototype.interceptLogs = function(context) {
var self = this;
// TODO: this is a bit nasty, figure out a better way
context.console = context.console || console;
//context.console.error = function(txt) {
// // TODO: logger should support an array instead of a single argument
// //self.logger.error.apply(self.logger, arguments);
// self.logger.error(self.name + " > " + txt);
//};
context.console.log = function(txt) {
self.logger.info(self.name + " > " + txt);
};
context.console.warn = function(txt) {
self.logger.warn(self.name + " > " + txt);
};
context.console.info = function(txt) {
self.logger.info(self.name + " > " + txt);
};
context.console.debug = function(txt) {
// TODO: ue JSON.stringify
self.logger.debug(self.name + " > " + txt);
};
context.console.trace = function(txt) {
self.logger.trace(self.name + " > " + txt);
};
};
// TODO: add deploy provider
Plugin.prototype.registerClientWeb3Provider = function(cb) {
this.clientWeb3Providers.push(cb);
this.pluginTypes.push('clientWeb3Provider');
};
Plugin.prototype.registerContractsGeneration = function(cb) {
this.contractsGenerators.push(cb);
this.pluginTypes.push('contractGeneration');
};
Plugin.prototype.registerPipeline = function(matcthingFiles, cb) {
// TODO: generate error for more than one pipeline per plugin
this.pipeline.push({matcthingFiles: matcthingFiles, cb: cb});
this.pluginTypes.push('pipeline');
};
Plugin.prototype.addFileToPipeline = function(file, intendedPath, options) {
this.pipelineFiles.push({file: file, intendedPath: intendedPath, options: options});
this.pluginTypes.push('pipelineFiles');
};
Plugin.prototype.addContractFile = function(file) {
this.contractsFiles.push(file);
this.pluginTypes.push('contractFiles');
};
Plugin.prototype.registerConsoleCommand = function(cb) {
this.console.push(cb);
this.pluginTypes.push('console');
};
Plugin.prototype.has = function(pluginType) {
return this.pluginTypes.indexOf(pluginType) >= 0;
};
Plugin.prototype.generateProvider = function(args) {
return this.clientWeb3Providers.map(function(cb) {
return cb.call(this, args);
}).join("\n");
};
Plugin.prototype.generateContracts = function(args) {
return this.contractsGenerators.map(function(cb) {
return cb.call(this, args);
}).join("\n");
};
Plugin.prototype.registerContractConfiguration = function(config) {
this.contractsConfigs.push(config);
this.pluginTypes.push('contractsConfig');
};
Plugin.prototype.registerCompiler = function(extension, cb) {
this.compilers.push({extension: extension, cb: cb});
this.pluginTypes.push('compilers');
};
Plugin.prototype.runCommands = function(cmd, options) {
return this.console.map(function(cb) {
return cb.call(this, cmd, options);
}).join("\n");
};
Plugin.prototype.runFilePipeline = function() {
var self = this;
return this.pipelineFiles.map(function(file) {
var obj = {};
obj.filename = file.file.replace('./','');
obj.content = self.loadPluginFile(file.file).toString();
obj.intendedPath = file.intendedPath;
obj.options = file.options;
obj.path = self.pathToFile(obj.filename);
return obj;
});
};
Plugin.prototype.runPipeline = function(args) {
// TODO: should iterate the pipelines
var pipeline = this.pipeline[0];
var shouldRunPipeline = utils.fileMatchesPattern(pipeline.matcthingFiles, args.targetFile);
if (shouldRunPipeline) {
return pipeline.cb.call(this, args);
} else {
return args.source;
}
};
module.exports = Plugin;

45
lib/core/plugins.js Normal file
View File

@ -0,0 +1,45 @@
var Plugin = require('./plugin.js');
var utils = require('./utils.js');
var Plugins = function(options) {
this.pluginList = options.plugins || [];
this.interceptLogs = options.interceptLogs;
this.plugins = [];
// TODO: need backup 'NullLogger'
this.logger = options.logger;
this.events = options.events;
this.config = options.config;
};
Plugins.prototype.loadPlugins = function() {
var pluginConfig;
for (var pluginName in this.pluginList) {
pluginConfig = this.pluginList[pluginName];
this.loadPlugin(pluginName, pluginConfig);
}
};
Plugins.prototype.listPlugins = function() {
var list = [];
for (var className in this.pluginList) {
list.push(className);
}
return list;
};
Plugins.prototype.loadPlugin = function(pluginName, pluginConfig) {
var pluginPath = utils.joinPath(process.env.PWD, 'node_modules', pluginName);
var plugin = require(pluginPath);
var pluginWrapper = new Plugin({name: pluginName, pluginModule: plugin, pluginConfig: pluginConfig, logger: this.logger, pluginPath: pluginPath, interceptLogs: this.interceptLogs, events: this.events, config: this.config});
pluginWrapper.loadPlugin();
this.plugins.push(pluginWrapper);
};
Plugins.prototype.getPluginsFor = function(pluginType) {
return this.plugins.filter(function(plugin) {
return plugin.has(pluginType);
});
};
module.exports = Plugins;

18
lib/core/runCode.js Normal file
View File

@ -0,0 +1,18 @@
var Web3 = require('web3');
var web3;
// ======================
// the eval is used for evaluating some of the contact calls for different purposes
// this should be at least moved to a different process and scope
// for now it is defined here
// ======================
function doEval(code, _web3) {
if (_web3) {
web3 = _web3;
}
return eval(code); // jshint ignore:line
}
module.exports = {
doEval: doEval
};

113
lib/core/services.js Normal file
View File

@ -0,0 +1,113 @@
var Web3 = require('web3');
var async = require('async');
var http = require('http');
var utils = require('./utils.js');
// TODO: needs a refactor and be done in a different way
var ServicesMonitor = function(options) {
this.logger = options.logger;
this.interval = options.interval || 5000;
this.config = options.config;
this.serverHost = options.serverHost || 'localhost';
this.serverPort = options.serverPort || 8000;
this.runWebserver = options.runWebserver;
this.version = options.version;
};
ServicesMonitor.prototype.startMonitor = function() {
this.check();
this.monitor = setInterval(this.check.bind(this), this.interval);
};
ServicesMonitor.prototype.stopMonitor = function() {
};
ServicesMonitor.prototype.check = function() {
var self = this;
async.waterfall([
function connectWeb3(callback) {
self.logger.trace('connectWeb3');
var web3 = new Web3();
var web3Endpoint = 'http://' + self.config.blockchainConfig.rpcHost + ':' + self.config.blockchainConfig.rpcPort;
web3.setProvider(new web3.providers.HttpProvider(web3Endpoint));
callback(null, web3, []);
},
function addEmbarkVersion(web3, result, callback) {
self.logger.trace('addEmbarkVersion');
result.push(('Embark ' + self.version).green);
callback(null, web3, result);
},
function checkEthereum(web3, result, callback) {
self.logger.trace('checkEthereum');
var service;
if (web3.isConnected()) {
service = (web3.version.node.split("/")[0] + " " + web3.version.node.split("/")[1].split("-")[0] + " (Ethereum)").green;
} else {
service = "No Blockchain node found".red;
}
result.push(service);
callback(null, web3, result);
},
function checkWhisper(web3, result, callback) {
self.logger.trace('checkWhisper');
web3.version.getWhisper(function(err, res) {
var service = 'Whisper';
result.push(err ? service.red : service.green);
callback(null, result);
});
},
function checkIPFS(result, callback) {
self.logger.trace('checkIPFS');
utils.checkIsAvailable('http://localhost:5001', function(available) {
if (available) {
//Ideally this method should be in an IPFS API JSONRPC wrapper
//The URL should also be flexible to accept non-default IPFS url
self.logger.trace("Checking IPFS version...");
http.get('http://localhost:5001/api/v0/version', function(res) {
var body = '';
res.on('data', function(d) {
body += d;
});
res.on('end', function() {
var parsed = JSON.parse(body);
if(parsed.Version){
result.push(("IPFS " + parsed.Version).green);
}
else{
result.push("IPFS".green);
}
callback(null, result);
});
res.on('error', function(err) {
self.logger.trace("Check IPFS version error: " + err);
result.push("IPFS".green);
callback(null, result);
});
});
}
else {
result.push('IPFS'.red);
return callback(null, result);
}
});
},
function checkDevServer(result, callback) {
var host = self.serverHost || self.config.webServerConfig.host;
var port = self.serverPort || self.config.webServerConfig.port;
self.logger.trace('checkDevServer');
var devServer = 'Webserver (http://' + host + ':' + port + ')';
devServer = (self.runWebserver) ? devServer.green : devServer.red;
result.push(devServer);
callback(null, result);
}
], function(err, result) {
if (err) {
self.logger.error(err.message);
} else {
self.logger.availableServices(result);
}
});
};
module.exports = ServicesMonitor;

102
lib/core/test.js Normal file
View File

@ -0,0 +1,102 @@
var async = require('async');
var Web3 = require('web3');
var Embark = require('../index.js');
var Engine = require('./engine.js');
var ABIGenerator = require('../contracts/abi.js');
var ContractsManager = require('../contracts/contracts.js');
var Deploy = require('../contracts/deploy.js');
var Config = require('./config.js');
var RunCode = require('./runCode.js');
var TestLogger = require('./test_logger.js');
var getSimulator = function() {
try {
var sim = require('ethereumjs-testrpc');
return sim;
} catch (e) {
if (e.code === 'MODULE_NOT_FOUND') {
console.log('Simulator not found; Please install it with "npm install ethereumjs-testrpc --save"');
console.log('IMPORTANT: if you using a NodeJS version older than 6.9.1 then you need to install an older version of testrpc "npm install ethereumjs-testrpc@2.0 --save"');
console.log('For more information see https://github.com/ethereumjs/testrpc');
// TODO: should throw exception instead
return process.exit();
}
console.log("==============");
console.log("Tried to load testrpc but an error occurred. This is a problem with testrpc");
console.log('IMPORTANT: if you using a NodeJS version older than 6.9.1 then you need to install an older version of testrpc "npm install ethereumjs-testrpc@2.0 --save". Alternatively install node 6.9.1 and the testrpc 3.0');
console.log("==============");
throw e;
}
};
var Test = function(options) {
this.options = options || {};
var simOptions = this.options.simulatorOptions || {};
this.engine = new Engine({
env: this.options.env || 'test',
// TODO: confi will need to detect if this is a obj
embarkConfig: this.options.embarkConfig || 'embark.json',
interceptLogs: false
});
this.engine.init({
logger: new TestLogger({logLevel: this.options.logLevel || 'debug'})
});
this.sim = getSimulator();
this.web3 = new Web3();
this.web3.setProvider(this.sim.provider(simOptions));
};
Test.prototype.deployAll = function(contractsConfig, cb) {
var self = this;
async.waterfall([
function getConfig(callback) {
self.engine.config.contractsConfig = {contracts: contractsConfig};
callback();
},
function startServices(callback) {
//{abiType: 'contracts', embarkJS: false}
self.engine.startService("abi");
self.engine.startService("deployment", {
web3: self.web3,
trackContracts: false
});
callback();
},
function deploy(callback) {
self.engine.events.on('abi-contracts-vanila', function(vanillaABI) {
callback(null, vanillaABI);
});
self.engine.deployManager.deployContracts(function(err, result) {
if (err) {
console.log(err);
callback(err);
}
});
}
], function(err, result) {
if (err) {
console.log("got error");
process.exit();
}
// this should be part of the waterfall and not just something done at the
// end
self.web3.eth.getAccounts(function(err, accounts) {
if (err) {
throw new Error(err);
}
self.web3.eth.defaultAccount = accounts[0];
RunCode.doEval(result, self.web3); // jshint ignore:line
cb();
});
});
};
module.exports = Test;

67
lib/core/utils.js Normal file
View File

@ -0,0 +1,67 @@
/*global exit */
var path = require('path');
var globule = require('globule');
var merge = require('merge');
var http = require('http');
var shelljs = require('shelljs');
function joinPath() {
return path.join.apply(path.join, arguments);
}
function filesMatchingPattern(files) {
return globule.find(files, {nonull: true});
}
function fileMatchesPattern(patterns, intendedPath) {
return globule.isMatch(patterns, intendedPath);
}
function recursiveMerge(target, source) {
return merge.recursive(target, source);
}
function checkIsAvailable(url, callback) {
http.get(url, function(res) {
callback(true);
}).on('error', function(res) {
callback(false);
});
}
function runCmd(cmd, options) {
var result = shelljs.exec(cmd, options || {silent: true});
if (result.code !== 0) {
console.log("error doing.. " + cmd);
console.log(result.output);
if (result.stderr !== undefined) {
console.log(result.stderr);
}
exit();
}
}
function cd(folder) {
shelljs.cd(folder);
}
function sed(file, pattern, replace) {
shelljs.sed('-i', pattern, replace, file);
}
function exit(code) {
process.exit(code);
}
module.exports = {
joinPath: joinPath,
filesMatchingPattern: filesMatchingPattern,
fileMatchesPattern: fileMatchesPattern,
recursiveMerge: recursiveMerge,
checkIsAvailable: checkIsAvailable,
runCmd: runCmd,
cd: cd,
sed: sed,
exit: exit
};

View File

@ -0,0 +1,28 @@
var CommandHistory = function() {
this.history = [];
this.pointer = -1;
};
CommandHistory.prototype.addCommand = function(cmd) {
this.history.push(cmd);
this.pointer = this.history.length;
};
CommandHistory.prototype.getPreviousCommand = function(cmd) {
if (this.pointer >= 0) {
this.pointer--;
}
return this.history[this.pointer];
};
CommandHistory.prototype.getNextCommand = function(cmd) {
if (this.pointer >= this.history.length) {
this.pointer = this.history.length - 1;
return '';
}
this.pointer++;
return this.history[this.pointer];
};
module.exports = CommandHistory;

60
lib/dashboard/console.js Normal file
View File

@ -0,0 +1,60 @@
var utils = require('../core/utils.js');
var RunCode = require('../core/runCode.js');
var Console = function(options) {
this.plugins = options.plugins;
this.version = options.version;
};
Console.prototype.runCode = function(code) {
RunCode.doEval(code); // jshint ignore:line
};
Console.prototype.processEmbarkCmd = function(cmd) {
if (cmd === 'help') {
var helpText = [
'Welcome to Embark ' + this.version,
'',
'possible commands are:',
// TODO: only if the blockchain is actually active!
// will need to pass te current embark state here
'web3 - instantiated web3.js object configured to the current environment',
'quit - to immediatly exit',
'',
'The web3 object and the interfaces for the deployed contracts and their methods are also available'
];
return helpText.join('\n');
} else if (cmd === 'quit') {
utils.exit();
}
return false;
};
Console.prototype.executeCmd = function(cmd, callback) {
var plugin, pluginOutput;
var plugins = this.plugins.getPluginsFor('console');
for (var i = 0; i < plugins.length; i++) {
plugin = plugins[i];
pluginOutput = plugin.runCommands(cmd, {});
if (pluginOutput !== false && pluginOutput !== 'false') return callback(pluginOutput);
}
var output = this.processEmbarkCmd(cmd);
if (output) {
return callback(output);
}
try {
var result = RunCode.doEval(cmd);
return callback(result);
}
catch(e) {
if (e.message.indexOf('not defined') > 0) {
return callback(("error: " + e.message).red + ("\nType " + "help".bold + " to see the list of available commands").cyan);
} else {
return callback(e.message);
}
}
};
module.exports = Console;

View File

@ -0,0 +1,43 @@
var async = require('async');
var Monitor = require('./monitor.js');
var Console = require('./console.js');
var Dashboard = function(options) {
this.logger = options.logger;
this.plugins = options.plugins;
this.version = options.version;
this.env = options.env;
};
Dashboard.prototype.start = function(done) {
var console, monitor;
var self = this;
async.waterfall([
function startConsole(callback) {
console = new Console({plugins: self.plugins, version: self.version});
callback();
},
function startMonitor(callback) {
monitor = new Monitor({env: self.env, console: console});
self.logger.logFunction = monitor.logEntry;
self.logger.contractsState = monitor.setContracts;
self.logger.availableServices = monitor.availableServices;
self.logger.setStatus = monitor.setStatus.bind(monitor);
self.logger.info('========================'.bold.green);
self.logger.info(('Welcome to Embark ' + self.version).yellow.bold);
self.logger.info('========================'.bold.green);
// TODO: do this after monitor is rendered
callback();
}
], function() {
self.console = console;
self.monitor = monitor;
done();
});
};
module.exports = Dashboard;

View File

@ -1,41 +1,16 @@
/*jshint esversion: 6 */
var blessed = require("blessed");
var CommandHistory = function() {
this.history = [];
this.pointer = -1;
};
CommandHistory.prototype.addCommand = function(cmd) {
this.history.push(cmd);
this.pointer = this.history.length;
};
CommandHistory.prototype.getPreviousCommand = function(cmd) {
if (this.pointer >= 0) {
this.pointer--;
}
return this.history[this.pointer];
};
CommandHistory.prototype.getNextCommand = function(cmd) {
if (this.pointer >= this.history.length) {
this.pointer = this.history.length - 1;
return '';
}
this.pointer++;
return this.history[this.pointer];
};
var CommandHistory = require('./command_history.js');
function Dashboard(options) {
var title = options && options.title || "Embark 2.0";
var title = (options && options.title) || "Embark 2.4.0";
this.env = options.env;
this.console = options.console;
this.history = new CommandHistory();
this.color = options && options.color || "green";
this.minimal = options && options.minimal || false;
this.color = (options && options.color) || "green";
this.minimal = (options && options.minimal) || false;
this.screen = blessed.screen({
smartCSR: true,
@ -45,10 +20,10 @@ function Dashboard(options) {
autoPadding: true
});
this.layoutLog.call(this);
this.layoutStatus.call(this);
this.layoutModules.call(this);
this.layoutCmd.call(this);
this.layoutLog();
this.layoutStatus();
this.layoutModules();
this.layoutCmd();
this.screen.key(["C-c"], function() {
process.exit(0);
@ -272,13 +247,13 @@ Dashboard.prototype.layoutStatus = function() {
label: "Available Services",
tags: true,
padding: this.minimal ? {
left: 1,
left: 1
} : 1,
width: "100%",
height: "60%",
valign: "top",
border: {
type: "line",
type: "line"
},
style: {
fg: -1,
@ -358,7 +333,7 @@ Dashboard.prototype.layoutCmd = function() {
this.input.on('submit', function(data) {
if (data !== '') {
self.history.addCommand(data);
self.logText.log('console> ' + data);
self.logText.log('console> '.bold.green + data);
self.console.executeCmd(data, function(result) {
self.logText.log(result);
});

View File

@ -1,138 +0,0 @@
var GethCommands = function(options) {
this.config = options.config;
this.name = "Go-Ethereum (https://github.com/ethereum/go-ethereum)";
};
GethCommands.prototype.initGenesisCommmand = function() {
var config = this.config;
var cmd = "geth ";
if (config.networkType === 'testnet') {
cmd += "--testnet ";
} else if (config.networkType === 'olympic') {
cmd += "--olympic ";
}
if (config.datadir) {
cmd += "--datadir=\"" + config.datadir + "\" ";
}
if (config.genesisBlock) {
cmd += "init \"" + config.genesisBlock + "\" ";
}
return cmd;
};
GethCommands.prototype.newAccountCommand = function() {
var config = this.config;
var cmd = "geth ";
if (config.networkType === 'testnet') {
cmd += "--testnet ";
} else if (config.networkType === 'olympic') {
cmd += "--olympic ";
}
if (config.datadir) {
cmd += "--datadir=\"" + config.datadir + "\" ";
}
if (config.account && config.account.password) {
cmd += "--password " + config.account.password + " ";
}
return cmd + "account new ";
};
GethCommands.prototype.listAccountsCommand = function() {
var config = this.config;
var cmd = "geth ";
if (config.networkType === 'testnet') {
cmd += "--testnet ";
} else if (config.networkType === 'olympic') {
cmd += "--olympic ";
}
if (config.datadir) {
cmd += "--datadir=\"" + config.datadir + "\" ";
}
if (config.account && config.account.password) {
cmd += "--password " + config.account.password + " ";
}
return cmd + "account list ";
};
GethCommands.prototype.mainCommand = function(address) {
var config = this.config;
var cmd = "geth ";
var rpc_api = ['eth', 'web3'];
if (config.datadir) {
cmd += "--datadir=\"" + config.datadir + "\" ";
}
if (config.networkType === 'testnet') {
cmd += "--testnet ";
} else if (config.networkType === 'olympic') {
cmd += "--olympic ";
}
if (config.networkType === 'custom') {
cmd += "--networkid " + config.networkId + " ";
}
if (config.account && config.account.password) {
cmd += "--password " + config.account.password + " ";
}
cmd += "--port " + "30303" + " ";
cmd += "--rpc ";
cmd += "--rpcport " + config.rpcPort + " ";
cmd += "--rpcaddr " + config.rpcHost + " ";
if (config.rpcCorsDomain) {
if (config.rpcCorsDomain === '*') {
console.log('==================================');
console.log('make sure you know what you are doing');
console.log('==================================');
}
cmd += "--rpccorsdomain=\"" + config.rpcCorsDomain + "\" ";
} else {
console.log('==================================');
console.log('warning: cors is not set');
console.log('==================================');
}
if (config.nodiscover) {
cmd += "--nodiscover ";
}
if (config.mineWhenNeeded || config.mine) {
cmd += "--mine ";
}
if (config.whisper) {
cmd += "--shh ";
rpc_api.push('shh');
}
cmd += '--rpcapi "' + rpc_api.join(',') + '" ';
var accountAddress = config.account.address || address;
if (accountAddress) {
cmd += "--unlock=" + accountAddress + " ";
}
if (config.mineWhenNeeded) {
cmd += "js .embark/development/js/mine.js";
}
return cmd;
};
module.exports = GethCommands;

View File

@ -1,257 +1,172 @@
/*jshint esversion: 6 */
var async = require('async');
//require("./core/debug_util.js")(__filename, async);
var Web3 = require('web3');
var fs = require('fs');
var grunt = require('grunt');
var mkdirp = require('mkdirp');
var colors = require('colors');
var chokidar = require('chokidar');
var Engine = require('./core/engine.js');
var Blockchain = require('./cmds/blockchain/blockchain.js');
var Simulator = require('./cmds/simulator.js');
var TemplateGenerator = require('./cmds/template_generator.js');
var Test = require('./core/test.js');
var Logger = require('./core/logger.js');
var Config = require('./core/config.js');
var Events = require('./core/events.js');
var Dashboard = require('./dashboard/dashboard.js');
var IPFS = require('./upload/ipfs.js');
var Swarm = require('./upload/swarm.js');
var Cmd = require('./cmd.js');
var Deploy = require('./deploy.js');
var ContractsManager = require('./contracts.js');
var ABIGenerator = require('./abi.js');
var TemplateGenerator = require('./template_generator.js');
var Blockchain = require('./blockchain.js');
var Server = require('./server.js');
var Watch = require('./watch.js');
var Pipeline = require('./pipeline.js');
var Test = require('./test.js');
var Logger = require('./logger.js');
var Config = require('./config.js');
var Monitor = require('./monitor.js');
var ServicesMonitor = require('./services.js');
var Console = require('./console.js');
var IPFS = require('./ipfs.js');
var Embark = {
version: '2.4.0',
process: function(args) {
var cmd = new Cmd(Embark);
cmd.process(args);
},
initConfig: function(env, options) {
this.events = new Events();
this.logger = new Logger({logLevel: 'debug'});
this.config = new Config({env: env, logger: this.logger, events: this.events});
this.config.loadConfigFiles(options);
this.plugins = this.config.plugins;
},
blockchain: function(env, client) {
var blockchain = Blockchain(this.config.blockchainConfig, client, env);
blockchain.run();
},
simulator: function(options) {
var simulator = new Simulator({blockchainConfig: this.config.blockchainConfig});
simulator.run(options);
},
generateTemplate: function(templateName, destinationFolder, name) {
var templateGenerator = new TemplateGenerator(templateName);
templateGenerator.generate(destinationFolder, name);
},
initConfig: function(env, options) {
this.config = new Config({env: env});
this.config.loadConfigFiles(options);
this.logger = new Logger({logLevel: 'debug'});
//this.contractsManager = new ContractsManager(configDir, files, env);
//this.contractsManager.init();
//return this.contractsManager;
},
redeploy: function(env) {
var self = this;
async.waterfall([
function reloadFiles(callback) {
self.config.reloadConfig();
callback();
},
self.buildDeployGenerate.bind(self)
], function(err, result) {
self.logger.trace("finished".underline);
});
},
run: function(options) {
var self = this;
var env = options.env;
async.waterfall([
function startConsole(callback) {
Embark.console = new Console();
callback();
},
function startMonitor(callback) {
Embark.monitor = new Monitor({env: env, console: Embark.console});
Embark.monitor.setStatus("Starting");
self.logger.logFunction = Embark.monitor.logEntry;
self.logger.contractsState = Embark.monitor.setContracts;
self.logger.availableServices = Embark.monitor.availableServices;
// TODO: do this after monitor is rendered
callback();
},
function monitorServices(callback) {
Embark.servicesMonitor = new ServicesMonitor({
logger: Embark.logger,
config: Embark.config,
serverPort: options.serverPort
});
Embark.servicesMonitor.startMonitor();
callback();
},
self.buildDeployGenerate.bind(self),
function startAssetServer(callback) {
Embark.monitor.setStatus("Starting Server");
var server = new Server({logger: self.logger, port: options.serverPort});
server.start(callback);
},
function watchFilesForChanges(callback) {
Embark.monitor.setStatus("Watching for changes");
var watch = new Watch({logger: self.logger});
watch.start();
watch.on('redeploy', function() {
self.logger.info("received redeploy event");
Embark.redeploy();
});
watch.on('rebuildAssets', function() {
self.logger.info("received rebuildAssets event");
// TODO: make this more efficient
Embark.redeploy();
});
callback();
}
], function(err, result) {
Embark.monitor.setStatus("Ready".green);
self.logger.trace("finished".underline);
var engine = new Engine({
env: options.env,
embarkConfig: 'embark.json'
});
},
engine.init();
build: function(env) {
async.waterfall([
function deployAndGenerateABI(callback) {
Embark.deploy(function(abi) {
callback(null, abi);
});
},
function buildPipeline(abi, callback) {
var pipeline = new Pipeline({});
pipeline.build(abi);
callback();
}
], function(err, result) {
self.logger.trace("finished".underline);
});
},
if (!options.useDashboard) {
console.log('========================'.bold.green);
console.log(('Welcome to Embark ' + Embark.version).yellow.bold);
console.log('========================'.bold.green);
}
blockchain: function(env, client) {
var blockchain = Blockchain(this.config.blockchainConfig, client);
blockchain.run({env: env});
},
buildAndDeploy: function(done) {
var self = this;
async.waterfall([
function buildContracts(callback) {
var contractsManager = new ContractsManager({
contractFiles: self.config.contractsFiles,
contractsConfig: self.config.contractsConfig,
logger: Embark.logger
});
try {
contractsManager.build();
callback(null, contractsManager);
} catch(err) {
callback(new Error(err.message));
}
},
function deployContracts(contractsManager, callback) {
//TODO: figure out where to put this since the web3 can be passed along if needed
// perhaps it should go into the deploy object itself
// TODO: should come from the config object
var web3 = new Web3();
var web3Endpoint = 'http://' + self.config.blockchainConfig.rpcHost + ':' + self.config.blockchainConfig.rpcPort;
web3.setProvider(new web3.providers.HttpProvider(web3Endpoint));
if (!web3.isConnected()) {
console.log(("Couldn't connect to " + web3Endpoint.underline + " are you sure it's on?").red);
console.log("make sure you have an ethereum node or simulator running. e.g 'embark blockchain'".magenta);
exit();
async.parallel([
function startDashboard(callback) {
if (!options.useDashboard) {
return callback();
}
web3.eth.getAccounts(function(err, accounts) {
web3.eth.defaultAccount = accounts[0];
var deploy = new Deploy({
web3: web3,
contractsManager: contractsManager,
logger: Embark.logger,
chainConfig: self.config.chainTracker,
env: self.config.env
});
deploy.deployAll(function() {
callback(null, contractsManager);
});
var dashboard = new Dashboard({
logger: engine.logger,
plugins: engine.plugins,
version: engine.version,
env: engine.env
});
dashboard.start(function() {
engine.events.on('abi-vanila', function(abi) {
dashboard.console.runCode(abi);
});
}
], function(err, result) {
if (err) {
done(err, null);
} else {
done(null, result);
}
});
},
deploy: function(done) {
var self = this;
async.waterfall([
function buildAndDeploy(callback) {
Embark.buildAndDeploy(function(contractsManager) {
callback(null, contractsManager);
callback();
});
},
function generateABI(contractsManager, callback) {
var abiGenerator = new ABIGenerator(self.config.blockchainConfig, contractsManager);
callback(null, abiGenerator.generateABI({useEmbarkJS: true}));
}
], function(err, result) {
done(result);
});
},
function (callback) {
var pluginList = engine.plugins.listPlugins();
if (pluginList.length > 0) {
engine.logger.info("loaded plugins: " + pluginList.join(", "));
}
if (options.useDashboard) {
engine.startService("monitor", {
serverHost: options.serverHost,
serverPort: options.serverPort,
runWebserver: options.runWebserver
});
}
engine.startService("pipeline");
engine.startService("abi");
engine.startService("deployment");
buildDeployGenerate: function(done) {
var self = this;
Embark.monitor.setStatus("Deploying...".magenta.underline);
async.waterfall([
function deployAndBuildContractsManager(callback) {
Embark.buildAndDeploy(function(err, contractsManager) {
if (err) {
callback(err);
} else {
callback(null, contractsManager);
engine.deployManager.deployContracts(function() {
engine.startService("fileWatcher");
if (options.runWebserver) {
engine.startService("webServer", {
host: options.serverHost,
port: options.serverPort
});
}
callback();
});
},
function generateConsoleABI(contractsManager, callback) {
var abiGenerator = new ABIGenerator(self.config.blockchainConfig, contractsManager);
var consoleABI = abiGenerator.generateABI({useEmbarkJS: false});
Embark.console.runCode(consoleABI);
callback(null, contractsManager);
},
function generateABI(contractsManager, callback) {
var abiGenerator = new ABIGenerator(self.config.blockchainConfig, contractsManager);
callback(null, abiGenerator.generateABI({useEmbarkJS: true}));
},
function buildPipeline(abi, callback) {
Embark.monitor.setStatus("Building Assets");
var pipeline = new Pipeline({
buildDir: self.config.buildDir,
contractsFiles: self.config.contractsFiles,
assetFiles: self.config.assetFiles,
logger: self.logger
});
pipeline.build(abi);
callback();
}
], function(err, result) {
if (err) {
self.logger.error("error deploying");
self.logger.error(err.message);
Embark.monitor.setStatus("Deployment Error".red);
engine.logger.error(err.message);
} else {
Embark.monitor.setStatus("Ready".green);
engine.logger.setStatus("Ready".green);
engine.logger.info("Looking for documentation? you can find it at ".cyan + "http://embark.readthedocs.io/".green.underline);
engine.logger.info("Ready".underline);
engine.events.emit('firstDeploymentDone');
}
done(result);
});
},
build: function(options) {
var self = this;
var engine = new Engine({
env: options.env,
embarkConfig: 'embark.json',
interceptLogs: false
});
engine.init();
async.waterfall([
function startServices(callback) {
var pluginList = engine.plugins.listPlugins();
if (pluginList.length > 0) {
engine.logger.info("loaded plugins: " + pluginList.join(", "));
}
engine.startService("pipeline");
engine.startService("abi");
engine.startService("deployment");
callback();
},
function deploy(callback) {
engine.deployManager.deployContracts(function() {
callback();
});
}
], function(err, result) {
if (err) {
engine.logger.error(err.message);
} else {
engine.logger.info("finished building".underline);
}
// needed due to child processes
process.exit();
});
},
@ -260,9 +175,17 @@ var Embark = {
},
// TODO: should deploy if it hasn't already
ipfs: function() {
var ipfs = new IPFS({buildDir: 'dist/'});
ipfs.deploy();
upload: function(platform) {
if (platform === 'ipfs') {
var ipfs = new IPFS({buildDir: 'dist/', plugins: this.plugins, storageConfig: this.config.storageConfig});
ipfs.deploy();
} else if (platform === 'swarm') {
var swarm = new Swarm({buildDir: 'dist/', plugins: this.plugins, storageConfig: this.config.storageConfig});
swarm.deploy();
} else {
console.log(("unknown platform: " + platform).red);
console.log('try "embark upload ipfs" or "embark upload swarm"'.green);
}
}
};

View File

@ -1,29 +0,0 @@
var colors = require('colors');
var IPFS = function(options) {
this.options = options;
this.buildDir = options.buildDir || 'dist/';
};
IPFS.prototype.deploy = function() {
var ipfs_bin = exec('which ipfs').output.split("\n")[0];
if (ipfs_bin==='ipfs not found'){
console.log('=== WARNING: IPFS not in an executable path. Guessing ~/go/bin/ipfs for path'.yellow);
ipfs_bin = "~/go/bin/ipfs";
}
var cmd = ipfs_bin + " add -r " + build_dir;
console.log(("=== adding " + cmd + " to ipfs").green);
var result = exec(cmd);
var rows = result.output.split("\n");
var dir_row = rows[rows.length - 2];
var dir_hash = dir_row.split(" ")[1];
console.log(("=== DApp available at http://localhost:8080/ipfs/" + dir_hash + "/").green);
console.log(("=== DApp available at http://gateway.ipfs.io/ipfs/" + dir_hash + "/").green);
};
module.exports = IPFS;

View File

@ -1,36 +0,0 @@
/*jshint esversion: 6, loopfunc: true */
var fs = require('fs');
var grunt = require('grunt');
var mkdirp = require('mkdirp');
var Pipeline = function(options) {
this.buildDir = options.buildDir;
this.contractsFiles = options.contractsFiles;
this.assetFiles = options.assetFiles;
this.logger = options.logger;
};
Pipeline.prototype.build = function(abi) {
var self = this;
for(var targetFile in this.assetFiles) {
var content = this.assetFiles[targetFile].map(file => {
self.logger.info("reading " + file.filename);
if (file.filename === 'embark.js') {
return file.content + "\n" + abi;
} else {
return file.content;
}
}).join("\n");
var dir = targetFile.split('/').slice(0, -1).join('/');
self.logger.info("creating dir " + this.buildDir + dir);
mkdirp.sync(this.buildDir + dir);
self.logger.info("writing file " + this.buildDir + targetFile);
fs.writeFileSync(this.buildDir + targetFile, content);
}
};
module.exports = Pipeline;

80
lib/pipeline/pipeline.js Normal file
View File

@ -0,0 +1,80 @@
/*jshint esversion: 6, loopfunc: true */
var fs = require('../core/fs.js');
var Pipeline = function(options) {
this.buildDir = options.buildDir;
this.contractsFiles = options.contractsFiles;
this.assetFiles = options.assetFiles;
this.logger = options.logger;
this.plugins = options.plugins;
};
Pipeline.prototype.build = function(abi, path) {
var self = this;
for(var targetFile in this.assetFiles) {
var contentFiles = this.assetFiles[targetFile].map(file => {
self.logger.trace("reading " + file.filename);
var pipelinePlugins = this.plugins.getPluginsFor('pipeline');
if (file.filename === 'embark.js') {
return {content: file.content + "\n" + abi, filename: file.filename, path: file.path, modified: true};
} else if (file.filename === 'abi.js') {
return {content: abi, filename: file.filename, path: file.path, modified: true};
} else if (['web3.js', 'ipfs.js', 'ipfs-api.js', 'orbit.js'].indexOf(file.filename) >= 0) {
file.modified = true;
return file;
} else {
if (pipelinePlugins.length > 0) {
pipelinePlugins.forEach(function(plugin) {
try {
if (file.options && file.options.skipPipeline) {
return;
}
file.content = plugin.runPipeline({targetFile: file.filename, source: file.content});
file.modified = true;
}
catch(err) {
self.logger.error(err.message);
}
});
}
return file;
}
});
var dir = targetFile.split('/').slice(0, -1).join('/');
self.logger.trace("creating dir " + this.buildDir + dir);
fs.mkdirpSync(this.buildDir + dir);
// if it's a directory
if (targetFile.slice(-1) === '/' || targetFile.indexOf('.') === -1) {
var targetDir = targetFile;
if (targetDir.slice(-1) !== '/') {
targetDir = targetDir + '/';
}
contentFiles.map(function(file) {
var filename = file.filename.replace('app/', '');
filename = filename.replace(targetDir, '');
self.logger.info("writing file " + (self.buildDir + targetDir + filename).bold.dim);
fs.copySync(self.buildDir + targetDir + filename, file.path, {overwrite: true});
});
} else {
var content = contentFiles.map(function(file) {
return file.content;
}).join("\n");
self.logger.info("writing file " + (this.buildDir + targetFile).bold.dim);
fs.writeFileSync(this.buildDir + targetFile, content);
}
}
};
module.exports = Pipeline;

View File

@ -5,6 +5,7 @@ var serveStatic = require('serve-static');
var Server = function(options) {
this.dist = options.dist || 'dist/';
this.port = options.port || 8000;
this.hostname = options.host || 'localhost';
this.logger = options.logger;
};
@ -15,8 +16,8 @@ Server.prototype.start = function(callback) {
serve(req, res, finalhandler(req, res));
});
this.logger.info(("listening on port " + this.port).underline.green);
server.listen(this.port) ;
this.logger.info("webserver available at " + ("http://" + this.hostname + ":" + this.port).bold.underline.green);
server.listen(this.port, this.hostname) ;
callback();
};

View File

@ -1,17 +1,20 @@
/*jshint esversion: 6 */
var fs = require('fs');
var chokidar = require('chokidar');
var fs = require('../core/fs.js');
// TODO: this should be receiving the config object not re-reading the
// embark.json file
var Watch = function(options) {
this.logger = options.logger;
this.events = {};
this.events = options.events;
};
Watch.prototype.start = function() {
var self = this;
// TODO: should come from the config object instead of reading the file
// directly
var embarkConfig = JSON.parse(fs.readFileSync("embark.json"));
var embarkConfig = fs.readJSONSync("embark.json");
this.watchAssets(embarkConfig, function() {
self.logger.trace('ready to watch asset changes');
@ -25,7 +28,7 @@ Watch.prototype.start = function() {
self.logger.trace('ready to watch config changes');
});
this.logger.info("ready to watch changes");
this.logger.info("ready to watch file changes");
};
Watch.prototype.watchAssets = function(embarkConfig, callback) {
@ -41,7 +44,8 @@ Watch.prototype.watchAssets = function(embarkConfig, callback) {
filesToWatch,
function(eventName, path) {
self.logger.info(`${eventName}: ${path}`);
self.trigger('rebuildAssets', path);
self.events.emit('file-' + eventName, 'asset', path);
self.events.emit('file-event', 'asset', path);
},
function() {
callback();
@ -55,7 +59,8 @@ Watch.prototype.watchContracts = function(embarkConfig, callback) {
[embarkConfig.contracts],
function(eventName, path) {
self.logger.info(`${eventName}: ${path}`);
self.trigger('redeploy', path);
self.events.emit('file-' + eventName, 'contract', path);
self.events.emit('file-event', 'contract', path);
},
function() {
callback();
@ -69,7 +74,8 @@ Watch.prototype.watchConfigs = function(callback) {
"config/**/contracts.json",
function(eventName, path) {
self.logger.info(`${eventName}: ${path}`);
self.trigger('redeploy', path);
self.events.emit('file-' + eventName, 'config', path);
self.events.emit('file-event', 'config', path);
},
function() {
callback();
@ -87,17 +93,9 @@ Watch.prototype.watchFiles = function(files, changeCallback, doneCallback) {
configWatcher
.on('add', path => changeCallback('add', path))
.on('change', path => changeCallback('add', path))
.on('unlink', path => changeCallback('add', path))
.on('change', path => changeCallback('change', path))
.on('unlink', path => changeCallback('remove', path))
.on('ready', doneCallback);
};
Watch.prototype.on = function(eventName, callback) {
this.events[eventName] = callback;
};
Watch.prototype.trigger = function(eventName, values) {
this.events[eventName](values);
};
module.exports = Watch;

Some files were not shown because too many files have changed in this diff Show More