2022-02-19 01:08:22 +08:00
|
|
|
const fs = require("fs");
|
|
|
|
const V = require("jsonschema").Validator;
|
|
|
|
const JSONValidator = new V();
|
|
|
|
|
|
|
|
function escapeRegExp(str) {
|
|
|
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
|
|
}
|
|
|
|
|
2022-05-21 06:21:37 +08:00
|
|
|
function findDuplicates(words) {
|
|
|
|
const wordFrequencies = {};
|
|
|
|
const duplicates = [];
|
|
|
|
|
|
|
|
words.forEach((word) => {
|
|
|
|
wordFrequencies[word] =
|
|
|
|
word in wordFrequencies ? wordFrequencies[word] + 1 : 1;
|
|
|
|
|
|
|
|
if (wordFrequencies[word] === 2) {
|
|
|
|
duplicates.push(word);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return duplicates;
|
|
|
|
}
|
|
|
|
|
2022-02-19 02:25:33 +08:00
|
|
|
function validateOthers() {
|
2022-02-19 01:08:22 +08:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
//fonts
|
|
|
|
const fontsData = JSON.parse(
|
|
|
|
fs.readFileSync("./static/fonts/_list.json", {
|
|
|
|
encoding: "utf8",
|
|
|
|
flag: "r",
|
|
|
|
})
|
|
|
|
);
|
|
|
|
const fontsSchema = {
|
|
|
|
type: "array",
|
|
|
|
items: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
name: {
|
|
|
|
type: "string",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
required: ["name"],
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const fontsValidator = JSONValidator.validate(fontsData, fontsSchema);
|
|
|
|
if (fontsValidator.valid) {
|
|
|
|
console.log("Fonts JSON schema is \u001b[32mvalid\u001b[0m");
|
|
|
|
} else {
|
|
|
|
console.log("Fonts JSON schema is \u001b[31minvalid\u001b[0m");
|
|
|
|
return reject(new Error(fontsValidator.errors));
|
|
|
|
}
|
|
|
|
|
|
|
|
//funbox
|
|
|
|
const funboxData = JSON.parse(
|
|
|
|
fs.readFileSync("./static/funbox/_list.json", {
|
|
|
|
encoding: "utf8",
|
|
|
|
flag: "r",
|
|
|
|
})
|
|
|
|
);
|
|
|
|
const funboxSchema = {
|
|
|
|
type: "array",
|
|
|
|
items: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
name: { type: "string" },
|
|
|
|
info: { type: "string" },
|
Multiple funboxes (#3578) egorguslyan
* input-controller
* result
* Finishing logic
* Numbers + layoutfluid
* One interface
* Filter results
* tts error on undefined
Extencions like NoScript can partly block scripts on the page.
If speech synthesis is not loaded, notification shows up
without freezing the page
* Improved randomcase
* Prevent dublicates in command line
* Change filter logic
* Prettier
* Convert numbers
* num
* Quote and zen modes
* withWords
* Misc
* Expand funboxes list for pb saving
* Move list to backend
* Move to constants
* Async withWords, checkFunbox tweak
* Prettier
* Forbid nonexistent funboxes
* Disable speech if language is ignored
TtS's init() uses setLanguage()
* canGetPb
* Less circular imports
* Ligatures typo
* Simon says blocks word highlight
* blockWordHighlight backend
* Changed imports
* usesLayout
* JSON schema
* Display notification instead of reseting
* canGetPB
* One getWordHtml
* Dividing properties
* No sync
* blockedModes
* forcedConfig
* Infinitness parameter, list sync
* applyConfig, memory
Remove extra applyConfig somewhere;
Memory in quotes and custom modes
* I lost this code after merging
* Remove arrowKeys
* isFunboxCompatible
* Fix logic
* sync canGetPb
* remove FunboxObjectType
* baloons
* moved cangetpb function to pb utils
* updated the pb check to be easier to understand
* Refactor isFunboxCompatible
* Check modes conflicts
* Strict highlightMode type
* Only one allowed or blocked highlight mode
* More checks
* Undefined only, not false
* Prettier
* Highlight modes
* added intersect helper function
* reworked forced config
- storing allowed modes as an array, not string
- first value will be used if config is outside of the allowed values
- instead of checking if highlight mode is allowed, checking if the whole config is available
- removed the "Finite" forced config and replaced it with "noInfiniteDuration" property
- config event listener now checks every config change, not just highlight mode. this will ensure any future forced configs will work straight out of the box
* ManualRestart in commandline
* fixed funbox commands not correctly showing which funbox is active
* Upd list
* Reduce list
* split funbox into smaller files
moved funbox files into its own folder
* missing none command
* added function to convert camel case to space separated words
* changed config validation to be blocking the change rather than reacting to the change
* reduced code duplication
* allowing sub color flash
* moved keymap key higlighting and flashing into an observable event
* moved tts into a observable event
* passing funbox into config validation funcitons
* replaced getActive with get
* only keeping functions structure in the list, moved the actual function bodies to funbox.ts
done to remove a circular dependency
still need to finish the rest of the funboxes
* removed empty function definitions (typing issues)
* removed unnecessary type
* unnecessary check
* moved mode checking to config validation
* longer notification
* checking funboxes before changing mode
* moved more functions
* fixed incorrect type
* checking funboxes when setting punctuation and numbers
* Rest of funboxes
* fixed funbox commands showing tags text and icon
* checking if funbox can be set with the current config
* better error message
* validating with setting time and words
importing from a new file
* added a function to capitalise the first letter of a string
* using function from a new file
new parameters
* moved test length check to a function in a different file
* moved some funbox validation into its own file
* only showing notifications if the setWordCount returned true
* moved funbox validation to its own file
* setting manual restart when trying to set funbox to nonoe
* moving this validation to before activating the funbox
* returning forcedConfigs along side if current value is allowed
moved infinite check to checkFunboxForcedConfigs
* removed function, replaced by funox validation
* removing duplicates
* throwing if no intersection
* wrong type
* always allowing setting funbox
sometimes it might be possible to update the config
* checking forced configs first, and updating config if possible
only setting funbox to none when couldnt update config
* basic difficulty levels
* xp funbox bonus
* removed console logs
* renamed import, renamed type
* lowercase b for consistency across the codebase
* renamed variable for readability
* renamed for clarity
* converted metadata to object
* changed from beforesubgroup on the command to before list on the subgroup
* using code suggested by bruce
* renamed type
* removed console log
* merch banner fix
* important animation
* updating the icon of "none" funbox command
* removed unnecessary import
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Miodec <jack@monkeytype.com>
2022-11-30 23:57:48 +08:00
|
|
|
canGetPb: { type: "boolean" },
|
|
|
|
alias: { type: "string" },
|
2022-02-19 01:08:22 +08:00
|
|
|
},
|
Multiple funboxes (#3578) egorguslyan
* input-controller
* result
* Finishing logic
* Numbers + layoutfluid
* One interface
* Filter results
* tts error on undefined
Extencions like NoScript can partly block scripts on the page.
If speech synthesis is not loaded, notification shows up
without freezing the page
* Improved randomcase
* Prevent dublicates in command line
* Change filter logic
* Prettier
* Convert numbers
* num
* Quote and zen modes
* withWords
* Misc
* Expand funboxes list for pb saving
* Move list to backend
* Move to constants
* Async withWords, checkFunbox tweak
* Prettier
* Forbid nonexistent funboxes
* Disable speech if language is ignored
TtS's init() uses setLanguage()
* canGetPb
* Less circular imports
* Ligatures typo
* Simon says blocks word highlight
* blockWordHighlight backend
* Changed imports
* usesLayout
* JSON schema
* Display notification instead of reseting
* canGetPB
* One getWordHtml
* Dividing properties
* No sync
* blockedModes
* forcedConfig
* Infinitness parameter, list sync
* applyConfig, memory
Remove extra applyConfig somewhere;
Memory in quotes and custom modes
* I lost this code after merging
* Remove arrowKeys
* isFunboxCompatible
* Fix logic
* sync canGetPb
* remove FunboxObjectType
* baloons
* moved cangetpb function to pb utils
* updated the pb check to be easier to understand
* Refactor isFunboxCompatible
* Check modes conflicts
* Strict highlightMode type
* Only one allowed or blocked highlight mode
* More checks
* Undefined only, not false
* Prettier
* Highlight modes
* added intersect helper function
* reworked forced config
- storing allowed modes as an array, not string
- first value will be used if config is outside of the allowed values
- instead of checking if highlight mode is allowed, checking if the whole config is available
- removed the "Finite" forced config and replaced it with "noInfiniteDuration" property
- config event listener now checks every config change, not just highlight mode. this will ensure any future forced configs will work straight out of the box
* ManualRestart in commandline
* fixed funbox commands not correctly showing which funbox is active
* Upd list
* Reduce list
* split funbox into smaller files
moved funbox files into its own folder
* missing none command
* added function to convert camel case to space separated words
* changed config validation to be blocking the change rather than reacting to the change
* reduced code duplication
* allowing sub color flash
* moved keymap key higlighting and flashing into an observable event
* moved tts into a observable event
* passing funbox into config validation funcitons
* replaced getActive with get
* only keeping functions structure in the list, moved the actual function bodies to funbox.ts
done to remove a circular dependency
still need to finish the rest of the funboxes
* removed empty function definitions (typing issues)
* removed unnecessary type
* unnecessary check
* moved mode checking to config validation
* longer notification
* checking funboxes before changing mode
* moved more functions
* fixed incorrect type
* checking funboxes when setting punctuation and numbers
* Rest of funboxes
* fixed funbox commands showing tags text and icon
* checking if funbox can be set with the current config
* better error message
* validating with setting time and words
importing from a new file
* added a function to capitalise the first letter of a string
* using function from a new file
new parameters
* moved test length check to a function in a different file
* moved some funbox validation into its own file
* only showing notifications if the setWordCount returned true
* moved funbox validation to its own file
* setting manual restart when trying to set funbox to nonoe
* moving this validation to before activating the funbox
* returning forcedConfigs along side if current value is allowed
moved infinite check to checkFunboxForcedConfigs
* removed function, replaced by funox validation
* removing duplicates
* throwing if no intersection
* wrong type
* always allowing setting funbox
sometimes it might be possible to update the config
* checking forced configs first, and updating config if possible
only setting funbox to none when couldnt update config
* basic difficulty levels
* xp funbox bonus
* removed console logs
* renamed import, renamed type
* lowercase b for consistency across the codebase
* renamed variable for readability
* renamed for clarity
* converted metadata to object
* changed from beforesubgroup on the command to before list on the subgroup
* using code suggested by bruce
* renamed type
* removed console log
* merch banner fix
* important animation
* updating the icon of "none" funbox command
* removed unnecessary import
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Miodec <jack@monkeytype.com>
2022-11-30 23:57:48 +08:00
|
|
|
required: ["name", "info", "canGetPb"],
|
2022-02-19 01:08:22 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
const funboxValidator = JSONValidator.validate(funboxData, funboxSchema);
|
|
|
|
if (funboxValidator.valid) {
|
|
|
|
console.log("Funbox JSON schema is \u001b[32mvalid\u001b[0m");
|
|
|
|
} else {
|
|
|
|
console.log("Funbox JSON schema is \u001b[31minvalid\u001b[0m");
|
|
|
|
return reject(new Error(funboxValidator.errors));
|
|
|
|
}
|
|
|
|
|
|
|
|
//themes
|
|
|
|
const themesData = JSON.parse(
|
|
|
|
fs.readFileSync("./static/themes/_list.json", {
|
|
|
|
encoding: "utf8",
|
|
|
|
flag: "r",
|
|
|
|
})
|
|
|
|
);
|
|
|
|
const themesSchema = {
|
|
|
|
type: "array",
|
|
|
|
items: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
name: { type: "string" },
|
|
|
|
bgColor: { type: "string" },
|
|
|
|
mainColor: { type: "string" },
|
|
|
|
},
|
|
|
|
required: ["name", "bgColor", "mainColor"],
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const themesValidator = JSONValidator.validate(themesData, themesSchema);
|
|
|
|
if (themesValidator.valid) {
|
|
|
|
console.log("Themes JSON schema is \u001b[32mvalid\u001b[0m");
|
|
|
|
} else {
|
|
|
|
console.log("Themes JSON schema is \u001b[31minvalid\u001b[0m");
|
|
|
|
return reject(new Error(themesValidator.errors));
|
|
|
|
}
|
|
|
|
|
|
|
|
//challenges
|
|
|
|
const challengesSchema = {
|
|
|
|
type: "array",
|
|
|
|
items: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
name: { type: "string" },
|
|
|
|
display: { type: "string" },
|
|
|
|
autoRole: { type: "boolean" },
|
|
|
|
type: { type: "string" },
|
|
|
|
message: { type: "string" },
|
|
|
|
parameters: {
|
|
|
|
type: "array",
|
|
|
|
},
|
|
|
|
requirements: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
wpm: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
min: { type: "number" },
|
|
|
|
max: { type: "number" },
|
|
|
|
exact: { type: "number" },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
time: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
min: { type: "number" },
|
|
|
|
max: { type: "number" },
|
|
|
|
exact: { type: "number" },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
acc: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
min: { type: "number" },
|
|
|
|
max: { type: "number" },
|
|
|
|
exact: { type: "number" },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
raw: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
min: { type: "number" },
|
|
|
|
max: { type: "number" },
|
|
|
|
exact: { type: "number" },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
con: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
min: { type: "number" },
|
|
|
|
max: { type: "number" },
|
|
|
|
exact: { type: "number" },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
config: {
|
|
|
|
type: "object",
|
|
|
|
},
|
2022-02-21 20:26:55 +08:00
|
|
|
funbox: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
exact: { type: "string" },
|
|
|
|
},
|
|
|
|
},
|
2022-02-19 01:08:22 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
required: ["name", "display", "type", "parameters"],
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const challengesData = JSON.parse(
|
|
|
|
fs.readFileSync("./static/challenges/_list.json", {
|
|
|
|
encoding: "utf8",
|
|
|
|
flag: "r",
|
|
|
|
})
|
|
|
|
);
|
|
|
|
const challengesValidator = JSONValidator.validate(
|
|
|
|
challengesData,
|
|
|
|
challengesSchema
|
|
|
|
);
|
|
|
|
if (challengesValidator.valid) {
|
|
|
|
console.log("Challenges list JSON schema is \u001b[32mvalid\u001b[0m");
|
|
|
|
} else {
|
|
|
|
console.log("Challenges list JSON schema is \u001b[31minvalid\u001b[0m");
|
|
|
|
return reject(new Error(challengesValidator.errors));
|
|
|
|
}
|
|
|
|
|
|
|
|
//layouts
|
|
|
|
const layoutsSchema = {
|
|
|
|
ansi: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
keymapShowTopRow: { type: "boolean" },
|
|
|
|
type: { type: "string", pattern: "^ansi$" },
|
|
|
|
keys: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
row1: {
|
|
|
|
type: "array",
|
|
|
|
items: { type: "string", minLength: 1, maxLength: 2 },
|
|
|
|
minItems: 13,
|
|
|
|
maxItems: 13,
|
|
|
|
},
|
|
|
|
row2: {
|
|
|
|
type: "array",
|
|
|
|
items: { type: "string", minLength: 1, maxLength: 2 },
|
|
|
|
minItems: 13,
|
|
|
|
maxItems: 13,
|
|
|
|
},
|
|
|
|
row3: {
|
|
|
|
type: "array",
|
|
|
|
items: { type: "string", minLength: 1, maxLength: 2 },
|
|
|
|
minItems: 11,
|
|
|
|
maxItems: 11,
|
|
|
|
},
|
|
|
|
row4: {
|
|
|
|
type: "array",
|
|
|
|
items: { type: "string", minLength: 1, maxLength: 2 },
|
|
|
|
minItems: 10,
|
|
|
|
maxItems: 10,
|
|
|
|
},
|
|
|
|
row5: {
|
|
|
|
type: "array",
|
|
|
|
items: { type: "string", minLength: 1, maxLength: 1 },
|
|
|
|
minItems: 1,
|
|
|
|
maxItems: 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
required: ["row1", "row2", "row3", "row4", "row5"],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
required: ["keymapShowTopRow", "type", "keys"],
|
|
|
|
},
|
|
|
|
iso: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
keymapShowTopRow: { type: "boolean" },
|
|
|
|
type: { type: "string", pattern: "^iso$" },
|
|
|
|
keys: {
|
|
|
|
type: "object",
|
|
|
|
properties: {
|
|
|
|
row1: {
|
|
|
|
type: "array",
|
|
|
|
items: { type: "string", minLength: 1, maxLength: 2 },
|
|
|
|
minItems: 13,
|
|
|
|
maxItems: 13,
|
|
|
|
},
|
|
|
|
row2: {
|
|
|
|
type: "array",
|
|
|
|
items: { type: "string", minLength: 1, maxLength: 2 },
|
|
|
|
minItems: 12,
|
|
|
|
maxItems: 12,
|
|
|
|
},
|
|
|
|
row3: {
|
|
|
|
type: "array",
|
|
|
|
items: { type: "string", minLength: 1, maxLength: 2 },
|
|
|
|
minItems: 12,
|
|
|
|
maxItems: 12,
|
|
|
|
},
|
|
|
|
row4: {
|
|
|
|
type: "array",
|
|
|
|
items: { type: "string", minLength: 1, maxLength: 2 },
|
|
|
|
minItems: 11,
|
|
|
|
maxItems: 11,
|
|
|
|
},
|
|
|
|
row5: {
|
|
|
|
type: "array",
|
|
|
|
items: { type: "string", minLength: 1, maxLength: 1 },
|
|
|
|
minItems: 1,
|
|
|
|
maxItems: 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
required: ["row1", "row2", "row3", "row4", "row5"],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
required: ["keymapShowTopRow", "type", "keys"],
|
|
|
|
},
|
|
|
|
};
|
|
|
|
const layoutsData = JSON.parse(
|
|
|
|
fs.readFileSync("./static/layouts/_list.json", {
|
|
|
|
encoding: "utf8",
|
|
|
|
flag: "r",
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let layoutsAllGood = true;
|
|
|
|
let layoutsErrors;
|
|
|
|
Object.keys(layoutsData).forEach((layoutName) => {
|
|
|
|
const layoutData = layoutsData[layoutName];
|
|
|
|
|
2022-08-22 20:22:16 +08:00
|
|
|
if (!layoutsSchema[layoutData.type]) {
|
|
|
|
const msg = `Layout ${layoutName} has an invalid type: ${layoutData.type}`;
|
|
|
|
console.log(msg);
|
2022-02-19 01:08:22 +08:00
|
|
|
layoutsAllGood = false;
|
2022-08-22 20:22:16 +08:00
|
|
|
layoutsErrors = [msg];
|
|
|
|
} else {
|
|
|
|
const layoutsValidator = JSONValidator.validate(
|
|
|
|
layoutData,
|
|
|
|
layoutsSchema[layoutData.type]
|
|
|
|
);
|
|
|
|
if (!layoutsValidator.valid) {
|
|
|
|
console.log(
|
|
|
|
`Layout ${layoutName} JSON schema is \u001b[31minvalid\u001b[0m`
|
|
|
|
);
|
|
|
|
layoutsAllGood = false;
|
|
|
|
layoutsErrors = layoutsValidator.errors;
|
|
|
|
}
|
2022-02-19 01:08:22 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
if (layoutsAllGood) {
|
|
|
|
console.log(`Layout JSON schemas are \u001b[32mvalid\u001b[0m`);
|
|
|
|
} else {
|
|
|
|
console.log(`Layout JSON schemas are \u001b[31minvalid\u001b[0m`);
|
|
|
|
return reject(new Error(layoutsErrors));
|
|
|
|
}
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-02-19 02:25:33 +08:00
|
|
|
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;
|
2022-02-20 21:45:00 +08:00
|
|
|
let quoteLengthsAllGood = true;
|
|
|
|
let quoteLengthErrors = [];
|
2022-02-19 02:25:33 +08:00
|
|
|
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;
|
|
|
|
}
|
2022-02-20 21:45:00 +08:00
|
|
|
const incorrectQuoteLength = quoteData.quotes
|
|
|
|
.filter((quote) => quote.text.length !== quote.length)
|
|
|
|
.map((quote) => quote.id);
|
|
|
|
if (incorrectQuoteLength.length !== 0) {
|
|
|
|
console.log(
|
|
|
|
`Quote ${quotefilename} ID(s) ${incorrectQuoteLength.join(
|
|
|
|
","
|
|
|
|
)} length fields are \u001b[31mincorrect\u001b[0m`
|
|
|
|
);
|
|
|
|
quoteFilesAllGood = false;
|
|
|
|
incorrectQuoteLength.map((id) => {
|
|
|
|
quoteLengthErrors.push(
|
|
|
|
`${quotefilename} ${id} length field is incorrect`
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
2022-02-19 02:25:33 +08:00
|
|
|
});
|
|
|
|
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));
|
|
|
|
}
|
2022-02-20 21:45:00 +08:00
|
|
|
if (quoteLengthsAllGood) {
|
|
|
|
console.log(`Quote length fields are \u001b[32mcorrect\u001b[0m`);
|
|
|
|
} else {
|
|
|
|
console.log(`Quote length fields are \u001b[31mincorrect\u001b[0m`);
|
|
|
|
return reject(new Error(quoteLengthErrors));
|
|
|
|
}
|
2022-02-19 02:25:33 +08:00
|
|
|
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;
|
2022-05-21 06:21:37 +08:00
|
|
|
let languageWordListsAllGood = true;
|
2022-02-19 02:25:33 +08:00
|
|
|
let languageFilesErrors;
|
2022-05-22 06:50:40 +08:00
|
|
|
const duplicatePercentageThreshold = 0.0001;
|
2022-05-21 06:36:43 +08:00
|
|
|
let langsWithDuplicates = 0;
|
2022-02-19 02:25:33 +08:00
|
|
|
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;
|
2022-05-21 06:21:37 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const duplicates = findDuplicates(languageFileData.words);
|
2022-05-21 07:02:37 +08:00
|
|
|
const duplicatePercentage =
|
|
|
|
(duplicates.length / languageFileData.words.length) * 100;
|
2022-05-21 06:21:37 +08:00
|
|
|
if (duplicatePercentage >= duplicatePercentageThreshold) {
|
2022-05-21 06:36:43 +08:00
|
|
|
langsWithDuplicates++;
|
2022-05-21 06:21:37 +08:00
|
|
|
languageWordListsAllGood = false;
|
2022-05-21 07:02:37 +08:00
|
|
|
languageFilesErrors = `Language '${languageFileData.name}' contains ${
|
|
|
|
duplicates.length
|
|
|
|
} (${Math.round(duplicatePercentage)}%) duplicates:`;
|
2022-05-21 06:21:37 +08:00
|
|
|
console.log(languageFilesErrors);
|
|
|
|
console.log(duplicates);
|
2022-02-19 02:25:33 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
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));
|
|
|
|
}
|
2022-05-21 06:21:37 +08:00
|
|
|
|
|
|
|
if (languageWordListsAllGood) {
|
|
|
|
console.log(
|
|
|
|
`Language word lists duplicate check is \u001b[32mvalid\u001b[0m`
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
console.log(
|
2022-05-21 06:36:43 +08:00
|
|
|
`Language word lists duplicate check is \u001b[31minvalid\u001b[0m (${langsWithDuplicates} languages contain duplicates)`
|
2022-05-21 06:21:37 +08:00
|
|
|
);
|
2022-05-21 06:59:48 +08:00
|
|
|
return reject(new Error(languageFilesErrors));
|
2022-05-21 06:21:37 +08:00
|
|
|
}
|
|
|
|
|
2022-02-19 02:25:33 +08:00
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function validateAll() {
|
|
|
|
return Promise.all([validateOthers(), validateLanguages(), validateQuotes()]);
|
|
|
|
}
|
|
|
|
|
2022-02-19 01:08:22 +08:00
|
|
|
module.exports = {
|
2022-02-19 02:25:33 +08:00
|
|
|
validateAll,
|
|
|
|
validateOthers,
|
|
|
|
validateLanguages,
|
|
|
|
validateQuotes,
|
2022-02-19 01:08:22 +08:00
|
|
|
};
|