pake-cli 3.11.7 โ†’ 3.11.9

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
@@ -207,3 +207,7 @@ Pake's development can not be without these Hackers. They contributed a lot of c
207
207
  - I have two cats, TangYuan and Coke. If you think Pake delights your life, you can feed them <a href="https://cats.tw93.fun?name=Pake" target="_blank">canned food ๐Ÿฅฉ</a>.
208
208
 
209
209
  <a href="https://cats.tw93.fun?name=Pake"><img src="https://cdn.jsdelivr.net/gh/tw93/sponsors@main/assets/sponsors.svg" width="1000" loading="lazy" /></a>
210
+
211
+ ## License
212
+
213
+ Pake is open source under GPL-3.0, see [LICENSE](./LICENSE); apps you build with Pake are entirely yours to use and distribute. If you fork Pake into your own product, to avoid confusion please give it a different name and credit Pake as the source.
package/dist/cli.js CHANGED
@@ -20,7 +20,7 @@ import { InvalidArgumentError, program as program$1, Option } from 'commander';
20
20
  import fs$1 from 'fs';
21
21
 
22
22
  var name = "pake-cli";
23
- var version = "3.11.7";
23
+ var version = "3.11.9";
24
24
  var description = "๐Ÿคฑ๐Ÿป Turn any webpage into a desktop app with one command. ๐Ÿคฑ๐Ÿป ไธ€้”ฎๆ‰“ๅŒ…็ฝ‘้กต็”Ÿๆˆ่ฝป้‡ๆกŒ้ขๅบ”็”จใ€‚";
