Skip to content

Commit

Permalink
feat(server): add stdin option to API (#2186)
Browse files Browse the repository at this point in the history
* feat(server): add stdin for api

* test(stdin): switch to async await tests for stdin

* test(cli): use await timer
  • Loading branch information
knagaitsev authored and hiroppy committed Sep 9, 2019
1 parent 27b1913 commit e819cfe
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 9 deletions.
3 changes: 3 additions & 0 deletions lib/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const createDomain = require('./utils/createDomain');
const runBonjour = require('./utils/runBonjour');
const routes = require('./utils/routes');
const getSocketServerImplementation = require('./utils/getSocketServerImplementation');
const handleStdin = require('./utils/handleStdin');
const schema = require('./options.json');

// Workaround for node ^8.6.0, ^9.0.0
Expand Down Expand Up @@ -69,6 +70,8 @@ class Server {

normalizeOptions(this.compiler, this.options);

handleStdin(this.options);

updateCompiler(this.compiler, this.options);

// this.SocketServerImplementation is a class, so it must be instantiated before use
Expand Down
4 changes: 4 additions & 0 deletions lib/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,9 @@
}
]
},
"stdin": {
"type": "boolean"
},
"useLocalIp": {
"type": "boolean"
},
Expand Down Expand Up @@ -450,6 +453,7 @@
"staticOptions": "should be {Object} (https://webpack.js.org/configuration/dev-server/#devserverstaticoptions)",
"stats": "should be {Object|Boolean} (https://webpack.js.org/configuration/dev-server/#devserverstats-)",
"transportMode": "should be {String|Object} (https://webpack.js.org/configuration/dev-server/#devservertransportmode)",
"stdin": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverstdin)",
"useLocalIp": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserveruselocalip)",
"warn": "should be {Function}",
"watchContentBase": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverwatchcontentbase)",
Expand Down
7 changes: 1 addition & 6 deletions lib/utils/createConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,7 @@ function createConfig(config, argv, { port }) {
}

if (argv.stdin) {
process.stdin.on('end', () => {
// eslint-disable-next-line no-process-exit
process.exit(0);
});

process.stdin.resume();
options.stdin = true;
}

// TODO https://github.com/webpack/webpack-dev-server/issues/616 (v4)
Expand Down
16 changes: 16 additions & 0 deletions lib/utils/handleStdin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';

function handleStdin(options) {
if (options.stdin) {
// listening for this event only once makes testing easier,
// since it prevents event listeners from hanging open
process.stdin.once('end', () => {
// eslint-disable-next-line no-process-exit
process.exit(0);
});

process.stdin.resume();
}
}

module.exports = handleStdin;
42 changes: 42 additions & 0 deletions test/cli/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const { join, resolve } = require('path');
const execa = require('execa');
const { unlinkAsync } = require('../helpers/fs');
const testBin = require('../helpers/test-bin');
const timer = require('../helpers/timer');

const httpsCertificateDirectory = resolve(
__dirname,
Expand Down Expand Up @@ -83,6 +84,47 @@ describe('CLI', () => {
}
});

it('without --stdin, with stdin "end" event should time out', async (done) => {
const configPath = resolve(
__dirname,
'../fixtures/simple-config/webpack.config.js'
);
const childProcess = testBin(false, configPath, true);

childProcess.once('exit', () => {
expect(childProcess.killed).toBeTruthy();
done();
});

await timer(500);
// this is meant to confirm that it does not have any effect on the running process
// since options.stdin is not enabled
childProcess.stdin.emit('end');
childProcess.stdin.pause();

await timer(500);

childProcess.kill();
});

it('--stdin, with "end" event should exit without time out', async () => {
const configPath = resolve(
__dirname,
'../fixtures/simple-config/webpack.config.js'
);
const childProcess = testBin('--stdin', configPath);

await timer(500);

childProcess.stdin.emit('end');
childProcess.stdin.pause();

const { exitCode, timedOut, killed } = await childProcess;
expect(exitCode).toEqual(0);
expect(timedOut).toBeFalsy();
expect(killed).toBeFalsy();
});

