@datatruck/cli 0.14.0 → 0.16.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.
Files changed (46) hide show
  1. package/Action/BackupAction.d.ts +10 -3
  2. package/Action/BackupAction.js +41 -49
  3. package/Action/RestoreAction.d.ts +2 -2
  4. package/Action/RestoreAction.js +8 -16
  5. package/Command/BackupCommand.js +2 -1
  6. package/Command/BackupSessionsCommand.js +1 -0
  7. package/Command/RestoreCommand.js +1 -1
  8. package/Command/RestoreSessionsCommand.js +1 -0
  9. package/Entity/StateEntityAbstract.d.ts +2 -5
  10. package/Error/AppError.d.ts +1 -0
  11. package/Error/AppError.js +4 -0
  12. package/Repository/DatatruckRepository.d.ts +1 -0
  13. package/Repository/DatatruckRepository.js +162 -158
  14. package/Repository/RepositoryAbstract.d.ts +4 -10
  15. package/Repository/ResticRepository.js +34 -17
  16. package/SessionDriver/ConsoleSessionDriver.d.ts +2 -7
  17. package/SessionDriver/ConsoleSessionDriver.js +51 -24
  18. package/SessionDriver/SqliteSessionDriver.js +5 -0
  19. package/SessionManager/BackupSessionManager.d.ts +12 -11
  20. package/SessionManager/BackupSessionManager.js +20 -5
  21. package/SessionManager/RestoreSessionManager.d.ts +14 -11
  22. package/SessionManager/RestoreSessionManager.js +20 -5
  23. package/SessionManager/SessionManagerAbstract.d.ts +18 -0
  24. package/SessionManager/SessionManagerAbstract.js +32 -0
  25. package/Task/GitTask.js +22 -14
  26. package/Task/MariadbTask.js +9 -4
  27. package/Task/MysqlDumpTask.d.ts +3 -1
  28. package/Task/MysqlDumpTask.js +5 -2
  29. package/Task/PostgresqlDumpTask.d.ts +3 -1
  30. package/Task/PostgresqlDumpTask.js +2 -2
  31. package/Task/SqlDumpTaskAbstract.d.ts +3 -1
  32. package/Task/SqlDumpTaskAbstract.js +55 -13
  33. package/Task/TaskAbstract.d.ts +3 -9
  34. package/cli.js +1 -1
  35. package/migrations/001-initial.sql +6 -30
  36. package/package.json +1 -1
  37. package/util/cli-util.d.ts +1 -1
  38. package/util/cli-util.js +17 -2
  39. package/util/fs-util.d.ts +10 -1
  40. package/util/fs-util.js +10 -1
  41. package/util/process-util.d.ts +3 -0
  42. package/util/process-util.js +11 -0
  43. package/util/progress.d.ts +12 -0
  44. package/util/progress.js +2 -0
  45. package/util/zip-util.d.ts +23 -5
  46. package/util/zip-util.js +82 -25
package/cli.js CHANGED
@@ -81,7 +81,7 @@ program.usage("dtt");
81
81
  program.option("-v,--verbose", "Verbose", (_, previous) => previous + 1, 0);
82
82
  program.option("-c,--config <path>", "Config path", process.env["DATATRUCK_CONFIG"] ?? (cwd.endsWith(path_1.sep) ? cwd : `${cwd}${path_1.sep}`));
83
83
  program.option("--progress <value>", "Progress type (auto, plain, tty)", "auto");
84
- program.option("--progress-interval <ms>", "Progress interval");
84
+ program.option("--progress-interval <ms>", "Progress interval", Number, 1000);
85
85
  program.option("-o,--output-format <format>", "Output format (json, pjson, yaml, table, custom=$, tpl=name)", "table");
86
86
  makeCommand(CommandFactory_1.CommandEnum.config).alias("c");
87
87
  makeCommand(CommandFactory_1.CommandEnum.init).alias("i");
