Auto open browser on server startup (#24843)

* Adds dev dependency on opn for opening browsers and other things.

* Adds a --open option to cli to open browser window.

* Removes unused variable in index.

* Adds opn types to dev dependencies from definitely typed.

* Adds open to the cliArgs type to allow for consistency.

* Updates snapshots that require valid cliArgs types.

* Moves opn to direct dependency since its used in cli.

* [cli] move --open handling to cluster manager

* Adds support for running --open with --no-base-path
This commit is contained in:
nicknak 2018-11-08 09:12:01 -05:00 committed by GitHub
parent c757990be1
commit c2425c1cdd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 97 additions and 13 deletions

View file

@ -150,6 +150,7 @@
"ngreact": "0.5.1",
"no-ui-slider": "1.2.0",
"node-fetch": "1.3.2",
"opn": "^5.4.0",
"oppsy": "^2.0.0",
"pegjs": "0.9.0",
"postcss-loader": "2.0.6",
@ -252,6 +253,7 @@
"@types/moment-timezone": "^0.5.8",
"@types/mustache": "^0.8.31",
"@types/node": "^8.10.20",
"@types/opn": "^5.1.0",
"@types/prop-types": "^15.5.3",
"@types/puppeteer": "^1.6.2",
"@types/react": "16.3.14",

View file

@ -18,9 +18,12 @@
*/
import { resolve } from 'path';
import { format as formatUrl } from 'url';
import opn from 'opn';
import { debounce, invoke, bindAll, once, uniq } from 'lodash';
import { fromEvent, race } from 'rxjs';
import { first } from 'rxjs/operators';
import * as Rx from 'rxjs';
import { first, mapTo, filter, map, take } from 'rxjs/operators';
import Log from '../log';
import Worker from './worker';
@ -88,6 +91,15 @@ export default class ClusterManager {
bindAll(this, 'onWatcherAdd', 'onWatcherError', 'onWatcherChange');
if (opts.open) {
this.setupOpen(formatUrl({
protocol: config.get('server.ssl.enabled') ? 'https' : 'http',
hostname: config.get('server.host'),
port: config.get('server.port'),
pathname: (this.basePathProxy ? this.basePathProxy.basePath : ''),
}));
}
if (opts.watch) {
const pluginPaths = config.get('plugins.paths');
const scanDirs = config.get('plugins.scanDirs');
@ -124,6 +136,28 @@ export default class ClusterManager {
}
}
setupOpen(openUrl) {
const serverListening$ = Rx.merge(
Rx.fromEvent(this.server, 'listening')
.pipe(mapTo(true)),
Rx.fromEvent(this.server, 'fork:exit')
.pipe(mapTo(false)),
Rx.fromEvent(this.server, 'crashed')
.pipe(mapTo(false))
);
const optimizeSuccess$ = Rx.fromEvent(this.optimizer, 'optimizeStatus')
.pipe(map(msg => !!msg.success));
Rx.combineLatest(serverListening$, optimizeSuccess$)
.pipe(
filter(([serverListening, optimizeSuccess]) => serverListening && optimizeSuccess),
take(1),
)
.toPromise()
.then(() => opn(openUrl));
}
setupWatching(extraPaths, extraIgnores) {
const chokidar = require('chokidar');
const { fromRoot } = require('../../utils');
@ -229,7 +263,10 @@ export default class ClusterManager {
return Promise.resolve();
}
return race(fromEvent(this.server, 'listening'), fromEvent(this.server, 'crashed'))
return Rx.race(
Rx.fromEvent(this.server, 'listening'),
Rx.fromEvent(this.server, 'crashed')
)
.pipe(first())
.toPromise();
}

View file

@ -125,6 +125,9 @@ export default class Worker extends EventEmitter {
case 'WORKER_BROADCAST':
this.emit('broadcast', data);
break;
case 'OPTIMIZE_STATUS':
this.emit('optimizeStatus', data);
break;
case 'WORKER_LISTENING':
this.listening = true;
this.emit('listening');

View file

@ -191,6 +191,7 @@ export default function (program) {
if (CAN_CLUSTER) {
command
.option('--dev', 'Run the server with development mode defaults')
.option('--open', 'Open a browser window to the base url after the server is started')
.option('--ssl', 'Run the dev server using HTTPS')
.option('--no-base-path', 'Don\'t put a proxy in front of the dev server, which adds a random basePath')
.option('--no-watch', 'Prevents automatic restarts of the server in --dev mode');
@ -214,6 +215,7 @@ export default function (program) {
configs: [].concat(opts.config || []),
cliArgs: {
dev: !!opts.dev,
open: !!opts.open,
envName: unknownOptions.env ? unknownOptions.env.name : undefined,
quiet: !!opts.quiet,
silent: !!opts.silent,

View file

@ -30,6 +30,7 @@ export function getEnvOptions(options: DeepPartial<EnvOptions> = {}): EnvOptions
configs: options.configs || [],
cliArgs: {
dev: true,
open: false,
quiet: false,
silent: false,
watch: false,

View file

@ -7,6 +7,7 @@ Env {
"basePath": false,
"dev": true,
"envName": "development",
"open": false,
"optimize": false,
"quiet": false,
"repl": false,
@ -42,6 +43,7 @@ Env {
"basePath": false,
"dev": false,
"envName": "production",
"open": false,
"optimize": false,
"quiet": false,
"repl": false,
@ -76,6 +78,7 @@ Env {
"cliArgs": Object {
"basePath": false,
"dev": true,
"open": false,
"optimize": false,
"quiet": false,
"repl": false,
@ -110,6 +113,7 @@ Env {
"cliArgs": Object {
"basePath": false,
"dev": false,
"open": false,
"optimize": false,
"quiet": false,
"repl": false,
@ -144,6 +148,7 @@ Env {
"cliArgs": Object {
"basePath": false,
"dev": false,
"open": false,
"optimize": false,
"quiet": false,
"repl": false,
@ -178,6 +183,7 @@ Env {
"cliArgs": Object {
"basePath": false,
"dev": false,
"open": false,
"optimize": false,
"quiet": false,
"repl": false,

View file

@ -50,6 +50,7 @@ export interface CliArgs {
repl: boolean;
basePath: boolean;
optimize: boolean;
open: boolean;
}
export class Env {

View file

@ -6,6 +6,7 @@ Array [
Object {
"basePath": true,
"dev": true,
"open": false,
"optimize": false,
"quiet": true,
"repl": false,
@ -59,6 +60,7 @@ Array [
Object {
"basePath": false,
"dev": true,
"open": false,
"optimize": false,
"quiet": false,
"repl": false,

View file

@ -18,23 +18,33 @@
*/
import WatchServer from './watch_server';
import WatchOptimizer from './watch_optimizer';
import WatchOptimizer, { STATUS } from './watch_optimizer';
export default async (kbnServer, kibanaHapiServer, config) => {
const watchOptimizer = new WatchOptimizer({
log: (tags, data) => kibanaHapiServer.log(tags, data),
uiBundles: kbnServer.uiBundles,
profile: config.get('optimize.profile'),
sourceMaps: config.get('optimize.sourceMaps'),
prebuild: config.get('optimize.watchPrebuild'),
unsafeCache: config.get('optimize.unsafeCache'),
});
const server = new WatchServer(
config.get('optimize.watchHost'),
config.get('optimize.watchPort'),
config.get('server.basePath'),
new WatchOptimizer({
log: (tags, data) => kibanaHapiServer.log(tags, data),
uiBundles: kbnServer.uiBundles,
profile: config.get('optimize.profile'),
sourceMaps: config.get('optimize.sourceMaps'),
prebuild: config.get('optimize.watchPrebuild'),
unsafeCache: config.get('optimize.unsafeCache'),
})
watchOptimizer
);
watchOptimizer.status$.subscribe({
next(status) {
process.send(['OPTIMIZE_STATUS', {
success: status.type === STATUS.SUCCESS
}]);
}
});
let ready = false;
const sendReady = () => {

View file

@ -24,7 +24,7 @@ import BaseOptimizer from '../base_optimizer';
import { createBundlesRoute } from '../bundles_route';
const STATUS = {
export const STATUS = {
RUNNING: 'optimizer running',
SUCCESS: 'optimizer completed successfully',
FAILURE: 'optimizer failed with stats',

View file

@ -57,6 +57,7 @@ export function createRootWithSettings(...settings: Array<Record<string, any>>)
configs: [],
cliArgs: {
dev: false,
open: false,
quiet: false,
silent: false,
watch: false,

View file

@ -887,6 +887,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.21.tgz#12b3f2359b27aa05a45d886c8ba1eb8d1a77e285"
integrity sha512-87XkD9qDXm8fIax+5y7drx84cXsu34ZZqfB7Cial3Q/2lxSoJ/+DRaWckkCbxP41wFSIrrb939VhzaNxj4eY1w==
"@types/opn@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@types/opn/-/opn-5.1.0.tgz#bff7bc371677f4bdbb37884400e03fd81f743927"
integrity sha512-TNPrB7Y1xl06zDI0aGyqkgxjhIev3oJ+cdqlZ52MTAHauWpEL/gIUdHebIfRHFZk9IqSBpE2ci1DT48iZH81yg==
dependencies:
"@types/node" "*"
"@types/p-cancelable@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@types/p-cancelable/-/p-cancelable-0.3.0.tgz#3e4fcc54a3dfd81d0f5b93546bb68d0df50553bb"
@ -8977,6 +8984,11 @@ is-word-character@^1.0.0:
resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.1.tgz#5a03fa1ea91ace8a6eb0c7cd770eb86d65c8befb"
integrity sha1-WgP6HqkazopusMfNdw64bWXIvvs=
is-wsl@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@ -11984,6 +11996,13 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
opn@^5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.4.0.tgz#cb545e7aab78562beb11aa3bfabc7042e1761035"
integrity sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==
dependencies:
is-wsl "^1.1.0"
oppsy@2.x.x, oppsy@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/oppsy/-/oppsy-2.0.0.tgz#3a194517adc24c3c61cdc56f35f4537e93a35e34"