@datatruck/cli 0.5.0 → 0.7.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 (56) hide show
  1. package/Action/BackupAction.d.ts +1 -0
  2. package/Action/BackupAction.js +4 -1
  3. package/Action/RestoreAction.d.ts +1 -0
  4. package/CHANGELOG.md +40 -0
  5. package/Command/BackupCommand.d.ts +1 -0
  6. package/Command/BackupCommand.js +8 -2
  7. package/Command/BackupSessionsCommand.js +1 -1
  8. package/Command/ConfigCommand.d.ts +1 -0
  9. package/Command/ConfigCommand.js +7 -1
  10. package/Command/InitCommand.js +1 -1
  11. package/Command/PruneCommand.js +2 -2
  12. package/Command/RestoreCommand.d.ts +1 -0
  13. package/Command/RestoreCommand.js +8 -2
  14. package/Command/RestoreSessionsCommand.js +1 -1
  15. package/Command/SnapshotsCommand.js +1 -1
  16. package/Repository/GitRepository.js +2 -3
  17. package/Repository/LocalRepository.d.ts +4 -0
  18. package/Repository/LocalRepository.js +38 -37
  19. package/Repository/RepositoryAbstract.d.ts +4 -4
  20. package/Repository/ResticRepository.js +30 -9
  21. package/SessionDriver/ConsoleSessionDriver.d.ts +6 -2
  22. package/SessionDriver/ConsoleSessionDriver.js +31 -40
  23. package/SessionDriver/SessionDriverAbstract.d.ts +1 -1
  24. package/SessionDriver/SessionDriverAbstract.js +1 -1
  25. package/SessionManager/BackupSessionManager.d.ts +1 -1
  26. package/SessionManager/BackupSessionManager.js +2 -2
  27. package/Task/GitTask.d.ts +4 -0
  28. package/Task/GitTask.js +28 -29
  29. package/Task/MariadbTask.js +27 -15
  30. package/Task/MssqlTask.js +1 -2
  31. package/Task/MysqlDumpTask.d.ts +1 -1
  32. package/Task/MysqlDumpTask.js +4 -2
  33. package/Task/PostgresqlDumpTask.d.ts +1 -1
  34. package/Task/PostgresqlDumpTask.js +2 -4
  35. package/Task/SqlDumpTaskAbstract.d.ts +1 -2
  36. package/Task/SqlDumpTaskAbstract.js +60 -17
  37. package/Task/TaskAbstract.d.ts +4 -4
  38. package/config.schema.json +8 -0
  39. package/package.json +2 -2
  40. package/util/GitUtil.js +2 -2
  41. package/util/ResticUtil.d.ts +1 -1
  42. package/util/ResticUtil.js +3 -4
  43. package/util/cli-util.d.ts +1 -0
  44. package/util/cli-util.js +13 -1
  45. package/util/datatruck/config-util.d.ts +1 -0
  46. package/util/datatruck/config-util.js +3 -0
  47. package/util/date-util.d.ts +4 -0
  48. package/util/date-util.js +17 -1
  49. package/util/fs-util.d.ts +33 -0
  50. package/util/fs-util.js +73 -4
  51. package/util/process-util.d.ts +2 -2
  52. package/util/process-util.js +6 -6
  53. package/util/string-util.d.ts +1 -0
  54. package/util/string-util.js +21 -1
  55. package/util/zip-util.d.ts +2 -2
  56. package/util/zip-util.js +9 -9
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConsoleSessionDriver = void 0;
4
4
  const AppError_1 = require("../Error/AppError");
5
5
  const cli_util_1 = require("../util/cli-util");
6
+ const date_util_1 = require("../util/date-util");
6
7
  const SessionDriverAbstract_1 = require("./SessionDriverAbstract");
7
8
  const chalk_1 = require("chalk");
8
9
  const sep = (0, chalk_1.grey)(`|`);
@@ -12,42 +13,28 @@ class ConsoleSessionDriver extends SessionDriverAbstract_1.SessionDriverAbstract
12
13
  constructor() {
13
14
  super(...arguments);
14
15
  this.prints = 0;
16
+ this.chron = (0, date_util_1.createChron)();
15
17
  }