it('should accept the promise function of webpack.config.js', async () => {
try {
const { exitCode } = await testBin(
Expand Down
19 changes: 16 additions & 3 deletions test/helpers/test-bin.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const path = require('path');
const { spawn } = require('child_process');
const execa = require('execa');

const webpackDevServerPath = path.resolve(
Expand All @@ -12,9 +13,12 @@ const basicConfigPath = path.resolve(
'../fixtures/cli/webpack.config.js'
);

function testBin(testArgs, configPath) {
function testBin(testArgs, configPath, useSpawn) {
const cwd = process.cwd();
const env = process.env.NODE_ENV;
const env = {
NODE_ENV: process.env.NODE_ENV,
PATH: process.env.PATH,
};

if (!configPath) {
configPath = basicConfigPath;
Expand All @@ -28,7 +32,16 @@ function testBin(testArgs, configPath) {

const args = [webpackDevServerPath, '--config', configPath].concat(testArgs);

return execa('node', args, { cwd, env, timeout: 10000 });
const opts = { cwd, env, timeout: 10000 };
let execLib = execa;
// use Node's spawn as a workaround for execa issues
// https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options
if (useSpawn) {
execLib = spawn;
delete opts.timeout;
}

return execLib('node', args, opts);
}

module.exports = testBin;
4 changes: 4 additions & 0 deletions test/options.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,10 @@ describe('options', () => {
},
],
},
stdin: {
success: [false],
failure: [''],
},
useLocalIp: {
success: [false],
failure: [''],
Expand Down
1 change: 1 addition & 0 deletions test/ports-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const portsList = {
'progress-option': 1,
'profile-option': 1,
Iframe: 1,
'stdin-option': 1,
};

let startPort = 8089;
Expand Down
62 changes: 62 additions & 0 deletions test/server/stdin-option.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use strict';

const config = require('../fixtures/simple-config/webpack.config');
const testServer = require('../helpers/test-server');
const timer = require('../helpers/timer');
const port = require('../ports-map')['stdin-option'];

describe('stdin', () => {
// eslint-disable-next-line no-unused-vars
let server;
let exitSpy;

beforeAll(() => {
exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
});

afterEach((done) => {
server = null;
exitSpy.mockReset();
process.stdin.removeAllListeners('end');
testServer.close(done);
});

describe('enabled', () => {
beforeAll((done) => {
server = testServer.start(
config,
{
port,
stdin: true,
},
done
);
});

it('should exit process', async () => {
process.stdin.emit('end');
await timer(1000);
process.stdin.pause();
expect(exitSpy.mock.calls[0]).toEqual([0]);
});
});

describe('disabled (default)', () => {
beforeAll((done) => {
server = testServer.start(
config,
{
port,
},
done
);
});

it('should not exit process', async () => {
process.stdin.emit('end');
await timer(1000);
process.stdin.pause();
expect(exitSpy.mock.calls.length).toEqual(0);
});
});
});
41 changes: 41 additions & 0 deletions test/server/utils/handleStdin.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict';

const timer = require('../../helpers/timer');
const handleStdin = require('../../../lib/utils/handleStdin');

describe('handleStdin', () => {
let exitSpy;

beforeAll(() => {
exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
});

afterEach(() => {
process.stdin.removeAllListeners('end');
exitSpy.mockReset();
});

describe('enabled', () => {
it('should exit process', async () => {
handleStdin({
stdin: true,
});
process.stdin.emit('end');

await timer(1000);
process.stdin.pause();
expect(exitSpy.mock.calls[0]).toEqual([0]);
});
});

describe('disabled (default)', () => {
it('should not exit process', async () => {
handleStdin({});
process.stdin.emit('end');

await timer(1000);
process.stdin.pause();
expect(exitSpy.mock.calls.length).toEqual(0);
});
});
});

0 comments on commit e819cfe

Please sign in to comment.