* Creates a backup of the file at filePath. If the file does not exist * (ENOENT), records a null backup (file-did-not-exist marker). All IO is * async. Lazy mkdir: tries copyFile first, creates the directory on ENOENT.
( filePath: string | null, version: number, )
| 748 | * async. Lazy mkdir: tries copyFile first, creates the directory on ENOENT. |
| 749 | */ |
| 750 | async function createBackup( |
| 751 | filePath: string | null, |
| 752 | version: number, |
| 753 | ): Promise<FileHistoryBackup> { |
| 754 | if (filePath === null) { |
| 755 | return { backupFileName: null, version, backupTime: new Date() } |
| 756 | } |
| 757 | |
| 758 | const backupFileName = getBackupFileName(filePath, version) |
| 759 | const backupPath = resolveBackupPath(backupFileName) |
| 760 | |
| 761 | // Stat first: if the source is missing, record a null backup and skip the |
| 762 | // copy. Separates "source missing" from "backup dir missing" cleanly — |
| 763 | // sharing a catch for both meant a file deleted between copyFile-success |
| 764 | // and stat would leave an orphaned backup with a null state record. |
| 765 | let srcStats: Stats |
| 766 | try { |
| 767 | srcStats = await stat(filePath) |
| 768 | } catch (e: unknown) { |
| 769 | if (isENOENT(e)) { |
| 770 | return { backupFileName: null, version, backupTime: new Date() } |
| 771 | } |
| 772 | throw e |
| 773 | } |
| 774 | |
| 775 | // copyFile preserves content and avoids reading the whole file into the JS |
| 776 | // heap (which the previous readFileSync+writeFileSync pipeline did, OOMing |
| 777 | // on large tracked files). Lazy mkdir: 99% of calls hit the fast path |
| 778 | // (directory already exists); on ENOENT, mkdir then retry. |
| 779 | try { |
| 780 | await copyFile(filePath, backupPath) |
| 781 | } catch (e: unknown) { |
| 782 | if (!isENOENT(e)) throw e |
| 783 | await mkdir(dirname(backupPath), { recursive: true }) |
| 784 | await copyFile(filePath, backupPath) |
| 785 | } |
| 786 | |
| 787 | // Preserve file permissions on the backup. |
| 788 | await chmod(backupPath, srcStats.mode) |
| 789 | |
| 790 | logEvent('tengu_file_history_backup_file_created', { |
| 791 | version: version, |
| 792 | fileSize: srcStats.size, |
| 793 | }) |
| 794 | |
| 795 | return { |
| 796 | backupFileName, |
| 797 | version, |
| 798 | backupTime: new Date(), |
| 799 | } |
| 800 | } |
| 801 | |
| 802 | /** |
| 803 | * Restores a file from its backup path with proper directory creation and permissions. |
no test coverage detected