Skip to content

Commit cdf18d9

Browse files
authored
feat(docker): support pwsh for running codesigning (#8787)
1 parent bd185eb commit cdf18d9

13 files changed

Lines changed: 172 additions & 76 deletions

File tree

.changeset/violet-dancers-bow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"app-builder-lib": minor
3+
---
4+
5+
feat: add `pwsh` detection to enable azure trusted signing within docker image

.github/workflows/test.yaml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ jobs:
7171
testFiles:
7272
- ArtifactPublisherTest,BuildTest,ExtraBuildTest,RepoSlugTest,binDownloadTest,configurationValidationTest,filenameUtilTest,filesTest,globTest,ignoreTest,macroExpanderTest,mainEntryTest,urlUtilTest,extraMetadataTest,linuxArchiveTest,linuxPackagerTest,HoistedNodeModuleTest,MemoLazyTest
7373
- snapTest,debTest,fpmTest,protonTest
74-
- winPackagerTest,installerTest,BuildTest
74+
- winPackagerTest,installerTest,BuildTest,winCodeSignTest
7575
steps:
7676
- name: Checkout code repository
7777
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
@@ -165,7 +165,7 @@ jobs:
165165
matrix:
166166
testFiles:
167167
- winCodeSignTest,differentialUpdateTest
168-
- installerTest,appxTest,msiTest,portableTest,assistedInstallerTest,protonTest
168+
- appxTest,msiTest,portableTest,assistedInstallerTest,protonTest
169169
- BuildTest,oneClickInstallerTest,winPackagerTest,nsisUpdaterTest,webInstallerTest
170170
steps:
171171
- name: Checkout code repository
@@ -186,6 +186,12 @@ jobs:
186186

187187
test-mac:
188188
runs-on: macos-latest
189+
strategy:
190+
fail-fast: false
191+
matrix:
192+
testFiles:
193+
- winPackagerTest,installerTest,winCodeSignTest
194+
- masTest,dmgTest,filesTest,macPackagerTest,differentialUpdateTest
189195
steps:
190196
- name: Checkout code repository
191197
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
@@ -196,8 +202,13 @@ jobs:
196202
cache-path: ~/Library/Caches/electron
197203
cache-key: v-23.3.10-macos-electron
198204

205+
- name: Install pwsh and wine via brew
206+
run: |
207+
brew install powershell/tap/powershell
208+
brew install --cask wine-stable
209+
199210
- name: Test
200211
run: pnpm ci:test
201212
env:
202-
TEST_FILES: masTest,dmgTest,filesTest,macPackagerTest,differentialUpdateTest
213+
TEST_FILES: ${{ matrix.testFiles }}
203214
FORCE_COLOR: 1

packages/app-builder-lib/scheme.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6226,7 +6226,7 @@
62266226
"type": "null"
62276227
}
62286228
],
6229-
"description": "Options for usage of Azure Trusted Signing (beta)"
6229+
"description": "Options for usage of Azure Trusted Signing (beta)\nCannot be used in conjunction with `signtoolOptions`, signing will default to Azure Trusted Signing"
62306230
},
62316231
"compression": {
62326232
"anyOf": [
@@ -6525,7 +6525,7 @@
65256525
"type": "null"
65266526
}
65276527
],
6528-
"description": "Options for usage with signtool.exe"
6528+
"description": "Options for usage with signtool.exe\nCannot be used in conjunction with `azureSignOptions`, signing will default to Azure Trusted Signing"
65296529
},
65306530
"target": {
65316531
"anyOf": [
Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { log, retry } from "builder-util"
22
import { WindowsConfiguration } from "../options/winOptions"
3-
import { VmManager } from "../vm/vm"
43
import { WinPackager } from "../winPackager"
54

65
export interface WindowsSignOptions {
@@ -9,7 +8,6 @@ export interface WindowsSignOptions {
98
}
109

1110
export async function signWindows(options: WindowsSignOptions, packager: WinPackager): Promise<boolean> {
12-
const packageManager = await packager.signingManager.value
1311
if (options.options.azureSignOptions) {
1412
if (options.options.signtoolOptions) {
1513
log.warn(null, "ignoring signtool options, using Azure Trusted Signing; please only configure one")
@@ -18,7 +16,7 @@ export async function signWindows(options: WindowsSignOptions, packager: WinPack
1816
} else {
1917
log.info({ path: log.filePath(options.path) }, "signing with signtool.exe")
2018
}
21-
19+
const packageManager = await packager.signingManager.value
2220
return signWithRetry(async () => packageManager.signFile(options))
2321
}
2422

@@ -37,16 +35,3 @@ function signWithRetry(signer: () => Promise<boolean>): Promise<boolean> {
3735
return false
3836
})
3937
}
40-
41-
export async function getPSCmd(vm: VmManager): Promise<string> {
42-
return await vm
43-
.exec("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `Get-Command pwsh.exe`])
44-
.then(() => {
45-
log.debug(null, "identified pwsh.exe for executing code signing")
46-
return "pwsh.exe"
47-
})
48-
.catch(() => {
49-
log.debug(null, "unable to find pwsh.exe, falling back to powershell.exe")
50-
return "powershell.exe"
51-
})
52-
}

packages/app-builder-lib/src/codeSign/windowsSignAzureManager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { asArray, InvalidConfigurationError, log } from "builder-util"
22
import { WindowsAzureSigningConfiguration, WindowsConfiguration } from "../options/winOptions"
33
import { WinPackager } from "../winPackager"
4-
import { getPSCmd, WindowsSignOptions } from "./windowsCodeSign"
4+
import { WindowsSignOptions } from "./windowsCodeSign"
55
import { Lazy } from "lazy-val"
66
import { SignManager } from "./signManager"
77
import { MemoLazy } from "builder-util-runtime"
@@ -29,7 +29,7 @@ export class WindowsSignAzureManager implements SignManager {
2929

3030
async initialize() {
3131
const vm = await this.packager.vm.value
32-
const ps = await getPSCmd(vm)
32+
const ps = await vm.powershellCommand.value
3333

3434
log.info(null, "installing required module (TrustedSigning) with scope CurrentUser")
3535
try {
@@ -105,7 +105,7 @@ export class WindowsSignAzureManager implements SignManager {
105105
// prerequisite: requires `initializeProviderModules` to already have been executed
106106
async signFile(options: WindowsSignOptions): Promise<boolean> {
107107
const vm = await this.packager.vm.value
108-
const ps = await getPSCmd(vm)
108+
const ps = await vm.powershellCommand.value
109109

110110
const {
111111
publisherName: _publisher, // extract from `extraSigningArgs`

packages/app-builder-lib/src/codeSign/windowsSignToolManager.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import { isUseSystemSigncode } from "../util/flags"
1111
import { VmManager } from "../vm/vm"
1212
import { WinPackager } from "../winPackager"
1313
import { WindowsSignOptions } from "./windowsCodeSign"
14-
import { getPSCmd } from "./windowsCodeSign"
1514
import { MemoLazy, parseDn } from "builder-util-runtime"
1615
import { Lazy } from "lazy-val"
1716
import { importCertificate } from "./codesign"
@@ -395,7 +394,7 @@ export class WindowsSignToolManager implements SignManager {
395394
const certificateSubjectName = options.signtoolOptions?.certificateSubjectName
396395
const certificateSha1 = options.signtoolOptions?.certificateSha1?.toUpperCase()
397396

398-
const ps = await getPSCmd(vm)
397+
const ps = await vm.powershellCommand.value
399398
const rawResult = await vm.exec(ps, [
400399
"-NoProfile",
401400
"-NonInteractive",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ export interface WindowsConfiguration extends PlatformSpecificBuildOptions {
2525

2626
/**
2727
* Options for usage with signtool.exe
28+
* Cannot be used in conjunction with `azureSignOptions`, signing will default to Azure Trusted Signing
2829
*/
2930
readonly signtoolOptions?: WindowsSigntoolConfiguration | null
3031

3132
/**
3233
* Options for usage of Azure Trusted Signing (beta)
34+
* Cannot be used in conjunction with `signtoolOptions`, signing will default to Azure Trusted Signing
3335
*/
3436
readonly azureSignOptions?: WindowsAzureSigningConfiguration | null
3537

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Lazy } from "lazy-val"
2+
import { isPwshAvailable, VmManager } from "./vm"
3+
import { log } from "builder-util"
4+
5+
export class PwshVmManager extends VmManager {
6+
constructor() {
7+
super()
8+
}
9+
10+
readonly powershellCommand = new Lazy<string>(async () => {
11+
log.info(null, "checking for `pwsh` for powershell")
12+
if (await isPwshAvailable.value) {
13+
return "pwsh"
14+
}
15+
const errorMessage = `unable to find \`pwsh\`, please install per instructions linked in logs`
16+
log.error(
17+
{
18+
mac: "https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-macos",
19+
linux: "https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux",
20+
},
21+
errorMessage
22+
)
23+
throw new Error(errorMessage)
24+
})
25+
}

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

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { DebugLogger, exec, ExtraSpawnOptions, InvalidConfigurationError, spawn } from "builder-util"
1+
import { DebugLogger, exec, ExtraSpawnOptions, InvalidConfigurationError, log, spawn } from "builder-util"
22
import { ExecFileOptions, SpawnOptions } from "child_process"
3+
import { Lazy } from "lazy-val"
34
import * as path from "path"
4-
5+
import { ParallelsVm } from "./ParallelsVm"
56
export class VmManager {
67
get pathSep(): string {
78
return path.sep
@@ -18,15 +19,52 @@ export class VmManager {
1819
toVmFile(file: string): string {
1920
return file
2021
}
22+
23+
readonly powershellCommand = new Lazy(() => {
24+
return this.exec("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `Get-Command pwsh.exe`])
25+
.then(() => {
26+
log.info(null, "identified pwsh.exe")
27+
return "pwsh.exe"
28+
})
29+
.catch(() => {
30+
log.info(null, "unable to find pwsh.exe, falling back to powershell.exe")
31+
return "powershell.exe"
32+
})
33+
})
2134
}
2235

2336
export async function getWindowsVm(debugLogger: DebugLogger): Promise<VmManager> {
2437
const parallelsVmModule = await import("./ParallelsVm")
25-
const vmList = (await parallelsVmModule.parseVmList(debugLogger)).filter(it => ["win-10", "win-11"].includes(it.os))
38+
let vmList: ParallelsVm[] = []
39+
try {
40+
vmList = (await parallelsVmModule.parseVmList(debugLogger)).filter(it => ["win-10", "win-11"].includes(it.os))
41+
} catch (_error) {
42+
if ((await isPwshAvailable.value) && (await isWineAvailable.value)) {
43+
const vmModule = await import("./PwshVm")
44+
return new vmModule.PwshVmManager()
45+
}
46+
}
2647
if (vmList.length === 0) {
27-
throw new InvalidConfigurationError("Cannot find suitable Parallels Desktop virtual machine (Windows 10 is required)")
48+
throw new InvalidConfigurationError("Cannot find suitable Parallels Desktop virtual machine (Windows 10 is required) and cannot access `pwsh` and `wine` locally")
2849
}
2950

3051
// prefer running or suspended vm
3152
return new parallelsVmModule.ParallelsVmManager(vmList.find(it => it.state === "running") || vmList.find(it => it.state === "suspended") || vmList[0])
3253
}
54+
55+
const isWineAvailable = new Lazy(async () => {
56+
return isCommandAvailable("wine", ["--version"])
57+
})
58+
59+
export const isPwshAvailable = new Lazy(async () => {
60+
return isCommandAvailable("pwsh", ["--version"])
61+
})
62+
63+
export const isCommandAvailable = async (command: string, args: string[]) => {
64+
try {
65+
await exec(command, args)
66+
return true
67+
} catch {
68+
return false
69+
}
70+
}

test/snapshots/windows/winCodeSignTest.js.snap

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`azure signing without credentials 1`] = `"ERR_ELECTRON_BUILDER_INVALID_CONFIGURATION"`;
4-
53
exports[`electronDist 1`] = `"ENOENT"`;
64

75
exports[`forceCodeSigning 1`] = `"ERR_ELECTRON_BUILDER_INVALID_CONFIGURATION"`;
@@ -14,3 +12,9 @@ Map {
1412
"C" => "GB",
1513
}
1614
`;
15+
16+
exports[`win code sign using pwsh 1`] = `
17+
{
18+
"win": [],
19+
}
20+
`;

0 commit comments

Comments
 (0)