Merge pull request #582 from yuya-oc/auto-updater

Implement auto updater for Windows and Mac
This commit is contained in:
Yuya Ochiai
2018-12-19 23:53:45 +09:00
committed by GitHub
21 changed files with 689 additions and 35 deletions

View File

@@ -593,7 +593,7 @@ A simple spellchecker compatible with Electron
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"

View File

@@ -1,4 +1,8 @@
{
"publish": [{
"provider": "generic",
"url": "https://releases.mattermost.com/desktop/"
}],
"appId": "com.mattermost.desktop",
"artifactName": "${name}-${version}-${os}-${arch}.${ext}",
"directories": {
@@ -72,7 +76,7 @@
},
"win": {
"target": [
"squirrel",
"nsis",
"zip"
],
"extraFiles": [
@@ -83,5 +87,8 @@
]
}
]
},
"nsis": {
"artifactName": "${name}-setup-${version}-win.${ext}"
}
}

View File

@@ -28,10 +28,9 @@
"test": "npm-run-all test:* lint:*",
"test:app": "cross-env NODE_ENV=production npm run build && mocha -r @babel/register --reporter mocha-circleci-reporter --recursive test/specs",
"package:all": "cross-env NODE_ENV=production npm-run-all check-build-config package:windows package:mac package:linux",
"package:windows": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --win --x64 --ia32 --config.extraMetadata.name=mattermost --publish=never",
"package:windows": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --win --x64 --ia32 --publish=never",
"package:mac": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --mac --publish=never",
"package:linux": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --linux --x64 --ia32 --config.extraMetadata.name=mattermost-desktop --publish=never",
"manipulate-windows-zip": "node scripts/manipulate_windows_zip.js",
"package:linux": "cross-env NODE_ENV=production npm-run-all check-build-config build && build --linux --x64 --ia32 --publish=never",
"lint:js": "eslint --ignore-path .gitignore --ignore-pattern node_modules --ext .js --ext .jsx .",
"fix:js": "eslint --ignore-path .gitignore --ignore-pattern node_modules --quiet --ext .js --ext .jsx . --fix",
"check-build-config": "node -r @babel/register scripts/check_build_config.js"

View File

