diff --git a/electron/main.ts b/electron/main.ts index 2903d14..4ca920f 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -13,6 +13,7 @@ import { MysqlManager } from "./services/MysqlManager"; import { NginxManager } from "./services/NginxManager"; import { RedisManager } from "./services/RedisManager"; import { NodeManager } from "./services/NodeManager"; +import { GoManager } from "./services/GoManager"; import { ServiceManager } from "./services/ServiceManager"; import { HostsManager } from "./services/HostsManager"; import { GitManager } from "./services/GitManager"; @@ -98,7 +99,7 @@ export function sendDownloadProgress( type: string, progress: number, downloaded: number, - total: number + total: number, ) { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send("download-progress", { @@ -117,6 +118,7 @@ const mysqlManager = new MysqlManager(configStore); const nginxManager = new NginxManager(configStore); const redisManager = new RedisManager(configStore); const nodeManager = new NodeManager(configStore); +const goManager = new GoManager(configStore); const serviceManager = new ServiceManager(configStore); const hostsManager = new HostsManager(); const gitManager = new GitManager(configStore); @@ -315,7 +317,7 @@ ipcMain.handle("window:close", () => mainWindow?.close()); // 打开外部链接 ipcMain.handle("shell:openExternal", (_, url: string) => - shell.openExternal(url) + shell.openExternal(url), ); ipcMain.handle("shell:openPath", (_, path: string) => shell.openPath(path)); @@ -332,33 +334,33 @@ ipcMain.handle("dialog:selectDirectory", async () => { // ==================== PHP 管理 ==================== ipcMain.handle("php:getVersions", () => phpManager.getInstalledVersions()); ipcMain.handle("php:getAvailableVersions", () => - phpManager.getAvailableVersions() + phpManager.getAvailableVersions(), ); ipcMain.handle("php:install", (_, version: string) => - phpManager.install(version) + phpManager.install(version), ); ipcMain.handle("php:uninstall", (_, version: string) => - phpManager.uninstall(version) + phpManager.uninstall(version), ); ipcMain.handle("php:setActive", (_, version: string) => - phpManager.setActive(version) + phpManager.setActive(version), ); ipcMain.handle("php:getExtensions", (_, version: string) => - phpManager.getExtensions(version) + phpManager.getExtensions(version), ); ipcMain.handle("php:openExtensionDir", (_, version: string) => - phpManager.openExtensionDir(version) + phpManager.openExtensionDir(version), ); ipcMain.handle( "php:getAvailableExtensions", (_, version: string, searchKeyword?: string) => - phpManager.getAvailableExtensions(version, searchKeyword) + phpManager.getAvailableExtensions(version, searchKeyword), ); ipcMain.handle("php:enableExtension", (_, version: string, ext: string) => - phpManager.enableExtension(version, ext) + phpManager.enableExtension(version, ext), ); ipcMain.handle("php:disableExtension", (_, version: string, ext: string) => - phpManager.disableExtension(version, ext) + phpManager.disableExtension(version, ext), ); ipcMain.handle( "php:installExtension", @@ -367,14 +369,14 @@ ipcMain.handle( version: string, ext: string, downloadUrl?: string, - packageName?: string - ) => phpManager.installExtension(version, ext, downloadUrl, packageName) + packageName?: string, + ) => phpManager.installExtension(version, ext, downloadUrl, packageName), ); ipcMain.handle("php:getConfig", (_, version: string) => - phpManager.getConfig(version) + phpManager.getConfig(version), ); ipcMain.handle("php:saveConfig", (_, version: string, config: string) => - phpManager.saveConfig(version, config) + phpManager.saveConfig(version, config), ); // ==================== Composer 管理 ==================== @@ -382,62 +384,62 @@ ipcMain.handle("composer:getStatus", () => phpManager.getComposerStatus()); ipcMain.handle("composer:install", () => phpManager.installComposer()); ipcMain.handle("composer:uninstall", () => phpManager.uninstallComposer()); ipcMain.handle("composer:setMirror", (_, mirror: string) => - phpManager.setComposerMirror(mirror) + phpManager.setComposerMirror(mirror), ); ipcMain.handle( "composer:createLaravelProject", (_, projectName: string, targetDir: string) => - phpManager.createLaravelProject(projectName, targetDir) + phpManager.createLaravelProject(projectName, targetDir), ); // ==================== MySQL 管理 ==================== ipcMain.handle("mysql:getVersions", () => mysqlManager.getInstalledVersions()); ipcMain.handle("mysql:getAvailableVersions", () => - mysqlManager.getAvailableVersions() + mysqlManager.getAvailableVersions(), ); ipcMain.handle("mysql:install", (_, version: string) => - mysqlManager.install(version) + mysqlManager.install(version), ); ipcMain.handle("mysql:uninstall", (_, version: string) => - mysqlManager.uninstall(version) + mysqlManager.uninstall(version), ); ipcMain.handle("mysql:start", (_, version: string) => - mysqlManager.start(version) + mysqlManager.start(version), ); ipcMain.handle("mysql:stop", (_, version: string) => - mysqlManager.stop(version) + mysqlManager.stop(version), ); ipcMain.handle("mysql:restart", (_, version: string) => - mysqlManager.restart(version) + mysqlManager.restart(version), ); ipcMain.handle("mysql:getStatus", (_, version: string) => - mysqlManager.getStatus(version) + mysqlManager.getStatus(version), ); ipcMain.handle( "mysql:changePassword", (_, version: string, newPassword: string, currentPassword?: string) => - mysqlManager.changeRootPassword(version, newPassword, currentPassword) + mysqlManager.changeRootPassword(version, newPassword, currentPassword), ); ipcMain.handle("mysql:getConfig", (_, version: string) => - mysqlManager.getConfig(version) + mysqlManager.getConfig(version), ); ipcMain.handle("mysql:saveConfig", (_, version: string, config: string) => - mysqlManager.saveConfig(version, config) + mysqlManager.saveConfig(version, config), ); ipcMain.handle("mysql:reinitialize", (_, version: string) => - mysqlManager.reinitialize(version) + mysqlManager.reinitialize(version), ); // ==================== Nginx 管理 ==================== ipcMain.handle("nginx:getVersions", () => nginxManager.getInstalledVersions()); ipcMain.handle("nginx:getAvailableVersions", () => - nginxManager.getAvailableVersions() + nginxManager.getAvailableVersions(), ); ipcMain.handle("nginx:install", (_, version: string) => - nginxManager.install(version) + nginxManager.install(version), ); ipcMain.handle("nginx:uninstall", (_, version: string) => - nginxManager.uninstall(version) + nginxManager.uninstall(version), ); ipcMain.handle("nginx:start", () => nginxManager.start()); ipcMain.handle("nginx:stop", () => nginxManager.stop()); @@ -446,39 +448,39 @@ ipcMain.handle("nginx:reload", () => nginxManager.reload()); ipcMain.handle("nginx:getStatus", () => nginxManager.getStatus()); ipcMain.handle("nginx:getConfig", () => nginxManager.getConfig()); ipcMain.handle("nginx:saveConfig", (_, config: string) => - nginxManager.saveConfig(config) + nginxManager.saveConfig(config), ); ipcMain.handle("nginx:getSites", () => nginxManager.getSites()); ipcMain.handle("nginx:addSite", (_, site: any) => nginxManager.addSite(site)); ipcMain.handle("nginx:removeSite", (_, name: string) => - nginxManager.removeSite(name) + nginxManager.removeSite(name), ); ipcMain.handle("nginx:updateSite", (_, originalName: string, site: any) => - nginxManager.updateSite(originalName, site) + nginxManager.updateSite(originalName, site), ); ipcMain.handle("nginx:enableSite", (_, name: string) => - nginxManager.enableSite(name) + nginxManager.enableSite(name), ); ipcMain.handle("nginx:disableSite", (_, name: string) => - nginxManager.disableSite(name) + nginxManager.disableSite(name), ); ipcMain.handle("nginx:generateLaravelConfig", (_, site: any) => - nginxManager.generateLaravelConfig(site) + nginxManager.generateLaravelConfig(site), ); ipcMain.handle("nginx:requestSSL", (_, domain: string, email: string) => - nginxManager.requestSSLCertificate(domain, email) + nginxManager.requestSSLCertificate(domain, email), ); // ==================== Redis 管理 ==================== ipcMain.handle("redis:getVersions", () => redisManager.getInstalledVersions()); ipcMain.handle("redis:getAvailableVersions", () => - redisManager.getAvailableVersions() + redisManager.getAvailableVersions(), ); ipcMain.handle("redis:install", (_, version: string) => - redisManager.install(version) + redisManager.install(version), ); ipcMain.handle("redis:uninstall", (_, version: string) => - redisManager.uninstall(version) + redisManager.uninstall(version), ); ipcMain.handle("redis:start", () => redisManager.start()); ipcMain.handle("redis:stop", () => redisManager.stop()); @@ -486,103 +488,131 @@ ipcMain.handle("redis:restart", () => redisManager.restart()); ipcMain.handle("redis:getStatus", () => redisManager.getStatus()); ipcMain.handle("redis:getConfig", () => redisManager.getConfig()); ipcMain.handle("redis:saveConfig", (_, config: string) => - redisManager.saveConfig(config) + redisManager.saveConfig(config), ); // ==================== Node.js 管理 ==================== ipcMain.handle("node:getVersions", () => nodeManager.getInstalledVersions()); ipcMain.handle("node:getAvailableVersions", () => - nodeManager.getAvailableVersions() + nodeManager.getAvailableVersions(), ); ipcMain.handle("node:install", (_, version: string, downloadUrl: string) => - nodeManager.install(version, downloadUrl) + nodeManager.install(version, downloadUrl), ); ipcMain.handle("node:uninstall", (_, version: string) => - nodeManager.uninstall(version) + nodeManager.uninstall(version), ); ipcMain.handle("node:setActive", (_, version: string) => - nodeManager.setActive(version) + nodeManager.setActive(version), ); ipcMain.handle("node:getInfo", (_, version: string) => - nodeManager.getNodeInfo(version) + nodeManager.getNodeInfo(version), +); + +// ==================== Go 管理 ==================== +ipcMain.handle("go:getVersions", () => goManager.getInstalledVersions()); +ipcMain.handle("go:getAvailableVersions", () => + goManager.getAvailableVersions(), +); +ipcMain.handle("go:install", (_, version: string, downloadUrl: string) => + goManager.install(version, downloadUrl), +); +ipcMain.handle("go:uninstall", (_, version: string) => + goManager.uninstall(version), +); +ipcMain.handle("go:setActive", (_, version: string) => + goManager.setActive(version), +); +ipcMain.handle("go:getInfo", (_, version: string) => + goManager.getGoInfo(version), ); // ==================== 服务管理 ==================== ipcMain.handle("service:getAll", () => serviceManager.getAllServices()); ipcMain.handle("service:setAutoStart", (_, service: string, enabled: boolean) => - serviceManager.setAutoStart(service, enabled) + serviceManager.setAutoStart(service, enabled), ); ipcMain.handle("service:getAutoStart", (_, service: string) => - serviceManager.getAutoStart(service) + serviceManager.getAutoStart(service), ); ipcMain.handle("service:startAll", () => serviceManager.startAll()); ipcMain.handle("service:stopAll", () => serviceManager.stopAll()); // PHP-CGI 管理 - 支持多版本 -ipcMain.handle("service:getPhpCgiStatus", () => serviceManager.getPhpCgiStatus()); +ipcMain.handle("service:getPhpCgiStatus", () => + serviceManager.getPhpCgiStatus(), +); ipcMain.handle("service:startPhpCgi", () => serviceManager.startPhpCgi()); ipcMain.handle("service:stopPhpCgi", () => serviceManager.stopPhpCgi()); ipcMain.handle("service:startAllPhpCgi", () => serviceManager.startAllPhpCgi()); ipcMain.handle("service:stopAllPhpCgi", () => serviceManager.stopAllPhpCgi()); -ipcMain.handle("service:startPhpCgiVersion", (_, version: string) => serviceManager.startPhpCgiVersion(version)); -ipcMain.handle("service:stopPhpCgiVersion", (_, version: string) => serviceManager.stopPhpCgiVersion(version)); -ipcMain.handle("service:getPhpCgiPort", (_, version: string) => serviceManager.getPhpCgiPort(version)); +ipcMain.handle("service:startPhpCgiVersion", (_, version: string) => + serviceManager.startPhpCgiVersion(version), +); +ipcMain.handle("service:stopPhpCgiVersion", (_, version: string) => + serviceManager.stopPhpCgiVersion(version), +); +ipcMain.handle("service:getPhpCgiPort", (_, version: string) => + serviceManager.getPhpCgiPort(version), +); // ==================== Hosts 管理 ==================== ipcMain.handle("hosts:get", () => hostsManager.getHosts()); ipcMain.handle("hosts:add", (_, domain: string, ip: string) => - hostsManager.addHost(domain, ip) + hostsManager.addHost(domain, ip), ); ipcMain.handle("hosts:remove", (_, domain: string) => - hostsManager.removeHost(domain) + hostsManager.removeHost(domain), ); // ==================== Git 管理 ==================== ipcMain.handle("git:getVersions", () => gitManager.getInstalledVersions()); ipcMain.handle("git:getAvailableVersions", () => - gitManager.getAvailableVersions() + gitManager.getAvailableVersions(), ); ipcMain.handle("git:install", (_, version: string) => - gitManager.install(version) + gitManager.install(version), ); ipcMain.handle("git:uninstall", () => gitManager.uninstall()); ipcMain.handle("git:checkSystem", () => gitManager.checkSystemGit()); ipcMain.handle("git:getConfig", () => gitManager.getGitConfig()); ipcMain.handle("git:setConfig", (_, name: string, email: string) => - gitManager.setGitConfig(name, email) + gitManager.setGitConfig(name, email), ); // ==================== Python 管理 ==================== -ipcMain.handle("python:getVersions", () => pythonManager.getInstalledVersions()); +ipcMain.handle("python:getVersions", () => + pythonManager.getInstalledVersions(), +); ipcMain.handle("python:getAvailableVersions", () => - pythonManager.getAvailableVersions() + pythonManager.getAvailableVersions(), ); ipcMain.handle("python:install", (_, version: string) => - pythonManager.install(version) + pythonManager.install(version), ); ipcMain.handle("python:uninstall", (_, version: string) => - pythonManager.uninstall(version) + pythonManager.uninstall(version), ); ipcMain.handle("python:setActive", (_, version: string) => - pythonManager.setActive(version) + pythonManager.setActive(version), ); ipcMain.handle("python:checkSystem", () => pythonManager.checkSystemPython()); ipcMain.handle("python:getPipInfo", (_, version: string) => - pythonManager.getPipInfo(version) + pythonManager.getPipInfo(version), ); ipcMain.handle( "python:installPackage", (_, version: string, packageName: string) => - pythonManager.installPackage(version, packageName) + pythonManager.installPackage(version, packageName), ); // ==================== 配置管理 ==================== ipcMain.handle("config:get", (_, key: string) => configStore.get(key)); ipcMain.handle("config:set", (_, key: string, value: any) => - configStore.set(key, value) + configStore.set(key, value), ); ipcMain.handle("config:getBasePath", () => configStore.getBasePath()); ipcMain.handle("config:setBasePath", (_, path: string) => - configStore.setBasePath(path) + configStore.setBasePath(path), ); // ==================== 应用设置 ==================== @@ -636,7 +666,7 @@ ipcMain.handle("app:setAutoLaunch", async (_, enabled: boolean) => { } catch (e) { // 忽略删除失败 } - + // 删除 VBS 脚本 const appDir = require("path").dirname(exePath); const vbsPath = join(appDir, "silent_start.vbs"); @@ -647,7 +677,7 @@ ipcMain.handle("app:setAutoLaunch", async (_, enabled: boolean) => { // 忽略删除失败 } } - + configStore.set("autoLaunch", false); return { success: true, message: "已禁用开机自启" }; } @@ -685,17 +715,17 @@ ipcMain.handle("app:getAutoLaunch", async () => { ipcMain.handle("app:getVersion", async () => { const { existsSync, readFileSync } = require("fs"); const { join } = require("path"); - + const version = app.getVersion(); let buildTime = ""; let buildDate = ""; - + // 尝试读取版本信息文件 try { const versionFilePath = app.isPackaged ? join(process.resourcesPath, "public", "version.json") : join(__dirname, "..", "public", "version.json"); - + if (existsSync(versionFilePath)) { const versionInfo = JSON.parse(readFileSync(versionFilePath, "utf-8")); buildTime = versionInfo.buildTime || ""; @@ -704,12 +734,12 @@ ipcMain.handle("app:getVersion", async () => { } catch (e) { // 忽略错误 } - + return { version, buildTime, buildDate, - isPackaged: app.isPackaged + isPackaged: app.isPackaged, }; }); @@ -733,9 +763,13 @@ ipcMain.handle("app:quit", () => { // ==================== 日志管理 ==================== ipcMain.handle("log:getFiles", () => logManager.getLogFiles()); ipcMain.handle("log:read", (_, logPath: string, lines?: number) => - logManager.readLog(logPath, lines) + logManager.readLog(logPath, lines), ); -ipcMain.handle("log:clear", (_, logPath: string) => logManager.clearLog(logPath)); -ipcMain.handle("log:getDirectory", (_, type: 'nginx' | 'php' | 'mysql' | 'sites', version?: string) => - logManager.getLogDirectory(type, version) +ipcMain.handle("log:clear", (_, logPath: string) => + logManager.clearLog(logPath), +); +ipcMain.handle( + "log:getDirectory", + (_, type: "nginx" | "php" | "mysql" | "sites", version?: string) => + logManager.getLogDirectory(type, version), ); diff --git a/electron/preload.ts b/electron/preload.ts index 10c598b..0ab0c85 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -1,220 +1,318 @@ -import { contextBridge, ipcRenderer } from 'electron' +import { contextBridge, ipcRenderer } from "electron"; // 暴露安全的 API 到渲染进程 -contextBridge.exposeInMainWorld('electronAPI', { +contextBridge.exposeInMainWorld("electronAPI", { // 窗口控制 - minimize: () => ipcRenderer.invoke('window:minimize'), - maximize: () => ipcRenderer.invoke('window:maximize'), - close: () => ipcRenderer.invoke('window:close'), + minimize: () => ipcRenderer.invoke("window:minimize"), + maximize: () => ipcRenderer.invoke("window:maximize"), + close: () => ipcRenderer.invoke("window:close"), // Shell - openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url), - openPath: (path: string) => ipcRenderer.invoke('shell:openPath', path), - + openExternal: (url: string) => ipcRenderer.invoke("shell:openExternal", url), + openPath: (path: string) => ipcRenderer.invoke("shell:openPath", path), + // Dialog - selectDirectory: () => ipcRenderer.invoke('dialog:selectDirectory'), + selectDirectory: () => ipcRenderer.invoke("dialog:selectDirectory"), // 下载进度监听 - onDownloadProgress: (callback: (data: { type: string; progress: number; downloaded: number; total: number }) => void) => { - ipcRenderer.on('download-progress', (_, data) => callback(data)) + onDownloadProgress: ( + callback: (data: { + type: string; + progress: number; + downloaded: number; + total: number; + }) => void, + ) => { + ipcRenderer.on("download-progress", (_, data) => callback(data)); }, removeDownloadProgressListener: () => { - ipcRenderer.removeAllListeners('download-progress') + ipcRenderer.removeAllListeners("download-progress"); }, // PHP 管理 php: { - getVersions: () => ipcRenderer.invoke('php:getVersions'), - getAvailableVersions: () => ipcRenderer.invoke('php:getAvailableVersions'), - install: (version: string) => ipcRenderer.invoke('php:install', version), - uninstall: (version: string) => ipcRenderer.invoke('php:uninstall', version), - setActive: (version: string) => ipcRenderer.invoke('php:setActive', version), - getExtensions: (version: string) => ipcRenderer.invoke('php:getExtensions', version), - openExtensionDir: (version: string) => ipcRenderer.invoke('php:openExtensionDir', version), - getAvailableExtensions: (version: string, searchKeyword?: string) => ipcRenderer.invoke('php:getAvailableExtensions', version, searchKeyword), - enableExtension: (version: string, ext: string) => ipcRenderer.invoke('php:enableExtension', version, ext), - disableExtension: (version: string, ext: string) => ipcRenderer.invoke('php:disableExtension', version, ext), - installExtension: (version: string, ext: string, downloadUrl?: string, packageName?: string) => ipcRenderer.invoke('php:installExtension', version, ext, downloadUrl, packageName), - getConfig: (version: string) => ipcRenderer.invoke('php:getConfig', version), - saveConfig: (version: string, config: string) => ipcRenderer.invoke('php:saveConfig', version, config) + getVersions: () => ipcRenderer.invoke("php:getVersions"), + getAvailableVersions: () => ipcRenderer.invoke("php:getAvailableVersions"), + install: (version: string) => ipcRenderer.invoke("php:install", version), + uninstall: (version: string) => + ipcRenderer.invoke("php:uninstall", version), + setActive: (version: string) => + ipcRenderer.invoke("php:setActive", version), + getExtensions: (version: string) => + ipcRenderer.invoke("php:getExtensions", version), + openExtensionDir: (version: string) => + ipcRenderer.invoke("php:openExtensionDir", version), + getAvailableExtensions: (version: string, searchKeyword?: string) => + ipcRenderer.invoke("php:getAvailableExtensions", version, searchKeyword), + enableExtension: (version: string, ext: string) => + ipcRenderer.invoke("php:enableExtension", version, ext), + disableExtension: (version: string, ext: string) => + ipcRenderer.invoke("php:disableExtension", version, ext), + installExtension: ( + version: string, + ext: string, + downloadUrl?: string, + packageName?: string, + ) => + ipcRenderer.invoke( + "php:installExtension", + version, + ext, + downloadUrl, + packageName, + ), + getConfig: (version: string) => + ipcRenderer.invoke("php:getConfig", version), + saveConfig: (version: string, config: string) => + ipcRenderer.invoke("php:saveConfig", version, config), }, // Composer 管理 composer: { - getStatus: () => ipcRenderer.invoke('composer:getStatus'), - install: () => ipcRenderer.invoke('composer:install'), - uninstall: () => ipcRenderer.invoke('composer:uninstall'), - setMirror: (mirror: string) => ipcRenderer.invoke('composer:setMirror', mirror), - createLaravelProject: (projectName: string, targetDir: string) => ipcRenderer.invoke('composer:createLaravelProject', projectName, targetDir) + getStatus: () => ipcRenderer.invoke("composer:getStatus"), + install: () => ipcRenderer.invoke("composer:install"), + uninstall: () => ipcRenderer.invoke("composer:uninstall"), + setMirror: (mirror: string) => + ipcRenderer.invoke("composer:setMirror", mirror), + createLaravelProject: (projectName: string, targetDir: string) => + ipcRenderer.invoke( + "composer:createLaravelProject", + projectName, + targetDir, + ), }, // MySQL 管理 mysql: { - getVersions: () => ipcRenderer.invoke('mysql:getVersions'), - getAvailableVersions: () => ipcRenderer.invoke('mysql:getAvailableVersions'), - install: (version: string) => ipcRenderer.invoke('mysql:install', version), - uninstall: (version: string) => ipcRenderer.invoke('mysql:uninstall', version), - start: (version: string) => ipcRenderer.invoke('mysql:start', version), - stop: (version: string) => ipcRenderer.invoke('mysql:stop', version), - restart: (version: string) => ipcRenderer.invoke('mysql:restart', version), - getStatus: (version: string) => ipcRenderer.invoke('mysql:getStatus', version), - changePassword: (version: string, newPassword: string, currentPassword?: string) => ipcRenderer.invoke('mysql:changePassword', version, newPassword, currentPassword), - getConfig: (version: string) => ipcRenderer.invoke('mysql:getConfig', version), - saveConfig: (version: string, config: string) => ipcRenderer.invoke('mysql:saveConfig', version, config) + getVersions: () => ipcRenderer.invoke("mysql:getVersions"), + getAvailableVersions: () => + ipcRenderer.invoke("mysql:getAvailableVersions"), + install: (version: string) => ipcRenderer.invoke("mysql:install", version), + uninstall: (version: string) => + ipcRenderer.invoke("mysql:uninstall", version), + start: (version: string) => ipcRenderer.invoke("mysql:start", version), + stop: (version: string) => ipcRenderer.invoke("mysql:stop", version), + restart: (version: string) => ipcRenderer.invoke("mysql:restart", version), + getStatus: (version: string) => + ipcRenderer.invoke("mysql:getStatus", version), + changePassword: ( + version: string, + newPassword: string, + currentPassword?: string, + ) => + ipcRenderer.invoke( + "mysql:changePassword", + version, + newPassword, + currentPassword, + ), + getConfig: (version: string) => + ipcRenderer.invoke("mysql:getConfig", version), + saveConfig: (version: string, config: string) => + ipcRenderer.invoke("mysql:saveConfig", version, config), }, // Nginx 管理 nginx: { - getVersions: () => ipcRenderer.invoke('nginx:getVersions'), - getAvailableVersions: () => ipcRenderer.invoke('nginx:getAvailableVersions'), - install: (version: string) => ipcRenderer.invoke('nginx:install', version), - uninstall: (version: string) => ipcRenderer.invoke('nginx:uninstall', version), - start: () => ipcRenderer.invoke('nginx:start'), - stop: () => ipcRenderer.invoke('nginx:stop'), - restart: () => ipcRenderer.invoke('nginx:restart'), - reload: () => ipcRenderer.invoke('nginx:reload'), - getStatus: () => ipcRenderer.invoke('nginx:getStatus'), - getConfig: () => ipcRenderer.invoke('nginx:getConfig'), - saveConfig: (config: string) => ipcRenderer.invoke('nginx:saveConfig', config), - getSites: () => ipcRenderer.invoke('nginx:getSites'), - addSite: (site: any) => ipcRenderer.invoke('nginx:addSite', site), - removeSite: (name: string) => ipcRenderer.invoke('nginx:removeSite', name), - updateSite: (originalName: string, site: any) => ipcRenderer.invoke('nginx:updateSite', originalName, site), - enableSite: (name: string) => ipcRenderer.invoke('nginx:enableSite', name), - disableSite: (name: string) => ipcRenderer.invoke('nginx:disableSite', name), - generateLaravelConfig: (site: any) => ipcRenderer.invoke('nginx:generateLaravelConfig', site), - requestSSL: (domain: string, email: string) => ipcRenderer.invoke('nginx:requestSSL', domain, email) + getVersions: () => ipcRenderer.invoke("nginx:getVersions"), + getAvailableVersions: () => + ipcRenderer.invoke("nginx:getAvailableVersions"), + install: (version: string) => ipcRenderer.invoke("nginx:install", version), + uninstall: (version: string) => + ipcRenderer.invoke("nginx:uninstall", version), + start: () => ipcRenderer.invoke("nginx:start"), + stop: () => ipcRenderer.invoke("nginx:stop"), + restart: () => ipcRenderer.invoke("nginx:restart"), + reload: () => ipcRenderer.invoke("nginx:reload"), + getStatus: () => ipcRenderer.invoke("nginx:getStatus"), + getConfig: () => ipcRenderer.invoke("nginx:getConfig"), + saveConfig: (config: string) => + ipcRenderer.invoke("nginx:saveConfig", config), + getSites: () => ipcRenderer.invoke("nginx:getSites"), + addSite: (site: any) => ipcRenderer.invoke("nginx:addSite", site), + removeSite: (name: string) => ipcRenderer.invoke("nginx:removeSite", name), + updateSite: (originalName: string, site: any) => + ipcRenderer.invoke("nginx:updateSite", originalName, site), + enableSite: (name: string) => ipcRenderer.invoke("nginx:enableSite", name), + disableSite: (name: string) => + ipcRenderer.invoke("nginx:disableSite", name), + generateLaravelConfig: (site: any) => + ipcRenderer.invoke("nginx:generateLaravelConfig", site), + requestSSL: (domain: string, email: string) => + ipcRenderer.invoke("nginx:requestSSL", domain, email), }, // Redis 管理 redis: { - getVersions: () => ipcRenderer.invoke('redis:getVersions'), - getAvailableVersions: () => ipcRenderer.invoke('redis:getAvailableVersions'), - install: (version: string) => ipcRenderer.invoke('redis:install', version), - uninstall: (version: string) => ipcRenderer.invoke('redis:uninstall', version), - start: () => ipcRenderer.invoke('redis:start'), - stop: () => ipcRenderer.invoke('redis:stop'), - restart: () => ipcRenderer.invoke('redis:restart'), - getStatus: () => ipcRenderer.invoke('redis:getStatus'), - getConfig: () => ipcRenderer.invoke('redis:getConfig'), - saveConfig: (config: string) => ipcRenderer.invoke('redis:saveConfig', config) + getVersions: () => ipcRenderer.invoke("redis:getVersions"), + getAvailableVersions: () => + ipcRenderer.invoke("redis:getAvailableVersions"), + install: (version: string) => ipcRenderer.invoke("redis:install", version), + uninstall: (version: string) => + ipcRenderer.invoke("redis:uninstall", version), + start: () => ipcRenderer.invoke("redis:start"), + stop: () => ipcRenderer.invoke("redis:stop"), + restart: () => ipcRenderer.invoke("redis:restart"), + getStatus: () => ipcRenderer.invoke("redis:getStatus"), + getConfig: () => ipcRenderer.invoke("redis:getConfig"), + saveConfig: (config: string) => + ipcRenderer.invoke("redis:saveConfig", config), + }, + + // Go 管理 + go: { + getVersions: () => ipcRenderer.invoke("go:getVersions"), + getAvailableVersions: () => ipcRenderer.invoke("go:getAvailableVersions"), + install: (version: string, downloadUrl: string) => + ipcRenderer.invoke("go:install", version, downloadUrl), + uninstall: (version: string) => ipcRenderer.invoke("go:uninstall", version), + setActive: (version: string) => ipcRenderer.invoke("go:setActive", version), + getInfo: (version: string) => ipcRenderer.invoke("go:getInfo", version), }, // Node.js 管理 node: { - getVersions: () => ipcRenderer.invoke('node:getVersions'), - getAvailableVersions: () => ipcRenderer.invoke('node:getAvailableVersions'), - install: (version: string, downloadUrl: string) => ipcRenderer.invoke('node:install', version, downloadUrl), - uninstall: (version: string) => ipcRenderer.invoke('node:uninstall', version), - setActive: (version: string) => ipcRenderer.invoke('node:setActive', version), - getInfo: (version: string) => ipcRenderer.invoke('node:getInfo', version) + getVersions: () => ipcRenderer.invoke("node:getVersions"), + getAvailableVersions: () => ipcRenderer.invoke("node:getAvailableVersions"), + install: (version: string, downloadUrl: string) => + ipcRenderer.invoke("node:install", version, downloadUrl), + uninstall: (version: string) => + ipcRenderer.invoke("node:uninstall", version), + setActive: (version: string) => + ipcRenderer.invoke("node:setActive", version), + getInfo: (version: string) => ipcRenderer.invoke("node:getInfo", version), }, // Git 管理 git: { - getVersions: () => ipcRenderer.invoke('git:getVersions'), - getAvailableVersions: () => ipcRenderer.invoke('git:getAvailableVersions'), - install: (version: string) => ipcRenderer.invoke('git:install', version), - uninstall: () => ipcRenderer.invoke('git:uninstall'), - checkSystem: () => ipcRenderer.invoke('git:checkSystem'), - getConfig: () => ipcRenderer.invoke('git:getConfig'), - setConfig: (name: string, email: string) => ipcRenderer.invoke('git:setConfig', name, email) + getVersions: () => ipcRenderer.invoke("git:getVersions"), + getAvailableVersions: () => ipcRenderer.invoke("git:getAvailableVersions"), + install: (version: string) => ipcRenderer.invoke("git:install", version), + uninstall: () => ipcRenderer.invoke("git:uninstall"), + checkSystem: () => ipcRenderer.invoke("git:checkSystem"), + getConfig: () => ipcRenderer.invoke("git:getConfig"), + setConfig: (name: string, email: string) => + ipcRenderer.invoke("git:setConfig", name, email), }, // Python 管理 python: { - getVersions: () => ipcRenderer.invoke('python:getVersions'), - getAvailableVersions: () => ipcRenderer.invoke('python:getAvailableVersions'), - install: (version: string) => ipcRenderer.invoke('python:install', version), - uninstall: (version: string) => ipcRenderer.invoke('python:uninstall', version), - setActive: (version: string) => ipcRenderer.invoke('python:setActive', version), - checkSystem: () => ipcRenderer.invoke('python:checkSystem'), - getPipInfo: (version: string) => ipcRenderer.invoke('python:getPipInfo', version), - installPackage: (version: string, packageName: string) => ipcRenderer.invoke('python:installPackage', version, packageName) + getVersions: () => ipcRenderer.invoke("python:getVersions"), + getAvailableVersions: () => + ipcRenderer.invoke("python:getAvailableVersions"), + install: (version: string) => ipcRenderer.invoke("python:install", version), + uninstall: (version: string) => + ipcRenderer.invoke("python:uninstall", version), + setActive: (version: string) => + ipcRenderer.invoke("python:setActive", version), + checkSystem: () => ipcRenderer.invoke("python:checkSystem"), + getPipInfo: (version: string) => + ipcRenderer.invoke("python:getPipInfo", version), + installPackage: (version: string, packageName: string) => + ipcRenderer.invoke("python:installPackage", version, packageName), }, // 服务管理 service: { - getAll: () => ipcRenderer.invoke('service:getAll'), - setAutoStart: (service: string, enabled: boolean) => ipcRenderer.invoke('service:setAutoStart', service, enabled), - getAutoStart: (service: string) => ipcRenderer.invoke('service:getAutoStart', service), - startAll: () => ipcRenderer.invoke('service:startAll'), - stopAll: () => ipcRenderer.invoke('service:stopAll'), + getAll: () => ipcRenderer.invoke("service:getAll"), + setAutoStart: (service: string, enabled: boolean) => + ipcRenderer.invoke("service:setAutoStart", service, enabled), + getAutoStart: (service: string) => + ipcRenderer.invoke("service:getAutoStart", service), + startAll: () => ipcRenderer.invoke("service:startAll"), + stopAll: () => ipcRenderer.invoke("service:stopAll"), // PHP-CGI 多版本管理 - getPhpCgiStatus: () => ipcRenderer.invoke('service:getPhpCgiStatus'), - startPhpCgi: () => ipcRenderer.invoke('service:startPhpCgi'), - stopPhpCgi: () => ipcRenderer.invoke('service:stopPhpCgi'), - startAllPhpCgi: () => ipcRenderer.invoke('service:startAllPhpCgi'), - stopAllPhpCgi: () => ipcRenderer.invoke('service:stopAllPhpCgi'), - startPhpCgiVersion: (version: string) => ipcRenderer.invoke('service:startPhpCgiVersion', version), - stopPhpCgiVersion: (version: string) => ipcRenderer.invoke('service:stopPhpCgiVersion', version), - getPhpCgiPort: (version: string) => ipcRenderer.invoke('service:getPhpCgiPort', version) + getPhpCgiStatus: () => ipcRenderer.invoke("service:getPhpCgiStatus"), + startPhpCgi: () => ipcRenderer.invoke("service:startPhpCgi"), + stopPhpCgi: () => ipcRenderer.invoke("service:stopPhpCgi"), + startAllPhpCgi: () => ipcRenderer.invoke("service:startAllPhpCgi"), + stopAllPhpCgi: () => ipcRenderer.invoke("service:stopAllPhpCgi"), + startPhpCgiVersion: (version: string) => + ipcRenderer.invoke("service:startPhpCgiVersion", version), + stopPhpCgiVersion: (version: string) => + ipcRenderer.invoke("service:stopPhpCgiVersion", version), + getPhpCgiPort: (version: string) => + ipcRenderer.invoke("service:getPhpCgiPort", version), }, // Hosts 管理 hosts: { - get: () => ipcRenderer.invoke('hosts:get'), - add: (domain: string, ip: string) => ipcRenderer.invoke('hosts:add', domain, ip), - remove: (domain: string) => ipcRenderer.invoke('hosts:remove', domain) + get: () => ipcRenderer.invoke("hosts:get"), + add: (domain: string, ip: string) => + ipcRenderer.invoke("hosts:add", domain, ip), + remove: (domain: string) => ipcRenderer.invoke("hosts:remove", domain), }, // 配置管理 config: { - get: (key: string) => ipcRenderer.invoke('config:get', key), - set: (key: string, value: any) => ipcRenderer.invoke('config:set', key, value), - getBasePath: () => ipcRenderer.invoke('config:getBasePath'), - setBasePath: (path: string) => ipcRenderer.invoke('config:setBasePath', path) + get: (key: string) => ipcRenderer.invoke("config:get", key), + set: (key: string, value: any) => + ipcRenderer.invoke("config:set", key, value), + getBasePath: () => ipcRenderer.invoke("config:getBasePath"), + setBasePath: (path: string) => + ipcRenderer.invoke("config:setBasePath", path), }, // 日志管理 log: { - getFiles: () => ipcRenderer.invoke('log:getFiles'), - read: (logPath: string, lines?: number) => ipcRenderer.invoke('log:read', logPath, lines), - clear: (logPath: string) => ipcRenderer.invoke('log:clear', logPath), - getDirectory: (type: 'nginx' | 'php' | 'mysql' | 'sites', version?: string) => - ipcRenderer.invoke('log:getDirectory', type, version) + getFiles: () => ipcRenderer.invoke("log:getFiles"), + read: (logPath: string, lines?: number) => + ipcRenderer.invoke("log:read", logPath, lines), + clear: (logPath: string) => ipcRenderer.invoke("log:clear", logPath), + getDirectory: ( + type: "nginx" | "php" | "mysql" | "sites", + version?: string, + ) => ipcRenderer.invoke("log:getDirectory", type, version), }, // 应用设置 app: { - setAutoLaunch: (enabled: boolean) => ipcRenderer.invoke('app:setAutoLaunch', enabled), - getAutoLaunch: () => ipcRenderer.invoke('app:getAutoLaunch'), - setStartMinimized: (enabled: boolean) => ipcRenderer.invoke('app:setStartMinimized', enabled), - getStartMinimized: () => ipcRenderer.invoke('app:getStartMinimized'), - getVersion: () => ipcRenderer.invoke('app:getVersion') as Promise<{ version: string; buildTime: string; buildDate: string; isPackaged: boolean }>, - setAutoStartServices: (enabled: boolean) => ipcRenderer.invoke('app:setAutoStartServices', enabled), - getAutoStartServices: () => ipcRenderer.invoke('app:getAutoStartServices'), - quit: () => ipcRenderer.invoke('app:quit') + setAutoLaunch: (enabled: boolean) => + ipcRenderer.invoke("app:setAutoLaunch", enabled), + getAutoLaunch: () => ipcRenderer.invoke("app:getAutoLaunch"), + setStartMinimized: (enabled: boolean) => + ipcRenderer.invoke("app:setStartMinimized", enabled), + getStartMinimized: () => ipcRenderer.invoke("app:getStartMinimized"), + getVersion: () => + ipcRenderer.invoke("app:getVersion") as Promise<{ + version: string; + buildTime: string; + buildDate: string; + isPackaged: boolean; + }>, + setAutoStartServices: (enabled: boolean) => + ipcRenderer.invoke("app:setAutoStartServices", enabled), + getAutoStartServices: () => ipcRenderer.invoke("app:getAutoStartServices"), + quit: () => ipcRenderer.invoke("app:quit"), }, // 监听服务状态变化 onServiceStatusChanged: (callback: () => void) => { - ipcRenderer.on('service-status-changed', callback) + ipcRenderer.on("service-status-changed", callback); }, removeServiceStatusChangedListener: (callback: () => void) => { - ipcRenderer.removeListener('service-status-changed', callback) - } -}) + ipcRenderer.removeListener("service-status-changed", callback); + }, +}); // 声明 Window 接口扩展 declare global { interface Window { - electronAPI: typeof api + electronAPI: typeof api; } } const api = { - minimize: () => ipcRenderer.invoke('window:minimize'), - maximize: () => ipcRenderer.invoke('window:maximize'), - close: () => ipcRenderer.invoke('window:close'), - openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url), - openPath: (path: string) => ipcRenderer.invoke('shell:openPath', path), + minimize: () => ipcRenderer.invoke("window:minimize"), + maximize: () => ipcRenderer.invoke("window:maximize"), + close: () => ipcRenderer.invoke("window:close"), + openExternal: (url: string) => ipcRenderer.invoke("shell:openExternal", url), + openPath: (path: string) => ipcRenderer.invoke("shell:openPath", path), php: {} as any, mysql: {} as any, nginx: {} as any, redis: {} as any, service: {} as any, hosts: {} as any, - config: {} as any -} - + config: {} as any, +}; diff --git a/electron/services/ConfigStore.ts b/electron/services/ConfigStore.ts index b914514..3454544 100644 --- a/electron/services/ConfigStore.ts +++ b/electron/services/ConfigStore.ts @@ -10,8 +10,10 @@ interface ConfigSchema { nginxVersions: string[]; redisVersions: string[]; nodeVersions: string[]; + goVersions: string[]; activePhpVersion: string; activeNodeVersion: string; + activeGoVersion: string; autoStart: { nginx: boolean; mysql: boolean; @@ -66,8 +68,10 @@ export class ConfigStore { nginxVersions: [], redisVersions: [], nodeVersions: [], + goVersions: [], activePhpVersion: "", activeNodeVersion: "", + activeGoVersion: "", autoStart: { nginx: false, mysql: false, @@ -99,6 +103,7 @@ export class ConfigStore { join(this.basePath, "nginx", "ssl"), join(this.basePath, "redis"), join(this.basePath, "nodejs"), + join(this.basePath, "go"), join(this.basePath, "logs"), join(this.basePath, "temp"), join(this.basePath, "www"), @@ -149,6 +154,10 @@ export class ConfigStore { return join(this.basePath, "nodejs"); } + getGoPath(): string { + return join(this.basePath, "go"); + } + getLogsPath(): string { return join(this.basePath, "logs"); } diff --git a/electron/services/GoManager.ts b/electron/services/GoManager.ts new file mode 100644 index 0000000..33670ec --- /dev/null +++ b/electron/services/GoManager.ts @@ -0,0 +1,457 @@ +import { ConfigStore } from "./ConfigStore"; +import { exec } from "child_process"; +import { promisify } from "util"; +import { + existsSync, + mkdirSync, + readdirSync, + rmSync, + unlinkSync, + renameSync, +} from "fs"; +import { join } from "path"; +import https from "https"; +import http from "http"; +import { createWriteStream } from "fs"; +import unzipper from "unzipper"; +import { sendDownloadProgress } from "../main"; + +const execAsync = promisify(exec); + +interface GoVersion { + version: string; + path: string; + isActive: boolean; +} + +interface AvailableGoVersion { + version: string; + stable: boolean; + downloadUrl: string; + filename: string; +} + +export class GoManager { + private configStore: ConfigStore; + private versionsCache: AvailableGoVersion[] = []; + private cacheTime: number = 0; + private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 分钟缓存 + + constructor(configStore: ConfigStore) { + this.configStore = configStore; + } + + /** + * 获取已安装的 Go 版本 + */ + async getInstalledVersions(): Promise { + const versions: GoVersion[] = []; + const goPath = this.configStore.getGoPath(); + const activeVersion = this.configStore.get("activeGoVersion") || ""; + + if (!existsSync(goPath)) { + return versions; + } + + const dirs = readdirSync(goPath, { withFileTypes: true }); + for (const dir of dirs) { + if (dir.isDirectory() && dir.name.startsWith("go-")) { + const versionDir = join(goPath, dir.name); + const goExe = join(versionDir, "bin", "go.exe"); + + if (existsSync(goExe)) { + const version = dir.name.replace("go-", ""); + versions.push({ + version, + path: versionDir, + isActive: version === activeVersion, + }); + } + } + } + + versions.sort((a, b) => { + const aParts = a.version.replace("go", "").split(".").map(Number); + const bParts = b.version.replace("go", "").split(".").map(Number); + for (let i = 0; i < 3; i++) { + if (aParts[i] !== bParts[i]) return bParts[i] - aParts[i]; + } + return 0; + }); + + return versions; + } + + /** + * 从 go.dev 获取可用版本列表 + */ + async getAvailableVersions(): Promise { + if ( + this.versionsCache.length > 0 && + Date.now() - this.cacheTime < this.CACHE_DURATION + ) { + return this.versionsCache; + } + + try { + const versions = await this.fetchVersionsFromGoDev(); + if (versions.length > 0) { + this.versionsCache = versions; + this.cacheTime = Date.now(); + return versions; + } + } catch (error) { + console.error("[Go] Failed to fetch versions:", error); + } + + return this.getFallbackVersions(); + } + + /** + * 从 go.dev/dl API 获取版本列表 + */ + private async fetchVersionsFromGoDev(): Promise { + return new Promise((resolve, reject) => { + const url = "https://go.dev/dl/?mode=json"; + + https + .get( + url, + { + headers: { "User-Agent": "PHPer-Dev-Manager/1.0" }, + timeout: 30000, + }, + (res) => { + let data = ""; + res.on("data", (chunk) => (data += chunk)); + res.on("end", () => { + try { + const releases = JSON.parse(data); + const versions: AvailableGoVersion[] = []; + + for (const rel of releases) { + const winZip = rel.files?.find( + (f: any) => + f.os === "windows" && + f.arch === "amd64" && + f.filename?.endsWith(".zip"), + ); + if (winZip) { + versions.push({ + version: rel.version, + stable: rel.stable ?? true, + downloadUrl: `https://go.dev/dl/${winZip.filename}`, + filename: winZip.filename, + }); + } + } + + resolve(versions.slice(0, 30)); + } catch (e) { + reject(e); + } + }); + }, + ) + .on("error", reject) + .on("timeout", () => reject(new Error("Request timeout"))); + }); + } + + /** + * 安装 Go 版本 + */ + async install( + version: string, + downloadUrl: string, + ): Promise<{ success: boolean; message: string }> { + try { + const goPath = this.configStore.getGoPath(); + const tempPath = this.configStore.getTempPath(); + const zipPath = join(tempPath, `go-${version}.zip`); + const extractDir = join(tempPath, `go-extract-${version}`); + const versionDir = join(goPath, `go-${version}`); + + if (!existsSync(goPath)) mkdirSync(goPath, { recursive: true }); + if (!existsSync(tempPath)) mkdirSync(tempPath, { recursive: true }); + + if ( + existsSync(versionDir) && + existsSync(join(versionDir, "bin", "go.exe")) + ) { + return { success: false, message: `Go ${version} 已安装` }; + } + + console.log(`[Go] Downloading ${version}...`); + await this.downloadFile(downloadUrl, zipPath, `go-${version}`); + + console.log(`[Go] Extracting ${version}...`); + await this.extractZip(zipPath, extractDir); + + // Go zip 解压后根目录是 "go" 文件夹,需要重命名为 go-version + const innerGoDir = join(extractDir, "go"); + if (existsSync(innerGoDir)) { + renameSync(innerGoDir, versionDir); + } else { + return { success: false, message: "解压失败:未找到 go 目录" }; + } + + try { + unlinkSync(zipPath); + rmSync(extractDir, { recursive: true, force: true }); + } catch (e) { + // 忽略清理错误 + } + + if (!existsSync(join(versionDir, "bin", "go.exe"))) { + return { success: false, message: "安装失败:go.exe 不存在" }; + } + + const goVersions = this.configStore.get("goVersions") || []; + if (!goVersions.includes(version)) { + goVersions.push(version); + this.configStore.set("goVersions", goVersions); + } + + if (goVersions.length === 1) { + await this.setActive(version); + } + + return { success: true, message: `Go ${version} 安装成功` }; + } catch (error: any) { + return { success: false, message: `安装失败: ${error.message}` }; + } + } + + /** + * 卸载 Go 版本 + */ + async uninstall( + version: string, + ): Promise<{ success: boolean; message: string }> { + try { + const goPath = this.configStore.getGoPath(); + const versionDir = join(goPath, `go-${version}`); + + if (!existsSync(versionDir)) { + return { success: false, message: `Go ${version} 未安装` }; + } + + const activeVersion = this.configStore.get("activeGoVersion"); + if (activeVersion === version) { + this.configStore.set("activeGoVersion", ""); + } + + rmSync(versionDir, { recursive: true, force: true }); + + const goVersions = this.configStore.get("goVersions") || []; + const index = goVersions.indexOf(version); + if (index > -1) { + goVersions.splice(index, 1); + this.configStore.set("goVersions", goVersions); + } + + return { success: true, message: `Go ${version} 已卸载` }; + } catch (error: any) { + return { success: false, message: `卸载失败: ${error.message}` }; + } + } + + /** + * 设置活动的 Go 版本 + */ + async setActive( + version: string, + ): Promise<{ success: boolean; message: string }> { + try { + const goPath = this.configStore.getGoPath(); + const versionDir = join(goPath, `go-${version}`); + + if (!existsSync(join(versionDir, "bin", "go.exe"))) { + return { success: false, message: `Go ${version} 未安装` }; + } + + await this.addToPath(versionDir); + + this.configStore.set("activeGoVersion", version); + + return { success: true, message: `已将 Go ${version} 设为默认版本` }; + } catch (error: any) { + return { success: false, message: `设置失败: ${error.message}` }; + } + } + + /** + * 获取 Go 信息 + */ + async getGoInfo( + version: string, + ): Promise<{ goVersion: string; path: string } | null> { + const goPath = this.configStore.getGoPath(); + const versionDir = join(goPath, `go-${version}`); + const goExe = join(versionDir, "bin", "go.exe"); + + if (!existsSync(goExe)) return null; + + try { + const { stdout } = await execAsync(`"${goExe}" version`, { + timeout: 5000, + }); + return { + goVersion: stdout.trim(), + path: versionDir, + }; + } catch { + return null; + } + } + + private async downloadFile( + url: string, + dest: string, + name: string, + ): Promise { + return new Promise((resolve, reject) => { + const protocol = url.startsWith("https") ? https : http; + + const request = protocol.get( + url, + { + headers: { "User-Agent": "PHPer-Dev-Manager/1.0" }, + timeout: 600000, + }, + (response) => { + if (response.statusCode === 301 || response.statusCode === 302) { + const redirectUrl = response.headers.location; + if (redirectUrl) { + this.downloadFile(redirectUrl, dest, name) + .then(resolve) + .catch(reject); + return; + } + } + + if (response.statusCode !== 200) { + reject(new Error(`Download failed: HTTP ${response.statusCode}`)); + return; + } + + const totalSize = parseInt( + response.headers["content-length"] || "0", + 10, + ); + let downloadedSize = 0; + let lastProgressTime = 0; + + const file = createWriteStream(dest); + + response.on("data", (chunk) => { + downloadedSize += chunk.length; + const now = Date.now(); + if (now - lastProgressTime > 500) { + const progress = + totalSize > 0 + ? Math.round((downloadedSize / totalSize) * 100) + : 0; + sendDownloadProgress("go", progress, downloadedSize, totalSize); + lastProgressTime = now; + } + }); + + response.pipe(file); + + file.on("finish", () => { + file.close(); + sendDownloadProgress("go", 100, totalSize, totalSize); + resolve(); + }); + + file.on("error", (err) => { + unlinkSync(dest); + reject(err); + }); + }, + ); + + request.on("error", reject); + request.on("timeout", () => { + request.destroy(); + reject(new Error("Download timeout")); + }); + }); + } + + private async extractZip(zipPath: string, destDir: string): Promise { + return new Promise((resolve, reject) => { + const { createReadStream } = require("fs"); + createReadStream(zipPath) + .pipe(unzipper.Extract({ path: destDir })) + .on("close", resolve) + .on("error", reject); + }); + } + + private async addToPath(goPath: string): Promise { + const binPath = join(goPath, "bin"); + const psScript = ` +$ErrorActionPreference = 'Stop' +$newPath = '${binPath.replace(/\\/g, "\\\\")}' + +$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User') +$pathArray = $currentPath -split ';' | Where-Object { $_ -ne '' } + +$filteredPaths = $pathArray | Where-Object { + $p = $_.ToLower() + -not ($p -like '*\\\\go-*\\\\bin*' -or $p -like '*\\\\go\\\\bin*' -or $p -like '*phper*go*') +} + +$newPathArray = @($newPath) + $filteredPaths +$finalPath = ($newPathArray | Select-Object -Unique) -join ';' +[Environment]::SetEnvironmentVariable('Path', $finalPath, 'User') +Write-Output "PATH updated" +`; + + const tempPs1 = join(this.configStore.getTempPath(), "update_go_path.ps1"); + const { writeFileSync } = require("fs"); + writeFileSync(tempPs1, psScript, "utf-8"); + + try { + await execAsync(`powershell -ExecutionPolicy Bypass -File "${tempPs1}"`, { + timeout: 30000, + }); + } finally { + try { + unlinkSync(tempPs1); + } catch (e) {} + } + } + + private getFallbackVersions(): AvailableGoVersion[] { + return [ + { + version: "go1.25.7", + stable: true, + downloadUrl: "https://go.dev/dl/go1.25.7.windows-amd64.zip", + filename: "go1.25.7.windows-amd64.zip", + }, + { + version: "go1.24.13", + stable: true, + downloadUrl: "https://go.dev/dl/go1.24.13.windows-amd64.zip", + filename: "go1.24.13.windows-amd64.zip", + }, + { + version: "go1.23.5", + stable: true, + downloadUrl: "https://go.dev/dl/go1.23.5.windows-amd64.zip", + filename: "go1.23.5.windows-amd64.zip", + }, + { + version: "go1.22.14", + stable: true, + downloadUrl: "https://go.dev/dl/go1.22.14.windows-amd64.zip", + filename: "go1.22.14.windows-amd64.zip", + }, + ]; + } +} diff --git a/package.json b/package.json index 80a756f..536e2a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "phper-dev-manager", - "version": "1.0.8", + "version": "1.0.10", "description": "PHP开发环境管理器 - 管理PHP、MySQL、Nginx、Redis服务", "main": "dist-electron/main.js", "scripts": { diff --git a/public/version.json b/public/version.json index 3055dbe..323eba5 100644 --- a/public/version.json +++ b/public/version.json @@ -1,5 +1,5 @@ { - "version": "1.0.8", - "buildTime": "2026-01-03T18:53:37.506Z", - "buildDate": "2026/1/4" + "version": "1.0.10", + "buildTime": "2026-02-05T01:05:37.725Z", + "buildDate": "2026/2/5" } \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index fdb8faf..81f5ef3 100644 --- a/src/App.vue +++ b/src/App.vue @@ -29,20 +29,21 @@