Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update DISCO documentation #614

Merged
merged 66 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
76d2c86
Fix emojis in README.md
JulienVig Jan 8, 2024
16c8bf4
Fix some text formatting in DEV.md
JulienVig Jan 8, 2024
ea74384
Update DISCO acronym formatting
JulienVig Jan 9, 2024
5cb9bea
WIP: Currently updating installation instructions in DEV.md
JulienVig Jan 9, 2024
200cec0
Rewrite DEV.md with additional explanations and installation instruct…
JulienVig Jan 9, 2024
87ddf1a
Add new installation script and fix npm package.json
JulienVig Jan 9, 2024
dd0c06a
Add a table of contents for documentation guides to DEV.md
JulienVig Jan 10, 2024
af7533e
Add specific instructions to uplaod model files in TASK.md
JulienVig Jan 10, 2024
2bc5c1f
Remove technical details from the main README
JulienVig Jan 10, 2024
72320e9
Update the first steps in the onboarding guide
JulienVig Jan 10, 2024
78b5213
Update text formatting in DEV.md
JulienVig Jan 10, 2024
720cfa8
Update DEV.md
JulienVig Jan 10, 2024
e2ac049
Add installation instructions for different settings to ONBOARDING.md
JulienVig Jan 10, 2024
1cf3e03
Rename discojs to Disco.js in DEV.md
JulienVig Jan 10, 2024
c2f1f01
Rename discojs to Disco.js in ONBOARDING.md
JulienVig Jan 10, 2024
a03c6b5
Include CLI dependencies in install.sh
JulienVig Jan 10, 2024
0b6f7ee
Include CLI dependencies in DEV.md
JulienVig Jan 10, 2024
e69936c
Add VSCode tips to ONBOARDING.md
JulienVig Jan 10, 2024
0dddbfc
Add VSCode tips to CONTRIBUTING.md
JulienVig Jan 10, 2024
36f875e
Add VSCode unresolved imports to FAQ.md
JulienVig Jan 10, 2024
99a7132
Update ToC in DEV.md
JulienVig Jan 10, 2024
d6128f1
Move Vue.js guide to docs folder
JulienVig Jan 10, 2024
40a502b
Create DISCOJS.md for Disco.js inner workings documentation
JulienVig Jan 10, 2024
ac66974
Add next steps to ONBOARDING.md and remove Disco.js theory
JulienVig Jan 10, 2024
55c1fdd
Update ONBOARDING.md
JulienVig Jan 10, 2024
848bd24
Update ToC in DEV.md
JulienVig Jan 10, 2024
4f60fb9
Update further documentation in DEV.md
JulienVig Jan 10, 2024
00b7ab9
Rephrase text in ONBOARDING.md
JulienVig Jan 10, 2024
843a5d8
WIP add more details to CONTRIBUTING.md
JulienVig Jan 11, 2024
96912b7
Update install.sh description in DEV.md
JulienVig Jan 15, 2024
78347a2
Fix typo in DEV.md
JulienVig Jan 15, 2024
2f80aa9
Update install.sh
JulienVig Jan 15, 2024
b958f2b
Merge branch '606-update-doc-julien' of github.com:epfml/disco into 6…
JulienVig Jan 15, 2024
746a0b8
Add test instructions to CONTRIBUTING.md
JulienVig Jan 15, 2024
0944c95
Add PR instructions to CONTRIBUTING.md
JulienVig Jan 15, 2024
bcc8fc0
WIP update DISCOJS.md
JulienVig Jan 16, 2024
73d0183
WIP update DISCOJS.md
JulienVig Jan 17, 2024
68d1836
WIP update DISCOJS.md
JulienVig Jan 17, 2024
cd44086
Improve install script and fix typo
JulienVig Jan 17, 2024
5e76173
Updated DISCOJS.md
JulienVig Jan 18, 2024
5bd32b3
More vue doc from Architecture.md to VUEJS.md
JulienVig Jan 18, 2024
4ecb635
Move TS doc from Arhitecture to CONTRIBUTING.md
JulienVig Jan 18, 2024
f88e134
Move doc from architecture to VUEJS.md
JulienVig Jan 18, 2024
94ab917
Delete docs/ARCHITECTURE.md
JulienVig Jan 18, 2024
4efb547
Remove ref to Architecture.md from DEV.md
JulienVig Jan 18, 2024
275bee8
Remove refs to Architecture.md in PRIVACY.md
JulienVig Jan 18, 2024
d3c2c3e
Fix typos
JulienVig Jan 18, 2024
6c8dfe3
WIP update PRIVACY.md
JulienVig Jan 22, 2024
7b0119c
Update PRIVACY.md
JulienVig Jan 25, 2024
3c22a5f
Fix typo PRIVACY.md
JulienVig Jan 25, 2024
d3795a4
WIP: update TASK.md
JulienVig Jan 29, 2024
e5d74f7
WIP update TASK.md
JulienVig Jan 29, 2024
7f92ecd
Merge branch 'develop' into 606-update-doc-julien
JulienVig Jan 30, 2024
b5853a4
Remove trailing space
JulienVig Jan 30, 2024
8be629e
Bump @vue/eslint-config-typescript from 7 to 8 to resolve dependency …
JulienVig Jan 30, 2024
61a45ad
Update TASK.md
JulienVig Jan 30, 2024
6c5bf28
Package-lock got out of sync with package.json, rolled it back to ori…
JulienVig Jan 30, 2024
9159787
Merge branch '606-update-doc-julien' of github.com:epfml/disco into 6…
JulienVig Jan 30, 2024
63640e6
Remove bash specifics from install.sh
JulienVig Feb 8, 2024
08ce89a
Address tharvik comments
JulienVig Feb 8, 2024
33a0a2a
Update file name change
JulienVig Feb 8, 2024
debd3ad
Merge branch 'develop' into 606-update-doc-julien
JulienVig Feb 8, 2024
b2ae3ff
Reword DEV.md
JulienVig Feb 12, 2024
d09e8e0
Make install.sh executable
JulienVig Feb 12, 2024
50f9577
DEV.md: Update install.sh instruction
JulienVig Feb 12, 2024
19adfe5
Reword ONBOARDING.md
JulienVig Feb 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 154 additions & 38 deletions DEV.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
<div align="center">
<h1><code>Disco</code></h1>

