11import { InvalidConfigurationError , log , isEmptyOrSpaces } from "builder-util"
2+ import { execWine } from "app-builder-lib/out/wine"
3+ import { getBinFromUrl } from "app-builder-lib/out/binDownload"
24import { sanitizeFileName } from "builder-util/out/filename"
35import { Arch , getArchSuffix , SquirrelWindowsOptions , Target , WinPackager } from "app-builder-lib"
46import * as path from "path"
@@ -20,31 +22,56 @@ export default class SquirrelWindowsTarget extends Target {
2022 }
2123
2224 private async prepareSignedVendorDirectory ( ) : Promise < string > {
23- // If not specified will use the Squirrel.Windows that is shipped with electron-installer(https://github.com/electron/windows-installer/tree/main/vendor)
24- // After https://github.com/electron-userland/electron-builder-binaries/pull/56 merged, will add `electron-builder-binaries` to get the latest version of squirrel.
25- let vendorDirectory = this . options . customSquirrelVendorDir || path . join ( require . resolve ( "electron-winstaller/package.json" ) , ".." , "vendor" )
26- if ( isEmptyOrSpaces ( vendorDirectory ) || ! fs . existsSync ( vendorDirectory ) ) {
27- log . warn ( { vendorDirectory } , "unable to access Squirrel.Windows vendor directory, falling back to default electron-winstaller" )
28- vendorDirectory = path . join ( require . resolve ( "electron-winstaller/package.json" ) , ".." , "vendor" )
29- }
30-
25+ const customSquirrelVendorDirectory = this . options . customSquirrelVendorDir
3126 const tmpVendorDirectory = await this . packager . info . tempDirManager . createTempDir ( { prefix : "squirrel-windows-vendor" } )
32- // Copy entire vendor directory to temp directory
33- await fs . promises . cp ( vendorDirectory , tmpVendorDirectory , { recursive : true } )
34- log . debug ( { from : vendorDirectory , to : tmpVendorDirectory } , "copied vendor directory" )
3527
36- const files = await fs . promises . readdir ( tmpVendorDirectory )
37- for ( const file of files ) {
38- if ( [ "Squirrel.exe" , "StubExecutable.exe" ] . includes ( file ) ) {
39- const filePath = path . join ( tmpVendorDirectory , file )
40- log . debug ( { file : filePath } , "signing vendor executable" )
41- await this . packager . sign ( filePath )
42- }
28+ if ( isEmptyOrSpaces ( customSquirrelVendorDirectory ) || ! fs . existsSync ( customSquirrelVendorDirectory ) ) {
29+ log . warn ( { customSquirrelVendorDirectory : customSquirrelVendorDirectory } , "unable to access custom Squirrel.Windows vendor directory, falling back to default vendor " )
30+ const windowInstallerPackage = require . resolve ( "electron-winstaller/package.json" )
31+ const vendorDirectory = path . join ( path . dirname ( windowInstallerPackage ) , "vendor" )
32+
33+ const squirrelBin = await getBinFromUrl (
34+ 35+ "squirrel.windows-2.0.1-patched.7z" ,
36+ "DWijIRRElidu/Rq0yegAKqo2g6aVJUPvcRyvkzUoBPbRasIk61P6xY2fBMdXw6wT17md7NzrTI9/zA1wT9vEqg=="
37+ )
38+
39+ await fs . promises . cp ( vendorDirectory , tmpVendorDirectory , { recursive : true } )
40+ // copy the patched squirrel to tmp vendor directory
41+ await fs . promises . cp ( path . join ( squirrelBin , "electron-winstaller" , "vendor" ) , tmpVendorDirectory , { recursive : true } )
42+ } else {
43+ // copy the custom squirrel vendor directory to tmp vendor directory
44+ await fs . promises . cp ( customSquirrelVendorDirectory , tmpVendorDirectory , { recursive : true } )
4345 }
4446
47+ const files = await fs . promises . readdir ( tmpVendorDirectory )
48+ const squirrelExe = files . find ( f => f === "Squirrel.exe" )
49+ if ( squirrelExe ) {
50+ const filePath = path . join ( tmpVendorDirectory , squirrelExe )
51+ log . debug ( { file : filePath } , "signing vendor executable" )
52+ await this . packager . sign ( filePath )
53+ } else {
54+ log . warn ( "Squirrel.exe not found in vendor directory, skipping signing" )
55+ }
4556 return tmpVendorDirectory
4657 }
4758
59+ private async generateStubExecutableExe ( appOutDir : string , vendorDir : string ) {
60+ const files = await fs . promises . readdir ( appOutDir , { withFileTypes : true } )
61+ const appExe = files . find ( f => f . name === `${ this . exeName } .exe` )
62+ if ( ! appExe ) {
63+ throw new Error ( `App executable not found in app directory: ${ appOutDir } ` )
64+ }
65+
66+ const filePath = path . join ( appOutDir , appExe . name )
67+ const stubExePath = path . join ( appOutDir , `${ this . exeName } _ExecutionStub.exe` )
68+ await fs . promises . copyFile ( path . join ( vendorDir , "StubExecutable.exe" ) , stubExePath )
69+ await execWine ( path . join ( vendorDir , "WriteZipToSetup.exe" ) , null , [ "--copy-stub-resources" , filePath , stubExePath ] )
70+ await this . packager . sign ( stubExePath )
71+ log . debug ( { file : filePath } , "signing app executable" )
72+ await this . packager . sign ( filePath )
73+ }
74+
4875 async build ( appOutDir : string , arch : Arch ) {
4976 const packager = this . packager
5077 const version = packager . appInfo . version
@@ -62,6 +89,7 @@ export default class SquirrelWindowsTarget extends Target {
6289 arch,
6390 } )
6491 const distOptions = await this . computeEffectiveDistOptions ( appOutDir , installerOutDir , setupFile )
92+ await this . generateStubExecutableExe ( appOutDir , distOptions . vendorDirectory ! )
6593 await createWindowsInstaller ( distOptions )
6694
6795 await packager . signAndEditResources ( artifactPath , arch , installerOutDir )
@@ -120,6 +148,10 @@ export default class SquirrelWindowsTarget extends Target {
120148 return this . options . name || this . packager . appInfo . name
121149 }
122150
151+ private get exeName ( ) {
152+ return this . packager . appInfo . productFilename || this . options . name || this . packager . appInfo . productName
153+ }
154+
123155 private select7zipArch ( vendorDirectory : string ) {
124156 // https://github.com/electron/windows-installer/blob/main/script/select-7z-arch.js
125157 // Even if we're cross-compiling for a different arch like arm64,
0 commit comments