Skip to content

Commit 27e558a

Browse files
committed
Simplify update mechanism to modify config instead of spawning bun
Replace the auto-update approach that spawned 'bun add' with a simpler config-based update that directly modifies opencode.jsonc to pin the new version. This is more reliable and lets opencode's native plugin management handle the actual installation on restart. - Remove performUpdate() function with subprocess spawning - Add updateConfigVersion() to update global/local config files - Simplify checkForUpdates() to update configs and notify user - Works with both versioned and unversioned plugin entries
1 parent 9759988 commit 27e558a

File tree

1 file changed

+56
-88
lines changed

1 file changed

+56
-88
lines changed

lib/version-checker.ts

Lines changed: 56 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { readFileSync } from 'fs'
1+
import { readFileSync, writeFileSync, existsSync } from 'fs'
22
import { join, dirname } from 'path'
33
import { fileURLToPath } from 'url'
4-
import { spawn } from 'child_process'
54
import { homedir } from 'os'
65

76
export const PACKAGE_NAME = '@tarquinen/opencode-dcp'
@@ -12,14 +11,12 @@ const __dirname = dirname(__filename)
1211

1312
export function getLocalVersion(): string {
1413
try {
15-
// Walk up from the current module to find the project's package.json
16-
// This works whether running from dist/lib/, lib/, or installed in node_modules
1714
let dir = __dirname
1815
for (let i = 0; i < 5; i++) {
1916
const pkgPath = join(dir, 'package.json')
2017
try {
2118
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
22-
if (pkg.name === '@tarquinen/opencode-dcp') {
19+
if (pkg.name === PACKAGE_NAME) {
2320
return pkg.version
2421
}
2522
} catch {
@@ -65,75 +62,55 @@ export function isOutdated(local: string, remote: string): boolean {
6562
return false
6663
}
6764

68-
export async function performUpdate(targetVersion: string, logger?: { info: (component: string, message: string, data?: any) => void }): Promise<boolean> {
69-
const cacheDir = join(homedir(), '.cache', 'opencode')
70-
const bunCacheDir = join(homedir(), '.bun', 'install', 'cache', '@tarquinen')
71-
const packageSpec = `${PACKAGE_NAME}@${targetVersion}`
65+
/**
66+
* Updates config files to pin the new version.
67+
* Checks both global and local project configs.
68+
* Handles: "@tarquinen/opencode-dcp", "@tarquinen/opencode-dcp@latest", "@tarquinen/[email protected]"
69+
*/
70+
export function updateConfigVersion(newVersion: string, logger?: { info: (component: string, message: string, data?: any) => void }): boolean {
71+
const configs = [
72+
join(homedir(), '.config', 'opencode', 'opencode.jsonc'), // Global
73+
join(process.cwd(), '.opencode', 'opencode.jsonc') // Local project
74+
]
7275

73-
logger?.info("version", "Starting auto-update", { targetVersion, cacheDir })
76+
let anyUpdated = false
7477

75-
// Clear bun's install cache for this package to prevent stale versions
76-
try {
77-
const { rmSync } = await import('fs')
78-
rmSync(bunCacheDir, { recursive: true, force: true })
79-
logger?.info("version", "Cleared bun cache", { bunCacheDir })
80-
} catch (err) {
81-
logger?.info("version", "Could not clear bun cache", { error: (err as Error).message })
82-
}
83-
84-
return new Promise((resolve) => {
85-
let resolved = false
78+
for (const configPath of configs) {
79+
try {
80+
if (!existsSync(configPath)) continue
8681

87-
// Use bun since opencode uses bun to manage its plugin dependencies
88-
const proc = spawn('bun', ['add', packageSpec], {
89-
cwd: cacheDir,
90-
stdio: 'pipe'
91-
})
82+
const content = readFileSync(configPath, 'utf-8')
9283

93-
let stderr = ''
94-
proc.stderr?.on('data', (data) => {
95-
stderr += data.toString()
96-
})
84+
// Match @tarquinen/opencode-dcp with optional version suffix (latest, 1.2.3, etc)
85+
// The regex matches: " @tarquinen/opencode-dcp (optional @anything) "
86+
const regex = new RegExp(`"${PACKAGE_NAME}(@[^"]*)?"`,'g')
87+
const newEntry = `"${PACKAGE_NAME}@${newVersion}"`
9788

98-
proc.on('close', (code) => {
99-
if (resolved) return
100-
resolved = true
101-
clearTimeout(timeoutId)
102-
if (code === 0) {
103-
logger?.info("version", "Auto-update succeeded", { targetVersion })
104-
resolve(true)
105-
} else {
106-
logger?.info("version", "Auto-update failed", { targetVersion, code, stderr: stderr.slice(0, 500) })
107-
resolve(false)
89+
if (!regex.test(content)) {
90+
continue
10891
}
109-
})
11092

111-
proc.on('error', (err) => {
112-
if (resolved) return
113-
resolved = true
114-
clearTimeout(timeoutId)
115-
logger?.info("version", "Auto-update error", { targetVersion, error: err.message })
116-
resolve(false)
117-
})
93+
// Reset regex state
94+
regex.lastIndex = 0
95+
const updatedContent = content.replace(regex, newEntry)
96+
97+
if (updatedContent !== content) {
98+
writeFileSync(configPath, updatedContent, 'utf-8')
99+
logger?.info("version", "Config updated", { configPath, newVersion })
100+
anyUpdated = true
101+
}
102+
} catch (err) {
103+
logger?.info("version", "Failed to update config", { configPath, error: (err as Error).message })
104+
}
105+
}
118106

119-
// Timeout after 60 seconds
120-
const timeoutId = setTimeout(() => {
121-
if (resolved) return
122-
resolved = true
123-
proc.kill()
124-
logger?.info("version", "Auto-update timed out", { targetVersion })
125-
resolve(false)
126-
}, 60000)
127-
})
107+
return anyUpdated
128108
}
129109

130110
export async function checkForUpdates(
131111
client: any,
132-
logger?: { info: (component: string, message: string, data?: any) => void },
133-
options: { showToast?: boolean; autoUpdate?: boolean } = {}
112+
logger?: { info: (component: string, message: string, data?: any) => void }
134113
): Promise<void> {
135-
const { showToast = true, autoUpdate = false } = options
136-
137114
try {
138115
const local = getLocalVersion()
139116
const npm = await getNpmVersion()
@@ -148,41 +125,32 @@ export async function checkForUpdates(
148125
return
149126
}
150127

151-
logger?.info("version", "Update available", { local, npm, autoUpdate })
152-
153-
if (autoUpdate) {
154-
// Attempt auto-update
155-
const success = await performUpdate(npm, logger)
156-
157-
if (success && showToast) {
158-
await client.tui.showToast({
159-
body: {
160-
title: "DCP: Updated!",
161-
message: `v${local} → v${npm}\nRestart OpenCode to apply`,
162-
variant: "success",
163-
duration: 6000
164-
}
165-
})
166-
} else if (!success && showToast) {
167-
await client.tui.showToast({
168-
body: {
169-
title: "DCP: Update failed",
170-
message: `v${local} → v${npm}\nManual: npm install ${PACKAGE_NAME}@${npm}`,
171-
variant: "warning",
172-
duration: 6000
173-
}
174-
})
175-
}
176-
} else if (showToast) {
128+
logger?.info("version", "Update available", { local, npm })
129+
130+
// Update any configs found
131+
const updated = updateConfigVersion(npm, logger)
132+
133+
if (updated) {
177134
await client.tui.showToast({
178135
body: {
179136
title: "DCP: Update available",
180-
message: `v${local} → v${npm}`,
137+
message: `v${local} → v${npm}\nRestart OpenCode to apply`,
181138
variant: "info",
182139
duration: 6000
183140
}
184141
})
142+
} else {
143+
// Config update failed or plugin not found in config, show manual instructions
144+
await client.tui.showToast({
145+
body: {
146+
title: "DCP: Update available",
147+
message: `v${local} → v${npm}\nUpdate opencode.jsonc:\n"${PACKAGE_NAME}@${npm}"`,
148+
variant: "info",
149+
duration: 8000
150+
}
151+
})
185152
}
186153
} catch {
154+
// Silently fail version checks
187155
}
188156
}

0 commit comments

Comments
 (0)