Skip to content
This repository has been archived by the owner on Apr 16, 2021. It is now read-only.

Add CI and unit tests #29

Merged
merged 20 commits into from
Jul 8, 2020
12 changes: 12 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"env": {
"es6": true,
"jest": true,
"node": true
},
"extends": "eslint:recommended"
}
12 changes: 6 additions & 6 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
time: '10:00'
open-pull-requests-limit: 10
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
time: "10:00"
open-pull-requests-limit: 10
20 changes: 0 additions & 20 deletions .github/workflows/build.yml

This file was deleted.

25 changes: 25 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Pull Request

# Run on PRs.
on: [pull_request]

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
node-version: [10.x, 12.x, 14.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn
- name: Prettier
run: yarn prettier --check .
- name: Lint
run: yarn eslint .
- run: yarn test
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
package-lock.json
package-lock.json
.DS_Store
70 changes: 68 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,69 @@
# Skynet SDK
# Skynet NodeJS SDK
mrcnski marked this conversation as resolved.
Show resolved Hide resolved

An SDK for integrating Skynet into Node.js applications
An SDK for integrating Skynet into Node.js applications.

## Installing

Using `npm`:

```sh
npm install @nebulous/skynet
mrcnski marked this conversation as resolved.
Show resolved Hide resolved
```

Using `yarn`:

```sh
yarn add @nebulous/skynet
```

## Examples

### Uploading and downloading

```js
const skynet = require("@nebulous/skynet");

const fs = require("fs");

(async () => {
// upload file

const src_file = "./testdata/file1.txt";

const text = fs.readFileSync(src_file);
mrcnski marked this conversation as resolved.
Show resolved Hide resolved
console.log(`Uploading file ${src_file} containing data: '${text}'`);

const skylink = await skynet.uploadFile(src_file, skynet.DefaultUploadOptions);
mrcnski marked this conversation as resolved.
Show resolved Hide resolved
if (!skylink.includes(skynet.UriSkynetPrefix)) {
console.log(`ERROR: invalid skylink returned: ${skylink}`);
return;
}
mrcnski marked this conversation as resolved.
Show resolved Hide resolved
console.log(`Upload successful, skylink: ${skylink}`);

// download file
mrcnski marked this conversation as resolved.
Show resolved Hide resolved

const dst_file = "./dst.txt";

console.log(`Downloading to ${dst_file}`);
const new_skylink = skylink.replace(skynet.UriSkynetPrefix, "");
await skynet.downloadFile(dst_file, new_skylink, skynet.DefaultDownloadOptions);
mrcnski marked this conversation as resolved.
Show resolved Hide resolved
const data = fs.readFileSync(dst_file);
if (!data.equals(text)) {
console.log(`ERROR: wrong data returned: ${data}`);
return;
}
console.log("Download successful");

// upload dir

const src_dir = "./src";

console.log(`Uploading dir ${src_dir}`);
const dir_skylink = await skynet.uploadDirectory(src_dir);
if (!dir_skylink.includes(skynet.UriSkynetPrefix)) {
console.log(`ERROR: invalid skylink returned: ${dir_skylink}`);
return;
}
console.log(`Dir upload successful, skylink: ${dir_skylink}`);
})();
```
16 changes: 0 additions & 16 deletions config.js

This file was deleted.

17 changes: 9 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"use strict";

const { DefaultUploadOptions, DefaultDownloadOptions } = require("./config");
const { UploadFile, UploadDirectory } = require("./src/upload");
const { DownloadFile } = require("./src/download");
const { DefaultUploadOptions, uploadFile, uploadDirectory } = require("./src/upload");
const { DefaultDownloadOptions, downloadFile } = require("./src/download");
const { UriSkynetPrefix } = require("./src/utils");

module.exports = {
DefaultUploadOptions: DefaultUploadOptions,
DefaultDownloadOptions: DefaultDownloadOptions,
UploadFile: UploadFile,
UploadDirectory: UploadDirectory,
DownloadFile: DownloadFile,
DefaultDownloadOptions,
DefaultUploadOptions,
downloadFile,
uploadDirectory,
uploadFile,
UriSkynetPrefix,
};
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
"name": "@nebulous/skynet",
"version": "1.0.1",
"description": "Skynet SDK",
"repository": "https://github.com/NebulousLabs/nodejs-skynet",
"main": "index.js",
"scripts": {
"test": "jest",
"format": "prettier --write ."
},
"husky": {
Expand Down Expand Up @@ -31,13 +33,15 @@
],
"license": "MIT",
"dependencies": {
"axios": "^0.19.2",
"form-data": "^3.0.0",
"fs": "0.0.1-security"
"axios": "0.19.2",
"form-data": "3.0.0",
"tmp": "0.2.1"
mrcnski marked this conversation as resolved.
Show resolved Hide resolved
},
"devDependencies": {
"eslint": "^7.2.0",
"jest": "^26.0.1",
"husky": "^4.2.3",
"lint-staged": "^10.0.8",
"prettier": "^2.0.2"
"prettier": "^2.0.5"
}
}
10 changes: 8 additions & 2 deletions src/download.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ const fs = require("fs");

