Skip to content
This repository has been archived by the owner on Jan 24, 2023. It is now read-only.

Deploy production builds when publishing #17

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# skynet-js - Javascript Sia Skynet Client
# skynet-js - Javascript Sia Skynet SDK

[![Version](https://img.shields.io/github/package-json/v/NebulousLabs/skynet-js)](https://www.npmjs.com/package/skynet-js)
[![Build Status](https://img.shields.io/github/workflow/status/NebulousLabs/skynet-js/Node.js%20CI)](https://github.com/NebulousLabs/skynet-js/actions)
[![Contributors](https://img.shields.io/github/contributors/NebulousLabs/skynet-js)](https://github.com/NebulousLabs/skynet-js/graphs/contributors)
[![License](https://img.shields.io/github/license/NebulousLabs/skynet-js)](https://github.com/NebulousLabs/skynet-js)

A Javascript module made to simplify communication with Sia Skynet portals from the browser.
A Javascript module made to simplify communication with Sia Skynet portals from the browser. Now with support for Node.js!

## Updating to v3 from v2

Expand Down
2 changes: 1 addition & 1 deletion babel.config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"presets": ["@babel/preset-typescript", "@babel/preset-env"],
"plugins": ["@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties"],
"plugins": ["@babel/plugin-proposal-class-properties"],
"env": {
"test": {
"presets": [
Expand Down
41 changes: 28 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
"name": "skynet-js",
"version": "3.0.2",
"description": "Sia Skynet Javascript Client",
"main": "dist/index.js",
"browser": "dist/web/index.web.js",
"main": "dist/node/index.node.js",
"module": "dist/web/index.web.js",
"files": [
"dist/*"
],
Expand All @@ -12,10 +14,14 @@
"not OperaMini all"
],
"scripts": {
"build": "rimraf dist && babel src --out-dir dist --extensions .ts --ignore src/**/*.test.ts && tsc --project tsconfig.build.json",
"lint:eslint": "eslint --ext .ts utils src --max-warnings 0",
"build": "rimraf dist && yarn build-bundle && yarn build-node && yarn build-web",
"build-bundle": "yarn run webpack",
"build-node": "babel src --out-dir dist/node --extensions .ts --ignore 'src/**/*.test.ts' && tsc --project tsconfig.build.json --outDir dist/node && rimraf dist/node/**/web* && rimraf dist/node/**/*web*",
"build-web": "babel src --out-dir dist/web --extensions .ts --ignore 'src/**/*.test.ts' && tsc --project tsconfig.build.json --outDir dist/web && rimraf dist/web/**/node* && rimraf dist/web/**/*node*",
"deploy": "node scripts/deploy.js",
"lint:eslint": "eslint --ext .ts src utils --max-warnings 0",
"lint:tsc": "tsc",
"prepublishOnly": "yarn build",
"prepublishOnly": "yarn build && yarn deploy",
"test": "jest --coverage --coverageDirectory ../coverage"
},
"jest": {
Expand All @@ -26,10 +32,10 @@
],
"coverageThreshold": {
"global": {
"branches": 100,
"functions": 100,
"lines": 100,
"statements": 100
"branches": 97,
"functions": 97,
"lines": 97,
"statements": 97
}
},
"rootDir": "src"
Expand All @@ -42,7 +48,7 @@
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --ext .ts --fix",
"tsc --esModuleInterop --noemit",
"tsc",
"prettier --write"
],
"*.{json,yml,md}": [
Expand Down Expand Up @@ -70,19 +76,19 @@
"base64-js": "^1.3.1",
"blakejs": "^1.1.0",
"buffer": "^6.0.1",
"form-data": "^4.0.0",
"mime": "^2.5.2",
"path-browserify": "^1.0.1",
"randombytes": "^2.1.0",
"sjcl": "^1.0.8",
"tweetnacl": "^1.0.3",
"url-join": "^4.0.1",
"url-parse": "^1.4.7"
"url-parse": "1.4.7"
},
"devDependencies": {
"@babel/cli": "^7.11.6",
"@babel/core": "^7.11.6",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@babel/preset-typescript": "^7.10.4",
"@types/base64-js": "^1.3.0",
Expand All @@ -91,20 +97,29 @@
"@types/node": "^14.11.2",
"@types/randombytes": "^2.0.0",
"@types/sjcl": "^1.0.29",
"@types/tmp": "^0.2.0",
"@types/url-join": "^4.0.0",
"@types/url-parse": "^1.4.3",
"@typescript-eslint/eslint-plugin": "^4.3.0",
"@typescript-eslint/parser": "^4.3.0",
"axios-mock-adapter": "^1.18.2",
"babel-loader": "^8.2.2",
"babel-plugin-transform-class-properties": "^6.24.1",
"eslint": "^7.11.0",
"eslint-plugin-compat": "^3.8.0",
"eslint-plugin-jsdoc": "^32.0.0",
"husky": "^5.0.9",
"fs-extra": "^9.1.0",
"husky": "^5.1.3",
"jest": "^26.4.2",
"lint-staged": "^10.3.0",
"prettier": "^2.1.1",
"rimraf": "^3.0.2",
"typescript": "^4.0.3"
"tar-fs": "^2.1.1",
"tmp": "^0.2.1",
"ts-loader": "^8.0.17",
"typescript": "^4.0.3",
"webpack": "^5.23.0",
"webpack-cli": "^4.5.0",
"webpack-merge": "^5.7.3"
}
}
119 changes: 119 additions & 0 deletions scripts/deploy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const { genKeyPairFromSeed, SkynetClient, uriSkynetPrefix } = require("..");

const fs = require("fs");
const fse = require('fs-extra');
const tar = require("tar-fs");

// The secret seed phrase to deploy with.
const deploySeed = "SKYNET_JS_DEPLOY_SEED";
// The location of the bundle to deploy. Must be a folder.
const bundlePath = "dist/bundle";
// Location of package.json, used to get the latest version.
const packageJson = "../package.json";
// Set to true to skip the download.
const skipDownload = false;
// Set to true to skip the upload.
const skipUpload = false;

const hns = "skynet-js";
const dataKey = "skynet-js";
const versionsDir = "versions";
const versionsTarFile = `${versionsDir}.tar`;

(async () => {
const client = new SkynetClient("https://siasky.net");

// Get the latest version from package.json.

const pjson = require(packageJson);
const version = pjson.version;
console.log(`Version: ${version}`);

// Download the existing version directory.

if (fs.existsSync(versionsDir)) {
fs.rmdirSync(versionsDir, { recursive: true });
}
if (fs.existsSync(versionsTarFile)) {
fs.rmSync(versionsTarFile);
}

if (!skipDownload) {
try {
console.log(`Downloading HNS domain '${hns}' to ${versionsTarFile}`);
const { contentType } = await client.downloadFileHnsToPath(hns, versionsTarFile, { query: { format: "tar" } });
if (!contentType || !contentType.includes("application/x-tar")) {
throw new Error(`Downloaded file is not tar, content-type: ${contentType}`);
}
// Untar to versions dir.
console.log(`Untarring ${versionsTarFile}`);
const writer = tar.extract(versionsDir, {
// Make sure all existing subfiles are readable.
readable: true,
});
await new Promise((resolve, reject) => {
fs.createReadStream(versionsTarFile).pipe(writer);
writer.on("finish", resolve);
writer.on("error", reject);
});

// Delete tar file.
fs.unlinkSync(versionsTarFile);
} catch (error) {
// If there was any error, stop. The initial directory should be uploaded manually.
console.log(error);
return;
}
}

// Copy the web bundle to the appropriate version dir.

// Compute the destination dir.
// TODO: Index by major version?
let versionSubdir = version.split(".").slice(0, 2).join(".");
const suffix = version.split("-").slice(1);
if (suffix.length > 0) {
versionSubdir = `${versionSubdir}-${suffix}`;
}
const destinationDir = `${versionsDir}/${versionSubdir}`;

// Copy the bundle. destination will be created or overwritten.
console.log(`Copying ${bundlePath} -> ${destinationDir}`);
if (fs.existsSync(destinationDir)) {
fs.rmdirSync(destinationDir, { recursive: true });
}
fs.mkdirSync(destinationDir, { recursive: true });
fse.copySync(bundlePath, destinationDir);

// Upload the directory and get the skylink.

if (!skipUpload) {
console.log(`Uploading '${versionsDir}' dir`);
let { skylink } = await client.uploadDirectoryFromPath(versionsDir, { disableDefaultPath: true });
skylink = skylink.slice(uriSkynetPrefix.length);
console.log(`Skylink: ${skylink}`);

// Delete versionsDir.
fs.rmdirSync(versionsDir, { recursive: true });

// Update the registry entry.

console.log(`Updating '${dataKey}' registry entry with skylink ${skylink}`);
const seed = process.env[deploySeed];
if (!seed) {
throw new Error(`Seed not found, make sure SKYNET_JS_DEPLOY_SEED is set`);
}
const { publicKey, privateKey } = genKeyPairFromSeed(seed);
const { entry } = await client.registry.getEntry(publicKey, dataKey);
await client.registry.setEntry(privateKey, { datakey: dataKey, data: skylink, revision: entry.revision + BigInt(1) });

// Print the registry URL.

const registryUrl = client.registry.getEntryUrl(publicKey, dataKey);
console.log(`Registry URL: ${registryUrl}`);
const skynsUrl = client.registry.getSkynsUrl(publicKey, dataKey);
console.log(`Skyns URL: ${skynsUrl}`);
}
})().catch((e) => {
console.log(e);
});
51 changes: 29 additions & 22 deletions src/client.ts → src/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import axios, { AxiosResponse } from "axios";
import axios, { AxiosResponse, ResponseType } from "axios";
import type { Method } from "axios";
import { uploadFile, uploadDirectory, uploadDirectoryRequest, uploadFileRequest } from "./upload";

import {
downloadFile,
downloadFileHns,
getSkylinkUrl,
getHnsUrl,
getHnsresUrl,
getMetadata,
getFileContent,
getFileContentHns,
getFileContentRequest,
openFile,
openFileHns,
resolveHns,
} from "./download";
import { getJSON, setJSON } from "./skydb";
import { getEntry, getEntryUrl, setEntry } from "./registry";
} from "../download";
import { getJSON, setJSON } from "../skydb";
import { getEntry, getEntryUrl, getSkynsUrl, setEntry } from "../registry";

import { addUrlQuery, defaultPortalUrl, makeUrl } from "./utils/url";
import { addUrlQuery, defaultPortalUrl, makeUrl } from "../utils/url";
import { CustomUploadOptions, UploadRequestResponse } from "../upload";

/**
* Custom client options.
Expand Down Expand Up @@ -54,40 +51,45 @@ export type RequestConfig = CustomClientOptions & {
skykeyName?: string;
skykeyId?: string;
headers?: Record<string, unknown>;
responseType?: ResponseType;
transformRequest?: (data: unknown) => string;
transformResponse?: (data: string) => Record<string, unknown>;
};

/**
* The Skynet Client which can be used to access Skynet.
* The base Skynet Client which can be used to access Skynet.
*/
export class SkynetClient {
export abstract class SkynetClient {
// TODO: This is currently the url of the skapp and not the portal. It should be the value of 'skynet-portal-api' header. This will be a promise, which will be a breaking change.
portalUrl: string;
customOptions: CustomClientOptions;

// Set methods (defined in other files).

// Upload
uploadFile = uploadFile;
protected uploadFileRequest = uploadFileRequest;
uploadDirectory = uploadDirectory;
protected uploadDirectoryRequest = uploadDirectoryRequest;

// Download
downloadFile = downloadFile;
downloadFileHns = downloadFileHns;
getSkylinkUrl = getSkylinkUrl;
getHnsUrl = getHnsUrl;
getHnsresUrl = getHnsresUrl;
getMetadata = getMetadata;
getFileContent = getFileContent;
getFileContentHns = getFileContentHns;
protected getFileContentRequest = getFileContentRequest;
openFile = openFile;
openFileHns = openFileHns;
resolveHns = resolveHns;

// Upload
abstract uploadFileContent(
this: SkynetClient,
fileContents: string,
fileName: string,
customOptions?: CustomUploadOptions
): Promise<UploadRequestResponse>;
protected abstract uploadFileContentRequest(
this: SkynetClient,
fileContents: string,
fileName: string,
customOptions?: CustomUploadOptions
): Promise<AxiosResponse>;

// SkyDB
db = {
getJSON: getJSON.bind(this),
Expand All @@ -98,6 +100,7 @@ export class SkynetClient {
registry = {
getEntry: getEntry.bind(this),
getEntryUrl: getEntryUrl.bind(this),
getSkynsUrl: getSkynsUrl.bind(this),
setEntry: setEntry.bind(this),
};

Expand All @@ -109,6 +112,9 @@ export class SkynetClient {
* @param [customOptions] Configuration for the client.
*/
constructor(portalUrl: string = defaultPortalUrl(), customOptions: CustomClientOptions = {}) {
if (!portalUrl) {
portalUrl = defaultPortalUrl();
}
this.portalUrl = portalUrl;
this.customOptions = customOptions;
}
Expand Down Expand Up @@ -159,6 +165,7 @@ export class SkynetClient {
headers,
auth,
onUploadProgress,
responseType: config.responseType,
transformRequest: config.transformRequest,
transformResponse: config.transformResponse,

Expand Down
25 changes: 25 additions & 0 deletions src/client/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { downloadFileHnsToPath, downloadFileToPath, downloadFileToPathRequest } from "../download/node";
import {
uploadDirectoryFromPath,
uploadDirectoryFromPathRequest,
uploadFileFromPath,
uploadFileFromPathRequest,
uploadFileContent,
uploadFileContentRequest,
} from "../upload/node";
import { SkynetClient as Client } from "./index";

export class SkynetClient extends Client {
// Download
downloadFileToPath = downloadFileToPath;
downloadFileHnsToPath = downloadFileHnsToPath;
protected downloadFileToPathRequest = downloadFileToPathRequest;

// Upload
uploadDirectoryFromPath = uploadDirectoryFromPath;
protected uploadDirectoryFromPathRequest = uploadDirectoryFromPathRequest;
uploadFileFromPath = uploadFileFromPath;
protected uploadFileFromPathRequest = uploadFileFromPathRequest;
uploadFileContent = uploadFileContent;
protected uploadFileContentRequest = uploadFileContentRequest;
}
Loading