fix: Windows auto-update path detection and install function support (#404)
* fix: Windows auto-update path detection and add install function support - Fix Windows path inconsistency: check ~/.config first, then %APPDATA% - Add actual update installation via runBunInstall when autoUpdate=true - Check both Windows config locations for comprehensive detection Closes #402 * fix: address review feedback on error logging and message clarity - Update misleading log message to clarify fallback behavior - Serialize error object before logging to prevent {} serialization --------- Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
This commit is contained in:
@@ -9,7 +9,10 @@ import {
|
|||||||
INSTALLED_PACKAGE_JSON,
|
INSTALLED_PACKAGE_JSON,
|
||||||
USER_OPENCODE_CONFIG,
|
USER_OPENCODE_CONFIG,
|
||||||
USER_OPENCODE_CONFIG_JSONC,
|
USER_OPENCODE_CONFIG_JSONC,
|
||||||
|
USER_CONFIG_DIR,
|
||||||
|
getWindowsAppdataDir,
|
||||||
} from "./constants"
|
} from "./constants"
|
||||||
|
import * as os from "node:os"
|
||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
|
|
||||||
export function isLocalDevMode(directory: string): boolean {
|
export function isLocalDevMode(directory: string): boolean {
|
||||||
@@ -23,12 +26,32 @@ function stripJsonComments(json: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getConfigPaths(directory: string): string[] {
|
function getConfigPaths(directory: string): string[] {
|
||||||
return [
|
const paths = [
|
||||||
path.join(directory, ".opencode", "opencode.json"),
|
path.join(directory, ".opencode", "opencode.json"),
|
||||||
path.join(directory, ".opencode", "opencode.jsonc"),
|
path.join(directory, ".opencode", "opencode.jsonc"),
|
||||||
USER_OPENCODE_CONFIG,
|
USER_OPENCODE_CONFIG,
|
||||||
USER_OPENCODE_CONFIG_JSONC,
|
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 {
|
export function getLocalDevPath(directory: string): string | null {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as path from "node:path"
|
import * as path from "node:path"
|
||||||
import * as os from "node:os"
|
import * as os from "node:os"
|
||||||
|
import * as fs from "node:fs"
|
||||||
|
|
||||||
export const PACKAGE_NAME = "oh-my-opencode"
|
export const PACKAGE_NAME = "oh-my-opencode"
|
||||||
export const NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`
|
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)
|
* 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 {
|
function getUserConfigDir(): string {
|
||||||
if (process.platform === "win32") {
|
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")
|
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_CONFIG_DIR = getUserConfigDir()
|
||||||
export const USER_OPENCODE_CONFIG = path.join(USER_CONFIG_DIR, "opencode", "opencode.json")
|
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")
|
export const USER_OPENCODE_CONFIG_JSONC = path.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { invalidatePackage } from "./cache"
|
|||||||
import { PACKAGE_NAME } from "./constants"
|
import { PACKAGE_NAME } from "./constants"
|
||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
import { getConfigLoadErrors, clearConfigLoadErrors } from "../../shared/config-errors"
|
import { getConfigLoadErrors, clearConfigLoadErrors } from "../../shared/config-errors"
|
||||||
|
import { runBunInstall } from "../../cli/config-manager"
|
||||||
import type { AutoUpdateCheckerOptions } from "./types"
|
import type { AutoUpdateCheckerOptions } from "./types"
|
||||||
|
|
||||||
const SISYPHUS_SPINNER = ["·", "•", "●", "○", "◌", "◦", " "]
|
const SISYPHUS_SPINNER = ["·", "•", "●", "○", "◌", "◦", " "]
|
||||||
@@ -100,16 +101,34 @@ async function runBackgroundUpdateCheck(
|
|||||||
|
|
||||||
if (pluginInfo.isPinned) {
|
if (pluginInfo.isPinned) {
|
||||||
const updated = updatePinnedVersion(pluginInfo.configPath, pluginInfo.entry, latestVersion)
|
const updated = updatePinnedVersion(pluginInfo.configPath, pluginInfo.entry, latestVersion)
|
||||||
if (updated) {
|
if (!updated) {
|
||||||
invalidatePackage(PACKAGE_NAME)
|
|
||||||
await showAutoUpdatedToast(ctx, currentVersion, latestVersion)
|
|
||||||
log(`[auto-update-checker] Config updated: ${pluginInfo.entry} → ${PACKAGE_NAME}@${latestVersion}`)
|
|
||||||
} else {
|
|
||||||
await showUpdateAvailableToast(ctx, latestVersion, getToastMessage)
|
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 {
|
} else {
|
||||||
invalidatePackage(PACKAGE_NAME)
|
|
||||||
await showUpdateAvailableToast(ctx, latestVersion, getToastMessage)
|
await showUpdateAvailableToast(ctx, latestVersion, getToastMessage)
|
||||||
|
log("[auto-update-checker] bun install failed; update not installed (falling back to notification-only)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runBunInstallSafe(): Promise<boolean> {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user