pake-cli 3.3.4 โ†’ 3.3.6

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
@@ -22,7 +22,7 @@
22
22
  - ๐ŸŽ **Lightweight**: Nearly 20 times smaller than Electron packages, typically around 5M
23
23
  - ๐Ÿš€ **Fast**: Built with Rust Tauri, much faster than traditional JS frameworks with lower memory usage
24
24
  - โšก **Easy to use**: One-command packaging via CLI or online building, no complex configuration needed
25
- - ๐Ÿ“ฆ **Feature-rich**: Supports shortcut pass-through, immersive windows, drag & drop, style customization, ad removal
25
+ - ๐Ÿ“ฆ **Feature-rich**: Supports shortcuts, immersive windows, drag & drop, style customization, ad removal
26
26
 
27
27
  ## Getting Started
28
28
 
@@ -529,7 +529,7 @@ Pake's development can not be without these Hackers. They contributed a lot of c
529
529
 
530
530
  ## Support
531
531
 
532
- 1. I have two cats, TangYuan and Coke. If you think Pake delights your life, you can feed them <a href="https://miaoyan.app/cats.html?name=Pake" target="_blank">some canned food ๐Ÿฅฉ</a>.
532
+ 1. I have two cats, TangYuan and Coke. If you think Pake delights your life, you can feed them <a href="https://miaoyan.app/cats.html?name=Pake" target="_blank">food ๐Ÿฅฉ</a>.
533
533
  2. If you like Pake, you can star it on GitHub. Also, welcome to [recommend Pake](https://twitter.com/intent/tweet?url=https://github.com/tw93/Pake&text=Pake%20-%20Turn%20any%20webpage%20into%20a%20desktop%20app%20with%20one%20command.%20Nearly%2020x%20smaller%20than%20Electron%20packages,%20supports%20macOS%20Windows%20Linux) to your friends.
534
534
  3. You can follow my [Twitter](https://twitter.com/HiTw93) to get the latest news of Pake or join our [Telegram](https://t.me/+GclQS9ZnxyI2ODQ1) chat group.
535
535
  4. I hope that you enjoy playing with it. Let us know if you find a website that would be great for a Mac App!
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ import sharp from 'sharp';
22
22
  import * as psl from 'psl';
23
23
 
24
24
  var name = "pake-cli";
25
- var version = "3.3.4";
25
+ var version = "3.3.6";
26
26
  var description = "๐Ÿคฑ๐Ÿป Turn any webpage into a desktop app with one command. ๐Ÿคฑ๐Ÿป ไธ€้”ฎๆ‰“ๅŒ…็ฝ‘้กต็”Ÿๆˆ่ฝป้‡ๆกŒ้ขๅบ”็”จใ€‚";
27
27
  var engines = {
28
28
  node: ">=18.0.0"
@@ -328,6 +328,26 @@ async function combineFiles(files, output) {
328
328
  return files;
329
329
  }
330
330
 
331
+ function generateSafeFilename(name) {
332
+ return name
333
+ .replace(/[<>:"/\\|?*]/g, '_')
334
+ .replace(/\s+/g, '_')
335
+ .replace(/\.+$/g, '')
336
+ .slice(0, 255);
337
+ }
338
+ function generateLinuxPackageName(name) {
339
+ return name
340
+ .toLowerCase()
341
+ .replace(/[^a-z0-9\u4e00-\u9fff]+/g, '-')
342
+ .replace(/^-+|-+$/g, '')
343
+ .replace(/-+/g, '-');
344
+ }
345
+ function generateIdentifierSafeName(name) {
346
+ return name
347
+ .replace(/[^a-zA-Z0-9]/g, '')
348
+ .toLowerCase();
349
+ }
350
+
331
351
  async function mergeConfig(url, options, tauriConf) {
332
352
  // Ensure .pake directory exists and copy source templates if needed
333
353
  const srcTauriDir = path.join(npmDirectory, 'src-tauri');
@@ -372,7 +392,7 @@ async function mergeConfig(url, options, tauriConf) {
372
392
  tauriConf.identifier = identifier;
373
393
  tauriConf.version = appVersion;
374
394
  if (platform === 'linux') {
375
- tauriConf.mainBinaryName = `pake-${name.toLowerCase()}`;
395
+ tauriConf.mainBinaryName = `pake-${generateIdentifierSafeName(name)}`;
376
396
  }
377
397
  if (platform == 'win32') {
378
398
  tauriConf.bundle.windows.wix.language[0] = installerLanguage;
@@ -418,8 +438,8 @@ async function mergeConfig(url, options, tauriConf) {
418
438
  // Remove hardcoded desktop files and regenerate with correct app name
419
439
  delete tauriConf.bundle.linux.deb.files;
420
440
  // Generate correct desktop file configuration
421
- const appNameLower = name.toLowerCase();
422
- const identifier = `com.pake.${appNameLower}`;
441
+ const appNameSafe = generateSafeFilename(name).toLowerCase();
442
+ const identifier = `com.pake.${appNameSafe}`;
423
443
  const desktopFileName = `${identifier}.desktop`;
424
444
  // Create desktop file content
425
445
  const desktopContent = `[Desktop Entry]
@@ -427,8 +447,8 @@ Version=1.0
427
447
  Type=Application
428
448
  Name=${name}
429
449
  Comment=${name}
430
- Exec=pake-${appNameLower}
431
- Icon=${appNameLower}_512
450
+ Exec=pake-${appNameSafe}
451
+ Icon=${appNameSafe}_512
432
452
  Categories=Network;WebBrowser;
433
453
  MimeType=text/html;text/xml;application/xhtml_xml;
434
454
  StartupNotify=true
@@ -472,28 +492,29 @@ StartupNotify=true
472
492
  const platformIconMap = {
473
493
  win32: {
474
494
  fileExt: '.ico',
475
- path: `png/${name.toLowerCase()}_256.ico`,
495
+ path: `png/${generateSafeFilename(name).toLowerCase()}_256.ico`,
476
496
  defaultIcon: 'png/icon_256.ico',
477
497
  message: 'Windows icon must be .ico and 256x256px.',
478
498
  },
479
499
  linux: {
480
500
  fileExt: '.png',
481
- path: `png/${name.toLowerCase()}_512.png`,
501
+ path: `png/${generateSafeFilename(name).toLowerCase()}_512.png`,
482
502
  defaultIcon: 'png/icon_512.png',
483
503
  message: 'Linux icon must be .png and 512x512px.',
484
504
  },
485
505
  darwin: {
486
506
  fileExt: '.icns',
487
- path: `icons/${name.toLowerCase()}.icns`,
507
+ path: `icons/${generateSafeFilename(name).toLowerCase()}.icns`,
488
508
  defaultIcon: 'icons/icon.icns',
489
509
  message: 'macOS icon must be .icns type.',
490
510
  },
491
511
  };
492
512
  const iconInfo = platformIconMap[platform];
493
- const exists = options.icon && (await fsExtra.pathExists(options.icon));
513
+ const resolvedIconPath = options.icon ? path.resolve(options.icon) : null;
514
+ const exists = resolvedIconPath && (await fsExtra.pathExists(resolvedIconPath));
494
515
  if (exists) {
495
516
  let updateIconPath = true;
496
- let customIconExt = path.extname(options.icon).toLowerCase();
517
+ let customIconExt = path.extname(resolvedIconPath).toLowerCase();
497
518
  if (customIconExt !== iconInfo.fileExt) {
498
519
  updateIconPath = false;
499
520
  logger.warn(`โœผ ${iconInfo.message}, but you give ${customIconExt}`);
@@ -503,10 +524,9 @@ StartupNotify=true
503
524
  const iconPath = path.join(npmDirectory, 'src-tauri/', iconInfo.path);
504
525
  tauriConf.bundle.resources = [iconInfo.path];
505
526
  // Avoid copying if source and destination are the same
506
- const absoluteIconPath = path.resolve(options.icon);
507
527
  const absoluteDestPath = path.resolve(iconPath);
508
- if (absoluteIconPath !== absoluteDestPath) {
509
- await fsExtra.copy(options.icon, iconPath);
528
+ if (resolvedIconPath !== absoluteDestPath) {
529
+ await fsExtra.copy(resolvedIconPath, iconPath);
510
530
  }
511
531
  }
512
532
  if (updateIconPath) {
@@ -528,8 +548,8 @@ StartupNotify=true
528
548
  // ้œ€่ฆๅˆคๆ–ญๅ›พๆ ‡ๆ ผๅผ๏ผŒ้ป˜่ฎคๅชๆ”ฏๆŒicoๅ’Œpngไธค็ง
529
549
  let iconExt = path.extname(systemTrayIcon).toLowerCase();
530
550
  if (iconExt == '.png' || iconExt == '.ico') {
531
- const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${name.toLowerCase()}${iconExt}`);
532
- trayIconPath = `png/${name.toLowerCase()}${iconExt}`;
551
+ const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${generateSafeFilename(name).toLowerCase()}${iconExt}`);
552
+ trayIconPath = `png/${generateSafeFilename(name).toLowerCase()}${iconExt}`;
533
553
  await fsExtra.copy(systemTrayIcon, trayIcoPath);
534
554
  }
535
555
  else {
@@ -854,7 +874,7 @@ class BaseBuilder {
854
874
  const extension = process.platform === 'win32' ? '.exe' : '';
855
875
  // Linux uses the unique binary name we set in merge.ts
856
876
  if (process.platform === 'linux') {
857
- return `pake-${appName.toLowerCase()}${extension}`;
877
+ return `pake-${generateIdentifierSafeName(appName)}${extension}`;
858
878
  }
859
879
  // Windows and macOS use 'pake' as binary name
860
880
  return `pake${extension}`;
@@ -1169,12 +1189,9 @@ const API_KEYS = {
1169
1189
  logoDev: ['pk_JLLMUKGZRpaG5YclhXaTkg', 'pk_Ph745P8mQSeYFfW2Wk039A'],
1170
1190
  brandfetch: ['1idqvJC0CeFSeyp3Yf7', '1idej-yhU_ThggIHFyG'],
1171
1191
  };
1172
- /**
1173
- * Generates platform-specific icon paths and handles copying for Windows
1174
- */
1175
1192
  function generateIconPath(appName, isDefault = false) {
1176
- const safeName = appName.toLowerCase().replace(/[^a-z0-9-_]/g, '_');
1177
- const baseName = isDefault ? 'icon' : safeName;
1193
+ const safeName = isDefault ? 'icon' : generateSafeFilename(appName).toLowerCase();
1194
+ const baseName = safeName;
1178
1195
  if (IS_WIN) {
1179
1196
  return path.join(npmDirectory, 'src-tauri', 'png', `${baseName}_256.ico`);
1180
1197
  }
@@ -1237,7 +1254,7 @@ async function convertIconFormat(inputPath, appName) {
1237
1254
  const platformOutputDir = path.join(outputDir, 'converted-icons');
1238
1255
  await fsExtra.ensureDir(platformOutputDir);
1239
1256
  const processedInputPath = await preprocessIcon(inputPath);
1240
- const iconName = appName.toLowerCase();
1257
+ const iconName = generateSafeFilename(appName).toLowerCase();
1241
1258
  // Generate platform-specific format
1242
1259
  if (IS_WIN) {
1243
1260
  // Support multiple sizes for better Windows compatibility
@@ -1513,8 +1530,8 @@ function resolveAppName(name, platform) {
1513
1530
  }
1514
1531
  function isValidName(name, platform) {
1515
1532
  const platformRegexMapping = {
1516
- linux: /^[a-z0-9][a-z0-9-]*$/,
1517
- default: /^[a-zA-Z0-9][a-zA-Z0-9- ]*$/,
1533
+ linux: /^[a-z0-9\u4e00-\u9fff][a-z0-9\u4e00-\u9fff-]*$/,
1534
+ default: /^[a-zA-Z0-9\u4e00-\u9fff][a-zA-Z0-9\u4e00-\u9fff- ]*$/,
1518
1535
  };
1519
1536
  const reg = platformRegexMapping[platform] || platformRegexMapping.default;
1520
1537
  return !!name && reg.test(name);
@@ -1530,10 +1547,8 @@ async function handleOptions(options, url) {
1530
1547
  const namePrompt = await promptText(promptMessage, defaultName);
1531
1548
  name = namePrompt || defaultName;
1532
1549
  }
1533
- // Handle platform-specific name formatting
1534
1550
  if (name && platform === 'linux') {
1535
- // Convert to lowercase and replace spaces with dashes for Linux
1536
- name = name.toLowerCase().replace(/\s+/g, '-');
1551
+ name = generateLinuxPackageName(name);
1537
1552
  }
1538
1553
  if (!isValidName(name, platform)) {
1539
1554
  const LINUX_NAME_ERROR = `โœ• Name should only include lowercase letters, numbers, and dashes (not leading dashes). Examples: com-123-xxx, 123pan, pan123, weread, we-read, 123.`;
@@ -1643,8 +1658,17 @@ program
1643
1658
  .addOption(new Option('--system-tray-icon <string>', 'Custom system tray icon')
1644
1659
  .default(DEFAULT_PAKE_OPTIONS.systemTrayIcon)
1645
1660
  .hideHelp())
1646
- .addOption(new Option('--hide-on-close', 'Hide window on close instead of exiting (default: true for macOS, false for others)')
1661
+ .addOption(new Option('--hide-on-close [boolean]', 'Hide window on close instead of exiting (default: true for macOS, false for others)')
1647
1662
  .default(DEFAULT_PAKE_OPTIONS.hideOnClose)
1663
+ .argParser((value) => {
1664
+ if (value === undefined)
1665
+ return true; // --hide-on-close without value
1666
+ if (value === 'true')
1667
+ return true;
1668
+ if (value === 'false')
1669
+ return false;
1670
+ throw new Error('--hide-on-close must be true or false');
1671
+ })
1648
1672
  .hideHelp())
1649
1673
  .addOption(new Option('--title <string>', 'Window title').hideHelp())
1650
1674
  .addOption(new Option('--incognito', 'Launch app in incognito/private mode')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pake-cli",
3
- "version": "3.3.4",
3
+ "version": "3.3.6",
4
4
  "description": "๐Ÿคฑ๐Ÿป Turn any webpage into a desktop app with one command. ๐Ÿคฑ๐Ÿป ไธ€้”ฎๆ‰“ๅŒ…็ฝ‘้กต็”Ÿๆˆ่ฝป้‡ๆกŒ้ขๅบ”็”จใ€‚",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -8,9 +8,3 @@ registry = "sparse+https://rsproxy.cn/index/"
8
8
  index = "https://rsproxy.cn/crates.io-index"
9
9
  [net]
10
10
  git-fetch-with-cli = true
11
-
12
- [env]
13
- # Fix for macOS 26 Beta compatibility issues
14
- # Forces use of compatible SDK when building on macOS 26 Beta
15
- MACOSX_DEPLOYMENT_TARGET = "15.5"
16
- SDKROOT = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk"
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "windows": [
3
3
  {
4
- "url": "https://weekly.tw93.fun",
4
+ "url": "https://github.com",
5
5
  "url_type": "web",
6
- "hide_title_bar": false,
6
+ "hide_title_bar": true,
7
7
  "fullscreen": false,
8
8
  "width": 1200,
9
- "height": 780,
9
+ "height": 800,
10
10
  "resizable": true,
11
11
  "always_on_top": false,
12
12
  "dark_mode": false,
@@ -1,6 +1,6 @@
1
1
  {
2
- "productName": "TestWorkflow",
3
- "identifier": "com.pake.33f03a",
2
+ "productName": "GitHubMultiArch",
3
+ "identifier": "com.pake.3097fc",
4
4
  "version": "1.0.0",
5
5
  "app": {
6
6
  "withGlobalTauri": true,
@@ -13,14 +13,14 @@
13
13
  },
14
14
  "bundle": {
15
15
  "icon": [
16
- "icons/testworkflow.icns"
16
+ "icons/githubmultiarch.icns"
17
17
  ],
18
18
  "active": true,
19
19
  "targets": [
20
- "dmg"
20
+ "app"
21
21
  ],
22
22
  "resources": [
23
- "icons/testworkflow.icns"
23
+ "icons/githubmultiarch.icns"
24
24
  ]
25
25
  }
26
26
  }
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "bundle": {
3
3
  "icon": [
4
- "icons/testworkflow.icns"
4
+ "icons/githubmultiarch.icns"
5
5
  ],
6
6
  "active": true,
7
7
  "targets": [
8
- "dmg"
8
+ "app"
9
9
  ],
10
10
  "resources": [
11
- "icons/testworkflow.icns"
11
+ "icons/githubmultiarch.icns"
12
12
  ]
13
13
  }
14
14
  }
@@ -252,27 +252,33 @@ document.addEventListener("DOMContentLoaded", () => {
252
252
  }
253
253
 
254
254
  function downloadFromDataUri(dataURI, filename) {
255
- const byteString = atob(dataURI.split(",")[1]);
256
- // write the bytes of the string to an ArrayBuffer
257
- const bufferArray = new ArrayBuffer(byteString.length);
255
+ try {
256
+ const byteString = atob(dataURI.split(",")[1]);
257
+ // write the bytes of the string to an ArrayBuffer
258
+ const bufferArray = new ArrayBuffer(byteString.length);
258
259
 
259
- // create a view into the buffer
260
- const binary = new Uint8Array(bufferArray);
260
+ // create a view into the buffer
261
+ const binary = new Uint8Array(bufferArray);
261
262
 
262
- // set the bytes of the buffer to the correct values
263
- for (let i = 0; i < byteString.length; i++) {
264
- binary[i] = byteString.charCodeAt(i);
265
- }
263
+ // set the bytes of the buffer to the correct values
264
+ for (let i = 0; i < byteString.length; i++) {
265
+ binary[i] = byteString.charCodeAt(i);
266
+ }
266
267
 
267
- // write the ArrayBuffer to a binary, and you're done
268
- const userLanguage = navigator.language || navigator.userLanguage;
269
- invoke("download_file_by_binary", {
270
- params: {
271
- filename,
272
- binary: Array.from(binary),
273
- language: userLanguage,
274
- },
275
- });
268
+ // write the ArrayBuffer to a binary, and you're done
269
+ const userLanguage = navigator.language || navigator.userLanguage;
270
+ invoke("download_file_by_binary", {
271
+ params: {
272
+ filename,
273
+ binary: Array.from(binary),
274
+ language: userLanguage,
275
+ },
276
+ }).catch(error => {
277
+ console.error('Failed to download data URI file:', filename, error);
278
+ });
279
+ } catch (error) {
280
+ console.error('Failed to process data URI:', dataURI, error);
281
+ }
276
282
  }
277
283
 
278
284
  function downloadFromBlobUrl(blobUrl, filename) {
@@ -284,7 +290,11 @@ document.addEventListener("DOMContentLoaded", () => {
284
290
  binary,
285
291
  language: userLanguage,
286
292
  },
293
+ }).catch(error => {
294
+ console.error('Failed to download blob file:', filename, error);
287
295
  });
296
+ }).catch(error => {
297
+ console.error('Failed to convert blob to binary:', blobUrl, error);
288
298
  });
289
299
  }
290
300
 
@@ -323,8 +333,16 @@ document.addEventListener("DOMContentLoaded", () => {
323
333
  anchorElement.download || e.metaKey || e.ctrlKey || isDownloadableFile(url);
324
334
 
325
335
  const handleExternalLink = (url) => {
336
+ // Don't try to open blob: or data: URLs with shell
337
+ if (isSpecialDownload(url)) {
338
+ console.warn('Cannot open special URL with shell:', url);
339
+ return;
340
+ }
341
+
326
342
  invoke("plugin:shell|open", {
327
343
  path: url,
344
+ }).catch(error => {
345
+ console.error('Failed to open URL with shell:', url, error);
328
346
  });
329
347
  };
330
348
 
@@ -351,6 +369,10 @@ document.addEventListener("DOMContentLoaded", () => {
351
369
  };
352
370
 
353
371
  const detectAnchorElementClick = (e) => {
372
+ // Safety check: ensure e.target exists and is an Element with closest method
373
+ if (!e.target || typeof e.target.closest !== 'function') {
374
+ return;
375
+ }
354
376
  const anchorElement = e.target.closest("a");
355
377
 
356
378
  if (anchorElement && anchorElement.href) {
@@ -701,7 +723,7 @@ document.addEventListener("DOMContentLoaded", () => {
701
723
  }
702
724
 
703
725
  // Check for parent elements with background images
704
- const parentWithBg = target.closest('[style*="background-image"]');
726
+ const parentWithBg = target && typeof target.closest === 'function' ? target.closest('[style*="background-image"]') : null;
705
727
  if (parentWithBg) {
706
728
  const bgImage = parentWithBg.style.backgroundImage;
707
729
  const urlMatch = bgImage.match(/url\(["']?([^"')]+)["']?\)/);
@@ -770,7 +792,7 @@ document.addEventListener("DOMContentLoaded", () => {
770
792
  const mediaInfo = getMediaInfo(target);
771
793
 
772
794
  // Check for links (but not if it's media)
773
- const linkElement = target.closest("a");
795
+ const linkElement = target && typeof target.closest === 'function' ? target.closest("a") : null;
774
796
  const isLink = linkElement && linkElement.href && !mediaInfo.isMedia;
775
797
 
776
798
  // Only show custom menu for media or links