@flun/desktop-builder 1.0.3 → 1.0.4

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.
package/CHANGELOG.md CHANGED
@@ -1,13 +1,16 @@
1
1
  # 变更日志
2
2
 
3
+ ## [1.0.4] - 2026-07-05 17:17
4
+ ### 紧急修复
5
+ - **修复依赖安装失败问题**:因 `node` 包安装脚本报错(`Cannot find module 'node-win-x64/package.json'`)导致安装中断,现通过 `--ignore-scripts` 和 `npm_config_ignore_scripts=true` 强制跳过脚本执行,确保依赖安装顺利完成。
6
+
7
+ ### 优化
8
+ - **优化依赖清理逻辑**:因 Electron 已内置 Node.js 运行时,无需在应用内额外保留 `node` 包及其平台特定二进制目录(如 `node-win-x64`、`node-darwin-x64` 等)。现主动清理这些冗余目录,减小应用体积,同时避免与 Electron 内置运行时产生潜在冲突。注意:此优化不影响 `bcrypt` 等原生模块正常工作,因为所需依赖(如 `node-gyp-build`、`node-addon-api` 等)仍被保留。
9
+
3
10
  ## [1.0.3] - 2026-07-04 21:32
4
11
  ### 修复
5
12
  - 修复 Windows 环境下 `npx desktop-builder build` 命令无任何输出的问题(入口判断逻辑改用 `pathToFileURL` 统一路径格式)。
6
13
 
7
14
  ## [1.0.2] - 2026-07-03 10:43
8
15
  ### 优化
9
- - 简化使用示例;
10
-
11
- ## [1.0.1] - 2026-07-03 10:38
12
- ### 首发
13
- - 将任意 Node.js 网站一键打包为当前桌面应用('win', 'mac', 'linux')(基于 Electron),支持高度自定义配置;
16
+ - 简化使用示例;
package/electron-main.js CHANGED
@@ -13,7 +13,6 @@ import { createRequire } from 'module';
13
13
  let mainWindow = null, serverProcess = null, windowCreationPromise = null, loadRetryCount = 0;
14
14
  const require = createRequire(import.meta.url),
15
15
  __dirname = path.dirname(fileURLToPath(import.meta.url)), execPromise = promisify(exec),
16
- // 配置注入
17
16
  CONFIG = {
18
17
  APP_URL: JSON.parse('__APP_URL__'),
19
18
  WINDOW_CONFIG: JSON.parse('__WINDOW_CONFIG__'),
@@ -23,15 +22,10 @@ const require = createRequire(import.meta.url),
23
22
  LOGGING_ENABLED: JSON.parse('__LOGGING_ENABLED__'),
24
23
  MENU_TEMPLATE: __MENU_TEMPLATE__,
25
24
  }, TARGET_URL = CONFIG.APP_URL,
26
- // 命令行开关
27
25
  switches = ['no-sandbox', 'ignore-certificate-errors', 'allow-insecure-localhost'],
28
-
29
- // --------------------- 函数声明 ---------------------
30
- // 日志(根据配置决定是否写入)
26
+ // --------------------- 工具函数 ---------------------
31
27
  writeLog = (filePath, msg) => {
32
- try {
33
- fs.appendFileSync(filePath, new Date().toISOString() + ' ' + msg + '\n');
34
- } catch (_) { }
28
+ try { fs.appendFileSync(filePath, new Date().toISOString() + ' ' + msg + '\n'); } catch (_) { }
35
29
  },
36
30
  log = msg => {
37
31
  if (!CONFIG.LOGGING_ENABLED) return;
@@ -41,7 +35,6 @@ const require = createRequire(import.meta.url),
41
35
  if (!CONFIG.LOGGING_ENABLED) return;
42
36
  writeLog(path.join(process.cwd(), 'myapp_emergency.log'), msg);
43
37
  },
44
- // 服务器管理
45
38
  killServerProcess = () => {
46
39
  if (!serverProcess) return;
47
40
  try {
@@ -50,7 +43,6 @@ const require = createRequire(import.meta.url),
50
43
  } catch (_) { }
51
44
  serverProcess = null;
52
45
  },
