@datatruck/cli 0.13.1 → 0.16.0

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 (48) hide show
  1. package/Action/BackupAction.js +7 -19
  2. package/Action/RestoreAction.js +4 -12
  3. package/Command/BackupCommand.js +2 -1
  4. package/Command/BackupSessionsCommand.js +1 -0
  5. package/Command/RestoreCommand.js +1 -1
  6. package/Command/RestoreSessionsCommand.js +1 -0
  7. package/Entity/StateEntityAbstract.d.ts +2 -5
  8. package/Error/AppError.d.ts +1 -0
  9. package/Error/AppError.js +4 -0
  10. package/Repository/DatatruckRepository.d.ts +2 -0
  11. package/Repository/DatatruckRepository.js +234 -123
  12. package/Repository/RepositoryAbstract.d.ts +4 -10
  13. package/Repository/ResticRepository.js +34 -17
  14. package/SessionDriver/ConsoleSessionDriver.d.ts +2 -7
  15. package/SessionDriver/ConsoleSessionDriver.js +51 -24
  16. package/SessionDriver/SqliteSessionDriver.js +5 -0
  17. package/SessionManager/BackupSessionManager.d.ts +12 -11
  18. package/SessionManager/BackupSessionManager.js +20 -5
  19. package/SessionManager/RestoreSessionManager.d.ts +14 -11
  20. package/SessionManager/RestoreSessionManager.js +20 -5
  21. package/SessionManager/SessionManagerAbstract.d.ts +18 -0
  22. package/SessionManager/SessionManagerAbstract.js +32 -0
  23. package/Task/GitTask.js +22 -14
  24. package/Task/MariadbTask.js +9 -4
  25. package/Task/MysqlDumpTask.d.ts +3 -1
  26. package/Task/MysqlDumpTask.js +5 -2
  27. package/Task/PostgresqlDumpTask.d.ts +3 -1
  28. package/Task/PostgresqlDumpTask.js +2 -2
  29. package/Task/SqlDumpTaskAbstract.d.ts +3 -1
  30. package/Task/SqlDumpTaskAbstract.js +55 -13
  31. package/Task/TaskAbstract.d.ts +3 -9
  32. package/cli.js +1 -1
  33. package/config.schema.json +3 -0
  34. package/migrations/001-initial.sql +6 -30
  35. package/package.json +1 -1
  36. package/util/cli-util.d.ts +1 -1
  37. package/util/cli-util.js +17 -2
  38. package/util/fs-util.d.ts +25 -21
  39. package/util/fs-util.js +60 -93
  40. package/util/math-util.js +2 -0
  41. package/util/process-util.d.ts +4 -0
  42. package/util/process-util.js +31 -4
  43. package/util/progress.d.ts +12 -0
  44. package/util/progress.js +2 -0
  45. package/util/string-util.d.ts +1 -0
  46. package/util/string-util.js +8 -1
  47. package/util/zip-util.d.ts +64 -20
  48. package/util/zip-util.js +153 -59
@@ -11,6 +11,7 @@ const chalk_1 = __importDefault(require("chalk"));
11
11
  const child_process_1 = require("child_process");
12
12
  const fs_1 = require("fs");
13
13
  const promises_1 = require("fs/promises");
