()
| 64 | } |
| 65 | |
| 66 | function _recoverInterruptedForceUpdateBootstrap() { |
| 67 | const fs = require('fs'); |
| 68 | const path = require('path'); |
| 69 | const installRoot = __dirname; |
| 70 | const backupPrefix = '.evolver-force-update-backup-'; |
| 71 | const journalName = '.evolver-force-update-journal.json'; |
| 72 | let backups = []; |
| 73 | try { |
| 74 | backups = fs.readdirSync(installRoot) |
| 75 | .filter((name) => name.startsWith(backupPrefix)) |
| 76 | .sort() |
| 77 | .reverse(); |
| 78 | } catch (_) { |
| 79 | return false; |
| 80 | } |
| 81 | for (const backupName of backups) { |
| 82 | const backupRoot = path.join(installRoot, backupName); |
| 83 | const journalPath = path.join(backupRoot, journalName); |
| 84 | let journal = null; |
| 85 | try { |
| 86 | journal = JSON.parse(fs.readFileSync(journalPath, 'utf8')); |
| 87 | } catch (_) { |
| 88 | // Not a force-update recovery journal; leave unrelated directories alone. |
| 89 | continue; |
| 90 | } |
| 91 | if (!journal || journal.state !== 'precommit' || !journal.requiredVersion) continue; |
| 92 | let currentVersion = ''; |
| 93 | try { |
| 94 | const pkg = JSON.parse(fs.readFileSync(path.join(installRoot, 'package.json'), 'utf8')); |
| 95 | currentVersion = pkg && pkg.version ? String(pkg.version) : ''; |
| 96 | } catch (_) { |
| 97 | // If the package marker is unreadable, prefer restoring the old payload. |
| 98 | } |
| 99 | if (_bootstrapVersionSatisfies(currentVersion, String(journal.requiredVersion))) { |
| 100 | try { fs.rmSync(backupRoot, { recursive: true, force: true }); } catch (_) { |
| 101 | // Cleanup failure is non-fatal; normal startup can continue. |
| 102 | } |
| 103 | continue; |
| 104 | } |
| 105 | let entries = []; |
| 106 | try { |
| 107 | entries = fs.readdirSync(backupRoot, { withFileTypes: true }); |
| 108 | } catch (readErr) { |
| 109 | _failClosedForceUpdateBootstrap(backupName, '', readErr); |
| 110 | } |
| 111 | for (const entry of entries) { |
| 112 | if (entry.name === journalName) continue; |
| 113 | const livePath = path.join(installRoot, entry.name); |
| 114 | const backupPath = path.join(backupRoot, entry.name); |
| 115 | try { |
| 116 | if (entry.name === 'index.js') { |
| 117 | const tmpPath = livePath + '.' + process.pid + '.recover-tmp'; |
| 118 | try { fs.rmSync(tmpPath, { force: true }); } catch (_) {} |
| 119 | fs.copyFileSync(backupPath, tmpPath); |
| 120 | try { |
| 121 | const backupStat = fs.statSync(backupPath); |
| 122 | fs.chmodSync(tmpPath, backupStat.mode & 0o777); |
| 123 | } catch (_) { |
no test coverage detected