<p>
<strong>Distributed collaborative learning</strong>
</p>

<h1>DISCO <code>developer guide</code></h1>
<p>
<a href="https://github.com/epfml/disco/actions/workflows/lint-test-build.yml"><img src="https://github.com/epfml/disco/actions/workflows/lint-test-build.yml/badge.svg" alt="build status" /></a>
<a href="https://github.com/epfml/disco/actions/workflows/deploy-server.yml"><img src="https://github.com/epfml/disco/actions/workflows/deploy-server.yml/badge.svg" alt="build status" /></a>
Expand All @@ -13,44 +8,165 @@

</div>

Welcome to the Disco🔮 developer guide.

If you want to get an in depth guide of how to run things (and why and how they work) you can have a look at our [on boarding](./docs/ONBOARDING.md) document

To quickly get up and running you can find some relevant information here as well as in the [server](./server/README.md) document.

If you run into any sort of trouble then hopefully you can find an answer in our [faq](./docs/FAQ.md); otherwise please create a new issue. If you want to contribute to Disco🔮, then please have a look at our [contributing](./docs/CONTRIBUTING.md) guide; and if you are curious about our architecture you can find information [here](./docs/ARCHITECTURE.md).

## Quick-start guide
Welcome to the DISCO developer guide.
Here you will have a first overview of the project, how to install and run an instance of DISCO and links to further documentation.