53
-
54
46
  focusMainWindow = () => {
55
47
  if (mainWindow && !mainWindow.isDestroyed()) {
56
48
  if (mainWindow.isMinimized()) mainWindow.restore();
@@ -59,7 +51,6 @@ const require = createRequire(import.meta.url),
59
51
  return false;
60
52
  },
61
53
  sleep = ms => new Promise(resolve => setTimeout(resolve, ms)),
62
- // 依赖安装
63
54
  ensureDependencies = async () => {
64
55
  const nodeModulesPath = path.join(__dirname, 'node_modules');
65
56
  try {
@@ -94,20 +85,40 @@ const require = createRequire(import.meta.url),
94
85
  return log('写入 package.json 失败: ' + err.message), false;
95
86
  }
96
87
  try {
97
- let cmd = 'npm install';
88
+ let cmd = 'npm install --ignore-scripts --no-optional --force --no-audit --no-fund';
98
89
  const lockPath = path.join(__dirname, 'package-lock.json');
99
90
  try {
100
- await fs.promises.access(lockPath, fs.constants.F_OK);
101
- log('找到 package-lock.json,使用 npm ci'), cmd = 'npm ci';
91
+ await fs.promises.access(lockPath, fs.constants.F_OK), log('找到 package-lock.json,使用 npm ci');
92
+ cmd = 'npm ci --ignore-scripts --no-optional --force --no-audit --no-fund';
102
93
  } catch {
103
94
  log('未找到 package-lock.json,使用 npm install');
104
95
  }
105
- const { stdout, stderr } = await execPromise(cmd, { cwd: __dirname, env: process.env, timeout: 120000 });
106
- if (stdout && stderr) log('依赖安装完成');
96
+
97
+ const env = { ...process.env, npm_config_ignore_scripts: 'true', npm_config_optional: 'false' },
98
+ { stdout, stderr } = await execPromise(cmd, { cwd: __dirname, env: env, timeout: 120000, });
99
+ log('依赖安装完成');
100
+
101
+ // 删除 node 和平台特定 node-* 目录
102
+ try {
103
+ const entries = await fs.promises.readdir(nodeModulesPath, { withFileTypes: true }), toDelete = [];
104
+ for (const entry of entries) {
105
+ if (!entry.isDirectory()) continue;
106
+ const name = entry.name;
107
+ if (name === 'node') toDelete.push(path.join(nodeModulesPath, name));
108
+ if (/^node-(win|darwin|linux|freebsd|sunos|aix)-/.test(name)) toDelete.push(path.join(nodeModulesPath, name));
109
+ }
110
+
111
+ for (const dir of toDelete) {
112
+ await fs.promises.rm(dir, { recursive: true, force: true }), log(`已删除问题目录: ${dir}`);
113
+ }
114
+ } catch (cleanErr) {
115
+ log('清理目录时出错(可忽略): ' + cleanErr.message);
116
+ }
117
+
107
118
  try {
108
119
  return await fs.promises.access(nodeModulesPath, fs.constants.F_OK), true;
109
120
  } catch {
110
- return log('安装 node_modules 失败'), false;
121
+ return log('安装后 node_modules 仍然不存在'), false;
111
122
  }
112
123
  } catch (error) {
113
124
  return log('依赖安装失败: ' + error.message), emergencyLog('依赖安装失败堆栈: ' + error.stack), false;
@@ -115,7 +126,6 @@ const require = createRequire(import.meta.url),
115
126
  }
116
127
  },
117
128
 
118
- // 端口检查
119
129
  ensurePortFree = port => {
120
130
  return new Promise((resolve) => {
121
131
  const server = net.createServer();
@@ -137,7 +147,6 @@ const require = createRequire(import.meta.url),
137
147
  });
138
148
  },
139
149
 
140
- // 等待服务器就绪
141
150
  waitForServer = (port, timeout = 30000) => {
142
151
  return new Promise((resolve) => {
143
152
  const start = Date.now(), protocol = new URL(CONFIG.APP_URL).protocol,
@@ -170,7 +179,6 @@ const require = createRequire(import.meta.url),
170
179
  }
171
180
  },
172
181
 
173
- // 创建窗口
174
182
  createWindow = async () => {
175
183
  if (mainWindow && !mainWindow.isDestroyed()) return focusMainWindow(), mainWindow;
176
184
  if (windowCreationPromise) {
@@ -183,13 +191,9 @@ const require = createRequire(import.meta.url),
183
191
  ...CONFIG.WINDOW_CONFIG,
184
192
  webPreferences: {
185
193
  ...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,
194
+ webSecurity: true, allowRunningInsecureContent: true, enableWebAuthn: true,
195
+ plugins: true, nodeIntegration: true,
196
+ sandbox: false, contextIsolation: false,
193
197
  }
194
198
  };
195
199
  mainWindow = new BrowserWindow(winConfig);
@@ -223,7 +227,6 @@ const require = createRequire(import.meta.url),
223
227
  return windowCreationPromise;
224
228
  },
225
229
 
226
- // 启动服务器
227
230
  startServer = async () => {
228
231
  if (!CONFIG.AUTO_START_SERVER) return;
229
232
  const serverPath = path.join(__dirname, CONFIG.SERVER_PATH);
@@ -250,7 +253,6 @@ const require = createRequire(import.meta.url),
250
253
  log('服务器启动失败,尝试重新加载窗口');
251
254
  },
252
255
 
253
- // 菜单设置(处理特殊标记)
254
256
  setupMenu = () => {
255
257
  const template = CONFIG.MENU_TEMPLATE;
256
258
  if (!template || template.length === 0) return;
@@ -285,7 +287,7 @@ try {
285
287
  app.commandLine.appendSwitch('vmodule', 'webauthn*=3');
286
288
  log(`已映射 ${hostname} 到 127.0.0.1`);
287
289
  } catch (_) { }
288
- // 单例锁
290
+
289
291
  if (!app.requestSingleInstanceLock()) { log('已有另一个实例在运行,退出'); app.quit(); }
290
292
  else app.on('second-instance', () => focusMainWindow());
291
293
  app.on('certificate-error', (e, wc, url, err, cert, cb) => { e.preventDefault(), cb(true); });
@@ -294,7 +296,7 @@ app.whenReady().then(async () => {
294
296
  setupMenu();
295
297
 
296
298
  const depsOk = await ensureDependencies();
297
- if (!depsOk) log('依赖安装失败,再次尝试启动服务器(可能失败)');
299
+ if (!depsOk) log('依赖安装失败,再次尝试启动服务器(可能失败)');
298
300
  await startServer(), await createWindow();
299
301
  });
300
302
 
package/lib/build.js CHANGED
@@ -11,11 +11,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)), require = create
11
11
  CACHE_DIR = path.join(os.homedir(), '.electron-builder-cache'), DEFAULT_ICON = path.join(__dirname, 'app.png'),
12
12
  DEFAULT_TARGETS = { win: ['nsis'], mac: ['dmg'], linux: ['AppImage'] },
13
13
  DEFAULT_INSTALLER_ICON = path.join(__dirname, 'setup.ico'), DEFAULT_UNINSTALLER_ICON = path.join(__dirname, 'un.ico');
14
- /**
15
- * 构建桌面应用程序
16
- * >查看定义:@see {@link build}
17
- * @returns {Promise<void>}
18
- */
14
+
19
15
  const build = async () => {
20
16
  const configPath = path.join(process.cwd(), 'desktopAppConfig.js');
21
17
  if (!(await fs.pathExists(configPath)))
@@ -37,7 +33,6 @@ const build = async () => {
37
33
  const projectRoot = path.resolve(process.cwd());
38
34
  console.log(chalk.blue('[信息] 正在复制项目文件从 ' + projectRoot + ' 到 ' + tempDir));
39
35
 
40
- // 复制文件,应用 excludeFiles
41
36
  await fs.copy(projectRoot, tempDir, {
42
37
  filter: (src) => {
43
38
  const relative = path.relative(projectRoot, src);
@@ -65,7 +60,6 @@ const build = async () => {
65
60
  dereference: true,
66
61
  });
67
62
 
68
- // 生成 main.mjs
69
63
  const mainTemplatePath = path.join(__dirname, '..', 'electron-main.js'),
70
64
  mainTemplate = await fs.readFile(mainTemplatePath, 'utf-8');
71
65
 
@@ -76,7 +70,7 @@ const build = async () => {
76
70
  return value;
77
71
  });
78
72
  }
79
- else console.log(chalk.yellow('[警告] 未配置菜单,将使用默认英文菜单;'));
73
+ else console.log(chalk.yellow('[警告] 未配置菜单,将使用默认英文菜单;'));
80
74
 
81
75
  const windowConfig = {
82
76
  width: 1200, height: 800, minWidth: 800, minHeight: 600,
@@ -96,9 +90,8 @@ const build = async () => {
96
90
  .replace('__AUTO_KILL_SERVER__', JSON.stringify(advanced.autoKillServer))
97
91
  .replace('__MENU_TEMPLATE__', menuCode)
98
92
  .replace('__LOGGING_ENABLED__', JSON.stringify(enableLogging));
99
-
100
93
  await fs.writeFile(path.join(tempDir, 'main.mjs'), mainJs);
101
- // 处理 package.json
94
+
102
95
  const origPkgPath = path.join(projectRoot, 'package.json');
103
96
  let allDeps = {}, pkgJson = {};
104
97
  if (await fs.pathExists(origPkgPath)) {
@@ -116,7 +109,6 @@ const build = async () => {
116
109
  await fs.writeJson(path.join(tempDir, 'deps.json'), allDeps, { spaces: 2 });
117
110
  console.log(chalk.blue('[信息] 成功写入 ' + Object.keys(allDeps).length + ' 个依赖包到 deps.json'));
118
111
 
119
- // 图标处理
120
112
  const handleIcon = async (userPath, defaultPath, img, label) => {
121
113
  const resolved = userPath ? path.resolve(process.cwd(), userPath) : null;
122
114
  let src = (resolved && (await fs.pathExists(resolved))) ? resolved : defaultPath;
@@ -129,7 +121,6 @@ const build = async () => {
129
121
  await handleIcon(config.branding?.installerIcon, DEFAULT_INSTALLER_ICON, 'installer.ico', '安装');
130
122
  await handleIcon(config.branding?.uninstallerIcon, DEFAULT_UNINSTALLER_ICON, 'uninstaller.ico', '卸载');
131
123
 
132
- // Electron 版本
133
124
  let electronVersion;
134
125
  try {
135
126
  const electronPkgPath = require.resolve('electron/package.json'), electronPkg = await fs.readJson(electronPkgPath);
@@ -138,7 +129,6 @@ const build = async () => {
138
129
  console.error(chalk.red('[错误] 未找到 electron 包,请先安装;')), process.exit(1);
139
130
  }
140
131
 
141
- // 平台映射
142
132
  const platformInfo = {
143
133
  win32: { platform: 'win', arg: '--win' },
144
134
  darwin: { platform: 'mac', arg: '--mac' },
@@ -146,8 +136,6 @@ const build = async () => {
146
136
  },
147
137
  info = platformInfo[process.platform] || platformInfo.win32, currentPlatform = info.platform,
148
138
  platformArg = info.arg, buildConfig = config.build || {},
149
- // ----- 构建 electron-builder 配置对象 -----
150
- // 基础默认配置
151
139
  defaultNsis = {
152
140
  oneClick: false,
153
141
  perMachine: true,
@@ -178,8 +166,7 @@ const build = async () => {
178
166
  delete userBuild.nsis, delete userBuild.dmg, delete userBuild.outputDir, delete userBuild.appId;
179
167
  Object.assign(configObj, userBuild);
180
168
 
181
- // 平台特定配置:根据当前平台补充默认 target(如果用户未提供该平台配置)
182
- const platformKey = currentPlatform; // 'win', 'mac', 'linux'
169
+ const platformKey = currentPlatform;
183
170
  if (!configObj[platformKey]) configObj[platformKey] = { target: DEFAULT_TARGETS[platformKey] };
184
171
  else if (!configObj[platformKey].target) configObj[platformKey].target = DEFAULT_TARGETS[platformKey];
185
172
 
@@ -218,7 +205,7 @@ const build = async () => {
218
205
  }
219
206
 
220
207
  if (!success) console.error(chalk.red('[错误] 构建失败,已尝试 3 次:'), lastError), process.exit(1);
221
- // 复制最终安装包,应用 excludeOutputs
208
+
222
209
  const tempDistDir = path.join(tempDir, 'dist'), userOutputDir = buildConfig.outputDir || './dist',
223
210
  targetDir = path.resolve(process.cwd(), userOutputDir);
224
211
 
@@ -247,12 +234,6 @@ const build = async () => {
247
234
  else console.warn(chalk.yellow('[警告] 未找到 dist 目录;'));
248
235
  };
249
236
 
250
- /**
251
- * 命令行入口
252
- * 解析 process.argv 并执行对应的构建命令
253
- * >查看定义:@see {@link runCLI}
254
- * @returns {Promise<void>}
255
- */
256
237
  const runCLI = async () => {
257
238
  const args = process.argv.slice(2), command = args[0];
258
239
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flun/desktop-builder",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "将任意 Node.js 网站一键打包为当前桌面应用('win', 'mac', 'linux')(基于 Electron),支持高度自定义配置;",
5
5
  "type": "module",
6
6
  "types": "index.d.ts",