pake-cli 3.2.0-beta1 → 3.2.0-beta11

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.
Files changed (41) hide show
  1. package/dist/cli.js +102 -19
  2. package/dist/dev.js +276 -72
  3. package/dist/dev.js.map +1 -1
  4. package/package.json +10 -7
  5. package/src-tauri/.cargo/config.toml +19 -4
  6. package/src-tauri/.pake/icons/githubcustomtraypng.icns +0 -0
  7. package/src-tauri/.pake/icons/githubcustomtraytest.icns +0 -0
  8. package/src-tauri/.pake/icons/githubsystemtraytest.icns +0 -0
  9. package/src-tauri/.pake/icons/testfix.icns +0 -0
  10. package/src-tauri/.pake/icons/twi.icns +0 -0
  11. package/src-tauri/.pake/icons/twitter.icns +0 -0
  12. package/src-tauri/.pake/icons/twitter1.icns +0 -0
  13. package/src-tauri/.pake/icons/twitter3.icns +0 -0
  14. package/src-tauri/.pake/icons/twitter4.icns +0 -0
  15. package/src-tauri/.pake/icons/twitter5.icns +0 -0
  16. package/src-tauri/.pake/icons/twitterapp.icns +0 -0
  17. package/src-tauri/.pake/icons/twittercustomtray.icns +0 -0
  18. package/src-tauri/.pake/icons/twitterfinal.icns +0 -0
  19. package/src-tauri/.pake/icons/twitteroptimized.icns +0 -0
  20. package/src-tauri/.pake/icons/twittertest.icns +0 -0
  21. package/src-tauri/.pake/icons/wk.icns +0 -0
  22. package/src-tauri/.pake/icons/wk1.icns +0 -0
  23. package/src-tauri/.pake/pake.json +33 -0
  24. package/src-tauri/.pake/png/githubcustomtraypng.png +0 -0
  25. package/src-tauri/.pake/png/twi_tray.png +0 -0
  26. package/src-tauri/.pake/png/twitter5_tray.png +0 -0
  27. package/src-tauri/.pake/png/twittercustomtray.png +0 -0
  28. package/src-tauri/.pake/png/twitterfinal_tray.png +0 -0
  29. package/src-tauri/.pake/png/twitteroptimized_tray.png +0 -0
  30. package/src-tauri/.pake/tauri.conf.json +24 -0
  31. package/src-tauri/.pake/tauri.linux.conf.json +16 -0
  32. package/src-tauri/.pake/tauri.macos.conf.json +15 -0
  33. package/src-tauri/.pake/tauri.windows.conf.json +15 -0
  34. package/src-tauri/gen/schemas/acl-manifests.json +1 -1
  35. package/src-tauri/gen/schemas/desktop-schema.json +0 -198
  36. package/src-tauri/gen/schemas/macOS-schema.json +0 -198
  37. package/src-tauri/pake.json +2 -1
  38. package/src-tauri/src/app/config.rs +2 -0
  39. package/src-tauri/src/app/window.rs +11 -7
  40. package/src-tauri/tauri.linux.conf.json +2 -2
  41. package/cli.js +0 -2
package/dist/dev.js CHANGED
@@ -10,7 +10,7 @@ import prompts from 'prompts';
10
10
  import ora from 'ora';
11
11
  import { fileTypeFromBuffer } from 'file-type';
12
12
  import * as psl from 'psl';
13
- import 'is-url';
13
+ import icongen from 'icon-gen';
14
14
  import { execa, execaSync } from 'execa';
15
15
  import dns from 'dns';
16
16
  import http from 'http';
@@ -35,34 +35,36 @@ const DEFAULT_PAKE_OPTIONS = {
35
35
  targets: 'deb',
36
36
  useLocalFile: false,
37
37
  systemTrayIcon: '',
38
- proxyUrl: "",
38
+ proxyUrl: '',
39
39
  debug: false,
40
40
  inject: [],
41
41
  installerLanguage: 'en-US',
42
+ hideOnClose: true,
43
+ incognito: false,
42
44
  };