16
18
  async onInit() {
17
- this.startTime = Date.now();
19
+ this.chron.start();
18
20
  this.renderInterval = setInterval(() => {
19
21
  if (this.lastMessage)
20
- this.printMessage(this.lastMessage);
22
+ this.printMessage(this.lastMessage, false);
21
23
  }, 100);
22
24
  }
23
- async onEnd() {
25
+ async onEnd(data) {
24
26
  clearInterval(this.renderInterval);
25
27
  if (!this.options.verbose)
26
28
  process.stdout.write(cli_util_1.showCursorCommand);
27
- const ellapsed = (Date.now() - this.startTime) / 1000;
28
- let ellapsedUnit;
29
- let ellapsedValue;
30
- if (ellapsed > 60 * 60) {
31
- ellapsedValue = ellapsed / 60 / 60;
32
- ellapsedUnit = `hour`;
33
- }
34
- else if (ellapsed > 60) {
35
- ellapsedValue = ellapsed / 60;
36
- ellapsedUnit = `minute`;
37
- }
38
- else {
39
- ellapsedValue = ellapsed;
40
- ellapsedUnit = `second`;
41
- }
42
- if (ellapsedValue !== 1)
43
- ellapsedUnit += `s`;
44
- const message = `Completed in ${ellapsedValue.toFixed(2)} ${ellapsedUnit}`;
45
- console.info(`\n${(0, chalk_1.grey)(message)}`);
29
+ (0, cli_util_1.logVars)({
30
+ ...data,
31
+ elapsed: this.chron.elapsed(true),
32
+ });
46
33
  }
47
34
  async onRead() {
48
35
  throw new AppError_1.AppError("Method not implemented");
49
36
  }
50
- printMessage(message) {
37
+ printMessage(message, endMessage) {
51
38
  const text = this.renderMessage(message);
52
39
  if (this.options.verbose && this.lastMessageText === text) {
53
40
  return;
@@ -58,8 +45,10 @@ class ConsoleSessionDriver extends SessionDriverAbstract_1.SessionDriverAbstract
58
45
  else {
59
46
  const columns = process.stdout.columns;
60
47
  const line = this.renderSpinner(text);
61
- const [truncatedLine, truncted] = (0, cli_util_1.truncate)(line, columns);
62
- if (this.lastColumns && columns !== this.lastColumns && truncted)
48
+ const [truncatedLine, truncated] = endMessage
49
+ ? [line, false]
50
+ : (0, cli_util_1.truncate)(line, columns);
51
+ if (this.lastColumns && columns !== this.lastColumns && truncated)
63
52
  process.stdout.write(`${cli_util_1.clearCommand}\n`);
64
53
  process.stdout.write(`${cli_util_1.clearCommand}${truncatedLine}${cli_util_1.hideCursorCommand}`);
65
54
  this.lastColumns = columns;
@@ -82,18 +71,19 @@ class ConsoleSessionDriver extends SessionDriverAbstract_1.SessionDriverAbstract
82
71
  const parts = [
83
72
  `${padding}${message.textPrefix} [${(0, chalk_1.grey)(sessionId)}] ${message.text}`,
84
73
  badges,
85
- ...(haveProgressBar
86
- ? [
87
- (0, chalk_1.cyan)((0, cli_util_1.renderProgressBar)(message.progressPercent ?? 0, 10)),
88
- `${message.progressPercent?.toFixed(2)}%`,
89
- `${message.progressCurrent}/${message.progressTotal}`,
90
- message.progressStep,
91
- `${message.progressStepPercent
92
- ? (0, chalk_1.cyan)((0, cli_util_1.renderProgressBar)(message.progressStepPercent ?? 0, 10))
93
- : ""}`,
94
- ].filter((v) => !!v?.length)
95
- : []),
96
74
  ];
75
+ if (typeof message.progressPercent === "number") {
76
+ parts.push((0, chalk_1.cyan)((0, cli_util_1.renderProgressBar)(message.progressPercent ?? 0, 10)), `${message.progressPercent?.toFixed(2)}%`);
77
+ }
78
+ if (typeof message.progressCurrent === "number" ||
79
+ typeof message.progressTotal === "number") {
80
+ parts.push(`${message.progressCurrent ?? "?"}/${message.progressTotal ?? "?"}`);
81
+ }
82
+ if (typeof message.progressStep === "string")
83
+ parts.push(message.progressStep);
84
+ if (typeof message.progressStepPercent === "number") {
85
+ parts.push((0, chalk_1.cyan)((0, cli_util_1.renderProgressBar)(message.progressStepPercent ?? 0, 10)));
86
+ }
97
87
  return parts.join(` ${sep} `);
98
88
  }
99
89
  async onWrite(data) {
@@ -169,10 +159,11 @@ class ConsoleSessionDriver extends SessionDriverAbstract_1.SessionDriverAbstract
169
159
  if (isHeader && data.action === SessionDriverAbstract_1.ActionEnum.End) {
170
160
  return;
171
161
  }
172
- this.printMessage(message);
173
- if (!this.options.verbose)
174
- if (!hasProgress || data.action === SessionDriverAbstract_1.ActionEnum.End || isHeader)
175
- process.stdout.write("\n");
162
+ const endMessage = !this.options.verbose &&
163
+ (!hasProgress || data.action === SessionDriverAbstract_1.ActionEnum.End || isHeader);
164
+ this.printMessage(message, endMessage);
165
+ if (endMessage)
166
+ process.stdout.write("\n");
176
167
  }
177
168
  }
178
169
  exports.ConsoleSessionDriver = ConsoleSessionDriver;
@@ -73,6 +73,6 @@ export declare abstract class SessionDriverAbstract {
73
73
  });
74
74
  onInit(): Promise<void>;
75
75
  abstract onWrite(data: WriteDataType): Promise<void>;
76
- onEnd(): Promise<void>;
76
+ onEnd(data?: Record<string, any>): Promise<void>;
77
77
  abstract onRead(data: ReadDataType, entity: EntityEnum): Promise<ReadResultType[]>;
78
78
  }
@@ -22,6 +22,6 @@ class SessionDriverAbstract {
22
22
  this.options = options;
23
23
  }
24
24
  async onInit() { }
25
- async onEnd() { }
25
+ async onEnd(data) { }
26
26
  }
