Skip to content

Commit 00d0dbc

Browse files
authored
feat: integrating @electron/notarize into mac signing flow (#7310)
1 parent a117ccb commit 00d0dbc

File tree

10 files changed

+248
-81
lines changed

10 files changed

+248
-81
lines changed

.changeset/healthy-walls-peel.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"app-builder-lib": minor
3+
---
4+
5+
feat: integrating @electron/notarize into mac signing flow

.github/workflows/test.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
- name: Test
7070
run: pnpm ci:test
7171
env:
72-
TEST_FILES: masTest,dmgTest,protonTest,filesTest
72+
TEST_FILES: masTest,dmgTest,protonTest,filesTest,macPackagerTest
7373
FORCE_COLOR: 1
7474

7575
# Need to separate from other tests because logic is specific to when TOKEN env vars are set

docs/configuration/mac.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,17 @@ The top-level [mac](configuration.md#Configuration-mac) key contains set of opti
9797
<p>This option has no effect unless building for “universal” arch.</p>
9898
</li>
9999
<li>
100-
<p><code id="MacConfiguration-singleArchFiles">singleArchFiles</code> String - Minimatch pattern of paths that are allowed to be present in one of the ASAR files, but not in the other.</p>
100+
<p><code id="MacConfiguration-singleArchFiles">singleArchFiles</code> String | “undefined” - Minimatch pattern of paths that are allowed to be present in one of the ASAR files, but not in the other.</p>
101101
<p>This option has no effect unless building for “universal” arch and applies only if <code>mergeASARs</code> is <code>true</code>.</p>
102102
</li>
103103
<li>
104-
<p><code id="MacConfiguration-x64ArchFiles">x64ArchFiles</code> String - Minimatch pattern of paths that are allowed to be x64 binaries in both ASAR files</p>
104+
<p><code id="MacConfiguration-x64ArchFiles">x64ArchFiles</code> String | “undefined” - Minimatch pattern of paths that are allowed to be x64 binaries in both ASAR files</p>
105105
<p>This option has no effect unless building for “universal” arch and applies only if <code>mergeASARs</code> is <code>true</code>.</p>
106106
</li>
107+
<li>
108+
<p><code id="MacConfiguration-notarize">notarize</code> module:app-builder-lib/out/options/macOptions.NotarizeOptions | Boolean | “undefined” - Options to use for @electron/notarize (ref: <a href="https://github.com/electron/notarize">https://github.com/electron/notarize</a>). Supports both <code>legacy</code> and <code>notarytool</code> notarization tools. Use <code>false</code> to explicitly disable</p>
109+
<p>Note: You MUST specify <code>APPLE_ID</code> and <code>APPLE_APP_SPECIFIC_PASSWORD</code> via environment variables to activate notarization step</p>
110+
</li>
107111
</ul>
108112

109113
<!-- end of generated block -->

packages/app-builder-lib/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@
4949
"dependencies": {
5050
"7zip-bin": "~5.1.1",
5151
"@develar/schema-utils": "~2.6.5",
52+
"@electron/notarize": "^1.2.3",
5253
"@electron/osx-sign": "^1.0.4",
5354
"@electron/rebuild": "^3.2.10",
54-
"@electron/universal": "1.3.3",
55+
"@electron/universal": "1.3.4",
5556
"@malept/flatpak-bundler": "^0.4.0",
5657
"async-exit-hook": "^2.0.1",
5758
"bluebird-lst": "^1.0.9",

packages/app-builder-lib/scheme.json

+71-4
Original file line numberDiff line numberDiff line change
@@ -2637,6 +2637,20 @@
26372637
"string"
26382638
]
26392639
},
2640+
"notarize": {
2641+
"anyOf": [
2642+
{
2643+
"$ref": "#/definitions/NotarizeOptions"
2644+
},
2645+
{
2646+
"type": [
2647+
"null",
2648+
"boolean"
2649+
]
2650+
}
2651+
],
2652+
"description": "Options to use for @electron/notarize (ref: https://github.com/electron/notarize).\nSupports both `legacy` and `notarytool` notarization tools. Use `false` to explicitly disable\n\nNote: You MUST specify `APPLE_ID` and `APPLE_APP_SPECIFIC_PASSWORD` via environment variables to activate notarization step"
2653+
},
26402654
"protocols": {
26412655
"anyOf": [
26422656
{
@@ -2756,7 +2770,10 @@
27562770
},
27572771
"singleArchFiles": {
27582772
"description": "Minimatch pattern of paths that are allowed to be present in one of the\nASAR files, but not in the other.\n\nThis option has no effect unless building for \"universal\" arch and applies\nonly if `mergeASARs` is `true`.",
2759-
"type": "string"
2773+
"type": [
2774+
"null",
2775+
"string"
2776+
]
27602777
},
27612778
"strictVerify": {
27622779
"anyOf": [
@@ -2856,7 +2873,10 @@
28562873
},
28572874
"x64ArchFiles": {
28582875
"description": "Minimatch pattern of paths that are allowed to be x64 binaries in both\nASAR files\n\nThis option has no effect unless building for \"universal\" arch and applies\nonly if `mergeASARs` is `true`.",
2859-
"type": "string"
2876+
"type": [
2877+
"null",
2878+
"string"
2879+
]
28602880
}
28612881
},
28622882
"type": "object"
@@ -3255,6 +3275,20 @@
32553275
"string"
32563276
]
32573277
},
3278+
"notarize": {
3279+
"anyOf": [
3280+
{
3281+
"$ref": "#/definitions/NotarizeOptions"
3282+
},
3283+
{
3284+
"type": [
3285+
"null",
3286+
"boolean"
3287+
]
3288+
}
3289+
],
3290+
"description": "Options to use for @electron/notarize (ref: https://github.com/electron/notarize).\nSupports both `legacy` and `notarytool` notarization tools. Use `false` to explicitly disable\n\nNote: You MUST specify `APPLE_ID` and `APPLE_APP_SPECIFIC_PASSWORD` via environment variables to activate notarization step"
3291+
},
32583292
"protocols": {
32593293
"anyOf": [
32603294
{
@@ -3374,7 +3408,10 @@
33743408
},
33753409
"singleArchFiles": {
33763410
"description": "Minimatch pattern of paths that are allowed to be present in one of the\nASAR files, but not in the other.\n\nThis option has no effect unless building for \"universal\" arch and applies\nonly if `mergeASARs` is `true`.",
3377-
"type": "string"
3411+
"type": [
3412+
"null",
3413+
"string"
3414+
]
33783415
},
33793416
"strictVerify": {
33803417
"anyOf": [
@@ -3474,7 +3511,10 @@
34743511
},
34753512
"x64ArchFiles": {
34763513
"description": "Minimatch pattern of paths that are allowed to be x64 binaries in both\nASAR files\n\nThis option has no effect unless building for \"universal\" arch and applies\nonly if `mergeASARs` is `true`.",
3477-
"type": "string"
3514+
"type": [
3515+
"null",
3516+
"string"
3517+
]
34783518
}
34793519
},
34803520
"type": "object"
@@ -3659,6 +3699,33 @@
36593699
},
36603700
"type": "object"
36613701
},
3702+
"NotarizeOptions": {
3703+
"additionalProperties": false,
3704+
"properties": {
3705+
"appBundleId": {
3706+
"description": "The app bundle identifier your Electron app is using. E.g. com.github.electron. Useful if notarization ID differs from app ID (unlikely).\nOnly used by `legacy` notarization tool",
3707+
"type": [
3708+
"null",
3709+
"string"
3710+
]
3711+
},
3712+
"ascProvider": {
3713+
"description": "Your Team Short Name. Only used by `legacy` notarization tool",
3714+
"type": [
3715+
"null",
3716+
"string"
3717+
]
3718+
},
3719+
"teamId": {
3720+
"description": "The team ID you want to notarize under. Only needed if using `notarytool`",
3721+
"type": [
3722+
"null",
3723+
"string"
3724+
]
3725+
}
3726+
},
3727+
"type": "object"
3728+
},
36623729
"NsisOptions": {
36633730
"additionalProperties": false,
36643731
"properties": {

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

+57-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import BluebirdPromise from "bluebird-lst"
2-
import { deepAssign, Arch, AsyncTaskManager, exec, InvalidConfigurationError, log, use, getArchSuffix } from "builder-util"
2+
import { deepAssign, Arch, AsyncTaskManager, exec, InvalidConfigurationError, log, use, getArchSuffix, spawn } from "builder-util"
33
import { signAsync } from "@electron/osx-sign"
44
import { SignOptions } from "@electron/osx-sign/dist/cjs/types"
55
import { mkdir, readdir } from "fs/promises"
@@ -20,6 +20,7 @@ import { createCommonTarget, NoOpTarget } from "./targets/targetFactory"
2020
import { isMacOsHighSierra } from "./util/macosVersion"
2121
import { getTemplatePath } from "./util/pathManager"
2222
import * as fs from "fs/promises"
23+
import { notarize, NotarizeOptions } from "@electron/notarize"
2324

2425
export default class MacPackager extends PlatformPackager<MacConfiguration> {
2526
readonly codeSigningInfo = new Lazy<CodeSigningInfo>(() => {
@@ -336,6 +337,8 @@ export default class MacPackager extends PlatformPackager<MacConfiguration> {
336337
await this.doFlat(appPath, artifactPath, masInstallerIdentity, keychainFile)
337338
await this.dispatchArtifactCreated(artifactPath, null, Arch.x64, this.computeSafeArtifactName(artifactName, "pkg", arch, true, this.platformSpecificBuildOptions.defaultArch))
338339
}
340+
341+
await this.notarizeIfProvided(appPath)
339342
}
340343

341344
private async adjustSignOptions(signOptions: any, masOptions: MasConfiguration | null) {
@@ -443,9 +446,10 @@ export default class MacPackager extends PlatformPackager<MacConfiguration> {
443446
protected async signApp(packContext: AfterPackContext, isAsar: boolean): Promise<any> {
444447
const appFileName = `${this.appInfo.productFilename}.app`
445448

446-
await BluebirdPromise.map(readdir(packContext.appOutDir), (file: string): any => {
449+
await BluebirdPromise.map(readdir(packContext.appOutDir), async (file: string): Promise<any> => {
447450
if (file === appFileName) {
448-
return this.sign(path.join(packContext.appOutDir, file), null, null, null)
451+
const appPath = path.join(packContext.appOutDir, file)
452+
await this.sign(appPath, null, null, null)
449453
}
450454
return null
451455
})
@@ -463,6 +467,56 @@ export default class MacPackager extends PlatformPackager<MacConfiguration> {
463467
}
464468
})
465469
}
470+
471+
private async notarizeIfProvided(appPath: string) {
472+
const notarizeOptions = this.platformSpecificBuildOptions.notarize
473+
if (notarizeOptions === false) {
474+
log.info({ reason: "`notarizeOptions` is explicitly set to false" }, "skipped macOS notarization")
475+
return
476+
}
477+
const appleId = process.env.APPLE_ID
478+
const appleIdPassword = process.env.APPLE_APP_SPECIFIC_PASSWORD
479+
if (!appleId && !appleIdPassword) {
480+
// if no credentials provided, skip silently
481+
return
482+
}
483+
if (!appleId) {
484+
throw new InvalidConfigurationError(`APPLE_ID env var needs to be set`)
485+
}
486+
if (!appleIdPassword) {
487+
throw new InvalidConfigurationError(`APPLE_APP_SPECIFIC_PASSWORD env var needs to be set`)
488+
}
489+
const options = this.generateOptions(appPath, appleId, appleIdPassword)
490+
await notarize(options)
491+
// Verify
492+
await spawn("spctl", ["-a", "-t", "open", "--context", "context:primary-signature", "-v", `"${appPath}"`])
493+
log.info(null, "notarization successful")
494+
}
495+
496+
private generateOptions(appPath: string, appleId: string, appleIdPassword: string): NotarizeOptions {
497+
const baseOptions = { appPath, appleId, appleIdPassword }
498+
const options = this.platformSpecificBuildOptions.notarize
499+
if (typeof options === "boolean") {
500+
return {
501+
...baseOptions,
502+
tool: "legacy",
503+
appBundleId: this.appInfo.id,
504+
}
505+
}
506+
if (options?.teamId) {
507+
return {
508+
...baseOptions,
509+
tool: "notarytool",
510+
teamId: options.teamId,
511+
}
512+
}
513+
return {
514+
...baseOptions,
515+
tool: "legacy",
516+
appBundleId: options?.appBundleId || this.appInfo.id,
517+
ascProvider: options?.ascProvider || undefined,
518+
}
519+
}
466520
}
467521

468522
function getCertificateTypes(isMas: boolean, isDevelopment: boolean): CertType[] {

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

+28-2
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export interface MacConfiguration extends PlatformSpecificBuildOptions {
194194
* This option has no effect unless building for "universal" arch and applies
195195
* only if `mergeASARs` is `true`.
196196
*/
197-
readonly singleArchFiles?: string
197+
readonly singleArchFiles?: string | null
198198

199199
/**
200200
* Minimatch pattern of paths that are allowed to be x64 binaries in both
@@ -203,7 +203,33 @@ export interface MacConfiguration extends PlatformSpecificBuildOptions {
203203
* This option has no effect unless building for "universal" arch and applies
204204
* only if `mergeASARs` is `true`.
205205
*/
206-
readonly x64ArchFiles?: string
206+
readonly x64ArchFiles?: string | null
207+
208+
/**
209+
* Options to use for @electron/notarize (ref: https://github.com/electron/notarize).
210+
* Supports both `legacy` and `notarytool` notarization tools. Use `false` to explicitly disable
211+
*
212+
* Note: You MUST specify `APPLE_ID` and `APPLE_APP_SPECIFIC_PASSWORD` via environment variables to activate notarization step
213+
*/
214+
readonly notarize?: NotarizeOptions | boolean | null
215+
}
216+
217+
export interface NotarizeOptions {
218+
/**
219+
* The app bundle identifier your Electron app is using. E.g. com.github.electron. Useful if notarization ID differs from app ID (unlikely).
220+
* Only used by `legacy` notarization tool
221+
*/
222+
readonly appBundleId?: string | null
223+
224+
/**
225+
* Your Team Short Name. Only used by `legacy` notarization tool
226+
*/
227+
readonly ascProvider?: string | null
228+
229+
/**
230+
* The team ID you want to notarize under. Only needed if using `notarytool`
231+
*/
232+
readonly teamId?: string | null
207233
}
208234

209235
export interface DmgOptions extends TargetSpecificOptions {

0 commit comments

Comments
 (0)