diff --git a/.circleci/config.yml b/.circleci/config.yml index 82588b2..f598e90 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -68,6 +68,9 @@ commands: paths: - << parameters.npmCacheDir >> key: npm-cache-{{ arch }}-{{ checksum "package-lock.json" }} + - run: + name: Test + command: npm run test - run: name: Build command: npm run dist diff --git a/electron-builder.yaml b/electron-builder.yaml index ca1e8b4..980260a 100644 --- a/electron-builder.yaml +++ b/electron-builder.yaml @@ -3,7 +3,6 @@ directories: output: dist appId: edu.mit.scratch.scratch-desktop productName: "Scratch Desktop" -afterSign: "scripts/afterSign.js" mac: category: public.app-category.education entitlements: buildResources/entitlements.mac.plist @@ -19,7 +18,6 @@ mac: target: - dmg - mas - type: distribution mas: category: public.app-category.education entitlements: buildResources/entitlements.mas.plist diff --git a/package.json b/package.json index 52b5876..3e9bee9 100644 --- a/package.json +++ b/package.json @@ -6,16 +6,21 @@ "version": "3.12.0", "license": "BSD-3-Clause", "scripts": { - "postinstall": "node ./scripts/npm-in-gui.js install", + "clean": "rimraf ./dist ./static/assets && node ./scripts/npm-in-gui.js run clean", "start": "electron-webpack dev --bail --display-error-details --env.minify=false", - "build-gui": "node ./scripts/npm-in-gui.js run build", - "watch-gui": "node ./scripts/npm-in-gui.js run watch", - "clean": "rimraf ./dist ./static/assets ./node_modules/scratch-gui/build ./node_modules/scratch-gui/dist", - "compile": "rimraf ./dist/ && electron-webpack --bail --display-error-details --env.minify=false", + "compile": "npm run build-gui && electron-webpack --bail --display-error-details --env.minify=false", "fetch": "rimraf ./static/assets/ && mkdirp ./static/assets/ && node ./scripts/fetchMediaLibraryAssets.js", - "dist": "npm run build-gui && npm run fetch && npm run compile -p && node ./scripts/electron-builder-wrapper.js", - "dist:dir": "npm run dist -- --dir -c.compression=store -c.mac.identity=null", - "lint": "eslint --cache --color --ext .jsx,.js ." + "build": "npm run build:dev", + "build:dev": "npm run compile && npm run doBuild -- --mode=dev", + "build:dir": "npm run compile && npm run doBuild -- --mode=dir", + "build:dist": "npm run compile && npm run doBuild -- --mode=dist", + "doBuild": "node ./scripts/electron-builder-wrapper.js", + "dist": "npm run clean && npm run compile && npm run fetch && npm run doBuild -- --mode=dist", + "test": "npm run test:lint", + "test:lint": "eslint --cache --color --ext .jsx,.js .", + "postinstall": "node ./scripts/npm-in-gui.js install", + "build-gui": "node ./scripts/npm-in-gui.js run build", + "watch-gui": "node ./scripts/npm-in-gui.js run watch" }, "repository": { "type": "git", diff --git a/scripts/electron-builder-wrapper.js b/scripts/electron-builder-wrapper.js index a96bcc5..90997c9 100644 --- a/scripts/electron-builder-wrapper.js +++ b/scripts/electron-builder-wrapper.js @@ -38,20 +38,40 @@ const getPlatformFlag = function () { /** * Run `electron-builder` once to build one or more target(s). - * @param {string} targetGroup - the target(s) to build in this pass. - * If the `targetGroup` is `'nsis'` then the environment must contain code-signing config (CSC_* or WIN_CSC_*). - * If the `targetGroup` is `'appx'` then code-signing config will be stripped from the environment if present. + * @param {object} wrapperConfig - overall configuration object for the wrapper script. + * @param {object} target - the target to build in this call. + * If the `target.name` is `'nsis'` then the environment must contain code-signing config (CSC_* or WIN_CSC_*). + * If the `target.name` is `'appx'` then code-signing config will be stripped from the environment if present. */ -const runBuilder = function (targetGroup) { - // the appx build fails if CSC_* or WIN_CSC_* variables are set - const shouldStripCSC = (targetGroup === 'appx'); +const runBuilder = function (wrapperConfig, target) { + // the AppX build fails if CSC_* or WIN_CSC_* variables are set + const shouldStripCSC = (target.name === 'appx'); const childEnvironment = shouldStripCSC ? stripCSC(process.env) : process.env; - if ((targetGroup === 'nsis') && !(childEnvironment.CSC_LINK || childEnvironment.WIN_CSC_LINK)) { + if ((target.name === 'nsis') && !(childEnvironment.CSC_LINK || childEnvironment.WIN_CSC_LINK)) { throw new Error(`NSIS build requires CSC_LINK or WIN_CSC_LINK`); } const platformFlag = getPlatformFlag(); - const customArgs = process.argv.slice(2); // remove `node` and `this-script.js` - const allArgs = [platformFlag, targetGroup, ...customArgs]; + let allArgs = [platformFlag, target.name]; + if (target.platform === 'darwin') { + allArgs.push(`--c.mac.type=${wrapperConfig.mode === 'dist' ? 'distribution' : 'development'}`); + } + if (wrapperConfig.doSign) { + // really this is "notarize only if we also sign" + allArgs.push('--c.afterSign=scripts/afterSign.js'); + } else { + if (target.name === 'mas') { + // electron-builder doesn't seem to support this configuration even if mac.type is "development" + console.log(`skipping target "${target.name}" because code-signing is disabled`); + return; + } + if (target.platform === 'darwin') { + allArgs.push('--c.mac.identity=null'); + } + } + if (!wrapperConfig.doPackage) { + allArgs.push('--dir', '--c.compression=store'); + } + allArgs = allArgs.concat(wrapperConfig.builderArgs); console.log(`running electron-builder with arguments: ${allArgs}`); const result = spawnSync('electron-builder', allArgs, { env: childEnvironment, @@ -70,27 +90,97 @@ const runBuilder = function (targetGroup) { }; /** - * @returns {Array.} - the default list of target groups on this platform. Each item in the array represents - * one call to `runBuilder` for one or more build target(s). + * @returns {Array.} - the default list of targets on this platform. Each item in the array represents one + * call to `runBuilder` for exactly one build target. In theory electron-builder can build two or more targets at the + * same time but doing so limits has unwanted side effects on both macOS and Windows (see function body). */ const calculateTargets = function () { + const targets = { + macAppStore: { + name: 'mas', + platform: 'darwin' + }, + macAppStoreDev: { + name: 'mas-dev', + platform: 'darwin' + }, + macDirectDownload: { + name: 'dmg', + platform: 'darwin' + }, + microsoftStore: { + name: 'appx', + platform: 'win32' + }, + windowsDirectDownload: { + name: 'nsis:ia32', + platform: 'win32' + } + }; switch (process.platform) { case 'win32': - // run in two passes so we can skip signing the appx - return ['nsis:ia32', 'appx']; + // Run in two passes so we can skip signing the AppX for distribution through the MS Store. + return [targets.microsoftStore, targets.windowsDirectDownload]; case 'darwin': // 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. + // Running them as separate passes means they can both get signed. // Seems like a bug in electron-builder... // Running the 'mas' build first means that its output is available while we wait for 'dmg' notarization. - // Add 'mas-dev' here to test a 'mas'-like build locally. You'll need a Mac Developer provisioning profile. - return ['mas', 'dmg']; + // Add macAppStoreDev here to test a MAS-like build locally. You'll need a Mac Developer provisioning profile. + return [targets.macAppStore, targets.macDirectDownload]; } throw new Error(`Could not determine targets for platform: ${process.platform}`); }; -// TODO: allow user to specify targets? We could theoretically build NSIS on Mac, for example. -const targets = calculateTargets(); -for (const targetGroup of targets) { - runBuilder(targetGroup); -} +const parseArgs = function () { + const scriptArgs = process.argv.slice(2); // remove `node` and `this-script.js` + const builderArgs = []; + let mode = 'dev'; // default + + for (const arg of scriptArgs) { + const modeSplit = arg.split(/--mode(\s+|=)/); + if (modeSplit.length === 3) { + mode = modeSplit[2]; + } else { + builderArgs.push(arg); + } + } + + let doPackage; + let doSign; + + switch (mode) { + case 'dev': + doPackage = true; + doSign = false; + break; + case 'dir': + doPackage = false; + doSign = false; + break; + case 'dist': + doPackage = true; + doSign = true; + } + + // TODO: allow user to specify targets? We could theoretically build NSIS on Mac, for example. + const targets = calculateTargets(); + + return { + builderArgs, + doPackage, // false = build to directory + doSign, + mode, + targets + }; +}; + +const main = function () { + const wrapperConfig = parseArgs(); + + for (const target of wrapperConfig.targets) { + runBuilder(wrapperConfig, target); + } +}; + +main();