@@ -5,11 +5,11 @@ VERSION=`cat package.json | jq -r '.version'`
SRC=$1
DEST=$2
cp "${SRC}/mattermost-${VERSION}-win-ia32.zip" "${DEST}/mattermost-desktop-${VERSION}-win32.zip"
cp "${SRC}/mattermost-${VERSION}-win-x64.zip" "${DEST}/mattermost-desktop-${VERSION}-win64.zip"
cp "${SRC}/squirrel-windows/mattermost-setup-${VERSION}.exe" "${DEST}/mattermost-setup-${VERSION}-win64.exe"
cp "${SRC}/squirrel-windows-ia32/mattermost-setup-${VERSION}-ia32.exe" "${DEST}/mattermost-setup-${VERSION}-win32.exe"
cp "${SRC}/mattermost-desktop-${VERSION}-win-ia32.zip" "${DEST}/mattermost-desktop-${VERSION}-win32.zip"
cp "${SRC}/mattermost-desktop-${VERSION}-win-x64.zip" "${DEST}/mattermost-desktop-${VERSION}-win64.zip"
cp "${SRC}/mattermost-desktop-setup-${VERSION}-win.exe" "${DEST}/"
cp "${SRC}"/mattermost-desktop-*.zip "${DEST}/"
cp "${SRC}"/*.tar.gz "${DEST}/"
cp "${SRC}"/*.deb "${DEST}/"
cp "${SRC}"/*.AppImage "${DEST}/"
cp "${SRC}"/*.yml "${DEST}/"

View File

@@ -4,24 +4,30 @@
'use strict';
const spawnSync = require('child_process').spawnSync;
const path = require('path');
const path7za = require('7zip-bin').path7za;
const pkg = require('../src/package.json');
const appVersion = pkg.version;
const productName = pkg.productName;
const name = pkg.name;
function renameInZip(zipPath, oldName, newName) {
const result = spawnSync(path7za, ['rn', zipPath, oldName, newName]);
return result.status === 0;
function disableInstallUpdate(zipPath) {
const zipFullPath = path.resolve(__dirname, '..', zipPath);
const appUpdaterConfigFile = 'app-updater-config.json';
const addResult = spawnSync(path7za, ['a', zipFullPath, appUpdaterConfigFile], {cwd: 'resources/windows'});
if (addResult.status !== 0) {
throw new Error(`7za a returned non-zero exit code for ${zipPath}`);
}
const renameResult = spawnSync(path7za, ['rn', zipFullPath, appUpdaterConfigFile, `resources/${appUpdaterConfigFile}`]);
if (renameResult.status !== 0) {
throw new Error(`7za rn returned non-zero exit code for ${zipPath}`);
}
}
console.log('Manipulating 64-bit zip...');
if (!renameInZip(`release/${productName}-${appVersion}-win.zip`, 'win-unpacked', `${productName}-${appVersion}-win64`)) {
throw new Error('7za returned non-zero exit code for 64-bit zip');
}
disableInstallUpdate(`release/${name}-${appVersion}-win-x64.zip`);
console.log('Manipulating 32-bit zip...');
if (!renameInZip(`release/${productName}-${appVersion}-ia32-win.zip`, 'win-ia32-unpacked', `${productName}-${appVersion}-win32`)) {
throw new Error('7za returned non-zero exit code for 32-bit zip');
}
disableInstallUpdate(`release/${name}-${appVersion}-win-ia32.zip`);

View File

@@ -0,0 +1,104 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import propTypes from 'prop-types';
import {Button, Navbar, ProgressBar} from 'react-bootstrap';
function InstallButton(props) {
if (props.notifyOnly) {
return (
<Button
bsStyle='primary'
onClick={props.onClickDownload}
>{'Download Update'}</Button>
);
}
return (
<Button
bsStyle='primary'
onClick={props.onClickInstall}
>{'Install Update'}</Button>
);
}
InstallButton.propTypes = {
notifyOnly: propTypes.bool.isRequired,
onClickInstall: propTypes.func.isRequired,
onClickDownload: propTypes.func.isRequired,
};
function UpdaterPage(props) {
return (
<div className='UpdaterPage'>
<Navbar fluid={true} >
<h1 className='UpdaterPage-heading'>{'New update is available'}</h1>
</Navbar>
<div className='container-fluid'>
<p>{`A new version of the ${props.appName} is available!`}</p>
<p>{'Read the '}
<a
href='#'
onClick={props.onClickReleaseNotes}
>{'release notes'}</a>
{' to learn more.'}
</p>
</div>
{props.isDownloading ?
<Navbar
className='UpdaterPage-footer'
fixedBottom={true}
fluid={true}
>
<ProgressBar
active={true}
now={props.progress}
label={`${props.progress}%`}
/>
<div className='pull-right'>
<Button
onClick={props.onClickCancel}
>{'Cancel'}</Button>
</div>
</Navbar> :
<Navbar
className='UpdaterPage-footer'
fixedBottom={true}
fluid={true}
>
<Button
className='UpdaterPage-skipButton'
bsStyle='link'
onClick={props.onClickSkip}
>{'Skip this version'}</Button>
<div className='pull-right'>
<Button
bsStyle='link'
onClick={props.onClickRemind}
>{'Remind me in 2 days'}</Button>
<InstallButton
notifyOnly={props.notifyOnly}
onClickInstall={props.onClickInstall}
onClickDownload={props.onClickDownload}
/>
</div>
</Navbar>
}
</div>
);
}
UpdaterPage.propTypes = {
appName: propTypes.string.isRequired,
notifyOnly: propTypes.bool.isRequired,
isDownloading: propTypes.bool.isRequired,
progress: propTypes.number,
onClickInstall: propTypes.func.isRequired,
onClickDownload: propTypes.func.isRequired,
onClickReleaseNotes: propTypes.func.isRequired,
onClickRemind: propTypes.func.isRequired,
onClickSkip: propTypes.func.isRequired,
onClickCancel: propTypes.func.isRequired,
};
export default UpdaterPage;

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {storiesOf} from '@storybook/react';
import {action} from '@storybook/addon-actions';
import UpdaterPage from '../UpdaterPage.jsx';
import '../../css/components/UpdaterPage.css';
/*
appName: propTypes.string.isRequired,
notifyOnly: propTypes.bool.isRequired,
isDownloading: propTypes.bool.isRequired,
progress: propTypes.number,
onClickInstall: propTypes.func.isRequired,
onClickDownload: propTypes.func.isRequired,
onClickReleaseNotes: propTypes.func.isRequired,
onClickRemind: propTypes.func.isRequired,
onClickSkip: propTypes.func.isRequired,
*/
const appName = 'Storybook App';
storiesOf('UpdaterPage', module).
add('Normal', () => (
<UpdaterPage
appName={appName}
notifyOnly={false}
isDownloading={false}
progress={0}
onClickInstall={action('clicked install')}
onClickReleaseNotes={action('clicked release notes')}
onClickRemind={action('clicked remind')}
onClickSkip={action('clicked skip')}
/>
)).
add('NotifyOnly', () => (
<UpdaterPage
appName={appName}
notifyOnly={true}
onClickDownload={action('clicked download')}
/>
)).
add('Downloading', () => (
<UpdaterPage
appName={appName}
isDownloading={true}
progress={0}
onClickCancel={action('clicked cancel')}
/>
));

