Skip to content

Commit 0a2d1d3

Browse files
committed
Implement own validation error instead of using the webpack one
1 parent 6abb060 commit 0a2d1d3

File tree

4 files changed

+153
-7
lines changed

4 files changed

+153
-7
lines changed

bin/webpack-dev-server.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,10 @@ function processOptions(wpOpt) {
325325
}
326326

327327
var compiler;
328-
// TODO: This is only temporarily, we'll probably make our own ValidationError for WDS errors.
329-
var WebpackOptionsValidationError = require("webpack/lib/WebpackOptionsValidationError");
330328
try {
331329
compiler = webpack(wpOpt);
332330
} catch(e) {
333-
if(e instanceof WebpackOptionsValidationError) {
331+
if(e instanceof webpack.WebpackOptionsValidationError) {
334332
console.error(colorError(options.stats.colors, e.message));
335333
process.exit(1); // eslint-disable-line
336334
}
@@ -349,7 +347,8 @@ function processOptions(wpOpt) {
349347
try {
350348
server = new Server(compiler, options);
351349
} catch(e) {
352-
if(e instanceof WebpackOptionsValidationError) {
350+
var OptionsValidationError = require("../lib/OptionsValidationError");
351+
if(e instanceof OptionsValidationError) {
353352
console.error(colorError(options.stats.colors, e.message));
354353
process.exit(1); // eslint-disable-line
355354
}

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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ var httpProxyMiddleware = require("http-proxy-middleware");
1111
var serveIndex = require("serve-index");
1212
var historyApiFallback = require("connect-history-api-fallback");
1313
var webpack = require("webpack");
14-
var WebpackOptionsValidationError = require("webpack/lib/WebpackOptionsValidationError");
14+
var OptionsValidationError = require("./OptionsValidationError");
1515
var optionsSchema = require("./optionsSchema.json");
1616

1717
function Server(compiler, options) {
@@ -20,7 +20,7 @@ function Server(compiler, options) {
2020

2121
var validationErrors = webpack.validateSchema(optionsSchema, options);
2222
if(validationErrors.length) {
23-
throw new WebpackOptionsValidationError(validationErrors);
23+
throw new OptionsValidationError(validationErrors);
2424
}
2525

2626
if(options.lazy && !options.filename) {

lib/optionsSchema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
]
3030
},
3131
"publicPath": {
32-
"description": "TODO.",
32+
"description": "URL path where the webpack files are served from.",
3333
"type": "string"
3434
},
3535
"port": {

0 commit comments

Comments
 (0)