diff --git a/.husky/lint-config.sh b/.husky/lint-config.sh index a4e07d8..2665786 100644 --- a/.husky/lint-config.sh +++ b/.husky/lint-config.sh @@ -1,9 +1,10 @@ # This config file is sourced by the pre-commit script. +# shellcheck disable=SC2034,SC2148 # Change 1 to 0 to disable linting. enabled=1 -# Directories containing Node.js projects to be linted, separated by spaces. +# Directories containing JavaScript projects to be linted, separated by spaces. node_dirs='backend frontend' # Command used to run a lint check. diff --git a/.husky/pre-commit b/.husky/pre-commit index 70911e4..a0fd94d 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -3,6 +3,8 @@ # produces a "cannot spawn" error with a bash shebang, since it uses dash. # However, dash is not available on many Unix-like systems. +# shellcheck disable=SC2317 + log() { echo "${0}: ${*}" >&2 } @@ -61,7 +63,7 @@ ask_yes_no() { fi while :; do - printf "${0}: ${prompt}" + printf "%s: %s" "${0}" "${prompt}" read -r selection selection="$(parse_yes_no "${selection}")" @@ -86,7 +88,7 @@ ask_yes_no() { explain_no_verify() { log "If you wish to bypass the lint check entirely," log "use the following command:" - log " git commit --no-verify" + log " NO_LINT=1 git commit" } dir_check() { @@ -176,12 +178,17 @@ cancel() { main() { config_file="$(dirname "${0}")/lint-config.sh" + + # shellcheck source=./lint-config.sh if ! . "${config_file}"; then error "Error while sourcing config file '${config_file}'." exit 1 fi - if [ "${enabled}" -eq 0 ]; then + secret_scan_script="$(dirname "${0}")/../.secret-scan/secret-scan.js" + node "${secret_scan_script}" || exit + + if [ "${enabled}" = 0 ] || [ "${NO_LINT}" = 1 ]; then warn "Lint check has been disabled." exit 0 fi diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100755 index 0000000..1ca98d4 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,3 @@ +#!/bin/sh +secret_scan_script="$(dirname "${0}")/../.secret-scan/secret-scan.js" +node "${secret_scan_script}" diff --git a/.secret-scan/.gitignore b/.secret-scan/.gitignore new file mode 100644 index 0000000..a8df5f5 --- /dev/null +++ b/.secret-scan/.gitignore @@ -0,0 +1,2 @@ +/secret-scan-cache.json +/secret-scan-report.json diff --git a/.secret-scan/secret-scan-config.json b/.secret-scan/secret-scan-config.json new file mode 100644 index 0000000..266a0e9 --- /dev/null +++ b/.secret-scan/secret-scan-config.json @@ -0,0 +1,35 @@ +{ + "//": [ + "Regexes used to scan the repository contents for secrets.", + "If possible, try to make the regex match the entire secret, or", + "allowedStrings might not work as expected. For example, if a regex", + "matches only 'mongodb', this string by itself does not contain any of the", + "strings in the allowlist, so it will still be flagged." + ], + "secretRegexes": { + "mongodbUrl": "mongodb([+]srv)?://[^\\s]+", + "firebaseJsonPrivateKeyFile": "-----BEGIN PRIVATE KEY-----[^\\s]+", + "emailAppPassword": "EMAIL_APP_PASSWORD=.*" + }, + "//": [ + "To prevent a particular string from being flagged, add it (or a substring", + "of it) to this array. This can be useful if your repository contains an", + "example of what a credential should look like, a development credential", + "(e.g. a database on localhost), or a previously leaked credential that", + "has already been revoked. Obviously, do not put active credentials here." + ], + "allowedStrings": [ + "mongodb://127.0.0.1", + "mongodb://localhost", + + "EMAIL_APP_PASSWORD=\"\"", + "EMAIL_APP_PASSWORD=''", + "EMAIL_APP_PASSWORD=\"xxx\"" + ], + "//": [ + "Do not check for secrets in these files. You should almost always use", + "allowedStrings instead of this. We only add this config because it", + "naturally contains things that look like secrets, but aren't." + ], + "skippedFiles": [".secret-scan/secret-scan-config.json"] +} diff --git a/.secret-scan/secret-scan.js b/.secret-scan/secret-scan.js new file mode 100644 index 0000000..21af69a --- /dev/null +++ b/.secret-scan/secret-scan.js @@ -0,0 +1,556 @@ +const child_process = require("node:child_process"); +const fs = require("node:fs"); +const path = require("node:path"); +const process = require("node:process"); +const util = require("node:util"); +const tty = require("node:tty"); + +const CACHE_PATH = path.join(__dirname, "secret-scan-cache.json"); +const CONFIG_PATH = path.join(__dirname, "secret-scan-config.json"); +const REPORT_PATH = path.join(__dirname, "secret-scan-report.json"); +const JSON_ENCODING = "utf8"; + +const EOL = /\r?\n/; + +const secretRemovalAdvice = ` +1. If you are absolutely confident that the reported + secrets are not actually secrets, see + ${CONFIG_PATH} + for next steps and try again. Ask your engineering + manager or VP Technology if you have any uncertainty + whatsoever. + +2. If the secrets are in a file in the working tree, and + this file should not be committed to Git, update your + .gitignore and try again. + +3. If the secrets are in the index, unstage them with + git restore --staged and try again. + +4. If the secrets are in an existing commit, you are + REQUIRED to report this to your engineering manager AND + VP Technology, even if you are sure that the commit was + never pushed. This is because a secret being committed + anywhere (even locally) indicates a potential issue with + the implementation or configuration of this secret + scanning tool. + + If the commit was pushed, assume that the secret is now + publicly known, and revoke it as soon as possible. + + Proper secret management is an important part of building + secure software for our clients. You'll never be punished + for reporting a problem; please err on the side of + letting us know if you're unsure. If something goes wrong + and is reported promptly, our policy is that the + responsibility belongs to the processes and tooling, not + the individual. +`.trim(); + +/** + * @param {string} text + * @returns {string} + */ +function redText(text) { + if (process.stdout.isTTY && process.stdout.hasColors()) { + // https://github.com/nodejs/node/issues/42770#issuecomment-1101093517 + const red = util.inspect.colors.red; + if (red !== undefined) { + return `\u001b[${red[0]}m` + text + `\u001b[${red[1]}m`; + } + } + return text; +} + +/** + * @param {string} filePath + * @returns {unknown} + */ +function parseJSONFromFile(filePath) { + const text = fs.readFileSync(filePath, { encoding: JSON_ENCODING }); + return JSON.parse(text); +} + +/** + * @template T + * @param {() => T} callback + * @returns {T | null} + */ +function nullIfFileNotFound(callback) { + try { + return callback(); + } catch (e) { + if ( + typeof e === "object" && + e !== null && + "code" in e && + (e.code === "ENOENT" || e.code === "EISDIR") + ) { + return null; + } + throw e; + } +} + +/** + * @param {unknown} array + * @returns {string[]} + */ +function asStringArray(array) { + if (!Array.isArray(array)) { + throw new Error(`Not a string array: ${JSON.stringify(array)}`); + } + + return array.map((s) => { + if (typeof s === "string") { + return s; + } + throw new Error(`Not a string: ${JSON.stringify(s)}`); + }); +} + +/** + * @typedef {{ + * allowedStrings: string[]; + * secretRegexes: Record; + * skippedFiles: string[]; + * }} SecretScanConfig + */ + +/** + * @returns {SecretScanConfig} + */ +function loadConfig() { + const parsed = parseJSONFromFile(CONFIG_PATH); + if ( + typeof parsed === "object" && + parsed !== null && + "allowedStrings" in parsed && + "secretRegexes" in parsed && + "skippedFiles" in parsed && + typeof parsed.secretRegexes === "object" && + parsed.secretRegexes !== null + ) { + const secretRegexes = Object.fromEntries( + Object.entries(parsed.secretRegexes).map(([k, v]) => { + if (typeof v !== "string") { + throw new Error(`Not a string: ${JSON.stringify(v)}`); + } + return [k, v]; + }), + ); + + return { + allowedStrings: asStringArray(parsed.allowedStrings), + secretRegexes, + skippedFiles: asStringArray(parsed.skippedFiles), + }; + } + throw new Error("Config format is invalid."); +} + +/** + * @typedef {{ + * config: unknown; + * script: string; + * safeCommitHashes: string[]; + * }} SecretScanCache + */ + +/** @returns {SecretScanCache | null} */ +function loadCache() { + return nullIfFileNotFound(() => { + const parsed = parseJSONFromFile(CACHE_PATH); + if ( + typeof parsed === "object" && + parsed !== null && + "config" in parsed && + "script" in parsed && + typeof parsed.script === "string" && + "safeCommitHashes" in parsed + ) { + return { + config: parsed.config, + script: parsed.script, + safeCommitHashes: asStringArray(parsed.safeCommitHashes), + }; + } else { + console.log("Cache format is invalid, so it will not be used."); + return null; + } + }); +} + +/** + * @param {SecretScanCache} cache + * @returns {void} + */ +function saveCache(cache) { + fs.writeFileSync(CACHE_PATH, JSON.stringify(cache), { + encoding: JSON_ENCODING, + }); +} + +function deleteReport() { + if (fs.statSync(REPORT_PATH, { throwIfNoEntry: false })?.isFile()) { + fs.unlinkSync(REPORT_PATH); + } +} + +/** + * @typedef {{ + * where: string; + * path: string; + * line: number; + * regexName: string; + * matchedText: string; + * }[]} SecretScanReport + */ + +/** + * @param {SecretScanReport} report + */ +function saveReport(report) { + fs.writeFileSync(REPORT_PATH, JSON.stringify(report, null, 2) + "\n", { + encoding: JSON_ENCODING, + }); +} + +/** + * @param {readonly [string, ...string[]]} command + * @returns {string} + */ +function runCommand(command) { + const process = child_process.spawnSync(command[0], command.slice(1), { + cwd: __dirname, + encoding: "utf8", + maxBuffer: Infinity, + }); + + if (process.status === 0) { + return process.stdout; + } + + console.log(process); + throw new Error( + `Command did not execute successfully: ${JSON.stringify(command)}`, + ); +} + +/** + * @param {string} text + * @returns {string[]} + */ +function nonEmptyLines(text) { + return text.split(EOL).filter((line) => line.length > 0); +} + +/** @returns {void} */ +function checkGitVersion() { + const command = ["git", "--version"]; + + let output; + try { + output = runCommand(["git", "--version"]); + } catch (e) { + console.log(e); + const msg = + "Could not run git from the command line. Make sure it is installed and on your PATH, and try again."; + throw new Error(msg); + } + + const expectedPrefix = "git version "; + if (!output.startsWith(expectedPrefix)) { + const msg = `Output of command ${JSON.stringify( + command, + )} did not start with expected prefix ${JSON.stringify( + expectedPrefix, + )}. Maybe the text encoding for child process output is not utf8 in this environment?`; + throw new Error(msg); + } +} + +/** @returns {string} */ +function getRepoRoot() { + const repoRoot = runCommand(["git", "rev-parse", "--show-toplevel"]).replace( + EOL, + "", + ); + + // Make sure we don't get "file not found" later and assume the file was + // deleted from the working tree, when the actual cause is having an incorrect + // path for the repo root. Don't ask me how I know... + if ( + !fs + .statSync(path.join(repoRoot, ".git"), { throwIfNoEntry: false }) + ?.isDirectory() + ) { + throw new Error( + `Could not determine repo root: got ${JSON.stringify( + repoRoot, + )}, but this is incorrect?`, + ); + } + + return repoRoot; +} + +/** @returns {number} */ +function checkGitHooks() { + checkGitVersion(); + + const expectedHooksPath = ".husky"; + const command = /** @type {const} */ ([ + "git", + "config", + "--get", + "core.hooksPath", + ]); + const helpMsg = `Husky has not installed the required Git hooks. Run "npm run prepare" and try again.`; + + let hooksPath; + try { + hooksPath = runCommand(command).replace(EOL, ""); + } catch (e) { + console.log(e); + throw new Error(helpMsg); + } + + if (hooksPath !== expectedHooksPath) { + const msg = [ + `Command ${JSON.stringify(command)} returned ${JSON.stringify( + hooksPath, + )}, expected ${JSON.stringify(expectedHooksPath)}.`, + helpMsg, + ].join("\n\n"); + throw new Error(msg); + } + + console.log( + `Git hooks are correctly installed in ${JSON.stringify( + expectedHooksPath, + )}.`, + ); + return 0; +} + +/** @returns {number} */ +function runSecretScan() { + const shouldSaveReport = process.env["SECRET_SCAN_WRITE_REPORT"] === "1"; + + deleteReport(); + + /** + * @type {SecretScanReport} + */ + const report = []; + + console.log( + `${__filename}: Scanning commit history and working tree for secrets.`, + ); + + checkGitVersion(); + const repoRoot = getRepoRoot(); + + const config = loadConfig(); + const script = fs.readFileSync(__filename, { encoding: "utf8" }); + + let loadedCache = loadCache(); + if (loadedCache !== null) { + if (JSON.stringify(config) !== JSON.stringify(loadedCache.config)) { + console.log("Invalidating cache because config has changed."); + loadedCache = null; + } else if (script !== loadedCache.script) { + console.log("Invalidating cache because script has changed."); + loadedCache = null; + } + } + + /** @type {SecretScanCache} */ + const cache = loadedCache ?? { + config: JSON.parse(JSON.stringify(config)), + script, + safeCommitHashes: [], + }; + + const previouslyScannedCommitHashes = new Set(cache.safeCommitHashes); + const filesToSkip = new Set(config.skippedFiles); + const secretRegexes = Object.fromEntries( + Object.entries(config.secretRegexes).map(([k, v]) => [ + k, + new RegExp(v, "g"), + ]), + ); + + /** @param {string} matchedText */ + function isFalsePositive(matchedText) { + return config.allowedStrings.some((allowed) => + matchedText.includes(allowed), + ); + } + + /** + * Scan the commit with the given hash, or null to scan the index and working + * tree. + * + * @param {string | null} maybeCommitHash + * @returns {void} + */ + function scan(maybeCommitHash) { + /** @type {{ path: string; where: string; contents: string; }[]} */ + const changedFiles = []; + + // Don't try to read deleted files. If you ever get an error message like + // "unknown revision or path not in the working tree", double check this. + const gitListFileOptions = [ + "--no-renames", + "--diff-filter=d", + "--name-only", + ]; + + if (maybeCommitHash === null) { + const workingTreePaths = nonEmptyLines( + runCommand(["git", "status", "--porcelain"]), + ).map((line) => line.slice(3)); + for (const workingTreePath of workingTreePaths) { + // If the file was deleted, we can ignore it. I was a bit too lazy to + // parse the status letters of `git status --porcelain`. + let contents = nullIfFileNotFound(() => + fs.readFileSync(path.join(repoRoot, workingTreePath), { + encoding: "utf8", + }), + ); + + if (contents !== null) { + changedFiles.push({ + path: workingTreePath, + where: "working tree", + contents, + }); + } + } + + const stagedPaths = nonEmptyLines( + runCommand(["git", "diff", "--staged", ...gitListFileOptions]), + ); + for (const stagedPath of stagedPaths) { + changedFiles.push({ + path: stagedPath, + where: "index", + contents: runCommand(["git", "show", ":" + stagedPath]), + }); + } + } else { + const [commitDescription, ...changedPaths] = nonEmptyLines( + runCommand([ + "git", + "show", + "--oneline", + ...gitListFileOptions, + maybeCommitHash, + ]), + ); + const where = `commit ${JSON.stringify(commitDescription)}`; + for (const changedPath of changedPaths) { + changedFiles.push({ + path: changedPath, + where, + contents: runCommand([ + "git", + "show", + `${maybeCommitHash}:${changedPath}`, + ]), + }); + } + } + + let secretDetected = false; + for (const { path, where, contents } of changedFiles) { + if (filesToSkip.has(path)) { + continue; + } + + for (const [regexName, regex] of Object.entries(secretRegexes)) { + for (const match of contents.matchAll(regex)) { + const matchedText = match[0]; + if (isFalsePositive(matchedText)) { + continue; + } + + const line = contents.substring(0, match.index).split("\n").length; + + secretDetected = true; + report.push({ + where, + path, + line, + regexName, + matchedText, + }); + + console.log( + `SECRET DETECTED in ${where}, file ${JSON.stringify( + path, + )}, line ${line}: regex ${regexName} (${regex}) matched text ${JSON.stringify( + matchedText, + )}`, + ); + } + } + } + + if (!secretDetected && maybeCommitHash !== null) { + cache.safeCommitHashes.push(maybeCommitHash); + } + } + + // Scan every commit. + const allCommitHashes = nonEmptyLines( + runCommand(["git", "log", "--pretty=format:%H"]), + ); + for (const hash of allCommitHashes) { + if (!previouslyScannedCommitHashes.has(hash)) { + scan(hash); + } + } + + // Scan the index and working tree. + scan(null); + + if (shouldSaveReport) { + saveReport(report); + console.log(`Report written to ${JSON.stringify(REPORT_PATH)}`); + } + + saveCache(cache); + + if (report.length > 0) { + console.log( + redText(`Secret scan completed with errors.\n\n${secretRemovalAdvice}\n`), + ); + return 1; + } else { + console.log("Secret scan completed successfully."); + return 0; + } +} + +/** @returns {number} */ +function main() { + let args = process.argv.slice(2); + if (args[0] === "--") { + args = args.slice(1); + } + + switch (JSON.stringify(args)) { + case JSON.stringify([]): + return runSecretScan(); + case JSON.stringify(["--check-git-hooks"]): + return checkGitHooks(); + default: + const msg = `Invalid command-line arguments: ${JSON.stringify(args)}`; + throw new Error(msg); + } +} + +process.exit(main()); diff --git a/backend/.env.example b/backend/.env.example index ca4dabe..3a23d39 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,10 +1,8 @@ -# Example environment variables for backend. Any "xxx"'s should be +# Example environment variables for backend. Any ""s should be # replaced with the real values. PORT=3001 MONGODB_URI="mongodb://127.0.0.1:27017/pap" FRONTEND_ORIGIN="http://localhost:3000" -EMAIL_USER="xxx@gmail.com" -EMAIL_APP_PASSWORD="xxx" -BACKEND_FIREBASE_SETTINGS='{ - "xxx": "xxx" -}' +EMAIL_USER="@gmail.com" +EMAIL_APP_PASSWORD="" +BACKEND_FIREBASE_SETTINGS='' diff --git a/backend/.eslintignore b/backend/.eslintignore new file mode 100644 index 0000000..e805402 --- /dev/null +++ b/backend/.eslintignore @@ -0,0 +1,4 @@ +# Do not run ESLint on these paths. Customize as needed. +build/ +dist/ +out/ diff --git a/backend/.eslintrc.json b/backend/.eslintrc.json new file mode 100644 index 0000000..80520d3 --- /dev/null +++ b/backend/.eslintrc.json @@ -0,0 +1,84 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/strict-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked", + "plugin:import/recommended", + "plugin:import/typescript", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module", + "project": true + }, + "plugins": ["@typescript-eslint", "import"], + "rules": { + // From https://github.com/TritonSE/linters/blob/main/backend.eslintrc.json + + // Avoid bugs. + "@typescript-eslint/no-shadow": ["error", { "ignoreTypeValueShadow": true }], + "@typescript-eslint/no-unsafe-unary-minus": "error", + "@typescript-eslint/no-unused-expressions": "error", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ], + "@typescript-eslint/switch-exhaustiveness-check": "error", + "array-callback-return": "error", + "eqeqeq": "error", + "no-await-in-loop": "error", + "no-constant-binary-expression": "error", + "no-constructor-return": "error", + "no-constant-condition": [ + "error", + { + "checkLoops": false + } + ], + "no-promise-executor-return": "error", + "no-self-compare": "error", + "no-template-curly-in-string": "error", + + // Stylistic. + "@typescript-eslint/consistent-type-definitions": ["warn", "type"], + "@typescript-eslint/no-use-before-define": "warn", + "@typescript-eslint/prefer-readonly": "warn", + "@typescript-eslint/prefer-regexp-exec": "warn", + "object-shorthand": ["warn", "properties"], + "sort-imports": ["warn", { "ignoreDeclarationSort": true }], + "import/consistent-type-specifier-style": ["warn", "prefer-top-level"], + "import/order": [ + "warn", + { + "alphabetize": { "order": "asc" }, + "groups": ["builtin", "external", "parent", "sibling", "index", "object", "type"], + "newlines-between": "always" + } + ], + "import/no-named-as-default-member": "off", + + // Disabled because of too many false positives. + "@typescript-eslint/no-unnecessary-condition": "off" + }, + "settings": { + // These settings are required for ESLint to resolve import paths. See + // https://stackoverflow.com/questions/55198502/using-eslint-with-typescript-unable-to-resolve-path-to-module + // and https://github.com/import-js/eslint-import-resolver-typescript + "import/parsers": { + "@typescript-eslint/parser": [".ts", ".tsx"] + }, + "import/resolver": { + "typescript": {} + } + } +} diff --git a/backend/.prettierignore b/backend/.prettierignore new file mode 100644 index 0000000..ce9a427 --- /dev/null +++ b/backend/.prettierignore @@ -0,0 +1,4 @@ +# Do not run Prettier on these paths. Customize as needed. +build/ +dist/ +out/ diff --git a/backend/__tests__/exampleTest.test.ts b/backend/__tests__/exampleTest.test.ts index de048a1..ee5dd0a 100644 --- a/backend/__tests__/exampleTest.test.ts +++ b/backend/__tests__/exampleTest.test.ts @@ -1,8 +1,9 @@ +import { MongoClient } from "mongodb"; +import { MongoMemoryServer } from "mongodb-memory-server"; +import mongoose from "mongoose"; import request from "supertest"; + import app from "src/app"; -import mongoose from "mongoose"; -import { MongoMemoryServer } from "mongodb-memory-server"; -import { MongoClient } from "mongodb"; const TEST_DB_NAME = "test"; diff --git a/backend/package-lock.json b/backend/package-lock.json index edb97c2..8275912 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -31,18 +31,18 @@ "@types/jest": "^29.5.10", "@types/nodemailer": "^6.4.14", "@types/supertest": "^2.0.16", - "@typescript-eslint/eslint-plugin": "^6.1.0", - "@typescript-eslint/parser": "^6.1.0", - "eslint": "^8.45.0", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "eslint": "^8.56.0", "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-import": "^2.27.5", + "eslint-config-prettier": "^8.10.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-no-relative-import-paths": "^1.5.2", "eslint-plugin-react": "^7.33.0", "husky": "^8.0.3", "nodemon": "^3.0.1", - "prettier": "^3.0.0", + "prettier": "^3.1.1", "ts-node": "^10.9.1", "typescript": "5.1.6" } @@ -1522,9 +1522,10 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.3", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -1544,9 +1545,10 @@ } }, "node_modules/@eslint/js": { - "version": "8.54.0", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, - "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -3231,13 +3233,15 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true }, "node_modules/@types/json5": { "version": "0.0.29", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true }, "node_modules/@types/jsonwebtoken": { "version": "9.0.5", @@ -3313,8 +3317,9 @@ }, "node_modules/@types/semver": { "version": "7.5.6", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true }, "node_modules/@types/send": { "version": "0.17.4", @@ -3378,15 +3383,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.12.0", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.14.0.tgz", + "integrity": "sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.12.0", - "@typescript-eslint/type-utils": "6.12.0", - "@typescript-eslint/utils": "6.12.0", - "@typescript-eslint/visitor-keys": "6.12.0", + "@typescript-eslint/scope-manager": "6.14.0", + "@typescript-eslint/type-utils": "6.14.0", + "@typescript-eslint/utils": "6.14.0", + "@typescript-eslint/visitor-keys": "6.14.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -3412,14 +3418,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.12.0", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.14.0.tgz", + "integrity": "sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "6.12.0", - "@typescript-eslint/types": "6.12.0", - "@typescript-eslint/typescript-estree": "6.12.0", - "@typescript-eslint/visitor-keys": "6.12.0", + "@typescript-eslint/scope-manager": "6.14.0", + "@typescript-eslint/types": "6.14.0", + "@typescript-eslint/typescript-estree": "6.14.0", + "@typescript-eslint/visitor-keys": "6.14.0", "debug": "^4.3.4" }, "engines": { @@ -3439,12 +3446,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.12.0", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.14.0.tgz", + "integrity": "sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.12.0", - "@typescript-eslint/visitor-keys": "6.12.0" + "@typescript-eslint/types": "6.14.0", + "@typescript-eslint/visitor-keys": "6.14.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3455,12 +3463,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.12.0", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.14.0.tgz", + "integrity": "sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "6.12.0", - "@typescript-eslint/utils": "6.12.0", + "@typescript-eslint/typescript-estree": "6.14.0", + "@typescript-eslint/utils": "6.14.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -3481,9 +3490,10 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.12.0", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.14.0.tgz", + "integrity": "sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA==", "dev": true, - "license": "MIT", "engines": { "node": "^16.0.0 || >=18.0.0" }, @@ -3493,12 +3503,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.12.0", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.14.0.tgz", + "integrity": "sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "6.12.0", - "@typescript-eslint/visitor-keys": "6.12.0", + "@typescript-eslint/types": "6.14.0", + "@typescript-eslint/visitor-keys": "6.14.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3519,16 +3530,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.12.0", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.14.0.tgz", + "integrity": "sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.12.0", - "@typescript-eslint/types": "6.12.0", - "@typescript-eslint/typescript-estree": "6.12.0", + "@typescript-eslint/scope-manager": "6.14.0", + "@typescript-eslint/types": "6.14.0", + "@typescript-eslint/typescript-estree": "6.14.0", "semver": "^7.5.4" }, "engines": { @@ -3543,11 +3555,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.12.0", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.14.0.tgz", + "integrity": "sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/types": "6.14.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -3630,8 +3643,9 @@ }, "node_modules/ajv": { "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3743,8 +3757,9 @@ }, "node_modules/array-union": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -4583,8 +4598,9 @@ }, "node_modules/dir-glob": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, - "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -4668,6 +4684,19 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/ent": { "version": "2.2.0", "license": "MIT", @@ -4906,14 +4935,15 @@ } }, "node_modules/eslint": { - "version": "8.54.0", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.54.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -4985,24 +5015,11 @@ "semver": "bin/semver.js" } }, - "node_modules/eslint-config-airbnb-typescript": { - "version": "17.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-config-airbnb-base": "^15.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", - "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3" - } - }, "node_modules/eslint-config-prettier": { "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", + "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", "dev": true, - "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -5028,6 +5045,31 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, "node_modules/eslint-module-utils": { "version": "2.8.0", "dev": true, @@ -5053,9 +5095,10 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, - "license": "MIT", "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -5073,7 +5116,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -5403,8 +5446,9 @@ }, "node_modules/fast-glob": { "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, - "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -5418,8 +5462,9 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -5826,6 +5871,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "license": "ISC", @@ -5856,9 +5913,10 @@ } }, "node_modules/globals": { - "version": "13.23.0", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -5885,8 +5943,9 @@ }, "node_modules/globby": { "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, - "license": "MIT", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -6151,8 +6210,9 @@ }, "node_modules/husky": { "version": "8.0.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz", + "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", "dev": true, - "license": "MIT", "bin": { "husky": "lib/bin.js" }, @@ -6188,8 +6248,9 @@ }, "node_modules/import-fresh": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, - "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -7188,8 +7249,9 @@ }, "node_modules/js-yaml": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -7270,8 +7332,9 @@ }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -7280,8 +7343,9 @@ }, "node_modules/json5": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "license": "MIT", "dependencies": { "minimist": "^1.2.0" }, @@ -7634,8 +7698,9 @@ }, "node_modules/merge2": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 8" } @@ -8288,8 +8353,9 @@ }, "node_modules/parent-module": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -8351,8 +8417,9 @@ }, "node_modules/path-type": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, - "license": "MIT", "engines": { "node": ">=8" } @@ -8445,9 +8512,10 @@ } }, "node_modules/prettier": { - "version": "3.1.0", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", "dev": true, - "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -8813,12 +8881,22 @@ }, "node_modules/resolve-from": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.2", "license": "MIT", @@ -9291,8 +9369,9 @@ }, "node_modules/strip-bom": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -9384,6 +9463,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/tar-stream": { "version": "3.1.6", "license": "MIT", @@ -9594,9 +9682,10 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, - "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", @@ -9628,8 +9717,9 @@ }, "node_modules/type-fest": { "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -9801,8 +9891,9 @@ }, "node_modules/uri-js": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } diff --git a/backend/package.json b/backend/package.json index 559d1db..c3de21e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,10 +5,11 @@ "scripts": { "start": "nodemon src/server.ts", "test": "jest", - "format": "prettier --write .", - "lint-fix": "(eslint --fix --cache --report-unused-disable-directives . || true) && prettier --write .", - "lint-check": "eslint --cache --report-unused-disable-directives --max-warnings 0 . && prettier --check .", - "prepare": "cd .. && husky install .husky" + "format": "npm run check-git-hooks && prettier --write .", + "lint-fix": "npm run check-git-hooks && (eslint --fix --cache --report-unused-disable-directives . || true) && prettier --write .", + "lint-check": "npm run check-git-hooks && eslint --cache --report-unused-disable-directives . && prettier --check .", + "prepare": "cd .. && husky install .husky", + "check-git-hooks": "cd .. && node .secret-scan/secret-scan.js -- --check-git-hooks" }, "dependencies": { "cors": "^2.8.5", @@ -34,18 +35,18 @@ "@types/jest": "^29.5.10", "@types/nodemailer": "^6.4.14", "@types/supertest": "^2.0.16", - "@typescript-eslint/eslint-plugin": "^6.1.0", - "@typescript-eslint/parser": "^6.1.0", - "eslint": "^8.45.0", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "eslint": "^8.56.0", "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-import": "^2.27.5", + "eslint-config-prettier": "^8.10.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-no-relative-import-paths": "^1.5.2", "eslint-plugin-react": "^7.33.0", "husky": "^8.0.3", "nodemon": "^3.0.1", - "prettier": "^3.0.0", + "prettier": "^3.1.1", "ts-node": "^10.9.1", "typescript": "5.1.6" }, diff --git a/backend/src/app.ts b/backend/src/app.ts index 2f5c0ea..aba9a1a 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -3,8 +3,8 @@ */ import "dotenv/config"; -import express, { NextFunction, Request, Response } from "express"; import cors from "cors"; +import express, { NextFunction, Request, Response } from "express"; import { isHttpError } from "http-errors"; const app = express(); @@ -29,10 +29,8 @@ app.use( * Error handler; all errors thrown by server are handled here. * Explicit typings required here because TypeScript cannot infer the argument types. */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars app.use((error: unknown, req: Request, res: Response, next: NextFunction) => { - // Don't use "next" argument - next; - // 500 is the "internal server error" error code, this will be our fallback let statusCode = 500; let errorMessage = "An error has occurred."; diff --git a/backend/src/server.ts b/backend/src/server.ts index 5666a64..b4e8e25 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -4,6 +4,7 @@ import "module-alias/register"; import mongoose from "mongoose"; + import app from "src/app"; import env from "src/util/validateEnv"; diff --git a/backend/src/util/validateEnv.ts b/backend/src/util/validateEnv.ts index 872dd7c..6d04f81 100644 --- a/backend/src/util/validateEnv.ts +++ b/backend/src/util/validateEnv.ts @@ -4,7 +4,7 @@ */ import { cleanEnv } from "envalid"; -import { port, str, email, json } from "envalid/dist/validators"; +import { email, json, port, str } from "envalid/dist/validators"; export default cleanEnv(process.env, { PORT: port(), // Port to run backend on diff --git a/frontend/.env.example b/frontend/.env.example index 444c617..12eba19 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,6 +1,4 @@ -# Example environment variables for frontend. Any "xxx"'s should be +# Example environment variables for frontend. Any ""s should be # replaced with the real values. NEXT_PUBLIC_BACKEND_URL="http://localhost:3001" -NEXT_PUBLIC_FIREBASE_SETTINGS='{ - "xxx": "xxx" -}' +NEXT_PUBLIC_FIREBASE_SETTINGS='' diff --git a/frontend/.eslintignore b/frontend/.eslintignore index 0f55811..a5ae024 100644 --- a/frontend/.eslintignore +++ b/frontend/.eslintignore @@ -1,2 +1,7 @@ -.eslintrc.cjs +# Do not run ESLint on these paths. Customize as needed. +.next/ +build/ +dist/ +out/ +public/ next.config.js diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json new file mode 100644 index 0000000..22166f6 --- /dev/null +++ b/frontend/.eslintrc.json @@ -0,0 +1,66 @@ +{ + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/strict-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked", + "plugin:import/recommended", + "plugin:import/typescript", + "prettier", + "next/core-web-vitals" + ], + "parserOptions": { + "project": true + }, + "rules": { + // From https://github.com/TritonSE/linters/blob/main/nextjs.eslintrc.json + + // Avoid bugs. + "@typescript-eslint/no-shadow": ["error", { "ignoreTypeValueShadow": true }], + "@typescript-eslint/no-unsafe-unary-minus": "error", + "@typescript-eslint/no-unused-expressions": "error", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ], + "@typescript-eslint/switch-exhaustiveness-check": "error", + "array-callback-return": "error", + "eqeqeq": "error", + "no-await-in-loop": "error", + "no-constant-binary-expression": "error", + "no-constructor-return": "error", + "no-constant-condition": [ + "error", + { + "checkLoops": false + } + ], + "no-promise-executor-return": "error", + "no-self-compare": "error", + "no-template-curly-in-string": "error", + + // Stylistic. + "@typescript-eslint/consistent-type-definitions": ["warn", "type"], + "@typescript-eslint/no-use-before-define": "warn", + "@typescript-eslint/prefer-readonly": "warn", + "@typescript-eslint/prefer-regexp-exec": "warn", + "object-shorthand": ["warn", "properties"], + "sort-imports": ["warn", { "ignoreDeclarationSort": true }], + "import/consistent-type-specifier-style": ["warn", "prefer-top-level"], + "import/order": [ + "warn", + { + "alphabetize": { "order": "asc" }, + "groups": ["builtin", "external", "parent", "sibling", "index", "object", "type"], + "newlines-between": "always" + } + ], + + // Disabled because of too many false positives. + "@typescript-eslint/no-unnecessary-condition": "off", + "react-hooks/exhaustive-deps": "off" + } +} diff --git a/frontend/.prettierignore b/frontend/.prettierignore index 7b04241..2e471db 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -1,2 +1,6 @@ -.eslintrc.cjs -.next +# Do not run Prettier on these paths. Customize as needed. +.next/ +build/ +dist/ +out/ +public/ diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts index 4f11a03..fd36f94 100644 --- a/frontend/next-env.d.ts +++ b/frontend/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9c6013e..174d6f4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,17 +25,18 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", "eslint": "^8.54.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-import": "^2.29.0", + "eslint-config-next": "^14.0.4", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-no-relative-import-paths": "^1.5.3", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "husky": "^8.0.3", - "prettier": "^3.0.0", + "prettier": "^3.1.1", "typescript": "~5.2.0" } }, @@ -1719,9 +1720,9 @@ "integrity": "sha512-7xRqh9nMvP5xrW4/+L0jgRRX+HoNRGnfJpD+5Wq6/13j3dsdzxO3BCXn7D3hMqsDb+vjZnJq+vI7+EtgrYZTeA==" }, "node_modules/@next/eslint-plugin-next": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.0.3.tgz", - "integrity": "sha512-j4K0n+DcmQYCVnSAM+UByTVfIHnYQy2ODozfQP+4RdwtRDfobrIvKq1K4Exb2koJ79HSSa7s6B2SA8T/1YR3RA==", + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.0.4.tgz", + "integrity": "sha512-U3qMNHmEZoVmHA0j/57nRfi3AscXNvkOnxDmle/69Jz/G0o/gWjXTDdlgILZdrxQ0Lw/jv2mPW8PGy0EGIHXhQ==", "dev": true, "dependencies": { "glob": "7.1.7" @@ -1951,6 +1952,12 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.6.1.tgz", + "integrity": "sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==", + "dev": true + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -2207,8 +2214,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/json5": { "version": "0.0.29", @@ -2256,8 +2262,7 @@ "version": "7.5.6", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/stack-utils": { "version": "2.0.3", @@ -2283,17 +2288,16 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.12.0.tgz", - "integrity": "sha512-XOpZ3IyJUIV1b15M7HVOpgQxPPF7lGXgsfcEIu3yDxFPaf/xZKt7s9QO/pbk7vpWQyVulpJbu4E5LwpZiQo4kA==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.14.0.tgz", + "integrity": "sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.12.0", - "@typescript-eslint/type-utils": "6.12.0", - "@typescript-eslint/utils": "6.12.0", - "@typescript-eslint/visitor-keys": "6.12.0", + "@typescript-eslint/scope-manager": "6.14.0", + "@typescript-eslint/type-utils": "6.14.0", + "@typescript-eslint/utils": "6.14.0", + "@typescript-eslint/visitor-keys": "6.14.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -2319,16 +2323,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.12.0.tgz", - "integrity": "sha512-s8/jNFPKPNRmXEnNXfuo1gemBdVmpQsK1pcu+QIvuNJuhFzGrpD7WjOcvDc/+uEdfzSYpNu7U/+MmbScjoQ6vg==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.14.0.tgz", + "integrity": "sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==", "dev": true, - "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.12.0", - "@typescript-eslint/types": "6.12.0", - "@typescript-eslint/typescript-estree": "6.12.0", - "@typescript-eslint/visitor-keys": "6.12.0", + "@typescript-eslint/scope-manager": "6.14.0", + "@typescript-eslint/types": "6.14.0", + "@typescript-eslint/typescript-estree": "6.14.0", + "@typescript-eslint/visitor-keys": "6.14.0", "debug": "^4.3.4" }, "engines": { @@ -2348,14 +2351,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.12.0.tgz", - "integrity": "sha512-5gUvjg+XdSj8pcetdL9eXJzQNTl3RD7LgUiYTl8Aabdi8hFkaGSYnaS6BLc0BGNaDH+tVzVwmKtWvu0jLgWVbw==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.14.0.tgz", + "integrity": "sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg==", "dev": true, - "peer": true, "dependencies": { - "@typescript-eslint/types": "6.12.0", - "@typescript-eslint/visitor-keys": "6.12.0" + "@typescript-eslint/types": "6.14.0", + "@typescript-eslint/visitor-keys": "6.14.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2366,14 +2368,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.12.0.tgz", - "integrity": "sha512-WWmRXxhm1X8Wlquj+MhsAG4dU/Blvf1xDgGaYCzfvStP2NwPQh6KBvCDbiOEvaE0filhranjIlK/2fSTVwtBng==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.14.0.tgz", + "integrity": "sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw==", "dev": true, - "peer": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.12.0", - "@typescript-eslint/utils": "6.12.0", + "@typescript-eslint/typescript-estree": "6.14.0", + "@typescript-eslint/utils": "6.14.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -2394,11 +2395,10 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.12.0.tgz", - "integrity": "sha512-MA16p/+WxM5JG/F3RTpRIcuOghWO30//VEOvzubM8zuOOBYXsP+IfjoCXXiIfy2Ta8FRh9+IO9QLlaFQUU+10Q==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.14.0.tgz", + "integrity": "sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA==", "dev": true, - "peer": true, "engines": { "node": "^16.0.0 || >=18.0.0" }, @@ -2408,14 +2408,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.12.0.tgz", - "integrity": "sha512-vw9E2P9+3UUWzhgjyyVczLWxZ3GuQNT7QpnIY3o5OMeLO/c8oHljGc8ZpryBMIyympiAAaKgw9e5Hl9dCWFOYw==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.14.0.tgz", + "integrity": "sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw==", "dev": true, - "peer": true, "dependencies": { - "@typescript-eslint/types": "6.12.0", - "@typescript-eslint/visitor-keys": "6.12.0", + "@typescript-eslint/types": "6.14.0", + "@typescript-eslint/visitor-keys": "6.14.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2436,18 +2435,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.12.0.tgz", - "integrity": "sha512-LywPm8h3tGEbgfyjYnu3dauZ0U7R60m+miXgKcZS8c7QALO9uWJdvNoP+duKTk2XMWc7/Q3d/QiCuLN9X6SWyQ==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.14.0.tgz", + "integrity": "sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg==", "dev": true, - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.12.0", - "@typescript-eslint/types": "6.12.0", - "@typescript-eslint/typescript-estree": "6.12.0", + "@typescript-eslint/scope-manager": "6.14.0", + "@typescript-eslint/types": "6.14.0", + "@typescript-eslint/typescript-estree": "6.14.0", "semver": "^7.5.4" }, "engines": { @@ -2462,13 +2460,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.12.0.tgz", - "integrity": "sha512-rg3BizTZHF1k3ipn8gfrzDXXSFKyOEB5zxYXInQ6z0hUvmQlhaZQzK+YmHmNViMA9HzW5Q9+bPPt90bU6GQwyw==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.14.0.tgz", + "integrity": "sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw==", "dev": true, - "peer": true, "dependencies": { - "@typescript-eslint/types": "6.12.0", + "@typescript-eslint/types": "6.14.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -2663,7 +2660,6 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -3160,12 +3156,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3430,7 +3420,6 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, - "peer": true, "dependencies": { "path-type": "^4.0.0" }, @@ -3497,6 +3486,19 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -3756,74 +3758,36 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-airbnb": { - "version": "19.0.4", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", - "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "node_modules/eslint-config-next": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.0.4.tgz", + "integrity": "sha512-9/xbOHEQOmQtqvQ1UsTQZpnA7SlDMBtuKJ//S4JnoyK3oGLhILKXdBgu/UO7lQo/2xOykQULS1qQ6p2+EpHgAQ==", "dev": true, "dependencies": { - "eslint-config-airbnb-base": "^15.0.0", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5" - }, - "engines": { - "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.28.0", - "eslint-plugin-react-hooks": "^4.3.0" - } - }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", - "dev": true, - "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "@next/eslint-plugin-next": "14.0.4", + "@rushstack/eslint-patch": "^1.3.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" }, "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" - } - }, - "node_modules/eslint-config-airbnb-base/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-config-airbnb-typescript": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", - "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", - "dev": true, - "dependencies": { - "eslint-config-airbnb-base": "^15.0.0" + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", - "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", - "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -3852,6 +3816,31 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, "node_modules/eslint-module-utils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", @@ -3879,9 +3868,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { "array-includes": "^3.1.7", @@ -3900,7 +3889,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -4208,7 +4197,6 @@ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, - "peer": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -4225,7 +4213,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "peer": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -4502,6 +4489,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", + "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -4573,7 +4572,6 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, - "peer": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -6172,7 +6170,6 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "peer": true, "engines": { "node": ">= 8" } @@ -6649,7 +6646,6 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -6774,9 +6770,9 @@ } }, "node_modules/prettier": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", - "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -7055,6 +7051,15 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", @@ -7539,6 +7544,15 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -7606,7 +7620,6 @@ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", "dev": true, - "peer": true, "engines": { "node": ">=16.13.0" }, @@ -7668,9 +7681,9 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", diff --git a/frontend/package.json b/frontend/package.json index 47fb9a7..98311d9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,10 +7,11 @@ "build": "next build", "start": "next start", "test": "jest", - "format": "prettier --write .", - "lint-fix": "(eslint --fix --cache --report-unused-disable-directives . || true) && prettier --write .", - "lint-check": "eslint --cache --report-unused-disable-directives --max-warnings 0 . && prettier --check .", - "prepare": "cd .. && husky install .husky" + "format": "npm run check-git-hooks && prettier --write .", + "lint-fix": "npm run check-git-hooks && (eslint --fix --cache --report-unused-disable-directives . || true) && prettier --write .", + "lint-check": "npm run check-git-hooks && eslint --cache --report-unused-disable-directives . && prettier --check .", + "prepare": "cd .. && husky install .husky", + "check-git-hooks": "cd .. && node .secret-scan/secret-scan.js -- --check-git-hooks" }, "dependencies": { "@testing-library/jest-dom": "^6.1.4", @@ -30,17 +31,18 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", "eslint": "^8.54.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-config-airbnb-typescript": "^17.1.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-import": "^2.29.0", + "eslint-config-next": "^14.0.4", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-no-relative-import-paths": "^1.5.3", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "husky": "^8.0.3", - "prettier": "^3.0.0", + "prettier": "^3.1.1", "typescript": "~5.2.0" } } diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index bff822e..4b0ee04 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,5 +1,6 @@ -import type { Metadata } from "next"; import { Inter } from "next/font/google"; + +import type { Metadata } from "next"; import "@/app/globals.css"; const inter = Inter({ subsets: ["latin"] }); diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index c836649..94bf4de 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,4 +1,5 @@ import Image from "next/image"; + import styles from "@/app/page.module.css"; export default function Home() { diff --git a/frontend/src/util/validateEnv.ts b/frontend/src/util/validateEnv.ts index 1365a76..3d16d63 100644 --- a/frontend/src/util/validateEnv.ts +++ b/frontend/src/util/validateEnv.ts @@ -4,7 +4,7 @@ */ import { cleanEnv } from "envalid"; -import { str, json } from "envalid/dist/validators"; +import { json, str } from "envalid/dist/validators"; /** * Note that in NextJS, environment variables' names must start with