43
45
  // Just for cli development
44
46
  const DEFAULT_DEV_PAKE_OPTIONS = {
45
47
  ...DEFAULT_PAKE_OPTIONS,
46
- url: 'https://weread.qq.com',
47
- name: 'WeRead',
48
+ url: 'https://weekly.tw93.fun/',
49
+ name: 'Weekly',
48
50
  hideTitleBar: true,
49
51
  };
50
52
 
51
53
  const logger = {
52
54
  info(...msg) {
53
- log.info(...msg.map(m => chalk.white(m)));
55
+ log.info(...msg.map((m) => chalk.white(m)));
54
56
  },
55
57
  debug(...msg) {
56
58
  log.debug(...msg);
57
59
  },
58
60
  error(...msg) {
59
- log.error(...msg.map(m => chalk.red(m)));
61
+ log.error(...msg.map((m) => chalk.red(m)));
60
62
  },
61
63
  warn(...msg) {
62
- log.info(...msg.map(m => chalk.yellow(m)));
64
+ log.info(...msg.map((m) => chalk.yellow(m)));
63
65
  },
64
66
  success(...msg) {
65
- log.info(...msg.map(m => chalk.green(m)));
67
+ log.info(...msg.map((m) => chalk.green(m)));
66
68
  },
67
69
  };
68
70
 
@@ -70,8 +72,7 @@ const logger = {
70
72
  const currentModulePath = fileURLToPath(import.meta.url);
71
73
  // Resolve the parent directory of the current module
72
74
  const npmDirectory = path.join(path.dirname(currentModulePath), '..');
73
- const tauriConfigDirectory = path.join(npmDirectory, 'src-tauri', '.pake')
74
- ;
75
+ const tauriConfigDirectory = path.join(npmDirectory, 'src-tauri', '.pake');
75
76
 
76
77
  const { platform: platform$2 } = process;
77
78
  const IS_MAC = platform$2 === 'darwin';
@@ -80,7 +81,11 @@ const IS_LINUX = platform$2 === 'linux';
80
81
 
81
82
  // Generates an identifier based on the given URL.
82
83
  function getIdentifier(url) {
83
- const postFixHash = crypto.createHash('md5').update(url).digest('hex').substring(0, 6);
84
+ const postFixHash = crypto
85
+ .createHash('md5')
86
+ .update(url)
87
+ .digest('hex')
88
+ .substring(0, 6);
84
89
  return `com.pake.${postFixHash}`;
85
90
  }
86
91
  async function promptText(message, initial) {
@@ -107,7 +112,70 @@ function getSpinner(text) {
107
112
  }).start();
108
113
  }
109
114
 
