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 +1 -0
- package/dist/cli.js +106 -31
- package/package.json +7 -7
- package/src-tauri/.pake/pake.json +2 -1
- package/src-tauri/Cargo.lock +18 -7
- package/src-tauri/Cargo.toml +5 -5
- package/src-tauri/pake.json +2 -1
- package/src-tauri/src/app/config.rs +2 -0
- package/src-tauri/src/inject/event.js +53 -56
- package/src-tauri/src/lib.rs +11 -4
- package/src-tauri/.cargo/config.toml +0 -10
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.
|
|
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.
|
|
78
|
-
chalk: "^5.6.
|
|
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.
|
|
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.
|
|
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.
|
|
108
|
+
rollup: "^4.52.4",
|
|
108
109
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
109
110
|
tslib: "^2.8.1",
|
|
110
|
-
typescript: "^5.9.
|
|
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'],
|
|
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
|
-
|
|
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) ||
|
|
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 =
|
|
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/${
|
|
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/${
|
|
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/${
|
|
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/${
|
|
566
|
-
trayIconPath = `png/${
|
|
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}
|
|
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}
|
|
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 =
|
|
737
|
-
|
|
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
|
+
"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.
|
|
56
|
-
"chalk": "^5.6.
|
|
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.
|
|
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.
|
|
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.
|
|
85
|
+
"rollup": "^4.52.4",
|
|
86
86
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
87
87
|
"tslib": "^2.8.1",
|
|
88
|
-
"typescript": "^5.9.
|
|
88
|
+
"typescript": "^5.9.3"
|
|
89
89
|
}
|
|
90
90
|
}
|
package/src-tauri/Cargo.lock
CHANGED
|
@@ -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
|
+
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.
|
|
3549
|
+
version = "1.0.228"
|
|
3550
3550
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3551
|
-
checksum = "
|
|
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.
|
|
3579
|
+
version = "1.0.228"
|
|
3570
3580
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3571
|
-
checksum = "
|
|
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.
|
|
3601
|
+
version = "1.0.145"
|
|
3592
3602
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
3593
|
-
checksum = "
|
|
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]]
|
package/src-tauri/Cargo.toml
CHANGED
|
@@ -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.
|
|
18
|
+
tauri-build = { version = "2.4.1", features = [] }
|
|
19
19
|
|
|
20
20
|
[dependencies]
|
|
21
|
-
serde_json = "1.0.
|
|
22
|
-
serde = { version = "1.0.
|
|
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.
|
|
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.
|
|
30
|
+
tauri-plugin-single-instance = "2.3.4"
|
|
31
31
|
tauri-plugin-notification = "2.3.1"
|
|
32
32
|
|
|
33
33
|
[features]
|
package/src-tauri/pake.json
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
);
|
package/src-tauri/src/lib.rs
CHANGED
|
@@ -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
|
-
|
|
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
|