diff --git a/desktop/forge.config.cjs b/desktop/forge.config.cjs index 18c9a5bc1..b5c34d726 100644 --- a/desktop/forge.config.cjs +++ b/desktop/forge.config.cjs @@ -1,188 +1,201 @@ const babel = require('@babel/core'); const { PluginBase } = require('@electron-forge/plugin-base'); const fs = require('fs-extra'); +const { request } = require('gaxios'); const klaw = require('klaw'); const path = require('path'); const transformDirectoryWithBabel = async (dirPath, outDirPath) => { for await (const { path: filePath, stats } of klaw(dirPath)) { if (stats.isFile()) { const outPath = path.resolve( outDirPath, path.relative(dirPath, filePath), ); const { code } = await new Promise(resolve => babel.transformFile(filePath, (err, result) => { if (err) { console.error(err); } resolve(result); }), ); if (code) { await fs.outputFile(outPath, code); } } } }; const runBabel = async () => { await Promise.all([ fs.outputFile('./dist/package.json', JSON.stringify({ type: 'commonjs' })), transformDirectoryWithBabel('./src', './dist'), ]); }; class BabelPlugin extends PluginBase { name = 'BabelPlugin'; getHooks() { return { // This hook runs during the packaging of the final executable prePackage: [runBabel], }; } // This function runs only in development mode, just before the // application starts async startLogic() { await runBabel(); // startLogic allows us to run electron ourselves and return the process // object. Electron Forge (package which handles bundling, packaging and // running dev mode) will then watch it instead of spawing electron by // itself. But we are fine with the default behaviour (Electron Forge // spawning electron) so we return false. return false; } } const optionsForFile = filePath => { const entitlements = process.env?.ENV === 'dev' ? 'entitlements-dev.plist' : 'entitlements.plist'; const basename = path.basename(filePath); if (basename === 'Comm' || basename === 'Comm.app') { return { entitlements }; } return {}; }; const signingOptions = { packagerMacos: {}, makerMacos: {}, makerWindows: {}, }; if (process.env?.ENV === 'dev') { if (fs.existsSync('macOS_App_Development_Profile.provisionprofile')) { signingOptions.packagerMacos = { osxSign: { identity: 'Development', preEmbedProvisioningProfile: true, provisioningProfile: 'macOS_App_Development_Profile.provisionprofile', optionsForFile, }, }; } } else { signingOptions.packagerMacos = { osxSign: { identity: 'Developer ID Application', preEmbedProvisioningProfile: true, provisioningProfile: 'macOS_App_Provisioning_Profile.provisionprofile', optionsForFile, }, osxNotarize: { tool: 'notarytool', appleId: process.env?.APPLE_USER_NAME, appleIdPassword: process.env?.APPLE_APP_SPECIFIC_PASSWORD, teamId: process.env?.TEAM_ID, }, }; signingOptions.makerMacos = { 'code-sign': { 'signing-identity': 'Developer ID Application', 'identifier': 'app.comm.macos', }, }; signingOptions.makerWindows = { certificateFile: process.env?.WINDOWS_CERTIFICATE, certificatePassword: process.env?.WINDOWS_PASSWORD, }; } module.exports = { packagerConfig: { name: 'Comm', icon: 'icons/icon', ignore: [ 'src', '.*config\\.cjs', '\\.eslintrc\\.json', '\\.flowconfig', 'flow-typed', 'addons', ], appBundleId: 'app.comm.macos', ...signingOptions.packagerMacos, }, makers: [ { name: '@electron-forge/maker-dmg', platforms: ['darwin'], config: { icon: 'icons/icon.icns', background: 'icons/dmg_background.png', additionalDMGOptions: { ...signingOptions.makerMacos }, contents: opts => [ { x: 340, y: 100, type: 'link', path: '/Applications' }, { x: 100, y: 100, type: 'file', path: opts.appPath }, ], }, }, { name: '@electron-forge/maker-zip', platforms: ['darwin'], }, { name: '@electron-forge/maker-squirrel', platforms: ['win32'], config: { name: 'Comm', title: 'Comm', authors: 'Comm Technologies, Inc.', description: 'Comm is a private messaging app for communities!', iconUrl: 'https://comm-external.s3.amazonaws.com/icon.ico', setupIcon: 'icons/icon.ico', loadingGif: 'icons/win_installer.gif', ...signingOptions.makerWindows, }, }, ], plugins: [new BabelPlugin()], hooks: { generateAssets: async () => { await Promise.all([ fs.copy('../keyserver/fonts', './assets/fonts'), fs.copy('../web/theme.css', './assets/theme.css'), fs.copy('../web/typography.css', './assets/typography.css'), ]); }, prePackage: async (forgeConfig, platform, arch) => { if ( arch === 'universal' && (fs.existsSync('./out/Comm-darwin-x64') || fs.existsSync('./out/Comm-darwin-arm64')) ) { throw new Error( 'Due to a bug in @electron/universal, please first run ' + '`yarn clean-build` or remove previous builds artifacts: ' + '"out/Comm-darwin-x64" and/or "out/Comm-darwin-arm64"\n', ); } + + if (platform === 'win32') { + const file = fs.createWriteStream( + './assets/windows-runtime-installer.exe', + ); + const response = await request({ + url: 'https://aka.ms/windowsappsdk/1.2/1.2.230313.1/windowsappruntimeinstall-x64.exe', + responseType: 'stream', + }); + response.data.pipe(file); + await new Promise(resolve => file.on('finish', resolve)); + } }, }, }; diff --git a/desktop/package.json b/desktop/package.json index 89031aec1..abfeaf53e 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,49 +1,50 @@ { "workspaces": { "nohoist": [ "**" ] }, "name": "desktop", "version": "3.0.0", "type": "module", "main": "./dist/main.js", "private": true, "license": "BSD-3-Clause", "scripts": { "dev": "ENV=dev electron-forge start", "package": "electron-forge package", "package-dev": "ENV=dev electron-forge package", "make": "electron-forge make", "make-dev": "ENV=dev electron-forge make", "clean": "rm -rf assets/ && rm -rf dist/ && yarn clean-build && rm -rf node_modules/", "clean-build": "rm -rf out/" }, "dependencies": { "@babel/runtime": "^7.20.1" }, "optionalDependencies": { "@commapp/windowspush": "file:addons/windows-pushnotifications" }, "devDependencies": { "@babel/core": "^7.13.14", "@babel/plugin-proposal-class-properties": "^7.13.0", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", "@babel/plugin-proposal-object-rest-spread": "^7.13.8", "@babel/plugin-proposal-optional-chaining": "^7.13.12", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-transform-runtime": "^7.13.10", "@babel/preset-env": "^7.13.12", "@electron-forge/cli": "^6.0.4", "@electron-forge/maker-dmg": "^6.0.4", "@electron-forge/maker-zip": "^6.0.4", "@electron-forge/maker-squirrel": "^6.0.4", "@electron-forge/plugin-base": "^6.0.4", "electron": "^22.0.0", "flow-bin": "^0.182.0", "flow-typed": "^3.2.1", "lib": "0.0.1", "fs-extra": "^10.1.0", - "klaw": "^4.0.1" + "klaw": "^4.0.1", + "gaxios": "^4.3.2" } } diff --git a/desktop/src/handle-squirrel-event.js b/desktop/src/handle-squirrel-event.js index a6aefc6ea..3ca005380 100644 --- a/desktop/src/handle-squirrel-event.js +++ b/desktop/src/handle-squirrel-event.js @@ -1,43 +1,46 @@ // @flow -import { spawn } from 'child_process'; +import { spawn, execSync } from 'child_process'; import { app } from 'electron'; import path from 'path'; // Squirrel will start the app with additional flags during installing, // uninstalling and updating so we can for example create or delete shortcuts. // After handling some of these events the app will be closed. If this function // returns false, the app should start normally. export function handleSquirrelEvent(): boolean { if (process.argv.length === 1) { return false; } const updateExe = path.resolve(process.execPath, '..', '..', 'Update.exe'); const commExeName = path.basename(process.execPath); const spawnUpdate = args => { return spawn(updateExe, args, { detached: true }).on('close', app.quit); }; const squirrelEvent = process.argv[1]; switch (squirrelEvent) { case '--squirrel-install': case '--squirrel-updated': + execSync( + path.resolve(__dirname, '../assets/windows-runtime-installer.exe'), + ); spawnUpdate(['--createShortcut', commExeName]); return true; case '--squirrel-uninstall': spawnUpdate(['--removeShortcut', commExeName]); return true; case '--squirrel-obsolete': app.quit(); return true; case '--squirrel-firstrun': return false; } return false; }