@datatruck/cli 0.12.1 → 0.14.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 (38) hide show
  1. package/Command/BackupCommand.js +2 -0
  2. package/Command/CommandAbstract.d.ts +2 -0
  3. package/Command/RestoreCommand.js +2 -0
  4. package/Command/SnapshotsCommand.js +6 -0
  5. package/Config/PackageRepositoryConfig.d.ts +3 -3
  6. package/Config/PackageRepositoryConfig.js +2 -2
  7. package/Config/RepositoryConfig.d.ts +3 -3
  8. package/Config/RepositoryConfig.js +2 -2
  9. package/Factory/RepositoryFactory.js +3 -3
  10. package/JsonSchema/DefinitionEnum.d.ts +2 -2
  11. package/JsonSchema/DefinitionEnum.js +2 -2
  12. package/JsonSchema/JsonSchema.js +3 -3
  13. package/Repository/{LocalRepository.d.ts → DatatruckRepository.d.ts} +11 -9
  14. package/Repository/{LocalRepository.js → DatatruckRepository.js} +215 -102
  15. package/Repository/GitRepository.js +3 -0
  16. package/Repository/RepositoryAbstract.d.ts +4 -1
  17. package/Repository/RepositoryAbstract.js +1 -0
  18. package/Repository/ResticRepository.js +25 -1
  19. package/SessionDriver/ConsoleSessionDriver.d.ts +8 -2
  20. package/SessionDriver/ConsoleSessionDriver.js +18 -10
  21. package/SessionDriver/SessionDriverAbstract.d.ts +6 -7
  22. package/Task/GitTask.js +1 -0
  23. package/Task/SqlDumpTaskAbstract.js +1 -0
  24. package/cli.js +2 -0
  25. package/config.schema.json +10 -6
  26. package/package.json +3 -1
  27. package/util/ResticUtil.d.ts +9 -2
  28. package/util/ResticUtil.js +47 -20
  29. package/util/datatruck/config-util.d.ts +6 -6
  30. package/util/fs-util.d.ts +18 -20
  31. package/util/fs-util.js +86 -101
  32. package/util/math-util.js +2 -0
  33. package/util/process-util.d.ts +1 -0
  34. package/util/process-util.js +23 -5
  35. package/util/string-util.d.ts +1 -0
  36. package/util/string-util.js +8 -1
  37. package/util/zip-util.d.ts +45 -18
  38. package/util/zip-util.js +98 -52
@@ -16,6 +16,11 @@ class ConsoleSessionDriver extends SessionDriverAbstract_1.SessionDriverAbstract
16
16
  this.chron = (0, date_util_1.createChron)();
17
17
  }
