@datatruck/cli 0.17.2 → 0.18.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.
- package/Repository/DatatruckRepository.d.ts +0 -1
- package/Repository/DatatruckRepository.js +3 -69
- package/Task/MariadbTask.d.ts +11 -0
- package/Task/MariadbTask.js +233 -51
- package/config.schema.json +44 -0
- package/package.json +3 -3
- package/utils/datatruck/config.d.ts +1 -1
- package/utils/fs.d.ts +23 -2
- package/utils/fs.js +66 -7
- package/utils/process.d.ts +55 -3
- package/utils/process.js +159 -19
|
@@ -49,7 +49,6 @@ export declare class DatatruckRepository extends RepositoryAbstract<DatatruckRep
|
|
|
49
49
|
static stringifyMetaData(data: MetaDataType): string;
|
|
50
50
|
onGetSource(): string;
|
|
51
51
|
onInit(data: InitDataType): Promise<void>;
|
|
52
|
-
private createFileScanner;
|
|
53
52
|
onPrune(data: PruneDataType): Promise<void>;
|
|
54
53
|
onSnapshots(data: SnapshotsDataType): Promise<SnapshotResultType[]>;
|
|
55
54
|
private normalizeCompressConfig;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.DatatruckRepository = exports.datatruckPackageRepositoryDefinition = exports.datatruckRepositoryDefinition = exports.datatruckRepositoryName = void 0;
|
|
7
4
|
const AppError_1 = require("../Error/AppError");
|
|
@@ -9,17 +6,14 @@ const DefinitionEnum_1 = require("../JsonSchema/DefinitionEnum");
|
|
|
9
6
|
const cli_1 = require("../utils/cli");
|
|
10
7
|
const paths_1 = require("../utils/datatruck/paths");
|
|
11
8
|
const fs_1 = require("../utils/fs");
|
|
12
|
-
const math_1 = require("../utils/math");
|
|
13
9
|
const string_1 = require("../utils/string");
|
|
14
10
|
const zip_1 = require("../utils/zip");
|
|
15
11
|
const RepositoryAbstract_1 = require("./RepositoryAbstract");
|
|
16
12
|
const assert_1 = require("assert");
|
|
17
|
-
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
18
13
|
const fs_2 = require("fs");
|
|
19
14
|
const promises_1 = require("fs/promises");
|
|
20
15
|
const micromatch_1 = require("micromatch");
|
|
21
16
|
const path_1 = require("path");
|
|
22
|
-
const perf_hooks_1 = require("perf_hooks");
|
|
23
17
|
const readline_1 = require("readline");
|
|
24
18
|
exports.datatruckRepositoryName = "datatruck";
|
|
25
19
|
exports.datatruckRepositoryDefinition = {
|
|
@@ -110,66 +104,6 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
|
|
|
110
104
|
async onInit(data) {
|
|
111
105
|
await (0, fs_1.mkdirIfNotExists)(this.config.outPath);
|
|
112
106
|
}
|
|
113
|
-
async createFileScanner(options) {
|
|
114
|
-
const object = {
|
|
115
|
-
total: 0,
|
|
116
|
-
current: 0,
|
|
117
|
-
progress: async (description, data) => {
|
|
118
|
-
await options.onProgress({
|
|
119
|
-
relative: {
|
|
120
|
-
description,
|
|
121
|
-
payload: data.path,
|
|
122
|
-
percent: data.percent,
|
|
123
|
-
},
|
|
124
|
-
absolute: {
|
|
125
|
-
total: object.total,
|
|
126
|
-
current: object.current + data.current,
|
|
127
|
-
percent: (0, math_1.progressPercent)(object.total, object.current + data.current),
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
if (data.type === "end") {
|
|
131
|
-
object.current += data.current;
|
|
132
|
-
}
|
|
133
|
-
},
|
|
134
|
-
updateProgress: async (end) => {
|
|
135
|
-
const currentTime = perf_hooks_1.performance.now();
|
|
136
|
-
const diff = currentTime - lastTime;
|
|
137
|
-
if (end || diff > 1000) {
|
|
138
|
-
await options.onProgress({
|
|
139
|
-
relative: {
|
|
140
|
-
description: end ? "Scanned files" : "Scanning files",
|
|
141
|
-
payload: object.total.toString(),
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
lastTime = currentTime;
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
start: async (cb) => {
|
|
148
|
-
for await (const entry of (0, fs_1.pathIterator)(stream)) {
|
|
149
|
-
if (!options.disableCounting)
|
|
150
|
-
object.total++;
|
|
151
|
-
await object.updateProgress();
|
|
152
|
-
if (cb)
|
|
153
|
-
await cb(entry);
|
|
154
|
-
}
|
|
155
|
-
if (!options.disableEndProgress)
|
|
156
|
-
await object.updateProgress(true);
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
await options.onProgress({
|
|
160
|
-
relative: {
|
|
161
|
-
description: "Scanning files",
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
const stream = fast_glob_1.default.stream(options.glob.include, {
|
|
165
|
-
dot: true,
|
|
166
|
-
markDirectories: true,
|
|
167
|
-
stats: true,
|
|
168
|
-
...options.glob,
|
|
169
|
-
});
|
|
170
|
-
let lastTime = perf_hooks_1.performance.now();
|
|
171
|
-
return object;
|
|
172
|
-
}
|
|
173
107
|
async onPrune(data) {
|
|
174
108
|
const snapshotName = DatatruckRepository.buildSnapshotName({
|
|
175
109
|
snapshotId: data.snapshot.id,
|
|
@@ -273,7 +207,7 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
|
|
|
273
207
|
const streams = [unpackedStream, singlePackStream, ...packStreams];
|
|
274
208
|
if (data.options.verbose)
|
|
275
209
|
(0, cli_1.logExec)(`Writing file lists in ${tmpDir}`);
|
|
276
|
-
const scanner = await
|
|
210
|
+
const scanner = await (0, fs_1.createFileScanner)({
|
|
277
211
|
glob: {
|
|
278
212
|
include,
|
|
279
213
|
cwd: sourcePath,
|
|
@@ -404,7 +338,7 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
|
|
|
404
338
|
if (data.options.verbose)
|
|
405
339
|
(0, cli_1.logExec)(`Copying backup files to ${targetPath}`);
|
|
406
340
|
await (0, promises_1.mkdir)(targetPath);
|
|
407
|
-
const scanner = await
|
|
341
|
+
const scanner = await (0, fs_1.createFileScanner)({
|
|
408
342
|
glob: {
|
|
409
343
|
include: ["**/*"],
|
|
410
344
|
cwd: sourcePath,
|
|
@@ -438,7 +372,7 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
|
|
|
438
372
|
packageName: data.package.name,
|
|
439
373
|
});
|
|
440
374
|
const sourcePath = (0, path_1.join)(this.config.outPath, snapshotName);
|
|
441
|
-
const scanner = await
|
|
375
|
+
const scanner = await (0, fs_1.createFileScanner)({
|
|
442
376
|
glob: {
|
|
443
377
|
include: ["unpacked/**/*"],
|
|
444
378
|
cwd: sourcePath,
|
package/Task/MariadbTask.d.ts
CHANGED
|
@@ -11,6 +11,17 @@ export type MariadbTaskConfigType = {
|
|
|
11
11
|
excludeTables?: string[];
|
|
12
12
|
includeDatabases?: string[];
|
|
13
13
|
excludeDatabases?: string[];
|
|
14
|
+
/**
|
|
15
|
+
* @default "auto"
|
|
16
|
+
*/
|
|
17
|
+
parallel?: number | "auto";
|
|
18
|
+
compress?: {
|
|
19
|
+
command: string;
|
|
20
|
+
args?: string[];
|
|
21
|
+
} | {
|
|
22
|
+
type: "gzip" | "pigz";
|
|
23
|
+
args?: string[];
|
|
24
|
+
};
|
|
14
25
|
};
|
|
15
26
|
export declare const mariadbTaskName = "mariadb";
|
|
16
27
|
export declare const mariadbTaskDefinition: JSONSchema7;
|
package/Task/MariadbTask.js
CHANGED
|
@@ -6,11 +6,13 @@ const cli_1 = require("../utils/cli");
|
|
|
6
6
|
const fs_1 = require("../utils/fs");
|
|
7
7
|
const math_1 = require("../utils/math");
|
|
8
8
|
const process_1 = require("../utils/process");
|
|
9
|
+
const zip_1 = require("../utils/zip");
|
|
9
10
|
const TaskAbstract_1 = require("./TaskAbstract");
|
|
10
11
|
const assert_1 = require("assert");
|
|
12
|
+
const fs_2 = require("fs");
|
|
11
13
|
const promises_1 = require("fs/promises");
|
|
14
|
+
const os_1 = require("os");
|
|
12
15
|
const path_1 = require("path");
|
|
13
|
-
const posix_1 = require("path/posix");
|
|
14
16
|
exports.mariadbTaskName = "mariadb";
|
|
15
17
|
exports.mariadbTaskDefinition = {
|
|
16
18
|
type: "object",
|
|
@@ -39,8 +41,65 @@ exports.mariadbTaskDefinition = {
|
|
|
39
41
|
excludeTables: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.stringListUtil),
|
|
40
42
|
includeDatabases: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.stringListUtil),
|
|
41
43
|
excludeDatabases: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.stringListUtil),
|
|
44
|
+
parallel: {
|
|
45
|
+
anyOf: [{ type: "integer", minimum: 1 }, { enum: ["auto"] }],
|
|
46
|
+
},
|
|
47
|
+
compress: {
|
|
48
|
+
anyOf: [
|
|
49
|
+
{
|
|
50
|
+
type: "object",
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
properties: {
|
|
53
|
+
command: { type: "string" },
|
|
54
|
+
args: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.stringListUtil),
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: "object",
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
properties: {
|
|
61
|
+
type: { enum: ["gzip", "pigz"] },
|
|
62
|
+
args: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.stringListUtil),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
42
67
|
},
|
|
43
68
|
};
|
|
69
|
+
function normalizeConfig(input) {
|
|
70
|
+
let parallel = input.parallel ?? "auto";
|
|
71
|
+
let cores = (0, os_1.cpus)().length;
|
|
72
|
+
if (parallel === "auto") {
|
|
73
|
+
parallel = input.compress ? Math.round(cores / 2) : cores;
|
|
74
|
+
cores = Math.max(1, cores - parallel);
|
|
75
|
+
}
|
|
76
|
+
let compress;
|
|
77
|
+
if (input.compress && "type" in input.compress) {
|
|
78
|
+
if (input.compress.type === "pigz") {
|
|
79
|
+
compress = { command: "pigz", args: [] };
|
|
80
|
+
if (!input.compress.args?.includes("-p")) {
|
|
81
|
+
compress.args.push("-p", cores.toString());
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (input.compress.type === "gzip") {
|
|
85
|
+
compress = { command: "gzip" };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
compress = input.compress;
|
|
90
|
+
}
|
|
91
|
+
return { parallel, compress };
|
|
92
|
+
}
|
|
93
|
+
const parseLineRegex = /^(?:\[(?<step>\d+)\] )?(?<dateTime>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})?\s+(?<text>.+)\s*$/;
|
|
94
|
+
function parseLine(line) {
|
|
95
|
+
const result = parseLineRegex.exec(line);
|
|
96
|
+
if (result) {
|
|
97
|
+
return result.groups;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
return { text: line };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
44
103
|
class MariadbTask extends TaskAbstract_1.TaskAbstract {
|
|
45
104
|
get command() {
|
|
46
105
|
return this.config.command ?? "mariabackup";
|
|
@@ -58,18 +117,25 @@ class MariadbTask extends TaskAbstract_1.TaskAbstract {
|
|
|
58
117
|
const targetPath = data.targetPath;
|
|
59
118
|
(0, assert_1.ok)(typeof sourcePath === "string");
|
|
60
119
|
(0, assert_1.ok)(typeof targetPath === "string");
|
|
120
|
+
const { parallel, compress } = normalizeConfig(config);
|
|
61
121
|
const args = [
|
|
62
122
|
`--backup`,
|
|
63
123
|
`--datadir=${sourcePath}`,
|
|
64
|
-
`--target-dir=${targetPath}`,
|
|
65
124
|
`--host=${config.hostname}`,
|
|
66
125
|
`--user=${config.username}`,
|
|
126
|
+
`--parallel=${parallel}`,
|
|
67
127
|
`--password=${typeof config.password === "string"
|
|
68
128
|
? config.password
|
|
69
129
|
: config.password
|
|
70
130
|
? (await (0, promises_1.readFile)(config.password.path)).toString()
|
|
71
131
|
: ""}`,
|
|
72
132
|
];
|
|
133
|
+
if (compress) {
|
|
134
|
+
args.push(`--stream=xbstream`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
args.push(`--target-dir=${targetPath}`);
|
|
138
|
+
}
|
|
73
139
|
if (config.includeDatabases)
|
|
74
140
|
args.push(`--databases=${config.includeDatabases.join(" ")}`);
|
|
75
141
|
if (config.excludeDatabases)
|
|
@@ -83,34 +149,23 @@ class MariadbTask extends TaskAbstract_1.TaskAbstract {
|
|
|
83
149
|
await (0, fs_1.forEachFile)(sourcePath, () => {
|
|
84
150
|
total++;
|
|
85
151
|
});
|
|
86
|
-
let
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
fatalError = true;
|
|
96
|
-
}
|
|
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();
|
|
152
|
+
let p1;
|
|
153
|
+
let lastLineText;
|
|
154
|
+
const pathRegex = /((Copying|Streaming) .+) to/;
|
|
155
|
+
const onData = async (line) => {
|
|
156
|
+
const { text } = parseLine(line);
|
|
157
|
+
lastLineText = text;
|
|
158
|
+
if (line.includes("[ERROR] InnoDB: Unsupported redo log format.") ||
|
|
159
|
+
line.includes("Error: cannot read redo log header")) {
|
|
160
|
+
p1.kill();
|
|
107
161
|
}
|
|
108
|
-
else
|
|
109
|
-
const
|
|
162
|
+
else {
|
|
163
|
+
const matches = pathRegex.exec(text);
|
|
164
|
+
if (matches)
|
|
165
|
+
current++;
|
|
110
166
|
await data.onProgress({
|
|
111
167
|
relative: {
|
|
112
|
-
|
|
113
|
-
payload: path,
|
|
168
|
+
payload: matches ? matches[1] : text,
|
|
114
169
|
},
|
|
115
170
|
absolute: {
|
|
116
171
|
current,
|
|
@@ -120,36 +175,163 @@ class MariadbTask extends TaskAbstract_1.TaskAbstract {
|
|
|
120
175
|
});
|
|
121
176
|
}
|
|
122
177
|
};
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
178
|
+
const stats = { xbFiles: total };
|
|
179
|
+
await (0, promises_1.writeFile)((0, path_1.join)(targetPath, "stats.dtt.json"), JSON.stringify(stats));
|
|
180
|
+
if (compress) {
|
|
181
|
+
const p0 = (0, fs_2.createWriteStream)((0, path_1.join)(targetPath, "db.xb.gz"));
|
|
182
|
+
p1 = (0, process_1.createProcess)(command, args, {
|
|
183
|
+
$log: {
|
|
184
|
+
exec: this.verbose,
|
|
185
|
+
stderr: this.verbose,
|
|
186
|
+
},
|
|
187
|
+
$stderr: {
|
|
188
|
+
parseLines: true,
|
|
189
|
+
onData,
|
|
190
|
+
},
|
|
191
|
+
$onExitCode: (code) => `Exit code: ${code} - ${lastLineText}`,
|
|
192
|
+
});
|
|
193
|
+
const p2 = (0, process_1.createProcess)(compress.command, compress.args);
|
|
194
|
+
p1.stdout.pipe(p2.stdin, { end: true });
|
|
195
|
+
p2.stdout.pipe(p0, { end: true });
|
|
196
|
+
await Promise.all([p1, p2, (0, fs_1.waitForClose)(p0)]);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
p1 = (0, process_1.createProcess)(command, args, {
|
|
200
|
+
$log: this.verbose,
|
|
201
|
+
$stdout: {
|
|
202
|
+
parseLines: true,
|
|
203
|
+
onData,
|
|
204
|
+
},
|
|
205
|
+
$stderr: {
|
|
206
|
+
parseLines: true,
|
|
207
|
+
onData,
|
|
208
|
+
},
|
|
209
|
+
$onExitCode: (code) => `Exit code: ${code} - ${lastLineText}`,
|
|
210
|
+
});
|
|
211
|
+
await p1;
|
|
212
|
+
await (0, process_1.exec)(command, [`--prepare`, `--target-dir=${targetPath}`], undefined, {
|
|
213
|
+
log: this.verbose,
|
|
214
|
+
stderr: { onData: () => { } },
|
|
215
|
+
});
|
|
216
|
+
}
|
|
139
217
|
}
|
|
140
218
|
async onRestore(data) {
|
|
141
219
|
this.verbose = data.options.verbose;
|
|
142
220
|
const restorePath = data.package.restorePath;
|
|
143
221
|
(0, assert_1.ok)(typeof restorePath === "string");
|
|
144
222
|
await (0, fs_1.mkdirIfNotExists)(restorePath);
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
223
|
+
const removeFiles = [];
|
|
224
|
+
let files = [];
|
|
225
|
+
const reloadFiles = async (data = {}) => {
|
|
226
|
+
if (data.removeFile)
|
|
227
|
+
removeFiles.push(data.removeFile);
|
|
228
|
+
return (files = (await (0, fs_1.readDir)(restorePath)).filter((v) => !removeFiles.includes(v)));
|
|
229
|
+
};
|
|
230
|
+
await reloadFiles();
|
|
231
|
+
const absolute = {
|
|
232
|
+
format: "amount",
|
|
233
|
+
total: 3,
|
|
234
|
+
current: 0,
|
|
235
|
+
description: undefined,
|
|
236
|
+
payload: undefined,
|
|
237
|
+
percent: undefined,
|
|
238
|
+
};
|
|
239
|
+
// Stats
|
|
240
|
+
const statsFile = files.find((file) => file === "stats.dtt.json");
|
|
241
|
+
let stats;
|
|
242
|
+
if (statsFile) {
|
|
243
|
+
const statsFilePath = (0, path_1.join)(restorePath, statsFile);
|
|
244
|
+
const statsBuffer = await (0, promises_1.readFile)(statsFilePath);
|
|
245
|
+
stats = JSON.parse(statsBuffer.toString());
|
|
246
|
+
await reloadFiles({ removeFile: statsFile });
|
|
247
|
+
}
|
|
248
|
+
const zipFile = files.find((file) => file.endsWith(".gz"));
|
|
249
|
+
absolute.current++;
|
|
250
|
+
absolute.percent = (0, math_1.progressPercent)(absolute.total, absolute.current);
|
|
251
|
+
// Extract
|
|
252
|
+
if (files.length === 1 && zipFile) {
|
|
253
|
+
absolute.description = "Extracting";
|
|
254
|
+
absolute.payload = zipFile;
|
|
255
|
+
await data.onProgress({
|
|
256
|
+
absolute,
|
|
257
|
+
});
|
|
258
|
+
await (0, zip_1.unzip)({
|
|
259
|
+
input: (0, path_1.join)(restorePath, zipFile),
|
|
260
|
+
output: restorePath,
|
|
261
|
+
verbose: this.verbose,
|
|
262
|
+
async onProgress(item) {
|
|
263
|
+
await data.onProgress({
|
|
264
|
+
absolute,
|
|
265
|
+
relative: {
|
|
266
|
+
payload: item.path,
|
|
267
|
+
format: "amount",
|
|
268
|
+
percent: item.percent,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
await reloadFiles({ removeFile: zipFile });
|
|
274
|
+
}
|
|
275
|
+
// Extract stream
|
|
276
|
+
const xbFile = files.find((file) => file.endsWith(".xb"));
|
|
277
|
+
absolute.current++;
|
|
278
|
+
absolute.percent = (0, math_1.progressPercent)(absolute.total, absolute.current);
|
|
279
|
+
if (files.length === 1 && xbFile) {
|
|
280
|
+
const xbFilePath = (0, path_1.join)(restorePath, xbFile);
|
|
281
|
+
const xbStream = (0, fs_2.createReadStream)(xbFilePath);
|
|
282
|
+
removeFiles.push(xbFile);
|
|
283
|
+
absolute.description = "Extracting stream";
|
|
284
|
+
absolute.payload = xbFile;
|
|
285
|
+
await data.onProgress({
|
|
286
|
+
absolute,
|
|
287
|
+
});
|
|
288
|
+
let currentXbFiles = 0;
|
|
289
|
+
const { parallel } = normalizeConfig({ parallel: this.config.parallel });
|
|
290
|
+
const p1 = (0, process_1.createProcess)("mbstream", ["-x", "-C", restorePath, "-v", "-p", parallel], {
|
|
291
|
+
$log: this.verbose,
|
|
292
|
+
$stderr: {
|
|
293
|
+
parseLines: true,
|
|
294
|
+
async onData(line) {
|
|
295
|
+
const { text: path } = parseLine(line);
|
|
296
|
+
await data.onProgress({
|
|
297
|
+
absolute,
|
|
298
|
+
relative: {
|
|
299
|
+
payload: path,
|
|
300
|
+
format: "amount",
|
|
301
|
+
current: ++currentXbFiles,
|
|
302
|
+
total: stats?.xbFiles,
|
|
303
|
+
percent: stats?.xbFiles
|
|
304
|
+
? (0, math_1.progressPercent)(stats.xbFiles, currentXbFiles)
|
|
305
|
+
: undefined,
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
xbStream.pipe(p1.stdin, { end: true });
|
|
312
|
+
await Promise.all([(0, fs_1.waitForClose)(xbStream), p1]);
|
|
313
|
+
}
|
|
314
|
+
// Prepare
|
|
315
|
+
absolute.current++;
|
|
316
|
+
absolute.percent = (0, math_1.progressPercent)(absolute.total, absolute.current);
|
|
317
|
+
if (files.length === 1 && xbFile) {
|
|
318
|
+
absolute.description = "Preparing";
|
|
319
|
+
await data.onProgress({
|
|
320
|
+
absolute,
|
|
321
|
+
});
|
|
322
|
+
await (0, process_1.exec)(this.command, [`--prepare`, `--target-dir=${restorePath}`], undefined, {
|
|
323
|
+
log: this.verbose,
|
|
324
|
+
stderr: { onData: () => { } },
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
await reloadFiles();
|
|
328
|
+
removeFiles.push(...files.filter((file) => file.startsWith("ib_logfile")));
|
|
329
|
+
// Remove files
|
|
330
|
+
for (const file of removeFiles) {
|
|
331
|
+
const filePath = (0, path_1.join)(restorePath, file);
|
|
332
|
+
if (this.verbose)
|
|
333
|
+
(0, cli_1.logExec)("rm", [filePath]);
|
|
334
|
+
await (0, promises_1.rm)(filePath);
|
|
153
335
|
}
|
|
154
336
|
}
|
|
155
337
|
}
|
package/config.schema.json
CHANGED
|
@@ -798,6 +798,50 @@
|
|
|
798
798
|
},
|
|
799
799
|
"excludeDatabases": {
|
|
800
800
|
"$ref": "#/definitions/stringlist-util"
|
|
801
|
+
},
|
|
802
|
+
"parallel": {
|
|
803
|
+
"anyOf": [
|
|
804
|
+
{
|
|
805
|
+
"type": "integer",
|
|
806
|
+
"minimum": 1
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
"enum": [
|
|
810
|
+
"auto"
|
|
811
|
+
]
|
|
812
|
+
}
|
|
813
|
+
]
|
|
814
|
+
},
|
|
815
|
+
"compress": {
|
|
816
|
+
"anyOf": [
|
|
817
|
+
{
|
|
818
|
+
"type": "object",
|
|
819
|
+
"additionalProperties": false,
|
|
820
|
+
"properties": {
|
|
821
|
+
"command": {
|
|
822
|
+
"type": "string"
|
|
823
|
+
},
|
|
824
|
+
"args": {
|
|
825
|
+
"$ref": "#/definitions/stringlist-util"
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
"type": "object",
|
|
831
|
+
"additionalProperties": false,
|
|
832
|
+
"properties": {
|
|
833
|
+
"type": {
|
|
834
|
+
"enum": [
|
|
835
|
+
"gzip",
|
|
836
|
+
"pigz"
|
|
837
|
+
]
|
|
838
|
+
},
|
|
839
|
+
"args": {
|
|
840
|
+
"$ref": "#/definitions/stringlist-util"
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
]
|
|
801
845
|
}
|
|
802
846
|
}
|
|
803
847
|
},
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datatruck/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"dependencies": {
|
|
5
|
-
"ajv": "^8.
|
|
5
|
+
"ajv": "^8.12.0",
|
|
6
6
|
"async": "^3.2.4",
|
|
7
7
|
"chalk": "^4.1.2",
|
|
8
8
|
"cli-table3": "^0.6.3",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"optionalDependencies": {
|
|
19
19
|
"ts-node": "^10.9.1",
|
|
20
|
-
"yaml": "^2.2.
|
|
20
|
+
"yaml": "^2.2.1"
|
|
21
21
|
},
|
|
22
22
|
"engine": {
|
|
23
23
|
"node": ">=16.0.0"
|
package/utils/fs.d.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
/// <reference types="node" />
|
|
4
4
|
/// <reference types="node" />
|
|
5
|
-
import {
|
|
5
|
+
import { Progress } from "./progress";
|
|
6
|
+
import { Entry, Options } from "fast-glob";
|
|
7
|
+
import { Dirent, ReadStream, Stats } from "fs";
|
|
6
8
|
import { WriteStream } from "fs";
|
|
7
9
|
import { Interface } from "readline";
|
|
8
10
|
export declare const isWSLSystem: boolean;
|
|
@@ -51,7 +53,7 @@ export declare function writeGitIgnoreList(options: {
|
|
|
51
53
|
paths: NodeJS.ReadableStream | string[];
|
|
52
54
|
outDir: string;
|
|
53
55
|
}): Promise<string>;
|
|
54
|
-
export declare function waitForClose(stream: WriteStream): Promise<
|
|
56
|
+
export declare function waitForClose(stream: WriteStream | ReadStream): Promise<void>;
|
|
55
57
|
export declare function copyFileWithStreams(source: string, target: string): Promise<unknown>;
|
|
56
58
|
export declare function updateFileStats(path: string, fileInfo: Stats): Promise<void>;
|
|
57
59
|
export declare function isNotFoundError(error: unknown): boolean;
|
|
@@ -97,4 +99,23 @@ export declare function cpy(options: {
|
|
|
97
99
|
files: number;
|
|
98
100
|
dirs: number;
|
|
99
101
|
}>;
|
|
102
|
+
export declare function createFileScanner(options: {
|
|
103
|
+
glob: Options & {
|
|
104
|
+
include: string[];
|
|
105
|
+
};
|
|
106
|
+
onProgress: (data: Progress) => Promise<void>;
|
|
107
|
+
disableCounting?: boolean;
|
|
108
|
+
disableEndProgress?: boolean;
|
|
109
|
+
}): Promise<{
|
|
110
|
+
total: number;
|
|
111
|
+
current: number;
|
|
112
|
+
progress: (description: string, data: {
|
|
113
|
+
path?: string;
|
|
114
|
+
current: number;
|
|
115
|
+
type?: "start" | "end";
|
|
116
|
+
percent?: number;
|
|
117
|
+
}) => Promise<void>;
|
|
118
|
+
updateProgress: (end?: boolean) => Promise<void>;
|
|
119
|
+
start: (cb?: ((entry: Required<Entry>) => any) | undefined) => Promise<void>;
|
|
120
|
+
}>;
|
|
100
121
|
export {};
|
package/utils/fs.js
CHANGED
|
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.cpy = exports.isNotFoundError = exports.updateFileStats = exports.copyFileWithStreams = exports.waitForClose = exports.writeGitIgnoreList = exports.fastglobToGitIgnore = exports.forEachFile = exports.readDir = exports.checkDir = exports.checkFile = exports.readPartialFile = exports.mkTmpDir = exports.fastFolderSizeAsync = exports.tmpDir = exports.rmTmpDir = exports.isTmpDir = exports.sessionTmpDir = exports.parentTmpDir = exports.existsFile = exports.findFile = exports.parsePackageFile = exports.parseFile = exports.parseFileExtensions = exports.readdirIfExists = exports.writeJSONFile = exports.existsDir = exports.ensureEmptyDir = exports.mkdirIfNotExists = exports.isDirEmpty = exports.isLocalDir = exports.pathIterator = exports.applyPermissions = exports.isEmptyDir = exports.isWSLSystem = void 0;
|
|
6
|
+
exports.createFileScanner = exports.cpy = exports.isNotFoundError = exports.updateFileStats = exports.copyFileWithStreams = exports.waitForClose = exports.writeGitIgnoreList = exports.fastglobToGitIgnore = exports.forEachFile = exports.readDir = exports.checkDir = exports.checkFile = exports.readPartialFile = exports.mkTmpDir = exports.fastFolderSizeAsync = exports.tmpDir = exports.rmTmpDir = exports.isTmpDir = exports.sessionTmpDir = exports.parentTmpDir = exports.existsFile = exports.findFile = exports.parsePackageFile = exports.parseFile = exports.parseFileExtensions = exports.readdirIfExists = exports.writeJSONFile = exports.existsDir = exports.ensureEmptyDir = exports.mkdirIfNotExists = exports.isDirEmpty = exports.isLocalDir = exports.pathIterator = exports.applyPermissions = exports.isEmptyDir = exports.isWSLSystem = void 0;
|
|
7
7
|
const globalData_1 = __importDefault(require("../globalData"));
|
|
8
|
+
const math_1 = require("./math");
|
|
8
9
|
const path_1 = require("./path");
|
|
9
10
|
const async_1 = require("async");
|
|
10
11
|
const crypto_1 = require("crypto");
|
|
@@ -66,12 +67,9 @@ async function isDirEmpty(path) {
|
|
|
66
67
|
}
|
|
67
68
|
exports.isDirEmpty = isDirEmpty;
|
|
68
69
|
async function mkdirIfNotExists(path) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
catch (e) { }
|
|
70
|
+
await (0, promises_1.mkdir)(path, {
|
|
71
|
+
recursive: true,
|
|
72
|
+
});
|
|
75
73
|
return path;
|
|
76
74
|
}
|
|
77
75
|
exports.mkdirIfNotExists = mkdirIfNotExists;
|
|
@@ -458,3 +456,64 @@ async function cpy(options) {
|
|
|
458
456
|
return stats;
|
|
459
457
|
}
|
|
460
458
|
exports.cpy = cpy;
|
|
459
|
+
async function createFileScanner(options) {
|
|
460
|
+
const object = {
|
|
461
|
+
total: 0,
|
|
462
|
+
current: 0,
|
|
463
|
+
progress: async (description, data) => {
|
|
464
|
+
await options.onProgress({
|
|
465
|
+
relative: {
|
|
466
|
+
description,
|
|
467
|
+
payload: data.path,
|
|
468
|
+
percent: data.percent,
|
|
469
|
+
},
|
|
470
|
+
absolute: {
|
|
471
|
+
total: object.total,
|
|
472
|
+
current: object.current + data.current,
|
|
473
|
+
percent: (0, math_1.progressPercent)(object.total, object.current + data.current),
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
if (data.type === "end") {
|
|
477
|
+
object.current += data.current;
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
updateProgress: async (end) => {
|
|
481
|
+
const currentTime = performance.now();
|
|
482
|
+
const diff = currentTime - lastTime;
|
|
483
|
+
if (end || diff > 1000) {
|
|
484
|
+
await options.onProgress({
|
|
485
|
+
relative: {
|
|
486
|
+
description: end ? "Scanned files" : "Scanning files",
|
|
487
|
+
payload: object.total.toString(),
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
lastTime = currentTime;
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
start: async (cb) => {
|
|
494
|
+
for await (const entry of pathIterator(stream)) {
|
|
495
|
+
if (!options.disableCounting)
|
|
496
|
+
object.total++;
|
|
497
|
+
await object.updateProgress();
|
|
498
|
+
if (cb)
|
|
499
|
+
await cb(entry);
|
|
500
|
+
}
|
|
501
|
+
if (!options.disableEndProgress)
|
|
502
|
+
await object.updateProgress(true);
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
await options.onProgress({
|
|
506
|
+
relative: {
|
|
507
|
+
description: "Scanning files",
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
const stream = fast_glob_1.default.stream(options.glob.include, {
|
|
511
|
+
dot: true,
|
|
512
|
+
markDirectories: true,
|
|
513
|
+
stats: true,
|
|
514
|
+
...options.glob,
|
|
515
|
+
});
|
|
516
|
+
let lastTime = performance.now();
|
|
517
|
+
return object;
|
|
518
|
+
}
|
|
519
|
+
exports.createFileScanner = createFileScanner;
|
package/utils/process.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
|
-
|
|
3
|
+
/// <reference types="node" />
|
|
4
|
+
import { SpawnOptions, ChildProcess, ChildProcessByStdio } from "child_process";
|
|
4
5
|
import { ReadStream, WriteStream } from "fs";
|
|
6
|
+
import { Readable, Writable } from "stream";
|
|
5
7
|
export type ExecLogSettingsType = {
|
|
6
8
|
colorize?: boolean;
|
|
7
9
|
exec?: boolean;
|
|
@@ -13,18 +15,22 @@ export type ExecLogSettingsType = {
|
|
|
13
15
|
export interface ExecSettingsInterface {
|
|
14
16
|
exec?: boolean;
|
|
15
17
|
pipe?: {
|
|
16
|
-
stream: WriteStream
|
|
18
|
+
stream: WriteStream;
|
|
17
19
|
onWriteProgress?: (data: {
|
|
18
20
|
totalBytes: number;
|
|
19
21
|
}) => void;
|
|
22
|
+
} | {
|
|
23
|
+
stream: ReadStream;
|
|
20
24
|
onReadProgress?: (data: {
|
|
21
25
|
totalBytes: number;
|
|
22
26
|
currentBytes: number;
|
|
23
27
|
progress: number;
|
|
24
28
|
}) => void;
|
|
29
|
+
} | {
|
|
30
|
+
stream: Readable;
|
|
25
31
|
};
|
|
26
32
|
log?: ExecLogSettingsType | boolean;
|
|
27
|
-
onSpawn?: (p: ChildProcess) =>
|
|
33
|
+
onSpawn?: (p: ChildProcess) => any;
|
|
28
34
|
stdout?: {
|
|
29
35
|
save?: boolean;
|
|
30
36
|
parseLines?: boolean;
|
|
@@ -49,6 +55,52 @@ export type ExecResultType = {
|
|
|
49
55
|
stderr: string;
|
|
50
56
|
exitCode: number;
|
|
51
57
|
};
|
|
58
|
+
export type ParseStreamDataOptions<S extends boolean = boolean> = {
|
|
59
|
+
save?: S;
|
|
60
|
+
parseLines?: boolean;
|
|
61
|
+
log?: LogProcessOptions | boolean;
|
|
62
|
+
onData?: (data: string) => void;
|
|
63
|
+
};
|
|
64
|
+
export declare function parseStreamData<S extends boolean>(stream: Readable, options?: ParseStreamDataOptions<S>): Promise<S extends true ? string : undefined>;
|
|
65
|
+
type OnExitCode = OnExitCodeValue | ((code: number) => OnExitCodeValue | void | undefined);
|
|
66
|
+
type OnExitCodeValue = Error | string | number | boolean;
|
|
67
|
+
export declare function waitForClose<O extends boolean, E extends boolean>(p: ChildProcess, options?: {
|
|
68
|
+
strict?: boolean;
|
|
69
|
+
stdout?: O;
|
|
70
|
+
stderr?: E;
|
|
71
|
+
onExitCode?: OnExitCode;
|
|
72
|
+
}): Promise<{
|
|
73
|
+
exitCode: number;
|
|
74
|
+
} & (O extends true ? {
|
|
75
|
+
stdout: string;
|
|
76
|
+
} : {}) & (E extends true ? {
|
|
77
|
+
stderr: string;
|
|
78
|
+
} : {})>;
|
|
79
|
+
export type LogProcessOptions = {
|
|
80
|
+
envNames?: string[];
|
|
81
|
+
env?: Record<string, any>;
|
|
82
|
+
pipe?: Readable | Writable;
|
|
83
|
+
toStderr?: boolean;
|
|
84
|
+
colorize?: boolean;
|
|
85
|
+
};
|
|
86
|
+
export declare function logProcessExec(command: string, argv: any[], options: LogProcessOptions): Promise<void>;
|
|
87
|
+
export type ProcessOptions<O1 extends boolean, O2 extends boolean> = {
|
|
88
|
+
$stdout?: Omit<ParseStreamDataOptions<O1>, "log">;
|
|
89
|
+
$stderr?: Omit<ParseStreamDataOptions<O2>, "log">;
|
|
90
|
+
$onExitCode?: OnExitCode;
|
|
91
|
+
$log?: boolean | {
|
|
92
|
+
exec?: boolean | LogProcessOptions;
|
|
93
|
+
stdout?: boolean | LogProcessOptions;
|
|
94
|
+
stderr?: boolean | LogProcessOptions;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
export declare function createProcess<O1 extends boolean, O2 extends boolean>(command: string, argv?: (string | number)[], options?: SpawnOptions & ProcessOptions<O1, O2>): ChildProcessByStdio<Writable, Readable, Readable> & PromiseLike<{
|
|
98
|
+
exitCode: number;
|
|
99
|
+
} & (O1 extends true ? {
|
|
100
|
+
stdout: string;
|
|
101
|
+
} : {}) & (O2 extends true ? {
|
|
102
|
+
stderr: string;
|
|
103
|
+
} : {})>;
|
|
52
104
|
export declare function exec(command: string, argv?: string[], options?: SpawnOptions | null, settings?: ExecSettingsInterface): Promise<ExecResultType>;
|
|
53
105
|
type EventNameType = "exit" | "SIGINT" | "SIGUSR1" | "SIGUSR2" | "SIGTERM" | "uncaughtException";
|
|
54
106
|
export declare function onExit(cb: (eventName: EventNameType, ...args: any[]) => void): void;
|
package/utils/process.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.onExit = exports.exec = exports.logExecStderr = exports.logExecStdout = void 0;
|
|
6
|
+
exports.onExit = exports.exec = exports.createProcess = exports.logProcessExec = exports.waitForClose = exports.parseStreamData = exports.logExecStderr = exports.logExecStdout = void 0;
|
|
7
7
|
const cli_1 = require("./cli");
|
|
8
8
|
const fs_1 = require("./fs");
|
|
9
9
|
const math_1 = require("./math");
|
|
@@ -12,6 +12,7 @@ const child_process_1 = require("child_process");
|
|
|
12
12
|
const fs_2 = require("fs");
|
|
13
13
|
const promises_1 = require("fs/promises");
|
|
14
14
|
const readline_1 = require("readline");
|
|
15
|
+
const stream_1 = require("stream");
|
|
15
16
|
function logExecStdout(input) {
|
|
16
17
|
let text = input.colorize ? chalk_1.default.grey(input.data) : input.data;
|
|
17
18
|
if (input.lineSalt)
|
|
@@ -23,6 +24,152 @@ function logExecStderr(data, colorize) {
|
|
|
23
24
|
process.stdout.write(colorize ? chalk_1.default.red(data) : data);
|
|
24
25
|
}
|
|
25
26
|
exports.logExecStderr = logExecStderr;
|
|
27
|
+
function parseStreamData(stream, options = {}) {
|
|
28
|
+
const log = options.log === true ? {} : options.log;
|
|
29
|
+
let result;
|
|
30
|
+
if (options.save)
|
|
31
|
+
result = "";
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const lines = options.parseLines;
|
|
34
|
+
const onData = (data) => {
|
|
35
|
+
if (options.onData)
|
|
36
|
+
options.onData(data.toString());
|
|
37
|
+
if (log)
|
|
38
|
+
logExecStdout({
|
|
39
|
+
data: lines ? `${data}\n` : data.toString(),
|
|
40
|
+
stderr: log.toStderr,
|
|
41
|
+
colorize: log.colorize,
|
|
42
|
+
});
|
|
43
|
+
if (options?.save)
|
|
44
|
+
result += data.toString();
|
|
45
|
+
};
|
|
46
|
+
if (lines) {
|
|
47
|
+
const rl = (0, readline_1.createInterface)({
|
|
48
|
+
input: stream,
|
|
49
|
+
});
|
|
50
|
+
rl.on("line", onData).on("close", () => resolve(result));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
stream
|
|
54
|
+
.on("data", onData)
|
|
55
|
+
.on("error", reject)
|
|
56
|
+
.once("close", () => resolve(result));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
exports.parseStreamData = parseStreamData;
|
|
61
|
+
function waitForClose(p, options = {}) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
let result = {
|
|
64
|
+
exitCode: 1,
|
|
65
|
+
};
|
|
66
|
+
if (options.stdout) {
|
|
67
|
+
result.stdout = "";
|
|
68
|
+
p.stdout.on("data", (data) => (result.stdout += data.toString()));
|
|
69
|
+
}
|
|
70
|
+
p.once("error", reject).once("close", (exitCode) => {
|
|
71
|
+
if (exitCode) {
|
|
72
|
+
let onExitCode = options.onExitCode ?? true;
|
|
73
|
+
if (typeof onExitCode === "function") {
|
|
74
|
+
onExitCode = onExitCode(exitCode);
|
|
75
|
+
}
|
|
76
|
+
if (typeof onExitCode === "string") {
|
|
77
|
+
reject(new Error(onExitCode));
|
|
78
|
+
}
|
|
79
|
+
else if (typeof onExitCode === "number") {
|
|
80
|
+
reject(new Error(`Exit code: ${onExitCode}`));
|
|
81
|
+
}
|
|
82
|
+
else if (onExitCode instanceof Error) {
|
|
83
|
+
reject(onExitCode);
|
|
84
|
+
}
|
|
85
|
+
else if (onExitCode === false) {
|
|
86
|
+
resolve({ ...result, exitCode: exitCode });
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
reject(new Error(`Exit code: ${exitCode}`));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
resolve({ ...result, exitCode: exitCode });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
exports.waitForClose = waitForClose;
|
|
99
|
+
async function logProcessExec(command, argv, options) {
|
|
100
|
+
const logEnv = options.envNames?.reduce((env, key) => {
|
|
101
|
+
const value = options?.env?.[key];
|
|
102
|
+
if (typeof value !== "undefined")
|
|
103
|
+
env[key] = value;
|
|
104
|
+
return env;
|
|
105
|
+
}, {});
|
|
106
|
+
(0, cli_1.logExec)(command, options.pipe
|
|
107
|
+
? [
|
|
108
|
+
...argv,
|
|
109
|
+
options.pipe instanceof stream_1.Readable ? "<" : ">",
|
|
110
|
+
"path" in options.pipe ? String(options.pipe.path) : "[stream]",
|
|
111
|
+
]
|
|
112
|
+
: argv, logEnv, options.toStderr);
|
|
113
|
+
}
|
|
114
|
+
exports.logProcessExec = logProcessExec;
|
|
115
|
+
function createProcess(command, argv = [], options = {}) {
|
|
116
|
+
const $log = options.$log === true
|
|
117
|
+
? { exec: {}, stdout: {}, stderr: {} }
|
|
118
|
+
: options.$log || {};
|
|
119
|
+
if ($log.exec)
|
|
120
|
+
logProcessExec(command, argv, $log.exec === true ? {} : $log.exec);
|
|
121
|
+
if (typeof options.cwd === "string") {
|
|
122
|
+
let isDir = false;
|
|
123
|
+
try {
|
|
124
|
+
isDir = (0, fs_2.statSync)(options.cwd).isDirectory();
|
|
125
|
+
}
|
|
126
|
+
catch (error) { }
|
|
127
|
+
if (!isDir)
|
|
128
|
+
throw new Error(`Current working directory does not exist: ${options.cwd}`);
|
|
129
|
+
}
|
|
130
|
+
const handler = (0, child_process_1.spawn)(command, argv.map((v) => (typeof v === "number" ? v.toString() : v)), options ?? {});
|
|
131
|
+
const { $stdout, $stderr, $onExitCode } = options;
|
|
132
|
+
async function exec() {
|
|
133
|
+
const [stdout, stderr, result] = await Promise.all([
|
|
134
|
+
(!!$log.stdout || !!$stdout) &&
|
|
135
|
+
parseStreamData(handler.stdout, {
|
|
136
|
+
log: $log.stdout,
|
|
137
|
+
...$stdout,
|
|
138
|
+
}),
|
|
139
|
+
(!!$log.stderr || !!$stderr) &&
|
|
140
|
+
parseStreamData(handler.stderr, {
|
|
141
|
+
log: $log.stderr,
|
|
142
|
+
...$stderr,
|
|
143
|
+
}),
|
|
144
|
+
waitForClose(handler, {
|
|
145
|
+
onExitCode: $onExitCode,
|
|
146
|
+
}),
|
|
147
|
+
]);
|
|
148
|
+
const endResult = {
|
|
149
|
+
exitCode: result.exitCode,
|
|
150
|
+
};
|
|
151
|
+
if (typeof stdout === "string")
|
|
152
|
+
endResult.stdout = stdout;
|
|
153
|
+
if (typeof stderr === "string")
|
|
154
|
+
endResult.stderr = stderr;
|
|
155
|
+
return endResult;
|
|
156
|
+
}
|
|
157
|
+
const promise = {
|
|
158
|
+
[Symbol.toStringTag]: "process",
|
|
159
|
+
then: function (onfulfilled, onrejected) {
|
|
160
|
+
return exec().then(onfulfilled, onrejected);
|
|
161
|
+
},
|
|
162
|
+
catch: function (onrejected) {
|
|
163
|
+
return exec().catch(onrejected);
|
|
164
|
+
},
|
|
165
|
+
finally: function (onfinally) {
|
|
166
|
+
return exec().finally(onfinally);
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
Object.assign(handler, promise);
|
|
170
|
+
return handler;
|
|
171
|
+
}
|
|
172
|
+
exports.createProcess = createProcess;
|
|
26
173
|
async function exec(command, argv = [], options = null, settings = {}) {
|
|
27
174
|
const pipe = settings.pipe;
|
|
28
175
|
let log = {};
|
|
@@ -34,23 +181,16 @@ async function exec(command, argv = [], options = null, settings = {}) {
|
|
|
34
181
|
}
|
|
35
182
|
return new Promise(async (resolve, reject) => {
|
|
36
183
|
if (log.exec) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
(0, cli_1.logExec)(command, pipe
|
|
44
|
-
? [
|
|
45
|
-
...argv,
|
|
46
|
-
pipe.stream instanceof fs_2.ReadStream ? "<" : ">",
|
|
47
|
-
String(pipe.stream.path),
|
|
48
|
-
]
|
|
49
|
-
: argv, logEnv, log.allToStderr);
|
|
184
|
+
logProcessExec(command, argv, {
|
|
185
|
+
env: options?.env,
|
|
186
|
+
envNames: log.envNames,
|
|
187
|
+
pipe: pipe?.stream,
|
|
188
|
+
toStderr: log.allToStderr,
|
|
189
|
+
});
|
|
50
190
|
}
|
|
51
191
|
if (typeof options?.cwd === "string" && !(await (0, fs_1.checkDir)(options.cwd)))
|
|
52
|
-
|
|
53
|
-
if (pipe?.
|
|
192
|
+
throw new Error(`Current working directory does not exist: ${options.cwd}`);
|
|
193
|
+
if (pipe?.stream instanceof fs_2.ReadStream && "onReadProgress" in pipe) {
|
|
54
194
|
const fileInfo = await (0, promises_1.stat)(pipe.stream.path);
|
|
55
195
|
const totalBytes = fileInfo.size;
|
|
56
196
|
let currentBytes = 0;
|
|
@@ -64,7 +204,7 @@ async function exec(command, argv = [], options = null, settings = {}) {
|
|
|
64
204
|
});
|
|
65
205
|
}
|
|
66
206
|
const p = (0, child_process_1.spawn)(command, argv, options ?? {});
|
|
67
|
-
settings.onSpawn?.(p);
|
|
207
|
+
await settings.onSpawn?.(p);
|
|
68
208
|
let spawnError;
|
|
69
209
|
const spawnData = {
|
|
70
210
|
stdout: "",
|
|
@@ -121,7 +261,7 @@ async function exec(command, argv = [], options = null, settings = {}) {
|
|
|
121
261
|
throw new Error(`stdout is not defined`);
|
|
122
262
|
if (!p.stderr)
|
|
123
263
|
throw new Error(`stderr is not defined`);
|
|
124
|
-
if (pipe.onWriteProgress) {
|
|
264
|
+
if ("onWriteProgress" in pipe && pipe.onWriteProgress) {
|
|
125
265
|
let totalBytes = 0;
|
|
126
266
|
p.stdout.on("data", (chunk) => {
|
|
127
267
|
totalBytes += chunk.length;
|
|
@@ -136,7 +276,7 @@ async function exec(command, argv = [], options = null, settings = {}) {
|
|
|
136
276
|
p.stderr.pipe(pipe.stream, { end: false });
|
|
137
277
|
p.on("close", tryFinish);
|
|
138
278
|
}
|
|
139
|
-
else if (pipe.stream instanceof
|
|
279
|
+
else if (pipe.stream instanceof stream_1.Readable) {
|
|
140
280
|
if (!p.stdin)
|
|
141
281
|
throw new Error(`stdin is not defined`);
|
|
142
282
|
pipe.stream.pipe(p.stdin);
|