@datatruck/cli 0.13.1 → 0.14.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.
@@ -19,6 +19,7 @@ export declare type DatatruckRepositoryConfigType = {
19
19
  };
20
20
  declare type CompressObjectType = {
21
21
  packs?: {
22
+ name?: string;
22
23
  include: string[];
23
24
  exclude?: string[];
24
25
  onePackByResult?: boolean;
@@ -50,6 +50,7 @@ exports.datatruckPackageRepositoryDefinition = {
50
50
  additionalProperties: false,
51
51
  required: ["include"],
52
52
  properties: {
53
+ name: { type: "string" },
53
54
  include: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.stringListUtil),
54
55
  exclude: (0, DefinitionEnum_1.makeRef)(DefinitionEnum_1.DefinitionEnum.stringListUtil),
55
56
  onePackByResult: { type: "boolean" },
@@ -160,7 +161,7 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
160
161
  }
161
162
  normalizeCompressConfig(packageConfig) {
162
163
  let compress = packageConfig?.compress ?? this.config.compress;
163
- if (compress === true) {
164
+ if (compress === true || (compress && !Array.isArray(compress.packs))) {
164
165
  return {
165
166
  packs: [
166
167
  {
@@ -202,74 +203,78 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
202
203
  cwd: sourcePath,
203
204
  ignore: exclude,
204
205
  dot: true,
205
- onlyFiles: compress ? false : true,
206
+ onlyFiles: false,
206
207
  markDirectories: true,
208
+ stats: true,
207
209
  });
210
+ const packs = compress?.packs || [];
211
+ const tmpDir = await (0, fs_util_1.mkTmpDir)("path-lists");
212
+ const nonPackStream = (0, fs_1.createWriteStream)((0, path_1.join)(tmpDir, "nonpack.txt"));
213
+ const singlePackStream = (0, fs_1.createWriteStream)((0, path_1.join)(tmpDir, "single-pack.txt"));
214
+ const packStreams = Array.from({ length: packs.length }).map((v, i) => (0, fs_1.createWriteStream)((0, path_1.join)(tmpDir, `pack-${i}.txt`)));
215
+ let totalFiles = 0;
216
+ const streams = [nonPackStream, singlePackStream, ...packStreams];
208
217
  if (data.options.verbose)
209
- (0, cli_util_1.logExec)(`Writing paths lists`);
210
- const pathLists = await (0, fs_util_1.writePathLists)({
211
- paths: stream,
212
- packs: compress?.packs,
218
+ (0, cli_util_1.logExec)(`Writing file lists in ${tmpDir}`);
219
+ await data.onProgress({
220
+ step: "Writing the file lists...",
213
221
  });
214
- if (data.options.verbose)
215
- (0, cli_util_1.logExec)(`Path lists: ${pathLists.path}`);
216
- let currentFiles = 0;
217
- if (compress?.packs) {
218
- let packIndex = 0;
219
- for (const packsPath of pathLists.includedPackPaths) {
220
- const pack = compress.packs[packIndex];
221
- if (pack.onePackByResult) {
222
- const reader = (0, readline_1.createInterface)({
223
- input: (0, fs_1.createReadStream)(packsPath),
224
- });
225
- let multipleIndex = 0;
226
- for await (let packPath of reader) {
227
- if (packPath.endsWith("/"))
228
- packPath = packPath.slice(0, -1);
229
- const target = (0, path_1.join)(outPath, `.${packIndex}-${multipleIndex++}-${encodeURIComponent(packPath.replace(/[\\/]/g, "-")).slice(0, 255)}.dd.zip`);
230
- const stats = await (0, zip_util_1.zip)({
231
- path: pkg.path,
232
- output: target,
233
- filter: [{ patterns: [packPath] }],
234
- excludeList: pathLists.excludedPackPaths[packIndex],
235
- verbose: data.options.verbose,
236
- onStream: async (stream) => await data.onProgress({
237
- total: pathLists.total.all,
238
- current: currentFiles + stream.data.files,
239
- percent: (0, math_util_1.progressPercent)(pathLists.total.all, currentFiles + stream.data.files),
240
- step: stream.type === "progress" ? stream.data.path : "",
241
- stepPercent: stream.type === "progress" ? stream.data.progress : null,
242
- }),
243
- });
244
- currentFiles += stats.files;
222
+ await Promise.all([
223
+ ...streams.map((p) => (0, fs_util_1.waitForClose)(p)),
224
+ (async () => {
225
+ for await (const entry of (0, fs_util_1.pathIterator)(stream)) {
226
+ const pathSubject = entry.stats.isDirectory()
227
+ ? entry.path.slice(0, -1)
228
+ : entry.path;
229
+ let stream = nonPackStream;
230
+ let successPackIndex;
231
+ for (const [packIndex, pack] of packs.entries()) {
232
+ if ((0, string_util_1.checkPath)(pathSubject, pack.include, pack.exclude)) {
233
+ stream = pack.onePackByResult
234
+ ? singlePackStream
235
+ : packStreams[packIndex];
236
+ successPackIndex = packIndex;
237
+ break;
238
+ }
239
+ }
240
+ const isNonPackStream = stream === nonPackStream;
241
+ const isPackStream = stream !== nonPackStream;
242
+ const isSinglePackStream = stream === singlePackStream;
243
+ const include = isPackStream
244
+ ? entry.stats.isDirectory()
245
+ ? await (0, fs_util_1.isEmptyDir)(entry.path)
246
+ : true
247
+ : true;
248
+ if (include) {
249
+ let value = entry.path;
250
+ if (isNonPackStream) {
251
+ value += `:${entry.stats.uid}:${entry.stats.gid}:${entry.stats.mode}`;
252
+ }
253
+ else if (isSinglePackStream) {
254
+ value += `:${successPackIndex}`;
255
+ }
256
+ if (!entry.stats.isDirectory())
257
+ totalFiles++;
258
+ stream.write(`${value}\n`);
245
259
  }
246
260
  }
247
- else {
248
- const target = (0, path_1.join)(outPath, `.${packIndex}.dd.zip`);
249
- const stats = await (0, zip_util_1.zip)({
250
- path: sourcePath,
251
- output: target,
252
- includeList: packsPath,
253
- excludeList: pathLists.excludedPackPaths[packIndex],
254
- verbose: data.options.verbose,
255
- onStream: async (stream) => await data.onProgress({
256
- total: pathLists.total.all,
257
- current: currentFiles + stream.data.files,
258
- percent: (0, math_util_1.progressPercent)(pathLists.total.all, currentFiles + stream.data.files),
259
- step: stream.type === "progress" ? stream.data.path : "",
260
- }),
261
- });
262
- currentFiles += stats.files;
261
+ for (const stream of streams) {
262
+ stream.end();
263
263
  }
264
- packIndex++;
265
- }
266
- }
264
+ })(),
265
+ ]);
266
+ let currentFiles = 0;
267
+ const dttFolder = `.dtt-${data.snapshot.id.slice(0, 8)}`;
268
+ const dttPath = (0, path_1.join)(outPath, dttFolder);
269
+ await (0, promises_1.mkdir)(dttPath);
270
+ // Non pack
267
271
  if (data.options.verbose)
268
272
  (0, cli_util_1.logExec)(`Copying files to ${outPath}`);
273
+ await (0, promises_1.copyFile)(nonPackStream.path, (0, path_1.join)(dttPath, "permissions.txt"));
269
274
  await (0, fs_util_1.cpy)({
270
275
  input: {
271
276
  type: "pathList",
272
- path: pathLists.path,
277
+ path: nonPackStream.path.toString(),
273
278
  basePath: sourcePath,
274
279
  },
275
280
  targetPath: outPath,
@@ -280,13 +285,66 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
280
285
  return;
281
286
  currentFiles++;
282
287
  await data.onProgress({
283
- total: pathLists.total.all,
288
+ total: totalFiles,
284
289
  current: currentFiles,
285
- percent: (0, math_util_1.progressPercent)(pathLists.total.all, currentFiles),
290
+ percent: (0, math_util_1.progressPercent)(totalFiles, currentFiles),
286
291
  step: entryPath,
287
292
  });
288
293
  },
289
294
  });
295
+ // Single pack
296
+ const singleReader = (0, readline_1.createInterface)({
297
+ input: (0, fs_1.createReadStream)(singlePackStream.path),
298
+ });
299
+ for await (const line of singleReader) {
300
+ let [packPath, packIndex] = line.split(":");
301
+ const pack = packs[packIndex];
302
+ if (packPath.endsWith("/"))
303
+ packPath = packPath.slice(0, -1);
304
+ const outBasename = (`pack${pack.name ? `-${encodeURIComponent(pack.name)}` : ""}` +
305
+ `-${encodeURIComponent(packPath.replace(/[\\/]/g, "-"))}` +
306
+ `.zip`).slice(0, 255);
307
+ const target = (0, path_1.join)(dttPath, outBasename);
308
+ const stats = await (0, zip_util_1.zip)({
309
+ path: pkg.path,
310
+ output: target,
311
+ filter: [{ patterns: [packPath] }],
312
+ verbose: data.options.verbose,
313
+ onStream: async (stream) => {
314
+ if (stream.type === "progress")
315
+ await data.onProgress({
316
+ total: totalFiles,
317
+ current: currentFiles + stream.data.files,
318
+ percent: (0, math_util_1.progressPercent)(totalFiles, currentFiles + stream.data.files),
319
+ step: stream.data.path,
320
+ stepPercent: stream.data.progress,
321
+ });
322
+ },
323
+ });
324
+ currentFiles += stats.files;
325
+ }
326
+ // Packs
327
+ for (const [packIndex, packStream] of packStreams.entries()) {
328
+ const pack = packs[packIndex];
329
+ const target = (0, path_1.join)(dttPath, `pack-${packIndex}${pack.name ? `-${pack.name}` : ""}.zip`);
330
+ const stats = await (0, zip_util_1.zip)({
331
+ path: sourcePath,
332
+ output: target,
333
+ includeList: packStream.path.toString(),
334
+ verbose: data.options.verbose,
335
+ onStream: async (stream) => {
336
+ if (stream.type === "progress")
337
+ await data.onProgress({
338
+ total: totalFiles,
339
+ current: currentFiles + stream.data.files,
340
+ percent: (0, math_util_1.progressPercent)(totalFiles, currentFiles + stream.data.files),
341
+ step: stream.data.path,
342
+ });
343
+ },
344
+ });
345
+ currentFiles += stats.files;
346
+ }
347
+ // Meta
290
348
  const metaPath = `${outPath}.json`;
291
349
  const nodePkg = (0, fs_util_1.parsePackageFile)();
292
350
  const meta = {
@@ -296,7 +354,8 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
296
354
  package: data.package.name,
297
355
  task: data.package.task?.name,
298
356
  version: nodePkg.version,
299
- size: await (0, fs_util_1.fastFolderSizeAsync)(outPath),
357
+ size: (await (0, fs_util_1.fastFolderSizeAsync)(outPath)) -
358
+ (await (0, fs_util_1.fastFolderSizeAsync)(dttPath)),
300
359
  };
301
360
  if (data.options.verbose)
302
361
  (0, cli_util_1.logExec)(`Writing metadata into ${metaPath}`);
@@ -313,7 +372,7 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
313
372
  const sourceMetaPath = `${sourcePath}.json`;
314
373
  const targetMetaPath = `${targetPath}.json`;
315
374
  if (data.options.verbose)
316
- (0, cli_util_1.logExec)(`Copying files to ${targetPath}`);
375
+ (0, cli_util_1.logExec)(`Copying backup files to ${targetPath}`);
317
376
  await (0, promises_1.mkdir)(targetPath);
318
377
  await (0, fs_util_1.cpy)({
319
378
  input: {
@@ -342,49 +401,100 @@ class DatatruckRepository extends RepositoryAbstract_1.RepositoryAbstract {
342
401
  });
343
402
  const sourcePath = (0, path_1.join)(this.config.outPath, snapshotName);
344
403
  let totalFiles = 0;
345
- let currentFiles = -1;
346
- await (0, fs_util_1.forEachFile)(sourcePath, () => {
404
+ let currentFiles = 0;
405
+ await data.onProgress({
406
+ step: "Counting files...",
407
+ });
408
+ const dttFolder = `.dtt-${data.snapshot.id.slice(0, 8)}`;
409
+ const dttPath = (0, path_1.join)(sourcePath, dttFolder);
410
+ const dttPathExists = await (0, fs_util_1.checkDir)(dttPath);
411
+ if (dttPathExists) {
412
+ const it = await (0, promises_1.opendir)(dttPath);
413
+ for await (const dirent of it) {
414
+ const path = (0, path_1.join)(dttPath, dirent.name);
415
+ if (dirent.name === "permissions.txt") {
416
+ totalFiles++;
417
+ }
418
+ else if (dirent.name.endsWith(".zip")) {
419
+ await (0, zip_util_1.listZip)({
420
+ path,
421
+ verbose: data.options.verbose,
422
+ onStream: (item) => {
423
+ const isDir = item.Folder === "+";
424
+ if (!isDir)
425
+ totalFiles++;
426
+ },
427
+ });
428
+ }
429
+ }
430
+ }
431
+ const allFiles = fast_glob_1.default.stream(["**"], {
432
+ cwd: sourcePath,
433
+ ignore: [dttFolder],
434
+ dot: true,
435
+ onlyFiles: true,
436
+ });
437
+ for await (const _file of allFiles) {
347
438
  totalFiles++;
348
- }, true);
439
+ }
349
440
  if (data.options.verbose)
350
441
  (0, cli_util_1.logExec)(`Copying files to ${restorePath}`);
351
442
  await (0, fs_util_1.cpy)({
352
443
  input: {
353
444
  type: "glob",
354
445
  sourcePath,
446
+ exclude: [dttFolder],
355
447
  },
356
448
  targetPath: restorePath,
357
449
  concurrency: this.config.fileCopyConcurrency,
358
- onPath: async ({ entryPath, entrySourcePath }) => {
359
- const isRootFile = (0, path_1.basename)(entryPath) === entryPath;
360
- const isZipFile = isRootFile &&
361
- entryPath.startsWith(".") &&
362
- entryPath.endsWith(".dd.zip");
363
- await data.onProgress({
364
- total: totalFiles,
365
- current: Math.max(currentFiles, 0),
366
- percent: (0, math_util_1.progressPercent)(totalFiles, Math.max(currentFiles, 0)),
367
- step: entryPath,
368
- });
369
- if (isZipFile) {
450
+ onPath: async ({ entryPath, isDir }) => {
451
+ if (!isDir) {
452
+ currentFiles++;
453
+ await data.onProgress({
454
+ total: totalFiles,
455
+ current: Math.max(currentFiles, 0),
456
+ percent: (0, math_util_1.progressPercent)(totalFiles, Math.max(currentFiles, 0)),
457
+ step: entryPath,
458
+ });
459
+ }
460
+ },
461
+ });
462
+ if (dttPathExists) {
463
+ const it = await (0, promises_1.opendir)(dttPath);
464
+ for await (const dirent of it) {
465
+ const path = (0, path_1.join)(dttPath, dirent.name);
466
+ if (dirent.name === "permissions.txt") {
467
+ if (data.options.verbose)
468
+ (0, cli_util_1.logExec)(`Applying permissions (${path})`);
469
+ currentFiles++;
470
+ await data.onProgress({
471
+ total: totalFiles,
472
+ current: currentFiles,
473
+ percent: (0, math_util_1.progressPercent)(totalFiles, currentFiles),
474
+ step: "Applying permissions",
475
+ });
476
+ await (0, fs_util_1.applyPermissions)(restorePath, path);
477
+ }
478
+ else if (dirent.name.endsWith(".zip")) {
370
479
  await (0, zip_util_1.unzip)({
371
- input: entrySourcePath,
480
+ input: path,
372
481
  output: restorePath,
373
482
  verbose: data.options.verbose,
374
- onStream: async (stream) => await data.onProgress({
375
- total: totalFiles,
376
- current: currentFiles + 1,
377
- percent: (0, math_util_1.progressPercent)(totalFiles, currentFiles + 1),
378
- step: stream.type === "progress"
379
- ? `Extracting ${stream.data.path}`
380
- : "",
381
- }),
483
+ onStream: async (stream) => {
484
+ await data.onProgress({
485
+ total: totalFiles,
486
+ current: currentFiles + stream.data.files,
487
+ percent: (0, math_util_1.progressPercent)(totalFiles, currentFiles + stream.data.files),
488
+ step: stream.type === "progress"
489
+ ? `Extracting ${stream.data.path}`
490
+ : "",
491
+ stepPercent: stream.data.progress,
492
+ });
493
+ },
382
494
  });
383
495
  }
384
- currentFiles++;
385
- return isZipFile ? false : true;
386
- },
387
- });
496
+ }
497
+ }
388
498
  }
389
499
  }
390
500
  exports.DatatruckRepository = DatatruckRepository;
@@ -475,6 +475,9 @@
475
475
  "include"
476
476
  ],
477
477
  "properties": {
478
+ "name": {
479
+ "type": "string"
480
+ },
478
481
  "include": {
479
482
  "$ref": "#/definitions/stringlist-util"
480
483
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datatruck/cli",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "dependencies": {
5
5
  "ajv": "^8.11.0",
6
6
  "async": "^3.2.4",
package/util/fs-util.d.ts CHANGED
@@ -1,9 +1,20 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
- import { Stats } from "fs";
4
+ /// <reference types="node" />
5
+ import { Dirent, Stats } from "fs";
6
+ import { WriteStream } from "fs";
5
7
  import { Interface } from "readline";
6
8
  export declare const isWSLSystem: boolean;
9
+ export declare function isEmptyDir(path: string): Promise<boolean>;
10
+ declare type EntryObject = {
11
+ name: string;
12
+ path: string;
13
+ dirent: Dirent;
14
+ stats: Stats;
15
+ };
16
+ export declare function applyPermissions(baseDir: string, permissionsPath: string): Promise<void>;
17
+ export declare function pathIterator(stream: AsyncIterable<string | Buffer>): AsyncIterable<EntryObject>;
7
18
  export declare function isLocalDir(path: string): boolean;
8
19
  export declare function isDirEmpty(path: string): Promise<boolean>;
9
20
  export declare function mkdirIfNotExists(path: string): Promise<string>;
@@ -28,7 +39,7 @@ export declare function mkTmpDir(prefix: string, id?: string): Promise<string>;
28
39
  export declare function readPartialFile(path: string, positions: [number, number?]): Promise<string>;
29
40
  export declare function checkFile(path: string): Promise<boolean>;
30
41
  export declare function checkDir(path: string): Promise<boolean>;
31
- export declare function readDir(path: string): Promise<string[]>;
42
+ export declare function readDir(path: string, optional?: boolean): Promise<string[]>;
32
43
  export declare function forEachFile(dirPath: string, cb: (path: string, dir: boolean) => void, includeDir?: boolean): Promise<void>;
33
44
  /**
34
45
  * @experimental
@@ -37,24 +48,7 @@ export declare function fastglobToGitIgnore(patterns: string[], baseDir: string)
37
48
  export declare function writeGitIgnoreList(options: {
38
49
  paths: NodeJS.ReadableStream | string[];
39
50
  }): Promise<string>;
40
- export declare function writePathLists(options: {
41
- paths: NodeJS.ReadableStream | string[];
42
- packs?: {
43
- include: string[];
44
- exclude?: string[];
45
- multiple?: boolean;
46
- }[];
47
- }): Promise<{
48
- path: string;
49
- includedPackPaths: string[];
50
- excludedPackPaths: string[];
51
- total: {
52
- all: number;
53
- path: number;
54
- packsPaths: number[];
55
- multipleStats: Record<string, number>;
56
- };
57
- }>;
51
+ export declare function waitForClose(stream: WriteStream): Promise<WriteStream>;
58
52
  export declare function copyFileWithStreams(source: string, target: string): Promise<unknown>;
59
53
  export declare function updateFileStats(path: string, fileInfo: Stats): Promise<void>;
60
54
  export declare function isNotFoundError(error: unknown): boolean;
@@ -91,3 +85,4 @@ export declare function cpy(options: {
91
85
  };
92
86
  }) => Promise<boolean | void>;
93
87
  }): Promise<void>;
88
+ export {};
package/util/fs-util.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.cpy = exports.isNotFoundError = exports.updateFileStats = exports.copyFileWithStreams = exports.writePathLists = exports.writeGitIgnoreList = exports.fastglobToGitIgnore = exports.forEachFile = exports.readDir = exports.checkDir = exports.checkFile = exports.readPartialFile = exports.mkTmpDir = exports.fastFolderSizeAsync = exports.tmpDir = 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.isWSLSystem = void 0;
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.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
8
  const path_util_1 = require("./path-util");
9
9
  const async_1 = require("async");
@@ -13,13 +13,49 @@ const fast_glob_1 = __importDefault(require("fast-glob"));
13
13
  const fs_1 = require("fs");
14
14
  const fs_2 = require("fs");
15
15
  const promises_1 = require("fs/promises");
16
- const micromatch_1 = require("micromatch");
17
16
  const os_1 = require("os");
18
17
  const path_1 = require("path");
19
18
  const path_2 = require("path");
20
19
  const readline_1 = require("readline");
21
20
  const util_1 = require("util");
22
21
  exports.isWSLSystem = (0, os_1.release)().includes("microsoft-standard-WSL");
22
+ async function isEmptyDir(path) {
23
+ const iterator = await (0, promises_1.opendir)(path);
24
+ let done = false;
25
+ try {
26
+ const next = await iterator[Symbol.asyncIterator]().next();
27
+ done = !!next.done;
28
+ return done;
29
+ }
30
+ finally {
31
+ if (!done) {
32
+ await iterator.close();
33
+ }
34
+ }
35
+ }
36
+ exports.isEmptyDir = isEmptyDir;
37
+ async function applyPermissions(baseDir, permissionsPath) {
38
+ const singleReader = (0, readline_1.createInterface)({
39
+ input: (0, fs_1.createReadStream)(permissionsPath),
40
+ });
41
+ for await (const line of singleReader) {
42
+ const [rpath, rawUid, rawGui, rawMode] = line.split(":");
43
+ const path = (0, path_1.join)(baseDir, rpath);
44
+ if (!path.startsWith(baseDir)) {
45
+ throw new Error(`Entry path is out of the base dir: (${path}, ${baseDir})`);
46
+ }
47
+ const uid = Number(rawUid);
48
+ const guid = Number(rawGui);
49
+ await (0, promises_1.chown)(path, uid, guid);
50
+ const mode = Number(rawMode);
51
+ await (0, promises_1.chmod)(path, mode);
52
+ }
53
+ }
54
+ exports.applyPermissions = applyPermissions;
55
+ function pathIterator(stream) {
56
+ return stream;
57
+ }
58
+ exports.pathIterator = pathIterator;
23
59
  function isLocalDir(path) {
24
60
  return /^[\/\.]|([A-Z]:)/i.test(path);
25
61
  }
@@ -59,9 +95,7 @@ async function writeJSONFile(path, json) {
59
95
  }
60
96
  exports.writeJSONFile = writeJSONFile;
61
97
  async function readdirIfExists(path) {
62
- if (!(await existsDir(path)))
63
- return [];
64
- return await readDir(path);
98
+ return await readDir(path, true);
65
99
  }
66
100
  exports.readdirIfExists = readdirIfExists;
67
101
  exports.parseFileExtensions = ["json", "js", "ts", "yaml", "yml"];
@@ -187,13 +221,15 @@ async function checkDir(path) {
187
221
  }
188
222
  }
189
223
  exports.checkDir = checkDir;
190
- async function readDir(path) {
224
+ async function readDir(path, optional) {
191
225
  try {
192
226
  return await (0, promises_1.readdir)(path);
193
227
  }
194
228
  catch (anyError) {
195
229
  const nodeError = anyError;
196
230
  if (nodeError.code === "ENOENT") {
231
+ if (optional)
232
+ return [];
197
233
  const error = new Error(nodeError.message);
198
234
  error.code = nodeError.code;
199
235
  error.errno = nodeError.errno;
@@ -262,93 +298,14 @@ async function writeGitIgnoreList(options) {
262
298
  return path;
263
299
  }
264
300
  exports.writeGitIgnoreList = writeGitIgnoreList;
265
- async function writePathLists(options) {
266
- const tempDir = await mkTmpDir("path-lists");
267
- const includedPaths = [];
268
- const excludedPaths = [];
269
- const included = [];
270
- const excluded = [];
271
- const multipleStats = {};
272
- const total = new Array((options.packs?.length || 0) + 1).fill(0);
273
- await Promise.all([
274
- ...new Array((options.packs?.length || 0) + 1).fill(null).map((_, index) => new Promise((resolve, reject) => {
275
- const path = (0, path_1.join)(tempDir, `${index}-included.txt`);
276
- const stream = (0, fs_2.createWriteStream)(path);
277
- includedPaths.push(path);
278
- included.push(stream);
279
- stream.on("close", resolve);
280
- stream.on("error", reject);
281
- })),
282
- ...new Array(options.packs?.length || 0).fill(null).map((_, index) => new Promise((resolve, reject) => {
283
- const path = (0, path_1.join)(tempDir, `${index}-excluded.txt`);
284
- const stream = (0, fs_2.createWriteStream)(path);
285
- excludedPaths.push(path);
286
- excluded.push(stream);
287
- stream.on("close", resolve);
288
- stream.on("error", reject);
289
- })),
290
- new Promise(async (resolve) => {
291
- const packDirectories = [];
292
- for await (const value of options.paths) {
293
- const entry = value.toString();
294
- const isDir = entry.endsWith("/");
295
- const matchEntry = isDir ? entry.slice(0, -1) : entry;
296
- let packIndex = 1;
297
- let matches = false;
298
- for (const pack of options.packs || []) {
299
- if ((0, micromatch_1.isMatch)(matchEntry, pack.include) &&
300
- (!pack.exclude || !(0, micromatch_1.isMatch)(matchEntry, pack.exclude))) {
301
- if (isDir)
302
- packDirectories.push([packIndex - 1, entry]);
303
- included[packIndex].write(`${entry}\n`);
304
- if (!isDir)
305
- total[packIndex]++;
306
- matches = true;
307
- break;
308
- }
309
- packIndex++;
310
- }
311
- if (!matches) {
312
- const packDir = packDirectories.find(([, p]) => entry.startsWith(p));
313
- if (packDir) {
314
- const [i, v] = packDir;
315
- const multipleExclude = options.packs?.[i].exclude;
316
- if (multipleExclude && (0, micromatch_1.isMatch)(matchEntry, multipleExclude)) {
317
- included[0].write(`${entry}\n`);
318
- excluded[i].write(`${entry}\n`);
319
- }
320
- else {
321
- if (!multipleStats[v])
322
- multipleStats[v] = 0;
323
- multipleStats[v]++;
324
- }
325
- }
326
- else {
327
- included[0].write(`${entry}\n`);
328
- }
329
- if (!isDir)
330
- total[0]++;
331
- }
332
- }
333
- for (const stream of [...included, ...excluded]) {
334
- stream.end();
335
- }
336
- resolve();
337
- }),
338
- ]);
339
- return {
340
- path: includedPaths[0],
341
- includedPackPaths: includedPaths.slice(1),
342
- excludedPackPaths: excludedPaths,
343
- total: {
344
- all: total.reduce((p, v) => p + v, 0),
345
- path: total[0],
346
- packsPaths: total.slice(1),
347
- multipleStats,
348
- },
349
- };
301
+ async function waitForClose(stream) {
302
+ return new Promise(async (resolve, reject) => {
303
+ stream.on("close", resolve);
304
+ stream.on("error", reject);
305
+ return stream;
306
+ });
350
307
  }
351
- exports.writePathLists = writePathLists;
308
+ exports.waitForClose = waitForClose;
352
309
  async function copyFileWithStreams(source, target) {
353
310
  const r = (0, fs_1.createReadStream)(source);
354
311
  const w = (0, fs_2.createWriteStream)(target);
@@ -391,6 +348,7 @@ async function cpy(options) {
391
348
  }
392
349
  };
393
350
  const task = async (rawEntryPath, basePath) => {
351
+ [rawEntryPath] = rawEntryPath.split(":");
394
352
  const isDir = rawEntryPath.endsWith("/");
395
353
  const entryPath = (0, path_1.normalize)(rawEntryPath);
396
354
  const entrySourcePath = (0, path_1.resolve)((0, path_1.join)(basePath, rawEntryPath));
package/util/math-util.js CHANGED
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.progressPercent = void 0;
4
4
  function progressPercent(total, current) {
5
+ if (total === 0 && current === 0)
6
+ return 0;
5
7
  return Number(((current / total) * 100).toFixed(2));
6
8
  }
7
9
  exports.progressPercent = progressPercent;
@@ -24,6 +24,7 @@ export interface ExecSettingsInterface {
24
24
  onSpawn?: (p: ChildProcess) => void;
25
25
  stdout?: {
26
26
  save?: boolean;
27
+ parseLines?: boolean;
27
28
  onData?: (data: string) => void;
28
29
  };
29
30
  stderr?: {
@@ -11,6 +11,7 @@ const chalk_1 = __importDefault(require("chalk"));
11
11
  const child_process_1 = require("child_process");
12
12
  const fs_1 = require("fs");
13
13
  const promises_1 = require("fs/promises");
14
+ const readline_1 = require("readline");
14
15
  function logExecStdout(input) {
15
16
  let text = input.colorize ? chalk_1.default.grey(input.data) : input.data;
16
17
  if (input.lineSalt)
@@ -70,7 +71,11 @@ async function exec(command, argv = [], options = null, settings = {}) {
70
71
  stderr: "",
71
72
  exitCode: 0,
72
73
  };
73
- let finishListeners = pipe?.stream instanceof fs_1.WriteStream ? 2 : 1;
74
+ let finishListeners = 1;
75
+ if (pipe?.stream instanceof fs_1.WriteStream)
76
+ finishListeners++;
77
+ if (settings.stdout?.parseLines)
78
+ finishListeners++;
74
79
  let streamError;
75
80
  const tryFinish = () => {
76
81
  if (!--finishListeners)
@@ -129,10 +134,11 @@ async function exec(command, argv = [], options = null, settings = {}) {
129
134
  if (log.stdout || settings.stdout) {
130
135
  if (!p.stdout)
131
136
  throw new Error(`stdout is not defined`);
132
- p.stdout.on("data", (data) => {
137
+ const parseLines = settings.stdout?.parseLines;
138
+ const onData = (data) => {
133
139
  if (log.stdout)
134
140
  logExecStdout({
135
- data: data.toString(),
141
+ data: parseLines ? `${data}\n` : data.toString(),
136
142
  stderr: log.allToStderr,
137
143
  colorize: log.colorize,
138
144
  });
@@ -140,7 +146,17 @@ async function exec(command, argv = [], options = null, settings = {}) {
140
146
  spawnData.stdout += data.toString();
141
147
  if (settings.stdout?.onData)
142
148
  settings.stdout.onData(data.toString());
143
- });
149
+ };
150
+ if (parseLines) {
151
+ const rl = (0, readline_1.createInterface)({
152
+ input: p.stdout,
153
+ });
154
+ rl.on("line", onData);
155
+ rl.on("close", tryFinish);
156
+ }
157
+ else {
158
+ p.stdout.on("data", onData);
159
+ }
144
160
  }
145
161
  if (log.stderr || settings.stderr) {
146
162
  if (!p.stderr)
@@ -15,5 +15,6 @@ export declare type UriType = {
15
15
  export declare function formatUri(input: UriType, hidePassword?: boolean): string;
16
16
  export declare function formatSeconds(seconds: number): string;
17
17
  export declare function makePathPatterns(values: string[] | undefined): string[] | undefined;
18
+ export declare function checkPath(path: string, include: string[], exclude?: string[]): boolean;
18
19
  export declare function checkMatch(subject: string | undefined, patterns: string[]): boolean;
19
20
  export declare function formatDateTime(datetime: string): string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatDateTime = exports.checkMatch = 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.lcfirst = exports.ucfirst = 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) {
@@ -99,6 +99,13 @@ function makePathPatterns(values) {
99
99
  });
100
100
  }
101
101
  exports.makePathPatterns = makePathPatterns;
102
+ function checkPath(path, include, exclude) {
103
+ return ((0, micromatch_1.isMatch)(path, include, {
104
+ dot: true,
105
+ }) &&
106
+ (!exclude || !(0, micromatch_1.isMatch)(path, exclude, { dot: true })));
107
+ }
108
+ exports.checkPath = checkPath;
102
109
  function checkMatch(subject, patterns) {
103
110
  if (!subject?.length)
104
111
  subject = "<empty>";
@@ -3,20 +3,6 @@ export interface ZipDataFilterType {
3
3
  exclude?: boolean;
4
4
  patterns: string[];
5
5
  }
6
- export declare type ZipStreamDataType = {
7
- type: "progress";
8
- data: {
9
- progress: number;
10
- files: number;
11
- path: string;
12
- };
13
- } | {
14
- type: "summary";
15
- data: {
16
- folders: number;
17
- files: number;
18
- };
19
- };
20
6
  export interface ZipDataType {
21
7
  command?: string;
22
8
  path: string;
@@ -26,7 +12,7 @@ export interface ZipDataType {
26
12
  includeList?: string;
27
13
  excludeList?: string;
28
14
  verbose?: boolean;
29
- onStream?: (data: ZipStreamDataType) => void;
15
+ onStream?: (data: ZipStream) => void;
30
16
  }
31
17
  export interface UnzipDataType {
32
18
  command?: string;
@@ -34,20 +20,60 @@ export interface UnzipDataType {
34
20
  files?: (ZipDataFilterType | string)[];
35
21
  output: string;
36
22
  verbose?: boolean;
37
- onStream?: (data: UnzipStreamDataType) => void;
23
+ onStream?: (data: UnzipStream) => void;
38
24
  }
39
- export declare type UnzipStreamDataType = {
25
+ export declare function buildArguments(filters: (ZipDataFilterType | string)[]): string[];
26
+ export declare function checkSSEOption(command?: string): Promise<boolean>;
27
+ declare type ListZipStream = {
28
+ Path?: string;
29
+ Folder?: string;
30
+ Size?: string;
31
+ "Packed Size"?: string;
32
+ Modified?: string;
33
+ Created?: string;
34
+ Accessed?: string;
35
+ Attributes?: string;
36
+ Encrypted?: string;
37
+ Comment?: string;
38
+ CRC?: string;
39
+ Method?: string;
40
+ Characteristics?: string;
41
+ "Host OS"?: string;
42
+ Version?: string;
43
+ Volume?: string;
44
+ Offset?: string;
45
+ };
46
+ export declare function listZip(data: {
47
+ command?: string;
48
+ path: string;
49
+ onStream: (item: ListZipStream) => void;
50
+ verbose?: boolean;
51
+ }): Promise<void>;
52
+ export declare type ZipStream = {
40
53
  type: "progress";
41
54
  data: {
42
55
  progress: number;
43
56
  files: number;
44
57
  path: string;
45
58
  };
59
+ } | {
60
+ type: "summary";
61
+ data: {
62
+ folders: number;
63
+ files: number;
64
+ };
46
65
  };
47
- export declare function buildArguments(filters: (ZipDataFilterType | string)[]): string[];
48
- export declare function checkSSEOption(command?: string): Promise<boolean>;
49
66
  export declare function zip(data: ZipDataType): Promise<{
50
67
  folders: number;
51
68
  files: number;
52
69
  }>;
70
+ export declare type UnzipStream = {
71
+ type: "progress";
72
+ data: {
73
+ progress: number;
74
+ files: number;
75
+ path: string;
76
+ };
77
+ };
53
78
  export declare function unzip(data: UnzipDataType): Promise<import("./process-util").ExecResultType>;
79
+ export {};
package/util/zip-util.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.unzip = exports.zip = exports.checkSSEOption = exports.buildArguments = void 0;
3
+ exports.unzip = exports.zip = exports.listZip = exports.checkSSEOption = exports.buildArguments = void 0;
4
4
  const process_util_1 = require("./process-util");
5
5
  const path_1 = require("path");
6
6
  function buildArguments(filters) {
@@ -30,36 +30,6 @@ function buildArguments(filters) {
30
30
  return args;
31
31
  }
32
32
  exports.buildArguments = buildArguments;
33
- function parseZipStream(chunk, buffer, cb) {
34
- const lines = chunk.replaceAll("\b", "").trim().split(/\r?\n/);
35
- for (const line of lines) {
36
- let matches = null;
37
- if ((matches = /^(\d+)% (\d+ )?\+/.exec(line))) {
38
- const path = line.slice(line.indexOf("+") + 1).trim();
39
- const progress = Number(matches[1]);
40
- if (!buffer.currentPaths)
41
- buffer.currentPaths = 0;
42
- if (path !== buffer.lastPath)
43
- buffer.currentPaths++;
44
- buffer.lastPath = path;
45
- cb({
46
- type: "progress",
47
- data: { progress, path, files: buffer.currentPaths },
48
- });
49
- }
50
- else if (line.startsWith("Add new data to archive:")) {
51
- const [, folders] = /(\d+) folders?/i.exec(line) || [, 0];
52
- const [, files] = /(\d+) files?/i.exec(line) || [, 0];
53
- cb({
54
- type: "summary",
55
- data: {
56
- folders: Number(folders),
57
- files: Number(files),
58
- },
59
- });
60
- }
61
- }
62
- }
63
33
  let checkSSEOptionResult;
64
34
  async function checkSSEOption(command = "7z") {
65
35
  const result = await (0, process_util_1.exec)(command);
@@ -68,12 +38,81 @@ async function checkSSEOption(command = "7z") {
68
38
  return (checkSSEOptionResult = result.stdout.includes(" -sse"));
69
39
  }
70
40
  exports.checkSSEOption = checkSSEOption;
41
+ const listZipLineEqChar = " = ";
42
+ function parseListZipLine(line, buffer) {
43
+ if (buffer.started) {
44
+ if (line === "") {
45
+ if (buffer.opened) {
46
+ const { stream } = buffer;
47
+ buffer.stream = {};
48
+ buffer.opened = false;
49
+ return stream;
50
+ }
51
+ }
52
+ else {
53
+ const separator = line.indexOf(listZipLineEqChar);
54
+ const key = line.slice(0, separator);
55
+ const value = line.slice(separator + listZipLineEqChar.length);
56
+ buffer.opened = true;
57
+ buffer.stream[key] = value;
58
+ }
59
+ }
60
+ else if (line.startsWith("----------")) {
61
+ buffer.started = true;
62
+ buffer.stream = {};
63
+ }
64
+ }
65
+ async function listZip(data) {
66
+ const buffer = {};
67
+ await (0, process_util_1.exec)(data.command ?? "7z", ["l", data.path, "-slt"], {}, {
68
+ log: {
69
+ exec: data.verbose ?? false,
70
+ stderr: data.verbose ?? false,
71
+ stdout: false,
72
+ },
73
+ onExitCodeError: (data, error) => (data.exitCode > 2 ? error : false),
74
+ stdout: {
75
+ parseLines: true,
76
+ onData: (line) => {
77
+ const stream = parseListZipLine(line, buffer);
78
+ if (stream) {
79
+ data.onStream?.(stream);
80
+ }
81
+ },
82
+ },
83
+ });
84
+ }
85
+ exports.listZip = listZip;
86
+ function parseZipLine(line) {
87
+ let matches = null;
88
+ if (!line.trim().length)
89
+ return;
90
+ if ((matches = /^\s*(\d+)% (\d+ )?\+/.exec(line))) {
91
+ const path = line.slice(line.indexOf("+") + 1).trim();
92
+ const progress = Number(matches[1]);
93
+ const files = Number(matches[2]);
94
+ return {
95
+ type: "progress",
96
+ data: { progress, path, files },
97
+ };
98
+ }
99
+ else if (line.startsWith("Add new data to archive:")) {
100
+ const [, folders] = /(\d+) folders?/i.exec(line) || [, 0];
101
+ const [, files] = /(\d+) files?/i.exec(line) || [, 0];
102
+ return {
103
+ type: "summary",
104
+ data: {
105
+ folders: Number(folders),
106
+ files: Number(files),
107
+ },
108
+ };
109
+ }
110
+ }
71
111
  async function zip(data) {
72
112
  let result = {
73
113
  folders: 0,
74
114
  files: 0,
75
115
  };
76
- let buffer = {};
77
116
  await (0, process_util_1.exec)(data.command ?? "7z", [
78
117
  "a",
79
118
  // https://sourceforge.net/p/sevenzip/bugs/2099/,
@@ -91,31 +130,29 @@ async function zip(data) {
91
130
  log: data.verbose ?? false,
92
131
  onExitCodeError: (data, error) => (data.exitCode > 2 ? error : false),
93
132
  stdout: {
94
- onData: (chunk) => {
95
- parseZipStream(chunk, buffer, (stream) => {
133
+ onData: (line) => {
134
+ const stream = parseZipLine(line);
135
+ if (stream) {
96
136
  data.onStream?.(stream);
97
137
  if (stream.type === "summary")
98
138
  result = stream.data;
99
- });
139
+ }
100
140
  },
101
141
  },
102
142
  });
103
143
  return result;
104
144
  }
105
145
  exports.zip = zip;
106
- function parseUnzipStream(chunk, cb) {
107
- const lines = chunk.trim().split(/\r?\n/g);
108
- for (const line of lines) {
109
- let matches = null;
110
- if ((matches = /^(\d+)% (\d+) \-/.exec(line))) {
111
- const progress = Number(matches[1]);
112
- const files = Number(matches[2]);
113
- const path = line.slice(line.indexOf("-") + 1).trim();
114
- cb({
115
- type: "progress",
116
- data: { progress, path, files },
117
- });
118
- }
146
+ function parseUnzipLine(line) {
147
+ let matches = null;
148
+ if ((matches = /^\s*(\d+)% (\d+) \-/.exec(line))) {
149
+ const progress = Number(matches[1]);
150
+ const files = Number(matches[2]);
151
+ const path = line.slice(line.indexOf("-") + 1).trim();
152
+ return {
153
+ type: "progress",
154
+ data: { progress, path, files },
155
+ };
119
156
  }
120
157
  }
121
158
  async function unzip(data) {
@@ -131,9 +168,11 @@ async function unzip(data) {
131
168
  stderr: { toExitCode: true },
132
169
  stdout: {
133
170
  ...(data.onStream && {
134
- onData: (chunk) => {
135
- if (data.onStream)
136
- parseUnzipStream(chunk, data.onStream);
171
+ parseLines: true,
172
+ onData: (line) => {
173
+ const stream = parseUnzipLine(line);
174
+ if (stream)
175
+ data.onStream(stream);
137
176
  },
138
177
  }),
139
178
  },