14
+ const readline_1 = require("readline");
14
15
  function logExecStdout(input) {
15
16
  let text = input.colorize ? chalk_1.default.grey(input.data) : input.data;
16
17
  if (input.lineSalt)
@@ -70,7 +71,11 @@ async function exec(command, argv = [], options = null, settings = {}) {
70
71
  stderr: "",
71
72
  exitCode: 0,
72
73
  };
73
- let finishListeners = pipe?.stream instanceof fs_1.WriteStream ? 2 : 1;
74
+ let finishListeners = 1;
75
+ if (pipe?.stream instanceof fs_1.WriteStream)
76
+ finishListeners++;
77
+ if (settings.stdout?.parseLines)
78
+ finishListeners++;
74
79
  let streamError;
75
80
  const tryFinish = () => {
76
81
  if (!--finishListeners)
@@ -116,6 +121,17 @@ async function exec(command, argv = [], options = null, settings = {}) {
116
121
  throw new Error(`stdout is not defined`);
117
122
  if (!p.stderr)
118
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
+ }
119
135
  p.stdout.pipe(pipe.stream, { end: false });
120
136
  p.stderr.pipe(pipe.stream, { end: false });
121
137
  p.on("close", tryFinish);
@@ -129,10 +145,11 @@ async function exec(command, argv = [], options = null, settings = {}) {
129
145
  if (log.stdout || settings.stdout) {
130
146
  if (!p.stdout)
131
147
  throw new Error(`stdout is not defined`);
132
- p.stdout.on("data", (data) => {
148
+ const parseLines = settings.stdout?.parseLines;
149
+ const onData = (data) => {
133
150
  if (log.stdout)
134
151
  logExecStdout({
135
- data: data.toString(),
152
+ data: parseLines ? `${data}\n` : data.toString(),
136
153
  stderr: log.allToStderr,
137
154
  colorize: log.colorize,
138
155
  });
@@ -140,7 +157,17 @@ async function exec(command, argv = [], options = null, settings = {}) {
140
157
  spawnData.stdout += data.toString();
141
158
  if (settings.stdout?.onData)
142
159
  settings.stdout.onData(data.toString());
143
- });
160
+ };
161
+ if (parseLines) {
162
+ const rl = (0, readline_1.createInterface)({
163
+ input: p.stdout,
164
+ });
165
+ rl.on("line", onData);
166
+ rl.on("close", tryFinish);
167
+ }
168
+ else {
169
+ p.stdout.on("data", onData);
170
+ }
144
171
  }
145
172
  if (log.stderr || settings.stderr) {
146
173
  if (!p.stderr)
@@ -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 });
@@ -15,5 +15,6 @@ export declare type UriType = {
15
15
  export declare function formatUri(input: UriType, hidePassword?: boolean): string;
16
16
  export declare function formatSeconds(seconds: number): string;
17
17
  export declare function makePathPatterns(values: string[] | undefined): string[] | undefined;
18
+ export declare function checkPath(path: string, include: string[], exclude?: string[]): boolean;
18
19
  export declare function checkMatch(subject: string | undefined, patterns: string[]): boolean;
19
20
  export declare function formatDateTime(datetime: string): string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatDateTime = exports.checkMatch = exports.makePathPatterns = exports.formatSeconds = exports.formatUri = exports.parseStringList = exports.render = exports.snakeCase = exports.lcfirst = exports.ucfirst = exports.serialize = void 0;
3
+ exports.formatDateTime = exports.checkMatch = exports.checkPath = exports.makePathPatterns = exports.formatSeconds = exports.formatUri = exports.parseStringList = exports.render = exports.snakeCase = exports.lcfirst = exports.ucfirst = exports.serialize = void 0;
4
4
  const AppError_1 = require("../Error/AppError");
5
5
  const micromatch_1 = require("micromatch");
6
6
  function serialize(message, data) {
@@ -99,6 +99,13 @@ function makePathPatterns(values) {
99
99
  });
100
100
  }
101
101
  exports.makePathPatterns = makePathPatterns;
102
+ function checkPath(path, include, exclude) {
103
+ return ((0, micromatch_1.isMatch)(path, include, {
104
+ dot: true,
105
+ }) &&
106
+ (!exclude || !(0, micromatch_1.isMatch)(path, exclude, { dot: true })));
107
+ }
108
+ exports.checkPath = checkPath;
102
109
  function checkMatch(subject, patterns) {
103
110
  if (!subject?.length)
104
111
  subject = "<empty>";
@@ -3,20 +3,6 @@ export interface ZipDataFilterType {
3
3
  exclude?: boolean;
4
4
  patterns: string[];
5
5
  }
6
- export declare type ZipStreamDataType = {
7
- type: "progress";
8
- data: {
9
- progress: number;
10
- files: number;
11
- path: string;
12
- };
13
- } | {
14
- type: "summary";
15
- data: {
16
- folders: number;
17
- files: number;
18
- };
19
- };
20
6
  export interface ZipDataType {
21
7
  command?: string;
22
8
  path: string;
@@ -26,7 +12,14 @@ export interface ZipDataType {
26
12
  includeList?: string;
27
13
  excludeList?: string;
28
14
  verbose?: boolean;
29
- onStream?: (data: ZipStreamDataType) => 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>;
30
23
  }
31
24
  export interface UnzipDataType {
32
25
  command?: string;
@@ -34,20 +27,71 @@ export interface UnzipDataType {
34
27
  files?: (ZipDataFilterType | string)[];
35
28
  output: string;
36
29
  verbose?: boolean;
37
- onStream?: (data: UnzipStreamDataType) => 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>;
38
37
  }
39
- export declare type UnzipStreamDataType = {
38
+ export declare function buildArguments(filters: (ZipDataFilterType | string)[]): string[];
39
+ export declare function checkSSEOption(command?: string): Promise<boolean>;
40
+ declare type ListZipStream = {
41
+ Path?: string;
42
+ Folder?: string;
43
+ Size?: string;
44
+ "Packed Size"?: string;
45
+ Modified?: string;
46
+ Created?: string;
47
+ Accessed?: string;
48
+ Attributes?: string;
49
+ Encrypted?: string;
50
+ Comment?: string;
51
+ CRC?: string;
52
+ Method?: string;
53
+ Characteristics?: string;
54
+ "Host OS"?: string;
55
+ Version?: string;
56
+ Volume?: string;
57
+ Offset?: string;
58
+ };
59
+ export declare function listZip(data: {
60
+ command?: string;
61
+ path: string;
62
+ onStream: (item: ListZipStream) => Promise<void>;
63
+ verbose?: boolean;
64
+ }): Promise<void>;
65
+ export declare type ZipStream = {
40
66
  type: "progress";
41
67
  data: {
42
68
  progress: number;
43
69
  files: number;
44
70
  path: string;
45
71
  };
72
+ } | {
73
+ type: "summary";
74
+ data: {
75
+ folders: number;
76
+ files: number;
77
+ };
46
78
  };
47
- export declare function buildArguments(filters: (ZipDataFilterType | string)[]): string[];
48
- export declare function checkSSEOption(command?: string): Promise<boolean>;
49
79
  export declare function zip(data: ZipDataType): Promise<{
50
80
  folders: number;
51
81
  files: number;
52
82
  }>;
53
- export declare function unzip(data: UnzipDataType): Promise<import("./process-util").ExecResultType>;
83
+ export declare type UnzipStream = {
84
+ type: "progress";
85
+ data: {
86
+ percent: number;
87
+ files: number;
88
+ path: string;
89
+ };
90
+ };
91
+ export declare function unzip(data: UnzipDataType): Promise<{
92
+ files: number;
93
+ stdout: string;
94
+ stderr: string;
95
+ exitCode: number;
96
+ }>;
97
+ export {};
package/util/zip-util.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.unzip = exports.zip = exports.checkSSEOption = exports.buildArguments = void 0;
3
+ exports.unzip = exports.zip = exports.listZip = exports.checkSSEOption = exports.buildArguments = void 0;
4
4
  const process_util_1 = require("./process-util");
5
5
  const path_1 = require("path");
6
6
  function buildArguments(filters) {
@@ -30,50 +30,100 @@ function buildArguments(filters) {
30
30
  return args;
31
31
  }
32
32
  exports.buildArguments = buildArguments;
33
- function parseZipStream(chunk, buffer, cb) {
34
- const lines = chunk.replaceAll("\b", "").trim().split(/\r?\n/);
35
- for (const line of lines) {
36
- let matches = null;
37
- if ((matches = /^(\d+)% (\d+ )?\+/.exec(line))) {
38
- const path = line.slice(line.indexOf("+") + 1).trim();
39
- const progress = Number(matches[1]);
40
- if (!buffer.currentPaths)
41
- buffer.currentPaths = 0;
42
- if (path !== buffer.lastPath)
43
- buffer.currentPaths++;
44
- buffer.lastPath = path;
45
- cb({
46
- type: "progress",
47
- data: { progress, path, files: buffer.currentPaths },
48
- });
49
- }
50
- else if (line.startsWith("Add new data to archive:")) {
51
- const [, folders] = /(\d+) folders?/i.exec(line) || [, 0];
52
- const [, files] = /(\d+) files?/i.exec(line) || [, 0];
53
- cb({
54
- type: "summary",
55
- data: {
56
- folders: Number(folders),
57
- files: Number(files),
58
- },
59
- });
60
- }
61
- }
62
- }
63
33
  let checkSSEOptionResult;
64
34
  async function checkSSEOption(command = "7z") {
65
- 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
+ });
66
40
  if (typeof checkSSEOptionResult === "boolean")
67
41
  return checkSSEOptionResult;
68
42
  return (checkSSEOptionResult = result.stdout.includes(" -sse"));
69
43
  }
70
44
  exports.checkSSEOption = checkSSEOption;
45
+ const listZipLineEqChar = " = ";
46
+ function parseListZipLine(line, buffer) {
47
+ if (buffer.started) {
48
+ if (line === "") {
49
+ if (buffer.opened) {
50
+ const { stream } = buffer;
51
+ buffer.stream = {};
52
+ buffer.opened = false;
53
+ return stream;
54
+ }
55
+ }
56
+ else {
57
+ const separator = line.indexOf(listZipLineEqChar);
58
+ const key = line.slice(0, separator);
59
+ const value = line.slice(separator + listZipLineEqChar.length);
60
+ buffer.opened = true;
61
+ buffer.stream[key] = value;
62
+ }
63
+ }
64
+ else if (line.startsWith("----------")) {
65
+ buffer.started = true;
66
+ buffer.stream = {};
67
+ }
68
+ }
69
+ async function listZip(data) {
70
+ const buffer = {};
71
+ await (0, process_util_1.exec)(data.command ?? "7z", ["l", data.path, "-slt"], {}, {
72
+ log: {
73
+ exec: data.verbose ?? false,
74
+ stderr: data.verbose ?? false,
75
+ stdout: false,
76
+ },
77
+ onExitCodeError: (data, error) => (data.exitCode > 2 ? error : false),
78
+ stdout: {
79
+ parseLines: true,
80
+ onData: async (line) => {
81
+ const stream = parseListZipLine(line, buffer);
82
+ if (stream) {
83
+ await data.onStream?.(stream);
84
+ }
85
+ },
86
+ },
87
+ });
88
+ }
89
+ exports.listZip = listZip;
90
+ function parseZipLine(line) {
91
+ let matches = null;
92
+ line = line.trim();
93
+ if (!line.length)
94
+ return;
95
+ if ((matches = /^(\d+)% (\d+ )?\+/.exec(line))) {
96
+ const path = line.slice(line.indexOf("+") + 1).trim();
97
+ const progress = Number(matches[1]);
98
+ const files = matches[2] ? Number(matches[2]) : 1;
99
+ return {
100
+ type: "progress",
101
+ data: { progress, path, files },
102
+ };
103
+ }
104
+ else if (line.startsWith("Add new data to archive:")) {
105
+ const [, folders] = /(\d+) folders?/i.exec(line) || [, 0];
106
+ const [, files] = /(\d+) files?/i.exec(line) || [, 0];
107
+ return {
108
+ type: "summary",
109
+ data: {
110
+ folders: Number(folders),
111
+ files: Number(files),
112
+ },
113
+ };
114
+ }
115
+ }
71
116
  async function zip(data) {
72
- let result = {
117
+ let summary = {
73
118
  folders: 0,
74
119
  files: 0,
75
120
  };
76
- let buffer = {};
121
+ await data.onProgress?.({
122
+ current: 0,
123
+ percent: 0,
124
+ total: 0,
125
+ type: "start",
126
+ });
77
127
  await (0, process_util_1.exec)(data.command ?? "7z", [
78
128
  "a",
79
129
  // https://sourceforge.net/p/sevenzip/bugs/2099/,
@@ -91,35 +141,58 @@ async function zip(data) {
91
141
  log: data.verbose ?? false,
92
142
  onExitCodeError: (data, error) => (data.exitCode > 2 ? error : false),
93
143
  stdout: {
94
- onData: (chunk) => {
95
- parseZipStream(chunk, buffer, (stream) => {
96
- data.onStream?.(stream);
97
- if (stream.type === "summary")
98
- result = stream.data;
99
- });
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
+ }
161
+ }
100
162
  },
101
163
  },
102
164
  });
103
- return result;
165
+ await data.onProgress?.({
166
+ total: summary.files,
167
+ current: summary.files,
168
+ percent: 100,
169
+ type: "end",
170
+ });
171
+ return summary;
104
172
  }
105
173
  exports.zip = zip;
106
- function parseUnzipStream(chunk, cb) {
107
- const lines = chunk.trim().split(/\r?\n/g);
108
- for (const line of lines) {
109
- let matches = null;
110
- if ((matches = /^(\d+)% (\d+) \-/.exec(line))) {
111
- const progress = Number(matches[1]);
112
- const files = Number(matches[2]);
113
- const path = line.slice(line.indexOf("-") + 1).trim();
114
- cb({
115
- type: "progress",
116
- data: { progress, path, files },
117
- });
118
- }
174
+ function parseUnzipLine(line) {
175
+ let matches = null;
176
+ if ((matches = /^\s*(\d+)% (\d+) \-/.exec(line))) {
177
+ const progress = Number(matches[1]);
178
+ const files = Number(matches[2]);
179
+ const path = line.slice(line.indexOf("-") + 1).trim();
180
+ return {
181
+ type: "progress",
182
+ data: { percent: progress, path, files },
183
+ };
119
184
  }
120
185
  }
121
186
  async function unzip(data) {
122
- 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", [
123
196
  "x",
124
197
  "-bsp1",
125
198
  (0, path_1.normalize)(data.input),
@@ -130,13 +203,34 @@ async function unzip(data) {
130
203
  log: data.verbose ?? false,
131
204
  stderr: { toExitCode: true },
132
205
  stdout: {
133
- ...(data.onStream && {
134
- onData: (chunk) => {
135
- if (data.onStream)
136
- parseUnzipStream(chunk, data.onStream);
206
+ ...((data.onStream || data.onProgress) && {
207
+ parseLines: true,
208
+ onData: async (line) => {
209
+ const stream = parseUnzipLine(line);
210
+ if (stream) {
211
+ if (stream.type === "progress") {
212
+ const current = Math.max(0, stream.data.files - 1);
213
+ summary.files = stream.data.files;
214
+ await data.onProgress?.({
215
+ current,
216
+ percent: stream.data.percent,
217
+ path: stream.data.path,
218
+ });
219
+ }
220
+ await data.onStream?.(stream);
221
+ }
137
222
  },
138
223
  }),
139
224
  },
140
225
  });
226
+ await data.onProgress?.({
227
+ current: summary.files,
228
+ percent: 100,
229
+ type: "end",
230
+ });
231
+ return {
232
+ ...result,
233
+ ...summary,
234
+ };
141
235
  }
142
236
  exports.unzip = unzip;