fix(config): make plugin entry migration atomic with temp-file + rename
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,6 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "bun:test"
|
||||
/// <reference path="../../bun-test.d.ts" />
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test"
|
||||
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { tmpdir } from "node:os"
|
||||
import { join } from "node:path"
|
||||
@@ -53,6 +55,43 @@ describe("migrateLegacyPluginEntry", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given renaming the temp file fails after writing the migrated config", () => {
|
||||
describe("#when migrating the config", () => {
|
||||
it("#then keeps the original config untouched and writes the migrated content to a sibling temp file", async () => {
|
||||
const configPath = join(testDir, "opencode.json")
|
||||
const originalContent = JSON.stringify({ plugin: ["oh-my-opencode@latest"] }, null, 2)
|
||||
const tempPath = `${configPath}.tmp`
|
||||
writeFileSync(configPath, originalContent)
|
||||
|
||||
const fs = await import("node:fs")
|
||||
const originalRenameSync = fs.renameSync
|
||||
|
||||
mock.module("node:fs", () => ({
|
||||
...fs,
|
||||
renameSync: () => {
|
||||
throw new Error("simulated rename failure")
|
||||
},
|
||||
}))
|
||||
|
||||
try {
|
||||
const { migrateLegacyPluginEntry } = await importFreshMigrationModule()
|
||||
|
||||
const result = migrateLegacyPluginEntry(configPath)
|
||||
|
||||
expect(result).toBe(false)
|
||||
expect(readFileSync(configPath, "utf-8")).toBe(originalContent)
|
||||
expect(readFileSync(tempPath, "utf-8")).toContain("oh-my-openagent@latest")
|
||||
expect(readFileSync(tempPath, "utf-8")).not.toContain("oh-my-opencode")
|
||||
} finally {
|
||||
mock.module("node:fs", () => ({
|
||||
...fs,
|
||||
renameSync: originalRenameSync,
|
||||
}))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given opencode.json contains pinned oh-my-opencode version", () => {
|
||||
describe("#when migrating the config", () => {
|
||||
it("#then preserves the version pin", async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs"
|
||||
import { closeSync, existsSync, fsyncSync, openSync, readFileSync, renameSync, writeFileSync } from "node:fs"
|
||||
import { applyEdits, modify } from "jsonc-parser"
|
||||
|
||||
import { parseJsoncSafe } from "./jsonc-parser"
|
||||
@@ -66,7 +66,15 @@ export function migrateLegacyPluginEntry(configPath: string): boolean {
|
||||
: JSON.stringify({ ...(parseResult.data as OpenCodeConfig), plugin: updatedPluginEntries }, null, 2) + "\n"
|
||||
if (!updated || updated === content) return false
|
||||
|
||||
writeFileSync(configPath, updated, "utf-8")
|
||||
const tempPath = `${configPath}.tmp`
|
||||
writeFileSync(tempPath, updated, "utf-8")
|
||||
const tempFileDescriptor = openSync(tempPath, "r")
|
||||
try {
|
||||
fsyncSync(tempFileDescriptor)
|
||||
} finally {
|
||||
closeSync(tempFileDescriptor)
|
||||
}
|
||||
renameSync(tempPath, configPath)
|
||||
log("[migrateLegacyPluginEntry] Auto-migrated opencode.json plugin entry", {
|
||||
configPath,
|
||||
from: LEGACY_PLUGIN_NAME,
|
||||
|
||||
Reference in New Issue
Block a user