const { trimTrailingSlash, trimSiaPrefix } = require("./utils");

function DownloadFile(path, skylink, opts) {
const DefaultDownloadOptions = {
portalUrl: "https://siasky.net",
};

function downloadFile(path, skylink, customOptions = {}) {
const opts = { ...DefaultDownloadOptions, ...customOptions };

const url = `${trimTrailingSlash(opts.portalUrl)}/${trimSiaPrefix(skylink)}`;

const writer = fs.createWriteStream(path);
Expand All @@ -24,4 +30,4 @@ function DownloadFile(path, skylink, opts) {
});
}

module.exports = { DownloadFile };
module.exports = { DefaultDownloadOptions, downloadFile };
33 changes: 33 additions & 0 deletions src/download.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const axios = require("axios");
const tmp = require("tmp");

const { downloadFile } = require("./download");

jest.mock("axios");

const portalUrl = "https://siasky.net";
const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg";
const tmpFile = tmp.fileSync();

describe("download", () => {
const dst_file = tmpFile.name;
const body = "asdf";

axios.get.mockResolvedValue({ data: { body, pipe: function () {} } });
mrcnski marked this conversation as resolved.
Show resolved Hide resolved

it("should send get request to default portal", () => {
downloadFile(dst_file, skylink);

expect(axios.get).toHaveBeenCalledWith(`${portalUrl}/${skylink}`, { responseType: "stream" });
});

it("should use custom options if defined", () => {
downloadFile(dst_file, skylink, {
portalUrl: "localhost",
});

expect(axios.get).toHaveBeenCalledWith(`localhost/${skylink}`, { responseType: "stream" });
});
});

tmpFile.removeCallback();
mrcnski marked this conversation as resolved.
Show resolved Hide resolved
32 changes: 23 additions & 9 deletions src/upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,42 @@ const axios = require("axios");
const FormData = require("form-data");
const fs = require("fs");

const { walkDirectory, trimTrailingSlash } = require("./utils");
const { walkDirectory, UriSkynetPrefix, trimTrailingSlash } = require("./utils");

function UploadFile(path, opts) {
const options = opts.customFilename ? { filename: opts.customFilename } : {};
const DefaultUploadOptions = {
portalUrl: "https://siasky.net",
portalUploadPath: "/skynet/skyfile",
portalFileFieldname: "file",
portalDirectoryFileFieldname: "files[]",
customFilename: "",
};

function uploadFile(path, customOptions = {}) {
const opts = { ...DefaultUploadOptions, ...customOptions };

const formData = new FormData();
const options = opts.customFilename ? { filename: opts.customFilename } : {};
formData.append(opts.portalFileFieldname, fs.createReadStream(path), options);

// Form the URL.
const url = `${trimTrailingSlash(opts.portalUrl)}${trimTrailingSlash(opts.portalUploadPath)}`;

return new Promise((resolve, reject) => {
axios
.post(url, formData, { headers: formData.getHeaders() })
.then((resp) => {
resolve(`sia://${resp.data.skylink}`);
.then((response) => {
resolve(`${UriSkynetPrefix}${response.data.skylink}`);
})
.catch((error) => {
reject(error);
});
});
}

function UploadDirectory(path, opts) {
function uploadDirectory(path, customOptions = {}) {
const opts = { ...DefaultUploadOptions, ...customOptions };

// Check if there is a directory at given path.
const stat = fs.statSync(path);
if (!stat.isDirectory()) {
throw new Error(`Given path is not a directory: ${path}`);
Expand All @@ -37,20 +50,21 @@ function UploadDirectory(path, opts) {
formData.append(opts.portalDirectoryFileFieldname, fs.createReadStream(file), { filepath: file });
}

// Form the URL.
const url = `${trimTrailingSlash(opts.portalUrl)}${trimTrailingSlash(opts.portalUploadPath)}?filename=${
opts.customFilename || path
}`;

return new Promise((resolve, reject) => {
axios
.post(url, formData, { headers: formData.getHeaders() })
.then((resp) => {
resolve(`sia://${resp.data.skylink}`);
.then((response) => {
resolve(`${UriSkynetPrefix}${response.data.skylink}`);
})
.catch((error) => {
reject(error);
});
});
}

module.exports = { UploadFile, UploadDirectory };
module.exports = { DefaultUploadOptions, uploadFile, uploadDirectory };
Loading