@cyberalien/deploy-utils 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ declare const stableNodeVersion = 24;
2
+ declare const defaultNVMVersion = "0.40.5";
3
+ export { defaultNVMVersion, stableNodeVersion };
@@ -0,0 +1,3 @@
1
+ const stableNodeVersion = 24;
2
+ const defaultNVMVersion = "0.40.5";
3
+ export { defaultNVMVersion, stableNodeVersion };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Various commands to install Node and its tools
3
+ */
4
+ declare const nodeInstallCommands: {
5
+ nvm: (version?: string) => string[];
6
+ node: (version?: number | string) => string;
7
+ corepack: string;
8
+ pm2: string;
9
+ };
10
+ /**
11
+ * Reinstall and restart app using pm2
12
+ */
13
+ declare const nodeRestartAppCommand = "rm -rf node_modules pnpm-lock.yaml && npx pnpm i && pm2 restart all --time";
14
+ export { nodeInstallCommands, nodeRestartAppCommand };
@@ -0,0 +1,15 @@
1
+ import { defaultNVMVersion } from "./config.js";
2
+ /**
3
+ * Various commands to install Node and its tools
4
+ */
5
+ const nodeInstallCommands = {
6
+ nvm: (version = defaultNVMVersion) => [`curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v${version}/install.sh | bash`, "source ~/.bashrc"],
7
+ node: (version = 24) => `nvm install ${version} && nvm use ${version} && nvm alias default ${version}`,
8
+ corepack: "npm i -g corepack@latest && corepack enable",
9
+ pm2: "npm install -g pm2"
10
+ };
11
+ /**
12
+ * Reinstall and restart app using pm2
13
+ */
14
+ const nodeRestartAppCommand = "rm -rf node_modules pnpm-lock.yaml && npx pnpm i && pm2 restart all --time";
15
+ export { nodeInstallCommands, nodeRestartAppCommand };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Command to update packages in Ubuntu
3
+ */
4
+ declare const ubuntoUpdateCommand = "apt-get update -y && apt-get --with-new-pkgs upgrade -y";
5
+ export { ubuntoUpdateCommand };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Command to update packages in Ubuntu
3
+ */
4
+ const ubuntoUpdateCommand = "apt-get update -y && apt-get --with-new-pkgs upgrade -y";
5
+ export { ubuntoUpdateCommand };
package/lib/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
- import { connectToSSH } from "./ssh/connect.js";
1
+ import { connectToSSH, connectToSSHWithKeys } from "./ssh/connect.js";
2
+ import { SSHExecResult, execSFTPCommand } from "./ssh/exec.js";
3
+ import { startSFTPSession } from "./ssh/sftp.js";
4
+ import { getSFTPFile } from "./ssh/get.js";
2
5
  import { readSFTPDir } from "./ssh/readdir.js";
3
- import { uploadSFTPFile } from "./ssh/upload.js";
6
+ import { putSFTPFile, uploadSFTPFile } from "./ssh/upload.js";
7
+ import { putSFTPFiles, uploadSFTPFiles } from "./ssh/upload-files.js";
4
8
  import { initTokens } from "./misc/tokens.js";
5
- export { connectToSSH, initTokens, readSFTPDir, uploadSFTPFile };
9
+ export { SSHExecResult, connectToSSH, connectToSSHWithKeys, execSFTPCommand, getSFTPFile, initTokens, putSFTPFile, putSFTPFiles, readSFTPDir, startSFTPSession, uploadSFTPFile, uploadSFTPFiles };
package/lib/index.js CHANGED
@@ -1,5 +1,9 @@
1
- import { connectToSSH } from "./ssh/connect.js";
1
+ import { connectToSSH, connectToSSHWithKeys } from "./ssh/connect.js";
2
+ import { execSFTPCommand } from "./ssh/exec.js";
3
+ import { startSFTPSession } from "./ssh/sftp.js";
4
+ import { getSFTPFile } from "./ssh/get.js";
2
5
  import { readSFTPDir } from "./ssh/readdir.js";
3
- import { uploadSFTPFile } from "./ssh/upload.js";
6
+ import { putSFTPFile, uploadSFTPFile } from "./ssh/upload.js";
7
+ import { putSFTPFiles, uploadSFTPFiles } from "./ssh/upload-files.js";
4
8
  import { initTokens } from "./misc/tokens.js";
5
- export { connectToSSH, initTokens, readSFTPDir, uploadSFTPFile };
9
+ export { connectToSSH, connectToSSHWithKeys, execSFTPCommand, getSFTPFile, initTokens, putSFTPFile, putSFTPFiles, readSFTPDir, startSFTPSession, uploadSFTPFile, uploadSFTPFiles };
@@ -3,4 +3,10 @@ import { Client, ConnectConfig } from "ssh2";
3
3
  * Connect to SSH
