@datatruck/cli 0.19.0 → 0.21.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.
@@ -149,7 +149,7 @@ class RestoreAction {
149
149
  if (typeof pkg.restorePath !== "string")
150
150
  throw new AppError_1.AppError("Restore path is not defined");
151
151
  await (0, fs_1.mkdirIfNotExists)(pkg.restorePath);
152
- if (!(await (0, fs_1.isDirEmpty)(pkg.restorePath)))
152
+ if (!(await (0, fs_1.isEmptyDir)(pkg.restorePath)))
153
153
  throw new AppError_1.AppError(`Restore path is not empty: ${pkg.restorePath}`);
154
154
  if (this.options.verbose)
155
155
  (0, cli_1.logExec)(`restorePath=${pkg.restorePath}`);
@@ -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 = {
@@ -11,17 +12,17 @@ export type MetaDataType = {
11
12
  };
12
13
  export type DatatruckRepositoryConfigType = {
13
14
  outPath: string;
14
- compress?: boolean;
15
+ compress?: boolean | CompressOptions;
15
16
  };
16
17
  type PackObject = {
17
18
  name?: string;
18
- compress?: boolean;
19
+ compress?: boolean | CompressOptions;
19
20
  include: string[];
20
21
  exclude?: string[];
21
22
  onePackByResult?: boolean;
22
23
  };
23
24
  export type DatatruckPackageRepositoryConfigType = {
24
- compress?: boolean;
25
+ compress?: boolean | CompressOptions;
25
26
  packs?: PackObject[];
26
27
  };
27
28
  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" },
@@ -98,13 +103,13 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
98
103
  const snapshotPath = (0, path_1.join)(this.config.outPath, snapshotName);
99
104
  if (data.options.verbose)
100
105
  (0, cli_1.logExec)(`Deleting ${snapshotPath}`);
101
- if (await (0, fs_1.checkDir)(snapshotPath))
106
+ if (await (0, fs_1.existsDir)(snapshotPath))
102
107
  await (0, promises_1.rm)(snapshotPath, {
103
108
  recursive: true,
104
109
  });
105
110
  }
106
111
  async onSnapshots(data) {
107
- if (!(await (0, fs_1.checkDir)(this.config.outPath)))
112
+ if (!(await (0, fs_1.existsDir)(this.config.outPath)))
108
113
  throw new Error(`Repository (${this.repository.name}) out path does not exist: ${this.config.outPath}`);
109
114
  const snapshotNames = await (0, fs_1.readDir)(this.config.outPath);
110
115
  const snapshots = [];
@@ -175,49 +180,56 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
175
180
  const configPacks = (data.packageConfig?.packs ?? []).map((p) => ({
176
181
  ...p,
177
182
  compress: p.compress ?? data.packageConfig?.compress ?? this.config.compress,
178
- includeResult: [],
179
183
  }));
180
184
  const defaultsPack = {
181
185
  name: "defaults",
182
186
  compress: data.packageConfig?.compress ?? this.config.compress,
183
187
  include: [],
184
- includeResult: [],
185
188
  };
186
189
  const packs = [...configPacks, defaultsPack];
190
+ const defaultsPackIndex = packs.findIndex((p) => p === defaultsPack);
191
+ const stream = (0, fs_1.createWriteStreamPool)({
192
+ path: await this.mkTmpDir("files"),
193
+ onStreamPath: (key) => `files-${key}.txt`,
194
+ });
187
195
  await scanner.start(async (entry) => {
188
196
  if (entry.dirent.isDirectory() &&
189
197
  !(await (0, fs_1.isEmptyDir)((0, path_1.join)(sourcePath, entry.path))))
190
198
  return false;
191
- const pack = configPacks.find((pack) => (0, string_1.checkPath)(entry.path, pack.include, pack.exclude)) || defaultsPack;
199
+ let packIndex = configPacks.findIndex((pack) => (0, string_1.checkPath)(entry.path, pack.include, pack.exclude));
200
+ if (packIndex === -1)
201
+ packIndex = defaultsPackIndex;
202
+ const pack = packs[packIndex];
192
203
  if (pack.onePackByResult) {
193
204
  const subname = (0, path_1.basename)(entry.path);
194
205
  packs.push({
195
206
  ...pack,
196
207
  name: pack.name ? `${pack.name}-${subname}` : subname,
197
- includeResult: [entry.path],
198
208
  });
209
+ packIndex = packs.length - 1;
199
210
  }
200
- else {
201
- pack.includeResult.push(entry.path);
202
- }
211
+ stream.writeLine(packIndex, entry.path);
203
212
  return true;
204
213
  });
214
+ await stream.end();
205
215
  let packIndex = 0;
206
216
  for (const pack of packs) {
207
- const packBasename = [`pack`, (packIndex++).toString(), pack.name]
217
+ const packBasename = [`pack`, packIndex.toString(), pack.name]
208
218
  .filter((v) => typeof v === "string")
209
219
  .map((v) => encodeURIComponent(v.toString().replace(/[\\/]/g, "-")))
210
220
  .join("-");
211
221
  const ext = pack.compress ? `.tar.gz` : `.tar`;
212
- if (pack.includeResult.length)
222
+ const includeList = stream.path(packIndex);
223
+ if (includeList)
213
224
  await (0, tar_1.createTar)({
214
225
  compress: pack.compress,
215
226
  verbose: data.options.verbose,
216
- include: pack.includeResult,
227
+ includeList,
217
228
  path: sourcePath,
218
229
  output: (0, path_1.join)(outPath, packBasename) + ext,
219
230
  onEntry: async (data) => await scanner.progress(pack.compress ? "Compressing" : "Packing", data.path),
220
231
  });
232
+ packIndex++;
221
233
  }
222
234
  await scanner.end();
223
235
  // Meta
@@ -316,6 +328,7 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
316
328
  await (0, tar_1.extractTar)({
317
329
  input: tarFile,
318
330
  output: restorePath,
331
+ decompress: tarFile.endsWith(".tar.gz"),
319
332
  verbose: data.options.verbose,
320
333
  onEntry: async (data) => await scanner.progress(tarFile.endsWith(".tar.gz") ? "Extracting" : "Unpacking", data.path),
321
334
  });
package/Task/GitTask.js CHANGED
@@ -231,14 +231,14 @@ class GitTask extends TaskAbstract_1.TaskAbstract {
231
231
  await incrementProgress();
232
232
  // Config
233
233
  const configPath = (0, path_1.join)(targetPath, "repo.config");
234
- if (await (0, fs_1.checkFile)(configPath)) {
234
+ if (await (0, fs_1.existsFile)(configPath)) {
235
235
  await (0, promises_1.copyFile)(configPath, (0, path_1.join)(restorePath, ".git", "config"));
236
236
  await incrementProgress();
237
237
  }
238
238
  // ls-files
239
239
  for (const name of ["untracked", "modified", "ignored"]) {
240
240
  const sourcePath = (0, path_1.join)(targetPath, `repo.${name}`);
241
- if (await (0, fs_1.checkDir)(sourcePath)) {
241
+ if (await (0, fs_1.existsDir)(sourcePath)) {
242
242
  if (data.options.verbose)
243
243
  (0, cli_1.logExec)(`Copying ${name} files to ${restorePath}`);
244
244
  await (0, fs_1.cpy)({
@@ -258,6 +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
+ decompress: true,
261
262
  output: restorePath,
262
263
  verbose: this.verbose,
263
264
  async onEntry(item) {
@@ -115,7 +115,7 @@ class SqlDumpTaskAbstract extends TaskAbstract_1.TaskAbstract {
115
115
  return true;
116
116
  });
117
117
  (0, assert_1.ok)(typeof outputPath === "string");
118
- if (!(await (0, fs_1.checkDir)(outputPath)))
118
+ if (!(await (0, fs_1.existsDir)(outputPath)))
119
119
  await (0, promises_1.mkdir)(outputPath, { recursive: true });
120
120
  if (!this.config.oneFileByTable) {
121
121
  const outPath = (0, path_1.join)(outputPath, serializeSqlFile({ database: this.config.database }));
@@ -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.19.0",
3
+ "version": "0.21.0",
4
4
  "dependencies": {
5
5
  "ajv": "^8.12.0",
6
6
  "async": "^3.2.4",
@@ -13,8 +13,7 @@
13
13
  "micromatch": "^4.0.5",
14
14
  "pretty-bytes": "^5.6.0",
15
15
  "sqlite": "^5.0.1",
16
- "sqlite3": "^5.1.6",
17
- "tar": "^6.2.0"
16
+ "sqlite3": "^5.1.6"
18
17
  },
19
18
  "optionalDependencies": {
20
19
  "ts-node": "^10.9.1",
package/utils/Git.js CHANGED
@@ -16,7 +16,7 @@ class Git {
16
16
  }
17
17
  async canBeInit(repo) {
18
18
  return ((0, fs_1.isLocalDir)(repo) &&
19
- (!(await (0, fs_1.checkDir)(repo)) || !(await (0, fs_1.readDir)(repo)).length));
19
+ (!(await (0, fs_1.existsDir)(repo)) || !(await (0, fs_1.readDir)(repo)).length));
20
20
  }
21
21
  async clone(options) {
22
22
  return await this.exec([
@@ -9,7 +9,11 @@ async function parsePaths(values, options) {
9
9
  paths.push(value);
10
10
  }
11
11
  else if (value.type === "spawn") {
12
- const spawnResult = await (0, process_1.exec)(value.command, value.args, { cwd: options.cwd }, { log: options.verbose, stderr: { save: true }, stdout: { save: true } });
12
+ const spawnResult = await (0, process_1.exec)(value.command, value.args, { cwd: options.cwd }, {
13
+ log: options.verbose,
14
+ stderr: { save: true },
15
+ stdout: { save: true },
16
+ });
13
17
  const spawnFiles = [spawnResult.stderr, spawnResult.stdout].flatMap((text) => text
14
18
  .split(/\r?\n/)
15
19
  .map((v) => v.trim())
package/utils/fs.d.ts CHANGED
@@ -1,29 +1,20 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
- /// <reference types="node" />
5
4
  import { Progress } from "./progress";
6
5
  import { Entry, Options } from "fast-glob";
7
- import { Dirent, ReadStream, Stats } from "fs";
6
+ import { ReadStream, Stats } from "fs";
8
7
  import { WriteStream } from "fs";
9
8
  import { Interface } from "readline";
10
9
  export declare const isWSLSystem: boolean;
11
10
  export declare function isEmptyDir(path: string): Promise<boolean>;
12
- type EntryObject = {
13
- name: string;
14
- path: string;
15
- dirent: Dirent;
16
- stats: Stats;
17
- };
18
- export declare function applyPermissions(baseDir: string, permissionsPath: string): Promise<void>;
19
- export declare function pathIterator(stream: AsyncIterable<string | Buffer>): AsyncIterable<EntryObject>;
20
11
  export declare function isLocalDir(path: string): boolean;
21
- export declare function isDirEmpty(path: string): Promise<boolean>;
22
12
  export declare function mkdirIfNotExists(path: string): Promise<string>;
23
13
  export declare function ensureEmptyDir(path: string): Promise<void>;
14
+ export declare function safeStat(path: string): Promise<Stats | undefined>;
24
15
  export declare function existsDir(path: string): Promise<boolean>;
16
+ export declare function existsFile(path: string): Promise<boolean>;
25
17
  export declare function writeJSONFile<T = any>(path: string, json: T): Promise<void>;
26
- export declare function readdirIfExists(path: string): Promise<string[]>;
27
18
  export declare const parseFileExtensions: string[];
28
19
  export declare function parseFile(path: string, jsKey?: string): Promise<any>;
29
20
  export declare function parsePackageFile(): {
@@ -32,7 +23,6 @@ export declare function parsePackageFile(): {
32
23
  description: string;
33
24
  };
34
25
  export declare function findFile(sourcePath: string, baseName: string, extensions: string[], errorMessage?: string): Promise<string>;
35
- export declare function existsFile(path: string): Promise<boolean>;
36
26
  export declare function parentTmpDir(): string;
37
27
  export declare function sessionTmpDir(): string;
38
28
  export declare function isTmpDir(path: string): boolean;
@@ -41,8 +31,6 @@ export declare function tmpDir(prefix: string, id?: string): string;
41
31
  export declare function fastFolderSizeAsync(path: string): Promise<number>;
42
32
  export declare function mkTmpDir(prefix: string, id?: string): Promise<string>;
43
33
  export declare function readPartialFile(path: string, positions: [number, number?]): Promise<string>;
44
- export declare function checkFile(path: string): Promise<boolean>;
45
- export declare function checkDir(path: string): Promise<boolean>;
46
34
  export declare function readDir(path: string, optional?: boolean): Promise<string[]>;
47
35
  export declare function forEachFile(dirPath: string, cb: (path: string, dir: boolean) => void, includeDir?: boolean): Promise<void>;
48
36
  /**
@@ -112,4 +100,21 @@ export declare function createFileScanner(options: {
112
100
  end: () => Promise<void>;
113
101
  start: (cb?: ((entry: Required<Entry>) => any) | undefined) => Promise<void>;
114
102
  }>;
103
+ type StreamItem = {
104
+ key: string;
105
+ stream: WriteStream;
106
+ finished: boolean;
107
+ error?: Error;
108
+ written?: boolean;
109
+ };
110
+ export declare function createWriteStreamPool(options: {
111
+ path: string;
112
+ onStreamPath?: (key: string) => string;
113
+ }): {
114
+ pool: Record<string, StreamItem>;
115
+ path(key: string | number): string | undefined;
116
+ writeLine(key: string | number, v: string): boolean;
117
+ end(): Promise<void>;
118
+ };
119
+ export declare function countFileLines(path: string): Promise<number>;
115
120
  export {};
package/utils/fs.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.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;
6
+ exports.countFileLines = exports.createWriteStreamPool = exports.createFileScanner = exports.cpy = exports.isNotFoundError = exports.updateFileStats = exports.copyFileWithStreams = exports.waitForClose = exports.writeGitIgnoreList = exports.fastglobToGitIgnore = exports.forEachFile = exports.readDir = exports.readPartialFile = exports.mkTmpDir = exports.fastFolderSizeAsync = exports.tmpDir = exports.rmTmpDir = exports.isTmpDir = exports.sessionTmpDir = exports.parentTmpDir = exports.findFile = exports.parsePackageFile = exports.parseFile = exports.parseFileExtensions = exports.writeJSONFile = exports.existsFile = exports.existsDir = exports.safeStat = exports.ensureEmptyDir = exports.mkdirIfNotExists = exports.isLocalDir = exports.isEmptyDir = exports.isWSLSystem = void 0;
7
7
  const globalData_1 = __importDefault(require("../globalData"));
8
8
  const math_1 = require("./math");
9
9
  const path_1 = require("./path");
@@ -35,37 +35,13 @@ async function isEmptyDir(path) {
35
35
  }
36
36
  }
37
37
  exports.isEmptyDir = isEmptyDir;
38
- async function applyPermissions(baseDir, permissionsPath) {
39
- const singleReader = (0, readline_1.createInterface)({
40
- input: (0, fs_1.createReadStream)(permissionsPath),
41
- });
42
- for await (const line of singleReader) {
43
- const [rpath, rawUid, rawGui, rawMode] = line.split(":");
44
- const path = (0, path_2.join)(baseDir, rpath);
45
- if (!path.startsWith(baseDir)) {
46
- throw new Error(`Entry path is out of the base dir: (${path}, ${baseDir})`);
47
- }
48
- const uid = Number(rawUid);
49
- const guid = Number(rawGui);
50
- await (0, promises_1.chown)(path, uid, guid);
51
- const mode = Number(rawMode);
52
- await (0, promises_1.chmod)(path, mode);
53
- }
54
- }
55
- exports.applyPermissions = applyPermissions;
56
38
  function pathIterator(stream) {
57
39
  return stream;
58
40
  }
59
- exports.pathIterator = pathIterator;
60
41
  function isLocalDir(path) {
61
42
  return /^[\/\.]|([A-Z]:)/i.test(path);
62
43
  }
63
44
  exports.isLocalDir = isLocalDir;
64
- async function isDirEmpty(path) {
65
- const files = await readDir(path);
66
- return !files.length;
67
- }
68
- exports.isDirEmpty = isDirEmpty;
69
45
  async function mkdirIfNotExists(path) {
70
46
  await (0, promises_1.mkdir)(path, {
71
47
  recursive: true,
@@ -74,28 +50,29 @@ async function mkdirIfNotExists(path) {
74
50
  }
75
51
  exports.mkdirIfNotExists = mkdirIfNotExists;
76
52
  async function ensureEmptyDir(path) {
77
- if (!(await isDirEmpty(path)))
53
+ if (!(await isEmptyDir(path)))
78
54
  throw new Error(`Dir is not empty: ${path}`);
79
55
  }
80
56
  exports.ensureEmptyDir = ensureEmptyDir;
81
- async function existsDir(path) {
57
+ async function safeStat(path) {
82
58
  try {
83
- const info = await (0, promises_1.stat)(path);
84
- return info.isDirectory();
85
- }
86
- catch (e) {
87
- return false;
59
+ return await (0, promises_1.stat)(path);
88
60
  }
61
+ catch (e) { }
62
+ }
63
+ exports.safeStat = safeStat;
64
+ async function existsDir(path) {
65
+ return (await safeStat(path))?.isDirectory() ?? false;
89
66
  }
90
67
  exports.existsDir = existsDir;
68
+ async function existsFile(path) {
69
+ return (await safeStat(path))?.isFile() ?? false;
70
+ }
71
+ exports.existsFile = existsFile;
91
72
  async function writeJSONFile(path, json) {
92
73
  await (0, promises_1.writeFile)(path, JSON.stringify(json));
93
74
  }
94
75
  exports.writeJSONFile = writeJSONFile;
95
- async function readdirIfExists(path) {
96
- return await readDir(path, true);
97
- }
98
- exports.readdirIfExists = readdirIfExists;
99
76
  exports.parseFileExtensions = ["json", "js", "ts", "yaml", "yml"];
100
77
  async function parseFile(path, jsKey) {
101
78
  if (!(0, path_3.isAbsolute)(path))
@@ -140,16 +117,6 @@ async function findFile(sourcePath, baseName, extensions, errorMessage = "Path n
140
117
  return path;
141
118
  }
142
119
  exports.findFile = findFile;
143
- async function existsFile(path) {
144
- try {
145
- const info = await (0, promises_1.stat)(path);
146
- return info.isFile();
147
- }
148
- catch (e) {
149
- return false;
150
- }
151
- }
152
- exports.existsFile = existsFile;
153
120
  function parentTmpDir() {
154
121
  return (0, path_2.join)(globalData_1.default.tempDir, "datatruck-temp");
155
122
  }
@@ -219,24 +186,6 @@ async function readPartialFile(path, positions) {
219
186
  });
220
187
  }
221
188
  exports.readPartialFile = readPartialFile;
222
- async function checkFile(path) {
223
- try {
224
- return (await (0, promises_1.stat)(path)).isFile();
225
- }
226
- catch (e) {
227
- return false;
228
- }
229
- }
230
- exports.checkFile = checkFile;
231
- async function checkDir(path) {
232
- try {
233
- return (await (0, promises_1.stat)(path)).isDirectory();
234
- }
235
- catch (e) {
236
- return false;
237
- }
238
- }
239
- exports.checkDir = checkDir;
240
189
  async function readDir(path, optional) {
241
190
  try {
242
191
  return await (0, promises_1.readdir)(path);
@@ -509,3 +458,74 @@ async function createFileScanner(options) {
509
458
  return object;
510
459
  }
511
460
  exports.createFileScanner = createFileScanner;
461
+ function createWriteStreamPool(options) {
462
+ const pool = {};
463
+ const create = (key) => {
464
+ const item = {
465
+ key,
466
+ stream: (0, fs_2.createWriteStream)((0, path_2.join)(options.path, options.onStreamPath ? options.onStreamPath(key) : key)),
467
+ finished: false,
468
+ };
469
+ item.stream
470
+ .once("error", (error) => {
471
+ item.finished = true;
472
+ item.error = error;
473
+ })
474
+ .once("close", () => (item.finished = true));
475
+ return (pool[item.key] = item);
476
+ };
477
+ return {
478
+ pool,
479
+ path(key) {
480
+ const item = pool[key];
481
+ if (!item)
482
+ return;
483
+ if (typeof item.stream.path !== "string")
484
+ throw new Error(`Stream path is not defined: ${key}`);
485
+ return item.stream.path;
486
+ },
487
+ writeLine(key, v) {
488
+ const item = pool[key] || create(key.toString());
489
+ if (item.finished) {
490
+ return false;
491
+ }
492
+ else if (item.written) {
493
+ return item.stream.write(`\n${v}`);
494
+ }
495
+ else {
496
+ item.written = true;
497
+ return item.stream.write(`${v}`);
498
+ }
499
+ },
500
+ async end() {
501
+ const items = Object.values(pool);
502
+ for (const item of items)
503
+ if (!item.finished)
504
+ item.stream.end();
505
+ const itemWithErrors = items.filter((v) => v.error);
506
+ if (itemWithErrors.length) {
507
+ const keys = itemWithErrors.map((item) => item.key);
508
+ throw new AggregateError(itemWithErrors.map((item) => item.error), `Streams faileds: ${keys.join(", ")}`);
509
+ }
510
+ await Promise.all(items
511
+ .filter((item) => !item.finished)
512
+ .map((item) => waitForClose(item.stream)));
513
+ },
514
+ };
515
+ }
516
+ exports.createWriteStreamPool = createWriteStreamPool;
517
+ function countFileLines(path) {
518
+ let lines = 0;
519
+ const rl = (0, readline_1.createInterface)({
520
+ input: (0, fs_1.createReadStream)(path),
521
+ });
522
+ return new Promise((resolve, reject) => {
523
+ rl.on("line", (line) => {
524
+ if (!line.length)
525
+ lines++;
526
+ });
527
+ rl.on("close", () => resolve(lines));
528
+ rl.on("error", reject);
529
+ });
530
+ }
531
+ exports.countFileLines = countFileLines;
@@ -33,7 +33,7 @@ export interface ExecSettingsInterface {
33
33
  onSpawn?: (p: ChildProcess) => any;
34
34
  stdout?: {
35
35
  save?: boolean;
36
- parseLines?: boolean;
36
+ parseLines?: boolean | "skip-empty";
37
37
  onData?: (data: string) => void;
38
38
  };
39
39
  stderr?: {
package/utils/process.js CHANGED
@@ -188,7 +188,7 @@ async function exec(command, argv = [], options = null, settings = {}) {
188
188
  toStderr: log.allToStderr,
189
189
  });
190
190
  }
191
- if (typeof options?.cwd === "string" && !(await (0, fs_1.checkDir)(options.cwd)))
191
+ if (typeof options?.cwd === "string" && !(await (0, fs_1.existsDir)(options.cwd)))
192
192
  throw new Error(`Current working directory does not exist: ${options.cwd}`);
193
193
  if (pipe?.stream instanceof fs_2.ReadStream && "onReadProgress" in pipe) {
194
194
  const fileInfo = await (0, promises_1.stat)(pipe.stream.path);
@@ -225,13 +225,13 @@ async function exec(command, argv = [], options = null, settings = {}) {
225
225
  if (spawnData.exitCode) {
226
226
  let exitCodeError;
227
227
  if (settings.stderr?.toExitCode) {
228
- exitCodeError = new Error(`Exit code ${spawnData.exitCode}: ${spawnData.stderr
228
+ exitCodeError = new Error(`Exit code ${spawnData.exitCode}: ${command} ${argv.join(" ")} | ${spawnData.stderr
229
229
  .split(/\r?\n/g)
230
230
  .filter((v) => !!v.length)
231
231
  .join(" | ")}`);
232
232
  }
233
233
  else {
234
- exitCodeError = new Error(`Exit code: ${spawnData.exitCode} (${command} ${argv.join(" ")})`);
234
+ exitCodeError = new Error(`Exit code ${spawnData.exitCode}: ${command} ${argv.join(" ")}`);
235
235
  }
236
236
  const exitCodeErrorResult = settings.onExitCodeError?.(spawnData, exitCodeError);
237
237
  if (exitCodeErrorResult instanceof Error) {
@@ -286,17 +286,24 @@ async function exec(command, argv = [], options = null, settings = {}) {
286
286
  if (!p.stdout)
287
287
  throw new Error(`stdout is not defined`);
288
288
  const parseLines = settings.stdout?.parseLines;
289
- const onData = (data) => {
289
+ const skipEmptyLines = parseLines === "skip-empty";
290
+ const onData = (inData) => {
291
+ let data = inData.toString();
292
+ if (parseLines) {
293
+ if (skipEmptyLines && !data.trim().length)
294
+ return;
295
+ data = `${inData}\n`;
296
+ }
290
297
  if (log.stdout)
291
298
  logExecStdout({
292
- data: parseLines ? `${data}\n` : data.toString(),
299
+ data,
293
300
  stderr: log.allToStderr,
294
301
  colorize: log.colorize,
295
302
  });
296
303
  if (settings.stdout?.save)
297
- spawnData.stdout += data.toString();
304
+ spawnData.stdout += data;
298
305
  if (settings.stdout?.onData)
299
- settings.stdout.onData(data.toString());
306
+ settings.stdout.onData(inData.toString());
300
307
  };
301
308
  if (parseLines) {
302
309
  const rl = (0, readline_1.createInterface)({
@@ -305,7 +312,9 @@ async function exec(command, argv = [], options = null, settings = {}) {
305
312
  rl.on("line", onData);
306
313
  rl.on("close", tryFinish);
307
314
  }
308
- else {
315
+ else if (log.stdout ||
316
+ settings.stdout?.save ||
317
+ settings.stdout?.onData) {
309
318
  p.stdout.on("data", onData);
310
319
  }
311
320
  }
package/utils/string.d.ts CHANGED
@@ -1,6 +1,4 @@
1
1
  export declare function serialize(message: string, data?: Object): string;
2
- export declare function ucfirst(value: string): string;
3
- export declare function lcfirst(value: string): string;
4
2
  export declare function snakeCase(value: string, char?: string): string;
5
3
  export declare function render(subject: string, vars: Record<string, string | undefined>): string;
6
4
  export declare function parseStringList(value: string, validValues?: string[]): string[];
package/utils/string.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatDateTime = exports.checkMatch = exports.checkPath = exports.makePathPatterns = exports.formatSeconds = exports.formatUri = exports.parseStringList = exports.render = exports.snakeCase = exports.lcfirst = exports.ucfirst = exports.serialize = void 0;
3
+ exports.formatDateTime = exports.checkMatch = exports.checkPath = exports.makePathPatterns = exports.formatSeconds = exports.formatUri = exports.parseStringList = exports.render = exports.snakeCase = exports.serialize = void 0;
4
4
  const AppError_1 = require("../Error/AppError");
5
5
  const micromatch_1 = require("micromatch");
6
6
  function serialize(message, data) {
@@ -9,14 +9,6 @@ function serialize(message, data) {
9
9
  return message;
10
10
  }
11
11
  exports.serialize = serialize;
12
- function ucfirst(value) {
13
- return value.charAt(0).toUpperCase() + value.slice(1);
14
- }
15
- exports.ucfirst = ucfirst;
16
- function lcfirst(value) {
17
- return value.charAt(0).toLowerCase() + value.slice(1);
18
- }
19
- exports.lcfirst = lcfirst;
20
12
  function snakeCase(value, char = "_") {
21
13
  return value.replace(/[A-Z]/g, (letter) => `${char}${letter.toLowerCase()}`);
22
14
  }
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,26 +8,47 @@ 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
- include: string[];
15
- compress?: boolean;
31
+ includeList: string;
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;
39
+ decompress?: boolean | DecompressOptions;
22
40
  total?: number;
23
41
  onEntry?: (entry: TarEntry) => void;
24
42
  }
43
+ export declare const compressDefinition: JSONSchema7;
44
+ export type TarVendor = "busybox" | "bsdtar" | "gnu";
45
+ export declare function getTarVendor(cache?: boolean, log?: boolean): Promise<TarVendor | null>;
25
46
  export type ListTarOptions = {
26
47
  input: string;
27
48
  onEntry?: (entry: Pick<TarEntry, "path">) => void;
28
49
  verbose?: boolean;
29
50
  };
30
51
  export declare function listTar(options: ListTarOptions): Promise<number>;
52
+ export declare function checkPigzLib(cache?: boolean): Promise<boolean>;
31
53
  export declare function createTar(options: CreateTarOptions): Promise<void>;
32
54
  export declare function extractTar(options: ExtractOptions): Promise<void>;
package/utils/tar.js CHANGED
@@ -1,91 +1,210 @@
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
- exports.extractTar = exports.createTar = exports.listTar = void 0;
3
+ exports.extractTar = exports.createTar = exports.checkPigzLib = exports.listTar = exports.getTarVendor = exports.compressDefinition = void 0;
7
4
  const cli_1 = require("./cli");
8
5
  const fs_1 = require("./fs");
9
6
  const math_1 = require("./math");
10
- const fs_2 = require("fs");
7
+ const process_1 = require("./process");
11
8
  const promises_1 = require("fs/promises");
12
- const tar_1 = __importDefault(require("tar"));
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
+ };
27
+ let tarVendor;
28
+ async function getTarVendor(cache = true, log = false) {
29
+ if (cache && typeof tarVendor !== "undefined")
30
+ return tarVendor;
31
+ const p = await (0, process_1.exec)("tar", ["--help"], {}, {
32
+ log,
33
+ stdout: {
34
+ save: true,
35
+ },
36
+ });
37
+ const find = () => {
38
+ if (p.stdout.includes("BusyBox")) {
39
+ return "busybox";
40
+ }
41
+ else if (p.stdout.includes("bsdtar")) {
42
+ return "bsdtar";
43
+ }
44
+ else if (p.stdout.includes("GNU")) {
45
+ return "gnu";
46
+ }
47
+ else {
48
+ return null;
49
+ }
50
+ };
51
+ return (tarVendor = find());
52
+ }
53
+ exports.getTarVendor = getTarVendor;
13
54
  async function listTar(options) {
14
- if (options.verbose)
15
- (0, cli_1.logExec)("tar", ["-ztvf", options.input]);
16
55
  let total = 0;
17
- await tar_1.default.list({
18
- file: options.input,
19
- onentry(entry) {
20
- options.onEntry?.({ path: entry.path });
21
- total++;
56
+ await (0, process_1.exec)("tar", ["-tf", toLocalPath(options.input), "--force-local"], {}, {
57
+ log: options.verbose,
58
+ stdout: {
59
+ parseLines: "skip-empty",
60
+ onData: (path) => {
61
+ options.onEntry?.({ path });
62
+ total++;
63
+ },
22
64
  },
23
65
  });
24
66
  return total;
25
67
  }
26
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
+ }
27
92
  async function createTar(options) {
28
- if (options.verbose)
29
- (0, cli_1.logExec)("tar", [
30
- options.compress ? "-czvf" : "-cvf",
31
- options.output,
32
- options.path,
33
- ]);
34
- let total = options.include.length;
35
- if (!total)
36
- throw new Error("'include' option is empty");
93
+ const vendor = await getTarVendor(true, options.verbose);
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
+ }));
37
99
  let current = 0;
38
- let progressPromise;
39
- const inStream = tar_1.default.create({
40
- gzip: options.compress,
41
- cwd: options.path,
42
- filter(path) {
43
- current++;
44
- options.onEntry?.({
45
- path: path,
46
- progress: {
47
- total,
48
- current,
49
- percent: (0, math_1.progressPercent)(total, current),
50
- },
51
- });
52
- return true;
100
+ const env = {
101
+ ...(compress?.cores === 1 &&
102
+ compress.level && {
103
+ GZIP_OPT: compress.level.toString(),
104
+ }),
105
+ };
106
+ await (0, process_1.exec)("tar", [
107
+ "-C",
108
+ toLocalPath(options.path),
109
+ compress?.cores === 1 ? "-czvf" : "-cvf",
110
+ toLocalPath(options.output),
111
+ "-T",
112
+ toLocalPath(options.includeList),
113
+ "--ignore-failed-read",
114
+ "--force-local",
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,
136
+ },
137
+ }, {
138
+ log: options.verbose ? { envNames: Object.keys(env) } : false,
139
+ stderr: { toExitCode: true },
140
+ stdout: {
141
+ parseLines: "skip-empty",
142
+ onData: (line) => {
143
+ current++;
144
+ const path = vendor === "bsdtar" ? line.slice(2) : line;
145
+ options.onEntry?.({
146
+ path,
147
+ progress: {
148
+ total,
149
+ current,
150
+ percent: (0, math_1.progressPercent)(total, current),
151
+ },
152
+ });
153
+ },
53
154
  },
54
- }, options.include);
55
- const outStream = (0, fs_2.createWriteStream)(options.output);
56
- await new Promise((resolve, reject) => {
57
- inStream.on("error", reject);
58
- outStream.on("error", reject);
59
- inStream.pipe(outStream);
60
- outStream.on("close", resolve);
61
155
  });
62
- await progressPromise;
63
156
  }
64
157
  exports.createTar = createTar;
158
+ /**
159
+ * Fix `tar.exe: Option --force-local is not supported`
160
+ */
161
+ function toLocalPath(path) {
162
+ return (0, os_1.platform)() === "win32" ? path.replace(/\\/g, "/") : path;
163
+ }
65
164
  async function extractTar(options) {
66
165
  let total = options.total ??
67
166
  (await listTar({ input: options.input, verbose: options.verbose }));
68
- if (options.verbose) {
69
- (0, cli_1.logExec)("tar", ["-xzvfp", options.input, "-C", options.output]);
167
+ if (options.verbose)
70
168
  (0, cli_1.logExec)("mkdir", ["-p", options.output]);
71
- }
72
- let current = 0;
73
169
  await (0, promises_1.mkdir)(options.output, { recursive: true });
74
170
  await (0, fs_1.ensureEmptyDir)(options.output);
75
- await tar_1.default.extract({
76
- file: options.input,
77
- cwd: options.output,
78
- preserveOwner: true,
79
- onentry(entry) {
80
- current++;
81
- options.onEntry?.({
82
- path: entry.path,
83
- progress: {
84
- total,
85
- current,
86
- percent: (0, math_1.progressPercent)(total, current),
87
- },
88
- });
171
+ const decompress = await ifX(options.decompress, async (decompress) => ({
172
+ ...decompress,
173
+ cores: await resolveCores(decompress.cores),
174
+ }));
175
+ let current = 0;
176
+ await (0, process_1.exec)("tar", [
177
+ decompress?.cores === 1 ? "-xzvpf" : "-xvpf",
178
+ toLocalPath(options.input),
179
+ "-C",
180
+ toLocalPath(options.output),
181
+ "--force-local",
182
+ ...(decompress && decompress.cores > 1
183
+ ? ["-I", `"pigz -p ${decompress.cores}"`]
184
+ : []),
185
+ ], {
186
+ ...(decompress &&
187
+ decompress.cores > 1 && {
188
+ shell: true,
189
+ }),
190
+ }, {
191
+ log: options.verbose,
192
+ stderr: {
193
+ toExitCode: true,
194
+ },
195
+ stdout: {
196
+ parseLines: "skip-empty",
197
+ onData: (path) => {
198
+ current++;
199
+ options.onEntry?.({
200
+ path,
201
+ progress: {
202
+ total,
203
+ current,
204
+ percent: (0, math_1.progressPercent)(total, current),
205
+ },
206
+ });
207
+ },
89
208
  },
90
209
  });
91
210
  }