Skip to content

Commit

Permalink
feat: integrating @electron/notarize into mac signing flow (#7310)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmaietta authored Dec 10, 2022
1 parent a117ccb commit 00d0dbc
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 81 deletions.
5 changes: 5 additions & 0 deletions .changeset/healthy-walls-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"app-builder-lib": minor
---

feat: integrating @electron/notarize into mac signing flow
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
- name: Test
run: pnpm ci:test
env:
TEST_FILES: masTest,dmgTest,protonTest,filesTest
TEST_FILES: masTest,dmgTest,protonTest,filesTest,macPackagerTest
FORCE_COLOR: 1

# Need to separate from other tests because logic is specific to when TOKEN env vars are set
Expand Down
8 changes: 6 additions & 2 deletions docs/configuration/mac.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,17 @@ The top-level [mac](configuration.md#Configuration-mac) key contains set of opti
<p>This option has no effect unless building for “universal” arch.</p>
</li>
<li>
<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>
<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>
<p>This option has no effect unless building for “universal” arch and applies only if <code>mergeASARs</code> is <code>true</code>.</p>
</li>
<li>
<p><code id="MacConfiguration-x64ArchFiles">x64ArchFiles</code> String - Minimatch pattern of paths that are allowed to be x64 binaries in both ASAR files</p>
<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>
<p>This option has no effect unless building for “universal” arch and applies only if <code>mergeASARs</code> is <code>true</code>.</p>
</li>
<li>
<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>
<p>Note: You MUST specify <code>APPLE_ID</code> and <code>APPLE_APP_SPECIFIC_PASSWORD</code> via environment variables to activate notarization step</p>
</li>
</ul>

<!-- end of generated block -->
Expand Down
3 changes: 2 additions & 1 deletion packages/app-builder-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@
"dependencies": {
"7zip-bin": "~5.1.1",
"@develar/schema-utils": "~2.6.5",
"@electron/notarize": "^1.2.3",
"@electron/osx-sign": "^1.0.4",
"@electron/rebuild": "^3.2.10",
"@electron/universal": "1.3.3",
"@electron/universal": "1.3.4",
"@malept/flatpak-bundler": "^0.4.0",
"async-exit-hook": "^2.0.1",
"bluebird-lst": "^1.0.9",
Expand Down
75 changes: 71 additions & 4 deletions packages/app-builder-lib/scheme.json
Original file line number Diff line number Diff line change
Expand Up @@ -2637,6 +2637,20 @@
"string"
]
},
"notarize": {
"anyOf": [
{
"$ref": "#/definitions/NotarizeOptions"
},
{
"type": [
"null",
"boolean"
]
}
],
"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"
},
"protocols": {
"anyOf": [
{
Expand Down Expand Up @@ -2756,7 +2770,10 @@
},
"singleArchFiles": {
"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`.",
"type": "string"
"type": [
"null",
"string"
]
},
"strictVerify": {
"anyOf": [
Expand Down Expand Up @@ -2856,7 +2873,10 @@
},
"x64ArchFiles": {
"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`.",
"type": "string"
"type": [
"null",
"string"
]
}
},
"type": "object"
Expand Down Expand Up @@ -3255,6 +3275,20 @@
"string"
]
},
"notarize": {
"anyOf": [
{
"$ref": "#/definitions/NotarizeOptions"
},
{
"type": [
"null",
"boolean"
]
}
],
"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"
},
"protocols": {
"anyOf": [
{
Expand Down Expand Up @@ -3374,7 +3408,10 @@
},
"singleArchFiles": {
"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`.",
"type": "string"
"type": [
"null",
"string"
]
},
"strictVerify": {
"anyOf": [
Expand Down Expand Up @@ -3474,7 +3511,10 @@
},
"x64ArchFiles": {
"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`.",
"type": "string"
"type": [
"null",
"string"
]
}
},
"type": "object"
Expand Down Expand Up @@ -3659,6 +3699,33 @@
},
"type": "object"
},
"NotarizeOptions": {
"additionalProperties": false,
"properties": {
"appBundleId": {
"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",
"type": [
"null",
"string"
]
},
"ascProvider": {
"description": "Your Team Short Name. Only used by `legacy` notarization tool",
"type": [
"null",
"string"
]
},
"teamId": {
"description": "The team ID you want to notarize under. Only needed if using `notarytool`",
"type": [
"null",
"string"
]
}
},
"type": "object"
},
"NsisOptions": {
"additionalProperties": false,
"properties": {
Expand Down
60 changes: 57 additions & 3 deletions packages/app-builder-lib/src/macPackager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BluebirdPromise from "bluebird-lst"
import { deepAssign, Arch, AsyncTaskManager, exec, InvalidConfigurationError, log, use, getArchSuffix } from "builder-util"
import { deepAssign, Arch, AsyncTaskManager, exec, InvalidConfigurationError, log, use, getArchSuffix, spawn } from "builder-util"
import { signAsync } from "@electron/osx-sign"
import { SignOptions } from "@electron/osx-sign/dist/cjs/types"
import { mkdir, readdir } from "fs/promises"
Expand All @@ -20,6 +20,7 @@ import { createCommonTarget, NoOpTarget } from "./targets/targetFactory"
import { isMacOsHighSierra } from "./util/macosVersion"
import { getTemplatePath } from "./util/pathManager"
import * as fs from "fs/promises"
import { notarize, NotarizeOptions } from "@electron/notarize"

export default class MacPackager extends PlatformPackager<MacConfiguration> {
readonly codeSigningInfo = new Lazy<CodeSigningInfo>(() => {
Expand Down Expand Up @@ -336,6 +337,8 @@ export default class MacPackager extends PlatformPackager<MacConfiguration> {
await this.doFlat(appPath, artifactPath, masInstallerIdentity, keychainFile)
await this.dispatchArtifactCreated(artifactPath, null, Arch.x64, this.computeSafeArtifactName(artifactName, "pkg", arch, true, this.platformSpecificBuildOptions.defaultArch))
}

await this.notarizeIfProvided(appPath)
}

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

await BluebirdPromise.map(readdir(packContext.appOutDir), (file: string): any => {
await BluebirdPromise.map(readdir(packContext.appOutDir), async (file: string): Promise<any> => {
if (file === appFileName) {
return this.sign(path.join(packContext.appOutDir, file), null, null, null)
const appPath = path.join(packContext.appOutDir, file)
await this.sign(appPath, null, null, null)
}
return null
})
Expand All @@ -463,6 +467,56 @@ export default class MacPackager extends PlatformPackager<MacConfiguration> {
}
})
}

private async notarizeIfProvided(appPath: string) {
const notarizeOptions = this.platformSpecificBuildOptions.notarize
if (notarizeOptions === false) {
log.info({ reason: "`notarizeOptions` is explicitly set to false" }, "skipped macOS notarization")
return
}
const appleId = process.env.APPLE_ID
const appleIdPassword = process.env.APPLE_APP_SPECIFIC_PASSWORD
if (!appleId && !appleIdPassword) {
// if no credentials provided, skip silently
return
}
if (!appleId) {
throw new InvalidConfigurationError(`APPLE_ID env var needs to be set`)
}
if (!appleIdPassword) {
throw new InvalidConfigurationError(`APPLE_APP_SPECIFIC_PASSWORD env var needs to be set`)
}
const options = this.generateOptions(appPath, appleId, appleIdPassword)
await notarize(options)
// Verify
await spawn("spctl", ["-a", "-t", "open", "--context", "context:primary-signature", "-v", `"${appPath}"`])
log.info(null, "notarization successful")
}

private generateOptions(appPath: string, appleId: string, appleIdPassword: string): NotarizeOptions {
const baseOptions = { appPath, appleId, appleIdPassword }
const options = this.platformSpecificBuildOptions.notarize
if (typeof options === "boolean") {
return {
...baseOptions,
tool: "legacy",
appBundleId: this.appInfo.id,
}
}
if (options?.teamId) {
return {
...baseOptions,
tool: "notarytool",
teamId: options.teamId,
}
}
return {
...baseOptions,
tool: "legacy",
appBundleId: options?.appBundleId || this.appInfo.id,
ascProvider: options?.ascProvider || undefined,
}
}
}

function getCertificateTypes(isMas: boolean, isDevelopment: boolean): CertType[] {
Expand Down
30 changes: 28 additions & 2 deletions packages/app-builder-lib/src/options/macOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export interface MacConfiguration extends PlatformSpecificBuildOptions {
* This option has no effect unless building for "universal" arch and applies
* only if `mergeASARs` is `true`.
*/
readonly singleArchFiles?: string
readonly singleArchFiles?: string | null

/**
* Minimatch pattern of paths that are allowed to be x64 binaries in both
Expand All @@ -203,7 +203,33 @@ export interface MacConfiguration extends PlatformSpecificBuildOptions {
* This option has no effect unless building for "universal" arch and applies
* only if `mergeASARs` is `true`.
*/
readonly x64ArchFiles?: string
readonly x64ArchFiles?: string | null

/**
* Options to use for @electron/notarize (ref: https://github.com/electron/notarize).
* Supports both `legacy` and `notarytool` notarization tools. Use `false` to explicitly disable
*
* Note: You MUST specify `APPLE_ID` and `APPLE_APP_SPECIFIC_PASSWORD` via environment variables to activate notarization step
*/
readonly notarize?: NotarizeOptions | boolean | null
}

export interface NotarizeOptions {
/**
* The app bundle identifier your Electron app is using. E.g. com.github.electron. Useful if notarization ID differs from app ID (unlikely).
* Only used by `legacy` notarization tool
*/
readonly appBundleId?: string | null

/**
* Your Team Short Name. Only used by `legacy` notarization tool
*/
readonly ascProvider?: string | null

/**
* The team ID you want to notarize under. Only needed if using `notarytool`
*/
readonly teamId?: string | null
}

export interface DmgOptions extends TargetSpecificOptions {
Expand Down
Loading

0 comments on commit 00d0dbc

Please sign in to comment.