From 087e80ce905d846f6047f7528ae9a2468afa583e Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 21 Sep 2022 15:56:20 +0100 Subject: [PATCH 1/7] deps: update @multiformats/multiaddr to 11.0.0 (#775) --- package.json | 14 +++++++------- src/factory.js | 6 +++--- src/ipfsd-client.js | 13 ++++++++----- src/ipfsd-daemon.js | 16 ++++++++++------ src/ipfsd-in-proc.js | 9 +++++---- test/controller.spec.js | 2 +- 6 files changed, 34 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 935b6948..47f289ad 100644 --- a/package.json +++ b/package.json @@ -155,24 +155,24 @@ "@hapi/hapi": "^20.0.0", "@libp2p/interface-peer-id": "^1.0.4", "@libp2p/logger": "^2.0.0", - "@multiformats/multiaddr": "^10.1.8", + "@multiformats/multiaddr": "^11.0.0", "execa": "^6.1.0", "ipfs-utils": "^9.0.1", "joi": "^17.2.1", "merge-options": "^3.0.1", "nanoid": "^4.0.0", - "p-wait-for": "^4.1.0", + "p-wait-for": "^5.0.0", "temp-write": "^5.0.0", "wherearewe": "^2.0.1" }, "devDependencies": { "@types/hapi__hapi": "^20.0.9", "aegir": "^37.0.15", - "go-ipfs": "^0.13.0", - "ipfs": "^0.63.5", - "ipfs-client": "^0.8.3", - "ipfs-core-types": "^0.11.1", - "ipfs-http-client": "^57.0.3", + "go-ipfs": "^0.15.0", + "ipfs": "^0.64.0", + "ipfs-client": "^0.9.0", + "ipfs-core-types": "^0.12.0", + "ipfs-http-client": "^58.0.0", "util": "^0.12.4" }, "browser": { diff --git a/src/factory.js b/src/factory.js index 0c2958f6..198cd957 100644 --- a/src/factory.js +++ b/src/factory.js @@ -10,9 +10,9 @@ import testsConfig from './config.js' const merge = mergeOptions.bind({ ignoreUndefined: true }) /** - * @typedef {import("./types").ControllerOptions} ControllerOptions - * @typedef {import("./types").ControllerOptionsOverrides} ControllerOptionsOverrides - * @typedef {import("./types").IPFSOptions} IPFSOptions + * @typedef {import('./types').ControllerOptions} ControllerOptions + * @typedef {import('./types').ControllerOptionsOverrides} ControllerOptionsOverrides + * @typedef {import('./types').IPFSOptions} IPFSOptions * @typedef {import('./types').Controller} Controller */ diff --git a/src/ipfsd-client.js b/src/ipfsd-client.js index 528ab016..438991f6 100644 --- a/src/ipfsd-client.js +++ b/src/ipfsd-client.js @@ -1,4 +1,4 @@ -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import http from 'ipfs-utils/src/http.js' import mergeOptions from 'merge-options' import { logger } from '@libp2p/logger' @@ -10,7 +10,10 @@ const daemonLog = { err: logger('ipfsd-ctl:client:stderr') } -/** @typedef {import("./index").ControllerOptions} ControllerOptions */ +/** + * @typedef {import('./index').ControllerOptions} ControllerOptions + * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr + */ /** * Controller for remote nodes @@ -61,7 +64,7 @@ class Client { */ _setApi (addr) { if (addr) { - this.apiAddr = new Multiaddr(addr) + this.apiAddr = multiaddr(addr) } } @@ -71,7 +74,7 @@ class Client { */ _setGateway (addr) { if (addr) { - this.gatewayAddr = new Multiaddr(addr) + this.gatewayAddr = multiaddr(addr) } } @@ -81,7 +84,7 @@ class Client { */ _setGrpc (addr) { if (addr) { - this.grpcAddr = new Multiaddr(addr) + this.grpcAddr = multiaddr(addr) } } diff --git a/src/ipfsd-daemon.js b/src/ipfsd-daemon.js index 9ad655f6..835af609 100644 --- a/src/ipfsd-daemon.js +++ b/src/ipfsd-daemon.js @@ -1,4 +1,4 @@ -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import fs from 'fs/promises' import mergeOptions from 'merge-options' import { logger } from '@libp2p/logger' @@ -9,6 +9,10 @@ import os from 'os' import { checkForRunningApi, repoExists, tmpDir, defaultRepo, buildInitArgs, buildStartArgs } from './utils.js' import waitFor from 'p-wait-for' +/** + * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr + */ + const merge = mergeOptions.bind({ ignoreUndefined: true }) const daemonLog = { @@ -27,8 +31,8 @@ function translateError (err) { } /** - * @typedef {import("./types").ControllerOptions} ControllerOptions - * @typedef {import("./types").Controller} Controller + * @typedef {import('./types').ControllerOptions} ControllerOptions + * @typedef {import('./types').Controller} Controller */ /** @@ -74,7 +78,7 @@ class Daemon { * @param {string} addr */ _setApi (addr) { - this.apiAddr = new Multiaddr(addr) + this.apiAddr = multiaddr(addr) } /** @@ -82,7 +86,7 @@ class Daemon { * @param {string} addr */ _setGrpc (addr) { - this.grpcAddr = new Multiaddr(addr) + this.grpcAddr = multiaddr(addr) } /** @@ -90,7 +94,7 @@ class Daemon { * @param {string} addr */ _setGateway (addr) { - this.gatewayAddr = new Multiaddr(addr) + this.gatewayAddr = multiaddr(addr) } _createApi () { diff --git a/src/ipfsd-in-proc.js b/src/ipfsd-in-proc.js index 15a6970e..c2ee70ce 100644 --- a/src/ipfsd-in-proc.js +++ b/src/ipfsd-in-proc.js @@ -1,4 +1,4 @@ -import { Multiaddr } from '@multiformats/multiaddr' +import { multiaddr } from '@multiformats/multiaddr' import mergeOptions from 'merge-options' import { repoExists, removeRepo, checkForRunningApi, tmpDir, defaultRepo } from './utils.js' import { logger } from '@libp2p/logger' @@ -10,8 +10,9 @@ const daemonLog = { err: logger('ipfsd-ctl:proc:stderr') } /** - * @typedef {import("./types").ControllerOptions} ControllerOptions - * @typedef {import("./types").InitOptions} InitOptions + * @typedef {import('./types').ControllerOptions} ControllerOptions + * @typedef {import('./types').InitOptions} InitOptions + * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr */ /** @@ -66,7 +67,7 @@ class InProc { * @param {string} addr */ _setApi (addr) { - this.apiAddr = new Multiaddr(addr) + this.apiAddr = multiaddr(addr) this.api = this.opts.ipfsHttpModule.create(addr) this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port diff --git a/test/controller.spec.js b/test/controller.spec.js index 2e3ba1d0..80b539b2 100644 --- a/test/controller.spec.js +++ b/test/controller.spec.js @@ -12,7 +12,7 @@ import * as ipfsHttpModule from 'ipfs-http-client' import * as goIpfsModule from 'go-ipfs' /** - * @typedef {import("../src/types").ControllerOptions} ControllerOptions + * @typedef {import('../src/types').ControllerOptions} ControllerOptions */ /** From d2c9df67079192984faf5842fec2095ec6588188 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 21 Sep 2022 16:16:34 +0000 Subject: [PATCH 2/7] chore(release): 12.0.3 [skip ci] ## [12.0.3](https://github.com/ipfs/js-ipfsd-ctl/compare/v12.0.2...v12.0.3) (2022-09-21) ### Dependencies * update @multiformats/multiaddr to 11.0.0 ([#775](https://github.com/ipfs/js-ipfsd-ctl/issues/775)) ([087e80c](https://github.com/ipfs/js-ipfsd-ctl/commit/087e80ce905d846f6047f7528ae9a2468afa583e)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2164f007..03ea812e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [12.0.3](https://github.com/ipfs/js-ipfsd-ctl/compare/v12.0.2...v12.0.3) (2022-09-21) + + +### Dependencies + +* update @multiformats/multiaddr to 11.0.0 ([#775](https://github.com/ipfs/js-ipfsd-ctl/issues/775)) ([087e80c](https://github.com/ipfs/js-ipfsd-ctl/commit/087e80ce905d846f6047f7528ae9a2468afa583e)) + ## [12.0.2](https://github.com/ipfs/js-ipfsd-ctl/compare/v12.0.1...v12.0.2) (2022-09-02) diff --git a/package.json b/package.json index 47f289ad..fde7a455 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipfsd-ctl", - "version": "12.0.2", + "version": "12.0.3", "description": "Spawn IPFS Daemons, JS or Go", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/ipfs/js-ipfsd-ctl#readme", From ee3a35a28c7a435c49da02d4e03d29c4520e5b55 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 4 Oct 2022 13:45:11 +0100 Subject: [PATCH 3/7] feat: convert to typescript (#777) Converts this module to typescript --- .aegir.js | 17 +- .gitignore | 1 + README.md | 5 +- package.json | 30 +-- src/{config.js => config.ts} | 15 +- src/endpoint/{routes.js => routes.ts} | 61 +++--- .../{server.browser.js => server.browser.ts} | 26 ++- src/endpoint/server.js | 66 ------- src/endpoint/server.ts | 58 ++++++ src/{factory.js => factory.ts} | 83 ++++---- src/index.js | 48 ----- src/{types.ts => index.ts} | 93 ++++++++- src/{ipfsd-client.js => ipfsd-client.ts} | 135 +++++-------- src/{ipfsd-daemon.js => ipfsd-daemon.ts} | 186 ++++++++---------- src/{ipfsd-in-proc.js => ipfsd-in-proc.ts} | 102 ++++------ src/{utils.browser.js => utils.browser.ts} | 22 +-- src/{utils.js => utils.ts} | 73 +++---- test/{browser.js => browser.ts} | 0 test/{browser.utils.js => browser.utils.ts} | 0 ...{controller.spec.js => controller.spec.ts} | 53 +++-- test/{create.spec.js => create.spec.ts} | 34 +--- test/{factory.spec.js => factory.spec.ts} | 14 +- test/{node.routes.js => node.routes.ts} | 12 +- test/{node.js => node.ts} | 5 +- test/{node.utils.js => node.utils.ts} | 0 tsconfig.json | 3 +- 26 files changed, 481 insertions(+), 661 deletions(-) rename src/{config.js => config.ts} (81%) rename src/endpoint/{routes.js => routes.ts} (70%) rename src/endpoint/{server.browser.js => server.browser.ts} (55%) delete mode 100644 src/endpoint/server.js create mode 100644 src/endpoint/server.ts rename src/{factory.js => factory.ts} (59%) delete mode 100644 src/index.js rename src/{types.ts => index.ts} (73%) rename src/{ipfsd-client.js => ipfsd-client.ts} (63%) rename src/{ipfsd-daemon.js => ipfsd-daemon.ts} (69%) rename src/{ipfsd-in-proc.js => ipfsd-in-proc.ts} (56%) rename src/{utils.browser.js => utils.browser.ts} (65%) rename src/{utils.js => utils.ts} (52%) rename test/{browser.js => browser.ts} (100%) rename test/{browser.utils.js => browser.utils.ts} (100%) rename test/{controller.spec.js => controller.spec.ts} (92%) rename test/{create.spec.js => create.spec.ts} (89%) rename test/{factory.spec.js => factory.spec.ts} (95%) rename test/{node.routes.js => node.routes.ts} (96%) rename test/{node.js => node.ts} (84%) rename test/{node.utils.js => node.utils.ts} (100%) diff --git a/.aegir.js b/.aegir.js index 8b6ca72d..f94961d0 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,25 +1,16 @@ -import { createServer } from './src/index.js' import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' import * as goIpfsModule from 'go-ipfs' /** @type {import('aegir').Options["build"]["config"]} */ -/* -const esbuild = { - inject: [path.join(__dirname, 'scripts/node-globals.js')], -} -*/ -export default { +const config = { bundlesize: { maxSize: '35kB' }, test: { - browser: { - config: { - //buildConfig: esbuild - } - }, before: async () => { + const { createServer } = await import('./dist/src/index.js') + const server = createServer(undefined, { ipfsModule, ipfsHttpModule @@ -47,3 +38,5 @@ export default { } } } + +export default config diff --git a/.gitignore b/.gitignore index ae2e4e24..f09df423 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ logs *.log coverage +.coverage # Runtime data pids diff --git a/README.md b/README.md index f65a5107..ba5bc188 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # ipfsd-ctl -[![ipfs.io](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io) -[![IRC](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs) -[![Discord](https://img.shields.io/discord/806902334369824788?style=flat-square)](https://discord.gg/ipfs) +[![ipfs.tech](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](https://ipfs.tech) +[![Discuss](https://img.shields.io/discourse/https/discuss.ipfs.tech/posts.svg?style=flat-square)](https://discuss.ipfs.tech) [![codecov](https://img.shields.io/codecov/c/github/ipfs/js-ipfsd-ctl.svg?style=flat-square)](https://codecov.io/gh/ipfs/js-ipfsd-ctl) [![CI](https://img.shields.io/github/workflow/status/ipfs/js-ipfsd-ctl/test%20&%20maybe%20release/master?style=flat-square)](https://github.com/ipfs/js-ipfsd-ctl/actions/workflows/js-test-and-release.yml) diff --git a/package.json b/package.json index fde7a455..6ea9038b 100644 --- a/package.json +++ b/package.json @@ -22,25 +22,9 @@ }, "type": "module", "types": "./dist/src/index.d.ts", - "typesVersions": { - "*": { - "*": [ - "*", - "dist/*", - "dist/src/*", - "dist/src/*/index" - ], - "src/*": [ - "*", - "dist/*", - "dist/src/*", - "dist/src/*/index" - ] - } - }, "files": [ "src", - "dist", + "dist/src", "!dist/test", "!**/*.tsbuildinfo" ], @@ -142,11 +126,13 @@ ] }, "scripts": { + "clean": "aegir clean", "lint": "aegir lint", + "dep-check": "aegir dep-check", "build": "aegir build", "test": "aegir test", - "test:node": "aegir test -t node", - "test:chrome": "aegir test -t browser", + "test:node": "aegir test -t node --cov", + "test:chrome": "aegir test -t browser --cov", "test:firefox": "aegir test -t browser -- --browser firefox", "release": "aegir release" }, @@ -176,9 +162,9 @@ "util": "^0.12.4" }, "browser": { - "./src/endpoint/server.js": "./src/endpoint/server.browser.js", - "./src/utils.js": "./src/utils.browser.js", - "./src/ipfsd-daemon.js": "./src/ipfsd-client.js", + "./dist/src/endpoint/server.js": "./dist/src/endpoint/server.browser.js", + "./dist/src/utils.js": "./dist/src/utils.browser.js", + "./dist/src/ipfsd-daemon.js": "./dist/src/ipfsd-client.js", "go-ipfs": false }, "jsdelivr": "dist/index.min.js", diff --git a/src/config.js b/src/config.ts similarity index 81% rename from src/config.js rename to src/config.ts index 808cbb45..52bd4104 100644 --- a/src/config.js +++ b/src/config.ts @@ -1,12 +1,13 @@ import { isBrowser, isWebWorker } from 'wherearewe' +import type { NodeType } from './index.js' -/** - * @param {object} args - * @param {import('./types').NodeType} args.type - */ -export default ({ type }) => { - /** @type {string[]} */ - let swarm +export interface ConfigInit { + type?: NodeType +} + +export default (init: ConfigInit) => { + const { type } = init + let swarm: string[] // from the browser tell remote nodes to listen over WS if (type !== 'proc' && (isBrowser || isWebWorker)) { diff --git a/src/endpoint/routes.js b/src/endpoint/routes.ts similarity index 70% rename from src/endpoint/routes.js rename to src/endpoint/routes.ts index 3f488805..ffa608fe 100644 --- a/src/endpoint/routes.js +++ b/src/endpoint/routes.ts @@ -3,10 +3,8 @@ import Joi from 'joi' import boom from '@hapi/boom' import { logger } from '@libp2p/logger' import { tmpDir } from '../utils.js' - -/** - * @typedef {import('../types').Factory} Factory - */ +import type { Server } from '@hapi/hapi' +import type { Factory } from '../index.js' const debug = logger('ipfsd-ctl:routes') @@ -18,12 +16,9 @@ const routeOptions = { } } -/** - * @param {Error & { stdout?: string }} err - */ -const badRequest = err => { +const badRequest = (err: Error & { stdout?: string }) => { let msg - if (err.stdout) { + if (err.stdout != null) { msg = err.stdout + ' - ' + err.message } else { msg = err.message @@ -32,27 +27,17 @@ const badRequest = err => { throw boom.badRequest(msg) } -/** - * @type {Record} - */ -const nodes = {} - -/** - * @namespace EndpointServerRoutes - * @ignore - * @param {import('@hapi/hapi').Server} server - * @param {() => Factory | Promise} createFactory - * @returns {void} - */ -export default (server, createFactory) => { +const nodes: Record = {} + +export default (server: Server, createFactory: () => Factory | Promise): void => { server.route({ method: 'GET', path: '/util/tmp-dir', handler: async (request) => { - const type = request.query.type || 'go' + const type = request.query.type ?? 'go' try { return { tmpDir: await tmpDir(type) } - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } } @@ -66,7 +51,7 @@ export default (server, createFactory) => { try { return { version: await nodes[id].version() } - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } }, @@ -77,7 +62,7 @@ export default (server, createFactory) => { method: 'POST', path: '/spawn', handler: async (request) => { - const opts = request.payload || {} + const opts = request.payload ?? {} try { const ipfsd = await createFactory() const id = nanoid() @@ -85,9 +70,9 @@ export default (server, createFactory) => { nodes[id] = await ipfsd.spawn(opts) return { id: id, - apiAddr: nodes[id].apiAddr ? nodes[id].apiAddr.toString() : '', - gatewayAddr: nodes[id].gatewayAddr ? nodes[id].gatewayAddr.toString() : '', - grpcAddr: nodes[id].grpcAddr ? nodes[id].grpcAddr.toString() : '', + apiAddr: nodes[id].apiAddr?.toString(), + gatewayAddr: nodes[id].gatewayAddr?.toString(), + grpcAddr: nodes[id].grpcAddr?.toString(), initialized: nodes[id].initialized, started: nodes[id].started, disposable: nodes[id].disposable, @@ -95,7 +80,7 @@ export default (server, createFactory) => { path: nodes[id].path, clean: nodes[id].clean } - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } } @@ -109,7 +94,7 @@ export default (server, createFactory) => { path: '/init', handler: async (request) => { const id = request.query.id - const payload = request.payload || {} + const payload = request.payload ?? {} try { await nodes[id].init(payload) @@ -117,7 +102,7 @@ export default (server, createFactory) => { return { initialized: nodes[id].initialized } - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } }, @@ -137,11 +122,11 @@ export default (server, createFactory) => { await nodes[id].start() return { - apiAddr: nodes[id].apiAddr ? nodes[id].apiAddr.toString() : '', - gatewayAddr: nodes[id].gatewayAddr ? nodes[id].gatewayAddr.toString() : '', - grpcAddr: nodes[id].grpcAddr ? nodes[id].grpcAddr.toString() : '' + apiAddr: nodes[id].apiAddr?.toString(), + gatewayAddr: nodes[id].gatewayAddr?.toString(), + grpcAddr: nodes[id].grpcAddr?.toString() } - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } }, @@ -163,7 +148,7 @@ export default (server, createFactory) => { await nodes[id].cleanup() return h.response().code(200) - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } }, @@ -183,7 +168,7 @@ export default (server, createFactory) => { await nodes[id].stop() return h.response().code(200) - } catch (/** @type {any} */ err) { + } catch (err: any) { badRequest(err) } }, diff --git a/src/endpoint/server.browser.js b/src/endpoint/server.browser.ts similarity index 55% rename from src/endpoint/server.browser.js rename to src/endpoint/server.browser.ts index ec4f9986..3592fe8e 100644 --- a/src/endpoint/server.browser.js +++ b/src/endpoint/server.browser.ts @@ -1,24 +1,20 @@ /* eslint-disable no-console */ +import type { ServerInit } from './server.js' + /** - * Creates an instance of Server. - * - * @class + * Creates an instance of Server */ class Server { - /** - * @class - * @param {object} options - * @param {number} [options.port=43134] - Server port. - * @param {Function} createNode - */ - constructor (options, createNode) { - options = options || { port: 43134 } + private readonly options: ServerInit + public port: number + public host: string + + constructor (options: ServerInit = { port: 43134, host: 'localhost' }) { + this.options = options + this.port = this.options.port ?? 43134 + this.host = this.options.host ?? 'localhost' - /** @type {*} */ - this.server = null - this.port = options.port - this.createNode = createNode console.warn('Server not implemented in the browser') } diff --git a/src/endpoint/server.js b/src/endpoint/server.js deleted file mode 100644 index ae61cd14..00000000 --- a/src/endpoint/server.js +++ /dev/null @@ -1,66 +0,0 @@ -import Hapi from '@hapi/hapi' -import routes from './routes.js' - -/** - * @typedef {import('../types').Factory} Factory - */ - -/** - * Creates an instance of Server. - * - * @class - */ -class Server { - /** - * @class - * @param {object} options - * @param {number} [options.port=43134] - * @param {string} [options.host='localhost'] - * @param {() => Factory | Promise} createFactory - */ - constructor (options = { port: 43134, host: 'localhost' }, createFactory) { - this.options = options - this.server = null - this.port = this.options.port == null ? 43134 : this.options.port - this.host = this.options.host == null ? 'localhost' : this.options.host - this.createFactory = createFactory - } - - /** - * Start the server - * - * @param {number} port - * @returns {Promise} - */ - async start (port = this.port) { - this.port = port - this.server = new Hapi.Server({ - port: port, - host: this.host, - routes: { - cors: true - } - }) - - routes(this.server, this.createFactory) - - await this.server.start() - - return this - } - - /** - * Stop the server - * - * @param {object} [options] - * @param {number} options.timeout - * @returns {Promise} - */ - async stop (options) { - if (this.server) { - await this.server.stop(options) - } - } -} - -export default Server diff --git a/src/endpoint/server.ts b/src/endpoint/server.ts new file mode 100644 index 00000000..af406400 --- /dev/null +++ b/src/endpoint/server.ts @@ -0,0 +1,58 @@ +import Hapi from '@hapi/hapi' +import type { CreateFactory } from '../index.js' +import routes from './routes.js' + +export interface ServerInit { + port?: number + host?: string +} + +/** + * Creates an instance of Server + */ +class Server { + private readonly options: ServerInit + private server: Hapi.Server | null + public port: number + public host: string + private readonly createFactory: CreateFactory + + constructor (options: ServerInit = { port: 43134, host: 'localhost' }, createFactory: CreateFactory) { + this.options = options + this.server = null + this.port = this.options.port ?? 43134 + this.host = this.options.host ?? 'localhost' + this.createFactory = createFactory + } + + /** + * Start the server + */ + async start (port = this.port): Promise { + this.port = port + this.server = new Hapi.Server({ + port: port, + host: this.host, + routes: { + cors: true + } + }) + + routes(this.server, this.createFactory) + + await this.server.start() + + return this + } + + /** + * Stop the server + */ + async stop (options: { timeout: number }): Promise { + if (this.server != null) { + await this.server.stop(options) + } + } +} + +export default Server diff --git a/src/factory.js b/src/factory.ts similarity index 59% rename from src/factory.js rename to src/factory.ts index 198cd957..1621efb7 100644 --- a/src/factory.js +++ b/src/factory.ts @@ -6,19 +6,13 @@ import ControllerDaemon from './ipfsd-daemon.js' import ControllerRemote from './ipfsd-client.js' import ControllerProc from './ipfsd-in-proc.js' import testsConfig from './config.js' +import type { Controller, ControllerOptions, ControllerOptionsOverrides, Factory } from './index.js' const merge = mergeOptions.bind({ ignoreUndefined: true }) -/** - * @typedef {import('./types').ControllerOptions} ControllerOptions - * @typedef {import('./types').ControllerOptionsOverrides} ControllerOptionsOverrides - * @typedef {import('./types').IPFSOptions} IPFSOptions - * @typedef {import('./types').Controller} Controller - */ - const defaults = { remote: !isNode && !isElectronMain, - endpoint: process.env.IPFSD_CTL_SERVER || 'http://localhost:43134', + endpoint: process.env.IPFSD_CTL_SERVER ?? 'http://localhost:43134', disposable: true, test: false, type: 'go', @@ -29,27 +23,33 @@ const defaults = { forceKillTimeout: 5000 } +export interface ControllerOptionsOverridesWithEndpoint { + js?: ControllerOptionsWithEndpoint + go?: ControllerOptionsWithEndpoint + proc?: ControllerOptionsWithEndpoint +} + +export interface ControllerOptionsWithEndpoint extends ControllerOptions { + endpoint: string +} + /** * Factory class to spawn ipfsd controllers */ -class Factory { - /** - * - * @param {ControllerOptions} options - * @param {ControllerOptionsOverrides} overrides - Pre-defined overrides per controller type - */ - constructor (options = {}, overrides = {}) { - /** @type ControllerOptions */ - this.opts = merge(defaults, options) +class DefaultFactory implements Factory { + public opts: ControllerOptionsWithEndpoint + public controllers: Controller[] + + private readonly overrides: ControllerOptionsOverridesWithEndpoint - /** @type ControllerOptionsOverrides */ + constructor (options: ControllerOptions = {}, overrides: ControllerOptionsOverrides = {}) { + this.opts = merge(defaults, options) this.overrides = merge({ js: merge(this.opts, { type: 'js' }), go: merge(this.opts, { type: 'go' }), proc: merge(this.opts, { type: 'proc' }) }, overrides) - /** @type {Controller[]} */ this.controllers = [] } @@ -57,30 +57,24 @@ class Factory { * Utility method to get a temporary directory * useful in browsers to be able to generate temp * repos manually - * - * @param {ControllerOptions} [options] - * @returns {Promise} */ - async tmpDir (options = {}) { - const opts = merge(this.opts, options) + async tmpDir (options: ControllerOptions = {}): Promise { + const opts: ControllerOptions = merge(this.opts, options) - if (opts.remote) { + if (opts.remote === true) { const res = await http.get( - `${opts.endpoint}/util/tmp-dir`, - { searchParams: new URLSearchParams({ type: `${opts.type}` }) } + `${opts.endpoint ?? ''}/util/tmp-dir`, + { searchParams: new URLSearchParams({ type: opts.type ?? '' }) } ) const out = await res.json() return out.tmpDir } - return Promise.resolve(tmpDir(opts.type)) + return await Promise.resolve(tmpDir(opts.type)) } - /** - * @param {IPFSOptions & { endpoint: string }} options - */ - async _spawnRemote (options) { + async _spawnRemote (options: ControllerOptionsWithEndpoint) { const opts = { json: { ...options, @@ -105,13 +99,10 @@ class Factory { /** * Spawn an IPFSd Controller - * - * @param {ControllerOptions} options - * @returns {Promise} */ - async spawn (options = { }) { - const type = options.type || this.opts.type || 'go' - const opts = merge( + async spawn (options: ControllerOptions = { }): Promise { + const type = options.type ?? this.opts.type ?? 'go' + const opts: ControllerOptionsWithEndpoint = merge( this.overrides[type], options ) @@ -122,7 +113,7 @@ class Factory { start: false, init: false }, - opts.test + opts.test === true ? { config: testsConfig(opts), preload: { enabled: false } @@ -131,11 +122,11 @@ class Factory { opts.ipfsOptions ) - let ctl + let ctl: Controller if (opts.type === 'proc') { // spawn in-proc controller ctl = new ControllerProc({ ...opts, ipfsOptions }) - } else if (opts.remote) { + } else if (opts.remote === true) { // spawn remote controller ctl = await this._spawnRemote({ ...opts, ipfsOptions }) } else { @@ -147,10 +138,10 @@ class Factory { this.controllers.push(ctl) // Auto init and start controller - if (opts.disposable && (!options.ipfsOptions || (options.ipfsOptions && options.ipfsOptions.init !== false))) { + if (opts.disposable === true && (options.ipfsOptions == null || options.ipfsOptions?.init !== false)) { await ctl.init(ipfsOptions.init) } - if (opts.disposable && (!options.ipfsOptions || (options.ipfsOptions && options.ipfsOptions.start !== false))) { + if (opts.disposable === true && (options.ipfsOptions == null || options.ipfsOptions?.start !== false)) { await ctl.start() } @@ -160,10 +151,10 @@ class Factory { /** * Stop all controllers */ - async clean () { - await Promise.all(this.controllers.map(n => n.stop())) + async clean (): Promise { + await Promise.all(this.controllers.map(async n => await n.stop())) this.controllers = [] } } -export default Factory +export default DefaultFactory diff --git a/src/index.js b/src/index.js deleted file mode 100644 index e26ba641..00000000 --- a/src/index.js +++ /dev/null @@ -1,48 +0,0 @@ -import DefaultFactory from './factory.js' -import Server from './endpoint/server.js' - -/** - * @typedef {import('./types').Controller} Controller - * @typedef {import('./types').ControllerOptions} ControllerOptions - * @typedef {import('./types').ControllerOptionsOverrides} ControllerOptionsOverrides - * @typedef {import('./types').Factory} Factory - */ - -/** - * Creates a factory - * - * @param {ControllerOptions} [options] - * @param {ControllerOptionsOverrides} [overrides] - * @returns {Factory} - */ -export const createFactory = (options, overrides) => { - return new DefaultFactory(options, overrides) -} - -/** - * Creates a node - * - * @param {ControllerOptions} [options] - * @returns {Promise} - */ -export const createController = (options) => { - const f = new DefaultFactory() - return f.spawn(options) -} - -/** - * Create a Endpoint Server - * - * @param {number | { port: number }} [options] - Configuration options or just the port. - * @param {ControllerOptions} [factoryOptions] - * @param {ControllerOptionsOverrides} [factoryOverrides] - */ -export const createServer = (options, factoryOptions = {}, factoryOverrides = {}) => { - if (typeof options === 'number') { - options = { port: options } - } - - return new Server(options, () => { - return createFactory(factoryOptions, factoryOverrides) - }) -} diff --git a/src/types.ts b/src/index.ts similarity index 73% rename from src/types.ts rename to src/index.ts index fcc1d4af..9ea81245 100644 --- a/src/types.ts +++ b/src/index.ts @@ -1,13 +1,9 @@ - -import type { EventEmitter } from 'events' +import DefaultFactory from './factory.js' +import Server from './endpoint/server.js' import type { IPFS } from 'ipfs-core-types' import type { Multiaddr } from '@multiformats/multiaddr' import type { PeerId } from '@libp2p/interface-peer-id' - -export interface Subprocess { - stderr: EventEmitter | null - stdout: EventEmitter | null -} +import type { ExecaChildProcess } from 'execa' export interface PeerData { id: PeerId @@ -15,18 +11,43 @@ export interface PeerData { } export interface Controller { + /** + * Initialize a repo + */ init: (options?: InitOptions) => Promise + + /** + * Start the daemon + */ start: () => Promise + + /** + * Stop the daemon + */ stop: () => Promise + + /** + * Delete the repo that was being used. + * If the node was marked as `disposable` this will be called + * automatically when the process is exited. + */ cleanup: () => Promise + + /** + * Get the pid of the `ipfs daemon` process + */ pid: () => Promise + + /** + * Get the version of ipfs + */ version: () => Promise path: string started: boolean initialized: boolean clean: boolean - api: IPFS - subprocess?: Subprocess | null + api: IPFSAPI + subprocess?: ExecaChildProcess | null opts: ControllerOptions apiAddr: Multiaddr peer: PeerData @@ -207,3 +228,57 @@ export interface Factory { controllers: Controller[] opts: ControllerOptions } + +export interface CreateFactory { (): Factory | Promise } + +/** + * Creates a factory + * + * @param {ControllerOptions} [options] + * @param {ControllerOptionsOverrides} [overrides] + * @returns {Factory} + */ +export const createFactory = (options?: ControllerOptions, overrides?: ControllerOptionsOverrides): Factory => { + return new DefaultFactory(options, overrides) +} + +/** + * Creates a node + */ +export const createController = async (options?: ControllerOptions): Promise => { + const f = new DefaultFactory() + return await f.spawn(options) +} + +export interface IPFSAPI extends IPFS { + apiHost?: string + apiPort?: number + gatewayHost?: string + gatewayPort?: number + grpcHost?: string + grpcPort?: number +} + +/** + * Create a Endpoint Server + * + * @param {number | { port: number }} [options] - Configuration options or just the port. + * @param {ControllerOptions} [factoryOptions] + * @param {ControllerOptionsOverrides} [factoryOverrides] + */ +export const createServer = (options?: number | { port: number }, factoryOptions: ControllerOptions = {}, factoryOverrides: ControllerOptionsOverrides = {}) => { + let port: number | undefined + + if (typeof options === 'number') { + port = options + } else if (options != null) { + port = options.port + } + + return new Server({ + port, + host: '127.0.0.1' + }, () => { + return createFactory(factoryOptions, factoryOverrides) + }) +} diff --git a/src/ipfsd-client.js b/src/ipfsd-client.ts similarity index 63% rename from src/ipfsd-client.js rename to src/ipfsd-client.ts index 438991f6..9d7c6151 100644 --- a/src/ipfsd-client.js +++ b/src/ipfsd-client.ts @@ -1,7 +1,8 @@ -import { multiaddr } from '@multiformats/multiaddr' +import { Multiaddr, multiaddr } from '@multiformats/multiaddr' import http from 'ipfs-utils/src/http.js' import mergeOptions from 'merge-options' import { logger } from '@libp2p/logger' +import type { Controller, ControllerOptions, InitOptions, IPFSAPI, PeerData, RemoteState } from './index.js' const merge = mergeOptions.bind({ ignoreUndefined: true }) @@ -10,24 +11,29 @@ const daemonLog = { err: logger('ipfsd-ctl:client:stderr') } -/** - * @typedef {import('./index').ControllerOptions} ControllerOptions - * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr - */ - /** * Controller for remote nodes - * - * @class */ -class Client { - /** - * @class - * @param {string} baseUrl - * @param {import('./types').RemoteState} remoteState - * @param {ControllerOptions} options - */ - constructor (baseUrl, remoteState, options) { +class Client implements Controller { + public path: string + // @ts-expect-error set during startup + public api: IPFSAPI + public subprocess: null + public opts: ControllerOptions + public initialized: boolean + public started: boolean + public clean: boolean + // @ts-expect-error set during startup + public apiAddr: Multiaddr + + private readonly baseUrl: string + private readonly id: string + private readonly disposable: boolean + private gatewayAddr?: Multiaddr + private grpcAddr?: Multiaddr + private _peerId: PeerData | null + + constructor (baseUrl: string, remoteState: RemoteState, options: ControllerOptions) { this.opts = options this.baseUrl = baseUrl this.id = remoteState.id @@ -36,17 +42,12 @@ class Client { this.started = remoteState.started this.disposable = remoteState.disposable this.clean = remoteState.clean - this.api = null - /** @type {import('./types').Subprocess | null} */ this.subprocess = null - /** @type {Multiaddr} */ - this.apiAddr // eslint-disable-line no-unused-expressions this._setApi(remoteState.apiAddr) this._setGateway(remoteState.gatewayAddr) this._setGrpc(remoteState.grpcAddr) this._createApi() - /** @type {import('./types').PeerData | null} */ this._peerId = null } @@ -58,88 +59,67 @@ class Client { return this._peerId } - /** - * @private - * @param {string} addr - */ - _setApi (addr) { - if (addr) { + private _setApi (addr: string): void { + if (addr != null) { this.apiAddr = multiaddr(addr) } } - /** - * @private - * @param {string} addr - */ - _setGateway (addr) { - if (addr) { + private _setGateway (addr: string): void { + if (addr != null) { this.gatewayAddr = multiaddr(addr) } } - /** - * @private - * @param {string} addr - */ - _setGrpc (addr) { - if (addr) { + private _setGrpc (addr: string): void { + if (addr != null) { this.grpcAddr = multiaddr(addr) } } - /** - * @private - */ - _createApi () { - if (this.opts.ipfsClientModule && this.grpcAddr && this.apiAddr) { + private _createApi (): void { + if (this.opts.ipfsClientModule != null && this.grpcAddr != null && this.apiAddr != null) { this.api = this.opts.ipfsClientModule.create({ grpc: this.grpcAddr, http: this.apiAddr }) - } else if (this.apiAddr) { + } else if (this.apiAddr != null) { this.api = this.opts.ipfsHttpModule.create(this.apiAddr) } - if (this.api) { - if (this.apiAddr) { + if (this.api != null) { + if (this.apiAddr != null) { this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } - if (this.gatewayAddr) { + if (this.gatewayAddr != null) { this.api.gatewayHost = this.gatewayAddr.nodeAddress().address this.api.gatewayPort = this.gatewayAddr.nodeAddress().port } - if (this.grpcAddr) { + if (this.grpcAddr != null) { this.api.grpcHost = this.grpcAddr.nodeAddress().address this.api.grpcPort = this.grpcAddr.nodeAddress().port } } } - /** - * Initialize a repo. - * - * @param {import('./types').InitOptions} [initOptions] - * @returns {Promise} - */ - async init (initOptions = {}) { + async init (initOptions: InitOptions = {}): Promise { if (this.initialized) { return this } let ipfsOptions = {} - if (this.opts.ipfsOptions != null && this.opts.ipfsOptions.init != null && !(typeof this.opts.ipfsOptions.init === 'boolean')) { + if (this.opts.ipfsOptions?.init != null && !(typeof this.opts.ipfsOptions.init === 'boolean')) { ipfsOptions = this.opts.ipfsOptions.init } const opts = merge( { emptyRepo: false, - profiles: this.opts.test ? ['test'] : [] + profiles: this.opts.test === true ? ['test'] : [] }, ipfsOptions, typeof initOptions === 'boolean' ? {} : initOptions @@ -158,14 +138,7 @@ class Client { return this } - /** - * Delete the repo that was being used. - * If the node was marked as `disposable` this will be called - * automatically when the process is exited. - * - * @returns {Promise} - */ - async cleanup () { + async cleanup (): Promise { if (this.clean) { return this } @@ -178,12 +151,7 @@ class Client { return this } - /** - * Start the daemon. - * - * @returns {Promise} - */ - async start () { + async start (): Promise { if (!this.started) { const req = await http.post( `${this.baseUrl}/start`, @@ -199,6 +167,10 @@ class Client { this.started = true } + if (this.api == null) { + throw new Error('api was not set') + } + // Add `peerId` const id = await this.api.id() this._peerId = id @@ -206,10 +178,7 @@ class Client { return this } - /** - * Stop the daemon - */ - async stop () { + async stop (): Promise { if (!this.started) { return this } @@ -227,12 +196,7 @@ class Client { return this } - /** - * Get the pid of the `ipfs daemon` process. - * - * @returns {Promise} - */ - async pid () { + async pid (): Promise { const req = await http.get( `${this.baseUrl}/pid`, { searchParams: new URLSearchParams({ id: this.id }) } @@ -242,12 +206,7 @@ class Client { return res.pid } - /** - * Get the version of ipfs - * - * @returns {Promise} - */ - async version () { + async version (): Promise { const req = await http.get( `${this.baseUrl}/version`, { searchParams: new URLSearchParams({ id: this.id }) } diff --git a/src/ipfsd-daemon.js b/src/ipfsd-daemon.ts similarity index 69% rename from src/ipfsd-daemon.js rename to src/ipfsd-daemon.ts index 835af609..915003b9 100644 --- a/src/ipfsd-daemon.js +++ b/src/ipfsd-daemon.ts @@ -1,17 +1,14 @@ -import { multiaddr } from '@multiformats/multiaddr' +import { Multiaddr, multiaddr } from '@multiformats/multiaddr' import fs from 'fs/promises' import mergeOptions from 'merge-options' import { logger } from '@libp2p/logger' -import { execa } from 'execa' +import { execa, ExecaChildProcess } from 'execa' import { nanoid } from 'nanoid' import path from 'path' import os from 'os' import { checkForRunningApi, repoExists, tmpDir, defaultRepo, buildInitArgs, buildStartArgs } from './utils.js' import waitFor from 'p-wait-for' - -/** - * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr - */ +import type { Controller, ControllerOptions, InitOptions, IPFSAPI, PeerData } from './index.js' const merge = mergeOptions.bind({ ignoreUndefined: true }) @@ -20,48 +17,44 @@ const daemonLog = { err: logger('ipfsd-ctl:daemon:stderr') } -/** - * @param {Error & { stdout: string, stderr: string }} err - */ -function translateError (err) { +function translateError (err: Error & { stdout: string, stderr: string }) { // get the actual error message to be the err.message err.message = `${err.stdout} \n\n ${err.stderr} \n\n ${err.message} \n\n` return err } -/** - * @typedef {import('./types').ControllerOptions} ControllerOptions - * @typedef {import('./types').Controller} Controller - */ - /** * Controller for daemon nodes - * - * @class - * */ -class Daemon { - /** - * @class - * @param {Required} opts - */ - constructor (opts) { +class Daemon implements Controller { + public path: string + // @ts-expect-error set during startup + public api: IPFSAPI + public subprocess?: ExecaChildProcess + public opts: ControllerOptions + public initialized: boolean + public started: boolean + public clean: boolean + // @ts-expect-error set during startup + public apiAddr: Multiaddr + + private gatewayAddr?: Multiaddr + private grpcAddr?: Multiaddr + private readonly exec?: string + private readonly env: Record + private readonly disposable: boolean + private _peerId: PeerData | null + + constructor (opts: ControllerOptions) { this.opts = opts - this.path = this.opts.ipfsOptions.repo || (opts.disposable ? tmpDir(opts.type) : defaultRepo(opts.type)) + this.path = this.opts.ipfsOptions?.repo ?? (opts.disposable === true ? tmpDir(opts.type) : defaultRepo(opts.type)) this.exec = this.opts.ipfsBin this.env = merge({ IPFS_PATH: this.path }, this.opts.env) - this.disposable = this.opts.disposable - this.subprocess = null + this.disposable = Boolean(this.opts.disposable) this.initialized = false this.started = false this.clean = true - /** @type {Multiaddr} */ - this.apiAddr // eslint-disable-line no-unused-expressions - this.grpcAddr = null - this.gatewayAddr = null - this.api = null - /** @type {import('./types').PeerData | null} */ this._peerId = null } @@ -73,67 +66,49 @@ class Daemon { return this._peerId } - /** - * @private - * @param {string} addr - */ - _setApi (addr) { + private _setApi (addr: string): void { this.apiAddr = multiaddr(addr) } - /** - * @private - * @param {string} addr - */ - _setGrpc (addr) { + private _setGrpc (addr: string): void { this.grpcAddr = multiaddr(addr) } - /** - * @private - * @param {string} addr - */ - _setGateway (addr) { + private _setGateway (addr: string): void { this.gatewayAddr = multiaddr(addr) } _createApi () { - if (this.opts.ipfsClientModule && this.grpcAddr) { + if (this.opts.ipfsClientModule != null && this.grpcAddr != null) { this.api = this.opts.ipfsClientModule.create({ grpc: this.grpcAddr, http: this.apiAddr }) - } else if (this.apiAddr) { + } else if (this.apiAddr != null) { this.api = this.opts.ipfsHttpModule.create(this.apiAddr) } - if (!this.api) { - throw new Error(`Could not create API from http '${this.apiAddr}' and/or gRPC '${this.grpcAddr}'`) + if (this.api == null) { + throw new Error(`Could not create API from http '${this.apiAddr.toString()}' and/or gRPC '${this.grpcAddr?.toString() ?? 'undefined'}'`) } - if (this.apiAddr) { + if (this.apiAddr != null) { this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } - if (this.gatewayAddr) { + if (this.gatewayAddr != null) { this.api.gatewayHost = this.gatewayAddr.nodeAddress().address this.api.gatewayPort = this.gatewayAddr.nodeAddress().port } - if (this.grpcAddr) { + if (this.grpcAddr != null) { this.api.grpcHost = this.grpcAddr.nodeAddress().address this.api.grpcPort = this.grpcAddr.nodeAddress().port } } - /** - * Initialize a repo. - * - * @param {import('./types').InitOptions} [initOptions={}] - * @returns {Promise} - */ - async init (initOptions = {}) { + async init (initOptions: InitOptions = {}): Promise { this.initialized = await repoExists(this.path) if (this.initialized) { this.clean = false @@ -142,9 +117,9 @@ class Daemon { initOptions = merge({ emptyRepo: false, - profiles: this.opts.test ? ['test'] : [] + profiles: this.opts.test === true ? ['test'] : [] }, - typeof this.opts.ipfsOptions.init === 'boolean' ? {} : this.opts.ipfsOptions.init, + typeof this.opts.ipfsOptions?.init === 'boolean' ? {} : this.opts.ipfsOptions?.init, typeof initOptions === 'boolean' ? {} : initOptions ) @@ -158,6 +133,10 @@ class Daemon { const args = buildInitArgs(opts) + if (this.exec == null) { + throw new Error('No executable specified') + } + const { stdout, stderr } = await execa(this.exec, args, { env: this.env }) @@ -170,7 +149,7 @@ class Daemon { if (this.opts.type === 'go') { await this._replaceConfig(merge( await this._getConfig(), - this.opts.ipfsOptions.config + this.opts.ipfsOptions?.config )) } @@ -204,10 +183,10 @@ class Daemon { // Check if a daemon is already running const api = checkForRunningApi(this.path) - if (api) { + if (api != null) { this._setApi(api) this._createApi() - } else if (!this.exec) { + } else if (this.exec == null) { throw new Error('No executable specified') } else { const args = buildStartArgs(this.opts) @@ -215,45 +194,46 @@ class Daemon { let output = '' const ready = new Promise((resolve, reject) => { + if (this.exec == null) { + return reject(new Error('No executable specified')) + } + this.subprocess = execa(this.exec, args, { env: this.env }) const { stdout, stderr } = this.subprocess - if (!stderr) { + if (stderr == null) { throw new Error('stderr was not defined on subprocess') } - if (!stdout) { + if (stdout == null) { throw new Error('stderr was not defined on subprocess') } stderr.on('data', data => daemonLog.err(data.toString())) stdout.on('data', data => daemonLog.info(data.toString())) - /** - * @param {Buffer} data - */ - const readyHandler = data => { + const readyHandler = (data: Buffer) => { output += data.toString() const apiMatch = output.trim().match(/API .*listening on:? (.*)/) const gwMatch = output.trim().match(/Gateway .*listening on:? (.*)/) const grpcMatch = output.trim().match(/gRPC .*listening on:? (.*)/) - if (apiMatch && apiMatch.length > 0) { + if ((apiMatch != null) && apiMatch.length > 0) { this._setApi(apiMatch[1]) } - if (gwMatch && gwMatch.length > 0) { + if ((gwMatch != null) && gwMatch.length > 0) { this._setGateway(gwMatch[1]) } - if (grpcMatch && grpcMatch.length > 0) { + if ((grpcMatch != null) && grpcMatch.length > 0) { this._setGrpc(grpcMatch[1]) } - if (output.match(/(?:daemon is running|Daemon is ready)/)) { + if (output.match(/(?:daemon is running|Daemon is ready)/) != null) { // we're good this._createApi() this.started = true @@ -263,7 +243,7 @@ class Daemon { } stdout.on('data', readyHandler) this.subprocess.catch(err => reject(translateError(err))) - this.subprocess.on('exit', () => { + void this.subprocess.on('exit', () => { this.started = false stderr.removeAllListeners() stdout.removeAllListeners() @@ -285,21 +265,14 @@ class Daemon { return this } - /** - * Stop the daemon. - * - * @param {object} [options] - * @param {number} [options.timeout=60000] - How long to wait for the daemon to stop - * @returns {Promise} - */ - async stop (options = {}) { - const timeout = options.timeout || 60000 + async stop (options: { timeout?: number } = {}): Promise { + const timeout = options.timeout ?? 60000 if (!this.started) { return this } - if (this.subprocess) { + if (this.subprocess != null) { /** @type {ReturnType | undefined} */ let killTimeout const subprocess = this.subprocess @@ -312,8 +285,8 @@ class Daemon { if (this.opts.forceKill !== false) { killTimeout = setTimeout(() => { // eslint-disable-next-line no-console - console.error(new Error(`Timeout stopping ${this.opts.type} node after ${this.opts.forceKillTimeout}ms. Process ${subprocess.pid} will be force killed now.`)) - this.subprocess && this.subprocess.kill('SIGKILL') + console.error(new Error(`Timeout stopping ${this.opts.type ?? 'unknown'} node after ${this.opts.forceKillTimeout ?? 'unknown'}ms. Process ${subprocess.pid ?? 'unknown'} will be force killed now.`)) + this.subprocess?.kill('SIGKILL') }, this.opts.forceKillTimeout) } @@ -325,7 +298,7 @@ class Daemon { timeout }) - if (killTimeout) { + if (killTimeout != null) { clearTimeout(killTimeout) } @@ -349,9 +322,9 @@ class Daemon { * * @returns {Promise} */ - pid () { - if (this.subprocess && this.subprocess.pid != null) { - return Promise.resolve(this.subprocess.pid) + async pid () { + if (this.subprocess?.pid != null) { + return await Promise.resolve(this.subprocess?.pid) } throw new Error('Daemon process is not running.') } @@ -366,6 +339,10 @@ class Daemon { * @returns {Promise} */ async _getConfig (key = 'show') { + if (this.exec == null) { + throw new Error('No executable specified') + } + const { stdout } = await execa( @@ -385,12 +362,12 @@ class Daemon { /** * Replace the current config with the provided one - * - * @private - * @param {object} config - * @returns {Promise} */ - async _replaceConfig (config) { + private async _replaceConfig (config: any): Promise { + if (this.exec == null) { + throw new Error('No executable specified') + } + const tmpFile = path.join(os.tmpdir(), nanoid()) await fs.writeFile(tmpFile, JSON.stringify(config)) @@ -405,12 +382,11 @@ class Daemon { return this } - /** - * Get the version of ipfs - * - * @returns {Promise} - */ - async version () { + async version (): Promise { + if (this.exec == null) { + throw new Error('No executable specified') + } + const { stdout } = await execa(this.exec, ['version'], { diff --git a/src/ipfsd-in-proc.js b/src/ipfsd-in-proc.ts similarity index 56% rename from src/ipfsd-in-proc.js rename to src/ipfsd-in-proc.ts index c2ee70ce..50404f62 100644 --- a/src/ipfsd-in-proc.js +++ b/src/ipfsd-in-proc.ts @@ -1,7 +1,8 @@ -import { multiaddr } from '@multiformats/multiaddr' +import { Multiaddr, multiaddr } from '@multiformats/multiaddr' import mergeOptions from 'merge-options' import { repoExists, removeRepo, checkForRunningApi, tmpDir, defaultRepo } from './utils.js' import { logger } from '@libp2p/logger' +import type { Controller, ControllerOptions, InitOptions, IPFSAPI, PeerData } from './index.js' const merge = mergeOptions.bind({ ignoreUndefined: true }) @@ -9,33 +10,35 @@ const daemonLog = { info: logger('ipfsd-ctl:proc:stdout'), err: logger('ipfsd-ctl:proc:stderr') } -/** - * @typedef {import('./types').ControllerOptions} ControllerOptions - * @typedef {import('./types').InitOptions} InitOptions - * @typedef {import('@multiformats/multiaddr').Multiaddr} Multiaddr - */ /** * Controller for in process nodes */ -class InProc { - /** - * @param {Required} opts - */ - constructor (opts) { +class InProc implements Controller { + public path: string + // @ts-expect-error set during startup + public api: IPFSAPI + public subprocess: null + public opts: ControllerOptions + public initialized: boolean + public started: boolean + public clean: boolean + // @ts-expect-error set during startup + public apiAddr: Multiaddr + + private initOptions: InitOptions + private readonly disposable: boolean + private _peerId: PeerData | null + + constructor (opts: ControllerOptions) { this.opts = opts - this.path = this.opts.ipfsOptions.repo || (opts.disposable ? tmpDir(opts.type) : defaultRepo(opts.type)) - this.initOptions = toInitOptions(opts.ipfsOptions.init) - this.disposable = opts.disposable + this.path = this.opts.ipfsOptions?.repo ?? (opts.disposable === true ? tmpDir(opts.type) : defaultRepo(opts.type)) + this.initOptions = toInitOptions(opts.ipfsOptions?.init) + this.disposable = Boolean(opts.disposable) this.initialized = false this.started = false this.clean = true - /** @type {Multiaddr} */ - this.apiAddr // eslint-disable-line no-unused-expressions - this.api = null - /** @type {import('./types').Subprocess | null} */ this.subprocess = null - /** @type {import('./types').PeerData | null} */ this._peerId = null } @@ -48,7 +51,7 @@ class InProc { } async setExec () { - if (this.api !== null) { + if (this.api != null) { return } @@ -62,24 +65,14 @@ class InProc { }) } - /** - * @private - * @param {string} addr - */ - _setApi (addr) { + private _setApi (addr: string): void { this.apiAddr = multiaddr(addr) this.api = this.opts.ipfsHttpModule.create(addr) this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } - /** - * Initialize a repo. - * - * @param {import('./types').InitOptions} [initOptions={}] - * @returns {Promise} - */ - async init (initOptions = {}) { + async init (initOptions: InitOptions = {}): Promise { this.initialized = await repoExists(this.path) if (this.initialized) { this.clean = false @@ -90,7 +83,7 @@ class InProc { this.initOptions = merge( { emptyRepo: false, - profiles: this.opts.test ? ['test'] : [] + profiles: this.opts.test === true ? ['test'] : [] }, this.initOptions, toInitOptions(initOptions) @@ -102,14 +95,7 @@ class InProc { return this } - /** - * Delete the repo that was being used. - * If the node was marked as `disposable` this will be called - * automatically when the process is exited. - * - * @returns {Promise} - */ - async cleanup () { + async cleanup (): Promise { if (!this.clean) { await removeRepo(this.path) this.clean = true @@ -117,15 +103,10 @@ class InProc { return this } - /** - * Start the daemon. - * - * @returns {Promise} - */ - async start () { + async start (): Promise { // Check if a daemon is already running const api = checkForRunningApi(this.path) - if (api) { + if (api != null) { this._setApi(api) } else { await this.setExec() @@ -140,12 +121,7 @@ class InProc { return this } - /** - * Stop the daemon. - * - * @returns {Promise} - */ - async stop () { + async stop (): Promise { if (!this.started) { return this } @@ -161,19 +137,12 @@ class InProc { /** * Get the pid of the `ipfs daemon` process - * - * @returns {Promise} */ - pid () { - return Promise.reject(new Error('not implemented')) + async pid (): Promise { + return await Promise.reject(new Error('not implemented')) } - /** - * Get the version of ipfs - * - * @returns {Promise} - */ - async version () { + async version (): Promise { await this.setExec() const { version } = await this.api.version() @@ -182,10 +151,7 @@ class InProc { } } -/** - * @param {boolean | InitOptions} [init] - */ -const toInitOptions = (init = {}) => +const toInitOptions = (init: boolean | InitOptions = {}): InitOptions => typeof init === 'boolean' ? {} : init export default InProc diff --git a/src/utils.browser.js b/src/utils.browser.ts similarity index 65% rename from src/utils.browser.js rename to src/utils.browser.ts index caf17ca3..ab4086e2 100644 --- a/src/utils.browser.js +++ b/src/utils.browser.ts @@ -1,11 +1,7 @@ import { nanoid } from 'nanoid' -/** - * @param {string} path - * @returns {Promise} - */ -const deleteDb = (path) => { - return new Promise((resolve, reject) => { +const deleteDb = async (path: string): Promise => { + return await new Promise((resolve, reject) => { const keys = self.indexedDB.deleteDatabase(path) keys.onerror = (err) => reject(err) keys.onsuccess = () => resolve() @@ -14,10 +10,8 @@ const deleteDb = (path) => { /** * close repoPath , repoPath/keys, repoPath/blocks and repoPath/datastore - * - * @param {string} repoPath */ -export const removeRepo = async (repoPath) => { +export const removeRepo = async (repoPath: string): Promise => { await deleteDb(repoPath) await deleteDb(repoPath + '/keys') await deleteDb(repoPath + '/blocks') @@ -27,8 +21,8 @@ export const removeRepo = async (repoPath) => { /** * @param {string} repoPath */ -export const repoExists = (repoPath) => { - return new Promise((resolve, reject) => { +export const repoExists = async (repoPath: string): Promise => { + return await new Promise((resolve, reject) => { const req = self.indexedDB.open(repoPath) let existed = true req.onerror = () => reject(req.error) @@ -43,14 +37,14 @@ export const repoExists = (repoPath) => { }) } -export const defaultRepo = () => { +export const defaultRepo = (): string => { return 'ipfs' } -export const checkForRunningApi = () => { +export const checkForRunningApi = (): string | null => { return null } -export const tmpDir = (type = '') => { +export const tmpDir = (type = ''): string => { return `${type}_ipfs_${nanoid()}` } diff --git a/src/utils.js b/src/utils.ts similarity index 52% rename from src/utils.js rename to src/utils.ts index 2688f037..12f8ae76 100644 --- a/src/utils.js +++ b/src/utils.ts @@ -4,33 +4,25 @@ import fs from 'fs' import { logger } from '@libp2p/logger' import { nanoid } from 'nanoid' import tempWrite from 'temp-write' +import type { ControllerOptions, IPFSOptions, NodeType } from './index.js' const log = logger('ipfsd-ctl:utils') -/** - * @param {string} repoPath - */ -export const removeRepo = async (repoPath) => { +export const removeRepo = async (repoPath: string): Promise => { try { await fs.promises.rm(repoPath, { recursive: true }) - } catch (/** @type {any} */ err) { + } catch (err: any) { // ignore } } -/** - * @param {string} repoPath - */ -export const repoExists = (repoPath) => { - return Promise.resolve(fs.existsSync(path.join(repoPath, 'config'))) +export const repoExists = async (repoPath: string): Promise => { + return await Promise.resolve(fs.existsSync(path.join(repoPath, 'config'))) } -/** - * @param {import('./types').NodeType} [type] - */ -export const defaultRepo = (type) => { +export const defaultRepo = (type?: NodeType): string => { if (process.env.IPFS_PATH !== undefined) { return process.env.IPFS_PATH } @@ -40,74 +32,65 @@ export const defaultRepo = (type) => { ) } -/** - * @param {string} [repoPath] - */ -export const checkForRunningApi = (repoPath = '') => { +export const checkForRunningApi = (repoPath = ''): string | null => { let api try { api = fs.readFileSync(path.join(repoPath, 'api')) - } catch (/** @type {any} */ err) { + } catch (err: any) { log('Unable to open api file') } - return api ? api.toString() : null + return (api != null) ? api.toString() : null } -export const tmpDir = (type = '') => { +export const tmpDir = (type = ''): string => { return path.join(os.tmpdir(), `${type}_ipfs_${nanoid()}`) } -/** - * @param {import('./types').ControllerOptions} opts - */ -export function buildInitArgs (opts = {}) { +export function buildInitArgs (opts: ControllerOptions = {}): string[] { const args = ['init'] - const ipfsOptions = opts.ipfsOptions || {} - const initOptions = ipfsOptions.init && typeof ipfsOptions.init !== 'boolean' ? ipfsOptions.init : {} + const ipfsOptions: IPFSOptions = opts.ipfsOptions ?? {} + const initOptions = ipfsOptions.init != null && typeof ipfsOptions.init !== 'boolean' ? ipfsOptions.init : {} // default-config only for JS if (opts.type === 'js') { - if (ipfsOptions.config) { + if (ipfsOptions.config != null) { args.push(tempWrite.sync(JSON.stringify(ipfsOptions.config))) } - if (initOptions.pass) { - args.push('--pass', '"' + initOptions.pass + '"') + if (initOptions.pass != null) { + args.push('--pass', `"${initOptions.pass}"`) } } // Translate IPFS options to cli args - if (initOptions.bits) { + if (initOptions.bits != null) { args.push('--bits', `${initOptions.bits}`) } - if (initOptions.algorithm) { + if (initOptions.algorithm != null) { args.push('--algorithm', initOptions.algorithm) } - if (initOptions.emptyRepo) { + if (initOptions.emptyRepo === true) { args.push('--empty-repo') } - if (Array.isArray(initOptions.profiles) && initOptions.profiles.length) { + if (Array.isArray(initOptions.profiles) && initOptions.profiles.length > 0) { args.push('--profile', initOptions.profiles.join(',')) } return args } -/** - * @param {import('./types').ControllerOptions} opts - */ -export function buildStartArgs (opts = {}) { - const ipfsOptions = opts.ipfsOptions || {} - const customArgs = opts.args || [] +export function buildStartArgs (opts: ControllerOptions = {}): string[] { + const ipfsOptions: IPFSOptions = opts.ipfsOptions ?? {} + const customArgs: string[] = opts.args ?? [] const args = ['daemon'].concat(customArgs) if (opts.type === 'js') { - if (ipfsOptions.pass) { + if (ipfsOptions.pass != null) { args.push('--pass', '"' + ipfsOptions.pass + '"') } @@ -115,20 +98,20 @@ export function buildStartArgs (opts = {}) { args.push('--enable-preload', Boolean(typeof ipfsOptions.preload === 'boolean' ? ipfsOptions.preload : ipfsOptions.preload.enabled).toString()) } - if (ipfsOptions.EXPERIMENTAL && ipfsOptions.EXPERIMENTAL.sharding) { + if (ipfsOptions.EXPERIMENTAL?.sharding === true) { args.push('--enable-sharding-experiment') } } - if (ipfsOptions.offline) { + if (ipfsOptions.offline === true) { args.push('--offline') } - if (ipfsOptions.EXPERIMENTAL && ipfsOptions.EXPERIMENTAL.ipnsPubsub) { + if ((ipfsOptions.EXPERIMENTAL != null) && ipfsOptions.EXPERIMENTAL.ipnsPubsub === true) { args.push('--enable-namesys-pubsub') } - if (ipfsOptions.repoAutoMigrate) { + if (ipfsOptions.repoAutoMigrate === true) { args.push('--migrate') } diff --git a/test/browser.js b/test/browser.ts similarity index 100% rename from test/browser.js rename to test/browser.ts diff --git a/test/browser.utils.js b/test/browser.utils.ts similarity index 100% rename from test/browser.utils.js rename to test/browser.utils.ts diff --git a/test/controller.spec.js b/test/controller.spec.ts similarity index 92% rename from test/controller.spec.js rename to test/controller.spec.ts index 80b539b2..c54d497e 100644 --- a/test/controller.spec.js +++ b/test/controller.spec.ts @@ -1,24 +1,19 @@ /* eslint-env mocha */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable no-loop-func */ import { expect } from 'aegir/chai' import merge from 'merge-options' -import { createFactory, createController } from '../src/index.js' +import { createFactory, createController, ControllerOptions, Factory } from '../src/index.js' import { repoExists } from '../src/utils.js' import { isBrowser, isWebWorker, isNode } from 'wherearewe' import waitFor from 'p-wait-for' import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' -// @ts-ignore no types +// @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' -/** - * @typedef {import('../src/types').ControllerOptions} ControllerOptions - */ - -/** - * @type {ControllerOptions[]} - */ -const types = [{ +const types: ControllerOptions[] = [{ type: 'js', ipfsOptions: { init: false, @@ -52,25 +47,29 @@ const types = [{ } }] -describe('Controller API', async function () { +describe('Controller API', function () { this.timeout(60000) - const factory = createFactory({ - test: true, - ipfsHttpModule, - ipfsModule: (await import('ipfs')) - }, { - js: { - ipfsBin: isNode ? ipfsModule.path() : undefined - }, - go: { - ipfsBin: isNode ? goIpfsModule.path() : undefined - } - }) + let factory: Factory + + before(async () => { + factory = createFactory({ + test: true, + ipfsHttpModule, + ipfsModule: (await import('ipfs')) + }, { + js: { + ipfsBin: isNode ? ipfsModule.path() : undefined + }, + go: { + ipfsBin: isNode ? goIpfsModule.path() : undefined + } + }) - before(() => factory.spawn({ type: 'js' })) + await factory.spawn({ type: 'js' }) + }) - after(() => factory.clean()) + after(async () => await factory.clean()) describe('init', () => { describe('should work with defaults', () => { @@ -335,10 +334,10 @@ describe('Controller API', async function () { await ctl.init() await ctl.start() await ctl.stop() - if (ctl.subprocess && ctl.subprocess.stderr) { + if (ctl.subprocess?.stderr != null) { expect(ctl.subprocess.stderr.listeners('data')).to.be.empty() } - if (ctl.subprocess && ctl.subprocess.stdout) { + if (ctl.subprocess?.stdout != null) { expect(ctl.subprocess.stdout.listeners('data')).to.be.empty() } }) diff --git a/test/create.spec.js b/test/create.spec.ts similarity index 89% rename from test/create.spec.js rename to test/create.spec.ts index e955b186..6fcc4484 100644 --- a/test/create.spec.js +++ b/test/create.spec.ts @@ -1,22 +1,19 @@ /* eslint-env mocha */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ import { expect } from 'aegir/chai' import { isNode, isBrowser, isWebWorker } from 'wherearewe' -import { createFactory, createController, createServer } from '../src/index.js' +import { createFactory, createController, createServer, ControllerOptions } from '../src/index.js' import Client from '../src/ipfsd-client.js' import Daemon from '../src/ipfsd-daemon.js' import Proc from '../src/ipfsd-in-proc.js' import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' -// @ts-ignore no types +// @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' import * as ipfsClientModule from 'ipfs-client' -/** - * @typedef {import('../src/types').ControllerOptions} ControllerOptions - */ - -describe('`createController` should return the correct class', async () => { +describe('`createController` should return the correct class', () => { it('for type `js` ', async () => { const f = await createController({ type: 'js', @@ -73,20 +70,14 @@ describe('`createController` should return the correct class', async () => { disposable: false, ipfsModule, ipfsClientModule: { - /** - * @param {any} opts - */ - create: (opts) => { + create: (opts: any) => { clientCreated = true return ipfsClientModule.create(opts) } }, ipfsHttpModule: { - /** - * @param {any} opts - */ - create: async (opts) => { + create: async (opts: any) => { httpCreated = true return ipfsHttpModule.create(opts) @@ -108,20 +99,14 @@ describe('`createController` should return the correct class', async () => { disposable: false, ipfsModule, ipfsClientModule: { - /** - * @param {any} opts - */ - create: (opts) => { + create: (opts: any) => { clientCreated = true return ipfsClientModule.create(opts) } }, ipfsHttpModule: { - /** - * @param {any} opts - */ - create: async (opts) => { + create: async (opts: any) => { httpCreated = true return ipfsHttpModule.create(opts) @@ -140,8 +125,7 @@ const defaultOps = { ipfsHttpModule } -/** @type {ControllerOptions[]} */ -const types = [{ +const types: ControllerOptions[] = [{ ...defaultOps, type: 'js', test: true, diff --git a/test/factory.spec.js b/test/factory.spec.ts similarity index 95% rename from test/factory.spec.js rename to test/factory.spec.ts index cc6f38ea..7a1f5f18 100644 --- a/test/factory.spec.js +++ b/test/factory.spec.ts @@ -1,10 +1,11 @@ /* eslint-env mocha */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ import { expect } from 'aegir/chai' import { isNode } from 'wherearewe' -import { createFactory } from '../src/index.js' +import { ControllerOptions, createFactory } from '../src/index.js' import * as ipfsModule from 'ipfs' -// @ts-ignore no types +// @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' import * as ipfsHttpModule from 'ipfs-http-client' @@ -12,14 +13,7 @@ const defaultOps = { ipfsHttpModule } -/** - * @typedef {import('../src/types').ControllerOptions} ControllerOptions - */ - -/** - * @type {ControllerOptions[]} - */ -const types = [{ +const types: ControllerOptions[] = [{ ...defaultOps, type: 'js', test: true, diff --git a/test/node.routes.js b/test/node.routes.ts similarity index 96% rename from test/node.routes.js rename to test/node.routes.ts index 8b475b06..fef9ef3d 100644 --- a/test/node.routes.js +++ b/test/node.routes.ts @@ -10,14 +10,8 @@ import * as ipfsHttpModule from 'ipfs-http-client' describe('routes', function () { this.timeout(60000) - /** - * @type {string} - */ - let id - /** - * @type {Hapi.Server} - */ - let server + let id: string + let server: Hapi.Server before(async () => { server = new Hapi.Server({ port: 43134 }) @@ -52,7 +46,7 @@ describe('routes', function () { expect(res).to.have.nested.property('result.apiAddr') expect(res).to.have.nested.property('result.gatewayAddr') - // @ts-ignore res.result is an object + // @ts-expect-error res.result is an object id = res.result.id }) }) diff --git a/test/node.js b/test/node.ts similarity index 84% rename from test/node.js rename to test/node.ts index 012c72e2..618ea9c5 100644 --- a/test/node.js +++ b/test/node.ts @@ -1,16 +1,17 @@ /* eslint-env mocha */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ import { expect } from 'aegir/chai' import { createFactory } from '../src/index.js' import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' -// @ts-ignore no types +// @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' import './node.routes.js' import './node.utils.js' -describe('Node specific tests', async function () { +describe('Node specific tests', function () { this.timeout(60000) const factory = createFactory({ diff --git a/test/node.utils.js b/test/node.utils.ts similarity index 100% rename from test/node.utils.js rename to test/node.utils.ts diff --git a/tsconfig.json b/tsconfig.json index 8708ca6c..13a35996 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,7 @@ { "extends": "aegir/src/config/tsconfig.aegir.json", "compilerOptions": { - "outDir": "dist", - "emitDeclarationOnly": true + "outDir": "dist" }, "include": [ "src", From 4862ef287694acaffaa7ab37547fccf05674ae67 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 4 Oct 2022 13:02:09 +0000 Subject: [PATCH 4/7] chore(release): 12.1.0 [skip ci] ## [12.1.0](https://github.com/ipfs/js-ipfsd-ctl/compare/v12.0.3...v12.1.0) (2022-10-04) ### Features * convert to typescript ([#777](https://github.com/ipfs/js-ipfsd-ctl/issues/777)) ([ee3a35a](https://github.com/ipfs/js-ipfsd-ctl/commit/ee3a35a28c7a435c49da02d4e03d29c4520e5b55)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03ea812e..6232858f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [12.1.0](https://github.com/ipfs/js-ipfsd-ctl/compare/v12.0.3...v12.1.0) (2022-10-04) + + +### Features + +* convert to typescript ([#777](https://github.com/ipfs/js-ipfsd-ctl/issues/777)) ([ee3a35a](https://github.com/ipfs/js-ipfsd-ctl/commit/ee3a35a28c7a435c49da02d4e03d29c4520e5b55)) + ## [12.0.3](https://github.com/ipfs/js-ipfsd-ctl/compare/v12.0.2...v12.0.3) (2022-09-21) diff --git a/package.json b/package.json index 6ea9038b..3ad14a3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipfsd-ctl", - "version": "12.0.3", + "version": "12.1.0", "description": "Spawn IPFS Daemons, JS or Go", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/ipfs/js-ipfsd-ctl#readme", From ecdda5547491e9826568bb8c3181121de38b5362 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 5 Oct 2022 09:05:00 +0100 Subject: [PATCH 5/7] fix: update export path to point to correct file (#778) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3ad14a3c..c078ff01 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "exports": { ".": { "types": "./dist/src/index.d.ts", - "import": "./src/index.js" + "import": "./dist/src/index.js" } }, "eslintConfig": { From 536d683dfa8db03baf859586cdc8c6e02ba5987a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 5 Oct 2022 08:23:42 +0000 Subject: [PATCH 6/7] chore(release): 12.1.1 [skip ci] ## [12.1.1](https://github.com/ipfs/js-ipfsd-ctl/compare/v12.1.0...v12.1.1) (2022-10-05) ### Bug Fixes * update export path to point to correct file ([#778](https://github.com/ipfs/js-ipfsd-ctl/issues/778)) ([ecdda55](https://github.com/ipfs/js-ipfsd-ctl/commit/ecdda5547491e9826568bb8c3181121de38b5362)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6232858f..b6391b5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [12.1.1](https://github.com/ipfs/js-ipfsd-ctl/compare/v12.1.0...v12.1.1) (2022-10-05) + + +### Bug Fixes + +* update export path to point to correct file ([#778](https://github.com/ipfs/js-ipfsd-ctl/issues/778)) ([ecdda55](https://github.com/ipfs/js-ipfsd-ctl/commit/ecdda5547491e9826568bb8c3181121de38b5362)) + ## [12.1.0](https://github.com/ipfs/js-ipfsd-ctl/compare/v12.0.3...v12.1.0) (2022-10-04) diff --git a/package.json b/package.json index c078ff01..cb668316 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipfsd-ctl", - "version": "12.1.0", + "version": "12.1.1", "description": "Spawn IPFS Daemons, JS or Go", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/ipfs/js-ipfsd-ctl#readme", From 9538c1d502b0dcb815d8238ed4498542bbf03867 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Wed, 5 Oct 2022 12:22:56 -0700 Subject: [PATCH 7/7] feat: add js-kubo-rpc-client (#764) * feat: add js-kubo-rpc-client see https://github.com/ipfs/kubo/issues/9125 * chore: update kubo-rpc-client package name * fix: use cross-env to set USE_KUBO_JS * fix: Remove USE_KUBO_JS env var * chore(deps): remove cross-env * docs(readme): update npm install cmds for using kubo-rpc-client * chore: log whether kubo-rpc-client or ipfs-http-client is in use * chore(tests): use kubo-rpc-client for commmunication with go controllers * chore(lint): fix lint failure * fix: addCorrectRpcModule returns the additionalOpts object * docs(readme): correct kubo-rpc-client name Co-authored-by: Alex Potsides * fix: use correct logger namespace convention * fix: move kubo-rpc-client to devDeps * fix: support generics for Controller type * chore(deps): use kubo-rpc-client@1.0.1 * feat!: convert to typescript Converts this module to typescript * chore: fix linting * chore: fix tests * chore: apply suggestions from code review * fix: resolve issues with js-kubo-rpc-client in typescript * fix: use kuboRpcModule for go in factory.spec.ts * fix: broken build from bad merge * chore: address PR comments Co-authored-by: Alex Potsides --- .aegir.js | 8 +++++--- README.md | 10 ++++------ package.json | 1 + src/config.ts | 4 ++-- src/factory.ts | 3 ++- src/index.ts | 36 ++++++++++++++++++++---------------- src/ipfsd-client.ts | 11 ++++++++++- src/ipfsd-daemon.ts | 11 ++++++++++- src/ipfsd-in-proc.ts | 13 ++++++++++++- src/utils.ts | 4 ++-- test/controller.spec.ts | 35 +++++++++++++++++++++++++---------- test/create.spec.ts | 17 +++++++---------- test/factory.spec.ts | 15 ++++++--------- 13 files changed, 106 insertions(+), 62 deletions(-) diff --git a/.aegir.js b/.aegir.js index f94961d0..9aa1d126 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,5 +1,6 @@ import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' +import * as kuboRpcModule from 'kubo-rpc-client' import * as goIpfsModule from 'go-ipfs' /** @type {import('aegir').Options["build"]["config"]} */ @@ -13,13 +14,14 @@ const config = { const server = createServer(undefined, { ipfsModule, - ipfsHttpModule }, { go: { - ipfsBin: goIpfsModule.path() + ipfsBin: goIpfsModule.path(), + kuboRpcModule }, js: { - ipfsBin: ipfsModule.path() + ipfsBin: ipfsModule.path(), + ipfsHttpModule } } ) diff --git a/README.md b/README.md index ba5bc188..a4feed0f 100644 --- a/README.md +++ b/README.md @@ -61,17 +61,15 @@ $ npm i ipfsd-ctl Version 1.0.0 changed a bit the api and the options methods take so please read the documentation below. -Please ensure your project also has dependencies on `ipfs`, `ipfs-http-client` and `go-ipfs`. +Please ensure your project also has dependencies on `ipfs`, `ipfs-http-client`, `kubo-rpc-client`, and `go-ipfs`. ```sh -npm install --save ipfs -npm install --save ipfs-http-client -npm install --save go-ipfs +npm install --save ipfs ipfs-http-client go-ipfs kubo-rpc-client ``` -If you are only going to use the `go` implementation of IPFS, you can skip installing the `js` implementation and vice versa, though both will require the `ipfs-http-client` module. +If you are only going to use the `go` implementation of IPFS, you can skip installing the `js` implementation and `ipfs-http-client` module. (e.g. `npm i --save go-ipfs kubo-rpc-client`) -If you are only using the `proc` type in-process IPFS node, you can skip installing `go-ipfs` and `ipfs-http-client`. +If you are only using the `proc` type in-process IPFS node, you can skip installing `go-ipfs` and `ipfs-http-client`. (e.g. `npm i --save ipfs`) > You also need to explicitly defined the options `ipfsBin`, `ipfsModule` and `ipfsHttpModule` according to your needs. Check [ControllerOptions](#controlleroptions) and [ControllerOptionsOverrides](#controlleroptionsoverrides) for more information. diff --git a/package.json b/package.json index cb668316..8289afb7 100644 --- a/package.json +++ b/package.json @@ -159,6 +159,7 @@ "ipfs-client": "^0.9.0", "ipfs-core-types": "^0.12.0", "ipfs-http-client": "^58.0.0", + "kubo-rpc-client": "^1.0.1", "util": "^0.12.4" }, "browser": { diff --git a/src/config.ts b/src/config.ts index 52bd4104..d0672e73 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,8 +1,8 @@ import { isBrowser, isWebWorker } from 'wherearewe' -import type { NodeType } from './index.js' +import type { ControllerType } from './index.js' export interface ConfigInit { - type?: NodeType + type?: ControllerType } export default (init: ConfigInit) => { diff --git a/src/factory.ts b/src/factory.ts index 1621efb7..1b98bf13 100644 --- a/src/factory.ts +++ b/src/factory.ts @@ -82,7 +82,8 @@ class DefaultFactory implements Factory { remote: false, ipfsBin: undefined, ipfsModule: undefined, - ipfsHttpModule: undefined + ipfsHttpModule: undefined, + kuboRpcModule: undefined } } diff --git a/src/index.ts b/src/index.ts index 9ea81245..6cde7e7e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,28 +10,30 @@ export interface PeerData { addresses: Multiaddr[] } -export interface Controller { +export type ControllerType = 'js' | 'go' | 'proc' + +export interface Controller { /** * Initialize a repo */ - init: (options?: InitOptions) => Promise + init: (options?: InitOptions) => Promise> /** * Start the daemon */ - start: () => Promise + start: () => Promise> /** * Stop the daemon */ - stop: () => Promise + stop: () => Promise> /** * Delete the repo that was being used. * If the node was marked as `disposable` this will be called * automatically when the process is exited. */ - cleanup: () => Promise + cleanup: () => Promise> /** * Get the pid of the `ipfs daemon` process @@ -65,8 +67,6 @@ export interface RemoteState { grpcAddr: string } -export type NodeType = 'js' | 'go' | 'proc' - export interface InitOptions { pass?: string bits?: number @@ -156,7 +156,7 @@ export interface IPFSOptions { repoAutoMigrate?: boolean } -export interface ControllerOptions { +export interface ControllerOptions { /** * Flag to activate custom config for tests */ @@ -176,7 +176,7 @@ export interface ControllerOptions { /** * The daemon type */ - type?: NodeType + type?: Type /** * Additional environment variables, passed to executing shell. Only applies for Daemon controllers */ @@ -189,6 +189,10 @@ export interface ControllerOptions { * Reference to an ipfs-http-client module */ ipfsHttpModule?: any + /** + * Reference to a kubo-rpc-client module + */ + kuboRpcModule?: any /** * Reference to an ipfs or ipfs-core module */ @@ -216,17 +220,17 @@ export interface ControllerOptions { } export interface ControllerOptionsOverrides { - js?: ControllerOptions - go?: ControllerOptions - proc?: ControllerOptions + js?: ControllerOptions<'js'> + go?: ControllerOptions<'go'> + proc?: ControllerOptions<'proc'> } -export interface Factory { +export interface Factory { tmpDir: (options?: ControllerOptions) => Promise - spawn: (options?: ControllerOptions) => Promise + spawn: (options?: ControllerOptions) => Promise> clean: () => Promise - controllers: Controller[] - opts: ControllerOptions + controllers: Array> + opts: ControllerOptions } export interface CreateFactory { (): Factory | Promise } diff --git a/src/ipfsd-client.ts b/src/ipfsd-client.ts index 9d7c6151..7f75ced3 100644 --- a/src/ipfsd-client.ts +++ b/src/ipfsd-client.ts @@ -10,6 +10,7 @@ const daemonLog = { info: logger('ipfsd-ctl:client:stdout'), err: logger('ipfsd-ctl:client:stderr') } +const rpcModuleLogger = logger('ipfsd-ctl:client') /** * Controller for remote nodes @@ -84,7 +85,15 @@ class Client implements Controller { http: this.apiAddr }) } else if (this.apiAddr != null) { - this.api = this.opts.ipfsHttpModule.create(this.apiAddr) + if (this.opts.kuboRpcModule != null) { + rpcModuleLogger('Using kubo-rpc-client') + this.api = this.opts.kuboRpcModule.create(this.apiAddr) + } else if (this.opts.ipfsHttpModule != null) { + rpcModuleLogger('Using ipfs-http-client') + this.api = this.opts.ipfsHttpModule.create(this.apiAddr) + } else { + throw new Error('You must pass either a kuboRpcModule or ipfsHttpModule') + } } if (this.api != null) { diff --git a/src/ipfsd-daemon.ts b/src/ipfsd-daemon.ts index 915003b9..25977127 100644 --- a/src/ipfsd-daemon.ts +++ b/src/ipfsd-daemon.ts @@ -16,6 +16,7 @@ const daemonLog = { info: logger('ipfsd-ctl:daemon:stdout'), err: logger('ipfsd-ctl:daemon:stderr') } +const rpcModuleLogger = logger('ipfsd-ctl:daemon') function translateError (err: Error & { stdout: string, stderr: string }) { // get the actual error message to be the err.message @@ -85,7 +86,15 @@ class Daemon implements Controller { http: this.apiAddr }) } else if (this.apiAddr != null) { - this.api = this.opts.ipfsHttpModule.create(this.apiAddr) + if (this.opts.kuboRpcModule != null) { + rpcModuleLogger('Using kubo-rpc-client') + this.api = this.opts.kuboRpcModule.create(this.apiAddr) + } else if (this.opts.ipfsHttpModule != null) { + rpcModuleLogger('Using ipfs-http-client') + this.api = this.opts.ipfsHttpModule.create(this.apiAddr) + } else { + throw new Error('You must pass either a kuboRpcModule or ipfsHttpModule') + } } if (this.api == null) { diff --git a/src/ipfsd-in-proc.ts b/src/ipfsd-in-proc.ts index 50404f62..f60d93e3 100644 --- a/src/ipfsd-in-proc.ts +++ b/src/ipfsd-in-proc.ts @@ -10,6 +10,7 @@ const daemonLog = { info: logger('ipfsd-ctl:proc:stdout'), err: logger('ipfsd-ctl:proc:stderr') } +const rpcModuleLogger = logger('ipfsd-ctl:proc') /** * Controller for in process nodes @@ -67,7 +68,17 @@ class InProc implements Controller { private _setApi (addr: string): void { this.apiAddr = multiaddr(addr) - this.api = this.opts.ipfsHttpModule.create(addr) + + if (this.opts.kuboRpcModule != null) { + rpcModuleLogger('Using kubo-rpc-client') + this.api = this.opts.kuboRpcModule.create(addr) + } else if (this.opts.ipfsHttpModule != null) { + rpcModuleLogger('Using ipfs-http-client') + this.api = this.opts.ipfsHttpModule.create(addr) + } else { + throw new Error('You must pass either a kuboRpcModule or ipfsHttpModule') + } + this.api.apiHost = this.apiAddr.nodeAddress().address this.api.apiPort = this.apiAddr.nodeAddress().port } diff --git a/src/utils.ts b/src/utils.ts index 12f8ae76..0e23ea51 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,7 +4,7 @@ import fs from 'fs' import { logger } from '@libp2p/logger' import { nanoid } from 'nanoid' import tempWrite from 'temp-write' -import type { ControllerOptions, IPFSOptions, NodeType } from './index.js' +import type { ControllerOptions, IPFSOptions, ControllerType } from './index.js' const log = logger('ipfsd-ctl:utils') @@ -22,7 +22,7 @@ export const repoExists = async (repoPath: string): Promise => { return await Promise.resolve(fs.existsSync(path.join(repoPath, 'config'))) } -export const defaultRepo = (type?: NodeType): string => { +export const defaultRepo = (type?: ControllerType): string => { if (process.env.IPFS_PATH !== undefined) { return process.env.IPFS_PATH } diff --git a/test/controller.spec.ts b/test/controller.spec.ts index c54d497e..3de78576 100644 --- a/test/controller.spec.ts +++ b/test/controller.spec.ts @@ -12,6 +12,7 @@ import * as ipfsModule from 'ipfs' import * as ipfsHttpModule from 'ipfs-http-client' // @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' +import * as kuboRpcModule from 'kubo-rpc-client' const types: ControllerOptions[] = [{ type: 'js', @@ -21,6 +22,7 @@ const types: ControllerOptions[] = [{ } }, { type: 'go', + kuboRpcModule, ipfsOptions: { init: false, start: false @@ -40,6 +42,7 @@ const types: ControllerOptions[] = [{ } }, { type: 'go', + kuboRpcModule, remote: true, ipfsOptions: { init: false, @@ -47,6 +50,19 @@ const types: ControllerOptions[] = [{ } }] +/** + * Set the options object with the correct RPC module depending on the type + */ +function addCorrectRpcModule (opts: ControllerOptions, additionalOpts: ControllerOptions) { + if (opts.type === 'go') { + additionalOpts.kuboRpcModule = kuboRpcModule + } else { + additionalOpts.ipfsHttpModule = ipfsHttpModule + } + + return additionalOpts +} + describe('Controller API', function () { this.timeout(60000) @@ -55,14 +71,15 @@ describe('Controller API', function () { before(async () => { factory = createFactory({ test: true, - ipfsHttpModule, ipfsModule: (await import('ipfs')) }, { js: { - ipfsBin: isNode ? ipfsModule.path() : undefined + ipfsBin: isNode ? ipfsModule.path() : undefined, + ipfsHttpModule }, go: { - ipfsBin: isNode ? goIpfsModule.path() : undefined + ipfsBin: isNode ? goIpfsModule.path() : undefined, + kuboRpcModule } }) @@ -191,13 +208,12 @@ describe('Controller API', function () { // have to use createController so we don't try to shut down // the node twice during test cleanup const ctl = await createController(merge( - opts, { - ipfsHttpModule, + opts, addCorrectRpcModule(opts, { ipfsModule, ipfsOptions: { repo: factory.controllers[0].path } - } + }) )) await ctl.init() @@ -219,7 +235,7 @@ describe('Controller API', function () { const ctl1 = await createController(merge( { type: 'go', - ipfsHttpModule, + kuboRpcModule, ipfsBin: goIpfsModule.path(), test: true, disposable: true, @@ -232,8 +248,7 @@ describe('Controller API', function () { expect(ctl1.started).to.be.true() const ctl2 = await createController(merge( - opts, { - ipfsHttpModule, + opts, addCorrectRpcModule(opts, { ipfsModule, test: true, disposable: true, @@ -241,7 +256,7 @@ describe('Controller API', function () { repo: ctl1.path, start: true } - } + }) )) expect(ctl2.started).to.be.true() diff --git a/test/create.spec.ts b/test/create.spec.ts index 6fcc4484..0716f05a 100644 --- a/test/create.spec.ts +++ b/test/create.spec.ts @@ -12,6 +12,7 @@ import * as ipfsHttpModule from 'ipfs-http-client' // @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' import * as ipfsClientModule from 'ipfs-client' +import * as kuboRpcModule from 'kubo-rpc-client' describe('`createController` should return the correct class', () => { it('for type `js` ', async () => { @@ -33,7 +34,7 @@ describe('`createController` should return the correct class', () => { const f = await createController({ type: 'go', disposable: false, - ipfsHttpModule, + kuboRpcModule, ipfsBin: isNode ? goIpfsModule.path() : undefined }) @@ -121,37 +122,33 @@ describe('`createController` should return the correct class', () => { }) }) -const defaultOps = { - ipfsHttpModule -} - const types: ControllerOptions[] = [{ - ...defaultOps, type: 'js', + ipfsHttpModule, test: true, ipfsModule, ipfsBin: isNode ? ipfsModule.path() : undefined }, { - ...defaultOps, ipfsBin: isNode ? goIpfsModule.path() : undefined, type: 'go', + kuboRpcModule, test: true }, { - ...defaultOps, type: 'proc', + ipfsHttpModule, test: true, ipfsModule }, { - ...defaultOps, type: 'js', + ipfsHttpModule, test: true, remote: true, ipfsModule, ipfsBin: isNode ? ipfsModule.path() : undefined }, { - ...defaultOps, ipfsBin: isNode ? goIpfsModule.path() : undefined, type: 'go', + kuboRpcModule, test: true, remote: true }] diff --git a/test/factory.spec.ts b/test/factory.spec.ts index 7a1f5f18..b96ff9c0 100644 --- a/test/factory.spec.ts +++ b/test/factory.spec.ts @@ -8,36 +8,33 @@ import * as ipfsModule from 'ipfs' // @ts-expect-error no types import * as goIpfsModule from 'go-ipfs' import * as ipfsHttpModule from 'ipfs-http-client' - -const defaultOps = { - ipfsHttpModule -} +import * as kuboRpcModule from 'kubo-rpc-client' const types: ControllerOptions[] = [{ - ...defaultOps, + ipfsHttpModule, type: 'js', test: true, ipfsModule, ipfsBin: isNode ? ipfsModule.path() : undefined }, { - ...defaultOps, + kuboRpcModule, ipfsBin: isNode ? goIpfsModule.path() : undefined, type: 'go', test: true }, { - ...defaultOps, + ipfsHttpModule, type: 'proc', test: true, ipfsModule }, { - ...defaultOps, + ipfsHttpModule, type: 'js', remote: true, test: true, ipfsModule, ipfsBin: isNode ? ipfsModule.path() : undefined }, { - ...defaultOps, + kuboRpcModule, ipfsBin: isNode ? goIpfsModule.path() : undefined, type: 'go', remote: true,