@sourcecodeoer/cli
Version:
CLI tools for SourceCode
132 lines (127 loc) • 4.52 kB
JavaScript
const {readFileSync} = require("fs");
const path = require("path");
const Joi = require('@hapi/joi');
// validation schema
const tags_categories_file = require("../default_auto_generated_tags");
const auto_generated_tags_categories = Object.keys(tags_categories_file);
const linux_absolute_path = /^(?:(\/[^/ ]*)+\/?)$/;
const window_absolute_path = /^[a-zA-Z]:\\.*$/;
const is_absolute_path = new RegExp("(" + linux_absolute_path.source + ")|(" + window_absolute_path.source + ")");
const flat = arr => [].concat(...arr);
exports = module.exports = {
"command": "validator",
"describe": "Check if your JSON result file generated by crawler meet the requirements for other commands ( eg: archiver / uploader)",
"builder": function (y) {
return y
.option("inputFile", {
alias: "in",
type: "string",
demandOption: true,
description: "Absolute path to a JSON file the validator could check"
})
.coerce("inputFile", (arg) => {
return JSON.parse(readFileSync(path.resolve(arg), 'utf-8'));
})
.help()
.argv;
},
"handler": function (argv) {
mainSchema.validateAsync(argv.inputFile)
.then(() => console.log("No validation errors detected"))
.catch((err) => console.error(err));
}
};
// schemas for validations
const tagsSchema = Joi
.array()
.items(
// either a tag proposal that uses one of the defined category in
Joi
.object({
text: Joi.string().required(),
category: Joi.valid(
Joi
.ref("/own_categories", {
adjust: (v) =>
flat(
Object
.keys(v)
.map(s => {
// As JSON doesn't allow to have number as key ( but JS yes), little trick here
let maybeANumber = Number(s);
return (isNaN(maybeANumber)) ? s : [maybeANumber, s];
})
),
in: true
}
)
)
}),
// either a auto generated tag
Joi
.object({
text: Joi.string().required(),
autoGenerated: Joi.boolean().truthy().required(),
category_id: Joi.string().valid(...auto_generated_tags_categories).required()
})
)
.min(1)
.required();
const archiveSchemas = Joi
.object({
folders: Joi
.array()
.items(
Joi
.string()
.pattern(is_absolute_path, {invert: true})
)
.required(),
files: Joi
.array()
.items(
Joi
.string()
.pattern(is_absolute_path, {invert: true})
)
.required()
})
.unknown(true)
.optional();
const mainSchema = Joi.object({
// exercises should be an array of exercise object
exercises: Joi
.array()
.required()
.min(1)
.items(
Joi
.object({
title: Joi.string().required(),
description: Joi.string().required(),
tags: tagsSchema,
url: Joi
.string()
.optional()
.regex(/^https?:\/\/[^\s$.?#].[^\s]*$/)
.invalid(null),
file: Joi
.string()
.optional()
.pattern(is_absolute_path),
archive_properties: archiveSchemas
})
.unknown(true)
.required()
),
// if not empty, it should be a map with strings as keys , value should be string or number
own_categories: Joi
.object({})
.required()
.pattern(
Joi.string(),
Joi.alternatives([Joi.string(), Joi.number()])
)
})
.unknown(true)
.required();