The following command lines will install the required dependencies, build Disco.js and start the Disco server (on localhost:8080) and the Disco web client (on localhost:8081). We recommend using (and assume you are) [nvm](https://github.com/nvm-sh/nvm) (the Node Version Manager).
## Structure

The DISCO project is composed of multiple parts. At the root level, there are four main folders: `discojs`, `server`, `web-client` and `cli`.

- `discojs`, or Disco.js, is the JavaScript library that contains federated and decentralized learning logic. The library allows to train and use machine learning models in a distributed fashion. The library itself is composed of the `disco-node` and `disco-web` modules, both of them extending the platform-agnostic code in `disco-core`. In other words, `disco-core` contains most of the implementation but can't be used by itself, while `disco-web` and `disco-node` allow using `disco-core` via different technologies. To some extents, you can think of `disco-core` as an abstract class extended by `disco-web`and `disco-node`.
JulienVig marked this conversation as resolved.
Show resolved Hide resolved
- `disco-node` lets you use Disco.js with Node.js. For example, the `server` and the `cli` rely on `disco-node`. A user can also directly import the `disco-node` package in their Node.js programs.
- `disco-web` allows using Disco.js through a browser. The `web-client`, discussed below, relies on `disco-web` to implement a browser UI.

The main difference between the two is how they handle storage: a browser doesn't have access to the file system (for security reasons) while a Node.js application does.
- `server` contains the server implementation necessary to use Disco.js. Indeed, while the federated and decentralized learning logic is implemented by Disco.js, we still need a server to orchestrate users in both paradigms. In decentralized learning, the server exposes an API for users to query the necessary information to train models in a decentralized fashion, such as the list of other peers. Thus, the server never receives training data or model parameters. In federated learning, the server receives model updates but never training data. It keeps track of participants and updates the model weights. A `server` instance is **always** necessary to use DISCO, whether one is using a browser UI, the CLI or directly programming with `disco-node`.
- `web-client` implements a browser User Interface. In other words, it implements a website allowing users to use DISCO without coding. Via the browser, a user can create and participate in federated and decentralized training sessions, evaluate models, etc.
- `cli` contains the Command Line Interface for Disco.js. For example, the CLI allows a user to create and join training sessions from the command line, benchmark performance by emulating multiple clients, etc.

Here is a summary diagram:

```mermaid
flowchart LR
subgraph discojs
discojs-node-->|extends|discojs-core;
discojs-web-->|extends|discojs-core;
end
subgraph User Interface
web-client-->|uses|discojs-web;
custom_browser["custom browser implementation"]-->|uses|discojs-web;
server-->| uses |discojs-node;
cli-->|uses|discojs-node;
custom_node["custom Node.js scripts"]-->|uses|discojs-node;
end
```

## Installation guide

The following instructions will install the required dependencies, build Disco.js and launch a DISCO server and a web client. If you run into any sort of trouble check our [FAQ](./docs/FAQ.md); otherwise please create a new issue or feel free to ask on [our slack](https://join.slack.com/t/disco-decentralized/shared_invite/zt-fpsb7c9h-1M9hnbaSonZ7lAgJRTyNsw).

1. We recommend using [nvm](https://github.com/nvm-sh/nvm) (Node Version Manager) to handle multiple Node.js versions. Start by installing `nvm` by following [their installation instructions](https://github.com/nvm-sh/nvm).
After installation, you should be able to run
```
nvm -v
0.39.7 # my nvm version at the time
```
2. Install Node.js version 16
```
nvm install 16
```
You can now choose which Node.js version to use:
tharvik marked this conversation as resolved.
Show resolved Hide resolved
```
nvm use 16
```
Using Node.js v16 should automatically set your [npm](https://docs.npmjs.com/about-npm) (Node Package Manager, different from n**v**m) version to 8:
```
npm --version
8.xx.xx
```
`nvm` manages your different Node.js versions while `npm` handles your different Node.js project packages within one version.

3. Clone the repository
```
git clone [email protected]:epfml/disco.git
cd disco
nvm use
cd discojs && npm ci && npm run build
cd ..
cd server && npm ci && npm run dev
cd ..
cd web-client && npm ci && npm run dev
```

For full details, see the respective README files, that is [discojs-core](./discojs/discojs-core/README.md), [discojs](./discojs/discojs/README.md), [discojs-node](./discojs/discojs-node/README.md), [server](./server/README.md), [web-client](./web-client/README.md) and [cli](./cli/README.md).

## Structure

The Disco GitHub repository is composed of four main directories:

- [discojs](./discojs/README.md) contains Disco.js, the main library used by the other projects within this repo. It is also available as standalone libraries on NPM, for both the browser and Node.js.
- [discojs-web](./discojs/discojs-web/README.md) contains the browser implementation of the Disco.js library. It is available on NPM as `@epmfl/discojs`.
- [discojs-node](./discojs/discojs-node/README.md) contains the Node.js implementatino of the Disco.js library. It is available on NPM as `@epfml/discojs-node`.
- [discojs-core](./discojs/discojs-core/README.md) contains code common to `discojs-web` and `discojs-node`. Little source code changes between the two implementations, hence the need for a "core" directory.
- [server](./server/README.md) contains the Node.js server for Disco, enabling the decentralized (with peer-to-peer coordination) and federated learning of Disco.js nodes. It uses `discojs-node` under the hood.
- [web-client](./web-client/README.md) contains a web client to interact with Disco.js and the Disco server via a user-friendly UI. This basically emulates a Disco.js node in the network. It uses `discojs-web` as backend.
- [cli](./cli/README.md) contains the CLI tool for Disco.js. Just like `web-client`, it emulates a Disco.js node in the network. Furthermore, it can actually emulate multiple nodes and even manages its own Disco server instance. It uses `discojs-node` under the hood.

## Example
4. Run the installation script
```
sh install.sh
JulienVig marked this conversation as resolved.
Show resolved Hide resolved
```

A full -- self-contained -- examples of the Disco.js API running two federated users can be found [here](./docs/node_example). This runs on Node.js outside of any browser, using the `@epfml/discojs-node` NPM package and the `server` module of this repo. A Disco server is run from the script itself and the data is already available in the repo.
<details>
<summary><b>What does <code>install.sh</code> do?</b></summary>
JulienVig marked this conversation as resolved.
Show resolved Hide resolved
</br>
The installation script installs the dependencies required by the different parts of the project, which are described in the Structure section.
JulienVig marked this conversation as resolved.
Show resolved Hide resolved
It first installs the Disco.js library dependencies, notably, `TensorFlow.js`, and anything else required for federated and decentralized learning logic.
The script then builds the library, a step necessary to compile TypeScript into JavaScript.

```
cd discojs
npm ci # stands for `clean install`, to ensure than only expected dependencies are being installed.
npm run build
```
The script then installs dependencies for the web client, which implements a browser UI.
By default, the project points to the [@epfml/disco-web](https://www.npmjs.com/package/@epfml/discojs) package published on the `npm` remote repository. In a development environment, we want to use the local web client in the `discojs/web-client` folder. To do so, we need to link the local folder as the actual dependency.

```
cd ../web-client
npm ci
npm link ../discojs/discojs-web
```
You can verify than the link is effective by checking that `npm ls` lists `@epfml/[email protected] -> ./../discojs/discojs-web`.

Similarly, we install the server dependencies, and then the `discojs-node` dependency to the local folder rather than the remote npm package [@epfml/disco-node](https://www.npmjs.com/package/@epfml/discojs-node):
```
cd ../server
npm ci
npm link ../discojs/discojs-node
```
Install the CLI dependencies:
```
cd ../cli
npm ci
```
Install `ts-node` globally. `ts-node` lets us compile and run TypeScript code in a single command.
```
cd ..
npm install -g ts-node
```
JulienVig marked this conversation as resolved.
Show resolved Hide resolved
Download and extract the sample training datasets. These datasets are used in the automated tests.
```
sh get_training_data.sh
```

</details>

5. Launch DISCO

As you may have seen, there are many ways to use DISCO. Here we will run a server and a web client. From there, a user can use DISCO from their browser.
* First launch a `server` instance, which is used for federated and decentralized learning tasks, e.g. to list peers participating in a decentralized task.
```
cd server
npm run dev
```
The server should be listening on `http://localhost:8080/`.
* Secondly, start a web client, which will allow you to use DISCO from your browser. You may have to do so **from another terminal** since the previous one is now used by the server.
```
cd web-client # from another terminal
npm run dev
```
The web client should be running on `http://localhost:8081`, if not first restart the server and then the web client.

> [!IMPORTANT]
> Make sure to first start the server to ensure that it is listening to port 8080.

**You can now access DISCO at http://localhost:8081/**


## Further documentation

* Next you may want to read our [onboarding guide](./docs/ONBOARDING.md) which lists the following steps to onboard DISCO.
* If you are only planning to use DISCO in your own scripts, you can find a stand-alone example relying on `discojs-node` [here](./docs/node_example). The example runs with Node.js outside any browser, using the `@epfml/discojs-node` NPM package and the `server` module. A DISCO server is launched by the script itself and the data is already available in the repo.

#### Table of contents
As there are many guides in the project, here is a table of contents referencing them all:
* [DISCO README](./README.md)
* [Developer guide](./DEV.md)
* The `docs` folder contains in-depth documentation on the project:
* [Onboarding guide](./docs/ONBOARDING.md)
* [Contributing guide](./docs/CONTRIBUTING.md)
* [Disco.js under the hood](./docs/DISCOJS.md)
* [FAQ](./docs/FAQ.md)
* [Example: using `discojs-node` in a script](./docs/node_example/README.md)
* [Privacy in DISCO](./docs/PRIVACY.md)
* [How to create a DISCO Task](./docs/TASK.md)
* [Vue.js architecture](./docs/VUEJS.md)
* Respective `README` files contain installation and packaging instructions relevant to the module
* [`discojs` README](./discojs/README.md)
* [`discojs-core` README](./discojs/discojs-core/README.md)
* [`discojs-node` README](./discojs/discojs-node/README.md)
* [`discojs-web` README](./discojs/discojs-web/README.md)
* [`server` README](./server/README.md)
* [`web-client` README](./web-client/README.md)
* [`cli` README](./cli/README.md)
21 changes: 10 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# **DISCO** - _Dis_&#8203;tributed _Co_&#8203;llaborative Machine Learning
# **DISCO** - DIStributed COllaborative Machine Learning



Expand All @@ -9,7 +9,7 @@ The latest version is always running on the following link, [directly in your br
:man_dancing: [https://epfml.github.io/disco/](https://epfml.github.io/disco/) :man_dancing:

___
:magic_wand: DEVELOPERS: Contribute or customize DISCO [HERE](DEV.md)
:magic_wand: DEVELOPERS: Have a look at our [developer guide](DEV.md)
___

:question: **WHY DISCO?**
Expand All @@ -30,7 +30,7 @@ ___
:question: **DISCO TECHNOLOGY**
- DISCO supports arbitrary deep learning tasks and model architectures, via [TF.js](https://www.tensorflow.org/js)
- :sparkles: relies on [peer2peer](https://peerjs.com/) communication
- Learn more about secure aggregation and differential privacy for privacy-respecting training [HERE](docs/PRIVACY.md)
- Have a look at how DISCO ensures privacy and confidentiality [HERE](docs/PRIVACY.md)

___

Expand All @@ -43,23 +43,22 @@ DISCO aims to enable open-access and easy-use distributed training which is
- :ninja: robust to malicious actors and data poisoning ([R6](https://arxiv.org/abs/2012.10333), [R7](https://arxiv.org/abs/2006.09365))
- :apple: :banana: interpretable in imperfectly interoperable data distributions ([R8](https://arxiv.org/abs/2107.06580))
- :mirror: personalizable ([R9](https://arxiv.org/abs/2103.00710))
- :carrot: fairly incentivizes participation
- :carrot: fairly incentivize participation


___


:checkered_flag: **HOW TO USE DISCO**
- Start by exploring our example *DISCOllaboratives* in the `Tasks` tab.
- Start by exploring our example *DISCOllaboratives* in the [`Tasks` page](https://epfml.github.io/disco/#/list).
- The example models are based on popular datasets such as [Titanic](https://www.kaggle.com/c/titanic), [MNIST](https://www.kaggle.com/c/digit-recognizer) or [CIFAR-10](https://www.kaggle.com/pankrzysiu/cifar10-python)
- It is also possible to create a custom task without coding. Just upload the following 2 files:
- A `TensorFlow.js` model file in JSON format (useful links to [create](https://www.tensorflow.org/js/guide/models_and_layers) and [save](https://www.tensorflow.org/js/guide/save_load) your model)
- A weight file in `.bin` format
- These are the initial weights provided to new users joining your task (pre-trained or random initialisation)
- It is also possible to create your own task without coding on the [custom training page](https://epfml.github.io/disco/#/create):
- Upload the initial model
- You can choose from several existing dataloaders
- Then...select your DISCO training scheme (:star2: or :sparkles:) ... connect your data and... :bar_chart:
- Choose between federated and decentralized for your DISCO training scheme ... connect your data and... done! :bar_chart:
- For more details on ML tasks and custom training have a look at [this guide](./docs/TASK.md)

> **Note**: Currently only `CSV` and `Image` data types are supported. Adding new data types, preprocessing code or dataloaders, is accessible in developer mode (see [developer guide](https://github.com/epfml/disco/blob/develop/DEV.md)). Specific instructions on how to build a custom task can be found [HERE](docs/TASK.md)
> **Note**: Currently only `CSV` and `Image` data types are supported. Adding new data types, preprocessing code or dataloaders, is accessible in developer mode (see [developer guide](https://github.com/epfml/disco/blob/develop/DEV.md)).

__

Expand Down
2 changes: 1 addition & 1 deletion discojs/discojs-core/src/aggregator/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export abstract class Base<T> {
*/
protected readonly roundCutoff = 0,
/**
* The number of communication rounds occuring during any given aggregation round.
* The number of communication rounds occurring during any given aggregation round.
*/
public readonly communicationRounds = 1
) {
Expand Down
3 changes: 2 additions & 1 deletion discojs/discojs-core/src/training/trainer/local_trainer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { tf } from '../..'
import { Trainer } from './trainer'

/** Class whose role is to locally (alone) train a model on a given dataset, without any collaborators.
/** Class whose role is to locally (alone) train a model on a given dataset,
* without any collaborators.
*/
export class LocalTrainer extends Trainer {
async onRoundBegin (accuracy: number): Promise<void> {}
Expand Down
10 changes: 5 additions & 5 deletions discojs/discojs-core/src/weights/weights_container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { tf, Weights } from '..'
export type TensorLike = tf.Tensor | ArrayLike<number>

/**
* Convenient wrapper object reprensenting an immutable list of TF.js tensors.
* Convenient wrapper object representing an immutable list of TF.js tensors.
*/
export class WeightsContainer {
private readonly _weights: List<tf.Tensor>
Expand Down Expand Up @@ -113,18 +113,18 @@ export class WeightsContainer {
}

/**
* Instanciates a new weights container from the given tensors or arrays of numbers.
* Instantiates a new weights container from the given tensors or arrays of numbers.
* @param weights The tensors or number arrays
* @returns The instanciated weights container
* @returns The instantiated weights container
*/
static of (...weights: TensorLike[]): WeightsContainer {
return new this(weights)
}

/**
* Instanciates a new weights container from the given model's weights.
* Instantiates a new weights container from the given model's weights.
* @param model The TF.js model
* @returns The instanciated weights container
* @returns The instantiated weights container
*/
static from (model: tf.LayersModel): WeightsContainer {
return new this(model.weights.map((w) => w.read()))
Expand Down
23 changes: 19 additions & 4 deletions discojs/test.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
DIR="$( cd "$( dirname "$0" )" ; pwd -P )"
set -e # Exit script on error
DIR="$( cd "$( dirname "$0" )" ; pwd -P )" # Fetch current directory
JulienVig marked this conversation as resolved.
Show resolved Hide resolved

cd $DIR/discojs-node && npm test &&
cd $DIR/discojs-core && npm test &&
cd $DIR/discojs-web && npm test
echo '\n>>> Make sure a server instance is running and reachable at http://localhost:8080! <<<'

echo '\n>>> Rebuilding discojs\n'
cd $DIR
npm run build

echo '\n>>> Testing disco-core\n'
cd $DIR/discojs-core
npm run test

echo '\n>>> Testing disco-node\n'
cd $DIR/discojs-node
npm run test

echo '\n>>> Testing disco-web\n'
cd $DIR/discojs-web
npm run test
Loading
Loading