1+ import * as path from "path"
2+ import { Promise as BluebirdPromise } from "bluebird"
3+ import { emptyDir , copy , createWriteStream , unlink } from "fs-extra-p"
4+ import { spawn , exec } from "../util/util"
5+ import { debug } from "../util/util"
6+ import { WinPackager } from "../winPackager"
7+
8+ const archiverUtil = require ( "archiver-utils" )
9+ const archiver = require ( "archiver" )
10+
11+ //noinspection JSUnusedLocalSymbols
12+ const __awaiter = require ( "../util/awaiter" )
13+
14+ export function convertVersion ( version : string ) : string {
15+ const parts = version . split ( "-" )
16+ const mainVersion = parts . shift ( )
17+ if ( parts . length > 0 ) {
18+ return [ mainVersion , parts . join ( "-" ) . replace ( / \. / g, "" ) ] . join ( "-" )
19+ }
20+ else {
21+ return mainVersion !
22+ }
23+ }
24+
25+ function syncReleases ( outputDirectory : string , options : SquirrelOptions ) {
26+ const args = prepareArgs ( [ "-u" , options . remoteReleases ! , "-r" , outputDirectory ] , path . join ( options . vendorPath , "SyncReleases.exe" ) )
27+ if ( options . remoteToken ) {
28+ args . push ( "-t" , options . remoteToken )
29+ }
30+ return spawn ( process . platform === "win32" ? path . join ( options . vendorPath , "SyncReleases.exe" ) : "mono" , args )
31+ }
32+
33+ export interface SquirrelOptions {
34+ vendorPath : string
35+ remoteReleases ?: string
36+ remoteToken ?: string
37+ loadingGif ?: string
38+ productName ?: string
39+ name : string
40+ packageCompressionLevel ?: number
41+ version : string
42+ msi ?: any
43+
44+ owners ?: string
45+ description ?: string
46+ iconUrl ?: string
47+ authors ?: string
48+ extraMetadataSpecs ?: string
49+ copyright ?: string
50+ }
51+
52+ export async function buildInstaller ( options : SquirrelOptions , outputDirectory : string , stageDir : string , setupExe : string , packager : WinPackager , appOutDir : string ) {
53+ const appUpdate = path . join ( stageDir , "Update.exe" )
54+ const promises = [
55+ copy ( path . join ( options . vendorPath , "Update.exe" ) , appUpdate )
56+ . then ( ( ) => packager . sign ( appUpdate ) ) ,
57+ emptyDir ( outputDirectory )
58+ ]
59+ if ( options . remoteReleases ) {
60+ promises . push ( syncReleases ( outputDirectory , options ) )
61+ }
62+ await BluebirdPromise . all ( promises )
63+
64+ const embeddedArchiveFile = path . join ( stageDir , "setup.zip" )
65+ const embeddedArchive = archiver ( "zip" , { zlib : { level : options . packageCompressionLevel == null ? 6 : options . packageCompressionLevel } } )
66+ const embeddedArchiveOut = createWriteStream ( embeddedArchiveFile )
67+ const embeddedArchivePromise = new BluebirdPromise ( function ( resolve , reject ) {
68+ embeddedArchive . on ( "error" , reject )
69+ embeddedArchiveOut . on ( "close" , resolve )
70+ } )
71+ embeddedArchive . pipe ( embeddedArchiveOut )
72+
73+ embeddedArchive . file ( appUpdate , { name : "Update.exe" } )
74+ embeddedArchive . file ( options . loadingGif ? path . resolve ( options . loadingGif ) : path . join ( __dirname , ".." , ".." , "templates" , "install-spinner.gif" ) , { name : "background.gif" } )
75+
76+ const version = convertVersion ( options . version )
77+ const packageName = `${ options . name } -${ version } -full.nupkg`
78+ const nupkgPath = path . join ( outputDirectory , packageName )
79+ const setupPath = path . join ( outputDirectory , setupExe || `${ options . name || options . productName } Setup.exe` )
80+
81+ await BluebirdPromise . all < any > ( [
82+ pack ( options , appOutDir , appUpdate , nupkgPath , version , options . packageCompressionLevel ) ,
83+ copy ( path . join ( options . vendorPath , "Setup.exe" ) , setupPath ) ,
84+ ] )
85+
86+ embeddedArchive . file ( nupkgPath , { name : packageName } )
87+
88+ const releaseEntry = await releasify ( options , nupkgPath , outputDirectory , packageName )
89+
90+ embeddedArchive . append ( releaseEntry , { name : "RELEASES" } )
91+ embeddedArchive . finalize ( )
92+ await embeddedArchivePromise
93+
94+ const writeZipToSetup = path . join ( options . vendorPath , "WriteZipToSetup.exe" )
95+ await exec ( process . platform === "win32" ? writeZipToSetup : "wine" , prepareArgs ( [ setupPath , embeddedArchiveFile ] , writeZipToSetup ) )
96+
97+ await packager . signAndEditResources ( setupPath )
98+ if ( options . msi && process . platform === "win32" ) {
99+ const outFile = setupExe . replace ( ".exe" , ".msi" )
100+ await msi ( options , nupkgPath , setupPath , outputDirectory , outFile )
101+ await packager . signAndEditResources ( path . join ( outputDirectory , outFile ) )
102+ }
103+ }
104+
105+ async function pack ( options : SquirrelOptions , directory : string , updateFile : string , outFile : string , version : string , packageCompressionLevel ?: number ) {
106+ const archive = archiver ( "zip" , { zlib : { level : packageCompressionLevel == null ? 9 : packageCompressionLevel } } )
107+ // const archiveOut = createWriteStream('/Users/develar/test.zip')
108+ const archiveOut = createWriteStream ( outFile )
109+ const archivePromise = new BluebirdPromise ( function ( resolve , reject ) {
110+ archive . on ( "error" , reject )
111+ archiveOut . on ( "close" , resolve )
112+ } )
113+ archive . pipe ( archiveOut )
114+
115+ const author = options . authors || options . owners
116+ const copyright = options . copyright || `Copyright © ${ new Date ( ) . getFullYear ( ) } ${ author } `
117+ const nuspecContent = `<?xml version="1.0"?>
118+ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
119+ <metadata>
120+ <id>${ options . name } </id>
121+ <version>${ version } </version>
122+ <title>${ options . productName } </title>
123+ <authors>${ author } </authors>
124+ <owners>${ options . owners || options . authors } </owners>
125+ <iconUrl>${ options . iconUrl } </iconUrl>
126+ <requireLicenseAcceptance>false</requireLicenseAcceptance>
127+ <description>${ options . description } </description>
128+ <copyright>${ copyright } </copyright>${ options . extraMetadataSpecs || "" }
129+ </metadata>
130+ </package>`
131+ debug ( `Created NuSpec file:\n${ nuspecContent } ` )
132+ archive . append ( nuspecContent . replace ( / \n / , "\r\n" ) , { name : `${ encodeURI ( options . name ) . replace ( / % 5 B / g, "[" ) . replace ( / % 5 D / g, "]" ) } .nuspec` } )
133+
134+ //noinspection SpellCheckingInspection
135+ archive . append ( `<?xml version="1.0" encoding="utf-8"?>
136+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
137+ <Relationship Type="http://schemas.microsoft.com/packaging/2010/07/manifest" Target="/${ options . name } .nuspec" Id="Re0" />
138+ <Relationship Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="/package/services/metadata/core-properties/1.psmdcp" Id="Re1" />
139+ </Relationships>` . replace ( / \n / , "\r\n" ) , { name : ".rels" , prefix : "_rels" } )
140+
141+ //noinspection SpellCheckingInspection
142+ archive . append ( `<?xml version="1.0" encoding="utf-8"?>
143+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
144+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
145+ <Default Extension="nuspec" ContentType="application/octet" />
146+ <Default Extension="pak" ContentType="application/octet" />
147+ <Default Extension="asar" ContentType="application/octet" />
148+ <Default Extension="bin" ContentType="application/octet" />
149+ <Default Extension="dll" ContentType="application/octet" />
150+ <Default Extension="exe" ContentType="application/octet" />
151+ <Default Extension="dat" ContentType="application/octet" />
152+ <Default Extension="psmdcp" ContentType="application/vnd.openxmlformats-package.core-properties+xml" />
153+ <Override PartName="/lib/net45/LICENSE" ContentType="application/octet" />
154+ <Default Extension="diff" ContentType="application/octet" />
155+ <Default Extension="bsdiff" ContentType="application/octet" />
156+ <Default Extension="shasum" ContentType="text/plain" />
157+ </Types>` . replace ( / \n / , "\r\n" ) , { name : "[Content_Types].xml" } )
158+
159+ archive . append ( `<?xml version="1.0" encoding="utf-8"?>
160+ <coreProperties xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
161+ xmlns="http://schemas.openxmlformats.org/package/2006/metadata/core-properties">
162+ <dc:creator>${ author } </dc:creator>
163+ <dc:description>${ options . description } </dc:description>
164+ <dc:identifier>${ options . name } </dc:identifier>
165+ <version>${ version } </version>
166+ <keywords/>
167+ <dc:title>${ options . productName } </dc:title>
168+ <lastModifiedBy>NuGet, Version=2.8.50926.602, Culture=neutral, PublicKeyToken=null;Microsoft Windows NT 6.2.9200.0;.NET Framework 4</lastModifiedBy>
169+ </coreProperties>` . replace ( / \n / , "\r\n" ) , { name : "1.psmdcp" , prefix : "package/services/metadata/core-properties" } )
170+
171+ archive . file ( updateFile , { name : "Update.exe" , prefix : "lib/net45" } )
172+ encodedZip ( archive , directory , "lib/net45" )
173+ await archivePromise
174+ }
175+
176+ async function releasify ( options : SquirrelOptions , nupkgPath : string , outputDirectory : string , packageName : string ) {
177+ const args = [
178+ "--releasify" , nupkgPath ,
179+ "--releaseDir" , outputDirectory
180+ ]
181+ const out = ( await exec ( process . platform === "win32" ? path . join ( options . vendorPath , "Update.com" ) : "mono" , prepareArgs ( args , path . join ( options . vendorPath , "Update-Mono.exe" ) ) ) ) . trim ( )
182+ if ( debug . enabled ) {
183+ debug ( out )
184+ }
185+
186+ const lines = out . split ( "\n" )
187+ for ( let i = lines . length - 1 ; i > - 1 ; i -- ) {
188+ const line = lines [ i ]
189+ if ( line . includes ( packageName ) ) {
190+ return line . trim ( )
191+ }
192+ }
193+
194+ throw new Error ( "Invalid output, cannot find last release entry" )
195+ }
196+
197+ async function msi ( options : SquirrelOptions , nupkgPath : string , setupPath : string , outputDirectory : string , outFile : string ) {
198+ const args = [
199+ "--createMsi" , nupkgPath ,
200+ "--bootstrapperExe" , setupPath
201+ ]
202+ await exec ( process . platform === "win32" ? path . join ( options . vendorPath , "Update.com" ) : "mono" , prepareArgs ( args , path . join ( options . vendorPath , "Update-Mono.exe" ) ) )
203+ //noinspection SpellCheckingInspection
204+ await exec ( path . join ( options . vendorPath , "candle.exe" ) , [ "-nologo" , "-ext" , "WixNetFxExtension" , "-out" , "Setup.wixobj" , "Setup.wxs" ] , {
205+ cwd : outputDirectory ,
206+ } )
207+ //noinspection SpellCheckingInspection
208+ await exec ( path . join ( options . vendorPath , "light.exe" ) , [ "-ext" , "WixNetFxExtension" , "-sval" , "-out" , outFile , "Setup.wixobj" ] , {
209+ cwd : outputDirectory ,
210+ } )
211+
212+ //noinspection SpellCheckingInspection
213+ await BluebirdPromise . all ( [
214+ unlink ( path . join ( outputDirectory , "Setup.wxs" ) ) ,
215+ unlink ( path . join ( outputDirectory , "Setup.wixobj" ) ) ,
216+ unlink ( path . join ( outputDirectory , outFile . replace ( ".msi" , ".wixpdb" ) ) ) . catch ( e => debug ( e . toString ( ) ) ) ,
217+ ] )
218+ }
219+
220+ function prepareArgs ( args : Array < string > , exePath : string ) {
221+ if ( process . platform !== "win32" ) {
222+ args . unshift ( exePath )
223+ }
224+ return args
225+ }
226+
227+ function encodedZip ( archive : any , dir : string , prefix : string ) {
228+ archiverUtil . walkdir ( dir , function ( error : any , files : any ) {
229+ if ( error ) {
230+ archive . emit ( "error" , error )
231+ return
232+ }
233+
234+ for ( let file of files ) {
235+ if ( file . stats . isDirectory ( ) ) {
236+ continue
237+ }
238+
239+ // GBK file name encoding (or Non-English file name) caused a problem
240+ const entryData = {
241+ name : encodeURI ( file . relative . replace ( / \\ / g, "/" ) ) . replace ( / % 5 B / g, "[" ) . replace ( / % 5 D / g, "]" ) ,
242+ prefix : prefix ,
243+ stats : file . stats ,
244+ }
245+ archive . _append ( file . path , entryData )
246+ }
247+
248+ archive . finalize ( )
249+ } )
250+ }
0 commit comments