@flun/desktop-builder 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,222 @@
1
+ /**
2
+ * @flun/desktop-builder 配置文件
3
+ * 所有路径相对于项目根目录
4
+ */
5
+ export default {
6
+ // 必填字段:serverPath,appUrl,appName;
7
+ serverPath: './server.js', // 网站启动脚本路径
8
+ appUrl: 'http://www.abc.com:7296', // 网站访问地址
9
+ appName: '我的桌面应用', // 应用显示名称
10
+ enableLogging: false, // 是否启用日志文件记录,默认关闭
11
+
12
+ // 窗口配置
13
+ window: {
14
+ width: 1200, // 默认宽度(px)
15
+ height: 800, // 默认高度(px)
16
+ minWidth: 800, // 最小宽度(px)
17
+ minHeight: 600, // 最小高度(px)
18
+ maxWidth: undefined, // 最大宽度(px),不限制则留空
19
+ maxHeight: undefined, // 最大高度(px),不限制则留空
20
+ resizable: true, // 是否可调整窗口大小
21
+ fullscreenable: true, // 是否允许全屏
22
+ alwaysOnTop: false, // 是否始终置顶
23
+ frame: true, // 是否显示标题栏
24
+ titleBarStyle: 'default', // 标题栏样式: default/hidden/hiddenInset
25
+ backgroundColor: '#ffffff', // 加载时的背景色
26
+ show: false, // false=等页面渲染完再显示,防白屏
27
+ webPreferences: {
28
+ // 注意:nodeIntegration、contextIsolation、sandbox 将被强制覆盖,此处配置无效
29
+ // 其他属性(如 plugins, webSecurity, enableWebAuthn 等)仍然生效
30
+ nodeIntegration: false, // 此值无效,实际强制为 true
31
+ contextIsolation: true, // 此值无效,实际强制为 false
32
+ },
33
+ },
34
+
35
+ // 图标
36
+ branding: {
37
+ appIcon: null, // 应用图标路径,建议 512x512 PNG
38
+ installerIcon: null, // 安装程序图标(必须 .ico)
39
+ uninstallerIcon: null // 卸载程序图标(必须 .ico)
40
+ },
41
+
42
+ // 菜单配置(可自由修改语言和结构)
43
+ menu: [
44
+ {
45
+ label: '文件',
46
+ submenu: [
47
+ { role: 'close', label: '关闭' },
48
+ { type: 'separator' },
49
+ { role: 'quit', label: '退出' }
50
+ ]
51
+ },
52
+ {
53
+ label: '编辑',
54
+ submenu: [
55
+ { role: 'undo', label: '撤销' },
56
+ { role: 'redo', label: '重做' },
57
+ { type: 'separator' },
58
+ { role: 'cut', label: '剪切' },
59
+ { role: 'copy', label: '复制' },
60
+ { role: 'paste', label: '粘贴' },
61
+ { role: 'selectAll', label: '全选' }
62
+ ]
63
+ },
64
+ {
65
+ label: '查看',
66
+ submenu: [
67
+ { role: 'reload', label: '重新加载' },
68
+ { role: 'forceReload', label: '强制重新加载' },
69
+ { role: 'toggleDevTools', label: '开发者工具' },
70
+ { type: 'separator' },
71
+ { role: 'resetZoom', label: '重置缩放' },
72
+ { role: 'zoomIn', label: '放大' },
73
+ { role: 'zoomOut', label: '缩小' },
74
+ { type: 'separator' },
75
+ { role: 'togglefullscreen', label: '全屏' }
76
+ ]
77
+ },
78
+ {
79
+ label: '窗口',
80
+ submenu: [
81
+ {
82
+ label: '在浏览器中打开',
83
+ click: '__TOGGLE_BROWSER__'
84
+ },
85
+ { role: 'minimize', label: '最小化' },
86
+ { role: 'zoom', label: '缩放' },
87
+ { type: 'separator' },
88
+ { role: 'close', label: '关闭' }
89
+ ]
90
+ },
91
+ {
92
+ label: '帮助',
93
+ submenu: [
94
+ {
95
+ label: '关于',
96
+ click: `() => { require('electron').shell.openExternal('https://electronjs.org'); }`
97
+ }
98
+ ]
99
+ }
100
+ ],
101
+
102
+ // 打包配置
103
+ build: {
104
+ appId: 'com.mycompany.myapp', // 应用唯一标识(反向域名格式)
105
+ outputDir: './dist', // 安装包输出目录
106
+
107
+ // ----- Windows 安装包选项 (NSIS) -----
108
+ nsis: {
109
+ oneClick: false, // true=一键安装,false=向导安装
110
+ perMachine: true, // true=安装到所有用户,false=仅当前用户
111
+ allowToChangeInstallationDirectory: true, // 是否允许用户更改安装路径
112
+ createDesktopShortcut: true, // 是否创建桌面快捷方式
113
+ createStartMenuShortcut: true, // 是否创建开始菜单快捷方式
114
+ shortcutName: '我的桌面应用', // 快捷方式名称
115
+ deleteAppDataOnUninstall: false, // 卸载时是否删除用户数据
116
+ },
117
+ // Windows 平台通用配置(可覆盖或补充)
118
+ win: {
119
+ target: ['nsis'], // 构建目标:nsis / portable / zip 等
120
+ // 其他可选字段:icon, publisherName, signingHashAlgorithms 等
121
+ },
122
+
123
+ // ----- macOS 配置(增强) -----
124
+ mac: {
125
+ target: ['dmg', 'zip'], // 构建目标:dmg / zip / pkg / mas 等
126
+ // 以下为可选高级字段(如需代码签名或 Mac App Store 发布,可取消注释并填写)
127
+ // identity: 'Developer ID Application: Your Name (TEAM123)', // 签名证书名称
128
+ // hardenedRuntime: true, // 启用 Hardened Runtime
129
+ // entitlements: './build/entitlements.mac.plist', // 签名 entitlements 文件
130
+ // entitlementsInherit: './build/entitlements.mac.inherit.plist', // Helper 进程 entitlements
131
+ // provisioningProfile: './build/profile.provisionprofile', // 仅 MAS 需要
132
+ },
133
+ // macOS DMG 选项
134
+ dmg: {
135
+ iconSize: 128, // 图标大小
136
+ window: { width: 540, height: 380 }, // DMG 窗口尺寸
137
+ // 以下为增强选项(可选)
138
+ // background: './build/dmg-background.png', // DMG 背景图片(建议 PNG)
139
+ // backgroundColor: '#ffffff', // 无背景图时的背景色
140
+ // icon: './build/volume-icon.icns', // DMG 卷宗图标(显示在 Finder 侧边栏)
141
+ // title: '${productName} ${version}', // 挂载后显示的卷宗名称
142
+ // format: 'UDZO', // 压缩格式(UDZO/ULFO/UDBZ 等)
143
+ // contents: [ // 自定义窗口内图标布局
144
+ // { x: 130, y: 220, type: 'file' },
145
+ // { x: 410, y: 220, type: 'link', path: '/Applications' }
146
+ // ]
147
+ },
148
+
149
+ // ----- Linux 配置(增强) -----
150
+ linux: {
151
+ target: ['AppImage', 'deb'], // 构建目标:AppImage / deb / rpm / snap / flatpak 等
152
+ category: 'Development', // 系统菜单分类(如 Utility, Network, Development 等)
153
+ // 以下为可选高级字段
154
+ // description: '完整的应用描述', // 长描述
155
+ // synopsis: '简短描述', // 短描述
156
+ // maintainer: '你的名字 <email@example.com>', // 维护者信息
157
+ // vendor: '我的公司', // 供应商名称
158
+ // executableArgs: ['--enable-features=...'], // 启动时的命令行参数
159
+ // desktop: { // 自定义 .desktop 文件内容
160
+ // entry: {
161
+ // Name: '我的应用',
162
+ // Comment: '一个很棒的应用',
163
+ // Categories: 'Development;Utility;',
164
+ // Keywords: 'app;tool;',
165
+ // Terminal: false,
166
+ // Type: 'Application'
167
+ // }
168
+ // },
169
+ // syncDesktopName: true, // 同步 .desktop 文件名与窗口类名,防止任务栏图标错乱
170
+ },
171
+ // 特定格式的额外配置(可选)
172
+ // appImage: {
173
+ // systemIntegration: 'doNotAsk' // 是否询问系统集成
174
+ // },
175
+ // deb: {
176
+ // depends: ['libgtk-3-0'] // deb 包的依赖
177
+ // },
178
+ },
179
+
180
+ // 高级选项
181
+ advanced: {
182
+ autoStartServer: true, // 是否自动启动后端服务
183
+ autoKillServer: true // 退出时是否自动关闭后端
184
+ },
185
+
186
+ // 排除文件/目录(相对于项目根目录,支持 glob 模式)
187
+ excludeFiles: [
188
+ '.vscode/',
189
+ '.idea/',
190
+ '.git/',
191
+ '.hintrc',
192
+ '.greenlockrc',
193
+ 'node_modules/', // ← 默认排除,以优化构建和安装速度(避免签名和解压海量文件)
194
+ 'dist/',
195
+ 'docs/',
196
+ 'temp/',
197
+ 'tests/',
198
+ './yarn.lock',
199
+ './desktop.ini',
200
+ './desktopAppConfig.js',
201
+ './sevWin.js',
202
+ './unSevWin.js',
203
+ '*.tgz',
204
+ '*.log'
205
+ ],
206
+
207
+ // 排除依赖包(从最终依赖列表中移除,不会安装)
208
+ excludeDependencies: [
209
+ '@flun/desktop-builder',
210
+ '@flun/windows'
211
+ ],
212
+
213
+ /**
214
+ * 排除输出文件(在最终输出目录中排除某些安装包文件)
215
+ * 例如 *.blockmap、latest.yml 等;
216
+ * 注意:此配置仅在复制最终安装包到输出目录时生效,不影响构建过程;
217
+ */
218
+ excludeOutputs: [
219
+ '*.blockmap',
220
+ 'latest.yml'
221
+ ]
222
+ };
@@ -0,0 +1,317 @@
1
+ import { app, BrowserWindow, Menu, shell } from 'electron';
2
+ import { spawn, exec } from 'child_process';
3
+ import { promisify } from 'util';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import fs from 'fs';
7
+ import http from 'http';
8
+ import https from 'https';
9
+ import net from 'net';
10
+ import { createRequire } from 'module';
11
+
12
+ // --------------------- 全局变量 ---------------------
13
+ let mainWindow = null, serverProcess = null, windowCreationPromise = null, loadRetryCount = 0;
14
+ const require = createRequire(import.meta.url),
15
+ __dirname = path.dirname(fileURLToPath(import.meta.url)), execPromise = promisify(exec),
16
+ // 配置注入
17
+ CONFIG = {
18
+ APP_URL: JSON.parse('__APP_URL__'),
19
+ WINDOW_CONFIG: JSON.parse('__WINDOW_CONFIG__'),
20
+ SERVER_PATH: JSON.parse('__SERVER_PATH__'),
21
+ AUTO_START_SERVER: JSON.parse('__AUTO_START_SERVER__'),
22
+ AUTO_KILL_SERVER: JSON.parse('__AUTO_KILL_SERVER__'),
23
+ LOGGING_ENABLED: JSON.parse('__LOGGING_ENABLED__'),
24
+ MENU_TEMPLATE: __MENU_TEMPLATE__,
25
+ }, TARGET_URL = CONFIG.APP_URL,
26
+ // 命令行开关
27
+ switches = ['no-sandbox', 'ignore-certificate-errors', 'allow-insecure-localhost'],
28
+
29
+ // --------------------- 函数声明 ---------------------
30
+ // 日志(根据配置决定是否写入)
31
+ writeLog = (filePath, msg) => {
32
+ try {
33
+ fs.appendFileSync(filePath, new Date().toISOString() + ' ' + msg + '\n');
34
+ } catch (_) { }
35
+ },
36
+ log = msg => {
37
+ if (!CONFIG.LOGGING_ENABLED) return;
38
+ writeLog(path.join(app.getPath('desktop'), 'myapp_debug.log'), msg);
39
+ },
40
+ emergencyLog = (msg) => {
41
+ if (!CONFIG.LOGGING_ENABLED) return;
42
+ writeLog(path.join(process.cwd(), 'myapp_emergency.log'), msg);
43
+ },
44
+ // 服务器管理
45
+ killServerProcess = () => {
46
+ if (!serverProcess) return;
47
+ try {
48
+ if (process.platform === 'win32') exec(`taskkill /pid ${serverProcess.pid} /T /F`);
49
+ else process.kill(-serverProcess.pid, 'SIGTERM');
50
+ } catch (_) { }
51
+ serverProcess = null;
52
+ },
53
+
54
+ focusMainWindow = () => {
55
+ if (mainWindow && !mainWindow.isDestroyed()) {
56
+ if (mainWindow.isMinimized()) mainWindow.restore();
57
+ return mainWindow.focus(), true;
58
+ }
59
+ return false;
60
+ },
61
+ sleep = ms => new Promise(resolve => setTimeout(resolve, ms)),
62
+ // 依赖安装
63
+ ensureDependencies = async () => {
64
+ const nodeModulesPath = path.join(__dirname, 'node_modules');
65
+ try {
66
+ return await fs.promises.access(nodeModulesPath, fs.constants.F_OK), true;
67
+ } catch {
68
+ log('未找到 node_modules,开始安装依赖...');
69
+ const depsPath = path.join(__dirname, 'deps.json');
70
+ try {
71
+ await fs.promises.access(depsPath, fs.constants.F_OK);
72
+ } catch {
73
+ return log('错误: deps.json 不存在,无法安装依赖'), false;
74
+ }
75
+ let deps;
76
+ try {
77
+ const depsContent = await fs.promises.readFile(depsPath, 'utf-8');
78
+ deps = JSON.parse(depsContent);
79
+ } catch (err) {
80
+ return log('读取 deps.json 失败: ' + err.message), false;
81
+ }
82
+ const pkgPath = path.join(__dirname, 'package.json');
83
+ let pkg;
84
+ try {
85
+ const pkgContent = await fs.promises.readFile(pkgPath, 'utf-8');
86
+ pkg = JSON.parse(pkgContent);
87
+ } catch (err) {
88
+ return log('读取 package.json 失败: ' + err.message), false;
89
+ }
90
+ pkg.dependencies = deps, pkg.devDependencies = {};
91
+ try {
92
+ await fs.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf-8');
93
+ } catch (err) {
94
+ return log('写入 package.json 失败: ' + err.message), false;
95
+ }
96
+ try {
97
+ let cmd = 'npm install';
98
+ const lockPath = path.join(__dirname, 'package-lock.json');
99
+ try {
100
+ await fs.promises.access(lockPath, fs.constants.F_OK);
101
+ log('找到 package-lock.json,使用 npm ci'), cmd = 'npm ci';
102
+ } catch {
103
+ log('未找到 package-lock.json,使用 npm install');
104
+ }
105
+ const { stdout, stderr } = await execPromise(cmd, { cwd: __dirname, env: process.env, timeout: 120000 });
106
+ if (stdout && stderr) log('依赖安装完成');
107
+ try {
108
+ return await fs.promises.access(nodeModulesPath, fs.constants.F_OK), true;
109
+ } catch {
110
+ return log('安装 node_modules 失败'), false;
111
+ }
112
+ } catch (error) {
113
+ return log('依赖安装失败: ' + error.message), emergencyLog('依赖安装失败堆栈: ' + error.stack), false;
114
+ }
115
+ }
116
+ },
117
+
118
+ // 端口检查
119
+ ensurePortFree = port => {
120
+ return new Promise((resolve) => {
121
+ const server = net.createServer();
122
+ server.once('error', (err) => {
123
+ if (err.code === 'EADDRINUSE') {
124
+ const cmd = process.platform === 'win32' ? `netstat -ano | findstr :${port}` : `lsof -i :${port} -t`;
125
+ exec(cmd, (err, stdout) => {
126
+ if (err) { resolve(false); return; }
127
+ const pids = stdout.split(/\s+/).filter(x => x && !isNaN(x));
128
+ let killed = false;
129
+ for (const pid of pids)
130
+ try { process.kill(parseInt(pid), 'SIGTERM'), killed = true; } catch (_) { };
131
+ setTimeout(() => resolve(killed), 1000);
132
+ });
133
+ }
134
+ else resolve(false);
135
+ });
136
+ server.once('listening', () => { server.close(), resolve(true); }), server.listen(port, '127.0.0.1');
137
+ });
138
+ },
139
+
140
+ // 等待服务器就绪
141
+ waitForServer = (port, timeout = 30000) => {
142
+ return new Promise((resolve) => {
143
+ const start = Date.now(), protocol = new URL(CONFIG.APP_URL).protocol,
144
+ httpModule = protocol === 'https:' ? https : http,
145
+ check = () => {
146
+ if (Date.now() - start > timeout) return log('服务器就绪超时'), resolve(false);
147
+ const req = httpModule.get({
148
+ hostname: '127.0.0.1', port, path: '/',
149
+ rejectUnauthorized: false,
150
+ timeout: 5000, family: 4,
151
+ }, res => {
152
+ log('服务器响应 ' + res.statusCode), resolve(true), req.destroy();
153
+ });
154
+ req.on('error', err => {
155
+ log('服务器尚未就绪: ' + err.message), setTimeout(check, 2000);
156
+ });
157
+ req.on('timeout', () => {
158
+ req.destroy(), setTimeout(check, 2000);
159
+ });
160
+ };
161
+ check();
162
+ });
163
+ },
164
+
165
+ openInBrowser = () => {
166
+ try {
167
+ shell.openExternal(CONFIG.APP_URL);
168
+ } catch (e) {
169
+ log('打开浏览器失败: ' + e.message);
170
+ }
171
+ },
172
+
173
+ // 创建窗口
174
+ createWindow = async () => {
175
+ if (mainWindow && !mainWindow.isDestroyed()) return focusMainWindow(), mainWindow;
176
+ if (windowCreationPromise) {
177
+ await windowCreationPromise;
178
+ if (mainWindow) return focusMainWindow(), mainWindow;
179
+ }
180
+ windowCreationPromise = (async () => {
181
+ try {
182
+ const winConfig = {
183
+ ...CONFIG.WINDOW_CONFIG,
184
+ webPreferences: {
185
+ ...CONFIG.WINDOW_CONFIG.webPreferences,
186
+ webSecurity: true,
187
+ allowRunningInsecureContent: true,
188
+ enableWebAuthn: true,
189
+ plugins: true,
190
+ nodeIntegration: true,
191
+ sandbox: false,
192
+ contextIsolation: false,
193
+ }
194
+ };
195
+ mainWindow = new BrowserWindow(winConfig);
196
+ mainWindow.webContents.session.setPermissionRequestHandler((wc, perm, cb) => cb(true));
197
+ mainWindow.webContents.session.setDevicePermissionHandler(() => true);
198
+
199
+ let shown = false;
200
+ mainWindow.once('ready-to-show', () => {
201
+ if (!mainWindow.isDestroyed())
202
+ mainWindow.show(), mainWindow.focus(), mainWindow.webContents.focus(), shown = true;
203
+ });
204
+
205
+ mainWindow.webContents.on('did-fail-load', (e, code, desc, url) => {
206
+ if (code === -102 && loadRetryCount < 3) {
207
+ loadRetryCount++;
208
+ setTimeout(() => { if (mainWindow) mainWindow.loadURL(TARGET_URL).catch(() => { }); }, 2000);
209
+ return;
210
+ }
211
+ if (!shown) mainWindow.show();
212
+ mainWindow.loadURL(`data:text/html;charset=utf-8,<h2>加载失败</h2><p>${desc}</p><p>URL: ${url}</p>`);
213
+ });
214
+
215
+ await mainWindow.loadURL(TARGET_URL), loadRetryCount = 0;
216
+ setTimeout(() => { if (mainWindow && !mainWindow.isVisible()) mainWindow.show(); }, 3000);
217
+ mainWindow.on('closed', () => mainWindow = null);
218
+ return mainWindow;
219
+ } catch (err) {
220
+ log('创建窗口失败: ' + err.message), emergencyLog('窗口创建失败: ' + err.message), app.quit();
221
+ } finally { windowCreationPromise = null; }
222
+ })();
223
+ return windowCreationPromise;
224
+ },
225
+
226
+ // 启动服务器
227
+ startServer = async () => {
228
+ if (!CONFIG.AUTO_START_SERVER) return;
229
+ const serverPath = path.join(__dirname, CONFIG.SERVER_PATH);
230
+ if (!fs.existsSync(serverPath)) { log('服务器文件不存在: ' + serverPath); return; }
231
+
232
+ let port = 7296;
233
+ try { port = parseInt(new URL(CONFIG.APP_URL).port) || 7296 } catch (_) { }
234
+
235
+ for (let attempt = 1; attempt <= 3; attempt++) {
236
+ await ensurePortFree(port);
237
+
238
+ const env = { ...process.env, NODE_PATH: path.join(__dirname, 'node_modules') };
239
+ serverProcess = spawn('node', [serverPath], { cwd: __dirname, stdio: ['ignore', 'pipe', 'pipe'], detached: true, env });
240
+ serverProcess.stdout.on('data', d => log('服务器 stdout: ' + d.toString().trim()));
241
+ serverProcess.stderr.on('data', d => log('服务器 stderr: ' + d.toString().trim()));
242
+ serverProcess.on('error', err => log('服务器进程错误: ' + err.message));
243
+ serverProcess.unref();
244
+
245
+ const ready = await waitForServer(port, 30000);
246
+ if (ready) return;
247
+
248
+ log(`服务器未就绪 (尝试 ${attempt}/3),重试...`), killServerProcess(), await sleep(2000);
249
+ }
250
+ log('服务器启动失败,尝试重新加载窗口');
251
+ },
252
+
253
+ // 菜单设置(处理特殊标记)
254
+ setupMenu = () => {
255
+ const template = CONFIG.MENU_TEMPLATE;
256
+ if (!template || template.length === 0) return;
257
+
258
+ const processMenu = (items) => {
259
+ return items.map(item => {
260
+ const newItem = { ...item };
261
+ if (newItem.submenu) newItem.submenu = processMenu(newItem.submenu);
262
+ if (newItem.click === '__TOGGLE_BROWSER__') newItem.click = openInBrowser;
263
+ else if (typeof newItem.click === 'string' &&
264
+ /^(async\s+)?(function\s*(\w*\s*)?\(|\(\)\s*=>|async\s*\(\)\s*=>)/.test(newItem.click.trim()))
265
+ try { newItem.click = eval(newItem.click) } catch (_) { }
266
+
267
+ return newItem;
268
+ });
269
+ };
270
+
271
+ Menu.setApplicationMenu(Menu.buildFromTemplate(processMenu(template)));
272
+ };
273
+
274
+ // --------------------- 主流程 ---------------------
275
+ globalThis.require = require;
276
+ switches.forEach(s => app.commandLine.appendSwitch(s));
277
+ try {
278
+ const hostname = new URL(TARGET_URL).hostname;
279
+ app.commandLine.appendSwitch('host-resolver-rules', `MAP ${hostname} 127.0.0.1`);
280
+ app.commandLine.appendSwitch('enable-features',
281
+ 'WebAuthentication,WebAuthn,WebAuthenticationHidSupport,WebAuthenticationWindowsApi,WebAuthenticationAndroidAccessory');
282
+ app.commandLine.appendSwitch('disable-features', 'UseDnsHttpsSvcb,BlockInsecurePrivateNetworkRequests');
283
+ app.commandLine.appendSwitch('enable-experimental-web-platform-features');
284
+ app.commandLine.appendSwitch('disable-blink-features', 'AutomationControlled');
285
+ app.commandLine.appendSwitch('vmodule', 'webauthn*=3');
286
+ log(`已映射 ${hostname} 到 127.0.0.1`);
287
+ } catch (_) { }
288
+ // 单例锁
289
+ if (!app.requestSingleInstanceLock()) { log('已有另一个实例在运行,退出'); app.quit(); }
290
+ else app.on('second-instance', () => focusMainWindow());
291
+ app.on('certificate-error', (e, wc, url, err, cert, cb) => { e.preventDefault(), cb(true); });
292
+
293
+ app.whenReady().then(async () => {
294
+ setupMenu();
295
+
296
+ const depsOk = await ensureDependencies();
297
+ if (!depsOk) log('依赖安装失败,再次尝试启动服务器(可能失败)');
298
+ await startServer(), await createWindow();
299
+ });
300
+
301
+ app.on('window-all-closed', () => {
302
+ log('所有窗口已关闭');
303
+ if (CONFIG.AUTO_KILL_SERVER) killServerProcess();
304
+ app.quit();
305
+ });
306
+
307
+ app.on('activate', () => {
308
+ if (BrowserWindow.getAllWindows().length === 0) createWindow();
309
+ else if (mainWindow) mainWindow.focus();
310
+ });
311
+
312
+ process.on('uncaughtException', (err) => {
313
+ const msg = '未捕获异常: ' + err.message + '\n' + err.stack;
314
+ log(msg), emergencyLog(msg), app.quit();
315
+ });
316
+
317
+ app.on('will-quit', () => killServerProcess());
package/index.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { runCLI, build } from './lib/build.js';
2
+
3
+ // =================================== lib/build.js ===================================
4
+ /**
5
+ * ```js
6
+ * // 文件导出内容
7
+ * build(); // 构建桌面应用程序
8
+ * runCLI(); // 解析 process.argv 并执行对应的构建命令
9
+ * ```
10
+ * >查看定义:@see {@link build}、{@link runCLI}
11
+ */
12
+ declare module './lib/build.js' {
13
+ export * from './lib/build.js';
14
+ }
15
+
16
+ // =================================== 模块导出入口 ===================================
17
+ /**
18
+ * 模块 主要功能:
19
+ * ```js
20
+ * build(); // 构建桌面应用程序
21
+ * ```
22
+ * ---
23
+ * >查看定义:@see {@link build}
24
+ * @example
25
+ * // 基础示例
26
+ * import { build } from '@flun/desktop-builder';
27
+ *
28
+ * try {
29
+ * await build();
30
+ * } catch (err) {
31
+ * console.error('❌ 失败:', err.message);
32
+ * process.exit(1);
33
+ * }
34
+ */
35
+ declare module './index.js' {
36
+ export { build } from './lib/build.js';
37
+ }
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runCLI, build } from './lib/build.js';
3
+ if (import.meta.url === `file://${process.argv[1]}`) await runCLI();
4
+ export { build };
package/lib/app.png ADDED
Binary file