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