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:
@@ -1,12 +1,14 @@
|
|||||||
import { readFileSync, writeFileSync } from "node:fs"
|
import { readFileSync, writeFileSync } from "node:fs"
|
||||||
import type { ConfigMergeResult } from "../types"
|
import type { ConfigMergeResult } from "../types"
|
||||||
import { PLUGIN_NAME, LEGACY_PLUGIN_NAME } from "../../shared"
|
import { PLUGIN_NAME, LEGACY_PLUGIN_NAME } from "../../shared"
|
||||||
|
import { backupConfigFile } from "./backup-config"
|
||||||
import { getConfigDir } from "./config-context"
|
import { getConfigDir } from "./config-context"
|
||||||
import { ensureConfigDirectoryExists } from "./ensure-config-directory-exists"
|
import { ensureConfigDirectoryExists } from "./ensure-config-directory-exists"
|
||||||
import { formatErrorWithSuggestion } from "./format-error-with-suggestion"
|
import { formatErrorWithSuggestion } from "./format-error-with-suggestion"
|
||||||
import { detectConfigFormat } from "./opencode-config-format"
|
import { detectConfigFormat } from "./opencode-config-format"
|
||||||
import { parseOpenCodeConfigFileWithError, type OpenCodeConfig } from "./parse-opencode-config-file"
|
import { parseOpenCodeConfigFileWithError, type OpenCodeConfig } from "./parse-opencode-config-file"
|
||||||
import { getPluginNameWithVersion } from "./plugin-name-with-version"
|
import { getPluginNameWithVersion } from "./plugin-name-with-version"
|
||||||
|
import { checkVersionCompatibility, extractVersionFromPluginEntry } from "./version-compatibility"
|
||||||
|
|
||||||
export async function addPluginToOpenCodeConfig(currentVersion: string): Promise<ConfigMergeResult> {
|
export async function addPluginToOpenCodeConfig(currentVersion: string): Promise<ConfigMergeResult> {
|
||||||
try {
|
try {
|
||||||
@@ -52,6 +54,29 @@ export async function addPluginToOpenCodeConfig(currentVersion: string): Promise
|
|||||||
&& !(plugin === LEGACY_PLUGIN_NAME || plugin.startsWith(`${LEGACY_PLUGIN_NAME}@`))
|
&& !(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]
|
const normalizedPlugins = [...otherPlugins]
|
||||||
|
|
||||||
if (canonicalEntries.length > 0) {
|
if (canonicalEntries.length > 0) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { DetectedConfig } from "../types"
|
|||||||
import { getOmoConfigPath } from "./config-context"
|
import { getOmoConfigPath } from "./config-context"
|
||||||
import { detectConfigFormat } from "./opencode-config-format"
|
import { detectConfigFormat } from "./opencode-config-format"
|
||||||
import { parseOpenCodeConfigFileWithError } from "./parse-opencode-config-file"
|
import { parseOpenCodeConfigFileWithError } from "./parse-opencode-config-file"
|
||||||
|
import { extractVersionFromPluginEntry } from "./version-compatibility"
|
||||||
|
|
||||||
function detectProvidersFromOmoConfig(): {
|
function detectProvidersFromOmoConfig(): {
|
||||||
hasOpenAI: boolean
|
hasOpenAI: boolean
|
||||||
@@ -60,9 +61,14 @@ function isOurPlugin(plugin: string): boolean {
|
|||||||
plugin === LEGACY_PLUGIN_NAME || plugin.startsWith(`${LEGACY_PLUGIN_NAME}@`)
|
plugin === LEGACY_PLUGIN_NAME || plugin.startsWith(`${LEGACY_PLUGIN_NAME}@`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findOurPluginEntry(plugins: string[]): string | null {
|
||||||
|
return plugins.find(isOurPlugin) ?? null
|
||||||
|
}
|
||||||
|
|
||||||
export function detectCurrentConfig(): DetectedConfig {
|
export function detectCurrentConfig(): DetectedConfig {
|
||||||
const result: DetectedConfig = {
|
const result: DetectedConfig = {
|
||||||
isInstalled: false,
|
isInstalled: false,
|
||||||
|
installedVersion: null,
|
||||||
hasClaude: true,
|
hasClaude: true,
|
||||||
isMax20: true,
|
isMax20: true,
|
||||||
hasOpenAI: true,
|
hasOpenAI: true,
|
||||||
@@ -86,7 +92,12 @@ export function detectCurrentConfig(): DetectedConfig {
|
|||||||
|
|
||||||
const openCodeConfig = parseResult.config
|
const openCodeConfig = parseResult.config
|
||||||
const plugins = openCodeConfig.plugin ?? []
|
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) {
|
if (!result.isInstalled) {
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const installConfig: InstallConfig = {
|
|||||||
hasOpencodeZen: false,
|
hasOpencodeZen: false,
|
||||||
hasZaiCodingPlan: false,
|
hasZaiCodingPlan: false,
|
||||||
hasKimiForCoding: false,
|
hasKimiForCoding: false,
|
||||||
|
hasOpencodeGo: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRecord(value: unknown): Record<string, unknown> {
|
function getRecord(value: unknown): Record<string, unknown> {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs"
|
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs"
|
||||||
import { parseJsonc } from "../../shared"
|
import { parseJsonc } from "../../shared"
|
||||||
import type { ConfigMergeResult, InstallConfig } from "../types"
|
import type { ConfigMergeResult, InstallConfig } from "../types"
|
||||||
|
import { backupConfigFile } from "./backup-config"
|
||||||
import { getConfigDir, getOmoConfigPath } from "./config-context"
|
import { getConfigDir, getOmoConfigPath } from "./config-context"
|
||||||
import { deepMergeRecord } from "./deep-merge-record"
|
import { deepMergeRecord } from "./deep-merge-record"
|
||||||
import { ensureConfigDirectoryExists } from "./ensure-config-directory-exists"
|
import { ensureConfigDirectoryExists } from "./ensure-config-directory-exists"
|
||||||
@@ -28,6 +29,15 @@ export function writeOmoConfig(installConfig: InstallConfig): ConfigMergeResult
|
|||||||
const newConfig = generateOmoConfig(installConfig)
|
const newConfig = generateOmoConfig(installConfig)
|
||||||
|
|
||||||
if (existsSync(omoConfigPath)) {
|
if (existsSync(omoConfigPath)) {
|
||||||
|
const backupResult = backupConfigFile(omoConfigPath)
|
||||||
|
if (!backupResult.success) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
configPath: omoConfigPath,
|
||||||
|
error: `Failed to create backup: ${backupResult.error}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stat = statSync(omoConfigPath)
|
const stat = statSync(omoConfigPath)
|
||||||
const content = readFileSync(omoConfigPath, "utf-8")
|
const content = readFileSync(omoConfigPath, "utf-8")
|
||||||
|
|||||||
Reference in New Issue
Block a user