diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index fb2f3b593..bc23e7405 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -11,11 +11,24 @@ jobs: - uses: actions/checkout@v2 - uses: dorny/paths-filter@v2 - id: changes + id: filter with: filters: | - json: + any-json: - 'frontend/**/*.json' + language-json: + - 'frontend/static/languages/*.json' + quotes-json: + - 'frontend/static/quotes/*.json' + other-json: + - 'frontend/funbox/*.json' + - 'frontend/fonts/*.json' + - 'frontend/themes/*.json' + - 'frontend/challenges/*.json' + any-tsscss: + - 'frontend/**/*.scss' + - 'frontend/**/*.js' + - 'frontend/**/*.ts' scss: - 'frontend/**/*.scss' ts: @@ -45,14 +58,30 @@ jobs: # - name: Run webpack # run: npm run build:live - - name: Lint and validate JSON - if: steps.filter.outputs.json == 'true' - run: npm run pr-check-json + - name: Lint JSON + if: steps.filter.outputs.any-json == 'true' + run: npm run pr-check-lint-json - - name: Lint and compile SCSS + - name: Validate languages JSON + if: steps.filter.outputs.language-json == 'true' + run: npm run pr-check-language-json + + - name: Validate quotes JSON + if: steps.filter.outputs.quotes-json == 'true' + run: npm run pr-check-quote-json + + - name: Validate other JSON + if: steps.filter.outputs.other-json == 'true' + run: npm run pr-check-other-json + + - name: Lint + if: steps.filter.outputs.any-tsscss == 'true' + run: npm run pr-check-lint-json + + - name: Compile SCSS if: steps.filter.outputs.scss == 'true' run: npm run pr-check-scss - - name: Lint source code and run webpack - if: steps.filter.outputs.scss == 'true' + - name: Run webpack + if: steps.filter.outputs.ts == 'true' run: npm run pr-check-ts diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js index dcf23499f..26d742345 100644 --- a/frontend/gulpfile.js +++ b/frontend/gulpfile.js @@ -36,7 +36,7 @@ task("lint-json", function () { }); task("validate-json-schema", function () { - return JSONValidation.validate(); + return JSONValidation.validateAll(); }); task("copy-src-contents", function () { @@ -150,8 +150,26 @@ task("build", series("clean", "compile")); task("build-production", series("clean", "compile-production")); -task("pr-check-json", series("lint-json", "validate-json-schema")); +//PR CHECK -task("pr-check-scss", series("lint", "sass")); +task("validate-quote-json-schema", function () { + return JSONValidation.validateQuotes(); +}); -task("pr-check-ts", series("lint", "webpack-production")); +task("validate-language-json-schema", function () { + return JSONValidation.validateLanguages(); +}); + +task("validate-other-json-schema", function () { + return JSONValidation.validateOthers(); +}); + +task("pr-check-lint-json", series("lint-json")); +task("pr-check-quote-json", series("validate-quote-json-schema")); +task("pr-check-language-json", series("validate-language-json-schema")); +task("pr-check-other-json", series("validate-other-json-schema")); + +task("pr-check-lint", series("lint")); +task("pr-check-scss", series("sass")); + +task("pr-check-ts", series("webpack-production")); diff --git a/frontend/json-validation.js b/frontend/json-validation.js index 1cc482ef5..4a26f0059 100644 --- a/frontend/json-validation.js +++ b/frontend/json-validation.js @@ -6,7 +6,7 @@ function escapeRegExp(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } -function validate() { +function validateOthers() { return new Promise((resolve, reject) => { //fonts const fontsData = JSON.parse( @@ -90,207 +90,6 @@ function validate() { return reject(new Error(themesValidator.errors)); } - //languages - const languagesData = JSON.parse( - fs.readFileSync("./static/languages/_list.json", { - encoding: "utf8", - flag: "r", - }) - ); - const languagesSchema = { - type: "array", - items: { - type: "string", - }, - }; - const languagesValidator = JSONValidator.validate( - languagesData, - languagesSchema - ); - if (languagesValidator.valid) { - console.log("Languages list JSON schema is \u001b[32mvalid\u001b[0m"); - } else { - console.log("Languages list JSON schema is \u001b[31minvalid\u001b[0m"); - return reject(new Error(languagesValidator.errors)); - } - - //languages group - const languagesGroupData = JSON.parse( - fs.readFileSync("./static/languages/_groups.json", { - encoding: "utf8", - flag: "r", - }) - ); - const languagesGroupSchema = { - type: "array", - items: { - type: "object", - properties: { - name: { type: "string" }, - languages: { - type: "array", - items: { - type: "string", - }, - }, - }, - required: ["name", "languages"], - }, - }; - const languagesGroupValidator = JSONValidator.validate( - languagesGroupData, - languagesGroupSchema - ); - if (languagesGroupValidator.valid) { - console.log("Languages groups JSON schema is \u001b[32mvalid\u001b[0m"); - } else { - console.log("Languages groups JSON schema is \u001b[31minvalid\u001b[0m"); - return reject(new Error(languagesGroupValidator.errors)); - } - - //language files - const languageFileSchema = { - type: "object", - properties: { - name: { type: "string" }, - leftToRight: { type: "boolean" }, - noLazyMode: { type: "boolean" }, - bcp47: { type: "string" }, - words: { - type: "array", - items: { type: "string", minLength: 1 }, - }, - accents: { - type: "array", - items: { - type: "array", - items: { type: "string", minLength: 1 }, - minItems: 2, - maxItems: 2, - }, - }, - }, - required: ["name", "leftToRight", "words"], - }; - let languageFilesAllGood = true; - let languageFilesErrors; - languagesData.forEach((language) => { - const languageFileData = JSON.parse( - fs.readFileSync(`./static/languages/${language}.json`, { - encoding: "utf8", - flag: "r", - }) - ); - languageFileSchema.properties.name.pattern = - "^" + escapeRegExp(language) + "$"; - const languageFileValidator = JSONValidator.validate( - languageFileData, - languageFileSchema - ); - if (!languageFileValidator.valid) { - languageFilesAllGood = false; - languageFilesErrors = languageFileValidator.errors; - } - }); - if (languageFilesAllGood) { - console.log( - `Language word list JSON schemas are \u001b[32mvalid\u001b[0m` - ); - } else { - console.log( - `Language word list JSON schemas are \u001b[31minvalid\u001b[0m` - ); - return reject(new Error(languageFilesErrors)); - } - - //quotes - const quoteSchema = { - type: "object", - properties: { - language: { type: "string" }, - groups: { - type: "array", - items: { - type: "array", - items: { - type: "number", - }, - minItems: 2, - maxItems: 2, - }, - }, - quotes: { - type: "array", - items: { - type: "object", - properties: { - text: { type: "string" }, - source: { type: "string" }, - length: { type: "number" }, - id: { type: "number" }, - }, - required: ["text", "source", "length", "id"], - }, - }, - }, - required: ["language", "groups", "quotes"], - }; - const quoteIdsSchema = { - type: "array", - items: { - type: "number", - }, - uniqueItems: true, - }; - let quoteFilesAllGood = true; - let quoteFilesErrors; - let quoteIdsAllGood = true; - let quoteIdsErrors; - const quotesFiles = fs.readdirSync("./static/quotes/"); - quotesFiles.forEach((quotefilename) => { - quotefilename = quotefilename.split(".")[0]; - const quoteData = JSON.parse( - fs.readFileSync(`./static/quotes/${quotefilename}.json`, { - encoding: "utf8", - flag: "r", - }) - ); - quoteSchema.properties.language.pattern = - "^" + escapeRegExp(quotefilename) + "$"; - const quoteValidator = JSONValidator.validate(quoteData, quoteSchema); - if (!quoteValidator.valid) { - console.log( - `Quote ${quotefilename} JSON schema is \u001b[31minvalid\u001b[0m` - ); - quoteFilesAllGood = false; - quoteFilesErrors = quoteValidator.errors; - } - const quoteIds = quoteData.quotes.map((quote) => quote.id); - const quoteIdsValidator = JSONValidator.validate( - quoteIds, - quoteIdsSchema - ); - if (!quoteIdsValidator.valid) { - console.log( - `Quote ${quotefilename} IDs are \u001b[31mnot unique\u001b[0m` - ); - quoteIdsAllGood = false; - quoteIdsErrors = quoteIdsValidator.errors; - } - }); - if (quoteFilesAllGood) { - console.log(`Quote file JSON schemas are \u001b[32mvalid\u001b[0m`); - } else { - console.log(`Quote file JSON schemas are \u001b[31minvalid\u001b[0m`); - return reject(new Error(quoteFilesErrors)); - } - if (quoteIdsAllGood) { - console.log(`Quote IDs are \u001b[32munique\u001b[0m`); - } else { - console.log(`Quote IDs are \u001b[31mnot unique\u001b[0m`); - return reject(new Error(quoteIdsErrors)); - } - //challenges const challengesSchema = { type: "array", @@ -500,6 +299,224 @@ function validate() { }); } +function validateQuotes() { + return new Promise((resolve, reject) => { + //quotes + const quoteSchema = { + type: "object", + properties: { + language: { type: "string" }, + groups: { + type: "array", + items: { + type: "array", + items: { + type: "number", + }, + minItems: 2, + maxItems: 2, + }, + }, + quotes: { + type: "array", + items: { + type: "object", + properties: { + text: { type: "string" }, + source: { type: "string" }, + length: { type: "number" }, + id: { type: "number" }, + }, + required: ["text", "source", "length", "id"], + }, + }, + }, + required: ["language", "groups", "quotes"], + }; + const quoteIdsSchema = { + type: "array", + items: { + type: "number", + }, + uniqueItems: true, + }; + let quoteFilesAllGood = true; + let quoteFilesErrors; + let quoteIdsAllGood = true; + let quoteIdsErrors; + const quotesFiles = fs.readdirSync("./static/quotes/"); + quotesFiles.forEach((quotefilename) => { + quotefilename = quotefilename.split(".")[0]; + const quoteData = JSON.parse( + fs.readFileSync(`./static/quotes/${quotefilename}.json`, { + encoding: "utf8", + flag: "r", + }) + ); + quoteSchema.properties.language.pattern = + "^" + escapeRegExp(quotefilename) + "$"; + const quoteValidator = JSONValidator.validate(quoteData, quoteSchema); + if (!quoteValidator.valid) { + console.log( + `Quote ${quotefilename} JSON schema is \u001b[31minvalid\u001b[0m` + ); + quoteFilesAllGood = false; + quoteFilesErrors = quoteValidator.errors; + } + const quoteIds = quoteData.quotes.map((quote) => quote.id); + const quoteIdsValidator = JSONValidator.validate( + quoteIds, + quoteIdsSchema + ); + if (!quoteIdsValidator.valid) { + console.log( + `Quote ${quotefilename} IDs are \u001b[31mnot unique\u001b[0m` + ); + quoteIdsAllGood = false; + quoteIdsErrors = quoteIdsValidator.errors; + } + }); + if (quoteFilesAllGood) { + console.log(`Quote file JSON schemas are \u001b[32mvalid\u001b[0m`); + } else { + console.log(`Quote file JSON schemas are \u001b[31minvalid\u001b[0m`); + return reject(new Error(quoteFilesErrors)); + } + if (quoteIdsAllGood) { + console.log(`Quote IDs are \u001b[32munique\u001b[0m`); + } else { + console.log(`Quote IDs are \u001b[31mnot unique\u001b[0m`); + return reject(new Error(quoteIdsErrors)); + } + resolve(); + }); +} + +function validateLanguages() { + return new Promise((resolve, reject) => { + //languages + const languagesData = JSON.parse( + fs.readFileSync("./static/languages/_list.json", { + encoding: "utf8", + flag: "r", + }) + ); + const languagesSchema = { + type: "array", + items: { + type: "string", + }, + }; + const languagesValidator = JSONValidator.validate( + languagesData, + languagesSchema + ); + if (languagesValidator.valid) { + console.log("Languages list JSON schema is \u001b[32mvalid\u001b[0m"); + } else { + console.log("Languages list JSON schema is \u001b[31minvalid\u001b[0m"); + return reject(new Error(languagesValidator.errors)); + } + + //languages group + const languagesGroupData = JSON.parse( + fs.readFileSync("./static/languages/_groups.json", { + encoding: "utf8", + flag: "r", + }) + ); + const languagesGroupSchema = { + type: "array", + items: { + type: "object", + properties: { + name: { type: "string" }, + languages: { + type: "array", + items: { + type: "string", + }, + }, + }, + required: ["name", "languages"], + }, + }; + const languagesGroupValidator = JSONValidator.validate( + languagesGroupData, + languagesGroupSchema + ); + if (languagesGroupValidator.valid) { + console.log("Languages groups JSON schema is \u001b[32mvalid\u001b[0m"); + } else { + console.log("Languages groups JSON schema is \u001b[31minvalid\u001b[0m"); + return reject(new Error(languagesGroupValidator.errors)); + } + + //language files + const languageFileSchema = { + type: "object", + properties: { + name: { type: "string" }, + leftToRight: { type: "boolean" }, + noLazyMode: { type: "boolean" }, + bcp47: { type: "string" }, + words: { + type: "array", + items: { type: "string", minLength: 1 }, + }, + accents: { + type: "array", + items: { + type: "array", + items: { type: "string", minLength: 1 }, + minItems: 2, + maxItems: 2, + }, + }, + }, + required: ["name", "leftToRight", "words"], + }; + let languageFilesAllGood = true; + let languageFilesErrors; + languagesData.forEach((language) => { + const languageFileData = JSON.parse( + fs.readFileSync(`./static/languages/${language}.json`, { + encoding: "utf8", + flag: "r", + }) + ); + languageFileSchema.properties.name.pattern = + "^" + escapeRegExp(language) + "$"; + const languageFileValidator = JSONValidator.validate( + languageFileData, + languageFileSchema + ); + if (!languageFileValidator.valid) { + languageFilesAllGood = false; + languageFilesErrors = languageFileValidator.errors; + } + }); + if (languageFilesAllGood) { + console.log( + `Language word list JSON schemas are \u001b[32mvalid\u001b[0m` + ); + } else { + console.log( + `Language word list JSON schemas are \u001b[31minvalid\u001b[0m` + ); + return reject(new Error(languageFilesErrors)); + } + resolve(); + }); +} + +function validateAll() { + return Promise.all([validateOthers(), validateLanguages(), validateQuotes()]); +} + module.exports = { - validate, + validateAll, + validateOthers, + validateLanguages, + validateQuotes, }; diff --git a/package.json b/package.json index 9ef679494..890e4e283 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,10 @@ "lint": "./node_modules/.bin/eslint './backend/**/*.js' './frontend/src/scripts/**/*.js'", "build:live": "cd ./frontend && npm run build:live", "pretty": "prettier --check './backend/**/*.js' './frontend/src/**/*.{js,scss}' './frontend/static/**/*.{json,html}'", - "pr-check-json": "cd frontend && npx gulp pr-check-json", + "pr-check-lint-json": "cd frontend && npx gulp pr-check-lint-json", + "pr-check-quote-json": "cd frontend && npx gulp pr-check-quote-json", + "pr-check-language-json": "cd frontend && npx gulp pr-check-language-json", + "pr-check-other-json": "cd frontend && npx gulp pr-check-other-json", "pr-check-scss": "cd frontend && npx gulp pr-check-scss", "pr-check-ts": "cd frontend && npx gulp pr-check-ts" },