View File

@@ -0,0 +1,16 @@
.UpdaterPage-heading {
font-size: 20pt;
margin: 10px 0px;
}
.UpdaterPage-skipButton {
padding-left: 0px;
}
.UpdaterPage-footer {
padding: 1em 0;
}
.UpdaterPage .progress-bar {
min-width: 2em;
}

View File

@@ -7,3 +7,4 @@
@import url("TabBar.css");
@import url("TeamListItem.css");
@import url("Finder.css");
@import url("UpdaterPage.css");

13
src/browser/updater.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Updater</title>
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="css/components/index.css">
</head>
<body>
<div id="content"></div>
<script src="updater_bundle.js"></script>
</body>
</html>

75
src/browser/updater.jsx Normal file
View File

@@ -0,0 +1,75 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import url from 'url';
import React from 'react';
import ReactDOM from 'react-dom';
import propTypes from 'prop-types';
import {ipcRenderer, remote} from 'electron';
import UpdaterPage from './components/UpdaterPage.jsx';
const thisURL = url.parse(location.href, true);
const notifyOnly = thisURL.query.notifyOnly === 'true';
class UpdaterPageContainer extends React.Component {
constructor(props) {
super(props);
this.state = props.initialState;
}
componentDidMount() {
ipcRenderer.on('start-download', () => {
this.setState({
isDownloading: true,
});
});
ipcRenderer.on('progress', (event, progress) => {
this.setState({
progress,
});
});
}
render() {
return (
<UpdaterPage
appName={`${remote.app.getName()} Desktop App`}
notifyOnly={this.props.notifyOnly}
{...this.state}
onClickReleaseNotes={() => {
ipcRenderer.send('click-release-notes');
}}
onClickSkip={() => {
ipcRenderer.send('click-skip');
}}
onClickRemind={() => {
ipcRenderer.send('click-remind');
}}
onClickInstall={() => {
ipcRenderer.send('click-install');
}}
onClickDownload={() => {
ipcRenderer.send('click-download');
}}
onClickCancel={() => {
ipcRenderer.send('click-cancel');
}}
/>
);
}
}
UpdaterPageContainer.propTypes = {
notifyOnly: propTypes.bool,
initialState: propTypes.object,
};
ReactDOM.render(
<UpdaterPageContainer
notifyOnly={notifyOnly}
initialState={{isDownloading: false, progress: 0}}
/>,
document.getElementById('content')
);

View File