25
25
  var engines = {
26
26
  node: ">=18.0.0"
@@ -58,6 +58,7 @@ var scripts = {
58
58
  analyze: "cd src-tauri && cargo bloat --release --crates",
59
59
  tauri: "tauri",
60
60
  cli: "cross-env NODE_ENV=development rollup -c -w",
61
+ "cli:dev": "cross-env NODE_ENV=development rollup -c -w",
61
62
  "cli:build": "cross-env NODE_ENV=production rollup -c",
62
63
  test: "pnpm run cli:build && cross-env PAKE_CREATE_APP=1 node tests/index.js",
63
64
  format: "prettier --write . --ignore-unknown && find tests -name '*.js' -exec sed -i '' 's/[[:space:]]*$//' {} \\; && cd src-tauri && cargo fmt --verbose",
@@ -68,7 +69,7 @@ var scripts = {
68
69
  };
69
70
  var type = "module";
70
71
  var exports$1 = "./dist/cli.js";
71
- var license = "MIT";
72
+ var license = "GPL-3.0-or-later";
72
73
  var dependencies = {
73
74
  "@tauri-apps/api": "~2.10.1",
74
75
  "@tauri-apps/cli": "^2.10.0",
@@ -248,38 +249,10 @@ async function shellExec(command, timeout = 300000, env) {
248
249
  if (error.timedOut) {
249
250
  throw new Error(`Command timed out after ${timeout}ms: "${command}". Try increasing timeout or check network connectivity.`);
250
251
  }
251
- let errorMsg = `Error occurred while executing command "${command}". Exit code: ${exitCode}. Details: ${errorMessage}`;
252
- // Provide helpful guidance for common Linux AppImage build failures
253
- // caused by strip tool incompatibility with modern glibc (2.38+)
254
- const lowerError = errorMessage.toLowerCase();
255
- if (process.platform === 'linux' &&
256
- (lowerError.includes('linuxdeploy') ||
257
- lowerError.includes('appimage') ||
258
- lowerError.includes('strip'))) {
259
- errorMsg +=
260
- '\n\nโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n' +
261
- 'Linux AppImage Build Failed\n' +
262
- 'โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n\n' +
263
- 'Cause: Strip tool incompatibility with glibc 2.38+\n' +
264
- ' (affects Debian Trixie, Arch Linux, and other modern distros)\n\n' +
265
- 'Quick fix:\n' +
266
- ' NO_STRIP=1 pake <url> --targets appimage --debug\n\n' +
267
- 'Alternatives:\n' +
268
- ' โ€ข Use DEB format: pake <url> --targets deb\n' +
269
- ' โ€ข Update binutils: sudo apt install binutils (or pacman -S binutils)\n' +
270
- ' โ€ข Detailed guide: https://github.com/tw93/Pake/blob/main/docs/faq.md\n' +
271
- 'โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”';
272
- if (lowerError.includes('fuse') ||
273
- lowerError.includes('operation not permitted') ||
274
- lowerError.includes('/dev/fuse')) {
275
- errorMsg +=
276
- '\n\nDocker / Container hint:\n' +
277
- ' AppImage tooling needs access to /dev/fuse. When running inside Docker, add:\n' +
278
- ' --privileged --device /dev/fuse --security-opt apparmor=unconfined\n' +
279
- ' or run on the host directly.';
280
- }
281
- }
282
- throw new Error(errorMsg);
252
+ // AppImage/linuxdeploy guidance is added by the caller (BaseBuilder), which
253
+ // knows the build target. We only have the command line here (the tool's
254
+ // diagnostics stream to the terminal via stdio:inherit, not into the error).
255
+ throw new Error(`Error occurred while executing command "${command}". Exit code: ${exitCode}. Details: ${errorMessage}`);
283
256
  }
284
257
  }
285
258
 
@@ -437,6 +410,14 @@ function generateIdentifierSafeName(name) {
437
410
  return cleaned;
438
411
  }
439
412
 
413
+ const LINUX_TARGET_TYPES = ['deb', 'appimage', 'rpm', 'zst'];
414
+ // Returns the valid Linux build targets from a comma-separated targets
415
+ // string, preserving LINUX_TARGET_TYPES order. Unknown entries are dropped.
416
+ function filterLinuxTargets(targets) {
417
+ const requested = targets.split(',').map((target) => target.trim());
418
+ return LINUX_TARGET_TYPES.filter((target) => requested.includes(target));
419
+ }
420
+
440
421
  /**
441
422
  * Pure transform from CLI options to the window-config slice that gets
442
423
  * merged into pake.json. Exposed for snapshot testing so option drift
@@ -562,18 +543,15 @@ Terminal=false
562
543
  [desktopInstallPath]: `assets/${desktopFileName}`,
563
544
  };
564
545
  const validTargets = [
565
- 'deb',
566
- 'appimage',
567
- 'rpm',
568
- 'deb-arm64',
569
- 'appimage-arm64',
570
- 'rpm-arm64',
546
+ ...LINUX_TARGET_TYPES,
547
+ ...LINUX_TARGET_TYPES.map((target) => `${target}-arm64`),
571
548
  ];
572
549
  const baseTarget = options.targets.includes('-arm64')
573
550
  ? options.targets.replace('-arm64', '')
574
551
  : options.targets;
575
552
  if (validTargets.includes(options.targets)) {
576
- tauriConf.bundle.targets = [baseTarget];
553
+ // zst is repacked from the deb payload, so Tauri itself bundles a deb.
554
+ tauriConf.bundle.targets = [baseTarget === 'zst' ? 'deb' : baseTarget];
577
555
  }
578
556
  else {
579
557
  logger.warn(`โœผ The target must be one of ${validTargets.join(', ')}, the default 'deb' will be used.`);
@@ -808,7 +786,7 @@ function getBuildTimeout() {
808
786
  }
809
787
  let packageManagerCache = null;
810
788
  function parseMajorVersion(version) {
811
- const match = version.match(/^(\d+)/);
789
+ const match = version.match(/^v?(\d+)/);
812
790
  return match ? Number(match[1]) : null;
813
791
  }
814
792
  function getPinnedPnpmMajorVersion() {
@@ -834,21 +812,10 @@ async function detectPackageManager() {
834
812
  return packageManagerCache;
835
813
  }
836
814
  const { execa } = await import('execa');
815
+ let pnpmVersion;
837
816
  try {
838
817
  const { stdout } = await execa('pnpm', ['--version']);
839
- const pnpmMajor = parseMajorVersion(stdout.trim());
840
- const pinnedPnpmMajor = getPinnedPnpmMajorVersion();
841
- if (pnpmMajor !== null &&
842
- pinnedPnpmMajor !== null &&
843
- pnpmMajor !== pinnedPnpmMajor &&
844
- (await detectNpm(execa))) {
845
- logger.warn(`โœผ Detected pnpm v${stdout.trim()}, but Pake is pinned to ${packageJson.packageManager}; using npm for package installation instead.`);
846
- packageManagerCache = 'npm';
847
- return 'npm';
848
- }
849
- logger.info('โœบ Using pnpm for package management.');
850
- packageManagerCache = 'pnpm';
851
- return 'pnpm';
818
+ pnpmVersion = stdout.trim();
852
819
  }
853
820
  catch {
854
821
  if (await detectNpm(execa)) {
@@ -858,6 +825,24 @@ async function detectPackageManager() {
858
825
  }
859
826
  throw new Error('Neither pnpm nor npm is available. Please install a package manager.');
860
827
  }
828
+ const normalizedPnpmVersion = pnpmVersion.startsWith('v')
829
+ ? pnpmVersion
830
+ : `v${pnpmVersion}`;
831
+ const pnpmMajor = parseMajorVersion(pnpmVersion);
832
+ const pinnedPnpmMajor = getPinnedPnpmMajorVersion();
833
+ if (pnpmMajor !== null &&
834
+ pinnedPnpmMajor !== null &&
835
+ pnpmMajor !== pinnedPnpmMajor) {
836
+ if (!(await detectNpm(execa))) {
837
+ throw new Error(`Detected pnpm ${normalizedPnpmVersion}, but Pake is pinned to ${packageJson.packageManager}. Install npm so Pake can fall back, or use pnpm ${pinnedPnpmMajor}.x to match the project pin.`);
838
+ }
839
+ logger.warn(`โœผ Detected pnpm ${normalizedPnpmVersion}, but Pake is pinned to ${packageJson.packageManager}; using npm for package management instead.`);
840
+ packageManagerCache = 'npm';
841
+ return 'npm';
842
+ }
843
+ logger.info('โœบ Using pnpm for package management.');
844
+ packageManagerCache = 'pnpm';
845
+ return 'pnpm';
861
846
  }
862
847
  function getInstallCommand(packageManager, useCnMirror) {
863
848
  const registryOption = useCnMirror
@@ -913,23 +898,30 @@ async function configureCargoRegistry(tauriSrcPath, useCnMirror) {
913
898
  logger.warn(`โœผ ${projectConf} still references rsproxy.cn. Remove it or set ${CN_MIRROR_ENV}=1 if you want to use the CN mirror.`);
914
899
  }
915
900
  }
916
- /**
917
- * Returns true when an error string looks like the well-known Tauri+linuxdeploy
918
- * strip failure that we automatically retry with NO_STRIP=1.
919
- */
920
- function isLinuxDeployStripError(error) {
921
- if (!(error instanceof Error) || !error.message) {
922
- return false;
923
- }
924
- const message = error.message.toLowerCase();
925
- return (message.includes('linuxdeploy') ||
926
- message.includes('failed to run linuxdeploy') ||
927
- message.includes('strip:') ||
928
- message.includes('unable to recognise the format of the input file') ||
929
- message.includes('appimage tool failed') ||
930
- message.includes('strip tool'));
931
- }
932
901
 
902
+ // Appended to the error when a Linux AppImage build fails for good. linuxdeploy's
903
+ // diagnostics stream to the terminal (stdio: 'inherit') and never reach
904
+ // error.message, so we cannot name the exact cause. We only reach here after
905
+ // NO_STRIP=1 has been applied and still failed, so strip is shown as ruled out.
906
+ const APPIMAGE_BAR = 'โ”'.repeat(56);
907
+ const APPIMAGE_FAILURE_GUIDANCE = `\n\n${APPIMAGE_BAR}\n` +
908
+ 'Linux AppImage Build Failed\n' +
909
+ `${APPIMAGE_BAR}\n\n` +
910
+ 'The AppImage bundler (linuxdeploy) failed. Common causes and fixes:\n\n' +
911
+ ' โ€ข Strip incompatibility (glibc 2.38+): NO_STRIP=1 was already applied and\n' +
912
+ ' the build still failed, so strip is likely not the cause.\n' +
913
+ ' โ€ข Missing gdk-pixbuf loaders (e.g. "cannot stat\n' +
914
+ " '/usr/lib/gdk-pixbuf-2.0/...'\"): install them, then rebuild:\n" +
915
+ ' Arch: sudo pacman -S gdk-pixbuf2 librsvg\n' +
916
+ ' Debian: sudo apt install librsvg2-common gdk-pixbuf2.0-bin\n' +
917
+ ' Fedora: sudo dnf install gdk-pixbuf2-modules librsvg2\n' +
918
+ ' then: sudo gdk-pixbuf-query-loaders --update-cache\n' +
919
+ ' (Arch refreshes the cache automatically via a pacman hook)\n' +
920
+ ' โ€ข Running in Docker/container: AppImage needs /dev/fuse:\n' +
921
+ ' --privileged --device /dev/fuse --security-opt apparmor=unconfined\n\n' +
922
+ 'Still stuck? Build a DEB instead: pake <url> --targets deb\n' +
923
+ 'Detailed guide: https://github.com/tw93/Pake/blob/main/docs/faq.md\n' +
924
+ APPIMAGE_BAR;
933
925
  class BaseBuilder {
934
926
  constructor(options) {
935
927
  this.options = options;
@@ -1004,7 +996,7 @@ class BaseBuilder {
1004
996
  const command = `cd "${npmDirectory}" && ${packageManager} run tauri${argSeparator} dev --config "${configPath}" ${featureArgs}`;
1005
997
  await shellExec(command);
1006
998
  }
1007
- async buildAndCopy(url, target) {
999
+ async buildAndCopy(url, target, logSuccess = true) {
1008
1000
  const { name = 'pake-app' } = this.options;
1009
1001
  await mergeConfig(url, this.options, tauriConfig);
1010
1002
  const packageManager = await detectPackageManager();
@@ -1021,14 +1013,11 @@ class BaseBuilder {
1021
1013
  ...(process.env.NO_STRIP ? { NO_STRIP: process.env.NO_STRIP } : {}),
1022
1014
  };
1023
1015
  const resolveExecEnv = () => Object.keys(buildEnv).length > 0 ? buildEnv : undefined;
1024
- // Warn users about potential AppImage build failures on modern Linux systems.
1025
- // The linuxdeploy tool bundled in Tauri uses an older strip tool that doesn't
1026
- // recognize the .relr.dyn section introduced in glibc 2.38+.
1027
- if (process.platform === 'linux' && target === 'appimage') {
1028
- if (!buildEnv.NO_STRIP) {
1029
- logger.warn('โš  Building AppImage on Linux may fail due to strip incompatibility with glibc 2.38+');
1030
- logger.warn('โš  If build fails, retry with: NO_STRIP=1 pake <url> --targets appimage');
1031
- }
1016
+ const isLinuxAppImage = process.platform === 'linux' && target === 'appimage';
1017
+ // AppImage builds can fail at the linuxdeploy strip step on glibc 2.38+.
1018
+ // A real failure now prints full guidance, so only hint in debug mode.
1019
+ if (isLinuxAppImage && !buildEnv.NO_STRIP && this.options.debug) {
1020
+ logger.warn('โš  AppImage strip step can fail on glibc 2.38+; Pake will auto-retry with NO_STRIP=1.');
1032
1021
  }
1033
1022
  const buildCommand = `cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`;
1034
1023
  const buildTimeout = getBuildTimeout();
@@ -1036,21 +1025,26 @@ class BaseBuilder {
1036
1025
  await shellExec(buildCommand, buildTimeout, resolveExecEnv());
1037
1026
  }
1038
1027
  catch (error) {
1039
- const shouldRetryWithoutStrip = process.platform === 'linux' &&
1040
- target === 'appimage' &&
1041
- !buildEnv.NO_STRIP &&
1042
- isLinuxDeployStripError(error);
1043
- if (shouldRetryWithoutStrip) {
1044
- logger.warn('โš  AppImage build failed during linuxdeploy strip step, retrying with NO_STRIP=1 automatically.');
1045
- buildEnv = {
1046
- ...buildEnv,
1047
- NO_STRIP: '1',
1048
- };
1049
- await shellExec(buildCommand, buildTimeout, resolveExecEnv());
1028
+ if (!isLinuxAppImage) {
1029
+ throw error;
1050
1030
  }
1051
- else {
1031
+ // linuxdeploy's diagnostics stream to the terminal (stdio: 'inherit') and
1032
+ // never reach error.message, so we cannot classify the cause. strip is the
1033
+ // most common AppImage failure, so retry once with NO_STRIP=1; if that
1034
+ // (or an already-NO_STRIP run) still fails, surface all known causes.
1035
+ if (buildEnv.NO_STRIP) {
1036
+ error.message += APPIMAGE_FAILURE_GUIDANCE;
1052
1037
  throw error;
1053
1038
  }
1039
+ logger.warn('โš  AppImage build failed, retrying once with NO_STRIP=1 (common glibc 2.38+ strip issue).');
1040
+ buildEnv = { ...buildEnv, NO_STRIP: '1' };
1041
+ try {
1042
+ await shellExec(buildCommand, buildTimeout, resolveExecEnv());
1043
+ }
1044
+ catch (retryError) {
1045
+ retryError.message += APPIMAGE_FAILURE_GUIDANCE;
1046
+ throw retryError;
1047
+ }
1054
1048
  }
1055
1049
  // Copy app
1056
1050
  const fileName = this.getFileName();
@@ -1063,8 +1057,10 @@ class BaseBuilder {
1063
1057
  await this.copyRawBinary(npmDirectory, name);
1064
1058
  }
1065
1059
  await fsExtra.remove(appPath);
1066
- logger.success('โœ” Build success!');
1067
- logger.success('โœ” App installer located in', distPath);
1060
+ if (logSuccess) {
1061
+ logger.success('โœ” Build success!');
1062
+ logger.success('โœ” App installer located in', distPath);
1063
+ }
1068
1064
  // Log binary location if preserved
1069
1065
  if (this.options.keepBinary) {
1070
1066
  const binaryPath = this.getRawBinaryPath(name);
@@ -1407,21 +1403,123 @@ class LinuxBuilder extends BaseBuilder {
1407
1403
  return `${name}_${version}_${arch}`;
1408
1404
  }
1409
1405
  async build(url) {
1410
- const targetTypes = ['deb', 'appimage', 'rpm'];
1411
- const requestedTargets = this.options.targets
1412
- .split(',')
1413
- .map((t) => t.trim());
1414
- for (const target of targetTypes) {
1415
- if (requestedTargets.includes(target)) {
1416
- this.currentBuildType = target;
1406
+ const targets = filterLinuxTargets(this.options.targets);
1407
+ if (targets.length === 0) {
1408
+ throw new Error(`No valid Linux target in "${this.options.targets}". Valid targets: ${LINUX_TARGET_TYPES.join(', ')}.`);
1409
+ }
1410
+ for (const target of targets) {
1411
+ this.currentBuildType = target;
1412
+ if (target === 'zst') {
1413
+ await this.buildAndCopy(url, 'deb', false);
1414
+ await this.createArchPackageFromDeb();
1415
+ }
1416
+ else {
1417
1417
  await this.buildAndCopy(url, target);
1418
1418
  }
1419
1419
  }
1420
1420
  }
1421
+ async ensureArchPackagingTools() {
1422
+ const requiredTools = [
1423
+ { tool: 'ar', pacmanPackage: 'binutils' },
1424
+ { tool: 'bsdtar', pacmanPackage: 'libarchive' },
1425
+ ];
1426
+ for (const { tool, pacmanPackage } of requiredTools) {
1427
+ try {
1428
+ await shellExec(`command -v ${tool} >/dev/null 2>&1`);
1429
+ }
1430
+ catch {
1431
+ throw new Error(`Building a zst package requires "${tool}". Install it first, e.g. "sudo pacman -S ${pacmanPackage}".`);
1432
+ }
1433
+ }
1434
+ }
1435
+ async createArchPackageFromDeb() {
1436
+ const { name = 'pake-app' } = this.options;
1437
+ const packageName = generateLinuxPackageName(name);
1438
+ const version = tauriConfig.version;
1439
+ const arch = this.buildArch === 'arm64' ? 'aarch64' : 'x86_64';
1440
+ const debPath = path.resolve(`${name}.deb`);
1441
+ const packagePath = path.resolve(`${name}-${version}-1-${arch}.pkg.tar.zst`);
1442
+ const workDir = path.resolve('.pake-arch-package');
1443
+ const dataDir = path.join(workDir, 'data');
1444
+ const controlDir = path.join(workDir, 'control');
1445
+ await this.ensureArchPackagingTools();
1446
+ await fsExtra.remove(workDir);
1447
+ await fsExtra.ensureDir(dataDir);
1448
+ await fsExtra.ensureDir(controlDir);
1449
+ try {
1450
+ await shellExec(`cd "${controlDir}" && ar x "${debPath}"`);
1451
+ const dataArchive = (await fsExtra.readdir(controlDir)).find((file) => file.startsWith('data.tar'));
1452
+ if (!dataArchive) {
1453
+ throw new Error(`Could not find data.tar payload in ${debPath}`);
1454
+ }
1455
+ await shellExec(`tar -xf "${path.join(controlDir, dataArchive)}" -C "${dataDir}"`);
1456
+ // Drop the desktop entry auto-generated by the Tauri deb bundler;
1457
+ // the payload already ships Pake's own com.pake.<name>.desktop.
1458
+ await fsExtra.remove(path.join(dataDir, 'usr', 'share', 'applications', `${packageName}.desktop`));
1459
+ const installedSize = await this.getDirectorySize(dataDir);
1460
+ const pkgInfo = `pkgname = ${packageName}
1461
+ pkgbase = ${packageName}
1462
+ pkgver = ${version}-1
1463
+ pkgdesc = ${name} Pake app
1464
+ url = https://github.com/tw93/Pake
1465
+ builddate = ${Math.floor(Date.now() / 1000)}
1466
+ packager = Pake
1467
+ size = ${installedSize}
1468
+ arch = ${arch}
1469
+ license = custom
1470
+ depend = cairo
1471
+ depend = desktop-file-utils
1472
+ depend = gdk-pixbuf2
1473
+ depend = glib2
1474
+ depend = gtk3
1475
+ depend = hicolor-icon-theme
1476
+ depend = libsoup3
1477
+ depend = pango
1478
+ depend = webkit2gtk-4.1
1479
+ `;
1480
+ await fsExtra.writeFile(path.join(dataDir, '.PKGINFO'), pkgInfo);
1481
+ await fsExtra.writeFile(path.join(dataDir, '.INSTALL'), `post_install() {
1482
+ gtk-update-icon-cache -q -t -f usr/share/icons/hicolor
1483
+ update-desktop-database -q usr/share/applications
1484
+ }
1485
+
1486
+ post_upgrade() {
1487
+ post_install
1488
+ }
1489
+
1490
+ post_remove() {
1491
+ gtk-update-icon-cache -q -t -f usr/share/icons/hicolor
1492
+ update-desktop-database -q usr/share/applications
1493
+ }
1494
+ `);
1495
+ await shellExec(`bsdtar --zstd -cf "${packagePath}" -C "${dataDir}" .PKGINFO .INSTALL usr`);
1496
+ await fsExtra.remove(debPath);
1497
+ logger.success('โœ” Build success!');
1498
+ logger.success('โœ” App installer located in', packagePath);
1499
+ }
1500
+ finally {
1501
+ await fsExtra.remove(workDir);
1502
+ }
1503
+ }
1504
+ async getDirectorySize(directory) {
1505
+ let size = 0;
1506
+ for (const entry of await fsExtra.readdir(directory, {
1507
+ withFileTypes: true,
1508
+ })) {
1509
+ const entryPath = path.join(directory, entry.name);
1510
+ if (entry.isDirectory()) {
1511
+ size += await this.getDirectorySize(entryPath);
1512
+ }
1513
+ else if (entry.isFile()) {
1514
+ size += (await fsExtra.stat(entryPath)).size;
1515
+ }
1516
+ }
1517
+ return size;
1518
+ }
1421
1519
  // Override buildAndCopy to ensure currentBuildType is synced if called directly, though the loop above handles it most of the time.
1422
- async buildAndCopy(url, target) {
1520
+ async buildAndCopy(url, target, logSuccess = true) {
1423
1521
  this.currentBuildType = target;
1424
- await super.buildAndCopy(url, target);
1522
+ await super.buildAndCopy(url, target, logSuccess);
1425
1523
  }
1426
1524
  getBuildCommand(packageManager = 'pnpm') {
1427
1525
  const configPath = path.join('src-tauri', '.pake', 'tauri.conf.json');
@@ -2334,8 +2432,8 @@ function resolveLocalAppName(filePath, platform) {
2334
2432
  return generateLinuxPackageName(baseName) || 'pake-app';
2335
2433
  }
2336
2434
  const normalized = baseName
2337
- .replace(/[^a-zA-Z0-9\u4e00-\u9fff -]/g, '')
2338
- .replace(/^[ -]+/, '')
2435
+ .replace(/[^a-zA-Z0-9\u4e00-\u9fff .-]/g, '')
2436
+ .replace(/^[ .-]+/, '')
2339
2437
  .replace(/\s+/g, ' ')
2340
2438
  .trim();
2341
2439
  return normalized || 'pake-app';
@@ -2343,7 +2441,7 @@ function resolveLocalAppName(filePath, platform) {
2343
2441
  function isValidName(name, platform) {
2344
2442
  const reg = platform === 'linux'
2345
2443
  ? /^[a-z0-9\u4e00-\u9fff][a-z0-9\u4e00-\u9fff-]*$/
2346
- : /^[a-zA-Z0-9\u4e00-\u9fff][a-zA-Z0-9\u4e00-\u9fff- ]*$/;
2444
+ : /^[a-zA-Z0-9\u4e00-\u9fff][a-zA-Z0-9\u4e00-\u9fff .-]*$/;
2347
2445
  return !!name && reg.test(name);
2348
2446
  }
2349
2447
  async function handleOptions(options, url) {
@@ -2364,7 +2462,7 @@ async function handleOptions(options, url) {
2364
2462
  }
2365
2463
  if (name && !isValidName(name, platform)) {
2366
2464
  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.`;
2367
- const DEFAULT_NAME_ERROR = `โœ• Name should only include letters, numbers, dashes, and spaces (not leading dashes and spaces). Examples: 123pan, 123Pan, Pan123, weread, WeRead, WERead, we-read, We Read, 123.`;
2465
+ const DEFAULT_NAME_ERROR = `โœ• Name should only include letters, numbers, dots, dashes, and spaces (not leading dots, dashes, and spaces). Examples: 123pan, 123Pan, Pan123, weread, WeRead, WERead, we-read, We Read, Vectorizer.AI, 123.`;
2368
2466
  const errorMsg = platform === 'linux' ? LINUX_NAME_ERROR : DEFAULT_NAME_ERROR;
2369
2467
  if (isActions) {
2370
2468
  logger.error(errorMsg);
@@ -2443,9 +2541,12 @@ const DEFAULT_PAKE_OPTIONS = {
2443
2541
 
2444
2542
  function validateNumberInput(value) {
2445
2543
  const parsedValue = Number(value);
2446
- if (isNaN(parsedValue)) {
2544
+ if (!Number.isFinite(parsedValue)) {
2447
2545
  throw new InvalidArgumentError('Not a number.');
2448
2546
  }
2547
+ if (parsedValue < 0) {
2548
+ throw new InvalidArgumentError('Must not be negative.');
2549
+ }
2449
2550
  return parsedValue;
2450
2551
  }
2451
2552
  function validateUrlInput(url) {
@@ -2578,8 +2679,8 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
2578
2679
  .addOption(new Option('--zoom <number>', 'Initial page zoom level (50-200)')
2579
2680
  .default(DEFAULT_PAKE_OPTIONS.zoom)
2580
2681
  .argParser((value) => {
2581
- const zoom = parseInt(value);
2582
- if (isNaN(zoom) || zoom < 50 || zoom > 200) {
2682
+ const zoom = Number(value);
2683
+ if (!Number.isFinite(zoom) || zoom < 50 || zoom > 200) {
2583
2684
  throw new Error('--zoom must be a number between 50 and 200');
2584
2685
  }
2585
2686
  return zoom;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pake-cli",
3
- "version": "3.11.7",
3
+ "version": "3.11.9",
4
4
  "description": "๐Ÿคฑ๐Ÿป Turn any webpage into a desktop app with one command. ๐Ÿคฑ๐Ÿป ไธ€้”ฎๆ‰“ๅŒ…็ฝ‘้กต็”Ÿๆˆ่ฝป้‡ๆกŒ้ขๅบ”็”จใ€‚",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -38,6 +38,7 @@
38
38
  "analyze": "cd src-tauri && cargo bloat --release --crates",
39
39
  "tauri": "tauri",
40
40
  "cli": "cross-env NODE_ENV=development rollup -c -w",
41
+ "cli:dev": "cross-env NODE_ENV=development rollup -c -w",
41
42
  "cli:build": "cross-env NODE_ENV=production rollup -c",
42
43
  "test": "pnpm run cli:build && cross-env PAKE_CREATE_APP=1 node tests/index.js",
43
44
  "format": "prettier --write . --ignore-unknown && find tests -name '*.js' -exec sed -i '' 's/[[:space:]]*$//' {} \\; && cd src-tauri && cargo fmt --verbose",
@@ -48,7 +49,7 @@
48
49
  },
49
50
  "type": "module",
50
51
  "exports": "./dist/cli.js",
51
- "license": "MIT",
52
+ "license": "GPL-3.0-or-later",
52
53
  "dependencies": {
53
54
  "@tauri-apps/api": "~2.10.1",
54
55
  "@tauri-apps/cli": "^2.10.0",
@@ -2564,7 +2564,7 @@ dependencies = [
2564
2564
 
2565
2565
  [[package]]
2566
2566
  name = "pake"
2567
- version = "3.11.7"
2567
+ version = "3.11.9"
2568
2568
  dependencies = [
2569
2569
  "objc2",
2570
2570
  "objc2-app-kit",
@@ -1,9 +1,9 @@
1
1
  [package]
2
2
  name = "pake"
3
- version = "3.11.7"
3
+ version = "3.11.9"
4
4
  description = "๐Ÿคฑ๐Ÿป Turn any webpage into a desktop app with Rust."
5
5
  authors = ["Tw93"]
6
- license = "MIT"
6
+ license = "GPL-3.0-or-later"
7
7
  repository = "https://github.com/tw93/Pake"
8
8
  edition = "2021"
9
9
  rust-version = "1.85.0"
@@ -1,5 +1,5 @@
1
1
  use crate::util::{check_file_or_append, get_download_message_with_lang, show_toast, MessageType};
2
- use std::fs::{self, File};
2
+ use std::fs::File;
3
3
  use std::io::Write;
4
4
  use std::str::FromStr;
5
5
  use std::sync::atomic::{AtomicI64, Ordering};
@@ -73,13 +73,6 @@ pub struct DownloadFileParams {
73
73
  language: Option<String>,
74
74
  }
75
75
 
76
- #[derive(serde::Deserialize)]
77
- pub struct BinaryDownloadParams {
78
- filename: String,
79
- binary: Vec<u8>,
80
- language: Option<String>,
81
- }
82
-
83
76
  #[derive(serde::Deserialize)]
84
77
  pub struct NotificationParams {
85
78
  title: String,
@@ -147,47 +140,6 @@ pub async fn download_file(app: AppHandle, params: DownloadFileParams) -> Result
147
140
  }
148
141
  }
149
142
 
150
- #[command]
151
- pub async fn download_file_by_binary(
152
- app: AppHandle,
153
- params: BinaryDownloadParams,
154
- ) -> Result<(), String> {
155
- let window: WebviewWindow = app.get_webview_window("pake").ok_or("Window not found")?;
156
-
157
- show_toast(
158
- &window,
159
- &get_download_message_with_lang(MessageType::Start, params.language.clone()),
160
- );
161
-
162
- let download_dir = app
163
- .path()
164
- .download_dir()
165
- .map_err(|e| format!("Failed to get download dir: {}", e))?;
166
-
167
- let output_path = download_dir.join(&params.filename);
168
-
169
- let path_str = output_path.to_str().ok_or("Invalid output path")?;
170
-
171
- let file_path = check_file_or_append(path_str);
172
-
173
- match fs::write(file_path, &params.binary) {
174
- Ok(_) => {
175
- show_toast(
176
- &window,
177
- &get_download_message_with_lang(MessageType::Success, params.language.clone()),
178
- );
179
- Ok(())
180
- }
181
- Err(e) => {
182
- show_toast(
183
- &window,
184
- &get_download_message_with_lang(MessageType::Failure, params.language),
185
- );
186
- Err(e.to_string())
187
- }
188
- }
189
- }
190
-
191
143
  #[command]
192
144
  pub fn send_notification(app: AppHandle, params: NotificationParams) -> Result<(), String> {
193
145
  use tauri_plugin_notification::NotificationExt;