pake-cli 3.3.7-beta โ†’ 3.4.0

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/README.md CHANGED
@@ -29,6 +29,7 @@
29
29
  - **Beginners**: Download ready-made [Popular Packages](#popular-packages) or use [Online Building](docs/github-actions-usage.md) with no environment setup required
30
30
  - **Developers**: Install [CLI Tool](docs/cli-usage.md) for one-command packaging of any website with customizable icons, window settings, and more
31
31
  - **Advanced Users**: Clone the project locally for [Custom Development](#development), or check [Advanced Usage](docs/advanced-usage.md) for style customization and feature enhancement
32
+ - **Troubleshooting**: Check [FAQ](docs/faq.md) for common issues and solutions
32
33
 
33
34
  ## Popular Packages
34
35
 
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import path from 'path';
6
6
  import fsExtra from 'fs-extra';
7
7
  import { fileURLToPath } from 'url';
8
8
  import prompts from 'prompts';
9
+ import os from 'os';
9
10
  import { execa, execaSync } from 'execa';
10
11
  import crypto from 'crypto';
11
12
  import ora from 'ora';
@@ -22,7 +23,7 @@ import sharp from 'sharp';
22
23
  import * as psl from 'psl';
23
24
 
24
25
  var name = "pake-cli";
25
- var version = "3.3.7-beta";
26
+ var version = "3.4.0";
26
27
  var description = "๐Ÿคฑ๐Ÿป Turn any webpage into a desktop app with one command. ๐Ÿคฑ๐Ÿป ไธ€้”ฎๆ‰“ๅŒ…็ฝ‘้กต็”Ÿๆˆ่ฝป้‡ๆกŒ้ขๅบ”็”จใ€‚";
27
28
  var engines = {
28
29
  node: ">=18.0.0"
@@ -74,12 +75,12 @@ var license = "MIT";
74
75
  var dependencies = {
75
76
  "@tauri-apps/api": "^2.8.0",
76
77
  "@tauri-apps/cli": "^2.8.4",
77
- axios: "^1.11.0",
78
- chalk: "^5.6.0",
78
+ axios: "^1.12.2",
79
+ chalk: "^5.6.2",
79
80
  commander: "^12.1.0",
80
81
  execa: "^9.6.0",
81
82
  "file-type": "^18.7.0",
82
- "fs-extra": "^11.3.1",
83
+ "fs-extra": "^11.3.2",
83
84
  "icon-gen": "^5.0.0",
84
85
  loglevel: "^1.9.2",
85
86
  ora: "^8.2.0",
@@ -96,7 +97,7 @@ var devDependencies = {
96
97
  "@rollup/plugin-replace": "^6.0.2",
97
98
  "@rollup/plugin-terser": "^0.4.4",
98
99
  "@types/fs-extra": "^11.0.4",
99
- "@types/node": "^20.19.13",
100
+ "@types/node": "^20.19.21",
100
101
  "@types/page-icon": "^0.3.6",
101
102
  "@types/prompts": "^2.4.9",
102
103
  "@types/tmp": "^0.2.6",
@@ -104,10 +105,10 @@ var devDependencies = {
104
105
  "app-root-path": "^3.1.0",
105
106
  "cross-env": "^7.0.3",
106
107
  prettier: "^3.6.2",
107
- rollup: "^4.50.0",
108
+ rollup: "^4.52.4",
108
109
  "rollup-plugin-typescript2": "^0.36.0",
109
110
  tslib: "^2.8.1",
110
- typescript: "^5.9.2"
111
+ typescript: "^5.9.3"
111
112
  };
112
113
  var packageJson = {
113
114
  name: name,
@@ -200,11 +201,11 @@ const IS_MAC = platform$1 === 'darwin';
200
201
  const IS_WIN = platform$1 === 'win32';
201
202
  const IS_LINUX = platform$1 === 'linux';
202
203
 
203
- async function shellExec(command, timeout = 300000, env) {
204
+ async function shellExec(command, timeout = 300000, env, showOutput = false) {
204
205
  try {
205
206
  const { exitCode } = await execa(command, {
206
207
  cwd: npmDirectory,
207
- stdio: ['inherit', 'pipe', 'inherit'], // Hide stdout verbose, keep stderr
208
+ stdio: showOutput ? 'inherit' : ['inherit', 'pipe', 'inherit'],
208
209
  shell: true,
209
210
  timeout,
210
211
  env: env ? { ...process.env, ...env } : process.env,
@@ -217,7 +218,18 @@ async function shellExec(command, timeout = 300000, env) {
217
218
  if (error.timedOut) {
218
219
  throw new Error(`Command timed out after ${timeout}ms: "${command}". Try increasing timeout or check network connectivity.`);
219
220
  }
220
- throw new Error(`Error occurred while executing command "${command}". Exit code: ${exitCode}. Details: ${errorMessage}`);
221
+ let errorMsg = `Error occurred while executing command "${command}". Exit code: ${exitCode}. Details: ${errorMessage}`;
222
+ if (process.platform === 'linux' &&
223
+ (errorMessage.includes('linuxdeploy') ||
224
+ errorMessage.includes('appimage') ||
225
+ errorMessage.includes('strip'))) {
226
+ errorMsg +=
227
+ '\n\nLinux AppImage build error. Try one of these solutions:\n' +
228
+ ' 1. Run with: NO_STRIP=true pake <url> --targets appimage\n' +
229
+ ' 2. Use DEB format instead: pake <url> --targets deb\n' +
230
+ ' 3. See detailed solutions: https://github.com/tw93/Pake/blob/main/docs/faq.md';
231
+ }
232
+ throw new Error(errorMsg);
221
233
  }
222
234
  }
223
235
 
@@ -284,6 +296,52 @@ async function isChinaIP(ip, domain) {
284
296
  }
285
297
  }
286
298
 
299
+ function normalizePathForComparison(targetPath) {
300
+ const normalized = path.normalize(targetPath);
301
+ return IS_WIN ? normalized.toLowerCase() : normalized;
302
+ }
303
+ function getCargoHomeCandidates() {
304
+ const candidates = new Set();
305
+ if (process.env.CARGO_HOME) {
306
+ candidates.add(process.env.CARGO_HOME);
307
+ }
308
+ const homeDir = os.homedir();
309
+ if (homeDir) {
310
+ candidates.add(path.join(homeDir, '.cargo'));
311
+ }
312
+ if (IS_WIN && process.env.USERPROFILE) {
313
+ candidates.add(path.join(process.env.USERPROFILE, '.cargo'));
314
+ }
315
+ return Array.from(candidates).filter(Boolean);
316
+ }
317
+ function ensureCargoBinOnPath() {
318
+ const currentPath = process.env.PATH || '';
319
+ const segments = currentPath.split(path.delimiter).filter(Boolean);
320
+ const normalizedSegments = new Set(segments.map((segment) => normalizePathForComparison(segment)));
321
+ const additions = [];
322
+ let cargoHomeSet = Boolean(process.env.CARGO_HOME);
323
+ for (const cargoHome of getCargoHomeCandidates()) {
324
+ const binDir = path.join(cargoHome, 'bin');
325
+ if (fsExtra.pathExistsSync(binDir) &&
326
+ !normalizedSegments.has(normalizePathForComparison(binDir))) {
327
+ additions.push(binDir);
328
+ normalizedSegments.add(normalizePathForComparison(binDir));
329
+ }
330
+ if (!cargoHomeSet && fsExtra.pathExistsSync(cargoHome)) {
331
+ process.env.CARGO_HOME = cargoHome;
332
+ cargoHomeSet = true;
333
+ }
334
+ }
335
+ if (additions.length) {
336
+ const prefix = additions.join(path.delimiter);
337
+ process.env.PATH = segments.length
338
+ ? `${prefix}${path.delimiter}${segments.join(path.delimiter)}`
339
+ : prefix;
340
+ }
341
+ }
342
+ function ensureRustEnv() {
343
+ ensureCargoBinOnPath();
344
+ }
287
345
  async function installRust() {
288
346
  const isActions = process.env.GITHUB_ACTIONS;
289
347
  const isInChina = await isChinaDomain('sh.rustup.rs');
@@ -293,8 +351,9 @@ async function installRust() {
293
351
  const rustInstallScriptForWindows = 'winget install --id Rustlang.Rustup';
294
352
  const spinner = getSpinner('Downloading Rust...');
295
353
  try {
296
- await shellExec(IS_WIN ? rustInstallScriptForWindows : rustInstallScriptForMac);
354
+ await shellExec(IS_WIN ? rustInstallScriptForWindows : rustInstallScriptForMac, 300000, undefined, true);
297
355
  spinner.succeed(chalk.green('โœ” Rust installed successfully!'));
356
+ ensureRustEnv();
298
357
  }
299
358
  catch (error) {
300
359
  spinner.fail(chalk.red('โœ• Rust installation failed!'));
@@ -303,6 +362,7 @@ async function installRust() {
303
362
  }
304
363
  }
305
364
  function checkRustInstalled() {
365
+ ensureCargoBinOnPath();
306
366
  try {
307
367
  execaSync('rustc', ['--version']);
308
368
  return true;
@@ -343,14 +403,14 @@ function generateLinuxPackageName(name) {
343
403
  .replace(/-+/g, '-');
344
404
  }
345
405
  function generateIdentifierSafeName(name) {
346
- const cleaned = name
347
- .replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, '')
348
- .toLowerCase();
406
+ const cleaned = name.replace(/[^a-zA-Z0-9\u4e00-\u9fff]/g, '').toLowerCase();
349
407
  if (cleaned === '') {
350
408
  const fallback = Array.from(name)
351
- .map(char => {
409
+ .map((char) => {
352
410
  const code = char.charCodeAt(0);
353
- if ((code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122)) {
411
+ if ((code >= 48 && code <= 57) ||
412
+ (code >= 65 && code <= 90) ||
413
+ (code >= 97 && code <= 122)) {
354
414
  return char.toLowerCase();
355
415
  }
356
416
  return code.toString(16);
@@ -362,6 +422,12 @@ function generateIdentifierSafeName(name) {
362
422
  return cleaned;
363
423
  }
364
424
 
425
+ /**
426
+ * Helper function to generate safe lowercase app name for file paths
427
+ */
428
+ function getSafeAppName(name) {
429
+ return generateSafeFilename(name).toLowerCase();
430
+ }
365
431
  async function mergeConfig(url, options, tauriConf) {
366
432
  // Ensure .pake directory exists and copy source templates if needed
367
433
  const srcTauriDir = path.join(npmDirectory, 'src-tauri');
@@ -382,7 +448,7 @@ async function mergeConfig(url, options, tauriConf) {
382
448
  await fsExtra.copy(sourcePath, destPath);
383
449
  }
384
450
  }));
385
- const { width, height, fullscreen, hideTitleBar, alwaysOnTop, appVersion, darkMode, disabledWebShortcuts, activationShortcut, userAgent, showSystemTray, systemTrayIcon, useLocalFile, identifier, name, resizable = true, inject, proxyUrl, installerLanguage, hideOnClose, incognito, title, wasm, enableDragDrop, } = options;
451
+ const { width, height, fullscreen, hideTitleBar, alwaysOnTop, appVersion, darkMode, disabledWebShortcuts, activationShortcut, userAgent, showSystemTray, systemTrayIcon, useLocalFile, identifier, name, resizable = true, inject, proxyUrl, installerLanguage, hideOnClose, incognito, title, wasm, enableDragDrop, multiInstance, } = options;
386
452
  const { platform } = process;
387
453
  const platformHideOnClose = hideOnClose ?? platform === 'darwin';
388
454
  const tauriConfWindowOptions = {
@@ -452,7 +518,7 @@ async function mergeConfig(url, options, tauriConf) {
452
518
  // Remove hardcoded desktop files and regenerate with correct app name
453
519
  delete tauriConf.bundle.linux.deb.files;
454
520
  // Generate correct desktop file configuration
455
- const appNameSafe = generateSafeFilename(name).toLowerCase();
521
+ const appNameSafe = getSafeAppName(name);
456
522
  const identifier = `com.pake.${appNameSafe}`;
457
523
  const desktopFileName = `${identifier}.desktop`;
458
524
  // Create desktop file content
@@ -503,22 +569,23 @@ StartupNotify=true
503
569
  }
504
570
  }
505
571
  // Set icon.
572
+ const safeAppName = getSafeAppName(name);
506
573
  const platformIconMap = {
507
574
  win32: {
508
575
  fileExt: '.ico',
509
- path: `png/${generateSafeFilename(name).toLowerCase()}_256.ico`,
576
+ path: `png/${safeAppName}_256.ico`,
510
577
  defaultIcon: 'png/icon_256.ico',
511
578
  message: 'Windows icon must be .ico and 256x256px.',
512
579
  },
513
580
  linux: {
514
581
  fileExt: '.png',
515
- path: `png/${generateSafeFilename(name).toLowerCase()}_512.png`,
582
+ path: `png/${safeAppName}_512.png`,
516
583
  defaultIcon: 'png/icon_512.png',
517
584
  message: 'Linux icon must be .png and 512x512px.',
518
585
  },
519
586
  darwin: {
520
587
  fileExt: '.icns',
521
- path: `icons/${generateSafeFilename(name).toLowerCase()}.icns`,
588
+ path: `icons/${safeAppName}.icns`,
522
589
  defaultIcon: 'icons/icon.icns',
523
590
  message: 'macOS icon must be .icns type.',
524
591
  },
@@ -562,8 +629,8 @@ StartupNotify=true
562
629
  // ้œ€่ฆๅˆคๆ–ญๅ›พๆ ‡ๆ ผๅผ๏ผŒ้ป˜่ฎคๅชๆ”ฏๆŒicoๅ’Œpngไธค็ง
563
630
  let iconExt = path.extname(systemTrayIcon).toLowerCase();
564
631
  if (iconExt == '.png' || iconExt == '.ico') {
565
- const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${generateSafeFilename(name).toLowerCase()}${iconExt}`);
566
- trayIconPath = `png/${generateSafeFilename(name).toLowerCase()}${iconExt}`;
632
+ const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${safeAppName}${iconExt}`);
633
+ trayIconPath = `png/${safeAppName}${iconExt}`;
567
634
  await fsExtra.copy(systemTrayIcon, trayIcoPath);
568
635
  }
569
636
  else {
@@ -597,6 +664,7 @@ StartupNotify=true
597
664
  await fsExtra.writeFile(injectFilePath, '');
598
665
  }
599
666
  tauriConf.pake.proxy_url = proxyUrl || '';
667
+ tauriConf.pake.multi_instance = multiInstance;
600
668
  // Configure WASM support with required HTTP headers
601
669
  if (wasm) {
602
670
  tauriConf.app.security = {
@@ -673,6 +741,7 @@ class BaseBuilder {
673
741
  logger.warn('โœผ The first use requires installing system dependencies.');
674
742
  logger.warn('โœผ See more in https://tauri.app/start/prerequisites/.');
675
743
  }
744
+ ensureRustEnv();
676
745
  if (!checkRustInstalled()) {
677
746
  const res = await prompts({
678
747
  type: 'confirm',
@@ -705,10 +774,10 @@ class BaseBuilder {
705
774
  logger.info(`โœบ Located in China, using ${packageManager}/rsProxy CN mirror.`);
706
775
  const projectCnConf = path.join(tauriSrcPath, 'rust_proxy.toml');
707
776
  await fsExtra.copy(projectCnConf, projectConf);
708
- await shellExec(`cd "${npmDirectory}" && ${packageManager} install${registryOption}${peerDepsOption} --silent`, timeout, buildEnv);
777
+ await shellExec(`cd "${npmDirectory}" && ${packageManager} install${registryOption}${peerDepsOption}`, timeout, buildEnv, this.options.debug);
709
778
  }
710
779
  else {
711
- await shellExec(`cd "${npmDirectory}" && ${packageManager} install${peerDepsOption} --silent`, timeout, buildEnv);
780
+ await shellExec(`cd "${npmDirectory}" && ${packageManager} install${peerDepsOption}`, timeout, buildEnv, this.options.debug);
712
781
  }
713
782
  spinner.succeed(chalk.green('Package installed!'));
714
783
  if (!tauriTargetPathExists) {
@@ -733,8 +802,11 @@ class BaseBuilder {
733
802
  buildSpinner.stop();
734
803
  // Show static message to keep the status visible
735
804
  logger.warn('โœธ Building app...');
736
- const buildEnv = this.getBuildEnvironment();
737
- await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`, this.getBuildTimeout(), buildEnv);
805
+ const buildEnv = {
806
+ ...this.getBuildEnvironment(),
807
+ ...(process.env.NO_STRIP && { NO_STRIP: process.env.NO_STRIP }),
808
+ };
809
+ await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`, this.getBuildTimeout(), buildEnv, this.options.debug);
738
810
  // Copy app
739
811
  const fileName = this.getFileName();
740
812
  const fileType = this.getFileType(target);
@@ -1176,6 +1248,7 @@ const DEFAULT_PAKE_OPTIONS = {
1176
1248
  wasm: false,
1177
1249
  enableDragDrop: false,
1178
1250
  keepBinary: false,
1251
+ multiInstance: false,
1179
1252
  };
1180
1253
 
1181
1254
  async function checkUpdateTips() {
@@ -1619,8 +1692,7 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
1619
1692
  program
1620
1693
  .addHelpText('beforeAll', logo)
1621
1694
  .usage(`[url] [options]`)
1622
- .showHelpAfterError()
1623
- .helpOption(false);
1695
+ .showHelpAfterError();
1624
1696
  program
1625
1697
  .argument('[url]', 'The web URL you want to package', validateUrlInput)
1626
1698
  // Refer to https://github.com/tj/commander.js#custom-option-processing, turn string array into a string connected with custom connectors.
@@ -1699,6 +1771,9 @@ program
1699
1771
  .addOption(new Option('--keep-binary', 'Keep raw binary file alongside installer')
1700
1772
  .default(DEFAULT_PAKE_OPTIONS.keepBinary)
1701
1773
  .hideHelp())
1774
+ .addOption(new Option('--multi-instance', 'Allow multiple app instances')
1775
+ .default(DEFAULT_PAKE_OPTIONS.multiInstance)
1776
+ .hideHelp())
1702
1777
  .addOption(new Option('--installer-language <string>', 'Installer language')
1703
1778
  .default(DEFAULT_PAKE_OPTIONS.installerLanguage)
1704
1779
  .hideHelp())
@@ -1706,12 +1781,12 @@ program
1706
1781
  .configureHelp({
1707
1782
  sortSubcommands: true,
1708
1783
  optionTerm: (option) => {
1709
- if (option.flags === '-v, --version')
1784
+ if (option.flags === '-v, --version' || option.flags === '-h, --help')
1710
1785
  return '';
1711
1786
  return option.flags;
1712
1787
  },
1713
1788
  optionDescription: (option) => {
1714
- if (option.flags === '-v, --version')
1789
+ if (option.flags === '-v, --version' || option.flags === '-h, --help')
1715
1790
  return '';
1716
1791
  return option.description;
1717
1792
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pake-cli",
3
- "version": "3.3.7-beta",
3
+ "version": "3.4.0",
4
4
  "description": "๐Ÿคฑ๐Ÿป Turn any webpage into a desktop app with one command. ๐Ÿคฑ๐Ÿป ไธ€้”ฎๆ‰“ๅŒ…็ฝ‘้กต็”Ÿๆˆ่ฝป้‡ๆกŒ้ขๅบ”็”จใ€‚",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -52,12 +52,12 @@
52
52
  "dependencies": {
53
53
  "@tauri-apps/api": "^2.8.0",
54
54
  "@tauri-apps/cli": "^2.8.4",
55
- "axios": "^1.11.0",
56
- "chalk": "^5.6.0",
55
+ "axios": "^1.12.2",
56
+ "chalk": "^5.6.2",
57
57
  "commander": "^12.1.0",
58
58
  "execa": "^9.6.0",
59
59
  "file-type": "^18.7.0",
60
- "fs-extra": "^11.3.1",
60
+ "fs-extra": "^11.3.2",
61
61
  "icon-gen": "^5.0.0",
62
62
  "loglevel": "^1.9.2",
63
63
  "ora": "^8.2.0",
@@ -74,7 +74,7 @@
74
74
  "@rollup/plugin-replace": "^6.0.2",
75
75
  "@rollup/plugin-terser": "^0.4.4",
76
76
  "@types/fs-extra": "^11.0.4",
77
- "@types/node": "^20.19.13",
77
+ "@types/node": "^20.19.21",
78
78
  "@types/page-icon": "^0.3.6",
79
79
  "@types/prompts": "^2.4.9",
80
80
  "@types/tmp": "^0.2.6",
@@ -82,9 +82,9 @@
82
82
  "app-root-path": "^3.1.0",
83
83
  "cross-env": "^7.0.3",
84
84
  "prettier": "^3.6.2",
85
- "rollup": "^4.50.0",
85
+ "rollup": "^4.52.4",
86
86
  "rollup-plugin-typescript2": "^0.36.0",
87
87
  "tslib": "^2.8.1",
88
- "typescript": "^5.9.2"
88
+ "typescript": "^5.9.3"
89
89
  }
90
90
  }
@@ -31,5 +31,6 @@
31
31
  },
32
32
  "system_tray_path": "png/icon_512.png",
33
33
  "inject": [],
34
- "proxy_url": ""
34
+ "proxy_url": "",
35
+ "multi_instance": false
35
36
  }
@@ -1,6 +1,6 @@
1
1
  # This file is automatically @generated by Cargo.
2
2
  # It is not intended for manual editing.
3
- version = 3
3
+ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "addr2line"
@@ -3546,10 +3546,11 @@ dependencies = [
3546
3546
 
3547
3547
  [[package]]
3548
3548
  name = "serde"
3549
- version = "1.0.219"
3549
+ version = "1.0.228"
3550
3550
  source = "registry+https://github.com/rust-lang/crates.io-index"
3551
- checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
3551
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
3552
3552
  dependencies = [
3553
+ "serde_core",
3553
3554
  "serde_derive",
3554
3555
  ]
3555
3556
 
@@ -3564,11 +3565,20 @@ dependencies = [
3564
3565
  "typeid",
3565
3566
  ]
3566
3567
 
3568
+ [[package]]
3569
+ name = "serde_core"
3570
+ version = "1.0.228"
3571
+ source = "registry+https://github.com/rust-lang/crates.io-index"
3572
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
3573
+ dependencies = [
3574
+ "serde_derive",
3575
+ ]
3576
+
3567
3577
  [[package]]
3568
3578
  name = "serde_derive"
3569
- version = "1.0.219"
3579
+ version = "1.0.228"
3570
3580
  source = "registry+https://github.com/rust-lang/crates.io-index"
3571
- checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
3581
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
3572
3582
  dependencies = [
3573
3583
  "proc-macro2",
3574
3584
  "quote",
@@ -3588,14 +3598,15 @@ dependencies = [
3588
3598
 
3589
3599
  [[package]]
3590
3600
  name = "serde_json"
3591
- version = "1.0.143"
3601
+ version = "1.0.145"
3592
3602
  source = "registry+https://github.com/rust-lang/crates.io-index"
3593
- checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
3603
+ checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
3594
3604
  dependencies = [
3595
3605
  "itoa",
3596
3606
  "memchr",
3597
3607
  "ryu",
3598
3608
  "serde",
3609
+ "serde_core",
3599
3610
  ]
3600
3611
 
3601
3612
  [[package]]
@@ -15,19 +15,19 @@ crate-type = ["staticlib", "cdylib", "lib"]
15
15
  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
16
16
 
17
17
  [build-dependencies]
18
- tauri-build = { version = "2.4.0", features = [] }
18
+ tauri-build = { version = "2.4.1", features = [] }
19
19
 
20
20
  [dependencies]
21
- serde_json = "1.0.143"
22
- serde = { version = "1.0.219", features = ["derive"] }
21
+ serde_json = "1.0.145"
22
+ serde = { version = "1.0.228", features = ["derive"] }
23
23
  tokio = { version = "1.47.1", features = ["full"] }
24
- tauri = { version = "2.8.4", features = ["tray-icon", "image-ico", "image-png", "macos-proxy"] }
24
+ tauri = { version = "2.8.5", features = ["tray-icon", "image-ico", "image-png", "macos-proxy"] }
25
25
  tauri-plugin-window-state = "2.4.0"
26
26
  tauri-plugin-oauth = "2.0.0"
27
27
  tauri-plugin-http = "2.5.2"
28
28
  tauri-plugin-global-shortcut = { version = "2.3.0" }
29
29
  tauri-plugin-shell = "2.3.1"
30
- tauri-plugin-single-instance = "2.3.3"
30
+ tauri-plugin-single-instance = "2.3.4"
31
31
  tauri-plugin-notification = "2.3.1"
32
32
 
33
33
  [features]
@@ -30,5 +30,6 @@
30
30
  },
31
31
  "system_tray_path": "icons/icon.png",
32
32
  "inject": [],
33
- "proxy_url": ""
33
+ "proxy_url": "",
34
+ "multi_instance": false
34
35
  }
@@ -59,6 +59,8 @@ pub struct PakeConfig {
59
59
  pub system_tray: FunctionON,
60
60
  pub system_tray_path: String,
61
61
  pub proxy_url: String,
62
+ #[serde(default)]
63
+ pub multi_instance: bool,
62
64
  }
63
65
 
64
66
  impl PakeConfig {
@@ -100,21 +100,6 @@ const DOWNLOADABLE_FILE_EXTENSIONS = {
100
100
  "scss",
101
101
  "sass",
102
102
  "less",
103
- "html",
104
- "htm",
105
- "php",
106
- "py",
107
- "java",
108
- "cpp",
109
- "c",
110
- "h",
111
- "cs",
112
- "rb",
113
- "go",
114
- "rs",
115
- "swift",
116
- "kt",
117
- "scala",
118
103
  "sh",
119
104
  "bat",
120
105
  "ps1",
@@ -163,6 +148,22 @@ function isChineseLanguage(language = getUserLanguage()) {
163
148
  );
164
149
  }
165
150
 
151
+ // User notification helper
152
+ function showDownloadError(filename) {
153
+ const isChinese = isChineseLanguage();
154
+ const message = isChinese
155
+ ? `ไธ‹่ฝฝๅคฑ่ดฅ: ${filename}`
156
+ : `Download failed: ${filename}`;
157
+
158
+ if (window.Notification && Notification.permission === "granted") {
159
+ new Notification(isChinese ? "ไธ‹่ฝฝ้”™่ฏฏ" : "Download Error", {
160
+ body: message,
161
+ });
162
+ } else {
163
+ console.error(message);
164
+ }
165
+ }
166
+
166
167
  // Unified file detection - replaces both isDownloadLink and isFileLink
167
168
  function isDownloadableFile(url) {
168
169
  try {
@@ -266,7 +267,7 @@ document.addEventListener("DOMContentLoaded", () => {
266
267
  }
267
268
 
268
269
  // write the ArrayBuffer to a binary, and you're done
269
- const userLanguage = navigator.language || navigator.userLanguage;
270
+ const userLanguage = getUserLanguage();
270
271
  invoke("download_file_by_binary", {
271
272
  params: {
272
273
  filename,
@@ -275,16 +276,18 @@ document.addEventListener("DOMContentLoaded", () => {
275
276
  },
276
277
  }).catch((error) => {
277
278
  console.error("Failed to download data URI file:", filename, error);
279
+ showDownloadError(filename);
278
280
  });
279
281
  } catch (error) {
280
282
  console.error("Failed to process data URI:", dataURI, error);
283
+ showDownloadError(filename || "file");
281
284
  }
282
285
  }
283
286
 
284
287
  function downloadFromBlobUrl(blobUrl, filename) {
285
288
  convertBlobUrlToBinary(blobUrl)
286
289
  .then((binary) => {
287
- const userLanguage = navigator.language || navigator.userLanguage;
290
+ const userLanguage = getUserLanguage();
288
291
  invoke("download_file_by_binary", {
289
292
  params: {
290
293
  filename,
@@ -293,10 +296,12 @@ document.addEventListener("DOMContentLoaded", () => {
293
296
  },
294
297
  }).catch((error) => {
295
298
  console.error("Failed to download blob file:", filename, error);
299
+ showDownloadError(filename);
296
300
  });
297
301
  })
298
302
  .catch((error) => {
299
303
  console.error("Failed to convert blob to binary:", blobUrl, error);
304
+ showDownloadError(filename);
300
305
  });
301
306
  }
302
307
 
@@ -385,20 +390,21 @@ document.addEventListener("DOMContentLoaded", () => {
385
390
 
386
391
  // Handle _blank links: same domain navigates in-app, cross-domain opens new window
387
392
  if (target === "_blank") {
388
- e.preventDefault();
389
- e.stopImmediatePropagation();
390
-
391
393
  if (isSameDomain(absoluteUrl)) {
392
- window.location.href = absoluteUrl;
393
- } else {
394
- const newWindow = originalWindowOpen.call(
395
- window,
396
- absoluteUrl,
397
- "_blank",
398
- "width=1200,height=800,scrollbars=yes,resizable=yes",
399
- );
400
- if (!newWindow) handleExternalLink(absoluteUrl);
394
+ // For same-domain links, let the browser/SPA handle it naturally
395
+ // This prevents full page reload in SPA apps like Discord
396
+ return;
401
397
  }
398
+
399
+ e.preventDefault();
400
+ e.stopImmediatePropagation();
401
+ const newWindow = originalWindowOpen.call(
402
+ window,
403
+ absoluteUrl,
404
+ "_blank",
405
+ "width=1200,height=800,scrollbars=yes,resizable=yes",
406
+ );
407
+ if (!newWindow) handleExternalLink(absoluteUrl);
402
408
  return;
403
409
  }
404
410
 
@@ -448,39 +454,24 @@ document.addEventListener("DOMContentLoaded", () => {
448
454
  // Rewrite the window.open function.
449
455
  const originalWindowOpen = window.open;
450
456
  window.open = function (url, name, specs) {
451
- // Apple login and google login
452
457
  if (name === "AppleAuthentication") {
453
- //do nothing
454
- } else if (
455
- specs &&
456
- (specs.includes("height=") || specs.includes("width="))
457
- ) {
458
- location.href = url;
459
- } else {
458
+ return originalWindowOpen.call(window, url, name, specs);
459
+ }
460
+
461
+ try {
460
462
  const baseUrl = window.location.origin + window.location.pathname;
461
463
  const hrefUrl = new URL(url, baseUrl);
462
464
  const absoluteUrl = hrefUrl.href;
463
465
 
464
- // Apply same domain logic as anchor links
465
- if (isSameDomain(absoluteUrl)) {
466
- // Same domain: navigate in app or open new window based on specs
467
- if (name === "_blank" || !name) {
468
- return originalWindowOpen.call(
469
- window,
470
- absoluteUrl,
471
- "_blank",
472
- "width=1200,height=800,scrollbars=yes,resizable=yes",
473
- );
474
- } else {
475
- location.href = absoluteUrl;
476
- }
477
- } else {
478
- // Cross domain: open in external browser
466
+ if (!isSameDomain(absoluteUrl)) {
479
467
  handleExternalLink(absoluteUrl);
468
+ return null;
480
469
  }
470
+
471
+ return originalWindowOpen.call(window, absoluteUrl, name, specs);
472
+ } catch (error) {
473
+ return originalWindowOpen.call(window, url, name, specs);
481
474
  }
482
- // Call the original window.open function to maintain its normal functionality.
483
- return originalWindowOpen.call(window, url, name, specs);
484
475
  };
485
476
 
486
477
  // Set the default zoom, There are problems with Loop without using try-catch.
@@ -688,13 +679,16 @@ document.addEventListener("DOMContentLoaded", () => {
688
679
  }
689
680
  } else {
690
681
  // Regular HTTP(S) image
691
- const userLanguage = navigator.language || navigator.userLanguage;
682
+ const userLanguage = getUserLanguage();
692
683
  invoke("download_file", {
693
684
  params: {
694
685
  url: imageUrl,
695
686
  filename: filename,
696
687
  language: userLanguage,
697
688
  },
689
+ }).catch((error) => {
690
+ console.error("Failed to download image:", filename, error);
691
+ showDownloadError(filename);
698
692
  });
699
693
  }
700
694
  }
@@ -742,7 +736,7 @@ document.addEventListener("DOMContentLoaded", () => {
742
736
 
743
737
  // Simplified menu builder
744
738
  function buildMenuItems(type, data) {
745
- const userLanguage = navigator.language || navigator.userLanguage;
739
+ const userLanguage = getUserLanguage();
746
740
  const items = [];
747
741
 
748
742
  switch (type) {
@@ -769,6 +763,9 @@ document.addEventListener("DOMContentLoaded", () => {
769
763
  const filename = getFilenameFromUrl(data.url);
770
764
  invoke("download_file", {
771
765
  params: { url: data.url, filename, language: userLanguage },
766
+ }).catch((error) => {
767
+ console.error("Failed to download file:", filename, error);
768
+ showDownloadError(filename);
772
769
  });
773
770
  }),
774
771
  );
@@ -24,6 +24,7 @@ pub fn run_app() {
24
24
  let hide_on_close = pake_config.windows[0].hide_on_close;
25
25
  let activation_shortcut = pake_config.windows[0].activation_shortcut.clone();
26
26
  let init_fullscreen = pake_config.windows[0].fullscreen;
27
+ let multi_instance = pake_config.multi_instance;
27
28
 
28
29
  let window_state_plugin = WindowStatePlugin::default()
29
30
  .with_state_flags(if init_fullscreen {
@@ -35,19 +36,25 @@ pub fn run_app() {
35
36
  .build();
36
37
 
37
38
  #[allow(deprecated)]
38
- tauri_app
39
+ let mut app_builder = tauri_app
39
40
  .plugin(window_state_plugin)
40
41
  .plugin(tauri_plugin_oauth::init())
41
42
  .plugin(tauri_plugin_http::init())
42
43
  .plugin(tauri_plugin_shell::init())
43
- .plugin(tauri_plugin_notification::init())
44
- .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
44
+ .plugin(tauri_plugin_notification::init());
45
+
46
+ // Only add single instance plugin if multiple instances are not allowed
47
+ if !multi_instance {
48
+ app_builder = app_builder.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
45
49
  if let Some(window) = app.get_webview_window("pake") {
46
50
  let _ = window.unminimize();
47
51
  let _ = window.show();
48
52
  let _ = window.set_focus();
49
53
  }
50
- }))
54
+ }));
55
+ }
56
+
57
+ app_builder
51
58
  .invoke_handler(tauri::generate_handler![
52
59
  download_file,
53
60
  download_file_by_binary,
@@ -1,10 +0,0 @@
1
- [source.crates-io]
2
- replace-with = 'rsproxy-sparse'
3
- [source.rsproxy]
4
- registry = "https://rsproxy.cn/crates.io-index"
5
- [source.rsproxy-sparse]
6
- registry = "sparse+https://rsproxy.cn/index/"
7
- [registries.rsproxy]
8
- index = "https://rsproxy.cn/crates.io-index"
9
- [net]
10
- git-fetch-with-cli = true