Merge pull request #522 from hashicorp/desktop-update-feature

feat: 🎸 Update desktop through menu option
This commit is contained in:
Susmitha Girumala authored and GitHub committed 2021-04-13 16:43:17 -04:00
commit 18e95435f5
10 files changed
+298 -31

No files matched your search

+4
View File
@@ -4,6 +4,10 @@ Canonical reference for changes, improvements, and bugfixes for Boundary Desktop
## Next
### New
- Check for client updates and update on-demand using new menu option. ([PR](https://github.com/hashicorp/boundary-ui/pull/522))
### Bug Fixes
- Trigger zoom-in menu option on keyboard shortcut. ([PR](https://github.com/hashicorp/boundary-ui/pull/505))
+11
View File
@@ -65,6 +65,17 @@ To run as a desktop app:
The Boundary CLI is downloaded and extracted to `electron-app/cli/` folder as part of
build. CLI version is defined in `electron-app/config/cli.js`.
#### Environment Variables
These environment variables may be used to customized the build.
| Variable | Description |
| -------- | ----------- |
| `DEBUG_APP_UPDATER` | Enable to debug app updater feature. Must be enabled for all `APP_UPDATER_*` variables to be used. |
| `APP_UPDATER_CURRENT_VERSION` | Version of client. |
| `APP_UPDATER_LATEST_VERSION_TAG` | Next version for comparison with current version. |
| `APP_UPDATER_LATEST_VERSION_LOCATION` | Location of app release to use for updating client. Can be a filepath or url. |
### Building for Production
Before executing a build, be sure to set any environment variables necessary
+4 -1
View File
@@ -93,4 +93,7 @@ ember-dist/
ember-test/
# CLI
cli/
cli/
# Autoupdate
nextVersion/
+1 -1
View File
@@ -10,7 +10,7 @@ const artifactDestination = path.resolve(__dirname, '..', 'cli');
const downloadArtifact = version => {
const url = `https://releases.hashicorp.com/boundary/${version}/boundary_${version}_darwin_amd64.zip`;
return new Promise((resolve, reject) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'boundary-artifact-'))
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'boundary-artifact-'));
console.log('Create tmp artifact directory: ', tmpDir);
const artifactFileName = url.split('/').pop();
+24 -23
View File
@@ -18,41 +18,42 @@ console.log('RELEASE_VERSION ', process.env.RELEASE_VERSION);
console.log('RELEASE_COMMIT ', process.env.RELEASE_COMMIT);
module.exports = {
releaseVersion: process.env.RELEASE_VERSION,
hooks: {
prePackage: () => {
if (!process.env.BOUNDARY_DESKTOP_SIGNING_IDENTITY) {
console.warn('\nWARNING: Could not find signing identity.');
}
}
},
},
packagerConfig: {
ignore: [
"/ember-test(/|$)",
"/tests(/|$)"
],
name: "Boundary",
appBundleId: "com.electron.boundary",
ignore: ['/ember-test(/|$)', '/tests(/|$)'],
name: 'Boundary',
appBundleId: 'com.electron.boundary',
// TODO: where should the client version number come from?
appVersion: `${formattedAppVersion}${formattedCLIVersion}\n`,
appCopyright: "Copyright © 2021 HashiCorp, Inc.",
icon: "./config/macos/icon.icns",
appCopyright: 'Copyright © 2021 HashiCorp, Inc.',
icon: './config/macos/icon.icns',
osxSign: {
identity: process.env.BOUNDARY_DESKTOP_SIGNING_IDENTITY,
"hardened-runtime": true,
entitlements: "./config/macos/entitlements.plist",
"entitlements-inherit": "./config/macos/entitlements.plist",
"signature-flags": "library"
}
'hardened-runtime': true,
entitlements: './config/macos/entitlements.plist',
'entitlements-inherit': './config/macos/entitlements.plist',
'signature-flags': 'library',
},
},
makers: [
{
name: "@electron-forge/maker-dmg",
name: '@electron-forge/maker-dmg',
config: {
title: "Boundary",
name: "boundary",
icon: "./config/macos/disk.icns",
background: "./config/macos/background.png"
}
}
]
}
title: 'Boundary',
name: 'boundary',
icon: './config/macos/disk.icns',
background: './config/macos/background.png',
},
},
{
name: '@electron-forge/maker-zip',
},
],
};
+3
View File
@@ -25,11 +25,14 @@
"electron-is-dev": "^1.2.0",
"electron-squirrel-startup": "^1.0.0",
"lookpath": "^1.1.0",
"node-html-parser": "^3.1.3",
"semver": "^7.3.5",
"shell-quote": "^1.7.2"
},
"devDependencies": {
"@electron-forge/cli": "6.0.0-beta.54",
"@electron-forge/maker-dmg": "^6.0.0-beta.54",
"@electron-forge/maker-zip": "^6.0.0-beta.54",
"decompress": "^4.2.1",
"devtron": "^1.4.0",
"electron": "12.0.2"
+132
View File
@@ -0,0 +1,132 @@
const fs = require('fs');
const path = require('path');
const https = require('https');
const semver = require('semver');
const { parse } = require('node-html-parser');
const { autoUpdater, dialog } = require('electron');
const { releaseVersion } = require('../config/forge.config.js');
const debug = process.env.DEBUG_APP_UPDATER;
const releasesUrl = 'https://releases.hashicorp.com/boundary-desktop/';
let currentVersion = releaseVersion;
if (debug && process.env.APP_UPDATER_CURRENT_VERSION) {
currentVersion = process.env.APP_UPDATER_CURRENT_VERSION;
}
// Query releases url to find latest version
const findLatestVersion = (url) => {
return new Promise((resolve, reject) => {
let data = '';
https.get(url, (response) => {
response.on('data', (chunk) => (data += chunk));
response.on('end', () => {
const html = parse(data);
// Ignore first link as it navigates to root
const fullVersion = html.querySelectorAll('a')[1].innerHTML;
resolve(fullVersion.split('_')[1]);
});
response.on('error', (err) => reject(err));
});
});
};
// Create update config using downloaded archive
const createAppUpdaterConfig = (url, version, destination) => {
if (debug) console.log(`[app-updater] url: ${url}, destination: ${destination}`);
const json = { url, version};
const config = JSON.stringify(json);
const configPath = `${destination}/feed.json`;
if (debug) console.log(`[app-updater] config: ${config}`);
fs.writeFileSync(configPath, config);
return configPath;
};
const downloadAndInstallUpdate = async (version) => {
const url = `${releasesUrl}boundary-desktop_${version}/boundary-desktop_${version}_darwin_amd64.zip`;
const destination = path.resolve(__dirname, '..', 'nextVersion');
if (!fs.existsSync(destination)) fs.mkdirSync(destination);
try {
let configPath;
if (debug) {
// Support hosted url and file paths
const location = process.env.APP_UPDATER_LATEST_VERSION_LOCATION;
configPath = createAppUpdaterConfig(
location.match(/^https/i) ? location : `file://${location}`,
version,
destination
);
} else {
configPath = createAppUpdaterConfig(url, version, destination);
}
autoUpdater.on('update-downloaded', () => {
const dialogOpts = {
type: 'info',
buttons: ['Restart', 'Later'],
detail:
'A new version has been downloaded. Restart the application to apply the updates.',
};
dialog.showMessageBox(dialogOpts).then((returnValue) => {
if (returnValue.response === 0) autoUpdater.quitAndInstall(true, true);
});
});
autoUpdater.on('error', (e) => {
dialog.showErrorBox('Could not update', e.message);
});
if (debug) console.log('[app-updater] config:', configPath);
autoUpdater.setFeedURL(`file://${configPath}`);
autoUpdater.checkForUpdates();
} catch (e) {
dialog.showErrorBox('Could not update', e.message);
}
};
/**
* Configure inbuilt app updater to use a custom config file
* to download and install next available app version.
* TODO: download progress in dialog
**/
module.exports = {
run: async ({ suppressNoUpdatePrompt }) => {
let latestVersion;
if (debug) {
latestVersion = process.env.APP_UPDATER_LATEST_VERSION_TAG;
} else {
latestVersion = await findLatestVersion(releasesUrl);
}
// Ensure version is in proper format
if (!semver.valid(latestVersion)) {
latestVersion = semver.coerce(latestVersion);
}
// Update not available - do nothing
if (!suppressNoUpdatePrompt && semver.lte(latestVersion, currentVersion)) {
const dialogOpts = {
type: 'info',
icon: null,
detail: 'No updates available',
};
dialog.showMessageBox(dialogOpts);
return;
}
// Update is available - prompt for download
const dialogOpts = {
type: 'info',
buttons: ['Download', 'Later'],
icon: null,
detail: 'A new version is available for download',
};
dialog.showMessageBox(dialogOpts).then((returnValue) => {
if (returnValue.response === 0) {
downloadAndInstallUpdate(latestVersion);
}
});
},
};
+11 -5
View File
@@ -11,6 +11,7 @@ const {
BrowserWindow,
ipcMain,
Menu,
MenuItem,
} = require('electron');
require('./handlers.js');
@@ -18,7 +19,7 @@ const origin = require('./origin.js');
const { generateCSPHeader } = require('./content-security-policy.js');
const menu = require('./menu.js');
const appUpdater = require('./app-updater.js');
const isDev = require('electron-is-dev');
// Register the custom file protocol
@@ -101,11 +102,13 @@ app.on('ready', async () => {
}
}
// Configure menu in prod env
if (!isDev) {
const menuTemplate = Menu.buildFromTemplate(menu.generateMenuTemplate());
Menu.setApplicationMenu(menuTemplate);
// Configure dev tools menu
const menuTemplate = Menu.buildFromTemplate(menu.generateMenuTemplate());
if (isDev) {
const view = menuTemplate.getMenuItemById('view');
view.submenu.append(new MenuItem({ role: 'toggleDevTools' }));
}
Menu.setApplicationMenu(menuTemplate);
mainWindow = new BrowserWindow({
width: 1280,
@@ -136,6 +139,9 @@ app.on('ready', async () => {
// Load the ember application
mainWindow.loadURL(emberAppURL);
// Check for updates on launch
appUpdater.run({ suppressNoUpdatePrompt: true });
// If a loading operation goes wrong, we'll send Electron back to
// Ember App entry point
mainWindow.webContents.on('did-fail-load', () => {
+7
View File
@@ -1,4 +1,5 @@
const { shell } = require('electron');
const appUpdater = require('./app-updater.js');
const generateMenuTemplate = () => {
return [
@@ -7,6 +8,11 @@ const generateMenuTemplate = () => {
label: 'Boundary',
submenu: [
{ role: 'about' },
{
id: 'update',
label: 'Check for Updates',
click: async () => appUpdater.run(),
},
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
@@ -44,6 +50,7 @@ const generateMenuTemplate = () => {
},
// { role: 'viewMenu' }
{
id: 'view',
label: 'View',
submenu: [
{ role: 'reload' },
+101 -1
View File
@@ -155,6 +155,16 @@
optionalDependencies:
electron-installer-dmg "^3.0.0"
"@electron-forge/maker-zip@^6.0.0-beta.54":
version "6.0.0-beta.54"
resolved "https://registry.yarnpkg.com/@electron-forge/maker-zip/-/maker-zip-6.0.0-beta.54.tgz#83d322afd912587fd233e87c30339d8150f29bed"
integrity sha512-wbJhK1rDOCZMTtKrjvavT8R+Yi+v/6axsnTXvzbkzzMQ0xADKNslTwzO6mmbBJea4oIbYmQ44DRAjI21TNyQ/A==
dependencies:
"@electron-forge/maker-base" "6.0.0-beta.54"
"@electron-forge/shared-types" "6.0.0-beta.54"
cross-zip "^3.0.0"
fs-extra "^9.0.1"
"@electron-forge/plugin-base@6.0.0-beta.54":
version "6.0.0-beta.54"
resolved "https://registry.yarnpkg.com/@electron-forge/plugin-base/-/plugin-base-6.0.0-beta.54.tgz#1847eeee00e9558d61ef9ff7c2d7c9f5dbf1109c"
@@ -505,6 +515,11 @@ bluebird@^3.1.1, bluebird@^3.5.0:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
boolbase@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
boolean@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.3.tgz#0fee0c9813b66bef25a8a6a904bb46736d05f024"
@@ -788,6 +803,29 @@ cross-spawn@^7.0.1:
shebang-command "^2.0.0"
which "^2.0.1"
cross-zip@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cross-zip/-/cross-zip-3.1.0.tgz#2b7d33f2a893bf83e232ccbabf4c6c706f6b313c"
integrity sha512-aX02l0SD3KE27pMl69gkxDdDM5D3u9Ic4Je+2b1B2fP0dWnlWWY6ns2Vk5DEgCXJRhL3GasSpicNQRNbDkq0+w==
dependencies:
rimraf "^3.0.0"
css-select@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-3.1.2.tgz#d52cbdc6fee379fba97fb0d3925abbd18af2d9d8"
integrity sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==
dependencies:
boolbase "^1.0.0"
css-what "^4.0.0"
domhandler "^4.0.0"
domutils "^2.4.3"
nth-check "^2.0.0"
css-what@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-4.0.0.tgz#35e73761cab2eeb3d3661126b23d7aa0e8432233"
integrity sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==
currently-unhandled@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
@@ -953,6 +991,36 @@ devtron@^1.4.0:
highlight.js "^9.3.0"
humanize-plus "^1.8.1"
dom-serializer@^1.0.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1"
integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==
dependencies:
domelementtype "^2.0.1"
domhandler "^4.0.0"
entities "^2.0.0"
domelementtype@^2.0.1, domelementtype@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
domhandler@^4.0.0, domhandler@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.1.0.tgz#c1d8d494d5ec6db22de99e46a149c2a4d23ddd43"
integrity sha512-/6/kmsGlMY4Tup/nGVutdrK9yQi4YjWVcVeoQmixpzjOUK1U7pQkvAPHBJeUxOgxF0J8f8lwCJSlCfD0V4CMGQ==
dependencies:
domelementtype "^2.2.0"
domutils@^2.4.3:
version "2.5.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.5.1.tgz#9b8e84b5d9f788499ae77506ea832e9b4f9aa1c0"
integrity sha512-hO1XwHMGAthA/1KL7c83oip/6UWo3FlUNIuWiWKltoiQ5oCOiqths8KknvY2jpOohUoUgnwa/+Rm7UpwpSbY/Q==
dependencies:
dom-serializer "^1.0.1"
domelementtype "^2.2.0"
domhandler "^4.1.0"
ds-store@^0.1.5:
version "0.1.6"
resolved "https://registry.yarnpkg.com/ds-store/-/ds-store-0.1.6.tgz#d1024ef746ed0c13f0f7fec85c7e858e8c4b7ca7"
@@ -1099,6 +1167,11 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"
entities@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
env-paths@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
@@ -1611,6 +1684,11 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
he@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
highlight.js@^10.4.1, highlight.js@^9.3.0:
version "10.7.2"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360"
@@ -2280,6 +2358,14 @@ node-gyp@^7.1.0:
tar "^6.0.2"
which "^2.0.2"
node-html-parser@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-3.1.3.tgz#987d39bc8df27bc70cbe3b4e9a31fd84f7454dfb"
integrity sha512-pCE2I5UY5iOBnWdJQkbYZSk+fyq2zepw0nsELpHQjVFyCzOeZhkMhnvKqGceKgzWsWx7EG4KtMqsy9Eklf5Thw==
dependencies:
css-select "^3.1.2"
he "1.2.0"
node-pre-gyp@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054"
@@ -2372,6 +2458,13 @@ npmlog@^4.0.2, npmlog@^4.1.2:
gauge "~2.7.3"
set-blocking "~2.0.0"
nth-check@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125"
integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==
dependencies:
boolbase "^1.0.0"
nugget@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0"
@@ -2947,7 +3040,7 @@ rimraf@^2.6.1, rimraf@^2.7.1:
dependencies:
glob "^7.1.3"
rimraf@^3.0.2:
rimraf@^3.0.0, rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
@@ -3027,6 +3120,13 @@ semver@^7.1.3, semver@^7.2.1, semver@^7.3.2:
dependencies:
lru-cache "^6.0.0"
semver@^7.3.5:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
serialize-error@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"