@@ -751,40 +751,145 @@ function versionAsTag(version: string | undefined): string | undefined {
751751 return / ^ v / iu. test ( trimmed ) ? trimmed : `v${ trimmed } ` ;
752752}
753753
754- function sanitizeDownloadFileName ( value : string ) : string {
754+ function launcherUpdatePackageLabel (
755+ platform : NodeJS . Platform = process . platform ,
756+ ) : string {
757+ if ( platform === "darwin" ) {
758+ return "macOS DMG 安装包" ;
759+ }
760+ if ( platform === "win32" ) {
761+ return "Windows 安装包" ;
762+ }
763+ return "当前平台安装包" ;
764+ }
765+
766+ function launcherUpdateDefaultFileName (
767+ platform : NodeJS . Platform = process . platform ,
768+ ) : string {
769+ if ( platform === "darwin" ) {
770+ return "MaiBot-OneKey-update.dmg" ;
771+ }
772+ if ( platform === "win32" ) {
773+ return "MaiBot-OneKey-update.exe" ;
774+ }
775+ return "MaiBot-OneKey-update" ;
776+ }
777+
778+ function launcherAssetArchNames ( arch : string ) : string [ ] {
779+ if ( arch === "arm64" ) {
780+ return [ "arm64" , "aarch64" ] ;
781+ }
782+ if ( arch === "x64" ) {
783+ return [ "x64" , "x86_64" , "amd64" ] ;
784+ }
785+ return [ arch . toLowerCase ( ) ] ;
786+ }
787+
788+ function scoreLauncherUpdateAsset (
789+ asset : GitHubReleaseAsset ,
790+ platformNames : string [ ] ,
791+ arch : string ,
792+ ) : number {
793+ const name = String ( asset . name ?? "" ) . toLowerCase ( ) ;
794+ const archNames = launcherAssetArchNames ( arch ) ;
795+ let score = 0 ;
796+ if ( platformNames . some ( ( platformName ) => name . includes ( platformName ) ) ) {
797+ score += 20 ;
798+ }
799+ if ( archNames . some ( ( archName ) => name . includes ( archName ) ) ) {
800+ score += 10 ;
801+ }
802+ if ( name . includes ( "universal" ) ) {
803+ score += 5 ;
804+ }
805+ return score ;
806+ }
807+
808+ function selectBestLauncherUpdateAsset (
809+ assets : GitHubReleaseAsset [ ] ,
810+ platformNames : string [ ] ,
811+ arch : string ,
812+ ) : GitHubReleaseAsset | undefined {
813+ return assets
814+ . map ( ( asset , index ) => ( {
815+ asset,
816+ index,
817+ score : scoreLauncherUpdateAsset ( asset , platformNames , arch ) ,
818+ } ) )
819+ . sort ( ( left , right ) => {
820+ if ( right . score !== left . score ) {
821+ return right . score - left . score ;
822+ }
823+ return left . index - right . index ;
824+ } ) [ 0 ] ?. asset ;
825+ }
826+
827+ function sanitizeDownloadFileName (
828+ value : string ,
829+ platform : NodeJS . Platform = process . platform ,
830+ ) : string {
755831 const sanitized = basename ( value )
756832 . replace ( / [ < > : " / \\ | ? * \u0000 - \u001F ] / gu, "-" )
757833 . replace ( / \s + / gu, " " )
758834 . trim ( )
759835 . replace ( / [ . ] + $ / u, "" ) ;
760- return sanitized || "MaiBot-OneKey-update.exe" ;
836+ return sanitized || launcherUpdateDefaultFileName ( platform ) ;
761837}
762838
763839function selectLauncherUpdateAsset (
764840 rawAssets : unknown ,
841+ platform : NodeJS . Platform = process . platform ,
842+ arch : string = process . arch ,
765843) : GitHubReleaseAsset | undefined {
766844 const assets = Array . isArray ( rawAssets )
767845 ? rawAssets . filter ( ( item ) : item is GitHubReleaseAsset => {
768846 return Boolean ( item && typeof item === "object" ) ;
769847 } )
770848 : [ ] ;
771- const executableAssets = assets . filter ( ( asset ) => {
849+ const downloadableAssets = assets . filter ( ( asset ) => {
772850 const name = typeof asset . name === "string" ? asset . name . toLowerCase ( ) : "" ;
773851 const url =
774852 typeof asset . browser_download_url === "string"
775853 ? asset . browser_download_url
776854 : "" ;
855+ return Boolean ( name && url ) ;
856+ } ) ;
857+
858+ if ( platform === "darwin" ) {
859+ const dmgAssets = downloadableAssets . filter ( ( asset ) => {
860+ const name =
861+ typeof asset . name === "string" ? asset . name . toLowerCase ( ) : "" ;
862+ return name . endsWith ( ".dmg" ) ;
863+ } ) ;
864+ return selectBestLauncherUpdateAsset (
865+ dmgAssets ,
866+ [ "mac" , "darwin" , "osx" ] ,
867+ arch ,
868+ ) ;
869+ }
870+
871+ if ( platform === "win32" ) {
872+ const executableAssets = downloadableAssets . filter ( ( asset ) => {
873+ const name =
874+ typeof asset . name === "string" ? asset . name . toLowerCase ( ) : "" ;
875+ return name . endsWith ( ".exe" ) && ! name . includes ( "uninstaller" ) ;
876+ } ) ;
877+ return selectBestLauncherUpdateAsset (
878+ executableAssets ,
879+ [ "win" , "windows" ] ,
880+ arch ,
881+ ) ;
882+ }
883+
884+ const linuxAssets = downloadableAssets . filter ( ( asset ) => {
885+ const name = typeof asset . name === "string" ? asset . name . toLowerCase ( ) : "" ;
777886 return (
778- name . endsWith ( ".exe" ) && ! name . includes ( "uninstaller" ) && Boolean ( url )
887+ name . endsWith ( ".appimage" ) ||
888+ name . endsWith ( ".deb" ) ||
889+ name . endsWith ( ".rpm" )
779890 ) ;
780891 } ) ;
781- return (
782- executableAssets . find ( ( asset ) =>
783- String ( asset . name ?? "" )
784- . toLowerCase ( )
785- . includes ( "win" ) ,
786- ) ?? executableAssets [ 0 ]
787- ) ;
892+ return selectBestLauncherUpdateAsset ( linuxAssets , [ "linux" ] , arch ) ;
788893}
789894
790895async function fetchLauncherReleaseNotesInRange (
@@ -1156,7 +1261,7 @@ async function downloadLauncherUpdate(
11561261 onProgress ?: LauncherUpdateDownloadProgressCallback ,
11571262) : Promise < LauncherUpdateDownloadResult > {
11581263 if ( ! update . downloadUrl || ! update . assetName ) {
1159- throw new Error ( " 最新版本没有可下载的 Windows 安装包" ) ;
1264+ throw new Error ( ` 最新版本没有可下载的${ launcherUpdatePackageLabel ( ) } ` ) ;
11601265 }
11611266
11621267 const controller = new AbortController ( ) ;
@@ -3241,12 +3346,21 @@ export function registerAppIpc({
32413346 ) ;
32423347 await assertLauncherUpdateAllowed ( serviceManager ) ;
32433348 const installerPath = download . installerPath ;
3244- const child = spawn ( installerPath , [ ] , {
3245- detached : true ,
3246- stdio : "ignore" ,
3247- windowsHide : false ,
3248- } ) ;
3249- child . unref ( ) ;
3349+ let willQuit = false ;
3350+ if ( process . platform === "win32" ) {
3351+ const child = spawn ( installerPath , [ ] , {
3352+ detached : true ,
3353+ stdio : "ignore" ,
3354+ windowsHide : false ,
3355+ } ) ;
3356+ child . unref ( ) ;
3357+ willQuit = true ;
3358+ } else {
3359+ const openError = await shell . openPath ( installerPath ) ;
3360+ if ( openError ) {
3361+ throw new Error ( `安装包打开失败:${ openError } ` ) ;
3362+ }
3363+ }
32503364 sendProgress ( {
32513365 phase : "completed" ,
32523366 assetName : update . assetName ,
@@ -3257,18 +3371,24 @@ export function registerAppIpc({
32573371 logStore . append (
32583372 "desktop" ,
32593373 "system" ,
3260- `启动器更新安装器已启动 : ${ installerPath } ` ,
3374+ `启动器更新安装包已打开 : ${ installerPath } ` ,
32613375 ) ;
3262- setTimeout ( ( ) => requestQuit ( ) , 800 ) ;
3376+ if ( willQuit ) {
3377+ setTimeout ( ( ) => requestQuit ( ) , 800 ) ;
3378+ }
32633379 return {
32643380 update : publicLauncherUpdateInfo ( update ) ,
32653381 installerPath,
32663382 started : true ,
3267- willQuit : true ,
3383+ willQuit,
32683384 } ;
32693385 } ,
32703386 ) ;
32713387
3388+ ipcMain . handle ( "launcher:quit" , ( ) : void => {
3389+ requestQuit ( ) ;
3390+ } ) ;
3391+
32723392 ipcMain . handle (
32733393 "plugins:listMarket" ,
32743394 async (
0 commit comments