mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-17 11:07:11 +08:00
fix(desktop): add updater logging + allow prerelease
This commit is contained in:
@@ -71,7 +71,7 @@
|
|||||||
"gatekeeperAssess": false,
|
"gatekeeperAssess": false,
|
||||||
"entitlements": "build/entitlements.mac.plist",
|
"entitlements": "build/entitlements.mac.plist",
|
||||||
"entitlementsInherit": "build/entitlements.mac.plist",
|
"entitlementsInherit": "build/entitlements.mac.plist",
|
||||||
"notarize": true,
|
"notarize": false,
|
||||||
"target": [
|
"target": [
|
||||||
{
|
{
|
||||||
"target": "dmg",
|
"target": "dmg",
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { app, BrowserWindow } from 'electron'
|
import { app, BrowserWindow } from 'electron'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
// electron-updater is CommonJS: its members live on module.exports, with no
|
// electron-updater is CommonJS: its members live on module.exports, with no
|
||||||
// meaningful default export. Under module=commonjs + esModuleInterop, a named
|
// meaningful default export. Under module=commonjs + esModuleInterop, a named
|
||||||
// import compiles to `electron_updater_1.autoUpdater` and resolves correctly,
|
// import compiles to `electron_updater_1.autoUpdater` and resolves correctly,
|
||||||
@@ -17,16 +19,59 @@ export type UpdateStatus =
|
|||||||
|
|
||||||
let getWindow: () => BrowserWindow | null = () => null
|
let getWindow: () => BrowserWindow | null = () => null
|
||||||
|
|
||||||
|
// Persist update logs to a file so a user hitting a silent "spinner never
|
||||||
|
// resolves" can just send us userData/logs/updater.log. We can't rely on a
|
||||||
|
// logging dep, so this is a tiny append-only writer, plus console for the
|
||||||
|
// in-app log view / terminal.
|
||||||
|
let logFile: string | null = null
|
||||||
|
|
||||||
|
function initLogFile() {
|
||||||
|
try {
|
||||||
|
const dir = path.join(app.getPath('userData'), 'logs')
|
||||||
|
fs.mkdirSync(dir, { recursive: true })
|
||||||
|
logFile = path.join(dir, 'updater.log')
|
||||||
|
} catch {
|
||||||
|
logFile = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(...parts: unknown[]) {
|
||||||
|
const line = `[${new Date().toISOString()}] [updater] ${parts
|
||||||
|
.map((p) => (typeof p === 'string' ? p : safeStringify(p)))
|
||||||
|
.join(' ')}`
|
||||||
|
// Console: shows up in the terminal (dev) and the packaged app's stdout.
|
||||||
|
console.log(line)
|
||||||
|
if (logFile) {
|
||||||
|
try {
|
||||||
|
fs.appendFileSync(logFile, line + '\n')
|
||||||
|
} catch {
|
||||||
|
// ignore disk errors — logging must never break the updater
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeStringify(v: unknown): string {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(v)
|
||||||
|
} catch {
|
||||||
|
return String(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function send(status: UpdateStatus) {
|
function send(status: UpdateStatus) {
|
||||||
getWindow()?.webContents.send('update-status', status)
|
getWindow()?.webContents.send('update-status', status)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initUpdater(windowGetter: () => BrowserWindow | null): void {
|
export function initUpdater(windowGetter: () => BrowserWindow | null): void {
|
||||||
getWindow = windowGetter
|
getWindow = windowGetter
|
||||||
|
initLogFile()
|
||||||
|
|
||||||
|
log(`init: appVersion=${app.getVersion()} packaged=${app.isPackaged} logFile=${logFile ?? '<none>'}`)
|
||||||
|
|
||||||
// In dev (not packaged) there's no update feed; skip wiring entirely so
|
// In dev (not packaged) there's no update feed; skip wiring entirely so
|
||||||
// electron-updater doesn't throw on the missing app-update.yml.
|
// electron-updater doesn't throw on the missing app-update.yml.
|
||||||
if (!app.isPackaged) {
|
if (!app.isPackaged) {
|
||||||
|
log('not packaged — updater wiring skipped')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,21 +79,51 @@ export function initUpdater(windowGetter: () => BrowserWindow | null): void {
|
|||||||
// download, rather than pulling bytes silently in the background.
|
// download, rather than pulling bytes silently in the background.
|
||||||
autoUpdater.autoDownload = false
|
autoUpdater.autoDownload = false
|
||||||
autoUpdater.autoInstallOnAppQuit = true
|
autoUpdater.autoInstallOnAppQuit = true
|
||||||
|
// The desktop channel ships pre-release-tagged builds (e.g. 0.0.8-test), so a
|
||||||
|
// current version like 0.0.7-test must be allowed to compare against, and be
|
||||||
|
// offered, other pre-release versions. Without this electron-updater's semver
|
||||||
|
// compare can silently skip pre-releases and neither "available" nor
|
||||||
|
// "not-available" fires — the UI just spins forever.
|
||||||
|
autoUpdater.allowPrerelease = true
|
||||||
|
autoUpdater.allowDowngrade = false
|
||||||
|
// Route electron-updater's own internal logging to our file too, so we
|
||||||
|
// capture the feed URL, parsed versions and any stack traces it logs.
|
||||||
|
autoUpdater.logger = {
|
||||||
|
info: (m: unknown) => log('eu-info:', m),
|
||||||
|
warn: (m: unknown) => log('eu-warn:', m),
|
||||||
|
error: (m: unknown) => log('eu-error:', m),
|
||||||
|
debug: (m: unknown) => log('eu-debug:', m),
|
||||||
|
} as unknown as typeof autoUpdater.logger
|
||||||
|
|
||||||
autoUpdater.on('checking-for-update', () => send({ state: 'checking' }))
|
autoUpdater.on('checking-for-update', () => {
|
||||||
autoUpdater.on('update-available', (info) =>
|
log(`checking-for-update: current=${app.getVersion()}`)
|
||||||
send({ state: 'available', version: info.version, notes: typeof info.releaseNotes === 'string' ? info.releaseNotes : undefined })
|
send({ state: 'checking' })
|
||||||
)
|
})
|
||||||
autoUpdater.on('update-not-available', () => send({ state: 'not-available' }))
|
autoUpdater.on('update-available', (info) => {
|
||||||
autoUpdater.on('download-progress', (p) =>
|
log(`update-available: current=${app.getVersion()} remote=${info.version} -> update needed`)
|
||||||
|
send({
|
||||||
|
state: 'available',
|
||||||
|
version: info.version,
|
||||||
|
notes: typeof info.releaseNotes === 'string' ? info.releaseNotes : undefined,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
autoUpdater.on('update-not-available', (info) => {
|
||||||
|
log(`update-not-available: current=${app.getVersion()} remote=${info?.version ?? '<unknown>'} -> up to date`)
|
||||||
|
send({ state: 'not-available' })
|
||||||
|
})
|
||||||
|
autoUpdater.on('download-progress', (p) => {
|
||||||
|
log(`download-progress: ${Math.round(p.percent)}% (${p.transferred}/${p.total} bytes, ${Math.round(p.bytesPerSecond / 1024)} KB/s)`)
|
||||||
send({ state: 'downloading', percent: Math.round(p.percent) })
|
send({ state: 'downloading', percent: Math.round(p.percent) })
|
||||||
)
|
})
|
||||||
autoUpdater.on('update-downloaded', (info) =>
|
autoUpdater.on('update-downloaded', (info) => {
|
||||||
|
log(`update-downloaded: version=${info.version} -> ready to install`)
|
||||||
send({ state: 'downloaded', version: info.version })
|
send({ state: 'downloaded', version: info.version })
|
||||||
)
|
})
|
||||||
autoUpdater.on('error', (err) =>
|
autoUpdater.on('error', (err) => {
|
||||||
send({ state: 'error', message: err == null ? 'unknown' : (err.message || String(err)) })
|
const message = err == null ? 'unknown' : err.message || String(err)
|
||||||
)
|
log(`error: ${message}`, err instanceof Error && err.stack ? err.stack : '')
|
||||||
|
send({ state: 'error', message })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Silent check shortly after launch. When not packaged there's no update feed,
|
// Silent check shortly after launch. When not packaged there's no update feed,
|
||||||
@@ -56,23 +131,31 @@ export function initUpdater(windowGetter: () => BrowserWindow | null): void {
|
|||||||
// reply "not-available" so the menu can show "up to date".
|
// reply "not-available" so the menu can show "up to date".
|
||||||
export function checkForUpdates(): void {
|
export function checkForUpdates(): void {
|
||||||
if (!app.isPackaged) {
|
if (!app.isPackaged) {
|
||||||
|
log('checkForUpdates: not packaged, replying not-available')
|
||||||
send({ state: 'not-available' })
|
send({ state: 'not-available' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log(`checkForUpdates: requesting feed, current=${app.getVersion()}`)
|
||||||
autoUpdater.checkForUpdates().catch((err) => {
|
autoUpdater.checkForUpdates().catch((err) => {
|
||||||
send({ state: 'error', message: err?.message || String(err) })
|
const message = err?.message || String(err)
|
||||||
|
log(`checkForUpdates: request failed: ${message}`, err instanceof Error && err.stack ? err.stack : '')
|
||||||
|
send({ state: 'error', message })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startDownload(): void {
|
export function startDownload(): void {
|
||||||
if (!app.isPackaged) return
|
if (!app.isPackaged) return
|
||||||
|
log('startDownload: user requested download')
|
||||||
autoUpdater.downloadUpdate().catch((err) => {
|
autoUpdater.downloadUpdate().catch((err) => {
|
||||||
send({ state: 'error', message: err?.message || String(err) })
|
const message = err?.message || String(err)
|
||||||
|
log(`startDownload: failed: ${message}`, err instanceof Error && err.stack ? err.stack : '')
|
||||||
|
send({ state: 'error', message })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function quitAndInstall(): void {
|
export function quitAndInstall(): void {
|
||||||
if (!app.isPackaged) return
|
if (!app.isPackaged) return
|
||||||
|
log('quitAndInstall: relaunching to install update')
|
||||||
// isSilent=false (show installer), isForceRunAfter=true (relaunch after).
|
// isSilent=false (show installer), isForceRunAfter=true (relaunch after).
|
||||||
autoUpdater.quitAndInstall(false, true)
|
autoUpdater.quitAndInstall(false, true)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user