Skip to content

Commit 16c8fa1

Browse files
authored
feat: allowing negative patterns to be provided for signExts as signing overrides (electron-userland#9335)
1 parent f0aaabf commit 16c8fa1

10 files changed

Lines changed: 47 additions & 21 deletions

File tree

.changeset/spicy-mails-sing.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"electron-builder-squirrel-windows": minor
3+
"app-builder-lib": minor
4+
---
5+
6+
feat: allowing negative patterns to be provided for `signExts` as signing overrides

packages/app-builder-lib/scheme.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6827,7 +6827,7 @@
68276827
}
68286828
],
68296829
"default": null,
6830-
"description": "Explicit file extensions to also sign. Advanced option."
6830+
"description": "Explicit file name/extensions (`str.endsWith`) to also sign. Advanced option.\nSupports negative patterns, e.g. example that excludes `.appx` files: `[\"somefilename\", \".dll\", \"!.appx\"]`."
68316831
},
68326832
"signtoolOptions": {
68336833
"anyOf": [

packages/app-builder-lib/src/options/winOptions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ export interface WindowsConfiguration extends PlatformSpecificBuildOptions {
6161
readonly signAndEditExecutable?: boolean
6262

6363
/**
64-
* Explicit file extensions to also sign. Advanced option.
64+
* Explicit file name/extensions (`str.endsWith`) to also sign. Advanced option.
65+
* Supports negative patterns, e.g. example that excludes `.appx` files: `["somefilename", ".dll", "!.appx"]`.
6566
* @see https://github.com/electron-userland/electron-builder/issues/7329
6667
* @default null
6768
*/

packages/app-builder-lib/src/targets/AppxTarget.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export default class AppXTarget extends Target {
149149
}
150150
this.buildQueueManager.add(async () => {
151151
await vm.exec(vm.toVmFile(path.join(vendorPath, "windows-10", signToolArch, "makeappx.exe")), makeAppXArgs)
152-
await packager.sign(artifactPath)
152+
await packager.signIf(artifactPath)
153153

154154
await stageDir.cleanup()
155155

packages/app-builder-lib/src/targets/MsiTarget.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export default class MsiTarget extends Target {
9999

100100
await stageDir.cleanup()
101101

102-
await packager.sign(artifactPath)
102+
await packager.signIf(artifactPath)
103103

104104
await packager.info.emitArtifactBuildCompleted({
105105
file: artifactPath,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ export class NsisTarget extends Target {
359359
defines.UNINSTALLER_OUT_FILE = definesUninstaller.UNINSTALLER_OUT_FILE
360360

361361
await this.executeMakensis(defines, commands, sharedHeader + (await this.computeFinalScript(script, true, archs)))
362-
await Promise.all<any>([packager.sign(installerPath), defines.UNINSTALLER_OUT_FILE == null ? Promise.resolve() : unlink(defines.UNINSTALLER_OUT_FILE)])
362+
await Promise.all<any>([packager.signIf(installerPath), defines.UNINSTALLER_OUT_FILE == null ? Promise.resolve() : unlink(defines.UNINSTALLER_OUT_FILE)])
363363

364364
const safeArtifactName = computeSafeArtifactNameIfNeeded(installerFilename, () => this.generateGitHubInstallerName(primaryArch, defaultArch))
365365
let updateInfo: any
@@ -438,7 +438,7 @@ export class NsisTarget extends Target {
438438
} else {
439439
await execWine(installerPath, null, [], { env: { __COMPAT_LAYER: "RunAsInvoker" } })
440440
}
441-
await packager.sign(uninstallerPath)
441+
await packager.signIf(uninstallerPath)
442442

443443
delete defines.BUILD_UNINSTALLER
444444
// platform-specific path, not wine

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export class CopyElevateHelper {
134134
const outFile = path.join(appOutDir, "resources", "elevate.exe")
135135
const promise = copyFile(path.join(it, "elevate.exe"), outFile, false)
136136
if (target.packager.platformSpecificBuildOptions.signAndEditExecutable !== false) {
137-
return promise.then(() => target.packager.sign(outFile))
137+
return promise.then(() => target.packager.signIf(outFile))
138138
}
139139
return promise
140140
})

packages/app-builder-lib/src/winPackager.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Arch, CopyFileTransformer, executeAppBuilder, FileTransformer, InvalidConfigurationError, use, walk } from "builder-util"
1+
import { Arch, CopyFileTransformer, executeAppBuilder, FileTransformer, InvalidConfigurationError, log, use, walk } from "builder-util"
22
import { Nullish } from "builder-util-runtime"
33
import { createHash } from "crypto"
44
import { readdir } from "fs/promises"
@@ -119,7 +119,12 @@ export class WinPackager extends PlatformPackager<WindowsConfiguration> {
119119
return chooseNotNull(chooseNotNull(this.platformSpecificBuildOptions.signtoolOptions?.certificatePassword, process.env.WIN_CSC_KEY_PASSWORD), super.doGetCscPassword())
120120
}
121121

122-
async sign(file: string): Promise<boolean> {
122+
async signIf(file: string): Promise<boolean> {
123+
if (!this.shouldSignFile(file, true)) {
124+
log.info({ file: log.filePath(file) }, "file signing skipped via signExts configuration")
125+
return false
126+
}
127+
123128
const signOptions: WindowsSignOptions = {
124129
path: file,
125130
options: this.platformSpecificBuildOptions,
@@ -207,17 +212,31 @@ export class WinPackager extends PlatformPackager<WindowsConfiguration> {
207212
await execWine(path.join(vendorPath, "rcedit-ia32.exe"), path.join(vendorPath, "rcedit-x64.exe"), args)
208213
}
209214

210-
await this.sign(file)
215+
await this.signIf(file)
211216
timer.end()
212217

213218
if (buildCacheManager != null) {
214219
await buildCacheManager.save()
215220
}
216221
}
217222

218-
private shouldSignFile(file: string): boolean {
219-
const shouldSignExplicit = !!this.platformSpecificBuildOptions.signExts?.some(ext => file.endsWith(ext))
220-
return shouldSignExplicit || file.endsWith(".exe")
223+
private shouldSignFile(file: string, fallbackValue = false): boolean {
224+
const backwardCompatibility = file.endsWith(".exe")
225+
const signExts = this.platformSpecificBuildOptions.signExts
226+
if (!signExts?.length) {
227+
return backwardCompatibility || fallbackValue
228+
}
229+
// process patterns ( !exe => exclude .exe, .dll => include .dll )
230+
// we process first to allow literal negatives in case a filename matches "help!.txt" or similar
231+
if (signExts.some(ext => file.endsWith(ext))) {
232+
return true
233+
}
234+
// process negative patterns
235+
if (signExts.some(ext => ext.startsWith("!") && file.endsWith(ext.substring(1)))) {
236+
return false
237+
}
238+
// if no explicit patterns matched, fall back to backward compatibility
239+
return backwardCompatibility || fallbackValue
221240
}
222241

223242
protected createTransformerForExtraFiles(packContext: AfterPackContext): FileTransformer | null {
@@ -229,7 +248,7 @@ export class WinPackager extends PlatformPackager<WindowsConfiguration> {
229248
if (this.shouldSignFile(file)) {
230249
const parentDir = path.dirname(file)
231250
if (parentDir !== packContext.appOutDir) {
232-
return new CopyFileTransformer(file => this.sign(file))
251+
return new CopyFileTransformer(file => this.signIf(file))
233252
}
234253
}
235254
return null
@@ -253,7 +272,7 @@ export class WinPackager extends PlatformPackager<WindowsConfiguration> {
253272
this.platformSpecificBuildOptions.requestedExecutionLevel
254273
)
255274
} else if (this.shouldSignFile(file)) {
256-
await this.sign(path.join(packContext.appOutDir, file))
275+
await this.signIf(path.join(packContext.appOutDir, file))
257276
}
258277
}
259278

@@ -267,7 +286,7 @@ export class WinPackager extends PlatformPackager<WindowsConfiguration> {
267286
}
268287
const filesToSign = await Promise.all([filesPromise(["resources", "app.asar.unpacked"]), filesPromise(["swiftshader"])])
269288
for (const file of filesToSign.flat(1)) {
270-
await this.sign(file)
289+
await this.signIf(file)
271290
}
272291

273292
return true

packages/electron-builder-squirrel-windows/src/SquirrelWindowsTarget.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export default class SquirrelWindowsTarget extends Target {
4949
if (squirrelExe) {
5050
const filePath = path.join(tmpVendorDirectory, squirrelExe)
5151
log.debug({ file: filePath }, "signing vendor executable")
52-
await this.packager.sign(filePath)
52+
await this.packager.signIf(filePath)
5353
} else {
5454
log.warn("Squirrel.exe not found in vendor directory, skipping signing")
5555
}
@@ -67,9 +67,9 @@ export default class SquirrelWindowsTarget extends Target {
6767
const stubExePath = path.join(appOutDir, `${this.exeName}_ExecutionStub.exe`)
6868
await fs.promises.copyFile(path.join(vendorDir, "StubExecutable.exe"), stubExePath)
6969
await execWine(path.join(vendorDir, "WriteZipToSetup.exe"), null, ["--copy-stub-resources", filePath, stubExePath])
70-
await this.packager.sign(stubExePath)
70+
await this.packager.signIf(stubExePath)
7171
log.debug({ file: filePath }, "signing app executable")
72-
await this.packager.sign(filePath)
72+
await this.packager.signIf(filePath)
7373
}
7474

7575
async build(appOutDir: string, arch: Arch) {
@@ -95,7 +95,7 @@ export default class SquirrelWindowsTarget extends Target {
9595
await packager.signAndEditResources(artifactPath, arch, installerOutDir)
9696

9797
if (this.options.msi) {
98-
await packager.sign(msiArtifactPath)
98+
await packager.signIf(msiArtifactPath)
9999
}
100100

101101
const safeArtifactName = (ext: string) => `${sanitizedName}-Setup-${version}${getArchSuffix(arch)}.${ext}`

test/src/helpers/CheckingPackager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class CheckingWinPackager extends WinPackager {
2323
const setupFile = this.expandArtifactNamePattern(newClass.options, "exe", arch, "${productName} Setup ${version}.${ext}")
2424
const installerOutDir = path.join(outDir, `squirrel-windows${getArchSuffix(arch)}`)
2525
this.effectiveDistOptions = await newClass.computeEffectiveDistOptions(installerOutDir, outDir, setupFile)
26-
await this.sign(this.computeAppOutDir(outDir, arch))
26+
await this.signIf(this.computeAppOutDir(outDir, arch))
2727
}
2828

2929
//noinspection JSUnusedLocalSymbols

0 commit comments

Comments
 (0)