From 60d880775e371e8ec98f5803b08bd7ba8d5b5b4a Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 31 May 2020 14:57:54 -0700 Subject: [PATCH 1/4] schema fixes Signed-off-by: shmck --- src/utils/schema/index.ts | 60 +++++++++++++++++++-------------------- src/utils/schema/meta.ts | 4 +-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/utils/schema/index.ts b/src/utils/schema/index.ts index d1f159b..29a9c4f 100644 --- a/src/utils/schema/index.ts +++ b/src/utils/schema/index.ts @@ -69,7 +69,6 @@ export default { examples: ["coderoad"], }, setup: { - type: "object", $ref: "#/definitions/setup_action", description: "Setup commits or commands used for setting up the test runner on tutorial launch", @@ -97,40 +96,41 @@ export default { additionalProperties: false, required: ["uri", "branch"], }, - }, - dependencies: { - type: "array", - description: "A list of tutorial dependencies", - items: { + + dependencies: { + type: "array", + description: "A list of tutorial dependencies", + items: { + type: "object", + properties: { + name: { + type: "string", + description: + "The command line process name of the dependency. It will be checked by running `name --version`", + examples: ["node", "python"], + }, + version: { + type: "string", + description: + "The version requirement. See https://github.com/npm/node-semver for options", + examples: [">=10"], + }, + }, + required: ["name", "version"], + }, + }, + appVersions: { type: "object", + description: + "A list of compatable coderoad versions. Currently only a VSCode extension.", properties: { - name: { - type: "string", - description: - "The command line process name of the dependency. It will be checked by running `name --version`", - examples: ["node", "python"], - }, - version: { + vscode: { type: "string", description: - "The version requirement. See https://github.com/npm/node-semver for options", - examples: [">=10"], + "The version range for coderoad-vscode that this tutorial is compatable with", + examples: [">=0.7.0"], }, }, - required: ["name", "version"], - }, - }, - appVersions: { - type: "object", - description: - "A list of compatable coderoad versions. Currently only a VSCode extension.", - properties: { - vscode: { - type: "string", - description: - "The version range for coderoad-vscode that this tutorial is compatable with", - examples: [">=0.7.0"], - }, }, }, additionalProperties: false, @@ -202,7 +202,7 @@ export default { }, }, }, - required: ["title", "description", "content"], + required: ["title", "summary", "content"], }, minItems: 1, }, diff --git a/src/utils/schema/meta.ts b/src/utils/schema/meta.ts index 91e0e11..aa0ae37 100644 --- a/src/utils/schema/meta.ts +++ b/src/utils/schema/meta.ts @@ -77,12 +77,12 @@ export default { }, watchers: { type: "array", - description: - "An array file paths that, when updated, will trigger the test runner to run", items: { $ref: "#/definitions/file_path", uniqueItems: true, }, + description: + "An array file paths that, when updated, will trigger the test runner to run", }, filter: { type: "string", From cc46fed5738110059baf72839213f9b9f920bcdf Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 31 May 2020 16:01:51 -0700 Subject: [PATCH 2/4] fix logs Signed-off-by: shmck --- src/utils/logs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 42b44f4..3df29a6 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -7,12 +7,12 @@ const _info = console.info; console.error = function () { // @ts-ignore - _error(red.apply(console, arguments)); + _error(red.bold.apply(console, arguments)); }; console.warn = function () { // @ts-ignore - _warn(yellow.apply(console, arguments)); + _warn(yellow.bold.apply(console, arguments)); }; console.info = function () { From f29235a5b40392726065a8b67c986c3f66c8fe6a Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 31 May 2020 16:02:21 -0700 Subject: [PATCH 3/4] validate schema with tests Signed-off-by: shmck --- src/utils/schema/meta.ts | 12 +++++----- src/utils/validate.ts | 25 ++++++++++++-------- tests/validate.test.ts | 51 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 tests/validate.test.ts diff --git a/src/utils/schema/meta.ts b/src/utils/schema/meta.ts index aa0ae37..4fd1520 100644 --- a/src/utils/schema/meta.ts +++ b/src/utils/schema/meta.ts @@ -1,9 +1,9 @@ export default { $schema: "http://json-schema.org/draft-07/schema#", - $id: "http://coderoad.io/tutorial_version.schema.json", - title: "Tutorial Version", + $id: "https://coderoad.io/tutorial-schema.json", + title: "Tutorial Schema", description: - "A CodeRoad tutorial version. This JSON data is converted into a tutorial with the CodeRoad editor extension", + "A CodeRoad tutorial schema data. This JSON data is converted into a tutorial with the CodeRoad editor extension", definitions: { semantic_version: { type: "string", @@ -39,7 +39,7 @@ export default { "An array of files which will be opened by the editor when entering the level or step", items: { $ref: "#/definitions/file_path", - uniqueItems: true, + // uniqueItems: true, }, }, command_array: { @@ -57,7 +57,7 @@ export default { "An array of git commits which will be loaded when the level/step or solution is loaded", items: { $ref: "#/definitions/sha1_hash", - uniqueItems: true, + // uniqueItems: true, }, minItems: 1, }, @@ -79,7 +79,7 @@ export default { type: "array", items: { $ref: "#/definitions/file_path", - uniqueItems: true, + // uniqueItems: true, }, description: "An array file paths that, when updated, will trigger the test runner to run", diff --git a/src/utils/validate.ts b/src/utils/validate.ts index bd1dd35..128ff52 100644 --- a/src/utils/validate.ts +++ b/src/utils/validate.ts @@ -1,24 +1,29 @@ -import * as T from "../../typings/tutorial"; import schema from "./schema"; // https://www.npmjs.com/package/ajv // @ts-ignore ajv typings not working import JsonSchema from "ajv"; -export function validateSchema(json: any) { +export function validateSchema(json: any): boolean | PromiseLike { // validate using https://json-schema.org/ - const jsonSchema = new JsonSchema({ allErrors: true, verbose: true }); - // support draft-07 of json schema - jsonSchema.addMetaSchema(require("ajv/lib/refs/json-schema-draft-07.json")); + const jsonSchema = new JsonSchema({ + allErrors: true, + // verbose: true, + }); - const validator = jsonSchema.compile(schema); - const valid = validator(json); + const valid = jsonSchema.validate(schema, json); if (!valid) { // log errors - console.log(jsonSchema.errorsText()); - throw new Error("Invalid schema. See log for details"); + if (process.env.NODE_ENV !== "test") { + console.error("Validation failed. See below for details"); + jsonSchema.errors?.forEach((error: JsonSchema.ErrorObject) => { + console.warn( + `Validation error at ${error.dataPath} - ${error.message}` + ); + }); + } } - return true; + return valid; } diff --git a/tests/validate.test.ts b/tests/validate.test.ts new file mode 100644 index 0000000..66626b5 --- /dev/null +++ b/tests/validate.test.ts @@ -0,0 +1,51 @@ +import * as T from "../typings/tutorial"; +import { validateSchema } from "../src/utils/validate"; + +describe("validate", () => { + it("should reject an empty tutorial", () => { + const json = { version: "here" }; + + const valid = validateSchema(json); + expect(valid).toBe(false); + }); + it("should return true for a valid tutorial", () => { + const json: Partial = { + version: "0.1.0", + summary: { title: "Title", description: "Description" }, + config: { + testRunner: { + command: "aCommand", + args: { + filter: "filter", + tap: "tap", + }, + directory: "coderoad", + setup: { + commits: ["abcdef1"], + commands: ["npm install"], + }, + }, + repo: { + uri: "https://github.com/some-repo.git", + branch: "someBranch", + }, + dependencies: [{ name: "name", version: ">=1" }], + appVersions: { + vscode: ">=0.7.0", + }, + }, + levels: [ + { + id: "L1", + title: "Level 1", + summary: "The first level", + content: "The first level", + steps: [], + }, + ], + }; + + const valid = validateSchema(json); + expect(valid).toBe(true); + }); +}); From bf94ae11c338dfa0596585aaa8f0992824ac843c Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 31 May 2020 16:08:31 -0700 Subject: [PATCH 4/4] add schema validation Signed-off-by: shmck --- src/build.ts | 23 ++++++++++++++++++++--- src/utils/logs.ts | 4 ++-- src/utils/validate.ts | 1 - 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/build.ts b/src/build.ts index 13fb17b..86d1165 100644 --- a/src/build.ts +++ b/src/build.ts @@ -6,6 +6,7 @@ import * as util from "util"; import { parse } from "./utils/parse"; import { getArg } from "./utils/args"; import { getCommits, CommitLogObject } from "./utils/commits"; +import { validateSchema } from "./utils/validate"; import * as T from "../typings/tutorial"; const write = util.promisify(fs.writeFile); @@ -58,7 +59,7 @@ async function build(args: string[]) { // path to run build from const localPath = path.join(process.cwd(), options.dir); - // load files + // load markdown and files let _markdown: string; let _yaml: string; try { @@ -72,6 +73,7 @@ async function build(args: string[]) { return; } + // parse yaml config let config; try { config = yamlParser.load(_yaml); @@ -80,6 +82,7 @@ async function build(args: string[]) { console.error(e.message); } + // load git commits to use in parse step let commits: CommitLogObject; try { commits = await getCommits({ @@ -92,7 +95,7 @@ async function build(args: string[]) { return; } - // Otherwise, continue with the other options + // parse tutorial from markdown and yaml let tutorial: T.Tutorial; try { tutorial = await parse({ @@ -106,11 +109,25 @@ async function build(args: string[]) { return; } + // validate tutorial based on json schema + try { + const valid = validateSchema(tutorial); + if (!valid) { + console.error("Tutorial validation failed. See above to see what to fix"); + return; + } + } catch (e) { + console.error("Error validating tutorial schema:"); + console.error(e.message); + } + + // write tutorial if (tutorial) { try { await write(options.output, JSON.stringify(tutorial), "utf8"); + console.info(`Success! See output at ${options.output}`); } catch (e) { - console.error("Error writing tutorial json:"); + console.error("Error writing tutorial json file:"); console.error(e.message); } } diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 3df29a6..42b44f4 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -7,12 +7,12 @@ const _info = console.info; console.error = function () { // @ts-ignore - _error(red.bold.apply(console, arguments)); + _error(red.apply(console, arguments)); }; console.warn = function () { // @ts-ignore - _warn(yellow.bold.apply(console, arguments)); + _warn(yellow.apply(console, arguments)); }; console.info = function () { diff --git a/src/utils/validate.ts b/src/utils/validate.ts index 128ff52..0d48626 100644 --- a/src/utils/validate.ts +++ b/src/utils/validate.ts @@ -16,7 +16,6 @@ export function validateSchema(json: any): boolean | PromiseLike { if (!valid) { // log errors if (process.env.NODE_ENV !== "test") { - console.error("Validation failed. See below for details"); jsonSchema.errors?.forEach((error: JsonSchema.ErrorObject) => { console.warn( `Validation error at ${error.dataPath} - ${error.message}`