Skip to content

Commit eb1fa80

Browse files
authored
Merge pull request #14 from coderoad/feature/validate
Feature/validate
2 parents cc36f25 + bf94ae1 commit eb1fa80

File tree

5 files changed

+123
-51
lines changed

5 files changed

+123
-51
lines changed

Diff for: src/build.ts

+20-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as util from "util";
66
import { parse } from "./utils/parse";
77
import { getArg } from "./utils/args";
88
import { getCommits, CommitLogObject } from "./utils/commits";
9+
import { validateSchema } from "./utils/validate";
910
import * as T from "../typings/tutorial";
1011

1112
const write = util.promisify(fs.writeFile);
@@ -58,7 +59,7 @@ async function build(args: string[]) {
5859
// path to run build from
5960
const localPath = path.join(process.cwd(), options.dir);
6061

61-
// load files
62+
// load markdown and files
6263
let _markdown: string;
6364
let _yaml: string;
6465
try {
@@ -72,6 +73,7 @@ async function build(args: string[]) {
7273
return;
7374
}
7475

76+
// parse yaml config
7577
let config;
7678
try {
7779
config = yamlParser.load(_yaml);
@@ -80,6 +82,7 @@ async function build(args: string[]) {
8082
console.error(e.message);
8183
}
8284

85+
// load git commits to use in parse step
8386
let commits: CommitLogObject;
8487
try {
8588
commits = await getCommits({
@@ -92,7 +95,7 @@ async function build(args: string[]) {
9295
return;
9396
}
9497

95-
// Otherwise, continue with the other options
98+
// parse tutorial from markdown and yaml
9699
let tutorial: T.Tutorial;
97100
try {
98101
tutorial = await parse({
@@ -106,11 +109,25 @@ async function build(args: string[]) {
106109
return;
107110
}
108111

112+
// validate tutorial based on json schema
113+
try {
114+
const valid = validateSchema(tutorial);
115+
if (!valid) {
116+
console.error("Tutorial validation failed. See above to see what to fix");
117+
return;
118+
}
119+
} catch (e) {
120+
console.error("Error validating tutorial schema:");
121+
console.error(e.message);
122+
}
123+
124+
// write tutorial
109125
if (tutorial) {
110126
try {
111127
await write(options.output, JSON.stringify(tutorial), "utf8");
128+
console.info(`Success! See output at ${options.output}`);
112129
} catch (e) {
113-
console.error("Error writing tutorial json:");
130+
console.error("Error writing tutorial json file:");
114131
console.error(e.message);
115132
}
116133
}

Diff for: src/utils/schema/index.ts

+30-30
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ export default {
6969
examples: ["coderoad"],
7070
},
7171
setup: {
72-
type: "object",
7372
$ref: "#/definitions/setup_action",
7473
description:
7574
"Setup commits or commands used for setting up the test runner on tutorial launch",
@@ -97,40 +96,41 @@ export default {
9796
additionalProperties: false,
9897
required: ["uri", "branch"],
9998
},
100-
},
101-
dependencies: {
102-
type: "array",
103-
description: "A list of tutorial dependencies",
104-
items: {
99+
100+
dependencies: {
101+
type: "array",
102+
description: "A list of tutorial dependencies",
103+
items: {
104+
type: "object",
105+
properties: {
106+
name: {
107+
type: "string",
108+
description:
109+
"The command line process name of the dependency. It will be checked by running `name --version`",
110+
examples: ["node", "python"],
111+
},
112+
version: {
113+
type: "string",
114+
description:
115+
"The version requirement. See https://github.com/npm/node-semver for options",
116+
examples: [">=10"],
117+
},
118+
},
119+
required: ["name", "version"],
120+
},
121+
},
122+
appVersions: {
105123
type: "object",
124+
description:
125+
"A list of compatable coderoad versions. Currently only a VSCode extension.",
106126
properties: {
107-
name: {
108-
type: "string",
109-
description:
110-
"The command line process name of the dependency. It will be checked by running `name --version`",
111-
examples: ["node", "python"],
112-
},
113-
version: {
127+
vscode: {
114128
type: "string",
115129
description:
116-
"The version requirement. See https://github.com/npm/node-semver for options",
117-
examples: [">=10"],
130+
"The version range for coderoad-vscode that this tutorial is compatable with",
131+
examples: [">=0.7.0"],
118132
},
119133
},
120-
required: ["name", "version"],
121-
},
122-
},
123-
appVersions: {
124-
type: "object",
125-
description:
126-
"A list of compatable coderoad versions. Currently only a VSCode extension.",
127-
properties: {
128-
vscode: {
129-
type: "string",
130-
description:
131-
"The version range for coderoad-vscode that this tutorial is compatable with",
132-
examples: [">=0.7.0"],
133-
},
134134
},
135135
},
136136
additionalProperties: false,
@@ -202,7 +202,7 @@ export default {
202202
},
203203
},
204204
},
205-
required: ["title", "description", "content"],
205+
required: ["title", "summary", "content"],
206206
},
207207
minItems: 1,
208208
},

Diff for: src/utils/schema/meta.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
export default {
22
$schema: "http://json-schema.org/draft-07/schema#",
3-
$id: "http://coderoad.io/tutorial_version.schema.json",
4-
title: "Tutorial Version",
3+
$id: "https://coderoad.io/tutorial-schema.json",
4+
title: "Tutorial Schema",
55
description:
6-
"A CodeRoad tutorial version. This JSON data is converted into a tutorial with the CodeRoad editor extension",
6+
"A CodeRoad tutorial schema data. This JSON data is converted into a tutorial with the CodeRoad editor extension",
77
definitions: {
88
semantic_version: {
99
type: "string",
@@ -39,7 +39,7 @@ export default {
3939
"An array of files which will be opened by the editor when entering the level or step",
4040
items: {
4141
$ref: "#/definitions/file_path",
42-
uniqueItems: true,
42+
// uniqueItems: true,
4343
},
4444
},
4545
command_array: {
@@ -57,7 +57,7 @@ export default {
5757
"An array of git commits which will be loaded when the level/step or solution is loaded",
5858
items: {
5959
$ref: "#/definitions/sha1_hash",
60-
uniqueItems: true,
60+
// uniqueItems: true,
6161
},
6262
minItems: 1,
6363
},
@@ -77,12 +77,12 @@ export default {
7777
},
7878
watchers: {
7979
type: "array",
80-
description:
81-
"An array file paths that, when updated, will trigger the test runner to run",
8280
items: {
8381
$ref: "#/definitions/file_path",
84-
uniqueItems: true,
82+
// uniqueItems: true,
8583
},
84+
description:
85+
"An array file paths that, when updated, will trigger the test runner to run",
8686
},
8787
filter: {
8888
type: "string",

Diff for: src/utils/validate.ts

+14-10
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
1-
import * as T from "../../typings/tutorial";
21
import schema from "./schema";
32

43
// https://www.npmjs.com/package/ajv
54
// @ts-ignore ajv typings not working
65
import JsonSchema from "ajv";
76

8-
export function validateSchema(json: any) {
7+
export function validateSchema(json: any): boolean | PromiseLike<boolean> {
98
// validate using https://json-schema.org/
10-
const jsonSchema = new JsonSchema({ allErrors: true, verbose: true });
11-
// support draft-07 of json schema
12-
jsonSchema.addMetaSchema(require("ajv/lib/refs/json-schema-draft-07.json"));
9+
const jsonSchema = new JsonSchema({
10+
allErrors: true,
11+
// verbose: true,
12+
});
1313

14-
const validator = jsonSchema.compile(schema);
15-
const valid = validator(json);
14+
const valid = jsonSchema.validate(schema, json);
1615

1716
if (!valid) {
1817
// log errors
19-
console.log(jsonSchema.errorsText());
20-
throw new Error("Invalid schema. See log for details");
18+
if (process.env.NODE_ENV !== "test") {
19+
jsonSchema.errors?.forEach((error: JsonSchema.ErrorObject) => {
20+
console.warn(
21+
`Validation error at ${error.dataPath} - ${error.message}`
22+
);
23+
});
24+
}
2125
}
2226

23-
return true;
27+
return valid;
2428
}

Diff for: tests/validate.test.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as T from "../typings/tutorial";
2+
import { validateSchema } from "../src/utils/validate";
3+
4+
describe("validate", () => {
5+
it("should reject an empty tutorial", () => {
6+
const json = { version: "here" };
7+
8+
const valid = validateSchema(json);
9+
expect(valid).toBe(false);
10+
});
11+
it("should return true for a valid tutorial", () => {
12+
const json: Partial<T.Tutorial> = {
13+
version: "0.1.0",
14+
summary: { title: "Title", description: "Description" },
15+
config: {
16+
testRunner: {
17+
command: "aCommand",
18+
args: {
19+
filter: "filter",
20+
tap: "tap",
21+
},
22+
directory: "coderoad",
23+
setup: {
24+
commits: ["abcdef1"],
25+
commands: ["npm install"],
26+
},
27+
},
28+
repo: {
29+
uri: "https://github.com/some-repo.git",
30+
branch: "someBranch",
31+
},
32+
dependencies: [{ name: "name", version: ">=1" }],
33+
appVersions: {
34+
vscode: ">=0.7.0",
35+
},
36+
},
37+
levels: [
38+
{
39+
id: "L1",
40+
title: "Level 1",
41+
summary: "The first level",
42+
content: "The first level",
43+
steps: [],
44+
},
45+
],
46+
};
47+
48+
const valid = validateSchema(json);
49+
expect(valid).toBe(true);
50+
});
51+
});

0 commit comments

Comments
 (0)