@webtypen/webframez-core 0.3.56 → 0.3.57
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.
|
@@ -38,6 +38,10 @@ export declare class BackupManager {
|
|
|
38
38
|
private writeContentManifest;
|
|
39
39
|
private backupDatabase;
|
|
40
40
|
private buildArtifact;
|
|
41
|
+
private statusLogPath;
|
|
42
|
+
private runStatus;
|
|
43
|
+
private statusLogOutputs;
|
|
44
|
+
private writeStatusLog;
|
|
41
45
|
run(key: string, options?: BackupRunOptions): Promise<BackupRunResult>;
|
|
42
46
|
private downloadRestoreArtifact;
|
|
43
47
|
private restoreChain;
|
|
@@ -448,6 +448,83 @@ class BackupManager {
|
|
|
448
448
|
type: "directory",
|
|
449
449
|
};
|
|
450
450
|
}
|
|
451
|
+
statusLogPath(backupType) {
|
|
452
|
+
const config = backupType.statusLog;
|
|
453
|
+
if (!config) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
if (typeof config === "string") {
|
|
457
|
+
return (0, BackupPathUtils_1.resolveProjectPath)(config);
|
|
458
|
+
}
|
|
459
|
+
if (config.enabled === false) {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
return (0, BackupPathUtils_1.resolveProjectPath)(config.path);
|
|
463
|
+
}
|
|
464
|
+
runStatus(result, error) {
|
|
465
|
+
if (error) {
|
|
466
|
+
return "failed";
|
|
467
|
+
}
|
|
468
|
+
if (result.outputs.find((output) => output.status === "failed")) {
|
|
469
|
+
return "failed";
|
|
470
|
+
}
|
|
471
|
+
if (result.outputs.length > 0 && result.outputs.every((output) => output.status === "skipped")) {
|
|
472
|
+
return "skipped";
|
|
473
|
+
}
|
|
474
|
+
return "success";
|
|
475
|
+
}
|
|
476
|
+
statusLogOutputs(result) {
|
|
477
|
+
return result.outputs.map((output) => ({
|
|
478
|
+
driver: output.driver,
|
|
479
|
+
status: output.status,
|
|
480
|
+
path: output.path,
|
|
481
|
+
payload: output.payload,
|
|
482
|
+
error: output.error,
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
writeStatusLog(backupType, result, options, error) {
|
|
486
|
+
if (result.dryRun) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const filepath = this.statusLogPath(backupType);
|
|
490
|
+
if (!filepath) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
const previous = fs_1.default.existsSync(filepath) ? safeReadJson(filepath) || {} : {};
|
|
495
|
+
const status = this.runStatus(result, error);
|
|
496
|
+
const runAt = result.endedAt || new Date().toISOString();
|
|
497
|
+
const outputs = this.statusLogOutputs(result);
|
|
498
|
+
const lastRun = {
|
|
499
|
+
run_at: runAt,
|
|
500
|
+
run_status: status,
|
|
501
|
+
run_outputs: outputs,
|
|
502
|
+
};
|
|
503
|
+
if (error) {
|
|
504
|
+
lastRun.run_error = error instanceof Error ? error.message : String(error);
|
|
505
|
+
}
|
|
506
|
+
const statusLog = {
|
|
507
|
+
last_run_at: runAt,
|
|
508
|
+
last_run_status: status,
|
|
509
|
+
last_run_outputs: outputs,
|
|
510
|
+
last_success_run: previous.last_success_run || null,
|
|
511
|
+
};
|
|
512
|
+
if (error) {
|
|
513
|
+
statusLog.last_run_error = lastRun.run_error;
|
|
514
|
+
}
|
|
515
|
+
if (status === "success") {
|
|
516
|
+
statusLog.last_success_run = lastRun;
|
|
517
|
+
}
|
|
518
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filepath), { recursive: true });
|
|
519
|
+
fs_1.default.writeFileSync(filepath, JSON.stringify(statusLog, null, 4), "utf-8");
|
|
520
|
+
}
|
|
521
|
+
catch (e) {
|
|
522
|
+
this.log(options, "Failed to write backup status log", {
|
|
523
|
+
path: filepath,
|
|
524
|
+
error: e instanceof Error ? e.message : String(e),
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
451
528
|
run(key, options) {
|
|
452
529
|
var _a, _b, _c;
|
|
453
530
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -477,113 +554,121 @@ class BackupManager {
|
|
|
477
554
|
files: [],
|
|
478
555
|
databases: [],
|
|
479
556
|
};
|
|
480
|
-
this.log(options, "Collecting file sources");
|
|
481
|
-
const fileEntries = normalizeArray(backupType.files).flatMap((source) => this.collectFilesFromSource(source, options));
|
|
482
|
-
const fileSize = fileEntries.reduce((sum, entry) => sum + entry.size, 0);
|
|
483
|
-
this.log(options, `Collected ${fileEntries.length} file(s)`, { size: fileSize });
|
|
484
|
-
const incremental = yield this.buildBackupManifest(key, id, now, artifactFilename, backupType, fileEntries, options);
|
|
485
|
-
result.kind = incremental.manifest.kind;
|
|
486
|
-
result.chainId = incremental.manifest.chainId;
|
|
487
|
-
result.parentBackupId = incremental.manifest.parentBackupId;
|
|
488
|
-
result.backupManifest = incremental.manifest;
|
|
489
|
-
result.files = incremental.filesToCopy.map((entry) => ({
|
|
490
|
-
from: entry.source,
|
|
491
|
-
to: entry.target,
|
|
492
|
-
size: entry.size,
|
|
493
|
-
}));
|
|
494
|
-
if (this.incrementalEnabled(backupType)) {
|
|
495
|
-
this.log(options, `Prepared ${incremental.manifest.kind} backup`, {
|
|
496
|
-
upserted: incremental.manifest.files.upserted.length,
|
|
497
|
-
deleted: incremental.manifest.files.deleted.length,
|
|
498
|
-
parentBackupId: incremental.manifest.parentBackupId,
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
if (options === null || options === void 0 ? void 0 : options.dryRun) {
|
|
502
|
-
this.log(options, "Dry run enabled; skipping artifact and output writes");
|
|
503
|
-
result.outputs = outputs.map((output) => ({
|
|
504
|
-
driver: output.driver,
|
|
505
|
-
status: "skipped",
|
|
506
|
-
path: output.path,
|
|
507
|
-
payload: { dryRun: true },
|
|
508
|
-
}));
|
|
509
|
-
result.databases = normalizeArray(backupType.databases).map((source) => ({
|
|
510
|
-
connection: source.connection,
|
|
511
|
-
to: (0, BackupPathUtils_1.normalizeBackupPath)(source.to || `database/${source.connection || "default"}`),
|
|
512
|
-
}));
|
|
513
|
-
result.endedAt = new Date().toISOString();
|
|
514
|
-
return result;
|
|
515
|
-
}
|
|
516
|
-
fs_1.default.rmSync(workRunDir, { recursive: true, force: true });
|
|
517
|
-
fs_1.default.mkdirSync(contentDir, { recursive: true });
|
|
518
557
|
try {
|
|
519
|
-
this.log(options,
|
|
520
|
-
this.
|
|
521
|
-
|
|
522
|
-
this.log(options,
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
558
|
+
this.log(options, "Collecting file sources");
|
|
559
|
+
const fileEntries = normalizeArray(backupType.files).flatMap((source) => this.collectFilesFromSource(source, options));
|
|
560
|
+
const fileSize = fileEntries.reduce((sum, entry) => sum + entry.size, 0);
|
|
561
|
+
this.log(options, `Collected ${fileEntries.length} file(s)`, { size: fileSize });
|
|
562
|
+
const incremental = yield this.buildBackupManifest(key, id, now, artifactFilename, backupType, fileEntries, options);
|
|
563
|
+
result.kind = incremental.manifest.kind;
|
|
564
|
+
result.chainId = incremental.manifest.chainId;
|
|
565
|
+
result.parentBackupId = incremental.manifest.parentBackupId;
|
|
566
|
+
result.backupManifest = incremental.manifest;
|
|
567
|
+
result.files = incremental.filesToCopy.map((entry) => ({
|
|
568
|
+
from: entry.source,
|
|
569
|
+
to: entry.target,
|
|
570
|
+
size: entry.size,
|
|
571
|
+
}));
|
|
572
|
+
if (this.incrementalEnabled(backupType)) {
|
|
573
|
+
this.log(options, `Prepared ${incremental.manifest.kind} backup`, {
|
|
574
|
+
upserted: incremental.manifest.files.upserted.length,
|
|
575
|
+
deleted: incremental.manifest.files.deleted.length,
|
|
576
|
+
parentBackupId: incremental.manifest.parentBackupId,
|
|
577
|
+
});
|
|
531
578
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
const retention = mergeRetention((_a = this.getConfig().defaults) === null || _a === void 0 ? void 0 : _a.retention, backupType.retention, output.retention);
|
|
538
|
-
const driver = BackupOutputDrivers_1.BackupOutputDrivers.get(output.driver);
|
|
539
|
-
this.log(options, `Writing artifact to '${output.driver}' output`, {
|
|
579
|
+
if (options === null || options === void 0 ? void 0 : options.dryRun) {
|
|
580
|
+
this.log(options, "Dry run enabled; skipping artifact and output writes");
|
|
581
|
+
result.outputs = outputs.map((output) => ({
|
|
582
|
+
driver: output.driver,
|
|
583
|
+
status: "skipped",
|
|
540
584
|
path: output.path,
|
|
541
|
-
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
585
|
+
payload: { dryRun: true },
|
|
586
|
+
}));
|
|
587
|
+
result.databases = normalizeArray(backupType.databases).map((source) => ({
|
|
588
|
+
connection: source.connection,
|
|
589
|
+
to: (0, BackupPathUtils_1.normalizeBackupPath)(source.to || `database/${source.connection || "default"}`),
|
|
590
|
+
}));
|
|
591
|
+
result.endedAt = new Date().toISOString();
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
fs_1.default.rmSync(workRunDir, { recursive: true, force: true });
|
|
595
|
+
fs_1.default.mkdirSync(contentDir, { recursive: true });
|
|
596
|
+
try {
|
|
597
|
+
this.log(options, `Copying ${incremental.filesToCopy.length} file(s) into backup work directory`);
|
|
598
|
+
this.copyFiles(incremental.filesToCopy, contentDir, options);
|
|
599
|
+
this.writeContentManifest(contentDir, incremental.manifest);
|
|
600
|
+
this.log(options, "File copy finished");
|
|
601
|
+
for (const source of normalizeArray(backupType.databases)) {
|
|
602
|
+
this.log(options, `Backing up database '${source.connection || "default"}'`);
|
|
603
|
+
result.databases.push(yield this.backupDatabase(source, contentDir, {
|
|
604
|
+
backupKey: key,
|
|
605
|
+
backupId: id,
|
|
606
|
+
log: (message, payload) => this.log(options, message, payload),
|
|
607
|
+
}));
|
|
608
|
+
this.log(options, `Database '${source.connection || "default"}' backup finished`);
|
|
609
|
+
}
|
|
610
|
+
result.artifact = this.buildArtifact(key, backupType, workRunDir, contentDir, now, id, options);
|
|
611
|
+
result.artifact.manifest = incremental.manifest;
|
|
612
|
+
const manifestPath = path_1.default.join(workRunDir, `${result.artifact.filename}.run-manifest.json`);
|
|
613
|
+
result.manifestPath = manifestPath;
|
|
614
|
+
for (const output of outputs) {
|
|
615
|
+
const retention = mergeRetention((_a = this.getConfig().defaults) === null || _a === void 0 ? void 0 : _a.retention, backupType.retention, output.retention);
|
|
616
|
+
const driver = BackupOutputDrivers_1.BackupOutputDrivers.get(output.driver);
|
|
617
|
+
this.log(options, `Writing artifact to '${output.driver}' output`, {
|
|
618
|
+
path: output.path,
|
|
619
|
+
folderPath: output.folderPath,
|
|
620
|
+
});
|
|
621
|
+
const outputResult = yield driver.write(result.artifact, output, {
|
|
557
622
|
backupKey: key,
|
|
558
623
|
backupId: id,
|
|
559
624
|
retention: retention,
|
|
560
625
|
log: (message, payload) => this.log(options, message, payload),
|
|
561
|
-
}, { dryRun: false }));
|
|
562
|
-
this.log(options, `Cleanup for '${output.driver}' output finished`, {
|
|
563
|
-
deleted: ((_b = result.cleanup[result.cleanup.length - 1]) === null || _b === void 0 ? void 0 : _b.deleted.length) || 0,
|
|
564
626
|
});
|
|
627
|
+
result.outputs.push(outputResult);
|
|
628
|
+
this.log(options, `Output '${output.driver}' finished with status '${outputResult.status}'`, {
|
|
629
|
+
path: outputResult.path,
|
|
630
|
+
error: outputResult.error,
|
|
631
|
+
});
|
|
632
|
+
if (outputResult.status === "success" && (retention === null || retention === void 0 ? void 0 : retention.runAfterBackup)) {
|
|
633
|
+
this.log(options, `Running cleanup for '${output.driver}' output`);
|
|
634
|
+
result.cleanup.push(yield driver.cleanup(output, {
|
|
635
|
+
backupKey: key,
|
|
636
|
+
backupId: id,
|
|
637
|
+
retention: retention,
|
|
638
|
+
log: (message, payload) => this.log(options, message, payload),
|
|
639
|
+
}, { dryRun: false }));
|
|
640
|
+
this.log(options, `Cleanup for '${output.driver}' output finished`, {
|
|
641
|
+
deleted: ((_b = result.cleanup[result.cleanup.length - 1]) === null || _b === void 0 ? void 0 : _b.deleted.length) || 0,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
565
644
|
}
|
|
645
|
+
result.endedAt = new Date().toISOString();
|
|
646
|
+
const outputManifestPath = (_c = result.outputs.find((output) => output.payload && output.payload.manifestPath)) === null || _c === void 0 ? void 0 : _c.payload.manifestPath;
|
|
647
|
+
if (outputManifestPath) {
|
|
648
|
+
result.manifestPath = outputManifestPath;
|
|
649
|
+
}
|
|
650
|
+
const manifestJson = JSON.stringify(result, null, 4);
|
|
651
|
+
fs_1.default.writeFileSync(manifestPath, manifestJson, "utf-8");
|
|
652
|
+
for (const output of result.outputs) {
|
|
653
|
+
if (output.payload && output.payload.manifestPath) {
|
|
654
|
+
fs_1.default.writeFileSync(output.payload.manifestPath, manifestJson, "utf-8");
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
this.writeStatusLog(backupType, result, options);
|
|
658
|
+
this.log(options, `Backup '${key}' finished`);
|
|
659
|
+
return result;
|
|
566
660
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
const manifestJson = JSON.stringify(result, null, 4);
|
|
573
|
-
fs_1.default.writeFileSync(manifestPath, manifestJson, "utf-8");
|
|
574
|
-
for (const output of result.outputs) {
|
|
575
|
-
if (output.payload && output.payload.manifestPath) {
|
|
576
|
-
fs_1.default.writeFileSync(output.payload.manifestPath, manifestJson, "utf-8");
|
|
661
|
+
finally {
|
|
662
|
+
if (backupType.cleanupWorkDir) {
|
|
663
|
+
this.log(options, "Cleaning backup work directory");
|
|
664
|
+
fs_1.default.rmSync(workRunDir, { recursive: true, force: true });
|
|
577
665
|
}
|
|
578
666
|
}
|
|
579
|
-
this.log(options, `Backup '${key}' finished`);
|
|
580
|
-
return result;
|
|
581
667
|
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
}
|
|
668
|
+
catch (e) {
|
|
669
|
+
result.endedAt = new Date().toISOString();
|
|
670
|
+
this.writeStatusLog(backupType, result, options, e);
|
|
671
|
+
throw e;
|
|
587
672
|
}
|
|
588
673
|
});
|
|
589
674
|
}
|
|
@@ -33,6 +33,10 @@ export type BackupAutomationConfig = {
|
|
|
33
33
|
priority?: number;
|
|
34
34
|
data?: any;
|
|
35
35
|
};
|
|
36
|
+
export type BackupStatusLogConfig = string | false | {
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
path: string;
|
|
39
|
+
};
|
|
36
40
|
export type BackupDefaultsConfig = {
|
|
37
41
|
workDir?: string;
|
|
38
42
|
outputDir?: string;
|
|
@@ -50,6 +54,7 @@ export type BackupTypeConfig = BackupDefaultsConfig & {
|
|
|
50
54
|
databases?: BackupDatabaseSourceConfig[];
|
|
51
55
|
outputs?: BackupOutputConfig[];
|
|
52
56
|
automation?: BackupAutomationConfig;
|
|
57
|
+
statusLog?: BackupStatusLogConfig;
|
|
53
58
|
};
|
|
54
59
|
export type BackupConfig = {
|
|
55
60
|
defaults?: BackupDefaultsConfig;
|
package/package.json
CHANGED