diff --git a/buildResources/entitlements.inherit.plist b/buildResources/entitlements.inherit.plist index 8c3993f..a7a925c 100644 --- a/buildResources/entitlements.inherit.plist +++ b/buildResources/entitlements.inherit.plist @@ -4,6 +4,8 @@ com.apple.security.app-sandbox +com.apple.security.cs.allow-unsigned-executable-memory + com.apple.security.inherit diff --git a/buildResources/entitlements.plist b/buildResources/entitlements.plist index 53effcb..44d4e94 100644 --- a/buildResources/entitlements.plist +++ b/buildResources/entitlements.plist @@ -4,6 +4,8 @@ com.apple.security.app-sandbox +com.apple.security.cs.allow-unsigned-executable-memory + com.apple.security.device.audio-input com.apple.security.device.camera diff --git a/electron-builder.yaml b/electron-builder.yaml index aaf7c39..8ff6017 100644 --- a/electron-builder.yaml +++ b/electron-builder.yaml @@ -3,8 +3,10 @@ directories: output: dist appId: edu.mit.scratch.scratch-desktop productName: "Scratch Desktop" +afterSign: "scripts/afterSign.js" mac: category: public.app-category.education + hardenedRuntime: true icon: buildResources/ScratchDesktop.icns provisioningProfile: embedded.provisionprofile target: diff --git a/package-lock.json b/package-lock.json index 06a048b..d9ef948 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4317,6 +4317,29 @@ } } }, + "electron-notarize": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/electron-notarize/-/electron-notarize-0.2.1.tgz", + "integrity": "sha512-oZ6/NhKeXmEKNROiFmRNfytqu3cxqC95sjooG7kBXQVEUSQkZnbiAhxVh5jXngL881G197pbwpeVPJyM7Ikmxw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, "electron-publish": { "version": "22.2.0", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.2.0.tgz", diff --git a/package.json b/package.json index 8299b6c..70001c5 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "electron": "^6.1.7", "electron-builder": "^22.2.0", "electron-devtools-installer": "^2.2.4", + "electron-notarize": "^0.2.1", "electron-store": "^3.3.0", "electron-webpack": "^2.7.4", "eslint": "^5.16.0", diff --git a/scripts/afterSign.js b/scripts/afterSign.js new file mode 100644 index 0000000..d544191 --- /dev/null +++ b/scripts/afterSign.js @@ -0,0 +1,42 @@ +const {notarize} = require('electron-notarize'); + +const notarizeMacBuild = async function (context) { + // keep this in sync with appId in the electron-builder config + const appId = 'edu.mit.scratch.scratch-desktop'; + + if (!process.env.AC_USERNAME) { + throw new Error( + 'Notarizing the macOS build requires an Apple ID.\n' + + 'Please set the environment variable AC_USERNAME.\n' + + 'Make sure your keychain has an item for "Application Loader: your@apple.id"' + ); + } + + const appleId = process.env.AC_USERNAME; + const appleIdKeychainItem = `Application Loader: ${appleId}`; + + console.log(`Notarizing with Apple ID "${appleId}" and keychain item "${appleIdKeychainItem}"`); + + const {appOutDir} = context; + const productFilename = context.packager.appInfo.productFilename; + await notarize({ + appBundleId: appId, + appPath: `${appOutDir}/${productFilename}.app`, + appleId, + appleIdPassword: `@keychain:${appleIdKeychainItem}` + }); +}; + +const afterSign = async function (context) { + const {electronPlatformName} = context; + + switch (electronPlatformName) { + case 'mas': // macOS build for Mac App Store + break; + case 'darwin': // macOS build NOT for Mac App Store + await notarizeMacBuild(context); + break; + } +}; + +module.exports = afterSign; diff --git a/scripts/electron-builder-wrapper.js b/scripts/electron-builder-wrapper.js index ed50dc8..f5aa406 100644 --- a/scripts/electron-builder-wrapper.js +++ b/scripts/electron-builder-wrapper.js @@ -52,11 +52,20 @@ const runBuilder = function (targetGroup) { const platformFlag = getPlatformFlag(); const command = `electron-builder ${platformFlag} ${targetGroup}`; console.log(`running: ${command}`); - spawnSync(command, { + const result = spawnSync(command, { env: childEnvironment, shell: true, stdio: 'inherit' }); + if (result.error) { + throw result.error; + } + if (result.signal) { + throw new Error(`Child process terminated due to signal ${result.signal}`); + } + if (result.status) { + throw new Error(`Child process returned status code ${result.status}`); + } }; /** @@ -69,8 +78,10 @@ const calculateTargets = function () { // run in two passes so we can skip signing the appx return ['nsis', 'appx']; case 'darwin': - // run in one pass for slightly better speed - return ['dmg mas']; + // Running 'dmg' and 'mas' in the same pass causes electron-builder to skip signing the non-MAS app copy. + // Running them as separate passes means they both get signed. + // Seems like a bug in electron-builder... + return ['dmg', 'mas']; } throw new Error(`Could not determine targets for platform: ${process.platform}`); };