pake-cli 3.11.1 โ 3.11.3
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 +4 -5
- package/dist/cli.js +406 -269
- package/package.json +3 -3
- package/src-tauri/Cargo.lock +1 -3
- package/src-tauri/Cargo.toml +3 -3
- package/src-tauri/pake.json +3 -3
- package/src-tauri/src/app/setup.rs +37 -29
- package/src-tauri/src/app/window.rs +44 -13
- package/src-tauri/src/inject/event.js +59 -23
- package/src-tauri/src/inject/style.js +31 -6
- package/src-tauri/src/lib.rs +8 -9
- package/src-tauri/tauri.conf.json +1 -1
- package/dist/code-review-graph.js +0 -283
- package/src-tauri/.pake/pake.json +0 -45
- package/src-tauri/.pake/tauri.conf.json +0 -46
- package/src-tauri/.pake/tauri.linux.conf.json +0 -12
- package/src-tauri/.pake/tauri.macos.conf.json +0 -32
- package/src-tauri/.pake/tauri.windows.conf.json +0 -15
package/dist/cli.js
CHANGED
|
@@ -13,16 +13,17 @@ import ora from 'ora';
|
|
|
13
13
|
import dns from 'dns';
|
|
14
14
|
import http from 'http';
|
|
15
15
|
import { promisify } from 'util';
|
|
16
|
-
import fs from 'fs';
|
|
16
|
+
import fs from 'fs/promises';
|
|
17
17
|
import { dir } from 'tmp-promise';
|
|
18
18
|
import { fileTypeFromBuffer } from 'file-type';
|
|
19
19
|
import icongen from 'icon-gen';
|
|
20
20
|
import sharp from 'sharp';
|
|
21
21
|
import * as psl from 'psl';
|
|
22
22
|
import { InvalidArgumentError, program as program$1, Option } from 'commander';
|
|
23
|
+
import fs$1 from 'fs';
|
|
23
24
|
|
|
24
25
|
var name = "pake-cli";
|
|
25
|
-
var version = "3.11.
|
|
26
|
+
var version = "3.11.3";
|
|
26
27
|
var description = "๐คฑ๐ป Turn any webpage into a desktop app with one command. ๐คฑ๐ป ไธ้ฎๆๅ
็ฝ้กต็ๆ่ฝป้ๆก้ขๅบ็จใ";
|
|
27
28
|
var engines = {
|
|
28
29
|
node: ">=18.0.0"
|
|
@@ -95,7 +96,6 @@ var devDependencies = {
|
|
|
95
96
|
"@rollup/plugin-terser": "^0.4.4",
|
|
96
97
|
"@types/fs-extra": "^11.0.4",
|
|
97
98
|
"@types/node": "^25.3.2",
|
|
98
|
-
"@types/page-icon": "^0.3.6",
|
|
99
99
|
"@types/prompts": "^2.4.9",
|
|
100
100
|
"@types/tmp": "^0.2.6",
|
|
101
101
|
"@types/update-notifier": "^6.0.8",
|
|
@@ -110,7 +110,8 @@ var devDependencies = {
|
|
|
110
110
|
};
|
|
111
111
|
var pnpm = {
|
|
112
112
|
overrides: {
|
|
113
|
-
sharp: "^0.34.5"
|
|
113
|
+
sharp: "^0.34.5",
|
|
114
|
+
"@img/sharp-libvips-darwin-arm64": "1.2.4"
|
|
114
115
|
},
|
|
115
116
|
onlyBuiltDependencies: [
|
|
116
117
|
"esbuild",
|
|
@@ -289,7 +290,7 @@ const logger = {
|
|
|
289
290
|
log.error(...msg.map((m) => chalk.red(m)));
|
|
290
291
|
},
|
|
291
292
|
warn(...msg) {
|
|
292
|
-
log.
|
|
293
|
+
log.warn(...msg.map((m) => chalk.yellow(m)));
|
|
293
294
|
},
|
|
294
295
|
success(...msg) {
|
|
295
296
|
log.info(...msg.map((m) => chalk.green(m)));
|
|
@@ -423,22 +424,22 @@ function checkRustInstalled() {
|
|
|
423
424
|
}
|
|
424
425
|
|
|
425
426
|
async function combineFiles(files, output) {
|
|
426
|
-
const contents = files.map((file) => {
|
|
427
|
+
const contents = await Promise.all(files.map(async (file) => {
|
|
427
428
|
if (file.endsWith('.css')) {
|
|
428
|
-
const fileContent = fs.
|
|
429
|
+
const fileContent = await fs.readFile(file, 'utf-8');
|
|
429
430
|
return `window.addEventListener('DOMContentLoaded', (_event) => {
|
|
430
431
|
const css = ${JSON.stringify(fileContent)};
|
|
431
432
|
const style = document.createElement('style');
|
|
432
|
-
style.
|
|
433
|
+
style.textContent = css;
|
|
433
434
|
document.head.appendChild(style);
|
|
434
435
|
});`;
|
|
435
436
|
}
|
|
436
|
-
const fileContent = fs.
|
|
437
|
+
const fileContent = await fs.readFile(file);
|
|
437
438
|
return ("window.addEventListener('DOMContentLoaded', (_event) => { " +
|
|
438
439
|
fileContent +
|
|
439
440
|
' });');
|
|
440
|
-
});
|
|
441
|
-
fs.
|
|
441
|
+
}));
|
|
442
|
+
await fs.writeFile(output, contents.join('\n'));
|
|
442
443
|
return files;
|
|
443
444
|
}
|
|
444
445
|
|
|
@@ -479,11 +480,15 @@ function generateIdentifierSafeName(name) {
|
|
|
479
480
|
return cleaned;
|
|
480
481
|
}
|
|
481
482
|
|
|
482
|
-
|
|
483
|
-
|
|
483
|
+
function asSupportedPlatform(platform) {
|
|
484
|
+
if (platform !== 'win32' && platform !== 'darwin' && platform !== 'linux') {
|
|
485
|
+
throw new Error(`Pake only supports win32, darwin, and linux; detected '${platform}'.`);
|
|
486
|
+
}
|
|
487
|
+
return platform;
|
|
488
|
+
}
|
|
489
|
+
async function copyTemplateConfigs() {
|
|
484
490
|
const srcTauriDir = path.join(npmDirectory, 'src-tauri');
|
|
485
491
|
await fsExtra.ensureDir(tauriConfigDirectory);
|
|
486
|
-
// Copy source config files to .pake directory (as templates)
|
|
487
492
|
const sourceFiles = [
|
|
488
493
|
'tauri.conf.json',
|
|
489
494
|
'tauri.macos.conf.json',
|
|
@@ -499,51 +504,11 @@ async function mergeConfig(url, options, tauriConf) {
|
|
|
499
504
|
await fsExtra.copy(sourcePath, destPath);
|
|
500
505
|
}
|
|
501
506
|
}));
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const platformHideOnClose = hideOnClose ?? platform === 'darwin';
|
|
505
|
-
const tauriConfWindowOptions = {
|
|
506
|
-
width,
|
|
507
|
-
height,
|
|
508
|
-
fullscreen,
|
|
509
|
-
maximize,
|
|
510
|
-
resizable,
|
|
511
|
-
hide_title_bar: hideTitleBar,
|
|
512
|
-
activation_shortcut: activationShortcut,
|
|
513
|
-
always_on_top: alwaysOnTop,
|
|
514
|
-
dark_mode: darkMode,
|
|
515
|
-
disabled_web_shortcuts: disabledWebShortcuts,
|
|
516
|
-
hide_on_close: platformHideOnClose,
|
|
517
|
-
incognito: incognito,
|
|
518
|
-
title: title,
|
|
519
|
-
enable_wasm: wasm,
|
|
520
|
-
enable_drag_drop: enableDragDrop,
|
|
521
|
-
start_to_tray: startToTray && showSystemTray,
|
|
522
|
-
force_internal_navigation: forceInternalNavigation,
|
|
523
|
-
internal_url_regex: internalUrlRegex,
|
|
524
|
-
zoom,
|
|
525
|
-
min_width: minWidth,
|
|
526
|
-
min_height: minHeight,
|
|
527
|
-
ignore_certificate_errors: ignoreCertificateErrors,
|
|
528
|
-
new_window: newWindow,
|
|
529
|
-
};
|
|
530
|
-
Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions });
|
|
531
|
-
tauriConf.productName = name;
|
|
532
|
-
tauriConf.identifier = identifier;
|
|
533
|
-
tauriConf.version = appVersion;
|
|
534
|
-
// Always set mainBinaryName to ensure binary uniqueness
|
|
535
|
-
const linuxBinaryName = `pake-${generateLinuxPackageName(name)}`;
|
|
536
|
-
tauriConf.mainBinaryName =
|
|
537
|
-
platform === 'linux'
|
|
538
|
-
? linuxBinaryName
|
|
539
|
-
: `pake-${generateIdentifierSafeName(name)}`;
|
|
540
|
-
if (platform == 'win32') {
|
|
541
|
-
tauriConf.bundle.windows.wix.language[0] = installerLanguage;
|
|
542
|
-
}
|
|
507
|
+
}
|
|
508
|
+
async function handleLocalFile(url, useLocalFile, tauriConf) {
|
|
543
509
|
const pathExists = await fsExtra.pathExists(url);
|
|
544
510
|
if (pathExists) {
|
|
545
511
|
logger.warn('โผ Your input might be a local file.');
|
|
546
|
-
tauriConf.pake.windows[0].url_type = 'local';
|
|
547
512
|
const fileName = path.basename(url);
|
|
548
513
|
const dirName = path.dirname(url);
|
|
549
514
|
const distDir = path.join(npmDirectory, 'dist');
|
|
@@ -555,8 +520,6 @@ async function mergeConfig(url, options, tauriConf) {
|
|
|
555
520
|
else {
|
|
556
521
|
fsExtra.moveSync(distDir, distBakDir, { overwrite: true });
|
|
557
522
|
fsExtra.copySync(dirName, distDir, { overwrite: true });
|
|
558
|
-
// ignore it, because about_pake.html have be erased.
|
|
559
|
-
// const filesToCopyBack = ['cli.js', 'about_pake.html'];
|
|
560
523
|
const filesToCopyBack = ['cli.js'];
|
|
561
524
|
await Promise.all(filesToCopyBack.map((file) => fsExtra.copy(path.join(distBakDir, file), path.join(distDir, file))));
|
|
562
525
|
}
|
|
@@ -566,28 +529,19 @@ async function mergeConfig(url, options, tauriConf) {
|
|
|
566
529
|
else {
|
|
567
530
|
tauriConf.pake.windows[0].url_type = 'web';
|
|
568
531
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
delete tauriConf.bundle.linux.deb.files;
|
|
583
|
-
// Generate correct desktop file configuration
|
|
584
|
-
const linuxName = generateLinuxPackageName(name);
|
|
585
|
-
const desktopFileName = `com.pake.${linuxName}.desktop`;
|
|
586
|
-
const iconName = `${linuxName}_512`;
|
|
587
|
-
// Create desktop file content
|
|
588
|
-
// Determine if title contains Chinese characters for Name[zh_CN]
|
|
589
|
-
const chineseName = title && /[\u4e00-\u9fa5]/.test(title) ? title : null;
|
|
590
|
-
const desktopContent = `[Desktop Entry]
|
|
532
|
+
}
|
|
533
|
+
async function mergeLinuxConfig(options, name, tauriConf, linuxBinaryName) {
|
|
534
|
+
const linuxBundle = tauriConf.bundle.linux;
|
|
535
|
+
if (!linuxBundle) {
|
|
536
|
+
throw new Error('Linux bundle configuration is missing from tauri.linux.conf.json; cannot build Linux target.');
|
|
537
|
+
}
|
|
538
|
+
delete linuxBundle.deb.files;
|
|
539
|
+
const linuxName = generateLinuxPackageName(name);
|
|
540
|
+
const desktopFileName = `com.pake.${linuxName}.desktop`;
|
|
541
|
+
const iconName = `${linuxName}_512`;
|
|
542
|
+
const { title } = options;
|
|
543
|
+
const chineseName = title && /[\u4e00-\u9fa5]/.test(title) ? title : null;
|
|
544
|
+
const desktopContent = `[Desktop Entry]
|
|
591
545
|
Version=1.0
|
|
592
546
|
Type=Application
|
|
593
547
|
Name=${name}
|
|
@@ -600,51 +554,39 @@ MimeType=text/html;text/xml;application/xhtml_xml;
|
|
|
600
554
|
StartupNotify=true
|
|
601
555
|
Terminal=false
|
|
602
556
|
`;
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
};
|
|
614
|
-
// Add desktop file support for RPM
|
|
615
|
-
if (!tauriConf.bundle.linux.rpm) {
|
|
616
|
-
tauriConf.bundle.linux.rpm = {};
|
|
617
|
-
}
|
|
618
|
-
tauriConf.bundle.linux.rpm.files = {
|
|
619
|
-
[desktopInstallPath]: `assets/${desktopFileName}`,
|
|
620
|
-
};
|
|
621
|
-
const validTargets = [
|
|
622
|
-
'deb',
|
|
623
|
-
'appimage',
|
|
624
|
-
'rpm',
|
|
625
|
-
'deb-arm64',
|
|
626
|
-
'appimage-arm64',
|
|
627
|
-
'rpm-arm64',
|
|
628
|
-
];
|
|
629
|
-
const baseTarget = options.targets.includes('-arm64')
|
|
630
|
-
? options.targets.replace('-arm64', '')
|
|
631
|
-
: options.targets;
|
|
632
|
-
if (validTargets.includes(options.targets)) {
|
|
633
|
-
tauriConf.bundle.targets = [baseTarget];
|
|
634
|
-
}
|
|
635
|
-
else {
|
|
636
|
-
logger.warn(`โผ The target must be one of ${validTargets.join(', ')}, the default 'deb' will be used.`);
|
|
637
|
-
}
|
|
557
|
+
const srcAssetsDir = path.join(npmDirectory, 'src-tauri/assets');
|
|
558
|
+
const srcDesktopFilePath = path.join(srcAssetsDir, desktopFileName);
|
|
559
|
+
await fsExtra.ensureDir(srcAssetsDir);
|
|
560
|
+
await fsExtra.writeFile(srcDesktopFilePath, desktopContent);
|
|
561
|
+
const desktopInstallPath = `/usr/share/applications/${desktopFileName}`;
|
|
562
|
+
linuxBundle.deb.files = {
|
|
563
|
+
[desktopInstallPath]: `assets/${desktopFileName}`,
|
|
564
|
+
};
|
|
565
|
+
if (!linuxBundle.rpm) {
|
|
566
|
+
linuxBundle.rpm = {};
|
|
638
567
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
568
|
+
linuxBundle.rpm.files = {
|
|
569
|
+
[desktopInstallPath]: `assets/${desktopFileName}`,
|
|
570
|
+
};
|
|
571
|
+
const validTargets = [
|
|
572
|
+
'deb',
|
|
573
|
+
'appimage',
|
|
574
|
+
'rpm',
|
|
575
|
+
'deb-arm64',
|
|
576
|
+
'appimage-arm64',
|
|
577
|
+
'rpm-arm64',
|
|
578
|
+
];
|
|
579
|
+
const baseTarget = options.targets.includes('-arm64')
|
|
580
|
+
? options.targets.replace('-arm64', '')
|
|
581
|
+
: options.targets;
|
|
582
|
+
if (validTargets.includes(options.targets)) {
|
|
583
|
+
tauriConf.bundle.targets = [baseTarget];
|
|
645
584
|
}
|
|
646
|
-
|
|
647
|
-
|
|
585
|
+
else {
|
|
586
|
+
logger.warn(`โผ The target must be one of ${validTargets.join(', ')}, the default 'deb' will be used.`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
async function mergeIcons(options, name, tauriConf, platform, safeAppName) {
|
|
648
590
|
const platformIconMap = {
|
|
649
591
|
win32: {
|
|
650
592
|
fileExt: '.ico',
|
|
@@ -670,7 +612,7 @@ Terminal=false
|
|
|
670
612
|
const exists = resolvedIconPath && (await fsExtra.pathExists(resolvedIconPath));
|
|
671
613
|
if (exists) {
|
|
672
614
|
let updateIconPath = true;
|
|
673
|
-
|
|
615
|
+
const customIconExt = path.extname(resolvedIconPath).toLowerCase();
|
|
674
616
|
if (customIconExt !== iconInfo.fileExt) {
|
|
675
617
|
updateIconPath = false;
|
|
676
618
|
logger.warn(`โผ ${iconInfo.message}, but you give ${customIconExt}`);
|
|
@@ -679,7 +621,6 @@ Terminal=false
|
|
|
679
621
|
else {
|
|
680
622
|
const iconPath = path.join(npmDirectory, 'src-tauri/', iconInfo.path);
|
|
681
623
|
tauriConf.bundle.resources = [iconInfo.path];
|
|
682
|
-
// Avoid copying if source and destination are the same
|
|
683
624
|
const absoluteDestPath = path.resolve(iconPath);
|
|
684
625
|
if (resolvedIconPath !== absoluteDestPath) {
|
|
685
626
|
try {
|
|
@@ -706,37 +647,32 @@ Terminal=false
|
|
|
706
647
|
}
|
|
707
648
|
// Set tray icon path.
|
|
708
649
|
let trayIconPath = platform === 'darwin' ? 'png/icon_512.png' : tauriConf.bundle.icon[0];
|
|
709
|
-
if (systemTrayIcon.length > 0) {
|
|
650
|
+
if (options.systemTrayIcon.length > 0) {
|
|
710
651
|
try {
|
|
711
|
-
await fsExtra.pathExists(systemTrayIcon);
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
if (iconExt == '.png' || iconExt == '.ico') {
|
|
652
|
+
await fsExtra.pathExists(options.systemTrayIcon);
|
|
653
|
+
const iconExt = path.extname(options.systemTrayIcon).toLowerCase();
|
|
654
|
+
if (iconExt === '.png' || iconExt === '.ico') {
|
|
715
655
|
const trayIcoPath = path.join(npmDirectory, `src-tauri/png/${safeAppName}${iconExt}`);
|
|
716
656
|
trayIconPath = `png/${safeAppName}${iconExt}`;
|
|
717
|
-
await fsExtra.copy(systemTrayIcon, trayIcoPath);
|
|
657
|
+
await fsExtra.copy(options.systemTrayIcon, trayIcoPath);
|
|
718
658
|
}
|
|
719
659
|
else {
|
|
720
660
|
logger.warn(`โผ System tray icon must be .ico or .png, but you provided ${iconExt}.`);
|
|
721
661
|
logger.warn(`โผ Default system tray icon will be used.`);
|
|
722
662
|
}
|
|
723
663
|
}
|
|
724
|
-
catch {
|
|
725
|
-
logger.warn(`โผ ${systemTrayIcon}
|
|
664
|
+
catch (err) {
|
|
665
|
+
logger.warn(`โผ Failed to apply system tray icon "${options.systemTrayIcon}": ${err instanceof Error ? err.message : String(err)}`);
|
|
726
666
|
logger.warn(`โผ Default system tray icon will remain unchanged.`);
|
|
727
667
|
}
|
|
728
668
|
}
|
|
729
|
-
// Ensure trayIcon object exists before setting iconPath
|
|
730
|
-
if (!tauriConf.app.trayIcon) {
|
|
731
|
-
tauriConf.app.trayIcon = {};
|
|
732
|
-
}
|
|
733
|
-
tauriConf.app.trayIcon.iconPath = trayIconPath;
|
|
734
669
|
tauriConf.pake.system_tray_path = trayIconPath;
|
|
735
670
|
delete tauriConf.app.trayIcon;
|
|
736
|
-
|
|
737
|
-
|
|
671
|
+
}
|
|
672
|
+
async function injectCustomCode(options, tauriConf) {
|
|
673
|
+
const { inject, proxyUrl, multiInstance, multiWindow, wasm } = options;
|
|
674
|
+
const injectFilePath = path.join(npmDirectory, 'src-tauri/src/inject/custom.js');
|
|
738
675
|
if (inject?.length > 0) {
|
|
739
|
-
// Ensure inject is an array before calling .every()
|
|
740
676
|
const injectArray = Array.isArray(inject) ? inject : [inject];
|
|
741
677
|
if (!injectArray.every((item) => item.endsWith('.css') || item.endsWith('.js'))) {
|
|
742
678
|
logger.error('The injected file must be in either CSS or JS format.');
|
|
@@ -753,7 +689,6 @@ Terminal=false
|
|
|
753
689
|
tauriConf.pake.proxy_url = proxyUrl || '';
|
|
754
690
|
tauriConf.pake.multi_instance = multiInstance;
|
|
755
691
|
tauriConf.pake.multi_window = multiWindow;
|
|
756
|
-
// Configure WASM support with required HTTP headers
|
|
757
692
|
if (wasm) {
|
|
758
693
|
tauriConf.app.security = {
|
|
759
694
|
headers: {
|
|
@@ -762,16 +697,16 @@ Terminal=false
|
|
|
762
697
|
},
|
|
763
698
|
};
|
|
764
699
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
700
|
+
}
|
|
701
|
+
async function generateMacEntitlements(camera, microphone) {
|
|
702
|
+
const entitlementEntries = [];
|
|
703
|
+
if (camera) {
|
|
704
|
+
entitlementEntries.push(' <key>com.apple.security.device.camera</key>\n <true/>');
|
|
705
|
+
}
|
|
706
|
+
if (microphone) {
|
|
707
|
+
entitlementEntries.push(' <key>com.apple.security.device.audio-input</key>\n <true/>');
|
|
708
|
+
}
|
|
709
|
+
const entitlementsContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
775
710
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
776
711
|
<plist version="1.0">
|
|
777
712
|
<dict>
|
|
@@ -779,10 +714,10 @@ ${entitlementEntries.join('\n')}
|
|
|
779
714
|
</dict>
|
|
780
715
|
</plist>
|
|
781
716
|
`;
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
717
|
+
const entitlementsPath = path.join(npmDirectory, 'src-tauri', 'entitlements.plist');
|
|
718
|
+
await fsExtra.writeFile(entitlementsPath, entitlementsContent);
|
|
719
|
+
}
|
|
720
|
+
async function writeAllConfigs(tauriConf, platform) {
|
|
786
721
|
const platformConfigPaths = {
|
|
787
722
|
win32: 'tauri.windows.conf.json',
|
|
788
723
|
darwin: 'tauri.macos.conf.json',
|
|
@@ -793,11 +728,85 @@ ${entitlementEntries.join('\n')}
|
|
|
793
728
|
await fsExtra.outputJSON(configPath, bundleConf, { spaces: 4 });
|
|
794
729
|
const pakeConfigPath = path.join(tauriConfigDirectory, 'pake.json');
|
|
795
730
|
await fsExtra.outputJSON(pakeConfigPath, tauriConf.pake, { spaces: 4 });
|
|
796
|
-
|
|
731
|
+
const tauriConf2 = JSON.parse(JSON.stringify(tauriConf));
|
|
797
732
|
delete tauriConf2.pake;
|
|
798
733
|
const configJsonPath = path.join(tauriConfigDirectory, 'tauri.conf.json');
|
|
799
734
|
await fsExtra.outputJSON(configJsonPath, tauriConf2, { spaces: 4 });
|
|
800
735
|
}
|
|
736
|
+
async function mergeConfig(url, options, tauriConf) {
|
|
737
|
+
await copyTemplateConfigs();
|
|
738
|
+
const { width, height, fullscreen, maximize, hideTitleBar, alwaysOnTop, appVersion, darkMode, disabledWebShortcuts, activationShortcut, userAgent, showSystemTray, useLocalFile, identifier, name = 'pake-app', resizable = true, installerLanguage, hideOnClose, incognito, title, wasm, enableDragDrop, startToTray, forceInternalNavigation, internalUrlRegex, zoom, minWidth, minHeight, ignoreCertificateErrors, newWindow, camera, microphone, } = options;
|
|
739
|
+
const platform = asSupportedPlatform(process.platform);
|
|
740
|
+
const platformHideOnClose = hideOnClose ?? platform === 'darwin';
|
|
741
|
+
const tauriConfWindowOptions = {
|
|
742
|
+
width,
|
|
743
|
+
height,
|
|
744
|
+
fullscreen,
|
|
745
|
+
maximize,
|
|
746
|
+
resizable,
|
|
747
|
+
hide_title_bar: hideTitleBar,
|
|
748
|
+
activation_shortcut: activationShortcut,
|
|
749
|
+
always_on_top: alwaysOnTop,
|
|
750
|
+
dark_mode: darkMode,
|
|
751
|
+
disabled_web_shortcuts: disabledWebShortcuts,
|
|
752
|
+
hide_on_close: platformHideOnClose,
|
|
753
|
+
incognito,
|
|
754
|
+
title,
|
|
755
|
+
enable_wasm: wasm,
|
|
756
|
+
enable_drag_drop: enableDragDrop,
|
|
757
|
+
start_to_tray: startToTray && showSystemTray,
|
|
758
|
+
force_internal_navigation: forceInternalNavigation,
|
|
759
|
+
internal_url_regex: internalUrlRegex,
|
|
760
|
+
zoom,
|
|
761
|
+
min_width: minWidth,
|
|
762
|
+
min_height: minHeight,
|
|
763
|
+
ignore_certificate_errors: ignoreCertificateErrors,
|
|
764
|
+
new_window: newWindow,
|
|
765
|
+
};
|
|
766
|
+
Object.assign(tauriConf.pake.windows[0], { url, ...tauriConfWindowOptions });
|
|
767
|
+
tauriConf.productName = name;
|
|
768
|
+
tauriConf.identifier = identifier;
|
|
769
|
+
tauriConf.version = appVersion;
|
|
770
|
+
const linuxBinaryName = `pake-${generateLinuxPackageName(name)}`;
|
|
771
|
+
tauriConf.mainBinaryName =
|
|
772
|
+
platform === 'linux'
|
|
773
|
+
? linuxBinaryName
|
|
774
|
+
: `pake-${generateIdentifierSafeName(name)}`;
|
|
775
|
+
if (platform === 'win32') {
|
|
776
|
+
const windowsBundle = tauriConf.bundle.windows;
|
|
777
|
+
if (!windowsBundle) {
|
|
778
|
+
throw new Error('Windows bundle configuration is missing from tauri.windows.conf.json; cannot build Windows target.');
|
|
779
|
+
}
|
|
780
|
+
windowsBundle.wix.language[0] = installerLanguage;
|
|
781
|
+
}
|
|
782
|
+
await handleLocalFile(url, useLocalFile, tauriConf);
|
|
783
|
+
const platformMap = {
|
|
784
|
+
win32: 'windows',
|
|
785
|
+
linux: 'linux',
|
|
786
|
+
darwin: 'macos',
|
|
787
|
+
};
|
|
788
|
+
const currentPlatform = platformMap[platform];
|
|
789
|
+
if (userAgent.length > 0) {
|
|
790
|
+
tauriConf.pake.user_agent[currentPlatform] = userAgent;
|
|
791
|
+
}
|
|
792
|
+
tauriConf.pake.system_tray[currentPlatform] = showSystemTray;
|
|
793
|
+
if (platform === 'linux') {
|
|
794
|
+
await mergeLinuxConfig(options, name, tauriConf, linuxBinaryName);
|
|
795
|
+
}
|
|
796
|
+
if (platform === 'darwin') {
|
|
797
|
+
const validMacTargets = ['app', 'dmg'];
|
|
798
|
+
if (validMacTargets.includes(options.targets)) {
|
|
799
|
+
tauriConf.bundle.targets = [options.targets];
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
const safeAppName = getSafeAppName(name);
|
|
803
|
+
await mergeIcons(options, name, tauriConf, platform, safeAppName);
|
|
804
|
+
await injectCustomCode(options, tauriConf);
|
|
805
|
+
if (platform === 'darwin') {
|
|
806
|
+
await generateMacEntitlements(camera, microphone);
|
|
807
|
+
}
|
|
808
|
+
await writeAllConfigs(tauriConf, platform);
|
|
809
|
+
}
|
|
801
810
|
|
|
802
811
|
class BaseBuilder {
|
|
803
812
|
constructor(options) {
|
|
@@ -884,7 +893,7 @@ class BaseBuilder {
|
|
|
884
893
|
}
|
|
885
894
|
else {
|
|
886
895
|
logger.error('โ Rust required to package your webapp.');
|
|
887
|
-
process.exit(
|
|
896
|
+
process.exit(1);
|
|
888
897
|
}
|
|
889
898
|
}
|
|
890
899
|
const isChina = await isChinaDomain('www.npmjs.com');
|
|
@@ -1093,6 +1102,10 @@ class BaseBuilder {
|
|
|
1093
1102
|
if (this.options.debug) {
|
|
1094
1103
|
fullCommand += ' --verbose';
|
|
1095
1104
|
}
|
|
1105
|
+
const features = this.getBuildFeatures();
|
|
1106
|
+
if (features.length > 0) {
|
|
1107
|
+
fullCommand += ` --features ${features.join(',')}`;
|
|
1108
|
+
}
|
|
1096
1109
|
return fullCommand;
|
|
1097
1110
|
}
|
|
1098
1111
|
getBuildFeatures() {
|
|
@@ -1114,11 +1127,6 @@ class BaseBuilder {
|
|
|
1114
1127
|
if (IS_MAC && this.options.targets === 'app') {
|
|
1115
1128
|
fullCommand += ' --bundles app';
|
|
1116
1129
|
}
|
|
1117
|
-
// Add features
|
|
1118
|
-
const features = this.getBuildFeatures();
|
|
1119
|
-
if (features.length > 0) {
|
|
1120
|
-
fullCommand += ` --features ${features.join(',')}`;
|
|
1121
|
-
}
|
|
1122
1130
|
return fullCommand;
|
|
1123
1131
|
}
|
|
1124
1132
|
getMacOSMajorVersion() {
|
|
@@ -1280,12 +1288,7 @@ class MacBuilder extends BaseBuilder {
|
|
|
1280
1288
|
if (!buildTarget) {
|
|
1281
1289
|
throw new Error(`Unsupported architecture: ${actualArch} for macOS`);
|
|
1282
1290
|
}
|
|
1283
|
-
|
|
1284
|
-
const features = this.getBuildFeatures();
|
|
1285
|
-
if (features.length > 0) {
|
|
1286
|
-
fullCommand += ` --features ${features.join(',')}`;
|
|
1287
|
-
}
|
|
1288
|
-
return fullCommand;
|
|
1291
|
+
return this.buildBaseCommand(packageManager, configPath, buildTarget);
|
|
1289
1292
|
}
|
|
1290
1293
|
getBasePath() {
|
|
1291
1294
|
const basePath = this.options.debug ? 'debug' : 'release';
|
|
@@ -1325,12 +1328,7 @@ class WinBuilder extends BaseBuilder {
|
|
|
1325
1328
|
if (!buildTarget) {
|
|
1326
1329
|
throw new Error(`Unsupported architecture: ${this.buildArch} for Windows`);
|
|
1327
1330
|
}
|
|
1328
|
-
|
|
1329
|
-
const features = this.getBuildFeatures();
|
|
1330
|
-
if (features.length > 0) {
|
|
1331
|
-
fullCommand += ` --features ${features.join(',')}`;
|
|
1332
|
-
}
|
|
1333
|
-
return fullCommand;
|
|
1331
|
+
return this.buildBaseCommand(packageManager, configPath, buildTarget);
|
|
1334
1332
|
}
|
|
1335
1333
|
getBasePath() {
|
|
1336
1334
|
const basePath = this.options.debug ? 'debug' : 'release';
|
|
@@ -1410,10 +1408,6 @@ class LinuxBuilder extends BaseBuilder {
|
|
|
1410
1408
|
? (this.getTauriTarget(this.buildArch, 'linux') ?? undefined)
|
|
1411
1409
|
: undefined;
|
|
1412
1410
|
let fullCommand = this.buildBaseCommand(packageManager, configPath, buildTarget);
|
|
1413
|
-
const features = this.getBuildFeatures();
|
|
1414
|
-
if (features.length > 0) {
|
|
1415
|
-
fullCommand += ` --features ${features.join(',')}`;
|
|
1416
|
-
}
|
|
1417
1411
|
if (this.currentBuildType) {
|
|
1418
1412
|
fullCommand += ` --bundles ${this.currentBuildType}`;
|
|
1419
1413
|
}
|
|
@@ -1470,6 +1464,85 @@ class BuilderProvider {
|
|
|
1470
1464
|
}
|
|
1471
1465
|
}
|
|
1472
1466
|
|
|
1467
|
+
const LOCAL_HOST_SUFFIXES = [
|
|
1468
|
+
'.local',
|
|
1469
|
+
'.lan',
|
|
1470
|
+
'.internal',
|
|
1471
|
+
'.home',
|
|
1472
|
+
'.localdomain',
|
|
1473
|
+
];
|
|
1474
|
+
const IPV4_ADDRESS_PATTERN = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
1475
|
+
function normalize(value) {
|
|
1476
|
+
return value.trim().toLowerCase();
|
|
1477
|
+
}
|
|
1478
|
+
function simplify(value) {
|
|
1479
|
+
return normalize(value).replace(/[\s._-]+/g, '');
|
|
1480
|
+
}
|
|
1481
|
+
function generateDashboardIconSlugs(appName) {
|
|
1482
|
+
const normalizedName = normalize(appName);
|
|
1483
|
+
if (!normalizedName) {
|
|
1484
|
+
return [];
|
|
1485
|
+
}
|
|
1486
|
+
const slugs = new Set([
|
|
1487
|
+
normalizedName,
|
|
1488
|
+
normalizedName.replace(/\s+/g, '-'),
|
|
1489
|
+
]);
|
|
1490
|
+
return [...slugs].filter(Boolean);
|
|
1491
|
+
}
|
|
1492
|
+
function isLikelyLocalHostname(hostname) {
|
|
1493
|
+
const normalizedHostname = normalize(hostname);
|
|
1494
|
+
if (!normalizedHostname) {
|
|
1495
|
+
return false;
|
|
1496
|
+
}
|
|
1497
|
+
return (normalizedHostname === 'localhost' ||
|
|
1498
|
+
IPV4_ADDRESS_PATTERN.test(normalizedHostname) ||
|
|
1499
|
+
normalizedHostname.includes(':') ||
|
|
1500
|
+
!normalizedHostname.includes('.') ||
|
|
1501
|
+
LOCAL_HOST_SUFFIXES.some((suffix) => normalizedHostname.endsWith(suffix)));
|
|
1502
|
+
}
|
|
1503
|
+
function shouldPreferDashboardIcons(url, appName) {
|
|
1504
|
+
if (!appName) {
|
|
1505
|
+
return false;
|
|
1506
|
+
}
|
|
1507
|
+
try {
|
|
1508
|
+
const hostname = new URL(url).hostname.toLowerCase();
|
|
1509
|
+
if (!hostname) {
|
|
1510
|
+
return false;
|
|
1511
|
+
}
|
|
1512
|
+
if (isLikelyLocalHostname(hostname)) {
|
|
1513
|
+
return true;
|
|
1514
|
+
}
|
|
1515
|
+
const parsed = psl.parse(hostname);
|
|
1516
|
+
if (!('domain' in parsed) || !parsed.domain) {
|
|
1517
|
+
return true;
|
|
1518
|
+
}
|
|
1519
|
+
const registrableDomain = parsed.domain.toLowerCase();
|
|
1520
|
+
if (hostname === registrableDomain) {
|
|
1521
|
+
return false;
|
|
1522
|
+
}
|
|
1523
|
+
const subdomain = 'subdomain' in parsed && typeof parsed.subdomain === 'string'
|
|
1524
|
+
? parsed.subdomain
|
|
1525
|
+
: '';
|
|
1526
|
+
if (!subdomain) {
|
|
1527
|
+
return false;
|
|
1528
|
+
}
|
|
1529
|
+
const productLabel = subdomain.split('.').pop() || '';
|
|
1530
|
+
const rootLabel = registrableDomain.split('.')[0] || '';
|
|
1531
|
+
const normalizedAppName = simplify(appName);
|
|
1532
|
+
return (normalizedAppName.length > 0 &&
|
|
1533
|
+
simplify(productLabel) === normalizedAppName &&
|
|
1534
|
+
simplify(rootLabel) !== normalizedAppName);
|
|
1535
|
+
}
|
|
1536
|
+
catch {
|
|
1537
|
+
return false;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function getIconSourcePriority(url, appName) {
|
|
1541
|
+
return shouldPreferDashboardIcons(url, appName)
|
|
1542
|
+
? ['dashboard', 'domain']
|
|
1543
|
+
: ['domain', 'dashboard'];
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1473
1546
|
const ICO_HEADER_SIZE = 6;
|
|
1474
1547
|
const ICO_DIR_ENTRY_SIZE = 16;
|
|
1475
1548
|
const ICO_TYPE_ICON = 1;
|
|
@@ -1571,11 +1644,48 @@ async function writeIcoWithPreferredSize(sourcePath, outputPath, preferredSize)
|
|
|
1571
1644
|
return false;
|
|
1572
1645
|
}
|
|
1573
1646
|
}
|
|
1647
|
+
/**
|
|
1648
|
+
* Builds an ICO file from an array of PNG buffers using the PNG-in-ICO format
|
|
1649
|
+
* (supported since Windows Vista). This preserves alpha transparency.
|
|
1650
|
+
*/
|
|
1651
|
+
function buildIcoFromPngBuffers(frames) {
|
|
1652
|
+
const count = frames.length;
|
|
1653
|
+
const headerSize = ICO_HEADER_SIZE + count * ICO_DIR_ENTRY_SIZE;
|
|
1654
|
+
const totalPayload = frames.reduce((acc, f) => acc + f.png.length, 0);
|
|
1655
|
+
const output = Buffer.alloc(headerSize + totalPayload);
|
|
1656
|
+
output.writeUInt16LE(0, 0);
|
|
1657
|
+
output.writeUInt16LE(ICO_TYPE_ICON, 2);
|
|
1658
|
+
output.writeUInt16LE(count, 4);
|
|
1659
|
+
let currentOffset = headerSize;
|
|
1660
|
+
for (let i = 0; i < count; i++) {
|
|
1661
|
+
const { size, png } = frames[i];
|
|
1662
|
+
const entryOffset = ICO_HEADER_SIZE + i * ICO_DIR_ENTRY_SIZE;
|
|
1663
|
+
const sizeByte = size >= 256 ? 0 : size;
|
|
1664
|
+
output.writeUInt8(sizeByte, entryOffset);
|
|
1665
|
+
output.writeUInt8(sizeByte, entryOffset + 1);
|
|
1666
|
+
output.writeUInt8(0, entryOffset + 2);
|
|
1667
|
+
output.writeUInt8(0, entryOffset + 3);
|
|
1668
|
+
output.writeUInt16LE(1, entryOffset + 4);
|
|
1669
|
+
output.writeUInt16LE(32, entryOffset + 6);
|
|
1670
|
+
output.writeUInt32LE(png.length, entryOffset + 8);
|
|
1671
|
+
output.writeUInt32LE(currentOffset, entryOffset + 12);
|
|
1672
|
+
png.copy(output, currentOffset);
|
|
1673
|
+
currentOffset += png.length;
|
|
1674
|
+
}
|
|
1675
|
+
return output;
|
|
1676
|
+
}
|
|
1574
1677
|
|
|
1575
1678
|
const ICON_CONFIG = {
|
|
1576
1679
|
minFileSize: 100,
|
|
1577
|
-
supportedFormats: [
|
|
1578
|
-
|
|
1680
|
+
supportedFormats: [
|
|
1681
|
+
'png',
|
|
1682
|
+
'ico',
|
|
1683
|
+
'jpeg',
|
|
1684
|
+
'jpg',
|
|
1685
|
+
'webp',
|
|
1686
|
+
'icns',
|
|
1687
|
+
'svg',
|
|
1688
|
+
],
|
|
1579
1689
|
transparentBackground: { r: 255, g: 255, b: 255, alpha: 0 },
|
|
1580
1690
|
downloadTimeout: {
|
|
1581
1691
|
ci: 5000,
|
|
@@ -1591,6 +1701,9 @@ const API_KEYS = {
|
|
|
1591
1701
|
logoDev: ['pk_JLLMUKGZRpaG5YclhXaTkg', 'pk_Ph745P8mQSeYFfW2Wk039A'],
|
|
1592
1702
|
brandfetch: ['1idqvJC0CeFSeyp3Yf7', '1idej-yhU_ThggIHFyG'],
|
|
1593
1703
|
};
|
|
1704
|
+
/**
|
|
1705
|
+
* Generates platform-specific icon paths and handles copying for Windows
|
|
1706
|
+
*/
|
|
1594
1707
|
function generateIconPath(appName, isDefault = false) {
|
|
1595
1708
|
const safeName = isDefault ? 'icon' : getIconBaseName(appName);
|
|
1596
1709
|
const baseName = safeName;
|
|
@@ -1605,7 +1718,7 @@ function generateIconPath(appName, isDefault = false) {
|
|
|
1605
1718
|
function getIconBaseName(appName) {
|
|
1606
1719
|
const baseName = IS_LINUX
|
|
1607
1720
|
? generateLinuxPackageName(appName)
|
|
1608
|
-
:
|
|
1721
|
+
: getSafeAppName(appName);
|
|
1609
1722
|
return baseName || 'pake-app';
|
|
1610
1723
|
}
|
|
1611
1724
|
async function copyWindowsIconIfNeeded(convertedPath, appName) {
|
|
@@ -1628,31 +1741,23 @@ async function copyWindowsIconIfNeeded(convertedPath, appName) {
|
|
|
1628
1741
|
}
|
|
1629
1742
|
}
|
|
1630
1743
|
/**
|
|
1631
|
-
*
|
|
1744
|
+
* Normalizes icon inputs to PNG while preserving alpha.
|
|
1632
1745
|
*/
|
|
1633
1746
|
async function preprocessIcon(inputPath) {
|
|
1634
1747
|
try {
|
|
1635
|
-
const
|
|
1636
|
-
|
|
1637
|
-
|
|
1748
|
+
const extension = path.extname(inputPath).toLowerCase();
|
|
1749
|
+
const shouldNormalize = ['.png', '.jpeg', '.jpg', '.webp', '.svg'].includes(extension);
|
|
1750
|
+
if (!shouldNormalize) {
|
|
1751
|
+
return inputPath;
|
|
1752
|
+
}
|
|
1638
1753
|
const { path: tempDir } = await dir();
|
|
1639
|
-
const outputPath = path.join(tempDir, 'icon-
|
|
1640
|
-
await sharp(
|
|
1641
|
-
create: {
|
|
1642
|
-
width: metadata.width || 512,
|
|
1643
|
-
height: metadata.height || 512,
|
|
1644
|
-
channels: 4,
|
|
1645
|
-
background: { ...ICON_CONFIG.whiteBackground, alpha: 1 },
|
|
1646
|
-
},
|
|
1647
|
-
})
|
|
1648
|
-
.composite([{ input: inputPath }])
|
|
1649
|
-
.png()
|
|
1650
|
-
.toFile(outputPath);
|
|
1754
|
+
const outputPath = path.join(tempDir, 'icon-normalized.png');
|
|
1755
|
+
await sharp(inputPath).ensureAlpha().png().toFile(outputPath);
|
|
1651
1756
|
return outputPath;
|
|
1652
1757
|
}
|
|
1653
1758
|
catch (error) {
|
|
1654
1759
|
if (error instanceof Error) {
|
|
1655
|
-
logger.warn(`Failed to
|
|
1760
|
+
logger.warn(`Failed to normalize icon: ${error.message}`);
|
|
1656
1761
|
}
|
|
1657
1762
|
return inputPath;
|
|
1658
1763
|
}
|
|
@@ -1719,15 +1824,22 @@ async function convertIconFormat(inputPath, appName) {
|
|
|
1719
1824
|
const iconName = getIconBaseName(appName);
|
|
1720
1825
|
// Generate platform-specific format
|
|
1721
1826
|
if (IS_WIN) {
|
|
1722
|
-
|
|
1723
|
-
await
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1827
|
+
const icoPath = path.join(platformOutputDir, `${iconName}_256${PLATFORM_CONFIG.win.format}`);
|
|
1828
|
+
const sourceBuffer = await fsExtra.readFile(processedInputPath);
|
|
1829
|
+
const frames = await Promise.all(PLATFORM_CONFIG.win.sizes.map(async (size) => {
|
|
1830
|
+
const png = await sharp(sourceBuffer)
|
|
1831
|
+
.resize(size, size, {
|
|
1832
|
+
fit: 'contain',
|
|
1833
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
1834
|
+
})
|
|
1835
|
+
.ensureAlpha()
|
|
1836
|
+
.png()
|
|
1837
|
+
.toBuffer();
|
|
1838
|
+
return { size, png };
|
|
1839
|
+
}));
|
|
1840
|
+
const icoBuffer = buildIcoFromPngBuffers(frames);
|
|
1841
|
+
await fsExtra.outputFile(icoPath, icoBuffer);
|
|
1842
|
+
return icoPath;
|
|
1731
1843
|
}
|
|
1732
1844
|
if (IS_LINUX) {
|
|
1733
1845
|
const outputPath = path.join(platformOutputDir, `${iconName}_${PLATFORM_CONFIG.linux.size}${PLATFORM_CONFIG.linux.format}`);
|
|
@@ -1882,15 +1994,74 @@ function generateIconServiceUrls(domain) {
|
|
|
1882
1994
|
*/
|
|
1883
1995
|
function generateDashboardIconUrls(appName) {
|
|
1884
1996
|
const baseUrl = 'https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png';
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
.
|
|
1893
|
-
.
|
|
1997
|
+
return generateDashboardIconSlugs(appName).map((slug) => `${baseUrl}/${slug}.png`);
|
|
1998
|
+
}
|
|
1999
|
+
function isSupportedIconFormat(extension) {
|
|
2000
|
+
return ICON_CONFIG.supportedFormats.includes(extension);
|
|
2001
|
+
}
|
|
2002
|
+
function looksLikeSvg(arrayBuffer) {
|
|
2003
|
+
const sample = Buffer.from(arrayBuffer)
|
|
2004
|
+
.toString('utf-8', 0, Math.min(arrayBuffer.byteLength, 512))
|
|
2005
|
+
.trimStart()
|
|
2006
|
+
.toLowerCase();
|
|
2007
|
+
return (sample.startsWith('<svg') ||
|
|
2008
|
+
(sample.startsWith('<?xml') && sample.includes('<svg')));
|
|
2009
|
+
}
|
|
2010
|
+
function getUrlExtension(iconUrl) {
|
|
2011
|
+
try {
|
|
2012
|
+
return path.extname(new URL(iconUrl).pathname).slice(1).toLowerCase();
|
|
2013
|
+
}
|
|
2014
|
+
catch {
|
|
2015
|
+
return path.extname(iconUrl).slice(1).toLowerCase();
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
async function detectDownloadedIconExtension(response, arrayBuffer, iconUrl) {
|
|
2019
|
+
const fileDetails = await fileTypeFromBuffer(arrayBuffer);
|
|
2020
|
+
if (fileDetails && isSupportedIconFormat(fileDetails.ext)) {
|
|
2021
|
+
return fileDetails.ext;
|
|
2022
|
+
}
|
|
2023
|
+
const contentType = response.headers
|
|
2024
|
+
.get('content-type')
|
|
2025
|
+
?.split(';')[0]
|
|
2026
|
+
.trim();
|
|
2027
|
+
if (contentType === 'image/svg+xml' && looksLikeSvg(arrayBuffer)) {
|
|
2028
|
+
return 'svg';
|
|
2029
|
+
}
|
|
2030
|
+
if (getUrlExtension(iconUrl) === 'svg' && looksLikeSvg(arrayBuffer)) {
|
|
2031
|
+
return 'svg';
|
|
2032
|
+
}
|
|
2033
|
+
return null;
|
|
2034
|
+
}
|
|
2035
|
+
async function resolveIconFromUrl(iconUrl, appName, downloadTimeout) {
|
|
2036
|
+
const iconPath = await downloadIcon(iconUrl, false, downloadTimeout);
|
|
2037
|
+
if (!iconPath) {
|
|
2038
|
+
return null;
|
|
2039
|
+
}
|
|
2040
|
+
const convertedPath = await convertIconFormat(iconPath, appName);
|
|
2041
|
+
if (!convertedPath) {
|
|
2042
|
+
return null;
|
|
2043
|
+
}
|
|
2044
|
+
return await copyWindowsIconIfNeeded(convertedPath, appName);
|
|
2045
|
+
}
|
|
2046
|
+
async function tryResolveIconSource(source, domain, appName, downloadTimeout) {
|
|
2047
|
+
const iconUrls = source === 'dashboard'
|
|
2048
|
+
? generateDashboardIconUrls(appName)
|
|
2049
|
+
: generateIconServiceUrls(domain);
|
|
2050
|
+
for (const iconUrl of iconUrls) {
|
|
2051
|
+
try {
|
|
2052
|
+
const resolvedPath = await resolveIconFromUrl(iconUrl, appName, downloadTimeout);
|
|
2053
|
+
if (resolvedPath) {
|
|
2054
|
+
return resolvedPath;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
catch (error) {
|
|
2058
|
+
if (error instanceof Error) {
|
|
2059
|
+
const label = source === 'dashboard' ? 'Dashboard icon' : 'Icon service';
|
|
2060
|
+
logger.debug(`${label} ${iconUrl} failed: ${error.message}`);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
return null;
|
|
1894
2065
|
}
|
|
1895
2066
|
/**
|
|
1896
2067
|
* Attempts to fetch favicon from website
|
|
@@ -1903,49 +2074,16 @@ async function tryGetFavicon(url, appName) {
|
|
|
1903
2074
|
const downloadTimeout = isCI
|
|
1904
2075
|
? ICON_CONFIG.downloadTimeout.ci
|
|
1905
2076
|
: ICON_CONFIG.downloadTimeout.default;
|
|
1906
|
-
const
|
|
1907
|
-
for (const
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
if (!faviconPath)
|
|
1911
|
-
continue;
|
|
1912
|
-
const convertedPath = await convertIconFormat(faviconPath, appName);
|
|
1913
|
-
if (convertedPath) {
|
|
1914
|
-
const finalPath = await copyWindowsIconIfNeeded(convertedPath, appName);
|
|
1915
|
-
spinner.succeed(chalk.green('Icon fetched and converted successfully!'));
|
|
1916
|
-
return finalPath;
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
catch (error) {
|
|
1920
|
-
if (error instanceof Error) {
|
|
1921
|
-
logger.debug(`Icon service ${serviceUrl} failed: ${error.message}`);
|
|
1922
|
-
}
|
|
2077
|
+
const sourcePriority = getIconSourcePriority(url, appName);
|
|
2078
|
+
for (const source of sourcePriority) {
|
|
2079
|
+
const resolvedIconPath = await tryResolveIconSource(source, domain, appName, downloadTimeout);
|
|
2080
|
+
if (!resolvedIconPath) {
|
|
1923
2081
|
continue;
|
|
1924
2082
|
}
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
const dashboardIconUrls = generateDashboardIconUrls(appName);
|
|
1930
|
-
for (const iconUrl of dashboardIconUrls) {
|
|
1931
|
-
try {
|
|
1932
|
-
const iconPath = await downloadIcon(iconUrl, false, downloadTimeout);
|
|
1933
|
-
if (!iconPath)
|
|
1934
|
-
continue;
|
|
1935
|
-
const convertedPath = await convertIconFormat(iconPath, appName);
|
|
1936
|
-
if (convertedPath) {
|
|
1937
|
-
const finalPath = await copyWindowsIconIfNeeded(convertedPath, appName);
|
|
1938
|
-
spinner.succeed(chalk.green(`Icon found via dashboard-icons fallback for "${appName}"!`));
|
|
1939
|
-
return finalPath;
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
catch (error) {
|
|
1943
|
-
if (error instanceof Error) {
|
|
1944
|
-
logger.debug(`Dashboard icon ${iconUrl} failed: ${error.message}`);
|
|
1945
|
-
}
|
|
1946
|
-
continue;
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
2083
|
+
spinner.succeed(chalk.green(source === 'dashboard'
|
|
2084
|
+
? `Icon found via dashboard-icons for "${appName}"!`
|
|
2085
|
+
: 'Icon fetched and converted successfully!'));
|
|
2086
|
+
return resolvedIconPath;
|
|
1949
2087
|
}
|
|
1950
2088
|
spinner.warn(`No favicon found for ${domain}. Using default.`);
|
|
1951
2089
|
return null;
|
|
@@ -1979,12 +2117,11 @@ async function downloadIcon(iconUrl, showSpinner = true, customTimeout) {
|
|
|
1979
2117
|
const arrayBuffer = await response.arrayBuffer();
|
|
1980
2118
|
if (!arrayBuffer || arrayBuffer.byteLength < ICON_CONFIG.minFileSize)
|
|
1981
2119
|
return null;
|
|
1982
|
-
const
|
|
1983
|
-
if (!
|
|
1984
|
-
!ICON_CONFIG.supportedFormats.includes(fileDetails.ext)) {
|
|
2120
|
+
const extension = await detectDownloadedIconExtension(response, arrayBuffer, iconUrl);
|
|
2121
|
+
if (!extension) {
|
|
1985
2122
|
return null;
|
|
1986
2123
|
}
|
|
1987
|
-
return await saveIconFile(arrayBuffer,
|
|
2124
|
+
return await saveIconFile(arrayBuffer, extension);
|
|
1988
2125
|
}
|
|
1989
2126
|
catch (error) {
|
|
1990
2127
|
clearTimeout(timeoutId);
|
|
@@ -2068,11 +2205,9 @@ function resolveLocalAppName(filePath, platform) {
|
|
|
2068
2205
|
return normalized || 'pake-app';
|
|
2069
2206
|
}
|
|
2070
2207
|
function isValidName(name, platform) {
|
|
2071
|
-
const
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
};
|
|
2075
|
-
const reg = platformRegexMapping[platform] || platformRegexMapping.default;
|
|
2208
|
+
const reg = platform === 'linux'
|
|
2209
|
+
? /^[a-z0-9\u4e00-\u9fff][a-z0-9\u4e00-\u9fff-]*$/
|
|
2210
|
+
: /^[a-zA-Z0-9\u4e00-\u9fff][a-zA-Z0-9\u4e00-\u9fff- ]*$/;
|
|
2076
2211
|
return !!name && reg.test(name);
|
|
2077
2212
|
}
|
|
2078
2213
|
async function handleOptions(options, url) {
|
|
@@ -2177,7 +2312,7 @@ function validateNumberInput(value) {
|
|
|
2177
2312
|
return parsedValue;
|
|
2178
2313
|
}
|
|
2179
2314
|
function validateUrlInput(url) {
|
|
2180
|
-
const isFile = fs.existsSync(url);
|
|
2315
|
+
const isFile = fs$1.existsSync(url);
|
|
2181
2316
|
if (!isFile) {
|
|
2182
2317
|
try {
|
|
2183
2318
|
return normalizeUrl(url);
|
|
@@ -2327,7 +2462,9 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
|
|
|
2327
2462
|
.addOption(new Option('--new-window', 'Allow sites to open new windows (for auth flows, tabs, branches)')
|
|
2328
2463
|
.default(DEFAULT_PAKE_OPTIONS.newWindow)
|
|
2329
2464
|
.hideHelp())
|
|
2330
|
-
.
|
|
2465
|
+
.addOption(new Option('--install', 'Auto-install app to /Applications (macOS) after build and remove local bundle')
|
|
2466
|
+
.default(DEFAULT_PAKE_OPTIONS.install)
|
|
2467
|
+
.hideHelp())
|
|
2331
2468
|
.addOption(new Option('--camera', 'Request camera permission on macOS')
|
|
2332
2469
|
.default(DEFAULT_PAKE_OPTIONS.camera)
|
|
2333
2470
|
.hideHelp())
|