27
27
  exports.SessionDriverAbstract = SessionDriverAbstract;
@@ -27,7 +27,7 @@ export declare class BackupSessionManager {
27
27
  repositoryName: string;
28
28
  }): number;
29
29
  initDrivers(): Promise<void>;
30
- endDrivers(): Promise<void>;
30
+ endDrivers(data?: Record<string, any>): Promise<void>;
31
31
  protected alter(data: WriteDataType): Promise<number>;
32
32
  readAll(options: BackupSessionsActionOptionsType): Promise<import("../SessionDriver/SessionDriverAbstract").ReadResultType[]>;
33
33
  init(input: Pick<BackupSessionEntity, "packageName" | "snapshotId" | "tags">): Promise<number>;
@@ -27,10 +27,10 @@ class BackupSessionManager {
27
27
  await driver.onInit();
28
28
  }
29
29
  }
30
- async endDrivers() {
30
+ async endDrivers(data) {
31
31
  const drivers = [this.options.driver, ...(this.options.altDrivers ?? [])];
32
32
  for (const driver of drivers) {
33
- await driver.onEnd();
33
+ await driver.onEnd(data);
34
34
  }
35
35
  }
36
36
  async alter(data) {
package/Task/GitTask.d.ts CHANGED
@@ -18,6 +18,10 @@ export declare type GitTaskConfigType = {
18
18
  * @default true
19
19
  */
20
20
  includeConfig?: boolean;
21
+ /**
22
+ * @default 1
23
+ */
24
+ fileCopyConcurrency?: number;
21
25
  };
22
26
  export declare const gitTaskName = "git";
23
27
  export declare const gitTaskDefinition: JSONSchema7;
package/Task/GitTask.js CHANGED
@@ -9,7 +9,6 @@ const process_util_1 = require("../util/process-util");
9
9
  const TaskAbstract_1 = require("./TaskAbstract");
10
10
  const assert_1 = require("assert");
11
11
  const fs_1 = require("fs");
12
- const fs_extra_1 = require("fs-extra");
13
12
  const promises_1 = require("fs/promises");
14
13
  const micromatch_1 = require("micromatch");
15
14
  const path_1 = require("path");
@@ -49,6 +48,10 @@ exports.gitTaskDefinition = {
49
48
  includeConfig: {
50
49
  type: "boolean",
51
50
  },
51
+ fileCopyConcurrency: {
52
+ type: "integer",
53
+ minimum: 1,
54
+ },
52
55
  },
53
56
  };
54
57
  class GitTask extends TaskAbstract_1.TaskAbstract {
@@ -69,6 +72,9 @@ class GitTask extends TaskAbstract_1.TaskAbstract {
69
72
  (0, assert_1.ok)(typeof targetPath === "string");
70
73
  // Bundle
71
74
  const bundlePath = (0, path_1.join)(targetPath, "repo.bundle");
75
+ await data.onProgress({
76
+ step: "Creating bundle...",
77
+ });
72
78
  await (0, process_util_1.exec)(this.command, ["bundle", "create", bundlePath, "--all"], {
73
79
  cwd: path,
74
80
  }, {
@@ -152,40 +158,28 @@ class GitTask extends TaskAbstract_1.TaskAbstract {
152
158
  for (const option of lsFilesConfig) {
153
159
  if (!option.include)
154
160
  continue;
155
- const createdPaths = [];
156
161
  const outPath = (0, path_1.join)(targetPath, `repo.${option.name}`);
157
162
  await (0, fs_util_1.mkdirIfNotExists)(outPath);
158
163
  if (data.options.verbose)
159
164
  (0, cli_util_1.logExec)(`Copying ${option.name} files to ${outPath}`);
160
- const reader = (0, readline_1.createInterface)({
161
- input: (0, fs_1.createReadStream)(option.pathsPath),
162
- });
163
- for await (const entry of reader) {
164
- const source = (0, path_1.join)(path, entry);
165
- const target = (0, path_1.join)(outPath, entry);
166
- if (entry.endsWith("/")) {
167
- await (0, promises_1.mkdir)(target, {
168
- recursive: true,
169
- });
170
- }
171
- else {
165
+ await (0, fs_util_1.cpy)({
166
+ input: {
167
+ type: "pathList",
168
+ path: option.pathsPath,
169
+ basePath: path,
170
+ },
171
+ targetPath: outPath,
172
+ concurrency: this.config.fileCopyConcurrency,
173
+ onPath: async ({ entryPath }) => {
172
174
  currentFiles++;
173
175
  await data.onProgress({
174
176
  total,
175
177
  current: currentFiles,
176
178
  percent: (0, math_util_1.progressPercent)(total, currentFiles),
177
- step: entry,
179
+ step: entryPath,
178
180
  });
179
- const dir = (0, path_1.dirname)(target);
180
- if (!createdPaths.includes(dir)) {
181
- await (0, promises_1.mkdir)(dir, {
182
- recursive: true,
183
- });
184
- createdPaths.push(dir);
185
- }
186
- await (0, promises_1.copyFile)(source, target);
187
- }
188
- }
181
+ },
182
+ });
189
183
  await (0, promises_1.rm)(option.pathsPath);
190
184
  }
191
185
  }
@@ -235,10 +229,15 @@ class GitTask extends TaskAbstract_1.TaskAbstract {
235
229
  if (await (0, fs_util_1.checkDir)(sourcePath)) {
236
230
  if (data.options.verbose)
237
231
  (0, cli_util_1.logExec)(`Copying ${name} files to ${restorePath}`);
238
- await (0, fs_extra_1.copy)(sourcePath, restorePath, {
239
- filter: async (path) => {
240
- await incrementProgress((0, path_1.relative)(sourcePath, path));
241
- return true;
232
+ await (0, fs_util_1.cpy)({
233
+ input: {
234
+ type: "glob",
235
+ sourcePath,
236
+ },
237
+ targetPath: restorePath,
238
+ concurrency: this.config.fileCopyConcurrency,
239
+ onPath: async ({ entryPath }) => {
240
+ await incrementProgress(entryPath);
242
241
  },
243
242
  });
244
243
  }
@@ -83,21 +83,30 @@ class MariadbTask extends TaskAbstract_1.TaskAbstract {
83
83
  await (0, fs_util_1.forEachFile)(sourcePath, () => {
84
84
  total++;
85
85
  });
86
- const onData = async (lines) => {
87
- const regex = /\[\d{1,}\] \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} Copying (.+) to/;
88
- let path = lines
89
- .split(/\r?\n/)
90
- .reduce((result, line) => {
91
- const matches = regex.exec(line);
92
- if (matches) {
93
- current++;
94
- result.push(matches[1]);
86
+ let childProcess;
87
+ const onData = async (strLines) => {
88
+ const paths = [];
89
+ const pathRegex = /\[\d{1,}\] \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} Copying (.+) to/;
90
+ const lines = strLines.split(/\r?\n/);
91
+ let fatalError = false;
92
+ for (const line of lines) {
93
+ if (line.includes("[ERROR] InnoDB: Unsupported redo log format.") ||
94
+ line.includes("Error: cannot read redo log header")) {
95
+ fatalError = true;
95
96
  }
96
- return result;
97
- }, [])
98
- .pop();
99
- if (path) {
100
- path = (0, posix_1.normalize)(path);
97
+ else {
98
+ const matches = pathRegex.exec(line);
99
+ if (matches) {
100
+ current++;
101
+ paths.push(matches[1]);
102
+ }
103
+ }
104
+ }
105
+ if (fatalError) {
106
+ childProcess.kill();
107
+ }
108
+ else if (paths.length) {
109
+ const path = (0, posix_1.normalize)(paths[0]);
101
110
  await data.onProgress({
102
111
  current,
103
112
  percent: (0, math_util_1.progressPercent)(total, current),
@@ -108,6 +117,9 @@ class MariadbTask extends TaskAbstract_1.TaskAbstract {
108
117
  };
109
118
  await (0, process_util_1.exec)(command, args, undefined, {
110
119
  log: this.verbose,
120
+ onSpawn: (p) => {
121
+ childProcess = p;
122
+ },
111
123
  stdout: {
112
124
  onData,
113
125
  },
@@ -117,7 +129,7 @@ class MariadbTask extends TaskAbstract_1.TaskAbstract {
117
129
  });
118
130
  await (0, process_util_1.exec)(command, [`--prepare`, `--target-dir=${targetPath}`], undefined, {
119
131
  log: this.verbose,
120
- stderr: { onData: async () => { } },
132
+ stderr: { onData: () => { } },
121
133
  });
122
134
  }
123
135
  async onRestore(data) {
package/Task/MssqlTask.js CHANGED
@@ -8,7 +8,6 @@ const fs_util_1 = require("../util/fs-util");
8
8
  const process_util_1 = require("../util/process-util");
9
9
  const TaskAbstract_1 = require("./TaskAbstract");
10
10
  const assert_1 = require("assert");
11
- const fs_extra_1 = require("fs-extra");
12
11
  const promises_1 = require("fs/promises");
13
12
  const micromatch_1 = require("micromatch");
14
13
  const path_1 = require("path");
@@ -85,7 +84,7 @@ class MssqlTask extends TaskAbstract_1.TaskAbstract {
85
84
  const restorePath = data.package.restorePath;
86
85
  (0, assert_1.ok)(typeof restorePath === "string");
87
86
  await (0, fs_util_1.mkdirIfNotExists)(restorePath);
88
- const files = await (0, fs_extra_1.readdir)(restorePath);
87
+ const files = await (0, promises_1.readdir)(restorePath);
89
88
  for (const file of files) {
90
89
  if (!file.endsWith(MssqlTask.SUFFIX))
91
90
  continue;
@@ -8,7 +8,7 @@ export declare class MysqlDumpTask extends SqlDumpTaskAbstract<MysqlDumpTaskConf
8
8
  onDatabaseIsEmpty(name: string): Promise<boolean>;
9
9
  onCreateDatabase(database: TargetDatabaseType): Promise<void>;
10
10
  onExecQuery(query: string): Promise<import("../util/process-util").ExecResultType>;
11
- onFetchTableNames(): Promise<string[]>;
11
+ onFetchTableNames(database: string): Promise<string[]>;
12
12
  onExport(tableNames: string[], output: string): Promise<void>;
13
13
  onImport(path: string, database: string): Promise<void>;
14
14
  }
@@ -17,6 +17,7 @@ class MysqlDumpTask extends SqlDumpTaskAbstract_1.SqlDumpTaskAbstract {
17
17
  const password = await this.fetchPassword();
18
18
  return [
19
19
  `--host=${this.config.hostname}`,
20
+ ...(this.config.port ? [`--port=${this.config.port}`] : []),
20
21
  `--user=${this.config.username}`,
21
22
  `--password=${password ?? ""}`,
22
23
  ...(database && this.config.database ? [this.config.database] : []),
@@ -57,14 +58,14 @@ class MysqlDumpTask extends SqlDumpTaskAbstract_1.SqlDumpTaskAbstract {
57
58
  },
58
59
  });
59
60
  }
60
- async onFetchTableNames() {
61
+ async onFetchTableNames(database) {
61
62
  return await this.fetchValues(`
62
63
  SELECT
63
64
  table_name
64
65
  FROM
65
66
  information_schema.tables
66
67
  WHERE
67
- table_schema = '${this.config.database}'
68
+ table_schema = '${database}'
68
69
  `);
69
70
  }
70
71
  async onExport(tableNames, output) {
@@ -77,6 +78,7 @@ class MysqlDumpTask extends SqlDumpTaskAbstract_1.SqlDumpTaskAbstract {
77
78
  await (0, process_util_1.exec)("mysqldump", [
78
79
  ...(await this.buildConnectionArgs()),
79
80
  "--lock-tables=false",
81
+ "--skip-add-drop-table=false",
80
82
  ...tableNames,
81
83
  ], null, {
82
84
  pipe: { stream: stream },
@@ -8,7 +8,7 @@ export declare class PostgresqlDumpTask extends SqlDumpTaskAbstract<PostgresqlDu
8
8
  onDatabaseIsEmpty(name: string): Promise<boolean>;
9
9
  onCreateDatabase(database: TargetDatabaseType): Promise<void>;
10
10
  onExecQuery(query: string): Promise<import("../util/process-util").ExecResultType>;
11
- onFetchTableNames(): Promise<string[]>;
11
+ onFetchTableNames(database: string): Promise<string[]>;
12
12
  onExport(tableNames: string[], output: string): Promise<void>;
13
13
  onImport(path: string, database: string): Promise<void>;
14
14
  }
@@ -60,14 +60,14 @@ class PostgresqlDumpTask extends SqlDumpTaskAbstract_1.SqlDumpTaskAbstract {
60
60
  },
61
61
  });
62
62
  }
63
- async onFetchTableNames() {
63
+ async onFetchTableNames(database) {
64
64
  return await this.fetchValues(`
65
65
  SELECT
66
66
  CONCAT(table_schema, '.', table_name)
67
67
  FROM
68
68
  information_schema.tables
69
69
  WHERE
70
- table_catalog = '${this.config.database}' AND
70
+ table_catalog = '${database}' AND
71
71
  table_schema NOT IN ('pg_catalog', 'information_schema')
72
72
  `);
73
73
  }
@@ -80,8 +80,6 @@ class PostgresqlDumpTask extends SqlDumpTaskAbstract_1.SqlDumpTaskAbstract {
80
80
  }),
81
81
  (0, process_util_1.exec)("pg_dump", [
82
82
  ...(await this.buildConnectionArgs()),
83
- "--clean",
84
- "--if-exists",
85
83
  ...(tableNames?.flatMap((v) => ["-t", v]) ?? []),
86
84
  ], null, {
87
85
  pipe: { stream: stream },
@@ -26,11 +26,10 @@ export declare abstract class SqlDumpTaskAbstract<TConfig extends SqlDumpTaskCon
26
26
  fetchValues(query: string): Promise<string[]>;
27
27
  abstract onCreateDatabase(database: TargetDatabaseType): Promise<void>;
28
28
  abstract onDatabaseIsEmpty(databaseName: string): Promise<boolean>;
29
- abstract onFetchTableNames(): Promise<string[]>;
29
+ abstract onFetchTableNames(database: string): Promise<string[]>;
30
30
  abstract onExecQuery(query: string): ReturnType<typeof exec>;
31
31
  abstract onExport(tableNames: string[], output: string): Promise<void>;
32
32
  abstract onImport(path: string, database: string): Promise<void>;
33
- fetchTableNames(): Promise<string[]>;
34
33
  onBackup(data: BackupDataType): Promise<void>;
35
34
  onRestore(data: RestoreDataType): Promise<void>;
36
35
  }
@@ -49,6 +49,39 @@ exports.sqlDumpTaskDefinition = {
49
49
  oneFileByTable: { type: "boolean" },
50
50
  },
51
51
  };
52
+ function serializeSqlFile(input) {
53
+ if (input.database && input.table) {
54
+ return `${input.database}.${input.table}.table.sql`;
55
+ }
56
+ else if (input.database && !input.table) {
57
+ return `${input.database}.database.sql`;
58
+ }
59
+ else if (!input.database && input.table) {
60
+ return `${input.table}.table.sql`;
61
+ }
62
+ else {
63
+ throw new AppError_1.AppError(`Invalid sql file input: ${JSON.stringify(input)}`);
64
+ }
65
+ }
66
+ function parseSqlFile(fileName) {
67
+ if (!fileName.endsWith(".sql"))
68
+ return;
69
+ const regex = /^(.+)\.(table|database)\.sql$/;
70
+ const matches = regex.exec(fileName);
71
+ if (!matches)
72
+ return { fileName };
73
+ const [, name, type] = matches;
74
+ const lastName = name.split(".").pop();
75
+ if (type === "table") {
76
+ return { fileName, table: lastName };
77
+ }
78
+ else if (type === "database") {
79
+ return { fileName, database: lastName };
80
+ }
81
+ else {
82
+ throw new Error(`Invalid sql file type: ${type}`);
83
+ }
84
+ }
52
85
  class SqlDumpTaskAbstract extends TaskAbstract_1.TaskAbstract {
53
86
  async fetchPassword() {
54
87
  if (typeof this.config.password === "string")
@@ -66,26 +99,23 @@ class SqlDumpTaskAbstract extends TaskAbstract_1.TaskAbstract {
66
99
  return result;
67
100
  }, []);
68
101
  }
69
- async fetchTableNames() {
70
- const tableNames = await this.onFetchTableNames();
102
+ async onBackup(data) {
103
+ this.verbose = data.options.verbose;
71
104
  const config = this.config;
72
- return tableNames.filter((tableName) => {
105
+ const outputPath = data.package.path;
106
+ const allTableNames = await this.onFetchTableNames(this.config.database);
107
+ const tableNames = allTableNames.filter((tableName) => {
73
108
  if (config.includeTables && !(0, micromatch_1.isMatch)(tableName, config.includeTables))
74
109
  return false;
75
110
  if (config.excludeTables && (0, micromatch_1.isMatch)(tableName, config.excludeTables))
76
111
  return false;
77
112
  return true;
78
113
  });
79
- }
80
- async onBackup(data) {
81
- this.verbose = data.options.verbose;
82
- const outputPath = data.package.path;
83
- const tableNames = await this.fetchTableNames();
84
114
  (0, assert_1.ok)(typeof outputPath === "string");
85
115
  if (!(await (0, fs_util_1.checkDir)(outputPath)))
86
116
  await (0, promises_1.mkdir)(outputPath, { recursive: true });
87
117
  if (!this.config.oneFileByTable) {
88
- const outPath = (0, path_1.join)(outputPath, this.config.database) + ".database.sql";
118
+ const outPath = (0, path_1.join)(outputPath, serializeSqlFile({ database: this.config.database }));
89
119
  await this.onExport(tableNames, outPath);
90
120
  }
91
121
  else {
@@ -98,7 +128,7 @@ class SqlDumpTaskAbstract extends TaskAbstract_1.TaskAbstract {
98
128
  step: tableName,
99
129
  });
100
130
  current++;
101
- const outPath = (0, path_1.join)(outputPath, tableName) + ".table.sql";
131
+ const outPath = (0, path_1.join)(outputPath, serializeSqlFile({ table: tableName }));
102
132
  await this.onExport([tableName], outPath);
103
133
  }
104
134
  }
@@ -125,20 +155,33 @@ class SqlDumpTaskAbstract extends TaskAbstract_1.TaskAbstract {
125
155
  database: database.name,
126
156
  });
127
157
  }
128
- if (!(await this.onDatabaseIsEmpty(database.name)))
158
+ const items = (await (0, promises_1.readdir)(restorePath))
159
+ .map(parseSqlFile)
160
+ .filter((v) => !!v);
161
+ // Database check
162
+ const databaseItems = items.filter((v) => v.database);
163
+ if (databaseItems.length && !(await this.onDatabaseIsEmpty(database.name)))
129
164
  throw new AppError_1.AppError(`Target database is not empty: ${database.name}`);
165
+ // Table check
166
+ const restoreTables = items
167
+ .filter((v) => v.table)
168
+ .map((v) => v.table);
169
+ const serverTables = await this.onFetchTableNames(database.name);
170
+ const errorTables = restoreTables.filter((v) => serverTables.includes(v));
171
+ if (errorTables.length) {
172
+ throw new AppError_1.AppError(`Target table already exists: ${errorTables.join(", ")}`);
173
+ }
130
174
  await this.onCreateDatabase(database);
131
175
  if (this.verbose)
132
176
  (0, cli_util_1.logExec)("readdir", [restorePath]);
133
- const files = (await (0, promises_1.readdir)(restorePath)).filter((name) => /\.sql$/i.test(name));
134
177
  let current = 0;
135
- for (const file of files) {
136
- const path = (0, path_1.join)(restorePath, file);
178
+ for (const item of items) {
179
+ const path = (0, path_1.join)(restorePath, item.fileName);
137
180
  data.onProgress({
138
- total: files.length,
181
+ total: items.length,
139
182
  current: current,
140
- percent: (0, math_util_1.progressPercent)(files.length, current),
141
- step: file,
183
+ percent: (0, math_util_1.progressPercent)(items.length, current),
184
+ step: item.fileName,
142
185
  });
143
186
  current++;
144
187
  await this.onImport(path, database.name);
@@ -3,10 +3,10 @@ import { RestoreActionOptionsType } from "../Action/RestoreAction";
3
3
  import { PackageConfigType } from "../Config/PackageConfig";
4
4
  import { SnapshotType } from "../Repository/RepositoryAbstract";
5
5
  export declare type ProgressDataType = {
6
- total: number;
7
- current: number;
8
- percent: number;
9
- step: string;
6
+ total?: number;
7
+ current?: number;
8
+ percent?: number;
9
+ step?: string;
10
10
  stepPercent?: number;
11
11
  };
12
12
  export declare type BackupDataType = {
@@ -487,6 +487,10 @@
487
487
  }
488
488
  }
489
489
  ]
490
+ },
491
+ "fileCopyConcurrency": {
492
+ "type": "integer",
493
+ "minimum": 1
490
494
  }
491
495
  }
492
496
  },
@@ -615,6 +619,10 @@
615
619
  },
616
620
  "includeConfig": {
617
621
  "type": "boolean"
622
+ },
623
+ "fileCopyConcurrency": {
624
+ "type": "integer",
625
+ "minimum": 1
618
626
  }
619
627
  }
620
628
  },
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@datatruck/cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "dependencies": {
5
5
  "ajv": "^8.11.0",
6
+ "async": "^3.2.4",
6
7
  "chalk": "^4.1.2",
7
8
  "cli-table3": "^0.6.2",
8
9
  "commander": "^9.2.0",
9
10
  "dayjs": "^1.11.2",
10
11
  "fast-glob": "^3.2.11",
11
- "fs-extra": "^10.1.0",
12
12
  "micromatch": "^4.0.5",
13
13
  "sqlite": "^4.1.1",
14
14
  "sqlite3": "^5.0.8"
package/util/GitUtil.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GitUtil = void 0;
4
4
  const fs_util_1 = require("./fs-util");
5
5
  const process_util_1 = require("./process-util");
6
- const fs_extra_1 = require("fs-extra");
6
+ const promises_1 = require("fs/promises");
7
7
  class GitUtil {
8
8
  constructor(options) {
9
9
  this.options = options;
@@ -16,7 +16,7 @@ class GitUtil {
16
16
  }
17
17
  async canBeInit(repo) {
18
18
  return ((0, fs_util_1.isLocalDir)(repo) &&
19
- (!(await (0, fs_util_1.checkDir)(repo)) || !(await (0, fs_extra_1.readdir)(repo)).length));
19
+ (!(await (0, fs_util_1.checkDir)(repo)) || !(await (0, promises_1.readdir)(repo)).length));
20
20
  }
21
21
  async clone(options) {
22
22
  return await this.exec([
@@ -84,7 +84,7 @@ export declare class ResticUtil {
84
84
  excludeFile?: string[];
85
85
  parent?: string;
86
86
  allowEmptySnapshot?: boolean;
87
- onStream?: (data: BackupStreamType) => Promise<void>;
87
+ onStream?: (data: BackupStreamType) => void;
88
88
  }): Promise<ExecResultType>;
89
89
  restore(options: {
90
90
  id: string;