refactor: 统一代码风格并更新依赖配置

- 统一使用单引号替代双引号
- 移除不必要的分号
- 更新prettier配置使用更宽松的格式
- 添加eslint配置文件和相关依赖
- 更新package.json中的脚本和依赖版本
This commit is contained in:
houakang
2026-04-10 10:39:41 +08:00
parent 4d578b3f0b
commit c738e638cf
15 changed files with 1004 additions and 429 deletions

View File

@@ -1,50 +1,53 @@
import { app, BrowserWindow, shell, ipcMain } from 'electron';
import path from 'node:path';
import fs from 'node:fs';
import net from 'node:net';
import { spawn } from 'node:child_process';
import started from 'electron-squirrel-startup';
import { app, BrowserWindow, shell, ipcMain } from 'electron'
import path from 'node:path'
import fs from 'node:fs'
import net from 'node:net'
import { spawn } from 'node:child_process'
import started from 'electron-squirrel-startup'
if (started) app.quit();
if (started) app.quit()
// ========== OpenCode 服务管理 ==========
const DEFAULT_PORT = 4096;
let opencodeProcess = null;
let opencodePort = null;
let opencodeStarting = null;
const DEFAULT_PORT = 4096
let opencodeProcess = null
let opencodePort = null
let opencodeStarting = null
function isPortAvailable(port) {
return new Promise((resolve) => {
const server = net.createServer();
server.once('error', () => resolve(false));
server.once('listening', () => server.close(() => resolve(true)));
server.listen(port, '127.0.0.1');
});
const server = net.createServer()
server.once('error', () => resolve(false))
server.once('listening', () => server.close(() => resolve(true)))
server.listen(port, '127.0.0.1')
})
}
async function resolvePort() {
let port = DEFAULT_PORT;
let port = DEFAULT_PORT
while (!(await isPortAvailable(port))) {
port++;
if (port > 65535) throw new Error('没有可用的端口');
port++
if (port > 65535) throw new Error('没有可用的端口')
}
return port;
return port
}
function waitForReady(port, timeout = 15000) {
return new Promise((resolve, reject) => {
const start = Date.now();
const start = Date.now()
const check = () => {
const socket = net.createConnection({ port, host: '127.0.0.1' });
socket.once('connect', () => { socket.end(); resolve(); });
const socket = net.createConnection({ port, host: '127.0.0.1' })
socket.once('connect', () => {
socket.end()
resolve()
})
socket.once('error', () => {
socket.destroy();
if (Date.now() - start >= timeout) return reject(new Error('OpenCode 服务启动超时'));
setTimeout(check, 300);
});
};
check();
});
socket.destroy()
if (Date.now() - start >= timeout) return reject(new Error('OpenCode 服务启动超时'))
setTimeout(check, 300)
})
}
check()
})
}
function buildInfo() {
@@ -52,112 +55,115 @@ function buildInfo() {
running: !!opencodeProcess,
port: opencodePort,
url: opencodePort ? `http://127.0.0.1:${opencodePort}` : null,
};
}
}
function buildEnv(exeDir) {
const env = { ...process.env };
const env = { ...process.env }
for (const key of Object.keys(env)) {
if (key.startsWith('npm_')) delete env[key];
if (key.startsWith('npm_')) delete env[key]
}
env.INIT_CWD = exeDir;
env.PWD = exeDir;
return env;
env.INIT_CWD = exeDir
env.PWD = exeDir
return env
}
function getExePath() {
// 开发模式__dirname = .vite/build往上两级到项目根
// 打包模式:用 process.resourcesPath
if (app.isPackaged) {
return path.join(process.resourcesPath, 'opencode.exe');
return path.join(process.resourcesPath, 'opencode.exe')
}
return path.join(__dirname, '..', '..', 'resources', 'windows', 'x64', 'opencode.exe');
return path.join(__dirname, '..', '..', 'resources', 'windows', 'x64', 'opencode.exe')
}
async function startOpencode() {
if (opencodeProcess) return buildInfo();
if (opencodeStarting) return opencodeStarting;
if (opencodeProcess) return buildInfo()
if (opencodeStarting) return opencodeStarting
opencodeStarting = (async () => {
const exePath = getExePath();
console.log('[opencode] exe path:', exePath);
const exeDir = path.dirname(exePath);
await fs.promises.access(exePath, fs.constants.F_OK);
const exePath = getExePath()
console.log('[opencode] exe path:', exePath)
const exeDir = path.dirname(exePath)
await fs.promises.access(exePath, fs.constants.F_OK)
opencodePort = await resolvePort();
opencodePort = await resolvePort()
opencodeProcess = spawn(exePath, ['serve', '--port', String(opencodePort)], {
cwd: exeDir,
windowsHide: true,
env: buildEnv(exeDir),
});
})
opencodeProcess.stdout?.on('data', (d) => console.log(`[opencode] ${d.toString().trim()}`));
opencodeProcess.stderr?.on('data', (d) => console.error(`[opencode error] ${d.toString().trim()}`));
opencodeProcess.once('error', (e) => console.error('[opencode spawn error]', e));
opencodeProcess.stdout?.on('data', (d) => console.log(`[opencode] ${d.toString().trim()}`))
opencodeProcess.stderr?.on('data', (d) => console.error(`[opencode error] ${d.toString().trim()}`))
opencodeProcess.once('error', (e) => console.error('[opencode spawn error]', e))
opencodeProcess.once('close', (code) => {
console.log(`[opencode exited] code=${code}`);
opencodeProcess = null;
opencodePort = null;
opencodeStarting = null;
});
console.log(`[opencode exited] code=${code}`)
opencodeProcess = null
opencodePort = null
opencodeStarting = null
})
await waitForReady(opencodePort);
return buildInfo();
})();
await waitForReady(opencodePort)
return buildInfo()
})()
try {
return await opencodeStarting;
return await opencodeStarting
} catch (err) {
opencodeProcess?.kill();
opencodeProcess = null;
opencodePort = null;
opencodeStarting = null;
throw err;
opencodeProcess?.kill()
opencodeProcess = null
opencodePort = null
opencodeStarting = null
throw err
}
}
function stopOpencode() {
opencodeProcess?.kill();
opencodeProcess = null;
opencodePort = null;
opencodeStarting = null;
opencodeProcess?.kill()
opencodeProcess = null
opencodePort = null
opencodeStarting = null
}
// ========== IPC Handlers ==========
function registerIpcHandlers() {
ipcMain.handle('opencode:start', () => startOpencode());
ipcMain.handle('opencode:stop', () => { stopOpencode(); return buildInfo(); });
ipcMain.handle('opencode:info', () => buildInfo());
ipcMain.handle('opencode:port', () => opencodePort);
ipcMain.handle('opencode:start', () => startOpencode())
ipcMain.handle('opencode:stop', () => {
stopOpencode()
return buildInfo()
})
ipcMain.handle('opencode:info', () => buildInfo())
ipcMain.handle('opencode:port', () => opencodePort)
ipcMain.handle('opencode:health', async () => {
if (!opencodePort) throw new Error('OpenCode 服务未启动');
const res = await fetch(`http://127.0.0.1:${opencodePort}/global/health`);
if (!res.ok) throw new Error(`健康检查失败: ${res.status}`);
return res.json();
});
if (!opencodePort) throw new Error('OpenCode 服务未启动')
const res = await fetch(`http://127.0.0.1:${opencodePort}/global/health`)
if (!res.ok) throw new Error(`健康检查失败: ${res.status}`)
return res.json()
})
ipcMain.handle('opencode:session:create', async (_e, data) => {
if (!opencodePort) throw new Error('OpenCode 服务未启动');
if (!opencodePort) throw new Error('OpenCode 服务未启动')
const res = await fetch(`http://127.0.0.1:${opencodePort}/session`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data ?? {}),
});
if (!res.ok) throw new Error(`创建会话失败: ${res.status}`);
return res.json();
});
})
if (!res.ok) throw new Error(`创建会话失败: ${res.status}`)
return res.json()
})
ipcMain.handle('opencode:session:send', async (_e, sessionId, text) => {
if (!opencodePort) throw new Error('OpenCode 服务未启动');
if (!opencodePort) throw new Error('OpenCode 服务未启动')
const res = await fetch(`http://127.0.0.1:${opencodePort}/session/${sessionId}/message`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ parts: [{ type: 'text', text }] }),
});
if (!res.ok) throw new Error(`发送消息失败: ${res.status}`);
return res.json();
});
})
if (!res.ok) throw new Error(`发送消息失败: ${res.status}`)
return res.json()
})
}
// ========== 窗口 ==========
@@ -174,45 +180,41 @@ const createWindow = () => {
},
titleBarStyle: 'hiddenInset',
show: false,
});
})
mainWindow.once('ready-to-show', () => mainWindow.show());
mainWindow.once('ready-to-show', () => mainWindow.show())
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
shell.openExternal(url)
return { action: 'deny' }
})
// 注入 baseUrl让渲染进程的 getBaseUrl() 能拿到正确端口
mainWindow.webContents.on('did-finish-load', () => {
if (opencodePort) {
mainWindow.webContents.executeJavaScript(
`window.__opencodeBaseUrl = 'http://127.0.0.1:${opencodePort}'`
);
mainWindow.webContents.executeJavaScript(`window.__opencodeBaseUrl = 'http://127.0.0.1:${opencodePort}'`)
}
});
})
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL)
} else {
mainWindow.loadFile(
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)
);
mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`))
}
};
}
app.whenReady().then(() => {
registerIpcHandlers();
createWindow();
registerIpcHandlers()
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
stopOpencode();
if (process.platform !== 'darwin') app.quit();
});
stopOpencode()
if (process.platform !== 'darwin') app.quit()
})
app.on('before-quit', () => stopOpencode());
app.on('before-quit', () => stopOpencode())