pake-cli 3.11.10 โ†’ 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.10";
23
+ var version = "3.12.0";
24
24
  var description = "๐Ÿคฑ๐Ÿป Turn any webpage into a desktop app with one command. ๐Ÿคฑ๐Ÿป ไธ€้”ฎๆ‰“ๅŒ…็ฝ‘้กต็”Ÿๆˆ่ฝป้‡ๆกŒ้ขๅบ”็”จใ€‚";
25
25
  var engines = {
26
26
  node: ">=18.0.0"
@@ -431,13 +431,14 @@ function needsTemporaryDebForZst(targets) {
431
431
  */
432
432
  function buildWindowConfigOverrides(options, platform = asSupportedPlatform(process.platform)) {
433
433
  const platformHideOnClose = options.hideOnClose ?? platform === 'darwin';
434
+ const platformHideTitleBar = platform === 'darwin' ? options.hideTitleBar : false;
434
435
  return {
435
436
  width: options.width,
436
437
  height: options.height,
437
438
  fullscreen: options.fullscreen,
438
439
  maximize: options.maximize,
439
440
  resizable: options.resizable ?? true,
440
- hide_title_bar: options.hideTitleBar,
441
+ hide_title_bar: platformHideTitleBar,
441
442
  activation_shortcut: options.activationShortcut,
442
443
  always_on_top: options.alwaysOnTop,
443
444
  dark_mode: options.darkMode,
@@ -712,6 +713,9 @@ async function mergeConfig(url, options, tauriConf) {
712
713
  await copyTemplateConfigs();
713
714
  const { appVersion, userAgent, showSystemTray, useLocalFile, identifier, name = 'pake-app', installerLanguage, wasm, camera, microphone, } = options;
714
715
  const platform = asSupportedPlatform(process.platform);
716
+ if (options.hideTitleBar && platform !== 'darwin') {
717
+ logger.warn('โœผ --hide-title-bar is only supported on macOS and will be ignored on this platform.');
718
+ }
715
719
  const tauriConfWindowOptions = buildWindowConfigOverrides(options, platform);
716
720
  Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions });
717
721
  tauriConf.productName = name;
@@ -1162,14 +1166,22 @@ class BaseBuilder {
1162
1166
  return 0; // Disable proxy feature if version detection fails
1163
1167
  }
1164
1168
  }
1169
+ getCargoTargetDir() {
1170
+ return process.env.CARGO_TARGET_DIR || path.join('src-tauri', 'target');
1171
+ }
1172
+ resolveBuildPath(npmDirectory, buildPath) {
1173
+ return path.isAbsolute(buildPath)
1174
+ ? buildPath
1175
+ : path.join(npmDirectory, buildPath);
1176
+ }
1165
1177
  getBasePath() {
1166
1178
  const basePath = this.options.debug ? 'debug' : 'release';
1167
- return `src-tauri/target/${basePath}/bundle/`;
1179
+ return path.join(this.getCargoTargetDir(), basePath, 'bundle');
1168
1180
  }
1169
1181
  getBuildAppPath(npmDirectory, fileName, fileType) {
1170
1182
  // For app bundles on macOS, the directory is 'macos', not 'app'
1171
1183
  const bundleDir = fileType.toLowerCase() === 'app' ? 'macos' : fileType.toLowerCase();
1172
- return path.join(npmDirectory, this.getBasePath(), bundleDir, `${fileName}.${fileType}`);
1184
+ return path.join(this.resolveBuildPath(npmDirectory, this.getBasePath()), bundleDir, `${fileName}.${fileType}`);
1173
1185
  }
1174
1186
  /**
1175
1187
  * Copy raw binary file to output directory
@@ -1196,9 +1208,9 @@ class BaseBuilder {
1196
1208
  const binaryName = this.getBinaryName(appName);
1197
1209
  // Handle cross-platform builds
1198
1210
  if (this.options.multiArch || this.hasArchSpecificTarget()) {
1199
- return path.join(npmDirectory, this.getArchSpecificPath(), basePath, binaryName);
1211
+ return path.join(this.resolveBuildPath(npmDirectory, this.getArchSpecificPath()), basePath, binaryName);
1200
1212
  }
1201
- return path.join(npmDirectory, 'src-tauri/target', basePath, binaryName);
1213
+ return path.join(this.resolveBuildPath(npmDirectory, this.getCargoTargetDir()), basePath, binaryName);
1202
1214
  }
1203
1215
  /**
1204
1216
  * Get the output path for the raw binary file
@@ -1229,7 +1241,7 @@ class BaseBuilder {
1229
1241
  * Get architecture-specific path for binary
1230
1242
  */
1231
1243
  getArchSpecificPath() {
1232
- return 'src-tauri/target'; // Override in subclasses if needed
1244
+ return this.getCargoTargetDir(); // Override in subclasses if needed
1233
1245
  }
1234
1246
  }
1235
1247
  BaseBuilder.ARCH_MAPPINGS = {
@@ -1315,7 +1327,10 @@ class MacBuilder extends BaseBuilder {
1315
1327
  const basePath = this.options.debug ? 'debug' : 'release';
1316
1328
  const actualArch = this.getActualArch();
1317
1329
  const target = this.getTauriTarget(actualArch, 'darwin');
1318
- return `src-tauri/target/${target}/${basePath}/bundle`;
1330
+ if (!target) {
1331
+ throw new Error(`Unsupported architecture: ${actualArch} for macOS`);
1332
+ }
1333
+ return path.join(this.getCargoTargetDir(), target, basePath, 'bundle');
1319
1334
  }
1320
1335
  hasArchSpecificTarget() {
1321
1336
  return true;
@@ -1323,7 +1338,10 @@ class MacBuilder extends BaseBuilder {
1323
1338
  getArchSpecificPath() {
1324
1339
  const actualArch = this.getActualArch();
1325
1340
  const target = this.getTauriTarget(actualArch, 'darwin');
1326
- return `src-tauri/target/${target}`;
1341
+ if (!target) {
1342
+ throw new Error(`Unsupported architecture: ${actualArch} for macOS`);
1343
+ }
1344
+ return path.join(this.getCargoTargetDir(), target);
1327
1345
  }
1328
1346
  }
1329
1347
 
@@ -1354,14 +1372,26 @@ class WinBuilder extends BaseBuilder {
1354
1372
  getBasePath() {
1355
1373
  const basePath = this.options.debug ? 'debug' : 'release';
1356
1374
  const target = this.getTauriTarget(this.buildArch, 'win32');
1357
- return `src-tauri/target/${target}/${basePath}/bundle/`;
1375
+ if (!target) {
1376
+ throw new Error(`Unsupported architecture: ${this.buildArch} for Windows`);
1377
+ }
1378
+ return path.join(this.getCargoTargetDir(), target, basePath, 'bundle');
1358
1379
  }
1359
1380
  hasArchSpecificTarget() {
1360
1381
  return true;
1361
1382
  }
1362
1383
  getArchSpecificPath() {
1363
1384
  const target = this.getTauriTarget(this.buildArch, 'win32');
1364
- return `src-tauri/target/${target}`;
1385
+ if (!target) {
1386
+ throw new Error(`Unsupported architecture: ${this.buildArch} for Windows`);
1387
+ }
1388
+ return path.join(this.getCargoTargetDir(), target);
1389
+ }
1390
+ getRawBinaryPath(appName) {
1391
+ return `${appName}.exe`;
1392
+ }
1393
+ getBinaryName(appName) {
1394
+ return `pake-${generateIdentifierSafeName(appName)}.exe`;
1365
1395
  }
1366
1396
  }
1367
1397
 
@@ -1556,7 +1586,10 @@ post_remove() {
1556
1586
  const basePath = this.options.debug ? 'debug' : 'release';
1557
1587
  if (this.buildArch === 'arm64') {
1558
1588
  const target = this.getTauriTarget(this.buildArch, 'linux');
1559
- return `src-tauri/target/${target}/${basePath}/bundle/`;
1589
+ if (!target) {
1590
+ throw new Error(`Unsupported architecture: ${this.buildArch} for Linux`);
1591
+ }
1592
+ return path.join(this.getCargoTargetDir(), target, basePath, 'bundle');
1560
1593
  }
1561
1594
  return super.getBasePath();
1562
1595
  }
@@ -1572,7 +1605,10 @@ post_remove() {
1572
1605
  getArchSpecificPath() {
1573
1606
  if (this.buildArch === 'arm64') {
1574
1607
  const target = this.getTauriTarget(this.buildArch, 'linux');
1575
- return `src-tauri/target/${target}`;
1608
+ if (!target) {
1609
+ throw new Error(`Unsupported architecture: ${this.buildArch} for Linux`);
1610
+ }
1611
+ return path.join(this.getCargoTargetDir(), target);
1576
1612
  }
1577
1613
  return super.getArchSpecificPath();
1578
1614
  }
@@ -2410,6 +2446,20 @@ function normalizeUrl(urlToNormalize) {
2410
2446
  throw new Error(`Your url "${urlWithProtocol}" is invalid: ${err.message}`);
2411
2447
  }
2412
2448
  }
2449
+ // Compiles a comma-separated domain list into a regex source for
2450
+ // internal_url_regex. Each domain is escaped and matched against the URL host
2451
+ // and its subdomains so path or query text cannot accidentally opt a link in.
2452
+ // Returns '' for empty input.
2453
+ function safeDomainsToRegex(domains) {
2454
+ const escaped = domains
2455
+ .split(',')
2456
+ .map((domain) => domain.trim().toLowerCase())
2457
+ .filter(Boolean)
2458
+ .map((domain) => domain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
2459
+ return escaped.length
2460
+ ? `^https?:\\/\\/(?:[^/?#@]+\\.)*(?:${escaped.join('|')})(?::\\d+)?(?:[/?#]|$)`
2461
+ : '';
2462
+ }
2413
2463
 
2414
2464
  /**
2415
2465
  * Error class used for user-facing CLI errors.
@@ -2490,6 +2540,10 @@ async function handleOptions(options, url) {
2490
2540
  name: resolvedName,
2491
2541
  identifier: resolveIdentifier(url, options.name, options.identifier),
2492
2542
  };
2543
+ // --safe-domain is sugar over --internal-url-regex; an explicit regex wins.
2544
+ if (!options.internalUrlRegex && options.safeDomain) {
2545
+ appOptions.internalUrlRegex = safeDomainsToRegex(options.safeDomain);
2546
+ }
2493
2547
  const iconPath = await handleIcon(appOptions, url);
2494
2548
  appOptions.icon = iconPath || '';
2495
2549
  return appOptions;
@@ -2538,6 +2592,7 @@ const DEFAULT_PAKE_OPTIONS = {
2538
2592
  startToTray: false,
2539
2593
  forceInternalNavigation: false,
2540
2594
  internalUrlRegex: '',
2595
+ safeDomain: '',
2541
2596
  enableFind: false,
2542
2597
  iterativeBuild: false,
2543
2598
  zoom: 100,
@@ -2590,6 +2645,7 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
2590
2645
  return program$1
2591
2646
  .addHelpText('beforeAll', logo)
2592
2647
  .usage(`[url] [options]`)
2648
+ .helpOption('-h, --help', 'Show all CLI options')
2593
2649
  .showHelpAfterError()
2594
2650
  .argument('[url]', 'The web URL you want to package', validateUrlInput)
2595
2651
  .option('--name <string>', 'Application name')
@@ -2678,12 +2734,9 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
2678
2734
  .addOption(new Option('--start-to-tray', 'Start app minimized to tray')
2679
2735
  .default(DEFAULT_PAKE_OPTIONS.startToTray)
2680
2736
  .hideHelp())
2681
- .addOption(new Option('--force-internal-navigation', 'Keep every link inside the Pake window instead of opening external handlers')
2682
- .default(DEFAULT_PAKE_OPTIONS.forceInternalNavigation)
2683
- .hideHelp())
2684
- .addOption(new Option('--internal-url-regex <string>', 'Regex pattern to match URLs that should be considered internal')
2685
- .default(DEFAULT_PAKE_OPTIONS.internalUrlRegex)
2686
- .hideHelp())
2737
+ .addOption(new Option('--force-internal-navigation', 'Keep every link inside the Pake window instead of opening external handlers').default(DEFAULT_PAKE_OPTIONS.forceInternalNavigation))
2738
+ .addOption(new Option('--internal-url-regex <string>', 'Regex pattern to match URLs that should be considered internal').default(DEFAULT_PAKE_OPTIONS.internalUrlRegex))
2739
+ .addOption(new Option('--safe-domain <domains>', 'Comma-separated domains kept inside the app (e.g. SSO/workspace callbacks)').default(DEFAULT_PAKE_OPTIONS.safeDomain))
2687
2740
  .addOption(new Option('--enable-find', 'Enable in-page Find UI with Cmd/Ctrl+F/G shortcuts')
2688
2741
  .default(DEFAULT_PAKE_OPTIONS.enableFind)
2689
2742
  .hideHelp())
@@ -2714,9 +2767,7 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
2714
2767
  .addOption(new Option('--iterative-build', 'Turn on rapid build mode (app only, no dmg/deb/msi), good for debugging')
2715
2768
  .default(DEFAULT_PAKE_OPTIONS.iterativeBuild)
2716
2769
  .hideHelp())
2717
- .addOption(new Option('--new-window', 'Allow sites to open new windows (for auth flows, tabs, branches)')
2718
- .default(DEFAULT_PAKE_OPTIONS.newWindow)
2719
- .hideHelp())
2770
+ .addOption(new Option('--new-window', 'Allow sites to open new windows (for auth flows, tabs, branches)').default(DEFAULT_PAKE_OPTIONS.newWindow))
2720
2771
  .addOption(new Option('--install', 'Auto-install app to /Applications (macOS) after build and remove local bundle')
2721
2772
  .default(DEFAULT_PAKE_OPTIONS.install)
2722
2773
  .hideHelp())
@@ -2729,14 +2780,19 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
2729
2780
  .version(packageJson.version, '-v, --version')
2730
2781
  .configureHelp({
2731
2782
  sortSubcommands: true,
2783
+ visibleOptions: (command) => {
2784
+ const options = [...command.options];
2785
+ const helpOption = command
2786
+ ._helpOption;
2787
+ if (helpOption) {
2788
+ options.push(helpOption);
2789
+ }
2790
+ return options;
2791
+ },
2732
2792
  optionTerm: (option) => {
2733
- if (option.flags === '-v, --version' || option.flags === '-h, --help')
2734
- return '';
2735
2793
  return option.flags;
2736
2794
  },
2737
2795
  optionDescription: (option) => {
2738
- if (option.flags === '-v, --version' || option.flags === '-h, --help')
2739
- return '';
2740
2796
  return option.description;
2741
2797
  },
2742
2798
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pake-cli",
3
- "version": "3.11.10",
3
+ "version": "3.12.0",
4
4
  "description": "๐Ÿคฑ๐Ÿป Turn any webpage into a desktop app with one command. ๐Ÿคฑ๐Ÿป ไธ€้”ฎๆ‰“ๅŒ…็ฝ‘้กต็”Ÿๆˆ่ฝป้‡ๆกŒ้ขๅบ”็”จใ€‚",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -2564,7 +2564,7 @@ dependencies = [
2564
2564
 
2565
2565
  [[package]]
2566
2566
  name = "pake"
2567
- version = "3.11.10"
2567
+ version = "3.12.0"
2568
2568
  dependencies = [
2569
2569
  "objc2",
2570
2570
  "objc2-app-kit",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "pake"
3
- version = "3.11.10"
3
+ version = "3.12.0"
4
4
  description = "๐Ÿคฑ๐Ÿป Turn any webpage into a desktop app with Rust."
5
5
  authors = ["Tw93"]
6
6
  license = "GPL-3.0-or-later"
@@ -27,12 +27,26 @@ function matchesAuthUrl(url, baseUrl = window.location.href) {
27
27
  /\/o\/oauth2/,
28
28
  ];
29
29
 
30
- const isMatch = oauthPatterns.some(
31
- (pattern) =>
32
- pattern.test(hostname) ||
33
- pattern.test(pathname) ||
34
- pattern.test(fullUrl),
35
- );
30
+ // Enterprise SSO. Match identity providers on the host, and SAML/SSO/ADFS on
31
+ // the pathname with endpoint-shaped patterns only, so ordinary pages such as
32
+ // /settings/sso/providers (or a query string carrying an SSO URL) are not
33
+ // misread as authentication.
34
+ const enterpriseHostPatterns = [/(^|\.)okta\.com$/, /(^|\.)onelogin\.com$/];
35
+ const enterprisePathPatterns = [
36
+ /\/saml2?\/(sso|acs|login|metadata|consume|redirect|callback|continue)/,
37
+ /\/sso\/(saml|oidc|oauth|login|authorize|redirect|callback|acs|start|continue|metadata)/,
38
+ /\/adfs\/ls\b/,
39
+ ];
40
+
41
+ const isMatch =
42
+ oauthPatterns.some(
43
+ (pattern) =>
44
+ pattern.test(hostname) ||
45
+ pattern.test(pathname) ||
46
+ pattern.test(fullUrl),
47
+ ) ||
48
+ enterpriseHostPatterns.some((pattern) => pattern.test(hostname)) ||
49
+ enterprisePathPatterns.some((pattern) => pattern.test(pathname));
36
50
 
37
51
  if (isMatch) {
38
52
  console.log("[Pake] OAuth URL detected:", url);
@@ -459,20 +459,23 @@ document.addEventListener("DOMContentLoaded", () => {
459
459
  const absoluteUrl = hrefUrl.href;
460
460
  let filename = anchorElement.download || getFilenameFromUrl(absoluteUrl);
461
461
 
462
- // Keep OAuth/authentication flows inside the app when popup support is enabled.
462
+ // Keep OAuth/authentication flows inside the app. Without --new-window,
463
+ // navigate in place so the SSO redirect chain and callback stay in the
464
+ // webview instead of falling through to the system browser.
463
465
  if (window.isAuthLink(absoluteUrl)) {
464
466
  console.log("[Pake] Handling OAuth navigation in-app:", absoluteUrl);
467
+ e.preventDefault();
468
+ e.stopImmediatePropagation();
465
469
 
466
470
  if (window.pakeConfig?.new_window) {
467
- e.preventDefault();
468
- e.stopImmediatePropagation();
469
-
470
471
  openAuthNavigation(
471
472
  originalWindowOpen,
472
473
  absoluteUrl,
473
474
  "_blank",
474
475
  "width=1200,height=800,scrollbars=yes,resizable=yes",
475
476
  );
477
+ } else {
478
+ window.location.href = absoluteUrl;
476
479
  }
477
480
 
478
481
  return;
@@ -488,7 +491,15 @@ document.addEventListener("DOMContentLoaded", () => {
488
491
  }
489
492
 
490
493
  if (isInternalUrl(absoluteUrl)) {
491
- // For internal links (based on regex or domain), let the browser handle it naturally
494
+ // With --new-window the Rust on_new_window handler opens an in-app
495
+ // window; without it, deferring to the native handler sends the
496
+ // _blank target to the system browser and strands SSO callbacks.
497
+ // Navigate in place so internal links stay inside the webview.
498
+ if (!window.pakeConfig?.new_window) {
499
+ e.preventDefault();
500
+ e.stopImmediatePropagation();
501
+ window.location.href = absoluteUrl;
502
+ }
492
503
  return;
493
504
  }
494
505
 
@@ -592,6 +603,14 @@ document.addEventListener("DOMContentLoaded", () => {
592
603
  return null;
593
604
  }
594
605
 
606
+ // With --new-window the native handler opens an in-app window; without it,
607
+ // originalWindowOpen would route the internal target to the system browser
608
+ // and strand SSO callbacks, so navigate in place instead.
609
+ if (!window.pakeConfig?.new_window) {
610
+ window.location.href = absoluteUrl;
611
+ return window;
612
+ }
613
+
595
614
  return originalWindowOpen.call(window, absoluteUrl, name, specs);
596
615
  } catch (error) {
597
616
  return originalWindowOpen.call(window, url, name, specs);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "productName": "Weekly",
3
3
  "identifier": "com.pake.weekly",
4
- "version": "3.11.10",
4
+ "version": "3.12.0",
5
5
  "app": {
6
6
  "withGlobalTauri": true,
7
7
  "trayIcon": {