@@ -11,11 +11,7 @@ CREATE TABLE "backup_session" (
11
11
  "endDate" TEXT,
12
12
  "state" TEXT,
13
13
  "error" TEXT,
14
- "progressCurrent" INTEGER,
15
- "progressTotal" INTEGER,
16
- "progressPercent" INTEGER,
17
- "progressStep" TEXT,
18
- "progressStepPercent" INTEGER,
14
+ "progress" TEXT,
19
15
 
20
16
  "snapshotId" TEXT NOT NULL,
21
17
  "packageName" TEXT NOT NULL,
@@ -32,11 +28,7 @@ CREATE TABLE "backup_session_task" (
32
28
  "endDate" TEXT,
33
29
  "state" TEXT,
34
30
  "error" TEXT,
35
- "progressCurrent" INTEGER,
36
- "progressTotal" INTEGER,
37
- "progressPercent" INTEGER,
38
- "progressStep" TEXT,
39
- "progressStepPercent" INTEGER,
31
+ "progress" TEXT,
40
32
 
41
33
  "sessionId" INTEGER NOT NULL,
42
34
  "taskName" TEXT NOT NULL
@@ -51,11 +43,7 @@ CREATE TABLE "backup_session_repository" (
51
43
  "endDate" TEXT,
52
44
  "state" TEXT,
53
45
  "error" TEXT,
54
- "progressCurrent" INTEGER,
55
- "progressTotal" INTEGER,
56
- "progressPercent" INTEGER,
57
- "progressStep" TEXT,
58
- "progressStepPercent" INTEGER,
46
+ "progress" TEXT,
59
47
 
60
48
  "sessionId" INTEGER NOT NULL,
61
49
  "repositoryName" TEXT NOT NULL,
@@ -71,11 +59,7 @@ CREATE TABLE "restore_session" (
71
59
  "endDate" TEXT,
72
60
  "state" TEXT,
73
61
  "error" TEXT,
74
- "progressCurrent" INTEGER,
75
- "progressTotal" INTEGER,
76
- "progressPercent" INTEGER,
77
- "progressStep" TEXT,
78
- "progressStepPercent" INTEGER,
62
+ "progress" TEXT,
79
63
 
80
64
  "snapshotId" TEXT NOT NULL,
81
65
  "packageName" TEXT NOT NULL -- ,
@@ -91,11 +75,7 @@ CREATE TABLE "restore_session_task" (
91
75
  "endDate" TEXT,
92
76
  "state" TEXT,
93
77
  "error" TEXT,
94
- "progressCurrent" INTEGER,
95
- "progressTotal" INTEGER,
96
- "progressPercent" INTEGER,
97
- "progressStep" TEXT,
98
- "progressStepPercent" INTEGER,
78
+ "progress" TEXT,
99
79
 
100
80
  "sessionId" INTEGER NOT NULL,
101
81
  "taskName" TEXT NOT NULL
@@ -110,11 +90,7 @@ CREATE TABLE "restore_session_repository" (
110
90
  "endDate" TEXT,
111
91
  "state" TEXT,
112
92
  "error" TEXT,
113
- "progressCurrent" INTEGER,
114
- "progressTotal" INTEGER,
115
- "progressPercent" INTEGER,
116
- "progressStep" TEXT,
117
- "progressStepPercent" INTEGER,
93
+ "progress" TEXT,
118
94
 
119
95
  "sessionId" INTEGER NOT NULL,
120
96
  "repositoryName" TEXT NOT NULL,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datatruck/cli",
3
- "version": "0.14.0",
3
+ "version": "0.16.1",
4
4
  "dependencies": {
5
5
  "ajv": "^8.11.0",
6
6
  "async": "^3.2.4",
@@ -5,7 +5,7 @@ export declare const showCursorCommand = "\u001B[?25h";
5
5
  export declare const clearCommand = "\r\u001B[K";
6
6
  export declare const hideCursorCommand = "\u001B[?25l";
7
7
  export declare function renderSpinner(counter: number): string;
8
- export declare function renderProgressBar(progress: number, size?: number): string;
8
+ export declare function renderProgressBar(progress: number, size?: number, subprogress?: number): string;
9
9
  export declare function logVars(data: Record<string, any>): void;
10
10
  export declare function logExec(command: string, argv?: string[], env?: NodeJS.ProcessEnv, logToStderr?: boolean): void;
11
11
  export declare function resultColumn(error: Error | null | string, state?: "started" | "ended"): "❌" | " ? " | "✅";
package/util/cli-util.js CHANGED
@@ -20,12 +20,27 @@ function renderSpinner(counter) {
20
20
  return exports.spinnerChars[counter % (exports.spinnerChars.length - 1)];
21
21
  }
22
22
  exports.renderSpinner = renderSpinner;
23
- function renderProgressBar(progress, size = 10) {
23
+ function renderProgressBar(progress, size = 10, subprogress) {
24
24
  const completeChar = "\u2588";
25
25
  const incompleteChar = "\u2591";
26
26
  const completedSize = Math.round((progress * size) / 100);
27
27
  const restSize = Math.max(size - completedSize, 0);
28
- return completeChar.repeat(completedSize) + incompleteChar.repeat(restSize);
28
+ let result = completeChar.repeat(completedSize) + incompleteChar.repeat(restSize);
29
+ if (typeof subprogress === "number") {
30
+ const subprogressChar = Math.round((subprogress * size) / 100);
31
+ if (subprogressChar === size) {
32
+ result =
33
+ result.slice(0, subprogressChar - 1) +
34
+ chalk_1.default.white(result[Math.max(0, subprogressChar - 1)]);
35
+ }
36
+ else {
37
+ result =
38
+ result.slice(0, subprogressChar) +
39
+ chalk_1.default.white(result[Math.max(0, subprogressChar - 1)]) +
40
+ result.slice(subprogressChar + 1);
41
+ }
42
+ }
43
+ return (0, chalk_2.cyan)(result);
29
44
  }
30
45
  exports.renderProgressBar = renderProgressBar;
31
46
  function logVars(data) {
package/util/fs-util.d.ts CHANGED
@@ -73,6 +73,11 @@ export declare function cpy(options: {
73
73
  */
74
74
  concurrency?: number;
75
75
  skipNotFoundError?: boolean;
76
+ onProgress?: (data: {
77
+ current: number;
78
+ path?: string;
79
+ type?: "start" | "end";
80
+ }) => Promise<boolean | void>;
76
81
  onPath?: (data: {
77
82
  isDir: boolean;
78
83
  entryPath: string;
@@ -84,5 +89,9 @@ export declare function cpy(options: {
84
89
  dirs: number;
85
90
  };
86
91
  }) => Promise<boolean | void>;
87
- }): Promise<void>;
92
+ }): Promise<{
93
+ paths: number;
94
+ files: number;
95
+ dirs: number;
96
+ }>;
88
97
  export {};
package/util/fs-util.js CHANGED
@@ -162,7 +162,7 @@ function sessionTmpDir() {
162
162
  exports.sessionTmpDir = sessionTmpDir;
163
163
  function tmpDir(prefix, id) {
164
164
  if (!id)
165
- id = (0, crypto_1.randomBytes)(8).toString("hex");
165
+ id = (0, crypto_1.randomUUID)().slice(0, 8);
166
166
  return (0, path_1.join)(sessionTmpDir(), `${prefix}-${id}`);
167
167
  }
168
168
  exports.tmpDir = tmpDir;
@@ -369,6 +369,10 @@ async function cpy(options) {
369
369
  else {
370
370
  const dir = (0, path_1.dirname)(entryTargetPath);
371
371
  await makeRecursiveDir(dir);
372
+ await options.onProgress?.({
373
+ current: stats.files,
374
+ path: entryPath,
375
+ });
372
376
  stats.files++;
373
377
  // https://github.com/nodejs/node/issues/44261
374
378
  if (exports.isWSLSystem) {
@@ -429,5 +433,10 @@ async function cpy(options) {
429
433
  },
430
434
  });
431
435
  }
436
+ await options.onProgress?.({
437
+ current: stats.files,
438
+ type: "end",
439
+ });
440
+ return stats;
432
441
  }
433
442
  exports.cpy = cpy;
@@ -14,6 +14,9 @@ export interface ExecSettingsInterface {
14
14
  exec?: boolean;
15
15
  pipe?: {
16
16
  stream: WriteStream | ReadStream;
17
+ onWriteProgress?: (data: {
18
+ totalBytes: number;
19
+ }) => void;
17
20
  onReadProgress?: (data: {
18
21
  totalBytes: number;
19
22
  currentBytes: number;
@@ -121,6 +121,17 @@ async function exec(command, argv = [], options = null, settings = {}) {
121
121
  throw new Error(`stdout is not defined`);
122
122
  if (!p.stderr)
123
123
  throw new Error(`stderr is not defined`);
124
+ if (pipe.onWriteProgress) {
125
+ let totalBytes = 0;
126
+ p.stdout.on("data", (chunk) => {
127
+ totalBytes += chunk.length;
128
+ pipe.onWriteProgress({ totalBytes });
129
+ });
130
+ p.stderr.on("data", (chunk) => {
131
+ totalBytes += chunk.length;
132
+ pipe.onWriteProgress({ totalBytes });
133
+ });
134
+ }
124
135
  p.stdout.pipe(pipe.stream, { end: false });
125
136
  p.stderr.pipe(pipe.stream, { end: false });
126
137
  p.on("close", tryFinish);
@@ -0,0 +1,12 @@
1
+ export declare type ProgressStats = {
2
+ percent?: number;
3
+ total?: number;
4
+ current?: number;
5
+ description?: string;
6
+ payload?: string;
7
+ format?: "amount" | "size";
8
+ };
9
+ export declare type Progress = {
10
+ absolute?: ProgressStats;
11
+ relative?: ProgressStats;
12
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -12,7 +12,14 @@ export interface ZipDataType {
12
12
  includeList?: string;
13
13
  excludeList?: string;
14
14
  verbose?: boolean;
15
- onStream?: (data: ZipStream) => void;
15
+ onProgress?: (data: {
16
+ percent: number;
17
+ total: number;
18
+ current: number;
19
+ path?: string;
20
+ type?: "start" | "end";
21
+ }) => void | Promise<void>;
22
+ onStream?: (data: ZipStream) => void | Promise<void>;
16
23
  }
17
24
  export interface UnzipDataType {
18
25
  command?: string;
@@ -20,7 +27,13 @@ export interface UnzipDataType {
20
27
  files?: (ZipDataFilterType | string)[];
21
28
  output: string;
22
29
  verbose?: boolean;
23
- onStream?: (data: UnzipStream) => void;
30
+ onProgress?: (data: {
31
+ percent: number;
32
+ current: number;
33
+ path?: string;
34
+ type?: "start" | "end";
35
+ }) => void | Promise<void>;
36
+ onStream?: (data: UnzipStream) => void | Promise<void>;
24
37
  }
25
38
  export declare function buildArguments(filters: (ZipDataFilterType | string)[]): string[];
26
39
  export declare function checkSSEOption(command?: string): Promise<boolean>;
@@ -46,7 +59,7 @@ declare type ListZipStream = {
46
59
  export declare function listZip(data: {
47
60
  command?: string;
48
61
  path: string;
49
- onStream: (item: ListZipStream) => void;
62
+ onStream: (item: ListZipStream) => Promise<void>;
50
63
  verbose?: boolean;
51
64
  }): Promise<void>;
52
65
  export declare type ZipStream = {
@@ -70,10 +83,15 @@ export declare function zip(data: ZipDataType): Promise<{
70
83
  export declare type UnzipStream = {
71
84
  type: "progress";
72
85
  data: {
73
- progress: number;
86
+ percent: number;
74
87
  files: number;
75
88
  path: string;
76
89
  };
77
90
  };
78
- export declare function unzip(data: UnzipDataType): Promise<import("./process-util").ExecResultType>;
91
+ export declare function unzip(data: UnzipDataType): Promise<{
92
+ files: number;
93
+ stdout: string;
94
+ stderr: string;
95
+ exitCode: number;
96
+ }>;
79
97
  export {};
package/util/zip-util.js CHANGED
@@ -32,7 +32,11 @@ function buildArguments(filters) {
32
32
  exports.buildArguments = buildArguments;
33
33
  let checkSSEOptionResult;
34
34
  async function checkSSEOption(command = "7z") {
35
- const result = await (0, process_util_1.exec)(command);
35
+ const result = await (0, process_util_1.exec)(command, [], {}, {
36
+ stdout: {
37
+ save: true,
38
+ },
39
+ });
36
40
  if (typeof checkSSEOptionResult === "boolean")
37
41
  return checkSSEOptionResult;
38
42
  return (checkSSEOptionResult = result.stdout.includes(" -sse"));
@@ -73,10 +77,10 @@ async function listZip(data) {
73
77
  onExitCodeError: (data, error) => (data.exitCode > 2 ? error : false),
74
78
  stdout: {
75
79
  parseLines: true,
76
- onData: (line) => {
80
+ onData: async (line) => {
77
81
  const stream = parseListZipLine(line, buffer);
78
82
  if (stream) {
79
- data.onStream?.(stream);
83
+ await data.onStream?.(stream);
80
84
  }
81
85
  },
82
86
  },
@@ -85,12 +89,13 @@ async function listZip(data) {
85
89
  exports.listZip = listZip;
86
90
  function parseZipLine(line) {
87
91
  let matches = null;
88
- if (!line.trim().length)
92
+ line = line.trim();
93
+ if (!line.length)
89
94
  return;
90
- if ((matches = /^\s*(\d+)% (\d+ )?\+/.exec(line))) {
95
+ if ((matches = /^(\d+)% (\d+ )?\+/.exec(line))) {
91
96
  const path = line.slice(line.indexOf("+") + 1).trim();
92
97
  const progress = Number(matches[1]);
93
- const files = Number(matches[2]);
98
+ const files = matches[2] ? Number(matches[2]) : 1;
94
99
  return {
95
100
  type: "progress",
96
101
  data: { progress, path, files },
@@ -109,10 +114,16 @@ function parseZipLine(line) {
109
114
  }
110
115
  }
111
116
  async function zip(data) {
112
- let result = {
117
+ let summary = {
113
118
  folders: 0,
114
119
  files: 0,
115
120
  };
121
+ await data.onProgress?.({
122
+ current: 0,
123
+ percent: 0,
124
+ total: 0,
125
+ type: "start",
126
+ });
116
127
  await (0, process_util_1.exec)(data.command ?? "7z", [
117
128
  "a",
118
129
  // https://sourceforge.net/p/sevenzip/bugs/2099/,
@@ -130,33 +141,58 @@ async function zip(data) {
130
141
  log: data.verbose ?? false,
131
142
  onExitCodeError: (data, error) => (data.exitCode > 2 ? error : false),
132
143
  stdout: {
133
- onData: (line) => {
134
- const stream = parseZipLine(line);
135
- if (stream) {
136
- data.onStream?.(stream);
137
- if (stream.type === "summary")
138
- result = stream.data;
144
+ onData: async (lines) => {
145
+ for (const line of lines.replaceAll("\b", "").split(/\r?\n/)) {
146
+ const stream = parseZipLine(line);
147
+ if (stream) {
148
+ if (stream.type === "summary")
149
+ summary = stream.data;
150
+ if (stream.type === "progress") {
151
+ const current = Math.max(0, stream.data.files - 1);
152
+ await data.onProgress?.({
153
+ total: summary.files,
154
+ current,
155
+ path: stream.data.path,
156
+ percent: stream.data.progress,
157
+ });
158
+ }
159
+ await data.onStream?.(stream);
160
+ }
139
161
  }
140
162
  },
141
163
  },
142
164
  });
143
- return result;
165
+ await data.onProgress?.({
166
+ total: summary.files,
167
+ current: summary.files,
168
+ percent: 100,
169
+ type: "end",
170
+ });
171
+ return summary;
144
172
  }
145
173
  exports.zip = zip;
146
174
  function parseUnzipLine(line) {
147
175
  let matches = null;
148
- if ((matches = /^\s*(\d+)% (\d+) \-/.exec(line))) {
149
- const progress = Number(matches[1]);
150
- const files = Number(matches[2]);
176
+ if ((matches = /^\s*(?<percent>\d+)%(?: (?<files>\d+))? \-/.exec(line))) {
177
+ const progress = Number(matches.groups["percent"]);
178
+ const files = matches.groups["files"] ? Number(matches[2]) : 1;
151
179
  const path = line.slice(line.indexOf("-") + 1).trim();
152
180
  return {
153
181
  type: "progress",
154
- data: { progress, path, files },
182
+ data: { percent: progress, path, files },
155
183
  };
156
184
  }
157
185
  }
158
186
  async function unzip(data) {
159
- return await (0, process_util_1.exec)(data.command ?? "7z", [
187
+ let summary = {
188
+ files: 0,
189
+ };
190
+ await data.onProgress?.({
191
+ current: summary.files,
192
+ percent: 0,
193
+ type: "start",
194
+ });
195
+ const result = await (0, process_util_1.exec)(data.command ?? "7z", [
160
196
  "x",
161
197
  "-bsp1",
162
198
  (0, path_1.normalize)(data.input),
@@ -167,15 +203,36 @@ async function unzip(data) {
167
203
  log: data.verbose ?? false,
168
204
  stderr: { toExitCode: true },
169
205
  stdout: {
170
- ...(data.onStream && {
171
- parseLines: true,
172
- onData: (line) => {
173
- const stream = parseUnzipLine(line);
174
- if (stream)
175
- data.onStream(stream);
206
+ ...((data.onStream || data.onProgress) && {
207
+ onData: async (chunk) => {
208
+ const lines = chunk.replaceAll("\b", "").split(/\r?\n/);
209
+ for (const line of lines) {
210
+ const stream = parseUnzipLine(line);
211
+ if (stream) {
212
+ if (stream.type === "progress") {
213
+ const current = Math.max(0, stream.data.files - 1);
214
+ summary.files = stream.data.files;
215
+ await data.onProgress?.({
216
+ current,
217
+ percent: stream.data.percent,
218
+ path: stream.data.path,
219
+ });
220
+ }
221
+ await data.onStream?.(stream);
222
+ }
223
+ }
176
224
  },
177
225
  }),
178
226
  },
179
227
  });
228
+ await data.onProgress?.({
229
+ current: summary.files,
230
+ percent: 100,
231
+ type: "end",
232
+ });
233
+ return {
234
+ ...result,
235
+ ...summary,
236
+ };
180
237
  }
181
238
  exports.unzip = unzip;