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 { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
||||||
import { tmpdir } from "node:os"
|
import { tmpdir } from "node:os"
|
||||||
import { join } from "node:path"
|
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("#given opencode.json contains pinned oh-my-opencode version", () => {
|
||||||
describe("#when migrating the config", () => {
|
describe("#when migrating the config", () => {
|
||||||
it("#then preserves the version pin", async () => {
|
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 { applyEdits, modify } from "jsonc-parser"
|
||||||
|
|
||||||
import { parseJsoncSafe } 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"
|
: JSON.stringify({ ...(parseResult.data as OpenCodeConfig), plugin: updatedPluginEntries }, null, 2) + "\n"
|
||||||
if (!updated || updated === content) return false
|
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", {
|
log("[migrateLegacyPluginEntry] Auto-migrated opencode.json plugin entry", {
|
||||||
configPath,
|
configPath,
|
||||||
from: LEGACY_PLUGIN_NAME,
|
from: LEGACY_PLUGIN_NAME,
|
||||||
|
|||||||
Reference in New Issue
Block a user