diff --git a/package.json b/package.json index a1430d99cee12..7fd66ad2acada 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "elasticsearch-browser": "^14.2.1", "encode-uri-query": "1.0.0", "even-better": "7.0.2", + "execa": "^0.10.0", "expiry-js": "0.1.7", "extract-text-webpack-plugin": "3.0.1", "fetch-mock": "^5.13.1", diff --git a/src/cli_plugin/install/kibana.js b/src/cli_plugin/install/kibana.js index d14f9a83d7304..0758a760bf82e 100644 --- a/src/cli_plugin/install/kibana.js +++ b/src/cli_plugin/install/kibana.js @@ -1,8 +1,6 @@ -import _ from 'lodash'; import path from 'path'; -import { fromRoot } from '../../utils'; -import KbnServer from '../../server/kbn_server'; -import { readYamlConfig } from '../../cli/serve/read_yaml_config'; +import execa from 'execa'; +import { fromRoot, watchStdioForLine } from '../../utils'; import { versionSatisfies, cleanVersion } from '../../utils/version'; import { statSync } from 'fs'; @@ -19,34 +17,22 @@ export function existingInstall(settings, logger) { export async function rebuildCache(settings, logger) { logger.log('Optimizing and caching browser bundles...'); - const serverConfig = _.merge( - readYamlConfig(settings.config), - { - env: 'production', - logging: { - silent: settings.silent, - quiet: !settings.silent, - verbose: false - }, - optimize: { - useBundleCache: false - }, - server: { - autoListen: false - }, - plugins: { - initialize: false, - scanDirs: [settings.pluginDir, fromRoot('src/core_plugins')] - }, - uiSettings: { - enabled: false - } - } - ); - - const kbnServer = new KbnServer(serverConfig); - await kbnServer.ready(); - await kbnServer.close(); + + const kibanaArgs = [ + fromRoot('./src/cli'), + '--env.name=production', + '--optimize.useBundleCache=false', + '--server.autoListen=false', + '--plugins.initialize=false', + '--uiSettings.enabled=false' + ]; + + const proc = execa(process.execPath, kibanaArgs, { + stdio: ['ignore', 'pipe', 'pipe'], + cwd: fromRoot('.'), + }); + + await watchStdioForLine(proc, () => {}, /Optimization .+ complete/); } export function assertVersion(settings) { diff --git a/src/dev/build/lib/__tests__/exec.js b/src/dev/build/lib/__tests__/exec.js index 57f05bdf7505e..752af5482aafa 100644 --- a/src/dev/build/lib/__tests__/exec.js +++ b/src/dev/build/lib/__tests__/exec.js @@ -1,5 +1,3 @@ -import { resolve } from 'path'; - import sinon from 'sinon'; import stripAnsi from 'strip-ansi'; @@ -32,31 +30,4 @@ describe('dev/build/lib/exec', () => { // log output of the process sinon.assert.calledWithExactly(onLogLine, sinon.match(/info\s+hi/)); }); - - it('send the proc SIGKILL if it logs a line matching exitAfter regexp', async function () { - // fixture proc will exit after 10 seconds if sigint not received, but the test won't fail - // unless we see the log line `SIGINT not received`, so we let the test take up to 30 seconds - // for potentially huge delays here and there - this.timeout(30000); - - await exec(log, process.execPath, [require.resolve('./fixtures/log_on_sigint')], { - exitAfter: /listening for SIGINT/ - }); - - sinon.assert.calledWithExactly(onLogLine, sinon.match(/listening for SIGINT/)); - sinon.assert.neverCalledWith(onLogLine, sinon.match(/SIGINT not received/)); - }); - - it('logs using level: option', async () => { - const parentDir = resolve(process.cwd(), '..'); - - await exec(log, process.execPath, ['-e', 'console.log(process.cwd())'], { - level: 'info', - cwd: parentDir, - }); - - // log output of the process, checking for \n to ensure cwd() doesn't log - // the subdir that this process is executing in - sinon.assert.calledWithExactly(onLogLine, sinon.match(parentDir + '\n')); - }); -}); +}); \ No newline at end of file diff --git a/src/dev/build/lib/exec.js b/src/dev/build/lib/exec.js index 051e1b3b8fe76..663cfb8cc7d8c 100644 --- a/src/dev/build/lib/exec.js +++ b/src/dev/build/lib/exec.js @@ -1,34 +1,7 @@ import execa from 'execa'; import chalk from 'chalk'; -import { Transform } from 'stream'; -import { - createPromiseFromStreams, - createSplitStream, - createMapStream, -} from '../../../utils'; - -// creates a stream that skips empty lines unless they are followed by -// another line, preventing the empty lines produced by splitStream -function skipLastEmptyLineStream() { - let skippedEmptyLine = false; - return new Transform({ - objectMode: true, - transform(line, enc, cb) { - if (skippedEmptyLine) { - this.push(''); - skippedEmptyLine = false; - } - - if (line === '') { - skippedEmptyLine = true; - return cb(); - } else { - return cb(null, line); - } - } - }); -} +import { watchStdioForLine } from '../../../utils'; export async function exec(log, cmd, args, options = {}) { const { @@ -44,32 +17,5 @@ export async function exec(log, cmd, args, options = {}) { cwd, }); - function onLogLine(line) { - log[level](line); - - if (exitAfter && exitAfter.test(line)) { - proc.kill('SIGINT'); - } - } - - await Promise.all([ - proc.catch(error => { - // ignore the error thrown by execa if it's because we killed with SIGINT - if (error.signal !== 'SIGINT') { - throw error; - } - }), - createPromiseFromStreams([ - proc.stdout, - createSplitStream('\n'), - skipLastEmptyLineStream(), - createMapStream(onLogLine), - ]), - createPromiseFromStreams([ - proc.stderr, - createSplitStream('\n'), - skipLastEmptyLineStream(), - createMapStream(onLogLine), - ]), - ]); + await watchStdioForLine(proc, line => log[level](line), exitAfter); } diff --git a/src/dev/build/lib/__tests__/fixtures/log_on_sigint.js b/src/utils/__tests__/fixtures/log_on_sigint.js similarity index 100% rename from src/dev/build/lib/__tests__/fixtures/log_on_sigint.js rename to src/utils/__tests__/fixtures/log_on_sigint.js diff --git a/src/utils/__tests__/watch_stdio_for_line.js b/src/utils/__tests__/watch_stdio_for_line.js new file mode 100644 index 0000000000000..969e638eccada --- /dev/null +++ b/src/utils/__tests__/watch_stdio_for_line.js @@ -0,0 +1,36 @@ +import execa from 'execa'; +import stripAnsi from 'strip-ansi'; +import sinon from 'sinon'; + +import { watchStdioForLine } from '../watch_stdio_for_line'; + +describe('src/utils/watch_stdio_for_line', function () { + const sandbox = sinon.sandbox.create(); + afterEach(() => sandbox.reset()); + + const onLogLine = sandbox.stub(); + const logFn = line => onLogLine(stripAnsi(line)); + + it('calls logFn with log lines', async () => { + const proc = execa(process.execPath, ['-e', 'console.log("hi")']); + + await watchStdioForLine(proc, logFn); + + // log output of the process + sinon.assert.calledWithExactly(onLogLine, sinon.match(/hi/)); + }); + + it('send the proc SIGKILL if it logs a line matching exitAfter regexp', async function () { + // fixture proc will exit after 10 seconds if sigint not received, but the test won't fail + // unless we see the log line `SIGINT not received`, so we let the test take up to 30 seconds + // for potentially huge delays here and there + this.timeout(30000); + + const proc = execa(process.execPath, [require.resolve('./fixtures/log_on_sigint')]); + + await watchStdioForLine(proc, logFn, /listening for SIGINT/); + + sinon.assert.calledWithExactly(onLogLine, sinon.match(/listening for SIGINT/)); + sinon.assert.neverCalledWith(onLogLine, sinon.match(/SIGINT not received/)); + }); +}); diff --git a/src/utils/index.js b/src/utils/index.js index 90f299e3389fa..2ebbe3631d4d9 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -7,6 +7,7 @@ export { unset } from './unset'; export { encodeQueryComponent } from './encode_query_component'; export { modifyUrl } from './modify_url'; export { getFlattenedObject } from './get_flattened_object'; +export { watchStdioForLine } from './watch_stdio_for_line'; export { getKbnTypeNames, diff --git a/src/utils/watch_stdio_for_line.js b/src/utils/watch_stdio_for_line.js new file mode 100644 index 0000000000000..653dacb3e22fa --- /dev/null +++ b/src/utils/watch_stdio_for_line.js @@ -0,0 +1,60 @@ +import { Transform } from 'stream'; + +import { + createPromiseFromStreams, + createSplitStream, + createMapStream, +} from './streams'; + +// creates a stream that skips empty lines unless they are followed by +// another line, preventing the empty lines produced by splitStream +function skipLastEmptyLineStream() { + let skippedEmptyLine = false; + return new Transform({ + objectMode: true, + transform(line, enc, cb) { + if (skippedEmptyLine) { + this.push(''); + skippedEmptyLine = false; + } + + if (line === '') { + skippedEmptyLine = true; + return cb(); + } else { + return cb(null, line); + } + } + }); +} + +export async function watchStdioForLine(proc, logFn, exitAfter) { + function onLogLine(line) { + logFn(line); + + if (exitAfter && exitAfter.test(line)) { + proc.kill('SIGINT'); + } + } + + await Promise.all([ + proc.catch(error => { + // ignore the error thrown by execa if it's because we killed with SIGINT + if (error.signal !== 'SIGINT') { + throw error; + } + }), + createPromiseFromStreams([ + proc.stdout, + createSplitStream('\n'), + skipLastEmptyLineStream(), + createMapStream(onLogLine), + ]), + createPromiseFromStreams([ + proc.stderr, + createSplitStream('\n'), + skipLastEmptyLineStream(), + createMapStream(onLogLine), + ]), + ]); +}