uzdu 1.0.18 → 1.1.1

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/README.md CHANGED
@@ -29,19 +29,19 @@ bunx uzdu -h
29
29
 
30
30
  ### uploading
31
31
 
32
- - [Amazon S3](https://docs.aws.amazon.com/s3/) `npx uzdu upload aws -h`
33
- - [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs) `npx uzdu upload az -h`
34
- - [Nexus](https://support.sonatype.com/hc/en-us/articles/115006744008-Repository-How-can-I-programmatically-upload-files-into-Nexus-3#DirectUploadusingHTTPPUTtotheRepositoryPath) `npx uzdu upload http -h`
35
- - SSH/SCP `npx uzdu upload ssh -h`
32
+ - [Amazon S3](https://docs.aws.amazon.com/s3/) `npx uzdu upload aws --dotenv /projects/environments/test.env -- build/index.html -- uzdu:ru-central1-d:http://storage.yandexcloud.net`
33
+ - [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs) `AZURE_STORAGE_CONNECTION_STRING=...; npx uzdu upload azure build/ $web`
34
+ - [Nexus](https://support.sonatype.com/hc/en-us/articles/115006744008-Repository-How-can-I-programmatically-upload-files-into-Nexus-3#DirectUploadusingHTTPPUTtotheRepositoryPath) `npx upload http --header "Authorization: Basic TOKEN=" -- website.zip https://nexus/repository/private-raw/dist/test-uzdu/website.zip",`
35
+ - SSH/SFTP `npx uzdu upload ssh /projects/website/build/ sftp://root:password@example.localtest.me/var/www/html/`
36
36
 
37
37
  ### downloading
38
38
 
39
- - http `npx uzdu download http -h`
39
+ - http `npx uzdu download http --dotenv --header \"Authorization: Basic TOKEN=\" https://nexus/repository/private-raw/dist/test-uzdu/website.zip website.zip`
40
40
 
41
41
  ### working with zip-archives
42
42
 
43
- - zip `npx uzdu zip -h`
44
- - unzip `npx uzdu unzip -h`
43
+ - zip `npx uzdu zip build/ ./build.zip`
44
+ - unzip `npx uzdu unzip /tmp/repo.zip ./src`
45
45
 
46
46
 
47
47
  ## For developers
@@ -3,160 +3,48 @@ import {
3
3
  getEnvironment,
4
4
  initEnvironment,
5
5
  listFiles,
6
- resolvePath
7
- } from "./chunk-OIXJ4D3Z.js";
8
-
9
- // src/azure.ts
10
- var azure_exports = {};
11
- __export(azure_exports, {
12
- default: () => upload
13
- });
14
- import { BlobServiceClient } from "@azure/storage-blob";
15
- import path from "path";
16
- import fs from "fs";
17
- async function upload(dir, options, metadataFile = ".metadata.json") {
18
- if (!options.connectionString) throw Error("Uploader needs connection string for Azure Blob Storage. Provide AZURE_STORAGE_CONNECTION_STRING environment variable!");
19
- const opts = Object.assign({}, { container: "$web" }, options);
20
- const blobServiceClient = BlobServiceClient.fromConnectionString(options.connectionString);
21
- const isDebug = process.env.DEBUG && process.env.DEBUG.toLowerCase() === "true";
22
- const containerClient = blobServiceClient.getContainerClient(opts.container);
23
- let dist = path.resolve(process.cwd(), dir);
24
- const files = await listFiles(dir);
25
- let metadata;
26
- try {
27
- const metadataJson = fs.readFileSync(path.join(dir, metadataFile), { encoding: "utf-8" });
28
- metadata = JSON.parse(metadataJson);
29
- } catch (e) {
30
- }
31
- if (Object.keys(files).length == 1) {
32
- const lstat = fs.lstatSync(dist);
33
- if (lstat.isFile()) {
34
- dist = path.dirname(dist);
35
- }
36
- }
37
- await Promise.all(Object.entries(files).map(async ([file, absFile]) => {
38
- let blobObj;
39
- if (metadata) {
40
- blobObj = metadata[file];
41
- }
42
- const blockBlobClient = containerClient.getBlockBlobClient(file);
43
- const blobHTTPHeaders = {};
44
- if (blobObj?.headers) {
45
- const { CacheControl, ContentType } = blobObj.headers;
46
- blobHTTPHeaders.blobCacheControl = CacheControl;
47
- blobHTTPHeaders.blobContentType = ContentType;
48
- }
49
- const localFilePath = absFile;
50
- await blockBlobClient.uploadFile(localFilePath, { blobHTTPHeaders });
51
- }));
52
- }
53
-
54
- // src/s3.ts
55
- var s3_exports = {};
56
- __export(s3_exports, {
57
- default: () => upload2
58
- });
59
- import { S3Client } from "@aws-sdk/client-s3";
60
- import { Upload } from "@aws-sdk/lib-storage";
61
- import fs2 from "fs";
62
- import path2 from "path";
63
- async function upload2(dir, s3config, metadataFile = ".metadata.json") {
64
- if (!s3config.accessKeyId || !s3config.secretAccessKey) {
65
- throw new Error("AWS credentials not found in environment variables AWS_KEY_ID and AWS_SECRET_KEY.");
66
- }
67
- if (!s3config.region) {
68
- throw new Error('Neither "region" in the bucket address nor AWS_REGION environment variable was found.');
69
- }
70
- if (!s3config.bucket) {
71
- throw new Error("Amazon S3 bucket name is required");
72
- }
73
- const { accessKeyId, secretAccessKey, region, endpoint } = s3config;
74
- const client = new S3Client({
75
- credentials: {
76
- accessKeyId,
77
- secretAccessKey
78
- },
79
- region,
80
- endpoint
81
- });
82
- let dist = path2.resolve(process.cwd(), dir);
83
- const files = await listFiles(dist);
84
- let metadata;
85
- try {
86
- const metadataJson = fs2.readFileSync(path2.join(dir, metadataFile), { encoding: "utf-8" });
87
- metadata = JSON.parse(metadataJson);
88
- } catch (e) {
89
- }
90
- if (Object.keys(files).length == 1) {
91
- const lstat = fs2.lstatSync(dist);
92
- if (lstat.isFile()) {
93
- dist = path2.dirname(dist);
94
- }
95
- }
96
- await Promise.all(Object.entries(files).map(async ([file, absFile]) => {
97
- const filePath = absFile;
98
- const fileContent = fs2.readFileSync(filePath);
99
- const params = {
100
- Bucket: s3config.bucket,
101
- Key: file,
102
- Body: fileContent
103
- };
104
- if (metadata) {
105
- const blobObj = metadata[file];
106
- if (blobObj && blobObj.headers) {
107
- const { CacheControl, ContentType } = blobObj.headers;
108
- if (CacheControl) params.CacheControl = CacheControl;
109
- if (ContentType) params.ContentType = ContentType;
110
- }
111
- }
112
- return new Upload({
113
- client,
114
- params,
115
- tags: [],
116
- queueSize: 4,
117
- // optional concurrency configuration
118
- partSize: 1024 * 1024 * 5,
119
- // optional size of each part, in bytes, at least 5MB
120
- leavePartsOnError: false
121
- // optional manually handle dropped parts
122
- }).done();
123
- }));
124
- }
6
+ resolvePath,
7
+ runSequentially
8
+ } from "./chunk-WWXWWCCX.js";
125
9
 
126
10
  // src/ssh.ts
127
11
  var ssh_exports = {};
128
12
  __export(ssh_exports, {
13
+ execute: () => execute,
129
14
  getConnectConfig: () => getConnectConfig,
130
15
  getCredentials: () => getCredentials,
131
16
  getDirMap: () => getDirMap,
132
17
  getMakeDirs: () => getMakeDirs,
133
18
  getRemoteDestination: () => getRemoteDestination,
134
- upload: () => upload3
19
+ upload: () => upload
135
20
  });
136
21
  import { Client } from "ssh2";
137
- import fs3 from "fs";
138
- import path3 from "path";
22
+ import fs from "fs";
23
+ import path from "path";
139
24
  import deepmerge from "deepmerge";
140
- async function upload3(source, sftpUrl, sshCredentials) {
25
+ async function upload(source, sftpUrl, options) {
141
26
  await new Promise((resolve, reject) => {
142
- fs3.stat(source, async (err, stats) => {
27
+ fs.stat(source, async (err, stats) => {
28
+ if (err) {
29
+ reject(err);
30
+ return;
31
+ }
143
32
  if (stats.isSymbolicLink()) {
144
33
  reject(new Error(`${source} is symlink`));
145
34
  } else {
146
35
  let sshConnection;
147
36
  try {
148
37
  const _connectConfig = getConnectConfig(sftpUrl);
149
- const _sshCredentials = sshCredentials || getCredentials();
38
+ const _sshCredentials = _connectConfig.password ? void 0 : getCredentials(options);
150
39
  const connectConfig = { ..._connectConfig, ..._sshCredentials };
151
40
  sshConnection = await connect(connectConfig);
152
41
  const files = await listFiles(source);
153
- const destination = getRemoteDestination(sftpUrl);
42
+ const destination = normilizeSftpPath(_connectConfig.path) || "";
154
43
  const _source = source.replace(/\/+$/, "");
155
44
  await mkdirs(sshConnection, destination, files);
156
45
  await uploadFiles(files, _source, destination, sshConnection);
157
46
  resolve();
158
47
  } catch (e) {
159
- console.error("SFTP error", e);
160
48
  reject(e);
161
49
  } finally {
162
50
  sshConnection?.destroy();
@@ -165,22 +53,69 @@ async function upload3(source, sftpUrl, sshCredentials) {
165
53
  });
166
54
  });
167
55
  }
56
+ async function execute(sshAddress, commands, options) {
57
+ await new Promise(async (resolve, reject) => {
58
+ let sshConnection;
59
+ try {
60
+ const _connectConfig = getConnectConfig(sshAddress);
61
+ const _sshCredentials = _connectConfig.password ? void 0 : getCredentials(options);
62
+ const connectConfig = { ..._connectConfig, ..._sshCredentials };
63
+ sshConnection = await connect(connectConfig);
64
+ const shellOptions = {
65
+ ...options,
66
+ cwd: normilizeSftpPath(_connectConfig.path)
67
+ };
68
+ await shellExec(sshConnection, commands, shellOptions);
69
+ resolve();
70
+ } catch (e) {
71
+ reject(e);
72
+ } finally {
73
+ sshConnection?.destroy();
74
+ }
75
+ });
76
+ }
168
77
  async function mkdirs(sshConnection, destination, sources) {
169
78
  const fileMap = getDirMap(sources);
170
79
  const makeDirs = getMakeDirs(fileMap, destination);
171
80
  const commands = makeDirs ? makeDirs.map((dir) => `mkdir -p "${dir}"`) : [`mkdir -p "${destination}"`];
172
- const commandLine = commands.length > 1 ? commands.join(";") : commands[0];
173
- await new Promise((res, rej) => {
174
- sshConnection.exec(commandLine, {}, (err, channel) => {
175
- if (err) {
176
- console.error("mkdir error", err);
177
- rej(new Error(`failed: mkdir -p ... : ${err}`));
178
- } else {
179
- channel.on("exit", (code, signal) => {
180
- if (code != 0) rej(new Error(`Exit code: ${code} for "mkdir -p ..."`));
181
- else res();
182
- });
183
- }
81
+ await shellExec(sshConnection, commands);
82
+ }
83
+ async function shellExec(sshConnection, commands, options) {
84
+ if (!commands) {
85
+ return Promise.reject("shellExec did not get any command");
86
+ }
87
+ if (options?.cwd) {
88
+ const command = `set -e; cd ${options.cwd}; set +e; ${commands.join(";")}`;
89
+ await shellCommand(sshConnection, command, options);
90
+ } else {
91
+ const shellCommands = commands.map((command) => () => shellCommand(sshConnection, command, options));
92
+ await runSequentially(shellCommands);
93
+ }
94
+ }
95
+ async function shellCommand(sshClient, command, options) {
96
+ return new Promise((res, rej) => {
97
+ const decoder = new TextDecoder();
98
+ sshClient.exec(command, (err, stream) => {
99
+ if (err) rej(err);
100
+ stream.on("close", (code, signal) => {
101
+ if (code != 0) {
102
+ options?.callback?.({ error: `closing SSH by signal=${signal} and exit code=${code}`, code, signal });
103
+ rej(new Error(`Close code ${code}`));
104
+ } else {
105
+ res(code);
106
+ }
107
+ }).on("exit", (code, signal) => {
108
+ if (code != 0) {
109
+ options?.callback?.({ error: `Exit command signal=${signal} and exit code=${code}`, code, signal });
110
+ rej(new Error(`Exit code ${code}`));
111
+ } else {
112
+ res(code);
113
+ }
114
+ }).on("data", (message) => {
115
+ options?.callback?.({ message: decoder.decode(message).replace(/\n$/, "") });
116
+ }).stderr.on("data", (error) => {
117
+ options?.callback?.({ error: decoder.decode(error) });
118
+ });
184
119
  });
185
120
  });
186
121
  }
@@ -198,7 +133,7 @@ function _uploadFile(source, destination, sftp) {
198
133
  else resolve();
199
134
  });
200
135
  } else if (stats.isDirectory()) {
201
- const f = path3.basename(source);
136
+ const f = path.basename(source);
202
137
  reject(new Error(`Overwriting directory ${destination} with the file ${f} is not allowed. Remove the directory manually.`));
203
138
  } else {
204
139
  reject(new Error("Remote path is symlink"));
@@ -210,33 +145,29 @@ function uploadFiles(sourceFiles, source, destination, sshConnection) {
210
145
  return new Promise((resolve, reject) => {
211
146
  sshConnection.sftp(async (err, sftp) => {
212
147
  if (err) {
213
- console.error("uploadFiles error");
214
148
  reject(err);
215
149
  } else {
216
150
  if (Object.keys(sourceFiles).length == 1) {
217
- const lstat2 = fs3.lstatSync(source);
151
+ const lstat2 = fs.lstatSync(source);
218
152
  if (lstat2.isFile()) {
219
- const dest = path3.join(destination, Object.keys(sourceFiles)[0]).replace(/\\/g, "/");
153
+ const dest = path.join(destination, Object.keys(sourceFiles)[0]).replace(/\\/g, "/");
220
154
  const src = source;
221
155
  await _uploadFile(src, dest, sftp).then(() => resolve()).catch((e) => {
222
- console.error(src);
223
156
  reject(e);
224
157
  });
225
158
  return;
226
159
  }
227
160
  }
228
161
  let sourceDir = source;
229
- const lstat = fs3.lstatSync(source);
162
+ const lstat = fs.lstatSync(source);
230
163
  if (lstat.isSymbolicLink()) {
231
- sourceDir = fs3.readlinkSync(source);
164
+ sourceDir = fs.readlinkSync(source);
232
165
  }
233
166
  const promises = [];
234
167
  Object.entries(sourceFiles).map(([baseName, absPath]) => {
235
- const dest = path3.join(destination, baseName).replace(/\\/g, "/");
168
+ const dest = path.join(destination, baseName).replace(/\\/g, "/");
236
169
  const promise = new Promise((res, rej) => {
237
170
  _uploadFile(absPath, dest, sftp).then(() => res()).catch((e) => {
238
- console.error(absPath);
239
- console.error(e);
240
171
  rej(e);
241
172
  });
242
173
  });
@@ -253,7 +184,7 @@ async function connect(sshConfig) {
253
184
  try {
254
185
  return await new Promise((resolve, reject) => {
255
186
  conn.on("error", (e) => {
256
- reject(new Error(`Target host error: ${e}`));
187
+ reject(e);
257
188
  }).on("ready", () => {
258
189
  resolve(conn);
259
190
  }).connect({
@@ -273,7 +204,6 @@ async function connect(sshConfig) {
273
204
  });
274
205
  });
275
206
  } catch (e) {
276
- console.error("Connection failed", e);
277
207
  conn.destroy();
278
208
  throw e;
279
209
  }
@@ -309,7 +239,7 @@ function getFileMap(file) {
309
239
  const parts = theFile.split("/");
310
240
  if (parts.length == 1) return { [parts[0]]: false };
311
241
  else {
312
- const aFile = path3.join(...parts.slice(1)).replace(/\\/g, "/");
242
+ const aFile = path.join(...parts.slice(1)).replace(/\\/g, "/");
313
243
  const fileMapEntry = getFileMap(aFile);
314
244
  return { [parts[0]]: fileMapEntry };
315
245
  }
@@ -327,13 +257,13 @@ function getCredentials(options) {
327
257
  if (uzduKeyPath) {
328
258
  const resolvedKeyPath = resolvePath(uzduKeyPath);
329
259
  try {
330
- privateKey = fs3.readFileSync(resolvedKeyPath);
260
+ privateKey = fs.readFileSync(resolvedKeyPath);
331
261
  } catch (e) {
332
262
  throw new Error(`Not found private Key file ${resolvedKeyPath}`);
333
263
  }
334
264
  } else {
335
265
  const uzduPassword = process.env.UZDU_SSH_PASSWORD;
336
- if (!uzduPassword) throw new Error("Specify either --privateKeyPath or password in SFTP URL. Otherwise consider using --dotenv and one of environment variables: UZDU_SSH_KEY_PATH, UZDU_SSH_KEY, UZDU_SSH_PASSWORD");
266
+ if (!uzduPassword) throw new Error("Specify password in SFTP URL. Otherwise consider using one of environment variables: UZDU_SSH_KEY_PATH, UZDU_SSH_KEY, UZDU_SSH_PASSWORD");
337
267
  password = uzduPassword;
338
268
  }
339
269
  }
@@ -345,17 +275,34 @@ function getCredentials(options) {
345
275
  return authConfig;
346
276
  }
347
277
  var sftpUrlRegex = /^sftp:\/\/(?:(?<username>[\w\.\-]{1,32})(?::(?<password>.+))?@)?(?:(?<host>[\w\.\-]+)|\[(?<ipv6>[\d:]+)\])(?::(?<port>\d{1,5}))?\/(?<path>.*)$/g;
348
- function getConnectConfig(sftpUrl) {
278
+ var sshUrlRegex = /^(?:(?<username>[\w\.\-]{1,32})(?::(?<password>.+))?@)?(?:(?<host>[\w\.\-]+)|\[(?<ipv6>[\d:]+)\])(?::(?<port>\d{1,5}))?$/g;
279
+ var suspectSftpRegex = /^sftp:\/\/.+/g;
280
+ function getConnectConfig(url) {
281
+ let execArray;
282
+ sshUrlRegex.lastIndex = 0;
349
283
  sftpUrlRegex.lastIndex = 0;
350
- const execArray = sftpUrlRegex.exec(sftpUrl);
284
+ if (sftpUrlRegex.test(url)) {
285
+ sftpUrlRegex.lastIndex = 0;
286
+ execArray = sftpUrlRegex.exec(url);
287
+ } else if (sshUrlRegex.test(url)) {
288
+ sshUrlRegex.lastIndex = 0;
289
+ suspectSftpRegex.lastIndex = 0;
290
+ if (suspectSftpRegex.test(url)) {
291
+ throw new Error(`SSH URL starts with "sftp://". If it is sftp URL, then add trailing slash after hostname[:port] - ${url}/, if it is SSH URL, consider password that does not start with 2 slashes.`);
292
+ }
293
+ execArray = sshUrlRegex.exec(url);
294
+ } else {
295
+ throw new Error(`Not an SFTP or SSH address: ${url}`);
296
+ }
351
297
  const { groups } = execArray ?? {};
352
298
  const host = groups?.host || groups?.ipv6;
353
- if (!host) throw new Error(`Wrong URL "${sftpUrl}": host or ivp6 is not specified`);
299
+ if (!host) throw new Error(`Wrong URL "${url}": host or ivp6 is not specified`);
354
300
  const username = groups?.username;
355
301
  const password = groups?.password;
302
+ const path2 = groups?.path;
356
303
  const _port = parseInt(groups.port);
357
304
  const port = isNaN(_port) ? void 0 : _port;
358
- const connectConfig = { username, password, host, port };
305
+ const connectConfig = { username, password, host, port, path: path2 };
359
306
  return connectConfig;
360
307
  }
361
308
  function getRemoteDestination(sftpUrl) {
@@ -363,31 +310,33 @@ function getRemoteDestination(sftpUrl) {
363
310
  const execArray = sftpUrlRegex.exec(sftpUrl);
364
311
  if (!execArray) throw new Error("Wrong sftp URL");
365
312
  if (!execArray.groups) throw new Error("Wrong URL: path is not specified");
366
- const path4 = execArray.groups.path;
367
- const re = /^(?<first>[^\/]+)(?:\/)?(?<second>.*)?/g;
313
+ const path2 = execArray.groups.path;
314
+ return normilizeSftpPath(execArray.groups?.path) || "";
315
+ }
316
+ function normilizeSftpPath(path2) {
317
+ if (path2 == void 0) return void 0;
318
+ const re = /^(?<first>[^\/]*)(?<root>\/)?(?<second>.*)?/g;
368
319
  re.lastIndex = 0;
369
- const execPathArray = re.exec(path4);
320
+ const execPathArray = re.exec(path2);
370
321
  const { groups } = execPathArray ?? {};
371
322
  const first = groups?.first;
372
323
  const second = groups?.second;
324
+ const root = groups?.root;
373
325
  let dest;
374
326
  if (first) {
375
327
  const execTild = /^(?<tild>~)/.exec(first);
376
328
  const { groups: groups2 } = execTild ?? {};
377
- dest = groups2?.tild ? `${second}` : `/${first}${second ? `/${second}` : ""}`;
329
+ dest = groups2?.tild ? `${second ? second : ""}` : `/${first}${second ? `/${second}` : ""}`;
378
330
  } else {
379
- dest = path4;
331
+ if (!root && !second) dest = "/";
332
+ else dest = path2;
380
333
  }
381
- const destination = dest.replace(/\/+$/, "");
334
+ const destination = dest != "/" ? dest.replace(/\/+$/, "") : dest;
382
335
  return destination;
383
336
  }
384
337
 
385
338
  export {
386
339
  upload,
387
- azure_exports,
388
- upload2,
389
- s3_exports,
390
- upload3,
391
- getCredentials,
340
+ execute,
392
341
  ssh_exports
393
342
  };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  __export,
3
3
  listFiles
4
- } from "./chunk-OIXJ4D3Z.js";
4
+ } from "./chunk-WWXWWCCX.js";
5
5
 
6
6
  // src/http.ts
7
7
  var http_exports = {};
@@ -7,6 +7,7 @@ var __export = (target, all) => {
7
7
  // src/utils.ts
8
8
  var utils_exports = {};
9
9
  __export(utils_exports, {
10
+ SequentilRunError: () => SequentilRunError,
10
11
  addMetadata: () => addMetadata,
11
12
  checkIsFile: () => checkIsFile,
12
13
  doUnzip: () => doUnzip,
@@ -17,6 +18,7 @@ __export(utils_exports, {
17
18
  makeZip: () => makeZip,
18
19
  outputConfiguration: () => outputConfiguration,
19
20
  resolvePath: () => resolvePath,
21
+ runSequentially: () => runSequentially,
20
22
  safeIndex: () => safeIndex,
21
23
  shouldBeDirectory: () => shouldBeDirectory,
22
24
  shouldBeFile: () => shouldBeFile
@@ -230,6 +232,35 @@ function parseEnvironment(src) {
230
232
  }
231
233
  return obj;
232
234
  }
235
+ async function runSequentially(tasks, breaksOnError = true) {
236
+ const results = [];
237
+ for (let i = 0; i < tasks.length; i++) {
238
+ try {
239
+ const result = await tasks[i]();
240
+ results.push(result);
241
+ } catch (err) {
242
+ if (breaksOnError) throw new SequentilRunError(i, `${err.message || err}`);
243
+ else {
244
+ console.error(`Error in #${i}: ${err.message || err}`);
245
+ results.push(null);
246
+ }
247
+ }
248
+ }
249
+ ;
250
+ return results;
251
+ }
252
+ var SequentilRunError = class extends Error {
253
+ /**
254
+ *
255
+ * @param message Text of the error
256
+ * @param index where in the sequense the error happened
257
+ */
258
+ constructor(index, message = "Sequential Error") {
259
+ super(message);
260
+ this.index = index;
261
+ this.message = message;
262
+ }
263
+ };
233
264
 
234
265
  export {
235
266
  __export,
@@ -243,5 +274,6 @@ export {
243
274
  shouldBeDirectory,
244
275
  outputConfiguration,
245
276
  resolvePath,
277
+ runSequentially,
246
278
  utils_exports
247
279
  };
@@ -0,0 +1,133 @@
1
+ import {
2
+ __export,
3
+ listFiles
4
+ } from "./chunk-WWXWWCCX.js";
5
+
6
+ // src/azure.ts
7
+ var azure_exports = {};
8
+ __export(azure_exports, {
9
+ default: () => upload
10
+ });
11
+ import { BlobServiceClient } from "@azure/storage-blob";
12
+ import path from "path";
13
+ import fs from "fs";
14
+ async function upload(dir, options, metadataFile = ".metadata.json") {
15
+ if (!options.connectionString) throw Error("Uploader needs connection string for Azure Blob Storage. Provide AZURE_STORAGE_CONNECTION_STRING environment variable!");
16
+ const opts = Object.assign({}, { container: "$web" }, options);
17
+ const blobServiceClient = BlobServiceClient.fromConnectionString(options.connectionString);
18
+ const isDebug = process.env.DEBUG && process.env.DEBUG.toLowerCase() === "true";
19
+ const containerClient = blobServiceClient.getContainerClient(opts.container);
20
+ let dist = path.resolve(process.cwd(), dir);
21
+ const files = await listFiles(dir);
22
+ let metadata;
23
+ try {
24
+ const metadataJson = fs.readFileSync(path.join(dir, metadataFile), { encoding: "utf-8" });
25
+ metadata = JSON.parse(metadataJson);
26
+ } catch (e) {
27
+ }
28
+ if (Object.keys(files).length == 1) {
29
+ const lstat = fs.lstatSync(dist);
30
+ if (lstat.isFile()) {
31
+ dist = path.dirname(dist);
32
+ }
33
+ }
34
+ await Promise.all(Object.entries(files).map(async ([file, absFile]) => {
35
+ let blobObj;
36
+ if (metadata) {
37
+ blobObj = metadata[file];
38
+ }
39
+ const blockBlobClient = containerClient.getBlockBlobClient(file);
40
+ const blobHTTPHeaders = {};
41
+ if (blobObj?.headers) {
42
+ const { CacheControl, ContentType } = blobObj.headers;
43
+ blobHTTPHeaders.blobCacheControl = CacheControl;
44
+ blobHTTPHeaders.blobContentType = ContentType;
45
+ }
46
+ const localFilePath = absFile;
47
+ await blockBlobClient.uploadFile(localFilePath, { blobHTTPHeaders });
48
+ }));
49
+ }
50
+
51
+ // src/s3.ts
52
+ var s3_exports = {};
53
+ __export(s3_exports, {
54
+ default: () => upload2
55
+ });
56
+ import { S3Client } from "@aws-sdk/client-s3";
57
+ import { Upload } from "@aws-sdk/lib-storage";
58
+ import fs2 from "fs";
59
+ import path2 from "path";
60
+ async function upload2(dir, s3config, metadataFile = ".metadata.json") {
61
+ if (!s3config.accessKeyId || !s3config.secretAccessKey) {
62
+ throw new Error("AWS credentials not found in environment variables AWS_KEY_ID and AWS_SECRET_KEY.");
63
+ }
64
+ if (!s3config.region) {
65
+ throw new Error('Neither "region" in the bucket address nor AWS_REGION environment variable was found.');
66
+ }
67
+ if (!s3config.bucket) {
68
+ throw new Error("Amazon S3 bucket name is required");
69
+ }
70
+ const { accessKeyId, secretAccessKey, region, endpoint } = s3config;
71
+ const client = new S3Client({
72
+ credentials: {
73
+ accessKeyId,
74
+ secretAccessKey
75
+ },
76
+ region,
77
+ endpoint
78
+ });
79
+ let dist = path2.resolve(process.cwd(), dir);
80
+ const files = await listFiles(dist);
81
+ let metadata;
82
+ try {
83
+ const metadataJson = fs2.readFileSync(path2.join(dir, metadataFile), { encoding: "utf-8" });
84
+ metadata = JSON.parse(metadataJson);
85
+ } catch (e) {
86
+ }
87
+ if (Object.keys(files).length == 1) {
88
+ const lstat = fs2.lstatSync(dist);
89
+ if (lstat.isFile()) {
90
+ dist = path2.dirname(dist);
91
+ }
92
+ }
93
+ await Promise.all(Object.entries(files).map(async ([file, absFile]) => {
94
+ const filePath = absFile;
95
+ const fileContent = await new Promise((resolve, reject) => {
96
+ fs2.readFile(filePath, (err, data) => {
97
+ if (err) reject(err);
98
+ else resolve(data);
99
+ });
100
+ });
101
+ const params = {
102
+ Bucket: s3config.bucket,
103
+ Key: file,
104
+ Body: fileContent
105
+ };
106
+ if (metadata) {
107
+ const blobObj = metadata[file];
108
+ if (blobObj && blobObj.headers) {
109
+ const { CacheControl, ContentType } = blobObj.headers;
110
+ if (CacheControl) params.CacheControl = CacheControl;
111
+ if (ContentType) params.ContentType = ContentType;
112
+ }
113
+ }
114
+ return new Upload({
115
+ client,
116
+ params,
117
+ tags: [],
118
+ queueSize: 4,
119
+ // optional concurrency configuration
120
+ partSize: 1024 * 1024 * 5,
121
+ // optional size of each part, in bytes, at least 5MB
122
+ leavePartsOnError: false
123
+ // optional manually handle dropped parts
124
+ }).done();
125
+ }));
126
+ }
127
+
128
+ export {
129
+ upload,
130
+ azure_exports,
131
+ upload2,
132
+ s3_exports
133
+ };
package/lib/uzdu-copy.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  checkIsFile,
4
4
  outputConfiguration
5
- } from "./chunk-OIXJ4D3Z.js";
5
+ } from "./chunk-WWXWWCCX.js";
6
6
 
7
7
  // src/uzdu-copy.ts
8
8
  import { Command } from "commander";
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  download
3
- } from "./chunk-7B56UNA6.js";
3
+ } from "./chunk-LFEIDM4S.js";
4
4
  import {
5
5
  getEnvironment,
6
6
  initEnvironment,
7
7
  outputConfiguration
8
- } from "./chunk-OIXJ4D3Z.js";
8
+ } from "./chunk-WWXWWCCX.js";
9
9
 
10
10
  // src/uzdu-download.ts
11
11
  import { Command, Option } from "commander";
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,28 @@
1
+ import {
2
+ execute
3
+ } from "./chunk-2E5O5JIN.js";
4
+ import {
5
+ outputConfiguration
6
+ } from "./chunk-WWXWWCCX.js";
7
+
8
+ // src/uzdu-exec.ts
9
+ import { Command, Option } from "commander";
10
+ import { consola } from "consola";
11
+ var command = new Command();
12
+ command.description("Execute commands on remote machine").name("uzdu exec");
13
+ command.command("ssh").description("Execute commands via SSH. In addition to sshUrl consider using environment variables UZDU_SSH_KEY_PATH, UZDU_SSH_KEY, UZDU_SSH_PASSWORD").argument("<sshUrl>", "the URL format ssh://[user[:password]@]host[:port]").argument("command", "single line command").addOption(
14
+ new Option("-d|--dotenv [file]", 'load environment variables from a property file, i.e. a file with "key=value" lines.').preset(".env")
15
+ ).addOption(new Option("--privateKeyPath [path to file]", "Path to SSH private key, fallback is UZDU_SSH_KEY_PATH environment variable. Also consider using UZDU_SSH_KEY to provide SSH private key content or UZDU_SSH_PASSWORD.")).action(async (sshUrl, command2, options, thisCommand) => {
16
+ try {
17
+ options.callback = (value) => {
18
+ if (value.message) consola.log(value.message);
19
+ if (value.error) consola.error(value.error);
20
+ };
21
+ await execute(sshUrl, [command2], options);
22
+ } catch (e) {
23
+ thisCommand.error(e.message || e, { exitCode: 127, code: "ssh.upload.error" });
24
+ }
25
+ });
26
+ command.configureOutput(outputConfiguration);
27
+ command.showHelpAfterError(true);
28
+ command.parse();
@@ -2,7 +2,7 @@ import {
2
2
  addMetadata,
3
3
  outputConfiguration,
4
4
  shouldBeDirectory
5
- } from "./chunk-OIXJ4D3Z.js";
5
+ } from "./chunk-WWXWWCCX.js";
6
6
 
7
7
  // src/uzdu-metadata.ts
8
8
  import { Argument, Command } from "commander";
package/lib/uzdu-unzip.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  doUnzip,
4
4
  outputConfiguration
5
- } from "./chunk-OIXJ4D3Z.js";
5
+ } from "./chunk-WWXWWCCX.js";
6
6
 
7
7
  // src/uzdu-unzip.ts
8
8
  import { Command } from "commander";
@@ -1,19 +1,20 @@
1
1
  import {
2
- getCredentials,
3
- upload as upload2,
4
- upload2 as upload3,
5
- upload3 as upload4
6
- } from "./chunk-ACV2CMWZ.js";
2
+ upload as upload3,
3
+ upload2 as upload4
4
+ } from "./chunk-XU5JZHWK.js";
7
5
  import {
8
6
  upload
9
- } from "./chunk-7B56UNA6.js";
7
+ } from "./chunk-LFEIDM4S.js";
8
+ import {
9
+ upload as upload2
10
+ } from "./chunk-2E5O5JIN.js";
10
11
  import {
11
12
  getEnvironment,
12
13
  initEnvironment,
13
14
  outputConfiguration,
14
15
  resolvePath,
15
16
  shouldBeDirectory
16
- } from "./chunk-OIXJ4D3Z.js";
17
+ } from "./chunk-WWXWWCCX.js";
17
18
 
18
19
  // src/uzdu-upload.ts
19
20
  import { Argument, Command, Option } from "commander";
@@ -38,10 +39,10 @@ command.command("aws").description("upload to AWS S3").argument("<from>", "the d
38
39
  const optConfig = { bucket: bucketName, endpoint };
39
40
  if (region) optConfig.region = region;
40
41
  const config = Object.assign(env, optConfig);
41
- if (!config.accessKeyId) throw new Error("AWS Access Key ID is not specified. Provide an environement variable S3_ACCESS_KEY_ID.");
42
- if (!config.secretAccessKey) throw new Error("AWS Secret Key is not specified. Provide an environment variable S3_SECRET_ACCESS_KEY.");
42
+ if (!config.accessKeyId) throw new Error("AWS Access Key ID is not specified. Provide an environement variable AWS_ACCESS_KEY_ID.");
43
+ if (!config.secretAccessKey) throw new Error("AWS Secret Key is not specified. Provide an environment variable AWS_SECRET_ACCESS_KEY.");
43
44
  if (!config.region) throw new Error("AWS region is not specified. Provide it in a bucket address or as an envronment variable S3_REGION.");
44
- await upload3(from, config);
45
+ await upload4(from, config);
45
46
  } catch (e) {
46
47
  thisCommand.error(e.message || e, { exitCode: 53, code: "aws.upload.error" });
47
48
  }
@@ -72,14 +73,14 @@ command.command("azure").alias("az").description("upload to Azure Blob Storage")
72
73
  }
73
74
  const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
74
75
  if (!connectionString) {
75
- throw new Error("AZURE_STORAGE_CONNECTION_STRING is absent in environment variables. Consider option --dotenv to laod it.");
76
+ throw new Error("AZURE_STORAGE_CONNECTION_STRING is absent in environment variables. Consider the command option --dotenv to load it from a file.");
76
77
  }
77
78
  shouldBeDirectory(from);
78
79
  const azOpt = {
79
80
  connectionString,
80
81
  container
81
82
  };
82
- await upload2(from, azOpt);
83
+ await upload3(from, azOpt);
83
84
  } catch (e) {
84
85
  thisCommand.error(e.message || e, { exitCode: 43, code: "az.upload.error" });
85
86
  }
@@ -88,10 +89,8 @@ command.command("ssh").description("upload via SFTP. In addition to sftpURL cons
88
89
  new Option("-d|--dotenv [file]", 'load environment variables from a property file, i.e. a file with "key=value" lines.').preset(".env")
89
90
  ).addOption(new Option("--privateKeyPath [path to file]", "Path to SSH private key, fallback is UZDU_SSH_KEY_PATH environment variable. Also consider using UZDU_SSH_KEY to provide SSH private key content or UZDU_SSH_PASSWORD.")).action(async (source, sftpUrl, options, thisCommand) => {
90
91
  try {
91
- const sshCredentials = getCredentials(options);
92
- await upload4(resolvePath(source), sftpUrl, sshCredentials);
92
+ await upload2(resolvePath(source), sftpUrl, options);
93
93
  } catch (e) {
94
- console.error(e);
95
94
  thisCommand.error(e.message || e, { exitCode: 127, code: "ssh.upload.error" });
96
95
  }
97
96
  });
package/lib/uzdu-zip.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  makeZip,
4
4
  outputConfiguration
5
- } from "./chunk-OIXJ4D3Z.js";
5
+ } from "./chunk-WWXWWCCX.js";
6
6
 
7
7
  // src/uzdu-zip.ts
8
8
  import { Command } from "commander";
package/lib/uzdu.d.ts CHANGED
@@ -73,8 +73,28 @@ declare function safeIndex<T>(arr: T[], index: number): T | undefined;
73
73
  * @return '/home/bob/GitHub/Repo/file.png'
74
74
  */
75
75
  declare function resolvePath(filePath: string): string;
76
+ /**
77
+ * Sequential runner
78
+ * @param tasks array of functions to be executed sequentially
79
+ * @param breaksOnError breaks all if any function fails.
80
+ * @returns array of results
81
+ * @throws SequentialRunError
82
+ */
83
+ declare function runSequentially(tasks: Function[], breaksOnError?: boolean): Promise<any[]>;
84
+ declare class SequentilRunError extends Error {
85
+ readonly index: number;
86
+ readonly message: string;
87
+ /**
88
+ *
89
+ * @param message Text of the error
90
+ * @param index where in the sequense the error happened
91
+ */
92
+ constructor(index: number, message?: string);
93
+ }
76
94
 
77
95
  type utils_BlobObject = BlobObject;
96
+ type utils_SequentilRunError = SequentilRunError;
97
+ declare const utils_SequentilRunError: typeof SequentilRunError;
78
98
  declare const utils_addMetadata: typeof addMetadata;
79
99
  declare const utils_checkIsFile: typeof checkIsFile;
80
100
  declare const utils_doUnzip: typeof doUnzip;
@@ -85,11 +105,12 @@ declare const utils_listFiles: typeof listFiles;
85
105
  declare const utils_makeZip: typeof makeZip;
86
106
  declare const utils_outputConfiguration: typeof outputConfiguration;
87
107
  declare const utils_resolvePath: typeof resolvePath;
108
+ declare const utils_runSequentially: typeof runSequentially;
88
109
  declare const utils_safeIndex: typeof safeIndex;
89
110
  declare const utils_shouldBeDirectory: typeof shouldBeDirectory;
90
111
  declare const utils_shouldBeFile: typeof shouldBeFile;
91
112
  declare namespace utils {
92
- export { type utils_BlobObject as BlobObject, utils_addMetadata as addMetadata, utils_checkIsFile as checkIsFile, utils_doUnzip as doUnzip, utils_getEnvironment as getEnvironment, utils_initEnvironment as initEnvironment, utils_listBlobs as listBlobs, utils_listFiles as listFiles, utils_makeZip as makeZip, utils_outputConfiguration as outputConfiguration, utils_resolvePath as resolvePath, utils_safeIndex as safeIndex, utils_shouldBeDirectory as shouldBeDirectory, utils_shouldBeFile as shouldBeFile };
113
+ export { type utils_BlobObject as BlobObject, utils_SequentilRunError as SequentilRunError, utils_addMetadata as addMetadata, utils_checkIsFile as checkIsFile, utils_doUnzip as doUnzip, utils_getEnvironment as getEnvironment, utils_initEnvironment as initEnvironment, utils_listBlobs as listBlobs, utils_listFiles as listFiles, utils_makeZip as makeZip, utils_outputConfiguration as outputConfiguration, utils_resolvePath as resolvePath, utils_runSequentially as runSequentially, utils_safeIndex as safeIndex, utils_shouldBeDirectory as shouldBeDirectory, utils_shouldBeFile as shouldBeFile };
93
114
  }
94
115
 
95
116
  declare function upload$3(dirOrFile: string, url: URL, headers?: string[]): Promise<void>;
@@ -132,7 +153,25 @@ type SshCredentials = {
132
153
  password?: undefined;
133
154
  privateKey: Buffer | string;
134
155
  };
135
- declare function upload(source: string, sftpUrl: string, sshCredentials?: SshCredentials): Promise<void>;
156
+ type ShellCallbackParams = {
157
+ message?: string;
158
+ error?: string;
159
+ signal?: number;
160
+ code?: number;
161
+ };
162
+ type ShellCommandCallback = (value: ShellCallbackParams) => void;
163
+ type SftpConnectConfig = ConnectConfig & {
164
+ path?: string;
165
+ };
166
+ declare function upload(source: string, sftpUrl: string, options?: {
167
+ privateKeyPath?: string;
168
+ dotenv?: string;
169
+ }): Promise<void>;
170
+ declare function execute(sshAddress: string, commands: string[], options?: {
171
+ privateKeyPath?: string;
172
+ dotenv?: string;
173
+ callback?: ShellCommandCallback;
174
+ }): Promise<void>;
136
175
  type FileMapEntry = {
137
176
  [key: string]: false | FileMapEntry;
138
177
  };
@@ -163,12 +202,13 @@ declare function getCredentials(options?: {
163
202
  * sftp://ubuntu:pa55w0rd@example.com/opt/file
164
203
  * sftp://root@[2001:db8::5]:222/opt/file
165
204
  * sftp://203.0.113.5/opt/file
205
+ * root:pa55w0rd@example.com
166
206
  * ```
167
- * @param sftpUrl
207
+ * @param url
168
208
  *
169
209
  * @throws Wrong URL: host or ivp6 is not specified
170
210
  */
171
- declare function getConnectConfig(sftpUrl: string): ConnectConfig;
211
+ declare function getConnectConfig(url: string): SftpConnectConfig;
172
212
  /**
173
213
  *
174
214
  * @param sftpUrl
@@ -178,7 +218,11 @@ declare function getConnectConfig(sftpUrl: string): ConnectConfig;
178
218
  */
179
219
  declare function getRemoteDestination(sftpUrl: string): string;
180
220
 
221
+ type ssh_SftpConnectConfig = SftpConnectConfig;
222
+ type ssh_ShellCallbackParams = ShellCallbackParams;
223
+ type ssh_ShellCommandCallback = ShellCommandCallback;
181
224
  type ssh_SshCredentials = SshCredentials;
225
+ declare const ssh_execute: typeof execute;
182
226
  declare const ssh_getConnectConfig: typeof getConnectConfig;
183
227
  declare const ssh_getCredentials: typeof getCredentials;
184
228
  declare const ssh_getDirMap: typeof getDirMap;
@@ -186,7 +230,7 @@ declare const ssh_getMakeDirs: typeof getMakeDirs;
186
230
  declare const ssh_getRemoteDestination: typeof getRemoteDestination;
187
231
  declare const ssh_upload: typeof upload;
188
232
  declare namespace ssh {
189
- export { type ssh_SshCredentials as SshCredentials, ssh_getConnectConfig as getConnectConfig, ssh_getCredentials as getCredentials, ssh_getDirMap as getDirMap, ssh_getMakeDirs as getMakeDirs, ssh_getRemoteDestination as getRemoteDestination, ssh_upload as upload };
233
+ export { type ssh_SftpConnectConfig as SftpConnectConfig, type ssh_ShellCallbackParams as ShellCallbackParams, type ssh_ShellCommandCallback as ShellCommandCallback, type ssh_SshCredentials as SshCredentials, ssh_execute as execute, ssh_getConnectConfig as getConnectConfig, ssh_getCredentials as getCredentials, ssh_getDirMap as getDirMap, ssh_getMakeDirs as getMakeDirs, ssh_getRemoteDestination as getRemoteDestination, ssh_upload as upload };
190
234
  }
191
235
 
192
236
  export { azure, http, s3, ssh, utils };
package/lib/uzdu.js CHANGED
@@ -1,23 +1,25 @@
1
1
  #! /usr/bin/env node
2
2
  import {
3
3
  azure_exports,
4
- s3_exports,
5
- ssh_exports
6
- } from "./chunk-ACV2CMWZ.js";
4
+ s3_exports
5
+ } from "./chunk-XU5JZHWK.js";
7
6
  import {
8
7
  http_exports
9
- } from "./chunk-7B56UNA6.js";
8
+ } from "./chunk-LFEIDM4S.js";
9
+ import {
10
+ ssh_exports
11
+ } from "./chunk-2E5O5JIN.js";
10
12
  import {
11
13
  outputConfiguration,
12
14
  utils_exports
13
- } from "./chunk-OIXJ4D3Z.js";
15
+ } from "./chunk-WWXWWCCX.js";
14
16
 
15
17
  // src/uzdu.ts
16
18
  import { Command } from "commander";
17
19
  var version;
18
20
  var description;
19
21
  try {
20
- version = "1.0.18";
22
+ version = "1.1.1";
21
23
  description = "UZDU - universal zipper, downloader and uploader. Move files to/from zip, clouds (AWS, Azure), to HTTP PUT (e.g. Nexus) and to SSH";
22
24
  } catch (e) {
23
25
  if (e instanceof ReferenceError) {
@@ -33,6 +35,7 @@ program.command("zip", "create zip-archive from a directory or a file");
33
35
  program.command("unzip", "unzip archive to a directory");
34
36
  program.command("copy", "copy files and directories");
35
37
  program.command("metadata", "create Amazon S3 metadata file. See https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html.").alias("meta");
38
+ program.command("exec", "execute shell command");
36
39
  program.configureOutput(outputConfiguration);
37
40
  async function main() {
38
41
  await program.parseAsync();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uzdu",
3
- "version": "1.0.18",
3
+ "version": "1.1.1",
4
4
  "description": "UZDU - universal zipper, downloader and uploader. Move files to/from zip, clouds (AWS, Azure), to HTTP PUT (e.g. Nexus) and to SSH",
5
5
  "bin": {
6
6
  "uzdu": "lib/uzdu.js"