pake-cli 3.2.0-beta1 → 3.2.0-beta15
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/dist/cli.js +313 -70
- package/dist/dev.js +276 -72
- package/dist/dev.js.map +1 -1
- package/package.json +16 -13
- package/src-tauri/.cargo/config.toml +19 -4
- package/src-tauri/.pake/pake.json +33 -0
- package/src-tauri/.pake/tauri.conf.json +24 -0
- package/src-tauri/.pake/tauri.linux.conf.json +16 -0
- package/src-tauri/.pake/tauri.macos.conf.json +15 -0
- package/src-tauri/.pake/tauri.windows.conf.json +15 -0
- package/src-tauri/Cargo.lock +57 -27
- package/src-tauri/Cargo.toml +2 -2
- package/src-tauri/gen/schemas/acl-manifests.json +1 -1
- package/src-tauri/gen/schemas/desktop-schema.json +24 -0
- package/src-tauri/gen/schemas/macOS-schema.json +24 -0
- package/src-tauri/pake.json +2 -1
- package/src-tauri/src/app/config.rs +2 -0
- package/src-tauri/src/app/window.rs +11 -7
- package/src-tauri/tauri.linux.conf.json +2 -2
- package/cli.js +0 -2
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import chalk from 'chalk';
|
|
2
3
|
import { InvalidArgumentError, program, Option } from 'commander';
|
|
3
4
|
import log from 'loglevel';
|
|
@@ -16,16 +17,18 @@ import updateNotifier from 'update-notifier';
|
|
|
16
17
|
import axios from 'axios';
|
|
17
18
|
import { dir } from 'tmp-promise';
|
|
18
19
|
import { fileTypeFromBuffer } from 'file-type';
|
|
20
|
+
import icongen from 'icon-gen';
|
|
21
|
+
import sharp from 'sharp';
|
|
19
22
|
import * as psl from 'psl';
|
|
20
23
|
|
|
21
24
|
var name = "pake-cli";
|
|
22
|
-
var version$1 = "3.2.0-
|
|
25
|
+
var version$1 = "3.2.0-beta15";
|
|
23
26
|
var description = "🤱🏻 Turn any webpage into a desktop app with Rust. 🤱🏻 利用 Rust 轻松构建轻量级多端桌面应用。";
|
|
24
27
|
var engines = {
|
|
25
28
|
node: ">=16.0.0"
|
|
26
29
|
};
|
|
27
30
|
var bin = {
|
|
28
|
-
pake: "./cli.js"
|
|
31
|
+
pake: "./dist/cli.js"
|
|
29
32
|
};
|
|
30
33
|
var repository = {
|
|
31
34
|
type: "git",
|
|
@@ -45,13 +48,12 @@ var keywords = [
|
|
|
45
48
|
];
|
|
46
49
|
var files = [
|
|
47
50
|
"dist",
|
|
48
|
-
"src-tauri"
|
|
49
|
-
"cli.js"
|
|
51
|
+
"src-tauri"
|
|
50
52
|
];
|
|
51
53
|
var scripts = {
|
|
52
54
|
start: "npm run dev",
|
|
53
55
|
dev: "npm run tauri dev",
|
|
54
|
-
build: "npm run tauri build --
|
|
56
|
+
build: "npm run tauri build --",
|
|
55
57
|
"build:debug": "npm run tauri build -- --debug",
|
|
56
58
|
"build:mac": "npm run tauri build -- --target universal-apple-darwin",
|
|
57
59
|
"build:config": "chmod +x script/app_config.mjs && node script/app_config.mjs",
|
|
@@ -60,25 +62,29 @@ var scripts = {
|
|
|
60
62
|
cli: "rollup -c rollup.config.js --watch",
|
|
61
63
|
"cli:dev": "cross-env NODE_ENV=development rollup -c rollup.config.js -w",
|
|
62
64
|
"cli:build": "cross-env NODE_ENV=production rollup -c rollup.config.js",
|
|
63
|
-
|
|
65
|
+
test: "npm run cli:build && PAKE_CREATE_APP=1 node tests/index.js",
|
|
66
|
+
format: "prettier --write . --ignore-unknown && cd src-tauri && cargo fmt --verbose",
|
|
67
|
+
"hooks:setup": "bash .githooks/setup.sh",
|
|
64
68
|
prepublishOnly: "npm run cli:build"
|
|
65
69
|
};
|
|
66
70
|
var type = "module";
|
|
67
|
-
var exports = "./dist/
|
|
71
|
+
var exports = "./dist/cli.js";
|
|
68
72
|
var license = "MIT";
|
|
69
73
|
var dependencies = {
|
|
70
|
-
"@tauri-apps/api": "^2.
|
|
71
|
-
"@tauri-apps/cli": "^2.
|
|
74
|
+
"@tauri-apps/api": "^2.8.0",
|
|
75
|
+
"@tauri-apps/cli": "^2.8.1",
|
|
72
76
|
axios: "^1.11.0",
|
|
73
|
-
chalk: "^5.
|
|
77
|
+
chalk: "^5.6.0",
|
|
74
78
|
commander: "^11.1.0",
|
|
75
79
|
execa: "^9.6.0",
|
|
76
80
|
"file-type": "^18.7.0",
|
|
77
81
|
"fs-extra": "^11.3.1",
|
|
82
|
+
"icon-gen": "^5.0.0",
|
|
78
83
|
loglevel: "^1.9.2",
|
|
79
84
|
ora: "^8.2.0",
|
|
80
85
|
prompts: "^2.4.2",
|
|
81
86
|
psl: "^1.15.0",
|
|
87
|
+
sharp: "^0.33.5",
|
|
82
88
|
"tmp-promise": "^3.0.3",
|
|
83
89
|
"update-notifier": "^7.3.1"
|
|
84
90
|
};
|
|
@@ -89,15 +95,15 @@ var devDependencies = {
|
|
|
89
95
|
"@rollup/plugin-replace": "^6.0.2",
|
|
90
96
|
"@rollup/plugin-terser": "^0.4.4",
|
|
91
97
|
"@types/fs-extra": "^11.0.4",
|
|
92
|
-
"@types/node": "^20.19.
|
|
98
|
+
"@types/node": "^20.19.11",
|
|
93
99
|
"@types/page-icon": "^0.3.6",
|
|
94
100
|
"@types/prompts": "^2.4.9",
|
|
95
|
-
"@types/psl": "^1.11.0",
|
|
96
101
|
"@types/tmp": "^0.2.6",
|
|
97
102
|
"@types/update-notifier": "^6.0.8",
|
|
98
103
|
"app-root-path": "^3.1.0",
|
|
99
104
|
"cross-env": "^7.0.3",
|
|
100
|
-
|
|
105
|
+
prettier: "^3.6.2",
|
|
106
|
+
rollup: "^4.46.3",
|
|
101
107
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
102
108
|
tslib: "^2.8.1",
|
|
103
109
|
typescript: "^5.9.2"
|
|
@@ -133,7 +139,8 @@ var windows = [
|
|
|
133
139
|
dark_mode: false,
|
|
134
140
|
activation_shortcut: "",
|
|
135
141
|
disabled_web_shortcuts: false,
|
|
136
|
-
hide_on_close: true
|
|
142
|
+
hide_on_close: true,
|
|
143
|
+
incognito: false
|
|
137
144
|
}
|
|
138
145
|
];
|
|
139
146
|
var user_agent = {
|
|
@@ -222,10 +229,10 @@ var MacConf = {
|
|
|
222
229
|
bundle: bundle$1
|
|
223
230
|
};
|
|
224
231
|
|
|
225
|
-
var productName = "
|
|
232
|
+
var productName = "weekly";
|
|
226
233
|
var bundle = {
|
|
227
234
|
icon: [
|
|
228
|
-
"png/
|
|
235
|
+
"png/weekly_512.png"
|
|
229
236
|
],
|
|
230
237
|
active: true,
|
|
231
238
|
linux: {
|
|
@@ -312,20 +319,25 @@ const IS_LINUX = platform$1 === 'linux';
|
|
|
312
319
|
const currentModulePath = fileURLToPath(import.meta.url);
|
|
313
320
|
// Resolve the parent directory of the current module
|
|
314
321
|
const npmDirectory = path.join(path.dirname(currentModulePath), '..');
|
|
315
|
-
const tauriConfigDirectory = path.join(npmDirectory, 'src-tauri');
|
|
322
|
+
const tauriConfigDirectory = path.join(npmDirectory, 'src-tauri', '.pake');
|
|
316
323
|
|
|
317
|
-
async function shellExec(command) {
|
|
324
|
+
async function shellExec(command, timeout = 300000, env) {
|
|
318
325
|
try {
|
|
319
326
|
const { exitCode } = await execa(command, {
|
|
320
327
|
cwd: npmDirectory,
|
|
321
|
-
stdio: 'inherit',
|
|
328
|
+
stdio: ['inherit', 'pipe', 'inherit'], // Hide stdout verbose, keep stderr
|
|
322
329
|
shell: true,
|
|
330
|
+
timeout,
|
|
331
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
323
332
|
});
|
|
324
333
|
return exitCode;
|
|
325
334
|
}
|
|
326
335
|
catch (error) {
|
|
327
336
|
const exitCode = error.exitCode ?? 'unknown';
|
|
328
337
|
const errorMessage = error.message || 'Unknown error occurred';
|
|
338
|
+
if (error.timedOut) {
|
|
339
|
+
throw new Error(`Command timed out after ${timeout}ms: "${command}". Try increasing timeout or check network connectivity.`);
|
|
340
|
+
}
|
|
329
341
|
throw new Error(`Error occurred while executing command "${command}". Exit code: ${exitCode}. Details: ${errorMessage}`);
|
|
330
342
|
}
|
|
331
343
|
}
|
|
@@ -403,11 +415,11 @@ async function installRust() {
|
|
|
403
415
|
const spinner = getSpinner('Downloading Rust...');
|
|
404
416
|
try {
|
|
405
417
|
await shellExec(IS_WIN ? rustInstallScriptForWindows : rustInstallScriptForMac);
|
|
406
|
-
spinner.succeed(chalk.green('Rust installed successfully!'));
|
|
418
|
+
spinner.succeed(chalk.green('✔ Rust installed successfully!'));
|
|
407
419
|
}
|
|
408
420
|
catch (error) {
|
|
409
|
-
|
|
410
|
-
|
|
421
|
+
spinner.fail(chalk.red('✕ Rust installation failed!'));
|
|
422
|
+
console.error(error.message);
|
|
411
423
|
process.exit(1);
|
|
412
424
|
}
|
|
413
425
|
}
|
|
@@ -438,7 +450,26 @@ async function combineFiles(files, output) {
|
|
|
438
450
|
}
|
|
439
451
|
|
|
440
452
|
async function mergeConfig(url, options, tauriConf) {
|
|
441
|
-
|
|
453
|
+
// Ensure .pake directory exists and copy source templates if needed
|
|
454
|
+
const srcTauriDir = path.join(npmDirectory, 'src-tauri');
|
|
455
|
+
await fsExtra.ensureDir(tauriConfigDirectory);
|
|
456
|
+
// Copy source config files to .pake directory (as templates)
|
|
457
|
+
const sourceFiles = [
|
|
458
|
+
'tauri.conf.json',
|
|
459
|
+
'tauri.macos.conf.json',
|
|
460
|
+
'tauri.windows.conf.json',
|
|
461
|
+
'tauri.linux.conf.json',
|
|
462
|
+
'pake.json',
|
|
463
|
+
];
|
|
464
|
+
await Promise.all(sourceFiles.map(async (file) => {
|
|
465
|
+
const sourcePath = path.join(srcTauriDir, file);
|
|
466
|
+
const destPath = path.join(tauriConfigDirectory, file);
|
|
467
|
+
if ((await fsExtra.pathExists(sourcePath)) &&
|
|
468
|
+
!(await fsExtra.pathExists(destPath))) {
|
|
469
|
+
await fsExtra.copy(sourcePath, destPath);
|
|
470
|
+
}
|
|
471
|
+
}));
|
|
472
|
+
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;
|
|
442
473
|
const { platform } = process;
|
|
443
474
|
// Set Windows parameters.
|
|
444
475
|
const tauriConfWindowOptions = {
|
|
@@ -452,6 +483,8 @@ async function mergeConfig(url, options, tauriConf) {
|
|
|
452
483
|
dark_mode: darkMode,
|
|
453
484
|
disabled_web_shortcuts: disabledWebShortcuts,
|
|
454
485
|
hide_on_close: hideOnClose,
|
|
486
|
+
incognito: incognito,
|
|
487
|
+
title: title || null,
|
|
455
488
|
};
|
|
456
489
|
Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions });
|
|
457
490
|
tauriConf.productName = name;
|
|
@@ -604,7 +637,6 @@ async function mergeConfig(url, options, tauriConf) {
|
|
|
604
637
|
};
|
|
605
638
|
const configPath = path.join(tauriConfigDirectory, platformConfigPaths[platform]);
|
|
606
639
|
const bundleConf = { bundle: tauriConf.bundle };
|
|
607
|
-
console.log('pakeConfig', tauriConf.pake);
|
|
608
640
|
await fsExtra.outputJSON(configPath, bundleConf, { spaces: 4 });
|
|
609
641
|
const pakeConfigPath = path.join(tauriConfigDirectory, 'pake.json');
|
|
610
642
|
await fsExtra.outputJSON(pakeConfigPath, tauriConf.pake, { spaces: 4 });
|
|
@@ -618,6 +650,19 @@ class BaseBuilder {
|
|
|
618
650
|
constructor(options) {
|
|
619
651
|
this.options = options;
|
|
620
652
|
}
|
|
653
|
+
getBuildEnvironment() {
|
|
654
|
+
return IS_MAC ? {
|
|
655
|
+
'CFLAGS': '-fno-modules',
|
|
656
|
+
'CXXFLAGS': '-fno-modules',
|
|
657
|
+
'MACOSX_DEPLOYMENT_TARGET': '14.0'
|
|
658
|
+
} : undefined;
|
|
659
|
+
}
|
|
660
|
+
getInstallTimeout() {
|
|
661
|
+
return process.platform === 'win32' ? 600000 : 300000;
|
|
662
|
+
}
|
|
663
|
+
getBuildTimeout() {
|
|
664
|
+
return 300000; // 5 minutes for build process
|
|
665
|
+
}
|
|
621
666
|
async prepare() {
|
|
622
667
|
const tauriSrcPath = path.join(npmDirectory, 'src-tauri');
|
|
623
668
|
const tauriTargetPath = path.join(tauriSrcPath, 'target');
|
|
@@ -645,14 +690,20 @@ class BaseBuilder {
|
|
|
645
690
|
const rustProjectDir = path.join(tauriSrcPath, '.cargo');
|
|
646
691
|
const projectConf = path.join(rustProjectDir, 'config.toml');
|
|
647
692
|
await fsExtra.ensureDir(rustProjectDir);
|
|
693
|
+
// 统一使用npm,简单可靠
|
|
694
|
+
const packageManager = 'npm';
|
|
695
|
+
const registryOption = isChina ? ' --registry=https://registry.npmmirror.com' : '';
|
|
696
|
+
const legacyPeerDeps = ' --legacy-peer-deps'; // 解决dependency conflicts
|
|
697
|
+
const timeout = this.getInstallTimeout();
|
|
698
|
+
const buildEnv = this.getBuildEnvironment();
|
|
648
699
|
if (isChina) {
|
|
649
700
|
logger.info('✺ Located in China, using npm/rsProxy CN mirror.');
|
|
650
701
|
const projectCnConf = path.join(tauriSrcPath, 'rust_proxy.toml');
|
|
651
702
|
await fsExtra.copy(projectCnConf, projectConf);
|
|
652
|
-
await shellExec(`cd "${npmDirectory}" &&
|
|
703
|
+
await shellExec(`cd "${npmDirectory}" && ${packageManager} install${registryOption}${legacyPeerDeps} --silent`, timeout, buildEnv);
|
|
653
704
|
}
|
|
654
705
|
else {
|
|
655
|
-
await shellExec(`cd "${npmDirectory}" &&
|
|
706
|
+
await shellExec(`cd "${npmDirectory}" && ${packageManager} install${legacyPeerDeps} --silent`, timeout, buildEnv);
|
|
656
707
|
}
|
|
657
708
|
spinner.succeed(chalk.green('Package installed!'));
|
|
658
709
|
if (!tauriTargetPathExists) {
|
|
@@ -669,9 +720,14 @@ class BaseBuilder {
|
|
|
669
720
|
const { name } = this.options;
|
|
670
721
|
await mergeConfig(url, this.options, tauriConfig);
|
|
671
722
|
// Build app
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
await
|
|
723
|
+
const buildSpinner = getSpinner('Building app...');
|
|
724
|
+
// Let spinner run for a moment so user can see it, then stop before npm command
|
|
725
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
726
|
+
buildSpinner.stop();
|
|
727
|
+
// Show static message to keep the status visible
|
|
728
|
+
logger.warn('✸ Building app...');
|
|
729
|
+
const buildEnv = this.getBuildEnvironment();
|
|
730
|
+
await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand()}`, this.getBuildTimeout(), buildEnv);
|
|
675
731
|
// Copy app
|
|
676
732
|
const fileName = this.getFileName();
|
|
677
733
|
const fileType = this.getFileType(target);
|
|
@@ -686,25 +742,66 @@ class BaseBuilder {
|
|
|
686
742
|
return target;
|
|
687
743
|
}
|
|
688
744
|
getBuildCommand() {
|
|
689
|
-
|
|
690
|
-
|
|
745
|
+
const baseCommand = this.options.debug
|
|
746
|
+
? 'npm run build:debug'
|
|
747
|
+
: 'npm run build';
|
|
748
|
+
// Use temporary config directory to avoid modifying source files
|
|
749
|
+
const configPath = path.join(npmDirectory, 'src-tauri', '.pake', 'tauri.conf.json');
|
|
750
|
+
let fullCommand = `${baseCommand} -- -c "${configPath}"`;
|
|
751
|
+
// For macOS, use app bundles by default unless DMG is explicitly requested
|
|
752
|
+
if (IS_MAC && this.options.targets === 'app') {
|
|
753
|
+
fullCommand += ' --bundles app';
|
|
754
|
+
}
|
|
755
|
+
// Add macos-proxy feature for modern macOS (Darwin 23+ = macOS 14+)
|
|
756
|
+
if (IS_MAC) {
|
|
757
|
+
const macOSVersion = this.getMacOSMajorVersion();
|
|
758
|
+
if (macOSVersion >= 23) {
|
|
759
|
+
fullCommand += ' --features macos-proxy';
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return fullCommand;
|
|
763
|
+
}
|
|
764
|
+
getMacOSMajorVersion() {
|
|
765
|
+
try {
|
|
766
|
+
const os = require('os');
|
|
767
|
+
const release = os.release();
|
|
768
|
+
const majorVersion = parseInt(release.split('.')[0], 10);
|
|
769
|
+
return majorVersion;
|
|
770
|
+
}
|
|
771
|
+
catch (error) {
|
|
772
|
+
return 0; // Disable proxy feature if version detection fails
|
|
773
|
+
}
|
|
691
774
|
}
|
|
692
775
|
getBasePath() {
|
|
693
776
|
const basePath = this.options.debug ? 'debug' : 'release';
|
|
694
777
|
return `src-tauri/target/${basePath}/bundle/`;
|
|
695
778
|
}
|
|
696
779
|
getBuildAppPath(npmDirectory, fileName, fileType) {
|
|
697
|
-
|
|
780
|
+
// For app bundles on macOS, the directory is 'macos', not 'app'
|
|
781
|
+
const bundleDir = fileType.toLowerCase() === 'app' ? 'macos' : fileType.toLowerCase();
|
|
782
|
+
return path.join(npmDirectory, this.getBasePath(), bundleDir, `${fileName}.${fileType}`);
|
|
698
783
|
}
|
|
699
784
|
}
|
|
700
785
|
|
|
701
786
|
class MacBuilder extends BaseBuilder {
|
|
702
787
|
constructor(options) {
|
|
703
788
|
super(options);
|
|
704
|
-
|
|
789
|
+
// Use DMG by default for distribution
|
|
790
|
+
// Only create app bundles for testing to avoid user interaction
|
|
791
|
+
if (process.env.PAKE_CREATE_APP === '1') {
|
|
792
|
+
this.options.targets = 'app';
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
this.options.targets = 'dmg';
|
|
796
|
+
}
|
|
705
797
|
}
|
|
706
798
|
getFileName() {
|
|
707
799
|
const { name } = this.options;
|
|
800
|
+
// For app bundles, use simple name without version/arch
|
|
801
|
+
if (this.options.targets === 'app') {
|
|
802
|
+
return name;
|
|
803
|
+
}
|
|
804
|
+
// For DMG files, use versioned filename
|
|
708
805
|
let arch;
|
|
709
806
|
if (this.options.multiArch) {
|
|
710
807
|
arch = 'universal';
|
|
@@ -811,6 +908,7 @@ const DEFAULT_PAKE_OPTIONS = {
|
|
|
811
908
|
inject: [],
|
|
812
909
|
installerLanguage: 'en-US',
|
|
813
910
|
hideOnClose: true,
|
|
911
|
+
incognito: false,
|
|
814
912
|
};
|
|
815
913
|
|
|
816
914
|
async function checkUpdateTips() {
|
|
@@ -819,61 +917,203 @@ async function checkUpdateTips() {
|
|
|
819
917
|
});
|
|
820
918
|
}
|
|
821
919
|
|
|
822
|
-
|
|
920
|
+
// Constants
|
|
921
|
+
const ICON_CONFIG = {
|
|
922
|
+
minFileSize: 100,
|
|
923
|
+
downloadTimeout: 10000,
|
|
924
|
+
supportedFormats: ['png', 'ico', 'jpeg', 'jpg', 'webp'],
|
|
925
|
+
whiteBackground: { r: 255, g: 255, b: 255 },
|
|
926
|
+
};
|
|
927
|
+
// API Configuration
|
|
928
|
+
const API_TOKENS = {
|
|
929
|
+
// cspell:disable-next-line
|
|
930
|
+
logoDev: ['pk_JLLMUKGZRpaG5YclhXaTkg', 'pk_Ph745P8mQSeYFfW2Wk039A'],
|
|
931
|
+
// cspell:disable-next-line
|
|
932
|
+
brandfetch: ['1idqvJC0CeFSeyp3Yf7', '1idej-yhU_ThggIHFyG'],
|
|
933
|
+
};
|
|
934
|
+
/**
|
|
935
|
+
* Adds white background to transparent icons only
|
|
936
|
+
*/
|
|
937
|
+
async function preprocessIcon(inputPath) {
|
|
938
|
+
try {
|
|
939
|
+
const metadata = await sharp(inputPath).metadata();
|
|
940
|
+
if (metadata.channels !== 4)
|
|
941
|
+
return inputPath; // No transparency
|
|
942
|
+
const { path: tempDir } = await dir();
|
|
943
|
+
const outputPath = path.join(tempDir, 'icon-with-background.png');
|
|
944
|
+
await sharp({
|
|
945
|
+
create: {
|
|
946
|
+
width: metadata.width || 512,
|
|
947
|
+
height: metadata.height || 512,
|
|
948
|
+
channels: 3,
|
|
949
|
+
background: ICON_CONFIG.whiteBackground,
|
|
950
|
+
},
|
|
951
|
+
})
|
|
952
|
+
.composite([{ input: inputPath }])
|
|
953
|
+
.png()
|
|
954
|
+
.toFile(outputPath);
|
|
955
|
+
return outputPath;
|
|
956
|
+
}
|
|
957
|
+
catch (error) {
|
|
958
|
+
logger.warn(`Failed to add background to icon: ${error.message}`);
|
|
959
|
+
return inputPath;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Converts icon to platform-specific format
|
|
964
|
+
*/
|
|
965
|
+
async function convertIconFormat(inputPath, appName) {
|
|
966
|
+
try {
|
|
967
|
+
if (!(await fsExtra.pathExists(inputPath)))
|
|
968
|
+
return null;
|
|
969
|
+
const { path: outputDir } = await dir();
|
|
970
|
+
const platformOutputDir = path.join(outputDir, 'converted-icons');
|
|
971
|
+
await fsExtra.ensureDir(platformOutputDir);
|
|
972
|
+
const processedInputPath = await preprocessIcon(inputPath);
|
|
973
|
+
const iconName = appName.toLowerCase();
|
|
974
|
+
// Generate platform-specific format
|
|
975
|
+
if (IS_WIN) {
|
|
976
|
+
await icongen(processedInputPath, platformOutputDir, {
|
|
977
|
+
report: false,
|
|
978
|
+
ico: { name: `${iconName}_256`, sizes: [256] },
|
|
979
|
+
});
|
|
980
|
+
return path.join(platformOutputDir, `${iconName}_256.ico`);
|
|
981
|
+
}
|
|
982
|
+
if (IS_LINUX) {
|
|
983
|
+
const outputPath = path.join(platformOutputDir, `${iconName}_512.png`);
|
|
984
|
+
await fsExtra.copy(processedInputPath, outputPath);
|
|
985
|
+
return outputPath;
|
|
986
|
+
}
|
|
987
|
+
// macOS
|
|
988
|
+
await icongen(processedInputPath, platformOutputDir, {
|
|
989
|
+
report: false,
|
|
990
|
+
icns: { name: iconName, sizes: [16, 32, 64, 128, 256, 512, 1024] },
|
|
991
|
+
});
|
|
992
|
+
const outputPath = path.join(platformOutputDir, `${iconName}.icns`);
|
|
993
|
+
return (await fsExtra.pathExists(outputPath)) ? outputPath : null;
|
|
994
|
+
}
|
|
995
|
+
catch (error) {
|
|
996
|
+
logger.warn(`Icon format conversion failed: ${error.message}`);
|
|
997
|
+
return null;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
async function handleIcon(options, url) {
|
|
823
1001
|
if (options.icon) {
|
|
824
1002
|
if (options.icon.startsWith('http')) {
|
|
825
1003
|
return downloadIcon(options.icon);
|
|
826
1004
|
}
|
|
827
|
-
|
|
828
|
-
|
|
1005
|
+
return path.resolve(options.icon);
|
|
1006
|
+
}
|
|
1007
|
+
// Try to get favicon from website if URL is provided
|
|
1008
|
+
if (url && url.startsWith('http') && options.name) {
|
|
1009
|
+
const faviconPath = await tryGetFavicon(url, options.name);
|
|
1010
|
+
if (faviconPath)
|
|
1011
|
+
return faviconPath;
|
|
1012
|
+
}
|
|
1013
|
+
logger.info('✼ No icon provided, using default icon.');
|
|
1014
|
+
const iconPath = IS_WIN
|
|
1015
|
+
? 'src-tauri/png/icon_256.ico'
|
|
1016
|
+
: IS_LINUX
|
|
1017
|
+
? 'src-tauri/png/icon_512.png'
|
|
1018
|
+
: 'src-tauri/icons/icon.icns';
|
|
1019
|
+
return path.join(npmDirectory, iconPath);
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Generates icon service URLs for a domain
|
|
1023
|
+
*/
|
|
1024
|
+
function generateIconServiceUrls(domain) {
|
|
1025
|
+
const logoDevUrls = API_TOKENS.logoDev
|
|
1026
|
+
.sort(() => Math.random() - 0.5)
|
|
1027
|
+
.map(token => `https://img.logo.dev/${domain}?token=${token}&format=png&size=256`);
|
|
1028
|
+
const brandfetchUrls = API_TOKENS.brandfetch
|
|
1029
|
+
.sort(() => Math.random() - 0.5)
|
|
1030
|
+
.map(key => `https://cdn.brandfetch.io/${domain}/w/400/h/400?c=${key}`);
|
|
1031
|
+
return [
|
|
1032
|
+
...logoDevUrls,
|
|
1033
|
+
...brandfetchUrls,
|
|
1034
|
+
`https://logo.clearbit.com/${domain}?size=256`,
|
|
1035
|
+
`https://logo.uplead.com/${domain}`,
|
|
1036
|
+
`https://www.google.com/s2/favicons?domain=${domain}&sz=256`,
|
|
1037
|
+
`https://favicon.is/${domain}`,
|
|
1038
|
+
`https://icons.duckduckgo.com/ip3/${domain}.ico`,
|
|
1039
|
+
`https://icon.horse/icon/${domain}`,
|
|
1040
|
+
`https://${domain}/favicon.ico`,
|
|
1041
|
+
`https://www.${domain}/favicon.ico`,
|
|
1042
|
+
`https://${domain}/apple-touch-icon.png`,
|
|
1043
|
+
`https://${domain}/apple-touch-icon-precomposed.png`,
|
|
1044
|
+
];
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Attempts to fetch favicon from website
|
|
1048
|
+
*/
|
|
1049
|
+
async function tryGetFavicon(url, appName) {
|
|
1050
|
+
try {
|
|
1051
|
+
const domain = new URL(url).hostname;
|
|
1052
|
+
const spinner = getSpinner(`Fetching icon from ${domain}...`);
|
|
1053
|
+
const serviceUrls = generateIconServiceUrls(domain);
|
|
1054
|
+
for (const serviceUrl of serviceUrls) {
|
|
1055
|
+
try {
|
|
1056
|
+
const faviconPath = await downloadIcon(serviceUrl, false);
|
|
1057
|
+
if (!faviconPath)
|
|
1058
|
+
continue;
|
|
1059
|
+
const convertedPath = await convertIconFormat(faviconPath, appName);
|
|
1060
|
+
if (convertedPath) {
|
|
1061
|
+
spinner.succeed(chalk.green('Icon fetched and converted successfully!'));
|
|
1062
|
+
return convertedPath;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
catch {
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
829
1068
|
}
|
|
1069
|
+
spinner.warn(`✼ No favicon found for ${domain}. Using default.`);
|
|
1070
|
+
return null;
|
|
830
1071
|
}
|
|
831
|
-
|
|
832
|
-
logger.warn(
|
|
833
|
-
|
|
834
|
-
? 'src-tauri/png/icon_256.ico'
|
|
835
|
-
: IS_LINUX
|
|
836
|
-
? 'src-tauri/png/icon_512.png'
|
|
837
|
-
: 'src-tauri/icons/icon.icns';
|
|
838
|
-
return path.join(npmDirectory, iconPath);
|
|
1072
|
+
catch (error) {
|
|
1073
|
+
logger.warn(`Failed to fetch favicon: ${error.message}`);
|
|
1074
|
+
return null;
|
|
839
1075
|
}
|
|
840
1076
|
}
|
|
841
|
-
|
|
842
|
-
|
|
1077
|
+
/**
|
|
1078
|
+
* Downloads icon from URL
|
|
1079
|
+
*/
|
|
1080
|
+
async function downloadIcon(iconUrl, showSpinner = true) {
|
|
843
1081
|
try {
|
|
844
|
-
const
|
|
1082
|
+
const response = await axios.get(iconUrl, {
|
|
845
1083
|
responseType: 'arraybuffer',
|
|
1084
|
+
timeout: ICON_CONFIG.downloadTimeout,
|
|
846
1085
|
});
|
|
847
|
-
const iconData =
|
|
848
|
-
if (!iconData)
|
|
1086
|
+
const iconData = response.data;
|
|
1087
|
+
if (!iconData || iconData.byteLength < ICON_CONFIG.minFileSize)
|
|
849
1088
|
return null;
|
|
850
|
-
}
|
|
851
1089
|
const fileDetails = await fileTypeFromBuffer(iconData);
|
|
852
|
-
if (!fileDetails) {
|
|
1090
|
+
if (!fileDetails || !ICON_CONFIG.supportedFormats.includes(fileDetails.ext)) {
|
|
853
1091
|
return null;
|
|
854
1092
|
}
|
|
855
|
-
|
|
856
|
-
let iconPath = `${tempPath}/icon.${fileDetails.ext}`;
|
|
857
|
-
// Fix this for linux
|
|
858
|
-
if (IS_LINUX) {
|
|
859
|
-
iconPath = 'png/linux_temp.png';
|
|
860
|
-
await fsExtra.outputFile(`${npmDirectory}/src-tauri/${iconPath}`, iconData);
|
|
861
|
-
}
|
|
862
|
-
else {
|
|
863
|
-
await fsExtra.outputFile(iconPath, iconData);
|
|
864
|
-
}
|
|
865
|
-
await fsExtra.outputFile(iconPath, iconData);
|
|
866
|
-
spinner.succeed(chalk.green('Icon downloaded successfully!'));
|
|
867
|
-
return iconPath;
|
|
1093
|
+
return await saveIconFile(iconData, fileDetails.ext);
|
|
868
1094
|
}
|
|
869
1095
|
catch (error) {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
return null;
|
|
1096
|
+
if (showSpinner && !(error.response?.status === 404)) {
|
|
1097
|
+
throw error;
|
|
873
1098
|
}
|
|
874
|
-
|
|
1099
|
+
return null;
|
|
875
1100
|
}
|
|
876
1101
|
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Saves icon file to temporary location
|
|
1104
|
+
*/
|
|
1105
|
+
async function saveIconFile(iconData, extension) {
|
|
1106
|
+
const buffer = Buffer.from(iconData);
|
|
1107
|
+
if (IS_LINUX) {
|
|
1108
|
+
const iconPath = 'png/linux_temp.png';
|
|
1109
|
+
await fsExtra.outputFile(`${npmDirectory}/src-tauri/${iconPath}`, buffer);
|
|
1110
|
+
return iconPath;
|
|
1111
|
+
}
|
|
1112
|
+
const { path: tempPath } = await dir();
|
|
1113
|
+
const iconPath = `${tempPath}/icon.${extension}`;
|
|
1114
|
+
await fsExtra.outputFile(iconPath, buffer);
|
|
1115
|
+
return iconPath;
|
|
1116
|
+
}
|
|
877
1117
|
|
|
878
1118
|
// Extracts the domain from a given URL.
|
|
879
1119
|
function getDomain(inputUrl) {
|
|
@@ -961,7 +1201,7 @@ async function handleOptions(options, url) {
|
|
|
961
1201
|
name,
|
|
962
1202
|
identifier: getIdentifier(url),
|
|
963
1203
|
};
|
|
964
|
-
appOptions.icon = await handleIcon(appOptions);
|
|
1204
|
+
appOptions.icon = await handleIcon(appOptions, url);
|
|
965
1205
|
return appOptions;
|
|
966
1206
|
}
|
|
967
1207
|
|
|
@@ -1054,6 +1294,10 @@ program
|
|
|
1054
1294
|
.addOption(new Option('--hide-on-close', 'Hide window on close instead of exiting')
|
|
1055
1295
|
.default(DEFAULT_PAKE_OPTIONS.hideOnClose)
|
|
1056
1296
|
.hideHelp())
|
|
1297
|
+
.addOption(new Option('--title <string>', 'Window title').hideHelp())
|
|
1298
|
+
.addOption(new Option('--incognito', 'Launch app in incognito/private mode')
|
|
1299
|
+
.default(DEFAULT_PAKE_OPTIONS.incognito)
|
|
1300
|
+
.hideHelp())
|
|
1057
1301
|
.addOption(new Option('--installer-language <string>', 'Installer language')
|
|
1058
1302
|
.default(DEFAULT_PAKE_OPTIONS.installerLanguage)
|
|
1059
1303
|
.hideHelp())
|
|
@@ -1074,7 +1318,6 @@ program
|
|
|
1074
1318
|
log.setLevel('debug');
|
|
1075
1319
|
}
|
|
1076
1320
|
const appOptions = await handleOptions(options, url);
|
|
1077
|
-
log.debug('PakeAppOptions', appOptions);
|
|
1078
1321
|
const builder = BuilderProvider.create(appOptions);
|
|
1079
1322
|
await builder.prepare();
|
|
1080
1323
|
await builder.build(url);
|