diff --git a/package.json b/package.json index 5868dcf2f..54ab67c25 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/zengm-games/zengm.git" }, "scripts": { - "build": "node --experimental-strip-types --disable-warning=ExperimentalWarning tools/build.ts", + "build": "node --experimental-strip-types --disable-warning=ExperimentalWarning tools/build/cli.ts", "deploy": "node --run lint && node --run test && node --experimental-strip-types --disable-warning=ExperimentalWarning tools/deploy.ts", "deploy-all": "node --run lint && node --run test && node --experimental-strip-types --disable-warning=ExperimentalWarning tools/deploy-all.ts", "dev": "node --experimental-strip-types --disable-warning=ExperimentalWarning tools/watch/cli.ts", diff --git a/tools/build.ts b/tools/build.ts deleted file mode 100644 index 4335476e4..000000000 --- a/tools/build.ts +++ /dev/null @@ -1,3 +0,0 @@ -import build from "./lib/build.ts"; - -await build(); diff --git a/tools/deploy-all.ts b/tools/deploy-all.ts index 6083646d1..92bae5a39 100644 --- a/tools/deploy-all.ts +++ b/tools/deploy-all.ts @@ -1,4 +1,4 @@ -import deploy from "./lib/deploy.ts"; +import { deploy } from "./lib/deploy.ts"; const sports = ["basketball", "football", "baseball", "hockey"] as const; diff --git a/tools/deploy.ts b/tools/deploy.ts index f9c1f12b5..92bd71c3e 100644 --- a/tools/deploy.ts +++ b/tools/deploy.ts @@ -1,3 +1,3 @@ -import deploy from "./lib/deploy.ts"; +import { deploy } from "./lib/deploy.ts"; await deploy(); diff --git a/tools/lib/build.ts b/tools/lib/build.ts deleted file mode 100644 index 4aa184a57..000000000 --- a/tools/lib/build.ts +++ /dev/null @@ -1,35 +0,0 @@ -import fs from "node:fs/promises"; -import { buildCss } from "./buildCss.ts"; -import { buildJs } from "./buildJs.ts"; -import { buildSw } from "./buildSw.ts"; -import { copyFiles } from "./copyFiles.ts"; -import { generateJsonSchema } from "./generateJsonSchema.ts"; -import { getSport } from "./getSport.ts"; -import { minifyIndexHtml } from "./minifyIndexHtml.ts"; -import { reset } from "./reset.ts"; - -export default async () => { - const sport = getSport(); - - console.log(`Building ${sport}...`); - - await reset(); - await copyFiles(); - - const jsonSchema = generateJsonSchema(sport); - await fs.mkdir("build/files", { recursive: true }); - await fs.writeFile( - "build/files/league-schema.json", - JSON.stringify(jsonSchema, null, 2), - ); - - console.log("Bundling JavaScript files..."); - await buildJs(); - - console.log("Processing CSS/HTML files..."); - await buildCss(); - await minifyIndexHtml(); - - console.log("Generating sw.js..."); - await buildSw(); -}; diff --git a/tools/lib/buildCss.ts b/tools/lib/buildCss.ts deleted file mode 100644 index 85c674917..000000000 --- a/tools/lib/buildCss.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Buffer } from "node:buffer"; -import fs from "node:fs"; -import browserslist from "browserslist"; -import * as lightningCSS from "lightningcss"; -import { PurgeCSS } from "purgecss"; -import * as sass from "sass"; -import { fileHash } from "./fileHash.ts"; -import { replace } from "./replace.ts"; - -export const buildCss = async (watch: boolean = false) => { - const filenames = ["light", "dark"]; - const rawCSS = filenames.map((filename) => { - const sassFilePath = `public/css/${filename}.scss`; - const sassResult = sass.renderSync({ - file: sassFilePath, - }); - return sassResult.css.toString(); - }); - - const purgeCSSResults = watch - ? [] - : await new PurgeCSS().purge({ - content: ["build/gen/*.js"], - css: rawCSS.map((raw) => ({ raw })), - safelist: { - standard: [/^qc-cmp2-persistent-link$/], - greedy: [ - // react-bootstrap stuff - /^modal/, - /^navbar/, - /^popover/, - /^tooltip/, - /^bs-tooltip/, - - // For align="end" in react-bootstrap - /^dropdown-menu-end$/, - - // flag-icons - /^fi$/, - /^fi-/, - - /^dark-select/, - /^bar-graph/, - /^watch-active/, - /^dashboard-top-link-other/, - ], - }, - }); - - for (let i = 0; i < filenames.length; i++) { - const filename = filenames[i]; - - let output; - if (!watch) { - // https://zengm.com/blog/2022/07/investigating-a-tricky-performance-bug/ - const DANGER_CSS = ".input-group.has-validation"; - if (!rawCSS[i].includes(DANGER_CSS)) { - throw new Error( - `rawCSS no longer contains ${DANGER_CSS} - same problem might exist with another name?`, - ); - } - - const purgeCSSResult = purgeCSSResults[i].css; - - const { code } = lightningCSS.transform({ - filename: `${filename}.css`, - code: Buffer.from(purgeCSSResult), - minify: true, - sourceMap: false, - targets: lightningCSS.browserslistToTargets( - browserslist("Chrome >= 75, Firefox >= 78, Safari >= 12.1"), - ), - }); - - output = code.toString(); - - if (output.includes(DANGER_CSS)) { - throw new Error(`CSS output contains ${DANGER_CSS}`); - } - } else { - output = rawCSS[i]; - } - - let outFilename; - if (watch) { - outFilename = `build/gen/${filename}.css`; - } else { - const hash = fileHash(output); - outFilename = `build/gen/${filename}-${hash}.css`; - - replace({ - paths: ["build/index.html"], - replaces: [ - { - searchValue: `CSS_HASH_${filename.toUpperCase()}`, - replaceValue: hash, - }, - ], - }); - } - - fs.writeFileSync(outFilename, output); - } -}; diff --git a/tools/lib/buildJSWorker.ts b/tools/lib/buildJSWorker.ts deleted file mode 100644 index af03058a9..000000000 --- a/tools/lib/buildJSWorker.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { rollup, type ModuleFormat } from "rollup"; -import rollupConfig from "./rollupConfig.ts"; -import { parentPort, workerData } from "node:worker_threads"; - -const LODASH_BLACKLIST = [ - /^lodash$/, - /^lodash-es/, - - // lodash/debounce and lodash/memoize are used by visx - /^lodash\/(?!debounce|memoize)/, -]; - -const BLACKLIST = { - ui: [...LODASH_BLACKLIST, /\/worker/], - worker: [...LODASH_BLACKLIST, /\/ui/, /^react/], -}; - -const buildFile = async ( - name: "ui" | "worker", - legacy: boolean, - rev: string, -) => { - const bundle = await rollup({ - ...rollupConfig("production", { - blacklistOptions: BLACKLIST[name], - statsFilename: `stats-${name}${legacy ? "-legacy" : ""}.html`, - legacy, - }), - input: { - [name]: `src/${name}/index.${name === "ui" ? "tsx" : "ts"}`, - }, - preserveEntrySignatures: false, - }); - - let format: ModuleFormat; - if (legacy) { - // ES modules don't work in workers in all the browsers currently supported - // Chrome 80, Firefox 114, Safari 15.5/16.4 - format = name === "ui" ? "es" : "iife"; - } else { - format = "es"; - } - - await bundle.write({ - compact: true, - format, - indent: false, - sourcemap: true, - entryFileNames: `[name]-${legacy ? "legacy-" : ""}${rev}.js`, - chunkFileNames: `chunk-${legacy ? "legacy-" : ""}[hash].js`, - dir: "build/gen", - }); - - parentPort!.postMessage("done"); -}; - -const { legacy, name, rev } = workerData; - -await buildFile(name, legacy, rev); diff --git a/tools/lib/buildJs.ts b/tools/lib/buildJs.ts deleted file mode 100644 index 92a2b3a68..000000000 --- a/tools/lib/buildJs.ts +++ /dev/null @@ -1,85 +0,0 @@ -import fs from "node:fs"; -import { Worker } from "node:worker_threads"; -import { fileHash } from "./fileHash.ts"; -import { generateVersionNumber } from "./generateVersionNumber.ts"; -import { replace } from "./replace.ts"; -import { setTimestamps } from "./setTimestamps.ts"; - -const versionNumber = generateVersionNumber(); -console.log(versionNumber); - -export const buildJs = async () => { - const promises = []; - for (const name of ["ui", "worker"]) { - for (const legacy of [false, true]) { - promises.push( - new Promise((resolve) => { - const worker = new Worker( - new URL("./buildJSWorker.ts", import.meta.url), - { - workerData: { - legacy, - name, - rev: versionNumber, - }, - }, - ); - - worker.on("message", () => { - resolve(); - }); - }), - ); - } - } - await Promise.all(promises); - - // Hack because otherwise I'm somehow left with no newline before the souce map URL, which confuses Bugsnag - const replacePaths = fs - .readdirSync("build/gen") - .filter((filename) => filename.endsWith(".js")) - .map((filename) => `build/gen/${filename}`); - replace({ - paths: replacePaths, - replaces: [ - { - searchValue: ";//# sourceMappingURL", - replaceValue: ";\n//# sourceMappingURL", - }, - ], - }); - - setTimestamps(versionNumber); - - const jsonFiles = [ - "names", - "names-female", - "real-player-data", - "real-player-stats", - ]; - const replaces = []; - for (const filename of jsonFiles) { - const filePath = `build/gen/${filename}.json`; - if (fs.existsSync(filePath)) { - const string = fs.readFileSync(filePath, "utf8"); - const compressed = JSON.stringify(JSON.parse(string)); - - const hash = fileHash(compressed); - const newFilename = filePath.replace(".json", `-${hash}.json`); - fs.rmSync(filePath); - fs.writeFileSync(newFilename, compressed); - - replaces.push({ - searchValue: `/gen/${filename}.json`, - replaceValue: `/gen/${filename}-${hash}.json`, - }); - } - } - replace({ - paths: [ - `build/gen/worker-legacy-${versionNumber}.js`, - `build/gen/worker-${versionNumber}.js`, - ], - replaces, - }); -}; diff --git a/tools/lib/buildSw.ts b/tools/lib/buildSw.ts deleted file mode 100644 index 79715bda5..000000000 --- a/tools/lib/buildSw.ts +++ /dev/null @@ -1,97 +0,0 @@ -import fs from "node:fs"; -import { babel } from "@rollup/plugin-babel"; -import replace from "@rollup/plugin-replace"; -import resolve from "@rollup/plugin-node-resolve"; -import terser from "@rollup/plugin-terser"; -import { rollup } from "rollup"; -import workboxBuild from "workbox-build"; -import { replace as replace2 } from "./replace.ts"; - -const getVersionNumber = () => { - const files = fs.readdirSync("build/gen"); - for (const file of files) { - if (file.endsWith(".js")) { - const versionNumber = file.split("-")[1].replace(".js", ""); - return versionNumber; - } - } - - throw new Error("versionNumber not found"); -}; - -// NOTE: This should be run *AFTER* all assets are built -const injectManifest = async () => { - const { count, size, warnings } = await workboxBuild.injectManifest({ - swSrc: "public/sw.js", - swDest: "build/sw.js", - globDirectory: "build", - globPatterns: [ - "**/*.{js,css,html}", - "fonts/*", - "gen/*.json", - "img/logos-primary/*.svg", - "img/logos-secondary/*.svg", - "ico/icon.svg", - "ico/logo.png", - "ico/logo-gold.png", - ], - dontCacheBustURLsMatching: /gen\/.*\.(js|css)/, - globIgnores: [ - "gen/*-legacy-*.js", - "gen/real-player-*.json", - "upgrade-50/*", - ], - - // Changing default is only needed for unminified versions from watch-js - maximumFileSizeToCacheInBytes: 100 * 1024 * 1024, - }); - - warnings.forEach(console.warn); - console.log(`${count} files will be precached, totaling ${size} bytes.`); -}; - -const bundle = async () => { - const bundle = await rollup({ - input: "build/sw.js", - plugins: [ - replace({ - preventAssignment: true, - values: { - "process.env.NODE_ENV": JSON.stringify("production"), - }, - }), - babel({ - babelHelpers: "bundled", - }), - resolve(), - terser({ - format: { - comments: false, - }, - }), - ], - }); - - await bundle.write({ - file: `build/sw.js`, - format: "iife", - indent: false, - sourcemap: true, - }); -}; - -export const buildSw = async () => { - await injectManifest(); - await bundle(); - - const versionNumber = getVersionNumber(); - replace2({ - paths: ["build/sw.js"], - replaces: [ - { - searchValue: "REV_GOES_HERE", - replaceValue: versionNumber, - }, - ], - }); -}; diff --git a/tools/lib/copyFiles.ts b/tools/lib/copyFiles.ts deleted file mode 100644 index ee1dfd549..000000000 --- a/tools/lib/copyFiles.ts +++ /dev/null @@ -1,130 +0,0 @@ -import fs from "node:fs"; -import fsp from "node:fs/promises"; -import { bySport } from "./bySport.ts"; -import { replace } from "./replace.ts"; - -const setSport = () => { - replace({ - paths: ["build/index.html"], - replaces: [ - { - searchValue: "GAME_NAME", - replaceValue: bySport({ - baseball: "ZenGM Baseball", - basketball: "Basketball GM", - football: "Football GM", - hockey: "ZenGM Hockey", - }), - }, - { - searchValue: "SPORT", - replaceValue: bySport({ - baseball: "baseball", - basketball: "basketball", - football: "football", - hockey: "hockey", - }), - }, - { - searchValue: "GOOGLE_ANALYTICS_COOKIE_DOMAIN", - replaceValue: bySport({ - basketball: "basketball-gm.com", - football: "football-gm.com", - default: "zengm.com", - }), - }, - { - searchValue: "WEBSITE_ROOT", - replaceValue: bySport({ - baseball: "zengm.com/baseball", - basketball: "basketball-gm.com", - football: "football-gm.com", - hockey: "zengm.com/hockey", - }), - }, - { - searchValue: "PLAY_SUBDOMAIN", - replaceValue: bySport({ - baseball: "baseball.zengm.com", - basketball: "play.basketball-gm.com", - football: "play.football-gm.com", - hockey: "hockey.zengm.com", - }), - }, - { - searchValue: "BETA_SUBDOMAIN", - replaceValue: bySport({ - baseball: "beta.baseball.zengm.com", - basketball: "beta.basketball-gm.com", - football: "beta.football-gm.com", - hockey: "beta.hockey.zengm.com", - }), - }, - ], - }); -}; - -export const copyFiles = async (watch: boolean = false) => { - const foldersToIgnore = [ - "baseball", - "basketball", - "css", - "football", - "hockey", - ]; - - await fsp.cp("public", "build", { - filter: (filename) => { - // Loop through folders to ignore. - for (const folder of foldersToIgnore) { - if (filename.startsWith(`public/${folder}`)) { - return false; - } - } - - // Remove service worker, so I don't have to deal with it being wonky in dev - if (watch && filename === "public/sw.js") { - return false; - } - - return true; - }, - recursive: true, - }); - - let sport = process.env.SPORT; - if (typeof sport !== "string") { - sport = "basketball"; - } - - await fsp.cp(`public/${sport}`, "build", { - filter: (filename) => !filename.includes(".gitignore"), - recursive: true, - }); - - // Remove the empty folders created by the "filter" function. - for (const folder of foldersToIgnore) { - await fsp.rm(`build/${folder}`, { recursive: true, force: true }); - } - - const realPlayerFilenames = ["real-player-data", "real-player-stats"]; - for (const filename of realPlayerFilenames) { - const sourcePath = `data/${filename}.${sport}.json`; - if (fs.existsSync(sourcePath)) { - await fsp.copyFile(sourcePath, `build/gen/${filename}.json`); - } - } - - await fsp.copyFile("data/names.json", "build/gen/names.json"); - await fsp.copyFile("data/names-female.json", "build/gen/names-female.json"); - - await fsp.cp("node_modules/flag-icons/flags/4x3", "build/img/flags", { - recursive: true, - }); - const flagHtaccess = ` - Header set Cache-Control "public,max-age=31536000" -`; - await fsp.writeFile("build/img/flags/.htaccess", flagHtaccess); - - setSport(); -}; diff --git a/tools/lib/deploy.ts b/tools/lib/deploy.ts index 07f353154..e034f5927 100644 --- a/tools/lib/deploy.ts +++ b/tools/lib/deploy.ts @@ -1,7 +1,7 @@ import { spawn } from "node:child_process"; import Cloudflare from "cloudflare"; import { readFile } from "node:fs/promises"; -import build from "./build.ts"; +import { build } from "../build/build.ts"; import { bySport } from "./bySport.ts"; import { getSport } from "./getSport.ts"; @@ -39,7 +39,7 @@ const mySpawn = (command: string, args: string[]) => { }); }; -const deploy = async () => { +export const deploy = async () => { const cloudflareConfig = JSON.parse( await readFile( new URL("../../../../.config/cloudflare.json", import.meta.url), @@ -130,5 +130,3 @@ const deploy = async () => { console.log("\nDone!"); }; - -export default deploy; diff --git a/tools/lib/fileHash.ts b/tools/lib/fileHash.ts deleted file mode 100644 index 2b5354089..000000000 --- a/tools/lib/fileHash.ts +++ /dev/null @@ -1,6 +0,0 @@ -import crypto from "node:crypto"; - -export const fileHash = (contents: string) => { - // https://github.com/sindresorhus/rev-hash - return crypto.createHash("md5").update(contents).digest("hex").slice(0, 10); -}; diff --git a/tools/lib/generateJsonSchema.ts b/tools/lib/generateJsonSchema.ts deleted file mode 100644 index 427eba5bd..000000000 --- a/tools/lib/generateJsonSchema.ts +++ /dev/null @@ -1,2722 +0,0 @@ -import { bySport } from "./bySport.ts"; - -const genRatings = (sport: string) => { - const properties: any = { - fuzz: { - type: "number", - }, - injuryIndex: { - type: "integer", - }, - ovr: { - type: "number", - minimum: 0, - maximum: 100, - }, - pos: { - type: "string", - }, - pot: { - type: "number", - minimum: 0, - maximum: 100, - }, - season: { - type: "integer", - }, - skills: { - type: "array", - items: { - $ref: "#/definitions/playerSkill", - }, - }, - }; - - const ratings = bySport({ - baseball: [ - "hgt", - "spd", - "hpw", - "con", - "eye", - "gnd", - "fly", - "thr", - "cat", - "ppw", - "ctl", - "mov", - "endu", - ], - basketball: [ - "dnk", - "drb", - "endu", - "fg", - "ft", - "hgt", - "ins", - "jmp", - "pss", - "reb", - "spd", - "stre", - "tp", - ], - football: [ - "hgt", - "stre", - "spd", - "endu", - "thv", - "thp", - "tha", - "bsc", - "elu", - "rtr", - "hnd", - "rbk", - "pbk", - "pcv", - "tck", - "prs", - "rns", - "kpw", - "kac", - "ppw", - "pac", - ], - hockey: [ - "hgt", - "stre", - "spd", - "endu", - "pss", - "wst", - "sst", - "stk", - "oiq", - "chk", - "blk", - "fcf", - "diq", - "glk", - ], - }); - - // These should be validated for their numeric value, but not required because different versions of the schema might not have them - const oldRatings = sport === "basketball" ? ["blk", "stl"] : []; - const newRatings = sport === "basketball" ? ["diq", "oiq"] : []; - - for (const rating of [...ratings, ...oldRatings, ...newRatings]) { - properties[rating] = { - type: "number", - minimum: 0, - maximum: 100, - }; - } - - return { - items: { - type: "object", - properties, - required: ratings, - }, - }; -}; - -const wrap = (child: any) => ({ - anyOf: [ - { - type: "array", - minItems: 1, - items: { - type: "object", - properties: { - start: { - anyOf: [ - { - type: "null", - }, - { - type: "integer", - }, - ], - }, - value: child, - }, - required: ["start", "value"], - }, - }, - child, - ], -}); - -export const generateJsonSchema = (sport: string) => { - if (sport === "test") { - return { - $schema: "http://json-schema.org/draft-07/schema#", - $id: "https://play.basketball-gm.com/files/league-schema.json", - title: "Test League File Schema", - description: "Test only!", - definitions: {}, - type: "object", - required: ["version"], - properties: {}, - }; - } - - const depth = bySport({ - baseball: { - depth: { - type: "object", - properties: { - L: { - type: "array", - items: { - type: "integer", - }, - }, - LP: { - type: "array", - items: { - type: "integer", - }, - }, - D: { - type: "array", - items: { - type: "integer", - }, - }, - DP: { - type: "array", - items: { - type: "integer", - }, - }, - P: { - type: "array", - items: { - type: "integer", - }, - }, - }, - required: ["L", "LP", "D", "DP", "P"], - }, - }, - basketball: {}, - football: { - depth: { - type: "object", - properties: { - QB: { - type: "array", - items: { - type: "integer", - }, - }, - RB: { - type: "array", - items: { - type: "integer", - }, - }, - WR: { - type: "array", - items: { - type: "integer", - }, - }, - TE: { - type: "array", - items: { - type: "integer", - }, - }, - OL: { - type: "array", - items: { - type: "integer", - }, - }, - DL: { - type: "array", - items: { - type: "integer", - }, - }, - LB: { - type: "array", - items: { - type: "integer", - }, - }, - CB: { - type: "array", - items: { - type: "integer", - }, - }, - S: { - type: "array", - items: { - type: "integer", - }, - }, - K: { - type: "array", - items: { - type: "integer", - }, - }, - P: { - type: "array", - items: { - type: "integer", - }, - }, - KR: { - type: "array", - items: { - type: "integer", - }, - }, - PR: { - type: "array", - items: { - type: "integer", - }, - }, - }, - required: [ - "QB", - "RB", - "WR", - "TE", - "OL", - "DL", - "LB", - "CB", - "S", - "K", - "P", - "KR", - "PR", - ], - }, - }, - hockey: { - depth: { - type: "object", - properties: { - F: { - type: "array", - items: { - type: "integer", - }, - }, - D: { - type: "array", - items: { - type: "integer", - }, - }, - G: { - type: "array", - items: { - type: "integer", - }, - }, - }, - required: ["F", "D", "G"], - }, - }, - }); - - const websitePlay = bySport({ - baseball: "baseball.zengm.com", - basketball: "play.basketball-gm.com", - football: "play.football-gm.com", - hockey: "hockey.zengm.com", - }); - - return { - $schema: "http://json-schema.org/draft-07/schema#", - $id: `https://${websitePlay}/files/league-schema.json`, - title: `${bySport({ - baseball: "ZenGM Baseball", - basketball: "Basketball GM", - football: "Footbal lGM", - hockey: "ZenGM Hockey", - })} League File Schema`, - description: `For use at https://${websitePlay}/`, - - definitions: { - budgetItem: { - type: "object", - properties: { - amount: { - type: "number", - minimum: 0, - }, - rank: { - type: "number", - minimum: 1, - }, - }, - required: ["amount", "rank"], - }, - playerContract: { - type: "object", - properties: { - amount: { - type: "number", - minimum: 0, - }, - exp: { - type: "number", - }, - rookie: { - const: true, - }, - rookieResign: { - const: true, - }, - }, - required: ["amount", "exp"], - }, - playerInjury: { - type: "object", - properties: { - gamesRemaining: { - type: "integer", - minimum: 0, - }, - type: { - type: "string", - }, - }, - required: ["gamesRemaining", "type"], - }, - playoffSeriesTeam: { - type: "object", - properties: { - cid: { - type: "integer", - }, - seed: { - type: "integer", - minimum: 1, - }, - tid: { - type: "integer", - }, - won: { - type: "integer", - minimum: 0, - }, - }, - required: ["cid", "seed", "tid", "won"], - }, - playerSkill: { - type: "string", - enum: bySport({ - baseball: [ - "Pp", - "Pf", - "Pw", - "Ro", - "Ri", - "D1", - "Dc", - "Dg", - "Df", - "A", - "Hp", - "Hc", - "E", - "S", - ], - basketball: ["3", "A", "B", "Di", "Dp", "Po", "Ps", "R", "V"], - football: [ - "Pa", - "Pd", - "Ps", - "A", - "X", - "H", - "Bp", - "Br", - "PR", - "RS", - "L", - ], - hockey: ["Pm", "Pw", "G", "E", "S"], - }), - }, - tradeTeam: { - type: "object", - properties: { - dpids: { - type: "array", - items: { - type: "integer", - }, - }, - pids: { - type: "array", - items: { - type: "integer", - }, - }, - tid: { - type: "integer", - }, - }, - required: ["dpids", "pids", "tid"], - }, - div: { - type: "object", - properties: { - did: { - type: "integer", - }, - cid: { - type: "integer", - }, - name: { - type: "string", - }, - }, - required: ["did", "cid", "name"], - }, - conf: { - type: "object", - properties: { - cid: { - type: "integer", - }, - name: { - type: "string", - }, - }, - required: ["cid", "name"], - }, - }, - - type: "object", - - required: ["version"], - - properties: { - version: { - type: "integer", - }, - startingSeason: { - type: "integer", - }, - awards: { - type: "array", - items: { - type: "object", - properties: {}, - }, - }, - draftPicks: { - type: "array", - items: { - type: "object", - properties: { - dpid: { - type: "integer", - }, - tid: { - type: "integer", - }, - originalTid: { - type: "integer", - }, - round: { - type: "integer", - minimum: 1, - }, - pick: { - type: "integer", - minimum: 0, - }, - season: { - anyOf: [ - { - type: "integer", - }, - { - type: "string", - }, - ], - }, - }, - required: ["tid", "originalTid", "round", "season"], - }, - }, - draftLotteryResults: { - type: "array", - items: { - type: "object", - properties: { - season: { - type: "integer", - }, - result: { - type: "array", - items: { - type: "object", - properties: { - tid: { - type: "integer", - }, - originalTid: { - type: "integer", - }, - chances: { - type: "integer", - minimum: 1, - }, - pick: { - type: "integer", - minimum: 1, - }, - won: { - type: "integer", - minimum: 0, - }, - lost: { - type: "integer", - minimum: 0, - }, - }, - required: ["tid", "originalTid", "chances", "won", "lost"], - }, - }, - }, - required: ["season", "result"], - }, - }, - events: { - type: "array", - items: { - type: "object", - properties: {}, - }, - }, - gameAttributes: { - anyOf: [ - { - type: "array", - }, - { - type: "object", - properties: { - aiJerseyRetirement: { - type: "boolean", - }, - aiTradesFactor: { - type: "number", - }, - allStarDunk: { - type: "boolean", - }, - allStarGame: { - // boolean is legacy - anyOf: [ - { - type: "boolean", - }, - { - type: "number", - }, - { - type: "null", - }, - ], - }, - allStarNum: { - type: "number", - }, - allStarThree: { - type: "boolean", - }, - allStarType: { - type: "string", - enum: ["draft", "byConf", "top"], - }, - alwaysShowCountry: { - type: "boolean", - }, - autoDeleteOldBoxScores: { - type: "boolean", - }, - autoExpand: { - type: "object", - properties: { - phase: { - const: "vote", - }, - abbrevs: { - type: "array", - items: { - type: "string", - }, - }, - }, - required: ["phase", "abbrevs"], - }, - autoExpandProb: { - type: "number", - minimum: 0, - maximum: 1, - }, - autoExpandNumTeams: { - type: "integer", - minimum: 1, - }, - autoExpandMaxNumTeams: { - type: "integer", - minimum: 1, - }, - autoExpandGeo: { - type: "string", - enum: ["naFirst", "naOnly", "any"], - }, - autoRelocate: { - type: "object", - properties: { - phase: { - const: "vote", - }, - tid: { - type: "integer", - minimum: 0, - }, - abbrev: { - type: "string", - }, - realigned: { - type: "array", - items: { - type: "array", - items: { - type: "integer", - minimum: 0, - }, - }, - }, - }, - required: ["phase", "tid", "abbrev"], - }, - autoRelocateProb: { - type: "number", - minimum: 0, - maximum: 1, - }, - autoRelocateGeo: { - type: "string", - enum: ["naFirst", "naOnly", "any"], - }, - autoRelocateRealign: { - type: "boolean", - }, - autoRelocateRebrand: { - type: "boolean", - }, - brotherRate: { - type: "number", - minimum: 0, - }, - budget: { - type: "boolean", - }, - challengeNoDraftPicks: { - type: "boolean", - }, - challengeNoFreeAgents: { - type: "boolean", - }, - challengeNoRatings: { - type: "boolean", - }, - challengeNoTrades: { - type: "boolean", - }, - challengeLoseBestPlayer: { - type: "boolean", - }, - challengeFiredLuxuryTax: { - type: "boolean", - }, - challengeFiredMissPlayoffs: { - type: "boolean", - }, - challengeSisyphusMode: { - type: "boolean", - }, - challengeThanosMode: { - anyOf: [ - { - type: "boolean", - }, - { - type: "number", - }, - ], - }, - confs: wrap({ - type: "array", - minItems: 1, - items: { - $ref: "#/definitions/conf", - }, - }), - daysLeft: { - type: "integer", - minimum: 0, - }, - defaultStadiumCapacity: { - type: "integer", - minimum: 0, - }, - - dh: { - anyOf: [ - { - type: "string", - enum: ["all", "none"], - }, - { - type: "array", - items: { - type: "integer", - minimum: 0, - }, - }, - ], - }, - difficulty: { - type: "number", - }, - divs: wrap({ - type: "array", - minItems: 1, - items: { - $ref: "#/definitions/div", - }, - }), - draftAges: { - type: "array", - items: { - type: "integer", - }, - minItems: 2, - maxItems: 2, - }, - draftPickAutoContract: { - type: "boolean", - }, - draftPickAutoContractPercent: { - type: "number", - minimum: 0, - }, - draftPickAutoContractRounds: { - type: "integer", - minimum: 0, - }, - draftType: { - type: "string", - // nba is legacy - enum: [ - "nba1994", - "nba2019", - "noLottery", - "noLotteryReverse", - "random", - "nba1990", - "randomLotteryFirst3", - "randomLottery", - "coinFlip", - "nba", - "freeAgents", - "nhl2017", - "nhl2021", - "mlb2022", - "custom", - ], - }, - draftLotteryCustomChances: { - type: "array", - items: { - type: "number", - }, - }, - draftLotteryCustomNumPicks: { - type: "integer", - minimum: 0, - }, - elam: { - type: "boolean", - }, - elamASG: { - type: "boolean", - }, - elamMinutes: { - type: "number", - minimum: 0, - }, - elamOvertime: { - type: "boolean", - }, - elamPoints: { - type: "integer", - minimum: 0, - }, - equalizeRegions: { - type: "boolean", - }, - forceRetireAge: { - type: "integer", - }, - forceRetireSeasons: { - type: "integer", - }, - foulsNeededToFoulOut: { - type: "integer", - minimum: 0, - }, - foulsUntilBonus: { - type: "array", - items: { - type: "integer", - }, - minItems: 3, - maxItems: 3, - }, - foulRateFactor: { - type: "number", - }, - gameOver: { - type: "boolean", - }, - gender: { - type: "string", - enum: ["female", "male"], - }, - godMode: { - type: "boolean", - }, - godModeInPast: { - type: "boolean", - }, - goatFormula: { - type: "string", - }, - goatSeasonFormula: { - type: "string", - }, - gracePeriodEnd: { - type: "integer", - }, - groupScheduleSeries: { - type: "boolean", - }, - heightFactor: { - type: "number", - }, - hideDisabledTeams: { - type: "boolean", - }, - hofFactor: { - type: "number", - }, - homeCourtAdvantage: { - type: "number", - }, - inflationAvg: { - type: "number", - }, - inflationMax: { - type: "number", - }, - inflationMin: { - type: "number", - }, - inflationStd: { - type: "number", - }, - injuries: { - type: "array", - items: { - type: "object", - properties: { - name: { - type: "string", - }, - frequency: { - type: "number", - }, - games: { - type: "number", - }, - }, - required: ["name", "frequency", "games"], - }, - }, - injuryRate: { - type: "number", - minimum: 0, - }, - lid: { - type: "integer", - }, - lowestDifficulty: { - type: "number", - }, - luxuryPayroll: { - type: "integer", - minimum: 0, - }, - luxuryTax: { - type: "number", - minimum: 0, - }, - maxContract: { - type: "integer", - minimum: 0, - }, - maxContractLength: { - type: "integer", - minimum: 1, - }, - maxRosterSize: { - type: "integer", - minimum: 0, - }, - minContract: { - type: "integer", - minimum: 0, - }, - minContractLength: { - type: "integer", - minimum: 1, - }, - minPayroll: { - type: "integer", - minimum: 0, - }, - minRetireAge: { - type: "integer", - }, - minRosterSize: { - type: "integer", - minimum: 0, - }, - names: { - type: "object", - properties: { - first: {}, - last: {}, - }, - required: ["first", "last"], - }, - numWatchColors: { - type: "integer", - }, - otherTeamsWantToHire: { - type: "boolean", - }, - playerBioInfo: { - type: "object", - properties: { - countries: { - type: "object", - }, - default: { - type: "object", - properties: { - colleges: { - type: "object", - }, - fractionSkipCollege: { - type: "number", - }, - }, - }, - frequencies: { - type: "object", - }, - }, - }, - playIn: { - type: "boolean", - }, - playerMoodTraits: { - type: "boolean", - }, - pointsFormula: wrap({ - type: "string", - }), - nextPhase: { - // Shouldn't actually be null, but legacy - anyOf: [ - { - type: "integer", - }, - { - type: "null", - }, - ], - }, - numDraftPicksCurrent: { - type: "integer", - minimum: 0, - }, - numDraftRounds: { - type: "integer", - minimum: 0, - }, - numGames: wrap({ - type: "integer", - minimum: 0, - }), - numGamesDiv: { - anyOf: [ - { - type: "integer", - minimum: 0, - }, - { - type: "null", - }, - ], - }, - numGamesConf: { - anyOf: [ - { - type: "integer", - minimum: 0, - }, - { - type: "null", - }, - ], - }, - numGamesPlayoffSeries: wrap({ - type: "array", - items: { - type: "integer", - minimum: 1, - }, - }), - numPlayersDunk: { - type: "integer", - minimum: 2, - }, - numPlayersOnCourt: { - type: "integer", - minimum: 1, - }, - numPlayersThree: { - type: "integer", - minimum: 2, - }, - numPlayoffByes: wrap({ - type: "integer", - }), - numSeasonsFutureDraftPicks: { - type: "integer", - minimum: 0, - }, - numTeams: { - type: "integer", - minimum: 0, - }, - phase: { - type: "integer", - minimum: -2, - maximum: 8, - }, - playoffsByConf: { - type: "boolean", - }, - playoffsNumTeamsDiv: wrap({ - type: "integer", - minimum: 0, - }), - playoffsReseed: { - type: "boolean", - }, - playersRefuseToNegotiate: { - type: "boolean", - }, - quarterLength: { - type: "number", - minimum: 0, - }, - maxOvertimes: wrap({ - anyOf: [ - { - type: "integer", - minimum: 0, - }, - { - type: "null", - }, - ], - }), - maxOvertimesPlayoffs: wrap({ - anyOf: [ - { - type: "integer", - minimum: 0, - }, - { - type: "null", - }, - ], - }), - numPeriods: { - type: "number", - minimum: 0, - }, - randomDebutsForever: { - type: "integer", - minimum: 1, - }, - realDraftRatings: { - type: "string", - }, - realPlayerDeterminism: { - type: "number", - minimum: 0, - maximum: 1, - }, - repeatSeason: { - type: "object", - properties: { - type: { - type: "string", - enum: ["playersAndRosters", "players"], - }, - startingSeason: { - type: "number", - }, - players: { - type: "object", - }, - }, - - // Type would be required, but upgrades - required: ["startingSeason"], - }, - riggedLootery: { - type: "array", - items: { - anyOf: [ - { - type: "integer", - }, - { - type: "null", - }, - ], - }, - }, - rookieContractLengths: { - type: "array", - items: { - type: "integer", - }, - minItems: 1, - }, - rookiesCanRefuse: { - type: "boolean", - }, - salaryCap: { - type: "integer", - minimum: 0, - }, - salaryCapType: { - type: "string", - enum: ["hard", "none", "soft"], - }, - season: { - type: "integer", - }, - shootoutRounds: wrap({ - type: "integer", - minimum: 0, - }), - shootoutRoundsPlayoffs: { - type: "integer", - minimum: 0, - }, - softCapTradeSalaryMatch: { - type: "number", - minimum: 0, - }, - sonRate: { - type: "number", - minimum: 0, - }, - spectator: { - type: "boolean", - }, - startingSeason: { - type: "integer", - }, - stopOnInjury: { - type: "boolean", - }, - stopOnInjuryGames: { - type: "integer", - }, - tiebreakers: { - type: "array", - minItems: 1, - }, - otl: wrap({ - type: "boolean", - }), - thanosCooldownEnd: { - type: "number", - }, - tradeDeadline: { - type: "number", - }, - tragicDeathRate: { - type: "number", - minimum: 0, - }, - tragicDeaths: { - type: "array", - items: { - type: "object", - properties: { - reason: { - type: "string", - }, - frequency: { - type: "number", - }, - }, - required: ["reason", "frequency"], - }, - }, - userTid: wrap({ - type: "integer", - }), - userTids: { - type: "array", - items: { - type: "integer", - minimum: 0, - }, - minItems: 1, - }, - weightFactor: { - type: "number", - }, - threePointers: { - type: "boolean", - }, - threePointTendencyFactor: { - type: "number", - }, - threePointAccuracyFactor: { - type: "number", - }, - twoPointAccuracyFactor: { - type: "number", - }, - ftAccuracyFactor: { - type: "number", - }, - blockFactor: { - type: "number", - }, - stealFactor: { - type: "number", - }, - turnoverFactor: { - type: "number", - }, - orbFactor: { - type: "number", - }, - pace: { - type: "number", - }, - expansionDraft: { - type: "object", - }, - passFactor: { - type: "number", - }, - rushYdsFactor: { - type: "number", - }, - passYdsFactor: { - type: "number", - }, - completionFactor: { - type: "number", - }, - scrambleFactor: { - type: "number", - }, - sackFactor: { - type: "number", - }, - fumbleFactor: { - type: "number", - }, - intFactor: { - type: "number", - }, - fgAccuracyFactor: { - type: "number", - }, - fourthDownFactor: { - type: "number", - }, - onsideFactor: { - type: "number", - }, - onsideRecoveryFactor: { - type: "number", - }, - hitFactor: { - type: "number", - }, - giveawayFactor: { - type: "number", - }, - takeawayFactor: { - type: "number", - }, - deflectionFactor: { - type: "number", - }, - saveFactor: { - type: "number", - }, - assistFactor: { - type: "number", - }, - foulFactor: { - type: "number", - }, - groundFactor: { - type: "number", - }, - lineFactor: { - type: "number", - }, - flyFactor: { - type: "number", - }, - powerFactor: { - type: "number", - }, - throwOutFactor: { - type: "number", - }, - strikeFactor: { - type: "number", - }, - balkFactor: { - type: "number", - }, - wildPitchFactor: { - type: "number", - }, - passedBallFactor: { - type: "number", - }, - hitByPitchFactor: { - type: "number", - }, - swingFactor: { - type: "number", - }, - contactFactor: { - type: "number", - }, - neutralSite: { - type: "string", - enum: ["never", "finals", "playoffs"], - }, - tradeProposalsSeed: { - type: "integer", - }, - rpdPot: { - type: "boolean", - }, - }, - }, - ], - }, - games: { - type: "array", - items: { - type: "object", - properties: { - att: { - type: "integer", - }, - gid: { - type: "integer", - }, - lost: { - type: "object", - properties: { - tid: { - type: "integer", - }, - pts: { - type: "integer", - }, - }, - required: ["tid", "pts"], - }, - playoffs: { - type: "boolean", - }, - overtimes: { - type: "integer", - minimum: 0, - }, - season: { - type: "integer", - }, - teams: { - type: "array", - items: {}, - minItems: 2, - maxItems: 2, - }, - won: { - type: "object", - properties: { - tid: { - type: "integer", - }, - pts: { - type: "integer", - }, - }, - required: ["tid", "pts"], - }, - }, - required: [ - "att", - "lost", - "playoffs", - "overtimes", - "season", - "teams", - "won", - ], - }, - }, - messages: { - type: "array", - items: { - type: "object", - properties: { - mid: { - type: "integer", - }, - from: { - type: "string", - }, - read: { - type: "boolean", - }, - text: { - type: "string", - }, - year: { - type: "integer", - }, - subject: { - type: "string", - }, - ownerMoods: { - type: "array", - items: { - type: "object", - properties: { - wins: { - type: "number", - }, - playoffs: { - type: "number", - }, - money: { - type: "number", - }, - }, - required: ["wins", "playoffs", "money"], - }, - }, - }, - required: ["from", "read", "text", "year"], - }, - }, - negotiations: { - type: "array", - items: { - type: "object", - properties: { - pid: { - type: "integer", - }, - tid: { - type: "integer", - }, - resigning: { - type: "boolean", - }, - }, - required: ["pid", "tid", "resigning"], - }, - }, - playerFeats: { - type: "array", - items: { - type: "object", - properties: { - fid: { - type: "integer", - }, - pid: { - type: "integer", - }, - name: { - type: "string", - }, - pos: { - type: "string", - }, - season: { - type: "integer", - }, - tid: { - type: "integer", - }, - oppTid: { - type: "integer", - }, - playoffs: { - type: "boolean", - }, - gid: { - type: "integer", - }, - stats: {}, - result: { - type: "string", - enum: ["W", "L", "T"], - }, - score: { - type: "string", - }, - overtimes: { - type: "integer", - minimum: 0, - }, - }, - required: [ - "pid", - "name", - "pos", - "season", - "tid", - "oppTid", - "playoffs", - "gid", - "stats", - "score", - "overtimes", - ], - }, - }, - players: { - type: "array", - items: { - type: "object", - properties: { - awards: { - type: "array", - items: { - type: "object", - properties: { - season: { - type: "integer", - }, - type: { - type: "string", - }, - }, - required: ["season", "type"], - }, - }, - born: { - type: "object", - properties: { - year: { - type: "integer", - }, - loc: { - type: "string", - }, - }, - required: ["year", "loc"], - }, - college: { - type: "string", - }, - contract: { - $ref: "#/definitions/playerContract", - }, - customMoodItems: { - type: "array", - items: { - type: "object", - properties: { - amount: { - type: "number", - }, - text: { - type: "string", - }, - tid: { - type: "integer", - }, - }, - required: ["amount", "text"], - }, - }, - diedYear: { - type: "integer", - }, - draft: { - type: "object", - properties: { - round: { - type: "integer", - }, - pick: { - type: "integer", - }, - tid: { - type: "integer", - }, - originalTid: { - type: "integer", - }, - year: { - type: "integer", - }, - pot: { - type: "integer", - }, - ovr: { - type: "integer", - }, - skills: { - type: "array", - items: { - $ref: "#/definitions/playerSkill", - }, - }, - }, - required: ["year"], - }, - face: {}, - firstName: { - type: "string", - }, - gamesUntilTradable: { - type: "integer", - }, - hgt: { - type: "number", - }, - hof: { - anyOf: [ - { - type: "boolean", - }, - { - const: 1, - }, - ], - }, - imgURL: { - type: "string", - }, - injuries: { - type: "array", - items: { - type: "object", - properties: { - season: { - type: "integer", - }, - games: { - type: "integer", - }, - type: { - type: "string", - }, - ovrDrop: { - type: "integer", - }, - potDrop: { - type: "integer", - }, - }, - required: ["season", "games", "type"], - }, - }, - injury: { - $ref: "#/definitions/playerInjury", - }, - jerseyNumber: { - type: "string", - }, - lastName: { - type: "string", - }, - moodTraits: { - type: "array", - items: { - enum: ["F", "L", "$", "W"], - }, - }, - name: { - type: "string", - }, - note: { - type: "string", - }, - noteBool: { - const: 1, - }, - numDaysFreeAgent: { - type: "integer", - minimum: 0, - }, - pid: { - type: "integer", - }, - pos: { - type: "string", - }, - ptModifier: { - type: "number", - }, - ratings: { - type: "array", - ...genRatings(sport), - }, - relatives: { - type: "array", - items: { - type: "object", - properties: { - type: { - type: "string", - enum: ["brother", "father", "son"], - }, - pid: { - type: "integer", - }, - name: { - type: "string", - }, - }, - required: ["type", "pid", "name"], - }, - }, - retiredYear: { - anyOf: [ - { - type: "integer", - }, - { - type: "null", - }, - ], - }, - rosterOrder: { - type: "integer", - }, - salaries: { - type: "array", - items: { - type: "object", - properties: { - amount: { - type: "number", - }, - season: { - type: "integer", - }, - }, - required: ["amount", "season"], - }, - }, - srID: { - type: "string", - }, - stats: { - type: "array", - items: { - type: "object", - properties: { - playoffs: { - type: "boolean", - }, - season: { - type: "integer", - }, - tid: { - type: "integer", - }, - }, - required: ["playoffs", "season", "tid"], - }, - }, - statsTids: { - type: "array", - items: { - type: "number", - }, - }, - tid: { - type: "integer", - }, - transactions: { - type: "array", - items: { - type: "object", - properties: { - season: { - type: "integer", - }, - phase: { - type: "integer", - }, - tid: { - type: "integer", - }, - type: { - type: "string", - enum: ["draft", "freeAgent", "trade", "godMode", "import"], - }, - pickNum: { - type: "integer", - }, - fromTid: { - type: "integer", - }, - eid: { - type: "integer", - }, - }, - required: ["season", "phase", "tid", "type"], - }, - }, - value: { - type: "number", - }, - valueNoPot: { - type: "number", - }, - valueFuzz: { - type: "number", - }, - valueNoPotFuzz: { - type: "number", - }, - watch: { - anyOf: [ - { - type: "boolean", - }, - { - type: "number", - }, - ], - }, - weight: { - anyOf: [ - { - type: "number", - }, - { - type: "null", - }, - ], - }, - yearsFreeAgent: { - type: "integer", - }, - }, - required: ["ratings", "tid"], - }, - }, - playoffSeries: { - type: "array", - items: { - type: "object", - properties: { - season: { - type: "integer", - }, - currentRound: { - type: "integer", - minimum: -1, - }, - series: { - type: "array", - items: { - type: "array", - items: { - type: "object", - properties: { - home: { - $ref: "#/definitions/playoffSeriesTeam", - }, - away: { - $ref: "#/definitions/playoffSeriesTeam", - }, - gids: { - type: "array", - items: { - type: "integer", - }, - }, - }, - required: ["home"], - }, - }, - }, - }, - required: ["season", "currentRound", "series"], - }, - }, - releasedPlayers: { - type: "array", - items: { - type: "object", - properties: { - rid: { - type: "integer", - }, - pid: { - type: "integer", - }, - tid: { - type: "integer", - }, - contract: { - // Don't use #/definitions/playerContract because that requires positive amount, but some people use a negative amount here for various purposes - type: "object", - properties: { - amount: { - type: "number", - }, - exp: { - type: "number", - }, - }, - required: ["amount", "exp"], - }, - }, - required: ["pid", "tid", "contract"], - }, - }, - schedule: { - type: "array", - items: { - type: "object", - properties: { - awayTid: { - type: "integer", - }, - homeTid: { - type: "integer", - }, - }, - required: ["awayTid", "homeTid"], - }, - }, - scheduledEvents: { - type: "array", - items: { - type: "object", - properties: { - id: { - type: "integer", - }, - type: { - type: "string", - }, - season: { - type: "integer", - }, - phase: { - type: "integer", - }, - info: { - type: "object", - }, - }, - required: ["type", "season", "phase", "info"], - }, - }, - seasonLeaders: { - type: "array", - items: { - type: "object", - properties: { - season: { - type: "integer", - }, - age: { - type: "integer", - }, - regularSeason: { - type: "object", - }, - playoffs: { - type: "object", - }, - ratings: { - type: "object", - }, - ratingsFuzz: { - type: "object", - }, - }, - required: ["season", "age", "regularSeason", "playoffs", "ratings"], - }, - }, - teams: { - type: "array", - items: { - type: "object", - properties: { - tid: { - type: "integer", - }, - cid: { - type: "integer", - }, - did: { - type: "integer", - }, - region: { - type: "string", - }, - name: { - type: "string", - }, - abbrev: { - type: "string", - }, - imgURL: { - type: "string", - }, - budget: { - anyOf: [ - { - type: "object", - properties: { - ticketPrice: { - $ref: "#/definitions/budgetItem", - }, - scouting: { - $ref: "#/definitions/budgetItem", - }, - coaching: { - $ref: "#/definitions/budgetItem", - }, - health: { - $ref: "#/definitions/budgetItem", - }, - facilities: { - $ref: "#/definitions/budgetItem", - }, - }, - required: [ - "ticketPrice", - "scouting", - "coaching", - "health", - "facilities", - ], - }, - { - type: "object", - properties: { - ticketPrice: { - type: "number", - minimum: 0, - }, - scouting: { - type: "integer", - minimum: 1, - maximum: 100, - }, - coaching: { - type: "integer", - minimum: 1, - maximum: 100, - }, - health: { - type: "integer", - minimum: 1, - maximum: 100, - }, - facilities: { - type: "integer", - minimum: 1, - maximum: 100, - }, - }, - required: [ - "ticketPrice", - "scouting", - "coaching", - "health", - "facilities", - ], - }, - ], - }, - initialBudget: { - type: "object", - properties: { - scouting: { - type: "integer", - minimum: 1, - maximum: 100, - }, - coaching: { - type: "integer", - minimum: 1, - maximum: 100, - }, - health: { - type: "integer", - minimum: 1, - maximum: 100, - }, - facilities: { - type: "integer", - minimum: 1, - maximum: 100, - }, - }, - required: ["scouting", "coaching", "health", "facilities"], - }, - strategy: {}, - pop: { - type: "number", - minimum: 0, - }, - stadiumCapacity: { - type: "integer", - minimum: 0, - }, - colors: { - type: "array", - items: { - type: "string", - }, - maxItems: 3, - minItems: 3, - }, - retiredJerseyNumbers: { - type: "array", - items: { - type: "object", - properties: { - number: { - type: "string", - }, - seasonRetired: { - type: "number", - }, - seasonTeamInfo: { - type: "number", - }, - pid: { - type: "number", - }, - name: { - type: "string", - }, - text: { - type: "string", - }, - }, - required: ["number", "seasonRetired", "seasonTeamInfo", "text"], - }, - }, - srID: { - type: "string", - }, - seasons: { - type: "array", - items: { - type: "object", - properties: { - tid: { - type: "integer", - }, - cid: { - type: "integer", - }, - did: { - type: "integer", - }, - region: { - type: "string", - }, - name: { - type: "string", - }, - abbrev: { - type: "string", - }, - imgURL: { - type: "string", - }, - season: { - type: "integer", - }, - gpHome: { - type: "integer", - minimum: 0, - }, - att: { - type: "integer", - minimum: 0, - }, - cash: { - type: "number", - }, - won: { - type: "integer", - minimum: 0, - }, - lost: { - type: "integer", - minimum: 0, - }, - tied: { - type: "integer", - minimum: 0, - }, - otl: { - type: "integer", - minimum: 0, - }, - wonHome: { - type: "integer", - minimum: 0, - }, - lostHome: { - type: "integer", - minimum: 0, - }, - tiedHome: { - type: "integer", - minimum: 0, - }, - otlHome: { - type: "integer", - minimum: 0, - }, - wonAway: { - type: "integer", - minimum: 0, - }, - lostAway: { - type: "integer", - minimum: 0, - }, - tiedAway: { - type: "integer", - minimum: 0, - }, - otlAway: { - type: "integer", - minimum: 0, - }, - wonDiv: { - type: "integer", - minimum: 0, - }, - lostDiv: { - type: "integer", - minimum: 0, - }, - tiedDiv: { - type: "integer", - minimum: 0, - }, - otlDiv: { - type: "integer", - minimum: 0, - }, - wonConf: { - type: "integer", - minimum: 0, - }, - otlConf: { - type: "integer", - minimum: 0, - }, - lostConf: { - type: "integer", - minimum: 0, - }, - tiedConf: { - type: "integer", - minimum: 0, - }, - lastTen: { - type: "array", - items: { - anyOf: [ - { - type: "integer", - }, - { - type: "string", - }, - ], - enum: [-1, 0, 1, "OTL"], - }, - }, - streak: { - type: "integer", - }, - playoffRoundsWon: { - type: "integer", - minimum: -1, - }, - hype: { - type: "number", - minimum: 0, - maximum: 1, - }, - pop: { - type: "number", - minimum: 0, - }, - stadiumCapacity: { - type: "integer", - minimum: 0, - }, - revenues: { - anyOf: [ - { - type: "object", - properties: { - luxuryTaxShare: { - $ref: "#/definitions/budgetItem", - }, - merch: { - $ref: "#/definitions/budgetItem", - }, - sponsor: { - $ref: "#/definitions/budgetItem", - }, - ticket: { - $ref: "#/definitions/budgetItem", - }, - nationalTv: { - $ref: "#/definitions/budgetItem", - }, - localTv: { - $ref: "#/definitions/budgetItem", - }, - }, - required: [ - "luxuryTaxShare", - "merch", - "sponsor", - "ticket", - "nationalTv", - "localTv", - ], - }, - { - type: "object", - properties: { - luxuryTaxShare: { - type: "number", - minimum: 0, - }, - merch: { - type: "number", - minimum: 0, - }, - sponsor: { - type: "number", - minimum: 0, - }, - ticket: { - type: "number", - minimum: 0, - }, - nationalTv: { - type: "number", - minimum: 0, - }, - localTv: { - type: "number", - minimum: 0, - }, - }, - required: [ - "luxuryTaxShare", - "merch", - "sponsor", - "ticket", - "nationalTv", - "localTv", - ], - }, - ], - }, - expenses: { - anyOf: [ - { - type: "object", - properties: { - salary: { - $ref: "#/definitions/budgetItem", - }, - luxuryTax: { - $ref: "#/definitions/budgetItem", - }, - minTax: { - $ref: "#/definitions/budgetItem", - }, - scouting: { - $ref: "#/definitions/budgetItem", - }, - coaching: { - $ref: "#/definitions/budgetItem", - }, - health: { - $ref: "#/definitions/budgetItem", - }, - facilities: { - $ref: "#/definitions/budgetItem", - }, - }, - required: [ - "salary", - "luxuryTax", - "minTax", - "scouting", - "coaching", - "health", - "facilities", - ], - }, - { - type: "object", - properties: { - salary: { - type: "number", - minimum: 0, - }, - luxuryTax: { - type: "number", - minimum: 0, - }, - minTax: { - type: "number", - minimum: 0, - }, - scouting: { - type: "number", - minimum: 0, - }, - coaching: { - type: "number", - minimum: 0, - }, - health: { - type: "number", - minimum: 0, - }, - facilities: { - type: "number", - minimum: 0, - }, - }, - required: [ - "salary", - "luxuryTax", - "minTax", - "scouting", - "coaching", - "health", - "facilities", - ], - }, - ], - }, - expenseLevels: { - type: "object", - properties: { - scouting: { - type: "number", - minimum: 0, - }, - coaching: { - type: "number", - minimum: 0, - }, - health: { - type: "number", - minimum: 0, - }, - facilities: { - type: "number", - minimum: 0, - }, - }, - required: ["scouting", "coaching", "health", "facilities"], - }, - payrollEndOfSeason: { - type: "integer", - }, - ownerMood: { - type: "object", - properties: { - wins: { - type: "number", - }, - playoffs: { - type: "number", - }, - money: { - type: "number", - }, - }, - required: ["wins", "playoffs", "money"], - }, - numPlayersTradedAway: { - type: "number", - minimum: 0, - }, - avgAge: { - type: "number", - }, - ovrStart: { - type: "number", - }, - ovrEnd: { - type: "number", - }, - note: { - type: "string", - }, - noteBool: { - const: 1, - }, - }, - required: ["season"], - }, - }, - stats: { - type: "array", - items: { - type: "object", - properties: {}, - }, - }, - firstSeasonAfterExpansion: { - type: "integer", - }, - adjustForInflation: { - type: "boolean", - }, - disabled: { - type: "boolean", - }, - keepRosterSorted: { - type: "boolean", - }, - ...depth, - }, - required: ["cid", "did", "region", "name", "abbrev"], - }, - }, - savedTrades: { - type: "array", - items: { - type: "object", - properties: { - hash: { - type: "string", - }, - tid: { - type: "integer", - }, - }, - required: ["hash", "tid"], - }, - }, - trade: { - type: "array", - items: { - type: "object", - properties: { - rid: { - type: "integer", - minimum: 0, - maximum: 0, - }, - teams: { - type: "array", - items: [ - { - $ref: "#/definitions/tradeTeam", - }, - { - $ref: "#/definitions/tradeTeam", - }, - ], - minItems: 2, - maxItems: 2, - }, - }, - required: ["rid", "teams"], - }, - maxItems: 1, - }, - }, - }; -}; diff --git a/tools/lib/generateVersionNumber.ts b/tools/lib/generateVersionNumber.ts deleted file mode 100644 index 2e4acda38..000000000 --- a/tools/lib/generateVersionNumber.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const generateVersionNumber = () => { - const date = new Date(); - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const day = String(date.getDate()).padStart(2, "0"); - const minutes = String(date.getMinutes() + 60 * date.getHours()).padStart( - 4, - "0", - ); - - return `${year}.${month}.${day}.${minutes}`; -}; diff --git a/tools/lib/minifyIndexHtml.ts b/tools/lib/minifyIndexHtml.ts deleted file mode 100644 index e73fd29f3..000000000 --- a/tools/lib/minifyIndexHtml.ts +++ /dev/null @@ -1,15 +0,0 @@ -import fs from "node:fs"; -import { minify } from "html-minifier-terser"; - -export const minifyIndexHtml = async () => { - const content = fs.readFileSync("build/index.html", "utf8"); - const minified = await minify(content, { - collapseBooleanAttributes: true, - collapseWhitespace: true, - minifyCSS: true, - minifyJS: true, - removeComments: true, - useShortDoctype: true, - }); - fs.writeFileSync("build/index.html", minified); -}; diff --git a/tools/lib/replace.ts b/tools/lib/replace.ts deleted file mode 100644 index 3cbd1c818..000000000 --- a/tools/lib/replace.ts +++ /dev/null @@ -1,20 +0,0 @@ -import fs from "node:fs"; - -export const replace = ({ - paths, - replaces, -}: { - paths: fs.PathOrFileDescriptor[]; - replaces: { - searchValue: string | RegExp; - replaceValue: string; - }[]; -}) => { - for (const path of paths) { - let contents = fs.readFileSync(path, "utf8"); - for (const { searchValue, replaceValue } of replaces) { - contents = contents.replaceAll(searchValue, replaceValue); - } - fs.writeFileSync(path, contents); - } -}; diff --git a/tools/lib/reset.ts b/tools/lib/reset.ts deleted file mode 100644 index 427aeddcc..000000000 --- a/tools/lib/reset.ts +++ /dev/null @@ -1,6 +0,0 @@ -import fsp from "node:fs/promises"; - -export const reset = async () => { - await fsp.rm("build", { recursive: true, force: true }); - await fsp.mkdir("build/gen", { recursive: true }); -}; diff --git a/tools/lib/setTimestamps.ts b/tools/lib/setTimestamps.ts deleted file mode 100644 index 6cd545616..000000000 --- a/tools/lib/setTimestamps.ts +++ /dev/null @@ -1,281 +0,0 @@ -import { bySport } from "./bySport.ts"; -import { replace } from "./replace.ts"; - -export const setTimestamps = (rev: string, watch: boolean = false) => { - if (watch) { - replace({ - paths: ["build/index.html"], - replaces: [ - { - searchValue: "-REV_GOES_HERE.js", - replaceValue: ".js", - }, - { - searchValue: '-" + bbgmVersion + "', - replaceValue: "", - }, - { - searchValue: /-CSS_HASH_(LIGHT|DARK)/g, - replaceValue: "", - }, - { - searchValue: "REV_GOES_HERE", - replaceValue: rev, - }, - ], - }); - } else { - replace({ - paths: [ - "build/index.html", - - // This is currently just for lastChangesVersion, so don't worry about it not working in watch mode - `build/gen/worker-${rev}.js`, - `build/gen/worker-legacy-${rev}.js`, - ], - replaces: [ - { - searchValue: "REV_GOES_HERE", - replaceValue: rev, - }, - ], - }); - } - - // Quantcast Choice. Consent Manager Tag v2.0 (for TCF 2.0) - const bannerAdsCode = ` - - - - - - - - - - -`; - - if (!watch) { - replace({ - paths: [`build/gen/ui-legacy-${rev}.js`], - replaces: [ - { - searchValue: "/gen/worker-", - replaceValue: "/gen/worker-legacy-", - }, - ], - }); - } - - replace({ - paths: ["build/index.html"], - replaces: [ - { - searchValue: "BANNER_ADS_CODE", - replaceValue: bannerAdsCode, - }, - { - searchValue: "GOOGLE_ANALYTICS_ID", - replaceValue: bySport({ - basketball: "UA-38759330-1", - football: "UA-38759330-2", - default: "UA-38759330-3", - }), - }, - { - searchValue: "BUGSNAG_API_KEY", - replaceValue: bySport({ - baseball: "37b1fd32d021f7716dc0e1d4a3e619bc", - basketball: "c10b95290070cb8888a7a79cc5408555", - football: "fed8957cbfca2d1c80997897b840e6cf", - hockey: "449e8ed576f7cbccf5c7649e936ab9ff", - }), - }, - ], - }); - - return rev; -}; diff --git a/tools/pre-test.ts b/tools/pre-test.ts index 920311b8a..c69157459 100644 --- a/tools/pre-test.ts +++ b/tools/pre-test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; if (!fs.existsSync("build/files/league-schema.json")) { - const { generateJsonSchema } = await import("./lib/generateJsonSchema.ts"); + const { generateJsonSchema } = await import("./build/generateJsonSchema.ts"); const jsonSchema = generateJsonSchema("test"); fs.mkdirSync("build/files", { recursive: true }); fs.writeFileSync( diff --git a/tools/watch/cli.ts b/tools/watch/cli.ts index e476cfb8a..cb7e485aa 100644 --- a/tools/watch/cli.ts +++ b/tools/watch/cli.ts @@ -1,9 +1,9 @@ import { statSync } from "node:fs"; import { spinners } from "./spinners.ts"; -import watchCSS from "./watchCSS.ts"; -import watchFiles from "./watchFiles.ts"; -import watchJS from "./watchJS.ts"; -import watchJSONSchema from "./watchJSONSchema.ts"; +import { watchCss } from "./watchCss.ts"; +import { watchFiles } from "./watchFiles.ts"; +import { watchJs } from "./watchJs.ts"; +import { watchJsonSchema } from "./watchJsonSchema.ts"; import { startServer } from "../lib/server.ts"; const param = process.argv[2]; @@ -50,9 +50,9 @@ const updateError = (filename: string, error: Error) => { // Needs to run first, to create output folder await watchFiles(updateStart, updateEnd, updateError); -// Schema is needed for JS bunlde, and watchJSONSchema is async -watchJSONSchema(updateStart, updateEnd, updateError).then(() => { - watchJS(updateStart, updateEnd, updateError); +// Schema is needed for JS bunlde, and watchJsonSchema is async +watchJsonSchema(updateStart, updateEnd, updateError).then(() => { + watchJs(updateStart, updateEnd, updateError); }); -watchCSS(updateStart, updateEnd, updateError); +watchCss(updateStart, updateEnd, updateError); diff --git a/tools/watch/watchCSS.ts b/tools/watch/watchCss.ts similarity index 83% rename from tools/watch/watchCSS.ts rename to tools/watch/watchCss.ts index fb98a90d4..48b167856 100644 --- a/tools/watch/watchCSS.ts +++ b/tools/watch/watchCss.ts @@ -1,13 +1,13 @@ import path from "node:path"; import { Worker } from "node:worker_threads"; -const watchCSS = async ( +export const watchCss = async ( updateStart: (filename: string) => void, updateEnd: (filename: string) => void, updateError: (filename: string, error: Error) => void, ) => { const worker = new Worker( - path.join(import.meta.dirname, "watchCSSWorker.ts"), + path.join(import.meta.dirname, "watchCssWorker.ts"), ); worker.on("message", (message) => { @@ -22,5 +22,3 @@ const watchCSS = async ( } }); }; - -export default watchCSS; diff --git a/tools/watch/watchCSSWorker.ts b/tools/watch/watchCssWorker.ts similarity index 93% rename from tools/watch/watchCSSWorker.ts rename to tools/watch/watchCssWorker.ts index ff7cbe18d..1fedec830 100644 --- a/tools/watch/watchCSSWorker.ts +++ b/tools/watch/watchCssWorker.ts @@ -1,6 +1,6 @@ import { watch } from "chokidar"; import { parentPort } from "node:worker_threads"; -import { buildCss } from "../lib/buildCss.ts"; +import { buildCss } from "../build/buildCss.ts"; const filenames = ["build/gen/light.css", "build/gen/dark.css"]; diff --git a/tools/watch/watchFiles.ts b/tools/watch/watchFiles.ts index c1bb300fd..6bdf0509f 100644 --- a/tools/watch/watchFiles.ts +++ b/tools/watch/watchFiles.ts @@ -1,12 +1,12 @@ import { watch } from "chokidar"; -import { copyFiles } from "../lib/copyFiles.ts"; -import { generateVersionNumber } from "../lib/generateVersionNumber.ts"; -import { reset } from "../lib/reset.ts"; -import { setTimestamps } from "../lib/setTimestamps.ts"; +import { copyFiles } from "../build/copyFiles.ts"; +import { generateVersionNumber } from "../build/generateVersionNumber.ts"; +import { reset } from "../build/reset.ts"; +import { setTimestamps } from "../build/setTimestamps.ts"; // Would be better to only copy individual files on update, but this is fast enough -const watchFiles = async ( +export const watchFiles = async ( updateStart: (filename: string) => void, updateEnd: (filename: string) => void, updateError: (filename: string, error: Error) => void, @@ -35,5 +35,3 @@ const watchFiles = async ( const watcher = watch(["public", "data", "node_modules/flag-icons"], {}); watcher.on("change", buildWatchFiles); }; - -export default watchFiles; diff --git a/tools/watch/watchJS.ts b/tools/watch/watchJs.ts similarity index 82% rename from tools/watch/watchJS.ts rename to tools/watch/watchJs.ts index 066668386..e4b721bcd 100644 --- a/tools/watch/watchJS.ts +++ b/tools/watch/watchJs.ts @@ -1,7 +1,7 @@ import path from "node:path"; import { Worker } from "node:worker_threads"; -const watchJS = ( +export const watchJs = ( updateStart: (filename: string) => void, updateEnd: (filename: string) => void, updateError: (filename: string, error: Error) => void, @@ -10,7 +10,7 @@ const watchJS = ( const filename = `build/gen/${name}.js`; const worker = new Worker( - path.join(import.meta.dirname, "watchJSWorker.ts"), + path.join(import.meta.dirname, "watchJsWorker.ts"), { workerData: { name, @@ -32,6 +32,4 @@ const watchJS = ( } }; -// watchJS((filename) => console.log('updateStart', filename), (filename) => console.log('updateEnd', filename), (filename, error) => console.log('updateError', filename, error)); - -export default watchJS; +// watchJs((filename) => console.log('updateStart', filename), (filename) => console.log('updateEnd', filename), (filename, error) => console.log('updateError', filename, error)); diff --git a/tools/watch/watchJSWorker.ts b/tools/watch/watchJsWorker.ts similarity index 100% rename from tools/watch/watchJSWorker.ts rename to tools/watch/watchJsWorker.ts diff --git a/tools/watch/watchJSONSchema.ts b/tools/watch/watchJsonSchema.ts similarity index 84% rename from tools/watch/watchJSONSchema.ts rename to tools/watch/watchJsonSchema.ts index aa27e99c0..094b51dce 100644 --- a/tools/watch/watchJSONSchema.ts +++ b/tools/watch/watchJsonSchema.ts @@ -8,7 +8,7 @@ const importFresh = async (modulePath: string) => { return await import(cacheBustingModulePath); }; -const watchJSONSchema = async ( +export const watchJsonSchema = async ( updateStart: (filename: string) => void, updateEnd: (filename: string) => void, updateError: (filename: string, error: Error) => void, @@ -25,7 +25,7 @@ const watchJSONSchema = async ( // Dynamically reload generateJsonSchema, cause that's what we're watching! const { generateJsonSchema } = await importFresh( - "../lib/generateJsonSchema.ts", + "../build/generateJsonSchema.ts", ); const jsonSchema = generateJsonSchema(sport); @@ -40,10 +40,8 @@ const watchJSONSchema = async ( await buildJSONSchema(); - const watcher = watch("tools/lib/generateJsonSchema.ts", {}); + const watcher = watch("tools/build/generateJsonSchema.ts", {}); watcher.on("change", buildJSONSchema); }; -// watchJSONSchema((filename) => console.log('updateStart', filename), (filename) => console.log('updateEnd', filename), (filename, error) => console.log('updateError', filename, error)); - -export default watchJSONSchema; +// watchJsonSchema((filename) => console.log('updateStart', filename), (filename) => console.log('updateEnd', filename), (filename, error) => console.log('updateError', filename, error));