diff --git a/src/hooks/auto-update-checker/checker.ts b/src/hooks/auto-update-checker/checker.ts index 00fa088..2991996 100644 --- a/src/hooks/auto-update-checker/checker.ts +++ b/src/hooks/auto-update-checker/checker.ts @@ -9,7 +9,10 @@ import { INSTALLED_PACKAGE_JSON, USER_OPENCODE_CONFIG, USER_OPENCODE_CONFIG_JSONC, + USER_CONFIG_DIR, + getWindowsAppdataDir, } from "./constants" +import * as os from "node:os" import { log } from "../../shared/logger" export function isLocalDevMode(directory: string): boolean { @@ -23,12 +26,32 @@ function stripJsonComments(json: string): string { } function getConfigPaths(directory: string): string[] { - return [ + const paths = [ path.join(directory, ".opencode", "opencode.json"), path.join(directory, ".opencode", "opencode.jsonc"), USER_OPENCODE_CONFIG, USER_OPENCODE_CONFIG_JSONC, ] + + if (process.platform === "win32") { + const crossPlatformDir = path.join(os.homedir(), ".config") + const appdataDir = getWindowsAppdataDir() + + if (appdataDir) { + const alternateDir = USER_CONFIG_DIR === crossPlatformDir ? appdataDir : crossPlatformDir + const alternateConfig = path.join(alternateDir, "opencode", "opencode.json") + const alternateConfigJsonc = path.join(alternateDir, "opencode", "opencode.jsonc") + + if (!paths.includes(alternateConfig)) { + paths.push(alternateConfig) + } + if (!paths.includes(alternateConfigJsonc)) { + paths.push(alternateConfigJsonc) + } + } + } + + return paths } export function getLocalDevPath(directory: string): string | null { diff --git a/src/hooks/auto-update-checker/constants.ts b/src/hooks/auto-update-checker/constants.ts index f216d81..d27a87c 100644 --- a/src/hooks/auto-update-checker/constants.ts +++ b/src/hooks/auto-update-checker/constants.ts @@ -1,5 +1,6 @@ import * as path from "node:path" import * as os from "node:os" +import * as fs from "node:fs" export const PACKAGE_NAME = "oh-my-opencode" export const NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags` @@ -28,14 +29,36 @@ export const INSTALLED_PACKAGE_JSON = path.join( /** * OpenCode config file locations (priority order) + * On Windows, checks ~/.config first (cross-platform), then %APPDATA% (fallback) + * This matches shared/config-path.ts behavior for consistency */ function getUserConfigDir(): string { if (process.platform === "win32") { - return process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming") + const crossPlatformDir = path.join(os.homedir(), ".config") + const appdataDir = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming") + + // Check cross-platform path first (~/.config) + const crossPlatformConfig = path.join(crossPlatformDir, "opencode", "opencode.json") + const crossPlatformConfigJsonc = path.join(crossPlatformDir, "opencode", "opencode.jsonc") + + if (fs.existsSync(crossPlatformConfig) || fs.existsSync(crossPlatformConfigJsonc)) { + return crossPlatformDir + } + + // Fall back to %APPDATA% + return appdataDir } return process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config") } +/** + * Get the Windows-specific APPDATA directory (for fallback checks) + */ +export function getWindowsAppdataDir(): string | null { + if (process.platform !== "win32") return null + return process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming") +} + export const USER_CONFIG_DIR = getUserConfigDir() export const USER_OPENCODE_CONFIG = path.join(USER_CONFIG_DIR, "opencode", "opencode.json") export const USER_OPENCODE_CONFIG_JSONC = path.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc") diff --git a/src/hooks/auto-update-checker/index.ts b/src/hooks/auto-update-checker/index.ts index 77a4f50..a7126d9 100644 --- a/src/hooks/auto-update-checker/index.ts +++ b/src/hooks/auto-update-checker/index.ts @@ -4,6 +4,7 @@ import { invalidatePackage } from "./cache" import { PACKAGE_NAME } from "./constants" import { log } from "../../shared/logger" import { getConfigLoadErrors, clearConfigLoadErrors } from "../../shared/config-errors" +import { runBunInstall } from "../../cli/config-manager" import type { AutoUpdateCheckerOptions } from "./types" const SISYPHUS_SPINNER = ["·", "•", "●", "○", "◌", "◦", " "] @@ -100,16 +101,34 @@ async function runBackgroundUpdateCheck( if (pluginInfo.isPinned) { const updated = updatePinnedVersion(pluginInfo.configPath, pluginInfo.entry, latestVersion) - if (updated) { - invalidatePackage(PACKAGE_NAME) - await showAutoUpdatedToast(ctx, currentVersion, latestVersion) - log(`[auto-update-checker] Config updated: ${pluginInfo.entry} → ${PACKAGE_NAME}@${latestVersion}`) - } else { + if (!updated) { await showUpdateAvailableToast(ctx, latestVersion, getToastMessage) + log("[auto-update-checker] Failed to update pinned version in config") + return } + log(`[auto-update-checker] Config updated: ${pluginInfo.entry} → ${PACKAGE_NAME}@${latestVersion}`) + } + + invalidatePackage(PACKAGE_NAME) + + const installSuccess = await runBunInstallSafe() + + if (installSuccess) { + await showAutoUpdatedToast(ctx, currentVersion, latestVersion) + log(`[auto-update-checker] Update installed: ${currentVersion} → ${latestVersion}`) } else { - invalidatePackage(PACKAGE_NAME) await showUpdateAvailableToast(ctx, latestVersion, getToastMessage) + log("[auto-update-checker] bun install failed; update not installed (falling back to notification-only)") + } +} + +async function runBunInstallSafe(): Promise { + try { + return await runBunInstall() + } catch (err) { + const errorMessage = err instanceof Error ? err.message : String(err) + log("[auto-update-checker] bun install error:", errorMessage) + return false } }