@@ -24,6 +24,7 @@ const buildConfig = {
],
helpLink: 'https://about.mattermost.com/default-desktop-app-documentation/',
enableServerManagement: true,
enableAutoUpdater: true,
};
export default buildConfig;

2
src/dev-app-update.yml Normal file
View File

@@ -0,0 +1,2 @@
provider: generic
url: 'http://localhost:8081/'

View File

@@ -23,9 +23,11 @@ import {parse as parseArgv} from 'yargs';
import {protocols} from '../electron-builder.json';
import squirrelStartup from './main/squirrelStartup';
import AutoLauncher from './main/AutoLauncher';
import CriticalErrorHandler from './main/CriticalErrorHandler';
import upgradeAutoLaunch from './main/autoLaunch';
import autoUpdater from './main/autoUpdater';
import buildConfig from './common/config/buildConfig';
const criticalErrorHandler = new CriticalErrorHandler();
@@ -34,11 +36,7 @@ process.on('uncaughtException', criticalErrorHandler.processUncaughtExceptionHan
global.willAppQuit = false;
app.setAppUserModelId('com.squirrel.mattermost.Mattermost'); // Use explicit AppUserModelID
if (squirrelStartup(() => {
app.quit();
})) {
global.willAppQuit = true;
}
import settings from './common/settings';
import CertificateStore from './main/certificateStore';
const certificateStore = CertificateStore.load(path.resolve(app.getPath('userData'), 'certificate.json'));
@@ -419,6 +417,10 @@ app.on('ready', () => {
}
appState.lastAppVersion = app.getVersion();
if (!global.isDev) {
upgradeAutoLaunch();
}
if (global.isDev) {
installExtension(REACT_DEVELOPER_TOOLS).
then((name) => console.log(`Added Extension: ${name}`)).
@@ -456,6 +458,9 @@ app.on('ready', () => {
mainWindow.webContents.on('crashed', () => {
throw new Error('webContents \'crashed\' event has been emitted');
});
mainWindow.on('ready-to-show', () => {
autoUpdater.checkForUpdates();
});
ipcMain.on('notified', () => {
if (process.platform === 'win32' || process.platform === 'linux') {
@@ -650,6 +655,21 @@ app.on('ready', () => {
permissionManager = new PermissionManager(permissionFile, trustedURLs);
session.defaultSession.setPermissionRequestHandler(permissionRequestHandler(mainWindow, permissionManager));
if (buildConfig.enableAutoUpdater) {
const updaterConfig = autoUpdater.loadConfig();
autoUpdater.initialize(appState, mainWindow, updaterConfig.isNotifyOnly());
ipcMain.on('check-for-updates', autoUpdater.checkForUpdates);
mainWindow.once('show', () => {
if (autoUpdater.shouldCheckForUpdatesOnStart(appState.updateCheckedDate)) {
ipcMain.emit('check-for-updates');
} else {
setTimeout(() => {
ipcMain.emit('check-for-updates');
}, autoUpdater.UPDATER_INTERVAL_IN_MS);
}
});
}
// Open the DevTools.
// mainWindow.openDevTools();
});

View File

@@ -11,4 +11,24 @@ export default class AppStateManager extends JsonFileManager {
get lastAppVersion() {
return this.getValue('lastAppVersion');
}
set skippedVersion(version) {
this.setValue('skippedVersion', version);
}
get skippedVersion() {
return this.getValue('skippedVersion');
}
set updateCheckedDate(date) {
this.setValue('updateCheckedDate', date.toISOString());
}
get updateCheckedDate() {
const date = this.getValue('updateCheckedDate');
if (date) {
return new Date(date);
}
return null;
}
}

20
src/main/autoLaunch.js Normal file
View File

@@ -0,0 +1,20 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import AutoLaunch from 'auto-launch';
async function upgradeAutoLaunch() {
if (process.platform === 'darwin') {
return;
}
const appLauncher = new AutoLaunch({
name: 'Mattermost',
isHidden: true,
});
const enabled = await appLauncher.isEnabled();
if (enabled) {
await appLauncher.enable();
}
}
export default upgradeAutoLaunch;

194
src/main/autoUpdater.js Normal file
View File

@@ -0,0 +1,194 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import path from 'path';
import {app, BrowserWindow, dialog, ipcMain, shell} from 'electron';
import logger from 'electron-log';
import {autoUpdater, CancellationToken} from 'electron-updater';
import semver from 'semver';
// eslint-disable-next-line no-magic-numbers
const UPDATER_INTERVAL_IN_MS = 48 * 60 * 60 * 1000; // 48 hours
autoUpdater.logger = logger;
autoUpdater.logger.transports.file.level = 'info';
let updaterModal = null;
function createEventListener(win, eventName) {
return (event) => {
if (event.sender === win.webContents) {
win.emit(eventName);
}
};
}
function createUpdaterModal(parentWindow, options) {
const windowWidth = 480;
const windowHeight = 280;
const windowOptions = {
title: `${app.getName()} Updater`,
parent: parentWindow,
modal: true,
maximizable: false,
show: false,
width: windowWidth,
height: windowHeight,
resizable: false,
autoHideMenuBar: true,
};
if (process.platform === 'linux') {
windowOptions.icon = options.linuxAppIcon;
}
const modal = new BrowserWindow(windowOptions);
modal.once('ready-to-show', () => {
modal.show();
});
let updaterURL = (global.isDev ? 'http://localhost:8080' : `file://${app.getAppPath()}`) + '/browser/updater.html';
if (options.notifyOnly) {
updaterURL += '?notifyOnly=true';
}
modal.loadURL(updaterURL);
for (const eventName of ['click-release-notes', 'click-skip', 'click-remind', 'click-install', 'click-download', 'click-cancel']) {
const listener = createEventListener(modal, eventName);
ipcMain.on(eventName, listener);
modal.on('closed', () => {
ipcMain.removeListener(eventName, listener);
});
}
return modal;
}
function isUpdateApplicable(now, skippedVersion, updateInfo) {
const releaseTime = new Date(updateInfo.releaseDate).getTime();
// 48 hours after a new version is added to releases.mattermost.com, user receives a “New update is available” dialog
if (now.getTime() - releaseTime < UPDATER_INTERVAL_IN_MS) {
return false;
}
// If a version was skipped, compare version.
if (skippedVersion) {
return semver.gt(updateInfo.version, skippedVersion);
}
return true;
}
function downloadAndInstall(cancellationToken) {
autoUpdater.on('update-downloaded', () => {
global.willAppQuit = true;
autoUpdater.quitAndInstall();
});
autoUpdater.downloadUpdate(cancellationToken);
}
function initialize(appState, mainWindow, notifyOnly = false) {
autoUpdater.autoDownload = false; // To prevent upgrading on quit
const assetsDir = path.resolve(app.getAppPath(), 'assets');
autoUpdater.on('error', (err) => {
console.error('Error in autoUpdater:', err.message);
}).on('update-available', (info) => {
let cancellationToken = null;
if (isUpdateApplicable(new Date(), appState.skippedVersion, info)) {
updaterModal = createUpdaterModal(mainWindow, {
linuxAppIcon: path.join(assetsDir, 'appicon.png'),
notifyOnly,
});
updaterModal.on('closed', () => {
updaterModal = null;
});
updaterModal.on('click-skip', () => {
appState.skippedVersion = info.version;
updaterModal.close();
}).on('click-remind', () => {
appState.updateCheckedDate = new Date();
setTimeout(() => { // eslint-disable-line max-nested-callbacks
autoUpdater.checkForUpdates();
}, UPDATER_INTERVAL_IN_MS);
updaterModal.close();
}).on('click-install', () => {
updaterModal.webContents.send('start-download');
autoUpdater.signals.progress((data) => { // eslint-disable-line max-nested-callbacks
updaterModal.send('progress', Math.floor(data.percent));
console.log('progress:', data);
});
cancellationToken = new CancellationToken();
downloadAndInstall(cancellationToken);
}).on('click-download', () => {
shell.openExternal('https://about.mattermost.com/download/#mattermostApps');
}).on('click-release-notes', () => {
shell.openExternal(`https://github.com/mattermost/desktop/releases/v${info.version}`);
}).on('click-cancel', () => {
cancellationToken.cancel();
updaterModal.close();
});
updaterModal.focus();
} else if (autoUpdater.isManual) {
autoUpdater.emit('update-not-available');
}
}).on('update-not-available', () => {
if (autoUpdater.isManual) {
dialog.showMessageBox(mainWindow, {
type: 'info',
buttons: ['Close'],
title: 'Your Desktop App is up to date',
message: 'You have the latest version of the Mattermost Desktop App.',
}, () => {}); // eslint-disable-line no-empty-function
}
setTimeout(() => {
autoUpdater.checkForUpdates();
}, UPDATER_INTERVAL_IN_MS);
});
}
function shouldCheckForUpdatesOnStart(updateCheckedDate) {
if (updateCheckedDate) {
if (Date.now() - updateCheckedDate.getTime() < UPDATER_INTERVAL_IN_MS) {
return false;
}
}
return true;
}
function checkForUpdates(isManual = false) {
autoUpdater.isManual = isManual;
if (!updaterModal) {
autoUpdater.checkForUpdates();
}
}
class AutoUpdaterConfig {
constructor() {
this.data = {};
}
isNotifyOnly() {
if (process.platform === 'win32') {
return true;
}
if (this.data.notifyOnly === true) {
return true;
}
return false;
}
}
function loadConfig() {
return new AutoUpdaterConfig();
}
export default {
UPDATER_INTERVAL_IN_MS,
checkForUpdates,
shouldCheckForUpdatesOnStart,
initialize,
loadConfig,
};

View File

@@ -3,7 +3,7 @@
// See LICENSE.txt for license information.
'use strict';
import {app, dialog, Menu, shell} from 'electron';
import {app, dialog, ipcMain, Menu, shell} from 'electron';
import settings from '../../common/settings';
import buildConfig from '../../common/config/buildConfig';
@@ -231,6 +231,14 @@ function createTemplate(mainWindow, config, isDev) {
label: `Version ${app.getVersion()}`,
enabled: false,
});
if (buildConfig.enableAutoUpdater) {
submenu.push({
label: 'Check for Updates...',
click() {
ipcMain.emit('check-for-updates', true);
},
});
}
template.push({label: '&Help', submenu});
return template;
}

View File

@@ -14,12 +14,14 @@
"electron-context-menu": "0.9.0",
"electron-devtools-installer": "^2.2.4",
"electron-is-dev": "^0.3.0",
"electron-squirrel-startup": "^1.0.0",
"electron-log": "^2.2.15",
"electron-updater": "2.21.10",
"prop-types": "^15.6.1",
"react": "^16.4.0",
"react-bootstrap": "~0.32.1",
"react-dom": "^16.4.0",
"react-transition-group": "^2.3.1",
"semver": "^5.5.0",
"simple-spellchecker": "^0.9.6",
"underscore": "^1.9.1",
"yargs": "^3.32.0"

View File

@@ -14,6 +14,12 @@ applescript@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/applescript/-/applescript-1.0.0.tgz#bb87af568cad034a4e48c4bdaf6067a3a2701317"
argparse@^1.0.7:
version "1.0.9"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
dependencies:
sprintf-js "~1.0.2"
asap@~2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
@@ -50,6 +56,16 @@ binarysearch@^0.2.4:
version "0.2.4"
resolved "https://registry.yarnpkg.com/binarysearch/-/binarysearch-0.2.4.tgz#46ef3e03fd4529e9328662e68e40328e7a0bf2ac"
bluebird-lst@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/bluebird-lst/-/bluebird-lst-1.0.5.tgz#bebc83026b7e92a72871a3dc599e219cbfb002a9"
dependencies:
bluebird "^3.5.1"
bluebird@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
bootstrap@^3.3.7:
version "3.3.7"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71"
@@ -61,10 +77,23 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
buffer-from@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04"
buffers@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
builder-util-runtime@~4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-4.2.1.tgz#0caa358f1331d70680010141ca591952b69b35bc"
dependencies:
bluebird-lst "^1.0.5"
debug "^3.1.0"
fs-extra-p "^4.6.0"
sax "^1.2.4"
camelcase@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
@@ -115,9 +144,9 @@ cross-unzip@0.0.2:
version "0.0.1"
resolved "git://github.com/cbaatz/damerau-levenshtein.git#8d7440a51ae9dc6912e44385115c7cb1da4e8ebc"
debug@^2.2.0:
version "2.6.8"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
debug@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
dependencies:
ms "2.0.0"
@@ -165,11 +194,23 @@ electron-is-dev@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.3.0.tgz#14e6fda5c68e9e4ecbeff9ccf037cbd7c05c5afe"
electron-squirrel-startup@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/electron-squirrel-startup/-/electron-squirrel-startup-1.0.0.tgz#19b4e55933fa0ef8f556784b9c660f772546a0b8"
electron-log@^2.2.15:
version "2.2.15"
resolved "https://registry.yarnpkg.com/electron-log/-/electron-log-2.2.15.tgz#28c2dd65c9437fcf31f09f5f856c338a83daab76"
electron-updater@2.21.10:
version "2.21.10"
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-2.21.10.tgz#aa66757ebf966f4247f247a8433af45cfe8e93b0"
dependencies:
debug "^2.2.0"
bluebird-lst "^1.0.5"
builder-util-runtime "~4.2.1"
electron-is-dev "^0.3.0"
fs-extra-p "^4.6.0"
js-yaml "^3.11.0"
lazy-val "^1.0.3"
lodash.isequal "^4.5.0"
semver "^5.5.0"
source-map-support "^0.5.5"
encoding@^0.1.11:
version "0.1.12"
@@ -177,6 +218,10 @@ encoding@^0.1.11:
dependencies:
iconv-lite "~0.4.13"
esprima@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
ext-list@^2.0.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37"
@@ -214,6 +259,21 @@ fbjs@^0.8.9:
setimmediate "^1.0.5"
ua-parser-js "^0.7.9"
fs-extra-p@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/fs-extra-p/-/fs-extra-p-4.6.0.tgz#c7b7117f0dcf8a99c9b2ed589067c960abcf3ef9"
dependencies:
bluebird-lst "^1.0.5"
fs-extra "^6.0.0"
fs-extra@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.1.tgz#8abc128f7946e310135ddc93b98bddb410e7a34b"
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -229,6 +289,10 @@ glob@^7.0.5:
once "^1.3.0"
path-is-absolute "^1.0.0"
graceful-fs@^4.1.2, graceful-fs@^4.1.6:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
iconv-lite@~0.4.13:
version "0.4.18"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2"
@@ -283,16 +347,37 @@ js-tokens@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
js-yaml@^3.11.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
optionalDependencies:
graceful-fs "^4.1.6"
keycode@^2.1.2:
version "2.1.9"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.9.tgz#964a23c54e4889405b4861a5c9f0480d45141dfa"
lazy-val@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.3.tgz#bb97b200ef00801d94c317e29dc6ed39e31c5edc"
lcid@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
dependencies:
invert-kv "^1.0.0"
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
@@ -482,10 +567,18 @@ rimraf@^2.5.2:
dependencies:
glob "^7.0.5"
sax@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
semver@^5.3.0:
version "5.4.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
semver@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
@@ -511,6 +604,21 @@ sort-keys@^1.0.0:
dependencies:
is-plain-obj "^1.0.0"
source-map-support@^0.5.5:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13"
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
string-width@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -549,6 +657,10 @@ underscore@^1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
universalify@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
untildify@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1"

View File

@@ -15,6 +15,7 @@ module.exports = merge(base, {
entry: {
index: './src/browser/index.jsx',
settings: './src/browser/settings.jsx',
updater: './src/browser/updater.jsx',
'webview/mattermost': './src/browser/webview/mattermost.js',
},
output: {