@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, `Copying ${incremental.filesToCopy.length} file(s) into backup work directory`);
520
- this.copyFiles(incremental.filesToCopy, contentDir, options);
521
- this.writeContentManifest(contentDir, incremental.manifest);
522
- this.log(options, "File copy finished");
523
- for (const source of normalizeArray(backupType.databases)) {
524
- this.log(options, `Backing up database '${source.connection || "default"}'`);
525
- result.databases.push(yield this.backupDatabase(source, contentDir, {
526
- backupKey: key,
527
- backupId: id,
528
- log: (message, payload) => this.log(options, message, payload),
529
- }));
530
- this.log(options, `Database '${source.connection || "default"}' backup finished`);
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
- result.artifact = this.buildArtifact(key, backupType, workRunDir, contentDir, now, id, options);
533
- result.artifact.manifest = incremental.manifest;
534
- const manifestPath = path_1.default.join(workRunDir, `${result.artifact.filename}.run-manifest.json`);
535
- result.manifestPath = manifestPath;
536
- for (const output of outputs) {
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
- folderPath: output.folderPath,
542
- });
543
- const outputResult = yield driver.write(result.artifact, output, {
544
- backupKey: key,
545
- backupId: id,
546
- retention: retention,
547
- log: (message, payload) => this.log(options, message, payload),
548
- });
549
- result.outputs.push(outputResult);
550
- this.log(options, `Output '${output.driver}' finished with status '${outputResult.status}'`, {
551
- path: outputResult.path,
552
- error: outputResult.error,
553
- });
554
- if (outputResult.status === "success" && (retention === null || retention === void 0 ? void 0 : retention.runAfterBackup)) {
555
- this.log(options, `Running cleanup for '${output.driver}' output`);
556
- result.cleanup.push(yield driver.cleanup(output, {
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
- result.endedAt = new Date().toISOString();
568
- const outputManifestPath = (_c = result.outputs.find((output) => output.payload && output.payload.manifestPath)) === null || _c === void 0 ? void 0 : _c.payload.manifestPath;
569
- if (outputManifestPath) {
570
- result.manifestPath = outputManifestPath;
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
- finally {
583
- if (backupType.cleanupWorkDir) {
584
- this.log(options, "Cleaning backup work directory");
585
- fs_1.default.rmSync(workRunDir, { recursive: true, force: true });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtypen/webframez-core",
3
- "version": "0.3.56",
3
+ "version": "0.3.57",
4
4
  "description": "The core package of the typescript based webframez (web-framework) by webtypen.de",
5
5
  "homepage": "https://webtypen.de/",
6
6
  "license": "MIT",