Browse Source

storage-node: introduce CLI typescript project

Shamil Gadelshin 4 years ago
parent
commit
f1e792e762

+ 4 - 2
storage-node/.gitignore

@@ -1,6 +1,6 @@
 build/
 coverage/
-dist
+dist/
 tmp/
 .DS_Store
 
@@ -26,4 +26,6 @@ node_modules/
 # Ignore nvm config file
 .nvmrc
 
-yarn.lock
+yarn.lock
+
+*.tsbuildinfo

+ 6 - 2
storage-node/package.json

@@ -32,7 +32,8 @@
   ],
   "scripts": {
     "test": "wsrun --serial test",
-    "lint": "eslint --ignore-path .gitignore ."
+    "lint": "eslint --ignore-path .gitignore .",
+    "postinstall": "tsc --build"
   },
   "devDependencies": {
     "eslint": "^5.16.0",
@@ -41,6 +42,9 @@
     "eslint-plugin-babel": "^5.3.1",
     "eslint-plugin-prettier": "^3.1.4",
     "prettier": "^2.0.5",
-    "wsrun": "^3.6.5"
+    "wsrun": "^3.6.5",
+    "typescript": "^3.9.6",
+    "@types/chai": "^4.2.11",
+    "@types/mocha": "^7.0.2"
   }
 }

+ 1 - 240
storage-node/packages/cli/bin/cli.js

@@ -1,245 +1,6 @@
 #!/usr/bin/env node
