UNPKG

electron-simple-publisher

Version:

Simple way to publish releases for electron-simple-updater

233 lines (207 loc) 6.1 kB
'use strict'; const fs = require('fs'); const os = require('os'); const path = require('path'); const NodeSsh = require('node-ssh'); const AbstractTransport = require('./abstract'); class SshTransport extends AbstractTransport { /** * @param {object} options * @param {object} options.transport * @param {string} options.transport.remotePath * @param {string} options.transport.remoteUrl * @param {string} options.transport.username * @param {string} options.transport.password * @param {boolean} options.transport.usePrivateKey * @param {string} options.transport.privateKeyPath * @param {string} options.transport.privateKey * @param {string} options.transport.afterUploadCommand * @param {string} options.transport.afterRemoveCommand */ constructor(options) { super(options); this.normalizeOptions(); } normalizeOptions() { const options = this.options; if (!this.commandOptions.updatesJsonUrl) { this.commandOptions.updatesJsonUrl = `${options.remoteUrl}/updates.json`; } if (!options.remoteUrl) { throw new Error('The transport.remoteUrl option is not set'); } if (options.remoteUrl.endsWith('/')) { options.remoteUrl = options.remoteUrl.slice(0, -1); } if (!this.options.remotePath) { throw new Error('The transport.remotePath option is not set'); } if (!options.username) { options.username = os.userInfo().username; } if (!options.password) { options.usePrivateKey = true; } if (options.usePrivateKey || !options.privateKeyPath || !options.privateKey) { options.privateKeyPath = path.join(os.homedir(), '.ssh', 'id_rsa'); } if (options.privateKeyPath) { options.privateKey = fs.readFileSync(options.privateKeyPath, 'utf-8'); } } init() { /** * @type {{}} */ this.ssh = new NodeSsh(); this.q = this.ssh.connect(this.options); return this.q; } /** * Upload file to a hosting and get its url * @abstract * @param {string} filePath * @param {object} build * @return {Promise<string>} File url */ uploadFile(filePath, build) { const remotePath = this.getRemoteFilePath(filePath, build); return this.q .then(() => { return this.ssh.putFile(filePath, remotePath); }) .then(() => { return this.getFileUrl(filePath, build); }); } /** * Save updates.json to a hosting * @return {Promise<string>} Url to updates.json */ pushUpdatesJson(data) { const remotePath = path.posix.join( this.options.remotePath, 'updates.json' ); let tmpPath; return this.q .then(() => { //noinspection ES6ModulesDependencies,NodeModulesDependencies return this.saveTemporaryFile(JSON.stringify(data, null, ' ')); }) .then((filePath) => { tmpPath = filePath; // Whe should remove updates.json, otherwise only part of the file // will be rewritten return this.ssh.execCommand('rm -rf updates.json', { cwd: this.options.remotePath }); }) .then(() => { return this.ssh.putFile(tmpPath, remotePath); }) .then(() => { return this.getUpdatesJsonUrl(); }); } /** * @return {Promise<Array<string>>} */ fetchBuildsList() { return this.executeCommand('ls -F ', false) .then((result) => { const dirs = (result.stdout || '').split('\n'); return dirs .filter(f => f.match(/\w+-\w+-\w+-[\w.]+\//)) .map(f => f.slice(0, -1)); }); } /** * @return {Promise} */ removeBuild(build) { const buildId = this.getBuildId(build); // We need to make an additional check before exec rm -rf if (!buildId.match(/\w+-\w+-\w+-v\d+\.\d+\.\d+/)) { return Promise.reject(`Could not remove build ${buildId}`); } if (this.options.remotePath.length < 2) { return Promise.reject('Wrong remote path ' + this.options.remotePath); } return this.executeCommand('rm -rf ' + buildId, false) .then((result) => { if (result.code === 0) return; return Promise.reject( `Error while deleting a release ${buildId}\n` + `${result.stdout}\n${result.stderr}` ); }); } afterUpload(build) { return this.executeCommand(this.options.afterUploadCommand); } afterRemove(build) { return this.executeCommand(this.options.afterRemoveCommand); } close() { this.ssh.dispose(); return Promise.resolve(); } getRemoteFilePath(localFilePath, build) { localFilePath = path.basename(localFilePath); return path.posix.join( this.options.remotePath, this.getBuildId(build), this.normalizeFileName(localFilePath) ); } getFileUrl(localFilePath, build) { let url = this.options.remoteUrl; if (url.endsWith('/')) { url = url.slice(0, -1); } return [ url, this.getBuildId(build), this.normalizeFileName(localFilePath) ].join('/'); } //noinspection JSMethodCanBeStatic normalizeFileName(fileName) { fileName = path.basename(fileName); return fileName.replace(/\s/g, '-'); } saveTemporaryFile(content) { const filePath = path.join( os.tmpdir(), 'publisher-update-' + Number(new Date()) + '.json' ); return new Promise((resolve, reject) => { fs.writeFile(filePath, content, (err) => { if (err) { reject(err); } else { resolve(filePath); } }); }); } executeCommand(command, log = true) { if (!command) { return Promise.resolve(); } return this.q .then(() => { return this.ssh.execCommand(command, { cwd: this.options.remotePath }); }) .then((result) => { if (log) { console.log('Execute command: ' + command); console.log(result.stdout); console.log(result.stderr); } return result; }); } } module.exports = SshTransport;