fix(installer): add upgrade path safety checks

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-04-08 13:26:33 +09:00
parent 01f7d5e2a8
commit cdd6e88557
4 changed files with 48 additions and 1 deletions
@@ -1,12 +1,14 @@
import { readFileSync, writeFileSync } from "node:fs"
import type { ConfigMergeResult } from "../types"
import { PLUGIN_NAME, LEGACY_PLUGIN_NAME } from "../../shared"
import { backupConfigFile } from "./backup-config"
import { getConfigDir } from "./config-context"
import { ensureConfigDirectoryExists } from "./ensure-config-directory-exists"
import { formatErrorWithSuggestion } from "./format-error-with-suggestion"
import { detectConfigFormat } from "./opencode-config-format"
import { parseOpenCodeConfigFileWithError, type OpenCodeConfig } from "./parse-opencode-config-file"
import { getPluginNameWithVersion } from "./plugin-name-with-version"
import { checkVersionCompatibility, extractVersionFromPluginEntry } from "./version-compatibility"
export async function addPluginToOpenCodeConfig(currentVersion: string): Promise<ConfigMergeResult> {
try {
@@ -52,6 +54,29 @@ export async function addPluginToOpenCodeConfig(currentVersion: string): Promise
&& !(plugin === LEGACY_PLUGIN_NAME || plugin.startsWith(`${LEGACY_PLUGIN_NAME}@`))
)
const existingEntry = canonicalEntries[0] ?? legacyEntries[0]
if (existingEntry) {
const installedVersion = extractVersionFromPluginEntry(existingEntry)
const compatibility = checkVersionCompatibility(installedVersion, currentVersion)
if (!compatibility.canUpgrade) {
return {
success: false,
configPath: path,
error: compatibility.reason ?? "Version compatibility check failed",
}
}
const backupResult = backupConfigFile(path)
if (!backupResult.success) {
return {
success: false,
configPath: path,
error: `Failed to create backup: ${backupResult.error}`,
}
}
}
const normalizedPlugins = [...otherPlugins]
if (canonicalEntries.length > 0) {
@@ -4,6 +4,7 @@ import type { DetectedConfig } from "../types"
import { getOmoConfigPath } from "./config-context"
import { detectConfigFormat } from "./opencode-config-format"
import { parseOpenCodeConfigFileWithError } from "./parse-opencode-config-file"
import { extractVersionFromPluginEntry } from "./version-compatibility"
function detectProvidersFromOmoConfig(): {
hasOpenAI: boolean
@@ -60,9 +61,14 @@ function isOurPlugin(plugin: string): boolean {
plugin === LEGACY_PLUGIN_NAME || plugin.startsWith(`${LEGACY_PLUGIN_NAME}@`)
}
function findOurPluginEntry(plugins: string[]): string | null {
return plugins.find(isOurPlugin) ?? null
}
export function detectCurrentConfig(): DetectedConfig {
const result: DetectedConfig = {
isInstalled: false,
installedVersion: null,
hasClaude: true,
isMax20: true,
hasOpenAI: true,
@@ -86,7 +92,12 @@ export function detectCurrentConfig(): DetectedConfig {
const openCodeConfig = parseResult.config
const plugins = openCodeConfig.plugin ?? []
result.isInstalled = plugins.some(isOurPlugin)
const ourPluginEntry = findOurPluginEntry(plugins)
result.isInstalled = !!ourPluginEntry
if (ourPluginEntry) {
result.installedVersion = extractVersionFromPluginEntry(ourPluginEntry)
}
if (!result.isInstalled) {
return result
@@ -18,6 +18,7 @@ const installConfig: InstallConfig = {
hasOpencodeZen: false,
hasZaiCodingPlan: false,
hasKimiForCoding: false,
hasOpencodeGo: false,
}
function getRecord(value: unknown): Record<string, unknown> {
@@ -1,6 +1,7 @@
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs"
import { parseJsonc } from "../../shared"
import type { ConfigMergeResult, InstallConfig } from "../types"
import { backupConfigFile } from "./backup-config"
import { getConfigDir, getOmoConfigPath } from "./config-context"
import { deepMergeRecord } from "./deep-merge-record"
import { ensureConfigDirectoryExists } from "./ensure-config-directory-exists"
@@ -28,6 +29,15 @@ export function writeOmoConfig(installConfig: InstallConfig): ConfigMergeResult
const newConfig = generateOmoConfig(installConfig)
if (existsSync(omoConfigPath)) {
const backupResult = backupConfigFile(omoConfigPath)
if (!backupResult.success) {
return {
success: false,
configPath: omoConfigPath,
error: `Failed to create backup: ${backupResult.error}`,
}
}
try {
const stat = statSync(omoConfigPath)
const content = readFileSync(omoConfigPath, "utf-8")