Skip to content

Commit

Permalink
Merge pull request #14 from wellwelwel/download-feat
Browse files Browse the repository at this point in the history
feat: support download via SFTP
  • Loading branch information
wellwelwel authored Mar 20, 2024
2 parents 7acc8cd + 391a3d9 commit fc45498
Show file tree
Hide file tree
Showing 20 changed files with 1,253 additions and 729 deletions.
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"editor.formatOnSave": true,
"files.trimTrailingWhitespace": true,
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.eslint": true
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit"
},
"eslint.validate": ["ts", "js"],
"files.exclude": {
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,23 @@ await svps.upload([

---

### Download Files

Download your remote files.

```js
await svps.download([
{
remote: '/workspace/backup.zip',
local: './my-local-path/backup.zip',
},
]);
```

- It uses **SFTP** to get the content from remote server

---

### Virtual Hosts (Domains Forwarding)

```js
Expand Down
1,310 changes: 876 additions & 434 deletions package-lock.json

Large diffs are not rendered by default.

29 changes: 15 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@
"svps": "bin/index.js"
},
"dependencies": {
"@types/ssh2": "^1.11.14",
"ssh2": "^1.14.0"
"@types/ssh2": "^1.15.0",
"ssh2": "^1.15.0"
},
"devDependencies": {
"@types/node": "^20.8.4",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
"eslint": "^8.51.0",
"@types/node": "^20.11.30",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
"eslint": "^8.57.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-import": "^2.29.1",
"packages-update": "^1.2.1",
"prettier": "^3.0.3",
"typescript": "^5.2.2"
"poku": "^1.9.2",
"prettier": "^3.2.5",
"typescript": "^5.4.2"
},
"scripts": {
"build": "rm -rf ./bin ./lib ./tests && npx tsc",
Expand All @@ -36,11 +37,11 @@
"docker:run": "docker run -d --name test-svps --privileged -p ${PORT:-22}:${PORT:-22} -e PORT=${PORT:-22} --restart always wellwelwel/vps:latest",
"tests:docker": "npm run docker:reset; npm run docker:run",
"tests:create": "cd ./tests && echo '{ \"type\": \"module\" }' | cat > ./package.json",
"tests:connection": "cd ./tests && node ./basic-connection.js",
"tests:mount": "cd ./tests && node ./mount.js",
"tests:commands": "cd ./tests && node ./commands.js",
"tests:upload": "cd ./tests && node ./upload.js",
"tests:virtual-hosts": "cd ./tests && node ./virtual-hosts.js",
"tests:connection": "cd ./tests && node ./basic-connection.test.js",
"tests:mount": "cd ./tests && node ./mount.test.js",
"tests:commands": "cd ./tests && node ./commands.test.js",
"tests:upload": "cd ./tests && node ./upload.test.js",
"tests:virtual-hosts": "cd ./tests && node ./virtual-hosts.test.js",
"tests:reset": "npm run docker:reset",
"pretests": "npm run build && npm run build:examples && npm run eslint:checker && npm run prettier:checker && npm run tests:docker",
"tests": "npm run tests:create && npm run tests:connection && npm run tests:mount && npm run tests:commands && npm run tests:upload && npm run tests:virtual-hosts",
Expand Down
12 changes: 12 additions & 0 deletions src/lib/modules/configs/downloads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { downloadFile } from '../../ssh.js';
import { DOWNLOAD } from '../../types/download.js';

export const downloads = async (paths: DOWNLOAD[]) => {
console.log(`\n\x1b[22m\x1b[36m\x1b[1m⦿ Downloading Files\x1b[0m`);

const uploadPromises = paths.map((path) =>
downloadFile(path.remote, path.local)
);

await Promise.all(uploadPromises);
};
4 changes: 1 addition & 3 deletions src/lib/modules/configs/uploads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,5 @@ import { upload } from '../prepare-files.js';
export const uploads = async (paths: UPLOAD[]) => {
console.log(`\n\x1b[22m\x1b[36m\x1b[1m⦿ Uploading Files\x1b[0m`);

for (const toUpload of paths) {
await upload(toUpload);
}
await Promise.all(paths.map((path) => upload(path)));
};
40 changes: 21 additions & 19 deletions src/lib/modules/prepare-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,25 +135,27 @@ export const upload = async (options: UPLOAD) => {
await ensureDir(remotePath);
}

for (const file of contents.files) {
const currentFile = relative(file.path).replace(/^\//, '');
const remotePath = `${remoteDir}/${currentFile}`;

/** Debug */
// console.log('origin', file.path);
// console.log('expectedFile', currentFile);
// console.log('remotePath', remotePath);
// console.log();

const message = ` \x1b[36m⌙ \x1b[0m\x1b[2m${file.size.padStart(
longestSize,
' '
)} ■ \x1b[0m\x1b[2m${remotePath}\x1b[0m`;

console.log(message);

await uploadFile(file.path, remotePath);
}
await Promise.all(
contents.files.map((file) => {
const currentFile = relative(file.path).replace(/^\//, '');
const remotePath = `${remoteDir}/${currentFile}`;

/** Debug */
// console.log('origin', file.path);
// console.log('expectedFile', currentFile);
// console.log('remotePath', remotePath);
// console.log();

const message = ` \x1b[36m⌙ \x1b[0m\x1b[2m${file.size.padStart(
longestSize,
' '
)} ■ \x1b[0m\x1b[2m${remotePath}\x1b[0m`;

console.log(message);

return uploadFile(file.path, remotePath);
})
);
}

if (permissions) await basicPermissions({ remote, ...permissions });
Expand Down
30 changes: 29 additions & 1 deletion src/lib/ssh.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Client, ConnectConfig, SFTPWrapper } from 'ssh2';
import { ACCESS } from './types/acess.js';
import { resolve as pathResolve } from 'path';
import path, { resolve as pathResolve } from 'path';

export const ssh2 = new Client();
export let SFTP: SFTPWrapper;
Expand Down Expand Up @@ -161,3 +161,31 @@ export const uploadFile = (
reject(error);
}
});

/** Uses `SFTP` to download a local file to remote server */
export const downloadFile = (
remotePath: string,
localPath: string
): Promise<true> =>
new Promise((resolve, reject) => {
try {
const resolvedPath = pathResolve(localPath);

SFTP.fastGet(remotePath, resolvedPath, (err) => {
if (err) {
reject(err);
return;
}

const remote = path.normalize(remotePath);
const local = path.normalize(localPath);
const message = ` \x1b[36m⌙ \x1b[0m\x1b[2m${remote} \x1b[36m▸\x1b[0m \x1b[0m\x1b[2m${local}\x1b[0m`;

console.log(message);

resolve(true);
});
} catch (error) {
reject(error);
}
});
27 changes: 27 additions & 0 deletions src/lib/tasks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { ACCESS } from '../types/acess.js';
import { VIRTUAL_HOST, BASIC_VIRTUAL_HOST } from '../types/virtual-hosts.js';
import { UPLOAD } from '../types/upload.js';
import { MOUNT } from '../types/mount.js';
import { DOWNLOAD } from '../types/download.js';
import { downloads } from '../modules/configs/downloads.js';

const createSVPS = () => {
let connected: boolean = false;
Expand Down Expand Up @@ -259,6 +261,31 @@ const createSVPS = () => {
}
};

/**
* Download files.
*/
download = async (paths: DOWNLOAD[]) => {
try {
console.log(
`\x1b[22m\x1b[36m\x1b[1m⦿ ${access.username}@${access.host}\x1b[0m`
);

if (!connected) await createConnection(access);

/** Uploading files and directories */
await downloads(paths);

console.log(`\x1b[0m\x1b[1m\x1b[32m✔︎ Success\x1b[0m\n`);

return true;
} catch (error) {
if (error instanceof Error)
throw new Error(
`\x1b[0m\x1b[1m\x1b[31m✖︎ ${error.message} \x1b[0m\n`
);
}
};

end = end;
};
};
Expand Down
16 changes: 16 additions & 0 deletions src/lib/types/download.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface DOWNLOAD {
/**
* Path to local file or directory.
*
* For directories, it will upload every sub directories and their files.
*/
local: string;
/**
* Absolute path to remote file or directory. It will ensure the remote path.
*
* ---
*
* Ex.: `/root/Documents/...`
*/
remote: string;
}
19 changes: 19 additions & 0 deletions src/tests/basic-connection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { assert } from 'poku';
import { SVPS } from '../lib/index.js';

(async () => {
const svps = new SVPS({
access: {
host: process.env?.HOST || '127.0.0.1',
username: String(process.env.USER),
password: process.env.PASS,
port: Number(process.env.PORT) || 22,
},
});

const mount = await svps.mount();

await svps.end();

assert.strictEqual(mount, true, 'Basic Connection');
})();
23 changes: 0 additions & 23 deletions src/tests/basic-connection.ts

This file was deleted.

19 changes: 19 additions & 0 deletions src/tests/commands.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { assert } from 'poku';
import { SVPS } from '../lib/index.js';

(async () => {
const svps = new SVPS({
access: {
host: process.env?.HOST || '127.0.0.1',
username: String(process.env.USER),
password: process.env.PASS,
port: Number(process.env.PORT) || 22,
},
});

const commands = await svps.commands(['crontab -l']);

await svps.end();

assert.strictEqual(commands, true, 'Personal commands');
})();
22 changes: 0 additions & 22 deletions src/tests/commands.ts

This file was deleted.

Loading

0 comments on commit fc45498

Please sign in to comment.