Skip to content

Commit 1de4edb

Browse files
committed
Merge branch 'options-schema'
2 parents 4660e77 + f6d24be commit 1de4edb

File tree

6 files changed

+478
-16
lines changed

6 files changed

+478
-16
lines changed

bin/webpack-dev-server.js

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,20 @@ function versionInfo() {
2323
"webpack " + require("webpack/package.json").version;
2424
}
2525

26-
function colorize(useColor, msg) {
26+
function colorInfo(useColor, msg) {
2727
if(useColor)
2828
// Make text blue and bold, so it *pops*
2929
return "\u001b[1m\u001b[34m" + msg + "\u001b[39m\u001b[22m";
3030
return msg;
3131
}
3232

33+
function colorError(useColor, msg) {
34+
if(useColor)
35+
// Make text red and bold, so it *pops*
36+
return "\u001b[1m\u001b[31m" + msg + "\u001b[39m\u001b[22m";
37+
return msg;
38+
}
39+
3340
var yargs = require("yargs")
3441
.usage(versionInfo() +
3542
"\nUsage: http://webpack.github.io/docs/webpack-dev-server.html");
@@ -204,8 +211,6 @@ function processOptions(wpOpt) {
204211
options.publicPath = "/" + options.publicPath;
205212
}
206213

207-
if(!options.outputPath)
208-
options.outputPath = "/";
209214
if(!options.filename)
210215
options.filename = firstWpOpt.output && firstWpOpt.output.filename;
211216

@@ -344,12 +349,8 @@ function startDevServer(wpOpt, options) {
344349
try {
345350
compiler = webpack(wpOpt);
346351
} catch(e) {
347-
var WebpackOptionsValidationError = require("webpack/lib/WebpackOptionsValidationError");
348-
if(e instanceof WebpackOptionsValidationError) {
349-
if(options.stats.colors)
350-
console.error("\u001b[1m\u001b[31m" + e.message + "\u001b[39m\u001b[22m");
351-
else
352-
console.error(e.message);
352+
if(e instanceof webpack.WebpackOptionsValidationError) {
353+
console.error(colorError(options.stats.colors, e.message));
353354
process.exit(1); // eslint-disable-line
354355
}
355356
throw e;
@@ -363,7 +364,17 @@ function startDevServer(wpOpt, options) {
363364

364365
var uri = domain + (options.inline !== false ? "/" : "/webpack-dev-server/");
365366

366-
var server = new Server(compiler, options);
367+
var server;
368+
try {
369+
server = new Server(compiler, options);
370+
} catch(e) {
371+
var OptionsValidationError = require("../lib/OptionsValidationError");
372+
if(e instanceof OptionsValidationError) {
373+
console.error(colorError(options.stats.colors, e.message));
374+
process.exit(1); // eslint-disable-line
375+
}
376+
throw e;
377+
}
367378

368379
if(options.socket) {
369380
server.listeningApp.on("error", function(e) {
@@ -401,18 +412,18 @@ function startDevServer(wpOpt, options) {
401412

402413
function reportReadiness(uri, options) {
403414
var useColor = options.stats.colors;
404-
var startSentence = "Project is running at " + colorize(useColor, uri)
415+
var startSentence = "Project is running at " + colorInfo(useColor, uri)
405416
if(options.socket) {
406-
startSentence = "Listening to socket at " + colorize(useColor, options.socket);
417+
startSentence = "Listening to socket at " + colorInfo(useColor, options.socket);
407418
}
408419
console.log((argv["progress"] ? "\n" : "") + startSentence);
409420

410-
console.log("webpack output is served from " + colorize(useColor, options.publicPath));
421+
console.log("webpack output is served from " + colorInfo(useColor, options.publicPath));
411422
var contentBase = Array.isArray(options.contentBase) ? options.contentBase.join(", ") : options.contentBase;
412423
if(contentBase)
413-
console.log("Content not from webpack is served from " + colorize(useColor, contentBase));
424+
console.log("Content not from webpack is served from " + colorInfo(useColor, contentBase));
414425
if(options.historyApiFallback)
415-
console.log("404s will fallback to " + colorize(useColor, options.historyApiFallback.index || "/index.html"));
426+
console.log("404s will fallback to " + colorInfo(useColor, options.historyApiFallback.index || "/index.html"));
416427
if(options.open)
417428
open(uri);
418429
}

lib/OptionsValidationError.js

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
var optionsSchema = require("./optionsSchema.json");
2+
3+
function OptionsValidationError(validationErrors) {
4+
Error.call(this);
5+
Error.captureStackTrace(this, OptionsValidationError);
6+
this.name = "WebpackDevServerOptionsValidationError";
7+
this.message = "Invalid configuration object. " +
8+
"webpack-dev-server has been initialised using a configuration object that does not match the API schema.\n" +
9+
validationErrors.map(function(err) {
10+
return " - " + indent(OptionsValidationError.formatValidationError(err), " ", false);
11+
}).join("\n");
12+
this.validationErrors = validationErrors;
13+
}
14+
module.exports = OptionsValidationError;
15+
16+
OptionsValidationError.prototype = Object.create(Error.prototype);
17+
OptionsValidationError.prototype.constructor = OptionsValidationError;
18+
19+
OptionsValidationError.formatValidationError = function formatValidationError(err) {
20+
var dataPath = "configuration" + err.dataPath;
21+
switch(err.keyword) {
22+
case "additionalProperties":
23+
return dataPath + " has an unknown property '" + err.params.additionalProperty + "'. These properties are valid:\n" +
24+
getSchemaPartText(err.parentSchema);
25+
case "oneOf":
26+
case "anyOf":
27+
case "enum":
28+
return dataPath + " should be one of these:\n" +
29+
getSchemaPartText(err.parentSchema);
30+
case "allOf":
31+
return dataPath + " should be:\n" +
32+
getSchemaPartText(err.parentSchema);
33+
case "type":
34+
switch(err.params.type) {
35+
case "object":
36+
return dataPath + " should be an object.";
37+
case "string":
38+
return dataPath + " should be a string.";
39+
case "boolean":
40+
return dataPath + " should be a boolean.";
41+
case "number":
42+
return dataPath + " should be a number.";
43+
}
44+
return dataPath + " should be " + err.params.type + ":\n" +
45+
getSchemaPartText(err.parentSchema);
46+
case "required":
47+
var missingProperty = err.params.missingProperty.replace(/^\./, "");
48+
return dataPath + " misses the property '" + missingProperty + "'.\n" +
49+
getSchemaPartText(err.parentSchema, ["properties", missingProperty]);
50+
case "minLength":
51+
if(err.params.limit === 1)
52+
return dataPath + " should not be empty.";
53+
else
54+
return dataPath + " " + err.message;
55+
default:
56+
return dataPath + " " + err.message + " (" + JSON.stringify(err, 0, 2) + ").\n" +
57+
getSchemaPartText(err.parentSchema);
58+
}
59+
}
60+
61+
function getSchemaPart(path, parents, additionalPath) {
62+
parents = parents || 0;
63+
path = path.split("/");
64+
path = path.slice(0, path.length - parents);
65+
if(additionalPath) {
66+
additionalPath = additionalPath.split("/");
67+
path = path.concat(additionalPath);
68+
}
69+
var schemaPart = optionsSchema;
70+
for(var i = 1; i < path.length; i++) {
71+
var inner = schemaPart[path[i]];
72+
if(inner)
73+
schemaPart = inner;
74+
}
75+
return schemaPart;
76+
}
77+
78+
function getSchemaPartText(schemaPart, additionalPath) {
79+
if(additionalPath) {
80+
for(var i = 0; i < additionalPath.length; i++) {
81+
var inner = schemaPart[additionalPath[i]];
82+
if(inner)
83+
schemaPart = inner;
84+
}
85+
}
86+
while(schemaPart.$ref) schemaPart = getSchemaPart(schemaPart.$ref);
87+
var schemaText = OptionsValidationError.formatSchema(schemaPart);
88+
if(schemaPart.description)
89+
schemaText += "\n" + schemaPart.description;
90+
return schemaText;
91+
}
92+
93+
function formatSchema(schema, prevSchemas) {
94+
prevSchemas = prevSchemas || [];
95+
96+
function formatInnerSchema(innerSchema, addSelf) {
97+
if(!addSelf) return formatSchema(innerSchema, prevSchemas);
98+
if(prevSchemas.indexOf(innerSchema) >= 0) return "(recursive)";
99+
return formatSchema(innerSchema, prevSchemas.concat(schema));
100+
}
101+
switch(schema.type) {
102+
case "string":
103+
return "string";
104+
case "boolean":
105+
return "boolean";
106+
case "number":
107+
return "number";
108+
case "object":
109+
if(schema.properties) {
110+
var required = schema.required || [];
111+
return "object { " + Object.keys(schema.properties).map(function(property) {
112+
if(required.indexOf(property) < 0) return property + "?";
113+
return property;
114+
}).concat(schema.additionalProperties ? ["..."] : []).join(", ") + " }";
115+
}
116+
if(schema.additionalProperties) {
117+
return "object { <key>: " + formatInnerSchema(schema.additionalProperties) + " }";
118+
}
119+
return "object";
120+
case "array":
121+
return "[" + formatInnerSchema(schema.items) + "]";
122+
}
123+
switch(schema.instanceof) {
124+
case "Function":
125+
return "function";
126+
case "RegExp":
127+
return "RegExp";
128+
}
129+
if(schema.$ref) return formatInnerSchema(getSchemaPart(schema.$ref), true);
130+
if(schema.allOf) return schema.allOf.map(formatInnerSchema).join(" & ");
131+
if(schema.oneOf) return schema.oneOf.map(formatInnerSchema).join(" | ");
132+
if(schema.anyOf) return schema.anyOf.map(formatInnerSchema).join(" | ");
133+
if(schema.enum) return schema.enum.map(function(item) {
134+
return JSON.stringify(item);
135+
}).join(" | ");
136+
return JSON.stringify(schema, 0, 2);
137+
}
138+
139+
function indent(str, prefix, firstLine) {
140+
if(firstLine) {
141+
return prefix + str.replace(/\n(?!$)/g, "\n" + prefix);
142+
} else {
143+
return str.replace(/\n(?!$)/g, "\n" + prefix);
144+
}
145+
}
146+
147+
OptionsValidationError.formatSchema = formatSchema;

lib/Server.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,21 @@ var spdy = require("spdy");
1010
var httpProxyMiddleware = require("http-proxy-middleware");
1111
var serveIndex = require("serve-index");
1212
var historyApiFallback = require("connect-history-api-fallback");
13+
var webpack = require("webpack");
14+
var OptionsValidationError = require("./OptionsValidationError");
15+
var optionsSchema = require("./optionsSchema.json");
1316

1417
var clientStats = { errorDetails: false };
1518

1619
function Server(compiler, options) {
1720
// Default options
1821
if(!options) options = {};
1922

23+
var validationErrors = webpack.validateSchema(optionsSchema, options);
24+
if(validationErrors.length) {
25+
throw new OptionsValidationError(validationErrors);
26+
}
27+
2028
if(options.lazy && !options.filename) {
2129
throw new Error("'filename' option must be set in lazy mode.");
2230
}

0 commit comments

Comments
 (0)