feat(desktop): buildscript

This commit is contained in:
xxnuo
2026-01-01 17:18:42 +08:00
parent d2eb1bf377
commit bcb6c370ac
3 changed files with 118 additions and 3 deletions

View File

@@ -1,7 +1,9 @@
import { app, dialog, nativeImage, shell, Menu } from 'electron';
import fs from 'fs/promises';
import fsSync from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { loadDesktopConfig, saveDesktopConfig, resetDesktopConfig } from './config.js';
import { loadDesktopConfig, saveDesktopConfig, resetDesktopConfig, getDefaultDesktopConfig } from './config.js';
import { resolveLocale, getMessages } from './i18n.js';
import { getFreePort, isPortAvailable } from './ports.js';
import { getServerStatus, startServerWithConfig, stopServerInstance } from './server.js';
@@ -24,6 +26,7 @@ let messages = getMessages(locale);
let tray = null;
const repoUrl = 'https://github.com/xxnuo/MTranServer';
let portCheckPromise = null;
let desktopLogPath = null;
function getLocalHost(host) {
if (!host || host === '0.0.0.0') return '127.0.0.1';
@@ -69,6 +72,17 @@ function updateTray() {
});
}
async function logDesktop(message, error) {
if (!desktopLogPath) return;
const timestamp = new Date().toISOString();
const details = error ? ` ${error.stack || error.message || error}` : '';
try {
await fs.appendFile(desktopLogPath, `[${timestamp}] ${message}${details}\n`, 'utf8');
} catch {
return;
}
}
async function ensurePortAvailable() {
if (portCheckPromise) return portCheckPromise;
portCheckPromise = (async () => {
@@ -76,6 +90,7 @@ async function ensurePortAvailable() {
const available = await isPortAvailable(desktopConfig.server.port, host);
if (available) return true;
await logDesktop(`port in use ${desktopConfig.server.port}`);
const result = await dialog.showMessageBox({
type: 'warning',
buttons: [messages.portInUseUseRandom, messages.portInUseQuit],
@@ -87,10 +102,12 @@ async function ensurePortAvailable() {
if (result.response === 0) {
const newPort = await getFreePort();
await logDesktop(`use random port ${newPort}`);
desktopConfig.server.port = newPort;
desktopConfig = await saveDesktopConfig(desktopConfig);
return true;
}
await logDesktop('quit after port in use');
await quitApp();
return false;
})();
@@ -105,9 +122,12 @@ async function startServer() {
const ok = await ensurePortAvailable();
if (!ok) return false;
try {
await logDesktop('starting server');
await startServerWithConfig(desktopConfig.server);
await logDesktop('server started');
return true;
} catch {
} catch (error) {
await logDesktop('server start failed', error);
dialog.showMessageBox({
type: 'error',
message: messages.serverStartFailed,
@@ -119,7 +139,9 @@ async function startServer() {
async function restartServer() {
try {
await logDesktop('restarting server');
await stopServerInstance();
await ensureWritableDirs();
const ok = await startServer();
if (!ok) return false;
updateWindowUrls({
@@ -138,6 +160,7 @@ async function restartServer() {
}
async function quitApp() {
await logDesktop('quit app');
app.isQuitting = true;
await stopServerInstance();
app.quit();
@@ -149,6 +172,40 @@ function updateLocale(nextLocale) {
updateTray();
}
async function ensureWritableDirs() {
const defaults = getDefaultDesktopConfig();
const updates = {};
const targets = [
['modelDir', defaults.server.modelDir],
['logDir', defaults.server.logDir],
['configDir', defaults.server.configDir]
];
for (const [key, fallback] of targets) {
const target = desktopConfig.server[key];
if (!target) {
updates[key] = fallback;
continue;
}
try {
await fs.mkdir(target, { recursive: true });
await fs.access(target, fsSync.constants.W_OK);
} catch {
updates[key] = fallback;
}
}
if (Object.keys(updates).length > 0) {
desktopConfig = await saveDesktopConfig({
...desktopConfig,
server: {
...desktopConfig.server,
...updates
}
});
}
}
function getLoadingUrl() {
const html = `
<!doctype html>
@@ -185,6 +242,10 @@ export async function startDesktop() {
Menu.setApplicationMenu(null);
desktopConfig = await loadDesktopConfig();
updateLocale(desktopConfig.locale);
desktopLogPath = path.join(desktopConfig.server.configDir, 'desktop.log');
await fs.mkdir(desktopConfig.server.configDir, { recursive: true });
await logDesktop('desktop starting');
await ensureWritableDirs();
const preloadPath = path.join(__dirname, 'preload.cjs');
const loadingUrl = getLoadingUrl();

View File

@@ -21,7 +21,7 @@
"bump": "bun scripts/bump.ts",
"electron": "bun run build:lib && electron ./electron-main.js",
"electron:dev": "bun run scripts/electron-dev.ts",
"electron:build": "bun run build:lib && electron-builder",
"electron:build": "bun run scripts/electron-build.ts",
"electron:build:all": "bun run build:lib && electron-builder -mwl",
"prepublishOnly": "bun scripts/build.ts --lib",
"push": "npm publish --registry=https://registry.npmjs.org",
@@ -58,6 +58,7 @@
"typescript": "^5.9.3"
},
"dependencies": {
"ee-first": "^1.1.1",
"express": "^5.2.1",
"express-validator": "^7.3.1",
"fzstd": "^0.1.1",
@@ -71,15 +72,23 @@
"build": {
"appId": "com.github.xxnuo.mtranserver",
"productName": "MTranServer",
"executableName": "mtranserver",
"copyright": "Copyright © 2025",
"extraMetadata": {
"main": "electron-main.js"
},
"asar": true,
"asarUnpack": [
"dist/mtranserver*",
"dist/*.exe"
],
"artifactName": "mtranserver-desktop-${version}-${os}-${arch}.${ext}",
"files": [
"desktop/**/*",
"electron-main.js",
"dist/**/*",
"!dist/mtranserver*",
"!dist/*.exe",
"images/**/*",
"package.json",
"node_modules/**/*"

45
scripts/electron-build.ts Normal file
View File

@@ -0,0 +1,45 @@
import { spawn } from 'child_process'
import fs from 'fs'
import path from 'path'
function run(command: string, args: string[]) {
return new Promise<void>((resolve, reject) => {
const proc = spawn(command, args, { stdio: 'inherit', env: process.env })
proc.on('exit', (code) => {
if (code === 0) resolve()
else reject(new Error(`exit ${code}`))
})
})
}
async function main() {
const bunBin = process.execPath
await run(bunBin, ['run', 'build:lib'])
const distDir = path.join(process.cwd(), 'dist')
if (fs.existsSync(distDir)) {
for (const entry of fs.readdirSync(distDir)) {
if (entry.startsWith('mtranserver') || entry.endsWith('.exe')) {
try {
fs.rmSync(path.join(distDir, entry), { recursive: true, force: true })
} catch {
continue
}
}
}
}
const platform = process.platform
const builderArgs = ['node_modules/.bin/electron-builder']
if (platform === 'linux') {
builderArgs.push('--linux', 'AppImage')
} else if (platform === 'darwin') {
builderArgs.push('--mac')
} else if (platform === 'win32') {
builderArgs.push('--win')
}
await run(bunBin, builderArgs)
}
main().catch(() => process.exit(1))