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