18
18
  async onInit() {
19
+ this.tty = this.options.verbose
20
+ ? false
21
+ : this.options.progress === "auto"
22
+ ? process.stdout.isTTY
23
+ : this.options.progress === "tty";
19
24
  this.chron.start();
20
25
  this.renderInterval = setInterval(() => {
21
26
  if (this.lastMessage)
@@ -24,7 +29,7 @@ class ConsoleSessionDriver extends SessionDriverAbstract_1.SessionDriverAbstract
24
29
  }
25
30
  async onEnd(data) {
26
31
  clearInterval(this.renderInterval);
27
- if (!this.options.verbose)
32
+ if (this.tty)
28
33
  process.stdout.write(cli_util_1.showCursorCommand);
29
34
  (0, cli_util_1.logVars)({
30
35
  ...data,
@@ -36,10 +41,10 @@ class ConsoleSessionDriver extends SessionDriverAbstract_1.SessionDriverAbstract
36
41
  }
37
42
  printMessage(message, endMessage) {
38
43
  const text = this.renderMessage(message);
39
- if (this.options.verbose && this.lastMessageText === text) {
44
+ if (!this.tty && this.lastMessageText === text) {
40
45
  return;
41
46
  }
42
- if (this.options.verbose) {
47
+ if (!this.tty) {
43
48
  process.stdout.write(`${this.renderSpinner(text)}\n`);
44
49
  }
45
50
  else {
@@ -58,7 +63,7 @@ class ConsoleSessionDriver extends SessionDriverAbstract_1.SessionDriverAbstract
58
63
  this.lastMessageText = text;
59
64
  }
60
65
  renderSpinner(text) {
61
- return text.replace("{spinner}", (0, chalk_1.grey)(this.options.verbose ? "?" : (0, cli_util_1.renderSpinner)(this.prints)));
66
+ return text.replace("{spinner}", (0, chalk_1.grey)(this.tty ? (0, cli_util_1.renderSpinner)(this.prints) : "?"));
62
67
  }
63
68
  renderMessage(message) {
64
69
  const badges = renderBadges([
@@ -66,7 +71,6 @@ class ConsoleSessionDriver extends SessionDriverAbstract_1.SessionDriverAbstract
66
71
  ...(message.errorBadge ? [message.errorBadge] : []),
67
72
  ]);
68
73
  const padding = " ".repeat(message.level ?? 0);
69
- const haveProgressBar = typeof message.progressPercent === "number";
70
74
  const sessionId = message.sessionId.toString().padStart(2, "0");
71
75
  const parts = [
72
76
  `${padding}${message.textPrefix} [${(0, chalk_1.grey)(sessionId)}] ${message.text}`,
@@ -89,6 +93,13 @@ class ConsoleSessionDriver extends SessionDriverAbstract_1.SessionDriverAbstract
89
93
  async onWrite(data) {
90
94
  if (data.action === SessionDriverAbstract_1.ActionEnum.Init)
91
95
  return;
96
+ if (data.action === SessionDriverAbstract_1.ActionEnum.Progress && this.options.progressInterval) {
97
+ const skip = this.lastProgressDate &&
98
+ Date.now() - this.lastProgressDate < this.options.progressInterval;
99
+ if (skip)
100
+ return;
101
+ this.lastProgressDate = Date.now();
102
+ }
92
103
  const message = {
93
104
  sessionId: "sessionId" in data.data ? data.data.sessionId : data.data.id,
94
105
  badges: [],
@@ -113,9 +124,7 @@ class ConsoleSessionDriver extends SessionDriverAbstract_1.SessionDriverAbstract
113
124
  if (data.data.error)
114
125
  message.errorBadge = {
115
126
  name: "error",
116
- value: this.options.verbose
117
- ? data.data.error
118
- : data.data.error.split("\n")[0],
127
+ value: this.tty ? data.data.error.split("\n")[0] : data.data.error,
119
128
  color: chalk_1.red,
120
129
  };
121
130
  }
@@ -159,8 +168,7 @@ class ConsoleSessionDriver extends SessionDriverAbstract_1.SessionDriverAbstract
159
168
  if (isHeader && data.action === SessionDriverAbstract_1.ActionEnum.End) {
160
169
  return;
161
170
  }
162
- const endMessage = !this.options.verbose &&
163
- (!hasProgress || data.action === SessionDriverAbstract_1.ActionEnum.End || isHeader);
171
+ const endMessage = this.tty && (!hasProgress || data.action === SessionDriverAbstract_1.ActionEnum.End || isHeader);
164
172
  this.printMessage(message, endMessage);
165
173
  if (endMessage)
166
174
  process.stdout.write("\n");
@@ -64,13 +64,12 @@ export declare type ReadResultType = {
64
64
  repositoryType: string;
65
65
  error: string | null;
66
66
  };
67
- export declare abstract class SessionDriverAbstract {
68
- readonly options: {
69
- verbose?: boolean;
70
- };
71
- constructor(options: {
72
- verbose?: boolean;
73
- });
67
+ export declare type SessionDriverOptions = {
68
+ verbose?: boolean;
69
+ };
70
+ export declare abstract class SessionDriverAbstract<TOptions extends SessionDriverOptions = SessionDriverOptions> {
71
+ readonly options: TOptions;
72
+ constructor(options: TOptions);
74
73
  onInit(): Promise<void>;
75
74
  abstract onWrite(data: WriteDataType): Promise<void>;
76
75
  onEnd(data?: Record<string, any>): Promise<void>;
package/Task/GitTask.js CHANGED
@@ -169,6 +169,7 @@ class GitTask extends TaskAbstract_1.TaskAbstract {
169
169
  basePath: path,
170
170
  },
171
171
  targetPath: outPath,
172
+ skipNotFoundError: true,
172
173
  concurrency: this.config.fileCopyConcurrency,
173
174
  onPath: async ({ entryPath }) => {
174
175
  currentFiles++;
@@ -15,6 +15,7 @@ const path_1 = require("path");
15
15
  exports.sqlDumpTaskDefinition = {
16
16
  type: "object",
17
17
  required: ["password", "hostname", "username", "database"],
18
+ additionalProperties: false,
18
19
  properties: {
19
20
  password: {
20
21
  anyOf: [
package/cli.js CHANGED
@@ -80,6 +80,8 @@ program.description(description);
80
80
  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
+ program.option("--progress <value>", "Progress type (auto, plain, tty)", "auto");
84
+ program.option("--progress-interval <ms>", "Progress interval");
83
85
  program.option("-o,--output-format <format>", "Output format (json, pjson, yaml, table, custom=$, tpl=name)", "table");
84
86
  makeCommand(CommandFactory_1.CommandEnum.config).alias("c");
85
87
  makeCommand(CommandFactory_1.CommandEnum.init).alias("i");
@@ -81,7 +81,7 @@
81
81
  "type": "object",
82
82
  "properties": {
83
83
  "type": {
84
- "const": "local"
84
+ "const": "datatruck"
85
85
  }
86
86
  }
87
87
  },
@@ -89,7 +89,7 @@
89
89
  "type": "object",
90
90
  "properties": {
91
91
  "config": {
92
- "$ref": "#/definitions/local-repository"
92
+ "$ref": "#/definitions/datatruck-repository"
93
93
  }
94
94
  }
95
95
  },
@@ -248,7 +248,7 @@
248
248
  "type": "object",
249
249
  "properties": {
250
250
  "type": {
251
- "const": "local"
251
+ "const": "datatruck"
252
252
  }
253
253
  }
254
254
  },
@@ -256,7 +256,7 @@
256
256
  "type": "object",
257
257
  "properties": {
258
258
  "config": {
259
- "$ref": "#/definitions/local-package-repository"
259
+ "$ref": "#/definitions/datatruck-package-repository"
260
260
  }
261
261
  }
262
262
  },
@@ -438,7 +438,7 @@
438
438
  "additionalProperties": false,
439
439
  "properties": {}
440
440
  },
441
- "local-repository": {
441
+ "datatruck-repository": {
442
442
  "type": "object",
443
443
  "required": [
444
444
  "outPath"
@@ -453,7 +453,7 @@
453
453
  }
454
454
  }
455
455
  },
456
- "local-package-repository": {
456
+ "datatruck-package-repository": {
457
457
  "type": "object",
458
458
  "additionalProperties": false,
459
459
  "properties": {
@@ -475,6 +475,9 @@
475
475
  "include"
476
476
  ],
477
477
  "properties": {
478
+ "name": {
479
+ "type": "string"
480
+ },
478
481
  "include": {
479
482
  "$ref": "#/definitions/stringlist-util"
480
483
  },
@@ -683,6 +686,7 @@
683
686
  "username",
684
687
  "database"
685
688
  ],
689
+ "additionalProperties": false,
686
690
  "properties": {
687
691
  "password": {
688
692
  "anyOf": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datatruck/cli",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "dependencies": {
5
5
  "ajv": "^8.11.0",
6
6
  "async": "^3.2.4",
@@ -8,8 +8,10 @@
8
8
  "cli-table3": "^0.6.2",
9
9
  "commander": "^9.4.0",
10
10
  "dayjs": "^1.11.5",
11
+ "fast-folder-size": "^1.7.1",
11
12
  "fast-glob": "^3.2.11",
12
13
  "micromatch": "^4.0.5",
14
+ "pretty-bytes": "^5.6.0",
13
15
  "sqlite": "^4.1.2",
14
16
  "sqlite3": "^5.0.11"
15
17
  },
@@ -10,13 +10,16 @@ export declare type RepositoryType = {
10
10
  } & Omit<UriType, "password">;
11
11
  export declare type BackupStreamType = {
12
12
  message_type: "status";
13
- seconds_elapsed: number;
13
+ seconds_elapsed?: number;
14
14
  percent_done: number;
15
- total_files: number;
15
+ total_files?: number;
16
16
  files_done?: number;
17
17
  total_bytes: number;
18
18
  bytes_done?: number;
19
19
  current_files?: string[];
20
+ } | {
21
+ message_type: "restore-status";
22
+ total_bytes: number;
20
23
  } | {
21
24
  message_type: "summary";
22
25
  files_new: number;
@@ -95,6 +98,10 @@ export declare class ResticUtil {
95
98
  restore(options: {
96
99
  id: string;
97
100
  target: string;
101
+ /**
102
+ * @default 30_000
103
+ */
104
+ progressInterval?: number | false;
98
105
  onStream?: (data: BackupStreamType) => Promise<void>;
99
106
  }): Promise<ExecResultType>;
100
107
  }
@@ -29,7 +29,7 @@ class ResticUtil {
29
29
  async exec(args, settings, options) {
30
30
  return await (0, process_util_1.exec)("restic", args, {
31
31
  stdio: ["ignore", "pipe", "pipe"],
32
- env: { ...this.options.env },
32
+ env: { ...process.env, ...this.options.env },
33
33
  cwd: options?.cwd,
34
34
  }, {
35
35
  stderr: { toExitCode: true },
@@ -40,7 +40,11 @@ class ResticUtil {
40
40
  stderr: true,
41
41
  colorize: true,
42
42
  allToStderr: true,
43
- envNames: ["RESTIC_REPOSITORY", "RESTIC_PASSWORD_FILE"],
43
+ envNames: [
44
+ "RESTIC_REPOSITORY",
45
+ "RESTIC_PASSWORD_FILE",
46
+ "RESTIC_PASSWORD",
47
+ ],
44
48
  }
45
49
  : {},
46
50
  ...(settings ?? {}),
@@ -96,11 +100,12 @@ class ResticUtil {
96
100
  return JSON.parse(result.stdout);
97
101
  }
98
102
  async checkBackupSetPathSupport() {
99
- const result = await this.exec(["backup", "--set-path"], {
100
- onExitCodeError: () => false,
101
- stderr: { save: true },
103
+ /*const result = await this.exec(["backup", "--set-path"], {
104
+ onExitCodeError: () => false,
105
+ stderr: { save: true },
102
106
  });
103
- return result.stderr.includes("flag needs an argument");
107
+ return result.stderr.includes("flag needs an argument");*/
108
+ return false;
104
109
  }
105
110
  async backup(options) {
106
111
  const exec = async () => await this.exec([
@@ -174,20 +179,42 @@ class ResticUtil {
174
179
  });
175
180
  }
176
181
  async restore(options) {
177
- return await this.exec(["restore", "--json", options.id, "--target", options.target], {
178
- stderr: {
179
- toExitCode: true,
180
- },
181
- stdout: {
182
- ...(options.onStream && {
183
- onData: async (data) => {
184
- if (data.startsWith("{") && data.endsWith("}")) {
185
- await options.onStream?.(JSON.parse(data));
186
- }
187
- },
188
- }),
189
- },
190
- });
182
+ let progressTimeout;
183
+ const progressInterval = options.progressInterval ?? 30000;
184
+ async function progressRutine() {
185
+ try {
186
+ const total_bytes = await (0, fs_util_1.fastFolderSizeAsync)(options.target);
187
+ options.onStream?.({
188
+ message_type: "restore-status",
189
+ total_bytes,
190
+ });
191
+ }
192
+ finally {
193
+ if (typeof progressInterval === "number")
194
+ progressTimeout = setTimeout(progressRutine, progressInterval);
195
+ }
196
+ }
197
+ if (typeof progressInterval === "number")
198
+ progressRutine();
199
+ try {
200
+ return await this.exec(["restore", "--json", options.id, "--target", options.target], {
201
+ stderr: {
202
+ toExitCode: true,
203
+ },
204
+ stdout: {
205
+ ...(options.onStream && {
206
+ onData: async (data) => {
207
+ if (data.startsWith("{") && data.endsWith("}")) {
208
+ await options.onStream?.(JSON.parse(data));
209
+ }
210
+ },
211
+ }),
212
+ },
213
+ });
214
+ }
215
+ finally {
216
+ clearTimeout(progressTimeout);
217
+ }
191
218
  }
192
219
  }
193
220
  exports.ResticUtil = ResticUtil;
@@ -31,17 +31,17 @@ export declare const pkgPathParams: {
31
31
  [name in "temp" | Exclude<keyof ResolvePackagePathParamsType, "path">]: string;
32
32
  };
33
33
  export declare const pkgIncludeParams: {
34
- snapshotDate: string;
35
34
  packageName: string;
36
35
  temp: string;
37
36
  snapshotId: string;
37
+ snapshotDate: string;
38
38
  action: string;
39
39
  };
40
40
  export declare const pkgExcludeParams: {
41
- snapshotDate: string;
42
41
  packageName: string;
43
42
  temp: string;
44
43
  snapshotId: string;
44
+ snapshotDate: string;
45
45
  action: string;
46
46
  };
47
47
  export declare const pkgRestorePathParams: {
@@ -52,32 +52,32 @@ export declare const dbNameParams: {
52
52
  };
53
53
  export declare const params: {
54
54
  pkgPath: {
55
- snapshotDate: string;
56
55
  packageName: string;
57
56
  temp: string;
58
57
  snapshotId: string;
58
+ snapshotDate: string;
59
59
  action: string;
60
60
  };
61
61
  pkgRestorePath: {
62
- snapshotDate: string;
63
62
  packageName: string;
64
63
  path: string;
65
64
  temp: string;
66
65
  snapshotId: string;
66
+ snapshotDate: string;
67
67
  action: string;
68
68
  };
69
69
  pkgInclude: {
70
- snapshotDate: string;
71
70
  packageName: string;
72
71
  temp: string;
73
72
  snapshotId: string;
73
+ snapshotDate: string;
74
74
  action: string;
75
75
  };
76
76
  pkgExclude: {
77
- snapshotDate: string;
78
77
  packageName: string;
79
78
  temp: string;
80
79
  snapshotId: string;
80
+ snapshotDate: string;
81
81
  action: string;
82
82
  };
83
83
  dbName: {
package/util/fs-util.d.ts CHANGED
@@ -1,9 +1,20 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
- import { Stats } from "fs";
4
+ /// <reference types="node" />
5
+ import { Dirent, Stats } from "fs";
6
+ import { WriteStream } from "fs";
5
7
  import { Interface } from "readline";
6
8
  export declare const isWSLSystem: boolean;
9
+ export declare function isEmptyDir(path: string): Promise<boolean>;
10
+ declare type EntryObject = {
11
+ name: string;
12
+ path: string;
13
+ dirent: Dirent;
14
+ stats: Stats;
15
+ };
16
+ export declare function applyPermissions(baseDir: string, permissionsPath: string): Promise<void>;
17
+ export declare function pathIterator(stream: AsyncIterable<string | Buffer>): AsyncIterable<EntryObject>;
7
18
  export declare function isLocalDir(path: string): boolean;
8
19
  export declare function isDirEmpty(path: string): Promise<boolean>;
9
20
  export declare function mkdirIfNotExists(path: string): Promise<string>;
@@ -23,11 +34,12 @@ export declare function existsFile(path: string): Promise<boolean>;
23
34
  export declare function parentTmpDir(): string;
24
35
  export declare function sessionTmpDir(): string;
25
36
  export declare function tmpDir(prefix: string, id?: string): string;
37
+ export declare function fastFolderSizeAsync(path: string): Promise<number>;
26
38
  export declare function mkTmpDir(prefix: string, id?: string): Promise<string>;
27
39
  export declare function readPartialFile(path: string, positions: [number, number?]): Promise<string>;
28
40
  export declare function checkFile(path: string): Promise<boolean>;
29
41
  export declare function checkDir(path: string): Promise<boolean>;
30
- export declare function readDir(path: string): Promise<string[]>;
42
+ export declare function readDir(path: string, optional?: boolean): Promise<string[]>;
31
43
  export declare function forEachFile(dirPath: string, cb: (path: string, dir: boolean) => void, includeDir?: boolean): Promise<void>;
32
44
  /**
33
45
  * @experimental
@@ -36,26 +48,10 @@ export declare function fastglobToGitIgnore(patterns: string[], baseDir: string)
36
48
  export declare function writeGitIgnoreList(options: {
37
49
  paths: NodeJS.ReadableStream | string[];
38
50
  }): Promise<string>;
39
- export declare function writePathLists(options: {
40
- paths: NodeJS.ReadableStream | string[];
41
- packs?: {
42
- include: string[];
43
- exclude?: string[];
44
- multiple?: boolean;
45
- }[];
46
- }): Promise<{
47
- path: string;
48
- includedPackPaths: string[];
49
- excludedPackPaths: string[];
50
- total: {
51
- all: number;
52
- path: number;
53
- packsPaths: number[];
54
- multipleStats: Record<string, number>;
55
- };
56
- }>;
51
+ export declare function waitForClose(stream: WriteStream): Promise<WriteStream>;
57
52
  export declare function copyFileWithStreams(source: string, target: string): Promise<unknown>;
58
53
  export declare function updateFileStats(path: string, fileInfo: Stats): Promise<void>;
54
+ export declare function isNotFoundError(error: unknown): boolean;
59
55
  export declare function cpy(options: {
60
56
  input: {
61
57
  type: "glob";
@@ -76,6 +72,7 @@ export declare function cpy(options: {
76
72
  * @default 1
77
73
  */
78
74
  concurrency?: number;
75
+ skipNotFoundError?: boolean;
79
76
  onPath?: (data: {
80
77
  isDir: boolean;
81
78
  entryPath: string;
@@ -88,3 +85,4 @@ export declare function cpy(options: {
88
85
  };
89
86
  }) => Promise<boolean | void>;
90
87
  }): Promise<void>;
88
+ export {};