110
- async function handleIcon(options) {
115
+ // Extracts the domain from a given URL.
116
+ function getDomain(inputUrl) {
117
+ try {
118
+ const url = new URL(inputUrl);
119
+ // Use PSL to parse domain names.
120
+ const parsed = psl.parse(url.hostname);
121
+ // If domain is available, split it and return the SLD.
122
+ if ('domain' in parsed && parsed.domain) {
123
+ return parsed.domain.split('.')[0];
124
+ }
125
+ else {
126
+ return null;
127
+ }
128
+ }
129
+ catch (error) {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ // Helper function to convert icon to required platform format
135
+ async function convertIconFormat(inputPath, appName) {
136
+ try {
137
+ const { path: outputDir } = await dir();
138
+ const platformOutputDir = path.join(outputDir, 'converted-icons');
139
+ await fsExtra.ensureDir(platformOutputDir);
140
+ // Get the required format based on current platform
141
+ const requiredFormat = IS_WIN ? 'ico' : IS_LINUX ? 'png' : 'icns';
142
+ const outputFileName = IS_WIN
143
+ ? `${appName.toLowerCase()}_256.ico`
144
+ : IS_LINUX
145
+ ? `${appName.toLowerCase()}_512.png`
146
+ : `${appName.toLowerCase()}.icns`;
147
+ const outputPath = path.join(platformOutputDir, outputFileName);
148
+ // Convert using icon-gen
149
+ if (requiredFormat === 'icns') {
150
+ await icongen(inputPath, platformOutputDir, {
151
+ report: false,
152
+ icns: {
153
+ name: appName.toLowerCase(),
154
+ sizes: [16, 32, 64, 128, 256, 512, 1024]
155
+ }
156
+ });
157
+ }
158
+ else if (requiredFormat === 'ico') {
159
+ await icongen(inputPath, platformOutputDir, {
160
+ report: false,
161
+ ico: {
162
+ name: `${appName.toLowerCase()}_256`,
163
+ sizes: [16, 24, 32, 48, 64, 128, 256]
164
+ }
165
+ });
166
+ }
167
+ else {
168
+ // For Linux, just copy the PNG file
169
+ await fsExtra.copy(inputPath, outputPath);
170
+ }
171
+ return outputPath;
172
+ }
173
+ catch (error) {
174
+ logger.warn(`✼ Icon format conversion failed: ${error.message}`);
175
+ return null;
176
+ }
177
+ }
178
+ async function handleIcon(options, url) {
111
179
  if (options.icon) {
112
180
  if (options.icon.startsWith('http')) {
113
181
  return downloadIcon(options.icon);
@@ -117,6 +185,13 @@ async function handleIcon(options) {
117
185
  }
118
186
  }
119
187
  else {
188
+ // Try to get favicon from website if URL is provided and it's a web URL
189
+ if (url && url.startsWith('http') && options.name) {
190
+ const faviconPath = await tryGetFavicon(url, options.name);
191
+ if (faviconPath) {
192
+ return faviconPath;
193
+ }
194
+ }
120
195
  logger.warn('✼ No icon given, default in use. For a custom icon, use --icon option.');
121
196
  const iconPath = IS_WIN
122
197
  ? 'src-tauri/png/icon_256.ico'
@@ -126,16 +201,69 @@ async function handleIcon(options) {
126
201
  return path.join(npmDirectory, iconPath);
127
202
  }
128
203
  }
129
- async function downloadIcon(iconUrl) {
130
- const spinner = getSpinner('Downloading icon...');
204
+ async function tryGetFavicon(url, appName) {
205
+ const domain = getDomain(url);
206
+ if (!domain) {
207
+ return null;
208
+ }
209
+ const spinner = getSpinner('Fetching website favicon...');
210
+ // List of logo/favicon services to try in order of preference
211
+ const logoServices = [
212
+ // Professional Logo APIs (free tier, higher quality)
213
+ `https://logo.clearbit.com/${domain}?size=256`,
214
+ `https://logo.uplead.com/${domain}`,
215
+ // Google's reliable service with larger size
216
+ `https://www.google.com/s2/favicons?domain=${domain}&sz=256`,
217
+ // Other favicon services
218
+ `https://favicon.is/${domain}`,
219
+ `https://icons.duckduckgo.com/ip3/${domain}.ico`,
220
+ `https://icon.horse/icon/${domain}`,
221
+ // Direct favicon checks
222
+ `https://${domain}/favicon.ico`,
223
+ `https://www.${domain}/favicon.ico`,
224
+ `https://${domain}/apple-touch-icon.png`,
225
+ `https://${domain}/apple-touch-icon-precomposed.png`,
226
+ ];
227
+ for (const logoUrl of logoServices) {
228
+ try {
229
+ const faviconPath = await downloadIcon(logoUrl, false);
230
+ if (faviconPath) {
231
+ spinner.text = 'Converting favicon to platform format...';
232
+ // Convert to the correct format for the current platform
233
+ const convertedPath = await convertIconFormat(faviconPath, appName);
234
+ if (convertedPath) {
235
+ spinner.succeed(chalk.green(`Favicon downloaded and converted for ${domain}!`));
236
+ return convertedPath;
237
+ }
238
+ else {
239
+ // If conversion fails, try the next service
240
+ continue;
241
+ }
242
+ }
243
+ }
244
+ catch (error) {
245
+ // Continue to next service
246
+ continue;
247
+ }
248
+ }
249
+ spinner.fail(chalk.yellow(`No favicon found for ${domain}, using default icon.`));
250
+ return null;
251
+ }
252
+ async function downloadIcon(iconUrl, showSpinner = true) {
253
+ const spinner = showSpinner ? getSpinner('Downloading icon...') : null;
131
254
  try {
132
- const iconResponse = await axios.get(iconUrl, { responseType: 'arraybuffer' });
255
+ const iconResponse = await axios.get(iconUrl, {
256
+ responseType: 'arraybuffer',
257
+ timeout: 10000, // 10 second timeout
258
+ });
133
259
  const iconData = await iconResponse.data;
134
- if (!iconData) {
260
+ if (!iconData || iconData.byteLength < 100) {
261
+ // Skip very small responses (likely error pages)
135
262
  return null;
136
263
  }
137
264
  const fileDetails = await fileTypeFromBuffer(iconData);
138
- if (!fileDetails) {
265
+ if (!fileDetails || !['png', 'ico', 'jpeg', 'jpg', 'gif', 'webp'].includes(fileDetails.ext)) {
266
+ // Only accept common image formats
139
267
  return null;
140
268
  }
141
269
  const { path: tempPath } = await dir();
@@ -149,33 +277,21 @@ async function downloadIcon(iconUrl) {
149
277
  await fsExtra.outputFile(iconPath, iconData);
150
278
  }
151
279
  await fsExtra.outputFile(iconPath, iconData);
152
- spinner.succeed(chalk.green('Icon downloaded successfully!'));
280
+ if (spinner) {
281
+ spinner.succeed(chalk.green('Icon downloaded successfully!'));
282
+ }
153
283
  return iconPath;
154
284
  }
155
285
  catch (error) {
156
- spinner.fail(chalk.red('Icon download failed!'));
286
+ if (spinner) {
287
+ spinner.fail(chalk.red('Icon download failed!'));
288
+ }
157
289
  if (error.response && error.response.status === 404) {
158
290
  return null;
159
291
  }
160
- throw error;
161
- }
162
- }
163
-
164
- // Extracts the domain from a given URL.
165
- function getDomain(inputUrl) {
166
- try {
167
- const url = new URL(inputUrl);
168
- // Use PSL to parse domain names.
169
- const parsed = psl.parse(url.hostname);
170
- // If domain is available, split it and return the SLD.
171
- if ('domain' in parsed && parsed.domain) {
172
- return parsed.domain.split('.')[0];
173
- }
174
- else {
175
- return null;
292
+ if (showSpinner) {
293
+ throw error;
176
294
  }
177
- }
178
- catch (error) {
179
295
  return null;
180
296
  }
181
297
  }
@@ -186,8 +302,8 @@ function resolveAppName(name, platform) {
186
302
  }
187
303
  function isValidName(name, platform) {
188
304
  const platformRegexMapping = {
189
- linux: /^[a-z0-9]+(-[a-z0-9]+)*$/,
190
- default: /^[a-zA-Z0-9]+([-a-zA-Z0-9])*$/,
305
+ linux: /^[a-z0-9][a-z0-9-]*$/,
306
+ default: /^[a-zA-Z0-9][a-zA-Z0-9- ]*$/,
191
307
  };
192
308
  const reg = platformRegexMapping[platform] || platformRegexMapping.default;
193
309
  return !!name && reg.test(name);
@@ -203,9 +319,14 @@ async function handleOptions(options, url) {
203
319
  const namePrompt = await promptText(promptMessage, defaultName);
204
320
  name = namePrompt || defaultName;
205
321
  }
322
+ // Handle platform-specific name formatting
323
+ if (name && platform === 'linux') {
324
+ // Convert to lowercase and replace spaces with dashes for Linux
325
+ name = name.toLowerCase().replace(/\s+/g, '-');
326
+ }
206
327
  if (!isValidName(name, platform)) {
207
- const LINUX_NAME_ERROR = `✕ name should only include lowercase letters, numbers, and dashes, and must contain at least one lowercase letter. Examples: com-123-xxx, 123pan, pan123, weread, we-read.`;
208
- const DEFAULT_NAME_ERROR = `✕ Name should only include letters and numbers, and dashes (dashes must not at the beginning), and must contain at least one letter. Examples: 123pan, 123Pan, Pan123, weread, WeRead, WERead, we-read.`;
328
+ const LINUX_NAME_ERROR = `✕ Name should only include lowercase letters, numbers, and dashes (not leading dashes). Examples: com-123-xxx, 123pan, pan123, weread, we-read, 123.`;
329
+ const DEFAULT_NAME_ERROR = `✕ Name should only include letters, numbers, dashes, and spaces (not leading dashes and spaces). Examples: 123pan, 123Pan, Pan123, weread, WeRead, WERead, we-read, We Read, 123.`;
209
330
  const errorMsg = platform === 'linux' ? LINUX_NAME_ERROR : DEFAULT_NAME_ERROR;
210
331
  logger.error(errorMsg);
211
332
  if (isActions) {
@@ -221,13 +342,13 @@ async function handleOptions(options, url) {
221
342
  name,
222
343
  identifier: getIdentifier(url),
223
344
  };
224
- appOptions.icon = await handleIcon(appOptions);
345
+ appOptions.icon = await handleIcon(appOptions, url);
225
346
  return appOptions;
226
347
  }
227
348
 
228
349
  var windows = [
229
350
  {
230
- url: "https://weread.qq.com",
351
+ url: "https://weekly.tw93.fun/",
231
352
  url_type: "web",
232
353
  hide_title_bar: true,
233
354
  fullscreen: false,
@@ -237,7 +358,9 @@ var windows = [
237
358
  always_on_top: false,
238
359
  dark_mode: false,
239
360
  activation_shortcut: "",
240
- disabled_web_shortcuts: false
361
+ disabled_web_shortcuts: false,
362
+ hide_on_close: true,
363
+ incognito: false
241
364
  }
242
365
  ];
243
366
  var user_agent = {
@@ -263,13 +386,13 @@ var pakeConf = {
263
386
  proxy_url: proxy_url
264
387
  };
265
388
 
266
- var productName$1 = "WeRead";
267
- var identifier = "com.pake.weread";
389
+ var productName$1 = "Weekly";
390
+ var identifier = "com.pake.weekly";
268
391
  var version = "1.0.0";
269
392
  var app = {
270
393
  withGlobalTauri: true,
271
394
  trayIcon: {
272
- iconPath: "png/weread_512.png",
395
+ iconPath: "png/weekly_512.png",
273
396
  iconAsTemplate: false,
274
397
  id: "pake-tray"
275
398
  }
@@ -287,12 +410,12 @@ var CommonConf = {
287
410
 
288
411
  var bundle$2 = {
289
412
  icon: [
290
- "png/weread_256.ico",
291
- "png/weread_32.ico"
413
+ "png/weekly_256.ico",
414
+ "png/weekly_32.ico"
292
415
  ],
293
416
  active: true,
294
417
  resources: [
295
- "png/weread_32.ico"
418
+ "png/weekly_32.ico"
296
419
  ],
297
420
  targets: [
298
421
  "msi"
@@ -313,7 +436,7 @@ var WinConf = {
313
436
 
314
437
  var bundle$1 = {
315
438
  icon: [
316
- "icons/weread.icns"
439
+ "icons/weekly.icns"
317
440
  ],
318
441
  active: true,
319
442
  macOS: {
@@ -326,10 +449,10 @@ var MacConf = {
326
449
  bundle: bundle$1
327
450
  };
328
451
 
329
- var productName = "we-read";
452
+ var productName = "weekly";
330
453
  var bundle = {
331
454
  icon: [
332
- "png/weread_512.png"
455
+ "png/weekly_512.png"
333
456
  ],
334
457
  active: true,
335
458
  linux: {
@@ -339,7 +462,7 @@ var bundle = {
339
462
  "wget"
340
463
  ],
341
464
  files: {
342
- "/usr/share/applications/com-pake-weread.desktop": "assets/com-pake-weread.desktop"
465
+ "/usr/share/applications/com-pake-weekly.desktop": "assets/com-pake-weekly.desktop"
343
466
  }
344
467
  }
345
468
  },
@@ -374,16 +497,23 @@ let tauriConfig = {
374
497
  pake: pakeConf,
375
498
  };
376
499
 
377
- async function shellExec(command) {
500
+ async function shellExec(command, timeout = 300000) {
378
501
  try {
379
502
  const { exitCode } = await execa(command, {
380
503
  cwd: npmDirectory,
381
- stdio: 'inherit'
504
+ stdio: 'inherit',
505
+ shell: true,
506
+ timeout,
382
507
  });
383
508
  return exitCode;
384
509
  }
385
510
  catch (error) {
386
- throw new Error(`Error occurred while executing command "${command}". Exit code: ${error.exitCode}`);
511
+ const exitCode = error.exitCode ?? 'unknown';
512
+ const errorMessage = error.message || 'Unknown error occurred';
513
+ if (error.timedOut) {
514
+ throw new Error(`Command timed out after ${timeout}ms: "${command}". Try increasing timeout or check network connectivity.`);
515
+ }
516
+ throw new Error(`Error occurred while executing command "${command}". Exit code: ${exitCode}. Details: ${errorMessage}`);
387
517
  }
388
518
  }
389
519
 
@@ -394,12 +524,12 @@ const ping = async (host) => {
394
524
  const start = new Date();
395
525
  // Prevent timeouts from affecting user experience.
396
526
  const requestPromise = new Promise((resolve, reject) => {
397
- const req = http.get(`http://${ip.address}`, res => {
527
+ const req = http.get(`http://${ip.address}`, (res) => {
398
528
  const delay = new Date().getTime() - start.getTime();
399
529
  res.resume();
400
530
  resolve(delay);
401
531
  });
402
- req.on('error', err => {
532
+ req.on('error', (err) => {
403
533
  reject(err);
404
534
  });
405
535
  });
@@ -461,21 +591,42 @@ function checkRustInstalled() {
461
591
  }
462
592
 
463
593
  async function combineFiles(files, output) {
464
- const contents = files.map(file => {
594
+ const contents = files.map((file) => {
465
595
  const fileContent = fs.readFileSync(file);
466
596
  if (file.endsWith('.css')) {
467
597
  return ("window.addEventListener('DOMContentLoaded', (_event) => { const css = `" +
468
598
  fileContent +
469
599
  "`; const style = document.createElement('style'); style.innerHTML = css; document.head.appendChild(style); });");
470
600
  }
471
- return "window.addEventListener('DOMContentLoaded', (_event) => { " + fileContent + ' });';
601
+ return ("window.addEventListener('DOMContentLoaded', (_event) => { " +
602
+ fileContent +
603
+ ' });');
472
604
  });
473
605
  fs.writeFileSync(output, contents.join('\n'));
474
606
  return files;
475
607
  }
476
608
 
477
609
  async function mergeConfig(url, options, tauriConf) {
478
- const { width, height, fullscreen, hideTitleBar, alwaysOnTop, appVersion, darkMode, disabledWebShortcuts, activationShortcut, userAgent, showSystemTray, systemTrayIcon, useLocalFile, identifier, name, resizable = true, inject, proxyUrl, installerLanguage, } = options;
610
+ // Ensure .pake directory exists and copy source templates if needed
611
+ const srcTauriDir = path.join(npmDirectory, 'src-tauri');
612
+ await fsExtra.ensureDir(tauriConfigDirectory);
613
+ // Copy source config files to .pake directory (as templates)
614
+ const sourceFiles = [
615
+ 'tauri.conf.json',
616
+ 'tauri.macos.conf.json',
617
+ 'tauri.windows.conf.json',
618
+ 'tauri.linux.conf.json',
619
+ 'pake.json',
620
+ ];
621
+ await Promise.all(sourceFiles.map(async (file) => {
622
+ const sourcePath = path.join(srcTauriDir, file);
623
+ const destPath = path.join(tauriConfigDirectory, file);
624
+ if ((await fsExtra.pathExists(sourcePath)) &&
625
+ !(await fsExtra.pathExists(destPath))) {
626
+ await fsExtra.copy(sourcePath, destPath);
627
+ }
628
+ }));
629
+ const { width, height, fullscreen, hideTitleBar, alwaysOnTop, appVersion, darkMode, disabledWebShortcuts, activationShortcut, userAgent, showSystemTray, systemTrayIcon, useLocalFile, identifier, name, resizable = true, inject, proxyUrl, installerLanguage, hideOnClose, incognito, title, } = options;
479
630
  const { platform } = process;
480
631
  // Set Windows parameters.
481
632
  const tauriConfWindowOptions = {
@@ -488,6 +639,9 @@ async function mergeConfig(url, options, tauriConf) {
488
639
  always_on_top: alwaysOnTop,
489
640
  dark_mode: darkMode,
490
641
  disabled_web_shortcuts: disabledWebShortcuts,
642
+ hide_on_close: hideOnClose,
643
+ incognito: incognito,
644
+ title: title || null,
491
645
  };
492
646
  Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions });
493
647
  tauriConf.productName = name;
@@ -515,7 +669,7 @@ async function mergeConfig(url, options, tauriConf) {
515
669
  // ignore it, because about_pake.html have be erased.
516
670
  // const filesToCopyBack = ['cli.js', 'about_pake.html'];
517
671
  const filesToCopyBack = ['cli.js'];
518
- await Promise.all(filesToCopyBack.map(file => fsExtra.copy(path.join(distBakDir, file), path.join(distDir, file))));
672
+ await Promise.all(filesToCopyBack.map((file) => fsExtra.copy(path.join(distBakDir, file), path.join(distDir, file))));
519
673
  }
520
674
  tauriConf.pake.windows[0].url = fileName;
521
675
  tauriConf.pake.windows[0].url_type = 'local';
@@ -619,11 +773,11 @@ async function mergeConfig(url, options, tauriConf) {
619
773
  const injectFilePath = path.join(npmDirectory, `src-tauri/src/inject/custom.js`);
620
774
  // inject js or css files
621
775
  if (inject?.length > 0) {
622
- if (!inject.every(item => item.endsWith('.css') || item.endsWith('.js'))) {
776
+ if (!inject.every((item) => item.endsWith('.css') || item.endsWith('.js'))) {
623
777
  logger.error('The injected file must be in either CSS or JS format.');
624
778
  return;
625
779
  }
626
- const files = inject.map(filepath => (path.isAbsolute(filepath) ? filepath : path.join(process.cwd(), filepath)));
780
+ const files = inject.map((filepath) => path.isAbsolute(filepath) ? filepath : path.join(process.cwd(), filepath));
627
781
  tauriConf.pake.inject = files;
628
782
  await combineFiles(files, injectFilePath);
629
783
  }
@@ -685,14 +839,21 @@ class BaseBuilder {
685
839
  const rustProjectDir = path.join(tauriSrcPath, '.cargo');
686
840
  const projectConf = path.join(rustProjectDir, 'config.toml');
687
841
  await fsExtra.ensureDir(rustProjectDir);
842
+ // For global CLI installation, always use npm
843
+ const packageManager = 'npm';
844
+ const registryOption = isChina
845
+ ? ' --registry=https://registry.npmmirror.com'
846
+ : '';
847
+ // Windows环境下需要更长的超时时间
848
+ const timeout = process.platform === 'win32' ? 600000 : 300000;
688
849
  if (isChina) {
689
850
  logger.info('✺ Located in China, using npm/rsProxy CN mirror.');
690
851
  const projectCnConf = path.join(tauriSrcPath, 'rust_proxy.toml');
691
852
  await fsExtra.copy(projectCnConf, projectConf);
692
- await shellExec(`cd "${npmDirectory}" && npm install --registry=https://registry.npmmirror.com`);
853
+ await shellExec(`cd "${npmDirectory}" && ${packageManager} install${registryOption}`, timeout);
693
854
  }
694
855
  else {
695
- await shellExec(`cd "${npmDirectory}" && npm install`);
856
+ await shellExec(`cd "${npmDirectory}" && ${packageManager} install`, timeout);
696
857
  }
697
858
  spinner.succeed(chalk.green('Package installed!'));
698
859
  if (!tauriTargetPathExists) {
@@ -726,25 +887,66 @@ class BaseBuilder {
726
887
  return target;
727
888
  }
728
889
  getBuildCommand() {
729
- // the debug option should support `--debug` and `--release`
730
- return this.options.debug ? 'npm run build:debug' : 'npm run build';
890
+ const baseCommand = this.options.debug
891
+ ? 'npm run build:debug'
892
+ : 'npm run build';
893
+ // Use temporary config directory to avoid modifying source files
894
+ const configPath = path.join(npmDirectory, 'src-tauri', '.pake', 'tauri.conf.json');
895
+ let fullCommand = `${baseCommand} -- -c "${configPath}"`;
896
+ // For macOS, use app bundles by default unless DMG is explicitly requested
897
+ if (IS_MAC && this.options.targets === 'app') {
898
+ fullCommand += ' --bundles app';
899
+ }
900
+ // Add macos-proxy feature for modern macOS (Darwin 23+ = macOS 14+)
901
+ if (IS_MAC) {
902
+ const macOSVersion = this.getMacOSMajorVersion();
903
+ if (macOSVersion >= 23) {
904
+ fullCommand += ' --features macos-proxy';
905
+ }
906
+ }
907
+ return fullCommand;
908
+ }
909
+ getMacOSMajorVersion() {
910
+ try {
911
+ const os = require('os');
912
+ const release = os.release();
913
+ const majorVersion = parseInt(release.split('.')[0], 10);
914
+ return majorVersion;
915
+ }
916
+ catch (error) {
917
+ return 0; // Disable proxy feature if version detection fails
918
+ }
731
919
  }
732
920
  getBasePath() {
733
921
  const basePath = this.options.debug ? 'debug' : 'release';
734
922
  return `src-tauri/target/${basePath}/bundle/`;
735
923
  }
736
924
  getBuildAppPath(npmDirectory, fileName, fileType) {
737
- return path.join(npmDirectory, this.getBasePath(), fileType.toLowerCase(), `${fileName}.${fileType}`);
925
+ // For app bundles on macOS, the directory is 'macos', not 'app'
926
+ const bundleDir = fileType.toLowerCase() === 'app' ? 'macos' : fileType.toLowerCase();
927
+ return path.join(npmDirectory, this.getBasePath(), bundleDir, `${fileName}.${fileType}`);
738
928
  }
739
929
  }
740
930
 
741
931
  class MacBuilder extends BaseBuilder {
742
932
  constructor(options) {
743
933
  super(options);
744
- this.options.targets = 'dmg';
934
+ // Use DMG by default for distribution
935
+ // Only create app bundles for testing to avoid user interaction
936
+ if (process.env.PAKE_CREATE_APP === '1') {
937
+ this.options.targets = 'app';
938
+ }
939
+ else {
940
+ this.options.targets = 'dmg';
941
+ }
745
942
  }
746
943
  getFileName() {
747
944
  const { name } = this.options;
945
+ // For app bundles, use simple name without version/arch
946
+ if (this.options.targets === 'app') {
947
+ return name;
948
+ }
949
+ // For DMG files, use versioned filename
748
950
  let arch;
749
951
  if (this.options.multiArch) {
750
952
  arch = 'universal';
@@ -755,7 +957,9 @@ class MacBuilder extends BaseBuilder {
755
957
  return `${name}_${tauriConfig.version}_${arch}`;
756
958
  }
757
959
  getBuildCommand() {
758
- return this.options.multiArch ? 'npm run build:mac' : super.getBuildCommand();
960
+ return this.options.multiArch
961
+ ? 'npm run build:mac'
962
+ : super.getBuildCommand();
759
963
  }
760
964
  getBasePath() {
761
965
  return this.options.multiArch