- installation/index.ts 新增 Windows 升级路径:下载 release zip → PowerShell 解压 → 写 .cmd 在进程退出后替换 exe(绕过运行中文件锁) - method() 在 Windows 兜底返回 curl,让手动下载的 exe 也能走自动更新 - 修 cmd/upgrade.ts 残留的 "opencode upgrade skipped" 文案 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -46,7 +46,7 @@ export const UpgradeCommand = {
|
|||||||
const target = args.target ? args.target.replace(/^v/, "") : await Installation.latest()
|
const target = args.target ? args.target.replace(/^v/, "") : await Installation.latest()
|
||||||
|
|
||||||
if (InstallationVersion === target) {
|
if (InstallationVersion === target) {
|
||||||
prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`)
|
prompts.log.warn(`zmo upgrade skipped: ${target} is already installed`)
|
||||||
prompts.outro("Done")
|
prompts.outro("Done")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { errorMessage } from "@/util/error"
|
|||||||
import { ChildProcess } from "effect/unstable/process"
|
import { ChildProcess } from "effect/unstable/process"
|
||||||
import { AppProcess } from "@opencode-ai/core/process"
|
import { AppProcess } from "@opencode-ai/core/process"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
import os from "os"
|
||||||
|
import fs from "fs"
|
||||||
import { EventV2 } from "@opencode-ai/core/event"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
||||||
@@ -155,8 +157,58 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProce
|
|||||||
return "sh"
|
return "sh"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const upgradeWindows = Effect.fnUntraced(function* (target: string) {
|
||||||
|
// Windows 自更新:运行中的 exe 无法直接覆盖自身,
|
||||||
|
// 因此下载新 zip → 解压 → 写一个 .cmd 在本进程退出后替换 exe。
|
||||||
|
const exePath = process.execPath
|
||||||
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "zmo-upgrade-"))
|
||||||
|
const zipPath = path.join(tmpDir, "zmo-windows-x64.zip")
|
||||||
|
const url = `https://github.com/helpLogin/zmo-cli/releases/download/v${target}/zmo-windows-x64.zip`
|
||||||
|
|
||||||
|
// 1. 下载新版 zip
|
||||||
|
const res = yield* httpOk.execute(HttpClientRequest.get(url))
|
||||||
|
const buf = yield* res.arrayBuffer
|
||||||
|
fs.writeFileSync(zipPath, Buffer.from(buf))
|
||||||
|
|
||||||
|
// 2. 用 PowerShell 解压(Win10+ 自带 Expand-Archive)
|
||||||
|
const unzip = yield* run([
|
||||||
|
"powershell",
|
||||||
|
"-NoProfile",
|
||||||
|
"-Command",
|
||||||
|
`Expand-Archive -Path '${zipPath}' -DestinationPath '${tmpDir}' -Force`,
|
||||||
|
])
|
||||||
|
if (unzip.code !== 0) return unzip
|
||||||
|
const newExe = path.join(tmpDir, "zmo.exe")
|
||||||
|
if (!fs.existsSync(newExe)) {
|
||||||
|
return { code: 1, stdout: "", stderr: "downloaded archive did not contain zmo.exe" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 写 .cmd:等待本进程退出 → 覆盖旧 exe → 清理临时目录
|
||||||
|
const cmdPath = path.join(tmpDir, "zmo-replace.cmd")
|
||||||
|
const script = [
|
||||||
|
"@echo off",
|
||||||
|
"ping 127.0.0.1 -n 3 >nul",
|
||||||
|
`:retry`,
|
||||||
|
`move /Y "${newExe}" "${exePath}" >nul 2>&1`,
|
||||||
|
`if errorlevel 1 (`,
|
||||||
|
` ping 127.0.0.1 -n 2 >nul`,
|
||||||
|
` goto retry`,
|
||||||
|
`)`,
|
||||||
|
`rmdir /S /Q "${tmpDir}" >nul 2>&1`,
|
||||||
|
].join("\r\n")
|
||||||
|
fs.writeFileSync(cmdPath, script)
|
||||||
|
|
||||||
|
// 4. detached 启动 cmd,本进程随后正常退出,让 cmd 完成替换
|
||||||
|
Bun.spawn(["cmd", "/c", cmdPath], { stdio: ["ignore", "ignore", "ignore"] }).unref()
|
||||||
|
|
||||||
|
return { code: 0, stdout: `zmo will finish updating to v${target} after exit. Please restart zmo.`, stderr: "" }
|
||||||
|
}, Effect.catch((err) => Effect.succeed({ code: 1, stdout: "", stderr: errorMessage(err) })))
|
||||||
|
|
||||||
const upgradeCurl = Effect.fnUntraced(
|
const upgradeCurl = Effect.fnUntraced(
|
||||||
function* (target: string) {
|
function* (target: string) {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
return yield* upgradeWindows(target)
|
||||||
|
}
|
||||||
const response = yield* httpOk.execute(HttpClientRequest.get("https://raw.githubusercontent.com/helpLogin/zmo-cli/main/install"))
|
const response = yield* httpOk.execute(HttpClientRequest.get("https://raw.githubusercontent.com/helpLogin/zmo-cli/main/install"))
|
||||||
const body = yield* response.text
|
const body = yield* response.text
|
||||||
const bodyBytes = new TextEncoder().encode(body)
|
const bodyBytes = new TextEncoder().encode(body)
|
||||||
@@ -216,6 +268,9 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProce
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Windows 上手动下载的 exe 不在包管理器里,但我们的 Windows 升级路径
|
||||||
|
// 可自我替换任意位置的 exe,所以兜底按 curl 处理以启用自动更新。
|
||||||
|
if (process.platform === "win32") return "curl" as Method
|
||||||
return "unknown" as Method
|
return "unknown" as Method
|
||||||
}),
|
}),
|
||||||
latest: Effect.fn("Installation.latest")(function* (installMethod?: Method) {
|
latest: Effect.fn("Installation.latest")(function* (installMethod?: Method) {
|
||||||
|
|||||||
Reference in New Issue
Block a user