mirror of
https://github.com/easingthemes/ssh-deploy
synced 2026-04-06 04:43:02 +08:00
feat!: replace rsyncwrapper with local rsync module
Add src/rsync.js as a drop-in replacement for rsyncwrapper, using child_process.spawn directly. Only implements the options this project uses. Single line change in rsyncCli.js to swap the import. BREAKING CHANGE: rsyncwrapper dependency removed, rsync command is now constructed and executed via a local module using child_process.spawn. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
dist/index.js
vendored
2
dist/index.js
vendored
File diff suppressed because one or more lines are too long
54
src/rsync.js
Normal file
54
src/rsync.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const { spawn } = require('child_process');
|
||||||
|
|
||||||
|
const escapeSpaces = (str) => (typeof str === 'string' ? str.replace(/\b\s/g, '\\ ') : str);
|
||||||
|
|
||||||
|
const buildRsyncCommand = ({ src, dest, excludeFirst, port, privateKey, args, sshCmdArgs }) => {
|
||||||
|
const cmdParts = [];
|
||||||
|
|
||||||
|
const sources = Array.isArray(src) ? src : [src];
|
||||||
|
cmdParts.push(...sources.map(escapeSpaces));
|
||||||
|
cmdParts.push(escapeSpaces(dest));
|
||||||
|
|
||||||
|
let sshCmd = `ssh -p ${port || 22} -i ${privateKey}`;
|
||||||
|
if (sshCmdArgs && sshCmdArgs.length > 0) {
|
||||||
|
sshCmd += ` ${sshCmdArgs.join(' ')}`;
|
||||||
|
}
|
||||||
|
cmdParts.push('--rsh', `"${sshCmd}"`);
|
||||||
|
|
||||||
|
cmdParts.push('--recursive');
|
||||||
|
|
||||||
|
if (Array.isArray(excludeFirst)) {
|
||||||
|
excludeFirst.forEach((pattern) => {
|
||||||
|
if (pattern) cmdParts.push(`--exclude=${escapeSpaces(pattern)}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(args)) {
|
||||||
|
cmdParts.push(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `rsync ${[...new Set(cmdParts)].join(' ')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = (options, callback) => {
|
||||||
|
const cmd = buildRsyncCommand(options);
|
||||||
|
const noop = () => {};
|
||||||
|
const onStdout = options.onStdout || noop;
|
||||||
|
const onStderr = options.onStderr || noop;
|
||||||
|
|
||||||
|
let stdout = '';
|
||||||
|
let stderr = '';
|
||||||
|
const proc = spawn('/bin/sh', ['-c', cmd]);
|
||||||
|
|
||||||
|
proc.stdout.on('data', (data) => { onStdout(data); stdout += data; });
|
||||||
|
proc.stderr.on('data', (data) => { onStderr(data); stderr += data; });
|
||||||
|
|
||||||
|
proc.on('exit', (code) => {
|
||||||
|
let error = null;
|
||||||
|
if (code !== 0) {
|
||||||
|
error = new Error(`rsync exited with code ${code}`);
|
||||||
|
error.code = code;
|
||||||
|
}
|
||||||
|
callback(error, stdout, stderr, cmd);
|
||||||
|
});
|
||||||
|
};
|
||||||
116
src/rsyncCli.js
116
src/rsyncCli.js
@@ -1,90 +1,35 @@
|
|||||||
const { execSync, spawn } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
|
const nodeRsync = require('./rsync');
|
||||||
|
|
||||||
const escapeSpaces = (str) => (typeof str === 'string' ? str.replace(/\b\s/g, '\\ ') : str);
|
const nodeRsyncPromise = async (config) => new Promise((resolve, reject) => {
|
||||||
|
const logCMD = (cmd) => {
|
||||||
const buildRsyncCommand = ({ src, dest, excludeFirst, port, privateKey, args, sshCmdArgs }) => {
|
|
||||||
const cmdParts = [];
|
|
||||||
|
|
||||||
// Sources and destination (with space escaping)
|
|
||||||
const sources = (Array.isArray(src) ? src : [src]).map(escapeSpaces);
|
|
||||||
cmdParts.push(...sources);
|
|
||||||
cmdParts.push(escapeSpaces(dest));
|
|
||||||
|
|
||||||
// SSH transport
|
|
||||||
let sshCmd = `ssh -p ${port || 22} -i ${privateKey}`;
|
|
||||||
if (sshCmdArgs && sshCmdArgs.length > 0) {
|
|
||||||
sshCmd += ` ${sshCmdArgs.join(' ')}`;
|
|
||||||
}
|
|
||||||
cmdParts.push('--rsh', `"${sshCmd}"`);
|
|
||||||
|
|
||||||
// Recursive
|
|
||||||
cmdParts.push('--recursive');
|
|
||||||
|
|
||||||
// Exclude-first patterns
|
|
||||||
if (excludeFirst && excludeFirst.length > 0) {
|
|
||||||
excludeFirst.forEach((pattern) => {
|
|
||||||
if (pattern) cmdParts.push(`--exclude=${escapeSpaces(pattern)}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// User-provided args
|
|
||||||
if (args && args.length > 0) {
|
|
||||||
cmdParts.push(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deduplicate while preserving order
|
|
||||||
const dedupedParts = [...new Set(cmdParts)];
|
|
||||||
return `rsync ${dedupedParts.join(' ')}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const runRsync = async (config) => new Promise((resolve, reject) => {
|
|
||||||
const cmd = buildRsyncCommand(config);
|
|
||||||
const logCMD = (c) => {
|
|
||||||
console.warn('================================================================');
|
console.warn('================================================================');
|
||||||
console.log(c);
|
console.log(cmd);
|
||||||
console.warn('================================================================');
|
console.warn('================================================================');
|
||||||
};
|
};
|
||||||
|
|
||||||
let stdout = '';
|
try {
|
||||||
let stderr = '';
|
nodeRsync(config, (error, stdout, stderr, cmd) => {
|
||||||
const proc = spawn('/bin/sh', ['-c', cmd]);
|
if (error) {
|
||||||
|
console.error('❌ [Rsync] error: ');
|
||||||
proc.stdout.on('data', (data) => {
|
console.error(error);
|
||||||
const str = data.toString();
|
console.error('❌ [Rsync] stderr: ');
|
||||||
console.log(str);
|
console.error(stderr);
|
||||||
stdout += str;
|
console.error('❌️ [Rsync] stdout: ');
|
||||||
});
|
console.error(stdout);
|
||||||
|
console.error('❌ [Rsync] command: ');
|
||||||
proc.stderr.on('data', (data) => {
|
logCMD(cmd);
|
||||||
const str = data.toString();
|
reject(new Error(`${error.message}\n\n${stderr}`));
|
||||||
console.error(str);
|
} else {
|
||||||
stderr += str;
|
console.log('⭐ [Rsync] command finished: ');
|
||||||
});
|
logCMD(cmd);
|
||||||
|
resolve(stdout);
|
||||||
proc.on('exit', (code) => {
|
}
|
||||||
if (code !== 0) {
|
});
|
||||||
const error = new Error(`rsync exited with code ${code}`);
|
} catch (error) {
|
||||||
error.code = code;
|
|
||||||
console.error('❌ [Rsync] error: ');
|
|
||||||
console.error(error);
|
|
||||||
console.error('❌ [Rsync] stderr: ');
|
|
||||||
console.error(stderr);
|
|
||||||
console.error('❌️ [Rsync] stdout: ');
|
|
||||||
console.error(stdout);
|
|
||||||
console.error('❌ [Rsync] command: ');
|
|
||||||
logCMD(cmd);
|
|
||||||
reject(new Error(`${error.message}\n\n${stderr}`));
|
|
||||||
} else {
|
|
||||||
console.log('⭐ [Rsync] command finished: ');
|
|
||||||
logCMD(cmd);
|
|
||||||
resolve(stdout);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
proc.on('error', (error) => {
|
|
||||||
console.error('❌ [Rsync] command error: ', error.message, error.stack);
|
console.error('❌ [Rsync] command error: ', error.message, error.stack);
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const validateRsync = async () => {
|
const validateRsync = async () => {
|
||||||
@@ -112,8 +57,17 @@ const rsyncCli = async ({
|
|||||||
console.log(`[Rsync] Starting Rsync Action: ${source} to ${rsyncServer}`);
|
console.log(`[Rsync] Starting Rsync Action: ${source} to ${rsyncServer}`);
|
||||||
if (exclude && exclude.length > 0) console.log(`[Rsync] excluding folders ${exclude}`);
|
if (exclude && exclude.length > 0) console.log(`[Rsync] excluding folders ${exclude}`);
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
ssh: true,
|
||||||
|
recursive: true,
|
||||||
|
onStdout: (data) => console.log(data.toString()),
|
||||||
|
onStderr: (data) => console.error(data.toString())
|
||||||
|
};
|
||||||
|
|
||||||
|
// RSYNC COMMAND
|
||||||
/* eslint-disable object-property-newline */
|
/* eslint-disable object-property-newline */
|
||||||
return runRsync({
|
return nodeRsyncPromise({
|
||||||
|
...defaultOptions,
|
||||||
src: source, dest: rsyncServer, excludeFirst: exclude, port: remotePort,
|
src: source, dest: rsyncServer, excludeFirst: exclude, port: remotePort,
|
||||||
privateKey: privateKeyPath, args, sshCmdArgs
|
privateKey: privateKeyPath, args, sshCmdArgs
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user