fix: roll back installer migration state
This commit is contained in:
@@ -232,6 +232,10 @@ function applyInstallerMigrationPlan({ configDir, plan, now = () => new Date().t
|
||||
actions: [],
|
||||
};
|
||||
const rollback = [];
|
||||
const installStatePath = path.join(configDir, INSTALL_STATE_NAME);
|
||||
const previousInstallStateBytes = fs.existsSync(installStatePath)
|
||||
? fs.readFileSync(installStatePath)
|
||||
: null;
|
||||
|
||||
try {
|
||||
for (const action of plan.actions) {
|
||||
@@ -285,6 +289,13 @@ function applyInstallerMigrationPlan({ configDir, plan, now = () => new Date().t
|
||||
return {
|
||||
appliedMigrationIds: journal.appliedMigrationIds,
|
||||
journalRelPath,
|
||||
rollback: () => rollbackAppliedMigrationResult({
|
||||
configDir,
|
||||
journal,
|
||||
journalPath,
|
||||
rollbackRoot,
|
||||
previousInstallStateBytes,
|
||||
}),
|
||||
};
|
||||
} catch (error) {
|
||||
const rollbackFailures = [];
|
||||
@@ -311,6 +322,54 @@ function applyInstallerMigrationPlan({ configDir, plan, now = () => new Date().t
|
||||
}
|
||||
}
|
||||
|
||||
function rollbackAppliedMigrationResult({ configDir, journal, journalPath, rollbackRoot, previousInstallStateBytes }) {
|
||||
const failures = [];
|
||||
for (const action of [...journal.actions].reverse()) {
|
||||
if (!action.rollbackRelPath) continue;
|
||||
const rollbackPath = path.join(configDir, action.rollbackRelPath);
|
||||
const dest = path.join(configDir, action.relPath);
|
||||
try {
|
||||
if (fs.existsSync(rollbackPath)) {
|
||||
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||
fs.copyFileSync(rollbackPath, dest);
|
||||
}
|
||||
} catch (error) {
|
||||
failures.push({ relPath: action.relPath, error: error.message });
|
||||
}
|
||||
if (action.backupRelPath) {
|
||||
try {
|
||||
fs.rmSync(path.join(configDir, action.backupRelPath), { force: true });
|
||||
} catch {
|
||||
// backup cleanup is best-effort; preserve restore failures above
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (previousInstallStateBytes === null) {
|
||||
fs.rmSync(path.join(configDir, INSTALL_STATE_NAME), { force: true });
|
||||
} else {
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(configDir, INSTALL_STATE_NAME), previousInstallStateBytes);
|
||||
}
|
||||
} catch (error) {
|
||||
failures.push({ relPath: INSTALL_STATE_NAME, error: error.message });
|
||||
}
|
||||
|
||||
try {
|
||||
fs.rmSync(journalPath, { force: true });
|
||||
fs.rmSync(rollbackRoot, { recursive: true, force: true });
|
||||
} catch {
|
||||
// journal cleanup is best-effort; the rollback above is the safety-critical part
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
const error = new Error('migration rollback incomplete');
|
||||
error.rollbackFailures = failures;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function runInstallerMigrations({
|
||||
configDir,
|
||||
migrationsDir = DEFAULT_MIGRATIONS_DIR,
|
||||
|
||||
Reference in New Issue
Block a user