Cross platform support by migrating to npm (#10)

This commit is contained in:
Chalith Desaman
2022-09-19 11:09:50 +05:30
committed by GitHub
parent 3362c60fe7
commit 50864085d9
11 changed files with 520 additions and 0 deletions

1
npm/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

43
npm/README.md Normal file
View File

@@ -0,0 +1,43 @@
# HotPocket developer kit
Evernode uses HotPocket as its smart contract engine. HotPocket smart contracts can be developed using any POSIX-compliant language/framework. To make it easy to develop and test HotPocket smart contracts on your local PC, you can use HotPocket developer kit.
## Installation
### Prerequisites
HotPocket developer kit requires you to install [Docker Engine](https://docs.docker.com/engine/install/) and [NodeJs](https://nodejs.org/en/) on your development machine.
### Supports cross platform
This is a npm global package which supports both Linux and Windows
1. Install [prerequisites](#prerequisites).
2. Run the following command to install hpdevkit on your machine.
```
npm i -g hpdevkit
```
## Updates
Update `hpdevkit` to the latest and update the supporting docker images.
Run one of following commands to update hpdevkit.
- Method 1 - Using hpdevkit CLI
```
hpdevkit update
```
- Method 2 - Using npm
```
npm update -g hpdevkit
```
**NOTE: You need to re-deploy your contracts to make the new changes effective.**
## Uninstall
Uninstall `hpdevkit` and the supporting docker images and containers.
- Using hpdevkit CLI
```
hpdevkit uninstall
```
**NOTE: Uninstalling from hpdevkit CLI is recommended. If you uninstall using npm you'll have to clean hpdevkit supporting docker images and containers manually.**
_**NOTE:** In Linux platforms, for Installation, Update and Uninstallation you'll need root privileges. Add `sudo` to above commands._

15
npm/appenv.js Normal file
View File

@@ -0,0 +1,15 @@
const process = require('process');
const appenv = {
cluster: 'default',
clusterSize: process.env.HP_CLUSTER_SIZE || 3,
defaultNode: process.env.HP_DEFAULT_NODE || 1,
devkitImage: process.env.HP_DEVKIT_IMAGE || 'evernodedev/hpdevkit',
instanceImage: process.env.HP_INSTANCE_IMAGE || 'evernodedev/hotpocket:latest-ubt.20.04-njs.16',
hpUserPortBegin: process.env.HP_USER_PORT_BEGIN || 8081,
hpPeerPortBegin: process.env.HP_PEER_PORT_BEGIN || 22861,
}
Object.freeze(appenv);
module.exports = appenv

59
npm/index.js Executable file
View File

@@ -0,0 +1,59 @@
#! /usr/bin/env node
const { program } = require('commander');
const { version, codeGen, deploy, clean, logs, start, stop, update, uninstall } = require('./lib/command-handler');
program
.command('version')
.description('hpdevkit version')
.action(version);
program
.command('gen <platform> <app-type> <project-name>')
.description('hpdevkit gen <platform> <app-type> <project-name>')
.action(codeGen);
program
.command('deploy <contract-path>')
.description('hpdevkit deploy <contract-path>')
.action(deploy);
program
.command('clean')
.description('hpdevkit clean')
.action(clean);
program
.command('logs <node-number>')
.description('hpdevkit logs <node-number>')
.action(logs);
program
.command('start <node-number>')
.description('hpdevkit start <node-number>')
.action(start);
program
.command('stop <node-number>')
.description('hpdevkit stop <node-number>')
.action(stop);
program
.command('update')
.description('hpdevkit update')
.action(update);
program
.command('uninstall')
.description('uninstall')
.action(uninstall);
try {
program.parse();
}
catch (e) {
// Console outputs will be handled inside command functions.
// Log the exception if not a console output.
if (!('stdout' in e) && !('stderr' in e))
console.error(e);
}

9
npm/lib/child-proc.js Normal file
View File

@@ -0,0 +1,9 @@
const { execSync } = require('child_process');
function exec(commad, streamOut = false) {
return execSync(commad, streamOut ? { stdio: 'inherit' } : {stdio : 'pipe' });
}
module.exports = {
exec
};

158
npm/lib/command-handler.js Normal file
View File

@@ -0,0 +1,158 @@
const fs = require('fs');
const appenv = require('../appenv');
const { exec } = require('./child-proc');
const {
CONSTANTS,
initializeDeploymentCluster,
runOnContainer,
executeOnContainer,
teardownDeploymentCluster,
isExists,
updateDockerImages
} = require('./common');
const { success, error, info, warn } = require('./logger');
function version() {
info(`command: version`);
try {
const res = exec(`npm -g list ${CONSTANTS.npmPackageName} --depth=0`);
const splitted = res.toString().split('\n');
if (splitted.length > 1) {
success(`\n${splitted[1].split('@')[1]}\n`);
return;
}
}
catch (e) { }
error(`\n${CONSTANTS.npmPackageName} is not installed.`);
}
function codeGen(platform, apptype, projName) {
info("Code generator\n");
if (fs.existsSync(projName)) {
error(`Directory '${projName}' already exists.`);
return;
}
let containerStarted = false;
try {
runOnContainer(CONSTANTS.codegenContainerName, null, null, null, null, `${platform} ${apptype} ${projName}`, 'codegen');
containerStarted = true;
exec(`docker cp ${CONSTANTS.codegenContainerName}:${CONSTANTS.codegenOutputDir} ./${projName}`);
success(`Project '${projName}' created.`);
}
catch (e) {
error(`Project '${projName}' generation failed.`);
}
finally {
if (containerStarted)
exec(`docker rm ${CONSTANTS.codegenContainerName}`, false);
}
}
function deploy(contractPath) {
info(`command: deploy (cluster: ${appenv.cluster})`);
initializeDeploymentCluster();
// If copying a directory, delete target bundle directory. If not create empty target bundle directory to copy a file.
const prepareBundleDir = contractPath ?
`rm -rf ${CONSTANTS.bundleMount}` :
`mkdir -p ${CONSTANTS.bundleMount} && rm -rf ${CONSTANTS.bundleMount}/* ${CONSTANTS.bundleMount}/.??*`;
executeOnContainer(CONSTANTS.deploymentContainerName, prepareBundleDir);
exec(`docker cp ${contractPath} "${CONSTANTS.deploymentContainerName}:${CONSTANTS.bundleMount}"`);
// Sync contract bundle to all instance directories in the cluster.
executeOnContainer(CONSTANTS.deploymentContainerName, 'cluster stop ; cluster sync ; cluster start');
if (appenv.defaultNode > 0) {
info(`Streaming logs of node ${appenv.defaultNode}:`);
executeOnContainer(CONSTANTS.deploymentContainerName, `cluster logs ${appenv.defaultNode}`);
}
}
function clean() {
info(`command: clean (cluster: ${appenv.cluster})`);
teardownDeploymentCluster();
}
function logs(nodeNumber) {
info(`command: logs (cluster: ${appenv.cluster})`);
runOnContainer(null, null, true, true, null, `logs ${nodeNumber}`, 'cluster');
}
function start(nodeNumber) {
info(`command: start (cluster: ${appenv.cluster})`);
runOnContainer(null, null, true, true, null, `start ${nodeNumber}`, 'cluster');
}
function stop(nodeNumber) {
info(`command: stop (cluster: ${appenv.cluster})`);
runOnContainer(null, null, true, true, null, `stop ${nodeNumber}`, 'cluster');
}
function update() {
info(`command: update`);
// Update npm package if outdated (Docker images will be updated from there). Otherwise only update the docker images.
try {
exec(`npm -g outdated ${CONSTANTS.npmPackageName}`);
info('\nUpdating docker images...');
updateDockerImages();
}
catch (e) {
const splitted = e.stdout.toString().trim().split('\n').map(l => l.trim().split(/\s+/));
if (splitted.length > 1) {
info(`\nUpdating ${CONSTANTS.npmPackageName} npm package...`);
exec(`npm -g install ${CONSTANTS.npmPackageName}@${splitted[1][3]}`, true);
}
}
success('\nUpdate Completed !!');
warn('NOTE: You need to re-deploy your contracts to make the new changes effective.');
}
function uninstall() {
info(`command: uninstall`);
info(`\nUninstalling ${CONSTANTS.npmPackageName} npm package...`);
exec(`npm -g uninstall ${CONSTANTS.npmPackageName}`, true);
// Remove deployment cluster if exist.
if (isExists(CONSTANTS.deploymentContainerName)) {
info('\nCleaning the deployed contracts...');
teardownDeploymentCluster();
}
// Remove docker images if exist.
if (isExists(appenv.devkitImage, 'image')) {
info('\nRemoving devkit docker image...');
exec(`docker image rm ${appenv.devkitImage}`, true);
}
if (isExists(appenv.instanceImage, 'image')) {
info('\nRemoving instance docker image...');
exec(`docker image rm ${appenv.instanceImage}`, true);
}
success('\nUninstalled hpdevkit !!');
}
module.exports = {
version,
codeGen,
deploy,
clean,
logs,
start,
stop,
update,
uninstall
};

122
npm/lib/common.js Normal file
View File

@@ -0,0 +1,122 @@
const appenv = require("../appenv");
const { exec } = require("./child-proc");
const { log, info } = require("./logger");
const GLOBAL_PREFIX = "hpdevkit";
const VERSION = "0.1.0";
const CONSTANTS = {
npmPackageName: `hpdevkit`,
volumeMount: `/${GLOBAL_PREFIX}_vol`,
volume: `${GLOBAL_PREFIX}_${appenv.cluster}_vol`,
network: `${GLOBAL_PREFIX}_${appenv.cluster}_net`,
containerPrefix: `${GLOBAL_PREFIX}_${appenv.cluster}_node`,
bundleMount: `${GLOBAL_PREFIX}_vol/contract_bundle`,
deploymentContainerName: `${GLOBAL_PREFIX}_${appenv.cluster}_deploymgr`,
confOverrideFile: "hp.cfg.override",
codegenOutputDir: "/codegen-output",
codegenContainerName: `${GLOBAL_PREFIX}_codegen`
};
function runOnContainer(name, detached, autoRemove, mountStock, mountVolume, entryCmd, entryPoint, interactive = true) {
command = `docker run`;
if (interactive)
command += " -it";
if (name)
command += ` --name ${name}`;
if (detached)
command += " -d";
if (autoRemove)
command += " --rm";
if (mountStock)
command += " --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock";
if (mountVolume)
command += ` --mount type=volume,src=${CONSTANTS.volume},dst=${CONSTANTS.volumeMount}`;
if (entryPoint)
command += ` --entrypoint ${entryPoint}`;
else
command += " --entrypoint /bin/bash";
command += ` -e CLUSTER=${appenv.cluster} -e CLUSTER_SIZE=${appenv.clusterSize} -e DEFAULT_NODE=${appenv.defaultNode} -e VOLUME=${CONSTANTS.volume} -e NETWORK=${CONSTANTS.network}`;
command += ` -e CONTAINER_PREFIX=${CONSTANTS.containerPrefix} -e VOLUME_MOUNT=${CONSTANTS.volumeMount} -e BUNDLE_MOUNT=${CONSTANTS.bundleMount} -e HOTPOCKET_IMAGE=${appenv.instanceImage}`;
command += ` -e CONFIG_OVERRIDES_FILE=${CONSTANTS.confOverrideFile} -e CODEGEN_OUTPUT=${CONSTANTS.codegenOutputDir}`;
command += ` -e HP_USER_PORT_BEGIN=${appenv.hpUserPortBegin} -e HP_PEER_PORT_BEGIN=${appenv.hpPeerPortBegin}`;
command += ` ${appenv.devkitImage}`;
if (entryCmd) {
if (entryPoint)
command += ` ${entryCmd}`;
else
command += ` -c "${entryCmd}"`;
}
exec(command, true);
}
function executeOnContainer(name, cmd) {
if (name)
exec(`docker exec ${name} /bin/bash -c "${cmd}"`, true);
}
function isExists(name, type = null) {
try {
const res = exec(`docker ${type === 'image' ? 'image ' : ''}inspect ${name}`);
if (!res)
return false;
const resJson = JSON.parse(res.toString().trim());
return !!(resJson && resJson.length);
}
catch (e) {
return false;
}
}
function initializeDeploymentCluster() {
if (!isExists(CONSTANTS.deploymentContainerName)) {
log("\nInitializing deployment cluster");
// Stop cluster if running. Create cluster if not exists.
runOnContainer(CONSTANTS.deploymentContainerName, null, true, true, null, 'cluster stop ; cluster create', null);
// Spin up management container.
runOnContainer(CONSTANTS.deploymentContainerName, true, false, true, true, null, null);
// Bind the instance mesh network config together.
executeOnContainer(CONSTANTS.deploymentContainerName, 'cluster bindmesh');
}
}
function teardownDeploymentCluster() {
exec(`docker stop ${CONSTANTS.deploymentContainerName}`);
exec(`docker rm ${CONSTANTS.deploymentContainerName}`);
runOnContainer(null, null, true, true, null, "cluster stop ; cluster destroy", null, false);
}
function updateDockerImages() {
exec(`docker pull ${appenv.devkitImage}`);
exec(`docker pull ${appenv.instanceImage}`, true);
// Clear if there's already deployed cluster since they are outdated now.
if (isExists(CONSTANTS.deploymentContainerName)) {
info('\nCleaning the deployed contracts...');
teardownDeploymentCluster();
}
}
module.exports = {
runOnContainer,
executeOnContainer,
isExists,
initializeDeploymentCluster,
teardownDeploymentCluster,
updateDockerImages,
CONSTANTS
};

53
npm/lib/logger.js Normal file
View File

@@ -0,0 +1,53 @@
const Reset = "\x1b[0m";
const Bright = "\x1b[1m";
const Dim = "\x1b[2m";
const Underscore = "\x1b[4m";
const Blink = "\x1b[5m";
const Reverse = "\x1b[7m";
const Hidden = "\x1b[8m";
const FgBlack = "\x1b[30m";
const FgRed = "\x1b[31m";
const FgGreen = "\x1b[32m";
const FgYellow = "\x1b[33m";
const FgBlue = "\x1b[34m";
const FgMagenta = "\x1b[35m";
const FgCyan = "\x1b[36m";
const FgWhite = "\x1b[37m";
const BgBlack = "\x1b[40m";
const BgRed = "\x1b[41m";
const BgGreen = "\x1b[42m";
const BgYellow = "\x1b[43m";
const BgBlue = "\x1b[44m";
const BgMagenta = "\x1b[45m";
const BgCyan = "\x1b[46m";
const BgWhite = "\x1b[47m";
function success(...args) {
console.log(`${FgGreen}%s${Reset}`, ...args);
}
function info(...args) {
console.log(`${FgBlue}%s${Reset}`, ...args);
}
function warn(...args) {
console.log(`${FgYellow}%s${Reset}`, ...args);
}
function error(...args) {
console.log(`${FgRed}%s${Reset}`, ...args);
}
function log(...args) {
console.log(...args);
}
module.exports = {
success,
info,
warn,
error,
log
};

35
npm/package-lock.json generated Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "hpdevkit",
"version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "hpdevkit",
"version": "0.5.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"commander": "9.4.0"
},
"bin": {
"hpdevkit": "index.js"
}
},
"node_modules/commander": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz",
"integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==",
"engines": {
"node": "^12.20.0 || >=14"
}
}
},
"dependencies": {
"commander": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz",
"integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw=="
}
}
}

22
npm/package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "hpdevkit",
"version": "0.5.0",
"description": "Developer toolkit for HotPocket smart contract development",
"scripts": {
"install": "node scripts/install.js"
},
"keywords": [
"HotPocket",
"toolkit",
"hpdevkit",
"smart contract"
],
"homepage": "https://github.com/HotPocketDev/evernode-sdk",
"license": "MIT",
"dependencies": {
"commander": "9.4.0"
},
"bin": {
"hpdevkit": "./index.js"
}
}

3
npm/scripts/install.js Normal file
View File

@@ -0,0 +1,3 @@
const { updateDockerImages } = require("../lib/common");
updateDockerImages();