Skip to content

Commit 0db9c66

Browse files
authored
feat(nsis): display "Space Required" text for NSIS installer (#7531)
1 parent dab3aeb commit 0db9c66

File tree

7 files changed

+93
-13
lines changed

7 files changed

+93
-13
lines changed

.changeset/modern-waves-compete.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"app-builder-lib": minor
3+
"builder-util": minor
4+
---
5+
6+
Display "Space required" text for NSIS installer

packages/app-builder-lib/src/targets/nsis/Defines.ts

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ export type Defines = {
4444
APP_ARM64_HASH?: string
4545
APP_32_HASH?: string
4646

47+
APP_64_UNPACKED_SIZE?: string
48+
APP_ARM64_UNPACKED_SIZE?: string
49+
APP_32_UNPACKED_SIZE?: string
50+
4751
REQUEST_EXECUTION_LEVEL?: PortableOptions["requestExecutionLevel"]
4852

4953
UNPACK_DIR_NAME?: string | false

packages/app-builder-lib/src/targets/nsis/NsisTarget.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ export class NsisTarget extends Target {
229229
defines.APP_BUILD_DIR = archs.get(archs.keys().next().value)
230230
} else {
231231
await BluebirdPromise.map(archs.keys(), async arch => {
232-
const fileInfo = await this.packageHelper.packArch(arch, this)
232+
const { fileInfo, unpackedSize } = await this.packageHelper.packArch(arch, this)
233233
const file = fileInfo.path
234234
const defineKey = arch === Arch.x64 ? "APP_64" : arch === Arch.arm64 ? "APP_ARM64" : "APP_32"
235235
defines[defineKey] = file
@@ -240,6 +240,10 @@ export class NsisTarget extends Target {
240240
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
241241
const defineHashKey = `${defineKey}_HASH` as "APP_64_HASH" | "APP_ARM64_HASH" | "APP_32_HASH"
242242
defines[defineHashKey] = Buffer.from(fileInfo.sha512, "base64").toString("hex").toUpperCase()
243+
// NSIS accepts size in KiloBytes and supports only whole numbers
244+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
245+
const defineUnpackedSizeKey = `${defineKey}_UNPACKED_SIZE` as "APP_64_UNPACKED_SIZE" | "APP_ARM64_UNPACKED_SIZE" | "APP_32_UNPACKED_SIZE"
246+
defines[defineUnpackedSizeKey] = Math.ceil(unpackedSize / 1024).toString()
243247

244248
if (this.isWebInstaller) {
245249
await packager.dispatchArtifactCreated(file, this, arch)

packages/app-builder-lib/src/targets/nsis/nsisUtil.ts

+21-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Arch, log } from "builder-util"
22
import { PackageFileInfo } from "builder-util-runtime"
33
import { getBinFromUrl, getBinFromCustomLoc } from "../../binDownload"
4-
import { copyFile } from "builder-util/out/fs"
4+
import { copyFile, dirSize } from "builder-util/out/fs"
55
import * as path from "path"
66
import { getTemplatePath } from "../../util/pathManager"
77
import { NsisTarget } from "./NsisTarget"
@@ -39,30 +39,42 @@ export const NSIS_PATH = () => {
3939
})
4040
}
4141

42+
export interface PackArchResult {
43+
fileInfo: PackageFileInfo
44+
unpackedSize: number
45+
}
46+
4247
export class AppPackageHelper {
43-
private readonly archToFileInfo = new Map<Arch, Promise<PackageFileInfo>>()
48+
private readonly archToResult = new Map<Arch, Promise<PackArchResult>>()
4449
private readonly infoToIsDelete = new Map<PackageFileInfo, boolean>()
4550

4651
/** @private */
4752
refCount = 0
4853

4954
constructor(private readonly elevateHelper: CopyElevateHelper) {}
5055

51-
async packArch(arch: Arch, target: NsisTarget): Promise<PackageFileInfo> {
52-
let infoPromise = this.archToFileInfo.get(arch)
53-
if (infoPromise == null) {
56+
async packArch(arch: Arch, target: NsisTarget): Promise<PackArchResult> {
57+
let resultPromise = this.archToResult.get(arch)
58+
if (resultPromise == null) {
5459
const appOutDir = target.archs.get(arch)!
55-
infoPromise = this.elevateHelper.copy(appOutDir, target).then(() => target.buildAppPackage(appOutDir, arch))
56-
this.archToFileInfo.set(arch, infoPromise)
60+
resultPromise = this.elevateHelper
61+
.copy(appOutDir, target)
62+
.then(() => target.buildAppPackage(appOutDir, arch))
63+
.then(async fileInfo => ({
64+
fileInfo,
65+
unpackedSize: await dirSize(appOutDir),
66+
}))
67+
this.archToResult.set(arch, resultPromise)
5768
}
5869

59-
const info = await infoPromise
70+
const result = await resultPromise
71+
const { fileInfo: info } = result
6072
if (target.isWebInstaller) {
6173
this.infoToIsDelete.set(info, false)
6274
} else if (!this.infoToIsDelete.has(info)) {
6375
this.infoToIsDelete.set(info, true)
6476
}
65-
return info
77+
return result
6678
}
6779

6880
async finishBuild(): Promise<any> {

packages/app-builder-lib/templates/nsis/common.nsh

+29-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
BrandingText "${PRODUCT_NAME} ${VERSION}"
55
ShowInstDetails nevershow
6-
SpaceTexts none
76
!ifdef BUILD_UNINSTALLER
87
ShowUninstDetails nevershow
98
!endif
@@ -13,6 +12,34 @@ Name "${PRODUCT_NAME}"
1312
!define APP_EXECUTABLE_FILENAME "${PRODUCT_FILENAME}.exe"
1413
!define UNINSTALL_FILENAME "Uninstall ${PRODUCT_FILENAME}.exe"
1514

15+
!macro setSpaceRequired SECTION_ID
16+
!ifdef APP_64_UNPACKED_SIZE
17+
!ifdef APP_32_UNPACKED_SIZE
18+
!ifdef APP_ARM64_UNPACKED_SIZE
19+
${if} ${IsNativeARM64}
20+
SectionSetSize ${SECTION_ID} ${APP_ARM64_UNPACKED_SIZE}
21+
${elseif} ${IsNativeAMD64}
22+
SectionSetSize ${SECTION_ID} ${APP_64_UNPACKED_SIZE}
23+
${else}
24+
SectionSetSize ${SECTION_ID} ${APP_32_UNPACKED_SIZE}
25+
${endif}
26+
!else
27+
${if} ${RunningX64}
28+
SectionSetSize ${SECTION_ID} ${APP_64_UNPACKED_SIZE}
29+
${else}
30+
SectionSetSize ${SECTION_ID} ${APP_32_UNPACKED_SIZE}
31+
${endif}
32+
!endif
33+
!else
34+
SectionSetSize ${SECTION_ID} ${APP_64_UNPACKED_SIZE}
35+
!endif
36+
!else
37+
!ifdef APP_32_UNPACKED_SIZE
38+
SectionSetSize ${SECTION_ID} ${APP_32_UNPACKED_SIZE}
39+
!endif
40+
!endif
41+
!macroend
42+
1643
!macro check64BitAndSetRegView
1744
# https://github.com/electron-userland/electron-builder/issues/2420
1845
${If} ${IsWin2000}
@@ -107,7 +134,7 @@ Name "${PRODUCT_NAME}"
107134
LogSet ${SETTING}
108135
!endif
109136
!macroend
110-
137+
111138
!define LogText "!insertmacro LogTextMacroEB"
112139
!macro LogTextMacroEB INPUT_TEXT
113140
!ifdef ENABLE_LOGGING_ELECTRON_BUILDER

packages/app-builder-lib/templates/nsis/installer.nsi

+7-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ Var oldMenuDirectory
4040
!endif
4141

4242
Function .onInit
43+
Call setInstallSectionSpaceRequired
44+
4345
SetOutPath $INSTDIR
4446
${LogSet} on
4547

@@ -82,7 +84,7 @@ FunctionEnd
8284
!include "installUtil.nsh"
8385
!endif
8486

85-
Section "install"
87+
Section "install" INSTALL_SECTION_ID
8688
!ifndef BUILD_UNINSTALLER
8789
# If we're running a silent upgrade of a per-machine installation, elevate so extracting the new app will succeed.
8890
# For a non-silent install, the elevation will be triggered when the install mode is selected in the UI,
@@ -114,6 +116,10 @@ Section "install"
114116
!endif
115117
SectionEnd
116118

119+
Function setInstallSectionSpaceRequired
120+
!insertmacro setSpaceRequired ${INSTALL_SECTION_ID}
121+
FunctionEnd
122+
117123
!ifdef BUILD_UNINSTALLER
118124
!include "uninstaller.nsh"
119125
!endif

packages/builder-util/src/fs.ts

+21
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,27 @@ export function copyDir(src: string, destination: string, options: CopyDirOption
306306
}).then(() => BluebirdPromise.map(links, it => symlink(it.link, it.file, symlinkType), CONCURRENCY))
307307
}
308308

309+
export async function dirSize(dirPath: string): Promise<number> {
310+
const entries = await readdir(dirPath, { withFileTypes: true })
311+
312+
const entrySizes = entries.map(async entry => {
313+
const entryPath = path.join(dirPath, entry.name)
314+
315+
if (entry.isDirectory()) {
316+
return await dirSize(entryPath)
317+
}
318+
319+
if (entry.isFile()) {
320+
const { size } = await stat(entryPath)
321+
return size
322+
}
323+
324+
return 0
325+
})
326+
327+
return (await Promise.all(entrySizes)).reduce((entrySize, totalSize) => entrySize + totalSize, 0)
328+
}
329+
309330
// eslint-disable-next-line @typescript-eslint/no-unused-vars
310331
export const DO_NOT_USE_HARD_LINKS = (file: string) => false
311332
// eslint-disable-next-line @typescript-eslint/no-unused-vars

0 commit comments

Comments
 (0)