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",