@datatruck/cli 0.2.0 → 0.3.2

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.
@@ -26,6 +26,7 @@ class BackupAction {
26
26
  });
27
27
  packages = (0, config_util_1.resolvePackages)(packages, {
28
28
  snapshotId: snapshot.id,
29
+ snapshotDate: snapshot.date,
29
30
  action: "backup",
30
31
  });
31
32
  for (const pkg of packages) {
@@ -206,6 +206,7 @@ class RestoreAction {
206
206
  let packages = (0, config_util_1.filterPackages)(this.config, this.options);
207
207
  packages = (0, config_util_1.resolvePackages)(packages, {
208
208
  snapshotId: this.options.snapshotId,
209
+ snapshotDate: snapshots[0].date,
209
210
  action: "restore",
210
211
  });
211
212
  const snapshotAndConfigs = this.assocConfigs(packages, snapshots);
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @datatruck/cli
2
2
 
3
+ ## 0.3.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`8957c3b`](https://github.com/swordev/datatruck/commit/8957c3b5846606db8b825fef357445210f2a3ac3) Thanks [@juanrgm](https://github.com/juanrgm)! - Fix restic progress parser
8
+
9
+ * [`2989718`](https://github.com/swordev/datatruck/commit/29897185e3d6659359d51ab2212351005137f86c) Thanks [@juanrgm](https://github.com/juanrgm)! - Show closing reason
10
+
11
+ - [`b9e0843`](https://github.com/swordev/datatruck/commit/b9e0843c7970944cfd30a7d2a543f515adfa60e4) Thanks [@juanrgm](https://github.com/juanrgm)! - Show restic progress in megabytes
12
+
13
+ ## 0.3.1
14
+
15
+ ### Patch Changes
16
+
17
+ - [`c3bb4c6`](https://github.com/swordev/datatruck/commit/c3bb4c609887c5525cf35487ea237750addb6e75) Thanks [@juanrgm](https://github.com/juanrgm)! - Fix restic stdout parser
18
+
19
+ ## 0.3.0
20
+
21
+ ### Minor Changes
22
+
23
+ - [`d63fd25`](https://github.com/swordev/datatruck/commit/d63fd25ffa8d2e539d2125dfd6a3f55020086804) Thanks [@juanrgm](https://github.com/juanrgm)! - Add `snapshotDate` param
24
+
25
+ * [`486ef4a`](https://github.com/swordev/datatruck/commit/486ef4add27ae1dbfd166b16c257522f43537ecd) Thanks [@juanrgm](https://github.com/juanrgm)! - Resolve params in `include` and `exclude`
26
+
27
+ - [`617dae2`](https://github.com/swordev/datatruck/commit/617dae2c8ed90e6e65e8109f03cfad0e64bd7c02) Thanks [@juanrgm](https://github.com/juanrgm)! - Add `script` task
28
+
29
+ ### Patch Changes
30
+
31
+ - [`d1b3ea9`](https://github.com/swordev/datatruck/commit/d1b3ea9c9540d30898c00490963523a4fbc68193) Thanks [@juanrgm](https://github.com/juanrgm)! - Avoid use gitignore if is not necessary in restic repository
32
+
3
33
  ## 0.2.0
4
34
 
5
35
  ### Minor Changes
@@ -3,6 +3,7 @@ import { MariadbTaskConfigType, mariadbTaskName } from "../Task/MariadbTask";
3
3
  import { MssqlTaskConfigType, mssqlTaskName } from "../Task/MssqlTask";
4
4
  import { MysqlDumpTaskConfigType, mysqlDumpTaskName } from "../Task/MysqlDumpTask";
5
5
  import { PostgresqlDumpTaskConfigType, postgresqlDumpTaskName } from "../Task/PostgresqlDumpTask";
6
+ import { ScriptTaskConfigType, scriptTaskName } from "../Task/ScriptTask";
6
7
  import { JSONSchema7 } from "json-schema";
7
8
  export declare const taskConfigDefinition: JSONSchema7;
8
9
  export declare type TaskConfigType = {
@@ -20,4 +21,7 @@ export declare type TaskConfigType = {
20
21
  } | {
21
22
  name: typeof postgresqlDumpTaskName;
22
23
  config: PostgresqlDumpTaskConfigType;
24
+ } | {
25
+ name: typeof scriptTaskName;
26
+ config: ScriptTaskConfigType;
23
27
  };
@@ -7,12 +7,14 @@ const MariadbTask_1 = require("../Task/MariadbTask");
7
7
  const MssqlTask_1 = require("../Task/MssqlTask");
8
8
  const MysqlDumpTask_1 = require("../Task/MysqlDumpTask");
9
9
  const PostgresqlDumpTask_1 = require("../Task/PostgresqlDumpTask");
10
+ const ScriptTask_1 = require("../Task/ScriptTask");
10
11
  const names = {
11
12
  [GitTask_1.gitTaskName]: DefinitionEnum_1.DefinitionEnum.gitTask,
12
13
  [MariadbTask_1.mariadbTaskName]: DefinitionEnum_1.DefinitionEnum.mariadbTask,
13
14
  [MssqlTask_1.mssqlTaskName]: DefinitionEnum_1.DefinitionEnum.mssqlTask,
14
15
  [MysqlDumpTask_1.mysqlDumpTaskName]: DefinitionEnum_1.DefinitionEnum.mysqlDumpTask,
15
16
  [PostgresqlDumpTask_1.postgresqlDumpTaskName]: DefinitionEnum_1.DefinitionEnum.postgresqlDumpTask,
17
+ [ScriptTask_1.scriptTaskName]: DefinitionEnum_1.DefinitionEnum.scriptTask,
16
18
  };
17
19
  exports.taskConfigDefinition = {
18
20
  type: "object",
@@ -7,6 +7,7 @@ const MariadbTask_1 = require("../Task/MariadbTask");
7
7
  const MssqlTask_1 = require("../Task/MssqlTask");
8
8
  const MysqlDumpTask_1 = require("../Task/MysqlDumpTask");
9
9
  const PostgresqlDumpTask_1 = require("../Task/PostgresqlDumpTask");
10
+ const ScriptTask_1 = require("../Task/ScriptTask");
10
11
  function TaskFactory(task) {
11
12
  if (task.name === GitTask_1.gitTaskName) {
12
13
  return new GitTask_1.GitTask(task.config ?? {});
@@ -23,6 +24,9 @@ function TaskFactory(task) {
23
24
  else if (task.name === MssqlTask_1.mssqlTaskName) {
24
25
  return new MssqlTask_1.MssqlTask(task.config ?? {});
25
26
  }
27
+ else if (task.name === ScriptTask_1.scriptTaskName) {
28
+ return new ScriptTask_1.ScriptTask(task.config ?? {});
29
+ }
26
30
  else {
27
31
  throw new AppError_1.AppError(`Invalid task name: ${task["name"]}`);
28
32
  }
@@ -11,6 +11,7 @@ export declare enum DefinitionEnum {
11
11
  gitRepository = "git-repository",
12
12
  gitPackageRepository = "git-package-repository",
13
13
  gitTask = "git-task",
14
+ scriptTask = "script-task",
14
15
  mariadbTask = "mariadb-task",
15
16
  mssqlTask = "mssql-task",
16
17
  mysqlDumpTask = "mysql-dump-task",
@@ -20,6 +21,6 @@ export declare enum DefinitionEnum {
20
21
  prunePolicy = "prune-policy",
21
22
  pathsObject = "paths-object"
22
23
  }
23
- export declare function makeRef(type: DefinitionEnum): {
24
+ export declare function makeRef(type: DefinitionEnum, subType?: string): {
24
25
  $ref: string;
25
26
  };
@@ -15,6 +15,7 @@ var DefinitionEnum;
15
15
  DefinitionEnum["gitRepository"] = "git-repository";
16
16
  DefinitionEnum["gitPackageRepository"] = "git-package-repository";
17
17
  DefinitionEnum["gitTask"] = "git-task";
18
+ DefinitionEnum["scriptTask"] = "script-task";
18
19
  DefinitionEnum["mariadbTask"] = "mariadb-task";
19
20
  DefinitionEnum["mssqlTask"] = "mssql-task";
20
21
  DefinitionEnum["mysqlDumpTask"] = "mysql-dump-task";
@@ -24,9 +25,9 @@ var DefinitionEnum;
24
25
  DefinitionEnum["prunePolicy"] = "prune-policy";
25
26
  DefinitionEnum["pathsObject"] = "paths-object";
26
27
  })(DefinitionEnum = exports.DefinitionEnum || (exports.DefinitionEnum = {}));
27
- function makeRef(type) {
28
+ function makeRef(type, subType) {
28
29
  return {
29
- $ref: `#/definitions/${type}`,
30
+ $ref: `#/definitions/${type}` + (subType ? `_${subType}` : ""),
30
31
  };
31
32
  }
32
33
  exports.makeRef = makeRef;
@@ -15,6 +15,7 @@ const MariadbTask_1 = require("../Task/MariadbTask");
15
15
  const MssqlTask_1 = require("../Task/MssqlTask");
16
16
  const MysqlDumpTask_1 = require("../Task/MysqlDumpTask");
17
17
  const PostgresqlDumpTask_1 = require("../Task/PostgresqlDumpTask");
18
+ const ScriptTask_1 = require("../Task/ScriptTask");
18
19
  const SqlDumpTaskAbstract_1 = require("../Task/SqlDumpTaskAbstract");
19
20
  const DefinitionEnum_1 = require("./DefinitionEnum");
20
21
  exports.definitions = {
@@ -35,6 +36,7 @@ exports.definitions = {
35
36
  [DefinitionEnum_1.DefinitionEnum.resticRepository]: ResticRepository_1.resticRepositoryDefinition,
36
37
  [DefinitionEnum_1.DefinitionEnum.resticPackageRepository]: ResticRepository_1.resticPackageRepositoryDefinition,
37
38
  [DefinitionEnum_1.DefinitionEnum.gitTask]: GitTask_1.gitTaskDefinition,
39
+ [DefinitionEnum_1.DefinitionEnum.scriptTask]: ScriptTask_1.scriptTaskDefinition,
38
40
  [DefinitionEnum_1.DefinitionEnum.sqlDumpTask]: SqlDumpTaskAbstract_1.sqlDumpTaskDefinition,
39
41
  [DefinitionEnum_1.DefinitionEnum.mariadbTask]: MariadbTask_1.mariadbTaskDefinition,
40
42
  [DefinitionEnum_1.DefinitionEnum.mssqlTask]: MssqlTask_1.mssqlTaskDefinition,
@@ -44,6 +46,18 @@ exports.definitions = {
44
46
  [DefinitionEnum_1.DefinitionEnum.prunePolicy]: PrunePolicyConfig_1.prunePolicyConfigDefinition,
45
47
  [DefinitionEnum_1.DefinitionEnum.pathsObject]: PackageConfig_1.pathsObjectDefinition,
46
48
  };
49
+ for (const key in exports.definitions) {
50
+ const schemaKey = key;
51
+ const schema = exports.definitions[schemaKey];
52
+ for (const defName in schema.definitions || {}) {
53
+ exports.definitions[`${schemaKey}_${defName}`] =
54
+ schema.definitions[defName];
55
+ }
56
+ exports.definitions[schemaKey] = {
57
+ ...schema,
58
+ };
59
+ delete exports.definitions[schemaKey].definitions;
60
+ }
47
61
  exports.schema = {
48
62
  definitions: exports.definitions,
49
63
  ...(0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.config),
@@ -205,6 +205,8 @@ class LocalRepository extends RepositoryAbstract_1.RepositoryAbstract {
205
205
  paths: stream,
206
206
  packs: compress?.packs,
207
207
  });
208
+ if (data.options.verbose)
209
+ (0, cli_util_1.logExec)(`Path lists: ${pathLists.path}`);
208
210
  let currentFiles = 0;
209
211
  if (compress?.packs) {
210
212
  let packIndex = 0;
@@ -155,28 +155,31 @@ class ResticRepository extends RepositoryAbstract_1.RepositoryAbstract {
155
155
  const pkg = data.package;
156
156
  const sourcePath = data.targetPath ?? data.package.path;
157
157
  (0, assert_1.ok)(sourcePath);
158
- const include = await (0, paths_util_1.parsePaths)(pkg.include ?? ["**"], {
159
- cwd: sourcePath,
160
- verbose: data.options.verbose,
161
- });
162
- const exclude = pkg.exclude
163
- ? await (0, paths_util_1.parsePaths)(pkg.exclude, {
158
+ let gitignorePath;
159
+ if (pkg.include || pkg.exclude) {
160
+ const include = await (0, paths_util_1.parsePaths)(pkg.include ?? ["**"], {
164
161
  cwd: sourcePath,
165
162
  verbose: data.options.verbose,
166
- })
167
- : undefined;
168
- const stream = fast_glob_1.default.stream(include, {
169
- cwd: sourcePath,
170
- ignore: exclude,
171
- dot: true,
172
- onlyFiles: true,
173
- markDirectories: true,
174
- });
175
- if (data.options.verbose)
176
- (0, cli_util_1.logExec)(`Writing paths lists`);
177
- const gitignorePath = await (0, fs_util_1.writeGitIgnoreList)({
178
- paths: stream,
179
- });
163
+ });
164
+ const exclude = pkg.exclude
165
+ ? await (0, paths_util_1.parsePaths)(pkg.exclude, {
166
+ cwd: sourcePath,
167
+ verbose: data.options.verbose,
168
+ })
169
+ : undefined;
170
+ const stream = fast_glob_1.default.stream(include, {
171
+ cwd: sourcePath,
172
+ ignore: exclude,
173
+ dot: true,
174
+ onlyFiles: true,
175
+ markDirectories: true,
176
+ });
177
+ if (data.options.verbose)
178
+ (0, cli_util_1.logExec)(`Writing paths lists`);
179
+ gitignorePath = await (0, fs_util_1.writeGitIgnoreList)({
180
+ paths: stream,
181
+ });
182
+ }
180
183
  if (data.options.tags?.some((tag) => tag.startsWith(ResticRepository.refPrefix)))
181
184
  throw new AppError_1.AppError(`Tag prefix is not allowed`);
182
185
  const packageTag = ResticRepository.buildSnapshotTag(RepositoryAbstract_1.SnapshotTagEnum.PACKAGE, data.package.name);
@@ -190,7 +193,7 @@ class ResticRepository extends RepositoryAbstract_1.RepositoryAbstract {
190
193
  cwd: sourcePath,
191
194
  paths: ["."],
192
195
  allowEmptySnapshot: true,
193
- excludeFile: [gitignorePath],
196
+ excludeFile: gitignorePath ? [gitignorePath] : undefined,
194
197
  parent: lastSnapshot?.id,
195
198
  // https://github.com/restic/restic/pull/3200
196
199
  ...((await restic.checkBackupSetPathSupport()) && {
@@ -207,8 +210,10 @@ class ResticRepository extends RepositoryAbstract_1.RepositoryAbstract {
207
210
  onStream: async (streamData) => {
208
211
  if (streamData.message_type === "status") {
209
212
  await data.onProgress({
210
- total: streamData.total_bytes,
211
- current: streamData.bytes_done ?? 0,
213
+ total: Number((streamData.total_bytes / 1024 / 1024).toFixed(2)),
214
+ current: streamData.bytes_done
215
+ ? Number((streamData.bytes_done / 1024 / 1024).toFixed(2))
216
+ : 0,
212
217
  percent: Number((streamData.percent_done * 100).toFixed(2)),
213
218
  step: streamData.current_files?.join(", ") ?? "-",
214
219
  });
package/Task/MssqlTask.js CHANGED
@@ -96,6 +96,7 @@ class MssqlTask extends TaskAbstract_1.TaskAbstract {
96
96
  database: databaseName,
97
97
  packageName: data.package.name,
98
98
  snapshotId: data.options.snapshotId,
99
+ snapshotDate: data.snapshot.date,
99
100
  });
100
101
  const databasePath = (0, path_1.join)(restorePath, file);
101
102
  const exists = await this.fetchDatabaseNames(databaseName);
@@ -0,0 +1,42 @@
1
+ import { BackupDataType, RestoreDataType, TaskAbstract } from "./TaskAbstract";
2
+ import { JSONSchema7 } from "json-schema";
3
+ export declare type ProcessStepConfig = {
4
+ command: string;
5
+ env?: Record<string, string>;
6
+ args?: string[];
7
+ };
8
+ export declare type NodeStepConfig = {
9
+ env?: Record<string, string>;
10
+ code: string | string[];
11
+ };
12
+ export declare type Step = {
13
+ type: "process";
14
+ config: ProcessStepConfig;
15
+ } | {
16
+ type: "node";
17
+ config: NodeStepConfig;
18
+ };
19
+ export declare type ScriptTaskConfigType = {
20
+ env?: Record<string, string | undefined>;
21
+ backupSteps: Step[];
22
+ restoreSteps: Step[];
23
+ };
24
+ export declare const scriptTaskName = "script";
25
+ export declare const scriptTaskDefinition: JSONSchema7;
26
+ export declare class ScriptTask extends TaskAbstract<ScriptTaskConfigType> {
27
+ protected verbose?: boolean;
28
+ onBeforeBackup(): Promise<{
29
+ targetPath: string;
30
+ }>;
31
+ protected getVars(data: BackupDataType | RestoreDataType): Record<string, string | undefined>;
32
+ static processSteps(input: Step[] | Step, options: {
33
+ env?: Record<string, string | undefined>;
34
+ vars: Record<string, string | undefined>;
35
+ verbose?: boolean;
36
+ }): Promise<void>;
37
+ onBackup(data: BackupDataType): Promise<void>;
38
+ onBeforeRestore(): Promise<{
39
+ targetPath: string;
40
+ }>;
41
+ onRestore(data: RestoreDataType): Promise<void>;
42
+ }
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ScriptTask = exports.scriptTaskDefinition = exports.scriptTaskName = void 0;
4
+ const DefinitionEnum_1 = require("../JsonSchema/DefinitionEnum");
5
+ const fs_util_1 = require("../util/fs-util");
6
+ const process_util_1 = require("../util/process-util");
7
+ const string_util_1 = require("../util/string-util");
8
+ const TaskAbstract_1 = require("./TaskAbstract");
9
+ const assert_1 = require("assert");
10
+ const promises_1 = require("fs/promises");
11
+ const path_1 = require("path");
12
+ var ScriptTaskDefinitionEnum;
13
+ (function (ScriptTaskDefinitionEnum) {
14
+ ScriptTaskDefinitionEnum["step"] = "step";
15
+ ScriptTaskDefinitionEnum["processStepConfig"] = "processStepConfig";
16
+ ScriptTaskDefinitionEnum["nodeStepConfig"] = "nodeStepConfig";
17
+ })(ScriptTaskDefinitionEnum || (ScriptTaskDefinitionEnum = {}));
18
+ const stepTypes = {
19
+ process: ScriptTaskDefinitionEnum.processStepConfig,
20
+ node: ScriptTaskDefinitionEnum.nodeStepConfig,
21
+ };
22
+ exports.scriptTaskName = "script";
23
+ exports.scriptTaskDefinition = {
24
+ definitions: {
25
+ step: {
26
+ type: "object",
27
+ required: ["type"],
28
+ properties: {
29
+ type: { enum: Object.keys(stepTypes) },
30
+ config: {},
31
+ },
32
+ anyOf: Object.keys(stepTypes).map((name) => ({
33
+ if: {
34
+ type: "object",
35
+ properties: {
36
+ type: { const: name },
37
+ },
38
+ },
39
+ then: {
40
+ type: "object",
41
+ properties: {
42
+ config: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.scriptTask, stepTypes[name]),
43
+ },
44
+ },
45
+ else: false,
46
+ })),
47
+ },
48
+ processStepConfig: {
49
+ type: "object",
50
+ required: ["command"],
51
+ properties: {
52
+ command: { type: "string" },
53
+ env: {
54
+ type: "object",
55
+ patternProperties: { ".+": { type: "string" } },
56
+ },
57
+ args: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.stringListUtil),
58
+ },
59
+ },
60
+ nodeStepConfig: {
61
+ type: "object",
62
+ required: ["code"],
63
+ properties: {
64
+ code: {
65
+ anyOf: [{ type: "string" }, (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.stringListUtil)],
66
+ },
67
+ env: {
68
+ type: "object",
69
+ patternProperties: { ".+": { type: "string" } },
70
+ },
71
+ },
72
+ },
73
+ },
74
+ type: "object",
75
+ additionalProperties: false,
76
+ required: ["backupSteps", "restoreSteps"],
77
+ properties: {
78
+ env: {
79
+ type: "object",
80
+ patternProperties: {
81
+ ".+": { type: "string" },
82
+ },
83
+ },
84
+ backupSteps: {
85
+ type: "array",
86
+ items: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.scriptTask, ScriptTaskDefinitionEnum.step),
87
+ },
88
+ restoreSteps: {
89
+ type: "array",
90
+ items: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.scriptTask, ScriptTaskDefinitionEnum.step),
91
+ },
92
+ },
93
+ };
94
+ class ScriptTask extends TaskAbstract_1.TaskAbstract {
95
+ async onBeforeBackup() {
96
+ return {
97
+ targetPath: await (0, fs_util_1.mkTmpDir)(ScriptTask.name),
98
+ };
99
+ }
100
+ getVars(data) {
101
+ return {
102
+ DTT_SNAPSHOT_ID: data.snapshot.id,
103
+ DTT_SNAPSHOT_DATE: data.snapshot.date,
104
+ DTT_PACKAGE_NAME: data.package.name,
105
+ DTT_PACKAGE_PATH: data.package.path,
106
+ DTT_TARGET_PATH: data.targetPath,
107
+ };
108
+ }
109
+ static async processSteps(input, options) {
110
+ const steps = Array.isArray(input) ? input : [input];
111
+ for (const step of steps) {
112
+ if (step.type === "process") {
113
+ await (0, process_util_1.exec)(step.config.command, (step.config.args || []).map((v) => (0, string_util_1.render)(v, options.vars)), {
114
+ env: {
115
+ ...process.env,
116
+ ...options.vars,
117
+ ...options.env,
118
+ ...step.config.env,
119
+ },
120
+ }, {
121
+ log: options.verbose,
122
+ });
123
+ }
124
+ else if (step.type === "node") {
125
+ const tempDir = await (0, fs_util_1.mkTmpDir)("script-task-node-step");
126
+ const scriptPath = (0, path_1.join)(tempDir, "script.js");
127
+ await (0, promises_1.writeFile)(scriptPath, Array.isArray(step.config.code)
128
+ ? step.config.code.join("\n")
129
+ : step.config.code);
130
+ await (0, process_util_1.exec)("node", [scriptPath], {
131
+ env: {
132
+ ...process.env,
133
+ ...options.vars,
134
+ ...options.env,
135
+ ...step.config.env,
136
+ },
137
+ }, {
138
+ log: options.verbose,
139
+ });
140
+ }
141
+ else {
142
+ throw new Error(`Invalid step type: ${step.type}`);
143
+ }
144
+ }
145
+ }
146
+ async onBackup(data) {
147
+ this.verbose = data.options.verbose;
148
+ const config = this.config;
149
+ const path = data.package.path;
150
+ const targetPath = data.targetPath;
151
+ (0, assert_1.ok)(typeof path === "string");
152
+ (0, assert_1.ok)(typeof targetPath === "string");
153
+ await ScriptTask.processSteps(config.backupSteps, {
154
+ env: config.env,
155
+ vars: this.getVars(data),
156
+ verbose: this.verbose,
157
+ });
158
+ }
159
+ async onBeforeRestore() {
160
+ return {
161
+ targetPath: await (0, fs_util_1.mkTmpDir)(ScriptTask.name),
162
+ };
163
+ }
164
+ async onRestore(data) {
165
+ this.verbose = data.options.verbose;
166
+ const config = this.config;
167
+ const restorePath = data.package.restorePath;
168
+ const targetPath = data.targetPath;
169
+ (0, assert_1.ok)(typeof restorePath === "string");
170
+ (0, assert_1.ok)(typeof targetPath === "string");
171
+ await (0, fs_util_1.mkdirIfNotExists)(restorePath);
172
+ await (0, fs_util_1.ensureEmptyDir)(restorePath);
173
+ await ScriptTask.processSteps(config.restoreSteps, {
174
+ env: config.env,
175
+ vars: this.getVars(data),
176
+ verbose: this.verbose,
177
+ });
178
+ }
179
+ }
180
+ exports.ScriptTask = ScriptTask;
@@ -111,6 +111,7 @@ class SqlDumpTaskAbstract extends TaskAbstract_1.TaskAbstract {
111
111
  name: (0, config_util_1.resolveDatabaseName)(this.config.database, {
112
112
  packageName: data.package.name,
113
113
  snapshotId: data.options.snapshotId,
114
+ snapshotDate: data.snapshot.date,
114
115
  action: "restore",
115
116
  database: undefined,
116
117
  }),
@@ -119,6 +120,7 @@ class SqlDumpTaskAbstract extends TaskAbstract_1.TaskAbstract {
119
120
  database.name = (0, config_util_1.resolveDatabaseName)(this.config.targetDatabase.name, {
120
121
  packageName: data.package.name,
121
122
  snapshotId: data.options.snapshotId,
123
+ snapshotDate: data.snapshot.date,
122
124
  action: "restore",
123
125
  database: database.name,
124
126
  });
package/cli.js CHANGED
@@ -84,10 +84,12 @@ exports.buildArgs = buildArgs;
84
84
  function parseArgs(args) {
85
85
  program.parse(args);
86
86
  const verbose = getGlobalOptions().verbose;
87
- (0, process_util_1.onExit)((eventName) => {
87
+ (0, process_util_1.onExit)((eventName, error) => {
88
88
  if (eventName !== "exit") {
89
89
  process.stdout.write(cli_util_1.showCursorCommand);
90
- console.log("\nClosing...");
90
+ console.log(`\nClosing... (reason: ${eventName})`);
91
+ if (error instanceof Error)
92
+ console.error((0, chalk_1.red)(error.stack));
91
93
  }
92
94
  if (!verbose)
93
95
  try {
@@ -261,7 +261,8 @@
261
261
  "mariadb",
262
262
  "mssql",
263
263
  "mysql-dump",
264
- "postgresql-dump"
264
+ "postgresql-dump",
265
+ "script"
265
266
  ]
266
267
  },
267
268
  "config": {}
@@ -361,6 +362,25 @@
361
362
  }
362
363
  },
363
364
  "else": false
365
+ },
366
+ {
367
+ "if": {
368
+ "type": "object",
369
+ "properties": {
370
+ "name": {
371
+ "const": "script"
372
+ }
373
+ }
374
+ },
375
+ "then": {
376
+ "type": "object",
377
+ "properties": {
378
+ "config": {
379
+ "$ref": "#/definitions/script-task"
380
+ }
381
+ }
382
+ },
383
+ "else": false
364
384
  }
365
385
  ]
366
386
  },
@@ -551,6 +571,36 @@
551
571
  }
552
572
  }
553
573
  },
574
+ "script-task": {
575
+ "type": "object",
576
+ "additionalProperties": false,
577
+ "required": [
578
+ "backupSteps",
579
+ "restoreSteps"
580
+ ],
581
+ "properties": {
582
+ "env": {
583
+ "type": "object",
584
+ "patternProperties": {
585
+ ".+": {
586
+ "type": "string"
587
+ }
588
+ }
589
+ },
590
+ "backupSteps": {
591
+ "type": "array",
592
+ "items": {
593
+ "$ref": "#/definitions/script-task_step"
594
+ }
595
+ },
596
+ "restoreSteps": {
597
+ "type": "array",
598
+ "items": {
599
+ "$ref": "#/definitions/script-task_step"
600
+ }
601
+ }
602
+ }
603
+ },
554
604
  "sqldump-task": {
555
605
  "type": "object",
556
606
  "required": [
@@ -810,6 +860,109 @@
810
860
  "else": false
811
861
  }
812
862
  ]
863
+ },
864
+ "script-task_step": {
865
+ "type": "object",
866
+ "required": [
867
+ "type"
868
+ ],
869
+ "properties": {
870
+ "type": {
871
+ "enum": [
872
+ "process",
873
+ "node"
874
+ ]
875
+ },
876
+ "config": {}
877
+ },
878
+ "anyOf": [
879
+ {
880
+ "if": {
881
+ "type": "object",
882
+ "properties": {
883
+ "type": {
884
+ "const": "process"
885
+ }
886
+ }
887
+ },
888
+ "then": {
889
+ "type": "object",
890
+ "properties": {
891
+ "config": {
892
+ "$ref": "#/definitions/script-task_processStepConfig"
893
+ }
894
+ }
895
+ },
896
+ "else": false
897
+ },
898
+ {
899
+ "if": {
900
+ "type": "object",
901
+ "properties": {
902
+ "type": {
903
+ "const": "node"
904
+ }
905
+ }
906
+ },
907
+ "then": {
908
+ "type": "object",
909
+ "properties": {
910
+ "config": {
911
+ "$ref": "#/definitions/script-task_nodeStepConfig"
912
+ }
913
+ }
914
+ },
915
+ "else": false
916
+ }
917
+ ]
918
+ },
919
+ "script-task_processStepConfig": {
920
+ "type": "object",
921
+ "required": [
922
+ "command"
923
+ ],
924
+ "properties": {
925
+ "command": {
926
+ "type": "string"
927
+ },
928
+ "env": {
929
+ "type": "object",
930
+ "patternProperties": {
931
+ ".+": {
932
+ "type": "string"
933
+ }
934
+ }
935
+ },
936
+ "args": {
937
+ "$ref": "#/definitions/stringlist-util"
938
+ }
939
+ }
940
+ },
941
+ "script-task_nodeStepConfig": {
942
+ "type": "object",
943
+ "required": [
944
+ "code"
945
+ ],
946
+ "properties": {
947
+ "code": {
948
+ "anyOf": [
949
+ {
950
+ "type": "string"
951
+ },
952
+ {
953
+ "$ref": "#/definitions/stringlist-util"
954
+ }
955
+ ]
956
+ },
957
+ "env": {
958
+ "type": "object",
959
+ "patternProperties": {
960
+ ".+": {
961
+ "type": "string"
962
+ }
963
+ }
964
+ }
965
+ }
813
966
  }
814
967
  },
815
968
  "$ref": "#/definitions/config"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datatruck/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.2",
4
4
  "dependencies": {
5
5
  "ajv": "^8.11.0",
6
6
  "chalk": "^4.1.2",
@@ -117,8 +117,17 @@ class ResticUtil {
117
117
  stdout: {
118
118
  ...(options.onStream && {
119
119
  onData: async (data) => {
120
- if (data.startsWith("{") && data.endsWith("}")) {
121
- await options.onStream?.(JSON.parse(data));
120
+ for (const rawLine of data.split("\n")) {
121
+ const line = rawLine.trim();
122
+ if (line.startsWith("{") && line.endsWith("}")) {
123
+ let parsedLine;
124
+ try {
125
+ parsedLine = JSON.parse(line);
126
+ }
127
+ catch (error) { }
128
+ if (parsedLine)
129
+ await options.onStream?.(parsedLine);
130
+ }
122
131
  }
123
132
  },
124
133
  }),
@@ -18,6 +18,7 @@ declare type ResolveDatabaseNameParamsType = ResolvePackageParamsType & {
18
18
  export declare function resolveDatabaseName(value: string, params: ResolveDatabaseNameParamsType): string;
19
19
  declare type ResolvePackageParamsType = {
20
20
  snapshotId: string;
21
+ snapshotDate: string;
21
22
  action: "backup" | "restore";
22
23
  };
23
24
  export declare function resolvePackage(pkg: PackageConfigType, params: ResolvePackageParamsType): PackageConfigType;
@@ -25,6 +26,20 @@ export declare function resolvePackages(packages: PackageConfigType[], params: R
25
26
  export declare const pkgPathParams: {
26
27
  [name in "temp" | Exclude<keyof ResolvePackagePathParamsType, "path">]: string;
27
28
  };
29
+ export declare const pkgIncludeParams: {
30
+ snapshotDate: string;
31
+ packageName: string;
32
+ temp: string;
33
+ snapshotId: string;
34
+ action: string;
35
+ };
36
+ export declare const pkgExcludeParams: {
37
+ snapshotDate: string;
38
+ packageName: string;
39
+ temp: string;
40
+ snapshotId: string;
41
+ action: string;
42
+ };
28
43
  export declare const pkgRestorePathParams: {
29
44
  [name in "temp" | keyof ResolvePackagePathParamsType]: string;
30
45
  };
@@ -33,20 +48,37 @@ export declare const dbNameParams: {
33
48
  };
34
49
  export declare const params: {
35
50
  pkgPath: {
51
+ snapshotDate: string;
36
52
  packageName: string;
37
53
  temp: string;
38
54
  snapshotId: string;
39
55
  action: string;
40
56
  };
41
57
  pkgRestorePath: {
58
+ snapshotDate: string;
42
59
  packageName: string;
43
60
  path: string;
44
61
  temp: string;
45
62
  snapshotId: string;
46
63
  action: string;
47
64
  };
65
+ pkgInclude: {
66
+ snapshotDate: string;
67
+ packageName: string;
68
+ temp: string;
69
+ snapshotId: string;
70
+ action: string;
71
+ };
72
+ pkgExclude: {
73
+ snapshotDate: string;
74
+ packageName: string;
75
+ temp: string;
76
+ snapshotId: string;
77
+ action: string;
78
+ };
48
79
  dbName: {
49
80
  snapshotId: string;
81
+ snapshotDate: string;
50
82
  action: string;
51
83
  packageName: string;
52
84
  database: string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.params = exports.dbNameParams = exports.pkgRestorePathParams = exports.pkgPathParams = exports.resolvePackages = exports.resolvePackage = exports.resolveDatabaseName = exports.resolvePackagePath = exports.filterPackages = exports.findRepositoryOrFail = void 0;
3
+ exports.params = exports.dbNameParams = exports.pkgRestorePathParams = exports.pkgExcludeParams = exports.pkgIncludeParams = exports.pkgPathParams = exports.resolvePackages = exports.resolvePackage = exports.resolveDatabaseName = exports.resolvePackagePath = exports.filterPackages = exports.findRepositoryOrFail = void 0;
4
4
  const AppError_1 = require("../../Error/AppError");
5
5
  const fs_util_1 = require("../fs-util");
6
6
  const string_util_1 = require("../string-util");
@@ -53,6 +53,10 @@ function resolvePackage(pkg, params) {
53
53
  packageName: pkg.name,
54
54
  path: undefined,
55
55
  };
56
+ if (pkg.include)
57
+ pkg.include = pkg.include.map((v) => typeof v === "string" ? (0, string_util_1.render)(v, pkgParams) : v);
58
+ if (pkg.exclude)
59
+ pkg.exclude = pkg.exclude.map((v) => typeof v === "string" ? (0, string_util_1.render)(v, pkgParams) : v);
56
60
  if (pkg.path)
57
61
  pkg.path = resolvePackagePath(pkg.path, pkgParams);
58
62
  if (pkg.restorePath)
@@ -71,23 +75,30 @@ exports.pkgPathParams = {
71
75
  action: "{action}",
72
76
  packageName: "{packageName}",
73
77
  snapshotId: "{snapshotId}",
78
+ snapshotDate: "{snapshotDate}",
74
79
  temp: "{temp}",
75
80
  };
81
+ exports.pkgIncludeParams = exports.pkgPathParams;
82
+ exports.pkgExcludeParams = exports.pkgPathParams;
76
83
  exports.pkgRestorePathParams = {
77
84
  action: "{action}",
78
85
  packageName: "{packageName}",
79
86
  path: "{path}",
80
87
  snapshotId: "{snapshotId}",
88
+ snapshotDate: "{snapshotDate}",
81
89
  temp: "{temp}",
82
90
  };
83
91
  exports.dbNameParams = {
84
92
  action: "{action}",
85
93
  packageName: "{packageName}",
86
94
  snapshotId: "{snapshotId}",
95
+ snapshotDate: "{snapshotDate}",
87
96
  database: "{database}",
88
97
  };
89
98
  exports.params = {
90
99
  pkgPath: exports.pkgPathParams,
91
100
  pkgRestorePath: exports.pkgRestorePathParams,
101
+ pkgInclude: exports.pkgIncludeParams,
102
+ pkgExclude: exports.pkgExcludeParams,
92
103
  dbName: exports.dbNameParams,
93
104
  };
@@ -46,5 +46,5 @@ export declare type ExecResultType = {
46
46
  };
47
47
  export declare function exec(command: string, argv?: string[], options?: SpawnOptions | null, settings?: ExecSettingsInterface): Promise<ExecResultType>;
48
48
  declare type EventNameType = "exit" | "SIGINT" | "SIGUSR1" | "SIGUSR2" | "SIGTERM" | "uncaughtException";
49
- export declare function onExit(cb: (eventName: EventNameType) => void): void;
49
+ export declare function onExit(cb: (eventName: EventNameType, ...args: any[]) => void): void;
50
50
  export {};
@@ -175,7 +175,7 @@ const eventNames = [
175
175
  ];
176
176
  function onExit(cb) {
177
177
  for (const eventName of eventNames) {
178
- process.on(eventName, cb.bind(null, eventName));
178
+ process.on(eventName, (...args) => cb(eventName, ...args));
179
179
  }
180
180
  }
181
181
  exports.onExit = onExit;
@@ -21,7 +21,13 @@ function snakeCase(value, char = "_") {
21
21
  }
22
22
  exports.snakeCase = snakeCase;
23
23
  function render(subject, vars) {
24
- return subject.replace(/{(\w+)}/g, function (match, name) {
24
+ return subject.replace(/{([\w/]*)}/g, function (match, name) {
25
+ if (!name.length) {
26
+ return "{";
27
+ }
28
+ else if (name === "/") {
29
+ return "}";
30
+ }
25
31
  const value = vars[name];
26
32
  if (typeof value === "undefined")
27
33
  throw new AppError_1.AppError(`Variable is not defined: '${subject}' (${name})`);