@datatruck/cli 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Action/BackupAction.d.ts +69 -34
- package/Action/BackupAction.js +284 -244
- package/Action/CleanCacheAction.d.ts +8 -4
- package/Action/CleanCacheAction.js +8 -5
- package/Action/ConfigAction.d.ts +12 -5
- package/Action/ConfigAction.js +14 -18
- package/Action/CopyAction.d.ts +49 -0
- package/Action/CopyAction.js +144 -0
- package/Action/InitAction.d.ts +3 -3
- package/Action/InitAction.js +9 -9
- package/Action/PruneAction.d.ts +9 -9
- package/Action/PruneAction.js +39 -23
- package/Action/RestoreAction.d.ts +48 -23
- package/Action/RestoreAction.js +158 -195
- package/Action/SnapshotsAction.d.ts +8 -8
- package/Action/SnapshotsAction.js +8 -8
- package/CHANGELOG.md +495 -0
- package/Command/BackupCommand.d.ts +6 -4
- package/Command/BackupCommand.js +9 -26
- package/Command/CleanCacheCommand.d.ts +4 -4
- package/Command/CleanCacheCommand.js +26 -5
- package/Command/CommandAbstract.d.ts +10 -7
- package/Command/CommandAbstract.js +4 -1
- package/Command/ConfigCommand.d.ts +6 -9
- package/Command/ConfigCommand.js +13 -8
- package/Command/CopyCommand.d.ts +15 -0
- package/Command/CopyCommand.js +61 -0
- package/Command/InitCommand.d.ts +4 -4
- package/Command/InitCommand.js +11 -15
- package/Command/PruneCommand.d.ts +3 -3
- package/Command/PruneCommand.js +13 -12
- package/Command/RestoreCommand.js +9 -17
- package/Command/SnapshotsCommand.d.ts +4 -4
- package/Command/SnapshotsCommand.js +16 -15
- package/Command/StartServerCommand.d.ts +3 -3
- package/Config/Config.d.ts +9 -0
- package/Config/Config.js +17 -0
- package/Config/PrunePolicyConfig.d.ts +2 -2
- package/Factory/CommandFactory.d.ts +27 -34
- package/Factory/CommandFactory.js +27 -54
- package/Factory/RepositoryFactory.d.ts +1 -1
- package/Factory/RepositoryFactory.js +3 -3
- package/Factory/TaskFactory.d.ts +1 -1
- package/Factory/TaskFactory.js +3 -3
- package/Repository/DatatruckRepository.d.ts +9 -8
- package/Repository/DatatruckRepository.js +42 -25
- package/Repository/GitRepository.d.ts +9 -8
- package/Repository/GitRepository.js +22 -25
- package/Repository/RepositoryAbstract.d.ts +39 -37
- package/Repository/RepositoryAbstract.js +4 -5
- package/Repository/ResticRepository.d.ts +9 -8
- package/Repository/ResticRepository.js +30 -28
- package/Task/GitTask.d.ts +6 -7
- package/Task/GitTask.js +24 -30
- package/Task/MariadbTask.d.ts +4 -5
- package/Task/MariadbTask.js +26 -32
- package/Task/MssqlTask.d.ts +5 -3
- package/Task/MssqlTask.js +11 -12
- package/Task/MysqlDumpTask.d.ts +10 -3
- package/Task/MysqlDumpTask.js +107 -31
- package/Task/ScriptTask.d.ts +23 -18
- package/Task/ScriptTask.js +34 -24
- package/Task/SqlDumpTaskAbstract.d.ts +8 -3
- package/Task/SqlDumpTaskAbstract.js +31 -19
- package/Task/TaskAbstract.d.ts +24 -25
- package/Task/TaskAbstract.js +6 -10
- package/cli.js +13 -5
- package/config.schema.json +86 -1
- package/package.json +4 -5
- package/utils/DataFormat.d.ts +23 -12
- package/utils/DataFormat.js +36 -14
- package/utils/cli.d.ts +2 -9
- package/utils/cli.js +9 -52
- package/utils/datatruck/client.d.ts +2 -0
- package/utils/datatruck/client.js +3 -0
- package/utils/datatruck/config.d.ts +2 -0
- package/utils/datatruck/config.js +18 -3
- package/utils/datatruck/paths.d.ts +5 -9
- package/utils/datatruck/paths.js +2 -2
- package/utils/datatruck/snapshot.d.ts +2 -2
- package/utils/date.d.ts +7 -3
- package/utils/date.js +22 -14
- package/utils/fs.d.ts +16 -11
- package/utils/fs.js +81 -48
- package/utils/list.d.ts +64 -0
- package/utils/list.js +145 -0
- package/utils/mysql.d.ts +2 -0
- package/utils/mysql.js +21 -2
- package/utils/process.d.ts +1 -0
- package/utils/process.js +24 -31
- package/utils/progress.d.ts +33 -0
- package/utils/progress.js +113 -0
- package/utils/steps.d.ts +11 -0
- package/utils/steps.js +22 -10
- package/utils/stream.d.ts +7 -0
- package/utils/stream.js +10 -0
- package/utils/string.d.ts +0 -1
- package/utils/string.js +1 -13
- package/utils/tar.d.ts +10 -3
- package/utils/tar.js +70 -44
- package/utils/temp.d.ts +26 -0
- package/utils/temp.js +133 -0
- package/utils/virtual-fs.d.ts +6 -2
- package/utils/virtual-fs.js +6 -0
- package/Action/BackupSessionsAction.d.ts +0 -13
- package/Action/BackupSessionsAction.js +0 -18
- package/Action/RestoreSessionsAction.d.ts +0 -13
- package/Action/RestoreSessionsAction.js +0 -18
- package/Command/BackupSessionsCommand.d.ts +0 -12
- package/Command/BackupSessionsCommand.js +0 -92
- package/Command/RestoreSessionsCommand.d.ts +0 -12
- package/Command/RestoreSessionsCommand.js +0 -91
- package/Decorator/EntityDecorator.d.ts +0 -11
- package/Decorator/EntityDecorator.js +0 -17
- package/Entity/BackupSessionEntity.d.ts +0 -6
- package/Entity/BackupSessionEntity.js +0 -25
- package/Entity/BackupSessionRepositoryEntity.d.ts +0 -6
- package/Entity/BackupSessionRepositoryEntity.js +0 -25
- package/Entity/BackupSessionTaskEntity.d.ts +0 -5
- package/Entity/BackupSessionTaskEntity.js +0 -24
- package/Entity/CrudEntityAbstract.d.ts +0 -5
- package/Entity/CrudEntityAbstract.js +0 -9
- package/Entity/RestoreSessionEntity.d.ts +0 -5
- package/Entity/RestoreSessionEntity.js +0 -24
- package/Entity/RestoreSessionRepositoryEntity.d.ts +0 -6
- package/Entity/RestoreSessionRepositoryEntity.js +0 -25
- package/Entity/RestoreSessionTaskEntity.d.ts +0 -5
- package/Entity/RestoreSessionTaskEntity.js +0 -24
- package/Entity/StateEntityAbstract.d.ts +0 -9
- package/Entity/StateEntityAbstract.js +0 -12
- package/Factory/EntityFactory.d.ts +0 -6
- package/Factory/EntityFactory.js +0 -40
- package/SessionDriver/ConsoleSessionDriver.d.ts +0 -42
- package/SessionDriver/ConsoleSessionDriver.js +0 -208
- package/SessionDriver/SessionDriverAbstract.d.ts +0 -77
- package/SessionDriver/SessionDriverAbstract.js +0 -28
- package/SessionDriver/SqliteSessionDriver.d.ts +0 -20
- package/SessionDriver/SqliteSessionDriver.js +0 -173
- package/SessionManager/BackupSessionManager.d.ts +0 -45
- package/SessionManager/BackupSessionManager.js +0 -218
- package/SessionManager/RestoreSessionManager.d.ts +0 -47
- package/SessionManager/RestoreSessionManager.js +0 -218
- package/SessionManager/SessionManagerAbstract.d.ts +0 -18
- package/SessionManager/SessionManagerAbstract.js +0 -36
- package/migrations/001-initial.sql +0 -98
- package/utils/entity.d.ts +0 -4
- package/utils/entity.js +0 -10
package/utils/fs.js
CHANGED
|
@@ -3,12 +3,12 @@ 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.
|
|
7
|
-
const globalData_1 = __importDefault(require("../globalData"));
|
|
6
|
+
exports.groupFiles = exports.ensureFreeDiskSpace = exports.checkFreeDiskSpace = exports.fetchDiskStats = exports.initEmptyDir = exports.tryRm = exports.safeRename = exports.fetchData = exports.countFileLines = exports.createWriteStreamPool = exports.createFileScanner = exports.createProgress = exports.cpy = exports.isNotFoundError = exports.updateFileStats = exports.copyFileWithStreams = exports.waitForClose = exports.writeGitIgnoreList = exports.fastglobToGitIgnore = exports.forEachFile = exports.readDir = exports.readPartialFile = exports.fastFolderSizeAsync = exports.findFile = exports.parsePackageFile = exports.parseFile = exports.parseFileExtensions = exports.writeJSONFile = exports.existsFile = exports.existsDir = exports.safeStat = exports.ensureExistsDir = exports.ensureSingleFile = exports.ensureEmptyDir = exports.mkdirIfNotExists = exports.isLocalDir = exports.isEmptyDir = exports.isWSLSystem = void 0;
|
|
8
7
|
const math_1 = require("./math");
|
|
9
8
|
const path_1 = require("./path");
|
|
9
|
+
const string_1 = require("./string");
|
|
10
10
|
const async_1 = require("async");
|
|
11
|
-
const
|
|
11
|
+
const bytes_1 = __importDefault(require("bytes"));
|
|
12
12
|
const fast_folder_size_1 = __importDefault(require("fast-folder-size"));
|
|
13
13
|
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
14
14
|
const fs_1 = require("fs");
|
|
@@ -53,6 +53,19 @@ async function ensureEmptyDir(path) {
|
|
|
53
53
|
throw new Error(`Dir is not empty: ${path}`);
|
|
54
54
|
}
|
|
55
55
|
exports.ensureEmptyDir = ensureEmptyDir;
|
|
56
|
+
async function ensureSingleFile(path) {
|
|
57
|
+
const files = await readDir(path);
|
|
58
|
+
if (files.length !== 1)
|
|
59
|
+
throw new Error(`Dir has not one file: ${files.length}`);
|
|
60
|
+
const [file] = files;
|
|
61
|
+
return (0, path_2.join)(path, file);
|
|
62
|
+
}
|
|
63
|
+
exports.ensureSingleFile = ensureSingleFile;
|
|
64
|
+
async function ensureExistsDir(path) {
|
|
65
|
+
if (!(await existsDir(path)))
|
|
66
|
+
throw new Error(`Dir is not crated: ${path}`);
|
|
67
|
+
}
|
|
68
|
+
exports.ensureExistsDir = ensureExistsDir;
|
|
56
69
|
async function safeStat(path) {
|
|
57
70
|
try {
|
|
58
71
|
return await (0, promises_1.stat)(path);
|
|
@@ -116,48 +129,10 @@ async function findFile(sourcePath, baseName, extensions, errorMessage = "Path n
|
|
|
116
129
|
return path;
|
|
117
130
|
}
|
|
118
131
|
exports.findFile = findFile;
|
|
119
|
-
function parentTmpDir() {
|
|
120
|
-
return (0, path_2.join)(globalData_1.default.tempDir, "datatruck-temp");
|
|
121
|
-
}
|
|
122
|
-
exports.parentTmpDir = parentTmpDir;
|
|
123
|
-
function sessionTmpDir() {
|
|
124
|
-
return (0, path_2.join)(parentTmpDir(), process.pid.toString());
|
|
125
|
-
}
|
|
126
|
-
exports.sessionTmpDir = sessionTmpDir;
|
|
127
|
-
function isTmpDir(path) {
|
|
128
|
-
return path.startsWith(sessionTmpDir()) && path.includes("datatruck-temp");
|
|
129
|
-
}
|
|
130
|
-
exports.isTmpDir = isTmpDir;
|
|
131
|
-
async function rmTmpDir(input) {
|
|
132
|
-
if (typeof input === "string") {
|
|
133
|
-
if (!isTmpDir(input))
|
|
134
|
-
throw new Error(`Path is not a temp dir: ${input}`);
|
|
135
|
-
await (0, promises_1.rm)(input, {
|
|
136
|
-
recursive: true,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
for (const path of input)
|
|
141
|
-
await rmTmpDir(path);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
exports.rmTmpDir = rmTmpDir;
|
|
145
|
-
function tmpDir(prefix, id) {
|
|
146
|
-
if (!id)
|
|
147
|
-
id = (0, crypto_1.randomUUID)().slice(0, 8);
|
|
148
|
-
return (0, path_2.join)(sessionTmpDir(), `${prefix}-${id}`);
|
|
149
|
-
}
|
|
150
|
-
exports.tmpDir = tmpDir;
|
|
151
132
|
async function fastFolderSizeAsync(path) {
|
|
152
133
|
return (await (0, util_1.promisify)(fast_folder_size_1.default)(path)) || 0;
|
|
153
134
|
}
|
|
154
135
|
exports.fastFolderSizeAsync = fastFolderSizeAsync;
|
|
155
|
-
async function mkTmpDir(prefix, id) {
|
|
156
|
-
const path = tmpDir(prefix, id);
|
|
157
|
-
await (0, promises_1.mkdir)(path, { recursive: true });
|
|
158
|
-
return path;
|
|
159
|
-
}
|
|
160
|
-
exports.mkTmpDir = mkTmpDir;
|
|
161
136
|
async function readPartialFile(path, positions) {
|
|
162
137
|
let result = "";
|
|
163
138
|
const statResult = await (0, promises_1.stat)(path);
|
|
@@ -316,7 +291,7 @@ async function cpy(options) {
|
|
|
316
291
|
const isDir = rawEntryPath.endsWith("/");
|
|
317
292
|
const entryPath = (0, path_2.normalize)(rawEntryPath);
|
|
318
293
|
const entrySourcePath = (0, path_2.resolve)((0, path_2.join)(basePath, rawEntryPath));
|
|
319
|
-
const entryTargetPath = (0, path_2.resolve)((0, path_2.join)(options.
|
|
294
|
+
const entryTargetPath = (0, path_2.resolve)((0, path_2.join)(options.outPath, rawEntryPath));
|
|
320
295
|
const onPathResult = await options?.onPath?.({
|
|
321
296
|
isDir,
|
|
322
297
|
entryPath,
|
|
@@ -414,7 +389,7 @@ function createProgress(options) {
|
|
|
414
389
|
return;
|
|
415
390
|
if (path && increment)
|
|
416
391
|
progress.current++;
|
|
417
|
-
|
|
392
|
+
options.onProgress({
|
|
418
393
|
relative: {
|
|
419
394
|
description,
|
|
420
395
|
payload: path,
|
|
@@ -434,14 +409,14 @@ async function createFileScanner(options) {
|
|
|
434
409
|
const progress = createProgress(options);
|
|
435
410
|
Object.assign(progress, {
|
|
436
411
|
progress: progress.update,
|
|
437
|
-
end:
|
|
412
|
+
end: () => {
|
|
438
413
|
if (!progress.disposed)
|
|
439
|
-
|
|
414
|
+
progress.update("Finished");
|
|
440
415
|
progress.disposed = true;
|
|
441
416
|
},
|
|
442
417
|
start: async (cb) => {
|
|
443
418
|
let lastTime = performance.now();
|
|
444
|
-
|
|
419
|
+
progress.update("Scanning files");
|
|
445
420
|
for await (const entry of pathIterator(stream)) {
|
|
446
421
|
if (cb) {
|
|
447
422
|
if (await cb(entry))
|
|
@@ -451,9 +426,9 @@ async function createFileScanner(options) {
|
|
|
451
426
|
progress.total++;
|
|
452
427
|
}
|
|
453
428
|
if (lastTime - performance.now() > 500)
|
|
454
|
-
|
|
429
|
+
progress.update("Scanning files");
|
|
455
430
|
}
|
|
456
|
-
|
|
431
|
+
progress.update("Scanned files");
|
|
457
432
|
},
|
|
458
433
|
});
|
|
459
434
|
const stream = fast_glob_1.default.stream(options.glob.include, {
|
|
@@ -575,3 +550,61 @@ async function tryRm(path) {
|
|
|
575
550
|
catch (_) { }
|
|
576
551
|
}
|
|
577
552
|
exports.tryRm = tryRm;
|
|
553
|
+
async function initEmptyDir(path) {
|
|
554
|
+
if (!path)
|
|
555
|
+
throw new Error(`Path is not defined`);
|
|
556
|
+
await mkdirIfNotExists(path);
|
|
557
|
+
await ensureEmptyDir(path);
|
|
558
|
+
return path;
|
|
559
|
+
}
|
|
560
|
+
exports.initEmptyDir = initEmptyDir;
|
|
561
|
+
async function fetchDiskStats(path) {
|
|
562
|
+
const fs = await (0, promises_1.statfs)(path);
|
|
563
|
+
const total = fs.bsize * fs.blocks;
|
|
564
|
+
const free = fs.bsize * fs.bavail;
|
|
565
|
+
return { total, free };
|
|
566
|
+
}
|
|
567
|
+
exports.fetchDiskStats = fetchDiskStats;
|
|
568
|
+
async function checkFreeDiskSpace(stat, inSize) {
|
|
569
|
+
const humanSize = typeof inSize === "number" ? (0, bytes_1.default)(inSize) : inSize;
|
|
570
|
+
const size = bytes_1.default.parse(inSize);
|
|
571
|
+
if (stat.free < size)
|
|
572
|
+
throw new Error(`Free disk space is less than ${humanSize}: ${(0, bytes_1.default)(stat.free)}/${(0, bytes_1.default)(stat.total)}`);
|
|
573
|
+
}
|
|
574
|
+
exports.checkFreeDiskSpace = checkFreeDiskSpace;
|
|
575
|
+
async function ensureFreeDiskSpace(input, inSize) {
|
|
576
|
+
if (Array.isArray(input)) {
|
|
577
|
+
for (const path of input) {
|
|
578
|
+
const stat = await fetchDiskStats(path);
|
|
579
|
+
await checkFreeDiskSpace(stat, inSize);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
await checkFreeDiskSpace(input, inSize);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
exports.ensureFreeDiskSpace = ensureFreeDiskSpace;
|
|
587
|
+
function groupFiles(inFiles, suffixes, gzSuffix = ".tar.gz") {
|
|
588
|
+
const compressed = {};
|
|
589
|
+
if (suffixes) {
|
|
590
|
+
const validGzSuffixes = suffixes.map((f) => `${f}${gzSuffix}`);
|
|
591
|
+
inFiles = inFiles.filter((f) => (0, string_1.endsWith)(f, suffixes) || (0, string_1.endsWith)(f, validGzSuffixes));
|
|
592
|
+
}
|
|
593
|
+
const grouped = inFiles.reduce((items, name) => {
|
|
594
|
+
const key = name.endsWith(gzSuffix)
|
|
595
|
+
? name.slice(0, -gzSuffix.length)
|
|
596
|
+
: name;
|
|
597
|
+
if (!items[key])
|
|
598
|
+
items[key] = [];
|
|
599
|
+
items[key].push(name);
|
|
600
|
+
return items;
|
|
601
|
+
}, {});
|
|
602
|
+
for (const key in grouped) {
|
|
603
|
+
const suffixFile = grouped[key].find((v) => v.endsWith(gzSuffix));
|
|
604
|
+
if (suffixFile) {
|
|
605
|
+
compressed[key] = suffixFile;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return [Object.keys(grouped), compressed];
|
|
609
|
+
}
|
|
610
|
+
exports.groupFiles = groupFiles;
|
package/utils/list.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Timer } from "./date";
|
|
2
|
+
import { ProgressManager } from "./progress";
|
|
3
|
+
import { Streams } from "./stream";
|
|
4
|
+
import { Listr, ListrGetRendererClassFromValue, ListrLogger, ListrTask, ListrTaskWrapper } from "listr2";
|
|
5
|
+
export declare class List3Logger<Levels extends string = string> extends ListrLogger<Levels> {
|
|
6
|
+
constructor(options?: {
|
|
7
|
+
streams?: Partial<Streams>;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export type Listr3Context = Record<string, Record<string, any>>;
|
|
11
|
+
type KeyIndex = number | string | (string | number)[];
|
|
12
|
+
type Listr3Task<T extends Listr3Context, K extends keyof T> = {
|
|
13
|
+
key: K;
|
|
14
|
+
keyIndex?: KeyIndex;
|
|
15
|
+
data: T[K];
|
|
16
|
+
title: string | {
|
|
17
|
+
initial: string;
|
|
18
|
+
started?: string;
|
|
19
|
+
failed?: string;
|
|
20
|
+
completed?: string;
|
|
21
|
+
};
|
|
22
|
+
runWrapper?: (cb: () => any) => any;
|
|
23
|
+
run: (task: ListrTaskWrapper<any, any, any>, data: T[K]) => Promise<void | ListrTask[] | Listr | undefined> | void | undefined | ListrTask[] | Listr;
|
|
24
|
+
exitOnError?: boolean;
|
|
25
|
+
enabled?: boolean;
|
|
26
|
+
skip?: boolean;
|
|
27
|
+
};
|
|
28
|
+
type List3TaskResultObject<K, D extends Record<string, any>> = {
|
|
29
|
+
key: K;
|
|
30
|
+
keyIndex?: string;
|
|
31
|
+
data: D;
|
|
32
|
+
elapsed: number;
|
|
33
|
+
error?: Error;
|
|
34
|
+
};
|
|
35
|
+
type SummaryResult = List3TaskResultObject<"summary", {
|
|
36
|
+
errors: number;
|
|
37
|
+
}>;
|
|
38
|
+
export type Listr3TaskResult<T extends Listr3Context> = {
|
|
39
|
+
[K in keyof T]: List3TaskResultObject<K, T[K]>;
|
|
40
|
+
}[keyof T];
|
|
41
|
+
export type Listr3TaskResultEnd<T extends Listr3Context> = Listr3TaskResult<T> | SummaryResult;
|
|
42
|
+
export declare class Listr3<T extends Listr3Context> extends Listr<void, "default", "simple"> {
|
|
43
|
+
readonly $options: {
|
|
44
|
+
streams?: Streams;
|
|
45
|
+
progressManager?: ProgressManager;
|
|
46
|
+
};
|
|
47
|
+
readonly resultMap: Record<string, Listr3TaskResult<T>>;
|
|
48
|
+
readonly resultList: Listr3TaskResult<T>[];
|
|
49
|
+
protected execTimer: Timer;
|
|
50
|
+
constructor($options: {
|
|
51
|
+
streams?: Streams;
|
|
52
|
+
progressManager?: ProgressManager;
|
|
53
|
+
});
|
|
54
|
+
private serializeKeyIndex;
|
|
55
|
+
private createResultIndex;
|
|
56
|
+
result(key: keyof T, keyIndex?: KeyIndex): Listr3TaskResult<T>;
|
|
57
|
+
$task<K extends keyof T>(item: Listr3Task<T, K>): ListrTask;
|
|
58
|
+
$tasks<K extends keyof T>(...items: (Listr3Task<T, K> | ListrTask | false)[]): ListrTask[];
|
|
59
|
+
add(tasks: ListrTask<void, ListrGetRendererClassFromValue<"default">> | ListrTask<void, ListrGetRendererClassFromValue<"default">>[]): this;
|
|
60
|
+
getSummaryResult(): SummaryResult;
|
|
61
|
+
getResult(): (SummaryResult | Listr3TaskResult<T>)[];
|
|
62
|
+
exec(): Promise<(Listr3TaskResult<T> | SummaryResult)[]>;
|
|
63
|
+
}
|
|
64
|
+
export {};
|
package/utils/list.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Listr3 = exports.List3Logger = void 0;
|
|
4
|
+
const date_1 = require("./date");
|
|
5
|
+
const stream_1 = require("./stream");
|
|
6
|
+
const listr2_1 = require("listr2");
|
|
7
|
+
class List3Logger extends listr2_1.ListrLogger {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
const streams = (0, stream_1.createStreams)(options.streams);
|
|
10
|
+
super({
|
|
11
|
+
processOutput: new listr2_1.ProcessOutput(streams.stdout, streams.stderr),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
exports.List3Logger = List3Logger;
|
|
16
|
+
class Listr3 extends listr2_1.Listr {
|
|
17
|
+
$options;
|
|
18
|
+
resultMap = {};
|
|
19
|
+
resultList = [];
|
|
20
|
+
execTimer;
|
|
21
|
+
constructor($options) {
|
|
22
|
+
super([], {
|
|
23
|
+
renderer: "default",
|
|
24
|
+
collectErrors: "minimal",
|
|
25
|
+
...($options.progressManager && {
|
|
26
|
+
fallbackRendererCondition: () => !$options.progressManager.tty,
|
|
27
|
+
}),
|
|
28
|
+
fallbackRenderer: "simple",
|
|
29
|
+
fallbackRendererOptions: {
|
|
30
|
+
logger: new List3Logger(),
|
|
31
|
+
timestamp: listr2_1.PRESET_TIMESTAMP,
|
|
32
|
+
timer: listr2_1.PRESET_TIMER,
|
|
33
|
+
},
|
|
34
|
+
rendererOptions: {
|
|
35
|
+
logger: new List3Logger(),
|
|
36
|
+
collapseSubtasks: false,
|
|
37
|
+
collapseErrors: false,
|
|
38
|
+
timer: listr2_1.PRESET_TIMER,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
this.$options = $options;
|
|
42
|
+
this.execTimer = (0, date_1.createTimer)();
|
|
43
|
+
}
|
|
44
|
+
serializeKeyIndex(keyIndex) {
|
|
45
|
+
return keyIndex
|
|
46
|
+
? Array.isArray(keyIndex)
|
|
47
|
+
? keyIndex.map((k) => k.toString())
|
|
48
|
+
: [keyIndex.toString()]
|
|
49
|
+
: [];
|
|
50
|
+
}
|
|
51
|
+
createResultIndex(key, keyIndex) {
|
|
52
|
+
return [key, ...this.serializeKeyIndex(keyIndex)].join(".");
|
|
53
|
+
}
|
|
54
|
+
result(key, keyIndex) {
|
|
55
|
+
const index = this.createResultIndex(key, keyIndex);
|
|
56
|
+
const result = this.resultMap[index];
|
|
57
|
+
if (!result)
|
|
58
|
+
throw new Error(`Task result not found: ${index}`);
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
$task(item) {
|
|
62
|
+
const index = this.createResultIndex(item.key, item.keyIndex);
|
|
63
|
+
if (this.resultMap[index])
|
|
64
|
+
throw new Error(`Duplicated task index: ${index}`);
|
|
65
|
+
this.resultMap[index] = {
|
|
66
|
+
key: item.key,
|
|
67
|
+
keyIndex: item.keyIndex
|
|
68
|
+
? this.serializeKeyIndex(item.keyIndex).join(".")
|
|
69
|
+
: undefined,
|
|
70
|
+
elapsed: 0,
|
|
71
|
+
error: undefined,
|
|
72
|
+
data: item.data,
|
|
73
|
+
};
|
|
74
|
+
this.resultList.push(this.resultMap[index]);
|
|
75
|
+
const title = typeof item.title === "string" ? { initial: item.title } : item.title;
|
|
76
|
+
return {
|
|
77
|
+
title: title.initial,
|
|
78
|
+
exitOnError: item.exitOnError,
|
|
79
|
+
enabled: item.enabled,
|
|
80
|
+
skip: item.skip,
|
|
81
|
+
task: async (_, task) => {
|
|
82
|
+
const result = this.result(item.key, item.keyIndex);
|
|
83
|
+
if (title.started)
|
|
84
|
+
task.title = title.started;
|
|
85
|
+
const timer = (0, date_1.createTimer)();
|
|
86
|
+
if (title)
|
|
87
|
+
try {
|
|
88
|
+
const runResult = item.runWrapper
|
|
89
|
+
? await item.runWrapper(async () => await item.run(task, result.data))
|
|
90
|
+
: await item.run(task, result.data);
|
|
91
|
+
if (title.completed)
|
|
92
|
+
task.title = title.completed;
|
|
93
|
+
return Array.isArray(runResult)
|
|
94
|
+
? task.newListr(runResult)
|
|
95
|
+
: runResult;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
result.error = error;
|
|
99
|
+
if (title.failed)
|
|
100
|
+
task.title = title.failed;
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
result.elapsed = timer.elapsed();
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
$tasks(...items) {
|
|
110
|
+
return items
|
|
111
|
+
.map((item) => (item ? ("key" in item ? this.$task(item) : item) : null))
|
|
112
|
+
.filter(Boolean);
|
|
113
|
+
}
|
|
114
|
+
add(tasks) {
|
|
115
|
+
super.add(tasks);
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
getSummaryResult() {
|
|
119
|
+
return {
|
|
120
|
+
key: "summary",
|
|
121
|
+
elapsed: this.execTimer.elapsed(),
|
|
122
|
+
data: {
|
|
123
|
+
errors: this.resultList.filter((i) => i.error).length,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
getResult() {
|
|
128
|
+
return [...this.resultList, this.getSummaryResult()];
|
|
129
|
+
}
|
|
130
|
+
async exec() {
|
|
131
|
+
try {
|
|
132
|
+
this.$options.progressManager?.start();
|
|
133
|
+
this.execTimer.reset();
|
|
134
|
+
await super.run();
|
|
135
|
+
return this.getResult();
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
this.$options.progressManager?.dispose();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.Listr3 = Listr3;
|
package/utils/mysql.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type MysqlCliOptions = {
|
|
|
9
9
|
verbose?: boolean;
|
|
10
10
|
database?: string;
|
|
11
11
|
};
|
|
12
|
+
export declare function assertDumpFile(path: string): Promise<void>;
|
|
12
13
|
export declare function createMysqlCli(options: MysqlCliOptions): Promise<{
|
|
13
14
|
options: MysqlCliOptions;
|
|
14
15
|
initSharedDir: (sharedDir?: string) => Promise<string>;
|
|
@@ -28,6 +29,7 @@ export declare function createMysqlCli(options: MysqlCliOptions): Promise<{
|
|
|
28
29
|
totalBytes: number;
|
|
29
30
|
}) => void) | undefined;
|
|
30
31
|
}) => Promise<[void, import("./process").ExecResultType]>;
|
|
32
|
+
assertDumpFile: typeof assertDumpFile;
|
|
31
33
|
fetchTableNames: (database: string, include?: string[], exclude?: string[]) => Promise<string[]>;
|
|
32
34
|
importFile: (input: {
|
|
33
35
|
path: string;
|
package/utils/mysql.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createMysqlCli = void 0;
|
|
3
|
+
exports.createMysqlCli = exports.assertDumpFile = void 0;
|
|
4
4
|
const AppError_1 = require("../Error/AppError");
|
|
5
5
|
const cli_1 = require("./cli");
|
|
6
6
|
const fs_1 = require("./fs");
|
|
7
7
|
const process_1 = require("./process");
|
|
8
8
|
const string_1 = require("./string");
|
|
9
|
+
const temp_1 = require("./temp");
|
|
9
10
|
const crypto_1 = require("crypto");
|
|
10
11
|
const fs_2 = require("fs");
|
|
11
12
|
const promises_1 = require("fs/promises");
|
|
@@ -22,6 +23,23 @@ function flatQuery(query, params) {
|
|
|
22
23
|
})
|
|
23
24
|
: query;
|
|
24
25
|
}
|
|
26
|
+
async function assertDumpFile(path) {
|
|
27
|
+
const headerContents = await (0, fs_1.readPartialFile)(path, [0, 100]);
|
|
28
|
+
const footerContents = await (0, fs_1.readPartialFile)(path, [-100]);
|
|
29
|
+
const successHeader = headerContents.split(/\r?\n/).some((line) => {
|
|
30
|
+
const firstLine = line.trim().toLowerCase();
|
|
31
|
+
return (firstLine.startsWith("-- mysql dump") ||
|
|
32
|
+
firstLine.startsWith("-- mariadb dump"));
|
|
33
|
+
});
|
|
34
|
+
if (!successHeader)
|
|
35
|
+
throw new AppError_1.AppError("No start line found");
|
|
36
|
+
const successFooter = footerContents
|
|
37
|
+
.split(/\r?\n/)
|
|
38
|
+
.some((line) => line.trim().toLowerCase().startsWith("-- dump completed"));
|
|
39
|
+
if (!successFooter)
|
|
40
|
+
throw new AppError_1.AppError("No end line found (incomplete backup)");
|
|
41
|
+
}
|
|
42
|
+
exports.assertDumpFile = assertDumpFile;
|
|
25
43
|
async function createMysqlCli(options) {
|
|
26
44
|
let sqlConfigPath;
|
|
27
45
|
const password = (await (0, fs_1.fetchData)(options.password, (p) => p.path)) ?? "";
|
|
@@ -35,7 +53,7 @@ async function createMysqlCli(options) {
|
|
|
35
53
|
async function createSqlConfig() {
|
|
36
54
|
if (sqlConfigPath)
|
|
37
55
|
return sqlConfigPath;
|
|
38
|
-
const dir = await (0,
|
|
56
|
+
const dir = await (0, temp_1.mkTmpDir)("mysql", "config");
|
|
39
57
|
const password = await (0, fs_1.fetchData)(options.password, (p) => p.path);
|
|
40
58
|
const data = [
|
|
41
59
|
`[client]`,
|
|
@@ -266,6 +284,7 @@ async function createMysqlCli(options) {
|
|
|
266
284
|
changeDatabase,
|
|
267
285
|
fetchAll,
|
|
268
286
|
dump,
|
|
287
|
+
assertDumpFile,
|
|
269
288
|
fetchTableNames,
|
|
270
289
|
importFile,
|
|
271
290
|
isDatabaseEmpty,
|
package/utils/process.d.ts
CHANGED
package/utils/process.js
CHANGED
|
@@ -189,7 +189,7 @@ async function exec(command, argv = [], options = null, settings = {}) {
|
|
|
189
189
|
});
|
|
190
190
|
}
|
|
191
191
|
if (typeof options?.cwd === "string" && !(await (0, fs_1.existsDir)(options.cwd)))
|
|
192
|
-
|
|
192
|
+
return reject(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);
|
|
195
195
|
const totalBytes = fileInfo.size;
|
|
@@ -216,6 +216,8 @@ async function exec(command, argv = [], options = null, settings = {}) {
|
|
|
216
216
|
finishListeners++;
|
|
217
217
|
if (settings.stdout?.parseLines)
|
|
218
218
|
finishListeners++;
|
|
219
|
+
if (settings.stderr?.parseLines)
|
|
220
|
+
finishListeners++;
|
|
219
221
|
let streamError;
|
|
220
222
|
const tryFinish = () => {
|
|
221
223
|
if (!--finishListeners)
|
|
@@ -282,10 +284,18 @@ async function exec(command, argv = [], options = null, settings = {}) {
|
|
|
282
284
|
pipe.stream.pipe(p.stdin);
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
287
|
+
const stdConfig = {
|
|
288
|
+
stdout: { log: log.stdout, settings: settings.stdout },
|
|
289
|
+
stderr: { log: log.stderr, settings: settings.stderr },
|
|
290
|
+
};
|
|
291
|
+
for (const inType in stdConfig) {
|
|
292
|
+
const type = inType;
|
|
293
|
+
const enabled = log[type] || settings[type];
|
|
294
|
+
if (!enabled)
|
|
295
|
+
continue;
|
|
296
|
+
if (!p[type])
|
|
297
|
+
throw new Error(`${type} is not defined`);
|
|
298
|
+
const parseLines = settings[type]?.parseLines;
|
|
289
299
|
const skipEmptyLines = parseLines === "skip-empty";
|
|
290
300
|
const onData = (inData) => {
|
|
291
301
|
let data = inData.toString();
|
|
@@ -294,46 +304,29 @@ async function exec(command, argv = [], options = null, settings = {}) {
|
|
|
294
304
|
return;
|
|
295
305
|
data = `${inData}\n`;
|
|
296
306
|
}
|
|
297
|
-
if (log
|
|
307
|
+
if (log[type])
|
|
298
308
|
logExecStdout({
|
|
299
309
|
data,
|
|
300
310
|
stderr: log.allToStderr,
|
|
301
311
|
colorize: log.colorize,
|
|
302
312
|
});
|
|
303
|
-
if (settings
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
313
|
+
if (settings[type]?.save ||
|
|
314
|
+
(type === "stderr" && settings[type]?.toExitCode))
|
|
315
|
+
spawnData[type] += data;
|
|
316
|
+
if (settings[type]?.onData)
|
|
317
|
+
settings[type].onData(inData.toString());
|
|
307
318
|
};
|
|
308
319
|
if (parseLines) {
|
|
309
320
|
const rl = (0, readline_1.createInterface)({
|
|
310
|
-
input: p
|
|
321
|
+
input: p[type],
|
|
311
322
|
});
|
|
312
323
|
rl.on("line", onData);
|
|
313
324
|
rl.on("close", tryFinish);
|
|
314
325
|
}
|
|
315
|
-
else if (log
|
|
316
|
-
|
|
317
|
-
settings.stdout?.onData) {
|
|
318
|
-
p.stdout.on("data", onData);
|
|
326
|
+
else if (log[type] || settings[type]?.save || settings[type]?.onData) {
|
|
327
|
+
p[type].on("data", onData);
|
|
319
328
|
}
|
|
320
329
|
}
|
|
321
|
-
if (log.stderr || settings.stderr) {
|
|
322
|
-
if (!p.stderr)
|
|
323
|
-
throw new Error(`stderr is not defined`);
|
|
324
|
-
p.stderr.on("data", (data) => {
|
|
325
|
-
if (log.stderr)
|
|
326
|
-
logExecStdout({
|
|
327
|
-
data: data.toString(),
|
|
328
|
-
stderr: log.allToStderr,
|
|
329
|
-
colorize: log.colorize,
|
|
330
|
-
});
|
|
331
|
-
if (settings.stderr?.save || settings.stderr?.toExitCode)
|
|
332
|
-
spawnData.stderr += data.toString();
|
|
333
|
-
if (settings.stderr?.onData)
|
|
334
|
-
settings.stderr.onData(data.toString());
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
330
|
p.on("error", (error) => (spawnError = error)).on("close", (exitCode) => {
|
|
338
331
|
spawnData.exitCode = exitCode ?? 0;
|
|
339
332
|
if (pipe?.stream instanceof fs_2.WriteStream)
|
package/utils/progress.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Timer } from "./date";
|
|
1
3
|
export type ProgressStats = {
|
|
2
4
|
percent?: number;
|
|
3
5
|
total?: number;
|
|
@@ -10,3 +12,34 @@ export type Progress = {
|
|
|
10
12
|
absolute?: ProgressStats;
|
|
11
13
|
relative?: ProgressStats;
|
|
12
14
|
};
|
|
15
|
+
export declare class ProgressManager {
|
|
16
|
+
readonly options: {
|
|
17
|
+
verbose?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* @default true
|
|
20
|
+
*/
|
|
21
|
+
tty?: boolean | "auto";
|
|
22
|
+
enabled?: boolean | "auto" | "interval";
|
|
23
|
+
interval?: number;
|
|
24
|
+
};
|
|
25
|
+
protected timer: Timer;
|
|
26
|
+
protected interval: Timer | undefined;
|
|
27
|
+
protected keydownListener: ((data: Buffer) => void) | undefined;
|
|
28
|
+
readonly tty: boolean;
|
|
29
|
+
readonly enabled: boolean | "interval";
|
|
30
|
+
constructor(options: {
|
|
31
|
+
verbose?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* @default true
|
|
34
|
+
*/
|
|
35
|
+
tty?: boolean | "auto";
|
|
36
|
+
enabled?: boolean | "auto" | "interval";
|
|
37
|
+
interval?: number;
|
|
38
|
+
});
|
|
39
|
+
elapsed(): number;
|
|
40
|
+
start(): void;
|
|
41
|
+
dispose(): void;
|
|
42
|
+
update(progress: Progress, cb: (text: string) => void): void;
|
|
43
|
+
}
|
|
44
|
+
export declare function renderProgress(progress: Progress, bar?: boolean): string[];
|
|
45
|
+
export declare function renderProgressStats(stats: ProgressStats, progressBar?: boolean): string;
|