@datatruck/cli 0.20.0 → 0.21.1

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.
@@ -19,7 +19,8 @@ export declare enum DefinitionEnum {
19
19
  sqlDumpTask = "sqldump-task",
20
20
  stringListUtil = "stringlist-util",
21
21
  prunePolicy = "prune-policy",
22
- pathsObject = "paths-object"
22
+ pathsObject = "paths-object",
23
+ compressUtil = "compress-util"
23
24
  }
24
25
  export declare function makeRef(type: DefinitionEnum, subType?: string): {
25
26
  $ref: string;
@@ -24,6 +24,7 @@ var DefinitionEnum;
24
24
  DefinitionEnum["stringListUtil"] = "stringlist-util";
25
25
  DefinitionEnum["prunePolicy"] = "prune-policy";
26
26
  DefinitionEnum["pathsObject"] = "paths-object";
27
+ DefinitionEnum["compressUtil"] = "compress-util";
27
28
  })(DefinitionEnum || (exports.DefinitionEnum = DefinitionEnum = {}));
28
29
  function makeRef(type, subType) {
29
30
  return {
@@ -17,6 +17,7 @@ const MysqlDumpTask_1 = require("../Task/MysqlDumpTask");
17
17
  const PostgresqlDumpTask_1 = require("../Task/PostgresqlDumpTask");
18
18
  const ScriptTask_1 = require("../Task/ScriptTask");
19
19
  const SqlDumpTaskAbstract_1 = require("../Task/SqlDumpTaskAbstract");
20
+ const tar_1 = require("../utils/tar");
20
21
  const DefinitionEnum_1 = require("./DefinitionEnum");
21
22
  exports.definitions = {
22
23
  [DefinitionEnum_1.DefinitionEnum.stringListUtil]: {
@@ -45,6 +46,7 @@ exports.definitions = {
45
46
  [DefinitionEnum_1.DefinitionEnum.config]: Config_1.configDefinition,
46
47
  [DefinitionEnum_1.DefinitionEnum.prunePolicy]: PrunePolicyConfig_1.prunePolicyConfigDefinition,
47
48
  [DefinitionEnum_1.DefinitionEnum.pathsObject]: PackageConfig_1.pathsObjectDefinition,
49
+ [DefinitionEnum_1.DefinitionEnum.compressUtil]: tar_1.compressDefinition,
48
50
  };
49
51
  for (const key in exports.definitions) {
50
52
  const schemaKey = key;
@@ -1,3 +1,4 @@
1
+ import { CompressOptions } from "../utils/tar";
1
2
  import { RepositoryAbstract, BackupDataType, InitDataType, RestoreDataType, SnapshotsDataType, SnapshotResultType, PruneDataType, CopyBackupType } from "./RepositoryAbstract";
2
3
  import type { JSONSchema7 } from "json-schema";
3
4
  export type MetaDataType = {
@@ -8,20 +9,23 @@ export type MetaDataType = {
8
9
  tags: string[];
9
10
  version: string;
10
11
  size: number;
12
+ tarStats?: Record<string, {
13
+ files: number;
14
+ }>;
11
15
  };
12
16
  export type DatatruckRepositoryConfigType = {
13
17
  outPath: string;
14
- compress?: boolean;
18
+ compress?: boolean | CompressOptions;
15
19
  };
16
20
  type PackObject = {
17
21
  name?: string;
18
- compress?: boolean;
22
+ compress?: boolean | CompressOptions;
19
23
  include: string[];
20
24
  exclude?: string[];
21
25
  onePackByResult?: boolean;
22
26
  };
23
27
  export type DatatruckPackageRepositoryConfigType = {
24
- compress?: boolean;
28
+ compress?: boolean | CompressOptions;
25
29
  packs?: PackObject[];
26
30
  };
27
31
  export declare const datatruckRepositoryName = "datatruck";
@@ -20,7 +20,9 @@ exports.datatruckRepositoryDefinition = {
20
20
  additionalProperties: false,
21
21
  properties: {
22
22
  outPath: { type: "string" },
23
- compress: { type: "boolean" },
23
+ compress: {
24
+ anyOf: [{ type: "boolean" }, (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.compressUtil)],
25
+ },
24
26
  },
25
27
  };
26
28
  exports.datatruckPackageRepositoryDefinition = {
@@ -28,7 +30,7 @@ exports.datatruckPackageRepositoryDefinition = {
28
30
  additionalProperties: false,
29
31
  properties: {
30
32
  compress: {
31
- type: "boolean",
33
+ anyOf: [{ type: "boolean" }, (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.compressUtil)],
32
34
  },
33
35
  packs: {
34
36
  type: "array",
@@ -38,6 +40,9 @@ exports.datatruckPackageRepositoryDefinition = {
38
40
  required: ["include"],
39
41
  properties: {
40
42
  name: { type: "string" },
43
+ compress: {
44
+ anyOf: [{ type: "boolean" }, (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.compressUtil)],
45
+ },
41
46
  include: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.stringListUtil),
42
47
  exclude: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.stringListUtil),
43
48
  onePackByResult: { type: "boolean" },
@@ -208,22 +213,26 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
208
213
  });
209
214
  await stream.end();
210
215
  let packIndex = 0;
216
+ const tarStats = {};
211
217
  for (const pack of packs) {
212
218
  const packBasename = [`pack`, packIndex.toString(), pack.name]
213
219
  .filter((v) => typeof v === "string")
214
220
  .map((v) => encodeURIComponent(v.toString().replace(/[\\/]/g, "-")))
215
- .join("-");
216
- const ext = pack.compress ? `.tar.gz` : `.tar`;
221
+ .join("-") + (pack.compress ? `.tar.gz` : `.tar`);
217
222
  const includeList = stream.path(packIndex);
218
- if (includeList)
223
+ if (includeList) {
224
+ tarStats[packBasename] = {
225
+ files: stream.lines(packIndex),
226
+ };
219
227
  await (0, tar_1.createTar)({
220
228
  compress: pack.compress,
221
229
  verbose: data.options.verbose,
222
230
  includeList,
223
231
  path: sourcePath,
224
- output: (0, path_1.join)(outPath, packBasename) + ext,
232
+ output: (0, path_1.join)(outPath, packBasename),
225
233
  onEntry: async (data) => await scanner.progress(pack.compress ? "Compressing" : "Packing", data.path),
226
234
  });
235
+ }
227
236
  packIndex++;
228
237
  }
229
238
  await scanner.end();
@@ -238,6 +247,7 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
238
247
  task: data.package.task?.name,
239
248
  version: nodePkg.version,
240
249
  size: await (0, fs_1.fastFolderSizeAsync)(outPath),
250
+ tarStats,
241
251
  };
242
252
  if (data.options.verbose)
243
253
  (0, cli_1.logExec)(`Writing metadata into ${metaPath}`);
@@ -293,6 +303,8 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
293
303
  packageName: data.package.name,
294
304
  });
295
305
  const sourcePath = (0, path_1.join)(this.config.outPath, snapshotName);
306
+ const metaPath = (0, path_1.join)(sourcePath, "meta.json");
307
+ const meta = await DatatruckRepository.parseMetaData(metaPath);
296
308
  const scanner = await (0, fs_1.createFileScanner)({
297
309
  onProgress: data.onProgress,
298
310
  glob: {
@@ -301,29 +313,40 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
301
313
  },
302
314
  });
303
315
  const tarFiles = [];
316
+ const tarStats = meta?.tarStats || {};
304
317
  await scanner.start(async (entry) => {
305
318
  const path = (0, path_1.join)(sourcePath, entry.name);
306
319
  const isTar = entry.name.endsWith(".tar");
307
320
  const isTarGz = entry.name.endsWith(".tar.gz");
308
321
  if (isTar || isTarGz) {
309
322
  tarFiles.push(path);
310
- await (0, tar_1.listTar)({
311
- input: path,
312
- verbose: data.options.verbose,
313
- onEntry: () => {
314
- scanner.total++;
315
- },
316
- });
323
+ if (typeof tarStats[entry.name]?.files === "number") {
324
+ scanner.total += tarStats[entry.name].files;
325
+ }
326
+ else {
327
+ scanner.progress("Scanning", entry.name, false);
328
+ const selfTarStats = (tarStats[entry.name] = { files: 0 });
329
+ await (0, tar_1.listTar)({
330
+ input: path,
331
+ verbose: data.options.verbose,
332
+ onEntry: () => {
333
+ scanner.total++;
334
+ selfTarStats.files++;
335
+ },
336
+ });
337
+ }
317
338
  }
318
339
  return false;
319
340
  });
320
341
  if (data.options.verbose)
321
342
  (0, cli_1.logExec)(`Unpacking files to ${restorePath}`);
322
343
  for (const tarFile of tarFiles) {
344
+ const entryName = (0, path_1.basename)(tarFile);
323
345
  await (0, tar_1.extractTar)({
346
+ total: tarStats[entryName].files,
324
347
  input: tarFile,
325
348
  output: restorePath,
326
- uncompress: tarFile.endsWith(".tar.gz"),
349
+ decompress: tarFile.endsWith(".tar.gz"),
327
350
  verbose: data.options.verbose,
328
351
  onEntry: async (data) => await scanner.progress(tarFile.endsWith(".tar.gz") ? "Extracting" : "Unpacking", data.path),
329
352
  });
@@ -258,7 +258,7 @@ class MariadbTask extends TaskAbstract_1.TaskAbstract {
258
258
  });
259
259
  await (0, tar_1.extractTar)({
260
260
  input: (0, path_1.join)(restorePath, zipFile),
261
- uncompress: true,
261
+ decompress: true,
262
262
  output: restorePath,
263
263
  verbose: this.verbose,
264
264
  async onEntry(item) {
@@ -4,7 +4,7 @@ export declare const mysqlDumpTaskName = "mysql-dump";
4
4
  export type MysqlDumpTaskConfigType = {} & SqlDumpTaskConfigType;
5
5
  export declare const mysqlDumpTaskDefinition: JSONSchema7;
6
6
  export declare class MysqlDumpTask extends SqlDumpTaskAbstract<MysqlDumpTaskConfigType> {
7
- buildConnectionArgs(database?: boolean): Promise<string[]>;
7
+ buildConnectionArgs(database?: string): Promise<string[]>;
8
8
  onDatabaseIsEmpty(name: string): Promise<boolean>;
9
9
  onCreateDatabase(database: TargetDatabaseType): Promise<void>;
10
10
  onExecQuery(query: string): Promise<import("../utils/process").ExecResultType>;
@@ -13,14 +13,14 @@ exports.mysqlDumpTaskDefinition = {
13
13
  allOf: [(0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.sqlDumpTask)],
14
14
  };
15
15
  class MysqlDumpTask extends SqlDumpTaskAbstract_1.SqlDumpTaskAbstract {
16
- async buildConnectionArgs(database = true) {
16
+ async buildConnectionArgs(database) {
17
17
  const password = await this.fetchPassword();
18
18
  return [
19
19
  `--host=${this.config.hostname}`,
20
20
  ...(this.config.port ? [`--port=${this.config.port}`] : []),
21
21
  `--user=${this.config.username}`,
22
22
  `--password=${password ?? ""}`,
23
- ...(database && this.config.database ? [this.config.database] : []),
23
+ ...(database ? [database] : []),
24
24
  ];
25
25
  }
26
26
  async onDatabaseIsEmpty(name) {
@@ -78,7 +78,7 @@ class MysqlDumpTask extends SqlDumpTaskAbstract_1.SqlDumpTaskAbstract {
78
78
  stream.on("error", reject);
79
79
  }),
80
80
  await (0, process_1.exec)("mysqldump", [
81
- ...(await this.buildConnectionArgs()),
81
+ ...(await this.buildConnectionArgs(this.config.database)),
82
82
  "--lock-tables=false",
83
83
  "--skip-add-drop-table=false",
84
84
  ...tableNames,
@@ -120,7 +120,7 @@ class MysqlDumpTask extends SqlDumpTaskAbstract_1.SqlDumpTaskAbstract {
120
120
  stream.on("error", reject);
121
121
  }),
122
122
  await (0, process_1.exec)("mysqldump", [
123
- ...(await this.buildConnectionArgs()),
123
+ ...(await this.buildConnectionArgs(this.config.database)),
124
124
  "--lock-tables=false",
125
125
  "--routines",
126
126
  "--events",
@@ -143,7 +143,7 @@ class MysqlDumpTask extends SqlDumpTaskAbstract_1.SqlDumpTaskAbstract {
143
143
  ]);
144
144
  }
145
145
  async onImport(path, database) {
146
- await (0, process_1.exec)("mysql", [...(await this.buildConnectionArgs(false)), database], null, {
146
+ await (0, process_1.exec)("mysql", await this.buildConnectionArgs(database), null, {
147
147
  pipe: {
148
148
  stream: (0, fs_3.createReadStream)(path),
149
149
  onReadProgress: (data) => {
@@ -81,7 +81,7 @@ class PostgresqlDumpTask extends SqlDumpTaskAbstract_1.SqlDumpTaskAbstract {
81
81
  stream.on("error", reject);
82
82
  }),
83
83
  (0, process_1.exec)("pg_dump", [
84
- ...(await this.buildConnectionArgs()),
84
+ ...(await this.buildConnectionArgs(this.config.database)),
85
85
  ...(tableNames?.flatMap((v) => ["-t", v]) ?? []),
86
86
  ], null, {
87
87
  pipe: { stream, onWriteProgress: onProgress },
@@ -449,7 +449,14 @@
449
449
  "type": "string"
450
450
  },
451
451
  "compress": {
452
- "type": "boolean"
452
+ "anyOf": [
453
+ {
454
+ "type": "boolean"
455
+ },
456
+ {
457
+ "$ref": "#/definitions/compress-util"
458
+ }
459
+ ]
453
460
  }
454
461
  }
455
462
  },
@@ -458,7 +465,14 @@
458
465
  "additionalProperties": false,
459
466
  "properties": {
460
467
  "compress": {
461
- "type": "boolean"
468
+ "anyOf": [
469
+ {
470
+ "type": "boolean"
471
+ },
472
+ {
473
+ "$ref": "#/definitions/compress-util"
474
+ }
475
+ ]
462
476
  },
463
477
  "packs": {
464
478
  "type": "array",
@@ -472,6 +486,16 @@
472
486
  "name": {
473
487
  "type": "string"
474
488
  },
489
+ "compress": {
490
+ "anyOf": [
491
+ {
492
+ "type": "boolean"
493
+ },
494
+ {
495
+ "$ref": "#/definitions/compress-util"
496
+ }
497
+ ]
498
+ },
475
499
  "include": {
476
500
  "$ref": "#/definitions/stringlist-util"
477
501
  },
@@ -975,6 +999,33 @@
975
999
  }
976
1000
  ]
977
1001
  },
1002
+ "compress-util": {
1003
+ "type": "object",
1004
+ "additionalProperties": false,
1005
+ "properties": {
1006
+ "level": {
1007
+ "type": "integer"
1008
+ },
1009
+ "cores": {
1010
+ "anyOf": [
1011
+ {
1012
+ "type": "integer"
1013
+ },
1014
+ {
1015
+ "type": "object",
1016
+ "required": [
1017
+ "percent"
1018
+ ],
1019
+ "properties": {
1020
+ "percent": {
1021
+ "type": "integer"
1022
+ }
1023
+ }
1024
+ }
1025
+ ]
1026
+ }
1027
+ }
1028
+ },
978
1029
  "script-task_step": {
979
1030
  "type": "object",
980
1031
  "required": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datatruck/cli",
3
- "version": "0.20.0",
3
+ "version": "0.21.1",
4
4
  "dependencies": {
5
5
  "ajv": "^8.12.0",
6
6
  "async": "^3.2.4",
package/utils/fs.d.ts CHANGED
@@ -96,12 +96,13 @@ export declare function createFileScanner(options: {
96
96
  disposed: boolean;
97
97
  total: number;
98
98
  current: number;
99
- progress: (description: string, path?: string) => Promise<void>;
99
+ progress: (description: string, path?: string, increment?: boolean) => Promise<void>;
100
100
  end: () => Promise<void>;
101
101
  start: (cb?: ((entry: Required<Entry>) => any) | undefined) => Promise<void>;
102
102
  }>;
103
103
  type StreamItem = {
104
104
  key: string;
105
+ lines: number;
105
106
  stream: WriteStream;
106
107
  finished: boolean;
107
108
  error?: Error;
@@ -113,6 +114,7 @@ export declare function createWriteStreamPool(options: {
113
114
  }): {
114
115
  pool: Record<string, StreamItem>;
115
116
  path(key: string | number): string | undefined;
117
+ lines(key: string | number): number;
116
118
  writeLine(key: string | number, v: string): boolean;
117
119
  end(): Promise<void>;
118
120
  };
package/utils/fs.js CHANGED
@@ -410,10 +410,10 @@ async function createFileScanner(options) {
410
410
  disposed: false,
411
411
  total: 0,
412
412
  current: 0,
413
- progress: async (description, path) => {
413
+ progress: async (description, path, increment = true) => {
414
414
  if (object.disposed)
415
415
  return;
416
- if (path)
416
+ if (path && increment)
417
417
  object.current++;
418
418
  await options.onProgress({
419
419
  relative: {
@@ -463,6 +463,7 @@ function createWriteStreamPool(options) {
463
463
  const create = (key) => {
464
464
  const item = {
465
465
  key,
466
+ lines: 0,
466
467
  stream: (0, fs_2.createWriteStream)((0, path_2.join)(options.path, options.onStreamPath ? options.onStreamPath(key) : key)),
467
468
  finished: false,
468
469
  };
@@ -484,16 +485,22 @@ function createWriteStreamPool(options) {
484
485
  throw new Error(`Stream path is not defined: ${key}`);
485
486
  return item.stream.path;
486
487
  },
488
+ lines(key) {
489
+ const item = pool[key];
490
+ return item?.lines;
491
+ },
487
492
  writeLine(key, v) {
488
493
  const item = pool[key] || create(key.toString());
489
494
  if (item.finished) {
490
495
  return false;
491
496
  }
492
497
  else if (item.written) {
498
+ item.lines++;
493
499
  return item.stream.write(`\n${v}`);
494
500
  }
495
501
  else {
496
502
  item.written = true;
503
+ item.lines++;
497
504
  return item.stream.write(`${v}`);
498
505
  }
499
506
  },
package/utils/tar.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { JSONSchema7 } from "json-schema";
1
2
  export type Progress = {
2
3
  percent: number;
3
4
  current: number;
@@ -7,22 +8,39 @@ export type TarEntry = {
7
8
  path: string;
8
9
  progress: Progress;
9
10
  };
11
+ export type CoresOptions = number | {
12
+ percent: number;
13
+ };
14
+ export type CompressOptions = {
15
+ level?: number;
16
+ /**
17
+ * @default {percent:50}
18
+ */
19
+ cores?: CoresOptions;
20
+ };
21
+ export type DecompressOptions = {
22
+ /**
23
+ * @default {percent:50}
24
+ */
25
+ cores?: CoresOptions;
26
+ };
10
27
  export interface CreateTarOptions {
11
28
  path: string;
12
29
  verbose?: boolean;
13
30
  output: string;
14
31
  includeList: string;
15
- compress?: boolean;
32
+ compress?: boolean | CompressOptions;
16
33
  onEntry?: (entry: TarEntry) => void;
17
34
  }
18
35
  export interface ExtractOptions {
19
36
  input: string;
20
37
  output: string;
21
38
  verbose?: boolean;
22
- uncompress?: boolean;
39
+ decompress?: boolean | DecompressOptions;
23
40
  total?: number;
24
41
  onEntry?: (entry: TarEntry) => void;
25
42
  }
43
+ export declare const compressDefinition: JSONSchema7;
26
44
  export type TarVendor = "busybox" | "bsdtar" | "gnu";
27
45
  export declare function getTarVendor(cache?: boolean, log?: boolean): Promise<TarVendor | null>;
28
46
  export type ListTarOptions = {
@@ -31,5 +49,6 @@ export type ListTarOptions = {
31
49
  verbose?: boolean;
32
50
  };
33
51
  export declare function listTar(options: ListTarOptions): Promise<number>;
52
+ export declare function checkPigzLib(cache?: boolean): Promise<boolean>;
34
53
  export declare function createTar(options: CreateTarOptions): Promise<void>;
35
54
  export declare function extractTar(options: ExtractOptions): Promise<void>;
package/utils/tar.js CHANGED
@@ -1,12 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractTar = exports.createTar = exports.listTar = exports.getTarVendor = void 0;
3
+ exports.extractTar = exports.createTar = exports.checkPigzLib = exports.listTar = exports.getTarVendor = exports.compressDefinition = void 0;
4
4
  const cli_1 = require("./cli");
5
5
  const fs_1 = require("./fs");
6
6
  const math_1 = require("./math");
7
7
  const process_1 = require("./process");
8
8
  const promises_1 = require("fs/promises");
9
9
  const os_1 = require("os");
10
+ exports.compressDefinition = {
11
+ type: "object",
12
+ additionalProperties: false,
13
+ properties: {
14
+ level: { type: "integer" },
15
+ cores: {
16
+ anyOf: [
17
+ { type: "integer" },
18
+ {
19
+ type: "object",
20
+ required: ["percent"],
21
+ properties: { percent: { type: "integer" } },
22
+ },
23
+ ],
24
+ },
25
+ },
26
+ };
10
27
  let tarVendor;
11
28
  async function getTarVendor(cache = true, log = false) {
12
29
  if (cache && typeof tarVendor !== "undefined")
@@ -49,37 +66,94 @@ async function listTar(options) {
49
66
  return total;
50
67
  }
51
68
  exports.listTar = listTar;
69
+ let pigzLib;
70
+ async function checkPigzLib(cache = true) {
71
+ if (cache && pigzLib !== undefined)
72
+ return pigzLib;
73
+ try {
74
+ return !(await (0, process_1.exec)("pigz", ["-V"])).exitCode;
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ exports.checkPigzLib = checkPigzLib;
81
+ async function resolveCores(input) {
82
+ if (!(await checkPigzLib()))
83
+ return 1;
84
+ const total = (0, os_1.cpus)().length;
85
+ return Math.min(total, typeof input === "number"
86
+ ? input
87
+ : Math.max(0, Math.round(((input?.percent || 50) * total) / 100)));
88
+ }
89
+ async function ifX(input, cb) {
90
+ return input ? await cb((input === true ? {} : input)) : undefined;
91
+ }
52
92
  async function createTar(options) {
53
93
  const vendor = await getTarVendor(true, options.verbose);
54
94
  const total = await (0, fs_1.countFileLines)(options.includeList);
95
+ const compress = await ifX(options.compress, async (compress) => ({
96
+ ...compress,
97
+ cores: await resolveCores(compress.cores),
98
+ }));
55
99
  let current = 0;
100
+ const env = {
101
+ ...(compress?.cores === 1 &&
102
+ compress.level && {
103
+ GZIP_OPT: compress.level.toString(),
104
+ }),
105
+ };
56
106
  await (0, process_1.exec)("tar", [
57
107
  "-C",
58
108
  toLocalPath(options.path),
59
- options.compress ? "-czvf" : "-cvf",
109
+ compress?.cores === 1 ? "-czvf" : "-cvf",
60
110
  toLocalPath(options.output),
61
111
  "-T",
62
112
  toLocalPath(options.includeList),
63
113
  "--ignore-failed-read",
64
114
  "--force-local",
65
- ], {}, {
66
- log: options.verbose,
67
- stderr: { toExitCode: true },
68
- stdout: {
69
- parseLines: "skip-empty",
70
- onData: (line) => {
71
- current++;
72
- const path = vendor === "bsdtar" ? line.slice(2) : line;
73
- options.onEntry?.({
74
- path,
75
- progress: {
76
- total,
77
- current,
78
- percent: (0, math_1.progressPercent)(total, current),
79
- },
80
- });
81
- },
115
+ ...(compress && compress.cores > 1
116
+ ? [
117
+ "-I",
118
+ `"${[
119
+ "pigz",
120
+ "-r",
121
+ !!compress.level && `-${compress.level}`,
122
+ `-p ${compress.cores}`,
123
+ ]
124
+ .filter(Boolean)
125
+ .join(" ")}"`,
126
+ ]
127
+ : []),
128
+ ], {
129
+ ...(compress &&
130
+ compress.cores > 1 && {
131
+ shell: true,
132
+ }),
133
+ env: {
134
+ ...process.env,
135
+ ...env,
82
136
  },
137
+ }, {
138
+ log: options.verbose ? { envNames: Object.keys(env) } : false,
139
+ stderr: { toExitCode: true },
140
+ stdout: options.onEntry
141
+ ? {
142
+ parseLines: "skip-empty",
143
+ onData: (line) => {
144
+ current++;
145
+ const path = vendor === "bsdtar" ? line.slice(2) : line;
146
+ options.onEntry?.({
147
+ path,
148
+ progress: {
149
+ total,
150
+ current,
151
+ percent: (0, math_1.progressPercent)(total, current),
152
+ },
153
+ });
154
+ },
155
+ }
156
+ : undefined,
83
157
  });
84
158
  }
85
159
  exports.createTar = createTar;
@@ -92,36 +166,51 @@ function toLocalPath(path) {
92
166
  async function extractTar(options) {
93
167
  let total = options.total ??
94
168
  (await listTar({ input: options.input, verbose: options.verbose }));
95
- if (options.verbose)
96
- (0, cli_1.logExec)("mkdir", ["-p", options.output]);
97
- await (0, promises_1.mkdir)(options.output, { recursive: true });
98
- await (0, fs_1.ensureEmptyDir)(options.output);
169
+ if (!(await (0, fs_1.existsDir)(options.output))) {
170
+ if (options.verbose)
171
+ (0, cli_1.logExec)("mkdir", ["-p", options.output]);
172
+ await (0, promises_1.mkdir)(options.output, { recursive: true });
173
+ }
174
+ const decompress = await ifX(options.decompress, async (decompress) => ({
175
+ ...decompress,
176
+ cores: await resolveCores(decompress.cores),
177
+ }));
99
178
  let current = 0;
100
179
  await (0, process_1.exec)("tar", [
101
- options.uncompress ? "-xzvpf" : "-xvpf",
180
+ decompress?.cores === 1 ? "-xzvpf" : "-xvpf",
102
181
  toLocalPath(options.input),
103
182
  "-C",
104
183
  toLocalPath(options.output),
105
184
  "--force-local",
106
- ], {}, {
185
+ ...(decompress && decompress.cores > 1
186
+ ? ["-I", `"pigz -p ${decompress.cores}"`]
187
+ : []),
188
+ ], {
189
+ ...(decompress &&
190
+ decompress.cores > 1 && {
191
+ shell: true,
192
+ }),
193
+ }, {
107
194
  log: options.verbose,
108
195
  stderr: {
109
196
  toExitCode: true,
110
197
  },
111
- stdout: {
112
- parseLines: "skip-empty",
113
- onData: (path) => {
114
- current++;
115
- options.onEntry?.({
116
- path,
117
- progress: {
118
- total,
119
- current,
120
- percent: (0, math_1.progressPercent)(total, current),
121
- },
122
- });
123
- },
124
- },
198
+ stdout: options.onEntry
199
+ ? {
200
+ parseLines: "skip-empty",
201
+ onData: (path) => {
202
+ current++;
203
+ options.onEntry?.({
204
+ path,
205
+ progress: {
206
+ total,
207
+ current,
208
+ percent: (0, math_1.progressPercent)(total, current),
209
+ },
210
+ });
211
+ },
212
+ }
213
+ : undefined,
125
214
  });
126
215
  }
127
216
  exports.extractTar = extractTar;