@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.
@@ -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 this.createFileScanner({
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 this.createFileScanner({
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 this.createFileScanner({
375
+ const scanner = await (0, fs_1.createFileScanner)({
442
376
  glob: {
443
377
  include: ["unpacked/**/*"],
444
378
  cwd: sourcePath,
@@ -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;
@@ -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 childProcess;
87
- const onData = async (strLines) => {
88
- const paths = [];
89
- const pathRegex = /\[\d{1,}\] \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} Copying (.+) to/;
90
- const lines = strLines.split(/\r?\n/);
91
- let fatalError = false;
92
- for (const line of lines) {
93
- if (line.includes("[ERROR] InnoDB: Unsupported redo log format.") ||
94
- line.includes("Error: cannot read redo log header")) {
95
- fatalError = true;
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 if (paths.length) {
109
- const path = (0, posix_1.normalize)(paths[0]);
162
+ else {
163
+ const matches = pathRegex.exec(text);
164
+ if (matches)
165
+ current++;
110
166
  await data.onProgress({
111
167
  relative: {
112
- description: "Copying file",
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
- await (0, process_1.exec)(command, args, undefined, {
124
- log: this.verbose,
125
- onSpawn: (p) => {
126
- childProcess = p;
127
- },
128
- stdout: {
129
- onData,
130
- },
131
- stderr: {
132
- onData,
133
- },
134
- });
135
- await (0, process_1.exec)(command, [`--prepare`, `--target-dir=${targetPath}`], undefined, {
136
- log: this.verbose,
137
- stderr: { onData: () => { } },
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 files = await (0, fs_1.readDir)(restorePath);
146
- for (const file of files) {
147
- if (file.startsWith("ib_logfile")) {
148
- const filePath = (0, path_1.join)(restorePath, file);
149
- if (this.verbose)
150
- (0, cli_1.logExec)("rm", [filePath]);
151
- await (0, promises_1.rm)(filePath);
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
  }
@@ -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.17.2",
3
+ "version": "0.18.0",
4
4
  "dependencies": {
5
- "ajv": "^8.11.2",
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.0"
20
+ "yaml": "^2.2.1"
21
21
  },
22
22
  "engine": {
23
23
  "node": ">=16.0.0"
@@ -59,8 +59,8 @@ export declare const params: {
59
59
  action: string;
60
60
  };
61
61
  pkgRestorePath: {
62
- packageName: string;
63
62
  path: string;
63
+ packageName: string;
64
64
  snapshotId: string;
65
65
  temp: string;
66
66
  snapshotDate: string;
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 { Dirent, Stats } from "fs";
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<WriteStream>;
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
- try {
70
- await (0, promises_1.mkdir)(path, {
71
- recursive: true,
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;
@@ -1,7 +1,9 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
- import { SpawnOptions, ChildProcess } from "child_process";
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 | ReadStream;
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) => void;
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
- const logEnv = log.envNames?.reduce((env, key) => {
38
- const value = options?.env?.[key];
39
- if (typeof value !== "undefined")
40
- env[key] = value;
41
- return env;
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
- return reject(new Error(`Current working directory does not exist: ${options.cwd}`));
53
- if (pipe?.onReadProgress && pipe.stream instanceof fs_2.ReadStream) {
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 fs_2.ReadStream) {
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);