fix(shared): migrate legacy config files atomically
Write the canonical config through a temp file and remove the legacy dual-file trap by archiving or deleting the old basename after migration. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { existsSync, copyFileSync, renameSync } from "node:fs"
|
import { closeSync, existsSync, fsyncSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"
|
||||||
import { join, dirname, basename } from "node:path"
|
import { join, dirname, basename } from "node:path"
|
||||||
|
|
||||||
import { log } from "./logger"
|
import { log } from "./logger"
|
||||||
@@ -10,6 +10,49 @@ function buildCanonicalPath(legacyPath: string): string {
|
|||||||
return join(dir, `${CONFIG_BASENAME}${ext}`)
|
return join(dir, `${CONFIG_BASENAME}${ext}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function writeFileAtomically(filePath: string, content: string): void {
|
||||||
|
const tempPath = `${filePath}.tmp`
|
||||||
|
writeFileSync(tempPath, content, "utf-8")
|
||||||
|
const tempFileDescriptor = openSync(tempPath, "r")
|
||||||
|
try {
|
||||||
|
fsyncSync(tempFileDescriptor)
|
||||||
|
} finally {
|
||||||
|
closeSync(tempFileDescriptor)
|
||||||
|
}
|
||||||
|
renameSync(tempPath, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
function archiveLegacyConfigFile(legacyPath: string): boolean {
|
||||||
|
const backupPath = `${legacyPath}.bak`
|
||||||
|
|
||||||
|
try {
|
||||||
|
renameSync(legacyPath, backupPath)
|
||||||
|
log("[migrateLegacyConfigFile] Legacy config was migrated and renamed to backup. Update the canonical file only.", {
|
||||||
|
legacyPath,
|
||||||
|
backupPath,
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
} catch (renameError) {
|
||||||
|
try {
|
||||||
|
rmSync(legacyPath)
|
||||||
|
log("[migrateLegacyConfigFile] Legacy config was migrated and removed after backup rename failed. Update the canonical file only.", {
|
||||||
|
legacyPath,
|
||||||
|
backupPath,
|
||||||
|
renameError,
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
} catch (removeError) {
|
||||||
|
log("[migrateLegacyConfigFile] WARNING: canonical config was written but the legacy file still exists and will be ignored. Remove or rename it manually.", {
|
||||||
|
legacyPath,
|
||||||
|
backupPath,
|
||||||
|
renameError,
|
||||||
|
removeError,
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function migrateLegacyConfigFile(legacyPath: string): boolean {
|
export function migrateLegacyConfigFile(legacyPath: string): boolean {
|
||||||
if (!existsSync(legacyPath)) return false
|
if (!existsSync(legacyPath)) return false
|
||||||
if (!basename(legacyPath).startsWith(LEGACY_CONFIG_BASENAME)) return false
|
if (!basename(legacyPath).startsWith(LEGACY_CONFIG_BASENAME)) return false
|
||||||
@@ -18,14 +61,17 @@ export function migrateLegacyConfigFile(legacyPath: string): boolean {
|
|||||||
if (existsSync(canonicalPath)) return false
|
if (existsSync(canonicalPath)) return false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
copyFileSync(legacyPath, canonicalPath)
|
const content = readFileSync(legacyPath, "utf-8")
|
||||||
log("[migrateLegacyConfigFile] Copied legacy config to canonical path", {
|
writeFileAtomically(canonicalPath, content)
|
||||||
|
const archivedLegacyConfig = archiveLegacyConfigFile(legacyPath)
|
||||||
|
log("[migrateLegacyConfigFile] Migrated legacy config to canonical path", {
|
||||||
from: legacyPath,
|
from: legacyPath,
|
||||||
to: canonicalPath,
|
to: canonicalPath,
|
||||||
|
archivedLegacyConfig,
|
||||||
})
|
})
|
||||||
return true
|
return archivedLegacyConfig
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log("[migrateLegacyConfigFile] Failed to copy legacy config file", { legacyPath, error })
|
log("[migrateLegacyConfigFile] Failed to migrate legacy config file", { legacyPath, error })
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user