hexo/node_modules/sftp-sync-deploy/lib/sftpSync.js

326 lines
13 KiB
JavaScript

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const ssh2_1 = require("ssh2");
const path = require("path");
const fs = require("fs");
const util_1 = require("./util");
const queuifiedSftp_1 = require("./queuifiedSftp");
const syncTable_1 = require("./syncTable");
/**
* Creates a new SftpDeploy instance
* @class
*/
class SftpSync {
/**
* Constructor
*/
constructor(config, options) {
/**
* Whether a SSH2 connection has been made or not
*/
this.connected = false;
this.config = config;
this.options = Object.assign({ dryRun: false, exclude: [], excludeMode: 'remove', concurrency: 100 }, options);
this.client = new ssh2_1.Client;
this.localRoot = util_1.chomp(path.resolve(this.config.localDir), path.sep);
this.remoteRoot = util_1.chomp(this.config.remoteDir, path.posix.sep);
}
/**
* Make SSH2 connection
*/
connect() {
let privKeyRaw;
if (this.config.privateKey) {
try {
privKeyRaw = fs.readFileSync(this.config.privateKey);
}
catch (err) {
throw new Error(`Local Error: Private key file not found ${this.config.privateKey}`);
}
}
return new Promise((resolve, reject) => {
this.client
.on('ready', () => {
this.connected = true;
resolve();
})
.on('error', err_1 => {
reject(new Error(`Connection Error: ${err_1.message}`));
})
.connect({
host: this.config.host,
port: this.config.port || 22,
username: this.config.username,
password: this.config.password,
passphrase: this.config.passphrase,
privateKey: privKeyRaw,
agent: this.config.agent
});
});
}
/**
* Close SSH2 connection
*/
close() {
this.connected = false;
this.client.end();
}
/**
* Sync with specified path
*/
sync(relativePath = '', isRootTask = true) {
return __awaiter(this, void 0, void 0, function* () {
const table = yield this.buildSyncTable(relativePath);
yield Promise.all(table.all.map((entry) => __awaiter(this, void 0, void 0, function* () {
const task = entry.getTask();
const args = [entry.path, false];
if (this.options.dryRun) {
entry.dryRunLog();
if (task.method === 'sync') {
return this.sync(entry.path, false);
}
return;
}
if (task.removeRemote) {
yield this.removeRemote(entry.path, false);
}
if (task.method === 'sync' && entry.remoteStat !== 'dir') {
yield this.createRemoteDirectory(entry.path);
}
yield this[task.method].apply(this, args);
entry.liveRunLog();
})));
if (isRootTask)
this.close();
});
}
/**
* Upload file/directory
*/
upload(relativePath, isRootTask = true) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.queuifiedSftp) {
yield this.initQueuifiedSftp();
return this.upload(relativePath, isRootTask);
}
const localPath = this.localFullPath(relativePath);
const remotePath = this.remoteFullPath(relativePath);
const stat = fs.lstatSync(localPath);
if (stat.isDirectory()) {
const files = fs.readdirSync(localPath);
if (files && files.length) {
yield Promise.all(files.map(filename => this.upload(path.posix.join(relativePath, filename), false)));
}
}
else {
try {
// const buffer = fs.readFileSync(localPath);
// const handle = await this.queuifiedSftp.open(remotePath, 'r+');
// await this.queuifiedSftp.writeData(handle, buffer, 0, buffer.length, 0);
// await this.queuifiedSftp.close(handle);
yield this.queuifiedSftp.fastPut(localPath, remotePath);
}
catch (err) {
switch (err.code) {
case ssh2_1.SFTP_STATUS_CODE.NO_SUCH_FILE: {
throw new Error(`Remote Error: Cannot upload file ${remotePath}`);
}
case ssh2_1.SFTP_STATUS_CODE.PERMISSION_DENIED: {
throw new Error(`Remote Error: Cannot upload file. Permission denied ${remotePath}`);
}
case ssh2_1.SFTP_STATUS_CODE.FAILURE: {
throw new Error(`Remote Error: Unknown error while uploading file ${remotePath}`);
}
default: throw err;
}
}
}
if (isRootTask)
this.close();
});
}
/**
* Remove a remote file or directory
*/
removeRemote(relativePath, isRootTask = true) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.queuifiedSftp) {
yield this.initQueuifiedSftp();
return this.removeRemote(relativePath, isRootTask);
}
const remotePath = this.remoteFullPath(relativePath);
const stat = yield this.queuifiedSftp.lstat(remotePath);
if (stat.isDirectory()) {
const files = yield this.queuifiedSftp.readdir(remotePath);
yield Promise.all(files.map(file => this.removeRemote(path.posix.join(relativePath, file.filename), false)));
yield this.queuifiedSftp.rmdir(remotePath);
}
else {
return this.queuifiedSftp.unlink(remotePath);
}
if (isRootTask)
this.close();
});
}
/**
* No operation
*/
noop() {
return __awaiter(this, void 0, void 0, function* () {
return;
});
}
/**
* Create a directory on a remote host
*/
createRemoteDirectory(relativePath) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.queuifiedSftp) {
yield this.initQueuifiedSftp();
return this.createRemoteDirectory(relativePath);
}
const remotePath = this.remoteFullPath(relativePath);
try {
yield this.queuifiedSftp.mkdir(remotePath);
}
catch (err) {
switch (err.code) {
case ssh2_1.SFTP_STATUS_CODE.NO_SUCH_FILE: {
throw new Error(`Remote Error: Cannot create directory ${remotePath}`);
}
case ssh2_1.SFTP_STATUS_CODE.PERMISSION_DENIED: {
throw new Error(`Remote Error: Cannot create directory. Permission denied ${remotePath}`);
}
case ssh2_1.SFTP_STATUS_CODE.FAILURE: {
throw new Error(`Remote Error: Unknown error while creating directory ${remotePath}`);
}
default: throw err;
}
}
});
}
/**
* Build a local and remote files status report for the specified path
*/
buildSyncTable(relativePath) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.queuifiedSftp) {
yield this.initQueuifiedSftp();
return this.buildSyncTable(relativePath);
}
const localPath = this.localFullPath(relativePath);
const remotePath = this.remoteFullPath(relativePath);
const table = new syncTable_1.SyncTable(relativePath, this.options);
const readLocal = () => __awaiter(this, void 0, void 0, function* () {
let files;
try {
files = fs.readdirSync(localPath);
}
catch (err) {
switch (err.code) {
case 'ENOENT': throw new Error(`Local Error: No such directory ${localPath}`);
case 'ENOTDIR': throw new Error(`Local Error: Not a directory ${localPath}`);
case 'EPERM': throw new Error(`Local Error: Cannot read directory. Permission denied ${localPath}`);
default: throw err;
}
}
if (!files || !files.length)
return;
yield Promise.all(files.map((filename) => __awaiter(this, void 0, void 0, function* () {
const fullPath = path.join(localPath, filename);
let stat;
try {
fs.accessSync(fullPath, fs.constants.R_OK);
stat = fs.lstatSync(fullPath);
}
catch (err) {
if (err.code === 'EPERM' || err.code === 'EACCES') {
table.set(filename, { localStat: 'error', localTimestamp: null });
}
return;
}
const mtime = Math.floor(new Date(stat.mtime).getTime() / 1000);
table.set(filename, {
localStat: stat.isDirectory() ? 'dir' : 'file',
localTimestamp: mtime
});
})));
});
const readRemote = () => __awaiter(this, void 0, void 0, function* () {
let files;
try {
files = yield this.queuifiedSftp.readdir(remotePath);
}
catch (err) { }
if (!files || !files.length)
return;
yield Promise.all(files.map((file) => __awaiter(this, void 0, void 0, function* () {
const fullPath = path.posix.join(remotePath, file.filename);
let stat;
try {
stat = yield this.queuifiedSftp.lstat(fullPath);
if (stat.isDirectory()) {
yield this.queuifiedSftp.readdir(fullPath);
}
else {
const buffer = yield this.queuifiedSftp.open(fullPath, 'r+');
yield this.queuifiedSftp.close(buffer);
}
}
catch (err) {
if (err.code === ssh2_1.SFTP_STATUS_CODE.PERMISSION_DENIED) {
table.set(file.filename, { remoteStat: 'error', remoteTimestamp: null });
}
return;
}
if (stat) {
table.set(file.filename, {
remoteStat: stat.isDirectory() ? 'dir' : 'file',
remoteTimestamp: stat.mtime
});
}
})));
});
yield Promise.all([readLocal(), readRemote()]);
return table.forEach(entry => entry.detectExclusion());
});
}
/**
* Get an async version of sftp stream
*/
initQueuifiedSftp(concurrency = this.options.concurrency) {
return __awaiter(this, void 0, void 0, function* () {
if (this.queuifiedSftp) {
return this.queuifiedSftp;
}
if (!this.connected) {
yield this.connect();
return this.initQueuifiedSftp(concurrency);
}
this.queuifiedSftp = yield queuifiedSftp_1.QueuifiedSFTP.init(this.client, concurrency);
return this.queuifiedSftp;
});
}
/**
* Get a full path of a local file or directory
*/
localFullPath(relativePath) {
return path.join(this.localRoot, relativePath);
}
/**
* Get a full path of a remote file or directory
*/
remoteFullPath(relativePath) {
return path.posix.join(this.remoteRoot, relativePath);
}
}
exports.SftpSync = SftpSync;