import { EventEmitter as Emitter } from 'events' import stream from 'stream' import Watcher from './watcher' import Ssh from './ssh' import Sync from './lib/sync' const devNull = new stream.Writable() devNull._write = () => null class RemoteCode { constructor(opts = {}) { // Set defaults // a id emitted with every event emit that can identify which remote code emitted this.id = opts.id this.watch = opts.watch || false this.options.ssh.keepaliveInterval = opts.ssh.keepaliveInterval || 500 this.options.ssh.readyTimeout = opts.ssh.readyTimeout || 2000 this.options.ssh.port = opts.ssh.port || 22 this.options.source = opts.source || '.' this.options.install = opts.install || 'yarn' this.options.install = opts.registry ? `${this.options.install} --registry ${opts.registry}` : this.options.install this.options.start = opts.start || 'nodemon .' if (!(opts.ssh.keyfilePath || opts.ssh.password)) { this.options.ssh.agent = process.env[opts.ssh.agent] || process.env.SSH_AUTH_SOCK || process.env.SSH_AGENT_SOCK if (!this.options.ssh.agent) { return new Error('no ssh authentification method provided') } } this.ssh = new Ssh() this.verbose = opts.verbose || false this.stdout = opts.stdout instanceof stream.Writable ? opts.stdout : new stream.Writable() this.stderr = opts.stderr instanceof stream.Writable ? opts.stderr : new stream.Writable() this.watcher = new Watcher(this.options) this.sync = new Sync(this.options) .addStdOutStream(this.stdout) .addStdErrStream(this.stderr) } _getStdOut() { if (this.verbose) { return this.stdout } return devNull } _getStdErr() { if (this.verbose) { return this.stderr } return devNull } sync() { this.emit('sync',{}) return this.sync.execute() } watch() { this.watcher.start() const watchEmitter = this.watcher.getEventEmitter() watchEmitter.on('sync', () => { this.syncCode() }) watchEmitter.on('install', () => { return this.install() .then(() => { this.ssh.liveReload.send('rs') }) }) return this.emitter } async init() { this.emitter.emit('start') const sshSettings = this.options.ssh return Promise.all([this.syncCode(), this.watch()]) .then(() => this.install()) .then(() => this.ssh.liveReload.connect(sshSettings)) .then(() => { this.emitter.emit('nodemon', 'start') this.ssh.liveReload.send(`cd ${this.options.target} && ${this.options.start}`) }) .catch(this._abort.bind(this)) } // execute a single command and then resolve execute(cmd, stdout, stderr) { console.log('remote codes exec', cmd) this.emitter.emit('exec', cmd) const ssh = new Ssh() const result = ssh.exec(this.options.ssh, cmd, stdout, stderr) return result } async install() { this.emit('install', 'triggered') if (!this.installInProgress) { this.emitter.emit('install', 'started') console.log('calling execute from install') return this.execute(`cd ${this.options.target} && ${this.options.install}`, this._getStdOut(), this._getStdErr()) .then(res => { this.emit('install', 'done', res) this.installInProgress = false return res }) } return Promise.resolve() } close() { this.emitter.emit('close') return Promise.all([this.watcher.close(), this.ssh.liveReload.close()]) } _abort(err) { this.emitter.emit('error', err) } } export default RemoteCode