Finally, Add tests and linting!

This commit is contained in:
zkldi
2022-01-31 19:23:18 +00:00
parent 6c4598342d
commit 02efe6f95a
28 changed files with 188298 additions and 46976 deletions
+3 -1
View File
@@ -6,4 +6,6 @@ scripts/single-time
.vs
scripts/rerunners/bpi-output.json
scripts/rerunners/bpi-output.json
failed-tests.log
File diff suppressed because it is too large Load Diff
+9
View File
@@ -3332,6 +3332,7 @@
"game": "bms",
"inactive": false,
"playtype": "7K",
"searchTerms": [],
"title": "Insane",
"type": "charts"
},
@@ -3343,6 +3344,7 @@
"game": "bms",
"inactive": false,
"playtype": "7K",
"searchTerms": [],
"title": "Insane 2",
"type": "charts"
},
@@ -3354,6 +3356,7 @@
"game": "bms",
"inactive": false,
"playtype": "7K",
"searchTerms": [],
"title": "LN",
"type": "charts"
},
@@ -3365,6 +3368,7 @@
"game": "bms",
"inactive": false,
"playtype": "7K",
"searchTerms": [],
"title": "Normal",
"type": "charts"
},
@@ -3376,6 +3380,7 @@
"game": "bms",
"inactive": false,
"playtype": "7K",
"searchTerms": [],
"title": "Normal 2",
"type": "charts"
},
@@ -3387,6 +3392,7 @@
"game": "bms",
"inactive": false,
"playtype": "7K",
"searchTerms": [],
"title": "Overjoy",
"type": "charts"
},
@@ -3398,6 +3404,7 @@
"game": "bms",
"inactive": false,
"playtype": "7K",
"searchTerms": [],
"title": "Satellite",
"type": "charts"
},
@@ -3409,6 +3416,7 @@
"game": "bms",
"inactive": false,
"playtype": "7K",
"searchTerms": [],
"title": "Scratch 3rd",
"type": "charts"
},
@@ -3914,6 +3922,7 @@
"game": "bms",
"inactive": false,
"playtype": "7K",
"searchTerms": [],
"title": "Stella",
"type": "charts"
},
+2
View File
@@ -0,0 +1,2 @@
node_modules
js
+92
View File
@@ -0,0 +1,92 @@
{
"root": true,
"env": {
"node": true,
"es6": true
},
"plugins": [
"@typescript-eslint",
"import"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2020
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"plugin:import/typescript",
"plugin:import/warnings",
"plugin:import/errors"
],
"rules": {
"prettier/prettier": [
"error",
{
"printWidth": 100,
"tabWidth": 4,
"semi": true,
"singleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"endOfLine": "auto",
"useTabs": true
}
],
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "logger"
}
],
"require-await": "warn",
"no-await-in-loop": "warn",
"no-unused-labels": "error",
"prefer-const": "error",
"eqeqeq": "error",
"no-eval": "error",
"no-var": "error",
"no-new-object": "error",
"quote-props": [
"error",
"as-needed"
],
"no-array-constructor": "error",
"prefer-template": "error",
"prefer-promise-reject-errors": "error",
"no-useless-escape": "error",
"no-loop-func": "error",
"prefer-rest-params": "error",
"default-param-last": "error",
"no-new-func": "error",
"arrow-body-style": "error",
"no-useless-constructor": "error",
"no-duplicate-imports": "error",
"dot-notation": "error",
"space-before-blocks": "error",
"no-prototype-builtins": "off",
"prefer-arrow-callback": "error",
"curly": "error",
"no-template-curly-in-string": "error",
"no-implied-eval": "error",
"yoda": "error",
"no-labels": "error",
"no-useless-concat": "error",
"radix": "error",
"no-octal-escape": "error",
"no-param-reassign": "error",
"no-proto": "error",
"no-return-await": "error",
"no-throw-literal": "error",
"no-unmodified-loop-condition": "error",
"no-unused-expressions": "error",
"no-useless-call": "error",
"no-useless-return": "error",
"require-unicode-regexp": "error"
}
}
@@ -66,20 +66,16 @@ function DeterministicCollectionSort() {
if (collection.startsWith("charts-")) {
content.sort(ChartSort);
}
else if (collection.startsWith("songs-")) {
} else if (collection.startsWith("songs-")) {
content.sort((a, b) => a.id - b.id);
}
else if (collection.startsWith("folders")) {
} else if (collection.startsWith("folders")) {
content.sort(FolderSort);
}
else if (collection.startsWith("tables")) {
} else if (collection.startsWith("tables")) {
content.sort(TableSort);
} else if (collection.startsWith("bms-course-lookup.json")) {
content.sort(BMSCourseSort);
}
content = content.map(SortObjectKeys);
fs.writeFileSync(collPath, JSON.stringify(content, null, "\t"));
@@ -87,7 +83,7 @@ function DeterministicCollectionSort() {
}
function SortObjectKeys(object) {
let newObject = {};
const newObject = {};
for (const key of Object.keys(object).sort()) {
let v = object[key];
@@ -106,4 +102,4 @@ if (require.main === module) {
DeterministicCollectionSort();
}
module.exports = DeterministicCollectionSort
module.exports = DeterministicCollectionSort;
+3 -5
View File
@@ -31,16 +31,14 @@ const collections = [];
if (options.all) {
collections.push("folders", "tables");
collections.push(...StaticConfig.allSupportedGames.map(e => `songs-${e}`));
collections.push(...StaticConfig.allSupportedGames.map(e => `charts-${e}`));
collections.push(...StaticConfig.allSupportedGames.map((e) => `songs-${e}`));
collections.push(...StaticConfig.allSupportedGames.map((e) => `charts-${e}`));
} else if (program.args[0]) {
collections.push(program.args[0]);
}
// Add the songs-{game} and charts-{game} collections.
(async () => {
logger.info(`Exporting ${collections.length} collections.`);
@@ -67,4 +65,4 @@ async function ExportCollection(collection) {
fs.writeFileSync(pt, JSON.stringify(data, null, options.useTabs ? "\t" : undefined));
logger.info(`Wrote to ${pt}.`);
}
}
+9 -3
View File
@@ -1,12 +1,18 @@
function FindChartWithPTDFVersion(collection, songID, playtype, difficulty, version) {
return collection.find(chart => chart.songID === songID && chart.playtype === playtype && chart.difficulty === difficulty && chart.versions.includes(version));
return collection.find(
(chart) =>
chart.songID === songID &&
chart.playtype === playtype &&
chart.difficulty === difficulty &&
chart.versions.includes(version)
);
}
function FindSongWithTitle(collection, title) {
return collection.find(e => e.title === title || e.altTitles.includes(title));
return collection.find((e) => e.title === title || e.altTitles.includes(title));
}
module.exports = {
FindSongWithTitle,
FindChartWithPTDFVersion,
}
};
+2 -3
View File
@@ -14,8 +14,7 @@ const logger = require("./logger");
const program = new Command();
program
.option("-d, --db <Database Name>")
program.option("-d, --db <Database Name>");
program.parse(process.argv);
const options = program.opts();
@@ -46,4 +45,4 @@ for (const file of fs.readdirSync(base)) {
} catch (err) {
logger.error(`Failed to import ${file}.`, { command, err });
}
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
const { CreateLogger } = require("mei-logger");
module.exports = CreateLogger("tachi-database-seed-scripts");
module.exports = CreateLogger("tachi-database-seed-scripts");
+2 -3
View File
@@ -1,5 +1,4 @@
const { MutateCollection, EfficientInPlaceDeepmerge } = require("./util");
const deepmerge = require("deepmerge");
function DoesMatchCriteria(element, mutation) {
for (const [key, value] of Object.entries(mutation.match)) {
@@ -26,5 +25,5 @@ function ApplyMutations(name, mutations) {
}
module.exports = {
ApplyMutations
}
ApplyMutations,
};
+16 -2
View File
@@ -4,7 +4,7 @@
"description": "Scripts for dealing with tachi-database-seeds.",
"main": "",
"scripts": {
"test": "tap"
"test": "./run-tests.sh"
},
"repository": {
"type": "git",
@@ -23,6 +23,8 @@
},
"homepage": "https://github.com/TeamNewGuys/tachi-database-seeds#readme",
"dependencies": {
"@types/node": "^17.0.13",
"chalk": "4",
"commander": "^8.2.0",
"csv-parse": "^5.0.3",
"deepmerge": "^4.2.2",
@@ -30,7 +32,19 @@
"glob": "^7.2.0",
"mei-logger": "^1.0.1",
"monk": "^7.3.4",
"tachi-common": "0.2.43",
"prudence": "^0.9.7",
"tachi-common": "0.5.12",
"ts-node": "^10.4.0",
"xml2js": "^0.4.23"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2",
"eslint": "^8.8.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-prettier": "^4.0.0",
"prettier": "^2.5.1",
"typescript": "^4.5.5"
}
}
+1204 -281
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -15,5 +15,5 @@ if (require.main === module) {
}
module.exports = {
RemoveUnderscoreID
}
RemoveUnderscoreID,
};
+22 -20
View File
@@ -79,8 +79,8 @@ for (const info of data.entries.music) {
hash,
notes,
playtype,
difficulty
})
difficulty,
});
}
}
@@ -95,7 +95,11 @@ MutateCollection("charts-iidx.json", (charts) => {
}
match = true;
break;
} else if (Array.isArray(chart.data.inGameID) ? chart.data.inGameID.includes(data.id) : chart.data.inGameID === data.id) {
} else if (
Array.isArray(chart.data.inGameID)
? chart.data.inGameID.includes(data.id)
: chart.data.inGameID === data.id
) {
existingReference = chart;
}
}
@@ -107,28 +111,26 @@ MutateCollection("charts-iidx.json", (charts) => {
}
charts.push({
"chartID": CreateChartID(),
"data": {
chartID: CreateChartID(),
data: {
"2dxtraSet": options.name,
"arcChartID": null,
"hashSHA256": data.hash,
"inGameID": data.id,
"notecount": data.notes
arcChartID: null,
hashSHA256: data.hash,
inGameID: data.id,
notecount: data.notes,
},
"difficulty": `${options.name} ${data.difficulty}`,
"isPrimary": true,
"level": "?",
"levelNum": 0,
difficulty: `${options.name} ${data.difficulty}`,
isPrimary: true,
level: "?",
levelNum: 0,
playtype: data.playtype,
"rgcID": null,
"songID": existingReference.songID,
"tierlistInfo": {},
"versions": [
options.version
]
rgcID: null,
songID: existingReference.songID,
tierlistInfo: {},
versions: [options.version],
});
}
}
return charts;
});
});
@@ -12,11 +12,11 @@ module.exports = function AddLevelVersionFolders(name, game, playtypes, version,
type: "charts",
data: {
level,
versions: version
versions: version,
},
inactive: false,
searchTerms: [],
}
};
for (const playtype of playtypes) {
const folderID = CreateFolderID(folder.data, folder.game, playtype);
@@ -42,19 +42,16 @@ module.exports = function AddLevelVersionFolders(name, game, playtypes, version,
tableID: `${game}-${playtype}-${version}-levels`,
name: `${FormatGame(game, playtype)} (${name})`,
description: `Levels for ${FormatGame(game, playtype)} in ${name}.`,
folders: ptFolders[playtype].map(e => e.folderID),
folders: ptFolders[playtype].map((e) => e.folderID),
game,
playtype,
inactive: false,
});
}
return tables;
});
}
};
// usage:
@@ -63,4 +60,4 @@ module.exports = function AddLevelVersionFolders(name, game, playtypes, version,
// levels.push(i.toString())
// }
//
// addLevelVersionFolders("CastHour", "iidx", ["SP", "DP"], "29", levels);
// addLevelVersionFolders("CastHour", "iidx", ["SP", "DP"], "29", levels);
@@ -14,7 +14,12 @@ program
program.parse(process.argv);
const options = program.opts();
if (!options.course || !["7K", "14K"].includes(options.playtype) || !options.set || Number.isNaN(Number(options.index))) {
if (
!options.course ||
!["7K", "14K"].includes(options.playtype) ||
!options.set ||
Number.isNaN(Number(options.index))
) {
throw new Error(`Missing parameters.`);
}
@@ -23,7 +28,7 @@ const parser = new XMLParser();
const data = parser.parse(fs.readFileSync(options.course));
MutateCollection("bms-course-lookup.json", (courses) => {
let existingCourses = new Set(courses.map(e => e.md5sums));
const existingCourses = new Set(courses.map((e) => e.md5sums));
let i = 0;
for (const d of data.courselist.course) {
@@ -37,11 +42,11 @@ MutateCollection("bms-course-lookup.json", (courses) => {
md5sums,
set: options.set,
playtype: options.playtype,
value: Number(options.index) + i
value: Number(options.index) + i,
});
i++;
}
return courses;
});
});
@@ -7,12 +7,8 @@ const { ReadCollection } = require("../../util");
const songs = ReadCollection("songs-iidx.json");
async function parseKr(
file,
mode,
catVals
) {
let charts = ReadCollection("charts-iidx.json");
async function parseKr(file, mode, catVals) {
const charts = ReadCollection("charts-iidx.json");
const krdata = JSON.parse(fs.readFileSync(path.join(__dirname, file), "utf-8"));
const mutations = [];
@@ -73,10 +69,10 @@ async function parseKr(
text: cv.text,
value: cv.value,
individualDifference: cv.idv,
}
}
}
})
},
},
},
});
console.log(`Updated ${chart.chartID} to value ${cv.value}.`);
}
@@ -1,13 +1,18 @@
const { MutateCollection } = require("../util");
module.exports = (idSet) => {
return MutateCollection("charts-iidx.json", (charts) => {
module.exports = (idSet) =>
MutateCollection("charts-iidx.json", (charts) => {
for (const chart of charts) {
if (idSet.includes(chart.data.inGameID) && !chart.versions.includes("inf") && chart.data["2dxtraSet"] === null && chart.difficulty !== "LEGGENDARIA" && chart.difficulty !== "BEGINNER") {
if (
idSet.includes(chart.data.inGameID) &&
!chart.versions.includes("inf") &&
chart.data["2dxtraSet"] === null &&
chart.difficulty !== "LEGGENDARIA" &&
chart.difficulty !== "BEGINNER"
) {
chart.versions.push("inf");
}
}
return charts;
});
}
File diff suppressed because one or more lines are too long
@@ -5,8 +5,7 @@ const { CreateChartID } = require("../util");
const path = require("path");
const program = new Command();
program
.option("-f, --file <mxb-json>")
program.option("-f, --file <mxb-json>");
program.parse(process.argv);
const options = program.opts();
@@ -17,15 +16,15 @@ if (!options.file) {
let data = JSON.parse(fs.readFileSync(options.file));
data = data.filter(e => e != null);
data = data.filter((e) => e !== null);
const songs = [];
const charts = [];
let songID = 1;
for (const song of data) {
const isUpper = song.charts.some(k => k !== null && k.filename.startsWith("exx"));
const isUra = song.charts.some(k => k !== null && k.filename.endsWith("_ura2"));
const isUpper = song.charts.some((k) => k !== null && k.filename.startsWith("exx"));
const isUra = song.charts.some((k) => k !== null && k.filename.endsWith("_ura2"));
let title = isUpper ? `${song.title} (UPPER)` : song.title;
title = isUra ? `${title} (URA)` : title;
@@ -38,9 +37,9 @@ for (const song of data) {
altTitles: [],
data: {
displayVersion: null,
genre: song.genre
genre: song.genre,
},
})
});
for (const [index, chart] of Object.entries(song.charts)) {
if (chart === null) {
@@ -55,7 +54,10 @@ for (const song of data) {
songID,
chartID: CreateChartID(),
// Turns HYPER into Hyper, but keeps EX the same.
difficulty: chart.difficulty === "EX" ? "EX" : chart.difficulty[0] + chart.difficulty.slice(1).toLowerCase(),
difficulty:
chart.difficulty === "EX"
? "EX"
: chart.difficulty[0] + chart.difficulty.slice(1).toLowerCase(),
playtype: "9B",
level: chart.level.toString(),
levelNum: chart.level,
@@ -66,7 +68,7 @@ for (const song of data) {
inGameID: index,
},
tierlistInfo: {},
versions: ["peace"]
versions: ["peace"],
});
}
@@ -74,4 +76,7 @@ for (const song of data) {
}
// fs.writeFileSync(path.join(__dirname, "../../collections/charts-popn.json"), JSON.stringify(charts, null, "\t"));
fs.writeFileSync(path.join(__dirname, "../../collections/songs-popn.json"), JSON.stringify(songs, null, "\t"));
fs.writeFileSync(
path.join(__dirname, "../../collections/songs-popn.json"),
JSON.stringify(songs, null, "\t")
);
@@ -10,8 +10,7 @@ const logger = require("../logger");
const program = new Command();
// https://github.com/shimmand/waccaSupportTools/blob/main/analyzePlayData/chartsTable.csv
program
.option("-f, --file <wacca.csv>")
program.option("-f, --file <wacca.csv>");
program.parse(process.argv);
const options = program.opts();
@@ -51,7 +50,6 @@ const STARTS = {
reverse: Date.parse("2021-8-10"),
};
(async () => {
const datum = await fetch("https://wacca.marv.jp/music/search.php", {
method: "POST",
@@ -59,7 +57,7 @@ const STARTS = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
},
body: "cat=all",
}).then(r => r.json());
}).then((r) => r.json());
const songs = [];
const charts = [];
@@ -75,7 +73,7 @@ const STARTS = {
prettiedTitle = "13 Donkeys";
}
let time = Date.parse(data.release_date);
const time = Date.parse(data.release_date);
let ver;
if (time < STARTS.s) {
@@ -97,7 +95,13 @@ const STARTS = {
const record = dataMap.get(prettiedTitle);
if (!record) {
logger.warn(`Can't find record with title ${prettiedTitle}. Dumping potentially similar titles.\n${[...dataMap.keys()].filter(e => e.startsWith(prettiedTitle[0])).join("\n")}`);
logger.warn(
`Can't find record with title ${prettiedTitle}. Dumping potentially similar titles.\n${[
...dataMap.keys(),
]
.filter((e) => e.startsWith(prettiedTitle[0]))
.join("\n")}`
);
continue;
}
@@ -111,15 +115,15 @@ const STARTS = {
titleJP: data.title.ruby,
artistJP: data.artist.ruby,
genre: data.category,
displayVersion: ver
}
displayVersion: ver,
},
});
for (let i = 0; i < 4; i++) {
let diff = record[1 + i * 3];
let [diffName, level] = diff.split(" ");
let levelNum = Number(record[2 + i * 3]);
let isNew = record[3 + i * 3];
const diff = record[1 + i * 3];
const [diffName, level] = diff.split(" ");
const levelNum = Number(record[2 + i * 3]);
const isNew = record[3 + i * 3];
if (!levelNum) {
continue;
@@ -135,17 +139,22 @@ const STARTS = {
difficulty: diffName,
playtype: "Single",
data: {
isHot: isNew === "true"
isHot: isNew === "true",
},
tierlistInfo: {},
versions: ["reverse"]
versions: ["reverse"],
});
}
songID++;
}
fs.writeFileSync(path.resolve(__dirname, "../../collections/charts-wacca.json"), JSON.stringify(charts, null, "\t"));
fs.writeFileSync(path.resolve(__dirname, "../../collections/songs-wacca.json"), JSON.stringify(songs, null, "\t"));
})();
fs.writeFileSync(
path.resolve(__dirname, "../../collections/charts-wacca.json"),
JSON.stringify(charts, null, "\t")
);
fs.writeFileSync(
path.resolve(__dirname, "../../collections/songs-wacca.json"),
JSON.stringify(songs, null, "\t")
);
})();
@@ -56,12 +56,7 @@ async function UpdatePoyashiData() {
continue;
}
const tachiChart = (await FindChartWithPTDFVersion(
tachiSong.id,
playtype,
diff,
"29"
));
const tachiChart = await FindChartWithPTDFVersion(tachiSong.id, playtype, diff, "29");
if (!tachiChart) {
logger.warn(
@@ -89,9 +84,7 @@ async function UpdatePoyashiData() {
tachiChart.data.worldRecord = Number(d.wr);
}
MutateCollection("charts-iidx.json", () => {
return chartData;
});
MutateCollection("charts-iidx.json", () => chartData);
logger.info(`Done.`);
}
@@ -111,7 +104,12 @@ function FindSongOnTitle(title) {
function FindChartWithPTDFVersion(songID, playtype, diff, version) {
for (const chart of chartData) {
if (chart.songID === songID && chart.playtype === playtype && chart.difficulty === diff && chart.versions.includes(version)) {
if (
chart.songID === songID &&
chart.playtype === playtype &&
chart.difficulty === diff &&
chart.versions.includes(version)
) {
return chart;
}
}
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# Output stderr and stdout to the terminal, but only save stderr to a file
# Isn't bash wonderful?
# I can't figure out how to make it write to failed-tests.log instead of appending
# so, whatever.
# look. I don't like bash much.
# I'd rather spend 0% of my time here.
rm failed-tests.log -f
ts-node test/collections.test.ts 2> >(tee -a failed-tests.log >&2)
@@ -0,0 +1,98 @@
import chalk from "chalk";
import { writeFileSync } from "fs";
import { ReadCollection } from "../util";
import { SCHEMAS } from "./schemas";
function FormatPrError(err, foreword = "Error") {
const receivedText =
typeof err.userVal === "object" && err.userVal !== null
? ""
: ` | Received ${err.userVal} [${err.userVal === null ? "null" : typeof err.userVal}]`;
return `${foreword}: ${err.keychain} | ${err.message}${receivedText}.`;
}
const songFormat = (s) => `${s.artist} - ${s.title} (${s.id})`;
const chartFormat = (s) => `${s.id} - ${s.playtype} ${s.difficulty} (${s.chartID})`;
const FormatFunctions: Record<keyof typeof SCHEMAS, (d) => string> = {
"bms-course-lookup": (d) => d.title,
folders: (d) => d.title,
tables: (d) => d.name,
"songs-bms": songFormat,
"songs-chunithm": songFormat,
// "songs-ddr": songFormat,
// "songs-gitadora": songFormat,
"songs-iidx": songFormat,
"songs-jubeat": songFormat,
// "songs-maimai": songFormat,
"songs-museca": songFormat,
"songs-pms": songFormat,
"songs-popn": songFormat,
"songs-sdvx": songFormat,
"songs-usc": songFormat,
"songs-wacca": songFormat,
"charts-bms": chartFormat,
"charts-chunithm": chartFormat,
// "charts-ddr": chartFormat,
// "charts-gitadora": chartFormat,
"charts-iidx": chartFormat,
"charts-jubeat": chartFormat,
// "charts-maimai": chartFormat,
"charts-museca": chartFormat,
"charts-pms": chartFormat,
"charts-popn": chartFormat,
"charts-sdvx": chartFormat,
"charts-usc": chartFormat,
"charts-wacca": chartFormat,
};
let exitCode = 0;
const suites = [];
for (const [collection, validator] of Object.entries(SCHEMAS)) {
console.log(`[VALIDATING] ${collection}`);
let success = 0;
let fails = 0;
const collectionName = `${collection}.json`;
const formatFn = FormatFunctions[collection];
const data = ReadCollection(collectionName, true);
for (const d of data) {
const pretty = formatFn(d);
try {
validator(d);
success++;
} catch (err) {
console.error(
chalk.red(`[ERR] ${collectionName} | ${pretty} | ${FormatPrError(err)}.`)
);
fails++;
}
}
const report = `GOOD: ${success}, BAD: ${fails}(${Math.min(
(success * 100) / fails,
100
).toFixed(2)}%)`;
if (fails > 0) {
console.error(chalk.red(`[FAILED] ${collection}. ${report}.`));
exitCode++;
} else {
console.log(chalk.green(`[GOOD] ${collection}. ${report}.`));
}
suites.push({ name: collection, report, good: fails === 0 });
}
console.log(`=== Suite Overview ===`);
for (const suite of suites) {
console.log(chalk[suite.good ? "green" : "red"](`${suite.name}: ${suite.report}`));
}
process.exit(exitCode);
+346
View File
@@ -0,0 +1,346 @@
import p, { PrudenceSchema } from "prudence";
import { Game, Playtypes, GetGamePTConfig, GetGameConfig } from "tachi-common";
import { allSupportedGames } from "tachi-common/js/config/static-config";
function prSchemaify(schema: PrudenceSchema) {
return (s: unknown): true => {
const err = p(s, schema);
if (err) {
throw err;
}
return true;
};
}
function IsValidPlaytype(game: Game, str: string): str is Playtypes[Game] {
return GetGameConfig(game).validPlaytypes.includes(str as Playtypes[Game]);
}
function IsValidGame(str: string): str is Game {
return allSupportedGames.includes(str as Game);
}
const getPlaytype = (game: Game, self: unknown): Playtypes[Game] => {
if (!self || typeof self !== "object") {
throw new Error("Expected an object.");
}
const gameConfig = GetGameConfig(game);
const s = self as Record<string, unknown>;
const playtype = s.playtype as string;
if (!IsValidPlaytype(game, playtype)) {
throw new Error(`Expected any of ${gameConfig.validPlaytypes.join(", ")}`);
}
return playtype;
};
const games = allSupportedGames;
const isValidPlaytype = (self: unknown, parent: Record<string, unknown>) => {
if (!parent.game || typeof parent.game !== "string" || !IsValidGame(parent.game)) {
throw new Error(`Invalid Schema, need game to base IsValidPlaytype off of.`);
}
if (typeof self !== "string") {
return "Expected a string.";
}
if (!IsValidPlaytype(parent.game, self)) {
return `Expected a valid playtype for ${parent.game}`;
}
return true;
};
const PR_SongDocument = (data: PrudenceSchema): PrudenceSchema => ({
id: p.isPositiveInteger,
title: "string",
artist: "string",
searchTerms: ["string"],
altTitles: ["string"],
data,
});
const PR_ChartDocument = (
game: Game,
playtype: Playtypes[Game],
data: PrudenceSchema
): PrudenceSchema => {
const gptConfig = GetGamePTConfig(game, playtype);
return {
songID: p.isPositiveInteger,
chartID: "string",
rgcID: "?string",
level: "string",
levelNum: "number",
isPrimary: "boolean",
difficulty: p.isIn(gptConfig.difficulties),
playtype: p.is(playtype),
data,
tierlistInfo: Object.fromEntries(
gptConfig.tierlists.map((t) => [
t,
p.optional({ text: "string", value: "number", individualDifference: "*boolean" }),
])
),
versions: [p.isIn(gptConfig.supportedVersions)],
};
};
export const SCHEMAS = {
"bms-course-lookup": prSchemaify({
title: "string",
// Must be comprised of 4 md5 hashes, which are 32 chars long.
md5sums: (self) => {
if (typeof self !== "string") {
return "Expected a string";
}
if (self.length !== 32 * 4) {
return "Expected 32 * 4 characters (4 md5 hashes long).";
}
if (!self.match(/^[a-z0-9]*$/u)) {
return "Expected all chars to be in the range of a-z0-9.";
}
return true;
},
set: p.isIn("genocideDan", "stslDan"),
playtype: p.isIn("7K", "14K"),
value: p.isInteger,
}),
folders: prSchemaify({
title: "string",
game: p.isIn(games),
playtype: isValidPlaytype,
folderID: "string",
inactive: "boolean",
searchTerms: ["string"],
type: p.isIn("songs", "charts", "static"),
data: (self, parent) => {
if (parent.type === "songs") {
return true; //temp. should be a song.
} else if (parent.type === "charts") {
return true; //temp. should be a chart.
}
return (
(Array.isArray(self) && self.every((r) => typeof r === "string")) ||
"Expected an array of only strings."
);
},
}),
tables: prSchemaify({
tableID: "string",
game: p.isIn(games),
playtype: isValidPlaytype,
title: "string",
default: "boolean",
description: "string",
folders: ["string"],
inactive: "boolean",
}),
"songs-bms": prSchemaify(
PR_SongDocument({
genre: "?string",
subtitle: "?string",
subartist: "?string",
tableString: "?string",
})
),
"songs-chunithm": prSchemaify(
PR_SongDocument({
genre: "string",
displayVersion: "string",
})
),
// "songs-ddr": prSchemaify(
// PR_SongDocument({
// displayVersion: "string",
// })
// ),
"songs-sdvx": prSchemaify(
PR_SongDocument({
displayVersion: "string",
})
),
"songs-usc": prSchemaify(PR_SongDocument({})),
// "songs-maimai": prSchemaify(
// PR_SongDocument({
// titleJP: "string",
// artistJP: "string",
// displayVersion: "string",
// })
// ),
"songs-museca": prSchemaify(
PR_SongDocument({
titleJP: "string",
artistJP: "string",
displayVersion: "string",
})
),
// "songs-gitadora": prSchemaify(
// PR_SongDocument({
// isHot: "boolean",
// })
// ),
"songs-iidx": prSchemaify(
PR_SongDocument({
genre: "string",
displayVersion: "string",
})
),
"songs-wacca": prSchemaify(
PR_SongDocument({
genre: "string",
displayVersion: "?string",
titleJP: "string",
artistJP: "string",
})
),
"songs-pms": prSchemaify(
PR_SongDocument({
genre: "?string",
subtitle: "?string",
subartist: "?string",
tableString: "?string",
})
),
"songs-popn": prSchemaify(
PR_SongDocument({
genre: "string",
displayVersion: "?string",
})
),
"songs-jubeat": prSchemaify(
PR_SongDocument({
displayVersion: "string",
})
),
"charts-iidx": (self) => {
const playtype = getPlaytype("iidx", self);
return prSchemaify(
PR_ChartDocument("iidx", playtype, {
notecount: p.isPositiveNonZeroInteger,
inGameID: p.or(p.isPositiveNonZeroInteger, [p.isPositiveNonZeroInteger]),
arcChartID: "?string",
hashSHA256: "?string",
"2dxtraSet": "?string",
kaidenAverage: "?number",
worldRecord: "?number",
bpiCoefficient: "?number",
})
)(self);
},
"charts-bms": (self) => {
const playtype = getPlaytype("bms", self);
return prSchemaify(
PR_ChartDocument("bms", playtype, {
notecount: p.isPositiveNonZeroInteger,
hashSHA256: "?string",
hashMD5: "?string",
tableFolders: [
{
table: "string",
level: "string",
},
],
})
)(self);
},
"charts-chunithm": prSchemaify(
PR_ChartDocument("chunithm", "Single", {
inGameID: p.isPositiveInteger,
})
),
"charts-jubeat": prSchemaify(
PR_ChartDocument("jubeat", "Single", {
inGameID: p.isPositiveInteger,
isHardMode: "boolean",
})
),
// "charts-gitadora": (self) => {
// const playtype = getPlaytype("gitadora", self);
// return prSchemaify(
// PR_ChartDocument("gitadora", playtype, {
// inGameID: p.isPositiveInteger,
// })
// )(self);
// },
// "charts-ddr": (self) => {
// const playtype = getPlaytype("ddr", self);
// return prSchemaify(
// PR_ChartDocument("ddr", playtype, {
// inGameID: "string",
// songHash: "string",
// })
// )(self);
// },
// "charts-maimai": prSchemaify(
// PR_ChartDocument("maimai", "Single", {
// maxPercent: p.gt(0),
// inGameID: p.isPositiveInteger,
// inGameStrID: "string",
// })
// ),
"charts-museca": prSchemaify(
PR_ChartDocument("museca", "Single", {
inGameID: p.isPositiveInteger,
})
),
"charts-sdvx": prSchemaify(
PR_ChartDocument("sdvx", "Single", {
inGameID: p.isPositiveInteger,
arcChartID: "?string",
})
),
"charts-usc": (self) => {
const playtype = getPlaytype("usc", self);
return prSchemaify(
PR_ChartDocument("usc", playtype, {
hashSHA1: p.or("string", ["string"]),
isOfficial: "boolean",
})
)(self);
},
"charts-wacca": prSchemaify(
PR_ChartDocument("wacca", "Single", {
isHot: "boolean",
})
),
"charts-pms": (self) => {
const playtype = getPlaytype("pms", self);
return prSchemaify(
PR_ChartDocument("pms", playtype, {
notecount: p.isPositiveNonZeroInteger,
hashSHA256: "?string",
hashMD5: "?string",
tableFolders: [
{
table: "string",
level: "string",
},
],
})
)(self);
},
"charts-popn": prSchemaify(
PR_ChartDocument("popn", "9B", {
hashSHA256: "?string",
inGameID: p.isPositiveInteger,
})
),
};
+27
View File
@@ -0,0 +1,27 @@
// NOT A REAL TSCONFIG FILE
// THIS IS USED FOR VSCODE SO IT INTEGRATES NICELY
// IF YOU USE THIS TO BUILD, IT WILL ALSO BUILD THE TEST FILES!!
// SO, DONT DO THAT, USE TSCONFIG.BUILD.JSON FOR THE BUILD.
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"lib": [
"ES2019"
],
"declaration": true,
"strict": false,
"allowJs": true,
"esModuleInterop": true,
"sourceMap": true,
"outDir": "./js",
"baseUrl": "src",
"typeRoots": [
"@types",
"node_modules/@types"
],
},
"exclude": [
"node_modules",
]
}
+22 -20
View File
@@ -7,7 +7,10 @@ const glob = require("glob");
function IterateCollections(cb) {
for (const collection of fs.readdirSync(COLLECTIONS_DIR)) {
const data = cb(JSON.parse(fs.readFileSync(path.join(COLLECTIONS_DIR, collection))), collection);
const data = cb(
JSON.parse(fs.readFileSync(path.join(COLLECTIONS_DIR, collection))),
collection
);
fs.writeFileSync(path.join(COLLECTIONS_DIR, collection), JSON.stringify(data, null, "\t"));
}
@@ -17,9 +20,13 @@ function IterateCollections(cb) {
const COLLECTIONS_DIR = path.join(__dirname, "../collections");
function ReadCollection(name) {
function ReadCollection(name, throwIfNotFound = false) {
const p = path.join(COLLECTIONS_DIR, name);
if (!fs.existsSync(p)) {
if (throwIfNotFound) {
throw new Error(`No collection ${name} exists.`);
}
fs.writeFileSync(p, JSON.stringify([]));
return [];
}
@@ -28,7 +35,6 @@ function ReadCollection(name) {
}
function MutateCollection(name, cb) {
const data = cb(ReadCollection(name));
fs.writeFileSync(path.join(COLLECTIONS_DIR, name), JSON.stringify(data, null, "\t"));
@@ -40,11 +46,7 @@ function CreateChartID() {
return crypto.randomBytes(20).toString("hex");
}
function CreateFolderID(
query,
game,
playtype
) {
function CreateFolderID(query, game, playtype) {
return `F${fjsh.hash(Object.assign({ game, playtype }, query), "SHA256")}`;
}
@@ -55,7 +57,7 @@ function EfficientInPlaceDeepmerge(ref, apply) {
if (typeof apply[key] === "object" && apply[key]) {
EfficientInPlaceDeepmerge(ref[key], apply[key]);
} else {
ref[key] = apply[key]
ref[key] = apply[key];
}
}
}
@@ -71,16 +73,16 @@ function AddSupportForBMSTable(header, game, playtype, tableName, tableID, table
game,
searchTerms: [],
type: "charts",
"data": {
data: {
"data¬tableFolders": {
"~elemMatch": {
"level": level.toString(),
"table": symbol
}
}
level: level.toString(),
table: symbol,
},
},
},
inactive: false,
}
};
const folderID = CreateFolderID(f);
@@ -96,16 +98,16 @@ function AddSupportForBMSTable(header, game, playtype, tableName, tableID, table
MutateCollection("tables.json", (t) => {
t.push({
folders: folders.map(e => e.folderID),
folders: folders.map((e) => e.folderID),
game,
playtype,
inactive: false,
description: tableDescription,
title: tableName,
tableID: tableID
tableID: tableID,
});
return t;
})
});
}
module.exports = {
@@ -115,5 +117,5 @@ module.exports = {
CreateFolderID,
ReadCollection,
EfficientInPlaceDeepmerge,
AddSupportForBMSTable
}
AddSupportForBMSTable,
};