Skip to content

Commit

Permalink
feat: improve the open option, you can specify target and app o…
Browse files Browse the repository at this point in the history
…ptions

BREAKING CHANGE: the `openPage` option and `--open-page` CLI option was removed in favor `open: boolean | string | (string | { target?: string | string[], app: string | string[] })[]` and `--open-target [URL]` and `--open-app <browser>` for CLI
  • Loading branch information
alexander-akait committed Apr 2, 2021
1 parent b3374c3 commit e3c2683
Show file tree
Hide file tree
Showing 10 changed files with 959 additions and 676 deletions.
32 changes: 28 additions & 4 deletions bin/cli-flags.js
Expand Up @@ -111,9 +111,11 @@ module.exports = {
'Do not close and exit the process on SIGNIT and SIGTERM.',
negative: true,
},
// TODO remove in the next major release in favor `--open-target`
{
name: 'open',
type: [Boolean, String],
multiple: true,
configs: [
{
type: 'boolean',
Expand All @@ -122,18 +124,40 @@ module.exports = {
type: 'string',
},
],
description:
'Open the default browser, or optionally specify a browser name.',
description: 'Open the default browser.',
},
{
name: 'open-page',
name: 'open-app',
type: String,
configs: [
{
type: 'string',
},
],
description: 'Open default browser with the specified page.',
description: 'Open specified browser.',
processor(opts) {
opts.open = opts.open || {};
opts.open.app = opts.openApp.split(' ');
delete opts.openApp;
},
},
{
name: 'open-target',
type: String,
configs: [
{
type: 'boolean',
},
{
type: 'string',
},
],
description: 'Open specified browser.',
processor(opts) {
opts.open = opts.open || {};
opts.open.target = opts.openTarget;
delete opts.openTarget;
},
multiple: true,
},
{
Expand Down
4 changes: 2 additions & 2 deletions lib/Server.js
Expand Up @@ -719,10 +719,10 @@ class Server {
);
}

if (this.options.open || this.options.openPage) {
if (this.options.open) {
const openTarget = prettyPrintUrl(this.hostname || 'localhost');

runOpen(openTarget, this.options, this.logger);
runOpen(openTarget, this.options.open, this.logger);
}
}

Expand Down
79 changes: 64 additions & 15 deletions lib/options.json
Expand Up @@ -10,7 +10,8 @@
"minLength": 1
},
"staticOptions": {
"type": "object"
"type": "object",
"additionalProperties": true
},
"publicPath": {
"anyOf": [
Expand All @@ -34,7 +35,8 @@
"type": "boolean"
},
{
"type": "object"
"type": "object",
"additionalProperties": true
}
]
},
Expand All @@ -53,6 +55,54 @@
"StaticString": {
"type": "string",
"minLength": 1
},
"OpenBoolean": {
"type": "boolean"
},
"OpenString": {
"type": "string",
"minLength": 1
},
"OpenObject": {
"type": "object",
"additionalProperties": false,
"properties": {
"target": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string",
"minLength": 1
},
{
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1
}
]
},
"app": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1
}
]
}
}
}
},
"properties": {
Expand Down Expand Up @@ -258,25 +308,25 @@
"open": {
"anyOf": [
{
"type": "string"
"$ref": "#/definitions/OpenBoolean"
},
{
"type": "boolean"
"$ref": "#/definitions/OpenString"
},
{
"type": "object"
}
]
},
"openPage": {
"anyOf": [
{
"type": "string"
"$ref": "#/definitions/OpenObject"
},
{
"type": "array",
"items": {
"type": "string"
"anyOf": [
{
"$ref": "#/definitions/OpenString"
},
{
"$ref": "#/definitions/OpenObject"
}
]
},
"minItems": 1
}
Expand Down Expand Up @@ -393,8 +443,7 @@
"onAfterSetupMiddleware": "should be {Function} (https://webpack.js.org/configuration/dev-server/#devserverafter)",
"onBeforeSetupMiddleware": "should be {Function} (https://webpack.js.org/configuration/dev-server/#devserverbefore)",
"onListening": "should be {Function} (https://webpack.js.org/configuration/dev-server/#onlistening)",
"open": "should be {String|Boolean|Object} (https://webpack.js.org/configuration/dev-server/#devserveropen)",
"openPage": "should be {String|Array} (https://webpack.js.org/configuration/dev-server/#devserveropenpage)",
"open": "should be {Boolean|String|(STRING | Object)[]} (https://webpack.js.org/configuration/dev-server/#devserveropen)",
"port": "should be {Number|String|Null} (https://webpack.js.org/configuration/dev-server/#devserverport)",
"proxy": "should be {Object|Array} (https://webpack.js.org/configuration/dev-server/#devserverproxy)",
"public": "should be {String} (https://webpack.js.org/configuration/dev-server/#devserverpublic)",
Expand Down
82 changes: 64 additions & 18 deletions lib/utils/runOpen.js
Expand Up @@ -5,29 +5,75 @@ const isAbsoluteUrl = require('is-absolute-url');

function runOpen(uri, options, logger) {
// https://github.com/webpack/webpack-dev-server/issues/1990
let openOptions = { wait: false };
let openOptionValue = '';

if (typeof options.open === 'string') {
openOptions = Object.assign({}, openOptions, { app: options.open });
openOptionValue = `: "${options.open}"`;
} else if (typeof options.open === 'object') {
openOptions = options.open;
openOptionValue = `: "${JSON.stringify(options.open)}"`;
}
const defaultOpenOptions = { wait: false };
const openTasks = [];

const getOpenTask = (item) => {
if (typeof item === 'boolean') {
return [{ target: uri, options: defaultOpenOptions }];
}

if (typeof item === 'string') {
return [{ target: item, options: defaultOpenOptions }];
}

let targets;

if (item.target) {
targets = Array.isArray(item.target) ? item.target : [item.target];
} else {
targets = [uri];
}

const pages =
typeof options.openPage === 'string'
? [options.openPage]
: options.openPage || [''];
return targets.map((target) => {
const openOptions = defaultOpenOptions;

if (item.app) {
openOptions.app = item.app;
}

return { target, options: openOptions };
});
};

if (Array.isArray(options)) {
options.forEach((item) => {
openTasks.push(...getOpenTask(item));
});
} else {
openTasks.push(...getOpenTask(options));
}

return Promise.all(
pages.map((page) => {
const pageUrl = page && isAbsoluteUrl(page) ? page : `${uri}${page}`;
openTasks.map((openTask) => {
let openTarget;

if (openTask.target) {
if (typeof openTask.target === 'boolean') {
openTarget = uri;
} else {
openTarget = isAbsoluteUrl(openTask.target)
? openTask.target
: new URL(openTask.target, uri).toString();
}
} else {
openTarget = uri;
}

return open(pageUrl, openOptions).catch(() => {
return open(openTarget, openTask.options).catch(() => {
logger.warn(
`Unable to open "${pageUrl}" in browser${openOptionValue}. If you are running in a headless environment, please do not use the --open flag`
`Unable to open "${openTarget}" page${
// eslint-disable-next-line no-nested-ternary
openTask.options.app
? Array.isArray(openTask.options.app)
? ` in "${
openTask.options.app[0]
}" app with "${openTask.options.app
.slice(1)
.join(' ')}" arguments`
: ` in "${openTask.options.app}" app`
: ''
}. If you are running in a headless environment, please do not use the "--open" flag or the "open" option.`
);
});
})
Expand Down
2 changes: 1 addition & 1 deletion test/__snapshots__/Validation.test.js.snap
Expand Up @@ -43,5 +43,5 @@ exports[`Validation validation should fail validation for invalid \`static\` con
exports[`Validation validation should fail validation for no additional properties 1`] = `
"Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema.
- configuration has an unknown property 'additional'. These properties are valid:
object { bonjour?, client?, compress?, dev?, firewall?, headers?, historyApiFallback?, host?, hot?, http2?, https?, liveReload?, onAfterSetupMiddleware?, onBeforeSetupMiddleware?, onListening?, open?, openPage?, port?, proxy?, public?, setupExitSignals?, static?, transportMode? }"
object { bonjour?, client?, compress?, dev?, firewall?, headers?, historyApiFallback?, host?, hot?, http2?, https?, liveReload?, onAfterSetupMiddleware?, onBeforeSetupMiddleware?, onListening?, open?, port?, proxy?, public?, setupExitSignals?, static?, transportMode? }"
`;
72 changes: 72 additions & 0 deletions test/cli/cli.test.js
Expand Up @@ -219,6 +219,78 @@ describe('CLI', () => {
.catch(done);
});

it('--open', (done) => {
testBin('--open')
.then((output) => {
expect(output.exitCode).toEqual(0);
done();
})
.catch(done);
});

it('--open /index.html', (done) => {
testBin('--open /index.html')
.then((output) => {
expect(output.exitCode).toEqual(0);
done();
})
.catch(done);
});

it('--open /first.html second.html', (done) => {
testBin('--open /first.html second.html')
.then((output) => {
expect(output.exitCode).toEqual(0);
done();
})
.catch(done);
});

it('--open-app google-chrome', (done) => {
testBin('--open-app google-chrome')
.then((output) => {
expect(output.exitCode).toEqual(0);
done();
})
.catch(done);
});

it('--open-target', (done) => {
testBin('--open-target')
.then((output) => {
expect(output.exitCode).toEqual(0);
done();
})
.catch(done);
});

it('--open-target index.html', (done) => {
testBin('--open-target index.html')
.then((output) => {
expect(output.exitCode).toEqual(0);
done();
})
.catch(done);
});

it('--open-target /first.html second.html', (done) => {
testBin('--open-target /first.html second.html')
.then((output) => {
expect(output.exitCode).toEqual(0);
done();
})
.catch(done);
});

it('--open-target /index.html --open-app google-chrome', (done) => {
testBin('--open-target /index.html --open-app google-chrome')
.then((output) => {
expect(output.exitCode).toEqual(0);
done();
})
.catch(done);
});

it('should log public path', (done) => {
testBin(
'--no-color',
Expand Down

0 comments on commit e3c2683

Please sign in to comment.