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 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-beta1";
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 --release",
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
- format: "npx prettier --write . --ignore-unknown && cd src-tauri && cargo fmt --verbose",
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/pake.js";
71
+ var exports = "./dist/cli.js";
68
72
  var license = "MIT";
69
73
  var dependencies = {
70
- "@tauri-apps/api": "^2.7.0",
71
- "@tauri-apps/cli": "^2.7.1",
74
+ "@tauri-apps/api": "^2.8.0",
75
+ "@tauri-apps/cli": "^2.8.1",
72
76
  axios: "^1.11.0",
73
- chalk: "^5.5.0",
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.10",
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
- rollup: "^4.46.2",
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 = "we-read";
232
+ var productName = "weekly";
226
233
  var bundle = {
227
234
  icon: [
228
- "png/weekly.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
- console.error('Error installing Rust:', error.message);
410
- spinner.fail(chalk.red('Rust installation failed!'));
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
- const { width, height, fullscreen, hideTitleBar, alwaysOnTop, appVersion, darkMode, disabledWebShortcuts, activationShortcut, userAgent, showSystemTray, systemTrayIcon, useLocalFile, identifier, name, resizable = true, inject, proxyUrl, installerLanguage, hideOnClose, } = options;
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}" && npm install --registry=https://registry.npmmirror.com`);
703
+ await shellExec(`cd "${npmDirectory}" && ${packageManager} install${registryOption}${legacyPeerDeps} --silent`, timeout, buildEnv);
653
704
  }
654
705
  else {
655
- await shellExec(`cd "${npmDirectory}" && npm install`);
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 spinner = getSpinner('Building app...');
673
- setTimeout(() => spinner.stop(), 3000);
674
- await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand()}`);
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
- // the debug option should support `--debug` and `--release`
690
- return this.options.debug ? 'npm run build:debug' : 'npm run build';
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
- return path.join(npmDirectory, this.getBasePath(), fileType.toLowerCase(), `${fileName}.${fileType}`);
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
- this.options.targets = 'dmg';
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
- async function handleIcon(options) {
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
- else {
828
- return path.resolve(options.icon);
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
- else {
832
- logger.warn('✼ No icon given, default in use. For a custom icon, use --icon option.');
833
- const iconPath = IS_WIN
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
- async function downloadIcon(iconUrl) {
842
- const spinner = getSpinner('Downloading icon...');
1077
+ /**
1078
+ * Downloads icon from URL
1079
+ */
1080
+ async function downloadIcon(iconUrl, showSpinner = true) {
843
1081
  try {
844
- const iconResponse = await axios.get(iconUrl, {
1082
+ const response = await axios.get(iconUrl, {
845
1083
  responseType: 'arraybuffer',
1084
+ timeout: ICON_CONFIG.downloadTimeout,
846
1085
  });
847
- const iconData = await iconResponse.data;
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
- const { path: tempPath } = await dir();
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
- spinner.fail(chalk.red('Icon download failed!'));
871
- if (error.response && error.response.status === 404) {
872
- return null;
1096
+ if (showSpinner && !(error.response?.status === 404)) {
1097
+ throw error;
873
1098
  }
874
- throw error;
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);