-/*
- * This file is part of the storage node for the Joystream project.
- * Copyright (C) 2019 Joystream Contributors
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
 
-'use strict'
-
-const fs = require('fs')
-const assert = require('assert')
-const { RuntimeApi } = require('@joystream/storage-runtime-api')
-const meow = require('meow')
-const chalk = require('chalk')
-const _ = require('lodash')
-const debug = require('debug')('joystream:storage-cli')
-const dev = require('./dev')
-
-// Parse CLI
-const FLAG_DEFINITIONS = {
-  // TODO
-}
-
-const cli = meow(
-  `
-  Usage:
-    $ storage-cli command [arguments..] [key_file] [passphrase]
-
-  Some commands require a key file as the last option holding the identity for
-  interacting with the runtime API.
-
-  Commands:
-    upload            Upload a file to a Colossus storage node. Requires a
-                      storage node URL, and a local file name to upload. As
-                      an optional third parameter, you can provide a Data
-                      Object Type ID - this defaults to "1" if not provided.
-    download          Retrieve a file. Requires a storage node URL and a content
-                      ID, as well as an output filename.
-    head              Send a HEAD request for a file, and print headers.
-                      Requires a storage node URL and a content ID.
-
-  Dev Commands:       Commands to run on a development chain.
-    dev-init          Setup chain with Alice as lead and storage provider.
-    dev-check         Check the chain is setup with Alice as lead and storage provider.
-  `,
-  { flags: FLAG_DEFINITIONS }
-)
-
-function assertFile(name, filename) {
-  assert(filename, `Need a ${name} parameter to proceed!`)
-  assert(fs.statSync(filename).isFile(), `Path "${filename}" is not a file, aborting!`)
-}
-
-function loadIdentity(api, filename, passphrase) {
-  if (filename) {
-    assertFile('keyfile', filename)
-    api.identities.loadUnlock(filename, passphrase)
-  } else {
-    debug('Loading Alice as identity')
-    api.identities.useKeyPair(dev.aliceKeyPair(api))
-  }
-}
-
-const commands = {
-  // add Alice well known account as storage provider
-  'dev-init': async api => {
-    // dev accounts are automatically loaded, no need to add explicitly to keyring using loadIdentity(api)
-    const dev = require('./dev')
-    return dev.init(api)
-  },
-  // Checks that the setup done by dev-init command was successful.
-  'dev-check': async api => {
-    // dev accounts are automatically loaded, no need to add explicitly to keyring using loadIdentity(api)
-    const dev = require('./dev')
-    return dev.check(api)
-  },
-  // The upload method is not correctly implemented
-  // needs to get the liaison after creating a data object,
-  // resolve the ipns id to the asset put api url of the storage-node
-  // before uploading..
-  upload: async (api, url, filename, doTypeId, keyfile, passphrase) => {
-    loadIdentity(keyfile, passphrase)
-    // Check parameters
-    assertFile('file', filename)
-
-    const size = fs.statSync(filename).size
-    debug(`File "${filename}" is ${chalk.green(size)} Bytes.`)
-
-    if (!doTypeId) {
-      doTypeId = 1
-    }
-
-    debug('Data Object Type ID is: ' + chalk.green(doTypeId))
-
-    // Generate content ID
-    // FIXME this require path is like this because of
-    // https://github.com/Joystream/apps/issues/207
-    const { ContentId } = require('@joystream/types/media')
-    let cid = ContentId.generate()
-    cid = cid.encode().toString()
-    debug('Generated content ID: ' + chalk.green(cid))
-
-    // Create Data Object
-    await api.assets.createDataObject(api.identities.key.address, cid, doTypeId, size)
-    debug('Data object created.')
-
-    // TODO in future, optionally contact liaison here?
-    const request = require('request')
-    url = `${url}asset/v0/${cid}`
-    debug('Uploading to URL', chalk.green(url))
-
-    const f = fs.createReadStream(filename)
-    const opts = {
-      url,
-      headers: {
-        'content-type': '',
-        'content-length': `${size}`,
-      },
-      json: true,
-    }
-    return new Promise((resolve, reject) => {
-      const r = request.put(opts, (error, response, body) => {
-        if (error) {
-          reject(error)
-          return
-        }
-
-        if (response.statusCode / 100 !== 2) {
-          reject(new Error(`${response.statusCode}: ${body.message || 'unknown reason'}`))
-          return
-        }
-        debug('Upload successful:', body.message)
-        resolve()
-      })
-      f.pipe(r)
-    })
-  },
-  // needs to be updated to take a content id and resolve it a potential set
-  // of providers that has it, and select one (possibly try more than one provider)
-  // to fetch it from the get api url of a provider..
-  download: async (api, url, contentId, filename) => {
-    const request = require('request')
-    url = `${url}asset/v0/${contentId}`
-    debug('Downloading URL', chalk.green(url), 'to', chalk.green(filename))
-
-    const f = fs.createWriteStream(filename)
-    const opts = {
-      url,
-      json: true,
-    }
-    return new Promise((resolve, reject) => {
-      const r = request.get(opts, (error, response, body) => {
-        if (error) {
-          reject(error)
-          return
-        }
-
-        debug(
-          'Downloading',
-          chalk.green(response.headers['content-type']),
-          'of size',
-          chalk.green(response.headers['content-length']),
-          '...'
-        )
-
-        f.on('error', err => {
-          reject(err)
-        })
-
-        f.on('finish', () => {
-          if (response.statusCode / 100 !== 2) {
-            reject(new Error(`${response.statusCode}: ${body.message || 'unknown reason'}`))
-            return
-          }
-          debug('Download completed.')
-          resolve()
-        })
-      })
-      r.pipe(f)
-    })
-  },
-  // similar to 'download' function
-  head: async (api, url, contentId) => {
-    const request = require('request')
-    url = `${url}asset/v0/${contentId}`
-    debug('Checking URL', chalk.green(url), '...')
-
-    const opts = {
-      url,
-      json: true,
-    }
-    return new Promise((resolve, reject) => {
-      request.head(opts, (error, response, body) => {
-        if (error) {
-          reject(error)
-          return
-        }
-
-        if (response.statusCode / 100 !== 2) {
-          reject(new Error(`${response.statusCode}: ${body.message || 'unknown reason'}`))
-          return
-        }
-
-        for (const propname in response.headers) {
-          debug(`  ${chalk.yellow(propname)}: ${response.headers[propname]}`)
-        }
-
-        resolve()
-      })
-    })
-  },
-}
-
-async function main() {
-  const api = await RuntimeApi.create()
-
-  // Simple CLI commands
-  const command = cli.input[0]
-  if (!command) {
-    throw new Error('Need a command to run!')
-  }
-
-  if (Object.prototype.hasOwnProperty.call(commands, command)) {
-    // Command recognized
-    const args = _.clone(cli.input).slice(1)
-    await commands[command](api, ...args)
-  } else {
-    throw new Error(`Command "${command}" not recognized, aborting!`)
-  }
-}
+const {main} = require('../dist/cli')
 
 main()
   .then(() => {

+ 242 - 0
storage-node/packages/cli/src/cli.ts

@@ -0,0 +1,242 @@
+#!/usr/bin/env node
+/*
+ * This file is part of the storage node for the Joystream project.
+ * Copyright (C) 2019 Joystream Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+'use strict'
+
+const fs = require('fs')
+const assert = require('assert')
+const { RuntimeApi } = require('@joystream/storage-runtime-api')
+const meow = require('meow')
+const chalk = require('chalk')
+const _ = require('lodash')
+const debug = require('debug')('joystream:storage-cli')
+const dev = require('./dev')
+
+// Parse CLI
+const FLAG_DEFINITIONS = {
+  // TODO
+}
+
+const cli = meow(
+  `
+  Usage:
+    $ storage-cli command [arguments..] [key_file] [passphrase]
+
+  Some commands require a key file as the last option holding the identity for
+  interacting with the runtime API.
+
+  Commands:
+    upload            Upload a file to a Colossus storage node. Requires a
+                      storage node URL, and a local file name to upload. As
+                      an optional third parameter, you can provide a Data
+                      Object Type ID - this defaults to "1" if not provided.
+    download          Retrieve a file. Requires a storage node URL and a content
+                      ID, as well as an output filename.
+    head              Send a HEAD request for a file, and print headers.
+                      Requires a storage node URL and a content ID.
+
+  Dev Commands:       Commands to run on a development chain.
+    dev-init          Setup chain with Alice as lead and storage provider.
+    dev-check         Check the chain is setup with Alice as lead and storage provider.
+  `,
+  { flags: FLAG_DEFINITIONS }
+)
+
+function assertFile(name, filename) {
+  assert(filename, `Need a ${name} parameter to proceed!`)
+  assert(fs.statSync(filename).isFile(), `Path "${filename}" is not a file, aborting!`)
+}
+
+function loadIdentity(api, filename, passphrase) {
+  if (filename) {
+    assertFile('keyfile', filename)
+    api.identities.loadUnlock(filename, passphrase)
+  } else {
+    debug('Loading Alice as identity')
+    api.identities.useKeyPair(dev.aliceKeyPair(api))
+  }
+}
+
+const commands = {
+  // add Alice well known account as storage provider
+  'dev-init': async api => {
+    // dev accounts are automatically loaded, no need to add explicitly to keyring using loadIdentity(api)
+    const dev = require('./dev')
+    return dev.init(api)
+  },
+  // Checks that the setup done by dev-init command was successful.
+  'dev-check': async api => {
+    // dev accounts are automatically loaded, no need to add explicitly to keyring using loadIdentity(api)
+    const dev = require('./dev')
+    return dev.check(api)
+  },
+  // The upload method is not correctly implemented
+  // needs to get the liaison after creating a data object,
+  // resolve the ipns id to the asset put api url of the storage-node
+  // before uploading..
+  upload: async (api, url, filename, doTypeId, keyfile, passphrase) => {
+    loadIdentity(api, keyfile, passphrase)
+    // Check parameters
+    assertFile('file', filename)
+
+    const size = fs.statSync(filename).size
+    debug(`File "${filename}" is ${chalk.green(size)} Bytes.`)
+
+    if (!doTypeId) {
+      doTypeId = 1
+    }
+
+    debug('Data Object Type ID is: ' + chalk.green(doTypeId))
+
+    // Generate content ID
+    // FIXME this require path is like this because of
+    // https://github.com/Joystream/apps/issues/207
+    const { ContentId } = require('@joystream/types/media')
+    let cid = ContentId.generate()
+    cid = cid.encode().toString()
+    debug('Generated content ID: ' + chalk.green(cid))
+
+    // Create Data Object
+    await api.assets.createDataObject(api.identities.key.address, cid, doTypeId, size)
+    debug('Data object created.')
+
+    // TODO in future, optionally contact liaison here?
+    const request = require('request')
+    url = `${url}asset/v0/${cid}`
+    debug('Uploading to URL', chalk.green(url))
+
+    const f = fs.createReadStream(filename)
+    const opts = {
+      url,
+      headers: {
+        'content-type': '',
+        'content-length': `${size}`,
+      },
+      json: true,
+    }
+    return new Promise((resolve, reject) => {
+      const r = request.put(opts, (error, response, body) => {
+        if (error) {
+          reject(error)
+          return
+        }
+
+        if (response.statusCode / 100 !== 2) {
+          reject(new Error(`${response.statusCode}: ${body.message || 'unknown reason'}`))
+          return
+        }
+        debug('Upload successful:', body.message)
+        resolve()
+      })
+      f.pipe(r)
+    })
+  },
+  // needs to be updated to take a content id and resolve it a potential set
+  // of providers that has it, and select one (possibly try more than one provider)
+  // to fetch it from the get api url of a provider..
+  download: async (api, url, contentId, filename) => {
+    const request = require('request')
+    url = `${url}asset/v0/${contentId}`
+    debug('Downloading URL', chalk.green(url), 'to', chalk.green(filename))
+
+    const f = fs.createWriteStream(filename)
+    const opts = {
+      url,
+      json: true,
+    }
+    return new Promise((resolve, reject) => {
+      const r = request.get(opts, (error, response, body) => {
+        if (error) {
+          reject(error)
+          return
+        }
+
+        debug(
+          'Downloading',
+          chalk.green(response.headers['content-type']),
+          'of size',
+          chalk.green(response.headers['content-length']),
+          '...'
+        )
+
+        f.on('error', err => {
+          reject(err)
+        })
+
+        f.on('finish', () => {
+          if (response.statusCode / 100 !== 2) {
+            reject(new Error(`${response.statusCode}: ${body.message || 'unknown reason'}`))
+            return
+          }
+          debug('Download completed.')
+          resolve()
+        })
+      })
+      r.pipe(f)
+    })
+  },
+  // similar to 'download' function
+  head: async (api, url, contentId) => {
+    const request = require('request')
+    url = `${url}asset/v0/${contentId}`
+    debug('Checking URL', chalk.green(url), '...')
+
+    const opts = {
+      url,
+      json: true,
+    }
+    return new Promise((resolve, reject) => {
+      request.head(opts, (error, response, body) => {
+        if (error) {
+          reject(error)
+          return
+        }
+
+        if (response.statusCode / 100 !== 2) {
+          reject(new Error(`${response.statusCode}: ${body.message || 'unknown reason'}`))
+          return
+        }
+
+        for (const propname in response.headers) {
+          debug(`  ${chalk.yellow(propname)}: ${response.headers[propname]}`)
+        }
+
+        resolve()
+      })
+    })
+  },
+}
+
+export async function main() {
+  const api = await RuntimeApi.create()
+
+  // Simple CLI commands
+  const command = cli.input[0]
+  if (!command) {
+    throw new Error('Need a command to run!')
+  }
+
+  if (Object.prototype.hasOwnProperty.call(commands, command)) {
+    // Command recognized
+    const args = _.clone(cli.input).slice(1)
+    await commands[command](api, ...args)
+  } else {
+    throw new Error(`Command "${command}" not recognized, aborting!`)
+  }
+}

+ 8 - 0
storage-node/packages/cli/bin/dev.js → storage-node/packages/cli/src/dev.ts

@@ -123,3 +123,11 @@ module.exports = {
   roleKeyPair,
   developmentPort,
 }
+
+export {
+  init,
+  check,
+  aliceKeyPair,
+  roleKeyPair,
+  developmentPort,
+};

+ 11 - 0
storage-node/packages/cli/tsconfig.json

@@ -0,0 +1,11 @@
+{
+  "include": [
+    "src"
+  ],
+  "extends": "../../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "dist",
+    "rootDir": "src",
+    "baseUrl": "."
+  }
+}

+ 23 - 0
storage-node/tsconfig.json

@@ -0,0 +1,23 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "rootDir": "./packages/",
+    "outDir": "./build",
+    "allowJs": true,
+    "target": "es2017",
+    "module": "commonjs",
+    "esModuleInterop": true,
+    "baseUrl": ".",
+    "skipLibCheck": true,
+    "types" : [ "node", "mocha" ]
+  },
+  "files": [],
+  "exclude": [
+    "**/node_modules/*",
+    "build"
+  ],
+  "references": [
+    { "path": "packages/cli" }
+ //   { "path": "packages/storage" }
+  ]
+}

+ 2 - 2
yarn.lock

@@ -3938,7 +3938,7 @@
   dependencies:
     "@types/node" "*"
 
-"@types/mocha@*":
+"@types/mocha@*", "@types/mocha@^7.0.2":
   version "7.0.2"
   resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce"
   integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==
@@ -22800,7 +22800,7 @@ typescript-formatter@^7.2.2:
     commandpost "^1.0.0"
     editorconfig "^0.15.0"
 
-typescript@3.7.2, typescript@3.7.x, typescript@^3.0.3, typescript@^3.6.4, typescript@^3.7.2, typescript@^3.8.3:
+typescript@3.7.2, typescript@3.7.x, typescript@^3.0.3, typescript@^3.6.4, typescript@^3.7.2, typescript@^3.8.3, typescript@^3.9.6:
   version "3.7.2"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb"
   integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==