@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.
- package/lib/commands/config.d.ts +3 -0
- package/lib/commands/config.js +3 -0
- package/lib/commands/node.d.ts +14 -0
- package/lib/commands/node.js +15 -0
- package/lib/commands/ubuntu.d.ts +5 -0
- package/lib/commands/ubuntu.js +5 -0
- package/lib/index.d.ts +7 -3
- package/lib/index.js +7 -3
- package/lib/ssh/connect.d.ts +7 -1
- package/lib/ssh/connect.js +30 -1
- package/lib/ssh/exec.d.ts +10 -0
- package/lib/ssh/exec.js +31 -0
- package/lib/ssh/get.d.ts +9 -0
- package/lib/ssh/get.js +19 -0
- package/lib/ssh/readdir.d.ts +2 -2
- package/lib/ssh/readdir.js +3 -9
- package/lib/ssh/sftp.d.ts +6 -0
- package/lib/ssh/sftp.js +15 -0
- package/lib/ssh/upload-files.d.ts +13 -0
- package/lib/ssh/upload-files.js +75 -0
- package/lib/ssh/upload.d.ts +9 -3
- package/lib/ssh/upload.js +20 -10
- package/package.json +6 -6
|
@@ -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 };
|
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 };
|
package/lib/ssh/connect.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/lib/ssh/connect.js
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/lib/ssh/exec.js
ADDED
|
@@ -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 };
|
package/lib/ssh/get.d.ts
ADDED
|
@@ -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 };
|
package/lib/ssh/readdir.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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(
|
|
10
|
+
declare function readSFTPDir(sftp: SFTPWrapper, path: string): Promise<FilesListItem[]>;
|
|
11
11
|
export { readSFTPDir };
|
package/lib/ssh/readdir.js
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Read remote directory
|
|
3
3
|
*/
|
|
4
|
-
function readSFTPDir(
|
|
4
|
+
function readSFTPDir(sftp, path) {
|
|
5
5
|
return new Promise((resolve, reject) => {
|
|
6
|
-
|
|
6
|
+
sftp.readdir(path, (err, list) => {
|
|
7
7
|
if (err) {
|
|
8
8
|
reject(err);
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
if (err) {
|
|
13
|
-
reject(err);
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
resolve(list);
|
|
17
|
-
});
|
|
11
|
+
resolve(list);
|
|
18
12
|
});
|
|
19
13
|
});
|
|
20
14
|
}
|
package/lib/ssh/sftp.js
ADDED
|
@@ -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 };
|
package/lib/ssh/upload.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import {
|
|
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(
|
|
6
|
-
|
|
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(
|
|
6
|
+
function uploadSFTPFile(sftp, remoteFile, content) {
|
|
5
7
|
return new Promise((resolve, reject) => {
|
|
6
|
-
|
|
8
|
+
sftp.writeFile(remoteFile, content, (err) => {
|
|
7
9
|
if (err) {
|
|
8
10
|
reject(err);
|
|
9
11
|
return;
|
|
10
12
|
}
|
|
11
|
-
|
|
12
|
-
if (err) {
|
|
13
|
-
reject(err);
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
resolve();
|
|
17
|
-
});
|
|
13
|
+
resolve();
|
|
18
14
|
});
|
|
19
15
|
});
|
|
20
16
|
}
|
|
21
|
-
|
|
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.
|
|
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.
|
|
18
|
+
"@types/node": "^25.9.4",
|
|
19
19
|
"@types/ssh2": "^1.15.5",
|
|
20
|
-
"oxlint": "^1.
|
|
21
|
-
"tsdown": "^0.21.
|
|
20
|
+
"oxlint": "^1.70.0",
|
|
21
|
+
"tsdown": "^0.21.10",
|
|
22
22
|
"typescript": "^6.0.3",
|
|
23
|
-
"vitest": "^4.1.
|
|
23
|
+
"vitest": "^4.1.9"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@cyberalien/git-utils": "^0.0.
|
|
26
|
+
"@cyberalien/git-utils": "^0.0.11",
|
|
27
27
|
"ssh2": "^1.17.0"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|