4
4
  */
5
5
  declare function connectToSSH(config: ConnectConfig): Promise<Client>;
6
- export { connectToSSH };
6
+ /**
7
+ * Connect with keys
8
+ *
9
+ * Keys can be provided as keys or as files to load keys from
10
+ */
11
+ declare function connectToSSHWithKeys(host: string, username: string, keysOrFilenames: string | string[]): Promise<Client>;
12
+ export { connectToSSH, connectToSSHWithKeys };
@@ -1,3 +1,4 @@
1
+ import { readFile } from "node:fs/promises";
1
2
  import { Client } from "ssh2";
2
3
  /**
3
4
  * Connect to SSH
@@ -16,4 +17,32 @@ function connectToSSH(config) {
16
17
  }
17
18
  });
18
19
  }
19
- export { connectToSSH };
20
+ /**
21
+ * Connect with keys
22
+ *
23
+ * Keys can be provided as keys or as files to load keys from
24
+ */
25
+ async function connectToSSHWithKeys(host, username, keysOrFilenames) {
26
+ const keys = [];
27
+ for (const key of Array.isArray(keysOrFilenames) ? keysOrFilenames : [keysOrFilenames]) {
28
+ if (key.startsWith("-----BEGIN")) {
29
+ keys.push(key);
30
+ continue;
31
+ }
32
+ const files = key.split(",").map((file) => file.trim());
33
+ for (const file of files) {
34
+ const content = await readFile(file, "utf-8");
35
+ keys.push(content);
36
+ }
37
+ }
38
+ for (const privateKey of keys) try {
39
+ return await connectToSSH({
40
+ host,
41
+ port: 22,
42
+ username,
43
+ privateKey
44
+ });
45
+ } catch (err) {}
46
+ throw new Error(`Failed to connect to ${host} with provided API keys`);
47
+ }
48
+ export { connectToSSH, connectToSSHWithKeys };
@@ -0,0 +1,10 @@
1
+ import { Client } from "ssh2";
2
+ import { ExecResult } from "@cyberalien/git-utils/lib/misc/exec.js";
3
+ interface SSHExecResult extends ExecResult {
4
+ code: number;
5
+ }
6
+ /**
7
+ * Execute command, optionally log output immediately
8
+ */
9
+ declare function execSFTPCommand(client: Client, cmd: string, log?: boolean): Promise<SSHExecResult>;
10
+ export { SSHExecResult, execSFTPCommand };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Execute command, optionally log output immediately
3
+ */
4
+ function execSFTPCommand(client, cmd, log = false) {
5
+ return new Promise((resolve, reject) => {
6
+ client.exec(cmd, (err, stream) => {
7
+ if (err) {
8
+ reject(err);
9
+ return;
10
+ }
11
+ let stdout = "";
12
+ let stderr = "";
13
+ stream.on("close", (code) => {
14
+ resolve({
15
+ stdout,
16
+ stderr,
17
+ code
18
+ });
19
+ });
20
+ stream.stdout.on("data", (data) => {
21
+ if (log) console.log(`${data}`);
22
+ stdout += `${data}`;
23
+ });
24
+ stream.stderr.on("data", (data) => {
25
+ if (log) console.error(`${data}`);
26
+ stderr += `${data}`;
27
+ });
28
+ });
29
+ });
30
+ }
31
+ export { execSFTPCommand };
@@ -0,0 +1,9 @@
1
+ import { SFTPWrapper } from "ssh2";
2
+ /**
3
+ * Get file, optionally save it
4
+ *
5
+ * returnLength is intended to be used to download/save binary buffers without converting them as string.
6
+ * If set, it will return length of data (as string) instead of data itself.
7
+ */
8
+ declare function getSFTPFile(sftp: SFTPWrapper, path: string, saveAs?: string, returnLength?: boolean): Promise<string>;
9
+ export { getSFTPFile };
package/lib/ssh/get.js ADDED
@@ -0,0 +1,19 @@
1
+ import { createFile } from "@cyberalien/git-utils/lib/misc/write.js";
2
+ /**
3
+ * Get file, optionally save it
4
+ *
5
+ * returnLength is intended to be used to download/save binary buffers without converting them as string.
6
+ * If set, it will return length of data (as string) instead of data itself.
7
+ */
8
+ function getSFTPFile(sftp, path, saveAs, returnLength = false) {
9
+ return new Promise((resolve, reject) => {
10
+ sftp.readFile(path, (err, data) => {
11
+ if (err) reject(err);
12
+ else if (saveAs) createFile(saveAs, data).then(() => {
13
+ resolve(returnLength ? `${data.length}` : `${data}`);
14
+ }).catch(reject);
15
+ else resolve(returnLength ? `${data.length}` : `${data}`);
16
+ });
17
+ });
18
+ }
19
+ export { getSFTPFile };
@@ -1,4 +1,4 @@
1
- import { Client, Stats } from "ssh2";
1
+ import { SFTPWrapper, Stats } from "ssh2";
2
2
  interface FilesListItem {
3
3
  filename: string;
4
4
  longname: string;
@@ -7,5 +7,5 @@ interface FilesListItem {
7
7
  /**
8
8
  * Read remote directory
9
9
  */
10
- declare function readSFTPDir(client: Client, path: string): Promise<FilesListItem[]>;
10
+ declare function readSFTPDir(sftp: SFTPWrapper, path: string): Promise<FilesListItem[]>;
11
11
  export { readSFTPDir };
@@ -1,20 +1,14 @@
1
1
  /**
2
2
  * Read remote directory
3
3
  */
4
- function readSFTPDir(client, path) {
4
+ function readSFTPDir(sftp, path) {
5
5
  return new Promise((resolve, reject) => {
6
- client.sftp((err, sftp) => {
6
+ sftp.readdir(path, (err, list) => {
7
7
  if (err) {
8
8
  reject(err);
9
9
  return;
10
10
  }
11
- sftp.readdir(path, (err, list) => {
12
- if (err) {
13
- reject(err);
14
- return;
15
- }
16
- resolve(list);
17
- });
11
+ resolve(list);
18
12
  });
19
13
  });
20
14
  }
@@ -0,0 +1,6 @@
1
+ import { Client, SFTPWrapper } from "ssh2";
2
+ /**
3
+ * Start SFTP session
4
+ */
5
+ declare function startSFTPSession(client: Client): Promise<SFTPWrapper>;
6
+ export { startSFTPSession };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Start SFTP session
3
+ */
4
+ function startSFTPSession(client) {
5
+ return new Promise((resolve, reject) => {
6
+ client.sftp((err, sftp) => {
7
+ if (err) {
8
+ reject(err);
9
+ return;
10
+ }
11
+ resolve(sftp);
12
+ });
13
+ });
14
+ }
15
+ export { startSFTPSession };
@@ -0,0 +1,13 @@
1
+ import { Client } from "ssh2";
2
+ import { GitRepoFilesList } from "@cyberalien/git-utils/lib/git/index.js";
3
+ /**
4
+ * Upload multiple files
5
+ *
6
+ * For binary files use putSFTPFiles() instead
7
+ */
8
+ declare function uploadSFTPFiles(client: Client, remoteRootDir: string, files: GitRepoFilesList): Promise<void>;
9
+ /**
10
+ * Upload multiple local files
11
+ */
12
+ declare function putSFTPFiles(client: Client, localRootDir: string, remoteRootDir: string, filenames: string[]): Promise<void>;
13
+ export { putSFTPFiles, uploadSFTPFiles };
@@ -0,0 +1,75 @@
1
+ import { execSFTPCommand } from "./exec.js";
2
+ import { startSFTPSession } from "./sftp.js";
3
+ import { putSFTPFile, uploadSFTPFile } from "./upload.js";
4
+ import { dirname, join } from "node:path/posix";
5
+ /**
6
+ * Create directories for files
7
+ */
8
+ async function createDirectories(client, rootDir, filenames) {
9
+ const directories = /* @__PURE__ */ new Set();
10
+ for (const file of filenames) {
11
+ let dir = dirname(file);
12
+ if (dir.startsWith("/")) {
13
+ if (rootDir) throw new Error(`File path ${file} is absolute`);
14
+ dir = dir.slice(1);
15
+ }
16
+ if (dir) directories.add(dir);
17
+ }
18
+ const sortedDirs = Array.from(directories).sort((a, b) => b.length - a.length);
19
+ const dirsToCreate = [];
20
+ for (const dir of sortedDirs) {
21
+ const match = `${dir}/`;
22
+ if (!dirsToCreate.some((d) => d.startsWith(match))) dirsToCreate.push(dir);
23
+ }
24
+ const commands = [`mkdir -p ${dirsToCreate.map((dir) => `"${join(rootDir, dir)}"`).join(" ")}`];
25
+ if (rootDir) {
26
+ commands.unshift(`cd "${rootDir}"`);
27
+ commands.unshift(`mkdir -p "${rootDir}"`);
28
+ }
29
+ const execResult = await execSFTPCommand(client, commands.join(" && "));
30
+ if (execResult.code !== 0) throw new Error(`Failed to create directories: ${execResult.stderr || execResult.stdout}`);
31
+ }
32
+ /**
33
+ * Upload multiple files
34
+ *
35
+ * For binary files use putSFTPFiles() instead
36
+ */
37
+ async function uploadSFTPFiles(client, remoteRootDir, files) {
38
+ const filenames = [];
39
+ let filesCount = 0;
40
+ for (const file in files) {
41
+ filesCount++;
42
+ if (files[file] === null) continue;
43
+ filenames.push(file);
44
+ }
45
+ await createDirectories(client, remoteRootDir, filenames);
46
+ const sftp = await startSFTPSession(client);
47
+ let index = 0;
48
+ for (const file in files) {
49
+ index++;
50
+ const content = files[file];
51
+ const target = join(remoteRootDir, file);
52
+ if (content === null) {
53
+ console.log(`[${index} / ${filesCount}] Deleting ${file}`);
54
+ await execSFTPCommand(client, `rm -f "${target}"`);
55
+ continue;
56
+ }
57
+ console.log(`[${index} / ${filesCount}] Uploading ${file}`);
58
+ await uploadSFTPFile(sftp, target, typeof content === "string" ? content : JSON.stringify(content, null, 2) + "\n");
59
+ }
60
+ }
61
+ /**
62
+ * Upload multiple local files
63
+ */
64
+ async function putSFTPFiles(client, localRootDir, remoteRootDir, filenames) {
65
+ await createDirectories(client, remoteRootDir, filenames);
66
+ const sftp = await startSFTPSession(client);
67
+ const filesCount = filenames.length;
68
+ let index = 0;
69
+ for (const file of filenames) {
70
+ index++;
71
+ console.log(`[${index} / ${filesCount}] Uploading ${file}`);
72
+ await putSFTPFile(sftp, join(localRootDir, file), join(remoteRootDir, file));
73
+ }
74
+ }
75
+ export { putSFTPFiles, uploadSFTPFiles };
@@ -1,6 +1,12 @@
1
- import { Client } from "ssh2";
1
+ import { SFTPWrapper } from "ssh2";
2
2
  /**
3
3
  * Upload file
4
+ *
5
+ * For binary files use putSFTPFile() instead
4
6
  */
5
- declare function uploadSFTPFile(client: Client, path: string, content: string): Promise<void>;
6
- export { uploadSFTPFile };
7
+ declare function uploadSFTPFile(sftp: SFTPWrapper, remoteFile: string, content: string): Promise<void>;
8
+ /**
9
+ * Upload local file
10
+ */
11
+ declare function putSFTPFile(sftp: SFTPWrapper, localFile: string, remoteFile: string): Promise<void>;
12
+ export { putSFTPFile, uploadSFTPFile };
package/lib/ssh/upload.js CHANGED
@@ -1,21 +1,31 @@
1
1
  /**
2
2
  * Upload file
3
+ *
4
+ * For binary files use putSFTPFile() instead
3
5
  */
4
- function uploadSFTPFile(client, path, content) {
6
+ function uploadSFTPFile(sftp, remoteFile, content) {
5
7
  return new Promise((resolve, reject) => {
6
- client.sftp((err, sftp) => {
8
+ sftp.writeFile(remoteFile, content, (err) => {
7
9
  if (err) {
8
10
  reject(err);
9
11
  return;
10
12
  }
11
- sftp.writeFile(path, content, (err) => {
12
- if (err) {
13
- reject(err);
14
- return;
15
- }
16
- resolve();
17
- });
13
+ resolve();
18
14
  });
19
15
  });
20
16
  }
21
- export { uploadSFTPFile };
17
+ /**
18
+ * Upload local file
19
+ */
20
+ function putSFTPFile(sftp, localFile, remoteFile) {
21
+ return new Promise((resolve, reject) => {
22
+ sftp.fastPut(localFile, remoteFile, (err) => {
23
+ if (err) {
24
+ reject(err);
25
+ return;
26
+ }
27
+ resolve();
28
+ });
29
+ });
30
+ }
31
+ export { putSFTPFile, uploadSFTPFile };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "type": "module",
4
4
  "description": "Functions for deploying stuff.",
5
5
  "author": "Vjacheslav Trushkin",
6
- "version": "0.0.1",
6
+ "version": "0.0.3",
7
7
  "license": "UNLICENSED",
8
8
  "homepage": "https://cyberalien.dev/",
9
9
  "sideEffects": false,
@@ -15,15 +15,15 @@
15
15
  "*.d.ts"
16
16
  ],
17
17
  "devDependencies": {
18
- "@types/node": "^25.6.0",
18
+ "@types/node": "^25.9.4",
19
19
  "@types/ssh2": "^1.15.5",
20
- "oxlint": "^1.60.0",
21
- "tsdown": "^0.21.9",
20
+ "oxlint": "^1.70.0",
21
+ "tsdown": "^0.21.10",
22
22
  "typescript": "^6.0.3",
23
- "vitest": "^4.1.4"
23
+ "vitest": "^4.1.9"
24
24
  },
25
25
  "dependencies": {
26
- "@cyberalien/git-utils": "^0.0.8",
26
+ "@cyberalien/git-utils": "^0.0.11",
27
27
  "ssh2": "^1.17.0"
28
28
  },
29
29
  "scripts": {