-
-
Save TimMensch/45df32352b9d32353ab8b5912362632b to your computer and use it in GitHub Desktop.
| /* | |
| Copyright 2017, Tim Mensch. | |
| Permission is hereby granted, free of charge, to any person obtaining | |
| a copy of this software and associated documentation files (the | |
| "Software"), to deal in the Software without restriction, including | |
| without limitation the rights to use, copy, modify, merge, publish, | |
| distribute, sublicense, and/or sell copies of the Software, and to | |
| permit persons to whom the Software is furnished to do so, subject | |
| to the following conditions: | |
| The above copyright notice and this permission notice shall be | |
| included in all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
| OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
| HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
| DEALINGS IN THE SOFTWARE. | |
| */ | |
| "use strict"; | |
| const gulp = require('gulp'); | |
| const mapStream = require('map-stream'); | |
| const newer = require('gulp-newer'); | |
| const transform = require('vinyl-transform'); | |
| const rename = require('gulp-rename'); | |
| const strip = require('gulp-strip-comments'); | |
| const fs = require('fs'); | |
| const promisify = require('es6-promisify'); | |
| const readFile = promisify(fs.readFile); | |
| const cache = require('gulp-cached'); | |
| const _ = require("lodash"); | |
| let TJS; | |
| let tv4; | |
| /** | |
| * Temporary (?) workaround for the fact that, sometimes, when there's a | |
| * type|otherType given, it will give *both* a type and a oneOf object. | |
| * The type in this case is completely wrong. This tree traversal deletes | |
| * any "type" object members that exist alongside a "oneOf" member. | |
| * | |
| * @param {any} schema | |
| */ | |
| function cleanSchema(schema) { | |
| for (let key in schema) { | |
| if (schema.hasOwnProperty(key)) { | |
| let member = schema[ key ]; | |
| if (typeof member === "object") { | |
| if (member.type && member.oneOf) { | |
| delete member.type; | |
| } | |
| cleanSchema(member); | |
| } | |
| } | |
| } | |
| } | |
| function makeGenerator(typeToValidate, cb) { | |
| return transform((filename) => { | |
| if (!TJS) | |
| TJS = require("typescript-json-schema"); | |
| return mapStream((chunk, next) => { | |
| // Thrown errors from within a mapStream callback | |
| // aren't propagated, so I'm wrapping this in a | |
| // try/catch. | |
| try { | |
| const schema = TJS.generateSchema( | |
| TJS.getProgramFromFiles([ "./src/declarations/es6.d.ts", filename ]), | |
| typeToValidate | |
| ); | |
| if (!schema) { | |
| return cb("Schema generation failed."); | |
| } | |
| cleanSchema(schema); | |
| return next(null, JSON.stringify(schema)); | |
| } catch (e) { | |
| return cb(e); | |
| } | |
| }); | |
| }); | |
| } | |
| function generateSchema(type, cb) { | |
| const generate = makeGenerator(type, cb); | |
| gulp.src("./src/declarations/scenario.d.ts") | |
| .pipe(newer({ dest: `./int/schema/${type}`, extra: [ "./src/declarations/trait.d.ts" ] })) | |
| .pipe(generate) | |
| .pipe(rename(type)) | |
| .pipe(gulp.dest("./int/schema")) | |
| .on('end', cb); | |
| } | |
| let schemas = {}; | |
| function validateSchema(type, files) { | |
| const promise = new Promise((resolve, reject) => { | |
| const schemaSource = readFile(`./int/schema/${type}`); | |
| const validate = transform((filename) => { | |
| return mapStream((chunk, next) => { | |
| schemaSource.then((schemaJson) => { | |
| try { | |
| const json = JSON.parse(chunk); | |
| if (schemaJson.length === 0) { | |
| return reject("Bad schema!"); | |
| } | |
| if (!schemas[ type ]) { | |
| schemas[ type ] = JSON.parse(schemaJson.toString()); | |
| } | |
| const schema = schemas[ type ]; | |
| if (!tv4) { | |
| tv4 = require("tv4"); | |
| } | |
| const result = tv4.validateResult(json, schema, false, true); | |
| if (!result.valid) { | |
| const suberrors = _.map( result.error.subErrors, (suberror) => { | |
| return `${suberror.message}: ${suberror.dataPath}`; | |
| }).join("\n"); | |
| return reject(`Validation error in ${filename}: ${result.error.message}. | |
| ${suberrors} | |
| At data path: ${result.error.dataPath}`); | |
| } | |
| return next(null, chunk); | |
| } catch (e) { | |
| console.error(`Error in parsing ${filename}: ${e}.`); | |
| return reject(e); | |
| } | |
| }); | |
| }); | |
| }); | |
| gulp.src(files) | |
| .pipe(cache(`${type}-linting`)) | |
| .pipe(strip()) | |
| .pipe(validate) | |
| .on("end", () => { | |
| resolve(); | |
| }); | |
| }); | |
| return promise; | |
| } | |
| function generateAndValidate(task, type, paths) { | |
| var generateTask = task + "-generate"; | |
| gulp.task(generateTask, function (cb) { | |
| generateSchema(type, cb); | |
| }); | |
| gulp.task(task, [ generateTask ], function () { | |
| return validateSchema(type, paths); | |
| }); | |
| } | |
| module.exports = generateAndValidate; |
Also, recommend naming js files like so:
validate-schema.js
not like so:
validateSchema.js
that's my preference
why? because *nix vs macos differ in case sensititivy, so I keep all filenames lowercase because i am badass like that, but last time I checked Mensch was on Windows so he's in the clear on this one :)
My JavaScript style is heavily influenced by my TypeScript style:
-
Double quotes are the standard I adhere to. JSON requires double quotes, in fact, and I like consistency. Single quotes are for ex-JavaScript-only devs who don't also use C, C++, C#, Go, Java, and Swift on a regular basis. If you note, all TypeScript samples use double quotes as well.
-
TypeScript itself will enforce consistent case in file includes. I also build most everything on Linux. It's an aesthetic difference, and I prefer to use camel case.
-
TJSis loaded lazily, if it's needed. Most of the time the schemas don't change, soTJSisn't needed. AndTJSis big, counting its dependencies. I like my gulpfile to load faster when it can. -
Not sure what you mean about the EE.prototype.once. If you mean the
.on("end")calls, literally every example is seen for gulp pipes useson. Is there an advantage toonce, given thatendcan only happen once?
Here's my feedback
EE.prototype.onceinstead ofEE.prototype.onin general